bmad-method 6.6.1-next.10 → 6.6.1-next.11
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/{tools/installer/modules/registry-fallback.yaml → bmad-modules.yaml} +29 -15
- package/package.json +1 -1
- package/src/bmm-skills/module.yaml +2 -2
- package/src/core-skills/module.yaml +1 -1
- package/tools/installer/core/installer.js +1 -22
- package/tools/installer/core/manifest.js +0 -22
- package/tools/installer/modules/channel-plan.js +1 -1
- package/tools/installer/modules/external-manager.js +9 -27
- package/tools/installer/modules/official-modules.js +9 -48
- package/tools/installer/ui.js +12 -196
- package/tools/installer/modules/community-manager.js +0 -704
- package/tools/installer/modules/registry-client.js +0 -187
|
@@ -1,18 +1,31 @@
|
|
|
1
|
-
#
|
|
2
|
-
#
|
|
3
|
-
#
|
|
1
|
+
# Official module registry — the single source of truth for which modules
|
|
2
|
+
# the BMad installer offers and how they are displayed.
|
|
3
|
+
#
|
|
4
|
+
# Order here determines display order in the installer picker (after the
|
|
5
|
+
# built-in core and bmm entries, which are loaded from local module.yaml).
|
|
4
6
|
#
|
|
5
7
|
# default_channel (optional) — the install channel when the user does not
|
|
6
8
|
# override with --channel/--pin/--next. Valid values: stable | next.
|
|
7
9
|
# Omit to inherit the installer's hardcoded default (stable).
|
|
8
10
|
|
|
9
11
|
modules:
|
|
12
|
+
bmad-method-test-architecture-enterprise:
|
|
13
|
+
url: https://github.com/bmad-code-org/bmad-method-test-architecture-enterprise
|
|
14
|
+
module-definition: src/module.yaml
|
|
15
|
+
code: tea
|
|
16
|
+
name: "BMad Test Architect"
|
|
17
|
+
description: "Quality strategy, test automation, and release gates for enterprise teams"
|
|
18
|
+
defaultSelected: false
|
|
19
|
+
type: bmad-org
|
|
20
|
+
npmPackage: bmad-method-test-architecture-enterprise
|
|
21
|
+
default_channel: stable
|
|
22
|
+
|
|
10
23
|
bmad-builder:
|
|
11
24
|
url: https://github.com/bmad-code-org/bmad-builder
|
|
12
25
|
module-definition: skills/module.yaml
|
|
13
26
|
code: bmb
|
|
14
27
|
name: "BMad Builder"
|
|
15
|
-
description: "
|
|
28
|
+
description: "Build AI agents, workflows, and modules from a conversation"
|
|
16
29
|
defaultSelected: false
|
|
17
30
|
type: bmad-org
|
|
18
31
|
npmPackage: bmad-builder
|
|
@@ -21,9 +34,9 @@ modules:
|
|
|
21
34
|
bmad-automator:
|
|
22
35
|
url: https://github.com/bmad-code-org/bmad-automator
|
|
23
36
|
module-definition: skills/module.yaml
|
|
24
|
-
code:
|
|
25
|
-
name: "BMad Automator"
|
|
26
|
-
description: "
|
|
37
|
+
code: automator
|
|
38
|
+
name: "BMad Automator Epic Builder Experimental"
|
|
39
|
+
description: "EXPERIMENTAL: only supports claude and codex currently"
|
|
27
40
|
defaultSelected: false
|
|
28
41
|
type: experimental
|
|
29
42
|
npmPackage: bmad-story-automator
|
|
@@ -34,7 +47,7 @@ modules:
|
|
|
34
47
|
module-definition: src/module.yaml
|
|
35
48
|
code: cis
|
|
36
49
|
name: "BMad Creative Intelligence Suite"
|
|
37
|
-
description: "
|
|
50
|
+
description: "Brainstorming, ideation, storytelling, design thinking, and problem-solving"
|
|
38
51
|
defaultSelected: false
|
|
39
52
|
type: bmad-org
|
|
40
53
|
npmPackage: bmad-creative-intelligence-suite
|
|
@@ -45,19 +58,20 @@ modules:
|
|
|
45
58
|
module-definition: src/module.yaml
|
|
46
59
|
code: gds
|
|
47
60
|
name: "BMad Game Dev Studio"
|
|
48
|
-
description: "Game development
|
|
61
|
+
description: "Game design and development for Unity, Unreal, Godot, and Phaser."
|
|
49
62
|
defaultSelected: false
|
|
50
63
|
type: bmad-org
|
|
51
64
|
npmPackage: bmad-game-dev-studio
|
|
52
65
|
default_channel: stable
|
|
53
66
|
|
|
54
|
-
bmad-method-
|
|
55
|
-
url: https://github.com/bmad-code-org/bmad-method-
|
|
67
|
+
bmad-method-wds-expansion:
|
|
68
|
+
url: https://github.com/bmad-code-org/bmad-method-wds-expansion
|
|
56
69
|
module-definition: src/module.yaml
|
|
57
|
-
code:
|
|
58
|
-
|
|
59
|
-
|
|
70
|
+
code: wds
|
|
71
|
+
plugin_name: bmad-wds # WDS marketplace.json declares the plugin under this name
|
|
72
|
+
name: "Whiteport Design Studio"
|
|
73
|
+
description: "Strategic UX and Design first planning methodology"
|
|
60
74
|
defaultSelected: false
|
|
61
75
|
type: bmad-org
|
|
62
|
-
npmPackage: bmad-
|
|
76
|
+
npmPackage: bmad-wds
|
|
63
77
|
default_channel: stable
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
code: bmm
|
|
2
|
-
name: "BMad Method
|
|
3
|
-
description: "
|
|
2
|
+
name: "BMad Method"
|
|
3
|
+
description: "Full-lifecycle AI agile development: analysis, planning, architecture, implementation"
|
|
4
4
|
default_selected: true # This module will be selected by default for new installations
|
|
5
5
|
|
|
6
6
|
# Variables from Core Config inserted:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
code: core
|
|
2
2
|
name: "BMad Core Module"
|
|
3
|
-
description: "
|
|
3
|
+
description: "Shared utilities across modules"
|
|
4
4
|
|
|
5
5
|
header: "BMad Core Configuration"
|
|
6
6
|
subheader: "Configure the core settings for your BMad installation.\nThese settings will be used across all installed bmad skills, workflows, and agents."
|
|
@@ -640,13 +640,7 @@ class Installer {
|
|
|
640
640
|
const moduleInfo = sourcePath ? await officialModules.getModuleInfo(sourcePath, moduleName, '') : null;
|
|
641
641
|
const displayName = moduleInfo?.name || moduleName;
|
|
642
642
|
|
|
643
|
-
const
|
|
644
|
-
let communityResolution = null;
|
|
645
|
-
if (!externalResolution) {
|
|
646
|
-
const { CommunityModuleManager } = require('../modules/community-manager');
|
|
647
|
-
communityResolution = new CommunityModuleManager().getResolution(moduleName);
|
|
648
|
-
}
|
|
649
|
-
const resolution = externalResolution || communityResolution;
|
|
643
|
+
const resolution = officialModules.externalModuleManager.getResolution(moduleName);
|
|
650
644
|
const cachedResolution = CustomModuleManager._resolutionCache.get(moduleName);
|
|
651
645
|
const versionInfo = await resolveModuleVersion(moduleName, {
|
|
652
646
|
moduleSourcePath: sourcePath,
|
|
@@ -1178,21 +1172,6 @@ class Installer {
|
|
|
1178
1172
|
}
|
|
1179
1173
|
}
|
|
1180
1174
|
|
|
1181
|
-
// Add installed community modules to available modules
|
|
1182
|
-
const { CommunityModuleManager } = require('../modules/community-manager');
|
|
1183
|
-
const communityMgr = new CommunityModuleManager();
|
|
1184
|
-
const communityModules = await communityMgr.listAll();
|
|
1185
|
-
for (const communityModule of communityModules) {
|
|
1186
|
-
if (installedModules.includes(communityModule.code) && !availableModules.some((m) => m.id === communityModule.code)) {
|
|
1187
|
-
availableModules.push({
|
|
1188
|
-
id: communityModule.code,
|
|
1189
|
-
name: communityModule.displayName,
|
|
1190
|
-
isExternal: true,
|
|
1191
|
-
fromCommunity: true,
|
|
1192
|
-
});
|
|
1193
|
-
}
|
|
1194
|
-
}
|
|
1195
|
-
|
|
1196
1175
|
// Add installed custom modules to available modules
|
|
1197
1176
|
const { CustomModuleManager } = require('../modules/custom-module-manager');
|
|
1198
1177
|
const customMgr = new CustomModuleManager();
|
|
@@ -310,28 +310,6 @@ class Manifest {
|
|
|
310
310
|
};
|
|
311
311
|
}
|
|
312
312
|
|
|
313
|
-
// Check if this is a community module
|
|
314
|
-
const { CommunityModuleManager } = require('../modules/community-manager');
|
|
315
|
-
const communityMgr = new CommunityModuleManager();
|
|
316
|
-
const communityInfo = await communityMgr.getModuleByCode(moduleName);
|
|
317
|
-
if (communityInfo) {
|
|
318
|
-
const communityResolution = communityMgr.getResolution(moduleName);
|
|
319
|
-
const versionInfo = await resolveModuleVersion(moduleName, {
|
|
320
|
-
moduleSourcePath,
|
|
321
|
-
fallbackVersion: communityInfo.version,
|
|
322
|
-
});
|
|
323
|
-
return {
|
|
324
|
-
version: communityResolution?.version || versionInfo.version || communityInfo.version,
|
|
325
|
-
source: 'community',
|
|
326
|
-
npmPackage: communityInfo.npmPackage || null,
|
|
327
|
-
repoUrl: communityInfo.url || null,
|
|
328
|
-
channel: communityResolution?.channel || null,
|
|
329
|
-
sha: communityResolution?.sha || null,
|
|
330
|
-
registryApprovedTag: communityResolution?.registryApprovedTag || null,
|
|
331
|
-
registryApprovedSha: communityResolution?.registryApprovedSha || null,
|
|
332
|
-
};
|
|
333
|
-
}
|
|
334
|
-
|
|
335
313
|
// Check if this is a custom module (from user-provided URL or local path)
|
|
336
314
|
const { CustomModuleManager } = require('../modules/custom-module-manager');
|
|
337
315
|
const customMgr = new CustomModuleManager();
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* We build the plan from:
|
|
8
8
|
* 1. CLI flags (--channel / --all-* / --next=CODE / --pin CODE=TAG)
|
|
9
9
|
* 2. Interactive answers (the "all stable?" gate + per-module picker)
|
|
10
|
-
* 3. Registry defaults (default_channel from
|
|
10
|
+
* 3. Registry defaults (default_channel from bmad-modules.yaml)
|
|
11
11
|
* 4. Hardcoded fallback 'stable'
|
|
12
12
|
*
|
|
13
13
|
* Precedence: --pin > --next=CODE > --channel (global) > registry default > 'stable'.
|
|
@@ -4,9 +4,9 @@ const path = require('node:path');
|
|
|
4
4
|
const { execSync } = require('node:child_process');
|
|
5
5
|
const yaml = require('yaml');
|
|
6
6
|
const prompts = require('../prompts');
|
|
7
|
-
const { RegistryClient } = require('./registry-client');
|
|
8
7
|
const { resolveChannel, tagExists, parseGitHubRepo } = require('./channel-resolver');
|
|
9
8
|
const { decideChannelForModule } = require('./channel-plan');
|
|
9
|
+
const { getProjectRoot } = require('../project-root');
|
|
10
10
|
|
|
11
11
|
const VALID_CHANNELS = new Set(['stable', 'next', 'pinned']);
|
|
12
12
|
|
|
@@ -46,15 +46,12 @@ async function writeChannelMarker(markerPath, data) {
|
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
const
|
|
50
|
-
const MARKETPLACE_REPO = 'bmad-plugins-marketplace';
|
|
51
|
-
const MARKETPLACE_REF = 'main';
|
|
52
|
-
const FALLBACK_CONFIG_PATH = path.join(__dirname, 'registry-fallback.yaml');
|
|
49
|
+
const REGISTRY_CONFIG_PATH = path.join(getProjectRoot(), 'bmad-modules.yaml');
|
|
53
50
|
|
|
54
51
|
/**
|
|
55
|
-
* Manages official modules from the
|
|
56
|
-
*
|
|
57
|
-
*
|
|
52
|
+
* Manages official modules from the bundled registry file. The remote
|
|
53
|
+
* marketplace fetch has been retired; this repo is the single source of truth
|
|
54
|
+
* for which official modules exist and how they are displayed.
|
|
58
55
|
*
|
|
59
56
|
* @class ExternalModuleManager
|
|
60
57
|
*/
|
|
@@ -65,9 +62,7 @@ class ExternalModuleManager {
|
|
|
65
62
|
// ExternalModuleManager) sees resolutions made during install.
|
|
66
63
|
static _resolutions = new Map();
|
|
67
64
|
|
|
68
|
-
constructor() {
|
|
69
|
-
this._client = new RegistryClient();
|
|
70
|
-
}
|
|
65
|
+
constructor() {}
|
|
71
66
|
|
|
72
67
|
/**
|
|
73
68
|
* Get the most recent channel resolution for a module (if any).
|
|
@@ -79,8 +74,7 @@ class ExternalModuleManager {
|
|
|
79
74
|
}
|
|
80
75
|
|
|
81
76
|
/**
|
|
82
|
-
* Load the official modules registry from
|
|
83
|
-
* bundled YAML file if the fetch fails.
|
|
77
|
+
* Load the official modules registry from the bundled YAML file.
|
|
84
78
|
* @returns {Object} Parsed YAML content with modules array
|
|
85
79
|
*/
|
|
86
80
|
async loadExternalModulesConfig() {
|
|
@@ -88,23 +82,10 @@ class ExternalModuleManager {
|
|
|
88
82
|
return this.cachedModules;
|
|
89
83
|
}
|
|
90
84
|
|
|
91
|
-
// Try remote registry first
|
|
92
|
-
try {
|
|
93
|
-
const config = await this._client.fetchGitHubYaml(MARKETPLACE_OWNER, MARKETPLACE_REPO, 'registry/official.yaml', MARKETPLACE_REF);
|
|
94
|
-
if (config?.modules?.length) {
|
|
95
|
-
this.cachedModules = config;
|
|
96
|
-
return config;
|
|
97
|
-
}
|
|
98
|
-
} catch {
|
|
99
|
-
// Fall through to local fallback
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Fallback to bundled file
|
|
103
85
|
try {
|
|
104
|
-
const content = await fs.readFile(
|
|
86
|
+
const content = await fs.readFile(REGISTRY_CONFIG_PATH, 'utf8');
|
|
105
87
|
const config = yaml.parse(content);
|
|
106
88
|
this.cachedModules = config;
|
|
107
|
-
await prompts.log.warn('Could not reach BMad registry; using bundled module list.');
|
|
108
89
|
return config;
|
|
109
90
|
} catch (error) {
|
|
110
91
|
await prompts.log.warn(`Failed to load modules config: ${error.message}`);
|
|
@@ -130,6 +111,7 @@ class ExternalModuleManager {
|
|
|
130
111
|
defaultSelected: mod.default_selected === true || mod.defaultSelected === true,
|
|
131
112
|
type: mod.type || 'bmad-org',
|
|
132
113
|
npmPackage: mod.npm_package || mod.npmPackage || null,
|
|
114
|
+
pluginName: mod.plugin_name || mod.pluginName || null,
|
|
133
115
|
defaultChannel: normalizeChannelName(mod.default_channel || mod.defaultChannel) || 'stable',
|
|
134
116
|
builtIn: mod.built_in === true,
|
|
135
117
|
isExternal: mod.built_in !== true,
|
|
@@ -231,14 +231,6 @@ class OfficialModules {
|
|
|
231
231
|
return externalSource;
|
|
232
232
|
}
|
|
233
233
|
|
|
234
|
-
// Check community modules (pass channelOptions for --next/--pin overrides)
|
|
235
|
-
const { CommunityModuleManager } = require('./community-manager');
|
|
236
|
-
const communityMgr = new CommunityModuleManager();
|
|
237
|
-
const communitySource = await communityMgr.findModuleSource(moduleCode, options);
|
|
238
|
-
if (communitySource) {
|
|
239
|
-
return communitySource;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
234
|
// Check custom modules (from user-provided URLs, already cloned to cache)
|
|
243
235
|
const { CustomModuleManager } = require('./custom-module-manager');
|
|
244
236
|
const customMgr = new CustomModuleManager();
|
|
@@ -269,21 +261,6 @@ class OfficialModules {
|
|
|
269
261
|
return this.installFromResolution(resolved, bmadDir, fileTrackingCallback, options);
|
|
270
262
|
}
|
|
271
263
|
|
|
272
|
-
// Community modules whose cloned repo ships marketplace.json get the same
|
|
273
|
-
// skill-level install treatment as custom-source installs. If the in-process
|
|
274
|
-
// cache wasn't populated (e.g. caller skipped the pre-clone phase), fall
|
|
275
|
-
// back to resolving directly from `~/.bmad/cache/community-modules/<name>/`
|
|
276
|
-
// so we don't silently regress to the legacy half-install path.
|
|
277
|
-
const { CommunityModuleManager } = require('./community-manager');
|
|
278
|
-
const communityMgr = new CommunityModuleManager();
|
|
279
|
-
let communityResolved = communityMgr.getPluginResolution(moduleName);
|
|
280
|
-
if (!communityResolved) {
|
|
281
|
-
communityResolved = await communityMgr.resolveFromCache(moduleName);
|
|
282
|
-
}
|
|
283
|
-
if (communityResolved) {
|
|
284
|
-
return this.installFromResolution(communityResolved, bmadDir, fileTrackingCallback, options);
|
|
285
|
-
}
|
|
286
|
-
|
|
287
264
|
const sourcePath = await this.findModuleSource(moduleName, {
|
|
288
265
|
silent: options.silent,
|
|
289
266
|
channelOptions: options.channelOptions,
|
|
@@ -310,14 +287,9 @@ class OfficialModules {
|
|
|
310
287
|
const manifestObj = new Manifest();
|
|
311
288
|
const versionInfo = await manifestObj.getModuleVersionInfo(moduleName, bmadDir, sourcePath);
|
|
312
289
|
|
|
313
|
-
// Pick up channel resolution recorded by
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
if (!externalResolution) {
|
|
317
|
-
const { CommunityModuleManager } = require('./community-manager');
|
|
318
|
-
communityResolution = new CommunityModuleManager().getResolution(moduleName);
|
|
319
|
-
}
|
|
320
|
-
const resolution = externalResolution || communityResolution;
|
|
290
|
+
// Pick up channel resolution recorded by the external manager (the only
|
|
291
|
+
// manager that does pre-clone resolution now that community is retired).
|
|
292
|
+
const resolution = this.externalModuleManager.getResolution(moduleName);
|
|
321
293
|
|
|
322
294
|
await manifestObj.addModule(bmadDir, moduleName, {
|
|
323
295
|
version: resolution?.version || versionInfo.version,
|
|
@@ -326,8 +298,6 @@ class OfficialModules {
|
|
|
326
298
|
repoUrl: versionInfo.repoUrl,
|
|
327
299
|
channel: resolution?.channel,
|
|
328
300
|
sha: resolution?.sha,
|
|
329
|
-
registryApprovedTag: communityResolution?.registryApprovedTag,
|
|
330
|
-
registryApprovedSha: communityResolution?.registryApprovedSha,
|
|
331
301
|
});
|
|
332
302
|
|
|
333
303
|
return { success: true, module: moduleName, path: targetPath, versionInfo };
|
|
@@ -375,27 +345,19 @@ class OfficialModules {
|
|
|
375
345
|
await this.createModuleDirectories(resolved.code, bmadDir, options);
|
|
376
346
|
}
|
|
377
347
|
|
|
378
|
-
// Update manifest. For
|
|
379
|
-
// CommunityModuleManager (stable/next/pinned) and propagate the registry's
|
|
380
|
-
// approved tag/sha. For custom-source installs we derive channel from the
|
|
348
|
+
// Update manifest. For custom-source installs we derive channel from the
|
|
381
349
|
// cloneRef (present → pinned, absent → next; local paths have no channel).
|
|
382
350
|
const { Manifest } = require('../core/manifest');
|
|
383
351
|
const manifestObj = new Manifest();
|
|
384
352
|
|
|
385
353
|
const hasGitClone = !!resolved.repoUrl;
|
|
386
|
-
const isCommunity = resolved.communitySource === true;
|
|
387
354
|
const manifestEntry = {
|
|
388
|
-
version: resolved.
|
|
389
|
-
source:
|
|
355
|
+
version: resolved.cloneRef || (hasGitClone ? 'main' : resolved.version || null),
|
|
356
|
+
source: 'custom',
|
|
390
357
|
npmPackage: null,
|
|
391
358
|
repoUrl: resolved.repoUrl || null,
|
|
392
359
|
};
|
|
393
|
-
if (
|
|
394
|
-
if (resolved.communityChannel) manifestEntry.channel = resolved.communityChannel;
|
|
395
|
-
if (resolved.cloneSha) manifestEntry.sha = resolved.cloneSha;
|
|
396
|
-
if (resolved.registryApprovedTag) manifestEntry.registryApprovedTag = resolved.registryApprovedTag;
|
|
397
|
-
if (resolved.registryApprovedSha) manifestEntry.registryApprovedSha = resolved.registryApprovedSha;
|
|
398
|
-
} else if (hasGitClone) {
|
|
360
|
+
if (hasGitClone) {
|
|
399
361
|
manifestEntry.channel = resolved.cloneRef ? 'pinned' : 'next';
|
|
400
362
|
if (resolved.cloneSha) manifestEntry.sha = resolved.cloneSha;
|
|
401
363
|
if (resolved.rawInput) manifestEntry.rawSource = resolved.rawInput;
|
|
@@ -408,11 +370,10 @@ class OfficialModules {
|
|
|
408
370
|
module: resolved.code,
|
|
409
371
|
path: targetPath,
|
|
410
372
|
// Mirror the manifestEntry.version precedence above so downstream summary
|
|
411
|
-
// lines show the same string we just wrote to disk (
|
|
412
|
-
// use the registry-approved tag via `communityVersion`; custom git-backed
|
|
373
|
+
// lines show the same string we just wrote to disk (custom git-backed
|
|
413
374
|
// installs show the cloned ref or 'main').
|
|
414
375
|
versionInfo: {
|
|
415
|
-
version: resolved.
|
|
376
|
+
version: resolved.cloneRef || (hasGitClone ? 'main' : resolved.version || ''),
|
|
416
377
|
},
|
|
417
378
|
};
|
|
418
379
|
}
|
package/tools/installer/ui.js
CHANGED
|
@@ -818,32 +818,18 @@ class UI {
|
|
|
818
818
|
// Phase 1: Official modules
|
|
819
819
|
const officialSelected = await this._selectOfficialModules(installedModuleIds, installedModuleVersions, channelOptions);
|
|
820
820
|
|
|
821
|
-
//
|
|
822
|
-
//
|
|
823
|
-
|
|
821
|
+
// Identify installed modules that aren't official (previously installed
|
|
822
|
+
// community modules or custom-source modules). Preserve them on update;
|
|
823
|
+
// they can be managed via --custom-source, uninstall, or a dedicated installer.
|
|
824
824
|
const externalManager = new ExternalModuleManager();
|
|
825
825
|
const registryModules = await externalManager.listAvailable();
|
|
826
826
|
const officialRegistryCodes = new Set(['core', 'bmm', ...registryModules.map((m) => m.code)]);
|
|
827
827
|
const installedNonOfficial = [...installedModuleIds].filter((id) => !officialRegistryCodes.has(id));
|
|
828
828
|
|
|
829
|
-
// Phase 2:
|
|
830
|
-
// Returns { codes, didBrowse } so we know if the user entered the flow
|
|
831
|
-
const communityResult = await this._browseCommunityModules(installedModuleIds);
|
|
832
|
-
|
|
833
|
-
// Phase 3: Custom URL modules
|
|
829
|
+
// Phase 2: Custom URL modules
|
|
834
830
|
const customSelected = await this._addCustomUrlModules(installedModuleIds);
|
|
835
831
|
|
|
836
|
-
|
|
837
|
-
const allSelected = new Set([...officialSelected, ...communityResult.codes, ...customSelected]);
|
|
838
|
-
|
|
839
|
-
// Auto-include installed non-official modules that the user didn't get
|
|
840
|
-
// a chance to manage (they declined to browse). If they did browse,
|
|
841
|
-
// trust their selections - they could have deselected intentionally.
|
|
842
|
-
if (!communityResult.didBrowse) {
|
|
843
|
-
for (const code of installedNonOfficial) {
|
|
844
|
-
allSelected.add(code);
|
|
845
|
-
}
|
|
846
|
-
}
|
|
832
|
+
const allSelected = new Set([...officialSelected, ...customSelected, ...installedNonOfficial]);
|
|
847
833
|
|
|
848
834
|
return [...allSelected];
|
|
849
835
|
}
|
|
@@ -954,166 +940,6 @@ class UI {
|
|
|
954
940
|
return result;
|
|
955
941
|
}
|
|
956
942
|
|
|
957
|
-
/**
|
|
958
|
-
* Browse and select community modules using category drill-down.
|
|
959
|
-
* Featured/promoted modules appear at the top.
|
|
960
|
-
* @param {Set} installedModuleIds - Currently installed module IDs
|
|
961
|
-
* @returns {Object} { codes: string[], didBrowse: boolean }
|
|
962
|
-
*/
|
|
963
|
-
async _browseCommunityModules(installedModuleIds = new Set()) {
|
|
964
|
-
const browseCommunity = await prompts.confirm({
|
|
965
|
-
message: 'Would you like to browse community modules?',
|
|
966
|
-
default: false,
|
|
967
|
-
});
|
|
968
|
-
if (!browseCommunity) return { codes: [], didBrowse: false };
|
|
969
|
-
|
|
970
|
-
const { CommunityModuleManager } = require('./modules/community-manager');
|
|
971
|
-
const communityMgr = new CommunityModuleManager();
|
|
972
|
-
|
|
973
|
-
const s = await prompts.spinner();
|
|
974
|
-
s.start('Loading community module catalog...');
|
|
975
|
-
|
|
976
|
-
let categories, featured, allCommunity;
|
|
977
|
-
try {
|
|
978
|
-
[categories, featured, allCommunity] = await Promise.all([
|
|
979
|
-
communityMgr.getCategoryList(),
|
|
980
|
-
communityMgr.listFeatured(),
|
|
981
|
-
communityMgr.listAll(),
|
|
982
|
-
]);
|
|
983
|
-
s.stop(`Community catalog loaded (${allCommunity.length} modules)`);
|
|
984
|
-
} catch (error) {
|
|
985
|
-
s.error('Failed to load community catalog');
|
|
986
|
-
await prompts.log.warn(` ${error.message}`);
|
|
987
|
-
return { codes: [], didBrowse: false };
|
|
988
|
-
}
|
|
989
|
-
|
|
990
|
-
if (allCommunity.length === 0) {
|
|
991
|
-
await prompts.log.info('No community modules are currently available.');
|
|
992
|
-
return { codes: [], didBrowse: false };
|
|
993
|
-
}
|
|
994
|
-
|
|
995
|
-
const selectedCodes = new Set();
|
|
996
|
-
let browsing = true;
|
|
997
|
-
|
|
998
|
-
while (browsing) {
|
|
999
|
-
const categoryChoices = [];
|
|
1000
|
-
|
|
1001
|
-
// Featured section at top
|
|
1002
|
-
if (featured.length > 0) {
|
|
1003
|
-
categoryChoices.push({
|
|
1004
|
-
value: '__featured__',
|
|
1005
|
-
label: `\u2605 Featured (${featured.length} module${featured.length === 1 ? '' : 's'})`,
|
|
1006
|
-
});
|
|
1007
|
-
}
|
|
1008
|
-
|
|
1009
|
-
// Categories with module counts
|
|
1010
|
-
for (const cat of categories) {
|
|
1011
|
-
categoryChoices.push({
|
|
1012
|
-
value: cat.slug,
|
|
1013
|
-
label: `${cat.name} (${cat.moduleCount} module${cat.moduleCount === 1 ? '' : 's'})`,
|
|
1014
|
-
});
|
|
1015
|
-
}
|
|
1016
|
-
|
|
1017
|
-
// Special actions at bottom
|
|
1018
|
-
categoryChoices.push(
|
|
1019
|
-
{ value: '__all__', label: '\u25CE View all community modules' },
|
|
1020
|
-
{ value: '__search__', label: '\u25CE Search by keyword' },
|
|
1021
|
-
{ value: '__done__', label: '\u2713 Done browsing' },
|
|
1022
|
-
);
|
|
1023
|
-
|
|
1024
|
-
const selectedCount = selectedCodes.size;
|
|
1025
|
-
const categoryChoice = await prompts.select({
|
|
1026
|
-
message: `Browse community modules${selectedCount > 0 ? ` (${selectedCount} selected)` : ''}:`,
|
|
1027
|
-
choices: categoryChoices,
|
|
1028
|
-
});
|
|
1029
|
-
|
|
1030
|
-
if (categoryChoice === '__done__') {
|
|
1031
|
-
browsing = false;
|
|
1032
|
-
continue;
|
|
1033
|
-
}
|
|
1034
|
-
|
|
1035
|
-
let modulesToShow;
|
|
1036
|
-
switch (categoryChoice) {
|
|
1037
|
-
case '__featured__': {
|
|
1038
|
-
modulesToShow = featured;
|
|
1039
|
-
|
|
1040
|
-
break;
|
|
1041
|
-
}
|
|
1042
|
-
case '__all__': {
|
|
1043
|
-
modulesToShow = allCommunity;
|
|
1044
|
-
|
|
1045
|
-
break;
|
|
1046
|
-
}
|
|
1047
|
-
case '__search__': {
|
|
1048
|
-
const query = await prompts.text({
|
|
1049
|
-
message: 'Search community modules:',
|
|
1050
|
-
placeholder: 'e.g., design, testing, game',
|
|
1051
|
-
});
|
|
1052
|
-
if (!query || query.trim() === '') continue;
|
|
1053
|
-
modulesToShow = await communityMgr.searchByKeyword(query.trim());
|
|
1054
|
-
if (modulesToShow.length === 0) {
|
|
1055
|
-
await prompts.log.warn('No matching modules found.');
|
|
1056
|
-
continue;
|
|
1057
|
-
}
|
|
1058
|
-
|
|
1059
|
-
break;
|
|
1060
|
-
}
|
|
1061
|
-
default: {
|
|
1062
|
-
modulesToShow = await communityMgr.listByCategory(categoryChoice);
|
|
1063
|
-
}
|
|
1064
|
-
}
|
|
1065
|
-
|
|
1066
|
-
// Build options for autocompleteMultiselect
|
|
1067
|
-
const trustBadge = (tier) => {
|
|
1068
|
-
if (tier === 'bmad-certified') return '\u2713';
|
|
1069
|
-
if (tier === 'community-reviewed') return '\u25CB';
|
|
1070
|
-
return '\u26A0';
|
|
1071
|
-
};
|
|
1072
|
-
|
|
1073
|
-
const options = modulesToShow.map((mod) => {
|
|
1074
|
-
const versionStr = mod.version ? ` (v${mod.version})` : '';
|
|
1075
|
-
const badge = trustBadge(mod.trustTier);
|
|
1076
|
-
return {
|
|
1077
|
-
label: `${mod.displayName}${versionStr} [${badge}]`,
|
|
1078
|
-
value: mod.code,
|
|
1079
|
-
hint: mod.description,
|
|
1080
|
-
};
|
|
1081
|
-
});
|
|
1082
|
-
|
|
1083
|
-
// Pre-check modules that are already selected or installed
|
|
1084
|
-
const initialValues = modulesToShow.filter((m) => selectedCodes.has(m.code) || installedModuleIds.has(m.code)).map((m) => m.code);
|
|
1085
|
-
|
|
1086
|
-
const selected = await prompts.autocompleteMultiselect({
|
|
1087
|
-
message: 'Select community modules:',
|
|
1088
|
-
options,
|
|
1089
|
-
initialValues: initialValues.length > 0 ? initialValues : undefined,
|
|
1090
|
-
required: false,
|
|
1091
|
-
maxItems: Math.min(options.length, 10),
|
|
1092
|
-
});
|
|
1093
|
-
|
|
1094
|
-
// Update accumulated selections: sync with what user selected in this view
|
|
1095
|
-
const shownCodes = new Set(modulesToShow.map((m) => m.code));
|
|
1096
|
-
for (const code of shownCodes) {
|
|
1097
|
-
if (selected && selected.includes(code)) {
|
|
1098
|
-
selectedCodes.add(code);
|
|
1099
|
-
} else {
|
|
1100
|
-
selectedCodes.delete(code);
|
|
1101
|
-
}
|
|
1102
|
-
}
|
|
1103
|
-
}
|
|
1104
|
-
|
|
1105
|
-
if (selectedCodes.size > 0) {
|
|
1106
|
-
const moduleLines = [];
|
|
1107
|
-
for (const code of selectedCodes) {
|
|
1108
|
-
const mod = await communityMgr.getModuleByCode(code);
|
|
1109
|
-
moduleLines.push(` \u2022 ${mod?.displayName || code}`);
|
|
1110
|
-
}
|
|
1111
|
-
await prompts.log.message('Selected community modules:\n' + moduleLines.join('\n'));
|
|
1112
|
-
}
|
|
1113
|
-
|
|
1114
|
-
return { codes: [...selectedCodes], didBrowse: true };
|
|
1115
|
-
}
|
|
1116
|
-
|
|
1117
943
|
/**
|
|
1118
944
|
* Prompt user to install modules from custom sources (Git URLs or local paths).
|
|
1119
945
|
* @param {Set} installedModuleIds - Currently installed module IDs
|
|
@@ -1121,7 +947,7 @@ class UI {
|
|
|
1121
947
|
*/
|
|
1122
948
|
async _addCustomUrlModules(installedModuleIds = new Set()) {
|
|
1123
949
|
const addCustom = await prompts.confirm({
|
|
1124
|
-
message: '
|
|
950
|
+
message: 'Do you want to install custom or community modules (Git URL or local path)?',
|
|
1125
951
|
default: false,
|
|
1126
952
|
});
|
|
1127
953
|
if (!addCustom) return [];
|
|
@@ -1885,19 +1711,14 @@ class UI {
|
|
|
1885
1711
|
const haveFlagIntent = channelOptions.global || channelOptions.nextSet.size > 0 || channelOptions.pins.size > 0;
|
|
1886
1712
|
if (haveFlagIntent) return;
|
|
1887
1713
|
|
|
1888
|
-
// Figure out which selected modules actually get a channel (externals
|
|
1889
|
-
//
|
|
1714
|
+
// Figure out which selected modules actually get a channel (externals only).
|
|
1715
|
+
// Bundled core/bmm and custom modules skip the picker.
|
|
1890
1716
|
const externalManager = new ExternalModuleManager();
|
|
1891
1717
|
const externals = await externalManager.listAvailable();
|
|
1892
1718
|
const externalByCode = new Map(externals.map((m) => [m.code, m]));
|
|
1893
1719
|
|
|
1894
|
-
const { CommunityModuleManager } = require('./modules/community-manager');
|
|
1895
|
-
const communityMgr = new CommunityModuleManager();
|
|
1896
|
-
const community = await communityMgr.listAll();
|
|
1897
|
-
const communityByCode = new Map(community.map((m) => [m.code, m]));
|
|
1898
|
-
|
|
1899
1720
|
const channelSelectable = selectedModules.filter((code) => {
|
|
1900
|
-
const info = externalByCode.get(code)
|
|
1721
|
+
const info = externalByCode.get(code);
|
|
1901
1722
|
return info && !info.builtIn;
|
|
1902
1723
|
});
|
|
1903
1724
|
if (channelSelectable.length === 0) return;
|
|
@@ -1912,7 +1733,7 @@ class UI {
|
|
|
1912
1733
|
const { fetchStableTags, parseGitHubRepo } = require('./modules/channel-resolver');
|
|
1913
1734
|
|
|
1914
1735
|
for (const code of channelSelectable) {
|
|
1915
|
-
const info = externalByCode.get(code)
|
|
1736
|
+
const info = externalByCode.get(code);
|
|
1916
1737
|
const repoUrl = info.url;
|
|
1917
1738
|
|
|
1918
1739
|
// Try to pre-resolve the top stable tag so we can surface it in the picker.
|
|
@@ -1987,11 +1808,6 @@ class UI {
|
|
|
1987
1808
|
const externals = await externalManager.listAvailable();
|
|
1988
1809
|
const externalByCode = new Map(externals.map((m) => [m.code, m]));
|
|
1989
1810
|
|
|
1990
|
-
const { CommunityModuleManager } = require('./modules/community-manager');
|
|
1991
|
-
const communityMgr = new CommunityModuleManager();
|
|
1992
|
-
const community = await communityMgr.listAll();
|
|
1993
|
-
const communityByCode = new Map(community.map((m) => [m.code, m]));
|
|
1994
|
-
|
|
1995
1811
|
const { fetchStableTags, classifyUpgrade, releaseNotesUrl } = require('./modules/channel-resolver');
|
|
1996
1812
|
const { parseGitHubRepo } = require('./modules/channel-resolver');
|
|
1997
1813
|
|
|
@@ -2003,7 +1819,7 @@ class UI {
|
|
|
2003
1819
|
const existingWithChannel = selectedModules.filter((code) => {
|
|
2004
1820
|
const prev = existingByName.get(code);
|
|
2005
1821
|
if (!prev) return false;
|
|
2006
|
-
const info = externalByCode.get(code)
|
|
1822
|
+
const info = externalByCode.get(code);
|
|
2007
1823
|
return info && !info.builtIn;
|
|
2008
1824
|
});
|
|
2009
1825
|
if (existingWithChannel.length > 0) {
|
|
@@ -2018,7 +1834,7 @@ class UI {
|
|
|
2018
1834
|
const prev = existingByName.get(code);
|
|
2019
1835
|
if (!prev) continue;
|
|
2020
1836
|
|
|
2021
|
-
const info = externalByCode.get(code)
|
|
1837
|
+
const info = externalByCode.get(code);
|
|
2022
1838
|
if (!info) continue;
|
|
2023
1839
|
// Bundled modules (core/bmm) ship with the installer binary itself —
|
|
2024
1840
|
// their version is stapled to the CLI version, not a git tag. Skip
|