Skip to content

feat: meter a second Claude or Codex account - #39

Merged
pekth merged 6 commits into
mainfrom
pekth/feat/multi-account-slots
Sep 27, 2026
Merged

pekth merged 6 commits into
mainfrom
pekth/feat/multi-account-slots

Conversation

@pekth

@pekth pekth commented Sep 27, 2026

Copy link
Copy Markdown
Owner

People who hold two accounts with the same CLI tool get one meter row per tool today: the row shows the first account's budget while the token totals silently mix both accounts' sessions. There is no way to see the second account's headroom, and no way to tell which account is about to get cut off.

What this does

A second account becomes its own provider slot (codexAlt / claudeAlt) — a full meter row with its own quota windows, plan badge, session history, heatmap, alerts, and (for Codex) reset credits. Slots are never merged with the primary account, because two accounts' windows are two different budgets.

Setup (documented in README and ADR 0005): a slot exists when its alternate config directory exists on the Mac.

launchctl setenv METERUSAGE_CODEX_ALT_HOME ~/.codex-alt     # CODEX_HOME-equivalent
launchctl setenv METERUSAGE_CLAUDE_ALT_CONFIG ~/.claude-alt # CLAUDE_CONFIG_DIR-equivalent

Stored defaults keys (meterusage.codexAltHome, meterusage.claudeAltConfig) work too; the environment wins. Config changes apply on relaunch.

Design notes

  • The Codex slot spawns its own codex app-server subprocess with CODEX_HOME set, so the CLI authenticates as the other account. meterusage still never reads any credential file; the Claude plan decode stays a narrow tier-only decode of the alternate directory's own .claude.json (no fallback to the primary account's file — a wrong-but-plausible tier is worse than none).
  • Slots are named by position ("Codex second account", a "2" beside the mark in the tray and notch). No account identifier is ever read or displayed, and directory paths render only back to the user in Settings, reduced to ~ form.
  • Per-slot state keys: durable history, quota archive, backoff, and alert state key on the slot's raw value; the JSON report gains additive codexAlt / claudeAlt entries (schema unchanged). Reset credits route to the slot whose loaded quota reported the credit id.
  • Service health stays per service: an alternate slot resolves its status through the base provider, so one outage tints both accounts without polling a public feed twice.
  • Single-account installs are unchanged: the slots default off, and an absent directory gates the slot out of polling, rendering, and alerts. Demo mode mounts synthetic second accounts for screenshot coverage.

Verification

  • swift build and swift test on macOS: 362 tests, 0 failures.
  • New MultiAccountTests cover resolution precedence (env > defaults), tilde expansion, blank-key handling, presence gating (including a directory removed mid-session), per-slot polling, reset routing, archive/history key separation, and JSON report entries.
  • Runtime check: METERUSAGE_DEMO=1 meterusage json lists claudeAlt and codexAlt entries alongside the primary slots.
  • Pre-file review: TypeSafe Jev judgments over per-concern diff chunks (presence gating, credential boundary, per-slot isolation, docs fidelity) — all supports at 0.92+ confidence; the two below-threshold findings were confirmed by direct read of the view/model hunks.

Docs

README (Second accounts section + provider matrix note), docs/PRIVACY.md (new source-table rows + identity-boundary paragraph), docs/KB.md, CHANGELOG.md, and new docs/adr/0005-multi-account-slots.md.

Model(s): glm-5.3-flash
Harness: T3 Code (OpenCode)

People who hold two accounts with the same tool got one meter row that
showed the first account's budget and silently mixed both accounts'
tokens. A second account is now its own provider slot (.codexAlt /
.claudeAlt) with its own quota windows, plan badge, session history,
heatmap, alerts, and (for Codex) reset credits — never merged, because
two accounts' windows are two different budgets.

A slot exists when its alternate config directory exists on the Mac:
METERUSAGE_CODEX_ALT_HOME (CODEX_HOME-equivalent) or
METERUSAGE_CLAUDE_ALT_CONFIG (CLAUDE_CONFIG_DIR-equivalent), with
stored defaults keys as fallback. The Codex slot spawns its own CLI
subprocess with that home; meterusage still never touches any auth
file. Slots are named by position only; no account identifier is ever
read or displayed.

Verified with swift build and swift test on macOS (362 tests) plus a
demo-mode meterusage json run listing both accounts per tool.
The second-account rows only appeared once the alternate config
directory already existed, so a fresh install showed nothing under
Settings and the feature was undiscoverable. The rows now always show
with an editable directory field (the same keys the environment
overrides), and the Accounts caption explains the setup flow.
The two fixed second-account slots capped at one extra account per
tool. Accounts are now a managed list: Settings gains a Second accounts
card with add and remove per row, a name, and the config directory —
a user with three Codex logins adds three rows and the app meters each.
Every surface keys readings by the slot's identity (provider + generated
id), so labels can change without orphaning history, archive, or alert
state; the JSON report carries the label in an additive account field.
Readings appear after relaunch, when composition builds each account's
sources.
The add buttons rendered the literal string 'Add (provider.disp…' — an
over-escaped interpolation. One compact 'Add account' menu in the section
header now names the tool per item (two side-by-side buttons truncated at
card width), rows animate in at 150 ms ease-out, and the copy is
tightened.
.fixedSize() let the header button overflow the popover edge; the label
shortens to 'Add' and the menu items still name the tool.
@pekth
pekth merged commit d99eded into main Sep 27, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant