@hasna/statusline 0.0.7 → 0.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,7 +5,7 @@ on and off — machine name, project + branch, model, context remaining, session
5
5
  and more — rendered as one or more rows in your agent's status bar.
6
6
 
7
7
  ```
8
- apple03 · statusline (main) · 12h · 1.2k · fable 5 [1m] · 90% left · $0.04
8
+ apple03 · statusline (main) · 12h · 1.2k · fable 5 [1m] · 90% left · 95% cached · $0.04
9
9
  ```
10
10
 
11
11
  Built with [Bun](https://bun.sh) + TypeScript. The CLI renderer is dependency-light; the optional MCP server uses the Model Context Protocol SDK. Apache-2.0.
@@ -22,7 +22,7 @@ That's it — Claude Code picks it up on the next status refresh (no restart nee
22
22
  ## Segments
23
23
 
24
24
  `statusline list` shows a compact, enabled-first summary of your segments (use
25
- `statusline list --all` for every row). The table below is the full catalog — all 26
25
+ `statusline list --all` for every row). The table below is the full catalog — all 31
26
26
  segment ids from `src/segments/index.ts`.
27
27
 
28
28
  `renderLine` walks your configured segment order, renders each one, and **drops**
@@ -36,7 +36,9 @@ anything that returns `null` or throws. A failed segment never breaks the host U
36
36
  | `git-branch` | off | Current Git branch | `git branch --show-current` in `cwd` | outside git or detached | `main` |
37
37
  | `commit-age` | on | Time since last commit (compact) | `git log -1` epoch vs now | no commits / not a repo | `12h` |
38
38
  | `loc` | on | Lines of code tracked by Git (compact) | `git ls-files` + `wc -l` in repo root | outside git or zero lines | `1.2k` |
39
- | `current-dir` | off | Current working directory (basename) | `StatusContext.cwd` | never (always renders basename) | `open-statusline` |
39
+ | `disk-free` | off | Free space on the volume holding the working directory | one `fs.statfsSync` syscall on `StatusContext.cwd` — no subprocess, no polling, no cache | the volume cannot be read (missing cwd, unsupported filesystem) | `disk 24.1G` · `disk 8G low` |
40
+ | `current-dir` | off | Current working directory (basename) | `StatusContext.cwd` | never (always renders basename) | `statusline` |
41
+ | `cwd` | off | Current working directory (full path) | `StatusContext.cwd`, home directory abbreviated to `~`, head elided past 40 chars | no cwd in the payload | `~/work/statusline` |
40
42
  | `model` | off | Current model name (no context tag) | `StatusContext.model.id` via friendly formatter | no model id | `fable 5` |
41
43
  | `model-context` | on | Model name with context-size tag | same formatter, keeps `[tag]` suffix | no model id | `fable 5 [1m]` |
42
44
  | `model-with-reasoning` | off | Model name with its reasoning effort | lowercased `model.display_name` (or friendly id) + `effort.level`; falls back to `thinking.enabled` when on | no model | `fable (xhigh)` |
@@ -45,14 +47,15 @@ anything that returns `null` or throws. A failed segment never breaks the host U
45
47
  | `auth-email` | off | Account email this session is logged in as | the agent's own login record for this config dir, else the registry | no login recorded | `dev@example.com` |
46
48
  | `five-hour-limit` | off | Percentage of the 5-hour rate limit used | `rate_limits.five_hour.used_percentage` | host reports no limit data | `5h:42%` |
47
49
  | `seven-day-limit` | off | Percentage of the 7-day rate limit used | `rate_limits.seven_day.used_percentage` | host reports no limit data | `7d:12%` |
48
- | `usage-session` | off | Session (5-hour) usage **remaining** for this pane's own account | this process's `CLAUDE_CONFIG_DIR` account uuid → the accounts usage cache at `~/.hasna/accounts/cache/usage/<uuid>.json` (kept warm by `@hasna/accounts`; **no cloud call**) | never omitted — shows `5h —` when usage is unknown, missing, stale, or rate-limited | `5h 87%` |
49
- | `usage-weekly` | off | Weekly usage **remaining** for this pane's own account (the binding limit) | same local usage cache as `usage-session` | never omitted — shows `7d —` when usage is unavailable | `7d 92%` |
50
+ | `usage-session` | off | Session (5-hour) usage **remaining** for this pane's own account | this process's `CLAUDE_CONFIG_DIR` account uuid → the accounts usage cache at `~/.hasna/accounts/cache/usage/<uuid>.json` (kept warm by `@hasna/accounts`; **no cloud call**) | not on the Anthropic API (custom endpoint); on Anthropic, shows `5h —` when usage is unknown, missing, stale, or rate-limited | `5h 87%` |
51
+ | `usage-weekly` | off | Weekly usage **remaining** for this pane's own account (the binding limit) | same local usage cache as `usage-session` | not on the Anthropic API (custom endpoint); on Anthropic, shows `7d —` when usage is unavailable | `7d 92%` |
50
52
  | `thread-title` | off | Thread title set with `/rename` | `session_name` | thread not renamed | `ship it` |
51
- | `context-used` | off | Percentage of context window used | session transcript JSONL (`contextUsage`) | transcript missing/unreadable | `10%` |
53
+ | `context-used` | off | Percentage of context window used | session transcript JSONL (`contextUsage`) | transcript missing/unreadable | `ctx 10%` |
52
54
  | `context-remaining` | on | Percentage of context window remaining | transcript via `contextUsage` | transcript missing/unreadable | `90% left` |
53
55
  | `used-tokens` | off | Total tokens in the context window | transcript usage block (input + output) | transcript missing/unreadable | `102k tok` |
56
+ | `cache-rate` | on | Percentage of input tokens served from cache | transcript usage block (`cache_read_input_tokens` / total input-side); Codex rollout / OpenCode DB for those providers | transcript missing/unreadable or zero input-side tokens | `87% cached` |
54
57
  | `cost` | on | Session cost in USD | `StatusContext.cost.totalCostUsd` | cost is zero or missing | `$1,234.50` |
55
- | `duration` | off | Session wall-clock duration | `StatusContext.cost.totalDurationMs` | duration is zero or missing | `1h30m` |
58
+ | `duration` | off | Session wall-clock duration | `StatusContext.cost.totalDurationMs` | duration is zero or missing | `1h 30m` |
56
59
  | `lines-changed` | off | Lines added/removed this session | `cost.totalLinesAdded/Removed` | both zero | `+142/-18` |
57
60
  | `output-style` | off | Active output style | `StatusContext.outputStyle` | style is `default` or missing | `concise` |
58
61
  | `agent-version` | off | Host agent version | `StatusContext.version` | version missing | `v2.1.39` |
@@ -61,14 +64,23 @@ anything that returns `null` or throws. A failed segment never breaks the host U
61
64
 
62
65
  ### Context segments and transcripts
63
66
 
64
- Segments that report context usage (`context-used`, `context-remaining`, `used-tokens`)
65
- read the session transcript path from the provider payload. `contextUsage` scans the
66
- JSONL for the last assistant entry with a `usage` block and sums
67
- `input_tokens + cache_creation_input_tokens + cache_read_input_tokens`. Window size is
68
- **1,000,000** when the model id contains `[1m]`, otherwise **200,000**.
67
+ Segments that report context usage (`context-used`, `context-remaining`, `used-tokens`,
68
+ `cache-rate`) read the session transcript path from the provider payload.
69
+ `contextUsage` scans the JSONL for the last assistant entry with a `usage` block and
70
+ sums `input_tokens + cache_creation_input_tokens + cache_read_input_tokens`. Window
71
+ size is **1,000,000** when the model id contains `[1m]`, otherwise **200,000**.
72
+
73
+ `cache-rate` is `cache_read_input_tokens / (input_tokens + cache_creation_input_tokens
74
+ + cache_read_input_tokens)` from the same last usage block, rendered as a rounded
75
+ percentage (`87% cached`). It renders `0% cached` during the pre-warm phase of a
76
+ session (a nonzero divisor with no cache hits yet) — the same render-zero-not-drop
77
+ convention as `five-hour-limit` — and is omitted only when the transcript is
78
+ missing/unreadable or the divisor is zero. For Codex and OpenCode it reads those
79
+ providers' own usage channels instead (see Providers below).
69
80
 
70
81
  With the test fixture transcript (100k input-side tokens on a 1m window), expect
71
- `context-used` → `10%`, `context-remaining` → `90% left`, `used-tokens` → `102k tok`.
82
+ `context-used` → `ctx 10%`, `context-remaining` → `90% left`, `used-tokens` → `102k tok`,
83
+ `cache-rate` → `95% cached`.
72
84
 
73
85
  ### Which account am I? (`auth-profile`)
74
86
 
@@ -107,7 +119,18 @@ registry knows about always wins.
107
119
  ### Colours
108
120
 
109
121
  Segments may declare a colour, applied by `renderLine` when colours are on
110
- (the default). The rate-limit segments escalate from yellow to red at 80%.
122
+ (the default). Only standard 16-colour ANSI codes are used, so the palette
123
+ works in any modern terminal on both light and dark backgrounds, and
124
+ separators are never coloured.
125
+
126
+ - the context gauges go green below 50%, yellow from 50% to 79%, and red at
127
+ 80% and above (the same shape the rate-limit segments use);
128
+ - the rate-limit segments escalate from yellow to red at 80%;
129
+ - `cwd` uses a calm blue accent and elides the head of long paths;
130
+ - `disk-free` stays dim while the volume has headroom, turns yellow below 20%
131
+ free and red below 10%, and appends a `low` marker so the warning survives
132
+ `NO_COLOR`;
133
+ - model, cost and duration render dim, so they never compete with a warning.
111
134
 
112
135
  ```bash
113
136
  statusline colors off # or set "colors": false in the config
@@ -121,7 +144,7 @@ statusline colors off # or set "colors": false in the config
121
144
  enabled segments first, capped rows, and hints for the detail paths:
122
145
 
123
146
  ```
124
- Segments: 7 enabled / 26 total (showing 12 of 26)
147
+ Segments: 8 enabled / 30 total (showing 12 of 30)
125
148
  state default id
126
149
  on yes machine
127
150
  on yes project
@@ -129,6 +152,7 @@ on yes commit-age
129
152
  on yes loc
130
153
  on yes model-context
131
154
  on yes context-remaining
155
+ on yes cache-rate
132
156
  on yes cost
133
157
  off no project-name
134
158
  off no git-branch
@@ -181,7 +205,7 @@ Config lives at `~/.config/statusline/config.json` (override with `$STATUSLINE_C
181
205
  {
182
206
  "separator": " · ",
183
207
  "colors": true,
184
- "segments": ["machine", "project", "commit-age", "loc", "model-context", "context-remaining", "cost"]
208
+ "segments": ["machine", "project", "commit-age", "loc", "model-context", "context-remaining", "cache-rate", "cost"]
185
209
  }
186
210
  ```
187
211
 
@@ -211,6 +235,14 @@ The `newline` segment starts a new row; empty rows are removed. Segments
211
235
  that need more than the payload offers (e.g. `context-remaining`) read the session
212
236
  transcript to compute it. A segment failure is never fatal — it's simply omitted.
213
237
 
238
+ There is no long-running statusline process, so nothing polls. `disk-free` reads
239
+ free space once per render with `fs.statfsSync` (about half a microsecond), the
240
+ same way the host re-runs the command for each refresh; shelling out to `df`
241
+ would fork a process every time and cost roughly a millisecond. That one reading
242
+ is shared by the segment's text and its colour, so they cannot disagree. The
243
+ segment adds under 2 µs to an in-process render, which is below the noise of the
244
+ ~17 ms process invocation.
245
+
214
246
  ## SDK
215
247
 
216
248
  The package root is a side-effect-free SDK surface:
@@ -233,7 +265,9 @@ saveConfig(next);
233
265
  Useful exports include `parseClaudeInput`, `renderLine`, `renderStatusline`, `segments`,
234
266
  `getSegment`, `listSegments`, `defaultConfig`, `loadConfig`, `saveConfig`, `configPath`,
235
267
  `previewStatusline`, `enableSegments`, `disableSegments`, `orderSegments`,
236
- `setSeparator`, `setColors`, `installClaude`, and the account, context, format, and Git helpers.
268
+ `setSeparator`, `setColors`, `installClaude`, the cache-rate channel
269
+ (`cacheRate`, `codexCacheRate`, `latestCodexSessionPath`, `opencodeCacheRate`), and the
270
+ account, context, format, and Git helpers.
237
271
 
238
272
  ## MCP
239
273
 
@@ -306,30 +340,63 @@ on every status refresh — **no restart needed**.
306
340
 
307
341
  Every field is optional — partial or empty payloads still produce a usable context.
308
342
 
309
- ### Codewith (not implemented)
343
+ ### Codex CLI (implemented, env-selected)
344
+
345
+ Codex ships its own built-in segment picker and has no external `statusLine` command
346
+ hook, so there is no `statusline install codex` — the renderer is invoked standalone
347
+ with the provider selected by the environment:
348
+
349
+ ```bash
350
+ STATUSLINE_PROVIDER=codex statusline render
351
+ ```
310
352
 
311
- `statusline install` only accepts `claude` today. Running
312
- `statusline install codewith` errors with *unsupported target* (`src/cli.ts`).
353
+ `cache-rate` then reads the newest Codex session rollout
354
+ (`~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl`, newest file mtime): the LAST
355
+ `event_msg` line whose `payload.type` is `token_count`, whose
356
+ `payload.info.total_token_usage` holds **cumulative session totals**
357
+ (`input_tokens`, `cached_input_tokens`, `cache_write_input_tokens`, …). The rate is
358
+ `cached_input_tokens / (input_tokens + cached_input_tokens + cache_write_input_tokens)`,
359
+ clamped to `[0, 1]`. Exposed on the SDK as `latestCodexSessionPath()` /
360
+ `codexCacheRate(path?)`. Caveat: the semantics of Codex's `input_tokens` relative to
361
+ `cached_input_tokens` (uncached-only vs total) are inferred from measured samples —
362
+ the clamp and the fixture tests lock the adapter to the documented shape.
363
+
364
+ ### OpenCode (implemented, env-selected)
365
+
366
+ OpenCode has no statusline/footer hook in its config as of v1.3.x, so the same
367
+ env-selector applies:
368
+
369
+ ```bash
370
+ STATUSLINE_PROVIDER=opencode statusline render
371
+ ```
313
372
 
314
- The renderer is provider-agnostic: a future `parseCodewithInput` would map Codewith's
315
- status payload into `StatusContext` (`src/providers/types.ts`) and reuse the same
316
- segment registry. No install wiring exists yet (tracked in STA-00003).
373
+ `cache-rate` reads `~/.local/share/opencode/opencode.db` (SQLite) with a read-only
374
+ `Bun.sqlite` query — zero new dependencies, and a WAL lock mid-write degrades to
375
+ omitting the segment, never a crash. It takes the newest session row's
376
+ `tokens_cache_read / (tokens_input + tokens_cache_read + tokens_cache_write)` from the
377
+ `session` table, falling back to `session_v2` when `session` has no populated row (both
378
+ schemas carry the columns). SDK surface: `opencodeCacheRate({ dbPath?, sessionId? })`.
317
379
 
318
- ### Cursor (not implemented)
380
+ ### Cursor (not implemented — no verifiable source)
319
381
 
320
- Same gap as Codewith — no `statusline install cursor` subcommand and no Cursor-specific
321
- parser. A future adapter would implement `parseCursorInput` → `StatusContext` and hook
322
- into Cursor's status bar the same way Claude does (tracked in STA-00004).
382
+ No `statusline install cursor` and no adapter. Unlike Codex and OpenCode, Cursor has
383
+ **no local per-turn token-usage record to read**: `~/.cursor/chats/*/store.db` holds
384
+ only `blobs`/`meta` tables, and `~/.cursor/ai-tracking/ai-code-tracking.db` carries no
385
+ per-turn usage. Token usage is only visible in Cursor's own UI/API, so per the honesty
386
+ rule no adapter is fabricated. If Cursor later persists usage locally, an adapter slots
387
+ in behind the same `cacheRate(ctx)` dispatch.
323
388
 
324
389
  ### Other agents
325
390
 
326
391
  | Agent | Status |
327
392
  |-------|--------|
328
- | OpenCode | ⏳ no statusline/footer hook in its config as of v1.3.x — the renderer is provider-agnostic (`src/providers/`), so an adapter slots in the moment one exists |
329
- | Codex CLI | Codex ships its own built-in segment picker; no external command hook |
393
+ | Codex CLI | ✅ `STATUSLINE_PROVIDER=codex` — `cache-rate` from the newest session rollout |
394
+ | OpenCode | ✅ `STATUSLINE_PROVIDER=opencode` — `cache-rate` from the sessions DB |
395
+ | Cursor | ❌ no local per-turn token usage exists — no adapter (see above) |
396
+ | Codewith | ⏳ no statusline/footer hook; a future `parseCodewithInput` would map its payload into `StatusContext` and reuse the registry (tracked in STA-00003) |
330
397
 
331
- Adding a provider means one small parser: agent payload → `StatusContext`
332
- (`src/providers/types.ts`). PRs welcome.
398
+ Adding a provider means one small adapter: agent payload/source → `StatusContext`
399
+ (`src/providers/types.ts`) or a `cacheRate(ctx)` data channel. PRs welcome.
333
400
 
334
401
  ## Development
335
402