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.
Files changed (59) hide show
  1. gitinstall/__init__.py +61 -0
  2. gitinstall/_sdk.py +541 -0
  3. gitinstall/academic.py +831 -0
  4. gitinstall/admin.html +327 -0
  5. gitinstall/auto_update.py +384 -0
  6. gitinstall/autopilot.py +349 -0
  7. gitinstall/badge.py +476 -0
  8. gitinstall/checkpoint.py +330 -0
  9. gitinstall/cicd.py +499 -0
  10. gitinstall/clawhub.html +718 -0
  11. gitinstall/config_schema.py +353 -0
  12. gitinstall/db.py +984 -0
  13. gitinstall/db_backend.py +445 -0
  14. gitinstall/dep_chain.py +337 -0
  15. gitinstall/dependency_audit.py +1153 -0
  16. gitinstall/detector.py +542 -0
  17. gitinstall/doctor.py +493 -0
  18. gitinstall/education.py +869 -0
  19. gitinstall/enterprise.py +802 -0
  20. gitinstall/error_fixer.py +953 -0
  21. gitinstall/event_bus.py +251 -0
  22. gitinstall/executor.py +577 -0
  23. gitinstall/feature_flags.py +138 -0
  24. gitinstall/fetcher.py +921 -0
  25. gitinstall/huggingface.py +922 -0
  26. gitinstall/hw_detect.py +988 -0
  27. gitinstall/i18n.py +664 -0
  28. gitinstall/installer_registry.py +362 -0
  29. gitinstall/knowledge_base.py +379 -0
  30. gitinstall/license_check.py +605 -0
  31. gitinstall/llm.py +569 -0
  32. gitinstall/log.py +236 -0
  33. gitinstall/main.py +1408 -0
  34. gitinstall/mcp_agent.py +841 -0
  35. gitinstall/mcp_server.py +386 -0
  36. gitinstall/monorepo.py +810 -0
  37. gitinstall/multi_source.py +425 -0
  38. gitinstall/onboard.py +276 -0
  39. gitinstall/planner.py +222 -0
  40. gitinstall/planner_helpers.py +323 -0
  41. gitinstall/planner_known_projects.py +1010 -0
  42. gitinstall/planner_templates.py +996 -0
  43. gitinstall/remote_gpu.py +633 -0
  44. gitinstall/resilience.py +608 -0
  45. gitinstall/run_tests.py +572 -0
  46. gitinstall/skills.py +476 -0
  47. gitinstall/tool_schemas.py +324 -0
  48. gitinstall/trending.py +279 -0
  49. gitinstall/uninstaller.py +415 -0
  50. gitinstall/validate_top100.py +607 -0
  51. gitinstall/watchdog.py +180 -0
  52. gitinstall/web.py +1277 -0
  53. gitinstall/web_ui.html +2277 -0
  54. gitinstall-1.1.0.dist-info/METADATA +275 -0
  55. gitinstall-1.1.0.dist-info/RECORD +59 -0
  56. gitinstall-1.1.0.dist-info/WHEEL +5 -0
  57. gitinstall-1.1.0.dist-info/entry_points.txt +3 -0
  58. gitinstall-1.1.0.dist-info/licenses/LICENSE +21 -0
  59. gitinstall-1.1.0.dist-info/top_level.txt +1 -0
@@ -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)