mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 20:17:45 +00:00
308 lines
10 KiB
TypeScript
308 lines
10 KiB
TypeScript
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<void> {
|
|
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<void> {
|
|
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<void> {
|
|
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<void> {
|
|
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 <profile> "your prompt"', 'command')}`);
|
|
console.error('');
|
|
console.error(' Create a new account profile:');
|
|
console.error(` ${color('ccs auth create <name>', '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<void> {
|
|
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<void> {
|
|
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<void> {
|
|
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 <PID>`, 'command')} (replace <PID> with actual ID)`
|
|
);
|
|
} else {
|
|
console.error(` ${color(getKillPidCommand(12345).replace('12345', '<PID>'), '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<void> {
|
|
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<void> {
|
|
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('');
|
|
}
|
|
}
|