CLI-only Codex account switcher for WSL/Ubuntu. cxs stores multiple Codex accounts under per-account homes, then syncs the selected account into the default Codex home before launching plain codex.
This is not a Desktop switcher. cxs run and cxs sync sync the selected account auth into ~/.codex so plain codex, Codex App remote projects, and user-level Codex provider config all agree on the active account. cxs run --isolated and a running cxs switch session still use per-account CODEX_HOME isolation.
~/.cxs/
config.json
accounts/
personal/
config.toml
auth.json
history.jsonl
sessions/ -> ~/.cxs/shared/sessions
logs/
shared/
sessions/
cache/
usage.json
By default, cxs run <account> first syncs that account to ~/.codex, then launches plain codex without CODEX_HOME. This means your global ~/.codex/config.toml settings, including custom model providers, remain active.
Isolated mode is still available:
cxs run --isolated <account>- No access token, refresh token, id token, or account id is written to
~/.cxs/config.jsonor~/.cxs/cache/usage.json. - Usage cache stores only usage snapshots and never auth secrets or identifiers.
- The canonical per-account
auth.jsonstays inside each account home;~/.codex/auth.jsonis only the last-run account copy used by plaincodex. - Resume sessions are shared through
~/.cxs/shared/sessions; per-accountsessionspaths are symlinks so/resumecan see sessions acrosscxsaccounts without sharing account credentials. - After a launched Codex session exits, or when
cxs syncis run explicitly, that account'sauth.jsonis copied to~/.codex/auth.jsonwith mode0600,~/.codex/config.tomlis set tocli_auth_credentials_store = "file", and any stale default app-server control socket is removed so plaincodexdoes not reuse a remote app-server session from another account. auth.jsonis chmodded to0600when possible.cxs doctorreports permission/setup problems.
npm install
npm run build
npm pack
npm install -g ./codex-switcher-0.1.0.tgzFor a repeatable local global install, use:
bash scripts/install-global.shAvoid npm link for a machine-level cxs install. npm link leaves
/opt/homebrew/lib/node_modules/codex-switcher pointing at the development
checkout, so moving or breaking the workspace also breaks /opt/homebrew/bin/cxs.
Installing from npm pack copies the built package into the global npm prefix
and keeps the CLI independent from the repo working tree.
Requirements:
- Node.js 20+
- WSL2/Ubuntu or Linux shell
codexavailable onPATH, or setCXS_CODEX_BIN=/absolute/path/to/codexscriptfrom util-linux (used by the/statususage fallback)
cxs login personalCreates ~/.cxs/accounts/personal, writes config.toml with:
cli_auth_credentials_store = "file"Then runs:
CODEX_HOME=~/.cxs/accounts/personal codex logincxs listShows account, masked email, default marker, and last-used time.
cxs use workSets the default account only. Does not run Codex.
cxs run
cxs run personal
cxs run work -- exec "review this diff"
cxs run -- exec "use default account"
cxs run --isolated workBy default, syncs the selected account to ~/.codex, sets it as the cxs default account, then runs the real plain codex process. CODEX_HOME is removed from the launched process environment so plain Codex uses ~/.codex.
This keeps account switching compatible with user-level Codex config such as custom providers in ~/.codex/config.toml.
Before launching Codex, cxs merges registered account session directories into ~/.cxs/shared/sessions and replaces each account's sessions directory with a symlink to that shared directory. This lets codex resume and /resume show previous sessions from other cxs accounts. Authentication remains per-account in ~/.cxs/accounts/<account>/auth.json; the selected account is copied to ~/.codex/auth.json for active use.
If your shell has CODEX_HOME set, plain codex launched outside cxs run will use that directory instead of ~/.codex. Run unset CODEX_HOME in that shell to let plain codex use the synced account.
cxs removes CODEX_HOME only for the launched codex child process. It does not modify your shell environment.
Before cxs run launches plain Codex, it removes the default app-server control socket. Without that, plain codex can attach to a persistent remote app-server that still has a previous account cached.
cxs run --isolated <account> keeps the previous behavior: it runs codex with CODEX_HOME=~/.cxs/accounts/<account>, then best-effort syncs that account back to ~/.codex after Codex exits. Use isolated mode when you intentionally want account-local Codex config instead of the global ~/.codex/config.toml.
If you intentionally keep a default Codex remote-control/app-server session alive, running cxs run or a running cxs switch may disconnect future plain codex launches from that server so the synced local account can take effect.
cxs sync
cxs sync work
cxs sync work --dry-runSyncs the selected account auth to ~/.codex without launching Codex. If no account is provided, cxs sync uses the current default account. A successful cxs sync work also sets work as the default account so cxs list and plain codex reflect the same intended account.
This is useful on a Mac mini or remote SSH host before using plain codex, a Codex App SSH remote project, or another remote app-server flow where the default Codex home needs to match a specific account.
sync is not a Desktop switcher. It does not modify Chrome profiles, Codex Desktop internal auth, or web state. It only manages ~/.codex/auth.json and ~/.codex/config.toml for plain Codex CLI compatibility.
Like post-run syncing, cxs sync removes the default app-server control socket. Existing persistent remote app-server sessions may be detached so future plain codex launches can use the synced local account.
--dry-run prints the selected account, source and destination paths, app-server control socket target, and planned default-account update without copying auth, editing config, removing sockets, or printing token values.
cxs switch
cxs switch --no-run
cxs switch --scan
cxs switch --sort quota
cxs switch --sort recent
cxs switch --sort nameUses @clack/prompts to select an account. By default it sets the selected account as default and runs codex. --no-run changes the default only.
When switch launches Codex, the selected account is also synced to ~/.codex after that Codex session exits. --no-run only changes the cxs default account and does not touch ~/.codex.
The switcher shows usage from the cache first. If the cache is stale or missing, it tries a quick backend refresh (5s timeout). Usage failures are never fatal — the UI continues to work and shows ? for unavailable quotas.
cxs usage # provider chain: backend API → /status scrape → local log → cache
cxs usage --refresh # allow slower backend + /status refresh (12s timeout)
cxs usage --scan # rescan local usage logs only (no network)
cxs usage --json # print UsageSnapshot JSONcxs usage queries usage in this priority order:
- Backend API — uses
auth.jsonaccess_token+account_idto call the ChatGPT backend usage endpoint. Fastest and most accurate for 5h/weekly windows. - Status scrape — if the backend fails, launches an isolated Codex session via
script -qfec, sends/status, and parses5h limit/Weekly limitfrom the output. - Local log — scans
~/.cxs/accounts/<account>/sessions/**/*.jsonlandlogs/**/*.jsonlforrate_limits/rateLimitsobjects withprimaryandsecondaryusage windows. - Cache — falls back to the last successful snapshot stored in
~/.cxs/cache/usage.json. - Unknown — if everything fails, shows
?and preserves the error reason without blocking other operations.
Security notes:
access_tokenandaccount_idare never written to cache or config.- The cache stores only
UsageSnapshotfields: account, email, plan, usageSource, fiveHour, weekly, fetchedAt. - The
assertNoSecretsguard rejects any write that contains token-shaped strings.
cxs repair-sessionsRepairs shared sessions/, history.jsonl, and session_index.jsonl links across ~/.codex and all configured cxs account homes. This is safe to run after Codex CLI/Desktop rewrites session metadata files and before checking resume/session lists.
cxs reset-credits
cxs reset-credits work
cxs reset-credits --current
cxs reset-credits --timezone Asia/SeoulShows a safe summary of Codex reset credit availability and credit expiries. By default it uses the configured default cxs account; pass an account name to check that account home, or --current to read the active plain Codex auth at ~/.codex/auth.json.
Security notes:
- Calls only
https://chatgpt.com/backend-api/wham/rate-limit-reset-credits. - Uses the local access token and account id only as request headers.
- Prints only available count, total earned count, and expiry times.
- Never prints tokens, account ids, emails, profile URLs, credit ids, cookies, or raw endpoint responses.
cxs doctorChecks:
- Node.js >= 20
- codex binary exists
scriptbinary exists (required for/statusfallback)~/.cxsroot- config validity
- default account validity
- account home/config/auth files
- auth permissions
- sessions/logs readability
- usage cache path
cxs export --redacted
cxs export --redacted --output /tmp/cxs-diagnostics.jsonExports a JSON diagnostics bundle containing runtime metadata, account setup metadata, auth file existence/mode, sessions/logs directory status, and usage-cache metadata.
The bundle intentionally redacts raw emails and never reads or emits auth.json token values. Output files are written with mode 0600 when possible.
# bash
cxs completion bash > ~/.local/share/cxs/completion.bash
source ~/.local/share/cxs/completion.bash
# zsh
cxs completion zsh > ~/.local/share/cxs/_cxs
fpath=(~/.local/share/cxs $fpath)
autoload -Uz compinit && compinitnpm install
npm test
npm run build
node dist/main.js --helpTMP=$(mktemp -d)
mkdir -p "$TMP/bin"
cat > "$TMP/bin/codex" <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
if [ "${1:-}" = "login" ]; then
mkdir -p "$CODEX_HOME"
printf '{"user":{"email":"person@example.com"}}
' > "$CODEX_HOME/auth.json"
exit 0
fi
printf 'FAKE_CODEX_HOME=%s ARGS=%s
' "$CODEX_HOME" "$*"
EOF
chmod +x "$TMP/bin/codex"
HOME="$TMP/home" PATH="$TMP/bin:$PATH" node dist/main.js login personal
HOME="$TMP/home" PATH="$TMP/bin:$PATH" node dist/main.js run personal -- exec 'hello world'
HOME="$TMP/home" PATH="$TMP/bin:$PATH" node dist/main.js run -- exec default
HOME="$TMP/home" PATH="$TMP/bin:$PATH" node dist/main.js sync personal
test -f "$TMP/home/.codex/auth.json"
HOME="$TMP/home" PATH="$TMP/bin:$PATH" node dist/main.js sync personal --dry-run
HOME="$TMP/home" PATH="$TMP/bin:$PATH" node dist/main.js usage --scan --json
HOME="$TMP/home" PATH="$TMP/bin:$PATH" node dist/main.js doctor- No PowerShell dependency.
- No Codex Desktop integration.
- Codex Desktop internal auth state, Chrome profiles, and web state are not modified directly.
- For plain Codex CLI compatibility, the selected account's
auth.jsoncan be explicitly synced to~/.codex/auth.json. - No automatic
--bestaccount selection in MVP.
- Optional
CXS_CODEX_BINdocumentation expansion and config-level override. - Optional encrypted vault after MVP.
assertNoSecretscould additionally blockaccountId/account_idfrom cache/config writes.