proc-cli 2.0.0

Semantic CLI tool for process management. Target by port, PID, name or path.
Documentation
---
layout: default
title: proc
description: "Semantic CLI tool for process management. Target by port, PID, name or path."
---
<div class="home">

  <section class="hero" aria-labelledby="hero-title">
    <div class="hero-copy">
      <h1 id="hero-title"><span class="hero-target">Target</span> processes by port, PID, name or path.</h1>
      <p class="hero-lede">proc is a semantic CLI tool for process management on macOS, Linux and Windows. Find what's on a port, why it's there, and stop it, in one command you'd guess on the first try.</p>
    </div>

    {%- comment -%} The default command, rendered without JavaScript: kill :3000 {%- endcomment -%}
    {%- for group in site.data.reader.groups -%}{%- for v in group.verbs -%}{%- if v.verb == "kill" -%}{%- assign default_verb = v -%}{%- endif -%}{%- endfor -%}{%- endfor -%}
    {%- assign default_example = default_verb.examples | first -%}
    <div class="reader" data-reader>
      <p class="reader-line" aria-live="polite"><span class="p-path">~</span><span class="p-chevron" aria-hidden="true">❯</span><span class="p-verb">proc</span> <span class="reader-verb" data-reader-verb>kill</span><span data-reader-gap> </span><span class="reader-target" data-reader-target>:3000</span></p>
      <div class="reader-explain" data-reader-explain aria-live="polite">{% include reader-explain.html ex=default_example %}</div>
      <div class="reader-picks">
        <div class="reader-row">
          <span class="reader-label reader-label-verb" id="reader-verbs-label">Command</span>
          <div class="reader-choices reader-verbs" role="group" aria-labelledby="reader-verbs-label" data-reader-verbs>
            {%- for group in site.data.reader.groups -%}
            {%- comment -%} Discovery gets its own row; Action and Monitoring share the next {%- endcomment -%}
            {%- if forloop.index == 2 %}<span class="reader-break" aria-hidden="true"></span>{% elsif forloop.index > 2 %}<span class="reader-sep" aria-hidden="true"></span>{% endif -%}
            {%- for v in group.verbs -%}
            <button type="button" data-verb="{{ v.verb }}" aria-pressed="{% if v.verb == 'kill' %}true{% else %}false{% endif %}">{{ v.verb }}</button>
            {%- endfor -%}
            {%- endfor -%}
          </div>
        </div>
        <div class="reader-row">
          <span class="reader-label reader-label-target" id="reader-targets-label">Target</span>
          <div class="reader-choices reader-targets" role="group" aria-labelledby="reader-targets-label" data-reader-targets>{% include reader-chips.html verb=default_verb pressed=true %}</div>
        </div>
      </div>
      {%- for group in site.data.reader.groups -%}{%- for v in group.verbs %}
      <template data-examples="{{ v.verb }}">{% include reader-chips.html verb=v %}</template>
      {%- endfor -%}{%- endfor %}
    </div>

    <div class="hero-actions">
      <div class="install-line"><span class="p-chevron" aria-hidden="true">❯</span><code>brew install yazeed/proc/proc</code><button type="button" data-copy="brew install yazeed/proc/proc" aria-label="Copy install command">Copy</button></div>
      <p class="hero-meta">Version {{ site.data.release.version }}. Also on <a href="{{ '/install' | relative_url }}">npm, cargo, Scoop, Nix and Docker</a>.</p>
    </div>
  </section>

  <figure class="demo">
    <video autoplay loop muted playsinline preload="metadata" aria-label="proc freeing port 3000 from a forgotten tmux session, then cleaning up leftover processes">
      <source src="{{ '/assets/demo.mp4' | relative_url }}" type="video/mp4">
    </video>
    <figcaption>A dev server won't start because port 3000 is taken. proc finds the old tmux session holding it, frees the port, then cleans up what else was left running.</figcaption>
  </figure>

  <section class="section" aria-labelledby="swap-title">
    <h2 id="swap-title">One command instead of a pipeline</h2>
    <p class="section-lede">The same jobs, without remembering which flags <code>lsof</code> needs or piping PIDs into <code>kill</code>.</p>
    <div class="swap">
      <div class="swap-row"><span class="swap-task">What's on port 3000?</span><span class="swap-old"><code>lsof -i :3000 -P -n</code></span><span class="swap-new"><code>proc on :3000</code></span></div>
      <div class="swap-row"><span class="swap-task">Why is it there?</span><span class="swap-old"><code>lsof -i :3000</code>, then <code>ps -o ppid=</code> up the chain</span><span class="swap-new"><code>proc why :3000</code></span></div>
      <div class="swap-row"><span class="swap-task">Free the port</span><span class="swap-old"><code>lsof -t -i:3000 | xargs kill && sleep 1 && lsof -i:3000</code></span><span class="swap-new"><code>proc free :3000</code></span></div>
      <div class="swap-row"><span class="swap-task">Wait for it to exit</span><span class="swap-old"><code>while ps aux | grep node | grep -v grep; do sleep 5; done</code></span><span class="swap-new"><code>proc wait node</code></span></div>
      <div class="swap-row"><span class="swap-task">Pause a process</span><span class="swap-old"><code>kill -STOP $(pgrep node)</code></span><span class="swap-new"><code>proc freeze node</code></span></div>
      <div class="swap-row"><span class="swap-task">Find leftovers</span><span class="swap-old">No standard tool</span><span class="swap-new"><code>proc orphans --in .</code></span></div>
    </div>
  </section>

  <section class="section" aria-labelledby="verbs-title">
    <h2 id="verbs-title">Nineteen commands, three jobs</h2>
    <p class="section-lede">Every command takes the same targets, and almost all take <code>--in</code> to stay inside one project, <code>--json</code> for scripts, and <code>--dry-run</code> before anything changes.</p>
    <div class="verbs">
      <div class="v-observe">
        <h3>Find out</h3>
        <p>Read-only. Nothing is touched.</p>
        <ul>
          <li><a href="{{ '/commands/on' | relative_url }}"><code>on :3000</code><span>what's on a port</span></a></li>
          <li><a href="{{ '/commands/why' | relative_url }}"><code>why :3000</code><span>what started it</span></a></li>
          <li><a href="{{ '/commands/in' | relative_url }}"><code>in .</code><span>started in this folder</span></a></li>
          <li><a href="{{ '/commands/by' | relative_url }}"><code>by node</code><span>by name</span></a></li>
          <li><a href="{{ '/commands/for' | relative_url }}"><code>for ./server</code><span>running a program</span></a></li>
          <li><a href="{{ '/commands/info' | relative_url }}"><code>info 1234</code><span>everything about one</span></a></li>
          <li><a href="{{ '/commands/ports' | relative_url }}"><code>ports</code><span>all listening ports</span></a></li>
          <li><a href="{{ '/commands/tree' | relative_url }}"><code>tree</code><span>parents and children</span></a></li>
        </ul>
      </div>
      <div class="v-act">
        <h3>Change it</h3>
        <p>Asks first, or previews with <code>--dry-run</code>.</p>
        <ul>
          <li><a href="{{ '/commands/stop' | relative_url }}"><code>stop node</code><span>ask it to exit</span></a></li>
          <li><a href="{{ '/commands/kill' | relative_url }}"><code>kill :3000</code><span>end it now</span></a></li>
          <li><a href="{{ '/commands/free' | relative_url }}"><code>free :3000</code><span>and check it's free</span></a></li>
          <li><a href="{{ '/commands/freeze' | relative_url }}"><code>freeze node</code><span>pause</span></a></li>
          <li><a href="{{ '/commands/thaw' | relative_url }}"><code>thaw node</code><span>resume</span></a></li>
          <li><a href="{{ '/commands/unstick' | relative_url }}"><code>unstick</code><span>recover a stuck process</span></a></li>
        </ul>
      </div>
      <div class="v-wait">
        <h3>Keep watching</h3>
        <p>Live views and waiting.</p>
        <ul>
          <li><a href="{{ '/commands/watch' | relative_url }}"><code>watch node</code><span>live table</span></a></li>
          <li><a href="{{ '/commands/wait' | relative_url }}"><code>wait :3000</code><span>until it exits</span></a></li>
          <li><a href="{{ '/commands/list' | relative_url }}"><code>list --sort cpu</code><span>what's busy</span></a></li>
          <li><a href="{{ '/commands/stuck' | relative_url }}"><code>stuck</code><span>busy and long-running</span></a></li>
          <li><a href="{{ '/commands/orphans' | relative_url }}"><code>orphans</code><span>parent has exited</span></a></li>
        </ul>
      </div>
    </div>
    <p class="more"><a href="{{ '/commands' | relative_url }}">All commands and options</a></p>
  </section>

  <section class="section" aria-labelledby="agents-title">
    <h2 id="agents-title">Made for scripts and coding agents</h2>
    <div class="agents">
      <div>
        <p class="section-lede">Every command prints JSON with <code>--json</code> (one document per command; <code>watch</code> streams one per refresh), and exit codes always mean the same thing. An agent can read proc the way you do, without parsing tables.</p>
        <ul class="prose-list">
          <li><code>0</code> success, <code>1</code> error, <code>2</code> not found, <code>3</code> permission denied, <code>4</code> invalid input</li>
          <li><code>--dry-run</code> shows what would be touched, and nothing else</li>
          <li>The <a href="{{ '/skill' | relative_url }}">agent skill</a> teaches Claude Code, Codex, Cursor, Gemini CLI and others when to reach for proc</li>
        </ul>
      </div>
      <div class="term highlight">
<pre><code><span class="gp">❯</span> proc on :8080 --json | jq '.process | {pid, cwd, uptime_secs}'
{
  <span class="nt">"pid"</span>: <span class="m">177</span>,
  <span class="nt">"cwd"</span>: <span class="s">"/home/dev/code/shop"</span>,
  <span class="nt">"uptime_secs"</span>: <span class="m">35</span>
}</code></pre>
      </div>
    </div>
  </section>

  <section class="section home-end" aria-labelledby="posts-title">
    <h2 id="posts-title">Posts</h2>
    <ul class="post-list">
      {% for post in site.posts limit:4 %}
      <li>
        <a href="{{ post.url | relative_url }}">
          <span class="post-list-title">{{ post.title }}</span>
          <span class="post-list-date">{{ post.date | date: "%B %-d, %Y" }}</span>
        </a>
      </li>
      {% endfor %}
    </ul>
    <p class="more"><a href="{{ '/posts' | relative_url }}">All posts</a></p>
  </section>

</div>