Switch between multiple Claude Code accounts without logging out. A terminal picker that gives each account its own isolated config directory.
Windows, no Go needed. Downloads the prebuilt .exe from the latest
release and adds it to your PATH:
irm https://raw.githubusercontent.com/NotAProgrammer187/claude-code-profiles/main/install.ps1 | iexThen open a new terminal and run ccswitch. (Prefer not to pipe a script?
Grab ccswitch.exe from the
Releases page
and put it anywhere on your PATH.)
Using Scoop? The manifest lives in this repo, so no bucket
is needed — and scoop update ccswitch handles updates from then on:
scoop install https://raw.githubusercontent.com/NotAProgrammer187/claude-code-profiles/main/scoop/ccswitch.jsonmacOS / Linux. Downloads the matching prebuilt binary and installs it to
~/.local/bin/ccswitch:
curl -fsSL https://raw.githubusercontent.com/NotAProgrammer187/claude-code-profiles/main/install.sh | sh(Or grab ccswitch-darwin-arm64, ccswitch-darwin-amd64, or
ccswitch-linux-amd64 from the
Releases page
yourself, chmod +x it and move it onto your PATH.) On macOS, credential
isolation is partial (see below).
Using Homebrew? This repo doubles as a tap — the formula picks the right binary for macOS (Intel or Apple Silicon) and Linux:
brew tap notaprogrammer187/ccswitch https://github.com/NotAProgrammer187/claude-code-profiles
brew install notaprogrammer187/ccswitch/ccswitchBoth installers — and ccswitch upgrade — verify what they download against
the SHA256SUMS manifest published with each release before installing it.
The Scoop manifest and Homebrew formula pin the same hashes, and each release
updates them automatically. A ccswitch installed by a package manager belongs
to that manager: ccswitch upgrade says so and points you at scoop update /
brew upgrade instead of fighting it.
Neither ccswitch nor Claude Code will surprise you with an update, but you
also shouldn't have to go looking: at most once a day, a background check
notes the latest release of each, and the moment you're back at a prompt —
Claude Code exiting, the picker closing — a one-liner tells you what's newer
and the command that updates it. No launch ever waits on the check, and
setting CCSWITCH_NO_UPDATE_CHECK=1 turns it off entirely.
Build from source — needs Go 1.22+, produces one static .exe:
git clone https://github.com/NotAProgrammer187/claude-code-profiles
cd claude-code-profiles
.\build.ps1 -Installccswitch |
open the picker |
ccswitch run work |
launch straight into a profile |
ccswitch run |
launch the profile this directory is linked to (see below) |
ccswitch run work -- --resume |
anything after -- is passed to claude |
ccswitch run --best |
launch the signed-in account with the most rate-limit headroom |
ccswitch use work |
pin the current shell to a profile (see below) |
ccswitch init pwsh |
shell integration: use applies itself, plus tab completion |
ccswitch link work |
use this profile for the current directory and below |
ccswitch unlink |
drop this directory's link |
ccswitch links |
list linked directories |
ccswitch usage |
show each account's rate-limit usage |
ccswitch usage --watch |
live usage panel to park beside Claude Code (see below) |
ccswitch sync --from work |
copy shared config into your other profiles (see below) |
ccswitch defaults |
show the settings every profile shares (see below) |
ccswitch defaults set remoteControlAtStartup false |
share one setting with every profile, now and on first login |
ccswitch new work |
create an empty profile from the CLI |
ccswitch import work |
copy the current ~/.claude into a new profile |
ccswitch rename old new |
rename a profile |
ccswitch rm work |
delete a profile (asks first; -y skips) |
ccswitch list |
print profiles |
ccswitch current |
print the profile this shell is set to |
ccswitch where work |
print a profile's config directory |
ccswitch doctor |
check the whole setup and say what to fix (see below) |
ccswitch upgrade |
update ccswitch to the latest release |
First run: press i to import the account you're already logged into (this
copies your existing ~/.claude and ~/.claude.json, so you keep settings, MCP
servers and history). Press n to add each further account — a new profile
starts empty and runs Claude Code's normal login flow the first time you launch
it. Everything the picker keys do is also a plain command (new, import,
rename, rm), so setup scripts and dotfiles can manage profiles without the
UI.
In the picker: press / to filter profiles by name or email, 1–9 to
jump straight to one, and ↑↓/⏎ to move and launch. The row your current
shell points at (if any) is tagged ◂ this shell.
Which account has headroom? Signed-in rows also show how much of each
account's 5-hour and weekly rate limit is used (5h 42% · wk 12%), turning
amber at 70% and red — with the reset time — at 90%. ccswitch usage prints
the same numbers in the terminal. This reads the endpoint Claude Code's own
/usage screen uses, with each profile's existing token; it's display-only
(nothing is written or refreshed), and if the endpoint ever changes shape the
rows simply omit it.
Don't want to think about it at all? ccswitch run --best checks every
signed-in account and launches the one with the most headroom — an account's
score is its most-used window, since that's the one that will stop you first,
and it prints what it picked and why (work has the most headroom (5h 12% · 7d 30%)). Accounts whose usage can't be read are skipped rather than guessed
at.
ccswitch usage answers the question once. ccswitch usage --watch keeps
answering it: open a second terminal next to Claude Code, run it there, and
leave it. Every signed-in account gets a meter for its 5-hour and weekly
windows, with the reset time under each.
Bars turn amber past 70% and red past 90% — the same thresholds the picker
uses, so the two views never disagree about what counts as getting close. And
because the panel is most useful when you're not looking at it, a window
crossing into the red also rings the terminal bell and posts a desktop
notification — a toast on Windows, Notification Center on macOS, notify-send
on Linux. One crossing is one alert: opening the panel on an account
already in the red shows red quietly, and a window has to drop back below 90%
before it will alert again. The
account your shell is pinned to is tagged ◂ shell; a profile this directory
is linked to is tagged ◂ here. Opus gets its own meter only once you've
actually used it, so plans without a separate Opus window don't show a
permanent 0%.
It refreshes every minute; --every 30s changes that, with a 15-second floor
because the endpoint is undocumented and shared with Claude Code itself. r
refreshes now, q quits. Narrow the window or shorten it and the panel drops
to one line per window automatically, so it stays readable in a thin column.
Profiles added while it's running are picked up on the next refresh — no restart needed. Like the rest of the usage code it's display-only: each profile's existing token is sent, nothing is written back, and an account whose fetch fails shows a dim line instead of taking the panel down.
The character at the top idles on its own clock — it sits still most of the
time, glances around occasionally and stretches now and then, slowly enough to
sit in the corner of your eye without asking for attention. To put your own art
there instead, fill in watchArt in cmd/ccswitch/watch.go — one string per
line, centred in the accent colour, with any line too wide for the panel
dropped rather than wrapped.
Claude Code's status line can name the account the session is running as —
useful the moment you have more than one. ccswitch statusline prints one
line shaped for the statusLine setting: profile, model, and both rate-limit
windows, amber at 70% and red (with the reset time) at 90%, the same
thresholds as everywhere else:
work · Opus 4.5 · 5h 42% · wk 12%
Wire it into every profile at once — this is exactly what defaults is for:
ccswitch defaults set statusLine '{"type":"command","command":"ccswitch statusline"}'(Or set that value in one profile's settings.json by hand.) The status line
runs the command constantly, so it never touches the network itself: the
numbers come from a small cache that a detached helper refreshes at most every
two minutes, and until the first refresh lands the line simply shows the
profile without numbers. Sessions launched without ccswitch work too — the
line reads the default ~/.claude and labels itself claude.
ccswitch run launches one session; ccswitch use points the shell itself at
a profile, so every plain claude you type afterwards runs as that account. A
child process can't change its parent's environment, so — like nvm or direnv —
use prints the command and you eval it:
# PowerShell
Invoke-Expression (ccswitch use work)# bash / zsh (incl. Git Bash)
eval "$(ccswitch use work)"The picker tags that shell's row ◂ this shell, ccswitch current names it,
and Invoke-Expression (ccswitch use --unset) (or eval "$(ccswitch use --unset)") unpins the shell again. Auto-detection covers PowerShell and POSIX
shells; pass --shell pwsh|cmd|bash|fish to override.
ccswitch init prints a shell function that does the eval for you, so plain
ccswitch use work pins the shell — and it adds tab completion for profile
names. Install it once:
# PowerShell
ccswitch init pwsh | Add-Content $PROFILEccswitch init bash >> ~/.bashrc
ccswitch init zsh >> ~/.zshrc
ccswitch init fish > ~/.config/fish/conf.d/ccswitch.fishOpen a new shell and ccswitch use work applies immediately;
ccswitch run <tab> completes profile names, and sync --only <tab> completes
its parts. Everything except use is passed straight through to the same
binary, so nothing else changes. (In PowerShell, words starting with - are
claimed by PowerShell's own parameter completion, so flags aren't completed
there — commands, profiles and flag values are. cmd.exe has no functions to
hang this on, so it keeps pasting what ccswitch use prints.)
Isolation is the point for credentials — it's also why a second account starts
with none of your setup. ccswitch sync copies the parts that aren't
account-specific from one profile into the others:
ccswitch sync --from work # into every other profile
ccswitch sync --from work --to personal,side # or just these
ccswitch sync --from work --only settings,mcp # or just these parts
ccswitch sync --from work -n # preview, write nothingIt always prints what it would write and asks before doing it (-y skips the
prompt). The parts it knows about are settings (settings.json), claude-md
(CLAUDE.md), commands, agents, skills, output-styles, hooks, and
mcp — your MCP servers, merged into each target's .claude.json so that
file's own account identity, project state and history stay intact.
Credentials, history and per-machine caches aren't on that list and can't be synced, so this never moves a login. Directories are merged, not mirrored: a file only the target has is left alone. It's a copy, not a symlink — run it again after you change something you want shared.
sync is a one-off copy, which is the wrong shape for preferences you never
want to think about again. Turn Remote Control off in one profile and the next
account you sign in to has it on, because an absent setting means "default" —
and a fresh profile has no settings at all. Share the key instead:
ccswitch defaults set remoteControlAtStartup false
ccswitch defaults # what's shared, and which profiles differ
ccswitch defaults apply # write it into every profile now
ccswitch defaults unset theme # stop sharing (profiles keep what they have)Shared keys live in ~/.ccswitch/settings.json and are written into a profile's
settings.json every time ccswitch launches or pins it — including the first
launch of a brand-new profile, before Claude Code's login flow runs. So the
setting is already in place for the session where you'd otherwise notice it was
missing. Setting one applies it everywhere immediately; after that, launches
that change nothing say nothing.
Any top-level settings.json key works (remoteControlAtStartup,
autoUploadSessions, theme, model, verbose, …). Values are read as JSON
when they parse as JSON and as strings otherwise, so false is a boolean and
dark is "dark"; quote a block to share it whole:
ccswitch defaults set env '{"FOO":"bar"}'.
Two things worth knowing. A shared key stops being a per-profile choice — change
it in one profile's /config and the next launch puts the shared value back,
which is what sharing means; unset it if you want that key to vary again. And
only the keys you name are touched: everything else in each profile's
settings.json — its model, its permissions, its MCP allowances — is left
alone. If a profile's settings.json can't be parsed, ccswitch says so and
writes nothing rather than replacing it.
ccswitch doctor answers "why isn't this working?" in one pass — one line per
check, each either fine or telling you what to do about it:
ccswitch 0.1.7 on windows/amd64
ok binary — ~\bin\ccswitch.exe
ok claude — ~\AppData\Roaming\npm\claude.cmd
warn environment — ANTHROPIC_API_KEY is set — ccswitch strips it at launch, but a plain `claude` will bill the key instead of using your login
ok storage — ~\.ccswitch
ok state — ~\.ccswitch\state.json
ok work — you@work.com, signed in
FAIL personal — settings.json does not parse — fix it by hand; ccswitch won't touch a file it can't read
1 problem, 1 warning.
It covers the claude binary being on PATH, environment variables that would
override your login or profiles, a second ccswitch install shadowing the one
you're running, ~/.ccswitch sitting inside a cloud-synced folder (profiles
hold live OAuth tokens), our own state and shared-settings files, every
profile's settings.json / .claude.json / credentials parsing, two profiles
signed in to the same account (one set of rate limits — --best would count it
twice), and directory links pointing at profiles or directories that no longer
exist.
It's strictly read-only: nothing is repaired, moved or rewritten, so you can run it while deciding what to do. Exit code 1 when it finds a real problem, so scripts can gate on it.
Claude Code keeps everything — credentials, settings.json, MCP servers,
CLAUDE.md, history — in one directory, chosen by the CLAUDE_CONFIG_DIR
environment variable. So a profile here is just a directory, and launching one
sets CLAUDE_CONFIG_DIR for that single child process before running claude.
Nothing is copied or overwritten on switch, so logins can't clobber each other, accounts can run in parallel across terminals, and switching is instant.
Because a profile is just a directory, it works with any account Claude Code can sign into — Pro, Max, Team, Enterprise, or an API key — with no change on ccswitch's side; the plan is only read to label the row. And there's no limit on the number of profiles: add as many as you like. Each one is a real, separately-authenticated account, so this isolates logins — it does not create extra capacity or get around any account's own usage limits or billing.
- Restart to switch. A running session reads its config at startup; launching another profile starts a new process and won't change an open one.
ANTHROPIC_API_KEYoverrides a subscription login. ccswitch strips it (andANTHROPIC_AUTH_TOKEN) from the child process and warns you when it sees them.- macOS is different. Credentials live in the Keychain there, so isolation is less complete. This is built for Windows first.
- Profiles hold live OAuth tokens. Don't sync
.ccswitchto cloud storage or commit it anywhere. - You are responsible for complying with Anthropic's Terms of Service and Usage Policy. ccswitch doesn't bypass authentication or billing — each profile logs in normally through Claude Code's own flow. It only isolates config directories.
This is an independent, community project. It is not affiliated with, endorsed by, or sponsored by Anthropic. "Claude" and "Claude Code" are trademarks of Anthropic, PBC, used here only nominatively to describe what this tool interoperates with.
ccswitch runs entirely on your machine, stores nothing on any server, ships no Anthropic code, and does not circumvent Claude Code's authentication or billing. Use of Claude Code through ccswitch remains subject to Anthropic's own Terms of Service and Usage Policy, and complying with them is your responsibility.
MIT — see LICENSE. Provided "as is", without warranty of any kind.

