feat: add Claude multi-account support#965
Conversation
There was a problem hiding this comment.
Pull request overview
Adds first-pass multi-account support for the Claude provider by discovering additional Claude Code credential sources on the machine, persisting extra account instances, and surfacing them as separate provider cards that can be renamed live in Customize.
Changes:
- Discover extra Claude accounts from
~/.claude*/ XDG config /CLAUDE_CONFIG_DIRplus keychain service-prefix enumeration, and reconcile them into persisted extra-account records. - Instantiate one
ClaudeProviderper extra account (claude.<uuid>), namespace widget IDs accordingly, and intentionally omit spend tiles for non-default accounts. - Add live provider display-name overrides (for extra-account renames) and corresponding UI + tests + docs.
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| Tests/OpenUsageTests/TestSupport.swift | Extends keychain test double with service-prefix enumeration support. |
| Tests/OpenUsageTests/LayoutStoreTests.swift | Adds coverage for live provider name overrides across LayoutStore accessors. |
| Tests/OpenUsageTests/ClaudeAccountDiscoveryTests.swift | Adds end-to-end tests for discovery, reconcile, scoping, and namespacing behavior. |
| Sources/OpenUsage/Views/CustomizeProviderDetailView.swift | Adds per-extra-account rename field in Customize detail view. |
| Sources/OpenUsage/Stores/LayoutStore+Customization.swift | Applies provider name overrides when returning Provider models to the UI. |
| Sources/OpenUsage/Stores/LayoutStore.swift | Adds providerNameOverride injection point for live provider display-name overrides. |
| Sources/OpenUsage/Services/SystemClients.swift | Adds keychain service-prefix enumeration API + Security-based implementation. |
| Sources/OpenUsage/Providers/Claude/ClaudeProvider.swift | Supports account-scoped provider instances and conditionally includes spend tiles only for default. |
| Sources/OpenUsage/Providers/Claude/ClaudeAuthStore.swift | Adds account scoping to isolate credential sources per extra Claude account. |
| Sources/OpenUsage/Providers/Claude/ClaudeAccountsStore.swift | New persisted store for extra Claude account records + reconcile logic + renames. |
| Sources/OpenUsage/Providers/Claude/ClaudeAccountDiscovery.swift | New discovery routine for locating extra Claude accounts from filesystem + keychain metadata. |
| Sources/OpenUsage/App/AppContainer.swift | Wires discovery + records into provider runtime list and LayoutStore defaults + live name override closure. |
| docs/providers/claude.md | Documents Claude multi-account behavior, discovery scope, and rename UX. |
| docs/provider-enablement.md | Notes that extra Claude accounts follow the same first-seen enablement path as new providers. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| /// Rename field for an extra Claude account. The placeholder shows the auto-generated name (e.g. | ||
| /// "Claude (3f9a2b1c)"); typing a name and committing it persists a custom name. The registry is built | ||
| /// at launch, so the new name shows on the next relaunch. No tooltip (house rule). |
| func genericPasswordServices(withPrefix prefix: String) throws -> [String] { | ||
| let query: [CFString: Any] = [ | ||
| kSecClass: kSecClassGenericPassword, | ||
| kSecMatchLimit: kSecMatchLimitAll, | ||
| kSecReturnAttributes: true | ||
| ] | ||
| var result: CFTypeRef? | ||
| let status = SecItemCopyMatching(query as CFDictionary, &result) | ||
| if status == errSecItemNotFound { return [] } | ||
| guard status == errSecSuccess else { | ||
| AppLog.warn(.keychain, "service enumeration failed (status \(status))") | ||
| throw KeychainError.readFailed("keychain enumeration failed (status \(status))") | ||
| } | ||
| guard let items = result as? [[String: Any]] else { return [] } | ||
| return items | ||
| .compactMap { $0[kSecAttrService as String] as? String } | ||
| .filter { $0.hasPrefix(prefix) } | ||
| } |
There was a problem hiding this comment.
Thanks for the PR! OpenUsage reviews external PRs only when they implement an issue a maintainer has already approved with the approved label. Please open an issue, wait for approval, then reopen this (or open a focused PR) linking it with Fixes #<issue>. Happy to take a look once it's linked to an approved issue.
— Powered by Cursor Automations
Sent by Cursor Automation: OpenUsage PR Gatekeeper
There was a problem hiding this comment.
Thanks for the PR! OpenUsage reviews external PRs only when they implement an issue a maintainer has already approved with the approved label. Please open an issue, wait for approval, then reopen this (or open a focused PR) linking it with Fixes #<issue>. Happy to take a look once it's linked to an approved issue.
— Powered by Cursor Automations
Sent by Cursor Automation: OpenUsage PR Gatekeeper


Approved issue
Fixes #402 (not approved but reopend? partially fixes it; only tackles Claude accounts)
TL;DR
Ability to have multiple Claude accounts and give them an appropriate name.
What was happening
I got annoyed by the fact that I couldn't see my work usage, but only personal.
What this changes
Add support for multiple Claude code accounts by checking
~/.claude*to find additional.credentials.jsonor getting them from the keychain with theclaude Code-credentialsprefix.Heads-up
Tests
I have verified the changes locally using a build, as well as written the correct tests where needed.
Screenshots