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,279 @@
1
+ """TwilioConnector — Twilio messaging integration."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+
7
+ import httpx
8
+
9
+ from integrations.base import (
10
+ BaseConnector,
11
+ ConnectorCapability,
12
+ ConnectorInfo,
13
+ )
14
+
15
+ logger = logging.getLogger("hyperclaw.integrations.twilio")
16
+
17
+
18
+ class TwilioConnector(BaseConnector):
19
+ """
20
+ Twilio messaging connector for SMS, WhatsApp, and voice.
21
+
22
+ Required config:
23
+ - account_sid: Twilio account SID
24
+ - auth_token: Twilio auth token
25
+ Optional config:
26
+ - from_number: Default from phone number
27
+ """
28
+
29
+ def __init__(self, config: dict) -> None:
30
+ self.config = config
31
+ self.account_sid = config.get("account_sid", "")
32
+ self.auth_token = config.get("auth_token", "")
33
+ self.from_number = config.get("from_number", "")
34
+
35
+ if config.get("enabled", False):
36
+ if not self.account_sid or not self.auth_token:
37
+ raise ValueError(
38
+ "TwilioConnector requires account_sid and auth_token"
39
+ )
40
+
41
+ self._client: httpx.AsyncClient | None = None
42
+
43
+ @property
44
+ def info(self) -> ConnectorInfo:
45
+ return ConnectorInfo(
46
+ connector_id="twilio",
47
+ platform="twilio",
48
+ category="communication",
49
+ capabilities=frozenset(
50
+ [
51
+ ConnectorCapability.SEND_MESSAGE,
52
+ ConnectorCapability.RECEIVE_MESSAGE,
53
+ ConnectorCapability.ACTION,
54
+ ConnectorCapability.WEBHOOKS,
55
+ ]
56
+ ),
57
+ auth_type="api_key",
58
+ rate_limit_per_minute=100,
59
+ )
60
+
61
+ @property
62
+ def _base_url(self) -> str:
63
+ return f"https://api.twilio.com/2010-04-01/Accounts/{self.account_sid}"
64
+
65
+ async def _get_client(self) -> httpx.AsyncClient:
66
+ if self._client is None:
67
+ self._client = httpx.AsyncClient(
68
+ timeout=30.0,
69
+ auth=(self.account_sid, self.auth_token),
70
+ )
71
+ return self._client
72
+
73
+ async def health(self) -> bool:
74
+ """Check if Twilio API is accessible."""
75
+ try:
76
+ client = await self._get_client()
77
+ response = await client.get(f"{self._base_url}.json")
78
+ return response.status_code == 200
79
+ except Exception as e:
80
+ logger.error(f"Twilio health check failed: {e}")
81
+ return False
82
+
83
+ async def send_sms(
84
+ self,
85
+ to: str,
86
+ body: str,
87
+ from_: str | None = None,
88
+ ) -> dict:
89
+ """Send an SMS message."""
90
+ client = await self._get_client()
91
+
92
+ data = {
93
+ "To": to,
94
+ "Body": body,
95
+ "From": from_ or self.from_number,
96
+ }
97
+
98
+ response = await client.post(
99
+ f"{self._base_url}/Messages.json",
100
+ data=data,
101
+ )
102
+
103
+ if response.status_code not in (200, 201):
104
+ raise Exception(f"Twilio send_sms failed: {response.text}")
105
+
106
+ return response.json()
107
+
108
+ async def send_whatsapp(
109
+ self,
110
+ to: str,
111
+ body: str,
112
+ from_: str | None = None,
113
+ ) -> dict:
114
+ """Send a WhatsApp message via Twilio."""
115
+ client = await self._get_client()
116
+
117
+ # Add whatsapp: prefix if not present
118
+ if not to.startswith("whatsapp:"):
119
+ to = f"whatsapp:{to}"
120
+
121
+ from_num = from_ or self.from_number
122
+ if not from_num.startswith("whatsapp:"):
123
+ from_num = f"whatsapp:{from_num}"
124
+
125
+ data = {
126
+ "To": to,
127
+ "Body": body,
128
+ "From": from_num,
129
+ }
130
+
131
+ response = await client.post(
132
+ f"{self._base_url}/Messages.json",
133
+ data=data,
134
+ )
135
+
136
+ if response.status_code not in (200, 201):
137
+ raise Exception(f"Twilio send_whatsapp failed: {response.text}")
138
+
139
+ return response.json()
140
+
141
+ async def make_call(
142
+ self,
143
+ to: str,
144
+ from_: str,
145
+ url: str,
146
+ ) -> dict:
147
+ """Make a voice call."""
148
+ client = await self._get_client()
149
+
150
+ data = {
151
+ "To": to,
152
+ "From": from_,
153
+ "Url": url,
154
+ }
155
+
156
+ response = await client.post(
157
+ f"{self._base_url}/Calls.json",
158
+ data=data,
159
+ )
160
+
161
+ if response.status_code not in (200, 201):
162
+ raise Exception(f"Twilio make_call failed: {response.text}")
163
+
164
+ return response.json()
165
+
166
+ async def send_mms(
167
+ self,
168
+ to: str,
169
+ body: str,
170
+ media_url: str,
171
+ from_: str | None = None,
172
+ ) -> dict:
173
+ """Send an MMS message with media."""
174
+ client = await self._get_client()
175
+
176
+ data = {
177
+ "To": to,
178
+ "Body": body,
179
+ "From": from_ or self.from_number,
180
+ "MediaUrl": media_url,
181
+ }
182
+
183
+ response = await client.post(
184
+ f"{self._base_url}/Messages.json",
185
+ data=data,
186
+ )
187
+
188
+ if response.status_code not in (200, 201):
189
+ raise Exception(f"Twilio send_mms failed: {response.text}")
190
+
191
+ return response.json()
192
+
193
+ async def list_messages(self, limit: int = 20) -> dict:
194
+ """List recent messages."""
195
+ client = await self._get_client()
196
+
197
+ response = await client.get(
198
+ f"{self._base_url}/Messages.json",
199
+ params={"PageSize": limit},
200
+ )
201
+
202
+ if response.status_code != 200:
203
+ raise Exception(f"Twilio list_messages failed: {response.text}")
204
+
205
+ return response.json()
206
+
207
+ async def get_message(self, message_sid: str) -> dict:
208
+ """Get a specific message."""
209
+ client = await self._get_client()
210
+
211
+ response = await client.get(
212
+ f"{self._base_url}/Messages/{message_sid}.json"
213
+ )
214
+
215
+ if response.status_code != 200:
216
+ raise Exception(f"Twilio get_message failed: {response.text}")
217
+
218
+ return response.json()
219
+
220
+ async def list_calls(self, limit: int = 20) -> dict:
221
+ """List recent calls."""
222
+ client = await self._get_client()
223
+
224
+ response = await client.get(
225
+ f"{self._base_url}/Calls.json",
226
+ params={"PageSize": limit},
227
+ )
228
+
229
+ if response.status_code != 200:
230
+ raise Exception(f"Twilio list_calls failed: {response.text}")
231
+
232
+ return response.json()
233
+
234
+ async def _read_impl(self, resource_id: str, **kwargs) -> dict:
235
+ """Read a message."""
236
+ return await self.get_message(resource_id)
237
+
238
+ async def _list_impl(
239
+ self, resource_type: str, filters: dict, **kwargs
240
+ ) -> list[dict]:
241
+ """List resources."""
242
+ limit = filters.get("limit", 20)
243
+ if resource_type == "messages":
244
+ result = await self.list_messages(limit)
245
+ return result.get("messages", [])
246
+ elif resource_type == "calls":
247
+ result = await self.list_calls(limit)
248
+ return result.get("calls", [])
249
+ return []
250
+
251
+ async def _action_impl(self, action_name: str, params: dict) -> dict:
252
+ """Execute Twilio-specific actions."""
253
+ if action_name == "send_sms":
254
+ return await self.send_sms(
255
+ to=params.get("to"),
256
+ body=params.get("body", ""),
257
+ from_=params.get("from"),
258
+ )
259
+ elif action_name == "send_whatsapp":
260
+ return await self.send_whatsapp(
261
+ to=params.get("to"),
262
+ body=params.get("body", ""),
263
+ from_=params.get("from"),
264
+ )
265
+ elif action_name == "make_call":
266
+ return await self.make_call(
267
+ to=params.get("to"),
268
+ from_=params.get("from"),
269
+ url=params.get("url"),
270
+ )
271
+ elif action_name == "send_mms":
272
+ return await self.send_mms(
273
+ to=params.get("to"),
274
+ body=params.get("body", ""),
275
+ media_url=params.get("media_url"),
276
+ from_=params.get("from"),
277
+ )
278
+
279
+ raise ValueError(f"Unknown action: {action_name}")
@@ -0,0 +1,290 @@
1
+ """ZoomConnector — Zoom video conferencing integration."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import base64
6
+ import logging
7
+
8
+ import httpx
9
+
10
+ from integrations.base import (
11
+ BaseConnector,
12
+ ConnectorCapability,
13
+ ConnectorInfo,
14
+ )
15
+
16
+ logger = logging.getLogger("hyperclaw.integrations.zoom")
17
+
18
+
19
+ class ZoomConnector(BaseConnector):
20
+ """
21
+ Zoom video conferencing connector.
22
+
23
+ Required config:
24
+ - account_id: Zoom account ID
25
+ - client_id: OAuth2 client ID
26
+ - client_secret: OAuth2 client secret
27
+ """
28
+
29
+ BASE_URL = "https://api.zoom.us/v2"
30
+ OAUTH_URL = "https://zoom.us/oauth/token"
31
+
32
+ def __init__(self, config: dict) -> None:
33
+ self.config = config
34
+ self.account_id = config.get("account_id", "")
35
+ self.client_id = config.get("client_id", "")
36
+ self.client_secret = config.get("client_secret", "")
37
+ self._access_token: str | None = None
38
+
39
+ if config.get("enabled", False):
40
+ if not all([self.account_id, self.client_id, self.client_secret]):
41
+ raise ValueError(
42
+ "ZoomConnector requires account_id, client_id, and client_secret"
43
+ )
44
+
45
+ self._client: httpx.AsyncClient | None = None
46
+
47
+ @property
48
+ def info(self) -> ConnectorInfo:
49
+ return ConnectorInfo(
50
+ connector_id="zoom",
51
+ platform="zoom",
52
+ category="communication",
53
+ capabilities=frozenset(
54
+ [
55
+ ConnectorCapability.READ_DATA,
56
+ ConnectorCapability.WRITE_DATA,
57
+ ConnectorCapability.LIST_DATA,
58
+ ConnectorCapability.ACTION,
59
+ ConnectorCapability.WEBHOOKS,
60
+ ]
61
+ ),
62
+ auth_type="oauth2",
63
+ rate_limit_per_minute=100,
64
+ )
65
+
66
+ async def _get_access_token(self) -> str:
67
+ """Get OAuth2 access token using Server-to-Server OAuth."""
68
+ if self._access_token:
69
+ return self._access_token
70
+
71
+ credentials = base64.b64encode(
72
+ f"{self.client_id}:{self.client_secret}".encode()
73
+ ).decode()
74
+
75
+ async with httpx.AsyncClient(timeout=30.0) as client:
76
+ response = await client.post(
77
+ self.OAUTH_URL,
78
+ headers={
79
+ "Authorization": f"Basic {credentials}",
80
+ "Content-Type": "application/x-www-form-urlencoded",
81
+ },
82
+ data={
83
+ "grant_type": "account_credentials",
84
+ "account_id": self.account_id,
85
+ },
86
+ )
87
+
88
+ if response.status_code != 200:
89
+ raise Exception(f"Zoom OAuth failed: {response.text}")
90
+
91
+ data = response.json()
92
+ self._access_token = data.get("access_token", "")
93
+ return self._access_token
94
+
95
+ async def _get_client(self) -> httpx.AsyncClient:
96
+ if self._client is None:
97
+ token = await self._get_access_token()
98
+ self._client = httpx.AsyncClient(
99
+ timeout=30.0,
100
+ headers={
101
+ "Authorization": f"Bearer {token}",
102
+ "Content-Type": "application/json",
103
+ },
104
+ )
105
+ return self._client
106
+
107
+ async def health(self) -> bool:
108
+ """Check if Zoom API is accessible."""
109
+ try:
110
+ client = await self._get_client()
111
+ response = await client.get(f"{self.BASE_URL}/users/me")
112
+ return response.status_code == 200
113
+ except Exception as e:
114
+ logger.error(f"Zoom health check failed: {e}")
115
+ return False
116
+
117
+ async def list_meetings(self, user_id: str = "me") -> dict:
118
+ """List meetings for a user."""
119
+ client = await self._get_client()
120
+
121
+ response = await client.get(f"{self.BASE_URL}/users/{user_id}/meetings")
122
+
123
+ if response.status_code != 200:
124
+ raise Exception(f"Zoom list_meetings failed: {response.text}")
125
+
126
+ return response.json()
127
+
128
+ async def create_meeting(
129
+ self,
130
+ topic: str,
131
+ start_time: str,
132
+ duration: int = 60,
133
+ agenda: str = "",
134
+ ) -> dict:
135
+ """Create a meeting."""
136
+ client = await self._get_client()
137
+
138
+ payload = {
139
+ "topic": topic,
140
+ "type": 2, # Scheduled meeting
141
+ "start_time": start_time,
142
+ "duration": duration,
143
+ "timezone": "UTC",
144
+ }
145
+ if agenda:
146
+ payload["agenda"] = agenda
147
+
148
+ response = await client.post(
149
+ f"{self.BASE_URL}/users/me/meetings",
150
+ json=payload,
151
+ )
152
+
153
+ if response.status_code not in (200, 201):
154
+ raise Exception(f"Zoom create_meeting failed: {response.text}")
155
+
156
+ return response.json()
157
+
158
+ async def get_meeting(self, meeting_id: str) -> dict:
159
+ """Get meeting details."""
160
+ client = await self._get_client()
161
+
162
+ response = await client.get(f"{self.BASE_URL}/meetings/{meeting_id}")
163
+
164
+ if response.status_code != 200:
165
+ raise Exception(f"Zoom get_meeting failed: {response.text}")
166
+
167
+ return response.json()
168
+
169
+ async def list_recordings(self, user_id: str = "me") -> dict:
170
+ """List recordings for a user."""
171
+ client = await self._get_client()
172
+
173
+ response = await client.get(f"{self.BASE_URL}/users/{user_id}/recordings")
174
+
175
+ if response.status_code != 200:
176
+ raise Exception(f"Zoom list_recordings failed: {response.text}")
177
+
178
+ return response.json()
179
+
180
+ async def get_transcript(self, meeting_id: str) -> dict:
181
+ """Get transcript for a meeting recording."""
182
+ client = await self._get_client()
183
+
184
+ # First get the recording details
185
+ response = await client.get(
186
+ f"{self.BASE_URL}/meetings/{meeting_id}/recordings"
187
+ )
188
+
189
+ if response.status_code != 200:
190
+ raise Exception(f"Zoom get_transcript failed: {response.text}")
191
+
192
+ data = response.json()
193
+
194
+ # Find VTT transcript file
195
+ for file in data.get("recording_files", []):
196
+ if file.get("file_type") == "TRANSCRIPT":
197
+ return file
198
+
199
+ return data
200
+
201
+ async def list_webinars(self, user_id: str = "me") -> dict:
202
+ """List webinars for a user."""
203
+ client = await self._get_client()
204
+
205
+ response = await client.get(f"{self.BASE_URL}/users/{user_id}/webinars")
206
+
207
+ if response.status_code != 200:
208
+ raise Exception(f"Zoom list_webinars failed: {response.text}")
209
+
210
+ return response.json()
211
+
212
+ async def send_chat_message(
213
+ self,
214
+ to_jid: str,
215
+ message: str,
216
+ channel_id: str | None = None,
217
+ ) -> dict:
218
+ """Send a chat message."""
219
+ client = await self._get_client()
220
+
221
+ payload = {"message": message}
222
+ if channel_id:
223
+ payload["to_channel"] = channel_id
224
+ else:
225
+ payload["to_jid"] = to_jid
226
+
227
+ response = await client.post(
228
+ f"{self.BASE_URL}/chat/users/me/messages",
229
+ json=payload,
230
+ )
231
+
232
+ if response.status_code not in (200, 201):
233
+ raise Exception(f"Zoom send_chat_message failed: {response.text}")
234
+
235
+ return response.json()
236
+
237
+ async def _read_impl(self, resource_id: str, **kwargs) -> dict:
238
+ """Read meeting details."""
239
+ return await self.get_meeting(resource_id)
240
+
241
+ async def _write_impl(self, resource_type: str, data: dict, **kwargs) -> dict:
242
+ """Create a meeting."""
243
+ if resource_type == "meeting":
244
+ return await self.create_meeting(
245
+ topic=data.get("topic", "Meeting"),
246
+ start_time=data.get("start_time"),
247
+ duration=data.get("duration", 60),
248
+ agenda=data.get("agenda", ""),
249
+ )
250
+ elif resource_type == "chat_message":
251
+ return await self.send_chat_message(
252
+ to_jid=data.get("to_jid", ""),
253
+ message=data.get("message", ""),
254
+ channel_id=data.get("channel_id"),
255
+ )
256
+ raise ValueError(f"Unknown resource type: {resource_type}")
257
+
258
+ async def _list_impl(
259
+ self, resource_type: str, filters: dict, **kwargs
260
+ ) -> list[dict]:
261
+ """List resources."""
262
+ user_id = filters.get("user_id", "me")
263
+ if resource_type == "meetings":
264
+ result = await self.list_meetings(user_id)
265
+ return result.get("meetings", [])
266
+ elif resource_type == "recordings":
267
+ result = await self.list_recordings(user_id)
268
+ return result.get("meetings", [])
269
+ elif resource_type == "webinars":
270
+ result = await self.list_webinars(user_id)
271
+ return result.get("webinars", [])
272
+ return []
273
+
274
+ async def _action_impl(self, action_name: str, params: dict) -> dict:
275
+ """Execute Zoom-specific actions."""
276
+ if action_name == "create_meeting":
277
+ return await self.create_meeting(
278
+ topic=params.get("topic", "Meeting"),
279
+ start_time=params.get("start_time"),
280
+ duration=params.get("duration", 60),
281
+ agenda=params.get("agenda", ""),
282
+ )
283
+ elif action_name == "send_chat":
284
+ return await self.send_chat_message(
285
+ to_jid=params.get("to_jid", ""),
286
+ message=params.get("message", ""),
287
+ channel_id=params.get("channel_id"),
288
+ )
289
+
290
+ raise ValueError(f"Unknown action: {action_name}")
@@ -0,0 +1 @@
1
+ # Data integrations
@@ -0,0 +1,135 @@
1
+ """GoogleAnalyticsConnector — Google Analytics Data API integration."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+
7
+ import httpx
8
+
9
+ from integrations.base import (
10
+ BaseConnector,
11
+ ConnectorCapability,
12
+ ConnectorInfo,
13
+ )
14
+ from integrations.productivity.google.base import GoogleOAuthBase
15
+
16
+ logger = logging.getLogger("hyperclaw.integrations.google_analytics")
17
+
18
+
19
+ class GoogleAnalyticsConnector(GoogleOAuthBase, BaseConnector):
20
+ """
21
+ Google Analytics Data API connector.
22
+
23
+ Required config:
24
+ - service_account_file: Path to service account JSON
25
+ OR
26
+ - access_token: OAuth2 access token
27
+ """
28
+
29
+ DATA_API_URL = "https://analyticsdata.googleapis.com/v1beta"
30
+ ADMIN_API_URL = "https://analyticsadmin.googleapis.com/v1alpha"
31
+
32
+ def __init__(self, config: dict) -> None:
33
+ self.config = config
34
+ self._init_google_auth(config)
35
+
36
+ if config.get("enabled", False):
37
+ self._validate_google_config(config)
38
+
39
+ @property
40
+ def info(self) -> ConnectorInfo:
41
+ return ConnectorInfo(
42
+ connector_id="google_analytics",
43
+ platform="google_analytics",
44
+ category="data",
45
+ capabilities=frozenset(
46
+ [
47
+ ConnectorCapability.READ_DATA,
48
+ ConnectorCapability.LIST_DATA,
49
+ ]
50
+ ),
51
+ auth_type="oauth2",
52
+ rate_limit_per_minute=60,
53
+ )
54
+
55
+ async def health(self) -> bool:
56
+ """Check if Google Analytics API is accessible."""
57
+ return True
58
+
59
+ async def run_report(
60
+ self,
61
+ property_id: str,
62
+ dimensions: list[str],
63
+ metrics: list[str],
64
+ date_range: dict,
65
+ ) -> dict:
66
+ """Run a Google Analytics report."""
67
+ client = await self._get_google_client()
68
+
69
+ payload = {
70
+ "dimensions": [{"name": d} for d in dimensions],
71
+ "metrics": [{"name": m} for m in metrics],
72
+ "dateRanges": [date_range],
73
+ }
74
+
75
+ response = await client.post(
76
+ f"{self.DATA_API_URL}/properties/{property_id}:runReport",
77
+ json=payload,
78
+ )
79
+
80
+ if response.status_code != 200:
81
+ raise Exception(f"Google Analytics run_report failed: {response.text}")
82
+
83
+ return response.json()
84
+
85
+ async def get_realtime(self, property_id: str) -> dict:
86
+ """Get realtime analytics data."""
87
+ client = await self._get_google_client()
88
+
89
+ payload = {
90
+ "dimensions": [{"name": "country"}],
91
+ "metrics": [{"name": "activeUsers"}],
92
+ }
93
+
94
+ response = await client.post(
95
+ f"{self.DATA_API_URL}/properties/{property_id}:runRealtimeReport",
96
+ json=payload,
97
+ )
98
+
99
+ if response.status_code != 200:
100
+ raise Exception(f"Google Analytics get_realtime failed: {response.text}")
101
+
102
+ return response.json()
103
+
104
+ async def list_properties(self) -> dict:
105
+ """List all Google Analytics properties."""
106
+ client = await self._get_google_client()
107
+
108
+ response = await client.get(f"{self.ADMIN_API_URL}/accountSummaries")
109
+
110
+ if response.status_code != 200:
111
+ raise Exception(
112
+ f"Google Analytics list_properties failed: {response.text}"
113
+ )
114
+
115
+ return response.json()
116
+
117
+ async def _read_impl(self, resource_id: str, **kwargs) -> dict:
118
+ """Read analytics data for a property."""
119
+ dimensions = kwargs.get("dimensions", ["date"])
120
+ metrics = kwargs.get("metrics", ["sessions"])
121
+ date_range = kwargs.get("date_range", {
122
+ "startDate": "30daysAgo",
123
+ "endDate": "today",
124
+ })
125
+
126
+ return await self.run_report(resource_id, dimensions, metrics, date_range)
127
+
128
+ async def _list_impl(
129
+ self, resource_type: str, filters: dict, **kwargs
130
+ ) -> list[dict]:
131
+ """List properties."""
132
+ if resource_type == "properties":
133
+ result = await self.list_properties()
134
+ return result.get("accountSummaries", [])
135
+ return []