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
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
"""
|
|
2
|
+
installer_registry.py - 安装器服务注册表
|
|
3
|
+
==========================================
|
|
4
|
+
|
|
5
|
+
灵感来源:PersonalBrain 的 Service Registry 模式
|
|
6
|
+
|
|
7
|
+
将 pip/npm/cargo/go/docker/conda/brew/apt 等包管理器抽象为
|
|
8
|
+
可注册的 Installer Service,新包管理器只需实现接口即可插入。
|
|
9
|
+
|
|
10
|
+
架构:
|
|
11
|
+
BaseInstaller (抽象基类)
|
|
12
|
+
├── PipInstaller
|
|
13
|
+
├── NpmInstaller
|
|
14
|
+
├── CargoInstaller
|
|
15
|
+
├── GoInstaller
|
|
16
|
+
├── DockerInstaller
|
|
17
|
+
├── CondaInstaller
|
|
18
|
+
├── BrewInstaller
|
|
19
|
+
└── AptInstaller
|
|
20
|
+
|
|
21
|
+
零外部依赖,纯 Python 标准库。
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import shutil
|
|
27
|
+
import subprocess
|
|
28
|
+
from dataclasses import dataclass, field
|
|
29
|
+
from typing import Optional
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class InstallerInfo:
|
|
34
|
+
"""安装器元信息"""
|
|
35
|
+
name: str
|
|
36
|
+
display_name: str
|
|
37
|
+
ecosystems: list[str] # python, node, rust, go, system, container
|
|
38
|
+
install_command: str # 主安装命令模板
|
|
39
|
+
version_command: str # 版本检测命令
|
|
40
|
+
available: bool = False
|
|
41
|
+
version: str = ""
|
|
42
|
+
priority: int = 50 # 优先级 (0=最高, 100=最低)
|
|
43
|
+
platforms: list[str] = field(default_factory=lambda: ["darwin", "linux", "win32"])
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class BaseInstaller:
|
|
47
|
+
"""安装器基类"""
|
|
48
|
+
|
|
49
|
+
info: InstallerInfo
|
|
50
|
+
|
|
51
|
+
def __init__(self):
|
|
52
|
+
self.info = self._get_info()
|
|
53
|
+
self._detect()
|
|
54
|
+
|
|
55
|
+
def _get_info(self) -> InstallerInfo:
|
|
56
|
+
"""子类必须实现:返回安装器 meta"""
|
|
57
|
+
raise NotImplementedError
|
|
58
|
+
|
|
59
|
+
def _detect(self):
|
|
60
|
+
"""检测安装器是否可用"""
|
|
61
|
+
try:
|
|
62
|
+
cmd = self.info.version_command.split()
|
|
63
|
+
result = subprocess.run(cmd, capture_output=True, text=True, timeout=10)
|
|
64
|
+
if result.returncode == 0:
|
|
65
|
+
self.info.available = True
|
|
66
|
+
ver = result.stdout.strip().split("\n")[0]
|
|
67
|
+
# 提取版本号
|
|
68
|
+
for part in ver.split():
|
|
69
|
+
if any(c.isdigit() for c in part):
|
|
70
|
+
self.info.version = part.strip("(),v")
|
|
71
|
+
break
|
|
72
|
+
except (FileNotFoundError, subprocess.TimeoutExpired, OSError):
|
|
73
|
+
self.info.available = False
|
|
74
|
+
|
|
75
|
+
def can_handle(self, project_types: list[str], dep_files: dict) -> bool:
|
|
76
|
+
"""判断是否能处理该项目"""
|
|
77
|
+
raise NotImplementedError
|
|
78
|
+
|
|
79
|
+
def generate_install_steps(self, project_info: dict) -> list[dict]:
|
|
80
|
+
"""生成安装步骤"""
|
|
81
|
+
raise NotImplementedError
|
|
82
|
+
|
|
83
|
+
def to_dict(self) -> dict:
|
|
84
|
+
return {
|
|
85
|
+
"name": self.info.name,
|
|
86
|
+
"display_name": self.info.display_name,
|
|
87
|
+
"ecosystems": self.info.ecosystems,
|
|
88
|
+
"available": self.info.available,
|
|
89
|
+
"version": self.info.version,
|
|
90
|
+
"priority": self.info.priority,
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
# ─────────────────────────────────────────────
|
|
95
|
+
# 内置安装器实现
|
|
96
|
+
# ─────────────────────────────────────────────
|
|
97
|
+
|
|
98
|
+
class PipInstaller(BaseInstaller):
|
|
99
|
+
def _get_info(self) -> InstallerInfo:
|
|
100
|
+
return InstallerInfo(
|
|
101
|
+
name="pip", display_name="pip (Python)",
|
|
102
|
+
ecosystems=["python"],
|
|
103
|
+
install_command="pip install -r requirements.txt",
|
|
104
|
+
version_command="pip --version",
|
|
105
|
+
priority=30,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
def can_handle(self, project_types: list[str], dep_files: dict) -> bool:
|
|
109
|
+
if not self.info.available:
|
|
110
|
+
return False
|
|
111
|
+
py_files = {"requirements.txt", "setup.py", "pyproject.toml",
|
|
112
|
+
"setup.cfg", "Pipfile"}
|
|
113
|
+
return bool(set(dep_files.keys()) & py_files) or "python" in project_types
|
|
114
|
+
|
|
115
|
+
def generate_install_steps(self, project_info: dict) -> list[dict]:
|
|
116
|
+
steps = []
|
|
117
|
+
dep_files = project_info.get("dependency_files", {})
|
|
118
|
+
if "requirements.txt" in dep_files:
|
|
119
|
+
steps.append({
|
|
120
|
+
"command": "pip install -r requirements.txt",
|
|
121
|
+
"description": "安装 Python 依赖",
|
|
122
|
+
})
|
|
123
|
+
elif "pyproject.toml" in dep_files:
|
|
124
|
+
steps.append({
|
|
125
|
+
"command": "pip install -e .",
|
|
126
|
+
"description": "安装 Python 项目(editable)",
|
|
127
|
+
})
|
|
128
|
+
elif "setup.py" in dep_files:
|
|
129
|
+
steps.append({
|
|
130
|
+
"command": "pip install -e .",
|
|
131
|
+
"description": "安装 Python 项目",
|
|
132
|
+
})
|
|
133
|
+
return steps
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class NpmInstaller(BaseInstaller):
|
|
137
|
+
def _get_info(self) -> InstallerInfo:
|
|
138
|
+
return InstallerInfo(
|
|
139
|
+
name="npm", display_name="npm (Node.js)",
|
|
140
|
+
ecosystems=["node", "javascript", "typescript"],
|
|
141
|
+
install_command="npm install",
|
|
142
|
+
version_command="npm --version",
|
|
143
|
+
priority=30,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
def can_handle(self, project_types: list[str], dep_files: dict) -> bool:
|
|
147
|
+
if not self.info.available:
|
|
148
|
+
return False
|
|
149
|
+
return "package.json" in dep_files or "node" in project_types
|
|
150
|
+
|
|
151
|
+
def generate_install_steps(self, project_info: dict) -> list[dict]:
|
|
152
|
+
return [{"command": "npm install", "description": "安装 Node.js 依赖"}]
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class CargoInstaller(BaseInstaller):
|
|
156
|
+
def _get_info(self) -> InstallerInfo:
|
|
157
|
+
return InstallerInfo(
|
|
158
|
+
name="cargo", display_name="Cargo (Rust)",
|
|
159
|
+
ecosystems=["rust"],
|
|
160
|
+
install_command="cargo build --release",
|
|
161
|
+
version_command="cargo --version",
|
|
162
|
+
priority=40,
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
def can_handle(self, project_types: list[str], dep_files: dict) -> bool:
|
|
166
|
+
if not self.info.available:
|
|
167
|
+
return False
|
|
168
|
+
return "Cargo.toml" in dep_files or "rust" in project_types
|
|
169
|
+
|
|
170
|
+
def generate_install_steps(self, project_info: dict) -> list[dict]:
|
|
171
|
+
return [{"command": "cargo build --release", "description": "编译 Rust 项目"}]
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class GoInstaller(BaseInstaller):
|
|
175
|
+
def _get_info(self) -> InstallerInfo:
|
|
176
|
+
return InstallerInfo(
|
|
177
|
+
name="go", display_name="Go",
|
|
178
|
+
ecosystems=["go"],
|
|
179
|
+
install_command="go build ./...",
|
|
180
|
+
version_command="go version",
|
|
181
|
+
priority=40,
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
def can_handle(self, project_types: list[str], dep_files: dict) -> bool:
|
|
185
|
+
if not self.info.available:
|
|
186
|
+
return False
|
|
187
|
+
return "go.mod" in dep_files or "go" in project_types
|
|
188
|
+
|
|
189
|
+
def generate_install_steps(self, project_info: dict) -> list[dict]:
|
|
190
|
+
return [
|
|
191
|
+
{"command": "go mod download", "description": "下载 Go 依赖"},
|
|
192
|
+
{"command": "go build ./...", "description": "编译 Go 项目"},
|
|
193
|
+
]
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
class DockerInstaller(BaseInstaller):
|
|
197
|
+
def _get_info(self) -> InstallerInfo:
|
|
198
|
+
return InstallerInfo(
|
|
199
|
+
name="docker", display_name="Docker",
|
|
200
|
+
ecosystems=["container"],
|
|
201
|
+
install_command="docker compose up -d",
|
|
202
|
+
version_command="docker --version",
|
|
203
|
+
priority=60,
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
def can_handle(self, project_types: list[str], dep_files: dict) -> bool:
|
|
207
|
+
if not self.info.available:
|
|
208
|
+
return False
|
|
209
|
+
docker_files = {"Dockerfile", "docker-compose.yml",
|
|
210
|
+
"docker-compose.yaml", "compose.yml", "compose.yaml"}
|
|
211
|
+
return bool(set(dep_files.keys()) & docker_files) or "docker" in project_types
|
|
212
|
+
|
|
213
|
+
def generate_install_steps(self, project_info: dict) -> list[dict]:
|
|
214
|
+
dep_files = project_info.get("dependency_files", {})
|
|
215
|
+
compose_files = {"docker-compose.yml", "docker-compose.yaml",
|
|
216
|
+
"compose.yml", "compose.yaml"}
|
|
217
|
+
if set(dep_files.keys()) & compose_files:
|
|
218
|
+
return [{"command": "docker compose up -d",
|
|
219
|
+
"description": "启动 Docker 容器"}]
|
|
220
|
+
return [{"command": "docker build -t app .",
|
|
221
|
+
"description": "构建 Docker 镜像"}]
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
class CondaInstaller(BaseInstaller):
|
|
225
|
+
def _get_info(self) -> InstallerInfo:
|
|
226
|
+
return InstallerInfo(
|
|
227
|
+
name="conda", display_name="Conda",
|
|
228
|
+
ecosystems=["python", "data-science"],
|
|
229
|
+
install_command="conda env create -f environment.yml",
|
|
230
|
+
version_command="conda --version",
|
|
231
|
+
priority=35,
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
def can_handle(self, project_types: list[str], dep_files: dict) -> bool:
|
|
235
|
+
if not self.info.available:
|
|
236
|
+
return False
|
|
237
|
+
conda_files = {"environment.yml", "environment.yaml", "conda.yml"}
|
|
238
|
+
return bool(set(dep_files.keys()) & conda_files)
|
|
239
|
+
|
|
240
|
+
def generate_install_steps(self, project_info: dict) -> list[dict]:
|
|
241
|
+
return [{"command": "conda env create -f environment.yml",
|
|
242
|
+
"description": "创建 Conda 环境"}]
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
class BrewInstaller(BaseInstaller):
|
|
246
|
+
def _get_info(self) -> InstallerInfo:
|
|
247
|
+
return InstallerInfo(
|
|
248
|
+
name="brew", display_name="Homebrew",
|
|
249
|
+
ecosystems=["system"],
|
|
250
|
+
install_command="brew install",
|
|
251
|
+
version_command="brew --version",
|
|
252
|
+
priority=50,
|
|
253
|
+
platforms=["darwin"],
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
def can_handle(self, project_types: list[str], dep_files: dict) -> bool:
|
|
257
|
+
if not self.info.available:
|
|
258
|
+
return False
|
|
259
|
+
return "Brewfile" in dep_files
|
|
260
|
+
|
|
261
|
+
def generate_install_steps(self, project_info: dict) -> list[dict]:
|
|
262
|
+
return [{"command": "brew bundle", "description": "安装 Homebrew 依赖"}]
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
class AptInstaller(BaseInstaller):
|
|
266
|
+
def _get_info(self) -> InstallerInfo:
|
|
267
|
+
return InstallerInfo(
|
|
268
|
+
name="apt", display_name="APT (Debian/Ubuntu)",
|
|
269
|
+
ecosystems=["system"],
|
|
270
|
+
install_command="sudo apt-get install -y",
|
|
271
|
+
version_command="apt --version",
|
|
272
|
+
priority=50,
|
|
273
|
+
platforms=["linux"],
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
def can_handle(self, project_types: list[str], dep_files: dict) -> bool:
|
|
277
|
+
return self.info.available
|
|
278
|
+
|
|
279
|
+
def generate_install_steps(self, project_info: dict) -> list[dict]:
|
|
280
|
+
return [] # APT steps are generated dynamically based on missing deps
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
# ─────────────────────────────────────────────
|
|
284
|
+
# 服务注册表
|
|
285
|
+
# ─────────────────────────────────────────────
|
|
286
|
+
|
|
287
|
+
class InstallerRegistry:
|
|
288
|
+
"""安装器注册表(单例模式)"""
|
|
289
|
+
|
|
290
|
+
_instance: Optional["InstallerRegistry"] = None
|
|
291
|
+
|
|
292
|
+
def __new__(cls):
|
|
293
|
+
if cls._instance is None:
|
|
294
|
+
cls._instance = super().__new__(cls)
|
|
295
|
+
cls._instance._installers = {}
|
|
296
|
+
cls._instance._initialized = False
|
|
297
|
+
return cls._instance
|
|
298
|
+
|
|
299
|
+
def __init__(self):
|
|
300
|
+
if not self._initialized:
|
|
301
|
+
self._register_builtins()
|
|
302
|
+
self._initialized = True
|
|
303
|
+
|
|
304
|
+
def _register_builtins(self):
|
|
305
|
+
"""注册所有内置安装器"""
|
|
306
|
+
builtin_classes = [
|
|
307
|
+
PipInstaller, NpmInstaller, CargoInstaller, GoInstaller,
|
|
308
|
+
DockerInstaller, CondaInstaller, BrewInstaller, AptInstaller,
|
|
309
|
+
]
|
|
310
|
+
for cls in builtin_classes:
|
|
311
|
+
try:
|
|
312
|
+
installer = cls()
|
|
313
|
+
self._installers[installer.info.name] = installer
|
|
314
|
+
except Exception:
|
|
315
|
+
pass
|
|
316
|
+
|
|
317
|
+
def register(self, installer: BaseInstaller):
|
|
318
|
+
"""注册自定义安装器"""
|
|
319
|
+
self._installers[installer.info.name] = installer
|
|
320
|
+
|
|
321
|
+
def get(self, name: str) -> Optional[BaseInstaller]:
|
|
322
|
+
"""获取指定安装器"""
|
|
323
|
+
return self._installers.get(name)
|
|
324
|
+
|
|
325
|
+
def list_available(self) -> list[BaseInstaller]:
|
|
326
|
+
"""列出所有可用的安装器"""
|
|
327
|
+
return sorted(
|
|
328
|
+
[i for i in self._installers.values() if i.info.available],
|
|
329
|
+
key=lambda i: i.info.priority,
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
def list_all(self) -> list[BaseInstaller]:
|
|
333
|
+
"""列出所有安装器(含不可用的)"""
|
|
334
|
+
return sorted(self._installers.values(), key=lambda i: i.info.priority)
|
|
335
|
+
|
|
336
|
+
def find_matching(self, project_types: list[str],
|
|
337
|
+
dep_files: dict) -> list[BaseInstaller]:
|
|
338
|
+
"""查找能处理当前项目的安装器"""
|
|
339
|
+
matching = []
|
|
340
|
+
for installer in self.list_available():
|
|
341
|
+
if installer.can_handle(project_types, dep_files):
|
|
342
|
+
matching.append(installer)
|
|
343
|
+
return matching
|
|
344
|
+
|
|
345
|
+
def format_registry(self) -> str:
|
|
346
|
+
"""格式化注册表状态"""
|
|
347
|
+
lines = ["📦 安装器注册表:", ""]
|
|
348
|
+
for installer in self.list_all():
|
|
349
|
+
status = "✅" if installer.info.available else "❌"
|
|
350
|
+
ver = f" v{installer.info.version}" if installer.info.version else ""
|
|
351
|
+
eco = ", ".join(installer.info.ecosystems)
|
|
352
|
+
lines.append(f" {status} {installer.info.display_name}{ver}")
|
|
353
|
+
lines.append(f" 生态:{eco} 优先级:{installer.info.priority}")
|
|
354
|
+
return "\n".join(lines)
|
|
355
|
+
|
|
356
|
+
def to_dict(self) -> dict:
|
|
357
|
+
"""序列化为字典"""
|
|
358
|
+
return {
|
|
359
|
+
"installers": [i.to_dict() for i in self.list_all()],
|
|
360
|
+
"available_count": len(self.list_available()),
|
|
361
|
+
"total_count": len(self._installers),
|
|
362
|
+
}
|