Minimal macOS calendar and reminders CLI built with Swift + EventKit.
- macOS
- Swift toolchain (SwiftPM)
- Calendar access permission (prompted on first run)
- Reminders access permission (prompted on first
reminderscommand; separate from calendar access)
Build a release binary and copy it into a directory on your PATH:
swift build -c release
mkdir -p "$HOME/.local/bin"
cp .build/release/ical "$HOME/.local/bin/ical"Optional system-wide install:
sudo cp .build/release/ical /usr/local/bin/icalIf ~/.local/bin is not on your PATH, add this to ~/.zshrc:
export PATH="$HOME/.local/bin:$PATH"Then reload your shell:
source ~/.zshrcNow you can run:
ical todayical version
ical --versionical today
ical tomorrow
ical weekOutput is sorted by start time with all-day events first. Empty fields are omitted. If no events exist, it prints No events.
ical add \
--title "Meeting with Joe" \
--start "today 14:00" \
--end "today 15:00" \
--calendar "Work" \
--location "Google Meet" \
--notes "Discuss pricing"
ical add \
--title "Morning Brief" \
--start "tomorrow 10:00" \
--end "tomorrow 10:30" \
--recurrence daily \
--recurrence-end "2026-03-31"Options:
--title <text>(required)--start <datetime>(required)--end <datetime>(required)--calendar <name>(optional, defaults to your default calendar)--location <text>(optional)--notes <text>(optional)--all-day(optional)--recurrence <daily|weekly|monthly|yearly>(optional)--recurrence-end <date>(optional, requires--recurrence)
By ID:
ical remove --id "<event-id>"
ical remove --id "<event-id>" --this-only
ical remove --id "<event-id>" --all-futureOr by title + exact start:
ical remove --title "Meeting with Luke" --start "today 14:00" --calendar "Work"For recurring events, use --this-only or --all-future to choose how removal is applied.
ical edit \
--id "<event-id>" \
--title "Updated title" \
--start "today 15:00" \
--end "today 15:30" \
--location "Office" \
--notes "Updated notes"Edit supports these optional flags:
--title,--start,--end,--calendar,--location,--notes--all-dayor--timed--clear-location--clear-notes--recurrence <daily|weekly|monthly|yearly>--recurrence-end <date>(requires--recurrence)--clear-recurrence--this-onlyor--all-future(required when editing a recurring event)
ical reminders # incomplete reminders, sorted by due date
ical reminders list --all # include completed reminders
ical reminders list --list "Groceries"If no reminders exist, it prints No reminders.
ical reminders add \
--title "Buy oat milk" \
--due "tomorrow 09:00" \
--list "Groceries" \
--notes "The barista kind" \
--priority 1Options:
--title <text>(required)--due <datetime>(optional; a date-only value like2026-07-20sets a day-granularity due date, a datetime also creates an alarm so the reminder notifies)--list <name>(optional, defaults to your default reminders list)--notes <text>(optional)--priority <0-9>(optional; 0 = none, 1 = high, 5 = medium, 9 = low)
ical reminders done --title "Buy oat milk"
ical reminders reopen --id "<reminder-id>"
ical reminders remove --id "<reminder-id>"Each takes --id or --title. A --title selector must uniquely match one incomplete reminder; use --id (printed by add) to disambiguate.
ical reminders edit \
--id "<reminder-id>" \
--title "Updated title" \
--due "2026-07-20 17:00" \
--priority 5Edit supports these optional flags:
--title,--due,--list,--notes,--priority--clear-due--clear-notes
Changing or clearing the due date also replaces or removes the reminder's alarm.
- ISO 8601 (for example
2026-02-20T14:00:00-05:00) today HH:mmtomorrow HH:mm