Skip to content
Jaybee4realPublic

About

Start and restart dev servers in named, reusable macOS Terminal tabs

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

perch by Novacraft

perch

Start, restart, and arrange dev servers in named, reusable macOS Terminal tabs.

I run a lot of servers at once: a few Next.js apps, some NestJS backends, four or five Metro bundlers on any given day. The usual way of scripting Terminal from the command line (osascript ... do script) opens a brand new window every single time. Restart Metro eight times in an afternoon and you have eight dead windows and no idea which one is live.

perch fixes that. Every server gets a tab with a stable title. Run the same command again and the server restarts in the same tab it was in before, instead of spawning a ninth window. The name comes from that: each server perches in its own spot and returns to it.

It has since grown a few larger conveniences: a favorites set you launch as a group, optional desktop placement and tiling across multiple monitors (via yabai), and a small web dashboard with a spatial arranger you can drag windows around in.

Origins

perch started life as pz-dev — a throwaway shell helper for one project, podzyme (the pz). It did one thing: start podzyme's dev servers in Terminal tabs that survived the coding-agent shell that launched them. It turned out every project I touched had the same problem, so pz-dev grew past podzyme, got the project-neutral name perch (each server perches in its own tab), and kept growing into favorites, multi-monitor placement, a web dashboard, and a menu bar app. The pz-dev command still works — it's symlinked to perch — so nothing that predates the rename breaks.

Features

  • Launch a registered project by name alone: perch {project-name}.
  • Product clusters: perch groups auto-groups projects by name prefix, and perch example starts the whole cluster (backend first).
  • Port as single source of truth: perch port lets a project's own start script bind its assigned port, with a framework-default fallback when perch isn't installed.
  • One reusable tab per server, matched by tab title. No window pileup.
  • Frees the port before starting, so restarts never die with EADDRINUSE.
  • Servers outlive the shell that launched them. They run in real Terminal tabs, not child processes.
  • Favorites: pick a working set once, launch it all with perch fav go, stop it with perch killall.
  • Desktop placement (optional): send each server to a specific desktop by category, tiled full width and stacked, across any monitor layout. Off cleanly if you don't want it.
  • Pin other apps too: perch apps add "Google Chrome" 2:1 sends any app's windows to a chosen display/desktop, not just terminals.
  • Park on monitor unplug: when an external display disconnects, perch minimizes the windows macOS dumps onto the laptop and restores them on reconnect — the laptop screen stays yours. Toggle with perch set park, or exempt a single app with perch apps park "<App>" off (also a per-app switch in the menu bar) so that one stays in place.
  • Herd your coding agents: run Claude Code / Codex / Cursor / opencode / aider as persistent, resumable sessions; see which one is working / blocked / idle at a glance; attach the whole herd as tabs in one terminal. See below.
  • Android emulators, perched: perch emu <avd> runs each AVD in its own tab with pinned DNS, so a VPN/Wi-Fi change can't leave it unable to resolve any host; perch emu adopt (and perch reap) pull emulators started elsewhere into their perch tab.
  • Menu bar app (PerchBar): start/restart/place projects from a native menu bar popover, with live ↓/↑ network speed (NetSpeed-style), a running-server count, and an Agents section that flags any blocked agent (⚠) right in the menu bar. perch bar on to launch at login.
  • Metro client logs: Metro servers append the RN app's console output to ~/.config/perch/logs/<marker>.log.
  • Window position memory: a closed-and-reopened server returns to its last size, position, and monitor.
  • Web dashboard: perch gui opens a local dashboard with live status, one-click start/stop, and a spatial arranger where you drag terminals between desktops and save.
  • Portable: perch setup auto-detects the machine's monitors and writes a sensible layout; perch doctor tells you exactly what (if anything) to install.

The "outlive the shell" point is the reason this exists as more than an alias. I use coding agents heavily, and a server started from an agent's shell dies when the session ends. A server started through perch does not.

Agents (herd your coding agents)

The same idea perch applies to dev servers — start it once, let it outlive the shell, find it again by name — applied to coding agents. Agents run as windows in one persistent tmux session, so they survive closing Terminal and you reattach whenever you want.

perch agent api ~/code/backend            # launch an agent (default command = first installed: claude, codex, …)
perch agent web ~/code/frontend "codex"   # …or name the command explicitly
perch agents                              # list them with live status
perch cockpit                             # attach the whole herd as tabs in ONE terminal

