2025-06-15 10:50:40 -07:00
|
|
|
|
const path = require("node:path");
|
2025-06-14 15:06:41 -05:00
|
|
|
|
const chalk = require("chalk");
|
|
|
|
|
|
const ora = require("ora");
|
2025-06-14 15:14:26 -05:00
|
|
|
|
const inquirer = require("inquirer");
|
2025-06-14 15:06:41 -05:00
|
|
|
|
const fileManager = require("./file-manager");
|
2025-06-14 15:14:26 -05:00
|
|
|
|
const configLoader = require("./config-loader");
|
2025-06-14 15:06:41 -05:00
|
|
|
|
const ideSetup = require("./ide-setup");
|
2025-06-12 22:38:24 -05:00
|
|
|
|
|
|
|
|
|
|
class Installer {
|
|
|
|
|
|
async install(config) {
|
2025-06-14 15:14:26 -05:00
|
|
|
|
const spinner = ora("Analyzing installation directory...").start();
|
2025-06-14 15:06:41 -05:00
|
|
|
|
|
2025-06-12 22:38:24 -05:00
|
|
|
|
try {
|
|
|
|
|
|
// Resolve installation directory
|
2025-06-15 10:50:40 -07:00
|
|
|
|
let installDir = path.resolve(config.directory);
|
|
|
|
|
|
if (path.basename(installDir) === '.bmad-core') {
|
|
|
|
|
|
// If user points directly to .bmad-core, treat its parent as the project root
|
|
|
|
|
|
installDir = path.dirname(installDir);
|
|
|
|
|
|
}
|
2025-06-14 15:06:41 -05:00
|
|
|
|
|
2025-06-14 15:14:26 -05:00
|
|
|
|
// Detect current state
|
|
|
|
|
|
const state = await this.detectInstallationState(installDir);
|
|
|
|
|
|
|
|
|
|
|
|
// Handle different states
|
|
|
|
|
|
switch (state.type) {
|
|
|
|
|
|
case "clean":
|
|
|
|
|
|
return await this.performFreshInstall(config, installDir, spinner);
|
|
|
|
|
|
|
|
|
|
|
|
case "v4_existing":
|
|
|
|
|
|
return await this.handleExistingV4Installation(
|
|
|
|
|
|
config,
|
|
|
|
|
|
installDir,
|
|
|
|
|
|
state,
|
|
|
|
|
|
spinner
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
case "v3_existing":
|
|
|
|
|
|
return await this.handleV3Installation(
|
|
|
|
|
|
config,
|
|
|
|
|
|
installDir,
|
|
|
|
|
|
state,
|
|
|
|
|
|
spinner
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
case "unknown_existing":
|
|
|
|
|
|
return await this.handleUnknownInstallation(
|
|
|
|
|
|
config,
|
|
|
|
|
|
installDir,
|
|
|
|
|
|
state,
|
|
|
|
|
|
spinner
|
2025-06-14 15:06:41 -05:00
|
|
|
|
);
|
2025-06-12 22:38:24 -05:00
|
|
|
|
}
|
2025-06-14 15:14:26 -05:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
spinner.fail("Installation failed");
|
|
|
|
|
|
throw error;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-06-14 15:06:41 -05:00
|
|
|
|
|
2025-06-14 15:14:26 -05:00
|
|
|
|
async detectInstallationState(installDir) {
|
|
|
|
|
|
const state = {
|
|
|
|
|
|
type: "clean",
|
|
|
|
|
|
hasV4Manifest: false,
|
|
|
|
|
|
hasV3Structure: false,
|
|
|
|
|
|
hasBmadCore: false,
|
|
|
|
|
|
hasOtherFiles: false,
|
|
|
|
|
|
manifest: null,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Check if directory exists
|
|
|
|
|
|
if (!(await fileManager.pathExists(installDir))) {
|
|
|
|
|
|
return state; // clean install
|
|
|
|
|
|
}
|
2025-06-14 15:06:41 -05:00
|
|
|
|
|
2025-06-14 15:14:26 -05:00
|
|
|
|
// Check for V4 installation (has .bmad-core with manifest)
|
|
|
|
|
|
const bmadCorePath = path.join(installDir, ".bmad-core");
|
|
|
|
|
|
const manifestPath = path.join(bmadCorePath, "install-manifest.yml");
|
2025-06-14 15:06:41 -05:00
|
|
|
|
|
2025-06-14 15:14:26 -05:00
|
|
|
|
if (await fileManager.pathExists(manifestPath)) {
|
|
|
|
|
|
state.type = "v4_existing";
|
|
|
|
|
|
state.hasV4Manifest = true;
|
|
|
|
|
|
state.hasBmadCore = true;
|
2025-06-14 23:49:10 -05:00
|
|
|
|
state.manifest = await fileManager.readManifest(installDir);
|
2025-06-14 15:14:26 -05:00
|
|
|
|
return state;
|
|
|
|
|
|
}
|
2025-06-14 15:06:41 -05:00
|
|
|
|
|
2025-06-14 15:14:26 -05:00
|
|
|
|
// Check for V3 installation (has bmad-agent directory)
|
|
|
|
|
|
const bmadAgentPath = path.join(installDir, "bmad-agent");
|
|
|
|
|
|
if (await fileManager.pathExists(bmadAgentPath)) {
|
|
|
|
|
|
state.type = "v3_existing";
|
|
|
|
|
|
state.hasV3Structure = true;
|
|
|
|
|
|
return state;
|
|
|
|
|
|
}
|
2025-06-14 15:06:41 -05:00
|
|
|
|
|
2025-06-14 15:14:26 -05:00
|
|
|
|
// Check for .bmad-core without manifest (broken V4 or manual copy)
|
|
|
|
|
|
if (await fileManager.pathExists(bmadCorePath)) {
|
|
|
|
|
|
state.type = "unknown_existing";
|
|
|
|
|
|
state.hasBmadCore = true;
|
|
|
|
|
|
return state;
|
|
|
|
|
|
}
|
2025-06-14 15:06:41 -05:00
|
|
|
|
|
2025-06-14 15:14:26 -05:00
|
|
|
|
// Check if directory has other files
|
|
|
|
|
|
const glob = require("glob");
|
|
|
|
|
|
const files = glob.sync("**/*", {
|
|
|
|
|
|
cwd: installDir,
|
|
|
|
|
|
nodir: true,
|
|
|
|
|
|
ignore: ["**/.git/**", "**/node_modules/**"],
|
|
|
|
|
|
});
|
2025-06-14 15:06:41 -05:00
|
|
|
|
|
2025-06-14 15:14:26 -05:00
|
|
|
|
if (files.length > 0) {
|
2025-06-15 10:50:40 -07:00
|
|
|
|
// Directory has other files, but no BMAD installation.
|
|
|
|
|
|
// Treat as clean install but record that it isn't empty.
|
2025-06-14 15:14:26 -05:00
|
|
|
|
state.hasOtherFiles = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return state; // clean install
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async performFreshInstall(config, installDir, spinner) {
|
|
|
|
|
|
spinner.text = "Installing BMAD Method...";
|
|
|
|
|
|
|
|
|
|
|
|
let files = [];
|
|
|
|
|
|
|
|
|
|
|
|
if (config.installType === "full") {
|
|
|
|
|
|
// Full installation - copy entire .bmad-core folder as a subdirectory
|
|
|
|
|
|
spinner.text = "Copying complete .bmad-core folder...";
|
|
|
|
|
|
const sourceDir = configLoader.getBmadCorePath();
|
|
|
|
|
|
const bmadCoreDestDir = path.join(installDir, ".bmad-core");
|
|
|
|
|
|
await fileManager.copyDirectory(sourceDir, bmadCoreDestDir);
|
|
|
|
|
|
|
|
|
|
|
|
// Get list of all files for manifest
|
|
|
|
|
|
const glob = require("glob");
|
|
|
|
|
|
files = glob
|
|
|
|
|
|
.sync("**/*", {
|
|
|
|
|
|
cwd: bmadCoreDestDir,
|
|
|
|
|
|
nodir: true,
|
|
|
|
|
|
ignore: ["**/.git/**", "**/node_modules/**"],
|
|
|
|
|
|
})
|
|
|
|
|
|
.map((file) => path.join(".bmad-core", file));
|
|
|
|
|
|
} else if (config.installType === "single-agent") {
|
|
|
|
|
|
// Single agent installation
|
|
|
|
|
|
spinner.text = `Installing ${config.agent} agent...`;
|
|
|
|
|
|
|
|
|
|
|
|
// Copy agent file
|
|
|
|
|
|
const agentPath = configLoader.getAgentPath(config.agent);
|
|
|
|
|
|
const destAgentPath = path.join(
|
|
|
|
|
|
installDir,
|
|
|
|
|
|
"agents",
|
|
|
|
|
|
`${config.agent}.md`
|
|
|
|
|
|
);
|
|
|
|
|
|
await fileManager.copyFile(agentPath, destAgentPath);
|
|
|
|
|
|
files.push(`agents/${config.agent}.md`);
|
2025-06-14 15:06:41 -05:00
|
|
|
|
|
2025-06-14 15:14:26 -05:00
|
|
|
|
// Copy dependencies
|
|
|
|
|
|
const dependencies = await configLoader.getAgentDependencies(
|
|
|
|
|
|
config.agent
|
|
|
|
|
|
);
|
|
|
|
|
|
const sourceBase = configLoader.getBmadCorePath();
|
2025-06-14 15:06:41 -05:00
|
|
|
|
|
2025-06-14 15:14:26 -05:00
|
|
|
|
for (const dep of dependencies) {
|
|
|
|
|
|
spinner.text = `Copying dependency: ${dep}`;
|
2025-06-14 15:06:41 -05:00
|
|
|
|
|
2025-06-14 15:14:26 -05:00
|
|
|
|
if (dep.includes("*")) {
|
|
|
|
|
|
// Handle glob patterns
|
|
|
|
|
|
const copiedFiles = await fileManager.copyGlobPattern(
|
|
|
|
|
|
dep.replace(".bmad-core/", ""),
|
|
|
|
|
|
sourceBase,
|
|
|
|
|
|
installDir
|
|
|
|
|
|
);
|
|
|
|
|
|
files.push(...copiedFiles);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// Handle single files
|
|
|
|
|
|
const sourcePath = path.join(
|
|
|
|
|
|
sourceBase,
|
|
|
|
|
|
dep.replace(".bmad-core/", "")
|
2025-06-14 15:06:41 -05:00
|
|
|
|
);
|
2025-06-14 15:14:26 -05:00
|
|
|
|
const destPath = path.join(
|
|
|
|
|
|
installDir,
|
|
|
|
|
|
dep.replace(".bmad-core/", "")
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (await fileManager.copyFile(sourcePath, destPath)) {
|
|
|
|
|
|
files.push(dep.replace(".bmad-core/", ""));
|
|
|
|
|
|
}
|
2025-06-12 22:38:24 -05:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-06-14 15:14:26 -05:00
|
|
|
|
}
|
2025-06-14 15:06:41 -05:00
|
|
|
|
|
2025-06-14 15:14:26 -05:00
|
|
|
|
// Set up IDE integration if requested
|
|
|
|
|
|
if (config.ide) {
|
|
|
|
|
|
spinner.text = `Setting up ${config.ide} integration...`;
|
|
|
|
|
|
await ideSetup.setup(config.ide, installDir, config.agent);
|
2025-06-12 22:38:24 -05:00
|
|
|
|
}
|
2025-06-14 15:14:26 -05:00
|
|
|
|
|
|
|
|
|
|
// Create manifest
|
|
|
|
|
|
spinner.text = "Creating installation manifest...";
|
|
|
|
|
|
await fileManager.createManifest(installDir, config, files);
|
|
|
|
|
|
|
|
|
|
|
|
spinner.succeed("Installation complete!");
|
|
|
|
|
|
this.showSuccessMessage(config, installDir);
|
2025-06-12 22:38:24 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-14 15:14:26 -05:00
|
|
|
|
async handleExistingV4Installation(config, installDir, state, spinner) {
|
|
|
|
|
|
spinner.stop();
|
2025-06-14 15:06:41 -05:00
|
|
|
|
|
2025-06-14 15:14:26 -05:00
|
|
|
|
console.log(chalk.yellow("\n🔍 Found existing BMAD v4 installation"));
|
|
|
|
|
|
console.log(` Directory: ${installDir}`);
|
|
|
|
|
|
console.log(` Version: ${state.manifest.version}`);
|
|
|
|
|
|
console.log(
|
|
|
|
|
|
` Installed: ${new Date(
|
|
|
|
|
|
state.manifest.installed_at
|
|
|
|
|
|
).toLocaleDateString()}`
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
const { action } = await inquirer.prompt([
|
|
|
|
|
|
{
|
|
|
|
|
|
type: "list",
|
|
|
|
|
|
name: "action",
|
|
|
|
|
|
message: "What would you like to do?",
|
|
|
|
|
|
choices: [
|
|
|
|
|
|
{ name: "Update existing installation", value: "update" },
|
|
|
|
|
|
{ name: "Reinstall (overwrite)", value: "reinstall" },
|
|
|
|
|
|
{ name: "Cancel", value: "cancel" },
|
|
|
|
|
|
],
|
|
|
|
|
|
},
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
switch (action) {
|
|
|
|
|
|
case "update":
|
2025-06-15 01:05:56 -05:00
|
|
|
|
return await this.performUpdate(config, installDir, state.manifest, spinner);
|
2025-06-14 15:14:26 -05:00
|
|
|
|
case "reinstall":
|
|
|
|
|
|
return await this.performReinstall(config, installDir, spinner);
|
|
|
|
|
|
case "cancel":
|
|
|
|
|
|
console.log("Installation cancelled.");
|
2025-06-12 22:38:24 -05:00
|
|
|
|
return;
|
2025-06-14 15:14:26 -05:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async handleV3Installation(config, installDir, state, spinner) {
|
|
|
|
|
|
spinner.stop();
|
2025-06-14 15:06:41 -05:00
|
|
|
|
|
2025-06-14 15:14:26 -05:00
|
|
|
|
console.log(
|
|
|
|
|
|
chalk.yellow("\n🔍 Found BMAD v3 installation (bmad-agent/ directory)")
|
|
|
|
|
|
);
|
|
|
|
|
|
console.log(` Directory: ${installDir}`);
|
|
|
|
|
|
|
|
|
|
|
|
const { action } = await inquirer.prompt([
|
|
|
|
|
|
{
|
|
|
|
|
|
type: "list",
|
|
|
|
|
|
name: "action",
|
|
|
|
|
|
message: "What would you like to do?",
|
|
|
|
|
|
choices: [
|
|
|
|
|
|
{ name: "Upgrade from v3 to v4 (recommended)", value: "upgrade" },
|
|
|
|
|
|
{ name: "Install v4 alongside v3", value: "alongside" },
|
|
|
|
|
|
{ name: "Cancel", value: "cancel" },
|
|
|
|
|
|
],
|
|
|
|
|
|
},
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
switch (action) {
|
2025-06-15 10:50:40 -07:00
|
|
|
|
case "upgrade": {
|
2025-06-14 15:14:26 -05:00
|
|
|
|
console.log(chalk.cyan("\n📦 Starting v3 to v4 upgrade process..."));
|
|
|
|
|
|
const V3ToV4Upgrader = require("../../upgraders/v3-to-v4-upgrader");
|
|
|
|
|
|
const upgrader = new V3ToV4Upgrader();
|
|
|
|
|
|
return await upgrader.upgrade({ projectPath: installDir });
|
2025-06-15 10:50:40 -07:00
|
|
|
|
}
|
2025-06-14 15:14:26 -05:00
|
|
|
|
case "alongside":
|
|
|
|
|
|
return await this.performFreshInstall(config, installDir, spinner);
|
|
|
|
|
|
case "cancel":
|
|
|
|
|
|
console.log("Installation cancelled.");
|
2025-06-12 22:38:24 -05:00
|
|
|
|
return;
|
2025-06-14 15:14:26 -05:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async handleUnknownInstallation(config, installDir, state, spinner) {
|
|
|
|
|
|
spinner.stop();
|
|
|
|
|
|
|
|
|
|
|
|
console.log(chalk.yellow("\n⚠️ Directory contains existing files"));
|
|
|
|
|
|
console.log(` Directory: ${installDir}`);
|
|
|
|
|
|
|
|
|
|
|
|
if (state.hasBmadCore) {
|
|
|
|
|
|
console.log(" Found: .bmad-core directory (but no manifest)");
|
|
|
|
|
|
}
|
|
|
|
|
|
if (state.hasOtherFiles) {
|
|
|
|
|
|
console.log(" Found: Other files in directory");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const { action } = await inquirer.prompt([
|
|
|
|
|
|
{
|
|
|
|
|
|
type: "list",
|
|
|
|
|
|
name: "action",
|
|
|
|
|
|
message: "What would you like to do?",
|
|
|
|
|
|
choices: [
|
|
|
|
|
|
{ name: "Install anyway (may overwrite files)", value: "force" },
|
|
|
|
|
|
{ name: "Choose different directory", value: "different" },
|
|
|
|
|
|
{ name: "Cancel", value: "cancel" },
|
|
|
|
|
|
],
|
|
|
|
|
|
},
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
switch (action) {
|
|
|
|
|
|
case "force":
|
|
|
|
|
|
return await this.performFreshInstall(config, installDir, spinner);
|
2025-06-15 10:50:40 -07:00
|
|
|
|
case "different": {
|
2025-06-14 15:14:26 -05:00
|
|
|
|
const { newDir } = await inquirer.prompt([
|
|
|
|
|
|
{
|
|
|
|
|
|
type: "input",
|
|
|
|
|
|
name: "newDir",
|
|
|
|
|
|
message: "Enter new installation directory:",
|
|
|
|
|
|
default: path.join(path.dirname(installDir), "bmad-project"),
|
|
|
|
|
|
},
|
|
|
|
|
|
]);
|
|
|
|
|
|
config.directory = newDir;
|
|
|
|
|
|
return await this.install(config);
|
2025-06-15 10:50:40 -07:00
|
|
|
|
}
|
2025-06-14 15:14:26 -05:00
|
|
|
|
case "cancel":
|
|
|
|
|
|
console.log("Installation cancelled.");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-06-14 15:06:41 -05:00
|
|
|
|
|
2025-06-15 01:05:56 -05:00
|
|
|
|
async performUpdate(newConfig, installDir, manifest, spinner) {
|
2025-06-14 15:14:26 -05:00
|
|
|
|
spinner.start("Checking for updates...");
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
2025-06-12 22:38:24 -05:00
|
|
|
|
// Check for modified files
|
2025-06-14 15:06:41 -05:00
|
|
|
|
spinner.text = "Checking for modified files...";
|
|
|
|
|
|
const modifiedFiles = await fileManager.checkModifiedFiles(
|
|
|
|
|
|
installDir,
|
|
|
|
|
|
manifest
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2025-06-14 15:14:26 -05:00
|
|
|
|
if (modifiedFiles.length > 0) {
|
2025-06-14 15:06:41 -05:00
|
|
|
|
spinner.warn("Found modified files");
|
|
|
|
|
|
console.log(chalk.yellow("\nThe following files have been modified:"));
|
2025-06-15 10:50:40 -07:00
|
|
|
|
for (const file of modifiedFiles) {
|
|
|
|
|
|
console.log(` - ${file}`);
|
|
|
|
|
|
}
|
2025-06-14 15:06:41 -05:00
|
|
|
|
|
2025-06-14 15:14:26 -05:00
|
|
|
|
const { action } = await inquirer.prompt([
|
|
|
|
|
|
{
|
|
|
|
|
|
type: "list",
|
|
|
|
|
|
name: "action",
|
|
|
|
|
|
message: "How would you like to proceed?",
|
|
|
|
|
|
choices: [
|
|
|
|
|
|
{ name: "Backup and overwrite modified files", value: "backup" },
|
|
|
|
|
|
{ name: "Skip modified files", value: "skip" },
|
|
|
|
|
|
{ name: "Cancel update", value: "cancel" },
|
|
|
|
|
|
],
|
|
|
|
|
|
},
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
if (action === "cancel") {
|
|
|
|
|
|
console.log("Update cancelled.");
|
|
|
|
|
|
return;
|
2025-06-12 22:38:24 -05:00
|
|
|
|
}
|
2025-06-14 15:06:41 -05:00
|
|
|
|
|
2025-06-14 15:14:26 -05:00
|
|
|
|
if (action === "backup") {
|
|
|
|
|
|
spinner.start("Backing up modified files...");
|
|
|
|
|
|
for (const file of modifiedFiles) {
|
|
|
|
|
|
const filePath = path.join(installDir, file);
|
|
|
|
|
|
const backupPath = await fileManager.backupFile(filePath);
|
|
|
|
|
|
console.log(
|
|
|
|
|
|
chalk.dim(` Backed up: ${file} → ${path.basename(backupPath)}`)
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
2025-06-12 22:38:24 -05:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-06-14 15:06:41 -05:00
|
|
|
|
|
2025-06-14 15:14:26 -05:00
|
|
|
|
// Perform update by re-running installation
|
|
|
|
|
|
spinner.text = "Updating files...";
|
2025-06-12 22:38:24 -05:00
|
|
|
|
const config = {
|
|
|
|
|
|
installType: manifest.install_type,
|
|
|
|
|
|
agent: manifest.agent,
|
|
|
|
|
|
directory: installDir,
|
2025-06-15 01:05:56 -05:00
|
|
|
|
ide: newConfig.ide || manifest.ide_setup, // Use new IDE choice if provided
|
2025-06-12 22:38:24 -05:00
|
|
|
|
};
|
2025-06-14 15:06:41 -05:00
|
|
|
|
|
2025-06-14 15:14:26 -05:00
|
|
|
|
await this.performFreshInstall(config, installDir, spinner);
|
2025-06-12 22:38:24 -05:00
|
|
|
|
} catch (error) {
|
2025-06-14 15:06:41 -05:00
|
|
|
|
spinner.fail("Update failed");
|
2025-06-12 22:38:24 -05:00
|
|
|
|
throw error;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-14 15:14:26 -05:00
|
|
|
|
async performReinstall(config, installDir, spinner) {
|
|
|
|
|
|
spinner.start("Reinstalling BMAD Method...");
|
|
|
|
|
|
|
|
|
|
|
|
// Remove existing .bmad-core
|
|
|
|
|
|
const bmadCorePath = path.join(installDir, ".bmad-core");
|
|
|
|
|
|
if (await fileManager.pathExists(bmadCorePath)) {
|
|
|
|
|
|
await fileManager.removeDirectory(bmadCorePath);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return await this.performFreshInstall(config, installDir, spinner);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
showSuccessMessage(config, installDir) {
|
|
|
|
|
|
console.log(chalk.green("\n✓ BMAD Method installed successfully!\n"));
|
|
|
|
|
|
|
|
|
|
|
|
if (config.ide) {
|
|
|
|
|
|
const ideConfig = configLoader.getIdeConfiguration(config.ide);
|
2025-06-15 10:50:40 -07:00
|
|
|
|
if (ideConfig?.instructions) {
|
2025-06-14 15:14:26 -05:00
|
|
|
|
console.log(
|
2025-06-15 10:50:40 -07:00
|
|
|
|
chalk.bold(`To use BMAD agents in ${ideConfig.name}:`)
|
2025-06-14 15:14:26 -05:00
|
|
|
|
);
|
|
|
|
|
|
console.log(ideConfig.instructions);
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
console.log(chalk.yellow("No IDE configuration was set up."));
|
|
|
|
|
|
console.log(
|
|
|
|
|
|
"You can manually configure your IDE using the agent files in:",
|
|
|
|
|
|
installDir
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (config.installType === "single-agent") {
|
|
|
|
|
|
console.log(
|
|
|
|
|
|
chalk.dim(
|
|
|
|
|
|
"\nNeed other agents? Run: npx bmad-method install --agent=<name>"
|
|
|
|
|
|
)
|
|
|
|
|
|
);
|
|
|
|
|
|
console.log(
|
|
|
|
|
|
chalk.dim("Need everything? Run: npx bmad-method install --full")
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Legacy method for backward compatibility
|
2025-06-15 10:50:40 -07:00
|
|
|
|
async update() {
|
2025-06-14 15:14:26 -05:00
|
|
|
|
console.log(chalk.yellow('The "update" command is deprecated.'));
|
|
|
|
|
|
console.log(
|
|
|
|
|
|
'Please use "install" instead - it will detect and offer to update existing installations.'
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
const installDir = await this.findInstallation();
|
|
|
|
|
|
if (installDir) {
|
|
|
|
|
|
const config = {
|
|
|
|
|
|
installType: "full",
|
|
|
|
|
|
directory: path.dirname(installDir),
|
|
|
|
|
|
ide: null,
|
|
|
|
|
|
};
|
|
|
|
|
|
return await this.install(config);
|
|
|
|
|
|
}
|
2025-06-15 10:50:40 -07:00
|
|
|
|
console.log(chalk.red("No BMAD installation found."));
|
2025-06-14 15:14:26 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-12 22:38:24 -05:00
|
|
|
|
async listAgents() {
|
|
|
|
|
|
const agents = await configLoader.getAvailableAgents();
|
2025-06-14 15:06:41 -05:00
|
|
|
|
|
|
|
|
|
|
console.log(chalk.bold("\nAvailable BMAD Agents:\n"));
|
|
|
|
|
|
|
2025-06-15 10:50:40 -07:00
|
|
|
|
for (const agent of agents) {
|
2025-06-12 22:38:24 -05:00
|
|
|
|
console.log(chalk.cyan(` ${agent.id.padEnd(20)}`), agent.description);
|
2025-06-15 10:50:40 -07:00
|
|
|
|
}
|
2025-06-14 15:06:41 -05:00
|
|
|
|
|
|
|
|
|
|
console.log(
|
|
|
|
|
|
chalk.dim("\nInstall with: npx bmad-method install --agent=<id>\n")
|
|
|
|
|
|
);
|
2025-06-12 22:38:24 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async showStatus() {
|
|
|
|
|
|
const installDir = await this.findInstallation();
|
2025-06-14 15:06:41 -05:00
|
|
|
|
|
2025-06-12 22:38:24 -05:00
|
|
|
|
if (!installDir) {
|
2025-06-14 15:06:41 -05:00
|
|
|
|
console.log(
|
|
|
|
|
|
chalk.yellow("No BMAD installation found in current directory tree")
|
|
|
|
|
|
);
|
2025-06-12 22:38:24 -05:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-06-14 15:06:41 -05:00
|
|
|
|
|
2025-06-12 22:38:24 -05:00
|
|
|
|
const manifest = await fileManager.readManifest(installDir);
|
2025-06-14 15:06:41 -05:00
|
|
|
|
|
2025-06-12 22:38:24 -05:00
|
|
|
|
if (!manifest) {
|
2025-06-14 15:06:41 -05:00
|
|
|
|
console.log(chalk.red("Invalid installation - manifest not found"));
|
2025-06-12 22:38:24 -05:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-06-14 15:06:41 -05:00
|
|
|
|
|
|
|
|
|
|
console.log(chalk.bold("\nBMAD Installation Status:\n"));
|
2025-06-12 22:38:24 -05:00
|
|
|
|
console.log(` Directory: ${installDir}`);
|
|
|
|
|
|
console.log(` Version: ${manifest.version}`);
|
2025-06-14 15:06:41 -05:00
|
|
|
|
console.log(
|
|
|
|
|
|
` Installed: ${new Date(
|
|
|
|
|
|
manifest.installed_at
|
|
|
|
|
|
).toLocaleDateString()}`
|
|
|
|
|
|
);
|
2025-06-12 22:38:24 -05:00
|
|
|
|
console.log(` Type: ${manifest.install_type}`);
|
2025-06-14 15:06:41 -05:00
|
|
|
|
|
2025-06-12 22:38:24 -05:00
|
|
|
|
if (manifest.agent) {
|
|
|
|
|
|
console.log(` Agent: ${manifest.agent}`);
|
|
|
|
|
|
}
|
2025-06-14 15:06:41 -05:00
|
|
|
|
|
2025-06-12 22:38:24 -05:00
|
|
|
|
if (manifest.ide_setup) {
|
|
|
|
|
|
console.log(` IDE Setup: ${manifest.ide_setup}`);
|
|
|
|
|
|
}
|
2025-06-14 15:06:41 -05:00
|
|
|
|
|
2025-06-12 22:38:24 -05:00
|
|
|
|
console.log(` Total Files: ${manifest.files.length}`);
|
2025-06-14 15:06:41 -05:00
|
|
|
|
|
2025-06-12 22:38:24 -05:00
|
|
|
|
// Check for modifications
|
2025-06-14 15:06:41 -05:00
|
|
|
|
const modifiedFiles = await fileManager.checkModifiedFiles(
|
|
|
|
|
|
installDir,
|
|
|
|
|
|
manifest
|
|
|
|
|
|
);
|
2025-06-12 22:38:24 -05:00
|
|
|
|
if (modifiedFiles.length > 0) {
|
|
|
|
|
|
console.log(chalk.yellow(` Modified Files: ${modifiedFiles.length}`));
|
|
|
|
|
|
}
|
2025-06-14 15:06:41 -05:00
|
|
|
|
|
|
|
|
|
|
console.log("");
|
2025-06-12 22:38:24 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async getAvailableAgents() {
|
|
|
|
|
|
return configLoader.getAvailableAgents();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async findInstallation() {
|
2025-06-13 19:11:17 -05:00
|
|
|
|
// Look for .bmad-core in current directory or parent directories
|
2025-06-12 22:38:24 -05:00
|
|
|
|
let currentDir = process.cwd();
|
2025-06-14 15:06:41 -05:00
|
|
|
|
|
2025-06-12 22:38:24 -05:00
|
|
|
|
while (currentDir !== path.dirname(currentDir)) {
|
2025-06-14 15:06:41 -05:00
|
|
|
|
const bmadDir = path.join(currentDir, ".bmad-core");
|
|
|
|
|
|
const manifestPath = path.join(bmadDir, "install-manifest.yml");
|
|
|
|
|
|
|
2025-06-12 22:38:24 -05:00
|
|
|
|
if (await fileManager.pathExists(manifestPath)) {
|
|
|
|
|
|
return bmadDir;
|
|
|
|
|
|
}
|
2025-06-14 15:06:41 -05:00
|
|
|
|
|
2025-06-12 22:38:24 -05:00
|
|
|
|
currentDir = path.dirname(currentDir);
|
|
|
|
|
|
}
|
2025-06-14 15:06:41 -05:00
|
|
|
|
|
2025-06-13 19:11:17 -05:00
|
|
|
|
// Also check if we're inside a .bmad-core directory
|
2025-06-14 15:06:41 -05:00
|
|
|
|
if (path.basename(process.cwd()) === ".bmad-core") {
|
|
|
|
|
|
const manifestPath = path.join(process.cwd(), "install-manifest.yml");
|
2025-06-12 22:38:24 -05:00
|
|
|
|
if (await fileManager.pathExists(manifestPath)) {
|
|
|
|
|
|
return process.cwd();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-06-14 15:06:41 -05:00
|
|
|
|
|
2025-06-12 22:38:24 -05:00
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-14 15:06:41 -05:00
|
|
|
|
module.exports = new Installer();
|