A Three-Bar Statusline for Claude Code
cc-context-monitor v0.2.0 is a one-command Claude Code plugin that reads CC's native stdin payload and renders three color-banded 10-dot progress bars: context window fill, 5-hour session rate-limit burn, and 7-day weekly quota burn. No ccusage required on CC v2.1.x+.
Halfway through a long Claude Code session, two questions start competing for peripheral vision. Am I about to hit the weekly quota ceiling. Am I about to blow out the 1M-token context window. Claude Code’s default statusline answers neither of those. It shows the model name and the working directory, which is useful the first five minutes and invisible for the next five hours.
Version 2.1.x of Claude Code quietly started piping a much richer JSON blob into the statusline hook. That blob already carries every signal a subscription user cares about: context fill percent, 5-hour rate-limit burn, 7-day weekly quota burn, model display name, cwd, output style. Nothing external needed to render it. cc-context-monitor v0.2.0 is a small bash wrapper that reads that payload, draws three color-banded dot bars, and installs in two slash commands.
What it shows
One line, every turn:
~/git/bdigital-public | feat/branch* | Opus 4.7 (1M context) | ●●○○○○○○○○ 18% ctx | ●●○○○○○○○○ 20% 5h | ●●●●●○○○○○ 54% 7dSix segments, reading left to right:
- cwd in cyan, home-relative.
- Git branch in magenta, with a trailing asterisk when the working tree is dirty. Omitted outside a repo.
- Model label straight from CC’s own
display_namefield. - Context window fill as a 10-dot bar plus percent. Green under 50, yellow under 75, red at 75 and up.
- 5-hour session quota as a bar plus percent, same color palette.
- 7-day weekly quota as a bar plus percent, same palette.
Three bars sharing one palette means pressure anywhere jumps out visually. Context filling up is one kind of problem. Weekly quota approaching ceiling is a different kind of problem. Seeing both on the same line, in the same colors, lets you spot the one that matters without re-parsing.
Any trailing annotation composed via CC_STATUSLINE_ANNOTATION (for example Remote control active) survives after the three bars, so setups composing other tools compose cleanly.
Install
From any Claude Code session:
/plugin marketplace add neurot1cal/bdigital-public/plugin install cc-context-monitor@bdigital-public/cc-context-monitorThree commands, three things happen. First adds the marketplace, second installs the plugin, third runs the user-invocable skill that backs up ~/.claude/settings.json, wires up the statusline, and confirms the change. A fresh Claude Code session then renders the bars on every turn.
If your Claude Code version rejects the owner/repo shorthand, pass the full Git URL to marketplace add instead: https://github.com/neurot1cal/bdigital-public.git. To iterate on a local fork before publishing, point at your working tree:
/plugin marketplace add /absolute/path/to/bdigital-public/plugin install cc-context-monitor@bdigital-publicSame pattern. Point at your working tree, install, iterate.
Why the rewrite
Version 0.1.0 wrapped ccusage and added a single-color context prefix. That was useful while it shipped, but dollar figures turned out to be the wrong primary signal for a subscription user. Cost calculated as token count times public API rates is not the number that caps your session. Subscription quota is the number that caps your session, and until recently there was no clean way to surface that without querying the CC transcript directly.
Anthropic fixed that on their side. Dump stdin from a v2.1.116 session and you get this:
{ "model": { "display_name": "Opus 4.7 (1M context)" }, "cwd": "/Users/bobulrich/git/bdigital-public", "context_window": { "used_percentage": 17, "remaining_percentage": 83 }, "rate_limits": { "five_hour": { "used_percentage": 19, "resets_at": 1776740400 }, "seven_day": { "used_percentage": 54, "resets_at": 1776970800 } }, "output_style": { "name": "default" }, "cost": { "total_cost_usd": 52.58, "total_lines_added": 1134 }}Everything you want for a quota-aware statusline is already in the payload. Context fill is there. Both rate-limit windows are there. Model label is pre-formatted. Cwd is there. v0.2.0 is what the plugin looks like when you stop wrapping an external tool and start reading the payload directly.
What’s actually running
statusline.sh is about 280 lines including comments. Four pieces.
Read stdin. CC hands the statusline a JSON blob on stdin, and the wrapper captures it once, validates with jq, and bails quietly on malformed input rather than crashing:
STDIN_JSON="$(cat || true)"if [ -z "${STDIN_JSON:-}" ] || ! printf '%s' "$STDIN_JSON" | jq -e . >/dev/null 2>&1; then printf '%scc-context-monitor: no statusline input%s\n' "$COLOR_DIM" "$COLOR_RESET" exit 0fiExtract signals. Every field the wrapper needs is a single jq expression. Fields that could be missing get a defaulting helper that returns an empty string rather than the literal word null, so downstream -z checks work cleanly:
CWD=$(jq_field '.cwd // .workspace.current_dir' "")CTX_USED_DIRECT=$(jq_field '.context_window.used_percentage' "")CTX_REMAINING=$(jq_field '.context_window.remaining_percentage' "")RATE_5H=$(jq_field '.rate_limits.five_hour.used_percentage' "")RATE_7D=$(jq_field '.rate_limits.seven_day.used_percentage' "")Render a bar. One helper draws an N-dot progress bar at any percent and color. It’s pure integer arithmetic, no awk or bc:
render_bar() { local pct="$1" color="$2" width="$CC_CTX_BAR_WIDTH" local filled=$(( (pct * width + 50) / 100 )) local unfilled=$(( width - filled )) local filled_part="" while [ "$filled" -gt 0 ]; do filled_part+="●"; filled=$(( filled - 1 )); done local unfilled_part="" while [ "$unfilled" -gt 0 ]; do unfilled_part+="○"; unfilled=$(( unfilled - 1 )); done printf '%s%s%s%s%s' "$color" "$filled_part" "$COLOR_DIM" "$unfilled_part" "$COLOR_RESET"}A sibling band_color helper returns the ANSI escape for green / yellow / red based on the two thresholds (CC_CTX_GREEN_MAX defaults to 49, CC_CTX_YELLOW_MAX defaults to 74). All three bars share that helper, so tweaking the thresholds reshapes the whole statusline uniformly.
Fallback for older CC versions. If context_window.used_percentage is absent from stdin, the wrapper tries ccusage statusline --offline and greps the (NN%) segment out of its output. On those older hosts the 5h and 7d bars are simply omitted rather than guessed at. Legacy users still get a ctx bar; everyone else gets all three.
No eval, no source, no shell substitution over the transcript path. Final assembly is one printf per segment and one for loop to join with |.
How it stays honest
A plugin that touches ~/.claude/settings.json has a bigger blast radius than it looks. If the wrapper crashes, your whole statusline crashes. If the skill writes bad JSON, every Claude Code session on that machine breaks until the backup is restored. Two test harnesses guard against that.
test-skill-structure.sh runs 55 assertions against the plugin shape without ever invoking the wrapper at runtime. It checks plugin.json parses as JSON and has all seven required fields. It checks SKILL.md carries the right frontmatter (user-invocable: true, the allowed-tools list, a trigger-focused description that does not read like a workflow doc). It checks statusline.sh is executable, has a bash shebang, passes bash -n, uses set -euo pipefail, references each of the three color thresholds and their ANSI codes, does not call eval, does not source untrusted input, references context_window.used_percentage (so dropping the native-field read fails the test), references rate_limits.five_hour and rate_limits.seven_day (same reasoning for the quota bars), and does not shell out to curl or wget. A word-budget check keeps SKILL.md under 1500 words.
run-evals.sh is the behavioral pass. Seven fixture cases live in statusline-output.json: a clean 0% session with all bars empty and green, a mid-green case with all three percentages under 50, a ctx-in-yellow-band case, a 7d-in-red-band case, a legacy-CC fallback where context_window is absent and the wrapper falls through to a stubbed ccusage, a malformed-stdin safety case, and an annotation-preservation case. Each fixture stubs a fake ccusage binary that prints canned output, pipes the fixture’s JSON to statusline.sh under an isolated PATH, and greps the output for the expected color codes ([32m green, [33m yellow, [31m red) alongside the percent labels (18% ctx, 20% 5h, 54% 7d).
Together: 55 structural assertions plus 7 end-to-end cases. Structural suite catches drift the author cannot see. Eval suite catches regressions in the color logic and the rate-limit wiring.
Tuning
Three environment variables shift the defaults:
CC_CTX_GREEN_MAX(default 49) moves the green-to-yellow boundary.CC_CTX_YELLOW_MAX(default 74) moves the yellow-to-red boundary.CC_CTX_BAR_WIDTH(default 10) trades horizontal space for resolution. 20-dot bars show finer gradations if your terminal is wide.
Prefix any of them on the statusline command itself:
{ "statusLine": { "type": "command", "command": "env CC_CTX_GREEN_MAX=40 CC_CTX_YELLOW_MAX=70 bash /Users/you/.claude/plugins/marketplaces/bdigital-public/plugins/cc-context-monitor/statusline.sh", "padding": 0 }}CC_STATUSLINE_ANNOTATION appends a dim-colored trailing label after the three bars. Setting it in your shell profile gives you an always-on marker (a hostname, a current ticket number, a “remote control active” hint) that survives through the wrapper rather than getting clobbered.
Closing
Plugin is MIT, vendored alongside its readable samples/ twin in bdigital-public, and works on any Claude Code v2.1.x+ host. One statusline, three bars, one palette. If you are running on a subscription and have been eyeing the default statusline wondering which number to watch, /plugin install cc-context-monitor@bdigital-public is the two-command upgrade.