A small TUI dashboard for your local dev servers. Auto-discovers anything listening on common dev ports, launches saved projects, tails logs with framework-aware error/warning classification, tracks RAM + CPU per server, and auto-stops idle servers when there's no stdout and no incoming connections for 15 minutes.
Built for macOS — Linux mostly works too (uses lsof, ps, pgrep).
┌─ pier · local dev server dashboard ──── running 2 · err 0 · warn 2 ─┐
│ › ● 3000 Oikion MVP MVP 45213 4m 612 MB 41% 0/2 managed │
│ ● 5173 Vite docs-site 67890 1h 148 MB 3% 0/0 managed │
└─────────────────────────────────────────────────────────────────────┘
↑↓ select n new s stop r restart l logs o browser q quit
pnpm add -g @steveapo/pier
# or npm i -g @steveapo/pierThen from any directory:
pier- Auto-discovers every dev server listening on a dev-ish port (3000–9999 plus common outliers). No config required for that — your Next.js, Vite, Python http.server, anything.
- Launches saved projects from
~/.pier/projects.json. First run drops a placeholder; pressnthen arrow to+ Add new project…to open the macOS folder picker and register one — the start command is auto-suggested from your lockfile (pnpm/yarn/npm/bun) andpackage.jsonscripts.dev. - Captures stdout/stderr for anything pier launched, with framework-aware classification: Next.js's
× Error:/⨯, Vite's[vite] error, TypeScripterror TS####, Python tracebacks (File "...", line N), HTTP request logs, deprecation warnings — all sorted intoerror / warn / request / infoso you can filter. - Resource metrics — RAM (RSS, summed across the descendant tree so Next.js + swc + workers shows the real footprint) and CPU%, refreshed every 3s, colour-coded.
- HTTP-aware idle auto-stop: a server is "idle" only when it has both no stdout for N minutes AND no ESTABLISHED connections to its port for N minutes. So chatty servers don't get killed during quiet refactoring, and silent-but-serving workers stay up.
- Native macOS notifications on crashes, error spikes (throttled to one per 60s), and auto-stops.
- Claim & restart: if you started a server in another terminal, pier auto-detects it as
discovered. Logs aren't captured (macOS doesn't let you tap into another process's stdout retroactively), but you can presscin the logs view to sniff the original command viaps, stop the external process, and respawn it as managed — logs start streaming.
~/.pier/projects.json is created on first run:
{
"idleMinutes": 15,
"projects": [
{
"id": "my-app",
"label": "My App",
"cwd": "/Users/you/Code/my-app",
"command": "pnpm dev",
"port": 3000,
"idleMinutes": 30
}
]
}idleMinutes: 0(top-level) disables auto-stop entirely.- Per-project
idleMinutesoverrides the global default. - Editing the JSON is supported; the launcher's
+ Add new project…is just a friendlier way to append to it.
| key | action |
|---|---|
↑ / ↓ |
move cursor |
n |
open launcher |
s |
stop selected server |
r |
restart (managed only) |
l |
open logs for selected server |
o |
open http://localhost:<port> in browser |
x |
forget a stopped/crashed managed entry |
q / Ctrl+C |
quit (stops all managed servers first) |
| key | action |
|---|---|
Esc |
back to list |
a |
show all lines |
e |
errors only |
w |
warnings only |
h |
HTTP requests only |
c |
(discovered server) claim & restart under pier |
s / r |
stop / restart |
| key | action |
|---|---|
↑ / ↓ |
select |
Enter |
launch the selected project, or open folder picker on the + Add row |
D |
remove the selected project from the config |
Esc |
back to list |
- status dot — green running · yellow starting · red crashed · gray stopped
- PORT — first listening port (detected via
lsof) - PROJECT / CMD — project label (managed) or command name (discovered)
- CWD — last segment of the working directory
- PID — the actual server process (Node, not the shell wrapper)
- UPTIME
- RAM — RSS summed across the descendant tree, colour-thresholded at 1 GB (yellow) / 2 GB (red)
- CPU — current %CPU summed across the tree (can exceed 100 on multi-core — that's expected during builds)
- ERR / WRN — counters since pier started managing the process
- SRC —
managed(pier spawned it) ordiscovered(pier found it vialsof)
bin/pier.js # entry — imports dist/cli.js (or falls back to tsx + src)
bin/pier-dev.js # `pnpm dev` entry — always tsx + src
src/cli.tsx # Ink app, polling loops, input dispatch
src/discover.ts # lsof scan → ServerEntry[]
src/process.ts # managed registry: spawn/kill, descendant tree, metric apply
src/ptree.ts # recursive `pgrep -P` walker
src/metrics.ts # `ps -o rss,pcpu` sampler
src/parser.ts # framework-aware log line classifier
src/idle.ts # idle detection (stdout-silent AND connection-silent)
src/notify.ts # `osascript` notification wrapper
src/linker.ts # macOS folder picker + lockfile sniff + claim-external
src/config.ts # ~/.pier/projects.json
src/components/*.tsx # Header, ServerList, LogPane, HelpBar
Builds with tsup into a single ESM file (dist/cli.js, ~200 KB). No build step required for local dev (pnpm dev uses tsx).
git clone https://github.com/steveapo/pier.git
cd pier
pnpm install
pnpm dev # hot-loaded source via tsx
pnpm typecheck # tsc --noEmit
pnpm build # produce dist/pnpm version patch # or minor / major
pnpm publish --access public
git push --follow-tagsprepublishOnly runs typecheck + build automatically.
- No log capture for discovered servers without claim-restart — macOS doesn't let you retroactively tap another terminal's stdout.
- Idle = stdout-silent AND connection-silent for N minutes. Tunable per project.
- No per-process disk I/O — would need
dtrace(and SIP off) on macOS. Deferred. - macOS-first. Linux is best-effort (the codebase only uses POSIX tools); the folder picker is
osascriptwhich is macOS-only — Linux falls through with a friendly error.
MIT © Stavros Apostolou