@agentunion/kite 1.3.1 → 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.
- package/CHANGELOG.md +287 -1
- package/cli.js +76 -0
- package/extensions/agents/assistant/entry.py +111 -1
- package/extensions/agents/assistant/server.py +263 -197
- package/extensions/channels/acp_channel/entry.py +111 -1
- package/extensions/channels/acp_channel/module.md +23 -22
- package/extensions/channels/acp_channel/server.py +263 -197
- package/extensions/event_hub_bench/entry.py +107 -1
- package/extensions/services/backup/entry.py +408 -72
- package/extensions/services/backup/module.md +24 -22
- package/extensions/services/model_service/entry.py +255 -71
- package/extensions/services/model_service/module.md +21 -22
- package/extensions/services/watchdog/entry.py +344 -90
- package/extensions/services/watchdog/monitor.py +237 -21
- package/extensions/services/web/WEBSOCKET_STATUS.md +143 -0
- package/extensions/services/web/config_example.py +35 -0
- package/extensions/services/web/config_loader.py +110 -0
- package/extensions/services/web/entry.py +114 -26
- package/extensions/services/web/module.md +35 -24
- package/extensions/services/web/pairing.py +250 -0
- package/extensions/services/web/pairing_codes.jsonl +16 -0
- package/extensions/services/web/relay.py +643 -0
- package/extensions/services/web/relay_config.json5 +67 -0
- package/extensions/services/web/routes/routes_management_ws.py +127 -0
- package/extensions/services/web/routes/routes_rpc.py +89 -0
- package/extensions/services/web/routes/routes_test.py +61 -0
- package/extensions/services/web/server.py +445 -99
- package/extensions/services/web/static/css/style.css +138 -2
- package/extensions/services/web/static/index.html +295 -2
- package/extensions/services/web/static/js/app.js +1579 -5
- package/extensions/services/web/static/js/kernel-client-example.js +161 -0
- package/extensions/services/web/static/js/kernel-client.js +383 -0
- package/extensions/services/web/static/js/registry-tests.js +558 -0
- package/extensions/services/web/static/js/token-manager.js +175 -0
- package/extensions/services/web/static/pairing.html +248 -0
- package/extensions/services/web/static/test_registry.html +262 -0
- package/extensions/services/web/web_config.json5 +29 -0
- package/kernel/entry.py +120 -32
- package/kernel/event_hub.py +159 -16
- package/kernel/module.md +36 -33
- package/kernel/registry_store.py +70 -20
- package/kernel/rpc_router.py +134 -57
- package/kernel/server.py +292 -15
- package/kite_cli/__init__.py +3 -0
- package/kite_cli/__main__.py +5 -0
- package/kite_cli/commands/__init__.py +1 -0
- package/kite_cli/commands/clean.py +101 -0
- package/kite_cli/commands/doctor.py +35 -0
- package/kite_cli/commands/history.py +111 -0
- package/kite_cli/commands/info.py +96 -0
- package/kite_cli/commands/install.py +313 -0
- package/kite_cli/commands/list.py +143 -0
- package/kite_cli/commands/log.py +81 -0
- package/kite_cli/commands/rollback.py +88 -0
- package/kite_cli/commands/search.py +73 -0
- package/kite_cli/commands/uninstall.py +85 -0
- package/kite_cli/commands/update.py +118 -0
- package/kite_cli/core/__init__.py +1 -0
- package/kite_cli/core/checker.py +142 -0
- package/kite_cli/core/dependency.py +229 -0
- package/kite_cli/core/downloader.py +209 -0
- package/kite_cli/core/install_info.py +40 -0
- package/kite_cli/core/tool_installer.py +397 -0
- package/kite_cli/core/validator.py +78 -0
- package/kite_cli/main.py +289 -0
- package/kite_cli/utils/__init__.py +1 -0
- package/kite_cli/utils/i18n.py +252 -0
- package/kite_cli/utils/interactive.py +63 -0
- package/kite_cli/utils/operation_log.py +77 -0
- package/kite_cli/utils/paths.py +34 -0
- package/kite_cli/utils/version.py +308 -0
- package/launcher/count_lines.py +34 -0
- package/launcher/entry.py +905 -166
- package/launcher/logging_setup.py +104 -0
- package/launcher/module.md +37 -37
- package/launcher/process_manager.py +12 -1
- package/package.json +2 -1
- package/scripts/plan_manager.py +315 -0
|
@@ -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
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""验证器 - 验证 module.md"""
|
|
2
|
+
import re
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Optional, Dict
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Validator:
|
|
8
|
+
"""验证模块的 module.md"""
|
|
9
|
+
|
|
10
|
+
@staticmethod
|
|
11
|
+
def find_module_md(directory: Path) -> Optional[Path]:
|
|
12
|
+
"""在目录中查找 module.md"""
|
|
13
|
+
# 直接查找
|
|
14
|
+
module_md = directory / "module.md"
|
|
15
|
+
if module_md.exists():
|
|
16
|
+
return module_md
|
|
17
|
+
|
|
18
|
+
# 递归查找(最多 2 层)
|
|
19
|
+
for md_file in directory.rglob("module.md"):
|
|
20
|
+
if md_file.relative_to(directory).parts.__len__() <= 2:
|
|
21
|
+
return md_file
|
|
22
|
+
|
|
23
|
+
return None
|
|
24
|
+
|
|
25
|
+
@staticmethod
|
|
26
|
+
def parse_module_md(path: Path) -> Optional[Dict]:
|
|
27
|
+
"""解析 module.md 的 YAML frontmatter"""
|
|
28
|
+
try:
|
|
29
|
+
content = path.read_text(encoding="utf-8")
|
|
30
|
+
|
|
31
|
+
# 提取 YAML frontmatter (--- ... ---)
|
|
32
|
+
match = re.search(r"^---\s*\n(.*?)\n---", content, re.DOTALL | re.MULTILINE)
|
|
33
|
+
if not match:
|
|
34
|
+
return None
|
|
35
|
+
|
|
36
|
+
yaml_content = match.group(1)
|
|
37
|
+
|
|
38
|
+
# 简单解析 YAML(只解析我们需要的字段)
|
|
39
|
+
data = {}
|
|
40
|
+
for line in yaml_content.split("\n"):
|
|
41
|
+
line = line.strip()
|
|
42
|
+
if not line or line.startswith("#"):
|
|
43
|
+
continue
|
|
44
|
+
if ":" in line:
|
|
45
|
+
key, value = line.split(":", 1)
|
|
46
|
+
key = key.strip()
|
|
47
|
+
value = value.strip().strip('"').strip("'")
|
|
48
|
+
data[key] = value
|
|
49
|
+
|
|
50
|
+
return data
|
|
51
|
+
except Exception:
|
|
52
|
+
return None
|
|
53
|
+
|
|
54
|
+
@classmethod
|
|
55
|
+
def validate_module(cls, directory: Path, expected_name: str) -> tuple[bool, Optional[str], Optional[Dict]]:
|
|
56
|
+
"""
|
|
57
|
+
验证模块
|
|
58
|
+
返回: (是否有效, 错误信息, module.md 数据)
|
|
59
|
+
"""
|
|
60
|
+
# 查找 module.md
|
|
61
|
+
module_md = cls.find_module_md(directory)
|
|
62
|
+
if not module_md:
|
|
63
|
+
return False, "未找到 module.md", None
|
|
64
|
+
|
|
65
|
+
# 解析 module.md
|
|
66
|
+
data = cls.parse_module_md(module_md)
|
|
67
|
+
if not data:
|
|
68
|
+
return False, "无法解析 module.md", None
|
|
69
|
+
|
|
70
|
+
# 验证 name 字段
|
|
71
|
+
actual_name = data.get("name")
|
|
72
|
+
if not actual_name:
|
|
73
|
+
return False, "module.md 缺少 name 字段", None
|
|
74
|
+
|
|
75
|
+
if actual_name != expected_name:
|
|
76
|
+
return False, f"模块名不匹配: 期望 {expected_name}, 实际 {actual_name}", None
|
|
77
|
+
|
|
78
|
+
return True, None, data
|