Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/update-yes-flag.md
Original file line number Diff line number Diff line change
@@ -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.
8 changes: 5 additions & 3 deletions apps/kimi-code/src/cli/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<void>;
export type UpgradeCommandHandler = (yes: boolean) => void | Promise<void>;
export type UpdateDownloadHandler = (version: string, manual: boolean) => void;

export function createProgram(
Expand All @@ -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]')
Expand Down Expand Up @@ -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
Expand Down
46 changes: 25 additions & 21 deletions apps/kimi-code/src/cli/sub/upgrade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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,
Expand All @@ -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 {
Expand Down Expand Up @@ -186,6 +189,7 @@ function createDefaultUpgradeDeps(overrides: Partial<UpgradeDeps>): 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,
};
Expand Down
8 changes: 4 additions & 4 deletions apps/kimi-code/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ async function handleMigrateCommand(
await runShell(MIGRATE_CLI_OPTIONS, version, { migrateOnly: true });
}

export async function handleUpgradeCommand(version: string): Promise<void> {
export async function handleUpgradeCommand(version: string, yes: boolean): Promise<void> {
const telemetryBootstrap = createCliTelemetryBootstrap();
const telemetryClient: TelemetryClient = {
track,
Expand All @@ -137,7 +137,7 @@ export async function handleUpgradeCommand(version: string): Promise<void> {
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(() => {});
Expand Down Expand Up @@ -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`);
Expand Down
5 changes: 3 additions & 2 deletions apps/kimi-code/test/cli/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,12 +205,12 @@ async function runHandleMainCommand(opts: CLIOptions): Promise<number | null> {
}
}

async function runHandleUpgradeCommand(): Promise<number> {
async function runHandleUpgradeCommand(yes = false): Promise<number> {
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) {
Expand Down Expand Up @@ -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);
Expand Down
18 changes: 9 additions & 9 deletions apps/kimi-code/test/cli/options.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -524,16 +524,16 @@ 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',
() => {
throw new Error('main action should not run');
},
() => {},
() => {},
() => {
upgradeCalls += 1;
(yes) => {
upgradeYes.push(yes);
},
);
program.exitOverride();
Expand All @@ -544,20 +544,20 @@ 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',
() => {
throw new Error('main action should not run');
},
() => {},
() => {},
() => {
upgradeCalls += 1;
(yes) => {
upgradeYes.push(yes);
},
);
program.exitOverride();
Expand All @@ -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', () => {
Expand Down
16 changes: 15 additions & 1 deletion apps/kimi-code/test/cli/upgrade.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 });

Expand All @@ -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 () => {
Expand Down
4 changes: 2 additions & 2 deletions docs/en/reference/kimi-command.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`

Expand Down
4 changes: 2 additions & 2 deletions docs/zh/reference/kimi-command.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`

Expand Down
Loading