claude-readout

module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Aug 20, 2026 License: MIT

README

claude-readout

CI

A status line for Claude Code that renders correctly in the terminal it is actually running in — including the one without a Nerd Font, and the one on a light background — and can prove it.

◆ Opus 5   ~/…/a-fairly-long-project-name feature/a-descriptive-branch-name +3 ~7 ?2 !1   #428  │  ctx ████████▒▒▒▒▒▒  62%   $4.27   5h ██████▒▒  78% →2h

Claude Code hands a JSON blob on stdin to whatever statusLine.command names and prints what comes back. That is the whole contract. Everything interesting is in what you do with a hundred columns — and in noticing that you may not have a hundred columns, a Nerd Font, or a dark background.

Why another one

There are at least ten of these. Writing an eleventh because the tenth had colours you didn't like is not a project. The wedge is narrower and it came out of debugging a real status line on a real machine:

Status lines in this space tend to assume a dark terminal and a Nerd Font. When either assumption is wrong they render broken, and the user cannot tell why.

Two measured failures from that session, both now regression tests:

The font. The terminal was set to Monaco. Parsing Monaco's cmap table directly shows it maps (U+2588) and (U+2592) and none of the eighth-blocks ▏▎▍▌▋▊▉, and not (U+2591). A progress bar built from plus is therefore drawn from two different fonts at two different advance widths, and comes out visibly jagged. Nothing tells you.

The background. iTerm2 profiles carry both a Background Color and a Background Color (Dark). With macOS in Dark mode the second one is what is on screen. A detector that reads the obvious key returns light for a terminal whose real background is #14191E, and then paints near-black text onto near-black. Meanwhile COLORFGBG=15;0 — published by the terminal at runtime — was right the whole time.

So: runtime signals outrank configuration files, and glyph availability is a property you measure rather than assume. claude-readout doctor does both and shows its work; docs/CAPABILITIES.md is the resulting terminal-and-font matrix.

claude-readout
Picks the palette from an OSC 11 query to the real terminal, then COLORFGBG, then the terminal's own config file, then dark
Picks the glyphs from the configured font's own cmap table, parsed directly — four tiers, unicode by default
Cost per render ~2–3 ms per invocation, process start included; the render itself is well under a millisecond warm. Single static binary, no runtime
Runtime network access none, ever
Tells you why it chose doctor — evidence and consequence on every line

The alternatives are worth knowing about, and some of them do things this one does not. As of August 2026: ccstatusline (~12.5k stars) has by far the largest user base and an interactive configuration editor; CCometixLine (~3.4k) is, like this one, a compiled binary rather than a Node program; and claude-powerline (~1.2k) does a powerline look properly. Capability detection — measuring the font and probing the background instead of assuming both — is what this one is for.

Install

v0.1.0 has not been tagged yet. Until it is, the Homebrew, curl and Scoop/winget channels below have nothing to download — build from source with go install. See RELEASING.md.

Homebrew:

brew install SteveVitali/tap/claude-readout

curl (macOS and Linux; downloads the release binary and verifies its checksum):

curl -fsSL https://raw.githubusercontent.com/SteveVitali/claude-readout/main/install.sh | sh

Pass options through the pipe with sh -s --, e.g. | sh -s -- --dry-run to see the plan without writing anything, or | sh -s -- --prefix ~/bin.

Go:

go install github.com/SteveVitali/claude-readout/cmd/claude-readout@latest

Windows builds ship in the release (windows/amd64, windows/arm64) and the Scoop and winget manifests are generated on every release run, but they are not published yet — on Windows, download the .zip from the releases page or use go install.

Quick start

Three commands, in the order they answer the three questions:

claude-readout init      # measure this terminal, write the config, patch settings.json
claude-readout preview   # see the result without restarting Claude Code
claude-readout doctor    # what does this terminal actually do, and why

init is the only command that touches Claude Code's settings, and init --dry-run tells you exactly what it would change first. See What it touches for everything any command reads or writes.

Commands

Command What it does
render the status line itself: reads the payload on stdin, writes one line. The hot path
init probe the terminal, cache the answers, write a config if there is none, and point statusLine.command at this binary. Idempotent; backs up settings.json first. --dry-run, --force, --json, --uninstall
doctor the full diagnosis: Claude Code, terminal, font, config, render cost — each line with its evidence. --json for CI, --fix for the safe remediations, --payload to check a real payload against the schema
preview draw the line from built-in sample sessions. --all-themes, --width N (repeatable), --glyphs TIER, --state NAME
theme list | show | probe list palettes, show one as this terminal renders it, or run the OSC 11 query and cache the answer
config path | edit | validate | dump where the config comes from, edit it, check it, or print it fully resolved
version version, commit, build date. --json
completion bash, zsh, fish or powershell completions

Global flags: --config <file>, --no-color.

What it detects

Four independent axes. Each has a ladder, a cached answer and an explicit override — detection is a default, never a cage. Anything you pin in config wins over anything detected.

Background → light or dark palette. theme.appearance in config, then an OSC 11 query to the terminal (cached for 24 hours, because render has no terminal to ask), then COLORFGBG, then the terminal's own configuration file, then dark.

Colour depth → truecolor / 256 / 16 / none. NO_COLOR first and absolutely, then theme.color_depth, then COLORTERM, then TERM. Palettes are authored once in truecolor and quantised down.

Glyph tier → what the font can actually draw. The terminal's configured font is resolved, its cmap table is parsed directly, and the codepoints each tier needs are tested. Cached for a week. If the font cannot be resolved the answer is unicode, not ascii.

Tier Meter Divider Needs
ascii ########=----- | nothing
unicode (default) ████████▒▒▒▒▒▒ U+2588, U+2592, U+2502
extended ████████▋░░░░░ U+2588, U+2592, U+2502, U+258F…U+2589, U+2591
nerdfont as extended, plus branch/model/PR icons powerline U+2588, U+2592, U+2502, U+258F…U+2589, U+2591, U+E0A0, U+E0B0, U+E0B1, U+F219, U+F407

Width. layout.width, then CLAUDE_READOUT_WIDTH, then COLUMNS, then ioctl(TIOCGWINSZ) on /dev/tty, then 100 — less Claude Code's padding and a four-column safety margin. doctor reports which rung answered, because a wrong width is the difference between an elegant line and a wrapped one.

Configuration

TOML at ${XDG_CONFIG_HOME:-~/.config}/claude-readout/config.toml. Everything is optional and every default equals the behaviour you get with no file at all. claude-readout config edit writes a fully-commented starter file the first time; claude-readout config dump prints the resolved result.

preset = "default"            # default | compact | minimal | powerline | ascii

[theme]
appearance  = "auto"          # auto | light | dark
palette     = "primer"
color_depth = "auto"          # auto | truecolor | 256 | 16 | none
glyphs      = "auto"          # auto | ascii | unicode | extended | nerdfont

[layout]
width        = "auto"         # "auto" or a column count
zone_divider = true
jitter_guard = true           # pad numeric fields to their maximum width

[meter]
context_cells = 14
limit_cells   = 8
warn_at       = 60
danger_at     = 85
style         = "shade"       # shade | solid | rule | none

# Segments are an ordered list; the order here is the order on the line.
# `rank` is sacrifice order under width pressure — higher goes first, and
# rank 0 is never dropped.
[[segment]]
kind = "model"
zone = "L"
rank = 0
show_context_size = true

[[segment]]
kind = "path"
zone = "L"
rank = 20
style = "leaf-emphasis"
max_width = 26

[[segment]]
kind = "context"
zone = "R"
rank = 0
label = "ctx"

# The escape hatch: anything the built-ins do not cover.
[[segment]]
kind = "custom"
zone = "R"
rank = 95
command = "tmux display-message -p '#S'"
timeout_ms = 50               # hard-killed, so a slow segment cannot
cache_ttl_s = 10              # stall the footer

Segment kinds: model, path, git, pr, context, cost, rate_limit, renew, custom.

Precedence, lowest to highest: built-in defaults → the preset they name → your config file → the project's .claude-readout.toml (searched upward from the working directory) → the environment → flags. The four capability axes are the one exception: a value pinned in config outranks the environment, because detection is a default rather than a cage.

Environment overrides: CLAUDE_READOUT_CONFIG, CLAUDE_READOUT_PRESET, CLAUDE_READOUT_THEME, CLAUDE_READOUT_PALETTE, CLAUDE_READOUT_COLOR_DEPTH, CLAUDE_READOUT_GLYPHS, CLAUDE_READOUT_WIDTH, CLAUDE_READOUT_ZONE_DIVIDER, CLAUDE_READOUT_JITTER_GUARD, CLAUDE_READOUT_METER_STYLE. Plus NO_COLOR, which wins over all of them.

What it touches

MIT licensed, no telemetry, no analytics, no update check, and no network access at runtime, ever — enforced by a test that fails if any package in the binary's dependency graph is a network client. The one nuance: a custom segment runs the command you put in your config, so that promise is about what the binary does on its own account.

