TransmuteLabs/Catalyst/tree/main/plugins/catalyst-statusline
catalyst-statusline
Catalyst packaging of the ClaudeCodeMods `statusline` mod for Claude Code, providing a template-driven multi-line status bar with themes, per-axis view settings, hover detail cards, and an interactive `/statusline-mod` picker. Installed via `claude plugin install catalyst-statusline@catalyst` with function hooks enabled.
About this mod
This mod renders a configurable status bar under the Claude Code prompt using a ui.render hook on the PromptHint site. The bar is a template over a segment registry, supporting multiple lines (;; separates lines, || separates segments), named themes (default, plain, powerline, pill, claude-code, codex, mono, or saved themes), and per-axis settings including shape, caps, glyphs, fill, bar, barWidth, palette, thresholds, face, border, overflow, separator, placement, details, model_label, and numbers. Segments include git branch, directory, branch, github, model, context usage, 5h/7d rate limits, session id, and cost. An interactive /statusline-mod picker allows arranging segments, selecting themes, and editing options with undo/redo and draft persistence. Installation: claude plugin install catalyst-statusline@catalyst; requires CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1.
Installation
Check the author's README for the marketplace and plugin name first. Commands may change as the repository evolves.
claude plugin marketplace add TransmuteLabs/Catalyst claude plugin install catalyst-statusline
Original text / README
catalyst-statusline
Catalyst packaging of the ClaudeCodeMods statusline mod (konsta95, MIT, upstream
0.3.2 at c49ad4a; licence in LICENSE-ClaudeCodeMods). Install:
claude plugin install catalyst-statusline@catalyst. The mod needs function hooks
enabled (CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1). Options live in /config; the
picker is /statusline-mod.
Wave A (0.4.0): template, lines, themes, axes
The bar is now a template over a self-describing segment registry, several lines tall, with named themes and per-axis view settings.
- Layout (
template): lines are separated by;;, segments inside a line by||. A segment isid=bodywith{ns.field}variables, e.g.ctx={ctx.text}||model={model.text} ;; cost={cost.text}. A template without;;is one line, so 0.2.0 settings read unchanged. A segment is an atom: one absent variable removes it whole;…(no data yet) and~(stale) keep it. Unknown variables show as{?name}in the bar and in the diagnostics. The separator is drawn by the renderer between the surviving segments — a missing source takes its separator with it, no doubles, none hanging on the edges. Narrow windows evict segments per line byevictOrder; the last survivor stays. Beyond the band'smaxRowsthe extra lines are not drawn, with a diagnostic. - Themes (
theme):default,plain,powerline,pill,claude-code,codex,mono, or a theme saved from the picker (stored in$.store). An unknown name is a diagnostic and the last good configuration applies. - View axes (each
themeby default, an explicit value wins):shape(plain/lean/pill/powerline/classic),caps,glyphs(withasciikeeping every decoration byte ASCII),fill,bar,barWidth,palette(semantic/mono/codex/claude-code),thresholds,face,border,overflow(evict/wrap),separator. Pinned axes with their own options:placement(abovethe prompt, several lines, yields to a survey;hintdraws the first line under the prompt with the engine's hint after it),details(hovercards over the row above /off),model_label(rawkeeps the[1m]tag,displaydrops it), andnumbers. numbers(raw/compact):rawprints the source's own figure verbatim;compactshortens (k/M) but never writes a nonzero value as zero.segmentColors:id=colour;id=colour; a bad entry is dropped one by one with a diagnostic. Segments whose colour carries their threshold scale (context, the rate-limit windows) are offered no colour.- Picker
/statusline-mod: a pane with a row of pills per line and the buttons ◀ ▶ ▲ ▼ ✕ (hotkeyshlkjx) for the pill in focus, where ✕ takes it out of its line;✕ строкаdeletes any line but the last one and names the elements it frees, and+ строкаstops at the rows the band holds once the band has been measured. The «Элементы» tab has a filter and a target line (◀ строка/строка ▶); an element not placed goes to the end of the target line, and a placed one (its pill reads✓ id · стр.N) leaves every line it stands in. Layout presets (default,ClaudeCodeStatusline,claude-hud) and a preview rendered by the same builder as the band. The panel draws on the terminal, the desktop, VS Code and mobile. On a surface with Select, «Вид» has one Select per axis; on mobile (no Select, no Input) the axes are pills and each settings text field is replaced by the lineввод текста здесь недоступен — поле: /config catalyst-statusline.<field>; the element search is not a setting and readsпоиск здесь недоступен. «Темы» is a button per theme on every surface; the theme in focus is shown in the preview without changing the draft. Every press either changes the draft, the tab or the level, closes the pane, or says why it did nothing. Under the tabs a state line readspath · цель: строка N · черновик: изменено полей: N не сохранено(N counts the changed fields, not the presses; orсохранено); under the bottom row a key hint. The bottom row:Сохранить(s) ·↶ Шаг назад(z, the draft's own undo, 50 steps, kept with the draft) ·Отменить все правки(only with unsaved edits; returns the draft to the saved state and keeps the pane open; itself a step↶ Шаг назадtakes back) ·Закрыть(q, keeps the draft) ·Откатить сохранение(the saves' undo stack of 30) ·Сбросить к теме, and on the element level← Назад(b) back to the tab it came from. Esc closes and keeps the draft; the next open says the unsaved draft is continued. Saving writes the plugin's own/configrows; a{ deny }is shown in the panel and the undo record keeps only the fields actually written; the draft and the open flag live under the session id, so a reload restores them./statusline-mod resetwrites every field back to its default.
Everything below is the upstream README of the base the mod was merged from; where it and the section above disagree, the section above is the 0.4.0 mod.
statusline (upstream)
ClaudeCodeStatusline as a
function-hook mod. The shell version is a statusLine command that Claude Code spawns
on every status-line update (a new assistant message is one trigger among session
start, /compact, permission-mode and vim-mode changes and timers, debounced at
300 ms), feeding it a JSON payload on stdin and printing the line it returns under the
prompt. This version runs inside the process: a ui.render hook on the PromptHint
site, the dim hint line under the prompt where the shell line drew, builds the same
segments from the session's own nouns ($.session.cwd(), repo(), model(),
usage(), id()), so there is no process to spawn and no payload to parse. Each
segment reveals its details when the pointer is over it, and /statusline-mod picks
the segments, their order and the options.
What it draws
One row: the chosen segments separated by | and painted in the chosen scheme, then
the engine's own hint (? for shortcuts, esc to interrupt) dim after them. Claude
Code draws its own pills, such as the permission mode, to the left of the bar.
| Segment | On by default | Drawn as | Detail on hover |
| --- | --- | --- | --- |
| git branch | yes | repo(branch) from the nearest .git/HEAD (worktree .git files followed, at most 12 levels up); the directory name when there is no repository | repository, branch, GitHub owner/name from the remote, and the cwd |
| directory | no | the name of the current directory | the full path |
| branch | no | the branch alone | the branch and its repository |
| github | no | owner/name from the GitHub remote | the same |
| model | yes | the display name derived from the model id with its spaces removed, as the shell version prints it (claude-opus-5-5[1m] reads Opus5.5), then the effort level once a main-loop turn.step has carried one | the model id and the effort level |
| context | yes | used/window tokens, coloured by the percentage used | the token count and the percentage |
| 5h | yes | percentage of the five-hour window used | the exact percentage and when it resets |
| 7d | yes | percentage of the seven-day window used | the same for the weekly window |
| session | yes | the session id | the id, which is also the transcript file name |
| cost | yes | $0.00 once the session has cost anything | the cost to four decimals |
A segment whose noun failed is drawn dim as <id>! with the error in its detail; a
segment with nothing to show yet (no cost, no rate-limit window) is left out. With
every segment off, the hint line is passed through unchanged.
With details on, hovering a segment shows its detail on a one-row card over the row
above the bar, which in the fullscreen UI is the prompt box's bottom rule, starting at
the bar's first column. The card is placed out of the layout, so showing it moves
nothing on screen.
The bar is rebuilt after session.start, as every model request of the main loop
goes out (turn.step, while the request is in flight, so the request waits for
nothing), after every turn.complete, after /model, /compact and /clear return,
and after the model row of /config is written. Once each main-loop response has
arrived, the bar reads the usage again, one call before the response's tools run, so
the context the response was answered over, the rate limits and the cost show while
those tools run. The classic command showed them at the same point, because Claude Code
re-ran it on every new message. A model switch shows at the latest from the first
request after it. A subagent's request changes nothing on the bar: it names its own
model and effort, not the session's. Rebuilds overlap, and one that finishes after a
newer one is dropped.
/statusline-mod
In an interactive session /statusline-mod opens a picker in the band directly above
the prompt, across its full width, where Claude Code draws its own surveys: a preview
of the bar, a row per segment, the chosen ones first in their order and then the rest,
and the options. The line under the prompt follows every change at once.
| Key | Pressed in | Does |
| --- | --- | --- |
| 1 to 0 | the empty prompt or the band | toggles the segment on that row |
| ctrl+x tab | the prompt | gives the band the keys, with the focus on the first row |
| ↑ ↓ | the band | walk the rows and the options, wrapping at either end |
| Enter | the band | presses the row or option in focus |
| u d | the band | move the segment that last had the focus up or down |
| s h p | the band | the next scheme, hover details on or off, the pinned copy on or off |
| r | the band | resets the segments |
| q | the band | closes the picker |
| Esc | the band | gives the keys back to the prompt and leaves the picker open |
A toggled segment moves to the end of the chosen ones or back among the rest, and u,
d and reset move rows too; through each of these the focus stays on the segment it
was on. With the focus on Move up or Move down, Enter moves the same segment again. The
picker closes with q or when a prompt is sent, stays open through the module reloads
an option change causes, and is not drawn in another session. In a band too short for
a row per segment the segments are laid out in a grid without their descriptions, and
the footer is drawn while it fits.
The chosen segments and their order are kept in the plugin's store and persist across
sessions. The options are the plugin's own /config rows: the picker writes them, and
Claude Code reloads the mod with the new value.
/statusline-mod show prints the current line and options, and /statusline-mod reset
restores the default segments. In a non-interactive session the bare command prints
what show prints.
Options
| Key | Type | Default | Meaning |
| --- | --- | --- | --- |
| scheme | claude-code, codex or mono | claude-code | The palette. claude-code uses the hues the shell version copied from the theme; mono paints nothing and dims every segment. |
| details | boolean | true | Reveal a segment's details on hover. Needs the fullscreen terminal UI with mouse tracking. |
| pin_status | boolean | false | Also pin the bar as plain text under the prompt through $.ui.status, where no hover is needed. |
Run it from a clone with
CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1 claude --plugin-dir mods/statusline
The mod neither reads nor changes the statusLine setting. To use it in place of the
shell version, remove the statusLine entry from settings.json.
Measured
Claude Code 2.1.280, 2026-09-22. The mod rows come from three sessions, each on Opus 5.5 in a 140 by 40 tmux pane with one short turn: two in the fullscreen UI and one in the classic UI. The shell row is three runs of 30 spawns of the classic command with a fixed payload on 2026-09-21.
| What | Value |
| --- | --- |
| Bar on screen after the process was spawned | 2.0 to 2.6 s (the screen was read every 0.5 s) |
| First render after session.start | 183 to 508 ms |
| Cold gather of the nouns, twice at start | 120 to 318 ms |
| Warm gather after a turn | 16 to 42 ms |
| Usage read once a response has arrived | 0.53 and 0.73 ms, against 31.2 and 28.4 ms for the whole gathers as the same two requests went out (one Haiku 4.5 probe session) |
| Render of the hint line | median 0.47 ms, 0.21 to 11.13 ms, across 21 renders |
| Shell version, one statusLine spawn | 132.4 ms median of 30, with 122.0 and 137.1 ms in two other runs of 30 (bare node spawn 119.4 ms median of 30); paid on every status-line update, asynchronously and off the renderer's path, so not on the same clock as the render row |
| Kit, one run from this repository | first mount 91.1 ms, 200 cached renders median 1.41 ms, p90 2.07 ms, max 12.1 ms |
The hover reveal was observed in both fullscreen sessions. With all seven default segments on the bar, the pointer was moved to the middle of each segment by writing SGR mouse reports into the pane. Each position showed that segment's detail and no other, on the row above the bar, and the bar stayed on the last row. With the pointer off the bar, before and after, no detail was shown. The one other row that changed was the engine's notice line, whose tmux focus-events notice was also on screen with the pointer off the bar. The same sweep over the previous build, which laid the details out in the hint line itself, showed the detail of three of the seven segments: each reveal wrapped that line and shifted the bottom 15 rows of the screen, taking the bar from under the pointer.
In the classic scrolling UI (CLAUDE_CODE_DISABLE_ALTERNATE_SCREEN=1) the bar is drawn
in the same line, but tmux reported no mouse mode enabled by Claude Code, where the
fullscreen sessions had any-motion and SGR reporting on. No pointer position reaches
the session there, so nothing reveals.
The model switch was measured on 2026-09-22 in four Haiku 4.5 sessions in a 140 by 40
pty, driven by claude_live_probe, two on this build and two on 0.2.0. After
/model sonnet this build redrew the bar as Sonnet5, where 0.2.0 redrew it as
Haiku4.5. In a turn of two model requests, a file read and then the answer, this
build redrew the context as 37K/200K after the read and before the answer, where 0.2.0
kept 0/200K until the turn ended.
The context during a response's tools, /clear and /compact were measured the same
day in eight more such sessions, four on this build and four on 0.2.1. In a turn whose
first response ran sleep 20, this build showed 42K/200K and the cost for the whole
sleep, where 0.2.1 showed 0/200K until the next request went out. On forks of that
conversation, /clear made this build show 0/200K and the new session id, where 0.2.1
kept 42K/200K and the old id. After /compact both builds kept 42K/200K, because the
engine goes on reporting the last response's figure; this build showed the cost growing
from $0.09 to $0.10 with the compaction's own request, and 0.2.1 kept $0.09.
The picker was driven on 2026-09-22 in Haiku 4.5 sessions on a 280 by 69 pty, the
owner's fullscreen layout, recording after each key the cells drawn inverted, which is
where the focus is. There the pane of 0.2.2 docked beside the transcript, and once its
Scheme select had the focus, ↓ cycled the schemes and the two options after it were
never reached. In the band, ↓ walked the ten rows and the seven options and wrapped.
u, u, d, d moved the context segment and the focus stayed on it at each new
row, as it did through a toggle off and back on, a digit toggling another row, and a
reset. A digit typed into the empty prompt toggled its row, and the next key typed
reached the prompt. q closed the picker, and so did sending a prompt. On a 100 by 30
pty the band drew the grid with the footer, and the digits and the focus worked the
same.
Two engine behaviours shaped the picker (2.1.280). After a module reload the engine
asked for the band twice before the first draw was done; in 11 of 15 runs that reloaded
the module, every press on the band was dropped afterwards while the focus still moved.
The mod finishes the band's draws in the order they began, and presses then survived
the reload in 5 of 5 such runs and in all four reloads of the final runs. And the focus
keeps its place in the band rather than its element: a plugin's $.ui.focus lands on
the element where the band shows it before the redraw a press asked for, so the mod
draws the segment the focus was on under a key no drawing had yet, which the engine
waits for (13 ms in a probe), and the focus lands on the redrawn row.
The line under the prompt was recorded frame by frame on 2026-09-22 and 2026-09-23, in Haiku 4.5 sessions on a 280 by 69 pty whose output was replayed through a terminal emulator. Each time Claude Code 2.1.280 dispatches a hooked hint line again, for a change of its props or a redraw the plugin asked for, one frame draws the engine's own hint row with the bar drawn last on the row below it, pushing the prompt up a row, before the new answer puts the bar back beside the pill: a copy of the bar one row down, for one frame. In a turn of six tool calls, a session with no hook on the line drew no such frame in two runs, and a hook drawing fixed text that never asked for a redraw drew one at each change of the engine's hint, among them the first key typed into the empty prompt during a streamed turn. 0.3.0 asked for a redraw after every refresh, two per model request, and the six-tool turn drew 16, 8 of them with nothing on the bar changed. This build asks only when what the bar draws has changed: the same turn drew 7 in a copy carrying the change and 9 on this build. The 2 in the second changed no cell of the bar, and without the mod's redraws that turn drew none mid-turn, so by elimination they were a hover card's figures, the exact tokens or the cost to four places. Typing during a streamed turn drew 6 on 0.3.0 and 5 on this build: three at hint changes, one at a figure's change, and one at the turn's end with the bar unchanged, which the fixed-text hook drew too.
The context after an interrupted turn was measured on 2026-09-23 in the same kind of
Haiku 4.5 sessions, with every read the mod made traced. When Esc interrupted a turn,
turn.complete came with isAborted true and the engine answered $.session.usage() with
the window and no token count, 41 ms after the read at the step's end had 37,105; its
typings keep a missing count for a fresh or just-compacted window. In the same situation
the engine's classic status line payload kept total_input_tokens at 37,191. 0.3.0 and
0.3.1 drew the missing count as 0/200K until the next response, in 6 of 6 interrupted
runs (three each) and none of 6 finished ones. This build keeps the figure on the bar after an interrupted turn: the
next interrupted run kept 37K/200K until the next prompt, as did a run left to finish.
Limits
- Hover is applied by the terminal surface. No hook runs when the pointer moves, so the mod cannot observe or log a reveal, and the kit cannot test one; the kit tests check the tree (every segment names a scope, each scope has a card placed out of the layout over the row above, a malformed hover tree is refused).
- A card is one row. Claude Code clips an absolutely placed box at the edge of its region, so a detail longer than the room from the bar's first column to the right edge of the terminal is cut off there.
- The mods API gives the model id only; the display name is derived from it, and an
id that is not
claude-*is shown as it came. - The effort level is unknown until the first
turn.step. The/configrows are read once in case one carries it; in each measured session none of the 43 rows did. - After
/compactthe context keeps the figure of the last response until the next one arrives, because that is the figure the engine reports (measured on 2.1.280). The types describe it as the status line's owntotal_input_tokens. - A switch made in the
/modelpicker was not observed live, because the probe types one line and cannot pick an entry. Ifcommand.runresolves before the pick, the bar keeps the old model until the next model request redraws it. PROBE_RUNin the environment makesturn.completewrite a JSON timing dump, underout/<PROBE_RUN>unlessPROBE_OUTnames another directory. Both exist for measurement and are otherwise inert.- The picker draws in the band above the prompt, which Claude Code raises on the terminal only, as it does the hint line the bar draws in.
- A plugin cannot give the band the keys (2.1.280 answers
that site does not hold the keyboard), so the picker opens with the keys in the prompt: the digits work from there, the arrows and Enter after ctrl+x tab, as the picker's footer says. - While the picker is open, a digit typed first into the empty prompt toggles a row, as a survey's digits do. The picker closes once a prompt is sent.
- The rows are re-sorted after a toggle, the chosen segments first, so after one a digit may name another segment.
- The kit has no implementation of a plugin's own
$.ui.focus(2.1.280: the call throws, and the test'son('ui.focus')never sees it). The kit checks that the segment is drawn under a new key; the focus landing on it was observed live. - The typings say a tree taller than the band scrolls and arms no digit. At 100 columns the grid needs six rows, eight with the footer, and a shorter band scrolls it.
- Each change of the engine's hint still shows the copy of the bar one row down for a frame: the first key typed into the empty prompt during a turn, sending a prompt, and the turn's end. A hook cannot avoid it, since one drawing fixed text shows it too; only Claude Code can. It is reported on the mods feedback thread, https://github.com/anthropics/claude-code/issues/91870#issuecomment-5790602695.
- A change to a hover card's figures alone still asks for a redraw, so the cards stay current, and costs that frame too.
- After an interrupted turn the next request still shows
0/200Kuntil its response arrives, since the engine has no count then either: the screen read 0.8 s after the prompt was sent showed it, and the one 0.8 s later showed the new figure. A finished turn,/clearand/compactshow what the engine reports. - The limit of two unsettled store writes per session key holds per module
environment, not per host. A reload starts a new environment with an empty count
while writes of the old one may still be pending: module state does not survive a
reload, and
$.store— the only state shared across environments — is the very operation that hangs. - Pruning keeps the 32 newest session snapshots. A session older than those
resumed with
--resumestarts the bar from zero. session.endraces its pending writes against a successful three-second/bin/sleep,clock.after(3000), and the hook-ownedclock.sleep(3000)withnext.signal. A refused or non-zero process sleep does not end the wait; the other bounds still apply. The hook-owned sleep also ends on cancellation and spends the hook's budget. An unsettled sent write is reported as possibly unstored; "the sleep bound did not run" is added only after a process sleep refusal.- A snapshot from a process whose clock is more than a day ahead is overwritten
by this process's next write;
session-snapshot-clocknames the affected key.