- Version 0.1.0 · Purity server-only (shells out via
node:child_process) - Purpose Answer “which process is listening on this port?” on POSIX and Windows, safely enough that the answer can be handed to
process.kill. - Workspace deps none
- External deps none (Node built-ins only)
Server-only, and deliberately so: it spawns
lsof (POSIX) or netstat (Windows). It exists because two callers that cannot import each other need the same answer — the clawboo CLI’s stop / restart and the dashboard server’s managed-Gateway control (stopGateway, POST /api/system/gateway). Each carried its own copy until the copies started to matter.Public API
All exports come from the single. barrel (src/index.ts). No subpath exports.
Functions
Types
The three details that matter
-sTCP:LISTENon POSIX. The command islsof -nP -iTCP:<port> -sTCP:LISTEN -t. Plainlsof -i :PORTalso matches connected sockets, so a browser tab open on the dashboard appears in that output and taking the first line can return the browser’s PID.-n/-Padditionally skip DNS and/etc/serviceslookups, because a stalled resolver would block this synchronous call.- A raised
maxBufferfornetstat -ano. Node’s 1 MB default throwsENOBUFSon a host with thousands of connections, and the surrounding catch would silently turn that into “no process found”. - A locale-independent listener fallback.
netstat.exetranslates its state column (ABHÖRENon German Windows,À L'ÉCOUTEon French), so an English-onlyLISTENINGmatch finds nothing there. When noLISTENINGrow matches, a row whose local address is the port and whose foreign address is the all-zero placeholder (0.0.0.0:0/[::]:0) is accepted — the structural signature of a listening socket, which no locale translates. The PID is read as the last column, which survives a localized state string containing a space.
Why a package rather than a copy in each app
apps/cli and apps/web/server are separate build targets and neither may import the other, so shared logic belongs in a @clawboo/* package. That matters more here than for most shared code: a fix applied to one copy and not the other is a bug that only manifests on one surface, and the failure mode is force-killing the wrong process.
Testing
src/__tests__/processLookup.test.ts covers the parsers against realistic netstat -ano and lsof -t fixtures, including a localized (ABHÖREN) row, a space-bearing French state string, IPv6 local addresses, CRLF output, UDP rows on the same port, prefix collisions (:187890 must not match :18789), and a synthetic LISTENING row whose foreign address is the target port, which is the only shape that isolates the local-address column check. findListenerPid is unit-tested too, through the exported LookupDeps seam (an injected run plus platform): the POSIX lsof -nP -iTCP:<port> -sTCP:LISTEN -t argv, the Windows netstat -ano path, and the tool-absent / non-zero-exit case, all without spawning anything. The CLI’s stopDashboard tests (apps/cli/src/__tests__/lifecycle.test.ts) stub the lookup out and cover the SIGTERM / poll / SIGKILL escalation instead.
See also
- CLI reference,
clawboo stopandclawboo restart - System API,
POST /api/system/gateway(the managed-Gateway lifecycle) - Packages overview, the full dependency graph and build order