@hank-warren/pi-statusline
Compact Pi footer statusline with Git/worktree context, token usage, and neon celebrations for exceptional prompt-cache hits.
Package details
Install @hank-warren/pi-statusline from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:@hank-warren/pi-statusline- Package
@hank-warren/pi-statusline- Version
0.5.0- Published
- Aug 20, 2026
- Downloads
- 1,583/mo · 1,208/wk
- Author
- hank-warren
- License
- MIT
- Types
- extension
- Size
- 113.2 KB
- Dependencies
- 0 dependencies · 2 peers
Pi manifest JSON
{
"extensions": [
"./index.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
pi-statusline
Replaces Pi's default footer with a compact statusline:
gpt-5.6-sol | pi-extensions:main* ⇣1 | 40k/1.0m | 97·54 80
⑂ pi-extensions:feature/statusline* ⇣2 #7 | infra:fix/alerts #168
019fafa7-29c0-7e99-9f82-5794d5721848
What it shows
- Line 1 — active model ID, current directory basename and Git branch, current context usage/window, and subscription usage headroom (see below). A yellow
*marks a dirty checkout and⇣Nshows how many commits it is behind its locally known upstream ref. Unknown context usage is rendered as?/<window>until Pi can provide an estimate. Exceptional prompt-cache hits trigger the celebration described below. - Worktree lines — when the session works in or sends tool calls into linked worktrees, one line shows the same branch/dirty/behind state for each worktree plus its associated PR number.
- Final line — the full Pi session ID.
Colors come from a selectable theme, with context warning thresholds. Every element can be turned off from /statusline.
/statusline
/statusline opens a /settings-style menu (TUI mode only) for configuring the footer. Changes apply live and persist immediately:
- Theme — the color palette, cycled with Enter or Space. See Themes.
- Cache celebration —
offor one of five badge animations, cycled with Enter or Space and previewed live in the statusline below. See Animation styles. - Model, Directory & git, Context, Subscription usage, Worktree line, Session ID line —
on/off, cycled with Enter or Space. Disabled segments are dropped from line 1 without leaving a stray|separator; hiding the worktree line also stops itsgit/ghpolling, and hiding usage stops the usage poller. With every element off the footer collapses to a single blank row. - Worktree root — the directory whose immediate children are tracked as session worktrees (default
~/repos/worktrees).~and$HOMEare expanded; a relative path is rejected and the previous value kept. - Repo aliases — short display names for repositories on the worktree line. Enter edits the selected
repo → aliaspair,ddeletes it, andAdd alias…creates one from arepo=aliasline.
Settings live in a single global file, ~/.pi/agent/statusline-settings.json, written atomically. Only values differing from the defaults are stored, unknown keys from a newer version are preserved, and a missing or malformed file simply yields defaults.
Saves are per-key rather than whole-file: a change writes only the fields it actually touched over whatever is on disk at that moment. Sessions load settings once at startup, so a whole-file write would let a session that started hours ago revert edits it never saw — including hand edits and changes made in another session. Opening /statusline also re-reads the file first, so the menu always edits current state. Two sessions changing the same field are still last-writer-wins; everything else merges.
Adding a setting
A setting is persisted, validated, diffed, and rendered in separate places, so add it to all of them:
StatuslineSettingsinsettings.ts— plusBOOLEAN_SETTING_KEYSif it is a toggle.defaultSettings().normalizeSettings()— theknownkey set, and a parse branch that falls back to the default for an invalid value rather than discarding the whole file.serializeSettings()— write it only when it differs from its default, keeping the file sparse.SETTING_KEYS.- A row in
buildSettingItems()and a branch inapplySettingChange()insettings-menu.ts, placed in the order the element renders. - Live-apply handling in
applySettings()inindex.ts, if the change needs more than a repaint (disposing a poller, forcing a full redraw on a row-count change).
Steps 1 and 5 are enforced: omitting the key from SETTING_KEYS fails npm run typecheck by name, and a test asserts it matches the keys of defaultSettings(). Nothing enforces steps 3, 4, 6 or 7 — a setting missing from serializeSettings applies live and never persists.
Compatibility rules, because old and new versions share one file:
- Never change a key's type or meaning — add a sibling key. This is why
showCacheCelebrationstayed a boolean when it gained animation styles: an older version reading a repurposed key falls back to its default and can write that fallback back. - Unknown keys survive an older version; unknown values do not. A theme name a reader does not recognise falls back to
default, and a whole-file write from that reader drops the choice. Extending a cosmetic enum is fine; encoding behaviour in one is riskier than adding a key. - Keep settings independent. Per-key saves mean two fields can be written by different sessions at different times, so resolve any relationship between settings at render time, not on disk.
Themes
| Name | Notes |
|---|---|
default |
The palette this package has always used — blue model, cyan branch, neon magenta/cyan cache badge |
dracula |
Dracula, with the pink #ff79c6 branch color used in Herdr sidebar configs |
github-dark |
GitHub's dark default |
catppuccin-mocha |
Catppuccin Mocha |
white |
No color: white text, dimmed punctuation, a white/grey badge flash |
A theme maps eleven roles — model, path, branch, text, dim, ok, warn, caution, danger, accent, and the two celebration badge frames — so switching recolors every element at once without changing a single character of rendered text. ok/warn/caution/danger drive the context meter, the usage meters, and the dirty/behind markers alike, so the warning gradient stays legible in every theme. An unknown theme name in the settings file falls back to default.
Repo aliases are no longer built in
Up to 0.2.x this package hardcoded five alias pairs and stripped a platform- prefix from every other repository. Those rules were specific to one machine and shipped to everyone. From 0.3.0 the alias map starts empty and repository names render verbatim — add whatever pairs you want under /statusline → Repo aliases.
Subscription usage meters
When Pi's ~/.pi/agent/auth.json contains OAuth credentials for Anthropic (Claude subscription) and/or OpenAI Codex, line 1 shows percent remaining for each rate-limit window after the context meter:
97·54— Claude 5-hour, then weekly remaining percent (Nerd Fontnf-cod-claudeicon). Subscriptions with a model-scoped weekly limit (e.g. Fable) show it as a third number — 97·54·24— and it is omitted when the account has none. 80— Codex weekly remaining percent (nf-cod-openaiicon)
Numbers are colored by remaining headroom: green above 60, yellow 41–60, orange 16–40, red at 15 and below.
Usage is fetched from the providers' own usage endpoints with Pi's stored tokens — read-only; tokens are never refreshed or written. Fetches happen on session start and after each turn, throttled to at most once every five minutes (the Anthropic usage endpoint rate-limits aggressively), and are strictly best-effort: on any failure the last-known value is kept, and providers without credentials (or before the first successful fetch) are simply omitted, leaving the statusline exactly as before. While a provider that does have credentials still has no value, the throttle drops to 30 seconds — Pi only refreshes an expired OAuth access token when that provider is first used, so a session starting with a stale Anthropic token would otherwise show no Claude meter for a full interval.
Which account each meter shows
With a single login per provider — the ordinary case, including logging out and back in as a different Anthropic account — nothing here applies: each meter shows that provider's account, exactly as before.
When @hank-warren/pi-multi-login has registered additional logins (anthropic-work, openai-codex-alt), a provider family can hold several accounts at once. Each meter then shows the account behind the main model, falling back to the base account (anthropic, openai-codex) when the main model belongs to the other family. So switching the main model between two Claude logins swaps the Claude meter and leaves the Codex meter alone, and a login used only for background work — such as a pi-auto-permissions reviewer, which is never the main model — is never polled at all. There is deliberately no marker for which account is shown: the meter tracks whatever you are actually spending.
Polling and the shared cache
Polling is host-wide, not per-session. Usage percentages describe the account rather than the session, and a busy machine runs dozens of pi processes, so every process shares ~/.pi/agent/statusline-usage.json, written atomically via a temp file and rename. It is keyed by credential id, one entry per account, each holding that account's last good values plus the time its last poll was started. A session adopts the cached values for its selected accounts on first refresh — so the meters are populated before it has issued a single request, and switching back to an account polled earlier repaints with no request at all — and only polls an account whose timestamp is older than the interval. Keying by account rather than by provider family is what lets two sessions on two different Anthropic logins coexist: keyed by family, each looked like an account switch to the other, so they evicted each other's values and re-polled every cycle.
An account answering 429 is parked for fifteen minutes (tracked per account, so a rate-limited Anthropic never stops codex from updating) and stops counting as pending, since retrying harder is what earns the rate limit in the first place. Every cache entry is keyed to a fingerprint (a sha256 prefix, never the token itself) of the credential that fetched it: switching accounts — or rotating a token — discards that entry's numbers and backoff and polls immediately, so an exhausted old account's meters never masquerade as the new account's. A logged-out account fails the same check, so the file garbage-collects itself.
Requires a Nerd Font new enough to include the codicon brand glyphs (v3.5.0+); older fonts render them as replacement boxes.
Cache-hit celebration
Whenever one assistant response reaches a prompt-cache hit rate of at least 96%, a temporary module is appended after context usage for about two seconds:
gpt-5.6-sol | pi-extensions:main | 135k/272k | ⚡96%·CACHE·HIT
Only the ⚡96%·CACHE·HIT badge animates, at 60 ms per frame. The existing model, repository, context, separators, worktree, and session-ID rendering do not change.
Animation styles
/statusline → Cache celebration cycles through off and five animations:
| Style | Motion |
|---|---|
flash |
The whole badge alternates between the theme's two celebration colors (the default, and what shipped before 0.4.0) |
wave |
A bright crest sweeps left to right, trailing back into the base color |
pulse |
The whole badge ramps between dim and bright on a triangle wave |
rainbow |
A full-spectrum gradient rotates along the characters |
sparkle |
Random characters flare to the highlight color and decay |
Every style except rainbow is derived from the active theme's two celebration roles, so they recolor with the theme — in the white theme they stay greyscale. rainbow is full-spectrum by design and ignores the palette.
Selecting the row previews it live: the real statusline below the menu loops the badge at 96% so you see the final rendering in context, and the loop stops as soon as you move to another row or close the menu. Choosing off keeps your style, so turning the celebration back on restores it.
To compare styles outside pi, or to iterate on their implementations:
npm run demo:celebrations # every style on a real statusline
npm run demo:celebrations -- --matrix # every style against every theme
That script imports the shipped styles directly, so editing celebration-styles.ts and re-running shows exactly what the extension will render.
The rate is evaluated per provider response as:
cacheRead / (input + cacheRead + cacheWrite)
Output and reasoning tokens are excluded because they are not prompt-cache candidates. A zero-token denominator does not trigger the effect, exactly 96% does, and only the displayed percentage is rounded. Another qualifying response during the animation restarts it from frame zero with the new percentage. After expiry, the original statusline is restored exactly.
Worktree/PR tracking behavior
- Only worktrees directly under the configured worktree root and touched on the active Pi session branch are included. Absolute,
~/, and$HOME/spellings of that root all match. - Worktree paths appearing only inside Bash heredoc payloads are ignored.
- Merged and closed PR worktrees are hidden.
- State is rebuilt on reload and tree navigation; deleted worktrees are pruned.
- Git state refreshes after each turn; branches without a PR are rechecked every five seconds; existing PR metadata is cached for five minutes.
PR lookups use the gh CLI when available and degrade gracefully without it.
Fullscreen TUI mode
Pi's fullscreen renderer only re-emits terminal rows whose rendered content changed. The worktree and session-ID lines are static for the life of a session, so if their cells ever desync from Pi's row cache — stale transcript text, a process sharing the tty, a stray escape sequence — nothing repaints them and the artifact persists.
To repair that promptly without repeatedly clearing the screen, a one-second sweep changes an invisible marker on the fullscreen footer and requests a targeted differential render. Only the statusline rows compare as changed, so they repaint at most once per second even while the rest of Pi is actively rendering. A 30-second forced full redraw remains as a fallback for corruption outside the footer, throttled to at most one every five seconds and also requested at turn boundaries.
Both repair layers are skipped entirely in regular TUI mode, which reprints its whole block each frame and therefore self-heals. Only the slower forced fallback can drop a scrollback text selection highlight for a single frame.
Install
pi install npm:@hank-warren/pi-statusline
License
MIT — see LICENSE.