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,349 @@
1
+ """
2
+ autopilot.py - 批量安装自动驾驶模式
3
+ ======================================
4
+
5
+ 灵感来源:ICE-cluade-SCompany 的 Autopilot Revenue 模式
6
+
7
+ 从文件/列表读取多个项目,后台逐个安装:
8
+ gitinstall autopilot projects.txt
9
+ gitinstall autopilot owner1/repo1 owner2/repo2 ...
10
+
11
+ 特性:
12
+ 1. 逐个安装:成功继续,失败跳过
13
+ 2. 进度追踪:实时显示总进度
14
+ 3. 汇总报告:完成后输出成功/失败统计
15
+ 4. 可恢复:中断后可从断点继续
16
+ 5. 并发控制:可配置同时安装数(默认 1)
17
+
18
+ 零外部依赖,纯 Python 标准库。
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import json
24
+ import os
25
+ import re
26
+ import time
27
+ from dataclasses import dataclass, field
28
+ from pathlib import Path
29
+ from typing import Optional
30
+
31
+ from log import get_logger
32
+ from i18n import t
33
+
34
+ logger = get_logger(__name__)
35
+
36
+
37
+ @dataclass
38
+ class BatchItem:
39
+ """批量安装项"""
40
+ identifier: str # owner/repo 或 URL
41
+ status: str = "pending" # pending, running, completed, failed, skipped
42
+ install_dir: str = ""
43
+ started_at: str = ""
44
+ completed_at: str = ""
45
+ duration_sec: float = 0.0
46
+ error: str = ""
47
+ strategy: str = ""
48
+
49
+
50
+ @dataclass
51
+ class BatchResult:
52
+ """批量安装结果"""
53
+ total: int = 0
54
+ completed: int = 0
55
+ failed: int = 0
56
+ skipped: int = 0
57
+ items: list[BatchItem] = field(default_factory=list)
58
+ started_at: str = ""
59
+ completed_at: str = ""
60
+ total_duration_sec: float = 0.0
61
+
62
+ @property
63
+ def success_rate(self) -> float:
64
+ done = self.completed + self.failed
65
+ if done == 0:
66
+ return 0.0
67
+ return self.completed / done
68
+
69
+ def to_dict(self) -> dict:
70
+ return {
71
+ "total": self.total,
72
+ "completed": self.completed,
73
+ "failed": self.failed,
74
+ "skipped": self.skipped,
75
+ "success_rate": self.success_rate,
76
+ "started_at": self.started_at,
77
+ "completed_at": self.completed_at,
78
+ "total_duration_sec": self.total_duration_sec,
79
+ "items": [
80
+ {
81
+ "identifier": it.identifier,
82
+ "status": it.status,
83
+ "install_dir": it.install_dir,
84
+ "duration_sec": it.duration_sec,
85
+ "error": it.error,
86
+ "strategy": it.strategy,
87
+ }
88
+ for it in self.items
89
+ ],
90
+ }
91
+
92
+
93
+ # ── 持久化路径 ──
94
+ AUTOPILOT_DIR = Path.home() / ".gitinstall" / "autopilot"
95
+ AUTOPILOT_STATE = AUTOPILOT_DIR / "state.json"
96
+
97
+
98
+ def parse_project_list(source: str) -> list[str]:
99
+ """解析项目列表
100
+
101
+ 支持:
102
+ - 单个 owner/repo
103
+ - 空格/逗号分隔多个
104
+ - 文件路径(每行一个或 JSON 数组)
105
+ """
106
+ projects = []
107
+
108
+ # 检查是否是文件
109
+ path = Path(source)
110
+ if path.exists() and path.is_file():
111
+ content = path.read_text(encoding="utf-8").strip()
112
+ # 尝试 JSON 数组
113
+ try:
114
+ data = json.loads(content)
115
+ if isinstance(data, list):
116
+ for item in data:
117
+ if isinstance(item, str):
118
+ projects.append(item.strip())
119
+ elif isinstance(item, dict):
120
+ projects.append(item.get("project", item.get("repo", "")))
121
+ return [p for p in projects if p]
122
+ except json.JSONDecodeError:
123
+ pass
124
+ # 按行解析
125
+ for line in content.split("\n"):
126
+ line = line.strip()
127
+ if line and not line.startswith("#"):
128
+ # 去除行注释
129
+ line = line.split("#")[0].strip()
130
+ if line:
131
+ projects.append(line)
132
+ return projects
133
+
134
+ # 空格/逗号分隔
135
+ for part in re.split(r'[,\s]+', source):
136
+ part = part.strip()
137
+ if part:
138
+ projects.append(part)
139
+
140
+ return projects
141
+
142
+
143
+ def run_autopilot(projects: list[str], install_dir: str = None,
144
+ llm_force: str = None, dry_run: bool = False,
145
+ on_progress: callable = None) -> BatchResult:
146
+ """执行批量安装
147
+
148
+ Args:
149
+ projects: 项目列表
150
+ install_dir: 基础安装目录
151
+ llm_force: 强制使用的 LLM
152
+ dry_run: 仅规划不执行
153
+ on_progress: 进度回调 (current, total, item)
154
+ """
155
+ result = BatchResult(
156
+ total=len(projects),
157
+ started_at=time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
158
+ )
159
+
160
+ for item_str in projects:
161
+ result.items.append(BatchItem(identifier=item_str))
162
+
163
+ # 保存初始状态
164
+ _save_state(result)
165
+
166
+ for i, item in enumerate(result.items):
167
+ item.status = "running"
168
+ item.started_at = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
169
+
170
+ # 进度回调
171
+ if on_progress:
172
+ on_progress(i + 1, result.total, item)
173
+
174
+ logger.info("="*60)
175
+ logger.info(t("autopilot.progress", current=i+1, total=result.total, identifier=item.identifier))
176
+ logger.info("="*60)
177
+
178
+ start_time = time.time()
179
+ try:
180
+ # 懒加载避免循环导入
181
+ from main import cmd_install
182
+
183
+ install_result = cmd_install(
184
+ item.identifier,
185
+ install_dir=install_dir,
186
+ llm_force=llm_force,
187
+ dry_run=dry_run,
188
+ )
189
+
190
+ item.duration_sec = time.time() - start_time
191
+ item.completed_at = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
192
+
193
+ if install_result.get("success"):
194
+ item.status = "completed"
195
+ item.install_dir = install_result.get("install_dir", "")
196
+ item.strategy = install_result.get("plan_strategy", "")
197
+ result.completed += 1
198
+ logger.info(t("autopilot.success", duration=f"{item.duration_sec:.1f}"))
199
+ else:
200
+ item.status = "failed"
201
+ item.error = install_result.get("error_summary", "未知错误")[:200]
202
+ item.strategy = install_result.get("plan_strategy", "")
203
+ result.failed += 1
204
+ logger.error(t("autopilot.install_failed", error=item.error[:80]))
205
+
206
+ except KeyboardInterrupt:
207
+ item.status = "skipped"
208
+ item.duration_sec = time.time() - start_time
209
+ result.skipped += 1
210
+ logger.warning(t("autopilot.user_interrupted"))
211
+ # 剩余标记为跳过
212
+ for remaining in result.items[i+1:]:
213
+ remaining.status = "skipped"
214
+ result.skipped += 1
215
+ break
216
+
217
+ except Exception as e:
218
+ item.status = "failed"
219
+ item.error = str(e)[:200]
220
+ item.duration_sec = time.time() - start_time
221
+ result.failed += 1
222
+ logger.error(t("autopilot.exception", error=e))
223
+
224
+ # 每步保存状态(支持恢复)
225
+ _save_state(result)
226
+
227
+ result.completed_at = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
228
+ result.total_duration_sec = sum(it.duration_sec for it in result.items)
229
+ _save_state(result)
230
+ return result
231
+
232
+
233
+ def resume_autopilot(llm_force: str = None,
234
+ install_dir: str = None) -> Optional[BatchResult]:
235
+ """从上次中断处恢复"""
236
+ state = _load_state()
237
+ if not state:
238
+ return None
239
+
240
+ # 找到第一个 pending/skipped 的项目
241
+ resume_idx = -1
242
+ for i, item in enumerate(state.items):
243
+ if item.status in ("pending", "skipped", "running"):
244
+ resume_idx = i
245
+ break
246
+
247
+ if resume_idx < 0:
248
+ return state # 所有项目已完成
249
+
250
+ # 恢复执行
251
+ pending = [it.identifier for it in state.items[resume_idx:]
252
+ if it.status in ("pending", "skipped", "running")]
253
+
254
+ if not pending:
255
+ return state
256
+
257
+ new_result = run_autopilot(pending, install_dir=install_dir,
258
+ llm_force=llm_force)
259
+
260
+ # 合并结果
261
+ for i, item in enumerate(state.items[resume_idx:]):
262
+ if i < len(new_result.items):
263
+ state.items[resume_idx + i] = new_result.items[i]
264
+
265
+ state.completed = sum(1 for it in state.items if it.status == "completed")
266
+ state.failed = sum(1 for it in state.items if it.status == "failed")
267
+ state.skipped = sum(1 for it in state.items if it.status == "skipped")
268
+ state.completed_at = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
269
+ state.total_duration_sec = sum(it.duration_sec for it in state.items)
270
+ _save_state(state)
271
+ return state
272
+
273
+
274
+ def _save_state(result: BatchResult):
275
+ """保存自动驾驶状态"""
276
+ AUTOPILOT_DIR.mkdir(parents=True, exist_ok=True)
277
+ with open(AUTOPILOT_STATE, "w", encoding="utf-8") as f:
278
+ json.dump(result.to_dict(), f, indent=2, ensure_ascii=False)
279
+ try:
280
+ os.chmod(AUTOPILOT_STATE, 0o600)
281
+ except OSError:
282
+ pass
283
+
284
+
285
+ def _load_state() -> Optional[BatchResult]:
286
+ """加载自动驾驶状态"""
287
+ if not AUTOPILOT_STATE.exists():
288
+ return None
289
+ try:
290
+ with open(AUTOPILOT_STATE, encoding="utf-8") as f:
291
+ data = json.load(f)
292
+ result = BatchResult(
293
+ total=data.get("total", 0),
294
+ completed=data.get("completed", 0),
295
+ failed=data.get("failed", 0),
296
+ skipped=data.get("skipped", 0),
297
+ started_at=data.get("started_at", ""),
298
+ completed_at=data.get("completed_at", ""),
299
+ total_duration_sec=data.get("total_duration_sec", 0.0),
300
+ )
301
+ for item_data in data.get("items", []):
302
+ result.items.append(BatchItem(
303
+ identifier=item_data.get("identifier", ""),
304
+ status=item_data.get("status", "pending"),
305
+ install_dir=item_data.get("install_dir", ""),
306
+ duration_sec=item_data.get("duration_sec", 0.0),
307
+ error=item_data.get("error", ""),
308
+ strategy=item_data.get("strategy", ""),
309
+ ))
310
+ return result
311
+ except (json.JSONDecodeError, OSError):
312
+ return None
313
+
314
+
315
+ # ─────────────────────────────────────────────
316
+ # 格式化输出
317
+ # ─────────────────────────────────────────────
318
+
319
+ def format_batch_result(result: BatchResult) -> str:
320
+ """格式化批量安装结果"""
321
+ lines = [
322
+ "",
323
+ "═" * 60,
324
+ "🚗 自动驾驶安装报告",
325
+ "═" * 60,
326
+ f" 总计:{result.total} 个项目",
327
+ f" ✅ 成功:{result.completed}",
328
+ f" ❌ 失败:{result.failed}",
329
+ f" ⏭️ 跳过:{result.skipped}",
330
+ f" 成功率:{result.success_rate:.1%}",
331
+ f" 总耗时:{result.total_duration_sec:.1f}s",
332
+ "",
333
+ "─" * 60,
334
+ ]
335
+
336
+ for item in result.items:
337
+ if item.status == "completed":
338
+ lines.append(f" ✅ {item.identifier} ({item.duration_sec:.1f}s)")
339
+ elif item.status == "failed":
340
+ lines.append(f" ❌ {item.identifier} ({item.duration_sec:.1f}s)")
341
+ if item.error:
342
+ lines.append(f" 错误:{item.error[:80]}")
343
+ elif item.status == "skipped":
344
+ lines.append(f" ⏭️ {item.identifier} (跳过)")
345
+ else:
346
+ lines.append(f" ⏳ {item.identifier} ({item.status})")
347
+
348
+ lines.append("═" * 60)
349
+ return "\n".join(lines)