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,379 @@
1
+ """
2
+ knowledge_base.py - 安装知识库
3
+ ================================
4
+
5
+ 灵感来源:ICE-OEM 的知识库向量检索
6
+
7
+ 把所有成功/失败安装案例存储为「知识」,新安装时通过
8
+ 关键词匹配找到「相似项目怎么装的」。
9
+
10
+ 知识结构:
11
+ 每个安装案例记录:
12
+ - 项目信息:owner/repo, project_types, languages
13
+ - 环境信息:OS, GPU, 已有工具
14
+ - 安装策略:使用的 steps
15
+ - 结果:成功/失败 + 错误信息
16
+ - 标签:便于检索
17
+
18
+ 存储:~/.gitinstall/knowledge.json(纯 JSON,无需向量数据库)
19
+
20
+ 零外部依赖,纯 Python 标准库。
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ import json
26
+ import os
27
+ import re
28
+ import time
29
+ from collections import Counter
30
+ from dataclasses import dataclass, field
31
+ from pathlib import Path
32
+ from typing import Optional
33
+
34
+
35
+ # ── 数据路径 ──
36
+ KB_DIR = Path.home() / ".gitinstall"
37
+ KB_FILE = KB_DIR / "knowledge.json"
38
+
39
+
40
+ @dataclass
41
+ class KnowledgeEntry:
42
+ """一条安装知识"""
43
+ id: str # 唯一 ID
44
+ project: str # owner/repo
45
+ project_types: list[str] = field(default_factory=list)
46
+ languages: list[str] = field(default_factory=list)
47
+ os_type: str = "" # darwin, linux, win32
48
+ arch: str = "" # x86_64, arm64
49
+ gpu_type: str = "" # nvidia, apple_silicon, none
50
+ strategy: str = "" # 使用的安装策略
51
+ steps: list[dict] = field(default_factory=list) # 安装步骤
52
+ success: bool = False
53
+ error_type: str = "" # 错误类别
54
+ error_message: str = "" # 错误信息
55
+ fix_applied: str = "" # 应用的修复
56
+ duration_sec: float = 0.0
57
+ tags: list[str] = field(default_factory=list)
58
+ created_at: str = ""
59
+ notes: str = ""
60
+
61
+ def to_dict(self) -> dict:
62
+ return {
63
+ "id": self.id,
64
+ "project": self.project,
65
+ "project_types": self.project_types,
66
+ "languages": self.languages,
67
+ "os_type": self.os_type,
68
+ "arch": self.arch,
69
+ "gpu_type": self.gpu_type,
70
+ "strategy": self.strategy,
71
+ "steps": self.steps,
72
+ "success": self.success,
73
+ "error_type": self.error_type,
74
+ "error_message": self.error_message,
75
+ "fix_applied": self.fix_applied,
76
+ "duration_sec": self.duration_sec,
77
+ "tags": self.tags,
78
+ "created_at": self.created_at,
79
+ "notes": self.notes,
80
+ }
81
+
82
+ @classmethod
83
+ def from_dict(cls, d: dict) -> "KnowledgeEntry":
84
+ return cls(
85
+ id=str(d.get("id", "")),
86
+ project=str(d.get("project", "")),
87
+ project_types=d.get("project_types", []),
88
+ languages=d.get("languages", []),
89
+ os_type=str(d.get("os_type", "")),
90
+ arch=str(d.get("arch", "")),
91
+ gpu_type=str(d.get("gpu_type", "")),
92
+ strategy=str(d.get("strategy", "")),
93
+ steps=d.get("steps", []),
94
+ success=bool(d.get("success", False)),
95
+ error_type=str(d.get("error_type", "")),
96
+ error_message=str(d.get("error_message", "")),
97
+ fix_applied=str(d.get("fix_applied", "")),
98
+ duration_sec=float(d.get("duration_sec", 0.0)),
99
+ tags=d.get("tags", []),
100
+ created_at=str(d.get("created_at", "")),
101
+ notes=str(d.get("notes", "")),
102
+ )
103
+
104
+ @property
105
+ def keywords(self) -> set[str]:
106
+ """提取关键词用于匹配"""
107
+ words = set()
108
+ words.add(self.project.lower())
109
+ # owner 和 repo 分别加
110
+ parts = self.project.split("/")
111
+ for p in parts:
112
+ words.add(p.lower())
113
+ words.update(t.lower() for t in self.project_types)
114
+ words.update(l.lower() for l in self.languages)
115
+ words.update(t.lower() for t in self.tags)
116
+ if self.os_type:
117
+ words.add(self.os_type.lower())
118
+ if self.gpu_type:
119
+ words.add(self.gpu_type.lower())
120
+ if self.strategy:
121
+ words.add(self.strategy.lower())
122
+ return words
123
+
124
+
125
+ @dataclass
126
+ class MatchResult:
127
+ """知识匹配结果"""
128
+ entry: KnowledgeEntry
129
+ score: float = 0.0 # 匹配分数 0-1
130
+ match_reasons: list[str] = field(default_factory=list)
131
+
132
+
133
+ # ─────────────────────────────────────────────
134
+ # Knowledge Base Manager
135
+ # ─────────────────────────────────────────────
136
+
137
+ class KnowledgeBase:
138
+ """安装知识库"""
139
+
140
+ def __init__(self, kb_file: Path = None):
141
+ self.kb_file = kb_file or KB_FILE
142
+ self._entries: list[KnowledgeEntry] = []
143
+ self._loaded = False
144
+
145
+ def _ensure_loaded(self):
146
+ if self._loaded:
147
+ return
148
+ self._load()
149
+ self._loaded = True
150
+
151
+ def _load(self):
152
+ if not self.kb_file.exists():
153
+ self._entries = []
154
+ return
155
+ try:
156
+ with open(self.kb_file, encoding="utf-8") as f:
157
+ data = json.load(f)
158
+ self._entries = [KnowledgeEntry.from_dict(d)
159
+ for d in (data if isinstance(data, list) else [])]
160
+ except (json.JSONDecodeError, OSError):
161
+ self._entries = []
162
+
163
+ def _save(self):
164
+ self.kb_file.parent.mkdir(parents=True, exist_ok=True)
165
+ with open(self.kb_file, "w", encoding="utf-8") as f:
166
+ json.dump([e.to_dict() for e in self._entries],
167
+ f, indent=2, ensure_ascii=False)
168
+ try:
169
+ os.chmod(self.kb_file, 0o600)
170
+ except OSError:
171
+ pass
172
+
173
+ def record(self, project: str, project_types: list[str] = None,
174
+ languages: list[str] = None, os_type: str = "",
175
+ arch: str = "", gpu_type: str = "",
176
+ strategy: str = "", steps: list[dict] = None,
177
+ success: bool = False, error_type: str = "",
178
+ error_message: str = "", fix_applied: str = "",
179
+ duration_sec: float = 0.0, tags: list[str] = None,
180
+ notes: str = "") -> KnowledgeEntry:
181
+ """记录一次安装知识"""
182
+ self._ensure_loaded()
183
+
184
+ entry_id = f"{project}_{int(time.time())}"
185
+ entry = KnowledgeEntry(
186
+ id=entry_id,
187
+ project=project,
188
+ project_types=project_types or [],
189
+ languages=languages or [],
190
+ os_type=os_type, arch=arch, gpu_type=gpu_type,
191
+ strategy=strategy,
192
+ steps=steps or [],
193
+ success=success,
194
+ error_type=error_type,
195
+ error_message=error_message[:500],
196
+ fix_applied=fix_applied,
197
+ duration_sec=duration_sec,
198
+ tags=tags or [],
199
+ created_at=time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
200
+ notes=notes,
201
+ )
202
+ self._entries.append(entry)
203
+
204
+ # 限制记录数量
205
+ if len(self._entries) > 5000:
206
+ self._entries = self._entries[-5000:]
207
+
208
+ self._save()
209
+ return entry
210
+
211
+ def search(self, project: str = "", project_types: list[str] = None,
212
+ languages: list[str] = None, os_type: str = "",
213
+ gpu_type: str = "", success_only: bool = False,
214
+ limit: int = 10) -> list[MatchResult]:
215
+ """搜索相似安装案例"""
216
+ self._ensure_loaded()
217
+
218
+ # 构建查询关键词
219
+ query_words = set()
220
+ if project:
221
+ query_words.add(project.lower())
222
+ for part in project.split("/"):
223
+ query_words.add(part.lower())
224
+ if project_types:
225
+ query_words.update(t.lower() for t in project_types)
226
+ if languages:
227
+ query_words.update(l.lower() for l in languages)
228
+ if os_type:
229
+ query_words.add(os_type.lower())
230
+ if gpu_type:
231
+ query_words.add(gpu_type.lower())
232
+
233
+ if not query_words:
234
+ return []
235
+
236
+ results = []
237
+ for entry in self._entries:
238
+ if success_only and not entry.success:
239
+ continue
240
+
241
+ entry_kw = entry.keywords
242
+ common = query_words & entry_kw
243
+ if not common:
244
+ continue
245
+
246
+ # 计算匹配分数
247
+ score = len(common) / max(len(query_words), 1)
248
+ reasons = []
249
+
250
+ # 完全相同项目 → 高分
251
+ if project and entry.project.lower() == project.lower():
252
+ score = min(score + 0.5, 1.0)
253
+ reasons.append("完全相同项目")
254
+
255
+ # 相同 OS → 加分
256
+ if os_type and entry.os_type == os_type:
257
+ score = min(score + 0.1, 1.0)
258
+ reasons.append(f"相同 OS ({os_type})")
259
+
260
+ # 相同 GPU → 加分
261
+ if gpu_type and entry.gpu_type == gpu_type:
262
+ score = min(score + 0.1, 1.0)
263
+ reasons.append(f"相同 GPU ({gpu_type})")
264
+
265
+ # 项目类型匹配
266
+ if project_types and entry.project_types:
267
+ type_overlap = set(t.lower() for t in project_types) & \
268
+ set(t.lower() for t in entry.project_types)
269
+ if type_overlap:
270
+ score = min(score + 0.15, 1.0)
271
+ reasons.append(f"类型匹配 ({', '.join(type_overlap)})")
272
+
273
+ results.append(MatchResult(entry=entry, score=score,
274
+ match_reasons=reasons))
275
+
276
+ # 按分数排序
277
+ results.sort(key=lambda r: r.score, reverse=True)
278
+ return results[:limit]
279
+
280
+ def get_success_rate(self, project: str = "") -> dict:
281
+ """获取安装成功率统计"""
282
+ self._ensure_loaded()
283
+
284
+ entries = self._entries
285
+ if project:
286
+ entries = [e for e in entries
287
+ if e.project.lower() == project.lower()]
288
+
289
+ total = len(entries)
290
+ success = sum(1 for e in entries if e.success)
291
+
292
+ # 按策略分组
293
+ strategy_stats: dict[str, dict] = {}
294
+ for e in entries:
295
+ key = e.strategy or "unknown"
296
+ if key not in strategy_stats:
297
+ strategy_stats[key] = {"total": 0, "success": 0}
298
+ strategy_stats[key]["total"] += 1
299
+ if e.success:
300
+ strategy_stats[key]["success"] += 1
301
+
302
+ # 常见错误
303
+ errors = Counter(e.error_type for e in entries if e.error_type)
304
+
305
+ return {
306
+ "total": total,
307
+ "success": success,
308
+ "rate": success / max(total, 1),
309
+ "strategies": strategy_stats,
310
+ "common_errors": dict(errors.most_common(5)),
311
+ }
312
+
313
+ def get_stats(self) -> dict:
314
+ """获取知识库统计"""
315
+ self._ensure_loaded()
316
+
317
+ total = len(self._entries)
318
+ success = sum(1 for e in self._entries if e.success)
319
+ projects = len(set(e.project for e in self._entries))
320
+ strategies = Counter(e.strategy for e in self._entries if e.strategy)
321
+
322
+ return {
323
+ "total_entries": total,
324
+ "success_count": success,
325
+ "failure_count": total - success,
326
+ "success_rate": success / max(total, 1),
327
+ "unique_projects": projects,
328
+ "top_strategies": dict(strategies.most_common(5)),
329
+ }
330
+
331
+ def count(self) -> int:
332
+ self._ensure_loaded()
333
+ return len(self._entries)
334
+
335
+
336
+ # ─────────────────────────────────────────────
337
+ # 格式化输出
338
+ # ─────────────────────────────────────────────
339
+
340
+ def format_search_results(results: list[MatchResult]) -> str:
341
+ """格式化搜索结果"""
342
+ if not results:
343
+ return " 📭 知识库中没有相似案例"
344
+
345
+ lines = [f" 📚 找到 {len(results)} 个相似安装案例:", ""]
346
+ for i, r in enumerate(results[:5]):
347
+ icon = "✅" if r.entry.success else "❌"
348
+ score_bar = "█" * int(r.score * 10) + "░" * (10 - int(r.score * 10))
349
+ lines.append(f" {i+1}. {icon} {r.entry.project}")
350
+ lines.append(f" 匹配度:{score_bar} {r.score:.0%}")
351
+ if r.match_reasons:
352
+ lines.append(f" 原因:{', '.join(r.match_reasons)}")
353
+ lines.append(f" 策略:{r.entry.strategy} OS:{r.entry.os_type}"
354
+ f" GPU:{r.entry.gpu_type or 'none'}")
355
+ if not r.entry.success and r.entry.error_type:
356
+ lines.append(f" ❌ 错误:{r.entry.error_type}")
357
+ if r.entry.fix_applied:
358
+ lines.append(f" 💡 修复:{r.entry.fix_applied}")
359
+ lines.append("")
360
+
361
+ return "\n".join(lines)
362
+
363
+
364
+ def format_kb_stats(stats: dict) -> str:
365
+ """格式化知识库统计"""
366
+ rate = stats.get("success_rate", 0)
367
+ bar = "█" * int(rate * 20) + "░" * (20 - int(rate * 20))
368
+ lines = [
369
+ "📚 安装知识库統计:",
370
+ f" 总记录:{stats.get('total_entries', 0)} "
371
+ f"项目数:{stats.get('unique_projects', 0)}",
372
+ f" 成功率:{bar} {rate:.1%}",
373
+ f" 成功 {stats.get('success_count', 0)} / "
374
+ f"失败 {stats.get('failure_count', 0)}",
375
+ ]
376
+ top = stats.get("top_strategies", {})
377
+ if top:
378
+ lines.append(f" 热门策略:{', '.join(f'{k}({v})' for k, v in top.items())}")
379
+ return "\n".join(lines)