Files
claude-code-conduit/server/google_calendar.mjs
mikael-lovqvists-claude-agent 25891ece7e Add Google Calendar integration and Claude docs
- New server/google_calendar.mjs wrapping googleapis v3 with auto token refresh
- Four new actions: calendar-list-events (auto-accept), calendar-create/update/delete-event (queue)
- bin/ccc-gcal-auth.mjs one-time OAuth2 consent flow helper
- config.example.json updated with google_calendar block
- server/config.mjs, index.mjs wired up following the same pattern as SMTP/mailer
- Bump version to 1.2.0
- Add CLAUDE.md and claude-info/ with architecture reference, feature plans, and contributing checklist

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 15:33:36 +00:00

77 lines
2.4 KiB
JavaScript

import { readFileSync, writeFileSync } from 'node:fs';
import { google } from 'googleapis';
// Returns a calendar client wrapping the Google Calendar API v3.
// cfg = { credentials_path, token_path }
export function create_calendar_client({ credentials_path, token_path }) {
const credentials = JSON.parse(readFileSync(credentials_path, 'utf8'));
const { client_id, client_secret, redirect_uris } = credentials.installed ?? credentials.web;
const oauth2_client = new google.auth.OAuth2(client_id, client_secret, redirect_uris[0]);
const token = JSON.parse(readFileSync(token_path, 'utf8'));
oauth2_client.setCredentials(token);
// Persist refreshed tokens automatically
oauth2_client.on('tokens', (new_tokens) => {
let current = {};
try {
current = JSON.parse(readFileSync(token_path, 'utf8'));
} catch (_) {}
const merged = { ...current, ...new_tokens };
writeFileSync(token_path, JSON.stringify(merged, null, '\t') + '\n', 'utf8');
});
const cal = google.calendar({ version: 'v3', auth: oauth2_client });
async function list_events({ calendar_id = 'primary', time_min, time_max, max_results = 10 } = {}) {
const res = await cal.events.list({
calendarId: calendar_id,
timeMin: time_min,
timeMax: time_max,
maxResults: max_results,
singleEvents: true,
orderBy: 'startTime',
});
return res.data.items ?? [];
}
async function create_event({ calendar_id = 'primary', summary, start, end, description }) {
const res = await cal.events.insert({
calendarId: calendar_id,
requestBody: {
summary,
description,
start: { dateTime: start },
end: { dateTime: end },
},
});
return res.data;
}
async function update_event({ calendar_id = 'primary', event_id, summary, start, end, description }) {
const patch = {};
if (summary !== undefined) { patch.summary = summary; }
if (description !== undefined) { patch.description = description; }
if (start !== undefined) { patch.start = { dateTime: start }; }
if (end !== undefined) { patch.end = { dateTime: end }; }
const res = await cal.events.patch({
calendarId: calendar_id,
eventId: event_id,
requestBody: patch,
});
return res.data;
}
async function delete_event({ calendar_id = 'primary', event_id }) {
await cal.events.delete({
calendarId: calendar_id,
eventId: event_id,
});
return { deleted: true, event_id };
}
return { list_events, create_event, update_event, delete_event };
}