---
name: pippin-cli
description: Use Pippin through MCP or its command-line interface for Apple mail, calendar, reminders, contacts, notes, memos, messages, audio, browser work, permissions, and diagnostics.
metadata:
version: "1.4.0"
---
# Pippin CLI skill
`pippin` is a macOS CLI that automates Apple's native apps (Mail, Calendar, Reminders,
Notes, Contacts, Voice Memos, Messages) plus audio and browser. It is built for agents:
every command speaks a stable, versioned JSON envelope under `--format agent`.
**Current state:** stable **v0.33.0** (`pippin --version` to confirm), exposing **45 MCP
tools** (mail, calendar, reminders, contacts, notes, memos, messages, status, doctor,
agent-info, …). The pippin MCP server is attached for both the **[agent-runtime]/[agent] gateway**
(stdio, pointed at `~/.local/bin/pippin mcp-server`) and **Claude Cowork** (via the
`pippin@mw-plugins` plugin's generated MCP configuration). Any agent *without* the MCP attached falls back
to the CLI: see "How to call pippin" below.
## Trigger phrases
Use this skill when the user asks to:
- Check mail / read email / search inbox / send mail
- Check calendar / list events / what's on my schedule / create an event
- Read reminders / create a reminder / complete a task
- Search notes / create a note / read a note
- List or search contacts
- Transcribe audio / voice memos / list recordings
- Browse the web / open a URL / take a screenshot
- Use pippin / Apple app automation
## How to call pippin
Two equivalent surfaces: both produce the same agent JSON, because the MCP server is a
thin wrapper that shells out to the CLI. Everything in this skill (commands, envelope,
gotchas) applies identically to both.
1. **MCP tools** (`mail_list`, `calendar_today`, `reminders_create`, `status`, …) when a
pippin MCP server is attached. **Prefer these when available**: no shell quoting, and
the host has already resolved the binary path and TCC identity for you.
2. **CLI fallback**: when no pippin MCP server is attached ([agent-runtime]/[agent] and Claude Cowork
have it; a bare Claude Code session, a scheduled task, or any other agent may not), or
when an MCP tool errors and you want to retry/diagnose at the shell, call the CLI
directly. **Invoke the stable path explicitly:**
```bash
~/.local/bin/pippin … --format agent
```
`~/.local/bin/pippin` is the TCC-granted binary (see Permissions below): agents and
scheduled tasks should always use this path, **not** the brew symlink
`/opt/homebrew/bin/pippin`, whose grant is lost on every `brew upgrade`. Bare `pippin`
on PATH resolves to the stable copy too, but spelling out the absolute path removes any
doubt about which binary (and which grant) you're invoking.
## Use these habits every time (efficiency)
These are the difference between fast, cheap calls and slow, token-heavy ones:
1. **Always pass `--format agent`** when you (not a human) consume the output. Compact
JSON in a versioned envelope. Never parse `text` output.
2. **Project with `--fields`** to return only the keys you need: universal on every
structured command, including `mail list/search/activity`, `calendar events/search/
today`, `reminders list/search`, `notes list/search`, `contacts list/search`.
`pippin mail list --limit 20 --fields id,subject,from --format agent` is far cheaper
than the full payload.
3. **Bound the work**: pass `--limit` and the narrowest filters you can (`--account`,
`--mailbox`, `--folder`, `--after/--before`, `--range`). Broad/unbounded queries are
what hit soft timeouts.
4. **Prefer narrow commands over the firehose.** `pippin status` gathers every subsystem;
if you only need mail, call `pippin mail list`. Narrow calls are faster and never
partial.
5. **Run independent reads in parallel** (e.g. mail + calendar + reminders for a briefing),
then process together.
## Output: the agent envelope (v1)
Every `--format agent` response is wrapped in a versioned envelope. **Parse `.data`, not
the top level.**
Success:
```json
{"v":1,"status":"ok","duration_ms":142,"data":,"warnings":["…"]?}
```
Error:
```json
{"v":1,"status":"error","duration_ms":51,"error":{"code":"access_denied","message":"…","remediation":{…}}}
```
- The previous raw payload now lives under `data` unchanged: `jq '.data | length'`,
`jq '.data[].id'`, etc.
- `warnings` (optional) carries non-fatal advisories such as partial-results notices.
- **Always branch on `status`** before reading `data`. On error, read `error.code`
(stable, snake_case) and `error.remediation` for the fix.
### Partial results (soft timeout)
Long scans (mail, notes, full `status`) are bounded by a soft wall-clock cap. When a call
runs out of budget it returns **what it has so far** plus `status:"ok"` with a `warnings`
entry (and, for `status`, a top-level `timedOut:true` in `data`). Treat partial results as
incomplete, not empty: re-run with a narrower filter / smaller `--limit` for the rest.
### Exit codes
The process exit code mirrors `error.code`, so shells/agents can branch without parsing:
`0` ok · `2` usage · `3` not-found · `4` auth/permission/config · `5` tool/bridge ·
`7` timeout/rate-limit.
## Permissions & TCC: read this before automating from an agent
Apple privacy permissions (TCC) are the #1 cause of "it works in my terminal but not from
the agent" failures.
**The grant keys on Pippin's identity and binary path, not on the launcher.** As of
**v0.31.0** pippin re-execs itself "disclaimed" so it is its own macOS TCC *responsible
process*. macOS therefore keys Reminders/Calendar/Contacts/Automation/Full-Disk consent on
pippin's signed identity at its binary path, **regardless of which app launched it**
(Terminal, Codex, a background MCP gateway, launchd). Grant pippin once and it works under
every launcher.
Two consequences that bite agents:
- **Grants are per binary PATH (bare CLI, not a `.app`).** `/opt/homebrew/bin/pippin`
resolves to a *versioned* `Cellar//…` path, so a brew grant is **lost on every
`brew upgrade`**. The durable, granted home is the **stable** `~/.local/bin/pippin`
(a copied real file at a fixed path; the grant survives rebuilds). **Agents and
scheduled tasks should invoke `~/.local/bin/pippin` explicitly**, never the brew symlink.
- **The prompt only appears in an interactive session.** `pippin permissions` (or
`pippin init`) triggers every promptable permission in one pass, but only from a real
TTY. Under MCP, `--format agent/json`, or a non-TTY pipe it **refuses to prompt** (an
unanswerable dialog would just hang) and prints a read-only report instead, and EventKit
commands fast-fail with `access_denied` rather than blocking.
**So the one-time setup for durable agent access:** run `pippin permissions` against
`~/.local/bin/pippin` **once in a Terminal** and approve the prompts. After that, that path
works from any launcher. (`make install` refreshes the binary at the same path without
losing the grant.)
- **Check state any time** without prompting: `pippin permissions --status --format agent`
or `pippin doctor`: each integration reports `granted` / `not_determined` /
`manual_required` + whether it's promptable.
- **Voice Memos and Messages need Full Disk Access**, which has *no* prompt: grant it
manually in System Settings ▸ Privacy & Security ▸ Full Disk Access to the pippin binary,
then relaunch.
When a structured call fails with `error.code == "access_denied"`, surface
`error.remediation` to the user verbatim: it names the exact System Settings pane and the
launcher to enable.
## Quick reference
```bash
# Mail (Mail.app must be running)
pippin mail accounts --format agent
pippin mail list [--account NAME] [--mailbox INBOX] [--unread] [--limit 20] [--fields id,subject,from] --format agent
pippin mail search [--account NAME] [--after YYYY-MM-DD] [--body] [--limit 10] [--fields id,subject] --format agent
pippin mail activity [--since YYYY-MM-DD] [--fields id,from] --format agent
pippin mail show --format agent # id is account||mailbox||numericId
pippin mail send --to --subject --body
pippin mail mark --read|--unread
pippin mail move --to # Trash, Junk, Sent, Drafts
pippin mail apply-rules [--account NAME] [--live] # bulk move/mark by rule; PREVIEWS unless --live
# Calendar
pippin calendar today|remaining|upcoming [--fields id,title,startDate] --format agent
pippin calendar events [--from YYYY-MM-DD] [--to YYYY-MM-DD] [--range today|week|month] --format agent
pippin calendar search --query [--fields id,title] --format agent
pippin calendar create --title --start [--end ] [--notes ] [--alert 15m]
pippin calendar show --format agent
# Reminders (note: title is POSITIONAL; --list takes an EventKit ID from `reminders lists`)
pippin reminders list [--completed] [--priority high|medium|low] [--fields id,title,dueDate] --format agent
pippin reminders create [--due YYYY-MM-DD] [--priority high|medium|low] [--notes ]
pippin reminders complete
pippin reminders search --format agent
# Notes
pippin notes list [--folder NAME] [--limit 50] [--fields id,title] --format agent
pippin notes show --format agent # agent mode returns plainText, not HTML body
pippin notes search --format agent
pippin notes create [--body ] [--folder NAME]
pippin notes edit [--body ] [--append]
# Contacts (--fields here selects which CN keys are *fetched* — server-side)
pippin contacts list [--group NAME] [--fields id,fullName,emails] --format agent
pippin contacts search --format agent
pippin contacts show --format agent
# Voice Memos / Messages (require Full Disk Access)
pippin memos list [--since YYYY-MM-DD] [--limit 20] --format agent
pippin memos transcribe
# Audio / Browser (gated behind PIPPIN_EXPERIMENTAL=1)
pippin audio speak [--voice af_heart] [--output-file path.wav]
pippin browser open --format agent ; pippin browser snapshot --format agent
# System / diagnostics
pippin status --format agent # whole-system dashboard (broad — may be partial)
pippin permissions [--status] [--format agent] # grant (interactive) or report TCC state
pippin doctor # permissions + dependency health
pippin agent-info --format agent # capability/feature probe
pippin --version
```
## Critical gotchas
1. **Mail.app must be open** for any `pippin mail` command: `open -a Mail && sleep 4`.
2. **Locked screen blocks all GUI automation** (pippin, osascript, screencapture). Check
idle: `ioreg -n IOHIDSystem | grep HIDIdleTime` (÷ 1e9 for seconds).
3. **Compound mail IDs** use `account||mailbox||numericId`. Always pass the full ID to
`show`/`mark`/`move`.
4. **Reminders/Calendar flag footguns**: `reminders create`'s title is *positional*;
`reminders --list` / `calendar create --calendar` take EventKit **IDs** (from
`reminders lists` / `calendar list`), not names. Filter calendar events by name with
`--calendar-name`.
5. **Permissions**: see the TCC section above: most "denied from the agent" issues are the
launcher/responsible-process mismatch, not a pippin bug.
6. **Audio/browser are experimental**: hidden unless `PIPPIN_EXPERIMENTAL=1`; require
mlx-audio / node respectively (`pippin doctor` checks).
## References
- Full command syntax: `references/commands.md`
- Output envelope & formats: `references/output-formats.md`
- Multi-step workflow patterns: `references/agent-patterns.md`