perch agents classifies each agent so you never hunt for the stuck one:

  • ● working — the agent is producing output right now.
  • ● blocked — a question or permission prompt is waiting on you.
  • ● idle — ready, nothing in flight.

perch cockpit opens a single terminal attached to every agent, each on its own tab, with a themed status bar showing the perch badge and live ▶ working ⏸ idle ⚠ blocked counts. The menu bar app mirrors all of this in an Agents section, and shows a ⚠N in the menu bar itself the moment any agent blocks.

Other commands: perch agent stop|restart <name>, perch agents stop [name] | killall, perch agents detect (which agent CLIs are installed).

Status is a heuristic (recent output ⇒ working; a pending prompt on screen ⇒ blocked), tuned to be useful rather than perfect. Requires tmux (brew install tmux).

Install

git clone https://github.com/Jaybee4real/perch.git
cd perch
./install.sh
perch doctor          # tells you if anything optional is missing

The installer symlinks perch into ~/.local/bin and seeds ~/.config/perch/projects.conf with a sample registry, unless you already have one, in which case it leaves yours alone. It keeps a pz-dev symlink around too, because that was the tool's old name.

macOS only. It drives Terminal.app through AppleScript, so iTerm2 and other emulators are out. Desktop placement and the dashboard are optional and degrade gracefully if their dependencies are absent (see Requirements).

Quickstart

Register a product's servers once — a shared name prefix lets you launch them as a cluster:

perch add {product}-web   {port} {dir} "{command}"
perch add {product}-api   {port} {dir} "{command}"
perch add {product}-metro {port} {dir} "{command}"

perch {project-name}       # one server, in a tab titled {project-name}
perch {project-name}       # again later: same tab, port freed, restarted in place
perch {product}            # the whole cluster at once (backend first)
perch stop {project-name}  # free one port

The ad-hoc form still works for anything unregistered. Pass - as the port for tools that do not hold one:

perch {marker} {dir} {port} "{command}"

Commands

perch <project>                 launch a registered project (restart in place if already open)
perch <prefix>                  start a whole product cluster, backend-first (e.g. perch example)
perch <marker> <dir> <port> <cmd>   ad-hoc launch of anything

perch fav                       interactive picker: choose a working set, save + launch
perch fav go                    launch the saved favorites (no menu)
perch fav set|add|list|clear    manage the favorites list
perch pick                      alias for `perch fav`

perch place [fav]               move open windows to their desktops + tile full-width, stacked
perch place [fav] notile        move to desktops but leave windows free-floating
perch killall [--close] [fav]   stop running servers (optionally close their tabs; optionally just favorites)
perch stop <project|port>       free one port

