@agentunion/kite 1.3.2 → 1.4.0

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.
Files changed (78) hide show
  1. package/CHANGELOG.md +200 -0
  2. package/cli.js +76 -0
  3. package/extensions/agents/assistant/entry.py +111 -1
  4. package/extensions/agents/assistant/server.py +263 -215
  5. package/extensions/channels/acp_channel/entry.py +111 -1
  6. package/extensions/channels/acp_channel/module.md +23 -22
  7. package/extensions/channels/acp_channel/server.py +263 -215
  8. package/extensions/event_hub_bench/entry.py +107 -1
  9. package/extensions/services/backup/entry.py +299 -21
  10. package/extensions/services/backup/module.md +24 -22
  11. package/extensions/services/model_service/entry.py +145 -19
  12. package/extensions/services/model_service/module.md +21 -22
  13. package/extensions/services/watchdog/entry.py +188 -25
  14. package/extensions/services/watchdog/monitor.py +144 -34
  15. package/extensions/services/web/WEBSOCKET_STATUS.md +143 -0
  16. package/extensions/services/web/config_example.py +35 -0
  17. package/extensions/services/web/config_loader.py +110 -0
  18. package/extensions/services/web/entry.py +114 -26
  19. package/extensions/services/web/module.md +35 -24
  20. package/extensions/services/web/pairing.py +250 -0
  21. package/extensions/services/web/pairing_codes.jsonl +16 -0
  22. package/extensions/services/web/relay.py +643 -0
  23. package/extensions/services/web/relay_config.json5 +67 -0
  24. package/extensions/services/web/routes/routes_management_ws.py +127 -0
  25. package/extensions/services/web/routes/routes_rpc.py +89 -0
  26. package/extensions/services/web/routes/routes_test.py +61 -0
  27. package/extensions/services/web/routes/schemas.py +0 -22
  28. package/extensions/services/web/server.py +421 -98
  29. package/extensions/services/web/static/css/style.css +67 -28
  30. package/extensions/services/web/static/index.html +234 -44
  31. package/extensions/services/web/static/js/app.js +1335 -48
  32. package/extensions/services/web/static/js/kernel-client-example.js +161 -0
  33. package/extensions/services/web/static/js/kernel-client.js +383 -0
  34. package/extensions/services/web/static/js/registry-tests.js +558 -0
  35. package/extensions/services/web/static/js/token-manager.js +175 -0
  36. package/extensions/services/web/static/pairing.html +248 -0
  37. package/extensions/services/web/static/test_registry.html +262 -0
  38. package/extensions/services/web/web_config.json5 +29 -0
  39. package/kernel/entry.py +120 -32
  40. package/kernel/event_hub.py +141 -16
  41. package/kernel/module.md +36 -33
  42. package/kernel/registry_store.py +48 -15
  43. package/kernel/rpc_router.py +120 -53
  44. package/kernel/server.py +219 -12
  45. package/kite_cli/__init__.py +3 -0
  46. package/kite_cli/__main__.py +5 -0
  47. package/kite_cli/commands/__init__.py +1 -0
  48. package/kite_cli/commands/clean.py +101 -0
  49. package/kite_cli/commands/doctor.py +35 -0
  50. package/kite_cli/commands/history.py +111 -0
  51. package/kite_cli/commands/info.py +96 -0
  52. package/kite_cli/commands/install.py +313 -0
  53. package/kite_cli/commands/list.py +143 -0
  54. package/kite_cli/commands/log.py +81 -0
  55. package/kite_cli/commands/rollback.py +88 -0
  56. package/kite_cli/commands/search.py +73 -0
  57. package/kite_cli/commands/uninstall.py +85 -0
  58. package/kite_cli/commands/update.py +118 -0
  59. package/kite_cli/core/__init__.py +1 -0
  60. package/kite_cli/core/checker.py +142 -0
  61. package/kite_cli/core/dependency.py +229 -0
  62. package/kite_cli/core/downloader.py +209 -0
  63. package/kite_cli/core/install_info.py +40 -0
  64. package/kite_cli/core/tool_installer.py +397 -0
  65. package/kite_cli/core/validator.py +78 -0
  66. package/kite_cli/main.py +289 -0
  67. package/kite_cli/utils/__init__.py +1 -0
  68. package/kite_cli/utils/i18n.py +252 -0
  69. package/kite_cli/utils/interactive.py +63 -0
  70. package/kite_cli/utils/operation_log.py +77 -0
  71. package/kite_cli/utils/paths.py +34 -0
  72. package/kite_cli/utils/version.py +308 -0
  73. package/launcher/entry.py +819 -158
  74. package/launcher/logging_setup.py +104 -0
  75. package/launcher/module.md +37 -37
  76. package/package.json +2 -1
  77. package/scripts/plan_manager.py +315 -0
  78. package/extensions/services/web/routes/routes_modules.py +0 -249
@@ -0,0 +1,209 @@
1
+ """下载器 - 从各平台下载模块(改进版,带详细错误提示)"""
2
+ import subprocess
3
+ import tempfile
4
+ import shutil
5
+ import zipfile
6
+ from pathlib import Path
7
+ from typing import Optional, Tuple
8
+ from kite_cli.core.tool_installer import ToolInstaller
9
+
10
+
11
+ class DownloadError(Exception):
12
+ """下载错误"""
13
+ pass
14
+
15
+
16
+ class Downloader:
17
+ """从各平台下载模块"""
18
+
19
+ @staticmethod
20
+ def download_pypi(name: str, dest: Path, version: str = None) -> Tuple[Optional[Path], Optional[str]]:
21
+ """
22
+ 从 PyPI 下载包
23
+ Args:
24
+ name: 包名
25
+ dest: 目标目录
26
+ version: 版本号(可选),如 "1.2.0"
27
+ 返回: (下载路径, 错误信息)
28
+ """
29
+ # 确保 pip 已安装
30
+ if not ToolInstaller.ensure_tool("pip"):
31
+ return None, "pip 未安装且自动安装失败,请手动安装 Python 和 pip"
32
+
33
+ # 构建包名(带版本)
34
+ package_spec = f"{name}=={version}" if version else name
35
+
36
+ try:
37
+ dest.mkdir(parents=True, exist_ok=True)
38
+ result = subprocess.run(
39
+ ["pip", "download", package_spec, "--dest", str(dest), "--no-deps"],
40
+ capture_output=True,
41
+ text=True,
42
+ timeout=60
43
+ )
44
+
45
+ if result.returncode != 0:
46
+ # pip 命令执行失败
47
+ error_msg = result.stderr.strip() if result.stderr else result.stdout.strip()
48
+ if "Could not find" in error_msg or "No matching distribution" in error_msg:
49
+ return None, f"PyPI 上未找到包 '{name}'"
50
+ elif "Network" in error_msg or "timeout" in error_msg.lower():
51
+ return None, f"网络错误: 无法连接到 PyPI"
52
+ else:
53
+ return None, f"pip 下载失败: {error_msg[:200]}"
54
+
55
+ # 查找下载的文件
56
+ files = list(dest.glob("*"))
57
+ if not files:
58
+ return None, "下载成功但未找到文件"
59
+
60
+ # 解压(.whl 或 .tar.gz)
61
+ downloaded_file = files[0]
62
+ extract_dir = dest / "extracted"
63
+ extract_dir.mkdir(exist_ok=True)
64
+
65
+ try:
66
+ # .whl 文件是 zip 格式
67
+ if downloaded_file.suffix == '.whl':
68
+ with zipfile.ZipFile(downloaded_file, 'r') as zip_ref:
69
+ zip_ref.extractall(extract_dir)
70
+ else:
71
+ shutil.unpack_archive(downloaded_file, extract_dir)
72
+ return extract_dir, None
73
+ except Exception as e:
74
+ return None, f"解压失败: {str(e)}"
75
+
76
+ except FileNotFoundError:
77
+ return None, "pip 命令未找到,请先安装 Python 和 pip"
78
+ except subprocess.TimeoutExpired:
79
+ return None, "下载超时(60秒),请检查网络连接"
80
+ except Exception as e:
81
+ return None, f"未知错误: {str(e)}"
82
+
83
+ @staticmethod
84
+ def download_npm(name: str, dest: Path, version: str = None) -> Tuple[Optional[Path], Optional[str]]:
85
+ """
86
+ 从 npm 下载包
87
+ Args:
88
+ name: 包名
89
+ dest: 目标目录
90
+ version: 版本号(可选),如 "1.2.0"
91
+ 返回: (下载路径, 错误信息)
92
+ """
93
+ # 确保 npm 已安装
94
+ if not ToolInstaller.ensure_tool("npm"):
95
+ return None, "npm 未安装且自动安装失败,请手动安装 Node.js 和 npm"
96
+
97
+ # 构建包名(带版本)
98
+ package_spec = f"{name}@{version}" if version else name
99
+
100
+ try:
101
+ dest.mkdir(parents=True, exist_ok=True)
102
+ result = subprocess.run(
103
+ ["npm", "pack", package_spec, "--pack-destination", str(dest)],
104
+ capture_output=True,
105
+ text=True,
106
+ timeout=60
107
+ )
108
+
109
+ if result.returncode != 0:
110
+ error_msg = result.stderr.strip() if result.stderr else result.stdout.strip()
111
+ if "404" in error_msg or "Not Found" in error_msg:
112
+ return None, f"npm 上未找到包 '{name}'"
113
+ elif "network" in error_msg.lower() or "ETIMEDOUT" in error_msg:
114
+ return None, "网络错误: 无法连接到 npm registry"
115
+ else:
116
+ return None, f"npm 下载失败: {error_msg[:200]}"
117
+
118
+ # 查找 .tgz 文件
119
+ tgz_files = list(dest.glob("*.tgz"))
120
+ if not tgz_files:
121
+ return None, "下载成功但未找到 .tgz 文件"
122
+
123
+ tgz_file = tgz_files[0]
124
+ extract_dir = dest / "extracted"
125
+ extract_dir.mkdir(exist_ok=True)
126
+
127
+ try:
128
+ shutil.unpack_archive(tgz_file, extract_dir)
129
+ # npm pack 会创建 package/ 子目录
130
+ package_dir = extract_dir / "package"
131
+ if package_dir.exists():
132
+ return package_dir, None
133
+ return extract_dir, None
134
+ except Exception as e:
135
+ return None, f"解压失败: {str(e)}"
136
+
137
+ except FileNotFoundError:
138
+ return None, "npm 命令未找到,请先安装 Node.js 和 npm"
139
+ except subprocess.TimeoutExpired:
140
+ return None, "下载超时(60秒),请检查网络连接"
141
+ except Exception as e:
142
+ return None, f"未知错误: {str(e)}"
143
+
144
+ @staticmethod
145
+ def download_git(url: str, dest: Path) -> Tuple[Optional[Path], Optional[str]]:
146
+ """
147
+ 从 Git 克隆仓库
148
+ 返回: (下载路径, 错误信息)
149
+ """
150
+ # 确保 git 已安装
151
+ if not ToolInstaller.ensure_tool("git"):
152
+ return None, "git 未安装且自动安装失败,请手动安装 Git"
153
+
154
+ try:
155
+ dest.mkdir(parents=True, exist_ok=True)
156
+ clone_dir = dest / "repo"
157
+ result = subprocess.run(
158
+ ["git", "clone", url, str(clone_dir)],
159
+ capture_output=True,
160
+ text=True,
161
+ timeout=120
162
+ )
163
+
164
+ if result.returncode != 0:
165
+ error_msg = result.stderr.strip() if result.stderr else result.stdout.strip()
166
+ if "not found" in error_msg.lower() or "does not exist" in error_msg.lower():
167
+ return None, f"Git 仓库不存在: {url}"
168
+ elif "Authentication failed" in error_msg or "Permission denied" in error_msg:
169
+ return None, "认证失败: 仓库可能是私有的或需要登录"
170
+ elif "network" in error_msg.lower() or "timeout" in error_msg.lower():
171
+ return None, "网络错误: 无法连接到 Git 服务器"
172
+ else:
173
+ return None, f"git clone 失败: {error_msg[:200]}"
174
+
175
+ if not clone_dir.exists():
176
+ return None, "克隆成功但目录不存在"
177
+
178
+ return clone_dir, None
179
+
180
+ except FileNotFoundError:
181
+ return None, "git 命令未找到,请先安装 Git"
182
+ except subprocess.TimeoutExpired:
183
+ return None, "克隆超时(120秒),仓库可能太大或网络太慢"
184
+ except Exception as e:
185
+ return None, f"未知错误: {str(e)}"
186
+
187
+ @staticmethod
188
+ def download_local(path: str, dest: Path) -> Tuple[Optional[Path], Optional[str]]:
189
+ """
190
+ 从本地路径复制
191
+ 返回: (下载路径, 错误信息)
192
+ """
193
+ try:
194
+ src = Path(path).resolve()
195
+ if not src.exists():
196
+ return None, f"本地路径不存在: {path}"
197
+
198
+ if not src.is_dir():
199
+ return None, f"路径不是目录: {path}"
200
+
201
+ dest.mkdir(parents=True, exist_ok=True)
202
+ copy_dir = dest / "local"
203
+ shutil.copytree(src, copy_dir)
204
+ return copy_dir, None
205
+
206
+ except PermissionError:
207
+ return None, f"权限不足: 无法读取 {path}"
208
+ except Exception as e:
209
+ return None, f"复制失败: {str(e)}"
@@ -0,0 +1,40 @@
1
+ """安装信息记录"""
2
+ import json
3
+ from datetime import datetime
4
+ from pathlib import Path
5
+ from kite_cli import __version__
6
+
7
+
8
+ def record_install_info(module_dir: Path, source_info: dict, module_data: dict, location: str):
9
+ """记录安装信息到 .kite_install.json"""
10
+ install_info = {
11
+ "name": module_data.get("name"),
12
+ "version": module_data.get("version", "unknown"),
13
+ "source": source_info,
14
+ "installed_at": datetime.utcnow().isoformat() + "Z",
15
+ "installed_by": f"kite-cli@{__version__}",
16
+ "location": location,
17
+ "dependencies": module_data.get("dependencies", {})
18
+ }
19
+
20
+ install_info_path = module_dir / ".kite_install.json"
21
+ try:
22
+ with open(install_info_path, "w", encoding="utf-8") as f:
23
+ json.dump(install_info, f, indent=2, ensure_ascii=False)
24
+ return True
25
+ except Exception as e:
26
+ print(f"[Warning] 记录安装信息失败: {e}")
27
+ return False
28
+
29
+
30
+ def read_install_info(module_dir: Path) -> dict:
31
+ """读取安装信息"""
32
+ install_info_path = module_dir / ".kite_install.json"
33
+ if not install_info_path.exists():
34
+ return None
35
+
36
+ try:
37
+ with open(install_info_path, encoding="utf-8") as f:
38
+ return json.load(f)
39
+ except Exception:
40
+ return None
@@ -0,0 +1,397 @@
1
+ """下载工具自动安装器 - 安装 pip/npm/git 等基础工具"""
2
+ import subprocess
3
+ import sys
4
+ import platform
5
+ from pathlib import Path
6
+
7
+
8
+ class ToolInstaller:
9
+ """下载工具安装器"""
10
+
11
+ @staticmethod
12
+ def check_tool(tool_name: str) -> bool:
13
+ """检查工具是否已安装"""
14
+ try:
15
+ # Windows 上某些命令需要 shell=True (如 npm.cmd)
16
+ result = subprocess.run(
17
+ [tool_name, "--version"],
18
+ capture_output=True,
19
+ timeout=5,
20
+ shell=True # Windows 兼容性
21
+ )
22
+ return result.returncode == 0
23
+ except (FileNotFoundError, subprocess.TimeoutExpired):
24
+ return False
25
+
26
+ @staticmethod
27
+ def install_python() -> bool:
28
+ """安装 Python(如果不存在)"""
29
+ if ToolInstaller.check_tool("python") or ToolInstaller.check_tool("python3"):
30
+ return True
31
+
32
+ print("[Install] Python 未安装,正在安装...")
33
+
34
+ system = platform.system()
35
+
36
+ if system == "Windows":
37
+ return ToolInstaller._install_python_windows()
38
+ elif system == "Darwin": # macOS
39
+ return ToolInstaller._install_python_macos()
40
+ elif system == "Linux":
41
+ return ToolInstaller._install_python_linux()
42
+ else:
43
+ print(f"[Error] 不支持的操作系统: {system}")
44
+ return False
45
+
46
+ @staticmethod
47
+ def _install_python_windows() -> bool:
48
+ """Windows 上安装 Python"""
49
+ print("[Info] Windows 系统需要手动安装 Python")
50
+ print("[Info] 请访问: https://www.python.org/downloads/")
51
+ print("[Info] 或使用 winget: winget install Python.Python.3.12")
52
+ return False
53
+
54
+ @staticmethod
55
+ def _install_python_macos() -> bool:
56
+ """macOS 上安装 Python"""
57
+ # 检查是否有 brew
58
+ if not ToolInstaller.check_tool("brew"):
59
+ print("[Error] 需要先安装 Homebrew")
60
+ print("[Info] 访问: https://brew.sh/")
61
+ return False
62
+
63
+ try:
64
+ print("[Install] 使用 Homebrew 安装 Python...")
65
+ result = subprocess.run(
66
+ ["brew", "install", "python@3.12"],
67
+ capture_output=True,
68
+ text=True,
69
+ timeout=300
70
+ )
71
+ if result.returncode == 0:
72
+ print("[Done] Python 安装完成")
73
+ return True
74
+ else:
75
+ print(f"[Error] 安装失败: {result.stderr}")
76
+ return False
77
+ except Exception as e:
78
+ print(f"[Error] 安装失败: {e}")
79
+ return False
80
+
81
+ @staticmethod
82
+ def _install_python_linux() -> bool:
83
+ """Linux 上安装 Python"""
84
+ # 检测包管理器
85
+ if ToolInstaller.check_tool("apt"):
86
+ pkg_manager = "apt"
87
+ install_cmd = ["sudo", "apt", "update", "&&", "sudo", "apt", "install", "-y", "python3", "python3-pip"]
88
+ elif ToolInstaller.check_tool("yum"):
89
+ pkg_manager = "yum"
90
+ install_cmd = ["sudo", "yum", "install", "-y", "python3", "python3-pip"]
91
+ elif ToolInstaller.check_tool("dnf"):
92
+ pkg_manager = "dnf"
93
+ install_cmd = ["sudo", "dnf", "install", "-y", "python3", "python3-pip"]
94
+ else:
95
+ print("[Error] 未检测到支持的包管理器 (apt/yum/dnf)")
96
+ return False
97
+
98
+ try:
99
+ print(f"[Install] 使用 {pkg_manager} 安装 Python...")
100
+ result = subprocess.run(
101
+ " ".join(install_cmd),
102
+ shell=True,
103
+ capture_output=True,
104
+ text=True,
105
+ timeout=300
106
+ )
107
+ if result.returncode == 0:
108
+ print("[Done] Python 安装完成")
109
+ return True
110
+ else:
111
+ print(f"[Error] 安装失败: {result.stderr}")
112
+ return False
113
+ except Exception as e:
114
+ print(f"[Error] 安装失败: {e}")
115
+ return False
116
+
117
+ @staticmethod
118
+ def install_pip() -> bool:
119
+ """安装 pip(如果不存在)"""
120
+ if ToolInstaller.check_tool("pip") or ToolInstaller.check_tool("pip3"):
121
+ return True
122
+
123
+ print("[Install] pip 未安装,正在安装...")
124
+
125
+ # 尝试使用 ensurepip
126
+ try:
127
+ python_cmd = "python" if ToolInstaller.check_tool("python") else "python3"
128
+ result = subprocess.run(
129
+ [python_cmd, "-m", "ensurepip", "--upgrade"],
130
+ capture_output=True,
131
+ text=True,
132
+ timeout=60
133
+ )
134
+ if result.returncode == 0:
135
+ print("[Done] pip 安装完成")
136
+ return True
137
+ except Exception:
138
+ pass
139
+
140
+ # 尝试使用 get-pip.py
141
+ try:
142
+ import urllib.request
143
+ print("[Install] 下载 get-pip.py...")
144
+ url = "https://bootstrap.pypa.io/get-pip.py"
145
+ get_pip_path = Path.home() / "get-pip.py"
146
+
147
+ urllib.request.urlretrieve(url, get_pip_path)
148
+
149
+ python_cmd = "python" if ToolInstaller.check_tool("python") else "python3"
150
+ result = subprocess.run(
151
+ [python_cmd, str(get_pip_path)],
152
+ capture_output=True,
153
+ text=True,
154
+ timeout=120
155
+ )
156
+
157
+ get_pip_path.unlink() # 删除临时文件
158
+
159
+ if result.returncode == 0:
160
+ print("[Done] pip 安装完成")
161
+ return True
162
+ else:
163
+ print(f"[Error] 安装失败: {result.stderr}")
164
+ return False
165
+
166
+ except Exception as e:
167
+ print(f"[Error] 安装失败: {e}")
168
+ return False
169
+
170
+ @staticmethod
171
+ def install_nodejs() -> bool:
172
+ """安装 Node.js 和 npm"""
173
+ if ToolInstaller.check_tool("node") and ToolInstaller.check_tool("npm"):
174
+ return True
175
+
176
+ print("[Install] Node.js/npm 未安装,正在安装...")
177
+
178
+ system = platform.system()
179
+
180
+ if system == "Windows":
181
+ return ToolInstaller._install_nodejs_windows()
182
+ elif system == "Darwin":
183
+ return ToolInstaller._install_nodejs_macos()
184
+ elif system == "Linux":
185
+ return ToolInstaller._install_nodejs_linux()
186
+ else:
187
+ print(f"[Error] 不支持的操作系统: {system}")
188
+ return False
189
+
190
+ @staticmethod
191
+ def _install_nodejs_windows() -> bool:
192
+ """Windows 上安装 Node.js"""
193
+ print("[Info] Windows 系统需要手动安装 Node.js")
194
+ print("[Info] 请访问: https://nodejs.org/")
195
+ print("[Info] 或使用 winget: winget install OpenJS.NodeJS")
196
+ return False
197
+
198
+ @staticmethod
199
+ def _install_nodejs_macos() -> bool:
200
+ """macOS 上安装 Node.js"""
201
+ if not ToolInstaller.check_tool("brew"):
202
+ print("[Error] 需要先安装 Homebrew")
203
+ print("[Info] 访问: https://brew.sh/")
204
+ return False
205
+
206
+ try:
207
+ print("[Install] 使用 Homebrew 安装 Node.js...")
208
+ result = subprocess.run(
209
+ ["brew", "install", "node"],
210
+ capture_output=True,
211
+ text=True,
212
+ timeout=300
213
+ )
214
+ if result.returncode == 0:
215
+ print("[Done] Node.js 安装完成")
216
+ return True
217
+ else:
218
+ print(f"[Error] 安装失败: {result.stderr}")
219
+ return False
220
+ except Exception as e:
221
+ print(f"[Error] 安装失败: {e}")
222
+ return False
223
+
224
+ @staticmethod
225
+ def _install_nodejs_linux() -> bool:
226
+ """Linux 上安装 Node.js"""
227
+ # 使用 NodeSource 仓库安装最新 LTS 版本
228
+ if ToolInstaller.check_tool("apt"):
229
+ try:
230
+ print("[Install] 添加 NodeSource 仓库...")
231
+ # 下载并执行 NodeSource 安装脚本
232
+ result = subprocess.run(
233
+ "curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -",
234
+ shell=True,
235
+ capture_output=True,
236
+ text=True,
237
+ timeout=120
238
+ )
239
+ if result.returncode != 0:
240
+ print(f"[Error] 添加仓库失败: {result.stderr}")
241
+ return False
242
+
243
+ print("[Install] 安装 Node.js...")
244
+ result = subprocess.run(
245
+ ["sudo", "apt", "install", "-y", "nodejs"],
246
+ capture_output=True,
247
+ text=True,
248
+ timeout=300
249
+ )
250
+ if result.returncode == 0:
251
+ print("[Done] Node.js 安装完成")
252
+ return True
253
+ else:
254
+ print(f"[Error] 安装失败: {result.stderr}")
255
+ return False
256
+ except Exception as e:
257
+ print(f"[Error] 安装失败: {e}")
258
+ return False
259
+
260
+ elif ToolInstaller.check_tool("yum") or ToolInstaller.check_tool("dnf"):
261
+ pkg_manager = "dnf" if ToolInstaller.check_tool("dnf") else "yum"
262
+ try:
263
+ print(f"[Install] 使用 {pkg_manager} 安装 Node.js...")
264
+ result = subprocess.run(
265
+ ["sudo", pkg_manager, "install", "-y", "nodejs", "npm"],
266
+ capture_output=True,
267
+ text=True,
268
+ timeout=300
269
+ )
270
+ if result.returncode == 0:
271
+ print("[Done] Node.js 安装完成")
272
+ return True
273
+ else:
274
+ print(f"[Error] 安装失败: {result.stderr}")
275
+ return False
276
+ except Exception as e:
277
+ print(f"[Error] 安装失败: {e}")
278
+ return False
279
+ else:
280
+ print("[Error] 未检测到支持的包管理器")
281
+ return False
282
+
283
+ @staticmethod
284
+ def install_git() -> bool:
285
+ """安装 Git"""
286
+ if ToolInstaller.check_tool("git"):
287
+ return True
288
+
289
+ print("[Install] Git 未安装,正在安装...")
290
+
291
+ system = platform.system()
292
+
293
+ if system == "Windows":
294
+ return ToolInstaller._install_git_windows()
295
+ elif system == "Darwin":
296
+ return ToolInstaller._install_git_macos()
297
+ elif system == "Linux":
298
+ return ToolInstaller._install_git_linux()
299
+ else:
300
+ print(f"[Error] 不支持的操作系统: {system}")
301
+ return False
302
+
303
+ @staticmethod
304
+ def _install_git_windows() -> bool:
305
+ """Windows 上安装 Git"""
306
+ print("[Info] Windows 系统需要手动安装 Git")
307
+ print("[Info] 请访问: https://git-scm.com/download/win")
308
+ print("[Info] 或使用 winget: winget install Git.Git")
309
+ return False
310
+
311
+ @staticmethod
312
+ def _install_git_macos() -> bool:
313
+ """macOS 上安装 Git"""
314
+ if not ToolInstaller.check_tool("brew"):
315
+ print("[Error] 需要先安装 Homebrew")
316
+ print("[Info] 访问: https://brew.sh/")
317
+ return False
318
+
319
+ try:
320
+ print("[Install] 使用 Homebrew 安装 Git...")
321
+ result = subprocess.run(
322
+ ["brew", "install", "git"],
323
+ capture_output=True,
324
+ text=True,
325
+ timeout=300
326
+ )
327
+ if result.returncode == 0:
328
+ print("[Done] Git 安装完成")
329
+ return True
330
+ else:
331
+ print(f"[Error] 安装失败: {result.stderr}")
332
+ return False
333
+ except Exception as e:
334
+ print(f"[Error] 安装失败: {e}")
335
+ return False
336
+
337
+ @staticmethod
338
+ def _install_git_linux() -> bool:
339
+ """Linux 上安装 Git"""
340
+ if ToolInstaller.check_tool("apt"):
341
+ pkg_manager = "apt"
342
+ install_cmd = ["sudo", "apt", "update", "&&", "sudo", "apt", "install", "-y", "git"]
343
+ elif ToolInstaller.check_tool("yum"):
344
+ pkg_manager = "yum"
345
+ install_cmd = ["sudo", "yum", "install", "-y", "git"]
346
+ elif ToolInstaller.check_tool("dnf"):
347
+ pkg_manager = "dnf"
348
+ install_cmd = ["sudo", "dnf", "install", "-y", "git"]
349
+ else:
350
+ print("[Error] 未检测到支持的包管理器")
351
+ return False
352
+
353
+ try:
354
+ print(f"[Install] 使用 {pkg_manager} 安装 Git...")
355
+ result = subprocess.run(
356
+ " ".join(install_cmd),
357
+ shell=True,
358
+ capture_output=True,
359
+ text=True,
360
+ timeout=300
361
+ )
362
+ if result.returncode == 0:
363
+ print("[Done] Git 安装完成")
364
+ return True
365
+ else:
366
+ print(f"[Error] 安装失败: {result.stderr}")
367
+ return False
368
+ except Exception as e:
369
+ print(f"[Error] 安装失败: {e}")
370
+ return False
371
+
372
+ @classmethod
373
+ def ensure_tool(cls, tool: str) -> bool:
374
+ """确保工具已安装,如果没有则尝试安装"""
375
+ if tool in ["python", "python3"]:
376
+ if cls.check_tool("python") or cls.check_tool("python3"):
377
+ return True
378
+ return cls.install_python()
379
+
380
+ elif tool in ["pip", "pip3"]:
381
+ if cls.check_tool("pip") or cls.check_tool("pip3"):
382
+ return True
383
+ return cls.install_pip()
384
+
385
+ elif tool in ["node", "npm"]:
386
+ if cls.check_tool("node") and cls.check_tool("npm"):
387
+ return True
388
+ return cls.install_nodejs()
389
+
390
+ elif tool == "git":
391
+ if cls.check_tool("git"):
392
+ return True
393
+ return cls.install_git()
394
+
395
+ else:
396
+ print(f"[Error] 未知工具: {tool}")
397
+ return False