@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,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
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""uninstall 命令实现"""
|
|
2
|
+
import shutil
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from kite_cli.utils.paths import get_install_path
|
|
5
|
+
from kite_cli.utils.interactive import confirm_action
|
|
6
|
+
from kite_cli.utils.i18n import t
|
|
7
|
+
from kite_cli.utils.operation_log import log_operation
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def run_uninstall(args):
|
|
11
|
+
"""执行卸载命令"""
|
|
12
|
+
module_name = args.name
|
|
13
|
+
location = args.location
|
|
14
|
+
uninstall_all = args.all
|
|
15
|
+
skip_confirm = args.yes
|
|
16
|
+
|
|
17
|
+
print(t('uninstalling', name=module_name))
|
|
18
|
+
|
|
19
|
+
# 查找模块
|
|
20
|
+
if uninstall_all:
|
|
21
|
+
# 卸载所有位置
|
|
22
|
+
locations = ["dev", "local", "global"]
|
|
23
|
+
elif location:
|
|
24
|
+
# 指定位置
|
|
25
|
+
locations = [location]
|
|
26
|
+
else:
|
|
27
|
+
# 自动查找
|
|
28
|
+
locations = find_module_locations(module_name)
|
|
29
|
+
if not locations:
|
|
30
|
+
print(t('module_not_found', name=module_name))
|
|
31
|
+
return 1
|
|
32
|
+
|
|
33
|
+
# 确认卸载
|
|
34
|
+
if not skip_confirm:
|
|
35
|
+
location_names = ", ".join(locations)
|
|
36
|
+
if not confirm_action(f"{t('uninstalling', name=module_name)} ({location_names})?"):
|
|
37
|
+
print(t('update_cancelled'))
|
|
38
|
+
return 1
|
|
39
|
+
|
|
40
|
+
# 执行卸载
|
|
41
|
+
success_count = 0
|
|
42
|
+
uninstalled_paths = []
|
|
43
|
+
for loc in locations:
|
|
44
|
+
module_path = get_install_path(loc, module_name)
|
|
45
|
+
if module_path.exists():
|
|
46
|
+
try:
|
|
47
|
+
shutil.rmtree(module_path)
|
|
48
|
+
print(t('uninstalled', location=loc, path=module_path))
|
|
49
|
+
success_count += 1
|
|
50
|
+
uninstalled_paths.append({"location": loc, "path": str(module_path)})
|
|
51
|
+
except Exception as e:
|
|
52
|
+
print(f"[Error] {t('update_failed', error=str(e))} ({loc})")
|
|
53
|
+
else:
|
|
54
|
+
print(f"[Warning] {t('module_not_found', name='')} ({loc}): {module_path}")
|
|
55
|
+
|
|
56
|
+
if success_count > 0:
|
|
57
|
+
# 记录操作日志
|
|
58
|
+
log_operation("uninstall", {
|
|
59
|
+
"module": module_name,
|
|
60
|
+
"locations": uninstalled_paths
|
|
61
|
+
}, success=True)
|
|
62
|
+
|
|
63
|
+
print(t('uninstall_complete', count=success_count))
|
|
64
|
+
print(t('restart_hint'))
|
|
65
|
+
return 0
|
|
66
|
+
else:
|
|
67
|
+
log_operation("uninstall", {
|
|
68
|
+
"module": module_name
|
|
69
|
+
}, success=False, error="No modules found")
|
|
70
|
+
|
|
71
|
+
print(t('uninstall_none'))
|
|
72
|
+
return 1
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def find_module_locations(module_name: str):
|
|
76
|
+
"""查找模块在哪些位置存在"""
|
|
77
|
+
locations = []
|
|
78
|
+
for loc in ["dev", "local", "global"]:
|
|
79
|
+
try:
|
|
80
|
+
module_path = get_install_path(loc, module_name)
|
|
81
|
+
if module_path.exists():
|
|
82
|
+
locations.append(loc)
|
|
83
|
+
except Exception:
|
|
84
|
+
pass
|
|
85
|
+
return locations
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""update 命令实现"""
|
|
2
|
+
import sys
|
|
3
|
+
import subprocess
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from kite_cli.utils.paths import get_install_path
|
|
6
|
+
from kite_cli.core.install_info import read_install_info
|
|
7
|
+
from kite_cli.utils.interactive import confirm_action
|
|
8
|
+
from kite_cli.utils.i18n import t
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def run_update(args):
|
|
12
|
+
"""执行更新命令"""
|
|
13
|
+
module_name = args.name if hasattr(args, 'name') else None
|
|
14
|
+
location = args.location if hasattr(args, 'location') else None
|
|
15
|
+
to_version = args.to_version if hasattr(args, 'to_version') else None
|
|
16
|
+
update_all = args.all if hasattr(args, 'all') else False
|
|
17
|
+
skip_confirm = args.yes if hasattr(args, 'yes') else False
|
|
18
|
+
|
|
19
|
+
if update_all:
|
|
20
|
+
print(t('update_not_implemented'))
|
|
21
|
+
return 1
|
|
22
|
+
|
|
23
|
+
if not module_name:
|
|
24
|
+
print(t('update_specify_name'))
|
|
25
|
+
return 1
|
|
26
|
+
|
|
27
|
+
# 显示更新信息
|
|
28
|
+
if to_version:
|
|
29
|
+
print(t('updating_module', name=f"{module_name} → {to_version}"))
|
|
30
|
+
else:
|
|
31
|
+
print(t('updating_module', name=module_name))
|
|
32
|
+
|
|
33
|
+
# 查找模块
|
|
34
|
+
if location:
|
|
35
|
+
locations = [location]
|
|
36
|
+
else:
|
|
37
|
+
locations = find_module_locations(module_name)
|
|
38
|
+
if not locations:
|
|
39
|
+
print(t('update_not_found', name=module_name))
|
|
40
|
+
return 1
|
|
41
|
+
|
|
42
|
+
# 只更新第一个找到的位置
|
|
43
|
+
loc = locations[0]
|
|
44
|
+
module_path = get_install_path(loc, module_name)
|
|
45
|
+
|
|
46
|
+
# 读取安装信息
|
|
47
|
+
install_info = read_install_info(module_path)
|
|
48
|
+
if not install_info:
|
|
49
|
+
print(t('update_no_install_info'))
|
|
50
|
+
print(t('update_manual_reinstall', name=module_name, location=loc))
|
|
51
|
+
return 1
|
|
52
|
+
|
|
53
|
+
source = install_info.get("source", {})
|
|
54
|
+
source_type = source.get("type")
|
|
55
|
+
|
|
56
|
+
if not source_type:
|
|
57
|
+
print(t('update_incomplete_info'))
|
|
58
|
+
return 1
|
|
59
|
+
|
|
60
|
+
# 构建安装命令
|
|
61
|
+
if source_type == "pypi":
|
|
62
|
+
source_arg = source.get("package", module_name)
|
|
63
|
+
# 如果指定了版本,添加版本号
|
|
64
|
+
if to_version:
|
|
65
|
+
source_arg = f"{source_arg}@{to_version}"
|
|
66
|
+
from_arg = "-f pypi"
|
|
67
|
+
elif source_type == "npm":
|
|
68
|
+
source_arg = source.get("package", module_name)
|
|
69
|
+
# 如果指定了版本,添加版本号
|
|
70
|
+
if to_version:
|
|
71
|
+
source_arg = f"{source_arg}@{to_version}"
|
|
72
|
+
from_arg = "-f npm"
|
|
73
|
+
elif source_type == "git":
|
|
74
|
+
source_arg = source.get("url", "")
|
|
75
|
+
from_arg = "-f git"
|
|
76
|
+
# Git 源不支持版本号
|
|
77
|
+
if to_version:
|
|
78
|
+
print("[Warning] Git 源不支持 --to 参数,将更新到最新提交")
|
|
79
|
+
elif source_type == "local":
|
|
80
|
+
print(t('update_local_no_auto'))
|
|
81
|
+
return 1
|
|
82
|
+
else:
|
|
83
|
+
print(t('update_unknown_source', type=source_type))
|
|
84
|
+
return 1
|
|
85
|
+
|
|
86
|
+
if not skip_confirm:
|
|
87
|
+
confirm_msg = t('update_confirm', name=module_name, source=source_type)
|
|
88
|
+
if to_version:
|
|
89
|
+
confirm_msg = f"确认更新 {module_name} 到版本 {to_version}?"
|
|
90
|
+
if not confirm_action(confirm_msg):
|
|
91
|
+
print(t('update_cancelled'))
|
|
92
|
+
return 1
|
|
93
|
+
|
|
94
|
+
# 调用 install 命令
|
|
95
|
+
print(t('reinstalling', name=module_name))
|
|
96
|
+
try:
|
|
97
|
+
cmd = [
|
|
98
|
+
sys.executable, "-m", "kite_cli", "install",
|
|
99
|
+
source_arg, "-l", loc, from_arg, "-y"
|
|
100
|
+
]
|
|
101
|
+
result = subprocess.run(cmd, capture_output=False, text=True)
|
|
102
|
+
return result.returncode
|
|
103
|
+
except Exception as e:
|
|
104
|
+
print(t('update_failed', error=str(e)))
|
|
105
|
+
return 1
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def find_module_locations(module_name: str):
|
|
109
|
+
"""查找模块在哪些位置存在"""
|
|
110
|
+
locations = []
|
|
111
|
+
for loc in ["dev", "local", "global"]:
|
|
112
|
+
try:
|
|
113
|
+
module_path = get_install_path(loc, module_name)
|
|
114
|
+
if module_path.exists():
|
|
115
|
+
locations.append(loc)
|
|
116
|
+
except Exception:
|
|
117
|
+
pass
|
|
118
|
+
return locations
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""核心功能模块"""
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"""平台检查器 - 检查模块在各平台是否存在"""
|
|
2
|
+
import subprocess
|
|
3
|
+
import json
|
|
4
|
+
from typing import Optional, List, Dict
|
|
5
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class PlatformChecker:
|
|
9
|
+
"""检查模块在各平台的存在性"""
|
|
10
|
+
|
|
11
|
+
@staticmethod
|
|
12
|
+
def check_pypi(name: str) -> Optional[Dict]:
|
|
13
|
+
"""检查 PyPI 是否存在该包"""
|
|
14
|
+
try:
|
|
15
|
+
# 使用 pip index versions
|
|
16
|
+
result = subprocess.run(
|
|
17
|
+
["pip", "index", "versions", name],
|
|
18
|
+
capture_output=True,
|
|
19
|
+
text=True,
|
|
20
|
+
timeout=10
|
|
21
|
+
)
|
|
22
|
+
if result.returncode == 0 and "Available versions:" in result.stdout:
|
|
23
|
+
# 解析版本
|
|
24
|
+
lines = result.stdout.split("\n")
|
|
25
|
+
for line in lines:
|
|
26
|
+
if "Available versions:" in line:
|
|
27
|
+
versions_str = line.split(":", 1)[1].strip()
|
|
28
|
+
versions = [v.strip() for v in versions_str.split(",")]
|
|
29
|
+
return {
|
|
30
|
+
"platform": "pypi",
|
|
31
|
+
"name": name,
|
|
32
|
+
"versions": versions,
|
|
33
|
+
"latest": versions[0] if versions else None
|
|
34
|
+
}
|
|
35
|
+
except (subprocess.TimeoutExpired, FileNotFoundError):
|
|
36
|
+
pass
|
|
37
|
+
return None
|
|
38
|
+
|
|
39
|
+
@staticmethod
|
|
40
|
+
def check_npm(name: str) -> Optional[Dict]:
|
|
41
|
+
"""检查 npm 是否存在该包"""
|
|
42
|
+
try:
|
|
43
|
+
# 获取所有版本
|
|
44
|
+
result = subprocess.run(
|
|
45
|
+
["npm", "view", name, "versions", "--json"],
|
|
46
|
+
capture_output=True,
|
|
47
|
+
text=True,
|
|
48
|
+
timeout=10,
|
|
49
|
+
shell=True # Windows 兼容性
|
|
50
|
+
)
|
|
51
|
+
if result.returncode == 0:
|
|
52
|
+
try:
|
|
53
|
+
versions_data = json.loads(result.stdout)
|
|
54
|
+
# versions 可能是字符串(单版本)或列表(多版本)
|
|
55
|
+
if isinstance(versions_data, str):
|
|
56
|
+
versions = [versions_data]
|
|
57
|
+
elif isinstance(versions_data, list):
|
|
58
|
+
versions = versions_data
|
|
59
|
+
else:
|
|
60
|
+
versions = []
|
|
61
|
+
|
|
62
|
+
if versions:
|
|
63
|
+
return {
|
|
64
|
+
"platform": "npm",
|
|
65
|
+
"name": name,
|
|
66
|
+
"versions": versions,
|
|
67
|
+
"latest": versions[-1] if versions else None # npm 返回的版本是升序
|
|
68
|
+
}
|
|
69
|
+
except json.JSONDecodeError:
|
|
70
|
+
# 降级到只获取最新版本
|
|
71
|
+
result = subprocess.run(
|
|
72
|
+
["npm", "view", name, "version"],
|
|
73
|
+
capture_output=True,
|
|
74
|
+
text=True,
|
|
75
|
+
timeout=10,
|
|
76
|
+
shell=True
|
|
77
|
+
)
|
|
78
|
+
if result.returncode == 0:
|
|
79
|
+
version = result.stdout.strip()
|
|
80
|
+
return {
|
|
81
|
+
"platform": "npm",
|
|
82
|
+
"name": name,
|
|
83
|
+
"versions": [version],
|
|
84
|
+
"latest": version
|
|
85
|
+
}
|
|
86
|
+
except (subprocess.TimeoutExpired, FileNotFoundError):
|
|
87
|
+
pass
|
|
88
|
+
return None
|
|
89
|
+
|
|
90
|
+
@staticmethod
|
|
91
|
+
def check_git(name: str) -> Optional[List[Dict]]:
|
|
92
|
+
"""检查 GitHub 是否存在相关仓库"""
|
|
93
|
+
try:
|
|
94
|
+
# 搜索 GitHub 仓库
|
|
95
|
+
result = subprocess.run(
|
|
96
|
+
["curl", "-s", f"https://api.github.com/search/repositories?q={name}+kite"],
|
|
97
|
+
capture_output=True,
|
|
98
|
+
text=True,
|
|
99
|
+
timeout=10
|
|
100
|
+
)
|
|
101
|
+
if result.returncode == 0:
|
|
102
|
+
data = json.loads(result.stdout)
|
|
103
|
+
if data.get("total_count", 0) > 0:
|
|
104
|
+
repos = []
|
|
105
|
+
for item in data.get("items", [])[:5]: # 最多返回 5 个
|
|
106
|
+
repos.append({
|
|
107
|
+
"platform": "git",
|
|
108
|
+
"name": item["name"],
|
|
109
|
+
"full_name": item["full_name"],
|
|
110
|
+
"url": item["clone_url"],
|
|
111
|
+
"description": item.get("description", "")
|
|
112
|
+
})
|
|
113
|
+
return repos
|
|
114
|
+
except (subprocess.TimeoutExpired, FileNotFoundError, json.JSONDecodeError):
|
|
115
|
+
pass
|
|
116
|
+
return None
|
|
117
|
+
|
|
118
|
+
@classmethod
|
|
119
|
+
def check_all(cls, name: str) -> Dict[str, any]:
|
|
120
|
+
"""并行检查所有平台"""
|
|
121
|
+
results = {
|
|
122
|
+
"pypi": None,
|
|
123
|
+
"npm": None,
|
|
124
|
+
"git": None
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
with ThreadPoolExecutor(max_workers=3) as executor:
|
|
128
|
+
futures = {
|
|
129
|
+
executor.submit(cls.check_pypi, name): "pypi",
|
|
130
|
+
executor.submit(cls.check_npm, name): "npm",
|
|
131
|
+
executor.submit(cls.check_git, name): "git"
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
for future in as_completed(futures):
|
|
135
|
+
platform = futures[future]
|
|
136
|
+
try:
|
|
137
|
+
result = future.result()
|
|
138
|
+
results[platform] = result
|
|
139
|
+
except Exception:
|
|
140
|
+
pass
|
|
141
|
+
|
|
142
|
+
return results
|