Skip to content

v2.0.0

Choose a tag to compare

@github-actions github-actions released this 07 Sep 09:40
· 66 commits to main since this release

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. Type tiles, section headers with counts and accents,
    port and process names a size up, memory with CPU and its trend in
    Advanced, and the browser and watch verbs on hover so the list stays
    calm. Guard buttons use a shield.
  • Debug snapshot renders keep their density and watch list in the
    throwaway suite instead of the developer's own preferences.
  • The core is a library. PortKillaCore (models, scanners, guard, CLI,
    MCP; Foundation only) sits under three targets: the menu-bar app, the new
    standalone portkilla executable, and the tests. The CLI no longer
    links AppKit, so it starts faster and uses less memory; the app binary
    still answers the same subcommands.
  • A declared PORTKILLA_OWNER now wins over process ancestry for both the
    caller and the target, so a bot started from inside another agent's
    session keeps its own name.
  • portkilla kill --dry-run mentions connected clients, and agent-docs
    explains the new refusal and points at free-port.
  • Row text uses relative text styles (body, subheadline, caption)
    instead of fixed point sizes, and the column widths are ScaledMetric,
    so the list follows whatever text scaling the system applies rather than
    clipping. macOS applies little of it to SwiftUI text today; the snapshot
    hook's PORTKILLA_SNAPSHOT_TEXTSIZE=large exists for when it does.
  • Rows no longer observe the whole PortManager: the section hands each
    row plain values (density, protected, watched, terminating), so a
    publish re-evaluates only the rows whose inputs changed.
  • The demo-GIF hook renders against a throwaway preference suite instead
    of the real one.

Fixed

  • The watchlist no longer disarms guards by looking at them. Rendering
    the Workbench's watchlist counted as a guard strike, so a guard stood
    down on its next intrusion after a few seconds on screen.
  • Bulk kills leave supervised servers alone. Kill All and the bulk
    sheet used to send a plain kill to pm2, launchd, and reloader servers,
    which came straight back; they are skipped with a note, and each row's
    own stop verb applies.
  • Every kill dialog carries the owner, client, and lease warnings,
    including the supervised ones, which showed none.
  • A reloader kill names what goes with it. Stopping nodemon (or another
    reloader) takes every server under it; those are now judged by the
    guard, listed in the dry run, and recorded in History.
  • Leases survive two agents at once. The lease store takes an advisory
    lock around every read-modify-write, so two exec --free-port calls at
    the same instant cannot both win a port.
  • Client counts refresh the row. A change in connected clients
    republishes the row and its warnings.
  • Smaller: --help after -- belongs to the command exec runs;
    history shows kills by default and refusals with --all, so its first
    entry always names the killer; the docs installer refuses garbled
    markers; the MCP kill tool flags every outcome that leaves the port
    busy; the inspector's peek reports a redirect without following it and
    stops at 64 KB; debug snapshot renders keep their density and watch
    list out of real preferences.

Security

  • A name alone claims nothing. A declared PORTKILLA_OWNER without a
    session could stop another agent's server, or release its lease, by
    matching the name. A known session now has to be shown, with
    PORTKILLA_SESSION or by running from the agent's own shell.
  • Refusal notifications are believed only when the CLI recorded them.
    Any local process could post one, and its "Stop it anyway" killed the
    port with no confirmation. Labels are cleaned and the kill goes through
    the usual confirmation.
  • Leases are held by a session, not a name.
  • pm2 app names are validated before they become arguments; pm2 stop all was reachable from a process name.
  • Command lines are redacted before they are shown, exported, written to
    History, or handed to an agent: --token=..., --api-key ...,
    DATABASE_PASSWORD=..., passwords inside URLs, and bearer tokens become
    [redacted]. Classification and project detection still see the original.

Distribution

  • scripts/build.sh bundles the standalone CLI as
    PortKilla.app/Contents/Helpers/portkilla, and the cask's binary stanza
    links that instead of the app binary. CI builds before testing so the
    scenario tests find the debug portkilla.
  • The release workflow can pin the Homebrew cask to each release with its
    SHA-256 (packaging/homebrew/portkilla.rb.tmpl, livecheck,
    brew upgrade support). It runs only when a HOMEBREW_TAP_TOKEN secret
    exists; see RELEASING.md.
  • README leads with a current screenshot and shows the Workbench; the demo
    GIF is regenerated.

📦 Install

Download PortKilla-2.0.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

  • Leftovers: text styles, row inputs, pinned cask job, fresh assets by @mukes555 in #25
  • mcp: announce on a terminal; --setup prints each agent's registration by @mukes555 in #26
  • Phase A: core library, standalone CLI, connection counts, redaction, guard rules by @mukes555 in #27
  • Phase B1: whois, orphan sweep, agent compatibility matrix, declared sessions by @mukes555 in #28
  • Phase B2: managed runtimes with real stop verbs, refusals as notifications by @mukes555 in #29
  • UI-1: Workbench window, palette in the search field, row transitions by @mukes555 in #30
  • UI-2: sparklines, connections with peers, HTTP peek, welcome tour, mascot by @mukes555 in #31
  • C1: leases on free ports, and portkilla exec by @mukes555 in #32
  • C2: drift detection by @mukes555 in #33
  • E1: setup wizard, rule files for every tool, a Claude Code plugin by @mukes555 in #34
  • Assets: screenshots and demo for the new UI by @mukes555 in #35
  • Artwork: the quokka icon and mascot by @mukes555 in #36
  • Fix the pre-2.0 review findings by @mukes555 in #37
  • UI-3: quokka branding, a bigger popover, richer rows by @mukes555 in #38
  • The head crop follows the outline; the traced quokka leads the menu bar by @mukes555 in #39
  • README: the quokka edition by @mukes555 in #40
  • Settings for 2.0: Agents, notifications per event, list filters, updates by @mukes555 in #41
  • Review round two: the findings from five angles by @mukes555 in #42
  • Release 2.0.0 by @mukes555 in #43

Full Changelog: v1.16.0...v2.0.0