calendar invite

This commit is contained in:
Yanxin Lu
2026-03-18 13:36:25 -07:00
parent d35e7b1475
commit de6528335c
14 changed files with 778 additions and 30 deletions

View File

@@ -0,0 +1,223 @@
---
name: calendar-invite
description: "Send, accept, and decline calendar invite emails (ICS/iCalendar) via himalaya. Syncs events to CalDAV (Migadu) via vdirsyncer."
metadata: {"clawdbot":{"emoji":"📅","requires":{"bins":["himalaya","vdirsyncer"],"skills":["himalaya"]}}}
---
# Calendar Invite
Send, accept, and decline calendar invitations via email using himalaya. Events are saved to local calendar and synced to CalDAV (Migadu) via vdirsyncer.
## Prerequisites
- `himalaya` configured and working (see the `himalaya` skill)
- `vdirsyncer` configured and syncing to `~/.openclaw/workspace/calendars/`
- `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:
- **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**. 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:
```bash
SKILL_DIR=~/.openclaw/workspace/skills/calendar-invite
# Send an invite
$SKILL_DIR/scripts/calendar-invite.sh send [options]
# Reply to an invite
$SKILL_DIR/scripts/calendar-invite.sh reply [options]
```
---
## Sending Invites
```bash
$SKILL_DIR/scripts/calendar-invite.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 + MML without sending |
### Send Examples
```bash
# Simple invite (--from and --timezone default to youlu@luyanxin.com / LA)
$SKILL_DIR/scripts/calendar-invite.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-invite.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-invite.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
```bash
# Accept by himalaya message ID
$SKILL_DIR/scripts/calendar-invite.sh reply \
--envelope-id 42 \
--action accept
# Decline with a comment
$SKILL_DIR/scripts/calendar-invite.sh reply \
--envelope-id 42 \
--action decline \
--comment "Sorry, I have a conflict."
# From an .ics file
$SKILL_DIR/scripts/calendar-invite.sh reply \
--ics-file ~/Downloads/meeting.ics \
--action tentative
```
### Reply Options
| Flag | Required | Description |
|-----------------|----------|-----------------------------------------------------|
| `--action` | Yes | `accept`, `decline`, or `tentative` |
| `--envelope-id` | * | Himalaya message 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-invite.sh reply --envelope-id 57 --action accept`
---
## How It Works
**Sending invites:**
1. Generates an RFC 5545 ICS file with `METHOD:REQUEST` (via `icalendar` library)
2. Builds an MML email with a `text/calendar` attachment
3. Sends via `himalaya template send`
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 template send`
5. On accept/tentative: saves event to local calendar. On decline: removes it
6. Runs `vdirsyncer sync` to push changes to Migadu CalDAV
**CalDAV sync:**
- Events 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
```bash
# 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 `type=text/calendar method=REQUEST` is set on the MML part
- 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/`
**Recipient doesn't see Accept/Decline?**
- Gmail, Outlook, Apple Mail all support `text/calendar` method=REQUEST
- Some webmail clients may vary