{"id":51961991,"url":"https://github.com/tmck-code/yet-another-statusline","last_synced_at":"2026-07-29T14:31:42.971Z","repository":{"id":354533436,"uuid":"1217996319","full_name":"tmck-code/yet-another-statusline","owner":"tmck-code","description":"A statusline for Claude Code inspired by terminal monitor programs","archived":false,"fork":false,"pushed_at":"2026-07-17T14:39:10.000Z","size":3512,"stargazers_count":229,"open_issues_count":4,"forks_count":23,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-07-17T16:08:03.360Z","etag":null,"topics":["ansi","claude","monitoring-tool","terminal"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tmck-code.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":"NOTICE","maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-22T12:35:06.000Z","updated_at":"2026-07-17T14:17:39.000Z","dependencies_parsed_at":"2026-07-17T16:04:25.391Z","dependency_job_id":null,"html_url":"https://github.com/tmck-code/yet-another-statusline","commit_stats":null,"previous_names":["tmck-code/claude-code-config","tmck-code/yet-another-statusline"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/tmck-code/yet-another-statusline","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tmck-code%2Fyet-another-statusline","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tmck-code%2Fyet-another-statusline/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tmck-code%2Fyet-another-statusline/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tmck-code%2Fyet-another-statusline/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tmck-code","download_url":"https://codeload.github.com/tmck-code/yet-another-statusline/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tmck-code%2Fyet-another-statusline/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":36037221,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-07-20T02:08:10.276Z","status":"online","status_checked_at":"2026-07-29T02:00:04.910Z","response_time":95,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ansi","claude","monitoring-tool","terminal"],"created_at":"2026-07-29T14:31:42.835Z","updated_at":"2026-07-29T14:31:42.934Z","avatar_url":"https://github.com/tmck-code.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# YAS! (Yet Another Statusline)\n\n🌈 Check out the official landing page here: [YAS! Yet Another Statusline](https://tmck-code.github.io/pages/yas.html)\n\n\u003cimg width=\"1834\" height=\"979\" alt=\"image\" src=\"https://github.com/user-attachments/assets/9c3137b4-dd01-478e-b3bb-6b9d39570104\" /\u003e\n\n_Most common form is displaying the first few rows, which include the loaded plugins \u0026 skills. Extra sections appear below them as needed_\n\n## Install/Update\n\nRequires Python 3.10+, and a [Nerd Font](https://www.nerdfonts.com/font-downloads) to render the icons.\n\n```bash\ncurl -fsSL https://raw.githubusercontent.com/tmck-code/yet-another-statusline/main/ops/install.sh | bash\n```\n\nAlongside `statusLine.command`, the installer also wires a `UserPromptSubmit`\nhook (`yas-prompt-hook.py`) that records per-session prompt timestamps, enabling\n**accurate turn-scoped subagent display**. It is upserted idempotently — foreign\nhooks are preserved, stale paths are rewritten on upgrade — and removed on\nuninstall.\n\nOr install manually:\n\n```bash\nclaude plugin marketplace add tmck-code/yet-another-statusline\nclaude plugin install yas@yet-another-statusline\nclaude -p \"/yas:init\"\n```\n\nIf you need to install non-interactively (e.g. in CI/docker)\n\n```bash\ncurl -fsSL https://raw.githubusercontent.com/tmck-code/yet-another-statusline/main/ops/install.sh | YAS_NO_TTY=1 bash\n# install with specific python version e.g. 3.15\ncurl -fsSL https://raw.githubusercontent.com/tmck-code/yet-another-statusline/main/ops/install.sh | YAS_NO_TTY=1 YAS_PYTHON=3.15 bash\n```\n\n### Reconfigure later — `/yas:config`\n\nRun `/yas:config` any time to re-run the wizard against the already-installed\nplugin — switch theme/glyph mode, toggle labels, change the soft limit, or move\nto Python **3.15**. It re-wires `settings.json` without re-registering the\nmarketplace or reinstalling the plugin.\n\n## Demo\n\nA dummy session to demonstrate the layout:\n\n\u003cimg width=\"3084\" height=\"1250\" alt=\"yas-0 2 5\" src=\"https://github.com/user-attachments/assets/94d318c7-d7b4-4ad0-a06c-90f303d7f9a7\" /\u003e\n\n## Widths\n\nThe statusline also renders differently according to available width\n\n| mode | width | screenshot |\n|------|-------|------------|\n| \"medium\" | \u003c=80 pixels | \u003cimg width=\"839\" height=\"122\" alt=\"image\" src=\"https://github.com/user-attachments/assets/56519acc-a65c-446a-a938-5a14f093c817\" /\u003e |\n| \"narrow\" | \u003c=55 pixels | \u003cimg width=\"537\" height=\"120\" alt=\"image\" src=\"https://github.com/user-attachments/assets/7254cbb7-ea37-4f41-8adc-506cf6b48033\" /\u003e |\n\n---\n\n## Configuration\n\nEvery configurable knob resolves through one fixed precedence chain (highest wins):\n\n```\nCLI flag  →  canonical YAS_* env var  →  legacy-alias env var  →  yas.toml  →  built-in default\n```\n\nThe first source in that chain that is **present and valid** wins; an absent or\ninvalid source falls through to the next (an empty-string env var counts as\nabsent). Canonical `YAS_*` env vars always win over their deprecated legacy\naliases when both are set — the aliases keep working but are deprecated.\n\n### Knobs\n\n| Knob | Env var | Legacy alias | `yas.toml` key | Default |\n|------|---------|--------------|----------------|---------|\n| `max_width` | `YAS_MAX_WIDTH` | — | `[layout].max_width` | `140` |\n| `full_width` | `YAS_FULL_WIDTH` | — | `[layout].full_width` | `false` |\n| `soft_limit` | `YAS_SOFT_LIMIT` | — | `[tokens].soft_limit` | `150000` |\n| `token_window` | `YAS_TOKEN_WINDOW` | `STATUSLINE_TOKEN_WINDOW` | `[tokens].token_window` | `60` |\n| `theme` | `YAS_THEME` (also `--theme` CLI) | `CLAUDE_STATUSLINE_THEME` | `[appearance].theme` | `claude-dark` |\n| `bg_shift` | `YAS_BG_SHIFT` (also `--bg-shift` CLI) | — | `[appearance].bg_shift` | `warm` |\n| `glyph_mode` | `YAS_GLYPH_MODE` (also `--glyph-mode` CLI) | — | `[appearance.glyphs].mode` | `nerdfont` |\n| `single_width` | `YAS_GLYPH_SINGLE_WIDTH` (also `--glyph-single-width` CLI) | — | `[appearance.glyphs].single_width` | `false` |\n| `context_state` | `YAS_CONTEXT_STATE` | — | `[context].state` | `false` |\n| `context_labels` | `YAS_CONTEXT_LABELS` | — | `[context].labels` | `Smart,Coasting,Foggy,Cooked,Dumb` |\n| `context_thresholds` | `YAS_CONTEXT_THRESHOLDS` | — | `[context].thresholds` | `25,50,70,90` |\n| `show_render_time` | `YAS_SHOW_RENDER_TIME` | — | `[layout].show_render_time` | `false` |\n| `show_tool_uses` | `YAS_SHOW_TOOL_USES` | — | `[layout].show_tool_uses` | `true` |\n\n#### Valid values\n\n- **`theme`** — 14 built-in themes; unknown or unset falls back to `claude-dark`:\n  - **Dark:** `claude-dark`, `catppuccin-mocha`, `dracula`, `gruvbox-dark`, `nord`, `one-dark`, `solarized-dark`, `tokyo-night`, `palenight`\n  - **Light:** `claude-light`, `catppuccin-latte`, `gruvbox-light`, `one-light`, `solarized-light`\n- **`bg_shift`** — `warm` or `cool`.\n- **`glyph_mode`** — all four modes preserve column geometry; an unknown value falls back to `nerdfont`:\n  - `nerdfont` — default; full fidelity, needs a Nerd Font.\n  - `ascii` — every non-ASCII glyph → width-1 ASCII; maximum compatibility.\n  - `unicode` — only Nerd Font PUA icons → non-PUA Unicode; keeps box/block/arrow glyphs.\n  - `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.\n\n#### Behaviour notes\n\n- **`single_width`** — orthogonal boolean that folds double-width dynamic content (wide emoji, CJK in branch names/paths) to width-1; combinable with any `glyph_mode`. The statusline's own glyphs are already width-1, so column geometry is preserved.\n- **`full_width`** — when `true`, makes the box fill the terminal and ignore `max_width`.\n- **`show_render_time`** — when `true`, 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.\n- **`show_tool_uses`** — when `true`, adds a row (wide layout only) under the tokens/cost band, listing per-tool `tool_use` counts.\n- **CLI flags** — `--theme NAME` / `--bg-shift DIR` also accept the `--theme=NAME` / `--bg-shift=DIR` form. Pass them in the `statusLine.command` of your `~/.claude/settings.json`.\n- **Legacy theme file** — `~/.claude/statusline-theme` (contents = a theme name) still works as the lowest-priority theme fallback, below `[appearance].theme`.\n\n### Context state word\n\n`context_state` adds a morphing word to the context line that names how full the\ncontext window is — `Smart → Coasting → Foggy → Cooked → Dumb` — so you can read\nthe state at a glance instead of doing percentage math. It is **off by default**;\nenable it with `YAS_CONTEXT_STATE=1` or `[context].state = true`.\n\nThe word is tinted with the same threshold colour as the context bar and sits\njust before it; in a narrow box it sheds first (the bar and percentage stay).\n\n- `context_labels` — exactly 5 comma-separated words (`Smart,Coasting,Foggy,Cooked,Dumb` by default).\n- `context_thresholds` — exactly 4 strictly-ascending integers in `1..99` (`25,50,70,90` by default), the start percentage of bands 2–5.\n\nThe percentage driving the word is YAS's **soft-limit fill ratio** (the same\nbasis as the context bar), so the word and the bar always agree — the word turns\n`Dumb` exactly as the bar fills toward the compaction `soft_limit`.\n\n\u003e Credit: the state-word idea and its default labels/thresholds are ported from\n\u003e [**Dumbometer**](https://github.com/MaximoCorrea1/dumbometer) by Maximo Correa\n\u003e Rosas (MIT). See [`NOTICE`](NOTICE). Dumbometer maps the word to the *full\n\u003e context window*; YAS maps it to the soft-limit fill ratio for bar consistency.\n\n### `yas.toml`\n\n`yas.toml` lives in `CLAUDE_CONFIG_DIR` (defaults to `~/.claude/`). It is **not**\nauto-created — its absence simply means all-defaults — and `/yas:init` never\nwrites it. See [`yas.example.toml`](yas.example.toml) for a fully-commented\ntemplate; copy it to `~/.claude/yas.toml` and uncomment what you want.\n\n```toml\n[layout]\nmax_width = 140\n\n[tokens]\nsoft_limit = 150000\ntoken_window = 60\n\n[appearance]\ntheme = \"claude-dark\"\nbg_shift = \"warm\"\n\n[appearance.glyphs]\nmode = \"nerdfont\"\nsingle_width = false\n\n[context]\nstate = false\nlabels = [\"Smart\", \"Coasting\", \"Foggy\", \"Cooked\", \"Dumb\"]\nthresholds = [25, 50, 70, 90]\n```\n\n\u003e **`yas.toml` requires Python 3.11+** — it is parsed with the stdlib `tomllib`.\n\u003e On Python 3.10 the file is silently skipped; **environment variables work on\n\u003e every Python version**.\n\nBad config never crashes the statusline. A malformed `yas.toml` is ignored\nwholesale, and a single bad / out-of-range / wrong-type value drops only that\none knob back to its default. When any `yas.toml` value is rejected, a compact\nwarning row — `⚠ yas.toml: N values ignored (...)` — appears at the bottom of\nthe box listing the rejected knob names. Detailed per-value reasons go to stderr\nonly when `YAS_DEBUG` is set.\n\n### Per-model `soft_limit` overrides\n\nBeyond the global `[tokens].soft_limit`, you can declare per-model overrides as\nan inline array under `[tokens]`:\n\n```toml\n[tokens]\nmodel = [\n    { match = \"opus\",         soft_limit = 200000 },   # the whole Opus family\n    { match = \"opus-4-8[1m]\", soft_limit = 1000000 },  # 1M-context variant (longer match wins)\n]\n```\n\n- `match` is a **case-insensitive plain substring** (no glob/regex), tested\n  against the model's id and display name.\n- When multiple entries match, the **longest** `match` wins; ties break by array\n  order (first wins). If no entry matches, the global `soft_limit` is used. So to\n  single out a variant from its family, give the variant the **longer, more\n  specific** `match` (above, `opus-4-8[1m]` outranks `opus` for the 1M model).\n- **A matching per-model override beats the global `soft_limit` from _any_\n  source — including the `YAS_SOFT_LIMIT` environment variable.** This is the\n  one documented exception to the \"env beats `yas.toml`\" rule: specificity beats\n  source precedence (there is intentionally no per-model env var). It lets you\n  raise the compaction-risk threshold for a 1M-context model variant distinctly\n  from the rest of its family.\n\n### Other environment variables\n\n| var | default | description |\n|-----|---------|-------------|\n| `CLAUDE_CONFIG_DIR` | `~/.claude` | base dir for config/state files (`yas.toml`, theme file, width file, token-rate log, output payloads) |\n| `YAS_DEBUG` | _(unset)_ | when set, prints detailed per-value config-rejection reasons to stderr |\n| `COLUMNS` | _(unset)_ | terminal-width fallback when tmux / width-file detection fail |\n\n### Terminal width\n\nWidth is detected by the first source that returns a positive value:\n\n1. `tmux display-message -p '#{pane_width}'`\n2. `~/.claude/terminal-width` file\n3. `COLUMNS` env var\n4. `shutil.get_terminal_size()` / `/dev/tty` ioctl\n\n---\n\n## Uninstalling\n\nRemove the statusline config and uninstall the plugin in one step:\n\n```bash\ncurl -fsSL https://raw.githubusercontent.com/tmck-code/yet-another-statusline/main/ops/install.sh | bash -s -- --uninstall --full\n```\n\nOr uninstall manually:\n\n```bash\nclaude -p \"/yas:uninstall\"\nclaude plugin uninstall yas@yet-another-statusline\n```\n\n`claude plugin uninstall` only deletes the plugin cache — it leaves `statusLine.command`\nin `~/.claude/settings.json` pointing at the now-missing script, so the statusline keeps\ntrying to run. Run the uninstall script (or `/yas:uninstall`) **first** to remove that\nconfig, then uninstall the plugin. Reload Claude Code afterwards.\n\n---\n\n## Commands\n\n```bash\nmake test            # run pytest suite\nmake demo            # animated demo at current terminal width\nmake statusline/test # same as demo — use during development\nmake demo/img        # render snapshots into demo/\nmake mon/run         # launch multi-session monitor TUI\n```\n\n## Contributing\n\nEnable the git pre-commit hooks (runs `ruff` / `mypy` / `pytest` on staged Python before each commit):\n\n```bash\nmake hooks\n```\n\n### Installing via git clone\n\nEdits to the checkout take effect immediately — no reinstall step.\n\n```bash\ngit clone https://github.com/tmck-code/yet-another-statusline\ncd yet-another-statusline\n```\n\nWire `statusLine.command` in `~/.claude/settings.json` to point at the checkout:\n```json\n\"statusLine\": {\n  \"async\": true,\n  \"command\": \"python3 \\\"/path/to/yet-another-statusline/claude/statusline_command.py\\\"\",\n  \"type\": \"command\"\n}\n```\n\n\u003e **Note:** if you also have the plugin installed, `claude plugin install` will overwrite\n\u003e `statusLine.command` back to the plugin cache path. Either uninstall the plugin or bump\n\u003e the version in `.claude-plugin/plugin.json` before reinstalling to keep your local path.\n\nFor accurate turn-scoped subagent display, also wire the `UserPromptSubmit` hook\n(`hooks/yas-prompt-hook.py`) so per-session prompt timestamps are recorded:\n```json\n\"hooks\": {\n  \"UserPromptSubmit\": [\n    { \"matcher\": \"\", \"hooks\": [\n      { \"type\": \"command\", \"command\": \"python3 \\\"/path/to/yet-another-statusline/hooks/yas-prompt-hook.py\\\"\" }\n    ] }\n  ]\n}\n```\n\n\u003e The `curl … | bash` installer wires this hook for you (upserted idempotently —\n\u003e foreign hooks preserved, stale paths rewritten on upgrade — and removed on\n\u003e uninstall); it is only a manual step for a git-clone install. Without it, the\n\u003e statusline falls back to a time-window heuristic for which subagents to show.\n\n\nThis prompts before setting `core.hooksPath`. CI runs the same checks on every push, so the hook is fast local feedback rather than the gate.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftmck-code%2Fyet-another-statusline","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftmck-code%2Fyet-another-statusline","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftmck-code%2Fyet-another-statusline/lists"}