popyapp/corral

See which project each Claude Code, Codex and Cursor process belongs to, how long it has really been idle, and what it is costing you. Native macOS, no permissions required.

4

stars

27

commits

Swift

primary language

Sep 10, 2026

updated

popy.app/apps/corral
ai-agents
claude-code
developer-tools
macos
process-monitor
swift
swiftui

README

Corral icon

Corral

Activity Monitor for Your Local AI Agents

A free, open-source, native macOS app that shows you every Claude Code, Codex and Cursor process on your machine — what project it belongs to, how long it has been sitting there, and how much of your Mac it is holding.

License: MIT macOS 13+ Swift

Why

Open Activity Monitor while you have a few agent sessions going and you get this:

2.1.228    5,8%   6:46:58.81   39 threads
2.1.231    2,0%   1:18:17.04   39 threads
2.1.228    1,5%   1:48:52.33   39 threads
2.1.228    1,5%   1:23:59.63   39 threads
2.1.232    1,2%   1:13:19.57   39 threads

Fifteen identical rows named after a version number. Claude Code installs itself as ~/.local/share/claude/versions/2.1.228, so the process name is the version — and nothing on that screen tells you which of them is the session you abandoned in a repo last Tuesday and which is the one doing your actual work.

So people reach for pkill -9 -f claude, kill everything including the session they were in the middle of, and move on.

Corral shows you the same processes with the one fact that makes them distinguishable — the working directory — plus how long each has really been idle, what it spawned, and what it is costing you.

What it shows

  • The project, not the version. Each agent is identified by its working directory, so the list reads heroshot, recall, appcleaner — not 2.1.228 five times.

  • Honest idle time. Not "quiet since this app opened": Corral reads the last write to each agent's controlling terminal, which the kernel has been stamping all along. An agent you walked away from on Tuesday says idle 3.1d the first second you open the window.

  • The whole footprint. Every agent's children — MCP servers, node helpers, the caffeinate that has been quietly stopping your Mac from sleeping — and the memory they hold together.

  • Where it came from. Executable path, full command line, parent process, controlling terminal, start time.

  • What is safe to reclaim. Agents idle for over an hour, totalled, behind one button. Anything still working — including an agent waiting on a build it started — is never in that total.

  • A menu bar item. Corral keeps running with its window closed, and the top right shows the busiest agent's CPU — or just how many are running when nothing is working hard. Hover for the summary, click for the list, click a row to open the window on that agent.

  • The tool's own icon. Taken from the copy of Claude, Cursor or ChatGPT already installed on your Mac, the same way Finder draws it. Corral ships no brand artwork; a tool you do not have installed falls back to a symbol.

  • Where the numbers have been. One graph in the header, plotting CPU by default. Click a figure — agents, memory, projects, cores — to plot that one instead; click the graph to switch between 15 minutes, 1 hour and 3 hours. "2.92 GB" cannot tell you whether that is the calm after you closed six agents or the start of a climb; the graph can. Point at a column to read what it held and how far back it sits.

    The axis is fixed: now at the right edge, running back the full range in 10-second, 30-second or 1-minute slices. Two minutes of history fills two minutes of a three-hour graph and leaves the rest empty, rather than being stretched across it — and a stretch when Corral was not running stays empty too, because it will not draw across a sleep it did not observe.

  • Light or dark. Corral follows the system, or you can pin it either way from the app menu or the menu bar item — a window you leave open all day is the kind you might want dark on a light desktop.

  • Sorting. By how long it has been running (the default — the thing you forgot about is the thing you came here to find), by memory, by CPU across the whole group, or by project name. Each order has an obvious direction, so picking one applies it; picking the same one again reverses it. Remembered between launches.

  • Search. ⌘F in either pane. An agent matches on its project, path, tool, version, pid, terminal, command line — and on what it spawned, so searching for an MCP server finds the agent running it.

  • What they left on disk. A second tab measures every cache, superseded version and log the tools have accumulated, sorted by how safe it is to remove. On the machine this was written on that came to 14 GB.

