Skip to main content

Manage a local registry of Claude login snapshots for CLI selection and Python SDK usage.

Project description

claude-select 🔐

PyPI version Python versions CI

中文说明

claude-select overview

claude-select is a local Claude auth registry and selector for people who use multiple Claude accounts on one machine.

It captures the current Claude CLI login state, stores each account as a snapshot in a local SQLite registry, shows expiry status in a table, and lets you:

  • select one stored account back into Claude's live auth state for CLI use
  • store long-lived tokens for SDK/program use
  • read one stored entry from Python and build env for Claude Agent SDK use

It does not auto-refresh OAuth tokens. It trusts the captured expiresAt value and asks the user to log in again when an account is near expiry or expired.

Install 🚀

pip install claude-select

How Users Get Started 👇

1. Capture your 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:

  1. choose an alias such as work or personal
  2. claude-select launches claude
  3. inside the claude CLI session, run /login and finish authorization
  4. exit claude and return to claude-select
  5. press Enter so claude-select can capture the current login snapshot
  6. look for a success block that confirms the account was saved and shows the current registry

You can add another 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

In that mode, claude-select will print guidance and wait while you run claude and /login yourself.

After CLI account capture, init can also walk you through claude setup-token so you can add a long-lived token for SDK/program usage.

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

Alias (blank to finish): personal
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 personal <b@gmail.com> [Personal].
Status: healthy
Expires in: 7h 59m

Current registry:
Alias     Kind  Email            Organization  Status   Expires In  Last Selected
--------  ----  ---------------  ------------  -------  ----------  -------------
personal  cli   b@gmail.com      Personal      healthy  7h 59m      -
work      cli   a@company.com    Team A        healthy  7h 57m      -
Add another account? [Y/n] n

What add looks like

$ claude-select add 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      -

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.
Paste the long-lived token: ********
Email: a@company.com
Organization (optional): Team A
Organization ID (optional):
Account UUID (optional):
Captured [token] work-sdk <a@company.com> [Team A].
Status: healthy
Expires in: 8759h 59m

Current registry:
Alias     Kind   Email            Organization  Status   Expires In   Last Selected
--------  -----  ---------------  ------------  -------  -----------  -------------
work-sdk  token  a@company.com    Team A        healthy  8759h 59m    -

2. 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         8759h 59m   -

3. 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

$ claude-select whoami
Current Claude live account
  matched alias: work
  email: a@company.com
  organization: Team A
  expires in: 7h 54m
  5h quota left: 76.0%
  5h resets in: 3h 12m
  7d quota left: 59.0%
  7d resets in: 2d 4h

4. Use an account in Python

from claude_select import AuthManager
from claude_code_sdk import ClaudeAgentOptions, query

manager = AuthManager()
env = manager.build_sdk_env("work")
auto_env = manager.build_sdk_env_auto()

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.

If you store multiple long-lived token entries with add-token, you can let claude-select auto-pick the best SDK token before each call:

from claude_select import AuthManager

manager = AuthManager()
env = manager.build_sdk_env_auto()

build_sdk_env_auto() only considers token entries, checks their cached 5h / 7d quota state, and chooses the best available token before returning an env mapping.

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 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
  • add: launch claude in the current terminal by default, then capture the current login into the registry
  • add-token: launch claude setup-token in the current terminal by default, then store a long-lived token for SDK/program use
  • relogin: launch claude in the current terminal by default, then overwrite one stored alias after the user logs in again
  • list: show the current registry table and do a light sync of the current live account first
  • list --usage: fetch and display 5h / 7d quota information for each stored alias
  • watch: keep a live Rich-powered view of the current Claude live account plus the local registry and quota data, with periodic live-state sync
  • select: write one stored snapshot back into Claude's live auth state
  • sync-current: read Claude's current live auth state and sync any refreshed token data back into the matching registry entry
  • remove: delete one stored account
  • export-env: print SDK env vars for one alias
  • current: show the last alias selected for CLI use
  • whoami: 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")
auto_env = manager.build_sdk_env_auto()
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 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 pick_sdk_account(self, preferred_alias: str | None = None) -> dict: ...
    def build_sdk_env_auto(self, preferred_alias: str | None = None, 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")

Top-level auto-selection helper:

from claude_select import build_sdk_env_auto

env = build_sdk_env_auto()

Quota responses are cached locally for 60 seconds. watch, whoami, and repeated SDK calls reuse that cache instead of hitting the remote usage endpoint on every refresh.

How Expiry Works ⏳

claude-select does not refresh tokens automatically.

It only reads the stored expiresAt timestamp and derives status from it:

  • healthy: more than 6 hours remain
  • expiring_soon: 6 hours or less remain
  • expired: already expired
  • unknown: no expiresAt value was captured

When an account is expired:

  • select fails
  • build_sdk_env() fails
  • the user should log in again and run:
claude-select relogin <alias>

Storage Model 🗃️

The registry is stored locally in SQLite:

  • macOS / Linux default:
    • ~/.config/claude-select/registry.db
  • custom XDG config root:
    • $XDG_CONFIG_HOME/claude-select/registry.db

Each stored account contains:

  • alias
  • email
  • organization name / id
  • account uuid
  • captured time
  • expiry time
  • last selected time
  • stored Claude oauthAccount
  • stored Claude claudeAiOauth credentials payload

Claude's own live state remains separate:

  • global Claude config
  • Claude credentials file or macOS Keychain

The registry is the source of truth. select copies one stored snapshot back into Claude's live state.

Current Limitations ⚠️

  • This project currently centers on captured Claude OAuth snapshots.
  • It does not auto-refresh tokens.
  • It relies on the structure of Claude's current local auth state.
  • Expiry monitoring is local-only; it does not call a remote validation API.
  • Selecting an account updates the local machine's active Claude login state.

Development 🛠️

Install dev 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

claude_select-0.3.0.tar.gz (35.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

claude_select-0.3.0-py3-none-any.whl (28.6 kB view details)

Uploaded Python 3

File details

Details for the file claude_select-0.3.0.tar.gz.

File metadata

  • Download URL: claude_select-0.3.0.tar.gz
  • Upload date:
  • Size: 35.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for claude_select-0.3.0.tar.gz
Algorithm Hash digest
SHA256 e51d65c4ef8638cef26eb6073c1235240ae5ec2eb5b0721a8bcce5f8d7dd3bf9
MD5 110314cc4c02b2033562daf5d8dce812
BLAKE2b-256 52178f160cd064bf068277aa328312a4ce201d878a68e1ea3849171329a26c88

See more details on using hashes here.

Provenance

The following attestation bundles were made for claude_select-0.3.0.tar.gz:

Publisher: publish.yml on Nomia/claude-select

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file claude_select-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: claude_select-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 28.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for claude_select-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 2458002b28b003e4e624b76a23e578c8d81becb58a1fb63ac476b4de3eec4f51
MD5 1ba9923036983e981919a9da4b50dc83
BLAKE2b-256 b1f3d10f983d80c7883a8eb62c246cdca3bc57c8a0ab93d983b6343c76005d84

See more details on using hashes here.

Provenance

The following attestation bundles were made for claude_select-0.3.0-py3-none-any.whl:

Publisher: publish.yml on Nomia/claude-select

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page