From 83f374c254dabbaae5b66174bc955bf222f0c49e Mon Sep 17 00:00:00 2001 From: Brian Madison Date: Sun, 12 Apr 2026 22:41:40 -0500 Subject: [PATCH] fix(installer): source built-in modules locally instead of from registry Core and BMM modules live in this repo (src/core-skills, src/bmm-skills) but the installer UI sourced them from the remote registry. When the registry was unreachable (VPN, proxy, firewall), the fallback YAML only had the 4 external modules, so core and bmm disappeared from the install list entirely. Now _selectOfficialModules and getDefaultModules always read built-in modules from the local source via OfficialModules.listAvailable(), then append external modules from the registry. Network failures only affect external modules. Closes #2239 --- src/core-skills/module.yaml | 1 + tools/installer/ui.js | 55 +++++++++++++++++++++++++++++-------- 2 files changed, 45 insertions(+), 11 deletions(-) diff --git a/src/core-skills/module.yaml b/src/core-skills/module.yaml index 48e7a58f7..5ac3cd887 100644 --- a/src/core-skills/module.yaml +++ b/src/core-skills/module.yaml @@ -1,5 +1,6 @@ code: core name: "BMad Core Module" +description: "Core configuration and shared resources" 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." diff --git a/tools/installer/ui.js b/tools/installer/ui.js index 527708494..9e48c647a 100644 --- a/tools/installer/ui.js +++ b/tools/installer/ui.js @@ -598,7 +598,7 @@ class UI { const officialCodes = new Set(officialSelected); const externalManager = new ExternalModuleManager(); const registryModules = await externalManager.listAvailable(); - const officialRegistryCodes = new Set(registryModules.map((m) => m.code)); + const officialRegistryCodes = new Set(['core', 'bmm', ...registryModules.map((m) => m.code)]); const installedNonOfficial = [...installedModuleIds].filter((id) => !officialRegistryCodes.has(id)); // Phase 2: Community modules (category drill-down) @@ -630,6 +630,11 @@ class UI { * @returns {Array} Selected official module codes */ async _selectOfficialModules(installedModuleIds = new Set()) { + // Built-in modules (core, bmm) come from local source, not the registry + const { OfficialModules } = require('./modules/official-modules'); + const builtInModules = (await new OfficialModules().listAvailable()).modules || []; + + // External modules come from the registry (with fallback) const externalManager = new ExternalModuleManager(); const registryModules = await externalManager.listAvailable(); @@ -637,20 +642,34 @@ class UI { const initialValues = []; const lockedValues = ['core']; - const buildModuleEntry = async (mod) => { - const isInstalled = installedModuleIds.has(mod.code); - const version = await getMarketplaceVersion(mod.code); - const label = version ? `${mod.name} (v${version})` : mod.name; + const buildModuleEntry = async (code, name, description, isDefault) => { + const isInstalled = installedModuleIds.has(code); + const version = await getMarketplaceVersion(code); + const label = version ? `${name} (v${version})` : name; return { label, - value: mod.code, - hint: mod.description, - selected: isInstalled, + value: code, + hint: description, + selected: isInstalled || isDefault, }; }; + // Add built-in modules first (always available regardless of network) + const builtInCodes = new Set(); + for (const mod of builtInModules) { + const code = mod.id; + builtInCodes.add(code); + const entry = await buildModuleEntry(code, mod.name, mod.description, mod.defaultSelected); + allOptions.push({ label: entry.label, value: entry.value, hint: entry.hint }); + if (entry.selected) { + initialValues.push(code); + } + } + + // Add external registry modules (skip built-in duplicates) for (const mod of registryModules) { - const entry = await buildModuleEntry(mod); + if (mod.builtIn || builtInCodes.has(mod.code)) continue; + const entry = await buildModuleEntry(mod.code, mod.name, mod.description, mod.defaultSelected); allOptions.push({ label: entry.label, value: entry.value, hint: entry.hint }); if (entry.selected) { initialValues.push(mod.code); @@ -1122,12 +1141,26 @@ class UI { * @returns {Array} Default module codes */ async getDefaultModules(installedModuleIds = new Set()) { + // Built-in modules with default_selected come from local source + const { OfficialModules } = require('./modules/official-modules'); + const builtInModules = (await new OfficialModules().listAvailable()).modules || []; + + const defaultModules = []; + const seen = new Set(); + + for (const mod of builtInModules) { + if (mod.defaultSelected || installedModuleIds.has(mod.id)) { + defaultModules.push(mod.id); + seen.add(mod.id); + } + } + + // Add external registry defaults const externalManager = new ExternalModuleManager(); const registryModules = await externalManager.listAvailable(); - const defaultModules = []; - for (const mod of registryModules) { + if (mod.builtIn || seen.has(mod.code)) continue; if (mod.defaultSelected || installedModuleIds.has(mod.code)) { defaultModules.push(mod.code); }