mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 08:17:11 +00:00
fix: run RecoveryManager before early-exit commands and improve config handling
Fixes #214 - Fresh install fails with 'Config not found' when running ccs config before RecoveryManager runs. Changes: - Move RecoveryManager.recoverAll() before all early-exit commands in ccs.ts - Fix loadConfigSafe() to return empty config instead of throwing in legacy mode - Add getActiveConfigPath() for mode-aware config path resolution - Rename cliproxy getConfigPath() to getCliproxyConfigPath() to avoid confusion - Update help-command.ts to show correct config path based on mode This ensures all commands benefit from auto-recovery on fresh installs, and gracefully handles missing config files in all code paths.
This commit is contained in:
+12
-10
@@ -291,6 +291,18 @@ async function main(): Promise<void> {
|
||||
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<void> {
|
||||
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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ export {
|
||||
getCliproxyDir,
|
||||
getProviderAuthDir,
|
||||
getAuthDir,
|
||||
getConfigPath,
|
||||
getCliproxyConfigPath,
|
||||
getBinDir,
|
||||
configExists,
|
||||
deleteConfig,
|
||||
|
||||
@@ -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: '"',
|
||||
|
||||
@@ -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'],
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
isCLIProxyInstalled,
|
||||
getInstalledCliproxyVersion,
|
||||
getCLIProxyPath,
|
||||
getConfigPath as getCliproxyConfigPath,
|
||||
getCliproxyConfigPath,
|
||||
getAllAuthStatus,
|
||||
CLIPROXY_DEFAULT_PORT,
|
||||
} from '../../cliproxy';
|
||||
|
||||
@@ -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<voi
|
||||
*/
|
||||
router.get('/config.yaml', async (_req: Request, res: Response): Promise<void> => {
|
||||
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<void> =>
|
||||
return;
|
||||
}
|
||||
|
||||
const configPath = getConfigPath();
|
||||
const configPath = getCliproxyConfigPath();
|
||||
|
||||
// Ensure parent directory exists
|
||||
const configDir = path.dirname(configPath);
|
||||
|
||||
Reference in New Issue
Block a user