By Sarah Chen · MyTechDailyNews, Software desk
Deadlines usually slip over process chores rather than architecture: a dev server that will not bind, a build that outlived the terminal that launched it, a port quietly held by something nobody can name. proc is a semantic command-line tool for process work on macOS, Linux, and Windows, and its version 1.14.5 update is organised around one idea — address a process the way you already describe it out loud. A leading colon means a port, a bare number means a PID, and anything else is compared against process names and full command lines. This guide maps that grammar across nineteen commands and three jobs, flags the permission-sensitive corners, and shows where one command replaces a pipeline of lsof, ps, grep, pgrep, xargs, kill, and sleep.
For a daily-tools reader, three things decide whether a CLI earns a place in the workflow: what it actually does, how much trust it costs, and what you can type right now. On the trust question, proc is conservative by default — it asks before changing state, and --dry-run converts any change into a preview. On the typing question, every example below uses the same four target shapes: port 3000, PID 1234, the name node, or a path.
:3000 selects the listener for port 3000. Paired with kill, that listener ends immediately via SIGKILL; the tool asks before changing state, and --dry-run supplies the preview instead.1234 affects only process 1234, and no other process in the table.node is checked against both the name and the full command line of every process, so nodemon and node server.js match too. Under kill, each matched node process is listed first and ended only after confirmation.:3000,:8080,node is handled together after a single confirmation, and a process that matches more than one selector is still acted on only once.node --in . limits node matches to the current folder and its subfolders, which keeps your editor and other projects out of the blast radius. Most commands accept --in../server.py — relative, absolute, or starting with ~ — finds processes running server.py from this folder; ./logs/app.log finds processes with app.log open, such as a server still writing to it.Use on to inspect without modifying anything. Against :3000 it returns the listening process with name, PID, folder, CPU, memory, and running time. Against 1234 it returns that process plus every port it listens on. Against node it returns each matching process and its ports. Hand it the group :3000,:8080,node and it answers all three questions in one pass — what sits on those two ports, and which ports the node processes hold. Add --in . and the reply covers only node processes started under this folder.
why traces origin. proc why :3000 walks backward through parent processes from the listener on port 3000 and reveals the start point: a terminal tab, a tmux session, an editor. With 1234 it prints the parent chain that produced that process, along with each parent's running time. With node it traces every matching process to whatever launched it, and with :3000,:8080 it returns one parent chain per held port.
Three commands, three axes. for works on files: ./server.py finds processes running server.py from this folder, and ./logs/app.log finds processes that have app.log open — a long-running server still writing to its own log is the typical case. by works on names: node lists every process whose name or command line matches, and node --in . trims that list to the current folder and below. in works on folders: . lists everything started where you are standing, while ~/code/shop lists everything started in ~/code/shop wherever you run the command — and it still resolves even if that folder has since been deleted.
With no target, list walks every process and sorts by highest resource use first. Add node to filter to matching processes. Add --status zombie to surface exited work its parent never cleaned up; --status also accepts running, sleeping, and stopped. When you need the whole record, info 1234 returns folder, path, user, parent, status, CPU, memory, and uptime for PID 1234; :3000 returns the same full detail for the listener; node returns it for every match.
ports with no target lists every listening port and the process using it. --exposed cuts the output down to ports reachable by other machines — those listening on 0.0.0.0 or :: — while --local keeps the localhost ones. --in . shows only ports held by processes started in this folder. tree answers a different question: with no target it prints every process as parents and children; node prints each node process together with the processes it started; and 1234 --ancestors reverses the direction, climbing from PID 1234 up to the first process the system started.
orphans finds processes whose parent has exited, which is exactly the profile of dev servers left behind after a terminal closes; system services are omitted from the result. With no target it checks all parentless processes, --in . limits the scan to this folder, and --in . --kill shows those orphans, asks, and then ends them. stuck looks for busy long-running work: with no target it reports processes running over 5 minutes that currently use more than half a CPU, and --kill ends whatever it finds after display and confirmation.
kill is immediate termination. :3000 kills the listener on port 3000 with SIGKILL, 1234 kills only PID 1234, node lists every match and ends them after confirmation, :3000,:8080,node clears the whole group after one confirmation, and node --in . kills only the node processes located under the current folder.
stop is the polite version. :3000 asks the listener on port 3000 to exit with SIGTERM and forces it after 10 seconds if it is still there; 1234 behaves the same way for that PID; node gracefully stops every match after confirmation; node --in . limits the stop to the current folder. You can also swap the signal: :3000 --signal HUP sends HUP instead of SIGTERM, and many servers treat HUP as "reload your config" — the fastest way to apply a config change without dropping the port.
free works on ports only. :3000 stops the process holding port 3000 politely first and forcefully second, then confirms the port is genuinely free. Commas combine ports, so :3000,:8080 frees both in one operation.
freeze pauses without ending. On macOS and Linux, node sends SIGSTOP to every matching process; 1234 pauses PID 1234 — the practical use being a heavy build that you freeze while you need the machine back; :3000 pauses the listener, which leaves existing connections hanging until you thaw it. thaw reverses it: node resumes previously frozen node processes with SIGCONT, and 1234 resumes PID 1234 whether proc freeze or Ctrl+Z paused it.
With no target, unstick finds stuck processes, wakes them with SIGCONT, and interrupts them the way Ctrl+C would; nothing is terminated unless you add --force. unstick 1234 runs the same gentle recovery on that PID. node --force escalates, falling back to terminating stuck node processes with SIGTERM and then SIGKILL.
watch is live monitoring. With no target it renders a busy-first table refreshed every 2 seconds, and q quits. node restricts the display to node processes; :3000 restricts it to the listener on port 3000.
wait blocks until a target exits, which is what makes it useful in scripts. :3000 waits until the process on port 3000 has gone, letting a script launch the next server; 1234 waits for that PID. In CI, node --timeout 60 abandons the wait after 60 seconds if node processes remain, preventing the job from hanging forever, and a timed-out --timeout exits 1 so the pipeline can fail loudly instead of silently.
Every command accepts the same target forms, which is the main reason the tool is guessable on the first try. Almost all of them support --in for project scoping, --json for machine-readable output, and --dry-run to preview a change before it happens. Listing flags narrow the search instead of acting on it: --status filters process states, --exposed filters ports. --kill takes whatever a listing command discovered and applies that command's action to it after asking — the same confirmation flow as proc kill. The whole surface is nineteen commands covering three jobs: inspect, change state, and watch or wait.
Two read-only commands carry real security value for anyone on a shared host or a laptop that visits untrusted networks. proc ports --exposed lists the ports other machines can reach, meaning those bound to 0.0.0.0 or ::, so a dev database or admin panel that was supposed to stay on localhost is easy to spot; proc ports --local shows the localhost set for comparison. proc orphans --in . reveals background servers you started days ago and forgot about — a forgotten listener is both a resource leak and an exposure. On the acting side, note the asymmetry: --dry-run only identifies what would be touched, nothing is terminated by unstick unless --force is supplied, and a permission problem is reported as a distinct exit code (3) rather than a silent partial success.
The classic blocker is a dev server that cannot start because port 3000 is taken. The workflow below locates the old tmux session that holds it, releases the port, and cleans up the other processes left running.
proc on :3000 and read the name, PID, folder, CPU, memory, and running time of the listener.proc why :3000. The parent chain tells you whether the listener came from a terminal tab, a tmux session, or your editor — that determines whether killing it also kills your session.proc list --in . and proc ports --in . to see everything else this folder started and the ports those processes hold.proc stop :3000, which sends SIGTERM and only forces the issue after 10 seconds. Reserve proc kill :3000 for a listener that ignores SIGTERM.proc free :3000 — it politely and forcefully stops the holder, then confirms the port is truly released. If several ports are stuck, :3000,:8080 handles both in one operation.proc orphans --in . --kill to display and, after confirmation, end parentless processes from this folder.proc wait :3000 blocks until the old process has exited, and proc watch :3000 keeps the new listener on screen.All of this is achievable with lsof, ps, grep, pgrep, xargs, kill, and sleep. The difference is that proc removes the need to remember lsof flags or to pipe PIDs into kill, and it adds a confirmation step in front of every state change.
| Task | Shell pipeline | proc command |
|---|---|---|
| Who owns port 3000? | lsof -i :3000 -P -n | proc on :3000 |
| Where did it come from? | lsof -i :3000, then ps -o ppid= up the chain | proc why :3000 |
| Release the port | lsof -t -i:3000 | xargs kill && sleep 1 && lsof -i:3000 | proc free :3000 |
| Block until the jobs are gone | while ps aux | grep node | grep -v grep; do sleep 5; done | proc wait node |
| Suspend instead of end | kill -STOP $(pgrep node) | proc freeze node |
| Hunt leftover processes | No standard tool | proc orphans --in . |
The documented install line is brew install yazeed/proc/proc on macOS. Version 1.14.5 also reaches users through npm, cargo, Scoop, Nix, and Docker, so pinning the tool into a project image or a shared devcontainer is a packaging choice rather than a manual task — which matters for teams that want the same command surface on Windows, Linux, and macOS boxes.
The tool is explicitly aimed at scripts and coding agents rather than only at interactive use. Each command emits one JSON document when you pass --json, and the exit codes are fixed: 0 success, 1 error, 2 not found, 3 permission denied, 4 invalid input. --dry-run only identifies what would be touched, so an agent can plan a change without risk. An agent skill teaches Claude Code, Codex, Cursor, Gemini CLI, and similar tools when to reach for proc. In the documented example proc on :8080 --json | jq '.process | {pid, cwd, uptime_secs}', the sample values returned are pid 177, cwd /home/dev/code/shop, and uptime_secs 35.
wait node --timeout 60 gives up after 60 seconds and exits 1.--timeout 60 so it abandons after 60 seconds and exits 1.proc covers process work on macOS, Linux, and Windows with the same target forms everywhere. Where the platforms diverge is the pause mechanism: on macOS and Linux, freeze sends SIGSTOP and thaw sends SIGCONT.
Use folder scoping. Because node also matches nodemon and node server.js, node --in . limits the operation to processes started in the current folder and its subfolders. Then run it once with --dry-run to read the target list, and remember that even a batch such as :3000,:8080,node is confirmed once and applied once per process.
kill is immediate SIGKILL. stop is polite SIGTERM with a forced escalation after 10 seconds, and --signal HUP swaps the signal — many servers treat HUP as "reload your config". free is port-specific and verifies the port is truly released afterwards. unstick tries recovery first: SIGCONT plus a Ctrl+C-style interrupt, terminating nothing unless --force is given.
Yes. Pass --json for a single machine-readable document, branch on the fixed exit codes 0, 1, 2, 3, 4, and keep --timeout on wait so a stuck target cannot block a pipeline indefinitely. The jq example above shows the shape of the payload you parse.
The documented line is brew install yazeed/proc/proc; npm, cargo, Scoop, Nix, and Docker all deliver version 1.14.5 as well. Match the channel to how the machine is already provisioned so upgrades stay automatic.
No — proc is a wrapper around the same semantics, so SIGTERM, SIGKILL, SIGSTOP, and SIGCONT behave exactly as the signal documentation describes; what changes is that you address processes by port, PID, name, path, or folder instead of assembling the signal call by hand.
proc on and proc why your default first moves — they change nothing and both handle grouped targets.proc orphans --in . --kill.proc ports --exposed monthly on any machine that hosts dev services, and compare with --local.--timeout 60 on wait.If your process work currently lives in half-remembered lsof flags and copy-pasted kill pipelines, version 1.14.5 is worth an afternoon: install it through whichever channel already owns your machine, learn the four target shapes, and keep the read-only commands as your reflex. The value is not a new capability — SIGTERM and SIGSTOP have been available for decades — but a single grammar that lets you ask what is on port 3000, why it is there, and how to clear it before your next build starts. Questions or tips for the Software desk: hello@techdaily.com.