Compare commits

...

7 Commits

Author SHA1 Message Date
Phil 2e46607533
Merge 6caca55e43 into df176d4206 2026-02-03 22:51:18 -05:00
Brian Madison df176d4206 installer remove double tool questioning 2026-02-03 21:36:21 -06:00
Phil 6caca55e43
Merge branch 'main' into dir-install-flag 2026-01-26 10:12:47 -05:00
Brian 2c3285f47e
Merge branch 'main' into dir-install-flag 2026-01-25 14:11:01 -06:00
Brian c46453259f
Merge branch 'main' into dir-install-flag 2026-01-24 19:46:50 -06:00
Phil 83ed3a978d
Merge branch 'main' into dir-install-flag 2026-01-23 13:09:19 -05:00
Phil Mahncke 3dd05b0584 feat: enhance install command with directory option and update prompt logic
Added a new CLI option for specifying the target project directory, allowing users to skip the interactive prompt. Updated the installation prompt method to accept options and handle directory validation accordingly.
2026-01-22 22:31:13 -05:00
2 changed files with 37 additions and 86 deletions

View File

@ -9,7 +9,10 @@ const ui = new UI();
module.exports = {
command: 'install',
description: 'Install BMAD Core agents and tools',
options: [['-d, --debug', 'Enable debug output for manifest generation']],
options: [
['-d, --debug', 'Enable debug output for manifest generation'],
['-D, --directory <path>', 'Target project directory (skips interactive prompt)'],
],
action: async (options) => {
try {
// Set debug flag as environment variable for all components
@ -18,7 +21,7 @@ module.exports = {
console.log(chalk.cyan('Debug mode enabled\n'));
}
const config = await ui.promptInstall();
const config = await ui.promptInstall(options);
// Handle cancel
if (config.actionType === 'cancel') {

View File

@ -26,9 +26,10 @@ const choiceUtils = { Separator };
class UI {
/**
* Prompt for installation configuration
* @param {Object} options - CLI options object (may contain directory property)
* @returns {Object} Installation configuration
*/
async promptInstall() {
async promptInstall(options = {}) {
CLIUtils.displayLogo();
// Display version-specific start message from install-messages.yaml
@ -36,7 +37,7 @@ class UI {
const messageLoader = new MessageLoader();
messageLoader.displayStartMessage();
const confirmedDirectory = await this.getConfirmedDirectory();
const confirmedDirectory = await this.getConfirmedDirectory(options);
// Preflight: Check for legacy BMAD v4 footprints immediately after getting directory
const { Detector } = require('../installers/lib/core/detector');
@ -435,95 +436,29 @@ class UI {
}
// ─────────────────────────────────────────────────────────────────────────────
// NEW INSTALL: Show recommended tools first with "Browse All" option
// NEW INSTALL: Show all tools with search
// ─────────────────────────────────────────────────────────────────────────────
const recommendedOptions = preferredIdes.map((ide) => {
const isConfigured = configuredPreferred.includes(ide.value);
const allTools = [...preferredIdes, ...otherIdes];
const allToolOptions = allTools.map((ide) => {
const isPreferred = preferredIdes.some((p) => p.value === ide.value);
let label = ide.name;
if (isPreferred) label += ' ⭐';
return {
label: isConfigured ? `${ide.name} ⭐ ✅` : `${ide.name}`,
label,
value: ide.value,
};
});
// Add "browse all" option at the end if there are additional tools
if (otherIdes.length > 0) {
const totalTools = preferredIdes.length + otherIdes.length;
recommendedOptions.push({
label: `→ Browse all supported tools (${totalTools} total)...`,
value: '__BROWSE_ALL__',
});
}
// Pre-select previously configured preferred tools
const recommendedInitialValues = configuredPreferred.length > 0 ? configuredPreferred : undefined;
const recommendedSelected = await prompts.multiselect({
message: `Integrate with ${chalk.dim('(↑/↓ to navigate • SPACE: select • ENTER: confirm)')}:`,
options: recommendedOptions,
initialValues: recommendedInitialValues,
const selectedIdes = await prompts.autocompleteMultiselect({
message: 'Select tools:',
options: allToolOptions,
initialValues: configuredIdes.length > 0 ? configuredIdes : undefined,
required: false,
maxItems: 8,
});
const selectedRecommended = recommendedSelected || [];
// ─────────────────────────────────────────────────────────────────────────────
// STEP 2: Handle "Browse All" selection - show additional tools if requested
// ─────────────────────────────────────────────────────────────────────────────
const wantsBrowseAll = selectedRecommended.includes('__BROWSE_ALL__');
const filteredRecommended = selectedRecommended.filter((v) => v !== '__BROWSE_ALL__');
// Show additional tools if:
// 1. User explicitly chose "Browse All", OR
// 2. User has previously configured "other" tools, OR
// 3. User selected no recommended tools (allow them to pick from other tools)
const showAdditionalTools = wantsBrowseAll || configuredOther.length > 0 || filteredRecommended.length === 0;
let selectedAdditionalOrAll = [];
if (showAdditionalTools) {
// Show ALL tools if:
// - User explicitly chose "Browse All", OR
// - User selected nothing from recommended (so they can pick from everything)
// Otherwise, show only "other" tools as additional options
const showAllTools = wantsBrowseAll || filteredRecommended.length === 0;
const toolsToShow = showAllTools ? [...preferredIdes, ...otherIdes] : otherIdes;
if (toolsToShow.length > 0) {
const allToolOptions = toolsToShow.map((ide) => {
const isConfigured = configuredIdes.includes(ide.value);
const isPreferred = preferredIdes.some((p) => p.value === ide.value);
let label = ide.name;
if (isPreferred) label += ' ⭐';
if (isConfigured) label += ' ✅';
return {
label,
value: ide.value,
};
});
// Pre-select: previously configured tools + any recommended tools already selected
const initialValues = [...configuredIdes, ...filteredRecommended].filter((v, i, arr) => arr.indexOf(v) === i); // dedupe
// Use "additional" only if user already selected some recommended tools
const isAdditional = !wantsBrowseAll && filteredRecommended.length > 0;
console.log('');
const selected = await prompts.autocompleteMultiselect({
message: isAdditional ? 'Select additional tools:' : 'Select tools:',
options: allToolOptions,
initialValues: initialValues.length > 0 ? initialValues : undefined,
required: false,
maxItems: 8,
});
selectedAdditionalOrAll = selected || [];
}
}
// Combine selections:
// - If "Browse All" was used, the second prompt contains ALL selections
// - Otherwise, combine recommended + additional
const allSelectedIdes = wantsBrowseAll ? selectedAdditionalOrAll : [...filteredRecommended, ...selectedAdditionalOrAll];
const allSelectedIdes = selectedIdes || [];
// ─────────────────────────────────────────────────────────────────────────────
// STEP 3: Confirm if no tools selected
@ -547,7 +482,6 @@ class UI {
}
// Display selected tools
const allTools = [...preferredIdes, ...otherIdes];
this.displaySelectedTools(allSelectedIdes, preferredIdes, allTools);
return {
@ -604,9 +538,23 @@ class UI {
/**
* Get confirmed directory from user
* @param {Object} options - CLI options object (may contain directory property)
* @returns {string} Confirmed directory path
*/
async getConfirmedDirectory() {
async getConfirmedDirectory(options = {}) {
// If directory provided via CLI, validate and return it
if (options.directory) {
const expandedPath = this.expandUserPath(options.directory);
const validationError = this.validateDirectorySync(expandedPath);
if (validationError) {
throw new Error(`Invalid directory: ${validationError}`);
}
await this.displayDirectoryInfo(expandedPath);
// Skip confirmation for CLI-provided directories
return expandedPath;
}
// Existing interactive prompt logic
let confirmedDirectory = null;
while (!confirmedDirectory) {
const directoryAnswer = await this.promptForDirectory();