π BONUS β CmdPulse: real progress bars for every Claude Code tool call
"for everyone tired about waiting for the next update β I made something everyone was
searching for, because it is far easier than what I'm working on.It took me about 1 hour to produce that, a function everyone was searching for, easyβ¦
imagine what the router is capable of after 2 weeksβ¦"
A live progress bar for every Claude Code tool call, rendered inside Claude Code's own
status line. It answers the one question the UI never answers:
Is it still working, or is it stuck?
β Έ Bash ββββββββββ 61% 2m14s cargo test --release
β [ 5/20] seed 0004: 145832 (mean: 152340.2, 3.1s/seed)
β phase Β·Β·Β· 8s awaiting permission: Bash
ββββββββββ 47% | [Opus 5 (1M context)] xhigh β» | my-project main* | β§ Inspect
Everything is local. Nothing is uploaded. No dependencies beyond bash and jq.
What it shows
| element | meaning |
|---|---|
ββββββββββ 61% |
how far through the learned median time for this exact command shape |
over (red) |
already past its usual time β the honest "this might be stuck" signal |
Β·Β·Β· sweeping |
fewer than 2 past runs, so no honest estimate exists yet |
2m14s |
live elapsed, readable to hours |
β [ 5/20] seed 0004β¦ |
the command's own stdout, streamed live (opt-in) |
β phase |
compaction, a pending permission prompt, or a subagent running |
β / β |
finished call, with true duration and start clock |
β§ Inspect |
opens the full HTML dashboard |
The percentage is an ETA against history this machine actually recorded β never a fake
byte count. A command CmdPulse has not seen twice gets a sweeping bar and the label Β·Β·Β·,
because inventing a number would be worse than admitting there isn't one.
Why the phase rows matter most
Tool bars only cover PreToolUse β PostToolUse. Three things happen outside that window
and look identical to a freeze:
- context compaction β long, silent
- a permission prompt β the machine is waiting on you
- a subagent thinking between its own tool calls
CmdPulse wires all 31 Claude Code hook events and renders these as β phase rows. Nine
carry meaning; the other 22 return immediately so they cost nothing.
Install
bash install.shCopies four scripts to ~/.claude/, backs up settings.json, and merges the config β
appending to any hooks you already have rather than overwriting them. No restart needed;
Claude Code re-reads settings.json live.
Requires bash and jq. On Windows use Git's bash (C:\Program Files\Git\bin\bash.exe);
the installer detects the platform and writes the correct command form.
Manual install and the Windows/POSIX settings forms are in REPRODUCE.md.
Every feature, flag and troubleshooting step is in USAGE.md.
The one number that matters: refreshInterval
"statusLine": { "type": "command", "command": "...", "refreshInterval": 3 }Do not lower this to 1 without reading USAGE.md Β§Performance.
Claude Code re-runs the status line on a timer, and each new run aborts the previous one
still executing (#_(){ this.#s?.abort() }). On Windows a full render costs ~1.3s, because
a shell script pays ~14ms per subprocess spawn and this one makes ~75. At refreshInterval: 1
every render is aborted before it finishes and the status line goes completely blank β
which looks exactly like the tool being broken. At 3 each render completes.
On Linux/macOS spawns are far cheaper and 1 is usually fine. Measure before changing it:
time (echo '{"model":{"display_name":"T"},"workspace":{"current_dir":"/tmp"},
"context_window":{"total_input_tokens":5000,"context_window_size":200000,
"used_percentage":2.5},"cost":{"total_cost_usd":0}}' | bash ~/.claude/statusline.sh)Keep refreshInterval comfortably above that number.
Live output streaming (opt-in)
export CMDPULSE_STREAM=1Rewrites Bash commands via the updatedInput field of PreToolUse so they tee their output
to a log the bar tails. Your script's own progress lines then appear under the bar, live.
Exit codes are preserved β the wrapper ends with exit ${PIPESTATUS[0]}. This is not
cosmetic: a naive cmd | tee yields tee's exit status, silently turning a failed build
green. Verified against a command exiting 101 (naive form returned 0; shipped form returns
101; a succeeding command still returns 0). If you modify the wrapper, re-run that test.
Off by default, and only ever applied to Bash.
The other two surfaces
bash ~/.claude/cmdpulse/cmdpulse.sh # live dashboard, 200ms, own clock
bash ~/.claude/cmdpulse/cmdpulse.sh top # where your time actually goes
bash ~/.claude/cmdpulse/cmdpulse-web.sh # HTML inspector: full input/output, copyablecmdpulse.sh in a split pane is the only surface that can animate a bar while a fast
command runs β it owns its own clock instead of waiting for Claude Code to be idle.
wezterm-cmdpulse.lua is included for WezTerm users: same bar in WezTerm's status bar at
200ms. Inert on other terminals.
Honest limits
- Fast tools can't be caught live.
Readaverages 66ms,Write24ms,Edit111ms. The
status line needs a 300ms quiet gap plus the render time, so onlyBash-class calls are
caught in flight. Everything else is shown by the completed-call row instead β and
everything is recorded either way. - The completed bar's fill is a replay, not live progress. Duration, start time and
outcome are true measurements; the animation is reconstructed across the afterglow window.
CMDPULSE_REPLAY=0disables it. - Hook cost is ~285ms per hook on Windows (bash + jq startup), so ~570ms per tool call.
Meaningfully cheaper on Linux/macOS. runs/grows two files per tool call and is not auto-pruned:
find ~/.claude/cmdpulse/runs -type f -mtime +7 -delete
Privacy
~/.claude/cmdpulse/ records every command you run and its output in events.ndjson,
runs/ and (if streaming is on) stream/. This package ships none of that. Delete those
directories before sharing your own copy.
Uninstall
cp ~/.claude/settings.json ~/.claude/settings.json.bak
jq 'del(.statusLine)
| .hooks |= with_entries(.value |= map(.hooks |= map(select((.command // "")
| test("cmdpulse") | not))))' ~/.claude/settings.json.bak > ~/.claude/settings.json
rm -rf ~/.claude/cmdpulse ~/.claude/statusline.shLicensed under the same terms as the rest of this repository.
ETA
β Έ Bash ββββββββββ 20% 4s ETA 16s cargo build --release
β Bash ββββββββββ 60% 12s ETA 8s cargo build --release
β Bash ββββββββββ over 25s cargo build --release
ETA is the learned median minus elapsed, shown only when that command signature has at
least 2 recorded runs. Fewer than that and you get ETA ? with a sweeping bar β the estimate
does not exist yet, and inventing one would be worse than saying so.
Rolling history
CMDPULSE_ROWS (default 3, max 12) shows the last N completed calls stacked above the status
line, each with mark, duration and start clock β so a burst of fast tools stays visible
instead of each overwriting the last.
Refresh rate β what is actually achievable
| surface | interval | why |
|---|---|---|
| Claude Code status line | 1s floor, ships at 3 | Math.max(1,t)*1000 in the binary; sub-second is not configurable, and a render slower than the interval is aborted, blanking the line |
cmdpulse.sh split pane |
100ms | owns its own clock, independent of the host |
| WezTerm status bar | 100ms | same |
A status-line render currently costs ~1.0β1.7s on Windows (~75 subprocess spawns at ~14ms
each). Reaching 100ms there would need roughly an 11Γ speedup β a single-jq rewrite of the
render path, not a config change. Until then, the split pane is the fast surface.
Real progress, when the command reports it
A generic tool cannot know how much work an arbitrary command has left β nothing exposes
that. But the command itself often says so, and with CMDPULSE_STREAM=1 that output is
already on disk, so CmdPulse parses it:
β ΄ Bash ββββββββββ 25% 45s ETA 2m15s 5/20 cargo test --release
β [ 5/20] seed 0004: 145832 (mean: 152340.2, 3.1s/seed)
[5/20], 12 of 34, 73% β all recognised. The ETA is then computed from the counter
itself (elapsed Γ· done Γ remaining) and needs no history at all.
| bar colour | meaning |
|---|---|
| cyan | the number came from the command's own output β measured |
| violet/gold | estimated from the learned median for that signature |
sweep + ETA ? |
fewer than 2 recorded runs; no honest estimate exists |
If a command reports nothing, the bar falls back to the median rather than inventing a
number. That fallback is verified by a control test, not assumed.
Topics β 42 tags, generated from .github/tags.txt
#Specification #ExpertRouting #KernelVerified #Bash #AgenticWorkflow #MachineChecked #Router #OpenSource #Copyleft #CliTool #Leanchecker #Eupl #PromptEngineering #ClaudeCode #FormalVerification #Anthropic #Spdx #StaticAnalysis #EnsembleMethods #ProofAssistant #Powershell #AiAgents #Mathlib #Moe #FreeSoftware #VerifiedSoftware #Plugin #MutationTesting #Agpl #LlmTooling #ProofEngineering #Lean4 #NonProfit #ReuseCompliance #DeveloperTools #Sigmoid #DependentTypes #Hooks #ClaudeCodePlugin #TheoremProving #MixtureOfExperts #DualLicensed