myagent-ai 1.3.4 → 1.4.1

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/core/version.py CHANGED
@@ -11,7 +11,7 @@ import subprocess
11
11
  from pathlib import Path
12
12
 
13
13
  # ── 基线版本(与 setup.py / package.json 保持一致) ──
14
- BASE_VERSION = "1.3.4"
14
+ BASE_VERSION = "1.4.1"
15
15
 
16
16
 
17
17
  def _version_from_git() -> str:
@@ -9,7 +9,7 @@ param(
9
9
 
10
10
  $ErrorActionPreference = "Stop"
11
11
  $PKG_NAME = "myagent-ai"
12
- $PKG_VERSION = "1.3.4"
12
+ $PKG_VERSION = "1.4.0"
13
13
 
14
14
  # Allow running scripts for the current process
15
15
  if ($PSVersionTable.PSVersion.Major -ge 5) {
@@ -216,11 +216,9 @@ function Install-MyAgent {
216
216
  Write-Host "[OK] $PKG_NAME installed" -ForegroundColor Green
217
217
  }
218
218
 
219
- # ── Install Python dependencies ─────────────────────────
219
+ # ── Install Python dependencies (使用独立虚拟环境) ──
220
220
  function Install-PythonDeps {
221
- # 获取 Python 命令(使用已检测到的路径或默认)
222
221
  $pyCmd = if ($Script:PythonCmd) { $Script:PythonCmd } else { "python" }
223
-
224
222
  $npmRoot = (npm root -g 2>$null)
225
223
  $pkgDir = Join-Path $npmRoot $PKG_NAME
226
224
  $reqFile = Join-Path $pkgDir "requirements.txt"
@@ -234,14 +232,20 @@ function Install-PythonDeps {
234
232
  }
235
233
  }
236
234
 
237
- Write-Host "[*] Installing Python dependencies ..." -ForegroundColor Yellow
238
- Write-Host " Using: $pyCmd" -ForegroundColor Gray
239
- & $pyCmd -m pip install -r $reqFile
240
- if ($LASTEXITCODE -ne 0) {
241
- Write-Host "[!] Some deps failed (non-critical). Try: $pyCmd -m pip install -r $reqFile" -ForegroundColor Yellow
242
- } else {
243
- Write-Host "[OK] Python dependencies installed" -ForegroundColor Green
244
- }
235
+ $venvDir = "$env:USERPROFILE\.myagent\venv"
236
+ $venvPython = Join-Path $venvDir "Scripts\python.exe"
237
+
238
+ Write-Host "[*] Creating virtual environment at $venvDir ..." -ForegroundColor Yellow
239
+ New-Item -ItemType Directory -Path "$env:USERPROFILE\.myagent" -Force | Out-Null
240
+ & $pyCmd -m venv $venvDir
241
+ Write-Host "[OK] Virtual environment created" -ForegroundColor Green
242
+
243
+ Write-Host "[*] Installing Python dependencies into venv ..." -ForegroundColor Yellow
244
+ & $venvPython -m pip install --upgrade pip --quiet 2>$null
245
+ & $venvPython -m pip install -r $reqFile --disable-pip-version-check
246
+ Write-Host "[OK] Dependencies installed into venv" -ForegroundColor Green
247
+ Write-Host "[i] Virtual env: $venvDir" -ForegroundColor Gray
248
+ Write-Host "[i] venv will be used automatically on startup" -ForegroundColor Gray
245
249
  }
246
250
 
247
251
  # ── Post-install guide ──────────────────────────────
@@ -21,7 +21,7 @@ NO_DEPS=false
21
21
  DRY_RUN=false
22
22
  SCRIPT_URL="https://raw.githubusercontent.com/ctz168/myagent/main/install/install.sh"
23
23
  PKG_NAME="myagent-ai"
24
- PKG_VERSION="1.3.4"
24
+ PKG_VERSION="1.4.0"
25
25
 
26
26
  for arg in "$@"; do
27
27
  case "$arg" in
@@ -345,14 +345,13 @@ install_myagent() {
345
345
  success "$PKG_NAME installed"
346
346
  }
347
347
 
348
- # ── Install Python dependencies ─────────────────────────
348
+ # ── Install Python dependencies (使用独立虚拟环境) ──
349
349
  install_python_deps() {
350
350
  local pkg_dir
351
351
  pkg_dir="$(npm root -g)/$PKG_NAME"
352
352
  local req_file="$pkg_dir/requirements.txt"
353
353
 
354
354
  if [ ! -f "$req_file" ]; then
355
- # 尝试从项目目录查找
356
355
  if [ -f "./requirements.txt" ]; then
357
356
  req_file="./requirements.txt"
358
357
  else
@@ -361,7 +360,6 @@ install_python_deps() {
361
360
  fi
362
361
  fi
363
362
 
364
- # 获取已安装的 Python 命令
365
363
  local py_cmd
366
364
  py_cmd="$(get_python_cmd)"
367
365
  if [ -z "$py_cmd" ]; then
@@ -369,26 +367,19 @@ install_python_deps() {
369
367
  return 1
370
368
  fi
371
369
 
372
- step "Installing Python dependencies ..."
373
- # 使用已检测到的 Python 命令安装依赖
374
- "$py_cmd" -m pip install -r "$req_file" --break-system-packages 2>/dev/null || \
375
- "$py_cmd" -m pip install -r "$req_file" 2>/dev/null || \
376
- {
377
- warn "pip install 失败 (可能受 PEP668 限制)"
378
- info "尝试使用 venv 安装..."
379
- local venv_dir="$HOME/.myagent/venv"
380
- "$py_cmd" -m venv "$venv_dir" 2>/dev/null
381
- if [ -f "$venv_dir/bin/pip" ]; then
382
- "$venv_dir/bin/pip" install -r "$req_file"
383
- success "已安装到虚拟环境 $venv_dir"
384
- info "启动时请使用: $venv_dir/bin/python main.py --web"
385
- return 0
386
- fi
387
- err "所有安装方式均失败"
388
- info "请手动安装: $py_cmd -m pip install -r $req_file --break-system-packages"
389
- return 1
390
- }
391
- success "Python dependencies installed"
370
+ local venv_dir="$HOME/.myagent/venv"
371
+
372
+ step "Creating virtual environment at $venv_dir ..."
373
+ mkdir -p "$HOME/.myagent"
374
+ "$py_cmd" -m venv "$venv_dir"
375
+ success "Virtual environment created"
376
+
377
+ step "Installing Python dependencies into venv ..."
378
+ "$venv_dir/bin/pip" install --upgrade pip --quiet 2>/dev/null || true
379
+ "$venv_dir/bin/pip" install -r "$req_file" --disable-pip-version-check
380
+ success "Dependencies installed into venv"
381
+ info "虚拟环境: $venv_dir"
382
+ info "启动时自动使用 venv,无需手动激活"
392
383
  }
393
384
 
394
385
  # ── 首次配置引导 ───────────────────────────────────
package/main.py CHANGED
@@ -808,11 +808,11 @@ def create_tray_icon(app: MyAgentApp, web_port: int = 8767):
808
808
  pass
809
809
  icon.stop()
810
810
 
811
- def _get_status_title(icon, item):
812
- """动态生成状态标题"""
811
+ def _get_status_title(item):
812
+ """动态生成状态标题 (pystray text callable: text(menu_item))"""
813
813
  if not app._running:
814
- return "🤖 MyAgent - 已停止"
815
- return "🤖 MyAgent - 运行中"
814
+ return "MyAgent - 已停止"
815
+ return "MyAgent - 运行中"
816
816
 
817
817
  def _autostart_checked(item):
818
818
  """返回当前自启状态的 checked 值"""
@@ -821,37 +821,37 @@ def create_tray_icon(app: MyAgentApp, web_port: int = 8767):
821
821
  def _refresh_menu(icon):
822
822
  """刷新托盘菜单 (重建以更新动态状态)"""
823
823
  try:
824
- icon.menu = _build_menu(icon)
824
+ icon.menu = _build_menu()
825
825
  icon.update_menu()
826
826
  except Exception:
827
827
  pass # 部分平台不支持动态菜单刷新
828
828
 
829
- def _build_menu(icon_ref=None):
830
- """构建菜单"""
829
+ def _build_menu():
830
+ """构建菜单 (Windows Win32 不支持 BMP 外 emoji, 使用纯文本)"""
831
831
  return pystray.Menu(
832
832
  pystray.MenuItem(
833
- lambda icon, item: "🤖 MyAgent - 运行中" if app._running else "🤖 MyAgent - 已停止",
833
+ _get_status_title,
834
834
  None, enabled=False,
835
835
  ),
836
836
  pystray.Menu.SEPARATOR,
837
- pystray.MenuItem("💬 打开聊天界面", open_chat_ui, default=True),
838
- pystray.MenuItem("🖥️ 打开管理后台", open_web_ui),
839
- pystray.MenuItem("📋 显示状态", show_status),
837
+ pystray.MenuItem("打开聊天界面", open_chat_ui, default=True),
838
+ pystray.MenuItem("打开管理后台", open_web_ui),
839
+ pystray.MenuItem("显示状态", show_status),
840
840
  pystray.Menu.SEPARATOR,
841
- pystray.MenuItem("📁 打开工作目录", open_workdir),
842
- pystray.MenuItem("📄 打开日志目录", open_logs),
843
- pystray.MenuItem("⚙️ 打开配置目录", open_config_dir),
841
+ pystray.MenuItem("打开工作目录", open_workdir),
842
+ pystray.MenuItem("打开日志目录", open_logs),
843
+ pystray.MenuItem("打开配置目录", open_config_dir),
844
844
  pystray.Menu.SEPARATOR,
845
- pystray.MenuItem("🔄 开机自启", toggle_autostart, checked=_autostart_checked),
846
- pystray.MenuItem("🔁 重启服务", restart_service),
845
+ pystray.MenuItem("开机自启", toggle_autostart, checked=_autostart_checked),
846
+ pystray.MenuItem("重启服务", restart_service),
847
847
  pystray.Menu.SEPARATOR,
848
- pystray.MenuItem("退出", on_quit),
848
+ pystray.MenuItem("退出", on_quit),
849
849
  )
850
850
 
851
851
  tray_icon = pystray.Icon(
852
852
  "myagent",
853
853
  create_icon_image(),
854
- f"MyAgent v{get_version()} - 运行中",
854
+ f"MyAgent v{get_version()}",
855
855
  _build_menu(),
856
856
  )
857
857
 
@@ -877,13 +877,19 @@ def run_with_tray(app: MyAgentApp, web_port: int = 8767):
877
877
  app.logger.warning("pystray 未安装,跳过系统托盘")
878
878
  return
879
879
 
880
- # 在后台线程运行托盘
881
- tray_thread = threading.Thread(target=tray.run, daemon=True)
880
+ # 在后台线程运行托盘 (daemon=True 保证崩溃不影响主进程)
881
+ def _tray_run():
882
+ try:
883
+ tray.run()
884
+ except Exception as e:
885
+ app.logger.warning(f"系统托盘异常退出 (不影响主服务): {e}")
886
+
887
+ tray_thread = threading.Thread(target=_tray_run, daemon=True)
882
888
  tray_thread.start()
883
889
  app.logger.info(f"系统托盘已启动 (聊天: http://127.0.0.1:{web_port}/ui/chat.html)")
884
890
 
885
891
  # 启动完成通知
886
- _tray_notify(app, "MyAgent 已启动", f"v{get_version()} | 端口: {web_port}")
892
+ _tray_notify(app, "MyAgent 已启动", f"v{get_version()} | 端口: {web_port}")
887
893
 
888
894
 
889
895
  def _tray_notify(app: MyAgentApp, title: str, message: str):
@@ -1100,7 +1106,7 @@ def main():
1100
1106
  api_server = ApiServer(app)
1101
1107
  await api_server.start(port=web_port)
1102
1108
  app.logger.info(f"服务已重启: http://127.0.0.1:{web_port}/ui/")
1103
- _tray_notify(app, "服务已重启", f"端口: {web_port}")
1109
+ _tray_notify(app, "服务已重启", f"端口: {web_port}")
1104
1110
  elif web_port:
1105
1111
  # Web 模式 (无托盘): 后台运行保持服务
1106
1112
  app.logger.info("Web 服务运行中... (Ctrl+C 退出)")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myagent-ai",
3
- "version": "1.3.4",
3
+ "version": "1.4.1",
4
4
  "description": "本地桌面端执行型AI助手 - Open Interpreter 风格 | Local Desktop Execution-Oriented AI Assistant",
5
5
  "main": "main.py",
6
6
  "bin": {
package/setup.py CHANGED
@@ -9,7 +9,7 @@ from pathlib import Path
9
9
  _version_path = Path(__file__).parent / "core" / "version.py"
10
10
  _version_vars = {}
11
11
  exec(_version_path.read_text(), _version_vars)
12
- __version__ = _version_vars.get("BASE_VERSION", "1.3.4")
12
+ __version__ = _version_vars.get("BASE_VERSION", "1.4.1")
13
13
 
14
14
  setup(
15
15
  name="myagent",
package/start.js CHANGED
@@ -2,65 +2,80 @@
2
2
  /**
3
3
  * start.js - 跨平台入口脚本 (Windows/macOS/Linux)
4
4
  * ================================================
5
- * npm bin 入口,自动检测平台:
6
- * - Windows: 直接调用 python main.py [args](不依赖 bash)
7
- * - macOS/Linux: 委托给 start.sh [args](保留完整的环境检测逻辑)
5
+ * npm bin 入口,自动管理独立虚拟环境:
6
+ * - 虚拟环境位置: ~/.myagent/venv (与其他项目完全隔离)
7
+ * - 首次运行自动创建 venv 并安装所有依赖
8
+ * - 后续运行直接使用 venv,无需重复安装
9
+ * - pip 升级/重装: myagent-ai reinstall
8
10
  *
9
11
  * 用法:
10
- * myagent-ai # 交互式选择运行模式
11
- * myagent-ai web # Web 管理后台
12
- * myagent-ai cli # CLI 模式
13
- * myagent-ai tray # 系统托盘模式
14
- * myagent-ai server # API 服务模式
15
- * myagent-ai setup # 配置向导
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
16
19
  */
17
20
  "use strict";
18
21
 
19
22
  const { spawn, execSync, execFileSync } = require("child_process");
20
23
  const path = require("path");
21
24
  const fs = require("fs");
25
+ const os = require("os");
22
26
 
23
27
  // ── 常量 ──────────────────────────────────────────────────
24
28
  const IS_WIN = process.platform === "win32";
25
29
 
26
- // 解析包目录(兼容 symlink / npm link / 全局安装)
30
+ // 虚拟环境目录
31
+ function getVenvDir() {
32
+ const homeDir = os.homedir();
33
+ return path.join(homeDir, ".myagent", "venv");
34
+ }
35
+
36
+ // venv 中 Python 可执行文件的路径
37
+ function getVenvPython(venvDir) {
38
+ return IS_WIN
39
+ ? path.join(venvDir, "Scripts", "python.exe")
40
+ : path.join(venvDir, "bin", "python");
41
+ }
42
+
43
+ // venv 中 pip 可执行文件的路径
44
+ function getVenvPip(venvDir) {
45
+ return IS_WIN
46
+ ? path.join(venvDir, "Scripts", "pip.exe")
47
+ : path.join(venvDir, "bin", "pip");
48
+ }
49
+
50
+ // MyAgent 数据目录
51
+ function getDataDir() {
52
+ return path.join(os.homedir(), ".myagent");
53
+ }
54
+
55
+ // ── 解析包目录 ────────────────────────────────────────────
27
56
  function resolvePackageDir() {
28
- // 1. 当前文件所在目录
29
57
  let dir = __dirname;
30
-
31
- // 2. 如果 main.py 不在这里,尝试 npm global prefix
32
58
  if (!fs.existsSync(path.join(dir, "main.py"))) {
33
59
  try {
34
60
  const npmRoot = execSync("npm root -g", {
35
- encoding: "utf8",
36
- stdio: ["pipe", "pipe", "pipe"],
61
+ encoding: "utf8", stdio: ["pipe", "pipe", "pipe"],
37
62
  }).trim();
38
63
  const candidate = path.join(npmRoot, "myagent-ai");
39
- if (fs.existsSync(path.join(candidate, "main.py"))) {
40
- dir = candidate;
41
- }
42
- } catch (_) {
43
- // ignore
44
- }
64
+ if (fs.existsSync(path.join(candidate, "main.py"))) dir = candidate;
65
+ } catch (_) {}
45
66
  }
46
-
47
- // 3. 向上查找
48
67
  if (!fs.existsSync(path.join(dir, "main.py"))) {
49
68
  let up = dir;
50
69
  for (let i = 0; i < 5; i++) {
51
70
  up = path.dirname(up);
52
- if (fs.existsSync(path.join(up, "main.py"))) {
53
- dir = up;
54
- break;
55
- }
71
+ if (fs.existsSync(path.join(up, "main.py"))) { dir = up; break; }
56
72
  }
57
73
  }
58
-
59
74
  return dir;
60
75
  }
61
76
 
62
- // ── 查找 Python ────────────────────────────────────────────
63
- function findPython() {
77
+ // ── 查找系统 Python(用于创建 venv) ─────────────────────
78
+ function findSystemPython() {
64
79
  const candidates = IS_WIN
65
80
  ? ["python", "python3", "python3.14", "python3.13", "python3.12", "python3.11"]
66
81
  : ["python3", "python3.14", "python3.13", "python3.12", "python"];
@@ -72,87 +87,109 @@ function findPython() {
72
87
  ["--version"],
73
88
  { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 5000 }
74
89
  );
75
- // 解析版本号
76
90
  const match = result.match(/Python (\d+)\.(\d+)/);
77
91
  if (match) {
78
92
  const major = parseInt(match[1], 10);
79
93
  const minor = parseInt(match[2], 10);
80
- if (major >= 3 && minor >= 10) {
81
- return cmd;
82
- }
94
+ if (major >= 3 && minor >= 10) return cmd;
83
95
  }
84
- } catch (_) {
85
- // 继续尝试下一个
96
+ } catch (_) {}
97
+ }
98
+
99
+ // Windows: 搜索常见安装路径
100
+ if (IS_WIN) {
101
+ const localAppData = process.env.LOCALAPPDATA || "";
102
+ const programFiles = process.env.ProgramFiles || "";
103
+ const programFilesX86 = process.env["ProgramFiles(x86)"] || "";
104
+ const searchPaths = [
105
+ path.join(localAppData, "Programs", "Python", "Python314", "python.exe"),
106
+ path.join(localAppData, "Programs", "Python", "Python313", "python.exe"),
107
+ path.join(localAppData, "Programs", "Python", "Python312", "python.exe"),
108
+ path.join(programFiles, "Python314", "python.exe"),
109
+ path.join(programFiles, "Python313", "python.exe"),
110
+ "C:\\Python314\\python.exe", "C:\\Python313\\python.exe",
111
+ ];
112
+ for (const pyPath of searchPaths) {
113
+ if (fs.existsSync(pyPath)) return pyPath;
86
114
  }
87
115
  }
88
116
 
89
117
  return null;
90
118
  }
91
119
 
92
- // ── Windows: 常见 Python 安装路径 ──────────────────────────
93
- function findPythonOnWindows() {
94
- const localAppData = process.env.LOCALAPPDATA || "";
95
- const programFiles = process.env.ProgramFiles || "";
96
- const programFilesX86 = process.env["ProgramFiles(x86)"] || "";
97
-
98
- const searchPaths = [
99
- path.join(localAppData, "Programs", "Python", "Python314", "python.exe"),
100
- path.join(localAppData, "Programs", "Python", "Python313", "python.exe"),
101
- path.join(localAppData, "Programs", "Python", "Python312", "python.exe"),
102
- path.join(programFiles, "Python314", "python.exe"),
103
- path.join(programFiles, "Python313", "python.exe"),
104
- path.join(programFiles, "Python312", "python.exe"),
105
- path.join(programFilesX86, "Python314", "python.exe"),
106
- path.join(programFilesX86, "Python313", "python.exe"),
107
- "C:\\Python314\\python.exe",
108
- "C:\\Python313\\python.exe",
109
- ];
110
-
111
- for (const pyPath of searchPaths) {
112
- if (fs.existsSync(pyPath)) {
113
- try {
114
- const result = execFileSync(pyPath, ["--version"], {
115
- encoding: "utf8",
116
- stdio: ["pipe", "pipe", "pipe"],
117
- timeout: 5000,
118
- });
119
- if (result.includes("Python")) {
120
- return pyPath;
121
- }
122
- } catch (_) {
123
- // continue
124
- }
120
+ // ── 虚拟环境管理 ─────────────────────────────────────────
121
+
122
+ /**
123
+ * 确保 venv 存在且可用
124
+ * Returns: { venvDir, venvPython, venvPip }
125
+ */
126
+ function ensureVenv() {
127
+ const venvDir = getVenvDir();
128
+ const venvPython = getVenvPython(venvDir);
129
+ const venvPip = getVenvPip(venvDir);
130
+
131
+ // 检查现有 venv 是否完整
132
+ if (fs.existsSync(venvPython)) {
133
+ try {
134
+ execFileSync(venvPython, ["--version"], {
135
+ encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 5000,
136
+ });
137
+ return { venvDir, venvPython, venvPip, isNew: false };
138
+ } catch (_) {
139
+ // venv 损坏,需要重建
140
+ console.log(" \x1b[33m[!]\x1b[0m 虚拟环境已损坏,正在重建...");
141
+ fs.rmSync(venvDir, { recursive: true, force: true });
125
142
  }
126
143
  }
127
144
 
128
- return null;
129
- }
145
+ // 创建 venv
146
+ const systemPython = findSystemPython();
147
+ if (!systemPython) {
148
+ console.error("\x1b[31m[✗]\x1b[0m 未找到 Python 3.10+");
149
+ console.error(" 请先安装 Python: https://www.python.org/downloads/");
150
+ console.error(" 安装时请勾选 'Add Python to PATH'");
151
+ console.error("");
152
+ console.error(" 或一键安装:");
153
+ if (IS_WIN) {
154
+ console.error(" powershell -c \"irm https://raw.githubusercontent.com/ctz168/myagent/main/install/install.ps1 | iex\"");
155
+ } else {
156
+ console.error(" curl -fsSL https://raw.githubusercontent.com/ctz168/myagent/main/install/install.sh | bash");
157
+ }
158
+ process.exit(1);
159
+ }
130
160
 
131
- // ── 构建 Python 参数 ───────────────────────────────────────
132
- function buildArgs(userArgs) {
133
- const mode = userArgs[0] || "";
134
- switch (mode) {
135
- case "cli":
136
- return ["main.py"];
137
- case "web":
138
- return ["main.py", "--web"];
139
- case "tray":
140
- return ["main.py", "--tray"];
141
- case "server":
142
- return ["main.py", "--server"];
143
- case "setup":
144
- return ["main.py", "--setup"];
145
- case "":
146
- // 无参数:不传任何 flag,让 Python 程序进入交互式选择
147
- return ["main.py"];
148
- default:
149
- // 透传未知参数
150
- return ["main.py", ...userArgs];
161
+ // 确保 ~/.myagent 目录存在
162
+ const dataDir = getDataDir();
163
+ if (!fs.existsSync(dataDir)) {
164
+ fs.mkdirSync(dataDir, { recursive: true });
165
+ }
166
+
167
+ console.log(" \x1b[36m[*]\x1b[0m 创建独立虚拟环境...");
168
+ console.log(` \x1b[90m[i]\x1b[0m 位置: ${venvDir}`);
169
+ console.log(` \x1b[90m[i]\x1b[0m 系统Python: ${systemPython}`);
170
+
171
+ try {
172
+ execFileSync(systemPython, ["-m", "venv", venvDir], {
173
+ encoding: "utf8", stdio: "inherit", timeout: 60000,
174
+ });
175
+ } catch (err) {
176
+ console.error(`\x1b[31m[✗]\x1b[0m 创建虚拟环境失败: ${err.message}`);
177
+ process.exit(1);
178
+ }
179
+
180
+ // 验证创建成功
181
+ if (!fs.existsSync(venvPython)) {
182
+ console.error("\x1b[31m[✗]\x1b[0m 虚拟环境创建后未找到 Python,请检查系统 Python 安装");
183
+ process.exit(1);
151
184
  }
185
+
186
+ console.log(" \x1b[32m[✓]\x1b[0m 虚拟环境已创建");
187
+ return { venvDir, venvPython, venvPip, isNew: true };
152
188
  }
153
189
 
154
- // ── Windows: pip 安装依赖(带升级和 fallback) ──────────────
155
- // 核心依赖包名列表(直接安装,不依赖 requirements.txt 解析)
190
+ // ── 依赖安装 ─────────────────────────────────────────────
191
+
192
+ // 核心依赖包(fallback 用,不依赖文件解析)
156
193
  const CORE_PACKAGES = [
157
194
  "openai>=1.12.0",
158
195
  "aiohttp>=3.9.0",
@@ -163,61 +200,17 @@ const CORE_PACKAGES = [
163
200
  "psutil>=5.9.0",
164
201
  ];
165
202
 
166
- function pipInstall(pythonCmd, packages, cwd) {
167
- const args = ["-m", "pip", "install", "--quiet", "--disable-pip-version-check", ...packages];
168
- return execFileSync(pythonCmd, args, {
169
- encoding: "utf8",
170
- stdio: "inherit",
171
- timeout: 180000,
172
- cwd,
173
- });
174
- }
175
-
176
- function pipInstallReqFile(pythonCmd, reqFile, cwd) {
177
- return execFileSync(pythonCmd, ["-m", "pip", "install", "-r", reqFile, "--disable-pip-version-check"], {
178
- encoding: "utf8",
179
- stdio: "inherit",
180
- timeout: 300000,
181
- cwd,
182
- });
183
- }
184
-
185
- function upgradePip(pythonCmd) {
186
- try {
187
- console.log(" \x1b[90m[i]\x1b[0m 升级 pip...");
188
- execFileSync(pythonCmd, ["-m", "pip", "install", "--upgrade", "pip", "--quiet"], {
189
- encoding: "utf8",
190
- stdio: ["pipe", "pipe", "pipe"],
191
- timeout: 120000,
192
- });
193
- } catch (_) {
194
- // 升级失败不阻断,继续尝试安装
195
- }
196
- }
197
-
198
- // ── Windows 快速依赖检查 ───────────────────────────────────
199
- function checkDepsWin(pkgDir) {
203
+ function installDeps(venvPython, venvPip, pkgDir) {
200
204
  const required = ["openai", "aiohttp", "requests"];
201
- let pythonCmd = findPython() || findPythonOnWindows();
205
+ let missing = [];
202
206
 
203
- if (!pythonCmd) {
204
- console.error("\x1b[31m[✗]\x1b[0m 未找到 Python 3.10+");
205
- console.error(" 请先安装 Python: https://www.python.org/downloads/");
206
- console.error(" 安装时请勾选 'Add Python to PATH'");
207
- console.error("");
208
- console.error(" 或一键安装: powershell -c \"irm https://raw.githubusercontent.com/ctz168/myagent/main/install/install.ps1 | iex\"");
209
- process.exit(1);
210
- }
207
+ console.log(" \x1b[36m[*]\x1b[0m 检查依赖...");
211
208
 
212
- // 检查核心依赖
213
- let missing = [];
214
209
  for (const mod of required) {
215
210
  try {
216
- execFileSync(
217
- pythonCmd,
218
- ["-c", `import ${mod}`],
219
- { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 5000, cwd: pkgDir }
220
- );
211
+ execFileSync(venvPython, ["-c", `import ${mod}`], {
212
+ encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 5000,
213
+ });
221
214
  console.log(` \x1b[32m[✓]\x1b[0m ${mod}`);
222
215
  } catch (_) {
223
216
  console.log(` \x1b[33m[!]\x1b[0m ${mod}`);
@@ -225,44 +218,62 @@ function checkDepsWin(pkgDir) {
225
218
  }
226
219
  }
227
220
 
228
- if (missing.length > 0) {
229
- console.log(`\x1b[33m[!]\x1b[0m 正在安装 ${missing.length} 个缺失依赖...`);
230
- const reqFile = path.join(pkgDir, "requirements.txt");
231
-
232
- // 策略1: 先升级 pip(旧版 pip 无法解析 requirements.txt)
233
- upgradePip(pythonCmd);
234
-
235
- // 策略2: 尝试 pip install -r requirements.txt
236
- let installed = false;
237
- if (fs.existsSync(reqFile)) {
238
- try {
239
- pipInstallReqFile(pythonCmd, reqFile, pkgDir);
240
- console.log(` \x1b[32m[✓]\x1b[0m 依赖安装完成`);
241
- installed = true;
242
- } catch (_) {
243
- // requirements.txt 安装失败,进入 fallback
244
- console.log(` \x1b[33m[!]\x1b[0m requirements.txt 安装失败,尝试直接安装核心依赖...`);
245
- }
221
+ if (missing.length === 0) return;
222
+
223
+ const reqFile = path.join(pkgDir, "requirements.txt");
224
+
225
+ // 策略1: 升级 pip
226
+ try {
227
+ console.log(" \x1b[90m[i]\x1b[0m 升级 pip...");
228
+ execFileSync(venvPython, ["-m", "pip", "install", "--upgrade", "pip", "--quiet"], {
229
+ encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 120000,
230
+ });
231
+ } catch (_) {}
232
+
233
+ // 策略2: pip install -r requirements.txt(venv 内无 PEP668 限制)
234
+ let installed = false;
235
+ if (fs.existsSync(reqFile)) {
236
+ console.log(`\x1b[33m[!]\x1b[0m 正在安装 ${missing.length} 个缺失依赖到虚拟环境...`);
237
+ try {
238
+ execFileSync(venvPython, ["-m", "pip", "install", "-r", reqFile, "--disable-pip-version-check"], {
239
+ encoding: "utf8", stdio: "inherit", timeout: 300000,
240
+ });
241
+ console.log(" \x1b[32m[✓]\x1b[0m 依赖安装完成");
242
+ installed = true;
243
+ } catch (_) {
244
+ console.log(" \x1b[33m[!]\x1b[0m requirements.txt 安装失败,尝试直接安装核心依赖...");
246
245
  }
246
+ }
247
247
 
248
- // 策略3: 直接按包名安装核心依赖(不依赖 requirements.txt 解析)
249
- if (!installed) {
250
- try {
251
- pipInstall(pythonCmd, CORE_PACKAGES, pkgDir);
252
- console.log(` \x1b[32m[✓]\x1b[0m 核心依赖安装完成`);
253
- console.log(` \x1b[90m[i]\x1b[0m 其他依赖将在 MyAgent 启动时自动安装`);
254
- } catch (_) {
255
- console.error(` \x1b[31m[]\x1b[0m 依赖安装失败,请手动运行:`);
256
- console.error(` ${pythonCmd} -m pip install -r "${reqFile}"`);
257
- console.error(` 或逐个安装:`);
258
- for (const pkg of CORE_PACKAGES) {
259
- console.error(` ${pythonCmd} -m pip install ${pkg}`);
260
- }
261
- }
248
+ // 策略3: 直接按包名安装
249
+ if (!installed) {
250
+ try {
251
+ execFileSync(venvPython, ["-m", "pip", "install", "--quiet", "--disable-pip-version-check", ...CORE_PACKAGES], {
252
+ encoding: "utf8", stdio: "inherit", timeout: 180000,
253
+ });
254
+ console.log(" \x1b[32m[✓]\x1b[0m 核心依赖安装完成");
255
+ console.log(" \x1b[90m[i]\x1b[0m 其他依赖将在启动时自动安装");
256
+ } catch (_) {
257
+ console.error(" \x1b[31m[✗]\x1b[0m 依赖安装失败,请手动运行:");
258
+ console.error(` ${venvPython} -m pip install -r "${reqFile}"`);
262
259
  }
263
260
  }
261
+ }
264
262
 
265
- return pythonCmd;
263
+ // ── 构建 main.py 参数 ────────────────────────────────────
264
+ function buildArgs(userArgs) {
265
+ const mode = userArgs[0] || "";
266
+ switch (mode) {
267
+ case "cli": return ["main.py"];
268
+ case "web": return ["main.py", "--web"];
269
+ case "tray": return ["main.py", "--tray"];
270
+ case "server": return ["main.py", "--server"];
271
+ case "setup": return ["main.py", "--setup"];
272
+ case "reinstall":
273
+ return []; // 特殊处理
274
+ default:
275
+ return ["main.py", ...userArgs];
276
+ }
266
277
  }
267
278
 
268
279
  // ── 主入口 ─────────────────────────────────────────────────
@@ -277,85 +288,93 @@ function main() {
277
288
  process.exit(1);
278
289
  }
279
290
 
291
+ // 特殊命令: reinstall
292
+ if (userArgs[0] === "reinstall") {
293
+ const venvDir = getVenvDir();
294
+ if (fs.existsSync(venvDir)) {
295
+ console.log(" \x1b[36m[*]\x1b[0m 删除旧虚拟环境...");
296
+ fs.rmSync(venvDir, { recursive: true, force: true });
297
+ }
298
+ const { venvPython, venvPip } = ensureVenv();
299
+ installDeps(venvPython, venvPip, pkgDir);
300
+ console.log("");
301
+ console.log(" \x1b[32m[✓]\x1b[0m 依赖已全部重新安装到虚拟环境");
302
+ console.log(` \x1b[90m[i]\x1b[0m 虚拟环境: ${venvDir}`);
303
+ console.log("");
304
+ return;
305
+ }
306
+
280
307
  // 打印 Banner
281
308
  console.log("");
282
309
  console.log(" \x1b[1m\x1b[36mMyAgent\x1b[0m - 本地桌面端执行型 AI 助手");
283
310
  console.log("");
284
311
 
285
- if (IS_WIN) {
286
- // ── Windows 路径 ──
287
- const pythonCmd = checkDepsWin(pkgDir);
288
-
289
- // 首次运行检测
290
- const homeDir = process.env.USERPROFILE || process.env.HOME || "";
291
- const configFile = path.join(homeDir, ".myagent", "config.json");
292
- if (!fs.existsSync(configFile)) {
293
- console.log("");
294
- console.log(" \x1b[90m[i]\x1b[0m 检测到首次运行,MyAgent 将在启动后引导你完成配置。");
295
- console.log(" 1. 配置 LLM API Key(支持 OpenAI/Anthropic/ModelScope 等)");
296
- console.log(" 2. 选择默认运行模式");
297
- console.log("");
298
- }
312
+ // 确保 venv 存在
313
+ const { venvDir, venvPython, isNew } = ensureVenv();
299
314
 
300
- // 构建 Python 参数
301
- const mode = userArgs[0] || "";
302
- const pyArgs = buildArgs(userArgs);
303
-
304
- if (mode) {
305
- const modeNames = {
306
- web: "Web 管理后台 (http://localhost:8767)",
307
- cli: "CLI 交互模式",
308
- tray: "系统托盘模式",
309
- server: "API 服务模式",
310
- setup: "配置向导",
311
- };
312
- console.log(`\x1b[36m启动 ${modeNames[mode] || mode}...\x1b[0m`);
313
- } else {
314
- console.log(`\x1b[36m启动...\x1b[0m`);
315
- }
315
+ // 检查并安装依赖
316
+ if (isNew) {
317
+ installDeps(venvPython, getVenvPip(venvDir), pkgDir);
318
+ } else {
319
+ // 即使 venv 已存在,也快速检查核心依赖
320
+ const venvPip = getVenvPip(venvDir);
321
+ installDeps(venvPython, venvPip, pkgDir);
322
+ }
316
323
 
317
- // 使用 spawn 以交互模式运行 Python
318
- const child = spawn(pythonCmd, pyArgs, {
319
- cwd: pkgDir,
320
- stdio: "inherit",
321
- env: { ...process.env },
322
- });
324
+ // 首次运行检测
325
+ const configFile = path.join(getDataDir(), "config.json");
326
+ if (!fs.existsSync(configFile)) {
327
+ console.log("");
328
+ console.log(" \x1b[90m[i]\x1b[0m 检测到首次运行,MyAgent 将在启动后引导你完成配置。");
329
+ console.log(" 1. 配置 LLM API Key(支持 OpenAI/Anthropic/ModelScope 等)");
330
+ console.log(" 2. 选择默认运行模式");
331
+ console.log("");
332
+ }
323
333
 
324
- child.on("error", (err) => {
325
- console.error(`\x1b[31m[]\x1b[0m 启动失败: ${err.message}`);
326
- process.exit(1);
327
- });
334
+ // 显示环境信息
335
+ console.log(` \x1b[90m[i]\x1b[0m 虚拟环境: ${venvDir}`);
336
+ console.log(` \x1b[90m[i]\x1b[0m Python: ${venvPython}`);
337
+ console.log("");
328
338
 
329
- child.on("exit", (code) => {
330
- process.exit(code || 0);
331
- });
339
+ // 构建 Python 参数
340
+ const mode = userArgs[0] || "";
341
+ const pyArgs = buildArgs(userArgs);
342
+
343
+ if (mode) {
344
+ const modeNames = {
345
+ web: "Web 管理后台 (http://localhost:8767)",
346
+ cli: "CLI 交互模式",
347
+ tray: "系统托盘模式",
348
+ server: "API 服务模式",
349
+ setup: "配置向导",
350
+ };
351
+ console.log(`\x1b[36m启动 ${modeNames[mode] || mode}...\x1b[0m`);
332
352
  } else {
333
- // ── macOS / Linux 路径:委托给 start.sh ──
334
- const bashPath = process.env.SHELL || "/bin/bash";
335
- const shell = path.basename(bashPath);
336
- const shellArgs = shell === "bash" ? [] : ["-l"]; // zsh 需要 -l 加载 profile
337
-
338
- const startSh = path.join(pkgDir, "start.sh");
339
- if (!fs.existsSync(startSh)) {
340
- console.error("\x1b[31m[✗]\x1b[0m 找不到 start.sh");
341
- process.exit(1);
342
- }
353
+ console.log(`\x1b[36m启动...\x1b[0m`);
354
+ }
343
355
 
344
- const child = spawn(bashPath, [...shellArgs, startSh, ...userArgs], {
345
- cwd: pkgDir,
346
- stdio: "inherit",
347
- env: { ...process.env },
348
- });
356
+ // 使用 venv Python 启动
357
+ const child = spawn(venvPython, pyArgs, {
358
+ cwd: pkgDir,
359
+ stdio: "inherit",
360
+ env: {
361
+ ...process.env,
362
+ // 设置 VIRTUAL_ENV 环境变量,让 Python 程序知道自己运行在 venv 中
363
+ VIRTUAL_ENV: venvDir,
364
+ PATH: IS_WIN
365
+ ? `${path.join(venvDir, "Scripts")};${process.env.PATH}`
366
+ : `${path.join(venvDir, "bin")}:${process.env.PATH}`,
367
+ },
368
+ });
349
369
 
350
- child.on("error", (err) => {
351
- console.error(`\x1b[31m[✗]\x1b[0m 启动失败: ${err.message}`);
352
- process.exit(1);
353
- });
370
+ child.on("error", (err) => {
371
+ console.error(`\x1b[31m[✗]\x1b[0m 启动失败: ${err.message}`);
372
+ process.exit(1);
373
+ });
354
374
 
355
- child.on("exit", (code) => {
356
- process.exit(code || 0);
357
- });
358
- }
375
+ child.on("exit", (code) => {
376
+ process.exit(code || 0);
377
+ });
359
378
  }
360
379
 
361
380
  main();
package/start.sh CHANGED
@@ -1,20 +1,32 @@
1
1
  #!/bin/bash
2
2
  # MyAgent Unix/macOS 启动脚本
3
- # 用法: ./start.sh [cli|web|tray|server|setup]
3
+ # 用法: ./start.sh [cli|web|tray|server|setup|reinstall]
4
4
  # 支持 npm 全局安装后从任意目录运行
5
+ #
6
+ # 所有依赖安装到独立虚拟环境 ~/.myagent/venv
5
7
 
6
8
  set -euo pipefail
7
9
 
10
+ # ── 颜色 ────────────────────────────────────────────
11
+ BOLD='\033[1m'
12
+ ACCENT='\033[36m'
13
+ INFO='\033[90m'
14
+ SUCCESS='\033[32m'
15
+ WARN='\033[33m'
16
+ ERROR='\033[31m'
17
+ NC='\033[0m'
18
+
19
+ info() { echo -e "${INFO}[i]${NC} $*"; }
20
+ success() { echo -e "${SUCCESS}[✓]${NC} $*"; }
21
+ warn() { echo -e "${WARN}[!]${NC} $*"; }
22
+ err() { echo -e "${ERROR}[✗]${NC} $*" >&2; }
23
+
8
24
  # ── 解析脚本真实路径(兼容 symlink) ─────────
9
- # npm 全局安装时 start.sh 会被 symlink 到 /usr/local/bin/myagent-ai
10
- # 需要解析 symlink 找到实际的包目录
11
25
  resolve_script_dir() {
12
26
  local src="${BASH_SOURCE[0]}"
13
- # 循环解析所有 symlink
14
27
  while [ -L "$src" ]; do
15
28
  local dir="$(cd -P "$(dirname "$src")" && pwd)"
16
29
  src="$(readlink "$src")"
17
- # 如果是相对路径,拼接上目录
18
30
  [[ "$src" != /* ]] && src="$dir/$src"
19
31
  done
20
32
  cd -P "$(dirname "$src")" && pwd
@@ -23,148 +35,111 @@ resolve_script_dir() {
23
35
  SCRIPT_DIR="$(resolve_script_dir)"
24
36
  PROJECT_DIR="$SCRIPT_DIR"
25
37
 
26
- # 如果 main.py 不在脚本目录(npm link 等场景),向上查找
27
38
  if [ ! -f "$PROJECT_DIR/main.py" ]; then
28
- # 尝试 npm global prefix 方式
29
39
  NPM_PKG_DIR="$(npm root -g 2>/dev/null)/myagent-ai"
30
40
  if [ -f "$NPM_PKG_DIR/main.py" ]; then
31
41
  PROJECT_DIR="$NPM_PKG_DIR"
32
42
  else
33
- echo -e "\033[31m[✗]\033[0m 错误: 找不到 main.py"
43
+ err "错误: 找不到 main.py"
34
44
  echo " 脚本目录: $SCRIPT_DIR"
35
45
  echo " 请确保从正确位置运行,或重新安装: npm install -g myagent-ai"
36
46
  exit 1
37
47
  fi
38
48
  fi
39
49
 
40
- BOLD='\033[1m'
41
- ACCENT='\033[36m'
42
- INFO='\033[90m'
43
- SUCCESS='\033[32m'
44
- WARN='\033[33m'
45
- ERROR='\033[31m'
46
- NC='\033[0m'
50
+ # ── 虚拟环境 ────────────────────────────────────────
51
+ VENV_DIR="$HOME/.myagent/venv"
52
+ VENV_PY="$VENV_DIR/bin/python"
53
+ VENV_PIP="$VENV_DIR/bin/pip"
54
+ MYAGENT_DIR="$HOME/.myagent"
47
55
 
48
- info() { echo -e "${INFO}[i]${NC} $*"; }
49
- success() { echo -e "${SUCCESS}[✓]${NC} $*"; }
50
- warn() { echo -e "${WARN}[!]${NC} $*"; }
51
- err() { echo -e "${ERROR}[✗]${NC} $*" >&2; }
52
-
53
- echo ""
54
- echo -e " ${BOLD}MyAgent${NC} - 本地桌面端执行型 AI 助手"
55
- echo ""
56
-
57
- # ── 获取 Python 命令 ───────────────────────────────
58
- get_python() {
59
- hash -r 2>/dev/null || true
60
- if command -v python3 &>/dev/null; then
61
- echo "python3"
62
- elif command -v python &>/dev/null; then
63
- echo "python"
64
- else
56
+ # 查找系统 Python
57
+ find_system_python() {
58
+ hash -r 2>/dev/null || true
59
+ for cmd in python3 python3.14 python3.13 python3.12 python; do
60
+ if command -v "$cmd" &>/dev/null; then
61
+ local ver
62
+ ver="$($cmd -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")' 2>/dev/null)" || continue
63
+ local major minor
64
+ major="$(echo "$ver" | cut -d. -f1)"
65
+ minor="$(echo "$ver" | cut -d. -f2)"
66
+ if [ "$major" -ge 3 ] && [ "$minor" -ge 10 ]; then
67
+ echo "$cmd"
68
+ return 0
69
+ fi
70
+ fi
71
+ done
65
72
  echo ""
66
- fi
67
73
  }
68
74
 
69
- PY="$(get_python)"
70
- if [ -z "$PY" ]; then
71
- err "未找到 Python,请先安装 Python 3.10+"
72
- echo " macOS: brew install python@3.12"
73
- echo " Linux: sudo apt install python3.12 python3-pip"
74
- echo " Windows: https://www.python.org/downloads/"
75
- echo ""
76
- echo " 一键安装: curl -fsSL https://raw.githubusercontent.com/ctz168/myagent/main/install/install.sh | bash"
77
- exit 1
78
- fi
75
+ # 确保虚拟环境存在
76
+ ensure_venv() {
77
+ # 检查现有 venv
78
+ if [ -f "$VENV_PY" ]; then
79
+ if "$VENV_PY" --version &>/dev/null; then
80
+ return 0
81
+ fi
82
+ # venv 损坏,重建
83
+ warn "虚拟环境已损坏,正在重建..."
84
+ rm -rf "$VENV_DIR"
85
+ fi
79
86
 
80
- # ── 检查 Python 版本 ─────────────────────────────
81
- check_python_version() {
82
- local ver major minor
83
- ver="$($PY -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")')"
84
- major="$(echo "$ver" | cut -d. -f1)"
85
- minor="$(echo "$ver" | cut -d. -f2)"
86
- if [ "$major" -lt 3 ] || { [ "$major" -eq 3 ] && [ "$minor" -lt 10 ]; }; then
87
- err "Python $ver detected, but 3.10+ is required"
87
+ # 需要创建
88
+ local sys_py
89
+ sys_py="$(find_system_python)"
90
+ if [ -z "$sys_py" ]; then
91
+ err "未找到 Python 3.10+"
92
+ echo " macOS: brew install python@3.14"
93
+ echo " Linux: sudo apt install python3.14 python3-pip"
94
+ echo ""
95
+ echo " 一键安装: curl -fsSL https://raw.githubusercontent.com/ctz168/myagent/main/install/install.sh | bash"
88
96
  exit 1
89
97
  fi
90
- success "Python $ver"
91
- }
92
- check_python_version
93
-
94
- # ── pip install (处理 PEP668) ──────────────────────
95
- pip_install() {
96
- local req_file="$1"
97
- $PY -m pip install -r "$req_file" --break-system-packages 2>/dev/null || \
98
- $PY -m pip install -r "$req_file" 2>/dev/null || \
99
- {
100
- warn "pip install 失败 (可能受 PEP668 限制)"
101
- info "尝试使用 venv 安装..."
102
- local venv_dir="$HOME/.myagent/venv"
103
- $PY -m venv "$venv_dir" 2>/dev/null
104
- if [ -f "$venv_dir/bin/pip" ]; then
105
- "$venv_dir/bin/pip" install -r "$req_file"
106
- success "已安装到虚拟环境 $venv_dir"
107
- info "启动时请使用: $venv_dir/bin/python main.py --web"
108
- return 0
109
- fi
110
- err "所有安装方式均失败"
111
- info "请手动安装: pip3 install -r $req_file --break-system-packages"
112
- info "或使用虚拟环境: python3 -m venv venv && source venv/bin/activate && pip install -r $req_file"
113
- return 1
114
- }
98
+
99
+ mkdir -p "$MYAGENT_DIR"
100
+ echo -e " ${ACCENT}[*]${NC} 创建独立虚拟环境..."
101
+ info "位置: $VENV_DIR"
102
+ info "系统Python: $sys_py"
103
+ "$sys_py" -m venv "$VENV_DIR"
104
+ success "虚拟环境已创建"
115
105
  }
116
106
 
117
- # ── 检查核心依赖 ─────────────────────────────────
118
- check_deps() {
119
- # 刷新命令缓存
120
- hash -r 2>/dev/null || true
107
+ # 检查并安装依赖
108
+ install_deps() {
121
109
  local missing=0
110
+ local req_file="$PROJECT_DIR/requirements.txt"
111
+
112
+ echo -e " ${ACCENT}[*]${NC} 检查依赖..."
122
113
 
123
- # 必需依赖
124
114
  for mod in openai aiohttp requests; do
125
- if $PY -c "import $mod" 2>/dev/null; then
126
- success "$mod"
115
+ if "$VENV_PY" -c "import $mod" 2>/dev/null; then
116
+ success " $mod"
127
117
  else
128
- warn "$mod 未安装"
118
+ warn " $mod"
129
119
  missing=1
130
120
  fi
131
121
  done
132
122
 
133
- # 内置技能依赖(缺一不可,启动时自动安装)
134
- for mod in duckduckgo_search bs4 psutil playwright mss pynput; do
135
- if $PY -c "import $mod" 2>/dev/null; then
136
- success "$mod"
137
- else
138
- warn "$mod 未安装"
139
- missing=1
140
- fi
141
- done
123
+ if [ "$missing" -eq 0 ]; then
124
+ return 0
125
+ fi
142
126
 
143
- # 可选依赖(不阻断启动)
144
- for mod in pygetwindow PIL edge_tts pystray; do
145
- if $PY -c "import $mod" 2>/dev/null; then
146
- success "$mod"
147
- else
148
- info "$mod 未安装(可选,不影响核心功能)"
149
- fi
150
- done
127
+ # 升级 pip
128
+ echo -e " ${INFO}[i]${NC} 升级 pip..."
129
+ "$VENV_PY" -m pip install --upgrade pip --quiet 2>/dev/null || true
151
130
 
152
- if [ "$missing" -eq 1 ]; then
153
- echo ""
154
- warn "部分核心依赖缺失,正在自动安装..."
155
- local req_file=""
156
- # 优先查找项目目录 requirements.txt
157
- if [ -f "$PROJECT_DIR/requirements.txt" ]; then
158
- req_file="$PROJECT_DIR/requirements.txt"
159
- elif [ -f "requirements.txt" ]; then
160
- req_file="requirements.txt"
161
- fi
162
- if [ -n "$req_file" ]; then
163
- pip_install "$req_file" || warn "依赖安装失败,请手动处理"
164
- else
165
- warn "未找到 requirements.txt,请手动安装依赖"
166
- fi
167
- echo ""
131
+ if [ -f "$req_file" ]; then
132
+ echo -e " ${WARN}[!]${NC} 正在安装缺失依赖到虚拟环境..."
133
+ "$VENV_PY" -m pip install -r "$req_file" --disable-pip-version-check
134
+ success "依赖安装完成"
135
+ else
136
+ warn "未找到 requirements.txt"
137
+ # fallback: 直接安装核心包
138
+ "$VENV_PY" -m pip install --quiet \
139
+ "openai>=1.12.0" "aiohttp>=3.9.0" "requests>=2.31.0" \
140
+ "duckduckgo-search>=6.0.0" "beautifulsoup4>=4.12.0" \
141
+ "lxml>=5.0.0" "psutil>=5.9.0" --disable-pip-version-check 2>/dev/null || true
142
+ success "核心依赖安装完成"
168
143
  fi
169
144
  }
170
145
 
@@ -183,6 +158,9 @@ check_first_run() {
183
158
  MODE="${1:-}"
184
159
 
185
160
  if [ -z "$MODE" ]; then
161
+ echo ""
162
+ echo -e " ${BOLD}MyAgent${NC} - 本地桌面端执行型 AI 助手"
163
+ echo ""
186
164
  echo "选择运行模式:"
187
165
  echo " 1) CLI 交互模式"
188
166
  echo " 2) Web 管理后台 (推荐, 端口 8767)"
@@ -201,42 +179,62 @@ if [ -z "$MODE" ]; then
201
179
  esac
202
180
  fi
203
181
 
204
- # 切换到项目目录(确保 main.py 的相对路径正确)
205
182
  cd "$PROJECT_DIR"
206
183
 
184
+ # 特殊命令: reinstall
185
+ if [ "$MODE" = "reinstall" ]; then
186
+ if [ -d "$VENV_DIR" ]; then
187
+ echo -e " ${ACCENT}[*]${NC} 删除旧虚拟环境..."
188
+ rm -rf "$VENV_DIR"
189
+ fi
190
+ ensure_venv
191
+ install_deps
192
+ echo ""
193
+ success "依赖已全部重新安装到虚拟环境"
194
+ info "虚拟环境: $VENV_DIR"
195
+ echo ""
196
+ exit 0
197
+ fi
198
+
199
+ # ── 主流程 ─────────────────────────────────────────
200
+ echo ""
201
+ echo -e " ${BOLD}${ACCENT}MyAgent${NC} - 本地桌面端执行型 AI 助手"
202
+ echo ""
203
+
204
+ ensure_venv
205
+ install_deps
206
+ check_first_run
207
+
208
+ info "虚拟环境: $VENV_DIR"
209
+ info "Python: $VENV_PY"
210
+ echo ""
211
+
207
212
  case "$MODE" in
208
213
  cli)
209
- check_deps
210
- check_first_run
211
214
  echo -e "${ACCENT}启动 CLI 模式...${NC}"
212
- exec $PY main.py
215
+ exec "$VENV_PY" main.py
213
216
  ;;
214
217
  web)
215
- check_deps
216
- check_first_run
217
218
  echo -e "${ACCENT}启动 Web 管理后台 (http://localhost:8767)...${NC}"
218
219
  echo -e "${INFO}按 Ctrl+C 停止${NC}"
219
220
  echo ""
220
- exec $PY main.py --web
221
+ exec "$VENV_PY" main.py --web
221
222
  ;;
222
223
  tray)
223
- check_deps
224
- check_first_run
225
224
  echo -e "${ACCENT}启动系统托盘模式...${NC}"
226
- exec $PY main.py --tray
225
+ exec "$VENV_PY" main.py --tray
227
226
  ;;
228
227
  server)
229
- check_deps
230
228
  echo -e "${ACCENT}启动纯 API 服务...${NC}"
231
- exec $PY main.py --server
229
+ exec "$VENV_PY" main.py --server
232
230
  ;;
233
231
  setup)
234
232
  echo -e "${ACCENT}启动配置向导...${NC}"
235
- exec $PY main.py --setup
233
+ exec "$VENV_PY" main.py --setup
236
234
  ;;
237
235
  *)
238
236
  err "未知模式: $MODE"
239
- echo "可用模式: cli | web | tray | server | setup"
237
+ echo "可用模式: cli | web | tray | server | setup | reinstall"
240
238
  exit 1
241
239
  ;;
242
240
  esac