Supported: Claude Code, Claude (desktop), Codex, Cursor and its CLI agent, Windsurf.

What the colours mean

Every row carries a dot. There are five states, and each one is a different decision:

StateMeans
🟢workingUsing the CPU, or writing to its terminal right now.
🔵waitingThe agent is parked, but a build, test run or MCP server it started is busy. It is waiting on its own work.
idleNothing for under an hour. Normal between prompts.
🟡idle (amber)Nothing for an hour to a day. Worth a look.
🔴abandonedNothing for over a day. Almost certainly forgotten.

The same legend is in the app, behind the ? next to the search field.

How it is measured, and what that cannot see. Corral watches two things: how much CPU a process has used since the last sample, and the last write to its controlling terminal. It does not watch the network, so an agent waiting on a reply from the model is, strictly, doing neither. In practice that gap stays green: CLI agents animate a thinking indicator while they wait, and that redraw is a terminal write. Output within the last 30 seconds counts as working.

Two honest limits:

  • waiting is inferred from children, not from the agent. A swift build burning a core proves its agent is working; an agent thinking quietly with no children and no output does not look busy, because nothing observable says it is. Before this existed, such a group read as plain idle — and was offered up to the Reclaim button along with the genuinely forgotten ones.
  • An agent with no controlling terminal has only CPU to go on. Its idle time can then only reach back to when Corral opened, which is why those rows say "quiet since Corral opened" rather than "idle", and why they are never bulk-stopped. corral list makes the same distinction in text.

Usage

How much have you got left, and how full is each conversation. Two different questions: an allowance belongs to the account and refills on a clock, a context window belongs to one conversation and is the only one of the two you can do something about right now.

They show in three places — a line on the right edge of the screen that opens into a ring per vendor when you go near it, a Usage tab in the window, and the fullness of each conversation in its own row of the agent list.

Where the figures come from differs by tool, and Corral says so rather than leaving a gap:

AllowanceContext
Codexfrom its own rollout logs, unaskedyes
Claude Codeneeds the status line, belowyes
Cursornot published anywhere on your Macneeds the status line

Codex writes its limits into every session log it keeps, so those are free. The other two record nothing — but both hand their figures to a status line command on every update, and Corral can be that command. Turn it on from the menu bar item, Report Usage to Corral, or from the Usage tab. It asks first, shows the exact setting it will add to ~/.claude/settings.json or ~/.cursor/cli-config.json, keeps a timestamped copy of the file, and refuses to touch a status line you already set up.

What Corral keeps out of what those tools send is the session id, the working directory, the context size and any limit percentages. Not the transcript path, not the branch, not the pull request — the payload describes what you are working on, and none of that is any of its business.

Every figure carries the date the tool wrote it. They are a by-product of the last turn an agent took, so a tool you have not run this week reports a week-old percentage, and one shown bare would read as current.

Still no network. These are files the tools put on your machine.

What it costs to run

A monitor that shows you what is eating your CPU has no business being on that list. A refresh takes ~8 ms across ~550 processes — 0.4% of one core at the two-second refresh rate. Measure it yourself with Corral --bench.

That took work. The first version read every process's argument vector every tick, which allocates a megabyte a time, and cost 17% of a core. The fix is that almost nothing about a process changes: its path, its arguments, its terminal and what tool it belongs to are all fixed at birth, so they are asked once and cached against the pid and its start time. Only memory and CPU are re-read.

On disk

  20 items · 13,97 GB total
  (skipping versions in use: 2.1.227, 2.1.228, 2.1.231, …)

  SAFE TO CLEAR — 1,44 GB
    554,9 MB    Network cache            ~/Library/Application Support/Claude/Cache
    294,7 MB    Superseded versions      ~/.local/share/claude/versions/2.1.229
    …
  WILL BE DOWNLOADED AGAIN — 11,94 GB
    10,89 GB    Local agent VM image     ~/Library/Application Support/Claude/vm_bundles
     1,05 GB    Plugin cache             ~/.claude/plugins/cache
  YOUR DATA — 591,9 MB
    520,6 MB    Conversation history     ~/.claude/projects

