Skip to content
SonghaiFanPublic

About

Native macOS menu bar app & CLI to see what is listening on localhost, identify projects & processes, and gently close them. The safer alternative to lsof and kill-port.

Topics

Resources

Security policy

Stars

124 stars

Watchers

0 watching

Forks

Repository files navigation

LeftOpen Logo

LeftOpen

Native macOS Menu Bar Port Manager & CLI

See what your tools left running on localhost, identify projects, and gently close them.

Website Release Homebrew Cask macOS 14+ Apple Notarized License: MIT

English · 中文


LeftOpen menu bar panel: closable ports first, apps and system services folded away

When I have several coding agents working in parallel, dev servers and test runners keep starting up on different ports. By the end of the day there are always a few still running.

Existing tools are either too much, or only tell you "port in use" without saying which process it is or which project it came from.

So I made LeftOpen. It sits quietly in the menu bar and shows you the listening ports, the processes behind them and the projects they probably belong to. Once you've had a look, you close the ones you no longer need.

Close the doors that were left ajar, gently.


This project is evolving quickly and will remain free and open source forever. If you’ve downloaded it or are using it, please Star the repository and Follow @SonghaiFan to keep up with my projects. Your Star is my biggest source of support and motivation to keep building.

Installation

Homebrew (recommended)

brew install --cask songhaifan/tap/leftopen

Supports Apple Silicon and Intel Macs running macOS Sonoma (14.0+). The app and bundled CLI are universal binaries, signed with a Developer ID and notarized by Apple.

Manual download

Get LeftOpen-release.zip from GitHub Releases, unzip, and move LeftOpen.app to /Applications.


Design & Features

  • Knows whose port it is. Walks up from the process's working directory to .git, package.json, pyproject.toml, Cargo.toml or go.mod, or resolves the owning .app. Global npm packages, python -m modules and standalone services (Redis, Postgres, Ollama…) are named too, so you rarely see a bare node or python.
  • Real icons, nothing bundled. Uses the .app icon when there is one; otherwise the icon the project or package ships itself (Tauri/Electron app icon, the favicon declared in index.html, public/ conventions); otherwise the favicon served by the local server; otherwise a symbol.
  • Grouped by who started them. Dev Servers (from a project, terminal, editor or agent), Background Services (launchd: brew services, login items), Apps and System. The group is also how a port is closed for good, and each one says so: SIGTERM for dev servers, brew services stop … for services launchd would restart, quitting the app for apps.
  • Closes gently first. Sends SIGTERM and re-checks process identity. If a single-process close still leaves it listening after five seconds, that process is marked and the warning explains that closing it again within two minutes sends SIGKILL. The second click or swipe is the deliberate force-close action; existing safety protections and identity checks still apply.
  • Local vs LAN. Tells apart ports bound to 127.0.0.1 from ones on 0.0.0.0 or a LAN address.
  • Hidden ports. Hide selected ports from the list, search, menu bar count, and change sounds in Settings → Ports → Hidden Ports, or choose Hide Port from its context menu. Enter ports with Return or commas, or paste several at once. Each saved port becomes a tag; click its × to show that port again. Services keep running.
  • Appearance. Choose System, Light or Dark in Settings → General, alongside language, login startup and sounds. Projects shows the local address service's health.
  • Quiet by default. Native SwiftUI MenuBarExtra. No Dock icon, no telemetry, and no background service unless you opt into fixed addresses. The only request that leaves this Mac is a daily check of the latest GitHub release, which you can turn off in Settings.
  • Fixed project addresses. Give a dev server a stable https://myapp.localhost URL that follows it across port changes, powered by bundled Portless. See below.
  • Same engine in the terminal. The app ships a leftopen CLI with identical inference and safety rules.

Fixed project addresses

Dev servers move between ports; their address doesn't have to. LeftOpen bundles Portless 0.15.7 and Node 24.14.0, so there's nothing extra to install.

  1. One-time setup: Turn on Settings → Projects → Fixed addresses. macOS asks for authorization to trust a local CA, install a loopback-only HTTPS service on the selected proxy port (443 by default), and manage exact /etc/hosts entries. Only explicit Settings actions ask; project actions and background scans never do.
  2. Per project: start the project as usual, then click Enable fixed address in its port details. It's reachable at https://myapp.localhost.

