1431 lines
51 KiB
JavaScript
Raw Normal View History

const chalk = require('chalk');
const inquirer = require('inquirer');
const path = require('node:path');
const os = require('node:os');
const fs = require('fs-extra');
const { CLIUtils } = require('./cli-utils');
const { CustomHandler } = require('../installers/lib/custom/handler');
/**
* UI utilities for the installer
*/
class UI {
constructor() {}
/**
* Prompt for installation configuration
* @returns {Object} Installation configuration
*/
async promptInstall() {
CLIUtils.displayLogo();
Major Enhancements: - Installation path is now fully configurable, allowing users to specify custom installation directories during setup - Default installation location changed to .bmad (hidden directory) for cleaner project root organization Web Bundle Improvements: - All web bundles (single agent and team) now include party mode support for multi-agent collaboration! - Advanced elicitation capabilities integrated into standalone agents - All bundles enhanced with party mode agent manifests - Added default-party.csv files to bmm, bmgd, and cis module teams - The default party file is what will be used with single agent bundles. teams can customize for different party configurations before web bundling through a setting in the team yaml file - New web bundle outputs for all agents (analyst, architect, dev, pm, sm, tea, tech-writer, ux-designer, game-*, creative-squad) Phase 4 Workflow Updates (In Progress): - Initiated shift to separate phase 4 implementation artifacts from documentation - Phase 4 implementation artifacts (stories, code review, sprint plan, context files) will move to dedicated location outside docs folder - Installer questions and configuration added for artifact path selection - Updated workflow.yaml files for code-review, sprint-planning, story-context, epic-tech-context, and retrospective workflows to support this, but still might require some udpates Additional Changes: - New agent and action command header models for standardization - Enhanced web-bundle-activation-steps fragment - Updated web-bundler.js to support new structure - VS Code settings updated for new .bmad directory - Party mode instructions and workflow enhanced for better orchestration IDE Installer Updates: - Show version number of installer in cli - improved Installer UX - Gemini TOML Improved to have clear loading instructions with @ commands - All tools agent launcher mds improved to use a central file template critical indication isntead of hardcoding in 2 different locations.
2025-11-09 17:39:05 -06:00
const version = CLIUtils.getVersion();
CLIUtils.displaySection('BMAD™ Setup', `Build More, Architect Dreams v${version}`);
const confirmedDirectory = await this.getConfirmedDirectory();
2025-10-02 21:45:59 -05:00
// Preflight: Check for legacy BMAD v4 footprints immediately after getting directory
const { Detector } = require('../installers/lib/core/detector');
const { Installer } = require('../installers/lib/core/installer');
const detector = new Detector();
const installer = new Installer();
const legacyV4 = await detector.detectLegacyV4(confirmedDirectory);
if (legacyV4.hasLegacyV4) {
await installer.handleLegacyV4Migration(confirmedDirectory, legacyV4);
}
2025-12-13 23:45:47 +08:00
// Check for legacy folders and prompt for rename before showing any menus
let hasLegacyCfg = false;
let hasLegacyBmadFolder = false;
let bmadDir = null;
let legacyBmadPath = null;
// First check for legacy .bmad folder (instead of _bmad)
// Only check if directory exists
if (await fs.pathExists(confirmedDirectory)) {
const entries = await fs.readdir(confirmedDirectory, { withFileTypes: true });
for (const entry of entries) {
if (entry.isDirectory() && entry.name === '.bmad') {
hasLegacyBmadFolder = true;
legacyBmadPath = path.join(confirmedDirectory, '.bmad');
bmadDir = legacyBmadPath;
// Check if it has _cfg folder
const cfgPath = path.join(legacyBmadPath, '_cfg');
if (await fs.pathExists(cfgPath)) {
hasLegacyCfg = true;
}
break;
2025-12-13 23:45:47 +08:00
}
}
}
// If no .bmad found, check for current installations
if (!hasLegacyBmadFolder) {
const bmadResult = await installer.findBmadDir(confirmedDirectory);
bmadDir = bmadResult.bmadDir;
hasLegacyCfg = bmadResult.hasLegacyCfg;
}
if (hasLegacyBmadFolder || hasLegacyCfg) {
console.log(chalk.yellow('\n⚠ Legacy folder structure detected'));
let message = 'The following folders need to be renamed:\n';
if (hasLegacyBmadFolder) {
message += chalk.dim(` • ".bmad" → "_bmad"\n`);
}
if (hasLegacyCfg) {
message += chalk.dim(` • "_cfg" → "_config"\n`);
}
console.log(message);
const { shouldRename } = await inquirer.prompt([
{
type: 'confirm',
name: 'shouldRename',
message: 'Would you like the installer to rename these folders for you?',
default: true,
},
]);
if (!shouldRename) {
console.log(chalk.red('\n❌ Installation cancelled'));
console.log(chalk.dim('You must manually rename the folders before proceeding:'));
if (hasLegacyBmadFolder) {
console.log(chalk.dim(` • Rename ".bmad" to "_bmad"`));
}
if (hasLegacyCfg) {
console.log(chalk.dim(` • Rename "_cfg" to "_config"`));
}
process.exit(0);
return;
}
// Perform the renames
const ora = require('ora');
const spinner = ora('Updating folder structure...').start();
try {
// First rename .bmad to _bmad if needed
if (hasLegacyBmadFolder) {
const newBmadPath = path.join(confirmedDirectory, '_bmad');
await fs.move(legacyBmadPath, newBmadPath);
bmadDir = newBmadPath;
spinner.succeed('Renamed ".bmad" to "_bmad"');
}
// Then rename _cfg to _config if needed
if (hasLegacyCfg) {
spinner.start('Renaming configuration folder...');
const oldCfgPath = path.join(bmadDir, '_cfg');
const newCfgPath = path.join(bmadDir, '_config');
await fs.move(oldCfgPath, newCfgPath);
spinner.succeed('Renamed "_cfg" to "_config"');
}
spinner.succeed('Folder structure updated successfully');
} catch (error) {
spinner.fail('Failed to update folder structure');
console.error(chalk.red(`Error: ${error.message}`));
process.exit(1);
}
}
// Check if there's an existing BMAD installation (after any folder renames)
2025-10-02 21:45:59 -05:00
const hasExistingInstall = await fs.pathExists(bmadDir);
// Collect IDE tool selection early - we need this to know if we should ask about TTS
let toolSelection;
let agentVibesConfig = { enabled: false, alreadyInstalled: false };
let claudeCodeSelected = false;
if (!hasExistingInstall) {
// For new installations, collect IDE selection first
// We don't have modules yet, so pass empty array
toolSelection = await this.promptToolSelection(confirmedDirectory, []);
// Check if Claude Code was selected
claudeCodeSelected = toolSelection.ides && toolSelection.ides.includes('claude-code');
// If Claude Code was selected, ask about TTS
if (claudeCodeSelected) {
const { enableTts } = await inquirer.prompt([
{
type: 'confirm',
name: 'enableTts',
message: 'Claude Code supports TTS (Text-to-Speech). Would you like to enable it?',
default: false,
},
]);
if (enableTts) {
agentVibesConfig = { enabled: true, alreadyInstalled: false };
}
}
}
2025-12-07 20:46:09 -06:00
// Always ask for custom content, but we'll handle it differently for new installs
2025-12-07 15:38:49 -06:00
let customContentConfig = { hasCustomContent: false };
2025-12-07 20:46:09 -06:00
if (hasExistingInstall) {
// Existing installation - prompt to add/update custom content
customContentConfig = await this.promptCustomContentForExisting();
} else {
// New installation - we'll prompt after creating the directory structure
// For now, set a flag to indicate we should ask later
customContentConfig._shouldAsk = true;
2025-12-07 15:38:49 -06:00
}
2025-10-28 21:44:04 -05:00
// Track action type (only set if there's an existing installation)
let actionType;
2025-10-02 21:45:59 -05:00
// Only show action menu if there's an existing installation
if (hasExistingInstall) {
2025-10-28 21:44:04 -05:00
const promptResult = await inquirer.prompt([
2025-10-02 21:45:59 -05:00
{
type: 'list',
name: 'actionType',
message: 'What would you like to do?',
choices: [
2025-10-26 16:17:37 -05:00
{ name: 'Quick Update (Settings Preserved)', value: 'quick-update' },
2025-10-28 12:47:45 -05:00
{ name: 'Modify BMAD Installation (Confirm or change each setting)', value: 'update' },
{ name: 'Add Custom Content', value: 'add-custom' },
2025-10-28 12:47:45 -05:00
{ name: 'Remove BMad Folder and Reinstall (Full clean install - BMad Customization Will Be Lost)', value: 'reinstall' },
2025-10-02 21:45:59 -05:00
{ name: 'Compile Agents (Quick rebuild of all agent .md files)', value: 'compile' },
2025-10-28 12:47:45 -05:00
{ name: 'Cancel', value: 'cancel' },
2025-10-02 21:45:59 -05:00
],
2025-10-26 16:17:37 -05:00
default: 'quick-update',
2025-10-02 21:45:59 -05:00
},
]);
2025-10-28 21:44:04 -05:00
// Extract actionType from prompt result
actionType = promptResult.actionType;
2025-10-26 16:17:37 -05:00
// Handle quick update separately
if (actionType === 'quick-update') {
2025-12-07 15:38:49 -06:00
// Quick update doesn't install custom content - just updates existing modules
2025-10-26 16:17:37 -05:00
return {
actionType: 'quick-update',
directory: confirmedDirectory,
2025-12-07 15:38:49 -06:00
customContent: { hasCustomContent: false },
2025-10-26 16:17:37 -05:00
};
}
// Handle add custom content separately
if (actionType === 'add-custom') {
customContentConfig = await this.promptCustomContentSource();
// After adding custom content, continue to select additional modules
const { installedModuleIds } = await this.getExistingInstallation(confirmedDirectory);
// Ask if user wants to add additional modules
const { wantsMoreModules } = await inquirer.prompt([
{
type: 'confirm',
name: 'wantsMoreModules',
message: 'Do you want to add any additional modules?',
default: false,
},
]);
let selectedModules = [];
if (wantsMoreModules) {
const moduleChoices = await this.getModuleChoices(installedModuleIds, customContentConfig);
selectedModules = await this.selectModules(moduleChoices);
// Process custom content selection
const selectedCustomContent = selectedModules.filter((mod) => mod.startsWith('__CUSTOM_CONTENT__'));
if (selectedCustomContent.length > 0) {
customContentConfig.selected = true;
customContentConfig.selectedFiles = selectedCustomContent.map((mod) => mod.replace('__CUSTOM_CONTENT__', ''));
// Convert to module IDs
const customContentModuleIds = [];
const customHandler = new CustomHandler();
for (const customFile of customContentConfig.selectedFiles) {
const customInfo = await customHandler.getCustomInfo(customFile);
if (customInfo) {
customContentModuleIds.push(customInfo.id);
}
}
selectedModules = [...selectedModules.filter((mod) => !mod.startsWith('__CUSTOM_CONTENT__')), ...customContentModuleIds];
}
}
return {
actionType: 'update',
directory: confirmedDirectory,
installCore: false, // Don't reinstall core
modules: selectedModules,
customContent: customContentConfig,
};
}
2025-10-02 21:45:59 -05:00
// Handle agent compilation separately
if (actionType === 'compile') {
return {
actionType: 'compile',
directory: confirmedDirectory,
};
}
2025-10-28 12:47:45 -05:00
// Handle cancel
if (actionType === 'cancel') {
return {
actionType: 'cancel',
directory: confirmedDirectory,
};
}
2025-10-28 21:44:04 -05:00
// Handle reinstall - DON'T return early, let it flow through configuration collection
// The installer will handle deletion when it sees actionType === 'reinstall'
// For now, just note that we're in reinstall mode and continue below
2025-10-28 12:47:45 -05:00
2025-10-28 21:44:04 -05:00
// If actionType === 'update' or 'reinstall', continue with normal flow below
2025-10-02 21:45:59 -05:00
}
2025-10-28 12:47:45 -05:00
// For new installations, ask about content types first
if (!hasExistingInstall) {
// Ask about official modules first
const { wantsOfficialModules } = await inquirer.prompt([
{
type: 'confirm',
name: 'wantsOfficialModules',
message: 'Will you be installing any official modules (BMad Method, BMad Builder, Creative Innovation Suite)?',
default: true,
},
]);
let selectedOfficialModules = [];
if (wantsOfficialModules) {
const { installedModuleIds } = await this.getExistingInstallation(confirmedDirectory);
const moduleChoices = await this.getModuleChoices(installedModuleIds, { hasCustomContent: false });
selectedOfficialModules = await this.selectModules(moduleChoices);
}
// Then ask about custom content
const { wantsCustomContent } = await inquirer.prompt([
{
type: 'confirm',
name: 'wantsCustomContent',
message: 'Will you be installing any locally stored custom content?',
default: false,
},
]);
if (wantsCustomContent) {
customContentConfig = await this.promptCustomContentSource();
}
// Store the selected modules for later
customContentConfig._selectedOfficialModules = selectedOfficialModules;
}
const { installedModuleIds } = await this.getExistingInstallation(confirmedDirectory);
// Collect core configuration first
const coreConfig = await this.collectCoreConfig(confirmedDirectory);
// Custom content will be handled during installation phase
// Store the custom content config for later use
if (customContentConfig._shouldAsk) {
2025-12-07 20:46:09 -06:00
delete customContentConfig._shouldAsk;
}
// Handle module selection
let selectedModules = [];
if (actionType === 'update' || actionType === 'reinstall') {
// Keep all existing installed modules during update/reinstall
selectedModules = [...installedModuleIds];
console.log(chalk.cyan('\n📦 Keeping existing modules: ') + selectedModules.join(', '));
} else if (!hasExistingInstall) {
// For new installs, we've already selected official modules
selectedModules = customContentConfig._selectedOfficialModules || [];
// Add custom content modules if any were selected
if (customContentConfig && customContentConfig.selectedModuleIds) {
selectedModules = [...selectedModules, ...customContentConfig.selectedModuleIds];
}
// Custom modules are already added via selectedModuleIds from customContentConfig
// No need for additional processing here
}
// AgentVibes TTS configuration already collected earlier for new installations
// For existing installations, keep the old behavior
if (hasExistingInstall && !agentVibesConfig.enabled) {
agentVibesConfig = await this.promptAgentVibes(confirmedDirectory);
}
Add Text-to-Speech Integration via TTS_INJECTION System (#934) * feat: Add provider-agnostic TTS integration via injection point system Implements comprehensive Text-to-Speech integration for BMAD agents using a generic TTS_INJECTION marker system. When AgentVibes (or any compatible TTS provider) is installed, all BMAD agents can speak their responses with unique AI voices. ## Key Features **Provider-Agnostic Architecture** - Uses generic `TTS_INJECTION` markers instead of vendor-specific naming - Future-proof for multiple TTS providers beyond AgentVibes - Clean separation - BMAD stays TTS-agnostic, providers handle injection **Installation Flow** - BMAD → AgentVibes: TTS instructions injected when AgentVibes detects existing BMAD installation - AgentVibes → BMAD: TTS instructions injected during BMAD installation when AgentVibes detected - User must manually create voice assignment file when AgentVibes installs first (documented limitation) **Party Mode Voice Support** - Each agent speaks with unique assigned voice in multi-agent discussions - PM, Architect, Developer, Analyst, UX Designer, etc. - all with distinct voices **Zero Breaking Changes** - Fully backward compatible - works without any TTS provider - `TTS_INJECTION` markers are benign HTML comments if not processed - No changes to existing agent behavior or non-TTS workflows ## Implementation Details **Files Modified:** - `tools/cli/installers/lib/core/installer.js` - TTS injection processing logic - `tools/cli/lib/ui.js` - AgentVibes detection and installation prompts - `tools/cli/commands/install.js` - Post-install guidance for AgentVibes setup - `src/utility/models/fragments/activation-rules.xml` - TTS_INJECTION marker for agents - `src/core/workflows/party-mode/instructions.md` - TTS_INJECTION marker for party mode **Injection Point System:** ```xml <rules> - ALWAYS communicate in {communication_language} <!-- TTS_INJECTION:agent-tts --> - Stay in character until exit selected </rules> ``` When AgentVibes is detected, the installer replaces this marker with: ``` - When responding to user messages, speak your responses using TTS: Call: `.claude/hooks/bmad-speak.sh '{agent-id}' '{response-text}'` after each response IMPORTANT: Use single quotes - do NOT escape special characters like ! or $ ``` **Special Character Handling:** - Explicit guidance to use single quotes without escaping - Prevents "backslash exclamation" artifacts in speech **User Experience:** ``` User: "How should we architect this feature?" Architect: [Text response] + 🔊 [Professional voice explains architecture] ``` Party Mode: ``` PM (John): "I'll focus on user value..." 🔊 [Male professional voice] UX Designer (Sara): "From a user perspective..." 🔊 [Female voice] Architect (Marcus): "The technical approach..." 🔊 [Male technical voice] ``` ## Testing **Unit Tests:** ✅ 62/62 passing - 49/49 schema validation tests - 13/13 installation component tests **Integration Testing:** - ✅ BMAD → AgentVibes (automatic injection) - ✅ AgentVibes → BMAD (automatic injection) - ✅ No TTS provider (markers remain as comments) ## Documentation Comprehensive testing guide created with: - Both installation scenario walkthroughs - Verification commands and expected outputs - Troubleshooting guidance ## Known Limitations **AgentVibes → BMAD Installation Order:** When AgentVibes installs first, voice assignment file must be created manually: ```bash mkdir -p .bmad/_cfg cat > .bmad/_cfg/agent-voice-map.csv << 'EOF' agent_id,voice_name pm,en_US-ryan-high architect,en_US-danny-low dev,en_US-joe-medium EOF ``` This limitation exists to prevent false legacy v4 detection warnings from BMAD installer. **Recommended:** Install BMAD first, then AgentVibes for automatic voice assignment. ## Related Work **Companion Implementation:** - Repository: paulpreibisch/AgentVibes - Commits: 6 commits implementing injection processing and voice routing - Features: Retroactive injection, file path extraction, escape stripping **GitHub Issues:** - paulpreibisch/AgentVibes#36 - BMAD agent ID support ## Breaking Changes None. Feature is opt-in and requires separate TTS provider installation. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: Enforce project hooks over global hooks in party mode before, claude would sometimes favor global agent vibes hooks over project specific * feat: Automate AgentVibes installer invocation after BMAD install Instead of showing manual installation instructions, the installer now: - Prompts "Press Enter to start AgentVibes installer..." - Automatically runs npx agentvibes@latest install - Handles errors gracefully with fallback instructions This provides a seamless installation flow matching the test script's interactive approach. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * docs: Add automated testing script and guide for PR #934 Added comprehensive testing tools for AgentVibes party mode integration: - test-bmad-pr.sh: Fully automated installation and verification script - Interactive mode selection (official PR or custom fork) - Automatic BMAD CLI setup and linking - AgentVibes installation with guided prompts - Built-in verification checks for voice maps and hooks - Saved configuration for quick re-testing - TESTING.md: Complete testing documentation - Quick start with one-line npx command - Manual installation alternative - Troubleshooting guide - Cleanup instructions Testers can now run a single command to test the full AgentVibes integration without needing to understand the complex setup process. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: Add shell: true to npx execSync to prevent permission denied error The execSync call for 'npx agentvibes@latest install' was failing with 'Permission denied' because the shell was trying to execute 'agentvibes@latest' directly instead of passing it as an argument to npx. Adding shell: true ensures the command runs in a proper shell context where npx can correctly interpret the @latest version syntax. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: Remove duplicate AgentVibes installation step from test script The test script was calling AgentVibes installer twice: 1. BMAD installer now automatically runs AgentVibes (new feature) 2. Test script had a separate Step 6 that also ran AgentVibes This caused the installer to run twice, with the second call failing because it was already installed. Changes: - Removed redundant Step 6 (AgentVibes installation) - Updated Step 5 to indicate it includes AgentVibes - Updated step numbers from 7 to 6 throughout - Added guidance that AgentVibes runs automatically Now the flow is cleaner: BMAD installer handles everything! 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: Address bmadcode review - preserve variables and move TTS logic to injection Fixes requested changes from PR review: 1. Preserve {bmad_folder} variable placeholder - Changed: {project_root}/.bmad/core/tasks/workflow.xml - To: {project_root}/{bmad_folder}/core/tasks/workflow.xml - Allows users to choose custom BMAD folder names during installation 2. Move TTS-specific hook guidance to injection system - Removed hardcoded hook enforcement from source files - Added hook guidance to processTTSInjectionPoints() in installer.js - Now only appears when AgentVibes is installed (via TTS_INJECTION) 3. Maintain TTS-agnostic source architecture - Source files remain clean of TTS-specific instructions - TTS details injected at install-time only when needed - Preserves provider-agnostic design principle Changes made: - src/core/workflows/party-mode/instructions.md - Reverted .bmad to {bmad_folder} variable - Replaced hardcoded hook guidance with <!-- TTS_INJECTION:party-mode --> - Removed <note> about play-tts.sh hook location - tools/cli/installers/lib/core/installer.js - Added hook enforcement to party-mode injection replacement - Guidance now injected only when enableAgentVibes is true Addresses review comments from bmadcode: - "needs to remain the variable. it will get set in the file at the install destination." - "items like this we will need to inject if user is using claude and TTS" 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: Change 'claude-code' to 'claude' in test script instructions The correct command to start Claude is 'claude', not 'claude-code'. Updated line 362-363 in test-bmad-pr.sh to show the correct command. * fix: Remove npm link from test script to avoid global namespace pollution - Removed 'npm link' command that was installing BMAD globally - Changed 'bmad install' to direct node execution using local clone - Updated success message to reflect no global installation This keeps testing fully isolated and prevents conflicts with: - Existing BMAD installations - Future official BMAD installs - Orphaned symlinks when test directory is deleted The test script now runs completely self-contained without modifying the user's global npm environment. --------- Co-authored-by: Claude Code <claude@anthropic.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Paul Preibisch <paul@paulpreibisch.com> Co-authored-by: Brian <bmadcode@gmail.com>
2025-11-26 08:51:57 -07:00
// Tool selection already collected for new installations
// For existing installations, we need to collect it now
if (hasExistingInstall && !toolSelection) {
const modulesForToolSelection = selectedModules;
toolSelection = await this.promptToolSelection(confirmedDirectory, modulesForToolSelection);
}
2025-11-04 21:58:41 -06:00
Major Enhancements: - Installation path is now fully configurable, allowing users to specify custom installation directories during setup - Default installation location changed to .bmad (hidden directory) for cleaner project root organization Web Bundle Improvements: - All web bundles (single agent and team) now include party mode support for multi-agent collaboration! - Advanced elicitation capabilities integrated into standalone agents - All bundles enhanced with party mode agent manifests - Added default-party.csv files to bmm, bmgd, and cis module teams - The default party file is what will be used with single agent bundles. teams can customize for different party configurations before web bundling through a setting in the team yaml file - New web bundle outputs for all agents (analyst, architect, dev, pm, sm, tea, tech-writer, ux-designer, game-*, creative-squad) Phase 4 Workflow Updates (In Progress): - Initiated shift to separate phase 4 implementation artifacts from documentation - Phase 4 implementation artifacts (stories, code review, sprint plan, context files) will move to dedicated location outside docs folder - Installer questions and configuration added for artifact path selection - Updated workflow.yaml files for code-review, sprint-planning, story-context, epic-tech-context, and retrospective workflows to support this, but still might require some udpates Additional Changes: - New agent and action command header models for standardization - Enhanced web-bundle-activation-steps fragment - Updated web-bundler.js to support new structure - VS Code settings updated for new .bmad directory - Party mode instructions and workflow enhanced for better orchestration IDE Installer Updates: - Show version number of installer in cli - improved Installer UX - Gemini TOML Improved to have clear loading instructions with @ commands - All tools agent launcher mds improved to use a central file template critical indication isntead of hardcoding in 2 different locations.
2025-11-09 17:39:05 -06:00
// No more screen clearing - keep output flowing
return {
2025-10-28 21:44:04 -05:00
actionType: actionType || 'update', // Preserve reinstall or update action
directory: confirmedDirectory,
installCore: true, // Always install core
modules: selectedModules,
2025-11-04 21:58:41 -06:00
// IDE selection collected after config, will be configured later
2025-10-28 12:47:45 -05:00
ides: toolSelection.ides,
skipIde: toolSelection.skipIde,
coreConfig: coreConfig, // Pass collected core config to installer
// Custom content configuration
customContent: customContentConfig,
enableAgentVibes: agentVibesConfig.enabled,
Add Text-to-Speech Integration via TTS_INJECTION System (#934) * feat: Add provider-agnostic TTS integration via injection point system Implements comprehensive Text-to-Speech integration for BMAD agents using a generic TTS_INJECTION marker system. When AgentVibes (or any compatible TTS provider) is installed, all BMAD agents can speak their responses with unique AI voices. ## Key Features **Provider-Agnostic Architecture** - Uses generic `TTS_INJECTION` markers instead of vendor-specific naming - Future-proof for multiple TTS providers beyond AgentVibes - Clean separation - BMAD stays TTS-agnostic, providers handle injection **Installation Flow** - BMAD → AgentVibes: TTS instructions injected when AgentVibes detects existing BMAD installation - AgentVibes → BMAD: TTS instructions injected during BMAD installation when AgentVibes detected - User must manually create voice assignment file when AgentVibes installs first (documented limitation) **Party Mode Voice Support** - Each agent speaks with unique assigned voice in multi-agent discussions - PM, Architect, Developer, Analyst, UX Designer, etc. - all with distinct voices **Zero Breaking Changes** - Fully backward compatible - works without any TTS provider - `TTS_INJECTION` markers are benign HTML comments if not processed - No changes to existing agent behavior or non-TTS workflows ## Implementation Details **Files Modified:** - `tools/cli/installers/lib/core/installer.js` - TTS injection processing logic - `tools/cli/lib/ui.js` - AgentVibes detection and installation prompts - `tools/cli/commands/install.js` - Post-install guidance for AgentVibes setup - `src/utility/models/fragments/activation-rules.xml` - TTS_INJECTION marker for agents - `src/core/workflows/party-mode/instructions.md` - TTS_INJECTION marker for party mode **Injection Point System:** ```xml <rules> - ALWAYS communicate in {communication_language} <!-- TTS_INJECTION:agent-tts --> - Stay in character until exit selected </rules> ``` When AgentVibes is detected, the installer replaces this marker with: ``` - When responding to user messages, speak your responses using TTS: Call: `.claude/hooks/bmad-speak.sh '{agent-id}' '{response-text}'` after each response IMPORTANT: Use single quotes - do NOT escape special characters like ! or $ ``` **Special Character Handling:** - Explicit guidance to use single quotes without escaping - Prevents "backslash exclamation" artifacts in speech **User Experience:** ``` User: "How should we architect this feature?" Architect: [Text response] + 🔊 [Professional voice explains architecture] ``` Party Mode: ``` PM (John): "I'll focus on user value..." 🔊 [Male professional voice] UX Designer (Sara): "From a user perspective..." 🔊 [Female voice] Architect (Marcus): "The technical approach..." 🔊 [Male technical voice] ``` ## Testing **Unit Tests:** ✅ 62/62 passing - 49/49 schema validation tests - 13/13 installation component tests **Integration Testing:** - ✅ BMAD → AgentVibes (automatic injection) - ✅ AgentVibes → BMAD (automatic injection) - ✅ No TTS provider (markers remain as comments) ## Documentation Comprehensive testing guide created with: - Both installation scenario walkthroughs - Verification commands and expected outputs - Troubleshooting guidance ## Known Limitations **AgentVibes → BMAD Installation Order:** When AgentVibes installs first, voice assignment file must be created manually: ```bash mkdir -p .bmad/_cfg cat > .bmad/_cfg/agent-voice-map.csv << 'EOF' agent_id,voice_name pm,en_US-ryan-high architect,en_US-danny-low dev,en_US-joe-medium EOF ``` This limitation exists to prevent false legacy v4 detection warnings from BMAD installer. **Recommended:** Install BMAD first, then AgentVibes for automatic voice assignment. ## Related Work **Companion Implementation:** - Repository: paulpreibisch/AgentVibes - Commits: 6 commits implementing injection processing and voice routing - Features: Retroactive injection, file path extraction, escape stripping **GitHub Issues:** - paulpreibisch/AgentVibes#36 - BMAD agent ID support ## Breaking Changes None. Feature is opt-in and requires separate TTS provider installation. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: Enforce project hooks over global hooks in party mode before, claude would sometimes favor global agent vibes hooks over project specific * feat: Automate AgentVibes installer invocation after BMAD install Instead of showing manual installation instructions, the installer now: - Prompts "Press Enter to start AgentVibes installer..." - Automatically runs npx agentvibes@latest install - Handles errors gracefully with fallback instructions This provides a seamless installation flow matching the test script's interactive approach. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * docs: Add automated testing script and guide for PR #934 Added comprehensive testing tools for AgentVibes party mode integration: - test-bmad-pr.sh: Fully automated installation and verification script - Interactive mode selection (official PR or custom fork) - Automatic BMAD CLI setup and linking - AgentVibes installation with guided prompts - Built-in verification checks for voice maps and hooks - Saved configuration for quick re-testing - TESTING.md: Complete testing documentation - Quick start with one-line npx command - Manual installation alternative - Troubleshooting guide - Cleanup instructions Testers can now run a single command to test the full AgentVibes integration without needing to understand the complex setup process. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: Add shell: true to npx execSync to prevent permission denied error The execSync call for 'npx agentvibes@latest install' was failing with 'Permission denied' because the shell was trying to execute 'agentvibes@latest' directly instead of passing it as an argument to npx. Adding shell: true ensures the command runs in a proper shell context where npx can correctly interpret the @latest version syntax. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: Remove duplicate AgentVibes installation step from test script The test script was calling AgentVibes installer twice: 1. BMAD installer now automatically runs AgentVibes (new feature) 2. Test script had a separate Step 6 that also ran AgentVibes This caused the installer to run twice, with the second call failing because it was already installed. Changes: - Removed redundant Step 6 (AgentVibes installation) - Updated Step 5 to indicate it includes AgentVibes - Updated step numbers from 7 to 6 throughout - Added guidance that AgentVibes runs automatically Now the flow is cleaner: BMAD installer handles everything! 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: Address bmadcode review - preserve variables and move TTS logic to injection Fixes requested changes from PR review: 1. Preserve {bmad_folder} variable placeholder - Changed: {project_root}/.bmad/core/tasks/workflow.xml - To: {project_root}/{bmad_folder}/core/tasks/workflow.xml - Allows users to choose custom BMAD folder names during installation 2. Move TTS-specific hook guidance to injection system - Removed hardcoded hook enforcement from source files - Added hook guidance to processTTSInjectionPoints() in installer.js - Now only appears when AgentVibes is installed (via TTS_INJECTION) 3. Maintain TTS-agnostic source architecture - Source files remain clean of TTS-specific instructions - TTS details injected at install-time only when needed - Preserves provider-agnostic design principle Changes made: - src/core/workflows/party-mode/instructions.md - Reverted .bmad to {bmad_folder} variable - Replaced hardcoded hook guidance with <!-- TTS_INJECTION:party-mode --> - Removed <note> about play-tts.sh hook location - tools/cli/installers/lib/core/installer.js - Added hook enforcement to party-mode injection replacement - Guidance now injected only when enableAgentVibes is true Addresses review comments from bmadcode: - "needs to remain the variable. it will get set in the file at the install destination." - "items like this we will need to inject if user is using claude and TTS" 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: Change 'claude-code' to 'claude' in test script instructions The correct command to start Claude is 'claude', not 'claude-code'. Updated line 362-363 in test-bmad-pr.sh to show the correct command. * fix: Remove npm link from test script to avoid global namespace pollution - Removed 'npm link' command that was installing BMAD globally - Changed 'bmad install' to direct node execution using local clone - Updated success message to reflect no global installation This keeps testing fully isolated and prevents conflicts with: - Existing BMAD installations - Future official BMAD installs - Orphaned symlinks when test directory is deleted The test script now runs completely self-contained without modifying the user's global npm environment. --------- Co-authored-by: Claude Code <claude@anthropic.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Paul Preibisch <paul@paulpreibisch.com> Co-authored-by: Brian <bmadcode@gmail.com>
2025-11-26 08:51:57 -07:00
agentVibesInstalled: agentVibesConfig.alreadyInstalled,
};
}
/**
* Prompt for tool/IDE selection (called after module configuration)
* @param {string} projectDir - Project directory to check for existing IDEs
* @param {Array} selectedModules - Selected modules from configuration
* @returns {Object} Tool configuration
*/
async promptToolSelection(projectDir, selectedModules) {
// Check for existing configured IDEs - use findBmadDir to detect custom folder names
const { Detector } = require('../installers/lib/core/detector');
const { Installer } = require('../installers/lib/core/installer');
const detector = new Detector();
const installer = new Installer();
2025-12-13 23:45:47 +08:00
const bmadResult = await installer.findBmadDir(projectDir || process.cwd());
const bmadDir = bmadResult.bmadDir;
const existingInstall = await detector.detect(bmadDir);
const configuredIdes = existingInstall.ides || [];
// Get IDE manager to fetch available IDEs dynamically
const { IdeManager } = require('../installers/lib/ide/manager');
const ideManager = new IdeManager();
const preferredIdes = ideManager.getPreferredIdes();
const otherIdes = ideManager.getOtherIdes();
// Build IDE choices array with separators
const ideChoices = [];
const processedIdes = new Set();
// First, add previously configured IDEs at the top, marked with ✅
if (configuredIdes.length > 0) {
ideChoices.push(new inquirer.Separator('── Previously Configured ──'));
for (const ideValue of configuredIdes) {
2025-10-26 19:38:38 -05:00
// Skip empty or invalid IDE values
if (!ideValue || typeof ideValue !== 'string') {
continue;
}
// Find the IDE in either preferred or other lists
const preferredIde = preferredIdes.find((ide) => ide.value === ideValue);
const otherIde = otherIdes.find((ide) => ide.value === ideValue);
const ide = preferredIde || otherIde;
if (ide) {
ideChoices.push({
name: `${ide.name}`,
value: ide.value,
checked: true, // Previously configured IDEs are checked by default
});
processedIdes.add(ide.value);
2025-10-26 19:38:38 -05:00
} else {
// Warn about unrecognized IDE (but don't fail)
console.log(chalk.yellow(`⚠️ Previously configured IDE '${ideValue}' is no longer available`));
}
}
}
// Add preferred tools (excluding already processed)
const remainingPreferred = preferredIdes.filter((ide) => !processedIdes.has(ide.value));
if (remainingPreferred.length > 0) {
ideChoices.push(new inquirer.Separator('── Recommended Tools ──'));
for (const ide of remainingPreferred) {
ideChoices.push({
name: `${ide.name}`,
value: ide.value,
checked: false,
});
processedIdes.add(ide.value);
}
}
// Add other tools (excluding already processed)
const remainingOther = otherIdes.filter((ide) => !processedIdes.has(ide.value));
if (remainingOther.length > 0) {
ideChoices.push(new inquirer.Separator('── Additional Tools ──'));
for (const ide of remainingOther) {
ideChoices.push({
name: ide.name,
value: ide.value,
checked: false,
});
}
}
let answers;
let userConfirmedNoTools = false;
// Loop until user selects at least one tool OR explicitly confirms no tools
while (!userConfirmedNoTools) {
answers = await inquirer.prompt([
{
type: 'checkbox',
name: 'ides',
message: 'Select tools to configure:',
choices: ideChoices,
pageSize: 30,
},
]);
// If tools were selected, we're done
if (answers.ides && answers.ides.length > 0) {
break;
}
// Warn that no tools were selected - users often miss the spacebar requirement
console.log();
console.log(chalk.red.bold('⚠️ WARNING: No tools were selected!'));
console.log(chalk.red(' You must press SPACEBAR to select items, then ENTER to confirm.'));
console.log(chalk.red(' Simply highlighting an item does NOT select it.'));
console.log();
const { goBack } = await inquirer.prompt([
{
type: 'confirm',
name: 'goBack',
message: chalk.yellow('Would you like to go back and select at least one tool?'),
default: true,
},
]);
if (goBack) {
// Re-display a message before looping back
console.log();
} else {
// User explicitly chose to proceed without tools
userConfirmedNoTools = true;
}
}
return {
ides: answers.ides || [],
skipIde: !answers.ides || answers.ides.length === 0,
};
}
/**
* Prompt for update configuration
* @returns {Object} Update configuration
*/
async promptUpdate() {
const answers = await inquirer.prompt([
{
type: 'confirm',
name: 'backupFirst',
message: 'Create backup before updating?',
default: true,
},
{
type: 'confirm',
name: 'preserveCustomizations',
message: 'Preserve local customizations?',
default: true,
},
]);
return answers;
}
/**
* Prompt for module selection
* @param {Array} modules - Available modules
* @returns {Array} Selected modules
*/
async promptModules(modules) {
const choices = modules.map((mod) => ({
name: `${mod.name} - ${mod.description}`,
value: mod.id,
checked: false,
}));
const { selectedModules } = await inquirer.prompt([
{
type: 'checkbox',
name: 'selectedModules',
message: 'Select modules to add:',
choices,
validate: (answer) => {
if (answer.length === 0) {
return 'You must choose at least one module.';
}
return true;
},
},
]);
return selectedModules;
}
/**
* Confirm action
* @param {string} message - Confirmation message
* @param {boolean} defaultValue - Default value
* @returns {boolean} User confirmation
*/
async confirm(message, defaultValue = false) {
const { confirmed } = await inquirer.prompt([
{
type: 'confirm',
name: 'confirmed',
message,
default: defaultValue,
},
]);
return confirmed;
}
/**
* Display installation summary
* @param {Object} result - Installation result
*/
showInstallSummary(result) {
CLIUtils.displaySection('Installation Complete', 'BMAD™ has been successfully installed');
const summary = [
`📁 Installation Path: ${result.path}`,
`📦 Modules Installed: ${result.modules?.length > 0 ? result.modules.join(', ') : 'core only'}`,
`🔧 Tools Configured: ${result.ides?.length > 0 ? result.ides.join(', ') : 'none'}`,
];
feat(installer): Enhanced TTS injection summary with tracking and documentation (#1037) ## Summary - Track all files with TTS injection applied during installation - Display informative summary explaining what TTS injection does - Show backup location and restore command for recovery ## What is TTS Injection? TTS (Text-to-Speech) injection adds voice instructions to BMAD agents, enabling them to speak their responses aloud using AgentVibes. Example: When you activate the PM agent, it will greet you with spoken audio like "Hey! I'm your Project Manager. How can I help?" ## Changes - **installer.js**: Track files in `processAgentFiles()`, `buildStandaloneAgents()`, and `rebuildAgentFiles()` when TTS markers are processed - **compiler.js**: Add TTS injection support for custom agent compilation - **ui.js**: Enhanced installation summary showing: - Explanation of what TTS injection is with example - List of all files with TTS injection applied (grouped by type) - Backup location (~/.bmad-tts-backups/) - Restore command for recovery ## Example Output ``` ═══════════════════════════════════════════════════ AgentVibes TTS Injection Summary ═══════════════════════════════════════════════════ What is TTS Injection? TTS (Text-to-Speech) injection adds voice instructions to BMAD agents, enabling them to speak their responses aloud using AgentVibes. Example: When you activate the PM agent, it will greet you with spoken audio like "Hey! I'm your Project Manager. How can I help?" ✅ TTS injection applied to 11 file(s): Party Mode (multi-agent conversations): • .bmad/core/workflows/party-mode/instructions.md Agent TTS (individual agent voices): • .bmad/bmm/agents/analyst.md • .bmad/bmm/agents/architect.md ... Backups & Recovery: Pre-injection backups are stored in: ~/.bmad-tts-backups/ To restore original files (removes TTS instructions): bmad-tts-injector.sh --restore /path/to/.bmad ``` 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Paul Preibisch <paul@paulpreibisch.com> Co-authored-by: Claude <noreply@anthropic.com>
2025-12-05 17:54:03 -07:00
// Add AgentVibes TTS info if enabled
if (result.agentVibesEnabled) {
summary.push(`🎤 AgentVibes TTS: Enabled`);
}
CLIUtils.displayBox(summary.join('\n\n'), {
borderColor: 'green',
borderStyle: 'round',
});
feat(installer): Enhanced TTS injection summary with tracking and documentation (#1037) ## Summary - Track all files with TTS injection applied during installation - Display informative summary explaining what TTS injection does - Show backup location and restore command for recovery ## What is TTS Injection? TTS (Text-to-Speech) injection adds voice instructions to BMAD agents, enabling them to speak their responses aloud using AgentVibes. Example: When you activate the PM agent, it will greet you with spoken audio like "Hey! I'm your Project Manager. How can I help?" ## Changes - **installer.js**: Track files in `processAgentFiles()`, `buildStandaloneAgents()`, and `rebuildAgentFiles()` when TTS markers are processed - **compiler.js**: Add TTS injection support for custom agent compilation - **ui.js**: Enhanced installation summary showing: - Explanation of what TTS injection is with example - List of all files with TTS injection applied (grouped by type) - Backup location (~/.bmad-tts-backups/) - Restore command for recovery ## Example Output ``` ═══════════════════════════════════════════════════ AgentVibes TTS Injection Summary ═══════════════════════════════════════════════════ What is TTS Injection? TTS (Text-to-Speech) injection adds voice instructions to BMAD agents, enabling them to speak their responses aloud using AgentVibes. Example: When you activate the PM agent, it will greet you with spoken audio like "Hey! I'm your Project Manager. How can I help?" ✅ TTS injection applied to 11 file(s): Party Mode (multi-agent conversations): • .bmad/core/workflows/party-mode/instructions.md Agent TTS (individual agent voices): • .bmad/bmm/agents/analyst.md • .bmad/bmm/agents/architect.md ... Backups & Recovery: Pre-injection backups are stored in: ~/.bmad-tts-backups/ To restore original files (removes TTS instructions): bmad-tts-injector.sh --restore /path/to/.bmad ``` 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Paul Preibisch <paul@paulpreibisch.com> Co-authored-by: Claude <noreply@anthropic.com>
2025-12-05 17:54:03 -07:00
// Display TTS injection details if present
if (result.ttsInjectedFiles && result.ttsInjectedFiles.length > 0) {
console.log('\n' + chalk.cyan.bold('═══════════════════════════════════════════════════'));
console.log(chalk.cyan.bold(' AgentVibes TTS Injection Summary'));
console.log(chalk.cyan.bold('═══════════════════════════════════════════════════\n'));
// Explain what TTS injection is
console.log(chalk.white.bold('What is TTS Injection?\n'));
console.log(chalk.dim(' TTS (Text-to-Speech) injection adds voice instructions to BMAD agents,'));
console.log(chalk.dim(' enabling them to speak their responses aloud using AgentVibes.\n'));
console.log(chalk.dim(' Example: When you activate the PM agent, it will greet you with'));
console.log(chalk.dim(' spoken audio like "Hey! I\'m your Project Manager. How can I help?"\n'));
console.log(chalk.green(`✅ TTS injection applied to ${result.ttsInjectedFiles.length} file(s):\n`));
// Group by type
const partyModeFiles = result.ttsInjectedFiles.filter((f) => f.type === 'party-mode');
const agentTTSFiles = result.ttsInjectedFiles.filter((f) => f.type === 'agent-tts');
if (partyModeFiles.length > 0) {
console.log(chalk.yellow(' Party Mode (multi-agent conversations):'));
for (const file of partyModeFiles) {
console.log(chalk.dim(`${file.path}`));
}
}
if (agentTTSFiles.length > 0) {
console.log(chalk.yellow(' Agent TTS (individual agent voices):'));
for (const file of agentTTSFiles) {
console.log(chalk.dim(`${file.path}`));
}
}
// Show backup info and restore command
console.log('\n' + chalk.white.bold('Backups & Recovery:\n'));
console.log(chalk.dim(' Pre-injection backups are stored in:'));
2025-12-13 16:22:34 +08:00
console.log(chalk.cyan(' ~/_bmad-tts-backups/\n'));
feat(installer): Enhanced TTS injection summary with tracking and documentation (#1037) ## Summary - Track all files with TTS injection applied during installation - Display informative summary explaining what TTS injection does - Show backup location and restore command for recovery ## What is TTS Injection? TTS (Text-to-Speech) injection adds voice instructions to BMAD agents, enabling them to speak their responses aloud using AgentVibes. Example: When you activate the PM agent, it will greet you with spoken audio like "Hey! I'm your Project Manager. How can I help?" ## Changes - **installer.js**: Track files in `processAgentFiles()`, `buildStandaloneAgents()`, and `rebuildAgentFiles()` when TTS markers are processed - **compiler.js**: Add TTS injection support for custom agent compilation - **ui.js**: Enhanced installation summary showing: - Explanation of what TTS injection is with example - List of all files with TTS injection applied (grouped by type) - Backup location (~/.bmad-tts-backups/) - Restore command for recovery ## Example Output ``` ═══════════════════════════════════════════════════ AgentVibes TTS Injection Summary ═══════════════════════════════════════════════════ What is TTS Injection? TTS (Text-to-Speech) injection adds voice instructions to BMAD agents, enabling them to speak their responses aloud using AgentVibes. Example: When you activate the PM agent, it will greet you with spoken audio like "Hey! I'm your Project Manager. How can I help?" ✅ TTS injection applied to 11 file(s): Party Mode (multi-agent conversations): • .bmad/core/workflows/party-mode/instructions.md Agent TTS (individual agent voices): • .bmad/bmm/agents/analyst.md • .bmad/bmm/agents/architect.md ... Backups & Recovery: Pre-injection backups are stored in: ~/.bmad-tts-backups/ To restore original files (removes TTS instructions): bmad-tts-injector.sh --restore /path/to/.bmad ``` 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Paul Preibisch <paul@paulpreibisch.com> Co-authored-by: Claude <noreply@anthropic.com>
2025-12-05 17:54:03 -07:00
console.log(chalk.dim(' To restore original files (removes TTS instructions):'));
console.log(chalk.cyan(` bmad-tts-injector.sh --restore ${result.path}\n`));
console.log(chalk.cyan('💡 BMAD agents will now speak when activated!'));
console.log(chalk.dim(' Ensure AgentVibes is installed: https://agentvibes.org'));
}
console.log('\n' + chalk.green.bold('✨ BMAD is ready to use!'));
}
/**
* Get confirmed directory from user
* @returns {string} Confirmed directory path
*/
async getConfirmedDirectory() {
let confirmedDirectory = null;
while (!confirmedDirectory) {
const directoryAnswer = await this.promptForDirectory();
await this.displayDirectoryInfo(directoryAnswer.directory);
if (await this.confirmDirectory(directoryAnswer.directory)) {
confirmedDirectory = directoryAnswer.directory;
}
}
return confirmedDirectory;
}
/**
* Get existing installation info and installed modules
* @param {string} directory - Installation directory
* @returns {Object} Object with existingInstall and installedModuleIds
*/
async getExistingInstallation(directory) {
const { Detector } = require('../installers/lib/core/detector');
const { Installer } = require('../installers/lib/core/installer');
const detector = new Detector();
const installer = new Installer();
const bmadDir = await installer.findBmadDir(directory);
const existingInstall = await detector.detect(bmadDir);
const installedModuleIds = new Set(existingInstall.modules.map((mod) => mod.id));
return { existingInstall, installedModuleIds };
}
/**
* Collect core configuration
* @param {string} directory - Installation directory
* @returns {Object} Core configuration
*/
async collectCoreConfig(directory) {
const { ConfigCollector } = require('../installers/lib/core/config-collector');
const configCollector = new ConfigCollector();
// Load existing configs first if they exist
await configCollector.loadExistingConfig(directory);
// Now collect with existing values as defaults (false = don't skip loading, true = skip completion message)
await configCollector.collectModuleConfig('core', directory, false, true);
return configCollector.collectedConfig.core;
}
/**
* Get module choices for selection
* @param {Set} installedModuleIds - Currently installed module IDs
* @param {Object} customContentConfig - Custom content configuration
* @returns {Array} Module choices for inquirer
*/
async getModuleChoices(installedModuleIds, customContentConfig = null) {
const moduleChoices = [];
const isNewInstallation = installedModuleIds.size === 0;
2025-12-07 15:38:49 -06:00
const customContentItems = [];
const hasCustomContentItems = false;
2025-12-07 20:46:09 -06:00
// Add custom content items
if (customContentConfig && customContentConfig.hasCustomContent && customContentConfig.customPath) {
// Existing installation - show from directory
const customHandler = new CustomHandler();
const customFiles = await customHandler.findCustomContent(customContentConfig.customPath);
for (const customFile of customFiles) {
const customInfo = await customHandler.getCustomInfo(customFile);
if (customInfo) {
customContentItems.push({
name: `${chalk.cyan('✓')} ${customInfo.name} ${chalk.gray(`(${customInfo.relativePath})`)}`,
value: `__CUSTOM_CONTENT__${customFile}`, // Unique value for each custom content
checked: true, // Default to selected since user chose to provide custom content
path: customInfo.path, // Track path to avoid duplicates
});
}
}
}
// Add official modules
const { ModuleManager } = require('../installers/lib/modules/manager');
2025-12-07 20:46:09 -06:00
// For new installations, don't scan project yet (will do after custom content is discovered)
// For existing installations, scan if user selected custom content
const shouldScanProject =
!isNewInstallation && customContentConfig && customContentConfig.hasCustomContent && customContentConfig.selected;
2025-12-07 15:38:49 -06:00
const moduleManager = new ModuleManager({
2025-12-07 20:46:09 -06:00
scanProjectForModules: shouldScanProject,
2025-12-07 15:38:49 -06:00
});
const { modules: availableModules, customModules: customModulesFromProject } = await moduleManager.listAvailable();
// First, add all items to appropriate sections
const allCustomModules = [];
// Add custom content items from directory
allCustomModules.push(...customContentItems);
2025-12-07 15:38:49 -06:00
// Add custom modules from project scan (if scanning is enabled)
for (const mod of customModulesFromProject) {
// Skip if this module is already in customContentItems (by path)
const isDuplicate = allCustomModules.some((item) => item.path && mod.path && path.resolve(item.path) === path.resolve(mod.path));
if (!isDuplicate) {
allCustomModules.push({
name: `${chalk.cyan('✓')} ${mod.name} ${chalk.gray(`(${mod.source})`)}`,
value: mod.id,
checked: isNewInstallation ? mod.defaultSelected || false : installedModuleIds.has(mod.id),
});
}
}
// Add separators and modules in correct order
if (allCustomModules.length > 0) {
// Add separator for custom content, all custom modules, and official content separator
moduleChoices.push(
new inquirer.Separator('── Custom Content ──'),
...allCustomModules,
new inquirer.Separator('── Official Content ──'),
);
}
// Add official modules (only non-custom ones)
for (const mod of availableModules) {
2025-12-07 15:38:49 -06:00
if (!mod.isCustom) {
moduleChoices.push({
name: mod.name,
value: mod.id,
checked: isNewInstallation ? mod.defaultSelected || false : installedModuleIds.has(mod.id),
});
}
}
return moduleChoices;
}
/**
* Prompt for module selection
* @param {Array} moduleChoices - Available module choices
* @returns {Array} Selected module IDs
*/
async selectModules(moduleChoices) {
const moduleAnswer = await inquirer.prompt([
{
type: 'checkbox',
name: 'modules',
message: 'Select modules to install:',
choices: moduleChoices,
},
]);
return moduleAnswer.modules || [];
}
/**
* Prompt for directory selection
* @returns {Object} Directory answer from inquirer
*/
async promptForDirectory() {
return await inquirer.prompt([
{
type: 'input',
name: 'directory',
message: `Installation directory:`,
default: process.cwd(),
validate: async (input) => this.validateDirectory(input),
filter: (input) => {
// If empty, use the default
if (!input || input.trim() === '') {
return process.cwd();
}
return this.expandUserPath(input);
},
},
]);
}
/**
* Display directory information
* @param {string} directory - The directory path
*/
async displayDirectoryInfo(directory) {
console.log(chalk.cyan('\nResolved installation path:'), chalk.bold(directory));
const dirExists = await fs.pathExists(directory);
if (dirExists) {
// Show helpful context about the existing path
const stats = await fs.stat(directory);
if (stats.isDirectory()) {
const files = await fs.readdir(directory);
if (files.length > 0) {
2025-12-13 19:41:09 +08:00
// Check for any bmad installation (any folder with _config/manifest.yaml)
const { Installer } = require('../installers/lib/core/installer');
const installer = new Installer();
const bmadDir = await installer.findBmadDir(directory);
2025-12-13 19:41:09 +08:00
const hasBmadInstall = (await fs.pathExists(bmadDir)) && (await fs.pathExists(path.join(bmadDir, '_config', 'manifest.yaml')));
console.log(
chalk.gray(`Directory exists and contains ${files.length} item(s)`) +
(hasBmadInstall ? chalk.yellow(` including existing BMAD installation (${path.basename(bmadDir)})`) : ''),
);
} else {
console.log(chalk.gray('Directory exists and is empty'));
}
}
}
}
/**
* Confirm directory selection
* @param {string} directory - The directory path
* @returns {boolean} Whether user confirmed
*/
async confirmDirectory(directory) {
const dirExists = await fs.pathExists(directory);
if (dirExists) {
const confirmAnswer = await inquirer.prompt([
{
type: 'confirm',
name: 'proceed',
message: `Install to this directory?`,
default: true,
},
]);
if (!confirmAnswer.proceed) {
console.log(chalk.yellow("\nLet's try again with a different path.\n"));
}
return confirmAnswer.proceed;
} else {
// Ask for confirmation to create the directory
const createConfirm = await inquirer.prompt([
{
type: 'confirm',
name: 'create',
message: `The directory '${directory}' doesn't exist. Would you like to create it?`,
default: false,
},
]);
if (!createConfirm.create) {
console.log(chalk.yellow("\nLet's try again with a different path.\n"));
}
return createConfirm.create;
}
}
/**
* Validate directory path for installation
* @param {string} input - User input path
* @returns {string|true} Error message or true if valid
*/
async validateDirectory(input) {
// Allow empty input to use the default
if (!input || input.trim() === '') {
return true; // Empty means use default
}
let expandedPath;
try {
expandedPath = this.expandUserPath(input.trim());
} catch (error) {
return error.message;
}
// Check if the path exists
const pathExists = await fs.pathExists(expandedPath);
if (!pathExists) {
// Find the first existing parent directory
const existingParent = await this.findExistingParent(expandedPath);
if (!existingParent) {
return 'Cannot create directory: no existing parent directory found';
}
// Check if the existing parent is writable
try {
await fs.access(existingParent, fs.constants.W_OK);
// Path doesn't exist but can be created - will prompt for confirmation later
return true;
} catch {
// Provide a detailed error message explaining both issues
return `Directory '${expandedPath}' does not exist and cannot be created: parent directory '${existingParent}' is not writable`;
}
}
// If it exists, validate it's a directory and writable
const stat = await fs.stat(expandedPath);
if (!stat.isDirectory()) {
return `Path exists but is not a directory: ${expandedPath}`;
}
// Check write permissions
try {
await fs.access(expandedPath, fs.constants.W_OK);
} catch {
return `Directory is not writable: ${expandedPath}`;
}
return true;
}
/**
* Find the first existing parent directory
* @param {string} targetPath - The path to check
* @returns {string|null} The first existing parent directory, or null if none found
*/
async findExistingParent(targetPath) {
let currentPath = path.resolve(targetPath);
// Walk up the directory tree until we find an existing directory
while (currentPath !== path.dirname(currentPath)) {
// Stop at root
const parent = path.dirname(currentPath);
if (await fs.pathExists(parent)) {
return parent;
}
currentPath = parent;
}
return null; // No existing parent found (shouldn't happen in practice)
}
/**
* Expands the user-provided path: handles ~ and resolves to absolute.
* @param {string} inputPath - User input path.
* @returns {string} Absolute expanded path.
*/
expandUserPath(inputPath) {
if (typeof inputPath !== 'string') {
throw new TypeError('Path must be a string.');
}
let expanded = inputPath.trim();
// Handle tilde expansion
if (expanded.startsWith('~')) {
if (expanded === '~') {
expanded = os.homedir();
} else if (expanded.startsWith('~' + path.sep)) {
const pathAfterHome = expanded.slice(2); // Remove ~/ or ~\
expanded = path.join(os.homedir(), pathAfterHome);
} else {
const restOfPath = expanded.slice(1);
const separatorIndex = restOfPath.indexOf(path.sep);
const username = separatorIndex === -1 ? restOfPath : restOfPath.slice(0, separatorIndex);
if (username) {
throw new Error(`Path expansion for ~${username} is not supported. Please use an absolute path or ~${path.sep}`);
}
}
}
// Resolve to the absolute path relative to the current working directory
return path.resolve(expanded);
}
Add Text-to-Speech Integration via TTS_INJECTION System (#934) * feat: Add provider-agnostic TTS integration via injection point system Implements comprehensive Text-to-Speech integration for BMAD agents using a generic TTS_INJECTION marker system. When AgentVibes (or any compatible TTS provider) is installed, all BMAD agents can speak their responses with unique AI voices. ## Key Features **Provider-Agnostic Architecture** - Uses generic `TTS_INJECTION` markers instead of vendor-specific naming - Future-proof for multiple TTS providers beyond AgentVibes - Clean separation - BMAD stays TTS-agnostic, providers handle injection **Installation Flow** - BMAD → AgentVibes: TTS instructions injected when AgentVibes detects existing BMAD installation - AgentVibes → BMAD: TTS instructions injected during BMAD installation when AgentVibes detected - User must manually create voice assignment file when AgentVibes installs first (documented limitation) **Party Mode Voice Support** - Each agent speaks with unique assigned voice in multi-agent discussions - PM, Architect, Developer, Analyst, UX Designer, etc. - all with distinct voices **Zero Breaking Changes** - Fully backward compatible - works without any TTS provider - `TTS_INJECTION` markers are benign HTML comments if not processed - No changes to existing agent behavior or non-TTS workflows ## Implementation Details **Files Modified:** - `tools/cli/installers/lib/core/installer.js` - TTS injection processing logic - `tools/cli/lib/ui.js` - AgentVibes detection and installation prompts - `tools/cli/commands/install.js` - Post-install guidance for AgentVibes setup - `src/utility/models/fragments/activation-rules.xml` - TTS_INJECTION marker for agents - `src/core/workflows/party-mode/instructions.md` - TTS_INJECTION marker for party mode **Injection Point System:** ```xml <rules> - ALWAYS communicate in {communication_language} <!-- TTS_INJECTION:agent-tts --> - Stay in character until exit selected </rules> ``` When AgentVibes is detected, the installer replaces this marker with: ``` - When responding to user messages, speak your responses using TTS: Call: `.claude/hooks/bmad-speak.sh '{agent-id}' '{response-text}'` after each response IMPORTANT: Use single quotes - do NOT escape special characters like ! or $ ``` **Special Character Handling:** - Explicit guidance to use single quotes without escaping - Prevents "backslash exclamation" artifacts in speech **User Experience:** ``` User: "How should we architect this feature?" Architect: [Text response] + 🔊 [Professional voice explains architecture] ``` Party Mode: ``` PM (John): "I'll focus on user value..." 🔊 [Male professional voice] UX Designer (Sara): "From a user perspective..." 🔊 [Female voice] Architect (Marcus): "The technical approach..." 🔊 [Male technical voice] ``` ## Testing **Unit Tests:** ✅ 62/62 passing - 49/49 schema validation tests - 13/13 installation component tests **Integration Testing:** - ✅ BMAD → AgentVibes (automatic injection) - ✅ AgentVibes → BMAD (automatic injection) - ✅ No TTS provider (markers remain as comments) ## Documentation Comprehensive testing guide created with: - Both installation scenario walkthroughs - Verification commands and expected outputs - Troubleshooting guidance ## Known Limitations **AgentVibes → BMAD Installation Order:** When AgentVibes installs first, voice assignment file must be created manually: ```bash mkdir -p .bmad/_cfg cat > .bmad/_cfg/agent-voice-map.csv << 'EOF' agent_id,voice_name pm,en_US-ryan-high architect,en_US-danny-low dev,en_US-joe-medium EOF ``` This limitation exists to prevent false legacy v4 detection warnings from BMAD installer. **Recommended:** Install BMAD first, then AgentVibes for automatic voice assignment. ## Related Work **Companion Implementation:** - Repository: paulpreibisch/AgentVibes - Commits: 6 commits implementing injection processing and voice routing - Features: Retroactive injection, file path extraction, escape stripping **GitHub Issues:** - paulpreibisch/AgentVibes#36 - BMAD agent ID support ## Breaking Changes None. Feature is opt-in and requires separate TTS provider installation. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: Enforce project hooks over global hooks in party mode before, claude would sometimes favor global agent vibes hooks over project specific * feat: Automate AgentVibes installer invocation after BMAD install Instead of showing manual installation instructions, the installer now: - Prompts "Press Enter to start AgentVibes installer..." - Automatically runs npx agentvibes@latest install - Handles errors gracefully with fallback instructions This provides a seamless installation flow matching the test script's interactive approach. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * docs: Add automated testing script and guide for PR #934 Added comprehensive testing tools for AgentVibes party mode integration: - test-bmad-pr.sh: Fully automated installation and verification script - Interactive mode selection (official PR or custom fork) - Automatic BMAD CLI setup and linking - AgentVibes installation with guided prompts - Built-in verification checks for voice maps and hooks - Saved configuration for quick re-testing - TESTING.md: Complete testing documentation - Quick start with one-line npx command - Manual installation alternative - Troubleshooting guide - Cleanup instructions Testers can now run a single command to test the full AgentVibes integration without needing to understand the complex setup process. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: Add shell: true to npx execSync to prevent permission denied error The execSync call for 'npx agentvibes@latest install' was failing with 'Permission denied' because the shell was trying to execute 'agentvibes@latest' directly instead of passing it as an argument to npx. Adding shell: true ensures the command runs in a proper shell context where npx can correctly interpret the @latest version syntax. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: Remove duplicate AgentVibes installation step from test script The test script was calling AgentVibes installer twice: 1. BMAD installer now automatically runs AgentVibes (new feature) 2. Test script had a separate Step 6 that also ran AgentVibes This caused the installer to run twice, with the second call failing because it was already installed. Changes: - Removed redundant Step 6 (AgentVibes installation) - Updated Step 5 to indicate it includes AgentVibes - Updated step numbers from 7 to 6 throughout - Added guidance that AgentVibes runs automatically Now the flow is cleaner: BMAD installer handles everything! 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: Address bmadcode review - preserve variables and move TTS logic to injection Fixes requested changes from PR review: 1. Preserve {bmad_folder} variable placeholder - Changed: {project_root}/.bmad/core/tasks/workflow.xml - To: {project_root}/{bmad_folder}/core/tasks/workflow.xml - Allows users to choose custom BMAD folder names during installation 2. Move TTS-specific hook guidance to injection system - Removed hardcoded hook enforcement from source files - Added hook guidance to processTTSInjectionPoints() in installer.js - Now only appears when AgentVibes is installed (via TTS_INJECTION) 3. Maintain TTS-agnostic source architecture - Source files remain clean of TTS-specific instructions - TTS details injected at install-time only when needed - Preserves provider-agnostic design principle Changes made: - src/core/workflows/party-mode/instructions.md - Reverted .bmad to {bmad_folder} variable - Replaced hardcoded hook guidance with <!-- TTS_INJECTION:party-mode --> - Removed <note> about play-tts.sh hook location - tools/cli/installers/lib/core/installer.js - Added hook enforcement to party-mode injection replacement - Guidance now injected only when enableAgentVibes is true Addresses review comments from bmadcode: - "needs to remain the variable. it will get set in the file at the install destination." - "items like this we will need to inject if user is using claude and TTS" 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: Change 'claude-code' to 'claude' in test script instructions The correct command to start Claude is 'claude', not 'claude-code'. Updated line 362-363 in test-bmad-pr.sh to show the correct command. * fix: Remove npm link from test script to avoid global namespace pollution - Removed 'npm link' command that was installing BMAD globally - Changed 'bmad install' to direct node execution using local clone - Updated success message to reflect no global installation This keeps testing fully isolated and prevents conflicts with: - Existing BMAD installations - Future official BMAD installs - Orphaned symlinks when test directory is deleted The test script now runs completely self-contained without modifying the user's global npm environment. --------- Co-authored-by: Claude Code <claude@anthropic.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Paul Preibisch <paul@paulpreibisch.com> Co-authored-by: Brian <bmadcode@gmail.com>
2025-11-26 08:51:57 -07:00
/**
* @function promptAgentVibes
* @intent Ask user if they want AgentVibes TTS integration during BMAD installation
* @why Enables optional voice features without forcing TTS on users who don't want it
* @param {string} projectDir - Absolute path to user's project directory
* @returns {Promise<Object>} Configuration object: { enabled: boolean, alreadyInstalled: boolean }
* @sideeffects None - pure user input collection, no files written
* @edgecases Shows warning if user enables TTS but AgentVibes not detected
* @calledby promptInstall() during installation flow, after core config, before IDE selection
* @calls checkAgentVibesInstalled(), inquirer.prompt(), chalk.green/yellow/dim()
*
* AI NOTE: This prompt is strategically positioned in installation flow:
2025-12-10 20:50:24 +09:00
* - AFTER core config (user_name, etc)
Add Text-to-Speech Integration via TTS_INJECTION System (#934) * feat: Add provider-agnostic TTS integration via injection point system Implements comprehensive Text-to-Speech integration for BMAD agents using a generic TTS_INJECTION marker system. When AgentVibes (or any compatible TTS provider) is installed, all BMAD agents can speak their responses with unique AI voices. ## Key Features **Provider-Agnostic Architecture** - Uses generic `TTS_INJECTION` markers instead of vendor-specific naming - Future-proof for multiple TTS providers beyond AgentVibes - Clean separation - BMAD stays TTS-agnostic, providers handle injection **Installation Flow** - BMAD → AgentVibes: TTS instructions injected when AgentVibes detects existing BMAD installation - AgentVibes → BMAD: TTS instructions injected during BMAD installation when AgentVibes detected - User must manually create voice assignment file when AgentVibes installs first (documented limitation) **Party Mode Voice Support** - Each agent speaks with unique assigned voice in multi-agent discussions - PM, Architect, Developer, Analyst, UX Designer, etc. - all with distinct voices **Zero Breaking Changes** - Fully backward compatible - works without any TTS provider - `TTS_INJECTION` markers are benign HTML comments if not processed - No changes to existing agent behavior or non-TTS workflows ## Implementation Details **Files Modified:** - `tools/cli/installers/lib/core/installer.js` - TTS injection processing logic - `tools/cli/lib/ui.js` - AgentVibes detection and installation prompts - `tools/cli/commands/install.js` - Post-install guidance for AgentVibes setup - `src/utility/models/fragments/activation-rules.xml` - TTS_INJECTION marker for agents - `src/core/workflows/party-mode/instructions.md` - TTS_INJECTION marker for party mode **Injection Point System:** ```xml <rules> - ALWAYS communicate in {communication_language} <!-- TTS_INJECTION:agent-tts --> - Stay in character until exit selected </rules> ``` When AgentVibes is detected, the installer replaces this marker with: ``` - When responding to user messages, speak your responses using TTS: Call: `.claude/hooks/bmad-speak.sh '{agent-id}' '{response-text}'` after each response IMPORTANT: Use single quotes - do NOT escape special characters like ! or $ ``` **Special Character Handling:** - Explicit guidance to use single quotes without escaping - Prevents "backslash exclamation" artifacts in speech **User Experience:** ``` User: "How should we architect this feature?" Architect: [Text response] + 🔊 [Professional voice explains architecture] ``` Party Mode: ``` PM (John): "I'll focus on user value..." 🔊 [Male professional voice] UX Designer (Sara): "From a user perspective..." 🔊 [Female voice] Architect (Marcus): "The technical approach..." 🔊 [Male technical voice] ``` ## Testing **Unit Tests:** ✅ 62/62 passing - 49/49 schema validation tests - 13/13 installation component tests **Integration Testing:** - ✅ BMAD → AgentVibes (automatic injection) - ✅ AgentVibes → BMAD (automatic injection) - ✅ No TTS provider (markers remain as comments) ## Documentation Comprehensive testing guide created with: - Both installation scenario walkthroughs - Verification commands and expected outputs - Troubleshooting guidance ## Known Limitations **AgentVibes → BMAD Installation Order:** When AgentVibes installs first, voice assignment file must be created manually: ```bash mkdir -p .bmad/_cfg cat > .bmad/_cfg/agent-voice-map.csv << 'EOF' agent_id,voice_name pm,en_US-ryan-high architect,en_US-danny-low dev,en_US-joe-medium EOF ``` This limitation exists to prevent false legacy v4 detection warnings from BMAD installer. **Recommended:** Install BMAD first, then AgentVibes for automatic voice assignment. ## Related Work **Companion Implementation:** - Repository: paulpreibisch/AgentVibes - Commits: 6 commits implementing injection processing and voice routing - Features: Retroactive injection, file path extraction, escape stripping **GitHub Issues:** - paulpreibisch/AgentVibes#36 - BMAD agent ID support ## Breaking Changes None. Feature is opt-in and requires separate TTS provider installation. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: Enforce project hooks over global hooks in party mode before, claude would sometimes favor global agent vibes hooks over project specific * feat: Automate AgentVibes installer invocation after BMAD install Instead of showing manual installation instructions, the installer now: - Prompts "Press Enter to start AgentVibes installer..." - Automatically runs npx agentvibes@latest install - Handles errors gracefully with fallback instructions This provides a seamless installation flow matching the test script's interactive approach. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * docs: Add automated testing script and guide for PR #934 Added comprehensive testing tools for AgentVibes party mode integration: - test-bmad-pr.sh: Fully automated installation and verification script - Interactive mode selection (official PR or custom fork) - Automatic BMAD CLI setup and linking - AgentVibes installation with guided prompts - Built-in verification checks for voice maps and hooks - Saved configuration for quick re-testing - TESTING.md: Complete testing documentation - Quick start with one-line npx command - Manual installation alternative - Troubleshooting guide - Cleanup instructions Testers can now run a single command to test the full AgentVibes integration without needing to understand the complex setup process. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: Add shell: true to npx execSync to prevent permission denied error The execSync call for 'npx agentvibes@latest install' was failing with 'Permission denied' because the shell was trying to execute 'agentvibes@latest' directly instead of passing it as an argument to npx. Adding shell: true ensures the command runs in a proper shell context where npx can correctly interpret the @latest version syntax. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: Remove duplicate AgentVibes installation step from test script The test script was calling AgentVibes installer twice: 1. BMAD installer now automatically runs AgentVibes (new feature) 2. Test script had a separate Step 6 that also ran AgentVibes This caused the installer to run twice, with the second call failing because it was already installed. Changes: - Removed redundant Step 6 (AgentVibes installation) - Updated Step 5 to indicate it includes AgentVibes - Updated step numbers from 7 to 6 throughout - Added guidance that AgentVibes runs automatically Now the flow is cleaner: BMAD installer handles everything! 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: Address bmadcode review - preserve variables and move TTS logic to injection Fixes requested changes from PR review: 1. Preserve {bmad_folder} variable placeholder - Changed: {project_root}/.bmad/core/tasks/workflow.xml - To: {project_root}/{bmad_folder}/core/tasks/workflow.xml - Allows users to choose custom BMAD folder names during installation 2. Move TTS-specific hook guidance to injection system - Removed hardcoded hook enforcement from source files - Added hook guidance to processTTSInjectionPoints() in installer.js - Now only appears when AgentVibes is installed (via TTS_INJECTION) 3. Maintain TTS-agnostic source architecture - Source files remain clean of TTS-specific instructions - TTS details injected at install-time only when needed - Preserves provider-agnostic design principle Changes made: - src/core/workflows/party-mode/instructions.md - Reverted .bmad to {bmad_folder} variable - Replaced hardcoded hook guidance with <!-- TTS_INJECTION:party-mode --> - Removed <note> about play-tts.sh hook location - tools/cli/installers/lib/core/installer.js - Added hook enforcement to party-mode injection replacement - Guidance now injected only when enableAgentVibes is true Addresses review comments from bmadcode: - "needs to remain the variable. it will get set in the file at the install destination." - "items like this we will need to inject if user is using claude and TTS" 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: Change 'claude-code' to 'claude' in test script instructions The correct command to start Claude is 'claude', not 'claude-code'. Updated line 362-363 in test-bmad-pr.sh to show the correct command. * fix: Remove npm link from test script to avoid global namespace pollution - Removed 'npm link' command that was installing BMAD globally - Changed 'bmad install' to direct node execution using local clone - Updated success message to reflect no global installation This keeps testing fully isolated and prevents conflicts with: - Existing BMAD installations - Future official BMAD installs - Orphaned symlinks when test directory is deleted The test script now runs completely self-contained without modifying the user's global npm environment. --------- Co-authored-by: Claude Code <claude@anthropic.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Paul Preibisch <paul@paulpreibisch.com> Co-authored-by: Brian <bmadcode@gmail.com>
2025-11-26 08:51:57 -07:00
* - BEFORE IDE selection (which can hang on Windows/PowerShell)
*
* Flow Logic:
* 1. Auto-detect if AgentVibes already installed (checks for hook files)
* 2. Show detection status to user (green checkmark or gray "not detected")
* 3. Prompt: "Enable AgentVibes TTS?" (defaults to true if detected)
* 4. If user says YES but AgentVibes NOT installed:
* Show warning with installation link (graceful degradation)
* 5. Return config to promptInstall(), which passes to installer.install()
*
* State Flow:
* promptAgentVibes() { enabled, alreadyInstalled }
*
* promptInstall() config.enableAgentVibes
*
* installer.install() this.enableAgentVibes
*
* processTTSInjectionPoints() injects OR strips markers
*
* RELATED:
* ========
* - Detection: checkAgentVibesInstalled() - looks for bmad-speak.sh and play-tts.sh
* - Processing: installer.js::processTTSInjectionPoints()
* - Markers: src/core/workflows/party-mode/instructions.md:101, src/modules/bmm/agents/*.md
* - GitHub Issue: paulpreibisch/AgentVibes#36
*/
async promptAgentVibes(projectDir) {
CLIUtils.displaySection('🎤 Voice Features', 'Enable TTS for multi-agent conversations');
// Check if AgentVibes is already installed
const agentVibesInstalled = await this.checkAgentVibesInstalled(projectDir);
if (agentVibesInstalled) {
console.log(chalk.green(' ✓ AgentVibes detected'));
} else {
console.log(chalk.dim(' AgentVibes not detected'));
}
const answers = await inquirer.prompt([
{
type: 'confirm',
name: 'enableTts',
message: 'Enable Agents to Speak Out loud (powered by Agent Vibes? Claude Code only currently)',
default: false, // Default to yes - recommended for best experience
Add Text-to-Speech Integration via TTS_INJECTION System (#934) * feat: Add provider-agnostic TTS integration via injection point system Implements comprehensive Text-to-Speech integration for BMAD agents using a generic TTS_INJECTION marker system. When AgentVibes (or any compatible TTS provider) is installed, all BMAD agents can speak their responses with unique AI voices. ## Key Features **Provider-Agnostic Architecture** - Uses generic `TTS_INJECTION` markers instead of vendor-specific naming - Future-proof for multiple TTS providers beyond AgentVibes - Clean separation - BMAD stays TTS-agnostic, providers handle injection **Installation Flow** - BMAD → AgentVibes: TTS instructions injected when AgentVibes detects existing BMAD installation - AgentVibes → BMAD: TTS instructions injected during BMAD installation when AgentVibes detected - User must manually create voice assignment file when AgentVibes installs first (documented limitation) **Party Mode Voice Support** - Each agent speaks with unique assigned voice in multi-agent discussions - PM, Architect, Developer, Analyst, UX Designer, etc. - all with distinct voices **Zero Breaking Changes** - Fully backward compatible - works without any TTS provider - `TTS_INJECTION` markers are benign HTML comments if not processed - No changes to existing agent behavior or non-TTS workflows ## Implementation Details **Files Modified:** - `tools/cli/installers/lib/core/installer.js` - TTS injection processing logic - `tools/cli/lib/ui.js` - AgentVibes detection and installation prompts - `tools/cli/commands/install.js` - Post-install guidance for AgentVibes setup - `src/utility/models/fragments/activation-rules.xml` - TTS_INJECTION marker for agents - `src/core/workflows/party-mode/instructions.md` - TTS_INJECTION marker for party mode **Injection Point System:** ```xml <rules> - ALWAYS communicate in {communication_language} <!-- TTS_INJECTION:agent-tts --> - Stay in character until exit selected </rules> ``` When AgentVibes is detected, the installer replaces this marker with: ``` - When responding to user messages, speak your responses using TTS: Call: `.claude/hooks/bmad-speak.sh '{agent-id}' '{response-text}'` after each response IMPORTANT: Use single quotes - do NOT escape special characters like ! or $ ``` **Special Character Handling:** - Explicit guidance to use single quotes without escaping - Prevents "backslash exclamation" artifacts in speech **User Experience:** ``` User: "How should we architect this feature?" Architect: [Text response] + 🔊 [Professional voice explains architecture] ``` Party Mode: ``` PM (John): "I'll focus on user value..." 🔊 [Male professional voice] UX Designer (Sara): "From a user perspective..." 🔊 [Female voice] Architect (Marcus): "The technical approach..." 🔊 [Male technical voice] ``` ## Testing **Unit Tests:** ✅ 62/62 passing - 49/49 schema validation tests - 13/13 installation component tests **Integration Testing:** - ✅ BMAD → AgentVibes (automatic injection) - ✅ AgentVibes → BMAD (automatic injection) - ✅ No TTS provider (markers remain as comments) ## Documentation Comprehensive testing guide created with: - Both installation scenario walkthroughs - Verification commands and expected outputs - Troubleshooting guidance ## Known Limitations **AgentVibes → BMAD Installation Order:** When AgentVibes installs first, voice assignment file must be created manually: ```bash mkdir -p .bmad/_cfg cat > .bmad/_cfg/agent-voice-map.csv << 'EOF' agent_id,voice_name pm,en_US-ryan-high architect,en_US-danny-low dev,en_US-joe-medium EOF ``` This limitation exists to prevent false legacy v4 detection warnings from BMAD installer. **Recommended:** Install BMAD first, then AgentVibes for automatic voice assignment. ## Related Work **Companion Implementation:** - Repository: paulpreibisch/AgentVibes - Commits: 6 commits implementing injection processing and voice routing - Features: Retroactive injection, file path extraction, escape stripping **GitHub Issues:** - paulpreibisch/AgentVibes#36 - BMAD agent ID support ## Breaking Changes None. Feature is opt-in and requires separate TTS provider installation. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: Enforce project hooks over global hooks in party mode before, claude would sometimes favor global agent vibes hooks over project specific * feat: Automate AgentVibes installer invocation after BMAD install Instead of showing manual installation instructions, the installer now: - Prompts "Press Enter to start AgentVibes installer..." - Automatically runs npx agentvibes@latest install - Handles errors gracefully with fallback instructions This provides a seamless installation flow matching the test script's interactive approach. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * docs: Add automated testing script and guide for PR #934 Added comprehensive testing tools for AgentVibes party mode integration: - test-bmad-pr.sh: Fully automated installation and verification script - Interactive mode selection (official PR or custom fork) - Automatic BMAD CLI setup and linking - AgentVibes installation with guided prompts - Built-in verification checks for voice maps and hooks - Saved configuration for quick re-testing - TESTING.md: Complete testing documentation - Quick start with one-line npx command - Manual installation alternative - Troubleshooting guide - Cleanup instructions Testers can now run a single command to test the full AgentVibes integration without needing to understand the complex setup process. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: Add shell: true to npx execSync to prevent permission denied error The execSync call for 'npx agentvibes@latest install' was failing with 'Permission denied' because the shell was trying to execute 'agentvibes@latest' directly instead of passing it as an argument to npx. Adding shell: true ensures the command runs in a proper shell context where npx can correctly interpret the @latest version syntax. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: Remove duplicate AgentVibes installation step from test script The test script was calling AgentVibes installer twice: 1. BMAD installer now automatically runs AgentVibes (new feature) 2. Test script had a separate Step 6 that also ran AgentVibes This caused the installer to run twice, with the second call failing because it was already installed. Changes: - Removed redundant Step 6 (AgentVibes installation) - Updated Step 5 to indicate it includes AgentVibes - Updated step numbers from 7 to 6 throughout - Added guidance that AgentVibes runs automatically Now the flow is cleaner: BMAD installer handles everything! 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: Address bmadcode review - preserve variables and move TTS logic to injection Fixes requested changes from PR review: 1. Preserve {bmad_folder} variable placeholder - Changed: {project_root}/.bmad/core/tasks/workflow.xml - To: {project_root}/{bmad_folder}/core/tasks/workflow.xml - Allows users to choose custom BMAD folder names during installation 2. Move TTS-specific hook guidance to injection system - Removed hardcoded hook enforcement from source files - Added hook guidance to processTTSInjectionPoints() in installer.js - Now only appears when AgentVibes is installed (via TTS_INJECTION) 3. Maintain TTS-agnostic source architecture - Source files remain clean of TTS-specific instructions - TTS details injected at install-time only when needed - Preserves provider-agnostic design principle Changes made: - src/core/workflows/party-mode/instructions.md - Reverted .bmad to {bmad_folder} variable - Replaced hardcoded hook guidance with <!-- TTS_INJECTION:party-mode --> - Removed <note> about play-tts.sh hook location - tools/cli/installers/lib/core/installer.js - Added hook enforcement to party-mode injection replacement - Guidance now injected only when enableAgentVibes is true Addresses review comments from bmadcode: - "needs to remain the variable. it will get set in the file at the install destination." - "items like this we will need to inject if user is using claude and TTS" 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: Change 'claude-code' to 'claude' in test script instructions The correct command to start Claude is 'claude', not 'claude-code'. Updated line 362-363 in test-bmad-pr.sh to show the correct command. * fix: Remove npm link from test script to avoid global namespace pollution - Removed 'npm link' command that was installing BMAD globally - Changed 'bmad install' to direct node execution using local clone - Updated success message to reflect no global installation This keeps testing fully isolated and prevents conflicts with: - Existing BMAD installations - Future official BMAD installs - Orphaned symlinks when test directory is deleted The test script now runs completely self-contained without modifying the user's global npm environment. --------- Co-authored-by: Claude Code <claude@anthropic.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Paul Preibisch <paul@paulpreibisch.com> Co-authored-by: Brian <bmadcode@gmail.com>
2025-11-26 08:51:57 -07:00
},
]);
if (answers.enableTts && !agentVibesInstalled) {
console.log(chalk.yellow('\n ⚠️ AgentVibes not installed'));
console.log(chalk.dim(' Install AgentVibes separately to enable TTS:'));
console.log(chalk.dim(' https://github.com/paulpreibisch/AgentVibes\n'));
}
return {
enabled: answers.enableTts,
alreadyInstalled: agentVibesInstalled,
};
}
/**
* @function checkAgentVibesInstalled
* @intent Detect if AgentVibes TTS hooks are present in user's project
* @why Allows auto-enabling TTS and showing helpful installation guidance
* @param {string} projectDir - Absolute path to user's project directory
* @returns {Promise<boolean>} true if both required AgentVibes hooks exist, false otherwise
* @sideeffects None - read-only file existence checks
* @edgecases Returns false if either hook missing (both required for functional TTS)
* @calledby promptAgentVibes() to determine default value and show detection status
* @calls fs.pathExists() twice (bmad-speak.sh, play-tts.sh)
*
* AI NOTE: This checks for the MINIMUM viable AgentVibes installation.
*
* Required Files:
* ===============
* 1. .claude/hooks/bmad-speak.sh
* - Maps agent display names agent IDs voice profiles
* - Calls play-tts.sh with agent's assigned voice
* - Created by AgentVibes installer
*
* 2. .claude/hooks/play-tts.sh
* - Core TTS router (ElevenLabs or Piper)
* - Provider-agnostic interface
* - Required by bmad-speak.sh
*
* Why Both Required:
* ==================
* - bmad-speak.sh alone: No TTS backend
* - play-tts.sh alone: No BMAD agent voice mapping
* - Both together: Full party mode TTS integration
*
* Detection Strategy:
* ===================
* We use simple file existence (not version checks) because:
* - Fast and reliable
* - Works across all AgentVibes versions
* - User will discover version issues when TTS runs (fail-fast)
*
* PATTERN: Adding New Detection Criteria
* =======================================
* If future AgentVibes features require additional files:
* 1. Add new pathExists check to this function
* 2. Update documentation in promptAgentVibes()
* 3. Consider: should missing file prevent detection or just log warning?
*
* RELATED:
* ========
* - AgentVibes Installer: creates these hooks
* - bmad-speak.sh: calls play-tts.sh with agent voices
* - Party Mode: uses bmad-speak.sh for agent dialogue
*/
async checkAgentVibesInstalled(projectDir) {
const fs = require('fs-extra');
const path = require('node:path');
// Check for AgentVibes hook files
const hookPath = path.join(projectDir, '.claude', 'hooks', 'bmad-speak.sh');
const playTtsPath = path.join(projectDir, '.claude', 'hooks', 'play-tts.sh');
return (await fs.pathExists(hookPath)) && (await fs.pathExists(playTtsPath));
}
2025-12-07 20:46:09 -06:00
/**
* Prompt for custom content for existing installations
* @returns {Object} Custom content configuration
*/
async promptCustomContentForExisting() {
try {
2025-12-10 20:50:24 +09:00
// Skip custom content installation - always return false
return { hasCustomContent: false };
2025-12-07 20:46:09 -06:00
2025-12-10 20:50:24 +09:00
// TODO: Custom content installation temporarily disabled
// CLIUtils.displaySection('Custom Content', 'Add new custom agents, workflows, or modules to your installation');
2025-12-07 20:46:09 -06:00
2025-12-10 20:50:24 +09:00
// const { hasCustomContent } = await inquirer.prompt([
// {
// type: 'list',
// name: 'hasCustomContent',
// message: 'Do you want to add or update custom content?',
// choices: [
// {
// name: 'No, continue with current installation only',
// value: false,
// },
// {
// name: 'Yes, I have custom content to add or update',
// value: true,
// },
// ],
// default: false,
// },
// ]);
2025-12-07 20:46:09 -06:00
2025-12-10 20:50:24 +09:00
// if (!hasCustomContent) {
// return { hasCustomContent: false };
// }
2025-12-07 20:46:09 -06:00
2025-12-10 20:50:24 +09:00
// TODO: Custom content installation temporarily disabled
// // Get directory path
// const { customPath } = await inquirer.prompt([
// {
// type: 'input',
// name: 'customPath',
// message: 'Enter directory to search for custom content (will scan subfolders):',
// default: process.cwd(),
// validate: async (input) => {
// if (!input || input.trim() === '') {
// return 'Please enter a directory path';
// }
2025-12-07 20:46:09 -06:00
2025-12-10 20:50:24 +09:00
// // Normalize and check if path exists
// const expandedPath = CLIUtils.expandPath(input.trim());
// const pathExists = await fs.pathExists(expandedPath);
// if (!pathExists) {
// return 'Directory does not exist';
// }
2025-12-07 20:46:09 -06:00
2025-12-10 20:50:24 +09:00
// // Check if it's actually a directory
// const stats = await fs.stat(expandedPath);
// if (!stats.isDirectory()) {
// return 'Path must be a directory';
// }
2025-12-07 20:46:09 -06:00
2025-12-10 20:50:24 +09:00
// return true;
// },
// transformer: (input) => {
// return CLIUtils.expandPath(input);
// },
// },
// ]);
2025-12-07 20:46:09 -06:00
2025-12-10 20:50:24 +09:00
// const resolvedPath = CLIUtils.expandPath(customPath);
2025-12-07 20:46:09 -06:00
2025-12-10 20:50:24 +09:00
// // Find custom content
// const customHandler = new CustomHandler();
// const customFiles = await customHandler.findCustomContent(resolvedPath);
2025-12-07 20:46:09 -06:00
2025-12-10 20:50:24 +09:00
// if (customFiles.length === 0) {
// console.log(chalk.yellow(`\nNo custom content found in ${resolvedPath}`));
2025-12-07 20:46:09 -06:00
2025-12-10 20:50:24 +09:00
// const { tryDifferent } = await inquirer.prompt([
// {
// type: 'confirm',
// name: 'tryDifferent',
// message: 'Try a different directory?',
// default: true,
// },
// ]);
2025-12-07 20:46:09 -06:00
2025-12-10 20:50:24 +09:00
// if (tryDifferent) {
// return await this.promptCustomContentForExisting();
// }
2025-12-07 20:46:09 -06:00
2025-12-10 20:50:24 +09:00
// return { hasCustomContent: false };
// }
2025-12-07 20:46:09 -06:00
2025-12-10 20:50:24 +09:00
// // Display found items
// console.log(chalk.cyan(`\nFound ${customFiles.length} custom content file(s):`));
// const customContentItems = [];
// for (const customFile of customFiles) {
// const customInfo = await customHandler.getCustomInfo(customFile);
// if (customInfo) {
// customContentItems.push({
// name: `${chalk.cyan('✓')} ${customInfo.name} ${chalk.gray(`(${customInfo.relativePath})`)}`,
// value: `__CUSTOM_CONTENT__${customFile}`,
// checked: true,
// });
// }
// }
2025-12-07 20:46:09 -06:00
2025-12-10 20:50:24 +09:00
// // Add option to keep existing custom content
// console.log(chalk.yellow('\nExisting custom modules will be preserved unless you remove them'));
// const { selectedFiles } = await inquirer.prompt([
// {
// type: 'checkbox',
// name: 'selectedFiles',
// message: 'Select custom content to add:',
// choices: customContentItems,
// pageSize: 15,
// validate: (answer) => {
// if (answer.length === 0) {
// return 'You must select at least one item';
// }
// return true;
// },
// },
// ]);
2025-12-07 20:46:09 -06:00
2025-12-10 20:50:24 +09:00
// return {
// hasCustomContent: true,
// customPath: resolvedPath,
// selected: true,
// selectedFiles: selectedFiles,
// };
2025-12-07 20:46:09 -06:00
} catch (error) {
console.error(chalk.red('Error configuring custom content:'), error);
return { hasCustomContent: false };
}
}
/**
* Prompt user for custom content source location
* @returns {Object} Custom content configuration
*/
async promptCustomContentSource() {
const customContentConfig = { hasCustomContent: true, sources: [] };
// Keep asking for more sources until user is done
while (true) {
// First ask if user wants to add another module or continue
if (customContentConfig.sources.length > 0) {
const { action } = await inquirer.prompt([
{
type: 'list',
name: 'action',
message: 'Would you like to:',
choices: [
{ name: 'Add another custom module', value: 'add' },
{ name: 'Continue with installation', value: 'continue' },
],
default: 'continue',
},
]);
if (action === 'continue') {
break;
}
}
let sourcePath;
let isValid = false;
while (!isValid) {
const { path: inputPath } = await inquirer.prompt([
{
type: 'input',
name: 'path',
message: 'Enter the path to your custom content folder (or press Enter to cancel):',
validate: async (input) => {
// Allow empty input to cancel
if (!input || input.trim() === '') {
return true; // Allow empty to exit
}
try {
// Expand the path
const expandedPath = this.expandUserPath(input.trim());
// Check if path exists
if (!(await fs.pathExists(expandedPath))) {
return 'Path does not exist';
}
// Check if it's a directory
const stat = await fs.stat(expandedPath);
if (!stat.isDirectory()) {
return 'Path must be a directory';
}
// Check for module.yaml in the root
const moduleYamlPath = path.join(expandedPath, 'module.yaml');
if (!(await fs.pathExists(moduleYamlPath))) {
return 'Directory must contain a module.yaml file in the root';
}
// Try to parse the module.yaml to get the module ID
try {
const yaml = require('yaml');
const content = await fs.readFile(moduleYamlPath, 'utf8');
const moduleData = yaml.parse(content);
if (!moduleData.code) {
return 'module.yaml must contain a "code" field for the module ID';
}
} catch (error) {
return 'Invalid module.yaml file: ' + error.message;
}
return true;
} catch (error) {
return 'Error validating path: ' + error.message;
}
},
},
]);
// If user pressed Enter without typing anything, exit the loop
if (!inputPath || inputPath.trim() === '') {
// If we have no modules yet, return false for no custom content
if (customContentConfig.sources.length === 0) {
return { hasCustomContent: false };
}
return customContentConfig;
}
sourcePath = this.expandUserPath(inputPath);
isValid = true;
}
// Read module.yaml to get module info
const yaml = require('yaml');
const moduleYamlPath = path.join(sourcePath, 'module.yaml');
const moduleContent = await fs.readFile(moduleYamlPath, 'utf8');
const moduleData = yaml.parse(moduleContent);
// Add to sources
customContentConfig.sources.push({
path: sourcePath,
id: moduleData.code,
name: moduleData.name || moduleData.code,
});
console.log(chalk.green(`✓ Confirmed local custom module: ${moduleData.name || moduleData.code}`));
}
// Ask if user wants to add these to the installation
const { shouldInstall } = await inquirer.prompt([
{
type: 'confirm',
name: 'shouldInstall',
message: `Install ${customContentConfig.sources.length} custom module(s) now?`,
default: true,
},
]);
if (shouldInstall) {
customContentConfig.selected = true;
// Store paths to module.yaml files, not directories
customContentConfig.selectedFiles = customContentConfig.sources.map((s) => path.join(s.path, 'module.yaml'));
// Also include module IDs for installation
customContentConfig.selectedModuleIds = customContentConfig.sources.map((s) => s.id);
}
return customContentConfig;
}
}
module.exports = { UI };