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,586 @@
1
+ """
2
+ Additional Messaging Platform Connectors.
3
+ Integration with Google Chat, Signal, Matrix, iMessage, LINE, IRC, WeChat, Mattermost.
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
+
13
+ import httpx
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ @dataclass
19
+ class ChatMessage:
20
+ """Chat message."""
21
+ id: str
22
+ text: str
23
+ sender: str | None = None
24
+ channel: str | None = None
25
+ timestamp: datetime | None = None
26
+ raw: dict = field(default_factory=dict)
27
+
28
+
29
+ class MessagingConnector(ABC):
30
+ """Abstract base for messaging connectors."""
31
+
32
+ name: str = "base"
33
+
34
+ @abstractmethod
35
+ async def send_message(self, channel: str, text: str, **kwargs) -> ChatMessage:
36
+ """Send a message."""
37
+ pass
38
+
39
+ @abstractmethod
40
+ async def get_messages(self, channel: str, limit: int = 50, **kwargs) -> list[ChatMessage]:
41
+ """Get recent messages from a channel."""
42
+ pass
43
+
44
+
45
+ # ═══════════════════════════════════════════════════════════════════════════════
46
+ # GOOGLE CHAT
47
+ # ═══════════════════════════════════════════════════════════════════════════════
48
+
49
+ class GoogleChatConnector(MessagingConnector):
50
+ """Google Chat connector."""
51
+
52
+ name = "google_chat"
53
+
54
+ def __init__(self, webhook_url: str | None = None, access_token: str | None = None):
55
+ self.webhook_url = webhook_url or os.environ.get("GOOGLE_CHAT_WEBHOOK")
56
+ self.access_token = access_token or os.environ.get("GOOGLE_CHAT_TOKEN")
57
+ self.base_url = "https://chat.googleapis.com/v1"
58
+
59
+ async def send_message(self, channel: str, text: str, **kwargs) -> ChatMessage:
60
+ # If webhook URL is provided, use that (simpler)
61
+ if self.webhook_url and not channel:
62
+ async with httpx.AsyncClient() as client:
63
+ response = await client.post(
64
+ self.webhook_url,
65
+ json={"text": text},
66
+ timeout=30.0,
67
+ )
68
+ response.raise_for_status()
69
+ data = response.json()
70
+ return ChatMessage(
71
+ id=data.get("name", ""),
72
+ text=text,
73
+ raw=data,
74
+ )
75
+
76
+ # Otherwise use the full API
77
+ async with httpx.AsyncClient() as client:
78
+ response = await client.post(
79
+ f"{self.base_url}/{channel}/messages",
80
+ headers={"Authorization": f"Bearer {self.access_token}"},
81
+ json={"text": text},
82
+ timeout=30.0,
83
+ )
84
+ response.raise_for_status()
85
+ data = response.json()
86
+
87
+ return ChatMessage(
88
+ id=data["name"],
89
+ text=text,
90
+ sender=data.get("sender", {}).get("displayName"),
91
+ channel=channel,
92
+ raw=data,
93
+ )
94
+
95
+ async def get_messages(self, channel: str, limit: int = 50, **kwargs) -> list[ChatMessage]:
96
+ async with httpx.AsyncClient() as client:
97
+ response = await client.get(
98
+ f"{self.base_url}/{channel}/messages",
99
+ headers={"Authorization": f"Bearer {self.access_token}"},
100
+ params={"pageSize": limit},
101
+ timeout=30.0,
102
+ )
103
+ response.raise_for_status()
104
+ data = response.json()
105
+
106
+ return [
107
+ ChatMessage(
108
+ id=msg["name"],
109
+ text=msg.get("text", ""),
110
+ sender=msg.get("sender", {}).get("displayName"),
111
+ channel=channel,
112
+ timestamp=datetime.fromisoformat(msg["createTime"].replace("Z", "+00:00"))
113
+ if msg.get("createTime") else None,
114
+ raw=msg,
115
+ )
116
+ for msg in data.get("messages", [])
117
+ ]
118
+
119
+
120
+ # ═══════════════════════════════════════════════════════════════════════════════
121
+ # SIGNAL (via signal-cli-rest-api)
122
+ # ═══════════════════════════════════════════════════════════════════════════════
123
+
124
+ class SignalConnector(MessagingConnector):
125
+ """Signal messenger connector (via signal-cli-rest-api)."""
126
+
127
+ name = "signal"
128
+
129
+ def __init__(
130
+ self,
131
+ api_url: str | None = None,
132
+ phone_number: str | None = None,
133
+ ):
134
+ self.api_url = (api_url or os.environ.get("SIGNAL_API_URL", "http://localhost:8080")).rstrip("/")
135
+ self.phone_number = phone_number or os.environ.get("SIGNAL_PHONE_NUMBER")
136
+
137
+ async def send_message(self, channel: str, text: str, **kwargs) -> ChatMessage:
138
+ """Send message to a phone number or group."""
139
+ async with httpx.AsyncClient() as client:
140
+ payload = {
141
+ "message": text,
142
+ "number": self.phone_number,
143
+ }
144
+
145
+ if channel.startswith("group."):
146
+ payload["recipients"] = [channel]
147
+ else:
148
+ payload["recipients"] = [channel]
149
+
150
+ response = await client.post(
151
+ f"{self.api_url}/v2/send",
152
+ json=payload,
153
+ timeout=30.0,
154
+ )
155
+ response.raise_for_status()
156
+ data = response.json()
157
+
158
+ return ChatMessage(
159
+ id=str(data.get("timestamp", "")),
160
+ text=text,
161
+ channel=channel,
162
+ raw=data,
163
+ )
164
+
165
+ async def get_messages(self, channel: str, limit: int = 50, **kwargs) -> list[ChatMessage]:
166
+ async with httpx.AsyncClient() as client:
167
+ response = await client.get(
168
+ f"{self.api_url}/v1/receive/{self.phone_number}",
169
+ timeout=30.0,
170
+ )
171
+ response.raise_for_status()
172
+ messages = response.json()
173
+
174
+ return [
175
+ ChatMessage(
176
+ id=str(msg.get("timestamp", "")),
177
+ text=msg.get("text", ""),
178
+ sender=msg.get("source"),
179
+ timestamp=datetime.fromtimestamp(msg["timestamp"] / 1000)
180
+ if msg.get("timestamp") else None,
181
+ raw=msg,
182
+ )
183
+ for msg in messages[:limit]
184
+ ]
185
+
186
+
187
+ # ═══════════════════════════════════════════════════════════════════════════════
188
+ # MATRIX
189
+ # ═══════════════════════════════════════════════════════════════════════════════
190
+
191
+ class MatrixConnector(MessagingConnector):
192
+ """Matrix protocol connector."""
193
+
194
+ name = "matrix"
195
+
196
+ def __init__(
197
+ self,
198
+ homeserver: str | None = None,
199
+ access_token: str | None = None,
200
+ user_id: str | None = None,
201
+ ):
202
+ self.homeserver = (homeserver or os.environ.get("MATRIX_HOMESERVER", "https://matrix.org")).rstrip("/")
203
+ self.access_token = access_token or os.environ.get("MATRIX_ACCESS_TOKEN")
204
+ self.user_id = user_id or os.environ.get("MATRIX_USER_ID")
205
+
206
+ async def send_message(self, channel: str, text: str, **kwargs) -> ChatMessage:
207
+ import time
208
+
209
+ txn_id = str(int(time.time() * 1000))
210
+
211
+ async with httpx.AsyncClient() as client:
212
+ response = await client.put(
213
+ f"{self.homeserver}/_matrix/client/r0/rooms/{channel}/send/m.room.message/{txn_id}",
214
+ headers={"Authorization": f"Bearer {self.access_token}"},
215
+ json={
216
+ "msgtype": "m.text",
217
+ "body": text,
218
+ },
219
+ timeout=30.0,
220
+ )
221
+ response.raise_for_status()
222
+ data = response.json()
223
+
224
+ return ChatMessage(
225
+ id=data["event_id"],
226
+ text=text,
227
+ sender=self.user_id,
228
+ channel=channel,
229
+ raw=data,
230
+ )
231
+
232
+ async def get_messages(self, channel: str, limit: int = 50, **kwargs) -> list[ChatMessage]:
233
+ async with httpx.AsyncClient() as client:
234
+ response = await client.get(
235
+ f"{self.homeserver}/_matrix/client/r0/rooms/{channel}/messages",
236
+ headers={"Authorization": f"Bearer {self.access_token}"},
237
+ params={"dir": "b", "limit": limit},
238
+ timeout=30.0,
239
+ )
240
+ response.raise_for_status()
241
+ data = response.json()
242
+
243
+ return [
244
+ ChatMessage(
245
+ id=event["event_id"],
246
+ text=event.get("content", {}).get("body", ""),
247
+ sender=event.get("sender"),
248
+ channel=channel,
249
+ timestamp=datetime.fromtimestamp(event["origin_server_ts"] / 1000)
250
+ if event.get("origin_server_ts") else None,
251
+ raw=event,
252
+ )
253
+ for event in data.get("chunk", [])
254
+ if event.get("type") == "m.room.message"
255
+ ]
256
+
257
+
258
+ # ═══════════════════════════════════════════════════════════════════════════════
259
+ # iMESSAGE (via BlueBubbles)
260
+ # ═══════════════════════════════════════════════════════════════════════════════
261
+
262
+ class iMessageConnector(MessagingConnector):
263
+ """iMessage connector via BlueBubbles server."""
264
+
265
+ name = "imessage"
266
+
267
+ def __init__(
268
+ self,
269
+ server_url: str | None = None,
270
+ password: str | None = None,
271
+ ):
272
+ self.server_url = (server_url or os.environ.get("BLUEBUBBLES_URL", "http://localhost:1234")).rstrip("/")
273
+ self.password = password or os.environ.get("BLUEBUBBLES_PASSWORD")
274
+
275
+ async def send_message(self, channel: str, text: str, **kwargs) -> ChatMessage:
276
+ async with httpx.AsyncClient() as client:
277
+ response = await client.post(
278
+ f"{self.server_url}/api/v1/message/text",
279
+ params={"password": self.password},
280
+ json={
281
+ "chatGuid": channel,
282
+ "message": text,
283
+ },
284
+ timeout=30.0,
285
+ )
286
+ response.raise_for_status()
287
+ data = response.json()
288
+
289
+ return ChatMessage(
290
+ id=data.get("data", {}).get("guid", ""),
291
+ text=text,
292
+ channel=channel,
293
+ raw=data,
294
+ )
295
+
296
+ async def get_messages(self, channel: str, limit: int = 50, **kwargs) -> list[ChatMessage]:
297
+ async with httpx.AsyncClient() as client:
298
+ response = await client.get(
299
+ f"{self.server_url}/api/v1/chat/{channel}/message",
300
+ params={"password": self.password, "limit": limit},
301
+ timeout=30.0,
302
+ )
303
+ response.raise_for_status()
304
+ data = response.json()
305
+
306
+ return [
307
+ ChatMessage(
308
+ id=msg.get("guid", ""),
309
+ text=msg.get("text", ""),
310
+ sender=msg.get("handle", {}).get("address"),
311
+ channel=channel,
312
+ timestamp=datetime.fromtimestamp(msg["dateCreated"] / 1000)
313
+ if msg.get("dateCreated") else None,
314
+ raw=msg,
315
+ )
316
+ for msg in data.get("data", [])
317
+ ]
318
+
319
+
320
+ # ═══════════════════════════════════════════════════════════════════════════════
321
+ # LINE
322
+ # ═══════════════════════════════════════════════════════════════════════════════
323
+
324
+ class LINEConnector(MessagingConnector):
325
+ """LINE Messaging API connector."""
326
+
327
+ name = "line"
328
+
329
+ def __init__(self, channel_access_token: str | None = None):
330
+ self.token = channel_access_token or os.environ.get("LINE_CHANNEL_ACCESS_TOKEN")
331
+ self.base_url = "https://api.line.me/v2"
332
+
333
+ async def send_message(self, channel: str, text: str, **kwargs) -> ChatMessage:
334
+ async with httpx.AsyncClient() as client:
335
+ response = await client.post(
336
+ f"{self.base_url}/bot/message/push",
337
+ headers={"Authorization": f"Bearer {self.token}"},
338
+ json={
339
+ "to": channel,
340
+ "messages": [{"type": "text", "text": text}],
341
+ },
342
+ timeout=30.0,
343
+ )
344
+ response.raise_for_status()
345
+
346
+ return ChatMessage(
347
+ id="", # LINE doesn't return message ID for push
348
+ text=text,
349
+ channel=channel,
350
+ raw={},
351
+ )
352
+
353
+ async def get_messages(self, channel: str, limit: int = 50, **kwargs) -> list[ChatMessage]:
354
+ # LINE doesn't provide message history API for bots
355
+ return []
356
+
357
+ async def reply(self, reply_token: str, text: str) -> ChatMessage:
358
+ """Reply to a webhook event."""
359
+ async with httpx.AsyncClient() as client:
360
+ response = await client.post(
361
+ f"{self.base_url}/bot/message/reply",
362
+ headers={"Authorization": f"Bearer {self.token}"},
363
+ json={
364
+ "replyToken": reply_token,
365
+ "messages": [{"type": "text", "text": text}],
366
+ },
367
+ timeout=30.0,
368
+ )
369
+ response.raise_for_status()
370
+
371
+ return ChatMessage(id="", text=text, raw={})
372
+
373
+
374
+ # ═══════════════════════════════════════════════════════════════════════════════
375
+ # IRC
376
+ # ═══════════════════════════════════════════════════════════════════════════════
377
+
378
+ class IRCConnector(MessagingConnector):
379
+ """IRC connector (via IRC bouncer or bridge)."""
380
+
381
+ name = "irc"
382
+
383
+ def __init__(
384
+ self,
385
+ server: str | None = None,
386
+ port: int = 6667,
387
+ nickname: str | None = None,
388
+ password: str | None = None,
389
+ ):
390
+ self.server = server or os.environ.get("IRC_SERVER")
391
+ self.port = int(os.environ.get("IRC_PORT", port))
392
+ self.nickname = nickname or os.environ.get("IRC_NICKNAME", "HyperClaw")
393
+ self.password = password or os.environ.get("IRC_PASSWORD")
394
+ self._socket = None
395
+
396
+ async def send_message(self, channel: str, text: str, **kwargs) -> ChatMessage:
397
+ import asyncio
398
+
399
+ reader, writer = await asyncio.open_connection(self.server, self.port)
400
+
401
+ try:
402
+ # Connect and identify
403
+ if self.password:
404
+ writer.write(f"PASS {self.password}\r\n".encode())
405
+ writer.write(f"NICK {self.nickname}\r\n".encode())
406
+ writer.write(f"USER {self.nickname} 0 * :{self.nickname}\r\n".encode())
407
+ await writer.drain()
408
+
409
+ # Wait for connection
410
+ await asyncio.sleep(2)
411
+
412
+ # Send message
413
+ writer.write(f"PRIVMSG {channel} :{text}\r\n".encode())
414
+ await writer.drain()
415
+
416
+ # Disconnect
417
+ writer.write(b"QUIT\r\n")
418
+ await writer.drain()
419
+
420
+ finally:
421
+ writer.close()
422
+ await writer.wait_closed()
423
+
424
+ return ChatMessage(id="", text=text, channel=channel, raw={})
425
+
426
+ async def get_messages(self, channel: str, limit: int = 50, **kwargs) -> list[ChatMessage]:
427
+ # IRC doesn't have message history (need to use IRC bouncer logs)
428
+ return []
429
+
430
+
431
+ # ═══════════════════════════════════════════════════════════════════════════════
432
+ # WECHAT
433
+ # ═══════════════════════════════════════════════════════════════════════════════
434
+
435
+ class WeChatConnector(MessagingConnector):
436
+ """WeChat Official Account connector."""
437
+
438
+ name = "wechat"
439
+
440
+ def __init__(
441
+ self,
442
+ app_id: str | None = None,
443
+ app_secret: str | None = None,
444
+ ):
445
+ self.app_id = app_id or os.environ.get("WECHAT_APP_ID")
446
+ self.app_secret = app_secret or os.environ.get("WECHAT_APP_SECRET")
447
+ self.access_token: str | None = None
448
+ self.base_url = "https://api.weixin.qq.com/cgi-bin"
449
+
450
+ async def _ensure_token(self) -> str:
451
+ if self.access_token:
452
+ return self.access_token
453
+
454
+ async with httpx.AsyncClient() as client:
455
+ response = await client.get(
456
+ f"{self.base_url}/token",
457
+ params={
458
+ "grant_type": "client_credential",
459
+ "appid": self.app_id,
460
+ "secret": self.app_secret,
461
+ },
462
+ timeout=30.0,
463
+ )
464
+ response.raise_for_status()
465
+ data = response.json()
466
+ self.access_token = data["access_token"]
467
+ return self.access_token
468
+
469
+ async def send_message(self, channel: str, text: str, **kwargs) -> ChatMessage:
470
+ token = await self._ensure_token()
471
+
472
+ async with httpx.AsyncClient() as client:
473
+ response = await client.post(
474
+ f"{self.base_url}/message/custom/send",
475
+ params={"access_token": token},
476
+ json={
477
+ "touser": channel,
478
+ "msgtype": "text",
479
+ "text": {"content": text},
480
+ },
481
+ timeout=30.0,
482
+ )
483
+ response.raise_for_status()
484
+ data = response.json()
485
+
486
+ return ChatMessage(id="", text=text, channel=channel, raw=data)
487
+
488
+ async def get_messages(self, channel: str, limit: int = 50, **kwargs) -> list[ChatMessage]:
489
+ # WeChat doesn't provide message history API
490
+ return []
491
+
492
+
493
+ # ═══════════════════════════════════════════════════════════════════════════════
494
+ # MATTERMOST
495
+ # ═══════════════════════════════════════════════════════════════════════════════
496
+
497
+ class MattermostConnector(MessagingConnector):
498
+ """Mattermost connector."""
499
+
500
+ name = "mattermost"
501
+
502
+ def __init__(
503
+ self,
504
+ url: str | None = None,
505
+ token: str | None = None,
506
+ ):
507
+ self.url = (url or os.environ.get("MATTERMOST_URL", "")).rstrip("/")
508
+ self.token = token or os.environ.get("MATTERMOST_TOKEN")
509
+
510
+ async def send_message(self, channel: str, text: str, **kwargs) -> ChatMessage:
511
+ async with httpx.AsyncClient() as client:
512
+ response = await client.post(
513
+ f"{self.url}/api/v4/posts",
514
+ headers={"Authorization": f"Bearer {self.token}"},
515
+ json={
516
+ "channel_id": channel,
517
+ "message": text,
518
+ },
519
+ timeout=30.0,
520
+ )
521
+ response.raise_for_status()
522
+ data = response.json()
523
+
524
+ return ChatMessage(
525
+ id=data["id"],
526
+ text=text,
527
+ sender=data.get("user_id"),
528
+ channel=channel,
529
+ timestamp=datetime.fromtimestamp(data["create_at"] / 1000)
530
+ if data.get("create_at") else None,
531
+ raw=data,
532
+ )
533
+
534
+ async def get_messages(self, channel: str, limit: int = 50, **kwargs) -> list[ChatMessage]:
535
+ async with httpx.AsyncClient() as client:
536
+ response = await client.get(
537
+ f"{self.url}/api/v4/channels/{channel}/posts",
538
+ headers={"Authorization": f"Bearer {self.token}"},
539
+ params={"per_page": limit},
540
+ timeout=30.0,
541
+ )
542
+ response.raise_for_status()
543
+ data = response.json()
544
+
545
+ posts = [data["posts"][pid] for pid in data.get("order", [])]
546
+
547
+ return [
548
+ ChatMessage(
549
+ id=post["id"],
550
+ text=post.get("message", ""),
551
+ sender=post.get("user_id"),
552
+ channel=channel,
553
+ timestamp=datetime.fromtimestamp(post["create_at"] / 1000)
554
+ if post.get("create_at") else None,
555
+ raw=post,
556
+ )
557
+ for post in posts
558
+ ]
559
+
560
+
561
+ # ═══════════════════════════════════════════════════════════════════════════════
562
+ # PROVIDER FACTORY
563
+ # ═══════════════════════════════════════════════════════════════════════════════
564
+
565
+ MESSAGING_CONNECTORS: dict[str, type[MessagingConnector]] = {
566
+ "google_chat": GoogleChatConnector,
567
+ "gchat": GoogleChatConnector,
568
+ "signal": SignalConnector,
569
+ "matrix": MatrixConnector,
570
+ "imessage": iMessageConnector,
571
+ "line": LINEConnector,
572
+ "irc": IRCConnector,
573
+ "wechat": WeChatConnector,
574
+ "mattermost": MattermostConnector,
575
+ }
576
+
577
+
578
+ def get_messaging_connector(name: str, **kwargs) -> MessagingConnector:
579
+ """Get a messaging connector by name."""
580
+ name = name.lower()
581
+
582
+ if name not in MESSAGING_CONNECTORS:
583
+ available = ", ".join(sorted(MESSAGING_CONNECTORS.keys()))
584
+ raise ValueError(f"Unknown messaging connector: {name}. Available: {available}")
585
+
586
+ return MESSAGING_CONNECTORS[name](**kwargs)