Three groups, because "reclaimable" is not one thing:

  • Safe to clear — caches the app rebuilds by itself. Ticked by default.
  • Will be downloaded again — big, and fetched again on demand. Your call.
  • Your data — transcripts, undo history, extensions. Never ticked for you.

A version that is currently running is never offered, whatever its number says. Everything goes to the Trash, never unlink.

Install

Download the latest Corral-<version>.dmg from the releases page, open it and drag Corral to Applications. Every commit on main publishes a build, each one listing its SHA-256 and the exact commit it came from — Corral --version prints that commit back to you, and About Corral shows the same pair as Version 0.1.5 (a1b2c3d), with the hash linking to that commit on GitHub.

A build made locally with make app has no release number, so it takes one from git instead: the last release tag plus how far past it the tree is, as 0.1.5+3. If the tree had uncommitted changes the hash carries a -dirty suffix, and About says the binary matches no commit at all.

Builds are ad-hoc signed rather than notarised, so the first launch needs right-click → Open.

Or build from source (macOS 13+, Xcode command line tools):

git clone https://github.com/popyapp/corral.git
cd corral
make app        # builds build/Corral.app
open build      # then drag Corral.app to /Applications

Or during development:

swift run Corral            # the window
swift run Corral --list     # the same inventory, printed

Terminal mode

Corral --list                  # human-readable
Corral --list --json           # machine-readable
Corral --list --search recall  # only agents matching a project, tool or pid
Corral --disk           # what is on disk (read-only; nothing is deleted)
Corral --bench          # how much a refresh costs
  17 agents · 98 processes · 2,44 GB · 11 projects · 17 idle

  Claude Code 2.1.227  ·  pid 5777
    project   ~/code/heroshot
    up        6.1d    cpu 39.2s    mem 88,5 MB    idle 15.0h
    tty       /dev/ttys016
    children  4 — node (MCP server, pid 5800), node (MCP server, pid 5801), …

Stopping things

  • Quit sends SIGTERM — the agent gets to exit cleanly — then waits and tells you honestly if anything ignored it.
  • Force Quit sends SIGKILL.
  • Either way, children are stopped before the agent. Kill the agent first and its MCP servers get reparented to launchd, where they sit forever with nobody to talk to — which is exactly the mess this app exists to clean up.
  • Corral refuses to signal anything that isn't yours, and never signals launchd or itself.

Privacy and permissions

Corral needs no special permissions — no Full Disk Access, no accessibility, no entitlements. Every fact it shows is already readable by any process running as you: sysctl for the process table and argument vectors, proc_pidinfo for working directories, proc_pid_rusage for CPU and memory.

It reads the argument vector and deliberately stops there — the environment block sits right after it in the same buffer and is full of API keys, so Corral never reads that far.

Nothing leaves your machine. There is no network code in this app.

Tests

swift test

Cursor, Codex and Windsurf have to work on a machine that has never run them, so most of the suite feeds the catalog the exact executable paths those tools produce — including CursorUIViewService, the macOS text-input helper that a naive name match would list as Cursor and offer to kill.

The live tests go further: they compile a small binary named codex, run it in a temp project directory, and assert Corral finds it, names the project, and can stop it. Copying /bin/sleep and renaming it does not work — macOS SIGKILLs an Apple-signed binary running from the wrong place — so the test builds its own.

Building

make build   # swift build
make test    # swift test
make app     # build/Corral.app
make dmg     # build/Corral-<version>.dmg, mounted and verified
make list    # run the CLI against your own machine
make icon    # regenerate the .icns (needs librsvg)
make clean

License

MIT

Contributors

hkulekci

27 commits

popyapp/corral

