skills/calendar/SKILL.md
Google Calendar integration for events and availability. Built on top of google-oauth for authentication, with SQLite caching, free/busy queries, and full Calendar API capabilities.
npx skillsauth add ticruz38/skills calendarInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Google Calendar integration for managing events and checking availability through your agent. Built on top of the google-oauth skill for secure authentication.
npm install
npm run build
The calendar skill requires Calendar authorization through the google-oauth skill:
# Connect your Google account with Calendar scope
node ../google-oauth/dist/cli.js connect default calendar
# Check connection status
node dist/cli.js status
node dist/cli.js health
node dist/cli.js calendars
# List events for next 7 days
node dist/cli.js list
# List more events
node dist/cli.js list --max 50 --days 14
# Search events
node dist/cli.js list --query "meeting"
# Specific calendar
node dist/cli.js list --calendar [email protected]
node dist/cli.js today
# Specific calendar
node dist/cli.js today --calendar [email protected]
# Next 10 events
node dist/cli.js upcoming
# Next 20 events for next 14 days
node dist/cli.js upcoming --max 20 --days 14
node dist/cli.js get <event-id>
# Quick create (1 hour, starting next hour)
node dist/cli.js create --summary "Team Meeting"
# With details
node dist/cli.js create \
--summary "Project Review" \
--description "Quarterly project review" \
--location "Conference Room A" \
--start "2024-01-15T10:00:00" \
--duration 90 \
--attendees "[email protected],[email protected]"
# With end time
node dist/cli.js create \
--summary "Lunch" \
--start "2024-01-15T12:00:00" \
--end "2024-01-15T13:00:00"
# Change title
node dist/cli.js update <event-id> --summary "New Title"
# Change time
node dist/cli.js update <event-id> \
--start "2024-01-15T14:00:00" \
--end "2024-01-15T15:00:00"
# Cancel event
node dist/cli.js update <event-id> --status cancelled
node dist/cli.js delete <event-id>
# Find 60-minute slots in next 7 days
node dist/cli.js free
# Find 30-minute slots in next 3 days
node dist/cli.js free --duration 30 --days 3
# Check specific calendars
node dist/cli.js free --calendars "primary,[email protected]"
import { CalendarSkill } from '@openclaw/calendar';
// Create skill for default profile
const calendar = new CalendarSkill();
// Or for specific profile
const workCalendar = CalendarSkill.forProfile('work');
const status = await calendar.getStatus();
console.log('Connected:', status.connected);
console.log('Email:', status.email);
console.log('Has Calendar:', status.hasCalendarScope);
const calendars = await calendar.listCalendars();
for (const cal of calendars) {
console.log(`${cal.summary}: ${cal.id}`);
if (cal.primary) {
console.log(' (Primary calendar)');
}
}
const result = await calendar.listEvents({
timeMin: new Date().toISOString(),
timeMax: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(),
maxResults: 50,
singleEvents: true,
orderBy: 'startTime',
});
for (const event of result.events) {
console.log(`${event.summary}: ${event.start.dateTime}`);
}
// Pagination
const nextPage = await calendar.listEvents({
pageToken: result.nextPageToken,
// ... other options
});
const event = await calendar.getEvent('event-id');
console.log('Title:', event.summary);
console.log('Description:', event.description);
console.log('Location:', event.location);
console.log('Start:', event.start.dateTime);
console.log('End:', event.end.dateTime);
// Attendees
for (const attendee of event.attendees || []) {
console.log(`${attendee.email}: ${attendee.responseStatus}`);
}
const event = await calendar.createEvent({
summary: 'Team Meeting',
description: 'Weekly sync',
location: 'Conference Room A',
start: {
dateTime: '2024-01-15T10:00:00',
timeZone: 'America/New_York',
},
end: {
dateTime: '2024-01-15T11:00:00',
timeZone: 'America/New_York',
},
attendees: [
{ email: '[email protected]' },
{ email: '[email protected]', optional: true },
],
});
console.log('Created:', event.htmlLink);
const event = await calendar.createEvent({
summary: 'Weekly Standup',
start: { dateTime: '2024-01-15T09:00:00' },
end: { dateTime: '2024-01-15T09:30:00' },
recurrence: [
{
frequency: 'WEEKLY',
byDay: ['MO', 'WE', 'FR'],
until: '2024-12-31',
},
],
});
await calendar.updateEvent('event-id', {
summary: 'Updated Title',
description: 'New description',
location: 'New Location',
start: { dateTime: '2024-01-15T14:00:00' },
end: { dateTime: '2024-01-15T15:00:00' },
});
await calendar.deleteEvent('event-id');
const freeSlots = await calendar.findFreeTime({
timeMin: new Date().toISOString(),
timeMax: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(),
duration: 60, // 60 minutes
calendars: ['primary', '[email protected]'],
});
for (const slot of freeSlots) {
console.log(`Free: ${slot.start} - ${slot.end}`);
}
const events = await calendar.getUpcomingEvents({
maxResults: 10,
days: 7,
});
for (const event of events) {
console.log(`${event.summary}: ${event.start.dateTime}`);
}
const events = await calendar.getTodayEvents();
console.log(`You have ${events.length} events today`);
const health = await calendar.healthCheck();
if (health.status === 'healthy') {
console.log('Calendar API is accessible');
} else {
console.error('Issue:', health.message);
}
// All-day event (no time)
const event = await calendar.createEvent({
summary: 'Vacation Day',
start: { date: '2024-01-15' },
end: { date: '2024-01-16' }, // End date is exclusive
});
// Timed event
const event = await calendar.createEvent({
summary: 'Meeting',
start: { dateTime: '2024-01-15T10:00:00', timeZone: 'America/New_York' },
end: { dateTime: '2024-01-15T11:00:00', timeZone: 'America/New_York' },
});
recurrence: [{
frequency: 'DAILY',
interval: 1, // Every day
count: 10, // For 10 occurrences
}]
recurrence: [{
frequency: 'WEEKLY',
byDay: ['MO', 'WE', 'FR'], // Monday, Wednesday, Friday
until: '2024-12-31', // Until end of year
}]
recurrence: [{
frequency: 'MONTHLY',
byMonthDay: [15], // 15th of each month
}]
Cached event metadata is stored in:
~/.openclaw/skills/calendar/cache.db
Tables:
events - Cached event datacalendars - Calendar listsync_tokens - Sync state for incremental updatesManage multiple Google accounts:
import { CalendarSkill } from '@openclaw/calendar';
// Work account
const work = CalendarSkill.forProfile('work');
// Personal account
const personal = CalendarSkill.forProfile('personal');
// Use independently
const workEvents = await work.listEvents({ maxResults: 10 });
const personalEvents = await personal.listEvents({ maxResults: 10 });
Each profile needs separate authentication:
node ../google-oauth/dist/cli.js connect work calendar
node ../google-oauth/dist/cli.js connect personal calendar
try {
const events = await calendar.listEvents();
} catch (error) {
if (error.message.includes('Not connected')) {
console.log('Please authenticate first');
} else if (error.message.includes('Calendar scope')) {
console.log('Re-authenticate with Calendar permissions');
} else {
console.error('Error:', error.message);
}
}
# Type checking
npm run typecheck
# Build
npm run build
# Check status
npm run status
# List calendars
npm run cli -- calendars
# List events
npm run cli -- list --max 5
# Find free time
npm run cli -- free --duration 60
Authenticate with google-oauth first:
node ../google-oauth/dist/cli.js connect default calendar
Your Google account is connected but without Calendar permissions. Reconnect:
node ../google-oauth/dist/cli.js disconnect default
node ../google-oauth/dist/cli.js connect default calendar
Check health status:
node dist/cli.js health
@openclaw/google-oauth: For Calendar authentication@openclaw/auth-provider: Base authentication (via google-oauth)sqlite3: Local cachingtesting
Suggest recipes based on dietary preferences, available ingredients, and cuisine preferences
development
Extract data from receipt photos using Google Vision API
business
QuickBooks Online integration for accounting sync - sync customers, invoices, and transactions with two-way sync and conflict resolution
testing
QuickBooks OAuth adapter for QuickBooks Online accounting integration. Built on top of auth-provider for secure token management with automatic refresh, multi-profile support, sandbox/production toggle, and health checks.