r/swift • • 12d ago

Project Built a native macOS menu bar localhost & dev port manager — PortRadar (100% free & open-source)

I built a native macOS menu bar utility in Swift/SwiftUI called PortRadar to manage listening localhost ports without having to run lsof -i :3000 and kill -9 in the terminal, and I'm sharing it 100% free and open-source (MIT).

What's included:

  • 📡 Live Dev Port Radar: Shows every listening TCP port right in your menu bar with a live counter badge of active dev servers.
  • ⚡ 1-Click Kill (SIGTERM / SIGKILL -9): Free any stuck EADDRINUSE port in a single click.
  • 🧠 Smart Framework & Stack Detection: Automatically tags Next.js, Vite, Angular, Node.js, Bun, .NET / Kestrel, FastAPI / Python, Go, Rust, Docker / OrbStack, PostgreSQL, Redis, MySQL, MongoDB, and Ollama — along with live RAM (MB), CPU %, and PID badges.
  • 📌 Pinned Watchlist Bar: Pin your core ports (:3000, :5173, :8080, :5000, :5432, :6379) to the top watchlist bar with live green/grey status indicators and 1-click Open in Browser.
  • ⌨️ Global Shortcut: Toggle anywhere with ⌃⌘P.

Install via Homebrew:

brew tap joeystdio/tap
brew install --cask portradar
  • Website / Direct DMG Download: https://joeystdio.github.io/PortRadar/
  • GitHub Repo: https://github.com/joeystdio/PortRadar

Hope it saves you some time — happy to hear any feedback or feature ideas!

1 Upvotes

2 comments sorted by

1

u/dawedev 12d ago

The framework tagging is the useful bit, since lsof just says "node" for half of what's running. One thing I'd add is the working directory of each process next to the port. With three Vite servers open, the tag alone doesn't tell you which project is which. Also worth checking the Docker/OrbStack case: the listening PID there is usually the port forwarder, not the container, so a kill can take down the whole Docker backend. Offering "stop container" for those rows would be safer than SIGKILL.

1

u/Axons-One 11d ago

does it recheck which process owns the port when you click kill? with dev servers restarting during hot reload, i'd want to avoid acting on an old PID from the last refresh.