Claude Code Profile Launcher — run multiple Claude Code instances against different third-party APIs, side by side, without interference.
cc-exec is a tiny launcher for the claude CLI that:
Profile config → environment variable injection → launch `claude`
Each profile is a JSON file describing a set of environment variables (base URL, API key, model, …). Running cc-exec <profile> injects those variables and starts the claude CLI with them. Because each launch is a separate process with its own environment, multiple profiles — Manager, Coding, Review — can run at the same time and never collide.
You want cc-exec if any of these is true:
- You run several Claude Code sessions in parallel, each pointed at a different provider (Anthropic official API, OpenRouter, a self-hosted Claude-compatible endpoint, …).
- You switch models often (Opus for planning, Sonnet for coding) and want a one-word command instead of retyping env vars.
- You share a machine and want per-project API keys kept in separate profile files rather than a single global shell config.
- You want repeatable, scriptable Claude launches in CI without
exportsoup or shell-specific tricks.
You probably don't need cc-exec if you only ever use one provider and one model — claude alone is enough.
Requires Node.js >= 20.
npm install -g cc-execOr run once without installing:
npx cc-exec listAfter global install, the cc-exec command is on your PATH:
cc-exec list
cc-execshells out to theclaudeCLI, so the official Claude Code CLI must also be installed and on yourPATH. Override the binary withCC_EXEC_CLAUDE_BINif you need a specific path.
# 1. Create a profile (interactive prompts for provider env)
cc-exec create coding
# 2. Update it interactively (each field pre-filled; keys stay masked)
cc-exec update coding
# 3. Launch Claude with that profile
cc-exec codingThat's it. cc-exec coding is equivalent to:
ANTHROPIC_BASE_URL=... ANTHROPIC_API_KEY=... ANTHROPIC_MODEL=... claudeProfiles live in:
| OS | Location |
|---|---|
| macOS / Linux | $XDG_CONFIG_HOME/cc-exec/profiles/ (default ~/.config/cc-exec/profiles/) |
| Windows | %APPDATA%\cc-exec\profiles\ |
Each profile is one JSON file, e.g. ~/.config/cc-exec/profiles/coding.json:
{
"description": "Coding worker profile",
"providerEnv": {
"ANTHROPIC_BASE_URL": "https://api.example.com",
"ANTHROPIC_API_KEY": "sk-replace-me",
"ANTHROPIC_MODEL": "claude-sonnet-4"
},
"claudeSettings": {
"env": { "BASH_DEFAULT_TIMEOUT_MS": "120000" },
"permissions": {
"allow": ["Bash(git:*)", "Edit(src/**)"],
"deny": ["Bash(rm -rf:*)"]
}
}
}providerEnvaccepts any environment variables — not onlyANTHROPIC_*.claudeSettings(optional) carries non-provider Claude settings - anenvfor non-provider vars, pluspermissions,hooks, etc. Passed via--settingsand merged with~/.claude/settings.json; omit it (or leave{}) for a provider-only profile. See How profile env is applied.- Unknown top-level keys are ignored, so the format is forward-compatible.
- The filename stem is the profile name (
coding.json→ profilecoding); there is nonamefield in the file.
See docs/configuration.md for the full reference.
| Command | Description |
|---|---|
cc-exec <profile> [args...] |
Launch claude with the profile's env. Extra args pass through. |
cc-exec <profile> -- [args...] |
Everything after -- is forwarded to claude verbatim. |
cc-exec list |
List all profiles. |
cc-exec show <profile> |
Show a profile (API keys are masked). |
cc-exec create <profile> |
Create a profile interactively (prompts for provider env). |
cc-exec update <profile> |
Update a profile interactively (fields pre-filled; secrets never shown, blank keeps the value). |
cc-exec remove <profile> |
Delete a profile. |
cc-exec help |
Show help. |
cc-exec version |
Show version. |
cc-exec list prints one row per profile with the provider-routing values that tend to differ across profiles, so a glance is enough to tell endpoints and models apart:
NAME DESCRIPTION MODEL FAST MODEL SUBAGENT BASE URL KEY
coding Coding worker claude-sonnet-4, claude-haiku claude-haiku claude-haiku https://api.example.com sk-****1234
manager Manager claude-opus-4 claude-haiku claude-haiku https://api.other.com sk-****abcd
- MODEL folds
ANTHROPIC_MODELand the fourANTHROPIC_DEFAULT_{FABLE,OPUS,SONNET,HAIKU}_MODELtier aliases into one comma-separated cell, with duplicates collapsed - three tiers pinned to the same id show once rather than three times. - FAST MODEL / SUBAGENT show
ANTHROPIC_SMALL_FAST_MODEL/CLAUDE_CODE_SUBAGENT_MODELas-is. - BASE URL shows
ANTHROPIC_BASE_URL. - KEY shows the masked auth key (
ANTHROPIC_API_KEY, elseANTHROPIC_AUTH_TOKEN, else any other sensitive var), solistis safe to share just likeshow.
Any argument cc-exec does not recognize is forwarded unchanged to the claude CLI — no parsing, no rewriting, no loss. This includes flags Claude may add in the future.
cc-exec coding -- run --resume abc123
# runs: claude run --resume abc123The -- separator is consumed once; everything after it reaches Claude as-is.
A profile has two parts:
providerEnv— provider-routing variables (ANTHROPIC_BASE_URL,ANTHROPIC_AUTH_TOKEN/ANTHROPIC_API_KEY,ANTHROPIC_MODEL,ANTHROPIC_DEFAULT_*_MODEL,CLAUDE_CODE_USE_BEDROCK/VERTEX/FOUNDRY, …). These are injected into the spawned process's environment.claudeSettings(optional) — non-provider Claude Code settings: anenvfield for non-provider env vars (e.g.BASH_DEFAULT_TIMEOUT_MS), pluspermissions,hooks, etc. Passed via--settingsand merged with~/.claude/settings.json.
When providerEnv is non-empty, cc-exec also sets CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST=1. This tells Claude Code to strip provider-managed vars from its settings sources (~/.claude/settings.json, project, local, --settings) so they cannot override what the profile injects via the spawn environment. The spawn env is not a settings source, so the profile's provider vars survive and win — a stale ANTHROPIC_MODEL (or ANTHROPIC_DEFAULT_FABLE_MODEL) in ~/.claude/settings.json can no longer bleed through. The flag is set only when providerEnv is non-empty; a profile with only claudeSettings leaves settings.json's provider config active.
The claudeSettings field is loaded as Claude Code flag settings and merged with ~/.claude/settings.json (flag settings win per key; arrays like permissions.allow concatenate, objects like hooks deep-merge). So your existing permissions/hooks are preserved, and the profile layers on top. The config directory itself (~/.claude: history, projects, plugins, …) is untouched.
Contract: put only provider vars in
providerEnv(the ones Claude Code strips when the flag is set). Put non-provider env inclaudeSettings.env. A provider var misplaced inclaudeSettings.envgets stripped by the flag and lost; a non-provider var misplaced inproviderEnvis not stripped and can be overridden by settings.json.
Shell env: the flag only strips provider vars from settings files. Env vars exported in the launching shell are inherited and not cleared — so don't export
ANTHROPIC_*in your shell (keep them in profiles/settings files).
The --settings sidecar lives at <config dir>/cc-exec/settings-cache/<profile>.json and is rewritten on every launch; its path is stable per profile, keeping Claude Code's API prompt cache warm across runs.
If you genuinely want ~/.claude/settings.json to control a provider variable, leave it out of the profile's providerEnv (and don't set the flag — i.e. use a profile with only claudeSettings, or no profile).
Create three profiles, each talking to a different backend. Each create asks for ANTHROPIC_BASE_URL, ANTHROPIC_API_KEY, and the model (leave any blank to skip it; leave ANTHROPIC_BASE_URL blank to skip provider config entirely):
cc-exec create manager
cc-exec create coding
cc-exec create review~/.config/cc-exec/profiles/manager.json — Anthropic official, Opus:
{
"description": "Planning profile on Anthropic official API",
"providerEnv": {
"ANTHROPIC_BASE_URL": "https://api.anthropic.com",
"ANTHROPIC_API_KEY": "sk-ant-manager-example-key",
"ANTHROPIC_MODEL": "claude-opus-4"
}
}~/.config/cc-exec/profiles/coding.json — OpenRouter, Sonnet:
{
"description": "Coding profile on OpenRouter",
"providerEnv": {
"ANTHROPIC_BASE_URL": "https://openrouter.ai/api/v1",
"ANTHROPIC_API_KEY": "sk-or-coding-example-key",
"ANTHROPIC_MODEL": "anthropic/claude-sonnet-4"
}
}~/.config/cc-exec/profiles/review.json — a third Claude-compatible provider:
{
"description": "Review profile on a custom endpoint",
"providerEnv": {
"ANTHROPIC_BASE_URL": "https://internal.example.com/claude",
"ANTHROPIC_API_KEY": "sk-review-example-key",
"ANTHROPIC_MODEL": "claude-sonnet-4"
}
}Now open three terminals and run, independently and simultaneously:
cc-exec manager # Opus on Anthropic
cc-exec coding # Sonnet on OpenRouter
cc-exec review # Sonnet on your custom endpointEach process gets its own environment, so the three sessions never bleed into each other.
Keys above are fake. Never commit real keys.
cc-exec create <name> asks for a description and the provider env fields one at a time. Leave any field blank to skip it.
description(blank defaults to<name> profile)ANTHROPIC_BASE_URL(blank skips the provider fields - emptyproviderEnv;claudeSettingsbelow is still asked)ANTHROPIC_API_KEY(blank omits the field)ANTHROPIC_MODEL(blank omits it; when set, theANTHROPIC_DEFAULT_{FABLE,OPUS,SONNET}_MODELaliases are pointed at it so a third-party endpoint never gets a built-in Anthropic id it can't serve)ANTHROPIC_SMALL_FAST_MODEL(also setsANTHROPIC_DEFAULT_HAIKU_MODEL; blank falls back to theANTHROPIC_MODELvalue; omitted only when both are blank)CLAUDE_CODE_SUBAGENT_MODEL(the model Claude Code uses for subagents / the Task tool; blank omits it)claudeSettings(non-provider Claude settings:env,permissions,hooks, …; always asked, even when BASE_URL was blank. Paste a JSON object - multi-line is fine, newlines are stripped on a TTY; blank ->{}. A bare object body without{}is auto-wrapped; trailing commas are tolerated; invalid JSON re-prompts with the previous content - arrow keys move the cursor, Ctrl+U clears)
After you enter a model name, you're asked whether it's a 1M-context model. Answering yes appends [1M] to the id (e.g. claude-sonnet-4[1M]) - a convention some Claude-compatible providers use to enable the 1M-token context window. The suffix propagates to the channels that inherit the model: ANTHROPIC_DEFAULT_{FABLE,OPUS,SONNET}_MODEL follow the main model, and ANTHROPIC_DEFAULT_HAIKU_MODEL follows ANTHROPIC_SMALL_FAST_MODEL. If the name you type already ends in [1M], the question is skipped. You can also add or remove [1M] by hand with cc-exec update (or by editing the JSON file directly).
create reads one answer per line from stdin, so it works without a TTY - handy for scripts, CI, or AI agents. End of input (EOF) is treated as "leave the rest blank" and finishes the profile:
printf 'coding profile\nhttps://api.example.com\nsk-key\nclaude-sonnet-4\ny\n\n\n{"env":{"BASH_DEFAULT_TIMEOUT_MS":"120000"}}\n' | cc-exec create coding
# description / BASE_URL / API_KEY / MODEL / [1M]? y / SMALL_FAST (blank -> uses MODEL) / SUBAGENT (blank -> skip) / claudeSettings (JSON object)In a real terminal the same flow is interactive: a gray placeholder hint per field (disappears as you type), an arrow-key Yes/No selector for the [1M] question, and answered fields clear as you go. Ctrl+C or Ctrl+D aborts without writing a partial profile (exit 130); only a fully completed flow writes the file.
cc-exec is written to run identically on:
- macOS — Apple Silicon & Intel
- Linux — x64 & ARM64
- Windows
It uses only cross-platform Node.js APIs (process.env, child_process.spawn, path, os.homedir). There are no shell features, no source/export, no which, and no Unix-only commands. On Windows, claude (an npm-installed .cmd shim) is resolved through spawn's shell mode so it works out of the box.
Config directory per OS:
- macOS / Linux:
~/.config/cc-exec/profiles/(or$XDG_CONFIG_HOME/cc-exec/profiles/) - Windows:
%APPDATA%\cc-exec\profiles\
Do I still need the claude CLI installed?
Yes. cc-exec launches whatever claude is on your PATH. Point at a specific binary with CC_EXEC_CLAUDE_BIN.
Can I name a profile list or show?
No — those are reserved subcommands. Pick another name (e.g. ls-profile).
How do I override the model just for one run?
Either update the profile (cc-exec update <profile>) or pass it through: cc-exec coding --model opus (the --model flag reaches claude unchanged).
My provider needs non-ANTHROPIC_* variables. Will that work?
Yes. providerEnv accepts any keys, e.g. OPENROUTER_API_KEY, HTTP_PROXY, etc.
A settings file (~/.claude/settings.json, etc.) sets the same provider env var as my profile. Which wins?
The profile wins. When the profile's providerEnv is non-empty, cc-exec sets CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST=1, which makes Claude Code strip provider-managed vars from its settings files - so settings.json's provider vars cannot override the profile's (injected via the spawn environment) or bleed through. Non-provider settings in settings.json are still preserved and merged with the profile's claudeSettings. If you want settings.json to control a provider var, leave it out of the profile's providerEnv (and use a profile with only claudeSettings, so the flag is not set). See How profile env is applied.
How do I change a profile without exposing my API key?
Use cc-exec update <profile>. It walks the same fields as create, each pre-filled with its current value - press Enter to keep a field, or type to change it. The auth key is the one field not pre-filled: it shows a gray "leave blank to keep unchanged" hint with the masked current key (e.g. sk-****1234), so the secret is never displayed. (cc-exec has no edit command - opening the raw file in an editor would leak the key.)
Does cc-exec store or log my API key?
No. Keys live only in the profile JSON files on disk under your config directory. show masks them; nothing is telemetry'd anywhere.
cc-execnever prints full API keys.showmasks sensitive values (e.g.sk-****xxxx);updatenever echoes the auth key at all (blank keeps it, with only the masked form shown in the prompt).- There is no
editcommand: opening the raw profile JSON in$EDITORwould expose the key, so interactive editing goes throughupdateinstead. - Secret detection is substring-based (
API_KEY,TOKEN,SECRET,PASSWORD,AUTH,CREDENTIAL), so provider-specific tokens are masked by default. - All examples in this README and the docs use fake keys.
- No key is hard-coded in the source.
- Profile files are plain JSON in your user config directory — protect them with filesystem permissions (
chmod 600) as you would any credential file. See docs/configuration.md.
npm install
npm run build # tsup -> dist/
npm test # vitest
npm run typecheck # tsc --noEmit
npm link # make `cc-exec` available locallySee docs/development.md.
MIT