custom inst imporove

This commit is contained in:
Brian Madison
2025-12-07 01:43:44 -06:00
parent eacfba2e5b
commit b252778043
50 changed files with 1264 additions and 1897 deletions

View File

@@ -198,8 +198,8 @@ module.exports = {
}
} else {
// Discover agents from custom location
const customAgentLocation = config.custom_agent_location
? resolvePath(config.custom_agent_location, config)
const customAgentLocation = config.custom_stand_alone_location
? resolvePath(config.custom_stand_alone_location, config)
: path.join(config.bmadFolder, 'custom', 'src', 'agents');
console.log(chalk.dim(`Searching for agents in: ${customAgentLocation}\n`));

View File

@@ -236,9 +236,31 @@ class ConfigCollector {
}
this.collectedConfig[moduleName] = { ...this.existingConfig[moduleName] };
// Special handling for user_name: ensure it has a value
if (
moduleName === 'core' &&
(!this.collectedConfig[moduleName].user_name || this.collectedConfig[moduleName].user_name === '[USER_NAME]')
) {
this.collectedConfig[moduleName].user_name = this.getDefaultUsername();
}
// Also populate allAnswers for cross-referencing
for (const [key, value] of Object.entries(this.existingConfig[moduleName])) {
this.allAnswers[`${moduleName}_${key}`] = value;
// Ensure user_name is properly set in allAnswers too
let finalValue = value;
if (moduleName === 'core' && key === 'user_name' && (!value || value === '[USER_NAME]')) {
finalValue = this.getDefaultUsername();
}
this.allAnswers[`${moduleName}_${key}`] = finalValue;
}
} else if (moduleName === 'core') {
// No existing core config - ensure we at least have user_name
if (!this.collectedConfig[moduleName]) {
this.collectedConfig[moduleName] = {};
}
if (!this.collectedConfig[moduleName].user_name) {
this.collectedConfig[moduleName].user_name = this.getDefaultUsername();
this.allAnswers[`${moduleName}_user_name`] = this.getDefaultUsername();
}
}
// Show "no config" message for modules with no new questions

View File

