calendar

SKILL.md

Calendar Skill

Google Calendar integration for managing events and checking availability through your agent. Built on top of the google-oauth skill for secure authentication.

Features

  • List Events: Browse events with time filters and pagination
  • Create & Update: Add and modify events with attendees
  • Free Time Finder: Find available time slots across calendars
  • Recurring Events: Support for recurring event patterns
  • Multiple Calendars: Work with any calendar in your account
  • Today & Upcoming: Quick access to relevant events
  • SQLite Cache: Local metadata caching for performance

Installation

npm install
npm run build

Prerequisites

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

CLI Usage

Check Status

# Check connection status
node dist/cli.js status

Health Check

node dist/cli.js health

List Calendars

node dist/cli.js calendars

List Events

# 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 work@company.com

Today's Events

node dist/cli.js today

# Specific calendar
node dist/cli.js today --calendar work@company.com

Upcoming Events

# Next 10 events
node dist/cli.js upcoming

# Next 20 events for next 14 days
node dist/cli.js upcoming --max 20 --days 14

Get Event Details

node dist/cli.js get <event-id>

Create Event

# 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 "alice@example.com,bob@example.com"

# With end time
node dist/cli.js create \
  --summary "Lunch" \
  --start "2024-01-15T12:00:00" \
  --end "2024-01-15T13:00:00"

Update Event

# 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

Delete Event

node dist/cli.js delete <event-id>

Find Free Time

# 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,work@company.com"

JavaScript/TypeScript API

Initialize

import { CalendarSkill } from '@openclaw/calendar';

// Create skill for default profile
const calendar = new CalendarSkill();

// Or for specific profile
const workCalendar = CalendarSkill.forProfile('work');

Check Status

const status = await calendar.getStatus();
console.log('Connected:', status.connected);
console.log('Email:', status.email);
console.log('Has Calendar:', status.hasCalendarScope);

List Calendars

const calendars = await calendar.listCalendars();

for (const cal of calendars) {
  console.log(`${cal.summary}: ${cal.id}`);
  if (cal.primary) {
    console.log('  (Primary calendar)');
  }
}

List Events

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
});

Get Single Event

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}`);
}

Create Event

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: 'alice@example.com' },
    { email: 'bob@example.com', optional: true },
  ],
});

console.log('Created:', event.htmlLink);

Create Recurring Event

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',
    },
  ],
});

Update Event

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' },
});

Delete Event

await calendar.deleteEvent('event-id');

Find Free Time

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', 'work@company.com'],
});

for (const slot of freeSlots) {
  console.log(`Free: ${slot.start} - ${slot.end}`);
}

Get Upcoming Events

const events = await calendar.getUpcomingEvents({
  maxResults: 10,
  days: 7,
});

for (const event of events) {
  console.log(`${event.summary}: ${event.start.dateTime}`);
}

Get Today's Events

const events = await calendar.getTodayEvents();

console.log(`You have ${events.length} events today`);

Health Check

const health = await calendar.healthCheck();

if (health.status === 'healthy') {
  console.log('Calendar API is accessible');
} else {
  console.error('Issue:', health.message);
}

Event Types

All-Day Events

// 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 Events

// 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 Patterns

Daily

recurrence: [{
  frequency: 'DAILY',
  interval: 1,  // Every day
  count: 10,    // For 10 occurrences
}]

Weekly

recurrence: [{
  frequency: 'WEEKLY',
  byDay: ['MO', 'WE', 'FR'],  // Monday, Wednesday, Friday
  until: '2024-12-31',        // Until end of year
}]

Monthly

recurrence: [{
  frequency: 'MONTHLY',
  byMonthDay: [15],  // 15th of each month
}]

Storage

Cached event metadata is stored in:

~/.openclaw/skills/calendar/cache.db

Tables:

  • events - Cached event data
  • calendars - Calendar list
  • sync_tokens - Sync state for incremental updates

Multi-Profile Support

Manage 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

Error Handling

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);
  }
}

Testing

# 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

Troubleshooting

"Not connected" error

Authenticate with google-oauth first:

node ../google-oauth/dist/cli.js connect default calendar

"Calendar scope not authorized"

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

API errors

Check health status:

node dist/cli.js health

Dependencies

  • @openclaw/google-oauth: For Calendar authentication
  • @openclaw/auth-provider: Base authentication (via google-oauth)
  • sqlite3: Local caching

Security Notes

  • OAuth tokens stored encrypted by auth-provider
  • Cache database has 0600 permissions (user read/write only)
  • Uses Calendar API with least-privilege scope
  • Free/busy queries don't expose event details
Weekly Installs
3
Repository
ticruz38/skills
First Seen
Feb 12, 2026
Installed on
openclaw2
codex2
mcpjam1
claude-code1
windsurf1
zencoder1