Skip to content
Launch Pilotv0.3.1

v0.3.1 · MIT · macOS

Every launchd job on your Mac,
in one browser tab.

Launch Pilot is a single Go binary that turns launchctl into a live browser console. Six time-aware statuses, per-job log tail, six diagnostic checks. Binds to 127.0.0.1; no daemon, no telemetry, no cloud.

$brew install RoboZephyr/tap/launch-pilot
runtimeLocal · 127.0.0.1 onlyscopeuser agents · system daemonsdatalaunchctl + plist · zero telemetry
127.0.0.1:54231 · Launch Pilot
SSE · live
All (7)MineSystem3rd-partysearch
Running3Scheduled1Completed1Error1Offline1
  • com.docker.vmnetdpid 8493d 12h
  • homebrew.mxcl.postgresql@16pid 36111d 02h
  • com.tailscale.tailscaledpid 120414h 03m
  • com.user.nightly-backuperrorexit 24 · 03:12
  • com.user.weekly-cleanupschedulednext · Sun 03:00
  • homebrew.autoupdatecompletedran 6m ago
  • com.zsa.wally.daemonofflineplist present
GET /api/eventstext/event-stream5s push

Statuses

Six values, derived from the raw launchctl table.

Most tools collapse to running / stopped / error. Launch Pilot keeps the distinctions launchctl actually exposes — so you can tell a job that's waiting for its schedule from a job that's missing in action.

Running

PID > 0

Scheduled

PID = 0, clean exit, schedule defined, no recent log

Completed

PID = 0, clean exit, log mtime inside --recent-window

Stopped

PID = 0, clean exit, no schedule, no recent log

Error

Last exit status non-zero

Offline

plist file present, launchctl list does not return label

Diagnostics

Six read-only checks per job.

When a job is failing, the diagnose endpoint walks the most common breakage modes and gives you a typed answer — not a wall of stderr.

Exit CodeDecodes the last exit status (e.g. 127 = command not found, 139 = segfault).
Program ExistsThe executable path in the plist actually exists on disk.
Program ExecutableThe file has the execute bit set for the loading user.
Plist OwnerThe plist is owned by the current user (no cross-user surprises).
Plist PermissionsNo group / world write bits — launchd will silently refuse otherwise.
Log PathParent directories for stdout / stderr log paths exist and are writable.

API

Everything the UI uses, also available to your scripts.

The web UI is one consumer of /api/jobs and /api/events. So is curl. Bind the server, point at the endpoints, parse the JSON.

verbpathdescription
GET/api/jobsList every job, including offline plists
GET/api/jobs/{label}Single-job snapshot
POST/api/jobs/{label}/startlaunchctl kickstart
POST/api/jobs/{label}/stoplaunchctl kill SIGTERM
POST/api/jobs/{label}/reloadlaunchctl bootout + bootstrap
GET/api/jobs/{label}/logs?lines=200Tail stdout + stderr (max 10,000)
GET/api/jobs/{label}/diagnoseRun the six health checks
GET/api/eventsSSE stream — full job list pushed every 5s

Install

One brew install. One binary. One tab.

The Homebrew tap is public; the formula is auditable. The binary is a single static Go executable with the Preact frontend embedded via go:embed — nothing else to install or serve.

01 · install
$ brew install RoboZephyr/tap/launch-pilot

Adds the public tap and pours the formula. Both arm64 and amd64 are built per release.

02 · run
$ launch-pilot

Picks a free port on 127.0.0.1 and opens your default browser. Pin with --port, skip the browser with --no-open.

03 · investigate

Filter by category (Mine / System / 3rd-party), drill into a job to see status reasoning, tail logs up to 10,000 lines, run the six diagnostic checks, and start / stop / reload via launchctl — no terminal context-switch.