Skip to content

Commit 5ca5e31

Browse files
mylukinclaude
andcommitted
fix(cli): require user confirmation before CLI update
- Show update notification on startup (non-blocking via update-notifier) - Ask for user confirmation (y/N) before running update - Skip confirmation in CI mode or JSON output mode - Update tests to match new flow Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent fc2ea6d commit 5ca5e31

File tree

7 files changed

+65
-16
lines changed

7 files changed

+65
-16
lines changed

.claude-plugin/marketplace.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
{
22
"$schema": "https://anthropic.com/claude-code/marketplace.schema.json",
33
"name": "ralph-dev",
4-
"version": "0.4.6",
4+
"version": "0.4.7",
55
"description": "Autonomous end-to-end development system that transforms requirements into production-ready code",
66
"owner": {
77
"name": "Lukin",
88
"email": "[email protected]"
99
},
1010
"metadata": {
1111
"description": "Autonomous end-to-end development system that transforms requirements into production-ready code with zero manual intervention",
12-
"version": "0.4.6",
12+
"version": "0.4.7",
1313
"homepage": "https://github.com/mylukin/ralph-dev",
1414
"repository": "https://github.com/mylukin/ralph-dev"
1515
},
@@ -18,7 +18,7 @@
1818
"name": "ralph-dev",
1919
"source": "./",
2020
"description": "Autonomous end-to-end development system - from requirement to production-ready code with zero manual intervention",
21-
"version": "0.4.6",
21+
"version": "0.4.7",
2222
"author": {
2323
"name": "Lukin",
2424
"email": "[email protected]"

.claude-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "ralph-dev",
3-
"version": "0.4.6",
3+
"version": "0.4.7",
44
"description": "Autonomous end-to-end development system - from requirement to production-ready code with zero manual intervention",
55
"author": {
66
"name": "Lukin",

cli/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cli/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "ralph-dev",
3-
"version": "0.4.6",
3+
"version": "0.4.7",
44
"description": "CLI tool for Ralph-dev - efficient operations for AI agents",
55
"main": "dist/index.js",
66
"bin": {

cli/src/commands/update.ts

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { execSync } from 'child_process';
99
import { existsSync, mkdirSync, rmSync, readdirSync } from 'fs';
1010
import { join } from 'path';
1111
import { homedir } from 'os';
12+
import { createInterface } from 'readline';
1213
import chalk from 'chalk';
1314
import { ExitCode } from '../core/exit-codes';
1415
import { handleError, Errors } from '../core/error-handler';
@@ -33,6 +34,24 @@ interface UpdateResult {
3334
const GITHUB_REPO = 'mylukin/ralph-dev';
3435
const PACKAGE_NAME = 'ralph-dev';
3536

37+
/**
38+
* Prompt user for confirmation
39+
*/
40+
function askConfirmation(question: string): Promise<boolean> {
41+
return new Promise((resolve) => {
42+
const rl = createInterface({
43+
input: process.stdin,
44+
output: process.stdout,
45+
});
46+
47+
rl.question(question, (answer) => {
48+
rl.close();
49+
const normalized = answer.toLowerCase().trim();
50+
resolve(normalized === 'y' || normalized === 'yes');
51+
});
52+
});
53+
}
54+
3655
/**
3756
* Get the latest version from npm registry
3857
*/
@@ -285,8 +304,31 @@ export function registerUpdateCommand(program: Command): void {
285304
process.exit(ExitCode.SUCCESS);
286305
}
287306

307+
// Check for updates first
308+
const latestVersion = getLatestVersion();
309+
const hasUpdate = latestVersion ? latestVersion !== currentVersion : true; // Assume update if can't check
310+
const versionKnown = latestVersion !== null;
311+
312+
if (!hasUpdate && !options.pluginOnly) {
313+
console.log(chalk.green(`\n✓ CLI is already at the latest version (${currentVersion})\n`));
314+
}
315+
316+
// Ask for confirmation if there's an update (skip in JSON mode or CI)
317+
if (hasUpdate && !options.pluginOnly && !options.json && !process.env.CI) {
318+
if (versionKnown) {
319+
console.log(chalk.yellow(`\n📦 Update available: ${currentVersion}${latestVersion}\n`));
320+
} else {
321+
console.log(chalk.yellow(`\n📦 Checking for updates...\n`));
322+
}
323+
const confirmed = await askConfirmation('Do you want to update? (y/N): ');
324+
if (!confirmed) {
325+
console.log(chalk.dim('\nUpdate cancelled.\n'));
326+
process.exit(ExitCode.SUCCESS);
327+
}
328+
}
329+
288330
// Update CLI
289-
if (!options.pluginOnly) {
331+
if (!options.pluginOnly && hasUpdate) {
290332
const cliResult = updateCLI();
291333
result.cli.updated = cliResult.success;
292334
result.cli.newVersion = cliResult.newVersion;

cli/src/index.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,11 @@ const shouldCheckUpdate = !process.env.NO_UPDATE_NOTIFIER && !process.env.CI;
3838

3939
if (shouldCheckUpdate) {
4040
const updateService = createAutoUpdateService(name, version, {
41-
autoUpdate: true,
41+
autoUpdate: false, // Don't auto-update, just show notification
4242
});
4343

44-
// Run update check in background (don't await to not block CLI)
45-
updateService.checkAndUpdate().catch(() => {
46-
// Silently ignore update check errors
47-
});
44+
// Show update notification (non-blocking)
45+
updateService.notify();
4846
}
4947

5048
// Parse command line arguments

cli/tests/commands/update.test.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -218,9 +218,14 @@ describe('Update Command Integration', () => {
218218

219219
let callCount = 0;
220220
vi.mocked(execSync).mockImplementation((cmd: any) => {
221+
const cmdStr = String(cmd);
221222
callCount++;
222-
if (callCount === 1) throw new Error('npm failed');
223-
return '0.5.0\n';
223+
// First call is npm view (version check) - return higher version to trigger update
224+
if (cmdStr.includes('npm view')) return '99.0.0\n';
225+
// Second call is npm install - fail it
226+
if (callCount === 2) throw new Error('npm failed');
227+
// Third call is npx npm install - succeed
228+
return '99.0.0\n';
224229
});
225230

226231
await program.parseAsync(['node', 'test', 'update', '--cli-only']);
@@ -236,7 +241,11 @@ describe('Update Command Integration', () => {
236241
const fs = await import('fs');
237242

238243
vi.mocked(fs.existsSync).mockReturnValue(false);
239-
vi.mocked(execSync).mockImplementation(() => {
244+
vi.mocked(execSync).mockImplementation((cmd: any) => {
245+
const cmdStr = String(cmd);
246+
// Version check returns higher version to trigger update
247+
if (cmdStr.includes('npm view')) return '99.0.0\n';
248+
// All install attempts fail
240249
throw new Error('update failed');
241250
});
242251

0 commit comments

Comments
 (0)