Target: ≤ 3 minutes from install to first successful switch on a warm machine, no maintainer assistance (PRD SM-5).
- Go 1.21+ on
$PATH. - Either Claude Code or Codex CLI installed and previously run at least once (so its config file exists).
If neither tool is installed, install one first — claudecm's job is to swap their configs, not to install them.
go install github.com/a2d2-dev/claudecm@latestExpected: go install completes silently; claudecm version prints a version line.
Pick whichever tool you already have set up. Both commands are non-interactive with --yes.
claudecm import claude-code --name existing --yesor
claudecm import codex --name existing --yesExpected: imported profile "existing" from claude-code (or codex). A profile file is now at ~/.claudecm/profiles/existing.yaml with file mode 0600.
claudecm add work \
--base-url https://api.anthropic.com \
--api-key sk-ant-xxxxxxxx \
--model claude-opus-4-5Expected: Profile "work" created..
You can also start from a built-in provider preset. Presets are convenience templates, not official provider support, certification, endorsement, or compatibility guarantees. They fill generated fields such as base_url, model, provider, and supported tool overlays; you still supply the secret, and every generated field can be overridden.
claudecm add work --preset moonshot --api-key sk-ant-xxxxxxxx --dry-runExpected: a redacted profile draft, not a write. The generated fields are visible:
core:
provider: moonshot
base_url: https://api.moonshot.cn/v1
api_key: sk-a***xxxx
model: kimi-k2-0711-preview
tools:
codex:
raw:
model: kimi-k2-0711-preview
model_provider: moonshot
model_providers.moonshot.base_url: https://api.moonshot.cn/v1
model_providers.moonshot.env_key: OPENAI_API_KEY
model_providers.moonshot.name: Moonshot AI
model_providers.moonshot.wire_api: chatThen save it:
claudecm add work --preset moonshot --api-key sk-ant-xxxxxxxxAvailable presets: moonshot, deepseek, glm, qwen. Use claudecm add --list-presets to inspect the current catalog. Endpoint and model names can drift, so override with --base-url, --model, --provider, or --set when a provider changes its API.
You can also build a draft from pasted text. This is local by default: --from-text runs the local extractor, redacts secrets in --dry-run, and does not use the network.
claudecm add work \
--from-text 'ANTHROPIC_BASE_URL=https://api.anthropic.com ANTHROPIC_AUTH_TOKEN=sk-ant-xxxxxxxx ANTHROPIC_MODEL=claude-opus-4-5' \
--dry-runUse --from-text - to read stdin:
cat provider-snippet.txt | claudecm add work --from-text - --dry-runFor the lowest-friction local onboarding, --auto / -a takes no profile name and sweeps the clipboard, environment, ~/.claude/settings.json, and ~/.codex/{auth.json,config.toml} in order. It drops candidates without an API key, collapses duplicate credentials, skips anything already recorded, and creates one profile per remaining credential. It never uses the network. Missing sources, such as no clipboard tool on PATH, are reported and do not stop the rest of the sweep; Codex auth.json is still read even if config.toml contains unknown sections.
claudecm add --auto --dry-run
claudecm add --auto --yesExpected: a redacted discovery list and a preview or creation report for every new profile. Interactive terminals prompt for each new credential name with a derived default, accept Enter to keep the default, then ask for confirmation before writing. --yes and non-interactive runs use derived names without prompting; non-interactive runs require --yes.
If the local extractor is not enough, --ai is opt-in per invocation and only runs in an interactive terminal. claudecm strips secret-shaped tokens locally, keeps captured secrets in-process, shows the exact desensitized payload for confirmation, and sends only the confirmed desensitized text in one Anthropic-compatible Messages request using the active profile's credentials, or --ai-profile <name> if you choose another credential-lending profile. Non-interactive or piped --ai runs refuse before sending.
claudecm add work --from-text 'messy provider note with sk-ant-xxxxxxxx' --ai --dry-runName rules. Profile names must match
^[a-z0-9][a-z0-9._-]{0,63}$(NFR-S5). Ifclaudecm addfails with a profile-name error, that regex is the reason — no uppercase, no leading dot/dash, ≤ 64 characters.
claudecm switch work --yesExpected: a pre-apply diff summary, followed by Switched to "work".. Behind the scenes claudecm has:
- Locked the target tool files.
- Backed up the current contents to
~/.claudecm/backups/<tool>/<file>/<timestamp>. - Written both
~/.claude/settings.jsonand~/.codex/config.toml(andauth.jsonif owned) atomically. - Reparsed each written file. If any reparse failed, both files were restored from the pre-Stage in-memory bytes (rollback).
First switch. The first
switchfor each tool creates the first entry in~/.claudecm/backups/.claudecm restore --listwill surface them.
Interactive switch. In a real terminal, bare
claudecm switchopens an optional fuzzy profile selector with a redacted preview. This is terminal-only convenience UX; scripts, CI, non-TTY stdin/stdout, andclaudecm switch <name> --yeskeep the stable v1 command behavior.
claudecm currentExpected: two lines, one per tool, showing the active profile name and the resolved base URL / model.
claudecm explain workExpected: a per-tool table of every owned key with its winning layer (env / on-disk / profile / default) plus the shadowed layers underneath. Secrets are redacted as sk-***<last4> unless you pass --reveal (NFR-S8).
claudecm switch existing --yes
claudecm currentExpected: current now shows existing as active for both tools.
If any step above misbehaves, in order of usefulness:
claudecm explain <name>— the fastest way to see whether your problem is an env var overriding the profile, a stale on-disk key, or a profile field you didn't intend. Every layer is visible.claudecm restore --list— every write goes through a backup step (FR-5). This lists timestamped snapshots per owned file.claudecm restore --file <path> --at <timestamp>reverts.~/.claudecm/audit.log— one line per write. Includes which command ran, which files were touched, and the backup timestamp. This is the ground truth whenexplainand memory disagree.
- Storage: profiles are plaintext YAML at
~/.claudecm/profiles/*.yaml, mode0600, directory0700. No cryptographic protection in v1 (ADR-0001 §Locked Decisions, NFR-D1). If your threat model requires vault-grade storage, wrapimport/exportaround your existing secret manager. - Sandboxing: every command accepts
--home <dir>to redirect$HOME. Useful for testing on the same machine without touching your real config. - Dry-run:
switch,add,edit,import,restoreall accept--dry-runand print the write plan without touching disk.