icloud-toolkit
// Unified iCloud integration for Calendar, Email, and Contacts. Use when: (1) creating, listing, searching, or deleting calendar events, (2) reading, sending, or searching email, (3) looking up, creating, editing, or deleting contacts, (4) any task involving iCloud calendar, email, or contacts. Handle
iCloud Toolkit
Calendar + Email + Contacts CLI. One script, all iCloud operations.
Script: scripts/icloud.py | Config: config/config.json
Rules
- Always use the CLI commands below instead of reading source files, parsing .ics/.vcf files directly, or grepping code. The CLI handles CalDAV discovery, IMAP operations, feed fetching, sync, timezone conversion, and formatting internally.
- One query first, fan out only if needed. Start with a single
calendar list --days Norcalendar search "<keyword>"— scan the output yourself before running more commands. Do not run multiple searches with different keywords in parallel. Each command triggers a sync cycle, so fewer calls = faster answers. - For multi-calendar queries (e.g., "when are we both free?"), run
calendar listfor each calendar separately, then reason over the combined output. - Subscribed calendars are read-only. You can list and search them, but
calendar createandcalendar deleteonly work on owned calendars. - Run
calendar refreshif the user mentions subscribing to a new calendar. - If a calendar returns no events, report that to the user — don't debug it. Empty results are valid. The user may not have events, or the calendar may need a
calendar refresh. Never write scripts to parse data or call APIs as a workaround. - Always ask permission before sending or deleting emails on behalf of the user.
- Don't re-read this file every turn. Once you've read SKILL.md in a session, you have the commands — just use them.
Quick reference
ICLOUD=~/.openclaw/workspace/skills/icloud-toolkit/scripts/icloud.py
# Calendar
python3 $ICLOUD calendar list # Today
python3 $ICLOUD calendar list --days 7 # Next 7 days
python3 $ICLOUD calendar list --days 7 --calendar Appointments # Specific calendar
python3 $ICLOUD calendar search "meeting" # Search events
python3 $ICLOUD calendar create <cal> <date> <start> <end> <title> [--location X] [--description X]
python3 $ICLOUD calendar create-allday <cal> <date> <title> [--description X]
python3 $ICLOUD calendar delete <uid>
python3 $ICLOUD calendar sync # Manual sync
python3 $ICLOUD calendar disable <name> # Hide from listings
python3 $ICLOUD calendar enable <name> # Show in listings
python3 $ICLOUD calendar refresh # Re-discover calendars
# Email
python3 $ICLOUD email list # Latest 10
python3 $ICLOUD email list --count 20 --folder "Sent Messages" # Sent folder
python3 $ICLOUD email list --unread # Unread only
python3 $ICLOUD email read <id> [--folder X]
python3 $ICLOUD email send <to> <subject> <body>
python3 $ICLOUD email reply <id> <body> [--all] [--folder X]
python3 $ICLOUD email search "from Apple" [--folder X]
python3 $ICLOUD email move <folder> <id> [--source X] # Move (folder first, then id)
python3 $ICLOUD email delete <id> [--folder X]
# Folder
python3 $ICLOUD folder purge <folder> # Purge all emails from folder
# Contacts
python3 $ICLOUD contact list [--addressbook X] [--count N]
python3 $ICLOUD contact show <uid>
python3 $ICLOUD contact search "John"
python3 $ICLOUD contact create John Doe --email j@x.com --phone +15551234567
python3 $ICLOUD contact create --fn "Acme Corp" --org "Acme Corp"
python3 $ICLOUD contact edit <uid> --add-email new@x.com
python3 $ICLOUD contact edit <uid> --first Jane --last Smith
python3 $ICLOUD contact delete <uid>
python3 $ICLOUD contact sync
Calendar
Times are local — the CLI converts to UTC automatically using the configured timezone.
python3 $ICLOUD calendar create Appointments 2026-03-15 14:00 15:30 "Team Meeting" \
--location "Board Room" --description "Weekly sync"
python3 $ICLOUD calendar create-allday General 2026-03-15 "Company Holiday"
Calendar names are configured during setup. To see available calendars, run calendar list.
Multi-calendar queries
For questions spanning multiple calendars (e.g., "when are we both off?", "do I have conflicts?"):
- Run
calendar list --days N --calendar <name>for each relevant calendar - Parse the text output to identify busy/free days
- Answer from the combined results
Disable/enable
Any calendar (owned or subscribed) can be excluded from listings:
python3 $ICLOUD calendar disable "Garbage Collection" # Exclude from list/search
python3 $ICLOUD calendar enable "Garbage Collection" # Include again
Refresh
Run calendar refresh to pick up newly subscribed calendars after initial setup.
iCloud folders: INBOX, Sent Messages, Drafts, Deleted Messages, Junk, Archive
Search uses himalaya query syntax (positional):
python3 $ICLOUD email search "from Apple"
python3 $ICLOUD email search "subject invoice"
python3 $ICLOUD email search "after 2026-02-01"
python3 $ICLOUD email search "from Apple and after 2026-01-01"
Emails are sent from the account_email in config — this may differ from the Apple ID used for authentication (e.g., custom domain users).
Reply auto-matches the From address to whichever address received the original email.
Contacts
# Full contact creation
python3 $ICLOUD contact create John Doe \
--email john@work.com --email john@home.com \
--phone +15551234567 --phone +15559876543 \
--org "Acme Corp" --title "Engineer" \
--nickname "Johnny" --birthday 1990-01-15 \
--note "Met at conference" --category friend \
--addressbook Work
# Editing preserves all existing data — only specified fields change
python3 $ICLOUD contact edit <uid> --add-email new@x.com # Add (keeps existing)
python3 $ICLOUD contact edit <uid> --remove-email old@x.com # Remove specific
python3 $ICLOUD contact edit <uid> --email only@x.com # Replace all
python3 $ICLOUD contact edit <uid> --add-phone +15559999999
python3 $ICLOUD contact edit <uid> --remove-phone +15551111111
python3 $ICLOUD contact edit <uid> --org "New Corp" --title "Manager"
Email notifications (heartbeat)
scripts/heartbeat-cron.py checks for new unread iCloud emails every 5 minutes and sends triage prompts to the agent.
python3 scripts/heartbeat-cron.py # Normal run
python3 scripts/heartbeat-cron.py --dry-run # Preview without agent call
State is stored in state/heartbeat-state.json (skill-local). Logs go to ~/.openclaw/logs/heartbeat-icloud.log.
Notes
- Sync is automatic before reads, before+after writes. Manual:
calendar sync/contact sync. - Self-test:
python3 $ICLOUD --test
First-time setup
When config/config.json is missing, the skill outputs SETUP_REQUIRED. Follow this two-step flow.
Step 1: Configure
Ask the user for their Apple ID, sending address (same as Apple ID unless custom domain), display name, and timezone (IANA format, auto-detected if omitted).
The password is managed via $ICLOUD_APP_PASSWORD. If not set:
Go to https://appleid.apple.com → Sign-In and Security → App-Specific Passwords → Generate one for "iCloud Toolkit". Then set it with:
/secret set ICLOUD_APP_PASSWORD
# Most users (sending address = Apple ID):
python3 $ICLOUD setup configure --email APPLE_ID --name "USER_NAME" --timezone TIMEZONE
# Custom domain users:
python3 $ICLOUD setup configure --email SENDING_ADDRESS --apple-id APPLE_ID --name "USER_NAME" --timezone TIMEZONE
This writes auth, generates vdirsyncer config, runs discovery+sync, and outputs discovered calendars/address books as JSON.
Step 2: Finalize
Calendars are auto-discovered — the user just picks a default.
python3 $ICLOUD setup finalize --email SENDING_ADDRESS --apple-id APPLE_ID --name "USER_NAME" --timezone TIMEZONE \
--default Personal \
--addressbooks '{"Contacts":"uuid-from-discovery"}' --default-addressbook Contacts
When --calendars is omitted, the skill auto-discovers all calendars from iCloud, splitting them into owned and subscribed. --addressbooks and --default-addressbook are optional.
python3 $ICLOUD setup verify # Run smoke tests