Skip to content

feat: show accounts pooled by a CLIProxyAPI usage hub - #1267

Closed
Bil0000 wants to merge 3 commits into
robinebers:mainfrom
Bil0000:feat/cliproxy-usage-hubs
Closed

Bil0000 wants to merge 3 commits into
robinebers:mainfrom
Bil0000:feat/cliproxy-usage-hubs

Conversation

@Bil0000

@Bil0000 Bil0000 commented Sep 15, 2026

Copy link
Copy Markdown

TL;DR

Connect a CLIProxyAPI hub in Settings → Usage Hubs and every Claude and Codex account it pools shows up as its own card, with the same live limits, plan badge, and reset-credit action a local login gets. Mirrors the usage-limit hub support in T3 Code (apps/server/src/usage/cliproxyApi.ts), on top of the account-first model from #1264.

Stacked on #1264. This branch is maddada:feat/codex-swap-accounts plus one commit; GitHub can't target a fork branch as the base, so the diff here includes #1264's commits until it merges. The hub change itself is c9c8b8b (21 files, +1256/-68).

What was happening

  • OpenUsage could only show accounts signed in on this Mac. Teams pooling subscriptions behind a CLIProxyAPI hub had no way to watch those accounts' Session/Weekly limits or banked reset credits from the menu bar.
  • T3 Code already reads exactly this data from a hub's management API (auth-files + api-call with $TOKEN$ substitution), so the request contract is known and stable.

What this changes

  • Settings → Usage Hubs. A new section between iCloud Sync and Appearance lists connected hubs (label, account count, URL) with Remove behind a confirmation alert, and Add Hub… opens an inline editor (hub URL, management key, optional label) in the same recessed block the API Key editor uses. Connect lists the hub's accounts before saving, so a wrong URL or key fails right there. Hubs persist in ~/.config/openusage/usage-hubs.json through the existing owner-only atomic writer; the key never touches UserDefaults.
  • UsageHubClient. Lists the hub's enabled claude/codex credentials (Codex identity from the listing's id_token claims, Claude identity from the account's /api/oauth/profile read once and cached), and exposes a Tunnel that conforms to HTTPClient and forwards any request through POST /v0/management/api-call for one credential. The existing CodexUsageClient, ClaudeUsageClient, both mappers, and CodexResetClaimService run unchanged against a hub account — no second parsing path.
  • UsageHubProvider. One ProviderRuntime per hub-held account. Each refresh relists the hub's accounts (so an account added to the hub later reaches the store and gets a card on the next launch, and a removed/disabled credential fails loudly with a typed error instead of fetching with a dead index), then reads usage through the tunnel. Errors map to ErrorCategory like every other provider.
  • Account registry. A new usageHub source kind. Hub-only accounts reconcile into ProviderAccountsStore and always mint a family@hash id — the bare claude/codex id stays reserved for a local login. An account that is also signed in on this Mac keeps its local card (the hub adds nothing a local login lacks). Hub accounts don't count as local logins for the "one local account owns unattributed history" rules from fix: discover Claude Swap accounts and isolate their usage #1226/Add Codex Swap account support #1264, so connecting a hub never flips a single local login into multi-account mode.
  • Descriptors. The live-limit rows are split out of CodexProvider/ClaudeProvider into limitDescriptors(provider:); local cards keep the same list plus trend and spend tiles, hub cards show only the limits. Hub cards inherit their family's DefaultLayout defaults through the existing expandingAccounts fan-out and slot right after the family's local cards in the provider order.
  • Reset credits. A Codex hub account gets a CodexResetClaimService over the tunnel; after a successful claim the hub's routing cooldown for that account is cleared via reset-quota (best-effort, like T3 Code) before the post-claim refresh.
  • Docs. docs/settings.md (new Usage Hubs section), docs/providers/claude.md, docs/providers/codex.md, README.

Heads-up

  • Cards are built at launch, so a hub added or removed in Settings takes effect on the next launch; the section says so inline. Same restart contract as Swap accounts in Add Codex Swap account support #1264.
  • Plain http:// works for localhost, IPs, and .local names; other hosts need https:// (ATS). Documented.
  • A Claude hub account's plan comes from Anthropic's profile, read at most once per launch per card (the same restraint ClaudeProvider applies per token).
  • Not in scope: local spend tiles / Usage Trend for hub accounts (the session logs live on the hub) and routing agent traffic through the hub — this is read-only usage, like T3 Code's hub source.

Tests

UsageHubTests (11 tests): listing filters (disabled, non-Claude/Codex, nameless credentials), known-identity reuse without profile calls, typed management errors (401 → key rejected, transport, bad URL), Codex card usage + credits + plan through the tunnel with the $TOKEN$ header and ChatGPT-Account-Id, Claude card usage + one-shot plan lookup, dropped-account and expired-login error categories (and that upstream error bodies never reach the badge), reset claim through the tunnel with cooldown clear, hub-only cards minting ids while a local login wins and never enters multi-account mode, card order + default fan-out, store round-trip, id/label derivation.

Full suite on macos-26 CI (fork run): 1385 tests, 3 skipped, 0 failures. The api-call envelope shape (Codex usage, Claude usage, Claude profile) was also checked live against a local CLIProxyAPI hub with the same request headers the code sends.

Screenshots

To follow.

maddada and others added 3 commits September 15, 2026 12:40
Preserve matching Codex and Swap logins on each account card, keep workspace identities distinguishable, and test credential fallback, account switching, and spending isolation.
Connect a CLIProxyAPI hub in Settings and every Claude and Codex account
it pools becomes its own card, refreshed through the hub's management
API with that account's token. Mirrors the usage-limit source T3 Code
supports for the same hubs.

- Settings ▸ Usage Hubs: add a hub (URL, management key, optional
  label) after a live account listing, or remove it behind a
  confirmation. Hubs persist in ~/.config/openusage/usage-hubs.json.
- UsageHubClient lists the hub's enabled Claude/Codex credentials and
  tunnels any provider request through `api-call`, so the existing
  Codex and Claude usage clients, mappers, and the reset-credit claim
  run unchanged against a hub account. A claim also clears the hub's
  routing cooldown (`reset-quota`).
- Hub-only accounts reconcile into the account registry with a
  `usageHub` source and a minted `family@hash` id; an account signed in
  locally keeps its local card, and hub accounts never count as local
  logins for the unattributed-history rules.
- Hub cards show the family's live-limit rows (split out of the Codex
  and Claude descriptor lists) and skip the log-backed trend and spend
  tiles, which need the session logs that stay on the hub.
@github-actions

Copy link
Copy Markdown

Thanks for your interest in contributing to OpenUsage!

External pull requests must reference an open issue that:

  1. Has been approved by a maintainer with the approved label.
  2. Is assigned to the pull request author (@Bil0000).

Please discuss the change on an issue first, wait for a maintainer to approve and assign it to you, then reopen this pull request with Fixes #123 in its description.

Read the contribution guidelines.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants