diff --git a/.changeset/update-yes-flag.md b/.changeset/update-yes-flag.md new file mode 100644 index 00000000000..0567b158387 --- /dev/null +++ b/.changeset/update-yes-flag.md @@ -0,0 +1,5 @@ +--- +"@moonshot-ai/kimi-code": patch +--- + +Add `-y, --yes` to `kimi upgrade` (alias `kimi update`) to skip the confirmation prompt and install the update directly. diff --git a/apps/kimi-code/src/cli/commands.ts b/apps/kimi-code/src/cli/commands.ts index 4ee0f26f4cb..e6022a8f119 100644 --- a/apps/kimi-code/src/cli/commands.ts +++ b/apps/kimi-code/src/cli/commands.ts @@ -16,7 +16,7 @@ import { registerWebCommand } from './sub/web'; export type MainCommandHandler = (opts: CLIOptions) => void; export type MigrateCommandHandler = (options: MigrateCommandOptions) => void; export type PluginNodeRunnerHandler = (entry: string, args: readonly string[]) => void; -export type UpgradeCommandHandler = () => void | Promise; +export type UpgradeCommandHandler = (yes: boolean) => void | Promise; export type UpdateDownloadHandler = (version: string, manual: boolean) => void; export function createProgram( @@ -31,6 +31,7 @@ export function createProgram( .description('The Starting Point for Next-Gen Agents') .version(version, '-V, --version') .allowUnknownOption(false) + .enablePositionalOptions() .configureHelp({ helpWidth: 100 }) .helpOption('-h, --help', 'Show help.') .usage('[options] [command]') @@ -131,8 +132,9 @@ export function createProgram( .command('upgrade') .alias('update') .description('Upgrade Kimi Code to the latest version.') - .action(async () => { - await onUpgrade(); + .option('-y, --yes', 'Skip the confirmation prompt and install the update directly.', false) + .action(async (options: { yes?: boolean }) => { + await onUpgrade(options.yes === true); }); program diff --git a/apps/kimi-code/src/cli/sub/upgrade.ts b/apps/kimi-code/src/cli/sub/upgrade.ts index 861d6ddf84e..0cac2f1d9eb 100644 --- a/apps/kimi-code/src/cli/sub/upgrade.ts +++ b/apps/kimi-code/src/cli/sub/upgrade.ts @@ -46,6 +46,7 @@ export interface UpgradeDeps { readonly stdout: WritableLike; readonly stderr: WritableLike; readonly isInteractive: boolean; + readonly yes: boolean; readonly track: UpgradeTrack; readonly logger: UpgradeLogger; } @@ -88,7 +89,7 @@ export async function handleUpgrade( const source = await deps.detectInstallSource().catch(() => 'unsupported' as const); const installCommand = installCommandFor(source, target.version, deps.platform); - if (!canAutoInstall(source, deps.platform) || !deps.isInteractive) { + if (!canAutoInstall(source, deps.platform) || (!deps.yes && !deps.isInteractive)) { trackUpgradeEvent(deps.track, 'upgrade_command_manual_command', { current_version: currentVersion, target_version: target.version, @@ -103,34 +104,36 @@ export async function handleUpgrade( return 0; } - trackUpgradeEvent(deps.track, 'upgrade_command_prompted', { - current_version: currentVersion, - target_version: target.version, - source, - }); - logUpgradeInfo(deps.logger, 'manual upgrade prompted', { - currentVersion, - targetVersion: target.version, - source, - }); - const choice = await deps.promptForInstallChoice({ - currentVersion, - target, - installCommand, - installSource: source, - }); - if (choice === 'skip') { - trackUpgradeEvent(deps.track, 'upgrade_command_skipped', { + if (!deps.yes) { + trackUpgradeEvent(deps.track, 'upgrade_command_prompted', { current_version: currentVersion, target_version: target.version, source, }); - logUpgradeInfo(deps.logger, 'manual upgrade skipped', { + logUpgradeInfo(deps.logger, 'manual upgrade prompted', { currentVersion, targetVersion: target.version, source, }); - return 0; + const choice = await deps.promptForInstallChoice({ + currentVersion, + target, + installCommand, + installSource: source, + }); + if (choice === 'skip') { + trackUpgradeEvent(deps.track, 'upgrade_command_skipped', { + current_version: currentVersion, + target_version: target.version, + source, + }); + logUpgradeInfo(deps.logger, 'manual upgrade skipped', { + currentVersion, + targetVersion: target.version, + source, + }); + return 0; + } } try { @@ -186,6 +189,7 @@ function createDefaultUpgradeDeps(overrides: Partial): UpgradeDeps stdout: overrides.stdout ?? process.stdout, stderr: overrides.stderr ?? process.stderr, isInteractive: overrides.isInteractive ?? (process.stdin.isTTY && process.stdout.isTTY), + yes: overrides.yes ?? false, track: overrides.track ?? trackTelemetry, logger: overrides.logger ?? log, }; diff --git a/apps/kimi-code/src/main.ts b/apps/kimi-code/src/main.ts index 9f78c1322ec..f95d57c2a28 100644 --- a/apps/kimi-code/src/main.ts +++ b/apps/kimi-code/src/main.ts @@ -114,7 +114,7 @@ async function handleMigrateCommand( await runShell(MIGRATE_CLI_OPTIONS, version, { migrateOnly: true }); } -export async function handleUpgradeCommand(version: string): Promise { +export async function handleUpgradeCommand(version: string, yes: boolean): Promise { const telemetryBootstrap = createCliTelemetryBootstrap(); const telemetryClient: TelemetryClient = { track, @@ -137,7 +137,7 @@ export async function handleUpgradeCommand(version: string): Promise { version, uiMode: CLI_UI_MODE, }); - exitCode = await handleUpgrade(version, { track, logger: log }); + exitCode = await handleUpgrade(version, { track, logger: log, yes }); } finally { await shutdownTelemetry({ timeoutMs: CLI_SHUTDOWN_TIMEOUT_MS }).catch(() => {}); await harness.close().catch(() => {}); @@ -275,8 +275,8 @@ function bootstrap(): void { process.exit(1); }); }, - () => { - void handleUpgradeCommand(version).catch(async (error: unknown) => { + (yes) => { + void handleUpgradeCommand(version, yes).catch(async (error: unknown) => { await logStartupFailure('upgrade', error); process.stderr.write(formatStartupError(error, { operation: 'upgrade' })); process.stderr.write(`See log: ${resolveGlobalLogPath(resolveKimiHome())}\n`); diff --git a/apps/kimi-code/test/cli/main.test.ts b/apps/kimi-code/test/cli/main.test.ts index d115c6e7687..edaf222842a 100644 --- a/apps/kimi-code/test/cli/main.test.ts +++ b/apps/kimi-code/test/cli/main.test.ts @@ -205,12 +205,12 @@ async function runHandleMainCommand(opts: CLIOptions): Promise { } } -async function runHandleUpgradeCommand(): Promise { +async function runHandleUpgradeCommand(yes = false): Promise { const exitSpy = vi.spyOn(process, 'exit').mockImplementation((code?: string | number | null) => { throw new ExitCalled(Number(code ?? 0)); }); try { - await handleUpgradeCommand('0.0.1-alpha.2'); + await handleUpgradeCommand('0.0.1-alpha.2', yes); throw new Error('expected process.exit'); } catch (error) { if (error instanceof ExitCalled) { @@ -464,6 +464,7 @@ describe('main entry command handling', () => { expect(mocks.handleUpgrade).toHaveBeenCalledWith('0.0.1-alpha.2', { track: mocks.track, logger: mocks.log, + yes: false, }); expect(mocks.shutdownTelemetry).toHaveBeenCalledWith({ timeoutMs: 3000 }); expect(mocks.harness.close).toHaveBeenCalledTimes(1); diff --git a/apps/kimi-code/test/cli/options.test.ts b/apps/kimi-code/test/cli/options.test.ts index 23cd8ccc72a..aa1cfb2cac4 100644 --- a/apps/kimi-code/test/cli/options.test.ts +++ b/apps/kimi-code/test/cli/options.test.ts @@ -524,7 +524,7 @@ describe('CLI options parsing', () => { describe('sub-commands', () => { it('routes upgrade without calling the main action', () => { - let upgradeCalls = 0; + const upgradeYes: boolean[] = []; const program = createProgram( '0.0.0', () => { @@ -532,8 +532,8 @@ describe('CLI options parsing', () => { }, () => {}, () => {}, - () => { - upgradeCalls += 1; + (yes) => { + upgradeYes.push(yes); }, ); program.exitOverride(); @@ -544,11 +544,11 @@ describe('CLI options parsing', () => { program.parse(['node', 'kimi', 'upgrade']); - expect(upgradeCalls).toBe(1); + expect(upgradeYes).toEqual([false]); }); it('routes update alias to the upgrade handler', () => { - let upgradeCalls = 0; + const upgradeYes: boolean[] = []; const program = createProgram( '0.0.0', () => { @@ -556,8 +556,8 @@ describe('CLI options parsing', () => { }, () => {}, () => {}, - () => { - upgradeCalls += 1; + (yes) => { + upgradeYes.push(yes); }, ); program.exitOverride(); @@ -566,9 +566,9 @@ describe('CLI options parsing', () => { writeErr: () => {}, }); - program.parse(['node', 'kimi', 'update']); + program.parse(['node', 'kimi', 'update', '-y']); - expect(upgradeCalls).toBe(1); + expect(upgradeYes).toEqual([true]); }); it('registers the visible sub-commands', () => { diff --git a/apps/kimi-code/test/cli/upgrade.test.ts b/apps/kimi-code/test/cli/upgrade.test.ts index 31b53b1d983..db270f94a8d 100644 --- a/apps/kimi-code/test/cli/upgrade.test.ts +++ b/apps/kimi-code/test/cli/upgrade.test.ts @@ -157,7 +157,7 @@ describe('handleUpgrade', () => { expect(stdout.join('')).toContain('To update manually, run: npm install -g @moonshot-ai/kimi-code@0.5.0'); }); - it('prints the manual update command without prompting when not interactive', async () => { + it('prints the manual update command without prompting when not interactive, and installs directly with yes', async () => { const { stdout, writable } = captureOutput(); const deps = createDeps({ latest: '0.5.0', source: 'npm-global', isInteractive: false }); @@ -170,6 +170,20 @@ describe('handleUpgrade', () => { source: 'npm-global', })); expect(stdout.join('')).toContain('To update manually, run: npm install -g @moonshot-ai/kimi-code@0.5.0'); + + const yesRun = captureOutput(); + const yesDeps = createDeps({ latest: '0.5.0', source: 'npm-global', isInteractive: false }); + + await expect(handleUpgrade('0.4.0', { ...yesDeps, ...yesRun.writable, yes: true })).resolves.toBe(0); + + expect(yesDeps.promptForInstallChoice).not.toHaveBeenCalled(); + expect(yesDeps.installUpdate).toHaveBeenCalledWith('npm-global', '0.5.0', 'darwin'); + expect(yesDeps.track).not.toHaveBeenCalledWith('upgrade_command_prompted', expect.anything()); + expect(yesDeps.track).toHaveBeenCalledWith('upgrade_command_install_selected', expect.objectContaining({ + target_version: '0.5.0', + source: 'npm-global', + })); + expect(yesRun.stdout.join('')).toContain('Updated @moonshot-ai/kimi-code to 0.5.0'); }); it('returns a failing exit code when the foreground install fails', async () => { diff --git a/docs/en/reference/kimi-command.md b/docs/en/reference/kimi-command.md index 915155bf7e3..280e8fe9575 100644 --- a/docs/en/reference/kimi-command.md +++ b/docs/en/reference/kimi-command.md @@ -266,10 +266,10 @@ For full migration instructions, see [Migrating from kimi-cli](../guides/migrati Immediately check for the latest version and display an update prompt; exits after you make a selection. `kimi update` is an alias for this command. ```sh -kimi upgrade +kimi upgrade [-y] ``` -For global npm, pnpm, yarn, and bun installations, `kimi upgrade` shows update options; selecting `Install update now` runs the corresponding foreground install command. For native installations (including Windows), it downloads and verifies the new binary in the foreground and swaps it in on the next start. When the current installation method cannot be upgraded automatically, the manual update command is printed instead. +For global npm, pnpm, yarn, and bun installations, `kimi upgrade` shows update options; selecting `Install update now` runs the corresponding foreground install command. For native installations (including Windows), it downloads and verifies the new binary in the foreground and swaps it in on the next start. When the current installation method cannot be upgraded automatically, the manual update command is printed instead. Pass `-y, --yes` to skip the confirmation prompt and install the update directly. ### `kimi vis` diff --git a/docs/zh/reference/kimi-command.md b/docs/zh/reference/kimi-command.md index ce1caba10ad..5b9483918a1 100644 --- a/docs/zh/reference/kimi-command.md +++ b/docs/zh/reference/kimi-command.md @@ -266,10 +266,10 @@ kimi migrate 立即检查最新版本并展示更新提示,选择操作后退出。也可以使用别名 `kimi update`。 ```sh -kimi upgrade +kimi upgrade [-y] ``` -对全局 npm、pnpm、yarn、bun 安装,`kimi upgrade` 会展示更新选项;选择 `Install update now` 后运行对应的前台安装命令。对 native 安装(含 Windows),会在前台下载并校验新二进制,并在下次启动时替换生效。当前安装方式无法自动升级时,改为打印手动更新命令。 +对全局 npm、pnpm、yarn、bun 安装,`kimi upgrade` 会展示更新选项;选择 `Install update now` 后运行对应的前台安装命令。对 native 安装(含 Windows),会在前台下载并校验新二进制,并在下次启动时替换生效。当前安装方式无法自动升级时,改为打印手动更新命令。传入 `-y, --yes` 可跳过确认提示,直接安装更新。 ### `kimi vis`