@lobu/cli 6.0.1 → 7.0.0

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 (265) hide show
  1. package/README.md +20 -27
  2. package/dist/bundled-skills/lobu/SKILL.md +11 -11
  3. package/dist/commands/_lib/apply/apply-cmd.d.ts +38 -0
  4. package/dist/commands/_lib/apply/apply-cmd.d.ts.map +1 -1
  5. package/dist/commands/_lib/apply/apply-cmd.js +574 -40
  6. package/dist/commands/_lib/apply/apply-cmd.js.map +1 -1
  7. package/dist/commands/_lib/apply/client.d.ts +180 -1
  8. package/dist/commands/_lib/apply/client.d.ts.map +1 -1
  9. package/dist/commands/_lib/apply/client.js +308 -28
  10. package/dist/commands/_lib/apply/client.js.map +1 -1
  11. package/dist/commands/_lib/apply/desired-state.d.ts +134 -3
  12. package/dist/commands/_lib/apply/desired-state.d.ts.map +1 -1
  13. package/dist/commands/_lib/apply/desired-state.js +703 -89
  14. package/dist/commands/_lib/apply/desired-state.js.map +1 -1
  15. package/dist/commands/_lib/apply/diff.d.ts +61 -3
  16. package/dist/commands/_lib/apply/diff.d.ts.map +1 -1
  17. package/dist/commands/_lib/apply/diff.js +382 -92
  18. package/dist/commands/_lib/apply/diff.js.map +1 -1
  19. package/dist/commands/_lib/apply/prompt.d.ts +6 -0
  20. package/dist/commands/_lib/apply/prompt.d.ts.map +1 -1
  21. package/dist/commands/_lib/apply/prompt.js +16 -0
  22. package/dist/commands/_lib/apply/prompt.js.map +1 -1
  23. package/dist/commands/_lib/apply/render.d.ts +9 -0
  24. package/dist/commands/_lib/apply/render.d.ts.map +1 -1
  25. package/dist/commands/_lib/apply/render.js +80 -3
  26. package/dist/commands/_lib/apply/render.js.map +1 -1
  27. package/dist/commands/agent.d.ts +7 -0
  28. package/dist/commands/agent.d.ts.map +1 -1
  29. package/dist/commands/agent.js +65 -1
  30. package/dist/commands/agent.js.map +1 -1
  31. package/dist/commands/chat.d.ts +12 -9
  32. package/dist/commands/chat.d.ts.map +1 -1
  33. package/dist/commands/chat.js +125 -57
  34. package/dist/commands/chat.js.map +1 -1
  35. package/dist/commands/dev.d.ts +23 -7
  36. package/dist/commands/dev.d.ts.map +1 -1
  37. package/dist/commands/dev.js +197 -49
  38. package/dist/commands/dev.js.map +1 -1
  39. package/dist/commands/doctor.d.ts +1 -0
  40. package/dist/commands/doctor.d.ts.map +1 -1
  41. package/dist/commands/doctor.js +136 -0
  42. package/dist/commands/doctor.js.map +1 -1
  43. package/dist/commands/eval.d.ts +8 -0
  44. package/dist/commands/eval.d.ts.map +1 -1
  45. package/dist/commands/eval.js +72 -6
  46. package/dist/commands/eval.js.map +1 -1
  47. package/dist/commands/init.d.ts +22 -5
  48. package/dist/commands/init.d.ts.map +1 -1
  49. package/dist/commands/init.js +355 -182
  50. package/dist/commands/init.js.map +1 -1
  51. package/dist/commands/link.d.ts +11 -0
  52. package/dist/commands/link.d.ts.map +1 -0
  53. package/dist/commands/link.js +28 -0
  54. package/dist/commands/link.js.map +1 -0
  55. package/dist/commands/login.d.ts.map +1 -1
  56. package/dist/commands/login.js +14 -2
  57. package/dist/commands/login.js.map +1 -1
  58. package/dist/commands/memory/_lib/browser-auth-cmd.d.ts.map +1 -1
  59. package/dist/commands/memory/_lib/browser-auth-cmd.js +3 -3
  60. package/dist/commands/memory/_lib/browser-auth-cmd.js.map +1 -1
  61. package/dist/commands/memory/_lib/mcp.d.ts +2 -2
  62. package/dist/commands/memory/_lib/mcp.d.ts.map +1 -1
  63. package/dist/commands/memory/_lib/mcp.js +24 -12
  64. package/dist/commands/memory/_lib/mcp.js.map +1 -1
  65. package/dist/commands/memory/_lib/openclaw-auth.d.ts +1 -0
  66. package/dist/commands/memory/_lib/openclaw-auth.d.ts.map +1 -1
  67. package/dist/commands/memory/_lib/openclaw-auth.js +14 -3
  68. package/dist/commands/memory/_lib/openclaw-auth.js.map +1 -1
  69. package/dist/commands/memory/_lib/openclaw-cmd.js +1 -1
  70. package/dist/commands/memory/_lib/openclaw-cmd.js.map +1 -1
  71. package/dist/commands/memory/_lib/schema.d.ts +29 -2
  72. package/dist/commands/memory/_lib/schema.d.ts.map +1 -1
  73. package/dist/commands/memory/_lib/schema.js +121 -5
  74. package/dist/commands/memory/_lib/schema.js.map +1 -1
  75. package/dist/commands/memory/_lib/seed-cmd.d.ts.map +1 -1
  76. package/dist/commands/memory/_lib/seed-cmd.js +46 -24
  77. package/dist/commands/memory/_lib/seed-cmd.js.map +1 -1
  78. package/dist/commands/memory/run.d.ts.map +1 -1
  79. package/dist/commands/memory/run.js +2 -2
  80. package/dist/commands/memory/run.js.map +1 -1
  81. package/dist/commands/org.d.ts +4 -0
  82. package/dist/commands/org.d.ts.map +1 -1
  83. package/dist/commands/org.js +10 -0
  84. package/dist/commands/org.js.map +1 -1
  85. package/dist/commands/platforms/platform-prompts.d.ts +0 -1
  86. package/dist/commands/platforms/platform-prompts.d.ts.map +1 -1
  87. package/dist/commands/platforms/platform-prompts.js +54 -8
  88. package/dist/commands/platforms/platform-prompts.js.map +1 -1
  89. package/dist/commands/telemetry.d.ts +10 -0
  90. package/dist/commands/telemetry.d.ts.map +1 -0
  91. package/dist/commands/telemetry.js +68 -0
  92. package/dist/commands/telemetry.js.map +1 -0
  93. package/dist/commands/token.d.ts +9 -0
  94. package/dist/commands/token.d.ts.map +1 -1
  95. package/dist/commands/token.js +54 -0
  96. package/dist/commands/token.js.map +1 -1
  97. package/dist/commands/whoami.d.ts.map +1 -1
  98. package/dist/commands/whoami.js +1 -1
  99. package/dist/commands/whoami.js.map +1 -1
  100. package/dist/connectors/README.md +534 -0
  101. package/dist/connectors/__tests__/browser-scraper-utils.test.ts +186 -0
  102. package/dist/connectors/apple_health.ts +138 -0
  103. package/dist/connectors/apple_screen_time.ts +82 -0
  104. package/dist/connectors/browser-scraper-utils.ts +246 -0
  105. package/dist/connectors/capterra.ts +277 -0
  106. package/dist/connectors/g2.ts +290 -0
  107. package/dist/connectors/github.ts +1530 -0
  108. package/dist/connectors/glassdoor.ts +295 -0
  109. package/dist/connectors/gmaps.ts +197 -0
  110. package/dist/connectors/google_calendar.ts +641 -0
  111. package/dist/connectors/google_gmail.ts +754 -0
  112. package/dist/connectors/google_photos.ts +776 -0
  113. package/dist/connectors/google_play.ts +349 -0
  114. package/dist/connectors/hackernews.ts +471 -0
  115. package/dist/connectors/index.ts +28 -0
  116. package/dist/connectors/ios_appstore.ts +226 -0
  117. package/dist/connectors/linkedin.ts +494 -0
  118. package/dist/connectors/local_directory.ts +91 -0
  119. package/dist/connectors/microsoft_outlook.ts +410 -0
  120. package/dist/connectors/producthunt.ts +471 -0
  121. package/dist/connectors/reddit.ts +600 -0
  122. package/dist/connectors/revolut.ts +572 -0
  123. package/dist/connectors/rss.ts +448 -0
  124. package/dist/connectors/spotify.ts +590 -0
  125. package/dist/connectors/trustpilot.ts +203 -0
  126. package/dist/connectors/website.ts +629 -0
  127. package/dist/connectors/whatsapp.ts +1081 -0
  128. package/dist/connectors/whatsapp_local.ts +125 -0
  129. package/dist/connectors/x.ts +536 -0
  130. package/dist/connectors/youtube.ts +666 -0
  131. package/dist/db/migrations/00000000000000_baseline.sql +4867 -0
  132. package/dist/db/migrations/20260405193000_add_mcp_sessions.sql +33 -0
  133. package/dist/db/migrations/20260408120000_remove_system_connectors.sql +48 -0
  134. package/dist/db/migrations/20260408120001_optional_compiled_code.sql +6 -0
  135. package/dist/db/migrations/20260409110000_add_active_watcher_run_index.sql +9 -0
  136. package/dist/db/migrations/20260409130000_connector_default_config.sql +5 -0
  137. package/dist/db/migrations/20260410120000_add_agent_secrets.sql +25 -0
  138. package/dist/db/migrations/20260413170000_add_watcher_group_id.sql +67 -0
  139. package/dist/db/migrations/20260416120000_add_entity_wa_jid_index.sql +14 -0
  140. package/dist/db/migrations/20260417100000_add_entity_identities.sql +77 -0
  141. package/dist/db/migrations/20260418100000_add_auth_runs.sql +83 -0
  142. package/dist/db/migrations/20260418110000_add_runs_created_by_user.sql +18 -0
  143. package/dist/db/migrations/20260419120000_add_event_identity_indexes.sql +56 -0
  144. package/dist/db/migrations/20260420120000_extend_reserved_org_slugs.sql +56 -0
  145. package/dist/db/migrations/20260424030000_add_watcher_run_correlation.sql +52 -0
  146. package/dist/db/migrations/20260424130000_relax_events_client_id_fk.sql +47 -0
  147. package/dist/db/migrations/20260425100000_normalize_watcher_feedback.sql +91 -0
  148. package/dist/db/migrations/20260425120000_add_run_diagnostics.sql +20 -0
  149. package/dist/db/migrations/20260425130000_add_repair_agent_plumbing.sql +46 -0
  150. package/dist/db/migrations/20260426120000_entities_entity_type_fk.sql +101 -0
  151. package/dist/db/migrations/20260426130000_db_integrity_cleanup.sql +104 -0
  152. package/dist/db/migrations/20260426130001_db_integrity_cleanup_concurrent.sql +187 -0
  153. package/dist/db/migrations/20260427133000_events_created_by_nullable.sql +74 -0
  154. package/dist/db/migrations/20260427140000_identity_engine_indexes.sql +140 -0
  155. package/dist/db/migrations/20260427150000_drop_events_source_id.sql +177 -0
  156. package/dist/db/migrations/20260427160000_drop_dead_schema.sql +76 -0
  157. package/dist/db/migrations/20260427170000_market_founder_to_member.sql +364 -0
  158. package/dist/db/migrations/20260428040000_cascade_events_watchers_org_fk.sql +66 -0
  159. package/dist/db/migrations/20260428050000_add_runs_approved_input.sql +9 -0
  160. package/dist/db/migrations/20260429010000_auth_profile_tenant_scoped_fk.sql +79 -0
  161. package/dist/db/migrations/20260429060000_extend_runs_for_lobu_queue.sql +108 -0
  162. package/dist/db/migrations/20260429120000_agent_changed_notify.sql +97 -0
  163. package/dist/db/migrations/20260429120100_user_auth_profiles_and_model_prefs.sql +36 -0
  164. package/dist/db/migrations/20260429120200_fix_notify_old_keys.sql +130 -0
  165. package/dist/db/migrations/20260429130000_oauth_states_cli_sessions_rate_limits.sql +83 -0
  166. package/dist/db/migrations/20260429140000_phase8_grants_chat_connections_mcp_sessions.sql +84 -0
  167. package/dist/db/migrations/20260429140100_runs_priority_expires_at_retry_delay.sql +44 -0
  168. package/dist/db/migrations/20260429180000_drop_invalidatable_cache_triggers.sql +25 -0
  169. package/dist/db/migrations/20260430005614_agents_apply_fields.sql +21 -0
  170. package/dist/db/migrations/20260430022231_fix_connection_config_encryption.sql +69 -0
  171. package/dist/db/migrations/20260430151215_add_task_run_type.sql +77 -0
  172. package/dist/db/migrations/20260501000000_drop_cli_sessions.sql +27 -0
  173. package/dist/db/migrations/20260501133000_lobu_memory_mcp_id.sql +117 -0
  174. package/dist/db/migrations/20260502000000_drop_chat_connections.sql +60 -0
  175. package/dist/db/migrations/20260503000000_agent_secrets_org_scope.sql +56 -0
  176. package/dist/db/migrations/20260504000000_flatten_agents_drop_sandbox_model.sql +48 -0
  177. package/dist/db/migrations/20260510220000_connector_required_capability.sql +47 -0
  178. package/dist/db/migrations/20260512000000_device_worker_connection_binding.sql +113 -0
  179. package/dist/db/migrations/20260512131703_connections_slug.sql +131 -0
  180. package/dist/db/migrations/20260513000000_chat_user_identities.sql +24 -0
  181. package/dist/db/migrations/20260513120000_auth_profiles_device_binding.sql +50 -0
  182. package/dist/db/migrations/20260513150000_auth_profiles_cdp_url.sql +43 -0
  183. package/dist/db/migrations/20260513200000_notifications_as_events.sql +86 -0
  184. package/dist/db/migrations/20260514000000_scheduled_jobs.sql +97 -0
  185. package/dist/db/migrations/20260514120000_auth_profiles_connector_key_nullable.sql +42 -0
  186. package/dist/eval/types.d.ts +2 -0
  187. package/dist/eval/types.d.ts.map +1 -1
  188. package/dist/index.d.ts +11 -0
  189. package/dist/index.d.ts.map +1 -1
  190. package/dist/index.js +210 -132
  191. package/dist/index.js.map +1 -1
  192. package/dist/internal/api-client.d.ts +4 -8
  193. package/dist/internal/api-client.d.ts.map +1 -1
  194. package/dist/internal/api-client.js +1 -1
  195. package/dist/internal/api-client.js.map +1 -1
  196. package/dist/internal/context.js +2 -2
  197. package/dist/internal/context.js.map +1 -1
  198. package/dist/internal/credentials.d.ts.map +1 -1
  199. package/dist/internal/credentials.js +6 -1
  200. package/dist/internal/credentials.js.map +1 -1
  201. package/dist/internal/gateway-url.d.ts +14 -0
  202. package/dist/internal/gateway-url.d.ts.map +1 -1
  203. package/dist/internal/gateway-url.js +19 -0
  204. package/dist/internal/gateway-url.js.map +1 -1
  205. package/dist/internal/index.d.ts +3 -4
  206. package/dist/internal/index.d.ts.map +1 -1
  207. package/dist/internal/index.js +3 -3
  208. package/dist/internal/index.js.map +1 -1
  209. package/dist/internal/oauth.d.ts +6 -5
  210. package/dist/internal/oauth.d.ts.map +1 -1
  211. package/dist/internal/oauth.js +2 -2
  212. package/dist/internal/project-link.d.ts +10 -0
  213. package/dist/internal/project-link.d.ts.map +1 -0
  214. package/dist/internal/project-link.js +48 -0
  215. package/dist/internal/project-link.js.map +1 -0
  216. package/dist/providers.json +2 -2
  217. package/dist/server.bundle.mjs +31654 -30866
  218. package/dist/start-local.bundle.mjs +74409 -0
  219. package/dist/templates/README.md.tmpl +10 -11
  220. package/dist/templates/TESTING.md.tmpl +9 -9
  221. package/package.json +15 -13
  222. package/dist/__tests__/chat.integration.test.d.ts +0 -2
  223. package/dist/__tests__/chat.integration.test.d.ts.map +0 -1
  224. package/dist/__tests__/chat.integration.test.js +0 -337
  225. package/dist/__tests__/chat.integration.test.js.map +0 -1
  226. package/dist/__tests__/dev.test.d.ts +0 -2
  227. package/dist/__tests__/dev.test.d.ts.map +0 -1
  228. package/dist/__tests__/dev.test.js +0 -25
  229. package/dist/__tests__/dev.test.js.map +0 -1
  230. package/dist/__tests__/init-memory.test.d.ts +0 -2
  231. package/dist/__tests__/init-memory.test.d.ts.map +0 -1
  232. package/dist/__tests__/init-memory.test.js +0 -45
  233. package/dist/__tests__/init-memory.test.js.map +0 -1
  234. package/dist/__tests__/token.test.d.ts +0 -2
  235. package/dist/__tests__/token.test.d.ts.map +0 -1
  236. package/dist/__tests__/token.test.js +0 -52
  237. package/dist/__tests__/token.test.js.map +0 -1
  238. package/dist/commands/_lib/apply/__tests__/client.test.d.ts +0 -2
  239. package/dist/commands/_lib/apply/__tests__/client.test.d.ts.map +0 -1
  240. package/dist/commands/_lib/apply/__tests__/client.test.js +0 -23
  241. package/dist/commands/_lib/apply/__tests__/client.test.js.map +0 -1
  242. package/dist/commands/_lib/apply/__tests__/desired-state.test.d.ts +0 -2
  243. package/dist/commands/_lib/apply/__tests__/desired-state.test.d.ts.map +0 -1
  244. package/dist/commands/_lib/apply/__tests__/desired-state.test.js +0 -140
  245. package/dist/commands/_lib/apply/__tests__/desired-state.test.js.map +0 -1
  246. package/dist/commands/_lib/apply/__tests__/diff.test.d.ts +0 -2
  247. package/dist/commands/_lib/apply/__tests__/diff.test.d.ts.map +0 -1
  248. package/dist/commands/_lib/apply/__tests__/diff.test.js +0 -378
  249. package/dist/commands/_lib/apply/__tests__/diff.test.js.map +0 -1
  250. package/dist/commands/apply.d.ts +0 -3
  251. package/dist/commands/apply.d.ts.map +0 -1
  252. package/dist/commands/apply.js +0 -5
  253. package/dist/commands/apply.js.map +0 -1
  254. package/dist/commands/memory/_lib/openclaw-auth.test.d.ts +0 -2
  255. package/dist/commands/memory/_lib/openclaw-auth.test.d.ts.map +0 -1
  256. package/dist/commands/memory/_lib/openclaw-auth.test.js +0 -9
  257. package/dist/commands/memory/_lib/openclaw-auth.test.js.map +0 -1
  258. package/dist/internal/__tests__/api-client.test.d.ts +0 -2
  259. package/dist/internal/__tests__/api-client.test.d.ts.map +0 -1
  260. package/dist/internal/__tests__/api-client.test.js +0 -95
  261. package/dist/internal/__tests__/api-client.test.js.map +0 -1
  262. package/dist/internal/__tests__/context.test.d.ts +0 -2
  263. package/dist/internal/__tests__/context.test.d.ts.map +0 -1
  264. package/dist/internal/__tests__/context.test.js +0 -77
  265. package/dist/internal/__tests__/context.test.js.map +0 -1
@@ -0,0 +1,410 @@
1
+ /**
2
+ * Microsoft Outlook Connector (V1 runtime)
3
+ *
4
+ * Syncs emails and calendar events from Microsoft 365 via the Microsoft Graph API.
5
+ * Auth via OAuth with Microsoft identity platform.
6
+ */
7
+
8
+ import {
9
+ type ActionContext,
10
+ type ActionResult,
11
+ type ConnectorDefinition,
12
+ ConnectorRuntime,
13
+ type EventEnvelope,
14
+ type SyncContext,
15
+ type SyncResult,
16
+ } from '@lobu/connector-sdk';
17
+
18
+ // ---------------------------------------------------------------------------
19
+ // Microsoft Graph API types
20
+ // ---------------------------------------------------------------------------
21
+
22
+ interface GraphMessage {
23
+ id: string;
24
+ conversationId: string;
25
+ subject: string;
26
+ bodyPreview: string;
27
+ body: { contentType: string; content: string };
28
+ from: { emailAddress: { name: string; address: string } };
29
+ toRecipients: Array<{ emailAddress: { name: string; address: string } }>;
30
+ ccRecipients: Array<{ emailAddress: { name: string; address: string } }>;
31
+ receivedDateTime: string;
32
+ sentDateTime: string;
33
+ hasAttachments: boolean;
34
+ importance: string;
35
+ isRead: boolean;
36
+ webLink: string;
37
+ parentFolderId: string;
38
+ }
39
+
40
+ interface GraphEvent {
41
+ id: string;
42
+ subject: string;
43
+ bodyPreview: string;
44
+ body: { contentType: string; content: string };
45
+ organizer: { emailAddress: { name: string; address: string } };
46
+ attendees: Array<{
47
+ emailAddress: { name: string; address: string };
48
+ type: string;
49
+ status: { response: string };
50
+ }>;
51
+ start: { dateTime: string; timeZone: string };
52
+ end: { dateTime: string; timeZone: string };
53
+ location: { displayName: string };
54
+ isAllDay: boolean;
55
+ isCancelled: boolean;
56
+ webLink: string;
57
+ createdDateTime: string;
58
+ }
59
+
60
+ interface GraphPagedResponse<T> {
61
+ value: T[];
62
+ '@odata.nextLink'?: string;
63
+ }
64
+
65
+ // ---------------------------------------------------------------------------
66
+ // Checkpoint
67
+ // ---------------------------------------------------------------------------
68
+
69
+ interface OutlookCheckpoint {
70
+ last_sync_at?: string;
71
+ }
72
+
73
+ // ---------------------------------------------------------------------------
74
+ // Helpers
75
+ // ---------------------------------------------------------------------------
76
+
77
+ function formatRecipients(
78
+ recipients: Array<{ emailAddress: { name: string; address: string } }>
79
+ ): string {
80
+ return recipients.map((r) => r.emailAddress.name || r.emailAddress.address).join(', ');
81
+ }
82
+
83
+ // ---------------------------------------------------------------------------
84
+ // Connector
85
+ // ---------------------------------------------------------------------------
86
+
87
+ export default class MicrosoftOutlookConnector extends ConnectorRuntime {
88
+ readonly definition: ConnectorDefinition = {
89
+ key: 'microsoft.outlook',
90
+ name: 'Microsoft Outlook',
91
+ description: 'Syncs emails and calendar events from Microsoft 365 via Graph API.',
92
+ version: '1.0.0',
93
+ faviconDomain: 'outlook.com',
94
+ authSchema: {
95
+ methods: [
96
+ {
97
+ type: 'oauth',
98
+ provider: 'microsoft',
99
+ requiredScopes: [
100
+ 'openid',
101
+ 'email',
102
+ 'profile',
103
+ 'offline_access',
104
+ 'Mail.Read',
105
+ 'Calendars.Read',
106
+ ],
107
+ optionalScopes: ['Mail.Send'],
108
+ loginScopes: ['openid', 'email', 'profile', 'offline_access', 'User.Read'],
109
+ clientIdKey: 'MICROSOFT_CLIENT_ID',
110
+ clientSecretKey: 'MICROSOFT_CLIENT_SECRET',
111
+ tokenUrl: 'https://login.microsoftonline.com/common/oauth2/v2.0/token',
112
+ tokenEndpointAuthMethod: 'client_secret_post',
113
+ loginProvisioning: {
114
+ autoCreateConnection: true,
115
+ },
116
+ setupInstructions:
117
+ 'Register an app in the Azure Portal (Entra ID > App registrations). Add {{redirect_uri}} as a redirect URI under "Web", then copy the Application (client) ID and create a client secret under Certificates & secrets.',
118
+ },
119
+ ],
120
+ },
121
+ feeds: {
122
+ messages: {
123
+ key: 'messages',
124
+ name: 'Messages',
125
+ requiredScopes: ['Mail.Read'],
126
+ description: 'Syncs email messages from Outlook.',
127
+ configSchema: {
128
+ type: 'object',
129
+ properties: {
130
+ folder: {
131
+ type: 'string',
132
+ default: 'inbox',
133
+ description: 'Mail folder to sync (e.g. "inbox", "sentitems", "drafts").',
134
+ },
135
+ max_results: {
136
+ type: 'integer',
137
+ minimum: 1,
138
+ maximum: 500,
139
+ default: 50,
140
+ description: 'Maximum messages to fetch per sync.',
141
+ },
142
+ lookback_days: {
143
+ type: 'integer',
144
+ minimum: 1,
145
+ maximum: 365,
146
+ default: 30,
147
+ description: 'How many days back to look on initial sync.',
148
+ },
149
+ },
150
+ },
151
+ eventKinds: {
152
+ email: {
153
+ description: 'An email message from Outlook',
154
+ metadataSchema: {
155
+ type: 'object',
156
+ properties: {
157
+ from: { type: 'string' },
158
+ to: { type: 'string' },
159
+ cc: { type: 'string' },
160
+ importance: { type: 'string' },
161
+ has_attachments: { type: 'boolean' },
162
+ is_read: { type: 'boolean' },
163
+ },
164
+ },
165
+ },
166
+ },
167
+ },
168
+ calendar: {
169
+ key: 'calendar',
170
+ name: 'Calendar Events',
171
+ requiredScopes: ['Calendars.Read'],
172
+ description: 'Syncs calendar events from Outlook.',
173
+ configSchema: {
174
+ type: 'object',
175
+ properties: {
176
+ lookback_days: {
177
+ type: 'integer',
178
+ minimum: 0,
179
+ maximum: 365,
180
+ default: 7,
181
+ description: 'How many days back to look for events.',
182
+ },
183
+ lookahead_days: {
184
+ type: 'integer',
185
+ minimum: 1,
186
+ maximum: 365,
187
+ default: 30,
188
+ description: 'How many days ahead to look for events.',
189
+ },
190
+ max_results: {
191
+ type: 'integer',
192
+ minimum: 1,
193
+ maximum: 500,
194
+ default: 100,
195
+ description: 'Maximum events to fetch per sync.',
196
+ },
197
+ },
198
+ },
199
+ eventKinds: {
200
+ calendar_event: {
201
+ description: 'A calendar event from Outlook',
202
+ metadataSchema: {
203
+ type: 'object',
204
+ properties: {
205
+ organizer: { type: 'string' },
206
+ location: { type: 'string' },
207
+ attendee_count: { type: 'number' },
208
+ is_all_day: { type: 'boolean' },
209
+ is_cancelled: { type: 'boolean' },
210
+ start_time: { type: 'string' },
211
+ end_time: { type: 'string' },
212
+ },
213
+ },
214
+ },
215
+ },
216
+ },
217
+ },
218
+ };
219
+
220
+ private readonly API_BASE = 'https://graph.microsoft.com/v1.0';
221
+ private readonly PAGE_SIZE = 50;
222
+ private readonly MAX_PAGES = 10;
223
+
224
+ // -------------------------------------------------------------------------
225
+ // sync
226
+ // -------------------------------------------------------------------------
227
+
228
+ async sync(ctx: SyncContext): Promise<SyncResult> {
229
+ const accessToken = ctx.credentials?.accessToken;
230
+ if (!accessToken) {
231
+ throw new Error('Microsoft Outlook requires OAuth authentication.');
232
+ }
233
+
234
+ switch (ctx.feedKey) {
235
+ case 'messages':
236
+ return this.syncMessages(ctx, accessToken);
237
+ case 'calendar':
238
+ return this.syncCalendar(ctx, accessToken);
239
+ default:
240
+ throw new Error(`Unknown feed: ${ctx.feedKey}`);
241
+ }
242
+ }
243
+
244
+ // -------------------------------------------------------------------------
245
+ // execute
246
+ // -------------------------------------------------------------------------
247
+
248
+ async execute(_ctx: ActionContext): Promise<ActionResult> {
249
+ return { success: false, error: 'Actions not supported' };
250
+ }
251
+
252
+ // -------------------------------------------------------------------------
253
+ // Feed: messages
254
+ // -------------------------------------------------------------------------
255
+
256
+ private async syncMessages(ctx: SyncContext, accessToken: string): Promise<SyncResult> {
257
+ const config = ctx.config as Record<string, unknown>;
258
+ const folder = (config.folder as string) ?? 'inbox';
259
+ const maxResults = (config.max_results as number) ?? 50;
260
+ const lookbackDays = (config.lookback_days as number) ?? 30;
261
+
262
+ const since = new Date();
263
+ since.setDate(since.getDate() - lookbackDays);
264
+ const sinceFilter = since.toISOString();
265
+
266
+ const events: EventEnvelope[] = [];
267
+ let url =
268
+ `${this.API_BASE}/me/mailFolders/${folder}/messages` +
269
+ `?$top=${Math.min(maxResults, this.PAGE_SIZE)}` +
270
+ '&$orderby=receivedDateTime desc' +
271
+ `&$filter=receivedDateTime ge ${sinceFilter}` +
272
+ '&$select=id,conversationId,subject,bodyPreview,from,toRecipients,ccRecipients,receivedDateTime,sentDateTime,hasAttachments,importance,isRead,webLink';
273
+
274
+ let fetched = 0;
275
+
276
+ for (let page = 0; page < this.MAX_PAGES && fetched < maxResults; page++) {
277
+ const data = await this.graphGet<GraphPagedResponse<GraphMessage>>(url, accessToken);
278
+
279
+ for (const msg of data.value) {
280
+ if (fetched >= maxResults) break;
281
+ events.push({
282
+ origin_id: `outlook_msg_${msg.id}`,
283
+ title: msg.subject,
284
+ payload_text: msg.bodyPreview || msg.subject,
285
+ author_name: msg.from?.emailAddress?.name || msg.from?.emailAddress?.address,
286
+ source_url: msg.webLink,
287
+ occurred_at: new Date(msg.receivedDateTime),
288
+ origin_type: 'email',
289
+ metadata: {
290
+ from: msg.from?.emailAddress?.address,
291
+ to: formatRecipients(msg.toRecipients ?? []),
292
+ cc: formatRecipients(msg.ccRecipients ?? []),
293
+ importance: msg.importance,
294
+ has_attachments: msg.hasAttachments,
295
+ is_read: msg.isRead,
296
+ },
297
+ });
298
+ fetched++;
299
+ }
300
+
301
+ if (ctx.emitEvents) await ctx.emitEvents(events.splice(0));
302
+
303
+ if (!data['@odata.nextLink'] || fetched >= maxResults) break;
304
+ url = data['@odata.nextLink'];
305
+ }
306
+
307
+ return {
308
+ events,
309
+ checkpoint: {
310
+ last_sync_at: new Date().toISOString(),
311
+ } satisfies OutlookCheckpoint as Record<string, unknown>,
312
+ };
313
+ }
314
+
315
+ // -------------------------------------------------------------------------
316
+ // Feed: calendar
317
+ // -------------------------------------------------------------------------
318
+
319
+ private async syncCalendar(ctx: SyncContext, accessToken: string): Promise<SyncResult> {
320
+ const config = ctx.config as Record<string, unknown>;
321
+ const lookbackDays = (config.lookback_days as number) ?? 7;
322
+ const lookaheadDays = (config.lookahead_days as number) ?? 30;
323
+ const maxResults = (config.max_results as number) ?? 100;
324
+
325
+ const startDate = new Date();
326
+ startDate.setDate(startDate.getDate() - lookbackDays);
327
+ const endDate = new Date();
328
+ endDate.setDate(endDate.getDate() + lookaheadDays);
329
+
330
+ const events: EventEnvelope[] = [];
331
+ let url =
332
+ `${this.API_BASE}/me/calendarView` +
333
+ `?startDateTime=${startDate.toISOString()}` +
334
+ `&endDateTime=${endDate.toISOString()}` +
335
+ `&$top=${Math.min(maxResults, this.PAGE_SIZE)}` +
336
+ '&$orderby=start/dateTime' +
337
+ '&$select=id,subject,bodyPreview,organizer,attendees,start,end,location,isAllDay,isCancelled,webLink,createdDateTime';
338
+
339
+ let fetched = 0;
340
+
341
+ for (let page = 0; page < this.MAX_PAGES && fetched < maxResults; page++) {
342
+ const data = await this.graphGet<GraphPagedResponse<GraphEvent>>(url, accessToken);
343
+
344
+ for (const evt of data.value) {
345
+ if (fetched >= maxResults) break;
346
+ events.push({
347
+ origin_id: `outlook_evt_${evt.id}`,
348
+ title: evt.subject,
349
+ payload_text: evt.bodyPreview || evt.subject,
350
+ author_name: evt.organizer?.emailAddress?.name || evt.organizer?.emailAddress?.address,
351
+ source_url: evt.webLink,
352
+ occurred_at: new Date(evt.start.dateTime),
353
+ origin_type: 'calendar_event',
354
+ metadata: {
355
+ organizer: evt.organizer?.emailAddress?.address,
356
+ location: evt.location?.displayName,
357
+ attendee_count: evt.attendees?.length ?? 0,
358
+ is_all_day: evt.isAllDay,
359
+ is_cancelled: evt.isCancelled,
360
+ start_time: evt.start.dateTime,
361
+ end_time: evt.end.dateTime,
362
+ },
363
+ });
364
+ fetched++;
365
+ }
366
+
367
+ if (ctx.emitEvents) await ctx.emitEvents(events.splice(0));
368
+
369
+ if (!data['@odata.nextLink'] || fetched >= maxResults) break;
370
+ url = data['@odata.nextLink'];
371
+ }
372
+
373
+ return {
374
+ events,
375
+ checkpoint: {
376
+ last_sync_at: new Date().toISOString(),
377
+ } satisfies OutlookCheckpoint as Record<string, unknown>,
378
+ };
379
+ }
380
+
381
+ // -------------------------------------------------------------------------
382
+ // API helpers
383
+ // -------------------------------------------------------------------------
384
+
385
+ private async graphGet<T>(url: string, accessToken: string): Promise<T> {
386
+ const response = await fetch(url, {
387
+ headers: {
388
+ Authorization: `Bearer ${accessToken}`,
389
+ 'Content-Type': 'application/json',
390
+ },
391
+ });
392
+
393
+ if (response.status === 401) {
394
+ throw new Error('Microsoft access token expired or invalid.');
395
+ }
396
+
397
+ if (response.status === 429) {
398
+ const retryAfter = response.headers.get('Retry-After');
399
+ throw new Error(
400
+ `Microsoft Graph rate limit exceeded. Retry after ${retryAfter ?? 'unknown'} seconds.`
401
+ );
402
+ }
403
+
404
+ if (!response.ok) {
405
+ throw new Error(`Microsoft Graph API error (${response.status}): ${await response.text()}`);
406
+ }
407
+
408
+ return response.json() as Promise<T>;
409
+ }
410
+ }