YAS! (Yet Another Statusline)
🌈 Check out the official landing page here: YAS! Yet Another Statusline
_Most common form is displaying the first few rows, which include the loaded plugins & skills. Extra sections appear below them as needed_
Install/Update
Requires Python 3.10+, and a Nerd Font to render the icons.
curl -fsSL https://raw.githubusercontent.com/tmck-code/yet-another-statusline/main/ops/install.sh | bash
Alongside statusLine.command, the installer also wires a UserPromptSubmit
hook (yas-prompt-hook.py) that records per-session prompt timestamps, enabling
accurate turn-scoped subagent display. It is upserted idempotently — foreign
hooks are preserved, stale paths are rewritten on upgrade — and removed on
uninstall.
Or install manually:
claude plugin marketplace add tmck-code/yet-another-statusline
claude plugin install yas@yet-another-statusline
claude -p "/yas:init"
If you need to install non-interactively (e.g. in CI/docker)
curl -fsSL https://raw.githubusercontent.com/tmck-code/yet-another-statusline/main/ops/install.sh | YAS_NO_TTY=1 bash
install with specific python version e.g. 3.15
curl -fsSL https://raw.githubusercontent.com/tmck-code/yet-another-statusline/main/ops/install.sh | YAS_NO_TTY=1 YAS_PYTHON=3.15 bash
Upgrading — the yas/ layout move
Everything YAS writes except yas.toml now lives under a single
$CLAUDE_CONFIG_DIR/yas/ subtree (yas/cache/ and yas/state/). The migration
runs automatically — eagerly when you re-run ops/install.sh, lazily on the
first render otherwise — and is a one-off; afterwards the check costs one
stat().
Day-total token counts and the last-prompt handshake are moved and survive
the upgrade. Rate-limit history, render timings, the transcript parse cache and
mon's per-session payloads are regenerated rather than moved, so expect a
brief cold start after the first upgrade: the t/m rate and sparkline, the
render-time figure and mon's session list start out empty and refill within
one render tick to about five minutes, depending on the item.
Reconfigure later — /yas:config
Run /yas:config any time to re-run the wizard against the already-installed
plugin — switch theme/glyph mode, toggle labels, change the soft limit, or move
to Python 3.15. It re-wires settings.json without re-registering the
marketplace or reinstalling the plugin.
Demo
A dummy session to demonstrate the layout:
Widths
The statusline also renders differently according to available width
| mode | width | screenshot |
|------|-------|------------|
| "medium" | <=80 pixels | |
| "narrow" | <=55 pixels |
|
Configuration
Every configurable knob resolves through one fixed precedence chain (highest wins):
CLI flag → canonical YAS_* env var → legacy-alias env var → yas.toml → built-in default
The first source in that chain that is present and valid wins; an absent or
invalid source falls through to the next (an empty-string env var counts as
absent). Canonical YAS_* env vars always win over their deprecated legacy
aliases when both are set — the aliases keep working but are deprecated.
Knobs
| Knob | Env var | Legacy alias | yas.toml key | Default |
|------|---------|--------------|----------------|---------|
| max_width | YAS_MAX_WIDTH | — | [layout].max_width | 140 |
| full_width | YAS_FULL_WIDTH | — | [layout].full_width | false |
| soft_limit | YAS_SOFT_LIMIT | — | [tokens].soft_limit | 150000 |
| token_window | YAS_TOKEN_WINDOW | STATUSLINE_TOKEN_WINDOW | [tokens].token_window | 60 |
| theme | YAS_THEME (also --theme CLI) | CLAUDE_STATUSLINE_THEME | [appearance].theme | claude-dark |
| bg_shift | YAS_BG_SHIFT (also --bg-shift CLI) | — | [appearance].bg_shift | warm |
| glyph_mode | YAS_GLYPH_MODE (also --glyph-mode CLI) | — | [appearance.glyphs].mode | nerdfont |
| single_width | YAS_GLYPH_SINGLE_WIDTH (also --glyph-single-width CLI) | — | [appearance.glyphs].single_width | false |
| show_icons | YAS_SHOW_ICONS | — | [appearance.glyphs].show_icons | true |
| context_state | YAS_CONTEXT_STATE | — | [context].state | false |
| context_labels | YAS_CONTEXT_LABELS | — | [context].labels | Smart,Coasting,Foggy,Cooked,Dumb |
| context_thresholds | YAS_CONTEXT_THRESHOLDS | — | [context].thresholds | 25,50,70,90 |
| show_render_time | YAS_SHOW_RENDER_TIME | — | [layout].show_render_time | false |
| show_tool_uses | YAS_SHOW_TOOL_USES | — | [layout].show_tool_uses | false |
| show_tokens_over_time | YAS_SHOW_TOKENS_OVER_TIME | — | [layout].show_tokens_over_time | false |
| labels | YAS_LABELS | — | [layout].labels | false |
| justify | YAS_JUSTIFY | — | [layout].justify | false |
| show_day_stats | YAS_SHOW_DAY_STATS | — | [tokens].show_day_stats | true |
| openspec_scan_depth | YAS_OPENSPEC_SCAN_DEPTH | — | [openspec].scan_depth | 1 |
Valid values
theme— 15 built-in themes; unknown or unset falls back toclaude-dark:
claude-dark, catppuccin-mocha, dracula, gruvbox-dark, nord, one-dark, solarized-dark, tokyo-night, palenight
- Light: claude-light, catppuccin-latte, dracula-light, gruvbox-light, one-light, solarized-light
bg_shift—warmorcool.glyph_mode— all four modes preserve column geometry; an unknown value falls back tonerdfont:
nerdfont — default; full fidelity, needs a Nerd Font.
- ascii — every non-ASCII glyph → width-1 ASCII; maximum compatibility.
- unicode — only Nerd Font PUA icons → non-PUA Unicode; keeps box/block/arrow glyphs.
- github — GitHub-paste-safe: folds every glyph a browser renders double-wide (the box-drawing frame, block/sparkline ramp, and EAW-ambiguous punctuation/icons) to a width-1, EAW-narrow/ASCII stand-in, so a render stays column-aligned when pasted into a GitHub markdown code block.
Behaviour notes
single_width— orthogonal boolean that folds double-width dynamic content (wide emoji, CJK in branch names/paths) to width-1; combinable with anyglyph_mode. The statusline's own glyphs are already width-1, so column geometry is preserved.show_icons— whenfalse, drops essentially every icon/glyph the statusline draws, including the top-left home/folder glyph. The exceptions — glyphs that still render — are the task-plan checkboxes, the subagent status markers, and the box/border and workflow structural glyphs (frame, sparkline blocks, separators, row markers). Text and numbers still render; only the glyph beside each is omitted, and column/border alignment is preserved.trueby default, preserving current behaviour.full_width— whentrue, makes the box fill the terminal and ignoremax_width.show_render_time— whentrue, annotates the bottom-right border with the previous run's wall-clock render time (e.g.…47.2ms──╯). Off by default; each run shows the prior run's timing, so it is blank on a session's first render.show_tool_uses— whentrue, adds a row (wide layout only) under the tokens/cost band, listing per-tooltool_usecounts.show_tokens_over_time— whentrue, adds a full-width "tokens over time" row (wide layout only) under the tokens/cost band, showing thet/mrate and live sparkline. The trailing column of the tokens/cost row always shows skills + plugins; the sparkline is opt-in via this row.labels— whentrue, paints small superscript field captions into the border/separator rows (wide layout only). Note: this is a different knob from[context].labels, which is the five-word state list — see Context state word. The two share a key name but live in different sections and take different types.justify— whentrue, aligns fields into columns instead of packing them left (wide layout only).show_day_stats— whentrue(the default), shows today's cumulative token and cost totals alongside the session's, assession/daypairs. Note: this key lives under[tokens], not[layout], unlike the other display toggles.openspec_scan_depth— how many repo-levels belowcwdthe OpenSpec downward scan descends looking for nestedopenspec/roots (monorepo-of-repos layout).1(the default) finds a repo directly belowcwd;2also finds one nested a level deeper;0disables the downward scan entirely (only anopenspec/found by walking upward fromcwdis used). Unlike the other numeric knobs,0is a legal value here.- CLI flags —
--theme NAME/--bg-shift DIRalso accept the--theme=NAME/--bg-shift=DIRform. Pass them in thestatusLine.commandof your~/.claude/settings.json. - Legacy theme file —
~/.claude/statusline-themeis no longer read. If you re-run the installer while the file is non-empty andyas.tomldoes not already set a theme, the installer folds its value intoyas.tomlonce, and the migration then deletes the file. Otherwise set the theme by hand:
[appearance]
theme = "claude-dark"
Context state word
context_state adds a morphing word to the context line that names how full the
context window is — Smart → Coasting → Foggy → Cooked → Dumb — so you can read
the state at a glance instead of doing percentage math. It is off by default;
enable it with YAS_CONTEXT_STATE=1 or [context].state = true.
The word is tinted with the same threshold colour as the context bar and sits just before it; in a narrow box it sheds first (the bar and percentage stay).
context_labels— exactly 5 comma-separated words (Smart,Coasting,Foggy,Cooked,Dumbby default).context_thresholds— exactly 4 strictly-ascending integers in1..99(25,50,70,90by default), the start percentage of bands 2–5.
Dumb exactly as the bar fills toward the compaction soft_limit.
Credit: the state-word idea and its default labels/thresholds are ported from
Dumbometer by Maximo Correa
Rosas (MIT). See NOTICE. Dumbometer maps the word to the *full
context window*; YAS maps it to the soft-limit fill ratio for bar consistency.
yas.toml
yas.toml lives in CLAUDE_CONFIG_DIR (defaults to ~/.claude/). It is not
auto-created — its absence simply means all-defaults — and /yas:init never
writes it. See yas.example.toml for a fully-commented
template; copy it to ~/.claude/yas.toml and uncomment what you want.
[layout]
max_width = 140
[tokens]
soft_limit = 150000
token_window = 60
[appearance]
theme = "claude-dark"
bg_shift = "warm"
[appearance.glyphs]
mode = "nerdfont"
single_width = false
show_icons = true
[context]
state = false
labels = ["Smart", "Coasting", "Foggy", "Cooked", "Dumb"]
thresholds = [25, 50, 70, 90]
[openspec]
scan_depth = 1
yas.tomlrequires Python 3.11+ — it is parsed with the stdlibtomllib.
On Python 3.10 the file is silently skipped; **environment variables work on
every Python version**.
Bad config never crashes the statusline. A malformed yas.toml is ignored
wholesale, and a single bad / out-of-range / wrong-type value drops only that
one knob back to its default. When any yas.toml value is rejected, a compact
warning row — ⚠ yas.toml: N values ignored (...) — appears at the bottom of
the box listing the rejected knob names. Detailed per-value reasons go to stderr
only when YAS_DEBUG is set.
Per-model soft_limit overrides
Beyond the global [tokens].soft_limit, you can declare per-model overrides as
an inline array under [tokens]:
[tokens]
model = [
{ match = "opus", soft_limit = 200000 }, # the whole Opus family
{ match = "opus-4-8[1m]", soft_limit = 1000000 }, # 1M-context variant (longer match wins)
]
matchis a case-insensitive plain substring (no glob/regex), tested
- When multiple entries match, the longest
matchwins; ties break by array
soft_limit is used. So to
single out a variant from its family, give the variant the **longer, more
specific** match (above, opus-4-8[1m] outranks opus for the 1M model).
- **A matching per-model override beats the global
soft_limitfrom _any_
YAS_SOFT_LIMIT environment variable.** This is the
one documented exception to the "env beats yas.toml" rule: specificity beats
source precedence (there is intentionally no per-model env var). It lets you
raise the compaction-risk threshold for a 1M-context model variant distinctly
from the rest of its family.
Other environment variables
| var | default | description |
|-----|---------|-------------|
| CLAUDE_CONFIG_DIR | ~/.claude | base dir for yas.toml and the yas/ state/cache subtree (logs, width file, session payloads) |
| YAS_DEBUG | _(unset)_ | when set, prints detailed per-value config-rejection reasons to stderr |
| COLUMNS | _(unset)_ | terminal-width fallback when tmux / width-file detection fail |
Terminal width
Width is detected by the first source that returns a positive value:
tmux display-message -p '#{pane_width}'~/.claude/yas/state/signals/terminal-widthfile (written byops/alacritty.py, which honoursCLAUDE_CONFIG_DIR)COLUMNSenv varshutil.get_terminal_size()//dev/ttyioctl
Uninstalling
Remove the statusline config and uninstall the plugin in one step:
curl -fsSL https://raw.githubusercontent.com/tmck-code/yet-another-statusline/main/ops/install.sh | bash -s -- --uninstall --full
Or uninstall manually:
claude -p "/yas:uninstall"
claude plugin uninstall yas@yet-another-statusline
claude plugin uninstall only deletes the plugin cache — it leaves statusLine.command
in ~/.claude/settings.json pointing at the now-missing script, so the statusline keeps
trying to run. Run the uninstall script (or /yas:uninstall) first to remove that
config, then uninstall the plugin. Reload Claude Code afterwards.
Commands
make test # run pytest suite
make demo # animated demo at current terminal width
make statusline/test # same as demo — use during development
make demo/img # render snapshots into demo/
make mon/run # launch multi-session monitor TUI
Contributing
Enable the git pre-commit hooks (runs ruff / mypy / pytest on staged Python before each commit):
make hooks
Installing via git clone
Edits to the checkout take effect immediately — no reinstall step.
git clone https://github.com/tmck-code/yet-another-statusline
cd yet-another-statusline
Wire statusLine.command in ~/.claude/settings.json to point at the checkout:
"statusLine": {
"async": true,
"command": "python3 \"/path/to/yet-another-statusline/claude/statusline_command.py\"",
"type": "command"
}
Note: if you also have the plugin installed, claude plugin install will overwrite
statusLine.command back to the plugin cache path. Either uninstall the plugin or bump
the version in .claude-plugin/plugin.json before reinstalling to keep your local path.
For accurate turn-scoped subagent display, also wire the UserPromptSubmit hook
(hooks/yas-prompt-hook.py) so per-session prompt timestamps are recorded:
"hooks": {
"UserPromptSubmit": [
{ "matcher": "", "hooks": [
{ "type": "command", "command": "python3 \"/path/to/yet-another-statusline/hooks/yas-prompt-hook.py\"" }
] }
]
}
The curl … | bash installer wires this hook for you (upserted idempotently —
foreign hooks preserved, stale paths rewritten on upgrade — and removed on
uninstall); it is only a manual step for a git-clone install. Without it, the
statusline falls back to a time-window heuristic for which subagents to show.
This prompts before setting core.hooksPath. CI runs the same checks on every push, so the hook is fast local feedback rather than the gate.