Skip to content

Releases: mukes555/PortNanny

v2.4.2

Choose a tag to compare

@github-actions github-actions released this 16 Sep 16:15
836edd0

What's new in v2.4.2

Notifications that know when to keep quiet, and a test harness that stops
writing to the store of whoever is using the Mac.

Fixed

  • Notifications could arrive in a flood. A watched port that an agent
    kept restarting produced a banner per change, each with its own sound and
    its own entry in Notification Center. With the screen locked, macOS held
    every one of them and delivered the lot at unlock. Now: one banner per
    port per minute, at most three in half a minute before the rest become a
    summary, one sound per burst rather than one each, and a new banner about
    a port replaces the last one instead of stacking.
  • Nothing is posted while the screen is locked or the display is asleep.
    The changes are counted instead, and one silent summary ("12 changes on
    :3000, :5173 and 2 more") arrives when the screen comes back. The guard
    still does its work while you are away; only the banners wait.
  • Refusal banners are rationed the same way: ten agents refused in a minute
    is one story, not ten interruptions.
  • portnanny version --json reports defaultsDomain, the preference domain
    that build reads. The end-to-end suites use it to check they are isolated
    before they run: they provoke refusals on purpose, and against the real
    domain every one of them became a banner on the person's screen, plus
    leases and History rows they had to clean up. Each run now gets its own
    domain, and the guard suite stops flipping the real "refuse unclaimed
    servers" setting.

📦 Install

Download PortNanny-2.4.2.dmg below, open it, and drag PortNanny to
Applications. Universal binary: runs natively on Apple Silicon and Intel;
requires macOS 13 (Ventura) or newer.

Not notarized (no Apple Developer account). First launch on macOS 15 or
newer: open it once, then System Settings → Privacy & Security → Open
Anyway
. On macOS 13 and 14: right-click PortNanny.app → Open → Open.
Either way, xattr -dr com.apple.quarantine /Applications/PortNanny.app
skips the dialog. Homebrew (brew install --cask portnanny) does this for you.

What's Changed

  • Ration the banners, and say nothing while nobody is looking by @mukes555 in #66
  • Keep the test suites out of the real preference domain by @mukes555 in #67
  • Release 2.4.2 by @mukes555 in #68

Full Changelog: v2.4.1...v2.4.2

v2.4.1

Choose a tag to compare

@github-actions github-actions released this 16 Sep 11:21
c360dad

What's new in v2.4.1

The guard, findable. It was the app's own automation and the only way to
reach it on a row was to know to right-click.

Fixed

  • The guard had no visible way in. A row offered Kill, Watch and (in
    Advanced) Details; guarding a port meant knowing to right-click it, or
    having something watched already, since the shield lived in the watched
    section. Every row now carries the shield beside Kill, dimmed until the
    mouse or the keyboard is on the row, and orange while the guard is on.
  • The orange shield meant "protected" on a row and "guarded" in the
    Workbench. Protected is a lock now, and the shield means the guard
    everywhere.

📦 Install

Download PortNanny-2.4.1.dmg below, open it, and drag PortNanny to
Applications. Universal binary: runs natively on Apple Silicon and Intel;
requires macOS 13 (Ventura) or newer.

Not notarized (no Apple Developer account). First launch on macOS 15 or
newer: open it once, then System Settings → Privacy & Security → Open
Anyway
. On macOS 13 and 14: right-click PortNanny.app → Open → Open.
Either way, xattr -dr com.apple.quarantine /Applications/PortNanny.app
skips the dialog. Homebrew (brew install --cask portnanny) does this for you.

What's Changed

Full Changelog: v2.4.0...v2.4.1

v2.4.0

Choose a tag to compare

@github-actions github-actions released this 16 Sep 09:38
11c6cd6

What's new in v2.4.0

PortNanny opens on who is working now: an Agents tab beside Simple and
Advanced, the AI tools on this Mac out of Settings and into it, and
portnanny agents for the same answer in a terminal.

Added

  • An Agents tab, next to Simple and Advanced, and PortNanny opens on it.
    What is listening is grouped by the session that started it: each agent
    with its servers and how much memory they hold, the ports an agent has
    reserved and not started on yet, the sessions that have ended (with one
    button to clean up after them), and everything nobody claims. When another
    agent has just been refused one of a session's ports, the section says so.
  • The AI tools on this Mac moved out of Settings and into the Agents tab,
    under the sessions they explain, with how many of each are running.
    Settings > Agents now holds settings only: the guard switch, project setup,
    and lease length. Current leases show in the Agents tab, under the session
    holding them.
  • portnanny agents answers the same question in a terminal, and in
    --json for the agents themselves: every session here, the servers it is
    running, the ports it has claimed, and which one is you. The rules file
    PortNanny installs into projects now points agents at it.
  • The Workbench opens on Agents, and its session cards show claims too.

📦 Install

Download PortNanny-2.4.0.dmg below, open it, and drag PortNanny to
Applications. Universal binary: runs natively on Apple Silicon and Intel;
requires macOS 13 (Ventura) or newer.

Not notarized (no Apple Developer account). First launch on macOS 15 or
newer: open it once, then System Settings → Privacy & Security → Open
Anyway
. On macOS 13 and 14: right-click PortNanny.app → Open → Open.
Either way, xattr -dr com.apple.quarantine /Applications/PortNanny.app
skips the dialog. Homebrew (brew install --cask portnanny) does this for you.

What's Changed

  • An Agents view: PortNanny, organised by who is working by @mukes555 in #61
  • Three tabs, Agents first, and Settings that only hold settings by @mukes555 in #62
  • Release 2.4.0 by @mukes555 in #63

Full Changelog: v2.3.0...v2.4.0

v2.3.0

Choose a tag to compare

@github-actions github-actions released this 15 Sep 23:51
9b66b81

What's new in v2.3.0

A bug-hunting round: fuzzing, concurrent agents, corrupted stores, hostile
tools and the app's own lifecycle. Everything below was reproduced first.

Fixed

  • Guarding a busy port killed the server on it. Watching or guarding a
    port recorded its occupant in a different format from the one scans
    write, so the next scan read the server already there as a newcomer: a
    watch announced it, and a guard killed the very thing it was set to
    protect.
  • One long argument froze every scan. The patterns that find a secret
    in a command line backtracked over every way of splitting a run of word
    characters: 800 characters took 2.6 seconds and a few kilobytes took
    minutes, on every listener, on every scan. 500 KB now takes 0.15s.
  • Killing a port inside tmux killed the whole session. A tmux server
    keeps the argv of the command that opened it, so tmux new -s build tsc --watch looked like a reloader and the kill went to tmux and everything
    under it: every pane, the editor, other servers.
  • A port held by another user read as free. The listener table cannot
    see root's or _postgres's sockets, so free 5432 exited 0 and the
    next server still could not bind. Kill, free, wait, reserve and exec now
    ask the kernel before answering.
  • Kills reported more than they did. A partial kill exited 0 as
    "killed"; a tree kill swallowed every child it could not stop (and its
    pgrep fallback killed none at all); a process that had already exited
    was recorded in History as killed; free checked that the pid died
    rather than that the port was free.
  • An agent installed through npm or pip looked like an ended session,
    because it runs as node …/bin/gemini or python3 …/bin/aider. That
    let kill --orphaned reap a live agent's server and allowed a rival
    agent's kill that should have been refused.
  • Agent kills trimmed History to 50 entries however long you had set
    it, and changing the length in Settings dropped everything an agent had
    recorded since the window last read it.
  • One damaged entry hid every lease and every history row, and the
    next write saved that empty list over the good ones. Entries are now
    salvaged one at a time.
  • Four crashes: a non-finite JSON-RPC id, an oversized CLAUDE_PID, a
    closed stderr, and a stored lease naming an impossible pid.
  • The popover's shortcuts stayed live after it closed, so Return in
    the Workbench started a kill from a list nobody could see, and Escape
    stopped working everywhere. A confirmation opened from the popover fed
    its own Return back and stacked a second dialog.
  • Cut, Copy, Paste, Select All, Undo and Close did nothing in every
    PortNanny text field and window: a menu bar app still needs a main menu
    for those keys to route.
  • The refresh timer stopped while any dialog or menu was open, blinding
    the guard exactly then; the guard announced "Auto-killing" before
    signalling anything and said nothing when a kill failed; refusal
    banners never asked for notification permission, so macOS dropped them;
    clicking a watch or guard banner did nothing; portnanny://show during
    launch was dropped, closing the welcome tour left nothing on screen, and
    a second copy of PortNanny guarded every port twice.
  • Smaller ones: free-port --range alone was rejected, a lease reason
    kept --token=… unredacted and quoted it back to other agents, a
    process name with a carriage return could write its own row into
    portnanny list, Docker containers publishing a port range went
    unnamed, a test worker under a path with a space could not be killed,
    portnanny://kill/9999/3000 acted on :3000, agent-docs replaced a
    symlinked CLAUDE.md with a private copy, and a scan lost to a stalled
    network mount stopped every later one.

Changed

  • Exit codes: a partial kill is 4, a lease or history write that fails is
    73, and exec answers 126 and 127 like a shell.
  • MCP results say isError truthfully: a wait_for_port_free that timed
    out is an error, an unknown list_ports filter is refused rather than
    silently listing everything, kill_port validates its port and pid, and
    list_ports with filter mine and no identity says so.
  • The shortcut recorder refuses ⌘W, ⌘Q and combinations macOS owns, which
    it used to accept and then never fire.
  • Toasts and errors now appear in the Workbench and Settings; both were
    drawn only by the popover.

📦 Install

Download PortNanny-2.3.0.dmg below, open it, and drag PortNanny to
Applications. Universal binary: runs natively on Apple Silicon and Intel;
requires macOS 13 (Ventura) or newer.

Not notarized (no Apple Developer account). First launch on macOS 15 or
newer: open it once, then System Settings → Privacy & Security → Open
Anyway
. On macOS 13 and 14: right-click PortNanny.app → Open → Open.
Either way, xattr -dr com.apple.quarantine /Applications/PortNanny.app
skips the dialog. Homebrew (brew install --cask portnanny) does this for you.

What's Changed

  • Bug hunt: crashes, the guard, hostile input, and what kills report by @mukes555 in #59
  • Release 2.3.0 by @mukes555 in #60

Full Changelog: v2.2.1...v2.3.0

v2.2.1

Choose a tag to compare

@github-actions github-actions released this 08 Sep 08:16
4cb3eb1

What's new in v2.2.1

Two bugs that end-to-end testing found and the unit suite could not: a
kill that could never stop a Docker container, and a force kill that only
worked from the list. The README now describes the app that exists.

Fixed

  • portnanny kill could not stop a Docker container. It scanned
    without asking for container names, so it never had one, and every
    Docker-published port answered "a container docker ps can name" and
    stopped nothing, while portnanny whois on the same port printed the
    exact docker stop command it would not run. The MCP kill_port had it
    too. A kill now fetches the name when a target is a container it cannot
    name yet, and pays the docker ps only then.
  • Command-Return force killed a selected row but not a typed one. With
    kill 3000 in the search field it sent a plain SIGTERM, contradicting
    the keyboard help and the code's own comment.

Changed

  • The README was checked line by line against the code and rewritten
    around what it found: eighteen claims were wrong or misleading, among
    them Command-K killing "the current filter" (on All it kills dev
    servers), "hide UDP sockets" (the switch shows them, and is on),
    "the only network request" (the inspector's peek is a local one), and
    a scan taking "about 20 ms with no subprocesses" (kernel calls in a few
    milliseconds, with lsof and ps as fallbacks). It also documents the
    right-click menu and that the Tests filter lists test processes rather
    than ports, neither of which it mentioned before.
  • Tests/e2e/ holds the harness that caught the Docker bug: the guard and
    the MCP server driven against real listeners. Manual, not in CI, since
    it needs real processes and Docker.

📦 Install

Download PortNanny-2.2.1.dmg below, open it, and drag PortNanny to
Applications. Universal binary: runs natively on Apple Silicon and Intel;
requires macOS 13 (Ventura) or newer.

Not notarized (no Apple Developer account). First launch on macOS 15 or
newer: open it once, then System Settings → Privacy & Security → Open
Anyway
. On macOS 13 and 14: right-click PortNanny.app → Open → Open.
Either way, xattr -dr com.apple.quarantine /Applications/PortNanny.app
skips the dialog. Homebrew (brew install --cask portnanny) does this for you.

What's Changed

  • README says what the app does, and says it once by @mukes555 in #55
  • portnanny kill can stop a Docker container again by @mukes555 in #56
  • Keep the end-to-end harness that caught the Docker bug by @mukes555 in #57
  • Release 2.2.1 by @mukes555 in #58

Full Changelog: v2.2.0...v2.2.1

v2.2.0

Choose a tag to compare

@github-actions github-actions released this 07 Sep 21:55
7246015

What's new in v2.2.0

A face for every state, and an audit behind it. The quokka was one image
wearing three names; she now has four, and a search that finds nothing
says so in her own voice. Behind that, six passes over the code found
thirty things worth fixing, including a guard that could miss a
supervisor's respawn and secrets reaching agents through a listener's
child processes.

Changed

  • New artwork, and three moods that are actually different. The mascot
    shipped as one file under three names, so "All quiet: nothing is
    listening" and the guard badge both showed the waving quokka. There is
    now a sleeping one for the empty state, a bust bold enough to read at
    badge size for the guard, and a new one for a search that matches
    nothing. The app icon, the README banner, and every screenshot are
    redrawn to match.
  • scripts/make_artwork.swift keeps an alpha channel the source already
    has. It used to recompute one from whiteness whatever it was given,
    which turned a transparent background opaque black and the white
    pinafore see-through.
  • The banner is a drawn asset rather than one composed by a script, so
    scripts/make_logo.swift and the two generated logo files are gone.
  • The README opens with the problem it solves and carries a nav row;
    docs/ARTWORK.md holds the prompts that draw the character.
  • Destructive buttons ask first. Clear History, both Release buttons,
    and Reset Defaults went straight through while every kill confirms.
  • Empty states tell the truth. A filtered list said "nothing is
    listening" while the header counted every port; the Workbench table had
    no empty or scanning state at all.
  • The detail sheets close properly: a real button with a name for
    VoiceOver, on Escape, instead of a fake traffic light. Ten icon-only
    buttons gained accessibility labels.
  • One vocabulary (Stop watching, Remove guard, Option-click), ":3000" in
    notification titles, and "free" readable in light mode.
  • Fewer wakeups: a successful scan no longer republishes to every view,
    the hidden scan runs at a background priority, and Settings stopped
    asking launchd for its login-item status on every redraw.

Fixed

  • The guard could miss a supervisor's respawn. Watched occupancy
    remembered a process name, so a server restarted under the same name
    looked like nothing had happened and the guard never fired.
  • wait --timeout 0 always said "still in use", because it never
    looked at the port before checking the clock.
  • Attribution walked the wrong chain. With a session pid to follow,
    the CLI stopped before reaching its own parents, so whoami named the
    wrong source and a lease taken by one agent could be refused to that
    same agent. A caller whose agent has no session id of its own now
    borrows its pid, the value exec stamps on what it starts, so a
    detached server and its own session match.
  • Claude Code installed through npm runs as node, matched no
    signature, and its live sessions read as ended, which let anyone stop
    its servers. Servers under a path containing a space (fnm's default)
    were classified Other rather than Node.js, and pm2 under fnm was not
    found.
  • A project name that arrived with the first full scan after a background
    one was not treated as a change, so rows kept a missing project. A CPU
    sample taken milliseconds after another showed a process at 50%.
  • A lease warning meant for people was collected and dropped.
  • A rule file with one PortNanny marker and not its pair was appended
    to, duplicating the block; the next run then deleted the person's own
    text between the two markers. list --mine --orphaned quietly applied
    one of the two. exec --range was dropped when it equalled the default.

Security

  • Command lines of a listener's children went into --json and MCP
    output unredacted, so a worker started with --token=... leaked. The
    redaction also missed digit-bearing names (S3_SECRET_KEY), JSON
    secrets, and header secrets.
  • A lease reason is capped and stripped of control characters, and so are
    process names and command lines: a process could name itself with escape
    sequences that rewrite the terminal reading portnanny list.
  • A tree kill could signal a recycled pid. Each child was checked
    against a name read at kill time, which compares the new process with
    itself and can never fail; the walk now carries the name it saw.
  • Output carries the short session key rather than the full one, so an
    agent cannot copy what it read and pass as another agent's session.
  • Every GitHub Action is pinned to a commit, the release token is scoped
    to the job that publishes, and the Homebrew cask is pinned to the
    checksum the build wrote rather than to a fresh download that could
    hash an error page.

📦 Install

Download PortNanny-2.2.0.dmg below, open it, and drag PortNanny to
Applications. Universal binary: runs natively on Apple Silicon and Intel;
requires macOS 13 (Ventura) or newer.

Not notarized (no Apple Developer account). First launch on macOS 15 or
newer: open it once, then System Settings → Privacy & Security → Open
Anyway
. On macOS 13 and 14: right-click PortNanny.app → Open → Open.
Either way, xattr -dr com.apple.quarantine /Applications/PortNanny.app
skips the dialog. Homebrew (brew install --cask portnanny) does this for you.

What's Changed

  • Full audit: 30 confirmed findings fixed, with tests by @mukes555 in #50
  • Close the last three security findings from the audit by @mukes555 in #51
  • Artwork prompts, and a README with a hook by @mukes555 in #52
  • New quokka artwork, and moods that differ from each other by @mukes555 in #53
  • Release 2.2.0 by @mukes555 in #54

Full Changelog: v2.1.1...v2.2.0

v2.1.1

Choose a tag to compare

@github-actions github-actions released this 07 Sep 18:04
942aeee

What's new in v2.1.1

The polish after the rename: a bigger, whole quokka in the header and the
Workbench, README images that never came from anyone's real Mac, and a
cask that current Homebrew installs without a word.

Changed

  • A bigger quokka. The avatar grows to 40 pt in the popover header and
    36 pt in the Workbench sidebar, and the head crop now runs from the cap
    to the chin with room on both sides, so the face is whole and centred
    instead of ending at the mouth. The menu bar keeps its round glyph.
  • The Raycast extension scaffold under extensions/raycast is gone (the
    extension was dropped), and so is the unpinned pre-2.0 cask file; the
    release workflow and the tap render the pinned template only.

Fixed

  • README images are scripted. The popover screenshots, the palette
    shot, the Workbench capture, and the menu bar strip were rendered from
    the live scan of the Mac that built them, which put its project folders,
    paths, and user name on the page. Every image now comes from the same
    fabricated ports as the demo GIF (PORTNANNY_SNAPSHOT_DATA=demo), a
    refresh in that mode is a no-op, and a test keeps the fixtures free of
    anything from the rendering machine. The old images were also removed
    from the repository's history.

Distribution

  • The cask uses the stanzas current Homebrew asks for (postflight_steps
    and depends_on macos: :ventura), so brew upgrade no longer prints
    deprecation warnings. docs/FIRST-RUN.md notes the one-time
    brew trust mukes555/tap that Homebrew may ask for after the rename.

📦 Install

Download PortNanny-2.1.1.dmg below, open it, and drag PortNanny to
Applications. Universal binary — runs natively on Apple Silicon and Intel;
requires macOS 13 (Ventura) or newer.

Not notarized (no Apple Developer account). First launch on macOS 15 or
newer: open it once, then System Settings → Privacy & Security → Open
Anyway
. On macOS 13 and 14: right-click PortNanny.app → Open → Open.
Either way, xattr -dr com.apple.quarantine /Applications/PortNanny.app
skips the dialog. Homebrew (brew install --cask portnanny) does this for you.

What's Changed

Full Changelog: v2.1.0...v2.1.1

v2.1.0

Choose a tag to compare

@github-actions github-actions released this 07 Sep 13:26

What's new in v2.1.0

PortKilla is now PortNanny. The old name said "killer" for an app that
spends most of its time keeping servers alive and attributed, and it sat
one letter from a much larger project. Nothing written for the old name
stops working.

Changed

  • Renamed to PortNanny. New app name and bundle id
    (com.mukes555.PortNanny), portnanny command, PORTNANNY_OWNER and
    PORTNANNY_SESSION, portnanny:// links, repository
    (github.com/mukes555/PortNanny; the old URL redirects), Homebrew cask
    portnanny, and Claude Code plugin portnanny@portnanny.
  • The maid quokka. New artwork for the icon, the menu bar, the header,
    the tour, and the banner: a quokka with a cap, a mug, and a laptop.
  • The README and the app's update banner say brew upgrade rather than
    brew reinstall, since the cask has been pinned per release since 2.0.

Added

  • The old name keeps working, for at least a year: the portkilla
    command (Homebrew links it next to portnanny), PORTKILLA_OWNER and
    PORTKILLA_SESSION (read wherever the new names are; exec exports
    both), and portkilla://kill/3000.
  • Preferences move over. The first launch copies settings, watched
    ports, guards, history, refusals, and leases from the PortKilla domain
    into the new one, once, and never over a value the new app has already
    written. The CLI does the same for the domain it shares with the app.

Distribution

  • brew upgrade --cask portnanny follows the rename (the tap carries
    cask_renames.json): PortKilla.app goes, PortNanny.app comes, and macOS
    asks again for notifications and Launch at login because it keys both to
    the bundle id. Every step is in docs/FIRST-RUN.md.

📦 Install

Download PortNanny-2.1.0.dmg below, open it, and drag PortNanny to
Applications. Universal binary — runs natively on Apple Silicon and Intel;
requires macOS 13 (Ventura) or newer.

Not notarized (no Apple Developer account). First launch on macOS 15 or
newer: open it once, then System Settings → Privacy & Security → Open
Anyway
. On macOS 13 and 14: right-click PortNanny.app → Open → Open.
Either way, xattr -dr com.apple.quarantine /Applications/PortNanny.app
skips the dialog. Homebrew (brew install --cask portnanny) does this for you.

What's Changed

Full Changelog: v2.0.0...v2.1.0

v2.0.0

Choose a tag to compare

@github-actions github-actions released this 07 Sep 09:40

What's new in v2.0.0

The quokka release: a new look, a Workbench window, port leases, a setup
wizard for AI tools, and a guard that knows whose server it is. Everything
since 1.16, reviewed twice.

Added

  • Settings > Agents. The AI tools this Mac has and how each is
    recognised, the guard's one switch ("Refuse agents a server nobody
    claims", followed by the CLI and the MCP server too), a project setup
    with a click per step (what portkilla setup does: rule files, the MCP
    registration for Claude Code), and leases: the default length
    portkilla reserve uses and a Release button for each active lease.
  • Notifications per event. A watched port freeing up, something taking
    one, a guard acting, and an agent being refused each have a switch under
    the master.
  • What the list shows. Settings > Display can hide UDP sockets and
    ephemeral ports (49152 and up).
  • Updates. The daily check is a switch, and "Include beta releases"
    offers prereleases, which rank below the release they precede.
  • portkilla setup walks through setting up the AI tools on this Mac:
    checks portkilla on PATH, and for each tool found offers to register
    the MCP server with Claude Code (claude mcp add), write its rule file
    into the project, and shows the rest (Cursor's mcp.json, Codex's
    config.toml, shell completions). Asks before every change; --yes
    applies all; without a terminal it prints the plan.
  • Rule files for every tool. portkilla agent-docs --cursor writes
    .cursor/rules/portkilla.mdc (with frontmatter), --windsurf writes
    .windsurf/rules/portkilla.md, --codex writes AGENTS.md, --claude
    CLAUDE.md; folders are created, existing files keep their content.
  • A Claude Code plugin in plugins/portkilla: the MCP server, the lsof
    hook, a portkilla skill, and /portkilla:ports and /portkilla:free
    commands. Install with claude plugin marketplace add mukes555/PortKilla
    then claude plugin install portkilla@portkilla.
  • Leases on free ports. portkilla reserve <port> [--for 10m] [--reason ...] takes a lease that free-port and exec skip for
    everyone else and that makes kill refuse other agents on the port
    (people are warned) until it expires (a day at most) or portkilla release <port> gives it back. portkilla reservations lists them; the
    Workbench watchlist shows them with a Release button, and a listener on
    a leased port wears a "reserved" chip. MCP: reserve_port,
    release_port, and a free_port tool.
  • Drift. PortKilla reads the port a project meant to use (PORT in
    .env files, --port/PORT= in package.json scripts, port: in
    vite.config) and marks a web server that ended up elsewhere with an
    "expected :3000" chip; the inspector and whois say who holds the port
    it wanted. portkilla drift lists every such server.
  • portkilla exec. portkilla exec --free-port --prefer 3000 -- npm run dev picks a free port nobody has leased, exports PORT, leases the
    port for the run, exports PORTKILLA_OWNER and PORTKILLA_SESSION
    (yours unless given) so the server is attributed even when the tool
    leaves no marker, forwards signals, and exits with the command's status.
    --port N insists on one port and explains who has it when it is busy.
  • The Workbench. A full-size window (overflow menu, or type > workbench)
    with a sidebar of views: a sortable table of every port; ports grouped by
    project with Reveal, Open in editor, and Kill all; ports grouped by agent
    session (live, ended, editor terminals, unclaimed) with Stop all and a
    one-click clean-up of ended sessions; the watchlist with guard and watch
    toggles and a field to add a port; and History. An inspector on the right
    shows the selected port's overview, the evidence behind its owner (the
    ancestry walked, the markers found), and its history, with Kill, Force,
    Tree, Open, Watch, and Guard at hand.
  • A command palette in the search field. Type kill 3000, open 5173,
    watch 8080, guard 8080, or free port and Return does it; the strip
    under the field says what will happen, including who owns the target and
    whether a supervisor is involved. > lists app commands (Refresh, Bulk
    Kill, Pin, Workbench, History, Find a Free Port, Settings, Quit).
  • Rows slide in and fade out as ports come and go.
  • Sparklines. The Workbench keeps the last sixty scans of CPU and
    memory per process: a trend column in the table, and CPU and memory
    charts in the inspector.
  • Who is connected. The inspector's Connections tab lists the remote
    end of every established connection, marked local, from the local
    network, or from elsewhere; portkilla whois prints the same.
  • Peek at a web server. A Peek button in the inspector sends one GET to
    the local server and shows its status, content type, and page title. A
    Settings toggle (off by default) does it automatically for web ports.
  • A welcome tour on first launch: what the app shows, how agents are
    kept from killing each other, and how to set the agents up (with the
    commands to copy). Later from the menu or > tour.
  • The quokka. A new app icon, and the mascot in the welcome tour (waving
    on the first page, on guard on the second) and in the empty state (asleep
    on the tablet). scripts/make_artwork.swift turns the artwork into the
    icon and the cut-outs.
  • Supervisors understood. PortKilla recognises what would undo a plain
    kill: pm2 apps, launchd jobs (Homebrew services and your own
    LaunchAgents), Docker containers, and reloaders (nodemon, next dev,
    uvicorn --reload, --watch modes, Flask's and Django's reloaders,
    gunicorn, puma, and nginx masters). Rows show an orange chip and the
    detail view says why. portkilla kill stops a reloader together with its
    child and runs pm2 stop, brew services stop, launchctl bootout, or
    docker stop for the rest; when the tool is not on PATH it prints the
    command and exits 6. --force kills the listener itself, except for
    Docker, where it means docker kill. The app asks "Stop via pm2" or
    "Kill anyway". Docker's backend is never killed through a port.
  • Refusals reach you. When the guard refuses an agent, the running app
    shows a notification with "Stop it anyway" and "Show in PortKilla", the
    History window lists the refusal in orange with who was refused, and
    portkilla history includes it.
  • portkilla whois shows the supervisor and its stop command; list --json
    carries managedBy.
  • portkilla whois <port> (or --pid): everything PortKilla knows about
    a listener, including the evidence behind its owner (the ancestry walked,
    the markers found, what was declared) and what kill would do for the
    caller and why. Also an MCP tool, whois_port.
  • portkilla kill --orphaned stops every server left behind by an
    agent session that has ended, for any agent; exit 0 when there is
    nothing to clean up.
  • portkilla doctor --agents prints the agent compatibility matrix
    checked against this machine: which tools run or are installed, how each
    is recognised, how precisely its sessions are told apart, and where each
    fact came from. The same matrix is in docs/AGENTS.md, kept in step by a
    test.
  • PORTKILLA_SESSION: tools that export no session id (Codex, Gemini,
    custom bots) can export any unique string next to PORTKILLA_OWNER, and
    their servers stay tied to that session after reparenting.
  • A refusal now says when the server runs in the caller's working
    directory: a hint that it may be the caller's own unclaimed server, or
    the user's.
  • portkilla schema whois and schema agents document the new outputs.
  • Connected clients. Each listener shows how many established
    connections it has (a chip in the row, a Clients line in the detail
    view, connections in list --json), and killing a server with live
    clients always confirms first.
  • portkilla free-port [--prefer N] [--range A-B] [--json] prints the
    first port that is neither listening nor bound (a real bind probe),
    starting from the preferred one. Exit 1 when the range is full.
  • portkilla schema <command> documents every field of that command's
    --json output, so scripts and agents do not have to guess.
  • Agents no longer kill what nobody claims. An identified agent that
    asks to stop a server PortKilla cannot attribute is refused (exit 3,
    guardVerdict: "refused", reason "not attributed"); it should start its
    own servers with PORTKILLA_OWNER set or ask the person. People at a
    plain terminal and the GUI are unaffected; --force still overrides.
    Commands from an editor terminal count as a person's, except against
    another agent's running server, where they need --force.
  • Scenario tests spawn real servers (portkilla __serve <port>, debug
    builds only) and drive the guard through the CLI as a separate process:
    attribution by declaration and by environment after reparenting,
    refusals, overrides, real kills, free-port, whoami.
  • portkilla mcp announces itself on stderr when run from a terminal
    (it used to wait in silence), and portkilla mcp --setup [claude|cursor| codex] prints the registration for each agent. There is no background
    mode by design: each agent starts its own copy over stdin/stdout when it
    needs one and stops it afterwards.

Changed

  • The quokka is the brand. The menu bar shows the quokka traced in
    black and white from the artwork (lighter while nothing is listening)
    or, from Settings > Display, the app icon in colour; the bolt is gone.
    The popover header carries the artwork's head with a live summary line,
    About shows the app icon, and the Workbench sidebar and empty inspector
    wear it too.
  • A bigger popover. Regular is now 580 by 720 points; Settings >
    Display offers Compact (the old 500 by 600) and Large (660 by 840). The
    pinned window follows.
  • **Richer rows....
Read more

v1.16.0

Choose a tag to compare

@github-actions github-actions released this 06 Sep 16:14

⚡ What's new in v1.16.0

The agent tools batch

Added

  • portkilla mcp: a Model Context Protocol server over stdin/stdout,
    no dependencies. Tools: list_ports, kill_port (dry-run by default,
    refusals come back as tool errors the model reads), whoami,
    wait_for_port_free. The agent spawns and reaps it, so nothing is
    resident or registered: the guard becomes a tool instead of a habit.
    Register with {"mcpServers":{"portkilla":{"command":"portkilla","args":["mcp"]}}}
    (Claude Code, Cursor) or [mcp_servers.portkilla] in Codex's config.
  • portkilla agent-docs --write [--file CLAUDE.md] appends the agent
    snippet between markers, once, and updates it in place on later runs.
    Opt-in only; no other command writes into your repository.
  • portkilla agent-docs --claude-hook prints a Claude Code PreToolUse
    hook that turns kill -9 $(lsof -ti:PORT) into a nudge toward
    portkilla free, at the point of the habit.

📦 Install

Download PortKilla-1.16.0.dmg below, open it, and drag PortKilla to
Applications. Universal binary — runs natively on Apple Silicon and Intel;
requires macOS 13 (Ventura) or newer.

Not notarized (no Apple Developer account). First launch on macOS 15 or
newer: open it once, then System Settings → Privacy & Security → Open
Anyway
. On macOS 13 and 14: right-click PortKilla.app → Open → Open.
Either way, xattr -dr com.apple.quarantine /Applications/PortKilla.app
skips the dialog. Homebrew (brew install --cask portkilla) does this for you.

What's Changed

Full Changelog: v1.15.0...v1.16.0