Article
I Built a Mac Menu Bar Tool for Watching Local Ports
Lately, Cursor, Claude Code and Codex spin up a bunch of services for me at the same time. Next takes 3000, Vite takes 5173, then I fire up a Nest, and there may be a Postgres next to it.
The editor closes, but the processes don't necessarily leave.
On the next launch, the terminal throws back just one line: EADDRINUSE. So I switch back and type:
PLAINTEXT
复制
lsof -nP -iTCP:3000 -sTCP:LISTEN
kill …
Sometimes after the kill the port is still held. Sometimes there are two node processes and I can't tell which one I killed. Sometimes I'm too quick and nearly touch the database.
The problem is small. It doesn't deserve a dashboard, and it doesn't deserve me digging the command out of my head every time. But I run into it several times a day, and each time it pulls my attention away.
So I decided to build one myself.
There Are Already Ways, but None Is What I Wanted
Of course I know I can alias an lsof command. Activity Monitor can search processes too. Docker Desktop shows containers as well.
But none of these entry points is at the exact moment I get stuck.
When I'm stuck, I'm still in the editor, my hands are still on the keyboard, and all I have in my head is "3000 is taken again". I don't want to open yet another window for this little thing, and I don't want to guess, among a long list of node, which one is the frontend from a moment ago.
What I want is narrow:
The ports I use all the time are right in front of me, and I can tell at a glance whether each is free or taken. The one that's taken should tell me which project it belongs to and where its path is. Then I decide whether to let it go. After letting go, the port must really be free.
That's it. No notifications, no dashboard, none of that "you're managing infrastructure" air.
I don't want it to become yet another thing to configure. No YAML, no startup scripts; install it and you can see the ports. Very little can be changed in Settings: whether to launch at login, how fast to refresh, whether the menu bar shows the count, and whether to show system ports. Enough, and then stop.
Berth

Berth is a small tool that lives only in the macOS menu bar. It watches the local ports your projects are actually using, so you can first see clearly who is holding one, and only then decide whether it can be released.
It doesn't go in the Dock. Click the icon, or press ⌥⌘P, and the panel appears. When you're done looking, it tucks away.
Frequently used ports can be pinned on top. A green dot is free, an orange dot is taken. That's what I want to see: does this berth have a boat in it right now.
3000, 5173, 8080, 5432 — to me these aren't numbers; they're positions I run into every day. Pinning them is closer to how I actually work than searching "who is using this port" every time.
It's Not Yet Another lsof Table
lsof gives PID and COMMAND. For me, what I really need to judge isn't "there is a node" but "which project's node is this".
So Berth groups by project and lays out the process, PID and path. Two nodes become two cards, rather than two lines of nearly identical commands. For things like Next and Vite, it shows them when it can tell, as a clue, not as a selling point.
When searching I also want it to feel like the terminal: jump to a port, open localhost, or just type release 3000. If a productivity tool itself makes me learn a new set of operations, it has already started reducing productivity.
Underneath it still uses lsof. I never meant to replace that tool itself; I just didn't want to keep splitting "take a look, decide, stop it, confirm the port is free" into four manual steps.
Before I Stop Something, I Need to Know Who It Is
Something that can end processes can't be clever.
Clicking release asks for confirmation first. Force quit is there, but only goes through when I explicitly want it. I hide system processes as much as I can; launchd, WindowServer and the like should never appear on a "one-click release" path. Databases even more so: Postgres, Redis and Mongo don't get the most prominent kill button and carry an extra warning.
Kill the wrong thing and unsaved work is gone. That's not a feature, it's a responsibility.
So it also doesn't do automatic restarts, doesn't scan the local network, and doesn't manage remote machines. It only looks at the ports the local machine's TCP is listening on. UDP, outbound connections and TIME_WAIT I don't handle. Whether it's a ghost process or a service still hanging around after the editor left, I only handle this one layer: is this port still listening.
Only by keeping it small do I dare let it touch kill.
A product can be simple, but it can't be careless.
Why It's Called Berth
A berth is a mooring place. A boat docks there and takes up a stretch of shoreline; when it sails off, that stretch is free again.
Local ports are the same. When a service arrives, the port is taken; when it leaves, the port should be clean. The loop I want is just four actions: see who's holding it, judge whether it can be killed, stop it, and confirm the port is now free.
The name is meant to express a berth chart, not a table. A table shows you every listening row; a berth chart cares only about the positions you actually use, and shows them even when they're empty.
One syllable, easy to say, and easy to turn into an icon.
It's Still Early
It only went public at the end of August. It's 0.1.1 now, the installer is about 4.4 MB, and it needs Apple Silicon and macOS 14 or later. No App Store, and no account. I haven't gotten to Intel machines in this version.
It isn't on the App Store because the App Sandbox can't be on by default, or it couldn't read the full listening information of other processes. It's not that I prefer direct sales; this tool has to see other processes, and the sandbox would turn it blind.
Updates go through Sparkle: check in the panel, and once the download finishes, click "Restart to finish the update" once. Signing, notarization and stapling are the bottom line, not a selling point. Software that can stop processes has to first make people feel it won't secretly do anything else. So there's no telemetry, no crash reporting, and no login.
The source is on GitHub under Apache 2.0. Unlike Wane, this time I didn't add the Commons Clause. It's just a small tool for my own use, and I hope others can use it directly and modify it. It's still rough right now, and there are plenty of edges I only found awkward after using it myself.
In the End
Berth can't solve vibe coding messing up your environment. It only lets me memorize one fewer command before the next EADDRINUSE pops up.
+
Copyright
Copyright of this article belongs to its author or rights holder. When reposting or quoting, please follow the applicable license terms and keep the link to this article.
Link to this article: https://xuyi.dev/en/2026-09-02-gyjasj