How it behaves:

  • Verified before forwarding. LeftOpen checks the project root, executable, working directory, hashed command identity and live listener before following a port change. Ambiguous or unrelated listeners, HTTPS-only upstreams and non-web ports are never forwarded. If the dev server rejects the hostname, the details show why; project files are never edited.
  • Start stopped projects. Saved projects that aren't running show Start when they have a dev script, using Portless's own launcher (free-port allocation, framework arguments, worktree naming). LeftOpen never reconstructs commands from process arguments. Projects without a script offer Open project.
  • Leases, not permanent rules. Mappings for observed services use short leases that expire when LeftOpen stops scanning and restore on launch. Disabling an address doesn't stop the project. The HTTPS service stays installed across app quits; if port 443 is already taken, LeftOpen reports it instead of taking over.
  • For agents and scripts: leftopen url [name|port|path] prints verified addresses (--json for structured output), and leftopen --json includes fixedURL for matching services.

Earlier HTTP addresses keep working; finish the HTTPS upgrade from the same Settings page. Portless (Apache-2.0) and Node ship with their licenses.

Address settings: Settings → Projects has a single fixed-address row, with no manual port configuration. Initial setup prefers 443, automatically trying 8443–8462 on conflict within the same authorization. Successful setup remembers the selected port and includes it in opened/copied URLs. Other listeners are never stopped; failures retain copyable diagnostics. Ports are not automatically migrated while LeftOpen-launched projects run. Turning off removes managed routes but preserves bindings, certificates and the proxy without stopping project servers; stop LeftOpen-launched projects first.


Why LeftOpen? (Common Use Cases)

  • Fix "Port already in use" (EADDRINUSE): When your dev server fails because port 3000, 5173, or 8080 is blocked by a lingering process, LeftOpen shows you what's running and shuts it down gently—without restarting your terminal or machine.
  • Identify the project, not just a generic node or python PID: Tools like lsof -i or kill-port only report raw PIDs or ambiguous process names. LeftOpen tracks the working directory and project root (package.json, Cargo.toml, pyproject.toml, go.mod, .git), giving you full context before taking action.
  • Spot accidental LAN exposure: See at a glance whether a port is bound strictly to 127.0.0.1 (local only) or 0.0.0.0 (accessible to anyone on your local network/Wi-Fi).
  • Graceful SIGTERM vs. destructive kill -9: Unlike blunt force-killing scripts, LeftOpen re-verifies PID and start time, shows sibling ports, and issues a standard SIGTERM so servers can clean up sockets, flush logs, and exit cleanly.

CLI

Available in the terminal right after installing:

# List every listening port and its owner (or: leftopen list)
leftopen

# Explain one port
leftopen 3000

# Open http://localhost:3000 in the default browser
leftopen open 3000

# Print a project's verified fixed address (e.g. https://myapp.localhost)
leftopen url myapp

# Close the process on a port (SIGTERM, asks first)
leftopen close 3000

# Structured JSON output
leftopen --json

Sample output:

LEFT OPEN
26 listening ports · 17 processes · 1 projects · 8 LAN-visible

MY PROJECTS (2)
PORT    PID      OWNER          PROCESS    AGE      SCOPE
5173    76344    visdelta       node       2h       LOCAL
         ↳ ~/Documents/visdelta
5511    4999     visdelta       node       27m      LOCAL
         ↳ ~/Documents/visdelta

APPLICATIONS (16)
PORT    PID      OWNER          PROCESS    AGE      SCOPE
5000    696      ControlCenter  Control    3d       LAN
9222    36824    Google Chrome  Chrome     5h       LOCAL

SERVICES (3)
PORT    PID      OWNER          PROCESS    AGE      SCOPE
11434   911      Ollama         ollama     1d       LOCAL

LOCAL = this Mac only · LAN = may be reachable from your local network

Development

Maintainers: one-click releases.

# Tests
swift test

# Build the app locally
LEFTOPEN_OUTPUT_DIR=dist/dev Scripts/build-app.sh

License

MIT License

About

Native macOS menu bar app & CLI to see what is listening on localhost, identify projects & processes, and gently close them. The safer alternative to lsof and kill-port.

Topics

Resources

Security policy

Stars

124 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages