Skip to content

About

Manage multiple claude code accounts with ease. Switching context has never been easier

Resources

Code of conduct

Contributing

Security policy

Stars

8 stars

Watchers

0 watching

Forks

Repository files navigation

ccpm

Run multiple Claude Code accounts in parallel. Fully isolated. One command.

CI npm License: MIT

ccpm (Claude Code Profile Manager) lets you keep multiple isolated Claude Code profiles on one machine. Each profile has its own credentials, settings, MCP servers, plugins, skills, and memory. Open two terminals, run two different accounts at the same time.

Why

Claude Code reads its config from a single directory (~/.claude). Without ccpm you can only be signed into one account at a time and switching means logging out, swapping files, or both.

ccpm gives every profile its own config directory and sets CLAUDE_CONFIG_DIR to it when you launch claude. Two terminals, two profiles, zero conflicts.

Install

CLI

# npm
npm i -g @ngcodes/ccpm

# go
go install github.com/nitin-1926/claude-code-profile-manager/ccpm@latest

# curl (macOS / Linux)
curl -fsSL https://raw.githubusercontent.com/nitin-1926/claude-code-profile-manager/main/scripts/install.sh | sh

# source
git clone https://github.com/nitin-1926/claude-code-profile-manager.git
cd claude-code-profile-manager/ccpm
go build -o ccpm .
./ccpm --version

Desktop (macOS)

curl -fsSL https://raw.githubusercontent.com/nitin-1926/claude-code-profile-manager/main/scripts/install-desktop.sh | sh

A native app over the same engine. The installer picks your chip, verifies the checksum and puts CCPM in Applications, and the app updates itself from then on. A downloadable .dmg is coming soon. The app uses the ccpm CLI for changes, so install the CLI too. More in Desktop app.

Quick start

ccpm in action

# Create profiles (each prompts for OAuth or API key)
ccpm add personal
ccpm add work

# Run them in parallel
ccpm run personal    # terminal 1
ccpm run work        # terminal 2

# See what you have
ccpm list

Sample output:

NAME       AUTH      STATUS
personal   oauth     ✓ nitin@gmail.com
work       api_key   ✓ sk-ant-...7f2k   ★

Migrating your existing ~/.claude

If you already use Claude Code, your first profile doesn't have to start empty. When ~/.claude exists, the ccpm add wizard offers to import your current config — skills, MCP servers, hooks, agents, commands, rules, and settings all carry over into the new profile.

# the wizard prompts: start empty, import from ~/.claude, or clone a profile
ccpm add personal

To do it explicitly (or to pull in changes you made to ~/.claude later), run ccpm import default — see the Import section for --only, --all, and dry-run flags.

Changelog

Release notes live on the docs site: ccpm.nitingupta.xyz/changelog.

Key features

  • Parallel sessions: run different Claude Code accounts side-by-side, fully isolated.
  • OAuth + API key per profile, with credentials in the OS keychain.
  • Per-profile assets: skills, agents, commands, rules, hooks, plugins, MCP servers, and settings install globally, per-profile, or per-project.
  • MCP transports: stdio, HTTP, and SSE. Remote MCPs authenticate via ccpm mcp auth so OAuth tokens land in the right profile.
  • Permissions and hooks without hand-editing JSON.
  • Transparent arg forwarding: unknown flags after ccpm run <profile> flow through to claude with no -- separator.
  • IDE default: ccpm set-default pins a profile for direct claude launches (VS Code, Cursor, Antigravity, any GUI extension). On macOS this is set system-wide via a LaunchAgent.
  • Encrypted vault: AES-256-GCM credential backups with a master key in your OS keychain.
  • Shared store: directory-based assets are symlinked from ~/.ccpm/share/ into profiles for deduplication.
  • macOS is the verified platform today. Linux and Windows builds compile and ship; OAuth set-default, auth backup/restore, and keychain-based status on those platforms are experimental until verified on real hardware.

Desktop app (optional)

A native desktop GUI for managing profiles lives in ccpm/desktop/. It's built with Wails (Go + native webview) in the same module as the CLI — so it reuses ccpm's own engine for reads and shells out to the ccpm CLI for writes (same locking, keychain, and validation). Local-first, no signup.

It gives you a left sidebar of profiles and, per profile, tabs for Overview, Cascade (the effective host→global→profile config with provenance badges and shadow/override hints), Assets, MCP & Plugins, Permissions (rules, mode, env), Settings (settings JSON, the status line layout, and the usage notch), Usage (an amber token-usage dashboard mirroring ccpm usage), History (browse, read and search this profile's past sessions), and Health (ccpm doctor). Clone / rename / delete / open / run profiles from the toolbar; the view auto-refreshes when the CLI changes things underneath it. Creating a profile or importing ~/.claude opens a Terminal running the ccpm add wizard (in-GUI sign-in is on the roadmap).

CCPM Desktop — profile overview
Overview — every profile's assets at a glance.

CCPM Desktop — token usage dashboard
Usage — token spend, burn rate, and a live 5-hour block, per profile.

CCPM Desktop — MCP servers and plugins
MCP & Plugins — manage servers and toggle plugins per profile.

Usage notch (macOS)

An optional notch that grows out of a screen edge, with one ring per profile: the big ring is your 5-hour window (or your weekly one, if you pick it as the main ring), the thin inner ring the other, coloured by how much headroom is left — the same thresholds ccpm statusline uses, so the notch and your terminal never disagree. On the top edge of a Mac with a camera notch it joins the camera notch. Hover a ring for the detail: which account and plan it belongs to, how old the reading is, and per window a reset time, a bar, and the percentage used.

Configure it in the Usage notch section of the Settings tab: a switch to show or hide it, and while it is on, whether it opens on hover or stays open, which edge it sits on (Top, Left, Right or Bottom), which window is the main ring, whether to show the percentage under each ring (hiding it gives the rings more room, handy on the top edge with several profiles), and a switch per profile. Turning it off and back on keeps your choices. In hover mode it folds to a slim pill at the screen edge and opens when you reach for it (Reduce Motion turns the animation into a jump).

Where the numbers come from. Claude Code already sends your rate-limit windows to ccpm statusline, and ccpm caches them per profile. No API call, no credentials read. The trade-off is that a profile's reading only refreshes when you actually use Claude Code on it — so the callout always tells you how old the number is, and says so plainly once it goes stale. A profile you've never run shows an empty ring and "No reading yet" rather than a misleading 0%, and so does a profile with its own custom statusLine, because only ccpm's status line records the readings. Only subscription plans (Max/Pro) report limits at all; API-key profiles say so instead of pretending.

Install (macOS)

One line in Terminal. It picks the build for your chip, verifies its checksum and puts CCPM in Applications, ready to open:

curl -fsSL https://raw.githubusercontent.com/nitin-1926/claude-code-profile-manager/main/scripts/install-desktop.sh | sh

A downloadable .dmg is coming soon.

Updates are automatic. When a new version ships, the app shows an in-app Update now prompt, downloads it, and swaps itself in place, with nothing to re-download. If it can't replace itself (its folder isn't writable by you, or macOS is running it from a temporary location because it was never moved to Applications), it tells you instead of quitting. The desktop app versions independently of the CLI (released on desktop-v* tags).

The app uses the ccpm CLI for write actions — install it first if you haven't (see Install for the npm / go / curl / source options).

Build from source

go install github.com/wailsapp/wails/v2/cmd/wails@latest   # one-time: the Wails CLI

cd ccpm
make desktop       # builds desktop/build/bin/CCPM.app
make desktop-dev   # hot-reload dev window

Commands

Profile lifecycle

Command Description
ccpm add <name> Create a new profile (interactive: import wizard + OAuth or API key)
ccpm run <name> [...] Launch Claude Code with a profile. Unknown flags forward to claude.
ccpm use <name> Set the profile for the current shell session (requires ccpm shell-init)
ccpm list / ls List profiles with auth status (--json for machine-readable output)
ccpm status [name] System overview, or auth health for one profile (--json)
ccpm rename <old> <new> Rename a profile (migrates keychain entries and plugin paths)
ccpm remove <name> / rm Delete a profile (--force to skip confirm)
ccpm clone <src> <new> Duplicate a profile (assets + settings + auth; --no-auth for assets only)
ccpm set-default [name] Pin the default profile for direct claude launches; macOS sets it system-wide
ccpm unset-default Clear the default
ccpm prompt Print the active profile name for a shell prompt (PS1 / starship / p10k)
ccpm statusline Render the in-TUI status line (active profile + usage/limits); Claude Code calls it
ccpm statusline configure Choose which segments the status line shows, and on which row
ccpm sync Re-apply global installs into one or all profiles (--dry-run to preview)
ccpm doctor Health check: env, auth, drift, symlinks, cascade (--fix prunes dangling symlinks)
ccpm consolidate Audit (and optionally --fix) asset drift across host, share, and profile scopes
ccpm export <name> Export a profile to a portable .tar.gz (credentials excluded by default)
ccpm import-bundle <file> Restore a profile from an export bundle
ccpm shell-init Print the shell hook (auto-detects zsh / bash / fish / powershell)
ccpm completion <shell> Generate a shell completion script (bash / zsh / fish / powershell)
ccpm uninstall Remove all profiles, keychain entries, vault backups, and ~/.ccpm/ (--force skips the prompt)
ccpm version Print the version; --check-latest checks GitHub for a newer CLI release (opt-in, 24h cache)
ccpm usage [name] Token usage from a profile's transcripts (see Usage)
ccpm diff <a> <b> Compare two profiles (assets, settings keys, env names, MCP servers, plugins)

Every command also accepts the global --log-level debug|info|warn|error flag (default warn); debug traces cascade adoption and lock activity to stderr.

ccpm run intercepts five flags before forwarding to claude: --ccpm-env KEY=VAL (one-shot env override, repeatable), --no-auto-adopt (skip the host-asset cascade scan for this launch), --no-statusline (skip injecting the default status line for this launch), --help, --version. Use -- to forward --help or --version to claude.

Assets: skills, agents, commands, rules

All four share the same command shape. Replace skill with agent, command, or rule.

Command Description
ccpm skill add <path> --global Install for every profile
ccpm skill add <path> --profile <p> Install for one profile
ccpm skill remove <name> --global Remove from every profile (alias rm)
ccpm skill link <name> --profile <p> Link a shared asset into a profile
ccpm skill list List installed (alias ls)

Source may be a directory (skills require a SKILL.md marker) or a single file (agents/commands/rules are usually .md). Pass --live-symlink to keep the source linked so edits show up live, or --copy to snapshot it.

Plugins

ccpm installs plugins itself, end-to-end, without a Claude Code session. Marketplaces clone into a shared store, plugin files cache once and symlink into each profile, and per-profile activation lives in the same settings fragment ccpm uses for everything else.

Command Description
ccpm plugin marketplace add <org>/<repo> Clone a marketplace (HTTPS default; --ssh to use git@)
ccpm plugin marketplace list / remove <name> Inspect or drop a marketplace
ccpm plugin install <name>@<marketplace> --global Install into every profile and enable
ccpm plugin install <name>@<marketplace> --profile <p> Install into one profile only
ccpm plugin install ... --install-only Install without enabling
ccpm plugin remove <name>@<marketplace> --global / --profile Remove the plugin from every profile or just one
ccpm plugin list [--profile <p>] Show installed plugins + per-profile enabled state
ccpm plugin enable / disable <name>@<marketplace> --profile <p> Toggle activation
ccpm plugin gc Drop unreferenced shared-cache entries (also part of sync)

Plugin clones default to HTTPS so an https://github.com/<org>/<repo>.git URL works without an SSH key. If you prefer SSH, pass --ssh.

Hooks

Command Description
ccpm hooks add <event> "<cmd>" --profile <p> Append a hook to an event
ccpm hooks add <event> "<cmd>" --matcher <regex> Restrict to a tool-name pattern
ccpm hooks remove <event> --profile <p> [--index] Remove the last entry, or one by index
ccpm hooks list --profile <p> Show merged hooks for a profile

Events: PreToolUse, PostToolUse, UserPromptSubmit, SessionStart, SessionEnd, Notification, Stop, SubagentStop, PreCompact. Hook shell scripts in ~/.claude/hooks/ are managed separately via ccpm import default --only hooks.

MCP servers

