fix(cli): resolve code review findings from @clack/prompts migration

Address 31 issues across 14 CLI files found during PR #1586 review
(Augment Code + CodeRabbit):

- Fix bmadDir ReferenceError by hoisting declaration before try block
- Wrap console.log monkey-patch in try/finally for safe restoration
- Fix transformWorkflowPath dead code and undefined return path
- Fix broken symlink crash in _config-driven.js and codex.js cleanup
- Pass installer instance through update() for agent recompilation
- Fix @clack/prompts API: defaultValue→default, initialValue→default
- Use nullish coalescing (??) instead of logical OR for falsy values
- Forward options in recursive promptToolSelection calls
- Remove no-op replaceAll('_bmad','_bmad') in manager and generator
- Remove unused confirm prompt in config-collector hasNoConfig branch
- Guard spinner.message() when spinner is not running
- Add missing methods to silent spinner stub (cancel, clear, isSpinning)
- Wrap install.js error handler with inner try/catch + console fallback
- Gate codex per-entry error log with silent flag
- Add return statements to all stream wrapper methods
- Remove dead variables (availableNames, hasCustomContentItems)
- Filter core module from update flow selection
- Replace borderColor ternary chain with object map
- Fix Kilo "agents" label to "modes" in IDE manager
- Normalize error return shape for unsupported IDEs
- Fix spinner message timing before dependency resolution
- Guard undefined moduleDir in dependency-resolver
- Fix workflowsInstalled counter inflation in custom handler
- Migrate console.warn calls to prompts.log.warn
- Replace console.log() with prompts.log.message('')
- Fix legacyBmadPath hardcoded to .bmad instead of entry.name
- Fix focusedValue never assigned breaking SPACE toggle and hints

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Davor Racić 2026-02-07 22:54:24 +01:00
parent 04edbf6ee3
commit 78090f6f4a
14 changed files with 118 additions and 125 deletions

View File

@ -85,16 +85,17 @@ module.exports = {
process.exit(0); process.exit(0);
} }
} catch (error) { } catch (error) {
// Check if error has a complete formatted message try {
if (error.fullMessage) { if (error.fullMessage) {
console.error(error.fullMessage); await prompts.log.error(error.fullMessage);
} else {
await prompts.log.error(`Installation failed: ${error.message}`);
}
if (error.stack) { if (error.stack) {
await prompts.log.message(error.stack); await prompts.log.message(error.stack);
} }
} else { } catch {
// Generic error handling for all other errors console.error(error.fullMessage || error.message || error);
await prompts.log.error(`Installation failed: ${error.message}`);
await prompts.log.message(error.stack);
} }
process.exit(1); process.exit(1);
} }

View File

@ -344,7 +344,7 @@ class ConfigCollector {
if (questions.length > 0) { if (questions.length > 0) {
// Only show header if we actually have questions // Only show header if we actually have questions
await CLIUtils.displayModuleConfigHeader(moduleName, moduleConfig.header, moduleConfig.subheader); await CLIUtils.displayModuleConfigHeader(moduleName, moduleConfig.header, moduleConfig.subheader);
console.log(); // Line break before questions await prompts.log.message('');
const promptedAnswers = await prompts.prompt(questions); const promptedAnswers = await prompts.prompt(questions);
// Merge prompted answers with static answers // Merge prompted answers with static answers
@ -738,20 +738,7 @@ class ConfigCollector {
const hasNoConfig = actualConfigKeys.length === 0; const hasNoConfig = actualConfigKeys.length === 0;
if (hasNoConfig && (moduleConfig.subheader || moduleConfig.header)) { if (hasNoConfig && (moduleConfig.subheader || moduleConfig.header)) {
// Module explicitly has no configuration - show with special styling
await prompts.log.step(moduleDisplayName); await prompts.log.step(moduleDisplayName);
// Ask user if they want to accept defaults or customize on the next line
const { customize } = await prompts.prompt([
{
type: 'confirm',
name: 'customize',
message: 'Accept Defaults (no to customize)?',
default: true,
},
]);
// Show the subheader if available, otherwise show a default message
if (moduleConfig.subheader) { if (moduleConfig.subheader) {
await prompts.log.message(` \u2713 ${moduleConfig.subheader}`); await prompts.log.message(` \u2713 ${moduleConfig.subheader}`);
} else { } else {

View File

@ -90,6 +90,10 @@ class DependencyResolver {
} }
} }
if (!moduleDir) {
continue;
}
if (!(await fs.pathExists(moduleDir))) { if (!(await fs.pathExists(moduleDir))) {
await prompts.log.warn('Module directory not found: ' + moduleDir); await prompts.log.warn('Module directory not found: ' + moduleDir);
continue; continue;

View File

@ -237,6 +237,7 @@ class Installer {
// before any config collection, so we don't need to check again here // before any config collection, so we don't need to check again here
const projectDir = path.resolve(config.directory); const projectDir = path.resolve(config.directory);
const bmadDir = path.join(projectDir, BMAD_FOLDER_NAME);
// If core config was pre-collected (from interactive mode), use it // If core config was pre-collected (from interactive mode), use it
if (config.coreConfig && Object.keys(config.coreConfig).length > 0) { if (config.coreConfig && Object.keys(config.coreConfig).length > 0) {
@ -374,12 +375,6 @@ class Installer {
spinner.start('Preparing installation...'); spinner.start('Preparing installation...');
try { try {
// Resolve target directory (path.resolve handles platform differences)
const projectDir = path.resolve(config.directory);
// Always use the standard _bmad folder name
const bmadDir = path.join(projectDir, BMAD_FOLDER_NAME);
// Create a project directory if it doesn't exist (user already confirmed) // Create a project directory if it doesn't exist (user already confirmed)
if (!(await fs.pathExists(projectDir))) { if (!(await fs.pathExists(projectDir))) {
spinner.message('Creating installation directory...'); spinner.message('Creating installation directory...');
@ -807,13 +802,13 @@ class Installer {
bmadDir: bmadDir, // Pass bmadDir so we can check cache bmadDir: bmadDir, // Pass bmadDir so we can check cache
}); });
spinner.message('Resolving dependencies...');
const resolution = await this.dependencyResolver.resolve(projectRoot, regularModulesForResolution, { const resolution = await this.dependencyResolver.resolve(projectRoot, regularModulesForResolution, {
verbose: config.verbose, verbose: config.verbose,
moduleManager: tempModuleManager, moduleManager: tempModuleManager,
}); });
spinner.message('Resolving dependencies...');
// Install modules with their dependencies // Install modules with their dependencies
if (allModules && allModules.length > 0) { if (allModules && allModules.length > 0) {
const installedModuleNames = new Set(); const installedModuleNames = new Set();
@ -1020,46 +1015,47 @@ class Installer {
console.log = () => {}; console.log = () => {};
} }
for (const ide of validIdes) { try {
if (!needsPrompting || ideConfigurations[ide]) { for (const ide of validIdes) {
// All IDEs pre-configured, or this specific IDE has config: keep spinner running if (!needsPrompting || ideConfigurations[ide]) {
spinner.message(`Configuring ${ide}...`); // All IDEs pre-configured, or this specific IDE has config: keep spinner running
} else { spinner.message(`Configuring ${ide}...`);
// This IDE needs prompting: stop spinner to allow user interaction } else {
if (spinner.isSpinning) { // This IDE needs prompting: stop spinner to allow user interaction
spinner.stop('Ready for IDE configuration'); if (spinner.isSpinning) {
spinner.stop('Ready for IDE configuration');
}
}
// Silent when this IDE has pre-collected config (no prompts for THIS IDE)
const ideHasConfig = Boolean(ideConfigurations[ide]);
const setupResult = await this.ideManager.setup(ide, projectDir, bmadDir, {
selectedModules: allModules || [],
preCollectedConfig: ideConfigurations[ide] || null,
verbose: config.verbose,
silent: ideHasConfig,
});
// Save IDE configuration for future updates
if (ideConfigurations[ide] && !ideConfigurations[ide]._alreadyConfigured) {
await this.ideConfigManager.saveIdeConfig(bmadDir, ide, ideConfigurations[ide]);
}
// Collect result for summary
if (setupResult.success) {
addResult(ide, 'ok', setupResult.detail || '');
} else {
addResult(ide, 'error', setupResult.error || 'failed');
}
// Restart spinner if we stopped it for prompting
if (needsPrompting && !spinner.isSpinning) {
spinner.start('Configuring IDEs...');
} }
} }
} finally {
// Silent when this IDE has pre-collected config (no prompts for THIS IDE) console.log = originalLog;
const ideHasConfig = Boolean(ideConfigurations[ide]);
const setupResult = await this.ideManager.setup(ide, projectDir, bmadDir, {
selectedModules: allModules || [],
preCollectedConfig: ideConfigurations[ide] || null,
verbose: config.verbose,
silent: ideHasConfig,
});
// Save IDE configuration for future updates
if (ideConfigurations[ide] && !ideConfigurations[ide]._alreadyConfigured) {
await this.ideConfigManager.saveIdeConfig(bmadDir, ide, ideConfigurations[ide]);
}
// Collect result for summary
if (setupResult.success) {
addResult(ide, 'ok', setupResult.detail || '');
} else {
addResult(ide, 'error', setupResult.error || 'failed');
}
// Restart spinner if we stopped it for prompting
if (needsPrompting && !spinner.isSpinning) {
spinner.start('Configuring IDEs...');
}
} }
// Restore console.log
console.log = originalLog;
} }
} }
@ -1338,7 +1334,7 @@ class Installer {
for (const module of existingInstall.modules) { for (const module of existingInstall.modules) {
spinner.message(`Updating module: ${module.id}...`); spinner.message(`Updating module: ${module.id}...`);
await this.moduleManager.update(module.id, bmadDir, config.force); await this.moduleManager.update(module.id, bmadDir, config.force, { installer: this });
} }
// Update manifest // Update manifest

View File

@ -268,14 +268,13 @@ class CustomHandler {
} }
results.filesCopied++; results.filesCopied++;
if (entry.name.endsWith('.md')) {
results.workflowsInstalled++;
}
if (fileTrackingCallback) { if (fileTrackingCallback) {
fileTrackingCallback(targetPath); fileTrackingCallback(targetPath);
} }
} }
if (entry.name.endsWith('.md')) {
results.workflowsInstalled++;
}
} catch (error) { } catch (error) {
results.errors.push(`Failed to copy ${entry.name}: ${error.message}`); results.errors.push(`Failed to copy ${entry.name}: ${error.message}`);
} }

View File

@ -492,19 +492,16 @@ LOAD and execute from: {project-root}/{{bmadFolderName}}/{{path}}
let removedCount = 0; let removedCount = 0;
for (const entry of entries) { for (const entry of entries) {
// Skip non-strings or undefined entries
if (!entry || typeof entry !== 'string') { if (!entry || typeof entry !== 'string') {
continue; continue;
} }
if (entry.startsWith('bmad')) { if (entry.startsWith('bmad')) {
const entryPath = path.join(targetPath, entry); const entryPath = path.join(targetPath, entry);
const stat = await fs.stat(entryPath); try {
if (stat.isFile()) {
await fs.remove(entryPath);
removedCount++;
} else if (stat.isDirectory()) {
await fs.remove(entryPath); await fs.remove(entryPath);
removedCount++; removedCount++;
} catch {
// Skip entries that can't be removed (broken symlinks, permission errors)
} }
} }
} }

View File

@ -284,15 +284,11 @@ class CodexSetup extends BaseIdeSetup {
const entryPath = path.join(destDir, entry); const entryPath = path.join(destDir, entry);
try { try {
const stat = await fs.stat(entryPath); await fs.remove(entryPath);
if (stat.isFile()) {
await fs.remove(entryPath);
} else if (stat.isDirectory()) {
await fs.remove(entryPath);
}
} catch (error) { } catch (error) {
// Skip files that can't be processed if (!options.silent) {
await prompts.log.message(` Skipping ${entry}: ${error.message}`); await prompts.log.message(` Skipping ${entry}: ${error.message}`);
}
} }
} }
} }

View File

@ -173,7 +173,7 @@ class IdeManager {
if (!handler) { if (!handler) {
await prompts.log.warn(`IDE '${ideName}' is not yet supported`); await prompts.log.warn(`IDE '${ideName}' is not yet supported`);
await prompts.log.message(`Supported IDEs: ${[...this.handlers.keys()].join(', ')}`); await prompts.log.message(`Supported IDEs: ${[...this.handlers.keys()].join(', ')}`);
return { success: false, reason: 'unsupported' }; return { success: false, ide: ideName, error: 'unsupported IDE' };
} }
try { try {
@ -200,7 +200,7 @@ class IdeManager {
} else if (handlerResult && handlerResult.modes !== undefined) { } else if (handlerResult && handlerResult.modes !== undefined) {
// Kilo handler returns { success, modes, workflows, tasks, tools } // Kilo handler returns { success, modes, workflows, tasks, tools }
const parts = []; const parts = [];
if (handlerResult.modes > 0) parts.push(`${handlerResult.modes} agents`); if (handlerResult.modes > 0) parts.push(`${handlerResult.modes} modes`);
if (handlerResult.workflows > 0) parts.push(`${handlerResult.workflows} workflows`); if (handlerResult.workflows > 0) parts.push(`${handlerResult.workflows} workflows`);
if (handlerResult.tasks > 0) parts.push(`${handlerResult.tasks} tasks`); if (handlerResult.tasks > 0) parts.push(`${handlerResult.tasks} tasks`);
if (handlerResult.tools > 0) parts.push(`${handlerResult.tools} tools`); if (handlerResult.tools > 0) parts.push(`${handlerResult.tools} tools`);

View File

@ -157,8 +157,7 @@ class WorkflowCommandGenerator {
.replaceAll('{{module}}', workflow.module) .replaceAll('{{module}}', workflow.module)
.replaceAll('{{description}}', workflow.description) .replaceAll('{{description}}', workflow.description)
.replaceAll('{{workflow_path}}', workflowPath) .replaceAll('{{workflow_path}}', workflowPath)
.replaceAll('_bmad', this.bmadFolderName) .replaceAll('_bmad', this.bmadFolderName);
.replaceAll('_bmad', '_bmad');
} }
/** /**
@ -238,15 +237,15 @@ When running any workflow:
const match = workflowPath.match(/\/src\/bmm\/(.+)/); const match = workflowPath.match(/\/src\/bmm\/(.+)/);
if (match) { if (match) {
transformed = `{project-root}/${this.bmadFolderName}/bmm/${match[1]}`; transformed = `{project-root}/${this.bmadFolderName}/bmm/${match[1]}`;
} else if (workflowPath.includes('/src/core/')) {
const match = workflowPath.match(/\/src\/core\/(.+)/);
if (match) {
transformed = `{project-root}/${this.bmadFolderName}/core/${match[1]}`;
}
} }
} else if (workflowPath.includes('/src/core/')) {
return transformed; const match = workflowPath.match(/\/src\/core\/(.+)/);
if (match) {
transformed = `{project-root}/${this.bmadFolderName}/core/${match[1]}`;
}
} }
return transformed;
} }
async loadWorkflowManifest(bmadDir) { async loadWorkflowManifest(bmadDir) {

View File

@ -287,7 +287,7 @@ class ModuleManager {
moduleInfo.dependencies = config.dependencies || []; moduleInfo.dependencies = config.dependencies || [];
moduleInfo.defaultSelected = config.default_selected === undefined ? false : config.default_selected; moduleInfo.defaultSelected = config.default_selected === undefined ? false : config.default_selected;
} catch (error) { } catch (error) {
console.warn(`Failed to read config for ${defaultName}:`, error.message); await prompts.log.warn(`Failed to read config for ${defaultName}: ${error.message}`);
} }
return moduleInfo; return moduleInfo;
@ -365,7 +365,20 @@ class ModuleManager {
// Helper to create a spinner or a no-op when silent // Helper to create a spinner or a no-op when silent
const createSpinner = async () => { const createSpinner = async () => {
if (silent) { if (silent) {
return { start() {}, stop() {}, error() {}, message() {} }; return {
start() {},
stop() {},
error() {},
message() {},
cancel() {},
clear() {},
get isSpinning() {
return false;
},
get isCancelled() {
return false;
},
};
} }
return await prompts.spinner(); return await prompts.spinner();
}; };
@ -603,7 +616,7 @@ class ModuleManager {
* @param {string} bmadDir - Target bmad directory * @param {string} bmadDir - Target bmad directory
* @param {boolean} force - Force update (overwrite modifications) * @param {boolean} force - Force update (overwrite modifications)
*/ */
async update(moduleName, bmadDir, force = false) { async update(moduleName, bmadDir, force = false, options = {}) {
const sourcePath = await this.findModuleSource(moduleName); const sourcePath = await this.findModuleSource(moduleName);
const targetPath = path.join(bmadDir, moduleName); const targetPath = path.join(bmadDir, moduleName);
@ -620,13 +633,13 @@ class ModuleManager {
if (force) { if (force) {
// Force update - remove and reinstall // Force update - remove and reinstall
await fs.remove(targetPath); await fs.remove(targetPath);
return await this.install(moduleName, bmadDir); return await this.install(moduleName, bmadDir, null, { installer: options.installer });
} else { } else {
// Selective update - preserve user modifications // Selective update - preserve user modifications
await this.syncModule(sourcePath, targetPath); await this.syncModule(sourcePath, targetPath);
// Recompile agents (#1133) // Recompile agents (#1133)
await this.compileModuleAgents(sourcePath, targetPath, moduleName, bmadDir); await this.compileModuleAgents(sourcePath, targetPath, moduleName, bmadDir, options.installer);
await this.processAgentFiles(targetPath, moduleName); await this.processAgentFiles(targetPath, moduleName);
} }
@ -694,7 +707,7 @@ class ModuleManager {
const config = yaml.parse(configContent); const config = yaml.parse(configContent);
Object.assign(moduleInfo, config); Object.assign(moduleInfo, config);
} catch (error) { } catch (error) {
console.warn(`Failed to read installed module config:`, error.message); await prompts.log.warn(`Failed to read installed module config: ${error.message}`);
} }
} }
@ -789,7 +802,6 @@ class ModuleManager {
// IMPORTANT: Replace escape sequence and placeholder BEFORE parsing YAML // IMPORTANT: Replace escape sequence and placeholder BEFORE parsing YAML
// Otherwise parsing will fail on the placeholder // Otherwise parsing will fail on the placeholder
yamlContent = yamlContent.replaceAll('_bmad', '_bmad');
yamlContent = yamlContent.replaceAll('_bmad', this.bmadFolderName); yamlContent = yamlContent.replaceAll('_bmad', this.bmadFolderName);
try { try {
@ -1323,7 +1335,7 @@ class ModuleManager {
await fs.writeFile(configPath, configContent, 'utf8'); await fs.writeFile(configPath, configContent, 'utf8');
} catch (error) { } catch (error) {
console.warn(`Failed to process module config:`, error.message); await prompts.log.warn(`Failed to process module config: ${error.message}`);
} }
} }
} }

View File

@ -164,15 +164,15 @@ async function promptInstallQuestions(installConfig, defaults, presetAnswers = {
case 'text': { case 'text': {
const response = await prompts.text({ const response = await prompts.text({
message: q.prompt, message: q.prompt,
defaultValue: q.default || '', default: q.default ?? '',
}); });
answers[q.var] = response || q.default || ''; answers[q.var] = response ?? q.default ?? '';
break; break;
} }
case 'boolean': { case 'boolean': {
const response = await prompts.confirm({ const response = await prompts.confirm({
message: q.prompt, message: q.prompt,
initialValue: q.default, default: q.default,
}); });
answers[q.var] = response; answers[q.var] = response;
break; break;

View File

@ -66,8 +66,8 @@ const CLIUtils = {
const color = await prompts.getColor(); const color = await prompts.getColor();
const borderColor = options.borderColor || 'cyan'; const borderColor = options.borderColor || 'cyan';
const formatBorder = const colorMap = { green: color.green, red: color.red, yellow: color.yellow, cyan: color.cyan, blue: color.blue };
borderColor === 'green' ? color.green : borderColor === 'red' ? color.red : borderColor === 'yellow' ? color.yellow : color.cyan; const formatBorder = colorMap[borderColor] || color.cyan;
await prompts.box(text, options.title, { await prompts.box(text, options.title, {
rounded: options.borderStyle === 'round' || options.borderStyle === undefined, rounded: options.borderStyle === 'round' || options.borderStyle === undefined,

View File

@ -112,7 +112,9 @@ async function spinner() {
s.stop(msg); s.stop(msg);
} }
}, },
message: (msg) => s.message(msg), message: (msg) => {
if (spinning) s.message(msg);
},
error: (msg) => { error: (msg) => {
spinning = false; spinning = false;
s.error(msg); s.error(msg);
@ -297,7 +299,7 @@ async function autocompleteMultiselect(options) {
const isSelected = this.selectedValues.includes(opt.value); const isSelected = this.selectedValues.includes(opt.value);
const isLocked = lockedSet.has(opt.value); const isLocked = lockedSet.has(opt.value);
const label = opt.label ?? String(opt.value ?? ''); const label = opt.label ?? String(opt.value ?? '');
const hintText = opt.hint && opt.value === this.focusedValue ? color.dim(` (${opt.hint})`) : ''; const hintText = opt.hint && isHighlighted ? color.dim(` (${opt.hint})`) : '';
let checkbox; let checkbox;
if (isLocked) { if (isLocked) {
@ -370,8 +372,9 @@ async function autocompleteMultiselect(options) {
// Handle SPACE toggle when NOT navigating (internal code only handles it when isNavigating=true) // Handle SPACE toggle when NOT navigating (internal code only handles it when isNavigating=true)
prompt.on('key', (char, key) => { prompt.on('key', (char, key) => {
if (key && key.name === 'space' && !prompt.isNavigating && prompt.focusedValue !== undefined) { if (key && key.name === 'space' && !prompt.isNavigating) {
prompt.toggleSelected(prompt.focusedValue); const focused = prompt.filteredOptions[prompt.cursor];
if (focused) prompt.toggleSelected(focused.value);
} }
}); });
// === END FIX === // === END FIX ===
@ -648,27 +651,27 @@ async function selectKey(options) {
const stream = { const stream = {
async info(generator) { async info(generator) {
const clack = await getClack(); const clack = await getClack();
clack.stream.info(generator); return clack.stream.info(generator);
}, },
async success(generator) { async success(generator) {
const clack = await getClack(); const clack = await getClack();
clack.stream.success(generator); return clack.stream.success(generator);
}, },
async step(generator) { async step(generator) {
const clack = await getClack(); const clack = await getClack();
clack.stream.step(generator); return clack.stream.step(generator);
}, },
async warn(generator) { async warn(generator) {
const clack = await getClack(); const clack = await getClack();
clack.stream.warn(generator); return clack.stream.warn(generator);
}, },
async error(generator) { async error(generator) {
const clack = await getClack(); const clack = await getClack();
clack.stream.error(generator); return clack.stream.error(generator);
}, },
async message(generator, options) { async message(generator, options) {
const clack = await getClack(); const clack = await getClack();
clack.stream.message(generator, options); return clack.stream.message(generator, options);
}, },
}; };

View File

@ -74,7 +74,7 @@ class UI {
for (const entry of entries) { for (const entry of entries) {
if (entry.isDirectory() && (entry.name === '.bmad' || entry.name === 'bmad')) { if (entry.isDirectory() && (entry.name === '.bmad' || entry.name === 'bmad')) {
hasLegacyBmadFolder = true; hasLegacyBmadFolder = true;
legacyBmadPath = path.join(confirmedDirectory, '.bmad'); legacyBmadPath = path.join(confirmedDirectory, entry.name);
bmadDir = legacyBmadPath; bmadDir = legacyBmadPath;
// Check if it has _cfg folder // Check if it has _cfg folder
@ -151,7 +151,7 @@ class UI {
const newBmadPath = path.join(confirmedDirectory, '_bmad'); const newBmadPath = path.join(confirmedDirectory, '_bmad');
await fs.move(legacyBmadPath, newBmadPath); await fs.move(legacyBmadPath, newBmadPath);
bmadDir = newBmadPath; bmadDir = newBmadPath;
s.stop('Renamed ".bmad" to "_bmad"'); s.stop(`Renamed "${path.basename(legacyBmadPath)}" to "_bmad"`);
} }
// Handle _cfg folder (either from .bmad or standalone) // Handle _cfg folder (either from .bmad or standalone)
@ -274,6 +274,7 @@ class UI {
await prompts.log.info(`Using modules from command-line: ${selectedModules.join(', ')}`); await prompts.log.info(`Using modules from command-line: ${selectedModules.join(', ')}`);
} else { } else {
selectedModules = await this.selectAllModules(installedModuleIds); selectedModules = await this.selectAllModules(installedModuleIds);
selectedModules = selectedModules.filter((m) => m !== 'core');
} }
// After module selection, ask about custom modules // After module selection, ask about custom modules
@ -561,7 +562,7 @@ class UI {
}); });
if (!confirmNoTools) { if (!confirmNoTools) {
return this.promptToolSelection(projectDir); return this.promptToolSelection(projectDir, options);
} }
return { ides: [], skipIde: true }; return { ides: [], skipIde: true };
@ -639,7 +640,7 @@ class UI {
if (!confirmNoTools) { if (!confirmNoTools) {
// User wants to select tools - recurse // User wants to select tools - recurse
return this.promptToolSelection(projectDir); return this.promptToolSelection(projectDir, options);
} }
return { return {
@ -825,7 +826,6 @@ class UI {
const isNewInstallation = installedModuleIds.size === 0; const isNewInstallation = installedModuleIds.size === 0;
const customContentItems = []; const customContentItems = [];
const hasCustomContentItems = false;
// Add custom content items // Add custom content items
if (customContentConfig && customContentConfig.hasCustomContent && customContentConfig.customPath) { if (customContentConfig && customContentConfig.hasCustomContent && customContentConfig.customPath) {
@ -962,7 +962,6 @@ class UI {
*/ */
async selectExternalModules(externalModuleChoices, defaultSelections = []) { async selectExternalModules(externalModuleChoices, defaultSelections = []) {
// Build a message showing available modules // Build a message showing available modules
const availableNames = externalModuleChoices.map((c) => c.name).join(', ');
const message = 'Select official BMad modules to install (use arrow keys, space to toggle):'; const message = 'Select official BMad modules to install (use arrow keys, space to toggle):';
// Mark choices as checked based on defaultSelections // Mark choices as checked based on defaultSelections