内容
# headroom-usage-indicator
A tiny **status-line indicator for Claude Code** that shows — at a glance, at the bottom of your screen — whether the **headroom** context-compression MCP is actually being used in your current session, and how many tokens it has saved you.
No more wondering *"did I remember to compress that huge file, or did I just burn context?"* — the indicator tells you, honestly, in real time.
> ### ⚠️ Updating from v2.7.4 or earlier? Run the doctor once.
>
> ```
> /headroom-usage-indicator:doctor --fix
> ```
>
> v2.8 spawns the bundled MCP by the **bare name** `headroom` — that change is what makes Windows work at all, and it removed the old launcher's `~/.headroom-venv` fallback. If an earlier doctor bootstrapped your engine into `~/.headroom-venv` (the default path for every release up to v2.7.4), it is **not** on your `PATH`, so the MCP stops connecting until `--fix` shims it into `~/.local/bin`.
>
> **The failure is quiet.** A dead MCP renders as an *idle* badge, which looks identical to "nothing compressed yet". SessionStart nudges you, and one command fixes it — but if your badge never leaves idle after updating, this is why.
---
## Quickstart (two commands and the doctor)
Type these into the Claude Code prompt:
```
/plugin marketplace add Abhi902/headroom-plugin
/plugin install headroom-usage-indicator@headroom-tools
```
Then ask Claude, in plain English: **"run the headroom doctor"** (or invoke it directly: `/headroom-usage-indicator:doctor`). It checks everything — `jq`, the headroom compression engine, the MCP server, the hooks, the status line — and, with your consent, fixes whatever is missing, including the one thing a plugin can't do by itself: writing the `statusLine` entry into your `~/.claude/settings.json`.
That's it. As of v2.5 the plugin is self-contained: the hooks (Dangi and the hcat gate) register themselves the moment the plugin is enabled, `hcat` is already on Claude's Bash PATH, and the headroom MCP server registration comes bundled — no scripts to copy, no `settings.json` hook surgery. If you installed an earlier version by hand, see [Migrating from a pre-v2.5 install](#migrating-from-a-pre-v25-manual-install).
## Verify it works
Paste this into the Claude Code prompt:
> Create a roughly 100 KB JSON file at /tmp/hr-demo.json (an array of a few thousand small objects), then show me what's in it.
You should see, in order:
1. the **hcat gate** step in — instead of raw-reading the file, Claude gets redirected to run `hcat /tmp/hr-demo.json`;
2. the output open with a receipt line like `── hcat: /tmp/hr-demo.json · 1 lines · 98.3 KB · ~25000 tok → ~7500 tok (70.0% saved) · original on disk …`;
3. the badge at the bottom of your screen flip green within a second or two: `● headroom · ~17.5k tok · $… · 1× …`.
If any of those three don't happen, ask Claude to **run the headroom doctor** — diagnosing exactly this is its whole job.

*The badge across its three real states — red idle (big files read raw), green active (an hcat receipt just landed: tokens and $ saved), grey decayed (quiet 60s, keeps the tally and flags what was missed).*
---
## The gauge and the engine
This plugin does **not** compress anything by itself — the actual compression is done by **headroom**, a local Python engine (→ https://github.com/headroomlabs-ai/headroom). Think of it like a fuel gauge: **headroom is the engine, this plugin is the gauge** (plus, since v2.3, a hand that reaches for the fuel-saver button for you).
The good news: you no longer have to plumb the engine in yourself. The plugin bundles the MCP server registration (its tools appear as `mcp__plugin_headroom-usage-indicator_headroom__headroom_compress` and friends; a server you registered yourself shows up as `mcp__headroom__headroom_compress` — either works, and the badge counts both), and if the engine itself is missing, the **doctor** offers to bootstrap it into `~/.headroom-venv`. If headroom is absent and you decline, everything stays politely silent — the badge sits at "idle", the gate lets Reads through — nothing breaks.
> **v2.7.3 — the bundled MCP server actually connects now.** From v2.5 through v2.7.2 the bundled registration was shipped broken: its launcher path carried literal quotes, and because Claude Code spawns an MCP `stdio` command *directly* (no shell ever unwraps quoting), every connection attempt died instantly and `/plugin` showed a ✗ beside headroom. It went unnoticed because anyone with a pre-plugin manual registration in `~/.claude.json` still had the tools from there. If you're upgrading, run **`/headroom-usage-indicator:doctor --fix`** once — it repairs an already-installed broken copy in place (with a backup), and from that release on it judged that launcher *exactly as spawned* rather than being fooled by its own quote-stripping (v2.8 removed the launcher and its quote-stripping altogether — the bundled MCP now spawns the bare `headroom` name).
## What it does
Every second, it looks at what your Claude session has actually done and updates a small badge in your status line. It reads the real session activity (not guesses), so it can't be fooled — just *looking* at headroom's stats does **not** make it say "active"; only a real compression does.
## What it shows
| Badge | Colour | Meaning |
|---|---|---|
| `○ headroom idle (not compressing yet)` — or `○ headroom idle · 4 big blobs uncompressed` | 🔴 red | headroom hasn't compressed anything yet this session; the count appears when large tool outputs are going uncompressed |
| `● headroom · ~2.4k tok · $0.01 · 3× \| $1.83 all-time 😴 dangi` | 🟢 green | a compression just happened — tokens saved, **money saved**, how many times, and your all-time total |
| `○ headroom idle · ~2.4k tok · $0.01 · 3× · 2 missed \| $1.83 all-time 🤖 dangi: 2!` | ⚪ grey | quiet for 60s — dims, but keeps the totals; `· N missed` counts big results beyond what you've compressed |
| `▲ headroom broken (engine) · run /doctor` | 🟡 yellow | a hook or `hcat` just recorded a real engine failure — takes over the badge until a clean `/doctor` run (or the next successful `hcat`) clears it (v2.7) |
- The **token count is the running total for the whole session** (it adds up every compression).
- It **resets to red** when you start a brand-new Claude session.
- **v2.7:** the badge is no longer just idle/active — it also notices when it's *broken*. See [Ambient health](#ambient-health-the-badge-notices-when-its-broken-v27) below.
## How the money number works
The badge prices the tokens headroom saved at the **input rate of the model your session is running** (e.g. $5/MTok on Opus, $10/MTok on Fable). The price table is **data, not code** (v2.6): it lives in `data/model-prices.json` (matched by model-id substring, first match wins), so adding a model is a one-line edit shipped with the plugin — no script change. If the model isn't in the table, the badge just shows tokens — it never guesses a dollar figure. `all-time` is the sum across all your sessions on this machine (stored in `~/.claude/headroom-indicator/`).
This is a deliberately **conservative floor**: compressed content would otherwise re-enter the context on every later API turn (mostly at the cheaper cache-read rate), so the true savings compound above the number shown.
## What counts as a "missed opportunity"
Any tool result of 4 KB or more that wasn't produced by headroom itself. Each compression you run forgives one big blob (compressing doesn't remove the original from the transcript, so a plain count would nag you about blobs you already handled). It's a size-only heuristic — a big code file you're editing may be a deliberate non-compression; treat the number as a nudge, not an accusation.
## Meet Dangi 🤖
Dangi is the plugin's real-time detector. The badge tells you what you missed; Dangi catches it **as it happens**:
- the moment a tool spits out ≥ 4 KB that isn't compressed, Dangi whispers to Claude (an in-context nudge, max once a minute) so it can compress right away — and when it can tell which file the output came from (a `Read`'s `file_path`, or a `cat`/`head` of a `.json/.csv/.log/…` in Bash), the nudge **names that file** so the fix is copy-paste (`hcat "<that file>"`), v2.6, now file-aware on the `Read` path too (v2.7);
- **v2.7 — true size, not payload size:** the platform truncates the hook's own payload to ~10K chars before Dangi ever sees it, so for **whole-file ingests** — a bare `cat`/`hcat` of the file, or a `Read` with no offset/limit — Dangi now `stat`s the file **on disk** and reports/tiers on `max(payload size, file size)` instead of the truncated figure. A file merely *named* in a filter command (`grep ERROR big.log`) or read bounded keeps the payload size — that output really was small. The *trigger* still fires on payload size only — a file that's written but never read still doesn't nudge;
- **v2.7 — size-tiered advice:** below `DANGI_HUGE_BYTES` (default 131072 bytes / 128 KiB) the nudge is the usual hcat/MCP-compress suggestion; at or above it, Dangi advises **delegation instead of compression** — spawn a disposable subagent to read/analyze the file and return only conclusions or an hcat-compressed digest, since compressing that much content in place would still flood the window;
- because the nudge is rate-limited, blobs that slip by while Dangi is quiet aren't lost — the next nudge **says how many were missed** in the gap, so batching never hides the backlog (v2.6);
- **v2.7 — it remembers:** a nudge on a file-backed blob ≥ 4 KB that actually *looks structured* (innate extension, or the same 512-byte sniff the gate uses — a big source file can't get itself compression-gated) also records that file as a learned "offender" (see [Detection that learns](#detection-that-learns-v27) below), so the hcat gate can catch it again later even off the static extension list;
- if it keeps happening, you get a desktop notification — via `osascript` on macOS, falling back to `notify-send` on Linux (max once per 5 minutes);
- and he lives at the end of your status line: `😴 dangi` when all is well, `🤖 dangi: 3!` when compression chances are slipping by.
Dangi ships as a plugin hook — registered automatically while the plugin is enabled, gone when it isn't. Set `DANGI_NO_NOTIFY=1` to silence the notifications. Dangi knows what not to nag about: edit tools (`Edit`/`Write`/`MultiEdit`/`NotebookEdit` echo the code you're changing), web results (`WebFetch`/`WebSearch` return prose), image-bearing outputs (base64, not text-compressible), headroom's own tools, and the output of a genuine `hcat` run (that *is* the compression).
## hcat: stop the tokens *before* they're spent (v2.3) 🚰
The badge and Dangi are honest, but they share a limit: by the time Claude *could* call `headroom_compress`, the big output is already in context — those tokens are spent, and re-sending the blob to the compressor costs output tokens on top. `headroom_compress` genuinely pays off inside subagents (compress before returning), but in the main session it's mostly consolation.
v2.3 adds the **prevention layer**:
- **`hcat <file>`** (shipped in the plugin's `bin/`, on Claude's Bash PATH while the plugin is enabled) compresses a structured file through headroom's local pipeline **before it ever enters context** — you get a compact schema+rows rendering (typically 70 %+ token reduction on JSON) plus a header citing the original path. Need an exact detail later? `Read` the original with an offset/limit — the file on disk is the source of truth. Savings are reported into `headroom_stats`. The compression run is bounded: if the engine does not finish within `HCAT_EXEC_TIMEOUT` seconds (default 90, under the Bash tool's own 120 s limit) hcat stops it, exits 4, points you at the original on disk and marks the badge broken.
- **The hcat gate** (a plugin PreToolUse hook) catches Claude *about to* raw-read a big (≥ 16 KB) structured file. For a `Read`, it still **denies once per file per session** with the exact `hcat` command to run instead; re-Reading the same file passes, so it's a redirect, never a wall. For a bare, single-line `cat <file>` in Bash, **v2.7 rewrites the command in place** (`cat <file>` → `hcat "<file>"`, via `updatedInput`, one shot) instead of denying it — the hcat receipt in the output makes the substitution visible, an `additionalContext` line tells Claude its command was rewritten, and there's no deny→re-plan→retry round trip. (Multiline commands are never touched — rewriting one line would silently drop the others.) Set `HCAT_GATE_NO_REWRITE=1` to restore the old deny-and-suggest behavior for Bash. If headroom isn't installed the gate stays silent. Kill switch: `HCAT_GATE_OFF=1`.
Both ship with the plugin — there is nothing to copy or register.
**v2.4:** the badge finally *sees* hcat. Every `hcat` run leaves a receipt in the transcript (`── hcat: … ~18899 tok → ~9351 tok (50.5% saved)`); the status line now parses those receipts and folds them into the token count, the dollar figure, the `N×` counter, the freshness dot, and the all-time total — passthrough receipts (files hcat couldn't shrink) count as nothing, and a big genuine receipt is never a "missed" blob (it *is* the compression). Before v2.4 the badge only counted `headroom_compress` MCP calls, so a session that saved everything via hcat still read "idle (not compressing yet)". **v2.4.1:** Dangi recognizes receipts too. **v2.5:** receipt attribution is *structural* on both paths — a receipt only counts when the tool result actually came from a Bash command that invoked `hcat`; an output that merely quotes a receipt line (a `grep` over docs, a `cat` of this README) counts as nothing — and, if big, as a missed opportunity.
**v2.7 — hcat works even without the Python engine.** When no engine is found, `hcat` no longer just refuses: it renders a lossless **TOON-lite** table via pure `jq` — a uniform JSON array of same-shaped objects becomes one header row plus CSV-like rows, typically 30–60% smaller, zero extra dependencies — before giving up. The receipt reads `… (NN.N% saved · toon-lite lossless, engine absent) …` and the token figures are ~4-bytes/token estimates (there's no engine tokenizer to ask). Files that aren't a uniform array of flat objects still exit 3 with the old "headroom python not found" message. When the engine *is* installed but its semantic compressor would save less than 5% on a given file, the same TOON-lite reformat is tried in Python before falling back to raw passthrough — receipt `… (NN.N% saved · toon-lite lossless) …`, reported into `headroom_stats` with `strategy:"toon-lite"`. Either way it's only used when it actually saves ≥ 5%; otherwise passthrough stands as before.
## Detection that learns (v2.7)
The hcat gate's static extension list (`.json/.jsonl/.ndjson/.csv/.tsv/.log`) misses extensionless API dumps and mislabeled `.txt` JSON. Two things close that gap without widening the list:
- **Offender memory** — every time Dangi nudges on a file-backed blob whose file is ≥ 4 KB, it appends `<epoch> <path>` to `$STATE_DIR/offenders` (deduped by path, entries older than `HEADROOM_OFFENDER_TTL` seconds — default `1209600`, 14 days — pruned on every write). The hcat gate treats a fresh offender entry as gate-eligible regardless of extension, so a file that burned context once gets caught on its next `Read`/`cat`. The list is plain text — inspect or delete `$STATE_DIR/offenders` to see or reset what's been learned.
- **Structural sniff** — failing both the extension list and the offender list, the gate reads the first 512 bytes of an eligible-sized file: if the first non-space character is `{`/`[`, or the first two lines carry the same ≥3 comma/tab count, it's treated as structured anyway. Disable with `HCAT_GATE_NO_SNIFF=1`.
## Ambient health: the badge notices when it's broken (v2.7)
Every layer above fails *silently* by design — a hook that prints anything but its one JSON decision breaks every tool call. That's normally fine (headroom missing = the gate stays quiet, hcat missing = passthrough), but it means a **real** breakage — a half-created venv, a broken `HCAT_PYTHON`, a compression that raised — used to look exactly like ordinary idle. v2.7 makes real failures visible:
- Hooks and `hcat` record genuine engine failures (not "never installed" — "resolved and then broken") as `<epoch> <component> <message>` in `$STATE_DIR/last-error`.
- A fresh entry (under 24h old) takes over the badge: `▲ headroom broken (<component>) · run /doctor`, in yellow, replacing the usual active/idle state.
- `hcat` clears its own `engine`/`runtime` errors the instant a compression actually succeeds; `/doctor` clears the file on a fully clean run and reports "cleared recorded failure state — badge restored".
- A new **SessionStart** hook, `scripts/session-probe.sh`, runs a fast subset of the doctor's checks once per session — `jq` present, `hcat` executable, engine python resolvable (existence only; the import itself is checked at use time by the gate/hcat), the bundled price table parses — and stays silent when everything's fine. A genuinely never-installed engine still gets a friendly one-line pointer to `/doctor --fix`; that alone does **not** flip the badge to broken (it's the ordinary idle state, not a breakage).
- **v2.7.2 — setup nudge:** when everything else is healthy but the **status line isn't wired yet** (the one manual step — a plugin can't register a status line itself), the probe emits a one-line `🤖 headroom setup: … run /headroom-usage-indicator:doctor --fix to show it`. This is why a fresh install no longer leaves you wondering where the badge is — it tells you the remaining step. It's a reminder, not a breakage (no `last-error`, no yellow badge), it defers to any real problem, and it goes silent the moment the status line is wired. The same nudge fires if the status line is wired but its `lib/` deps are missing (the [#2](https://github.com/Abhi902/headroom-plugin/issues/2) shape — badge would read zero).
## Session ledger + next-session invoice (v2.7)
A **Stop/SessionEnd** hook, `scripts/ledger-hook.sh`, walks the transcript with the same structural attribution as the badge and appends one cumulative snapshot per session to `$STATE_DIR/ledger.jsonl`: tokens/dollars saved, plus the big outputs that went uncompressed — count, size, an estimated token cost, and the biggest offenders' paths. A per-session checksum marker keeps a busy session's many `Stop` firings from spamming the file, and a session that neither saved nor missed anything writes nothing.
At the **next** session's start, `session-probe.sh` surfaces the last ledger line once (never twice, even across many sessions) as an invoice:
> 🤖 headroom invoice: last session: saved ~12.4k tok (~$0.06) · 3 big output(s) went uncompressed (~8.2k tok ≈ $0.04 left on the table — biggest: /tmp/report.json)
---
## What installing actually sets up
For the curious — after the Quickstart, here is where everything lives:
| Piece | Where | How it got there |
|---|---|---|
| Dangi, the hcat gate, session-probe, the session ledger | `hooks/hooks.json` inside the plugin | auto-registered while the plugin is enabled (SessionStart, PreToolUse, PostToolUse, Stop, SessionEnd) |
| `hcat` | `bin/hcat` inside the plugin | on Claude's Bash PATH automatically |
| headroom MCP registration | `.mcp.json` inside the plugin | bundled; spawns `headroom mcp serve` by name — the doctor makes sure `headroom` is on PATH |
| headroom engine (Python) | `~/.headroom-venv` (or your own install: pip, pipx, uv) | the doctor bootstraps it with your consent and shims `headroom` into `~/.local/bin` |
| status line | `statusLine` in `~/.claude/settings.json`, pointing at a copy of `scripts/statusline.sh` at `~/.claude/headroom-statusline.sh` (with its `attribution.jq` + `headroom-state.sh` deps in `~/.claude/lib/`) | **the one manual step** — the doctor writes it for you (merge-aware: an existing custom status line is kept and backed up under `_headroomStatusLineBackup`), and provisions the `lib/` deps the badge needs to count savings (v2.7.1) |
If you'd rather wire the status line by hand, the merge-aware installer lives in `skills/headroom-usage-indicator/SKILL.md`; the standalone entry it writes boils down to (with your real home directory in place of `/Users/you` — a `~` inside the quoted path would never be expanded):
```json
"statusLine": { "type": "command", "command": "bash \"/Users/you/.claude/headroom-statusline.sh\"", "refreshInterval": 1 }
```
If the badge doesn't appear at the bottom right away, type `/statusline` once to refresh — or it'll be there next session.
> **If you hand-wired this before v2.7.3, check your quoting.** Earlier versions of this README showed the path as `bash "~/.claude/headroom-statusline.sh"` — but no shell expands a `~` inside quotes, so that entry could never resolve and the badge silently never rendered. v2.7.3's doctor detects that (in either quote style), reports it, and `--fix` rewrites the entry to an absolute path. An *unquoted* `bash ~/.claude/...` was always fine and is left alone.
## Updating
New versions arrive through the plugin marketplace:
```
/plugin marketplace update headroom-tools
/plugin update headroom-usage-indicator@headroom-tools
```
The hooks, `hcat`, and the MCP definition update with the plugin — nothing to re-copy. The one exception is the status-line script, which runs from a copy at `~/.claude/headroom-statusline.sh` (plus its `~/.claude/lib/` deps): if a release changes it, ask Claude to run the doctor once and it refreshes the copy and the deps. **Coming from v2.7.0 or earlier, run `/headroom-usage-indicator:doctor --fix` once after updating** — earlier installs never provisioned the badge's `lib/` deps, so it was stuck reporting zero savings until you do ([#2](https://github.com/Abhi902/headroom-plugin/issues/2), fixed in v2.7.1). Legacy (pre-v2.5) manual installs get none of this for free — every update means re-running the installer, which is one more reason to migrate.
**Coming from v2.7.2 or earlier, run `/headroom-usage-indicator:doctor --fix` once too** — v2.7.3 fixes the bundled MCP registration that never connected (see [above](#the-gauge-and-the-engine)), and `--fix` repairs the already-installed broken copy rather than waiting for the next fresh install.
**Coming from v2.7.4 or earlier, run `/headroom-usage-indicator:doctor --fix` once more** — v2.8 spawns the bundled MCP by its bare name, so you need headroom on PATH. If the doctor bootstrapped your engine into `~/.headroom-venv`, `--fix` shims it into `~/.local/bin`; if that directory isn't on PATH, it also registers the engine as a user-scoped MCP by absolute path (`claude mcp add -s user headroom ...`), so the MCP works without a shell-rc edit. The bundled bare-name entry stays unconnected until `headroom` is on PATH; the doctor prints the one rc line for that too.
## Windows
Works under **Git for Windows (Git Bash)** — Claude Code runs its hooks and the status line through it, so it is a hard prerequisite (PowerShell-only setups are not supported). Then:
1. `winget install jqlang.jq` and a Python 3.10+ (`winget install Python.Python.3.12`, or `uv`).
2. Install the plugin as in the Quickstart and run `/headroom-usage-indicator:doctor --fix`. The doctor finds a venv in the `Scripts\` layout, `uv tool install headroom-ai` trampolines and pip's `headroom.exe` launchers, bootstraps a venv with `py -3`/`python` if nothing is installed, shims `headroom.exe` into `%USERPROFILE%\.local\bin`, and writes the status line as `"C:\…\Git\bin\bash.exe" "C:\Users\you\.claude\headroom-statusline.sh"`.
3. **Expect `--fix` to end in a red `FAIL` the first time, and treat it as the last install step rather than an error.** On a stock Windows box `%USERPROFILE%\.local\bin` is not on **Path**, so the doctor shims `headroom.exe` there and then tells you so (printing the Git Bash spelling of the path, forward slashes and all). Add that directory to your user **Path** (Settings → System → About → Advanced system settings → Environment Variables) and restart Claude Code — the MCP cannot connect until you do. The doctor deliberately does not edit your registry for you. Installing the engine with `uv tool install headroom-ai` or `pipx` instead puts `headroom` on PATH for you and skips this step entirely.
One Windows caveat worth knowing: the bundled MCP is spawned by the **bare name** `headroom`, and Windows resolves a bare command name from the spawning process's **project directory before PATH**. So a `headroom.exe` committed to a repository you open would run instead of your installed engine, and detection alone cannot stop it, because nothing runs before the MCP is spawned. `/headroom-usage-indicator:doctor --fix` also **registers the engine by its absolute path** (`claude mcp add -s user`), so you always have one headroom server that cannot be hijacked this way. **It does not remove the exposure:** Claude Code keeps the plugin's bundled server under its own name, so the bare-name entry is still spawned next to it. The doctor and the session probe flag such a file if they find one in the project; remove or rename it.
**Windows on ARM:** `headroom-ai` ships compiled `abi3` wheels and publishes none for `win_arm64`, so an ARM64 interpreter matches nothing, falls back to the source distribution, and wants a full build toolchain. The doctor detects this by asking the interpreter for its own wheel tag (`uname` is no help — Git for Windows is an x86_64 build and reports x86_64 even on an ARM64 host) and tells you the real remedy: install the **x64** build of Python, which runs emulated and matches the `win_amd64` wheel.
`hcat` output is UTF-8 on every platform (`PYTHONIOENCODING=utf-8`), and piping it into `head` exits quietly instead of printing a `BrokenPipeError` traceback — Windows has no `SIGPIPE`, so that path is handled explicitly rather than by the signal default. Desktop notifications from Dangi are macOS/Linux only for now.
CI runs the full suite on `ubuntu-latest`, `macos-latest` **and** `windows-latest`, with a real engine installed on all three, plus a Windows-only pass that exercises the resolver against genuine venv/`uv tool`/pip layouts, `doctor --fix` end to end including idempotency, a shell-less MCP spawn by bare name with a negative control, and the status line rendered from PowerShell through the command the doctor actually wrote. Windows failures are ratcheted by name against [`.github/windows-known-failures.txt`](.github/windows-known-failures.txt) — an unlisted failure reds the build, and a listed fixture that starts passing also reds the build until its line is deleted, so the list can only shrink. What CI still cannot run is Claude Code itself; see [#9](https://github.com/Abhi902/headroom-plugin/issues/9).
## Uninstall
Leaving should be as easy as arriving:
```
/plugin uninstall headroom-usage-indicator@headroom-tools
```
That removes the hooks, `hcat`, and the bundled MCP in one go. Then tidy what lives outside the plugin:
0. if `/headroom-usage-indicator:doctor --fix` ever registered the engine by absolute path (Windows, or macOS/Linux with `headroom` off PATH), remove that user-scoped server too: `claude mcp remove headroom -s user` (`claude mcp get headroom` shows whether one exists);
1. remove the `"statusLine"` block from `~/.claude/settings.json` (or ask Claude to *"remove the headroom status line"* — if you had a custom status line before, restore it from `_headroomStatusLineBackup`);
2. delete the state and the script copy:
```bash
rm -f ~/.claude/headroom-statusline.sh
rm -rf ~/.claude/headroom-indicator
```
If you ever did a pre-v2.5 manual install, also remove the old copies and their `settings.json` hook entries — see the migration note below. The headroom engine itself (`~/.headroom-venv`, if the doctor created it) is yours to keep or `rm -rf` as you please.
## Migrating from a pre-v2.5 manual install
Before v2.5, the installer copied scripts into `~/.claude/` and registered hooks directly in your `settings.json`. If those leftovers are still present alongside the plugin, **the hooks double-fire** (two Dangis, both polite, still one too many). Ask the doctor to clean up — with your consent it removes:
- the `hooks.PostToolUse` entry referencing `dangi-hook.sh` and the `hooks.PreToolUse` entry referencing `hcat-gate.sh` from `~/.claude/settings.json`;
- the copies `~/.claude/dangi-hook.sh`, `~/.claude/hcat-gate.sh`, and `~/.claude/hcat`.
The status-line copy (`~/.claude/headroom-statusline.sh`) stays — that one is still how the badge runs.
---
## FAQ
**It always says "idle" — why?**
Most likely the headroom engine isn't installed or the MCP isn't loading. Ask Claude to **run the headroom doctor** — it checks each link in the chain and tells you which one is broken. (Manual check: `mcp__plugin_headroom-usage-indicator_headroom__headroom_compress` — or `mcp__headroom__headroom_compress` for a server you registered yourself — should exist in your session's tools.)
If `/plugin` shows a **✗ beside headroom** and those tools are missing entirely, you're on a version between v2.5 and v2.7.2, where the bundled MCP registration could never connect. Update to v2.7.3 and run **`/headroom-usage-indicator:doctor --fix`** — see [The gauge and the engine](#the-gauge-and-the-engine).
If the engine *is* working and `hcat` is clearly compressing (you see receipts in the transcript) but the badge **still** sits at idle showing zero, the status-line script is missing its runtime deps. The badge runs from a copy at `~/.claude/headroom-statusline.sh` and reads `attribution.jq` + `headroom-state.sh` from `~/.claude/lib/` next to it; without them it silently degrades to zero. Run **`/headroom-usage-indicator:doctor --fix`** — it (re)installs those deps and the badge starts reporting real totals. (Fixed in **v2.7.1** — earlier installs never provisioned them; [#2](https://github.com/Abhi902/headroom-plugin/issues/2).)
If the badge says **"N big blobs uncompressed"** for files the gate *did* compress — you saw the `cat` get rewritten and the receipt land, and it still reads zero — that's **fixed in v2.7.4**. The gate rewrites `cat` into an `hcat` run, but Claude Code records the *original* `cat` in the transcript, and the badge attributed receipts by reading that recorded command. So every auto-rewritten compression banked nothing and was then counted as a *miss* — the badge reported the exact opposite of what happened, and the session ledger and next-session invoice inherited it. Update and the numbers correct themselves; nothing to re-run. (Same release: a large **passthrough** receipt — incompressible content, where the raw bytes really did enter the window — now counts as a miss instead of being silently exempt.)
**It says "▲ headroom broken" — what now?**
That's different from idle: a hook or `hcat` recorded a real engine failure in the last 24h (not "never installed" — "resolved and then broken", e.g. a bad `HCAT_PYTHON` or a half-created venv). Run `/doctor` — it explains the specific failure and, once the underlying issue is fixed, clears the recorded state and restores the badge.
**Do I still have to remember to compress things?**
Less than you used to. The hcat gate redirects big structured-file reads automatically, and Dangi nudges Claude about the rest. The badge is the honest scorekeeper on top.
**I already have a custom status line — will this wipe it?**
No. The status-line setup is **merge-aware**: it *appends* the headroom badge to your existing status line (so you keep `Model · ctx · dir (branch)` and gain the headroom dot) and backs up your original under `_headroomStatusLineBackup` in `settings.json`. To restore, copy that key back over `statusLine`.
**On Windows, `--fix` refused to keep my custom status line — why?**
Your existing command is written for a native spawner, with a backslash path (`node C:\Users\you\sl.js`, `.\tools\sl.cmd`) or a `%VAR%` (`%APPDATA%\sl.js`). The badge runs your old status line *inside bash*, and bash strips those backslashes and never expands `%VAR%`, so chaining it would quietly break both. Since v2.8 the doctor refuses that merge and leaves `settings.json` untouched. Rewrite the command with forward slashes and no `%VAR%` (`node C:/Users/you/sl.js` works in both worlds), then re-run `--fix`. If the backslash is a regex escape (`grep '.\w+' f`), single-quote the argument: bash keeps backslashes inside single quotes, and the doctor lets that through.
**`--fix` said it won't register the `headroom` it found — why?**
The shim in `~/.local/bin` and the user-scoped MCP registration both outlive the project you ran the doctor in: every later session, in every project, spawns them. So since v2.8, `--fix` refuses to persist a `headroom` that resolves inside the current project, or through a relative path. That is exactly what a repository that sets `HCAT_PYTHON`, `UV_TOOL_DIR` or `PATH` in its project settings would steer it to. The message names the setting that pointed there. Undo it, or install the engine normally (`uv tool install headroom-ai`, `pipx`, or let the doctor bootstrap `~/.headroom-venv`), and run `--fix` again. An existing registration that already works is left alone.
**Can I change the colours / the 60-second decay / show a different tool?**
Yes — see the **Customize** section in `skills/headroom-usage-indicator/SKILL.md`. The same pattern works for any MCP tool (`mcp__server__tool`), not just headroom.
**Is any of this sent anywhere?**
No. It's a local shell command reading your local session file. The engine runs offline (`HF_HUB_OFFLINE=1`, update checks off). Nothing leaves your machine.
---
## Appendix: Manual / legacy install (no plugin)
If you can't (or won't) use the plugin marketplace, the copy-everything-to-`~/.claude` flow still works. Clone this repo, then follow the **legacy fallback installer** at the bottom of `skills/headroom-usage-indicator/SKILL.md` — it copies `statusline.sh`, `dangi-hook.sh`, `hcat-gate.sh`, `hcat`, and (v2.7) `session-probe.sh` and `ledger-hook.sh` into `~/.claude/`, and registers all five hook events (SessionStart, PreToolUse, PostToolUse, Stop, SessionEnd) in your `settings.json` itself — the plugin's `hooks/hooks.json` does this automatically, so a legacy install has to do it by hand.
Two honest caveats about the legacy flow:
- **`hcat` is NOT on Claude's PATH** in a legacy install — the "on PATH" convenience only exists while the plugin is enabled. Claude must invoke it by full path: `~/.claude/hcat <file>`. The gate is install-aware: in a legacy layout its deny message cites that full sibling path (`~/.claude/hcat "<path>"`), and the bare-`hcat`/on-PATH wording appears only for plugin installs.
- You must install the **headroom engine** yourself (→ https://github.com/headroomlabs-ai/headroom) **and register its MCP server yourself** — this repo's `.mcp.json` is only read when Claude Code loads it as the enabled plugin (or while you are working inside this repo), never in your other projects, so a legacy install has to add the server (`headroom mcp serve`) to its own MCP configuration. Putting `headroom` on PATH is necessary but not sufficient: it is what makes the registered bare-name command spawn once you have registered it. You also need `jq` (`brew install jq` or `apt install jq`).
Do **not** run the legacy installer if the plugin is installed — you'd register every hook twice.
## What's inside
- `skills/headroom-usage-indicator/SKILL.md` — the status-line skill: the merge-aware installer, how the badge works, a common-mistakes table, verification steps, and customization notes.
- `skills/doctor/SKILL.md` — the doctor: checks `jq`, the engine, the MCP, the hooks, the status line, and ambient-health state; fixes what you consent to, including legacy-install and project-settings cleanup.
- `hooks/hooks.json` — plugin-native registration for session-probe (SessionStart), the hcat gate (PreToolUse), Dangi (PostToolUse), and the session ledger (Stop, SessionEnd).
- `bin/hcat` — compress-at-the-source, on Claude's PATH while the plugin is enabled; falls back to a lossless TOON-lite (jq-only) rendering when the Python engine is absent.
- `scripts/` — `statusline.sh`, `dangi-hook.sh`, `hcat-gate.sh`, `session-probe.sh`, `ledger-hook.sh`, `doctor.sh` (the working parts), plus `scripts/lib/` (`attribution.jq`, `headroom-state.sh`, `engine-resolve.sh` — shared deps the installer copies next to the status-line script; `doctor-mcp.sh` — the doctor's own workspace-containment and MCP-registration helpers, sourced only by `doctor.sh`).
- `data/model-prices.json` — the badge's price table as data; adding a model is an edit here, not a code change.
- `.mcp.json` — bundled headroom MCP server definition (`headroom mcp serve`, spawned by name).
- `test.sh` — the synthetic-transcript test suite; run it from the repo root.
- `scripts/ci/` — `windows-check.sh` (the real-Windows gate: resolver layouts, `doctor --fix`, the status-line command) and `spawn-probe.mjs` (spawns an MCP server without a shell, the way Claude Code does, and fails unless a JSON-RPC `initialize` comes back).
## License
MIT
连接信息
猜你喜欢
暂无推荐服务器