diff --git a/CHANGELOG.md b/CHANGELOG.md index 133de4e..224ac4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,43 @@ ## Unreleased +## v0.30.0 — 2026-08-19 — the future folds + +**The 7d strip stopped drawing the future one cell at a time.** Thirty +hollow cells after the now-marker carried one fact between them — "the +week has room left" — and spent thirty columns saying it (thirty-one +when the forecast painted them all `×`). The live row now keeps two +cells after `▮` and folds the rest into `...▯5hx28`: the count of 5h +slots to the reset, `×` red when the tail projects dry. History still +draws in full — that is the information — and the fold only engages +when it hides at least ten cells, so a closing week still draws to its +edge. The `week` subcommand's wide ledger is untouched. + +**The 5h strip reads by the hour.** Ten half-hour cells were more +resolution than a glance uses; five hour cells tell the same story in +half the width, and the freed columns go to the notice beside them. + +**The live demo became the instrument it demos.** The GitHub Pages +site dropped the fake macOS terminal window for a meter: a recessed +register carrying the same frame-by-frame simulation (now with the +folded week tail, hour cells, and a dry-forecast frame), a mono +reading number the anatomy table cites (№ 0008), answering-pair +anatomy rows — the left column states the reading, the right says +what to do about it — and an odometer roll on quota digits that +change. Narrow screens get a truthfully compacted line, the same way +the script itself gives up the trace chip first. Stale claims fixed +across the surfaces: 417 tests everywhere (README said 383, the site +384, llms.txt 379), and llms.txt learned the new week-row grammar. + +**The forecast refuses a corrupt profile.** A weekday that claims to +average more than the whole pool per day (`145%/day` of a 100-point +week) can only come from a broken accountant — measured live when a +pre-v0.29.0 build sharing the same home wrote `recent_24h: 343` and the +walk called a 2%-used fresh window dry in 30 hours, red, on every +render. Impossible input now earns silence, not a siren; `recent_24h` +alone clamps to 100 (legitimately larger across a reset, but this +window cannot lose more than everything in a day). + ## v0.29.0 — 2026-08-19 — the store learns to count **Fixed: burn was counted wrong, by a factor of three.** The weekday diff --git a/DESIGN.md b/DESIGN.md index 0d3b530..6212ed8 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -82,12 +82,15 @@ own signal. ## The ledgers (row 2) ``` -5h ▃▄▮▯▯▯▯▯▯▯ 0.6x @04:00 7d ▅▁▂ ▃▅ˍ▃▅ ▃▃▁▂▁ ▅ˍ▂▁▁ ˍˍˍ▃▅ ˍˍˍ▅ ▆▆ˍ▂▮▯▯ 0.7x @Wed 09:00 +5h ▃▄▮▯▯ 0.6x @04:00 7d ▅▁▂ ▃▅ˍ▃▅ ▃▃▁▂▁ ▮▯▯...▯5hx14 0.7x @Wed 09:00 ``` -One grammar, two scales. `5h` = this window as 10 half hours; `7d` = the -period as 34 five-hour windows, oldest left, a gap at each local midnight -*in history only* — the run from `▮` on is contiguous. Each strip ends +One grammar, two scales. `5h` = this window as 5 hours; `7d` = the +period as 34 five-hour slots, oldest left, a gap at each local midnight +*in history only* — the run from `▮` on is contiguous. History draws in +full; the live row folds the 7d future after two kept cells into +`...▯5hx14` — the slots left to the reset, all alike, `×` red when the +tail projects dry (the `week` report still draws every slot). Each strip ends with its pace (used ÷ elapsed; dim <1x, pressure ≥1x, hidden under 15 min) and the reset its right edge is — axis labels, not restated badges. @@ -97,6 +100,7 @@ and the reset its right edge is — axis labels, not restated badges. ░ unknown — no sample; never drawn as idle ▮ now ▯ ahead — the hollow of ▮ +...▯5hx14 the folded future: 14 more 5h slots to the reset (live 7d row) × pace won't cover it (7d: learned forecast, linear when cold; 5h: linear) ``` diff --git a/README.md b/README.md index 5034de2..e2a63b2 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,7 @@ Every component earns its place: | User tier | Neutral white-weight (MAX bold, PRO normal, dim otherwise) — identity, never a status color. Truncated display name. | | Quota | Integer percentages. The 5h badge always carries its reset time while a window is live — `5h[42%@14:30]` reads "42% used, resets at 14:30" — because on a 5h horizon the reset is the number you plan the current sitting around. Wall-clock, not a countdown, on purpose: Claude Code only re-renders the statusline on activity, so a relative "@1h38m" silently decays into a lie during idle gaps, while "@14:30" stays true in a frozen frame. (The 7d badge is hybrid: day-relative `@5d` while the reset is >= 24h out — decays one day per day, mild and narrow — switching to the same wall-clock `@04:00` inside the last day, where an `@6h`/`@<1h` countdown decayed by the hour exactly when pressure keeps the suffix visible.) When a window's utilization climbs between renders, a reverse-video `+N` token appears right after the badge for ~60s: `5h[44%@14:32]+2` means "you just burned 2%". A drop (window reset) stays quiet — the fresh low number is its own signal. **7d is forecast, not leveled**: a learned per-weekday burn profile (EWMA over your own usage history) plus your recent 24h burn project whether the quota outlasts the window — your heavy Tuesday counts more than a generic average. The verdict is color alone; under pressure the badge shows when relief arrives: `7d[44%@5d]` red means "at your pace, dry days before the reset 5 days from now"; inside the last day it reads `7d[92%@04:00]` — resets at 04:00. Cold start (<14 days history) falls back to window-average pacing. Recovery color when reset is imminent. **Model-scoped weekly quota**: when the usage API carries a per-model weekly limit (`limits[]`, `kind=weekly_scoped`) for the model your session is running, it renders right after the model+context block — `fabl5[1m][12%] fb[67%]` on a Fable 5 session, `op[33%]` on Opus — because the quota is a property of the model you're running, not of the account-wide 5h/7d cluster. It's a weekly number (same reset as the 7d badge), scoped to one model. Other models' scoped quotas stay hidden: only the limit constraining *this* session is signal. Supersedes the legacy `seven_day_opus`/`seven_day_sonnet` fields, which the API now sends as null. | | Extra usage | Monthly spend, limit, prepaid balance. `--extra auto` shows when quota runs out. | -| Week row | **A row of its own, under the badges**: `5h ▂▅█▃▮▯▯▯▯▯ 0.9x @23:00 7d ▅▁▂ ▃▅ˍ▃▅ …▮▯▯ 0.7x @Wed 09:00` — this sitting as ten half hours, the week as its 5h windows (day-gapped), height = what each cell burned, `▮` now, `×` where the pool runs dry, each strip ending with its pace and reset. Reconstructed from your own usage log; `auto` shows it once there is history to show. See [Week row](#week-row). | +| Week row | **A row of its own, under the badges**: `5h ▅█▃▮▯ 0.9x @23:00 7d ▅▁▂ ▃▅ˍ▃▅ …▮▯▯...▯5hx19 0.7x @Wed 09:00` — this sitting by the hour, the week as its 5h windows (day-gapped, the far future folded to a counted `...▯5hx19`), height = what each cell burned, `▮` now, `×` where the pool runs dry, each strip ending with its pace and reset. Reconstructed from your own usage log; `auto` shows it once there is history to show. See [Week row](#week-row). | | Deadman | **Invisible until a switch is armed.** Surfaces [deadman](https://github.com/thevibeworks/deadman) — a dead man's switch that hands the session off when you stop responding. `[☠ armed 42m]` (dim) counts down to the auto-handoff; `[☠ warned 3m]` (yellow) means the phone warning went out; `[☠ due]` means the handoff fires imminently. Sits on the left lane next to the path — it describes this session's lifecycle, not a quota. One `command -v` when the tool is absent, one fast file read when present; nothing armed renders nothing. `--deadman off` disables it. | | Cache health | **Quiet until it bites.** Claude Code never re-renders an idle session, and while you work the prompt cache is always freshly ~1 TTL from expiry — so a proactive "expiring soon" isn't honestly observable, and `auto` spends no width on it. It speaks only when a rewrite actually happens: `≡!419k` the instant you resume onto a dead cache (idle longer than the TTL) or a mid-session prefix collapse — a 419k-token re-cache at ~20x the read rate (and the same burn on your 5h/7d quota on subscriptions). **Bold red past 200k** — the premium-band miss. `≡~` while a large prefix rebuilds. `--cache always` additionally keeps the freeze-safe deadline `≡@15:20` (last request + TTL; a past time in a frozen frame reads "expired at 15:20"). TTL defaults to 1h (claude.ai subscriber sessions) or 5m (API-key auth); an observed usage breakdown overrides it. The `≡` glyph (U+2261) reads as stacked cache layers — one terminal column, quiet and distinct. | @@ -118,7 +118,7 @@ red, matching its Claude Code TUI color) = model family; everything else is | OAuth + macOS Keychain | -- | Yes | | Change flash on every refresh (`+.37` cost, `+9`/`-27` context, `+N` quota) | -- | Yes | | Model-scoped weekly quota (`fb`/`op`/`sn`) | -- | Yes | -| Week row: the 5h window as half hours, the 7d period as 5h windows, what each cost | -- | Yes | +| Week row: the 5h window by the hour, the 7d period as 5h windows, what each cost | -- | Yes | | Works behind trusted mitm proxies (NODE_EXTRA_CA_CERTS) | -- | Yes | | 383 bats tests + CI | -- | Yes | @@ -255,7 +255,7 @@ right edge shared with line 1: ```text proj (main*) +84/-14 8m $6.72 fabl5[1m][██░░42%] fb[66%] [MAX|@work] 5h[38%@23:00] 7d[39%] -- 3x5h left · 20%/win 5h ▂▅█▃▮▯▯▯▯▯ 0.9x 7d ▅▁▂ ▃▅ˍ▃▅ ▃▃▁▂▁ ▅ˍ▂▁▁ ˍˍˍ▃▅ ˍˍˍ▅ ▆▆ˍ▂▮▯▯ 0.7x @Wed 09:00 +- 3x5h left · 20%/win 5h ▅█▃▮▯ 0.9x 7d ▅▁▂ ▃▅ˍ▃▅ ▃▃▁▂▁ ▅ˍ▂▁▁ ˍˍˍ▃▅ ˍˍˍ▅ ▆▆ˍ▂▮▯▯ 0.7x @Wed 09:00 ``` The 5h strip prints no reset: line 1's `5h[38%@23:00]` already carries @@ -272,7 +272,7 @@ edge and the full sentence sits flush-left beneath them: ```text proj (main*) fabl5[1m][██░░42%] fb[66%] [MAX|@work] 5h[38%@23:00] 7d[39%] - 5h ▂▅█▃▮▯▯▯▯▯ 0.9x @23:00 7d ▅▁▂ ▃▅ˍ▃▅ ▃▃▁▂▁ ▅ˍ▂▁▁ ˍˍˍ▃▅ ˍˍˍ▅ ▆▆ˍ▂▮▯▯ 0.7x @Wed 09:00 + 5h ▅█▃▮▯ 0.9x @23:00 7d ▅▁▂ ▃▅ˍ▃▅ ▃▃▁▂▁ ▅ˍ▂▁▁ ˍˍˍ▃▅ ˍˍˍ▅ ▆▆ˍ▂▮▯▯ 0.7x @Wed 09:00 - budget ~3x5h left · even 20%/win · heading ~52% ``` @@ -285,13 +285,16 @@ budget line shows with it (windows left, what even looks like, where you land); pressure and surplus clauses still take its place when they fire. -- **`5h`** — this sitting: the current 5h window as 10 half-hour cells, - height = the 5h points that half hour added (each positive step between - consecutive samples credited to the half hour the later sample fell in). -- **`7d`** — the week: the 7d period as its 5h windows (34 cells, oldest +- **`5h`** — this sitting: the current 5h window as 5 hour cells, + height = the 5h points that hour added (each positive step between + consecutive samples credited to the hour the later sample fell in). +- **`7d`** — the week: the 7d period as its 5h windows (34 slots, oldest left, the last a 3h stub), height = the 7d points that window burned, with a thin gap at each local midnight so days read as clusters — and a - day that held five windows shows it — without a ruler. + day that held five windows shows it — without a ruler. History draws in + full; the future folds: two hollow cells after `▮`, then `...▯5hx28` — + 28 more 5h slots to the reset, all alike (`×` red when the tail projects + dry). The `week` subcommand's wide ledger still draws every slot. | Cell | Meaning | |------|---------| @@ -301,6 +304,7 @@ fire. | `▮` | the cell you are in now | | `▯` | a cell still ahead of you — the hollow of `▮`, an empty slot waiting | | `×` | a cell the pool will not cover at the current pace (7d: the learned forecast's dry point, linear when untrained; 5h: linear, the same projection as the badge) | +| `...▯5hx28` | the folded 7d future: 28 more 5h slots to the reset, one token instead of 28 hollow cells (`×` red when the tail ends dry) | Burn cells take their badge's pressure color; everything else is neutral, so the row never adds an alarm channel of its own. Both strips are @@ -327,7 +331,7 @@ long one: ```text proj (main*) +84/-14 8m $6.72 fabl5[1m][██░░42%] fb[91%] [MAX|@work] 5h[38%@23:00] 7d[55%] -+ fb 91% vs 7d 55% · go op 5h ▂▅█▃▮▯▯▯▯▯ 0.9x 7d ▅▁▂ ▃▅ˍ▃▅ … ▆▆ˍ▂▮▯▯ 0.6x @Wed 09:00 ++ fb 91% vs 7d 55% · go op 5h ▅█▃▮▯ 0.9x 7d ▅▁▂ ▃▅ˍ▃▅ … ▆▆ˍ▂▮▯▯ 0.6x @Wed 09:00 + fb weekly 91% against 7d 55% · the model caps first, not the account; op sits at 33%, so run it for the bulk ``` @@ -504,7 +508,7 @@ run. Setting only `CLAUDE_CACHE_DIR` keeps the legacy single-dir behavior. npm exec --yes bats -- t/ ``` -383 tests across `t/statusline.bats` (371 statusline + integration) and +417 tests across `t/statusline.bats` (405 statusline + integration) and `t/install.bats` (12 installer). CI runs on push and PR to `main`. ## Project Structure diff --git a/docs/index.html b/docs/index.html index f5005c9..a29ebcf 100644 --- a/docs/index.html +++ b/docs/index.html @@ -3,206 +3,288 @@ -Claude Code Statusline — quota, ledgers, notices. Live, in one bash file. - +Claude Code Statusline — a meter for your Claude Code plan + + + + + + -
-
-

Claude Code Statusline

-

Quota, context, cost, model, and git — live in your Claude Code prompt. - Line 1: how much of each window is left. Row 2: what to do about it, and where it went. Row 3 says it in full, then fades.

-

one bash file · 384 tests · deps: jq + curl · no daemon · no telemetry

+
+

claude-code-statusline — a meter for your Claude Code plan

+
417 tests · jq + curl · no daemon · no telemetry
+
-
-
claude
-
-
-
+
-
- curl -fsSL https://raw.githubusercontent.com/thevibeworks/claude-code-statusline/main/install.sh | bash - -
+
+
№ 0000
+
-
+
the line is built for a 90-column terminal — swipe the register to pan
+
-
+
+ connect + curl -fsSL https://raw.githubusercontent.com/thevibeworks/claude-code-statusline/main/install.sh | bash + +
-

Anatomy of the line

-
-
my-project  +131/-16 6m $7.53 fabl5[1m]xh[█░░░░░12%] ≡!419k fb[67%] [MAX|you] 5h[38%@14:00] 7d[55%@2d]
- - - - - - - - - - - - - - - - - - - - - -
+131/-16 6m $7.53Session activity. Lines added/removed, API time, session cost — cost flashes its increment (+.37) for a minute after each jump.
fabl5[1m]xh[█░░░░░12%]Model + context. Abbreviated model, 1M-window tag, reasoning-effort badge, context pressure bar. On 1M models the bar also carries the premium-pricing band. After /compact the bar snaps to [░░░░░░0%] and flashes the drop.
fb[67%]Model-scoped weekly quota. When the API carries a per-model weekly limit for the model you're running (Fable here), it hugs the model block. Other models' limits stay hidden — only the one constraining this session is signal.
5h[38%@14:00]5-hour window. Wall-clock reset time, deliberately not a countdown: the line only re-renders on activity, so @1h38m would decay into a lie during idle gaps. @14:00 stays true in a frozen frame.
7d[55%@2d]7-day window, forecast-colored. A learned per-weekday burn profile projects whether quota outlasts the window — your heavy Tuesday counts more than a generic average. Red means "at your pace, dry before the reset".
5h ▂▅█▃▯▯▯▯▯ 0.8x @14:005h ledger. This sitting as ten half hours, height = the 5h points each cell cost; now, ahead, ˍ ran-but-negligible, no sample. Ends with its pace (used ÷ elapsed) and the reset its right edge stands for — axis labels, not badges restated.
7d ▅▁▂ ▃▅ˍ▃▅ … ▆▆▯▯▯▯ 0.7x @Wed 09:007d ledger. The week as its 5h windows (34 cells, oldest left, a gap at each local midnight in history only), height = the 7d points each burned; × where the pool runs dry at your pace. Reconstructed from your own usage log — the same history ccpace reads.
+ fb 91% vs 7d 55% · go opThe pin. Left of the ledgers, compacted to the room line 1 leaves (cut at the rightmost joint, so it gives up as little as possible). It stays while the condition holds. The number you act on is bold; the hue is the stance — cyan opportunity, yellow/red pressure, dim budget.
+ fb weekly 91% against 7d 55% · the model caps first…The flash. Row 3: the same notice in full, for ~90 s after the condition first appears in this session — then it disappears and leaves the pin. What it knows: which limit binds first (fb 91% vs 7d 55% = the model caps before the account), an out-of-band quota re-base (7d rebased 53%→12%), the last 5h window of the week, expiring surplus checked against your own burn ratio. --notice off keeps row 3 quiet.
≡!419k  ≡~  !netTruth badges. ≡!419k — prompt-cache break on resume (bold red past 200k); ≡~ — rebuilding; stale-data markers say why a fetch failed. Silent while healthy.
+

