mirror of
https://github.com/bmadcode/BMAD-METHOD.git
synced 2025-12-17 09:45:25 +00:00
agent customzation almost working again
This commit is contained in:
parent
25c79e3fe5
commit
ce42d56fdd
@ -35,5 +35,4 @@ agent:
|
|||||||
exec: "{project-root}/_bmad/core/workflows/party-mode/workflow.md"
|
exec: "{project-root}/_bmad/core/workflows/party-mode/workflow.md"
|
||||||
description: "Group chat with all agents"
|
description: "Group chat with all agents"
|
||||||
|
|
||||||
# Empty prompts section (no custom prompts for this agent)
|
|
||||||
prompts: []
|
prompts: []
|
||||||
|
|||||||
@ -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
|
|
||||||
@ -32,7 +32,7 @@ class CustomModuleCache {
|
|||||||
|
|
||||||
const content = await fs.readFile(this.manifestPath, 'utf8');
|
const content = await fs.readFile(this.manifestPath, 'utf8');
|
||||||
const yaml = require('js-yaml');
|
const yaml = require('js-yaml');
|
||||||
return yaml.load(content) || {};
|
return yaml.parse(content) || {};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -49,7 +49,7 @@ class Detector {
|
|||||||
if (await fs.pathExists(coreConfigPath)) {
|
if (await fs.pathExists(coreConfigPath)) {
|
||||||
try {
|
try {
|
||||||
const configContent = await fs.readFile(coreConfigPath, 'utf8');
|
const configContent = await fs.readFile(coreConfigPath, 'utf8');
|
||||||
const config = yaml.load(configContent);
|
const config = yaml.parse(configContent);
|
||||||
if (!result.version && config.version) {
|
if (!result.version && config.version) {
|
||||||
result.version = config.version;
|
result.version = config.version;
|
||||||
}
|
}
|
||||||
@ -77,7 +77,7 @@ class Detector {
|
|||||||
if (await fs.pathExists(moduleConfigPath)) {
|
if (await fs.pathExists(moduleConfigPath)) {
|
||||||
try {
|
try {
|
||||||
const configContent = await fs.readFile(moduleConfigPath, 'utf8');
|
const configContent = await fs.readFile(moduleConfigPath, 'utf8');
|
||||||
const config = yaml.load(configContent);
|
const config = yaml.parse(configContent);
|
||||||
moduleInfo.version = config.version || 'unknown';
|
moduleInfo.version = config.version || 'unknown';
|
||||||
moduleInfo.name = config.name || moduleId;
|
moduleInfo.name = config.name || moduleId;
|
||||||
moduleInfo.description = config.description;
|
moduleInfo.description = config.description;
|
||||||
@ -106,7 +106,7 @@ class Detector {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const configContent = await fs.readFile(moduleConfigPath, 'utf8');
|
const configContent = await fs.readFile(moduleConfigPath, 'utf8');
|
||||||
const config = yaml.load(configContent);
|
const config = yaml.parse(configContent);
|
||||||
moduleInfo.version = config.version || 'unknown';
|
moduleInfo.version = config.version || 'unknown';
|
||||||
moduleInfo.name = config.name || entry.name;
|
moduleInfo.name = config.name || entry.name;
|
||||||
moduleInfo.description = config.description;
|
moduleInfo.description = config.description;
|
||||||
@ -239,7 +239,7 @@ class Detector {
|
|||||||
try {
|
try {
|
||||||
const yaml = require('js-yaml');
|
const yaml = require('js-yaml');
|
||||||
const manifestContent = await fs.readFile(manifestPath, 'utf8');
|
const manifestContent = await fs.readFile(manifestPath, 'utf8');
|
||||||
const manifest = yaml.load(manifestContent);
|
const manifest = yaml.parse(manifestContent);
|
||||||
// V6+ manifest has installation.version
|
// V6+ manifest has installation.version
|
||||||
return manifest && manifest.installation && manifest.installation.version;
|
return manifest && manifest.installation && manifest.installation.version;
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
@ -1448,6 +1448,7 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
|||||||
await fs.ensureDir(bmadDir);
|
await fs.ensureDir(bmadDir);
|
||||||
await fs.ensureDir(path.join(bmadDir, '_cfg'));
|
await fs.ensureDir(path.join(bmadDir, '_cfg'));
|
||||||
await fs.ensureDir(path.join(bmadDir, '_cfg', 'agents'));
|
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 sourcePath = getModulePath('core');
|
||||||
const targetPath = path.join(bmadDir, 'core');
|
const targetPath = path.join(bmadDir, 'core');
|
||||||
|
|
||||||
// Copy core files with filtering for localskip agents
|
// Copy core files (skip .agent.yaml files like modules do)
|
||||||
await this.copyDirectoryWithFiltering(sourcePath, targetPath);
|
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
|
// Process agent files to inject activation block
|
||||||
await this.processAgentFiles(targetPath, 'core');
|
await this.processAgentFiles(targetPath, 'core');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copy directory with filtering for localskip agents
|
* Copy core files (similar to copyModuleWithFiltering but for core)
|
||||||
* @param {string} sourcePath - Source directory path
|
* @param {string} sourcePath - Source path
|
||||||
* @param {string} targetPath - Target directory path
|
* @param {string} targetPath - Target path
|
||||||
*/
|
*/
|
||||||
async copyDirectoryWithFiltering(sourcePath, targetPath) {
|
async copyCoreFiles(sourcePath, targetPath) {
|
||||||
// Get all files in source directory
|
// Get all files in source
|
||||||
const files = await this.getFileList(sourcePath);
|
const files = await this.getFileList(sourcePath);
|
||||||
|
|
||||||
for (const file of files) {
|
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
|
// 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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1725,7 +1756,7 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
|||||||
const targetFile = path.join(targetPath, file);
|
const targetFile = path.join(targetPath, file);
|
||||||
|
|
||||||
// Check if this is an agent 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
|
// Read the file to check for localskip
|
||||||
const content = await fs.readFile(sourceFile, 'utf8');
|
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
|
// Check if this is a workflow.yaml file
|
||||||
await this.copyFileWithPlaceholderReplacement(sourceFile, targetFile, this.bmadFolderName || 'bmad');
|
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
|
// Track the installed file
|
||||||
this.installedFiles.push(targetFile);
|
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);
|
const agentFiles = await fs.readdir(agentsPath);
|
||||||
|
|
||||||
for (const agentFile of agentFiles) {
|
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')) {
|
if (agentFile.endsWith('.agent.yaml')) {
|
||||||
const agentName = agentFile.replace('.agent.yaml', '');
|
continue;
|
||||||
const yamlPath = path.join(agentsPath, agentFile);
|
}
|
||||||
const mdPath = path.join(agentsPath, `${agentName}.md`);
|
|
||||||
const customizePath = path.join(cfgAgentsDir, `${moduleName}-${agentName}.customize.yaml`);
|
|
||||||
|
|
||||||
// Create customize template if it doesn't exist
|
// Only process .md files (already compiled from YAML)
|
||||||
if (!(await fs.pathExists(customizePath))) {
|
if (!agentFile.endsWith('.md')) {
|
||||||
const genericTemplatePath = getSourcePath('utility', 'agent-components', 'agent.customize.template.yaml');
|
continue;
|
||||||
if (await fs.pathExists(genericTemplatePath)) {
|
}
|
||||||
await this.copyFileWithPlaceholderReplacement(genericTemplatePath, customizePath, this.bmadFolderName || 'bmad');
|
|
||||||
console.log(chalk.dim(` Created customize: ${moduleName}-${agentName}.customize.yaml`));
|
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
|
// Read the existing .md file to check for sidecar info
|
||||||
const customizeExists = await fs.pathExists(customizePath);
|
let hasSidecar = false;
|
||||||
let xmlContent = await this.xmlHandler.buildFromYaml(yamlPath, customizeExists ? customizePath : null, {
|
try {
|
||||||
includeMetadata: true,
|
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
|
// Copy sidecar files if agent has hasSidecar flag
|
||||||
// const processedContent = xmlContent.replaceAll('{project-root}', projectDir);
|
if (hasSidecar) {
|
||||||
|
const { copyAgentSidecarFiles } = require('../../../lib/agent/installer');
|
||||||
|
|
||||||
// Replace _bmad with actual folder name
|
// Get agent sidecar folder from core config
|
||||||
xmlContent = xmlContent.replaceAll('_bmad', this.bmadFolderName || 'bmad');
|
const coreConfigPath = path.join(bmadDir, 'bmb', 'config.yaml');
|
||||||
|
let agentSidecarFolder;
|
||||||
|
|
||||||
// Replace {agent_sidecar_folder} if configured
|
if (await fs.pathExists(coreConfigPath)) {
|
||||||
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');
|
|
||||||
const yamlLib = require('yaml');
|
const yamlLib = require('yaml');
|
||||||
const agentYaml = yamlLib.parse(agentYamlContent);
|
const coreConfigContent = await fs.readFile(coreConfigPath, 'utf8');
|
||||||
hasSidecar = agentYaml?.agent?.metadata?.hasSidecar === true;
|
const coreConfig = yamlLib.parse(coreConfigContent);
|
||||||
} catch {
|
agentSidecarFolder = coreConfig.agent_sidecar_folder || agentSidecarFolder;
|
||||||
// Continue without sidecar processing
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write the built .md file to bmad/{module}/agents/ with POSIX-compliant final newline
|
// Resolve path variables
|
||||||
const content = xmlContent.endsWith('\n') ? xmlContent : xmlContent + '\n';
|
const resolvedSidecarFolder = agentSidecarFolder
|
||||||
await fs.writeFile(mdPath, content, 'utf8');
|
.replaceAll('{project-root}', projectDir)
|
||||||
this.installedFiles.push(mdPath);
|
.replaceAll('_bmad', this.bmadFolderName || 'bmad');
|
||||||
|
|
||||||
// Copy sidecar files if agent has hasSidecar flag
|
// Create sidecar directory for this agent
|
||||||
if (hasSidecar) {
|
const agentSidecarDir = path.join(resolvedSidecarFolder, agentName);
|
||||||
const { copyAgentSidecarFiles } = require('../../../lib/agent/installer');
|
await fs.ensureDir(agentSidecarDir);
|
||||||
|
|
||||||
// Get agent sidecar folder from core config
|
// Find and copy sidecar folder from source module
|
||||||
const coreConfigPath = path.join(bmadDir, 'bmb', 'config.yaml');
|
const sourceModulePath = moduleName === 'core' ? getModulePath('core') : getSourcePath(`modules/${moduleName}`);
|
||||||
let agentSidecarFolder;
|
const sourceAgentPath = path.join(sourceModulePath, 'agents');
|
||||||
|
|
||||||
if (await fs.pathExists(coreConfigPath)) {
|
// Copy sidecar files (preserve existing, add new)
|
||||||
const yamlLib = require('yaml');
|
const sidecarResult = copyAgentSidecarFiles(sourceAgentPath, agentSidecarDir, null);
|
||||||
const coreConfigContent = await fs.readFile(coreConfigPath, 'utf8');
|
|
||||||
const coreConfig = yamlLib.parse(coreConfigContent);
|
|
||||||
agentSidecarFolder = coreConfig.agent_sidecar_folder || agentSidecarFolder;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resolve path variables
|
if (sidecarResult.copied.length > 0) {
|
||||||
const resolvedSidecarFolder = agentSidecarFolder
|
console.log(chalk.dim(` Copied ${sidecarResult.copied.length} new sidecar file(s) to: ${agentSidecarDir}`));
|
||||||
.replaceAll('{project-root}', projectDir)
|
}
|
||||||
.replaceAll('_bmad', this.bmadFolderName || 'bmad');
|
if (sidecarResult.preserved.length > 0) {
|
||||||
|
console.log(chalk.dim(` Preserved ${sidecarResult.preserved.length} existing sidecar file(s)`));
|
||||||
// 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)`));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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) {
|
if (customizeExists) {
|
||||||
const customizeContent = await fs.readFile(customizePath, 'utf8');
|
const customizeContent = await fs.readFile(customizePath, 'utf8');
|
||||||
const yaml = require('js-yaml');
|
const yaml = require('js-yaml');
|
||||||
const customizeYaml = yaml.load(customizeContent);
|
const customizeYaml = yaml.parse(customizeContent);
|
||||||
|
|
||||||
// Detect what fields are customized (similar to rebuildAgentFiles)
|
// Detect what fields are customized (similar to rebuildAgentFiles)
|
||||||
if (customizeYaml) {
|
if (customizeYaml) {
|
||||||
@ -2064,34 +2074,52 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build YAML + customize to .md
|
// Read the YAML content
|
||||||
let xmlContent = await this.xmlHandler.buildFromYaml(sourceYamlPath, customizeExists ? customizePath : null, {
|
const yamlContent = await fs.readFile(sourceYamlPath, 'utf8');
|
||||||
includeMetadata: true,
|
|
||||||
|
// 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
|
// Replace _bmad with actual folder name if needed
|
||||||
// const processedContent = xmlContent.replaceAll('{project-root}', projectDir);
|
const finalXml = xml.replaceAll('_bmad', path.basename(bmadDir));
|
||||||
|
|
||||||
// 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);
|
|
||||||
|
|
||||||
// Write the rebuilt .md file with POSIX-compliant final newline
|
// 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');
|
await fs.writeFile(targetMdPath, content, 'utf8');
|
||||||
|
|
||||||
// Display result with customizations if any
|
// 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}`);
|
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
|
// Check for custom modules with missing sources
|
||||||
const manifest = await this.manifest.read(bmadDir);
|
|
||||||
if (manifest && manifest.customModules && manifest.customModules.length > 0) {
|
if (manifest && manifest.customModules && manifest.customModules.length > 0) {
|
||||||
console.log(chalk.yellow('\nChecking custom module sources before compilation...'));
|
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 projectRoot = getProjectRoot();
|
||||||
const installedModules = manifest.modules || [];
|
|
||||||
await this.handleMissingCustomSources(customModuleSources, bmadDir, projectRoot, 'compile-agents', installedModules);
|
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
|
// Update IDE configurations using the existing IDE list from manifest
|
||||||
if (existingIdes && existingIdes.length > 0) {
|
if (manifest && manifest.ides && manifest.ides.length > 0) {
|
||||||
for (const ide of existingIdes) {
|
for (const ide of manifest.ides) {
|
||||||
await this.ideManager.setup(ide, projectDir, bmadDir, {
|
await this.ideManager.setup(ide, projectDir, bmadDir, {
|
||||||
selectedModules: installedModules,
|
selectedModules: installedModules,
|
||||||
skipModuleInstall: true, // Skip module installation, just update IDE files
|
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 relativePath = path.relative(bmadDir, fullPath);
|
||||||
const fileName = path.basename(fullPath);
|
const fileName = path.basename(fullPath);
|
||||||
|
|
||||||
// Skip _cfg directory - system files
|
// Skip _cfg directory EXCEPT for agent customizations
|
||||||
if (relativePath.startsWith('_cfg/') || relativePath.startsWith('_cfg\\')) {
|
if (
|
||||||
|
(relativePath.startsWith('_cfg/') || relativePath.startsWith('_cfg\\')) && // Allow .customize.yaml files in _cfg/agents/
|
||||||
|
!(relativePath.includes('/agents/') && fileName.endsWith('.customize.yaml'))
|
||||||
|
) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -142,14 +142,14 @@ class ManifestGenerator {
|
|||||||
let workflow;
|
let workflow;
|
||||||
if (entry.name === 'workflow.yaml') {
|
if (entry.name === 'workflow.yaml') {
|
||||||
// Parse YAML workflow
|
// Parse YAML workflow
|
||||||
workflow = yaml.load(content);
|
workflow = yaml.parse(content);
|
||||||
} else {
|
} else {
|
||||||
// Parse MD workflow with YAML frontmatter
|
// Parse MD workflow with YAML frontmatter
|
||||||
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
||||||
if (!frontmatterMatch) {
|
if (!frontmatterMatch) {
|
||||||
continue; // Skip MD files without frontmatter
|
continue; // Skip MD files without frontmatter
|
||||||
}
|
}
|
||||||
workflow = yaml.load(frontmatterMatch[1]);
|
workflow = yaml.parse(frontmatterMatch[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip template workflows (those with placeholder values)
|
// Skip template workflows (those with placeholder values)
|
||||||
@ -459,7 +459,7 @@ class ManifestGenerator {
|
|||||||
if (await fs.pathExists(manifestPath)) {
|
if (await fs.pathExists(manifestPath)) {
|
||||||
try {
|
try {
|
||||||
const existingContent = await fs.readFile(manifestPath, 'utf8');
|
const existingContent = await fs.readFile(manifestPath, 'utf8');
|
||||||
const existingManifest = yaml.load(existingContent);
|
const existingManifest = yaml.parse(existingContent);
|
||||||
if (existingManifest && existingManifest.customModules) {
|
if (existingManifest && existingManifest.customModules) {
|
||||||
existingCustomModules = existingManifest.customModules;
|
existingCustomModules = existingManifest.customModules;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -86,7 +86,7 @@ class CustomHandler {
|
|||||||
// Try to parse YAML with error handling
|
// Try to parse YAML with error handling
|
||||||
let config;
|
let config;
|
||||||
try {
|
try {
|
||||||
config = yaml.load(configContent);
|
config = yaml.parse(configContent);
|
||||||
} catch (parseError) {
|
} catch (parseError) {
|
||||||
console.warn(chalk.yellow(`Warning: YAML parse error in ${configPath}:`, parseError.message));
|
console.warn(chalk.yellow(`Warning: YAML parse error in ${configPath}:`, parseError.message));
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@ -348,7 +348,7 @@ class BaseIdeSetup {
|
|||||||
try {
|
try {
|
||||||
const yaml = require('js-yaml');
|
const yaml = require('js-yaml');
|
||||||
const content = await fs.readFile(fullPath, 'utf8');
|
const content = await fs.readFile(fullPath, 'utf8');
|
||||||
const workflowData = yaml.load(content);
|
const workflowData = yaml.parse(content);
|
||||||
|
|
||||||
if (workflowData && workflowData.name) {
|
if (workflowData && workflowData.name) {
|
||||||
workflows.push({
|
workflows.push({
|
||||||
@ -456,7 +456,7 @@ class BaseIdeSetup {
|
|||||||
if (frontmatterMatch) {
|
if (frontmatterMatch) {
|
||||||
const yaml = require('js-yaml');
|
const yaml = require('js-yaml');
|
||||||
try {
|
try {
|
||||||
const frontmatter = yaml.load(frontmatterMatch[1]);
|
const frontmatter = yaml.parse(frontmatterMatch[1]);
|
||||||
standalone = frontmatter.standalone === true;
|
standalone = frontmatter.standalone === true;
|
||||||
} catch {
|
} catch {
|
||||||
// Ignore YAML parse errors
|
// Ignore YAML parse errors
|
||||||
|
|||||||
@ -50,7 +50,7 @@ class AntigravitySetup extends BaseIdeSetup {
|
|||||||
try {
|
try {
|
||||||
// Load injection configuration
|
// Load injection configuration
|
||||||
const configContent = await fs.readFile(injectionConfigPath, 'utf8');
|
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
|
// Ask about subagents if they exist and we haven't asked yet
|
||||||
if (injectionConfig.subagents && !config.subagentChoices) {
|
if (injectionConfig.subagents && !config.subagentChoices) {
|
||||||
|
|||||||
@ -49,7 +49,7 @@ class ClaudeCodeSetup extends BaseIdeSetup {
|
|||||||
try {
|
try {
|
||||||
// Load injection configuration
|
// Load injection configuration
|
||||||
const configContent = await fs.readFile(injectionConfigPath, 'utf8');
|
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
|
// Ask about subagents if they exist and we haven't asked yet
|
||||||
if (injectionConfig.subagents && !config.subagentChoices) {
|
if (injectionConfig.subagents && !config.subagentChoices) {
|
||||||
|
|||||||
@ -34,7 +34,7 @@ class GeminiSetup extends BaseIdeSetup {
|
|||||||
if (await fs.pathExists(coreConfigPath)) {
|
if (await fs.pathExists(coreConfigPath)) {
|
||||||
try {
|
try {
|
||||||
const configContent = await fs.readFile(coreConfigPath, 'utf8');
|
const configContent = await fs.readFile(coreConfigPath, 'utf8');
|
||||||
const config = yaml.load(configContent);
|
const config = yaml.parse(configContent);
|
||||||
|
|
||||||
if (config.user_name) {
|
if (config.user_name) {
|
||||||
configValues.user_name = config.user_name;
|
configValues.user_name = config.user_name;
|
||||||
|
|||||||
@ -150,7 +150,7 @@ class KiroCliSetup extends BaseIdeSetup {
|
|||||||
*/
|
*/
|
||||||
async processAgentFile(agentFile, agentsDir, projectDir) {
|
async processAgentFile(agentFile, agentsDir, projectDir) {
|
||||||
const yamlContent = await fs.readFile(agentFile, 'utf8');
|
const yamlContent = await fs.readFile(agentFile, 'utf8');
|
||||||
const agentData = yaml.load(yamlContent);
|
const agentData = yaml.parse(yamlContent);
|
||||||
|
|
||||||
if (!this.validateBmadCompliance(agentData)) {
|
if (!this.validateBmadCompliance(agentData)) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -152,7 +152,7 @@ class OpenCodeSetup extends BaseIdeSetup {
|
|||||||
|
|
||||||
let frontmatter = {};
|
let frontmatter = {};
|
||||||
try {
|
try {
|
||||||
frontmatter = yaml.load(match[1]) || {};
|
frontmatter = yaml.parse(match[1]) || {};
|
||||||
} catch {
|
} catch {
|
||||||
frontmatter = {};
|
frontmatter = {};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,7 +14,7 @@ async function loadModuleInjectionConfig(handler, moduleName) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const configContent = await fs.readFile(configPath, 'utf8');
|
const configContent = await fs.readFile(configPath, 'utf8');
|
||||||
const config = yaml.load(configContent) || {};
|
const config = yaml.parse(configContent) || {};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
config,
|
config,
|
||||||
|
|||||||
@ -822,12 +822,27 @@ class ModuleManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for customizations
|
// Check for customizations and build answers object
|
||||||
let customizedFields = [];
|
let customizedFields = [];
|
||||||
|
let answers = {};
|
||||||
if (await fs.pathExists(customizePath)) {
|
if (await fs.pathExists(customizePath)) {
|
||||||
const customizeContent = await fs.readFile(customizePath, 'utf8');
|
const customizeContent = await fs.readFile(customizePath, 'utf8');
|
||||||
const customizeData = yaml.load(customizeContent);
|
const customizeData = yaml.load(customizeContent);
|
||||||
customizedFields = customizeData.customized_fields || [];
|
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
|
// Load core config to get agent_sidecar_folder
|
||||||
@ -835,23 +850,22 @@ class ModuleManager {
|
|||||||
let coreConfig = {};
|
let coreConfig = {};
|
||||||
|
|
||||||
if (await fs.pathExists(coreConfigPath)) {
|
if (await fs.pathExists(coreConfigPath)) {
|
||||||
const yamlLib = require('yaml');
|
const yaml = require('yaml');
|
||||||
const coreConfigContent = await fs.readFile(coreConfigPath, 'utf8');
|
const coreConfigContent = await fs.readFile(coreConfigPath, 'utf8');
|
||||||
coreConfig = yamlLib.parse(coreConfigContent);
|
coreConfig = yaml.load(coreConfigContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if agent has sidecar
|
// Check if agent has sidecar
|
||||||
let hasSidecar = false;
|
let hasSidecar = false;
|
||||||
try {
|
try {
|
||||||
const yamlLib = require('yaml');
|
const agentYaml = yaml.parse(yamlContent);
|
||||||
const agentYaml = yamlLib.parse(yamlContent);
|
|
||||||
hasSidecar = agentYaml?.agent?.metadata?.hasSidecar === true;
|
hasSidecar = agentYaml?.agent?.metadata?.hasSidecar === true;
|
||||||
} catch {
|
} catch {
|
||||||
// Continue without sidecar processing
|
// Continue without sidecar processing
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compile with customizations if any
|
// 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
|
// Replace _bmad placeholder if needed
|
||||||
if (xml.includes('_bmad') && this.bmadFolderName) {
|
if (xml.includes('_bmad') && this.bmadFolderName) {
|
||||||
|
|||||||
@ -9,6 +9,8 @@ const fs = require('node:fs');
|
|||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const { processAgentYaml, extractInstallConfig, stripInstallConfig, getDefaultValues } = require('./template-engine');
|
const { processAgentYaml, extractInstallConfig, stripInstallConfig, getDefaultValues } = require('./template-engine');
|
||||||
const { escapeXml } = require('../../../lib/xml-utils');
|
const { escapeXml } = require('../../../lib/xml-utils');
|
||||||
|
const { ActivationBuilder } = require('../activation-builder');
|
||||||
|
const { AgentAnalyzer } = require('../agent-analyzer');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build frontmatter for agent
|
* Build frontmatter for agent
|
||||||
@ -30,137 +32,7 @@ You must fully embody this agent's persona and follow all activation instruction
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// buildSimpleActivation function removed - replaced by ActivationBuilder for proper fragment loading from src/utility/agent-components/
|
||||||
* 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 = '<activation critical="MANDATORY">\n';
|
|
||||||
|
|
||||||
let stepNum = 1;
|
|
||||||
|
|
||||||
// Standard steps
|
|
||||||
activation += ` <step n="${stepNum++}">Load persona from this current agent file (already in context)</step>\n`;
|
|
||||||
activation += ` <step n="${stepNum++}">Load and read {project-root}/.bmad/core/config.yaml to get {user_name}, {communication_language}, {output_folder}</step>\n`;
|
|
||||||
activation += ` <step n="${stepNum++}">Remember: user's name is {user_name}</step>\n`;
|
|
||||||
|
|
||||||
// Agent-specific steps from critical_actions
|
|
||||||
for (const action of criticalActions) {
|
|
||||||
activation += ` <step n="${stepNum++}">${action}</step>\n`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Menu and interaction steps
|
|
||||||
activation += ` <step n="${stepNum++}">ALWAYS communicate in {communication_language}</step>\n`;
|
|
||||||
activation += ` <step n="${stepNum++}">Show greeting using {user_name} from config, communicate in {communication_language}, then display numbered list of
|
|
||||||
ALL menu items from menu section</step>\n`;
|
|
||||||
activation += ` <step n="${stepNum++}">STOP and WAIT for user input - do NOT execute menu items automatically - accept number or cmd trigger or fuzzy command
|
|
||||||
match</step>\n`;
|
|
||||||
activation += ` <step n="${stepNum++}">On user input: Number → execute menu item[n] | Text → case-insensitive substring match | Multiple matches → ask user
|
|
||||||
to clarify | No match → show "Not recognized"</step>\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 += ` <step n="${stepNum++}">When executing a menu item: Check menu-handlers section below - extract any attributes from the selected menu item and follow the corresponding handler instructions</step>\n`;
|
|
||||||
|
|
||||||
// Menu handlers - only include what's used
|
|
||||||
activation += `
|
|
||||||
<menu-handlers>
|
|
||||||
<handlers>\n`;
|
|
||||||
|
|
||||||
if (usedHandlers.has('action')) {
|
|
||||||
activation += ` <handler type="action">
|
|
||||||
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
|
|
||||||
</handler>\n`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (usedHandlers.has('workflow')) {
|
|
||||||
activation += ` <handler type="workflow">
|
|
||||||
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
|
|
||||||
</handler>\n`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (usedHandlers.has('exec')) {
|
|
||||||
activation += ` <handler type="exec">
|
|
||||||
When menu item has: exec="command" → Execute the command directly
|
|
||||||
</handler>\n`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (usedHandlers.has('tmpl')) {
|
|
||||||
activation += ` <handler type="tmpl">
|
|
||||||
When menu item has: tmpl="template-path" → Load and apply the template
|
|
||||||
</handler>\n`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (usedHandlers.has('data')) {
|
|
||||||
activation += ` <handler type="data">
|
|
||||||
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
|
|
||||||
</handler>\n`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (usedHandlers.has('validate-workflow')) {
|
|
||||||
activation += ` <handler type="validate-workflow">
|
|
||||||
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
|
|
||||||
</handler>\n`;
|
|
||||||
}
|
|
||||||
|
|
||||||
activation += ` </handlers>
|
|
||||||
</menu-handlers>\n`;
|
|
||||||
}
|
|
||||||
|
|
||||||
activation += `
|
|
||||||
<rules>
|
|
||||||
- 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}.
|
|
||||||
</rules>
|
|
||||||
</activation>\n`;
|
|
||||||
|
|
||||||
return activation;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build persona XML section
|
* Build persona XML section
|
||||||
@ -370,9 +242,9 @@ function processExecArray(execArray) {
|
|||||||
* @param {Object} agentYaml - Parsed and processed agent YAML
|
* @param {Object} agentYaml - Parsed and processed agent YAML
|
||||||
* @param {string} agentName - Final agent name (for ID and frontmatter)
|
* @param {string} agentName - Final agent name (for ID and frontmatter)
|
||||||
* @param {string} targetPath - Target path for agent ID
|
* @param {string} targetPath - Target path for agent ID
|
||||||
* @returns {string} Compiled XML string with frontmatter
|
* @returns {Promise<string>} Compiled XML string with frontmatter
|
||||||
*/
|
*/
|
||||||
function compileToXml(agentYaml, agentName = '', targetPath = '') {
|
async function compileToXml(agentYaml, agentName = '', targetPath = '') {
|
||||||
const agent = agentYaml.agent;
|
const agent = agentYaml.agent;
|
||||||
const meta = agent.metadata;
|
const meta = agent.metadata;
|
||||||
|
|
||||||
@ -394,8 +266,16 @@ function compileToXml(agentYaml, agentName = '', targetPath = '') {
|
|||||||
|
|
||||||
xml += `<agent ${agentAttrs.join(' ')}>\n`;
|
xml += `<agent ${agentAttrs.join(' ')}>\n`;
|
||||||
|
|
||||||
// Activation block - pass menu items and deployment type to determine which handlers to include
|
// Activation block - use ActivationBuilder for proper fragment loading
|
||||||
xml += buildSimpleActivation(agent.critical_actions || [], agent.menu || [], 'ide');
|
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
|
// Persona section
|
||||||
xml += buildPersonaXml(agent.persona);
|
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} agentName - Optional final agent name (user's custom persona name)
|
||||||
* @param {string} targetPath - Optional target path for agent ID
|
* @param {string} targetPath - Optional target path for agent ID
|
||||||
* @param {Object} options - Additional options including config
|
* @param {Object} options - Additional options including config
|
||||||
* @returns {Object} { xml: string, metadata: Object }
|
* @returns {Promise<Object>} { xml: string, metadata: Object }
|
||||||
*/
|
*/
|
||||||
function compileAgent(yamlContent, answers = {}, agentName = '', targetPath = '', options = {}) {
|
async function compileAgent(yamlContent, answers = {}, agentName = '', targetPath = '', options = {}) {
|
||||||
// Parse YAML
|
// 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
|
// Apply customization merges before template processing
|
||||||
// The persona name (metadata.name) should always come from the YAML file
|
// Handle metadata overrides (like name)
|
||||||
// We should NEVER modify metadata.name as it's part of the agent's identity
|
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
|
// Extract install_config
|
||||||
const installConfig = extractInstallConfig(agentYaml);
|
const installConfig = extractInstallConfig(agentYaml);
|
||||||
@ -456,7 +341,7 @@ function compileAgent(yamlContent, answers = {}, agentName = '', targetPath = ''
|
|||||||
const cleanYaml = stripInstallConfig(processedYaml);
|
const cleanYaml = stripInstallConfig(processedYaml);
|
||||||
|
|
||||||
// Replace {agent_sidecar_folder} in XML content
|
// 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) {
|
if (finalAnswers.agent_sidecar_folder) {
|
||||||
xml = xml.replaceAll('{agent_sidecar_folder}', finalAnswers.agent_sidecar_folder);
|
xml = xml.replaceAll('{agent_sidecar_folder}', finalAnswers.agent_sidecar_folder);
|
||||||
}
|
}
|
||||||
@ -543,7 +428,6 @@ module.exports = {
|
|||||||
compileAgentFile,
|
compileAgentFile,
|
||||||
escapeXml,
|
escapeXml,
|
||||||
buildFrontmatter,
|
buildFrontmatter,
|
||||||
buildSimpleActivation,
|
|
||||||
buildPersonaXml,
|
buildPersonaXml,
|
||||||
buildPromptsXml,
|
buildPromptsXml,
|
||||||
buildMenuXml,
|
buildMenuXml,
|
||||||
|
|||||||
@ -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 chalk = require('chalk');
|
||||||
const inquirer = require('inquirer');
|
const inquirer = require('inquirer');
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
@ -56,7 +36,6 @@ class UI {
|
|||||||
// Check if there's an existing BMAD installation
|
// Check if there's an existing BMAD installation
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
// Use findBmadDir to detect any custom folder names (V6+)
|
|
||||||
const bmadDir = await installer.findBmadDir(confirmedDirectory);
|
const bmadDir = await installer.findBmadDir(confirmedDirectory);
|
||||||
const hasExistingInstall = await fs.pathExists(bmadDir);
|
const hasExistingInstall = await fs.pathExists(bmadDir);
|
||||||
|
|
||||||
@ -131,60 +110,9 @@ class UI {
|
|||||||
const { installedModuleIds } = await this.getExistingInstallation(confirmedDirectory);
|
const { installedModuleIds } = await this.getExistingInstallation(confirmedDirectory);
|
||||||
const coreConfig = await this.collectCoreConfig(confirmedDirectory);
|
const coreConfig = await this.collectCoreConfig(confirmedDirectory);
|
||||||
|
|
||||||
// For new installations, create the directory structure first so we can cache custom content
|
// Custom content will be handled during installation phase
|
||||||
if (!hasExistingInstall && customContentConfig._shouldAsk) {
|
// Store the custom content config for later use
|
||||||
// Create the bmad directory based on core config
|
if (customContentConfig._shouldAsk) {
|
||||||
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
|
|
||||||
delete customContentConfig._shouldAsk;
|
delete customContentConfig._shouldAsk;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -202,44 +130,26 @@ class UI {
|
|||||||
// Check which custom content items were selected
|
// Check which custom content items were selected
|
||||||
const selectedCustomContent = selectedModules.filter((mod) => mod.startsWith('__CUSTOM_CONTENT__'));
|
const selectedCustomContent = selectedModules.filter((mod) => mod.startsWith('__CUSTOM_CONTENT__'));
|
||||||
|
|
||||||
// For cached modules (new installs), check if any cached modules were selected
|
if (selectedCustomContent.length > 0) {
|
||||||
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) {
|
|
||||||
customContentConfig.selected = true;
|
customContentConfig.selected = true;
|
||||||
|
customContentConfig.selectedFiles = selectedCustomContent.map((mod) => mod.replace('__CUSTOM_CONTENT__', ''));
|
||||||
|
|
||||||
// Handle directory-based custom content (existing installs)
|
// Convert custom content to module IDs for installation
|
||||||
if (selectedCustomContent.length > 0) {
|
const customContentModuleIds = [];
|
||||||
customContentConfig.selectedFiles = selectedCustomContent.map((mod) => mod.replace('__CUSTOM_CONTENT__', ''));
|
const customHandler = new CustomHandler();
|
||||||
// Convert custom content to module IDs for installation
|
for (const customFile of customContentConfig.selectedFiles) {
|
||||||
const customContentModuleIds = [];
|
// Get the module info to extract the ID
|
||||||
const customHandler = new CustomHandler();
|
const customInfo = await customHandler.getCustomInfo(customFile);
|
||||||
for (const customFile of customContentConfig.selectedFiles) {
|
if (customInfo) {
|
||||||
// Get the module info to extract the ID
|
customContentModuleIds.push(customInfo.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) {
|
} else if (customContentConfig.hasCustomContent) {
|
||||||
// User provided custom content but didn't select any
|
// User provided custom content but didn't select any
|
||||||
customContentConfig.selected = false;
|
customContentConfig.selected = false;
|
||||||
customContentConfig.selectedFiles = [];
|
customContentConfig.selectedFiles = [];
|
||||||
customContentConfig.selectedCachedModules = [];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -610,67 +520,20 @@ class UI {
|
|||||||
const hasCustomContentItems = false;
|
const hasCustomContentItems = false;
|
||||||
|
|
||||||
// Add custom content items
|
// Add custom content items
|
||||||
if (customContentConfig && customContentConfig.hasCustomContent) {
|
if (customContentConfig && customContentConfig.hasCustomContent && customContentConfig.customPath) {
|
||||||
if (customContentConfig.cachedModules) {
|
// Existing installation - show from directory
|
||||||
// New installation - show cached modules
|
const customHandler = new CustomHandler();
|
||||||
for (const cachedModule of customContentConfig.cachedModules) {
|
const customFiles = await customHandler.findCustomContent(customContentConfig.customPath);
|
||||||
// Get the module info from cache
|
|
||||||
const yaml = require('js-yaml');
|
|
||||||
const fs = require('fs-extra');
|
|
||||||
|
|
||||||
// Try multiple possible config file locations
|
for (const customFile of customFiles) {
|
||||||
const possibleConfigPaths = [
|
const customInfo = await customHandler.getCustomInfo(customFile);
|
||||||
path.join(cachedModule.cachePath, 'module.yaml'),
|
if (customInfo) {
|
||||||
path.join(cachedModule.cachePath, 'custom.yaml'),
|
customContentItems.push({
|
||||||
path.join(cachedModule.cachePath, '_module-installer', 'module.yaml'),
|
name: `${chalk.cyan('✓')} ${customInfo.name} ${chalk.gray(`(${customInfo.relativePath})`)}`,
|
||||||
path.join(cachedModule.cachePath, '_module-installer', 'custom.yaml'),
|
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
|
||||||
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
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -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
|
* Confirm directory selection
|
||||||
* @param {string} directory - The directory path
|
* @param {string} directory - The directory path
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user