claudewheel v0.24.2 /claudewheel.tokens
Edit
On this page

Parse, expire, and write OAuth token entries in ~/.claudewheel/tokens.json.

#claudewheel.tokens

#claudewheel.tokens

Parse, expire, and write OAuth token entries in ~/.claudewheel/tokens.json.

#TokenExpiryDisposition

How a token's expiry is recorded when writing it to tokens.json.

The caller MUST choose one explicitly -- there is no default -- so token lifetime is never silently fabricated.

  • TTL: a claude setup-token, genuinely valid for TOKEN_TTL_DAYS.

The entry gets created (today) and expires_at (created + TTL).

  • UNKNOWN: an externally-issued token whose expiry we cannot know.

The entry gets created (today) and the expiry_unknown marker, and NO expires_at -- expiry is reported as unknown, never assumed.

#parse_entry

python
def parse_entry(entry: object) -> str | None

Extract the token string from a tokens.json entry.

Supports both formats: a bare string, or a dict like {"token": ..., "created": ..., "expires_at": ...}. Returns None if the entry is empty, absent, or unrecognized.

#TokenExpiry

Computed token lifetime: creation date, expiry date, days remaining.

remaining_days is None only for entries marked with an unknown expiry disposition -- a distinct, honest "we don't know" that consumers must handle separately from the "assume fresh" fallback (which reports a concrete TOKEN_TTL_DAYS).

#compute_expiry

python
def compute_expiry(entry: object, tokens_mtime: float, today: date | None=None) -> TokenExpiry

Compute a token entry's creation date, expiry date, and remaining days.

Precedence: an explicit unknown-expiry marker yields (None, None, None); else explicit "expires_at" ISO date; else "created" + TOKEN_TTL_DAYS; else (legacy bare-string entry) the tokens.json file mtime + TOKEN_TTL_DAYS. Unparseable or absent dict fields yield (None, None, TOKEN_TTL_DAYS), matching the historical health-check behavior of assuming a fresh token.

#_read_tokens_for_write

python
def _read_tokens_for_write(path: Path) -> dict[str, Any]

Read a tokens.json for a write operation. Missing -> {}; corrupt -> OSError.

Preserves the historical write-path contract: a corrupt file is a hard OSError so callers never silently clobber it. Shared by TokenStore.add and TokenStore.set_tier so the message and behavior stay identical regardless of which path is targeted.

#_write_token

python
def _write_token(path: Path, name: str, token: str, *, expiry: TokenExpiryDisposition, tier: str | None=None, subscription: str | None=None) -> None

Add/update a token entry in the tokens.json at path (0600, atomic).

expiry selects how the entry records its lifetime -- see :class:TokenExpiryDisposition. There is no default: the caller must decide, so token expiry is never silently fabricated.

#_write_tier

python
def _write_tier(path: Path, name: str, *, tier: str | None=None, subscription: str | None=None) -> None

Merge/create a tier metadata entry in the tokens.json at path.

#TokenStoreError

Raised when a tokens.json cannot be read/parsed and resolution cannot proceed.

#TokenStore

Path-injected read/write facade over a single tokens.json file.

All paths are explicit -- TokenStore never reads module path constants. Read APIs (load/token_for/names/expiry_for) raise TokenStoreError on a corrupt or unreadable file. Write APIs (add/set_tier) preserve the historical OSError contract; rename/remove swallow read errors and return False, mirroring the profile_ops helpers they replace.

#load

python
def load(self) -> dict[str, Any]

Parse the tokens.json. Missing -> {}; corrupt/unreadable -> TokenStoreError.

#token_for

python
def token_for(self, name: str) -> str | None

Return the token string for name, or None if absent/tier-only.

#plan_env_for

python
def plan_env_for(self, name: str) -> dict[str, str]

Return name's declared plan tier as Claude Code env vars.

Reads the subscriptionType and rateLimitTier fields of the entry, mapping each present one to its CLAUDE_CODE_* variable. An entry that declares neither yields an empty dict; a legacy bare-string entry likewise yields one.

Declared values are validated against the sets Claude Code actually compares against, because a value it does not recognize behaves exactly like no value at all -- the tier resolves to null and the failure looks identical to not having configured anything. Raises :class:ValueError naming the offending field and the accepted values.

#names

python
def names(self) -> set[str]

Return the set of profile names present in the file.

#expiry_for

python
def expiry_for(self, name: str) -> TokenExpiry | None

Compute name's expiry, using the file mtime for legacy entries.

Returns None when the entry is absent. Raises TokenStoreError if the file is corrupt (via load()).

#add

python
def add(self, name: str, token: str, *, expiry: TokenExpiryDisposition, tier: str | None=None, subscription: str | None=None) -> None

Add/update a token entry (0600, atomic). OSError on corrupt file.

expiry is required: the caller must choose how the token's lifetime is recorded (see :class:TokenExpiryDisposition).

#set_tier

python
def set_tier(self, name: str, *, tier: str | None=None, subscription: str | None=None) -> None

Merge/create tier metadata (0600, atomic). OSError on corrupt file.

#rename

python
def rename(self, old: str, new: str) -> bool

Move the old key to new. Returns True if the entry existed.

#remove

python
def remove(self, name: str) -> bool

Remove name's entry. Returns True if it existed.

Search