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
gitinstall/skills.py ADDED
@@ -0,0 +1,476 @@
1
+ """
2
+ skills.py - gitinstall Skills 插件系统
3
+ =======================================
4
+
5
+ 灵感来源:OpenClaw ClawHub Skills 注册表
6
+
7
+ Skills 是社区贡献的安装策略扩展,允许用户:
8
+ 1. 安装社区共享的安装策略(类似 OpenClaw 的 Skills)
9
+ 2. 创建自己的安装策略并分享
10
+ 3. 自动发现并使用匹配的 Skill
11
+
12
+ Skill 结构:
13
+ ~/.gitinstall/skills/<skill-name>/
14
+ ├── skill.json # 元数据(名称、版本、匹配规则)
15
+ ├── install.json # 安装步骤定义
16
+ └── README.md # 说明文档
17
+
18
+ 零外部依赖,纯 Python 标准库。
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import json
24
+ import os
25
+ import re
26
+ import shutil
27
+ import subprocess
28
+ import sys
29
+ import time
30
+ from dataclasses import dataclass, field
31
+ from pathlib import Path
32
+ from typing import Optional
33
+
34
+
35
+ # ── Skills 根目录 ──
36
+ SKILLS_DIR = Path.home() / ".gitinstall" / "skills"
37
+
38
+ # ── 内置 Skills 注册表 URL ──
39
+ REGISTRY_URL = "https://raw.githubusercontent.com/gitinstall/skills-registry/main/registry.json"
40
+
41
+
42
+ @dataclass
43
+ class SkillMeta:
44
+ """Skill 元数据"""
45
+ name: str
46
+ version: str
47
+ description: str
48
+ author: str = ""
49
+ match_repos: list[str] = field(default_factory=list) # 精确匹配: ["owner/repo"]
50
+ match_patterns: list[str] = field(default_factory=list) # 正则匹配项目类型
51
+ match_languages: list[str] = field(default_factory=list) # 匹配编程语言
52
+ match_files: list[str] = field(default_factory=list) # 匹配项目中包含的文件
53
+ tags: list[str] = field(default_factory=list)
54
+ homepage: str = ""
55
+ min_gitinstall_version: str = ""
56
+
57
+ @classmethod
58
+ def from_dict(cls, d: dict) -> "SkillMeta":
59
+ return cls(
60
+ name=str(d.get("name", "")),
61
+ version=str(d.get("version", "0.1.0")),
62
+ description=str(d.get("description", "")),
63
+ author=str(d.get("author", "")),
64
+ match_repos=[str(x) for x in d.get("match_repos", [])],
65
+ match_patterns=[str(x) for x in d.get("match_patterns", [])],
66
+ match_languages=[str(x) for x in d.get("match_languages", [])],
67
+ match_files=[str(x) for x in d.get("match_files", [])],
68
+ tags=[str(x) for x in d.get("tags", [])],
69
+ homepage=str(d.get("homepage", "")),
70
+ min_gitinstall_version=str(d.get("min_gitinstall_version", "")),
71
+ )
72
+
73
+
74
+ @dataclass
75
+ class SkillInstallPlan:
76
+ """Skill 定义的安装计划"""
77
+ steps: list[dict] = field(default_factory=list)
78
+ launch_command: str = ""
79
+ env_vars: dict = field(default_factory=dict)
80
+ pre_checks: list[str] = field(default_factory=list)
81
+ notes: str = ""
82
+
83
+ @classmethod
84
+ def from_dict(cls, d: dict) -> "SkillInstallPlan":
85
+ return cls(
86
+ steps=[s for s in d.get("steps", []) if isinstance(s, dict)],
87
+ launch_command=str(d.get("launch_command", "")),
88
+ env_vars={str(k): str(v) for k, v in d.get("env_vars", {}).items()},
89
+ pre_checks=[str(x) for x in d.get("pre_checks", [])],
90
+ notes=str(d.get("notes", "")),
91
+ )
92
+
93
+
94
+ @dataclass
95
+ class Skill:
96
+ """完整的 Skill 对象"""
97
+ meta: SkillMeta
98
+ plan: SkillInstallPlan
99
+ path: Path
100
+ enabled: bool = True
101
+
102
+
103
+ # ─────────────────────────────────────────────
104
+ # Skill 管理
105
+ # ─────────────────────────────────────────────
106
+
107
+ class SkillManager:
108
+ """Skills 管理器"""
109
+
110
+ def __init__(self, skills_dir: Path = None):
111
+ self.skills_dir = skills_dir or SKILLS_DIR
112
+
113
+ def _ensure_dir(self):
114
+ self.skills_dir.mkdir(parents=True, exist_ok=True)
115
+
116
+ def list_skills(self) -> list[Skill]:
117
+ """列出所有已安装的 Skills"""
118
+ if not self.skills_dir.exists():
119
+ return []
120
+
121
+ skills = []
122
+ for d in sorted(self.skills_dir.iterdir()):
123
+ if not d.is_dir():
124
+ continue
125
+ skill = self._load_skill(d)
126
+ if skill:
127
+ skills.append(skill)
128
+ return skills
129
+
130
+ def _load_skill(self, skill_dir: Path) -> Optional[Skill]:
131
+ """从目录加载一个 Skill"""
132
+ meta_file = skill_dir / "skill.json"
133
+ plan_file = skill_dir / "install.json"
134
+
135
+ if not meta_file.exists():
136
+ return None
137
+
138
+ try:
139
+ with open(meta_file, encoding="utf-8") as f:
140
+ meta_data = json.load(f)
141
+ meta = SkillMeta.from_dict(meta_data)
142
+
143
+ plan = SkillInstallPlan()
144
+ if plan_file.exists():
145
+ with open(plan_file, encoding="utf-8") as f:
146
+ plan_data = json.load(f)
147
+ plan = SkillInstallPlan.from_dict(plan_data)
148
+
149
+ enabled = not (skill_dir / ".disabled").exists()
150
+
151
+ return Skill(meta=meta, plan=plan, path=skill_dir, enabled=enabled)
152
+ except (json.JSONDecodeError, KeyError):
153
+ return None
154
+
155
+ def find_matching_skills(
156
+ self,
157
+ owner: str = "",
158
+ repo: str = "",
159
+ project_types: list[str] = None,
160
+ language: str = "",
161
+ file_list: list[str] = None,
162
+ ) -> list[Skill]:
163
+ """
164
+ 查找匹配当前项目的 Skills。
165
+
166
+ 匹配优先级:
167
+ 1. 精确匹配 owner/repo → 最高置信度
168
+ 2. 语言匹配 → 中等置信度
169
+ 3. 文件匹配 → 中等置信度
170
+ 4. 项目类型正则匹配 → 低置信度
171
+ """
172
+ full_name = f"{owner}/{repo}".lower() if owner and repo else ""
173
+ project_types = project_types or []
174
+ file_list = file_list or []
175
+ language_lower = language.lower() if language else ""
176
+
177
+ matched = []
178
+ for skill in self.list_skills():
179
+ if not skill.enabled:
180
+ continue
181
+
182
+ # 精确仓库匹配
183
+ if full_name and full_name in [r.lower() for r in skill.meta.match_repos]:
184
+ matched.append(skill)
185
+ continue
186
+
187
+ # 语言匹配
188
+ if language_lower and language_lower in [l.lower() for l in skill.meta.match_languages]:
189
+ matched.append(skill)
190
+ continue
191
+
192
+ # 文件匹配
193
+ if file_list and skill.meta.match_files:
194
+ if any(f in file_list for f in skill.meta.match_files):
195
+ matched.append(skill)
196
+ continue
197
+
198
+ # 项目类型正则匹配
199
+ if project_types and skill.meta.match_patterns:
200
+ for pat in skill.meta.match_patterns:
201
+ try:
202
+ regex = re.compile(pat, re.IGNORECASE)
203
+ if any(regex.search(pt) for pt in project_types):
204
+ matched.append(skill)
205
+ break
206
+ except re.error:
207
+ pass
208
+
209
+ return matched
210
+
211
+ def create_skill(
212
+ self,
213
+ name: str,
214
+ description: str,
215
+ steps: list[dict],
216
+ launch_command: str = "",
217
+ match_repos: list[str] = None,
218
+ match_languages: list[str] = None,
219
+ match_files: list[str] = None,
220
+ match_patterns: list[str] = None,
221
+ author: str = "",
222
+ tags: list[str] = None,
223
+ ) -> Path:
224
+ """
225
+ 创建一个新的 Skill。
226
+
227
+ 参数:
228
+ name: Skill 名称(英文、连字符,如 "pytorch-cuda")
229
+ steps: 安装步骤列表
230
+ match_repos: 精确匹配的 owner/repo 列表
231
+ """
232
+ # 验证名称格式
233
+ if not re.match(r'^[a-z0-9][a-z0-9-]*[a-z0-9]$', name) and len(name) > 2:
234
+ if not re.match(r'^[a-z0-9]+$', name):
235
+ raise ValueError(f"Skill 名称无效: {name}(仅允许小写字母、数字、连字符)")
236
+
237
+ self._ensure_dir()
238
+ skill_dir = self.skills_dir / name
239
+ if skill_dir.exists():
240
+ raise FileExistsError(f"Skill '{name}' 已存在: {skill_dir}")
241
+
242
+ skill_dir.mkdir(parents=True)
243
+
244
+ # 写入 skill.json
245
+ meta = {
246
+ "name": name,
247
+ "version": "1.0.0",
248
+ "description": description,
249
+ "author": author or os.getenv("USER", "unknown"),
250
+ "match_repos": match_repos or [],
251
+ "match_languages": match_languages or [],
252
+ "match_files": match_files or [],
253
+ "match_patterns": match_patterns or [],
254
+ "tags": tags or [],
255
+ "created_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
256
+ }
257
+ with open(skill_dir / "skill.json", "w", encoding="utf-8") as f:
258
+ json.dump(meta, f, indent=2, ensure_ascii=False)
259
+
260
+ # 写入 install.json
261
+ plan = {
262
+ "steps": steps,
263
+ "launch_command": launch_command,
264
+ }
265
+ with open(skill_dir / "install.json", "w", encoding="utf-8") as f:
266
+ json.dump(plan, f, indent=2, ensure_ascii=False)
267
+
268
+ # 写入 README.md
269
+ readme = f"# {name}\n\n{description}\n\n## 安装步骤\n\n"
270
+ for i, step in enumerate(steps, 1):
271
+ readme += f"{i}. {step.get('description', '')}\n ```\n {step.get('command', '')}\n ```\n\n"
272
+ with open(skill_dir / "README.md", "w", encoding="utf-8") as f:
273
+ f.write(readme)
274
+
275
+ return skill_dir
276
+
277
+ def remove_skill(self, name: str) -> bool:
278
+ """删除一个 Skill"""
279
+ skill_dir = self.skills_dir / name
280
+ if not skill_dir.exists():
281
+ return False
282
+ # 安全检查:确保路径在 skills 目录内
283
+ try:
284
+ skill_dir.resolve().relative_to(self.skills_dir.resolve())
285
+ except ValueError:
286
+ raise ValueError(f"路径越界: {skill_dir}")
287
+ shutil.rmtree(skill_dir)
288
+ return True
289
+
290
+ def toggle_skill(self, name: str, enabled: bool) -> bool:
291
+ """启用/禁用 Skill"""
292
+ skill_dir = self.skills_dir / name
293
+ if not skill_dir.exists():
294
+ return False
295
+ flag = skill_dir / ".disabled"
296
+ if enabled:
297
+ flag.unlink(missing_ok=True)
298
+ else:
299
+ flag.touch()
300
+ return True
301
+
302
+ def export_skill(self, name: str) -> Optional[dict]:
303
+ """导出 Skill 为 JSON(用于分享)"""
304
+ skill = self._load_skill(self.skills_dir / name)
305
+ if not skill:
306
+ return None
307
+ meta_file = skill.path / "skill.json"
308
+ plan_file = skill.path / "install.json"
309
+ result = {}
310
+ with open(meta_file, encoding="utf-8") as f:
311
+ result["skill"] = json.load(f)
312
+ if plan_file.exists():
313
+ with open(plan_file, encoding="utf-8") as f:
314
+ result["install"] = json.load(f)
315
+ return result
316
+
317
+ def import_skill(self, data: dict) -> Path:
318
+ """从 JSON 导入 Skill"""
319
+ skill_data = data.get("skill", {})
320
+ install_data = data.get("install", {})
321
+ name = skill_data.get("name", "")
322
+ if not name:
323
+ raise ValueError("Skill 数据缺少 name 字段")
324
+
325
+ steps = install_data.get("steps", [])
326
+ return self.create_skill(
327
+ name=name,
328
+ description=skill_data.get("description", ""),
329
+ steps=steps,
330
+ launch_command=install_data.get("launch_command", ""),
331
+ match_repos=skill_data.get("match_repos", []),
332
+ match_languages=skill_data.get("match_languages", []),
333
+ match_files=skill_data.get("match_files", []),
334
+ match_patterns=skill_data.get("match_patterns", []),
335
+ author=skill_data.get("author", ""),
336
+ tags=skill_data.get("tags", []),
337
+ )
338
+
339
+
340
+ # ─────────────────────────────────────────────
341
+ # 内建 Skills(开箱即用)
342
+ # ─────────────────────────────────────────────
343
+
344
+ BUILTIN_SKILLS = [
345
+ {
346
+ "name": "project-health",
347
+ "description": "安装前检查项目健康度(stars、最近更新、issues 数量)",
348
+ "tags": ["analysis", "safety"],
349
+ },
350
+ {
351
+ "name": "auto-venv",
352
+ "description": "Python 项目自动创建 virtualenv 并隔离安装",
353
+ "tags": ["python", "isolation"],
354
+ "match_languages": ["python"],
355
+ "match_files": ["requirements.txt", "setup.py", "pyproject.toml"],
356
+ },
357
+ {
358
+ "name": "docker-prefer",
359
+ "description": "优先使用 Docker 安装(当项目提供 Dockerfile 时)",
360
+ "tags": ["docker", "isolation"],
361
+ "match_files": ["Dockerfile", "docker-compose.yml", "docker-compose.yaml"],
362
+ },
363
+ {
364
+ "name": "gpu-optimizer",
365
+ "description": "根据本机 GPU 自动选择最优 AI/ML 框架版本",
366
+ "tags": ["ai", "gpu", "optimization"],
367
+ "match_patterns": ["pytorch", "tensorflow", "mlx", "cuda"],
368
+ },
369
+ {
370
+ "name": "batch-install",
371
+ "description": "从清单文件批量安装多个 GitHub 项目",
372
+ "tags": ["batch", "automation"],
373
+ },
374
+ {
375
+ "name": "env-snapshot",
376
+ "description": "安装前保存环境快照,支持回滚",
377
+ "tags": ["safety", "rollback"],
378
+ },
379
+ {
380
+ "name": "post-install-test",
381
+ "description": "安装后自动运行项目测试验证安装成功",
382
+ "tags": ["testing", "verification"],
383
+ },
384
+ {
385
+ "name": "changelog-notify",
386
+ "description": "跟踪已安装项目的更新,通知版本变化",
387
+ "tags": ["update", "tracking"],
388
+ },
389
+ # ── 第二批 Skills(安全、合规、多语言、AI 增强) ──
390
+ {
391
+ "name": "dependency-audit",
392
+ "description": "安装前扫描依赖的 CVE 漏洞、误植攻击、废弃包",
393
+ "tags": ["security", "audit"],
394
+ "match_files": ["requirements.txt", "package.json", "Cargo.toml", "go.mod"],
395
+ },
396
+ {
397
+ "name": "license-guard",
398
+ "description": "检查项目许可证兼容性,标记 GPL/AGPL 传染风险",
399
+ "tags": ["compliance", "license"],
400
+ },
401
+ {
402
+ "name": "auto-updater",
403
+ "description": "追踪已安装项目版本,检测并通知新 release",
404
+ "tags": ["update", "tracking"],
405
+ },
406
+ {
407
+ "name": "clean-uninstall",
408
+ "description": "安全卸载项目:清理 venv、容器、缓存、编译产物",
409
+ "tags": ["cleanup", "uninstall"],
410
+ },
411
+ {
412
+ "name": "monorepo-nav",
413
+ "description": "自动识别 monorepo 结构,安装指定子包",
414
+ "tags": ["monorepo", "navigation"],
415
+ "match_files": ["lerna.json", "pnpm-workspace.yaml", "Cargo.toml"],
416
+ "match_patterns": ["monorepo", "workspace"],
417
+ },
418
+ {
419
+ "name": "proxy-tunnel",
420
+ "description": "受限网络环境自动配置代理(GitHub/PyPI/npm/Docker 镜像)",
421
+ "tags": ["network", "proxy", "mirror"],
422
+ },
423
+ {
424
+ "name": "devcontainer",
425
+ "description": "检测 .devcontainer 配置,使用 VS Code Dev Container 启动",
426
+ "tags": ["docker", "devcontainer", "vscode"],
427
+ "match_files": [".devcontainer/devcontainer.json", ".devcontainer.json"],
428
+ },
429
+ {
430
+ "name": "cost-estimator",
431
+ "description": "估算 AI/ML 项目运行所需的计算资源和云服务成本",
432
+ "tags": ["ai", "cost", "estimation"],
433
+ "match_patterns": ["pytorch", "tensorflow", "mlx", "transformers", "diffusion"],
434
+ },
435
+ ]
436
+
437
+
438
+ def ensure_builtin_skills():
439
+ """确保内建 Skills 已注册(首次运行时自动调用)"""
440
+ mgr = SkillManager()
441
+ existing = {s.meta.name for s in mgr.list_skills()}
442
+
443
+ for bs in BUILTIN_SKILLS:
444
+ if bs["name"] in existing:
445
+ continue
446
+ try:
447
+ mgr.create_skill(
448
+ name=bs["name"],
449
+ description=bs["description"],
450
+ steps=[], # 内建 Skills 的逻辑在代码中实现,不需要 install.json 步骤
451
+ match_languages=bs.get("match_languages", []),
452
+ match_files=bs.get("match_files", []),
453
+ match_patterns=bs.get("match_patterns", []),
454
+ tags=bs.get("tags", []),
455
+ )
456
+ except (FileExistsError, ValueError):
457
+ pass
458
+
459
+
460
+ # ─────────────────────────────────────────────
461
+ # 格式化输出
462
+ # ─────────────────────────────────────────────
463
+
464
+ def format_skills_list(skills: list[Skill]) -> str:
465
+ """格式化 Skills 列表"""
466
+ if not skills:
467
+ return " (未安装任何 Skill)\n 使用 'gitinstall skills init' 初始化内建 Skills"
468
+
469
+ lines = []
470
+ for s in skills:
471
+ status = "✅" if s.enabled else "⏸️ "
472
+ tags = ", ".join(s.meta.tags[:3]) if s.meta.tags else ""
473
+ tag_str = f" [{tags}]" if tags else ""
474
+ lines.append(f" {status} {s.meta.name} v{s.meta.version}{tag_str}")
475
+ lines.append(f" {s.meta.description}")
476
+ return "\n".join(lines)