Skip to content

irwansetiawan/perchd

Repository files navigation

                  v
                ( o)>      p e r c h d
               //\         ─────────────────────────────────
      ~~~~~~~~~~V_/_~~~~~   the one branch you actually land on

      │  >  feature/auth      next      :3000     ACTIVE
      │     fix/payments      vite      :5173
      │     spike/ai-search   fastapi   :8000
      │     chore/bump-deps   next      :3000
      │
      │  your agents build every branch · one server on the one you watch

perchd

Eight agents. Eight branches. One perch.

npm version Node >= 20 Built for parallel agents macOS and Linux MIT license

perchd is npm run dev, pointed at any worktree —
switch which branch is live without the port-collision dance.

1 live server · 0 port collisions · 0 daemons


It's the agentic era. You don't write one branch at a time anymore.

You open six git worktrees, point a coding agent at each — Claude Code here, Cursor there, Codex on the spike — and they all start typing at once. Six dev servers want port 3000. You want to look at one of them.

perchd is the perch. One dev server, on its native port, following whichever branch you're looking at. No cd. No remembering the command. No port roulette.

You already know the command

You don't have to learn perchd to use perchd. Run perchd and you get a menu of your worktrees with the one you're standing in pre-selected — press Enter and it's your npm run dev: foreground, logs streaming, on the native port. Arrow to a different worktree and that's the switch. Or name one directly and skip the menu:

perchd                 # menu of worktrees, current one pre-selected → Enter runs it
perchd feature/auth    # skip the menu — run that worktree by name
perchd main            # the main tree, without leaving your branch

Same muscle memory, all the way down to Ctrl-C: it stops the server, exactly like npm run dev. The menu is the point, not overhead — it's how you switch which branch you're previewing without ever cd-ing between worktrees.

The switcher underneath

That one live server actually runs in the background, and your terminal is just a viewport onto its log stream — so you can move it, leave it running, or watch it from somewhere else:

perchd                   # pick a worktree → attach, live logs, like npm run dev
^C                       #   → stops it, just like npm run dev
perchd feature/auth -d   # switch to another branch, but DON'T attach — keep your prompt
perchd attach            # come watch that one; here ^C only detaches, it keeps running
perchd stop              # terminate whatever's active, from anywhere

