Apple Calendar + Reminders (macOS) Control
One skill to drive both Calendar.app and Reminders.app from the terminal.
Works on every calendar/list synced into macOS (iCloud, Google, Exchange,
CalDAV) — no API keys, no OAuth.
Verified pattern: icalBuddy for fast calendar reads (when installed) +
osascript (AppleScript) for all writes. Everything falls back to built-in
osascript, so the skill works with zero installs. Reminders are osascript-only.
When to Use
- Calendar: user mentions "calendar"/"캘린더", an event/meeting/appointment.
- Reminders: user mentions a "reminder"/"미리알림", a personal to-do, or a task
with a due date synced to iPhone/iCloud.
When to Use a Heavier Tool Instead
These scripts cover the common 90%. For the following, a native EventKit tool
such as che-ical-mcp (Swift MCP,
29 tools) is a better fit — note it requires a signed binary install:
- Writing attendees/organizer (AppleScript/EventKit exposes these read-only).
- Undo/redo of calendar operations.
- Geofence (location-triggered) reminders, structured lat/long locations.
- Per-event IANA timezone, conflict detection, duplicate detection, large batch
create/move with idempotency.
Recurring-occurrence-level edits (change one instance of a series) are also
limited here — treat the series as a whole or use the heavier tool.
Operating Rules (follow for every write)
- Read → write → verify. Before create/update/delete, do a bounded read of
the target window/list. After the write, read back and report the final state
(title, time, calendar/list).
- Confirm destructive or broad changes. Deletes, cross-calendar moves, and
multi-item edits need explicit user confirmation. Title/name-based bulk
deletes require
--force.
- Disambiguate duplicates. If multiple items share a title, do not act on
"the one" — list candidates (use UID for events) and ask which.
- Make recurrence & timezone explicit. Confirm RRULE and all-day/timezone
intent before writing recurring events; avoid silent defaults that drift
after DST.
- Minimal exposure. Query only the window/fields needed; never dump entire
calendars for a narrow lookup. Data stays local — never send it to third
parties.
Setup & Permissions
- macOS only.
osascript is built in.
- First run triggers a TCC permission prompt for Calendar and/or Reminders.
Grant access under System Settings → Privacy & Security → Calendars /
Reminders for the controlling terminal/app.
- Optional calendar read speed-up:
brew install ical-buddy (binary icalBuddy).
- Calendar names are matched case-insensitively; an unknown name errors with
the list of available calendars.
Calendar Scripts
| Script |
Purpose |
cal-list.sh |
List all calendar names |
cal-events.sh |
Read events (quick ranges, date range, --json) |
cal-add.sh |
Create an event (alarm, recurrence) |
cal-update.sh |
Edit an event by UID (selective fields) |
cal-search.sh |
Find events by summary substring (--json) |
cal-delete.sh |
Delete event(s) by UID or exact title (destructive) |
scripts/cal-list.sh
# Quick ranges + JSON
scripts/cal-events.sh today
scripts/cal-events.sh week --json
scripts/cal-events.sh --from 2026-06-29 --to 2026-07-06 --calendar "Home"
# Create with alarm (15 min before) and weekly recurrence
scripts/cal-add.sh --calendar "Home" --title "Standup" \
--start "2026-06-29 09:00" --end "2026-06-29 09:15" \
--alarm 15 --repeat "FREQ=WEEKLY;BYDAY=MO,WE,FR"
scripts/cal-add.sh --calendar "Home" --title "Holiday" --start "2026-07-04" --allday
# Edit by UID (only passed fields change)
scripts/cal-update.sh --calendar "Home" --uid "ABCD-1234" \
--start "2026-06-29 14:00" --end "2026-06-29 15:00" --location "Room 2"
scripts/cal-search.sh "standup" --calendar "Work" --json
scripts/cal-delete.sh --calendar "Home" --uid "ABCD-1234"
scripts/cal-delete.sh --calendar "Home" --title "Lunch" --force
Event dates accept YYYY-MM-DD or YYYY-MM-DD HH:MM. Omitting --end defaults
to start + 1 hour. cal-add.sh prints the new event UID; use it with
cal-update.sh/cal-delete.sh. JSON/osascript output uses sortable ISO dates.
Reminder Scripts
| Script |
Purpose |
rem-lists.sh |
List all reminder lists |
rem-list.sh |
List reminders (filters, sort, --json) |
rem-add.sh |
Create a reminder (list, due, notes, priority) |
rem-complete.sh |
Mark reminder(s) complete by exact name |
rem-delete.sh |
Delete reminder(s) by exact name (destructive) |
rem-cleanup.sh |
Delete completed reminders (dry-run by default) |
scripts/rem-lists.sh
scripts/rem-list.sh --list "Work"
scripts/rem-list.sh --overdue
scripts/rem-list.sh --completed
scripts/rem-list.sh --sort due --json
scripts/rem-add.sh --title "Buy milk"
scripts/rem-add.sh --title "Call mom" --list "Personal" --due "2026-06-29 18:00"
scripts/rem-add.sh --title "Submit report" --due "2026-07-02 17:00" --priority high
scripts/rem-complete.sh --title "Buy milk" --list "Personal"
scripts/rem-delete.sh --title "Buy milk" --force
scripts/rem-cleanup.sh # preview completed
scripts/rem-cleanup.sh --force # actually delete completed
Reminder --due accepts YYYY-MM-DD (defaults to 09:00) or YYYY-MM-DD HH:MM.
--priority: none|high|medium|low (or raw 0|1|5|9). rem-list.sh filters:
default incomplete, --all, --completed, --overdue; sort with
--sort due|title. rem-add.sh prints the new reminder id.
Destructive Operations
cal-delete.sh --uid removes one event; --title removes all matching and
requires --force.
rem-delete.sh matches by exact name, can remove multiple, requires --force.
rem-cleanup.sh is dry-run by default; --force deletes all completed.
- Deletion is permanent — confirm intent with the user before running.
Date Handling
Dates are built component-by-component in AppleScript (year/month/day/hour/
minute), avoiding locale-dependent string parsing; day is set to 1 first to
prevent month overflow. Read output (osascript/JSON path) is normalized to
sortable YYYY-MM-DD HH:MM. See references/applescript-patterns.md for the
raw AppleScript and extension points.
Output Formats
- Default: tab-separated (TSV) rows for easy
cut/awk piping.
--json (on cal-events.sh, cal-search.sh, rem-list.sh): a JSON array of
objects with escaped values, for scripting/agent consumption.
Limitations
- The osascript calendar-read fallback iterates calendars and can be slow over
wide ranges — install
icalBuddy for fast reads (note: --json always uses
the osascript path for stable columns).
- Attendee/organizer writes, undo/redo, geofence reminders, per-event timezone,
and occurrence-level recurring edits are out of scope (see "When to Use a
Heavier Tool").
- Reminder match (complete/delete) is by exact name; read first, then act.
1---2name: jaw-calendar-reminders3description: Control macOS Calendar.app and Reminders.app from the CLI. Calendars/events (list, read, create, update, search, delete) and reminder lists/reminders (list, add, complete, delete, cleanup) with due dates, alarms, recurrence, priority, and JSON output. Zero install — built-in osascript; uses icalBuddy for fast calendar reads when present.4---56# Apple Calendar + Reminders (macOS) Control78One skill to drive both **Calendar.app** and **Reminders.app** from the terminal.9Works on every calendar/list synced into macOS (iCloud, Google, Exchange,10CalDAV) — no API keys, no OAuth.1112Verified pattern: **icalBuddy for fast calendar reads (when installed) +13osascript (AppleScript) for all writes.** Everything falls back to built-in14`osascript`, so the skill works with zero installs. Reminders are osascript-only.1516## When to Use1718- Calendar: user mentions "calendar"/"캘린더", an event/meeting/appointment.19- Reminders: user mentions a "reminder"/"미리알림", a personal to-do, or a task20 with a due date synced to iPhone/iCloud.2122## When to Use a Heavier Tool Instead2324These scripts cover the common 90%. For the following, a native EventKit tool25such as [che-ical-mcp](https://github.com/PsychQuant/che-ical-mcp) (Swift MCP,2629 tools) is a better fit — note it requires a signed binary install:2728- Writing attendees/organizer (AppleScript/EventKit exposes these read-only).29- Undo/redo of calendar operations.30- Geofence (location-triggered) reminders, structured lat/long locations.31- Per-event IANA timezone, conflict detection, duplicate detection, large batch32 create/move with idempotency.3334Recurring-occurrence-level edits (change one instance of a series) are also35limited here — treat the series as a whole or use the heavier tool.3637## Operating Rules (follow for every write)38391. **Read → write → verify.** Before create/update/delete, do a bounded read of40 the target window/list. After the write, read back and report the final state41 (title, time, calendar/list).422. **Confirm destructive or broad changes.** Deletes, cross-calendar moves, and43 multi-item edits need explicit user confirmation. Title/name-based bulk44 deletes require `--force`.453. **Disambiguate duplicates.** If multiple items share a title, do not act on46 "the one" — list candidates (use UID for events) and ask which.474. **Make recurrence & timezone explicit.** Confirm RRULE and all-day/timezone48 intent before writing recurring events; avoid silent defaults that drift49 after DST.505. **Minimal exposure.** Query only the window/fields needed; never dump entire51 calendars for a narrow lookup. Data stays local — never send it to third52 parties.5354## Setup & Permissions5556- macOS only. `osascript` is built in.57- **First run triggers a TCC permission prompt** for Calendar and/or Reminders.58 Grant access under System Settings → Privacy & Security → Calendars /59 Reminders for the controlling terminal/app.60- Optional calendar read speed-up: `brew install ical-buddy` (binary `icalBuddy`).61- Calendar names are matched **case-insensitively**; an unknown name errors with62 the list of available calendars.6364## Calendar Scripts6566| Script | Purpose |67|--------|---------|68| `cal-list.sh` | List all calendar names |69| `cal-events.sh` | Read events (quick ranges, date range, `--json`) |70| `cal-add.sh` | Create an event (alarm, recurrence) |71| `cal-update.sh` | Edit an event by UID (selective fields) |72| `cal-search.sh` | Find events by summary substring (`--json`) |73| `cal-delete.sh` | Delete event(s) by UID or exact title (destructive) |7475```bash76scripts/cal-list.sh7778# Quick ranges + JSON79scripts/cal-events.sh today80scripts/cal-events.sh week --json81scripts/cal-events.sh --from 2026-06-29 --to 2026-07-06 --calendar "Home"8283# Create with alarm (15 min before) and weekly recurrence84scripts/cal-add.sh --calendar "Home" --title "Standup" \85 --start "2026-06-29 09:00" --end "2026-06-29 09:15" \86 --alarm 15 --repeat "FREQ=WEEKLY;BYDAY=MO,WE,FR"87scripts/cal-add.sh --calendar "Home" --title "Holiday" --start "2026-07-04" --allday8889# Edit by UID (only passed fields change)90scripts/cal-update.sh --calendar "Home" --uid "ABCD-1234" \91 --start "2026-06-29 14:00" --end "2026-06-29 15:00" --location "Room 2"9293scripts/cal-search.sh "standup" --calendar "Work" --json94scripts/cal-delete.sh --calendar "Home" --uid "ABCD-1234"95scripts/cal-delete.sh --calendar "Home" --title "Lunch" --force96```9798Event dates accept `YYYY-MM-DD` or `YYYY-MM-DD HH:MM`. Omitting `--end` defaults99to start + 1 hour. `cal-add.sh` prints the new event UID; use it with100`cal-update.sh`/`cal-delete.sh`. JSON/osascript output uses sortable ISO dates.101102## Reminder Scripts103104| Script | Purpose |105|--------|---------|106| `rem-lists.sh` | List all reminder lists |107| `rem-list.sh` | List reminders (filters, sort, `--json`) |108| `rem-add.sh` | Create a reminder (list, due, notes, priority) |109| `rem-complete.sh` | Mark reminder(s) complete by exact name |110| `rem-delete.sh` | Delete reminder(s) by exact name (destructive) |111| `rem-cleanup.sh` | Delete completed reminders (dry-run by default) |112113```bash114scripts/rem-lists.sh115scripts/rem-list.sh --list "Work"116scripts/rem-list.sh --overdue117scripts/rem-list.sh --completed118scripts/rem-list.sh --sort due --json119scripts/rem-add.sh --title "Buy milk"120scripts/rem-add.sh --title "Call mom" --list "Personal" --due "2026-06-29 18:00"121scripts/rem-add.sh --title "Submit report" --due "2026-07-02 17:00" --priority high122scripts/rem-complete.sh --title "Buy milk" --list "Personal"123scripts/rem-delete.sh --title "Buy milk" --force124scripts/rem-cleanup.sh # preview completed125scripts/rem-cleanup.sh --force # actually delete completed126```127128Reminder `--due` accepts `YYYY-MM-DD` (defaults to 09:00) or `YYYY-MM-DD HH:MM`.129`--priority`: `none|high|medium|low` (or raw `0|1|5|9`). `rem-list.sh` filters:130default incomplete, `--all`, `--completed`, `--overdue`; sort with131`--sort due|title`. `rem-add.sh` prints the new reminder id.132133## Destructive Operations134135- `cal-delete.sh --uid` removes one event; `--title` removes **all** matching and136 requires `--force`.137- `rem-delete.sh` matches by exact name, can remove multiple, requires `--force`.138- `rem-cleanup.sh` is **dry-run by default**; `--force` deletes all completed.139- Deletion is permanent — confirm intent with the user before running.140141## Date Handling142143Dates are built component-by-component in AppleScript (year/month/day/hour/144minute), avoiding locale-dependent string parsing; `day` is set to 1 first to145prevent month overflow. Read output (osascript/JSON path) is normalized to146sortable `YYYY-MM-DD HH:MM`. See `references/applescript-patterns.md` for the147raw AppleScript and extension points.148149## Output Formats150151- Default: tab-separated (TSV) rows for easy `cut`/`awk` piping.152- `--json` (on `cal-events.sh`, `cal-search.sh`, `rem-list.sh`): a JSON array of153 objects with escaped values, for scripting/agent consumption.154155## Limitations156157- The osascript calendar-read fallback iterates calendars and can be slow over158 wide ranges — install `icalBuddy` for fast reads (note: `--json` always uses159 the osascript path for stable columns).160- Attendee/organizer writes, undo/redo, geofence reminders, per-event timezone,161 and occurrence-level recurring edits are out of scope (see "When to Use a162 Heavier Tool").163- Reminder match (complete/delete) is by **exact** name; read first, then act.