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
|
@@ -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").
|
|
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
|
|
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 =
|
|
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)
|
package/tools/installer/ui.js
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
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
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
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
|
-
|
|
542
|
-
|
|
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
|
|