custom module installer improved, and removed agent-install
This commit is contained in:
parent
b252778043
commit
119187a1e7
|
|
@ -1,125 +1,68 @@
|
||||||
# Custom Agent Installation
|
# Custom Agent Installation
|
||||||
|
|
||||||
Install and personalize BMAD agents in your project.
|
BMAD agents and workflows are now installed through the main CLI installer using a `custom.yaml` configuration file or by having an installer file.
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
|
Create a `custom.yaml` file in the root of your agent/workflow folder:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
code: my-custom-agent
|
||||||
|
name: 'My Custom Agent'
|
||||||
|
default_selected: true
|
||||||
|
```
|
||||||
|
|
||||||
|
Then run the BMAD installer from your project directory:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# From your project directory with BMAD installed
|
npx bmad-method install
|
||||||
npx bmad-method agent-install
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Or if you have bmad-cli installed globally:
|
Or if you have bmad-cli installed globally:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bmad agent-install
|
bmad install
|
||||||
|
```
|
||||||
|
|
||||||
|
## Installation Methods
|
||||||
|
|
||||||
|
### Method 1: Stand-alone Folder with custom.yaml
|
||||||
|
|
||||||
|
Place your agent or workflow in a folder with a `custom.yaml` file at the root:
|
||||||
|
|
||||||
|
```
|
||||||
|
my-agent/
|
||||||
|
├── custom.yaml # Required configuration file
|
||||||
|
├── my-agent.agent.yaml
|
||||||
|
└── sidecar/ # Optional
|
||||||
|
└── instructions.md
|
||||||
|
```
|
||||||
|
|
||||||
|
### Method 2: Installer File
|
||||||
|
|
||||||
|
For more complex installations, include an `installer.js` or `installer.yaml` file in your agent/workflow folder:
|
||||||
|
|
||||||
|
```
|
||||||
|
my-workflow/
|
||||||
|
├── workflow.md
|
||||||
|
└── installer.yaml # Custom installation logic
|
||||||
```
|
```
|
||||||
|
|
||||||
## What It Does
|
## What It Does
|
||||||
|
|
||||||
1. **Discovers** available agent templates from your custom agents folder
|
1. **Discovers** available agents and workflows from folders with `custom.yaml`
|
||||||
2. **Prompts** you to personalize the agent (name, behavior, preferences)
|
2. **Installs** to your project's `.bmad/custom/` directory
|
||||||
3. **Compiles** the agent with your choices baked in
|
3. **Creates** IDE commands for all your configured IDEs (Claude Code, Codex, Cursor, etc.)
|
||||||
4. **Installs** to your project's `.bmad/custom/agents/` directory
|
4. **Registers** the agent/workflow in the BMAD system
|
||||||
5. **Creates** IDE commands for all your configured IDEs (Claude Code, Codex, Cursor, etc.)
|
|
||||||
6. **Saves** your configuration for automatic reinstallation during BMAD updates
|
|
||||||
|
|
||||||
## Options
|
## Example custom.yaml
|
||||||
|
|
||||||
```bash
|
```yaml
|
||||||
bmad agent-install [options]
|
code: my-custom-agent
|
||||||
|
name: 'My Custom Agent'
|
||||||
Options:
|
default_selected: true
|
||||||
-p, --path <path> #Direct path to specific agent YAML file or folder
|
|
||||||
-d, --defaults #Use default values without prompting
|
|
||||||
-t, --target <path> #Target installation directory
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Installing from Custom Locations
|
|
||||||
|
|
||||||
Use the `-s` / `--source` option to install agents from any location:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Install agent from a custom folder (expert agent with sidecar)
|
|
||||||
bmad agent-install -s path/to/my-agent
|
|
||||||
|
|
||||||
# Install a specific .agent.yaml file (simple agent)
|
|
||||||
bmad agent-install -s path/to/my-agent.agent.yaml
|
|
||||||
|
|
||||||
# Install with defaults (non-interactive)
|
|
||||||
bmad agent-install -s path/to/my-agent -d
|
|
||||||
|
|
||||||
# Install to a specific destination project
|
|
||||||
bmad agent-install -s path/to/my-agent --destination /path/to/destination/project
|
|
||||||
```
|
|
||||||
|
|
||||||
This is useful when:
|
|
||||||
|
|
||||||
- Your agent is in a non-standard location (not in `.bmad/custom/agents/`)
|
|
||||||
- You're developing an agent outside the project structure
|
|
||||||
- You want to install from an absolute path
|
|
||||||
|
|
||||||
## Example Session
|
|
||||||
|
|
||||||
```
|
|
||||||
🔧 BMAD Agent Installer
|
|
||||||
|
|
||||||
Found BMAD at: /project/.bmad
|
|
||||||
Searching for agents in: /project/.bmad/custom/agents
|
|
||||||
|
|
||||||
Available Agents:
|
|
||||||
|
|
||||||
1. 📄 commit-poet (simple)
|
|
||||||
2. 📚 journal-keeper (expert)
|
|
||||||
|
|
||||||
Select agent to install (number): 1
|
|
||||||
|
|
||||||
Selected: commit-poet
|
|
||||||
|
|
||||||
📛 Agent Persona Name
|
|
||||||
|
|
||||||
Agent type: commit-poet
|
|
||||||
Default persona: Inkwell Von Comitizen
|
|
||||||
|
|
||||||
Custom name (or Enter for default): Fred
|
|
||||||
|
|
||||||
Persona: Fred
|
|
||||||
File: fred-commit-poet.md
|
|
||||||
|
|
||||||
📝 Agent Configuration
|
|
||||||
|
|
||||||
What's your preferred default commit message style?
|
|
||||||
* 1. Conventional (feat/fix/chore)
|
|
||||||
2. Narrative storytelling
|
|
||||||
3. Poetic haiku
|
|
||||||
4. Detailed explanation
|
|
||||||
Choice (default: 1): 1
|
|
||||||
|
|
||||||
How enthusiastic should the agent be?
|
|
||||||
1. Moderate - Professional with personality
|
|
||||||
* 2. High - Genuinely excited
|
|
||||||
3. EXTREME - Full theatrical drama
|
|
||||||
Choice (default: 2): 3
|
|
||||||
|
|
||||||
Include emojis in commit messages? [Y/n]: y
|
|
||||||
|
|
||||||
✨ Agent installed successfully!
|
|
||||||
Name: fred-commit-poet
|
|
||||||
Location: /project/.bmad/custom/agents/fred-commit-poet
|
|
||||||
Compiled: fred-commit-poet.md
|
|
||||||
|
|
||||||
✓ Source saved for reinstallation
|
|
||||||
✓ Added to agent-manifest.csv
|
|
||||||
✓ Created IDE commands:
|
|
||||||
claude-code: /bmad:custom:agents:fred-commit-poet
|
|
||||||
codex: /bmad-custom-agents-fred-commit-poet
|
|
||||||
github-copilot: bmad-agent-custom-fred-commit-poet
|
|
||||||
```
|
|
||||||
|
|
||||||
## Reinstallation
|
|
||||||
|
|
||||||
Custom agents are automatically reinstalled when you run `bmad init --quick`. Your personalization choices are preserved in `.bmad/_cfg/custom/agents/`.
|
|
||||||
|
|
||||||
## Installing Reference Agents
|
## Installing Reference Agents
|
||||||
|
|
||||||
The BMAD source includes example agents you can install. **You must copy them to your project first.**
|
The BMAD source includes example agents you can install. **You must copy them to your project first.**
|
||||||
|
|
@ -130,8 +73,9 @@ The BMAD source includes example agents you can install. **You must copy them to
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# From your project root
|
# From your project root
|
||||||
|
mkdir -p .bmad/custom/agents/my-agent
|
||||||
cp node_modules/bmad-method/src/modules/bmb/reference/agents/stand-alone/commit-poet.agent.yaml \
|
cp node_modules/bmad-method/src/modules/bmb/reference/agents/stand-alone/commit-poet.agent.yaml \
|
||||||
.bmad/custom/agents/
|
.bmad/custom/agents/my-agent/
|
||||||
```
|
```
|
||||||
|
|
||||||
**For expert agents** (folder with sidecar files):
|
**For expert agents** (folder with sidecar files):
|
||||||
|
|
@ -142,19 +86,29 @@ cp -r node_modules/bmad-method/src/modules/bmb/reference/agents/agent-with-memor
|
||||||
.bmad/custom/agents/
|
.bmad/custom/agents/
|
||||||
```
|
```
|
||||||
|
|
||||||
### Step 2: Install and Personalize
|
### Step 2: Create custom.yaml
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npx bmad-method agent-install
|
# In the agent folder, create custom.yaml
|
||||||
# or: bmad agent-install (if BMAD installed locally)
|
cat > .bmad/custom/agents/my-agent/custom.yaml << EOF
|
||||||
|
code: my-agent
|
||||||
|
name: "My Custom Agent"
|
||||||
|
default_selected: true
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: Install
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx bmad-method install
|
||||||
|
# or: bmad install (if BMAD installed locally)
|
||||||
```
|
```
|
||||||
|
|
||||||
The installer will:
|
The installer will:
|
||||||
|
|
||||||
1. Find the copied template in `.bmad/custom/agents/`
|
1. Find the agent with its `custom.yaml`
|
||||||
2. Prompt for personalization (name, behavior, preferences)
|
2. Install it to the appropriate location
|
||||||
3. Compile and install with your choices baked in
|
3. Create IDE commands for immediate use
|
||||||
4. Create IDE commands for immediate use
|
|
||||||
|
|
||||||
### Available Reference Agents
|
### Available Reference Agents
|
||||||
|
|
||||||
|
|
@ -180,4 +134,4 @@ src/modules/bmb/reference/agents/
|
||||||
|
|
||||||
## Creating Your Own
|
## Creating Your Own
|
||||||
|
|
||||||
Use the BMB agent builder to craft your agents. Once ready to use yourself, place your `.agent.yaml` files or folder in `.bmad/custom/agents/`.
|
Use the BMB agent builder to craft your agents. Once ready to use, place your `.agent.yaml` files or folders with `custom.yaml` in `.bmad/custom/agents/` or `.bmad/custom/workflows/`.
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@
|
||||||
"bmad-method": "tools/bmad-npx-wrapper.js"
|
"bmad-method": "tools/bmad-npx-wrapper.js"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"bmad:agent-install": "node tools/cli/bmad-cli.js agent-install",
|
|
||||||
"bmad:install": "node tools/cli/bmad-cli.js install",
|
"bmad:install": "node tools/cli/bmad-cli.js install",
|
||||||
"bmad:status": "node tools/cli/bmad-cli.js status",
|
"bmad:status": "node tools/cli/bmad-cli.js status",
|
||||||
"bundle": "node tools/cli/bundlers/bundle-web.js all",
|
"bundle": "node tools/cli/bundlers/bundle-web.js all",
|
||||||
|
|
|
||||||
|
|
@ -22,5 +22,5 @@ custom_stand_alone_location:
|
||||||
|
|
||||||
custom_module_location:
|
custom_module_location:
|
||||||
prompt: "Where do custom modules get stored?"
|
prompt: "Where do custom modules get stored?"
|
||||||
default: "bmad-custom-modules-src/modules"
|
default: "bmad-custom-modules-src"
|
||||||
result: "{project-root}/{value}"
|
result: "{project-root}/{value}"
|
||||||
|
|
|
||||||
|
|
@ -217,9 +217,13 @@ Features demonstrated:
|
||||||
# Copy to your project
|
# Copy to your project
|
||||||
cp /path/to/commit-poet.agent.yaml .bmad/custom/agents/
|
cp /path/to/commit-poet.agent.yaml .bmad/custom/agents/
|
||||||
|
|
||||||
# Install with personalization
|
# Create custom.yaml and install
|
||||||
bmad agent-install
|
echo "code: my-agent
|
||||||
# or: npx bmad-method agent-install
|
name: My Agent
|
||||||
|
default_selected: true" > custom.yaml
|
||||||
|
|
||||||
|
npx bmad-method install
|
||||||
|
# or: bmad install
|
||||||
```
|
```
|
||||||
|
|
||||||
The installer:
|
The installer:
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,24 @@
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
```bash
|
Create a `custom.yaml` file in the agent folder:
|
||||||
# Quick install (interactive)
|
|
||||||
npx bmad-method agent-install --source ./{agent_filename}.agent.yaml
|
|
||||||
|
|
||||||
# Quick install (non-interactive)
|
```yaml
|
||||||
npx bmad-method agent-install --source ./{agent_filename}.agent.yaml --defaults
|
code: { agent_code }
|
||||||
|
name: '{agent_name}'
|
||||||
|
default_selected: true
|
||||||
|
```
|
||||||
|
|
||||||
|
Then run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx bmad-method install
|
||||||
|
```
|
||||||
|
|
||||||
|
Or if you have bmad-cli installed globally:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bmad install
|
||||||
```
|
```
|
||||||
|
|
||||||
## About This Agent
|
## About This Agent
|
||||||
|
|
|
||||||
|
|
@ -1,641 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
@ -198,9 +198,27 @@ class ConfigCollector {
|
||||||
}
|
}
|
||||||
|
|
||||||
let configPath = null;
|
let configPath = null;
|
||||||
|
let isCustomModule = false;
|
||||||
|
|
||||||
if (await fs.pathExists(installerConfigPath)) {
|
if (await fs.pathExists(installerConfigPath)) {
|
||||||
configPath = installerConfigPath;
|
configPath = installerConfigPath;
|
||||||
} else {
|
} else {
|
||||||
|
// Check if this is a custom module with custom.yaml
|
||||||
|
const { ModuleManager } = require('../modules/manager');
|
||||||
|
const moduleManager = new ModuleManager();
|
||||||
|
const moduleSourcePath = await moduleManager.findModuleSource(moduleName);
|
||||||
|
|
||||||
|
if (moduleSourcePath) {
|
||||||
|
const rootCustomConfigPath = path.join(moduleSourcePath, 'custom.yaml');
|
||||||
|
const moduleInstallerCustomPath = path.join(moduleSourcePath, '_module-installer', 'custom.yaml');
|
||||||
|
|
||||||
|
if ((await fs.pathExists(rootCustomConfigPath)) || (await fs.pathExists(moduleInstallerCustomPath))) {
|
||||||
|
isCustomModule = true;
|
||||||
|
// For custom modules, we don't have an install-config schema, so just use existing values
|
||||||
|
// The custom.yaml values will be loaded and merged during installation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// No config schema for this module - use existing values
|
// No config schema for this module - use existing values
|
||||||
if (this.existingConfig && this.existingConfig[moduleName]) {
|
if (this.existingConfig && this.existingConfig[moduleName]) {
|
||||||
if (!this.collectedConfig[moduleName]) {
|
if (!this.collectedConfig[moduleName]) {
|
||||||
|
|
|
||||||
|
|
@ -378,6 +378,35 @@ class ModuleManager {
|
||||||
throw new Error(`Module '${moduleName}' not found in any source location`);
|
throw new Error(`Module '${moduleName}' not found in any source location`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if this is a custom module and read its custom.yaml values
|
||||||
|
let customConfig = null;
|
||||||
|
const rootCustomConfigPath = path.join(sourcePath, 'custom.yaml');
|
||||||
|
const moduleInstallerCustomPath = path.join(sourcePath, '_module-installer', 'custom.yaml');
|
||||||
|
|
||||||
|
if (await fs.pathExists(rootCustomConfigPath)) {
|
||||||
|
try {
|
||||||
|
const customContent = await fs.readFile(rootCustomConfigPath, 'utf8');
|
||||||
|
customConfig = yaml.load(customContent);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(chalk.yellow(`Warning: Failed to read custom.yaml for ${moduleName}:`, error.message));
|
||||||
|
}
|
||||||
|
} else if (await fs.pathExists(moduleInstallerCustomPath)) {
|
||||||
|
try {
|
||||||
|
const customContent = await fs.readFile(moduleInstallerCustomPath, 'utf8');
|
||||||
|
customConfig = yaml.load(customContent);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(chalk.yellow(`Warning: Failed to read custom.yaml for ${moduleName}:`, error.message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this is a custom module, merge its values into the module config
|
||||||
|
if (customConfig) {
|
||||||
|
options.moduleConfig = { ...options.moduleConfig, ...customConfig };
|
||||||
|
if (options.logger) {
|
||||||
|
options.logger.log(chalk.cyan(` Merged custom configuration for ${moduleName}`));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check if already installed
|
// Check if already installed
|
||||||
if (await fs.pathExists(targetPath)) {
|
if (await fs.pathExists(targetPath)) {
|
||||||
console.log(chalk.yellow(`Module '${moduleName}' already installed, updating...`));
|
console.log(chalk.yellow(`Module '${moduleName}' already installed, updating...`));
|
||||||
|
|
@ -552,8 +581,8 @@ class ModuleManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip config.yaml templates - we'll generate clean ones with actual values
|
// Skip config.yaml templates - we'll generate clean ones with actual values
|
||||||
// But allow custom.yaml which is used for custom modules
|
// Also skip custom.yaml files - their values will be merged into core config
|
||||||
if ((file === 'config.yaml' || file.endsWith('/config.yaml')) && !file.endsWith('custom.yaml')) {
|
if (file === 'config.yaml' || file.endsWith('/config.yaml') || file === 'custom.yaml' || file.endsWith('/custom.yaml')) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue