← Public packages
@kody/google
Call Gmail, Calendar, Tasks, Drive, Docs, Sheets, People, YouTube, and Analytics through saved Google OAuth.
src/calendar.ts
81 lines · 10.1 KB · TypeScriptimport { scanEventsAcrossCalendars } from './calendar-across.ts'
import { requestGoogle } from './core.ts'
import type { GoogleAuthParams } from './accounts.ts'
import type { JsonObject } from './types.ts'
export type CalendarListParams = GoogleAuthParams & { maxResults?: number; pageToken?: string; minAccessRole?: string; showDeleted?: boolean; showHidden?: boolean; fields?: string }
export type CalendarEventsParams = GoogleAuthParams & { calendarId?: string; timeMin?: string; timeMax?: string; maxResults?: number; q?: string; singleEvents?: boolean; orderBy?: string; pageToken?: string; fields?: string }
export type CalendarEventsAcrossCalendarsParams = CalendarEventsParams & {
calendarMaxResults?: number
showHiddenCalendars?: boolean
/**
* Wall-clock budget in milliseconds. Defaults to 20000. Capped at 60000.
* There is no unlimited scan.
*/
budgetMs?: number
}
export type CalendarEventMutationParams = GoogleAuthParams & { calendarId?: string; event?: JsonObject; dryRun?: boolean; sendUpdates?: string }
export type CalendarEventDeleteParams = GoogleAuthParams & { calendarId?: string; eventId: string; sendUpdates?: string; dryRun?: boolean }
export type CalendarEventGetParams = GoogleAuthParams & { calendarId?: string; eventId: string; fields?: string }
export type CalendarEventMoveParams = GoogleAuthParams & { calendarId?: string; eventId: string; destination: string; sendUpdates?: string; dryRun?: boolean }
export type CalendarEventQuickAddParams = GoogleAuthParams & { calendarId?: string; text: string; sendUpdates?: string; dryRun?: boolean }
export type CalendarAclListParams = GoogleAuthParams & { calendarId?: string; maxResults?: number; pageToken?: string; showDeleted?: boolean; syncToken?: string; fields?: string }
export type CalendarGetParams = GoogleAuthParams & { calendarId?: string; fields?: string }
export type CalendarFreeBusyParams = GoogleAuthParams & { request?: JsonObject; items?: Array<{ id: string }>; timeMin?: string; timeMax?: string; timeZone?: string; groupExpansionMax?: number; calendarExpansionMax?: number; fields?: string }
/**
* Return the Google Calendar helper namespace for calendars, events, ACL, and free/busy.
* `listEventsAcrossCalendars` stops at a 20 second wall-clock budget (60 second cap) and sets `truncated` when the scan stops early.
* @returns Helper namespace or overview for this Google product.
* @example
* import calendar from 'kody:@kody/google/calendar'
* const events = await calendar().listEvents({ maxResults: 5 })
* // => { items: [...], summary: '...' }
*/
export default function calendar() { return { listCalendars, getCalendar, listEvents, listEventsAcrossCalendars, createEvent, getEvent, updateEvent, deleteEvent, moveEvent, quickAddEvent, listCalendarAcl, queryFreeBusy } }
export async function listCalendars(params: CalendarListParams = {}): Promise<JsonObject> { const { maxResults = 50, pageToken, minAccessRole, showDeleted, showHidden, fields } = params; return await requestGoogle({ ...params, api: 'google', path: '/calendar/v3/users/me/calendarList', query: { maxResults, pageToken, minAccessRole, showDeleted, showHidden, fields } }) as JsonObject }
export async function getCalendar(params: CalendarGetParams): Promise<JsonObject> { const { calendarId = 'primary', fields } = params; return await requestGoogle({ ...params, api: 'google', path: '/calendar/v3/calendars/' + encodeURIComponent(calendarId), query: { fields } }) as JsonObject }
export async function listEvents(params: CalendarEventsParams): Promise<JsonObject> { const { calendarId = 'primary', timeMin, timeMax, maxResults = 10, q, singleEvents = true, orderBy = singleEvents ? 'startTime' : undefined, pageToken, fields } = params; return await requestGoogle({ ...params, api: 'google', path: '/calendar/v3/calendars/' + encodeURIComponent(calendarId) + '/events', query: { timeMin, timeMax, maxResults, q, singleEvents, orderBy, pageToken, fields } }) as JsonObject }
/**
* List events on every calendar the account can see.
* Stops when `budgetMs` elapses so an MCP caller gets a partial result
* instead of hanging past the client timeout. Hidden calendars stay included
* unless `showHiddenCalendars` is false. Progress lines are written with
* `console.info` so the run record has diagnostics when the call returns.
*
* @param params.integration - Saved OAuth integration name. Wins over `account`. Defaults to `google`.
* @param params.account - Account selector (`work` → `google-work`). Omitted uses the default integration.
* @param params.budgetMs - Wall-clock budget in milliseconds. Defaults to 20000 (under the typical ~32s MCP client window). Values above 60000 are capped. Non-positive values use the default. There is no unlimited scan. One in-flight Google request can finish after the deadline.
* @param params.showHiddenCalendars - Include hidden calendars. Defaults to true.
* @param params.calendarMaxResults - Page size for calendarList. Defaults to 250.
* @param params.maxResults - Page size for each calendar's events. Defaults to 250.
* @param params.timeMin - Optional RFC3339 lower bound forwarded to each calendar's event list.
* @param params.timeMax - Optional RFC3339 upper bound forwarded to each calendar's event list.
* @returns Collected events, the calendars listed, per-calendar failures, `budgetMs`, `calendarsScanned`, and `truncated` / `truncatedReason` (`'budget'` when the scan stopped early, otherwise null).
* @example
* import { listEventsAcrossCalendars } from 'kody:@kody/google/calendar'
* const upcoming = await listEventsAcrossCalendars({
* timeMin: '2026-09-27T00:00:00Z',
* timeMax: '2026-10-04T00:00:00Z',
* })
* // => { items, calendars, failures, budgetMs: 20000, truncated, truncatedReason, calendarsScanned }
*/
export async function listEventsAcrossCalendars(
params: CalendarEventsAcrossCalendarsParams = {},
) {
return await scanEventsAcrossCalendars({
params,
listCalendars,
listEvents,
log: (message) => console.info(message),
})
}
export async function createEvent(params: CalendarEventMutationParams): Promise<JsonObject> { const { calendarId = 'primary', event, dryRun = false, sendUpdates } = params; if (!event || typeof event !== 'object') throw new Error('createEvent requires event.'); if (dryRun) return { dryRun: true, account: params.account, calendarId, event }; return await requestGoogle({ ...params, api: 'google', method: 'POST', path: '/calendar/v3/calendars/' + encodeURIComponent(calendarId) + '/events', query: { sendUpdates }, body: event }) as JsonObject }
export async function getEvent(params: CalendarEventGetParams): Promise<JsonObject> { const { calendarId = 'primary', eventId, fields } = params; if (!eventId) throw new Error('getEvent requires eventId.'); return await requestGoogle({ ...params, api: 'google', path: '/calendar/v3/calendars/' + encodeURIComponent(calendarId) + '/events/' + encodeURIComponent(eventId), query: { fields } }) as JsonObject }
export async function updateEvent(params: CalendarEventMutationParams & { eventId: string }): Promise<JsonObject> { const { calendarId = 'primary', eventId, event, dryRun = false, sendUpdates } = params; if (!eventId) throw new Error('updateEvent requires eventId.'); if (!event || typeof event !== 'object') throw new Error('updateEvent requires event.'); if (dryRun) return { dryRun: true, account: params.account, calendarId, eventId, event }; return await requestGoogle({ ...params, api: 'google', method: 'PATCH', path: '/calendar/v3/calendars/' + encodeURIComponent(calendarId) + '/events/' + encodeURIComponent(eventId), query: { sendUpdates }, body: event }) as JsonObject }
export async function deleteEvent(params: CalendarEventDeleteParams): Promise<JsonObject | null> { const { calendarId = 'primary', eventId, sendUpdates, dryRun = false } = params; if (!eventId) throw new Error('deleteEvent requires eventId.'); if (dryRun) return { dryRun: true, account: params.account, calendarId, eventId, wouldDelete: true }; await requestGoogle({ ...params, api: 'google', method: 'DELETE', path: '/calendar/v3/calendars/' + encodeURIComponent(calendarId) + '/events/' + encodeURIComponent(eventId), query: { sendUpdates } }); return null }
export async function moveEvent(params: CalendarEventMoveParams): Promise<JsonObject> { const { calendarId = 'primary', eventId, destination, sendUpdates, dryRun = false } = params; if (!eventId) throw new Error('moveEvent requires eventId.'); if (!destination) throw new Error('moveEvent requires destination.'); if (dryRun) return { dryRun: true, account: params.account, calendarId, eventId, destination }; return await requestGoogle({ ...params, api: 'google', method: 'POST', path: '/calendar/v3/calendars/' + encodeURIComponent(calendarId) + '/events/' + encodeURIComponent(eventId) + '/move', query: { destination, sendUpdates } }) as JsonObject }
export async function quickAddEvent(params: CalendarEventQuickAddParams): Promise<JsonObject> { const { calendarId = 'primary', text, sendUpdates, dryRun = false } = params; if (!text) throw new Error('quickAddEvent requires text.'); if (dryRun) return { dryRun: true, account: params.account, calendarId, text }; return await requestGoogle({ ...params, api: 'google', method: 'POST', path: '/calendar/v3/calendars/' + encodeURIComponent(calendarId) + '/events/quickAdd', query: { text, sendUpdates } }) as JsonObject }
export async function listCalendarAcl(params: CalendarAclListParams): Promise<JsonObject> { const { calendarId = 'primary', maxResults, pageToken, showDeleted, syncToken, fields } = params; return await requestGoogle({ ...params, api: 'google', path: '/calendar/v3/calendars/' + encodeURIComponent(calendarId) + '/acl', query: { maxResults, pageToken, showDeleted, syncToken, fields } }) as JsonObject }
export async function queryFreeBusy(params: CalendarFreeBusyParams): Promise<JsonObject> { const { request, items, timeMin, timeMax, timeZone, groupExpansionMax, calendarExpansionMax, fields } = params; const body = request || { timeMin, timeMax, timeZone, groupExpansionMax, calendarExpansionMax, items }; if (!request && !items) throw new Error('queryFreeBusy requires request or items.'); return await requestGoogle({ ...params, api: 'google', method: 'POST', path: '/calendar/v3/freeBusy', query: { fields }, body }) as JsonObject }