npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@burakboduroglu/portkill

v0.4.6

Published

Kill processes listening on TCP ports — CLI for macOS/Linux: dry-run, port ranges, --list, local web UI (--gui) on loopback. Node.js 18+.

Readme

.portkill

Free stuck TCP ports in one command — no lsof pipelines, no guessing PIDs.

Install with Homebrew or npm →

CI npm Release License Last commit

TypeScript Node.js macOS Linux Homebrew Vitest


Your dev server crashes, the port stays taken, and the next npm run dev greets you with EADDRINUSE. The fix is a pipeline you look up every time — lsof -i :3000, read the PID out of the table, kill -9, hope it was the right one.

.portkill is that pipeline as one command. It shows what owns the port, lets you preview before anything is signalled, and stops only what you meant to stop. Same logic from the terminal or from a local web UI.

The name reads like .portkill — a small, local dev utility, .env-style prefix. The CLI binary is still portkill.

What it is

Give it a port and it answers with the process that holds it, by name and PID. Give it several, or an inclusive range, and it walks them in order. Nothing is signalled until you confirm — or you pass --dry-run and nothing is signalled at all, so you can look first and decide after.

It sends SIGTERM by default, not SIGKILL, so a process gets the chance to shut down the way it wants to. It never escalates: if a listener belongs to another user, portkill says so and exits 3 rather than reaching for sudo on your behalf.

portkill --gui puts the same logic behind a small local web UI on loopback, for when a browser tab is closer to hand than a terminal.

Highlights

| | Feature | How it works | | --- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------ | | 🔍 | See before you act | --dry-run resolves every port and prints what it would stop, without sending a single signal. | | 🎯 | Ports, not PIDs | lsof on both platforms, with fuser as the Linux fallback, so you never copy a PID out of a table again. | | 📚 | Ranges | portkill 9000-9002 expands inclusively, capped at 4096 ports per range token so a typo cannot expand into a fork bomb. | | 🖥️ | Local web UI | --gui serves the same resolve-and-kill logic on 127.0.0.1, with a browser confirm. No Electron, no bundled runtime. | | 🧯 | Polite by default | SIGTERM unless you ask otherwise, a confirmation prompt unless you pass --force, and never sudo. | | 🚦 | Scriptable | Exit codes distinguish success, nothing-found, and permission denied, so a shell script can branch on the outcome. | | 🎨 | Readable output | Colour through chalk, and it steps aside for NO_COLOR or a non-TTY. | | 🪶 | Small | Roughly 1,500 lines of TypeScript with two runtime dependencies, published as a four-file tarball. |

Install

Homebrew — burakboduroglu/homebrew-portkill

brew install burakboduroglu/portkill/portkill
portkill --version

npm — @burakboduroglu/portkill

npm i -g @burakboduroglu/portkill

No install at all

npx @burakboduroglu/portkill --list

If Homebrew reports that /opt/homebrew/bin/portkill already exists, an older npm global install is in the way:

npm uninstall -g @burakboduroglu/portkill
brew link portkill

From source

git clone https://github.com/burakboduroglu/portkill.git && cd portkill
bun install && bun run build
bun link   # optional: puts `portkill` on PATH

Quick start

# What is listening, everywhere?
portkill --list

# See what would happen — nothing is signalled
portkill 3000 8080 --dry-run

# Stop them (prompts unless --force)
portkill 3000 8080

# An inclusive range
portkill 9000-9002

# Something that will not die politely
portkill 3000 --signal SIGKILL

# The same logic in a browser tab
portkill --gui

CLI reference

| Flag | Meaning | | ----------------- | -------------------------------------- | | -n, --dry-run | Show targets only; do not send signals | | -f, --force | Skip the terminal confirmation | | -s, --signal | Signal to send (default SIGTERM) | | -l, --list | List all TCP listeners | | --gui | Open the local web UI | | -v, --verbose | More detail on stderr | | -V, --version | Print the version |

| Exit code | Meaning | | --------- | ---------------------------------------------------------------- | | 0 | Every requested port was handled | | 1 | General error — invalid arguments, unexpected failure | | 2 | No process was listening on any requested port | | 3 | Permission denied — another user's process, or a privileged port |

Full reference: docs/cli-reference.md.

The local web UI

portkill --gui starts an HTTP server on loopback — 127.0.0.1, and ::1 when it is available — prints the URL, and opens it. The page lists listeners, previews a kill, and asks the browser to confirm before it calls the API. Ctrl+C stops the server; nothing is left running behind you.

It has no authentication. It is bound to loopback, but while it runs, anything that can reach your loopback can call it. Treat it as local-only tooling and close it when you are done — SECURITY.md is specific about what that means.

Walkthrough

How it works

Port arguments are matched against ^\d+$, range-checked to 1–65535, expanded, and deduplicated before anything else happens — so what reaches the system is always a number.

Discovery runs lsof -nP -iTCP:<port> -sTCP:LISTEN through execFile, without a shell. On Linux, if lsof is missing, it falls back to fuser -n tcp <port>. lsof exiting 1 means "no matches", not failure, which is a distinction portkill makes rather than reporting an error you would have to interpret.

Stopping a listener is process.kill(pid, signal) — a syscall, not a shell command. EPERM becomes "permission denied" and exit 3. ESRCH means the process died between discovery and the signal, which is a success, not an error.

Requirements

  • Node.js ≥ 18
  • macOS or Linux, with lsof available (fuser covers the Linux fallback)

Project layout

src/
├─ index.ts              Commander wiring, flags, exit code
├─ commands/
│  ├─ kill.ts            Resolve ports → kill → outcomes
│  └─ list.ts            Every listener on the machine
├─ core/
│  ├─ finder.ts          lsof and fuser discovery for one port
│  ├─ lister.ts          One lsof pass for the whole table
│  └─ killer.ts          process.kill, EPERM and ESRCH handling
├─ gui/
│  ├─ server.ts          Loopback HTTP server and JSON API
│  ├─ index-html.ts      The single-page UI
│  └─ open-browser.ts    Opens the printed URL
└─ utils/                Port parsing, output, colour, exit codes, platform

core/ never prints and never reads flags; it takes an injected execFile or kill, which is why the tests can cover it without touching a real process.

Docs

| Doc | What it is | | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------ | | CLI reference | Every flag, exit code, outcome, and the local GUI HTTP API | | Changelog | What changed in each release | | Security policy | What portkill touches, and private vulnerability reporting | | Release | Cutting a release: version, changelog, tag, Homebrew formula | | Contributing | Fork, branch, tests, PR expectations | | Code of Conduct | Community standards (Contributor Covenant 2.0) |

Development

bun install
bun run build
bun run test
bun run test:coverage
bun run lint
bun run typecheck
bun run format

core/ takes an injected execFile or kill, so the tests drive discovery and signalling without a real process on a real port; the command tests mock finder in turn. CI runs the checks on every push and the suite across Node 18, 20 and 22 on both Linux and macOS, and the coverage thresholds in vitest.config.ts are enforced there. Terminal colour comes from chalk; set NO_COLOR=1 to turn it off (no-color.org).

Contributing

Bug reports and small, focused fixes are welcome. The tool is deliberately narrow — Windows support and long-running port monitors are out of scope — so open an issue before building anything substantial.

License

MIT — see LICENSE.