Ctrl-C stops the server you started — like npm run dev. The one exception is perchd attach: you're peeking at a server that's meant to persist, so there Ctrl-C only detaches and leaves it running (the banner tells you which mode you're in). Either way, perchd stop is the definitive kill.

So there are two ways to keep a server alive while you move on: start it detached with -d, or attach to it and Ctrl-C back out. Closing the terminal window on a server you started stops it too — no orphans.

Before / after

Without perchd, switching what you're previewing is a small ritual every time:

$ cd ../repo-feature-auth && pnpm dev     # wait — which port was this one?
^C                                        # kill the vite server from the other worktree
$ lsof -ti tcp:3000 | xargs kill          # something's *still* on 3000
$ cd ../repo-fix-payments                 # ...was it `npm run dev` or `make dev` here?

With perchd:

$ perchd feature/auth
# old server stops, frees its port, auth starts on :3000, logs stream
# → http://localhost:3000

That was it.

Why single-active

The bottleneck moved. Agents are cheap and parallel; your attention is not.

Agent concurrency (8 worktrees) is not preview concurrency (1 pair of eyes).

So perchd keeps exactly one dev server alive — the worktree you're actually looking at — and that one assumption pays for everything:

  • No port collisions. Only one server runs, so every project starts on its own conventional default port. Two Next apps both want 3000? Fine — they never run at the same time.
  • No proxy, no port hashing, no .env rewriting. Your bookmarks never move. It's always localhost:<the default for that framework>.
  • No daemon. perchd reconciles lazily the next time you run it. Delete the active worktree and the server gets stopped and cleared on your next status or switch — self-healing, no watcher.
  • No orphans. Dev servers spawn children (Turbopack, webpack, esbuild). perchd starts each server as its own process group and tears down the whole group, so rapid switches never leave zombies on a port.

If a worktree needs several processes at once, point its runner at mprocs / make dev / a dev:all script. perchd supervises one process group; that group can fan out all it likes.

Install

Install it globally — perchd is a CLI you run from any worktree, in any terminal, so it belongs on your PATH, not in one project's node_modules:

npm i -g perchd       # or: pnpm add -g perchd   ·   bun add -g perchd

Requires Node ≥ 20, on macOS or Linux (Windows is out of scope for v1). If fzf is on your PATH, the picker uses it; otherwise a clean built-in prompt takes over.

Quickstart

cd any/worktree/of/your/repo
perchd                       # menu (current worktree pre-selected) → Enter runs it
^C                           # stop it — just like npm run dev
perchd feature/auth -d       # switch to another branch by name, keep it in the background
perchd status                # table: worktree, runner, port, which is ACTIVE
perchd stop                  # stop the active server

Commands

Command What it does
perchd Menu of worktrees, current one pre-selected — Enter runs it (drop-in), or pick another to switch. Then attaches.
perchd <branch|path> Skip the menu: switch the active dev server to that worktree and attach.
perchd … -d / --detach Switch but stay in the background — print the URL and return the prompt.
perchd switch [branch|path] Explicit synonym for perchd [branch|path]. Note it now attaches by default; add -d for the old background behavior.
perchd attach [branch] Attach to the active server (or switch to branch, then attach).
perchd stop Stop the active server.
perchd status / perchd ls Table: worktree, branch, runner, port, ACTIVE?, pid, uptime.
perchd restart Restart the active server in place.
perchd logs [-f] Print (or follow with -f) the active server's log.
perchd open Open the active server's URL in the browser.
perchd path [branch] Print a worktree's absolute path (for the shell cd function).
perchd gc Stop + clear a deleted active worktree; reap stale pids.
perchd doctor Diagnose stale pids, dead ports, undetected worktrees, foreign port holders.
perchd config Print the resolved config and detected runner per worktree.
perchd watch Foreground watcher: auto-stops the active server the instant its worktree is deleted.
perchd dev [target] Deprecated — an alias for perchd [target]. See migrating.

Flags on a switch: -d/--detach (don't attach), --cmd <str> and --port <n> (one-off overrides), --no-wait (skip the readiness wait), --force (kill a foreign process holding the target port).

Passthrough is runner-agnostic — perchd appends your -- <args> verbatim to whatever command it resolved, and does not assume npm. For npm's own script forwarding, include npm's separator yourself: perchd -- -- --host runs npm run dev -- --host.

perchd --port 4000     # override the port
perchd -- --host       # append args to the underlying runner

Two honest caveats about attached output. Because the server writes to a log stream that outlives your terminal rather than to the terminal itself:

  • The dev server's own keypress shortcuts (e.g. vite's r/q) are inactive — use perchd restart and perchd stop. perchd's servers don't read stdin by design; it's what keeps them from being suspended once they outlive your terminal.
  • Colour comes through, but interactive progress re-rendering (spinners, bars that redraw in place) isn't faithfully reproduced. Logs, URLs, and errors are all exactly what you'd see from npm run dev.

Migrating from perchd dev

Through 0.3.x, the foreground drop-in was a subcommand: perchd dev. It's now just perchd. perchd dev still works and behaves identically — it prints a deprecation hint and will be removed in a future major.

Before (≤ 0.3.x) Now
perchd dev perchd
perchd dev feature/auth perchd feature/auth
perchd (background switch) perchd -d
perchd switch feature/auth (background) perchd feature/auth -d
Ctrl-C stopped the server Ctrl-C still stops it — same as before

perchd switch still exists as an explicit synonym, but like every switch it now attaches by default — pass -d to get the old background behavior.

If you used 0.4.0 specifically: that release briefly made Ctrl-C detach instead of stop. 0.5.0 reverts to the intuitive npm run dev behavior — Ctrl-C stops the server you started. Only perchd attach detaches on Ctrl-C now.

Supported frameworks

Auto-detected with zero config — perchd reads the worktree and figures out how it wants to run:

  • JS/TS: Next.js, Vite (React/Vue/Svelte), Nuxt, Remix, Astro, SvelteKit, generic Node (dev/start/serve script). Package manager inferred from the lockfile (pnpm/bun/yarn/npm).
  • Python: Django, FastAPI, Flask. Toolchain inferred from the lockfile (uv/poetry/.venv/system).
  • Convention runners: mprocs, process-compose, Procfile (overmind/hivemind/foreman), Makefile dev: target, justfile dev recipe.

Your agent invented a framework perchd doesn't know? Adding one is a single detector module — see CONTRIBUTING.md.

Config (optional)

Everything is auto-detected; .perchd.toml at the repo root is only for overrides and non-standard projects:

ready_timeout = 30           # seconds to wait for the port to listen
stop_timeout  = 8            # seconds before SIGTERM escalates to SIGKILL

[runner]                     # force the runner for the whole repo
command = "make dev"
port    = 3000

[worktrees."feature/auth"]   # per-branch override
command = "pnpm dev"
cwd     = "apps/web"         # relative to the worktree root
port    = 4000
env     = { DEBUG = "1" }

Jumping into the active worktree (cd)

A child process can't change your shell's directory, so perchd ships a path subcommand and a small shell function. Add this to your ~/.zshrc:

perchd() {
  if [[ "$1" == "cd" ]]; then
    cd "$(command perchd path "$2")"
  else
    command perchd "$@"
  fi
}

Then perchd cd feature/auth drops you right where your agent has been working.

FAQ

Do I have to learn a new tool? Barely. Run perchd, press Enter on the pre-selected current worktree, and it's your npm run dev — same foreground, same logs, same port. The only new thing is that the menu lets you arrow to another worktree instead of cd-ing there first.

Does Ctrl-C stop the server, like npm run dev? Yes — when you started it (perchd / perchd <branch>), Ctrl-C stops it, and so does closing the terminal. The one exception is perchd attach: there you're looking at a server that's meant to keep running, so Ctrl-C just detaches and leaves it alive. The banner tells you which mode you're in, and perchd stop always terminates.

How do I keep a server running while I move on? Two ways: start it detached with perchd <branch> -d (never attaches), or perchd attach to a running one and Ctrl-C back out.

Can't it just run all eight servers at once? No. You have two eyes. It runs the one you're looking at. That's not a limitation — it's the whole design.

My agent spawns Turbopack / webpack / esbuild children. Orphans on the port? No. perchd signals the whole process group, not the bare pid, and waits for the port to free before starting the next one.

Do I need a daemon running in the background? No daemon. perchd does its bookkeeping lazily, the next time you run it.

Does perchd phone home? Only to check for its own updates: at most once a day it asks the npm registry for the latest perchd version (in a detached background process, so it never slows a command), and prints a one-line "you're outdated" notice to stderr if you're behind. Set NO_UPDATE_NOTIFIER=1 (or run under CI) to turn it off. Nothing else leaves your machine.

Does it manage my agents (Claude Code, Cursor, Codex)? No. It manages dev servers. The agents are your problem — perchd just gives you a clean window onto whatever they've built.

I deleted the active worktree out from under it. Your next status or switch notices, stops the server, and clears the state. Self-healing.

License

MIT © Irwan Setiawan

About

Keep exactly one dev server alive across many git worktrees, on the project's native port — no proxy, no port hashing.

Topics

Resources

License

Contributing

Stars

2 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors