Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

27 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

cc-exec

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.


When you need cc-exec

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 export soup 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.


Install

Requires Node.js >= 20.

npm install -g cc-exec

Or run once without installing:

npx cc-exec list

After global install, the cc-exec command is on your PATH:

cc-exec list

cc-exec shells out to the claude CLI, so the official Claude Code CLI must also be installed and on your PATH. Override the binary with CC_EXEC_CLAUDE_BIN if you need a specific path.


Quick start

# 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 coding

That's it. cc-exec coding is equivalent to:

ANTHROPIC_BASE_URL=... ANTHROPIC_API_KEY=... ANTHROPIC_MODEL=... claude

Profile configuration

Profiles 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:*)"]
    }
  }
}
  • providerEnv accepts any environment variables — not only ANTHROPIC_*.
  • claudeSettings (optional) carries non-provider Claude settings - an env for non-provider vars, plus permissions, hooks, etc. Passed via --settings and 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 → profile coding); there is no name field in the file.

See docs/configuration.md for the full reference.


CLI commands

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.

Listing profiles

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_MODEL and the four ANTHROPIC_DEFAULT_{FABLE,OPUS,SONNET,HAIKU}_MODEL tier 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_MODEL as-is.
  • BASE URL shows ANTHROPIC_BASE_URL.
  • KEY shows the masked auth key (ANTHROPIC_API_KEY, else ANTHROPIC_AUTH_TOKEN, else any other sensitive var), so list is safe to share just like show.

Argument passthrough (important)

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 abc123

The -- separator is consumed once; everything after it reaches Claude as-is.

How profile env is applied (important)

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: an env field for non-provider env vars (e.g. BASH_DEFAULT_TIMEOUT_MS), plus permissions, hooks, etc. Passed via --settings and 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 in claudeSettings.env. A provider var misplaced in claudeSettings.env gets stripped by the flag and lost; a non-provider var misplaced in providerEnv is 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).


Multi-API example: Manager + Coding + Review

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 endpoint

Each process gets its own environment, so the three sessions never bleed into each other.

Keys above are fake. Never commit real keys.


Creating profiles

cc-exec create <name> asks for a description and the provider env fields one at a time. Leave any field blank to skip it.

  1. description (blank defaults to <name> profile)
  2. ANTHROPIC_BASE_URL (blank skips the provider fields - empty providerEnv; claudeSettings below is still asked)
  3. ANTHROPIC_API_KEY (blank omits the field)
  4. ANTHROPIC_MODEL (blank omits it; when set, the ANTHROPIC_DEFAULT_{FABLE,OPUS,SONNET}_MODEL aliases are pointed at it so a third-party endpoint never gets a built-in Anthropic id it can't serve)
  5. ANTHROPIC_SMALL_FAST_MODEL (also sets ANTHROPIC_DEFAULT_HAIKU_MODEL; blank falls back to the ANTHROPIC_MODEL value; omitted only when both are blank)
  6. CLAUDE_CODE_SUBAGENT_MODEL (the model Claude Code uses for subagents / the Task tool; blank omits it)
  7. 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)

1M context ([1M] suffix)

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).

Non-interactive / scripted creation

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.


Cross-platform notes

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\

FAQ

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.


Security

  • cc-exec never prints full API keys. show masks sensitive values (e.g. sk-****xxxx); update never echoes the auth key at all (blank keeps it, with only the masked form shown in the prompt).
  • There is no edit command: opening the raw profile JSON in $EDITOR would expose the key, so interactive editing goes through update instead.
  • 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.

Development

npm install
npm run build      # tsup -> dist/
npm test           # vitest
npm run typecheck  # tsc --noEmit
npm link           # make `cc-exec` available locally

See docs/development.md.


License

MIT

About

Claude Code Profile Launcher - run multiple Claude Code instances against different third-party APIs, side by side, without interference.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages