From e67d54161dced14aa7ced3986e8176d3d08b4c45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Davor=20Raci=C4=87?= Date: Mon, 9 Feb 2026 19:36:08 +0100 Subject: [PATCH] fix: add --yes flag guards to all unguarded prompts in update/modify path Guard 5 interactive prompts in ui.js that caused the installer to hang when --yes flag was used with existing installations. Add skipPrompts field to 3 return objects that were missing it, ensuring installer.js downstream guards work correctly for all update paths. Co-Authored-By: Claude Opus 4.6 --- tools/cli/lib/ui.js | 62 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/tools/cli/lib/ui.js b/tools/cli/lib/ui.js index 76114c8a9..224d147e3 100644 --- a/tools/cli/lib/ui.js +++ b/tools/cli/lib/ui.js @@ -189,7 +189,7 @@ class UI { const installedVersion = existingInstall.version || 'unknown'; // Check if version is pre beta - const shouldProceed = await this.showLegacyVersionWarning(installedVersion, currentVersion, path.basename(bmadDir)); + const shouldProceed = await this.showLegacyVersionWarning(installedVersion, currentVersion, path.basename(bmadDir), options); // If user chose to cancel, exit the installer if (!shouldProceed) { @@ -227,6 +227,14 @@ class UI { } actionType = options.action; await prompts.log.info(`Using action from command-line: ${actionType}`); + } else if (options.yes) { + // Default to quick-update if available, otherwise first available choice + if (choices.length === 0) { + throw new Error('No valid actions available for this installation'); + } + const hasQuickUpdate = choices.some((c) => c.value === 'quick-update'); + actionType = hasQuickUpdate ? 'quick-update' : choices[0].value; + await prompts.log.info(`Non-interactive mode (--yes): defaulting to ${actionType}`); } else { actionType = await prompts.select({ message: 'How would you like to proceed?', @@ -242,6 +250,7 @@ class UI { actionType: 'quick-update', directory: confirmedDirectory, customContent: { hasCustomContent: false }, + skipPrompts: options.yes || false, }; } @@ -252,6 +261,7 @@ class UI { actionType: 'compile-agents', directory: confirmedDirectory, customContent: { hasCustomContent: false }, + skipPrompts: options.yes || false, }; } @@ -272,6 +282,11 @@ class UI { .map((m) => m.trim()) .filter(Boolean); await prompts.log.info(`Using modules from command-line: ${selectedModules.join(', ')}`); + } else if (options.yes) { + selectedModules = await this.getDefaultModules(installedModuleIds); + await prompts.log.info( + `Non-interactive mode (--yes): using default modules (installed + defaults): ${selectedModules.join(', ')}`, + ); } else { selectedModules = await this.selectAllModules(installedModuleIds); } @@ -330,6 +345,22 @@ class UI { }, }; } + } else if (options.yes) { + // Non-interactive mode: preserve existing custom modules (matches default: false) + const cacheDir = path.join(bmadDir, '_config', 'custom'); + if (await fs.pathExists(cacheDir)) { + const entries = await fs.readdir(cacheDir, { withFileTypes: true }); + for (const entry of entries) { + if (entry.isDirectory()) { + customModuleResult.selectedCustomModules.push(entry.name); + } + } + await prompts.log.info( + `Non-interactive mode (--yes): preserving ${customModuleResult.selectedCustomModules.length} existing custom module(s)`, + ); + } else { + await prompts.log.info('Non-interactive mode (--yes): no existing custom modules found'); + } } else { const changeCustomModules = await prompts.confirm({ message: 'Modify custom modules, agents, or workflows?', @@ -378,6 +409,7 @@ class UI { skipIde: toolSelection.skipIde, coreConfig: coreConfig, customContent: customModuleResult.customContentConfig, + skipPrompts: options.yes || false, }; } } @@ -529,6 +561,27 @@ class UI { if (configuredIdes.length > 0) { const allTools = [...preferredIdes, ...otherIdes]; + // Non-interactive: handle --tools and --yes flags before interactive prompt + if (options.tools) { + if (options.tools.toLowerCase() === 'none') { + await prompts.log.info('Skipping tool configuration (--tools none)'); + return { ides: [], skipIde: true }; + } + const selectedIdes = options.tools + .split(',') + .map((t) => t.trim()) + .filter(Boolean); + await prompts.log.info(`Using tools from command-line: ${selectedIdes.join(', ')}`); + await this.displaySelectedTools(selectedIdes, preferredIdes, allTools); + return { ides: selectedIdes, skipIde: false }; + } + + if (options.yes) { + await prompts.log.info(`Non-interactive mode (--yes): keeping configured tools: ${configuredIdes.join(', ')}`); + await this.displaySelectedTools(configuredIdes, preferredIdes, allTools); + return { ides: configuredIdes, skipIde: false }; + } + // Sort: configured tools first, then preferred, then others const sortedTools = [ ...allTools.filter((ide) => configuredIdes.includes(ide.value)), @@ -1630,7 +1683,7 @@ class UI { * @param {string} bmadFolderName - Name of the BMAD folder * @returns {Promise} True if user wants to proceed, false if they cancel */ - async showLegacyVersionWarning(installedVersion, currentVersion, bmadFolderName) { + async showLegacyVersionWarning(installedVersion, currentVersion, bmadFolderName, options = {}) { if (!this.isLegacyVersion(installedVersion)) { return true; // Not legacy, proceed } @@ -1656,6 +1709,11 @@ class UI { await prompts.log.warn('VERSION WARNING'); await prompts.note(warningContent, 'Version Warning'); + if (options.yes) { + await prompts.log.warn('Non-interactive mode (--yes): auto-proceeding with legacy update'); + return true; + } + const proceed = await prompts.select({ message: 'How would you like to proceed?', choices: [