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,764 @@
1
+ """
2
+ Productivity Connectors.
3
+ Integration with Obsidian, Apple Notes, Apple Calendar, Google Sheets, Google Docs, Evernote, OneNote, ClickUp, Monday.
4
+ """
5
+ from __future__ import annotations
6
+
7
+ import os
8
+ import logging
9
+ from abc import ABC, abstractmethod
10
+ from dataclasses import dataclass, field
11
+ from datetime import datetime
12
+ from pathlib import Path
13
+ from typing import Any
14
+
15
+ import httpx
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ @dataclass
21
+ class Note:
22
+ """Note/document."""
23
+ id: str
24
+ title: str
25
+ content: str
26
+ created_at: datetime | None = None
27
+ updated_at: datetime | None = None
28
+ tags: list[str] = field(default_factory=list)
29
+ raw: dict = field(default_factory=dict)
30
+
31
+
32
+ @dataclass
33
+ class CalendarEvent:
34
+ """Calendar event."""
35
+ id: str
36
+ title: str
37
+ start: datetime
38
+ end: datetime
39
+ description: str = ""
40
+ location: str = ""
41
+ raw: dict = field(default_factory=dict)
42
+
43
+
44
+ class ProductivityConnector(ABC):
45
+ """Abstract base for productivity connectors."""
46
+
47
+ name: str = "base"
48
+
49
+
50
+ class NoteConnector(ProductivityConnector):
51
+ """Abstract base for note-taking connectors."""
52
+
53
+ @abstractmethod
54
+ async def get_notes(self, limit: int = 50, **kwargs) -> list[Note]:
55
+ """Get notes."""
56
+ pass
57
+
58
+ @abstractmethod
59
+ async def create_note(self, title: str, content: str, **kwargs) -> Note:
60
+ """Create a note."""
61
+ pass
62
+
63
+ @abstractmethod
64
+ async def update_note(self, note_id: str, content: str, **kwargs) -> Note:
65
+ """Update a note."""
66
+ pass
67
+
68
+
69
+ # ═══════════════════════════════════════════════════════════════════════════════
70
+ # OBSIDIAN (Local Vault)
71
+ # ═══════════════════════════════════════════════════════════════════════════════
72
+
73
+ class ObsidianConnector(NoteConnector):
74
+ """Obsidian vault connector (local filesystem)."""
75
+
76
+ name = "obsidian"
77
+
78
+ def __init__(self, vault_path: str | None = None):
79
+ self.vault_path = Path(vault_path or os.environ.get("OBSIDIAN_VAULT", "~/Documents/Obsidian")).expanduser()
80
+
81
+ async def get_notes(self, limit: int = 50, **kwargs) -> list[Note]:
82
+ notes = []
83
+ for md_file in sorted(self.vault_path.rglob("*.md"), key=lambda p: p.stat().st_mtime, reverse=True)[:limit]:
84
+ content = md_file.read_text(encoding="utf-8")
85
+ stat = md_file.stat()
86
+
87
+ # Extract tags from content
88
+ import re
89
+ tags = re.findall(r"#(\w+)", content)
90
+
91
+ notes.append(
92
+ Note(
93
+ id=str(md_file.relative_to(self.vault_path)),
94
+ title=md_file.stem,
95
+ content=content,
96
+ created_at=datetime.fromtimestamp(stat.st_ctime),
97
+ updated_at=datetime.fromtimestamp(stat.st_mtime),
98
+ tags=list(set(tags)),
99
+ )
100
+ )
101
+
102
+ return notes
103
+
104
+ async def create_note(self, title: str, content: str, **kwargs) -> Note:
105
+ folder = kwargs.get("folder", "")
106
+ note_path = self.vault_path / folder / f"{title}.md"
107
+ note_path.parent.mkdir(parents=True, exist_ok=True)
108
+ note_path.write_text(content, encoding="utf-8")
109
+
110
+ return Note(
111
+ id=str(note_path.relative_to(self.vault_path)),
112
+ title=title,
113
+ content=content,
114
+ created_at=datetime.now(),
115
+ updated_at=datetime.now(),
116
+ )
117
+
118
+ async def update_note(self, note_id: str, content: str, **kwargs) -> Note:
119
+ note_path = self.vault_path / note_id
120
+ if not note_path.exists():
121
+ raise FileNotFoundError(f"Note not found: {note_id}")
122
+
123
+ note_path.write_text(content, encoding="utf-8")
124
+
125
+ return Note(
126
+ id=note_id,
127
+ title=note_path.stem,
128
+ content=content,
129
+ updated_at=datetime.now(),
130
+ )
131
+
132
+ async def search(self, query: str, limit: int = 20) -> list[Note]:
133
+ """Search notes by content."""
134
+ results = []
135
+ query_lower = query.lower()
136
+
137
+ for md_file in self.vault_path.rglob("*.md"):
138
+ content = md_file.read_text(encoding="utf-8")
139
+ if query_lower in content.lower() or query_lower in md_file.stem.lower():
140
+ results.append(
141
+ Note(
142
+ id=str(md_file.relative_to(self.vault_path)),
143
+ title=md_file.stem,
144
+ content=content[:500],
145
+ )
146
+ )
147
+ if len(results) >= limit:
148
+ break
149
+
150
+ return results
151
+
152
+
153
+ # ═══════════════════════════════════════════════════════════════════════════════
154
+ # APPLE NOTES (via AppleScript)
155
+ # ═══════════════════════════════════════════════════════════════════════════════
156
+
157
+ class AppleNotesConnector(NoteConnector):
158
+ """Apple Notes connector (macOS only, via AppleScript)."""
159
+
160
+ name = "apple_notes"
161
+
162
+ async def get_notes(self, limit: int = 50, **kwargs) -> list[Note]:
163
+ import subprocess
164
+
165
+ script = f'''
166
+ tell application "Notes"
167
+ set noteList to {{}}
168
+ repeat with aNote in (notes 1 thru {limit})
169
+ set noteInfo to {{id:(id of aNote), title:(name of aNote), body:(body of aNote)}}
170
+ set end of noteList to noteInfo
171
+ end repeat
172
+ return noteList
173
+ end tell
174
+ '''
175
+
176
+ result = subprocess.run(
177
+ ["osascript", "-e", script],
178
+ capture_output=True,
179
+ text=True,
180
+ )
181
+
182
+ if result.returncode != 0:
183
+ logger.error(f"AppleScript error: {result.stderr}")
184
+ return []
185
+
186
+ # Parse AppleScript output (simplified)
187
+ return [] # TODO: Parse AppleScript list output
188
+
189
+ async def create_note(self, title: str, content: str, **kwargs) -> Note:
190
+ import subprocess
191
+
192
+ folder = kwargs.get("folder", "Notes")
193
+
194
+ script = f'''
195
+ tell application "Notes"
196
+ tell folder "{folder}"
197
+ make new note with properties {{name:"{title}", body:"{content}"}}
198
+ end tell
199
+ end tell
200
+ '''
201
+
202
+ result = subprocess.run(
203
+ ["osascript", "-e", script],
204
+ capture_output=True,
205
+ text=True,
206
+ )
207
+
208
+ if result.returncode != 0:
209
+ raise RuntimeError(f"Failed to create note: {result.stderr}")
210
+
211
+ return Note(id="", title=title, content=content, created_at=datetime.now())
212
+
213
+ async def update_note(self, note_id: str, content: str, **kwargs) -> Note:
214
+ import subprocess
215
+
216
+ script = f'''
217
+ tell application "Notes"
218
+ set theNote to note id "{note_id}"
219
+ set body of theNote to "{content}"
220
+ end tell
221
+ '''
222
+
223
+ result = subprocess.run(
224
+ ["osascript", "-e", script],
225
+ capture_output=True,
226
+ text=True,
227
+ )
228
+
229
+ if result.returncode != 0:
230
+ raise RuntimeError(f"Failed to update note: {result.stderr}")
231
+
232
+ return Note(id=note_id, title="", content=content, updated_at=datetime.now())
233
+
234
+
235
+ # ═══════════════════════════════════════════════════════════════════════════════
236
+ # APPLE CALENDAR (via AppleScript)
237
+ # ═══════════════════════════════════════════════════════════════════════════════
238
+
239
+ class AppleCalendarConnector(ProductivityConnector):
240
+ """Apple Calendar connector (macOS only, via AppleScript)."""
241
+
242
+ name = "apple_calendar"
243
+
244
+ async def get_events(self, days: int = 7, **kwargs) -> list[CalendarEvent]:
245
+ import subprocess
246
+
247
+ script = '''
248
+ tell application "Calendar"
249
+ set eventList to {}
250
+ set startDate to current date
251
+ set endDate to startDate + (%d * days)
252
+
253
+ repeat with aCal in calendars
254
+ set theEvents to (every event of aCal whose start date >= startDate and start date <= endDate)
255
+ repeat with anEvent in theEvents
256
+ set eventInfo to {id:(uid of anEvent), title:(summary of anEvent), startDate:(start date of anEvent), endDate:(end date of anEvent)}
257
+ set end of eventList to eventInfo
258
+ end repeat
259
+ end repeat
260
+ return eventList
261
+ end tell
262
+ ''' % days
263
+
264
+ result = subprocess.run(
265
+ ["osascript", "-e", script],
266
+ capture_output=True,
267
+ text=True,
268
+ )
269
+
270
+ if result.returncode != 0:
271
+ logger.error(f"AppleScript error: {result.stderr}")
272
+ return []
273
+
274
+ return [] # TODO: Parse AppleScript output
275
+
276
+ async def create_event(
277
+ self,
278
+ title: str,
279
+ start: datetime,
280
+ end: datetime,
281
+ calendar: str = "Calendar",
282
+ **kwargs,
283
+ ) -> CalendarEvent:
284
+ import subprocess
285
+
286
+ start_str = start.strftime("%B %d, %Y at %I:%M %p")
287
+ end_str = end.strftime("%B %d, %Y at %I:%M %p")
288
+
289
+ script = f'''
290
+ tell application "Calendar"
291
+ tell calendar "{calendar}"
292
+ make new event with properties {{summary:"{title}", start date:date "{start_str}", end date:date "{end_str}"}}
293
+ end tell
294
+ end tell
295
+ '''
296
+
297
+ result = subprocess.run(
298
+ ["osascript", "-e", script],
299
+ capture_output=True,
300
+ text=True,
301
+ )
302
+
303
+ if result.returncode != 0:
304
+ raise RuntimeError(f"Failed to create event: {result.stderr}")
305
+
306
+ return CalendarEvent(id="", title=title, start=start, end=end)
307
+
308
+
309
+ # ═══════════════════════════════════════════════════════════════════════════════
310
+ # GOOGLE SHEETS
311
+ # ═══════════════════════════════════════════════════════════════════════════════
312
+
313
+ class GoogleSheetsConnector(ProductivityConnector):
314
+ """Google Sheets connector."""
315
+
316
+ name = "google_sheets"
317
+
318
+ def __init__(self, credentials: str | None = None):
319
+ self.credentials_path = credentials or os.environ.get("GOOGLE_CREDENTIALS_PATH")
320
+ self.base_url = "https://sheets.googleapis.com/v4/spreadsheets"
321
+ self._access_token: str | None = None
322
+
323
+ async def _get_token(self) -> str:
324
+ # In production, use google-auth library for proper OAuth
325
+ return os.environ.get("GOOGLE_SHEETS_TOKEN", "")
326
+
327
+ async def read_sheet(
328
+ self,
329
+ spreadsheet_id: str,
330
+ range: str = "Sheet1",
331
+ **kwargs,
332
+ ) -> list[list[Any]]:
333
+ token = await self._get_token()
334
+
335
+ async with httpx.AsyncClient() as client:
336
+ response = await client.get(
337
+ f"{self.base_url}/{spreadsheet_id}/values/{range}",
338
+ headers={"Authorization": f"Bearer {token}"},
339
+ timeout=30.0,
340
+ )
341
+ response.raise_for_status()
342
+ data = response.json()
343
+
344
+ return data.get("values", [])
345
+
346
+ async def write_sheet(
347
+ self,
348
+ spreadsheet_id: str,
349
+ range: str,
350
+ values: list[list[Any]],
351
+ **kwargs,
352
+ ) -> dict:
353
+ token = await self._get_token()
354
+
355
+ async with httpx.AsyncClient() as client:
356
+ response = await client.put(
357
+ f"{self.base_url}/{spreadsheet_id}/values/{range}",
358
+ headers={"Authorization": f"Bearer {token}"},
359
+ params={"valueInputOption": "USER_ENTERED"},
360
+ json={"values": values},
361
+ timeout=30.0,
362
+ )
363
+ response.raise_for_status()
364
+ return response.json()
365
+
366
+ async def append_row(
367
+ self,
368
+ spreadsheet_id: str,
369
+ range: str,
370
+ values: list[Any],
371
+ **kwargs,
372
+ ) -> dict:
373
+ token = await self._get_token()
374
+
375
+ async with httpx.AsyncClient() as client:
376
+ response = await client.post(
377
+ f"{self.base_url}/{spreadsheet_id}/values/{range}:append",
378
+ headers={"Authorization": f"Bearer {token}"},
379
+ params={"valueInputOption": "USER_ENTERED"},
380
+ json={"values": [values]},
381
+ timeout=30.0,
382
+ )
383
+ response.raise_for_status()
384
+ return response.json()
385
+
386
+
387
+ # ═══════════════════════════════════════════════════════════════════════════════
388
+ # GOOGLE DOCS
389
+ # ═══════════════════════════════════════════════════════════════════════════════
390
+
391
+ class GoogleDocsConnector(ProductivityConnector):
392
+ """Google Docs connector."""
393
+
394
+ name = "google_docs"
395
+
396
+ def __init__(self):
397
+ self.base_url = "https://docs.googleapis.com/v1/documents"
398
+
399
+ async def _get_token(self) -> str:
400
+ return os.environ.get("GOOGLE_DOCS_TOKEN", "")
401
+
402
+ async def get_document(self, document_id: str) -> dict:
403
+ token = await self._get_token()
404
+
405
+ async with httpx.AsyncClient() as client:
406
+ response = await client.get(
407
+ f"{self.base_url}/{document_id}",
408
+ headers={"Authorization": f"Bearer {token}"},
409
+ timeout=30.0,
410
+ )
411
+ response.raise_for_status()
412
+ return response.json()
413
+
414
+ async def create_document(self, title: str) -> dict:
415
+ token = await self._get_token()
416
+
417
+ async with httpx.AsyncClient() as client:
418
+ response = await client.post(
419
+ self.base_url,
420
+ headers={"Authorization": f"Bearer {token}"},
421
+ json={"title": title},
422
+ timeout=30.0,
423
+ )
424
+ response.raise_for_status()
425
+ return response.json()
426
+
427
+ async def insert_text(self, document_id: str, text: str, index: int = 1) -> dict:
428
+ token = await self._get_token()
429
+
430
+ async with httpx.AsyncClient() as client:
431
+ response = await client.post(
432
+ f"{self.base_url}/{document_id}:batchUpdate",
433
+ headers={"Authorization": f"Bearer {token}"},
434
+ json={
435
+ "requests": [
436
+ {
437
+ "insertText": {
438
+ "location": {"index": index},
439
+ "text": text,
440
+ }
441
+ }
442
+ ]
443
+ },
444
+ timeout=30.0,
445
+ )
446
+ response.raise_for_status()
447
+ return response.json()
448
+
449
+
450
+ # ═══════════════════════════════════════════════════════════════════════════════
451
+ # EVERNOTE
452
+ # ═══════════════════════════════════════════════════════════════════════════════
453
+
454
+ class EvernoteConnector(NoteConnector):
455
+ """Evernote connector."""
456
+
457
+ name = "evernote"
458
+
459
+ def __init__(self, token: str | None = None):
460
+ self.token = token or os.environ.get("EVERNOTE_TOKEN")
461
+ self.base_url = "https://api.evernote.com"
462
+
463
+ async def get_notes(self, limit: int = 50, **kwargs) -> list[Note]:
464
+ # Evernote uses a complex SDK - simplified HTTP approach
465
+ async with httpx.AsyncClient() as client:
466
+ response = await client.post(
467
+ f"{self.base_url}/edam/note",
468
+ headers={"Authorization": f"Bearer {self.token}"},
469
+ json={"maxNotes": limit},
470
+ timeout=30.0,
471
+ )
472
+ response.raise_for_status()
473
+ data = response.json()
474
+
475
+ return [
476
+ Note(
477
+ id=note["guid"],
478
+ title=note["title"],
479
+ content=note.get("content", ""),
480
+ created_at=datetime.fromtimestamp(note["created"] / 1000)
481
+ if note.get("created") else None,
482
+ updated_at=datetime.fromtimestamp(note["updated"] / 1000)
483
+ if note.get("updated") else None,
484
+ tags=note.get("tagNames", []),
485
+ raw=note,
486
+ )
487
+ for note in data.get("notes", [])
488
+ ]
489
+
490
+ async def create_note(self, title: str, content: str, **kwargs) -> Note:
491
+ notebook_guid = kwargs.get("notebook_guid")
492
+
493
+ async with httpx.AsyncClient() as client:
494
+ response = await client.post(
495
+ f"{self.base_url}/edam/note",
496
+ headers={"Authorization": f"Bearer {self.token}"},
497
+ json={
498
+ "title": title,
499
+ "content": f'<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE en-note SYSTEM "http://xml.evernote.com/pub/enml2.dtd"><en-note>{content}</en-note>',
500
+ "notebookGuid": notebook_guid,
501
+ },
502
+ timeout=30.0,
503
+ )
504
+ response.raise_for_status()
505
+ data = response.json()
506
+
507
+ return Note(id=data["guid"], title=title, content=content, created_at=datetime.now())
508
+
509
+ async def update_note(self, note_id: str, content: str, **kwargs) -> Note:
510
+ async with httpx.AsyncClient() as client:
511
+ response = await client.post(
512
+ f"{self.base_url}/edam/note/{note_id}",
513
+ headers={"Authorization": f"Bearer {self.token}"},
514
+ json={
515
+ "guid": note_id,
516
+ "content": f'<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE en-note SYSTEM "http://xml.evernote.com/pub/enml2.dtd"><en-note>{content}</en-note>',
517
+ },
518
+ timeout=30.0,
519
+ )
520
+ response.raise_for_status()
521
+ data = response.json()
522
+
523
+ return Note(id=note_id, title=data.get("title", ""), content=content, updated_at=datetime.now())
524
+
525
+
526
+ # ═══════════════════════════════════════════════════════════════════════════════
527
+ # ONENOTE
528
+ # ═══════════════════════════════════════════════════════════════════════════════
529
+
530
+ class OneNoteConnector(NoteConnector):
531
+ """Microsoft OneNote connector."""
532
+
533
+ name = "onenote"
534
+
535
+ def __init__(self, access_token: str | None = None):
536
+ self.token = access_token or os.environ.get("MICROSOFT_ACCESS_TOKEN")
537
+ self.base_url = "https://graph.microsoft.com/v1.0/me/onenote"
538
+
539
+ async def get_notes(self, limit: int = 50, **kwargs) -> list[Note]:
540
+ async with httpx.AsyncClient() as client:
541
+ response = await client.get(
542
+ f"{self.base_url}/pages",
543
+ headers={"Authorization": f"Bearer {self.token}"},
544
+ params={"$top": limit},
545
+ timeout=30.0,
546
+ )
547
+ response.raise_for_status()
548
+ data = response.json()
549
+
550
+ return [
551
+ Note(
552
+ id=page["id"],
553
+ title=page["title"],
554
+ content="", # Content requires separate call
555
+ created_at=datetime.fromisoformat(page["createdDateTime"].replace("Z", "+00:00"))
556
+ if page.get("createdDateTime") else None,
557
+ updated_at=datetime.fromisoformat(page["lastModifiedDateTime"].replace("Z", "+00:00"))
558
+ if page.get("lastModifiedDateTime") else None,
559
+ raw=page,
560
+ )
561
+ for page in data.get("value", [])
562
+ ]
563
+
564
+ async def create_note(self, title: str, content: str, **kwargs) -> Note:
565
+ section_id = kwargs.get("section_id")
566
+
567
+ html_content = f"""
568
+ <!DOCTYPE html>
569
+ <html>
570
+ <head><title>{title}</title></head>
571
+ <body>{content}</body>
572
+ </html>
573
+ """
574
+
575
+ async with httpx.AsyncClient() as client:
576
+ response = await client.post(
577
+ f"{self.base_url}/sections/{section_id}/pages",
578
+ headers={
579
+ "Authorization": f"Bearer {self.token}",
580
+ "Content-Type": "application/xhtml+xml",
581
+ },
582
+ content=html_content,
583
+ timeout=30.0,
584
+ )
585
+ response.raise_for_status()
586
+ data = response.json()
587
+
588
+ return Note(id=data["id"], title=title, content=content, created_at=datetime.now())
589
+
590
+ async def update_note(self, note_id: str, content: str, **kwargs) -> Note:
591
+ async with httpx.AsyncClient() as client:
592
+ response = await client.patch(
593
+ f"{self.base_url}/pages/{note_id}/content",
594
+ headers={
595
+ "Authorization": f"Bearer {self.token}",
596
+ "Content-Type": "application/json",
597
+ },
598
+ json=[
599
+ {
600
+ "target": "body",
601
+ "action": "replace",
602
+ "content": content,
603
+ }
604
+ ],
605
+ timeout=30.0,
606
+ )
607
+ response.raise_for_status()
608
+
609
+ return Note(id=note_id, title="", content=content, updated_at=datetime.now())
610
+
611
+
612
+ # ═══════════════════════════════════════════════════════════════════════════════
613
+ # CLICKUP
614
+ # ═══════════════════════════════════════════════════════════════════════════════
615
+
616
+ class ClickUpConnector(ProductivityConnector):
617
+ """ClickUp project management connector."""
618
+
619
+ name = "clickup"
620
+
621
+ def __init__(self, api_key: str | None = None):
622
+ self.api_key = api_key or os.environ.get("CLICKUP_API_KEY")
623
+ self.base_url = "https://api.clickup.com/api/v2"
624
+
625
+ async def get_tasks(self, list_id: str, **kwargs) -> list[dict]:
626
+ async with httpx.AsyncClient() as client:
627
+ response = await client.get(
628
+ f"{self.base_url}/list/{list_id}/task",
629
+ headers={"Authorization": self.api_key},
630
+ timeout=30.0,
631
+ )
632
+ response.raise_for_status()
633
+ return response.json().get("tasks", [])
634
+
635
+ async def create_task(self, list_id: str, name: str, **kwargs) -> dict:
636
+ async with httpx.AsyncClient() as client:
637
+ response = await client.post(
638
+ f"{self.base_url}/list/{list_id}/task",
639
+ headers={"Authorization": self.api_key},
640
+ json={"name": name, **kwargs},
641
+ timeout=30.0,
642
+ )
643
+ response.raise_for_status()
644
+ return response.json()
645
+
646
+ async def update_task(self, task_id: str, **kwargs) -> dict:
647
+ async with httpx.AsyncClient() as client:
648
+ response = await client.put(
649
+ f"{self.base_url}/task/{task_id}",
650
+ headers={"Authorization": self.api_key},
651
+ json=kwargs,
652
+ timeout=30.0,
653
+ )
654
+ response.raise_for_status()
655
+ return response.json()
656
+
657
+
658
+ # ═══════════════════════════════════════════════════════════════════════════════
659
+ # MONDAY.COM
660
+ # ═══════════════════════════════════════════════════════════════════════════════
661
+
662
+ class MondayConnector(ProductivityConnector):
663
+ """Monday.com project management connector."""
664
+
665
+ name = "monday"
666
+
667
+ def __init__(self, api_key: str | None = None):
668
+ self.api_key = api_key or os.environ.get("MONDAY_API_KEY")
669
+ self.base_url = "https://api.monday.com/v2"
670
+
671
+ async def _query(self, query: str, variables: dict | None = None) -> dict:
672
+ async with httpx.AsyncClient() as client:
673
+ response = await client.post(
674
+ self.base_url,
675
+ headers={
676
+ "Authorization": self.api_key,
677
+ "Content-Type": "application/json",
678
+ },
679
+ json={"query": query, "variables": variables or {}},
680
+ timeout=30.0,
681
+ )
682
+ response.raise_for_status()
683
+ return response.json()
684
+
685
+ async def get_boards(self) -> list[dict]:
686
+ query = "{ boards { id name } }"
687
+ result = await self._query(query)
688
+ return result.get("data", {}).get("boards", [])
689
+
690
+ async def get_items(self, board_id: str, limit: int = 50) -> list[dict]:
691
+ query = f"""
692
+ {{
693
+ boards(ids: [{board_id}]) {{
694
+ items_page(limit: {limit}) {{
695
+ items {{
696
+ id
697
+ name
698
+ state
699
+ column_values {{
700
+ id
701
+ text
702
+ }}
703
+ }}
704
+ }}
705
+ }}
706
+ }}
707
+ """
708
+ result = await self._query(query)
709
+ boards = result.get("data", {}).get("boards", [])
710
+ if boards:
711
+ return boards[0].get("items_page", {}).get("items", [])
712
+ return []
713
+
714
+ async def create_item(self, board_id: str, name: str, **kwargs) -> dict:
715
+ column_values = kwargs.get("column_values", {})
716
+ import json
717
+
718
+ query = """
719
+ mutation ($boardId: ID!, $itemName: String!, $columnValues: JSON) {
720
+ create_item(board_id: $boardId, item_name: $itemName, column_values: $columnValues) {
721
+ id
722
+ name
723
+ }
724
+ }
725
+ """
726
+ result = await self._query(
727
+ query,
728
+ {
729
+ "boardId": board_id,
730
+ "itemName": name,
731
+ "columnValues": json.dumps(column_values),
732
+ },
733
+ )
734
+ return result.get("data", {}).get("create_item", {})
735
+
736
+
737
+ # ═══════════════════════════════════════════════════════════════════════════════
738
+ # PROVIDER FACTORY
739
+ # ═══════════════════════════════════════════════════════════════════════════════
740
+
741
+ PRODUCTIVITY_CONNECTORS: dict[str, type[ProductivityConnector]] = {
742
+ "obsidian": ObsidianConnector,
743
+ "apple_notes": AppleNotesConnector,
744
+ "apple_calendar": AppleCalendarConnector,
745
+ "google_sheets": GoogleSheetsConnector,
746
+ "sheets": GoogleSheetsConnector,
747
+ "google_docs": GoogleDocsConnector,
748
+ "docs": GoogleDocsConnector,
749
+ "evernote": EvernoteConnector,
750
+ "onenote": OneNoteConnector,
751
+ "clickup": ClickUpConnector,
752
+ "monday": MondayConnector,
753
+ }
754
+
755
+
756
+ def get_productivity_connector(name: str, **kwargs) -> ProductivityConnector:
757
+ """Get a productivity connector by name."""
758
+ name = name.lower()
759
+
760
+ if name not in PRODUCTIVITY_CONNECTORS:
761
+ available = ", ".join(sorted(PRODUCTIVITY_CONNECTORS.keys()))
762
+ raise ValueError(f"Unknown productivity connector: {name}. Available: {available}")
763
+
764
+ return PRODUCTIVITY_CONNECTORS[name](**kwargs)