Skip to content

ollfel/porthole

Repository files navigation

Porthole

A local macOS dashboard to manage every port that your machine is listening on. For each port, Porthole shows the process, working directory, git repo and branch, and the agent or shell that spawned it, and gives you the ability to instantly kill or restart.

macOS only. Single static binary. No daemon, no telemetry, no network access beyond 127.0.0.1.


Install

curl -fsSL https://raw.githubusercontent.com/ollfel/porthole/main/install.sh | bash

Or grab a release tarball directly from the Releases page and drop the binary in your $PATH.

Run

porthole

Then open http://127.0.0.1:47212.

The first poll runs immediately, then every 2 seconds. New ports flash with a "new" badge. Closing a process makes its row disappear and (after the next poll) move into history.

Flags and subcommands

porthole                    serve the dashboard (default)
porthole --port 47213       bind a different port
porthole clear-history      delete all closed-port rows
porthole clear-all          delete every row (live reappears on next poll)

Why

Increasingly developer workflows require co-ordination of multiple agents from different tools, working not just on different features but across different projects in parallel.

Each agent spins up its own dev server, databases and background processes, each on its own port: your local machine is a stateful environment that changes continuously without your explicit direction or, possibly, awareness. Your machine accumulates running servers, stale processes and occupied ports that you did not explicitly start and cannot easily see.

Remembering what each port is being used for and resolving conflicts forces you to context switch and acts as a drag on shipping what you care about.

Existing solutions do not account for multiple agents, from different AI tools, working across many projects concurrently:

  1. Terminal gives you lsof -i | grep LISTEN, a static snapshot of ports and PIDs but little else meaningful.
  2. Individual coding tools — Claude Code, Codex, Conductor, Cursor — operate with their own context, unaware of each other and what else is running.
  3. Docker Desktop solves the problem only for containerised processes.
  4. Other GUIs, such as Netiquette, show port usage but do not know about your project or agent workflows.

What

Porthole gives you continuous visibility into every process and which repo owns which port:

  1. Shows every running dev server — with the project name derived automatically from the process working directory, not a raw PID.
  2. Displays the port each server is running on.
  3. Kill or restart any server with a single click.
  4. Updates live — so when an agent spins something up or a process dies unexpectedly, you see it immediately.
  5. Surfaces silent failures — orphaned processes, stale servers, port conflicts — before they cause confusion or wasted debugging time.

Who it's for

Any developer who has ever:

  • Forgotten which project is running on which port
  • Tried to start a server only to find the port already in use
  • Lost track of what an AI agent started on their behalf
  • Juggled multiple projects simultaneously and wished they had a control panel
  • Spent ten minutes debugging a cryptic error that turned out to be a zombie process holding a port

Restart behavior

Restart re-runs the original command in its original working directory using Porthole's own environment. This works great for most dev servers — next dev, python3 -m http.server, bun run start, and anything else that reads config from files in cwd.

If your process needs custom env vars (virtualenvs, API keys, direnv, 1Password CLI, aws-vault, doppler, etc.), the restart will likely fail. Use Copy instead: it gives you cd {cwd} && {command} ready to paste into your own terminal, where your secret managers are already live. That way secrets stay in your shell, not in Porthole.


Security

Porthole binds 127.0.0.1 only, validates the Host header on every request (blocking DNS rebinding), and requires a same-origin Origin header on POST routes. /api/kill sends SIGTERM via process.kill(pid) and can only affect processes your user already owns.

History is stored at ~/.porthole/history.db (SQLite, 30-day retention). Clear it anytime with porthole clear-history.

See ARCHITECTURE.md for the full threat model.


Build from source

bun install
bun test                # 63 tests, ~200ms
bun run dev             # bun --hot, edit src/* and refresh
bun run build           # dist/porthole-darwin-arm64
bun run build:all       # arm64 + x64

The compiled binary embeds src/index.html at build time, so the static binary in dist/ is fully self-contained — no asset paths to chase.

Project layout

src/
  poller.ts        # lsof wrapper + diff
  enricher.ts      # cwd, git, parent-chain, docker heuristic
  db.ts            # bun:sqlite wrapper
  server.ts        # Bun.serve + SSE + CLI entry point
  index.html       # single-file frontend (no bundler)
  *.test.ts        # bun:test suites
migrations/
  0001_init.sql    # schema (also embedded in db.ts)
.github/workflows/
  ci.yml           # tests on every push
  release.yml      # tag → build arm64 + x64 → GitHub release
install.sh         # curl | bash installer
ARCHITECTURE.md    # how it's built (poll loop, data model, security)
DESIGN.md          # visual design system (typography, color, spacing)

Reporting issues

Found a bug, have a feature idea, or something just feels wrong? Open an issue at https://github.com/ollfel/porthole/issues. A bug report template is included to capture the most useful repro info.


License

MIT © 2026 ollfel

About

A local macOS dashboard to manage every port that your machine is listening on

Topics

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors