gitinstall 1.1.0__py3-none-any.whl
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.
- gitinstall/__init__.py +61 -0
- gitinstall/_sdk.py +541 -0
- gitinstall/academic.py +831 -0
- gitinstall/admin.html +327 -0
- gitinstall/auto_update.py +384 -0
- gitinstall/autopilot.py +349 -0
- gitinstall/badge.py +476 -0
- gitinstall/checkpoint.py +330 -0
- gitinstall/cicd.py +499 -0
- gitinstall/clawhub.html +718 -0
- gitinstall/config_schema.py +353 -0
- gitinstall/db.py +984 -0
- gitinstall/db_backend.py +445 -0
- gitinstall/dep_chain.py +337 -0
- gitinstall/dependency_audit.py +1153 -0
- gitinstall/detector.py +542 -0
- gitinstall/doctor.py +493 -0
- gitinstall/education.py +869 -0
- gitinstall/enterprise.py +802 -0
- gitinstall/error_fixer.py +953 -0
- gitinstall/event_bus.py +251 -0
- gitinstall/executor.py +577 -0
- gitinstall/feature_flags.py +138 -0
- gitinstall/fetcher.py +921 -0
- gitinstall/huggingface.py +922 -0
- gitinstall/hw_detect.py +988 -0
- gitinstall/i18n.py +664 -0
- gitinstall/installer_registry.py +362 -0
- gitinstall/knowledge_base.py +379 -0
- gitinstall/license_check.py +605 -0
- gitinstall/llm.py +569 -0
- gitinstall/log.py +236 -0
- gitinstall/main.py +1408 -0
- gitinstall/mcp_agent.py +841 -0
- gitinstall/mcp_server.py +386 -0
- gitinstall/monorepo.py +810 -0
- gitinstall/multi_source.py +425 -0
- gitinstall/onboard.py +276 -0
- gitinstall/planner.py +222 -0
- gitinstall/planner_helpers.py +323 -0
- gitinstall/planner_known_projects.py +1010 -0
- gitinstall/planner_templates.py +996 -0
- gitinstall/remote_gpu.py +633 -0
- gitinstall/resilience.py +608 -0
- gitinstall/run_tests.py +572 -0
- gitinstall/skills.py +476 -0
- gitinstall/tool_schemas.py +324 -0
- gitinstall/trending.py +279 -0
- gitinstall/uninstaller.py +415 -0
- gitinstall/validate_top100.py +607 -0
- gitinstall/watchdog.py +180 -0
- gitinstall/web.py +1277 -0
- gitinstall/web_ui.html +2277 -0
- gitinstall-1.1.0.dist-info/METADATA +275 -0
- gitinstall-1.1.0.dist-info/RECORD +59 -0
- gitinstall-1.1.0.dist-info/WHEEL +5 -0
- gitinstall-1.1.0.dist-info/entry_points.txt +3 -0
- gitinstall-1.1.0.dist-info/licenses/LICENSE +21 -0
- gitinstall-1.1.0.dist-info/top_level.txt +1 -0
gitinstall/__init__.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""
|
|
2
|
+
gitinstall — 嵌入式软件安装引擎
|
|
3
|
+
================================
|
|
4
|
+
|
|
5
|
+
在任何项目中嵌入 gitinstall,获得跨平台软件安装能力。
|
|
6
|
+
|
|
7
|
+
核心函数::
|
|
8
|
+
|
|
9
|
+
import gitinstall
|
|
10
|
+
|
|
11
|
+
env = gitinstall.detect() # 检测系统环境
|
|
12
|
+
plan = gitinstall.plan("owner/repo", env=env) # 生成安装方案
|
|
13
|
+
result = gitinstall.install(plan) # 执行安装
|
|
14
|
+
fix = gitinstall.diagnose(stderr, command) # 报错诊断
|
|
15
|
+
|
|
16
|
+
辅助函数::
|
|
17
|
+
|
|
18
|
+
info = gitinstall.fetch("owner/repo") # 获取项目信息
|
|
19
|
+
report = gitinstall.doctor() # 系统诊断
|
|
20
|
+
audit = gitinstall.audit("owner/repo") # 依赖安全审计
|
|
21
|
+
|
|
22
|
+
零外部依赖 · 跨平台 · 线程安全 · AI 可选增强
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from ._sdk import (
|
|
26
|
+
detect,
|
|
27
|
+
plan,
|
|
28
|
+
install,
|
|
29
|
+
diagnose,
|
|
30
|
+
fetch,
|
|
31
|
+
doctor,
|
|
32
|
+
audit,
|
|
33
|
+
__version__,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
from .tool_schemas import (
|
|
37
|
+
openai_tools,
|
|
38
|
+
anthropic_tools,
|
|
39
|
+
gemini_tools,
|
|
40
|
+
json_schemas,
|
|
41
|
+
call_tool,
|
|
42
|
+
tool_names,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
__all__ = [
|
|
46
|
+
"detect",
|
|
47
|
+
"plan",
|
|
48
|
+
"install",
|
|
49
|
+
"diagnose",
|
|
50
|
+
"fetch",
|
|
51
|
+
"doctor",
|
|
52
|
+
"audit",
|
|
53
|
+
"__version__",
|
|
54
|
+
# AI 集成
|
|
55
|
+
"openai_tools",
|
|
56
|
+
"anthropic_tools",
|
|
57
|
+
"gemini_tools",
|
|
58
|
+
"json_schemas",
|
|
59
|
+
"call_tool",
|
|
60
|
+
"tool_names",
|
|
61
|
+
]
|
gitinstall/_sdk.py
ADDED
|
@@ -0,0 +1,541 @@
|
|
|
1
|
+
"""
|
|
2
|
+
gitinstall SDK — 嵌入式软件安装引擎的公共 API
|
|
3
|
+
==============================================
|
|
4
|
+
|
|
5
|
+
四个核心函数:
|
|
6
|
+
detect() → 检测系统环境
|
|
7
|
+
plan() → 生成安装方案
|
|
8
|
+
install() → 执行安装
|
|
9
|
+
diagnose() → 报错诊断
|
|
10
|
+
|
|
11
|
+
辅助函数:
|
|
12
|
+
fetch() → 获取项目信息
|
|
13
|
+
doctor() → 系统诊断
|
|
14
|
+
audit() → 依赖安全审计
|
|
15
|
+
|
|
16
|
+
用法::
|
|
17
|
+
|
|
18
|
+
import gitinstall
|
|
19
|
+
|
|
20
|
+
env = gitinstall.detect()
|
|
21
|
+
plan = gitinstall.plan("comfyanonymous/ComfyUI", env=env)
|
|
22
|
+
result = gitinstall.install(plan)
|
|
23
|
+
|
|
24
|
+
if not result["success"]:
|
|
25
|
+
fix = gitinstall.diagnose(result["error_message"], result["last_command"])
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
import sys
|
|
31
|
+
from pathlib import Path
|
|
32
|
+
from typing import Callable, Optional
|
|
33
|
+
|
|
34
|
+
# 确保 tools/ 目录在 sys.path 中,使内部 bare import 正常工作
|
|
35
|
+
_THIS_DIR = Path(__file__).parent
|
|
36
|
+
if str(_THIS_DIR) not in sys.path:
|
|
37
|
+
sys.path.insert(0, str(_THIS_DIR))
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# ── 核心函数 1:detect ───────────────────────
|
|
41
|
+
|
|
42
|
+
def detect() -> dict:
|
|
43
|
+
"""检测当前系统环境。
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
dict: 包含 os/hardware/gpu/runtimes/package_managers/disk 等信息。
|
|
47
|
+
|
|
48
|
+
示例::
|
|
49
|
+
|
|
50
|
+
env = gitinstall.detect()
|
|
51
|
+
print(env["gpu"]["type"]) # "apple_mps"
|
|
52
|
+
print(env["os"]["arch"]) # "arm64"
|
|
53
|
+
"""
|
|
54
|
+
from detector import EnvironmentDetector
|
|
55
|
+
return EnvironmentDetector().detect()
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
# ── 核心函数 2:plan ─────────────────────────
|
|
59
|
+
|
|
60
|
+
def plan(
|
|
61
|
+
identifier: str,
|
|
62
|
+
*,
|
|
63
|
+
env: dict = None,
|
|
64
|
+
llm: str = None,
|
|
65
|
+
local: bool = False,
|
|
66
|
+
) -> dict:
|
|
67
|
+
"""为指定项目生成安装方案。
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
identifier: GitHub 项目标识,如 "comfyanonymous/ComfyUI" 或 URL。
|
|
71
|
+
env: 环境信息 dict(来自 detect())。省略则自动检测。
|
|
72
|
+
llm: 指定 LLM(如 "ollama"/"openai"/"none")。省略则自动选择。
|
|
73
|
+
local: True 时用 git clone 本地分析,避免 GitHub API 限额。
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
dict: 包含 steps/launch_command/confidence/strategy 等字段。
|
|
77
|
+
失败时 status="error"。
|
|
78
|
+
|
|
79
|
+
示例::
|
|
80
|
+
|
|
81
|
+
plan = gitinstall.plan("pytorch/pytorch")
|
|
82
|
+
for step in plan["steps"]:
|
|
83
|
+
print(step["command"])
|
|
84
|
+
"""
|
|
85
|
+
from main import cmd_plan
|
|
86
|
+
result = cmd_plan(identifier, llm_force=llm, use_local=local)
|
|
87
|
+
|
|
88
|
+
# 如果调用方提供了 env 但 cmd_plan 内部会自行检测,
|
|
89
|
+
# 这里的 env 参数用于未来优化(避免重复检测)。
|
|
90
|
+
# 当前版本 cmd_plan 总是自行检测环境。
|
|
91
|
+
|
|
92
|
+
return result
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
# ── 核心函数 3:install ──────────────────────
|
|
96
|
+
|
|
97
|
+
def install(
|
|
98
|
+
plan_or_identifier,
|
|
99
|
+
*,
|
|
100
|
+
install_dir: str = None,
|
|
101
|
+
llm: str = None,
|
|
102
|
+
local: bool = False,
|
|
103
|
+
dry_run: bool = False,
|
|
104
|
+
on_progress: Optional[Callable[[dict], None]] = None,
|
|
105
|
+
) -> dict:
|
|
106
|
+
"""执行安装。
|
|
107
|
+
|
|
108
|
+
可以传入 plan() 返回的 dict,或直接传项目标识符(会自动调用 plan)。
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
plan_or_identifier: plan() 的返回值 dict,或项目标识字符串。
|
|
112
|
+
install_dir: 安装目录。省略则使用默认目录。
|
|
113
|
+
llm: 指定 LLM。
|
|
114
|
+
local: 使用本地模式获取项目信息。
|
|
115
|
+
dry_run: True 时只展示计划不执行。
|
|
116
|
+
on_progress: 进度回调函数,接收 event dict。
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
dict: 包含 success/install_dir/launch_command/steps_completed 等字段。
|
|
120
|
+
|
|
121
|
+
回调事件::
|
|
122
|
+
|
|
123
|
+
on_progress 会收到以下类型的 event:
|
|
124
|
+
{"type": "plan_ready", "steps": [...], "project": "..."}
|
|
125
|
+
{"type": "step_start", "step": 1, "total": 5, "command": "...", "description": "..."}
|
|
126
|
+
{"type": "step_done", "step": 1, "total": 5, "success": True, "duration": 2.3}
|
|
127
|
+
{"type": "step_failed","step": 1, "total": 5, "error": "..."}
|
|
128
|
+
{"type": "install_done", "success": True, "install_dir": "..."}
|
|
129
|
+
|
|
130
|
+
示例::
|
|
131
|
+
|
|
132
|
+
# 方式 1:直接传标识符
|
|
133
|
+
result = gitinstall.install("comfyanonymous/ComfyUI")
|
|
134
|
+
|
|
135
|
+
# 方式 2:传 plan dict
|
|
136
|
+
p = gitinstall.plan("comfyanonymous/ComfyUI")
|
|
137
|
+
result = gitinstall.install(p)
|
|
138
|
+
|
|
139
|
+
# 方式 3:带进度回调
|
|
140
|
+
def progress(event):
|
|
141
|
+
print(f"[{event.get('step', '?')}/{event.get('total', '?')}] {event.get('type')}")
|
|
142
|
+
result = gitinstall.install("comfyanonymous/ComfyUI", on_progress=progress)
|
|
143
|
+
"""
|
|
144
|
+
# 如果传入的是字符串,走完整的 cmd_install 路径
|
|
145
|
+
if isinstance(plan_or_identifier, str):
|
|
146
|
+
if on_progress:
|
|
147
|
+
return _install_with_progress(
|
|
148
|
+
plan_or_identifier,
|
|
149
|
+
install_dir=install_dir,
|
|
150
|
+
llm=llm,
|
|
151
|
+
local=local,
|
|
152
|
+
dry_run=dry_run,
|
|
153
|
+
on_progress=on_progress,
|
|
154
|
+
)
|
|
155
|
+
from main import cmd_install
|
|
156
|
+
return cmd_install(
|
|
157
|
+
plan_or_identifier,
|
|
158
|
+
install_dir=install_dir,
|
|
159
|
+
llm_force=llm,
|
|
160
|
+
dry_run=dry_run,
|
|
161
|
+
use_local=local,
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
# 如果传入的是 plan dict,直接执行
|
|
165
|
+
if isinstance(plan_or_identifier, dict):
|
|
166
|
+
plan_dict = plan_or_identifier
|
|
167
|
+
if plan_dict.get("status") == "error":
|
|
168
|
+
return plan_dict
|
|
169
|
+
|
|
170
|
+
plan_data = plan_dict.get("plan", plan_dict)
|
|
171
|
+
project_name = plan_dict.get("project", "")
|
|
172
|
+
steps = plan_data.get("steps", [])
|
|
173
|
+
|
|
174
|
+
if not steps:
|
|
175
|
+
return {"status": "error", "message": "安装方案中没有步骤。", "success": False}
|
|
176
|
+
|
|
177
|
+
if dry_run:
|
|
178
|
+
return {"status": "ok", "dry_run": True, "plan": plan_data, "success": True}
|
|
179
|
+
|
|
180
|
+
from executor import InstallExecutor
|
|
181
|
+
from llm import create_provider
|
|
182
|
+
from pathlib import Path
|
|
183
|
+
|
|
184
|
+
llm_provider = create_provider(force=llm)
|
|
185
|
+
executor = InstallExecutor(llm_provider=llm_provider, verbose=True)
|
|
186
|
+
|
|
187
|
+
if install_dir:
|
|
188
|
+
base_dir = str(Path(install_dir).expanduser())
|
|
189
|
+
Path(base_dir).mkdir(parents=True, exist_ok=True)
|
|
190
|
+
executor.executor.work_dir = base_dir
|
|
191
|
+
executor.executor._current_dir = base_dir
|
|
192
|
+
|
|
193
|
+
if on_progress:
|
|
194
|
+
on_progress({
|
|
195
|
+
"type": "plan_ready",
|
|
196
|
+
"steps": [s.get("description", "") for s in steps],
|
|
197
|
+
"project": project_name,
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
result = _execute_with_callbacks(executor, plan_data, project_name, on_progress)
|
|
201
|
+
return result
|
|
202
|
+
|
|
203
|
+
raise TypeError(f"install() 需要 str 或 dict,收到 {type(plan_or_identifier).__name__}")
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _install_with_progress(
|
|
207
|
+
identifier: str,
|
|
208
|
+
*,
|
|
209
|
+
install_dir: str = None,
|
|
210
|
+
llm: str = None,
|
|
211
|
+
local: bool = False,
|
|
212
|
+
dry_run: bool = False,
|
|
213
|
+
on_progress: Callable,
|
|
214
|
+
) -> dict:
|
|
215
|
+
"""带进度回调的完整安装流程。"""
|
|
216
|
+
# 1. 规划
|
|
217
|
+
on_progress({"type": "detecting", "message": "检测系统环境..."})
|
|
218
|
+
plan_result = plan(identifier, llm=llm, local=local)
|
|
219
|
+
|
|
220
|
+
if plan_result.get("status") != "ok":
|
|
221
|
+
return plan_result
|
|
222
|
+
|
|
223
|
+
plan_data = plan_result["plan"]
|
|
224
|
+
project_name = plan_result.get("project", identifier)
|
|
225
|
+
steps = plan_data.get("steps", [])
|
|
226
|
+
|
|
227
|
+
on_progress({
|
|
228
|
+
"type": "plan_ready",
|
|
229
|
+
"steps": [s.get("description", "") for s in steps],
|
|
230
|
+
"project": project_name,
|
|
231
|
+
"confidence": plan_result.get("confidence", ""),
|
|
232
|
+
"strategy": plan_data.get("strategy", ""),
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
if dry_run:
|
|
236
|
+
return {"status": "ok", "dry_run": True, "plan": plan_data, "success": True}
|
|
237
|
+
|
|
238
|
+
# 2. 预检
|
|
239
|
+
from resilience import preflight_check
|
|
240
|
+
pf = preflight_check(steps)
|
|
241
|
+
if not pf.all_ready:
|
|
242
|
+
on_progress({
|
|
243
|
+
"type": "preflight",
|
|
244
|
+
"missing_tools": pf.missing_tools,
|
|
245
|
+
"install_commands": [s.get("command", "") for s in pf.install_commands],
|
|
246
|
+
})
|
|
247
|
+
steps = pf.install_commands + steps
|
|
248
|
+
plan_data["steps"] = steps
|
|
249
|
+
|
|
250
|
+
# 3. 执行
|
|
251
|
+
from executor import InstallExecutor
|
|
252
|
+
from llm import create_provider
|
|
253
|
+
from pathlib import Path
|
|
254
|
+
|
|
255
|
+
llm_provider = create_provider(force=llm)
|
|
256
|
+
executor = InstallExecutor(llm_provider=llm_provider, verbose=True)
|
|
257
|
+
|
|
258
|
+
if install_dir:
|
|
259
|
+
base_dir = str(Path(install_dir).expanduser())
|
|
260
|
+
Path(base_dir).mkdir(parents=True, exist_ok=True)
|
|
261
|
+
executor.executor.work_dir = base_dir
|
|
262
|
+
executor.executor._current_dir = base_dir
|
|
263
|
+
|
|
264
|
+
result = _execute_with_callbacks(executor, plan_data, project_name, on_progress)
|
|
265
|
+
|
|
266
|
+
# 4. 失败时尝试回退
|
|
267
|
+
if not result["success"]:
|
|
268
|
+
from resilience import generate_fallback_plans
|
|
269
|
+
owner, _, repo = identifier.partition("/")
|
|
270
|
+
env = detect()
|
|
271
|
+
project_types = plan_result.get("_project_types", [])
|
|
272
|
+
dep_files = plan_result.get("_dependency_files", {})
|
|
273
|
+
primary_strategy = plan_data.get("strategy", "")
|
|
274
|
+
|
|
275
|
+
fallback_plans = generate_fallback_plans(
|
|
276
|
+
owner, repo, project_types, env, dependency_files=dep_files,
|
|
277
|
+
)
|
|
278
|
+
tried = {primary_strategy}
|
|
279
|
+
|
|
280
|
+
for fb in fallback_plans:
|
|
281
|
+
if fb.strategy in tried:
|
|
282
|
+
continue
|
|
283
|
+
tried.add(fb.strategy)
|
|
284
|
+
|
|
285
|
+
on_progress({
|
|
286
|
+
"type": "fallback_start",
|
|
287
|
+
"strategy": fb.strategy,
|
|
288
|
+
"tier": fb.tier,
|
|
289
|
+
})
|
|
290
|
+
|
|
291
|
+
fb_plan = {
|
|
292
|
+
"steps": fb.steps,
|
|
293
|
+
"launch_command": plan_data.get("launch_command", ""),
|
|
294
|
+
"strategy": fb.strategy,
|
|
295
|
+
}
|
|
296
|
+
executor.executor.reset(install_dir)
|
|
297
|
+
fb_result = _execute_with_callbacks(executor, fb_plan, project_name, on_progress)
|
|
298
|
+
|
|
299
|
+
if fb_result["success"]:
|
|
300
|
+
result = fb_result
|
|
301
|
+
break
|
|
302
|
+
|
|
303
|
+
# 5. 遥测记录
|
|
304
|
+
try:
|
|
305
|
+
from db import record_install_telemetry
|
|
306
|
+
record_install_telemetry(
|
|
307
|
+
project=project_name,
|
|
308
|
+
strategy=plan_data.get("strategy", ""),
|
|
309
|
+
gpu_info=plan_result.get("gpu_info"),
|
|
310
|
+
env=plan_result.get("_env") or {},
|
|
311
|
+
success=result["success"],
|
|
312
|
+
error_type="step_failed" if not result["success"] else None,
|
|
313
|
+
error_message=result.get("error_summary") if not result["success"] else None,
|
|
314
|
+
duration_sec=None,
|
|
315
|
+
steps_total=result.get("steps_total", 0),
|
|
316
|
+
steps_completed=result.get("steps_completed", 0),
|
|
317
|
+
)
|
|
318
|
+
except Exception:
|
|
319
|
+
pass
|
|
320
|
+
|
|
321
|
+
return result
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def _execute_with_callbacks(
|
|
325
|
+
executor,
|
|
326
|
+
plan_data: dict,
|
|
327
|
+
project_name: str,
|
|
328
|
+
on_progress: Optional[Callable] = None,
|
|
329
|
+
) -> dict:
|
|
330
|
+
"""执行安装计划,在每步前后触发回调。"""
|
|
331
|
+
from executor import check_command_safety
|
|
332
|
+
|
|
333
|
+
steps = plan_data.get("steps", [])
|
|
334
|
+
launch_command = plan_data.get("launch_command", "")
|
|
335
|
+
completed = []
|
|
336
|
+
error_summary = ""
|
|
337
|
+
|
|
338
|
+
for i, step in enumerate(steps, 1):
|
|
339
|
+
command = step.get("command", "").strip()
|
|
340
|
+
description = step.get("description", f"步骤 {i}")
|
|
341
|
+
working_dir = step.get("working_dir", "")
|
|
342
|
+
|
|
343
|
+
if not command:
|
|
344
|
+
continue
|
|
345
|
+
|
|
346
|
+
is_safe, warning = check_command_safety(command)
|
|
347
|
+
if not is_safe:
|
|
348
|
+
error_summary = warning
|
|
349
|
+
if on_progress:
|
|
350
|
+
on_progress({
|
|
351
|
+
"type": "step_blocked",
|
|
352
|
+
"step": i, "total": len(steps),
|
|
353
|
+
"command": command, "reason": warning,
|
|
354
|
+
})
|
|
355
|
+
break
|
|
356
|
+
|
|
357
|
+
if on_progress:
|
|
358
|
+
on_progress({
|
|
359
|
+
"type": "step_start",
|
|
360
|
+
"step": i, "total": len(steps),
|
|
361
|
+
"command": command, "description": description,
|
|
362
|
+
})
|
|
363
|
+
|
|
364
|
+
step_result = executor.executor.run(command, working_dir or None)
|
|
365
|
+
|
|
366
|
+
if step_result.success:
|
|
367
|
+
completed.append(step_result)
|
|
368
|
+
if on_progress:
|
|
369
|
+
on_progress({
|
|
370
|
+
"type": "step_done",
|
|
371
|
+
"step": i, "total": len(steps),
|
|
372
|
+
"success": True,
|
|
373
|
+
"duration": step_result.duration_sec,
|
|
374
|
+
})
|
|
375
|
+
else:
|
|
376
|
+
# 尝试自动修复
|
|
377
|
+
fixed = executor._try_fix(step_result, i, len(steps))
|
|
378
|
+
if fixed:
|
|
379
|
+
completed.append(step_result)
|
|
380
|
+
if on_progress:
|
|
381
|
+
on_progress({
|
|
382
|
+
"type": "step_fixed",
|
|
383
|
+
"step": i, "total": len(steps),
|
|
384
|
+
"fix": step_result.fix_command,
|
|
385
|
+
})
|
|
386
|
+
else:
|
|
387
|
+
error_summary = (
|
|
388
|
+
f"第 {i} 步失败:{description}\n"
|
|
389
|
+
f"命令:{command}\n"
|
|
390
|
+
f"报错:{step_result.stderr[:500]}"
|
|
391
|
+
)
|
|
392
|
+
if on_progress:
|
|
393
|
+
on_progress({
|
|
394
|
+
"type": "step_failed",
|
|
395
|
+
"step": i, "total": len(steps),
|
|
396
|
+
"error": step_result.stderr[:500],
|
|
397
|
+
"command": command,
|
|
398
|
+
})
|
|
399
|
+
break
|
|
400
|
+
|
|
401
|
+
success = len(completed) == len([s for s in steps if s.get("command", "").strip()])
|
|
402
|
+
install_dir = executor.executor._current_dir
|
|
403
|
+
|
|
404
|
+
if on_progress:
|
|
405
|
+
on_progress({
|
|
406
|
+
"type": "install_done",
|
|
407
|
+
"success": success,
|
|
408
|
+
"install_dir": install_dir,
|
|
409
|
+
"steps_completed": len(completed),
|
|
410
|
+
"steps_total": len(steps),
|
|
411
|
+
})
|
|
412
|
+
|
|
413
|
+
return {
|
|
414
|
+
"status": "ok" if success else "error",
|
|
415
|
+
"success": success,
|
|
416
|
+
"project": project_name,
|
|
417
|
+
"install_dir": install_dir,
|
|
418
|
+
"launch_command": launch_command,
|
|
419
|
+
"error_summary": error_summary,
|
|
420
|
+
"steps_completed": len(completed),
|
|
421
|
+
"steps_total": len(steps),
|
|
422
|
+
"plan_strategy": plan_data.get("strategy", ""),
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
# ── 核心函数 4:diagnose ────────────────────
|
|
427
|
+
|
|
428
|
+
def diagnose(
|
|
429
|
+
stderr: str,
|
|
430
|
+
command: str = "",
|
|
431
|
+
stdout: str = "",
|
|
432
|
+
) -> dict | None:
|
|
433
|
+
"""诊断安装报错并返回修复建议。
|
|
434
|
+
|
|
435
|
+
Args:
|
|
436
|
+
stderr: 错误输出文本。
|
|
437
|
+
command: 引发错误的命令。
|
|
438
|
+
stdout: 标准输出文本(可选)。
|
|
439
|
+
|
|
440
|
+
Returns:
|
|
441
|
+
dict: 包含 root_cause/fix_commands/confidence 等字段。
|
|
442
|
+
如果无法诊断,返回 None。
|
|
443
|
+
|
|
444
|
+
示例::
|
|
445
|
+
|
|
446
|
+
fix = gitinstall.diagnose(
|
|
447
|
+
stderr="error: externally-managed-environment",
|
|
448
|
+
command="pip install pandas"
|
|
449
|
+
)
|
|
450
|
+
if fix:
|
|
451
|
+
print(fix["fix_commands"]) # ["pip install --break-system-packages pandas"]
|
|
452
|
+
"""
|
|
453
|
+
from error_fixer import diagnose as _diagnose
|
|
454
|
+
result = _diagnose(command, stderr, stdout)
|
|
455
|
+
if result is None:
|
|
456
|
+
return None
|
|
457
|
+
return {
|
|
458
|
+
"root_cause": result.root_cause,
|
|
459
|
+
"fix_commands": result.fix_commands,
|
|
460
|
+
"retry_original": result.retry_original,
|
|
461
|
+
"confidence": result.confidence,
|
|
462
|
+
"outcome": result.outcome,
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
# ── 辅助函数 ────────────────────────────────
|
|
467
|
+
|
|
468
|
+
def fetch(identifier: str, *, local: bool = False) -> dict:
|
|
469
|
+
"""获取 GitHub 项目信息。
|
|
470
|
+
|
|
471
|
+
Args:
|
|
472
|
+
identifier: 项目标识("owner/repo" 或 URL)。
|
|
473
|
+
local: True 时用 git clone 本地分析。
|
|
474
|
+
|
|
475
|
+
Returns:
|
|
476
|
+
dict: 包含项目名、描述、星标、语言、类型、依赖文件等。
|
|
477
|
+
"""
|
|
478
|
+
if local:
|
|
479
|
+
from fetcher import fetch_project_local
|
|
480
|
+
info = fetch_project_local(identifier)
|
|
481
|
+
else:
|
|
482
|
+
from fetcher import fetch_project
|
|
483
|
+
info = fetch_project(identifier)
|
|
484
|
+
return {
|
|
485
|
+
"full_name": info.full_name,
|
|
486
|
+
"description": info.description,
|
|
487
|
+
"stars": info.stars,
|
|
488
|
+
"language": info.language,
|
|
489
|
+
"license": info.license,
|
|
490
|
+
"project_type": info.project_type,
|
|
491
|
+
"clone_url": info.clone_url,
|
|
492
|
+
"homepage": info.homepage,
|
|
493
|
+
"dependency_files": list(info.dependency_files.keys()),
|
|
494
|
+
"readme_preview": info.readme[:500] if info.readme else "",
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
def doctor() -> dict:
|
|
499
|
+
"""运行系统诊断,返回检查结果。
|
|
500
|
+
|
|
501
|
+
Returns:
|
|
502
|
+
dict: 包含 checks 列表和 all_ok 布尔值。
|
|
503
|
+
"""
|
|
504
|
+
from doctor import run_doctor
|
|
505
|
+
report = run_doctor()
|
|
506
|
+
return {
|
|
507
|
+
"status": "ok",
|
|
508
|
+
"all_ok": report.all_ok,
|
|
509
|
+
"ok_count": report.ok_count,
|
|
510
|
+
"warn_count": report.warn_count,
|
|
511
|
+
"error_count": report.error_count,
|
|
512
|
+
"checks": [
|
|
513
|
+
{
|
|
514
|
+
"name": c.name,
|
|
515
|
+
"level": c.level,
|
|
516
|
+
"message": c.message,
|
|
517
|
+
"detail": c.detail,
|
|
518
|
+
"fix_hint": c.fix_hint,
|
|
519
|
+
}
|
|
520
|
+
for c in report.checks
|
|
521
|
+
],
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
def audit(identifier: str, *, online: bool = False) -> dict:
|
|
526
|
+
"""审计项目依赖的安全漏洞。
|
|
527
|
+
|
|
528
|
+
Args:
|
|
529
|
+
identifier: 项目标识。
|
|
530
|
+
online: True 时联网查询 CVE 数据库。
|
|
531
|
+
|
|
532
|
+
Returns:
|
|
533
|
+
dict: 包含各生态系统的漏洞报告。
|
|
534
|
+
"""
|
|
535
|
+
from main import cmd_audit
|
|
536
|
+
return cmd_audit(identifier, online=online)
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
# ── 版本信息 ─────────────────────────────────
|
|
540
|
+
|
|
541
|
+
__version__ = "1.1.0"
|