feat(cc-config): switch between Claude Code accounts, keeping the workspace
Adds a "Switch profile" button left of Backups on /settings?tool=claude-code. A *profile* is one Anthropic account - one quota - saved under a name, so the local `claude` CLI can be pointed at a different account without disturbing anything else. A switch swaps exactly two identity artifacts and nothing more: `claudeAiOauth` inside `<CLAUDE_HOME>/.credentials.json`, and `oauthAccount` + `userID` inside the `.claude.json` config file(s). Those files are key-merged in place, and only where they already exist, so `projects`/`numStartups`/everything else survives. Skills, subagents, commands, memory, plugins, marketplaces, MCP servers and settings are never read or written here - that is what makes a switch keep the whole workspace and change only the account. `mcpOAuth` stays put too: those are the machine's MCP server tokens, not account identity. Profiles are one JSON file each under `<CLAUDE_HOME>/agenthub-profiles/` (dir 0700, files 0600, atomic temp+rename) and hold live OAuth tokens, so no route ever returns token material - `GET /profiles` answers identity metadata only. The dashboard cannot log an account in; only `claude auth login` mints tokens. Adding a profile is therefore always "log in from a terminal, then snapshot what the CLI wrote". `activateProfile` re-snapshots the outgoing account first so tokens Claude Code refreshed since its last save are not lost, and refuses (`EUNSAVED`) to switch away from an account that was never saved - that would discard the only copy of its credentials. A switch applies to new sessions only, since a running session rewrites the config on exit. Routes: GET/POST/DELETE /api/cc-config/profiles, POST /profiles/activate. Verified: server 1420/1420 (14 new, incl. token-free responses and 0600/0700 permissions), client 459/459.
This commit is contained in:
@@ -795,6 +795,61 @@ exports[`screen snapshots > Claude Config 1`] = `
|
||||
Project
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
class="inline-flex items-center gap-2 rounded-lg border border-border bg-surface-2 px-3 py-1.5 text-xs font-medium text-fg-secondary hover:bg-surface-3 transition-colors"
|
||||
>
|
||||
<svg
|
||||
class="lucide lucide-user-cog w-3.5 h-3.5"
|
||||
fill="none"
|
||||
height="24"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<circle
|
||||
cx="18"
|
||||
cy="15"
|
||||
r="3"
|
||||
/>
|
||||
<circle
|
||||
cx="9"
|
||||
cy="7"
|
||||
r="4"
|
||||
/>
|
||||
<path
|
||||
d="M10 15H6a4 4 0 0 0-4 4v2"
|
||||
/>
|
||||
<path
|
||||
d="m21.7 16.4-.9-.3"
|
||||
/>
|
||||
<path
|
||||
d="m15.2 13.9-.9-.3"
|
||||
/>
|
||||
<path
|
||||
d="m16.6 18.7.3-.9"
|
||||
/>
|
||||
<path
|
||||
d="m19.1 12.2.3-.9"
|
||||
/>
|
||||
<path
|
||||
d="m19.6 18.7-.4-1"
|
||||
/>
|
||||
<path
|
||||
d="m16.8 12.3-.4-1"
|
||||
/>
|
||||
<path
|
||||
d="m14.3 16.6 1-.4"
|
||||
/>
|
||||
<path
|
||||
d="m20.7 13.8 1-.4"
|
||||
/>
|
||||
</svg>
|
||||
Switch profile
|
||||
</button>
|
||||
<button
|
||||
class="inline-flex items-center gap-2 rounded-lg border border-border bg-surface-2 px-3 py-1.5 text-xs font-medium text-fg-secondary hover:bg-surface-3 transition-colors"
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user