642 lines
25 KiB
JavaScript
642 lines
25 KiB
JavaScript
const chalk = require('chalk');
|
||
const path = require('node:path');
|
||
const fs = require('node:fs');
|
||
const readline = require('node:readline');
|
||
const yaml = require('js-yaml');
|
||
const inquirer = require('inquirer');
|
||
const {
|
||
findBmadConfig,
|
||
resolvePath,
|
||
discoverAgents,
|
||
loadAgentConfig,
|
||
promptInstallQuestions,
|
||
detectBmadProject,
|
||
addToManifest,
|
||
extractManifestData,
|
||
checkManifestForPath,
|
||
updateManifestEntry,
|
||
saveAgentSource,
|
||
createIdeSlashCommands,
|
||
updateManifestYaml,
|
||
} = require('../lib/agent/installer');
|
||
|
||
/**
|
||
* Initialize BMAD core infrastructure in a directory
|
||
* @param {string} projectDir - Project directory where .bmad should be created
|
||
* @param {string} bmadFolderName - Name of the BMAD folder (default: .bmad)
|
||
* @returns {Promise<Object>} BMAD project info
|
||
*/
|
||
async function initializeBmadCore(projectDir, bmadFolderName = '.bmad') {
|
||
const bmadDir = path.join(projectDir, bmadFolderName);
|
||
const cfgDir = path.join(bmadDir, '_cfg');
|
||
|
||
console.log(chalk.cyan('\n🏗️ Initializing BMAD Core Infrastructure\n'));
|
||
|
||
// Use the ConfigCollector to ask proper core configuration questions
|
||
const { ConfigCollector } = require('../installers/lib/core/config-collector');
|
||
const configCollector = new ConfigCollector();
|
||
|
||
// Collect core configuration answers
|
||
await configCollector.loadExistingConfig(projectDir);
|
||
await configCollector.collectModuleConfig('core', projectDir, true, true);
|
||
|
||
// Extract core answers from allAnswers (they are prefixed with 'core_')
|
||
const coreAnswers = {};
|
||
if (configCollector.allAnswers) {
|
||
for (const [key, value] of Object.entries(configCollector.allAnswers)) {
|
||
if (key.startsWith('core_')) {
|
||
const configKey = key.slice(5); // Remove 'core_' prefix
|
||
coreAnswers[configKey] = value;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Ask for IDE selection
|
||
console.log(chalk.cyan('\n💻 IDE Configuration\n'));
|
||
console.log(chalk.dim('Select IDEs to integrate with the installed agents:'));
|
||
|
||
const { UI } = require('../lib/ui');
|
||
const ui = new UI();
|
||
const ideConfig = await ui.promptToolSelection(projectDir, ['core']);
|
||
const selectedIdes = ideConfig.ides || [];
|
||
|
||
// Create directory structure
|
||
console.log(chalk.dim('\nCreating directory structure...'));
|
||
await fs.promises.mkdir(bmadDir, { recursive: true });
|
||
await fs.promises.mkdir(cfgDir, { recursive: true });
|
||
await fs.promises.mkdir(path.join(bmadDir, 'core'), { recursive: true });
|
||
await fs.promises.mkdir(path.join(bmadDir, 'custom', 'agents'), { recursive: true });
|
||
await fs.promises.mkdir(path.join(cfgDir, 'agents'), { recursive: true });
|
||
await fs.promises.mkdir(path.join(cfgDir, 'custom', 'agents'), { recursive: true });
|
||
|
||
// Create core config.yaml file
|
||
const coreConfigFile = {
|
||
'# CORE Module Configuration': 'Generated by BMAD Agent Installer',
|
||
Version: require(path.join(__dirname, '../../../package.json')).version,
|
||
Date: new Date().toISOString(),
|
||
bmad_folder: bmadFolderName,
|
||
...coreAnswers,
|
||
};
|
||
|
||
const coreConfigPath = path.join(bmadDir, 'core', 'config.yaml');
|
||
await fs.promises.writeFile(coreConfigPath, yaml.dump(coreConfigFile), 'utf8');
|
||
|
||
// Create manifest.yaml with complete structure
|
||
const manifest = {
|
||
version: require(path.join(__dirname, '../../../package.json')).version,
|
||
date: new Date().toISOString(),
|
||
user_name: coreAnswers.user_name,
|
||
communication_language: coreAnswers.communication_language,
|
||
document_output_language: coreAnswers.document_output_language,
|
||
output_folder: coreAnswers.output_folder,
|
||
install_user_docs: coreAnswers.install_user_docs,
|
||
bmad_folder: bmadFolderName,
|
||
modules: ['core'],
|
||
ides: selectedIdes,
|
||
custom_agents: [],
|
||
};
|
||
|
||
const manifestPath = path.join(cfgDir, 'manifest.yaml');
|
||
await fs.promises.writeFile(manifestPath, yaml.dump(manifest), 'utf8');
|
||
|
||
// Create empty manifests
|
||
const agentManifestPath = path.join(cfgDir, 'agent-manifest.csv');
|
||
await fs.promises.writeFile(agentManifestPath, 'type,subtype,name,path,display_name,description,author,version,tags\n', 'utf8');
|
||
|
||
// Setup IDE configurations
|
||
if (selectedIdes.length > 0) {
|
||
console.log(chalk.dim('\nSetting up IDE configurations...'));
|
||
const { IdeManager } = require('../installers/lib/ide/manager');
|
||
const ideManager = new IdeManager();
|
||
|
||
for (const ide of selectedIdes) {
|
||
await ideManager.setup(ide, projectDir, bmadDir, {
|
||
selectedModules: ['core'],
|
||
skipModuleInstall: false,
|
||
verbose: false,
|
||
preCollectedConfig: coreAnswers,
|
||
});
|
||
}
|
||
}
|
||
|
||
console.log(chalk.green('\n✓ BMAD core infrastructure initialized'));
|
||
console.log(chalk.dim(` BMAD folder: ${bmadDir}`));
|
||
console.log(chalk.dim(` Core config: ${coreConfigPath}`));
|
||
console.log(chalk.dim(` Manifest: ${manifestPath}`));
|
||
if (selectedIdes.length > 0) {
|
||
console.log(chalk.dim(` IDEs configured: ${selectedIdes.join(', ')}`));
|
||
}
|
||
|
||
return {
|
||
projectRoot: projectDir,
|
||
bmadFolder: bmadDir,
|
||
cfgFolder: cfgDir,
|
||
manifestFile: agentManifestPath,
|
||
ides: selectedIdes,
|
||
};
|
||
}
|
||
|
||
module.exports = {
|
||
command: 'agent-install',
|
||
description: 'Install and compile BMAD agents with personalization',
|
||
options: [
|
||
['-s, --source <path>', 'Path to specific agent YAML file or folder'],
|
||
['-d, --defaults', 'Use default values without prompting'],
|
||
['-t, --destination <path>', 'Target installation directory (default: current project BMAD installation)'],
|
||
],
|
||
action: async (options) => {
|
||
try {
|
||
console.log(chalk.cyan('\n🔧 BMAD Agent Installer\n'));
|
||
|
||
// Find BMAD config
|
||
const config = findBmadConfig();
|
||
if (!config) {
|
||
console.log(chalk.yellow('No BMAD installation found in current directory.'));
|
||
console.log(chalk.dim('Looking for .bmad/bmb/config.yaml...'));
|
||
console.log(chalk.red('\nPlease run this command from a project with BMAD installed.'));
|
||
process.exit(1);
|
||
}
|
||
|
||
console.log(chalk.dim(`Found BMAD at: ${config.bmadFolder}`));
|
||
|
||
let selectedAgent = null;
|
||
|
||
// If source provided, use it directly
|
||
if (options.source) {
|
||
const providedPath = path.resolve(options.source);
|
||
|
||
if (!fs.existsSync(providedPath)) {
|
||
console.log(chalk.red(`Path not found: ${providedPath}`));
|
||
process.exit(1);
|
||
}
|
||
|
||
const stat = fs.statSync(providedPath);
|
||
if (stat.isFile() && providedPath.endsWith('.agent.yaml')) {
|
||
selectedAgent = {
|
||
type: 'simple',
|
||
name: path.basename(providedPath, '.agent.yaml'),
|
||
path: providedPath,
|
||
yamlFile: providedPath,
|
||
};
|
||
} else if (stat.isDirectory()) {
|
||
const yamlFiles = fs.readdirSync(providedPath).filter((f) => f.endsWith('.agent.yaml'));
|
||
if (yamlFiles.length === 1) {
|
||
selectedAgent = {
|
||
type: 'expert',
|
||
name: path.basename(providedPath),
|
||
path: providedPath,
|
||
yamlFile: path.join(providedPath, yamlFiles[0]),
|
||
hasSidecar: true,
|
||
};
|
||
} else {
|
||
console.log(chalk.red('Directory must contain exactly one .agent.yaml file'));
|
||
process.exit(1);
|
||
}
|
||
} else {
|
||
console.log(chalk.red('Path must be an .agent.yaml file or a folder containing one'));
|
||
process.exit(1);
|
||
}
|
||
} else {
|
||
// Discover agents from custom location
|
||
const customAgentLocation = config.custom_stand_alone_location
|
||
? resolvePath(config.custom_stand_alone_location, config)
|
||
: path.join(config.bmadFolder, 'custom', 'src', 'agents');
|
||
|
||
console.log(chalk.dim(`Searching for agents in: ${customAgentLocation}\n`));
|
||
|
||
const agents = discoverAgents(customAgentLocation);
|
||
|
||
if (agents.length === 0) {
|
||
console.log(chalk.yellow('No agents found in custom agent location.'));
|
||
console.log(chalk.dim(`Expected location: ${customAgentLocation}`));
|
||
console.log(chalk.dim('\nCreate agents using the BMad Builder workflow or place .agent.yaml files there.'));
|
||
process.exit(0);
|
||
}
|
||
|
||
// List available agents
|
||
console.log(chalk.cyan('Available Agents:\n'));
|
||
for (const [idx, agent] of agents.entries()) {
|
||
const typeIcon = agent.type === 'expert' ? '📚' : '📄';
|
||
console.log(` ${idx + 1}. ${typeIcon} ${chalk.bold(agent.name)} ${chalk.dim(`(${agent.type})`)}`);
|
||
}
|
||
|
||
// Prompt for selection
|
||
const rl = readline.createInterface({
|
||
input: process.stdin,
|
||
output: process.stdout,
|
||
});
|
||
|
||
const selection = await new Promise((resolve) => {
|
||
rl.question('\nSelect agent to install (number): ', resolve);
|
||
});
|
||
rl.close();
|
||
|
||
const selectedIdx = parseInt(selection, 10) - 1;
|
||
if (isNaN(selectedIdx) || selectedIdx < 0 || selectedIdx >= agents.length) {
|
||
console.log(chalk.red('Invalid selection'));
|
||
process.exit(1);
|
||
}
|
||
|
||
selectedAgent = agents[selectedIdx];
|
||
}
|
||
|
||
console.log(chalk.cyan(`\nSelected: ${chalk.bold(selectedAgent.name)}`));
|
||
|
||
// Load agent configuration
|
||
const agentConfig = loadAgentConfig(selectedAgent.yamlFile);
|
||
|
||
// Check if agent has sidecar
|
||
if (agentConfig.metadata.hasSidecar) {
|
||
selectedAgent.hasSidecar = true;
|
||
}
|
||
|
||
if (agentConfig.metadata.name) {
|
||
console.log(chalk.dim(`Agent Name: ${agentConfig.metadata.name}`));
|
||
}
|
||
if (agentConfig.metadata.title) {
|
||
console.log(chalk.dim(`Title: ${agentConfig.metadata.title}`));
|
||
}
|
||
if (agentConfig.metadata.hasSidecar) {
|
||
console.log(chalk.dim(`Sidecar: Yes`));
|
||
}
|
||
|
||
// Get the agent type (source name)
|
||
const agentType = selectedAgent.name; // e.g., "commit-poet"
|
||
|
||
// Confirm/customize agent persona name
|
||
const rl1 = readline.createInterface({
|
||
input: process.stdin,
|
||
output: process.stdout,
|
||
});
|
||
|
||
const defaultPersonaName = agentConfig.metadata.name || agentType;
|
||
console.log(chalk.cyan('\n📛 Agent Persona Name\n'));
|
||
console.log(chalk.dim(` Agent type: ${agentType}`));
|
||
console.log(chalk.dim(` Default persona: ${defaultPersonaName}`));
|
||
console.log(chalk.dim(' Leave blank to use default, or provide a custom name.'));
|
||
console.log(chalk.dim(' Examples:'));
|
||
console.log(chalk.dim(` - (blank) → "${defaultPersonaName}" as ${agentType}.md`));
|
||
console.log(chalk.dim(` - "Fred" → "Fred" as fred-${agentType}.md`));
|
||
console.log(chalk.dim(` - "Captain Code" → "Captain Code" as captain-code-${agentType}.md`));
|
||
|
||
const customPersonaName = await new Promise((resolve) => {
|
||
rl1.question(`\n Custom name (or Enter for default): `, resolve);
|
||
});
|
||
rl1.close();
|
||
|
||
// Determine final agent file name based on persona name
|
||
let finalAgentName;
|
||
let personaName;
|
||
if (customPersonaName.trim()) {
|
||
personaName = customPersonaName.trim();
|
||
const namePrefix = personaName.toLowerCase().replaceAll(/\s+/g, '-');
|
||
finalAgentName = `${namePrefix}-${agentType}`;
|
||
} else {
|
||
personaName = defaultPersonaName;
|
||
finalAgentName = agentType;
|
||
}
|
||
|
||
console.log(chalk.dim(` Persona: ${personaName}`));
|
||
console.log(chalk.dim(` File: ${finalAgentName}.md`));
|
||
|
||
// Get answers (prompt or use defaults)
|
||
let presetAnswers = {};
|
||
|
||
// If custom persona name provided, inject it as custom_name for template processing
|
||
if (customPersonaName.trim()) {
|
||
presetAnswers.custom_name = personaName;
|
||
}
|
||
|
||
let answers;
|
||
if (agentConfig.installConfig && !options.defaults) {
|
||
answers = await promptInstallQuestions(agentConfig.installConfig, agentConfig.defaults, presetAnswers);
|
||
} else if (agentConfig.installConfig && options.defaults) {
|
||
console.log(chalk.dim('\nUsing default configuration values.'));
|
||
answers = { ...agentConfig.defaults, ...presetAnswers };
|
||
} else {
|
||
answers = { ...agentConfig.defaults, ...presetAnswers };
|
||
}
|
||
|
||
// Determine target directory
|
||
let targetDir = options.destination ? path.resolve(options.destination) : null;
|
||
|
||
// If no target specified, prompt for it
|
||
if (targetDir) {
|
||
// Check if target has BMAD infrastructure
|
||
const otherProject = detectBmadProject(targetDir);
|
||
|
||
if (!otherProject) {
|
||
// No BMAD infrastructure found - offer to initialize
|
||
console.log(chalk.yellow(`\n⚠️ No BMAD infrastructure found in: ${targetDir}`));
|
||
|
||
const initResponse = await inquirer.prompt([
|
||
{
|
||
type: 'confirm',
|
||
name: 'initialize',
|
||
message: 'Initialize BMAD core infrastructure here? (Choose No for direct installation)',
|
||
default: true,
|
||
},
|
||
]);
|
||
|
||
if (initResponse.initialize) {
|
||
// Initialize BMAD core
|
||
targetDir = path.resolve(targetDir);
|
||
await initializeBmadCore(targetDir, '.bmad');
|
||
// Set targetDir to the custom agents folder
|
||
targetDir = path.join(targetDir, '.bmad', 'custom', 'agents');
|
||
console.log(chalk.dim(` Agent will be installed to: ${targetDir}`));
|
||
} else {
|
||
// User declined - keep original targetDir
|
||
console.log(chalk.yellow(` Installing agent directly to: ${targetDir}`));
|
||
}
|
||
} else if (otherProject && !targetDir.includes('agents')) {
|
||
console.log(chalk.yellow(`\n⚠️ Path is inside BMAD project: ${otherProject.projectRoot}`));
|
||
|
||
const projectChoice = await inquirer.prompt([
|
||
{
|
||
type: 'list',
|
||
name: 'choice',
|
||
message: 'Choose installation method:',
|
||
choices: [
|
||
{ name: `Install to BMAD's custom agents folder (${otherProject.bmadFolder}/custom/agents)`, value: 'bmad' },
|
||
{ name: `Install directly to specified path (${targetDir})`, value: 'direct' },
|
||
],
|
||
default: 'bmad',
|
||
},
|
||
]);
|
||
|
||
if (projectChoice.choice === 'bmad') {
|
||
targetDir = path.join(otherProject.bmadFolder, 'custom', 'agents');
|
||
console.log(chalk.dim(` Installing to BMAD custom agents folder: ${targetDir}`));
|
||
} else {
|
||
console.log(chalk.yellow(` Installing directly to: ${targetDir}`));
|
||
}
|
||
}
|
||
} else {
|
||
const rl = readline.createInterface({
|
||
input: process.stdin,
|
||
output: process.stdout,
|
||
});
|
||
|
||
console.log(chalk.cyan('\n📂 Installation Target\n'));
|
||
|
||
// Option 1: Current project's custom agents folder
|
||
const currentCustom = path.join(config.bmadFolder, 'custom', 'agents');
|
||
console.log(` 1. Current project: ${chalk.dim(currentCustom)}`);
|
||
console.log(` 2. Enter path directly (e.g., /Users/brianmadison/dev/test)`);
|
||
|
||
const choice = await new Promise((resolve) => {
|
||
rl.question('\n Select option (1 or 2): ', resolve);
|
||
});
|
||
|
||
if (choice.trim() === '1' || choice.trim() === '') {
|
||
targetDir = currentCustom;
|
||
} else if (choice.trim() === '2') {
|
||
const userPath = await new Promise((resolve) => {
|
||
rl.question(' Enter path: ', resolve);
|
||
});
|
||
|
||
// Detect if it's a BMAD project and use its custom folder
|
||
const otherProject = detectBmadProject(path.resolve(userPath));
|
||
|
||
if (otherProject) {
|
||
console.log(chalk.yellow(`\n⚠️ Path is inside BMAD project: ${otherProject.projectRoot}`));
|
||
|
||
const projectChoice = await inquirer.prompt([
|
||
{
|
||
type: 'list',
|
||
name: 'choice',
|
||
message: 'Choose installation method:',
|
||
choices: [
|
||
{ name: `Install to BMAD's custom agents folder (${otherProject.bmadFolder}/custom/agents)`, value: 'bmad' },
|
||
{ name: `Install directly to specified path (${userPath})`, value: 'direct' },
|
||
],
|
||
default: 'bmad',
|
||
},
|
||
]);
|
||
|
||
if (projectChoice.choice === 'bmad') {
|
||
targetDir = path.join(otherProject.bmadFolder, 'custom', 'agents');
|
||
console.log(chalk.dim(` Installing to BMAD custom agents folder: ${targetDir}`));
|
||
} else {
|
||
targetDir = path.resolve(userPath);
|
||
console.log(chalk.yellow(` Installing directly to: ${targetDir}`));
|
||
}
|
||
} else {
|
||
// No BMAD found - offer to initialize
|
||
console.log(chalk.yellow(`\n⚠️ No BMAD infrastructure found in: ${userPath}`));
|
||
|
||
const initResponse = await inquirer.prompt([
|
||
{
|
||
type: 'confirm',
|
||
name: 'initialize',
|
||
message: 'Initialize BMAD core infrastructure here? (Choose No for direct installation)',
|
||
default: true,
|
||
},
|
||
]);
|
||
|
||
if (initResponse.initialize) {
|
||
await initializeBmadCore(path.resolve(userPath), '.bmad');
|
||
targetDir = path.join(path.resolve(userPath), '.bmad', 'custom', 'agents');
|
||
console.log(chalk.dim(` Agent will be installed to: ${targetDir}`));
|
||
} else {
|
||
// User declined - create the directory and install directly
|
||
targetDir = path.resolve(userPath);
|
||
console.log(chalk.yellow(` Installing agent directly to: ${targetDir}`));
|
||
}
|
||
}
|
||
} else {
|
||
console.log(chalk.red(' Invalid selection. Please choose 1 or 2.'));
|
||
rl.close();
|
||
process.exit(1);
|
||
}
|
||
|
||
rl.close();
|
||
}
|
||
|
||
if (!fs.existsSync(targetDir)) {
|
||
fs.mkdirSync(targetDir, { recursive: true });
|
||
}
|
||
|
||
console.log(chalk.dim(`\nInstalling to: ${targetDir}`));
|
||
|
||
// Detect if target is within a BMAD project
|
||
const targetProject = detectBmadProject(targetDir);
|
||
if (targetProject) {
|
||
console.log(chalk.cyan(` Detected BMAD project at: ${targetProject.projectRoot}`));
|
||
}
|
||
|
||
// Check for duplicate in manifest by path (not by type)
|
||
let shouldUpdateExisting = false;
|
||
let existingEntry = null;
|
||
|
||
if (targetProject) {
|
||
// Check if this exact installed name already exists
|
||
const expectedPath = `.bmad/custom/agents/${finalAgentName}/${finalAgentName}.md`;
|
||
existingEntry = checkManifestForPath(targetProject.manifestFile, expectedPath);
|
||
|
||
if (existingEntry) {
|
||
const rl2 = readline.createInterface({
|
||
input: process.stdin,
|
||
output: process.stdout,
|
||
});
|
||
|
||
console.log(chalk.yellow(`\n⚠️ Agent "${finalAgentName}" already installed`));
|
||
console.log(chalk.dim(` Type: ${agentType}`));
|
||
console.log(chalk.dim(` Path: ${existingEntry.path}`));
|
||
|
||
const overwrite = await new Promise((resolve) => {
|
||
rl2.question(' Overwrite existing installation? [Y/n]: ', resolve);
|
||
});
|
||
rl2.close();
|
||
|
||
if (overwrite.toLowerCase() === 'n') {
|
||
console.log(chalk.yellow('Installation cancelled.'));
|
||
process.exit(0);
|
||
}
|
||
|
||
shouldUpdateExisting = true;
|
||
}
|
||
}
|
||
|
||
// Install the agent with custom name
|
||
// Override the folder name with finalAgentName
|
||
const agentTargetDir = path.join(targetDir, finalAgentName);
|
||
|
||
if (!fs.existsSync(agentTargetDir)) {
|
||
fs.mkdirSync(agentTargetDir, { recursive: true });
|
||
}
|
||
|
||
// Compile and install
|
||
const { compileAgent } = require('../lib/agent/compiler');
|
||
|
||
// Calculate target path for agent ID
|
||
const projectRoot = targetProject ? targetProject.projectRoot : config.projectRoot;
|
||
const compiledFileName = `${finalAgentName}.md`;
|
||
const compiledPath = path.join(agentTargetDir, compiledFileName);
|
||
const relativePath = path.relative(projectRoot, compiledPath);
|
||
|
||
// Read core config to get agent_sidecar_folder
|
||
const coreConfigPath = path.join(config.bmadFolder, 'bmb', 'config.yaml');
|
||
let coreConfig = {};
|
||
if (fs.existsSync(coreConfigPath)) {
|
||
const yamlLib = require('yaml');
|
||
const content = fs.readFileSync(coreConfigPath, 'utf8');
|
||
coreConfig = yamlLib.parse(content);
|
||
}
|
||
|
||
// Compile with proper name and path
|
||
const { xml, metadata, processedYaml } = compileAgent(
|
||
fs.readFileSync(selectedAgent.yamlFile, 'utf8'),
|
||
answers,
|
||
finalAgentName,
|
||
relativePath,
|
||
{ config: coreConfig },
|
||
);
|
||
|
||
// Write compiled XML (.md) with custom name
|
||
fs.writeFileSync(compiledPath, xml, 'utf8');
|
||
|
||
const result = {
|
||
success: true,
|
||
agentName: finalAgentName,
|
||
targetDir: agentTargetDir,
|
||
compiledFile: compiledPath,
|
||
sidecarCopied: false,
|
||
};
|
||
|
||
// Handle sidecar files for agents with hasSidecar flag
|
||
if (selectedAgent.hasSidecar === true && selectedAgent.type === 'expert') {
|
||
const { copyAgentSidecarFiles } = require('../lib/agent/installer');
|
||
|
||
// Get agent sidecar folder from config or use default
|
||
const agentSidecarFolder = coreConfig?.agent_sidecar_folder || '{project-root}/.myagent-data';
|
||
|
||
// Resolve path variables
|
||
const resolvedSidecarFolder = agentSidecarFolder
|
||
.replaceAll('{project-root}', projectRoot)
|
||
.replaceAll('{bmad_folder}', config.bmadFolder);
|
||
|
||
// Create sidecar directory for this agent
|
||
const agentSidecarDir = path.join(resolvedSidecarFolder, finalAgentName);
|
||
if (!fs.existsSync(agentSidecarDir)) {
|
||
fs.mkdirSync(agentSidecarDir, { recursive: true });
|
||
}
|
||
|
||
// Find and copy sidecar folder
|
||
const sidecarFiles = copyAgentSidecarFiles(selectedAgent.path, agentSidecarDir, selectedAgent.yamlFile);
|
||
result.sidecarCopied = true;
|
||
result.sidecarFiles = sidecarFiles;
|
||
result.sidecarDir = agentSidecarDir;
|
||
|
||
console.log(chalk.dim(` Sidecar copied to: ${agentSidecarDir}`));
|
||
}
|
||
|
||
console.log(chalk.green('\n✨ Agent installed successfully!'));
|
||
console.log(chalk.cyan(` Name: ${result.agentName}`));
|
||
console.log(chalk.cyan(` Location: ${result.targetDir}`));
|
||
console.log(chalk.cyan(` Compiled: ${path.basename(result.compiledFile)}`));
|
||
|
||
if (result.sidecarCopied) {
|
||
console.log(chalk.cyan(` Sidecar files: ${result.sidecarFiles.length} files copied`));
|
||
}
|
||
|
||
// Save source YAML to _cfg/custom/agents/ and register in manifest
|
||
if (targetProject) {
|
||
// Save source for reinstallation with embedded answers
|
||
console.log(chalk.dim(`\nSaving source to: ${targetProject.cfgFolder}/custom/agents/`));
|
||
saveAgentSource(selectedAgent, targetProject.cfgFolder, finalAgentName, answers);
|
||
console.log(chalk.green(` ✓ Source saved for reinstallation`));
|
||
|
||
// Register/update in manifest
|
||
console.log(chalk.dim(`Registering in manifest: ${targetProject.manifestFile}`));
|
||
|
||
const manifestData = extractManifestData(xml, { ...metadata, name: finalAgentName }, relativePath, 'custom');
|
||
// Use finalAgentName as the manifest name field (unique identifier)
|
||
manifestData.name = finalAgentName;
|
||
// Use compiled metadata.name (persona name after template processing), not source agentConfig
|
||
manifestData.displayName = metadata.name || agentType;
|
||
// Store the actual installed path/name
|
||
manifestData.path = relativePath;
|
||
|
||
if (shouldUpdateExisting && existingEntry) {
|
||
updateManifestEntry(targetProject.manifestFile, manifestData, existingEntry._lineNumber);
|
||
console.log(chalk.green(` ✓ Updated existing entry in agent-manifest.csv`));
|
||
} else {
|
||
addToManifest(targetProject.manifestFile, manifestData);
|
||
console.log(chalk.green(` ✓ Added to agent-manifest.csv`));
|
||
}
|
||
|
||
// Create IDE slash commands
|
||
const ideResults = await createIdeSlashCommands(targetProject.projectRoot, finalAgentName, relativePath, metadata);
|
||
if (Object.keys(ideResults).length > 0) {
|
||
console.log(chalk.green(` ✓ Created IDE commands:`));
|
||
for (const [ideName, result] of Object.entries(ideResults)) {
|
||
console.log(chalk.dim(` ${ideName}: ${result.command}`));
|
||
}
|
||
}
|
||
|
||
// Update manifest.yaml with custom_agents tracking
|
||
const manifestYamlPath = path.join(targetProject.cfgFolder, 'manifest.yaml');
|
||
if (updateManifestYaml(manifestYamlPath, finalAgentName, agentType)) {
|
||
console.log(chalk.green(` ✓ Updated manifest.yaml custom_agents`));
|
||
}
|
||
}
|
||
|
||
console.log(chalk.dim(`\nAgent ID: ${relativePath}`));
|
||
|
||
if (targetProject) {
|
||
console.log(chalk.yellow('\nAgent is now registered and available in the target project!'));
|
||
} else {
|
||
console.log(chalk.yellow('\nTo use this agent, reference it in your manifest or load it directly.'));
|
||
}
|
||
|
||
process.exit(0);
|
||
} catch (error) {
|
||
console.error(chalk.red('Agent installation failed:'), error.message);
|
||
console.error(chalk.dim(error.stack));
|
||
process.exit(1);
|
||
}
|
||
},
|
||
};
|