Read the meter

+

Every reading answers twice: the left column states the number, the right column says what to do about it. The № cites the register frame above where it appears.

+
+
+
+131/-16 6m $7.53№ 0002
+
Session activity and cost. Lines added and removed, API time, dollars — the cost flashes its increment (+.37) for a minute after each jump, so a spike is seen, not discovered.
+
+
+
fabl5[1m]xh[█░░░░░12%]№ 0002
+
Model and context pressure. Abbreviated model, 1M-window tag, effort badge, context bar. Yellow means think about compacting; on 1M models the bar also carries the premium-pricing band the calm percentage hides.
+
+
+
fb[67%]№ 0006
+
The model's own weekly cap. Shown only for the model you are running — when it outruns the account, the pin below says so and names the way out: spread the bulk to a cooler model.
+
+
+
5h[38%@14:00]№ 0000
+
The 5-hour window. Wall-clock reset, deliberately not a countdown: the line only re-renders on activity, and @14:00 stays true in a frozen frame where @1h38m would decay into a lie.
+
+
+
7d[55%@2d]№ 0000
+
The 7-day window, forecast-colored. A learned per-weekday burn profile projects whether the pool outlasts the window — your heavy Tuesday counts more than a generic average. Red answers: at your pace, dry before the reset.
+
+
+
5h ▂▅█ 0.8x№ 0001
+
The 5h ledger — this sitting by the hour. Height is what each hour cost; now, ahead, ˍ ran-but-negligible, no sample. Ends with its pace: above 1x you cap before the reset.
+
+
+
7d ▃▅ˍ▃▅ ▃▃▁▂▁▯▯...▯5hx14 0.7x№ 0001
+
The week ledger — history in full, the future folded. Each cell one 5h window, day-gapped; after the now-marker the empty run folds to a counted tail: ...▯5hx14 answers "14 slots to the reset", and × red answers "the tail runs dry".
+
+
+
! 7d dry ~Tue 04:00 · hard stop№ 0008
+
The projection letter, before the bill. The learned forecast can warn on a calm Friday about your heavy Tuesday. The pin stays while the condition holds; row 3 explains once, then fades.
+
+
+
+ fb 91% vs 7d 55% · go op№ 0006
+
The pin. One notice, compacted to the room line 1 leaves, cut at its weakest joint. The number you act on is bold; the hue is the stance — cyan opportunity, yellow and red pressure, dim budget.
+
+
+
≡!419k ≡~ !net№ 0010
+
Truth badges. A prompt-cache break on resume (≡!419k — a full-prefix rewrite at 20× the read rate), a rebuild in progress, a fetch that failed and why. Silent while healthy: zero width spent on a warm cache.
+
-

Built like it matters

+

Built like a meter should be

An advisor, not a dashboard

-

A second row that interprets the badges: ! 5h caps ~05:18, 42m before reset when you'll hit a wall, + 7d resets @07:00, 56% unused · spend it when paid capacity is about to expire unused. Quiet means no row at all.

+

Row 2 interprets the badges: ! 5h caps ~05:18, 42m before reset when you will hit a wall, + 7d resets @07:00, 56% unused · spend it when paid capacity is about to expire. Quiet means no row at all.

Numbers that don't lie

-

Wall-clock resets instead of decaying countdowns. A visible [░░░░░░0%] instead of a hidden bar. Stale data is always badged, never passed off as fresh.

-

Cache break detection

-

Resume after an idle gap longer than the TTL and ≡!419k fires — the full-prefix rewrite you can't see coming, sized and colored by severity. Silent while healthy; no width wasted on a warm cache.

-

Polite to the API

-

N concurrent Claude Code instances share one cached fetch behind an atomic lock — at most one usage request per account per TTL window. Escalating backoff honors Retry-After.

-

Works behind TLS inspection

-

Corporate mitm proxy or a local tracer: it builds a combined CA bundle from NODE_EXTRA_CA_CERTS automatically, so fetches survive where plain curl dies.

-

Documented wire contract

-

The observed /api/oauth/usage schema is versioned in-repo and re-synced against CLI captures, so parser changes check against ground truth.

-

384 tests on one bash file

-

bats suite covering quota math, lock contention (20-contender single-winner), forecast pacing, ANSI rendering, and the installer. CI on every push.

+

Wall-clock resets instead of decaying countdowns. A visible [░░░░░░0%] after /compact instead of a hidden bar. Stale data is badged, never passed off as fresh. Burn is a monotone envelope, never a re-earned dip.

Nothing running in the background

-

No daemon, no telemetry, no npm. It runs when Claude Code renders the prompt, reads stdin + one cached API call, prints a line, exits.

+

No daemon, no telemetry, no npm. It runs when Claude Code renders the prompt, reads stdin plus one shared cached API call, prints its rows, exits. 417 bats tests on the one file.

What it is not

@@ -210,10 +292,11 @@

What it is not

-