Lift chip text toward white so small tier badges (max/pro) read clearly on the
dark surface. Rename the icon toggle from Color/Mono to 'Icon' (it only swaps
the menu-bar icon, not the bar theme) to remove the misunderstanding. Make the
Dashboard button actually work when the server is down: probe reachability and,
if not up, launch 'ccs config' (detached) which boots the web-server and opens
the dashboard itself; degrade gracefully when the ccs binary isn't found.
Replace the raw system green/yellow/orange/red headroom colors (garish on the
dark surface and colliding with the brand orange) with a muted, intuitive
green-amber-coral-red palette that leans coral for 'low' so a near-empty
window never reads as the accent. Drop the cool indigo card wash for a neutral
elevated surface so the warm bars no longer clash; subscription identity stays
on the section header and badge.
Replace the prose window lines with an aligned per-window bar list (5h /
weekly / Opus / Sonnet), colored by headroom, with the binding window
highlighted and a compact reset chip. Humanize durations beyond a day
(Nd Nh instead of raw hours) and only show the burn-rate pace clause when a
window is projected to exhaust before it resets, so a meaningless 'resets in
44h' / '~110h left' projection no longer appears.
Lead the dropdown with subscriptions: a dedicated card shows the binding
window as the hero (gauge, remaining %, reset countdown, and a burn-rate
'left at this pace' line), a secondary window, and the Opus/Sonnet split for
Max, with a stale marker for older Codex data. Demote spend/usage to a compact
strip and surface cross-tool headroom. Add pure burn-rate / time-to-exhaustion
and binding-window math in Core. Kill the menu scroll indicator for real
(NSScrollView hider) so content stays aligned. Spend-cap alerts default off
(pay-per-use, opt-in).
Expose per-window detail (5h / weekly / Opus / Sonnet) on native subscription
rows instead of collapsing to a single percentage, so the bar can show the
binding window, resets, and a burn-rate pace line. Fix the Codex collector to
scan recent session rollouts newest-first for the latest non-null rate_limits
(today's exec-mode session is null) and mark the result stale with its source
time, so Codex quota appears instead of silently vanishing.
Show Claude Code and Codex subscription rows with the Tier 1 quota gauge and
reset countdown, visually distinguished from CLIProxy provider accounts, and
let them drive the existing quota alerts. Formatting + harness coverage for the
native rows.
Surface the logged-in Claude Code and Codex subscription quota as first-class
summary rows so the existing gauge and alert engine render them with no new UI.
Claude Code: read the native token (~/.claude/.credentials.json, macOS Keychain
fallback) and reuse the existing Anthropic usage fetch+normalize via a new
token-fed entry point. The /oauth/usage endpoint is hostile to polling, so the
fetch is server-side only behind a 10-minute on-demand cache, in-flight
coalescing, Retry-After + exponential backoff with jitter, a 3-strike circuit
breaker with a 15-minute cooldown, and serve-stale-on-failure; logged-out or
unsupported subscriptions never spend a token call. Codex: zero-network read of
the latest rate_limits from local session rollouts, omitted when absent.
Native rows side-load bounded so a slow or failed fetch degrades to CLIProxy
rows, never an error.
Add real quota gauges with threshold bands and a reset countdown for
quota-capable accounts. Introduce a pure, deterministic alert rule engine
(CCSBarCore) covering quota-remaining, daily/monthly spend caps, reauth, and
cooldown, with per-rule dedupe keys that re-arm on period rollover or
clears-then-recurs so it never spams. Deliver via UNUserNotificationCenter
with an in-dropdown Alerts fallback when notifications are denied. Add a
preferences surface (per-rule toggles, caps, quota levels, glance mode)
persisted in UserDefaults, and a configurable menu-bar glance
(auto/today/month/lowest-quota/account-count) that still never shows a
lifetime dollar. Default spend caps raised to $500/day and $10000/month.
monthToDate (calendar 1st-of-month to now, local) on BarAnalytics in both
compute paths, kept distinct from the rolling last30d so a fresh month resets
toward zero. Feeds the monthly-spend alert and the month-spend glance without
a rolling-window false breach.
Mirror the richer analytics payload (quotaStatus, per-account last-active,
bySurface) in the Codable models. Add a per-surface usage section (Claude
Code, Codex, Droid, CLIProxy) with proportional bars beside the existing
spend grid, sparkline, and top models. Rebuild the menu-bar title chain so a
lifetime dollar can never sit in the always-on title (it read as live spend):
quota% then today spend then account count. Rebuild the accounts rows
(default badge, tri-state quota, honest no-data cost, health), pivot to an
honest idle hero when no recent data, hide the scroll indicator, and tighten
spacing.
Derive a tri-state quotaStatus (ok|unsupported|error) per account so a
provider with no quota API renders as 'no quota' with a healthy dot instead
of an alarming bare dash, and treat unsupported as healthy in deriveHealth.
Switch the analytics endpoint off the CLIProxy snapshot, which freezes
whenever the proxy restarts (usage is in-memory only), and onto the merged
daily/hourly usage the dashboard uses. Recent activity from Claude Code,
Codex, and Droid now shows, and a per-surface breakdown answers where usage
goes. Add lastActivityAt, daysSinceLastActivity, hasRecentData and a 30-day
series; per-account today_cost reads null (unknown) rather than a misleading 0.
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)