Command Description
ccpm mcp add <name> --scope global --command <cmd> Stdio MCP for every profile (ccpm global fragment)
ccpm mcp add <name> --scope profile --profile <p> --command <cmd> Stdio MCP for one profile
ccpm mcp add <name> --scope project --command <cmd> Stdio MCP in the current repo's .mcp.json
ccpm mcp add <name> --transport http --url <url> [--header K=V] Remote HTTP MCP
ccpm mcp add <name> --transport sse --url <url> SSE MCP
ccpm mcp auth <name> --profile <p> Complete OAuth in the profile's scope
ccpm mcp remove <name> --scope <global|profile|project> Remove a server
ccpm mcp import <file.json> --scope ... Import from a JSON file ({mcpServers:{...}})
ccpm mcp list List MCPs with source (--json supported)

--global and --profile <name> are accepted as aliases for --scope global / --scope profile. For --scope project, ccpm discovers the project root by walking up from CWD looking for .claude/settings.json, .claude/settings.local.json, or .mcp.json, or pass --project-dir <path> explicitly.

Permissions

ccpm permissions manages permissions.{allow,ask,deny,defaultMode} in the profile fragment (or with --global, in ~/.claude/settings.json). Adding to one bucket removes from the other two so the lists stay disjoint.

Command Description
ccpm permissions allow "Bash(git status:*)" --profile <p> Add to permissions.allow
ccpm permissions ask "Edit(**/*.md)" --profile <p> Add to permissions.ask
ccpm permissions deny "Bash(rm:*)" --profile <p> Add to permissions.deny
ccpm permissions remove "<rule>" --profile <p> Strip from all three
ccpm permissions list --profile <p> Show all rules + the default mode
ccpm permissions mode <default|acceptEdits|plan|auto|dontAsk|bypassPermissions> --profile <p> Set permissions.defaultMode

Trust

Trust decides what ccpm's own view (ccpm settings show, the desktop app) takes from a repo's .claude/settings.json and settings.local.json. For an untrusted repo it keeps only inert display, editor and model keys. ccpm never writes project settings or MCP servers into a profile: Claude Code reads them from the repo itself, behind its own trust prompt.

Command Description
ccpm trust add [path] Grant trust (defaults to CWD). Alias: grant
ccpm trust remove [path] Revoke trust. Aliases: rm, forget, revoke
ccpm trust list List trusted directories. Alias: ls

Environment variables

ccpm env persists env vars on a profile; they are added to the process env at every ccpm run. A profile entry replaces the same variable inherited from your shell, and --ccpm-env replaces both.

Command Description
ccpm env set KEY=VALUE [KEY=VALUE...] --profile <p> Persist env vars on the profile
ccpm env unset KEY [KEY...] --profile <p> Remove env vars
ccpm env list --profile <p> List persisted env vars
ccpm run <p> --ccpm-env KEY=VALUE (repeatable) One-shot env override at launch time

CLAUDE_CONFIG_DIR and ANTHROPIC_API_KEY are reserved: ccpm always computes them and they cannot be set via ccpm env. Use --ccpm-env for a one-shot override.

Sessions

Command Description
ccpm sessions list <profile> Sessions scoped to the current working directory, most recently active first (--limit N, --json)
ccpm sessions list <profile> --all Sessions from every project the profile has worked on

The table shows each session's LAST ACTIVE time. --json gives started (the first timestamp) and last_active. Subagent transcripts are not listed.

Usage

ccpm usage [profile] reports token usage read from the profile's Claude Code transcripts: raw input, output, cache-write and cache-read counts, no dollar cost. On a terminal it opens an interactive dashboard (Overview / Days / Models / Projects / Sessions tabs; [ / ] switch profile, w cycles the time window, q quits). With no name it uses the active profile, then the default.

Command Description
ccpm usage [profile] --plain Static report: totals, a contribution heatmap, and a per-model breakdown
ccpm usage --by-model / --by-project / --sessions Swap the report body
ccpm usage --since 30d Limit to a window: duration (168h), days (30d), or date (2026-06-01)
ccpm usage --all Aggregate every profile
ccpm usage --json Machine-readable output

Any of those flags prints a static report instead of the dashboard. Each run reads only the transcript bytes added since the last one. To keep the store warm between runs, ccpm config set usage_tracking true injects a SessionEnd hook (ccpm usage sync) into launched profiles; ccpm usage is accurate without it.

Import

Command Description
ccpm import default --profile <p> Interactive: import skills/commands/hooks/agents/rules/settings/MCP/plugins
ccpm import default --all --only skills Non-interactive: import specific targets into every profile
ccpm import default --profile <p> --no-share Copy directly instead of symlinking from the shared store
ccpm import from-profile --src <a> --profile <b> Clone assets from one ccpm profile into another

--only accepts a comma-separated list of: skills, commands, rules, hooks, agents, settings, mcp, plugins.

Backup & migrate profiles

ccpm export bundles a profile (its skills, agents, commands, rules and hooks, its settings and MCP fragments, and plugin metadata) into a single .tar.gz you can copy to another machine and restore with ccpm import-bundle. Session history (transcripts, todos, shell snapshots) is left out unless you pass --include-history. ccpm clone duplicates a profile in place.

Command Description
ccpm export <name> [-o file.tar.gz] Export a profile to a portable bundle (credentials excluded by default)
ccpm export <name> --include-history Include session history (may contain secrets you pasted into a session)
ccpm export <name> --include-credentials Include credential files (sensitive, trusted same-user moves only)
ccpm import-bundle <file.tar.gz> [--profile <p>] Restore a profile from a bundle (path-traversal-safe; --profile overrides the name)
ccpm import-bundle <file.tar.gz> --trust-bundle Restore hooks, MCP servers, command settings and env without asking (needed without a terminal)
ccpm clone <src> <new> Duplicate a profile, copying assets, settings, and credentials
ccpm clone <src> <new> --no-auth Clone assets/settings only; leave the new profile unauthenticated

Credentials are NOT included in an export by default: OS-keychain tokens are machine-bound, and .credentials.json / .claude.json hold secrets you usually don't want in a shareable file. Use --include-credentials only for a trusted same-user move (e.g. a Linux machine migration). When a restored or cloned profile has no credentials, authenticate it afterward with ccpm auth refresh <name>.

Treat a bundle from someone else as untrusted code. Before restoring, ccpm import-bundle lists every hook, MCP server, command-running setting and env key in it and asks you to confirm.

OAuth clone caveat: a cloned OAuth profile shares the source account's tokens, so when Claude rotates the refresh token in one, the other goes stale. For a clone you'll use long-term against the same account, prefer --no-auth and run ccpm auth refresh <new> to give it its own login. API-key clones have no such caveat.

Shell completion

ccpm completion <shell> prints a completion script for bash, zsh, fish, or powershell, with completion for profile names on run / use / remove / rename / set-default / clone / export.

# zsh: load on every new shell
echo 'source <(ccpm completion zsh)' >> ~/.zshrc

# bash
echo 'source <(ccpm completion bash)' >> ~/.bashrc

# fish
ccpm completion fish > ~/.config/fish/completions/ccpm.fish

Run ccpm completion <shell> --help for the per-shell install details.

Shell prompt

ccpm prompt prints the active profile name so you can show which Claude Code account a terminal is bound to. It resolves $CCPM_ACTIVE_PROFILE (set by ccpm use), then $CLAUDE_CONFIG_DIR, then — only with --show-default — the configured default. It prints nothing (exit 0) when no profile is active, so it stays quiet in non-ccpm shells.

# bash/zsh PS1
PS1='$(ccpm prompt --format "[ccpm:%s] ")'"$PS1"

# starship custom command
command = "ccpm prompt"

Status line (which profile is running, plus usage/limits)

Where ccpm prompt feeds your shell prompt, ccpm statusline feeds the Claude Code TUI — two rows pinned to the bottom of the session window. Row 1 is the session; row 2 is the budget:

⬢ work · claude-code-profile-manager/ccpm · ⎇ feat/history-tab · Opus 5 · ctx 34%
effort high · 5h 58% ↺16:15 · 7d 88% ↺Mon 8 Sep 08:25 · $1.23

The 5h / 7d segments are the percentage used of your rolling subscription usage windows, matching Claude's own /usage panel (Pro/Max only — Claude Code supplies them; they appear after the first response and are absent for API-key profiles). effort appears only for models that report a reasoning-effort level. A renewal falling on today's date shows a bare clock; one on any other date gives the day and date. Segments drop out when their data is absent and an empty row is not printed, so an API-key profile outside a git repo prints ⬢ work · myproject · Opus 4.8 and $0.12 — no branch and no usage windows, but the directory still shows.

