Free & open source · MIT

le — Localhost Explorer for the terminal.

A fast, keyboard-driven TUI for seeing what's listening on your machine and stopping it the right way. Single static binary, macOS and Linux — including servers over SSH. No account, no telemetry.

le running in a real terminal: a live table of listeners with risk-colored rails — port, process, directory, risk, owner, and the stop command that works — plus a detail pane explaining the selected Flask dev server
The real thing — a live table with risk rails, and a verdict pane for the selected row.

lsof -i :3000 gives you a PID. It doesn't tell you the PID belongs to a Homebrew service — or a launchd agent with KeepAlive — that respawns the moment you kill -9 it. le names the owner and picks the stop that actually works: TERM for plain processes, brew services stop for Homebrew, docker stop for containers, launchctl bootout for launchd agents — and when it can't confirm what a process is, it refuses to auto-kill and names the supervisor so you can decide.

Install

One line, or one tarball.

One line, any Mac or Linux box

curl -fsSL https://localhostexplorer.com/install.sh | sh

No Homebrew, no Go — downloads the latest release, verifies its checksum, installs it. Distrust curl | sh? Sensible: read the script — it's just over a hundred lines and refuses on any mismatch.

Homebrew (macOS)

brew install alikatgh/tap/le

Man pages and shell completions included. brew install alikatgh/tap/localhostexplorer works too.

Binaries, by hand (macOS + Linux)

tar -xzf le_*.tar.gz
sudo mv le /usr/local/bin/

amd64 and arm64 tarballs on the Releases page, checksummed, with a signed build-provenance attestation: gh attestation verify le_*.tar.gz --repo alikatgh/le-cli. Needs only lsof and ps — preinstalled on macOS, available on every Linux.

Commands

A verb for everything, no ceremony.

See & stop

le              # live TUI (default)
le list         # one-shot table and exit
le list node    # …filtered
le list --json  # …for scripts / jq
le stop 3000    # by port (or PID)
le stop --dir . # everything this project spun up
le stop --dir . -n # …dry run first

Hold & wait

le hold 3000    # squat a port (Ctrl-C frees)
le wait 5432    # block until a port frees up
le ready 8080   # block until something listens
le ready 8080 -t 30s # …bounded, for CI

le ready 5173 && open http://localhost:5173 opens the browser the moment Vite is up. le wait 5432 && pg_ctl start restarts Postgres once the port clears. Every command has --help.

Watch & tunnel

le forward svc/api 8080:80 # port-forward that
                           # reconnects itself
le check 3000   # HTTP health: status + latency
le qr 3000      # open it on your phone (LAN QR)
le watch 5173   # notify when it exits
le watch-all    # live +/- feed of listeners
le open 8080    # browser, the moment it's up

le forward runs the kubectl port-forward you'd have typed — printed, not hidden — and supervises it: pod restarts, rollouts, idle drops all get an automatic, logged reconnect with backoff. Ctrl-C stops both, no orphan squatting the port. New in v0.1.16. Why tunnels needed naming →

The TUI

Keyboard all the way down.

Navigate

j / k    # move down / up
g / G    # top / bottom
/        # filter: port, name, folder
1 – 7    # sort by any column
z        # group by owner — ⏎ folds one,
         # Z folds all
r        # refresh now (auto every 3s)

Act

# open the row's own actions —
         # j/k pick a field, ⏎ runs it
x        # stop the selection (asks first)
o        # open the port (http/https auto)
f        # pin the port to the top (*) —
         # survives restarts, sorts, sessions
c        # copy the stop command — OSC 52,
         # so it works over SSH
?        # help

That c key matters on servers: OSC 52 is the terminal's own clipboard channel, so the stop command lands on your clipboard even when le runs on a box you SSH'd into. More on the SSH story →

opens the selected row's own actions in the detail pane — the specific port to open (not just the first one), the binary as distinct from the folder, the stop. New in v0.1.22. Why it took three releases →

Six themes: default, light, nord, dracula, solarized, and mono (colorblind-friendly — no risk colors, bold still marks high). Press t to cycle live, or set theme = nord in ~/.config/le/config to make it stick. The current theme, refresh cadence, and config path live in the ? overlay's settings section.

Why trust it with a kill signal

Read the code that decides.

Every stop re-verifies the PID's start time first, so a recycled PID never gets a signal meant for something else — and a launchd bootout re-verifies the label still maps to the scanned PID, because labels can be re-bootstrapped onto a different program between scan and stop. The ps/lsof parsers are fuzzed nightly in CI. Three adversarial review rounds found eighteen real bugs before they found you — the full reports, refuted candidates included, ship in the repo at docs/audits/.

le is MIT and free forever — that's the point. If it earns a place in your shell, the $5 menu-bar app is how you support the work.