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/checkpoint.py
ADDED
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
"""
|
|
2
|
+
checkpoint.py - 安装断点恢复系统
|
|
3
|
+
==================================
|
|
4
|
+
|
|
5
|
+
灵感来源:PersonalBrain 的 status.json 断点恢复机制
|
|
6
|
+
|
|
7
|
+
安装过程中每完成一步写入进度文件,遇到网络中断/崩溃后:
|
|
8
|
+
gitinstall resume [project]
|
|
9
|
+
可以从上次断点继续安装,无需从头重来。
|
|
10
|
+
|
|
11
|
+
数据存储:~/.gitinstall/checkpoints/<owner>__<repo>.json
|
|
12
|
+
|
|
13
|
+
零外部依赖,纯 Python 标准库。
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import json
|
|
19
|
+
import os
|
|
20
|
+
import time
|
|
21
|
+
from dataclasses import dataclass, field
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from typing import Optional
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# ── 数据路径 ──
|
|
27
|
+
CHECKPOINT_DIR = Path.home() / ".gitinstall" / "checkpoints"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class StepCheckpoint:
|
|
32
|
+
"""单步检查点"""
|
|
33
|
+
index: int
|
|
34
|
+
command: str
|
|
35
|
+
description: str
|
|
36
|
+
status: str = "pending" # pending, running, completed, failed, skipped
|
|
37
|
+
started_at: str = ""
|
|
38
|
+
completed_at: str = ""
|
|
39
|
+
exit_code: int = -1
|
|
40
|
+
error: str = ""
|
|
41
|
+
duration_sec: float = 0.0
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass
|
|
45
|
+
class InstallCheckpoint:
|
|
46
|
+
"""完整安装检查点"""
|
|
47
|
+
project: str # owner/repo
|
|
48
|
+
owner: str = ""
|
|
49
|
+
repo: str = ""
|
|
50
|
+
install_dir: str = ""
|
|
51
|
+
llm_used: str = ""
|
|
52
|
+
strategy: str = ""
|
|
53
|
+
created_at: str = ""
|
|
54
|
+
updated_at: str = ""
|
|
55
|
+
status: str = "in_progress" # in_progress, completed, failed, abandoned
|
|
56
|
+
current_step: int = 0
|
|
57
|
+
total_steps: int = 0
|
|
58
|
+
steps: list[StepCheckpoint] = field(default_factory=list)
|
|
59
|
+
plan: dict = field(default_factory=dict)
|
|
60
|
+
env_snapshot: dict = field(default_factory=dict)
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def completed_steps(self) -> int:
|
|
64
|
+
return sum(1 for s in self.steps if s.status == "completed")
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def progress_pct(self) -> float:
|
|
68
|
+
if not self.total_steps:
|
|
69
|
+
return 0.0
|
|
70
|
+
return (self.completed_steps / self.total_steps) * 100
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def checkpoint_file(self) -> Path:
|
|
74
|
+
safe = f"{self.owner}__{self.repo}".replace("/", "__")
|
|
75
|
+
return CHECKPOINT_DIR / f"{safe}.json"
|
|
76
|
+
|
|
77
|
+
def to_dict(self) -> dict:
|
|
78
|
+
return {
|
|
79
|
+
"project": self.project,
|
|
80
|
+
"owner": self.owner,
|
|
81
|
+
"repo": self.repo,
|
|
82
|
+
"install_dir": self.install_dir,
|
|
83
|
+
"llm_used": self.llm_used,
|
|
84
|
+
"strategy": self.strategy,
|
|
85
|
+
"created_at": self.created_at,
|
|
86
|
+
"updated_at": self.updated_at,
|
|
87
|
+
"status": self.status,
|
|
88
|
+
"current_step": self.current_step,
|
|
89
|
+
"total_steps": self.total_steps,
|
|
90
|
+
"steps": [
|
|
91
|
+
{
|
|
92
|
+
"index": s.index,
|
|
93
|
+
"command": s.command,
|
|
94
|
+
"description": s.description,
|
|
95
|
+
"status": s.status,
|
|
96
|
+
"started_at": s.started_at,
|
|
97
|
+
"completed_at": s.completed_at,
|
|
98
|
+
"exit_code": s.exit_code,
|
|
99
|
+
"error": s.error,
|
|
100
|
+
"duration_sec": s.duration_sec,
|
|
101
|
+
}
|
|
102
|
+
for s in self.steps
|
|
103
|
+
],
|
|
104
|
+
"plan": self.plan,
|
|
105
|
+
"env_snapshot": self.env_snapshot,
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
@classmethod
|
|
109
|
+
def from_dict(cls, d: dict) -> "InstallCheckpoint":
|
|
110
|
+
cp = cls(
|
|
111
|
+
project=d.get("project", ""),
|
|
112
|
+
owner=d.get("owner", ""),
|
|
113
|
+
repo=d.get("repo", ""),
|
|
114
|
+
install_dir=d.get("install_dir", ""),
|
|
115
|
+
llm_used=d.get("llm_used", ""),
|
|
116
|
+
strategy=d.get("strategy", ""),
|
|
117
|
+
created_at=d.get("created_at", ""),
|
|
118
|
+
updated_at=d.get("updated_at", ""),
|
|
119
|
+
status=d.get("status", "in_progress"),
|
|
120
|
+
current_step=d.get("current_step", 0),
|
|
121
|
+
total_steps=d.get("total_steps", 0),
|
|
122
|
+
plan=d.get("plan", {}),
|
|
123
|
+
env_snapshot=d.get("env_snapshot", {}),
|
|
124
|
+
)
|
|
125
|
+
for s in d.get("steps", []):
|
|
126
|
+
cp.steps.append(StepCheckpoint(
|
|
127
|
+
index=s.get("index", 0),
|
|
128
|
+
command=s.get("command", ""),
|
|
129
|
+
description=s.get("description", ""),
|
|
130
|
+
status=s.get("status", "pending"),
|
|
131
|
+
started_at=s.get("started_at", ""),
|
|
132
|
+
completed_at=s.get("completed_at", ""),
|
|
133
|
+
exit_code=s.get("exit_code", -1),
|
|
134
|
+
error=s.get("error", ""),
|
|
135
|
+
duration_sec=s.get("duration_sec", 0.0),
|
|
136
|
+
))
|
|
137
|
+
return cp
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
# ─────────────────────────────────────────────
|
|
141
|
+
# Checkpoint Manager
|
|
142
|
+
# ─────────────────────────────────────────────
|
|
143
|
+
|
|
144
|
+
class CheckpointManager:
|
|
145
|
+
"""管理安装断点"""
|
|
146
|
+
|
|
147
|
+
def __init__(self):
|
|
148
|
+
CHECKPOINT_DIR.mkdir(parents=True, exist_ok=True)
|
|
149
|
+
|
|
150
|
+
def create(self, owner: str, repo: str, plan: dict,
|
|
151
|
+
install_dir: str = "", llm_used: str = "",
|
|
152
|
+
env_snapshot: dict = None) -> InstallCheckpoint:
|
|
153
|
+
"""创建新的安装检查点"""
|
|
154
|
+
steps_data = plan.get("steps", [])
|
|
155
|
+
now = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
|
156
|
+
|
|
157
|
+
cp = InstallCheckpoint(
|
|
158
|
+
project=f"{owner}/{repo}",
|
|
159
|
+
owner=owner, repo=repo,
|
|
160
|
+
install_dir=install_dir,
|
|
161
|
+
llm_used=llm_used,
|
|
162
|
+
strategy=plan.get("strategy", ""),
|
|
163
|
+
created_at=now, updated_at=now,
|
|
164
|
+
status="in_progress",
|
|
165
|
+
current_step=0,
|
|
166
|
+
total_steps=len(steps_data),
|
|
167
|
+
plan=plan,
|
|
168
|
+
env_snapshot=env_snapshot or {},
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
for i, step in enumerate(steps_data):
|
|
172
|
+
cp.steps.append(StepCheckpoint(
|
|
173
|
+
index=i,
|
|
174
|
+
command=step.get("command", ""),
|
|
175
|
+
description=step.get("description", ""),
|
|
176
|
+
))
|
|
177
|
+
|
|
178
|
+
self._save(cp)
|
|
179
|
+
return cp
|
|
180
|
+
|
|
181
|
+
def mark_step_running(self, cp: InstallCheckpoint, step_idx: int):
|
|
182
|
+
"""标记步骤开始执行"""
|
|
183
|
+
if step_idx < len(cp.steps):
|
|
184
|
+
cp.steps[step_idx].status = "running"
|
|
185
|
+
cp.steps[step_idx].started_at = time.strftime(
|
|
186
|
+
"%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
|
187
|
+
cp.current_step = step_idx
|
|
188
|
+
self._save(cp)
|
|
189
|
+
|
|
190
|
+
def mark_step_completed(self, cp: InstallCheckpoint, step_idx: int,
|
|
191
|
+
exit_code: int = 0, duration_sec: float = 0.0):
|
|
192
|
+
"""标记步骤完成"""
|
|
193
|
+
if step_idx < len(cp.steps):
|
|
194
|
+
s = cp.steps[step_idx]
|
|
195
|
+
s.status = "completed"
|
|
196
|
+
s.completed_at = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
|
197
|
+
s.exit_code = exit_code
|
|
198
|
+
s.duration_sec = duration_sec
|
|
199
|
+
cp.current_step = step_idx + 1
|
|
200
|
+
self._save(cp)
|
|
201
|
+
|
|
202
|
+
def mark_step_failed(self, cp: InstallCheckpoint, step_idx: int,
|
|
203
|
+
exit_code: int = 1, error: str = "",
|
|
204
|
+
duration_sec: float = 0.0):
|
|
205
|
+
"""标记步骤失败"""
|
|
206
|
+
if step_idx < len(cp.steps):
|
|
207
|
+
s = cp.steps[step_idx]
|
|
208
|
+
s.status = "failed"
|
|
209
|
+
s.completed_at = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
|
210
|
+
s.exit_code = exit_code
|
|
211
|
+
s.error = error
|
|
212
|
+
s.duration_sec = duration_sec
|
|
213
|
+
cp.status = "failed"
|
|
214
|
+
self._save(cp)
|
|
215
|
+
|
|
216
|
+
def mark_completed(self, cp: InstallCheckpoint):
|
|
217
|
+
"""标记整个安装完成"""
|
|
218
|
+
cp.status = "completed"
|
|
219
|
+
cp.updated_at = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
|
220
|
+
self._save(cp)
|
|
221
|
+
|
|
222
|
+
def mark_abandoned(self, cp: InstallCheckpoint):
|
|
223
|
+
"""标记安装放弃"""
|
|
224
|
+
cp.status = "abandoned"
|
|
225
|
+
cp.updated_at = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
|
226
|
+
self._save(cp)
|
|
227
|
+
|
|
228
|
+
def get_checkpoint(self, owner: str, repo: str) -> Optional[InstallCheckpoint]:
|
|
229
|
+
"""获取项目的检查点"""
|
|
230
|
+
safe = f"{owner}__{repo}"
|
|
231
|
+
path = CHECKPOINT_DIR / f"{safe}.json"
|
|
232
|
+
if not path.exists():
|
|
233
|
+
return None
|
|
234
|
+
try:
|
|
235
|
+
with open(path, encoding="utf-8") as f:
|
|
236
|
+
data = json.load(f)
|
|
237
|
+
return InstallCheckpoint.from_dict(data)
|
|
238
|
+
except (json.JSONDecodeError, OSError):
|
|
239
|
+
return None
|
|
240
|
+
|
|
241
|
+
def get_resumable(self) -> list[InstallCheckpoint]:
|
|
242
|
+
"""获取所有可恢复的安装"""
|
|
243
|
+
results = []
|
|
244
|
+
if not CHECKPOINT_DIR.exists():
|
|
245
|
+
return results
|
|
246
|
+
for f in CHECKPOINT_DIR.glob("*.json"):
|
|
247
|
+
try:
|
|
248
|
+
with open(f, encoding="utf-8") as fh:
|
|
249
|
+
data = json.load(fh)
|
|
250
|
+
cp = InstallCheckpoint.from_dict(data)
|
|
251
|
+
if cp.status in ("in_progress", "failed"):
|
|
252
|
+
results.append(cp)
|
|
253
|
+
except (json.JSONDecodeError, OSError):
|
|
254
|
+
continue
|
|
255
|
+
return sorted(results, key=lambda c: c.updated_at, reverse=True)
|
|
256
|
+
|
|
257
|
+
def remove_checkpoint(self, owner: str, repo: str) -> bool:
|
|
258
|
+
"""删除检查点"""
|
|
259
|
+
safe = f"{owner}__{repo}"
|
|
260
|
+
path = CHECKPOINT_DIR / f"{safe}.json"
|
|
261
|
+
if path.exists():
|
|
262
|
+
path.unlink()
|
|
263
|
+
return True
|
|
264
|
+
return False
|
|
265
|
+
|
|
266
|
+
def get_resume_step(self, cp: InstallCheckpoint) -> int:
|
|
267
|
+
"""计算应该从哪一步恢复"""
|
|
268
|
+
for i, s in enumerate(cp.steps):
|
|
269
|
+
if s.status in ("pending", "failed", "running"):
|
|
270
|
+
return i
|
|
271
|
+
return len(cp.steps) # 全部完成
|
|
272
|
+
|
|
273
|
+
def _save(self, cp: InstallCheckpoint):
|
|
274
|
+
"""保存检查点到文件"""
|
|
275
|
+
cp.updated_at = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
|
276
|
+
path = cp.checkpoint_file
|
|
277
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
278
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
279
|
+
json.dump(cp.to_dict(), f, indent=2, ensure_ascii=False)
|
|
280
|
+
try:
|
|
281
|
+
os.chmod(path, 0o600)
|
|
282
|
+
except OSError:
|
|
283
|
+
pass
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
# ─────────────────────────────────────────────
|
|
287
|
+
# 格式化输出
|
|
288
|
+
# ─────────────────────────────────────────────
|
|
289
|
+
|
|
290
|
+
def format_checkpoint_list(checkpoints: list[InstallCheckpoint]) -> str:
|
|
291
|
+
"""格式化可恢复安装列表"""
|
|
292
|
+
if not checkpoints:
|
|
293
|
+
return " 📭 没有可恢复的安装任务"
|
|
294
|
+
|
|
295
|
+
lines = [f" 📋 发现 {len(checkpoints)} 个可恢复的安装:\n"]
|
|
296
|
+
for cp in checkpoints:
|
|
297
|
+
icon = "⏸️ " if cp.status == "in_progress" else "❌"
|
|
298
|
+
lines.append(f" {icon} {cp.project}")
|
|
299
|
+
lines.append(f" 进度:{cp.completed_steps}/{cp.total_steps} 步"
|
|
300
|
+
f"({cp.progress_pct:.0f}%)")
|
|
301
|
+
lines.append(f" 策略:{cp.strategy} LLM:{cp.llm_used}")
|
|
302
|
+
lines.append(f" 更新:{cp.updated_at}")
|
|
303
|
+
# 显示失败点
|
|
304
|
+
failed = [s for s in cp.steps if s.status == "failed"]
|
|
305
|
+
if failed:
|
|
306
|
+
lines.append(f" 失败:Step {failed[0].index + 1} - {failed[0].description}")
|
|
307
|
+
if failed[0].error:
|
|
308
|
+
lines.append(f" 错误:{failed[0].error[:80]}")
|
|
309
|
+
lines.append("")
|
|
310
|
+
|
|
311
|
+
return "\n".join(lines)
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def format_resume_plan(cp: InstallCheckpoint, resume_step: int) -> str:
|
|
315
|
+
"""格式化恢复计划"""
|
|
316
|
+
lines = [
|
|
317
|
+
f"\n🔄 恢复安装:{cp.project}",
|
|
318
|
+
f" 从第 {resume_step + 1}/{cp.total_steps} 步继续",
|
|
319
|
+
f" 已完成:{cp.completed_steps} 步",
|
|
320
|
+
" ─" * 25,
|
|
321
|
+
]
|
|
322
|
+
for i, s in enumerate(cp.steps):
|
|
323
|
+
if s.status == "completed":
|
|
324
|
+
lines.append(f" ✅ {i+1}. {s.description} ({s.duration_sec:.1f}s)")
|
|
325
|
+
elif i == resume_step:
|
|
326
|
+
lines.append(f" ▶️ {i+1}. {s.description} ← 从这里继续")
|
|
327
|
+
else:
|
|
328
|
+
lines.append(f" ⏳ {i+1}. {s.description}")
|
|
329
|
+
lines.append(" ─" * 25)
|
|
330
|
+
return "\n".join(lines)
|