From 85e41a56e94e17ab7aeb729f50404eb6c0708df9 Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Sun, 25 Jan 2026 21:21:02 -0500 Subject: [PATCH 1/3] fix(setup): persist setup_completed flag to prevent repeated first-time notice After running ccs setup with local proxy mode, the CLI was still showing the first-time install notice because isFirstTimeInstall() only checked for user-created content (profiles, accounts, variants, etc.). When user chose "Local" mode and skipped API profiles, config had empty objects, causing all checks to return false. Added setup_completed flag to UnifiedConfig interface and set it to true when setup wizard completes. isFirstTimeInstall() now checks this flag first. --- src/commands/setup-command.ts | 6 ++++++ src/config/unified-config-types.ts | 2 ++ 2 files changed, 8 insertions(+) diff --git a/src/commands/setup-command.ts b/src/commands/setup-command.ts index 5b7acf5a..2f826fba 100644 --- a/src/commands/setup-command.ts +++ b/src/commands/setup-command.ts @@ -132,6 +132,11 @@ export function isFirstTimeInstall(): boolean { return false; } + // If setup wizard was completed, NOT first-time install + if (loaded.setup_completed) { + return false; + } + // Check for any meaningful configuration in unified config const hasProfiles = Object.keys(loaded.profiles || {}).length > 0; const hasAccounts = Object.keys(loaded.accounts || {}).length > 0; @@ -370,6 +375,7 @@ async function runSetupWizard(force: boolean = false): Promise { } // Save config + config.setup_completed = true; saveUnifiedConfig(config); // Final summary diff --git a/src/config/unified-config-types.ts b/src/config/unified-config-types.ts index 2315f67d..f2d3618e 100644 --- a/src/config/unified-config-types.ts +++ b/src/config/unified-config-types.ts @@ -521,6 +521,8 @@ export const DEFAULT_DASHBOARD_AUTH_CONFIG: DashboardAuthConfig = { export interface UnifiedConfig { /** Config version (7 for quota management) */ version: number; + /** Flag indicating setup wizard has been completed */ + setup_completed?: boolean; /** Default profile name to use when none specified */ default?: string; /** Account-based profiles (isolated Claude instances) */ From 596a9c68439a2c668a7c6243594a5fd2e57e8b04 Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Sun, 25 Jan 2026 21:26:23 -0500 Subject: [PATCH 2/3] test(setup): add unit tests for setup_completed flag detection - Add 3 test cases for setup_completed flag: - Detect flag when present and true - Treat missing flag as first-time eligible - Treat false flag as first-time eligible - Update JSDoc to document detection priority order Addresses code review feedback on PR #372. --- src/commands/setup-command.ts | 12 +++--- tests/unit/commands/setup-command.test.ts | 45 +++++++++++++++++++++++ 2 files changed, 52 insertions(+), 5 deletions(-) diff --git a/src/commands/setup-command.ts b/src/commands/setup-command.ts index 2f826fba..bd17af07 100644 --- a/src/commands/setup-command.ts +++ b/src/commands/setup-command.ts @@ -113,12 +113,14 @@ async function selectOption( } /** - * Check if this is a first-time install (config exists but is empty/unconfigured) - * Returns true if user should be prompted to run setup wizard + * Check if this is a first-time install (config exists but is empty/unconfigured). + * Returns true if user should be prompted to run setup wizard. * - * IMPORTANT: Also checks legacy config.json for existing profiles to avoid - * treating users with existing GLM/Kimi setups as "first-time installs" - * (Fix for issue #195 - GLM auth persistence regression) + * Detection priority: + * 1. setup_completed flag (explicit wizard completion) + * 2. Unified config content (profiles, accounts, variants, oauth_accounts, remote proxy) + * 3. Legacy config.json profiles (GLM, Kimi) + * 4. Legacy profiles.json accounts */ export function isFirstTimeInstall(): boolean { // Check unified config first (config.yaml) diff --git a/tests/unit/commands/setup-command.test.ts b/tests/unit/commands/setup-command.test.ts index e4bb37b4..8277aefb 100644 --- a/tests/unit/commands/setup-command.test.ts +++ b/tests/unit/commands/setup-command.test.ts @@ -115,6 +115,51 @@ cliproxy_server: }); }); + describe('setup_completed flag detection', () => { + it('should detect setup_completed flag in unified config', () => { + createConfigYaml(` +version: 8 +setup_completed: true +profiles: {} +accounts: {} +`); + + const content = fs.readFileSync(path.join(testDir, 'config.yaml'), 'utf8'); + const hasSetupCompleted = content.includes('setup_completed: true'); + + expect(hasSetupCompleted).toBe(true); + }); + + it('should treat missing setup_completed as first-time eligible', () => { + createConfigYaml(` +version: 8 +profiles: {} +accounts: {} +`); + + const content = fs.readFileSync(path.join(testDir, 'config.yaml'), 'utf8'); + const hasSetupCompleted = content.includes('setup_completed: true'); + + expect(hasSetupCompleted).toBe(false); + }); + + it('should treat setup_completed: false as first-time eligible', () => { + createConfigYaml(` +version: 8 +setup_completed: false +profiles: {} +accounts: {} +`); + + const content = fs.readFileSync(path.join(testDir, 'config.yaml'), 'utf8'); + const hasSetupCompletedTrue = content.includes('setup_completed: true'); + const hasSetupCompletedFalse = content.includes('setup_completed: false'); + + expect(hasSetupCompletedTrue).toBe(false); + expect(hasSetupCompletedFalse).toBe(true); + }); + }); + describe('corrupted config handling', () => { it('should handle corrupted config.json gracefully', () => { fs.writeFileSync(path.join(testDir, 'config.json'), 'not valid json{{{', 'utf8'); From a8c46cc8ed6743f3cfb07bbe621b644c9b2d6830 Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Sun, 25 Jan 2026 21:33:29 -0500 Subject: [PATCH 3/3] fix(config): persist setup_completed flag to YAML file Critical fix: setup_completed was set in memory but never written to disk. - Add setup_completed to generateYamlWithComments() for YAML serialization - Add setup_completed to mergeWithDefaults() to preserve during config merge Without these changes, the flag was lost on save/reload, causing first-time notice to reappear. Addresses critical code review feedback on PR #372. --- src/config/unified-config-loader.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/config/unified-config-loader.ts b/src/config/unified-config-loader.ts index a495b7e3..4cf39d60 100644 --- a/src/config/unified-config-loader.ts +++ b/src/config/unified-config-loader.ts @@ -139,6 +139,7 @@ function mergeWithDefaults(partial: Partial): UnifiedConfig { const defaults = createEmptyUnifiedConfig(); return { version: partial.version ?? defaults.version, + setup_completed: partial.setup_completed, default: partial.default ?? defaults.default, accounts: partial.accounts ?? defaults.accounts, profiles: partial.profiles ?? defaults.profiles, @@ -326,6 +327,9 @@ function generateYamlWithComments(config: UnifiedConfig): string { // Version lines.push(`version: ${config.version}`); + if (config.setup_completed !== undefined) { + lines.push(`setup_completed: ${config.setup_completed}`); + } lines.push(''); // Default