import { initUI, header, color, dim, info, errorBox } from './ui'; import { ERROR_CODES, getErrorDocUrl } from './error-codes'; import type { ErrorCode } from './error-codes'; import { getPortCheckCommand, getKillPidCommand } from './platform-commands'; /** * Error types with structured messages (Legacy - kept for compatibility) */ export const ErrorTypes = { NO_CLAUDE_CLI: 'NO_CLAUDE_CLI', MISSING_SETTINGS: 'MISSING_SETTINGS', INVALID_CONFIG: 'INVALID_CONFIG', UNKNOWN_PROFILE: 'UNKNOWN_PROFILE', PERMISSION_DENIED: 'PERMISSION_DENIED', GENERIC: 'GENERIC', } as const; export type ErrorType = (typeof ErrorTypes)[keyof typeof ErrorTypes]; /** * Enhanced error manager with context-aware messages */ export class ErrorManager { /** * Show error code and documentation URL */ static showErrorCode(errorCode: ErrorCode): void { console.error(dim(`Error: ${errorCode}`)); console.error(dim(getErrorDocUrl(errorCode))); console.error(''); } /** * Show Claude CLI not found error */ static async showClaudeNotFound(): Promise { await initUI(); console.error(''); console.error( errorBox( 'Claude CLI not found\n\n' + 'CCS requires Claude CLI to be installed\n' + 'and available in PATH.', 'ERROR' ) ); console.error(''); console.error(header('SOLUTIONS')); console.error(''); console.error(' 1. Install Claude CLI'); console.error(` ${color('https://docs.claude.com/install', 'path')}`); console.error(''); // Windows-specific guidance for native installer users if (process.platform === 'win32') { console.error(' 2. If you used the Windows installer, run:'); console.error(` ${color('claude install', 'command')}`); console.error(dim(' This adds Claude to your PATH')); console.error(''); console.error(' 3. Verify installation'); console.error(` ${color('Get-Command claude', 'command')}`); console.error(''); console.error(' 4. Custom path (if installed elsewhere)'); console.error(` ${color('$env:CCS_CLAUDE_PATH="C:\\path\\to\\claude.exe"', 'command')}`); } else { console.error(' 2. Verify installation'); console.error(` ${color('command -v claude', 'command')}`); console.error(''); console.error(' 3. Custom path (if installed elsewhere)'); console.error(` ${color('export CCS_CLAUDE_PATH="/path/to/claude"', 'command')}`); } console.error(''); this.showErrorCode(ERROR_CODES.CLAUDE_NOT_FOUND); } /** * Show settings file not found error */ static async showSettingsNotFound(settingsPath: string): Promise { await initUI(); const isClaudeSettings = settingsPath.includes('.claude') && settingsPath.endsWith('settings.json'); console.error(''); console.error(errorBox('Settings file not found\n\n' + `File: ${settingsPath}`, 'ERROR')); console.error(''); if (isClaudeSettings) { console.error('This file is auto-created when you login to Claude CLI.'); console.error(''); console.error(header('SOLUTIONS')); console.error(` ${color(`echo '{}' > ${settingsPath}`, 'command')}`); console.error(` ${color('claude /login', 'command')}`); console.error(''); console.error(dim('Why: Newer Claude CLI versions require explicit login.')); } else { console.error(header('SOLUTIONS')); console.error(` ${color('npm install -g @kaitranntt/ccs --force', 'command')}`); console.error(''); console.error(dim('This will recreate missing profile settings.')); } console.error(''); this.showErrorCode(ERROR_CODES.CONFIG_INVALID_PROFILE); } /** * Show invalid configuration error */ static async showInvalidConfig(configPath: string, errorDetail: string): Promise { await initUI(); console.error(''); console.error( errorBox( 'Configuration invalid\n\n' + `File: ${configPath}\n` + `Issue: ${errorDetail}`, 'ERROR' ) ); console.error(''); console.error(header('SOLUTIONS')); console.error(''); console.error(` ${dim('# Backup corrupted file')}`); console.error(` ${color(`mv ${configPath} ${configPath}.backup`, 'command')}`); console.error(''); console.error(` ${dim('# Reinstall CCS')}`); console.error(` ${color('npm install -g @kaitranntt/ccs --force', 'command')}`); console.error(''); console.error(dim('Your profile settings will be preserved.')); console.error(''); this.showErrorCode(ERROR_CODES.CONFIG_INVALID_JSON); } /** * Show profile not found error */ static async showProfileNotFound( profileName: string, availableProfiles: string[], suggestions: string[] = [] ): Promise { await initUI(); console.error(''); console.error(errorBox(`Profile '${profileName}' not found`, 'ERROR')); console.error(''); if (suggestions && suggestions.length > 0) { console.error(header('DID YOU MEAN')); suggestions.forEach((s) => console.error(` ${color(s, 'command')}`)); console.error(''); } console.error(header('AVAILABLE PROFILES')); availableProfiles.forEach((line) => console.error(` ${color(line, 'info')}`)); console.error(''); console.error(header('SOLUTIONS')); console.error(''); console.error(' Use an existing profile:'); console.error(` ${color('ccs "your prompt"', 'command')}`); console.error(''); console.error(' Create a new account profile:'); console.error(` ${color('ccs auth create ', 'command')}`); console.error(''); console.error(info(`Tip: Use ${color('ccs config', 'command')} for web-based configuration`)); console.error(''); this.showErrorCode(ERROR_CODES.PROFILE_NOT_FOUND); } /** * Show permission denied error */ static async showPermissionDenied(filePath: string): Promise { await initUI(); console.error(''); console.error(errorBox('Permission denied\n\n' + `Cannot write to: ${filePath}`, 'ERROR')); console.error(''); console.error(header('SOLUTIONS')); console.error(''); console.error(` ${dim('# Fix ownership')}`); console.error(` ${color('sudo chown -R $USER ~/.ccs ~/.claude', 'command')}`); console.error(''); console.error(` ${dim('# Fix permissions')}`); console.error(` ${color('chmod 755 ~/.ccs ~/.claude', 'command')}`); console.error(''); console.error(` ${dim('# Retry installation')}`); console.error(` ${color('npm install -g @kaitranntt/ccs --force', 'command')}`); console.error(''); this.showErrorCode(ERROR_CODES.FS_CANNOT_WRITE_FILE); } /** * Show CLIProxy OAuth timeout error */ static async showOAuthTimeout(provider: string): Promise { await initUI(); console.error(''); console.error( errorBox('OAuth Timeout\n\n' + 'Authentication did not complete within 2 minutes.', 'ERROR') ); console.error(''); console.error(header('TROUBLESHOOTING')); console.error(' 1. Check if browser opened (popup blocker?)'); console.error(' 2. Complete login in browser, then return here'); console.error(' 3. Try different browser'); console.error(' 4. Disable browser extensions temporarily'); console.error(''); console.error(header('FOR HEADLESS/SSH ENVIRONMENTS')); console.error(` ${color(`ccs ${provider} --auth --headless`, 'command')}`); console.error(''); console.error(dim('This displays manual authentication steps.')); console.error(''); console.error(info(`Tip: Use ${color('ccs config', 'command')} for web-based configuration`)); console.error(''); } /** * Show CLIProxy port conflict error */ static async showPortConflict(port: number): Promise { await initUI(); const isWindows = process.platform === 'win32'; console.error(''); console.error( errorBox('Port Conflict\n\n' + `CLIProxy port ${port} is already in use.`, 'ERROR') ); console.error(''); console.error(header('SOLUTIONS')); console.error(''); console.error(' 1. Find process using port:'); console.error(` ${color(getPortCheckCommand(port), 'command')}`); console.error(''); console.error(' 2. Kill the process:'); if (isWindows) { console.error( ` ${color(`taskkill /F /PID `, 'command')} (replace with actual ID)` ); } else { console.error(` ${color(getKillPidCommand(12345).replace('12345', ''), 'command')}`); } console.error(''); console.error(' 3. Auto-fix: Run:'); console.error(` ${color('ccs doctor --fix', 'command')}`); console.error(''); } /** * Show CLIProxy binary download failure error */ static async showBinaryDownloadFailed(url: string, error: string): Promise { await initUI(); console.error(''); console.error(errorBox('Binary Download Failed\n\n' + `Error: ${error}`, 'ERROR')); console.error(''); console.error(header('TROUBLESHOOTING')); console.error(' 1. Check internet connection'); console.error(' 2. Check firewall/proxy settings'); console.error(' 3. Try again in a few minutes'); console.error(''); console.error(header('MANUAL DOWNLOAD')); console.error(` URL: ${color(url, 'path')}`); console.error(` Save to: ${color('~/.ccs/bin/cliproxyapi', 'path')}`); console.error(` ${color('chmod +x ~/.ccs/bin/cliproxyapi', 'command')}`); console.error(''); } /** * Show CLIProxy authentication required error */ static async showAuthRequired(provider: string): Promise { await initUI(); const displayName = provider.charAt(0).toUpperCase() + provider.slice(1); console.error(''); console.error(errorBox(`${displayName} authentication required`, 'ERROR')); console.error(''); console.error(header('TO AUTHENTICATE')); console.error(` ${color(`ccs ${provider} --auth`, 'command')}`); console.error(''); console.error(dim('This will open a browser for OAuth login.')); console.error(dim('After login, you can use the profile normally.')); console.error(''); console.error(info(`Tip: Use ${color('ccs config', 'command')} for web-based configuration`)); console.error(''); } }