Files
youlu-openclaw-workspace/skills/calendar/SKILL.md
Yanxin Lu 35f048dd83 calendar: add recurring events, event management, and safety rules
Add --rrule flag to send with DTSTART/BYDAY validation (RFC 5545).
Add event list (via khal) and event delete (safe single-event removal).
Document safety rules from 2026-03-25 incident: always dry-run recurring
events, never rm .ics files, space out SMTP sends.
2026-03-25 09:32:22 -07:00

14 KiB

name, description, metadata
name description metadata
calendar Calendar invites and VTODO task management via CalDAV. Send/reply to invites, create/list/complete/delete todos. Syncs to Migadu CalDAV via vdirsyncer.
clawdbot
emoji requires
📅
bins skills
himalaya
vdirsyncer
todo
himalaya

Calendar

Send, accept, and decline calendar invitations via email. Create and manage VTODO tasks with CalDAV sync. Events and tasks sync to Migadu CalDAV via vdirsyncer.

Testing

See TESTING.md for dry-run and live test steps, verification checklists, and troubleshooting.

Prerequisites

  • himalaya configured and working (see the himalaya skill)
  • vdirsyncer configured and syncing to ~/.openclaw/workspace/calendars/
  • todoman (todo) for VTODO management (list, complete, delete)
  • khal for reading calendar (optional but recommended)
  • Runs via uv run (dependencies managed in pyproject.toml)

Important: Email Sending Rules

Calendar invites are outbound emails. Follow the workspace email rules:

Usage

All commands go through the wrapper script:

SKILL_DIR=~/.openclaw/workspace/skills/calendar

# Send an invite (supports recurring events via --rrule)
$SKILL_DIR/scripts/calendar.sh send [options]

# Reply to an invite
$SKILL_DIR/scripts/calendar.sh reply [options]

# Manage events
$SKILL_DIR/scripts/calendar.sh event list [options]
$SKILL_DIR/scripts/calendar.sh event delete [options]

# Manage todos
$SKILL_DIR/scripts/calendar.sh todo add [options]
$SKILL_DIR/scripts/calendar.sh todo list [options]
$SKILL_DIR/scripts/calendar.sh todo edit [options]
$SKILL_DIR/scripts/calendar.sh todo complete [options]
$SKILL_DIR/scripts/calendar.sh todo delete [options]
$SKILL_DIR/scripts/calendar.sh todo check

Sending Invites

$SKILL_DIR/scripts/calendar.sh send \
  --to "friend@example.com" \
  --subject "Lunch on Friday" \
  --summary "Lunch at Tartine" \
  --start "2026-03-20T12:00:00" \
  --end "2026-03-20T13:00:00" \
  --location "Tartine Bakery, SF"

Send Options

Flag Required Description
--to Yes Recipient(s), comma-separated
--subject Yes Email subject line
--summary Yes Event title (shown on calendar)
--start Yes Start time, ISO 8601 (2026-03-20T14:00:00)
--end Yes End time, ISO 8601 (2026-03-20T15:00:00)
--from No Sender email (default: youlu@luyanxin.com)
--timezone No IANA timezone (default: America/Los_Angeles)
--location No Event location
--description No Event description / notes
--organizer No Organizer display name (defaults to --from)
--rrule No Recurrence rule (e.g. FREQ=WEEKLY;COUNT=13;BYDAY=TU)
--uid No Custom event UID (auto-generated if omitted)
--account No Himalaya account name (if not default)
--dry-run No Print ICS + MIME without sending

Send Examples

# Simple invite (--from and --timezone default to youlu@luyanxin.com / LA)
$SKILL_DIR/scripts/calendar.sh send \
  --to "alice@example.com" \
  --subject "Coffee Chat" \
  --summary "Coffee Chat" \
  --start "2026-03-25T10:00:00" \
  --end "2026-03-25T10:30:00"

# Multiple attendees with details
$SKILL_DIR/scripts/calendar.sh send \
  --to "alice@example.com, bob@example.com" \
  --subject "Team Sync" \
  --summary "Weekly Team Sync" \
  --start "2026-03-23T09:00:00" \
  --end "2026-03-23T09:30:00" \
  --location "Zoom - https://zoom.us/j/123456" \
  --description "Weekly check-in. Agenda: updates, blockers, action items."

# Recurring: every Tuesday for 13 weeks (--start MUST fall on a Tuesday)
$SKILL_DIR/scripts/calendar.sh send \
  --to "alice@example.com" \
  --subject "Allergy Shot (Tue)" \
  --summary "Allergy Shot (Tue)" \
  --start "2026-03-31T14:30:00" \
  --end "2026-03-31T15:00:00" \
  --location "11965 Venice Blvd. #300, LA" \
  --rrule "FREQ=WEEKLY;COUNT=13;BYDAY=TU"

