@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.
- package/README.md +20 -27
- package/dist/bundled-skills/lobu/SKILL.md +11 -11
- package/dist/commands/_lib/apply/apply-cmd.d.ts +38 -0
- package/dist/commands/_lib/apply/apply-cmd.d.ts.map +1 -1
- package/dist/commands/_lib/apply/apply-cmd.js +574 -40
- package/dist/commands/_lib/apply/apply-cmd.js.map +1 -1
- package/dist/commands/_lib/apply/client.d.ts +180 -1
- package/dist/commands/_lib/apply/client.d.ts.map +1 -1
- package/dist/commands/_lib/apply/client.js +308 -28
- package/dist/commands/_lib/apply/client.js.map +1 -1
- package/dist/commands/_lib/apply/desired-state.d.ts +134 -3
- package/dist/commands/_lib/apply/desired-state.d.ts.map +1 -1
- package/dist/commands/_lib/apply/desired-state.js +703 -89
- package/dist/commands/_lib/apply/desired-state.js.map +1 -1
- package/dist/commands/_lib/apply/diff.d.ts +61 -3
- package/dist/commands/_lib/apply/diff.d.ts.map +1 -1
- package/dist/commands/_lib/apply/diff.js +382 -92
- package/dist/commands/_lib/apply/diff.js.map +1 -1
- package/dist/commands/_lib/apply/prompt.d.ts +6 -0
- package/dist/commands/_lib/apply/prompt.d.ts.map +1 -1
- package/dist/commands/_lib/apply/prompt.js +16 -0
- package/dist/commands/_lib/apply/prompt.js.map +1 -1
- package/dist/commands/_lib/apply/render.d.ts +9 -0
- package/dist/commands/_lib/apply/render.d.ts.map +1 -1
- package/dist/commands/_lib/apply/render.js +80 -3
- package/dist/commands/_lib/apply/render.js.map +1 -1
- package/dist/commands/agent.d.ts +7 -0
- package/dist/commands/agent.d.ts.map +1 -1
- package/dist/commands/agent.js +65 -1
- package/dist/commands/agent.js.map +1 -1
- package/dist/commands/chat.d.ts +12 -9
- package/dist/commands/chat.d.ts.map +1 -1
- package/dist/commands/chat.js +125 -57
- package/dist/commands/chat.js.map +1 -1
- package/dist/commands/dev.d.ts +23 -7
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +197 -49
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/doctor.d.ts +1 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +136 -0
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/eval.d.ts +8 -0
- package/dist/commands/eval.d.ts.map +1 -1
- package/dist/commands/eval.js +72 -6
- package/dist/commands/eval.js.map +1 -1
- package/dist/commands/init.d.ts +22 -5
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +355 -182
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/link.d.ts +11 -0
- package/dist/commands/link.d.ts.map +1 -0
- package/dist/commands/link.js +28 -0
- package/dist/commands/link.js.map +1 -0
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +14 -2
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/memory/_lib/browser-auth-cmd.d.ts.map +1 -1
- package/dist/commands/memory/_lib/browser-auth-cmd.js +3 -3
- package/dist/commands/memory/_lib/browser-auth-cmd.js.map +1 -1
- package/dist/commands/memory/_lib/mcp.d.ts +2 -2
- package/dist/commands/memory/_lib/mcp.d.ts.map +1 -1
- package/dist/commands/memory/_lib/mcp.js +24 -12
- package/dist/commands/memory/_lib/mcp.js.map +1 -1
- package/dist/commands/memory/_lib/openclaw-auth.d.ts +1 -0
- package/dist/commands/memory/_lib/openclaw-auth.d.ts.map +1 -1
- package/dist/commands/memory/_lib/openclaw-auth.js +14 -3
- package/dist/commands/memory/_lib/openclaw-auth.js.map +1 -1
- package/dist/commands/memory/_lib/openclaw-cmd.js +1 -1
- package/dist/commands/memory/_lib/openclaw-cmd.js.map +1 -1
- package/dist/commands/memory/_lib/schema.d.ts +29 -2
- package/dist/commands/memory/_lib/schema.d.ts.map +1 -1
- package/dist/commands/memory/_lib/schema.js +121 -5
- package/dist/commands/memory/_lib/schema.js.map +1 -1
- package/dist/commands/memory/_lib/seed-cmd.d.ts.map +1 -1
- package/dist/commands/memory/_lib/seed-cmd.js +46 -24
- package/dist/commands/memory/_lib/seed-cmd.js.map +1 -1
- package/dist/commands/memory/run.d.ts.map +1 -1
- package/dist/commands/memory/run.js +2 -2
- package/dist/commands/memory/run.js.map +1 -1
- package/dist/commands/org.d.ts +4 -0
- package/dist/commands/org.d.ts.map +1 -1
- package/dist/commands/org.js +10 -0
- package/dist/commands/org.js.map +1 -1
- package/dist/commands/platforms/platform-prompts.d.ts +0 -1
- package/dist/commands/platforms/platform-prompts.d.ts.map +1 -1
- package/dist/commands/platforms/platform-prompts.js +54 -8
- package/dist/commands/platforms/platform-prompts.js.map +1 -1
- package/dist/commands/telemetry.d.ts +10 -0
- package/dist/commands/telemetry.d.ts.map +1 -0
- package/dist/commands/telemetry.js +68 -0
- package/dist/commands/telemetry.js.map +1 -0
- package/dist/commands/token.d.ts +9 -0
- package/dist/commands/token.d.ts.map +1 -1
- package/dist/commands/token.js +54 -0
- package/dist/commands/token.js.map +1 -1
- package/dist/commands/whoami.d.ts.map +1 -1
- package/dist/commands/whoami.js +1 -1
- package/dist/commands/whoami.js.map +1 -1
- package/dist/connectors/README.md +534 -0
- package/dist/connectors/__tests__/browser-scraper-utils.test.ts +186 -0
- package/dist/connectors/apple_health.ts +138 -0
- package/dist/connectors/apple_screen_time.ts +82 -0
- package/dist/connectors/browser-scraper-utils.ts +246 -0
- package/dist/connectors/capterra.ts +277 -0
- package/dist/connectors/g2.ts +290 -0
- package/dist/connectors/github.ts +1530 -0
- package/dist/connectors/glassdoor.ts +295 -0
- package/dist/connectors/gmaps.ts +197 -0
- package/dist/connectors/google_calendar.ts +641 -0
- package/dist/connectors/google_gmail.ts +754 -0
- package/dist/connectors/google_photos.ts +776 -0
- package/dist/connectors/google_play.ts +349 -0
- package/dist/connectors/hackernews.ts +471 -0
- package/dist/connectors/index.ts +28 -0
- package/dist/connectors/ios_appstore.ts +226 -0
- package/dist/connectors/linkedin.ts +494 -0
- package/dist/connectors/local_directory.ts +91 -0
- package/dist/connectors/microsoft_outlook.ts +410 -0
- package/dist/connectors/producthunt.ts +471 -0
- package/dist/connectors/reddit.ts +600 -0
- package/dist/connectors/revolut.ts +572 -0
- package/dist/connectors/rss.ts +448 -0
- package/dist/connectors/spotify.ts +590 -0
- package/dist/connectors/trustpilot.ts +203 -0
- package/dist/connectors/website.ts +629 -0
- package/dist/connectors/whatsapp.ts +1081 -0
- package/dist/connectors/whatsapp_local.ts +125 -0
- package/dist/connectors/x.ts +536 -0
- package/dist/connectors/youtube.ts +666 -0
- package/dist/db/migrations/00000000000000_baseline.sql +4867 -0
- package/dist/db/migrations/20260405193000_add_mcp_sessions.sql +33 -0
- package/dist/db/migrations/20260408120000_remove_system_connectors.sql +48 -0
- package/dist/db/migrations/20260408120001_optional_compiled_code.sql +6 -0
- package/dist/db/migrations/20260409110000_add_active_watcher_run_index.sql +9 -0
- package/dist/db/migrations/20260409130000_connector_default_config.sql +5 -0
- package/dist/db/migrations/20260410120000_add_agent_secrets.sql +25 -0
- package/dist/db/migrations/20260413170000_add_watcher_group_id.sql +67 -0
- package/dist/db/migrations/20260416120000_add_entity_wa_jid_index.sql +14 -0
- package/dist/db/migrations/20260417100000_add_entity_identities.sql +77 -0
- package/dist/db/migrations/20260418100000_add_auth_runs.sql +83 -0
- package/dist/db/migrations/20260418110000_add_runs_created_by_user.sql +18 -0
- package/dist/db/migrations/20260419120000_add_event_identity_indexes.sql +56 -0
- package/dist/db/migrations/20260420120000_extend_reserved_org_slugs.sql +56 -0
- package/dist/db/migrations/20260424030000_add_watcher_run_correlation.sql +52 -0
- package/dist/db/migrations/20260424130000_relax_events_client_id_fk.sql +47 -0
- package/dist/db/migrations/20260425100000_normalize_watcher_feedback.sql +91 -0
- package/dist/db/migrations/20260425120000_add_run_diagnostics.sql +20 -0
- package/dist/db/migrations/20260425130000_add_repair_agent_plumbing.sql +46 -0
- package/dist/db/migrations/20260426120000_entities_entity_type_fk.sql +101 -0
- package/dist/db/migrations/20260426130000_db_integrity_cleanup.sql +104 -0
- package/dist/db/migrations/20260426130001_db_integrity_cleanup_concurrent.sql +187 -0
- package/dist/db/migrations/20260427133000_events_created_by_nullable.sql +74 -0
- package/dist/db/migrations/20260427140000_identity_engine_indexes.sql +140 -0
- package/dist/db/migrations/20260427150000_drop_events_source_id.sql +177 -0
- package/dist/db/migrations/20260427160000_drop_dead_schema.sql +76 -0
- package/dist/db/migrations/20260427170000_market_founder_to_member.sql +364 -0
- package/dist/db/migrations/20260428040000_cascade_events_watchers_org_fk.sql +66 -0
- package/dist/db/migrations/20260428050000_add_runs_approved_input.sql +9 -0
- package/dist/db/migrations/20260429010000_auth_profile_tenant_scoped_fk.sql +79 -0
- package/dist/db/migrations/20260429060000_extend_runs_for_lobu_queue.sql +108 -0
- package/dist/db/migrations/20260429120000_agent_changed_notify.sql +97 -0
- package/dist/db/migrations/20260429120100_user_auth_profiles_and_model_prefs.sql +36 -0
- package/dist/db/migrations/20260429120200_fix_notify_old_keys.sql +130 -0
- package/dist/db/migrations/20260429130000_oauth_states_cli_sessions_rate_limits.sql +83 -0
- package/dist/db/migrations/20260429140000_phase8_grants_chat_connections_mcp_sessions.sql +84 -0
- package/dist/db/migrations/20260429140100_runs_priority_expires_at_retry_delay.sql +44 -0
- package/dist/db/migrations/20260429180000_drop_invalidatable_cache_triggers.sql +25 -0
- package/dist/db/migrations/20260430005614_agents_apply_fields.sql +21 -0
- package/dist/db/migrations/20260430022231_fix_connection_config_encryption.sql +69 -0
- package/dist/db/migrations/20260430151215_add_task_run_type.sql +77 -0
- package/dist/db/migrations/20260501000000_drop_cli_sessions.sql +27 -0
- package/dist/db/migrations/20260501133000_lobu_memory_mcp_id.sql +117 -0
- package/dist/db/migrations/20260502000000_drop_chat_connections.sql +60 -0
- package/dist/db/migrations/20260503000000_agent_secrets_org_scope.sql +56 -0
- package/dist/db/migrations/20260504000000_flatten_agents_drop_sandbox_model.sql +48 -0
- package/dist/db/migrations/20260510220000_connector_required_capability.sql +47 -0
- package/dist/db/migrations/20260512000000_device_worker_connection_binding.sql +113 -0
- package/dist/db/migrations/20260512131703_connections_slug.sql +131 -0
- package/dist/db/migrations/20260513000000_chat_user_identities.sql +24 -0
- package/dist/db/migrations/20260513120000_auth_profiles_device_binding.sql +50 -0
- package/dist/db/migrations/20260513150000_auth_profiles_cdp_url.sql +43 -0
- package/dist/db/migrations/20260513200000_notifications_as_events.sql +86 -0
- package/dist/db/migrations/20260514000000_scheduled_jobs.sql +97 -0
- package/dist/db/migrations/20260514120000_auth_profiles_connector_key_nullable.sql +42 -0
- package/dist/eval/types.d.ts +2 -0
- package/dist/eval/types.d.ts.map +1 -1
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +210 -132
- package/dist/index.js.map +1 -1
- package/dist/internal/api-client.d.ts +4 -8
- package/dist/internal/api-client.d.ts.map +1 -1
- package/dist/internal/api-client.js +1 -1
- package/dist/internal/api-client.js.map +1 -1
- package/dist/internal/context.js +2 -2
- package/dist/internal/context.js.map +1 -1
- package/dist/internal/credentials.d.ts.map +1 -1
- package/dist/internal/credentials.js +6 -1
- package/dist/internal/credentials.js.map +1 -1
- package/dist/internal/gateway-url.d.ts +14 -0
- package/dist/internal/gateway-url.d.ts.map +1 -1
- package/dist/internal/gateway-url.js +19 -0
- package/dist/internal/gateway-url.js.map +1 -1
- package/dist/internal/index.d.ts +3 -4
- package/dist/internal/index.d.ts.map +1 -1
- package/dist/internal/index.js +3 -3
- package/dist/internal/index.js.map +1 -1
- package/dist/internal/oauth.d.ts +6 -5
- package/dist/internal/oauth.d.ts.map +1 -1
- package/dist/internal/oauth.js +2 -2
- package/dist/internal/project-link.d.ts +10 -0
- package/dist/internal/project-link.d.ts.map +1 -0
- package/dist/internal/project-link.js +48 -0
- package/dist/internal/project-link.js.map +1 -0
- package/dist/providers.json +2 -2
- package/dist/server.bundle.mjs +31654 -30866
- package/dist/start-local.bundle.mjs +74409 -0
- package/dist/templates/README.md.tmpl +10 -11
- package/dist/templates/TESTING.md.tmpl +9 -9
- package/package.json +15 -13
- package/dist/__tests__/chat.integration.test.d.ts +0 -2
- package/dist/__tests__/chat.integration.test.d.ts.map +0 -1
- package/dist/__tests__/chat.integration.test.js +0 -337
- package/dist/__tests__/chat.integration.test.js.map +0 -1
- package/dist/__tests__/dev.test.d.ts +0 -2
- package/dist/__tests__/dev.test.d.ts.map +0 -1
- package/dist/__tests__/dev.test.js +0 -25
- package/dist/__tests__/dev.test.js.map +0 -1
- package/dist/__tests__/init-memory.test.d.ts +0 -2
- package/dist/__tests__/init-memory.test.d.ts.map +0 -1
- package/dist/__tests__/init-memory.test.js +0 -45
- package/dist/__tests__/init-memory.test.js.map +0 -1
- package/dist/__tests__/token.test.d.ts +0 -2
- package/dist/__tests__/token.test.d.ts.map +0 -1
- package/dist/__tests__/token.test.js +0 -52
- package/dist/__tests__/token.test.js.map +0 -1
- package/dist/commands/_lib/apply/__tests__/client.test.d.ts +0 -2
- package/dist/commands/_lib/apply/__tests__/client.test.d.ts.map +0 -1
- package/dist/commands/_lib/apply/__tests__/client.test.js +0 -23
- package/dist/commands/_lib/apply/__tests__/client.test.js.map +0 -1
- package/dist/commands/_lib/apply/__tests__/desired-state.test.d.ts +0 -2
- package/dist/commands/_lib/apply/__tests__/desired-state.test.d.ts.map +0 -1
- package/dist/commands/_lib/apply/__tests__/desired-state.test.js +0 -140
- package/dist/commands/_lib/apply/__tests__/desired-state.test.js.map +0 -1
- package/dist/commands/_lib/apply/__tests__/diff.test.d.ts +0 -2
- package/dist/commands/_lib/apply/__tests__/diff.test.d.ts.map +0 -1
- package/dist/commands/_lib/apply/__tests__/diff.test.js +0 -378
- package/dist/commands/_lib/apply/__tests__/diff.test.js.map +0 -1
- package/dist/commands/apply.d.ts +0 -3
- package/dist/commands/apply.d.ts.map +0 -1
- package/dist/commands/apply.js +0 -5
- package/dist/commands/apply.js.map +0 -1
- package/dist/commands/memory/_lib/openclaw-auth.test.d.ts +0 -2
- package/dist/commands/memory/_lib/openclaw-auth.test.d.ts.map +0 -1
- package/dist/commands/memory/_lib/openclaw-auth.test.js +0 -9
- package/dist/commands/memory/_lib/openclaw-auth.test.js.map +0 -1
- package/dist/internal/__tests__/api-client.test.d.ts +0 -2
- package/dist/internal/__tests__/api-client.test.d.ts.map +0 -1
- package/dist/internal/__tests__/api-client.test.js +0 -95
- package/dist/internal/__tests__/api-client.test.js.map +0 -1
- package/dist/internal/__tests__/context.test.d.ts +0 -2
- package/dist/internal/__tests__/context.test.d.ts.map +0 -1
- package/dist/internal/__tests__/context.test.js +0 -77
- 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
|
+
}
|