macOS Calendar & Reminders automation (agendactl)
Drive macOS Calendar and Reminders by calling the agendactl CLI. Only call commands and pass
arguments — authorization, time zones, stable ids, and validation (end > start, etc.) are all
handled inside the CLI. Whatever the CLI does not expose is simply not available; do not route
around it by writing scripts.
Running agendactl
The binary ships inside this skill at scripts/agendactl. Resolve it in this order:
- If
agendactl is on PATH, just call agendactl.
- Otherwise, run the bundled binary using this skill's own directory — the absolute path of the
folder this
SKILL.md was loaded from — e.g. ~/.claude/skills/agendactl/scripts/agendactl or
~/.pi/agent/skills/agendactl/scripts/agendactl. Resolve scripts/agendactl against the skill
directory, NOT against the current working directory (the cwd is usually the user's project, not
this skill). No install step beyond having the skill folder is required.
Subcommands and flags are identical either way. Do not find the whole disk for it, and do not
substitute another tool (Google Calendar, Outlook, Feishu/Lark, or a separate task app): this drives
the Apple Calendar.app / Reminders.app on this Mac (iCloud-synced), and only agendactl reads that
data.
Design notes (read first)
- Responses carry meta. Every
agendactl response has _at / _tz / _iso / _note at the top
level. _at is the moment the CLI ran (meta), NOT the item's start/end/due time. The
underscore prefix marks meta; bare names are data. _note spells this out.
- Get "today" from the response, not from context. When the user says "today / tonight /
tomorrow", read the date from any
agendactl response's ._iso field (e.g. 2026-06-29). Do not
call date for it and do not guess — the host's reported "today" can be stale or wrong, and
writing an event to yesterday drops it out of the calendar view.
- CLI-only. Only call
agendactl <app> … with arguments. Never write code; never use
osascript/AppleScript/JXA or touch EventKit directly — for macOS Calendar/Reminders, agendactl is
the tool, even for a one-liner. If something is not reachable, say "not supported yet" rather than
inventing it.
- List the containers before writing. Run
calendars / lists first to get the real
container names (the response also returns _iso, handy for building "today").
Calendar vs Reminders — route correctly
Concept. CALENDAR = a schedule entry (meeting/event, has a start/end interval, has
location/notes, has no "complete" concept — it exists or it is deleted). REMINDERS = a to-do
(has a due time, can be completed).
Go to calendar when the user says "add a meeting" / "what's on this week" / "3–4pm" (an
interval) / "cancel that event" / anything with a start–end span.
Go to reminders when the user says "remind me" / "to-do" / "mark complete" / "what do I have
to do today" / any to-do concept.
Easy to confuse:
- "What's on this week" / "what meetings this week" → calendar (a schedule).
- "What do I have to do today" / "today's to-dos" → reminders (things to do).
- "Meeting at 3 tomorrow" → calendar (a meeting, an interval).
- "Remind me to be at the 3pm meeting" → reminders (the user said "remind" = a to-do not to
forget; do not also create a calendar event).
Command map (same verbs, different prefix — don't cross them):
| action |
calendar |
reminders |
| list containers |
agendactl calendar calendars |
agendactl reminders lists |
| list items |
agendactl calendar list-events |
agendactl reminders list |
| create |
agendactl calendar create-event |
agendactl reminders create |
| update |
agendactl calendar update-event <id> |
agendactl reminders update <id> |
| complete |
(none — events have no "complete"; use delete-event) |
agendactl reminders complete <id> |
| delete |
agendactl calendar delete-event <id> |
agendactl reminders delete <id> |
Calendar commands
| command |
purpose |
agendactl calendar calendars |
list all calendars: name + id + writable. Run before any write. |
agendactl calendar list-events [--calendar <name>] [--from <iso>] [--to <iso>] [--limit <n>] |
list events (default next 30 days), returns {…meta, items:[...]} |
agendactl calendar create-event --calendar <name> --summary <title> --start <iso> --end <iso> [--location <text>] [--notes <text>] [--all-day] |
create an event, returns one with id |
agendactl calendar update-event <id> [--summary] [--start] [--end] [--location] [--notes] |
update (fields not given stay unchanged) |
agendactl calendar delete-event <id> |
delete an event |
<id> comes from list-events / create-event. --end must be later than --start (else
exit 1); when changing only one end, keep the order valid.
Reminders commands
| command |
purpose |
agendactl reminders lists |
list all lists: name + id + writable. Run before any write. |
agendactl reminders list [--list <name>] [--status incomplete|completed|all] [--due today|<iso>] [--limit <n>] |
list reminders (default incomplete), returns {…meta, items:[...]} |
agendactl reminders create --list <name> --name <text> [--notes <text>] [--due <iso>] [--priority 0-9] |
create a reminder, returns one with id |
agendactl reminders update <id> [--name] [--notes] [--due] [--priority] [--complete] |
update (fields not given stay unchanged) |
agendactl reminders complete <id> |
mark complete |
agendactl reminders delete <id> |
delete |
Priority: 0=none, 1–4=high, 5=medium, 6–9=low (Apple's convention). --status defaults to
incomplete.
Dates
- Input ISO 8601:
2026-06-29T09:00:00 (no tz suffix = local time); 2026-06-29 is also
accepted. Output is UTC ISO (with Z).
- For relative dates ("today / tomorrow / the day after"), first read
._iso from any response for
"today", then do calendar arithmetic with macOS BSD date — date -j -f "%Y-%m-%d" -v+1d "$ISO" +%Y-%m-%d for "tomorrow" (note: BSD -j -f -v, not GNU date -d). agendactl itself accepts only
full ISO 8601, never expressions like "tomorrow".
Example — "review tomorrow 3–4pm":
RESP=$(agendactl calendar calendars) # real calendar name (+ _iso)
ISO=$(echo "$RESP" | jq -r ._iso)
CAL=$(echo "$RESP" | jq -r '.items[] | select(.writable) | .name' | head -1)
TOMORROW=$(date -j -f "%Y-%m-%d" -v+1d "$ISO" +%Y-%m-%d)
agendactl calendar create-event --calendar "$CAL" --summary "Review" \
--start "${TOMORROW}T15:00:00" --end "${TOMORROW}T16:00:00"
Standard workflow (required for writes)
- List containers first (
calendars / lists) to get real names — don't guess ("Personal"
vs "个人", duplicate "Holidays" calendars write to the wrong place).
- Call
create / update / complete / delete.
- Compound filtering (e.g. "events over 1 hour this week", "today's high-priority to-dos"):
coarse-filter with
--from/--to/--list/--status/--due, then refine in context yourself —
the CLI does not do complex queries.
- After writing, re-read with
list / list-events to confirm.
- Confirm with the user before bulk deletes, then delete one by one.
Exit codes (judge by these, don't guess)
| code |
meaning |
what to do |
| 0 |
success |
read the JSON on stdout |
| 1 |
usage/arg error |
read stderr, fix the arguments |
| 2 |
name/id not found |
pick from stderr's available:; or run calendars/lists first |
| 3 |
not authorized |
have the user grant Full Access in System Settings → Privacy & Security → Calendars / Reminders |
| 4 |
runtime error |
read the reason on stderr |
On failure, stderr's first line is agendactl: <reason>.
Not covered (say "not supported yet", don't invent)
- Calendar: attendees/invites, room booking, shared-calendar permissions, recurrence-rule editing.
- Reminders: subtasks, attachments, structured URL/flag read-write.
1---2name: agendactl3description: Read & write the local macOS Calendar app and Reminders app on this Mac (Apple EventKit, iCloud-synced) via the bundled `agendactl` CLI. Use this skill for any request to add, list, view, change, reschedule, complete, or delete a calendar event or a reminder/to-do on this machine — e.g. "remind me to send the report", "what meetings do I have this week", "move the standup to Wednesday", "mark it done", "cancel that event", "what's on tomorrow" — even when the words "calendar" or "reminder" aren't used. This is macOS-native Apple Calendar/Reminders, not Google, Outlook, or Feishu/Lark, and not a generic task app. Always drive it through `agendactl` with arguments; never write osascript/AppleScript/JXA or shell out to these apps another way. Calendar = timed events with a start/end interval (no "complete"); reminders = to-dos with a due date that can be completed — route by intent.4---56# macOS Calendar & Reminders automation (agendactl)78Drive macOS Calendar and Reminders by calling the `agendactl` CLI. **Only call commands and pass9arguments** — authorization, time zones, stable ids, and validation (`end > start`, etc.) are all10handled inside the CLI. Whatever the CLI does not expose is simply not available; do not route11around it by writing scripts.1213## Running agendactl1415The binary ships **inside this skill** at `scripts/agendactl`. Resolve it in this order:16171. If `agendactl` is on `PATH`, just call `agendactl`.182. Otherwise, run the bundled binary using **this skill's own directory** — the absolute path of the19 folder this `SKILL.md` was loaded from — e.g. `~/.claude/skills/agendactl/scripts/agendactl` or20 `~/.pi/agent/skills/agendactl/scripts/agendactl`. **Resolve `scripts/agendactl` against the skill21 directory, NOT against the current working directory** (the cwd is usually the user's project, not22 this skill). No install step beyond having the skill folder is required.2324Subcommands and flags are identical either way. Do not `find` the whole disk for it, and do not25substitute another tool (Google Calendar, Outlook, Feishu/Lark, or a separate task app): this drives26the Apple Calendar.app / Reminders.app on this Mac (iCloud-synced), and only `agendactl` reads that27data.2829## Design notes (read first)3031- **Responses carry meta.** Every `agendactl` response has `_at` / `_tz` / `_iso` / `_note` at the top32 level. `_at` is the **moment the CLI ran** (meta), NOT the item's `start`/`end`/`due` time. The33 underscore prefix marks meta; bare names are data. `_note` spells this out.34- **Get "today" from the response, not from context.** When the user says "today / tonight /35 tomorrow", read the date from any `agendactl` response's `._iso` field (e.g. `2026-06-29`). Do not36 call `date` for it and do not guess — the host's reported "today" can be stale or wrong, and37 writing an event to yesterday drops it out of the calendar view.38- **CLI-only.** Only call `agendactl <app> …` with arguments. Never write code; never use39 osascript/AppleScript/JXA or touch EventKit directly — for macOS Calendar/Reminders, `agendactl` is40 the tool, even for a one-liner. If something is not reachable, say "not supported yet" rather than41 inventing it.42- **List the containers before writing.** Run `calendars` / `lists` first to get the real43 container names (the response also returns `_iso`, handy for building "today").4445## Calendar vs Reminders — route correctly4647- **Concept.** CALENDAR = a schedule entry (meeting/event, has a `start`/`end` interval, has48 location/notes, has **no** "complete" concept — it exists or it is deleted). REMINDERS = a to-do49 (has a `due` time, **can be completed**).50- **Go to calendar when** the user says "add a meeting" / "what's on this week" / "3–4pm" (an51 interval) / "cancel that event" / anything with a start–end span.52- **Go to reminders when** the user says "remind me" / "to-do" / "mark complete" / "what do I have53 to do today" / any to-do concept.54- **Easy to confuse:**55 - "What's on this week" / "what meetings this week" → **calendar** (a schedule).56 - "What do I have to do today" / "today's to-dos" → **reminders** (things to do).57 - "Meeting at 3 tomorrow" → **calendar** (a meeting, an interval).58 - "Remind me to be at the 3pm meeting" → **reminders** (the user said "remind" = a to-do not to59 forget; do **not** also create a calendar event).60- **Command map** (same verbs, different prefix — don't cross them):6162 | action | calendar | reminders |63 |---|---|---|64 | list containers | `agendactl calendar calendars` | `agendactl reminders lists` |65 | list items | `agendactl calendar list-events` | `agendactl reminders list` |66 | create | `agendactl calendar create-event` | `agendactl reminders create` |67 | update | `agendactl calendar update-event <id>` | `agendactl reminders update <id>` |68 | complete | (none — events have no "complete"; use `delete-event`) | `agendactl reminders complete <id>` |69 | delete | `agendactl calendar delete-event <id>` | `agendactl reminders delete <id>` |7071## Calendar commands7273| command | purpose |74|---|---|75| `agendactl calendar calendars` | list all calendars: name + id + writable. **Run before any write.** |76| `agendactl calendar list-events [--calendar <name>] [--from <iso>] [--to <iso>] [--limit <n>]` | list events (default next 30 days), returns `{…meta, items:[...]}` |77| `agendactl calendar create-event --calendar <name> --summary <title> --start <iso> --end <iso> [--location <text>] [--notes <text>] [--all-day]` | create an event, returns one with id |78| `agendactl calendar update-event <id> [--summary] [--start] [--end] [--location] [--notes]` | update (fields not given stay unchanged) |79| `agendactl calendar delete-event <id>` | delete an event |8081`<id>` comes from `list-events` / `create-event`. **`--end` must be later than `--start`** (else82exit 1); when changing only one end, keep the order valid.8384## Reminders commands8586| command | purpose |87|---|---|88| `agendactl reminders lists` | list all lists: name + id + writable. **Run before any write.** |89| `agendactl reminders list [--list <name>] [--status incomplete\|completed\|all] [--due today\|<iso>] [--limit <n>]` | list reminders (default incomplete), returns `{…meta, items:[...]}` |90| `agendactl reminders create --list <name> --name <text> [--notes <text>] [--due <iso>] [--priority 0-9]` | create a reminder, returns one with id |91| `agendactl reminders update <id> [--name] [--notes] [--due] [--priority] [--complete]` | update (fields not given stay unchanged) |92| `agendactl reminders complete <id>` | mark complete |93| `agendactl reminders delete <id>` | delete |9495Priority: `0`=none, `1–4`=high, `5`=medium, `6–9`=low (Apple's convention). `--status` defaults to96`incomplete`.9798## Dates99100- Input ISO 8601: `2026-06-29T09:00:00` (**no tz suffix = local time**); `2026-06-29` is also101 accepted. Output is UTC ISO (with `Z`).102- For relative dates ("today / tomorrow / the day after"), first read `._iso` from any response for103 "today", then do calendar arithmetic with macOS BSD `date` — `date -j -f "%Y-%m-%d" -v+1d "$ISO"104 +%Y-%m-%d` for "tomorrow" (note: BSD `-j -f -v`, not GNU `date -d`). `agendactl` itself accepts only105 full ISO 8601, never expressions like "tomorrow".106107Example — "review tomorrow 3–4pm":108```bash109RESP=$(agendactl calendar calendars) # real calendar name (+ _iso)110ISO=$(echo "$RESP" | jq -r ._iso)111CAL=$(echo "$RESP" | jq -r '.items[] | select(.writable) | .name' | head -1)112TOMORROW=$(date -j -f "%Y-%m-%d" -v+1d "$ISO" +%Y-%m-%d)113agendactl calendar create-event --calendar "$CAL" --summary "Review" \114 --start "${TOMORROW}T15:00:00" --end "${TOMORROW}T16:00:00"115```116117## Standard workflow (required for writes)1181191. **List containers first** (`calendars` / `lists`) to get real names — don't guess ("Personal"120 vs "个人", duplicate "Holidays" calendars write to the wrong place).1212. Call `create` / `update` / `complete` / `delete`.1223. **Compound filtering** (e.g. "events over 1 hour this week", "today's high-priority to-dos"):123 coarse-filter with `--from`/`--to`/`--list`/`--status`/`--due`, then refine in context yourself —124 the CLI does not do complex queries.1254. After writing, re-read with `list` / `list-events` to confirm.1265. **Confirm with the user before bulk deletes**, then delete one by one.127128## Exit codes (judge by these, don't guess)129130| code | meaning | what to do |131|---|---|---|132| 0 | success | read the JSON on stdout |133| 1 | usage/arg error | read stderr, fix the arguments |134| 2 | name/id not found | pick from stderr's `available:`; or run `calendars`/`lists` first |135| 3 | not authorized | have the user grant Full Access in System Settings → Privacy & Security → Calendars / Reminders |136| 4 | runtime error | read the reason on stderr |137138On failure, stderr's first line is `agendactl: <reason>`.139140## Not covered (say "not supported yet", don't invent)141142- Calendar: attendees/invites, room booking, shared-calendar permissions, recurrence-rule editing.143- Reminders: subtasks, attachments, structured URL/flag read-write.