feat(settings): every AI tool's login profiles in one Settings section, linked to the account pool
Settings → AI tools → Profiles holds one tab per tool: Claude Code (the cc-profiles panel, moved out of the Claude Code page's modal — its button now links here), Codex (the whole $CODEX_HOME/auth.json) and Oh My Pi (`omp auth export` / `omp auth import -`, secrets on stdin; omp's credential tables are emptied first because import merges, and a failed import puts the outgoing set back). Antigravity is listed as not switchable: agy keeps its login in the OS keyring. server/lib/tool-profiles.js + /api/tool-profiles keep cc-profiles' rules (one login = one profile, the outgoing login is re-saved before a switch, an unsaved one refuses it, the live login's profile is not deletable) and never return a token; /tool-profiles joins ADMIN_WRITE_PREFIXES and STAFF_ONLY_PREFIXES, writes are same-origin only. The account pool takes a saved Codex profile too (moved in like a Claude one, GET /api/accounts/profiles?tool=codex). Each Claude/Codex profile has a "Move to pool" link (/admin?poolAdd=&poolProfile= opens the pool's Add dialog on it), Settings shows the pool's size, and the pool card links back.
This commit is contained in:
@@ -22,7 +22,9 @@
|
||||
* restart (`switched`); `PUT /:name/users` (`{tool, userIds}`) is the
|
||||
* Users page's Edit of one account — exactly those users run on it; `GET /`
|
||||
* lists the pins under `pins`.
|
||||
* `GET /profiles` lists the saved profiles that can be moved in (identity only). Like every Users-page change
|
||||
* A Codex account may also be a saved Codex `profile` (tool-profiles.js), moved
|
||||
* in the same way. `GET /profiles[?tool=codex]` lists the saved profiles that
|
||||
* can be moved in (identity only). Like every Users-page change
|
||||
* they need the admin's own password again (`currentPassword`), are audited
|
||||
* (the account NAME only), and refuse in local mode. The secret only goes IN:
|
||||
* no route answers with it (`/accounts` is in ADMIN_WRITE_PREFIXES).
|
||||
@@ -35,6 +37,7 @@ const codexPool = require("../lib/codex-pool");
|
||||
const auth = require("../lib/auth");
|
||||
const { recordAccountChange } = require("../lib/audit");
|
||||
const profiles = require("../lib/cc-profiles");
|
||||
const toolProfiles = require("../lib/tool-profiles");
|
||||
const pins = require("../lib/account-pins");
|
||||
|
||||
const router = Router();
|
||||
@@ -57,8 +60,19 @@ router.get("/", async (req, res) => {
|
||||
});
|
||||
|
||||
/** Saved switch-user profiles an admin may move into the pool — never a token. */
|
||||
router.get("/profiles", (_req, res) => {
|
||||
router.get("/profiles", (req, res) => {
|
||||
try {
|
||||
if (req.query.tool === "codex") {
|
||||
return res.json({
|
||||
profiles: toolProfiles.listProfiles("codex").items.map((p) => ({
|
||||
name: p.name,
|
||||
label: p.label,
|
||||
email: p.email,
|
||||
subscriptionType: p.detail,
|
||||
active: p.active,
|
||||
})),
|
||||
});
|
||||
}
|
||||
res.json({
|
||||
profiles: profiles.listProfiles().items.map((p) => ({
|
||||
name: p.name,
|
||||
@@ -185,6 +199,20 @@ function fromProfile(name, poolName) {
|
||||
return account;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move a saved Codex profile (Settings → AI tools → Profiles) into the pool.
|
||||
* The profile file goes, as for Claude: the pool is now the login's holder.
|
||||
* The host's own CODEX_HOME is left as it is — Codex's old refresh token still
|
||||
* refreshes (codex-pool.js), so the host keeps working on it.
|
||||
*/
|
||||
function fromCodexProfile(name, poolName) {
|
||||
const stored = toolProfiles.readProfile("codex", name);
|
||||
if (!stored) throw Object.assign(new Error(`no saved profile ${name}`), { code: "ENOTFOUND" });
|
||||
const account = codexPool.addAccount(poolName || name, { auth: stored.secret });
|
||||
toolProfiles.dropProfile("codex", name);
|
||||
return account;
|
||||
}
|
||||
|
||||
/** Codex takes an API key, or the text of an auth.json (`codex login`'s). */
|
||||
function codexSecret(token) {
|
||||
const text = String(token || "").trim();
|
||||
@@ -203,7 +231,9 @@ router.post(
|
||||
poolChange(async (_req, body) => {
|
||||
const account =
|
||||
body.tool === "codex"
|
||||
? codexPool.addAccount(body.name, codexSecret(body.token))
|
||||
? body.profile
|
||||
? fromCodexProfile(String(body.profile), body.name)
|
||||
: codexPool.addAccount(body.name, codexSecret(body.token))
|
||||
: body.profile
|
||||
? fromProfile(String(body.profile), body.name)
|
||||
: body.credentials
|
||||
|
||||
Reference in New Issue
Block a user