Skip to content
Merged
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
26 changes: 26 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,31 @@
# Changelog

## v0.8.0 — name the wall (2026-09-02)

Claude Code can now offer `/low-priority` at a spent 5h session window, but
the gated offer, active mode, and separate allowance do not exist in
`/api/oauth/usage`. ccpace now says what its source can prove:
`5h capped · 47% of 7d left · back @Tue 2 04:00`. It distinguishes the
shorter wall from the weekly pool without claiming a session-only escape is
available for an account it cannot see into.

Notifications now carry the same facts. Their producers and formatter had
drifted onto different key names, causing real threshold and delta messages
to say `0%`; the payload now has canonical `window`, `utilization`,
`reset_at`, and `reset_time` fields while retaining the old aliases for
custom hooks. Custom-notifier envelopes also gain a stable event `id`, such
as `full:work:5h:<reset>`, for dedupe and tracing across restarts.

The old watch cache used the maximum of every counter as an account-level
stop: 5h at 100, or one scoped model at 100, froze the whole account until a
reset. That is false once lower-priority service can bypass 5h, and it was
already false for another model. Worse, the early return ignored a newer
shared cache from statusline and even `r`. The cache is now reserved for
one genuinely terminal state — aggregate 7d spent with no paid path — and
both newer shared evidence and manual refresh evict it.

85 tests.

## v0.7.0 — a guess may not delete a window (2026-09-01)

**The `×` cell is retired.** The ledger used to overwrite dry-projected
Expand Down
30 changes: 26 additions & 4 deletions DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,12 +111,30 @@ the DATE, and the `!` row is where it goes. Two lines describing one week
with two numbers is not more information; it is an argument the reader has
to settle.

A 5h cap is not a weekly cap. Its quota row says `cap`, never the occasional
endpoint value `101%`; the reset is the useful fact once the counter binds.
The wall row keeps the weekly pool beside it: `5h capped · 47% of 7d left · back
@Tue 2 04:00`. Claude Code may offer `/low-priority` there, but that gated
offer and its allowance are absent from `/api/oauth/usage`; this surface
states the two counters it can prove and does not impersonate session state.

## Notifications

The watch surface and its side channels share one vocabulary. Every event
payload names `window`, `utilization`, `reset_at`, and the display-safe
`reset_time`; producers do not invent aliases that formatters have to guess.
The envelope adds a stable, readable `id` (`full:work:5h:<reset>`), so a
custom notifier can dedupe or trace one condition across process restarts.
Threshold and delta IDs add the utilization that caused the event; two real
climbs inside one window remain two events.

## Provenance on the rule

`(cached)` frozen at 100% until reset · `(stale 12m · !429)` last fetch
failed, numbers this old · `(idle 12m)` Claude Code did nothing since,
so nothing was asked. Never a blank block: the last cache beats an empty
frame.
`(cached)` the aggregate weekly pool is spent and no paid path exists,
so this payload cannot move before its 7d reset · `(stale 12m · !429)`
last fetch failed, numbers this old · `(idle 12m)` Claude Code did nothing
since, so nothing was asked. A manual refresh or a newer shared cache always
breaks `(cached)`. Never a blank block: the last cache beats an empty frame.

## Requests

Expand All @@ -128,6 +146,10 @@ Ask only when the answer can have changed.
container sharing `~/.claude`) → no request; `r` overrides.
- Reset boundaries wake the loop; the poll interval (15 min ± 10%) is the
ceiling, not the metronome.
- A 5h or model-scoped cap never freezes the account: lower-priority service
or another model can keep moving the weekly counters.
- Only 7d at cap with no paid path is terminal until reset. Even then, `r`
and a newer cache from statusline take precedence over the optimization.
- Failure: last cache + badge, retry next tick; never a lockout, never
`usage.err` (that file is the statusline's).
- Profile 24 h (tier comes from the credentials file); prepaid 1 h and
Expand Down
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,12 @@ sorted by tier.
- Budget math truncates at the subscription period end (derived from
the billing anniversary — the API exposes no cancel/renew date, so
the boundary is assumed and marked with `~`).
- A spent 5h window is named separately from the week behind it:
`5h capped · 47% of 7d left · back @Tue 2 04:00`. Current Claude Code
may offer `/low-priority` at that wall, but eligibility and its separate
allowance travel with the session request, not `/api/oauth/usage`.
ccpace therefore reports the proven wall and weekly headroom without
claiming the mode is available for an account it cannot see into.

## Notifications

Expand All @@ -86,6 +92,11 @@ ccpace --watch --bark # bark CLI env: BARK_KEY on BARK_SE
ccpace --watch --notifier ~/bin/my-hook.sh # JSON on stdin
```

Custom notifier payloads carry a stable, inspectable `id` plus canonical
`window`, `utilization`, `reset_at`, and `reset_time` fields in `data`.
For example, `full:work:5h:2026-09-02T11:00:00+00:00` identifies one
condition across custom-notifier restarts.

Env: `CCPACE_NTFY`, `CCPACE_BARK`, `CCPACE_NOTIFIER`, `CCPACE_INTERVAL`,
`CCPACE_THRESHOLD`, `CCPACE_TZ` (e.g. `America/New_York,Asia/Tokyo`).
Bare `--bark` reads the bark CLI's own `BARK_KEY` / `BARK_SERVER`
Expand Down Expand Up @@ -119,6 +130,10 @@ writing; `CCPACE_DATA_DIR` relocates the store.
is the ceiling. A failed fetch keeps the last good numbers on screen,
badged `(stale 12m · !429)`, and the next poll is the retry — nothing
is locked out.
- A 5h or model-scoped cap does not freeze the account: lower-priority
service or another model can still move 7d. Only an exhausted aggregate
week with no paid path is cached to its reset, and both `r` and a newer
shared statusline cache break that optimization.
- The grammar — rows, ledger, provenance, requests — is one page:
[DESIGN.md](DESIGN.md).
- Forecasts are your own history extrapolated, not a promise. Below two
Expand Down
Loading
Loading