peekaboo app
app bundles every app-management primitive Peekaboo exposes: launching, quitting, hiding, relaunching, switching/focusing, and listing processes. Commands run through the selected Peekaboo runtime host so they share its macOS session, LaunchServices, and AX view instead of the caller's sandbox.
#Subcommands
| Name | Purpose | Key flags |
|---|---|---|
launch | Verify an exact running app in the background, or explicitly start/open it in the foreground. | --bundle-id, --open <path|url> (repeatable), --new-instance, --wait-ready, --wait-for-window, --foreground. |
quit | Quit one app or all regular apps (with optional exclusions). | Positional <app> or --app, --pid, --expected-process-start-identity, --all, --except "Finder,Terminal", --force. |
relaunch | Quit + relaunch the same app with explicit foreground consent. | Positional <app> or --app, or --pid; --wait, --force, --wait-until-ready, --foreground (required). |
hide / unhide | Hide an app, or unhide and activate it with explicit consent. | Positional <app> or --app, or --pid; unhide requires --activate. |
switch | Activate a specific app or cycle Cmd+Tab style. | Positional <app> or --to, --cycle, --verify (only with an app target). |
focus | Activate and focus an app through the same service path as the MCP app tool. | Positional <app> or --app, or --pid. |
list | App-management view of running apps, filtering hidden/background apps by default. | --include-hidden, --include-background. |
#Implementation notes
- Launch resolves explicit paths, bundle IDs, PID selectors, and friendly names on the selected runtime host. Without
--foreground, it may only return an exact already-running app as a verified no-op; it may resolve the application URL but never dispatches a LaunchServices open/start. Cold launch,--open,--new-instance, and relaunch refuse before dispatch because macOS does not provide a trustworthy nonactivation guarantee. These refusals reportINTERACTION_FAILED,effect: refused,retry_safe: true, andmutation_dispatched: false, with explicit foreground guidance. Background launch also requires a host that advertises this exact no-op contract, so a rolling upgrade cannot delegate to an older host that would cold-launch. The deprecated--no-focusflag remains a no-op compatibility alias. - Background no-op
launch --wait-readyand--wait-for-windowretain the selected PID/process-generation receipt throughout their read-only waits. A readiness failure remains explicitly retry-safe withmutation_dispatched: false. APID:selector stays pinned to that exact process generation for both the no-op and a plain foreground activation; it cannot be combined with--openor--new-instance. Foreground launch keeps the full existing LaunchServices behavior for path/name/bundle selectors: it can start windowless/accessory apps, deliver documents/URLs, create a distinct process, and wait up to 10 seconds for a real WindowServer window.relaunchretains its single--wait-until-readyspelling and requires--foregroundbefore the target is resolved or quit. - JSON launch output returns the launch-bound numeric compatibility field
process_start_identityplus the lossless authoritative stringprocess_start_identity_decimalbesidepid, along with refreshedwindow_count,window_ready, andwindow_ids. Relaunch usesnew_process_start_identityand authoritativenew_process_start_identity_decimal. JSON-number consumers must not use the numeric forms for exact comparison because values above 2^53 can lose precision. A current native host captures that process generation from the exact process selected by LaunchServices and refuses the result if the PID is recycled before return. Older runtime hosts may omit the process identity for foreground launch, but background launch fails closed unless the host advertises the safe no-op contract; cleanup callers must never probe a returned PID to manufacture a new receipt.window_identityisexactwhen the window IDs came from WindowServer andunknownfor an older runtime host that cannot provide that metadata. - MCP app lifecycle and focus results expose the same generation through
target_identity.kind: processandtarget_identity.process_start_identity_decimal. Agents must chain that target identity; the generic numericprocess_start_identitymetadata is compatibility-only and is intentionally not exported as authoritative safety metadata. - Quit mode supports
--allplus--except, automatically ignoring core system processes (Finder,Dock,SystemUIServer,WindowServer). Bulk quit targets only generation-pinned applications whose bounded metadata explicitly classifies them as regular; accessory, prohibited, and incomplete rows are never treated as regular by default. Controlled cleanup can pair--pidwith the lossless unsigned-decimal--expected-process-start-identity(including the full UInt64 range); Peekaboo atomically rejects a recycled PID instead of terminating its replacement. Each JSON result publishes the frozen target plan aspidplus authoritativeprocess_start_identity_decimal. When quits fail, the command prints hints about unsaved changes and suggests--force. - Hide remains background-capable. Unhide requires
--activatebefore runtime-host resolution and carries the selected PID/process-generation receipt through verified activation because showing an application's windows can move them in front. Hosts that cannot enforce the receipt are rejected, and the legacy identifier-only Bridge unhide operation is refused. switch --cyclesynthesizes Cmd+Tab events usingCGEventso it behaves like the real keyboard shortcut;switch --toactivates the exact PID resolved via AX.- App activation is successful only after the exact resolved PID reports active and Workspace-frontmost. When the
- CLI and MCP focus/switch/unhide operations never reduce a selected application to a bare PID or name before activation; the runtime host rechecks the original process-generation receipt immediately before and after native activation.
switch --verifyperforms an additional command-level confirmation after the shared verified activation path (not- Supply one selector shape. Launch rejects a positional app combined with
--bundle-id; app lifecycle commands reject a textual--appcombined with--pid. A redundant--app PID:123 --pid 123pair is accepted only when both PIDs match. - With
--foreground,relaunchsends the initially selected PID/process-generation receipt, quit, termination polling (up to 5 s), the requested delay, and launch as one daemon-held transaction, so even a short daemon idle timeout cannot strand the app closed. The host rejects PID reuse before quit, refuses to relaunch its own daemon, launches via bundle ID or bundle path, can wait forisFinishedLaunching, and returns authoritativeprevious_process_start_identity_decimalandnew_process_start_identity_decimalgenerations for race-free follow-up cleanup. app listfilters hidden/background apps unless--include-hiddenor--include-backgroundis passed and emits its establisheddata.appspayload. Inventory snapshots WindowServer once, then reads LaunchServices metadata on at most eight generation-scoped per-process lanes with a 250 ms per-process and one-second overall bound, so one wedged hidden app or a broader stall cannot hold the whole Bridge request. Repeated reads coalesce behind a still-blocked process generation instead of growing an expired queue. A timed-out or deadline-skipped row retains its exact PID/window IDs, carriesmetadata_warnings, and omitsis_hiddenrather than guessing; use both inclusive flags to retain rows whose hidden state and activation policy are unknown. Top-levelwarningsmakes a partial result visible in JSON and text output. Each current native process generation is available asprocess_start_identityplus the lossless canonical stringprocess_start_identity_decimal; shell/JSON-number consumers must use the decimal string for exact comparison and treat missing values from older hosts as unknown. The result'sschema_capabilitiesarray advertisesprocessStartIdentityDecimaleven whenappsis empty, so installers can require the lossless receipt contract without inferring CLI capability from ambient processes.
target owns visible ordinary windows, the frontmost WindowServer window must also belong to that PID. Peekaboo first uses native application activation, then falls back to the application's AX frontmost attribute when macOS accepts the request without completing it. Multi-window apps activate all of their windows; use window focus when one specific window must become key.
supported with --cycle).
#Examples
# Verify an already-running Xcode generation without dispatching a launch
peekaboo app launch "Xcode" --wait-ready
# Open a project with explicit foreground consent
peekaboo app launch "Xcode" --open ~/Projects/Peekaboo.xcodeproj --foreground
# Start an independent TextEdit process with explicit foreground consent
peekaboo app launch "TextEdit" --new-instance --wait-for-window --foreground
# Explicitly activate Safari after launching it
peekaboo app launch "Safari" --foreground
# Unhide and activate one exact app
peekaboo app unhide TextEdit --activate
# Quit everything but Finder and Terminal
peekaboo app quit --all --except "Finder,Terminal"
# Quit one app positionally
peekaboo app quit TextEdit
# Atomically quit only the saved process generation
peekaboo app quit --pid 1234 --expected-process-start-identity 987654321 --force
# Cycle to the next app exactly once
peekaboo app switch --cycle
# Switch and verify the app is frontmost
peekaboo app switch Safari --verify
# Focus an app without a separate launch
peekaboo app focus Safari
#Troubleshooting
- Verify Screen Recording + Accessibility permissions (
peekaboo permissions status). - Confirm your target with
peekaboo app list,peekaboo window list, orpeekaboo seebefore rerunning. - Re-run with
--jsonor--verboseto surface detailed errors.