@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,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
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
"""依赖管理 - 安装 Python/npm/Kite 模块依赖"""
|
|
2
|
+
import subprocess
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from kite_cli.core.tool_installer import ToolInstaller
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class DependencyInstaller:
|
|
9
|
+
"""依赖安装器"""
|
|
10
|
+
|
|
11
|
+
@staticmethod
|
|
12
|
+
def install_python_deps(module_dir: Path, packages: list) -> bool:
|
|
13
|
+
"""安装 Python 依赖到模块内的 .venv"""
|
|
14
|
+
if not packages:
|
|
15
|
+
return True
|
|
16
|
+
|
|
17
|
+
# 确保 Python 和 pip 已安装
|
|
18
|
+
if not ToolInstaller.ensure_tool("python"):
|
|
19
|
+
print(" [Error] Python 未安装且自动安装失败")
|
|
20
|
+
return False
|
|
21
|
+
if not ToolInstaller.ensure_tool("pip"):
|
|
22
|
+
print(" [Error] pip 未安装且自动安装失败")
|
|
23
|
+
return False
|
|
24
|
+
|
|
25
|
+
print(f"[Install] 安装 Python 依赖: {', '.join(packages)}")
|
|
26
|
+
|
|
27
|
+
venv_dir = module_dir / ".venv"
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
# 创建虚拟环境
|
|
31
|
+
if not venv_dir.exists():
|
|
32
|
+
print(" 创建虚拟环境...")
|
|
33
|
+
result = subprocess.run(
|
|
34
|
+
[sys.executable, "-m", "venv", str(venv_dir)],
|
|
35
|
+
capture_output=True,
|
|
36
|
+
text=True,
|
|
37
|
+
timeout=60
|
|
38
|
+
)
|
|
39
|
+
if result.returncode != 0:
|
|
40
|
+
print(f" [Error] 创建虚拟环境失败: {result.stderr}")
|
|
41
|
+
return False
|
|
42
|
+
|
|
43
|
+
# 确定 pip 路径
|
|
44
|
+
if sys.platform == "win32":
|
|
45
|
+
pip_path = venv_dir / "Scripts" / "pip.exe"
|
|
46
|
+
else:
|
|
47
|
+
pip_path = venv_dir / "bin" / "pip"
|
|
48
|
+
|
|
49
|
+
# 安装依赖
|
|
50
|
+
print(" 安装包...")
|
|
51
|
+
result = subprocess.run(
|
|
52
|
+
[str(pip_path), "install"] + packages,
|
|
53
|
+
capture_output=True,
|
|
54
|
+
text=True,
|
|
55
|
+
timeout=300
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
if result.returncode != 0:
|
|
59
|
+
print(f" [Error] 安装失败: {result.stderr}")
|
|
60
|
+
return False
|
|
61
|
+
|
|
62
|
+
print(" [Done] Python 依赖安装完成")
|
|
63
|
+
return True
|
|
64
|
+
|
|
65
|
+
except Exception as e:
|
|
66
|
+
print(f" [Error] 安装失败: {e}")
|
|
67
|
+
return False
|
|
68
|
+
|
|
69
|
+
@staticmethod
|
|
70
|
+
def install_npm_deps(module_dir: Path, packages: list) -> bool:
|
|
71
|
+
"""安装 npm 依赖到模块内的 node_modules"""
|
|
72
|
+
if not packages:
|
|
73
|
+
return True
|
|
74
|
+
|
|
75
|
+
# 确保 npm 已安装
|
|
76
|
+
if not ToolInstaller.ensure_tool("npm"):
|
|
77
|
+
print(" [Error] npm 未安装且自动安装失败")
|
|
78
|
+
return False
|
|
79
|
+
|
|
80
|
+
print(f"[Install] 安装 npm 依赖: {', '.join(packages)}")
|
|
81
|
+
|
|
82
|
+
try:
|
|
83
|
+
# 检查是否有 package.json
|
|
84
|
+
package_json = module_dir / "package.json"
|
|
85
|
+
if not package_json.exists():
|
|
86
|
+
# 创建基础 package.json
|
|
87
|
+
import json
|
|
88
|
+
with open(package_json, "w") as f:
|
|
89
|
+
json.dump({
|
|
90
|
+
"name": module_dir.name,
|
|
91
|
+
"version": "1.0.0",
|
|
92
|
+
"dependencies": {}
|
|
93
|
+
}, f, indent=2)
|
|
94
|
+
|
|
95
|
+
# 安装依赖
|
|
96
|
+
result = subprocess.run(
|
|
97
|
+
["npm", "install"] + packages,
|
|
98
|
+
cwd=str(module_dir),
|
|
99
|
+
capture_output=True,
|
|
100
|
+
text=True,
|
|
101
|
+
timeout=300
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
if result.returncode != 0:
|
|
105
|
+
print(f" [Error] 安装失败: {result.stderr}")
|
|
106
|
+
return False
|
|
107
|
+
|
|
108
|
+
print(" [Done] npm 依赖安装完成")
|
|
109
|
+
return True
|
|
110
|
+
|
|
111
|
+
except Exception as e:
|
|
112
|
+
print(f" [Error] 安装失败: {e}")
|
|
113
|
+
return False
|
|
114
|
+
|
|
115
|
+
@staticmethod
|
|
116
|
+
def install_git_deps(module_dir: Path, repos: list) -> bool:
|
|
117
|
+
"""安装 Git 依赖到模块内的 .git_deps"""
|
|
118
|
+
if not repos:
|
|
119
|
+
return True
|
|
120
|
+
|
|
121
|
+
# 确保 git 已安装
|
|
122
|
+
if not ToolInstaller.ensure_tool("git"):
|
|
123
|
+
print(" [Error] git 未安装且自动安装失败")
|
|
124
|
+
return False
|
|
125
|
+
|
|
126
|
+
print(f"[Install] 安装 Git 依赖: {', '.join(repos)}")
|
|
127
|
+
|
|
128
|
+
git_deps_dir = module_dir / ".git_deps"
|
|
129
|
+
git_deps_dir.mkdir(exist_ok=True)
|
|
130
|
+
|
|
131
|
+
try:
|
|
132
|
+
for repo_url in repos:
|
|
133
|
+
# 提取仓库名
|
|
134
|
+
repo_name = repo_url.rstrip('/').split('/')[-1]
|
|
135
|
+
if repo_name.endswith('.git'):
|
|
136
|
+
repo_name = repo_name[:-4]
|
|
137
|
+
|
|
138
|
+
target_dir = git_deps_dir / repo_name
|
|
139
|
+
|
|
140
|
+
# 如果已存在,跳过
|
|
141
|
+
if target_dir.exists():
|
|
142
|
+
print(f" [Skip] {repo_name} 已存在")
|
|
143
|
+
continue
|
|
144
|
+
|
|
145
|
+
print(f" 克隆 {repo_name}...")
|
|
146
|
+
result = subprocess.run(
|
|
147
|
+
["git", "clone", repo_url, str(target_dir)],
|
|
148
|
+
capture_output=True,
|
|
149
|
+
text=True,
|
|
150
|
+
timeout=120
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
if result.returncode != 0:
|
|
154
|
+
print(f" [Error] 克隆失败: {result.stderr}")
|
|
155
|
+
return False
|
|
156
|
+
|
|
157
|
+
print(" [Done] Git 依赖安装完成")
|
|
158
|
+
return True
|
|
159
|
+
|
|
160
|
+
except FileNotFoundError:
|
|
161
|
+
print(" [Error] git 命令未找到,请先安装 Git")
|
|
162
|
+
return False
|
|
163
|
+
except Exception as e:
|
|
164
|
+
print(f" [Error] 安装失败: {e}")
|
|
165
|
+
return False
|
|
166
|
+
|
|
167
|
+
@staticmethod
|
|
168
|
+
def install_kite_deps(modules: list, location: str) -> bool:
|
|
169
|
+
"""递归安装 Kite 模块依赖"""
|
|
170
|
+
if not modules:
|
|
171
|
+
return True
|
|
172
|
+
|
|
173
|
+
print(f"[Install] 安装 Kite 模块依赖: {', '.join(modules)}")
|
|
174
|
+
|
|
175
|
+
# 这里需要递归调用 kite install
|
|
176
|
+
# 为了避免循环导入,使用 subprocess
|
|
177
|
+
for module in modules:
|
|
178
|
+
print(f" 安装 {module}...")
|
|
179
|
+
try:
|
|
180
|
+
result = subprocess.run(
|
|
181
|
+
[sys.executable, "-m", "kite_cli", "install", module, "-l", location, "-y"],
|
|
182
|
+
capture_output=True,
|
|
183
|
+
text=True,
|
|
184
|
+
timeout=300
|
|
185
|
+
)
|
|
186
|
+
if result.returncode != 0:
|
|
187
|
+
print(f" [Error] 安装 {module} 失败")
|
|
188
|
+
return False
|
|
189
|
+
except Exception as e:
|
|
190
|
+
print(f" [Error] 安装 {module} 失败: {e}")
|
|
191
|
+
return False
|
|
192
|
+
|
|
193
|
+
print(" [Done] Kite 模块依赖安装完成")
|
|
194
|
+
return True
|
|
195
|
+
|
|
196
|
+
@classmethod
|
|
197
|
+
def install_all_deps(cls, module_dir: Path, module_data: dict, location: str, skip_deps: bool = False) -> bool:
|
|
198
|
+
"""安装所有依赖"""
|
|
199
|
+
if skip_deps:
|
|
200
|
+
print("[Warning] 跳过依赖安装 (--no-deps)")
|
|
201
|
+
return True
|
|
202
|
+
|
|
203
|
+
# 从 module.md 读取依赖
|
|
204
|
+
dependencies = module_data.get("dependencies", {})
|
|
205
|
+
if not dependencies:
|
|
206
|
+
print("[Info] 无依赖需要安装")
|
|
207
|
+
return True
|
|
208
|
+
|
|
209
|
+
# Python 依赖
|
|
210
|
+
python_deps = dependencies.get("python", [])
|
|
211
|
+
if python_deps and not cls.install_python_deps(module_dir, python_deps):
|
|
212
|
+
return False
|
|
213
|
+
|
|
214
|
+
# npm 依赖
|
|
215
|
+
npm_deps = dependencies.get("npm", [])
|
|
216
|
+
if npm_deps and not cls.install_npm_deps(module_dir, npm_deps):
|
|
217
|
+
return False
|
|
218
|
+
|
|
219
|
+
# Git 依赖
|
|
220
|
+
git_deps = dependencies.get("git", [])
|
|
221
|
+
if git_deps and not cls.install_git_deps(module_dir, git_deps):
|
|
222
|
+
return False
|
|
223
|
+
|
|
224
|
+
# Kite 模块依赖
|
|
225
|
+
kite_deps = dependencies.get("kite", [])
|
|
226
|
+
if kite_deps and not cls.install_kite_deps(kite_deps, location):
|
|
227
|
+
return False
|
|
228
|
+
|
|
229
|
+
return True
|