Everything you need to manage multiple Claude Code accounts.
ccpm is a single static binary that creates fully isolated Claude Code profiles — each with its own credentials, settings, memory, and MCP servers.
Installation
Pick a package manager. ccpm ships as a single static binary, so any of these paths gets you to the same place.
go install github.com/nitin-1926/claude-code-profile-manager/ccpm@latestnpm i -g @ngcodes/ccpmcurl -fsSL https://raw.githubusercontent.com/nitin-1926/claude-code-profile-manager/main/scripts/install.sh | shgit clone https://github.com/nitin-1926/claude-code-profile-manager.git
cd claude-code-profile-manager
make build
# binary at ./bin/ccpmQuick start
Three commands and you have two completely separate Claude Code sessions running side by side.
# create your first profile
ccpm add personal
# create a work profile
ccpm add work
# run them in parallel
ccpm run personal # terminal 1
ccpm run work # terminal 2Every command accepts a global --verbose / -v flag that prints extra diagnostic output — useful when filing an issue.
Profile management
ccpm add <name>
Create a new profile. If ~/.claude exists or you already have at least one ccpm profile, an import wizard runs first so the new profile can start from your default Claude config or be cloned from an existing profile. Then you choose between OAuth (browser login) or API key authentication.
$ ccpm add personal
How do you want to seed this profile?
1) Start empty
2) Import from ~/.claude (skills, commands, hooks, agents, settings)
3) Clone from another profile
Enter choice [1/2/3]: 2
Choose authentication method:
1) OAuth (browser login via claude /login)
2) API key (enter your Anthropic API key)
Enter choice [1/2]: 1
✓ profile "personal" authenticated via OAuthccpm list
List all profiles with their authentication status. Also available as ccpm ls.
ccpm remove <name>
Delete a profile including its directory, keychain entries, and vault backup. Use --force (-f) to skip confirmation. Also available as ccpm rm.
# with confirmation prompt
ccpm remove work
# skip the prompt
ccpm rm work --forceccpm status
Show system overview: ccpm version, Claude binary location, all profiles and their auth health.
Running Claude
ccpm run <name>
Recommended. Launch Claude Code with the given profile. Sets CLAUDE_CONFIG_DIR and ANTHROPIC_API_KEY (for API key profiles), then replaces the process with Claude. Works without any shell setup.
Unknown flags after the profile name flow through to claude directly — no -- separator needed for the common cases. Three flags are intercepted by ccpm: --ccpm-env KEY=VALUE (repeatable, one-shot env override), --help, and --version. Use -- to forward the latter two to claude.
# flags forward to claude without a separator
ccpm run work --dangerously-skip-permissions
ccpm run work --model claude-sonnet-4-6
# one-shot env override (persists nothing)
ccpm run work --ccpm-env ANTHROPIC_BASE_URL=https://proxy.example
# forward --help or --version to claude with the -- separator
ccpm run work -- --help
ccpm run work -- --versionccpm use [name]
Set the active profile for your entire shell session. Requires the shell hook. After running this, any claude command in that terminal uses the selected profile.
Called without a name in an interactive terminal, ccpm use opens a profile picker. In non-TTY contexts (scripts, CI) the name argument is required.
Authentication
ccpm auth status [name]
Check credential validity across profiles. Shows email for OAuth profiles, masked key for API key profiles, and vault backup status. Pass a profile name to inspect just that one; omit it to see every profile. Entries flagged as ⚠ expire within seven days.
ccpm auth refresh <name>
Re-authenticate a profile. For OAuth: launches Claude for /login. For API key: prompts for a new key (hidden input in a TTY, or reads from stdin when piped).
ccpm auth backup / restore
Save an encrypted credential backup to ~/.ccpm/vault/ (AES-256-GCM, master key in the OS keychain) or restore one after a machine migration. See Vault backup for the full story.
Import & wizard
ccpm has three ways to bring existing Claude assets into a profile: the interactive wizard that runs during ccpm add, ccpm import default for pulling from ~/.claude, and ccpm import from-profile for cloning between ccpm profiles.
ccpm import default
Import skills, commands, hooks, agents, rules, settings, MCP servers, and plugins from ~/.claude into one or all profiles. Dedupable targets (skills, agents, commands, hooks, rules) are routed through the shared store at ~/.ccpm/share/ and symlinked into the profile so the same asset is not copied twice.
# import everything into one profile
ccpm import default --profile work
# import only skills into every profile
ccpm import default --all --only skills
# preview what would happen without writing
ccpm import default --profile work --dry-run
# overwrite existing profile files
ccpm import default --profile work --force
# copy directly instead of symlinking (opts out of dedup)
ccpm import default --profile work --no-share
# keep symlink-to-dir entries as live symlinks into the share store
ccpm import default --profile work --live-symlinks
# skip every per-item prompt and import all discovered assets
ccpm import default --profile work --select-all
# decide whether imported MCP servers live in the global or per-profile fragment
ccpm import default --profile work --mcp-scope profileValid --only values: skills, commands, rules, hooks, agents, settings, mcp, plugins. Pass them comma-separated.
Interactive wizard. In a TTY, ccpm import default opens a guided flow: pick the target profile (or all), choose which asset types to import, select individual items within each type, decide whether symlink-to- directory sources stay live or are snapshotted, and pick MCP scope (global vs. per-profile). Use --select-all, --no-live-symlinks, and --mcp-scope to skip prompts in scripts.
ccpm import from-profile
Clone assets from one ccpm profile into another. Useful for bootstrapping a new profile from a known-good one, or for copying a subset of tools between personal and work setups. In a TTY both source and target are picker-driven; otherwise --src and --profile are required.
# clone everything from "work" into new profile "work-staging"
ccpm add work-staging
ccpm import from-profile --src work --profile work-staging
# clone only skills and commands
ccpm import from-profile --src work --profile work-staging --only skills,commands
# overwrite existing files in the target profile
ccpm import from-profile --src work --profile work-staging --forceSettings merge: existing keys in the target profile win; new keys from the source are added. MCP servers are not cloned via this command — use MCP commands directly to share MCP fragments.
ccpm sync
Re-apply every global install (skills, MCP fragments, settings) to one or all profiles. Useful after editing ~/.ccpm/share/ directly, or to heal a profile whose symlinks or settings have drifted. Sync also runs automatically on ccpm add and ccpm run.
# sync every profile
ccpm sync --all
# sync just one
ccpm sync --profile work
# TTY: omit flags to pick profiles interactively
ccpm syncIn a TTY with no flags, ccpm sync opens a multi-select picker. In non-TTY contexts the default is to sync all profiles.
Skills, MCP, and settings
These three asset types are the heart of ccpm's sharing model. Install something with --global and every profile picks it up; install with --profile <name> and only that profile sees it. Global installs automatically propagate to new profiles created afterward.
| Asset | Shared store | In profile | Mechanism |
|---|---|---|---|
| Skills / agents / commands | ~/.ccpm/share/<kind>/<name> | <profile>/<kind>/<name> | Symlink |
| MCP servers | ~/.ccpm/share/mcp/{global,<profile>}.json | <profile>/settings.json#mcpServers | Merge at launch |
| Settings | ~/.claude/settings.json (shared baseline) + ~/.ccpm/share/settings/<profile>.json (per-profile) | <profile>/settings.json | Deep merge + owned-keys override + project layer |
# global skill (installed into every profile)
ccpm skill add ~/code-review --global
# per-profile MCP with an auth token
ccpm mcp add github --command "npx -y @modelcontextprotocol/server-github" \
--env GITHUB_TOKEN=ghp_... --profile work
# profile-specific setting
ccpm settings set model claude-opus-4 --profile work
# shared-across-profiles setting → edit the host file directly
# (no ccpm command — this is native Claude's settings layer)
# ~/.claude/settings.json is the cross-profile baselineccpm skill / ccpm mcp accept --global or --profile (they prompt if you omit both in a TTY). ccpm settings only accepts --profile: shared defaults live in ~/.claude/settings.json directly.
Skill commands
ccpm skill installs a directory that contains a SKILL.md into the shared store, then links it into one or all profiles. Live symlinks keep the profile copy pointing at the original source; the default is to snapshot the directory into ~/.ccpm/share/skills/.
# install a local skill globally
ccpm skill add ~/code-review --global
# install only into "work"
ccpm skill add ~/code-review --profile work
# keep a symlink-to-dir source live (updates in-place)
ccpm skill add ~/code-review --global --live-symlink
# always snapshot (disable the live-symlink prompt)
ccpm skill add ~/code-review --global --copy
# list all installed skills (alias: skill ls)
ccpm skill list
# remove a skill from all profiles (alias: skill rm)
ccpm skill remove code-review --global
# remove from one profile only
ccpm skill rm code-review --profile work
# link a shared skill into a specific profile
ccpm skill link code-review --profile workAgents, commands, and rules
ccpm agent, ccpm command, and ccpm rule share the exact subcommand shape as ccpm skill: add/remove/list/link with --global, --profile, --live-symlink, and --copy flags. Each kind has its own shared store (~/.ccpm/share/{agents,commands,rules}/) and its own symlink subdirectory under every profile. Unlike skills (directories with a SKILL.md marker), the source for agents/commands/rules can be a single file (typically a .md file).
# install a custom agent for all profiles
ccpm agent add ~/my-agent.md --global
# install a slash command for one profile
ccpm command add ~/commands/ship.md --profile work
# install a rule into the shared store
ccpm rule add ~/rules/house-style.md --global
# list / remove / link work the same as skills
ccpm agent list
ccpm command rm ship --profile work
ccpm rule link house-style --profile stagingPlugin commands
Plugin files are installed by Claude Code itself — run /plugin install <name> inside a ccpm session to add one. ccpm manages the enabledPlugins settings key per profile so you can override which plugins are active in each profile. There is no ccpm plugin install (the stub exists only to point users back to the in-session command).
# show installed plugins + enabled state across every profile
ccpm plugin list
# limit to one profile
ccpm plugin list --profile work
# enable a plugin for one profile
ccpm plugin enable vercel@claude-plugins-official --profile work
# disable a globally-enabled plugin in one profile
ccpm plugin disable vercel@claude-plugins-official --profile personalGlobal activation (every profile) is whatever Claude Code wrote into ~/.claude/settings.json under enabledPlugins — ccpm reads that as the baseline and layers profile fragments on top.
Hook commands
ccpm hooks manages entries under the hooks key in a profile's settings fragment. Each entry has an optional matcher (tool-name pattern — empty matches all) and a command. Hook scripts on disk (files in ~/.claude/hooks/) are managed separately via ccpm import default --only hooks.
# run a shell command before every tool use
ccpm hooks add PreToolUse "echo firing" --profile work
# restrict to Edit / Write tools
ccpm hooks add PostToolUse "make lint" --matcher "Edit|Write" --profile work
# show the merged hook view (baseline + profile overrides)
ccpm hooks list --profile work
# remove the last entry (or use --index N for a specific position)
ccpm hooks remove PreToolUse --profile workKnown events: PreToolUse, PostToolUse, UserPromptSubmit, SessionStart, SessionEnd, Notification, Stop, SubagentStop, PreCompact.
MCP commands
ccpm mcp supports three scopes and three transports. Scope controls *where* the server definition is written: the shared fragment (--scope global), a single profile (--scope profile --profile <name>), or the current project's .mcp.json(--scope project). Transport controls the wire format: stdio (default; use --command), http, or sse (use --url and optional --header KEY=VALUE).
# stdio MCP for one profile, with env vars
ccpm mcp add github \
--scope profile --profile work \
--command "npx" \
--args "-y,@modelcontextprotocol/server-github" \
--env GITHUB_TOKEN=ghp_...
# remote HTTP MCP with a bearer token header
ccpm mcp add supabase \
--scope profile --profile work \
--transport http \
--url https://mcp.supabase.com/mcp \
--header "Authorization=Bearer $SUPABASE_TOKEN"
# globally-shared server (all profiles, now and future)
ccpm mcp add linear \
--scope global \
--command "npx -y @linear/mcp" \
--env LINEAR_API_KEY=lin_...
# project-scoped MCP — writes to <repo>/.mcp.json
ccpm mcp add repo-tools --scope project --command node --args "./mcp/index.js"
# OAuth for a remote MCP — spawns native claude scoped to the profile
ccpm mcp auth supabase --profile work
# list MCPs with their source (ccpm-global | ccpm-profile | host | project)
ccpm mcp list
# remove (alias: mcp rm)
ccpm mcp remove github --scope profile --profile work
# bulk import
ccpm mcp import ./mcp-servers.json --scope global--args takes a comma-separated list; --env and --header take KEY=VALUE pairs and may be repeated. --global and --profile <name> are still accepted as aliases for --scope global and --scope profile --profile <name>.
Env var commands
Persist environment variables on a profile; they're layered into the process env at every ccpm run, sitting below the parent process env and above ccpm run --ccpm-env overrides. CLAUDE_CONFIG_DIR and ANTHROPIC_API_KEY are reserved — ccpm always computes them.
# persist env vars on a profile
ccpm env set ANTHROPIC_BASE_URL=https://proxy.example CLAUDE_CODE_MAX_OUTPUT_TOKENS=32768 --profile work
# remove
ccpm env unset ANTHROPIC_BASE_URL --profile work
# list
ccpm env list --profile workPermission commands
ccpm permissions manages permissions.{allow,ask,deny,defaultMode} directly — no JSON surgery. Adding a rule to one bucket removes it from the other two so the lists stay disjoint. Use --global to write to ~/.claude/settings.json (the cross-profile baseline) instead of a profile fragment.
# allow, ask, or deny a tool-pattern rule (syntax matches native claude)
ccpm permissions allow "Bash(git status:*)" --profile work
ccpm permissions ask "Edit(**/*.md)" --profile work
ccpm permissions deny "Bash(rm:*)" --profile work
# strip a rule from all three buckets
ccpm permissions remove "Bash(git status:*)" --profile work
# set the default mode (native enum)
ccpm permissions mode plan --profile work
# valid values: default, acceptEdits, plan, auto, dontAsk, bypassPermissions
# list all rules and the current default mode
ccpm permissions list --profile workSessions
ccpm sessions list <profile> reads the JSONL session files Claude Code stores inside a profile at <profileDir>/projects/<encoded-cwd>/*.jsonl. By default it scopes to the current working directory (matching claude --resume); pass --all to list every project.
# sessions for the current project in profile "work"
ccpm sessions list work
# every session the profile has ever recorded
ccpm sessions list work --allSettings commands
Per-profile settings fragments live at ~/.ccpm/share/settings/<profile>.jsonand are deep-merged into the profile's settings.json. Keys you set through ccpm are tracked in a .owned.json sidecar so Claude Code cannot silently overwrite them (see Settings precedence).
ccpm does not manage shared settings. The cross-profile baseline is ~/.claude/settings.json — the same file native Claude Code reads — and ccpm merges it into every profile at launch. Edit it with a text editor, or run claude /config natively, to change defaults for every profile. There is no --global flag on ccpm settings.
# set a per-profile scalar
ccpm settings set model claude-opus-4 --profile work
# dot-notation nested key
ccpm settings set permissions.allow.Bash true --profile work
# JSON values (objects, arrays) are parsed automatically
ccpm settings set env.FOO '{"a":1,"b":2}' --profile work
# read the effective value for a profile
ccpm settings get model --profile work
# dump the fully merged settings for a profile
ccpm settings show --profile work
# apply a JSON fragment file (deep-merged into the profile)
ccpm settings apply ./team-defaults.json --profile work
# set the native statusLine block (empty string removes it)
ccpm settings statusline "~/.claude/statusline.sh" --profile work
# set the outputStyle (Build | Explanatory | Learning | Direct | default)
ccpm settings outputstyle Explanatory --profile workAll subcommands (set, get, apply, show, statusline, outputstyle) require --profile. The statusline wrapper writes the native {type: "command", command: ...} shape so it stays loadable by native claude.
MCP auth model
How an MCP server authenticates determines whether ccpm can isolate it per profile. There are three categories:
GITHUB_TOKEN or LINEAR_API_KEY. ccpm stores the value inside the per-profile MCP fragment at ~/.ccpm/share/mcp/<profile>.json, so every profile can carry a different account. Use --env KEY=VALUE with ccpm mcp add..claude.json under mcpOAuth. Because CLAUDE_CONFIG_DIR is per-profile, each profile gets its own OAuth session automatically. To trigger an OAuth dance from ccpm without launching a full claude session, run ccpm mcp auth <server> --profile <name> — it spawns native claude with CLAUDE_CONFIG_DIR pinned to the profile so tokens land in the right scope.~/.config/<service>/ or a non-namespaced OS keychain entry. These are shared across all profilesand ccpm cannot isolate them without cooperation from the MCP server. Treat them as "one account for all profiles" and plan accordingly.Settings precedence
At launch, ccpm materializes settings.json for a profile by merging in this order (lowest → highest, higher wins):
- The profile's existing
<profile>/settings.json— preserves keys Claude Code auto-wrote that nothing else redefines. ~/.claude/settings.json— the host file native Claude Code uses. Edit it to change defaults for every profile.~/.ccpm/share/settings/<profile>.json— the per-profile ccpm fragment. Beats the shared baseline for this profile.- Owned-keys override. Any leaf key you set via
ccpm settings set --profileorccpm settings apply --profileis recorded in a.owned.jsonsidecar and re-applied from the profile fragment. This guarantees values you explicitly set through ccpm are never silently overwritten by Claude Code rewriting its own config. ./.claude/settings.jsonat the project root (discovered by walking up from CWD). Per-repo overrides beat profile defaults../.claude/settings.local.jsonat the project root — gitignored per-machine overrides for the same project.- Enterprise managed-settings.
/Library/Application Support/ClaudeCode/managed-settings.jsonon macOS,/etc/claude-code/managed-settings.jsonon Linux, andC:\ProgramData\ClaudeCode\managed-settings.jsonon Windows — plus siblingmanaged-settings.d/*.jsondrop-ins merged in alphabetical order. Highest precedence so org-level policy always wins, matching native Claude Code.
Objects merge key-by-key; arrays and scalars from a higher-precedence source replace the lower one.
Doctor
ccpm doctoris your one-stop health check. It never fails builds — warnings are informational — but it will tell you when something is actually broken so you don't chase ghosts.
It reports on, in order:
- Environment — ccpm version, platform, Claude Code binary path, and
claude --version(with a warning on macOS if you're below v2.1.56, which is required for per-profile OAuth keychain isolation). - ccpm base directory — confirms
~/.ccpm/exists and is readable. - Per-profile auth health — OAuth token validity and expiry for each profile. On macOS OAuth profiles, the namespaced keychain service name is printed so you can inspect the entry manually with Keychain Access.
- Root vs. profile diff — anything in
~/.claudethat no profile has adopted yet, and vice-versa. Prints a one-line hint pointing at the rightccpm importcommand. - Symlink integrity — flags broken symlinks and copies under a profile that have drifted from the shared store.
- Shared asset manifest — how many skills, MCP servers, and settings keys are tracked in
~/.ccpm/installs.json. - Drift fingerprint — detects when
~/.claudehas changed since the lastccpm import defaultsnapshot. - Drift notifications — whether the
check_default_driftconfig flag is on (see Drift detection). - Platform notes — platform-specific caveats such as the Windows symlink fallback marker and global-cache MCP isolation limits.
Exit code is 0 on success or when only warnings are present, and 1 when real issues are detected.
$ ccpm doctor
Environment
ccpm 0.4.12
platform darwin/arm64
claude 2.1.61 (/usr/local/bin/claude)
Profiles
personal oauth ✓ valid keychain: Claude Code-credentials-7b3a4f19
work apikey ✓ valid
Root vs profiles
~/.claude has "python-review" skill; no profile adopted it
↳ ccpm import default --only skills --all
No symlink issues. No drift detected.Drift detection
Every ccpm import default snapshots the files under ~/.claude (skills, commands, rules, hooks, agents, settings, MCP fragments) into a fingerprint. Later, ccpm can tell you whether your default Claude config has drifted away from what your profiles were built from — so a skill you tweaked in ~/.claude does not get stale in your profiles.
ccpm default fingerprint
# record the current ~/.claude state as the drift baseline
ccpm default fingerprint update
# compare ~/.claude against the last fingerprint
ccpm default fingerprint checkcheck prints added, modified, and removed paths and suggests the right ccpm import default --profile <name> to sync changes into a profile. Run update to accept the current state without importing.
ccpm config
Drift nudges on ccpm run and ccpm use are controlled by a single config key.
# turn drift warnings on (default is off)
ccpm config set check_default_drift true
# turn them off
ccpm config set check_default_drift false
# read the current value
ccpm config get check_default_driftVault backup
ccpm can create encrypted backups of your credentials for disaster recovery and machine migration. Uses AES-256-GCM encryption with a master key stored in your OS keychain.
# backup credentials
ccpm auth backup personal
# restore after machine migration
ccpm auth restore personalUninstall
ccpm uninstall removes every profile, deletes API keys from the OS keychain, wipes vault backups, and deletes ~/.ccpm/. It does not remove the ccpm binary itself or the shell hook you added to ~/.zshrc / ~/.bashrc — the command prints those cleanup steps so you can run them by hand.
# with confirmation prompt
ccpm uninstall
# skip the confirmation
ccpm uninstall --forceShell integration
The shell hook wraps ccpm use so it can set environment variables in your current shell. Without it, ccpm use cannot modify your shell environment.
Setup
# add to ~/.zshrc or ~/.bashrc (shell auto-detected)
eval "$(ccpm shell-init)"
# force a specific shell (bash | zsh | fish | powershell)
eval "$(ccpm shell-init --shell zsh)"
# reload
source ~/.zshrcUsage
# set profile for this terminal session
ccpm use personal
# now any 'claude' command uses the personal profile
claudeSupported shells: zsh, bash, fish, PowerShell.
IDE / VS Code
The VS Code Claude extension ignores CLAUDE_CONFIG_DIR and always reads from ~/.claude. Use set-default to control which account VS Code uses. Call it without an argument in a TTY for a profile picker. Restart the extension after switching so it re-reads credentials.
# set which profile VS Code uses
ccpm set-default work
✓ profile "work" is now the default
# pick interactively
ccpm set-default
# clear the default
ccpm unset-defaultPrivacy & security
Credential storage
API keys are stored in your OS keychain (macOS Keychain, Linux Secret Service, Windows Credential Manager) — never in plaintext files. OAuth tokens are managed by Claude Code itself within the isolated profile directory.
Encrypted vault
Vault backups use AES-256-GCM encryption with a master key stored in your OS keychain. The encrypted files live locally in ~/.ccpm/vault/.
Local config only
All configuration, profiles, and data live in ~/.ccpm/ on your filesystem. No cloud storage, no sync services, no external dependencies.
Open source
ccpm is fully open source under the MIT license. Audit the code yourself.
Platform support
set-default, auth backup/restore, keychain-based status) are experimental — they have not been exercised against a real Linux Secret Service or Windows Credential Manager install. macOS now; Linux + Windows coming soon.| Feature | macOS ✓ | Windows ⚠ | Linux ⚠ |
|---|---|---|---|
| OAuth per-profile | Keychain entry namespaced by profile dir | wincred entry, same namespacing (theoretical) | .credentials.json (legacy) |
| API key storage | Keychain | Credential Manager | Secret Service |
| Parallel sessions | Yes | Yes | Yes |
| Shared skill dedup | Symlinks | Symlinks (Developer Mode) or copy fallback | Symlinks |
| Shell hook | zsh, bash, fish | PowerShell | zsh, bash, fish |
Claude Code-credentials entry across all profiles, so multiple OAuth profiles cannot stay authenticated simultaneously. ccpm doctor warns when your installed Claude Code is too old.Known limitations
~/.claude. Use ccpm set-default <profile>to point it at a specific ccpm profile. On macOS (verified) and Windows (experimental) this copies the profile's namespaced credential-store entry into the default slot under the OS-user account; on Linux it falls back to copying .credentials.json until a libsecret-backed handler ships.~/.ccpm/.windows-copy-fallback. Turn on Developer Mode for true deduplication.~/.config/<service>/ or a non-namespaced OS keychain entry) are shared across every profile. See MCP auth model for details.~/ paths on Linux. ccpm always uses absolute paths, so this is handled automatically.go-keyring requires D-Bus and a secret service (gnome-keyring or kwallet). On headless servers, API key profiles need a running secret service.