diff --git a/src/ccs.ts b/src/ccs.ts index 5ed864e8..a8b7778b 100644 --- a/src/ccs.ts +++ b/src/ccs.ts @@ -253,10 +253,10 @@ async function main(): Promise { return; } - // Special case: profile command - if (firstArg === 'profile') { - const { handleProfileCommand } = await import('./commands/profile-command'); - await handleProfileCommand(args.slice(1)); + // Special case: api command (manages API profiles) + if (firstArg === 'api') { + const { handleApiCommand } = await import('./commands/api-command'); + await handleApiCommand(args.slice(1)); return; } diff --git a/src/commands/profile-command.ts b/src/commands/api-command.ts similarity index 70% rename from src/commands/profile-command.ts rename to src/commands/api-command.ts index f2bf9962..ab7c9fff 100644 --- a/src/commands/profile-command.ts +++ b/src/commands/api-command.ts @@ -1,8 +1,8 @@ /** - * Profile Command Handler + * API Command Handler * - * Manages CCS profiles for custom API providers. - * Commands: create, list + * Manages CCS API profiles for custom API providers. + * Commands: create, list, remove */ import * as fs from 'fs'; @@ -23,7 +23,7 @@ import { import { InteractivePrompt } from '../utils/prompt'; import { getCcsDir, getConfigPath, loadConfig } from '../utils/config-manager'; -interface ProfileCommandArgs { +interface ApiCommandArgs { name?: string; baseUrl?: string; apiKey?: string; @@ -33,10 +33,10 @@ interface ProfileCommandArgs { } /** - * Parse command line arguments for profile commands + * Parse command line arguments for api commands */ -function parseArgs(args: string[]): ProfileCommandArgs { - const result: ProfileCommandArgs = {}; +function parseArgs(args: string[]): ApiCommandArgs { + const result: ApiCommandArgs = {}; for (let i = 0; i < args.length; i++) { const arg = args[i]; @@ -60,20 +60,20 @@ function parseArgs(args: string[]): ProfileCommandArgs { } /** - * Validate profile name + * Validate API profile name */ -function validateProfileName(name: string): string | null { +function validateApiName(name: string): string | null { if (!name) { - return 'Profile name is required'; + return 'API name is required'; } if (!/^[a-zA-Z][a-zA-Z0-9._-]*$/.test(name)) { - return 'Profile name must start with letter, contain only letters, numbers, dot, dash, underscore'; + return 'API name must start with letter, contain only letters, numbers, dot, dash, underscore'; } if (name.length > 32) { - return 'Profile name must be 32 characters or less'; + return 'API name must be 32 characters or less'; } // Reserved names - const reserved = ['default', 'auth', 'profile', 'doctor', 'sync', 'update', 'help', 'version']; + const reserved = ['default', 'auth', 'api', 'doctor', 'sync', 'update', 'help', 'version']; if (reserved.includes(name.toLowerCase())) { return `'${name}' is a reserved name`; } @@ -96,9 +96,9 @@ function validateUrl(url: string): string | null { } /** - * Check if profile already exists in config.json + * Check if API profile already exists in config.json */ -function profileExists(name: string): boolean { +function apiExists(name: string): boolean { try { const config = loadConfig(); return name in config.profiles; @@ -108,7 +108,7 @@ function profileExists(name: string): boolean { } /** - * Create settings.json file for profile + * Create settings.json file for API profile */ function createSettingsFile(name: string, baseUrl: string, apiKey: string, model: string): string { const ccsDir = getCcsDir(); @@ -127,7 +127,7 @@ function createSettingsFile(name: string, baseUrl: string, apiKey: string, model } /** - * Update config.json with new profile + * Update config.json with new API profile */ function updateConfig(name: string, _settingsPath: string): void { const configPath = getConfigPath(); @@ -157,7 +157,7 @@ function updateConfig(name: string, _settingsPath: string): void { } /** - * Handle 'ccs profile create' command + * Handle 'ccs api create' command */ async function handleCreate(args: string[]): Promise { await initUI(); @@ -166,14 +166,14 @@ async function handleCreate(args: string[]): Promise { console.log(header('Create API Profile')); console.log(''); - // Step 1: Profile name + // Step 1: API name let name = parsedArgs.name; if (!name) { - name = await InteractivePrompt.input('Profile name', { - validate: validateProfileName, + name = await InteractivePrompt.input('API name', { + validate: validateApiName, }); } else { - const error = validateProfileName(name); + const error = validateApiName(name); if (error) { console.log(fail(error)); process.exit(1); @@ -181,8 +181,8 @@ async function handleCreate(args: string[]): Promise { } // Check if exists - if (profileExists(name) && !parsedArgs.force) { - console.log(fail(`Profile '${name}' already exists`)); + if (apiExists(name) && !parsedArgs.force) { + console.log(fail(`API '${name}' already exists`)); console.log(` Use ${color('--force', 'command')} to overwrite`); process.exit(1); } @@ -223,7 +223,7 @@ async function handleCreate(args: string[]): Promise { // Create files console.log(''); - console.log(info('Creating profile...')); + console.log(info('Creating API profile...')); try { const settingsPath = createSettingsFile(name, baseUrl, apiKey, model); @@ -232,11 +232,11 @@ async function handleCreate(args: string[]): Promise { console.log(''); console.log( infoBox( - `Profile: ${name}\n` + + `API: ${name}\n` + `Settings: ~/.ccs/${name}.settings.json\n` + `Base URL: ${baseUrl}\n` + `Model: ${model}`, - 'Profile Created' + 'API Profile Created' ) ); console.log(''); @@ -244,18 +244,18 @@ async function handleCreate(args: string[]): Promise { console.log(` ${color(`ccs ${name} "your prompt"`, 'command')}`); console.log(''); } catch (error) { - console.log(fail(`Failed to create profile: ${(error as Error).message}`)); + console.log(fail(`Failed to create API profile: ${(error as Error).message}`)); process.exit(1); } } /** - * Check if profile has real API key (not placeholder) + * Check if API profile has real API key (not placeholder) */ -function isProfileConfigured(profileName: string): boolean { +function isApiConfigured(apiName: string): boolean { try { const ccsDir = getCcsDir(); - const settingsPath = path.join(ccsDir, `${profileName}.settings.json`); + const settingsPath = path.join(ccsDir, `${apiName}.settings.json`); if (!fs.existsSync(settingsPath)) return false; const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8')); @@ -268,31 +268,31 @@ function isProfileConfigured(profileName: string): boolean { } /** - * Handle 'ccs profile list' command + * Handle 'ccs api list' command */ async function handleList(): Promise { await initUI(); - console.log(header('CCS Profiles')); + console.log(header('CCS API Profiles')); console.log(''); try { const config = loadConfig(); - const profiles = Object.keys(config.profiles); + const apis = Object.keys(config.profiles); - if (profiles.length === 0) { - console.log(warn('No profiles configured')); + if (apis.length === 0) { + console.log(warn('No API profiles configured')); console.log(''); - console.log('To create a profile:'); - console.log(` ${color('ccs profile create', 'command')}`); + console.log('To create an API profile:'); + console.log(` ${color('ccs api create', 'command')}`); console.log(''); return; } // Build table data with status indicators - const rows: string[][] = profiles.map((name) => { + const rows: string[][] = apis.map((name) => { const settingsPath = config.profiles[name]; - const status = isProfileConfigured(name) ? color('[OK]', 'success') : color('[!]', 'warning'); + const status = isApiConfigured(name) ? color('[OK]', 'success') : color('[!]', 'warning'); return [name, settingsPath, status]; }); @@ -300,7 +300,7 @@ async function handleList(): Promise { // Print table console.log( table(rows, { - head: ['Profile', 'Settings File', 'Status'], + head: ['API', 'Settings File', 'Status'], colWidths: [15, 35, 10], }) ); @@ -323,22 +323,22 @@ async function handleList(): Promise { console.log(''); } - console.log(dim(`Total: ${profiles.length} profile(s)`)); + console.log(dim(`Total: ${apis.length} API profile(s)`)); console.log(''); } catch (error) { - console.log(fail(`Failed to list profiles: ${(error as Error).message}`)); + console.log(fail(`Failed to list API profiles: ${(error as Error).message}`)); process.exit(1); } } /** - * Handle 'ccs profile remove' command + * Handle 'ccs api remove' command */ async function handleRemove(args: string[]): Promise { await initUI(); const parsedArgs = parseArgs(args); - // Load config first to get available profiles + // Load config first to get available APIs let config: { profiles: Record; cliproxy?: Record }; try { config = loadConfig(); @@ -347,35 +347,35 @@ async function handleRemove(args: string[]): Promise { process.exit(1); } - const profiles = Object.keys(config.profiles); - if (profiles.length === 0) { - console.log(warn('No profiles to remove')); + const apis = Object.keys(config.profiles); + if (apis.length === 0) { + console.log(warn('No API profiles to remove')); process.exit(0); } - // Interactive profile selection if not provided + // Interactive API selection if not provided let name = parsedArgs.name; if (!name) { - console.log(header('Remove Profile')); + console.log(header('Remove API Profile')); console.log(''); - console.log('Available profiles:'); - profiles.forEach((p, i) => console.log(` ${i + 1}. ${p}`)); + console.log('Available APIs:'); + apis.forEach((p, i) => console.log(` ${i + 1}. ${p}`)); console.log(''); - name = await InteractivePrompt.input('Profile name to remove', { + name = await InteractivePrompt.input('API name to remove', { validate: (val) => { - if (!val) return 'Profile name is required'; - if (!profiles.includes(val)) return `Profile '${val}' not found`; + if (!val) return 'API name is required'; + if (!apis.includes(val)) return `API '${val}' not found`; return null; }, }); } if (!(name in config.profiles)) { - console.log(fail(`Profile '${name}' not found`)); + console.log(fail(`API '${name}' not found`)); console.log(''); - console.log('Available profiles:'); - profiles.forEach((p) => console.log(` - ${p}`)); + console.log('Available APIs:'); + apis.forEach((p) => console.log(` - ${p}`)); process.exit(1); } @@ -384,12 +384,13 @@ async function handleRemove(args: string[]): Promise { // Confirm deletion console.log(''); - console.log(`Profile '${color(name, 'command')}' will be removed.`); + console.log(`API '${color(name, 'command')}' will be removed.`); console.log(` Settings: ${settingsPath}`); console.log(''); const confirmed = - parsedArgs.yes || (await InteractivePrompt.confirm('Delete this profile?', { default: false })); + parsedArgs.yes || + (await InteractivePrompt.confirm('Delete this API profile?', { default: false })); if (!confirmed) { console.log(info('Cancelled')); @@ -408,25 +409,25 @@ async function handleRemove(args: string[]): Promise { fs.unlinkSync(expandedPath); } - console.log(ok(`Profile removed: ${name}`)); + console.log(ok(`API profile removed: ${name}`)); console.log(''); } /** - * Show help for profile commands + * Show help for api commands */ async function showHelp(): Promise { await initUI(); - console.log(header('CCS Profile Management')); + console.log(header('CCS API Management')); console.log(''); console.log(subheader('Usage')); - console.log(` ${color('ccs profile', 'command')} [options]`); + console.log(` ${color('ccs api', 'command')} [options]`); console.log(''); console.log(subheader('Commands')); console.log(` ${color('create [name]', 'command')} Create new API profile (interactive)`); - console.log(` ${color('list', 'command')} List all profiles`); - console.log(` ${color('remove ', 'command')} Remove a profile`); + console.log(` ${color('list', 'command')} List all API profiles`); + console.log(` ${color('remove ', 'command')} Remove an API profile`); console.log(''); console.log(subheader('Options')); console.log(` ${color('--base-url ', 'command')} API base URL (create)`); @@ -437,23 +438,23 @@ async function showHelp(): Promise { console.log(''); console.log(subheader('Examples')); console.log(` ${dim('# Interactive wizard')}`); - console.log(` ${color('ccs profile create', 'command')}`); + console.log(` ${color('ccs api create', 'command')}`); console.log(''); console.log(` ${dim('# Create with name')}`); - console.log(` ${color('ccs profile create myapi', 'command')}`); + console.log(` ${color('ccs api create myapi', 'command')}`); console.log(''); - console.log(` ${dim('# Remove profile')}`); - console.log(` ${color('ccs profile remove myapi', 'command')}`); + console.log(` ${dim('# Remove API profile')}`); + console.log(` ${color('ccs api remove myapi', 'command')}`); console.log(''); - console.log(` ${dim('# Show all profiles')}`); - console.log(` ${color('ccs profile list', 'command')}`); + console.log(` ${dim('# Show all API profiles')}`); + console.log(` ${color('ccs api list', 'command')}`); console.log(''); } /** - * Main profile command router + * Main api command router */ -export async function handleProfileCommand(args: string[]): Promise { +export async function handleApiCommand(args: string[]): Promise { const command = args[0]; if (!command || command === '--help' || command === '-h' || command === 'help') { @@ -478,7 +479,7 @@ export async function handleProfileCommand(args: string[]): Promise { console.log(fail(`Unknown command: ${command}`)); console.log(''); console.log('Run for help:'); - console.log(` ${color('ccs profile --help', 'command')}`); + console.log(` ${color('ccs api --help', 'command')}`); process.exit(1); } } diff --git a/src/commands/help-command.ts b/src/commands/help-command.ts index 9f375f14..edac032a 100644 --- a/src/commands/help-command.ts +++ b/src/commands/help-command.ts @@ -1,15 +1,28 @@ -import { initUI, box, header, color, dim } from '../utils/ui'; +import { initUI, box, color, dim, sectionHeader, subheader } from '../utils/ui'; // Version is read from VERSION file during build const VERSION = '5.3.0'; /** - * Print a section with header and items + * Print a major section with ═══ borders (only for 3 main sections) + * Format: + * ═══ TITLE ═══ + * Subtitle line 1 + * Subtitle line 2 + * + * command Description */ -function printSection(title: string, subtitle: string, items: [string, string][]): void { - // Header with optional subtitle - const headerText = subtitle ? `${title} ${dim(subtitle)}` : title; - console.log(header(headerText)); +function printMajorSection(title: string, subtitles: string[], items: [string, string][]): void { + // Section header with ═══ borders + console.log(sectionHeader(title)); + + // Subtitles on separate lines (dim) + for (const subtitle of subtitles) { + console.log(` ${dim(subtitle)}`); + } + + // Empty line before items + console.log(''); // Calculate max command length for alignment const maxCmdLen = Math.max(...items.map(([cmd]) => cmd.length)); @@ -19,6 +32,49 @@ function printSection(title: string, subtitle: string, items: [string, string][] console.log(` ${color(paddedCmd, 'command')} ${desc}`); } + // Extra spacing after section + console.log(''); +} + +/** + * Print a sub-section with colored title + * Format: + * Title (context): + * command Description + */ +function printSubSection(title: string, items: [string, string][]): void { + // Sub-section header (colored, no borders) + console.log(subheader(`${title}:`)); + + // Calculate max command length for alignment + const maxCmdLen = Math.max(...items.map(([cmd]) => cmd.length)); + + for (const [cmd, desc] of items) { + const paddedCmd = cmd.padEnd(maxCmdLen + 2); + console.log(` ${color(paddedCmd, 'command')} ${desc}`); + } + + // Spacing after section + console.log(''); +} + +/** + * Print a config/paths section + * Format: + * Title: + * Label: path + */ +function printConfigSection(title: string, items: [string, string][]): void { + console.log(subheader(`${title}:`)); + + // Calculate max label length for alignment + const maxLabelLen = Math.max(...items.map(([label]) => label.length)); + + for (const [label, path] of items) { + const paddedLabel = label.padEnd(maxLabelLen); + console.log(` ${paddedLabel} ${color(path, 'path')}`); + } + console.log(''); } @@ -29,9 +85,17 @@ export async function handleHelpCommand(): Promise { // Initialize UI (if not already) await initUI(); - // Hero box with title + // Hero box with ASCII art logo + // Each letter: C=╔═╗/║ /╚═╝, C=╔═╗/║ /╚═╝, S=╔═╗/╚═╗/╚═╝ + const logo = ` +╔═╗ ╔═╗ ╔═╗ +║ ║ ╚═╗ v${VERSION} +╚═╝ ╚═╝ ╚═╝ + +Claude Code Profile & Model Switcher`.trim(); + console.log( - box(`CCS v${VERSION}\nClaude Code Profile & Model Switcher`, { + box(logo, { padding: 1, borderStyle: 'round', titleAlignment: 'center', @@ -40,106 +104,135 @@ export async function handleHelpCommand(): Promise { console.log(''); // Usage section - console.log(header('USAGE')); - console.log(` $ ${color('ccs', 'command')} [flags] [-- claude-args...]`); - console.log(` $ ${color('ccs', 'command')} [flags]`); + console.log(subheader('Usage:')); + console.log(` ${color('ccs', 'command')} [profile] [claude-args...]`); + console.log(` ${color('ccs', 'command')} [flags]`); console.log(''); - // API Key Profiles section - printSection('API KEY PROFILES', 'Configure: ~/.ccs/*.settings.json', [ - ['ccs', 'Use default Claude account'], - ['ccs glm', 'GLM-4.6 via Zhipu AI'], - ['ccs glmt', 'GLM-4.6 (Turbo mode)'], - ['ccs kimi', 'Kimi via Moonshot AI'], + // ═══════════════════════════════════════════════════════════════════════════ + // MAJOR SECTION 1: API Key Profiles + // ═══════════════════════════════════════════════════════════════════════════ + printMajorSection( + 'API Key Profiles', + ['Configure in ~/.ccs/*.settings.json'], + [ + ['ccs', 'Use default Claude account'], + ['ccs glm', 'GLM 4.6 (API key required)'], + ['ccs glmt', 'GLM with thinking mode'], + ['ccs kimi', 'Kimi for Coding (API key)'], + ['', ''], // Spacer + ['ccs api create', 'Create custom API profile'], + ['ccs api remove', 'Remove an API profile'], + ['ccs api list', 'List all API profiles'], + ] + ); + + // ═══════════════════════════════════════════════════════════════════════════ + // MAJOR SECTION 2: Account Management + // ═══════════════════════════════════════════════════════════════════════════ + printMajorSection( + 'Account Management', + ['Run multiple Claude accounts concurrently'], + [ + ['ccs auth --help', 'Show account management commands'], + ['ccs auth create ', 'Create new account profile'], + ['ccs auth list', 'List all account profiles'], + ] + ); + + // ═══════════════════════════════════════════════════════════════════════════ + // MAJOR SECTION 3: CLI Proxy (OAuth Providers) + // ═══════════════════════════════════════════════════════════════════════════ + printMajorSection( + 'CLI Proxy (OAuth Providers)', + [ + 'Zero-config OAuth authentication via CLIProxyAPI', + 'First run: Browser opens for authentication', + 'Settings: ~/.ccs/{provider}.settings.json (created after auth)', + ], + [ + ['ccs gemini', 'Google Gemini (gemini-2.5-pro)'], + ['ccs codex', 'OpenAI Codex (gpt-5.1-codex-max)'], + ['ccs agy', 'Antigravity (gemini-3-pro-preview)'], + ['ccs qwen', 'Qwen Code (qwen3-coder)'], + ['', ''], // Spacer + ['ccs --auth', 'Authenticate only'], + ['ccs --logout', 'Clear authentication'], + ['ccs --headless', 'Headless auth (for SSH)'], + ['ccs codex "explain code"', 'Use with prompt'], + ] + ); + + // ═══════════════════════════════════════════════════════════════════════════ + // SUB-SECTIONS (simpler styling) + // ═══════════════════════════════════════════════════════════════════════════ + + // Delegation + printSubSection('Delegation (inside Claude Code CLI)', [ + ['/ccs "task"', 'Delegate task (auto-selects profile)'], + ['/ccs --glm "task"', 'Force GLM-4.6 for simple tasks'], + ['/ccs --kimi "task"', 'Force Kimi for long context'], + ['/ccs:continue "follow-up"', 'Continue last delegation session'], ]); - // Profile management section - printSection('PROFILE MANAGEMENT', '', [ - ['ccs profile create', 'Create custom API profile'], - ['ccs profile list', 'List all profiles'], - ['ccs profile remove', 'Remove a profile'], + // Diagnostics + printSubSection('Diagnostics', [ + ['ccs doctor', 'Run health check and diagnostics'], + ['ccs sync', 'Sync delegation commands and skills'], + ['ccs update', 'Update CCS to latest version'], ]); - // Account management section - printSection('ACCOUNT MANAGEMENT', 'Multiple Claude accounts', [ - ['ccs auth create ', 'Create new account'], - ['ccs auth list', 'List all accounts'], - ['ccs auth default ', 'Set default account'], + // Flags + printSubSection('Flags', [ + ['-h, --help', 'Show this help message'], + ['-v, --version', 'Show version and installation info'], + ['-sc, --shell-completion', 'Install shell auto-completion'], ]); - // OAuth section - printSection('OAUTH PROVIDERS', 'Zero config, browser auth', [ - ['ccs gemini', 'Google Gemini (gemini-2.5-pro)'], - ['ccs codex', 'OpenAI Codex (gpt-5.1-codex-max)'], - ['ccs agy', 'Antigravity (gemini-3-pro-preview)'], - ['ccs qwen', 'Qwen Code (qwen3-coder)'], + // Configuration + printConfigSection('Configuration', [ + ['Config File:', '~/.ccs/config.json'], + ['Profiles:', '~/.ccs/profiles.json'], + ['Instances:', '~/.ccs/instances/'], + ['Settings:', '~/.ccs/*.settings.json'], ]); - // OAuth flags - console.log(header('OAUTH FLAGS')); - console.log(` ${color('ccs --auth', 'command')} Authenticate only`); - console.log(` ${color('ccs --logout', 'command')} Clear authentication`); - console.log(` ${color('ccs --headless', 'command')} Headless auth (for SSH)`); - console.log(''); - - // Delegation section - printSection('DELEGATION', 'Inside Claude Code CLI', [ - ['/ccs "task"', 'Delegate (auto-select profile)'], - ['/ccs --glm "task"', 'Force GLM-4.6'], - ['/ccs --kimi "task"', 'Force Kimi'], - ['/ccs:continue', 'Continue last delegation'], - ]); - - // Diagnostics section - printSection('DIAGNOSTICS', '', [ - ['ccs doctor', 'Run health check'], - ['ccs sync', 'Sync delegation commands'], - ['ccs update', 'Update to latest version'], - ]); - - // Flags section - console.log(header('FLAGS')); - console.log(` ${color('-h, --help', 'command')} Show this help`); - console.log(` ${color('-v, --version', 'command')} Show version`); - console.log(` ${color('-sc, --shell-completion', 'command')} Install shell completion`); - console.log(''); - - // Configuration paths - console.log(header('CONFIGURATION')); - console.log(` Config: ${color('~/.ccs/config.json', 'path')}`); - console.log(` Profiles: ${color('~/.ccs/profiles.json', 'path')}`); - console.log(` Settings: ${color('~/.ccs/*.settings.json', 'path')}`); - console.log(` CLIProxy: ${color('~/.ccs/cliproxy/', 'path')}`); + // CLI Proxy paths + console.log(subheader('CLI Proxy:')); + console.log(` Binary: ${color('~/.ccs/cliproxy/bin/cli-proxy-api', 'path')}`); + console.log(` Config: ${color('~/.ccs/cliproxy/config.yaml', 'path')}`); + console.log(` Auth: ${color('~/.ccs/cliproxy/auth/', 'path')}`); + console.log(` ${dim('Port: 8317 (default)')}`); console.log(''); // Shared Data - console.log(header('SHARED DATA')); - console.log(` Commands: ${color('~/.ccs/shared/commands/', 'path')}`); - console.log(` Skills: ${color('~/.ccs/shared/skills/', 'path')}`); - console.log(` Agents: ${color('~/.ccs/shared/agents/', 'path')}`); + console.log(subheader('Shared Data:')); + console.log(` Commands: ${color('~/.ccs/shared/commands/', 'path')}`); + console.log(` Skills: ${color('~/.ccs/shared/skills/', 'path')}`); + console.log(` Agents: ${color('~/.ccs/shared/agents/', 'path')}`); console.log(` ${dim('Note: Symlinked across all profiles')}`); console.log(''); - // Examples - console.log(header('EXAMPLES')); - console.log(` ${dim('# Use default account')}`); - console.log(` $ ${color('ccs', 'command')}`); - console.log(''); - console.log(` ${dim('# OAuth provider (browser auth first time)')}`); - console.log(` $ ${color('ccs gemini', 'command')}`); - console.log(''); - console.log(` ${dim('# API key model with prompt')}`); - console.log(` $ ${color('ccs glm "implement the API"', 'command')}`); + // Examples (aligned with consistent spacing) + console.log(subheader('Examples:')); + console.log(` $ ${color('ccs', 'command')} ${dim('# Use default account')}`); + console.log( + ` $ ${color('ccs gemini', 'command')} ${dim('# OAuth (browser opens first time)')}` + ); + console.log(` $ ${color('ccs glm "implement API"', 'command')} ${dim('# API key model')}`); console.log(''); - // Footer - console.log(dim('Docs: https://github.com/kaitranntt/ccs')); - console.log(dim('License: MIT')); + // Docs link + console.log(` ${dim('Docs: https://github.com/kaitranntt/ccs')}`); console.log(''); // Uninstall - console.log(color('Uninstall:', 'warning')); - console.log(' npm uninstall -g @kaitranntt/ccs'); + console.log(subheader('Uninstall:')); + console.log(` ${color('npm uninstall -g @kaitranntt/ccs', 'command')}`); + console.log(''); + + // License + console.log(dim('License: MIT')); console.log(''); process.exit(0); diff --git a/src/utils/ui.ts b/src/utils/ui.ts index ff60900d..0ad23222 100644 --- a/src/utils/ui.ts +++ b/src/utils/ui.ts @@ -139,7 +139,7 @@ export function color(text: string, semantic: SemanticColor): string { case 'secondary': return chalkModule.hex(COLORS.secondary)(text); case 'command': - return chalkModule.yellow.italic(text); + return chalkModule.yellow.bold(text); case 'path': return chalkModule.cyan.underline(text); default: @@ -458,6 +458,24 @@ export function hr(char = '─', width = 60): string { return dim(char.repeat(width)); } +/** + * Print section header with ═══ borders + * Format: ═══ Title ═══ + */ +export function sectionHeader(title: string): string { + const border = '═══'; + const headerText = `${border} ${title} ${border}`; + // Use gradient + bold for visual appeal + if (gradientModule && chalkModule && useColors()) { + return chalkModule.bold(gradientModule([COLORS.primary, COLORS.secondary])(headerText)); + } + // Fallback to bold primary color + if (useColors() && chalkModule) { + return chalkModule.hex(COLORS.primary).bold(headerText); + } + return headerText; +} + // ============================================================================= // TASK LISTS (Listr2 Integration) // ============================================================================= @@ -604,6 +622,7 @@ export const ui = { // Headers header, subheader, + sectionHeader, hr, } as const;