Every dev server in one window — a native macOS control room for your local services.
Import a repo, click a script, and ServicePilot runs it: live logs with level
highlighting, split panes, a real terminal, and menu bar control. It reads the
scripts already in your package.json — no Procfile, no compose file, nothing
new to configure.
⬇︎ Download the latest .dmg — universal, runs on both Intel and Apple Silicon Macs.
-
Open the
.dmgand drag ServicePilot into Applications. -
First launch only. ServicePilot isn't signed with an Apple Developer ID yet, so macOS quarantines it. Clear the flag once — open Terminal and run:
xattr -cr /Applications/ServicePilot.app
Then double-click ServicePilot as normal. That's it — you only ever do this once.
Prefer not to use Terminal?
Double-click ServicePilot — macOS will block it. Then go to System Settings → Privacy & Security, scroll to the bottom, and click Open Anyway next to the ServicePilot message, then confirm with Open. (On older macOS you can instead right-click ServicePilot → Open → Open.)
None of this means ServicePilot is unsafe — it's the standard macOS gate for any app from a developer who hasn't paid for Apple's signing program yet. The source is right here if you'd like to read or build it yourself.
After installation, ServicePilot checks for updates when it opens. You can also check at any time from ServicePilot → Check for Updates…; available updates are downloaded, verified, installed, and activated with a relaunch.
- Repos & scripts — import any repo and ServicePilot lists every
package.jsonscript as a startable service. Start, stop and restart from the sidebar or the keyboard, rename scripts with aliases, and star the ones you use daily to filter the sidebar down to favourites. - Logs — a virtualised viewer with full ANSI colour, per-service buffers, and log-level detection that edge-lights lines red for errors and amber for warnings. Filter pills hide the levels you don't care about, and the viewer toolbar clears the current log.
- Split view —
⌘\tiles another log pane so you can watch the API and the frontend at the same time; each pane picks its own service.⌘⇧\closes the focused pane. - Terminal —
⌘`drops a real PTY terminal into the drawer, running your login shell in the repo you're looking at. - Menu bar — closing the window hides ServicePilot to the menu bar instead of quitting. The tray popover lists every service with a live status dot and start/stop/restart controls, driven by the main window so no second sidecar is spawned.
- Workflows — group the services that always come up together and launch the whole set in one click.
- Stray server discovery — a Background Servers section surfaces
node,bunanddenodev servers running outside ServicePilot, enriched with port, command, path and uptime. Kill them (SIGTERM, then SIGKILL), copy the path, reveal in Finder, or adopt them onto a matching imported repo. Anything ServicePilot owns is excluded by walking the parent-process tree. - Port detection — already-running services are matched to their scripts by port scan, so a server you started before opening the app still shows as up.
- Native — real macOS vibrancy, hidden title bar, glass panels. Tauri 2 and React 19, no Electron and no bundled Chromium.
1–9 |
switch to service N |
Tab |
cycle services |
Enter / s / r |
start / stop / restart |
⌘K |
focus service search |
⌘\ / ⌘⇧\ |
add / remove a split pane |
⌘` |
toggle the terminal |
q |
quit the focused service |
- Node.js 18+
- Bun — compiles the sidecar binary
- Rust and Cargo
- Tauri CLI v2 prerequisites
git clone https://github.com/benoneill66/ServicePilot.git
cd ServicePilot
npm install # root (sidecar) dependencies
cd desktop && npm install && cd ..
npm run app # build the sidecar, then launch in dev modeServicePilot/
├── sidecar/ # Bun/Node process manager (newline-JSON over stdio)
│ ├── main.ts # Entry point + command dispatch
│ ├── config.ts # Persistent config (~/.servicepilot/config.json)
│ ├── process-manager.ts
│ ├── discovery.ts # Finds dev servers running outside ServicePilot
│ ├── port-scan.ts # Matches running ports to known scripts
│ ├── shell-env.ts # Login-shell PATH resolution
│ └── repo-scanner.ts
├── desktop/ # Tauri 2 + React 19 frontend
│ ├── src/ # Components, hooks, styles (+ tray.tsx popover entry)
│ └── src-tauri/ # Rust backend, Tauri config, bundled sidecar binaries
├── docs/ # Landing page (GitHub Pages) + release notes & plans
└── scripts/ # Sidecar, .dmg and release scripts
The sidecar is compiled to a standalone binary and shipped as a Tauri
externalBin, so installed copies need no Node or Bun at runtime.
The version in desktop/src-tauri/tauri.conf.json is the source of truth; keep
desktop/package.json in step with it.
npm run dist # universal .dmg -> dist-dmg/
npm run release:dry # preflight + build, publish nothing
npm run release # tag, push, and publish the GitHub releasescripts/release.sh refuses to run on a dirty tree, off main, out of sync with
origin, on an existing tag, or with gh authenticated as the wrong account.
Release notes are read from docs/releases/v<version>.md, whose first line
becomes the release title — write them before releasing.
Update archives are cryptographically signed separately from Apple's app code
signing. The release machine expects the private key at
~/.tauri/servicepilot.key; keep a secure backup outside the repository. To set
up a new release machine, restore that same key—generating a replacement would
prevent existing installations from accepting future updates. CI may instead set
TAURI_SIGNING_PRIVATE_KEY or TAURI_SIGNING_PRIVATE_KEY_PATH (and
TAURI_SIGNING_PRIVATE_KEY_PASSWORD when applicable).
MIT — see LICENSE.