From eebcb7b10351ce9c939ffcc769d354bc463a8ee1 Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Fri, 2 Jan 2026 19:48:12 -0500 Subject: [PATCH] fix(accounts): integrate CLIProxy OAuth accounts into API endpoint - Add CLIProxy accounts to GET /api/accounts (was only showing legacy/unified) - Use unique ID for key to prevent collision between accounts with same nickname - Skip accounts with missing identifier - Route set-default to CLIProxy account manager for provider:id format - Route delete to CLIProxy account manager for provider:id format --- src/web-server/routes/account-routes.ts | 81 ++++++++++++++++++++++++- 1 file changed, 78 insertions(+), 3 deletions(-) diff --git a/src/web-server/routes/account-routes.ts b/src/web-server/routes/account-routes.ts index 686c604f..841855b2 100644 --- a/src/web-server/routes/account-routes.ts +++ b/src/web-server/routes/account-routes.ts @@ -8,10 +8,29 @@ import { Router, Request, Response } from 'express'; import ProfileRegistry from '../../auth/profile-registry'; import { isUnifiedMode } from '../../config/unified-config-loader'; +import { + getAllAccountsSummary, + setDefaultAccount as setCliproxyDefault, + removeAccount as removeCliproxyAccount, +} from '../../cliproxy/account-manager'; +import { CLIProxyProvider } from '../../cliproxy/types'; const router = Router(); const registry = new ProfileRegistry(); +/** Parse CLIProxy account key format: "provider:accountId" */ +function parseCliproxyKey(key: string): { provider: CLIProxyProvider; accountId: string } | null { + const providers: CLIProxyProvider[] = ['gemini', 'codex', 'agy', 'qwen', 'iflow', 'kiro', 'ghcp']; + const colonIndex = key.indexOf(':'); + if (colonIndex === -1) return null; + + const provider = key.slice(0, colonIndex) as CLIProxyProvider; + const accountId = key.slice(colonIndex + 1); + + if (!providers.includes(provider) || !accountId) return null; + return { provider, accountId }; +} + /** * GET /api/accounts - List accounts from both profiles.json and config.yaml */ @@ -21,8 +40,20 @@ router.get('/', (_req: Request, res: Response): void => { const legacyProfiles = registry.getAllProfiles(); const unifiedAccounts = registry.getAllAccountsUnified(); + // Get CLIProxy OAuth accounts (gemini, codex, agy, etc.) + const cliproxyAccounts = getAllAccountsSummary(); + // Merge profiles: unified config takes precedence - const merged: Record = {}; + const merged: Record< + string, + { + type: string; + created: string; + last_used: string | null; + provider?: string; + displayName?: string; + } + > = {}; // Add legacy profiles first for (const [name, meta] of Object.entries(legacyProfiles)) { @@ -42,6 +73,26 @@ router.get('/', (_req: Request, res: Response): void => { }; } + // Add CLIProxy OAuth accounts + for (const [provider, accounts] of Object.entries(cliproxyAccounts)) { + for (const acct of accounts) { + // Skip accounts with no valid identifier + if (!acct.id) { + continue; + } + // Use unique ID for key to prevent collisions between accounts with same nickname/email + const displayName = acct.nickname || acct.email || acct.id; + const key = `${provider}:${acct.id}`; + merged[key] = { + type: 'cliproxy', + provider, + displayName, + created: acct.createdAt || new Date().toISOString(), + last_used: null, + }; + } + } + // Convert to array format const accounts = Object.entries(merged).map(([name, meta]) => ({ name, @@ -69,6 +120,18 @@ router.post('/default', (req: Request, res: Response): void => { return; } + // Check if this is a CLIProxy account (format: "provider:accountId") + const cliproxyKey = parseCliproxyKey(name); + if (cliproxyKey) { + const success = setCliproxyDefault(cliproxyKey.provider, cliproxyKey.accountId); + if (!success) { + res.status(404).json({ error: `CLIProxy account not found: ${name}` }); + return; + } + res.json({ default: name }); + return; + } + // Use unified config if in unified mode, otherwise use legacy if (isUnifiedMode()) { registry.setDefaultUnified(name); @@ -110,7 +173,7 @@ router.delete('/:name', (req: Request, res: Response): void => { return; } - // Check if trying to delete default + // Check if trying to delete default (for non-CLIProxy accounts) const currentDefault = registry.getDefaultUnified() ?? registry.getDefaultProfile(); if (name === currentDefault) { res @@ -119,7 +182,19 @@ router.delete('/:name', (req: Request, res: Response): void => { return; } - // Delete the profile + // Check if this is a CLIProxy account (format: "provider:accountId") + const cliproxyKey = parseCliproxyKey(name); + if (cliproxyKey) { + const success = removeCliproxyAccount(cliproxyKey.provider, cliproxyKey.accountId); + if (!success) { + res.status(404).json({ error: `CLIProxy account not found: ${name}` }); + return; + } + res.json({ success: true, deleted: name }); + return; + } + + // Delete the profile (legacy/unified) registry.deleteProfile(name); res.json({ success: true, deleted: name });