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,260 @@
1
+ """TelegramConnector — Telegram Bot API integration."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from typing import AsyncIterator
7
+
8
+ import httpx
9
+
10
+ from integrations.base import (
11
+ BaseConnector,
12
+ ConnectorCapability,
13
+ ConnectorInfo,
14
+ InboundMessage,
15
+ OutboundMessage,
16
+ )
17
+
18
+ logger = logging.getLogger("hyperclaw.integrations.telegram")
19
+
20
+
21
+ from hyperclaw.tools.egress import telegram_payload # noqa: E402 (link previews off)
22
+
23
+
24
+ class TelegramConnector(BaseConnector):
25
+ """
26
+ Telegram Bot API connector.
27
+
28
+ Required config:
29
+ - bot_token: Telegram Bot API token
30
+ """
31
+
32
+ BASE_URL = "https://api.telegram.org"
33
+
34
+ def __init__(self, config: dict) -> None:
35
+ self.config = config
36
+ self.bot_token = config.get("bot_token", "")
37
+
38
+ if config.get("enabled", False) and not self.bot_token:
39
+ raise ValueError("TelegramConnector requires bot_token")
40
+
41
+ self._last_update_id = 0
42
+ self._client: httpx.AsyncClient | None = None
43
+
44
+ @property
45
+ def info(self) -> ConnectorInfo:
46
+ return ConnectorInfo(
47
+ connector_id="telegram",
48
+ platform="telegram",
49
+ category="messaging",
50
+ capabilities=frozenset(
51
+ [
52
+ ConnectorCapability.SEND_MESSAGE,
53
+ ConnectorCapability.RECEIVE_MESSAGE,
54
+ ConnectorCapability.FILE_UPLOAD,
55
+ ConnectorCapability.FILE_DOWNLOAD,
56
+ ConnectorCapability.WEBHOOKS,
57
+ ConnectorCapability.NOTIFY,
58
+ ConnectorCapability.ACTION,
59
+ ]
60
+ ),
61
+ auth_type="api_key",
62
+ rate_limit_per_minute=30,
63
+ supports_threads=True,
64
+ supports_attachments=True,
65
+ supports_reactions=True,
66
+ )
67
+
68
+ async def _get_client(self) -> httpx.AsyncClient:
69
+ if self._client is None:
70
+ self._client = httpx.AsyncClient(timeout=30.0)
71
+ return self._client
72
+
73
+ def _api_url(self, method: str) -> str:
74
+ return f"{self.BASE_URL}/bot{self.bot_token}/{method}"
75
+
76
+ async def health(self) -> bool:
77
+ """Check if bot token is valid."""
78
+ try:
79
+ client = await self._get_client()
80
+ response = await client.get(self._api_url("getMe"))
81
+ data = response.json()
82
+ return data.get("ok", False)
83
+ except Exception as e:
84
+ logger.error(f"Telegram health check failed: {e}")
85
+ return False
86
+
87
+ async def _send_impl(self, message: OutboundMessage) -> str:
88
+ """Send a message via Telegram."""
89
+ client = await self._get_client()
90
+
91
+ payload = {
92
+ "chat_id": message.recipient_id,
93
+ "text": message.content,
94
+ }
95
+
96
+ if message.thread_id:
97
+ payload["reply_to_message_id"] = message.thread_id
98
+
99
+ if message.format == "markdown":
100
+ payload["parse_mode"] = "MarkdownV2"
101
+ elif message.format == "html":
102
+ payload["parse_mode"] = "HTML"
103
+
104
+ response = await client.post(self._api_url("sendMessage"), json=telegram_payload(payload))
105
+ data = response.json()
106
+
107
+ if not data.get("ok"):
108
+ raise Exception(f"Telegram send failed: {data.get('description')}")
109
+
110
+ return str(data["result"]["message_id"])
111
+
112
+ async def _receive_impl(self) -> AsyncIterator[InboundMessage]:
113
+ """Poll for updates using long polling."""
114
+ client = await self._get_client()
115
+
116
+ params = {
117
+ "offset": self._last_update_id + 1,
118
+ "timeout": 30,
119
+ "allowed_updates": ["message"],
120
+ }
121
+
122
+ response = await client.get(self._api_url("getUpdates"), params=params)
123
+ data = response.json()
124
+
125
+ if not data.get("ok"):
126
+ return
127
+
128
+ for update in data.get("result", []):
129
+ self._last_update_id = update["update_id"]
130
+
131
+ msg = update.get("message", {})
132
+ if not msg:
133
+ continue
134
+
135
+ sender = msg.get("from", {})
136
+ attachments = []
137
+
138
+ # Handle attachments
139
+ for attach_type in ["photo", "document", "video", "audio", "voice"]:
140
+ if attach_type in msg:
141
+ attach_data = msg[attach_type]
142
+ if isinstance(attach_data, list):
143
+ attach_data = attach_data[-1] # Get largest photo
144
+ attachments.append({"type": attach_type, "data": attach_data})
145
+
146
+ yield InboundMessage(
147
+ message_id=str(msg.get("message_id", "")),
148
+ platform="telegram",
149
+ sender_id=str(sender.get("id", "")),
150
+ sender_name=sender.get("first_name", "")
151
+ + " "
152
+ + sender.get("last_name", ""),
153
+ content=msg.get("text", "") or msg.get("caption", ""),
154
+ thread_id=str(msg.get("reply_to_message", {}).get("message_id", ""))
155
+ or None,
156
+ channel_id=str(msg.get("chat", {}).get("id", "")),
157
+ attachments=attachments,
158
+ raw=msg,
159
+ )
160
+
161
+ async def send_photo(
162
+ self,
163
+ chat_id: str,
164
+ photo_url: str,
165
+ caption: str = "",
166
+ ) -> str:
167
+ """Send a photo to a chat."""
168
+ client = await self._get_client()
169
+
170
+ payload = {
171
+ "chat_id": chat_id,
172
+ "photo": photo_url,
173
+ }
174
+ if caption:
175
+ payload["caption"] = caption
176
+
177
+ response = await client.post(self._api_url("sendPhoto"), json=payload)
178
+ data = response.json()
179
+
180
+ if not data.get("ok"):
181
+ raise Exception(f"Telegram sendPhoto failed: {data.get('description')}")
182
+
183
+ return str(data["result"]["message_id"])
184
+
185
+ async def send_document(
186
+ self,
187
+ chat_id: str,
188
+ file_path: str,
189
+ caption: str = "",
190
+ ) -> str:
191
+ """Send a document to a chat."""
192
+ client = await self._get_client()
193
+
194
+ with open(file_path, "rb") as f:
195
+ files = {"document": f}
196
+ data = {"chat_id": chat_id}
197
+ if caption:
198
+ data["caption"] = caption
199
+
200
+ response = await client.post(
201
+ self._api_url("sendDocument"),
202
+ data=data,
203
+ files=files,
204
+ )
205
+
206
+ result = response.json()
207
+
208
+ if not result.get("ok"):
209
+ raise Exception(f"Telegram sendDocument failed: {result.get('description')}")
210
+
211
+ return str(result["result"]["message_id"])
212
+
213
+ async def send_keyboard(
214
+ self,
215
+ chat_id: str,
216
+ text: str,
217
+ buttons: list[list[str]],
218
+ ) -> str:
219
+ """Send a message with an inline keyboard."""
220
+ client = await self._get_client()
221
+
222
+ keyboard = {
223
+ "inline_keyboard": [
224
+ [{"text": btn, "callback_data": btn} for btn in row] for row in buttons
225
+ ]
226
+ }
227
+
228
+ payload = {
229
+ "chat_id": chat_id,
230
+ "text": text,
231
+ "reply_markup": keyboard,
232
+ }
233
+
234
+ response = await client.post(self._api_url("sendMessage"), json=telegram_payload(payload))
235
+ data = response.json()
236
+
237
+ if not data.get("ok"):
238
+ raise Exception(f"Telegram sendKeyboard failed: {data.get('description')}")
239
+
240
+ return str(data["result"]["message_id"])
241
+
242
+ async def _action_impl(self, action_name: str, params: dict) -> dict:
243
+ """Execute Telegram-specific actions."""
244
+ client = await self._get_client()
245
+
246
+ if action_name == "send_chat_action":
247
+ response = await client.post(
248
+ self._api_url("sendChatAction"),
249
+ json={"chat_id": params["chat_id"], "action": params.get("action", "typing")},
250
+ )
251
+ return response.json()
252
+
253
+ elif action_name == "get_chat":
254
+ response = await client.get(
255
+ self._api_url("getChat"),
256
+ params={"chat_id": params["chat_id"]},
257
+ )
258
+ return response.json()
259
+
260
+ raise ValueError(f"Unknown action: {action_name}")
@@ -0,0 +1,203 @@
1
+ """WhatsAppConnector — Meta WhatsApp Business API integration."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from typing import AsyncIterator
7
+
8
+ import httpx
9
+
10
+ from integrations.base import (
11
+ BaseConnector,
12
+ ConnectorCapability,
13
+ ConnectorInfo,
14
+ InboundMessage,
15
+ OutboundMessage,
16
+ )
17
+
18
+ logger = logging.getLogger("hyperclaw.integrations.whatsapp")
19
+
20
+
21
+ class WhatsAppConnector(BaseConnector):
22
+ """
23
+ WhatsApp Business API connector via Meta Graph API.
24
+
25
+ Required config:
26
+ - access_token: Meta Graph API access token
27
+ - phone_number_id: WhatsApp Business phone number ID
28
+
29
+ Note: This connector handles health data and sets hypershield_context accordingly.
30
+ """
31
+
32
+ BASE_URL = "https://graph.facebook.com/v17.0"
33
+
34
+ # HyperShield context for health data protection
35
+ hypershield_context = "health_data"
36
+
37
+ def __init__(self, config: dict) -> None:
38
+ self.config = config
39
+ self.access_token = config.get("access_token", "")
40
+ self.phone_number_id = config.get("phone_number_id", "")
41
+
42
+ if config.get("enabled", False):
43
+ if not self.access_token:
44
+ raise ValueError("WhatsAppConnector requires access_token")
45
+ if not self.phone_number_id:
46
+ raise ValueError("WhatsAppConnector requires phone_number_id")
47
+
48
+ self._client: httpx.AsyncClient | None = None
49
+
50
+ @property
51
+ def info(self) -> ConnectorInfo:
52
+ return ConnectorInfo(
53
+ connector_id="whatsapp",
54
+ platform="whatsapp",
55
+ category="messaging",
56
+ capabilities=frozenset(
57
+ [
58
+ ConnectorCapability.SEND_MESSAGE,
59
+ ConnectorCapability.RECEIVE_MESSAGE,
60
+ ConnectorCapability.FILE_UPLOAD,
61
+ ConnectorCapability.FILE_DOWNLOAD,
62
+ ConnectorCapability.WEBHOOKS,
63
+ ]
64
+ ),
65
+ auth_type="bearer",
66
+ rate_limit_per_minute=80,
67
+ supports_threads=False,
68
+ supports_attachments=True,
69
+ supports_reactions=True,
70
+ )
71
+
72
+ async def _get_client(self) -> httpx.AsyncClient:
73
+ if self._client is None:
74
+ self._client = httpx.AsyncClient(
75
+ timeout=30.0,
76
+ headers={
77
+ "Authorization": f"Bearer {self.access_token}",
78
+ "Content-Type": "application/json",
79
+ },
80
+ )
81
+ return self._client
82
+
83
+ async def health(self) -> bool:
84
+ """Check if credentials are valid."""
85
+ try:
86
+ client = await self._get_client()
87
+ response = await client.get(
88
+ f"{self.BASE_URL}/{self.phone_number_id}",
89
+ )
90
+ return response.status_code == 200
91
+ except Exception as e:
92
+ logger.error(f"WhatsApp health check failed: {e}")
93
+ return False
94
+
95
+ async def _send_impl(self, message: OutboundMessage) -> str:
96
+ """Send a text message via WhatsApp."""
97
+ client = await self._get_client()
98
+
99
+ payload = {
100
+ "messaging_product": "whatsapp",
101
+ "recipient_type": "individual",
102
+ "to": message.recipient_id,
103
+ "type": "text",
104
+ "text": {"body": message.content},
105
+ }
106
+
107
+ response = await client.post(
108
+ f"{self.BASE_URL}/{self.phone_number_id}/messages",
109
+ json=payload,
110
+ )
111
+
112
+ if response.status_code not in (200, 201):
113
+ raise Exception(f"WhatsApp send failed: {response.text}")
114
+
115
+ data = response.json()
116
+ messages = data.get("messages", [])
117
+ return messages[0].get("id", "") if messages else ""
118
+
119
+ async def _receive_impl(self) -> AsyncIterator[InboundMessage]:
120
+ """
121
+ Receive messages. WhatsApp uses webhooks for incoming messages.
122
+ This is a stub; implement webhook handler separately.
123
+ """
124
+ return
125
+ yield
126
+
127
+ async def send_template(
128
+ self,
129
+ to: str,
130
+ template_name: str,
131
+ language_code: str = "en",
132
+ components: list[dict] | None = None,
133
+ ) -> str:
134
+ """Send a pre-approved message template."""
135
+ client = await self._get_client()
136
+
137
+ payload = {
138
+ "messaging_product": "whatsapp",
139
+ "recipient_type": "individual",
140
+ "to": to,
141
+ "type": "template",
142
+ "template": {
143
+ "name": template_name,
144
+ "language": {"code": language_code},
145
+ },
146
+ }
147
+
148
+ if components:
149
+ payload["template"]["components"] = components
150
+
151
+ response = await client.post(
152
+ f"{self.BASE_URL}/{self.phone_number_id}/messages",
153
+ json=payload,
154
+ )
155
+
156
+ if response.status_code not in (200, 201):
157
+ raise Exception(f"WhatsApp send_template failed: {response.text}")
158
+
159
+ data = response.json()
160
+ messages = data.get("messages", [])
161
+ return messages[0].get("id", "") if messages else ""
162
+
163
+ async def send_media(
164
+ self,
165
+ to: str,
166
+ media_type: str,
167
+ url: str,
168
+ caption: str = "",
169
+ ) -> str:
170
+ """
171
+ Send media (image, video, audio, document).
172
+
173
+ Args:
174
+ to: Recipient phone number
175
+ media_type: One of "image", "video", "audio", "document"
176
+ url: Public URL of the media
177
+ caption: Optional caption (for image/video/document)
178
+ """
179
+ client = await self._get_client()
180
+
181
+ media_object = {"link": url}
182
+ if caption and media_type in ("image", "video", "document"):
183
+ media_object["caption"] = caption
184
+
185
+ payload = {
186
+ "messaging_product": "whatsapp",
187
+ "recipient_type": "individual",
188
+ "to": to,
189
+ "type": media_type,
190
+ media_type: media_object,
191
+ }
192
+
193
+ response = await client.post(
194
+ f"{self.BASE_URL}/{self.phone_number_id}/messages",
195
+ json=payload,
196
+ )
197
+
198
+ if response.status_code not in (200, 201):
199
+ raise Exception(f"WhatsApp send_media failed: {response.text}")
200
+
201
+ data = response.json()
202
+ messages = data.get("messages", [])
203
+ return messages[0].get("id", "") if messages else ""
@@ -0,0 +1,17 @@
1
+ """HyperClaw Productivity Connectors."""
2
+
3
+ from integrations.productivity.notion import NotionConnector
4
+ from integrations.productivity.airtable import AirtableConnector
5
+ from integrations.productivity.trello import TrelloConnector
6
+ from integrations.productivity.asana import AsanaConnector
7
+ from integrations.productivity.linear import LinearConnector
8
+ from integrations.productivity.todoist import TodoistConnector
9
+
10
+ __all__ = [
11
+ "NotionConnector",
12
+ "AirtableConnector",
13
+ "TrelloConnector",
14
+ "AsanaConnector",
15
+ "LinearConnector",
16
+ "TodoistConnector",
17
+ ]
@@ -0,0 +1,217 @@
1
+ """AirtableConnector — Airtable 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
+
15
+ logger = logging.getLogger("hyperclaw.integrations.airtable")
16
+
17
+
18
+ class AirtableConnector(BaseConnector):
19
+ """
20
+ Airtable API connector.
21
+
22
+ Required config:
23
+ - api_key: Airtable API key or Personal Access Token
24
+ - base_id: Airtable base ID
25
+ """
26
+
27
+ BASE_URL = "https://api.airtable.com/v0"
28
+
29
+ def __init__(self, config: dict) -> None:
30
+ self.config = config
31
+ self.api_key = config.get("api_key", "")
32
+ self.base_id = config.get("base_id", "")
33
+
34
+ if config.get("enabled", False):
35
+ if not self.api_key:
36
+ raise ValueError("AirtableConnector requires api_key")
37
+ if not self.base_id:
38
+ raise ValueError("AirtableConnector requires base_id")
39
+
40
+ self._client: httpx.AsyncClient | None = None
41
+
42
+ @property
43
+ def info(self) -> ConnectorInfo:
44
+ return ConnectorInfo(
45
+ connector_id="airtable",
46
+ platform="airtable",
47
+ category="productivity",
48
+ capabilities=frozenset(
49
+ [
50
+ ConnectorCapability.READ_DATA,
51
+ ConnectorCapability.WRITE_DATA,
52
+ ConnectorCapability.DELETE_DATA,
53
+ ConnectorCapability.LIST_DATA,
54
+ ConnectorCapability.SEARCH,
55
+ ]
56
+ ),
57
+ auth_type="bearer",
58
+ rate_limit_per_minute=100,
59
+ )
60
+
61
+ async def _get_client(self) -> httpx.AsyncClient:
62
+ if self._client is None:
63
+ self._client = httpx.AsyncClient(
64
+ timeout=30.0,
65
+ headers={
66
+ "Authorization": f"Bearer {self.api_key}",
67
+ "Content-Type": "application/json",
68
+ },
69
+ )
70
+ return self._client
71
+
72
+ async def health(self) -> bool:
73
+ """Check if Airtable API is accessible."""
74
+ try:
75
+ client = await self._get_client()
76
+ response = await client.get(f"{self.BASE_URL}/meta/bases")
77
+ return response.status_code == 200
78
+ except Exception as e:
79
+ logger.error(f"Airtable health check failed: {e}")
80
+ return False
81
+
82
+ async def list_records(
83
+ self,
84
+ table_name: str,
85
+ filter_formula: str | None = None,
86
+ sort: list[dict] | None = None,
87
+ max_records: int = 100,
88
+ ) -> list[dict]:
89
+ """List records in a table."""
90
+ client = await self._get_client()
91
+
92
+ params = {"maxRecords": max_records}
93
+ if filter_formula:
94
+ params["filterByFormula"] = filter_formula
95
+ if sort:
96
+ for i, s in enumerate(sort):
97
+ params[f"sort[{i}][field]"] = s.get("field")
98
+ params[f"sort[{i}][direction]"] = s.get("direction", "asc")
99
+
100
+ response = await client.get(
101
+ f"{self.BASE_URL}/{self.base_id}/{table_name}",
102
+ params=params,
103
+ )
104
+
105
+ if response.status_code != 200:
106
+ raise Exception(f"Airtable list records failed: {response.text}")
107
+
108
+ return response.json().get("records", [])
109
+
110
+ async def get_record(
111
+ self,
112
+ table_name: str,
113
+ record_id: str,
114
+ ) -> dict:
115
+ """Get a single record."""
116
+ client = await self._get_client()
117
+
118
+ response = await client.get(
119
+ f"{self.BASE_URL}/{self.base_id}/{table_name}/{record_id}"
120
+ )
121
+
122
+ if response.status_code != 200:
123
+ raise Exception(f"Airtable get record failed: {response.text}")
124
+
125
+ return response.json()
126
+
127
+ async def create_record(
128
+ self,
129
+ table_name: str,
130
+ fields: dict,
131
+ ) -> dict:
132
+ """Create a new record."""
133
+ client = await self._get_client()
134
+
135
+ response = await client.post(
136
+ f"{self.BASE_URL}/{self.base_id}/{table_name}",
137
+ json={"fields": fields},
138
+ )
139
+
140
+ if response.status_code not in (200, 201):
141
+ raise Exception(f"Airtable create record failed: {response.text}")
142
+
143
+ return response.json()
144
+
145
+ async def update_record(
146
+ self,
147
+ table_name: str,
148
+ record_id: str,
149
+ fields: dict,
150
+ ) -> dict:
151
+ """Update a record."""
152
+ client = await self._get_client()
153
+
154
+ response = await client.patch(
155
+ f"{self.BASE_URL}/{self.base_id}/{table_name}/{record_id}",
156
+ json={"fields": fields},
157
+ )
158
+
159
+ if response.status_code != 200:
160
+ raise Exception(f"Airtable update record failed: {response.text}")
161
+
162
+ return response.json()
163
+
164
+ async def delete_record(
165
+ self,
166
+ table_name: str,
167
+ record_id: str,
168
+ ) -> bool:
169
+ """Delete a record."""
170
+ client = await self._get_client()
171
+
172
+ response = await client.delete(
173
+ f"{self.BASE_URL}/{self.base_id}/{table_name}/{record_id}"
174
+ )
175
+
176
+ return response.status_code == 200
177
+
178
+ async def _read_impl(self, resource_id: str, **kwargs) -> dict:
179
+ """Read a record."""
180
+ return await self.get_record(
181
+ table_name=kwargs.get("table_name"),
182
+ record_id=resource_id,
183
+ )
184
+
185
+ async def _write_impl(self, resource_type: str, data: dict, **kwargs) -> dict:
186
+ """Create a record."""
187
+ return await self.create_record(
188
+ table_name=data.get("table_name"),
189
+ fields=data.get("fields", {}),
190
+ )
191
+
192
+ async def _delete_impl(self, resource_id: str, **kwargs) -> bool:
193
+ """Delete a record."""
194
+ return await self.delete_record(
195
+ table_name=kwargs.get("table_name"),
196
+ record_id=resource_id,
197
+ )
198
+
199
+ async def _list_impl(
200
+ self, resource_type: str, filters: dict, **kwargs
201
+ ) -> list[dict]:
202
+ """List records."""
203
+ return await self.list_records(
204
+ table_name=filters.get("table_name"),
205
+ filter_formula=filters.get("filter_formula"),
206
+ sort=filters.get("sort"),
207
+ max_records=filters.get("max_records", 100),
208
+ )
209
+
210
+ async def _search_impl(self, query: str, **kwargs) -> list[dict]:
211
+ """Search records using filter formula."""
212
+ table_name = kwargs.get("table_name")
213
+ field = kwargs.get("field", "Name")
214
+ return await self.list_records(
215
+ table_name=table_name,
216
+ filter_formula=f"SEARCH('{query}', {{{field}}})",
217
+ )