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:
2026-09-07 15:13:38 +07:00
parent 17d56d0e01
commit 4bc77ded17
13 changed files with 1496 additions and 3 deletions
+23
View File
@@ -215,6 +215,29 @@
"scopeLabel": "Scope",
"filterAll": "All types"
},
"profiles": {
"title": "Claude Code profiles",
"openButton": "Switch profile",
"subtitle": "A profile is one Anthropic account - its own quota. Switching swaps only the login (credentials + account identity); skills, subagents, memory, plugins, marketplaces, MCP servers and settings all stay exactly as they are.",
"warning": "Close your running Claude Code sessions before switching - a live session rewrites the config on exit and would put the old account back.",
"activeLabel": "Logged in now",
"noActive": "No Claude account is logged in on this machine.",
"unsavedActive": "This account is not saved as a profile yet - save it below, otherwise you cannot switch away without losing its credentials.",
"saveLabel": "Save the current account as a profile",
"namePlaceholder": "profile name (e.g. work)",
"saveButton": "Save",
"saveHint": "The dashboard cannot log in for you. To add another account: run `claude auth login` in a terminal, sign in, then come back and save it under a new name.",
"saveSuccess": "Saved the current account as profile \"{{name}}\".",
"empty": "No profiles saved yet - save the account you are logged in as to get started.",
"activeBadge": "active",
"switchButton": "Switch to",
"switching": "Switching…",
"switchSuccess": "Switched to profile \"{{name}}\". Start a new Claude Code session to pick it up.",
"deleteButton": "Delete profile",
"deleteSuccess": "Deleted profile \"{{name}}\".",
"savedAt": "Saved {{time}}",
"loginHint": "Add another account by logging in from a terminal first:"
},
"explain": {
"hooks": {
"title": "Editing hooks writes settings.json",
+23
View File
@@ -210,6 +210,29 @@
"scopeLabel": "Phạm vi",
"filterAll": "Tất cả loại"
},
"profiles": {
"title": "Profile Claude Code",
"openButton": "Đổi profile",
"subtitle": "Mỗi profile là một tài khoản Anthropic - quota riêng. Khi đổi, chỉ phần đăng nhập (credentials + danh tính tài khoản) được hoán đổi; skills, subagent, memory, plugin, marketplace, MCP server và settings vẫn giữ nguyên.",
"warning": "Hãy đóng các phiên Claude Code đang chạy trước khi đổi - phiên đang chạy sẽ ghi lại config khi thoát và đưa tài khoản cũ quay về.",
"activeLabel": "Đang đăng nhập",
"noActive": "Chưa có tài khoản Claude nào đăng nhập trên máy này.",
"unsavedActive": "Tài khoản này chưa được lưu thành profile - hãy lưu bên dưới, nếu không sẽ không thể đổi sang profile khác mà không mất credentials của nó.",
"saveLabel": "Lưu tài khoản hiện tại thành profile",
"namePlaceholder": "tên profile (vd: work)",
"saveButton": "Lưu",
"saveHint": "Dashboard không thể đăng nhập thay bạn. Để thêm tài khoản khác: chạy `claude auth login` trong terminal, đăng nhập, rồi quay lại đây lưu với tên mới.",
"saveSuccess": "Đã lưu tài khoản hiện tại thành profile \"{{name}}\".",
"empty": "Chưa có profile nào - hãy lưu tài khoản đang đăng nhập để bắt đầu.",
"activeBadge": "đang dùng",
"switchButton": "Chuyển sang",
"switching": "Đang chuyển…",
"switchSuccess": "Đã chuyển sang profile \"{{name}}\". Mở phiên Claude Code mới để áp dụng.",
"deleteButton": "Xoá profile",
"deleteSuccess": "Đã xoá profile \"{{name}}\".",
"savedAt": "Lưu lúc {{time}}",
"loginHint": "Thêm tài khoản khác bằng cách đăng nhập từ terminal trước:"
},
"explain": {
"hooks": {
"title": "Sửa hooks tức là ghi vào settings.json",