perch gui                       open the web dashboard (http://localhost:7620)
perch gui stop                  stop the dashboard server

perch set                       show optional settings (placement, tile)
perch set placement <on|off>    toggle desktop placement
perch set tile <on|off>         toggle full-width stacked tiling

perch setup                     auto-detect this machine's monitors + write the desktop map
perch doctor                    check dependencies and config, with copy-paste fixes

perch list                      every project, its port, whether it's up
perch groups                    projects grouped by product cluster, with ports + status
perch port [name]               print a project's assigned port (by name, or by current dir)
perch add / remove              register / unregister a project
perch bounds [clear [name]]     inspect / forget remembered window positions
perch config | help | -v        help works at every level, e.g. `perch fav add help`

Favorites

Most days I want the same dozen-ish servers up. Register everything once, then pick the set:

perch fav          # a grouped picker; type numbers, ranges (1-5), a group name (metro, web, ...), then Enter
perch fav go       # brings them all up in category order: Metro, Landing, Web, Backend, Admin
perch killall fav  # stops just the favorites

Selections persist in ~/.config/perch/favorites. perch fav set a b c writes the set non-interactively.

Desktop placement and tiling (optional)

If yabai is installed and running, perch can send each server to a specific desktop and tile the windows there full width, stacked one after another. Categories are inferred from the project name and port (metro, landing, web, backend, admin), and each category maps to a desktop.

perch setup        # detect monitors, write ~/.config/perch/spaces.conf for THIS machine
perch fav go       # launches + places + tiles
perch place        # re-apply placement to whatever is open (no restart)

macOS renumbers its Mission Control spaces whenever a fullscreen app appears, so the map is written as display:desktop (for example metro|2:2 means "display 2, desktop 2, counting real desktops only") and resolved to a live index at placement time. Edit ~/.config/perch/spaces.conf by hand, or just drag windows in the dashboard and save.

perch setup adapts to whatever you have: on a single-screen laptop everything lands on one desktop; with four or more desktops it reserves desktop 1 for your own work and spreads servers across 2, 3, 4. If there is no map yet, the first perch place writes one automatically.

Turn it all off with perch set placement off. Without yabai, placement is simply skipped and everything else works.

The web dashboard

perch gui

Opens a local dashboard at http://localhost:7620 (bound to localhost only). It shells out to the same CLI, so the two never drift. Two views:

  • Servers: every project grouped by category with a live up/down dot, one-click start and stop, and a favorite toggle. An Add project button opens a native folder picker (the dashboard is local, so it gets the real path), auto-fills the name, and registers the project.
  • Layout: a to-scale map of your monitors (position and orientation), each desktop drawn as a mini screen with every window in its real place. perch terminals are colored and draggable; other apps and fullscreen spaces are shown dimmed for context. Drag a terminal to another desktop, hit Save, and it moves the window, remembers the new desktop in spaces.conf, and re-tiles.

Window position memory

Whether or not you use desktop placement, perch remembers each server's window rectangle and restores it when it has to open a fresh window (a reused tab is left alone). With yabai this covers the monitor and desktop too. Inspect or reset with perch bounds.

The registry

~/.config/perch/projects.conf, one line per project:

name|port|dir|command
{product}-web|3000|~/code/{product}/web|yarn dev
{product}-api|8000|~/code/{product}/api|yarn start:dev
{product}-desktop|-|~/code/{product}/desktop|$HOME/tools/flutter/bin/flutter run

Lines starting with # are comments. ~ and $HOME both expand. Edit the file directly or use perch add.

Your registry is private. It lives only in ~/.config/perch/ and is never tracked by this repo (the repo ships projects.conf.example and gitignores the real names). Same for your favorites, settings, and desktop map.

One convention worth copying: have each project's own start script bind its perch port, falling back to the framework default when perch isn't installed:

next dev -p $(perch port 2>/dev/null || echo 3000)
vite --port $(perch port 2>/dev/null || echo 5173) --strictPort
react-native start --port $(perch port 2>/dev/null || echo 8081)
PORT=$(perch port 2>/dev/null || echo 3000) nest start --watch

perch port prints the port assigned to the current directory (or perch port <name>). perch frees the port before launching and the script binds exactly that one — so yarn dev on its own lands on the right port too, and the port column in perch list stays honest. Without perch on the machine, each falls back to its framework default.

Requirements

  • macOS with Terminal.app. AppleScript is load-bearing; there is no Linux or Windows port.
  • python3 (ships with the Xcode command line tools) for the web dashboard and perch setup.
  • yabai for desktop placement only. Install with brew install koekeishiya/formulae/yabai, run yabai --start-service, and grant it Accessibility. Everything except placement works without it.
  • tmux for the agents commands only (brew install tmux). Nothing else needs it.

Run perch doctor and it prints exactly which of these is missing plus the command to fix it.

How the tab reuse works

AppleScript walks every Terminal window and compares each tab's custom title against the project name. On a match, the command runs in that tab and the window comes forward. No match, one new tab, titled. Some Terminal windows (the Settings window, for one) do not expose tabs and throw if you ask, so the loop wraps each window in a try. That guard looks paranoid but its absence produced error -1728 often enough that I learned to keep it.

Killing the old server is a plain lsof -ti tcp:<port> | xargs kill -9. Blunt, but for dev servers I have never wanted the graceful version, I want the port back. perch killall --close is gentler about the tab: it kills each window's whole tty group first so Terminal does not pop its "terminate running processes?" prompt.

Things it does not do

  • No Linux, no Windows. AppleScript is load-bearing.
  • It will not stop you from registering two projects on the same port. perch list makes the clash easy to spot.
  • Rename a project and its old tab keeps the old title and gets orphaned. Close it yourself.
  • Desktop placement cannot create Mission Control desktops for you. Make them in Mission Control first; perch setup maps to whatever exists.

About

Start and restart dev servers in named, reusable macOS Terminal tabs

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages