diff --git a/.changeset/doctor-windows-path-separators.md b/.changeset/doctor-windows-path-separators.md new file mode 100644 index 000000000..df001362f --- /dev/null +++ b/.changeset/doctor-windows-path-separators.md @@ -0,0 +1,5 @@ +--- +"@moonshot-ai/kimi-code": patch +--- + +Fix mixed path separators in `kimi doctor` output on Windows (config.toml showed `C:/...` while tui.toml showed `C:\...`) diff --git a/apps/kimi-code/src/cli/sub/doctor.ts b/apps/kimi-code/src/cli/sub/doctor.ts index 0ccc38d28..8431c5a1d 100644 --- a/apps/kimi-code/src/cli/sub/doctor.ts +++ b/apps/kimi-code/src/cli/sub/doctor.ts @@ -1,6 +1,6 @@ import { existsSync } from 'node:fs'; import { readFile } from 'node:fs/promises'; -import { isAbsolute, resolve } from 'node:path'; +import { isAbsolute, normalize, resolve } from 'node:path'; import { createKimiConfigRpc, @@ -259,7 +259,12 @@ function formatFailure(results: readonly CheckResult[], issueCount: number): str function formatResults(results: readonly CheckResult[]): string[] { const lines: string[] = []; for (const result of results) { - lines.push(`${result.status} ${result.label.padEnd(12)} ${result.path}`); + // Normalize the path to the platform's native separators so config.toml + // (resolved via `pathe`, always `/`) and tui.toml (resolved via + // `node:path`, native separator) render consistently. Without this, + // Windows users see both `C:/Users/me/.kimi-code/config.toml` and + // `C:\Users\me\.kimi-code\tui.toml` in the same output. + lines.push(`${result.status} ${result.label.padEnd(12)} ${normalize(result.path)}`); if (result.message !== undefined) { for (const line of result.message.split('\n')) { lines.push(` ${line}`); diff --git a/apps/kimi-code/src/main.ts b/apps/kimi-code/src/main.ts index 8a7d9fa73..608cbdda1 100644 --- a/apps/kimi-code/src/main.ts +++ b/apps/kimi-code/src/main.ts @@ -5,6 +5,8 @@ * outer update preflight, then delegates to the requested UI runner. */ +import { normalize } from 'node:path'; + import { createKimiHarness, flushDiagnosticLogs, @@ -21,6 +23,16 @@ import { withTelemetryContext, } from '@moonshot-ai/kimi-telemetry'; +/** + * The SDK resolves the log path via `pathe` (forward slashes everywhere) + * which produces `C:/Users/.../kimi-code.log` on Windows. Normalize to the + * platform-native separator before showing the path to the user so it + * matches what they'd see in File Explorer / their shell. + */ +function formatLogPathForUser(homeDir: string): string { + return normalize(resolveGlobalLogPath(homeDir)); +} + import { createProgram } from './cli/commands'; import type { CLIOptions } from './cli/options'; import { OptionConflictError, validateOptions } from './cli/options'; @@ -142,7 +154,7 @@ export function main(): void { operation, }), ); - process.stderr.write(`See log: ${resolveGlobalLogPath(resolveKimiHome())}\n`); + process.stderr.write(`See log: ${formatLogPathForUser(resolveKimiHome())}\n`); process.exit(1); }); }, @@ -150,7 +162,7 @@ export function main(): void { void handleMigrateCommand(version).catch(async (error: unknown) => { await logStartupFailure('run migration', error); process.stderr.write(formatStartupError(error, { operation: 'run migration' })); - process.stderr.write(`See log: ${resolveGlobalLogPath(resolveKimiHome())}\n`); + process.stderr.write(`See log: ${formatLogPathForUser(resolveKimiHome())}\n`); process.exit(1); }); }, @@ -165,7 +177,7 @@ export function main(): void { void handleUpgradeCommand(version).catch(async (error: unknown) => { await logStartupFailure('upgrade', error); process.stderr.write(formatStartupError(error, { operation: 'upgrade' })); - process.stderr.write(`See log: ${resolveGlobalLogPath(resolveKimiHome())}\n`); + process.stderr.write(`See log: ${formatLogPathForUser(resolveKimiHome())}\n`); process.exit(1); }); }, diff --git a/apps/kimi-code/test/cli/doctor.test.ts b/apps/kimi-code/test/cli/doctor.test.ts index b6404c97d..1acc96aa4 100644 --- a/apps/kimi-code/test/cli/doctor.test.ts +++ b/apps/kimi-code/test/cli/doctor.test.ts @@ -267,4 +267,33 @@ max_context_size = "large" expect(err).toContain('Validation issues:'); expect(err).toContain('models.kimi.max_context_size:'); }); + + it('normalizes path separators in formatted output', async () => { + // Regression: config.toml is resolved via `pathe` (always `/`) while + // tui.toml is resolved via `node:path` (native separators on Windows). + // The formatted output must use one consistent separator — otherwise + // Windows users see a mix of `C:/Users/me/...` and `C:\Users\me\...` + // in the same `kimi doctor` block. + const { deps, stdout } = makeDeps(); + const code = await handleDoctor( + { + ...deps, + // Force a `/`-only path here to simulate what `pathe` would yield. + defaultConfigPath: () => 'C:/fixture/.kimi-code/config.toml', + defaultTuiConfigPath: () => 'C:\\fixture\\.kimi-code\\tui.toml', + fileExists: () => false, + }, + {}, + ); + + expect(code).toBe(0); + const out = stdout.join(''); + // On Windows, both lines should use backslashes; on POSIX, both forward + // slashes. Path.sep is what `normalize` aligns to. + const sep = process.platform === 'win32' ? '\\' : '/'; + const expectedConfig = `C:${sep}fixture${sep}.kimi-code${sep}config.toml`; + const expectedTui = `C:${sep}fixture${sep}.kimi-code${sep}tui.toml`; + expect(out).toContain(expectedConfig); + expect(out).toContain(expectedTui); + }); });