Files
youlu-openclaw-workspace/skills/calendar/SKILL.md
Yanxin Lu f05a84d8ca contacts: extract into standalone skill, add himalaya wrapper for all email sends
Refactors the contacts system from being embedded in cal_tool.py into a
standalone contacts skill that serves as the single source of truth for
recipient validation across all outbound email paths.

- New skills/contacts/ skill: list, add, delete, resolve commands
- New skills/himalaya/scripts/himalaya.sh wrapper: validates To/Cc/Bcc
  recipients against contacts for message send, template send, and
  message write commands; passes everything else through unchanged
- cal_tool.py now delegates to contacts.py resolve instead of inline logic
- TOOLS.md updated: agent should use himalaya wrapper, not raw himalaya
2026-03-31 11:12:08 -07:00

16 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: Recipient Validation

The send command only accepts recipients that exist in the contacts list. This prevents hallucinated email addresses.

  • --to "小橘子:work" — resolves contact name + email type
  • --to "小橘子" — resolves by name (errors if contact has multiple emails)
  • --to "user@example.com" — accepted only if the email exists in contacts
  • Unknown addresses are rejected with the available contacts list shown

Adding contacts and sending invites are separate operations. Do not add a contact and send to it in the same request — contact additions should be a deliberate, user-initiated action.

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

# --to accepts contact names (resolved via contacts list)
$SKILL_DIR/scripts/calendar.sh send \
  --to "小橘子:work" \
  --subject "Lunch on Friday" \
  --summary "Lunch at Tartine" \
  --start "2026-03-20T12:00:00" \
  --end "2026-03-20T13:00:00" \
  --location "Tartine Bakery, SF" \
  --alarm 1h

Send Options

Flag Required Description
--to Yes Recipient(s) — contact name, name:type, or known email
--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)
--alarm No Reminder trigger: 1d, 2h, 30m (default: 1d)
--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 a single (non-recurring) event
$SKILL_DIR/scripts/calendar.sh event delete --match "Lunch at Tartine"

# Cancel ONE occurrence of a recurring event (adds EXDATE, keeps the series)
$SKILL_DIR/scripts/calendar.sh event delete --match "Allergy Shot" --date "2026-03-28"

# Delete an entire recurring series (requires --all safety flag)
$SKILL_DIR/scripts/calendar.sh event delete --match "Allergy Shot" --all

Event Delete Safety

  • Deletes only ONE event at a time. If multiple events match, it lists them and exits.
  • Recurring events require --date or --all. Without either flag, the tool refuses to act and shows usage.
    • --date YYYY-MM-DD: Adds an EXDATE to skip that one occurrence. The rest of the series continues.
    • --all: Deletes the entire .ics file (the whole series). Use only when the user explicitly wants to cancel all future occurrences.
  • NEVER use rm on calendar .ics files directly. Always use event delete.
  • After deleting/cancelling, verify with event list or khal list.

Recipient Resolution

The send --to flag delegates to the contacts skill (skills/contacts/) for address resolution. See the contacts skill SKILL.md for full documentation on adding/managing contacts.

# By name (works when contact has a single email)
$SKILL_DIR/scripts/calendar.sh send --to "小鹿" ...

# By name + type (required when contact has multiple emails)
$SKILL_DIR/scripts/calendar.sh send --to "小橘子:work" ...

# By raw email (must exist in contacts)
$SKILL_DIR/scripts/calendar.sh send --to "Erica.Jiang@anderson.ucla.edu" ...

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