diff --git a/src/web-server/routes/account-routes.ts b/src/web-server/routes/account-routes.ts new file mode 100644 index 00000000..848383f5 --- /dev/null +++ b/src/web-server/routes/account-routes.ts @@ -0,0 +1,68 @@ +/** + * Account Routes - CRUD operations for Claude accounts (profiles.json) + * + * Separated from profile-routes.ts to avoid dual-mounting conflicts. + */ + +import { Router, Request, Response } from 'express'; +import * as fs from 'fs'; +import * as path from 'path'; +import { getCcsDir } from '../../utils/config-manager'; + +const router = Router(); + +/** + * GET /api/accounts - List accounts from profiles.json + */ +router.get('/', (_req: Request, res: Response): void => { + try { + const profilesPath = path.join(getCcsDir(), 'profiles.json'); + + if (!fs.existsSync(profilesPath)) { + res.json({ accounts: [], default: null }); + return; + } + + const data = JSON.parse(fs.readFileSync(profilesPath, 'utf8')); + const accounts = Object.entries(data.profiles || {}).map(([name, meta]) => { + const metadata = meta as Record; + return { + name, + ...metadata, + }; + }); + + res.json({ accounts, default: data.default || null }); + } catch (error) { + res.status(500).json({ error: (error as Error).message }); + } +}); + +/** + * POST /api/accounts/default - Set default account + */ +router.post('/default', (req: Request, res: Response): void => { + try { + const { name } = req.body; + + if (!name) { + res.status(400).json({ error: 'Missing required field: name' }); + return; + } + + const profilesPath = path.join(getCcsDir(), 'profiles.json'); + + const data = fs.existsSync(profilesPath) + ? JSON.parse(fs.readFileSync(profilesPath, 'utf8')) + : { profiles: {} }; + + data.default = name; + fs.writeFileSync(profilesPath, JSON.stringify(data, null, 2) + '\n'); + + res.json({ default: name }); + } catch (error) { + res.status(500).json({ error: (error as Error).message }); + } +}); + +export default router; diff --git a/src/web-server/routes/cliproxy-stats-routes.ts b/src/web-server/routes/cliproxy-stats-routes.ts index c1b4a676..00917d40 100644 --- a/src/web-server/routes/cliproxy-stats-routes.ts +++ b/src/web-server/routes/cliproxy-stats-routes.ts @@ -19,10 +19,9 @@ import { checkCliproxyUpdate } from '../../cliproxy/binary-manager'; const router = Router(); /** - * GET /api/cliproxy/stats - Get CLIProxyAPI usage statistics - * Returns: CliproxyStats or error if proxy not running + * Shared handler for stats/usage endpoint */ -router.get('/stats', async (_req: Request, res: Response): Promise => { +const handleStatsRequest = async (_req: Request, res: Response): Promise => { try { // Check if proxy is running first const running = await isCliproxyRunning(); @@ -48,7 +47,18 @@ router.get('/stats', async (_req: Request, res: Response): Promise => { } catch (error) { res.status(500).json({ error: (error as Error).message }); } -}); +}; + +/** + * GET /api/cliproxy/stats - Get CLIProxyAPI usage statistics + * Returns: CliproxyStats or error if proxy not running + */ +router.get('/stats', handleStatsRequest); + +/** + * GET /api/cliproxy/usage - Alias for /stats (frontend compatibility) + */ +router.get('/usage', handleStatsRequest); /** * GET /api/cliproxy/status - Check CLIProxyAPI running status diff --git a/src/web-server/routes/index.ts b/src/web-server/routes/index.ts index 819cb717..6b967ac3 100644 --- a/src/web-server/routes/index.ts +++ b/src/web-server/routes/index.ts @@ -9,6 +9,7 @@ import { Router } from 'express'; // Import domain routers import profileRoutes from './profile-routes'; +import accountRoutes from './account-routes'; import configRoutes from './config-routes'; import healthRoutes from './health-routes'; import providerRoutes from './provider-routes'; @@ -28,7 +29,7 @@ export const apiRoutes = Router(); // Profile CRUD, settings management, presets, accounts apiRoutes.use('/profiles', profileRoutes); apiRoutes.use('/settings', settingsRoutes); -apiRoutes.use('/accounts', profileRoutes); +apiRoutes.use('/accounts', accountRoutes); // ==================== Unified Config ==================== // Config format, migration diff --git a/src/web-server/routes/profile-routes.ts b/src/web-server/routes/profile-routes.ts index 311d5426..b7943bea 100644 --- a/src/web-server/routes/profile-routes.ts +++ b/src/web-server/routes/profile-routes.ts @@ -1,13 +1,11 @@ /** - * Profile Routes - CRUD operations for user profiles and accounts + * Profile Routes - CRUD operations for user profiles * * Uses unified config (config.yaml) when available, falls back to legacy (config.json). + * Note: Account routes have been moved to account-routes.ts */ import { Router, Request, Response } from 'express'; -import * as fs from 'fs'; -import * as path from 'path'; -import { getCcsDir } from '../../utils/config-manager'; import { isReservedName, RESERVED_PROFILE_NAMES } from '../../config/reserved-names'; import { createApiProfile, removeApiProfile } from '../../api/services/profile-writer'; import { apiProfileExists, listApiProfiles } from '../../api/services/profile-reader'; @@ -20,15 +18,19 @@ const router = Router(); /** * GET /api/profiles - List all profiles */ -router.get('/', (_req: Request, res: Response) => { - const result = listApiProfiles(); - // Map isConfigured -> configured for UI compatibility - const profiles = result.profiles.map((p) => ({ - name: p.name, - settingsPath: p.settingsPath, - configured: p.isConfigured, - })); - res.json({ profiles }); +router.get('/', (_req: Request, res: Response): void => { + try { + const result = listApiProfiles(); + // Map isConfigured -> configured for UI compatibility + const profiles = result.profiles.map((p) => ({ + name: p.name, + settingsPath: p.settingsPath, + configured: p.isConfigured, + })); + res.json({ profiles }); + } catch (error) { + res.status(500).json({ error: (error as Error).message }); + } }); /** @@ -117,52 +119,4 @@ router.delete('/:name', (req: Request, res: Response): void => { res.json({ name, deleted: true }); }); -// ==================== Accounts ==================== - -/** - * GET /api/accounts - List accounts from profiles.json - */ -router.get('/accounts', (_req: Request, res: Response): void => { - const profilesPath = path.join(getCcsDir(), 'profiles.json'); - - if (!fs.existsSync(profilesPath)) { - res.json({ accounts: [], default: null }); - return; - } - - const data = JSON.parse(fs.readFileSync(profilesPath, 'utf8')); - const accounts = Object.entries(data.profiles || {}).map(([name, meta]) => { - const metadata = meta as Record; - return { - name, - ...metadata, - }; - }); - - res.json({ accounts, default: data.default || null }); -}); - -/** - * POST /api/accounts/default - Set default account - */ -router.post('/accounts/default', (req: Request, res: Response): void => { - const { name } = req.body; - - if (!name) { - res.status(400).json({ error: 'Missing required field: name' }); - return; - } - - const profilesPath = path.join(getCcsDir(), 'profiles.json'); - - const data = fs.existsSync(profilesPath) - ? JSON.parse(fs.readFileSync(profilesPath, 'utf8')) - : { profiles: {} }; - - data.default = name; - fs.writeFileSync(profilesPath, JSON.stringify(data, null, 2) + '\n'); - - res.json({ default: name }); -}); - export default router;