@modelprofile.com/authswitch
Manage Codex, OpenCode and Claude Code accounts, switch interactively, and inspect live subscription, usage and reset information. Codex switching also preserves ChatGPT remote-control pairings.
Issue Reporting and Security
For reporting bugs, issues, or security vulnerabilities, please visit community.foss.global/. This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a code.foss.global/ account to submit Pull Requests directly.
The problem
The Codex CLI holds exactly one active credential. Logging in as a second account overwrites the first, so working across two accounts means re-running the browser login every time.
Remote control makes that worse. A ChatGPT client pairs with the local Codex app-server, and the pairing is recorded in Codex' state database keyed by the account id. Swap the credential naively and the running app-server keeps serving the old identity until it is restarted, and the incoming account can come back without the enrollment that made it reachable from the ChatGPT app.
authswitch keeps a stash of credentials keyed by account email, captures each account's remote-control enrollments next to its credential, and puts both back together.
Install
This package is published to the private Verdaccio registry, not to npmjs, so the scope has to be mapped first:
pnpm config set @modelprofile.com:registry https://verdaccio.lossless.digital
pnpm install -g @modelprofile.com/authswitch
Node.js 24 or newer is required.
Usage
Run authswitch, authswitch -i, or authswitch --interactive for an interactive guide. Use the arrow keys and
Enter to switch accounts, save the current login, list accounts and live status, check remote
control, or remove a saved account. The guide shows the current account and returns
to the menu after each successful action; choose Back in a submenu or Exit to finish.
Before showing the actions, the guide checks whether the current credential has a matching, readable saved copy. A new or refreshed login gets an offer to save it while keeping it active. The switch action checks again before offering targets; declining that save cancels the switch. Scripts still preserve the outgoing login automatically through the harness adapter.
When saving, choose whether to keep the account active or clear the active login so
you can run codex login for another account. Removing a saved account requires
confirmation and leaves the active login in place.
The guide requires an interactive terminal. With no arguments, piped output and CI
runs show help instead; explicitly requesting -i, --interactive, or --tui
without an interactive terminal fails. Explicit commands remain available for scripts:
authswitch # interactive guide
authswitch -i # explicitly open the guide
authswitch codex --interactive # guide for one harness
authswitch --tui # full-screen account management
authswitch codex --tui # start the dashboard on Codex
authswitch list # all known accounts across registered harnesses, with live status
authswitch list --json # complete account/status document for scripts
authswitch codex stash # save the active credential under its account email
authswitch codex list # Codex accounts, including an unsaved active login, with live status
authswitch codex use [email] # activate one; prompts when no email is given
authswitch codex preuse <email> # send the default prompt without activating the account
authswitch codex current # print the account currently in use
authswitch codex drop <email> # forget a stash
authswitch codex doctor # report credential storage and remote-control state
authswitch opencode list # all OpenCode provider logins and saved accounts
authswitch opencode stash openai --keep
authswitch opencode use alice@example.com
authswitch opencode login openai # device login; save the new account without switching
authswitch codex login # save a new Codex account without disturbing the current login
authswitch claude stash --keep # save a Claude Code subscriber login
authswitch claude list --json
authswitch claude use alice@example.com
OpenCode and Claude Code
Both adapters support current, stash, use, drop, doctor, the guide,
the management TUI and human/JSON lists. With an AGL installation that supports
authswitch coordination, the CLI delegates credential changes to AGL. AGL owns
stopping and restarting its OpenCode runtime. Idle changes need no additional
restart confirmation; active work requires consent to wait for it to finish.
Unmanaged native processes must still be exited manually: a terminal session
cannot safely be reconstructed from its PID. Status lookups remain read-only
and work while harnesses are running. preuse remains a Codex capability.
authswitch codex login and authswitch opencode login openai use the shared
OpenAI device login flow. They display a verification link and code, then save
the new account without changing the active native login. Ctrl-C cancels login.
Select the saved account with use when ready to switch. Claude Code and other
OpenCode providers retain their native login commands.
Hosted account management
AuthSwitchService exposes credential-free account lists, login capabilities,
login prompts, saved-account identifiers and mutation outcomes for authenticated
hosts such as AGL. Every request has protocolVersion: 1. list, login and
mutate start bounded background operations; get polls the returned operation
ID and cancel cancels a pending login. Operations outlive a disconnected client,
and completed results remain available for ten minutes. Call close() during
host shutdown. The host must authenticate and authorize every request.
Login requests identify the harness, provider and supported flow explicitly.
AuthSwitchLogin is the backend login owner and accepts additional provider
adapters. Its credential-bearing completion is for backend storage only.
When an enclosing registry owns an injected adapter, construct it with
{ disposeProviders: false } as the second argument and dispose that registry
after closing the login service.
OpenCodeHarness.importCredential(providerId, credential) saves an inactive
login. activateCredential(providerId, credential) preserves and verifies the
outgoing native login before activation; the caller retains ownership of the
incoming credential's durable storage. Both are backend APIs, never wire payloads.
AuthSwitchOperations is shared by the command, guide and TUI. Its optional
coordinator receives only the harness, operation, account ID, consent to wait,
and a credential-location fingerprint. AGL owns private-controller discovery;
authswitch uses agl authswitch --request <json> without an AGL package dependency.
A failed or ambiguous transport never falls through to a second local mutation.
AGL and the CLI must use matching credential locations and compatible versions.
OpenCode credentials live in ${XDG_DATA_HOME:-~/.local/share}/opencode/auth.json.
Several providers can be active at once. Saving or switching openai preserves
every other provider, and stash openai without --keep clears only OpenAI.
When several logins are active, the guide asks which to save; scripts must name
the provider or account. In the TUI, select an active row before pressing a or c.
ChatGPT OAuth accounts display their email and use the same direct status APIs as
Codex. API keys and opaque provider tokens have no reliable email, so their labels
contain the provider and a SHA-256 fingerprint. Their quota/billing information is
explicitly unavailable. OPENCODE_AUTH_CONTENT overrides the file and blocks file
management. Provider configuration and environment credentials remain managed by OpenCode.
Claude Code supports subscriber file logins on Linux and Windows. The adapter pairs
~/.claude/.credentials.json with ~/.claude.json's oauthAccount; with
CLAUDE_CONFIG_DIR, both files are inside that directory. Switching preserves
unrelated settings and credential entries and clears native account-specific caches.
A metadata write failure rolls back our writes and keeps the outgoing account saved.
macOS Keychain switching and environment/API-key/cloud-provider logins are explicitly
unsupported. A file login shadowed by an environment override cannot be switched.
User and current-directory project settings are also checked for credential helpers
and provider environment overrides; helpers are never executed. Authswitch manages
the local file login, so launch-time flags, managed policies and credentials supplied
by embedding applications remain under Claude Code's control.
Claude's direct OAuth profile and usage lookups report the plan, weekly and five-hour
usage, model-specific windows, and available extra-usage budget information.
These endpoints require the user:profile scope and may reject expired or restricted
logins. A null reset timestamp is shown as Not scheduled. Billing renewal and
cancellation dates and earned reset credits are unavailable from these endpoints;
authswitch does not infer them from account creation or token expiry dates.
OpenCode and Claude saved records are separate owner-only files under
~/.authswitch/opencode and ~/.authswitch/claude. Emails are accepted as account
references; ambiguous matches report provider-qualified references and full IDs.
Preusing an account
preuse sends one text prompt through the selected account. Use it after a reset
to make the first token-consuming request for the next usage window:
authswitch codex preuse alice@example.com
authswitch codex preuse alice@example.com --prompt "Write 2000 words about strawberries."
authswitch codex preuse alice@example.com --model gpt-5.5 --prompt "Describe a strawberry in one sentence."
The default prompt is Write 2000 words about strawberries. Without an account,
an interactive terminal shows an account picker. Scripts must specify an account
and, when multiple harnesses are registered, the harness. Active and saved
accounts are supported, including an active login that has not been stashed.
Codex inference uses the published FlexHarness models/providers packages and the
selected ChatGPT login's access token and account ID. It selects the account's
advertised default text model unless --model is supplied, preferring a supported
low reasoning setting when the catalog provides one. API-key logins are rejected
because they do not have ChatGPT subscription reset windows. Expired credentials
must be renewed through Codex and saved again.
Each invocation consumes quota and sends at most one inference request. It does not watch for resets or schedule future requests. No active login is switched, credential refreshed or written, app-server restarted, or earned reset consumed. The prompt has no tools, files or conversation history. The generated prose is drained without being printed or stored; the command reports provider token counts and then reads the account's reset schedule. It does not infer that a timer started merely from a zero-percent usage reading or a successful response.
Ctrl-C cancels the request. Codex preuse has a three-minute deadline, with a
ten-second model-catalog limit. Failed or interrupted requests can have consumed
tokens and are never retried automatically. A completed prompt exits with 0 even
if the subsequent reset lookup fails, reporting that uncertainty separately;
inference failure exits with 1, invalid arguments or unsupported harnesses with
2, and an interrupted prompt with 130. Check authswitch list before repeating
an uncertain request. --json remains a list-only option.
Management dashboard
authswitch --tui opens a resizable account table with subscription, usage and
reset availability, usage bars, scrollable account details and an activity log.
With multiple registered adapters, a harness selector switches the view; account
selection and mutations always belong to the displayed harness. Status loads
incrementally through each account's read-only API, without activating it.
| Key | Action |
|---|---|
| Arrows, Home/End, PageUp/PageDown | Select accounts or scroll the focused view |
| Tab / Shift-Tab | Move focus between harnesses, accounts, tabs and details |
| Enter | Switch to the selected saved account; activate a harness in its selector |
/, s, S |
Filter the table, cycle its sort column, reverse sorting |
a / c |
Save the current login and keep it active / save and clear it |
d |
Remove the selected saved copy, preserving the active login |
r / g |
Refresh status / run harness diagnostics |
q / Ctrl-C |
Close and restore the terminal |
Save, switch and removal actions require confirmation. Confirmations start on Cancel; use Left/Right or Tab to select the action, then Enter. Switching first checks the current credential and offers to save an unsaved login. A declined, failed or unverified save prevents switching. Plain character shortcuts remain available as text while editing a table filter; Enter keeps the filter and Escape clears it. Closing waits for an in-flight operation to finish its cleanup.
Interactive mode flags accept an optional harness and cannot be combined with commands or another mode flag.
Getting your accounts into the stash
stash reads whatever Codex currently has active, files it under the account's email, and empties the active slot so the next login starts clean:
authswitch codex stash # -> stashed alice@example.com
codex login # log in as the second account
authswitch codex stash # -> stashed bob@example.com
Use --keep to take a checkpoint without logging yourself out.
Switching
authswitch codex use alice@example.com
Or with no argument, to pick from a list:
authswitch codex use
The account argument also accepts the directory slug, the local part, or a unique prefix, so authswitch codex use alice resolves to alice@example.com when that is unambiguous. It is deliberately not a substring match — this argument decides which credential becomes active, and an incidental substring hit should never pick an account you did not name.
What a switch actually does
use runs as one sequence, and steps 1 to 4 stop at the first failure:
- Stop the app-server. It holds the state database open, owns the remote-control websocket, and refreshes tokens on its own schedule. Reading or writing
auth.jsonwhile it runs races that refresh — the copy taken could be a token the daemon has already rotated away, and a credential the daemon wrote afterwards would be destroyed unstashed.authswitchstops it through Codex' owncodex app-server daemon stoprather than signalling the process behind Codex' back, and aborts if it will not stop. - Check in the outgoing credential. Only now is the active credential read, stashed under its account email, and read back from the stash to prove it landed. A credential is never overwritten until a copy has been verified on disk. Re-stashing an account that already has a stash refreshes it.
- Write the incoming credential to
auth.json, owner-only, through an exclusively-created temp file and a rename. - Restore the incoming account's remote-control enrollments into the state database. If the state database or its enrollment table is missing, this fails loudly rather than reporting that it restored nothing.
- Start the app-server again — but only if it was running to begin with, so the machine's posture is unchanged. This runs even when an earlier step failed: a failed switch must not leave remote control down.
stash follows the same quiesce-and-resume discipline, for the same reason.
If the switch completes but something did not finish — most likely the app-server refusing to come back — the command prints what is outstanding and exits non-zero, so it cannot look like a success to a script.
Running use for the account that is already active is not a no-op: it restores that account's enrollments, which is the repair path when remote control has stopped working for the current login.
Re-selecting the active account keeps its current tokens, including any refresh since it was last saved, and updates the verified stash before restoring pairings.
doctor prints where the credential lives, which state database is in use, every
enrollment row, whether the app-server is up, and any stash directory left incomplete
by an interrupted write.
Account status
authswitch list lists every registered harness; authswitch codex list limits the
output to Codex. Each section includes saved accounts and an identifiable active
login even if it has never been saved. * marks the active account, and the saved
marker checks the credential itself rather than just the existence of metadata.
The overview compares login state, plan provenance, weekly usage, earned resets and
the weekly reset countdown. Countdowns use days, hours and minutes, such as
7d 5h 6min;
<1min means less than a minute remains and due means the reported deadline has
passed, without claiming the service has refreshed the quota. All countdowns share
the same snapshot time.
Usage and countdown refer to the same general account window, preferring weekly.
If no weekly window is reported, the longest general window is shown with its
actual duration. Other exhausted general windows retain their own reset warning.
Feature limits such as Spark and code review have a separate table and never
determine the general summary. The TUI uses the same weekly-first selection.
Window durations come from the provider, not from the plan name or
primary/secondary position; a Pro account with only a general weekly window gets
no invented 5-hour limit.
Email addresses identify Codex accounts throughout the overview, usage/reset schedules, earned reset expiry details, grouped credits and activity metrics, saved-login notes and availability/actions tables. Other harnesses use their account labels. Provider facts already represented by structured fields are not repeated. Narrow terminals use compact account sections. Missing data stays Unavailable or Not reported; known zeroes remain zero. Stored plans are marked unverified. Renewal/cancellation dates are shown only when an adapter can provide live billing data. Codex uses the desktop app's account-check endpoint for automatic renewal, explicit renewal/cancellation dates and subscription expiry.
JSON output
authswitch list --json, authswitch codex list --json, and the ls alias write
one JSON document to stdout, with no color, tables or progress messages. --json
may appear before or after the command and requires list or ls. It cannot be
combined with a mutation or interactive mode. Unknown list arguments exit with 2.
The exported IAccountList contract contains:
schemaVersion: 2,generatedAt(ISO UTC), andcomplete.harnesses[]:id,label,loginHint,saveUnavailableReason,accounts, and harness-levelproblems(distinguishing failed discovery from an empty list).- Each account's opaque
id,label,isActive, verifiedisStashed,savedAt,details, andstatuscontaining all labelledfacts,problems, and optional typedsummaryfields. Missing fields remain omitted, not replaced by zero.
JSON retains full fact strings and absolute ISO reset/expiry timestamps. It exports
the credential-free adapter contract, never credentials, raw HTTP responses or
harness internals. A partial result still produces valid JSON and exits with 1;
successful and empty results exit with 0. complete describes lookup success,
not whether the provider exposes every possible metric.
Version 2 of this JSON schema allows usageWindows[].resetAt to be null and adds
optional account slotId fields. Consumers migrating from authswitch 1.x must
accept schema version 2 and handle null reset timestamps. Scripts should qualify
mutation commands with codex, opencode or claude, since all three now register
by default. Saved Codex credentials retain their existing format.
For each ChatGPT login, Codex status includes the live plan, primary and secondary usage windows with remaining percentages and UTC reset times, additional limits such as code review, credit availability and balance, spend limits when returned, earned reset availability and expiry details, lifetime and peak daily tokens, activity streaks, and the latest seven reported daily token totals.
The Codex adapter implements the read-only GET requests used by Codex 0.154.0 at
chatgpt.com/backend-api/wham: usage, rate-limit-reset-credits, and profiles/me.
Billing uses chatgpt.com/backend-api/accounts/check/v4-2023-04-27, verified against
the official desktop bundle 26.908.40834. It selects the exact credential account;
another workspace's default entry cannot supply its billing information.
Each request uses that account's access token and account ID. For the active
account it reads the current credential, which may be newer than the stash.
Listing never switches accounts, starts or stops an app-server, refreshes tokens,
writes credentials, stores status results, or consumes an earned reset.
These backend contracts come from Codex's source and can change. Missing fields, expired logins, denied requests, timeouts, and unsupported responses are shown explicitly; they are never reported as zero usage. Other accounts and successful sections still appear, and a failed status lookup makes the command exit nonzero. An expired saved login must be renewed through Codex and saved again.
The returned usage plan is not a billing status. Billing dates come from the separate account-check response, never from a quota reset, token expiry or stored plan. Automatic renewal is on only when the provider says it will renew and no cancellation date is scheduled. A non-renewing account without a cancellation date does not acquire an invented date. The expiry of an entitlement is displayed separately. When renewal is off, a returned renewal timestamp is labelled Renewal boundary rather than promising a future charge.
The billing request uses a verified Chromium-compatible User-Agent because the service challenged the generic authswitch User-Agent in controlled comparisons. This is a fixed HTTP request profile and requires no local browser installation. Other Codex status endpoints retain the authswitch User-Agent. If the billing endpoint still returns a Cloudflare verification challenge, that is reported explicitly as unavailable billing and a partial result, without treating it as a rejected login. Authswitch does not import browser cookies, replay desktop integrity state, refresh credentials or switch accounts to bypass the challenge. A plan from the stored login remains unverified when the live usage request fails. API-key logins and keyring credentials cannot provide these ChatGPT subscription metrics.
Harness adapters
The CLI operates on IAuthHarness, with opaque account IDs scoped to each harness.
Codex, OpenCode and Claude Code are built-in adapters. Adding an adapter does not require
changing command routing, save prompts, account pickers or status rendering:
import { AuthSwitchCli, CodexHarness } from '@modelprofile.com/authswitch';
import { AnotherHarness } from './anotherharness.js'; // your IAuthHarness implementation
const cli = new AuthSwitchCli([new CodexHarness(), new AnotherHarness()]);
process.exitCode = await cli.run(process.argv.slice(2));
Each adapter owns discovery, identity matching, save verification, credential
storage, switching and process lifecycle, live status APIs, and diagnostics.
readState() and readAccountStatus() must be read-only. switchAccount() must
preserve and verify the outgoing credential without relying on the interactive
save prompt. removeAccount() removes only the saved copy. Status facts carry
provider-specific labels and units; unsupported capabilities are explicit. Adapter
labels, details, facts and outcomes contain plain text; smartconsole owns terminal
formatting for both the guide and dashboard.
Credentials, email assumptions and Codex enrollment metadata stay out of the
shared interface. The existing CodexSwitcher API and single-switcher CLI
constructor remain available.
An account's optional slotId identifies the credential it replaces. Accounts in
different slots may be active simultaneously. saveCurrent({ keepActive, accountId })
must target the selected active account; omitting accountId is allowed only when
the adapter has one unambiguous active login. usageWindows[].resetAt may be null
when the provider explicitly reports no scheduled reset; missing windows stay omitted.
Adapters may implement preuseAccount(accountId, options) as an optional
quota-consuming capability. IHarnessPreuseOptions carries the prompt, optional
model identifier and cancellation signal; IHarnessPreuseResult returns the
selected model and optional input/output/total token counts. The adapter owns
credential selection and inference and must leave the active login unchanged.
Unsupported adapters reject the command before account selection. Prompt results
never contain credentials or raw provider responses.
Adapters can also return an optional IHarnessAccountStatus.summary with a
subscription plan and its live/stored provenance, labelled usage windows with
duration, used percentage and UTC reset time, and available reset credits with
optional expiry details. This harness-neutral data supports shared presentation
without parsing provider-specific fact strings. Missing data stays unknown;
explicit zeroes remain zero. A successful reset-detail lookup supplies the summary
count; if it fails, the limits response can still supply availability.
Each usage window may set scope: 'account' | 'feature' (default account).
Adapters mark model-specific or feature-specific quotas as feature so those
quotas remain available in JSON and details without distorting the general
account summary. Weekly windows are identified by durationSeconds: 604800;
neither window labels nor plan names are parsed to infer limits.
Optional summary.billing contains provider-reported hasActiveSubscription,
autoRenew, renewsAt, cancelsAt, and expiresAt, independent of the plan name.
Dates are ISO UTC and omitted
when unknown. They must come from a live billing source; entitlement expiry alone
does not establish renewal or cancellation. Codex omits this field if its billing
lookup fails and supplies a corresponding problem.
Facts may supply a section label for grouped comparison and a summaryKey
(subscription, billing, usageWindows, resets, or resetDetails) when the
fact duplicates a structured field. The list hides such a fact only if that field
is displayed; JSON retains every fact. Adapters without this metadata continue to
display their facts in Additional information.
With multiple adapters, the guide first asks which harness to manage and provides an action to choose another. Unqualified mutation commands prompt for a harness; scripts must qualify them. Account references never resolve across harnesses.
Where things are stored
| What | Where |
|---|---|
| Stashed credentials | ~/.authswitch/codex/<email>/auth.json (mode 0600, in a 0700 directory) |
| Stash metadata and enrollments | ~/.authswitch/codex/<email>/stash.json |
| Codex' active credential | $CODEX_HOME/auth.json, default ~/.codex/auth.json |
| Codex' remote-control enrollments | the remote_control_enrollments table in $CODEX_HOME/state_<n>.sqlite |
CODEX_HOME and AUTHSWITCH_HOME both override their defaults, which is also how the test suite runs against a throwaway directory. AUTHSWITCH_CODEX_BIN points at a specific codex executable when it is not on PATH, and NO_COLOR turns off colouring (as does piping the output anywhere that is not a terminal).
Account identity comes from the id_token inside the credential — the email claim names the stash, the account id ties it to its enrollments. The token is decoded, never verified, and never printed: authswitch only ever reports emails, plan names and server names.
Limits
Keyring-backed credentials cannot be switched. Codex can keep credentials in an OS keyring instead of auth.json, in which case there is no file to move. authswitch detects this and refuses rather than half-working; doctor reports it as keyring (not switchable). Switch Codex to file storage via auth_credentials_store_mode in config.toml and log in again to use this tool.
Other Codex clients keep running. Stopping the managed app-server does not stop an editor extension that spawned its own. Close it, or expect it to keep using the credential it already loaded.
The Codex stash is keyed by email. Two Codex workspaces with the same email cannot both be saved under that key. Their account IDs are distinguished during listing and identity checks, and a collision refuses to overwrite either login. Account IDs in the shared harness interface do not have this email restriction.
A stash is a credential. ~/.authswitch holds live refresh tokens. It is written owner-only, but it is not encrypted, and it belongs in the same threat model as ~/.codex/auth.json itself.
Development
pnpm install
pnpm build
pnpm test
The test suite builds a fixture CODEX_HOME with a real SQLite state database and exercises the full stash/login/restore cycle, including the case where a fresh login has removed an account's enrollment row. It never touches the developer's own Codex installation.
License and Legal Information
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the repository license file.
Please note: The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
Trademarks
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.
Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.
Company Information
Task Venture Capital GmbH
Registered at District Court Bremen HRB 35230 HB, Germany
For any legal inquiries or further information, please contact us via email at hello@task.vc.
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.