@@ -38,7 +38,6 @@ const { CLIUtils } = require('../../../lib/cli-utils');
const { ManifestGenerator } = require('./manifest-generator');
const { IdeConfigManager } = require('./ide-config-manager');
const { replaceAgentSidecarFolders } = require('./post-install-sidecar-replacement');
const { CustomHandler } = require('../custom/handler');
class Installer {
constructor() {
@@ -52,7 +51,6 @@ class Installer {
this.dependencyResolver = new DependencyResolver();
this.configCollector = new ConfigCollector();
this.ideConfigManager = new IdeConfigManager();
this.customHandler = new CustomHandler();
this.installedFiles = []; // Track all installed files
this.ttsInjectedFiles = []; // Track files with TTS injection applied
}
@@ -914,6 +912,7 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
await this.moduleManager.runModuleInstaller('core', bmadDir, {
installedIDEs: config.ides || [],
moduleConfig: moduleConfigs.core || {},
coreConfig: moduleConfigs.core || {},
logger: {
log: (msg) => console.log(msg),
error: (msg) => console.error(msg),
@@ -931,6 +930,7 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
await this.moduleManager.runModuleInstaller(moduleName, bmadDir, {
installedIDEs: config.ides || [],
moduleConfig: moduleConfigs[moduleName] || {},
coreConfig: moduleConfigs.core || {},
logger: {
log: (msg) => console.log(msg),
error: (msg) => console.error(msg),
@@ -1028,9 +1028,6 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
}
}
// Update custom content (add new files, don't remove existing)
await this.updateCustomContent(projectDir, bmadDir);
// Replace {agent_sidecar_folder} placeholders in all agent files
console.log(chalk.dim('\n Configuring agent sidecar folders...'));
const sidecarResults = await replaceAgentSidecarFolders(bmadDir);
@@ -1168,133 +1165,6 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
return { success: true };
}
/**
* Update custom content (add new files without removing existing ones)
* @param {string} projectDir - Project directory
* @param {string} bmadDir - BMAD installation directory
*/
async updateCustomContent(projectDir, bmadDir) {
try {
// Find all custom content
const customContents = await this.customHandler.findCustomContent(projectDir);
if (customContents.length === 0) {
return; // No custom content to update
}
// Load core config
const coreConfigPath = path.join(bmadDir, 'bmb', 'config.yaml');
let coreConfig = {};
if (await fs.pathExists(coreConfigPath)) {
const yamlLib = require('yaml');
const coreConfigContent = await fs.readFile(coreConfigPath, 'utf8');
coreConfig = yamlLib.load(coreConfigContent);
}
console.log(chalk.dim('\nUpdating custom content...'));
for (const customPath of customContents) {
const customInfo = await this.customHandler.getCustomInfo(customPath);
if (customInfo) {
console.log(chalk.dim(` Checking: ${customInfo.name}`));
// Install only adds new files, doesn't remove existing ones
const results = await this.customHandler.install(
customPath,
bmadDir,
{
user_name: coreConfig.user_name,
communication_language: coreConfig.communication_language,
output_folder: coreConfig.output_folder,
bmad_folder: path.basename(bmadDir),
},
(filePath) => {
this.installedFiles.push(filePath);
},
);
// Only show if new files were added or preserved
if (results.filesCopied > 0 || results.preserved > 0) {
if (results.filesCopied > 0) {
console.log(chalk.dim(` Added ${results.filesCopied} new file(s)`));
}
if (results.preserved > 0) {
console.log(chalk.dim(` Preserved ${results.preserved} existing file(s)`));
}
}
}
}
} catch (error) {
console.warn(chalk.yellow(`Warning: Failed to update custom content: ${error.message}`));
}
}
/**
* Install custom content by ID
* @param {string} customId - Custom content ID
* @param {string} bmadDir - BMAD installation directory
* @param {Object} coreConfig - Core configuration
*/
async installCustomContent(customId, bmadDir, coreConfig) {
try {
// Find the custom content
const customContents = await this.customHandler.findCustomContent(process.cwd());
let customInfo = null;
let customPath = null;
for (const path of customContents) {
const info = await this.customHandler.getCustomInfo(path);
if (info && info.id === customId) {
customInfo = info;
customPath = path;
break;
}
}
if (!customInfo || !customPath) {
console.warn(chalk.yellow(`Warning: Custom content '${customId}' not found`));
return;
}
console.log(chalk.dim(` Installing: ${customInfo.name}`));
// Install the custom content
const results = await this.customHandler.install(
customPath,
bmadDir,
{
user_name: coreConfig.user_name,
communication_language: coreConfig.communication_language,
output_folder: coreConfig.output_folder,
bmad_folder: path.basename(bmadDir),
},
(filePath) => {
this.installedFiles.push(filePath);
},
);
// Show results
if (results.agentsInstalled > 0) {
console.log(chalk.dim(` ${results.agentsInstalled} agent(s) installed`));
}
if (results.workflowsInstalled > 0) {
console.log(chalk.dim(` ${results.workflowsInstalled} workflow(s) installed`));
}
if (results.filesCopied > 0) {
console.log(chalk.dim(` ${results.filesCopied} file(s) copied`));
}
if (results.preserved > 0) {
console.log(chalk.dim(` ${results.preserved} file(s) preserved`));
}
if (results.errors.length > 0) {
console.log(chalk.yellow(` ${results.errors.length} error(s)`));
for (const error of results.errors) console.log(chalk.dim(` - ${error}`));
}
} catch (error) {
console.error(chalk.red(`Failed to install custom content '${customId}':`, error.message));
}
}
/**
* Private: Create directory structure
*/

View File

@@ -121,8 +121,14 @@ class ModuleManager {
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
// Skip hidden directories and node_modules
if (entry.name.startsWith('.') || entry.name === 'node_modules' || entry.name === 'dist' || entry.name === 'build') {
// Skip hidden directories, node_modules, and literal placeholder directories
if (
entry.name.startsWith('.') ||
entry.name === 'node_modules' ||
entry.name === 'dist' ||
entry.name === 'build' ||
entry.name === '{project-root}'
) {
continue;
}
@@ -139,9 +145,14 @@ class ModuleManager {
// Check if this directory contains a module (install-config.yaml OR custom.yaml)
const installerConfigPath = path.join(fullPath, '_module-installer', 'install-config.yaml');
const customConfigPath = path.join(fullPath, 'custom.yaml');
const customConfigPath = path.join(fullPath, '_module-installer', 'custom.yaml');
const rootCustomConfigPath = path.join(fullPath, 'custom.yaml');
if ((await fs.pathExists(installerConfigPath)) || (await fs.pathExists(customConfigPath))) {
if (
(await fs.pathExists(installerConfigPath)) ||
(await fs.pathExists(customConfigPath)) ||
(await fs.pathExists(rootCustomConfigPath))
) {
modulePaths.add(fullPath);
// Don't scan inside modules - they might have their own nested structures
continue;
@@ -176,11 +187,12 @@ class ModuleManager {
for (const entry of entries) {
if (entry.isDirectory()) {
const modulePath = path.join(this.modulesSourcePath, entry.name);
// Check for module structure (only install-config.yaml is valid now)
// Check for module structure (install-config.yaml OR custom.yaml)
const installerConfigPath = path.join(modulePath, '_module-installer', 'install-config.yaml');
const customConfigPath = path.join(modulePath, '_module-installer', 'custom.yaml');
// Skip if this doesn't look like a module
if (!(await fs.pathExists(installerConfigPath))) {
if (!(await fs.pathExists(installerConfigPath)) && !(await fs.pathExists(customConfigPath))) {
continue;
}
@@ -228,13 +240,16 @@ class ModuleManager {
async getModuleInfo(modulePath, defaultName, sourceDescription) {
// Check for module structure (install-config.yaml OR custom.yaml)
const installerConfigPath = path.join(modulePath, '_module-installer', 'install-config.yaml');
const customConfigPath = path.join(modulePath, 'custom.yaml');
const customConfigPath = path.join(modulePath, '_module-installer', 'custom.yaml');
const rootCustomConfigPath = path.join(modulePath, 'custom.yaml');
let configPath = null;
if (await fs.pathExists(installerConfigPath)) {
configPath = installerConfigPath;
} else if (await fs.pathExists(customConfigPath)) {
configPath = customConfigPath;
} else if (await fs.pathExists(rootCustomConfigPath)) {
configPath = rootCustomConfigPath;
}
// Skip if this doesn't look like a module
@@ -242,6 +257,8 @@ class ModuleManager {
return null;
}
// Mark as custom if it's using custom.yaml OR if it's outside src/modules
const isCustomSource = sourceDescription !== 'src/modules';
const moduleInfo = {
id: defaultName,
path: modulePath,
@@ -252,7 +269,7 @@ class ModuleManager {
description: 'BMAD Module',
version: '5.0.0',
source: sourceDescription,
isCustom: configPath === customConfigPath,
isCustom: configPath === customConfigPath || configPath === rootCustomConfigPath || isCustomSource,
};
// Read module config for metadata
@@ -295,8 +312,8 @@ class ModuleManager {
return srcModulePath;
}
// Also check for custom.yaml in src/modules
const customConfigPath = path.join(srcModulePath, 'custom.yaml');
// Also check for custom.yaml in src/modules/_module-installer
const customConfigPath = path.join(srcModulePath, '_module-installer', 'custom.yaml');
if (await fs.pathExists(customConfigPath)) {
return srcModulePath;
}
@@ -314,13 +331,16 @@ class ModuleManager {
// Need to read configs to match by ID
for (const modulePath of allModulePaths) {
const installerConfigPath = path.join(modulePath, '_module-installer', 'install-config.yaml');
const customConfigPath = path.join(modulePath, 'custom.yaml');
const customConfigPath = path.join(modulePath, '_module-installer', 'custom.yaml');
const rootCustomConfigPath = path.join(modulePath, 'custom.yaml');
let configPath = null;
if (await fs.pathExists(installerConfigPath)) {
configPath = installerConfigPath;
} else if (await fs.pathExists(customConfigPath)) {
configPath = customConfigPath;
} else if (await fs.pathExists(rootCustomConfigPath)) {
configPath = rootCustomConfigPath;
}
if (configPath) {
@@ -532,7 +552,8 @@ class ModuleManager {
}
// Skip config.yaml templates - we'll generate clean ones with actual values
if (file === 'config.yaml' || file.endsWith('/config.yaml')) {
// But allow custom.yaml which is used for custom modules
if ((file === 'config.yaml' || file.endsWith('/config.yaml')) && !file.endsWith('custom.yaml')) {
continue;
}
@@ -1059,6 +1080,7 @@ class ModuleManager {
const result = await moduleInstaller.install({
projectRoot,
config: options.moduleConfig || {},
coreConfig: options.coreConfig || {},
installedIDEs: options.installedIDEs || [],
logger,
});