bmad-method 6.5.1-next.5 → 6.5.1-next.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "bmad-method",
4
- "version": "6.5.1-next.5",
4
+ "version": "6.5.1-next.7",
5
5
  "description": "Breakthrough Method of Agile AI-driven Development",
6
6
  "keywords": [
7
7
  "agile",
@@ -15,8 +15,9 @@ module.exports = {
15
15
  ['--modules <modules>', 'Comma-separated list of module IDs to install (e.g., "bmm,bmb")'],
16
16
  [
17
17
  '--tools <tools>',
18
- 'Comma-separated list of tool/IDE IDs to configure (e.g., "claude-code,cursor"). Use "none" to skip tool configuration.',
18
+ '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.',
19
19
  ],
20
+ ['--list-tools', 'Print all supported tool/IDE IDs (with target directories) and exit.'],
20
21
  ['--action <type>', 'Action type for existing installations: install, update, or quick-update'],
21
22
  ['--user-name <name>', 'Name for agents to use (default: system username)'],
22
23
  ['--communication-language <lang>', 'Language for agent communication (default: English)'],
@@ -40,6 +41,12 @@ module.exports = {
40
41
  ],
41
42
  action: async (options) => {
42
43
  try {
44
+ if (options.listTools) {
45
+ const { formatPlatformList } = require('../ide/platform-codes');
46
+ process.stdout.write((await formatPlatformList()) + '\n');
47
+ process.exit(0);
48
+ }
49
+
43
50
  // Set debug flag as environment variable for all components
44
51
  if (options.debug) {
45
52
  process.env.BMAD_DEBUG_MANIFEST = 'true';
@@ -81,7 +88,7 @@ module.exports = {
81
88
  } else {
82
89
  await prompts.log.error(`Installation failed: ${error.message}`);
83
90
  }
84
- if (error.stack) {
91
+ if (error.stack && !error.expected) {
85
92
  await prompts.log.message(error.stack);
86
93
  }
87
94
  } catch {
@@ -31,7 +31,50 @@ function clearCache() {
31
31
  _cachedPlatformCodes = null;
32
32
  }
33
33
 
34
+ /**
35
+ * Format the installable platform list for human-readable output (used by --list-tools).
36
+ * Sourced from IdeManager so this view matches what --tools accepts at install time
37
+ * (suspended platforms excluded).
38
+ * @returns {Promise<string>} Formatted multi-line string with id, name, target_dir, preferred flag.
39
+ */
40
+ async function formatPlatformList() {
41
+ const { IdeManager } = require('./manager');
42
+ const ideManager = new IdeManager();
43
+ await ideManager.ensureInitialized();
44
+
45
+ const entries = ideManager.getAvailableIdes().map((ide) => {
46
+ const handler = ideManager.handlers.get(ide.value);
47
+ return {
48
+ id: ide.value,
49
+ name: ide.name,
50
+ targetDir: handler?.installerConfig?.target_dir || '',
51
+ preferred: ide.preferred,
52
+ };
53
+ });
54
+
55
+ const idWidth = Math.max(...entries.map((e) => e.id.length), 'ID'.length);
56
+ const nameWidth = Math.max(...entries.map((e) => e.name.length), 'Name'.length);
57
+
58
+ const pad = (s, w) => s + ' '.repeat(Math.max(0, w - s.length));
59
+ const lines = [
60
+ `Supported tool IDs (pass via --tools <id>[,<id>...]):`,
61
+ '',
62
+ ` ${pad('ID', idWidth)} ${pad('Name', nameWidth)} Target dir`,
63
+ ` ${pad('-'.repeat(idWidth), idWidth)} ${pad('-'.repeat(nameWidth), nameWidth)} ${'-'.repeat(10)}`,
64
+ ];
65
+
66
+ for (const e of entries) {
67
+ const star = e.preferred ? ' *' : ' ';
68
+ lines.push(`${star}${pad(e.id, idWidth)} ${pad(e.name, nameWidth)} ${e.targetDir}`);
69
+ }
70
+
71
+ lines.push('', '* = recommended / preferred', '', 'Example: bmad-method install --modules bmm --tools claude-code');
72
+
73
+ return lines.join('\n');
74
+ }
75
+
34
76
  module.exports = {
35
77
  loadPlatformCodes,
36
78
  clearCache,
79
+ formatPlatformList,
37
80
  };
@@ -24,8 +24,9 @@ class CustomModuleManager {
24
24
 
25
25
  /**
26
26
  * Parse a user-provided source input into a structured descriptor.
27
- * Accepts local file paths, HTTPS Git URLs, and SSH Git URLs.
28
- * For HTTPS URLs with deep paths (e.g., /tree/main/subdir), extracts the subdir.
27
+ * Accepts local file paths, HTTPS Git URLs, HTTP Git URLs, and SSH Git URLs.
28
+ * For HTTPS/HTTP URLs with deep paths (e.g., /tree/main/subdir), extracts the subdir.
29
+ * The original protocol (http or https) is preserved in the returned cloneUrl.
29
30
  *
30
31
  * @param {string} input - URL or local file path
31
32
  * @returns {Object} Parsed source descriptor:
@@ -127,11 +128,11 @@ class CustomModuleManager {
127
128
  };
128
129
  }
129
130
 
130
- // HTTPS URL: https://host/owner/repo[/tree/branch/subdir][.git]
131
- const httpsMatch = trimmed.match(/^https?:\/\/([^/]+)\/([^/]+)\/([^/.]+?)(?:\.git)?(\/.*)?$/);
131
+ // HTTPS/HTTP URL: https://host/owner/repo[/tree/branch/subdir][.git]
132
+ const httpsMatch = trimmed.match(/^(https?):\/\/([^/]+)\/([^/]+)\/([^/.]+?)(?:\.git)?(\/.*)?$/);
132
133
  if (httpsMatch) {
133
- const [, host, owner, repo, remainder] = httpsMatch;
134
- const cloneUrl = `https://${host}/${owner}/${repo}`;
134
+ const [, protocol, host, owner, repo, remainder] = httpsMatch;
135
+ const cloneUrl = `${protocol}://${host}/${owner}/${repo}`;
135
136
  let subdir = null;
136
137
  let urlRef = null; // branch/tag extracted from /tree/<ref>/subdir
137
138
 
@@ -311,7 +312,7 @@ class CustomModuleManager {
311
312
  /**
312
313
  * Clone a custom module repository to cache.
313
314
  * Supports any Git host (GitHub, GitLab, Bitbucket, self-hosted, etc.).
314
- * @param {string} sourceInput - Git URL (HTTPS or SSH)
315
+ * @param {string} sourceInput - Git URL (HTTPS, HTTP, or SSH)
315
316
  * @param {Object} [options] - Clone options
316
317
  * @param {boolean} [options.silent] - Suppress spinner output
317
318
  * @param {boolean} [options.skipInstall] - Skip npm install (for browsing before user confirms)
@@ -200,12 +200,15 @@ class UI {
200
200
  actionType = options.action;
201
201
  await prompts.log.info(`Using action from command-line: ${actionType}`);
202
202
  } else if (options.yes) {
203
- // Default to quick-update if available, otherwise first available choice
203
+ // Default to quick-update if available, unless flags that require the
204
+ // full update path are present (e.g. --custom-source which re-clones
205
+ // modules at a new version — quick-update skips that entirely).
204
206
  if (choices.length === 0) {
205
207
  throw new Error('No valid actions available for this installation');
206
208
  }
207
209
  const hasQuickUpdate = choices.some((c) => c.value === 'quick-update');
208
- actionType = hasQuickUpdate ? 'quick-update' : choices[0].value;
210
+ const needsFullUpdate = !!options.customSource;
211
+ actionType = hasQuickUpdate && !needsFullUpdate ? 'quick-update' : (choices.find((c) => c.value === 'update') || choices[0]).value;
209
212
  await prompts.log.info(`Non-interactive mode (--yes): defaulting to ${actionType}`);
210
213
  } else {
211
214
  actionType = await prompts.select({
@@ -241,8 +244,11 @@ class UI {
241
244
  .map((m) => m.trim())
242
245
  .filter(Boolean);
243
246
  await prompts.log.info(`Using modules from command-line: ${selectedModules.join(', ')}`);
244
- } else if (options.customSource) {
245
- // Custom source without --modules: start with empty list (core added below)
247
+ } else if (options.customSource && !options.yes) {
248
+ // Custom source without --modules or --yes: start with empty list
249
+ // (only custom source modules + core will be installed).
250
+ // When --yes is also set, fall through to the --yes branch so all
251
+ // installed modules are included alongside the custom source modules.
246
252
  selectedModules = [];
247
253
  } else if (options.yes) {
248
254
  selectedModules = await this.getDefaultModules(installedModuleIds);
@@ -398,6 +404,37 @@ class UI {
398
404
  * @param {Object} options - Command-line options
399
405
  * @returns {Object} Tool configuration
400
406
  */
407
+ _parseToolsFlag(toolsArg, allKnownValues) {
408
+ const selectedIdes = toolsArg
409
+ .split(',')
410
+ .map((t) => t.trim())
411
+ .filter(Boolean);
412
+
413
+ if (selectedIdes.length === 0) {
414
+ const err = new Error(
415
+ '--tools was passed empty. Provide at least one tool ID (e.g. --tools claude-code) or run with --list-tools to see valid IDs.',
416
+ );
417
+ err.expected = true;
418
+ throw err;
419
+ }
420
+
421
+ const unknown = selectedIdes.filter((id) => !allKnownValues.has(id));
422
+ if (unknown.length > 0) {
423
+ const err = new Error(
424
+ [
425
+ `Unknown tool ID${unknown.length === 1 ? '' : 's'}: ${unknown.join(', ')}`,
426
+ '',
427
+ 'Run with --list-tools to see all valid IDs.',
428
+ 'Common: claude-code, cursor, copilot, windsurf, cline',
429
+ ].join('\n'),
430
+ );
431
+ err.expected = true;
432
+ throw err;
433
+ }
434
+
435
+ return selectedIdes;
436
+ }
437
+
401
438
  async promptToolSelection(projectDir, options = {}) {
402
439
  const { ExistingInstall } = require('./core/existing-install');
403
440
  const { Installer } = require('./core/installer');
@@ -432,15 +469,10 @@ class UI {
432
469
  const allTools = [...preferredIdes, ...otherIdes];
433
470
 
434
471
  // Non-interactive: handle --tools and --yes flags before interactive prompt
435
- if (options.tools) {
436
- if (options.tools.toLowerCase() === 'none') {
437
- await prompts.log.info('Skipping tool configuration (--tools none)');
438
- return { ides: [], skipIde: true };
439
- }
440
- const selectedIdes = options.tools
441
- .split(',')
442
- .map((t) => t.trim())
443
- .filter(Boolean);
472
+ // Use !== undefined so an explicit --tools "" falls through to _parseToolsFlag and
473
+ // gets a specific "passed empty" error instead of being silently ignored.
474
+ if (options.tools !== undefined) {
475
+ const selectedIdes = this._parseToolsFlag(options.tools, allKnownValues);
444
476
  await prompts.log.info(`Using tools from command-line: ${selectedIdes.join(', ')}`);
445
477
  await this.displaySelectedTools(selectedIdes, preferredIdes, allTools);
446
478
  return { ides: selectedIdes, skipIde: false };
@@ -516,21 +548,13 @@ class UI {
516
548
 
517
549
  let selectedIdes = [];
518
550
 
519
- // Check if tools are provided via command-line
520
- if (options.tools) {
521
- // Check for explicit "none" value to skip tool installation
522
- if (options.tools.toLowerCase() === 'none') {
523
- await prompts.log.info('Skipping tool configuration (--tools none)');
524
- return { ides: [], skipIde: true };
525
- } else {
526
- selectedIdes = options.tools
527
- .split(',')
528
- .map((t) => t.trim())
529
- .filter(Boolean);
530
- await prompts.log.info(`Using tools from command-line: ${selectedIdes.join(', ')}`);
531
- await this.displaySelectedTools(selectedIdes, preferredIdes, allTools);
532
- return { ides: selectedIdes, skipIde: false };
533
- }
551
+ // Check if tools are provided via command-line.
552
+ // Use !== undefined so an explicit --tools "" still hits _parseToolsFlag's empty-value error.
553
+ if (options.tools !== undefined) {
554
+ selectedIdes = this._parseToolsFlag(options.tools, allKnownValues);
555
+ await prompts.log.info(`Using tools from command-line: ${selectedIdes.join(', ')}`);
556
+ await this.displaySelectedTools(selectedIdes, preferredIdes, allTools);
557
+ return { ides: selectedIdes, skipIde: false };
534
558
  } else if (options.yes) {
535
559
  // If --yes flag is set, skip tool prompt and use previously configured tools or empty
536
560
  if (configuredIdes.length > 0) {
@@ -538,8 +562,18 @@ class UI {
538
562
  await this.displaySelectedTools(configuredIdes, preferredIdes, allTools);
539
563
  return { ides: configuredIdes, skipIde: false };
540
564
  } else {
541
- await prompts.log.info('Skipping tool configuration (--yes flag, no previous tools)');
542
- return { ides: [], skipIde: true };
565
+ const err = new Error(
566
+ [
567
+ '--tools is required for non-interactive install (--yes / -y) when no tools are previously configured.',
568
+ '',
569
+ 'Common: claude-code, cursor, copilot, windsurf, cline',
570
+ 'See all supported tools: bmad-method install --list-tools',
571
+ '',
572
+ 'Example: bmad-method install --modules bmm --tools claude-code -y',
573
+ ].join('\n'),
574
+ );
575
+ err.expected = true;
576
+ throw err;
543
577
  }
544
578
  }
545
579