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/detector.py ADDED
@@ -0,0 +1,542 @@
1
+ """
2
+ detector.py - 全平台环境检测器
3
+ =====================================
4
+
5
+ 覆盖:
6
+ - macOS (Intel / Apple Silicon M1-M4)
7
+ - Linux (Ubuntu/Debian/Arch/Fedora/openSUSE)
8
+ - Windows 10/11
9
+ - Windows WSL2
10
+
11
+ 检测内容:
12
+ - OS + 发行版 + 版本 + 架构
13
+ - CPU + 内存 + 磁盘空间
14
+ - GPU (NVIDIA CUDA / AMD ROCm / Apple MPS)
15
+ - 已安装的包管理器
16
+ - 已安装的运行时 (Python/Node/Go/Rust/Docker/Git)
17
+ - 已配置的 LLM 环境变量
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import json
23
+ import os
24
+ import platform
25
+ import re
26
+ import shutil
27
+ import subprocess
28
+ import sys
29
+ from pathlib import Path
30
+ from typing import Optional
31
+
32
+
33
+ # ─────────────────────────────────────────────
34
+ # 工具函数
35
+ # ─────────────────────────────────────────────
36
+
37
+ def _run(cmd: list[str], timeout: int = 5) -> Optional[str]:
38
+ """运行命令,返回 stdout 或 None(失败时不抛异常)"""
39
+ try:
40
+ result = subprocess.run(
41
+ cmd,
42
+ capture_output=True,
43
+ text=True,
44
+ timeout=timeout,
45
+ # Windows 安全:不通过 shell 执行
46
+ )
47
+ return result.stdout.strip() if result.returncode == 0 else None
48
+ except (subprocess.TimeoutExpired, FileNotFoundError, PermissionError):
49
+ return None
50
+
51
+
52
+ def _which(binary: str) -> Optional[str]:
53
+ """查找可执行文件路径"""
54
+ return shutil.which(binary)
55
+
56
+
57
+ def _version(binary: str, version_flag: str = "--version") -> Optional[str]:
58
+ """获取工具版本号"""
59
+ if not _which(binary):
60
+ return None
61
+ output = _run([binary, version_flag])
62
+ if not output:
63
+ return None
64
+ # 提取第一行的版本号
65
+ first_line = output.split("\n")[0]
66
+ match = re.search(r'[\d]+\.[\d]+(?:\.[\d]+)?', first_line)
67
+ return match.group(0) if match else first_line[:50]
68
+
69
+
70
+ # ─────────────────────────────────────────────
71
+ # 环境检测主类
72
+ # ─────────────────────────────────────────────
73
+
74
+ class EnvironmentDetector:
75
+
76
+ def detect(self) -> dict:
77
+ """执行完整环境检测,返回结构化结果"""
78
+ return {
79
+ "os": self._detect_os(),
80
+ "hardware": self._detect_hardware(),
81
+ "gpu": self._detect_gpu(),
82
+ "package_managers": self._detect_package_managers(),
83
+ "runtimes": self._detect_runtimes(),
84
+ "disk": self._detect_disk(),
85
+ "llm_configured": self._detect_llm_env(),
86
+ "network": self._detect_network(),
87
+ }
88
+
89
+ # ── OS ──────────────────────────────────
90
+
91
+ def _detect_os(self) -> dict:
92
+ system = platform.system()
93
+
94
+ if system == "Darwin":
95
+ return self._detect_macos()
96
+ elif system == "Linux":
97
+ return self._detect_linux()
98
+ elif system == "Windows":
99
+ return self._detect_windows()
100
+ else:
101
+ return {"type": "unknown", "system": system}
102
+
103
+ def _detect_macos(self) -> dict:
104
+ arch = platform.machine() # "arm64" 或 "x86_64"
105
+ mac_ver = platform.mac_ver()[0]
106
+
107
+ chip = "unknown"
108
+ if arch == "arm64":
109
+ # 检测 Apple Silicon 代别
110
+ chip_info = _run(["sysctl", "-n", "machdep.cpu.brand_string"]) or ""
111
+ if "M4" in chip_info:
112
+ chip = "Apple M4"
113
+ elif "M3" in chip_info:
114
+ chip = "Apple M3"
115
+ elif "M2" in chip_info:
116
+ chip = "Apple M2"
117
+ elif "M1" in chip_info:
118
+ chip = "Apple M1"
119
+ else:
120
+ chip = "Apple Silicon"
121
+ else:
122
+ chip = _run(["sysctl", "-n", "machdep.cpu.brand_string"]) or "Intel"
123
+
124
+ return {
125
+ "type": "macos",
126
+ "version": mac_ver,
127
+ "arch": arch,
128
+ "chip": chip,
129
+ "is_apple_silicon": arch == "arm64",
130
+ "shell": os.environ.get("SHELL", "/bin/zsh"),
131
+ "home": str(Path.home()),
132
+ }
133
+
134
+ def _detect_linux(self) -> dict:
135
+ arch = platform.machine()
136
+ distro = "unknown"
137
+ distro_version = ""
138
+
139
+ # 读取 /etc/os-release
140
+ os_release = {}
141
+ for path in ["/etc/os-release", "/usr/lib/os-release"]:
142
+ try:
143
+ with open(path) as f:
144
+ for line in f:
145
+ line = line.strip()
146
+ if "=" in line:
147
+ k, v = line.split("=", 1)
148
+ os_release[k] = v.strip('"')
149
+ break
150
+ except FileNotFoundError:
151
+ continue
152
+
153
+ distro = os_release.get("ID", "linux")
154
+ distro_version = os_release.get("VERSION_ID", "")
155
+ distro_name = os_release.get("PRETTY_NAME", distro)
156
+
157
+ # 检测 WSL
158
+ is_wsl = False
159
+ try:
160
+ with open("/proc/version") as f:
161
+ if "microsoft" in f.read().lower():
162
+ is_wsl = True
163
+ except FileNotFoundError:
164
+ pass
165
+
166
+ return {
167
+ "type": "linux",
168
+ "distro": distro, # ubuntu / arch / fedora / ...
169
+ "distro_name": distro_name,
170
+ "version": distro_version,
171
+ "arch": arch,
172
+ "is_wsl": is_wsl,
173
+ "shell": os.environ.get("SHELL", "/bin/bash"),
174
+ "home": str(Path.home()),
175
+ }
176
+
177
+ def _detect_windows(self) -> dict:
178
+ arch = platform.machine() # "AMD64" 或 "ARM64"
179
+ win_ver = platform.version()
180
+ release = platform.release()
181
+
182
+ return {
183
+ "type": "windows",
184
+ "version": win_ver,
185
+ "release": release, # "10" 或 "11"
186
+ "arch": arch,
187
+ "is_wsl": False,
188
+ "shell": os.environ.get("COMSPEC", "cmd.exe"),
189
+ "home": str(Path.home()),
190
+ "powershell": bool(_which("powershell") or _which("pwsh")),
191
+ }
192
+
193
+ # ── 硬件 ──────────────────────────────────
194
+
195
+ def _detect_hardware(self) -> dict:
196
+ result = {
197
+ "cpu_count": os.cpu_count(),
198
+ "ram_gb": self._detect_ram_gb(),
199
+ }
200
+ return result
201
+
202
+ def _detect_ram_gb(self) -> Optional[float]:
203
+ system = platform.system()
204
+ try:
205
+ if system == "Darwin":
206
+ output = _run(["sysctl", "-n", "hw.memsize"])
207
+ if output:
208
+ return round(int(output) / (1024 ** 3), 1)
209
+ elif system == "Linux":
210
+ with open("/proc/meminfo") as f:
211
+ for line in f:
212
+ if line.startswith("MemTotal:"):
213
+ kb = int(line.split()[1])
214
+ return round(kb / (1024 ** 2), 1)
215
+ elif system == "Windows":
216
+ output = _run(["wmic", "ComputerSystem", "get", "TotalPhysicalMemory"])
217
+ if output:
218
+ for line in output.split("\n"):
219
+ line = line.strip()
220
+ if line.isdigit():
221
+ return round(int(line) / (1024 ** 3), 1)
222
+ except Exception:
223
+ pass
224
+ return None
225
+
226
+ # ── GPU ──────────────────────────────────
227
+
228
+ def _detect_gpu(self) -> dict:
229
+ system = platform.system()
230
+ arch = platform.machine()
231
+
232
+ # Apple Silicon → MPS
233
+ if system == "Darwin" and arch == "arm64":
234
+ return {
235
+ "type": "apple_mps",
236
+ "name": "Apple Neural Engine + GPU",
237
+ "pytorch_flag": "mps",
238
+ "cuda_available": False,
239
+ }
240
+
241
+ # NVIDIA CUDA
242
+ nvidia = self._detect_nvidia()
243
+ if nvidia:
244
+ return nvidia
245
+
246
+ # AMD ROCm(Linux only)
247
+ if system == "Linux":
248
+ rocm = self._detect_rocm()
249
+ if rocm:
250
+ return rocm
251
+
252
+ # 集成显卡 / 无独显
253
+ return {
254
+ "type": "cpu_only",
255
+ "name": "No dedicated GPU",
256
+ "pytorch_flag": "cpu",
257
+ "cuda_available": False,
258
+ }
259
+
260
+ def _detect_nvidia(self) -> Optional[dict]:
261
+ """检测 NVIDIA GPU 和 CUDA 版本"""
262
+ nvidia_smi = _which("nvidia-smi")
263
+ if not nvidia_smi:
264
+ return None
265
+
266
+ gpu_name = _run(["nvidia-smi", "--query-gpu=name", "--format=csv,noheader,nounits"])
267
+
268
+ # 解析 CUDA 版本
269
+ cuda_ver = None
270
+ nvcc_output = _run(["nvcc", "--version"])
271
+ if nvcc_output:
272
+ match = re.search(r'release (\d+\.\d+)', nvcc_output)
273
+ if match:
274
+ cuda_ver = match.group(1)
275
+
276
+ if not cuda_ver:
277
+ # 从 nvidia-smi 尝试解析
278
+ smi_output = _run(["nvidia-smi"])
279
+ if smi_output:
280
+ match = re.search(r'CUDA Version:\s*([\d.]+)', smi_output)
281
+ if match:
282
+ cuda_ver = match.group(1)
283
+
284
+ return {
285
+ "type": "nvidia_cuda",
286
+ "name": (gpu_name or "NVIDIA GPU").split("\n")[0].strip(),
287
+ "cuda_version": cuda_ver,
288
+ "pytorch_flag": f"cu{cuda_ver.replace('.', '')[:3]}" if cuda_ver else "cu121",
289
+ "cuda_available": True,
290
+ }
291
+
292
+ def _detect_rocm(self) -> Optional[dict]:
293
+ """检测 AMD ROCm"""
294
+ if not _which("rocm-smi") and not Path("/opt/rocm").exists():
295
+ return None
296
+
297
+ rocm_version = None
298
+ rocm_info = _run(["rocm-smi", "--showfwinfo"])
299
+ if rocm_info:
300
+ match = re.search(r'ROCm\s+([\d.]+)', rocm_info)
301
+ if match:
302
+ rocm_version = match.group(1)
303
+
304
+ return {
305
+ "type": "amd_rocm",
306
+ "name": "AMD GPU (ROCm)",
307
+ "rocm_version": rocm_version,
308
+ "pytorch_flag": "rocm",
309
+ "cuda_available": False,
310
+ }
311
+
312
+ # ── 包管理器 ──────────────────────────────
313
+
314
+ def _detect_package_managers(self) -> dict:
315
+ """检测所有平台的包管理器"""
316
+ managers = {}
317
+
318
+ checks = [
319
+ # 通用
320
+ ("pip", ["pip", "--version"]),
321
+ ("pip3", ["pip3", "--version"]),
322
+ ("conda", ["conda", "--version"]),
323
+ ("uv", ["uv", "--version"]), # 新一代 Python 包管理
324
+ # macOS
325
+ ("brew", ["brew", "--version"]),
326
+ # Linux
327
+ ("apt", ["apt", "--version"]),
328
+ ("apt-get", ["apt-get", "--version"]),
329
+ ("dnf", ["dnf", "--version"]),
330
+ ("pacman", ["pacman", "--version"]),
331
+ ("yay", ["yay", "--version"]), # Arch AUR
332
+ ("zypper", ["zypper", "--version"]),
333
+ ("snap", ["snap", "--version"]),
334
+ # Windows
335
+ ("winget", ["winget", "--version"]),
336
+ ("choco", ["choco", "--version"]),
337
+ ("scoop", ["scoop", "--version"]),
338
+ # 语言级
339
+ ("npm", ["npm", "--version"]),
340
+ ("pnpm", ["pnpm", "--version"]),
341
+ ("yarn", ["yarn", "--version"]),
342
+ ("bun", ["bun", "--version"]),
343
+ ("cargo", ["cargo", "--version"]),
344
+ ("go", ["go", "version"]),
345
+ ]
346
+
347
+ for name, cmd in checks:
348
+ if _which(cmd[0]):
349
+ ver = _version(cmd[0], cmd[1] if len(cmd) > 1 else "--version")
350
+ managers[name] = {"available": True, "version": ver}
351
+
352
+ return managers
353
+
354
+ # ── 运行时 ──────────────────────────────────
355
+
356
+ def _detect_runtimes(self) -> dict:
357
+ """检测主要开发运行时"""
358
+ runtimes = {}
359
+
360
+ # Python(最重要)—— 独立检测系统可用的 python3,而不是当前解释器
361
+ py_version = sys.version.split()[0]
362
+ py_executable = sys.executable
363
+ py_path = _which("python3") or _which("python")
364
+ # 如果系统 python3 与当前解释器不同,优先报告系统的
365
+ if py_path and os.path.realpath(py_path) != os.path.realpath(sys.executable):
366
+ sys_py_ver = _version("python3") or _version("python")
367
+ if sys_py_ver:
368
+ py_version = sys_py_ver
369
+ py_executable = py_path
370
+ runtimes["python"] = {
371
+ "available": True,
372
+ "version": py_version,
373
+ "executable": py_executable,
374
+ "path": py_path,
375
+ }
376
+
377
+ # Node.js
378
+ node_ver = _version("node")
379
+ if node_ver:
380
+ runtimes["node"] = {"available": True, "version": node_ver}
381
+
382
+ # Git(安装几乎所有项目都需要)
383
+ git_ver = _version("git")
384
+ runtimes["git"] = {
385
+ "available": bool(git_ver),
386
+ "version": git_ver,
387
+ }
388
+
389
+ # Docker
390
+ docker_ver = _version("docker")
391
+ if docker_ver:
392
+ # 检测 Docker 是否真正运行
393
+ docker_running = _run(["docker", "ps"]) is not None
394
+ runtimes["docker"] = {
395
+ "available": True,
396
+ "version": docker_ver,
397
+ "daemon_running": docker_running,
398
+ }
399
+
400
+ # Rust
401
+ rust_ver = _version("rustc")
402
+ if rust_ver:
403
+ runtimes["rust"] = {"available": True, "version": rust_ver}
404
+
405
+ # Go
406
+ go_ver = _version("go", "version")
407
+ if go_ver:
408
+ runtimes["go"] = {"available": True, "version": go_ver}
409
+
410
+ # Java(部分工具需要)
411
+ java_ver = _version("java", "-version")
412
+ if java_ver:
413
+ runtimes["java"] = {"available": True, "version": java_ver}
414
+
415
+ # ffmpeg(视频处理类项目)
416
+ ffmpeg_ver = _version("ffmpeg", "-version")
417
+ if ffmpeg_ver:
418
+ runtimes["ffmpeg"] = {"available": True, "version": ffmpeg_ver}
419
+
420
+ return runtimes
421
+
422
+ # ── 磁盘空间 ──────────────────────────────
423
+
424
+ def _detect_disk(self) -> dict:
425
+ """检测 home 目录所在分区的可用空间"""
426
+ try:
427
+ stat = os.statvfs(str(Path.home()))
428
+ free_gb = round((stat.f_frsize * stat.f_bavail) / (1024 ** 3), 1)
429
+ total_gb = round((stat.f_frsize * stat.f_blocks) / (1024 ** 3), 1)
430
+ return {"free_gb": free_gb, "total_gb": total_gb, "path": str(Path.home())}
431
+ except AttributeError:
432
+ # Windows 不支持 statvfs
433
+ import shutil as sh
434
+ usage = sh.disk_usage(str(Path.home()))
435
+ return {
436
+ "free_gb": round(usage.free / (1024 ** 3), 1),
437
+ "total_gb": round(usage.total / (1024 ** 3), 1),
438
+ "path": str(Path.home()),
439
+ }
440
+
441
+ # ── LLM 环境变量检测 ──────────────────────
442
+
443
+ def _detect_llm_env(self) -> dict:
444
+ """检测已配置的 LLM API Keys(只检测是否存在,不暴露值)"""
445
+ keys = {
446
+ "anthropic": "ANTHROPIC_API_KEY",
447
+ "openai": "OPENAI_API_KEY",
448
+ "openrouter": "OPENROUTER_API_KEY",
449
+ "gemini": "GEMINI_API_KEY",
450
+ "groq": "GROQ_API_KEY",
451
+ "deepseek": "DEEPSEEK_API_KEY",
452
+ }
453
+ return {name: bool(os.getenv(env_var, "").strip()) for name, env_var in keys.items()}
454
+
455
+ # ── 网络检测 ──────────────────────────────
456
+
457
+ def _detect_network(self) -> dict:
458
+ """检测网络可达性(主要检测 GitHub)"""
459
+ import socket
460
+ result = {}
461
+ targets = [
462
+ ("github", "github.com", 443),
463
+ ("pypi", "pypi.org", 443),
464
+ ]
465
+ for name, host, port in targets:
466
+ try:
467
+ socket.create_connection((host, port), timeout=10).close()
468
+ result[name] = True
469
+ except (socket.timeout, OSError):
470
+ result[name] = False
471
+ return result
472
+
473
+
474
+ # ─────────────────────────────────────────────
475
+ # 格式化输出(供 CLI 使用)
476
+ # ─────────────────────────────────────────────
477
+
478
+ def format_env_summary(env: dict) -> str:
479
+ """格式化环境信息为人类可读的摘要"""
480
+ lines = []
481
+ os_info = env.get("os", {})
482
+ gpu_info = env.get("gpu", {})
483
+ hw = env.get("hardware", {})
484
+ disk = env.get("disk", {})
485
+
486
+ # OS 行
487
+ if os_info.get("type") == "macos":
488
+ lines.append(f"💻 {os_info.get('chip', 'Mac')} / macOS {os_info.get('version', '')} ({os_info.get('arch', '')})")
489
+ elif os_info.get("type") == "linux":
490
+ wsl = " [WSL2]" if os_info.get("is_wsl") else ""
491
+ lines.append(f"🐧 {os_info.get('distro_name', 'Linux')}{wsl} ({os_info.get('arch', '')})")
492
+ elif os_info.get("type") == "windows":
493
+ lines.append(f"🪟 Windows {os_info.get('release', '')} ({os_info.get('arch', '')})")
494
+
495
+ # 硬件
496
+ ram = hw.get("ram_gb")
497
+ cpu = hw.get("cpu_count")
498
+ if ram and cpu:
499
+ lines.append(f"⚙️ {cpu} 核 / {ram} GB RAM / 磁盘剩余 {disk.get('free_gb', '?')} GB")
500
+
501
+ # GPU
502
+ gpu_type = gpu_info.get("type", "cpu_only")
503
+ if gpu_type == "apple_mps":
504
+ lines.append("🎮 GPU: Apple MPS ✅")
505
+ elif gpu_type == "nvidia_cuda":
506
+ cuda = gpu_info.get("cuda_version", "未知")
507
+ lines.append(f"🎮 GPU: {gpu_info.get('name', 'NVIDIA')} / CUDA {cuda} ✅")
508
+ elif gpu_type == "amd_rocm":
509
+ lines.append(f"🎮 GPU: {gpu_info.get('name', 'AMD')} / ROCm ✅")
510
+ else:
511
+ lines.append("🎮 GPU: 无独立显卡(将使用 CPU 模式)")
512
+
513
+ # 运行时
514
+ runtimes = env.get("runtimes", {})
515
+ rt_parts = []
516
+ if "python" in runtimes:
517
+ rt_parts.append(f"Python {runtimes['python']['version']}")
518
+ if "node" in runtimes:
519
+ rt_parts.append(f"Node {runtimes['node']['version']}")
520
+ if "git" in runtimes and runtimes["git"]["available"]:
521
+ rt_parts.append("git ✓")
522
+ if "docker" in runtimes:
523
+ running = "✅" if runtimes["docker"].get("daemon_running") else "⚠️(未运行)"
524
+ rt_parts.append(f"Docker {running}")
525
+ if rt_parts:
526
+ lines.append("🔧 运行时:" + " | ".join(rt_parts))
527
+
528
+ # 包管理器
529
+ pms = env.get("package_managers", {})
530
+ available_pms = [name for name, info in pms.items() if info.get("available")]
531
+ if available_pms:
532
+ lines.append("📦 包管理器:" + " | ".join(available_pms[:6]))
533
+
534
+ return "\n".join(lines)
535
+
536
+
537
+ if __name__ == "__main__":
538
+ detector = EnvironmentDetector()
539
+ env = detector.detect()
540
+ print(json.dumps(env, ensure_ascii=False, indent=2))
541
+ print("\n" + "─" * 50)
542
+ print(format_env_summary(env))