See which project each Claude Code, Codex and Cursor process belongs to, how long it has really been idle, and what it is costing you. Native macOS, no permissions required.

4

stars

27

commits

Swift

primary language

Sep 10, 2026

updated

popy.app/apps/corral
ai-agents
claude-code
developer-tools
macos
process-monitor
swift
swiftui

README

Corral icon

Corral

Activity Monitor for Your Local AI Agents

A free, open-source, native macOS app that shows you every Claude Code, Codex and Cursor process on your machine — what project it belongs to, how long it has been sitting there, and how much of your Mac it is holding.

License: MIT macOS 13+ Swift

Why

Open Activity Monitor while you have a few agent sessions going and you get this:

2.1.228    5,8%   6:46:58.81   39 threads
2.1.231    2,0%   1:18:17.04   39 threads
2.1.228    1,5%   1:48:52.33   39 threads
2.1.228    1,5%   1:23:59.63   39 threads
2.1.232    1,2%   1:13:19.57   39 threads

Fifteen identical rows named after a version number. Claude Code installs itself as ~/.local/share/claude/versions/2.1.228, so the process name is the version — and nothing on that screen tells you which of them is the session you abandoned in a repo last Tuesday and which is the one doing your actual work.

So people reach for pkill -9 -f claude, kill everything including the session they were in the middle of, and move on.

Corral shows you the same processes with the one fact that makes them distinguishable — the working directory — plus how long each has really been idle, what it spawned, and what it is costing you.

What it shows

  • The project, not the version. Each agent is identified by its working directory, so the list reads heroshot, recall, appcleaner — not 2.1.228 five times.

  • Honest idle time. Not "quiet since this app opened": Corral reads the last write to each agent's controlling terminal, which the kernel has been stamping all along. An agent you walked away from on Tuesday says idle 3.1d the first second you open the window.

  • The whole footprint. Every agent's children — MCP servers, node helpers, the caffeinate that has been quietly stopping your Mac from sleeping — and the memory they hold together.

  • Where it came from. Executable path, full command line, parent process, controlling terminal, start time.

  • What is safe to reclaim. Agents idle for over an hour, totalled, behind one button. Anything still working — including an agent waiting on a build it started — is never in that total.

  • A menu bar item. Corral keeps running with its window closed, and the top right shows the busiest agent's CPU — or just how many are running when nothing is working hard. Hover for the summary, click for the list, click a row to open the window on that agent.

  • The tool's own icon. Taken from the copy of Claude, Cursor or ChatGPT already installed on your Mac, the same way Finder draws it. Corral ships no brand artwork; a tool you do not have installed falls back to a symbol.

  • Where the numbers have been. One graph in the header, plotting CPU by default. Click a figure — agents, memory, projects, cores — to plot that one instead; click the graph to switch between 15 minutes, 1 hour and 3 hours. "2.92 GB" cannot tell you whether that is the calm after you closed six agents or the start of a climb; the graph can. Point at a column to read what it held and how far back it sits.

    The axis is fixed: now at the right edge, running back the full range in 10-second, 30-second or 1-minute slices. Two minutes of history fills two minutes of a three-hour graph and leaves the rest empty, rather than being stretched across it — and a stretch when Corral was not running stays empty too, because it will not draw across a sleep it did not observe.

  • Light or dark. Corral follows the system, or you can pin it either way from the app menu or the menu bar item — a window you leave open all day is the kind you might want dark on a light desktop.

  • Sorting. By how long it has been running (the default — the thing you forgot about is the thing you came here to find), by memory, by CPU across the whole group, or by project name. Each order has an obvious direction, so picking one applies it; picking the same one again reverses it. Remembered between launches.

  • Search. ⌘F in either pane. An agent matches on its project, path, tool, version, pid, terminal, command line — and on what it spawned, so searching for an MCP server finds the agent running it.

  • What they left on disk. A second tab measures every cache, superseded version and log the tools have accumulated, sorted by how safe it is to remove. On the machine this was written on that came to 14 GB.

