diff --git a/docs/how-to/install-bmad.md b/docs/how-to/install-bmad.md index 616e6e430..b88f06279 100644 --- a/docs/how-to/install-bmad.md +++ b/docs/how-to/install-bmad.md @@ -18,7 +18,7 @@ Use `npx bmad-method install` to set up BMad in your project. One command handle - **Node.js** 20+ (the installer requires it) - **Git** (for cloning external modules) -- **An AI tool** such as Claude Code or Cursor — or install without one using `--tools none` +- **An AI tool** such as Claude Code or Cursor (run `npx bmad-method install --list-tools` to see all 42 supported tools) ::: @@ -122,7 +122,8 @@ Under `--yes`, patch and minor upgrades apply automatically. Majors stay frozen | `--yes`, `-y` | Skip all prompts; accept flag values + defaults | | `--directory ` | Install into this directory (default: current working dir) | | `--modules ` | Exact module set. Core is auto-added. Not a delta — list everything you want kept. | -| `--tools ` or `--tools none` | IDE/tool selection. `none` skips tool config entirely. | +| `--tools ` | IDE/tool selection. Required for fresh `--yes` installs. Run `--list-tools` for valid IDs. | +| `--list-tools` | Print all supported tool/IDE IDs (with target directories) and exit. | | `--action ` | `install`, `update`, or `quick-update`. Defaults based on existing install state. | | `--custom-source ` | Install custom modules from Git URLs or local paths | | `--channel ` | Apply to all externals (aliased as `--all-stable` / `--all-next`) | @@ -165,17 +166,17 @@ npx bmad-method install --yes --modules bmm,bmb --all-next --tools claude-code ```bash npx bmad-method install --yes --action update \ - --modules bmm,bmb,gds \ - --tools none + --modules bmm,bmb,gds ``` +`--tools` is omitted intentionally — `--action update` reuses the tools configured during the first install. + **Mix channels — bmb on next, gds on stable:** ```bash npx bmad-method install --yes --action update \ --modules bmm,bmb,cis,gds \ - --next=bmb \ - --tools none + --next=bmb ``` :::caution[Rate limit on shared IPs] @@ -204,7 +205,7 @@ For cross-machine reproducibility, don't rely on rerunning the same `--modules` ```bash npx bmad-method install --yes --modules bmb,cis \ - --pin bmb=v1.7.0 --pin cis=v0.4.2 --tools none + --pin bmb=v1.7.0 --pin cis=v0.4.2 --tools claude-code ``` ## Troubleshooting diff --git a/tools/installer/commands/install.js b/tools/installer/commands/install.js index e10a0c96a..55adcfb9c 100644 --- a/tools/installer/commands/install.js +++ b/tools/installer/commands/install.js @@ -15,8 +15,9 @@ module.exports = { ['--modules ', 'Comma-separated list of module IDs to install (e.g., "bmm,bmb")'], [ '--tools ', - 'Comma-separated list of tool/IDE IDs to configure (e.g., "claude-code,cursor"). Use "none" to skip tool configuration.', + 'Comma-separated list of tool/IDE IDs to configure (e.g., "claude-code,cursor"). Required for fresh non-interactive (--yes) installs. Run with --list-tools to see all valid IDs.', ], + ['--list-tools', 'Print all supported tool/IDE IDs (with target directories) and exit.'], ['--action ', 'Action type for existing installations: install, update, or quick-update'], ['--user-name ', 'Name for agents to use (default: system username)'], ['--communication-language ', 'Language for agent communication (default: English)'], @@ -40,6 +41,12 @@ module.exports = { ], action: async (options) => { try { + if (options.listTools) { + const { formatPlatformList } = require('../ide/platform-codes'); + process.stdout.write((await formatPlatformList()) + '\n'); + process.exit(0); + } + // Set debug flag as environment variable for all components if (options.debug) { process.env.BMAD_DEBUG_MANIFEST = 'true'; @@ -81,7 +88,7 @@ module.exports = { } else { await prompts.log.error(`Installation failed: ${error.message}`); } - if (error.stack) { + if (error.stack && !error.expected) { await prompts.log.message(error.stack); } } catch { diff --git a/tools/installer/ide/platform-codes.js b/tools/installer/ide/platform-codes.js index f29be8fcb..82560d8bf 100644 --- a/tools/installer/ide/platform-codes.js +++ b/tools/installer/ide/platform-codes.js @@ -31,7 +31,63 @@ function clearCache() { _cachedPlatformCodes = null; } +/** + * Format the platform list for human-readable output (used by --list-tools). + * @returns {Promise} Formatted multi-line string with id, name, target_dir, preferred flag. + */ +async function formatPlatformList() { + const config = await loadPlatformCodes(); + const platforms = config.platforms || {}; + + const entries = Object.entries(platforms).map(([id, p]) => ({ + id, + name: p.name || id, + targetDir: p.installer?.target_dir || '', + preferred: p.preferred === true, + suspended: typeof p.suspended === 'string' ? p.suspended : null, + })); + + entries.sort((a, b) => { + if (a.preferred !== b.preferred) return a.preferred ? -1 : 1; + return a.id.localeCompare(b.id); + }); + + const idWidth = Math.max(...entries.map((e) => e.id.length), 'ID'.length); + const nameWidth = Math.max(...entries.map((e) => e.name.length), 'Name'.length); + + const pad = (s, w) => s + ' '.repeat(Math.max(0, w - s.length)); + const lines = [ + `Supported tool IDs (pass via --tools [,...]):`, + '', + ` ${pad('ID', idWidth)} ${pad('Name', nameWidth)} Target dir`, + ` ${pad('-'.repeat(idWidth), idWidth)} ${pad('-'.repeat(nameWidth), nameWidth)} ${'-'.repeat(10)}`, + ]; + + for (const e of entries) { + const star = e.preferred ? ' *' : ' '; + const suffix = e.suspended ? ` [suspended: ${e.suspended}]` : ''; + lines.push(`${star}${pad(e.id, idWidth)} ${pad(e.name, nameWidth)} ${e.targetDir}${suffix}`); + } + + lines.push('', '* = recommended / preferred', '', 'Example: bmad-method install --modules bmm --tools claude-code'); + + return lines.join('\n'); +} + +/** + * @returns {Promise} List of valid platform IDs (suspended ones excluded). + */ +async function getValidPlatformIds() { + const config = await loadPlatformCodes(); + const platforms = config.platforms || {}; + return Object.entries(platforms) + .filter(([, p]) => !p.suspended) + .map(([id]) => id); +} + module.exports = { loadPlatformCodes, clearCache, + formatPlatformList, + getValidPlatformIds, }; diff --git a/tools/installer/ui.js b/tools/installer/ui.js index 7b720743b..15b18f9a1 100644 --- a/tools/installer/ui.js +++ b/tools/installer/ui.js @@ -404,6 +404,37 @@ class UI { * @param {Object} options - Command-line options * @returns {Object} Tool configuration */ + _parseToolsFlag(toolsArg, allKnownValues) { + const selectedIdes = String(toolsArg) + .split(',') + .map((t) => t.trim()) + .filter(Boolean); + + if (selectedIdes.length === 0) { + const err = new Error( + '--tools was passed empty. Provide at least one tool ID (e.g. --tools claude-code) or run with --list-tools to see valid IDs.', + ); + err.expected = true; + throw err; + } + + const unknown = selectedIdes.filter((id) => !allKnownValues.has(id)); + if (unknown.length > 0) { + const err = new Error( + [ + `Unknown tool ID${unknown.length === 1 ? '' : 's'}: ${unknown.join(', ')}`, + '', + 'Run with --list-tools to see all valid IDs.', + 'Common: claude-code, cursor, copilot, windsurf, cline', + ].join('\n'), + ); + err.expected = true; + throw err; + } + + return selectedIdes; + } + async promptToolSelection(projectDir, options = {}) { const { ExistingInstall } = require('./core/existing-install'); const { Installer } = require('./core/installer'); @@ -439,14 +470,7 @@ class UI { // Non-interactive: handle --tools and --yes flags before interactive prompt if (options.tools) { - if (options.tools.toLowerCase() === 'none') { - await prompts.log.info('Skipping tool configuration (--tools none)'); - return { ides: [], skipIde: true }; - } - const selectedIdes = options.tools - .split(',') - .map((t) => t.trim()) - .filter(Boolean); + const selectedIdes = this._parseToolsFlag(options.tools, allKnownValues); await prompts.log.info(`Using tools from command-line: ${selectedIdes.join(', ')}`); await this.displaySelectedTools(selectedIdes, preferredIdes, allTools); return { ides: selectedIdes, skipIde: false }; @@ -524,19 +548,10 @@ class UI { // 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') { - await prompts.log.info('Skipping tool configuration (--tools none)'); - return { ides: [], skipIde: true }; - } else { - selectedIdes = options.tools - .split(',') - .map((t) => t.trim()) - .filter(Boolean); - await prompts.log.info(`Using tools from command-line: ${selectedIdes.join(', ')}`); - await this.displaySelectedTools(selectedIdes, preferredIdes, allTools); - return { ides: selectedIdes, skipIde: false }; - } + selectedIdes = this._parseToolsFlag(options.tools, allKnownValues); + await prompts.log.info(`Using tools from command-line: ${selectedIdes.join(', ')}`); + await this.displaySelectedTools(selectedIdes, preferredIdes, allTools); + return { ides: selectedIdes, skipIde: false }; } else if (options.yes) { // If --yes flag is set, skip tool prompt and use previously configured tools or empty if (configuredIdes.length > 0) { @@ -544,8 +559,20 @@ class UI { await this.displaySelectedTools(configuredIdes, preferredIdes, allTools); return { ides: configuredIdes, skipIde: false }; } else { - await prompts.log.info('Skipping tool configuration (--yes flag, no previous tools)'); - return { ides: [], skipIde: true }; + { + const err = new Error( + [ + '--tools is required for non-interactive install (--yes / -y) when no tools are previously configured.', + '', + 'Common: claude-code, cursor, copilot, windsurf, cline', + 'See all supported tools: bmad-method install --list-tools', + '', + 'Example: bmad-method install --modules bmm --tools claude-code -y', + ].join('\n'), + ); + err.expected = true; + throw err; + } } }