@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.
- package/CHANGELOG.md +200 -0
- package/cli.js +76 -0
- package/extensions/agents/assistant/entry.py +111 -1
- package/extensions/agents/assistant/server.py +263 -215
- 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 -215
- package/extensions/event_hub_bench/entry.py +107 -1
- package/extensions/services/backup/entry.py +299 -21
- package/extensions/services/backup/module.md +24 -22
- package/extensions/services/model_service/entry.py +145 -19
- package/extensions/services/model_service/module.md +21 -22
- package/extensions/services/watchdog/entry.py +188 -25
- package/extensions/services/watchdog/monitor.py +144 -34
- 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/routes/schemas.py +0 -22
- package/extensions/services/web/server.py +421 -98
- package/extensions/services/web/static/css/style.css +67 -28
- package/extensions/services/web/static/index.html +234 -44
- package/extensions/services/web/static/js/app.js +1335 -48
- 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 +141 -16
- package/kernel/module.md +36 -33
- package/kernel/registry_store.py +48 -15
- package/kernel/rpc_router.py +120 -53
- package/kernel/server.py +219 -12
- 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/entry.py +819 -158
- package/launcher/logging_setup.py +104 -0
- package/launcher/module.md +37 -37
- package/package.json +2 -1
- package/scripts/plan_manager.py +315 -0
- package/extensions/services/web/routes/routes_modules.py +0 -249
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
"""install 命令实现"""
|
|
2
|
+
import os
|
|
3
|
+
import tempfile
|
|
4
|
+
import shutil
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from kite_cli.core.checker import PlatformChecker
|
|
7
|
+
from kite_cli.core.downloader import Downloader
|
|
8
|
+
from kite_cli.core.validator import Validator
|
|
9
|
+
from kite_cli.core.dependency import DependencyInstaller
|
|
10
|
+
from kite_cli.core.install_info import record_install_info
|
|
11
|
+
from kite_cli.utils.interactive import select_source, select_location, confirm_action
|
|
12
|
+
from kite_cli.utils.paths import get_install_path
|
|
13
|
+
from kite_cli.utils.i18n import t
|
|
14
|
+
from kite_cli.utils.operation_log import log_operation
|
|
15
|
+
from kite_cli.utils.version import parse_version_spec, filter_versions, get_latest_version
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def run_install(args):
|
|
19
|
+
"""执行安装命令"""
|
|
20
|
+
source = args.source
|
|
21
|
+
location = args.location
|
|
22
|
+
from_platform = args.from_platform
|
|
23
|
+
skip_confirm = args.yes
|
|
24
|
+
no_deps = args.no_deps
|
|
25
|
+
|
|
26
|
+
# 如果没有指定 source,检查当前目录
|
|
27
|
+
if not source:
|
|
28
|
+
cwd = Path.cwd()
|
|
29
|
+
module_md = cwd / "module.md"
|
|
30
|
+
|
|
31
|
+
if module_md.exists():
|
|
32
|
+
print(f"[Info] 检测到当前目录包含 module.md")
|
|
33
|
+
source = str(cwd)
|
|
34
|
+
from_platform = "local"
|
|
35
|
+
print(f"[Info] 将安装当前目录: {source}")
|
|
36
|
+
else:
|
|
37
|
+
print("[Error] 缺少参数 <source>")
|
|
38
|
+
print("[Info] 请指定模块名,或在包含 module.md 的目录下执行")
|
|
39
|
+
print("[Info] 示例:")
|
|
40
|
+
print(" kite install requests # 安装 PyPI/npm 包")
|
|
41
|
+
print(" kite install ./my-module # 安装本地模块")
|
|
42
|
+
print(" kite install # 在模块目录下安装当前目录")
|
|
43
|
+
return 1
|
|
44
|
+
|
|
45
|
+
# 解析版本号 (如 pkg@1.2.0)
|
|
46
|
+
package_name, version_spec = parse_version_spec(source)
|
|
47
|
+
|
|
48
|
+
# 保留名称检查 - 防止安装 Kite 框架本身或核心组件
|
|
49
|
+
RESERVED_NAMES = ['kite', 'launcher', 'kernel', 'core']
|
|
50
|
+
|
|
51
|
+
# 提取模块名(去除路径、URL等)
|
|
52
|
+
module_name = package_name.lower().strip()
|
|
53
|
+
if '/' in module_name:
|
|
54
|
+
module_name = module_name.split('/')[-1]
|
|
55
|
+
if module_name.endswith('.git'):
|
|
56
|
+
module_name = module_name[:-4]
|
|
57
|
+
|
|
58
|
+
# 只检查完全匹配的保留名称
|
|
59
|
+
if module_name in RESERVED_NAMES:
|
|
60
|
+
print(t('reserved_name_error', name=source))
|
|
61
|
+
print(t('reserved_name_hint', name=module_name))
|
|
62
|
+
print(t('update_framework_hint'))
|
|
63
|
+
return 1
|
|
64
|
+
|
|
65
|
+
if version_spec:
|
|
66
|
+
print(t('searching_module', name=f"{package_name}@{version_spec}"))
|
|
67
|
+
else:
|
|
68
|
+
print(t('searching_module', name=package_name))
|
|
69
|
+
|
|
70
|
+
# 步骤 1: 检查各平台
|
|
71
|
+
if from_platform:
|
|
72
|
+
# 指定了平台,只检查该平台
|
|
73
|
+
candidates = check_single_platform(package_name, from_platform, version_spec)
|
|
74
|
+
else:
|
|
75
|
+
# 未指定平台,检查所有平台
|
|
76
|
+
candidates = check_all_platforms(package_name, version_spec)
|
|
77
|
+
|
|
78
|
+
if not candidates:
|
|
79
|
+
print(t('module_not_found', name=source))
|
|
80
|
+
return 1
|
|
81
|
+
|
|
82
|
+
# 步骤 2: 用户选择来源(如果有多个)
|
|
83
|
+
if len(candidates) > 1:
|
|
84
|
+
selected = select_source(candidates)
|
|
85
|
+
if not selected:
|
|
86
|
+
print("[Cancelled] 已取消")
|
|
87
|
+
return 1
|
|
88
|
+
else:
|
|
89
|
+
selected = candidates[0]
|
|
90
|
+
|
|
91
|
+
print(t('select_source', platform=selected['platform'], name=selected.get('name', source)))
|
|
92
|
+
|
|
93
|
+
# 步骤 3: 下载到临时目录
|
|
94
|
+
temp_dir = Path(tempfile.mkdtemp(prefix="kite-install-"))
|
|
95
|
+
try:
|
|
96
|
+
print(t('downloading'))
|
|
97
|
+
downloaded_dir, error = download_module(selected, temp_dir)
|
|
98
|
+
if not downloaded_dir:
|
|
99
|
+
print(t('download_failed'))
|
|
100
|
+
if error:
|
|
101
|
+
print(f" {error}")
|
|
102
|
+
return 1
|
|
103
|
+
|
|
104
|
+
print(t('download_complete', path=downloaded_dir))
|
|
105
|
+
|
|
106
|
+
# 步骤 4: 验证 module.md
|
|
107
|
+
print(t('validating'))
|
|
108
|
+
|
|
109
|
+
# 对于本地安装,不验证模块名,直接读取 module.md
|
|
110
|
+
if selected["platform"] == "local":
|
|
111
|
+
module_md = Validator.find_module_md(downloaded_dir)
|
|
112
|
+
if not module_md:
|
|
113
|
+
print(t('validation_failed', error="未找到 module.md"))
|
|
114
|
+
return 1
|
|
115
|
+
module_data = Validator.parse_module_md(module_md)
|
|
116
|
+
if not module_data or not module_data.get("name"):
|
|
117
|
+
print(t('validation_failed', error="无法解析 module.md 或缺少 name 字段"))
|
|
118
|
+
return 1
|
|
119
|
+
else:
|
|
120
|
+
# 其他平台需要验证模块名匹配
|
|
121
|
+
valid, error, module_data = Validator.validate_module(downloaded_dir, source)
|
|
122
|
+
if not valid:
|
|
123
|
+
print(t('validation_failed', error=error))
|
|
124
|
+
return 1
|
|
125
|
+
|
|
126
|
+
print(t('validation_passed', name=module_data.get('name'), version=module_data.get('version', 'unknown')))
|
|
127
|
+
|
|
128
|
+
# 步骤 5: 选择安装位置
|
|
129
|
+
if not location:
|
|
130
|
+
location = select_location()
|
|
131
|
+
if not location:
|
|
132
|
+
print("[Cancelled] 已取消")
|
|
133
|
+
return 1
|
|
134
|
+
|
|
135
|
+
# 步骤 6: 检查冲突
|
|
136
|
+
install_path = get_install_path(location, module_data['name'])
|
|
137
|
+
if install_path.exists():
|
|
138
|
+
if not skip_confirm:
|
|
139
|
+
if not confirm_action(f"模块 {module_data['name']} 已存在,是否覆盖?"):
|
|
140
|
+
print("[Cancelled] 已取消")
|
|
141
|
+
return 1
|
|
142
|
+
print("[Warning] 删除旧版本...")
|
|
143
|
+
shutil.rmtree(install_path)
|
|
144
|
+
|
|
145
|
+
# 步骤 7: 安装依赖
|
|
146
|
+
if not no_deps:
|
|
147
|
+
print(t('installing_deps'))
|
|
148
|
+
if not DependencyInstaller.install_all_deps(downloaded_dir, module_data, location, no_deps):
|
|
149
|
+
print(t('deps_failed'))
|
|
150
|
+
if not skip_confirm:
|
|
151
|
+
if not confirm_action("是否继续安装模块(不含依赖)?"):
|
|
152
|
+
return 1
|
|
153
|
+
else:
|
|
154
|
+
return 1
|
|
155
|
+
|
|
156
|
+
# 步骤 8: 复制到目标位置
|
|
157
|
+
print(t('installing_to', path=install_path))
|
|
158
|
+
install_path.parent.mkdir(parents=True, exist_ok=True)
|
|
159
|
+
shutil.copytree(downloaded_dir, install_path)
|
|
160
|
+
|
|
161
|
+
# 步骤 9: 记录安装信息
|
|
162
|
+
source_info = {
|
|
163
|
+
"type": selected["platform"],
|
|
164
|
+
"name": selected.get("name", source),
|
|
165
|
+
}
|
|
166
|
+
if selected["platform"] == "pypi":
|
|
167
|
+
source_info["package"] = selected["name"]
|
|
168
|
+
source_info["version"] = selected.get("latest")
|
|
169
|
+
elif selected["platform"] == "npm":
|
|
170
|
+
source_info["package"] = selected["name"]
|
|
171
|
+
source_info["version"] = selected.get("latest")
|
|
172
|
+
elif selected["platform"] == "git":
|
|
173
|
+
source_info["url"] = selected["url"]
|
|
174
|
+
source_info["full_name"] = selected.get("full_name")
|
|
175
|
+
elif selected["platform"] == "local":
|
|
176
|
+
source_info["path"] = selected["path"]
|
|
177
|
+
|
|
178
|
+
record_install_info(install_path, source_info, module_data, location)
|
|
179
|
+
|
|
180
|
+
# 记录操作日志
|
|
181
|
+
log_operation("install", {
|
|
182
|
+
"module": module_data['name'],
|
|
183
|
+
"version": module_data.get('version', 'unknown'),
|
|
184
|
+
"source": source_info,
|
|
185
|
+
"location": location,
|
|
186
|
+
"path": str(install_path)
|
|
187
|
+
}, success=True)
|
|
188
|
+
|
|
189
|
+
print(t('install_complete'))
|
|
190
|
+
print(t('restart_hint'))
|
|
191
|
+
|
|
192
|
+
return 0
|
|
193
|
+
|
|
194
|
+
except Exception as e:
|
|
195
|
+
# 记录失败日志
|
|
196
|
+
log_operation("install", {
|
|
197
|
+
"source": source,
|
|
198
|
+
"location": location
|
|
199
|
+
}, success=False, error=str(e))
|
|
200
|
+
raise
|
|
201
|
+
|
|
202
|
+
finally:
|
|
203
|
+
# 清理临时目录
|
|
204
|
+
shutil.rmtree(temp_dir, ignore_errors=True)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def check_single_platform(source: str, platform: str, version_spec: str = None):
|
|
208
|
+
"""检查单个平台"""
|
|
209
|
+
candidates = []
|
|
210
|
+
|
|
211
|
+
if platform == "pypi":
|
|
212
|
+
result = PlatformChecker.check_pypi(source)
|
|
213
|
+
if result:
|
|
214
|
+
# 根据版本规范过滤
|
|
215
|
+
if version_spec:
|
|
216
|
+
matched_versions = filter_versions(result.get("versions", []), version_spec)
|
|
217
|
+
if matched_versions:
|
|
218
|
+
result["matched_version"] = matched_versions[0] # 选择最新的匹配版本
|
|
219
|
+
result["version_spec"] = version_spec
|
|
220
|
+
candidates.append(result)
|
|
221
|
+
else:
|
|
222
|
+
candidates.append(result)
|
|
223
|
+
|
|
224
|
+
elif platform == "npm":
|
|
225
|
+
result = PlatformChecker.check_npm(source)
|
|
226
|
+
if result:
|
|
227
|
+
# 根据版本规范过滤
|
|
228
|
+
if version_spec:
|
|
229
|
+
matched_versions = filter_versions(result.get("versions", []), version_spec)
|
|
230
|
+
if matched_versions:
|
|
231
|
+
result["matched_version"] = matched_versions[0]
|
|
232
|
+
result["version_spec"] = version_spec
|
|
233
|
+
candidates.append(result)
|
|
234
|
+
else:
|
|
235
|
+
candidates.append(result)
|
|
236
|
+
|
|
237
|
+
elif platform == "git":
|
|
238
|
+
results = PlatformChecker.check_git(source)
|
|
239
|
+
if results:
|
|
240
|
+
candidates.extend(results)
|
|
241
|
+
|
|
242
|
+
elif platform == "local":
|
|
243
|
+
# 本地路径直接返回
|
|
244
|
+
if Path(source).exists():
|
|
245
|
+
candidates.append({
|
|
246
|
+
"platform": "local",
|
|
247
|
+
"path": source
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
return candidates
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def check_all_platforms(source: str, version_spec: str = None):
|
|
254
|
+
"""检查所有平台"""
|
|
255
|
+
candidates = []
|
|
256
|
+
|
|
257
|
+
# 先检查是否是本地路径
|
|
258
|
+
if Path(source).exists():
|
|
259
|
+
candidates.append({
|
|
260
|
+
"platform": "local",
|
|
261
|
+
"path": source,
|
|
262
|
+
"name": Path(source).name
|
|
263
|
+
})
|
|
264
|
+
return candidates
|
|
265
|
+
|
|
266
|
+
# 并行检查 PyPI/npm/Git
|
|
267
|
+
results = PlatformChecker.check_all(source)
|
|
268
|
+
|
|
269
|
+
if results["pypi"]:
|
|
270
|
+
result = results["pypi"]
|
|
271
|
+
# 根据版本规范过滤
|
|
272
|
+
if version_spec:
|
|
273
|
+
matched_versions = filter_versions(result.get("versions", []), version_spec)
|
|
274
|
+
if matched_versions:
|
|
275
|
+
result["matched_version"] = matched_versions[0]
|
|
276
|
+
result["version_spec"] = version_spec
|
|
277
|
+
candidates.append(result)
|
|
278
|
+
else:
|
|
279
|
+
candidates.append(result)
|
|
280
|
+
|
|
281
|
+
if results["npm"]:
|
|
282
|
+
result = results["npm"]
|
|
283
|
+
# 根据版本规范过滤
|
|
284
|
+
if version_spec:
|
|
285
|
+
matched_versions = filter_versions(result.get("versions", []), version_spec)
|
|
286
|
+
if matched_versions:
|
|
287
|
+
result["matched_version"] = matched_versions[0]
|
|
288
|
+
result["version_spec"] = version_spec
|
|
289
|
+
candidates.append(result)
|
|
290
|
+
else:
|
|
291
|
+
candidates.append(result)
|
|
292
|
+
|
|
293
|
+
if results["git"]:
|
|
294
|
+
candidates.extend(results["git"])
|
|
295
|
+
|
|
296
|
+
return candidates
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def download_module(selected: dict, temp_dir: Path):
|
|
300
|
+
"""根据选择的来源下载模块"""
|
|
301
|
+
platform = selected["platform"]
|
|
302
|
+
version = selected.get("matched_version") or selected.get("latest")
|
|
303
|
+
|
|
304
|
+
if platform == "pypi":
|
|
305
|
+
return Downloader.download_pypi(selected["name"], temp_dir, version)
|
|
306
|
+
elif platform == "npm":
|
|
307
|
+
return Downloader.download_npm(selected["name"], temp_dir, version)
|
|
308
|
+
elif platform == "git":
|
|
309
|
+
return Downloader.download_git(selected["url"], temp_dir)
|
|
310
|
+
elif platform == "local":
|
|
311
|
+
return Downloader.download_local(selected["path"], temp_dir)
|
|
312
|
+
|
|
313
|
+
return None, "未知的平台类型"
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"""list 命令实现 - 列出启动器能找到的所有模块"""
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from kite_cli.utils.i18n import t
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def run_list(args):
|
|
9
|
+
"""执行列表命令 - 使用启动器的模块扫描器"""
|
|
10
|
+
verbose = args.verbose
|
|
11
|
+
|
|
12
|
+
# 确保环境变量设置
|
|
13
|
+
if "KITE_PROJECT" not in os.environ:
|
|
14
|
+
# 尝试从当前目录推断
|
|
15
|
+
cwd = Path.cwd()
|
|
16
|
+
if (cwd / "launcher").exists() and (cwd / "kernel").exists():
|
|
17
|
+
os.environ["KITE_PROJECT"] = str(cwd)
|
|
18
|
+
else:
|
|
19
|
+
print("[Error] 错误: 未找到 KITE_PROJECT 环境变量")
|
|
20
|
+
print("[Info] 请在 Kite 项目目录下运行此命令")
|
|
21
|
+
return 1
|
|
22
|
+
|
|
23
|
+
# 导入启动器的模块扫描器
|
|
24
|
+
project_root = Path(os.environ["KITE_PROJECT"])
|
|
25
|
+
launcher_path = project_root / "launcher"
|
|
26
|
+
|
|
27
|
+
if not launcher_path.exists():
|
|
28
|
+
print(f"[Error] 错误: 未找到 launcher 目录: {launcher_path}")
|
|
29
|
+
return 1
|
|
30
|
+
|
|
31
|
+
# 添加到 sys.path
|
|
32
|
+
if str(launcher_path.parent) not in sys.path:
|
|
33
|
+
sys.path.insert(0, str(launcher_path.parent))
|
|
34
|
+
|
|
35
|
+
try:
|
|
36
|
+
from launcher.module_scanner import ModuleScanner
|
|
37
|
+
except ImportError as e:
|
|
38
|
+
print(f"[Error] 错误: 无法导入 ModuleScanner: {e}")
|
|
39
|
+
return 1
|
|
40
|
+
|
|
41
|
+
# 读取 discovery 配置(如果存在)
|
|
42
|
+
discovery = {}
|
|
43
|
+
discovery_file = launcher_path / "discovery.yaml"
|
|
44
|
+
if discovery_file.exists():
|
|
45
|
+
try:
|
|
46
|
+
import yaml
|
|
47
|
+
with open(discovery_file, "r", encoding="utf-8") as f:
|
|
48
|
+
discovery = yaml.safe_load(f) or {}
|
|
49
|
+
except Exception as e:
|
|
50
|
+
print(f"[Warning] 警告: 无法读取 discovery.yaml: {e}")
|
|
51
|
+
|
|
52
|
+
# 扫描模块
|
|
53
|
+
scanner = ModuleScanner(discovery=discovery, launcher_dir=str(launcher_path))
|
|
54
|
+
modules = scanner.scan()
|
|
55
|
+
|
|
56
|
+
# 手动添加 kernel 模块(ModuleScanner 不扫描 kernel 目录本身)
|
|
57
|
+
kernel_dir_path = project_root / "kernel"
|
|
58
|
+
kernel_module_md = kernel_dir_path / "module.md"
|
|
59
|
+
if kernel_module_md.exists():
|
|
60
|
+
try:
|
|
61
|
+
kernel_info = scanner._try_load(str(kernel_dir_path))
|
|
62
|
+
if kernel_info and kernel_info.name not in modules:
|
|
63
|
+
modules[kernel_info.name] = kernel_info
|
|
64
|
+
except Exception as e:
|
|
65
|
+
print(f"[Warning] 警告: 无法加载 kernel 模块: {e}")
|
|
66
|
+
|
|
67
|
+
if not modules:
|
|
68
|
+
print(t('no_modules'))
|
|
69
|
+
return 0
|
|
70
|
+
|
|
71
|
+
# 分类模块
|
|
72
|
+
kernel_modules = []
|
|
73
|
+
builtin_modules = []
|
|
74
|
+
external_modules = []
|
|
75
|
+
|
|
76
|
+
extensions_dir = project_root / "extensions"
|
|
77
|
+
kernel_dir = project_root / "kernel"
|
|
78
|
+
|
|
79
|
+
for name, info in modules.items():
|
|
80
|
+
module_path = Path(info.module_dir)
|
|
81
|
+
|
|
82
|
+
# 判断模块类型
|
|
83
|
+
# 1. kernel 目录下的模块 = 内核模块
|
|
84
|
+
if module_path.parent == kernel_dir or name == "kernel":
|
|
85
|
+
kernel_modules.append((name, info))
|
|
86
|
+
# 2. extensions 目录下的模块 = 内置模块
|
|
87
|
+
elif module_path.is_relative_to(extensions_dir):
|
|
88
|
+
builtin_modules.append((name, info))
|
|
89
|
+
# 3. 其他位置的模块 = 外置模块
|
|
90
|
+
else:
|
|
91
|
+
external_modules.append((name, info))
|
|
92
|
+
|
|
93
|
+
# 显示结果
|
|
94
|
+
print(t('installed_modules') + "\n")
|
|
95
|
+
|
|
96
|
+
if kernel_modules:
|
|
97
|
+
print(f"【{t('category_kernel')}】")
|
|
98
|
+
for name, info in sorted(kernel_modules, key=lambda x: x[0]):
|
|
99
|
+
_print_module(name, info, verbose)
|
|
100
|
+
print()
|
|
101
|
+
|
|
102
|
+
if builtin_modules:
|
|
103
|
+
print(f"【{t('category_builtin')}】")
|
|
104
|
+
for name, info in sorted(builtin_modules, key=lambda x: x[0]):
|
|
105
|
+
_print_module(name, info, verbose)
|
|
106
|
+
print()
|
|
107
|
+
|
|
108
|
+
if external_modules:
|
|
109
|
+
print(f"【{t('category_external')}】")
|
|
110
|
+
for name, info in sorted(external_modules, key=lambda x: x[0]):
|
|
111
|
+
_print_module(name, info, verbose)
|
|
112
|
+
print()
|
|
113
|
+
|
|
114
|
+
total = len(modules)
|
|
115
|
+
print(t('total_modules', count=total))
|
|
116
|
+
print(t('stats_breakdown', kernel=len(kernel_modules), builtin=len(builtin_modules), external=len(external_modules)))
|
|
117
|
+
|
|
118
|
+
return 0
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _print_module(name, info, verbose):
|
|
122
|
+
"""打印单个模块信息"""
|
|
123
|
+
display_name = info.display_name or name
|
|
124
|
+
version = info.version or "unknown"
|
|
125
|
+
state_icon = "✓" if info.state == "enabled" else "○"
|
|
126
|
+
|
|
127
|
+
if verbose:
|
|
128
|
+
print(f" {state_icon} {display_name} (v{version})")
|
|
129
|
+
print(f" 名称: {name}")
|
|
130
|
+
print(f" 类型: {info.type}")
|
|
131
|
+
print(f" 状态: {info.state}")
|
|
132
|
+
print(f" 运行时: {info.runtime}")
|
|
133
|
+
print(f" 入口: {info.entry}")
|
|
134
|
+
print(f" 路径: {info.module_dir}")
|
|
135
|
+
if info.depends_on:
|
|
136
|
+
print(f" 依赖: {', '.join(info.depends_on)}")
|
|
137
|
+
print()
|
|
138
|
+
else:
|
|
139
|
+
# 类型标签国际化
|
|
140
|
+
type_key = f"type_{info.type}"
|
|
141
|
+
type_label = t(type_key) if type_key in ['type_kernel', 'type_infrastructure', 'type_channel', 'type_agent', 'type_service', 'type_tool'] else info.type
|
|
142
|
+
type_badge = f"[{type_label}]"
|
|
143
|
+
print(f" {state_icon} {display_name} (v{version}) {type_badge}")
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""log 命令实现 - 查看操作日志"""
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from kite_cli.utils.operation_log import read_operations, get_log_file
|
|
4
|
+
from kite_cli.utils.i18n import t
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def run_log(args):
|
|
8
|
+
"""执行日志查看命令"""
|
|
9
|
+
limit = args.limit if hasattr(args, 'limit') else 20
|
|
10
|
+
show_path = args.path if hasattr(args, 'path') else False
|
|
11
|
+
|
|
12
|
+
if show_path:
|
|
13
|
+
# 显示日志文件路径
|
|
14
|
+
log_file = get_log_file()
|
|
15
|
+
print(f"[Info] 日志文件路径: {log_file}")
|
|
16
|
+
return 0
|
|
17
|
+
|
|
18
|
+
# 读取操作日志
|
|
19
|
+
operations = read_operations(limit=limit)
|
|
20
|
+
|
|
21
|
+
if not operations:
|
|
22
|
+
print("[Info] 暂无操作记录")
|
|
23
|
+
return 0
|
|
24
|
+
|
|
25
|
+
print(f"[Log] 最近 {len(operations)} 条操作记录:\n")
|
|
26
|
+
|
|
27
|
+
for op in operations:
|
|
28
|
+
timestamp = op.get("timestamp", "unknown")
|
|
29
|
+
operation = op.get("operation", "unknown")
|
|
30
|
+
success = op.get("success", True)
|
|
31
|
+
details = op.get("details", {})
|
|
32
|
+
error = op.get("error")
|
|
33
|
+
|
|
34
|
+
# 格式化时间戳
|
|
35
|
+
try:
|
|
36
|
+
dt = datetime.fromisoformat(timestamp.replace('Z', '+00:00'))
|
|
37
|
+
time_str = dt.strftime("%Y-%m-%d %H:%M:%S")
|
|
38
|
+
except Exception:
|
|
39
|
+
time_str = timestamp
|
|
40
|
+
|
|
41
|
+
# 状态标记
|
|
42
|
+
status = "[Done]" if success else "[Error]"
|
|
43
|
+
|
|
44
|
+
# 操作类型
|
|
45
|
+
if operation == "install":
|
|
46
|
+
module = details.get("module", "unknown")
|
|
47
|
+
version = details.get("version", "")
|
|
48
|
+
location = details.get("location", "")
|
|
49
|
+
print(f"{time_str} {status} install {module} v{version} -> {location}")
|
|
50
|
+
if error:
|
|
51
|
+
print(f" 错误: {error}")
|
|
52
|
+
|
|
53
|
+
elif operation == "uninstall":
|
|
54
|
+
module = details.get("module", "unknown")
|
|
55
|
+
locations = details.get("locations", [])
|
|
56
|
+
if locations:
|
|
57
|
+
loc_str = ", ".join([l["location"] for l in locations])
|
|
58
|
+
print(f"{time_str} {status} uninstall {module} from {loc_str}")
|
|
59
|
+
else:
|
|
60
|
+
print(f"{time_str} {status} uninstall {module}")
|
|
61
|
+
if error:
|
|
62
|
+
print(f" 错误: {error}")
|
|
63
|
+
|
|
64
|
+
elif operation == "update":
|
|
65
|
+
module = details.get("module", "unknown")
|
|
66
|
+
print(f"{time_str} {status} update {module}")
|
|
67
|
+
if error:
|
|
68
|
+
print(f" 错误: {error}")
|
|
69
|
+
|
|
70
|
+
elif operation == "clean":
|
|
71
|
+
cleaned = details.get("cleaned", [])
|
|
72
|
+
print(f"{time_str} {status} clean ({len(cleaned)} items)")
|
|
73
|
+
|
|
74
|
+
else:
|
|
75
|
+
print(f"{time_str} {status} {operation}")
|
|
76
|
+
if error:
|
|
77
|
+
print(f" 错误: {error}")
|
|
78
|
+
|
|
79
|
+
print()
|
|
80
|
+
|
|
81
|
+
return 0
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""rollback 命令实现 - 回滚最后一次操作"""
|
|
2
|
+
import shutil
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from kite_cli.utils.operation_log import get_last_operation, log_operation
|
|
5
|
+
from kite_cli.utils.interactive import confirm_action
|
|
6
|
+
from kite_cli.utils.paths import get_install_path
|
|
7
|
+
from kite_cli.utils.i18n import t
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def run_rollback(args):
|
|
11
|
+
"""执行回滚命令"""
|
|
12
|
+
skip_confirm = args.yes if hasattr(args, 'yes') else False
|
|
13
|
+
|
|
14
|
+
# 获取最后一次成功的操作
|
|
15
|
+
last_op = get_last_operation()
|
|
16
|
+
|
|
17
|
+
if not last_op:
|
|
18
|
+
print("[Error] 没有可回滚的操作")
|
|
19
|
+
return 1
|
|
20
|
+
|
|
21
|
+
if not last_op.get("success"):
|
|
22
|
+
print("[Error] 最后一次操作失败,无法回滚")
|
|
23
|
+
return 1
|
|
24
|
+
|
|
25
|
+
operation = last_op.get("operation")
|
|
26
|
+
details = last_op.get("details", {})
|
|
27
|
+
|
|
28
|
+
if operation == "install":
|
|
29
|
+
return rollback_install(details, skip_confirm)
|
|
30
|
+
elif operation == "uninstall":
|
|
31
|
+
print("[Error] 卸载操作无法自动回滚")
|
|
32
|
+
print("[Info] 请手动重新安装模块")
|
|
33
|
+
return 1
|
|
34
|
+
elif operation == "update":
|
|
35
|
+
print("[Error] 更新操作无法自动回滚")
|
|
36
|
+
print("[Info] 请手动安装旧版本")
|
|
37
|
+
return 1
|
|
38
|
+
else:
|
|
39
|
+
print(f"[Error] 不支持回滚操作类型: {operation}")
|
|
40
|
+
return 1
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def rollback_install(details: dict, skip_confirm: bool) -> int:
|
|
44
|
+
"""回滚安装操作"""
|
|
45
|
+
module = details.get("module", "unknown")
|
|
46
|
+
location = details.get("location", "")
|
|
47
|
+
path = details.get("path", "")
|
|
48
|
+
|
|
49
|
+
if not path:
|
|
50
|
+
print("[Error] 无法确定模块路径")
|
|
51
|
+
return 1
|
|
52
|
+
|
|
53
|
+
module_path = Path(path)
|
|
54
|
+
|
|
55
|
+
if not module_path.exists():
|
|
56
|
+
print(f"[Warning] 模块已不存在: {module_path}")
|
|
57
|
+
return 0
|
|
58
|
+
|
|
59
|
+
# 确认回滚
|
|
60
|
+
if not skip_confirm:
|
|
61
|
+
if not confirm_action(f"确认回滚安装 {module} (删除 {location})?"):
|
|
62
|
+
print("[Cancelled] 已取消")
|
|
63
|
+
return 1
|
|
64
|
+
|
|
65
|
+
# 删除模块
|
|
66
|
+
try:
|
|
67
|
+
shutil.rmtree(module_path)
|
|
68
|
+
print(f"[Done] 已回滚安装: {module} from {location}")
|
|
69
|
+
|
|
70
|
+
# 记录回滚操作
|
|
71
|
+
log_operation("rollback", {
|
|
72
|
+
"original_operation": "install",
|
|
73
|
+
"module": module,
|
|
74
|
+
"location": location,
|
|
75
|
+
"path": str(module_path)
|
|
76
|
+
}, success=True)
|
|
77
|
+
|
|
78
|
+
return 0
|
|
79
|
+
|
|
80
|
+
except Exception as e:
|
|
81
|
+
print(f"[Error] 回滚失败: {e}")
|
|
82
|
+
|
|
83
|
+
log_operation("rollback", {
|
|
84
|
+
"original_operation": "install",
|
|
85
|
+
"module": module
|
|
86
|
+
}, success=False, error=str(e))
|
|
87
|
+
|
|
88
|
+
return 1
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""search 命令实现"""
|
|
2
|
+
from kite_cli.core.checker import PlatformChecker
|
|
3
|
+
from kite_cli.utils.i18n import t
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def run_search(args):
|
|
7
|
+
"""执行搜索命令"""
|
|
8
|
+
keyword = args.keyword
|
|
9
|
+
from_platform = args.from_platform
|
|
10
|
+
|
|
11
|
+
print(t('searching_for', keyword=keyword) + "\n")
|
|
12
|
+
|
|
13
|
+
if from_platform:
|
|
14
|
+
# 指定平台搜索
|
|
15
|
+
results = search_single_platform(keyword, from_platform)
|
|
16
|
+
else:
|
|
17
|
+
# 搜索所有平台
|
|
18
|
+
results = PlatformChecker.check_all(keyword)
|
|
19
|
+
|
|
20
|
+
# 显示结果
|
|
21
|
+
found_count = 0
|
|
22
|
+
|
|
23
|
+
# PyPI
|
|
24
|
+
if results.get("pypi"):
|
|
25
|
+
found_count += 1
|
|
26
|
+
pypi = results["pypi"]
|
|
27
|
+
print(f"【{t('search_pypi')}】")
|
|
28
|
+
print(f" • {pypi['name']}")
|
|
29
|
+
print(f" {t('search_latest_version')}: {pypi['latest']}")
|
|
30
|
+
if len(pypi['versions']) > 1:
|
|
31
|
+
print(f" {t('search_available_versions')}: {', '.join(pypi['versions'][:5])}")
|
|
32
|
+
print()
|
|
33
|
+
|
|
34
|
+
# npm
|
|
35
|
+
if results.get("npm"):
|
|
36
|
+
found_count += 1
|
|
37
|
+
npm = results["npm"]
|
|
38
|
+
print(f"【{t('search_npm')}】")
|
|
39
|
+
print(f" • {npm['name']}")
|
|
40
|
+
print(f" {t('search_version')}: {npm['latest']}")
|
|
41
|
+
print()
|
|
42
|
+
|
|
43
|
+
# Git
|
|
44
|
+
if results.get("git"):
|
|
45
|
+
found_count += len(results["git"])
|
|
46
|
+
print(f"【{t('search_git')}】")
|
|
47
|
+
for repo in results["git"]:
|
|
48
|
+
print(f" • {repo['full_name']}")
|
|
49
|
+
if repo.get("description"):
|
|
50
|
+
print(f" {repo['description']}")
|
|
51
|
+
print(f" {repo['url']}")
|
|
52
|
+
print()
|
|
53
|
+
|
|
54
|
+
if found_count == 0:
|
|
55
|
+
print(t('no_results'))
|
|
56
|
+
return 1
|
|
57
|
+
else:
|
|
58
|
+
print(t('found_results', count=found_count))
|
|
59
|
+
return 0
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def search_single_platform(keyword: str, platform: str):
|
|
63
|
+
"""搜索单个平台"""
|
|
64
|
+
results = {"pypi": None, "npm": None, "git": None}
|
|
65
|
+
|
|
66
|
+
if platform == "pypi":
|
|
67
|
+
results["pypi"] = PlatformChecker.check_pypi(keyword)
|
|
68
|
+
elif platform == "npm":
|
|
69
|
+
results["npm"] = PlatformChecker.check_npm(keyword)
|
|
70
|
+
elif platform == "git":
|
|
71
|
+
results["git"] = PlatformChecker.check_git(keyword)
|
|
72
|
+
|
|
73
|
+
return results
|