From pippin
`pippin` is a macOS CLI that automates Apple's native apps (Mail, Calendar, Reminders,
How this skill is triggered — by the user, by Claude, or both
Slash command
/pippin:pippin-cliThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
`pippin` is a macOS CLI that automates Apple's native apps (Mail, Calendar, Reminders,
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 Hermes/Talia gateway
(stdio, pointed at ~/.local/bin/pippin mcp-server) and Claude Cowork (via the
pippin@mw-plugins plugin's .mcp.json). Any agent without the MCP attached falls back
to the CLI — see "How to call pippin" below.
Use this skill when the user asks to:
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.
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.
CLI fallback — when no pippin MCP server is attached (Hermes/Talia 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:
~/.local/bin/pippin <area> <verb> … --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.
These are the difference between fast, cheap calls and slow, token-heavy ones:
--format agent when you (not a human) consume the output. Compact
JSON in a versioned envelope. Never parse text output.--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.--limit and the narrowest filters you can (--account,
--mailbox, --folder, --after/--before, --range). Broad/unbounded queries are
what hit soft timeouts.pippin status gathers every subsystem;
if you only need mail, call pippin mail list. Narrow calls are faster and never
partial.Every --format agent response is wrapped in a versioned envelope. Parse .data, not
the top level.
Success:
{"v":1,"status":"ok","duration_ms":142,"data":<payload>,"warnings":["…"]?}
Error:
{"v":1,"status":"error","duration_ms":51,"error":{"code":"access_denied","message":"…","remediation":{…}}}
data unchanged — jq '.data | length',
jq '.data[].id', etc.warnings (optional) carries non-fatal advisories such as partial-results notices.status before reading data. On error, read error.code
(stable, snake_case) and error.remediation for the fix.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.
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.
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 own identity + 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:
.app). /opt/homebrew/bin/pippin
resolves to a versioned Cellar/<ver>/… 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.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.)
pippin permissions --status --format agent
or pippin doctor — each integration reports granted / not_determined /
manual_required + whether it's promptable.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.
# 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 <query> [--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 <id> --format agent # id is account||mailbox||numericId
pippin mail send --to <email> --subject <s> --body <t>
pippin mail mark <id> --read|--unread
pippin mail move <id> --to <mailbox> # Trash, Junk, Sent, Drafts
# 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 <q> [--fields id,title] --format agent
pippin calendar create --title <t> --start <date> [--end <date>] [--notes <n>] [--alert 15m]
pippin calendar show <id> --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 <title> [--due YYYY-MM-DD] [--priority high|medium|low] [--notes <n>]
pippin reminders complete <id>
pippin reminders search <query> --format agent
# Notes
pippin notes list [--folder NAME] [--limit 50] [--fields id,title] --format agent
pippin notes show <id> --format agent # agent mode returns plainText, not HTML body
pippin notes search <query> --format agent
pippin notes create <title> [--body <text>] [--folder NAME]
pippin notes edit <id> [--body <text>] [--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 <query> --format agent
pippin contacts show <id> --format agent
# Voice Memos / Messages (require Full Disk Access)
pippin memos list [--since YYYY-MM-DD] [--limit 20] --format agent
pippin memos transcribe <id>
# Audio / Browser (gated behind PIPPIN_EXPERIMENTAL=1)
pippin audio speak <text> [--voice af_heart] [--output-file path.wav]
pippin browser open <url> --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
pippin mail command: open -a Mail && sleep 4.ioreg -n IOHIDSystem | grep HIDIdleTime (÷ 1e9 for seconds).account||mailbox||numericId — always pass the full ID to
show/mark/move.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.PIPPIN_EXPERIMENTAL=1; require
mlx-audio / node respectively (pippin doctor checks).references/commands.mdreferences/output-formats.mdreferences/agent-patterns.mdGuides 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 mattwag05/mw-plugins --plugin pippin