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 +1 -1
- package/install/install.ps1 +16 -12
- package/install/install.sh +15 -24
- package/main.py +28 -22
- package/package.json +1 -1
- package/setup.py +1 -1
- package/start.js +261 -242
- package/start.sh +129 -131
package/core/version.py
CHANGED
package/install/install.ps1
CHANGED
|
@@ -9,7 +9,7 @@ param(
|
|
|
9
9
|
|
|
10
10
|
$ErrorActionPreference = "Stop"
|
|
11
11
|
$PKG_NAME = "myagent-ai"
|
|
12
|
-
$PKG_VERSION = "1.
|
|
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
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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 ──────────────────────────────
|
package/install/install.sh
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
373
|
-
|
|
374
|
-
"
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
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(
|
|
812
|
-
"""动态生成状态标题"""
|
|
811
|
+
def _get_status_title(item):
|
|
812
|
+
"""动态生成状态标题 (pystray text callable: text(menu_item))"""
|
|
813
813
|
if not app._running:
|
|
814
|
-
return "
|
|
815
|
-
return "
|
|
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(
|
|
824
|
+
icon.menu = _build_menu()
|
|
825
825
|
icon.update_menu()
|
|
826
826
|
except Exception:
|
|
827
827
|
pass # 部分平台不支持动态菜单刷新
|
|
828
828
|
|
|
829
|
-
def _build_menu(
|
|
830
|
-
"""构建菜单"""
|
|
829
|
+
def _build_menu():
|
|
830
|
+
"""构建菜单 (Windows Win32 不支持 BMP 外 emoji, 使用纯文本)"""
|
|
831
831
|
return pystray.Menu(
|
|
832
832
|
pystray.MenuItem(
|
|
833
|
-
|
|
833
|
+
_get_status_title,
|
|
834
834
|
None, enabled=False,
|
|
835
835
|
),
|
|
836
836
|
pystray.Menu.SEPARATOR,
|
|
837
|
-
pystray.MenuItem("
|
|
838
|
-
pystray.MenuItem("
|
|
839
|
-
pystray.MenuItem("
|
|
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("
|
|
842
|
-
pystray.MenuItem("
|
|
843
|
-
pystray.MenuItem("
|
|
841
|
+
pystray.MenuItem("打开工作目录", open_workdir),
|
|
842
|
+
pystray.MenuItem("打开日志目录", open_logs),
|
|
843
|
+
pystray.MenuItem("打开配置目录", open_config_dir),
|
|
844
844
|
pystray.Menu.SEPARATOR,
|
|
845
|
-
pystray.MenuItem("
|
|
846
|
-
pystray.MenuItem("
|
|
845
|
+
pystray.MenuItem("开机自启", toggle_autostart, checked=_autostart_checked),
|
|
846
|
+
pystray.MenuItem("重启服务", restart_service),
|
|
847
847
|
pystray.Menu.SEPARATOR,
|
|
848
|
-
pystray.MenuItem("
|
|
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
|
-
|
|
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, "
|
|
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, "
|
|
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
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.
|
|
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
|
-
* -
|
|
7
|
-
* -
|
|
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
|
|
12
|
-
* myagent-ai cli
|
|
13
|
-
* myagent-ai tray
|
|
14
|
-
* myagent-ai server
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
// ──
|
|
63
|
-
function
|
|
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
|
-
// ──
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
// ──
|
|
155
|
-
|
|
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
|
|
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
|
|
205
|
+
let missing = [];
|
|
202
206
|
|
|
203
|
-
|
|
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
|
-
|
|
218
|
-
|
|
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
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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
|
-
|
|
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
|
-
|
|
286
|
-
|
|
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
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
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
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
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
|
-
|
|
325
|
-
|
|
326
|
-
|
|
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
|
-
|
|
330
|
-
|
|
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
|
-
|
|
334
|
-
|
|
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
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
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
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
370
|
+
child.on("error", (err) => {
|
|
371
|
+
console.error(`\x1b[31m[✗]\x1b[0m 启动失败: ${err.message}`);
|
|
372
|
+
process.exit(1);
|
|
373
|
+
});
|
|
354
374
|
|
|
355
|
-
|
|
356
|
-
|
|
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
|
-
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
#
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
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 $
|
|
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
|
-
|
|
135
|
-
|
|
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
|
-
|
|
145
|
-
|
|
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 [ "$
|
|
153
|
-
echo ""
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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 $
|
|
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 $
|
|
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 $
|
|
225
|
+
exec "$VENV_PY" main.py --tray
|
|
227
226
|
;;
|
|
228
227
|
server)
|
|
229
|
-
check_deps
|
|
230
228
|
echo -e "${ACCENT}启动纯 API 服务...${NC}"
|
|
231
|
-
exec $
|
|
229
|
+
exec "$VENV_PY" main.py --server
|
|
232
230
|
;;
|
|
233
231
|
setup)
|
|
234
232
|
echo -e "${ACCENT}启动配置向导...${NC}"
|
|
235
|
-
exec $
|
|
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
|