Files
claude-code-conduit/bin/ccc-gcal-auth.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

87 lines
2.4 KiB
JavaScript

#!/usr/bin/env node
// One-time OAuth2 authorization flow for Google Calendar.
// Run this on the host to generate a token file for the server.
//
// Usage:
// ccc-gcal-auth --credentials google-credentials.json --token google-token.json
import { readFileSync, writeFileSync } from 'node:fs';
import { createServer } from 'node:http';
import { google } from 'googleapis';
const SCOPES = ['https://www.googleapis.com/auth/calendar'];
const PORT = 3016;
const REDIRECT_URI = `http://localhost:${PORT}/callback`;
function get_arg(argv, flag) {
const i = argv.indexOf(flag);
return i !== -1 ? argv[i + 1] : null;
}
const credentials_path = get_arg(process.argv, '--credentials');
const token_path = get_arg(process.argv, '--token');
if (!credentials_path || !token_path) {
console.error('Usage: ccc-gcal-auth --credentials <path> --token <output-path>');
process.exit(1);
}
let credentials;
try {
credentials = JSON.parse(readFileSync(credentials_path, 'utf8'));
} catch (err) {
console.error(`Cannot read credentials file: ${err.message}`);
process.exit(1);
}
const { client_id, client_secret } = credentials.installed ?? credentials.web;
const oauth2_client = new google.auth.OAuth2(client_id, client_secret, REDIRECT_URI);
const auth_url = oauth2_client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES,
prompt: 'consent',
});
console.log('Open this URL in your browser to authorize Google Calendar access:\n');
console.log(auth_url);
console.log(`\nListening for redirect on http://localhost:${PORT}/callback ...`);
const server = createServer(async (req, res) => {
let url;
try {
url = new URL(req.url, `http://localhost:${PORT}`);
} catch (_) {
res.end('Bad request');
return;
}
if (url.pathname !== '/callback') {
res.end('Not found');
return;
}
const code = url.searchParams.get('code');
if (!code) {
res.writeHead(400);
res.end('Missing code parameter');
return;
}
try {
const { tokens } = await oauth2_client.getToken(code);
writeFileSync(token_path, JSON.stringify(tokens, null, '\t') + '\n', 'utf8');
res.end('Authorization successful. You may close this tab.');
console.log(`\nToken written to ${token_path}`);
server.close();
} catch (err) {
res.writeHead(500);
res.end(`Error: ${err.message}`);
console.error(`Token exchange failed: ${err.message}`);
server.close();
process.exit(1);
}
});
server.listen(PORT, '127.0.0.1');