feat(installer): bundle module registry, retire marketplace, refresh display names

Prepares v6.7.0 for release:

- Moves bundled module list from tools/installer/modules/registry-fallback.yaml
  to bmad-modules.yaml at repo root; renames to reflect single-source-of-truth role.
- Retires the remote marketplace registry fetch in ExternalModuleManager; the
  installer now reads the bundled YAML only.
- Adds WDS (Whiteport Design Studio) entry alongside BMM, BMB, BMA, CIS, GDS, TEA.
- Refreshes display names and descriptions on every bundled module; TEA
  repositioned after BMM in the picker.
- Adds plugin_name override field on registry entries so modules whose
  marketplace.json declares a plugin under a different name than the installer
  code (e.g. WDS uses bmad-wds) match without falling back to the single-plugin
  heuristic.
- Removes the community modules picker from the interactive installer; previously
  installed community modules are preserved on update and can still be installed
  via --custom-source.
- Renames the custom-source confirm prompt for clarity.

CHANGELOG.md updated with the full v6.7.0 entry.
This commit is contained in:
Brian Madison 2026-05-17 16:58:03 -05:00
parent 71136bc6af
commit 8a31b20774
8 changed files with 97 additions and 226 deletions

View File

@ -1,5 +1,49 @@
# Changelog # Changelog
## v6.7.0 - 2026-05-17
### ✨ Headline
**PRD and Product Brief rebuilt as lean, outcome-driven facilitators called bmad-prd and bmad-brief.** Both flagship planning skills now ship three first-class intents (Create / Update / Validate), support express and guided modes, drive elicitation rather than LLM-suggested filler, and adapt output to your needs. New PRD validation pipeline replaces the adversarial reviewer with a quality-rubric synthesis pass that emits both HTML and markdown reports. New **bmad-investigate** skill brings forensic, evidence-graded case files for bug triage, incident RCA, and unfamiliar-code exploration.
A new .decision-log pattern is implemented in this release that will track through workflows all decisions made from the start, allowing for easier continuation or later modifications, where memory of what was decided and why will be remembered.
The existing create, edit and validate prd skills still exist but internally will route to the single prd skill with the proper intent. These shims will be removed with the 7.0.0 release when similar updates are completed across all of v6.
The shape of the toml customizations is still the same, so if you make them for create already, it will still work. There are new fields supported also that can improve your experience with the new bmad-prd skill.
### 💥 Breaking Changes
* **Community modules picker removed from the interactive installer.** Previously installed community modules are preserved on update. Install community modules headlessly with `--custom-source <git-url-or-path>`, or wait for the forthcoming dedicated community installer.
* **Remote marketplace registry retired.** The installer no longer fetches `registry/official.yaml` from `bmad-code-org/bmad-plugins-marketplace`. The bundled module list, now at `bmad-modules.yaml` in the repo root (renamed from `tools/installer/modules/registry-fallback.yaml`), is the single source of truth for which official modules appear in the picker. Per-module version bumps continue to happen in each module's own repo.
### 🎁 Features
* **WDS (Whiteport Design Studio) now bundled in the official module picker.** Selectable alongside BMM, BMB, BMA, CIS, GDS, and TEA without needing `--custom-source`.
* **Refreshed display names and hints across all bundled modules.** Shorter, clearer names; hints now describe what each module provides. TEA repositioned to sit directly after BMM in the picker.
* **Registry entries can declare a `plugin_name` override.** When a module's `.claude-plugin/marketplace.json` declares the plugin under a name different from the module's installer code (e.g., WDS uses `bmad-wds`), set `plugin_name: <name>` on the registry entry to match the marketplace plugin without falling back to the single-plugin heuristic.
* **bmad-prd overhaul** — Three intents (Create / Update / Validate); new Discovery shape (Brain dump → Stakes calibration → Working mode → mode-scoped work); capability-first or user-first modes; Essential Spine template plus Adapt-In Menu with authorized section invention for compliance, integration, hardware, SLAs, monetization, data governance; subagent web research default-on; rebuilt validation via PRD Quality Rubric → synthesis pass → HTML + markdown reports; cross-skill parity with `bmad-product-brief` (variable names, `.decision-log.md`, `persistent_facts` auto-loads `project-context.md`); headless mode with per-intent inputs and `partial` status (#2385, #2378)
* **bmad-product-brief refactor** — Streamlined from a five-stage scripted workflow to a single outcome-driven SKILL.md with Create / Update / Validate intents; inline discovery, elicitation, and review (no more scripted agent fan-outs); new `assets/brief-template.md` with adapt-aggressively guidance; finalize chain through `bmad-distillator` and `bmad-help`; JSON headless responses (#2370, #2371)
* **New bmad-investigate skill** — Forensic case investigation with evidence-graded findings (Confirmed / Deduced / Hypothesized), delegation discipline for large codebases, resume-on-collision logic; supports both defect-chasing and area-exploration modes (#2345 and follow-ups)
* **Interactive directory prompt in installer**`@clack/core` AutocompletePrompt for install-path selection: Tab-cycles existing child dirs, accepts not-yet-created paths, validates raw input (#2387)
* **OpenCode and GitHub Copilot pointer files** — Generic `installCommandPointers()` mechanism driven by per-platform YAML. OpenCode gets `.opencode/commands/<id>.md` for every skill; Copilot gets `.github/agents/<id>.agent.md` for persona agents only (plus `bmad-tea` allowlist), keeping the Custom Agents picker uncluttered. Works for external modules automatically via `skill-manifest.csv` (#2324)
* **BMad Automator (`bma`) registered** — Bundled registry fallback gains source-root external-module support, enabling `--modules bma` (#2345)
### 🐛 Fixes
* **Clear installer error on missing module definition**`findExternalModuleSource()` throws an actionable error naming the module, missing path, and channel, with a suggested `--next=<code>` recovery path, replacing a silent ENOENT in `getFileList` (#2377)
* **bmad-product-brief Update/Validate discipline** — Headless Update now requires decision-log entry + addendum before modifying `brief.md`; distillate regeneration is mandatory; Validate always returns `"offer_to_update": true`; eval expectations tightened (#2371)
* **Module help catalog directional clarity** — Renamed `after`/`before` columns (and JSON manifest keys) to `preceded-by`/`followed-by` to eliminate ambiguity that was causing dependency-direction flips; `required` retains hard-gate semantics (#2360)
* **bmad-help removed from Copilot Custom Agents picker** — Not a true agent; every persona already advertises it on activation (#2359)
* **bmad-investigate robustness** — Collapsed multi-line description, unwrapped case-file template, tightened PRD discovery glob (review follow-ups)
* **Dependency security audit** — Lockfile-only fixes closed 12 of 14 open Dependabot alerts (`vite`, `postcss`, `h3`, `yaml`, `brace-expansion`, `picomatch`, `astro`, others). Two `astro <6.1.10` alerts and one `markdown-it` (via `markdownlint-cli2`) deferred pending major bumps (#2382)
### 📚 Docs
* New `docs/explanation/forensic-investigation.md` (EN + FR) explaining the bmad-investigate workflow and evidence-grading discipline; workflow maps updated in both languages
* Installer prerequisite docs updated across README, install/upgrade/non-interactive/tutorial guides and FR / CS / ZH-CN / VI-VN translations to advertise Node.js 20.12+ (#2387)
## v6.6.0 - 2026-04-28 ## v6.6.0 - 2026-04-28
### 💥 Breaking Changes ### 💥 Breaking Changes

View File

@ -1,18 +1,31 @@
# Fallback module registry — used only when the BMad Marketplace repo # Official module registry — the single source of truth for which modules
# (bmad-code-org/bmad-plugins-marketplace) is unreachable. # the BMad installer offers and how they are displayed.
# The remote registry/official.yaml is the source of truth. #
# Order here determines display order in the installer picker (after the
# built-in core and bmm entries, which are loaded from local module.yaml).
# #
# default_channel (optional) — the install channel when the user does not # default_channel (optional) — the install channel when the user does not
# override with --channel/--pin/--next. Valid values: stable | next. # override with --channel/--pin/--next. Valid values: stable | next.
# Omit to inherit the installer's hardcoded default (stable). # Omit to inherit the installer's hardcoded default (stable).
modules: modules:
bmad-method-test-architecture-enterprise:
url: https://github.com/bmad-code-org/bmad-method-test-architecture-enterprise
module-definition: src/module.yaml
code: tea
name: "BMad Test Architect"
description: "Quality strategy, test automation, and release gates for enterprise teams"
defaultSelected: false
type: bmad-org
npmPackage: bmad-method-test-architecture-enterprise
default_channel: stable
bmad-builder: bmad-builder:
url: https://github.com/bmad-code-org/bmad-builder url: https://github.com/bmad-code-org/bmad-builder
module-definition: skills/module.yaml module-definition: skills/module.yaml
code: bmb code: bmb
name: "BMad Builder" name: "BMad Builder"
description: "Agent and Builder" description: "Build AI agents, workflows, and modules from a conversation"
defaultSelected: false defaultSelected: false
type: bmad-org type: bmad-org
npmPackage: bmad-builder npmPackage: bmad-builder
@ -21,9 +34,9 @@ modules:
bmad-automator: bmad-automator:
url: https://github.com/bmad-code-org/bmad-automator url: https://github.com/bmad-code-org/bmad-automator
module-definition: skills/module.yaml module-definition: skills/module.yaml
code: baut code: automator
name: "BMad Automator" name: "BMad Automator Epic Builder Experimental"
description: "Story automation skills" description: "EXPERIMENTAL: only supports claude and codex currently"
defaultSelected: false defaultSelected: false
type: experimental type: experimental
npmPackage: bmad-story-automator npmPackage: bmad-story-automator
@ -34,7 +47,7 @@ modules:
module-definition: src/module.yaml module-definition: src/module.yaml
code: cis code: cis
name: "BMad Creative Intelligence Suite" name: "BMad Creative Intelligence Suite"
description: "Creative tools for writing, brainstorming, and more" description: "Brainstorming, ideation, storytelling, design thinking, and problem-solving"
defaultSelected: false defaultSelected: false
type: bmad-org type: bmad-org
npmPackage: bmad-creative-intelligence-suite npmPackage: bmad-creative-intelligence-suite
@ -45,19 +58,20 @@ modules:
module-definition: src/module.yaml module-definition: src/module.yaml
code: gds code: gds
name: "BMad Game Dev Studio" name: "BMad Game Dev Studio"
description: "Game development agents and workflows" description: "Game design and development for Unity, Unreal, Godot, and Phaser."
defaultSelected: false defaultSelected: false
type: bmad-org type: bmad-org
npmPackage: bmad-game-dev-studio npmPackage: bmad-game-dev-studio
default_channel: stable default_channel: stable
bmad-method-test-architecture-enterprise: bmad-method-wds-expansion:
url: https://github.com/bmad-code-org/bmad-method-test-architecture-enterprise url: https://github.com/bmad-code-org/bmad-method-wds-expansion
module-definition: src/module.yaml module-definition: src/module.yaml
code: tea code: wds
name: "Test Architect" plugin_name: bmad-wds # WDS marketplace.json declares the plugin under this name
description: "Master Test Architect for quality strategy, test automation, and release gates" name: "Whiteport Design Studio"
description: "Strategic UX and Design first planning methodology"
defaultSelected: false defaultSelected: false
type: bmad-org type: bmad-org
npmPackage: bmad-method-test-architecture-enterprise npmPackage: bmad-wds
default_channel: stable default_channel: stable

View File

@ -1,6 +1,6 @@
code: bmm code: bmm
name: "BMad Method Agile-AI Driven-Development" name: "BMad Method"
description: "AI-driven agile development framework" description: "Full-lifecycle AI agile development: analysis, planning, architecture, implementation"
default_selected: true # This module will be selected by default for new installations default_selected: true # This module will be selected by default for new installations
# Variables from Core Config inserted: # Variables from Core Config inserted:

View File

@ -1,6 +1,6 @@
code: core code: core
name: "BMad Core Module" name: "BMad Core Module"
description: "Core configuration and shared resources" description: "Shared utilities across modules"
header: "BMad Core Configuration" header: "BMad Core Configuration"
subheader: "Configure the core settings for your BMad installation.\nThese settings will be used across all installed bmad skills, workflows, and agents." subheader: "Configure the core settings for your BMad installation.\nThese settings will be used across all installed bmad skills, workflows, and agents."

View File

@ -7,7 +7,7 @@
* We build the plan from: * We build the plan from:
* 1. CLI flags (--channel / --all-* / --next=CODE / --pin CODE=TAG) * 1. CLI flags (--channel / --all-* / --next=CODE / --pin CODE=TAG)
* 2. Interactive answers (the "all stable?" gate + per-module picker) * 2. Interactive answers (the "all stable?" gate + per-module picker)
* 3. Registry defaults (default_channel from registry-fallback.yaml / official.yaml) * 3. Registry defaults (default_channel from bmad-modules.yaml)
* 4. Hardcoded fallback 'stable' * 4. Hardcoded fallback 'stable'
* *
* Precedence: --pin > --next=CODE > --channel (global) > registry default > 'stable'. * Precedence: --pin > --next=CODE > --channel (global) > registry default > 'stable'.

View File

@ -593,6 +593,11 @@ class CommunityModuleManager {
* @returns {{plugin: Object, source: 'name'|'hint'|'single-fallback'}|null} * @returns {{plugin: Object, source: 'name'|'hint'|'single-fallback'}|null}
*/ */
_selectPluginForModule(plugins, moduleInfo) { _selectPluginForModule(plugins, moduleInfo) {
if (moduleInfo.pluginName) {
const byPluginName = plugins.find((p) => p && p.name === moduleInfo.pluginName);
if (byPluginName) return { plugin: byPluginName, source: 'plugin-name' };
}
const byCode = plugins.find((p) => p && p.name === moduleInfo.code); const byCode = plugins.find((p) => p && p.name === moduleInfo.code);
if (byCode) return { plugin: byCode, source: 'name' }; if (byCode) return { plugin: byCode, source: 'name' };

View File

@ -4,9 +4,9 @@ const path = require('node:path');
const { execSync } = require('node:child_process'); const { execSync } = require('node:child_process');
const yaml = require('yaml'); const yaml = require('yaml');
const prompts = require('../prompts'); const prompts = require('../prompts');
const { RegistryClient } = require('./registry-client');
const { resolveChannel, tagExists, parseGitHubRepo } = require('./channel-resolver'); const { resolveChannel, tagExists, parseGitHubRepo } = require('./channel-resolver');
const { decideChannelForModule } = require('./channel-plan'); const { decideChannelForModule } = require('./channel-plan');
const { getProjectRoot } = require('../project-root');
const VALID_CHANNELS = new Set(['stable', 'next', 'pinned']); const VALID_CHANNELS = new Set(['stable', 'next', 'pinned']);
@ -46,15 +46,12 @@ async function writeChannelMarker(markerPath, data) {
} }
} }
const MARKETPLACE_OWNER = 'bmad-code-org'; const REGISTRY_CONFIG_PATH = path.join(getProjectRoot(), 'bmad-modules.yaml');
const MARKETPLACE_REPO = 'bmad-plugins-marketplace';
const MARKETPLACE_REF = 'main';
const FALLBACK_CONFIG_PATH = path.join(__dirname, 'registry-fallback.yaml');
/** /**
* Manages official modules from the remote BMad marketplace registry. * Manages official modules from the bundled registry file. The remote
* Fetches registry/official.yaml from GitHub; falls back to the bundled * marketplace fetch has been retired; this repo is the single source of truth
* external-official-modules.yaml when the network is unavailable. * for which official modules exist and how they are displayed.
* *
* @class ExternalModuleManager * @class ExternalModuleManager
*/ */
@ -65,9 +62,7 @@ class ExternalModuleManager {
// ExternalModuleManager) sees resolutions made during install. // ExternalModuleManager) sees resolutions made during install.
static _resolutions = new Map(); static _resolutions = new Map();
constructor() { constructor() {}
this._client = new RegistryClient();
}
/** /**
* Get the most recent channel resolution for a module (if any). * Get the most recent channel resolution for a module (if any).
@ -79,8 +74,7 @@ class ExternalModuleManager {
} }
/** /**
* Load the official modules registry from GitHub, falling back to the * Load the official modules registry from the bundled YAML file.
* bundled YAML file if the fetch fails.
* @returns {Object} Parsed YAML content with modules array * @returns {Object} Parsed YAML content with modules array
*/ */
async loadExternalModulesConfig() { async loadExternalModulesConfig() {
@ -88,23 +82,10 @@ class ExternalModuleManager {
return this.cachedModules; return this.cachedModules;
} }
// Try remote registry first
try { try {
const config = await this._client.fetchGitHubYaml(MARKETPLACE_OWNER, MARKETPLACE_REPO, 'registry/official.yaml', MARKETPLACE_REF); const content = await fs.readFile(REGISTRY_CONFIG_PATH, 'utf8');
if (config?.modules?.length) {
this.cachedModules = config;
return config;
}
} catch {
// Fall through to local fallback
}
// Fallback to bundled file
try {
const content = await fs.readFile(FALLBACK_CONFIG_PATH, 'utf8');
const config = yaml.parse(content); const config = yaml.parse(content);
this.cachedModules = config; this.cachedModules = config;
await prompts.log.warn('Could not reach BMad registry; using bundled module list.');
return config; return config;
} catch (error) { } catch (error) {
await prompts.log.warn(`Failed to load modules config: ${error.message}`); await prompts.log.warn(`Failed to load modules config: ${error.message}`);
@ -130,6 +111,7 @@ class ExternalModuleManager {
defaultSelected: mod.default_selected === true || mod.defaultSelected === true, defaultSelected: mod.default_selected === true || mod.defaultSelected === true,
type: mod.type || 'bmad-org', type: mod.type || 'bmad-org',
npmPackage: mod.npm_package || mod.npmPackage || null, npmPackage: mod.npm_package || mod.npmPackage || null,
pluginName: mod.plugin_name || mod.pluginName || null,
defaultChannel: normalizeChannelName(mod.default_channel || mod.defaultChannel) || 'stable', defaultChannel: normalizeChannelName(mod.default_channel || mod.defaultChannel) || 'stable',
builtIn: mod.built_in === true, builtIn: mod.built_in === true,
isExternal: mod.built_in !== true, isExternal: mod.built_in !== true,

View File

@ -818,32 +818,18 @@ class UI {
// Phase 1: Official modules // Phase 1: Official modules
const officialSelected = await this._selectOfficialModules(installedModuleIds, installedModuleVersions, channelOptions); const officialSelected = await this._selectOfficialModules(installedModuleIds, installedModuleVersions, channelOptions);
// Determine which installed modules are NOT official (community or custom). // Identify installed modules that aren't official (previously installed
// These must be preserved even if the user declines to browse community/custom. // community modules or custom-source modules). Preserve them on update;
const officialCodes = new Set(officialSelected); // they can be managed via --custom-source, uninstall, or a dedicated installer.
const externalManager = new ExternalModuleManager(); const externalManager = new ExternalModuleManager();
const registryModules = await externalManager.listAvailable(); const registryModules = await externalManager.listAvailable();
const officialRegistryCodes = new Set(['core', 'bmm', ...registryModules.map((m) => m.code)]); const officialRegistryCodes = new Set(['core', 'bmm', ...registryModules.map((m) => m.code)]);
const installedNonOfficial = [...installedModuleIds].filter((id) => !officialRegistryCodes.has(id)); const installedNonOfficial = [...installedModuleIds].filter((id) => !officialRegistryCodes.has(id));
// Phase 2: Community modules (category drill-down) // Phase 2: Custom URL modules
// Returns { codes, didBrowse } so we know if the user entered the flow
const communityResult = await this._browseCommunityModules(installedModuleIds);
// Phase 3: Custom URL modules
const customSelected = await this._addCustomUrlModules(installedModuleIds); const customSelected = await this._addCustomUrlModules(installedModuleIds);
// Merge all selections const allSelected = new Set([...officialSelected, ...customSelected, ...installedNonOfficial]);
const allSelected = new Set([...officialSelected, ...communityResult.codes, ...customSelected]);
// Auto-include installed non-official modules that the user didn't get
// a chance to manage (they declined to browse). If they did browse,
// trust their selections - they could have deselected intentionally.
if (!communityResult.didBrowse) {
for (const code of installedNonOfficial) {
allSelected.add(code);
}
}
return [...allSelected]; return [...allSelected];
} }
@ -954,166 +940,6 @@ class UI {
return result; return result;
} }
/**
* Browse and select community modules using category drill-down.
* Featured/promoted modules appear at the top.
* @param {Set} installedModuleIds - Currently installed module IDs
* @returns {Object} { codes: string[], didBrowse: boolean }
*/
async _browseCommunityModules(installedModuleIds = new Set()) {
const browseCommunity = await prompts.confirm({
message: 'Would you like to browse community modules?',
default: false,
});
if (!browseCommunity) return { codes: [], didBrowse: false };
const { CommunityModuleManager } = require('./modules/community-manager');
const communityMgr = new CommunityModuleManager();
const s = await prompts.spinner();
s.start('Loading community module catalog...');
let categories, featured, allCommunity;
try {
[categories, featured, allCommunity] = await Promise.all([
communityMgr.getCategoryList(),
communityMgr.listFeatured(),
communityMgr.listAll(),
]);
s.stop(`Community catalog loaded (${allCommunity.length} modules)`);
} catch (error) {
s.error('Failed to load community catalog');
await prompts.log.warn(` ${error.message}`);
return { codes: [], didBrowse: false };
}
if (allCommunity.length === 0) {
await prompts.log.info('No community modules are currently available.');
return { codes: [], didBrowse: false };
}
const selectedCodes = new Set();
let browsing = true;
while (browsing) {
const categoryChoices = [];
// Featured section at top
if (featured.length > 0) {
categoryChoices.push({
value: '__featured__',
label: `\u2605 Featured (${featured.length} module${featured.length === 1 ? '' : 's'})`,
});
}
// Categories with module counts
for (const cat of categories) {
categoryChoices.push({
value: cat.slug,
label: `${cat.name} (${cat.moduleCount} module${cat.moduleCount === 1 ? '' : 's'})`,
});
}
// Special actions at bottom
categoryChoices.push(
{ value: '__all__', label: '\u25CE View all community modules' },
{ value: '__search__', label: '\u25CE Search by keyword' },
{ value: '__done__', label: '\u2713 Done browsing' },
);
const selectedCount = selectedCodes.size;
const categoryChoice = await prompts.select({
message: `Browse community modules${selectedCount > 0 ? ` (${selectedCount} selected)` : ''}:`,
choices: categoryChoices,
});
if (categoryChoice === '__done__') {
browsing = false;
continue;
}
let modulesToShow;
switch (categoryChoice) {
case '__featured__': {
modulesToShow = featured;
break;
}
case '__all__': {
modulesToShow = allCommunity;
break;
}
case '__search__': {
const query = await prompts.text({
message: 'Search community modules:',
placeholder: 'e.g., design, testing, game',
});
if (!query || query.trim() === '') continue;
modulesToShow = await communityMgr.searchByKeyword(query.trim());
if (modulesToShow.length === 0) {
await prompts.log.warn('No matching modules found.');
continue;
}
break;
}
default: {
modulesToShow = await communityMgr.listByCategory(categoryChoice);
}
}
// Build options for autocompleteMultiselect
const trustBadge = (tier) => {
if (tier === 'bmad-certified') return '\u2713';
if (tier === 'community-reviewed') return '\u25CB';
return '\u26A0';
};
const options = modulesToShow.map((mod) => {
const versionStr = mod.version ? ` (v${mod.version})` : '';
const badge = trustBadge(mod.trustTier);
return {
label: `${mod.displayName}${versionStr} [${badge}]`,
value: mod.code,
hint: mod.description,
};
});
// Pre-check modules that are already selected or installed
const initialValues = modulesToShow.filter((m) => selectedCodes.has(m.code) || installedModuleIds.has(m.code)).map((m) => m.code);
const selected = await prompts.autocompleteMultiselect({
message: 'Select community modules:',
options,
initialValues: initialValues.length > 0 ? initialValues : undefined,
required: false,
maxItems: Math.min(options.length, 10),
});
// Update accumulated selections: sync with what user selected in this view
const shownCodes = new Set(modulesToShow.map((m) => m.code));
for (const code of shownCodes) {
if (selected && selected.includes(code)) {
selectedCodes.add(code);
} else {
selectedCodes.delete(code);
}
}
}
if (selectedCodes.size > 0) {
const moduleLines = [];
for (const code of selectedCodes) {
const mod = await communityMgr.getModuleByCode(code);
moduleLines.push(` \u2022 ${mod?.displayName || code}`);
}
await prompts.log.message('Selected community modules:\n' + moduleLines.join('\n'));
}
return { codes: [...selectedCodes], didBrowse: true };
}
/** /**
* Prompt user to install modules from custom sources (Git URLs or local paths). * Prompt user to install modules from custom sources (Git URLs or local paths).
* @param {Set} installedModuleIds - Currently installed module IDs * @param {Set} installedModuleIds - Currently installed module IDs
@ -1121,7 +947,7 @@ class UI {
*/ */
async _addCustomUrlModules(installedModuleIds = new Set()) { async _addCustomUrlModules(installedModuleIds = new Set()) {
const addCustom = await prompts.confirm({ const addCustom = await prompts.confirm({
message: 'Would you like to install from a custom source (Git URL or local path)?', message: 'Do you want to install custom or community modules (Git URL or local path)?',
default: false, default: false,
}); });
if (!addCustom) return []; if (!addCustom) return [];