Peekaboo
CLI timing flags accept bare milliseconds or ms/s suffixes, for example 500, 500ms, 2s, or 1.5s. Coordinate input uses --at x,y; add --global when a targeted coordinate should remain screen-global.
Peekaboo is a macOS automation CLI and agent runtime. Prefer the freshly built repo binary, live help, and canonical docs over copied command references because command surfaces move quickly.
Peekaboo 4 uses press for standalone keys and chords, drag --from/--to for drags, see --tree for CLI AX inspection, action for named accessibility actions, and real noun/verb subcommand trees. Use verify for stable predicates instead of fixed sleeps. JSON action results include an effect value; read-only results omit it. See docs/v4-migration.md when converting older invocations.
Start Here
- In repo work, build and use the current-source binary:
pnpm run build:cli
BIN="$(swift build --package-path Apps/CLI --show-bin-path)/peekaboo"
"$BIN" --version
- Record the installed PATH binary separately when diagnosing environment drift:
command -v peekaboo && peekaboo --version
- Confirm permissions and current tool surfaces before automation:
"$BIN" permissions status --json
"$BIN" tools --json
"$BIN" learn
- Find command docs:
node scripts/docs-list.mjs
Canonical References
- Live CLI help:
peekaboo <command> --help
- Full agent guide:
peekaboo learn
- Tool catalog:
peekaboo tools
- Command docs in this repo:
docs/commands/README.md and docs/commands/*.md
- Permissions and bridge behavior:
docs/permissions.md, docs/bridge-host.md, docs/integrations/subprocess.md
- Repo rules:
AGENTS.md
Observation Strategy
- Use
peekaboo see --tree --no-screenshot (CLI) or inspect_ui (MCP) for native macOS AX text, labels, buttons, text fields, control state, and element IDs when a screenshot would add noise.
- Use
peekaboo see for screenshots, visual layout, annotated maps, pixels, colors, screen/menu-bar targets, or cases where AX text is missing or incomplete.
- Use
browser for browser page content, forms, DOM/a11y snapshots, console, network, page screenshots, and performance traces when browser tooling is available.
- Use native Peekaboo tools for app chrome, browser toolbars, menus, dialogs, permissions, windows, and non-browser apps.
- Treat element IDs from
see or inspect_ui as valid only for the current visible state. After a mutating action, verify from the action result or fetch fresh state.
Operating Rules
- Use
peekaboo see --json --path /tmp/<name>.png or peekaboo see --tree --no-screenshot --json before element interactions so you have fresh element IDs and snapshot IDs.
- Prefer the exact element ID string from the current snapshot for clicks and typing; treat ID shapes as opaque. Use labels when IDs are unavailable and coordinates only as a last resort.
- Check
peekaboo permissions status --json before assuming a capture or control failure is a CLI bug.
- Use
--json when another tool or agent needs to parse results.
- Respect the user's desktop: avoid destructive app/window actions unless requested.
- If a command fails because the target UI changed, recapture with
see before retrying.
see --json element bounds are screen coordinates. Snapshot IDs keep element actions tied to the observed UI.
- When using
see in agent smoke tests, pass an explicit /tmp --path so capture artifacts do not land in a user-visible default location.
- Prefer
--foreground only when an app requires a key window, Space switch, or foreground mouse event. Background delivery is the default when Peekaboo can resolve a target process.
Common Workflows
# AX-only state when a screenshot would add noise.
peekaboo see --tree --no-screenshot --app Calculator --json > /tmp/peekaboo-calc-ax.json
# Visual layout plus element IDs and snapshot ID.
peekaboo see --app Calculator --path /tmp/calc.png --json > /tmp/calc.json
ruby -rjson -e 'j=JSON.parse(File.read("/tmp/calc.json")); puts j.dig("data","snapshot_id"); puts JSON.pretty_generate((j.dig("data","ui_elements")||[]).map{|e| e.slice("id","label","identifier","bounds")})'
# Click an element discovered in the current snapshot.
SNAP=$(ruby -rjson -e 'j=JSON.parse(File.read("/tmp/calc.json")); puts j.dig("data","snapshot_id")')
ELEMENT_ID="<element-id-from-current-snapshot>"
peekaboo click --on "$ELEMENT_ID" --snapshot "$SNAP" --json
# Browser page content and DOM-oriented actions belong to browser tooling.
peekaboo browser status --json
# Browser toolbar, menus, permission prompts, and native app chrome still belong to Peekaboo.
peekaboo menu list --app Safari --json
# Routine management examples stay read-only by default.
peekaboo clipboard get --json
peekaboo permissions status --all-sources --json
peekaboo app list --include-hidden --include-background --json
peekaboo config provider list --json
peekaboo agent sessions --json
peekaboo press cmd+shift+t --app Safari --window-id 12345
peekaboo verify --app Safari --window-exists --timeout 2s --json
Input Path Testing
Peekaboo has two broad input paths:
- UIAX/action path: accessibility actions such as
AXPress, AXSetValue.
- Synthetic path: pointer/keyboard events, commonly the CAEvent/CGEvent-style path.
Useful overrides:
# Confirm command exposes the override.
peekaboo click --help | rg 'input-strategy|actionOnly|synthOnly'
# UIAX/action click path from a saved snapshot.
peekaboo see --app Calculator --path /tmp/calc.png --json > /tmp/calc.json
SNAP=$(ruby -rjson -e 'j=JSON.parse(File.read("/tmp/calc.json")); puts j.dig("data","snapshot_id")')
ELEMENT_ID="<element-id-from-current-snapshot>"
peekaboo click --on "$ELEMENT_ID" --snapshot "$SNAP" --input-strategy actionOnly --json --focus-background
# Direct accessibility action; good for proving UIAX independent of pointer events.
peekaboo action AXPress --on "$ELEMENT_ID" --snapshot "$SNAP" --json
# Synthetic click path; allow focus if you need visible app state to mutate.
peekaboo click --on "$ELEMENT_ID" --snapshot "$SNAP" --input-strategy synthOnly --json --foreground
# Negative control: coordinates cannot use actionOnly.
peekaboo click --at 10,10 --input-strategy actionOnly --json
Interpretation:
actionOnly success proves live AX re-resolution and action invocation.
synthOnly success proves coordinate resolution and event delivery, but verify app state independently.
action AXPress is the cleanest UIAX smoke test.
- Compare with Computer Use or another AX inspector when labels/descriptions differ.
Calculator Smoke Test
Calculator is a handy fixture because it exposes descriptions and identifiers.
BIN="$(swift build --package-path Apps/CLI --show-bin-path)/peekaboo"
"$BIN" permissions status --json > /tmp/peekaboo-skill-refresh-permissions.json
"$BIN" tools --json > /tmp/peekaboo-skill-refresh-tools.json
"$BIN" see --tree --no-screenshot --app Calculator --json > /tmp/peekaboo-skill-refresh-calc-ax.json
"$BIN" see --app Calculator --path /tmp/peekaboo-skill-refresh-calc.png --json --timeout 10s > /tmp/calc.json
ruby -rjson -e 'j=JSON.parse(File.read("/tmp/calc.json")); puts JSON.pretty_generate((j.dig("data","ui_elements")||[]).select{|e| ["Clear","AllClear","One","Two","Add","Equals","StandardInputView"].include?(e["identifier"].to_s)}.map{|e| e.slice("id","label","identifier","description","help","bounds")})'
SNAP=$(ruby -rjson -e 'j=JSON.parse(File.read("/tmp/calc.json")); puts j.dig("data","snapshot_id")')
BUTTON_ID="<button-id-from-current-snapshot>"
"$BIN" action AXPress --on "$BUTTON_ID" --snapshot "$SNAP" --json
"$BIN" click --on "$BUTTON_ID" --snapshot "$SNAP" --input-strategy actionOnly --json --focus-background
Expected current behavior:
see --json includes bounds for each ui_elements entry.
see --tree --no-screenshot --json and see --json should expose element IDs plus Calculator identifiers such as One, Two, and StandardInputView. Copy the returned ID exactly instead of assuming a prefix or shape.
tools --json should include browser, click, inspect_ui, and see.
- Snapshot-backed UIAX must use the captured app/window, not the frontmost app.
Repo Validation
node scripts/docs-lint.mjs
ruby -e 'h=File.read("skills/peekaboo/SKILL.md").split(/^---\s*$/,3)[1]; keys=h.lines.grep(/^[A-Za-z0-9_-]+:/).map { |line| line.split(":",2).first }; abort("frontmatter keys: #{keys.inspect}") unless keys.sort == ["description","name"]'
! rg -n 'elem_[0-9]+' skills/peekaboo/SKILL.md
! rg -n '^allowed-tools:' skills/peekaboo/SKILL.md
pnpm run build:cli
BIN="$(swift build --package-path Apps/CLI --show-bin-path)/peekaboo"; "$BIN" --version
"$BIN" click --help | rg -- '--foreground|--focus-background|--input-strategy|Opaque element ID'
"$BIN" see --help | rg -- '--json|--annotate|--app|--no-web-focus'
"$BIN" see --help | rg '\-\-tree|\-\-no-screenshot|\-\-app|\-\-json'
git diff --check -- skills/peekaboo/SKILL.md docs/agent-skill.md docs/commands/see.md docs/automation.md scripts/docs-lint.mjs
Notes:
- If tests fail with
no such module 'Testing', record it as local toolchain fallout; still run builds/lint/live smoke tests.
- SwiftPM may warn about Commander identity conflicts; do not chase unless the task is dependency hygiene.
- Keep live validation artifacts under
/tmp/peekaboo-skill-refresh-* and do not commit or publish screenshots or UI dumps.
Keep this skill compact. Do not vendor generated command references here; update canonical CLI docs or Commander metadata instead.
1---2name: peekaboo-23description: Use Peekaboo for macOS desktop automation, screenshots, visual UI maps, native accessibility inspection, app/window/menu/dialog control, native app and browser chrome control, browser-page MCP tooling, MCP diagnostics, and Peekaboo repo validation. Use when Codex needs current macOS UI state, direct desktop control, privacy-aware live smoke tests, or changes to the Peekaboo repository.4---56# Peekaboo78CLI timing flags accept bare milliseconds or `ms`/`s` suffixes, for example `500`, `500ms`, `2s`, or `1.5s`. Coordinate input uses `--at x,y`; add `--global` when a targeted coordinate should remain screen-global.910Peekaboo is a macOS automation CLI and agent runtime. Prefer the freshly built repo binary, live help, and canonical docs over copied command references because command surfaces move quickly.1112Peekaboo 4 uses `press` for standalone keys and chords, `drag --from/--to` for drags, `see --tree` for CLI AX inspection, `action` for named accessibility actions, and real noun/verb subcommand trees. Use `verify` for stable predicates instead of fixed sleeps. JSON action results include an `effect` value; read-only results omit it. See `docs/v4-migration.md` when converting older invocations.1314## Start Here15161. In repo work, build and use the current-source binary:17 ```bash18 pnpm run build:cli19 BIN="$(swift build --package-path Apps/CLI --show-bin-path)/peekaboo"20 "$BIN" --version21 ```222. Record the installed PATH binary separately when diagnosing environment drift:23 ```bash24 command -v peekaboo && peekaboo --version25 ```263. Confirm permissions and current tool surfaces before automation:27 ```bash28 "$BIN" permissions status --json29 "$BIN" tools --json30 "$BIN" learn31 ```324. Find command docs:33 ```bash34 node scripts/docs-list.mjs35 ```3637## Canonical References3839- Live CLI help: `peekaboo <command> --help`40- Full agent guide: `peekaboo learn`41- Tool catalog: `peekaboo tools`42- Command docs in this repo: `docs/commands/README.md` and `docs/commands/*.md`43- Permissions and bridge behavior: `docs/permissions.md`, `docs/bridge-host.md`, `docs/integrations/subprocess.md`44- Repo rules: `AGENTS.md`4546## Observation Strategy4748- Use `peekaboo see --tree --no-screenshot` (CLI) or `inspect_ui` (MCP) for native macOS AX text, labels, buttons, text fields, control state, and element IDs when a screenshot would add noise.49- Use `peekaboo see` for screenshots, visual layout, annotated maps, pixels, colors, screen/menu-bar targets, or cases where AX text is missing or incomplete.50- Use `browser` for browser page content, forms, DOM/a11y snapshots, console, network, page screenshots, and performance traces when browser tooling is available.51- Use native Peekaboo tools for app chrome, browser toolbars, menus, dialogs, permissions, windows, and non-browser apps.52- Treat element IDs from `see` or `inspect_ui` as valid only for the current visible state. After a mutating action, verify from the action result or fetch fresh state.5354## Operating Rules5556- Use `peekaboo see --json --path /tmp/<name>.png` or `peekaboo see --tree --no-screenshot --json` before element interactions so you have fresh element IDs and snapshot IDs.57- Prefer the exact element ID string from the current snapshot for clicks and typing; treat ID shapes as opaque. Use labels when IDs are unavailable and coordinates only as a last resort.58- Check `peekaboo permissions status --json` before assuming a capture or control failure is a CLI bug.59- Use `--json` when another tool or agent needs to parse results.60- Respect the user's desktop: avoid destructive app/window actions unless requested.61- If a command fails because the target UI changed, recapture with `see` before retrying.62- `see --json` element bounds are screen coordinates. Snapshot IDs keep element actions tied to the observed UI.63- When using `see` in agent smoke tests, pass an explicit `/tmp` `--path` so capture artifacts do not land in a user-visible default location.64- Prefer `--foreground` only when an app requires a key window, Space switch, or foreground mouse event. Background delivery is the default when Peekaboo can resolve a target process.6566## Common Workflows6768```bash69# AX-only state when a screenshot would add noise.70peekaboo see --tree --no-screenshot --app Calculator --json > /tmp/peekaboo-calc-ax.json7172# Visual layout plus element IDs and snapshot ID.73peekaboo see --app Calculator --path /tmp/calc.png --json > /tmp/calc.json74ruby -rjson -e 'j=JSON.parse(File.read("/tmp/calc.json")); puts j.dig("data","snapshot_id"); puts JSON.pretty_generate((j.dig("data","ui_elements")||[]).map{|e| e.slice("id","label","identifier","bounds")})'7576# Click an element discovered in the current snapshot.77SNAP=$(ruby -rjson -e 'j=JSON.parse(File.read("/tmp/calc.json")); puts j.dig("data","snapshot_id")')78ELEMENT_ID="<element-id-from-current-snapshot>"79peekaboo click --on "$ELEMENT_ID" --snapshot "$SNAP" --json8081# Browser page content and DOM-oriented actions belong to browser tooling.82peekaboo browser status --json8384# Browser toolbar, menus, permission prompts, and native app chrome still belong to Peekaboo.85peekaboo menu list --app Safari --json8687# Routine management examples stay read-only by default.88peekaboo clipboard get --json89peekaboo permissions status --all-sources --json90peekaboo app list --include-hidden --include-background --json91peekaboo config provider list --json92peekaboo agent sessions --json93peekaboo press cmd+shift+t --app Safari --window-id 1234594peekaboo verify --app Safari --window-exists --timeout 2s --json95```9697## Input Path Testing9899Peekaboo has two broad input paths:100101- UIAX/action path: accessibility actions such as `AXPress`, `AXSetValue`.102- Synthetic path: pointer/keyboard events, commonly the CAEvent/CGEvent-style path.103104Useful overrides:105106```bash107# Confirm command exposes the override.108peekaboo click --help | rg 'input-strategy|actionOnly|synthOnly'109110# UIAX/action click path from a saved snapshot.111peekaboo see --app Calculator --path /tmp/calc.png --json > /tmp/calc.json112SNAP=$(ruby -rjson -e 'j=JSON.parse(File.read("/tmp/calc.json")); puts j.dig("data","snapshot_id")')113ELEMENT_ID="<element-id-from-current-snapshot>"114peekaboo click --on "$ELEMENT_ID" --snapshot "$SNAP" --input-strategy actionOnly --json --focus-background115116# Direct accessibility action; good for proving UIAX independent of pointer events.117peekaboo action AXPress --on "$ELEMENT_ID" --snapshot "$SNAP" --json118119# Synthetic click path; allow focus if you need visible app state to mutate.120peekaboo click --on "$ELEMENT_ID" --snapshot "$SNAP" --input-strategy synthOnly --json --foreground121122# Negative control: coordinates cannot use actionOnly.123peekaboo click --at 10,10 --input-strategy actionOnly --json124```125126Interpretation:127128- `actionOnly` success proves live AX re-resolution and action invocation.129- `synthOnly` success proves coordinate resolution and event delivery, but verify app state independently.130- `action AXPress` is the cleanest UIAX smoke test.131- Compare with Computer Use or another AX inspector when labels/descriptions differ.132133## Calculator Smoke Test134135Calculator is a handy fixture because it exposes descriptions and identifiers.136137```bash138BIN="$(swift build --package-path Apps/CLI --show-bin-path)/peekaboo"139"$BIN" permissions status --json > /tmp/peekaboo-skill-refresh-permissions.json140"$BIN" tools --json > /tmp/peekaboo-skill-refresh-tools.json141"$BIN" see --tree --no-screenshot --app Calculator --json > /tmp/peekaboo-skill-refresh-calc-ax.json142"$BIN" see --app Calculator --path /tmp/peekaboo-skill-refresh-calc.png --json --timeout 10s > /tmp/calc.json143ruby -rjson -e 'j=JSON.parse(File.read("/tmp/calc.json")); puts JSON.pretty_generate((j.dig("data","ui_elements")||[]).select{|e| ["Clear","AllClear","One","Two","Add","Equals","StandardInputView"].include?(e["identifier"].to_s)}.map{|e| e.slice("id","label","identifier","description","help","bounds")})'144145SNAP=$(ruby -rjson -e 'j=JSON.parse(File.read("/tmp/calc.json")); puts j.dig("data","snapshot_id")')146BUTTON_ID="<button-id-from-current-snapshot>"147"$BIN" action AXPress --on "$BUTTON_ID" --snapshot "$SNAP" --json148"$BIN" click --on "$BUTTON_ID" --snapshot "$SNAP" --input-strategy actionOnly --json --focus-background149```150151Expected current behavior:152153- `see --json` includes `bounds` for each `ui_elements` entry.154- `see --tree --no-screenshot --json` and `see --json` should expose element IDs plus Calculator identifiers such as `One`, `Two`, and `StandardInputView`. Copy the returned ID exactly instead of assuming a prefix or shape.155- `tools --json` should include `browser`, `click`, `inspect_ui`, and `see`.156- Snapshot-backed UIAX must use the captured app/window, not the frontmost app.157158## Repo Validation159160```bash161node scripts/docs-lint.mjs162ruby -e 'h=File.read("skills/peekaboo/SKILL.md").split(/^---\s*$/,3)[1]; keys=h.lines.grep(/^[A-Za-z0-9_-]+:/).map { |line| line.split(":",2).first }; abort("frontmatter keys: #{keys.inspect}") unless keys.sort == ["description","name"]'163! rg -n 'elem_[0-9]+' skills/peekaboo/SKILL.md164! rg -n '^allowed-tools:' skills/peekaboo/SKILL.md165pnpm run build:cli166BIN="$(swift build --package-path Apps/CLI --show-bin-path)/peekaboo"; "$BIN" --version167"$BIN" click --help | rg -- '--foreground|--focus-background|--input-strategy|Opaque element ID'168"$BIN" see --help | rg -- '--json|--annotate|--app|--no-web-focus'169"$BIN" see --help | rg '\-\-tree|\-\-no-screenshot|\-\-app|\-\-json'170git diff --check -- skills/peekaboo/SKILL.md docs/agent-skill.md docs/commands/see.md docs/automation.md scripts/docs-lint.mjs171```172173Notes:174175- If tests fail with `no such module 'Testing'`, record it as local toolchain fallout; still run builds/lint/live smoke tests.176- SwiftPM may warn about Commander identity conflicts; do not chase unless the task is dependency hygiene.177- Keep live validation artifacts under `/tmp/peekaboo-skill-refresh-*` and do not commit or publish screenshots or UI dumps.178179Keep this skill compact. Do not vendor generated command references here; update canonical CLI docs or Commander metadata instead.