hyperagents 1.2.3rc5.post3__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 (361) hide show
  1. agents/__init__.py +31 -0
  2. agents/background_monitor.py +453 -0
  3. agents/learning.py +460 -0
  4. agents/planner.py +421 -0
  5. agents/transcriber.py +341 -0
  6. civilization/__init__.py +51 -0
  7. civilization/api/__init__.py +7 -0
  8. civilization/api/civilization_api.py +361 -0
  9. civilization/graph/__init__.py +19 -0
  10. civilization/graph/knowledge_linker.py +348 -0
  11. civilization/graph/org_graph.py +348 -0
  12. civilization/graph/process_graph.py +386 -0
  13. civilization/ingestion/__init__.py +20 -0
  14. civilization/ingestion/chunker.py +273 -0
  15. civilization/ingestion/document_ingestor.py +358 -0
  16. civilization/ingestion/embedder.py +262 -0
  17. civilization/ingestion/extractor.py +224 -0
  18. civilization/interview/__init__.py +19 -0
  19. civilization/interview/gap_detector.py +380 -0
  20. civilization/interview/interviewer.py +323 -0
  21. civilization/interview/questioner.py +363 -0
  22. civilization/retrieval/__init__.py +17 -0
  23. civilization/retrieval/civilization_rag.py +316 -0
  24. civilization/retrieval/context_injector.py +342 -0
  25. civilization/retrieval/relevance_ranker.py +287 -0
  26. civilization/schema.py +299 -0
  27. civilization/store.py +177 -0
  28. civilization/sync/__init__.py +16 -0
  29. civilization/sync/confluence_sync.py +317 -0
  30. civilization/sync/gdrive_sync.py +262 -0
  31. civilization/sync/notion_sync.py +237 -0
  32. civilization/versioning/__init__.py +19 -0
  33. civilization/versioning/diff_engine.py +351 -0
  34. civilization/versioning/staleness_detector.py +303 -0
  35. civilization/versioning/version_manager.py +293 -0
  36. cli/__init__.py +1 -0
  37. cli/doctor.py +334 -0
  38. cli/hyperclaw.py +1357 -0
  39. cli/onboarding.py +469 -0
  40. core/__init__.py +1 -0
  41. core/connectors/__init__.py +119 -0
  42. core/connectors/infrastructure.py +772 -0
  43. core/connectors/messaging.py +586 -0
  44. core/connectors/productivity.py +764 -0
  45. core/connectors/smarthome.py +528 -0
  46. core/connectors/social.py +730 -0
  47. core/hyperrouter/__init__.py +23 -0
  48. core/hyperrouter/bandit.py +218 -0
  49. core/hyperrouter/fast_loop.py +68 -0
  50. core/hyperrouter/slow_loop.py +143 -0
  51. core/hyperstate/__init__.py +24 -0
  52. core/hyperstate/certifier.py +154 -0
  53. core/hyperstate/schema.py +160 -0
  54. core/hyperstate/state_manager.py +103 -0
  55. core/hyperstate/store.py +163 -0
  56. core/providers/__init__.py +121 -0
  57. core/providers/embeddings.py +389 -0
  58. core/providers/image.py +534 -0
  59. core/providers/llm.py +1935 -0
  60. core/providers/search.py +608 -0
  61. core/providers/voice.py +474 -0
  62. hyperagents-1.2.3rc5.post3.dist-info/METADATA +469 -0
  63. hyperagents-1.2.3rc5.post3.dist-info/RECORD +361 -0
  64. hyperagents-1.2.3rc5.post3.dist-info/WHEEL +5 -0
  65. hyperagents-1.2.3rc5.post3.dist-info/entry_points.txt +6 -0
  66. hyperagents-1.2.3rc5.post3.dist-info/licenses/LICENSE +21 -0
  67. hyperagents-1.2.3rc5.post3.dist-info/top_level.txt +13 -0
  68. hyperclaw/__init__.py +67 -0
  69. hyperclaw/__main__.py +220 -0
  70. hyperclaw/agent.py +775 -0
  71. hyperclaw/agent_coordinator.py +577 -0
  72. hyperclaw/anthropic_params.py +225 -0
  73. hyperclaw/api_utils.py +85 -0
  74. hyperclaw/approval_telegram.py +209 -0
  75. hyperclaw/approval_terminal.py +80 -0
  76. hyperclaw/attachments.py +177 -0
  77. hyperclaw/branding.py +55 -0
  78. hyperclaw/brief/__init__.py +11 -0
  79. hyperclaw/brief/cli.py +204 -0
  80. hyperclaw/brief/collect.py +146 -0
  81. hyperclaw/brief/compose.py +333 -0
  82. hyperclaw/brief/diff.py +110 -0
  83. hyperclaw/brief/errors.py +99 -0
  84. hyperclaw/brief/google.py +269 -0
  85. hyperclaw/brief/google_auth.py +294 -0
  86. hyperclaw/brief/links.py +124 -0
  87. hyperclaw/brief/service.py +424 -0
  88. hyperclaw/brief/store.py +205 -0
  89. hyperclaw/brief/tasks.py +200 -0
  90. hyperclaw/chat_scope.py +115 -0
  91. hyperclaw/cli_tui.py +497 -0
  92. hyperclaw/client_guard.py +717 -0
  93. hyperclaw/connection_check.py +113 -0
  94. hyperclaw/cost_guard.py +1728 -0
  95. hyperclaw/default_config/agents.yaml +534 -0
  96. hyperclaw/default_config/hyperclaw.yaml +27 -0
  97. hyperclaw/default_config/models.yaml +161 -0
  98. hyperclaw/deployment.py +39 -0
  99. hyperclaw/digest/__init__.py +9 -0
  100. hyperclaw/digest/cli.py +117 -0
  101. hyperclaw/digest/errors.py +100 -0
  102. hyperclaw/digest/gitlab.py +187 -0
  103. hyperclaw/digest/http.py +113 -0
  104. hyperclaw/digest/redact.py +32 -0
  105. hyperclaw/digest/service.py +271 -0
  106. hyperclaw/digest/slack.py +223 -0
  107. hyperclaw/digest/state.py +124 -0
  108. hyperclaw/digest/summarize.py +198 -0
  109. hyperclaw/digest/tokens.py +143 -0
  110. hyperclaw/distribution.py +46 -0
  111. hyperclaw/documents.py +351 -0
  112. hyperclaw/feeds.py +339 -0
  113. hyperclaw/google_workspace/__init__.py +5 -0
  114. hyperclaw/google_workspace/api.py +151 -0
  115. hyperclaw/google_workspace/auth.py +397 -0
  116. hyperclaw/google_workspace/calendar.py +157 -0
  117. hyperclaw/google_workspace/cli.py +72 -0
  118. hyperclaw/google_workspace/drive.py +236 -0
  119. hyperclaw/google_workspace/errors.py +58 -0
  120. hyperclaw/google_workspace/gmail.py +295 -0
  121. hyperclaw/google_workspace/meet.py +122 -0
  122. hyperclaw/google_workspace/tools.py +319 -0
  123. hyperclaw/hyperspeed.py +57 -0
  124. hyperclaw/integrations_layer.py +1427 -0
  125. hyperclaw/live_state.py +188 -0
  126. hyperclaw/managed_config.py +245 -0
  127. hyperclaw/media_hub.py +179 -0
  128. hyperclaw/memory_manager.py +584 -0
  129. hyperclaw/model_catalog.py +551 -0
  130. hyperclaw/model_policy.py +211 -0
  131. hyperclaw/model_router.py +595 -0
  132. hyperclaw/model_selector.py +72 -0
  133. hyperclaw/naming.py +284 -0
  134. hyperclaw/notices.py +65 -0
  135. hyperclaw/oai_stream.py +106 -0
  136. hyperclaw/orchestrator.py +591 -0
  137. hyperclaw/outbox.py +82 -0
  138. hyperclaw/persona.py +367 -0
  139. hyperclaw/prometheus.py +219 -0
  140. hyperclaw/prompt_assembly.py +157 -0
  141. hyperclaw/provider_setup.py +146 -0
  142. hyperclaw/providers.py +340 -0
  143. hyperclaw/qualification.py +341 -0
  144. hyperclaw/qualification_keys/hyper-d292fd127cef5a3e.pub +1 -0
  145. hyperclaw/qualification_records/anthropic--claude-sonnet-5-5.json +14 -0
  146. hyperclaw/reasoning_display.py +35 -0
  147. hyperclaw/scheduler.py +454 -0
  148. hyperclaw/server.py +847 -0
  149. hyperclaw/setup.py +627 -0
  150. hyperclaw/skills.py +717 -0
  151. hyperclaw/solomon.py +442 -0
  152. hyperclaw/swarm.py +533 -0
  153. hyperclaw/taint.py +832 -0
  154. hyperclaw/team/__init__.py +28 -0
  155. hyperclaw/team/dispatcher.py +431 -0
  156. hyperclaw/team/genesis.py +106 -0
  157. hyperclaw/team/lead.py +110 -0
  158. hyperclaw/team/model.py +135 -0
  159. hyperclaw/team/morning.py +238 -0
  160. hyperclaw/team/repos.py +66 -0
  161. hyperclaw/team/roster.py +264 -0
  162. hyperclaw/team/specialist.py +235 -0
  163. hyperclaw/team/status.py +163 -0
  164. hyperclaw/team/watchers.py +112 -0
  165. hyperclaw/teamsetup/__init__.py +17 -0
  166. hyperclaw/teamsetup/checks.py +208 -0
  167. hyperclaw/teamsetup/item.py +391 -0
  168. hyperclaw/teamsetup/oauth_loopback.py +84 -0
  169. hyperclaw/teamsetup/onboard.py +550 -0
  170. hyperclaw/teamsetup/profile.py +82 -0
  171. hyperclaw/teamsetup/secrets.py +313 -0
  172. hyperclaw/teamsetup/selfcheck.py +295 -0
  173. hyperclaw/teamsetup/service.py +495 -0
  174. hyperclaw/teamsetup/slack_oauth.py +442 -0
  175. hyperclaw/teamsetup/tty.py +62 -0
  176. hyperclaw/telegram_bot.py +1106 -0
  177. hyperclaw/telegram_intake.py +126 -0
  178. hyperclaw/telegram_pairing.py +360 -0
  179. hyperclaw/terminal_provider.py +323 -0
  180. hyperclaw/tools/__init__.py +1 -0
  181. hyperclaw/tools/approval.py +949 -0
  182. hyperclaw/tools/args.py +212 -0
  183. hyperclaw/tools/catalog.py +244 -0
  184. hyperclaw/tools/classify.py +266 -0
  185. hyperclaw/tools/effects.py +724 -0
  186. hyperclaw/tools/egress.py +210 -0
  187. hyperclaw/tools/envelope.py +188 -0
  188. hyperclaw/tools/guard.py +367 -0
  189. hyperclaw/tools/ledger.py +339 -0
  190. hyperclaw/tools/receipts.py +70 -0
  191. hyperclaw/tools/registry.py +318 -0
  192. hyperclaw/tools/service_guard.py +151 -0
  193. hyperclaw/tools/surfaces.py +54 -0
  194. hyperclaw/tools/workspace.py +87 -0
  195. hyperclaw/trading/__init__.py +77 -0
  196. hyperclaw/truth_guard.py +791 -0
  197. hyperclaw/tts.py +124 -0
  198. hyperclaw/tui.py +4255 -0
  199. hyperclaw/tui_bridge.py +938 -0
  200. hyperclaw/unattended.py +60 -0
  201. hyperclaw/updates/__init__.py +54 -0
  202. hyperclaw/updates/check.py +131 -0
  203. hyperclaw/updates/index.py +178 -0
  204. hyperclaw/updates/install.py +110 -0
  205. hyperclaw/updates/managed.py +161 -0
  206. hyperclaw/updates/manifest.py +138 -0
  207. hyperclaw/updates/notices.py +86 -0
  208. hyperclaw/updates/paths.py +53 -0
  209. hyperclaw/updates/release.py +146 -0
  210. hyperclaw/updates/upgrade.py +253 -0
  211. hyperclaw/updates/versioning.py +104 -0
  212. hyperclaw/userconfig.py +119 -0
  213. hyperclaw/xai.py +83 -0
  214. integrations/__init__.py +31 -0
  215. integrations/automation/__init__.py +1 -0
  216. integrations/automation/make.py +87 -0
  217. integrations/automation/zapier.py +87 -0
  218. integrations/base.py +476 -0
  219. integrations/communication/__init__.py +1 -0
  220. integrations/communication/twilio.py +279 -0
  221. integrations/communication/zoom.py +290 -0
  222. integrations/data/__init__.py +1 -0
  223. integrations/data/google_analytics.py +135 -0
  224. integrations/data/postgres.py +182 -0
  225. integrations/data/supabase_connector.py +202 -0
  226. integrations/data/weather_feeds.py +450 -0
  227. integrations/developer/__init__.py +9 -0
  228. integrations/developer/github.py +406 -0
  229. integrations/developer/gitlab.py +298 -0
  230. integrations/enterprise/__init__.py +13 -0
  231. integrations/enterprise/confluence.py +266 -0
  232. integrations/enterprise/hubspot.py +293 -0
  233. integrations/enterprise/jira.py +314 -0
  234. integrations/enterprise/salesforce.py +286 -0
  235. integrations/finance/__init__.py +1 -0
  236. integrations/finance/polymarket_client.py +247 -0
  237. integrations/finance/quickbooks.py +281 -0
  238. integrations/finance/stripe.py +261 -0
  239. integrations/gateway.py +238 -0
  240. integrations/messaging/__init__.py +32 -0
  241. integrations/messaging/discord.py +159 -0
  242. integrations/messaging/email.py +401 -0
  243. integrations/messaging/imessage.py +443 -0
  244. integrations/messaging/signal.py +175 -0
  245. integrations/messaging/slack.py +217 -0
  246. integrations/messaging/sms.py +175 -0
  247. integrations/messaging/teams.py +208 -0
  248. integrations/messaging/telegram.py +260 -0
  249. integrations/messaging/whatsapp.py +203 -0
  250. integrations/productivity/__init__.py +17 -0
  251. integrations/productivity/airtable.py +217 -0
  252. integrations/productivity/asana.py +248 -0
  253. integrations/productivity/google/__init__.py +19 -0
  254. integrations/productivity/google/base.py +89 -0
  255. integrations/productivity/google/calendar.py +272 -0
  256. integrations/productivity/google/docs.py +202 -0
  257. integrations/productivity/google/drive.py +268 -0
  258. integrations/productivity/google/gmail.py +279 -0
  259. integrations/productivity/google/meet.py +218 -0
  260. integrations/productivity/google/sheets.py +222 -0
  261. integrations/productivity/google/tasks.py +199 -0
  262. integrations/productivity/linear.py +279 -0
  263. integrations/productivity/microsoft/__init__.py +13 -0
  264. integrations/productivity/microsoft/base.py +93 -0
  265. integrations/productivity/microsoft/calendar.py +207 -0
  266. integrations/productivity/microsoft/onedrive.py +222 -0
  267. integrations/productivity/microsoft/outlook.py +241 -0
  268. integrations/productivity/microsoft/sharepoint.py +211 -0
  269. integrations/productivity/notion.py +243 -0
  270. integrations/productivity/todoist.py +250 -0
  271. integrations/productivity/trello.py +244 -0
  272. integrations/storage/__init__.py +1 -0
  273. integrations/storage/box.py +279 -0
  274. integrations/storage/dropbox.py +215 -0
  275. integrations/telegram_inbound.py +179 -0
  276. integrations/telegram_poller.py +151 -0
  277. memory/__init__.py +1 -0
  278. memory/causal_graph.py +125 -0
  279. memory/impact_tracker.py +88 -0
  280. memory/vector_memory.py +436 -0
  281. models/__init__.py +12 -0
  282. models/chatjimmy_client.py +210 -0
  283. models/claude_client.py +235 -0
  284. models/claude_code_subagent.py +192 -0
  285. models/openai_client.py +120 -0
  286. models/router.py +162 -0
  287. recursive/discovery_loop.py +118 -0
  288. recursive/optimization_loop.py +135 -0
  289. recursive/validation_loop.py +38 -0
  290. security/__init__.py +1 -0
  291. security/audit_logger.py +106 -0
  292. security/filesystem_guard.py +100 -0
  293. security/hypershield.py +99 -0
  294. security/network_guard.py +97 -0
  295. security/policy_engine.py +198 -0
  296. swarm/__init__.py +1 -0
  297. swarm/agents/__init__.py +0 -0
  298. swarm/agents/base.py +230 -0
  299. swarm/agents/business/__init__.py +0 -0
  300. swarm/agents/business/counsel.py +31 -0
  301. swarm/agents/business/herald.py +32 -0
  302. swarm/agents/business/ledger.py +26 -0
  303. swarm/agents/business/nexus.py +30 -0
  304. swarm/agents/business/ops.py +30 -0
  305. swarm/agents/business/pipeline.py +28 -0
  306. swarm/agents/business/revenue.py +30 -0
  307. swarm/agents/business/sovereign.py +31 -0
  308. swarm/agents/business/strategos.py +27 -0
  309. swarm/agents/business/talent.py +27 -0
  310. swarm/agents/business/venture.py +32 -0
  311. swarm/agents/comms/__init__.py +1 -0
  312. swarm/agents/comms/cipher.py +32 -0
  313. swarm/agents/comms/echo_comms.py +31 -0
  314. swarm/agents/comms/envoy.py +31 -0
  315. swarm/agents/comms/herald_scheduler.py +33 -0
  316. swarm/agents/comms/pulse.py +29 -0
  317. swarm/agents/creative/__init__.py +0 -0
  318. swarm/agents/creative/author.py +27 -0
  319. swarm/agents/creative/lens.py +25 -0
  320. swarm/agents/executive/__init__.py +1 -0
  321. swarm/agents/intelligence/__init__.py +1 -0
  322. swarm/agents/intelligence/arbiter.py +30 -0
  323. swarm/agents/intelligence/sentinel.py +30 -0
  324. swarm/agents/media/__init__.py +0 -0
  325. swarm/agents/personal/__init__.py +0 -0
  326. swarm/agents/personal/atlas.py +27 -0
  327. swarm/agents/personal/hearth.py +26 -0
  328. swarm/agents/personal/midas.py +27 -0
  329. swarm/agents/personal/navigator.py +27 -0
  330. swarm/agents/personal/nourish.py +31 -0
  331. swarm/agents/personal/vitals.py +33 -0
  332. swarm/agents/recursive/__init__.py +0 -0
  333. swarm/agents/recursive/alchemist.py +73 -0
  334. swarm/agents/recursive/calibrator.py +45 -0
  335. swarm/agents/recursive/scout.py +97 -0
  336. swarm/agents/scientific/__init__.py +0 -0
  337. swarm/agents/scientific/cosmos.py +36 -0
  338. swarm/agents/scientific/gaia.py +26 -0
  339. swarm/agents/scientific/medicus.py +33 -0
  340. swarm/agents/scientific/oracle_agent.py +39 -0
  341. swarm/agents/scientific/scribe.py +39 -0
  342. swarm/agents/talent/__init__.py +0 -0
  343. swarm/agents/talent/deal.py +28 -0
  344. swarm/agents/talent/roster.py +29 -0
  345. swarm/agents/talent/scout.py +29 -0
  346. swarm/agents/talent/stage.py +27 -0
  347. swarm/agents/tech/__init__.py +0 -0
  348. swarm/agents/tech/aegis.py +28 -0
  349. swarm/agents/tech/bridge.py +28 -0
  350. swarm/agents/tech/forge.py +28 -0
  351. swarm/agents/trading/__init__.py +1 -0
  352. swarm/agents/trading/global_prediction_engine.py +611 -0
  353. swarm/agents/trading/polymarket_trader.py +165 -0
  354. swarm/agents/trading/prediction_strategist.py +218 -0
  355. swarm/autogen_bridge.py +68 -0
  356. swarm/bid_protocol.py +196 -0
  357. swarm/dispatcher.py +527 -0
  358. swarm/nexus.py +181 -0
  359. swarm/registry.py +112 -0
  360. telegram_listener.py +403 -0
  361. ui/tui/app.py +214 -0
@@ -0,0 +1,772 @@
1
+ """
2
+ Infrastructure Connectors.
3
+ Integration with Docker, AWS, GCP, Azure, Tailscale, Snowflake, BigQuery, Datadog, PagerDuty.
4
+ """
5
+ from __future__ import annotations
6
+
7
+ import os
8
+ import logging
9
+ from abc import ABC, abstractmethod
10
+ from dataclasses import dataclass, field
11
+ from datetime import datetime
12
+ from typing import Any
13
+
14
+ import httpx
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ @dataclass
20
+ class Container:
21
+ """Docker container."""
22
+ id: str
23
+ name: str
24
+ image: str
25
+ status: str
26
+ ports: dict = field(default_factory=dict)
27
+ raw: dict = field(default_factory=dict)
28
+
29
+
30
+ @dataclass
31
+ class CloudResource:
32
+ """Cloud resource."""
33
+ id: str
34
+ name: str
35
+ type: str
36
+ region: str | None = None
37
+ status: str = ""
38
+ raw: dict = field(default_factory=dict)
39
+
40
+
41
+ class InfrastructureConnector(ABC):
42
+ """Abstract base for infrastructure connectors."""
43
+
44
+ name: str = "base"
45
+
46
+
47
+ # ═══════════════════════════════════════════════════════════════════════════════
48
+ # DOCKER
49
+ # ═══════════════════════════════════════════════════════════════════════════════
50
+
51
+ class DockerConnector(InfrastructureConnector):
52
+ """Docker Engine API connector."""
53
+
54
+ name = "docker"
55
+
56
+ def __init__(self, host: str | None = None):
57
+ self.host = host or os.environ.get("DOCKER_HOST", "unix:///var/run/docker.sock")
58
+ if self.host.startswith("unix://"):
59
+ self.transport = httpx.HTTPTransport(uds=self.host[7:])
60
+ self.base_url = "http://localhost"
61
+ else:
62
+ self.transport = None
63
+ self.base_url = self.host
64
+
65
+ async def list_containers(self, all: bool = False) -> list[Container]:
66
+ async with httpx.AsyncClient(transport=self.transport) as client:
67
+ response = await client.get(
68
+ f"{self.base_url}/containers/json",
69
+ params={"all": str(all).lower()},
70
+ timeout=30.0,
71
+ )
72
+ response.raise_for_status()
73
+ data = response.json()
74
+
75
+ return [
76
+ Container(
77
+ id=c["Id"][:12],
78
+ name=c["Names"][0].lstrip("/") if c.get("Names") else "",
79
+ image=c["Image"],
80
+ status=c["Status"],
81
+ ports={p["PrivatePort"]: p.get("PublicPort") for p in c.get("Ports", [])},
82
+ raw=c,
83
+ )
84
+ for c in data
85
+ ]
86
+
87
+ async def start_container(self, container_id: str) -> dict:
88
+ async with httpx.AsyncClient(transport=self.transport) as client:
89
+ response = await client.post(
90
+ f"{self.base_url}/containers/{container_id}/start",
91
+ timeout=30.0,
92
+ )
93
+ if response.status_code == 204:
94
+ return {"status": "started"}
95
+ response.raise_for_status()
96
+ return response.json()
97
+
98
+ async def stop_container(self, container_id: str, timeout: int = 10) -> dict:
99
+ async with httpx.AsyncClient(transport=self.transport) as client:
100
+ response = await client.post(
101
+ f"{self.base_url}/containers/{container_id}/stop",
102
+ params={"t": timeout},
103
+ timeout=30.0,
104
+ )
105
+ if response.status_code == 204:
106
+ return {"status": "stopped"}
107
+ response.raise_for_status()
108
+ return response.json()
109
+
110
+ async def logs(self, container_id: str, tail: int = 100) -> str:
111
+ async with httpx.AsyncClient(transport=self.transport) as client:
112
+ response = await client.get(
113
+ f"{self.base_url}/containers/{container_id}/logs",
114
+ params={"stdout": "true", "stderr": "true", "tail": tail},
115
+ timeout=30.0,
116
+ )
117
+ response.raise_for_status()
118
+ return response.text
119
+
120
+ async def run(self, image: str, command: str | None = None, **kwargs) -> Container:
121
+ """Create and start a container."""
122
+ async with httpx.AsyncClient(transport=self.transport) as client:
123
+ # Create container
124
+ create_response = await client.post(
125
+ f"{self.base_url}/containers/create",
126
+ json={
127
+ "Image": image,
128
+ "Cmd": command.split() if command else None,
129
+ **kwargs,
130
+ },
131
+ timeout=60.0,
132
+ )
133
+ create_response.raise_for_status()
134
+ container_id = create_response.json()["Id"]
135
+
136
+ # Start container
137
+ await client.post(
138
+ f"{self.base_url}/containers/{container_id}/start",
139
+ timeout=30.0,
140
+ )
141
+
142
+ return Container(id=container_id[:12], name="", image=image, status="running")
143
+
144
+
145
+ # ═══════════════════════════════════════════════════════════════════════════════
146
+ # AWS
147
+ # ═══════════════════════════════════════════════════════════════════════════════
148
+
149
+ class AWSConnector(InfrastructureConnector):
150
+ """AWS connector (via boto3)."""
151
+
152
+ name = "aws"
153
+
154
+ def __init__(
155
+ self,
156
+ access_key: str | None = None,
157
+ secret_key: str | None = None,
158
+ region: str | None = None,
159
+ ):
160
+ self.access_key = access_key or os.environ.get("AWS_ACCESS_KEY_ID")
161
+ self.secret_key = secret_key or os.environ.get("AWS_SECRET_ACCESS_KEY")
162
+ self.region = region or os.environ.get("AWS_REGION", "us-east-1")
163
+
164
+ def _get_client(self, service: str):
165
+ try:
166
+ import boto3
167
+ except ImportError:
168
+ raise ImportError("boto3 required: pip install boto3")
169
+
170
+ return boto3.client(
171
+ service,
172
+ region_name=self.region,
173
+ aws_access_key_id=self.access_key,
174
+ aws_secret_access_key=self.secret_key,
175
+ )
176
+
177
+ async def list_ec2_instances(self) -> list[CloudResource]:
178
+ import asyncio
179
+
180
+ ec2 = self._get_client("ec2")
181
+
182
+ def _list():
183
+ response = ec2.describe_instances()
184
+ instances = []
185
+ for reservation in response["Reservations"]:
186
+ for instance in reservation["Instances"]:
187
+ name = ""
188
+ for tag in instance.get("Tags", []):
189
+ if tag["Key"] == "Name":
190
+ name = tag["Value"]
191
+ break
192
+ instances.append(
193
+ CloudResource(
194
+ id=instance["InstanceId"],
195
+ name=name,
196
+ type="ec2",
197
+ region=self.region,
198
+ status=instance["State"]["Name"],
199
+ raw=instance,
200
+ )
201
+ )
202
+ return instances
203
+
204
+ return await asyncio.to_thread(_list)
205
+
206
+ async def list_s3_buckets(self) -> list[CloudResource]:
207
+ import asyncio
208
+
209
+ s3 = self._get_client("s3")
210
+
211
+ def _list():
212
+ response = s3.list_buckets()
213
+ return [
214
+ CloudResource(
215
+ id=bucket["Name"],
216
+ name=bucket["Name"],
217
+ type="s3",
218
+ raw=bucket,
219
+ )
220
+ for bucket in response["Buckets"]
221
+ ]
222
+
223
+ return await asyncio.to_thread(_list)
224
+
225
+ async def invoke_lambda(self, function_name: str, payload: dict) -> dict:
226
+ import asyncio
227
+ import json
228
+
229
+ lambda_client = self._get_client("lambda")
230
+
231
+ def _invoke():
232
+ response = lambda_client.invoke(
233
+ FunctionName=function_name,
234
+ Payload=json.dumps(payload),
235
+ )
236
+ return json.loads(response["Payload"].read())
237
+
238
+ return await asyncio.to_thread(_invoke)
239
+
240
+
241
+ # ═══════════════════════════════════════════════════════════════════════════════
242
+ # GCP
243
+ # ═══════════════════════════════════════════════════════════════════════════════
244
+
245
+ class GCPConnector(InfrastructureConnector):
246
+ """Google Cloud Platform connector."""
247
+
248
+ name = "gcp"
249
+
250
+ def __init__(
251
+ self,
252
+ project_id: str | None = None,
253
+ credentials_path: str | None = None,
254
+ ):
255
+ self.project_id = project_id or os.environ.get("GCP_PROJECT_ID")
256
+ self.credentials_path = credentials_path or os.environ.get("GOOGLE_APPLICATION_CREDENTIALS")
257
+ self._token: str | None = None
258
+
259
+ async def _get_token(self) -> str:
260
+ if self._token:
261
+ return self._token
262
+
263
+ # Use gcloud CLI for token
264
+ import subprocess
265
+
266
+ result = subprocess.run(
267
+ ["gcloud", "auth", "print-access-token"],
268
+ capture_output=True,
269
+ text=True,
270
+ )
271
+ if result.returncode == 0:
272
+ self._token = result.stdout.strip()
273
+ return self._token
274
+ raise RuntimeError("Failed to get GCP token")
275
+
276
+ async def list_compute_instances(self, zone: str = "us-central1-a") -> list[CloudResource]:
277
+ token = await self._get_token()
278
+
279
+ async with httpx.AsyncClient() as client:
280
+ response = await client.get(
281
+ f"https://compute.googleapis.com/compute/v1/projects/{self.project_id}/zones/{zone}/instances",
282
+ headers={"Authorization": f"Bearer {token}"},
283
+ timeout=30.0,
284
+ )
285
+ response.raise_for_status()
286
+ data = response.json()
287
+
288
+ return [
289
+ CloudResource(
290
+ id=instance["id"],
291
+ name=instance["name"],
292
+ type="compute",
293
+ region=zone,
294
+ status=instance["status"],
295
+ raw=instance,
296
+ )
297
+ for instance in data.get("items", [])
298
+ ]
299
+
300
+ async def list_gcs_buckets(self) -> list[CloudResource]:
301
+ token = await self._get_token()
302
+
303
+ async with httpx.AsyncClient() as client:
304
+ response = await client.get(
305
+ f"https://storage.googleapis.com/storage/v1/b",
306
+ headers={"Authorization": f"Bearer {token}"},
307
+ params={"project": self.project_id},
308
+ timeout=30.0,
309
+ )
310
+ response.raise_for_status()
311
+ data = response.json()
312
+
313
+ return [
314
+ CloudResource(
315
+ id=bucket["id"],
316
+ name=bucket["name"],
317
+ type="gcs",
318
+ region=bucket.get("location"),
319
+ raw=bucket,
320
+ )
321
+ for bucket in data.get("items", [])
322
+ ]
323
+
324
+
325
+ # ═══════════════════════════════════════════════════════════════════════════════
326
+ # AZURE
327
+ # ═══════════════════════════════════════════════════════════════════════════════
328
+
329
+ class AzureConnector(InfrastructureConnector):
330
+ """Microsoft Azure connector."""
331
+
332
+ name = "azure"
333
+
334
+ def __init__(
335
+ self,
336
+ subscription_id: str | None = None,
337
+ tenant_id: str | None = None,
338
+ client_id: str | None = None,
339
+ client_secret: str | None = None,
340
+ ):
341
+ self.subscription_id = subscription_id or os.environ.get("AZURE_SUBSCRIPTION_ID")
342
+ self.tenant_id = tenant_id or os.environ.get("AZURE_TENANT_ID")
343
+ self.client_id = client_id or os.environ.get("AZURE_CLIENT_ID")
344
+ self.client_secret = client_secret or os.environ.get("AZURE_CLIENT_SECRET")
345
+ self._token: str | None = None
346
+
347
+ async def _get_token(self) -> str:
348
+ if self._token:
349
+ return self._token
350
+
351
+ async with httpx.AsyncClient() as client:
352
+ response = await client.post(
353
+ f"https://login.microsoftonline.com/{self.tenant_id}/oauth2/v2.0/token",
354
+ data={
355
+ "grant_type": "client_credentials",
356
+ "client_id": self.client_id,
357
+ "client_secret": self.client_secret,
358
+ "scope": "https://management.azure.com/.default",
359
+ },
360
+ timeout=30.0,
361
+ )
362
+ response.raise_for_status()
363
+ data = response.json()
364
+ self._token = data["access_token"]
365
+ return self._token
366
+
367
+ async def list_vms(self, resource_group: str | None = None) -> list[CloudResource]:
368
+ token = await self._get_token()
369
+
370
+ url = f"https://management.azure.com/subscriptions/{self.subscription_id}"
371
+ if resource_group:
372
+ url += f"/resourceGroups/{resource_group}"
373
+ url += "/providers/Microsoft.Compute/virtualMachines?api-version=2023-03-01"
374
+
375
+ async with httpx.AsyncClient() as client:
376
+ response = await client.get(
377
+ url,
378
+ headers={"Authorization": f"Bearer {token}"},
379
+ timeout=30.0,
380
+ )
381
+ response.raise_for_status()
382
+ data = response.json()
383
+
384
+ return [
385
+ CloudResource(
386
+ id=vm["id"],
387
+ name=vm["name"],
388
+ type="vm",
389
+ region=vm["location"],
390
+ raw=vm,
391
+ )
392
+ for vm in data.get("value", [])
393
+ ]
394
+
395
+
396
+ # ═══════════════════════════════════════════════════════════════════════════════
397
+ # TAILSCALE
398
+ # ═══════════════════════════════════════════════════════════════════════════════
399
+
400
+ class TailscaleConnector(InfrastructureConnector):
401
+ """Tailscale VPN connector."""
402
+
403
+ name = "tailscale"
404
+
405
+ def __init__(self, api_key: str | None = None, tailnet: str | None = None):
406
+ self.api_key = api_key or os.environ.get("TAILSCALE_API_KEY")
407
+ self.tailnet = tailnet or os.environ.get("TAILSCALE_TAILNET", "-")
408
+ self.base_url = "https://api.tailscale.com/api/v2"
409
+
410
+ async def list_devices(self) -> list[CloudResource]:
411
+ async with httpx.AsyncClient() as client:
412
+ response = await client.get(
413
+ f"{self.base_url}/tailnet/{self.tailnet}/devices",
414
+ auth=(self.api_key, ""),
415
+ timeout=30.0,
416
+ )
417
+ response.raise_for_status()
418
+ data = response.json()
419
+
420
+ return [
421
+ CloudResource(
422
+ id=device["id"],
423
+ name=device["name"],
424
+ type="tailscale",
425
+ status="online" if device.get("online") else "offline",
426
+ raw=device,
427
+ )
428
+ for device in data.get("devices", [])
429
+ ]
430
+
431
+ async def get_device(self, device_id: str) -> CloudResource:
432
+ async with httpx.AsyncClient() as client:
433
+ response = await client.get(
434
+ f"{self.base_url}/device/{device_id}",
435
+ auth=(self.api_key, ""),
436
+ timeout=30.0,
437
+ )
438
+ response.raise_for_status()
439
+ device = response.json()
440
+
441
+ return CloudResource(
442
+ id=device["id"],
443
+ name=device["name"],
444
+ type="tailscale",
445
+ status="online" if device.get("online") else "offline",
446
+ raw=device,
447
+ )
448
+
449
+
450
+ # ═══════════════════════════════════════════════════════════════════════════════
451
+ # SNOWFLAKE
452
+ # ═══════════════════════════════════════════════════════════════════════════════
453
+
454
+ class SnowflakeConnector(InfrastructureConnector):
455
+ """Snowflake data warehouse connector."""
456
+
457
+ name = "snowflake"
458
+
459
+ def __init__(
460
+ self,
461
+ account: str | None = None,
462
+ user: str | None = None,
463
+ password: str | None = None,
464
+ warehouse: str | None = None,
465
+ database: str | None = None,
466
+ ):
467
+ self.account = account or os.environ.get("SNOWFLAKE_ACCOUNT")
468
+ self.user = user or os.environ.get("SNOWFLAKE_USER")
469
+ self.password = password or os.environ.get("SNOWFLAKE_PASSWORD")
470
+ self.warehouse = warehouse or os.environ.get("SNOWFLAKE_WAREHOUSE")
471
+ self.database = database or os.environ.get("SNOWFLAKE_DATABASE")
472
+
473
+ def _get_connection(self):
474
+ try:
475
+ import snowflake.connector
476
+ except ImportError:
477
+ raise ImportError("snowflake-connector-python required: pip install snowflake-connector-python")
478
+
479
+ return snowflake.connector.connect(
480
+ account=self.account,
481
+ user=self.user,
482
+ password=self.password,
483
+ warehouse=self.warehouse,
484
+ database=self.database,
485
+ )
486
+
487
+ async def query(self, sql: str) -> list[dict]:
488
+ import asyncio
489
+
490
+ def _query():
491
+ conn = self._get_connection()
492
+ try:
493
+ cursor = conn.cursor()
494
+ cursor.execute(sql)
495
+ columns = [desc[0] for desc in cursor.description]
496
+ return [dict(zip(columns, row)) for row in cursor.fetchall()]
497
+ finally:
498
+ conn.close()
499
+
500
+ return await asyncio.to_thread(_query)
501
+
502
+ async def list_warehouses(self) -> list[CloudResource]:
503
+ results = await self.query("SHOW WAREHOUSES")
504
+ return [
505
+ CloudResource(
506
+ id=row["name"],
507
+ name=row["name"],
508
+ type="warehouse",
509
+ status=row.get("state", ""),
510
+ raw=row,
511
+ )
512
+ for row in results
513
+ ]
514
+
515
+
516
+ # ═══════════════════════════════════════════════════════════════════════════════
517
+ # BIGQUERY
518
+ # ═══════════════════════════════════════════════════════════════════════════════
519
+
520
+ class BigQueryConnector(InfrastructureConnector):
521
+ """Google BigQuery connector."""
522
+
523
+ name = "bigquery"
524
+
525
+ def __init__(self, project_id: str | None = None):
526
+ self.project_id = project_id or os.environ.get("GCP_PROJECT_ID")
527
+ self.base_url = "https://bigquery.googleapis.com/bigquery/v2"
528
+
529
+ async def _get_token(self) -> str:
530
+ import subprocess
531
+
532
+ result = subprocess.run(
533
+ ["gcloud", "auth", "print-access-token"],
534
+ capture_output=True,
535
+ text=True,
536
+ )
537
+ if result.returncode == 0:
538
+ return result.stdout.strip()
539
+ raise RuntimeError("Failed to get GCP token")
540
+
541
+ async def query(self, sql: str) -> list[dict]:
542
+ token = await self._get_token()
543
+
544
+ async with httpx.AsyncClient() as client:
545
+ response = await client.post(
546
+ f"{self.base_url}/projects/{self.project_id}/queries",
547
+ headers={"Authorization": f"Bearer {token}"},
548
+ json={"query": sql, "useLegacySql": False},
549
+ timeout=120.0,
550
+ )
551
+ response.raise_for_status()
552
+ data = response.json()
553
+
554
+ if not data.get("jobComplete"):
555
+ # Poll for completion
556
+ job_id = data["jobReference"]["jobId"]
557
+ while True:
558
+ import asyncio
559
+ await asyncio.sleep(1)
560
+ status_response = await client.get(
561
+ f"{self.base_url}/projects/{self.project_id}/queries/{job_id}",
562
+ headers={"Authorization": f"Bearer {token}"},
563
+ timeout=30.0,
564
+ )
565
+ status_response.raise_for_status()
566
+ data = status_response.json()
567
+ if data.get("jobComplete"):
568
+ break
569
+
570
+ schema = data.get("schema", {}).get("fields", [])
571
+ columns = [field["name"] for field in schema]
572
+
573
+ return [
574
+ dict(zip(columns, [cell.get("v") for cell in row.get("f", [])]))
575
+ for row in data.get("rows", [])
576
+ ]
577
+
578
+ async def list_datasets(self) -> list[CloudResource]:
579
+ token = await self._get_token()
580
+
581
+ async with httpx.AsyncClient() as client:
582
+ response = await client.get(
583
+ f"{self.base_url}/projects/{self.project_id}/datasets",
584
+ headers={"Authorization": f"Bearer {token}"},
585
+ timeout=30.0,
586
+ )
587
+ response.raise_for_status()
588
+ data = response.json()
589
+
590
+ return [
591
+ CloudResource(
592
+ id=ds["id"],
593
+ name=ds["datasetReference"]["datasetId"],
594
+ type="dataset",
595
+ raw=ds,
596
+ )
597
+ for ds in data.get("datasets", [])
598
+ ]
599
+
600
+
601
+ # ═══════════════════════════════════════════════════════════════════════════════
602
+ # DATADOG
603
+ # ═══════════════════════════════════════════════════════════════════════════════
604
+
605
+ class DatadogConnector(InfrastructureConnector):
606
+ """Datadog monitoring connector."""
607
+
608
+ name = "datadog"
609
+
610
+ def __init__(
611
+ self,
612
+ api_key: str | None = None,
613
+ app_key: str | None = None,
614
+ site: str = "datadoghq.com",
615
+ ):
616
+ self.api_key = api_key or os.environ.get("DATADOG_API_KEY")
617
+ self.app_key = app_key or os.environ.get("DATADOG_APP_KEY")
618
+ self.base_url = f"https://api.{site}/api/v1"
619
+
620
+ async def get_metrics(self, query: str, start: int, end: int) -> dict:
621
+ async with httpx.AsyncClient() as client:
622
+ response = await client.get(
623
+ f"{self.base_url}/query",
624
+ headers={
625
+ "DD-API-KEY": self.api_key,
626
+ "DD-APPLICATION-KEY": self.app_key,
627
+ },
628
+ params={"query": query, "from": start, "to": end},
629
+ timeout=30.0,
630
+ )
631
+ response.raise_for_status()
632
+ return response.json()
633
+
634
+ async def list_monitors(self) -> list[dict]:
635
+ async with httpx.AsyncClient() as client:
636
+ response = await client.get(
637
+ f"{self.base_url}/monitor",
638
+ headers={
639
+ "DD-API-KEY": self.api_key,
640
+ "DD-APPLICATION-KEY": self.app_key,
641
+ },
642
+ timeout=30.0,
643
+ )
644
+ response.raise_for_status()
645
+ return response.json()
646
+
647
+ async def send_event(self, title: str, text: str, **kwargs) -> dict:
648
+ async with httpx.AsyncClient() as client:
649
+ response = await client.post(
650
+ f"{self.base_url}/events",
651
+ headers={
652
+ "DD-API-KEY": self.api_key,
653
+ "DD-APPLICATION-KEY": self.app_key,
654
+ },
655
+ json={"title": title, "text": text, **kwargs},
656
+ timeout=30.0,
657
+ )
658
+ response.raise_for_status()
659
+ return response.json()
660
+
661
+
662
+ # ═══════════════════════════════════════════════════════════════════════════════
663
+ # PAGERDUTY
664
+ # ═══════════════════════════════════════════════════════════════════════════════
665
+
666
+ class PagerDutyConnector(InfrastructureConnector):
667
+ """PagerDuty incident management connector."""
668
+
669
+ name = "pagerduty"
670
+
671
+ def __init__(self, api_key: str | None = None):
672
+ self.api_key = api_key or os.environ.get("PAGERDUTY_API_KEY")
673
+ self.base_url = "https://api.pagerduty.com"
674
+
675
+ async def list_incidents(self, status: str = "triggered,acknowledged") -> list[dict]:
676
+ async with httpx.AsyncClient() as client:
677
+ response = await client.get(
678
+ f"{self.base_url}/incidents",
679
+ headers={
680
+ "Authorization": f"Token token={self.api_key}",
681
+ "Content-Type": "application/json",
682
+ },
683
+ params={"statuses[]": status.split(",")},
684
+ timeout=30.0,
685
+ )
686
+ response.raise_for_status()
687
+ return response.json().get("incidents", [])
688
+
689
+ async def create_incident(
690
+ self,
691
+ title: str,
692
+ service_id: str,
693
+ urgency: str = "high",
694
+ **kwargs,
695
+ ) -> dict:
696
+ async with httpx.AsyncClient() as client:
697
+ response = await client.post(
698
+ f"{self.base_url}/incidents",
699
+ headers={
700
+ "Authorization": f"Token token={self.api_key}",
701
+ "Content-Type": "application/json",
702
+ },
703
+ json={
704
+ "incident": {
705
+ "type": "incident",
706
+ "title": title,
707
+ "service": {"id": service_id, "type": "service_reference"},
708
+ "urgency": urgency,
709
+ **kwargs,
710
+ }
711
+ },
712
+ timeout=30.0,
713
+ )
714
+ response.raise_for_status()
715
+ return response.json().get("incident", {})
716
+
717
+ async def acknowledge_incident(self, incident_id: str) -> dict:
718
+ async with httpx.AsyncClient() as client:
719
+ response = await client.put(
720
+ f"{self.base_url}/incidents/{incident_id}",
721
+ headers={
722
+ "Authorization": f"Token token={self.api_key}",
723
+ "Content-Type": "application/json",
724
+ },
725
+ json={"incident": {"type": "incident", "status": "acknowledged"}},
726
+ timeout=30.0,
727
+ )
728
+ response.raise_for_status()
729
+ return response.json().get("incident", {})
730
+
731
+ async def resolve_incident(self, incident_id: str) -> dict:
732
+ async with httpx.AsyncClient() as client:
733
+ response = await client.put(
734
+ f"{self.base_url}/incidents/{incident_id}",
735
+ headers={
736
+ "Authorization": f"Token token={self.api_key}",
737
+ "Content-Type": "application/json",
738
+ },
739
+ json={"incident": {"type": "incident", "status": "resolved"}},
740
+ timeout=30.0,
741
+ )
742
+ response.raise_for_status()
743
+ return response.json().get("incident", {})
744
+
745
+
746
+ # ═══════════════════════════════════════════════════════════════════════════════
747
+ # PROVIDER FACTORY
748
+ # ═══════════════════════════════════════════════════════════════════════════════
749
+
750
+ INFRASTRUCTURE_CONNECTORS: dict[str, type[InfrastructureConnector]] = {
751
+ "docker": DockerConnector,
752
+ "aws": AWSConnector,
753
+ "gcp": GCPConnector,
754
+ "google_cloud": GCPConnector,
755
+ "azure": AzureConnector,
756
+ "tailscale": TailscaleConnector,
757
+ "snowflake": SnowflakeConnector,
758
+ "bigquery": BigQueryConnector,
759
+ "datadog": DatadogConnector,
760
+ "pagerduty": PagerDutyConnector,
761
+ }
762
+
763
+
764
+ def get_infrastructure_connector(name: str, **kwargs) -> InfrastructureConnector:
765
+ """Get an infrastructure connector by name."""
766
+ name = name.lower()
767
+
768
+ if name not in INFRASTRUCTURE_CONNECTORS:
769
+ available = ", ".join(sorted(INFRASTRUCTURE_CONNECTORS.keys()))
770
+ raise ValueError(f"Unknown infrastructure connector: {name}. Available: {available}")
771
+
772
+ return INFRASTRUCTURE_CONNECTORS[name](**kwargs)