Reads, on the hot path — every render:

  • the JSON payload on stdin, from Claude Code
  • ${XDG_CONFIG_HOME:-~/.config}/claude-readout/config.toml, and .claude-readout.toml from the working directory upward
  • ${XDG_CACHE_HOME:-~/.cache}/claude-readout/ — the cached background and glyph-tier answers
  • the terminal's own configuration file, but only when the background cache and COLORFGBG have both missed: com.googlecode.iterm2.plist, ~/Library/Preferences/.GlobalPreferences.plist, kitty.conf, ghostty/config, alacritty.toml, or Windows Terminal's settings.json, whichever matches the terminal you are in
  • ~/.claude.json, for the subscription renewal date behind the renew segment
  • runs git status --porcelain=v2 in the session's directory, cached for 3 seconds

Reads, only in the commands you run yourself — doctor, init, theme probe:

  • ~/.claude/settings.json, to report or patch statusLine
  • the resolved font file, to parse its cmap table — plus one fc-match subprocess on Linux. render never opens a font file; it reads the tier these commands cached.

Writes, ever:

  • ${XDG_CACHE_HOME:-~/.cache}/claude-readout/doctor, init and theme probe write the background and tier they measure; render and the git and custom readers write their own short-lived entries
  • ${XDG_CONFIG_HOME:-~/.config}/claude-readout/config.tomlinit, config edit, and doctor --fix when it pins a key it recommends
  • ~/.claude/settings.jsoninit only, backed up first, and only the statusLine entry. Every other key, including every other key inside statusLine, comes out exactly as it went in.

Caveats

Honest ones, because the pitch is honesty about capability:

  • render cannot ask the terminal anything. Claude Code spawns it without a tty, so the OSC 11 background query and the font measurement happen in doctor, init and theme probe, and render reads the cache they leave. Run init once per terminal. doctor's own cold row will exceed the 10 ms budget for exactly this reason — the budget is about the warm path, and the report says so.
  • tmux and screen may swallow the OSC 11 query, or answer it themselves rather than passing it to the host terminal. The probe has a 250 ms timeout and falls through to COLORFGBG; doctor says when it is inside a multiplexer and recommends pinning theme.appearance.
  • Windows Terminal's colour scheme is not parsed. Its font is read from settings.json, but resolving a scheme name through its theme tables is real work that has not been done — and it answers OSC 11, which is a better signal anyway.
  • VS Code's terminal.integrated.fontFamily is not read, so a VS Code terminal falls back to the platform default font for the glyph measurement. It answers OSC 11 for the background.
  • WezTerm's .wezterm.lua is not parsed — it is a program, not a config file, and running a user's Lua to learn a colour is not a trade this makes. WezTerm answers OSC 11.
  • doctor writes the caches it reports. That is the point of running it, but it is a side effect worth stating. doctor --fix deliberately will not touch Claude Code's settings.json: installing the status line is init's job and it should be a thing you asked for.
  • preview does not run custom segments, on purpose — previewing a configuration should not execute the commands in it.
  • One contrast shortfall, recorded rather than waived. The light palette's amber fill measures 2.46:1 against its own rendered track in truecolor and 2.19:1 at 256 colours, under the 2.5:1 the design rules set. It is inherited from the reference implementation and pinned by the byte-identity corpus. It is not dangerous — bar length encodes the value redundantly — but it is real, and a unit test holds it at that measured value so it cannot quietly get worse.
  • At 256 colours and below, the dark palette's accent and its danger text quantise to the same index. Authoring once and quantising down makes that inevitable; boldness is what keeps them apart.
  • On Windows, doctor and init print filesystem paths with forward slashes, including in --json finding values. That is deliberate: Git Bash eats unquoted backslashes, so the statusLine.command init writes must use forward slashes, and a report that tidied them away would be describing a command that does not work.

Uninstall

claude-readout init --uninstall   # remove the statusLine entry

That deletes the statusLine entry from ~/.claude/settings.json and leaves every other key exactly as it was. It refuses if the status line configured is not this program's — pass --force if you mean it anyway.

It does not put back a status line you had before installing — it removes this one and leaves you to restore the other. settings.json.bak is written once, the first time claude-readout touches the file, and is never overwritten afterwards, so it always holds the configuration you had before your first init. To put that configuration back:

cp ~/.claude/settings.json.bak ~/.claude/settings.json

--uninstall deliberately leaves your config file and the cache directory alone — remove them too if you want nothing left:

rm -rf ~/.config/claude-readout ~/.cache/claude-readout
rm -f ~/.local/bin/claude-readout    # or: brew uninstall claude-readout

Using as an Agent Skill

The repo doubles as a skill directory (SKILL.md), so Claude Code can drive it conversationally:

ln -s /path/to/claude-readout ~/.claude/skills/claude-readout

Then "my status line looks broken" or "set up claude-readout" in any conversation.

Development

