statusline

module
v1.2.0 Latest Latest
Warning

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

Go to latest
Published: Jul 14, 2026 License: Apache-2.0

README

statusline

statusline rendering a full Claude Code session in the claude-dark theme

CI status CodeQL status Go version License

A single-binary Claude Code statusLine command written in Go. Flat, no-background segments joined by a thin Nerd Font chevron divider, truecolor gradients, a palette that automatically follows Claude Code's own dark/light theme setting, and an optional TOML config file for anyone who wants to tweak it further.

(The image above is a real render — statusline demo --theme dark --scenario full — captured as an image since GitHub can't render truecolor ANSI escape codes or Nerd Font glyphs inside a Markdown code block.)

Contents

Install

From source:

git clone https://github.com/scrothers/statusline.git
cd statusline
make build      # produces ./statusline

Or, once a release exists:

go install github.com/scrothers/statusline/cmd/statusline@latest

Preview it

statusline demo renders built-in sample payloads directly — no need to craft JSON fixtures or wire up Claude Code first:

./statusline demo                                   # "full" scenario, both dark and light
./statusline demo --theme light                      # one base only
./statusline demo --theme dark --scenario minimal    # early-session look
./statusline demo --scenario narrow --columns 40     # test width-based truncation

Scenarios: minimal (early session, no git repo yet), full (every segment at once — dirty repo, open PR, context/cost/rate-limit/cache data, a named session, a worktree), narrow (the full payload rendered at 30 columns, to see which segments drop first).

Configure Claude Code

Add to ~/.claude/settings.json:

{
  "statusLine": {
    "type": "command",
    "command": "~/code/statusline/statusline",
    "padding": 2,
    "refreshInterval": 2,
    "hideVimModeIndicator": true
  }
}
  • refreshInterval: 2 keeps the clock-style duration, cost, and context bar ticking during long idle stretches (a slow tool call, a background subagent), not just after each assistant message.
  • hideVimModeIndicator: true suppresses Claude Code's built-in -- INSERT -- text, since the vim badge already shows the mode when enabled in config (see Segments).

Segments

Three lines, each answering one question. Every segment is plain colored text — no background is ever painted, so lines read as flat, breathing text joined by a thin chevron divider, not powerline pills.

Line Segments Notes
1 — Claude model, provider badge, billing badge, thinking, effort, context window, rate limits (5h/7d), cache The model's own state: what it's running, how hard, and how much room/budget is left.
2 — session session name, directory, lines added/removed, token counts, cost, duration Omitted fields (no custom name, no diff yet) just don't appear.
3 — git repo (host/owner/name), open PR (number + review state), branch + status, worktree The whole line disappears outside a git repository.

Segments not in the default layout but still available via custom config: vim (vim mode), agent (subagent name), output_style. These render with no background either — they just aren't wired into any line by default.

Under width pressure, segments drop in priority order (lowest first) until a line fits; model and directory never drop, only self-truncate.

model's icon is colored per model family — Opus, Sonnet, Haiku, Fable, and Mythos each get their own theme-defined accent (see Themes) instead of one flat color, so the tier you're running is visible at a glance; an id that doesn't decode to one of those five falls back to the theme's general identity accent.

provider renders a small badge next to model (AWS/GCP/Azure/Cloudflare/DigitalOcean/Router/Gateway icon) identifying which route the model traffic took, resolved in three tiers:

  1. An explicit provider = "aws"/"gcp"/"azure"/"cloudflare"/ "digitalocean"/"router"/"gateway" in config always wins.
  2. Otherwise, Claude Code's own routing environment variables (inherited from the parent process) — CLAUDE_CODE_USE_BEDROCK/ANTHROPIC_BEDROCK_*/ ANTHROPIC_AWS_* for AWS, CLAUDE_CODE_USE_VERTEX/ANTHROPIC_VERTEX_* for GCP, ANTHROPIC_FOUNDRY_* for Azure, or the destination host of a non-default ANTHROPIC_BASE_URL — matched against known gateway-product domains (Cloudflare AI Gateway's gateway.ai.cloudflare.com, DigitalOcean's inference.do-ai.run/*.ondigitalocean.app) before falling back to a generic gateway badge for anything else. Host matching is a proper dot-boundary suffix match on the parsed URL, not a raw substring search, so a lookalike like notcloudflare.com or api.anthropic.com.evil.example never false-positives. This env-var tier is the only reliable way to detect Azure, Cloudflare, DigitalOcean, or a bare corporate-relayed id at all — none of them carry any distinguishing shape in model.id itself (a gateway that just relays a renamed id like claude-4-8-opus is structurally identical to a legitimate legacy Anthropic id like claude-3-opus).
  3. Last resort: the model.id shape itself — a Bedrock anthropic. prefix or ARN, a Vertex @date suffix or resource path, or a vendor/model prefix from an OpenRouter-style aggregator.

Silent when none of the three find anything, which is the common plain-first-party case.

billing renders a small API-icon badge next to provider when the session is being metered per-token through the API rather than drawn against a Claude subscription plan. The payload's rate_limits object is populated only for Claude.ai subscribers, and only after their first API response in the session, so the badge fires on cost being present (proof a turn has already completed) while rate_limits is still absent — distinguishing genuine API billing from a subscriber's early-session window before their first rate-limit snapshot has arrived. Silent for subscription sessions and for a session with no completed turn yet.

effort is the one segment colored by intensity rather than theme: its icon escalates from an empty gauge (low) through a full gauge (xhigh) to fire (max) and an alert fire (ultra), and its color runs a fixed green → red → purple scale independent of the active theme, so "getting hotter" reads the same everywhere.

context_window and the two ratelimit_* gauges share the same bar treatment: context_window's width scales with the detected terminal width (clamped between 8 and 24 cells); the rate-limit bars stay a fixed, narrower 6 cells and are explicitly labeled 5h/7d so the two aren't distinguishable by icon alone. On all three, each bar cell's color is fixed by its position along the bar — green on the left sliding through warning to danger on the right — so filling the bar reveals more of a stable on-screen gradient from the left rather than shifting every already-filled cell's color together each time the percentage changes. (Each gauge's icon and percentage text use a separate smooth gradient based on its own overall percentage.) The context bar also shows a used/remaining token count next to the percentage whenever the context window size is known.

Each ratelimit_* gauge also shows a muted reset countdown (a restart icon plus a compact "resets in" duration like 2h or 3d 2h) whenever the payload reports a reset time — a coarser, more actionable signal than the percentage alone, since it tells you when the budget actually comes back rather than just how much is used right now.

cache reuses that same smooth gradient for its icon and hit-rate percentage, but inverted: a high cache-hit rate is good, so it runs green at 100% down to red at 0% (the opposite direction from the gauges above, where high usage is the thing to watch). The raw cache-read count in parentheses stays a plain muted color.

lines_changed leads with a pencil icon (given two trailing spaces of its own, since its glyph reads tight against a following icon), then diff-added/diff-removed icons that alone carry the +/- meaning. token_counts leads with a ticket icon (same two-space treatment) and breaks down usage into four counts, each behind its own icon: an inbound tray for input tokens, an outbound tray for output tokens, a database-plus for cache-creation tokens, and the same cache icon used elsewhere for cache-read tokens. Input/output are session-cumulative totals — the same time scope as lines_changed's add/remove counts — while cache-creation/ cache-read come from the most recent API response, since the schema has no cumulative field for either and "how well is caching working right now" is inherently a per-turn question anyway (the same time scope the standalone cache segment already uses). In both segments, only the icons carry semantic color (green/red for add-remove and input-output, info for the cache pair); the counts themselves are always the theme's secondary text color, with no ASCII sign.

cost and duration follow the same icon/text split: the dollar icon is success-green (money) and the clock icon is info, while the amount and the clock-style duration both render in the theme's primary text color — plain prose, not a semantic accent, since a dollar figure or elapsed time isn't inherently good/bad/informational the way a gauge fill is.

Line 3 follows the same rules as everywhere else. git's status badges (staged/modified/untracked/conflicts/ahead/behind) each split into a category-colored icon and a TextSecondary count, matching lines_changed/token_counts; conflicts use a proper alert icon rather than a bare !. The branch name, repo's final /name piece, pr's number, and worktree's name all use IdentityText — the same "headline label following an identity-colored icon" role as model/directory/ session_name — while pr's icon and review-state word still share the review-state color, since that state is the actual signal.

repo's icon is host-branded: GitHub, GitLab, and Forgejo each get their own logo when the remote host contains that name (matching public domains like github.com and enterprise/self-hosted ones like github.company.com alike), falling back to a generic git icon for anything else — including Forgejo instances with no "forgejo" in their hostname (e.g. Codeberg), since the host string is the only signal available.

Themes

statusline has no user-selectable aesthetic themes — it automatically follows Claude Code's own active theme, so it always visually matches what Claude Code itself is rendering with, no configuration required. There are two built-in palettes, claude-dark and claude-light; every other Claude Code theme preset collapses to whichever of the two it's based on. Palettes are foreground-only (identity accent + per-model-family identity accents (identity_opus/identity_sonnet/identity_haiku/identity_fable/ identity_mythos) + success/warning/danger/info/muted roles) — there's no background token, since the statusline never paints one. Every color token, including the per-family ones, can be overridden individually via [theme_overrides] (see Configuration).

Detection

statusline reads Claude Code's own settings.json files directly — it never receives theme information from Claude Code's statusLine payload, since that JSON carries no color/theme field at all. Resolution follows Claude Code's own settings-scope precedence, narrowed to the scopes a separate process can see on disk:

  1. <project>/.claude/settings.local.json
  2. <project>/.claude/settings.json (<project> is the payload's workspace.project_dir)
  3. $CLAUDE_CONFIG_DIR/settings.json (default ~/.claude/settings.json)
  4. No theme key found anywhere → dark, Claude Code's own documented default

The first scope that sets a theme key wins outright — same as Claude Code's own "local overrides project overrides user" behavior. Each preset value maps to a base:

Claude Code theme value statusline base
dark, dark-daltonized, dark-ansi claude-dark
light, light-daltonized, light-ansi claude-light
auto best-effort — see below
custom:<slug> that theme's own base field (see below)

statusline always emits 24-bit truecolor, so the daltonized/ANSI-16 variants currently render identically to their plain dark/light counterpart rather than shifting hues or downgrading to a 16-color palette — a possible future feature, not a silent gap today.

auto (Claude Code's own terminal light/dark background detection) isn't exposed anywhere for another process to read, and Claude Code resolves it internally rather than persisting the answer. statusline approximates it best-effort via the COLORFGBG environment variable (set by many terminals and tmux configs) and falls back to dark when it's absent or unparsable — deliberately not a live terminal query, since statusline's stdout is piped to Claude Code rather than connected to the terminal, and probing /dev/tty directly would risk racing Claude Code's own terminal control on every render.

custom:<slug> (a theme defined in ~/.claude/themes/<slug>.json) is honored for its base field only — the light/dark choice — not its overrides color tokens, since those use color forms (rgb(...), ansi256(...), ansi:<name>) statusline doesn't parse. A custom:<plugin>:<slug> reference (a plugin-contributed theme, not a file under ~/.claude/themes/) isn't resolvable from a settings read at all. Both cases fall back to dark.

Manual override

theme = "dark" / theme = "light" in config, or --theme dark|light on the command line, force a specific base and skip detection entirely — for previewing, running the binary outside Claude Code, or overriding a wrong detection. Any other value is treated as unset (falls through to detection).

claude-dark and claude-light are built from Anthropic's published brand palette (the #D97757 coral, #141413 ink, #FAF9F5 cream, plus a sage green and dusty blue used for success/info); warning and danger aren't part of that palette, so they're original colors chosen to sit comfortably in the same warm, muted family rather than clashing with a generic bright red/yellow.

Configuration

Optional TOML config file, discovered in this order (first match wins):

  1. --config <path> flag
  2. $XDG_CONFIG_HOME/statusline/config.toml (or ~/.config/statusline/config.toml)
  3. ~/.claude/statusline-config.toml
  4. Built-in defaults (auto-detected theme, the 3-line layout above)

A missing or malformed config file is never fatal — it falls back to the built-in default and prints a warning to stderr (never stdout, which is reserved for the rendered statusline).

Example forcing the theme and disabling one segment:

theme = "light"

[segments.pr]
enabled = false

[theme_overrides]
success = "#00ff00"

Take over lines[].segments entirely to reorder, drop, or add segments — see the Segments table for every available ID:

[[lines]]
enabled = true
segments = ["model", "context_window", "cache"]

Requirements

  • A Nerd Font in your terminal for the icons and the divider glyph. Without one, set nerd_font = false in config to fall back to plain Unicode/ASCII for every icon.
  • git on PATH for the repo/branch/PR segments; everything else works without it.

Development

make build             # go build -> ./statusline
make test              # unit tests
make test-integration  # + real git subprocess tests
make test-e2e          # + builds and drives the real binary
make bench             # benchmarks for the render/parse/config/theme hot paths
make lint              # go vet + golangci-lint
make security          # govulncheck + gosec

The whole non-git-subprocess render path — JSON decode, config/theme load, and the full three-line render — benchmarks at roughly 1.3ms combined on modest hardware, comfortably inside the sub-100ms-per-invocation budget the design targets; internal/gitstatus's porcelain parser alone handles a 500-file working tree in well under a millisecond.

Contributing

Bug reports, feature requests, and pull requests are welcome — see CONTRIBUTING.md for the development workflow and what to check before opening a PR. This project follows the Contributor Covenant.

Security

See SECURITY.md for supported versions and how to report a vulnerability privately. Please don't file a public issue for security reports.

License

Apache License 2.0 — see LICENSE.md.

Directories

Path Synopsis
cmd
statusline command
Command statusline is a Claude Code statusLine command: it reads session JSON from stdin and prints a themed, Nerd-Font-powerline status line to stdout.
Command statusline is a Claude Code statusLine command: it reads session JSON from stdin and prints a themed, Nerd-Font-powerline status line to stdout.
internal
cache
Package cache provides a small generic JSON file cache with atomic writes, used to avoid re-running slow subprocesses (git status) on every statusline refresh.
Package cache provides a small generic JSON file cache with atomic writes, used to avoid re-running slow subprocesses (git status) on every statusline refresh.
claudetheme
Package claudetheme detects Claude Code's own active color theme — "dark" or "light" — by reading Claude Code's own settings.json files directly, so statusline's palette always matches what Claude Code itself is rendering with.
Package claudetheme detects Claude Code's own active color theme — "dark" or "light" — by reading Claude Code's own settings.json files directly, so statusline's palette always matches what Claude Code itself is rendering with.
config
Package config loads the statusline's TOML configuration: a built-in default merged with an optional user file, discovered by a fixed precedence and never failing hard — a missing or malformed config always degrades to the built-in default plus a warning.
Package config loads the statusline's TOML configuration: a built-in default merged with an optional user file, discovered by a fixed precedence and never failing hard — a missing or malformed config always degrades to the built-in default plus a warning.
demo
Package demo provides canned statusline scenarios — synthetic payloads and git status, not real Claude Code JSON — so the demo command can render representative output without any external input.
Package demo provides canned statusline scenarios — synthetic payloads and git status, not real Claude Code JSON — so the demo command can render representative output without any external input.
fallback
Package fallback provides the last-resort degraded statusline used when normal rendering can't proceed at all (stdin parse failure, an unrecovered panic that reached main, or an empty render result), so the binary always prints something rather than leaving the status bar blank.
Package fallback provides the last-resort degraded statusline used when normal rendering can't proceed at all (stdin parse failure, an unrecovered panic that reached main, or an empty render result), so the binary always prints something rather than leaving the status bar blank.
gitstatus
Package gitstatus collects git branch and working-tree status for the git segment, caching results per session so repeated statusline refreshes don't re-run git on every trigger.
Package gitstatus collects git branch and working-tree status for the git segment, caching results per session so repeated statusline refreshes don't re-run git on every trigger.
input
Package input defines the JSON schema Claude Code sends to a statusLine command on stdin and parses it defensively, tolerating absent, null, and unknown fields so the binary never fails on partial or future data.
Package input defines the JSON schema Claude Code sends to a statusLine command on stdin and parses it defensively, tolerating absent, null, and unknown fields so the binary never fails on partial or future data.
modelid
Package modelid decodes a raw Claude model identifier — from the first-party API, Amazon Bedrock, Google Vertex AI, OpenRouter, or a corporate API gateway that reorders the family/version segments — into a normalized, human-readable "<Family> <Version>" label.
Package modelid decodes a raw Claude model identifier — from the first-party API, Amazon Bedrock, Google Vertex AI, OpenRouter, or a corporate API gateway that reorders the family/version segments — into a normalized, human-readable "<Family> <Version>" label.
render
Package render composes segment output into the final statusline text: it joins each line's segments with powerline cap/connector glyphs (or a plain divider between pill-less badges), drops lowest-priority segments under width pressure, and never lets one segment's panic take down the whole line.
Package render composes segment output into the final statusline text: it joins each line's segments with powerline cap/connector glyphs (or a plain divider between pill-less badges), drops lowest-priority segments under width pressure, and never lets one segment's panic take down the whole line.
segment
Package segment defines the Segment interface and every concrete statusline segment (model, directory, git, context window, cost, rate limits, and low-priority bonus badges), each rendering itself into style.Chunks from a RenderContext.
Package segment defines the Segment interface and every concrete statusline segment (model, directory, git, context window, cost, rate limits, and low-priority bonus badges), each rendering itself into style.Chunks from a RenderContext.
style
Package style provides truecolor ANSI painting, OSC 8 hyperlinks, and the escape-sequence stripping used both for NO_COLOR passthrough and for rendering-independent golden test comparisons.
Package style provides truecolor ANSI painting, OSC 8 hyperlinks, and the escape-sequence stripping used both for NO_COLOR passthrough and for rendering-independent golden test comparisons.
theme
Package theme resolves the five built-in named color palettes (Gruvbox, Catppuccin Mocha, Tokyo Night, Nord, Dracula) that segments render against, plus the shared Nerd Font icon table with plain-text fallbacks.
Package theme resolves the five built-in named color palettes (Gruvbox, Catppuccin Mocha, Tokyo Night, Nord, Dracula) that segments render against, plus the shared Nerd Font icon table with plain-text fallbacks.

Jump to

Keyboard shortcuts

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