fix(dashboard): read accounts from unified config

Dashboard accounts page was only reading from profiles.json, ignoring
accounts stored in config.yaml when in unified mode. This caused
accounts created via CLI to be invisible in the dashboard.

- Use ProfileRegistry instead of direct file access
- Merge legacy and unified accounts (unified takes precedence)
- Set default via unified config when in unified mode

Fixes #203
This commit is contained in:
kaitranntt
2025-12-25 14:25:40 -05:00
parent af071cd467
commit 8d7845d67f
+43 -26
View File
@@ -1,38 +1,57 @@
/**
* Account Routes - CRUD operations for Claude accounts (profiles.json)
* Account Routes - CRUD operations for Claude accounts
*
* Separated from profile-routes.ts to avoid dual-mounting conflicts.
* Uses ProfileRegistry to read from both legacy (profiles.json)
* and unified config (config.yaml) for consistent data with CLI.
*/
import { Router, Request, Response } from 'express';
import * as fs from 'fs';
import * as path from 'path';
import { getCcsDir } from '../../utils/config-manager';
import ProfileRegistry from '../../auth/profile-registry';
import { isUnifiedMode } from '../../config/unified-config-loader';
const router = Router();
const registry = new ProfileRegistry();
/**
* GET /api/accounts - List accounts from profiles.json
* GET /api/accounts - List accounts from both profiles.json and config.yaml
*/
router.get('/', (_req: Request, res: Response): void => {
try {
const profilesPath = path.join(getCcsDir(), 'profiles.json');
// Get profiles from both legacy and unified config (same logic as CLI)
const legacyProfiles = registry.getAllProfiles();
const unifiedAccounts = registry.getAllAccountsUnified();
if (!fs.existsSync(profilesPath)) {
res.json({ accounts: [], default: null });
return;
// Merge profiles: unified config takes precedence
const merged: Record<string, { type: string; created: string; last_used: string | null }> = {};
// Add legacy profiles first
for (const [name, meta] of Object.entries(legacyProfiles)) {
merged[name] = {
type: meta.type || 'account',
created: meta.created,
last_used: meta.last_used || null,
};
}
const data = JSON.parse(fs.readFileSync(profilesPath, 'utf8'));
const accounts = Object.entries(data.profiles || {}).map(([name, meta]) => {
const metadata = meta as Record<string, unknown>;
return {
name,
...metadata,
// Override with unified config accounts (takes precedence)
for (const [name, account] of Object.entries(unifiedAccounts)) {
merged[name] = {
type: 'account',
created: account.created,
last_used: account.last_used,
};
});
}
res.json({ accounts, default: data.default || null });
// Convert to array format
const accounts = Object.entries(merged).map(([name, meta]) => ({
name,
...meta,
}));
// Get default from unified config first, fallback to legacy
const defaultProfile = registry.getDefaultUnified() ?? registry.getDefaultProfile() ?? null;
res.json({ accounts, default: defaultProfile });
} catch (error) {
res.status(500).json({ error: (error as Error).message });
}
@@ -50,14 +69,12 @@ router.post('/default', (req: Request, res: Response): void => {
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');
// Use unified config if in unified mode, otherwise use legacy
if (isUnifiedMode()) {
registry.setDefaultUnified(name);
} else {
registry.setDefaultProfile(name);
}
res.json({ default: name });
} catch (error) {