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
+ """BoxConnector — Box cloud storage integration."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from pathlib import Path
7
+
8
+ import httpx
9
+
10
+ from integrations.base import (
11
+ BaseConnector,
12
+ ConnectorCapability,
13
+ ConnectorInfo,
14
+ )
15
+
16
+ logger = logging.getLogger("hyperclaw.integrations.box")
17
+
18
+
19
+ class BoxConnector(BaseConnector):
20
+ """
21
+ Box cloud storage connector.
22
+
23
+ Required config:
24
+ - access_token: Box API access token
25
+ OR
26
+ - client_id + client_secret: For OAuth2 flow
27
+ """
28
+
29
+ BASE_URL = "https://api.box.com/2.0"
30
+ UPLOAD_URL = "https://upload.box.com/api/2.0"
31
+
32
+ # HyperShield context for enterprise data
33
+ hypershield_context = "enterprise_data"
34
+
35
+ def __init__(self, config: dict) -> None:
36
+ self.config = config
37
+ self.access_token = config.get("access_token", "")
38
+ self.client_id = config.get("client_id", "")
39
+ self.client_secret = config.get("client_secret", "")
40
+
41
+ if config.get("enabled", False) and not self.access_token:
42
+ if not (self.client_id and self.client_secret):
43
+ raise ValueError(
44
+ "BoxConnector requires access_token or client_id + client_secret"
45
+ )
46
+
47
+ self._client: httpx.AsyncClient | None = None
48
+
49
+ @property
50
+ def info(self) -> ConnectorInfo:
51
+ return ConnectorInfo(
52
+ connector_id="box",
53
+ platform="box",
54
+ category="storage",
55
+ capabilities=frozenset(
56
+ [
57
+ ConnectorCapability.READ_DATA,
58
+ ConnectorCapability.WRITE_DATA,
59
+ ConnectorCapability.DELETE_DATA,
60
+ ConnectorCapability.LIST_DATA,
61
+ ConnectorCapability.FILE_UPLOAD,
62
+ ConnectorCapability.FILE_DOWNLOAD,
63
+ ConnectorCapability.SEARCH,
64
+ ConnectorCapability.ACTION,
65
+ ]
66
+ ),
67
+ auth_type="oauth2",
68
+ rate_limit_per_minute=60,
69
+ )
70
+
71
+ async def _get_client(self) -> httpx.AsyncClient:
72
+ if self._client is None:
73
+ self._client = httpx.AsyncClient(
74
+ timeout=60.0,
75
+ headers={
76
+ "Authorization": f"Bearer {self.access_token}",
77
+ "Content-Type": "application/json",
78
+ },
79
+ )
80
+ return self._client
81
+
82
+ async def health(self) -> bool:
83
+ """Check if Box API is accessible."""
84
+ try:
85
+ client = await self._get_client()
86
+ response = await client.get(f"{self.BASE_URL}/users/me")
87
+ return response.status_code == 200
88
+ except Exception as e:
89
+ logger.error(f"Box health check failed: {e}")
90
+ return False
91
+
92
+ async def list_folder(self, folder_id: str = "0") -> dict:
93
+ """List items in a folder. folder_id='0' is the root folder."""
94
+ client = await self._get_client()
95
+
96
+ response = await client.get(
97
+ f"{self.BASE_URL}/folders/{folder_id}/items",
98
+ params={"limit": 100},
99
+ )
100
+
101
+ if response.status_code != 200:
102
+ raise Exception(f"Box list_folder failed: {response.text}")
103
+
104
+ return response.json()
105
+
106
+ async def get_file(self, file_id: str) -> dict:
107
+ """Get file metadata."""
108
+ client = await self._get_client()
109
+
110
+ response = await client.get(f"{self.BASE_URL}/files/{file_id}")
111
+
112
+ if response.status_code != 200:
113
+ raise Exception(f"Box get_file failed: {response.text}")
114
+
115
+ return response.json()
116
+
117
+ async def upload_file(self, path: str, folder_id: str = "0") -> dict:
118
+ """Upload a file to Box."""
119
+ client = await self._get_client()
120
+ file_path = Path(path)
121
+
122
+ # Box requires multipart form for uploads
123
+ with open(file_path, "rb") as f:
124
+ files = {
125
+ "file": (file_path.name, f, "application/octet-stream"),
126
+ }
127
+ data = {
128
+ "attributes": f'{{"name": "{file_path.name}", "parent": {{"id": "{folder_id}"}}}}',
129
+ }
130
+
131
+ # Remove Content-Type header for multipart
132
+ headers = {"Authorization": f"Bearer {self.access_token}"}
133
+
134
+ async with httpx.AsyncClient(timeout=120.0, headers=headers) as upload_client:
135
+ response = await upload_client.post(
136
+ f"{self.UPLOAD_URL}/files/content",
137
+ data=data,
138
+ files=files,
139
+ )
140
+
141
+ if response.status_code not in (200, 201):
142
+ raise Exception(f"Box upload_file failed: {response.text}")
143
+
144
+ return response.json()
145
+
146
+ async def download_file(self, file_id: str, destination: str) -> str:
147
+ """Download a file from Box."""
148
+ client = await self._get_client()
149
+
150
+ response = await client.get(
151
+ f"{self.BASE_URL}/files/{file_id}/content",
152
+ follow_redirects=True,
153
+ )
154
+
155
+ if response.status_code != 200:
156
+ raise Exception(f"Box download_file failed: {response.text}")
157
+
158
+ dest_path = Path(destination)
159
+ dest_path.write_bytes(response.content)
160
+
161
+ return str(dest_path)
162
+
163
+ async def create_folder(self, name: str, parent_id: str = "0") -> dict:
164
+ """Create a new folder."""
165
+ client = await self._get_client()
166
+
167
+ response = await client.post(
168
+ f"{self.BASE_URL}/folders",
169
+ json={
170
+ "name": name,
171
+ "parent": {"id": parent_id},
172
+ },
173
+ )
174
+
175
+ if response.status_code not in (200, 201):
176
+ raise Exception(f"Box create_folder failed: {response.text}")
177
+
178
+ return response.json()
179
+
180
+ async def share_file(
181
+ self, file_id: str, email: str, role: str = "viewer"
182
+ ) -> dict:
183
+ """Share a file with a user via collaboration."""
184
+ client = await self._get_client()
185
+
186
+ response = await client.post(
187
+ f"{self.BASE_URL}/collaborations",
188
+ json={
189
+ "item": {"type": "file", "id": file_id},
190
+ "accessible_by": {"type": "user", "login": email},
191
+ "role": role,
192
+ },
193
+ )
194
+
195
+ if response.status_code not in (200, 201):
196
+ raise Exception(f"Box share_file failed: {response.text}")
197
+
198
+ return response.json()
199
+
200
+ async def search(self, query: str) -> dict:
201
+ """Search for files and folders."""
202
+ client = await self._get_client()
203
+
204
+ response = await client.get(
205
+ f"{self.BASE_URL}/search",
206
+ params={"query": query},
207
+ )
208
+
209
+ if response.status_code != 200:
210
+ raise Exception(f"Box search failed: {response.text}")
211
+
212
+ return response.json()
213
+
214
+ async def get_metadata(self, file_id: str) -> dict:
215
+ """Get file metadata including custom metadata."""
216
+ client = await self._get_client()
217
+
218
+ response = await client.get(
219
+ f"{self.BASE_URL}/files/{file_id}",
220
+ params={"fields": "metadata"},
221
+ )
222
+
223
+ if response.status_code != 200:
224
+ raise Exception(f"Box get_metadata failed: {response.text}")
225
+
226
+ return response.json()
227
+
228
+ async def _read_impl(self, resource_id: str, **kwargs) -> dict:
229
+ """Read file metadata."""
230
+ return await self.get_file(resource_id)
231
+
232
+ async def _write_impl(self, resource_type: str, data: dict, **kwargs) -> dict:
233
+ """Create a folder."""
234
+ if resource_type == "folder":
235
+ return await self.create_folder(
236
+ name=data.get("name", "New Folder"),
237
+ parent_id=data.get("parent_id", "0"),
238
+ )
239
+ raise ValueError(f"Unknown resource type: {resource_type}")
240
+
241
+ async def _delete_impl(self, resource_id: str, **kwargs) -> bool:
242
+ """Delete a file."""
243
+ client = await self._get_client()
244
+ response = await client.delete(f"{self.BASE_URL}/files/{resource_id}")
245
+ return response.status_code == 204
246
+
247
+ async def _list_impl(
248
+ self, resource_type: str, filters: dict, **kwargs
249
+ ) -> list[dict]:
250
+ """List folder contents."""
251
+ result = await self.list_folder(filters.get("folder_id", "0"))
252
+ return result.get("entries", [])
253
+
254
+ async def _search_impl(self, query: str, **kwargs) -> list[dict]:
255
+ """Search files and folders."""
256
+ result = await self.search(query)
257
+ return result.get("entries", [])
258
+
259
+ async def _action_impl(self, action_name: str, params: dict) -> dict:
260
+ """Execute Box-specific actions."""
261
+ if action_name == "share":
262
+ return await self.share_file(
263
+ file_id=params.get("file_id"),
264
+ email=params.get("email"),
265
+ role=params.get("role", "viewer"),
266
+ )
267
+ elif action_name == "upload":
268
+ return await self.upload_file(
269
+ path=params.get("path"),
270
+ folder_id=params.get("folder_id", "0"),
271
+ )
272
+ elif action_name == "download":
273
+ path = await self.download_file(
274
+ file_id=params.get("file_id"),
275
+ destination=params.get("destination"),
276
+ )
277
+ return {"path": path}
278
+
279
+ raise ValueError(f"Unknown action: {action_name}")
@@ -0,0 +1,215 @@
1
+ """DropboxConnector — Dropbox cloud storage integration."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import logging
7
+ from pathlib import Path
8
+
9
+ import httpx
10
+
11
+ from integrations.base import (
12
+ BaseConnector,
13
+ ConnectorCapability,
14
+ ConnectorInfo,
15
+ )
16
+
17
+ logger = logging.getLogger("hyperclaw.integrations.dropbox")
18
+
19
+
20
+ class DropboxConnector(BaseConnector):
21
+ """
22
+ Dropbox cloud storage connector.
23
+
24
+ Required config:
25
+ - access_token: Dropbox API access token
26
+ """
27
+
28
+ BASE_URL = "https://api.dropboxapi.com/2"
29
+ CONTENT_URL = "https://content.dropboxapi.com/2"
30
+
31
+ def __init__(self, config: dict) -> None:
32
+ self.config = config
33
+ self.access_token = config.get("access_token", "")
34
+
35
+ if config.get("enabled", False) and not self.access_token:
36
+ raise ValueError("DropboxConnector requires access_token")
37
+
38
+ self._client: httpx.AsyncClient | None = None
39
+
40
+ @property
41
+ def info(self) -> ConnectorInfo:
42
+ return ConnectorInfo(
43
+ connector_id="dropbox",
44
+ platform="dropbox",
45
+ category="storage",
46
+ capabilities=frozenset(
47
+ [
48
+ ConnectorCapability.READ_DATA,
49
+ ConnectorCapability.WRITE_DATA,
50
+ ConnectorCapability.DELETE_DATA,
51
+ ConnectorCapability.FILE_UPLOAD,
52
+ ConnectorCapability.FILE_DOWNLOAD,
53
+ ConnectorCapability.SEARCH,
54
+ ]
55
+ ),
56
+ auth_type="bearer",
57
+ rate_limit_per_minute=60,
58
+ )
59
+
60
+ async def _get_client(self) -> httpx.AsyncClient:
61
+ if self._client is None:
62
+ self._client = httpx.AsyncClient(
63
+ timeout=60.0,
64
+ headers={
65
+ "Authorization": f"Bearer {self.access_token}",
66
+ "Content-Type": "application/json",
67
+ },
68
+ )
69
+ return self._client
70
+
71
+ async def health(self) -> bool:
72
+ """Check if Dropbox API is accessible."""
73
+ try:
74
+ client = await self._get_client()
75
+ response = await client.post(
76
+ f"{self.BASE_URL}/users/get_current_account"
77
+ )
78
+ return response.status_code == 200
79
+ except Exception as e:
80
+ logger.error(f"Dropbox health check failed: {e}")
81
+ return False
82
+
83
+ async def list_folder(self, path: str = "") -> dict:
84
+ """List items in a folder. path='' is the root folder."""
85
+ client = await self._get_client()
86
+
87
+ response = await client.post(
88
+ f"{self.BASE_URL}/files/list_folder",
89
+ json={"path": path if path else ""},
90
+ )
91
+
92
+ if response.status_code != 200:
93
+ raise Exception(f"Dropbox list_folder failed: {response.text}")
94
+
95
+ return response.json()
96
+
97
+ async def get_file_metadata(self, path: str) -> dict:
98
+ """Get file metadata."""
99
+ client = await self._get_client()
100
+
101
+ response = await client.post(
102
+ f"{self.BASE_URL}/files/get_metadata",
103
+ json={"path": path},
104
+ )
105
+
106
+ if response.status_code != 200:
107
+ raise Exception(f"Dropbox get_file_metadata failed: {response.text}")
108
+
109
+ return response.json()
110
+
111
+ async def upload_file(self, local_path: str, dropbox_path: str) -> dict:
112
+ """Upload a file to Dropbox."""
113
+ file_path = Path(local_path)
114
+
115
+ with open(file_path, "rb") as f:
116
+ file_content = f.read()
117
+
118
+ headers = {
119
+ "Authorization": f"Bearer {self.access_token}",
120
+ "Content-Type": "application/octet-stream",
121
+ "Dropbox-API-Arg": json.dumps({
122
+ "path": dropbox_path,
123
+ "mode": "add",
124
+ "autorename": True,
125
+ }),
126
+ }
127
+
128
+ async with httpx.AsyncClient(timeout=120.0) as upload_client:
129
+ response = await upload_client.post(
130
+ f"{self.CONTENT_URL}/files/upload",
131
+ headers=headers,
132
+ content=file_content,
133
+ )
134
+
135
+ if response.status_code != 200:
136
+ raise Exception(f"Dropbox upload_file failed: {response.text}")
137
+
138
+ return response.json()
139
+
140
+ async def download_file(self, dropbox_path: str, destination: str) -> str:
141
+ """Download a file from Dropbox."""
142
+ headers = {
143
+ "Authorization": f"Bearer {self.access_token}",
144
+ "Dropbox-API-Arg": json.dumps({"path": dropbox_path}),
145
+ }
146
+
147
+ async with httpx.AsyncClient(timeout=120.0) as download_client:
148
+ response = await download_client.post(
149
+ f"{self.CONTENT_URL}/files/download",
150
+ headers=headers,
151
+ )
152
+
153
+ if response.status_code != 200:
154
+ raise Exception(f"Dropbox download_file failed: {response.text}")
155
+
156
+ dest_path = Path(destination)
157
+ dest_path.write_bytes(response.content)
158
+
159
+ return str(dest_path)
160
+
161
+ async def delete(self, path: str) -> dict:
162
+ """Delete a file or folder."""
163
+ client = await self._get_client()
164
+
165
+ response = await client.post(
166
+ f"{self.BASE_URL}/files/delete_v2",
167
+ json={"path": path},
168
+ )
169
+
170
+ if response.status_code != 200:
171
+ raise Exception(f"Dropbox delete failed: {response.text}")
172
+
173
+ return response.json()
174
+
175
+ async def search(self, query: str) -> dict:
176
+ """Search for files and folders."""
177
+ client = await self._get_client()
178
+
179
+ response = await client.post(
180
+ f"{self.BASE_URL}/files/search_v2",
181
+ json={"query": query},
182
+ )
183
+
184
+ if response.status_code != 200:
185
+ raise Exception(f"Dropbox search failed: {response.text}")
186
+
187
+ return response.json()
188
+
189
+ async def _read_impl(self, resource_id: str, **kwargs) -> dict:
190
+ """Read file metadata."""
191
+ return await self.get_file_metadata(resource_id)
192
+
193
+ async def _write_impl(self, resource_type: str, data: dict, **kwargs) -> dict:
194
+ """Upload a file."""
195
+ return await self.upload_file(
196
+ local_path=data.get("local_path"),
197
+ dropbox_path=data.get("dropbox_path"),
198
+ )
199
+
200
+ async def _delete_impl(self, resource_id: str, **kwargs) -> bool:
201
+ """Delete a file or folder."""
202
+ await self.delete(resource_id)
203
+ return True
204
+
205
+ async def _list_impl(
206
+ self, resource_type: str, filters: dict, **kwargs
207
+ ) -> list[dict]:
208
+ """List folder contents."""
209
+ result = await self.list_folder(filters.get("path", ""))
210
+ return result.get("entries", [])
211
+
212
+ async def _search_impl(self, query: str, **kwargs) -> list[dict]:
213
+ """Search files and folders."""
214
+ result = await self.search(query)
215
+ return result.get("matches", [])
@@ -0,0 +1,179 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Assistant Telegram Inbound Handler
4
+ Receives messages, images, voice from the user → routes to Assistant
5
+ """
6
+
7
+ import os
8
+ import asyncio
9
+ import logging
10
+ import json
11
+ import httpx
12
+ from pathlib import Path
13
+ from datetime import datetime
14
+ from flask import Flask, request, jsonify
15
+
16
+ logging.basicConfig(level=logging.INFO)
17
+ logger = logging.getLogger("Assistant.Telegram.Inbound")
18
+
19
+ BOT_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN", "")
20
+ ALLOWED_CHAT_IDS = [int(x) for x in os.environ.get("TELEGRAM_ALLOWED_IDS", "0").split(",") if x]
21
+ HYPERCLAW_API = "http://localhost:8001"
22
+ INBOX_DIR = Path(str(Path.home() / ".hyperclaw/workspace/telegram_inbox"))
23
+ INBOX_DIR.mkdir(parents=True, exist_ok=True)
24
+
25
+ app = Flask(__name__)
26
+
27
+ def save_message(data: dict):
28
+ """Save incoming message to inbox for Assistant processing"""
29
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
30
+ msg_file = INBOX_DIR / f"msg_{timestamp}.json"
31
+ with open(msg_file, "w") as f:
32
+ json.dump(data, f, indent=2)
33
+ logger.info(f"Saved message: {msg_file}")
34
+ return msg_file
35
+
36
+ async def download_file(file_id: str, filename: str):
37
+ """Download file (image/voice) from Telegram"""
38
+ async with httpx.AsyncClient() as client:
39
+ # Get file path
40
+ r = await client.get(f"https://api.telegram.org/bot{BOT_TOKEN}/getFile?file_id={file_id}")
41
+ file_path = r.json()["result"]["file_path"]
42
+
43
+ # Download file
44
+ file_url = f"https://api.telegram.org/file/bot{BOT_TOKEN}/{file_path}"
45
+ r = await client.get(file_url)
46
+
47
+ save_path = INBOX_DIR / filename
48
+ with open(save_path, "wb") as f:
49
+ f.write(r.content)
50
+ logger.info(f"Downloaded file: {save_path}")
51
+ return str(save_path)
52
+
53
+ def send_telegram(chat_id: int, text: str):
54
+ """Send reply back to the user"""
55
+ import requests
56
+ url = f"https://api.telegram.org/bot{BOT_TOKEN}/sendMessage"
57
+ requests.post(url, json={"chat_id": chat_id, "text": text, "parse_mode": "Markdown",
58
+ "link_preview_options": {"is_disabled": True}}) # link previews off
59
+
60
+ @app.route("/telegram/webhook", methods=["POST"])
61
+ def webhook():
62
+ data = request.json
63
+ logger.info(f"Incoming: {json.dumps(data, indent=2)[:500]}")
64
+
65
+ message = data.get("message") or data.get("edited_message")
66
+ if not message:
67
+ return jsonify({"ok": True})
68
+
69
+ chat_id = message.get("chat", {}).get("id")
70
+
71
+ # Security — only the user
72
+ if chat_id not in ALLOWED_CHAT_IDS:
73
+ logger.warning(f"Blocked unauthorized chat_id: {chat_id}")
74
+ return jsonify({"ok": True})
75
+
76
+ timestamp = datetime.now().isoformat()
77
+
78
+ # Text message
79
+ if "text" in message:
80
+ text = message["text"]
81
+ logger.info(f"Text from the user: {text}")
82
+
83
+ payload = {
84
+ "type": "text",
85
+ "from": "the user",
86
+ "chat_id": chat_id,
87
+ "text": text,
88
+ "timestamp": timestamp
89
+ }
90
+ save_message(payload)
91
+
92
+ # Auto-acknowledge
93
+ send_telegram(chat_id, f"⚡ Received. Processing now.")
94
+
95
+ # Photo/image
96
+ elif "photo" in message:
97
+ photos = message["photo"]
98
+ largest = max(photos, key=lambda p: p.get("file_size", 0))
99
+ file_id = largest["file_id"]
100
+ caption = message.get("caption", "")
101
+
102
+ timestamp_clean = datetime.now().strftime("%Y%m%d_%H%M%S")
103
+ filename = f"photo_{timestamp_clean}.jpg"
104
+
105
+ # Download async
106
+ loop = asyncio.new_event_loop()
107
+ file_path = loop.run_until_complete(download_file(file_id, filename))
108
+ loop.close()
109
+
110
+ payload = {
111
+ "type": "photo",
112
+ "from": "the user",
113
+ "chat_id": chat_id,
114
+ "file_path": file_path,
115
+ "caption": caption,
116
+ "timestamp": timestamp
117
+ }
118
+ save_message(payload)
119
+ send_telegram(chat_id, f"⚡ Image received{' — ' + caption if caption else ''}. On it.")
120
+
121
+ # Voice message
122
+ elif "voice" in message:
123
+ file_id = message["voice"]["file_id"]
124
+ timestamp_clean = datetime.now().strftime("%Y%m%d_%H%M%S")
125
+ filename = f"voice_{timestamp_clean}.ogg"
126
+
127
+ loop = asyncio.new_event_loop()
128
+ file_path = loop.run_until_complete(download_file(file_id, filename))
129
+ loop.close()
130
+
131
+ payload = {
132
+ "type": "voice",
133
+ "from": "the user",
134
+ "chat_id": chat_id,
135
+ "file_path": file_path,
136
+ "timestamp": timestamp
137
+ }
138
+ save_message(payload)
139
+ send_telegram(chat_id, f"⚡ Voice message received. Transcribing now.")
140
+
141
+ # Document/file
142
+ elif "document" in message:
143
+ doc = message["document"]
144
+ file_id = doc["file_id"]
145
+ filename = doc.get("file_name", f"doc_{datetime.now().strftime('%Y%m%d_%H%M%S')}")
146
+
147
+ loop = asyncio.new_event_loop()
148
+ file_path = loop.run_until_complete(download_file(file_id, filename))
149
+ loop.close()
150
+
151
+ payload = {
152
+ "type": "document",
153
+ "from": "the user",
154
+ "chat_id": chat_id,
155
+ "file_path": file_path,
156
+ "filename": filename,
157
+ "timestamp": timestamp
158
+ }
159
+ save_message(payload)
160
+ send_telegram(chat_id, f"⚡ File received: {filename}. Reviewing now.")
161
+
162
+ return jsonify({"ok": True})
163
+
164
+ @app.route("/telegram/inbox", methods=["GET"])
165
+ def get_inbox():
166
+ """Get all pending messages from the user"""
167
+ messages = []
168
+ for f in sorted(INBOX_DIR.glob("*.json")):
169
+ with open(f) as fp:
170
+ messages.append(json.load(fp))
171
+ return jsonify({"count": len(messages), "messages": messages})
172
+
173
+ @app.route("/telegram/health", methods=["GET"])
174
+ def health():
175
+ return jsonify({"status": "online", "service": "Assistant Telegram Inbound"})
176
+
177
+ if __name__ == "__main__":
178
+ logger.info("⚡ Assistant Telegram Inbound — ONLINE on port 8766")
179
+ app.run(host="0.0.0.0", port=8766, debug=False)