One command to flip your Claude Code, Codex, Gemini CLI, or Antigravity from
your work account to your personal one, and back. No re-login, no browser, no copying
tokens around -- and the switch itself never touches the network. (One opt-in
command, swapdex quota, reads your remaining balance from Anthropic; nothing
else does.)
Then swapdex add work, swapdex add personal, and swapdex use personal.
Full install notes · what it will not do.
Why
If you run Claude Code, Codex, Gemini CLI, or Antigravity under more than one account -- a work seat and a personal subscription, a client's org and your own -- switching means logging out and back in every time.
swapdex gives each account its own permanent space -- its own
CLAUDE_CONFIG_DIR slot -- and flips between them without ever copying a token.
swapdex use work points your default account there and a plain claude
follows it; swapdex run work launches straight into that account (each terminal
can be a different one). Because nothing is copied, a switch can never log an
account out -- even if a session is still running when you switch.
swapdex onboard sets this up in a few prompts.
It is a switcher, not a rotator. It manages accounts you already own for distinct purposes, with no feature for cycling them to get around a rate limit -- see What it will not do.
Safety is the design center: in the slot model swapdex never writes a credential at all -- each account's own login creates and refreshes its token, in its own slot -- and it only ever hands the official CLI its own credentials: no wrapper, no proxy, no client spoofing.
Concepts
- Account -- one login you own (a work seat, a personal subscription). Its
redacted identity (email, tier) is shown by
slots,status, anddoctor; never a token. - Slot -- an account's own permanent
CLAUDE_CONFIG_DIR, where its login lives and refreshes in place. swapdex creates one per account (or adopts a~/.claude-*dir you already use) and never copies tokens between them. - Default account -- the one a plain
claudeuses, via a tiny shim on your PATH.swapdex use <name>repoints it;swapdex run <name>ignores it and launches a specific account directly.
swapdex still keeps the classic snapshot commands (add copies a live login
into a profile, use on that profile swaps it back, guarded against the
running-session logout) for the shared-slot workflow; swapdex migrate moves
Claude and Codex profiles whose accounts are not already slotted onto their own
slots.
Install
# npm - you already have it, since Claude Code and Codex ship this way
# Homebrew (macOS / Linux)
# crates.io (needs a Rust toolchain)
# or the one-liner (prebuilt binary -> ~/.local/bin)
|
Pick one and stay with it. Each installer wants the same name on PATH, and
with two of them the shims keep calling whichever copy wrote them - so updating
the other one changes nothing, silently. swapdex doctor reports this, along
with whether the version you are running is the one that is published.
Linux, WSL, and macOS (Claude's macOS login lives in the Keychain; swapdex
swaps it there, via /usr/bin/security). Requires at least one supported CLI
(Claude Code, Codex, Gemini, Antigravity) already installed and logged in. Full command, exit-code, and environment
reference: docs/COMMANDS.md.
Use
# First run: guided setup -- registers ~/.claude-* dirs you already use,
# moves old profiles onto slots, offers the shim. A bare `swapdex` runs this
# automatically the first time there is something to set up.
# Launch an account in its own slot (first time = sign in; concurrent-safe,
# so each terminal can be a different account)
# Make a plain `claude` follow a default account
# See your accounts and who's active
# Register a config dir you already run by hand; move old profiles to slots
# Sessions grouped by the account active when they ran (needs sessionwiki)
# Recent local token usage per tool (5h/7d) -- tells you when to switch
# Remaining quota per Claude account -- the one opt-in network read
# Set up a second machine with the same accounts (never carries a login)
# Anything off? Every finding comes with its fix
The classic snapshot commands still work for the shared-slot workflow: swapdex add <name> snapshots the current login, swapdex use <name> swaps it back
(backed up first, and refused while a claude session is running on that login
so it can't be logged out), swapdex restore undoes the last swap, and swapdex ui is the full-screen picker. swapdex migrate matches Claude and Codex
profiles to slots by account and creates spaces only for accounts without one.
status shows the active account per tool, matched back to a saved profile:
claude-code: you@work.com [max] (profile 'work')
codex: you@personal.com [chatgpt] (profile 'personal')
The active account is read from the pointer a switch sets, and falls back to the live login on a machine that has no slots. Where the tool's own config dir holds a different account -- you signed in directly without the shim, or an old copy-model switch left one behind -- that gets its own line rather than being shown as the active account:
codex: you@work.com (profile 'work')
(a plain `codex` would launch on 'personal' instead - `swapdex shim`
makes it follow your switches)
Both are true and they answer different questions, so swapdex prints both instead of picking one. A machine sat in exactly that state for six days.
For your shell prompt or statusline, status --short prints one compact line:
e.g. in a starship prompt: command = "swapdex status --short" in a
custom module, or in PS1
via $(swapdex status --short).
It also drops straight into Claude Code's own status line, so the active
account is always visible inside the tool you are switching
(~/.claude/settings.json):
usage reads your local session logs (no network) to gauge how heavily you've
been using each tool lately, so you know when to switch to a fresher account:
Local usage - this machine, approximate (not the billed quota):
claude-code 5h: 8.2M tok / 12 sess 7d: 61.4M tok / 88 sess
@work 5h: 6.0M tok 7d: 40.1M tok
@personal 5h: 2.2M tok 7d: 19.3M tok
Once a switch history exists, tokens are attributed to the profile active at
each event's timestamp (the same honest join sessions uses); anything before
your first switch stays untagged. Still deliberately a hint, not a
quota-dodging auto-rotator.
Where usage is your local activity, quota is the vendor's actual remaining
balance -- the one command that reaches the network, and only when you run it:
$ swapdex quota
quota - remaining on your Claude accounts
live from Anthropic's usage endpoint; opt-in network, spends 0 message quota.
work (active) you@work.com
5h ▓▓▓▓▓▓▓░░░ 68% left resets in 2h 14m
7d ▓▓▓▓▓▓░░░░ 57% left resets in 3d 4h
personal you@personal.com
snapshot token expired - `swapdex use personal` to refresh, then `swapdex quota`
It reads each account's remaining quota from Anthropic's official OAuth usage
endpoint using that account's own token -- read-only, and it spends zero
message quota. The active account is always live; a saved account whose token
has expired reports so rather than showing a stale number (swapdex never
refreshes tokens -- that is the line between a switcher and a rotator). It is
also in swapdex ui under the % key.
The dashboard
swapdex ui is the same thing without the commands: your accounts, which one is
active, and how much each has left. On a machine with no profiles yet it opens on
what you are already signed into and offers to save that as your first one, so
setup is one keystroke and a name.
Keeping accounts from expiring
An account nobody opens dies on its own. These refresh tokens go stale when they are not exercised -- measured across two machines, an idle Codex slot stops working about ten days after its last run -- and once the refresh token is gone only a browser sign-in brings the account back.
swapdex renews idle accounts for you, but only while its proxy is running, because that is the process holding the timer:
That installs a launchd/systemd unit per tool. The proxy then sweeps every thirty minutes and renews anything approaching its deadline, including slots nobody has opened. A slot the tool is running in is never touched: its own session holds the refresh token, and renewing from outside would retire the one that session is about to use.
Without the service, nothing is on a timer. You can sweep by hand:
swapdex doctor reports whether the service is installed and running.
How it works
Slots (the model swapdex uses now). Each account gets its own
CLAUDE_CONFIG_DIR -- a directory under ~/.local/share/swapdex/slots/, or a
~/.claude-* dir you adopt. Claude keys its login to that dir (a file on Linux,
a Keychain item on macOS), so each account's token lives and refreshes in its
own slot. swapdex never copies a token between slots: swapdex run <name>
execs claude with that slot's CLAUDE_CONFIG_DIR, and swapdex use <name>
writes a one-line pointer that a small claude shim on your PATH reads. Shared
config (settings.json, global CLAUDE.md) is symlinked into each new slot;
the token and history stay per-slot. Because no credential is ever moved, a
token refresh in one account can never revoke another -- a switch cannot log
you out.
Classic snapshots (still supported). Each CLI also keeps its login in a small on-disk file:
- Claude Code:
~/.claude/.credentials.jsonplus theoauthAccountblock inside~/.claude.json - Codex:
~/.codex/auth.json - Gemini CLI:
~/.gemini/oauth_creds.jsonplus~/.gemini/google_accounts.json - Antigravity:
~/.gemini/antigravity-cli/antigravity-oauth-token
add copies the current login into a private store at ~/.local/share/swapdex;
use on a snapshot profile writes it back atomically, backing up the current
login first, and only the oauthAccount block of ~/.claude.json is swapped so
your projects, MCP servers, and settings are untouched. That switch is refused
while a claude session is running on the same login slot, since the session's
next token refresh would otherwise revoke the saved copy. On macOS the Claude
token lives in the login Keychain, one item per CLAUDE_CONFIG_DIR. swapdex migrate [--tool claude|codex] moves unslotted Claude and Codex profiles onto
their own slots, retiring the shared homes.
Safety
- Every credential file swapdex writes is
0600; the store directory is0700. - Writes are atomic (temp file created
0600, then renamed) so an interrupted switch can never leave a half-written credential that bricks the CLI. - Symlinked credential paths and running as root are refused.
usewrites a backup of the current login (fsynced, or the switch aborts; exception: an unreadable/corrupt live file is skipped with a warning -useis exactly the command that can replace a corrupt login) before overwriting anything, andswapdex restorebrings it back in one command if the switch was a mistake. The store keeps the last 2 backups per tool, andusewarns when the outgoing login is not saved as a profile -- so save accounts you care about withadd.- No token, refresh token, or home path is ever printed.
The store holds plaintext refresh tokens. Protect ~/.local/share/swapdex
like ~/.ssh, and do not sync it across machines (it is single-machine,
single-user by design).
Do not copy a credential out of the store for something else to use. These
refresh tokens are single-use: the server retires the outgoing one whenever a
holder renews, so two programs holding one account's credential silently
retire each other's. The copy that missed a renewal keeps working until its
access token lapses, which is why the failure arrives hours or days after the
change that caused it -- one such split cost a scheduled job 42 hours. A
program that needs its own Codex or Claude access should sign in for itself;
swapdex is for accounts a person switches between, not a credential source
for other software.
Codex renewal is checked before access expires: a running Codex proxy checks
every 30 minutes and attempts renewal for idle slots within 48 hours of expiry.
swapdex refresh --keep-alive runs the same check without a proxy. If a local
session holds a due account, renewal is deferred to avoid retiring the token
that session holds. The account list and picker then show that renewal is
deferred and its refresh validity is unverified; this does not mean the access
token has expired or the account needs a new login. A confirmed refresh
rejection is reported separately as requiring re-login.
An external copy can renew without changing any local file. Neither the local
access token's issue time nor last_refresh reveals that remote event. These
checks therefore cannot certify refresh validity after unseen remote activity;
external consumers need their own login instead of a copy of a managed slot.
What it will not do
These are structural properties, not promises -- the code is built so they cannot happen:
- No HTTP client, no background network. The binary has no HTTP client in
its dependency graph (CI asserts this on every commit), so it cannot phone
home or exfiltrate a token. Switching,
ls,status,usage-- all 100% local. The one exception is the opt-inswapdex quotacommand, which shells out tocurlto read your own remaining balance from Anthropic's official usage endpoint (that account's own token, read-only, spends zero message quota). It runs only when you type it, sends no data anywhere, and touches no other endpoint. - No auto-rotation. There is no
--auto,--next, or--when-rate-limitedflag.useonly ever switches to a name you type. - No token export. There is no command that prints a saved credential.
- No wrapper, no client spoofing. swapdex swaps the credential file that the
official
claude/codexbinary already reads, then gets out of the way. It never sits between the CLI and the API, never proxies requests, and never presents itself as the official client. Your traffic is the real CLI's traffic. (Launching the official tool once, on your explicit pick -login's sign-in flow,ui's session resume - is a hand-off, not a wrapper: swapdexexecs and is gone.)
Anthropic and OpenAI both permit multiple accounts for genuinely different purposes but forbid using multiple accounts to get around a single workload's rate limit, and forbid routing subscription OAuth tokens through third-party tools or spoofing the official client. swapdex is built for the former and structurally cannot do the latter -- it only ever hands the real CLI its own credentials. See Anthropic Usage Policy and OpenAI Usage Policies.
MCP (read-only)
swapdex mcp runs a read-only MCP server exposing whoami and list_accounts
so an agent can see which account is active. There is deliberately no switch
tool -- an agent can never change your account.
Works with
swapdex is the accounts layer of a small local AI-CLI stack:
- sessionwiki -- index, search, and
resume your AI coding sessions.
swapdex sessionsgroups them by account, and after a switch inswapdex uiyou get that account's recent sessions with asessionwiki resume <id>hint -- switch, land back in your work. - prodex -- share one logged-in ChatGPT Pro session across agents. swapdex coexists with it without touching its auth.
Alternatives
Good tools exist in this space; they make different trade-offs (each line from that project's README, July 2026):
- claude-swap -- Claude Code only, a TUI with live usage bars, and optional auto-switching near your limit. If you want auto-rotation, use it -- swapdex deliberately refuses to have that feature.
- aisw -- cross-tool including Gemini, OS-keyring storage, Windows support. More features, bigger surface.
- caam -- cross-tool with a shell wrapper and automatic rotation on rate limits; the philosophical opposite of swapdex.
Pick swapdex if you want the smallest thing that switches your AI CLIs
together, can always undo (restore), diagnoses itself (doctor), shows
your remaining balance (quota), and structurally cannot rotate, proxy, or
spoof the official client.
Roadmap
Claude Code on macOS (Keychain).Shipped (0.17-0.24): swapdex swaps Claude's login inside the macOS Keychain via/usr/bin/security, resolves the item exactly the wayclaudeitself does (one item perCLAUDE_CONFIG_DIRprofile), anddoctordiagnoses any mismatch.Permanent per-account slots.Shipped (0.26): each account gets its ownCLAUDE_CONFIG_DIR, so a switch copies no token and can never log an account out -- even with a session running.run,use(repoint) + theclaudeshim,onboard,adopt,migrate, andsync-mcp(shares your MCP servers across slots, since they live in the per-account.claude.json).
Being considered, explicitly opt-in and advisory-only:
- Per-directory hints (cross-tool). Bind a directory to a profile and have
swapdex resolve <dir>suggest the right account ("this directory is bound towork-- runswapdex use work"). It would cover both Claude (CLAUDE_CONFIG_DIR) and Codex (CODEX_HOME) in one binding. It will never be a shell wrapper, never auto-switch, and never let anything but an explicitswapdex usechange the active account -- that bright line is what keeps swapdex a switcher, not a rotator.
License
MIT