# Dry run (always use for recurring events to verify)
$SKILL_DIR/scripts/calendar.sh send \
  --to "test@example.com" \
  --subject "Test" \
  --summary "Test Event" \
  --start "2026-04-01T15:00:00" \
  --end "2026-04-01T16:00:00" \
  --dry-run

Recurring Events (--rrule)

The --rrule flag accepts an RFC 5545 RRULE string. Common patterns:

Pattern RRULE
Weekly on Tue, 13 weeks FREQ=WEEKLY;COUNT=13;BYDAY=TU
Weekly on Mon/Wed/Fri, until date FREQ=WEEKLY;UNTIL=20260630T000000Z;BYDAY=MO,WE,FR
Every 2 weeks on Thu FREQ=WEEKLY;INTERVAL=2;BYDAY=TH
Monthly on the 15th, 6 times FREQ=MONTHLY;COUNT=6;BYMONTHDAY=15
Daily for 5 days FREQ=DAILY;COUNT=5

Critical rule: For FREQ=WEEKLY with a single BYDAY, the --start date must fall on that day of the week. The tool validates this and will reject mismatches. RFC 5545 says mismatched DTSTART/BYDAY produces undefined behavior.

Best practice: Always --dry-run first for recurring events to verify the generated ICS.


Managing Events

# List upcoming events (next 90 days)
$SKILL_DIR/scripts/calendar.sh event list

# Search events by text
$SKILL_DIR/scripts/calendar.sh event list --search "Allergy"

# List with UIDs (for deletion)
$SKILL_DIR/scripts/calendar.sh event list --format "{uid} {title}"

# Custom date range
$SKILL_DIR/scripts/calendar.sh event list --range-start "2026-04-01" --range-end "2026-04-30"

# Delete by UID
$SKILL_DIR/scripts/calendar.sh event delete --uid "abc123@openclaw"

# Delete by summary match
$SKILL_DIR/scripts/calendar.sh event delete --match "Allergy Shot (Tue)"

Event Delete Safety

  • Deletes only ONE event at a time. If multiple events match, it lists them and exits.
  • NEVER use rm on calendar .ics files directly. Always use event delete.
  • After deleting, verify with event list or khal list.

Replying to Invites

# Accept by himalaya envelope ID
$SKILL_DIR/scripts/calendar.sh reply \
  --envelope-id 42 \
  --action accept

# Decline with a comment
$SKILL_DIR/scripts/calendar.sh reply \
  --envelope-id 42 \
  --action decline \
  --comment "Sorry, I have a conflict."

# From an .ics file
$SKILL_DIR/scripts/calendar.sh reply \
  --ics-file ~/Downloads/meeting.ics \
  --action tentative

Reply Options

Flag Required Description
--action Yes accept, decline, or tentative
--envelope-id * Himalaya envelope ID containing the .ics attachment
--ics-file * Path to an .ics file (alternative to --envelope-id)
--from No Your email (default: youlu@luyanxin.com)
--account No Himalaya account name
--folder No Himalaya folder (default: INBOX)
--comment No Optional message to include in reply
--dry-run No Preview without sending

* One of --envelope-id or --ics-file is required.

Typical Workflow

  1. List emails: himalaya envelope list
  2. Read the invite: himalaya message read 57
  3. Reply: $SKILL_DIR/scripts/calendar.sh reply --envelope-id 57 --action accept

VTODO Tasks

Manage tasks as RFC 5545 VTODO components, stored in ~/.openclaw/workspace/calendars/tasks/ and synced to CalDAV.

Sync Model

The agent's local CalDAV is the source of truth. When a todo is created, it's saved locally and synced to Migadu CalDAV via vdirsyncer — all connected devices (DAVx5, etc.) pick it up automatically. When the user completes a task, they tell the agent, and the agent runs todo complete. The daily todo check cron reads from local files.

Priority Mapping (RFC 5545)

Label --priority RFC 5545 value
高 (high) high 1
中 (medium) medium 5 (default)
低 (low) low 9

todo add — Create a Todo

$SKILL_DIR/scripts/calendar.sh todo add \
  --summary "跟进iui保险报销" \
  --due "2026-03-25" \
  --priority high \
  --description "确认iui费用保险报销进度" \
  --alarm 1d
Flag Required Description
--summary Yes Todo title
--due No Due date, YYYY-MM-DD (default: tomorrow)
--priority No high, medium, or low (default: medium)
--description No Notes / description
--alarm No Reminder trigger: 1d, 2h, 30m (default: 1d)
--dry-run No Preview ICS without saving

todo list — List Todos

$SKILL_DIR/scripts/calendar.sh todo list           # pending only
$SKILL_DIR/scripts/calendar.sh todo list --all      # include completed

todo edit — Modify a Todo

$SKILL_DIR/scripts/calendar.sh todo edit --match "保险报销" --due "2026-03-26"
$SKILL_DIR/scripts/calendar.sh todo edit --uid "abc123@openclaw" --priority high
$SKILL_DIR/scripts/calendar.sh todo edit --match "census" --due "2026-03-28" --priority low
Flag Required Description
--uid * Todo UID
--match * Match on summary text
--due No New due date (YYYY-MM-DD)
--priority No New priority: high, medium, or low

* One of --uid or --match is required. At least one edit flag must be provided.

todo complete — Mark as Done

$SKILL_DIR/scripts/calendar.sh todo complete --uid "abc123@openclaw"
$SKILL_DIR/scripts/calendar.sh todo complete --match "保险报销"

todo delete — Remove a Todo

$SKILL_DIR/scripts/calendar.sh todo delete --uid "abc123@openclaw"
$SKILL_DIR/scripts/calendar.sh todo delete --match "保险报销"

todo check — Daily Digest (Cron)

$SKILL_DIR/scripts/calendar.sh todo check

Same as todo list but only pending items. Exits silently when nothing is pending.


How It Works

Sending invites:

  1. Generates an RFC 5545 ICS file with METHOD:REQUEST (via icalendar library)
  2. Builds a MIME email with a text/calendar attachment (via Python email.mime)
  3. Sends via himalaya message send (piped through stdin)
  4. Saves the event to ~/.openclaw/workspace/calendars/home/
  5. Runs vdirsyncer sync to push to Migadu CalDAV

Replying to invites:

  1. Extracts the .ics attachment from the email (via himalaya attachment download)
  2. Parses the original event with the icalendar library
  3. Generates a reply ICS with METHOD:REPLY and the correct PARTSTAT
  4. Sends the reply to the organizer via himalaya message send (stdin)
  5. On accept/tentative: saves event to local calendar. On decline: removes it
  6. Runs vdirsyncer sync to push changes to Migadu CalDAV

Managing todos:

  1. todo add: Creates a VTODO ICS file (via icalendar library), saves to calendars/tasks/, syncs to CalDAV
  2. todo list/complete/delete/check: Delegates to todoman CLI for robust RFC 5545 VTODO parsing
  3. Runs vdirsyncer sync after mutations to push changes to Migadu CalDAV

CalDAV sync:

  • Events and tasks sync to Migadu and appear on all connected devices (DAVx5, etc.)
  • Heartbeat runs vdirsyncer sync periodically as a fallback
  • If sync fails, it warns but doesn't block — next heartbeat catches up

Integration with Email Processor

The email processor (scripts/email_processor/) may classify incoming calendar invites as reminder or confirmation. When reviewing pending emails:

  1. Check if the email contains a calendar invite (look for .ics attachment or "calendar" in subject)
  2. If it does, use reply instead of the email processor's delete/archive/keep actions
  3. The email processor handles the email lifecycle; this skill handles the calendar response

Checking the Calendar

# List upcoming events (next 7 days)
khal list today 7d

# List events for a specific date
khal list 2026-03-25

# Check for conflicts before sending an invite
khal list 2026-03-25 2026-03-26

Timezone Reference

Common IANA timezones:

  • America/Los_Angeles — Pacific (default)
  • America/Denver — Mountain
  • America/Chicago — Central
  • America/New_York — Eastern
  • Asia/Shanghai — China
  • Asia/Tokyo — Japan
  • Europe/London — UK
  • UTC — Coordinated Universal Time

Troubleshooting

Invite shows as attachment instead of calendar event?

  • Ensure the MIME part has Content-Type: text/calendar; method=REQUEST
  • Some clients require the METHOD:REQUEST line in the ICS body

Times are wrong?

  • Double-check --timezone matches the intended timezone
  • Use ISO 8601 format: YYYY-MM-DDTHH:MM:SS (no timezone offset in the value)

Event not showing on phone/other devices?

  • Run vdirsyncer sync manually to force sync
  • Check ~/.openclaw/workspace/logs/vdirsyncer.log for errors
  • Verify the .ics file exists in ~/.openclaw/workspace/calendars/home/

Todos not syncing?

  • Check that ~/.openclaw/workspace/calendars/tasks/ exists
  • Verify vdirsyncer has a cal/tasks pair configured
  • Run vdirsyncer sync manually

Recipient doesn't see Accept/Decline?

  • Gmail, Outlook, Apple Mail all support text/calendar method=REQUEST
  • Some webmail clients may vary