diff --git a/src/ccs.ts b/src/ccs.ts index 572d1a60..02550848 100644 --- a/src/ccs.ts +++ b/src/ccs.ts @@ -291,6 +291,18 @@ async function main(): Promise { await autoMigrate(); } + // Auto-recovery for missing configuration (BEFORE any early-exit commands) + // This ensures ALL commands benefit from auto-recovery, not just profile-switching flow + // Recovery is safe to run early - it only creates missing files with safe defaults + const RecoveryManagerModule = await import('./management/recovery-manager'); + const RecoveryManager = RecoveryManagerModule.default; + const recovery = new RecoveryManager(); + const recovered = recovery.recoverAll(); + + if (recovered) { + recovery.showRecoveryHints(); + } + // Special case: version command (check BEFORE profile detection) if (firstArg === 'version' || firstArg === '--version' || firstArg === '-v') { handleVersionCommand(); @@ -455,16 +467,6 @@ async function main(): Promise { return; } - // Auto-recovery for missing configuration - const RecoveryManagerModule = await import('./management/recovery-manager'); - const RecoveryManager = RecoveryManagerModule.default; - const recovery = new RecoveryManager(); - const recovered = recovery.recoverAll(); - - if (recovered) { - recovery.showRecoveryHints(); - } - // First-time install: offer setup wizard for interactive users // Check independently of recovery status (user may have empty config.yaml) // Skip if headless, CI, or non-TTY environment diff --git a/src/cliproxy/config-generator.ts b/src/cliproxy/config-generator.ts index 81d42c37..c2d4b83d 100644 --- a/src/cliproxy/config-generator.ts +++ b/src/cliproxy/config-generator.ts @@ -129,9 +129,10 @@ export function getConfigPathForPort(port: number): string { } /** - * Get config file path (default port) + * Get CLIProxy config file path (default port) + * Named distinctly from config-manager's getConfigPath to avoid confusion. */ -export function getConfigPath(): string { +export function getCliproxyConfigPath(): string { return getConfigPathForPort(CLIPROXY_DEFAULT_PORT); } @@ -377,7 +378,7 @@ export function regenerateConfig(port: number = CLIPROXY_DEFAULT_PORT): string { * @returns true if config should be regenerated */ export function configNeedsRegeneration(): boolean { - const configPath = getConfigPath(); + const configPath = getCliproxyConfigPath(); if (!fs.existsSync(configPath)) { return false; // Will be created on first use } diff --git a/src/cliproxy/index.ts b/src/cliproxy/index.ts index edbf9fd0..08bc50a2 100644 --- a/src/cliproxy/index.ts +++ b/src/cliproxy/index.ts @@ -65,7 +65,7 @@ export { getCliproxyDir, getProviderAuthDir, getAuthDir, - getConfigPath, + getCliproxyConfigPath, getBinDir, configExists, deleteConfig, diff --git a/src/cliproxy/openai-compat-manager.ts b/src/cliproxy/openai-compat-manager.ts index 5254a156..9bb8dfd4 100644 --- a/src/cliproxy/openai-compat-manager.ts +++ b/src/cliproxy/openai-compat-manager.ts @@ -7,7 +7,7 @@ import * as fs from 'fs'; import * as yaml from 'js-yaml'; -import { getConfigPath } from './config-generator'; +import { getCliproxyConfigPath } from './config-generator'; /** Model alias configuration */ export interface OpenAICompatModel { @@ -48,7 +48,7 @@ interface ConfigYaml { * Load current config.yaml */ function loadConfig(): ConfigYaml { - const configPath = getConfigPath(); + const configPath = getCliproxyConfigPath(); if (!fs.existsSync(configPath)) { return {}; } @@ -65,7 +65,7 @@ function loadConfig(): ConfigYaml { * Save config.yaml with proper formatting */ function saveConfig(config: ConfigYaml): void { - const configPath = getConfigPath(); + const configPath = getCliproxyConfigPath(); const content = yaml.dump(config, { lineWidth: -1, // Disable line wrapping quotingType: '"', diff --git a/src/commands/help-command.ts b/src/commands/help-command.ts index 82f70a86..4115a622 100644 --- a/src/commands/help-command.ts +++ b/src/commands/help-command.ts @@ -1,6 +1,7 @@ import * as fs from 'fs'; import * as path from 'path'; import { initUI, box, color, dim, sectionHeader, subheader } from '../utils/ui'; +import { isUnifiedMode } from '../config/unified-config-loader'; // Get version from package.json (same as version-command.ts) const VERSION = JSON.parse( @@ -234,7 +235,7 @@ Run ${color('ccs config', 'command')} for web dashboard`.trim(); // Configuration printConfigSection('Configuration', [ - ['Config File:', '~/.ccs/config.json'], + ['Config File:', isUnifiedMode() ? '~/.ccs/config.yaml' : '~/.ccs/config.json'], ['Profiles:', '~/.ccs/profiles.json'], ['Instances:', '~/.ccs/instances/'], ['Settings:', '~/.ccs/*.settings.json'], diff --git a/src/management/checks/cliproxy-check.ts b/src/management/checks/cliproxy-check.ts index a4424f30..4a4998b6 100644 --- a/src/management/checks/cliproxy-check.ts +++ b/src/management/checks/cliproxy-check.ts @@ -8,7 +8,7 @@ import { isCLIProxyInstalled, getCLIProxyPath, getAllAuthStatus, - getConfigPath, + getCliproxyConfigPath, getInstalledCliproxyVersion, CLIPROXY_DEFAULT_PORT, configNeedsRegeneration, @@ -60,7 +60,7 @@ export class CLIProxyConfigChecker implements IHealthChecker { run(results: HealthCheck): void { const spinner = ora('Checking CLIProxy config').start(); - const configPath = getConfigPath(); + const configPath = getCliproxyConfigPath(); if (fs.existsSync(configPath)) { // Check if config needs regeneration (version mismatch or missing features) diff --git a/src/utils/config-manager.ts b/src/utils/config-manager.ts index a28938e5..08368f45 100644 --- a/src/utils/config-manager.ts +++ b/src/utils/config-manager.ts @@ -27,12 +27,26 @@ export function getCcsDir(): string { } /** - * Get config file path + * Get config file path (legacy JSON path) + * @deprecated Use getActiveConfigPath() for mode-aware config path */ export function getConfigPath(): string { return process.env.CCS_CONFIG || path.join(getCcsHome(), '.ccs', 'config.json'); } +/** + * Get the active config file path based on current mode. + * Returns config.yaml in unified mode, config.json in legacy mode. + * @returns Path to the active config file + */ +export function getActiveConfigPath(): string { + const ccsDir = getCcsDir(); + if (isUnifiedMode()) { + return path.join(ccsDir, 'config.yaml'); + } + return path.join(ccsDir, 'config.json'); +} + /** * Load and validate config.json */ @@ -126,7 +140,8 @@ export function loadConfigSafe(): Config { const configPath = getConfigPath(); if (!fs.existsSync(configPath)) { - throw new Error(`Config not found: ${configPath}`); + // Return empty config for graceful degradation (matches unified mode behavior) + return { profiles: {} }; } const raw = fs.readFileSync(configPath, 'utf8'); diff --git a/src/web-server/health/cliproxy-checks.ts b/src/web-server/health/cliproxy-checks.ts index 80f33e8e..5c1ee2f1 100644 --- a/src/web-server/health/cliproxy-checks.ts +++ b/src/web-server/health/cliproxy-checks.ts @@ -9,7 +9,7 @@ import { isCLIProxyInstalled, getInstalledCliproxyVersion, getCLIProxyPath, - getConfigPath as getCliproxyConfigPath, + getCliproxyConfigPath, getAllAuthStatus, CLIPROXY_DEFAULT_PORT, } from '../../cliproxy'; diff --git a/src/web-server/routes/cliproxy-stats-routes.ts b/src/web-server/routes/cliproxy-stats-routes.ts index a4ac196c..53e32c9a 100644 --- a/src/web-server/routes/cliproxy-stats-routes.ts +++ b/src/web-server/routes/cliproxy-stats-routes.ts @@ -14,7 +14,7 @@ import { } from '../../cliproxy/stats-fetcher'; import { getCliproxyWritablePath, - getConfigPath, + getCliproxyConfigPath, getAuthDir, } from '../../cliproxy/config-generator'; import { getProxyStatus as getProxyProcessStatus, stopProxy } from '../../cliproxy/session-tracker'; @@ -272,7 +272,7 @@ router.get('/error-logs/:name', async (req: Request, res: Response): Promise => { try { - const configPath = getConfigPath(); + const configPath = getCliproxyConfigPath(); if (!fs.existsSync(configPath)) { res.status(404).json({ error: 'Config file not found' }); return; @@ -299,7 +299,7 @@ router.put('/config.yaml', async (req: Request, res: Response): Promise => return; } - const configPath = getConfigPath(); + const configPath = getCliproxyConfigPath(); // Ensure parent directory exists const configDir = path.dirname(configPath);