Switch between multiple AI CLI accounts and profiles in a single shell. Today: Codex and Claude Code. Built so more providers slot in without rewriting the CLI surface.
- Quick start
- Why
- Requirements
- Key features
- Install
- Shell setup
- How it works
- Usage
- Configuration
- Roadmap
- License
# Install via Homebrew (macOS)
brew install baedonguri/tap/aiwitch
# Enable in-shell switching (add to ~/.zshrc; bash/fish similar — see Shell setup)
eval "$(aiwitch shell init zsh)"
# Add profiles (registers and runs the provider's login flow)
aiwitch add codex personal # ChatGPT login by default
aiwitch add codex work
aiwitch add claude personal # spawns `claude`; run `/login` inside the REPL
# Switch the current shell to a profile
aiwitch use personal
# Then run the provider's CLI with that profile's account
codex # or `claude`
aiwitch add <provider> <profile>triggers the provider's login flow for the new profile. Codex supports--auth apifor API-key login (see Login); Claude is interactive-only — log in via/logininside the spawnedclaudeTUI.
Running multiple AI CLI accounts — personal, work, an API-key-only sandbox — means juggling CODEX_HOME / CLAUDE_CONFIG_DIR and shuffling auth files by hand. aiwitch keeps each account in its own home directory and switches the active one in your current shell with a single command.
Inspired by nvm/pyenv-style version switchers, applied to AI CLI accounts.
- macOS or Linux with Rust 1.85+ (Windows is not supported in v0.1.0)
- Homebrew on macOS, if you want the
brew installpath - Codex CLI and/or Claude Code installed and on
PATH(only the providers you intend to use) - For Codex API-key login: an OpenAI API key on stdin (
$OPENAI_API_KEYor piped)
- Per-profile provider home — every profile has an isolated directory (
CODEX_HOMEfor Codex,CLAUDE_CONFIG_DIRfor Claude); switching is a single env-var swap. See How it works. - In-shell switching —
aiwitch use <profile>andaiwitch add <provider> <profile>mutate the current shell via a small snippet for zsh, bash, and fish. - Two Codex auth modes — ChatGPT login and API-key login; the latter reads the key from stdin and pipes it straight to
codexwithoutaiwitchpersisting it. - Claude interactive login —
aiwitch add claude <profile>provisions an isolatedCLAUDE_CONFIG_DIRand spawns theclaudeTUI; users log in via/logininside the REPL. - TOML config — a single
~/.config/aiwitch/profiles.tomlwith strict name validation and duplicate detection. - Backend trait — adding a new provider is a
Backendimpl, not a CLI rewrite.
brew install baedonguri/tap/aiwitchAfter tapping once:
brew tap baedonguri/tap
brew install aiwitchRequires Rust 1.85+.
cargo install --git https://github.com/baedonguri/aiwitch --tag v0.3.0aiwitch switches profiles by exporting environment variables in the current shell, so it needs a small shell function.
# zsh — add to ~/.zshrc
eval "$(aiwitch shell init zsh)"
# bash — add to ~/.bashrc
eval "$(aiwitch shell init bash)"
# fish — add to ~/.config/fish/config.fish
aiwitch shell init fish | sourceThis wires up two extra subcommands:
| Command | What it does |
|---|---|
aiwitch use <profile> |
Switch the current shell to an existing profile. |
aiwitch add <provider> <profile> |
Add a profile, run its login flow, and activate it in the current shell. |
Without shell init you can still drive everything manually via eval "$(aiwitch env <profile>)".
Each provider picks up its account from a directory pointed to by an environment variable — CODEX_HOME for Codex, CLAUDE_CONFIG_DIR for Claude Code. aiwitch keeps one such directory per profile and exposes them as named entries in ~/.config/aiwitch/profiles.toml.
~/.codex-work/ ← CODEX_HOME for codex/"work"
└── auth.json
~/.codex-personal/ ← CODEX_HOME for codex/"personal"
└── auth.json
~/.claude-personal/ ← CLAUDE_CONFIG_DIR for claude/"personal"
└── (managed by Claude Code; on Linux a `.credentials.json` lives here)
aiwitch use <profile> evaluates to export <PROVIDER_VAR>=... and export AIWITCH_CURRENT=... (or set -gx on fish). The next codex / claude invocation picks up the right account; aiwitch current reads AIWITCH_CURRENT. No background daemon, no symlink swapping, no global state to corrupt.
macOS note for Claude profiles: Claude Code stores OAuth credentials in the system Keychain on macOS, not inside
CLAUDE_CONFIG_DIR.aiwitch listwill showemail=-,plan=-,expires=-for Claude profiles even after a successful login — that's expected, not a bug. On Linux,CLAUDE_CONFIG_DIR/.credentials.jsonis parsed best-effort.
aiwitch add <provider> <profile> registers the profile and immediately runs the provider's login flow. The provider is required; supported values are codex and claude.
aiwitch add codex personal # Codex ChatGPT login
echo "$OPENAI_API_KEY" | aiwitch add codex work --auth api # Codex API-key login (stdin)
aiwitch add claude personal # Claude — spawns `claude` TUI; run `/login`| Flag | Meaning |
|---|---|
--home <PATH> |
Directory used as the provider home. Optional; defaults to ~/.codex-<profile> (codex) or ~/.claude-<profile> (claude). |
--auth <chatgpt|api> |
Codex only. Login mode; defaults to chatgpt. Rejected with an explicit error for claude (Claude is interactive-only). |
If login fails or is canceled, the profile is still registered. Retry with
aiwitch login <profile>(add--api-keyfor API-key mode).
aiwitch listShows each profile's name, provider, email, plan, and token expiry. The active profile (per provider) is marked.
Requires shell setup — aiwitch use is a shell function, not a binary subcommand.
aiwitch use work
aiwitch current # → workaiwitch remove personal # forget the profile, keep the home dir
aiwitch remove personal --purge # also delete the default home diraiwitch remove deletes the entry from ~/.config/aiwitch/profiles.toml. Without --purge, the per-profile home directory (e.g. ~/.codex-personal) is preserved so credentials stay recoverable. With --purge, aiwitch deletes the directory only when it matches the default ~/.codex-<name> / ~/.claude-<name> pattern; custom paths and symlinks are rejected with a rm -rf <path> hint so the tool never deletes a directory it didn't create.
If AIWITCH_CURRENT in your shell still points at the removed profile, you'll see a stderr warning. Start a new shell or run aiwitch use <other> to switch.
Note:
aiwitch removerewritesprofiles.tomlvia serde, so any custom comments, blank lines, or key ordering you added by hand are normalized away.
aiwitch rename work officeUpdates the entry name in profiles.toml. When the profile uses the default ~/.codex-<name> / ~/.claude-<name> home directory, the directory on disk is also moved to ~/.codex-<new> / ~/.claude-<new>. Custom home_dir paths are left untouched — rename them yourself if you want.
If the new home_dir already exists, the rename aborts before any change. Cross-filesystem renames (EXDEV) are rejected with an explicit hint — move the directory onto the same filesystem and retry. If AIWITCH_CURRENT still points at the old name, you'll see a stderr warning; re-run aiwitch use <new> to refresh the current shell.
Note: Like
remove,renamere-serializesprofiles.toml, so handwritten comments and formatting in that file are lost. Ifhome_diris a symlink,aiwitchmoves the symlink itself — the underlying directory stays put — and prints a stderr warning so you notice.
aiwitch run <profile> -- <cmd> spawns <cmd> with the profile's provider env vars set, without mutating the current shell. The -- separator is recommended so flags are passed to the child instead of consumed by aiwitch.
# Current shell stays on `personal`; just this codex invocation runs as `work`.
aiwitch run work -- codex exec "fix this bug"
# Useful in CI / scripts where `eval "$(aiwitch env ...)"` is awkward.
aiwitch run ci-bot -- codex --versionThe child inherits the parent environment and aiwitch overlays only CODEX_HOME / CLAUDE_CONFIG_DIR and AIWITCH_CURRENT. Other variables are not stripped — if OPENAI_API_KEY or ANTHROPIC_API_KEY are exported in your shell, the provider CLI may use them instead of the profile's stored credentials. To run with a cleaner environment, use standard tools:
env -u OPENAI_API_KEY aiwitch run work -- codex # drop one variable
env -i HOME="$HOME" PATH="$PATH" aiwitch run work -- codex # nuke everything elseThe exit code of <cmd> is propagated; on Unix, signal-terminated children are reported as 128 + signal.
aiwitch doctorReports per-profile status (home dir, login, token expiry) and global checks (provider CLI on PATH, AIWITCH_CURRENT). Exits non-zero on any [err]; warnings don't affect the exit code.
# Codex — ChatGPT login (delegates to codex)
aiwitch login work
# Codex — API key login: aiwitch reads the key from stdin and pipes it to codex.
# aiwitch itself never persists the key.
echo "$OPENAI_API_KEY" | aiwitch login work --api-key
# Claude — interactive only: spawns `claude`; log in via `/login` inside the REPL.
aiwitch login claude-personal
aiwitch login <claude-profile> --api-keyis rejected with an explicit error before stdin is read; Claude does not yet have an API-key login flow.
aiwitch env work # POSIX: export K='v'
aiwitch env work --shell fish # fish: set -gx K 'v'Profiles are stored in ~/.config/aiwitch/profiles.toml:
[[profiles]]
name = "work"
backend = "codex"
home_dir = "~/.codex-work"
[[profiles]]
name = "personal"
backend = "codex"
home_dir = "~/.codex-personal"
[[profiles]]
name = "claude-personal"
backend = "claude"
home_dir = "~/.claude-personal"The file is validated on every load: names must be [A-Za-z0-9_-]+ (no leading dash), and duplicate names are rejected.
- More providers (Gemini, …) behind the existing
Backendtrait. - Prebuilt Homebrew bottles to skip the Rust build step.
- Optional encrypted secret storage (today
aiwitchdoes not store secrets — they live in the provider's own home dir).