myagent-ai 1.15.55 → 1.15.57

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/main.py CHANGED
@@ -39,7 +39,7 @@ from core.config_broadcast import ConfigBroadcaster, ReloadType
39
39
  from core.update_manager import UpdateManager, UpdateType
40
40
  from core.version import get_version
41
41
  from core.permissions import PermissionManager
42
- from core.deps_checker import check_and_install_deps
42
+ from core.deps_checker import check_and_install_deps # 保留导入供其他模块使用
43
43
 
44
44
 
45
45
  def _get_screen_resolution() -> tuple[int, int]:
@@ -154,21 +154,8 @@ class MyAgentApp:
154
154
  self.logger.info(f"数据目录: {self.config_mgr.data_dir}")
155
155
  self.logger.info("=" * 60)
156
156
 
157
- # 1.5 自动检测并安装缺失依赖(开箱即用)
158
- # 可通过 --skip-deps 参数或 MYAGENT_SKIP_DEPS=1 环境变量跳过
159
- if os.environ.get("MYAGENT_SKIP_DEPS") == "1":
160
- self.logger.info("跳过依赖检查(--skip-deps)")
161
- else:
162
- self.logger.info("检查依赖...")
163
- deps_result = check_and_install_deps(auto_fix=True, silent=False)
164
- if deps_result["installed"] > 0:
165
- self.logger.info(
166
- f"自动安装了 {deps_result['installed']} 个依赖"
167
- )
168
- if deps_result["failed"] > 0:
169
- self.logger.warning(
170
- f"{deps_result['failed']} 个依赖安装失败,相关功能可能不可用"
171
- )
157
+ # 1.5 依赖检查已移至 start.js (install/reinstall 命令)
158
+ # 启动时不再检查依赖,确保启动速度干净利落
172
159
 
173
160
  # 2. LLM 客户端
174
161
  llm_cfg = self.config.llm
@@ -692,26 +679,8 @@ def create_tray_icon(app: MyAgentApp, web_port: int = 8767):
692
679
  from PIL import Image, ImageDraw, ImageFont
693
680
  except Exception:
694
681
  # pystray 可能因缺少 GUI 环境 (X11/Wayland) 或未安装而失败
695
- if os.environ.get('MYAGENT_SKIP_DEPS') == '1':
696
- logger.info("pystray/PIL 未安装,跳过自动安装(--skip-deps)")
697
- return None
698
- try:
699
- from core.deps_checker import _pip_install
700
- logger.info("pystray/PIL 未安装,正在自动安装...")
701
- success, msg = _pip_install(["pystray>=0.19.5", "Pillow>=10.0.0"])
702
- if success:
703
- logger.info(f"pystray/PIL 自动安装成功: {msg}")
704
- import importlib
705
- import pystray as _pystray
706
- importlib.reload(_pystray)
707
- import pystray
708
- from PIL import Image, ImageDraw, ImageFont
709
- else:
710
- logger.warning(f"pystray/PIL 自动安装失败: {msg}")
711
- return None
712
- except Exception as e:
713
- logger.warning(f"pystray 初始化失败 (可能缺少 GUI 环境): {e}")
714
- return None
682
+ logger.info("pystray 不可用(未安装或缺少 GUI 环境),跳过系统托盘")
683
+ return None
715
684
 
716
685
  # ── 图标颜色主题 ──
717
686
  _COLOR_NORMAL = "#4A90D9" # 蓝色 = 运行中
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myagent-ai",
3
- "version": "1.15.55",
3
+ "version": "1.15.57",
4
4
  "description": "本地桌面端执行型AI助手 - Open Interpreter 风格 | Local Desktop Execution-Oriented AI Assistant",
5
5
  "main": "main.py",
6
6
  "bin": {
package/start.js CHANGED
@@ -2,22 +2,10 @@
2
2
  /**
3
3
  * start.js - 跨平台入口脚本 (Windows/macOS/Linux)
4
4
  * ================================================
5
- * npm bin 入口,自动管理独立虚拟环境:
6
- * - 虚拟环境位置: ~/.myagent/venv (与其他项目完全隔离)
7
- * - 首次运行自动创建 venv 并安装所有依赖
8
- * - 后续运行直接使用 venv,无需重复安装
9
- * - pip 升级/重装: myagent-ai reinstall
10
- *
11
- * 用法:
12
- * myagent-ai # 交互式选择运行模式
13
- * myagent-ai web # Web 管理后台
14
- * myagent-ai cli # CLI 模式
15
- * myagent-ai tray # 系统托盘模式
16
- * myagent-ai server # API 服务模式
17
- * myagent-ai setup # 配置向导
18
- * myagent-ai reinstall # 重新安装依赖到 venv
19
- * myagent-ai uninstall # 卸载 MyAgent(删除npm包+数据)
20
- * myagent-ai --skip-deps # 跳过依赖检查,直接启动
5
+ * 职责分明:
6
+ * - install/reinstall: 创建 venv + 安装所有依赖
7
+ * - web/cli/tray/server: 直接启动,不检查依赖
8
+ * - uninstall: 卸载
21
9
  */
22
10
  "use strict";
23
11
 
@@ -26,35 +14,19 @@ const path = require("path");
26
14
  const fs = require("fs");
27
15
  const os = require("os");
28
16
 
29
- // ── 常量 ──────────────────────────────────────────────────
30
17
  const IS_WIN = process.platform === "win32";
18
+ const PKG_NAME = "myagent-ai";
31
19
 
32
- // 虚拟环境目录
33
- function getVenvDir() {
34
- const homeDir = os.homedir();
35
- return path.join(homeDir, ".myagent", "venv");
36
- }
37
-
38
- // venv 中 Python 可执行文件的路径
39
- function getVenvPython(venvDir) {
40
- return IS_WIN
41
- ? path.join(venvDir, "Scripts", "python.exe")
42
- : path.join(venvDir, "bin", "python");
43
- }
20
+ // ── 路径工具 ──────────────────────────────────────────────
44
21
 
45
- // venv pip 可执行文件的路径
46
- function getVenvPip(venvDir) {
47
- return IS_WIN
48
- ? path.join(venvDir, "Scripts", "pip.exe")
49
- : path.join(venvDir, "bin", "pip");
22
+ function getHome() { return os.homedir(); }
23
+ function getDataDir() { return path.join(getHome(), ".myagent"); }
24
+ function getVenvDir() { return path.join(getDataDir(), "venv"); }
25
+ function getSentinelFile() { return path.join(getDataDir(), ".deps_installed"); }
26
+ function getVenvPython(d) {
27
+ return IS_WIN ? path.join(d, "Scripts", "python.exe") : path.join(d, "bin", "python");
50
28
  }
51
29
 
52
- // MyAgent 数据目录
53
- function getDataDir() {
54
- return path.join(os.homedir(), ".myagent");
55
- }
56
-
57
- // ── 解析包目录 ────────────────────────────────────────────
58
30
  function resolvePackageDir() {
59
31
  let dir = __dirname;
60
32
  if (!fs.existsSync(path.join(dir, "main.py"))) {
@@ -62,8 +34,8 @@ function resolvePackageDir() {
62
34
  const npmRoot = execSync("npm root -g", {
63
35
  encoding: "utf8", stdio: ["pipe", "pipe", "pipe"],
64
36
  }).trim();
65
- const candidate = path.join(npmRoot, "myagent-ai");
66
- if (fs.existsSync(path.join(candidate, "main.py"))) dir = candidate;
37
+ const c = path.join(npmRoot, PKG_NAME);
38
+ if (fs.existsSync(path.join(c, "main.py"))) dir = c;
67
39
  } catch (_) {}
68
40
  }
69
41
  if (!fs.existsSync(path.join(dir, "main.py"))) {
@@ -76,599 +48,356 @@ function resolvePackageDir() {
76
48
  return dir;
77
49
  }
78
50
 
79
- // ── 查找系统 Python(用于创建 venv) ─────────────────────
80
51
  function findSystemPython() {
81
52
  const candidates = IS_WIN
82
53
  ? ["python", "python3", "python3.14", "python3.13"]
83
54
  : ["python3", "python3.14", "python3.13", "python"];
84
-
85
55
  for (const cmd of candidates) {
86
56
  try {
87
- const result = execFileSync(
88
- IS_WIN ? `${cmd}.exe` : cmd,
89
- ["--version"],
57
+ const r = execFileSync(
58
+ IS_WIN ? `${cmd}.exe` : cmd, ["--version"],
90
59
  { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 5000 }
91
60
  );
92
- const match = result.match(/Python (\d+)\.(\d+)/);
93
- if (match) {
94
- const major = parseInt(match[1], 10);
95
- const minor = parseInt(match[2], 10);
96
- if (major >= 3 && minor >= 13) return cmd;
97
- }
61
+ const m = r.match(/Python (\d+)\.(\d+)/);
62
+ if (m && parseInt(m[1]) >= 3 && parseInt(m[2]) >= 13) return cmd;
98
63
  } catch (_) {}
99
64
  }
100
-
101
- // Windows: 搜索常见安装路径
102
65
  if (IS_WIN) {
103
66
  const localAppData = process.env.LOCALAPPDATA || "";
104
67
  const programFiles = process.env.ProgramFiles || "";
105
- const programFilesX86 = process.env["ProgramFiles(x86)"] || "";
106
- const searchPaths = [
68
+ for (const p of [
107
69
  path.join(localAppData, "Programs", "Python", "Python314", "python.exe"),
108
70
  path.join(localAppData, "Programs", "Python", "Python313", "python.exe"),
109
71
  path.join(programFiles, "Python314", "python.exe"),
110
72
  path.join(programFiles, "Python313", "python.exe"),
111
73
  "C:\\Python314\\python.exe", "C:\\Python313\\python.exe",
112
- ];
113
- for (const pyPath of searchPaths) {
114
- if (fs.existsSync(pyPath)) return pyPath;
74
+ ]) {
75
+ if (fs.existsSync(p)) return p;
115
76
  }
116
77
  }
117
-
118
78
  return null;
119
79
  }
120
80
 
121
- // ── 虚拟环境管理 ─────────────────────────────────────────
122
-
123
- /**
124
- * 尝试自动安装 python3.xx-venv 模块 (Debian/Ubuntu)
125
- * Returns: true if successfully installed
126
- */
127
- function tryAutoInstallVenvModule(systemPython) {
128
- try {
129
- // 先检测是否是 apt 系统
130
- execFileSync("which", ["apt-get"], {
131
- encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 5000,
132
- });
133
- } catch (_) {
134
- // 不是 apt 系统
135
- return false;
81
+ function getPipMirrorArgs() {
82
+ const envMirror = process.env.MYAGENT_PIP_MIRROR || "";
83
+ if (envMirror === "1" || envMirror === "true" || envMirror === "cn") {
84
+ return ["-i", "https://pypi.tuna.tsinghua.edu.cn/simple", "--trusted-host", "pypi.tuna.tsinghua.edu.cn"];
136
85
  }
137
-
138
- try {
139
- // 获取 Python 版本号
140
- const ver = execFileSync(systemPython, ["-c", "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')"], {
141
- encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 5000,
142
- }).trim();
143
-
144
- console.log(` \x1b[90m[i]\x1b[0m 正在安装 python${ver}-venv (需要 sudo 权限)...`);
145
-
146
- execFileSync("sudo", ["apt-get", "install", "-y", `python${ver}-venv`], {
147
- encoding: "utf8", stdio: "inherit", timeout: 120000,
148
- });
149
-
150
- console.log(` \x1b[32m[✓]\x1b[0m python${ver}-venv 已安装`);
151
- return true;
152
- } catch (err) {
153
- console.log(` \x1b[33m[!]\x1b[0m 自动安装 venv 模块失败: ${err.message}`);
154
- console.log(` \x1b[90m[i]\x1b[0m 请手动运行: sudo apt install python3-venv`);
155
- return false;
86
+ const lang = (process.env.LANG || process.env.LC_ALL || "").toLowerCase();
87
+ if (lang.includes("zh_cn") || lang.includes("chinese")) {
88
+ return ["-i", "https://pypi.tuna.tsinghua.edu.cn/simple", "--trusted-host", "pypi.tuna.tsinghua.edu.cn"];
156
89
  }
90
+ return [];
157
91
  }
158
92
 
159
- /**
160
- * 确保 venv 存在且可用
161
- * Returns: { venvDir, venvPython, venvPip }
162
- */
93
+ // ── 虚拟环境 ──────────────────────────────────────────────
94
+
163
95
  function ensureVenv() {
164
96
  const venvDir = getVenvDir();
165
97
  const venvPython = getVenvPython(venvDir);
166
- const venvPip = getVenvPip(venvDir);
167
-
168
- // 检查现有 venv 是否完整
169
98
  if (fs.existsSync(venvPython)) {
170
99
  try {
171
100
  execFileSync(venvPython, ["--version"], {
172
101
  encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 5000,
173
102
  });
174
- return { venvDir, venvPython, venvPip, isNew: false };
103
+ return venvPython;
175
104
  } catch (_) {
176
- // venv 损坏,需要重建
177
- console.log(" \x1b[33m[!]\x1b[0m 虚拟环境已损坏,正在重建...");
178
105
  fs.rmSync(venvDir, { recursive: true, force: true });
179
106
  }
180
107
  }
181
-
182
- // 创建 venv
183
- const systemPython = findSystemPython();
184
- if (!systemPython) {
185
- console.error("\x1b[31m[✗]\x1b[0m 未找到 Python 3.13+");
186
- console.error(" 请先安装 Python: https://www.python.org/downloads/");
187
- console.error(" 安装时请勾选 'Add Python to PATH'");
188
- console.error("");
189
- console.error(" 或一键安装:");
190
- if (IS_WIN) {
191
- console.error(" powershell -c \"irm https://raw.githubusercontent.com/ctz168/myagent/main/install/install.ps1 | iex\"");
192
- } else {
193
- console.error(" curl -fsSL https://raw.githubusercontent.com/ctz168/myagent/main/install/install.sh | bash");
194
- }
108
+ const sysPy = findSystemPython();
109
+ if (!sysPy) {
110
+ console.error("\x1b[31m未找到 Python 3.13+\x1b[0m");
111
+ console.error("请先安装 Python: https://www.python.org/downloads/");
195
112
  process.exit(1);
196
113
  }
197
-
198
- // 确保 ~/.myagent 目录存在
199
114
  const dataDir = getDataDir();
200
- if (!fs.existsSync(dataDir)) {
201
- fs.mkdirSync(dataDir, { recursive: true });
202
- }
203
-
204
- console.log(" \x1b[36m[*]\x1b[0m 创建独立虚拟环境...");
205
- console.log(` \x1b[90m[i]\x1b[0m 位置: ${venvDir}`);
206
- console.log(` \x1b[90m[i]\x1b[0m 系统Python: ${systemPython}`);
115
+ if (!fs.existsSync(dataDir)) fs.mkdirSync(dataDir, { recursive: true });
207
116
 
117
+ console.log(" 创建虚拟环境...");
208
118
  try {
209
- execFileSync(systemPython, ["-m", "venv", venvDir], {
119
+ execFileSync(sysPy, ["-m", "venv", venvDir], {
210
120
  encoding: "utf8", stdio: "inherit", timeout: 60000,
211
121
  });
212
- } catch (err) {
213
- // Debian/Ubuntu python3.xx-venv 需要单独安装
214
- console.log(`\x1b[33m[!]\x1b[0m 虚拟环境创建失败,尝试自动安装 venv 模块...`);
215
-
216
- const autoInstalled = !IS_WIN && tryAutoInstallVenvModule(systemPython);
217
-
218
- if (autoInstalled) {
219
- // 重试创建 venv
122
+ } catch (_) {
123
+ // Debian/Ubuntu: 尝试安装 venv 模块
124
+ if (!IS_WIN) {
220
125
  try {
221
- execFileSync(systemPython, ["-m", "venv", venvDir], {
126
+ execFileSync("sudo", ["apt-get", "install", "-y", "python3-venv"], {
127
+ encoding: "utf8", stdio: "inherit", timeout: 120000,
128
+ });
129
+ execFileSync(sysPy, ["-m", "venv", venvDir], {
222
130
  encoding: "utf8", stdio: "inherit", timeout: 60000,
223
131
  });
224
- } catch (retryErr) {
225
- console.error(`\x1b[31m[✗]\x1b[0m 重试创建虚拟环境仍然失败: ${retryErr.message}`);
132
+ } catch (_) {
133
+ console.error("\x1b[31m创建虚拟环境失败\x1b[0m");
134
+ console.error("请手动运行: sudo apt install python3-venv");
226
135
  process.exit(1);
227
136
  }
228
137
  } else {
229
- console.error(`\x1b[31m[✗]\x1b[0m 创建虚拟环境失败: ${err.message}`);
230
- if (!IS_WIN) {
231
- console.error("");
232
- console.error(" 在 Debian/Ubuntu 上,python3.xx-venv 需要单独安装:");
233
- console.error(" sudo apt install python3-venv");
234
- console.error(" 然后运行: myagent-ai reinstall");
235
- console.error("");
236
- }
138
+ console.error("\x1b[31m创建虚拟环境失败\x1b[0m");
237
139
  process.exit(1);
238
140
  }
239
141
  }
240
-
241
- // 验证创建成功
242
- if (!fs.existsSync(venvPython)) {
243
- console.error("\x1b[31m[✗]\x1b[0m 虚拟环境创建后未找到 Python,请检查系统 Python 安装");
142
+ if (!fs.existsSync(getVenvPython(venvDir))) {
143
+ console.error("\x1b[31m虚拟环境创建失败\x1b[0m");
244
144
  process.exit(1);
245
145
  }
246
-
247
- console.log(" \x1b[32m[✓]\x1b[0m 虚拟环境已创建");
248
- return { venvDir, venvPython, venvPip, isNew: true };
146
+ console.log(" \x1b[32m✓\x1b[0m 虚拟环境已创建");
147
+ return getVenvPython(venvDir);
249
148
  }
250
149
 
251
- // ── 依赖安装 ─────────────────────────────────────────────
150
+ // ── 依赖安装(仅在 install/reinstall/首次运行时调用) ───
252
151
 
253
- // requirements.txt 解析出 Python 模块名(用于 import 检查)
254
- function parseRequirements(reqFile) {
255
- const modules = [];
256
- try {
257
- const lines = fs.readFileSync(reqFile, "utf8").split("\n");
258
- for (const raw of lines) {
259
- const line = raw.trim();
260
- if (!line || line.startsWith("#")) continue;
261
- // 解析: openai>=1.12.0 → openai, python-telegram-bot>=21.0 → telegram
262
- const match = line.match(/^([a-zA-Z0-9_-]+)/);
263
- if (match) {
264
- const pkg = match[1].toLowerCase().replace(/-/g, "_");
265
- // 特殊映射: pip 包名和 import 名不一致的
266
- const importMap = {
267
- "beautifulsoup4": "bs4",
268
- "py_getwindow": "pygetwindow",
269
- "python_telegram_bot": "telegram",
270
- "pillow": "PIL",
271
- "psutil": "psutil",
272
- "edge_tts": "edge_tts",
273
- };
274
- modules.push(importMap[pkg] || pkg);
275
- }
276
- }
277
- } catch (_) {
278
- // 解析失败,回退到核心包列表
279
- modules.push("openai", "aiohttp", "requests");
280
- }
281
- return modules;
282
- }
283
-
284
- // 核心依赖包(fallback 用,不依赖文件解析)
285
- // 包含 tray (pystray/Pillow) 等关键依赖,确保回退安装时也能装上
286
- const CORE_PACKAGES = [
287
- "openai>=1.12.0",
288
- "aiohttp>=3.9.0",
289
- "requests>=2.31.0",
290
- "duckduckgo-search>=6.0.0",
291
- "beautifulsoup4>=4.12.0",
292
- "lxml>=5.0.0",
293
- "psutil>=5.9.0",
294
- "pystray>=0.19.5",
295
- "Pillow>=10.0.0",
296
- "edge-tts>=6.1.0",
297
- "faster-whisper>=1.0.0",
298
- "anthropic>=0.18.0",
299
- ];
300
-
301
- // 获取 pip 镜像源参数(国内用户自动使用清华镜像)
302
- function getPipMirrorArgs() {
303
- const envMirror = process.env.MYAGENT_PIP_MIRROR || "";
304
- if (envMirror === "1" || envMirror === "true" || envMirror === "cn") {
305
- return ["-i", "https://pypi.tuna.tsinghua.edu.cn/simple",
306
- "--trusted-host", "pypi.tuna.tsinghua.edu.cn"];
307
- }
308
- // 通过语言环境判断是否在国内(Windows 通过环境变量,其他通过 LANG)
309
- const lang = (process.env.LANG || process.env.LC_ALL || "").toLowerCase();
310
- if (lang.includes("zh_cn") || lang.includes("chinese")) {
311
- return ["-i", "https://pypi.tuna.tsinghua.edu.cn/simple",
312
- "--trusted-host", "pypi.tuna.tsinghua.edu.cn"];
313
- }
314
- return [];
315
- }
316
-
317
- function installDeps(venvPython, venvPip, pkgDir) {
152
+ function installAllDeps(venvPython, pkgDir) {
318
153
  const reqFile = path.join(pkgDir, "requirements.txt");
319
- const allModules = parseRequirements(reqFile);
320
-
321
- // 快速检查关键模块是否都存在
322
- let missing = [];
323
- console.log(" \x1b[36m[*]\x1b[0m 检查依赖...");
324
-
325
- // 检查所有模块(不超过 30 个),托盘和 GUI 依赖也必须检测
326
- const checkLimit = Math.min(allModules.length, 30);
327
- for (let i = 0; i < checkLimit; i++) {
328
- const mod = allModules[i];
329
- try {
330
- execFileSync(venvPython, ["-c", `import ${mod}`], {
331
- encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 5000,
332
- });
333
- } catch (_) {
334
- missing.push(mod);
335
- }
336
- }
337
-
338
- if (missing.length === 0) {
339
- console.log(` \x1b[32m[✓]\x1b[0m 全部 ${checkLimit} 个依赖检查通过`);
340
- return;
341
- }
342
-
343
- console.log(` \x1b[33m[!]\x1b[0m 缺失 ${missing.length} 个包: ${missing.join(", ")}`);
154
+ const mirrorArgs = getPipMirrorArgs();
155
+ if (mirrorArgs.length) console.log(" 使用国内镜像源 (清华)");
344
156
 
345
- // 策略1: 升级 pip
157
+ // 升级 pip
346
158
  try {
347
- console.log(" \x1b[90m[i]\x1b[0m 升级 pip...");
348
- execFileSync(venvPython, ["-m", "pip", "install", "--upgrade", "pip", "--quiet"], {
159
+ execFileSync(venvPython, ["-m", "pip", "install", "--upgrade", "pip", "-q"], {
349
160
  encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 120000,
350
161
  });
351
162
  } catch (_) {}
352
163
 
353
- // pip 镜像源参数
354
- const mirrorArgs = getPipMirrorArgs();
355
-
356
- // Linux: 预装 evdev 预编译包,避免 pynput 安装时编译失败
164
+ // Linux: 预装 evdev
357
165
  if (!IS_WIN) {
358
166
  try {
359
- execFileSync(venvPython, ["-m", "pip", "install", "evdev-binary", "--quiet", "--disable-pip-version-check"], {
167
+ execFileSync(venvPython, ["-m", "pip", "install", "evdev-binary", "-q", "--disable-pip-version-check"], {
360
168
  encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 60000,
361
169
  });
362
170
  } catch (_) {}
363
171
  }
364
172
 
365
- // 策略2: pip install -r requirements.txt(venv 内无 PEP668 限制)
366
- let installed = false;
173
+ // 安装全部依赖
367
174
  if (fs.existsSync(reqFile)) {
368
- console.log(`\x1b[33m[!]\x1b[0m 正在安装 ${missing.length} 个缺失依赖到虚拟环境...`);
369
- if (mirrorArgs.length > 0) {
370
- console.log(" \x1b[90m[i]\x1b[0m 使用国内镜像源 (清华)");
371
- }
175
+ console.log(" 安装依赖 (可能需要几分钟)...");
372
176
  try {
373
177
  execFileSync(venvPython, ["-m", "pip", "install", "-r", reqFile, "--disable-pip-version-check", ...mirrorArgs], {
374
- encoding: "utf8", stdio: "inherit", timeout: 300000,
178
+ encoding: "utf8", stdio: "inherit", timeout: 600000,
375
179
  });
376
- console.log(" \x1b[32m[✓]\x1b[0m 依赖安装完成");
377
- installed = true;
180
+ console.log(" \x1b[32m✓\x1b[0m 全部依赖安装完成");
181
+ return true;
378
182
  } catch (err) {
379
- console.log(" \x1b[33m[!]\x1b[0m requirements.txt 安装失败,尝试直接安装核心依赖...");
380
- console.log(` \x1b[90m[i]\x1b[0m 错误: ${err.message}`);
183
+ console.log(` \x1b[33m安装失败: ${err.message.split("\n").pop().trim()}\x1b[0m`);
381
184
  }
382
185
  }
383
186
 
384
- // 策略3: 直接按包名安装核心依赖
385
- if (!installed) {
187
+ // 回退: 核心依赖
188
+ const CORE = [
189
+ "openai>=1.12.0", "aiohttp>=3.9.0", "requests>=2.31.0",
190
+ "duckduckgo-search>=6.0.0", "beautifulsoup4>=4.12.0", "lxml>=5.0.0",
191
+ "psutil>=5.9.0", "Pillow>=10.0.0", "edge-tts>=6.1.0",
192
+ "anthropic>=0.18.0",
193
+ ];
194
+ for (const mirrors of [mirrorArgs, ["-i", "https://mirrors.aliyun.com/pypi/simple/", "--trusted-host", "mirrors.aliyun.com"]]) {
386
195
  try {
387
- execFileSync(venvPython, ["-m", "pip", "install", "--quiet", "--disable-pip-version-check", ...mirrorArgs, ...CORE_PACKAGES], {
388
- encoding: "utf8", stdio: "inherit", timeout: 180000,
196
+ console.log(" 尝试安装核心依赖...");
197
+ execFileSync(venvPython, ["-m", "pip", "install", "-q", "--disable-pip-version-check", ...mirrors, ...CORE], {
198
+ encoding: "utf8", stdio: "inherit", timeout: 300000,
389
199
  });
390
- console.log(" \x1b[32m[✓]\x1b[0m 核心依赖安装完成");
391
- console.log(" \x1b[90m[i]\x1b[0m 其他依赖将在启动时自动安装");
392
- } catch (_) {
393
- // 策略4: 尝试阿里云镜像
394
- console.log(" \x1b[33m[!]\x1b[0m 核心依赖安装失败,尝试阿里云镜像...");
395
- try {
396
- const aliyunArgs = ["-i", "https://mirrors.aliyun.com/pypi/simple/", "--trusted-host", "mirrors.aliyun.com"];
397
- execFileSync(venvPython, ["-m", "pip", "install", "--quiet", "--disable-pip-version-check", ...aliyunArgs, ...CORE_PACKAGES], {
398
- encoding: "utf8", stdio: "inherit", timeout: 180000,
399
- });
400
- console.log(" \x1b[32m[✓]\x1b[0m 核心依赖安装完成(阿里云镜像)");
401
- } catch (_) {
402
- console.error(" \x1b[31m[✗]\x1b[0m 依赖安装失败,请手动运行:");
403
- console.error(` ${venvPython} -m pip install -r "${reqFile}"`);
404
- console.error("");
405
- console.error(" 或使用重新安装命令: myagent-ai reinstall");
406
- console.error("");
407
- }
408
- }
200
+ console.log(" \x1b[32m✓\x1b[0m 核心依赖安装完成");
201
+ return true;
202
+ } catch (_) {}
409
203
  }
204
+ return false;
410
205
  }
411
206
 
412
- // ── 构建 main.py 参数 ────────────────────────────────────
413
- function buildArgs(userArgs) {
414
- const mode = userArgs[0] || "";
415
- switch (mode) {
416
- case "cli": return ["main.py"];
417
- case "web": return ["main.py", "--web"];
418
- case "tray": return ["main.py", "--tray"];
419
- case "server": return ["main.py", "--server"];
420
- case "setup": return ["main.py", "--setup"];
421
- case "reinstall":
422
- return []; // 特殊处理
423
- case "uninstall":
424
- return []; // 特殊处理
425
- default:
426
- return ["main.py", ...userArgs];
427
- }
207
+ // 标记依赖已安装
208
+ function markDepsInstalled(version) {
209
+ try {
210
+ const dir = getDataDir();
211
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
212
+ fs.writeFileSync(getSentinelFile(), version);
213
+ } catch (_) {}
428
214
  }
429
215
 
430
- // ── 主入口 ─────────────────────────────────────────────────
431
- function main() {
432
- const userArgs = process.argv.slice(2);
433
- const pkgDir = resolvePackageDir();
434
-
435
- if (!fs.existsSync(path.join(pkgDir, "main.py"))) {
436
- console.error("\x1b[31m[✗]\x1b[0m 错误: 找不到 main.py");
437
- console.error(` 搜索路径: ${pkgDir}`);
438
- console.error(' 请重新安装: npm install -g myagent-ai');
439
- process.exit(1);
216
+ function isDepsInstalled(pkgVersion) {
217
+ try {
218
+ if (!fs.existsSync(getSentinelFile())) return false;
219
+ return fs.readFileSync(getSentinelFile(), "utf8").trim() === pkgVersion;
220
+ } catch (_) {
221
+ return false;
440
222
  }
223
+ }
441
224
 
442
- // 特殊命令: uninstall
443
- if (userArgs[0] === "uninstall") {
444
- const venvDir = getVenvDir();
445
- const purgeArg = userArgs.includes("--purge") || userArgs.includes("-p");
446
- const dataDir = getDataDir();
447
-
448
- // Step 1: 停止服务
449
- console.log(" \x1b[36m[*]\x1b[0m 停止 MyAgent 服务...");
450
- try {
451
- const http = require("http");
452
- const req = http.request({ hostname: "127.0.0.1", port: 8767, path: "/api/shutdown", method: "POST", timeout: 5000 }, () => {
453
- // 忽略响应
454
- });
455
- req.on("error", () => {});
456
- req.end();
457
- console.log(" \x1b[32m[✓]\x1b[0m 已发送关闭请求");
458
- setTimeout(() => {}, 2000);
459
- } catch (_) {
460
- console.log(" \x1b[90m[i]\x1b[0m 服务未运行");
461
- }
225
+ // ── 命令处理 ──────────────────────────────────────────────
462
226
 
463
- // Step 2: 终止 Python/Node 进程
464
- try {
465
- if (IS_WIN) {
466
- execSync("taskkill /F /IM python.exe /FI \"WINDOWTITLE eq myagent*\"", { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 5000 });
467
- } else {
468
- execSync("pkill -f 'myagent|main.py' 2>/dev/null; sleep 1", { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 10000 });
469
- }
470
- } catch (_) {}
471
- console.log(" \x1b[32m[✓]\x1b[0m 进程已停止");
227
+ function cmdInstall(pkgDir) {
228
+ console.log("");
229
+ console.log(" \x1b[36mMyAgent 安装依赖\x1b[0m");
230
+ console.log("");
231
+ const venvPython = ensureVenv();
232
+ installAllDeps(venvPython, pkgDir);
233
+ let ver = "";
234
+ try { ver = JSON.parse(fs.readFileSync(path.join(pkgDir, "package.json"), "utf8")).version || ""; } catch (_) {}
235
+ markDepsInstalled(ver);
236
+ console.log("");
237
+ console.log(" \x1b[32m✓ 安装完成,可以启动了: myagent-ai web\x1b[0m");
238
+ console.log("");
239
+ }
472
240
 
473
- // Step 3: npm uninstall
474
- console.log(" \x1b[36m[*]\x1b[0m 卸载 npm 全局包...");
475
- try {
476
- execFileSync("npm", ["uninstall", "-g", PKG_NAME], {
477
- encoding: "utf8", stdio: "inherit", timeout: 60000,
478
- });
479
- console.log(" \x1b[32m[✓]\x1b[0m npm 全局包已卸载");
480
- } catch (_) {
481
- console.log(" \x1b[90m[i]\x1b[0m npm 全局包未找到");
482
- }
241
+ function cmdReinstall(pkgDir) {
242
+ console.log("");
243
+ console.log(" \x1b[36mMyAgent 重装依赖\x1b[0m");
244
+ console.log("");
245
+ const venvDir = getVenvDir();
246
+ if (fs.existsSync(venvDir)) {
247
+ console.log(" 删除旧虚拟环境...");
248
+ fs.rmSync(venvDir, { recursive: true, force: true });
249
+ }
250
+ const venvPython = ensureVenv();
251
+ installAllDeps(venvPython, pkgDir);
252
+ let ver = "";
253
+ try { ver = JSON.parse(fs.readFileSync(path.join(pkgDir, "package.json"), "utf8")).version || ""; } catch (_) {}
254
+ markDepsInstalled(ver);
255
+ console.log("");
256
+ console.log(" \x1b[32m✓ 重装完成\x1b[0m");
257
+ console.log("");
258
+ }
483
259
 
484
- // Step 4: 清理数据(--purge)
485
- if (purgeArg) {
486
- console.log("");
487
- console.log(" \x1b[36m[*]\x1b[0m 清除所有数据...");
488
- if (fs.existsSync(dataDir)) {
489
- fs.rmSync(dataDir, { recursive: true, force: true });
490
- console.log(" \x1b[32m[✓]\x1b[0m 数据目录已删除: " + dataDir);
491
- } else {
492
- console.log(" \x1b[90m[i]\x1b[0m 数据目录不存在");
493
- }
494
- } else {
495
- console.log("");
496
- console.log(" \x1b[90m[i]\x1b[0m 保留数据目录: " + dataDir);
497
- console.log(" \x1b[90m[i]\x1b[0m 如需彻底清除: myagent-ai uninstall --purge");
498
- }
260
+ function cmdUninstall() {
261
+ const venvDir = getVenvDir();
262
+ const dataDir = getDataDir();
263
+ const purgeArg = process.argv.includes("--purge") || process.argv.includes("-p");
499
264
 
500
- console.log("");
501
- console.log(" \x1b[32m[✓]\x1b[0m 卸载完成!");
502
- console.log(" \x1b[90m[i]\x1b[0m 重新安装: npm install -g myagent-ai");
503
- console.log("");
504
- return;
505
- }
265
+ console.log("");
266
+ console.log(" \x1b[36mMyAgent 卸载\x1b[0m");
267
+ console.log("");
506
268
 
507
- // 特殊命令: reinstall
508
- if (userArgs[0] === "reinstall") {
509
- const venvDir = getVenvDir();
510
- if (fs.existsSync(venvDir)) {
511
- console.log(" \x1b[36m[*]\x1b[0m 删除旧虚拟环境...");
512
- fs.rmSync(venvDir, { recursive: true, force: true });
269
+ // 停止服务
270
+ try {
271
+ const http = require("http");
272
+ const req = http.request({ hostname: "127.0.0.1", port: 8767, path: "/api/shutdown", method: "POST", timeout: 5000 }, () => {});
273
+ req.on("error", () => {});
274
+ req.end();
275
+ } catch (_) {}
276
+ try {
277
+ if (IS_WIN) {
278
+ execSync('taskkill /F /IM python.exe /FI "WINDOWTITLE eq myagent*" 2>nul', { encoding: "utf8", timeout: 5000 });
279
+ } else {
280
+ execSync("pkill -f 'myagent|main.py' 2>/dev/null", { encoding: "utf8", timeout: 10000 });
513
281
  }
514
- const { venvPython, venvPip } = ensureVenv();
515
- installDeps(venvPython, venvPip, pkgDir);
516
- console.log("");
517
- console.log(" \x1b[32m[✓]\x1b[0m 依赖已全部重新安装到虚拟环境");
518
- console.log(` \x1b[90m[i]\x1b[0m 虚拟环境: ${venvDir}`);
519
- console.log("");
520
- return;
521
- }
282
+ } catch (_) {}
522
283
 
523
- // 打印 Banner(显示版本号)
524
- let pkgVersion = "";
525
284
  try {
526
- const pkgJson = path.join(pkgDir, "package.json");
527
- const pkg = JSON.parse(fs.readFileSync(pkgJson, "utf8"));
528
- pkgVersion = pkg.version || "";
285
+ execFileSync("npm", ["uninstall", "-g", PKG_NAME], {
286
+ encoding: "utf8", stdio: "inherit", timeout: 60000,
287
+ });
529
288
  } catch (_) {}
530
- console.log("");
531
- if (pkgVersion) {
532
- console.log(` \x1b[1m\x1b[36mMyAgent\x1b[0m v${pkgVersion} - 本地桌面端执行型 AI 助手`);
533
- } else {
534
- console.log(" \x1b[1m\x1b[36mMyAgent\x1b[0m - 本地桌面端执行型 AI 助手");
289
+
290
+ if (purgeArg && fs.existsSync(dataDir)) {
291
+ fs.rmSync(dataDir, { recursive: true, force: true });
292
+ console.log(" 数据已清除");
535
293
  }
536
294
  console.log("");
295
+ console.log(" \x1b[32m✓ 卸载完成\x1b[0m");
296
+ console.log("");
297
+ }
537
298
 
538
- // 是否跳过依赖检查
539
- const skipDeps = userArgs.includes("--skip-deps") || userArgs.includes("--no-deps");
540
- // 从参数中移除 --skip-deps/--no-deps,避免传入 main.py
541
- const filteredArgs = userArgs.filter(a => a !== "--skip-deps" && a !== "--no-deps");
542
-
543
- // 确保 venv 存在
544
- const { venvDir, venvPython, isNew } = ensureVenv();
545
-
546
- // 检查并安装依赖(--skip-deps 时跳过)
547
- if (skipDeps) {
548
- console.log(" \x1b[90m[i]\x1b[0m 跳过依赖检查(--skip-deps)");
549
- } else if (isNew) {
550
- installDeps(venvPython, getVenvPip(venvDir), pkgDir);
551
- } else {
552
- // 即使 venv 已存在,也快速检查核心依赖
553
- const venvPip = getVenvPip(venvDir);
554
- installDeps(venvPython, venvPip, pkgDir);
299
+ function cmdRun(pkgDir, userArgs) {
300
+ const venvPython = getVenvPython(getVenvDir());
301
+ if (!fs.existsSync(venvPython)) {
302
+ console.error("\x1b[31m虚拟环境不存在,请先运行: myagent-ai install\x1b[0m");
303
+ process.exit(1);
555
304
  }
556
305
 
557
- // 首次运行检测
558
- const configFile = path.join(getDataDir(), "config.json");
559
- const isFirstRun = !fs.existsSync(configFile);
560
- if (isFirstRun) {
306
+ let ver = "";
307
+ try { ver = JSON.parse(fs.readFileSync(path.join(pkgDir, "package.json"), "utf8")).version || ""; } catch (_) {}
308
+
309
+ // 首次运行自动安装
310
+ if (!isDepsInstalled(ver)) {
311
+ console.log("");
312
+ console.log(" \x1b[36m首次运行,正在安装依赖...\x1b[0m");
561
313
  console.log("");
562
- console.log(" \x1b[90m[i]\x1b[0m 检测到首次运行,MyAgent 将引导你完成 LLM 配置。");
314
+ installAllDeps(venvPython, pkgDir);
315
+ markDepsInstalled(ver);
563
316
  console.log("");
564
317
  }
565
318
 
566
- // 显示环境信息
567
- console.log(` \x1b[90m[i]\x1b[0m 虚拟环境: ${venvDir}`);
568
- console.log(` \x1b[90m[i]\x1b[0m Python: ${venvPython}`);
319
+ // 显示版本
569
320
  console.log("");
321
+ if (ver) {
322
+ console.log(` MyAgent v${ver}`);
323
+ }
324
+
325
+ // 首次运行检测
326
+ const configFile = path.join(getDataDir(), "config.json");
327
+ const isFirstRun = !fs.existsSync(configFile);
570
328
 
571
- // 构建 Python 参数(使用过滤后的参数)
572
- let mode = filteredArgs[0] || "";
573
- let pyArgs = buildArgs(filteredArgs);
329
+ // 构建参数
330
+ let mode = userArgs[0] || "";
331
+ let pyArgs;
332
+ switch (mode) {
333
+ case "cli": pyArgs = ["main.py"]; break;
334
+ case "web": pyArgs = ["main.py", "--web"]; break;
335
+ case "tray": pyArgs = ["main.py", "--tray"]; break;
336
+ case "server": pyArgs = ["main.py", "--server"]; break;
337
+ case "setup": pyArgs = ["main.py", "--setup"]; break;
338
+ default: pyArgs = ["main.py", ...userArgs]; mode = "";
339
+ }
574
340
 
575
- // 首次运行且未指定模式 → 自动选择 web 模式
576
341
  if (isFirstRun && !mode) {
577
342
  mode = "web";
578
343
  pyArgs = ["main.py", "--web"];
579
- console.log(" \x1b[90m[i]\x1b[0m 首次运行,自动启动 Web 模式进行配置...");
580
- console.log("");
581
344
  }
582
345
 
583
- if (mode) {
584
- const modeNames = {
585
- web: "Web 管理后台 (http://localhost:8767)",
586
- cli: "CLI 交互模式",
587
- tray: "系统托盘模式",
588
- server: "API 服务模式",
589
- setup: "配置向导",
590
- };
591
- console.log(`\x1b[36m启动 ${modeNames[mode] || mode}...\x1b[0m`);
592
- } else {
593
- console.log(`\x1b[36m启动...\x1b[0m`);
594
- }
595
-
596
- // 使用 venv 的 Python 启动
346
+ const venvDir = getVenvDir();
597
347
  const spawnEnv = {
598
348
  ...process.env,
599
- // 设置 VIRTUAL_ENV 环境变量,让 Python 程序知道自己运行在 venv 中
600
349
  VIRTUAL_ENV: venvDir,
601
350
  PATH: IS_WIN
602
351
  ? `${path.join(venvDir, "Scripts")};${process.env.PATH}`
603
352
  : `${path.join(venvDir, "bin")}:${process.env.PATH}`,
604
353
  };
605
- // 将 --skip-deps 信号传递给 Python,让 main.py 也跳过依赖检查
606
- if (skipDeps) {
607
- spawnEnv.MYAGENT_SKIP_DEPS = "1";
608
- }
609
- const child = spawn(venvPython, pyArgs, {
610
- cwd: pkgDir,
611
- stdio: "inherit",
612
- env: spawnEnv,
613
- });
614
-
615
- child.on("error", (err) => {
616
- console.error(`\x1b[31m[✗]\x1b[0m 启动失败: ${err.message}`);
617
- process.exit(1);
618
- });
619
354
 
620
- child.on("exit", (code) => {
621
- process.exit(code || 0);
622
- });
355
+ const child = spawn(venvPython, pyArgs, { cwd: pkgDir, stdio: "inherit", env: spawnEnv });
356
+ child.on("error", (err) => { console.error(`\x1b[31m启动失败: ${err.message}\x1b[0m`); process.exit(1); });
357
+ child.on("exit", (code) => { process.exit(code || 0); });
623
358
 
624
- // Web 模式:等服务启动后自动打开浏览器
359
+ // Web 模式自动打开浏览器
625
360
  if (mode === "web") {
626
- const port = 8767;
627
- const url = `http://127.0.0.1:${port}`;
361
+ const url = "http://127.0.0.1:8767";
628
362
  let opened = false;
629
-
630
- // 轮询等待服务就绪
631
- const pollInterval = setInterval(() => {
363
+ const poll = setInterval(() => {
632
364
  const http = require("http");
633
365
  const req = http.get(`${url}/api/status`, (res) => {
634
- clearInterval(pollInterval);
366
+ clearInterval(poll);
635
367
  if (!opened) {
636
368
  opened = true;
637
- console.log(` \x1b[32m[✓]\x1b[0m 管理后台已就绪: ${url}`);
638
- console.log(" \x1b[90m[i]\x1b[0m 正在打开浏览器...");
639
- console.log("");
640
- // 跨平台打开浏览器
641
369
  try {
642
- const { exec } = require("child_process");
643
- if (IS_WIN) {
644
- // Windows: start "" "url" — 空字符串防止 URL 被当作标题
645
- exec(`start "" "${url}"`);
646
- } else if (process.platform === "darwin") {
647
- exec(`open "${url}"`);
648
- } else {
649
- exec(`xdg-open "${url}"`);
650
- }
651
- } catch (_) {
652
- console.log(` \x1b[90m[i]\x1b[0m 请手动打开浏览器访问: ${url}`);
653
- }
370
+ if (IS_WIN) execSync(`start "" "${url}"`);
371
+ else if (process.platform === "darwin") execSync(`open "${url}"`);
372
+ else execSync(`xdg-open "${url}"`);
373
+ } catch (_) {}
654
374
  }
655
375
  });
656
- req.on("error", () => {
657
- // 服务尚未就绪,继续等待
658
- });
659
- req.setTimeout(2000, () => {
660
- req.destroy();
661
- });
376
+ req.on("error", () => {});
377
+ req.setTimeout(2000, () => { req.destroy(); });
662
378
  }, 1500);
379
+ setTimeout(() => { clearInterval(poll); }, 120000);
380
+ }
381
+ }
663
382
 
664
- // 最多等待 120 秒(首次安装 venv + 依赖可能需要较长时间)
665
- setTimeout(() => {
666
- clearInterval(pollInterval);
667
- if (!opened) {
668
- console.log(` \x1b[33m[!]\x1b[0m 等待超时,请手动打开浏览器访问: ${url}`);
669
- }
670
- }, 120000);
383
+ // ── 主入口 ──────────────────────────────────────────────
384
+
385
+ function main() {
386
+ const args = process.argv.slice(2);
387
+ const cmd = args[0];
388
+ const pkgDir = resolvePackageDir();
389
+
390
+ if (!fs.existsSync(path.join(pkgDir, "main.py"))) {
391
+ console.error("\x1b[31m找不到 main.py,请重新安装: npm install -g myagent-ai\x1b[0m");
392
+ process.exit(1);
671
393
  }
394
+
395
+ if (cmd === "uninstall") { cmdUninstall(); return; }
396
+ if (cmd === "reinstall") { cmdReinstall(pkgDir); return; }
397
+ if (cmd === "install") { cmdInstall(pkgDir); return; }
398
+
399
+ // 默认: 直接启动
400
+ cmdRun(pkgDir, args);
672
401
  }
673
402
 
674
403
  main();
package/web/ui/index.html CHANGED
@@ -38,7 +38,7 @@ body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;b
38
38
  .sidebar{width:220px;background:var(--surface);border-right:1px solid var(--border);display:flex;flex-direction:column;flex-shrink:0}
39
39
  .sidebar .logo{padding:20px;font-size:18px;font-weight:700;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:8px}
40
40
  .sidebar .logo span{color:var(--primary)}
41
- .nav{flex:1;padding:0 8px 8px;overflow-y:auto;min-height:0;display:flex;flex-direction:column}
41
+ .nav{flex:1;padding:0 8px 8px;overflow:hidden;min-height:0;display:flex;flex-direction:column}
42
42
  .nav-item{display:flex;align-items:center;gap:10px;padding:10px 12px;border-radius:var(--radius);cursor:pointer;color:var(--text2);font-size:14px;transition:all .15s;margin-bottom:2px}
43
43
  .nav-item:hover{background:var(--surface2);color:var(--text)}
44
44
  .nav-item.active{background:var(--primary);color:#fff}