Previously the send command hardcoded a 1-day VALARM. Now accepts --alarm with duration format (e.g. 1h, 30m, 2d), defaulting to 1d.
15 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. |
|
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
himalayaconfigured and working (see thehimalayaskill)vdirsyncerconfigured and syncing to~/.openclaw/workspace/calendars/todoman(todo) for VTODO management (list, complete, delete)khalfor reading calendar (optional but recommended)- Runs via
uv run(dependencies managed inpyproject.toml)
Important: Email Sending Rules
Calendar invites are outbound emails. Follow the workspace email rules:
- youlu@luyanxin.com -> mail@luyx.org: send directly, no confirmation needed
- All other recipients: confirm with user before sending
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" \
--alarm 1h
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) |
--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
--dateor--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
rmon calendar .ics files directly. Always useevent delete. - After deleting/cancelling, verify with
event listorkhal 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
- List emails:
himalaya envelope list - Read the invite:
himalaya message read 57 - 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:
- Generates an RFC 5545 ICS file with
METHOD:REQUEST(viaicalendarlibrary) - Builds a MIME email with a
text/calendarattachment (via Pythonemail.mime) - Sends via
himalaya message send(piped through stdin) - Saves the event to
~/.openclaw/workspace/calendars/home/ - Runs
vdirsyncer syncto push to Migadu CalDAV
Replying to invites:
- Extracts the
.icsattachment from the email (viahimalaya attachment download) - Parses the original event with the
icalendarlibrary - Generates a reply ICS with
METHOD:REPLYand the correctPARTSTAT - Sends the reply to the organizer via
himalaya message send(stdin) - On accept/tentative: saves event to local calendar. On decline: removes it
- Runs
vdirsyncer syncto push changes to Migadu CalDAV
Managing todos:
todo add: Creates a VTODO ICS file (viaicalendarlibrary), saves tocalendars/tasks/, syncs to CalDAVtodo list/complete/delete/check: Delegates totodomanCLI for robust RFC 5545 VTODO parsing- Runs
vdirsyncer syncafter 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 syncperiodically 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:
- Check if the email contains a calendar invite (look for
.icsattachment or "calendar" in subject) - If it does, use
replyinstead of the email processor's delete/archive/keep actions - 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— MountainAmerica/Chicago— CentralAmerica/New_York— EasternAsia/Shanghai— ChinaAsia/Tokyo— JapanEurope/London— UKUTC— 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:REQUESTline in the ICS body
Times are wrong?
- Double-check
--timezonematches 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 syncmanually to force sync - Check
~/.openclaw/workspace/logs/vdirsyncer.logfor 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/taskspair configured - Run
vdirsyncer syncmanually
Recipient doesn't see Accept/Decline?
- Gmail, Outlook, Apple Mail all support
text/calendarmethod=REQUEST - Some webmail clients may vary