feat: add non-interactive installation support

Add command-line flags to support non-interactive installation for CI/CD
pipelines and automated deployments:

- --directory: Installation directory
- --modules: Comma-separated module IDs
- --tools: Tool/IDE IDs (use "none" to skip)
- --custom-content: Custom module paths
- --action: Action type for existing installations
- --user-name, --communication-language, --document-output-language, --output-folder: Core config
- -y, --yes: Accept all defaults

When flags are provided, prompts are skipped. Missing values gracefully
fall back to interactive prompts.
This commit is contained in:
wladimiiir 2026-02-03 23:03:29 +01:00
parent 594235522c
commit c18944f3ff
4 changed files with 622 additions and 52 deletions

View File

@ -30,6 +30,14 @@ npx bmad-method install
Follow the installer prompts, then open your AI IDE (Claude Code, Cursor, Windsurf, etc.) in the project folder. Follow the installer prompts, then open your AI IDE (Claude Code, Cursor, Windsurf, etc.) in the project folder.
**Non-Interactive Installation**: For CI/CD pipelines or automated deployments, use command-line flags:
```bash
npx bmad-method install --directory /path/to/project --modules bmm --tools claude-code --yes
```
See [Non-Interactive Installation Guide](docs/non-interactive-installation.md) for all available options.
> **Not sure what to do?** Run `/bmad-help` — it tells you exactly what's next and what's optional. You can also ask it questions like: > **Not sure what to do?** Run `/bmad-help` — it tells you exactly what's next and what's optional. You can also ask it questions like:
- `/bmad-help How should I build a web app for my TShirt Business that can scale to millions?` - `/bmad-help How should I build a web app for my TShirt Business that can scale to millions?`

View File

