Companion to the claude-gisx status line: after each turn, hands it Claude Code's own /context breakdown so the status line can draw the context window…

A rich, dynamic statusline for Claude Code — single self-contained binary, no runtime deps.
Opus 4.8/high · ████████▌░ 86%/347k · myproject:main · 42m · $1.20 · +128 -31 · PR #42
ctx messages 224.8k · tools 28.3k · skills 10.0k · system 2.8k · free 100.0k · buffer 33.0k
cache warm 25m of 1h left · hit 96% · in 1.5M (1.4M read, 80.0k write, 2.0k uncached) · out 15.1k · daily 15.7M ▼47%
5h 32% ↻ 3h 12m · 7d 8% ↻ 5d 2h · Fable 61% ↻ 5d 2h · extra $4.20/$20.00
curl -fsSL https://raw.githubusercontent.com/Justar96/claude-gisx/main/install.sh | bash
irm https://raw.githubusercontent.com/Justar96/claude-gisx/main/install.ps1 | iex
The installer:
SHA256SUMS is published with the release.~/.local/bin/claude-gisx (Linux/macOS) or %LOCALAPPDATA%\Programs\claude-gisx\claude-gisx.exe (Windows).claude-gisx setup to wire it into ~/.claude/settings.json. Your existing statusLine is backed up first. Setup also sets hideVimModeIndicator, since the statusline renders the vim mode itself, and refreshInterval: 30, so the cache countdown keeps moving while you're idle instead of freezing at your last message. Either is left alone if you already set it. It also installs the gisx mod to ~/.claude/skills/gisx/.| Env var | Description |
|---|---|
CLAUDE_GISX_VERSION | Pin a release tag (e.g. v1.0.0). Default: latest. |
CLAUDE_GISX_INSTALL_DIR | Override install dir. Default: ~/.local/bin (Unix) / %LOCALAPPDATA%\Programs\claude-gisx (Windows). |
CLAUDE_GISX_REPO | Pull binaries from a fork: owner/repo. |
CLAUDE_GISX_SKIP_SETUP | Install the binary only, don't touch settings.json. |
Restart Claude Code once done.
The same claude-gisx binary does everything. Run it with no args for an at-a-glance setup screen:
$ claude-gisx
claude-gisx v1.0.0
rich, dynamic statusline for Claude Code
✓ installed — restart Claude Code if you just installed
Get started
• claude-gisx setup wire into ~/.claude/settings.json
• claude-gisx status show current install state and backup
• claude-gisx uninstall restore your previous statusLine
• claude-gisx help this screen
…
| Command | What it does | |
|---|---|---|
claude-gisx (no stdin, no args) | Show the install/setup help screen. | |
claude-gisx setup | Write ~/.claude/settings.json so Claude Code uses claude-gisx, and install the gisx mod. Backs up any existing statusLine to ~/.claude/.gisx/. On an install that's already active, it brings the settings and the mod up to date. | |
claude-gisx status | Inspect current settings and backup state. | |
claude-gisx update | Download the latest release, verify its SHA-256 against the published SHA256SUMS, and replace the running binary in place. --check only reports, --force reinstalls the current version. | |
claude-gisx uninstall | Restore the previous statusLine (or remove it), and remove the gisx mod. | |
claude-gisx help / --help | Help screen. | |
claude-gisx version | Print the binary version. | |
| `<json> \ | claude-gisx` | Render a statusline from session JSON on stdin. This is what Claude Code calls. |
claude-gisx ingest | Store a context breakdown from stdin. The gisx mod calls it after each turn. | |
claude-gisx sync | Bring an active install's settings and mod up to date. update runs it on the new binary. |
pct%/Nk reading sized to the live context_window.context_window_size (so a 200k Haiku shows 42%/200k and a 1M Opus shows 28%/1M). Clean, mark-free bar in one solid color that matches the percentage and changes as a whole when usage crosses a threshold. Ten cells wide, filled in eighth-cell steps, so it moves every 1.25% rather than every 10%. Since the label carries the window size, a (1M context) suffix on the model name is dropped86%/347k = 86% of the way to compaction), read from the same env vars and settings Claude Code uses. See Auto-compactFable 61% ↻ 5d 2h). Behind a Claude apps gateway, the spend limit from rate_limits.spend_limit joins them (it can pass 100%). Per-model quotas and extra credits come from the usage API, refreshed every 60s by a detached background process; renders show the cached figures and never wait on itdaily 15.7M ▼47% closes the session line (line 2): the newest day of dailyModelTokens in ~/.claude/stats-cache.json, against the daily average of the 7 days before it (green ▲ up, dim ▼ down). Measured against that cache's own newest date and hidden once it's >10 days stale, since Claude Code only refreshes it when you open /usagecache warm 25m of 1h left · hit 96% 2 misses · in 1.5M (1.4M read, 80.0k write, 2.0k uncached) · out 15.1k. warm means the cached prefix is still inside its TTL (5m or 1h), with the time until it expires; cold means it has expired and the next request re-caches it (recache 412.0k); off means no response this session reported cache tokens. hit is cache reads as a share of all input, and misses count requests that re-processed content the cache already held, with no compaction to explain it. State, hit ratio and misses come from Claude Code's prompt_cache (v2.1.251+, the same figures /usage shows); older versions get the hit ratio from the transcript. in is total input = cache reads + cache writes + uncached input, summed from the transcript's per-request usage (cache_read_input_tokens, cache_creation_input_tokens, input_tokens), so it counts the cached prefix once per request, as billed. The hit ratio is graded on its own scale (green ≥90, cyan ≥75, yellow ≥50, red below), since a healthy session lives in the 90s. The transcript is read incrementally from where the last render stopped/compact first, since the next message would otherwise write all of it back at the cache-write price (set the size with CLAUDE_GISX_RECACHE_WARN). When Claude Code will compact an idle session by itself shortly before its one-hour cache lapses (v2.1.290+), a magenta idle compact in 46m counts down to it. That is shown only when Claude Code would actually do it: the rollout flag it caches in ~/.claude.json is on (mode compact or compact_on_return), auto-compact is on, idleCompaction isn't false in settings, the cache is a warm 1h one, and the prompt is at least CLAUDE_CODE_IDLE_COMPACT_MIN_TOKENS (200k when unset, never below 100k). It fires at 90% of the hour, about 54 minutes after the last request. Ported from the observability/prompt-cache-control mod in claude-code-templates/context breaks it down: system prompt, tools, MCP, agents, memory, skills and messages, each in its own color and in /context's order, so the conversation grows at the right end. Its length is still how full the window is, and the percentage keeps its threshold color. Shares are strictly proportional, so a category too small for a cell shows only in the key: a ctx line beneath, biggest first, each name in its bar color, then the free space and the auto-compact buffer. Without the mod the bar keeps its single color and the key is left outgit status --no-optional-locks, so it never fights a commit in progress), plus worktree (⌥, for Claude worktree sessions and any linked worktree via workspace.git_worktree; hidden when it's named after the directory or branch already shown) and PR/MR (PR #1234 or MR #12 for GitLab, colored by review state, clickable via OSC 8 where the terminal supports it) indicators when present+128 -31) from the live cost.* fieldsClaude Opus 4.8/high), from the session's effort.level — hidden for models with no effort dialfast) when the session has fast mode on↑ gisx v1.2.2 · run claude-gisx update. GitHub is checked at most once every 6 hours in a detached background process, so a render never waits on the network (silent on failure). Dev builds never nag; set CLAUDE_GISX_NO_UPDATE_CHECK=1 to disable entirelyCLAUDE_GISX_PLUGIN at any shell command (including curl against your own API) and its stdout becomes line 4. Timeout + caching are built in. See Tip-line pluginIf you'd rather skip the installer wiring, add this yourself to ~/.claude/settings.json:
{
"statusLine": {
"type": "command",
"command": "claude-gisx",
"hideVimModeIndicator": true,
"refreshInterval": 30
}
}
The command must resolve on the PATH Claude Code uses; if ~/.local/bin isn't on it, use the absolute path.
Windows: use forward slashes in the path ("C:/Users/you/AppData/Local/Programs/claude-gisx/claude-gisx.exe"). Claude Code routes the command through Git Bash on Windows, which interprets backslashes as escape characters and silently drops stdout. The installer writes forward slashes automatically; this only matters if you edit settings.json by hand.
claude-gisx uninstall
rm ~/.local/bin/claude-gisx # or %LOCALAPPDATA%\Programs\claude-gisx on Windows
uninstall restores whatever statusLine was there before claude-gisx (or removes it if you had none).
The status line's payload says how full the context window is, never what fills it. Claude Code gives that breakdown only to mods, so claude-gisx carries a small one, mods/gisx, built into the binary. claude-gisx setup copies it to ~/.claude/skills/gisx/, where Claude Code 2.1.287+ loads it as gisx@skills-dir in the next session.
After each turn that moved the context window, the mod reads $.session.usage({ breakdown: 'summary' }), which Claude Code computes locally with no API call, and pipes it to claude-gisx ingest. That writes it beside the session in ~/.claude/cache/statusline-context-<session>.json, and the next render colors the context bar and draws its key from it. The colors trail the live fill by one turn at most. The same data gives the bar the point where auto-compact fires as Claude Code itself works it out, which wins over gisx's reading of the settings.
setup wrote into hooks/env.ts, so it doesn't depend on Claude Code's PATH.setup replaces the folder in one rename, so a session never loads half of it, and leaves it alone when nothing changed. It never touches a gisx plugin it didn't write: the manifest carries "metadata": { "installedBy": "claude-gisx" }.uninstall removes it. claude-gisx status shows whether it's installed.claude plugin validate mods/gisx and claude plugin test mods/gisx.██▊░░░░░░░ 28%/1M
The context window is the only bar on the statusline. With no auto-compact setting it's sized to the live context_window.context_window_size, so a Haiku 200k session shows 42%/200k and an Opus 1M session shows 28%/1M. With one, it's sized to the compaction point instead — see below.
The whole fill takes one color, the same as the percentage beside it, and switches all at once at each threshold:
| Usage | Color |
|---|---|
| < 20% | Green |
| 20%+ | Cyan |
| 50%+ | Orange |
| 70%+ | Yellow |
| 90%+ | Red |
When auto-compact is configured, the bar's 100% is the token count at which Claude Code compacts, and the label shows that count: 86%/347k means 86% of the way to compaction. It's the same figure Claude Code's own "% left until auto-compact" is measured against.
The compaction point is worked out the way Claude Code (v2.1.299) does it:
CLAUDE_CODE_AUTO_COMPACT_WINDOW, else autoCompactWindow from settings (what /autocompact writes): modelSettings.<model>.autoCompactWindow beats the top-level value, and ~/.claude/settings.json, <project>/.claude/settings.json and <project>/.claude/settings.local.json are merged in that order. Clamped to at least 100k and at most the model's context window.CLAUDE_AUTOCOMPACT_PCT_OVERRIDE% if that's lower. A percentage with no window applies to the model's full context window.With neither set (or "auto"), Claude Code compacts at a window it tunes per model and doesn't expose, so the bar shows the raw context window. DISABLE_COMPACT or DISABLE_AUTO_COMPACT shows a dim compact:off next to the bar.
# On a 1M model, compact around 360k: the bar reads against 347k.
CLAUDE_CODE_AUTO_COMPACT_WINDOW=360000 claude
# Compact at 80% of the model's full context.
CLAUDE_AUTOCOMPACT_PCT_OVERRIDE=80 claude
Line 4 is a plugin point and is left out until you set one. Wire it to any shell command — including a curl call against your own API — and that command's stdout fills it.
| Env var | Default | Description |
|---|---|---|
CLAUDE_GISX_PLUGIN | (unset) | Shell command or script path. When set, its output is line 4. |
CLAUDE_GISX_PLUGIN_TIMEOUT | 2 | Seconds before the plugin is killed. Keep this low so the statusline stays responsive. |
CLAUDE_GISX_PLUGIN_CACHE | 30 | Seconds to cache the plugin's last successful stdout. Prevents hammering your API. |
CLAUDE_GISX_NO_UPDATE_CHECK | (unset) | Set to anything to skip the GitHub release check and its update notice. |
CLAUDE_GISX_REPO | Justar96/claude-gisx | Repo that update checks and downloads from. |
jq to pull fields like session_id, model.id, cost.total_cost_usd.\033[...) are honored.# ~/.claude/statusline-plugin.sh
#!/bin/bash
session_id=$(jq -r '.session_id // ""')
curl -s --max-time 1 \
-H "Authorization: Bearer $MY_API_TOKEN" \
"https://my.api.example.com/usage?session=$session_id" \
| jq -r '"[36mtokens[0m " + (.tokens_remaining|tostring) +
" [2m·[0m [33mbudget[0m $" + (.budget_remaining|tostring)'
chmod +x ~/.claude/statusline-plugin.sh
export CLAUDE_GISX_PLUGIN=~/.claude/statusline-plugin.sh
export CLAUDE_GISX_PLUGIN_CACHE=60 # poll your API at most once per minute
Requires Go 1.23+. Pure Go, no cgo, no external dependencies — go build produces a static binary in seconds.
git clone https://github.com/Justar96/claude-gisx
cd claude-gisx
make build # build for host platform → ./claude-gisx
make test # go test -race
make check # fmt-check + vet + test (what CI runs)
make dist # cross-compile all five release targets → dist/
make run # build and render with a sample stdin fixture
Releases ship linux-x64, linux-arm64, darwin-x64, darwin-arm64, and windows-x64 binaries (~6 MB each). Tag with v* to trigger .github/workflows/release.yml, which uploads the five binaries plus SHA256SUMS to GitHub Releases. Every push/PR runs .github/workflows/ci.yml (vet + gofmt + tests + smoke build).
hooks/register.ts 50 lines1import type { Register, SessionContextBreakdown } from 'claude-code'
2
3import { GISX } from './env'
4
5// The status line's payload carries the context window's totals but never
6// its make-up; /context's breakdown reaches mods alone. After each turn that
7// moved the window, this hands that breakdown to the gisx binary, which keeps
8// it beside the session for its next render. A `summary` breakdown is counted
9// locally from the last response's usage, so it costs no API call.
10
11export type Ingest = {
12 v: 1
13 session_id: string
14 max_tokens: number
15 total_tokens: number
16 auto_compact_threshold: number | null
17 categories: { name: string; tokens: number; kind: string; color: string }[]
18}
19
20export function toIngest(sessionId: string, b: SessionContextBreakdown): Ingest {
21 return {
22 v: 1,
23 session_id: sessionId,
24 max_tokens: b.rawMaxTokens,
25 total_tokens: b.totalTokens,
26 auto_compact_threshold: b.isAutoCompactEnabled ? (b.autoCompactThreshold ?? null) : null,
27 categories: b.categories.map(c => ({ name: c.name, tokens: c.tokens, kind: c.kind, color: c.color })),
28 }
29}
30
31export const register: Register = on => {
32 on('session.measure', async ($, e, next) => {
33 const result = await next(e)
34 if (!e.changed.includes('context')) {
35 return result
36 }
37 const usage = await $.session.usage({ breakdown: 'summary' })
38 const b = usage.context.breakdown
39 if (b === undefined) {
40 return result
41 }
42 const payload = toIngest(await $.session.id(), b)
43 const run = await $.process.run([GISX, 'ingest'], { stdin: JSON.stringify(payload), timeoutMs: 5000 })
44 if (run.exitCode !== 0) {
45 throw new Error(`${GISX} ingest exited ${run.exitCode}: ${run.stderr.trim()}`)
46 }
47 return result
48 })
49}
50hooks/env.ts 5 lines1// How the mod reaches the gisx binary. `claude-gisx setup` rewrites this file
2// with the absolute path of the binary it installed, so the mod never depends
3// on the PATH Claude Code was started with.
4export const GISX = 'claude-gisx'
5