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,251 @@
1
+ """
2
+ event_bus.py - 安装事件总线 + Webhook 通知
3
+ ============================================
4
+
5
+ 灵感来源:ICE-OEM 的告警派发队列
6
+
7
+ 安装过程中发布事件,支持:
8
+ 1. 事件监听器:内部模块订阅事件
9
+ 2. Webhook 推送:安装完成/失败时推送到外部(Slack/Discord/钉钉/企微)
10
+ 3. 事件历史:记录所有事件供回溯
11
+
12
+ 事件类型:
13
+ install.started, install.step_started, install.step_completed,
14
+ install.step_failed, install.completed, install.failed,
15
+ audit.warning, license.issue, watchdog.alert
16
+
17
+ 配置:
18
+ GITINSTALL_WEBHOOK_URL=https://hooks.slack.com/services/xxx
19
+ GITINSTALL_WEBHOOK_SECRET=xxx
20
+
21
+ 零外部依赖,纯 Python 标准库。
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ import hashlib
27
+ import hmac
28
+ import json
29
+ import os
30
+ import threading
31
+ import time
32
+ import urllib.error
33
+ import urllib.request
34
+ from dataclasses import dataclass, field
35
+ from typing import Callable, Optional
36
+
37
+
38
+ # ── 事件类型常量 ──
39
+ EVT_INSTALL_STARTED = "install.started"
40
+ EVT_STEP_STARTED = "install.step_started"
41
+ EVT_STEP_COMPLETED = "install.step_completed"
42
+ EVT_STEP_FAILED = "install.step_failed"
43
+ EVT_INSTALL_COMPLETED = "install.completed"
44
+ EVT_INSTALL_FAILED = "install.failed"
45
+ EVT_AUDIT_WARNING = "audit.warning"
46
+ EVT_LICENSE_ISSUE = "license.issue"
47
+ EVT_WATCHDOG_ALERT = "watchdog.alert"
48
+ EVT_CHECKPOINT_SAVED = "checkpoint.saved"
49
+ EVT_RESUME_STARTED = "resume.started"
50
+
51
+
52
+ @dataclass
53
+ class Event:
54
+ """安装事件"""
55
+ event_type: str
56
+ timestamp: str = ""
57
+ project: str = ""
58
+ data: dict = field(default_factory=dict)
59
+ source: str = "gitinstall"
60
+
61
+ def __post_init__(self):
62
+ if not self.timestamp:
63
+ self.timestamp = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
64
+
65
+ def to_dict(self) -> dict:
66
+ return {
67
+ "event_type": self.event_type,
68
+ "timestamp": self.timestamp,
69
+ "project": self.project,
70
+ "data": self.data,
71
+ "source": self.source,
72
+ }
73
+
74
+
75
+ # ── 事件监听器类型 ──
76
+ EventListener = Callable[[Event], None]
77
+
78
+
79
+ class EventBus:
80
+ """事件总线"""
81
+
82
+ def __init__(self):
83
+ self._listeners: dict[str, list[EventListener]] = {}
84
+ self._history: list[Event] = []
85
+ self._lock = threading.Lock()
86
+ self._max_history = 1000
87
+
88
+ def subscribe(self, event_type: str, listener: EventListener):
89
+ """订阅事件"""
90
+ with self._lock:
91
+ self._listeners.setdefault(event_type, []).append(listener)
92
+
93
+ def subscribe_all(self, listener: EventListener):
94
+ """订阅所有事件"""
95
+ self.subscribe("*", listener)
96
+
97
+ def unsubscribe(self, event_type: str, listener: EventListener):
98
+ """取消订阅"""
99
+ with self._lock:
100
+ listeners = self._listeners.get(event_type, [])
101
+ if listener in listeners:
102
+ listeners.remove(listener)
103
+
104
+ def publish(self, event: Event):
105
+ """发布事件"""
106
+ with self._lock:
107
+ self._history.append(event)
108
+ if len(self._history) > self._max_history:
109
+ self._history = self._history[-self._max_history:]
110
+
111
+ # 通知特定类型的监听器
112
+ specific = list(self._listeners.get(event.event_type, []))
113
+ # 通知通配符监听器
114
+ wildcard = list(self._listeners.get("*", []))
115
+
116
+ # 在锁外执行回调
117
+ for listener in specific + wildcard:
118
+ try:
119
+ listener(event)
120
+ except Exception:
121
+ pass
122
+
123
+ def emit(self, event_type: str, project: str = "", **data):
124
+ """便捷发布事件"""
125
+ self.publish(Event(event_type=event_type, project=project, data=data))
126
+
127
+ def get_history(self, event_type: str = None,
128
+ limit: int = 50) -> list[Event]:
129
+ """获取事件历史"""
130
+ with self._lock:
131
+ events = list(self._history)
132
+ if event_type:
133
+ events = [e for e in events if e.event_type == event_type]
134
+ return events[-limit:]
135
+
136
+ def clear_history(self):
137
+ """清空历史"""
138
+ with self._lock:
139
+ self._history.clear()
140
+
141
+
142
+ # ─────────────────────────────────────────────
143
+ # Webhook 推送器
144
+ # ─────────────────────────────────────────────
145
+
146
+ class WebhookNotifier:
147
+ """Webhook 通知器"""
148
+
149
+ def __init__(self, url: str = None, secret: str = None):
150
+ self.url = url or os.environ.get("GITINSTALL_WEBHOOK_URL", "")
151
+ self.secret = secret or os.environ.get("GITINSTALL_WEBHOOK_SECRET", "")
152
+ self._send_lock = threading.Lock()
153
+
154
+ @property
155
+ def enabled(self) -> bool:
156
+ return bool(self.url)
157
+
158
+ def notify(self, event: Event):
159
+ """发送 Webhook 通知(异步)"""
160
+ if not self.enabled:
161
+ return
162
+ thread = threading.Thread(target=self._send, args=(event,), daemon=True)
163
+ thread.start()
164
+
165
+ def _send(self, event: Event):
166
+ """实际发送 Webhook"""
167
+ with self._send_lock:
168
+ try:
169
+ payload = json.dumps(event.to_dict(), ensure_ascii=False).encode()
170
+ headers = {
171
+ "Content-Type": "application/json",
172
+ "User-Agent": "gitinstall-webhook/1.0",
173
+ }
174
+
175
+ # HMAC 签名(防篡改)
176
+ if self.secret:
177
+ sig = hmac.new(
178
+ self.secret.encode(), payload, hashlib.sha256
179
+ ).hexdigest()
180
+ headers["X-Gitinstall-Signature"] = f"sha256={sig}"
181
+
182
+ req = urllib.request.Request(
183
+ self.url, data=payload, headers=headers, method="POST"
184
+ )
185
+ with urllib.request.urlopen(req, timeout=10) as resp:
186
+ resp.read()
187
+ except (urllib.error.URLError, OSError):
188
+ pass # Webhook 失败不影响安装流程
189
+
190
+ def format_slack_message(self, event: Event) -> dict:
191
+ """格式化为 Slack Block 消息"""
192
+ icons = {
193
+ EVT_INSTALL_COMPLETED: "✅",
194
+ EVT_INSTALL_FAILED: "❌",
195
+ EVT_AUDIT_WARNING: "🚨",
196
+ EVT_WATCHDOG_ALERT: "🐕",
197
+ }
198
+ icon = icons.get(event.event_type, "📦")
199
+
200
+ return {
201
+ "text": f"{icon} [{event.event_type}] {event.project}",
202
+ "blocks": [
203
+ {
204
+ "type": "section",
205
+ "text": {
206
+ "type": "mrkdwn",
207
+ "text": (
208
+ f"*{icon} {event.event_type}*\n"
209
+ f"项目:`{event.project}`\n"
210
+ f"时间:{event.timestamp}"
211
+ ),
212
+ },
213
+ }
214
+ ],
215
+ }
216
+
217
+
218
+ # ─────────────────────────────────────────────
219
+ # 全局实例
220
+ # ─────────────────────────────────────────────
221
+
222
+ _bus: Optional[EventBus] = None
223
+ _webhook: Optional[WebhookNotifier] = None
224
+
225
+
226
+ def get_event_bus() -> EventBus:
227
+ """获取全局事件总线"""
228
+ global _bus
229
+ if _bus is None:
230
+ _bus = EventBus()
231
+ # 自动注册 Webhook 监听器
232
+ wh = get_webhook_notifier()
233
+ if wh.enabled:
234
+ # 只推送关键事件
235
+ for evt in (EVT_INSTALL_COMPLETED, EVT_INSTALL_FAILED,
236
+ EVT_AUDIT_WARNING, EVT_WATCHDOG_ALERT):
237
+ _bus.subscribe(evt, wh.notify)
238
+ return _bus
239
+
240
+
241
+ def get_webhook_notifier() -> WebhookNotifier:
242
+ """获取全局 Webhook 通知器"""
243
+ global _webhook
244
+ if _webhook is None:
245
+ _webhook = WebhookNotifier()
246
+ return _webhook
247
+
248
+
249
+ def emit(event_type: str, project: str = "", **data):
250
+ """便捷发布事件(全局)"""
251
+ get_event_bus().emit(event_type, project=project, **data)