Manage your Mac from the command line. Reminders, Calendar, Contacts, Notes, Mail, Music, Keychain, Safari, and 30+ more Apple apps.
- Fast reads, supported writes. Bulk reads use local macOS indexes where they are reliable; Calendar falls back to app automation when its fast path is unavailable. Writes use the apps' supported JXA/AppleScript interfaces.
- Stable identity. Calendar, Mail, Contacts, and Reminders expose durable ids, and destructive operations prefer exact ids over titles or list positions. Ambiguous legacy matches fail safely.
- Complete discovery.
cider schemais generated from the real command parser, so agents can discover all commands, arguments, defaults, dry-run support, and identifier contracts without a second hand-maintained API list. - Efficient batches. Calendar, Mail, and Reminders batch commands reuse one app automation session and return a result for every item, including partial failures.
- Full-fidelity PIM data. Contacts return labeled multi-value fields and richer profile data; Calendar, Mail, and Reminders expose deeper read and mutation APIs without truncating content.
- Prompt-free diagnostics.
cider doctorandcider auth-statusinspect tools, data stores, and access state without triggering macOS permission dialogs.
brew tap thrashr888/tap && brew install ciderOr via Cargo:
cargo install cider-cli# See your reminders
cider reminders
# Pretty tables for humans
cider reminders --pretty
# Create a reminder
cider reminders create --title "Buy milk" --list Shopping
# Complete one by id — titles repeat, ids don't
cider reminders complete --id 4b7c5902-46a7-4f7a-a385-91b562ca8eb6
# Check your calendar
cider calendar
# Fetch/update/delete the exact event by its stable id
cider calendar get --id <event-id>
cider --dry-run calendar update --id <event-id> --location "Zoom"
# Search contacts
cider contacts list --search Smith
# Check local data access without triggering permission dialogs
cider doctor
cider auth-status
# Control music
cider music play
cider music status
cider music pause
# Send a message
cider messages send --to "+15551234567" --text "On my way"
# Watchlist prices from Apple Stocks
cider stocks
cider stocks quote --symbol AAPL
# Apple Weather at your home's address, or anywhere (needs Cider Bridge)
cider weather
cider weather --forecast --days 5
cider weather --lat 37.75 --lon -122.49
# Fast bulk listing of every Apple Note (no bodies)
cider notes list --brief
# Search your Mac
cider spotlight --query "quarterly report"| App | Actions |
|---|---|
| Reminders | list, get, create, update, complete, reopen, delete, batch actions, lists |
| Calendar | list, get, create, batch-create, update, delete, calendars |
| Contacts | list, get, create, update, delete, groups |
| Notes | list, get, create, update, delete, folders |
list/search, get, read, unread, trash, batch actions, mailboxes, send |
|
| Keychain | list, search, get-password, add, delete, keychains |
| App | Actions |
|---|---|
| Music | list, play, pause, next, previous, status, playlists |
| Messages | list, send |
| Shortcuts | list, run, view, export, gen, install, sign — an ssh step to this Mac needs Remote Login on (System Settings › General › Sharing); gen refuses to build one while port 22 is closed unless you pass --allow-unreachable-ssh |
| Screenshots | list, capture |
| Time Machine | status, list, start, stop |
| iCloud Drive | list, download, evict (evict removes the local copy; the file stays in iCloud) |
| Screen Sharing | status, enable, disable |
| System Info | show, set-name, defaults-read, defaults-write |
| Safari | bookmarks, history, tabs, reading-list |
| Wi-Fi | status, networks |
| App | Actions |
|---|---|
| FaceTime | list |
| Passwords | list, get, create, update, delete |
Activity Monitor, Apps, Automator, Bluetooth, Books, Clock, Console, Disks, Fonts, Home (list, homes, rooms, accessories, scenes), iCloud (account, quota, status, log, list — placeholder-aware, never downloads), Photo Booth, Photos, Spotlight, Stocks (list, watchlists, quote), Voice Memos, Weather (current, --forecast; needs Cider Bridge)
These need the optional Swift helper described under Bridge; cider bridge status says what you have.
| Command | What it does |
|---|---|
home state, home run, home set, home triggers … |
Live HomeKit values, scenes, characteristics, and timer automations (personal build only) |
weather [--forecast] [--days N] [--home <name> | --lat --lon] |
WeatherKit current conditions or daily forecast, with Apple's required attribution |
reminders create|update|complete|reopen|delete|batch-*, calendar create|update|delete |
Same commands, EventKit instead of AppleScript when cider-bridge is installed (--envelope says "source": "cli"|"native") |
watch [--source …] [--via auto|cli|fsevents] |
One JSON line per store change; EventKit/Contacts notifications with the CLI, FSEvents otherwise |
Default output is compact JSON — pipe to jq, feed to scripts, or use with AI agents:
cider contacts | jq '.[].name'
cider calendar | jq '[.[] | select(.is_all_day == false)]'
cider activity-monitor | jq '.[0].top_processes[:5]'Add --pretty anywhere for human-readable tables:
$ cider --pretty reminders
ID LIST PRIORITY TITLE
──────────────────────────────────────────────────────────────────
4b7c5902-46a7-4f7a-a385-91b562ca8eb6 Shopping 1 Buy milk
f4c021a1-2ed3-4f14-ab65-b8ce3b315a27 Work 0 Review PR
217 items
Write operations return a status object:
$ cider --pretty reminders create --title "Buy milk" --list Shopping
✓ created (buy_milk) — Reminder added
Batch writes use one app automation session and report every item, including partial failures:
{"ok":false,"action":"batch-delete","requested":2,"succeeded":1,"failed":1,"results":[{"id":"a","ok":true},{"id":"b","ok":false,"error":"not found"}]}A partial batch exits non-zero after writing this result, and --envelope
keeps the outer ok value false as well.
Repeat --id for Reminders and Mail batches. Calendar batch creation accepts
a JSON array, or --json - to read it from stdin:
cider --dry-run reminders batch-complete --id <id-1> --id <id-2>
cider mail batch-read --id '<message-1@example.com>' --id '<message-2@example.com>'
printf '%s' '[{"title":"1:1","start":"2026-09-02T17:00:00Z","end":"2026-09-02T17:30:00Z"}]' \
| cider calendar batch-create --json -reminders complete and reminders delete take either --title or --id.
Titles are not unique, so a --title call acts on the first open match and
says so when there were others:
$ cider reminders complete --title "Review PR"
{"action":"completed","message":"Marked 'Review PR' (1 of 2 matching — pass --id to choose)","ok":true}
Pass the id from reminders list to name one exactly. A --title match only
ever considers reminders that are still open — the same set reminders list
shows — so a finished reminder of the same name can never absorb the action.
Reminder content round-trips in full: list and get return complete titles
and notes (newlines intact, no length cap), and update edits a reminder in
place — preserving its id and creation date:
$ cider reminders get --id 4b7c5902-46a7-4f7a-a385-91b562ca8eb6
$ cider reminders update --id 4b7c5902-... --priority 1 --new-title "Buy oat milk"
$ cider reminders update --id 4b7c5902-... --append-notes "also: check the sale"
$ long-notes-command | cider reminders update --id 4b7c5902-... --notes -
--notes - (and --append-notes -) read from stdin, for long or multiline
content that shell arguments handle badly.
Calendar mutations likewise prefer the id printed by calendar list.
Legacy --title plus --date deletion remains accepted, but it now refuses
to act if several events match instead of deleting an arbitrary one.
Mail list/get output uses the RFC Message-ID as id when Mail has one and
also includes local_id. Stable --id targeting is preferred; the old
one-based --index form remains available for compatibility. Mail listing can
search subject/sender/preview, select a mailbox, and filter unread or flagged
messages:
cider mail list --search invoice --mailbox INBOX --unread --limit 25
cider mail get --id '<message-id@example.com>'Contacts include all labeled emails, phones, URLs, and postal addresses plus
middle name, nickname, job title, department, birthday, and notes when present.
Create and update accept the same richer name and work fields; repeat --email
or --phone during creation to add several values.
cider schema is generated from the real command parser. It describes every
top-level command, action, argument, required/default value, read/write kind,
dry-run support, and stable identifier contract. This avoids a second,
hand-maintained command list drifting out of date:
cider schema
cider schema --source calendarcider permissions lists every macOS permission cider can need with its
state for the app that launched it, the System Settings pane, and the
Info.plist keys a host app must declare (see Permissions).
cider doctor checks required macOS tools, the Calendar, Contacts,
Reminders, and newest Mail data stores, the bridge, and summarizes the
permissions in one permissions check. cider auth-status is the older
per-store view of the same read/write state. None of them sends an
AppleEvent: even a permission probe can open a macOS dialog, so Automation
authorization is reported as not_probed and real writes surface any
denial.
Cider exposes one API, not user-selectable backends. Internally it uses the fastest reliable macOS path for each operation: local SQLite indexes for bulk reads, and the apps' supported JXA/AppleScript interfaces for writes. That keeps reads fast and writes supported without shipping Swift or Node sidecars. If a Calendar database read fails, Cider falls through to its slower app automation path and reports the failed fast path on stderr.
cider is also a Rust crate, so another Rust program can skip the subprocess, the JSON round-trip, and the question of whether the binary is installed and new enough:
[dependencies]
cider-cli = { version = "0.5", default-features = false }for r in cider::sources::reminders::list(Some("Shopping")).await? {
println!("{} {}", r.id, r.title);
}
cider::sources::reminders::complete(
cider::sources::reminders::Target::Id(&id),
Some("Shopping"),
).await?;Every sources::* module returns plain serde types — the CLI is a thin Clap
front-end over exactly these functions. default-features = false drops the
Clap front-end and the --pretty table renderer, which a library caller never
uses.
The library shells out to macOS's own tools (osascript, sqlite3), so it
needs nothing on PATH — but it inherits your process's TCC permissions, and
sees the same Full Disk Access denials the CLI reports. Your app is the
responsible process, so it must carry the usage strings listed under
Permissions;
cider::permissions::report().await and cider::doctor::inspect().await
return what cider permissions and cider doctor print.
- macOS
- The permissions in Permissions, granted to the app that
launches cider;
cider permissionsshows which are missing screen-sharing enable/disablerequiressudomail sendandmessages sendwill actually send — not a drill
cider follows agent-friendly CLI principles:
- JSON arrays/objects on stdout, errors on stderr
- Compact output by default (no
--pretty) for token efficiency - Write results:
{"ok": true, "action": "...", "id": "...", "message": "..."} - Each command is stateless and independent
- Broken pipe safe (
cider contacts | headwon't error)
This repo includes Agent Skills so compatible agents can learn how to use cider effectively.
# Install the repo's skills
npx skills add thrashr888/cider
# Install just the cider CLI usage skill
npx skills add thrashr888/cider@cider-cli
# Install to a specific agent
npx skills add thrashr888/cider -a claude-code
npx skills add thrashr888/cider -a cursorOr copy the skills into another project manually:
git clone https://github.com/thrashr888/cider.git
cp -r cider/.skills /path/to/your/project/.skillsCompatible agents automatically discover skills in the .skills/ directory.
cider-cli— guide for usingciderto read and change Apple app data from the terminal
The cider-cli skill helps agents:
- discover commands with
cider --helpandcider schema --source <name> - prefer compact JSON for automation and
--prettyonly for human review - use
--dry-runbefore supported mutations - account for macOS permissions, dialogs, and real side effects like
mail sendandmessages send
This repo also contains repo-maintenance skills in .agents/skills/ for agents working on cider itself.
git clone https://github.com/thrashr888/cider
cd cider
cargo build --release
# Binary at target/release/cidermacOS attributes a command-line tool's privacy access to its responsible
process: the app that launched it — Terminal, iTerm, an agent runner, or
the app that links the crate — so every grant below belongs to that app, not
to cider. A prompt appears only if that app's Info.plist declares the
matching usage string, and an app that never asked never appears in System
Settings › Privacy & Security, so nobody can pre-grant it.
Run cider permissions first (every permission, its state for your launcher,
the exact pane, and who to grant it to; --source calendar narrows it to one
command, --pretty tabulates), then cider doctor (tools, stores, the
bridge, and a one-line permissions summary). Both are prompt-free: they
open files for reading, ask an installed cider-bridge for its status, and
ping a bridge that is already running — never an AppleEvent, never a launch.
| Permission | Needed by | Granted to | How |
|---|---|---|---|
| Full Disk Access | messages, mail, safari, reading-list, photos, books, voice-memos, facetime, icloud account, stocks, shortcuts, home (cache), watch, and the SQLite reads behind calendar, reminders, contacts |
launching app | Privacy & Security › Full Disk Access: add the app by hand, then relaunch it. No prompt, no Info.plist key; sudo does not bypass it |
| Calendars | calendar through cider-bridge (EventKit) |
launching app | Privacy & Security › Calendars → Full Access, not Add Only (Add Only hides every event). Current macOS shows no Calendar prompt to a command-line requester: the first call registers the app in the pane, and you set it by hand |
| Reminders | reminders through cider-bridge |
launching app | The first call prompts; grant Full Access. Afterwards: Privacy & Security › Reminders |
| Contacts | contacts through cider-bridge |
launching app | Privacy & Security › Contacts. Like Calendar, no prompt for a command-line requester: set it by hand after the first call |
| Automation (one pair per target app) | notes, music, mail send/read/unread/trash/get, messages send, safari tabs, shortcuts run/view, and the AppleScript/JXA fallbacks for calendar, reminders, contacts when the bridge is absent |
launching app → target app | The first AppleEvent prompts, per pair; afterwards Privacy & Security › Automation. Always not_probed: the probe would itself be an AppleEvent |
| HomeKit | home state/run/set/triggers, home --live |
Cider Bridge.app | The bridge app prompts on its first HomeKit call; Privacy & Security › HomeKit → Cider Bridge. Personal build only |
| Location | nothing yet (reserved for a location command) |
launching app | Privacy & Security › Location Services |
WeatherKit (weather) needs no user permission.
Full Disk Access is the one to grant first. It covers every store cider
reads straight from disk: ~/Library/Messages/chat.db,
~/Library/Mail/V*/MailData/Envelope Index, ~/Library/Safari/History.db
and Bookmarks.plist, the Photos library database, Books, Voice Memos, the
call history, ~/Library/Accounts/Accounts4.sqlite, the Stocks and Home
containers, ~/Library/Shortcuts, and the Calendar, Reminders, and Contacts
databases. cider permissions checks it by opening the Messages and Safari
stores for reading, which never prompts — and it has to open them: a
protected file's metadata reads fine even when opening it fails with EPERM.
When another app links the crate or runs the binary, that app is the
responsible process. It must ship the usage strings, or macOS refuses to
prompt and the access is silently denied forever — and, having never asked,
the app never appears in System Settings for the user to fix. Copy these
into the host's Info.plist (cider::HOST_INFO_PLIST_KEYS in the library):
<key>NSCalendarsFullAccessUsageDescription</key>
<string>Reads and updates your calendar events.</string>
<key>NSRemindersFullAccessUsageDescription</key>
<string>Reads and updates your reminders.</string>
<key>NSContactsUsageDescription</key>
<string>Looks up and updates your contacts.</string>
<key>NSAppleEventsUsageDescription</key>
<string>Controls Notes, Mail, Music, Messages, Safari, and Shortcuts on your behalf.</string>
<!-- optional today: reserved for a future `location` command -->
<key>NSLocationWhenInUseUsageDescription</key>
<string>Uses your location.</string>Full Disk Access and HomeKit need no key: the user adds the host app under
Full Disk Access by hand, and HomeKit belongs to Cider Bridge.app, which
declares its own. From Rust, cider::permissions::report().await returns
the same report the CLI prints — show how_to_grant for anything denied,
add_only, or not_determined — and cider::doctor::inspect().await is
cider doctor.
Everything above works with the Rust binary alone. Some Apple data only
exists behind a framework that loads in a signed app, so cider can also
use an optional Swift helper: Cider Bridge.app, a Mac Catalyst app it
launches on demand and talks to over a Unix socket (it quits after ten idle
minutes; no daemon), and cider-bridge, a native CLI it runs per call.
Sources and protocol: bridge/, docs/RFC-swift-bridge.md.
Start here: cider bridge status (what is installed, whether the app is
answering, protocol versions, per-store authorization) and cider doctor
(the bridge_* checks: app, socket, CLI, signing-profile expiry,
authorization, HomeKit). Neither launches the app or opens a dialog.
| Area | Without the bridge | With the bridge |
|---|---|---|
| Home | cider home reads the Home app's on-disk cache: homes, rooms, accessories, scenes, no live values, as fresh as the last time the Home app ran (--envelope reports "source": "cache" and cache_age_s; rows carry cache_updated_at) |
The same reads go live ("source": "bridge"; --live insists on it), plus home state [--room] [--accessory], home run --scene, home set --accessory --characteristic --value, and home triggers create-timer|enable|disable|delete — timer automations that fire on the home hub with the Mac asleep. Needs a personal build (below). |
| Weather | Nothing: the Weather app's cache is encrypted | cider weather [--forecast --days N] [--home <name> | --lat --lon], WeatherKit with Apple's required attribution block — keep it next to the numbers you show a person. Location is --lat/--lon, else --home, else the primary home's address from the Home app. |
| Reminders, Calendar writes | AppleScript/JXA (works, seconds per write) | EventKit through cider-bridge: same commands and result shape, milliseconds, the saved row as record; --dry-run names the path and --envelope reports "source": "cli"|"native". Bulk reads stay on SQLite. CIDER_BRIDGE_CLI=off forces the old path. |
cider watch |
FSEvents on the on-disk stores ("kind":"files_changed", coalesced paths) |
Item-level EventKit/Contacts change notifications for reminders, calendar, contacts ("kind":"store_changed"); --via cli fails rather than falling back. An event says that a store changed; re-read it to learn what. |
Home ids differ between the two backends (the cache reports homeUUID, the
bridge HMHome.uniqueIdentifier; room, accessory, and scene ids match), so
select homes by name. --home takes a name first, then an id from either
space, mapping through the name when it can.
The Homebrew formula ships the bridge app and the CLI next to cider
(libexec/Cider Bridge.app, bin/cider-bridge), Developer ID signed and
notarized. That covers everything except HomeKit: WeatherKit, EventKit
and Contacts writes, and watch. Apple grants the HomeKit entitlement only
to App Store and development builds, so a packaged bridge reports
homekit_entitled: false and HomeKit commands fail with
homekit_unavailable (cache-backed home reads keep working). Cargo
installs get the Rust binary only.
- Xcode, XcodeGen (
brew install xcodegen), and a paid Apple Developer team. - In the developer portal, enable HomeKit (and WeatherKit, if wanted) on the App ID
dev.thrasher.cider.bridge. Xcode's automatic signing registers your Mac and makes the profile but cannot add HomeKit to a Catalyst App ID; that step is manual, once. - From a cider checkout:
cider bridge build --install --team <TEAM_ID>(orCIDER_TEAM_IDin the environment orbridge/.env.local). This builds with XcodeGen +xcodebuild, installs~/Applications/Cider Bridge.app— which wins over the packaged copy — and putscider-bridgeinside it.
The development profile expires a year after it is made and the app then
silently stops launching; cider doctor reports bridge_profile as
expiring inside the last thirty days. Rebuild to renew.
The bridge's grants follow the rules in Permissions:
Calendars, Reminders, and Contacts belong to the app that launched cider
(cider permissions, or cider bridge status → cli_authorization, shows
each store's state with the fix), and HomeKit belongs to Cider Bridge.app
itself.
cider bridge status|build|install|quit manages the app;
CIDER_BRIDGE_APP=/path/to/Cider Bridge.app and
CIDER_BRIDGE_CLI=/path/to/cider-bridge point at builds elsewhere. Cider
checks the bridge's protocol version on every connection and fails with
bridge_incompatible when a stale app or CLI answers, naming the fix
(cider bridge build --install, or brew upgrade cider for the packaged
copy).
MIT