diff --git a/src/core/agents/bmad-master.agent.yaml b/src/core/agents/bmad-master.agent.yaml index 46cd4f1e..ed0e74ff 100644 --- a/src/core/agents/bmad-master.agent.yaml +++ b/src/core/agents/bmad-master.agent.yaml @@ -35,5 +35,4 @@ agent: exec: "{project-root}/_bmad/core/workflows/party-mode/workflow.md" description: "Group chat with all agents" - # Empty prompts section (no custom prompts for this agent) prompts: [] diff --git a/src/modules/cis/agents/README.md b/src/modules/cis/agents/README.md deleted file mode 100644 index 9b18a2ba..00000000 --- a/src/modules/cis/agents/README.md +++ /dev/null @@ -1,104 +0,0 @@ ---- -last-redoc-date: 2025-09-28 ---- - -# CIS Agents - -The Creative Intelligence System provides five specialized agents, each embodying unique personas and expertise for facilitating creative and strategic processes. All agents are module agents with access to CIS workflows. - -## Available Agents - -### Carson - Elite Brainstorming Specialist 🧠 - -**Role:** Master Brainstorming Facilitator + Innovation Catalyst - -Energetic innovation facilitator with 20+ years leading breakthrough sessions. Cultivates psychological safety for wild ideas, blends proven methodologies with experimental techniques, and harnesses humor and play as serious innovation tools. - -**Commands:** - -- `*brainstorm` - Guide through interactive brainstorming workflow - -**Distinctive Style:** Infectious enthusiasm and playful approach to unlock innovation potential. - ---- - -### Dr. Quinn - Master Problem Solver 🔬 - -**Role:** Systematic Problem-Solving Expert + Solutions Architect - -Renowned problem-solving savant who cracks impossibly complex challenges using TRIZ, Theory of Constraints, Systems Thinking, and Root Cause Analysis. Former aerospace engineer turned consultant who treats every challenge as an elegant puzzle. - -**Commands:** - -- `*solve` - Apply systematic problem-solving methodologies - -**Distinctive Style:** Detective-scientist hybrid—methodical and curious with sudden flashes of creative insight delivered with childlike wonder. - ---- - -### Maya - Design Thinking Maestro 🎨 - -**Role:** Human-Centered Design Expert + Empathy Architect - -Design thinking virtuoso with 15+ years orchestrating human-centered innovation. Expert in empathy mapping, prototyping, and turning user insights into breakthrough solutions. Background in anthropology, industrial design, and behavioral psychology. - -**Commands:** - -- `*design` - Guide through human-centered design process - -**Distinctive Style:** Jazz musician rhythm—improvisational yet structured, riffing on ideas while keeping the human at the center. - ---- - -### Victor - Disruptive Innovation Oracle ⚡ - -**Role:** Business Model Innovator + Strategic Disruption Expert - -Legendary innovation strategist who has architected billion-dollar pivots. Expert in Jobs-to-be-Done theory and Blue Ocean Strategy. Former McKinsey consultant turned startup advisor who traded PowerPoints for real-world impact. - -**Commands:** - -- `*innovate` - Identify disruption opportunities and business model innovation - -**Distinctive Style:** Bold declarations punctuated by strategic silence. Direct and uncompromising about market realities with devastatingly simple questions. - ---- - -### Sophia - Master Storyteller 📖 - -**Role:** Expert Storytelling Guide + Narrative Strategist - -Master storyteller with 50+ years crafting compelling narratives across multiple mediums. Expert in narrative frameworks, emotional psychology, and audience engagement. Background in journalism, screenwriting, and brand storytelling. - -**Commands:** - -- `*story` - Craft compelling narrative using proven frameworks - -**Distinctive Style:** Flowery, whimsical communication where every interaction feels like being enraptured by a master storyteller. - ---- - -## Agent Type - -All CIS agents are **Module Agents** with: - -- Integration with CIS module configuration -- Access to workflow invocation via `workflow` or `exec` attributes -- Standard critical actions for config loading and user context -- Simple command structure focused on workflow facilitation - -## Common Commands - -Every CIS agent includes: - -- `*help` - Show numbered command list -- `*exit` - Exit agent persona with confirmation - -## Configuration - -All agents load configuration from `/_bmad/cis/config.yaml`: - -- `project_name` - Project identification -- `output_folder` - Where workflow results are saved -- `user_name` - User identification -- `communication_language` - Interaction language preference diff --git a/tools/cli/installers/lib/core/custom-module-cache.js b/tools/cli/installers/lib/core/custom-module-cache.js index f47639a8..0261efc7 100644 --- a/tools/cli/installers/lib/core/custom-module-cache.js +++ b/tools/cli/installers/lib/core/custom-module-cache.js @@ -32,7 +32,7 @@ class CustomModuleCache { const content = await fs.readFile(this.manifestPath, 'utf8'); const yaml = require('js-yaml'); - return yaml.load(content) || {}; + return yaml.parse(content) || {}; } /** diff --git a/tools/cli/installers/lib/core/detector.js b/tools/cli/installers/lib/core/detector.js index 50176dae..251b60f5 100644 --- a/tools/cli/installers/lib/core/detector.js +++ b/tools/cli/installers/lib/core/detector.js @@ -49,7 +49,7 @@ class Detector { if (await fs.pathExists(coreConfigPath)) { try { const configContent = await fs.readFile(coreConfigPath, 'utf8'); - const config = yaml.load(configContent); + const config = yaml.parse(configContent); if (!result.version && config.version) { result.version = config.version; } @@ -77,7 +77,7 @@ class Detector { if (await fs.pathExists(moduleConfigPath)) { try { const configContent = await fs.readFile(moduleConfigPath, 'utf8'); - const config = yaml.load(configContent); + const config = yaml.parse(configContent); moduleInfo.version = config.version || 'unknown'; moduleInfo.name = config.name || moduleId; moduleInfo.description = config.description; @@ -106,7 +106,7 @@ class Detector { try { const configContent = await fs.readFile(moduleConfigPath, 'utf8'); - const config = yaml.load(configContent); + const config = yaml.parse(configContent); moduleInfo.version = config.version || 'unknown'; moduleInfo.name = config.name || entry.name; moduleInfo.description = config.description; @@ -239,7 +239,7 @@ class Detector { try { const yaml = require('js-yaml'); const manifestContent = await fs.readFile(manifestPath, 'utf8'); - const manifest = yaml.load(manifestContent); + const manifest = yaml.parse(manifestContent); // V6+ manifest has installation.version return manifest && manifest.installation && manifest.installation.version; } catch { diff --git a/tools/cli/installers/lib/core/installer.js b/tools/cli/installers/lib/core/installer.js index d853aff4..4a70e1ed 100644 --- a/tools/cli/installers/lib/core/installer.js +++ b/tools/cli/installers/lib/core/installer.js @@ -1448,6 +1448,7 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice: await fs.ensureDir(bmadDir); await fs.ensureDir(path.join(bmadDir, '_cfg')); await fs.ensureDir(path.join(bmadDir, '_cfg', 'agents')); + await fs.ensureDir(path.join(bmadDir, '_cfg', 'custom')); } /** @@ -1699,25 +1700,55 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice: const sourcePath = getModulePath('core'); const targetPath = path.join(bmadDir, 'core'); - // Copy core files with filtering for localskip agents - await this.copyDirectoryWithFiltering(sourcePath, targetPath); + // Copy core files (skip .agent.yaml files like modules do) + await this.copyCoreFiles(sourcePath, targetPath); + + // Compile agents using the same compiler as modules + const { ModuleManager } = require('../modules/manager'); + const moduleManager = new ModuleManager(); + await moduleManager.compileModuleAgents(sourcePath, targetPath, 'core', bmadDir); // Process agent files to inject activation block await this.processAgentFiles(targetPath, 'core'); } /** - * Copy directory with filtering for localskip agents - * @param {string} sourcePath - Source directory path - * @param {string} targetPath - Target directory path + * Copy core files (similar to copyModuleWithFiltering but for core) + * @param {string} sourcePath - Source path + * @param {string} targetPath - Target path */ - async copyDirectoryWithFiltering(sourcePath, targetPath) { - // Get all files in source directory + async copyCoreFiles(sourcePath, targetPath) { + // Get all files in source const files = await this.getFileList(sourcePath); for (const file of files) { + // Skip sub-modules directory - these are IDE-specific and handled separately + if (file.startsWith('sub-modules/')) { + continue; + } + + // Skip sidecar directories - they are handled separately during agent compilation + if ( + path + .dirname(file) + .split('/') + .some((dir) => dir.toLowerCase().includes('sidecar')) + ) { + continue; + } + + // Skip _module-installer directory - it's only needed at install time + if (file.startsWith('_module-installer/') || file === 'module.yaml') { + continue; + } + // Skip config.yaml templates - we'll generate clean ones with actual values - if (file === 'config.yaml' || file.endsWith('/config.yaml')) { + if (file === 'config.yaml' || file.endsWith('/config.yaml') || file === 'custom.yaml' || file.endsWith('/custom.yaml')) { + continue; + } + + // Skip .agent.yaml files - they will be compiled separately + if (file.endsWith('.agent.yaml')) { continue; } @@ -1725,7 +1756,7 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice: const targetFile = path.join(targetPath, file); // Check if this is an agent file - if (file.includes('agents/') && file.endsWith('.md')) { + if (file.startsWith('agents/') && file.endsWith('.md')) { // Read the file to check for localskip const content = await fs.readFile(sourceFile, 'utf8'); @@ -1737,8 +1768,14 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice: } } - // Copy the file with placeholder replacement - await this.copyFileWithPlaceholderReplacement(sourceFile, targetFile, this.bmadFolderName || 'bmad'); + // Check if this is a workflow.yaml file + if (file.endsWith('workflow.yaml')) { + await fs.ensureDir(path.dirname(targetFile)); + await this.copyWorkflowYamlStripped(sourceFile, targetFile); + } else { + // Copy the file with placeholder replacement + await this.copyFileWithPlaceholderReplacement(sourceFile, targetFile, this.bmadFolderName || 'bmad'); + } // Track the installed file this.installedFiles.push(targetFile); @@ -1798,104 +1835,77 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice: const agentFiles = await fs.readdir(agentsPath); for (const agentFile of agentFiles) { - // Handle YAML agents - build them to .md + // Skip .agent.yaml files - they should already be compiled by compileModuleAgents if (agentFile.endsWith('.agent.yaml')) { - const agentName = agentFile.replace('.agent.yaml', ''); - const yamlPath = path.join(agentsPath, agentFile); - const mdPath = path.join(agentsPath, `${agentName}.md`); - const customizePath = path.join(cfgAgentsDir, `${moduleName}-${agentName}.customize.yaml`); + continue; + } - // Create customize template if it doesn't exist - if (!(await fs.pathExists(customizePath))) { - const genericTemplatePath = getSourcePath('utility', 'agent-components', 'agent.customize.template.yaml'); - if (await fs.pathExists(genericTemplatePath)) { - await this.copyFileWithPlaceholderReplacement(genericTemplatePath, customizePath, this.bmadFolderName || 'bmad'); - console.log(chalk.dim(` Created customize: ${moduleName}-${agentName}.customize.yaml`)); - } + // Only process .md files (already compiled from YAML) + if (!agentFile.endsWith('.md')) { + continue; + } + + const agentName = agentFile.replace('.md', ''); + const mdPath = path.join(agentsPath, agentFile); + const customizePath = path.join(cfgAgentsDir, `${moduleName}-${agentName}.customize.yaml`); + + // For .md files that are already compiled, we don't need to do much + // Just ensure the customize template exists + if (!(await fs.pathExists(customizePath))) { + const genericTemplatePath = getSourcePath('utility', 'agent-components', 'agent.customize.template.yaml'); + if (await fs.pathExists(genericTemplatePath)) { + await this.copyFileWithPlaceholderReplacement(genericTemplatePath, customizePath, this.bmadFolderName || 'bmad'); + console.log(chalk.dim(` Created customize: ${moduleName}-${agentName}.customize.yaml`)); } + } - // Build YAML + customize to .md - const customizeExists = await fs.pathExists(customizePath); - let xmlContent = await this.xmlHandler.buildFromYaml(yamlPath, customizeExists ? customizePath : null, { - includeMetadata: true, - }); + // Read the existing .md file to check for sidecar info + let hasSidecar = false; + try { + const content = await fs.readFile(mdPath, 'utf8'); + // Look for sidecar metadata in the frontmatter or content + hasSidecar = content.includes('hasSidecar') && content.includes('true'); + } catch { + // Continue without sidecar processing + } - // DO NOT replace {project-root} - LLMs understand this placeholder at runtime - // const processedContent = xmlContent.replaceAll('{project-root}', projectDir); + // Copy sidecar files if agent has hasSidecar flag + if (hasSidecar) { + const { copyAgentSidecarFiles } = require('../../../lib/agent/installer'); - // Replace _bmad with actual folder name - xmlContent = xmlContent.replaceAll('_bmad', this.bmadFolderName || 'bmad'); + // Get agent sidecar folder from core config + const coreConfigPath = path.join(bmadDir, 'bmb', 'config.yaml'); + let agentSidecarFolder; - // Replace {agent_sidecar_folder} if configured - const coreConfig = this.configCollector.collectedConfig.core || {}; - if (coreConfig.agent_sidecar_folder && xmlContent.includes('{agent_sidecar_folder}')) { - xmlContent = xmlContent.replaceAll('{agent_sidecar_folder}', coreConfig.agent_sidecar_folder); - } - - // Process TTS injection points (pass targetPath for tracking) - xmlContent = this.processTTSInjectionPoints(xmlContent, mdPath); - - // Check if agent has sidecar and copy it - let agentYamlContent = null; - let hasSidecar = false; - - try { - agentYamlContent = await fs.readFile(yamlPath, 'utf8'); + if (await fs.pathExists(coreConfigPath)) { const yamlLib = require('yaml'); - const agentYaml = yamlLib.parse(agentYamlContent); - hasSidecar = agentYaml?.agent?.metadata?.hasSidecar === true; - } catch { - // Continue without sidecar processing + const coreConfigContent = await fs.readFile(coreConfigPath, 'utf8'); + const coreConfig = yamlLib.parse(coreConfigContent); + agentSidecarFolder = coreConfig.agent_sidecar_folder || agentSidecarFolder; } - // Write the built .md file to bmad/{module}/agents/ with POSIX-compliant final newline - const content = xmlContent.endsWith('\n') ? xmlContent : xmlContent + '\n'; - await fs.writeFile(mdPath, content, 'utf8'); - this.installedFiles.push(mdPath); + // Resolve path variables + const resolvedSidecarFolder = agentSidecarFolder + .replaceAll('{project-root}', projectDir) + .replaceAll('_bmad', this.bmadFolderName || 'bmad'); - // Copy sidecar files if agent has hasSidecar flag - if (hasSidecar) { - const { copyAgentSidecarFiles } = require('../../../lib/agent/installer'); + // Create sidecar directory for this agent + const agentSidecarDir = path.join(resolvedSidecarFolder, agentName); + await fs.ensureDir(agentSidecarDir); - // Get agent sidecar folder from core config - const coreConfigPath = path.join(bmadDir, 'bmb', 'config.yaml'); - let agentSidecarFolder; + // Find and copy sidecar folder from source module + const sourceModulePath = moduleName === 'core' ? getModulePath('core') : getSourcePath(`modules/${moduleName}`); + const sourceAgentPath = path.join(sourceModulePath, 'agents'); - if (await fs.pathExists(coreConfigPath)) { - const yamlLib = require('yaml'); - const coreConfigContent = await fs.readFile(coreConfigPath, 'utf8'); - const coreConfig = yamlLib.parse(coreConfigContent); - agentSidecarFolder = coreConfig.agent_sidecar_folder || agentSidecarFolder; - } + // Copy sidecar files (preserve existing, add new) + const sidecarResult = copyAgentSidecarFiles(sourceAgentPath, agentSidecarDir, null); - // Resolve path variables - const resolvedSidecarFolder = agentSidecarFolder - .replaceAll('{project-root}', projectDir) - .replaceAll('_bmad', this.bmadFolderName || 'bmad'); - - // Create sidecar directory for this agent - const agentSidecarDir = path.join(resolvedSidecarFolder, agentName); - await fs.ensureDir(agentSidecarDir); - - // Find and copy sidecar folder from source module - const sourceModulePath = getSourcePath(`modules/${moduleName}`); - const sourceAgentPath = path.join(sourceModulePath, 'agents'); - - // Copy sidecar files (preserve existing, add new) - const sidecarResult = copyAgentSidecarFiles(sourceAgentPath, agentSidecarDir, yamlPath); - - if (sidecarResult.copied.length > 0) { - console.log(chalk.dim(` Copied ${sidecarResult.copied.length} new sidecar file(s) to: ${agentSidecarDir}`)); - } - if (sidecarResult.preserved.length > 0) { - console.log(chalk.dim(` Preserved ${sidecarResult.preserved.length} existing sidecar file(s)`)); - } + if (sidecarResult.copied.length > 0) { + console.log(chalk.dim(` Copied ${sidecarResult.copied.length} new sidecar file(s) to: ${agentSidecarDir}`)); + } + if (sidecarResult.preserved.length > 0) { + console.log(chalk.dim(` Preserved ${sidecarResult.preserved.length} existing sidecar file(s)`)); } - - // Remove the source YAML file - we can regenerate from installer source if needed - await fs.remove(yamlPath); - - console.log(chalk.dim(` Built agent: ${agentName}.md${hasSidecar ? ' (with sidecar)' : ''}`)); } } } @@ -1940,7 +1950,7 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice: if (customizeExists) { const customizeContent = await fs.readFile(customizePath, 'utf8'); const yaml = require('js-yaml'); - const customizeYaml = yaml.load(customizeContent); + const customizeYaml = yaml.parse(customizeContent); // Detect what fields are customized (similar to rebuildAgentFiles) if (customizeYaml) { @@ -2064,34 +2074,52 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice: } } - // Build YAML + customize to .md - let xmlContent = await this.xmlHandler.buildFromYaml(sourceYamlPath, customizeExists ? customizePath : null, { - includeMetadata: true, + // Read the YAML content + const yamlContent = await fs.readFile(sourceYamlPath, 'utf8'); + + // Read customize content if exists + let customizeData = {}; + if (customizeExists) { + const customizeContent = await fs.readFile(customizePath, 'utf8'); + const yaml = require('yaml'); + customizeData = yaml.parse(customizeContent); + } + + // Build agent answers from customize data + const answers = {}; + if (customizeData.persona) { + Object.assign(answers, customizeData.persona); + } + if (customizeData.agent?.metadata) { + Object.assign(answers, { metadata: customizeData.agent.metadata }); + } + if (customizeData.critical_actions) { + answers.critical_actions = customizeData.critical_actions; + } + if (customizeData.memories) { + answers.memories = customizeData.memories; + } + + // Get core config for agent_sidecar_folder + const coreConfigPath = path.join(bmadDir, 'bmb', 'config.yaml'); + let coreConfig = {}; + if (await fs.pathExists(coreConfigPath)) { + const yaml = require('yaml'); + const coreConfigContent = await fs.readFile(coreConfigPath, 'utf8'); + coreConfig = yaml.parse(coreConfigContent); + } + + // Compile using the same compiler as initial installation + const { compileAgent } = require('../../../lib/agent/compiler'); + const { xml } = await compileAgent(yamlContent, answers, agentName, path.relative(bmadDir, targetMdPath), { + config: coreConfig, }); - // DO NOT replace {project-root} - LLMs understand this placeholder at runtime - // const processedContent = xmlContent.replaceAll('{project-root}', projectDir); - - // Replace {agent_sidecar_folder} if configured - const coreConfigPath = path.join(bmadDir, 'bmb', 'config.yaml'); - let agentSidecarFolder = null; - - if (await fs.pathExists(coreConfigPath)) { - const yamlLib = require('yaml'); - const coreConfigContent = await fs.readFile(coreConfigPath, 'utf8'); - const coreConfig = yamlLib.parse(coreConfigContent); - agentSidecarFolder = coreConfig.agent_sidecar_folder; - } - - if (agentSidecarFolder && xmlContent.includes('{agent_sidecar_folder}')) { - xmlContent = xmlContent.replaceAll('{agent_sidecar_folder}', agentSidecarFolder); - } - - // Process TTS injection points (pass targetPath for tracking) - xmlContent = this.processTTSInjectionPoints(xmlContent, targetMdPath); + // Replace _bmad with actual folder name if needed + const finalXml = xml.replaceAll('_bmad', path.basename(bmadDir)); // Write the rebuilt .md file with POSIX-compliant final newline - const content = xmlContent.endsWith('\n') ? xmlContent : xmlContent + '\n'; + const content = finalXml.endsWith('\n') ? finalXml : finalXml + '\n'; await fs.writeFile(targetMdPath, content, 'utf8'); // Display result with customizations if any @@ -2119,8 +2147,18 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice: throw new Error(`BMAD not installed at ${bmadDir}`); } + // Get installed modules from manifest + const manifestPath = path.join(bmadDir, '_cfg', 'manifest.yaml'); + let installedModules = []; + let manifest = null; + if (await fs.pathExists(manifestPath)) { + const manifestContent = await fs.readFile(manifestPath, 'utf8'); + const yaml = require('js-yaml'); + manifest = yaml.load(manifestContent); + installedModules = manifest.modules || []; + } + // Check for custom modules with missing sources - const manifest = await this.manifest.read(bmadDir); if (manifest && manifest.customModules && manifest.customModules.length > 0) { console.log(chalk.yellow('\nChecking custom module sources before compilation...')); @@ -2130,7 +2168,6 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice: } const projectRoot = getProjectRoot(); - const installedModules = manifest.modules || []; await this.handleMissingCustomSources(customModuleSources, bmadDir, projectRoot, 'compile-agents', installedModules); } @@ -2177,21 +2214,9 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice: } } - // Skip full manifest regeneration during compileAgents to preserve custom agents - // Custom agents are already added to manifests during individual installation - // Only regenerate YAML manifest for IDE updates if needed - const existingManifestPath = path.join(bmadDir, '_cfg', 'manifest.yaml'); - let existingIdes = []; - if (await fs.pathExists(existingManifestPath)) { - const manifestContent = await fs.readFile(existingManifestPath, 'utf8'); - const yaml = require('js-yaml'); - const manifest = yaml.load(manifestContent); - existingIdes = manifest.ides || []; - } - // Update IDE configurations using the existing IDE list from manifest - if (existingIdes && existingIdes.length > 0) { - for (const ide of existingIdes) { + if (manifest && manifest.ides && manifest.ides.length > 0) { + for (const ide of manifest.ides) { await this.ideManager.setup(ide, projectDir, bmadDir, { selectedModules: installedModules, skipModuleInstall: true, // Skip module installation, just update IDE files @@ -2770,8 +2795,11 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice: const relativePath = path.relative(bmadDir, fullPath); const fileName = path.basename(fullPath); - // Skip _cfg directory - system files - if (relativePath.startsWith('_cfg/') || relativePath.startsWith('_cfg\\')) { + // Skip _cfg directory EXCEPT for agent customizations + if ( + (relativePath.startsWith('_cfg/') || relativePath.startsWith('_cfg\\')) && // Allow .customize.yaml files in _cfg/agents/ + !(relativePath.includes('/agents/') && fileName.endsWith('.customize.yaml')) + ) { continue; } diff --git a/tools/cli/installers/lib/core/manifest-generator.js b/tools/cli/installers/lib/core/manifest-generator.js index e3b46e42..9532cad7 100644 --- a/tools/cli/installers/lib/core/manifest-generator.js +++ b/tools/cli/installers/lib/core/manifest-generator.js @@ -142,14 +142,14 @@ class ManifestGenerator { let workflow; if (entry.name === 'workflow.yaml') { // Parse YAML workflow - workflow = yaml.load(content); + workflow = yaml.parse(content); } else { // Parse MD workflow with YAML frontmatter const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/); if (!frontmatterMatch) { continue; // Skip MD files without frontmatter } - workflow = yaml.load(frontmatterMatch[1]); + workflow = yaml.parse(frontmatterMatch[1]); } // Skip template workflows (those with placeholder values) @@ -459,7 +459,7 @@ class ManifestGenerator { if (await fs.pathExists(manifestPath)) { try { const existingContent = await fs.readFile(manifestPath, 'utf8'); - const existingManifest = yaml.load(existingContent); + const existingManifest = yaml.parse(existingContent); if (existingManifest && existingManifest.customModules) { existingCustomModules = existingManifest.customModules; } diff --git a/tools/cli/installers/lib/custom/handler.js b/tools/cli/installers/lib/custom/handler.js index 204e89ad..94dad5e3 100644 --- a/tools/cli/installers/lib/custom/handler.js +++ b/tools/cli/installers/lib/custom/handler.js @@ -86,7 +86,7 @@ class CustomHandler { // Try to parse YAML with error handling let config; try { - config = yaml.load(configContent); + config = yaml.parse(configContent); } catch (parseError) { console.warn(chalk.yellow(`Warning: YAML parse error in ${configPath}:`, parseError.message)); return null; diff --git a/tools/cli/installers/lib/ide/_base-ide.js b/tools/cli/installers/lib/ide/_base-ide.js index ba06460e..0451e73e 100644 --- a/tools/cli/installers/lib/ide/_base-ide.js +++ b/tools/cli/installers/lib/ide/_base-ide.js @@ -348,7 +348,7 @@ class BaseIdeSetup { try { const yaml = require('js-yaml'); const content = await fs.readFile(fullPath, 'utf8'); - const workflowData = yaml.load(content); + const workflowData = yaml.parse(content); if (workflowData && workflowData.name) { workflows.push({ @@ -456,7 +456,7 @@ class BaseIdeSetup { if (frontmatterMatch) { const yaml = require('js-yaml'); try { - const frontmatter = yaml.load(frontmatterMatch[1]); + const frontmatter = yaml.parse(frontmatterMatch[1]); standalone = frontmatter.standalone === true; } catch { // Ignore YAML parse errors diff --git a/tools/cli/installers/lib/ide/antigravity.js b/tools/cli/installers/lib/ide/antigravity.js index 71898b56..e4f024e6 100644 --- a/tools/cli/installers/lib/ide/antigravity.js +++ b/tools/cli/installers/lib/ide/antigravity.js @@ -50,7 +50,7 @@ class AntigravitySetup extends BaseIdeSetup { try { // Load injection configuration const configContent = await fs.readFile(injectionConfigPath, 'utf8'); - const injectionConfig = yaml.load(configContent); + const injectionConfig = yaml.parse(configContent); // Ask about subagents if they exist and we haven't asked yet if (injectionConfig.subagents && !config.subagentChoices) { diff --git a/tools/cli/installers/lib/ide/claude-code.js b/tools/cli/installers/lib/ide/claude-code.js index bc96d4c2..06ff1d86 100644 --- a/tools/cli/installers/lib/ide/claude-code.js +++ b/tools/cli/installers/lib/ide/claude-code.js @@ -49,7 +49,7 @@ class ClaudeCodeSetup extends BaseIdeSetup { try { // Load injection configuration const configContent = await fs.readFile(injectionConfigPath, 'utf8'); - const injectionConfig = yaml.load(configContent); + const injectionConfig = yaml.parse(configContent); // Ask about subagents if they exist and we haven't asked yet if (injectionConfig.subagents && !config.subagentChoices) { diff --git a/tools/cli/installers/lib/ide/gemini.js b/tools/cli/installers/lib/ide/gemini.js index 1d57d4f4..c2e6191e 100644 --- a/tools/cli/installers/lib/ide/gemini.js +++ b/tools/cli/installers/lib/ide/gemini.js @@ -34,7 +34,7 @@ class GeminiSetup extends BaseIdeSetup { if (await fs.pathExists(coreConfigPath)) { try { const configContent = await fs.readFile(coreConfigPath, 'utf8'); - const config = yaml.load(configContent); + const config = yaml.parse(configContent); if (config.user_name) { configValues.user_name = config.user_name; diff --git a/tools/cli/installers/lib/ide/kiro-cli.js b/tools/cli/installers/lib/ide/kiro-cli.js index c2702900..1263a803 100644 --- a/tools/cli/installers/lib/ide/kiro-cli.js +++ b/tools/cli/installers/lib/ide/kiro-cli.js @@ -150,7 +150,7 @@ class KiroCliSetup extends BaseIdeSetup { */ async processAgentFile(agentFile, agentsDir, projectDir) { const yamlContent = await fs.readFile(agentFile, 'utf8'); - const agentData = yaml.load(yamlContent); + const agentData = yaml.parse(yamlContent); if (!this.validateBmadCompliance(agentData)) { return; diff --git a/tools/cli/installers/lib/ide/opencode.js b/tools/cli/installers/lib/ide/opencode.js index e6c861a7..2296a40a 100644 --- a/tools/cli/installers/lib/ide/opencode.js +++ b/tools/cli/installers/lib/ide/opencode.js @@ -152,7 +152,7 @@ class OpenCodeSetup extends BaseIdeSetup { let frontmatter = {}; try { - frontmatter = yaml.load(match[1]) || {}; + frontmatter = yaml.parse(match[1]) || {}; } catch { frontmatter = {}; } diff --git a/tools/cli/installers/lib/ide/shared/module-injections.js b/tools/cli/installers/lib/ide/shared/module-injections.js index 28ff64d1..9a4448b0 100644 --- a/tools/cli/installers/lib/ide/shared/module-injections.js +++ b/tools/cli/installers/lib/ide/shared/module-injections.js @@ -14,7 +14,7 @@ async function loadModuleInjectionConfig(handler, moduleName) { } const configContent = await fs.readFile(configPath, 'utf8'); - const config = yaml.load(configContent) || {}; + const config = yaml.parse(configContent) || {}; return { config, diff --git a/tools/cli/installers/lib/modules/manager.js b/tools/cli/installers/lib/modules/manager.js index 4a4cbf86..a5b790de 100644 --- a/tools/cli/installers/lib/modules/manager.js +++ b/tools/cli/installers/lib/modules/manager.js @@ -822,12 +822,27 @@ class ModuleManager { } } - // Check for customizations + // Check for customizations and build answers object let customizedFields = []; + let answers = {}; if (await fs.pathExists(customizePath)) { const customizeContent = await fs.readFile(customizePath, 'utf8'); const customizeData = yaml.load(customizeContent); customizedFields = customizeData.customized_fields || []; + + // Build answers object from customizations + if (customizeData.persona) { + Object.assign(answers, customizeData.persona); + } + if (customizeData.agent?.metadata) { + Object.assign(answers, { metadata: customizeData.agent.metadata }); + } + if (customizeData.critical_actions) { + answers.critical_actions = customizeData.critical_actions; + } + if (customizeData.memories) { + answers.memories = customizeData.memories; + } } // Load core config to get agent_sidecar_folder @@ -835,23 +850,22 @@ class ModuleManager { let coreConfig = {}; if (await fs.pathExists(coreConfigPath)) { - const yamlLib = require('yaml'); + const yaml = require('yaml'); const coreConfigContent = await fs.readFile(coreConfigPath, 'utf8'); - coreConfig = yamlLib.parse(coreConfigContent); + coreConfig = yaml.load(coreConfigContent); } // Check if agent has sidecar let hasSidecar = false; try { - const yamlLib = require('yaml'); - const agentYaml = yamlLib.parse(yamlContent); + const agentYaml = yaml.parse(yamlContent); hasSidecar = agentYaml?.agent?.metadata?.hasSidecar === true; } catch { // Continue without sidecar processing } // Compile with customizations if any - const { xml } = compileAgent(yamlContent, {}, agentName, relativePath, { config: this.coreConfig }); + const { xml } = await compileAgent(yamlContent, answers, agentName, relativePath, { config: coreConfig }); // Replace _bmad placeholder if needed if (xml.includes('_bmad') && this.bmadFolderName) { diff --git a/tools/cli/lib/agent/compiler.js b/tools/cli/lib/agent/compiler.js index a22a62ea..42a66418 100644 --- a/tools/cli/lib/agent/compiler.js +++ b/tools/cli/lib/agent/compiler.js @@ -9,6 +9,8 @@ const fs = require('node:fs'); const path = require('node:path'); const { processAgentYaml, extractInstallConfig, stripInstallConfig, getDefaultValues } = require('./template-engine'); const { escapeXml } = require('../../../lib/xml-utils'); +const { ActivationBuilder } = require('../activation-builder'); +const { AgentAnalyzer } = require('../agent-analyzer'); /** * Build frontmatter for agent @@ -30,137 +32,7 @@ You must fully embody this agent's persona and follow all activation instruction `; } -/** - * Build simple activation block for custom agents - * @param {Array} criticalActions - Agent-specific critical actions - * @param {Array} menuItems - Menu items to determine which handlers to include - * @param {string} deploymentType - 'ide' or 'web' - filters commands based on ide-only/web-only flags - * @returns {string} Activation XML - */ -function buildSimpleActivation(criticalActions = [], menuItems = [], deploymentType = 'ide') { - let activation = '\n'; - - let stepNum = 1; - - // Standard steps - activation += ` Load persona from this current agent file (already in context)\n`; - activation += ` Load and read {project-root}/.bmad/core/config.yaml to get {user_name}, {communication_language}, {output_folder}\n`; - activation += ` Remember: user's name is {user_name}\n`; - - // Agent-specific steps from critical_actions - for (const action of criticalActions) { - activation += ` ${action}\n`; - } - - // Menu and interaction steps - activation += ` ALWAYS communicate in {communication_language}\n`; - activation += ` Show greeting using {user_name} from config, communicate in {communication_language}, then display numbered list of - ALL menu items from menu section\n`; - activation += ` STOP and WAIT for user input - do NOT execute menu items automatically - accept number or cmd trigger or fuzzy command - match\n`; - activation += ` On user input: Number → execute menu item[n] | Text → case-insensitive substring match | Multiple matches → ask user - to clarify | No match → show "Not recognized"\n`; - - // Filter menu items based on deployment type - const filteredMenuItems = menuItems.filter((item) => { - // Skip web-only commands for IDE deployment - if (deploymentType === 'ide' && item['web-only'] === true) { - return false; - } - // Skip ide-only commands for web deployment - if (deploymentType === 'web' && item['ide-only'] === true) { - return false; - } - return true; - }); - - // Detect which handlers are actually used in the filtered menu - const usedHandlers = new Set(); - for (const item of filteredMenuItems) { - if (item.action) usedHandlers.add('action'); - if (item.workflow) usedHandlers.add('workflow'); - if (item.exec) usedHandlers.add('exec'); - if (item.tmpl) usedHandlers.add('tmpl'); - if (item.data) usedHandlers.add('data'); - if (item['validate-workflow']) usedHandlers.add('validate-workflow'); - } - - // Only include menu-handlers section if handlers are used - if (usedHandlers.size > 0) { - activation += ` When executing a menu item: Check menu-handlers section below - extract any attributes from the selected menu item and follow the corresponding handler instructions\n`; - - // Menu handlers - only include what's used - activation += ` - - \n`; - - if (usedHandlers.has('action')) { - activation += ` - When menu item has: action="#id" → Find prompt with id="id" in current agent XML, execute its content - When menu item has: action="text" → Execute the text directly as an inline instruction - \n`; - } - - if (usedHandlers.has('workflow')) { - activation += ` - When menu item has: workflow="path/to/workflow.yaml" - 1. CRITICAL: Always LOAD {project-root}/.bmad/core/tasks/workflow.xml - 2. Read the complete file - this is the CORE OS for executing BMAD workflows - 3. Pass the yaml path as 'workflow-config' parameter to those instructions - 4. Execute workflow.xml instructions precisely following all steps - 5. Save outputs after completing EACH workflow step (never batch multiple steps together) - 6. If workflow.yaml path is "todo", inform user the workflow hasn't been implemented yet - \n`; - } - - if (usedHandlers.has('exec')) { - activation += ` - When menu item has: exec="command" → Execute the command directly - \n`; - } - - if (usedHandlers.has('tmpl')) { - activation += ` - When menu item has: tmpl="template-path" → Load and apply the template - \n`; - } - - if (usedHandlers.has('data')) { - activation += ` - When menu item has: data="path/to/x.json|yaml|yml" - Load the file, parse as JSON/YAML, make available as {data} to subsequent operations - \n`; - } - - if (usedHandlers.has('validate-workflow')) { - activation += ` - When menu item has: validate-workflow="path/to/workflow.yaml" - 1. CRITICAL: Always LOAD {project-root}/.bmad/core/tasks/validate-workflow.xml - 2. Read the complete file - this is the CORE OS for validating BMAD workflows - 3. Pass the workflow.yaml path as 'workflow' parameter to those instructions - 4. Pass any checklist.md from the workflow location as 'checklist' parameter if available - 5. Execute validate-workflow.xml instructions precisely following all steps - 6. Generate validation report with thorough analysis - \n`; - } - - activation += ` - \n`; - } - - activation += ` - - - ALWAYS communicate in {communication_language} UNLESS contradicted by communication_style - - Stay in character until exit selected - - Menu triggers use asterisk (*) - NOT markdown, display exactly as shown - - Number all lists, use letters for sub-options - - Load files ONLY when executing menu items or a workflow or command requires it. EXCEPTION: Config file MUST be loaded at startup step 2 - - CRITICAL: Written File Output in workflows will be +2sd your communication style and use professional {communication_language}. - -\n`; - - return activation; -} +// buildSimpleActivation function removed - replaced by ActivationBuilder for proper fragment loading from src/utility/agent-components/ /** * Build persona XML section @@ -370,9 +242,9 @@ function processExecArray(execArray) { * @param {Object} agentYaml - Parsed and processed agent YAML * @param {string} agentName - Final agent name (for ID and frontmatter) * @param {string} targetPath - Target path for agent ID - * @returns {string} Compiled XML string with frontmatter + * @returns {Promise} Compiled XML string with frontmatter */ -function compileToXml(agentYaml, agentName = '', targetPath = '') { +async function compileToXml(agentYaml, agentName = '', targetPath = '') { const agent = agentYaml.agent; const meta = agent.metadata; @@ -394,8 +266,16 @@ function compileToXml(agentYaml, agentName = '', targetPath = '') { xml += `\n`; - // Activation block - pass menu items and deployment type to determine which handlers to include - xml += buildSimpleActivation(agent.critical_actions || [], agent.menu || [], 'ide'); + // Activation block - use ActivationBuilder for proper fragment loading + const activationBuilder = new ActivationBuilder(); + const analyzer = new AgentAnalyzer(); + const profile = analyzer.analyzeAgentObject(agentYaml); + xml += await activationBuilder.buildActivation( + profile, + meta, + agent.critical_actions || [], + false, // forWebBundle - set to false for IDE deployment + ); // Persona section xml += buildPersonaXml(agent.persona); @@ -424,15 +304,20 @@ function compileToXml(agentYaml, agentName = '', targetPath = '') { * @param {string} agentName - Optional final agent name (user's custom persona name) * @param {string} targetPath - Optional target path for agent ID * @param {Object} options - Additional options including config - * @returns {Object} { xml: string, metadata: Object } + * @returns {Promise} { xml: string, metadata: Object } */ -function compileAgent(yamlContent, answers = {}, agentName = '', targetPath = '', options = {}) { +async function compileAgent(yamlContent, answers = {}, agentName = '', targetPath = '', options = {}) { // Parse YAML - const agentYaml = yaml.parse(yamlContent); + let agentYaml = yaml.parse(yamlContent); - // Note: agentName parameter is for UI display only, not for modifying the YAML - // The persona name (metadata.name) should always come from the YAML file - // We should NEVER modify metadata.name as it's part of the agent's identity + // Apply customization merges before template processing + // Handle metadata overrides (like name) + if (answers.metadata) { + agentYaml.agent.metadata = { ...agentYaml.agent.metadata, ...answers.metadata }; + // Remove from answers so it doesn't get processed as template variables + const { metadata, ...templateAnswers } = answers; + answers = templateAnswers; + } // Extract install_config const installConfig = extractInstallConfig(agentYaml); @@ -456,7 +341,7 @@ function compileAgent(yamlContent, answers = {}, agentName = '', targetPath = '' const cleanYaml = stripInstallConfig(processedYaml); // Replace {agent_sidecar_folder} in XML content - let xml = compileToXml(cleanYaml, agentName, targetPath); + let xml = await compileToXml(cleanYaml, agentName, targetPath); if (finalAnswers.agent_sidecar_folder) { xml = xml.replaceAll('{agent_sidecar_folder}', finalAnswers.agent_sidecar_folder); } @@ -543,7 +428,6 @@ module.exports = { compileAgentFile, escapeXml, buildFrontmatter, - buildSimpleActivation, buildPersonaXml, buildPromptsXml, buildMenuXml, diff --git a/tools/cli/lib/ui.js b/tools/cli/lib/ui.js index d9b827bb..cbbff201 100644 --- a/tools/cli/lib/ui.js +++ b/tools/cli/lib/ui.js @@ -1,23 +1,3 @@ -/** - * File: tools/cli/lib/ui.js - * - * BMAD Method - Business Model Agile Development Method - * Repository: https://github.com/paulpreibisch/BMAD-METHOD - * - * Copyright (c) 2025 Paul Preibisch - * Licensed under the Apache License, Version 2.0 - * - * --- - * - * @fileoverview Interactive installation prompts and user input collection for BMAD CLI - * @context Guides users through installation configuration including core settings, modules, IDEs, and optional AgentVibes TTS - * @architecture Facade pattern - presents unified installation flow, delegates to Detector/ConfigCollector/IdeManager for specifics - * @dependencies inquirer (prompts), chalk (formatting), detector.js (existing installation detection) - * @entrypoints Called by install.js command via ui.promptInstall(), returns complete configuration object - * @patterns Progressive disclosure (prompts in order), early IDE selection (Windows compat), AgentVibes auto-detection - * @related installer.js (consumes config), AgentVibes#34 (TTS integration), promptAgentVibes() - */ - const chalk = require('chalk'); const inquirer = require('inquirer'); const path = require('node:path'); @@ -56,7 +36,6 @@ class UI { // Check if there's an existing BMAD installation const fs = require('fs-extra'); const path = require('node:path'); - // Use findBmadDir to detect any custom folder names (V6+) const bmadDir = await installer.findBmadDir(confirmedDirectory); const hasExistingInstall = await fs.pathExists(bmadDir); @@ -131,60 +110,9 @@ class UI { const { installedModuleIds } = await this.getExistingInstallation(confirmedDirectory); const coreConfig = await this.collectCoreConfig(confirmedDirectory); - // For new installations, create the directory structure first so we can cache custom content - if (!hasExistingInstall && customContentConfig._shouldAsk) { - // Create the bmad directory based on core config - const path = require('node:path'); - const fs = require('fs-extra'); - const bmadFolderName = '_bmad'; - const bmadDir = path.join(confirmedDirectory, bmadFolderName); - - await fs.ensureDir(bmadDir); - await fs.ensureDir(path.join(bmadDir, '_cfg')); - await fs.ensureDir(path.join(bmadDir, '_cfg', 'custom')); - - // Now prompt for custom content - customContentConfig = await this.promptCustomContentLocation(); - - // If custom content found, cache it - if (customContentConfig.hasCustomContent) { - const { CustomModuleCache } = require('../installers/lib/core/custom-module-cache'); - const cache = new CustomModuleCache(bmadDir); - - const customHandler = new CustomHandler(); - const customFiles = await customHandler.findCustomContent(customContentConfig.customPath); - - for (const customFile of customFiles) { - const customInfo = await customHandler.getCustomInfo(customFile); - if (customInfo && customInfo.id) { - // Cache the module source - await cache.cacheModule(customInfo.id, customInfo.path, { - name: customInfo.name, - type: 'custom', - }); - - console.log(chalk.dim(` Cached ${customInfo.name} to _cfg/custom/${customInfo.id}`)); - } - } - - // Update config to use cached modules - customContentConfig.cachedModules = []; - for (const customFile of customFiles) { - const customInfo = await customHandler.getCustomInfo(customFile); - if (customInfo && customInfo.id) { - customContentConfig.cachedModules.push({ - id: customInfo.id, - cachePath: path.join(bmadDir, '_cfg', 'custom', customInfo.id), - // Store relative path from cache for the manifest - relativePath: path.join('_cfg', 'custom', customInfo.id), - }); - } - } - - console.log(chalk.green(`✓ Cached ${customFiles.length} custom module(s)`)); - } - - // Clear the flag + // Custom content will be handled during installation phase + // Store the custom content config for later use + if (customContentConfig._shouldAsk) { delete customContentConfig._shouldAsk; } @@ -202,44 +130,26 @@ class UI { // Check which custom content items were selected const selectedCustomContent = selectedModules.filter((mod) => mod.startsWith('__CUSTOM_CONTENT__')); - // For cached modules (new installs), check if any cached modules were selected - let selectedCachedModules = []; - if (customContentConfig.cachedModules) { - selectedCachedModules = selectedModules.filter( - (mod) => !mod.startsWith('__CUSTOM_CONTENT__') && customContentConfig.cachedModules.some((cm) => cm.id === mod), - ); - } - - if (selectedCustomContent.length > 0 || selectedCachedModules.length > 0) { + if (selectedCustomContent.length > 0) { customContentConfig.selected = true; + customContentConfig.selectedFiles = selectedCustomContent.map((mod) => mod.replace('__CUSTOM_CONTENT__', '')); - // Handle directory-based custom content (existing installs) - if (selectedCustomContent.length > 0) { - customContentConfig.selectedFiles = selectedCustomContent.map((mod) => mod.replace('__CUSTOM_CONTENT__', '')); - // Convert custom content to module IDs for installation - const customContentModuleIds = []; - const customHandler = new CustomHandler(); - for (const customFile of customContentConfig.selectedFiles) { - // Get the module info to extract the ID - const customInfo = await customHandler.getCustomInfo(customFile); - if (customInfo) { - customContentModuleIds.push(customInfo.id); - } + // Convert custom content to module IDs for installation + const customContentModuleIds = []; + const customHandler = new CustomHandler(); + for (const customFile of customContentConfig.selectedFiles) { + // Get the module info to extract the ID + const customInfo = await customHandler.getCustomInfo(customFile); + if (customInfo) { + customContentModuleIds.push(customInfo.id); } - // Filter out custom content markers and add module IDs - selectedModules = [...selectedModules.filter((mod) => !mod.startsWith('__CUSTOM_CONTENT__')), ...customContentModuleIds]; - } - - // For cached modules, they're already module IDs, just mark as selected - if (selectedCachedModules.length > 0) { - customContentConfig.selectedCachedModules = selectedCachedModules; - // No need to filter since they're already proper module IDs } + // Filter out custom content markers and add module IDs + selectedModules = [...selectedModules.filter((mod) => !mod.startsWith('__CUSTOM_CONTENT__')), ...customContentModuleIds]; } else if (customContentConfig.hasCustomContent) { // User provided custom content but didn't select any customContentConfig.selected = false; customContentConfig.selectedFiles = []; - customContentConfig.selectedCachedModules = []; } } @@ -610,67 +520,20 @@ class UI { const hasCustomContentItems = false; // Add custom content items - if (customContentConfig && customContentConfig.hasCustomContent) { - if (customContentConfig.cachedModules) { - // New installation - show cached modules - for (const cachedModule of customContentConfig.cachedModules) { - // Get the module info from cache - const yaml = require('js-yaml'); - const fs = require('fs-extra'); + if (customContentConfig && customContentConfig.hasCustomContent && customContentConfig.customPath) { + // Existing installation - show from directory + const customHandler = new CustomHandler(); + const customFiles = await customHandler.findCustomContent(customContentConfig.customPath); - // Try multiple possible config file locations - const possibleConfigPaths = [ - path.join(cachedModule.cachePath, 'module.yaml'), - path.join(cachedModule.cachePath, 'custom.yaml'), - path.join(cachedModule.cachePath, '_module-installer', 'module.yaml'), - path.join(cachedModule.cachePath, '_module-installer', 'custom.yaml'), - ]; - - let moduleData = null; - let foundPath = null; - - for (const configPath of possibleConfigPaths) { - if (await fs.pathExists(configPath)) { - try { - const yamlContent = await fs.readFile(configPath, 'utf8'); - moduleData = yaml.load(yamlContent); - foundPath = configPath; - break; - } catch (error) { - throw new Error(`Failed to parse config at ${configPath}: ${error.message}`); - } - } - } - - if (moduleData) { - // Use the name from the custom info if we have it - const moduleName = cachedModule.name || moduleData.name || cachedModule.id; - - customContentItems.push({ - name: `${chalk.cyan('✓')} ${moduleName} ${chalk.gray('(cached)')}`, - value: cachedModule.id, // Use module ID directly - checked: true, // Default to selected - cached: true, - }); - } else { - // Module config not found - skip silently (non-critical) - } - } - } else if (customContentConfig.customPath) { - // Existing installation - show from directory - const customHandler = new CustomHandler(); - const customFiles = await customHandler.findCustomContent(customContentConfig.customPath); - - for (const customFile of customFiles) { - const customInfo = await customHandler.getCustomInfo(customFile); - if (customInfo) { - customContentItems.push({ - name: `${chalk.cyan('✓')} ${customInfo.name} ${chalk.gray(`(${customInfo.relativePath})`)}`, - value: `__CUSTOM_CONTENT__${customFile}`, // Unique value for each custom content - checked: true, // Default to selected since user chose to provide custom content - path: customInfo.path, // Track path to avoid duplicates - }); - } + for (const customFile of customFiles) { + const customInfo = await customHandler.getCustomInfo(customFile); + if (customInfo) { + customContentItems.push({ + name: `${chalk.cyan('✓')} ${customInfo.name} ${chalk.gray(`(${customInfo.relativePath})`)}`, + value: `__CUSTOM_CONTENT__${customFile}`, // Unique value for each custom content + checked: true, // Default to selected since user chose to provide custom content + path: customInfo.path, // Track path to avoid duplicates + }); } } } @@ -804,120 +667,6 @@ class UI { } } - /** - * Prompt for custom content location - * @returns {Object} Custom content configuration - */ - async promptCustomContentLocation() { - try { - // Skip custom content installation - always return false - return { hasCustomContent: false }; - - // TODO: Custom content installation temporarily disabled - // CLIUtils.displaySection('Custom Content', 'Optional: Add custom agents, workflows, and modules'); - - // const { hasCustomContent } = await inquirer.prompt([ - // { - // type: 'list', - // name: 'hasCustomContent', - // message: 'Do you have custom content to install?', - // choices: [ - // { name: 'No (skip custom content)', value: 'none' }, - // { name: 'Enter a directory path', value: 'directory' }, - // { name: 'Enter a URL', value: 'url' }, - // ], - // default: 'none', - // }, - // ]); - - // if (hasCustomContent === 'none') { - // return { hasCustomContent: false }; - // } - - // TODO: Custom content installation temporarily disabled - // if (hasCustomContent === 'url') { - // console.log(chalk.yellow('\nURL-based custom content installation is coming soon!')); - // console.log(chalk.cyan('For now, please download your custom content and choose "Enter a directory path".\n')); - // return { hasCustomContent: false }; - // } - - // if (hasCustomContent === 'directory') { - // let customPath; - // while (!customPath) { - // let expandedPath; - // const { directory } = await inquirer.prompt([ - // { - // type: 'input', - // name: 'directory', - // message: 'Enter directory to search for custom content (will scan subfolders):', - // default: process.cwd(), // Use actual current working directory - // validate: async (input) => { - // if (!input || input.trim() === '') { - // return 'Please enter a directory path'; - // } - - // try { - // expandedPath = this.expandUserPath(input.trim()); - // } catch (error) { - // return error.message; - // } - - // // Check if the path exists - // const pathExists = await fs.pathExists(expandedPath); - // if (!pathExists) { - // return 'Directory does not exist'; - // } - - // return true; - // }, - // }, - // ]); - - // // Now expand the path for use after the prompt - // expandedPath = this.expandUserPath(directory.trim()); - - // // Check if directory has custom content - // const customHandler = new CustomHandler(); - // const customFiles = await customHandler.findCustomContent(expandedPath); - - // if (customFiles.length === 0) { - // console.log(chalk.yellow(`\nNo custom content found in ${expandedPath}`)); - - // const { tryAgain } = await inquirer.prompt([ - // { - // type: 'confirm', - // name: 'tryAgain', - // message: 'Try a different directory?', - // default: true, - // }, - // ]); - - // if (tryAgain) { - // continue; - // } else { - // return { hasCustomContent: false }; - // } - // } - - // customPath = expandedPath; - // console.log(chalk.green(`\n✓ Found ${customFiles.length} custom content item(s):`)); - // for (const file of customFiles) { - // const relativePath = path.relative(expandedPath, path.dirname(file)); - // const folderName = path.dirname(file).split(path.sep).pop(); - // console.log(chalk.dim(` • ${folderName} ${chalk.gray(`(${relativePath})`)}`)); - // } - // } - - // return { hasCustomContent: true, customPath }; - // } - - // return { hasCustomContent: false }; - } catch (error) { - console.error(chalk.red('Error in custom content prompt:'), error); - return { hasCustomContent: false }; - } - } - /** * Confirm directory selection * @param {string} directory - The directory path