go test ./...       # includes the 112-case byte-identity corpus
go vet ./...
gofmt -l .

CI runs the suite on Linux, macOS and Windows — all three blocking — plus a cross-compile of all six release targets, shellcheck, a p99 < 10 ms render budget gate, and a full release rehearsal that publishes nothing.

See CONTRIBUTING.md for the walkthrough (including how to add a segment), docs/ARCHITECTURE.md for the design and why each decision went the way it did, docs/CAPABILITIES.md for the terminal and font matrix, and RELEASING.md for the release process.

A companion to claude-hibernate, which hibernates and wakes Claude Code sessions across reboots.

Community project. Not affiliated with or endorsed by Anthropic. "Claude" is Anthropic's trademark.

License

MIT

Directories

Path Synopsis
cmd
claude-readout command
Command claude-readout is a status line for Claude Code that renders correctly in the terminal it is actually running in.
Command claude-readout is a status line for Claude Code that renders correctly in the terminal it is actually running in.
internal
cache
Package cache is the one place this program writes to disk on the hot path.
Package cache is the one place this program writes to disk on the hot path.
caps
Package caps carries what this terminal can actually do (§6).
Package caps carries what this terminal can actually do (§6).
claudecode
Package claudecode is what this program knows about the program that runs it: where Claude Code keeps its settings, what the status line entry in them means, and the one trap §7.1 describes.
Package claudecode is what this program knows about the program that runs it: where Claude Code keeps its settings, what the status line entry in them means, and the one trap §7.1 describes.
claudeconf
Package claudeconf reads the facts Claude Code already records locally, so the status line does not have to ask anyone for them.
Package claudeconf reads the facts Claude Code already records locally, so the status line does not have to ask anyone for them.
config
Package config is §8: the TOML file, the precedence chain that layers it, and the resolved value the rest of the program reads.
Package config is §8: the TOML file, the precedence chain that layers it, and the resolved value the rest of the program reads.
corpus
Package corpus loads the frozen reference corpus: 112 cases captured from the Python prototype that this port reproduces byte-for-byte.
Package corpus loads the frozen reference corpus: 112 cases captured from the Python prototype that this port reproduces byte-for-byte.
distcheck
Package distcheck has no code.
Package distcheck has no code.
docscheck
Package docscheck has no code.
Package docscheck has no code.
doctor
Package doctor is the diagnosis (§10): what this terminal can do, what Claude Code was told to run, what the configuration says, and what the line actually costs to draw — each with the evidence that produced it and the consequence of it being what it is.
Package doctor is the diagnosis (§10): what this terminal can do, what Claude Code was told to run, what the configuration says, and what the line actually costs to draw — each with the evidence that produced it and the consequence of it being what it is.
e2e
Package e2e drives the shipped binary through one whole install lifecycle.
Package e2e drives the shipped binary through one whole install lifecycle.
fileedit
Package fileedit writes files that belong to somebody else.
Package fileedit writes files that belong to somebody else.
git
Package git reads the repository state the status line shows.
Package git reads the repository state the status line shows.
layout
Package layout is the fitter: zones, groups, ranks, and the arithmetic that makes one line fit one terminal.
Package layout is the fitter: zones, groups, ranks, and the arithmetic that makes one line fit one terminal.
paint
Package paint is the only stage that knows about ANSI (§5.2).
Package paint is the only stage that knows about ANSI (§5.2).
preview
Package preview draws the status line without Claude Code (§9, §14).
Package preview draws the status line without Claude Code (§9, §14).
render
Package render composes the pipeline: payload plus capabilities in, one line of ANSI out (§5.2).
Package render composes the pipeline: payload plus capabilities in, one line of ANSI out (§5.2).
sample
Package sample is the built-in sessions this program renders when there is no real one: `doctor`'s measurement of what the line costs, and `preview`'s answer to "what will this look like".
Package sample is the built-in sessions this program renders when there is no real one: `doctor`'s measurement of what the line costs, and `preview`'s answer to "what will this look like".
schema
Package schema decodes the JSON blob Claude Code hands the status line on stdin.
Package schema decodes the JSON blob Claude Code hands the status line on stdin.
segment
Package segment turns the decoded payload into priced pieces of the line.
Package segment turns the decoded payload into priced pieces of the line.
setup
Package setup is `init` (§9): the one command that changes a user's machine on purpose.
Package setup is `init` (§9): the one command that changes a user's machine on purpose.
textwidth
Package textwidth measures text the way a terminal draws it: in columns.
Package textwidth measures text the way a terminal draws it: in columns.
theme
Package theme holds the built-in palettes.
Package theme holds the built-in palettes.

Jump to

Keyboard shortcuts

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