# proc
<p align="center">
<img src="https://raw.githubusercontent.com/yazeed/proc-cli/main/demo.gif" alt="proc demo: npm run dev fails because port 3000 is taken; proc on, why and free find the old tmux session holding it and free the port; proc orphans finds and cleans up leftover processes" width="800" />
</p>
[](https://github.com/yazeed/proc-cli/actions)
[](https://opensource.org/licenses/MIT)
[](https://crates.io/crates/proc-cli)
[](https://hub.docker.com/r/yazeed/proc-cli)
[](https://www.npmjs.com/package/proc-cli)
[](CHANGELOG.md)
[](https://crates.io/crates/proc-cli)
[](https://opencollective.com/proc-cli)
[](https://safeskill.dev/scan/proc-cli)
[](https://socket.dev/npm/package/proc-cli)
[](https://proc-cli.com/)
Semantic CLI tool for process management. Target by port, PID, name or path.
```bash
proc on :3000 # what's on port 3000?
proc for ./server # what's running this program?
proc by node --in . --min-cpu 5 # node in cwd using >5% CPU
proc kill :3000 :8080 node # kill mixed targets at once
proc info :3000 1234 # info for port + PID
```
### Why proc?
| Task | proc | Without proc |
|------|------|-------------|
| What's on port 3000? | `proc on :3000` | `lsof -i :3000 -P -n` |
| Kill it | `proc kill :3000` | `lsof -i :3000 -t \| xargs kill -9` |
| Free port 3000 | `proc free :3000` | `lsof -i :3000 -t \| xargs kill && sleep 1 && lsof -i :3000` |
| Why is port busy? | `proc why :3000` | `lsof -i :3000` + `ps -o ppid=` chain |
| Pause a process | `proc freeze node` | `kill -STOP $(pgrep node)` |
| Find orphans (macOS, Linux) | `proc orphans --in .` | No standard tool |
| Wait for process | `proc wait node` | `while ps aux \| grep node \| grep -v grep; do sleep 5; done` |
| Ports used by node | `proc on node` | `lsof -i -P -n \| grep node` |
| Node processes in cwd | `proc by node --in .` | `ps aux \| grep node` + manual check |
| Kill mixed targets | `proc kill :3000 :8080 node` | Three separate commands |
| Watch in real-time | `proc watch node` | `watch -n2 'ps aux \| grep node'` |
| What runs this program? | `proc for ./server` | `lsof ./server` or `fuser ./server` |
One syntax. One tool. [Full comparison →](https://proc-cli.com/posts/proc-vs-lsof-fuser-killport-fkill/)
## Install
| Platform | Command |
|----------|---------|
| macOS | `brew install yazeed/proc/proc` |
| Windows | `scoop bucket add proc https://github.com/yazeed/scoop-bucket-proc && scoop install proc` |
| Rust | `cargo install proc-cli` |
| npm/bun | `npm install -g proc-cli` |
| Nix | `nix profile install github:yazeed/proc-cli` |
| Docker | `docker run --rm -it --pid=host --net=host yazeed/proc-cli` |
| Shell | `curl -fsSL https://raw.githubusercontent.com/yazeed/proc-cli/main/install.sh \| bash` |
<details>
<summary>More options</summary>
| Platform | Method | Command |
|----------|--------|---------|
| macOS/Linux | cargo-binstall | `cargo binstall proc-cli` |
| Arch Linux | AUR (community-maintained) | `yay -S proc-bin` |
**Manual download:**
```bash
# macOS (Apple Silicon)
curl -fsSL https://github.com/yazeed/proc-cli/releases/latest/download/proc-darwin-aarch64.tar.gz | tar xz
sudo mv proc-darwin-aarch64 /usr/local/bin/proc
# macOS (Intel)
curl -fsSL https://github.com/yazeed/proc-cli/releases/latest/download/proc-darwin-x86_64.tar.gz | tar xz
sudo mv proc-darwin-x86_64 /usr/local/bin/proc
# Linux (x86_64)
curl -fsSL https://github.com/yazeed/proc-cli/releases/latest/download/proc-linux-x86_64.tar.gz | tar xz
sudo mv proc-linux-x86_64 /usr/local/bin/proc
# Linux (ARM64)
curl -fsSL https://github.com/yazeed/proc-cli/releases/latest/download/proc-linux-aarch64.tar.gz | tar xz
sudo mv proc-linux-aarch64 /usr/local/bin/proc
# Windows (PowerShell)
Invoke-WebRequest -Uri https://github.com/yazeed/proc-cli/releases/latest/download/proc-windows-x86_64.exe.zip -OutFile proc.zip
Expand-Archive proc.zip -DestinationPath .
Move-Item proc-windows-x86_64.exe C:\Windows\System32\proc.exe
```
</details>
### Agent Skill
Teach your coding agent (Claude Code, Codex, Cursor, Gemini CLI and others) to use proc with structured JSON output:
```bash
npx skills add yazeed/proc-cli
```
In Claude Code you can also install it as a plugin:
```text
/plugin marketplace add yazeed/proc-cli
/plugin install proc-cli@proc-cli
```
The skill lives in [`skills/proc-cli/`](skills/proc-cli/SKILL.md) and follows the [Agent Skills](https://agentskills.io) standard. [What it teaches, how to update it, and prompts to try →](https://proc-cli.com/skill)
## Quick Start
**What's using port 3000?**
```bash
proc on :3000
```
**Kill it:**
```bash
proc kill :3000
```
**What's running this program?**
```bash
proc for ./server
```
**Find all node processes in current directory using >5% CPU:**
```bash
proc by node --in . --min-cpu 5
```
**Kill multiple targets at once:**
```bash
proc kill :3000 :8080 node
```
**Watch processes in real-time:**
```bash
proc watch node --in .
```
## Target Syntax
All commands accept the same target syntax:
| Target | Example | Description |
|--------|---------|-------------|
| Port | `:3000` | Every process listening on port 3000 |
| PID | `1234` | Process with ID 1234 |
| Name | `node` | Processes named "node" (the whole name, ignoring case) |
| Multi | `:3000 :8080 node` | Separate arguments |
Give several targets as separate arguments, and mix the three kinds freely: `proc kill 1234 :3000 node`. A process named twice is handled once, except by the read-only `on` and `why`, which answer each target under its own header. Comma-separated targets (`:3000,:8080,node`) still work but are deprecated, and will be removed in a future major release: a command that gets one prints a `note:` on stderr with the separate-argument form (human output only; `--json` is unchanged).
A name matches the whole process name, ignoring case, the way `killall` and `pkill` read it. `proc kill node` stops the processes named `node` and leaves `Claude Helper` alone, and `--by` follows the same rule. The file name of the executable and of the first argument count as names too, so a long name that Linux cuts to 15 characters still matches, and on Windows a trailing `.exe` is optional. Add `--cmd` to match any part of the name or the full command line instead, like `pkill -f`: `proc kill serve --cmd`. A path is not a name: `proc kill ./server` finds nothing, so use `--cmd`, or `proc for <file>` for an executable. proc doesn't expand wildcards; an unquoted `node*` is expanded by your shell into file names; use `--cmd` for partial matches.
`--in` takes the next word as its directory unless that word starts with `-`, so put it after the targets (`proc kill 1234 5678 --in .`) or write `--in=DIR`.
## Commands
### Discovery
| Command | Alias | Description |
|---------|-------|-------------|
| `on <target>` | `:` | Bidirectional port/process lookup |
| `for <file>` | `f` | Find processes by file path |
| `by <name>` | `b` | Filter processes by name |
| `in <path>` | | Filter processes by working directory |
| `list` | `l`, `ps` | List all processes |
| `info <target>` | `i`, `inspect` | Detailed process information |
| `ports` | `p` | List all listening ports |
| `tree` | `t` | Process hierarchy |
| `watch` | `w`, `top` | Real-time process monitoring |
| `why <target>` | | Trace process ancestry (why is this port busy?) |
| `orphans` | `o` | Find orphaned processes (Unix only: exits 4 on Windows) |
| `stuck` | `x` | Find hung processes |
### Lifecycle
| Command | Alias | Description |
|---------|-------|-------------|
| `kill <target>` | `k` | Force kill (SIGKILL) |
| `stop <target>` | `s` | Graceful stop (SIGTERM, `--signal` for custom) |
| `freeze <target>` | | Pause process (SIGSTOP) |
| `thaw <target>` | | Resume frozen process (SIGCONT) |
| `free <:port>` | | Kill process and verify port freed |
| `wait <target>` | | Block until process(es) exit |
| `unstick` | `u` | Recover stuck processes |
### Filters
Filters can be combined with discovery and lifecycle commands:
| Filter | Description |
|--------|-------------|
| `--in <path>` | Filter by working directory |
| `--by <name>` | Filter by process name (the whole name, like a name target) |
| `--cmd` | Match names and `--by` as a substring of the process name or full command line (like `pkill -f`) |
| `--path <path>` | Filter by executable path (`list`) |
| `--min-cpu <n>` | Processes using >n% CPU |
| `--min-mem <n>` | Processes using >n MB memory |
| `--min-uptime <s>` | Processes running longer than s seconds |
| `--parent <pid>` | Children of a specific parent PID |
| `--status <s>` | Filter by status: running, sleeping, stopped, zombie |
| `--exposed` | Only network-exposed ports (`ports`) |
| `--local` | Only localhost ports (`ports`) |
| `--range <s-e>` | Filter ports by range (e.g., `3000-9000`) |
| `--sort/-s <key>` | Sort by: cpu, mem (biggest first); pid, name (ascending). Ties come by PID |
| `--limit/-n <n>` | Limit number of results (`-l` on `watch`, where `-n` is `--interval`) |
| `--ancestors/-a` | Show ancestry path to root (`tree`) |
| `--depth/-d <n>` | Maximum tree depth (default: 10) |
| `--compact/-C` | Show PIDs only in compact view (`tree`) |
| `--timeout/-t <s>` | `stuck`, `unstick`: only processes running longer than this (default: 300); stuck means busy or blocked for 2 seconds |
### Options
| Option | Short | Description |
|--------|-------|-------------|
| `--json` | `-j` | JSON output |
| `--verbose` | `-v` | Show paths, cwd, full commands |
| `--wide` | `-w` | Print full directories and arguments instead of fitting the terminal (`list`, `by`, `in`, `for`, `stuck`, `orphans`, `watch`) |
| `--yes` | `-y` | Skip confirmation; required with `--json` or without a terminal, where nobody can be asked |
| `--dry-run` | | Preview without executing |
| `--force` | `-f` | Force termination if recovery fails (`unstick`) |
| `--graceful` | `-g` | Send SIGTERM instead of SIGKILL (`kill`) |
| `--signal` | `-S` | Custom signal: HUP, INT, USR1, etc. (`stop`) |
| `--kill` | `-k` | Kill found processes (`stuck`, `orphans`) |
| `--wait` | | Max seconds to wait for port to free (`free`) |
| `--interval` | `-n` | Seconds between status lines (`wait`) or refreshes (`watch`) |
| `--timeout` | `-t` | Max seconds to wait before giving up (`wait`, `stop`) |
| `--quiet` | `-q` | Suppress periodic status messages (`wait`) |
### Scripting
With `--json`, every command prints one JSON document with `action` (the command name) and `success`. `proc watch --json` streams one per refresh. Errors, including invalid flags, are JSON too: `{"action", "success": false, "error", "exit_code"}`. Exit 4 means nothing was done, and its error adds `"reason"`: `"invalid_input"` (a wrong flag, value or argument), `"needs_confirmation"` or `"not_supported"` (this OS cannot do it). A command that changes processes run with `--json` but without `--yes` exits 4 and adds `"needs_confirmation": true` and `"processes"` (the ones it would have changed), so a script can show them before running it again with `--yes`. Every command that takes targets also has a `"not_found"` array in its JSON (the targets that matched nothing, empty when all were found), and it appears in that exit 4 output too.
| Exit code | Meaning |
|-----------|---------|
| `0` | Success |
| `1` | Error, or not fully successful: a process failed, a port stayed busy, `wait` timed out |
| `2` | Process or port not found |
| `3` | Permission denied: proc itself got an I/O permission error. A signal the OS refuses (another user's process) is a `failed` entry and exit 1 |
| `4` | Invalid input (unknown flag, bad argument, invalid signal), an action this OS doesn't support (`freeze`, `thaw`, `orphans` and `stop --signal` on Windows), or a command that changes processes run without `--yes` where nobody can confirm (`--json`, or no terminal): nothing was changed |
`"success": false` always comes with a non-zero exit code. Full schemas: [`skills/proc-cli/reference.md`](skills/proc-cli/reference.md).
## Examples
```bash
# What's on port 3000?
proc on :3000
# What ports is node using?
proc on node
# What's running this program?
proc for ./server
# What has this file open?
proc for /var/log/app.log
# Node processes in current directory
proc by node --in .
# Processes using >10% CPU
proc list --min-cpu 10
# Kill everything on ports 3000 and 8080
proc kill :3000 :8080
# Kill only node processes in current directory
proc kill node --in . --dry-run
# Process tree filtered by CPU usage
proc tree --min-cpu 5
# Watch all processes sorted by CPU (alias: proc top)
proc watch
# Watch node processes with 1s refresh
proc watch node -n 1
# Watch process on port 3000
proc watch :3000
# Watch processes in cwd sorted by memory
proc watch --in . --sort mem
# Freeze/thaw (pause and resume)
proc freeze node # Pause all node processes
proc thaw node # Resume them
# Free a port (kill + verify available)
proc free :3000 # Kill and confirm port 3000 is free
proc free :3000 :8080 --yes # Free multiple ports
# Why is port 3000 busy?
proc why :3000 # Show ancestry chain with port context
# Find orphaned processes
proc orphans # List orphans
proc orphans --in . --kill # Kill orphans in current directory
# Wait for processes to exit
proc wait node # Block until all node processes exit
proc wait :3000 --timeout 60 # Wait up to 60s for port process to exit
proc wait node -n 10 -q # Check every 10s, quiet mode
# Send custom signal
proc stop worker --signal INT # Ask with SIGINT instead of SIGTERM (force-killed after --timeout)
# Find and recover stuck processes
proc stuck
proc unstick --force
```
## Platform Support
| Platform | Architecture | Status |
|----------|--------------|--------|
| macOS | Apple Silicon (ARM64) | ✅ |
| macOS | Intel (x86_64) | ✅ |
| Linux | x86_64 | ✅ |
| Linux | ARM64 | ✅ |
| Windows | x86_64 | ✅ |
| Docker | linux/amd64, linux/arm64 | ✅ |
The Linux binaries are statically linked (musl) and need no particular glibc version, so they also run on Alpine and on older releases such as Debian 10, Ubuntu 20.04 and Amazon Linux 2.
## Shell Completions
Generate completions for your shell:
```bash
# Bash
proc completions bash > /etc/bash_completion.d/proc
# Zsh
proc completions zsh > ~/.zsh/completions/_proc
# Fish
proc completions fish > ~/.config/fish/completions/proc.fish
```
## Man Page
Generate and install the man page:
```bash
proc manpage | sudo tee /usr/local/share/man/man1/proc.1 > /dev/null
```
## Building from Source
```bash
git clone https://github.com/yazeed/proc-cli
cd proc-cli
cargo build --release
```
The binary will be at `target/release/proc`.
## Contributing
See [CONTRIBUTING.md](CONTRIBUTING.md).
## License
MIT