fix: single agent install and team installation support
This commit is contained in:
@@ -46,21 +46,29 @@ program
|
||||
.description('Install BMAD Method agents and tools')
|
||||
.option('-f, --full', 'Install complete .bmad-core folder')
|
||||
.option('-a, --agent <agent>', 'Install specific agent with dependencies')
|
||||
.option('-t, --team <team>', 'Install specific team with required agents and dependencies')
|
||||
.option('-x, --expansion-only', 'Install only expansion packs (no bmad-core)')
|
||||
.option('-d, --directory <path>', 'Installation directory (default: .bmad-core)')
|
||||
.option('-i, --ide <ide...>', 'Configure for specific IDE(s) - can specify multiple (cursor, claude-code, windsurf, roo, other)')
|
||||
.option('-e, --expansion-packs <packs...>', 'Install specific expansion packs (can specify multiple)')
|
||||
.action(async (options) => {
|
||||
try {
|
||||
await initializeModules();
|
||||
if (!options.full && !options.agent) {
|
||||
if (!options.full && !options.agent && !options.team && !options.expansionOnly) {
|
||||
// Interactive mode
|
||||
const answers = await promptInstallation();
|
||||
await installer.install(answers);
|
||||
} else {
|
||||
// Direct mode
|
||||
let installType = 'full';
|
||||
if (options.agent) installType = 'single-agent';
|
||||
else if (options.team) installType = 'team';
|
||||
else if (options.expansionOnly) installType = 'expansion-only';
|
||||
|
||||
const config = {
|
||||
installType: options.full ? 'full' : 'single-agent',
|
||||
installType,
|
||||
agent: options.agent,
|
||||
team: options.team,
|
||||
directory: options.directory || '.bmad-core',
|
||||
ides: (options.ide || []).filter(ide => ide !== 'other'),
|
||||
expansionPacks: options.expansionPacks || []
|
||||
@@ -161,9 +169,17 @@ async function promptInstallation() {
|
||||
name: 'Complete installation (recommended) - All agents and tools',
|
||||
value: 'full'
|
||||
},
|
||||
{
|
||||
name: 'Team installation - Install a specific team with required agents',
|
||||
value: 'team'
|
||||
},
|
||||
{
|
||||
name: 'Single agent - Choose one agent to install',
|
||||
value: 'single-agent'
|
||||
},
|
||||
{
|
||||
name: 'Expansion packs only - Install expansion packs without bmad-core',
|
||||
value: 'expansion-only'
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -187,25 +203,62 @@ async function promptInstallation() {
|
||||
answers.agent = agent;
|
||||
}
|
||||
|
||||
// Ask for expansion pack selection (only for full installation)
|
||||
if (installType === 'full') {
|
||||
// If team installation, ask which team
|
||||
if (installType === 'team') {
|
||||
const teams = await installer.getAvailableTeams();
|
||||
const { team } = await inquirer.prompt([
|
||||
{
|
||||
type: 'list',
|
||||
name: 'team',
|
||||
message: 'Select a team to install:',
|
||||
choices: teams.map(t => ({
|
||||
name: `${t.icon || '📋'} ${t.name}: ${t.description}`,
|
||||
value: t.id
|
||||
}))
|
||||
}
|
||||
]);
|
||||
answers.team = team;
|
||||
}
|
||||
|
||||
// Ask for expansion pack selection
|
||||
if (installType === 'full' || installType === 'team' || installType === 'expansion-only') {
|
||||
try {
|
||||
const availableExpansionPacks = await installer.getAvailableExpansionPacks();
|
||||
|
||||
if (availableExpansionPacks.length > 0) {
|
||||
let choices;
|
||||
let message;
|
||||
|
||||
if (installType === 'expansion-only') {
|
||||
message = 'Select expansion packs to install (required):'
|
||||
choices = availableExpansionPacks.map(pack => ({
|
||||
name: `${pack.name} - ${pack.description}`,
|
||||
value: pack.id
|
||||
}));
|
||||
} else {
|
||||
message = 'Select expansion packs to install (optional):';
|
||||
choices = [
|
||||
{ name: 'Skip expansion packs', value: 'none', checked: true },
|
||||
new inquirer.Separator(' --- Expansion Packs ---'),
|
||||
...availableExpansionPacks.map(pack => ({
|
||||
name: `${pack.name} - ${pack.description}`,
|
||||
value: pack.id
|
||||
}))
|
||||
];
|
||||
}
|
||||
|
||||
const { expansionPacks } = await inquirer.prompt([
|
||||
{
|
||||
type: 'checkbox',
|
||||
name: 'expansionPacks',
|
||||
message: 'Select expansion packs to install (optional):',
|
||||
choices: [
|
||||
{ name: 'BMAD Core only (no expansion packs)', value: 'none', checked: true },
|
||||
new inquirer.Separator(' --- Expansion Packs ---'),
|
||||
...availableExpansionPacks.map(pack => ({
|
||||
name: `${pack.name} - ${pack.description}`,
|
||||
value: pack.id
|
||||
}))
|
||||
]
|
||||
message,
|
||||
choices,
|
||||
validate: installType === 'expansion-only' ? (answer) => {
|
||||
if (answer.length < 1) {
|
||||
return 'You must select at least one expansion pack for expansion-only installation.';
|
||||
}
|
||||
return true;
|
||||
} : undefined
|
||||
}
|
||||
]);
|
||||
|
||||
|
||||
@@ -78,10 +78,7 @@ class ConfigLoader {
|
||||
// Convert to flat list of file paths
|
||||
const depPaths = [];
|
||||
|
||||
// Add core files
|
||||
const config = await this.load();
|
||||
const coreFiles = config['agent-dependencies']?.['core-files'] || [];
|
||||
depPaths.push(...coreFiles);
|
||||
// Core files and utilities are included automatically by DependencyResolver
|
||||
|
||||
// Add agent file itself is already handled by installer
|
||||
|
||||
@@ -121,6 +118,82 @@ class ConfigLoader {
|
||||
getAgentPath(agentId) {
|
||||
return path.join(this.getBmadCorePath(), 'agents', `${agentId}.md`);
|
||||
}
|
||||
|
||||
async getAvailableTeams() {
|
||||
const teamsDir = path.join(this.getBmadCorePath(), 'agent-teams');
|
||||
|
||||
try {
|
||||
const entries = await fs.readdir(teamsDir, { withFileTypes: true });
|
||||
const teams = [];
|
||||
|
||||
for (const entry of entries) {
|
||||
if (entry.isFile() && entry.name.endsWith('.yml')) {
|
||||
const teamPath = path.join(teamsDir, entry.name);
|
||||
|
||||
try {
|
||||
const teamContent = await fs.readFile(teamPath, 'utf8');
|
||||
const teamConfig = yaml.load(teamContent);
|
||||
|
||||
if (teamConfig.bundle) {
|
||||
teams.push({
|
||||
id: path.basename(entry.name, '.yml'),
|
||||
name: teamConfig.bundle.name || entry.name,
|
||||
description: teamConfig.bundle.description || 'Team configuration',
|
||||
icon: teamConfig.bundle.icon || '📋'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`Warning: Could not load team config ${entry.name}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return teams;
|
||||
} catch (error) {
|
||||
console.warn(`Warning: Could not scan teams directory: ${error.message}`);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
getTeamPath(teamId) {
|
||||
return path.join(this.getBmadCorePath(), 'agent-teams', `${teamId}.yml`);
|
||||
}
|
||||
|
||||
async getTeamDependencies(teamId) {
|
||||
// Use DependencyResolver to dynamically parse team dependencies
|
||||
const DependencyResolver = require('../../lib/dependency-resolver');
|
||||
const resolver = new DependencyResolver(path.join(__dirname, '..', '..', '..'));
|
||||
|
||||
try {
|
||||
const teamDeps = await resolver.resolveTeamDependencies(teamId);
|
||||
|
||||
// Convert to flat list of file paths
|
||||
const depPaths = [];
|
||||
|
||||
// Add team config file
|
||||
depPaths.push(`.bmad-core/agent-teams/${teamId}.yml`);
|
||||
|
||||
// Add all agents
|
||||
for (const agent of teamDeps.agents) {
|
||||
const filePath = `.bmad-core/agents/${agent.id}.md`;
|
||||
if (!depPaths.includes(filePath)) {
|
||||
depPaths.push(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
// Add all resolved resources
|
||||
for (const resource of teamDeps.resources) {
|
||||
const filePath = `.bmad-core/${resource.type}/${resource.id}.${resource.type === 'workflows' ? 'yml' : 'md'}`;
|
||||
if (!depPaths.includes(filePath)) {
|
||||
depPaths.push(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
return depPaths;
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to resolve team dependencies for ${teamId}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new ConfigLoader();
|
||||
@@ -220,11 +220,12 @@ class Installer {
|
||||
const agentPath = configLoader.getAgentPath(config.agent);
|
||||
const destAgentPath = path.join(
|
||||
installDir,
|
||||
".bmad-core",
|
||||
"agents",
|
||||
`${config.agent}.md`
|
||||
);
|
||||
await fileManager.copyFile(agentPath, destAgentPath);
|
||||
files.push(`agents/${config.agent}.md`);
|
||||
files.push(`.bmad-core/agents/${config.agent}.md`);
|
||||
|
||||
// Copy dependencies
|
||||
const dependencies = await configLoader.getAgentDependencies(
|
||||
@@ -240,9 +241,9 @@ class Installer {
|
||||
const copiedFiles = await fileManager.copyGlobPattern(
|
||||
dep.replace(".bmad-core/", ""),
|
||||
sourceBase,
|
||||
installDir
|
||||
path.join(installDir, ".bmad-core")
|
||||
);
|
||||
files.push(...copiedFiles);
|
||||
files.push(...copiedFiles.map(f => `.bmad-core/${f}`));
|
||||
} else {
|
||||
// Handle single files
|
||||
const sourcePath = path.join(
|
||||
@@ -251,14 +252,72 @@ class Installer {
|
||||
);
|
||||
const destPath = path.join(
|
||||
installDir,
|
||||
dep.replace(".bmad-core/", "")
|
||||
dep
|
||||
);
|
||||
|
||||
if (await fileManager.copyFile(sourcePath, destPath)) {
|
||||
files.push(dep.replace(".bmad-core/", ""));
|
||||
files.push(dep);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (config.installType === "team") {
|
||||
// Team installation
|
||||
spinner.text = `Installing ${config.team} team...`;
|
||||
|
||||
// Get team dependencies
|
||||
const teamDependencies = await configLoader.getTeamDependencies(config.team);
|
||||
const sourceBase = configLoader.getBmadCorePath();
|
||||
|
||||
// Install all team dependencies
|
||||
for (const dep of teamDependencies) {
|
||||
spinner.text = `Copying team dependency: ${dep}`;
|
||||
|
||||
if (dep.includes("*")) {
|
||||
// Handle glob patterns
|
||||
const copiedFiles = await fileManager.copyGlobPattern(
|
||||
dep.replace(".bmad-core/", ""),
|
||||
sourceBase,
|
||||
path.join(installDir, ".bmad-core")
|
||||
);
|
||||
files.push(...copiedFiles.map(f => `.bmad-core/${f}`));
|
||||
} else {
|
||||
// Handle single files
|
||||
const sourcePath = path.join(sourceBase, dep.replace(".bmad-core/", ""));
|
||||
const destPath = path.join(installDir, dep);
|
||||
|
||||
if (await fileManager.copyFile(sourcePath, destPath)) {
|
||||
files.push(dep);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (config.installType === "expansion-only") {
|
||||
// Expansion-only installation - create minimal .bmad-core structure
|
||||
spinner.text = "Creating minimal .bmad-core structure for expansion packs...";
|
||||
|
||||
const bmadCoreDestDir = path.join(installDir, ".bmad-core");
|
||||
await fileManager.ensureDirectory(bmadCoreDestDir);
|
||||
|
||||
// Create basic directory structure
|
||||
const dirs = ['agents', 'agent-teams', 'templates', 'tasks', 'checklists', 'workflows', 'data', 'utils', 'schemas'];
|
||||
for (const dir of dirs) {
|
||||
await fileManager.ensureDirectory(path.join(bmadCoreDestDir, dir));
|
||||
}
|
||||
|
||||
// Copy minimal required files (schemas, utils, etc.)
|
||||
const sourceBase = configLoader.getBmadCorePath();
|
||||
const essentialFiles = [
|
||||
'schemas/**/*',
|
||||
'utils/**/*'
|
||||
];
|
||||
|
||||
for (const pattern of essentialFiles) {
|
||||
const copiedFiles = await fileManager.copyGlobPattern(
|
||||
pattern,
|
||||
sourceBase,
|
||||
bmadCoreDestDir
|
||||
);
|
||||
files.push(...copiedFiles.map(f => `.bmad-core/${f}`));
|
||||
}
|
||||
}
|
||||
|
||||
// Install expansion packs if requested
|
||||
@@ -664,6 +723,10 @@ class Installer {
|
||||
return configLoader.getAvailableExpansionPacks();
|
||||
}
|
||||
|
||||
async getAvailableTeams() {
|
||||
return configLoader.getAvailableTeams();
|
||||
}
|
||||
|
||||
async installExpansionPacks(installDir, selectedPacks, spinner) {
|
||||
if (!selectedPacks || selectedPacks.length === 0) {
|
||||
return [];
|
||||
|
||||
Reference in New Issue
Block a user