diff --git a/src/ccs.ts b/src/ccs.ts index 6ee9da64..5cc90df5 100644 --- a/src/ccs.ts +++ b/src/ccs.ts @@ -318,6 +318,14 @@ async function main(): Promise { console.error(warn(`CCS directory is under ${cloudService}.`)); console.error(' OAuth tokens in cliproxy/auth/ will be synced to cloud.'); } + } else if (process.env.CCS_HOME) { + // Also warn for CCS_HOME env var pointing to cloud sync + const { detectCloudSyncPath } = await import('./utils/config-manager'); + const cloudService = detectCloudSyncPath(process.env.CCS_HOME); + if (cloudService) { + console.error(warn(`CCS directory is under ${cloudService}.`)); + console.error(' OAuth tokens in cliproxy/auth/ will be synced to cloud.'); + } } const firstArg = args[0]; diff --git a/src/management/recovery-manager.ts b/src/management/recovery-manager.ts index 79cedbcc..ec7e3710 100644 --- a/src/management/recovery-manager.ts +++ b/src/management/recovery-manager.ts @@ -41,7 +41,7 @@ class RecoveryManager { ensureCcsDirectory(): boolean { if (!fs.existsSync(this.ccsDir)) { fs.mkdirSync(this.ccsDir, { recursive: true, mode: 0o755 }); - this.recovered.push('Created ~/.ccs/ directory'); + this.recovered.push(`Created ${this.ccsDir} directory`); return true; } return false; @@ -60,7 +60,7 @@ class RecoveryManager { return false; // Config exists and is valid } // Config exists but is corrupted - will be recreated below - this.recovered.push('Detected corrupted ~/.ccs/config.yaml'); + this.recovered.push(`Detected corrupted ${this.ccsDir}/config.yaml`); } // Check for legacy config.json - if exists, let autoMigrate handle it @@ -76,7 +76,7 @@ class RecoveryManager { try { saveUnifiedConfig(config); - this.recovered.push('Created ~/.ccs/config.yaml'); + this.recovered.push(`Created ${this.ccsDir}/config.yaml`); return true; } catch (_saveErr) { // Fallback: create minimal config.json for backward compat @@ -85,7 +85,7 @@ class RecoveryManager { const tmpPath = `${legacyConfigPath}.tmp`; fs.writeFileSync(tmpPath, JSON.stringify(fallbackConfig, null, 2) + '\n', 'utf8'); fs.renameSync(tmpPath, legacyConfigPath); - this.recovered.push('Created ~/.ccs/config.json (fallback)'); + this.recovered.push(`Created ${this.ccsDir}/config.json (fallback)`); return true; } catch (_fallbackErr) { // Both writes failed - log but don't crash @@ -104,7 +104,7 @@ class RecoveryManager { // Create ~/.claude/ if missing if (!fs.existsSync(this.claudeDir)) { fs.mkdirSync(this.claudeDir, { recursive: true, mode: 0o755 }); - this.recovered.push('Created ~/.claude/ directory'); + this.recovered.push(`Created ${this.claudeDir} directory`); } // Create settings.json if missing @@ -113,7 +113,7 @@ class RecoveryManager { fs.writeFileSync(tmpPath, '{}\n', 'utf8'); fs.renameSync(tmpPath, claudeSettingsPath); - this.recovered.push('Created ~/.claude/settings.json'); + this.recovered.push(`Created ${claudeSettingsPath}`); return true; } @@ -129,7 +129,7 @@ class RecoveryManager { // Create shared directory if (!fs.existsSync(this.sharedDir)) { fs.mkdirSync(this.sharedDir, { recursive: true, mode: 0o755 }); - this.recovered.push('Created ~/.ccs/shared/'); + this.recovered.push(`Created ${this.sharedDir}`); created = true; } @@ -190,7 +190,7 @@ class RecoveryManager { } if (installed) { - this.recovered.push('Installed shell completions to ~/.ccs/completions/'); + this.recovered.push(`Installed shell completions to ${this.completionsDir}`); } return installed; @@ -238,7 +238,7 @@ class RecoveryManager { this.recovered.forEach((msg) => console.log(` - ${msg}`)); // Show login hint if created Claude settings - if (this.recovered.some((msg) => msg.includes('~/.claude/settings.json'))) { + if (this.recovered.some((msg) => msg.includes('settings.json') && msg.includes('.claude'))) { console.log(''); console.log(info('Next step: Login to Claude CLI')); console.log(' Run: claude /login'); diff --git a/src/utils/config-manager.ts b/src/utils/config-manager.ts index ffadad5a..c00d9319 100644 --- a/src/utils/config-manager.ts +++ b/src/utils/config-manager.ts @@ -30,16 +30,25 @@ export function getCcsHome(): string { return process.env.CCS_HOME || os.homedir(); } +/** + * Resolve the CCS directory with source information. + * Single source of truth for precedence logic. + */ +function _resolveCcsDir(): { source: string; dir: string } { + if (_globalConfigDir) return { source: '--config-dir', dir: _globalConfigDir }; + if (process.env.CCS_DIR) return { source: 'CCS_DIR', dir: path.resolve(process.env.CCS_DIR) }; + if (process.env.CCS_HOME) + return { source: 'CCS_HOME', dir: path.join(path.resolve(process.env.CCS_HOME), '.ccs') }; + return { source: 'default', dir: path.join(os.homedir(), '.ccs') }; +} + /** * Get the CCS directory path. * Precedence: --config-dir flag > CCS_DIR env > CCS_HOME env (legacy, appends .ccs) > ~/.ccs default * @returns Path to CCS config directory */ export function getCcsDir(): string { - if (_globalConfigDir) return _globalConfigDir; - if (process.env.CCS_DIR) return path.resolve(process.env.CCS_DIR); - if (process.env.CCS_HOME) return path.join(path.resolve(process.env.CCS_HOME), '.ccs'); - return path.join(os.homedir(), '.ccs'); + return _resolveCcsDir().dir; } /** @@ -47,11 +56,8 @@ export function getCcsDir(): string { * @returns [source_label, resolved_path] tuple */ export function getCcsDirSource(): [string, string] { - if (_globalConfigDir) return ['--config-dir', _globalConfigDir]; - if (process.env.CCS_DIR) return ['CCS_DIR', path.resolve(process.env.CCS_DIR)]; - if (process.env.CCS_HOME) - return ['CCS_HOME', path.join(path.resolve(process.env.CCS_HOME), '.ccs')]; - return ['default', path.join(os.homedir(), '.ccs')]; + const r = _resolveCcsDir(); + return [r.source, r.dir]; } /** diff --git a/src/web-server/middleware/auth-middleware.ts b/src/web-server/middleware/auth-middleware.ts index 664f5782..044963d7 100644 --- a/src/web-server/middleware/auth-middleware.ts +++ b/src/web-server/middleware/auth-middleware.ts @@ -38,10 +38,12 @@ function getSessionSecret(): string { return process.env.CCS_SESSION_SECRET; } + const secretPath = getSessionSecretPath(); + // 2. Try to read persisted secret try { - if (fs.existsSync(getSessionSecretPath())) { - const secret = fs.readFileSync(getSessionSecretPath(), 'utf-8').trim(); + if (fs.existsSync(secretPath)) { + const secret = fs.readFileSync(secretPath, 'utf-8').trim(); if (secret.length >= 32) { return secret; } @@ -53,11 +55,11 @@ function getSessionSecret(): string { // 3. Generate and persist new random secret const newSecret = crypto.randomBytes(32).toString('hex'); try { - const dir = path.dirname(getSessionSecretPath()); + const dir = path.dirname(secretPath); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } - fs.writeFileSync(getSessionSecretPath(), newSecret, { mode: 0o600 }); + fs.writeFileSync(secretPath, newSecret, { mode: 0o600 }); } catch (err) { // Log warning - sessions won't persist across restarts console.warn('[!] Failed to persist session secret:', (err as Error).message); diff --git a/src/web-server/usage/disk-cache.ts b/src/web-server/usage/disk-cache.ts index 46836bfc..45aa981e 100644 --- a/src/web-server/usage/disk-cache.ts +++ b/src/web-server/usage/disk-cache.ts @@ -42,8 +42,9 @@ const CACHE_VERSION = 3; * Ensure ~/.ccs/cache directory exists */ function ensureCacheDir(): void { - if (!fs.existsSync(getCacheDir())) { - fs.mkdirSync(getCacheDir(), { recursive: true }); + const dir = getCacheDir(); + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); } }