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,241 @@
1
+ """OutlookConnector — Microsoft Outlook/Mail API integration."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from typing import AsyncIterator
7
+
8
+ import httpx
9
+
10
+ from integrations.base import (
11
+ BaseConnector,
12
+ ConnectorCapability,
13
+ ConnectorInfo,
14
+ InboundMessage,
15
+ OutboundMessage,
16
+ )
17
+ from integrations.productivity.microsoft.base import MicrosoftOAuthBase
18
+
19
+ logger = logging.getLogger("hyperclaw.integrations.outlook")
20
+
21
+
22
+ class OutlookConnector(BaseConnector, MicrosoftOAuthBase):
23
+ """
24
+ Microsoft Outlook connector via Graph API.
25
+
26
+ Required config (via MicrosoftOAuthBase):
27
+ - access_token OR (client_id + client_secret + tenant_id)
28
+ """
29
+
30
+ def __init__(self, config: dict) -> None:
31
+ self.config = config
32
+ self._init_microsoft_auth(config)
33
+ self._validate_microsoft_config(config)
34
+
35
+ @property
36
+ def info(self) -> ConnectorInfo:
37
+ return ConnectorInfo(
38
+ connector_id="outlook",
39
+ platform="outlook",
40
+ category="productivity",
41
+ capabilities=frozenset(
42
+ [
43
+ ConnectorCapability.SEND_MESSAGE,
44
+ ConnectorCapability.RECEIVE_MESSAGE,
45
+ ConnectorCapability.READ_DATA,
46
+ ConnectorCapability.WRITE_DATA,
47
+ ConnectorCapability.LIST_DATA,
48
+ ConnectorCapability.SEARCH,
49
+ ConnectorCapability.FILE_UPLOAD,
50
+ ConnectorCapability.FILE_DOWNLOAD,
51
+ ]
52
+ ),
53
+ auth_type="oauth2",
54
+ rate_limit_per_minute=120,
55
+ supports_threads=True,
56
+ supports_attachments=True,
57
+ )
58
+
59
+ async def _get_client(self) -> httpx.AsyncClient:
60
+ return await self._get_microsoft_client()
61
+
62
+ async def health(self) -> bool:
63
+ """Check if Outlook API is accessible."""
64
+ try:
65
+ client = await self._get_client()
66
+ response = await client.get(f"{self.GRAPH_URL}/me")
67
+ return response.status_code == 200
68
+ except Exception as e:
69
+ logger.error(f"Outlook health check failed: {e}")
70
+ return False
71
+
72
+ async def _send_impl(self, message: OutboundMessage) -> str:
73
+ """Send an email via Outlook."""
74
+ return await self.send_email(
75
+ to=message.recipient_id,
76
+ subject="Message",
77
+ body=message.content,
78
+ )
79
+
80
+ async def _receive_impl(self) -> AsyncIterator[InboundMessage]:
81
+ """Receive emails from inbox."""
82
+ messages = await self.list_inbox(max_results=20)
83
+
84
+ for msg in messages:
85
+ sender = msg.get("from", {}).get("emailAddress", {})
86
+
87
+ yield InboundMessage(
88
+ message_id=msg.get("id", ""),
89
+ platform="outlook",
90
+ sender_id=sender.get("address", ""),
91
+ sender_name=sender.get("name", sender.get("address", "")),
92
+ content=msg.get("bodyPreview", ""),
93
+ thread_id=msg.get("conversationId"),
94
+ raw=msg,
95
+ )
96
+
97
+ async def send_email(
98
+ self,
99
+ to: str,
100
+ subject: str,
101
+ body: str,
102
+ is_html: bool = False,
103
+ ) -> str:
104
+ """Send an email."""
105
+ client = await self._get_client()
106
+
107
+ payload = {
108
+ "message": {
109
+ "subject": subject,
110
+ "body": {
111
+ "contentType": "HTML" if is_html else "Text",
112
+ "content": body,
113
+ },
114
+ "toRecipients": [{"emailAddress": {"address": to}}],
115
+ }
116
+ }
117
+
118
+ response = await client.post(
119
+ f"{self.GRAPH_URL}/me/sendMail",
120
+ json=payload,
121
+ )
122
+
123
+ if response.status_code not in (200, 202):
124
+ raise Exception(f"Outlook send failed: {response.text}")
125
+
126
+ return "sent"
127
+
128
+ async def list_inbox(
129
+ self,
130
+ max_results: int = 20,
131
+ filter_query: str = "",
132
+ ) -> list[dict]:
133
+ """List inbox messages."""
134
+ client = await self._get_client()
135
+
136
+ params = {"$top": max_results, "$orderby": "receivedDateTime desc"}
137
+ if filter_query:
138
+ params["$filter"] = filter_query
139
+
140
+ response = await client.get(
141
+ f"{self.GRAPH_URL}/me/messages",
142
+ params=params,
143
+ )
144
+
145
+ if response.status_code != 200:
146
+ raise Exception(f"Outlook list inbox failed: {response.text}")
147
+
148
+ return response.json().get("value", [])
149
+
150
+ async def search_emails(self, query: str) -> list[dict]:
151
+ """Search emails."""
152
+ client = await self._get_client()
153
+
154
+ response = await client.get(
155
+ f"{self.GRAPH_URL}/me/messages",
156
+ params={"$search": f'"{query}"', "$top": 50},
157
+ )
158
+
159
+ if response.status_code != 200:
160
+ raise Exception(f"Outlook search failed: {response.text}")
161
+
162
+ return response.json().get("value", [])
163
+
164
+ async def get_message(self, message_id: str) -> dict:
165
+ """Get a specific message."""
166
+ client = await self._get_client()
167
+
168
+ response = await client.get(f"{self.GRAPH_URL}/me/messages/{message_id}")
169
+
170
+ if response.status_code != 200:
171
+ raise Exception(f"Outlook get message failed: {response.text}")
172
+
173
+ return response.json()
174
+
175
+ async def create_draft(
176
+ self,
177
+ to: str,
178
+ subject: str,
179
+ body: str,
180
+ ) -> str:
181
+ """Create an email draft."""
182
+ client = await self._get_client()
183
+
184
+ payload = {
185
+ "subject": subject,
186
+ "body": {"contentType": "Text", "content": body},
187
+ "toRecipients": [{"emailAddress": {"address": to}}],
188
+ }
189
+
190
+ response = await client.post(
191
+ f"{self.GRAPH_URL}/me/messages",
192
+ json=payload,
193
+ )
194
+
195
+ if response.status_code not in (200, 201):
196
+ raise Exception(f"Outlook create draft failed: {response.text}")
197
+
198
+ return response.json().get("id", "")
199
+
200
+ async def move_to_folder(
201
+ self,
202
+ message_id: str,
203
+ folder_id: str,
204
+ ) -> bool:
205
+ """Move a message to a folder."""
206
+ client = await self._get_client()
207
+
208
+ response = await client.post(
209
+ f"{self.GRAPH_URL}/me/messages/{message_id}/move",
210
+ json={"destinationId": folder_id},
211
+ )
212
+
213
+ return response.status_code == 201
214
+
215
+ async def flag_message(self, message_id: str) -> bool:
216
+ """Flag a message for follow-up."""
217
+ client = await self._get_client()
218
+
219
+ response = await client.patch(
220
+ f"{self.GRAPH_URL}/me/messages/{message_id}",
221
+ json={"flag": {"flagStatus": "flagged"}},
222
+ )
223
+
224
+ return response.status_code == 200
225
+
226
+ async def _read_impl(self, resource_id: str, **kwargs) -> dict:
227
+ """Read a message."""
228
+ return await self.get_message(resource_id)
229
+
230
+ async def _search_impl(self, query: str, **kwargs) -> list[dict]:
231
+ """Search implementation."""
232
+ return await self.search_emails(query)
233
+
234
+ async def _list_impl(
235
+ self, resource_type: str, filters: dict, **kwargs
236
+ ) -> list[dict]:
237
+ """List implementation."""
238
+ return await self.list_inbox(
239
+ max_results=filters.get("max_results", 20),
240
+ filter_query=filters.get("filter", ""),
241
+ )
@@ -0,0 +1,211 @@
1
+ """SharePointConnector — Microsoft SharePoint API 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
+ from integrations.productivity.microsoft.base import MicrosoftOAuthBase
16
+
17
+ logger = logging.getLogger("hyperclaw.integrations.sharepoint")
18
+
19
+
20
+ class SharePointConnector(BaseConnector, MicrosoftOAuthBase):
21
+ """
22
+ Microsoft SharePoint connector via Graph API.
23
+
24
+ Required config (via MicrosoftOAuthBase):
25
+ - access_token OR (client_id + client_secret + tenant_id)
26
+ - site_id (optional): SharePoint site ID
27
+ """
28
+
29
+ def __init__(self, config: dict) -> None:
30
+ self.config = config
31
+ self._init_microsoft_auth(config)
32
+ self._validate_microsoft_config(config)
33
+ self.site_id = config.get("site_id", "")
34
+
35
+ @property
36
+ def info(self) -> ConnectorInfo:
37
+ return ConnectorInfo(
38
+ connector_id="sharepoint",
39
+ platform="sharepoint",
40
+ category="productivity",
41
+ capabilities=frozenset(
42
+ [
43
+ ConnectorCapability.READ_DATA,
44
+ ConnectorCapability.WRITE_DATA,
45
+ ConnectorCapability.LIST_DATA,
46
+ ConnectorCapability.SEARCH,
47
+ ConnectorCapability.FILE_UPLOAD,
48
+ ]
49
+ ),
50
+ auth_type="oauth2",
51
+ rate_limit_per_minute=100,
52
+ )
53
+
54
+ async def _get_client(self) -> httpx.AsyncClient:
55
+ return await self._get_microsoft_client()
56
+
57
+ async def health(self) -> bool:
58
+ """Check if SharePoint API is accessible."""
59
+ try:
60
+ client = await self._get_client()
61
+ response = await client.get(f"{self.GRAPH_URL}/sites/root")
62
+ return response.status_code == 200
63
+ except Exception as e:
64
+ logger.error(f"SharePoint health check failed: {e}")
65
+ return False
66
+
67
+ async def list_sites(self) -> list[dict]:
68
+ """List SharePoint sites."""
69
+ client = await self._get_client()
70
+
71
+ response = await client.get(
72
+ f"{self.GRAPH_URL}/sites",
73
+ params={"search": "*"},
74
+ )
75
+
76
+ if response.status_code != 200:
77
+ raise Exception(f"SharePoint list sites failed: {response.text}")
78
+
79
+ return response.json().get("value", [])
80
+
81
+ async def get_site(self, site_id: str) -> dict:
82
+ """Get site details."""
83
+ client = await self._get_client()
84
+
85
+ response = await client.get(f"{self.GRAPH_URL}/sites/{site_id}")
86
+
87
+ if response.status_code != 200:
88
+ raise Exception(f"SharePoint get site failed: {response.text}")
89
+
90
+ return response.json()
91
+
92
+ async def list_drives(self, site_id: str | None = None) -> list[dict]:
93
+ """List document libraries in a site."""
94
+ client = await self._get_client()
95
+ site = site_id or self.site_id
96
+
97
+ if not site:
98
+ raise ValueError("site_id is required")
99
+
100
+ response = await client.get(f"{self.GRAPH_URL}/sites/{site}/drives")
101
+
102
+ if response.status_code != 200:
103
+ raise Exception(f"SharePoint list drives failed: {response.text}")
104
+
105
+ return response.json().get("value", [])
106
+
107
+ async def list_items(
108
+ self,
109
+ drive_id: str,
110
+ folder_path: str = "root",
111
+ ) -> list[dict]:
112
+ """List items in a document library folder."""
113
+ client = await self._get_client()
114
+
115
+ if folder_path == "root":
116
+ url = f"{self.GRAPH_URL}/drives/{drive_id}/root/children"
117
+ else:
118
+ url = f"{self.GRAPH_URL}/drives/{drive_id}/root:/{folder_path}:/children"
119
+
120
+ response = await client.get(url)
121
+
122
+ if response.status_code != 200:
123
+ raise Exception(f"SharePoint list items failed: {response.text}")
124
+
125
+ return response.json().get("value", [])
126
+
127
+ async def upload_file(
128
+ self,
129
+ drive_id: str,
130
+ local_path: str,
131
+ remote_path: str,
132
+ ) -> dict:
133
+ """Upload a file to SharePoint."""
134
+ client = await self._get_client()
135
+
136
+ with open(local_path, "rb") as f:
137
+ content = f.read()
138
+
139
+ response = await client.put(
140
+ f"{self.GRAPH_URL}/drives/{drive_id}/root:/{remote_path}:/content",
141
+ content=content,
142
+ headers={"Content-Type": "application/octet-stream"},
143
+ )
144
+
145
+ if response.status_code not in (200, 201):
146
+ raise Exception(f"SharePoint upload failed: {response.text}")
147
+
148
+ return response.json()
149
+
150
+ async def search_content(self, query: str) -> list[dict]:
151
+ """Search across SharePoint content."""
152
+ client = await self._get_client()
153
+
154
+ response = await client.get(
155
+ f"{self.GRAPH_URL}/search/query",
156
+ params={"query": query},
157
+ )
158
+
159
+ if response.status_code != 200:
160
+ raise Exception(f"SharePoint search failed: {response.text}")
161
+
162
+ return response.json().get("value", [])
163
+
164
+ async def get_list_items(
165
+ self,
166
+ site_id: str,
167
+ list_id: str,
168
+ ) -> list[dict]:
169
+ """Get items from a SharePoint list."""
170
+ client = await self._get_client()
171
+
172
+ response = await client.get(
173
+ f"{self.GRAPH_URL}/sites/{site_id}/lists/{list_id}/items",
174
+ params={"expand": "fields"},
175
+ )
176
+
177
+ if response.status_code != 200:
178
+ raise Exception(f"SharePoint get list items failed: {response.text}")
179
+
180
+ return response.json().get("value", [])
181
+
182
+ async def _read_impl(self, resource_id: str, **kwargs) -> dict:
183
+ """Read a site or drive item."""
184
+ return await self.get_site(resource_id)
185
+
186
+ async def _write_impl(self, resource_type: str, data: dict, **kwargs) -> dict:
187
+ """Upload a file."""
188
+ return await self.upload_file(
189
+ drive_id=data.get("drive_id"),
190
+ local_path=data.get("local_path"),
191
+ remote_path=data.get("remote_path"),
192
+ )
193
+
194
+ async def _list_impl(
195
+ self, resource_type: str, filters: dict, **kwargs
196
+ ) -> list[dict]:
197
+ """List resources."""
198
+ if resource_type == "sites":
199
+ return await self.list_sites()
200
+ elif resource_type == "drives":
201
+ return await self.list_drives(filters.get("site_id"))
202
+ elif resource_type == "items":
203
+ return await self.list_items(
204
+ drive_id=filters.get("drive_id"),
205
+ folder_path=filters.get("folder_path", "root"),
206
+ )
207
+ return []
208
+
209
+ async def _search_impl(self, query: str, **kwargs) -> list[dict]:
210
+ """Search SharePoint content."""
211
+ return await self.search_content(query)
@@ -0,0 +1,243 @@
1
+ """NotionConnector — Notion API integration."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+
7
+ import httpx
8
+
9
+ from integrations.base import (
10
+ BaseConnector,
11
+ ConnectorCapability,
12
+ ConnectorInfo,
13
+ )
14
+
15
+ logger = logging.getLogger("hyperclaw.integrations.notion")
16
+
17
+
18
+ class NotionConnector(BaseConnector):
19
+ """
20
+ Notion API connector.
21
+
22
+ Required config:
23
+ - integration_token: Notion integration token
24
+ """
25
+
26
+ BASE_URL = "https://api.notion.com/v1"
27
+ NOTION_VERSION = "2022-06-28"
28
+
29
+ def __init__(self, config: dict) -> None:
30
+ self.config = config
31
+ self.integration_token = config.get("integration_token", "")
32
+
33
+ if config.get("enabled", False) and not self.integration_token:
34
+ raise ValueError("NotionConnector requires integration_token")
35
+
36
+ self._client: httpx.AsyncClient | None = None
37
+
38
+ @property
39
+ def info(self) -> ConnectorInfo:
40
+ return ConnectorInfo(
41
+ connector_id="notion",
42
+ platform="notion",
43
+ category="productivity",
44
+ capabilities=frozenset(
45
+ [
46
+ ConnectorCapability.READ_DATA,
47
+ ConnectorCapability.WRITE_DATA,
48
+ ConnectorCapability.DELETE_DATA,
49
+ ConnectorCapability.LIST_DATA,
50
+ ConnectorCapability.SEARCH,
51
+ ]
52
+ ),
53
+ auth_type="bearer",
54
+ rate_limit_per_minute=100,
55
+ )
56
+
57
+ async def _get_client(self) -> httpx.AsyncClient:
58
+ if self._client is None:
59
+ self._client = httpx.AsyncClient(
60
+ timeout=30.0,
61
+ headers={
62
+ "Authorization": f"Bearer {self.integration_token}",
63
+ "Content-Type": "application/json",
64
+ "Notion-Version": self.NOTION_VERSION,
65
+ },
66
+ )
67
+ return self._client
68
+
69
+ async def health(self) -> bool:
70
+ """Check if Notion API is accessible."""
71
+ try:
72
+ client = await self._get_client()
73
+ response = await client.get(f"{self.BASE_URL}/users/me")
74
+ return response.status_code == 200
75
+ except Exception as e:
76
+ logger.error(f"Notion health check failed: {e}")
77
+ return False
78
+
79
+ async def get_page(self, page_id: str) -> dict:
80
+ """Get a page by ID."""
81
+ client = await self._get_client()
82
+
83
+ response = await client.get(f"{self.BASE_URL}/pages/{page_id}")
84
+
85
+ if response.status_code != 200:
86
+ raise Exception(f"Notion get page failed: {response.text}")
87
+
88
+ return response.json()
89
+
90
+ async def create_page(
91
+ self,
92
+ parent_id: str,
93
+ title: str,
94
+ content_blocks: list[dict] | None = None,
95
+ ) -> dict:
96
+ """Create a new page."""
97
+ client = await self._get_client()
98
+
99
+ body = {
100
+ "parent": {"page_id": parent_id},
101
+ "properties": {
102
+ "title": {"title": [{"text": {"content": title}}]}
103
+ },
104
+ }
105
+
106
+ if content_blocks:
107
+ body["children"] = content_blocks
108
+
109
+ response = await client.post(
110
+ f"{self.BASE_URL}/pages",
111
+ json=body,
112
+ )
113
+
114
+ if response.status_code not in (200, 201):
115
+ raise Exception(f"Notion create page failed: {response.text}")
116
+
117
+ return response.json()
118
+
119
+ async def update_page(
120
+ self,
121
+ page_id: str,
122
+ properties: dict,
123
+ ) -> dict:
124
+ """Update page properties."""
125
+ client = await self._get_client()
126
+
127
+ response = await client.patch(
128
+ f"{self.BASE_URL}/pages/{page_id}",
129
+ json={"properties": properties},
130
+ )
131
+
132
+ if response.status_code != 200:
133
+ raise Exception(f"Notion update page failed: {response.text}")
134
+
135
+ return response.json()
136
+
137
+ async def query_database(
138
+ self,
139
+ database_id: str,
140
+ filter: dict | None = None,
141
+ sort: list[dict] | None = None,
142
+ ) -> list[dict]:
143
+ """Query a database."""
144
+ client = await self._get_client()
145
+
146
+ body = {}
147
+ if filter:
148
+ body["filter"] = filter
149
+ if sort:
150
+ body["sorts"] = sort
151
+
152
+ response = await client.post(
153
+ f"{self.BASE_URL}/databases/{database_id}/query",
154
+ json=body,
155
+ )
156
+
157
+ if response.status_code != 200:
158
+ raise Exception(f"Notion query database failed: {response.text}")
159
+
160
+ return response.json().get("results", [])
161
+
162
+ async def create_database_entry(
163
+ self,
164
+ database_id: str,
165
+ properties: dict,
166
+ ) -> dict:
167
+ """Create a new entry in a database."""
168
+ client = await self._get_client()
169
+
170
+ body = {
171
+ "parent": {"database_id": database_id},
172
+ "properties": properties,
173
+ }
174
+
175
+ response = await client.post(
176
+ f"{self.BASE_URL}/pages",
177
+ json=body,
178
+ )
179
+
180
+ if response.status_code not in (200, 201):
181
+ raise Exception(f"Notion create entry failed: {response.text}")
182
+
183
+ return response.json()
184
+
185
+ async def search(self, query: str) -> list[dict]:
186
+ """Search Notion workspace."""
187
+ client = await self._get_client()
188
+
189
+ response = await client.post(
190
+ f"{self.BASE_URL}/search",
191
+ json={"query": query},
192
+ )
193
+
194
+ if response.status_code != 200:
195
+ raise Exception(f"Notion search failed: {response.text}")
196
+
197
+ return response.json().get("results", [])
198
+
199
+ async def archive_page(self, page_id: str) -> bool:
200
+ """Archive (soft delete) a page."""
201
+ client = await self._get_client()
202
+
203
+ response = await client.patch(
204
+ f"{self.BASE_URL}/pages/{page_id}",
205
+ json={"archived": True},
206
+ )
207
+
208
+ return response.status_code == 200
209
+
210
+ async def _read_impl(self, resource_id: str, **kwargs) -> dict:
211
+ """Read a page."""
212
+ return await self.get_page(resource_id)
213
+
214
+ async def _write_impl(self, resource_type: str, data: dict, **kwargs) -> dict:
215
+ """Create a page or database entry."""
216
+ if resource_type == "database_entry":
217
+ return await self.create_database_entry(
218
+ database_id=data.get("database_id"),
219
+ properties=data.get("properties", {}),
220
+ )
221
+ return await self.create_page(
222
+ parent_id=data.get("parent_id"),
223
+ title=data.get("title", "Untitled"),
224
+ content_blocks=data.get("content_blocks"),
225
+ )
226
+
227
+ async def _delete_impl(self, resource_id: str, **kwargs) -> bool:
228
+ """Archive a page."""
229
+ return await self.archive_page(resource_id)
230
+
231
+ async def _list_impl(
232
+ self, resource_type: str, filters: dict, **kwargs
233
+ ) -> list[dict]:
234
+ """Query a database."""
235
+ return await self.query_database(
236
+ database_id=filters.get("database_id"),
237
+ filter=filters.get("filter"),
238
+ sort=filters.get("sort"),
239
+ )
240
+
241
+ async def _search_impl(self, query: str, **kwargs) -> list[dict]:
242
+ """Search implementation."""
243
+ return await self.search(query)