Dev servers and ports
ws notices when something in a pane starts listening on a TCP port (a Rails
app, a Vite dev server, a docs site, a game’s debug server) and shows the
port where you’ll see it. prefix o opens it in your browser, and
prefix O lists every one in every workspace. There’s nothing to configure:
it works for any server in any pane, whether you started it, a template did,
or an agent did.
Handing each workstream its own block of ports may come later.
prefix means Ctrl-Space.
Where ports show
Section titled “Where ports show”- On the pane: its frame’s bottom border, on the right, in blue:
A pane with several servers lists them all: :3000 :3036 ↗.
- On the tab’s chip in the sidebar (current workspace): the first port in
the tab, after the PR badge if there is one:
Invoice #101 ✓ :3000. - In the dev servers list (
prefix O): every port in every workspace. See below. - In the finder (
prefix f): each port is a result, such as:3000 ruby bin/rails serverinacme › Invoice #101 › zsh. Typeportorportsfirst to list only them (port 30).Entergoes to the pane. - On the Dashboard: under Now, a button per port (
:3000 acme › Invoice #101) that goes to its pane, and All dev servers to open the list. It shows up to six; past that, the last button says how many more. - For scripts:
ws tab listprints the tab’s ports as a fifth column, andws tab list --jsonhas aportsarray. See Scripting.
Opening one
Section titled “Opening one”prefix o opens http://localhost:<port> in your default browser (open on
macOS, xdg-open on Linux), and the top bar says opened http://localhost:3000.
- If the focused pane listens on one port, that one opens.
- If it listens on none, ws looks at the whole tab: with one port in the tab, that one opens.
- With several to choose from, a menu lists them with the pane each is in;
pick one with
j/kandEnter. - With none in the tab:
nothing in this tab is listening on a port. When other tabs have some, it addsother tabs have some (C-Space O lists them all).
The pane’s right-click menu (on its top border) also has Open http://localhost:3000, one item per port.
Every dev server: prefix O
Section titled “Every dev server: prefix O”prefix O opens the dev servers list: every port anything in any pane
listens on, in every workspace and tab, not only the one you’re in.
╭─ dev servers · 3 ──────────────────────────────────────────────────────╮│ :3000 acme › Invoice #101 › rails ruby bin/rails server -p 3000 ││ :5173 acme › Invoice #101 › vite node ~/acme/node_modules/.bin/vite ││ :8765 docs › 2 › zsh python3 -m http.server 8765 │╰────────────────────────────────────────────────────────────────────────╯Each row is a port, where it is (workspace › tab › pane, named as the
sidebar and tab bar name them) and what’s listening: the command line of the
process that holds the port, shortened to fit (the program by its name, not
its full path, and your home directory as ~). The list is
sorted by port and keeps up while it’s open, so a server that starts or stops
shows within about 3 seconds. It opens on the focused pane’s first port, if
it has one.
| Keys | Action |
|---|---|
j / ↓, k / ↑ |
Move |
g / Home, G / End |
First, last |
Enter |
Go to the pane: its workspace, tab and the pane itself |
o |
Open http://localhost:<port> in your browser; the list stays open |
Esc / q |
Close |
A click on a row goes to its pane, the mouse wheel moves the selection, and a click outside closes the list. The general menu (right-click the top bar or empty sidebar) has Dev servers… too.
To use another key, bind the ports command in keys.toml (or with ws keys), as with any command. See Custom keys.
How it works
Section titled “How it works”Every 3 seconds, ws looks at each pane’s process tree: the pane’s program, its foreground job and everything they started. It asks the OS which of those processes have a TCP socket in the listening state:
- macOS:
lsof -nP -a -iTCP -sTCP:LISTEN -p <pids>. - Linux: each process’s
/proc/<pid>/fdsockets, matched against the listening entries in/proc/net/tcpand/proc/net/tcp6.
The scan runs on a background thread, so the screen never waits on it. A server shows up within about 3 seconds of starting to listen and disappears within 3 seconds of stopping.
Some ports are left out because they’re not something to open:
- ports 49152 and above (the OS’s ephemeral range, which tools such as language servers and agents use internally);
- Node’s inspector ports, 9229 and 9230.
Limitations
Section titled “Limitations”- Collisions aren’t prevented. If two workstreams both start
bin/rails son port 3000, the second still fails with “address already in use”. The first one’s pane and chip show:3000, which tells you where it is. - Only TCP listeners on this machine. A server inside Docker shows the
port only if the process holding it (
docker-proxy, or the VM’s port forwarder) is in the pane’s process tree, which usually it isn’t. - URLs are always
http://localhost:<port>. A server that only answers HTTPS, or on a path, opens at the root over HTTP. - Restore re-runs the pane’s command as before; the port reappears once the server listens again.
See also
Section titled “See also”- Keys:
prefix oandprefix O - The interface: pane frames and chips
- Scripting:
ws tab listand itsports