Files
ccs/src/auth/commands/list-command.ts
T
kaitranntt 0341f4f86f refactor(auth): modularize auth-commands into commands/ directory
- extract create, list, show, remove, default command handlers

- extract types.ts with CommandContext and parseArgs

- slim auth-commands.ts from 725 to 235 lines (68% reduction)

- add barrel exports at commands/index.ts and auth/index.ts
2025-12-19 12:36:24 -05:00

139 lines
4.2 KiB
TypeScript

/**
* List Command Handler
*
* Lists all saved profiles from both legacy and unified config.
*/
import { ProfileMetadata } from '../../types';
import { initUI, header, color, dim, warn, table } from '../../utils/ui';
import { exitWithError } from '../../errors';
import { ExitCode } from '../../errors/exit-codes';
import { CommandContext, ListOutput, parseArgs, formatRelativeTime } from './types';
/**
* Handle the list command
*/
export async function handleList(ctx: CommandContext, args: string[]): Promise<void> {
await initUI();
const { verbose, json } = parseArgs(args);
try {
// Get profiles from both legacy (profiles.json) and unified config (config.yaml)
const legacyProfiles = ctx.registry.getAllProfiles();
const unifiedAccounts = ctx.registry.getAllAccountsUnified();
// Merge profiles: unified config takes precedence
const profiles: Record<string, ProfileMetadata> = { ...legacyProfiles };
for (const [name, account] of Object.entries(unifiedAccounts)) {
profiles[name] = {
type: 'account',
created: account.created,
last_used: account.last_used,
};
}
const defaultProfile = ctx.registry.getDefaultUnified() ?? ctx.registry.getDefaultProfile();
const profileNames = Object.keys(profiles);
// JSON output mode
if (json) {
const output: ListOutput = {
version: ctx.version,
profiles: profileNames.map((name) => {
const profile = profiles[name];
const isDefault = name === defaultProfile;
const instancePath = ctx.instanceMgr.getInstancePath(name);
return {
name: name,
type: profile.type || 'account',
is_default: isDefault,
created: profile.created,
last_used: profile.last_used || null,
instance_path: instancePath,
};
}),
};
console.log(JSON.stringify(output, null, 2));
return;
}
// Human-readable output
if (profileNames.length === 0) {
console.log(warn('No account profiles found'));
console.log('');
console.log('To create your first profile:');
console.log(` ${color('ccs auth create <profile>', 'command')}`);
console.log('');
console.log('Example:');
console.log(` ${color('ccs auth create work', 'command')}`);
console.log('');
return;
}
console.log(header('Saved Account Profiles'));
console.log('');
// Sort by last_used (descending), then alphabetically
const sorted = profileNames.sort((a, b) => {
const aProfile = profiles[a];
const bProfile = profiles[b];
// Default first
if (a === defaultProfile) return -1;
if (b === defaultProfile) return 1;
// Then by last_used
if (aProfile.last_used && bProfile.last_used) {
return new Date(bProfile.last_used).getTime() - new Date(aProfile.last_used).getTime();
}
if (aProfile.last_used) return -1;
if (bProfile.last_used) return 1;
// Then alphabetically
return a.localeCompare(b);
});
// Build table rows
const rows: string[][] = sorted.map((name) => {
const profile = profiles[name];
const isDefault = name === defaultProfile;
// Status column
const status = isDefault ? color('[OK] default', 'success') : color('[OK]', 'success');
// Last used column
let lastUsed = '-';
if (profile.last_used) {
lastUsed = formatRelativeTime(new Date(profile.last_used));
}
const row = [color(name, isDefault ? 'primary' : 'info'), profile.type || 'account', status];
if (verbose) {
row.push(lastUsed);
}
return row;
});
// Headers
const headers = verbose
? ['Profile', 'Type', 'Status', 'Last Used']
: ['Profile', 'Type', 'Status'];
// Print table
console.log(
table(rows, {
head: headers,
colWidths: verbose ? [15, 12, 15, 12] : [15, 12, 15],
})
);
console.log('');
console.log(dim(`Total: ${profileNames.length} profile(s)`));
console.log('');
} catch (error) {
exitWithError(`Failed to list profiles: ${(error as Error).message}`, ExitCode.GENERAL_ERROR);
}
}