@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,641 @@
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
+ // Always request a full page — `maxResults` is a soft cap on *stored*
260
+ // events, not a reason to shrink the request size (shrinking to 1 once the
261
+ // cap is hit would crawl a busy calendar one event per round-trip).
262
+ const params = new URLSearchParams({
263
+ maxResults: '250',
264
+ orderBy: 'startTime',
265
+ singleEvents: 'true',
266
+ timeMin: timeMin.toISOString(),
267
+ timeMax: timeMax.toISOString(),
268
+ });
269
+ if (pageToken) {
270
+ params.set('pageToken', pageToken);
271
+ }
272
+
273
+ const url = `${this.BASE_URL}/calendars/${encodeURIComponent(calendarId)}/events?${params.toString()}`;
274
+ const response = await this.apiGet(url, token);
275
+
276
+ if (!response.ok) {
277
+ throw new Error(
278
+ `Calendar events.list error (${response.status}): ${await response.text()}`
279
+ );
280
+ }
281
+
282
+ const data = (await response.json()) as CalendarEventListResponse;
283
+
284
+ if (data.items) {
285
+ for (const calEvent of data.items) {
286
+ if (events.length >= maxResults) break;
287
+ const envelope = this.calendarEventToEnvelope(calEvent);
288
+ if (envelope) events.push(envelope);
289
+ }
290
+ }
291
+
292
+ nextSyncToken = data.nextSyncToken;
293
+ pageToken = data.nextPageToken;
294
+ // Google only returns nextSyncToken on the LAST page (no nextPageToken).
295
+ // Must keep paginating until pageToken is exhausted, otherwise the sync
296
+ // token is never obtained and every subsequent sync re-runs the full
297
+ // window from scratch — so we keep paging past `maxResults`, just stop
298
+ // appending events once the cap is reached.
299
+ if (!pageToken) break;
300
+ }
301
+
302
+ return this.buildResult(events, nextSyncToken, events.length);
303
+ }
304
+
305
+ // -------------------------------------------------------------------------
306
+ // execute
307
+ // -------------------------------------------------------------------------
308
+
309
+ async execute(ctx: ActionContext): Promise<ActionResult> {
310
+ try {
311
+ const token = ctx.credentials?.accessToken;
312
+ if (!token) {
313
+ return {
314
+ success: false,
315
+ error: 'Google Calendar actions require Google OAuth credentials.',
316
+ };
317
+ }
318
+
319
+ switch (ctx.actionKey) {
320
+ case 'create_event':
321
+ return await this.createEvent(token, ctx.input);
322
+ case 'update_event':
323
+ return await this.updateEvent(token, ctx.input);
324
+ case 'delete_event':
325
+ return await this.deleteEvent(token, ctx.input);
326
+ case 'get_event':
327
+ return await this.getEvent(token, ctx.input);
328
+ default:
329
+ return { success: false, error: `Unknown action: ${ctx.actionKey}` };
330
+ }
331
+ } catch (error) {
332
+ return {
333
+ success: false,
334
+ error: error instanceof Error ? error.message : String(error),
335
+ };
336
+ }
337
+ }
338
+
339
+ // -------------------------------------------------------------------------
340
+ // Incremental sync
341
+ // -------------------------------------------------------------------------
342
+
343
+ private async syncWithToken(
344
+ token: string,
345
+ calendarId: string,
346
+ syncToken: string,
347
+ maxResults: number
348
+ ): Promise<{ events: EventEnvelope[]; nextSyncToken?: string } | null> {
349
+ const events: EventEnvelope[] = [];
350
+ let pageToken: string | undefined;
351
+ let nextSyncToken: string | undefined;
352
+
353
+ while (true) {
354
+ const params = new URLSearchParams({
355
+ maxResults: String(Math.max(1, Math.min(250, maxResults - events.length))),
356
+ syncToken,
357
+ });
358
+ if (pageToken) {
359
+ params.set('pageToken', pageToken);
360
+ }
361
+
362
+ const url = `${this.BASE_URL}/calendars/${encodeURIComponent(calendarId)}/events?${params.toString()}`;
363
+ const response = await this.apiGet(url, token);
364
+
365
+ // 410 Gone means syncToken is expired
366
+ if (response.status === 410) {
367
+ return null;
368
+ }
369
+
370
+ if (!response.ok) {
371
+ throw new Error(
372
+ `Calendar events.list error (${response.status}): ${await response.text()}`
373
+ );
374
+ }
375
+
376
+ const data = (await response.json()) as CalendarEventListResponse;
377
+
378
+ if (data.items) {
379
+ for (const calEvent of data.items) {
380
+ const envelope = this.calendarEventToEnvelope(calEvent);
381
+ if (envelope) events.push(envelope);
382
+ }
383
+ }
384
+
385
+ nextSyncToken = data.nextSyncToken;
386
+ pageToken = data.nextPageToken;
387
+ // Paginate until exhausted so we capture the trailing nextSyncToken.
388
+ if (!pageToken) break;
389
+ }
390
+
391
+ return { events, nextSyncToken };
392
+ }
393
+
394
+ // -------------------------------------------------------------------------
395
+ // Actions
396
+ // -------------------------------------------------------------------------
397
+
398
+ private async createEvent(token: string, input: Record<string, unknown>): Promise<ActionResult> {
399
+ const summary = input.summary as string;
400
+ const start = input.start as string;
401
+ const end = input.end as string;
402
+ const description = input.description as string | undefined;
403
+ const location = input.location as string | undefined;
404
+ const attendeesStr = input.attendees as string | undefined;
405
+ const calendarId = (input.calendar_id as string) || 'primary';
406
+
407
+ if (!summary || !start || !end) {
408
+ return { success: false, error: 'summary, start, and end are required.' };
409
+ }
410
+
411
+ const eventBody: Record<string, unknown> = {
412
+ summary,
413
+ start: { dateTime: start },
414
+ end: { dateTime: end },
415
+ };
416
+
417
+ if (description) eventBody.description = description;
418
+ if (location) eventBody.location = location;
419
+
420
+ if (attendeesStr) {
421
+ const attendees = attendeesStr
422
+ .split(',')
423
+ .map((email) => email.trim())
424
+ .filter((email) => email.length > 0)
425
+ .map((email) => ({ email }));
426
+ if (attendees.length > 0) {
427
+ eventBody.attendees = attendees;
428
+ }
429
+ }
430
+
431
+ const url = `${this.BASE_URL}/calendars/${encodeURIComponent(calendarId)}/events`;
432
+ const response = await fetch(url, {
433
+ method: 'POST',
434
+ headers: {
435
+ Authorization: `Bearer ${token}`,
436
+ 'Content-Type': 'application/json',
437
+ },
438
+ body: JSON.stringify(eventBody),
439
+ });
440
+
441
+ if (!response.ok) {
442
+ const errText = await response.text();
443
+ return { success: false, error: `Calendar create error (${response.status}): ${errText}` };
444
+ }
445
+
446
+ const created = (await response.json()) as CalendarEvent;
447
+
448
+ return {
449
+ success: true,
450
+ output: {
451
+ event_id: created.id,
452
+ html_link: created.htmlLink,
453
+ summary: created.summary,
454
+ start: created.start.dateTime || created.start.date,
455
+ end: created.end.dateTime || created.end.date,
456
+ },
457
+ };
458
+ }
459
+
460
+ private async updateEvent(token: string, input: Record<string, unknown>): Promise<ActionResult> {
461
+ const eventId = input.event_id as string;
462
+ const calendarId = (input.calendar_id as string) || 'primary';
463
+
464
+ if (!eventId) {
465
+ return { success: false, error: 'event_id is required.' };
466
+ }
467
+
468
+ const patch: Record<string, unknown> = {};
469
+ if (input.summary !== undefined) patch.summary = input.summary;
470
+ if (input.description !== undefined) patch.description = input.description;
471
+ if (input.location !== undefined) patch.location = input.location;
472
+ if (input.start !== undefined) patch.start = { dateTime: input.start as string };
473
+ if (input.end !== undefined) patch.end = { dateTime: input.end as string };
474
+
475
+ const url = `${this.BASE_URL}/calendars/${encodeURIComponent(calendarId)}/events/${encodeURIComponent(eventId)}`;
476
+ const response = await fetch(url, {
477
+ method: 'PATCH',
478
+ headers: {
479
+ Authorization: `Bearer ${token}`,
480
+ 'Content-Type': 'application/json',
481
+ },
482
+ body: JSON.stringify(patch),
483
+ });
484
+
485
+ if (!response.ok) {
486
+ const errText = await response.text();
487
+ return { success: false, error: `Calendar update error (${response.status}): ${errText}` };
488
+ }
489
+
490
+ const updated = (await response.json()) as CalendarEvent;
491
+
492
+ return {
493
+ success: true,
494
+ output: {
495
+ event_id: updated.id,
496
+ url: updated.htmlLink,
497
+ summary: updated.summary,
498
+ },
499
+ };
500
+ }
501
+
502
+ private async deleteEvent(token: string, input: Record<string, unknown>): Promise<ActionResult> {
503
+ const eventId = input.event_id as string;
504
+ const calendarId = (input.calendar_id as string) || 'primary';
505
+
506
+ if (!eventId) {
507
+ return { success: false, error: 'event_id is required.' };
508
+ }
509
+
510
+ const url = `${this.BASE_URL}/calendars/${encodeURIComponent(calendarId)}/events/${encodeURIComponent(eventId)}`;
511
+ const response = await fetch(url, {
512
+ method: 'DELETE',
513
+ headers: { Authorization: `Bearer ${token}` },
514
+ });
515
+
516
+ if (!response.ok) {
517
+ const errText = await response.text();
518
+ return { success: false, error: `Calendar delete error (${response.status}): ${errText}` };
519
+ }
520
+
521
+ return {
522
+ success: true,
523
+ output: { deleted: true, event_id: eventId },
524
+ };
525
+ }
526
+
527
+ private async getEvent(token: string, input: Record<string, unknown>): Promise<ActionResult> {
528
+ const eventId = input.event_id as string;
529
+ const calendarId = (input.calendar_id as string) || 'primary';
530
+
531
+ if (!eventId) {
532
+ return { success: false, error: 'event_id is required.' };
533
+ }
534
+
535
+ const url = `${this.BASE_URL}/calendars/${encodeURIComponent(calendarId)}/events/${encodeURIComponent(eventId)}`;
536
+ const response = await this.apiGet(url, token);
537
+
538
+ if (!response.ok) {
539
+ const errText = await response.text();
540
+ return { success: false, error: `Calendar get error (${response.status}): ${errText}` };
541
+ }
542
+
543
+ const event = (await response.json()) as CalendarEvent;
544
+
545
+ return {
546
+ success: true,
547
+ output: {
548
+ event_id: event.id,
549
+ summary: event.summary,
550
+ start: event.start.dateTime || event.start.date,
551
+ end: event.end.dateTime || event.end.date,
552
+ description: event.description,
553
+ location: event.location,
554
+ attendees: event.attendees?.map((a) => ({
555
+ email: a.email,
556
+ name: a.displayName,
557
+ status: a.responseStatus,
558
+ })),
559
+ organizer: event.organizer
560
+ ? { email: event.organizer.email, name: event.organizer.displayName }
561
+ : undefined,
562
+ url: event.htmlLink,
563
+ status: event.status,
564
+ },
565
+ };
566
+ }
567
+
568
+ // -------------------------------------------------------------------------
569
+ // Helpers
570
+ // -------------------------------------------------------------------------
571
+
572
+ private calendarEventToEnvelope(calEvent: CalendarEvent): EventEnvelope | null {
573
+ if (calEvent.status === 'cancelled') return null;
574
+
575
+ const startTime = calEvent.start.dateTime || calEvent.start.date;
576
+ if (!startTime) return null;
577
+
578
+ const occurredAt = new Date(startTime);
579
+ if (Number.isNaN(occurredAt.getTime())) return null;
580
+
581
+ const isAllDay = !calEvent.start.dateTime;
582
+ const endTime = calEvent.end.dateTime || calEvent.end.date;
583
+
584
+ // Build payload text from description + attendees
585
+ const parts: string[] = [];
586
+ if (calEvent.description) {
587
+ parts.push(calEvent.description);
588
+ }
589
+ if (calEvent.attendees && calEvent.attendees.length > 0) {
590
+ const attendeeList = calEvent.attendees.map((a) => a.displayName || a.email).join(', ');
591
+ parts.push(`Attendees: ${attendeeList}`);
592
+ }
593
+
594
+ return {
595
+ origin_id: calEvent.id,
596
+ title: calEvent.summary || '(no title)',
597
+ payload_text: parts.join('\n\n'),
598
+ author_name: calEvent.organizer?.displayName || calEvent.organizer?.email,
599
+ source_url: calEvent.htmlLink,
600
+ occurred_at: occurredAt,
601
+ origin_type: 'event',
602
+ metadata: {
603
+ status: calEvent.status,
604
+ ...(calEvent.location ? { location: calEvent.location } : {}),
605
+ ...(calEvent.organizer?.email ? { organizer: calEvent.organizer.email } : {}),
606
+ attendee_count: calEvent.attendees?.length ?? 0,
607
+ start_time: startTime,
608
+ ...(endTime ? { end_time: endTime } : {}),
609
+ all_day: isAllDay,
610
+ },
611
+ };
612
+ }
613
+
614
+ private buildResult(
615
+ events: EventEnvelope[],
616
+ syncToken: string | undefined,
617
+ itemsFound: number
618
+ ): SyncResult {
619
+ // Sort events by occurred_at descending
620
+ events.sort((a, b) => b.occurred_at.getTime() - a.occurred_at.getTime());
621
+
622
+ const newCheckpoint: CalendarCheckpoint = {
623
+ ...(syncToken ? { sync_token: syncToken } : {}),
624
+ last_sync_at: new Date().toISOString(),
625
+ };
626
+
627
+ return {
628
+ events,
629
+ checkpoint: newCheckpoint as Record<string, unknown>,
630
+ metadata: {
631
+ items_found: itemsFound,
632
+ },
633
+ };
634
+ }
635
+
636
+ private async apiGet(url: string, token: string): Promise<Response> {
637
+ return fetch(url, {
638
+ headers: { Authorization: `Bearer ${token}` },
639
+ });
640
+ }
641
+ }