A native Go TUI dashboard for Claude Code usage. Reads
~/.claude/projects/*/*.jsonl transcripts, computes token / cost /
5-hour-window breakdowns. No third-party telemetry — the only network
call is to Anthropic's own usage API for your account (to refine the quota
gauges), and it falls back to transcript-only data when no credential is
present.
- 5h + 7d quota bars — rolling-window gauges in the header, fed by the Anthropic usage API where available with a JSONL fallback.
- Usage histogram — horizontally-scrollable bar chart per time
bucket, heat-coloured relative to the peak. Defaults to a cost view;
utoggles cost / output / usage units andzcycles 15m / 1h / 24h granularity. - fsnotify live updates — file watcher (not polling) keeps the cache in sync as Claude writes new turns; the TUI redraws on each refresh.
brew install martinciu/tap/ccpulseShell completions are installed automatically. This is a macOS cask,
which Homebrew only supports on macOS — on Linux, use the .deb/.rpm
packages or the tarball below.
curl -LO https://github.com/martinciu/ccpulse/releases/latest/download/ccpulse_<version>_linux_amd64.deb
sudo dpkg -i ccpulse_*.debReplace <version> with the actual release number (e.g. 0.1.0) — it is
part of the asset filename, so the URL 404s if left literal. Swap amd64
for arm64 on ARM. The
releases page lists the
current version.
curl -LO https://github.com/martinciu/ccpulse/releases/latest/download/ccpulse_<version>_linux_amd64.rpm
sudo rpm -i ccpulse_*.rpmSame <version> and amd64→arm64 substitutions as above.
For hosts where a package manager isn't an option — e.g. a sandbox with
no-new-privileges where sudo/dpkg is blocked — install the static
binary from the tarball into a directory you own:
# Debian/Ubuntu: dpkg --print-architecture → amd64 or arm64
ARCH=$(dpkg --print-architecture)
curl -fsSL "https://github.com/martinciu/ccpulse/releases/latest/download/ccpulse_<version>_linux_${ARCH}.tar.gz" | tar xz
install -Dm755 ccpulse ~/.local/bin/ccpulse # ensure ~/.local/bin is on PATHccpulse is a single static binary with no runtime dependencies. On
non-Debian systems substitute the arch by hand (artifacts use Go's
amd64/arm64, not uname -m's x86_64/aarch64). macOS users without
Homebrew can install the matching darwin_{amd64,arm64}.tar.gz the same way.
Each release ships a checksums.txt alongside the binaries. To verify
integrity before installing:
curl -LO https://github.com/martinciu/ccpulse/releases/latest/download/checksums.txt
sha256sum --ignore-missing -c checksums.txtRun this from the directory where you downloaded the .deb / .rpm /
tarball. The Homebrew install path is already integrity-checked by
brew against the cask's sha256.
go install github.com/martinciu/ccpulse/cmd/ccpulse@latestRequires Go 1.25+. Skips the build-channel ldflag, so the binary writes
dev debug logs to ~/.cache/ccpulse/debug.log.
After install, launch the TUI:
ccpulseOn first launch the TUI cold-walks ~/.claude/projects/ and backfills
the cache (progress shown in the header). After that, the fsnotify
watcher keeps the cache up to date automatically.
Requires mise and git.
git clone https://github.com/martinciu/ccpulse ~/code/ccpulse
cd ~/code/ccpulse
mise install # fetches Go 1.25 into the project-scoped toolchain
make install # builds → ~/.local/bin/ccpulseThe binary lives in ~/.local/bin/ccpulse.
Opens the interactive TUI: 5h + 7d quota bars and a horizontally-scrollable
usage histogram. ←/→ (or h/l) scroll the chart, z cycles bucket
zoom (15m / 1h / 24h), u toggles units (cost / output / usage), ?
toggles full help, q quits.
Drops the SQLite cache and rebuilds it from a full scan of projects_root.
ccpulse index --rebuild # drop the cache, then do a full scanThe TUI backfills automatically on launch, so this is only needed if
the cache gets out of sync or you want a clean slate. The bare
ccpulse index (no --rebuild) is intentionally an error.
Prints the current 5-hour rolling window without opening the TUI.
ccpulse status # human-readable summary
ccpulse status --json # JSON: 5h + 7d percent/reset, tokens_5h, cost_5h_usd,
# ceiling, optional projection, today/7d/30d periods,
# live throughput (tokens/hr + $/hr)--json is useful for scripting or status bars that consume structured data
— see Status-bar integration for copy-paste tmux
and starship recipes.
ccpulse status --quiet is designed to be called from Claude Code's Stop
hook, which fires after every assistant turn. The internal 3-minute cache
TTL absorbs the burst rate, so the Anthropic usage API is hit at most
once every few minutes even during heavy use — no daemon, no blind
polling. Add this to ~/.claude/settings.json (or merge the "hooks" key
if you already have one):
{
"hooks": {
"Stop": [
{
"matcher": "",
"hooks": [
{ "type": "command", "command": "ccpulse status --quiet" }
]
}
]
}
}ccpulse doctor reports whether this hook is configured.
status --json drives any prompt or status bar. The recipes below render a
compact 5h X% · 7d Y% segment. They pipe through
jq (brew install jq / apt install jq).
Each field read uses a // 0 fallback, so the segment stays correct at 0% and
before the 7-day window has any usage (where percent_7d is absent from the
JSON).
tmux (~/.tmux.conf) — refreshes on its own interval, no per-command timeout:
set -g status-interval 15
set -ag status-right "#(ccpulse status --json | jq -r '\"5h \" + ((.percent // 0)|tostring) + \"% · 7d \" + ((.percent_7d // 0)|tostring) + \"%\"') "For a fleshed-out two-chip version — separate 7d weekly and 5h block chips with
reset timers and overreach markers — see my dotfiles: the
render script
and how it's
wired in tmux.conf.
starship (~/.config/starship.toml):
[custom.ccpulse]
command = "ccpulse status --json | jq -r '\"5h \\((.percent // 0))% · 7d \\((.percent_7d // 0))%\"'"
when = true
ignore_timeout = true # fresh fetch can exceed starship's 500ms default
format = "[$output]($style) "
style = "yellow"Latency caveat. A cached call returns in ~40ms (3-minute usage-API TTL); a
fresh fetch takes ~640ms, capped at 5s when offline. starship's
command_timeout defaults to 500ms, so a fresh fetch occasionally exceeds
it (roughly every 3 minutes, when the TTL lapses) and the module silently
disappears. Two fixes, usable together: keep ignore_timeout = true (above),
and keep the cache warm with the status --quiet Stop hook
so almost every prompt hits the 40ms path. tmux is unaffected — it polls on
status-interval with no per-command timeout. (status takes a shared lock on
the cache, so it coexists with a running TUI and the Stop hook.)
Extend it. Other status --json fields you can fold into the segment:
minutes_to_reset (countdown to the 5h reset — null when idle, so branch on
it) and cost_5h_usd (current 5h spend in USD, always present):
ccpulse status --json | jq -r '"5h \((.percent // 0))% · $\(.cost_5h_usd) · " + (if .minutes_to_reset then "\(.minutes_to_reset)m left" else "idle" end)'Trailing rollups. periods carries today / 7d / 30d token and cost
totals, each with the five-way tokens_breakdown. The 7d window is anchored
to the weekly quota reset when the usage API is reachable, and falls back to
the last 7 calendar days otherwise. Keys 7d / 30d aren't bare jq
identifiers, so index them with bracket syntax:
ccpulse status --json | jq -r '"today $\(.periods.today.cost_usd) · 7d $\(.periods["7d"].cost_usd) · 30d $\(.periods["30d"].cost_usd)"'Live burn rate. throughput carries the current token/hour and cost/hour
rate over a rolling 30-minute window (extrapolated ×2 to an hourly figure). It
reads 0 when idle — no null-branch needed — so it drops straight into a prompt:
ccpulse status --json | jq -r '"\(.throughput.tokens_per_hour) tok/hr · $\(.throughput.cost_per_hour_usd)/hr"'ccpulse config edit # create config if missing, then open in $EDITOR
ccpulse config show # print the live config (defaults + your overrides)
ccpulse config path # print the path to config.tomledit never overwrites an existing file — safe to run at any time to
check where the file lives before editing it manually.
Runs a health-check checklist and prints a pass/fail report:
- Config file loads and
projects_rootis readable - SQLite cache opens and
PRAGMA integrity_checkpasses - Pricing table version (and the versions present in the cache)
- Anthropic OAuth credential — present, plan tier, and not expired
- Usage-API cache freshness and
parse-errors.logsize - Whether the Claude Code
Stophook is configured
Run this first when something looks wrong.
Prints the build version, commit hash, and build date.
~/.config/ccpulse/config.toml (created on first config edit):
[history]
retention_days = 0 # 0 = keep usage history forever; positive int prunes older rows
[paths]
projects_root = "~/.claude/projects"
cache_dir = "~/.cache/ccpulse"
[ui]
reduce_motion = false # true disables the TUI slide-in animations[history] retention_days— drop usage history rows older than N days on each insert. Default0keeps history forever. Usage history is recorded once per ~3 minutes whenever ccpulse is running and successfully reaches the Anthropic usage API.[ui] reduce_motion— settrueto disable the chart and quota-bar slide-in animations.
Two environment variables override the [paths] block at runtime (handy for pointing at a fixture without editing the config):
CCPULSE_PROJECTS_ROOT— overridesprojects_rootCCPULSE_CACHE_DIR— overridescache_dir
The plan tier (used to compute the 5h / 7d quota ceilings) is read from your Claude Code OAuth credential — there is no config knob for it.
Run ccpulse doctor first — it reports on config, the cache and its
integrity_check, pricing, the Anthropic OAuth credential, usage-API
cache freshness, and the Claude Code Stop hook (see the doctor
command).
If the TUI launches empty, run ccpulse index --rebuild to do a cold
scan of ~/.claude/projects/ and rebuild the cache.
If the cache is corrupt (rare; usually after a kill-during-write), the
TUI auto-rebuilds on launch. Manual: ccpulse index --rebuild.
Parse errors are logged to ~/.cache/ccpulse/parse-errors.log
(rotated at 10 MB). Empty when everything is healthy.
MIT (see LICENSE).
Contributions: ccpulse doesn't accept external pull requests, but bug reports
and feature requests are welcome — see CONTRIBUTING.md.
