Manage a local registry of Claude CLI auth snapshots plus simple SDK token entries.
Project description
claude-select 🔐
claude-select is a local Claude auth registry and selector for people who use multiple Claude accounts on one machine.
It stores two entry kinds:
clientries: captured from Claude CLI/login; these supportwatch,list --usage,whoami,select,sync-current, andrelogintokenentries: captured fromclaude setup-token; these are simple long-lived SDK credentials for Python/program use
Long-lived token entries do not participate in quota monitoring, quota-aware auto-selection, or CLI account switching.
Install 🚀
pip install claude-select
How Users Get Started 👇
1. Capture your CLI accounts
Run the guided bootstrap:
claude-select init
By default, claude-select launches claude in the current terminal for each account capture.
For each account:
- choose an alias such as
workorpersonal claude-selectlaunchesclaude- inside the
claudeCLI session, run/loginand finish authorization - exit
claudeand return toclaude-select - press Enter so
claude-selectcan capture the current login snapshot - look for a success block that confirms the account was saved and shows the current registry
You can add another CLI account later:
claude-select add work
claude-select add personal
If you do not want claude-select to launch claude for you, use:
claude-select add work --no-launch
What init looks like
$ claude-select init
Claude account bootstrap
Add accounts one by one. Complete /login for each account before capture.
Alias (blank to finish): work
Launching `claude` in this terminal.
Inside Claude, run `/login` and finish account authorization.
When login is complete, exit Claude to return here.
Press Enter after login is complete...
Captured work <a@company.com> [Team A].
Status: healthy
Expires in: 7h 57m
Current registry:
Alias Kind Email Organization Status Expires In Last Selected
----- ---- --------------- ------------ ------- ---------- -------------
work cli a@company.com Team A healthy 7h 57m -
Add another account? [Y/n] y
2. Optionally add a long-lived SDK token
You can store a claude setup-token token for explicit Python usage:
claude-select add-token work-sdk
add-token launches claude setup-token, tries to detect the token from terminal output, then stores it as a simple SDK credential. Because these official long-lived tokens are inference-only, profile metadata detection is best-effort and may fall back to manual prompts.
What add-token looks like
$ claude-select add-token work-sdk
Launching `claude setup-token` in this terminal.
Complete authorization. When the token is printed, copy it and return here.
Detected the long-lived token from setup-token output.
Validated token for SDK/program use.
Profile metadata is unavailable for this token scope.
Email: a@company.com
Organization (optional): Team A
Captured [token] work-sdk <a@company.com> [Team A].
Status: healthy
Expires in: 364d
Current registry:
Alias Kind Email Organization Status Expires In Last Selected
-------- ----- --------------- ------------ ------- ---------- -------------
work cli a@company.com Team A healthy 7h 57m -
work-sdk token a@company.com Team A healthy 364d -
3. See what is stored
claude-select list
claude-select list --usage
claude-select whoami
claude-select watch
Example table:
Alias Kind Email Organization Status Expires In Last Selected
-------- ----- ---------------- -------------- -------------- ---------- -------------
personal cli a@example.com Personal healthy 18h 12m 2h ago
work cli b@company.com Team A expiring_soon 1h 05m -
work-sdk token b@company.com Team A healthy 364d -
With --usage, cli entries show 5h/7d quota data and token entries show n/a because inference-only tokens do not expose quota/profile endpoints.
4. Select an account for Claude CLI
claude-select select work
This reads the stored snapshot from the local registry and writes it back into Claude's current live auth backend:
- macOS: Keychain + Claude config
- Linux / Windows: Claude credentials file + Claude config
Example:
$ claude-select select work
Selected work <a@company.com> [Team A].
Updated Claude live auth state:
- config: /Users/you/.claude.json
- credentials store: macOS Keychain
Current CLI alias: work
5. Use an entry in Python
from claude_select import AuthManager
from claude_code_sdk import ClaudeAgentOptions, query
manager = AuthManager()
env = manager.build_sdk_env("work")
# or explicitly use a long-lived SDK token entry:
# env = manager.build_sdk_env("work-sdk")
options = ClaudeAgentOptions(env=env)
async for message in query(prompt="analyze this repo", options=options):
print(message)
The Python side reads from the same local registry, but it does not mutate Claude's live auth state.
Full Python SDK guide:
CLI Commands 🧰
claude-select init
claude-select add <alias>
claude-select add-token <alias>
claude-select relogin <alias>
claude-select list
claude-select list --usage
claude-select watch
claude-select select [alias]
claude-select sync-current
claude-select remove <alias>
claude-select export-env <alias> --json
claude-select current
claude-select whoami
Command behavior:
init: guided multi-account bootstrap for CLI accounts, then optional token capture phaseadd: launchclaudein the current terminal by default, then capture the current login into the registryadd-token: launchclaude setup-tokenin the current terminal by default, then store a long-lived token for explicit SDK/program userelogin: launchclaudein the current terminal by default, then overwrite one stored CLI alias after the user logs in againlist: show the current registry table and do a light sync of the current live account firstlist --usage: fetch and display 5h / 7d quota information forclientries;tokenentries shown/awatch: keep a live Rich-powered view of the current Claude live account plus the local registry and CLI quota data, with periodic live-state syncselect: write one storedclisnapshot back into Claude's live auth statesync-current: read Claude's current live auth state and sync any refreshed token data back into the matchingcliregistry entryremove: delete one stored entryexport-env: print SDK env vars for one aliascurrent: show the last alias selected for CLI usewhoami: show Claude's current live auth state, matched alias, and current quota summary after a light sync
Python API 🐍
Minimal usage:
from claude_select import AuthManager
manager = AuthManager()
accounts = manager.list_accounts()
accounts_with_usage = manager.list_accounts(include_usage=True)
details = manager.get_account("work")
env = manager.build_sdk_env("work")
sdk_env = manager.build_sdk_env("work-sdk")
auth_payload = manager.export_sdk_auth("work")
live_quota = manager.get_live_quota()
account_quota = manager.get_account_quota("work")
all_quotas = manager.list_account_quotas()
Current public surface:
class AuthManager:
def list_accounts(self) -> list[dict]: ...
def get_account(self, alias: str): ...
def capture_current_account(self, alias: str, overwrite: bool = True) -> dict: ...
def add_token_account(self, alias: str, token: str, *, email: str, organization_name: str = "", organization_id: str = "", account_uuid: str = "", overwrite: bool = True) -> dict: ...
def relogin_account(self, alias: str) -> dict: ...
def remove_account(self, alias: str) -> None: ...
def select_account(self, alias: str) -> dict: ...
def build_sdk_env(self, alias: str, base_env: dict[str, str] | None = None) -> dict[str, str]: ...
def export_sdk_auth(self, alias: str) -> dict: ...
def get_live_quota(self) -> dict: ...
def get_account_quota(self, alias: str) -> dict: ...
def list_account_quotas(self) -> list[dict]: ...
def current_alias(self) -> str | None: ...
def render_table(self) -> str: ...
Top-level helper:
from claude_select import build_sdk_env
env = build_sdk_env("work")
Compatibility note:
build_sdk_env_auto()andpick_sdk_account()remain importable for older integrations, but they now raiseAccountSelectionError.- Reason: long-lived
claude setup-tokentokens are inference-only and do not expose the profile/quota data required for reliable quota-aware rotation.
Quota responses are cached locally for 60 seconds. watch, whoami, and repeated CLI quota reads reuse that cache instead of hitting the remote usage endpoint on every refresh.
How Expiry Works ⏳
claude-select does not refresh tokens automatically.
It reads the stored expiresAt timestamp and derives status from it:
healthy: more than 6 hours remainexpiring_soon: 6 hours or less remainexpired: already expiredunknown: noexpiresAtwas captured
If a cli account is expired:
selectfailsbuild_sdk_env()fails for that alias- you should run
claude-select relogin <alias>
Long-lived token entries do not participate in relogin; replace them by running add-token again.
Storage Layout 🗃️
The local registry is a SQLite database:
- macOS / Linux default:
~/.config/claude-select/registry.db
- if XDG is set:
$XDG_CONFIG_HOME/claude-select/registry.db
Each entry stores:
- alias
- kind (
cli_snapshotortoken) - organization name / id
- account uuid
- captured time
- expiresAt
- last selected time
oauthAccountclaudeAiOauthcredential payload
Claude's own live state remains separate:
- Claude global config
- Claude credentials file or macOS Keychain
The registry is the source of truth. select writes one stored cli snapshot back into Claude's current live state.
Current Limitations ⚠️
clientries are the only entries that support quota monitoring,watch,select,sync-current, andrelogin.tokenentries are simple SDK credentials only.- The tool does not auto-refresh OAuth tokens.
- The implementation depends on Claude's current local auth layout.
- Expiry monitoring is based on stored
expiresAt; it does not proactively validate every entry with a remote auth probe.
Development 🛠️
Install development dependencies:
pip install -e .[dev]
Run checks:
ruff check .
ruff format --check .
mypy
pytest
python -m build
python -m twine check dist/*
License 📄
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file claude_select-0.4.0.tar.gz.
File metadata
- Download URL: claude_select-0.4.0.tar.gz
- Upload date:
- Size: 38.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
73242e46e924e1c36a48601972369bfebbb9a0a5a5b64270f68886c72a4cf21c
|
|
| MD5 |
cf4a3d596fe8cd5efe812cbb6864647b
|
|
| BLAKE2b-256 |
f2fbbad11aa37733bbd1256dffb3d687a1a36f54a29a72193bf890488b94630c
|
Provenance
The following attestation bundles were made for claude_select-0.4.0.tar.gz:
Publisher:
publish.yml on Nomia/claude-select
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
claude_select-0.4.0.tar.gz -
Subject digest:
73242e46e924e1c36a48601972369bfebbb9a0a5a5b64270f68886c72a4cf21c - Sigstore transparency entry: 1517621070
- Sigstore integration time:
-
Permalink:
Nomia/claude-select@65c1c2e68132400d7a2f531f6ff5fbfa81768613 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/Nomia
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@65c1c2e68132400d7a2f531f6ff5fbfa81768613 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file claude_select-0.4.0-py3-none-any.whl.
File metadata
- Download URL: claude_select-0.4.0-py3-none-any.whl
- Upload date:
- Size: 30.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c25ed81ead7f89690289630772c790d76a6a1a55da79935ae16e00b09f64496c
|
|
| MD5 |
5126d0ae0ab3c2a623ee8c7a4c91ad0d
|
|
| BLAKE2b-256 |
a86edf33528efa85aa56a15682e190632e38c373f136bd52af9fb5ee39b4ebbb
|
Provenance
The following attestation bundles were made for claude_select-0.4.0-py3-none-any.whl:
Publisher:
publish.yml on Nomia/claude-select
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
claude_select-0.4.0-py3-none-any.whl -
Subject digest:
c25ed81ead7f89690289630772c790d76a6a1a55da79935ae16e00b09f64496c - Sigstore transparency entry: 1517621232
- Sigstore integration time:
-
Permalink:
Nomia/claude-select@65c1c2e68132400d7a2f531f6ff5fbfa81768613 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/Nomia
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@65c1c2e68132400d7a2f531f6ff5fbfa81768613 -
Trigger Event:
workflow_dispatch
-
Statement type: