@lobu/cli 6.0.0 → 6.1.1

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 (222) hide show
  1. package/README.md +20 -27
  2. package/dist/bundled-skills/lobu/SKILL.md +12 -12
  3. package/dist/commands/_lib/apply/apply-cmd.d.ts +2 -0
  4. package/dist/commands/_lib/apply/apply-cmd.d.ts.map +1 -1
  5. package/dist/commands/_lib/apply/apply-cmd.js +26 -0
  6. package/dist/commands/_lib/apply/apply-cmd.js.map +1 -1
  7. package/dist/commands/_lib/apply/client.d.ts +1 -1
  8. package/dist/commands/_lib/apply/client.d.ts.map +1 -1
  9. package/dist/commands/_lib/apply/desired-state.js +6 -6
  10. package/dist/commands/_lib/apply/desired-state.js.map +1 -1
  11. package/dist/commands/agent.d.ts +7 -0
  12. package/dist/commands/agent.d.ts.map +1 -1
  13. package/dist/commands/agent.js +65 -1
  14. package/dist/commands/agent.js.map +1 -1
  15. package/dist/commands/chat.d.ts +12 -9
  16. package/dist/commands/chat.d.ts.map +1 -1
  17. package/dist/commands/chat.js +117 -56
  18. package/dist/commands/chat.js.map +1 -1
  19. package/dist/commands/dev.d.ts +15 -7
  20. package/dist/commands/dev.d.ts.map +1 -1
  21. package/dist/commands/dev.js +79 -44
  22. package/dist/commands/dev.js.map +1 -1
  23. package/dist/commands/doctor.d.ts +1 -0
  24. package/dist/commands/doctor.d.ts.map +1 -1
  25. package/dist/commands/doctor.js +136 -0
  26. package/dist/commands/doctor.js.map +1 -1
  27. package/dist/commands/eval.d.ts +8 -0
  28. package/dist/commands/eval.d.ts.map +1 -1
  29. package/dist/commands/eval.js +56 -1
  30. package/dist/commands/eval.js.map +1 -1
  31. package/dist/commands/init.d.ts +20 -5
  32. package/dist/commands/init.d.ts.map +1 -1
  33. package/dist/commands/init.js +332 -183
  34. package/dist/commands/init.js.map +1 -1
  35. package/dist/commands/link.d.ts +11 -0
  36. package/dist/commands/link.d.ts.map +1 -0
  37. package/dist/commands/link.js +28 -0
  38. package/dist/commands/link.js.map +1 -0
  39. package/dist/commands/login.d.ts.map +1 -1
  40. package/dist/commands/login.js +14 -2
  41. package/dist/commands/login.js.map +1 -1
  42. package/dist/commands/memory/_lib/browser-auth-cmd.d.ts.map +1 -1
  43. package/dist/commands/memory/_lib/browser-auth-cmd.js +4 -4
  44. package/dist/commands/memory/_lib/browser-auth-cmd.js.map +1 -1
  45. package/dist/commands/memory/_lib/install-targets.d.ts.map +1 -1
  46. package/dist/commands/memory/_lib/install-targets.js +1 -5
  47. package/dist/commands/memory/_lib/install-targets.js.map +1 -1
  48. package/dist/commands/memory/_lib/mcp.d.ts +2 -2
  49. package/dist/commands/memory/_lib/mcp.d.ts.map +1 -1
  50. package/dist/commands/memory/_lib/mcp.js +24 -12
  51. package/dist/commands/memory/_lib/mcp.js.map +1 -1
  52. package/dist/commands/memory/_lib/openclaw-auth.d.ts +1 -0
  53. package/dist/commands/memory/_lib/openclaw-auth.d.ts.map +1 -1
  54. package/dist/commands/memory/_lib/openclaw-auth.js +14 -3
  55. package/dist/commands/memory/_lib/openclaw-auth.js.map +1 -1
  56. package/dist/commands/memory/_lib/openclaw-cmd.js +1 -1
  57. package/dist/commands/memory/_lib/openclaw-cmd.js.map +1 -1
  58. package/dist/commands/memory/_lib/schema.d.ts +2 -2
  59. package/dist/commands/memory/_lib/schema.d.ts.map +1 -1
  60. package/dist/commands/memory/_lib/schema.js +3 -3
  61. package/dist/commands/memory/_lib/schema.js.map +1 -1
  62. package/dist/commands/memory/_lib/seed-cmd.d.ts.map +1 -1
  63. package/dist/commands/memory/_lib/seed-cmd.js +5 -6
  64. package/dist/commands/memory/_lib/seed-cmd.js.map +1 -1
  65. package/dist/commands/memory/run.d.ts.map +1 -1
  66. package/dist/commands/memory/run.js +2 -2
  67. package/dist/commands/memory/run.js.map +1 -1
  68. package/dist/commands/platforms/platform-prompts.d.ts +0 -1
  69. package/dist/commands/platforms/platform-prompts.d.ts.map +1 -1
  70. package/dist/commands/platforms/platform-prompts.js +54 -8
  71. package/dist/commands/platforms/platform-prompts.js.map +1 -1
  72. package/dist/commands/telemetry.d.ts +10 -0
  73. package/dist/commands/telemetry.d.ts.map +1 -0
  74. package/dist/commands/telemetry.js +68 -0
  75. package/dist/commands/telemetry.js.map +1 -0
  76. package/dist/commands/whoami.d.ts.map +1 -1
  77. package/dist/commands/whoami.js +1 -1
  78. package/dist/commands/whoami.js.map +1 -1
  79. package/dist/connectors/README.md +534 -0
  80. package/dist/connectors/__tests__/browser-scraper-utils.test.ts +186 -0
  81. package/dist/connectors/browser-scraper-utils.ts +214 -0
  82. package/dist/connectors/capterra.ts +273 -0
  83. package/dist/connectors/g2.ts +286 -0
  84. package/dist/connectors/github.ts +1553 -0
  85. package/dist/connectors/glassdoor.ts +291 -0
  86. package/dist/connectors/gmaps.ts +197 -0
  87. package/dist/connectors/google_calendar.ts +631 -0
  88. package/dist/connectors/google_gmail.ts +751 -0
  89. package/dist/connectors/google_photos.ts +776 -0
  90. package/dist/connectors/google_play.ts +342 -0
  91. package/dist/connectors/hackernews.ts +471 -0
  92. package/dist/connectors/index.ts +23 -0
  93. package/dist/connectors/ios_appstore.ts +226 -0
  94. package/dist/connectors/linkedin.ts +471 -0
  95. package/dist/connectors/microsoft_outlook.ts +410 -0
  96. package/dist/connectors/producthunt.ts +471 -0
  97. package/dist/connectors/reddit.ts +600 -0
  98. package/dist/connectors/rss.ts +448 -0
  99. package/dist/connectors/spotify.ts +590 -0
  100. package/dist/connectors/trustpilot.ts +199 -0
  101. package/dist/connectors/website.ts +629 -0
  102. package/dist/connectors/whatsapp.ts +1073 -0
  103. package/dist/connectors/x.ts +526 -0
  104. package/dist/connectors/youtube.ts +666 -0
  105. package/dist/db/migrations/00000000000000_baseline.sql +4867 -0
  106. package/dist/db/migrations/20260405193000_add_mcp_sessions.sql +33 -0
  107. package/dist/db/migrations/20260408120000_remove_system_connectors.sql +48 -0
  108. package/dist/db/migrations/20260408120001_optional_compiled_code.sql +6 -0
  109. package/dist/db/migrations/20260409110000_add_active_watcher_run_index.sql +9 -0
  110. package/dist/db/migrations/20260409130000_connector_default_config.sql +5 -0
  111. package/dist/db/migrations/20260410120000_add_agent_secrets.sql +25 -0
  112. package/dist/db/migrations/20260413170000_add_watcher_group_id.sql +67 -0
  113. package/dist/db/migrations/20260416120000_add_entity_wa_jid_index.sql +14 -0
  114. package/dist/db/migrations/20260417100000_add_entity_identities.sql +77 -0
  115. package/dist/db/migrations/20260418100000_add_auth_runs.sql +83 -0
  116. package/dist/db/migrations/20260418110000_add_runs_created_by_user.sql +18 -0
  117. package/dist/db/migrations/20260419120000_add_event_identity_indexes.sql +56 -0
  118. package/dist/db/migrations/20260420120000_extend_reserved_org_slugs.sql +56 -0
  119. package/dist/db/migrations/20260424030000_add_watcher_run_correlation.sql +52 -0
  120. package/dist/db/migrations/20260424130000_relax_events_client_id_fk.sql +47 -0
  121. package/dist/db/migrations/20260425100000_normalize_watcher_feedback.sql +91 -0
  122. package/dist/db/migrations/20260425120000_add_run_diagnostics.sql +20 -0
  123. package/dist/db/migrations/20260425130000_add_repair_agent_plumbing.sql +46 -0
  124. package/dist/db/migrations/20260426120000_entities_entity_type_fk.sql +101 -0
  125. package/dist/db/migrations/20260426130000_db_integrity_cleanup.sql +104 -0
  126. package/dist/db/migrations/20260426130001_db_integrity_cleanup_concurrent.sql +187 -0
  127. package/dist/db/migrations/20260427133000_events_created_by_nullable.sql +74 -0
  128. package/dist/db/migrations/20260427140000_identity_engine_indexes.sql +140 -0
  129. package/dist/db/migrations/20260427150000_drop_events_source_id.sql +177 -0
  130. package/dist/db/migrations/20260427160000_drop_dead_schema.sql +76 -0
  131. package/dist/db/migrations/20260427170000_market_founder_to_member.sql +364 -0
  132. package/dist/db/migrations/20260428040000_cascade_events_watchers_org_fk.sql +66 -0
  133. package/dist/db/migrations/20260428050000_add_runs_approved_input.sql +9 -0
  134. package/dist/db/migrations/20260429010000_auth_profile_tenant_scoped_fk.sql +79 -0
  135. package/dist/db/migrations/20260429060000_extend_runs_for_lobu_queue.sql +108 -0
  136. package/dist/db/migrations/20260429120000_agent_changed_notify.sql +97 -0
  137. package/dist/db/migrations/20260429120100_user_auth_profiles_and_model_prefs.sql +36 -0
  138. package/dist/db/migrations/20260429120200_fix_notify_old_keys.sql +130 -0
  139. package/dist/db/migrations/20260429130000_oauth_states_cli_sessions_rate_limits.sql +83 -0
  140. package/dist/db/migrations/20260429140000_phase8_grants_chat_connections_mcp_sessions.sql +84 -0
  141. package/dist/db/migrations/20260429140100_runs_priority_expires_at_retry_delay.sql +44 -0
  142. package/dist/db/migrations/20260429180000_drop_invalidatable_cache_triggers.sql +25 -0
  143. package/dist/db/migrations/20260430005614_agents_apply_fields.sql +21 -0
  144. package/dist/db/migrations/20260430022231_fix_connection_config_encryption.sql +69 -0
  145. package/dist/db/migrations/20260430151215_add_task_run_type.sql +77 -0
  146. package/dist/db/migrations/20260501000000_drop_cli_sessions.sql +27 -0
  147. package/dist/db/migrations/20260501133000_lobu_memory_mcp_id.sql +117 -0
  148. package/dist/db/migrations/20260502000000_drop_chat_connections.sql +60 -0
  149. package/dist/db/migrations/20260503000000_agent_secrets_org_scope.sql +56 -0
  150. package/dist/db/migrations/20260504000000_flatten_agents_drop_sandbox_model.sql +48 -0
  151. package/dist/index.d.ts.map +1 -1
  152. package/dist/index.js +147 -23
  153. package/dist/index.js.map +1 -1
  154. package/dist/internal/api-client.d.ts +4 -8
  155. package/dist/internal/api-client.d.ts.map +1 -1
  156. package/dist/internal/api-client.js +1 -1
  157. package/dist/internal/api-client.js.map +1 -1
  158. package/dist/internal/context.js +2 -2
  159. package/dist/internal/context.js.map +1 -1
  160. package/dist/internal/credentials.d.ts.map +1 -1
  161. package/dist/internal/credentials.js +6 -1
  162. package/dist/internal/credentials.js.map +1 -1
  163. package/dist/internal/index.d.ts +2 -3
  164. package/dist/internal/index.d.ts.map +1 -1
  165. package/dist/internal/index.js +2 -2
  166. package/dist/internal/index.js.map +1 -1
  167. package/dist/internal/oauth.d.ts +7 -6
  168. package/dist/internal/oauth.d.ts.map +1 -1
  169. package/dist/internal/oauth.js +3 -3
  170. package/dist/internal/project-link.d.ts +10 -0
  171. package/dist/internal/project-link.d.ts.map +1 -0
  172. package/dist/internal/project-link.js +48 -0
  173. package/dist/internal/project-link.js.map +1 -0
  174. package/dist/providers.json +2 -2
  175. package/dist/server.bundle.mjs +3173 -4404
  176. package/dist/start-local.bundle.mjs +71481 -0
  177. package/dist/templates/README.md.tmpl +10 -11
  178. package/package.json +14 -12
  179. package/dist/__tests__/chat.integration.test.d.ts +0 -2
  180. package/dist/__tests__/chat.integration.test.d.ts.map +0 -1
  181. package/dist/__tests__/chat.integration.test.js +0 -337
  182. package/dist/__tests__/chat.integration.test.js.map +0 -1
  183. package/dist/__tests__/dev.test.d.ts +0 -2
  184. package/dist/__tests__/dev.test.d.ts.map +0 -1
  185. package/dist/__tests__/dev.test.js +0 -25
  186. package/dist/__tests__/dev.test.js.map +0 -1
  187. package/dist/__tests__/init-memory.test.d.ts +0 -2
  188. package/dist/__tests__/init-memory.test.d.ts.map +0 -1
  189. package/dist/__tests__/init-memory.test.js +0 -45
  190. package/dist/__tests__/init-memory.test.js.map +0 -1
  191. package/dist/__tests__/token.test.d.ts +0 -2
  192. package/dist/__tests__/token.test.d.ts.map +0 -1
  193. package/dist/__tests__/token.test.js +0 -52
  194. package/dist/__tests__/token.test.js.map +0 -1
  195. package/dist/commands/_lib/apply/__tests__/client.test.d.ts +0 -2
  196. package/dist/commands/_lib/apply/__tests__/client.test.d.ts.map +0 -1
  197. package/dist/commands/_lib/apply/__tests__/client.test.js +0 -23
  198. package/dist/commands/_lib/apply/__tests__/client.test.js.map +0 -1
  199. package/dist/commands/_lib/apply/__tests__/desired-state.test.d.ts +0 -2
  200. package/dist/commands/_lib/apply/__tests__/desired-state.test.d.ts.map +0 -1
  201. package/dist/commands/_lib/apply/__tests__/desired-state.test.js +0 -140
  202. package/dist/commands/_lib/apply/__tests__/desired-state.test.js.map +0 -1
  203. package/dist/commands/_lib/apply/__tests__/diff.test.d.ts +0 -2
  204. package/dist/commands/_lib/apply/__tests__/diff.test.d.ts.map +0 -1
  205. package/dist/commands/_lib/apply/__tests__/diff.test.js +0 -378
  206. package/dist/commands/_lib/apply/__tests__/diff.test.js.map +0 -1
  207. package/dist/commands/apply.d.ts +0 -3
  208. package/dist/commands/apply.d.ts.map +0 -1
  209. package/dist/commands/apply.js +0 -5
  210. package/dist/commands/apply.js.map +0 -1
  211. package/dist/commands/memory/_lib/openclaw-auth.test.d.ts +0 -2
  212. package/dist/commands/memory/_lib/openclaw-auth.test.d.ts.map +0 -1
  213. package/dist/commands/memory/_lib/openclaw-auth.test.js +0 -9
  214. package/dist/commands/memory/_lib/openclaw-auth.test.js.map +0 -1
  215. package/dist/internal/__tests__/api-client.test.d.ts +0 -2
  216. package/dist/internal/__tests__/api-client.test.d.ts.map +0 -1
  217. package/dist/internal/__tests__/api-client.test.js +0 -95
  218. package/dist/internal/__tests__/api-client.test.js.map +0 -1
  219. package/dist/internal/__tests__/context.test.d.ts +0 -2
  220. package/dist/internal/__tests__/context.test.d.ts.map +0 -1
  221. package/dist/internal/__tests__/context.test.js +0 -77
  222. 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
+ }