Summary aggregator could block indefinitely: it ran the full system health
audit (a synchronous execSync) on every request and had no per-account or
request-level timeout. Now:
- per-account fetch is bounded; whole response is raced against a deadline
- health is derived per-account from each quota result (no blocking audit)
- stale-while-revalidate keeps the last value when a refresh is slow/fails
Adds GET /api/bar/analytics plus a pure, tested aggregator rolling up
today/7d/30d/all-time spend, a 7-day sparkline, and top models.
normalizeCliproxyUsageHistoryDetail passed calculateHistoryDetailCost (a
~6ms model-pricing lookup) as an eager default argument to
normalizePersistedNumber, so it ran for every record even when a persisted
cost was already present. A few thousand records turned into a multi-second
synchronous stall that wedged the bar summary aggregator and the dashboard
startup prewarm. Compute the fallback only when cost is actually missing.
Send the composite provider:accountId (row.id) for Set as default so the server
can resolve the CLIProxy account; feature the account closest to exhaustion
(lowest remaining quota) in the title instead of the healthiest; rename the
packaged asset to CCS-Bar.app.zip to match what 'ccs bar install' downloads.
Only managed-quota providers (agy/claude/codex/gemini/ghcp) enforce tier_lock;
validate the provider against that set so locking a non-managed provider returns
400 instead of persisting a silently-unenforced config entry.
Resolve the app path via os.homedir() in launch to match install/uninstall;
validate the host on every redirect hop (manual follow, not blind
maxRedirections); reject zip-slip entries before extracting into ~/Applications.
Drop the detail.source fallback and dead numeric-key lookup in the usage
transformer so unmapped auth_index rows go to the 'unknown' bucket instead of
mis-keying cost; null out today_cost when an email cost-key is shared by more
than one account (duplicate-email providers) instead of double-displaying the
combined spend.
package_app.sh assembles and signs CCS Bar.app (menu-bar-only via LSUIElement).
Default ad-hoc signing for v1 with documented Gatekeeper guidance; developer-id
mode wired for the notarized public-launch path.
Menu-bar app that paints cached rows instantly and fires a debounced
force-refresh on open. Dropdown shows per-account health, quota, tier, cost and
paused state with pause/resume, set default, solo and tier-lock actions, plus an
offline state when CCS is not running.
Pure-Foundation CCSBarCore: thin CCS web-server client (summary force-fresh,
pause/resume/default/solo/tier-lock), bar.json discovery with an offline state,
summary models, status-title formatting, and a force-refresh debouncer. Tested
via a runnable assert harness (ccs-bar-check) so it builds without full Xcode.
Add ccs bar (install/launch/uninstall/version) and the ~/.ccs/bar.json
discovery handshake the app reads. install resolves a floating release asset,
follows redirects, validates the download host over HTTPS, checks status,
verifies the extracted app, and runs a real version-compat handshake against
/api/overview.
Make tier_lock a per-provider map and honor it in findHealthyAccount and
preflightCheck for the selected provider only, so locking one provider never
disables failover for others. Add POST /api/accounts/tier-lock with tier
validation against known tiers, and serialize quota_management on config write
so the lock persists.
Single endpoint merging per-account quota, tier, paused, health and today cost
for the menu bar. Cached by default; ?refresh=true invalidates the quota cache
and pulls live server-side, debounced ~15s. Per-account errors degrade one row
without failing the payload; force-fresh skips paused accounts and caps fetch
concurrency.
Carry the CLIProxy auth_index through the usage transformer and aggregator,
build an auth_index->account map from the auth files, and wire it into the
usage syncer so persisted snapshots stamp accountId. Adds getTodayCostByAccount
and a snapshot detail reader. Backward-compatible: accountId is optional and
profile-based aggregation is unchanged.
72ea1fc9 added a 'free' audience to AUDIENCE_ORDER in account-visual-groups.ts
but left the unit test asserting the old 'personal'/'Personal · Free' labels,
so the suite fails on dev. Realign the two assertions to the current source
output: free-tier codex variants resolve to audience 'free', inline label
'Free', and null compact detail label.
The validate/build/test/compose-parity jobs are gated to trusted author
associations so untrusted fork code never runs on the self-hosted runners.
But a job skipped via a job-level if reports no status, and branch protection
requires the job names typecheck/lint/format/build/test directly, so fork PRs
hang on 'Expected - waiting for status to be reported' and can never merge.
Add a single CI Gate job (if: always(), needs the gated jobs, no checkout so it
runs no third-party code) that fails only when a gated job actually
failed/cancelled and passes when they succeed or skip-for-fork. Branch
protection should require CI Gate instead of the individual job names.
Closes#1461
Final review polish:
- Add an inline comment on the claude-opus-4-7-thinking registry entry
explaining it is a pricing-only id kept for historical analytics data
(no catalog model after Opus 4.7 moved to adaptive thinking levels),
and why it carries no fast tier.
- Add a test asserting applyServiceTier preserves the serviceTiers map on
its result, guarding against a future simplification dropping it.
Built [OnSteroids](https://onsteroids.ai)
Address presto review on the fast-tier work:
- Move the claude-opus-4-8 entry after claude-opus-4-7-thinking so the
registry keeps chronological 4.6 -> 4.7 -> 4.8 ordering (the 4.8 insert
had split the 4-7 / 4-7-thinking pair).
- Drop the fast serviceTier from claude-opus-4-7-thinking: that id is a
legacy pricing-only entry with no catalog model, so a fast premium on it
is meaningless. The active 4.6-thinking variant (agy provider) keeps its
fast tier.
- Extend the 4.7 fast-tier test with cache-rate assertions and add an
equivalent 4.6 fast-tier test so the derived buildRates math is covered
for every premium-tier model.
Built [OnSteroids](https://onsteroids.ai)
Hoist the fast-mode rate sets into OPUS_46_47_FAST_RATES and
OPUS_48_FAST_RATES module constants (built via buildRates) so the
registry entries reference shared values instead of repeating literal
buildRates(...) calls. Also wires the fast tier onto the -thinking
aliases (claude-opus-4-6-thinking / -4-7-thinking) so they bill at the
same premium as their base ids.
Built [OnSteroids](https://onsteroids.ai)
Address review feedback on the per-service-tier pricing:
- Extract `buildRates(input, output)` plus named CACHE_5M_WRITE_MULTIPLIER
/ CACHE_READ_MULTIPLIER constants so fast-tier cache rates are derived
from Anthropic's documented multipliers instead of hand-computed numbers.
This removes the inconsistency where Opus 4.6 fast-tier lacked the
explanatory inline comments that 4.7/4.8 carried (presto suggestion #3).
- Document in `applyServiceTier` that no production caller passes
`serviceTier` yet (Anthropic's service_tier is not captured on
CliproxyRequestDetail), so fast-mode usage is currently billed at the
standard rate until the usage pipeline records the tier.
- Add a combined date-suffix + fast-tier lookup test to prove the two
resolution stages compose.
Built [OnSteroids](https://onsteroids.ai)
Commit 72ea1fc9 ("fix(accounts): simplify codex free tier badges") split
free codex accounts out of the `personal` audience into a distinct
`free` audience and dropped the `'Personal · '` prefix on inlineLabel,
but `account-visual-groups.test.ts` was not updated and has been
failing in CI ever since.
Update expectations to match the shipped behavior:
- `audience` for `-free` tokens is `'free'`, not `'personal'`
- `inlineLabel` for a bare free account is `'Free'`, not `'Personal · Free'`
- `compactDetailLabel` is `null` when no extra parts are present
- Sort order: business -> personal -> free -> unknown
Drive-by fix unrelated to the Opus 4.8 work in this PR but caught
because these tests started failing on every push to the branch.
Built [OnSteroids](https://onsteroids.ai)
Extend `ModelPricing` with an optional `serviceTiers` map keyed by
Anthropic's `service_tier` request parameter. `getModelPricing(model,
{ serviceTier })` returns the matching tier rates when present and
transparently falls through to base rates otherwise — existing call
sites keep current behavior.
Registry entries added (per Anthropic Fast mode pricing docs):
- claude-opus-4-8 fast: $10/$50 input/output (2x premium)
- claude-opus-4-7 fast: $30/$150 (6x premium)
- claude-opus-4-6 fast: $30/$150 (6x premium)
Cache rates derived from the documented Prompt caching multipliers
(1.25x for 5-min cache write, 0.1x for cache read).
Note: tier tracking through CliproxyRequestDetail is not wired yet —
that's a follow-up so usage transformers can pass `serviceTier` when
Anthropic returns it on the response. Schema is in place; integration
can land independently.
Also adds a defensive `claude-opus-4-8-20260530` pricing test to exercise
`stripDateSuffix` even though Anthropic no longer issues date-stamped IDs
for the 4.6+ generation (addresses presto-review suggestion #1).
Built [OnSteroids](https://onsteroids.ai)