A tiny CLI that shows you which dev servers are running across the git worktrees of your project, on which ports, for how long, and which agent is in each worktree.
When you run several agents (Claude Code, Codex, …) in parallel — each in its own
git worktree, each spinning up its own dev server — it gets hard to track who is
listening on what. owl answers that, for you and for the agents.
◆ OWL · PygmalionV2 5 UP 1 START 1 ERR ◌ 27 idle 14:21:09
──────────────────────────────────────────────────────────────────────────────
STATUS WORKTREE BRANCH UPTIME PORT URL AGENT
▌ ● healthy qa-vc-590 fix/vc-590-ux-r… 1h54m :3000 localhost:3000 ✦ claude
▏ ▲ 500 ERR checkout fix/pay 3m :5173 localhost:5173 ✦ codex
▏ ◐ starting api feat/api 45s :8000 localhost:8000
A "cockpit" board: a summary chip bar, then dense rows. Status is calm by default (a colored dot + word) and only a 5xx keeps a red pill that screams; pink carries the chrome (brand, worktrees, ports, agent tags); a cyan bar marks the selection.
The dashboard lists only worktrees with a running server; idle ones are
summarized as a count (31 idle). Use owl ls -a when you want the full list.
It is observation-first: owl discovers what's already running and never
launches or assigns ports. The one state-changing action is killing the selected
server from the dashboard (k, with a confirmation).
Requires Go 1.24+ and lsof (preinstalled on macOS; apt install lsof on Linux).
go build -o owl ./cmd/owl # build a local ./owl binary
go install ./cmd/owl # install this checkout onto your PATH
# or install onto your PATH:
go install github.com/Mars7attack/owl/cmd/owl@latestRun it from anywhere inside your project (it finds every worktree of the repo):
owl # live TUI dashboard — keep it in a corner of your screen
owl -i 1s # refresh faster
owl -C ~/code/app # point it at another repoMonitor several projects in one dashboard by repeating -C:
owl -C ~/code/api -C ~/code/web
owl ls -C ~/code/api -C ~/code/webSave a workspace when you use the same set often:
owl workspace save daily ~/code/api ~/code/web
owl -w daily # live TUI for the saved workspace
owl ls -w daily # one-shot JSON snapshot for agents/scripts
owl workspace list
owl workspace show daily
owl workspace rm dailySaved workspaces live in ~/.config/owl/workspaces.json.
TUI keys: ↑/↓ move · o open in browser · c copy URL · k kill (with confirm) · r refresh · q quit.
For agents and scripts, a one-shot JSON snapshot — fields are named, so an agent reads the keys instead of guessing positional columns. One server object per line keeps it scannable and token-light:
owl ls # only running servers + a count of idle worktrees
owl ls -a # list every worktree, including idle ones{
"repo": "PygmalionV2",
"up": 3,
"idle": 28,
"servers": [
{"worktree":"qa-vc-590","branch":"fix/vc-590-ux-review","port":3000,"status":"healthy","uptime":"2h31m","command":"node"},
{"worktree":"qa-vc-590","branch":"fix/vc-590-ux-review","port":8000,"status":"healthy","uptime":"2h31m","command":"python3.12"},
{"worktree":"ia-navigation","branch":"refacto/ia-navigation","port":3002,"status":"healthy","uptime":"7m","command":"node","agent":"claude"}
]
}status is healthy, starting, or down, or error with the HTTP code
for a 5xx. agent (claude / codex) appears only when a session is running
in that worktree. Zero-valued fields are omitted. With -a, idle worktrees are
added as {…,"status":"down"} entries and the idle count is dropped.
Workspace snapshots include project context:
{
"repo": "2 projects",
"projects": ["api","web"],
"up": 2,
"idle": 4,
"servers": [
{"project":"api","worktree":"main","branch":"main","port":8000,"status":"healthy","command":"go"},
{"project":"web","worktree":"checkout","branch":"fix/pay","port":3000,"status":"healthy","command":"node"}
]
}The point of owl ls is for agents to call it before they touch a port. Wire it
in persistently — a line in your agent instructions (CLAUDE.md / AGENTS.md):
Before starting a dev server, run
owl lsto see which ports are already taken, and pick a free one.
— or, in Claude Code, on demand via a /owl slash command
(~/.claude/commands/owl.md) that injects that same reminder into a session.
Either way, make clear owl is monitor-only: it reports what's running but
never starts a server, so the agent picks a free port and launches it with its
own command (owl ls is the only non-interactive subcommand).
One scan joins two sources:
- Listening ports —
lsofenumerates listening TCP sockets (port,pid,command). - Worktrees —
git worktree listenumerates the repo's worktrees. In workspace mode, this runs once per requested repo, then the process scans are shared across the whole workspace.
Each listening process is mapped to its working directory (also via lsof) and
attributed to the worktree whose path is the longest prefix of that directory.
Longest-prefix matters because worktrees are usually nested inside the main
worktree (e.g. .worktrees/feature/) and dev servers run from a subfolder
(e.g. <worktree>/frontend), so a directory legitimately matches several
worktrees — the deepest one wins. Worktrees with nothing listening show as down.
Each running server also gets its uptime (from ps), and its worktree is
tagged with any Claude Code / Codex session running there — detected by the
agent's process name and matched to a worktree the same longest-prefix way.
Status (cheap HTTP probe of each listening port):
| status | meaning |
|---|---|
healthy |
the server answered with a status below 500 — reachable & serving (redirects and 404 on / are normal) |
<code> |
the server answered with a 5xx — up, but erroring (the code, e.g. 500, is shown) |
starting |
the port is listening but HTTP isn't answering yet (Vite/Next compiling, or a non-HTTP server) |
down |
the worktree exists but nothing is listening for it |
Ports in the OS ephemeral range (≥ 49152) are ignored — they're never intentional dev servers (this filters out e.g. Chrome's devtools port).
One core, two façades, no daemon:
internal/scan the scan: lsof ports + git worktrees + ps (uptime, agents), joined; HTTP health (the tested core)
internal/render the `owl ls` JSON output
internal/tui the live dashboard (Bubble Tea)
internal/workspace saved workspace config
cmd/owl flag parsing + dispatch
The scan's inputs (ports, cwd, worktrees, health, uptime, agents) are interfaces, so the join logic is unit-tested with fakes — no real processes or repo needed.
- macOS and Linux only (relies on
lsofandps). - Health is HTTP-only: a listening non-HTTP server reads as
starting. - Attribution needs a process's cwd to live inside a worktree (the normal case for a dev server started in that worktree).
- Agent detection is heuristic — it matches the
claude/codexprocess name, so an agent launched under a different name may be missed. kkills the listening process (SIGTERM, then SIGKILL); a parent process manager (e.g.pnpm dev) may linger or respawn.