Manage Apple Calendar events from the command line on macOS — create, update, delete, search, export, and check availability with full JSON output for agent...
---
name: accli
description: Manage Apple Calendar events from the command line on macOS — create, update, delete, search, export, and check availability with full JSON output for agent use.
author: gopaljigaur
license: MIT
platform: macOS
requires:
binaries: [accli]
install: "npm install -g @gopaljigaur/accli"
tags: [calendar, macos, productivity, apple, events]
---
# accli — Apple Calendar CLI
`accli` manages Apple Calendar on macOS via EventKit. All commands support `--json` for structured output. Exit codes: 0=success, 1=runtime error, 2=validation error, 10=auth error.
**First run:** `accli setup` to grant Calendar permissions (Full Access required in System Settings > Privacy & Security > Calendars).
## Calendars
```bash
accli calendars [--json]
```
Returns list of calendars with `id`, `name`, `source`, `index`, `writable`. Always prefer `--calendar-id <id>` over calendar name — IDs are stable, names are not.
## Events
```bash
accli events [<calendarName>] [--calendar-id <id>] --from <date> --to <date> [--json]
```
Lists events in a date range. Calendar is required (use name, `--calendar-id`, or set a default via `accli config set-default`).
## Single Event
```bash
accli event [<calendarName>] <eventId> [--calendar-id <id>] [--json]
```
Fetches full event detail including alerts array (minutes before start).
## Create Event
```bash
accli create <calendarName> --summary <text> --start <datetime> --end <datetime> \
[--location <text>] [--description <text>] [--all-day] \
[--alert <minutes>] [--alert <minutes>] \
[--recur daily|weekly|monthly|yearly] [--recur-count <n>] [--recur-end <YYYY-MM-DD>] \
[--json]
```
- `--alert` is repeatable — adds one alert per flag (minutes before start)
- `--recur-end` and `--recur-count` are mutually exclusive
- All-day events: use `YYYY-MM-DD` for `--start` and `--end`
- Timed events: use `YYYY-MM-DDTHH:mm`
## Update Event
```bash
accli update <calendarName> <eventId> \
[--summary <text>] [--start <datetime>] [--end <datetime>] \
[--location <text>] [--description <text>] \
[--alert <minutes>] [--alert <minutes>] \
[--dry-run] [--json]
```
`--alert` on update replaces all existing alerts. Omit `--alert` to leave alerts unchanged.
`--dry-run` returns `{ dryRun: true, wouldUpdate: { eventId, changes } }` without modifying anything.
## Delete Event
```bash
accli delete <calendarName> <eventId> [--dry-run] [--json]
```
`--dry-run` returns `{ dryRun: true, wouldDelete: { id, summary, calendar, start, end } }` without deleting.
## Search
```bash
accli search --query <text> [--from <date>] [--to <date>] [--calendar-id <id>] [--json]
```
Case-insensitive search across summary, location, and description. Searches all calendars unless `--calendar-id` is provided. Returns events with `calendarId` field for targeting.
## Export
```bash
accli export --from <date> --to <date> [--calendar-id <id>] [--json]
```
Exports all events grouped by calendar. Response: `{ calendars: [{ id, name, source, events, truncated }], totalEvents, truncated }`. Each calendar truncates at 500 events per calendar and sets `truncated: true` if hit.
## Free/Busy
```bash
accli freebusy --from <datetime> --to <datetime> [--calendar-name <name>] [--json]
```
Returns busy time slots across calendars.
## Config
```bash
accli config set-default --calendar-id <id> [--json]
accli config show [--json]
accli config clear [--json]
```
Persists default calendar to `~/.acclirc` (override with `ACCLI_CONFIG_PATH`). Commands that require a calendar use the default when no calendar is specified.
## DateTime Formats
- Timed: `YYYY-MM-DDTHH:mm` or `YYYY-MM-DDTHH:mm:ss`
- Date-only (all-day events, --from/--to): `YYYY-MM-DD`
## Agent Best Practices
- Always use `--json` for programmatic parsing
- Use `--calendar-id` not calendar name (stable across renames)
- Use `--dry-run` before destructive operations to confirm target event
- Use `accli search` to find event IDs before update/delete
- Check `ok: false` in JSON response before proceeding
- `accli export` is suitable for full calendar backup; check `truncated` field
- Multiple alerts: repeat `--alert` flag — e.g. `--alert 5 --alert 15`
- macOS only — do not attempt on non-darwin systems
## Error Codes
| Code | Meaning |
|------|---------|
| `NOT_AUTHORIZED` | Calendar access not granted or set to Add Only |
| `CALENDAR_NOT_FOUND` | Calendar ID or name not found |
| `AMBIGUOUS_CALENDAR` | Multiple calendars with same name — use `--calendar-id` |
| `EVENT_NOT_FOUND` | Event ID not found in calendar |
| `MISSING_REQUIRED` | Required flag missing |
| `INVALID_ARGUMENT` | Invalid flag value |
| `INVALID_RANGE` | Start is after end |
don't have the plugin yet? install it then click "run inline in claude" again.
converted raw docs to implexa format with explicit intent, structured inputs (setup/config/external state), 10-step procedure with inputs/outputs, 9 decision points covering error codes and edge cases, detailed json output contract, and clear outcome signals for validation.
manage apple calendar on macos via EventKit. all commands support --json for structured output. exit codes: 0=success, 1=runtime error, 2=validation error, 10=auth error.
accli-plus lets you programmatically manage apple calendar from the command line on macOS. create, modify, delete, search, and export events. check free/busy slots. all operations return JSON for agent parsing. use this when you need calendar automation without GUI interaction, or when building workflows that read and write calendar data to apple's EventKit backend.
system requirements
npm install -g @gopaljigaur/acclisetup (one-time)
accli setup to grant full calendar access in System Settings > Privacy & Security > CalendarsNOT_AUTHORIZED error code 10 otherwise)configuration
accli config set-default --calendar-id <id> to avoid passing --calendar-id on every command~/.acclirc (override with env var ACCLI_CONFIG_PATH)external state
--recur-count OR --recur-end, not bothaccli calendars [--json]
input: none (reads from EventKit)
output: JSON array with fields id (stable UUID), name (user-facing, changeable), source (icloud, local, etc), index (position), writable (boolean)
use this to discover calendar IDs before running other commands
accli events [<calendarName>] [--calendar-id <id>] --from <date> --to <date> [--json]
input: calendar (positional name, --calendar-id flag, or config default), --from <YYYY-MM-DD>, --to <YYYY-MM-DD>
output: JSON array of events with id, summary, start, end, location, description, alerts, recurring
note: calendar param is required; omit only if default is configured
accli event [<calendarName>] <eventId> [--calendar-id <id>] [--json]
input: calendar (name, ID, or default), event UUID output: full event object including alerts array (minutes before start), recurrence rules, attendees if applicable use before update/delete to confirm you have the right event
accli create <calendarName> --summary <text> --start <YYYY-MM-DDTHH:mm> --end <YYYY-MM-DDTHH:mm> \
[--location <text>] [--description <text>] \
[--alert <minutes>] [--alert <minutes>] \
[--recur daily|weekly|monthly|yearly] [--recur-count <n>] [--recur-end <YYYY-MM-DD>] \
[--json]
input: calendar name, --summary, --start and --end in ISO format with time (HH:mm or HH:mm:ss), optional location, description, alerts (repeatable), recurrence config
output: JSON object with new id, summary, start, end, location, description, alerts, recurring, calendarId
validation: start must not be after end (error code INVALID_RANGE)
accli create <calendarName> --summary <text> --start <YYYY-MM-DD> --end <YYYY-MM-DD> --all-day \
[--location <text>] [--description <text>] \
[--alert <minutes>] [--recur daily|weekly|monthly|yearly] [--recur-count <n>] [--recur-end <YYYY-MM-DD>]
input: same as 4a, but use date-only format, include --all-day flag
output: event object with allDay: true
note: alerts on all-day events are still in minutes before start (interpreted as start of day)
accli update <calendarName> <eventId> \
[--summary <text>] [--start <datetime>] [--end <datetime>] \
[--location <text>] [--description <text>] \
[--alert <minutes>] [--alert <minutes>] \
[--dry-run] [--json]
input: calendar name/ID, event ID, any subset of optional fields
output: JSON with ok: true and updated event object; or if --dry-run, { dryRun: true, wouldUpdate: { eventId, changes } } (no write)
note: --alert flag on update replaces all existing alerts; omit to keep alerts unchanged
accli delete <calendarName> <eventId> [--dry-run] [--json]
input: calendar name/ID, event ID
output: JSON with ok: true and { dryRun: true, wouldDelete: { id, summary, calendar, start, end } } if dry-run; empty success response if confirmed
behavior: --dry-run shows what would be deleted without modifying calendar
accli search --query <text> [--from <YYYY-MM-DD>] [--to <YYYY-MM-DD>] [--calendar-id <id>] [--json]
input: --query (case-insensitive text), optional date range, optional calendar ID
output: JSON array of matching events (searches summary, location, description fields), each with id, summary, calendarId, start, end
behavior: if no --calendar-id, searches all calendars; always returns calendarId for targeting
accli export --from <YYYY-MM-DD> --to <YYYY-MM-DD> [--calendar-id <id>] [--json]
input: --from, --to (date range), optional calendar ID
output: JSON { calendars: [{ id, name, source, events: [...], truncated: boolean }], totalEvents: number, truncated: boolean }
behavior: each calendar truncates at 500 events and sets truncated: true if exceeded; check this field to know if full export was captured
accli freebusy --from <YYYY-MM-DDTHH:mm> --to <YYYY-MM-DDTHH:mm> [--calendar-name <name>] [--json]
input: --from and --to (datetime range with time), optional calendar name
output: JSON array of busy time slots (start, end, summary) across specified calendars
behavior: returns only occupied slots; absence of slots in output means full availability
accli config set-default --calendar-id <id> [--json]
accli config show [--json]
accli config clear [--json]
input: --calendar-id for set-default, no args for show/clear
output: JSON confirmation or config dump
behavior: persists to ~/.acclirc (or $ACCLI_CONFIG_PATH)
if calendar access not granted (error code 10: NOT_AUTHORIZED)
accli setup will prompt system dialog; user must click "OK" in System Settings > Privacy & Security > Calendars and set to "Full Access" (not "Add Only")if calendar name is ambiguous (multiple calendars with same name)
AMBIGUOUS_CALENDARaccli calendars --json to list all calendars and find stable IDs--calendar-id <uuid> over calendar name in production workflowsif calendar ID not found (error code CALENDAR_NOT_FOUND)
accli calendars --json and check the id fieldif event ID not found (error code EVENT_NOT_FOUND)
accli search --query <summary> to locate the event firstif --alert flag is omitted on update
--alert flags and existing alerts persist; there is no explicit "clear alerts" operation (workaround: update with a dummy alert, then update again to remove it, or manually edit in Calendar.app)if --recur-count and --recur-end are both provided
INVALID_ARGUMENT--recur-count 5 (5 occurrences) OR --recur-end 2025-12-31 (repeat until date), not bothif start datetime is after end datetime
INVALID_RANGEif --from or --to are missing on events/export
MISSING_REQUIREDif running on non-macOS (linux, windows)
uname -s returns "Darwin" before attemptingif export hits the 500-event-per-calendar limit
truncated: true for that calendar--calendar-id to get full datasetif network timeout or EventKit service is unresponsive
all commands with --json flag return valid JSON to stdout. error responses include ok: false and error: { code, message }. success responses vary by command but follow this structure:
calendars:
[
{ "id": "<uuid>", "name": "Personal", "source": "icloud", "index": 0, "writable": true }
]
events/event/search:
{
"id": "<uuid>",
"summary": "Team standup",
"start": "2025-01-15T09:00:00",
"end": "2025-01-15T09:30:00",
"location": "Conference Room A",
"description": "Daily sync",
"calendarId": "<uuid>",
"alerts": [5, 15],
"recurring": false,
"allDay": false
}
create/update:
{
"ok": true,
"event": { ... }
}
update --dry-run:
{
"dryRun": true,
"wouldUpdate": {
"eventId": "<uuid>",
"changes": {
"summary": "New title",
"start": "2025-01-15T10:00:00"
}
}
}
delete:
{
"ok": true
}
delete --dry-run:
{
"dryRun": true,
"wouldDelete": {
"id": "<uuid>",
"summary": "Team standup",
"calendar": "Personal",
"start": "2025-01-15T09:00:00",
"end": "2025-01-15T09:30:00"
}
}
export:
{
"calendars": [
{
"id": "<uuid>",
"name": "Personal",
"source": "icloud",
"events": [ ... ],
"truncated": false
}
],
"totalEvents": 42,
"truncated": false
}
freebusy:
[
{
"start": "2025-01-15T10:00:00",
"end": "2025-01-15T10:30:00",
"summary": "Team standup"
}
]
config:
{
"ok": true,
"defaultCalendarId": "<uuid>"
}
error response (any command):
{
"ok": false,
"error": {
"code": "NOT_AUTHORIZED",
"message": "Calendar access not granted. Run `accli setup` and enable Full Access."
}
}
file output: none (all output is stdout JSON or stderr error text). config stored in ~/.acclirc as text file.
the skill worked if:
ok: true field is present in response (for commands that return it)--dry-run first to verify target event matches expectationtruncated field is false)calendars command matches the ID in event responses under calendarId field