Replaces custom .ics parsing with todoman CLI (--porcelain for JSON). todo add still uses icalendar directly (needs ICS creation + email). Updates MIGRATION.md with todoman install/config instructions.
12 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
Owner Auto-Attendee
When sending invites, mail@luyx.org (owner's SimpleLogin alias) is always added as an attendee automatically. All invites include a 1-day reminder (VALARM) by default. This ensures the owner receives every invite and can Accept/Decline from their own email client. No need to include it in --to — it's added by the script.
When accepting or tentatively accepting a received invite, the original invite is automatically forwarded to mail@luyx.org so the event lands on the owner's calendar too.
Usage
All commands go through the wrapper script:
SKILL_DIR=~/.openclaw/workspace/skills/calendar
# Send an invite
$SKILL_DIR/scripts/calendar.sh send [options]
# Reply to an invite
$SKILL_DIR/scripts/calendar.sh reply [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 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) |
--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."
# Dry run
$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
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 (no two-way sync). When a todo is created, it's saved locally and emailed to mail@luyx.org as a delivery copy. 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 + email 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 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 NEEDS-ACTION items. Exits silently when no pending items. Output is designed for piping to himalaya.
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/, emails to owner, syncstodo 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