Conversation
With several concurrent Claude Code sessions every statusline invocation is a fresh process, so per-render fixed costs multiply by session count. - terminal: probe each ancestor with a single `ps -o ppid=,tty=` call instead of two spawns per ancestor; the walk runs per render on macOS where numeric widths are deliberately not persisted - usage-fetch, claude-service-status: load https-proxy-agent (and its transitive graph) on demand, only when a proxy is actually configured Benchmark (13MB transcript, 4 parallel workers x 20 renders via stdin): 27.2 -> 20.6 CPU-seconds total, 0.34 -> 0.26 CPU-s per render (-24%), wall 7.5s -> 5.6s. node --cpu-prof shows the undici/proxy graph gone from startup (compileForInternalLoader 40.7ms -> 7.5ms per render). Co-Authored-By: Claude Code <noreply@anthropic.com>
#1) * feat(widgets): add Tokens Last Turn widget for per-message token usage Implements sirmalloc#44: a widget showing token usage for the most recent assistant message only. The usage is collected in the shared single-pass transcript scan (getTranscriptAnalysis) behind an includeLastTurnTokens opt-in and is deduplicated by message.id: Claude Code writes one JSONL entry per content block of a single API call, all sharing one message.id, so entries are grouped per id and only the highest output_tokens is kept instead of summing every entry (sirmalloc#549). Streaming partials resolve to the finalized value, and the in-flight call still counts while it streams. Co-Authored-By: Claude Code <noreply@anthropic.com> * fix(jsonl): keep sidechain and API-error usage out of the last-turn widget trackLastTurnTokens now runs only for main-chain usage entries, matching the recency rule already used for context length. While a Task subagent runs, its assistant entries are the newest usage rows in the transcript, so without the gate Turn: reported the subagent call as the user's turn. Also documents the widget in docs/USAGE.md. Co-Authored-By: Claude Code <noreply@anthropic.com> * ci: trigger workflow registration for PR branch Co-Authored-By: Claude Code <noreply@anthropic.com> * ci: trigger CI after enabling Actions on the fork Co-Authored-By: Claude Code <noreply@anthropic.com> --------- Co-authored-by: axisrow <axisrow@users.noreply.github.com> Co-authored-by: Claude Code <noreply@anthropic.com>
…wiring - terminal: check the ancestor TTY before reassigning pid so each loop iteration reads as "one ps answer, one pid"; no call-order change - claude-service-status: cover the lazy proxy-agent branch with tests (agent attached when HTTPS_PROXY is set, absent otherwise), matching the usage-fetch proxy coverage Co-Authored-By: Claude Code <noreply@anthropic.com>
…ation - ps: use two -o flags instead of the comma list; FreeBSD's parser treats everything after the first '=' as one header, which collapsed the combined form into a single bogus column and dropped the probe to the tput fallback. Still one spawn per ancestor. - walk: start at process.ppid and read each generation's TTY before terminating on its PPID, restoring the old two-spawn walk's generation coverage (1..8) and keeping a dead-end PPID from hiding a valid width on the same ps row. Known narrowing vs the old sequence: if the combined lookup fails entirely, the walk stops (the old two-call flow could still continue on the separately obtained PPID). Co-Authored-By: Claude Code <noreply@anthropic.com>
* perf: cut per-repaint subprocess and module-graph cost (sirmalloc#397) With several concurrent Claude Code sessions every statusline invocation is a fresh process, so per-render fixed costs multiply by session count. - terminal: probe each ancestor with a single `ps -o ppid=,tty=` call instead of two spawns per ancestor; the walk runs per render on macOS where numeric widths are deliberately not persisted - usage-fetch, claude-service-status: load https-proxy-agent (and its transitive graph) on demand, only when a proxy is actually configured Benchmark (13MB transcript, 4 parallel workers x 20 renders via stdin): 27.2 -> 20.6 CPU-seconds total, 0.34 -> 0.26 CPU-s per render (-24%), wall 7.5s -> 5.6s. node --cpu-prof shows the undici/proxy graph gone from startup (compileForInternalLoader 40.7ms -> 7.5ms per render). Co-Authored-By: Claude Code <noreply@anthropic.com> * fix(review): clarify width-walk invariant and test status-page proxy wiring - terminal: check the ancestor TTY before reassigning pid so each loop iteration reads as "one ps answer, one pid"; no call-order change - claude-service-status: cover the lazy proxy-agent branch with tests (agent attached when HTTPS_PROXY is set, absent otherwise), matching the usage-fetch proxy coverage Co-Authored-By: Claude Code <noreply@anthropic.com> * fix(terminal): restore walk parity and BSD-safe ps format per verification - ps: use two -o flags instead of the comma list; FreeBSD's parser treats everything after the first '=' as one header, which collapsed the combined form into a single bogus column and dropped the probe to the tput fallback. Still one spawn per ancestor. - walk: start at process.ppid and read each generation's TTY before terminating on its PPID, restoring the old two-spawn walk's generation coverage (1..8) and keeping a dead-end PPID from hiding a valid width on the same ps row. Known narrowing vs the old sequence: if the combined lookup fails entirely, the walk stops (the old two-call flow could still continue on the separately obtained PPID). Co-Authored-By: Claude Code <noreply@anthropic.com> --------- Co-authored-by: axisrow <axisrow@users.noreply.github.com> Co-authored-by: Claude Code <noreply@anthropic.com>
…der path (#3) * fix(review): clarify width-walk invariant and test status-page proxy wiring - terminal: check the ancestor TTY before reassigning pid so each loop iteration reads as "one ps answer, one pid"; no call-order change - claude-service-status: cover the lazy proxy-agent branch with tests (agent attached when HTTPS_PROXY is set, absent otherwise), matching the usage-fetch proxy coverage Co-Authored-By: Claude Code <noreply@anthropic.com> * perf(widgets): load Ink editors only when opened Extend the lazy TUI boundary from 35440e4 to widget editors: the registry previously initialized Ink's useInput/reconciler/Yoga graph on every piped render. Move editor implementations into lazy chunks and retain synchronous renderEditor APIs through React Suspense. Add an isolated startup regression check and wait for lazy editors in interaction tests.\n\nVerified: bun test (2361 pass), bun run lint, Node/Bun built renders and 13MB four-worker benchmarks. Node14 distribution build failure predates this change and is tracked separately in the perf verification report. * docs(perf): record PR 2 adversarial findings and follow-up benchmarks --------- Co-authored-by: axisrow <axisrow@users.noreply.github.com> Co-authored-by: Claude Code <noreply@anthropic.com>
The fork's main had PR #2's pre-review revision (177686f); this brings in the final revision from ao/ccstatusline-2/root: per-repaint subprocess and module-graph cost cut (sirmalloc#397), the width-walk invariant clarification, and the BSD-safe two-flag ps format with restored walk parity (0fc829c). Conflicted on src/utils/terminal.ts: kept the TTY-probe-before-break ordering from the final revision and dropped the obsolete duplicate check after the parentPid break. Co-Authored-By: Claude Code <noreply@anthropic.com>
* fix(jsonl): dedup cumulative token totals by message.id (#8, sirmalloc#549) Claude Code writes one JSONL entry per content block of a single API call, all sharing one message.id. The cumulative token path summed every entry, inflating Tokens Input/Output/Total by up to ~1.84x. Mirror the grouping already used for Tokens Last Turn: consecutive counted entries sharing an id contribute one usage (prompt side once, output as the running max). - transcripts without duplicate-id entries are unchanged (existing tests) - streaming partials resolve to the finalized value, counted once mid-stream - sidechain/API-error rows keep their current treatment in sums and recency Added vitest cases on a synthetic sirmalloc#549-shaped transcript asserting exact cumulative totals (12 in / 292 out / 16660 total vs inflated 16/393/...), streaming partials with ids, and a sidechain per-content-block group. Co-Authored-By: Claude Code <noreply@anthropic.com> * docs(jsonl): note the pinned recency snapshot within a message.id group Review follow-up on #9: the duplicate early return deliberately keeps the context-length snapshot from the group's first counted entry. Write the invariant down at the return so it survives as a conscious assumption. Co-Authored-By: Claude Code <noreply@anthropic.com> --------- Co-authored-by: axisrow <axisrow@users.noreply.github.com> Co-authored-by: Claude Code <noreply@anthropic.com>
…es (#10) * feat(widgets): compact label presets with global and per-widget toggles Add abbreviated label presets for labeled widgets, complementary to the free-form label editing proposed upstream (sirmalloc#601): - New `compactLabels` settings flag (default off). Additive v4 key: old configs parse unchanged and render identically, no version bump needed. - Presets live in one map (`Model:` -> `M:`, `Context:` -> `Ctx:`, `Cost:` -> `` — the value's own `$` is the glyph) inside `formatRawOrLabeledValue`, so every widget routed through the helper picks them up with no per-widget changes. Model, Session Cost, and Context Bar moved onto the helper. - One-click global toggle in Global Overrides `(j)`; per-widget override via `(j) compact label` in the items editor (metadata `compactLabel`), which wins over the global setting in both directions. Capability for the editor keybind is derived from a cached preview probe of the widget registry. - Extend the Git Branch width-cap pattern to Current Working Dir and Session Name (`w` max-width keybind). - Document per-widget padding/separator/label width overhead in docs/USAGE.md; defaults unchanged. Tests: preset resolution, toggle metadata, global injection precedence, migration parity (v1->v4 and schema-less v4 parse), registry-wide `j` keybind reservation, capable-type probe sanity, Global Overrides `(j)` keystroke test. bun test 2400 pass; bun run lint clean. Co-Authored-By: Claude Code <noreply@anthropic.com> * fix(tui): compact-label toggle cycles over the effective state The per-widget (j) toggle only wrote/removed 'true', so with the global Compact Labels setting on it became a visual no-op: the render-time injection re-compactified the widget after the key was removed, and no row marker showed the effective state. - toggleCompactLabel now takes the settings and cycles the EFFECTIVE state: compact (saved 'true' or inherited global on) -> write 'false'; forced off -> drop the key and inherit; otherwise -> write 'true'. - getCompactLabelModifierText reports the effective state: '(compact label)' / '(compact label: on)' / '(compact label: off)'. - ItemsEditor/input-handlers pass settings through to both. - docs/USAGE.md: fix preset savings arithmetic (Model -4, Context -4). Co-Authored-By: Claude Code <noreply@anthropic.com> * test(renderer): cover (j) toggle forcing labels off while global compact is on Co-Authored-By: Claude Code <noreply@anthropic.com> * style: fix import order in compact-labels renderer test Co-Authored-By: Claude Code <noreply@anthropic.com> --------- Co-authored-by: axisrow <axisrow@users.noreply.github.com> Co-authored-by: Claude Code <noreply@anthropic.com>
…sirmalloc#602) (#11) * feat(cli): non-interactive subcommands for agent-driven configuration (sirmalloc#602) Add `src/utils/cli.ts` and wire it into mode detection in src/ccstatusline.ts: args present + TTY stdin dispatches the CLI before the interactive TUI; piped stdin keeps the statusline render path. Subcommands operating on the same settings file (load/save/migration via src/utils/config.ts): - get [--json] - print the effective post-migration config - widget add <line> <widget> [--index N] [--option value ...] - widget remove <line> <index-or-type> - widget move <line> <index> --to <index> - set <option-path> <value> - global options, JSON or plain string values - validate [--file <path>] - exit 0/1 with a machine-readable report - help Guarantees preserved: mutations refuse to run on an unreadable/invalid config (never overwrite it with in-memory defaults), every write is validate-before-write through SettingsSchema and persists via saveSettings() (atomic temp+rename, symlink write-through). All errors are single-line and machine-parseable with a --json variant. No new dependencies; runtime stays Bun+Node compatible. Vitest coverage (34 cases) for every subcommand including error paths, idempotency, invalid-config refusal, and symlink write-through. Co-Authored-By: Claude Code <noreply@anthropic.com> * fix(cli): review follow-ups - hasOwn option guard, --json argv walk, README section - set <path>: guard option paths with Object.prototype.hasOwnProperty instead of `in`, so inherited keys like toString/constructor cannot pass the check, get stripped by the schema, and report a false success (Node 14-safe; no Object.hasOwn). Regression test: set toString / powerline.constructor fail and leave the file untouched. - extractJsonFlag(): walk argv and treat only a standalone --json as the output flag; tokens filling a value or boolean option are kept intact, so 'widget add 0 custom-text --customText --json' no longer loses its value. Unit tests for the flag/value boundary plus an add-persistence case. - README: new 'Non-Interactive CLI' section documenting the subcommands and noting that get/validate write defaults on a missing settings.json (first run) while never overwriting an existing unreadable/invalid file. Co-Authored-By: Claude Code <noreply@anthropic.com> * docs(cli): note first-run default config creation in help output Adds a line to the CLI usage block so agent loops see that get and validate write the default config when settings.json is missing (the behavior itself is unchanged, matching the TUI first-run contract). Co-Authored-By: Claude Code <noreply@anthropic.com> --------- Co-authored-by: axisrow <axisrow@users.noreply.github.com> Co-authored-by: Claude Code <noreply@anthropic.com>
Fork builds carry the base upstream version plus a .forkN suffix (currently 2.2.30.fork1), so the TUI title, --version output and any build are visibly distinguishable from the npm release. Rebase the suffix onto the new base version at each upstream sync. Co-Authored-By: Claude Code <noreply@anthropic.com>
…only Add a top-level `theme` setting that cycles a POWERLINE_THEMES segment palette across widgets as foreground colors when powerline is disabled. The theme's bg[] array is the vivid palette (fg[] holds the dark text-on-segment colors); ansi16-level bg* names are converted with the existing bgToFg() helper. Semantics: explicit per-widget colors win over the theme, theme beats widget defaults; explicit colors keep their palette slot so the rhythm of the surrounding widgets is stable. Separators and flex separators are neither themed nor consume slots; merged widgets share one slot; preserves-colors widgets keep their intrinsic ANSI output and still advance the slot - both mirroring the powerline renderer. Unset, 'custom', or unknown names disable theming. Additive optional schema key - configs written before it parse unchanged (no version bump); `ccstatusline set theme <name>` works through the existing generic `set` allowlist with zero CLI changes. Closes the feature half of sirmalloc#604. Co-Authored-By: Claude Code <noreply@anthropic.com>
…CLI warnings Follow-up to the regular-mode theme feature: - Parametrized tests: every POWERLINE_THEMES entry (custom excluded) cycles its bg[] palette identically at color levels 1/2/3, plus a guard that every theme ships a non-empty palette at every level. - TUI: the theme selector now also serves regular (non-powerline) mode via a new mode prop - the main menu gains a Theme entry (hidden while powerline is enabled, which has its own picker); None clears the setting; the powerline-only "copy theme to widget colors" flow stays powerline-only. - CLI: validate reports unknown theme names as warnings (valid stays true, warnings array in --json output), set theme warns on unknown names while still writing them - a typo must not invalidate a config. Co-Authored-By: Claude Code <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Addresses the feature half of #604 — applying the built-in theme catalog in regular (non-powerline) mode. The U+E0B0 separator-font fallback (the bug half) is deliberately left for a separate change, so the issue stays open.
The same diff is merged and live-verified in our fork: axisrow#12.
What
Makes the built-in theme catalog usable in regular (non-powerline) mode: a new top-level
themesetting cycles the chosen theme's segment palette across widgets as foreground colors.Semantics
theme: "dracula"(anyPOWERLINE_THEMESname) cyclestheme[level].bg[]as widget foregrounds —bg[]is the vivid segment palette, whilefg[]holds the dark text colors meant to sit on those segments. ansi16-levelbg*names are converted through the existingbgToFg()helper.'custom'/ unknown name → no theming. The level followscolorLevel, using the 16/256/truecolor calibrations already shipped with each theme.ccstatusline set theme <name>/getwork through the existing generic allowlist — zero CLI code changes.Rework: uniform coverage across all themes
POWERLINE_THEMESentry (customexcluded) at every color level 1/2/3 — each theme cycles itsbg[]palette through the identical slot rule — plus a guard that every theme ships a non-empty palette at all levels.modeprop): the main menu gains a "Theme" entry (hidden while powerline mode is enabled — powerline has its own picker), with aNoneentry that clears the setting. The powerline-only "copy theme to widget colors" flow remains powerline-only.validatereports unknown theme names as warnings (validstaystrue;warningsarray in the--jsonpayload);set theme <name>warns on unknown names while still writing them, so a typo never invalidates a config. No schema change — unknown names keep rendering as a silent no-op.Verification
bun test: all theme/menu/CLI tests green (see note below). Covers cycle order, explicit-wins + slot rhythm, separators unthemed, preserves-colors, merged widgets sharing a slot, unset/'custom'/unknown names, the all-themes × all-levels matrix, the regular-mode selector writes (settings.themeset/clear), and the CLI warning paths.bun run lintclean;bun run buildOK.theme: "dracula",colorLevel: 3config: uncolored widgets emit exact truecolor codes (38;2;189;147;249,38;2;248;248;242, …), separators stay unthemed; atheme: "nord"config renders the Nord palette the same way.validatewith an unknown theme → exit 0,OK … (with warnings), machine-readablewarningsarray listing the known themes;set theme nord→get --jsonreports"theme":"nord".Note: the repo's ink-based TUI tests have pre-existing timing flakiness on a loaded machine — the same tests (ItemsEditor, TerminalWidthMenu, ImportPreviewDialog, UsageLocaleEditor) fail intermittently on a clean checkout too. All tests touched by this PR are deterministic and green.
🤖 Generated with Claude Code