cctop 0.13.0

An htop-like terminal monitor for AI coding agent sessions on Linux (Claude Code, Codex, Cursor, Gemini CLI, OpenCode, Pi, Windsurf)
# `cctop optimize` and `cctop compare`

[← back to the README](../README.md)

The table says what a session cost. These two say what it cost you *for*.

```bash
cctop optimize   # what was spent and not got back
cctop compare    # how each model did on the work you gave it
```

Both are also `o` and `c` in the TUI, drawn over the table, and both take
`--json` and `--provider <name>`.

Neither writes anything — not to your configuration, not anywhere. They read
transcripts and print.

## Why they are slower than everything else

They re-read every transcript. The individual tool calls, with their arguments,
are the thing both commands reason about, and those are
[never cached](providers/README.md) — at roughly 31 KB a session they were 83%
of a cache that had to be read in full before the first frame. So the table gets
a cache that stays small and these get a full parse, which takes a second or two
on a large machine.

## What `optimize` looks for

Findings come in three classes, and the class matters more than the wording:

| | |
|---|---|
| **fix** | Something to go and do — a setting, a deny rule |
| **habit** | Only you can change it |
| **note** | Worth knowing. Not a criticism |

Each carries what it cost, and whether that figure was **measured** — counted
from tokens the transcript recorded — or **estimated** from this machine's own
averages. The distinction is not decoration. A measured saving is one you can
check; an estimated one is an argument.

What it currently detects:

- Reads into generated or vendored directories — `node_modules`, `.git`,
  build output
- The same file read twice inside one session, which is usually a compaction
  that took it out of the window
- One file read from scratch by five or more separate sessions — a piece of
  context the agent needs every time and is told nowhere
- Sessions that read ten times more than they edited, excluding the ones whose
  job was to explore
- Tool calls that failed and were billed anyway
- Sessions that spent real money and changed no file

Underneath, where the money went by kind of work: coding, debugging, testing,
exploration, planning, delegation, git, build, conversation.

**A finding has to be worth more than the time it takes to act on.** The report
once opened with a `fix` worth $0.0071, ranked above a habit worth a dollar, and
both figures were right. Neither was worth having: *a dollar is like a few
minutes of my time*, and a fix is several minutes — read the row, find the
settings file, edit it, check it did something. A list whose first row is a
losing trade is a list people stop reading.

So a finding must clear **$5, or 1% of what these sessions spent, whichever is
larger**. The share matters because the absolute floor alone misjudges scale:
$5 back is worth having on a corpus that spent $40 and invisible on one that
spent $5,000, and only the second kind of user is drowning in findings.

What falls below the bar is not silently dropped — it gets one line saying how
many findings there were and what they came to, because a report that detected
four things and printed none of them is indistinguishable from a broken
detector. `--json` carries the same thing as `floor_usd` and `below_floor`, so
a script can reach past the bar without reimplementing the detectors.

A finding with no price on it is kept. Unpriced is not the same as small, and
filtering on the number alone would drop the findings cctop knows least about
while keeping the ones it has measured as trivial. When those are all that is
left, the headline says so rather than claiming `$0.00 looks recoverable`.

**The headline only counts what could actually be recovered.** A `note` records
what a set of sessions *spent*, which is an observation and not a saving — an
earlier version added them together and advertised $218 of ordinary work as
though it were waste.

## What `compare` measures

Per model, and then per model per kind of work:

| | |
|---|---|
| **1-shot** | Share of files that took one contiguous attempt |
| **$/file** | Cost per file actually changed |
| **$/call** | Cost per tool call |
| **cache** | Share of input that came from the cache |

The one worth understanding is **1-shot**, because of how a retry is counted.
Editing a file, going away to run something, and editing that same file again is
a retry. Editing a *different* file is progress, not a retry. That makes it a
sharper signal than `ERR%`: a failed call is noise, an agent editing one file
four times is a story.

### It is observational, and it says so

You did not give two models the same work. You gave the expensive one the
problems you expected to be hard. A table that ignores that reports the
expensive model as worse while measuring nothing but your own routing.

Nothing can fix that from a transcript, so two things make it visible instead:
the caveat is printed under every table, and the same figures are broken out per
kind of work — most of "this model is worse" turns out to be "this model was
given the debugging".

A session that used several models is credited entirely to whichever cost the
most. The transcript records which model billed a request, not which model asked
for a given tool call.

## What counts as editing

An edit tool — `Edit`, `Write`, Codex's `apply_patch` — is the obvious case. The
less obvious one is a shell command, and missing it was wrong in a way worth
recording: a session driven in "do the work through Bash" mode edits with
`sed -i`, a heredoc and a redirect and never touches an edit tool at all. cctop
counted no edits, filed those sessions as Testing, and then reported them as
having spent money and changed nothing.

So a shell command is read for a write: an in-place editor, `tee`, a copy or a
move, or a redirect whose target names a path rather than a descriptor —
`2>&1`, `>&2` and `/dev/null` being the three that appear constantly and write
nothing worth counting.

This undercounts, deliberately. A `python3 - <<PY` whose script calls
`open(p, "w")` writes a file that the command line cannot reveal. A missed write
leaves a session looking quieter than it was; a false one would accuse somebody
of editing a file they only read, and that is the worse mistake.

One consequence to know about: a shell write carries no file name, so it counts
toward *whether* a session changed anything but not toward the one-shot rate,
which needs a path. `1-shot` and `$/file` therefore describe edit-tool work
only.

## Where the numbers are floors

The per-session tool history is capped, so a session that made more calls than
the cap has its oldest ones dropped. Any count derived from it is therefore a
floor, and a finding built on a capped session says so on its own line rather
than quietly under-reporting.

## What is deliberately missing

**Applying fixes.** `optimize` tells you what to change and does not change it.
Writing to somebody's `~/.claude/` is a different kind of commitment from
reading it, and it should not arrive in the same release as the detectors that
decide what to write. The findings come first; automating the ones that turn out
to be right can follow.

**Config scanning.** Nothing yet reads `CLAUDE.md`, MCP server definitions, or
agent and skill files to find the ones that are never used. Those detectors need
a different source of truth from the transcripts and are the obvious next step.