@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,631 @@
1
+ /**
2
+ * Google Calendar Connector (V1 runtime)
3
+ *
4
+ * Syncs calendar events from Google Calendar and supports creating
5
+ * new events via the Calendar API v3.
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
+ // Calendar API types
20
+ // ---------------------------------------------------------------------------
21
+
22
+ interface CalendarEvent {
23
+ id: string;
24
+ status: string;
25
+ htmlLink: string;
26
+ summary?: string;
27
+ description?: string;
28
+ location?: string;
29
+ creator?: { email?: string; displayName?: string };
30
+ organizer?: { email?: string; displayName?: string };
31
+ start: { dateTime?: string; date?: string; timeZone?: string };
32
+ end: { dateTime?: string; date?: string; timeZone?: string };
33
+ attendees?: Array<{
34
+ email: string;
35
+ displayName?: string;
36
+ responseStatus?: string;
37
+ }>;
38
+ created: string;
39
+ updated: string;
40
+ }
41
+
42
+ interface CalendarEventListResponse {
43
+ kind: string;
44
+ summary?: string;
45
+ items?: CalendarEvent[];
46
+ nextPageToken?: string;
47
+ nextSyncToken?: string;
48
+ }
49
+
50
+ // ---------------------------------------------------------------------------
51
+ // Checkpoint
52
+ // ---------------------------------------------------------------------------
53
+
54
+ interface CalendarCheckpoint {
55
+ sync_token?: string;
56
+ last_sync_at?: string;
57
+ }
58
+
59
+ // ---------------------------------------------------------------------------
60
+ // Connector
61
+ // ---------------------------------------------------------------------------
62
+
63
+ export default class GoogleCalendarConnector extends ConnectorRuntime {
64
+ readonly definition: ConnectorDefinition = {
65
+ key: 'google.calendar',
66
+ name: 'Google Calendar',
67
+ description: 'Syncs calendar events from Google Calendar and supports creating new events.',
68
+ version: '1.0.0',
69
+ faviconDomain: 'calendar.google.com',
70
+ authSchema: {
71
+ methods: [
72
+ {
73
+ type: 'oauth',
74
+ provider: 'google',
75
+ requiredScopes: ['https://www.googleapis.com/auth/calendar.readonly'],
76
+ optionalScopes: ['https://www.googleapis.com/auth/calendar.events'],
77
+ loginScopes: ['openid', 'email', 'profile'],
78
+ clientIdKey: 'GOOGLE_CLIENT_ID',
79
+ clientSecretKey: 'GOOGLE_CLIENT_SECRET',
80
+ tokenUrl: 'https://oauth2.googleapis.com/token',
81
+ tokenEndpointAuthMethod: 'client_secret_post',
82
+ loginProvisioning: {
83
+ autoCreateConnection: true,
84
+ },
85
+ },
86
+ ],
87
+ },
88
+ feeds: {
89
+ events: {
90
+ key: 'events',
91
+ name: 'Events',
92
+ requiredScopes: ['https://www.googleapis.com/auth/calendar.readonly'],
93
+ description: 'Syncs calendar events from Google Calendar.',
94
+ configSchema: {
95
+ type: 'object',
96
+ properties: {
97
+ calendar_id: {
98
+ type: 'string',
99
+ default: 'primary',
100
+ description: 'Calendar ID to sync (default: "primary").',
101
+ },
102
+ lookback_days: {
103
+ type: 'integer',
104
+ minimum: 1,
105
+ maximum: 365,
106
+ default: 30,
107
+ description: 'Number of days to look back on initial sync.',
108
+ },
109
+ max_results: {
110
+ type: 'integer',
111
+ minimum: 1,
112
+ maximum: 2500,
113
+ default: 100,
114
+ description: 'Maximum events to fetch per sync.',
115
+ },
116
+ },
117
+ },
118
+ eventKinds: {
119
+ event: {
120
+ description: 'A Google Calendar event',
121
+ metadataSchema: {
122
+ type: 'object',
123
+ properties: {
124
+ status: { type: 'string' },
125
+ location: { type: 'string' },
126
+ organizer: { type: 'string' },
127
+ attendee_count: { type: 'number' },
128
+ start_time: { type: 'string' },
129
+ end_time: { type: 'string' },
130
+ all_day: { type: 'boolean' },
131
+ },
132
+ },
133
+ },
134
+ },
135
+ },
136
+ },
137
+ actions: {
138
+ create_event: {
139
+ key: 'create_event',
140
+ name: 'Create Event',
141
+ description: 'Create a new event on Google Calendar.',
142
+ requiresApproval: true,
143
+ inputSchema: {
144
+ type: 'object',
145
+ required: ['summary', 'start', 'end'],
146
+ properties: {
147
+ summary: { type: 'string', description: 'Event title.' },
148
+ start: { type: 'string', description: 'Start time (ISO 8601 datetime).' },
149
+ end: { type: 'string', description: 'End time (ISO 8601 datetime).' },
150
+ description: { type: 'string', description: 'Event description.' },
151
+ location: { type: 'string', description: 'Event location.' },
152
+ attendees: {
153
+ type: 'string',
154
+ description: 'Comma-separated attendee email addresses.',
155
+ },
156
+ calendar_id: {
157
+ type: 'string',
158
+ description: 'Calendar ID (default: "primary").',
159
+ },
160
+ },
161
+ },
162
+ },
163
+ update_event: {
164
+ key: 'update_event',
165
+ name: 'Update Event',
166
+ description: 'Update an existing calendar event.',
167
+ requiresApproval: true,
168
+ inputSchema: {
169
+ type: 'object',
170
+ required: ['event_id'],
171
+ properties: {
172
+ event_id: { type: 'string', description: 'Event ID to update.' },
173
+ calendar_id: {
174
+ type: 'string',
175
+ description: 'Calendar ID (default: "primary").',
176
+ },
177
+ summary: { type: 'string', description: 'Event title.' },
178
+ start: { type: 'string', description: 'Start time (ISO 8601 datetime).' },
179
+ end: { type: 'string', description: 'End time (ISO 8601 datetime).' },
180
+ description: { type: 'string', description: 'Event description.' },
181
+ location: { type: 'string', description: 'Event location.' },
182
+ },
183
+ },
184
+ },
185
+ delete_event: {
186
+ key: 'delete_event',
187
+ name: 'Delete Event',
188
+ description: 'Delete/cancel an event.',
189
+ requiresApproval: true,
190
+ inputSchema: {
191
+ type: 'object',
192
+ required: ['event_id'],
193
+ properties: {
194
+ event_id: { type: 'string', description: 'Event ID to delete.' },
195
+ calendar_id: {
196
+ type: 'string',
197
+ description: 'Calendar ID (default: "primary").',
198
+ },
199
+ },
200
+ },
201
+ },
202
+ get_event: {
203
+ key: 'get_event',
204
+ name: 'Get Event',
205
+ description: 'Get full event details.',
206
+ inputSchema: {
207
+ type: 'object',
208
+ required: ['event_id'],
209
+ properties: {
210
+ event_id: { type: 'string', description: 'Event ID to retrieve.' },
211
+ calendar_id: {
212
+ type: 'string',
213
+ description: 'Calendar ID (default: "primary").',
214
+ },
215
+ },
216
+ },
217
+ },
218
+ },
219
+ };
220
+
221
+ private readonly BASE_URL = 'https://www.googleapis.com/calendar/v3';
222
+
223
+ // -------------------------------------------------------------------------
224
+ // sync
225
+ // -------------------------------------------------------------------------
226
+
227
+ async sync(ctx: SyncContext): Promise<SyncResult> {
228
+ const token = ctx.credentials?.accessToken;
229
+ if (!token) {
230
+ throw new Error('Google Calendar requires Google OAuth credentials.');
231
+ }
232
+
233
+ const calendarId = (ctx.config.calendar_id as string) || 'primary';
234
+ const maxResults = Math.min((ctx.config.max_results as number) ?? 100, 2500);
235
+ const lookbackDays = (ctx.config.lookback_days as number) ?? 30;
236
+
237
+ const checkpoint = (ctx.checkpoint ?? {}) as CalendarCheckpoint;
238
+ const events: EventEnvelope[] = [];
239
+
240
+ // Try incremental sync with syncToken first
241
+ if (checkpoint.sync_token) {
242
+ const result = await this.syncWithToken(token, calendarId, checkpoint.sync_token, maxResults);
243
+ if (result) {
244
+ return this.buildResult(result.events, result.nextSyncToken, result.events.length);
245
+ }
246
+ // syncToken invalid (410) -- fall through to full sync
247
+ }
248
+
249
+ // Full sync
250
+ const timeMin = new Date();
251
+ timeMin.setDate(timeMin.getDate() - lookbackDays);
252
+ const timeMax = new Date();
253
+ timeMax.setDate(timeMax.getDate() + 365); // Include future events
254
+
255
+ let pageToken: string | undefined;
256
+ let nextSyncToken: string | undefined;
257
+
258
+ while (true) {
259
+ const params = new URLSearchParams({
260
+ maxResults: String(Math.min(250, maxResults - events.length)),
261
+ orderBy: 'startTime',
262
+ singleEvents: 'true',
263
+ timeMin: timeMin.toISOString(),
264
+ timeMax: timeMax.toISOString(),
265
+ });
266
+ if (pageToken) {
267
+ params.set('pageToken', pageToken);
268
+ }
269
+
270
+ const url = `${this.BASE_URL}/calendars/${encodeURIComponent(calendarId)}/events?${params.toString()}`;
271
+ const response = await this.apiGet(url, token);
272
+
273
+ if (!response.ok) {
274
+ throw new Error(
275
+ `Calendar events.list error (${response.status}): ${await response.text()}`
276
+ );
277
+ }
278
+
279
+ const data = (await response.json()) as CalendarEventListResponse;
280
+
281
+ if (data.items) {
282
+ for (const calEvent of data.items) {
283
+ const envelope = this.calendarEventToEnvelope(calEvent);
284
+ if (envelope) events.push(envelope);
285
+ }
286
+ }
287
+
288
+ nextSyncToken = data.nextSyncToken;
289
+ pageToken = data.nextPageToken;
290
+ if (!pageToken || events.length >= maxResults) break;
291
+ }
292
+
293
+ return this.buildResult(events, nextSyncToken, events.length);
294
+ }
295
+
296
+ // -------------------------------------------------------------------------
297
+ // execute
298
+ // -------------------------------------------------------------------------
299
+
300
+ async execute(ctx: ActionContext): Promise<ActionResult> {
301
+ try {
302
+ const token = ctx.credentials?.accessToken;
303
+ if (!token) {
304
+ return {
305
+ success: false,
306
+ error: 'Google Calendar actions require Google OAuth credentials.',
307
+ };
308
+ }
309
+
310
+ switch (ctx.actionKey) {
311
+ case 'create_event':
312
+ return await this.createEvent(token, ctx.input);
313
+ case 'update_event':
314
+ return await this.updateEvent(token, ctx.input);
315
+ case 'delete_event':
316
+ return await this.deleteEvent(token, ctx.input);
317
+ case 'get_event':
318
+ return await this.getEvent(token, ctx.input);
319
+ default:
320
+ return { success: false, error: `Unknown action: ${ctx.actionKey}` };
321
+ }
322
+ } catch (error) {
323
+ return {
324
+ success: false,
325
+ error: error instanceof Error ? error.message : String(error),
326
+ };
327
+ }
328
+ }
329
+
330
+ // -------------------------------------------------------------------------
331
+ // Incremental sync
332
+ // -------------------------------------------------------------------------
333
+
334
+ private async syncWithToken(
335
+ token: string,
336
+ calendarId: string,
337
+ syncToken: string,
338
+ maxResults: number
339
+ ): Promise<{ events: EventEnvelope[]; nextSyncToken?: string } | null> {
340
+ const events: EventEnvelope[] = [];
341
+ let pageToken: string | undefined;
342
+ let nextSyncToken: string | undefined;
343
+
344
+ while (true) {
345
+ const params = new URLSearchParams({
346
+ maxResults: String(Math.min(250, maxResults - events.length)),
347
+ syncToken,
348
+ });
349
+ if (pageToken) {
350
+ params.set('pageToken', pageToken);
351
+ }
352
+
353
+ const url = `${this.BASE_URL}/calendars/${encodeURIComponent(calendarId)}/events?${params.toString()}`;
354
+ const response = await this.apiGet(url, token);
355
+
356
+ // 410 Gone means syncToken is expired
357
+ if (response.status === 410) {
358
+ return null;
359
+ }
360
+
361
+ if (!response.ok) {
362
+ throw new Error(
363
+ `Calendar events.list error (${response.status}): ${await response.text()}`
364
+ );
365
+ }
366
+
367
+ const data = (await response.json()) as CalendarEventListResponse;
368
+
369
+ if (data.items) {
370
+ for (const calEvent of data.items) {
371
+ const envelope = this.calendarEventToEnvelope(calEvent);
372
+ if (envelope) events.push(envelope);
373
+ }
374
+ }
375
+
376
+ nextSyncToken = data.nextSyncToken;
377
+ pageToken = data.nextPageToken;
378
+ if (!pageToken || events.length >= maxResults) break;
379
+ }
380
+
381
+ return { events, nextSyncToken };
382
+ }
383
+
384
+ // -------------------------------------------------------------------------
385
+ // Actions
386
+ // -------------------------------------------------------------------------
387
+
388
+ private async createEvent(token: string, input: Record<string, unknown>): Promise<ActionResult> {
389
+ const summary = input.summary as string;
390
+ const start = input.start as string;
391
+ const end = input.end as string;
392
+ const description = input.description as string | undefined;
393
+ const location = input.location as string | undefined;
394
+ const attendeesStr = input.attendees as string | undefined;
395
+ const calendarId = (input.calendar_id as string) || 'primary';
396
+
397
+ if (!summary || !start || !end) {
398
+ return { success: false, error: 'summary, start, and end are required.' };
399
+ }
400
+
401
+ const eventBody: Record<string, unknown> = {
402
+ summary,
403
+ start: { dateTime: start },
404
+ end: { dateTime: end },
405
+ };
406
+
407
+ if (description) eventBody.description = description;
408
+ if (location) eventBody.location = location;
409
+
410
+ if (attendeesStr) {
411
+ const attendees = attendeesStr
412
+ .split(',')
413
+ .map((email) => email.trim())
414
+ .filter((email) => email.length > 0)
415
+ .map((email) => ({ email }));
416
+ if (attendees.length > 0) {
417
+ eventBody.attendees = attendees;
418
+ }
419
+ }
420
+
421
+ const url = `${this.BASE_URL}/calendars/${encodeURIComponent(calendarId)}/events`;
422
+ const response = await fetch(url, {
423
+ method: 'POST',
424
+ headers: {
425
+ Authorization: `Bearer ${token}`,
426
+ 'Content-Type': 'application/json',
427
+ },
428
+ body: JSON.stringify(eventBody),
429
+ });
430
+
431
+ if (!response.ok) {
432
+ const errText = await response.text();
433
+ return { success: false, error: `Calendar create error (${response.status}): ${errText}` };
434
+ }
435
+
436
+ const created = (await response.json()) as CalendarEvent;
437
+
438
+ return {
439
+ success: true,
440
+ output: {
441
+ event_id: created.id,
442
+ html_link: created.htmlLink,
443
+ summary: created.summary,
444
+ start: created.start.dateTime || created.start.date,
445
+ end: created.end.dateTime || created.end.date,
446
+ },
447
+ };
448
+ }
449
+
450
+ private async updateEvent(token: string, input: Record<string, unknown>): Promise<ActionResult> {
451
+ const eventId = input.event_id as string;
452
+ const calendarId = (input.calendar_id as string) || 'primary';
453
+
454
+ if (!eventId) {
455
+ return { success: false, error: 'event_id is required.' };
456
+ }
457
+
458
+ const patch: Record<string, unknown> = {};
459
+ if (input.summary !== undefined) patch.summary = input.summary;
460
+ if (input.description !== undefined) patch.description = input.description;
461
+ if (input.location !== undefined) patch.location = input.location;
462
+ if (input.start !== undefined) patch.start = { dateTime: input.start as string };
463
+ if (input.end !== undefined) patch.end = { dateTime: input.end as string };
464
+
465
+ const url = `${this.BASE_URL}/calendars/${encodeURIComponent(calendarId)}/events/${encodeURIComponent(eventId)}`;
466
+ const response = await fetch(url, {
467
+ method: 'PATCH',
468
+ headers: {
469
+ Authorization: `Bearer ${token}`,
470
+ 'Content-Type': 'application/json',
471
+ },
472
+ body: JSON.stringify(patch),
473
+ });
474
+
475
+ if (!response.ok) {
476
+ const errText = await response.text();
477
+ return { success: false, error: `Calendar update error (${response.status}): ${errText}` };
478
+ }
479
+
480
+ const updated = (await response.json()) as CalendarEvent;
481
+
482
+ return {
483
+ success: true,
484
+ output: {
485
+ event_id: updated.id,
486
+ url: updated.htmlLink,
487
+ summary: updated.summary,
488
+ },
489
+ };
490
+ }
491
+
492
+ private async deleteEvent(token: string, input: Record<string, unknown>): Promise<ActionResult> {
493
+ const eventId = input.event_id as string;
494
+ const calendarId = (input.calendar_id as string) || 'primary';
495
+
496
+ if (!eventId) {
497
+ return { success: false, error: 'event_id is required.' };
498
+ }
499
+
500
+ const url = `${this.BASE_URL}/calendars/${encodeURIComponent(calendarId)}/events/${encodeURIComponent(eventId)}`;
501
+ const response = await fetch(url, {
502
+ method: 'DELETE',
503
+ headers: { Authorization: `Bearer ${token}` },
504
+ });
505
+
506
+ if (!response.ok) {
507
+ const errText = await response.text();
508
+ return { success: false, error: `Calendar delete error (${response.status}): ${errText}` };
509
+ }
510
+
511
+ return {
512
+ success: true,
513
+ output: { deleted: true, event_id: eventId },
514
+ };
515
+ }
516
+
517
+ private async getEvent(token: string, input: Record<string, unknown>): Promise<ActionResult> {
518
+ const eventId = input.event_id as string;
519
+ const calendarId = (input.calendar_id as string) || 'primary';
520
+
521
+ if (!eventId) {
522
+ return { success: false, error: 'event_id is required.' };
523
+ }
524
+
525
+ const url = `${this.BASE_URL}/calendars/${encodeURIComponent(calendarId)}/events/${encodeURIComponent(eventId)}`;
526
+ const response = await this.apiGet(url, token);
527
+
528
+ if (!response.ok) {
529
+ const errText = await response.text();
530
+ return { success: false, error: `Calendar get error (${response.status}): ${errText}` };
531
+ }
532
+
533
+ const event = (await response.json()) as CalendarEvent;
534
+
535
+ return {
536
+ success: true,
537
+ output: {
538
+ event_id: event.id,
539
+ summary: event.summary,
540
+ start: event.start.dateTime || event.start.date,
541
+ end: event.end.dateTime || event.end.date,
542
+ description: event.description,
543
+ location: event.location,
544
+ attendees: event.attendees?.map((a) => ({
545
+ email: a.email,
546
+ name: a.displayName,
547
+ status: a.responseStatus,
548
+ })),
549
+ organizer: event.organizer
550
+ ? { email: event.organizer.email, name: event.organizer.displayName }
551
+ : undefined,
552
+ url: event.htmlLink,
553
+ status: event.status,
554
+ },
555
+ };
556
+ }
557
+
558
+ // -------------------------------------------------------------------------
559
+ // Helpers
560
+ // -------------------------------------------------------------------------
561
+
562
+ private calendarEventToEnvelope(calEvent: CalendarEvent): EventEnvelope | null {
563
+ if (calEvent.status === 'cancelled') return null;
564
+
565
+ const startTime = calEvent.start.dateTime || calEvent.start.date;
566
+ if (!startTime) return null;
567
+
568
+ const occurredAt = new Date(startTime);
569
+ if (Number.isNaN(occurredAt.getTime())) return null;
570
+
571
+ const isAllDay = !calEvent.start.dateTime;
572
+ const endTime = calEvent.end.dateTime || calEvent.end.date;
573
+
574
+ // Build payload text from description + attendees
575
+ const parts: string[] = [];
576
+ if (calEvent.description) {
577
+ parts.push(calEvent.description);
578
+ }
579
+ if (calEvent.attendees && calEvent.attendees.length > 0) {
580
+ const attendeeList = calEvent.attendees.map((a) => a.displayName || a.email).join(', ');
581
+ parts.push(`Attendees: ${attendeeList}`);
582
+ }
583
+
584
+ return {
585
+ origin_id: calEvent.id,
586
+ title: calEvent.summary || '(no title)',
587
+ payload_text: parts.join('\n\n'),
588
+ author_name: calEvent.organizer?.displayName || calEvent.organizer?.email,
589
+ source_url: calEvent.htmlLink,
590
+ occurred_at: occurredAt,
591
+ origin_type: 'event',
592
+ metadata: {
593
+ status: calEvent.status,
594
+ ...(calEvent.location ? { location: calEvent.location } : {}),
595
+ ...(calEvent.organizer?.email ? { organizer: calEvent.organizer.email } : {}),
596
+ attendee_count: calEvent.attendees?.length ?? 0,
597
+ start_time: startTime,
598
+ ...(endTime ? { end_time: endTime } : {}),
599
+ all_day: isAllDay,
600
+ },
601
+ };
602
+ }
603
+
604
+ private buildResult(
605
+ events: EventEnvelope[],
606
+ syncToken: string | undefined,
607
+ itemsFound: number
608
+ ): SyncResult {
609
+ // Sort events by occurred_at descending
610
+ events.sort((a, b) => b.occurred_at.getTime() - a.occurred_at.getTime());
611
+
612
+ const newCheckpoint: CalendarCheckpoint = {
613
+ ...(syncToken ? { sync_token: syncToken } : {}),
614
+ last_sync_at: new Date().toISOString(),
615
+ };
616
+
617
+ return {
618
+ events,
619
+ checkpoint: newCheckpoint as Record<string, unknown>,
620
+ metadata: {
621
+ items_found: itemsFound,
622
+ },
623
+ };
624
+ }
625
+
626
+ private async apiGet(url: string, token: string): Promise<Response> {
627
+ return fetch(url, {
628
+ headers: { Authorization: `Bearer ${token}` },
629
+ });
630
+ }
631
+ }