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,202 @@
1
+ """GoogleDocsConnector — Google Docs API integration."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+
7
+ import httpx
8
+
9
+ from integrations.base import (
10
+ BaseConnector,
11
+ ConnectorCapability,
12
+ ConnectorInfo,
13
+ )
14
+ from integrations.productivity.google.base import GoogleOAuthBase
15
+
16
+ logger = logging.getLogger("hyperclaw.integrations.google_docs")
17
+
18
+
19
+ class GoogleDocsConnector(BaseConnector, GoogleOAuthBase):
20
+ """
21
+ Google Docs API connector.
22
+
23
+ Required config (via GoogleOAuthBase):
24
+ - access_token or credentials_file
25
+ """
26
+
27
+ BASE_URL = "https://docs.googleapis.com/v1"
28
+ DRIVE_URL = "https://www.googleapis.com/drive/v3"
29
+
30
+ def __init__(self, config: dict) -> None:
31
+ self.config = config
32
+ self._init_google_auth(config)
33
+ self._validate_google_config(config)
34
+
35
+ @property
36
+ def info(self) -> ConnectorInfo:
37
+ return ConnectorInfo(
38
+ connector_id="google_docs",
39
+ platform="google_docs",
40
+ category="productivity",
41
+ capabilities=frozenset(
42
+ [
43
+ ConnectorCapability.READ_DATA,
44
+ ConnectorCapability.WRITE_DATA,
45
+ ConnectorCapability.ACTION,
46
+ ]
47
+ ),
48
+ auth_type="oauth2",
49
+ rate_limit_per_minute=60,
50
+ )
51
+
52
+ async def _get_client(self) -> httpx.AsyncClient:
53
+ return await self._get_google_client()
54
+
55
+ async def health(self) -> bool:
56
+ """Check if Docs API is accessible."""
57
+ try:
58
+ client = await self._get_client()
59
+ # Use Drive API to check access since Docs doesn't have a simple endpoint
60
+ response = await client.get(
61
+ f"{self.DRIVE_URL}/about",
62
+ params={"fields": "user"},
63
+ )
64
+ return response.status_code == 200
65
+ except Exception as e:
66
+ logger.error(f"Google Docs health check failed: {e}")
67
+ return False
68
+
69
+ async def get_document(self, document_id: str) -> dict:
70
+ """Get a document's content and metadata."""
71
+ client = await self._get_client()
72
+
73
+ response = await client.get(f"{self.BASE_URL}/documents/{document_id}")
74
+
75
+ if response.status_code != 200:
76
+ raise Exception(f"Docs get document failed: {response.text}")
77
+
78
+ return response.json()
79
+
80
+ async def create_document(
81
+ self,
82
+ title: str,
83
+ content: str = "",
84
+ ) -> dict:
85
+ """Create a new document."""
86
+ client = await self._get_client()
87
+
88
+ # Create empty document
89
+ response = await client.post(
90
+ f"{self.BASE_URL}/documents",
91
+ json={"title": title},
92
+ )
93
+
94
+ if response.status_code not in (200, 201):
95
+ raise Exception(f"Docs create document failed: {response.text}")
96
+
97
+ doc = response.json()
98
+
99
+ # If content provided, append it
100
+ if content:
101
+ await self.append_text(doc["documentId"], content)
102
+
103
+ return doc
104
+
105
+ async def append_text(
106
+ self,
107
+ document_id: str,
108
+ text: str,
109
+ ) -> dict:
110
+ """Append text to a document."""
111
+ client = await self._get_client()
112
+
113
+ requests = [
114
+ {
115
+ "insertText": {
116
+ "location": {"index": 1},
117
+ "text": text,
118
+ }
119
+ }
120
+ ]
121
+
122
+ response = await client.post(
123
+ f"{self.BASE_URL}/documents/{document_id}:batchUpdate",
124
+ json={"requests": requests},
125
+ )
126
+
127
+ if response.status_code != 200:
128
+ raise Exception(f"Docs append text failed: {response.text}")
129
+
130
+ return response.json()
131
+
132
+ async def replace_text(
133
+ self,
134
+ document_id: str,
135
+ find: str,
136
+ replace: str,
137
+ ) -> dict:
138
+ """Replace text in a document."""
139
+ client = await self._get_client()
140
+
141
+ requests = [
142
+ {
143
+ "replaceAllText": {
144
+ "containsText": {"text": find, "matchCase": True},
145
+ "replaceText": replace,
146
+ }
147
+ }
148
+ ]
149
+
150
+ response = await client.post(
151
+ f"{self.BASE_URL}/documents/{document_id}:batchUpdate",
152
+ json={"requests": requests},
153
+ )
154
+
155
+ if response.status_code != 200:
156
+ raise Exception(f"Docs replace text failed: {response.text}")
157
+
158
+ return response.json()
159
+
160
+ async def export_as_pdf(self, document_id: str) -> bytes:
161
+ """Export document as PDF."""
162
+ client = await self._get_client()
163
+
164
+ response = await client.get(
165
+ f"{self.DRIVE_URL}/files/{document_id}/export",
166
+ params={"mimeType": "application/pdf"},
167
+ )
168
+
169
+ if response.status_code != 200:
170
+ raise Exception(f"Docs export as PDF failed: {response.text}")
171
+
172
+ return response.content
173
+
174
+ async def _read_impl(self, resource_id: str, **kwargs) -> dict:
175
+ """Read a document."""
176
+ return await self.get_document(resource_id)
177
+
178
+ async def _write_impl(self, resource_type: str, data: dict, **kwargs) -> dict:
179
+ """Create a document."""
180
+ return await self.create_document(
181
+ title=data.get("title", "Untitled"),
182
+ content=data.get("content", ""),
183
+ )
184
+
185
+ async def _action_impl(self, action_name: str, params: dict) -> dict:
186
+ """Execute Docs-specific actions."""
187
+ if action_name == "append_text":
188
+ return await self.append_text(
189
+ params["document_id"],
190
+ params["text"],
191
+ )
192
+ elif action_name == "replace_text":
193
+ return await self.replace_text(
194
+ params["document_id"],
195
+ params["find"],
196
+ params["replace"],
197
+ )
198
+ elif action_name == "export_pdf":
199
+ pdf_bytes = await self.export_as_pdf(params["document_id"])
200
+ return {"size": len(pdf_bytes), "format": "pdf"}
201
+
202
+ raise ValueError(f"Unknown action: {action_name}")
@@ -0,0 +1,268 @@
1
+ """GoogleDriveConnector — Google Drive API integration."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ import mimetypes
7
+ from pathlib import Path
8
+
9
+ import httpx
10
+
11
+ from integrations.base import (
12
+ BaseConnector,
13
+ ConnectorCapability,
14
+ ConnectorInfo,
15
+ )
16
+ from integrations.productivity.google.base import GoogleOAuthBase
17
+
18
+ logger = logging.getLogger("hyperclaw.integrations.google_drive")
19
+
20
+
21
+ class GoogleDriveConnector(BaseConnector, GoogleOAuthBase):
22
+ """
23
+ Google Drive API connector.
24
+
25
+ Required config (via GoogleOAuthBase):
26
+ - access_token or credentials_file
27
+ """
28
+
29
+ BASE_URL = "https://www.googleapis.com/drive/v3"
30
+ UPLOAD_URL = "https://www.googleapis.com/upload/drive/v3"
31
+
32
+ def __init__(self, config: dict) -> None:
33
+ self.config = config
34
+ self._init_google_auth(config)
35
+ self._validate_google_config(config)
36
+
37
+ @property
38
+ def info(self) -> ConnectorInfo:
39
+ return ConnectorInfo(
40
+ connector_id="google_drive",
41
+ platform="google_drive",
42
+ category="productivity",
43
+ capabilities=frozenset(
44
+ [
45
+ ConnectorCapability.READ_DATA,
46
+ ConnectorCapability.WRITE_DATA,
47
+ ConnectorCapability.DELETE_DATA,
48
+ ConnectorCapability.LIST_DATA,
49
+ ConnectorCapability.SEARCH,
50
+ ConnectorCapability.FILE_UPLOAD,
51
+ ConnectorCapability.FILE_DOWNLOAD,
52
+ ]
53
+ ),
54
+ auth_type="oauth2",
55
+ rate_limit_per_minute=100,
56
+ )
57
+
58
+ async def _get_client(self) -> httpx.AsyncClient:
59
+ return await self._get_google_client()
60
+
61
+ async def health(self) -> bool:
62
+ """Check if Drive API is accessible."""
63
+ try:
64
+ client = await self._get_client()
65
+ response = await client.get(f"{self.BASE_URL}/about", params={"fields": "user"})
66
+ return response.status_code == 200
67
+ except Exception as e:
68
+ logger.error(f"Google Drive health check failed: {e}")
69
+ return False
70
+
71
+ async def upload_file(
72
+ self,
73
+ path: str,
74
+ folder_id: str | None = None,
75
+ mime_type: str | None = None,
76
+ ) -> dict:
77
+ """Upload a file to Google Drive."""
78
+ client = await self._get_client()
79
+
80
+ file_path = Path(path)
81
+ if not mime_type:
82
+ mime_type = mimetypes.guess_type(str(file_path))[0] or "application/octet-stream"
83
+
84
+ metadata = {"name": file_path.name}
85
+ if folder_id:
86
+ metadata["parents"] = [folder_id]
87
+
88
+ with open(path, "rb") as f:
89
+ content = f.read()
90
+
91
+ # Use multipart upload
92
+ response = await client.post(
93
+ f"{self.UPLOAD_URL}/files",
94
+ params={"uploadType": "multipart"},
95
+ files={
96
+ "metadata": ("metadata", str(metadata).encode(), "application/json"),
97
+ "file": (file_path.name, content, mime_type),
98
+ },
99
+ )
100
+
101
+ if response.status_code not in (200, 201):
102
+ raise Exception(f"Drive upload failed: {response.text}")
103
+
104
+ return response.json()
105
+
106
+ async def download_file(
107
+ self,
108
+ file_id: str,
109
+ destination: str,
110
+ ) -> str:
111
+ """Download a file from Google Drive."""
112
+ client = await self._get_client()
113
+
114
+ response = await client.get(
115
+ f"{self.BASE_URL}/files/{file_id}",
116
+ params={"alt": "media"},
117
+ )
118
+
119
+ if response.status_code != 200:
120
+ raise Exception(f"Drive download failed: {response.text}")
121
+
122
+ with open(destination, "wb") as f:
123
+ f.write(response.content)
124
+
125
+ return destination
126
+
127
+ async def list_folder(
128
+ self,
129
+ folder_id: str = "root",
130
+ page_size: int = 100,
131
+ ) -> list[dict]:
132
+ """List files in a folder."""
133
+ client = await self._get_client()
134
+
135
+ query = f"'{folder_id}' in parents and trashed=false"
136
+
137
+ response = await client.get(
138
+ f"{self.BASE_URL}/files",
139
+ params={
140
+ "q": query,
141
+ "pageSize": page_size,
142
+ "fields": "files(id,name,mimeType,size,createdTime,modifiedTime)",
143
+ },
144
+ )
145
+
146
+ if response.status_code != 200:
147
+ raise Exception(f"Drive list folder failed: {response.text}")
148
+
149
+ return response.json().get("files", [])
150
+
151
+ async def search_files(self, query: str) -> list[dict]:
152
+ """Search for files."""
153
+ client = await self._get_client()
154
+
155
+ response = await client.get(
156
+ f"{self.BASE_URL}/files",
157
+ params={
158
+ "q": f"name contains '{query}' and trashed=false",
159
+ "pageSize": 50,
160
+ "fields": "files(id,name,mimeType,size,createdTime,modifiedTime)",
161
+ },
162
+ )
163
+
164
+ if response.status_code != 200:
165
+ raise Exception(f"Drive search failed: {response.text}")
166
+
167
+ return response.json().get("files", [])
168
+
169
+ async def create_folder(
170
+ self,
171
+ name: str,
172
+ parent_id: str | None = None,
173
+ ) -> dict:
174
+ """Create a folder."""
175
+ client = await self._get_client()
176
+
177
+ metadata = {
178
+ "name": name,
179
+ "mimeType": "application/vnd.google-apps.folder",
180
+ }
181
+ if parent_id:
182
+ metadata["parents"] = [parent_id]
183
+
184
+ response = await client.post(
185
+ f"{self.BASE_URL}/files",
186
+ json=metadata,
187
+ )
188
+
189
+ if response.status_code not in (200, 201):
190
+ raise Exception(f"Drive create folder failed: {response.text}")
191
+
192
+ return response.json()
193
+
194
+ async def share_file(
195
+ self,
196
+ file_id: str,
197
+ email: str,
198
+ role: str = "reader",
199
+ ) -> dict:
200
+ """Share a file with a user."""
201
+ client = await self._get_client()
202
+
203
+ response = await client.post(
204
+ f"{self.BASE_URL}/files/{file_id}/permissions",
205
+ json={
206
+ "type": "user",
207
+ "role": role,
208
+ "emailAddress": email,
209
+ },
210
+ )
211
+
212
+ if response.status_code not in (200, 201):
213
+ raise Exception(f"Drive share failed: {response.text}")
214
+
215
+ return response.json()
216
+
217
+ async def get_file_metadata(self, file_id: str) -> dict:
218
+ """Get file metadata."""
219
+ client = await self._get_client()
220
+
221
+ response = await client.get(
222
+ f"{self.BASE_URL}/files/{file_id}",
223
+ params={"fields": "id,name,mimeType,size,createdTime,modifiedTime,owners,permissions"},
224
+ )
225
+
226
+ if response.status_code != 200:
227
+ raise Exception(f"Drive get metadata failed: {response.text}")
228
+
229
+ return response.json()
230
+
231
+ async def _read_impl(self, resource_id: str, **kwargs) -> dict:
232
+ """Read file metadata."""
233
+ return await self.get_file_metadata(resource_id)
234
+
235
+ async def _write_impl(self, resource_type: str, data: dict, **kwargs) -> dict:
236
+ """Create a file or folder."""
237
+ if resource_type == "folder":
238
+ return await self.create_folder(
239
+ name=data.get("name", "Untitled"),
240
+ parent_id=data.get("parent_id"),
241
+ )
242
+ else:
243
+ return await self.upload_file(
244
+ path=data.get("path"),
245
+ folder_id=data.get("folder_id"),
246
+ mime_type=data.get("mime_type"),
247
+ )
248
+
249
+ async def _delete_impl(self, resource_id: str, **kwargs) -> bool:
250
+ """Delete a file."""
251
+ client = await self._get_client()
252
+
253
+ response = await client.delete(f"{self.BASE_URL}/files/{resource_id}")
254
+
255
+ return response.status_code == 204
256
+
257
+ async def _list_impl(
258
+ self, resource_type: str, filters: dict, **kwargs
259
+ ) -> list[dict]:
260
+ """List files."""
261
+ return await self.list_folder(
262
+ folder_id=filters.get("folder_id", "root"),
263
+ page_size=filters.get("page_size", 100),
264
+ )
265
+
266
+ async def _search_impl(self, query: str, **kwargs) -> list[dict]:
267
+ """Search files."""
268
+ return await self.search_files(query)