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/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)
|