@ -0,0 +1,314 @@
---
title: Non-Interactive Installation
description: Install BMAD using command-line flags for CI/CD pipelines and automated deployments
---
# Non-Interactive Installation
BMAD now supports non-interactive installation through command-line flags. This is particularly useful for:
- Automated deployments and CI/CD pipelines
- Scripted installations
- Batch installations across multiple projects
- Quick installations with known configurations
## Installation Modes
### 1. Fully Interactive (Default)
Run without any flags to use the traditional interactive prompts:
```bash
npx bmad-method install
```
### 2. Fully Non-Interactive
Provide all required flags to skip all prompts:
```bash
npx bmad-method install \
--directory /path/to/project \
--modules bmm,bmb \
--tools claude-code,cursor \
--user-name "John Doe" \
--communication-language English \
--document-output-language English \
--output-folder _bmad-output
```
### 3. Semi-Interactive (Graceful Fallback)
Provide some flags and let BMAD prompt for the rest:
```bash
npx bmad-method install \
--directory /path/to/project \
--modules bmm
```
In this case, BMAD will:
- Use the provided directory and modules
- Prompt for tool selection
- Prompt for core configuration
### 4. Quick Install with Defaults
Use the `-y` or `--yes` flag to accept all defaults:
```bash
npx bmad-method install --yes
```
This will:
- Install to the current directory
- Skip custom content prompts
- Use default values for all configuration
- Use previously configured tools (or skip tool configuration if none exist)
### 5. Install Without Tools
To skip tool/IDE configuration entirely:
**Option 1: Use --tools none**
```bash
npx bmad-method install --directory ~/myapp --modules bmm --tools none
```
**Option 2: Use --yes flag (if no tools were previously configured)**
```bash
npx bmad-method install --yes
```
**Option 3: Omit --tools and select "None" in the interactive prompt**
```bash
npx bmad-method install --directory ~/myapp --modules bmm
# Then select "⚠ None - I am not installing any tools" when prompted
```
## Available Flags
### Installation Options
| Flag | Description | Example |
|------|-------------|---------|
| `--directory <path>` | Installation directory | `--directory ~/projects/myapp` |
| `--modules <modules>` | Comma-separated module IDs | `--modules bmm,bmb` |
| `--tools <tools>` | Comma-separated tool/IDE IDs (use "none" to skip) | `--tools claude-code,cursor` or `--tools none` |
| `--custom-content <paths>` | Comma-separated paths to custom modules | `--custom-content ~/my-module,~/another-module` |
| `--action <type>` | Action for existing installations | `--action quick-update` |
### Core Configuration
| Flag | Description | Default |
|------|-------------|---------|
| `--user-name <name>` | Name for agents to use | System username |
| `--communication-language <lang>` | Agent communication language | English |
| `--document-output-language <lang>` | Document output language | English |
| `--output-folder <path>` | Output folder path | _bmad-output |
### Other Options
| Flag | Description |
|------|-------------|
| `-y, --yes` | Accept all defaults and skip prompts |
| `-d, --debug` | Enable debug output for manifest generation |
## Action Types
When working with existing installations, use the `--action` flag:
- `install` - Fresh installation (default for new directories)
- `update` - Modify existing installation (change modules/config)
- `quick-update` - Refresh installation without changing configuration
- `compile-agents` - Recompile agents with customizations only
Example:
```bash
npx bmad-method install --action quick-update
```
## Module IDs
Available module IDs for the `--modules` flag:
### Core Modules
- `bmm` - BMad Method Master
- `bmb` - BMad Builder
### External Modules
Check the [BMad registry](https://github.com/bmad-code-org) for available external modules.
## Tool/IDE IDs
Available tool IDs for the `--tools` flag:
- `claude-code` - Claude Code CLI
- `cursor` - Cursor IDE
- `windsurf` - Windsurf IDE
- `vscode` - Visual Studio Code
- `jetbrains` - JetBrains IDEs
- And more...
Run the interactive installer once to see all available tools.
## Examples
### Basic Installation
Install BMM module with Claude Code:
```bash
npx bmad-method install \
--directory ~/projects/myapp \
--modules bmm \
--tools claude-code \
--user-name "Development Team"
```
### Installation Without Tools
Install without configuring any tools/IDEs:
```bash
npx bmad-method install \
--directory ~/projects/myapp \
--modules bmm \
--tools none \
--user-name "Development Team"
```
### Full Installation with Multiple Modules
```bash
npx bmad-method install \
--directory ~/projects/myapp \
--modules bmm,bmb \
--tools claude-code,cursor \
--user-name "John Doe" \
--communication-language English \
--document-output-language English \
--output-folder _output
```
### Update Existing Installation
```bash
npx bmad-method install \
--directory ~/projects/myapp \
--action update \
--modules bmm,bmb,custom-module
```
### Quick Update (Preserve Settings)
```bash
npx bmad-method install \
--directory ~/projects/myapp \
--action quick-update
```
### Installation with Custom Content
```bash
npx bmad-method install \
--directory ~/projects/myapp \
--modules bmm \
--custom-content ~/my-custom-module,~/another-module \
--tools claude-code
```
### CI/CD Pipeline Installation
```bash
#!/bin/bash
# install-bmad.sh
npx bmad-method install \
--directory "${GITHUB_WORKSPACE}" \
--modules bmm \
--tools claude-code \
--user-name "CI Bot" \
--communication-language English \
--document-output-language English \
--output-folder _bmad-output \
--yes
```
## Environment-Specific Installations
### Development Environment
```bash
npx bmad-method install \
--directory . \
--modules bmm,bmb \
--tools claude-code,cursor \
--user-name "${USER}"
```
### Production Environment
```bash
npx bmad-method install \
--directory /opt/app \
--modules bmm \
--tools claude-code \
--user-name "Production Team" \
--output-folder /var/bmad-output
```
## Validation and Error Handling
BMAD validates all provided flags:
- **Directory**: Must be a valid path with write permissions
- **Modules**: Will warn about invalid module IDs (but won't fail)
- **Tools**: Will warn about invalid tool IDs (but won't fail)
- **Custom Content**: Each path must contain a valid `module.yaml` file
- **Action**: Must be one of: install, update, quick-update, compile-agents
Invalid values will either:
1. Show an error and exit (for critical options like directory)
2. Show a warning and skip (for optional items like custom content)
3. Fall back to interactive prompts (for missing required values)
## Tips and Best Practices
1. **Use absolute paths** for `--directory` to avoid ambiguity
2. **Test flags locally** before using in CI/CD pipelines
3. **Combine with `-y`** for truly unattended installations
4. **Check module availability** by running the interactive installer once
5. **Use `--debug`** flag if you encounter issues during installation
6. **Skip tool configuration** with `--tools none` for server/CI environments where IDEs aren't needed
7. **Partial flags are OK** - Omit flags and let BMAD prompt for missing values interactively
## Troubleshooting
### Installation fails with "Invalid directory"
Check that:
- The directory path exists or its parent exists
- You have write permissions
- The path is absolute or correctly relative to current directory
### Module not found
- Verify the module ID is correct (check available modules in interactive mode)
- External modules may need to be available in the registry
### Custom content path invalid
Ensure each custom content path:
- Points to a directory
- Contains a `module.yaml` file in the root
- Has a `code` field in the `module.yaml`
## Feedback and Issues
If you encounter any issues with non-interactive installation:
1. Run with `--debug` flag for detailed output
2. Try the interactive mode to verify the issue
3. Report issues on GitHub: <https://github.com/bmad-code-org/BMAD-METHOD/issues>

View File

@ -9,7 +9,22 @@ const ui = new UI();
module.exports = { module.exports = {
command: 'install', command: 'install',
description: 'Install BMAD Core agents and tools', 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'],
['--directory <path>', 'Installation directory (default: current directory)'],
['--modules <modules>', 'Comma-separated list of module IDs to install (e.g., "bmm,bmb")'],
[
'--tools <tools>',
'Comma-separated list of tool/IDE IDs to configure (e.g., "claude-code,cursor"). Use "none" to skip tool configuration.',
],
['--custom-content <paths>', 'Comma-separated list of paths to custom modules/agents/workflows'],
['--action <type>', 'Action type for existing installations: install, update, quick-update, or compile-agents'],
['--user-name <name>', 'Name for agents to use (default: system username)'],
['--communication-language <lang>', 'Language for agent communication (default: English)'],
['--document-output-language <lang>', 'Language for document output (default: English)'],
['--output-folder <path>', 'Output folder path relative to project root (default: _bmad-output)'],
['-y, --yes', 'Accept all defaults and skip prompts where possible'],
],
action: async (options) => { action: async (options) => {
try { try {
// Set debug flag as environment variable for all components // Set debug flag as environment variable for all components
@ -18,7 +33,7 @@ module.exports = {
console.log(chalk.cyan('Debug mode enabled\n')); console.log(chalk.cyan('Debug mode enabled\n'));
} }
const config = await ui.promptInstall(); const config = await ui.promptInstall(options);
// Handle cancel // Handle cancel
if (config.actionType === 'cancel') { if (config.actionType === 'cancel') {

View File

@ -26,9 +26,10 @@ const choiceUtils = { Separator };
class UI { class UI {
/** /**
* Prompt for installation configuration * Prompt for installation configuration
* @param {Object} options - Command-line options from install command
* @returns {Object} Installation configuration * @returns {Object} Installation configuration
*/ */
async promptInstall() { async promptInstall(options = {}) {
CLIUtils.displayLogo(); CLIUtils.displayLogo();
// Display version-specific start message from install-messages.yaml // Display version-specific start message from install-messages.yaml
@ -36,7 +37,20 @@ class UI {
const messageLoader = new MessageLoader(); const messageLoader = new MessageLoader();
messageLoader.displayStartMessage(); messageLoader.displayStartMessage();
const confirmedDirectory = await this.getConfirmedDirectory(); // Get directory from options or prompt
let confirmedDirectory;
if (options.directory) {
// Use provided directory from command-line
const expandedDir = this.expandUserPath(options.directory);
const validation = this.validateDirectorySync(expandedDir);
if (validation !== true) {
throw new Error(`Invalid directory: ${validation}`);
}
confirmedDirectory = expandedDir;
console.log(chalk.cyan('Using directory from command-line:'), chalk.bold(confirmedDirectory));
} else {
confirmedDirectory = await this.getConfirmedDirectory();
}
// Preflight: Check for legacy BMAD v4 footprints immediately after getting directory // Preflight: Check for legacy BMAD v4 footprints immediately after getting directory
const { Detector } = require('../installers/lib/core/detector'); const { Detector } = require('../installers/lib/core/detector');
@ -218,11 +232,21 @@ class UI {
// Common actions // Common actions
choices.push({ name: 'Modify BMAD Installation', value: 'update' }); choices.push({ name: 'Modify BMAD Installation', value: 'update' });
actionType = await prompts.select({ // Check if action is provided via command-line
message: 'How would you like to proceed?', if (options.action) {
choices: choices, const validActions = choices.map((c) => c.value);
default: choices[0].value, if (!validActions.includes(options.action)) {
}); throw new Error(`Invalid action: ${options.action}. Valid actions: ${validActions.join(', ')}`);
}
actionType = options.action;
console.log(chalk.cyan('Using action from command-line:'), chalk.bold(actionType));
} else {
actionType = await prompts.select({
message: 'How would you like to proceed?',
choices: choices,
default: choices[0].value,
});
}
// Handle quick update separately // Handle quick update separately
if (actionType === 'quick-update') { if (actionType === 'quick-update') {
@ -253,30 +277,94 @@ class UI {
console.log(chalk.dim(` Found existing modules: ${[...installedModuleIds].join(', ')}`)); console.log(chalk.dim(` Found existing modules: ${[...installedModuleIds].join(', ')}`));
// Unified module selection - all modules in one grouped multiselect // Unified module selection - all modules in one grouped multiselect
let selectedModules = await this.selectAllModules(installedModuleIds); let selectedModules;
if (options.modules) {
// Use modules from command-line
selectedModules = options.modules
.split(',')
.map((m) => m.trim())
.filter(Boolean);
console.log(chalk.cyan('Using modules from command-line:'), chalk.bold(selectedModules.join(', ')));
} else {
selectedModules = await this.selectAllModules(installedModuleIds);
}
// After module selection, ask about custom modules // After module selection, ask about custom modules
console.log(''); console.log('');
const changeCustomModules = await prompts.confirm({
message: 'Modify custom modules, agents, or workflows?',
default: false,
});
let customModuleResult = { selectedCustomModules: [], customContentConfig: { hasCustomContent: false } }; let customModuleResult = { selectedCustomModules: [], customContentConfig: { hasCustomContent: false } };
if (changeCustomModules) {
customModuleResult = await this.handleCustomModulesInModifyFlow(confirmedDirectory, selectedModules);
} else {
// Preserve existing custom modules if user doesn't want to modify them
const { Installer } = require('../installers/lib/core/installer');
const installer = new Installer();
const { bmadDir } = await installer.findBmadDir(confirmedDirectory);
const cacheDir = path.join(bmadDir, '_config', 'custom'); if (options.customContent) {
if (await fs.pathExists(cacheDir)) { // Use custom content from command-line
const entries = await fs.readdir(cacheDir, { withFileTypes: true }); const paths = options.customContent
for (const entry of entries) { .split(',')
if (entry.isDirectory()) { .map((p) => p.trim())
customModuleResult.selectedCustomModules.push(entry.name); .filter(Boolean);
console.log(chalk.cyan('Using custom content from command-line:'), chalk.bold(paths.join(', ')));
// Build custom content config similar to promptCustomContentSource
const customPaths = [];
const selectedModuleIds = [];
for (const customPath of paths) {
const expandedPath = this.expandUserPath(customPath);
const validation = this.validateCustomContentPathSync(expandedPath);
if (validation !== true) {
console.log(chalk.yellow(`⚠️ Skipping invalid custom content path: ${customPath} - ${validation}`));
continue;
}
// Read module metadata
let moduleMeta;
try {
const moduleYamlPath = path.join(expandedPath, 'module.yaml');
const moduleYaml = await fs.readFile(moduleYamlPath, 'utf-8');
const yaml = require('yaml');
moduleMeta = yaml.parse(moduleYaml);
} catch (error) {
console.log(chalk.yellow(`⚠️ Skipping custom content path: ${customPath} - failed to read module.yaml: ${error.message}`));
continue;
}
if (!moduleMeta.code) {
console.log(chalk.yellow(`⚠️ Skipping custom content path: ${customPath} - module.yaml missing 'code' field`));
continue;
}
customPaths.push(expandedPath);
selectedModuleIds.push(moduleMeta.code);
}
if (customPaths.length > 0) {
customModuleResult = {
selectedCustomModules: selectedModuleIds,
customContentConfig: {
hasCustomContent: true,
paths: customPaths,
selectedModuleIds: selectedModuleIds,
},
};
}
} else {
const changeCustomModules = await prompts.confirm({
message: 'Modify custom modules, agents, or workflows?',
default: false,
});
if (changeCustomModules) {
customModuleResult = await this.handleCustomModulesInModifyFlow(confirmedDirectory, selectedModules);
} else {
// Preserve existing custom modules if user doesn't want to modify them
const { Installer } = require('../installers/lib/core/installer');
const installer = new Installer();
const { bmadDir } = await installer.findBmadDir(confirmedDirectory);
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);
}
} }
} }
} }
@ -288,9 +376,9 @@ class UI {
} }
// Get tool selection // Get tool selection
const toolSelection = await this.promptToolSelection(confirmedDirectory); const toolSelection = await this.promptToolSelection(confirmedDirectory, options);
const coreConfig = await this.collectCoreConfig(confirmedDirectory); const coreConfig = await this.collectCoreConfig(confirmedDirectory, options);
return { return {
actionType: 'update', actionType: 'update',
@ -309,16 +397,76 @@ class UI {
const { installedModuleIds } = await this.getExistingInstallation(confirmedDirectory); const { installedModuleIds } = await this.getExistingInstallation(confirmedDirectory);
// Unified module selection - all modules in one grouped multiselect // Unified module selection - all modules in one grouped multiselect
let selectedModules = await this.selectAllModules(installedModuleIds); let selectedModules;
if (options.modules) {
// Use modules from command-line
selectedModules = options.modules
.split(',')
.map((m) => m.trim())
.filter(Boolean);
console.log(chalk.cyan('Using modules from command-line:'), chalk.bold(selectedModules.join(', ')));
} else {
selectedModules = await this.selectAllModules(installedModuleIds);
}
// Ask about custom content (local modules/agents/workflows) // Ask about custom content (local modules/agents/workflows)
const wantsCustomContent = await prompts.confirm({ if (options.customContent) {
message: 'Add custom modules, agents, or workflows from your computer?', // Use custom content from command-line
default: false, const paths = options.customContent
}); .split(',')
.map((p) => p.trim())
.filter(Boolean);
console.log(chalk.cyan('Using custom content from command-line:'), chalk.bold(paths.join(', ')));
if (wantsCustomContent) { // Build custom content config similar to promptCustomContentSource
customContentConfig = await this.promptCustomContentSource(); const customPaths = [];
const selectedModuleIds = [];
for (const customPath of paths) {
const expandedPath = this.expandUserPath(customPath);
const validation = this.validateCustomContentPathSync(expandedPath);
if (validation !== true) {
console.log(chalk.yellow(`⚠️ Skipping invalid custom content path: ${customPath} - ${validation}`));
continue;
}
// Read module metadata
let moduleMeta;
try {
const moduleYamlPath = path.join(expandedPath, 'module.yaml');
const moduleYaml = await fs.readFile(moduleYamlPath, 'utf-8');
const yaml = require('yaml');
moduleMeta = yaml.parse(moduleYaml);
} catch (error) {
console.log(chalk.yellow(`⚠️ Skipping custom content path: ${customPath} - failed to read module.yaml: ${error.message}`));
continue;
}
if (!moduleMeta.code) {
console.log(chalk.yellow(`⚠️ Skipping custom content path: ${customPath} - module.yaml missing 'code' field`));
continue;
}
customPaths.push(expandedPath);
selectedModuleIds.push(moduleMeta.code);
}
if (customPaths.length > 0) {
customContentConfig = {
hasCustomContent: true,
paths: customPaths,
selectedModuleIds: selectedModuleIds,
};
}
} else if (!options.yes) {
const wantsCustomContent = await prompts.confirm({
message: 'Add custom modules, agents, or workflows from your computer?',
default: false,
});
if (wantsCustomContent) {
customContentConfig = await this.promptCustomContentSource();
}
} }
// Add custom content modules if any were selected // Add custom content modules if any were selected
@ -327,8 +475,8 @@ class UI {
} }
selectedModules = selectedModules.filter((m) => m !== 'core'); selectedModules = selectedModules.filter((m) => m !== 'core');
let toolSelection = await this.promptToolSelection(confirmedDirectory); let toolSelection = await this.promptToolSelection(confirmedDirectory, options);
const coreConfig = await this.collectCoreConfig(confirmedDirectory); const coreConfig = await this.collectCoreConfig(confirmedDirectory, options);
return { return {
actionType: 'install', actionType: 'install',
@ -345,9 +493,10 @@ class UI {
/** /**
* Prompt for tool/IDE selection (called after module configuration) * Prompt for tool/IDE selection (called after module configuration)
* @param {string} projectDir - Project directory to check for existing IDEs * @param {string} projectDir - Project directory to check for existing IDEs
* @param {Object} options - Command-line options
* @returns {Object} Tool configuration * @returns {Object} Tool configuration
*/ */
async promptToolSelection(projectDir) { async promptToolSelection(projectDir, options = {}) {
// Check for existing configured IDEs - use findBmadDir to detect custom folder names // Check for existing configured IDEs - use findBmadDir to detect custom folder names
const { Detector } = require('../installers/lib/core/detector'); const { Detector } = require('../installers/lib/core/detector');
const { Installer } = require('../installers/lib/core/installer'); const { Installer } = require('../installers/lib/core/installer');
@ -433,13 +582,37 @@ class UI {
let selectedIdes = []; let selectedIdes = [];
selectedIdes = await prompts.groupMultiselect({ // Check if tools are provided via command-line
message: `Select tools to configure ${chalk.dim('(↑/↓ navigates, SPACE toggles, ENTER to confirm)')}:`, if (options.tools) {
options: groupedOptions, // Check for explicit "none" value to skip tool installation
initialValues: initialValues.length > 0 ? initialValues : undefined, if (options.tools.toLowerCase() === 'none') {
required: true, console.log(chalk.cyan('Skipping tool configuration (--tools none)'));
selectableGroups: false, selectedIdes = [];
}); } else {
selectedIdes = options.tools
.split(',')
.map((t) => t.trim())
.filter(Boolean);
console.log(chalk.cyan('Using tools from command-line:'), chalk.bold(selectedIdes.join(', ')));
}
} else if (options.yes) {
// If --yes flag is set, skip tool prompt and use previously configured tools or empty
if (initialValues.length > 0) {
selectedIdes = initialValues;
console.log(chalk.cyan('Using previously configured tools (--yes flag):'), chalk.bold(selectedIdes.join(', ')));
} else {
console.log(chalk.cyan('Skipping tool configuration (--yes flag, no previous tools)'));
selectedIdes = [];
}
} else {
selectedIdes = await prompts.groupMultiselect({
message: `Select tools to configure ${chalk.dim('(↑/↓ navigates, SPACE toggles, ENTER to confirm)')}:`,
options: groupedOptions,
initialValues: initialValues.length > 0 ? initialValues : undefined,
required: true,
selectableGroups: false,
});
}
// If user selected both "__NONE__" and other tools, honor the "None" choice // If user selected both "__NONE__" and other tools, honor the "None" choice
if (selectedIdes && selectedIdes.includes('__NONE__') && selectedIdes.length > 1) { if (selectedIdes && selectedIdes.includes('__NONE__') && selectedIdes.length > 1) {
@ -542,15 +715,75 @@ class UI {
/** /**
* Collect core configuration * Collect core configuration
* @param {string} directory - Installation directory * @param {string} directory - Installation directory
* @param {Object} options - Command-line options
* @returns {Object} Core configuration * @returns {Object} Core configuration
*/ */
async collectCoreConfig(directory) { async collectCoreConfig(directory, options = {}) {
const { ConfigCollector } = require('../installers/lib/core/config-collector'); const { ConfigCollector } = require('../installers/lib/core/config-collector');
const configCollector = new ConfigCollector(); const configCollector = new ConfigCollector();
// Load existing configs first if they exist
await configCollector.loadExistingConfig(directory); // If options are provided, set them directly
// Now collect with existing values as defaults (false = don't skip loading, true = skip completion message) if (options.userName || options.communicationLanguage || options.documentOutputLanguage || options.outputFolder) {
await configCollector.collectModuleConfig('core', directory, false, true); const coreConfig = {};
if (options.userName) {
coreConfig.user_name = options.userName;
console.log(chalk.cyan('Using user name from command-line:'), chalk.bold(options.userName));
}
if (options.communicationLanguage) {
coreConfig.communication_language = options.communicationLanguage;
console.log(chalk.cyan('Using communication language from command-line:'), chalk.bold(options.communicationLanguage));
}
if (options.documentOutputLanguage) {
coreConfig.document_output_language = options.documentOutputLanguage;
console.log(chalk.cyan('Using document output language from command-line:'), chalk.bold(options.documentOutputLanguage));
}
if (options.outputFolder) {
coreConfig.output_folder = options.outputFolder;
console.log(chalk.cyan('Using output folder from command-line:'), chalk.bold(options.outputFolder));
}
// Load existing config to merge with provided options
await configCollector.loadExistingConfig(directory);
// Merge provided options with existing config (or defaults)
const existingConfig = configCollector.collectedConfig.core || {};
configCollector.collectedConfig.core = { ...existingConfig, ...coreConfig };
// If not all options are provided, collect the missing ones interactively (unless --yes flag)
if (
!options.yes &&
(!options.userName || !options.communicationLanguage || !options.documentOutputLanguage || !options.outputFolder)
) {
await configCollector.collectModuleConfig('core', directory, false, true);
}
} else if (options.yes) {
// Use all defaults when --yes flag is set
await configCollector.loadExistingConfig(directory);
const existingConfig = configCollector.collectedConfig.core || {};
// If no existing config, use defaults
if (Object.keys(existingConfig).length === 0) {
let safeUsername;
try {
safeUsername = os.userInfo().username;
} catch {
safeUsername = process.env.USER || process.env.USERNAME || 'User';
}
const defaultUsername = safeUsername.charAt(0).toUpperCase() + safeUsername.slice(1);
configCollector.collectedConfig.core = {
user_name: defaultUsername,
communication_language: 'English',
document_output_language: 'English',
output_folder: '_bmad-output',
};
console.log(chalk.cyan('Using default configuration (--yes flag)'));
}
} else {
// Load existing configs first if they exist
await configCollector.loadExistingConfig(directory);
// Now collect with existing values as defaults (false = don't skip loading, true = skip completion message)
await configCollector.collectModuleConfig('core', directory, false, true);
}
const coreConfig = configCollector.collectedConfig.core; const coreConfig = configCollector.collectedConfig.core;
// Ensure we always have a core config object, even if empty // Ensure we always have a core config object, even if empty