Merge c18944f3ff into 5b80649d3a
This commit is contained in:
commit
a934157b49
|
|
@ -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?`
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
@ -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') {
|
||||||
|
|
|
||||||
|
|
@ -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' });
|
||||||
|
|
||||||
|
// Check if action is provided via command-line
|
||||||
|
if (options.action) {
|
||||||
|
const validActions = choices.map((c) => c.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({
|
actionType = await prompts.select({
|
||||||
message: 'How would you like to proceed?',
|
message: 'How would you like to proceed?',
|
||||||
choices: choices,
|
choices: choices,
|
||||||
default: choices[0].value,
|
default: choices[0].value,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Handle quick update separately
|
// Handle quick update separately
|
||||||
if (actionType === 'quick-update') {
|
if (actionType === 'quick-update') {
|
||||||
|
|
@ -253,16 +277,79 @@ 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('');
|
||||||
|
let customModuleResult = { selectedCustomModules: [], customContentConfig: { hasCustomContent: false } };
|
||||||
|
|
||||||
|
if (options.customContent) {
|
||||||
|
// Use custom content from command-line
|
||||||
|
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(', ')));
|
||||||
|
|
||||||
|
// 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({
|
const changeCustomModules = await prompts.confirm({
|
||||||
message: 'Modify custom modules, agents, or workflows?',
|
message: 'Modify custom modules, agents, or workflows?',
|
||||||
default: false,
|
default: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
let customModuleResult = { selectedCustomModules: [], customContentConfig: { hasCustomContent: false } };
|
|
||||||
if (changeCustomModules) {
|
if (changeCustomModules) {
|
||||||
customModuleResult = await this.handleCustomModulesInModifyFlow(confirmedDirectory, selectedModules);
|
customModuleResult = await this.handleCustomModulesInModifyFlow(confirmedDirectory, selectedModules);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -281,6 +368,7 @@ class UI {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Merge any selected custom modules
|
// Merge any selected custom modules
|
||||||
if (customModuleResult.selectedCustomModules.length > 0) {
|
if (customModuleResult.selectedCustomModules.length > 0) {
|
||||||
|
|
@ -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,9 +397,68 @@ 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)
|
||||||
|
if (options.customContent) {
|
||||||
|
// Use custom content from command-line
|
||||||
|
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(', ')));
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
customContentConfig = {
|
||||||
|
hasCustomContent: true,
|
||||||
|
paths: customPaths,
|
||||||
|
selectedModuleIds: selectedModuleIds,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else if (!options.yes) {
|
||||||
const wantsCustomContent = await prompts.confirm({
|
const wantsCustomContent = await prompts.confirm({
|
||||||
message: 'Add custom modules, agents, or workflows from your computer?',
|
message: 'Add custom modules, agents, or workflows from your computer?',
|
||||||
default: false,
|
default: false,
|
||||||
|
|
@ -320,6 +467,7 @@ class UI {
|
||||||
if (wantsCustomContent) {
|
if (wantsCustomContent) {
|
||||||
customContentConfig = await this.promptCustomContentSource();
|
customContentConfig = await this.promptCustomContentSource();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Add custom content modules if any were selected
|
// Add custom content modules if any were selected
|
||||||
if (customContentConfig && customContentConfig.selectedModuleIds) {
|
if (customContentConfig && customContentConfig.selectedModuleIds) {
|
||||||
|
|
@ -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,6 +582,29 @@ class UI {
|
||||||
|
|
||||||
let selectedIdes = [];
|
let selectedIdes = [];
|
||||||
|
|
||||||
|
// Check if tools are provided via command-line
|
||||||
|
if (options.tools) {
|
||||||
|
// Check for explicit "none" value to skip tool installation
|
||||||
|
if (options.tools.toLowerCase() === 'none') {
|
||||||
|
console.log(chalk.cyan('Skipping tool configuration (--tools none)'));
|
||||||
|
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({
|
selectedIdes = await prompts.groupMultiselect({
|
||||||
message: `Select tools to configure ${chalk.dim('(↑/↓ navigates, SPACE toggles, ENTER to confirm)')}:`,
|
message: `Select tools to configure ${chalk.dim('(↑/↓ navigates, SPACE toggles, ENTER to confirm)')}:`,
|
||||||
options: groupedOptions,
|
options: groupedOptions,
|
||||||
|
|
@ -440,6 +612,7 @@ class UI {
|
||||||
required: true,
|
required: true,
|
||||||
selectableGroups: false,
|
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();
|
||||||
|
|
||||||
|
// If options are provided, set them directly
|
||||||
|
if (options.userName || options.communicationLanguage || options.documentOutputLanguage || options.outputFolder) {
|
||||||
|
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
|
// Load existing configs first if they exist
|
||||||
await configCollector.loadExistingConfig(directory);
|
await configCollector.loadExistingConfig(directory);
|
||||||
// Now collect with existing values as defaults (false = don't skip loading, true = skip completion message)
|
// Now collect with existing values as defaults (false = don't skip loading, true = skip completion message)
|
||||||
await configCollector.collectModuleConfig('core', directory, false, true);
|
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
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue