2025-09-28 23:17:07 -05:00
|
|
|
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');
|
2025-12-08 12:24:30 -07:00
|
|
|
const { CustomHandler } = require('../installers/lib/custom/handler');
|
2025-09-28 23:17:07 -05:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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}`);
|
2025-09-28 23:17:07 -05:00
|
|
|
|
|
|
|
|
const confirmedDirectory = await this.getConfirmedDirectory();
|
2025-10-02 21:45:59 -05:00
|
|
|
|
2025-11-08 15:19:19 -06: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-10-02 21:45:59 -05:00
|
|
|
// Check if there's an existing BMAD installation
|
|
|
|
|
const fs = require('fs-extra');
|
|
|
|
|
const path = require('node:path');
|
2025-11-08 15:19:19 -06:00
|
|
|
const bmadDir = await installer.findBmadDir(confirmedDirectory);
|
2025-10-02 21:45:59 -05:00
|
|
|
const hasExistingInstall = await fs.pathExists(bmadDir);
|
|
|
|
|
|
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: '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
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
|
2025-09-28 23:17:07 -05:00
|
|
|
const { installedModuleIds } = await this.getExistingInstallation(confirmedDirectory);
|
|
|
|
|
const coreConfig = await this.collectCoreConfig(confirmedDirectory);
|
2025-12-06 16:56:09 -06:00
|
|
|
|
2025-12-13 17:50:33 +08:00
|
|
|
// 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;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-06 16:56:09 -06:00
|
|
|
// Skip module selection during update/reinstall - keep existing modules
|
|
|
|
|
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 {
|
|
|
|
|
// Only show module selection for new installs
|
2025-12-07 13:39:03 -06:00
|
|
|
const moduleChoices = await this.getModuleChoices(installedModuleIds, customContentConfig);
|
2025-12-06 16:56:09 -06:00
|
|
|
selectedModules = await this.selectModules(moduleChoices);
|
2025-12-07 13:39:03 -06:00
|
|
|
|
|
|
|
|
// Check which custom content items were selected
|
|
|
|
|
const selectedCustomContent = selectedModules.filter((mod) => mod.startsWith('__CUSTOM_CONTENT__'));
|
2025-12-07 20:46:09 -06:00
|
|
|
|
2025-12-13 17:50:33 +08:00
|
|
|
if (selectedCustomContent.length > 0) {
|
2025-12-07 13:39:03 -06:00
|
|
|
customContentConfig.selected = true;
|
2025-12-13 17:50:33 +08:00
|
|
|
customContentConfig.selectedFiles = selectedCustomContent.map((mod) => mod.replace('__CUSTOM_CONTENT__', ''));
|
2025-12-07 20:46:09 -06:00
|
|
|
|
2025-12-13 17:50:33 +08:00
|
|
|
// Convert custom content to module IDs for installation
|
|
|
|
|
const customContentModuleIds = [];
|
|
|
|
|
const customHandler = new CustomHandler();
|
|
|
|
|
for (const customFile of customContentConfig.selectedFiles) {
|
|
|
|
|
// Get the module info to extract the ID
|
|
|
|
|
const customInfo = await customHandler.getCustomInfo(customFile);
|
|
|
|
|
if (customInfo) {
|
|
|
|
|
customContentModuleIds.push(customInfo.id);
|
2025-12-07 17:17:50 -06:00
|
|
|
}
|
|
|
|
|
}
|
2025-12-13 17:50:33 +08:00
|
|
|
// Filter out custom content markers and add module IDs
|
|
|
|
|
selectedModules = [...selectedModules.filter((mod) => !mod.startsWith('__CUSTOM_CONTENT__')), ...customContentModuleIds];
|
2025-12-07 13:39:03 -06:00
|
|
|
} else if (customContentConfig.hasCustomContent) {
|
|
|
|
|
// User provided custom content but didn't select any
|
|
|
|
|
customContentConfig.selected = false;
|
|
|
|
|
customContentConfig.selectedFiles = [];
|
|
|
|
|
}
|
2025-12-06 16:56:09 -06:00
|
|
|
}
|
2025-09-28 23:17:07 -05:00
|
|
|
|
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
|
|
|
// Prompt for AgentVibes TTS integration
|
|
|
|
|
const agentVibesConfig = await this.promptAgentVibes(confirmedDirectory);
|
|
|
|
|
|
2025-11-04 21:58:41 -06:00
|
|
|
// Collect IDE tool selection AFTER configuration prompts (fixes Windows/PowerShell hang)
|
|
|
|
|
// This allows text-based prompts to complete before the checkbox prompt
|
|
|
|
|
const toolSelection = await this.promptToolSelection(confirmedDirectory, selectedModules);
|
|
|
|
|
|
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
|
2025-09-28 23:17:07 -05:00
|
|
|
|
|
|
|
|
return {
|
2025-10-28 21:44:04 -05:00
|
|
|
actionType: actionType || 'update', // Preserve reinstall or update action
|
2025-09-28 23:17:07 -05:00
|
|
|
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,
|
2025-09-28 23:17:07 -05:00
|
|
|
coreConfig: coreConfig, // Pass collected core config to installer
|
2025-12-07 13:39:03 -06:00
|
|
|
// 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,
|
2025-09-28 23:17:07 -05:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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) {
|
2025-11-12 22:40:45 -06:00
|
|
|
// Check for existing configured IDEs - use findBmadDir to detect custom folder names
|
2025-09-28 23:17:07 -05:00
|
|
|
const { Detector } = require('../installers/lib/core/detector');
|
2025-11-12 22:40:45 -06:00
|
|
|
const { Installer } = require('../installers/lib/core/installer');
|
2025-09-28 23:17:07 -05:00
|
|
|
const detector = new Detector();
|
2025-11-12 22:40:45 -06:00
|
|
|
const installer = new Installer();
|
|
|
|
|
const bmadDir = await installer.findBmadDir(projectDir || process.cwd());
|
2025-09-28 23:17:07 -05:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-28 23:17:07 -05:00
|
|
|
// 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`));
|
2025-09-28 23:17:07 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CLIUtils.displaySection('Tool Integration', 'Select AI coding assistants and IDEs to configure');
|
|
|
|
|
|
2025-11-19 22:12:45 -06:00
|
|
|
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: 15,
|
|
|
|
|
},
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
// 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 the section header before looping back
|
|
|
|
|
console.log();
|
|
|
|
|
CLIUtils.displaySection('Tool Integration', 'Select AI coding assistants and IDEs to configure');
|
|
|
|
|
} else {
|
|
|
|
|
// User explicitly chose to proceed without tools
|
|
|
|
|
userConfirmedNoTools = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-09-28 23:17:07 -05:00
|
|
|
|
|
|
|
|
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'}`,
|
|
|
|
|
];
|
|
|
|
|
|
2025-12-05 17:54:03 -07:00
|
|
|
// Add AgentVibes TTS info if enabled
|
|
|
|
|
if (result.agentVibesEnabled) {
|
|
|
|
|
summary.push(`🎤 AgentVibes TTS: Enabled`);
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-28 23:17:07 -05:00
|
|
|
CLIUtils.displayBox(summary.join('\n\n'), {
|
|
|
|
|
borderColor: 'green',
|
|
|
|
|
borderStyle: 'round',
|
|
|
|
|
});
|
|
|
|
|
|
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'));
|
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'));
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-28 23:17:07 -05:00
|
|
|
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');
|
2025-11-12 22:40:45 -06:00
|
|
|
const { Installer } = require('../installers/lib/core/installer');
|
2025-09-28 23:17:07 -05:00
|
|
|
const detector = new Detector();
|
2025-11-12 22:40:45 -06:00
|
|
|
const installer = new Installer();
|
|
|
|
|
const bmadDir = await installer.findBmadDir(directory);
|
2025-09-28 23:17:07 -05:00
|
|
|
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
|
2025-12-07 13:39:03 -06:00
|
|
|
* @param {Object} customContentConfig - Custom content configuration
|
2025-09-28 23:17:07 -05:00
|
|
|
* @returns {Array} Module choices for inquirer
|
|
|
|
|
*/
|
2025-12-07 13:39:03 -06:00
|
|
|
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 13:39:03 -06:00
|
|
|
|
2025-12-07 20:46:09 -06:00
|
|
|
// Add custom content items
|
2025-12-13 17:50:33 +08:00
|
|
|
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
|
|
|
|
|
});
|
2025-12-07 13:39:03 -06:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Add official modules
|
2025-09-28 23:17:07 -05:00
|
|
|
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-09-28 23:17:07 -05:00
|
|
|
|
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)
|
2025-12-07 13:39:03 -06:00
|
|
|
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),
|
|
|
|
|
});
|
|
|
|
|
}
|
2025-12-07 13:39:03 -06:00
|
|
|
}
|
2025-12-06 22:45:02 -06:00
|
|
|
|
|
|
|
|
return moduleChoices;
|
2025-09-28 23:17:07 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Prompt for module selection
|
|
|
|
|
* @param {Array} moduleChoices - Available module choices
|
|
|
|
|
* @returns {Array} Selected module IDs
|
|
|
|
|
*/
|
|
|
|
|
async selectModules(moduleChoices) {
|
|
|
|
|
CLIUtils.displaySection('Module Selection', 'Choose the BMAD modules to install');
|
|
|
|
|
|
|
|
|
|
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-11-12 22:40:45 -06:00
|
|
|
// Check for any bmad installation (any folder with _cfg/manifest.yaml)
|
|
|
|
|
const { Installer } = require('../installers/lib/core/installer');
|
|
|
|
|
const installer = new Installer();
|
|
|
|
|
const bmadDir = await installer.findBmadDir(directory);
|
|
|
|
|
const hasBmadInstall = (await fs.pathExists(bmadDir)) && (await fs.pathExists(path.join(bmadDir, '_cfg', 'manifest.yaml')));
|
|
|
|
|
|
2025-09-28 23:17:07 -05:00
|
|
|
console.log(
|
|
|
|
|
chalk.gray(`Directory exists and contains ${files.length} item(s)`) +
|
2025-11-12 22:40:45 -06:00
|
|
|
(hasBmadInstall ? chalk.yellow(` including existing BMAD installation (${path.basename(bmadDir)})`) : ''),
|
2025-09-28 23:17:07 -05:00
|
|
|
);
|
|
|
|
|
} 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',
|
2025-11-26 17:46:26 -06:00
|
|
|
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 };
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-09-28 23:17:07 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
module.exports = { UI };
|