ccpulse

module
v0.11.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 2, 2026 License: MIT

README

ccpulse

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.

ccpulse TUI demo

Features

  • 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; u toggles cost / output / usage units and z cycles 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.

Installation

Homebrew (macOS)
brew install martinciu/tap/ccpulse

Shell 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.

Debian / Ubuntu (.deb)
curl -LO https://github.com/martinciu/ccpulse/releases/latest/download/ccpulse_<version>_linux_amd64.deb
sudo dpkg -i ccpulse_*.deb

Replace <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.

Fedora / RHEL / openSUSE (.rpm)
curl -LO https://github.com/martinciu/ccpulse/releases/latest/download/ccpulse_<version>_linux_amd64.rpm
sudo rpm -i ccpulse_*.rpm

Same <version> and amd64arm64 substitutions as above.

Manual / no-root (tarball)

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 PATH

ccpulse 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.

Verifying the download (optional)

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.txt

Run 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
go install github.com/martinciu/ccpulse/cmd/ccpulse@latest

Requires 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:

ccpulse

On 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.

Build from source

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/ccpulse

The binary lives in ~/.local/bin/ccpulse.

Commands

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. The zoom and unit you pick are remembered across restarts.

ccpulse index

Drops the SQLite cache and rebuilds it from a full scan of projects_root.

ccpulse index --rebuild   # drop the cache, then do a full scan

The 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.

ccpulse status

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.

Claude Code hook

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-bar integration

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
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.toml

edit never overwrites an existing file — safe to run at any time to check where the file lives before editing it manually.

ccpulse doctor

Runs a health-check checklist and prints a pass/fail report:

  • Config file loads and projects_root is readable
  • SQLite cache opens and PRAGMA integrity_check passes
  • 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.log size
  • Whether the Claude Code Stop hook is configured

Run this first when something looks wrong.

ccpulse version

Prints the build version, commit hash, and build date.

Configuration

~/.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. Default 0 keeps history forever. Usage history is recorded once per ~3 minutes whenever ccpulse is running and successfully reaches the Anthropic usage API.
  • [ui] reduce_motion — set true to 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 — overrides projects_root
  • CCPULSE_CACHE_DIR — overrides cache_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.

Troubleshooting

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.

The TUI's chart zoom and view are remembered in ~/.cache/ccpulse/ui-state.toml. Delete it to reset to the defaults (15m zoom, cost view). A missing or corrupt file is ignored silently.

License

MIT (see LICENSE).

Contributions: ccpulse doesn't accept external pull requests, but bug reports and feature requests are welcome — see CONTRIBUTING.md.

Directories

Path Synopsis
cmd
ccpulse command
pkg
anthro
Package anthro talks to api.anthropic.com on behalf of ccpulse, using the Claude Code OAuth credential.
Package anthro talks to api.anthropic.com on behalf of ccpulse, using the Claude Code OAuth credential.
cache
Package cache persists parsed messages and time-bucketed token aggregates in a local SQLite database (modernc.org/sqlite).
Package cache persists parsed messages and time-bucketed token aggregates in a local SQLite database (modernc.org/sqlite).
channel
Package channel exposes the build channel (dev or release) chosen at link time by cmd/ccpulse/main.go via an ldflag-injected variable.
Package channel exposes the build channel (dev or release) chosen at link time by cmd/ccpulse/main.go via an ldflag-injected variable.
config
Package config loads the TOML config at ~/.config/ccpulse/config.toml.
Package config loads the TOML config at ~/.config/ccpulse/config.toml.
devlog
Package devlog wires slog.Default() based on the build channel and the resolved log level.
Package devlog wires slog.Default() based on the build channel and the resolved log level.
ingest
Package ingest catches the ccpulse cache up to current EOF for one or many .jsonl transcripts.
Package ingest catches the ccpulse cache up to current EOF for one or many .jsonl transcripts.
models
Package models derives stable identities and display names for the model ids recorded in transcripts.
Package models derives stable identities and display names for the model ids recorded in transcripts.
parse
Package parse turns Claude Code JSONL transcripts into Message records.
Package parse turns Claude Code JSONL transcripts into Message records.
pricing
Package pricing embeds pricing.json and computes the USD cost of a message.
Package pricing embeds pricing.json and computes the USD cost of a message.
projects
Package projects resolves a Claude Code working directory (cwd) to a stable repo-root path so a repo's main checkout, its worktrees, and its subdirectories all collapse to a single project.
Package projects resolves a Claude Code working directory (cwd) to a stable repo-root path so a repo's main checkout, its worktrees, and its subdirectories all collapse to a single project.
secfile
Package secfile provides file/dir helpers that enforce private modes (0700 for dirs, 0600 for files) and chmod existing entries to those modes on next access.
Package secfile provides file/dir helpers that enforce private modes (0700 for dirs, 0600 for files) and chmod existing entries to those modes on next access.
status
Package status computes the 5-hour rolling and 7-day quota windows and maps plan tier to token ceiling.
Package status computes the 5-hour rolling and 7-day quota windows and maps plan tier to token ceiling.
termsafe
Package termsafe strips non-printable runes from strings that reach the terminal but did not come from ccpulse itself.
Package termsafe strips non-printable runes from strings that reach the terminal but did not come from ccpulse itself.
tui
Package tui — breakdown-panel slide animation (issues #416, #475).
Package tui — breakdown-panel slide animation (issues #416, #475).
uistate
Package uistate persists small TUI UI preferences (zoom level, active view) to <cacheDir>/ui-state.toml so they survive restarts (#490).
Package uistate persists small TUI UI preferences (zoom level, active view) to <cacheDir>/ui-state.toml so they survive restarts (#490).
watcher
Package watcher wraps fsnotify with a debounce so a flurry of writes to the same JSONL collapses into a single onChange callback.
Package watcher wraps fsnotify with a debounce so a flurry of writes to the same JSONL collapses into a single onChange callback.
scripts
seedyear command
scripts/seedyear — synthesise a year of Claude Code messages into the dev ccpulse cache for manual bar-chart perf probing (issue #71).
scripts/seedyear — synthesise a year of Claude Code messages into the dev ccpulse cache for manual bar-chart perf probing (issue #71).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL