# Everyport

> See every dev server running on your machine, or on any machine you can reach. A menu bar and tray app for macOS, Windows and Linux, plus `everyport`, a CLI and terminal UI. Free, open source (MIT), no account, no telemetry.

`everyport` finds servers listening on ports 3000 to 65535 by default. For each one it reports the project, git branch or worktree, framework, the Claude Code or Codex session that started it, and the memory and CPU of its whole process tree. It can stop, restart and clean up servers, and it skips protected processes such as databases unless told otherwise.

For agents and scripts:

- `everyport list --json` prints one snapshot. `everyport watch --jsonl` prints a snapshot on every change.
- `everyport stop <port>` and `everyport restart <port>` refuse protected servers without `--protected`. `everyport clean` asks first unless you pass `--yes`.
- `everyport --on <machine> <command>` runs a command on another machine, such as one added with `everyport remote add devbox -- ssh devbox`.
- `everyport stdio` and `everyport serve` speak a JSON protocol that any client can use.

---

<!-- https://everyport.dev/docs/getting-started -->

# Getting started

## Install

Install the desktop app, the CLI, or both. The desktop app is a menu bar app on macOS, and a tray app on Windows and Linux. The CLI is one binary, `everyport`, with a terminal UI, for servers, VMs and containers.

To install both with one command:

```bash
curl -fsSL https://everyport.dev/install.sh | sh          # macOS and Linux
[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor 3072; irm https://everyport.dev/install.ps1 | iex               # Windows (PowerShell)
```

Or install each one separately:

| | Desktop app | CLI |
|---|---|---|
| macOS | `brew install --cask greenfield-inc/tap/everyport` | `brew install greenfield-inc/tap/everyport` |
| Windows | `.msi` or `-setup.exe` from [Releases](https://github.com/greenfield-inc/everyport/releases/latest) | `[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor 3072; irm https://github.com/greenfield-inc/everyport/releases/latest/download/install.ps1 \| iex` |
| Linux | `.deb`, `.rpm` or `.AppImage` from [Releases](https://github.com/greenfield-inc/everyport/releases/latest) | `curl -fsSL https://github.com/greenfield-inc/everyport/releases/latest/download/install.sh \| sh` |

See [CLI only](https://github.com/greenfield-inc/everyport#cli-only) for npm.

## The desktop app

1. Open Everyport. On macOS, its menu bar icon shows how many servers are running.
2. Click the icon, or press <kbd>⌥</kbd> <kbd>⌘</kbd> <kbd>P</kbd> (<kbd>Ctrl</kbd> <kbd>Alt</kbd> <kbd>P</kbd> on Windows and Linux) from any app. A small window, the popover, opens under the icon.
3. The popover lists every dev server on this computer. The top shows the memory your servers use. Hover it to see other apps and what's free.
4. Click a server for its details: memory and CPU charts for the last 10 minutes, its process tree, project, branch, and the coding agent session that started it.
5. Click **Clean up** to see the servers you probably don't need, and stop them.

On many Linux desktops a left click opens the tray menu. Pick **Open Everyport** at the top.

### Keyboard

| Keys | Action |
|---|---|
| <kbd>↑</kbd> <kbd>↓</kbd> | Select a server |
| <kbd>Enter</kbd> | Open its details |
| <kbd>Esc</kbd> or <kbd>←</kbd> | Go back |
| <kbd>⌘</kbd> <kbd>O</kbd> | Open it in your browser |
| <kbd>⌘</kbd> <kbd>R</kbd> | Restart it |
| <kbd>⌘</kbd> <kbd>⌫</kbd> | Stop it |

On Windows and Linux, use <kbd>Ctrl</kbd> instead of <kbd>⌘</kbd>. In Clean up, <kbd>Enter</kbd> or <kbd>Space</kbd> selects a server.

### Protected servers

Databases such as `postgres` and `redis-server` are protected. Clean up and auto-kill skip them, and Stop and Restart ask you to confirm first. Edit the list in [Settings → Clean up](/docs/settings#protected-processes).

## The terminal

Run `everyport` for the terminal UI. It shows the same list, with the same details and actions.

| Keys | Action |
|---|---|
| <kbd>↑</kbd> <kbd>↓</kbd> or <kbd>j</kbd> <kbd>k</kbd> | Select a server |
| <kbd>Enter</kbd> | Open its details |
| <kbd>o</kbd> | Open it in your browser |
| <kbd>v</kbd> | Open its Vercel preview, when [previews are on](/docs/settings#general) |
| <kbd>s</kbd> | Stop it |
| <kbd>r</kbd> | Restart it |
| <kbd>c</kbd> | Clean up |
| <kbd>Tab</kbd> or <kbd>m</kbd> | Switch machines |
| <kbd>?</kbd> | All keys |
| <kbd>q</kbd> | Quit |

For scripts, use the commands:

```bash
everyport list                  # the servers, once
everyport list --json           # the same, as JSON
everyport watch --jsonl         # a JSON snapshot on every change
everyport stop 5173             # stop the server on port 5173
everyport clean --yes           # stop what Clean up suggests, without asking
```

See the [CLI reference](/docs/cli) for every command.

## Updating

The desktop app checks GitHub for a newer release when it starts and every 12 hours, or when you pick **Check for Updates…** in the tray menu. The check sends one request to GitHub and nothing else. When there is a newer release:

- the popover shows an **Update to 0.1.3** button at the bottom
- the tray menu shows **Update to Everyport 0.1.3…**
- a notification appears, once for each new version

Click any of these to update. A terminal opens and runs the install command. It downloads and verifies the new version, quits Everyport, replaces it and opens the new one. In some cases it works differently:

- **No terminal opens:** **Settings → General** shows the command to paste in a terminal, with a **Copy** button.
- **Installed with Homebrew:** the terminal runs `brew upgrade --cask everyport`.
- **Installed from a `.deb`, `.rpm` or `.msi`:** Update opens the release page. Download and install the new package from there.

**Skip This Version**, in the notification or in **Settings → General**, hides that version until a newer one comes out. To stop checking, turn off **Check for updates automatically** in **Settings → General**.

For the CLI, run `everyport update`. If you installed the CLI with Homebrew, cargo, npm or PyPI, it prints the command to update it that way. When run in a terminal, `everyport --version` also tells you if a newer release is out. It checks at most once a day. Set `EVERYPORT_NO_UPDATE_CHECK=1` to turn it off.

## Next

- [Watch other machines](/docs/machines) over SSH, Docker, Kubernetes or WSL.
- [Set alerts, clean up and auto-kill](/docs/settings).
- [Fix a problem](/docs/troubleshooting).

---

<!-- https://everyport.dev/docs/machines -->

# Remote machines

Everyport shows servers on any machine where it can run a program: a devbox over SSH, a Docker container, a Kubernetes pod or a WSL distro. The app runs `everyport stdio` there through a command you give it, and reads the same protocol it reads from this computer.

For SSH setup, and what to do when a machine can't connect, see [Connect a machine](https://github.com/greenfield-inc/everyport/blob/main/docs/machines.md).

## WSL

On Windows, the app adds each installed WSL distro for you. Servers inside WSL show under their distro, with their Linux process tree, and open at `localhost` as usual.

## Add a machine

In the app, open **Settings → Machines**:

- **Found on this computer** lists hosts from `~/.ssh/config`, your Pane remote hosts, online Tailscale peers and WSL distros. Click **Add**.
- **Add a machine** takes a name and a command that runs a program on the machine:

| Connection | Command |
|---|---|
| SSH | `ssh devbox` |
| Docker | `docker exec -i my-container` |
| Kubernetes | `kubectl exec -i my-pod --` |
| WSL | `wsl -d Ubuntu --` |
| Anything else | your own command prefix |

The command must run without asking for a password. For SSH, use a key or an agent.

From the terminal:

```bash
everyport remote add devbox -- ssh devbox
everyport remote list                   # saved and discovered machines, and the everyport on each
everyport remote rm devbox
```

The app and the CLI share the saved machines, in `machines.toml` in the [settings folder](/docs/settings#the-settings-files).

## Install Everyport on the machine

The first time you connect, the app checks the machine's OS and CPU and asks to install the matching `everyport`. It downloads the release from GitHub, copies it over the same connection, and checks its SHA-256 checksum on the machine. It installs to `~/.local/bin/everyport`, or `%LOCALAPPDATA%\everyport\everyport.exe` on Windows. When the app updates, it updates an `everyport` it installed there, without asking. An `everyport` you installed yourself, such as with Homebrew, stays as it is.

On read-only machines, or to install it yourself, use any command from [CLI only](https://github.com/greenfield-inc/everyport#cli-only).

## Use a machine from the terminal

```bash
everyport --on devbox                   # terminal UI for devbox
everyport --on devbox list --json
everyport --on devbox stop 5173
everyport --on devbox open 5173         # forwards the port and opens it here
```

`--on` works with `list`, `watch`, `stop`, `restart`, `open`, `clean`, `doctor` and the terminal UI. The machine can be a saved one or any host `everyport remote list` discovers.

The first time, `everyport` asks before installing itself there, and asks again before updating an older copy. Pass `--yes` to skip the question, as in scripts. Without a terminal to ask in, and without `--yes`, `everyport` stops with an error when it's missing, and uses an older copy as it is.

In the terminal UI, <kbd>Tab</kbd> or <kbd>m</kbd> switches between this computer and your saved machines.

## Open a remote server

Opening a remote server's URL forwards its port to this computer, then opens it in your browser:

| Connection | How the port is forwarded |
|---|---|
| SSH | `ssh -L` |
| Kubernetes | `kubectl port-forward` |
| WSL | Not needed. WSL servers already open at `localhost`. |
| Docker and other commands | `everyport connect` on the machine relays each connection through the same command |
| `everyport serve` | Not supported. Forward the port yourself. |

`everyport --on devbox open 5173` keeps the forward open until you press <kbd>Ctrl</kbd> <kbd>C</kbd>.

Opening a server's folder, its editor, or resuming its Claude Code or Codex session works for this computer and WSL only.

## Connect through Tailscale or a proxy

When a command connection won't work, such as from a browser or a device that can't run `ssh`, run `everyport serve` on the machine. It speaks the same protocol over HTTP:

```bash
tailscale serve --bg http://127.0.0.1:7767
everyport serve --url https://devbox.tail1234.ts.net
```

`everyport serve` listens only on loopback (`127.0.0.1:7767` by default, change it with `--listen`), so it's reachable only through a tunnel or proxy you set up. Every request needs the token in the connection code it prints. `--url` puts the address clients use into the code. `everyport serve` runs until you stop it, so keep it running, for example in `tmux` or as a service.

Add the machine with the connection code `everyport serve` printed, in **Settings → Machines → Add a machine**, or from the terminal:

```bash
everyport remote add devbox --code everyport://eyJ0b2tlbiI6…   # the whole code
```

The code holds the token, so share it only with people who may stop your servers. To make a new token, delete `serve-token` from the [settings folder](/docs/settings#the-settings-files) and restart `everyport serve`.

### From a web page

Browsers can't read `everyport serve` responses unless you allow the page's origin. Pass `--allow-origin` once per origin:

```bash
everyport serve --allow-origin https://dash.example.com --allow-origin http://localhost:5173
```

See [the protocol](/docs/protocol#everyport-serve) for the HTTP API.

---

<!-- https://everyport.dev/docs/settings -->

# Settings and clean up

Open Settings with the gear next to **Clean up** in the popover, or **Settings…** in the tray menu. It has three sections: General, Machines and Clean up. Changes apply as soon as you make them, and apply to this computer. To change the limits on another machine, edit the `config.toml` there.

## General

| Setting | Default | What it does |
|---|---|---|
| Launch at login | Off | Starts the app when you log in |
| Open Everyport | <kbd>⌥</kbd> <kbd>⌘</kbd> <kbd>P</kbd> (<kbd>Ctrl</kbd> <kbd>Alt</kbd> <kbd>P</kbd>) | The global shortcut. Click it and press new keys, or <kbd>Esc</kbd> to cancel. On macOS it needs <kbd>⌥</kbd> with <kbd>⌘</kbd> or <kbd>⌃</kbd>, or <kbd>⌘</kbd> and <kbd>⌃</kbd> together. On Windows and Linux it needs <kbd>Alt</kbd>, or <kbd>Win</kbd> and <kbd>Ctrl</kbd> together. **Reset** restores the default. |
| Scan every | 2 seconds | How often Everyport checks ports and processes: 1, 2, 5 or 10 seconds |
| Updates → Check for updates automatically | On | Checks GitHub for a newer release when the app starts and every 12 hours. See [Updating](/docs/getting-started#updating). |
| Updates → Everyport and its version | | What the last check found. **Check Now** checks, **Update** updates, and **Skip This Version** hides a release until a newer one comes out. |
| Integrations → Vercel previews | Off | Links each branch to its Vercel preview. It uses the GitHub CLI (`gh`), which goes online. |
| Appearance → Mode | System | System, Light or Dark |
| Appearance → Theme | Doozy Default | The color theme. There are 40. |

**Documentation**, at the bottom of General, opens these docs.

## Machines

The first row is **This computer**. Below it are the machines you've added, each with its status: Not connected, Connecting…, Everyport isn't installed, Installing Everyport…, Connected, or Can't connect. **Remove** forgets a machine.

**Add a machine** takes a name and either a command that runs a program on the machine, such as `ssh devbox`, or an `everyport://` connection code from `everyport serve`. See [Remote machines](/docs/machines).

**How machines connect** sums up the setup, and **?** beside the title opens [Connect a machine](https://github.com/greenfield-inc/everyport/blob/main/docs/machines.md). **Check** beside a machine runs the connection check and shows each step, with the fix under the one that failed.

**Found on this computer** lists machines the app discovered: hosts in `~/.ssh/config`, your Pane remote hosts, online Tailscale peers and, on Windows, WSL distros. **Add** saves one. WSL distros show in the app without being added.

When a machine has no `everyport`, **Install Everyport…** asks before it copies the matching binary over the same connection and checks its checksum. When the app updates, it updates the copy it installed without asking.

## Clean up

### Alerts

| Setting | Default | Options |
|---|---|---|
| Alert when a server uses more than | 2 GB | 512 MB, 1, 2, 4, 8 or 16 GB |
| Call it a leak when it grows by (within 10 minutes) | 500 MB | 250 MB, 500 MB, 1 GB or 2 GB |

### Suggest stopping servers that

**Clean up** in the popover suggests stopping servers that:

| Reason | Default | Options |
|---|---|---|
| Had their folder or worktree deleted | Always on | |
| Have been idle for (no CPU and no open connections) | 4 hours | 1, 2, 4, 8 or 24 hours |
| Have been running for | 3 days | 1, 3, 7 or 14 days |
| Are leaking memory, by the leak setting above | Always on | |

### Auto-kill

**When servers qualify** decides what happens when a server starts to qualify for clean up:

| Mode | What happens |
|---|---|
| Off (default) | Nothing. The server waits under Clean up. |
| Ask | A notification asks before stopping it. |
| Stop them | The app stops it for you. |

Auto-kill acts only on a server that newly qualifies, so turning it on never stops servers that already qualified. It never stops a leaking server or a protected one. Those stay under Clean up for you to decide.

Auto-kill runs only in the desktop app, for this computer. `everyport watch`, the terminal UI and `everyport stdio` never turn it on by themselves. To stop what Clean up suggests from the terminal, run `everyport clean`. It asks first, unless you pass `--yes`.

### Protected processes

**Never stop** lists processes that Clean up and auto-kill always skip, and that Stop and Restart ask about first. A server is protected when any process in its tree has one of these names. The defaults are `postgres`, `redis-server`, `mongod`, `mysqld` and `mysql`. Click **×** to remove one, or type a name in **Add…** and press <kbd>Enter</kbd>.

In the terminal, `everyport stop` and `everyport restart` refuse a protected server. Pass `--protected` to stop or restart it anyway, or `everyport stop --force` to kill it.

## The settings files

Settings live in one folder:

| OS | Folder |
|---|---|
| macOS | `~/Library/Application Support/everyport` |
| Windows | `%APPDATA%\everyport` |
| Linux | `~/.config/everyport` |

| File | Holds | Used by |
|---|---|---|
| `config.toml` | Scanning, alerts, clean up, protected processes, auto-kill, Vercel previews | The app and the CLI |
| `app.toml` | Theme, mode, shortcut and update checks | The app |
| `machines.toml` | Machines you've added | The app and `everyport remote` |
| `serve-token` | The `everyport serve` token | `everyport serve` |

Every key in `config.toml` is optional. Keys you leave out take their defaults:

```toml
min_port = 3000                  # the lowest port everyport shows (not in Settings)
max_port = 65535                 # the highest port everyport shows (not in Settings)
interval_ms = 2000
alert_memory = 2147483648        # bytes
leak_growth = 524288000          # bytes
idle_after_secs = 14400
long_running_after_secs = 259200
protected = ["postgres", "redis-server", "mongod", "mysqld", "mysql"]
auto_kill = "off"                # "off", "ask" or "act" (Stop them)
vercel_previews = false
```

The app picks up edits to the file right away. The CLI reads it when a command starts. If the file can't be read, the app shows the error in Settings and keeps the last settings that worked, and `everyport doctor` names the problem. On another machine, Everyport uses that machine's own `config.toml`.

---

<!-- https://everyport.dev/docs/cli -->

# CLI reference

Every `everyport` command and its options, exactly as `everyport <command> --help` prints them. For what each one is for, see the [README](https://github.com/greenfield-inc/everyport#cli).

## `everyport`

```text
See every dev server running on your machine. Run with no command for the terminal UI.

Usage: everyport [OPTIONS] [COMMAND]

Commands:
  list     List servers once
  watch    Print a snapshot on every change
  stop     Stop the server on a port
  restart  Stop it, then rerun its command in the folder it started from
  open     Open the server in your browser, forwarding its port with --on
  clean    Stop the servers Clean up suggests
  stdio    Speak the Everyport protocol on stdin and stdout
  serve    Speak the Everyport protocol over HTTP on loopback
  remote   Manage remote machines
  doctor   Check permissions and platform support, and the way to each machine
  update   Update everyport to the newest release
  help     Print this message or the help of the given subcommand(s)

Options:
      --on <MACHINE>  Run the command on another machine
  -y, --yes           Don't ask: install everyport on the machine, or stop what clean suggests
  -h, --help          Print help
  -V, --version       Print version
```

## `everyport list`

```text
List servers once

Usage: everyport list [OPTIONS]

Options:
      --json  Print the snapshot as JSON
  -y, --yes   Don't ask: install everyport on the machine, or stop what clean suggests
  -h, --help  Print help
```

## `everyport watch`

```text
Print a snapshot on every change

Usage: everyport watch [OPTIONS] --jsonl

Options:
      --jsonl  One JSON snapshot per line
  -y, --yes    Don't ask: install everyport on the machine, or stop what clean suggests
  -h, --help   Print help
```

## `everyport stop`

```text
Stop the server on a port

Usage: everyport stop [OPTIONS] <PORT>

Arguments:
  <PORT>  

Options:
      --force      Kill instead of asking it to quit, even if it's protected
      --protected  Stop it even if it's protected, asking it to quit first
  -y, --yes        Don't ask: install everyport on the machine, or stop what clean suggests
  -h, --help       Print help
```

## `everyport restart`

```text
Stop it, then rerun its command in the folder it started from

Usage: everyport restart [OPTIONS] <PORT>

Arguments:
  <PORT>  

Options:
      --protected  Restart it even if it's protected
  -y, --yes        Don't ask: install everyport on the machine, or stop what clean suggests
  -h, --help       Print help
```

## `everyport open`

```text
Open the server in your browser, forwarding its port with --on

Usage: everyport open [OPTIONS] <PORT>

Arguments:
  <PORT>  

Options:
  -y, --yes   Don't ask: install everyport on the machine, or stop what clean suggests
  -h, --help  Print help
```

## `everyport clean`

```text
Stop the servers Clean up suggests

Usage: everyport clean [OPTIONS]

Options:
  -y, --yes   Don't ask: install everyport on the machine, or stop what clean suggests
  -h, --help  Print help
```

## `everyport stdio`

```text
Speak the Everyport protocol on stdin and stdout

Usage: everyport stdio [OPTIONS]

Options:
  -y, --yes   Don't ask: install everyport on the machine, or stop what clean suggests
  -h, --help  Print help
```

## `everyport serve`

```text
Speak the Everyport protocol over HTTP on loopback

Usage: everyport serve [OPTIONS]

Options:
      --listen <LISTEN>        Loopback address to listen on [default: 127.0.0.1:7767]
      --url <URL>              URL clients use to reach this server, such as a Tailscale URL, for the connection code
  -y, --yes                    Don't ask: install everyport on the machine, or stop what clean suggests
      --allow-origin <ORIGIN>  Web origin whose pages may use the server, such as https://dash.example.com (repeatable)
  -h, --help                   Print help
```

## `everyport remote`

```text
Manage remote machines

Usage: everyport remote [OPTIONS] <COMMAND>

Commands:
  add   Save a machine: `everyport remote add devbox -- ssh devbox`, or `--code` from `everyport serve`
  list  List saved and discovered machines, and the everyport installed on each
  rm    Remove a saved machine
  help  Print this message or the help of the given subcommand(s)

Options:
  -y, --yes   Don't ask: install everyport on the machine, or stop what clean suggests
  -h, --help  Print help
```

## `everyport remote add`

```text
Save a machine: `everyport remote add devbox -- ssh devbox`, or `--code` from `everyport serve`

Usage: everyport remote add [OPTIONS] <NAME> [-- <COMMAND>...]

Arguments:
  <NAME>        Name to use with --on
  [COMMAND]...  Command prefix that runs a program on the machine

Options:
      --code <CODE>  Connection code that `everyport serve` prints
  -y, --yes          Don't ask: install everyport on the machine, or stop what clean suggests
  -h, --help         Print help
```

## `everyport remote list`

```text
List saved and discovered machines, and the everyport installed on each

Usage: everyport remote list [OPTIONS]

Options:
  -y, --yes   Don't ask: install everyport on the machine, or stop what clean suggests
  -h, --help  Print help
```

## `everyport remote rm`

```text
Remove a saved machine

Usage: everyport remote rm [OPTIONS] <NAME>

Arguments:
  <NAME>  

Options:
  -y, --yes   Don't ask: install everyport on the machine, or stop what clean suggests
  -h, --help  Print help
```

## `everyport doctor`

```text
Check permissions and platform support, and the way to each machine

Usage: everyport doctor [OPTIONS]

Options:
      --on <MACHINE>  Check only the way to this machine
  -y, --yes           Don't ask: install everyport on the machine, or stop what clean suggests
  -h, --help          Print help
```

## `everyport update`

```text
Update everyport to the newest release

Runs the CLI's install command, which replaces this everyport. A copy from Homebrew, cargo, npm or PyPI prints that package manager's command instead. `everyport --version` in a terminal also says when a newer release is out, checking at most once a day. Set EVERYPORT_NO_UPDATE_CHECK=1 to turn that off.

Usage: everyport update [OPTIONS]

Options:
  -y, --yes
          Don't ask: install everyport on the machine, or stop what clean suggests

  -h, --help
          Print help (see a summary with '-h')
```

---

<!-- https://everyport.dev/docs/protocol -->

# The Everyport protocol

`everyport` reports the servers on a machine as a stream of JSON events and takes JSON requests to act on them. Anything that can run a command or open a URL can use it: an editor extension, a status bar, a dashboard, or a workspace app.

There are two transports, with the same JSON:

- `everyport stdio`: events on stdout, requests on stdin. The desktop app runs it as a sidecar, and runs it on remote machines through `ssh`, `docker exec -i`, `kubectl exec -i --` or `wsl`.
- `everyport serve`: HTTP on loopback, with events as server-sent events.

The types are defined in [`crates/everyport/src/protocol.rs`](https://github.com/greenfield-inc/everyport/blob/main/crates/everyport/src/protocol.rs), and TypeScript types are generated from it into `@everyport/protocol`.

## Conventions

- Timestamps are Unix milliseconds.
- Memory is bytes.
- CPU values are floats, in percent of one core, so a busy server on an 8-core machine can report up to 800.0. `system.cpu_percent` is whole-machine CPU, from 0 to 100.
- Optional fields are always present, as `null` when empty. The tables below mark them "or `null`".
- "Clean up" is the list of servers `everyport` suggests stopping: those whose worktree was deleted, or that are idle, long-running or leaking.

## Framing

On stdio, every message is one line of JSON ending in `\n`: one event per line on stdout, and one request per line on stdin. Blank lines on stdin are ignored.

Over HTTP, events are server-sent events with one event per `data:` line, and a request is the body of a `POST`. See [everyport serve](#everyport-serve).

## Handshake

Every connection starts with `hello`, then a `snapshot` of the current state. After that, `everyport` sends:

- a `snapshot` whenever anything changes (it scans every 2 s by default)
- an `alert` when a server crosses a threshold
- a `result` for each request

A client needs no reply to `hello`. After a request, its `result` comes first, then any snapshot it caused. `everyport stdio` exits with status 0 when stdin closes, after answering every request it already read.

In this transcript, → is a line from `everyport` and ← a line to it.

```text
→ {"type":"hello","protocol":1,"everyport_version":"0.1.0","host":{"hostname":"devbox","os":"linux","arch":"x86_64","cores":8}}
→ {"type":"snapshot","taken_at":1790195040000,"system":{...},"servers":[...]}
← {"id":1,"method":"refresh"}
→ {"type":"result","id":1,"error":null}
→ {"type":"snapshot","taken_at":1790195041000,"system":{...},"servers":[...]}
```

## Events

Every event has a `type`.

### hello

The first event on every connection.

```json
{
  "type": "hello",
  "protocol": 1,
  "everyport_version": "0.1.0",
  "host": { "hostname": "devbox", "os": "linux", "arch": "x86_64", "cores": 8 }
}
```

`os` is `macos`, `linux` or `windows`. `arch` is the Rust target arch, such as `aarch64` or `x86_64`.

### snapshot

The full state. It's sent after `hello`, after every scan where something other than `taken_at` changed, and after every `refresh`. Replace your state with it; there are no diffs.

```json
{
  "type": "snapshot",
  "taken_at": 1790195040000,
  "system": {
    "memory_total": 17179869184,
    "memory_used": 12777527706,
    "memory_other_apps": 7516192768,
    "cpu_percent": 18.0
  },
  "servers": [
    {
      "port": 3000,
      "pid": 48213,
      "root": { "pid": 48198, "started_at": 1790183520000 },
      "process_name": "node",
      "addresses": ["127.0.0.1", "::1"],
      "cwd": "/Users/dev/conductor/workspaces/everyport/providence",
      "cwd_exists": true,
      "command": "npm run dev",
      "launch_dir": "/Users/dev/conductor/workspaces/everyport/providence",
      "started_at": 1790183520000,
      "project": {
        "name": "everyport",
        "root": "/Users/dev/conductor/workspaces/everyport/providence",
        "framework": "Next.js",
        "branch": "menubar-port-monitor",
        "worktree": "providence",
        "github": "greenfield-inc/everyport",
        "vercel": {
          "project_id": "prj_everyport",
          "preview_url": "https://everyport-git-menubar-port-monitor.vercel.app"
        }
      },
      "workspace": { "kind": "conductor", "name": "providence", "open_url": null },
      "agent": {
        "kind": "claude_code",
        "id": "68c8fda6-2f4e-4c1a-9a7b-1d2e3f4a5b6c",
        "title": "Tray count badge",
        "started_at": 1790183040000,
        "transcript_path": "/Users/dev/.claude/projects/providence/68c8fda6.jsonl",
        "directory": "/Users/dev/conductor/workspaces/everyport/providence",
        "resume_command": "claude --resume 68c8fda6-2f4e-4c1a-9a7b-1d2e3f4a5b6c"
      },
      "processes": [
        { "proc": { "pid": 48198, "started_at": 1790183520000 }, "name": "npm run dev", "depth": 0, "memory": 60817408, "cpu_percent": 0.1 },
        { "proc": { "pid": 48213, "started_at": 1790183520900 }, "name": "next-server", "depth": 1, "memory": 1095761920, "cpu_percent": 9.8 }
      ],
      "memory": 1328545792,
      "cpu_percent": 12.0,
      "connections": 4,
      "history": [
        { "at": 1790194440000, "memory": 1267015352, "cpu_percent": 3.0 },
        { "at": 1790194470000, "memory": 1305507759, "cpu_percent": 4.0 }
      ],
      "last_active": 1790195000000,
      "protected": false,
      "status": "running",
      "clean_up": null
    }
  ],
  "other_ports": [
    { "port": 5432, "addresses": ["0.0.0.0"], "owner": "root", "process_name": "docker-proxy" }
  ]
}
```

`system`:

| Field | Meaning |
|---|---|
| `memory_total`, `memory_used` | The machine's physical memory and how much is in use |
| `memory_other_apps` | Memory used by everything that isn't a listed server |
| `cpu_percent` | Whole-machine CPU, 0 to 100 |

`servers` is sorted by port. Each server is a listening port and the process tree behind it:

| Field | Meaning |
|---|---|
| `port` | The listening TCP port |
| `pid` | The process that owns the socket |
| `root` | The topmost process of the server's tree, such as `npm run dev`. When one command runs several servers, such as `concurrently` starting an API and Vite, each server's tree starts just below where their trees meet, so `stop` and `restart` cover that server only. Pass it to `stop` and `restart`. |
| `process_name` | Name of the process that owns the socket |
| `addresses` | Bound addresses, such as `127.0.0.1` and `::1` |
| `cwd`, `cwd_exists` | The server's folder (or `null`), and whether it still exists. It's `false` once a worktree is deleted. |
| `command`, `launch_dir` | The root's command line and the folder it started in, or `null`. `restart` runs `command` in `launch_dir`. |
| `started_at` | When the root process started, or `null` |
| `project` | `name` (from `package.json`, the repo folder or the folder) is always set. For `/` and folders under a package manager or the OS, such as `/opt/homebrew/var/postgresql@15`, it's the process name. `root`, `framework`, `branch`, `worktree`, `github` (`owner/repo`) and `vercel` are `null` when unknown. |
| `workspace` | The Conductor workspace, Pane worktree or git worktree the server runs in, or `null`. `kind` is `conductor`, `pane` or `git_worktree`. `open_url` opens it in its app, such as `pane://open?pane=<id>&panel=<id>`, or is `null`. |
| `agent` | The Claude Code or Codex session that started the server, or `null`. `kind` is `claude_code` or `codex`. `resume_command` resumes it in `directory`. |
| `processes` | The whole tree, depth first and root first. `depth` is 0 for the root. |
| `memory`, `cpu_percent` | Sums over `processes`. A process that several servers hold, such as one listening on two ports, counts only on the lowest port, so the servers' sum counts each process once. |
| `connections` | Open connections to the port |
| `history` | Samples covering up to the last 10 minutes, oldest first. Don't assume a fixed spacing. |
| `last_active` | Last time the server had connections or used CPU |
| `protected` | A process in its tree is on the protected list, such as `postgres`. Clean up never suggests it, and `stop` and `restart` need `confirm_protected`. |
| `status` | `running`, `attention` (over the memory threshold or leaking) or `idle` (idle for over an hour, or its folder is gone) |
| `clean_up` | Why Clean up suggests stopping it, or `null` |

`clean_up` is one of:

```json
{ "kind": "worktree_deleted" }
{ "kind": "idle", "seconds": 18000 }
{ "kind": "long_running", "seconds": 259200 }
{ "kind": "leaking", "bytes": 1191182336 }
```

`other_ports` lists the ports that processes of other users or the system hold, such as a Docker-published port or a system database. `everyport` can't inspect those processes, so these ports have no tree and no actions. The list is sorted by port and covers the configured port range. A port in `servers` never appears here.

| Field | Meaning |
|---|---|
| `port` | The listening TCP port |
| `addresses` | Bound addresses, such as `0.0.0.0` and `::` |
| `owner` | The user the process runs as, such as `root`, or `null` when the OS doesn't say |
| `process_name` | Name of the process that owns the socket, or `null` when the OS doesn't say. Linux doesn't tell a normal user which process holds another user's socket. |

On macOS and Linux, when `everyport` runs as root, every port is a server and `other_ports` is empty. On macOS, `everyport` reads other users' ports from `nettop` at most every 10 seconds, so a new one can take that long to appear. macOS lists other users' sockets only to its own tools.

### alert

A server crossed a threshold. `everyport` sends it once, and again only after the server recovers and crosses it again. Clients decide whether and how to notify.

```json
{ "type": "alert", "port": 6006, "kind": "leaking", "memory": 3017089024 }
```

`kind` is `over_threshold` (memory above [`alert_memory`](#configure)), `leaking` (grew by [`leak_growth`](#configure) over the history window), or `clean_up` (Clean up started to suggest stopping it and [`auto_kill`](#configure) is `ask`).

### result

The answer to the request with the same `id`. `error` is `null` on success, or a message for the user.

```json
{ "type": "result", "id": 7, "error": null }
{ "type": "result", "id": 8, "error": "pid 48198 has exited or now belongs to another process" }
```

A line that isn't a valid request still gets a `result` with an error. Its `id` is the request's `id` when one can be read, and `0` otherwise, so avoid using `0` as an id.

## Requests

A request has a numeric `id` that you choose, a `method`, and `params` for the methods that take them. Every field of `params` is required, except in `configure`, which takes only the fields to change. Requests run one at a time, in order.

Use ids from 1 up to 2^53, so JavaScript clients keep them exact. `everyport` only echoes them back, so they need to be unique only among your requests in flight.

### refresh

Scan now and send a fresh snapshot, even if nothing changed.

```json
{ "id": 1, "method": "refresh" }
```

### stop

Stop a server's process tree, deepest processes first. `everyport` asks each process to quit, and kills whatever is left after 3 s. With `force: true`, it kills at once. On macOS and Linux, asking is SIGTERM and killing is SIGKILL. On Windows, asking sends Ctrl+C to the server's console when only the server and the shells that launched it are on that console. Otherwise it closes the process's windows, or terminates a process that has none. Killing terminates it. `port` is the server's port, and `root` must be the server's `root` from the snapshot. `everyport` checks every process's start time first, so it never signals a process whose pid was reused.

A server is protected when any process in its tree is on the `protected` list, such as `postgres`. `everyport` refuses to stop it unless `confirm_protected` is `true`, and the error `result` names the protected process, such as `postgres :5432 is protected; send confirm_protected to stop it anyway`. `confirm_protected` defaults to `false`. Clients ask the user before sending `true`.

```json
{ "id": 2, "method": "stop", "params": { "port": 3000, "root": { "pid": 48198, "started_at": 1790183520000 }, "force": false, "confirm_protected": false } }
```

The result arrives as soon as the processes are asked to quit. The server leaves the snapshot once its tree is gone.

### restart

Stop the server, then run its `command` again in its `launch_dir`, detached from `everyport`. The result arrives as soon as the old tree is asked to quit. Once that tree is gone and `port` is free, `everyport` starts the command, and the new server shows up in a later snapshot.

A protected server needs `confirm_protected: true`, as for `stop`. An error `result` covers what `everyport` can check up front: the server is protected, the process changed, or its command or folder can't be read. A failure after that gets no second `result`. The server just doesn't come back on `port` in the snapshots over the next 10 s or so. The new server's output is in `everyport/port-<port>.log` in the system temp folder (`$TMPDIR` or `%TEMP%`), and a failure to start it is one line on `everyport`'s stderr.

```json
{ "id": 3, "method": "restart", "params": { "port": 3000, "root": { "pid": 48198, "started_at": 1790183520000 }, "confirm_protected": false } }
```

### configure

Change scanner settings. Fields you leave out keep their current value; `everyport` starts from `config.toml`. On stdio, they apply to that connection's scanner. Over HTTP, one scanner serves every client, so they apply to all of them.

```json
{
  "id": 4,
  "method": "configure",
  "params": {
    "min_port": 3000,
    "max_port": 65535,
    "interval_ms": 2000,
    "alert_memory": 2147483648,
    "leak_growth": 524288000,
    "idle_after_secs": 14400,
    "long_running_after_secs": 259200,
    "protected": ["postgres", "redis-server", "mongod", "mysqld", "mysql"],
    "auto_kill": "off",
    "vercel_previews": false
  }
}
```

These are the defaults.

| Field | Meaning |
|---|---|
| `min_port`, `max_port` | Ports to report |
| `interval_ms` | Time between scans |
| `alert_memory` | Memory above which a server needs attention and raises an alert |
| `leak_growth` | Growth over the history window that counts as leaking |
| `idle_after_secs` | Idle time after which Clean up suggests a server |
| `long_running_after_secs` | Uptime after which Clean up suggests a server |
| `protected` | Process names that protect a server: Clean up never suggests it, and `stop` and `restart` need `confirm_protected` |
| `auto_kill` | What happens when a server starts to qualify for Clean up while `everyport` watches: `off` lists it, `ask` also sends a `clean_up` alert, `act` stops it. Servers that already qualify when `everyport` starts or when `auto_kill` changes are only listed, and so are leaking servers and servers whose process tree runs a protected process. |
| `vercel_previews` | Look up each branch's Vercel preview through the GitHub CLI (`gh`), which goes online |

Every `everyport` command starts from `config.toml` in the Everyport config folder (see [everyport serve](#everyport-serve) for where it is), which the desktop app writes from Settings. It holds these same fields, and any it leaves out take their defaults. `everyport` never auto-kills on its own: `auto_kill` starts `off` whatever the file says, and only a client that sends it in `configure` turns it on. The desktop app does that for its own computer only.

## Versioning

`hello.protocol` is `1`. It goes up only when a change would break existing clients, such as removing or renaming a field or changing its meaning. New fields, event types and methods are added without a bump, so:

- ignore fields you don't know
- ignore events whose `type` you don't know
- expect an error `result` from an older `everyport` for a method it doesn't know

If `hello.protocol` is higher than the version you support, ask the user to update the client, and show `hello.everyport_version` in the message.

## Other machines

On another machine, run the same `everyport stdio` through a command prefix, such as `ssh devbox everyport stdio` or `docker exec -i box everyport stdio`. The protocol is the same.

To open a server from a machine whose prefix can't forward ports, such as `docker exec -i`, run `everyport connect <port>` through the prefix. It connects to `localhost:<port>` on that machine and pipes the connection to its stdin and stdout, so a client can relay one TCP connection per `everyport connect`. It exits when the server closes the connection, and fails with status 1 when nothing answers on the port. `everyport connect --check <port>` only connects and exits, so a client can check the port before it listens locally. ssh and kubectl forward ports themselves, so they don't need it.

## everyport serve

```bash
everyport serve                                   # listens on 127.0.0.1:7767
everyport serve --listen 127.0.0.1:8080
everyport serve --url https://devbox.tail1234.ts.net
everyport serve --allow-origin https://dash.example.com
```

`everyport serve` listens only on loopback, and refuses any other address. Put a tunnel or proxy you trust in front of it, such as `tailscale serve --bg http://127.0.0.1:7767`.

### Token and connection code

Every request needs `Authorization: Bearer <token>`. The token is created on first run and kept in `serve-token` in the Everyport config folder (`~/Library/Application Support/everyport` on macOS, `%APPDATA%\everyport` on Windows, `~/.config/everyport` on Linux), readable only by your user. To make a new token, delete the file and restart `everyport serve`.

On start, `everyport serve` prints a connection code:

```text
everyport://eyJ0b2tlbiI6Ii4uLiIsInVybCI6Imh0dHA6Ly8xMjcuMC4wLjE6Nzc2NyJ9
```

It's `everyport://` followed by the unpadded base64url encoding of a JSON object with the URL and the token:

```json
{ "url": "http://127.0.0.1:7767", "token": "..." }
```

`url` is the listen address unless you pass `--url`, which you should when clients reach the server through a tunnel. The code contains the token, so share it only with people who may control your servers.

### GET /events

A server-sent event stream. Each event is one `data:` line with the event's JSON, followed by a blank line. It starts with `hello` and a `snapshot`, like stdio. A `: ping` comment arrives every 15 s so proxies keep the stream open. There are no `id:` or `event:` fields and no resume: after a reconnect, you get `hello` and a full `snapshot` again.

```bash
curl -N -H "Authorization: Bearer $TOKEN" http://127.0.0.1:7767/events
```

```text
data: {"type":"hello","protocol":1,"everyport_version":"0.1.0","host":{...}}

data: {"type":"snapshot","taken_at":1790195040000,"system":{...},"servers":[...]}

: ping

```

Results come only in the `/call` response.

### POST /call

Runs one request and returns its `result` event as the response body. The body is read as JSON whatever its `Content-Type`.

```bash
curl -H "Authorization: Bearer $TOKEN" -d '{"id":1,"method":"refresh"}' http://127.0.0.1:7767/call
```

```json
{"type":"result","id":1,"error":null}
```

Snapshot changes the request causes arrive on `/events`.

### Status codes

| Status | When |
|---|---|
| `200` | `/events` stream, or `/call` ran the request. A failed request is still `200`, with `error` set. |
| `400` | The body isn't a valid request (the body is a `result` with the error), or the HTTP request is malformed |
| `401` | The token is missing or wrong |
| `404`, `405` | Unknown path, or the wrong method for it |

### Browsers

By default, responses carry no CORS headers, so a web page can't read them, even with a leaked connection code. To use the API from a page, allow its origin with `--allow-origin`, once per origin:

```bash
everyport serve --allow-origin https://dash.example.com --allow-origin http://localhost:5173
```

Responses to a request from an allowed origin carry `Access-Control-Allow-Origin` with that origin, and `OPTIONS` preflight requests from it are answered without a token. Every other request still needs the token. `EventSource` can't send headers, so read `/events` with `fetch` and a stream reader.

---

<!-- https://everyport.dev/docs/troubleshooting -->

# Troubleshooting

## Start with `everyport doctor`

`everyport doctor` checks what Everyport can see on this machine and exits with status 1 if a check fails:

```text
$ everyport doctor
everyport 0.1.0 · protocol 1 · macOS aarch64 · 10 cores
✓ Listening ports: 35 found
✓ Processes: 1083 found
✓ Folder and command of your own processes
✓ Memory and CPU of your own processes
✓ Settings folder: /Users/you/Library/Application Support/everyport
```

On Linux it also prints a tip for [blank windows on NVIDIA](#the-window-is-blank-on-nvidia). It then checks the way to every saved and found machine. `everyport doctor --on devbox` checks one, and stops at the first failed step with its fix. See [Connect a machine](https://github.com/greenfield-inc/everyport/blob/main/docs/machines.md).

## `everyport: command not found`

The install scripts put `everyport` in `~/.local/bin`, which isn't on every `PATH`. Add it in your shell profile, such as `~/.zshrc` or `~/.bashrc`:

```bash
export PATH="$HOME/.local/bin:$PATH"
```

Over SSH, the shell may skip your profile, so call it by its full path: `ssh devbox '~/.local/bin/everyport doctor'`.

## A server is missing

- Everyport shows ports 3000 to 65535. To change the range, set `min_port` and `max_port` in [`config.toml`](/docs/settings#the-settings-files).
- A server run by another user or the system shows with fewer details. On macOS it can take up to 10 seconds to appear. See [Other users' servers](#other-users-servers).

## Other users' servers

Without admin rights, Everyport sees full details only for processes your user owns.

| OS | Ports owned by other users or the system |
|---|---|
| Linux | Listed with port, address and owner. Linux shows which process holds the port only to root, so run `sudo everyport` to see it. |
| macOS | Listed with port, owner and process name, read from `nettop`. A new one can take up to 10 seconds to appear. |
| Windows | Listed with port, owner and process name. Without admin rights, the owner can be missing. |

## Stop or restart asks first, or refuses in the terminal

The server is protected: a process in its tree is on the **Never stop** list, such as `postgres`. The app asks you to confirm. In the terminal, `everyport stop` and `everyport restart` refuse it:

```text
everyport: redis-server :6379 is protected. Run `everyport stop 6379 --protected` to stop it anyway.
```

Pass `--protected` to `everyport stop` or `everyport restart` to go ahead, or `--force` to `everyport stop` to kill it. See [Protected processes](/docs/settings#protected-processes).

## A settings file has an error

The app shows the error at the top of Settings and keeps the last settings it could read. `everyport doctor` prints `Settings:` with the error. Fix or delete the file named in the error. See [The settings files](/docs/settings#the-settings-files).

## Windows

### The install command fails

Run it in PowerShell, not Command Prompt, where it fails with `'irm' is not recognized`. It works in Windows PowerShell 5.1, which ships with Windows, and in PowerShell 7:

```powershell
[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor 3072; irm https://everyport.dev/install.ps1 | iex
```

Options need the script block form. `-Cli` installs only the CLI, and `-NoOpen` skips opening the app:

```powershell
[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor 3072; & ([scriptblock]::Create((irm https://everyport.dev/install.ps1))) -Cli
```

If the download still fails with a TLS or connection error, install [PowerShell 7](https://aka.ms/powershell) and run the command in `pwsh`.

### SmartScreen asks before the installer runs

Windows builds aren't signed yet. If you download the `.msi` in a browser and open it, SmartScreen shows **Windows protected your PC**. Click **More info**, then **Run anyway**. The PowerShell install command doesn't show this prompt, and it checks the download against the release's SHA-256 checksums.

## Linux

### No tray icon on GNOME

GNOME hides tray icons. Install the [AppIndicator and KStatusNotifierItem Support](https://extensions.gnome.org/extension/615/appindicator-support/) extension, then log out and back in. Ubuntu ships it turned on.

Many Linux desktops open the tray menu on a left click. Pick **Open Everyport** at the top of the menu, or press <kbd>Ctrl</kbd> <kbd>Alt</kbd> <kbd>P</kbd>.

### The window is blank on NVIDIA

WebKitGTK can draw a blank window with NVIDIA drivers. Start the app with the environment variable `WEBKIT_DISABLE_DMABUF_RENDERER=1`. For the AppImage:

```bash
WEBKIT_DISABLE_DMABUF_RENDERER=1 ./everyport-0.1.0-x86_64.AppImage
```

For the `.deb` or `.rpm`, add `env WEBKIT_DISABLE_DMABUF_RENDERER=1` to the start of the `Exec=` line in the app's `.desktop` file.

## Remote machines

- **Can't connect**: run the machine's command yourself, such as `ssh devbox`, and fix what it prints. The command must run without asking for a password; for SSH, use a key or agent.
- **Everyport isn't installed**: click **Install Everyport…** in Settings → Machines, or install it yourself with any command from [CLI only](https://github.com/greenfield-inc/everyport#cli-only). `everyport remote list` shows the everyport on each machine.
- **`everyport serve` exits with `is not a loopback address`**: it listens only on `127.0.0.1` or `::1`. Put a tunnel or proxy in front of it. See [Remote machines](/docs/machines#connect-through-tailscale-or-a-proxy).
- **A web page can't read `everyport serve`**: start it with `--allow-origin` for the page's origin.

---

<!-- https://everyport.dev/docs/faq -->

# FAQ

## Is Everyport free?

Yes. It's open source under the [MIT license](https://github.com/greenfield-inc/everyport/blob/main/LICENSE), with no account and no paid tier.

## Does it send anything over the internet?

No telemetry and no account. The app goes online only to:

- look up Vercel previews through the GitHub CLI (`gh`), if you turn previews on
- check GitHub for a newer release, unless you turn that off in Settings. It asks GitHub where the latest release is and sends nothing else.
- download `everyport` from GitHub Releases when you install it on another machine

## Which servers does it show?

Every process listening on a port from 3000 to 65535. To change the range, set `min_port` and `max_port` in [`config.toml`](/docs/settings#the-settings-files). They aren't in the Settings window.

## Does it need admin rights?

No. It shows full details for processes your user owns, and less for ports owned by other users or the system. See [Other users' servers](/docs/troubleshooting#other-users-servers).

## Can it stop my database by accident?

Not by default. Databases such as `postgres` and `redis-server` are on the **Never stop** list. Clean up and auto-kill skip them, and Stop and Restart ask first. In the terminal, `everyport stop` and `everyport restart` refuse them without `--protected`. Edit the list in [Settings → Clean up](/docs/settings#protected-processes).

## Can I see servers on another machine?

Yes: a devbox over SSH, a Docker container, a Kubernetes pod or a WSL distro. The first time you connect, Everyport asks, then installs itself there over the same connection, in `~/.local/bin` or `%LOCALAPPDATA%\everyport` on Windows. See [Machines](/docs/machines).

## Do I need the desktop app?

No. The `everyport` CLI has the same list in a terminal UI, and `everyport list --json` for scripts. To install only the CLI, run `curl -fsSL https://everyport.dev/install.sh | sh -s -- --cli`. For Windows and other ways, see [Getting started](/docs/getting-started#install).

## How do I update?

The app tells you when there's a new version. Click **Update to** at the bottom of the popover. For the CLI, run `everyport update`. See [Updating](/docs/getting-started#updating).

## How does it relate to WhatThePort?

Everyport began as a cross-platform port of [WhatThePort](https://github.com/tomjohndesign/what-the-port), a macOS app by Tomjohn Design. It adds Windows and Linux, remote machines, the CLI and a JSON protocol.

## Can I build my own tool on it?

Yes. `everyport stdio` and `everyport serve` speak the same JSON protocol, and `everyport watch --jsonl` prints a snapshot on every change. See [the protocol](/docs/protocol).
