skill
LLMSwapper
Local dashboard to switch the active Claude Code account with one click, showing each account's 5-hour and weekly quota. Zero dependencies, host and WSL, Docker-ready.
Set up
Skills are installed by placing their folder where your agent loads skills.
The download comes from github.com/monac-cc/LLMSwapper itself, not from our server, so what you get is exactly what its author published.
Install instructions come from the README
README
synced Oct 21, 2026LLMSwapper
Switch the active AI-subscription account with one click - and see how much quota each one has left before you do.
Today it swaps Claude Code and OpenAI Codex CLI accounts. The name is a plan, not a claim. What is coming.
Install
Node 18+ and Claude Code, Codex CLI or both. No npm install, no build step.
git clone https://github.com/monac-cc/LLMSwapper.git
cd LLMSwapper
node server.js # opens http://127.0.0.1:7373
| Windows | Also detects your WSL distros and swaps inside them, from the same screen. |
| macOS | Credentials usually live in the login Keychain - item Claude Code-credentials, account claude-code-user - and the app reads and writes that item through security; a plain ~/.claude/.credentials.json is used when that item does not exist. With CLAUDE_CONFIG_DIR set it addresses that directory's own item, as Claude Code does. |
| Linux | Nothing special. This is the host the Docker image is really for. |
docker compose up -d # http://127.0.0.1:7373
git pull && docker compose up -d # update: compose rebuilds the image on every up
By hand, on a port of your choice:
docker build -t llmswapper .
docker run -d --name llmswapper -p 127.0.0.1:7373:7373 \
-v "$PWD/data:/app/data" \
-v "$HOME:/home/node" \
llmswapper
- Publish on
127.0.0.1only. Inside the container the server must listen on0.0.0.0, so the loopback guarantee has to be imposed here. - Mount the
data/you already use. A named volume gives the container a separate, empty account store. On a Linux host create it first (mkdir -p data) if the panel has never run there: Docker creates a missing bind-mount source as root, and the server, which runs as uid 1000, cannot write to it. - Never run the container and
node server.jstogether. The rate floor is per process; two instances rate-limit each other. - Windows: set
$env:CLAUDE_HOME = $env:USERPROFILEfirst. macOS: swapping needs credentials in a file, not the Keychain. Linux: add--user "$(id -u):$(id -g)"if the mounts belong to another uid. - Update with
git pull && docker compose up -d. The compose file rebuilds on everyup; withdocker runyou have todocker buildagain yourself. The container banner shows the build stamp, so a stale container is visible at a glance. - Mount your home directory, not
~/.claudeand~/.claude.jsonseparately. Claude Code rewrites~/.claude.jsonby renaming a new file over it, and a single-file bind mount on a Linux host keeps the old inode: the container reads a frozen copy and its writes never reach the host. With the home directory mounted, the rename happens inside the mount and every write stays atomic. The container sees your whole home; it already held your Claude credentials, which is the valuable part. - Docker Desktop (Windows, macOS) resolves the share by path, so there the narrower pair
-v "$HOME/.claude:/home/node/.claude" -v "$HOME/.claude.json:/home/node/.claude.json"also works. On a file mount the server writes in place (a mount point cannot be replaced by rename), and if the mount ever detaches it refuses to swap and says so in a banner, rather than pretending. - A container cannot see host processes or reach WSL. The panel says so on screen rather than reporting "not running" for what it cannot see.
Another port: PORT=7400 node server.js. It never hops to the next free one - the rate floor is
per process, so a second instance would rate-limit both.
powershell -ExecutionPolicy Bypass -File scripts\install-autostart.ps1
Registers a per-user scheduled task (no admin) that launches the panel hidden at logon, restarts
it 15 seconds after any crash and writes the time and exit code of each one to the log (a clean
exit, such as the port being busy, is left alone), and never kills it on a time limit. To stop it,
end the task and the server it leaves running (Task Scheduler does not stop that one):
Stop-ScheduledTask LLMSwapper; Stop-Process -Id (Get-NetTCPConnection -LocalPort 7373 -State Listen).OwningProcess. It also starts it right away - unless something already listens on
7373, in which case stop that first and run Start-ScheduledTask LLMSwapper. Output goes to data\server.log. Remove with -Uninstall. Don't combine it with running
node server.js by hand - same rule as Docker, one instance only.
Status: Claude Code and Codex CLI. A Claude | Codex switch at the top of the page picks which one you are looking at; each keeps its own accounts, environments and tab. The problem is not specific to either - any coding tool backed by a personal subscription holds one account at a time - and Other providers is a list of criteria, not of dates.
What it does
Claude Code holds one account at a time; using another means /login and a browser round trip,
every time. LLMSwapper stores each account once and makes the change a click - on your machine and
inside your WSL distros - and shows what each has left of its 5-hour and weekly windows, so it
answers the real question: which account should I switch to right now.
The interface is bilingual (ES / EN) with light and dark themes, both remembered. Server-side error text is still Spanish only - a known gap, since translating it needs the API to return codes rather than sentences.
Adding accounts
Two ways in. The choice decides one thing: whether the account can identify itself.
Paste a long-lived token - recommended. Run claude setup-token, approve in the browser, paste
what it prints. Lasts a year, no renewal. The form has an open a terminal for me link that runs
the command for you.
- Grants one scope,
user:inference, so it has no email, plan or organisation - it shows the name you give it. - Quota meters still work, from a different source. See below.
Import a live session - claude then /login, then press import. Full scope, so it
identifies itself and reads quota for free.
- Costs one login per account, and each login lasts about 30 days, fixed when you log in: renewing rotates the tokens but never extends it. The row warns three days ahead; then log in again (Shift + click import keeps your current session) or turn it into a one-year token.
- Shift + click imports from a
CLAUDE_CONFIG_DIR=/tmp/other claudesession without disturbing yours.
Adding the same account twice updates it in place.
Usage meters
The panel asks every minute and on demand; how fresh each reading really is depends on its source. Where the numbers come from, and what they cost:
| Account | Source | Cost per reading |
|---|---|---|
| Imported | /api/oauth/usage | 0 tokens |
setup-token | anthropic-ratelimit-unified-* headers | 8 in + 1 out |
- An inference-only token is answered
403by the usage endpoint forever, so the panel never asks. - The free endpoints carry no rate-limit headers, so the probe spends the floor: Haiku,
max_tokens: 1, a one-character prompt. About 2,600 tokens a day per account, against a window measured in hundreds of thousands. - When it cannot refresh, it shows the last reading marked stale. Switching is unaffected.
Switching
Press swap:
- Warns if Claude Code is open - the change lands on new sessions.
- Backs up credentials and
~/.claude.jsontodata/backups/(last 20). - Refreshes the token if it is about to expire.
- Rewrites only
claudeAiOauthandoauthAccount. Projects, history andmcpOAuthare untouched. - Verifies against the API and rolls both files back if the token is rejected (401/403). A rate limit or a network failure keeps the swap and says it could not be confirmed.
Environments
The tabs are the places a swap can write: the host, named after its OS, and every WSL distro.
Nothing to configure - it runs wsl.exe -l -q --running and takes what comes back.
- Lists only distros that are running - a scan must not boot the stopped ones - and skips system distros (
docker-desktop) and any distro where Claude Code has never run, checked by resolving$HOMEand looking for~/.claude.jsonover the share. - Detection is cached 30 s. The ↻ beside the tabs re-scans past it, and says when nothing changed.
- A dot means Claude Code is running there. Each environment tracks its own active account.
- WSL is a Windows feature; elsewhere there is one tab.
Codex
Press Codex in the switch at the top. The same page - rows, meters, tabs, swap - now works on OpenAI Codex CLI accounts (ChatGPT sign-in), kept in a store of their own: nothing on the Claude side ever sees an OpenAI token, or the reverse. The switch and each view's tab are remembered.
Adding an account. Three ways in, none of which touches the session you are using:
- add account - opens a terminal running
codex loginwithCODEX_HOMEpointed at a panel-owned directory (data/codex/login/). Finish the login in the browser, then press import in the step that appears under the header. The panel stores it and deletes that directory'sauth.json. If Windows has reserved the port the browser login answers on (1455 -os error 10013), the terminal runscodex login --device-authinstead: open the link it prints and type the code. - import - stores the account Codex is signed in with in the selected environment right now.
- Shift + click import - reads a login from another directory, one you signed in to with
CODEX_HOME=/tmp/other codex login. That directory and the panel then share one refresh token, and the panel says so: use the account through the panel from then on, not from there.
There is no Codex setup-token, so there is nothing to paste. API-key auth.json files are refused:
only ChatGPT accounts have a quota to show. Adding the same account twice updates it in place, and
the name you gave it stays.
Switching is the same audited swap as Claude's: backup to data/codex/backups/ (last 20),
refresh if the token is about to expire, rewrite only tokens, auth_mode and last_refresh in
auth.json (every other key survives), verify against the usage endpoint, and restore the file if OpenAI
rejects the token (401/403) - or delete it again if there was none before. A Cloudflare bot
challenge (an HTML 403) only warns: it says nothing about the token.
WSL. A distro gets a Codex tab once it is running and has a ~/.codex directory, i.e. once
Codex is installed and has run there. Swapping writes that distro's auth.json, so an account you
signed in to on Windows works inside WSL with no second login.
Meters read chatgpt.com/backend-api/wham/usage, the endpoint Codex's own /status reads: the
5-hour and weekly windows, 0 tokens per reading, at most a minute old. When OpenAI reports only the
weekly window, the 5-hour meter says there is no 5-hour limit rather than showing a blank. A 429 backs
off for 10 minutes and keeps the last reading, marked stale.
An account whose sign-in OpenAI has revoked shows "sign in again": add account, log in with it,
import.
Codex's own limits, and the panel's:
- Never log out of Codex to switch accounts. Codex's logout revokes that account's tokens at OpenAI, so the copy the panel holds dies with it - the row then says "revoked" and the account has to be added again. Add accounts with add account and switch with swap; neither logs anything out.
- A swap is a
codex login, and open Paseo sessions follow it. It writes the session toauth.json, so every Codex you start afterwards runs on the new account. If Paseo is installed, its open Codex agents are reloaded withpaseo agent reload, which resumes the same conversation on the new account: idle ones at once (the one that ran out of quota included). One still working is never stopped: it goes on with its account and switches the moment it stops by itself, so your next message runs on the new one. - Terminal
codexsessions switch at once, mid-answer included. They run against Codex's shared background server (/statussays "Local background server"), which never re-readsauth.json. The panel logs that server in with the new account itself, throughcodex app-server proxy, the way Codex's host apps do (account/login/startwith the access token, kept in the server's memory): the request in flight finishes on the old account and the next one goes out on the new. Nothing is restarted. Codex does not renew a login handed over like that, so the panel feeds the server every newer token that lands inauth.json, and renews it itself a day before it expires. A server that does not answer is tried again in 30 seconds. A WSL distro linked to the Windowsauth.json(below) gets the same for its own background server, while the distro is running. - What runs outside that server keeps the old account. The desktop app,
codex execor a Codex in an unlinked WSL distro readauth.jsonwhen they started; the swap reaches the next one you start. The panel says so after the swap. - Want one account in Windows and WSL at once? Link the distro's file to the Windows one, once,
inside the distro:
ln -sf /mnt/c/Users/<you>/.codex/auth.json ~/.codex/auth.json. Then there is a single file, Codex coordinates its refreshes through it (verified: it writes through the link), the WSL tab disappears from the Codex view and every swap on Windows applies to both. - One environment per account at a time. Codex refresh tokens rotate, and the old one dies on
use: two
auth.jsonfiles holding the same one kill each other on the first refresh. Swapping an account into a second environment is refused until the first one is switched to another account. - Keyring storage is refused. With
cli_auth_credentials_store = "keyring"(orauto) inconfig.toml, the session is not inauth.json, so a swap would do nothing. The panel refuses and says how to go back to"file". - No automatic rotation and no
/swapper*skills for Codex yet. Those drive Claude only. CODEX_ACCESS_TOKENoutranksauth.json. While it is set, a swap is a silent no-op; the panel shows a banner.CODEX_API_KEYonly reachescodex execand is not treated as an override.
Slash commands (Claude Code skills)
Three skills drive the panel from inside any Claude Code session, so you can check quota or
switch account without leaving the terminal. They talk to the running panel over
127.0.0.1:7373; nothing works unless node server.js is up (PORT if you moved it).
| Command | What you type | What it does |
|---|---|---|
/swapper-usage | /swapper-usage | One line per account with its 5-hour and weekly quota; marks the one in use (and when its session resets) and the freest one to switch to. Read-only. |
/swapper <name> | /swapper Manuel · /swapper devs@… | Switches the host's active account to the one named (label or email), then shows what it has left. |
/swapper-auto | /swapper-auto on · off · status | Turns automatic rotation on or off, and shows the whole rotation queue: the account in use, which is next, and any excluded one with the reason. |
/swapper-usage prints one aligned line per account — ● in use, → the freest to switch to:
Swapper · host 5h 7d
● Cyberxia 31% 53% en uso · reset 4h 12m
→ Castillo · carloscastilloazr 0% 48% más libre
Manuel Cordero 0% 0%
Castillo · ccastillo 81% 71% casi
CTO 100% 68% tope
Two accounts sharing a label are told apart by the email's local part. /swapper-auto uses the
same shape: ● the account in use, → siguiente the one it would rotate to, and excluida · why
on any it will skip.
/swapper Manuel matches by name or email — if it is ambiguous (two accounts labelled the same)
it lists them and asks for the email rather than guessing. The change lands on new Claude Code
sessions; one already open keeps its token.
Automatic rotation (/swapper-auto on). The server then watches the active account and, the
moment its 5-hour session reaches 90%, swaps to the freshest account that still has room — so
your next claude launch lands on capacity you have not spent. It only rotates into an account
below the threshold in both windows; if none qualifies it stays put. State is on disk, so it
survives a restart, and it is the same audited swap (backup, verify, roll back) as a manual one.
Turn it off with /swapper-auto off. An account whose token is rejected is left out of the queue, and a rotation that fails waits out the same cooldown as one that succeeds, so a dead credential is never retried every three minutes.
Install
Copy the three skill folders into your user skills folder, once:
# macOS / Linux / Git Bash
cp -r skills/swapper skills/swapper-usage skills/swapper-auto ~/.claude/skills/
# Windows PowerShell
Copy-Item skills\swapper,skills\swapper-usage,skills\swapper-auto $env:USERPROFILE\.claude\skills\ -Recurse
Each skill is self-contained — a SKILL.md and a small swapper.mjs — and needs only Node and
the running panel. They work from any Claude Code session, not just one opened in this repo.
Paseo plugin
paseo-plugin/ shows the LLMswapper of every paired Paseo device in one compact panel, one row per account with its 5-hour and 7-day bars, and switches the account on any of them. Install it with paseo plugin add git:monac-cc/LLMSwapper:paseo-plugin.
It also makes a switch reach the current session. A running Claude Code keeps its token in memory, so after a switch the plugin has Paseo reload that machine's Claude agents, which resume the same conversation on the new account: idle ones at once, busy ones when their turn ends. Switches made here, by /swapper or by automatic rotation count too.
Other providers
Claude Code and Codex CLI ship today. The rest are candidates, in rough order of fit.
| Provider | Tool | Status |
|---|---|---|
| Anthropic | Claude Code | Shipping - host and WSL, token or import |
| OpenAI | Codex CLI | Shipping - host and WSL, ChatGPT sign-in, see Codex |
| GitHub | Copilot | Considering - ~/.config/github-copilot/ |
| Gemini CLI | Considering - ~/.gemini/ | |
| Cursor / Windsurf | editor sign-in | Investigating - held by the editor, not a file this tool can rewrite |
A provider fits when its credentials live in a file this app can rewrite, the account is identifiable enough to label, and swapping does not require killing a running session. One that keeps its session in an OS keychain, or pinned to a process, needs a different mechanism. If a tool you use fits, an issue naming it and where it stores credentials is the most useful thing to send.
Security
- Loopback only by default, plus a required header on every API call. DNS rebinding is rejected by validating the
Hosthostname. - Reaching it from another device is opt-in and unauthenticated.
SWAPPER_BIND=0.0.0.0widens the socket andSWAPPER_ALLOWED_HOSTS=192.168.1.10,my-pcwidens theHostallowlist - both, or it stays closed. Only the hosts you name are let in; every other one, rebinding included, is still refused. There is no login, so anyone who can route to that address can swap, rename and delete your accounts. A tunnel or a private mesh is the better answer; this is for a network you trust. - Tokens live in
data/(real NTFS ACL on Windows) and never leave the process. Anything matchingsk-ant-*, an OpenAI JWT or an OpenAI refresh token is scrubbed from logs and responses; a test fails the build if a token literal appears in any source file. - No OAuth flow in the panel. You mint tokens in your own terminal and paste them, or sign in with the CLI's own login and import the result.
- A pasted token is a year-long secret. The field is a password input, emptied when the form closes.
- Every swap is preceded by a backup and rolls back on failure.
Limitations
ANTHROPIC_API_KEY,ANTHROPIC_AUTH_TOKENandCLAUDE_CODE_OAUTH_TOKENoutrank the credentials file. While one is set, swapping is a silent no-op. The panel detects this and says so.- Token accounts have no identity - no email, plan or organisation. Nothing to read, so nothing is invented. Name one with its real email and
/statusreads as before. - Remote Control does not work with
setup-tokenaccounts. Anthropic documents it. TheRemote Control disconnected - /loginline is not a broken session; silence it with"disableRemoteControl": true. - The usage endpoint allows ~5 requests per 5 minutes for the whole app, hence the hard floor and the persisted, escalating cooldown.
Troubleshooting
| Symptom | Cause |
|---|---|
| Swapping "works" but Claude Code keeps the old account | An overriding environment variable. |
Remote Control disconnected - /login | Expected with setup-token accounts. |
Not logged in - Please run /login | The credentials blob has no scopes. |
| Meters empty and marked stale | Rate-limited. Recovers on its own. |
Ya hay algo escuchando en… at start-up | Another instance. The port never hops. |
Development
node test.js # 103 checks, ~13 s, no external network
Every fetch is stubbed, so a run never spends the usage endpoint's budget. Each module has its
own self-check (node lib/swap.js). Internals are in ARCHITECTURE.md.