Supported: Claude Code, Claude (desktop), Codex, Cursor and its CLI agent, Windsurf.

What the colours mean

Every row carries a dot. There are five states, and each one is a different decision:

StateMeans
🟢workingUsing the CPU, or writing to its terminal right now.
🔵waitingThe agent is parked, but a build, test run or MCP server it started is busy. It is waiting on its own work.
idleNothing for under an hour. Normal between prompts.
🟡idle (amber)Nothing for an hour to a day. Worth a look.
🔴abandonedNothing for over a day. Almost certainly forgotten.

The same legend is in the app, behind the ? next to the search field.

How it is measured, and what that cannot see. Corral watches two things: how much CPU a process has used since the last sample, and the last write to its controlling terminal. It does not watch the network, so an agent waiting on a reply from the model is, strictly, doing neither. In practice that gap stays green: CLI agents animate a thinking indicator while they wait, and that redraw is a terminal write. Output within the last 30 seconds counts as working.

Two honest limits:

  • waiting is inferred from children, not from the agent. A swift build burning a core proves its agent is working; an agent thinking quietly with no children and no output does not look busy, because nothing observable says it is. Before this existed, such a group read as plain idle — and was offered up to the Reclaim button along with the genuinely forgotten ones.
  • An agent with no controlling terminal has only CPU to go on. Its idle time can then only reach back to when Corral opened, which is why those rows say "quiet since Corral opened" rather than "idle", and why they are never bulk-stopped. corral list makes the same distinction in text.

Usage

How much have you got left, and how full is each conversation. Two different questions: an allowance belongs to the account and refills on a clock, a context window belongs to one conversation and is the only one of the two you can do something about right now.

They show in three places — a line on the right edge of the screen that opens into a ring per vendor when you go near it, a Usage tab in the window, and the fullness of each conversation in its own row of the agent list.

Where the figures come from differs by tool, and Corral says so rather than leaving a gap:

AllowanceContext
Codexfrom its own rollout logs, unaskedyes
Claude Codeneeds the status line, belowyes
Cursornot published anywhere on your Macneeds the status line

Codex writes its limits into every session log it keeps, so those are free. The other two record nothing — but both hand their figures to a status line command on every update, and Corral can be that command. Turn it on from the menu bar item, Report Usage to Corral, or from the Usage tab. It asks first, shows the exact setting it will add to ~/.claude/settings.json or ~/.cursor/cli-config.json, keeps a timestamped copy of the file, and refuses to touch a status line you already set up.

What Corral keeps out of what those tools send is the session id, the working directory, the context size and any limit percentages. Not the transcript path, not the branch, not the pull request — the payload describes what you are working on, and none of that is any of its business.

Every figure carries the date the tool wrote it. They are a by-product of the last turn an agent took, so a tool you have not run this week reports a week-old percentage, and one shown bare would read as current.

Still no network. These are files the tools put on your machine.

What it costs to run

A monitor that shows you what is eating your CPU has no business being on that list. A refresh takes ~8 ms across ~550 processes — 0.4% of one core at the two-second refresh rate. Measure it yourself with Corral --bench.

That took work. The first version read every process's argument vector every tick, which allocates a megabyte a time, and cost 17% of a core. The fix is that almost nothing about a process changes: its path, its arguments, its terminal and what tool it belongs to are all fixed at birth, so they are asked once and cached against the pid and its start time. Only memory and CPU are re-read.

On disk

  20 items · 13,97 GB total
  (skipping versions in use: 2.1.227, 2.1.228, 2.1.231, …)

  SAFE TO CLEAR — 1,44 GB
    554,9 MB    Network cache            ~/Library/Application Support/Claude/Cache
    294,7 MB    Superseded versions      ~/.local/share/claude/versions/2.1.229
    …
  WILL BE DOWNLOADED AGAIN — 11,94 GB
    10,89 GB    Local agent VM image     ~/Library/Application Support/Claude/vm_bundles
     1,05 GB    Plugin cache             ~/.claude/plugins/cache
  YOUR DATA — 591,9 MB
    520,6 MB    Conversation history     ~/.claude/projects

Three groups, because "reclaimable" is not one thing:

  • Safe to clear — caches the app rebuilds by itself. Ticked by default.
  • Will be downloaded again — big, and fetched again on demand. Your call.
  • Your data — transcripts, undo history, extensions. Never ticked for you.

A version that is currently running is never offered, whatever its number says. Everything goes to the Trash, never unlink.

Install

Download the latest Corral-<version>.dmg from the releases page, open it and drag Corral to Applications. Every commit on main publishes a build, each one listing its SHA-256 and the exact commit it came from — Corral --version prints that commit back to you, and About Corral shows the same pair as Version 0.1.5 (a1b2c3d), with the hash linking to that commit on GitHub.

A build made locally with make app has no release number, so it takes one from git instead: the last release tag plus how far past it the tree is, as 0.1.5+3. If the tree had uncommitted changes the hash carries a -dirty suffix, and About says the binary matches no commit at all.

Builds are ad-hoc signed rather than notarised, so the first launch needs right-click → Open.

Or build from source (macOS 13+, Xcode command line tools):

git clone https://github.com/popyapp/corral.git
cd corral
make app        # builds build/Corral.app
open build      # then drag Corral.app to /Applications

Or during development:

swift run Corral            # the window
swift run Corral --list     # the same inventory, printed

Terminal mode

Corral --list                  # human-readable
Corral --list --json           # machine-readable
Corral --list --search recall  # only agents matching a project, tool or pid
Corral --disk           # what is on disk (read-only; nothing is deleted)
Corral --bench          # how much a refresh costs
  17 agents · 98 processes · 2,44 GB · 11 projects · 17 idle

  Claude Code 2.1.227  ·  pid 5777
    project   ~/code/heroshot
    up        6.1d    cpu 39.2s    mem 88,5 MB    idle 15.0h
    tty       /dev/ttys016
    children  4 — node (MCP server, pid 5800), node (MCP server, pid 5801), …

Stopping things

  • Quit sends SIGTERM — the agent gets to exit cleanly — then waits and tells you honestly if anything ignored it.
  • Force Quit sends SIGKILL.
  • Either way, children are stopped before the agent. Kill the agent first and its MCP servers get reparented to launchd, where they sit forever with nobody to talk to — which is exactly the mess this app exists to clean up.
  • Corral refuses to signal anything that isn't yours, and never signals launchd or itself.

Privacy and permissions

Corral needs no special permissions — no Full Disk Access, no accessibility, no entitlements. Every fact it shows is already readable by any process running as you: sysctl for the process table and argument vectors, proc_pidinfo for working directories, proc_pid_rusage for CPU and memory.

It reads the argument vector and deliberately stops there — the environment block sits right after it in the same buffer and is full of API keys, so Corral never reads that far.

Nothing leaves your machine. There is no network code in this app.

Tests

swift test

Cursor, Codex and Windsurf have to work on a machine that has never run them, so most of the suite feeds the catalog the exact executable paths those tools produce — including CursorUIViewService, the macOS text-input helper that a naive name match would list as Cursor and offer to kill.

The live tests go further: they compile a small binary named codex, run it in a temp project directory, and assert Corral finds it, names the project, and can stop it. Copying /bin/sleep and renaming it does not work — macOS SIGKILLs an Apple-signed binary running from the wrong place — so the test builds its own.

Building

make build   # swift build
make test    # swift test
make app     # build/Corral.app
make dmg     # build/Corral-<version>.dmg, mounted and verified
make list    # run the CLI against your own machine
make icon    # regenerate the .icns (needs librsvg)
make clean

License

MIT

Contributors

hkulekci

27 commits

Languages

Swift

98.6%

Shell

1.4%