Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
- [Features](#-features)
- [Localizations](#-localizations)
- [Quick Start](#-quick-start)
- [Non-Interactive CLI (for agents & scripts)](#-non-interactive-cli-for-agents--scripts)
- [Windows Support](docs/WINDOWS.md)
- [Usage](docs/USAGE.md)
- [Development](docs/DEVELOPMENT.md)
Expand Down Expand Up @@ -400,6 +401,37 @@ For pinned installs, launch the TUI with `npx -y ccstatusline@latest` or `bunx -

</details>

## 🤖 Non-Interactive CLI (for agents & scripts)

Every TUI setting is also scriptable without launching the interactive UI. All
subcommands operate on the same `settings.json` (honor `--config <path>`),
refuse to modify an unreadable/invalid config, and validate every change
before writing. Errors are single-line; add `--json` to any command for a
machine-readable single-line payload.

```bash
ccstatusline get [--json] # print the effective (post-migration) config
ccstatusline widget add <line> <widget> [--index N] [--option value ...]
ccstatusline widget remove <line> <index-or-type>
ccstatusline widget move <line> <index> --to <index>
ccstatusline set <option-path> <value> # global options; value is JSON or a plain string
ccstatusline validate [--file <path>] # exit 0/1 with a machine-readable report
ccstatusline help
```

Indices are 0-based. `widget add` accepts the widget options the TUI exposes
(`--color`, `--customText`, `--bold`, `--maxWidth`, `--metadata key=value`, …).

`set theme <name>` applies one of the built-in powerline themes (`nord`,
`dracula`, `tokyonight`, …) to regular (non-powerline) mode as foreground
colors only, cycling the theme's segment palette across widgets. Explicit
per-widget colors win; separators are left untouched; `custom` or an unknown
name disables theming.

> **Note:** `get` and `validate` read through the same loader the TUI uses, so
> on a missing `settings.json` they write the default config on that first run
> (the file is never overwritten when it exists but is unreadable or invalid).

## 🤝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.
Expand Down
17 changes: 17 additions & 0 deletions docs/USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ ccstatusline --version
### Tokens, Usage & Context

- **Tokens Input** / **Tokens Output** / **Tokens Cached** / **Tokens Total** - Show current-session token counts. Input/output prefer cumulative transcript metrics and fall back to `context_window.total_input_tokens` / `context_window.total_output_tokens` when transcript metrics are unavailable; cached/total use transcript metrics.
- **Tokens Last Turn** - Show token usage (input + output + cache) for the most recent main-chain assistant API call only, instead of the cumulative session total. Claude Code transcripts record one entry per content block of a call sharing a single `message.id`, so entries are deduplicated per call rather than summed. Sidechain (subagent) and API-error entries are ignored, so the widget keeps reporting your last turn while a subagent runs.
- **Cache Hit Rate** / **Cache Read** / **Cache Write** - Show prompt-cache efficiency. Cache Hit Rate uses cache reads divided by cache reads plus cache writes; Cache Read and Cache Write include each value's share of prompt context. They default to the latest turn from `context_window.current_usage`, can switch to cumulative session totals, and can hide when empty.
- **Cache Timer** - Estimate time remaining before the current prompt-cache entry expires. It shows `HOT` while a main-chain turn is active, then counts down from the latest assistant request with cache activity and becomes `COLD` just before expiry. The default TTL is 5 minutes; it can switch to 1 hour, hide when no cache anchor is available, and customize the glyph for each state. Because Claude Code transcripts expose cache token activity rather than the actual expiry timestamp, the countdown is best effort.
- **Input Speed** / **Output Speed** / **Total Speed** - Show session-average token throughput with an optional per-widget rolling window (`0-120` seconds; `0` = full-session average).
Expand Down Expand Up @@ -112,6 +113,17 @@ Configure global formatting preferences that apply to all widgets:
- Press **(s)** to edit separator
- Manual separators look past widgets that render empty, so the intended separator remains between visible neighbors without duplicating an earlier visible boundary. Inherited separator colors come from the actual preceding visible widget.

<details>
<summary><b>Per-widget width overhead (audit)</b></summary>

- **Default padding is empty** (`defaultPadding` unset), so widgets add no padding unless you configure it. A non-empty padding of N characters adds up to 2N columns per widget (one per enabled side).
- **Manual `|` separators render as ` | `** (3 columns); in Powerline mode separator widgets are ignored and replaced by the powerline separators.
- **Labels are the dominant fixed cost** of labeled widgets. Defaults: `Model: ` (7), `Ctx: ` (5), `Cost: ` (6), token widgets `In: `/`Out: `/`Total: `/`Cached: ` (4–8), cache widgets `Cache Read: `/`Cache Write: `/`Cache Hit: ` (10–12), timers `Block: `/`Reset: `/`Cache: ` (7) and `Weekly Reset: ` (13), usage widgets `Session: `/`Weekly: `/`Weekly Sonnet: `/`Weekly Opus: `/`Weekly Fable: ` (8–15).
- **Compact Labels** (above) trims the presets it covers (`Model:` −4, `Context:` −4, `Cost:` −6, label gone — the value's own `$` remains); Minimalist Mode strips labels entirely.
- **Unbounded-content widgets** (paths, names, URLs) can overflow a narrow terminal: Git Branch, Git Root Dir, Current Working Dir, and Session Name support a per-widget max-width cap — select the widget in the line editor and press **(w)idth**. The line renderer truncates the whole line with an ellipsis regardless.

</details>

<details>
<summary><b>Global Formatting Options</b></summary>

Expand All @@ -121,6 +133,10 @@ Configure global formatting preferences that apply to all widgets:
- Press **(o)** to toggle
- **Minimalist Mode** - Force widgets into raw-value rendering globally for a cleaner, label-free status line
- Press **(m)** to toggle
- **Compact Labels** - Use short label presets on labeled widgets: `Model:` → `M:`, `Context:` → `Ctx:`, `Cost:` → `$`
- Press **(j)** to toggle; off by default, so existing configs render exactly as before
- Per-widget override: select a labeled widget in the line editor and press **(j) compact label** to force it on/off regardless of the global setting
- Labels without a preset (e.g. `In:`, `Cached:`) keep their default form; presets live in `COMPACT_LABELS` in `src/widgets/shared/raw-or-labeled.ts`
- **Number Formatting** - Choose precise, compact, or whole-number output independently for token, speed, percent, memory, and cost values
- Press **(n)** to configure each number type; a global choice overrides per-widget formatting for that type
- **Override Foreground Color** - Force all widgets to use the same text color, or a whole-line **gradient** (see below)
Expand Down Expand Up @@ -250,6 +266,7 @@ Common controls in the line editor:
- `Space` cycle a manual separator character
- `r` toggle raw value (supported widgets)
- `.` cycle precise/compact/whole number formatting (supported widgets)
- `j` toggle the compact label preset for labeled widgets (Model → `M:`, Context → `Ctx:`, Cost → `$`)
- `m` cycle merge mode (`off` → `merge` → `merge no padding`)
- `x` exclude the selected widget and the rest of its line from shared Powerline column widths (shown only when Powerline auto-alignment is enabled)
- `Esc` go back
Expand Down
308 changes: 308 additions & 0 deletions docs/performance-397-results.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,308 @@
{
"aggregates": {
"base-node": {
"batches": 3,
"renders": 240,
"cpu_ms": 253.76999999999998,
"pooled_p50_ms": 240.19275000318885,
"batch_cpu_ms": [
246.321,
246.197,
268.792
],
"batch_p50_ms": [
231.993,
220.007,
283.665
]
},
"pr-node": {
"batches": 4,
"renders": 320,
"cpu_ms": 256.7125,
"pooled_p50_ms": 243.43347904505208,
"batch_cpu_ms": [
245.566,
260.781,
272.594,
247.909
],
"batch_p50_ms": [
225.165,
235.287,
258.215,
239.932
]
},
"lazy-node": {
"batches": 3,
"renders": 240,
"cpu_ms": 219.515,
"pooled_p50_ms": 210.23656299803406,
"batch_cpu_ms": [
211.795,
217.841,
228.909
],
"batch_p50_ms": [
199.623,
190.829,
244.28
]
},
"pr-bun": {
"batches": 2,
"renders": 160,
"cpu_ms": 171.86,
"pooled_p50_ms": 153.23652152437717,
"batch_cpu_ms": [
172.445,
171.275
],
"batch_p50_ms": [
150.541,
153.675
]
},
"lazy-bun": {
"batches": 2,
"renders": 160,
"cpu_ms": 144.7725,
"pooled_p50_ms": 123.54808300733566,
"batch_cpu_ms": [
140.06,
149.485
],
"batch_p50_ms": [
112.039,
132.893
]
}
},
"runs": [
{
"label": "base13-a",
"runtime": "/opt/homebrew/bin/node",
"entry": "/private/tmp/ccstatusline-perf-4/base/dist/ccstatusline.js",
"bytes": 13009445,
"renders": 80,
"width_override": false,
"cpu_total_s": 19.7057,
"cpu_per_render_ms": 246.321,
"wall_s": 4.81,
"p50_ms": 231.993,
"p95_ms": 334.335,
"hashes": [
"454f8606b708708a4afd72598c1493889cacba100fc86da1f418f1977c044f86"
]
},
{
"label": "base13-b",
"runtime": "/opt/homebrew/bin/node",
"entry": "/private/tmp/ccstatusline-perf-4/base/dist/ccstatusline.js",
"bytes": 13009445,
"renders": 80,
"width_override": false,
"cpu_total_s": 19.6957,
"cpu_per_render_ms": 246.197,
"wall_s": 4.523,
"p50_ms": 220.007,
"p95_ms": 270.82,
"hashes": [
"454f8606b708708a4afd72598c1493889cacba100fc86da1f418f1977c044f86"
]
},
{
"label": "final-base-node",
"runtime": "/opt/homebrew/bin/node",
"entry": "/private/tmp/ccstatusline-perf-4/base/dist/ccstatusline.js",
"bytes": 13009445,
"renders": 80,
"width_override": false,
"cpu_total_s": 21.5033,
"cpu_per_render_ms": 268.792,
"wall_s": 5.728,
"p50_ms": 283.665,
"p95_ms": 429.653,
"hashes": [
"454f8606b708708a4afd72598c1493889cacba100fc86da1f418f1977c044f86"
]
},
{
"label": "pr13-a",
"runtime": "/opt/homebrew/bin/node",
"entry": "/private/tmp/ccstatusline-perf-4/pr/dist/ccstatusline.js",
"bytes": 13009445,
"renders": 80,
"width_override": false,
"cpu_total_s": 19.6453,
"cpu_per_render_ms": 245.566,
"wall_s": 4.719,
"p50_ms": 225.165,
"p95_ms": 309.623,
"hashes": [
"454f8606b708708a4afd72598c1493889cacba100fc86da1f418f1977c044f86"
]
},
{
"label": "pr13-b",
"runtime": "/opt/homebrew/bin/node",
"entry": "/private/tmp/ccstatusline-perf-4/pr/dist/ccstatusline.js",
"bytes": 13009445,
"renders": 80,
"width_override": false,
"cpu_total_s": 20.8624,
"cpu_per_render_ms": 260.781,
"wall_s": 5.43,
"p50_ms": 235.287,
"p95_ms": 421.339,
"hashes": [
"454f8606b708708a4afd72598c1493889cacba100fc86da1f418f1977c044f86"
]
},
{
"label": "pr13-c",
"runtime": "/opt/homebrew/bin/node",
"entry": "/private/tmp/ccstatusline-perf-4/pr/dist/ccstatusline.js",
"bytes": 13009445,
"renders": 80,
"width_override": false,
"cpu_total_s": 21.8075,
"cpu_per_render_ms": 272.594,
"wall_s": 5.458,
"p50_ms": 258.215,
"p95_ms": 362.965,
"hashes": [
"454f8606b708708a4afd72598c1493889cacba100fc86da1f418f1977c044f86"
]
},
{
"label": "final-pr-node",
"runtime": "/opt/homebrew/bin/node",
"entry": "/private/tmp/ccstatusline-perf-4/pr/dist/ccstatusline.js",
"bytes": 13009445,
"renders": 80,
"width_override": false,
"cpu_total_s": 19.8328,
"cpu_per_render_ms": 247.909,
"wall_s": 4.826,
"p50_ms": 239.932,
"p95_ms": 309.555,
"hashes": [
"454f8606b708708a4afd72598c1493889cacba100fc86da1f418f1977c044f86"
]
},
{
"label": "lazy13-a",
"runtime": "/opt/homebrew/bin/node",
"entry": "/private/tmp/ccstatusline-perf-4/lazy/dist/ccstatusline.js",
"bytes": 13009445,
"renders": 80,
"width_override": false,
"cpu_total_s": 16.9436,
"cpu_per_render_ms": 211.795,
"wall_s": 4.174,
"p50_ms": 199.623,
"p95_ms": 294.081,
"hashes": [
"454f8606b708708a4afd72598c1493889cacba100fc86da1f418f1977c044f86"
]
},
{
"label": "lazy13-b",
"runtime": "/opt/homebrew/bin/node",
"entry": "/private/tmp/ccstatusline-perf-4/lazy/dist/ccstatusline.js",
"bytes": 13009445,
"renders": 80,
"width_override": false,
"cpu_total_s": 17.4273,
"cpu_per_render_ms": 217.841,
"wall_s": 4.353,
"p50_ms": 190.829,
"p95_ms": 336.969,
"hashes": [
"454f8606b708708a4afd72598c1493889cacba100fc86da1f418f1977c044f86"
]
},
{
"label": "final-lazy-node",
"runtime": "/opt/homebrew/bin/node",
"entry": "/Users/axisrow/.ao/data/worktrees/ccstatusline/ccstatusline-4/dist/ccstatusline.js",
"bytes": 13009445,
"renders": 80,
"width_override": false,
"cpu_total_s": 18.3127,
"cpu_per_render_ms": 228.909,
"wall_s": 5.099,
"p50_ms": 244.28,
"p95_ms": 352.927,
"hashes": [
"454f8606b708708a4afd72598c1493889cacba100fc86da1f418f1977c044f86"
]
},
{
"label": "pr-bun13",
"runtime": "/opt/homebrew/bin/bun",
"entry": "/private/tmp/ccstatusline-perf-4/pr/dist/ccstatusline.js",
"bytes": 13009445,
"renders": 80,
"width_override": false,
"cpu_total_s": 13.7956,
"cpu_per_render_ms": 172.445,
"wall_s": 3.102,
"p50_ms": 150.541,
"p95_ms": 182.732,
"hashes": [
"454f8606b708708a4afd72598c1493889cacba100fc86da1f418f1977c044f86"
]
},
{
"label": "final-pr-bun",
"runtime": "/opt/homebrew/bin/bun",
"entry": "/private/tmp/ccstatusline-perf-4/pr/dist/ccstatusline.js",
"bytes": 13009445,
"renders": 80,
"width_override": false,
"cpu_total_s": 13.702,
"cpu_per_render_ms": 171.275,
"wall_s": 3.312,
"p50_ms": 153.675,
"p95_ms": 226.265,
"hashes": [
"454f8606b708708a4afd72598c1493889cacba100fc86da1f418f1977c044f86"
]
},
{
"label": "lazy-bun13",
"runtime": "/opt/homebrew/bin/bun",
"entry": "/private/tmp/ccstatusline-perf-4/lazy/dist/ccstatusline.js",
"bytes": 13009445,
"renders": 80,
"width_override": false,
"cpu_total_s": 11.2048,
"cpu_per_render_ms": 140.06,
"wall_s": 2.383,
"p50_ms": 112.039,
"p95_ms": 158.231,
"hashes": [
"454f8606b708708a4afd72598c1493889cacba100fc86da1f418f1977c044f86"
]
},
{
"label": "final-lazy-bun",
"runtime": "/opt/homebrew/bin/bun",
"entry": "/Users/axisrow/.ao/data/worktrees/ccstatusline/ccstatusline-4/dist/ccstatusline.js",
"bytes": 13009445,
"renders": 80,
"width_override": false,
"cpu_total_s": 11.9588,
"cpu_per_render_ms": 149.485,
"wall_s": 2.809,
"p50_ms": 132.893,
"p95_ms": 186.214,
"hashes": [
"454f8606b708708a4afd72598c1493889cacba100fc86da1f418f1977c044f86"
]
}
]
}
Loading