ccpm run wires this in automatically: when a profile has no statusLine of its own, it injects ccpm statusline as the profile's status line. It never overwrites a status line you set in ~/.claude/settings.json, a profile, or a trusted project. To opt out:

ccpm config set statusline false          # persistently, all profiles
ccpm run work --no-statusline             # just this launch
ccpm settings statusline "" --profile work  # remove one ccpm already injected

Choosing the segments

ccpm statusline configure picks which of the nine segments show, and on which row. It asks for row 1, then row 2 from what is left; anything unpicked is switched off. The result is printed against sample data so you can see it straight away. You are also offered it the first time you run ccpm config set statusline true.

ccpm statusline configure                 # global default, interactive
ccpm statusline configure --profile work  # an override for one profile
ccpm statusline configure --reset         # back to the built-in layout

A profile's override wins over the global default; --reset --profile work drops it again. The segment keys are profile, workspace, branch, model, context, effort, five_hour, seven_day, cost, and scripts can skip the prompts by naming all nine across three flags:

ccpm statusline configure --row1 profile,model --row2 five_hour,seven_day \
                          --off workspace,branch,context,effort,cost

Order within a row is yours too: the desktop app has move-up/move-down controls per segment, --row1 a,b,c renders in the order given, and the picker keeps an order you already set rather than re-sorting it. Layouts live in ~/.ccpm/config.json (settings.statusline, or profiles.<name>.statusline). The desktop app's Settings tab has the same controls with a live preview.

Turning a segment on never invents data — enabling the 5h window on an API-key profile still shows nothing. Turning branch off also stops ccpm reading .git/HEAD on every assistant message.

Settings

ccpm does not maintain its own global settings layer. The cross-profile baseline is ~/.claude/settings.json (the file native Claude Code reads); ccpm merges it into every profile at launch. Use --profile for per-account overrides, and per-repo .claude/settings.json for project overrides.

Command Description
ccpm settings set <key> <value> --profile <p> Set a setting for one profile (dot notation, JSON values)
ccpm settings get <key> --profile <p> Read the effective value
ccpm settings apply <file.json> --profile <p> Deep-merge a JSON fragment
ccpm settings show --profile <p> Dump the fully-merged settings
ccpm settings statusline "<cmd>" --profile <p> Set the native statusLine block. Empty "" removes it.
ccpm settings outputstyle <style> --profile <p> Set outputStyle (allowlist: default, Build, Explanatory, Learning, Direct)

Authentication

Command Description
ccpm auth status Credential validity across profiles
ccpm auth refresh <name> Re-authenticate a profile
ccpm auth backup <name> Create an encrypted credential backup
ccpm auth restore <name> Restore credentials from backup

Drift fingerprint

ccpm import default snapshots the files under ~/.claude it touched. Later you can ask whether the host config has drifted:

Command Description
ccpm default fingerprint update Record the current ~/.claude state as the baseline
ccpm default fingerprint check Diff ~/.claude against the baseline; suggests import
ccpm config set check_default_drift true Show drift notifications on ccpm run / ccpm use

Config

Command Description
ccpm config set cascade_auto_adopt false Disable the host-asset cascade
ccpm config set check_default_drift true Enable drift notifications on run/use
ccpm config set statusline false Disable default status-line auto-injection
ccpm statusline configure Pick the status line's segments and rows
ccpm config set usage_tracking true Keep the usage store warm via a SessionEnd hook
ccpm config get default_dir Print the default profile's absolute path

Exit codes

For scripting: 0 success (warnings may print to stderr but never change the exit code), 1 command failed, 3 partial failure (ccpm import or ccpm sync did some of its work but at least one step failed), 4 ccpm doctor found health issues.

How it works

ccpm run <profile> materializes the profile (merges settings + MCP fragments, links host-cascaded assets), sets CLAUDE_CONFIG_DIR to the profile dir, and execs claude. Each terminal gets a completely isolated Claude Code instance. No daemons, no patches.

Asset resolution (higher wins)

  1. Project: <repo>/.claude/<asset>/ and <repo>/.claude/settings.json. Native Claude Code's loader already gives this priority.
  2. Profile: assets installed via ccpm <asset> add --profile <name>.
  3. Global: assets installed via ccpm <asset> add --global (live in ~/.ccpm/share/).
  4. Host cascade: anything in ~/.claude/<asset>/ (a /plugin install inside a session, npx skills add, manual edits) auto-adopts into every profile at launch. Opt out with ccpm config set cascade_auto_adopt false or --no-auto-adopt on run / sync. Profile-local always wins over host; ccpm doctor flags shadowed names.

Settings merge order (lowest to highest)

ccpm writes layers 1 to 4 into the profile's settings.json. Layers 5 to 7 are applied by Claude Code itself from the project and system directories; ccpm shows them in ccpm settings show but never writes them into the profile.

  1. Keys already in <profile>/settings.json that ccpm didn't write (what Claude Code or you set in a session, such as /model).
  2. ~/.claude/settings.json (shared baseline; edit directly to change every profile).
  3. ~/.ccpm/share/settings/<profile>.json (ccpm per-profile fragment).
  4. Owned-keys sidecar (values explicitly set via ccpm settings set --profile; protects them from being silently overwritten).
  5. ./.claude/settings.json at the project root.
  6. ./.claude/settings.local.json at the project root (gitignored local overrides).
  7. Enterprise managed-settings (/Library/Application Support/ClaudeCode/managed-settings.json on macOS; /etc/claude-code/managed-settings.json on Linux; C:\Program Files\ClaudeCode\managed-settings.json on Windows; plus managed-settings.d/*.json drop-ins merged alphabetically). Highest precedence so org policy always wins.

MCP merge order: host ~/.claude.json#mcpServers → ccpm global fragment → ccpm profile fragment. Claude Code reads project .mcp.json itself.

Removing a setting or MCP server at its source (ccpm mcp remove, ccpm settings unset, deleting it from ~/.claude/settings.json) removes it from the profile on the next launch; values you or Claude Code changed in the profile are kept.

Objects merge key-by-key; arrays and scalars from a higher-precedence source replace the lower one.

Directory layout

~/.ccpm/
├── config.json          # profile registry, config keys, status line layouts
├── installs.json        # manifest of installed assets (skill/agent/command/rule/hook/mcp/plugin)
│                          # entries carry scope: global | profile | host
├── share/
│   ├── skills/          # shared skills (symlinked into profiles)
│   ├── agents/ commands/ rules/ hooks/
│   ├── mcp/             # MCP fragments: global.json + <profile>.json
│   └── settings/        # per-profile settings fragments
├── profiles/
│   ├── personal/        # CLAUDE_CONFIG_DIR for "personal"
│   └── work/
└── vault/               # encrypted credential backups

Privacy and security

ccpm is 100% local. It never collects data or phones home, and the CLI makes no network requests except the opt-in ccpm version --check-latest release check. The optional desktop app only contacts GitHub to check for its own updates.

  • API keys live in the OS keychain (macOS Keychain, Linux Secret Service, Windows Credential Manager).
  • Vault backups use AES-256-GCM with a master key in your OS keychain.
  • All data lives in ~/.ccpm/. No telemetry, no analytics, no tracking.

Platform support

macOS is the verified platform today. Linux and Windows builds compile, install, and run, but the OAuth-isolation paths (set-default, auth backup/restore, keychain-based status) are experimental until exercised against a real Linux Secret Service or Windows Credential Manager install.

Feature macOS ✓ verified Windows ⚠ experimental Linux ⚠ experimental
OAuth per-profile Keychain entry namespaced by profile dir wincred entry namespaced by profile dir (theoretical) .credentials.json
API key storage Keychain Credential Manager Secret Service
Parallel sessions Yes Yes Yes
Shared asset dedup Symlinks Symlinks (Developer Mode) or copy1 Symlinks
Shell hook zsh, bash, fish PowerShell zsh, bash, fish
set-default (system-wide GUI) LaunchAgent + launchctl setenv Keychain / identity only Keychain / identity only

Requires Claude Code v2.1.56+ on macOS for OAuth isolation. Earlier versions share a single keychain entry across profiles. ccpm doctor warns when your Claude Code is too old.

MCP authentication model

How an MCP server authenticates determines whether ccpm can isolate it per profile.

  1. Env-var MCPs (e.g. GITHUB_TOKEN): isolated. Stored in the per-profile fragment at ~/.ccpm/share/mcp/<profile>.json. Configure with ccpm mcp add <name> --env KEY=VALUE --profile <p>.
  2. OAuth MCPs (open a browser, cache tokens in .claude.json#mcpOAuth): isolated, because CLAUDE_CONFIG_DIR is per-profile.
  3. Globally-cached MCPs (servers that write to ~/.config/<service> or a non-namespaced OS keychain entry): shared across profiles. ccpm cannot isolate them without cooperation from the server.

Known limitations

  • IDE extensions ignore CLAUDE_CONFIG_DIR: VS Code, Cursor, and Antigravity launch claude directly without going through a shell. Use ccpm set-default <profile> to pin one for them. On macOS this is system-wide via a LaunchAgent; on Linux and Windows the keychain and identity sync apply but the system-wide env mechanism is not yet implemented (terminal use is covered by ccpm shell-init).
  • Headless Linux: go-keyring requires D-Bus and a secret service (gnome-keyring or kwallet). API-key profiles on headless servers need a running secret service.
  • Globally-cached MCP servers cannot be isolated per profile (see the MCP auth section).
  • macOS keychain logins over about 4 KB (possible once several MCP servers store OAuth tokens in the login) can't be written by set-default, clone, rename or auth restore. ccpm stops with an error and leaves the keychain unchanged.

Troubleshooting / FAQ

/plugin install fails with git@github.com: Permission denied (publickey)

Claude Code clones plugin marketplaces over SSH by default. If you authenticate over HTTPS only, force git to rewrite SSH URLs to HTTPS:

git config --global url."https://github.com/".insteadOf "git@github.com:"

This applies to every tool that uses git.

"Keychain access denied" / repeated keychain prompts

ccpm stores credentials in the OS keychain (macOS Keychain, Linux Secret Service, Windows Credential Manager). If access is denied, allow ccpm (and claude) to read the relevant keychain items when prompted. On macOS you can inspect the namespaced entry name via ccpm doctor.

"Claude Code too old (needs 2.1.56+)" on macOS OAuth

Per-profile OAuth isolation on macOS needs Claude Code v2.1.56+ (older builds share a single keychain entry across profiles). Run ccpm doctor to see your installed version, then update Claude Code.

Broken or dangling symlinks

Run ccpm doctor to detect them, then ccpm doctor --fix to prune dangling shared-asset symlinks. For deeper asset drift across host, share, and profile scopes, use ccpm consolidate --fix.

My IDE still uses the wrong account

After ccpm set-default <profile>, already-running IDE windows (VS Code, Cursor, Antigravity) need one restart to pick up the new CLAUDE_CONFIG_DIR. Future launches and logins are automatic.

Running sessions don't see my change

ccpm set-default, ccpm rename, and ccpm remove don't affect already-running Claude Code sessions. Restart the session (and any IDE window) to pick up the change.

Uninstall

ccpm uninstall removes every profile, deletes API keys and OAuth logins from the OS keychain, wipes vault backups and the vault key, clears the system default set by ccpm set-default, and deletes ~/.ccpm/. Without a terminal it needs --force. It does not remove the ccpm binary itself or the shell hook line 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 --force

Releasing

scripts/release.sh handles the end-to-end release (bump, verify, tag, GitHub Release, npm publish) with preflight checks (git/go/node/npm/gh on PATH, on main, clean tree, in sync with origin/main, logged in to gh and npm, target tag unused). Run ./scripts/release.sh --help for the full flag list (patch/minor/major/explicit version, --dry-run, --stash, --allow-dirty, --skip-tests, --skip-npm, -y).

Contributing

Contributions welcome. Open an issue first to discuss what you want to change.

  1. Fork the repo.
  2. Create a feature branch (git checkout -b feature/your-feature).
  3. Make your changes and run the tests (cd ccpm && go test ./...).
  4. Open a pull request.

License

MIT

Author

Built by Nitin Gupta.

Footnotes

  1. Without Developer Mode or admin, Windows cannot create symlinks; ccpm falls back to copying and writes a marker at ~/.ccpm/.windows-copy-fallback. Turn on Developer Mode for true deduplication. ↩

About

Manage multiple claude code accounts with ease. Switching context has never been easier

Resources

Code of conduct

Contributing

Security policy

Stars

8 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages