Skip to content

Commit 79dfa6d

Browse files
lroolleclaude
andcommitted
feat: stdin-first architecture — context, quota, effort from CLI pipe
Use context_window.used_percentage and rate_limits directly from the CLI's JSON input instead of computing independently. Transcript parsing kept as fallback for CLI < v2.1.132. OAuth fetch now only needed for extra_usage, prepaid balance, and user profile. New signals: effort level (opus4.6[1m] max) and fast mode shown next to model when non-default. format_reset_clock and format_reset_relative handle both ISO 8601 and unix epoch timestamps. Fixed during review: - Extra_usage/prepaid block was displaced into the stdin elif branch (dead code) while the cache_file branch lost it entirely - --argjson with ISO string timestamps would crash jq silently - Dead != "null" guard removed (jq // "" already handles null) - Indentation normalized after context block refactor 93 bats tests (was 85). Tested against CLI v2.1.141. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 0158390 commit 79dfa6d

5 files changed

Lines changed: 344 additions & 106 deletions

File tree

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
raw-stdin-*.json
2+
*.cache
3+
*.lock
4+
*.err
5+
sessions/

CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,22 @@
22

33
## Unreleased
44

5+
Tested against Claude Code CLI v2.1.141 (MAX and PRO accounts).
6+
7+
### Stdin-First Architecture
8+
9+
Context percentage, rate limits, effort level, and fast mode are now read
10+
directly from the CLI's JSON input instead of computing independently.
11+
12+
- **Context bar** uses `context_window.used_percentage` from stdin. Transcript
13+
JSONL parsing is kept as fallback for CLI versions before v2.1.132.
14+
- **Quota display** uses `rate_limits` from stdin when no OAuth cache exists.
15+
OAuth fetch is still needed for extra-usage and user profile data.
16+
- **Effort level** shown next to model when non-default: `opus4.6[1m] max`.
17+
- **Fast mode** shown as `opus4.6[1m] fast` when enabled.
18+
- `format_reset_clock` and `format_reset_relative` now handle both ISO 8601
19+
and unix epoch timestamps (CLI sends epoch, OAuth API sends ISO).
20+
521
### Added
622

723
- Added default `extra` statusline component for Claude Code extra usage:
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
* [2026-05-14] Stdin-first architecture and UX overhaul :ARCH:FEAT:
2+
3+
** Tension
4+
5+
The statusline was doing too much work independently. Context
6+
percentage was calculated by parsing the transcript JSONL file —
7+
reading the last 20 lines, extracting token counts, summing, dividing
8+
by a window size we also computed ourselves. Quota data came
9+
exclusively from OAuth API calls we initiated. All of this ran on
10+
every render (3x per second during debounce).
11+
12+
Meanwhile, Claude Code CLI v2.1.132+ was already sending
13+
pre-calculated =context_window.used_percentage=, =rate_limits= with
14+
=resets_at= timestamps, =effort.level=, and =fast_mode= directly in
15+
the stdin JSON. We were ignoring it and recomputing from scratch.
16+
17+
The real trigger: dumping the raw stdin to debug log and seeing the
18+
full schema — =context_window=, =rate_limits=, =effort=, =vim.mode=,
19+
=session_name= — all there, all unused. We were doing 60 lines of
20+
transcript parsing to get a number the CLI was handing us for free.
21+
22+
** Observation
23+
24+
Raw stdin from CLI v2.1.141 (MAX account):
25+
26+
#+begin_src json
27+
{
28+
"context_window": {
29+
"used_percentage": 21,
30+
"context_window_size": 1000000
31+
},
32+
"rate_limits": {
33+
"five_hour": { "used_percentage": 4, "resets_at": 1778756400 },
34+
"seven_day": { "used_percentage": 4, "resets_at": 1779292800 }
35+
},
36+
"effort": { "level": "high" },
37+
"fast_mode": false,
38+
"thinking": { "enabled": true },
39+
"vim": { "mode": "NORMAL" }
40+
}
41+
#+end_src
42+
43+
PRO account had =seven_day.used_percentage: 28.000000000000004= —
44+
floating point noise that our =printf '%.0f'= already handles.
45+
46+
Key format difference: CLI sends =resets_at= as unix epoch seconds.
47+
Our OAuth API returns it as ISO 8601 strings. The =format_reset_clock=
48+
and =format_reset_relative= functions needed to handle both.
49+
50+
Code review (linus-code-reviewer agent) found three bugs before
51+
release:
52+
1. Color variables defined at line 1222, first used at line 1114 —
53+
git branch and path had zero ANSI coloring in production
54+
2. =format_duration(0)= returned "1m" instead of "0m"
55+
3. Division by zero when =CLAUDE_CONTEXT_LIMIT=0=
56+
57+
CI failed on the model abbreviation test — =opus4.6[1m]= only worked
58+
when =~/.claude/settings.json= had a configured model. The else branch
59+
fell back to generic family name "opus[1m]". CI has no settings file.
60+
61+
** Decision
62+
63+
*Stdin as primary, transcript/OAuth as fallback.* Not a full rip-out.
64+
65+
- =context_window.used_percentage= from stdin: use it when present,
66+
fall back to transcript parsing for CLI < v2.1.132
67+
- =rate_limits= from stdin: use when no OAuth cache exists (covers
68+
first-message display without an API call)
69+
- OAuth API: still required for extra_usage, prepaid balance, user
70+
profile/tier — none of these are in stdin
71+
- Kept =get_context_limit()= and transcript parsing as dead-path
72+
fallback; ~30 lines of dormant code for backward compat
73+
74+
Rejected alternatives:
75+
- Rip out transcript parsing entirely: breaks users on older CLI
76+
- Rip out OAuth fetching: loses extra_usage, tier display, prepaid
77+
balance — the features that differentiate us
78+
- Prefer stdin rate_limits over OAuth cache: loses extra_usage data
79+
that comes in the same OAuth response
80+
81+
*Effort/fast mode display.* Added next to model text:
82+
- =opus4.6[1m] max= — effort is max
83+
- =opus4.6[1m] fast= — fast mode enabled
84+
- Hidden when effort is "high" (the default) — no noise
85+
86+
*--extra display gating.* New =--extra= flag:
87+
- =always= (default) — backward compatible
88+
- =on-limit= — show extra only when 5h >= 80% or 7d >= 70%
89+
- =off= — never show
90+
- =developer= theme defaults to =on-limit=, =minimal= to =off=
91+
92+
*Color hierarchy.* Principle: color encodes urgency, not category.
93+
- Path demoted to dim cyan (was normal cyan — same weight as model)
94+
- Git dirty=yellow (pops), clean=dim yellow (recedes)
95+
- Time to plain dim (was dim cyan — unnecessary color)
96+
- Tier label gets semantic color: MAX=green, PRO=cyan
97+
98+
*Versioning.* Team suggested matching CLI version (2.1.141). Rejected:
99+
creates false coupling. We keep our own semver. Document tested CLI
100+
version in CHANGELOG.
101+
102+
** Tradeoff
103+
104+
- Transcript fallback is dead code for v2.1.132+ users. It adds ~30
105+
lines that will never execute. The alternative (ripping it out) would
106+
break anyone on an older CLI.
107+
- OAuth is still needed for the premium features (extra_usage, tier,
108+
prepaid). If Anthropic adds these to stdin in a future version, we
109+
can simplify further. Until then, two data paths coexist.
110+
- The =resets_at= format split (epoch vs ISO) means our reset
111+
formatting functions have a type-check branch. Small cost, but it's
112+
the kind of dual-format handling that accumulates.
113+
- =session_name= is available in stdin but not displayed. Could replace
114+
or supplement project path. Deferred — low urgency, nonzero design
115+
work.
116+
- =vim.mode= available but not displayed — built-in already shows it.
117+
=thinking.enabled= available but always true for current models.
118+
119+
** Next
120+
121+
The live wire is the OAuth/stdin data merge. Right now, if the OAuth
122+
cache exists, it wins entirely (it has extra_usage). If it doesn't,
123+
stdin rate_limits are used. But the OAuth cache can be up to 5 minutes
124+
stale while stdin is always fresh. The right architecture is: stdin for
125+
quota percentages (always freshest), OAuth only for extra_usage and
126+
profile. That requires splitting =build_usage_display= to accept mixed
127+
sources. Not urgent — the staleness window is small — but it's the
128+
next structural improvement.
129+
130+
** Artifacts
131+
132+
Committed (v0.4.0):
133+
- =statusline.sh=: +548/-187 lines across extra_usage, color hierarchy,
134+
--extra flag, bug fixes, dead code removal
135+
- =t/statusline.bats=: 73 tests (was 0 before this cycle)
136+
- =t/install.bats=: 12 tests
137+
- =.github/workflows/test.yml=: CI on push/PR
138+
- =install.sh=: one-liner installer
139+
- =README.md=: full rewrite
140+
- =CHANGELOG.md=: v0.3.0 and v0.4.0 release notes
141+
142+
Uncommitted (pending v0.5.0):
143+
- Stdin-first context/quota, effort/fast display
144+
- Epoch timestamp support in reset formatters
145+
- 91 tests (was 85)
146+
- =.gitignore= for raw stdin dumps and cache files
147+
148+
Real stdin captured from CLI v2.1.141:
149+
- =raw-stdin-max.json= (gitignored — contains session IDs and paths)
150+
- =raw-stdin-pro.json= (gitignored — same)

0 commit comments

Comments
 (0)