README
ΒΆ
claude-status-line-go
Go CLI tool that reads Claude Code JSON from stdin and prints a formatted status line.
π okano.dev/claude-status-line-go
Output Format
π§ Opus 5Β·1M β π claude-status-line-go β πΏ feature/cache-stats β3
π‘5h ββββββββββ 83% βΊ 22m β CTX ββββββββββ 68% β Ξ£115k β277 β‘99% β π
7d ββββββββββ 74% βΊ 2d4h β $7.92
The token segment reads: Ξ£ total prompt size, β tokens generated, β‘ share of
the prompt served from the cache. Ξ£ is the sum of all three input fields Claude
Code reports β input_tokens counts only the part that missed the cache, so on a
warm conversation it can be a couple of tokens while the real prompt is 100k+.
β‘ counts cache reads only; a cold turn that writes 48k to the cache reports
0%, because those tokens were billed at a premium rather than served cheaply.
Both rate-limit windows render the same way β icon, progress bar, percentage and
time until reset β and share the limit_warn / limit_crit color thresholds.
The weekly window only appears once it reaches weekly_show_at (60% by default),
and its countdown switches to 2d4h form when the reset is more than a day out.
Installation
macOS and Linux
curl -fsSL https://okano.dev/claude-status-line-go/install.sh | sh
Works out your OS and architecture, resolves the newest release, checks the
SHA-256 against checksums.txt, and installs to /usr/local/bin β or
~/.local/bin if that isn't yours to write to. It never calls sudo.
Read it first if you'd rather.
Pin a version, or pick the destination:
curl -fsSL https://okano.dev/claude-status-line-go/install.sh | VERSION=1.4.0 INSTALL_DIR=~/bin sh
Prefer not to pipe into a shell?
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
ARCH=$(uname -m | sed 's/x86_64/amd64/; s/aarch64/arm64/')
VERSION=$(curl -fsSLI -o /dev/null -w '%{url_effective}' \
https://github.com/williamokano/claude-status-line-go/releases/latest | sed 's#.*/v##')
FILE="claude-status-line-go_${VERSION}_${OS}_${ARCH}.tar.gz"
BASE="https://github.com/williamokano/claude-status-line-go/releases/download/v${VERSION}"
curl -fsSLO "${BASE}/${FILE}"
curl -fsSLO "${BASE}/checksums.txt"
grep " ${FILE}$" checksums.txt | sha256sum -c - # macOS: shasum -a 256 -c -
tar -xzf "$FILE" claude-status-line-go
sudo install -m 755 claude-status-line-go /usr/local/bin/
Windows
The Windows archive holds claude-status-line-go.exe. tar ships with Windows
10 build 17063 and later, so nothing else is needed.
$ErrorActionPreference = 'Stop'
$ProgressPreference = 'SilentlyContinue' # or Invoke-WebRequest crawls
$Repo = 'williamokano/claude-status-line-go'
$Version = (Invoke-RestMethod "https://api.github.com/repos/$Repo/releases/latest").tag_name.TrimStart('v')
$Arch = if ($env:PROCESSOR_ARCHITECTURE -eq 'ARM64') { 'arm64' } else { 'amd64' }
$File = "claude-status-line-go_${Version}_windows_${Arch}.tar.gz"
$Base = "https://github.com/$Repo/releases/download/v$Version"
Invoke-WebRequest "$Base/$File" -OutFile $File
Invoke-WebRequest "$Base/checksums.txt" -OutFile checksums.txt
$want = (Select-String checksums.txt -Pattern ([regex]::Escape($File))).Line.Split(' ')[0]
$got = (Get-FileHash $File -Algorithm SHA256).Hash.ToLower()
if ($want -ne $got) { throw 'checksum mismatch' }
$Dest = "$env:LOCALAPPDATA\Programs\claude-status-line-go"
New-Item -ItemType Directory -Force -Path $Dest | Out-Null
tar -xzf $File -C $Dest claude-status-line-go.exe
Add $Dest to your PATH to finish. There's no Chocolatey or WinGet package β
both want a published, moderated manifest rather than a link to a release.
Using go install
go install github.com/williamokano/claude-status-line-go/cmd/claude-status-line-go@latest
Because @latest resolves through the Go module proxy, a freshly pushed tag can
take a few minutes to become visible. Check what you actually got with
claude-status-line-go --version.
From Source
git clone https://github.com/williamokano/claude-status-line-go.git
cd claude-status-line-go
make build
Usage
As Claude Code Status Line
Automatic (recommended)
claude-status-line-go install
This registers the binary as the statusLine command in ~/.claude/settings.json,
preserving any other settings already in that file. Use --project to register it
in the current project's .claude/settings.json instead:
claude-status-line-go install --project
Manual
Add to your Claude Code settings (~/.claude/settings.json or project .claude/settings.json):
{
"statusLine": {
"type": "command",
"command": "claude-status-line-go"
}
}
Standalone
echo '{"model":...}' | claude-status-line-go
CLI Commands & Flags
| Command | Description |
|---|---|
install |
Register this binary as the Claude Code status line |
config init |
Write the commented default config, ready to edit |
config path |
Print the config file path |
plugins |
Show each plugin's state and what it renders |
plugins refresh [name] |
Run command plugins now, in the foreground |
plugins clean |
Drop cache entries that can't be useful any more |
| Flag | Description |
|---|---|
-h, --help |
Print usage information |
-v, --version |
Print version |
--no-color |
Disable ANSI color output |
--completion bash|zsh|fish |
Print shell completion script |
--project |
With install: register in ./.claude/settings.json instead of the global settings |
Environment Variables
| Variable | Default | Description |
|---|---|---|
CSL_SHOW_COST |
true |
Show session cost |
CSL_SHOW_WEEKLY |
true |
Show weekly usage |
CSL_SHOW_TOKENS |
true |
Show token counts |
CSL_SHOW_GIT |
true |
Show git branch |
CSL_SHOW_GIT_DIRTY |
true |
Show dirty file count |
CSL_BAR_SIZE |
10 |
Progress bar width |
CSL_LIMIT_WARN |
60 |
Rate limit warning threshold (%), 5-hour and weekly |
CSL_LIMIT_CRIT |
85 |
Rate limit critical threshold (%), 5-hour and weekly |
CSL_CTX_WARN |
60 |
Context warning threshold (%) |
CSL_CTX_CRIT |
85 |
Context critical threshold (%) |
CSL_WEEKLY_SHOW_AT |
60 |
Show weekly when >= this % |
CSL_FORMAT |
β | Custom output format template |
NO_COLOR |
β | Set to any value to disable ANSI colors |
Configuration File
You don't need one β every default is sensible and the tool works with no config at all. When you do want to change something, don't go hunting for the path:
claude-status-line-go config init
That writes a fully commented file with every option and two ready-to-uncomment
plugin examples. Everything in it is already the default, so an untouched file
changes nothing. It won't overwrite an existing config unless you pass
--force.
config path prints the location on its own, so it composes:
nvim $(claude-status-line-go config path)
The file lives at ~/.config/claude-status-line-go/claude-status-line.yaml,
or under $XDG_CONFIG_HOME if you've set one:
show_cost: true
show_weekly: true
show_tokens: true
show_git: true
show_git_dirty: true
bar_size: 10
limit_warn: 60
limit_crit: 85
ctx_warn: 60
ctx_crit: 85
weekly_show_at: 60
Precedence is defaults, then this file, then the environment β so a CSL_*
variable always wins over the file.
Plugins
Plugins add your own segments. A plugin reports data β a value, an optional
maximum, maybe a label β and this tool draws it. That way your segment gets the
same bar glyphs, the same bar_size and the same NO_COLOR handling as the
built-in ones, instead of every plugin reinventing bars and colours slightly
differently.
plugins:
- name: issues
command: >-
printf '{"value":%s,"max":100}'
"$(gh issue list --json number --jq 'length')"
interval: 60s
icon: "π―"
bar: true
thresholds:
- { at: 0, color: green }
- { at: 40, color: yellow }
- { at: 70, color: red }
β¦ β Ξ£115k β277 β‘99% β π― ββββββββββ 31/100 β $7.92
A real one, start to finish
Commits you haven't pushed. No API tokens and no setup β it works in any git
repo, and hide_when: zero keeps it invisible until you're actually behind.
plugins:
- name: unpushed
command: >-
printf '{"value":%s,"max":20,"label":"unpushed"}'
"$(git log --oneline @{u}.. 2>/dev/null | wc -l)"
interval: 30s
icon: "β¬"
bar: true
hide_when: zero
thresholds:
- { at: 0, color: green }
- { at: 25, color: yellow }
- { at: 50, color: red }
π‘5h ββββββββββ 0% β CTX ββββββββββ 0% β β¬ ββββββββββ 3/20 β $0.00
π‘5h ββββββββββ 0% β CTX ββββββββββ 0% β $0.00
The second line is the same plugin after pushing. Outside a git repo, or on a branch with no upstream, the count is zero and the segment stays hidden β no error and no noise.
What a slow command actually does
Five consecutive renders with a command that takes 800 ms:
| Render | Time | Segment | What happened |
|---|---|---|---|
| 1 | 4 ms | (nothing) | No cached value. The render hands the command to a detached process and exits without waiting |
| 2 | 5 ms | π§© ββββββββββ 1/10 |
The background run finished, so there's something to draw |
| 3 | 3 ms | π§© ββββββββββ 1/10 |
Inside interval β nothing is spawned, this is a file read and no more |
| 4 | 5 ms | π§© ββββββββββ 1/10 |
Past interval. The old value is drawn at once and a refresh starts behind it |
| 5 | 5 ms | π§© ββββββββββ 2/10 |
The refresh landed |
The trade is that a number can be up to interval old β never that the status
line is slow. If the command fails, the last good value stays on screen rather
than the segment vanishing; if it has never succeeded, there's nothing to draw.
Sources
| Key | Description |
|---|---|
file |
A path read at render time. Costs nothing β ideal when something already writes the data, such as a Claude Code hook |
command |
A shell command. Never runs on the render path β see below |
A command's result is cached and refreshed out of band. A render reads the cache
and, if it's older than interval, hands the work to a detached process and
draws the stale value immediately. A 350 ms gh call therefore costs the status
line nothing; it only means the number can be up to interval old.
The command gets the Claude Code JSON on stdin, runs in the project directory,
and has CSL_PROJECT_DIR and CSL_PLUGIN_NAME set. Its cache is keyed per
project, so task counts and PR state don't leak between repos. If it fails, the
last good value stays on screen and the error goes to stderr.
Checking a plugin
Claude Code discards this tool's stderr, so a plugin that fails has nowhere to say so and simply doesn't appear. Ask instead:
claude-status-line-go plugins
issues [command]
source printf '{"value":31,"max":100}'
state ok, cached 1s ago
renders π― ββββββββββ 31/100
broken [command]
source echo 'oh no' >&2; exit 3
state no data β the command has not run yet, so the next render will start it
claude-status-line-go plugins refresh [name] runs command plugins in the
foreground and reports what went wrong:
β issues
β broken: plugin "broken": exit status 3: oh no
Cached values
Command results are cached per plugin and per project, so they accumulate as you move between repos. They're pruned automatically β every background refresh also drops entries for plugins no longer in your config, entries untouched for over two weeks, and abandoned lock files. To do it on demand:
claude-status-line-go plugins clean
Removed 1 entry for plugins no longer configured: deleted-plugin
Removed 1 entry untouched for over 14 days: keep
Deleting a cached value costs nothing but one refresh the next time you're in that project.
Limits
Plugin output is capped at 64 KB, and a single rendered segment at 120
characters β a plugin that accidentally prints a file shouldn't take the
terminal with it. Names must be unique: they key the cache file and the
placeholders. Setting both file and command uses the command and warns.
Output contract
Emit a JSON object on stdout:
{"value": 12, "max": 33, "label": "aplicar"}
| Field | Description |
|---|---|
value |
The number. With max, drives the percentage, bar and thresholds |
max |
Optional. Without it the segment is just text β no bar, no colour |
text |
Overrides the auto-formatted value/max |
label |
A short name, available to display |
state |
ok, warn or crit β skips threshold resolution |
hide |
true to drop the segment this render |
raw |
Pre-rendered output, ANSI and all. Opts out of everything above |
Bare text works too and is taken as text, so echo "12 left" is a valid
plugin. Any key not listed above is reachable as {plugin.<name>.<key>} β
that's how one plugin reports several pieces of information.
Options
| Key | Default | Description |
|---|---|---|
name |
β | Required. Names the placeholders |
icon |
β | Prefix glyph |
bar |
false |
Draw a progress bar, using the global bar_size |
display |
β | Layout template, e.g. "{icon} {label} {bar} {value}/{max}" |
thresholds |
β | Colour stops, see below |
hide_when |
β | zero drops the segment when the value is 0; full drops it when the value reaches max |
interval |
60s |
How long a command result stays fresh |
timeout |
5s |
Caps one run of a command |
hide_when is for counters that aren't worth the space at rest β nothing left
to do, or everything done:
- name: todo
file: .claude/todo.json
icon: "π―"
bar: true
hide_when: zero # no open items, so say nothing
thresholds is a list, where at is a lower bound in percent and each colour
applies upward. There's no "invert" flag β for a ramp where more is better, list
the colours the other way round:
thresholds:
- { at: 0, color: red }
- { at: 31, color: yellow }
- { at: 61, color: green }
Placeholders
Plugin segments append to the default layout in declaration order, just before
cost. To place them yourself, use format:
| Placeholder | Description |
|---|---|
{plugin.<name>} |
The finished segment |
{plugin.<name>.value} |
Reported value |
{plugin.<name>.max} |
Reported maximum |
{plugin.<name>.pct} |
Percentage |
{plugin.<name>.bar} |
Progress bar |
{plugin.<name>.text} |
Text |
{plugin.<name>.label} |
Label |
{plugin.<name>.color} |
Resolved colour name |
{plugin.<name>.<key>} |
Any extra key the plugin reported |
Custom Output Format
Use the CSL_FORMAT env var or format config field to customize the output.
Available placeholders:
| Placeholder | Description |
|---|---|
{model} |
Model name as reported by Claude Code (e.g. Opus 4.8, Sonnet 5) |
{ctx_size} |
Context window size (200k, 1M) |
{project} |
Project folder name |
{branch} |
Git branch name |
{dirty} |
Dirty file count (β3) |
{limit_bar} |
5-hour rate limit progress bar |
{limit_pct} |
5-hour rate limit percentage |
{limit_color} |
5-hour rate limit color ANSI code |
{limit_reset} |
Time until the 5-hour limit resets |
{ctx_bar} |
Context progress bar |
{ctx_pct} |
Context percentage |
{ctx_color} |
Context color ANSI code |
{tokens_total} |
Total prompt tokens β uncached + cache creation + cache read |
{tokens_out} |
Output tokens |
{cache_hit_pct} |
Share of the prompt served from cache (cache reads / total) |
{tokens_in} |
Uncached input tokens only β the part that missed the cache |
{tokens_cache} |
Cache tokens (creation + read) |
{cost} |
Session cost |
{weekly_bar} |
Weekly usage progress bar |
{weekly_pct} |
Weekly usage percentage |
{weekly_color} |
Weekly usage color ANSI code |
{weekly_reset} |
Time until the weekly limit resets |
{reset}, {dim}, {bold} |
ANSI format codes |
{red}, {green}, {yellow} |
ANSI color codes |
{blue}, {magenta}, {cyan}, {white} |
ANSI color codes |
Example:
format: "{cyan}π§ {model}Β·{ctx_size}{reset} {dim}β π {project}{reset} {dim}β πΏ {branch}{reset}{dirty}"
Shell Completions
eval "$(claude-status-line-go --completion bash)" # Bash
eval "$(claude-status-line-go --completion zsh)" # Zsh
claude-status-line-go --completion fish | source # Fish
Development
make build # Build the binary
make test # Run tests
make lint # Lint code
make clean # Clean artifacts
Directories
ΒΆ
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
claude-status-line-go
command
|
|
|
internal
|
|
|
installer
Package installer registers claude-status-line-go as the Claude Code status line by writing the "statusLine" key into a settings.json file, preserving any other keys already present.
|
Package installer registers claude-status-line-go as the Claude Code status line by writing the "statusLine" key into a settings.json file, preserving any other keys already present. |
|
plugin
Package plugin turns user-supplied data sources into status line segments.
|
Package plugin turns user-supplied data sources into status line segments. |