How this skill is triggered — by the user, by Claude, or both
Slash command
/ekctl-skill:ekctlThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Use the `ekctl` command-line tool to manage Calendar events and Reminders on macOS. Output defaults to JSON; CSV and plain-text are available on every command via `--format csv` and `--format text`.
Use the ekctl command-line tool to manage Calendar events and Reminders on macOS. Output defaults to JSON; CSV and plain-text are available on every command via --format csv and --format text.
Ensure ekctl is installed:
brew tap schappim/ekctl && brew install ekctl
If not installed, guide the user to install it first.
Verify version (this skill targets ekctl >= 1.4.0 — earlier versions don't have today/tomorrow/next, --search, --availability, or --format):
ekctl --version
Before any calendar/reminder operation, check if the user has aliases configured:
ekctl alias list
If aliases exist, use them instead of raw IDs. If no aliases exist, help the user set them up after listing calendars.
ekctl list calendars
This returns both event calendars (type: "event") and reminder lists (type: "reminder").
Help users create aliases for frequently used calendars:
ekctl alias set work "CALENDAR_ID"
ekctl alias set personal "CALENDAR_ID"
ekctl alias set groceries "REMINDER_LIST_ID"
Use the appropriate command based on what the user wants to do. See command-reference.md for all commands and scripting-examples.md for jq patterns and integration recipes.
Three top-level subcommands cover the most common queries with no date arithmetic required. Prefer these over computing --from/--to yourself:
ekctl today --calendar work # events occurring today (local time)
ekctl tomorrow --calendar work # events occurring tomorrow
ekctl next --calendar work # the single next upcoming event
ekctl next --calendar work --count 5 # the next 5 upcoming events
ekctl next --calendar work --days 7 # only look 7 days ahead
next returns events sorted by start time and includes events currently in progress (their endDate is still in the future).
All three accept the same filter and format flags as list events, so they compose:
ekctl today --calendar work,personal --availability busy --format csv
ekctl next --calendar work --search standup --count 3 --format text
Two filters narrow list events and (--search only) list reminders. They compose with everything else:
--search <term> — case-insensitive substring match across an event's title, location, and notes (or a reminder's title and notes). Use this instead of post-filtering through jq.--availability busy|free|tentative|unavailable|notSupported — events only. Filters to events whose EKEventAvailability matches.# All standup-related events for the week
ekctl next --calendar work --search standup --days 7
# Only "busy" events today — actual blocked-out time
ekctl today --calendar work --availability busy
# Reminders that mention milk
ekctl list reminders --list groceries --search milk
Every output-producing command takes --format:
json (default) — full structure, pretty-printed.csv — RFC 4180. Header is the union of every field across the returned items, alphabetised. Nested objects flatten to dot-notated columns (calendar.id, calendar.title); nested arrays (like attendees) become a JSON-encoded cell.text — key: value lines per item, blank line between items. Greppable.CSV and text are auto-discovered from the same dictionary that produces JSON, so new fields appear in all three formats simultaneously — no drift.
ekctl today --calendar work --format csv > today.csv
ekctl list calendars --format text
--calendar accepts a comma-separated list. Each returned event keeps its calendar.id and calendar.title, so the merged stream is still distinguishable:
ekctl today --calendar work,personal,family
ekctl list events --calendar work,personal --from "2026-02-01T00:00:00Z" --to "2026-02-28T23:59:59Z"
For commands that take explicit --from / --to / --start / --end / --due, use ISO 8601 with timezone:
| Example | Description |
|---|---|
2026-01-15T09:00:00Z | 9:00 AM UTC |
2026-01-15T09:00:00+10:00 | 9:00 AM AEST |
2026-01-15T00:00:00Z | Start of day (midnight UTC) |
2026-01-15T23:59:59Z | End of day |
For "today" / "tomorrow" / "this week", use the subcommands above instead of computing dates — today/tomorrow/next are local-timezone-aware and don't rely on the BSD-only date -v+1d flag (which breaks on Linux).
If you genuinely need a custom range outside today/tomorrow/next, generate dates like this:
# An explicit absolute date
START="2026-01-15T09:00:00Z"
# A relative date in the user's local zone via Swift / Python is more portable
# than `date -v+1d`. For most cases, `ekctl next --days N` is simpler.
ekctl today --calendar work --availability busy
ekctl next --calendar work --count 3
ekctl today --calendar work,personal --search "1:1"
ekctl add event \
--calendar work \
--title "Team Standup" \
--start "2026-01-15T09:00:00Z" \
--end "2026-01-15T09:30:00Z" \
--location "Conference Room A" \
--notes "Weekly sync"
ekctl add event \
--calendar work \
--title "Weekly 1:1" \
--start "2026-01-15T14:00:00Z" \
--end "2026-01-15T14:30:00Z" \
--recurrence-frequency weekly \
--recurrence-end-count 12
ekctl add event \
--calendar personal \
--title "Vacation Day" \
--start "2026-01-20T00:00:00Z" \
--end "2026-01-21T00:00:00Z" \
--all-day
ekctl add reminder \
--list personal \
--title "Call the dentist" \
--due "2026-01-16T10:00:00Z" \
--priority 1
ekctl list reminders --list personal --completed false
ekctl complete reminder "REMINDER_ID"
ekctl update event "EVENT_ID" --title "Renamed meeting" --location "New room"
ekctl update reminder "REMINDER_ID" --due "2026-01-17T10:00:00Z" --priority 5
Events include these fields (since 1.4.0):
{
"id": "ABC123:DEF456",
"title": "Team Meeting",
"calendar": { "id": "...", "title": "Work" },
"startDate": "2026-01-15T09:00:00+11:00",
"endDate": "2026-01-15T10:00:00+11:00",
"location": "Conference Room A",
"notes": null,
"url": null,
"allDay": false,
"hasAlarms": true,
"hasRecurrenceRules": false,
"availability": "busy",
"attendees": [
{ "name": "Jane Doe", "email": "[email protected]", "status": "accepted", "role": "required" }
]
}
Reminders include:
{
"id": "REM123-456-789",
"title": "Buy groceries",
"list": { "id": "...", "title": "Personal" },
"dueDate": "2026-01-20T17:00:00+11:00",
"completed": false,
"priority": 0,
"notes": null,
"url": null
}
ekctl returns JSON by default. For most filtering needs, prefer the built-in flags over jq — they're typically clearer and don't require a date / substring round-trip:
| Need | Old way (jq) | New way (built-in) |
|---|---|---|
| Events for today | --from $TODAY --to $TOMORROW | ekctl today --calendar work |
| Title contains "standup" | ... | jq '.events[] | select(.title | contains("standup"))' | --search standup |
| Only busy events | ... | jq '.events[] | select(.availability == "busy")' | --availability busy |
| Multiple calendars | run twice + merge | --calendar work,personal |
| CSV export | ... | jq -r '... | @csv' | --format csv |
| The next event only | sort + head -n1 | ekctl next --calendar work |
jq is still the right tool for cross-cutting transformations (e.g., extracting just titles, computing durations, joining with external data). See scripting-examples.md.
Check the status field in responses:
{
"status": "error",
"error": "Calendar not found with ID: invalid-id"
}
In CSV format, errors become a single row:
error,status
Calendar not found with ID: invalid-id,error
In text format:
error: Calendar not found with ID: invalid-id
status: error
Common errors:
ekctl list calendars and ekctl alias list.--count must be a positive integer / --days must be a positive integer — next rejects non-positive values.❌ Computing today/tomorrow with date -u -v+1d shell arithmetic — BSD-only, breaks on Linux containers
✅ Use ekctl today / ekctl tomorrow / ekctl next instead
❌ Post-filtering with jq '.events[] | select(.title | contains(…))'
✅ Use --search — same result, far simpler
❌ Filtering by availability with jq '.events[] | select(.availability == "busy")'
✅ Use --availability busy — server-side, also cleaner
❌ Building CSV with jq -r '... | @csv' hand-mapped column lists
✅ Use --format csv — picks up new fields automatically, RFC 4180 escaping built in
❌ Running list events twice to merge two calendars
✅ --calendar work,personal does it in one call, with calendar.id per event so you can still demux
❌ Using raw calendar IDs repeatedly without suggesting aliases
✅ Help users set up aliases for calendars they use often
❌ Ignoring the JSON status field
✅ Always check status and surface errors to the user
❌ Listing all events without a date range
✅ Use today / tomorrow / next, or sensible explicit --from/--to
command-reference.md for complete command and flag documentationscripting-examples.md for jq patterns and integration recipesGuides creation, editing, and verification of skills for AI coding agents using test-driven development with subagent scenarios. Use when authoring or debugging skills.
npx claudepluginhub schappim/ekctl-skill --plugin ekctl-skill