About proc-cli
proc-cli is a process and port management skill for agents using the proc CLI. It documents structured JSON commands for discovery, monitoring, and lifecycle actions across macOS, Linux, and Windows.
Key features
- 01Find processes by port, PID, name, or path
- 02Inspect process details and ancestry
- 03Monitor processes and wait for exit
- 04Stop, freeze, or release targeted processes
- 05Single-document JSON command output
Use cases
- 01Find the process occupying a port
- 02Inspect a process tree before cleanup
- 03Monitor a job until it exits
- 04Identify resource-intensive or orphaned processes
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ yazeed/ proc-cli --skill proc-cliAfter installing, type/proc-cliin Claude Code to confirm it loaded.
Bundled filesreference.md
Details
From the project READMEproc CLI — Process Management Tool
proc is a semantic CLI for processes and ports on macOS, Linux, and Windows. You target processes by port (:3000), PID, name, file or directory, and every command can return a single JSON document.
Before You Start
- Check it's installed:
proc --version. This skill describes proc 1.14.3 (proc inspectanduptime_secsneed 1.14 or later). - If it's missing, ask the user before installing. Options:
brew install yazeed/proc/proc(macOS),cargo install proc-cli,npm install -g proc-cli,scoop bucket add proc https://github.com/yazeed/scoop-bucket-proc && scoop install proc(Windows), ornix profile install github:yazeed/proc.
Critical Rules
- Always pass
--jsonon every command for structured output - Always pass
--yeson destructive commands to skip interactive prompts - Use
--dry-runbefore destructive actions to preview what would happen - Check the exit code: 0=success, 1=general error (including partial failures and
waittimeouts), 2=not found, 3=permission denied, 4=invalid input."success": falsealways comes with a non-zero exit code - Errors with
--jsonproduce{"action":"<cmd>", "success":false, "error":"...", "exit_code":N}on stdout — including invalid flags or arguments - Every JSON response is a single document (except the
watchstream) withaction(the command name, aliases resolved) andsuccessfields - Only touch processes you started or the user named. Prefer port and PID targets; scope name targets with
--in <dir>or--by <name> - Exit 3 means you need elevated rights. Ask the user before retrying with
sudo
Use proc Instead Of
| Instead of | Use |
|---|---|
lsof -i :3000, netstat -tlnp, ss -tlnp | proc on :3000 --json, proc ports --json |
lsof -ti :3000 | xargs kill -9 | proc free :3000 --yes --json (kills, then verifies the port is free) |
ps aux | grep node, pgrep -f node | proc by node --json, or several patterns: proc list 'node,vite' --json |
pkill -f pattern | proc kill pattern --in . --dry-run --json, then the same with --yes |
kill PID, kill -9 PID | proc stop PID --yes --json, proc kill PID --yes --json |
kill -STOP / kill -CONT | proc freeze / proc thaw |
while kill -0 $PID; do sleep 1; done | proc wait PID --timeout 60 --json |
lsof /path/to/file | proc for /path/to/file --json |
pstree, ps -o ppid= | proc tree PID --json, proc why PID --json |
top, ps aux --sort=-%cpu | proc list --sort cpu --limit 10 --json |
Target Syntax
Most commands accept the same target format:
:3000— process listening on port 30001234— process with PID 1234node— processes matching name "node" (substring match on name and command line):3000,:8080,node— comma-separated multiple targets, deduplicated by PID
Exceptions: proc free takes ports only, proc for takes a file path, proc in takes a directory.
Aliases you may see users type: : (on), f (for), b (by), l/ps (list), i/inspect (info), p (ports), k (kill), s (stop), t (tree), w/top (watch), x (stuck), u (unstick), o (orphans). JSON action always uses the full name.
Commands
Discovery (read-only, no --yes needed)
proc on :3000 --json # what process is on port 3000?
proc on 1234 --json # what ports does PID 1234 use?
proc on node --json # what ports are node processes using?
proc for ./server.py --json # what's running (or holding open) this file?
proc by node --json # find processes by name
proc by node --in . --json # ...started from the current directory
proc in . --json # all processes whose cwd is under this directory
proc in ~/project --by node --json
proc list --json # all processes
proc list --min-cpu 10 --json # processes using >10% CPU
proc list --status stopped --json # frozen/stopped processes
proc list --path /usr/local/bin --json # filter by executable path
proc info :3000,1234 --json # inspect one or more targets: full details
proc ports --json # all listening ports
proc ports --exposed --json # only ports bound to 0.0.0.0 / ::
proc ports --local --json # only ports bound to 127.0.0.1 / ::1
proc ports --range 3000-3999 --json
proc why :3000 --json # trace ancestry — why is this port busy?
proc tree node --json # descendants of node processes
proc tree 1234 --ancestors --json # path from the top ancestor down to PID 1234
proc orphans --in . --json # processes whose parent died (reparented to PID 1)
proc stuck --json # processes >50% CPU running longer than --timeout (default 300s)
To inspect a process, use proc info (aliases: inspect, i; JSON action is always "info"). For each target it reports the name, PID, command line, executable path, working directory, user, parent PID, status, CPU, memory, start time and uptime. list, by and in return the same process fields for many processes at once.
Lifecycle (destructive — always use --yes --json)
proc kill :3000 --yes --json # force kill (SIGKILL)
proc kill node --in . --graceful --yes --json # SIGTERM instead of SIGKILL
proc stop node --yes --json # SIGTERM, then SIGKILL after --timeout (default 10s)
proc stop nginx --signal HUP --yes --json # send a different initial signal
proc freeze node --yes --json # pause (SIGSTOP)
proc thaw node --yes --json # resume (SIGCONT)
proc free :3000 --yes --json # kill + verify port is free (--wait, default 10s)
proc free :3000,:8080 --yes --json # free multiple ports
proc unstick 1234 --yes --json # SIGCONT then SIGINT; no kill
proc unstick 1234 --force --yes --json # ...then SIGTERM, SIGKILL if still stuck
proc orphans --in . --kill --yes --json # kill orphans whose cwd is under this directory
Monitoring
proc wait node --timeout 60 --json # block until all node processes exit (max 60s)
proc wait :3000 -n 1 -q --timeout 30 --json # poll every 1s, quiet
timeout 10 proc watch node --json # NDJSON stream, one object per refresh (-n seconds)
Playbooks
A port is busy (EADDRINUSE, "address already in use")
proc on :3000 --json # who holds it? check name, cwd and command
proc why :3000 --json # what started it (ancestry)
proc free :3000 --yes --json # only if it's yours or the user agreed
If the holder is the user's own app, another project, or another agent's server, ask before freeing it, or start yours on a different port.
Run a server or job in the background, then clean it up
- Note its port or PID when you start it. Stop it by that target:
proc stop :4789 --yes --jsonorproc kill 12345 --yes --json. - Don't stop it with a name pattern copied from the command you typed. The real command line often differs (
bunx serveruns asnode …/serve), so the pattern can silently match nothing. killandstopsignal only the processes you target, not their children. Killingnpm run devcan leave itsnodeorts-nodechild running. Checkproc tree PID --jsonand target the children too, or stop by port.- Confirm it's gone:
proc on :4789 --jsonexits 2 when nothing is listening.
Before finishing a task, check for leftovers
proc ports --in <project-dir> --json # servers you left listening
proc orphans --in <project-dir> --json # processes whose parent already exited
proc list 'headless,ms-playwright,puppeteer' --json # headless browsers
--in matches the process's working directory, so pass the directory the process ran in (where you cd'd), which may not be your own cwd. Before stopping a leftover, check proc why PID --json. If its ancestry leads to another agent session, an editor or the user's terminal, it isn't yours: report it instead of killing it.
The machine is slow
proc list --sort cpu --limit 10 --json
proc list --min-mem 1000 --sort mem --json # over 1 GB
proc stuck --json # sustained high CPU
Report what you find with each process's cwd, uptime_secs and parent_pid, and let the user decide what to stop.
Restart something cleanly
proc stop :3000 --yes --json # SIGTERM, SIGKILL after 10s
proc on :3000 --json # exit 2 = nothing listening; safe to start again
proc free :3000 --yes --json does both in one step: SIGTERM, SIGKILL after at most 5 seconds, then it checks the port was released. Use proc wait for processes you expect to exit on their own: with no matching process it exits 2 straight away.
Safety
- Name targets match every process proc can see whose name or command line contains the pattern: other projects, editors, the user's terminals, and other agents' MCP servers and browsers. Always
--dry-runa name target first, and scope it with--in. - proc never matches its own process or its parent chain (up to 10 levels) by name, so
proc by clauderun from inside an agent leaves out that agent. PID and port targets are not protected. - Don't use
kill(SIGKILL) as a first resort. Preferstop, which gives the process time to clean up.
Filters
Not every command takes every filter — check proc <cmd> --help.
| Filter | Commands |
|---|---|
--in [dir] (-i, defaults to .) | all except why, free, in |
--by <name> (-b) | all except why, free, by and list (those two take the name as an argument) |
--min-cpu <pct>, --min-mem <MB> | list, by, in, for, tree, watch |
--status <s> (running, sleeping, stopped, zombie) | list, by, in, for, tree |
--min-uptime <secs> | list, by, in, for, tree |
--parent <pid> | list, by, in |
--path <path> (-p, executable path) | list |
--sort <key> (cpu, mem, pid, name) | list, by, in, for, watch |
--sort <key> (port, pid, name) | ports |
--limit <n> (-n; -l on watch) | list, by, in, for, ports, watch |
--depth <n> (-d, default 10), --compact (-C, PIDs only) | tree |
Output Schemas
For complete JSON output schemas and field definitions, see reference.md. The fields you'll use most on a process: pid, name, command, cwd, parent_pid, user, status, cpu_percent, memory_mb, start_time, uptime_secs.
Agent/Pipe Readiness
- One-shot queries:
on,for,by,in,list,info,ports,why,tree,orphans,stuck— return immediately - Blocking:
proc wait— always pass--timeout - Streaming:
proc watch --json— NDJSON forever; bound it withtimeout Nor| head -n N. Without--jsonand without a TTY it prints one snapshot. Preferproc listfor a single snapshot - Destructive:
kill,stop,freeze,thaw,free,unstick, andorphans/stuckwith--kill— always pass--yes --json - Showing results to the user: run without
--jsonfor a readable table (anUPTIMEcolumn appears on terminals 100+ columns wide)
Gotchas
- Port targets must start with
:—proc on 3000looks up PID 3000, not port 3000. proc freeonly accepts port targets (:3000). Useproc killfor name/PID targets.- Name matching is substring-based:
proc by nodematches "node", "nodejs", "nodemon". Narrow with--inor use PIDs. proc bytakes one pattern;proc listtakes several, comma-separated:proc list 'git commit,pre-commit' --json. Patterns are plain case-insensitive substrings matched against the name and the full command line, not regular expressions.- Uptime is
uptime_secsin the JSON (seconds).start_timeis a Unix timestamp, not a duration. - Processes can keep running in a directory that was deleted (a removed worktree, say), and they're often the leftovers you're looking for. From proc 1.14.1,
proc in <dir>lists them andcwdshows the plain path. In 1.14.0 and earlier,proc in <dir>rejects a deleted directory with exit 4, so useproc list --in <dir>orproc orphans --in <dir>there (on macOS; on Linux those versions miss them, because the kernel reports their cwd as<dir> (deleted)). --dry-run --jsonprints"dry_run": trueplus theprocessesthat would be affected; nothing is changed.proc orphans --killandproc stuck --killreport likeproc kill(succeeded/failed), withaction"orphans"/"stuck".- If any process fails in
kill/stop/freeze/thaw/orphans --kill/stuck --kill, the exit code is 1 andfailedlists why; the ones insucceededwere still acted on. proc waiton timeout prints its result withtimed_out: trueand exits 1.--signalis only onproc stop. Valid signals: HUP, INT, QUIT, ABRT, KILL, TERM, STOP, CONT, USR1, USR2 (with or withoutSIG, any case).--gracefulis only onproc kill.- Unknown flags exit 4; with
--jsonthe error is a JSON object on stdout, otherwise plain text on stderr. freezeandthaware Unix only — on Windows they fail with exit 1.proc stuckis a heuristic (sustained high CPU), not a hang detector.- An orphan (parent PID 1) isn't necessarily abandoned: daemons and
tmux/nohupjobs are orphaned on purpose. Checkcwd,commandand uptime before treating one as a leftover.