Compare commits
1 Commits
782f08b956
...
d39b563506
| Author | SHA1 | Date |
|---|---|---|
|
|
d39b563506 |
|
|
@ -73,7 +73,7 @@
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
"[javascript]": {
|
"[javascript]": {
|
||||||
"editor.defaultFormatter": "vscode.typescript-language-features"
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
},
|
},
|
||||||
"[json]": {
|
"[json]": {
|
||||||
"editor.defaultFormatter": "vscode.json-language-features"
|
"editor.defaultFormatter": "vscode.json-language-features"
|
||||||
|
|
|
||||||
2
LICENSE
2
LICENSE
|
|
@ -21,6 +21,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
SOFTWARE.
|
SOFTWARE.
|
||||||
|
|
||||||
TRADEMARK NOTICE:
|
TRADEMARK NOTICE:
|
||||||
BMad™ , BMAD-CORE™ and BMAD-METHOD™ are trademarks of BMad Code, LLC. The use of these
|
BMAD™, BMAD-CORE™ and BMAD-METHOD™ are trademarks of BMad Code, LLC. The use of these
|
||||||
trademarks in this software does not grant any rights to use the trademarks
|
trademarks in this software does not grant any rights to use the trademarks
|
||||||
for any other purpose.
|
for any other purpose.
|
||||||
|
|
|
||||||
|
|
@ -202,7 +202,7 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for full development guidelines.
|
||||||
|
|
||||||
MIT License - See [LICENSE](LICENSE) for details.
|
MIT License - See [LICENSE](LICENSE) for details.
|
||||||
|
|
||||||
**Trademarks:** BMad™ and BMAD-METHOD™ are trademarks of BMad Code, LLC.
|
**Trademarks:** BMAD™ and BMAD-METHOD™ are trademarks of BMad Code, LLC.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,14 +12,16 @@ agent:
|
||||||
role: "Master Task Executor + BMad Expert + Guiding Facilitator Orchestrator"
|
role: "Master Task Executor + BMad Expert + Guiding Facilitator Orchestrator"
|
||||||
identity: "Master-level expert in the BMAD Core Platform and all loaded modules with comprehensive knowledge of all resources, tasks, and workflows. Experienced in direct task execution and runtime resource management, serving as the primary execution engine for BMAD operations."
|
identity: "Master-level expert in the BMAD Core Platform and all loaded modules with comprehensive knowledge of all resources, tasks, and workflows. Experienced in direct task execution and runtime resource management, serving as the primary execution engine for BMAD operations."
|
||||||
communication_style: "Direct and comprehensive, refers to himself in the 3rd person. Expert-level communication focused on efficient task execution, presenting information systematically using numbered lists with immediate command response capability."
|
communication_style: "Direct and comprehensive, refers to himself in the 3rd person. Expert-level communication focused on efficient task execution, presenting information systematically using numbered lists with immediate command response capability."
|
||||||
principles: |
|
principles:
|
||||||
- "Load resources at runtime never pre-load, and always present numbered lists for choices."
|
- "Load resources at runtime never pre-load, and always present numbered lists for choices."
|
||||||
|
|
||||||
|
# Agent-specific critical actions
|
||||||
critical_actions:
|
critical_actions:
|
||||||
- "Load into memory {project-root}/_bmad/core/config.yaml and set variable project_name, output_folder, user_name, communication_language"
|
- "Load into memory {project-root}/_bmad/core/config.yaml and set variable project_name, output_folder, user_name, communication_language"
|
||||||
- "Remember the users name is {user_name}"
|
- "Remember the users name is {user_name}"
|
||||||
- "ALWAYS communicate in {communication_language}"
|
- "ALWAYS communicate in {communication_language}"
|
||||||
|
|
||||||
|
# Agent menu items
|
||||||
menu:
|
menu:
|
||||||
- trigger: "list-tasks"
|
- trigger: "list-tasks"
|
||||||
action: "list all tasks from {project-root}/_bmad/_config/task-manifest.csv"
|
action: "list all tasks from {project-root}/_bmad/_config/task-manifest.csv"
|
||||||
|
|
@ -32,3 +34,5 @@ agent:
|
||||||
- trigger: "party-mode"
|
- trigger: "party-mode"
|
||||||
exec: "{project-root}/_bmad/core/workflows/party-mode/workflow.md"
|
exec: "{project-root}/_bmad/core/workflows/party-mode/workflow.md"
|
||||||
description: "Group chat with all agents"
|
description: "Group chat with all agents"
|
||||||
|
|
||||||
|
prompts: []
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
code: core
|
code: core
|
||||||
name: "BMad™ Core Module"
|
name: "BMAD™ Core Module"
|
||||||
|
|
||||||
header: "BMad™ Core Configuration"
|
header: "BMAD™ Core Configuration"
|
||||||
subheader: "Configure the core settings for your BMad™ installation.\nThese settings will be used across all modules and agents."
|
subheader: "Configure the core settings for your BMAD™ installation.\nThese settings will be used across all modules and agents."
|
||||||
|
|
||||||
user_name:
|
user_name:
|
||||||
prompt: "What shall the agents call you (TIP: Use a team name if using with a group)?"
|
prompt: "What shall the agents call you (TIP: Use a team name if using with a group)?"
|
||||||
|
|
@ -16,7 +16,7 @@ communication_language:
|
||||||
|
|
||||||
document_output_language:
|
document_output_language:
|
||||||
prompt: "Preferred document output language?"
|
prompt: "Preferred document output language?"
|
||||||
default: "English"
|
default: "{communication_language}"
|
||||||
result: "{value}"
|
result: "{value}"
|
||||||
|
|
||||||
output_folder:
|
output_folder:
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ Provides the **HOW** (universal knowledge) while agents provide the **WHAT** (do
|
||||||
### Example: Frame Expert (Technical Diagrams)
|
### Example: Frame Expert (Technical Diagrams)
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
# workflows/excalidraw-diagrams/create-flowchart/workflow.yaml
|
# workflows/diagrams/create-flowchart/workflow.yaml
|
||||||
helpers: '{project-root}/_bmad/core/resources/excalidraw/excalidraw-helpers.md'
|
helpers: '{project-root}/_bmad/core/resources/excalidraw/excalidraw-helpers.md'
|
||||||
json_validation: '{project-root}/_bmad/core/resources/excalidraw/validate-json-instructions.md'
|
json_validation: '{project-root}/_bmad/core/resources/excalidraw/validate-json-instructions.md'
|
||||||
```
|
```
|
||||||
|
|
@ -79,7 +79,7 @@ json_validation: '{project-root}/_bmad/core/resources/excalidraw/validate-json-i
|
||||||
**Domain-specific additions:**
|
**Domain-specific additions:**
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
# workflows/excalidraw-diagrams/_shared/flowchart-templates.yaml
|
# workflows/diagrams/_shared/flowchart-templates.yaml
|
||||||
flowchart:
|
flowchart:
|
||||||
start_node:
|
start_node:
|
||||||
type: ellipse
|
type: ellipse
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
name: brainstorming
|
name: brainstorming-session
|
||||||
description: Facilitate interactive brainstorming sessions using diverse creative techniques and ideation methods
|
description: Facilitate interactive brainstorming sessions using diverse creative techniques and ideation methods
|
||||||
context_file: '' # Optional context file path for project-specific guidance
|
context_file: '' # Optional context file path for project-specific guidance
|
||||||
---
|
---
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,76 @@
|
||||||
|
const fs = require('fs-extra');
|
||||||
|
const path = require('node:path');
|
||||||
|
const chalk = require('chalk');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BMB Module Installer
|
||||||
|
* Sets up custom agent and workflow locations for the BMad Builder module
|
||||||
|
*
|
||||||
|
* @param {Object} options - Installation options
|
||||||
|
* @param {string} options.projectRoot - The root directory of the target project
|
||||||
|
* @param {Object} options.config - Module configuration from module.yaml
|
||||||
|
* @param {Object} options.coreConfig - Core configuration containing user_name
|
||||||
|
* @param {Array<string>} options.installedIDEs - Array of IDE codes that were installed
|
||||||
|
* @param {Object} options.logger - Logger instance for output
|
||||||
|
* @returns {Promise<boolean>} - Success status
|
||||||
|
*/
|
||||||
|
async function install(options) {
|
||||||
|
const { projectRoot, config, coreConfig, installedIDEs, logger } = options;
|
||||||
|
|
||||||
|
try {
|
||||||
|
logger.log(chalk.blue('🔧 Setting up BMB Module...'));
|
||||||
|
|
||||||
|
// Generate custom.yaml in bmb_creations_output_folder
|
||||||
|
if (config['bmb_creations_output_folder']) {
|
||||||
|
// The config value contains {project-root} which needs to be resolved
|
||||||
|
const rawLocation = config['bmb_creations_output_folder'];
|
||||||
|
const customLocation = rawLocation.replace('{project-root}', projectRoot);
|
||||||
|
const customDestPath = path.join(customLocation, 'custom.yaml');
|
||||||
|
|
||||||
|
logger.log(chalk.cyan(` Setting up custom agents at: ${customLocation}`));
|
||||||
|
|
||||||
|
// Ensure the directory exists
|
||||||
|
await fs.ensureDir(customLocation);
|
||||||
|
|
||||||
|
// Generate the custom.yaml content
|
||||||
|
const userName = (coreConfig && coreConfig.user_name) || 'my';
|
||||||
|
const customContent = `code: my-custom-bmad
|
||||||
|
name: "${userName}-Custom-BMad: Sample Stand Alone Custom Agents and Workflows"
|
||||||
|
default_selected: true
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Write the custom.yaml file (only if it doesn't exist to preserve user changes)
|
||||||
|
if (await fs.pathExists(customDestPath)) {
|
||||||
|
logger.log(chalk.yellow(` ✓ custom.yaml already exists at ${customDestPath}`));
|
||||||
|
} else {
|
||||||
|
await fs.writeFile(customDestPath, customContent, 'utf8');
|
||||||
|
logger.log(chalk.green(` ✓ Created custom.yaml at ${customDestPath}`));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up custom module location if configured
|
||||||
|
if (config['bmb_creations_output_folder']) {
|
||||||
|
const rawModuleLocation = config['bmb_creations_output_folder'];
|
||||||
|
const moduleLocation = rawModuleLocation.replace('{project-root}', projectRoot);
|
||||||
|
|
||||||
|
logger.log(chalk.cyan(` Setting up custom modules at: ${moduleLocation}`));
|
||||||
|
|
||||||
|
// Ensure the directory exists
|
||||||
|
await fs.ensureDir(moduleLocation);
|
||||||
|
logger.log(chalk.green(` ✓ Created modules directory at ${moduleLocation}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle IDE-specific configurations if needed
|
||||||
|
if (installedIDEs && installedIDEs.length > 0) {
|
||||||
|
logger.log(chalk.cyan(` Configuring BMB for IDEs: ${installedIDEs.join(', ')}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.log(chalk.green('✓ BMB Module setup complete'));
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(chalk.red(`Error setting up BMB module: ${error.message}`));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { install };
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
code: bmb
|
code: bmb
|
||||||
name: "BMB: BMad Builder - Agent, Workflow and Module Builder"
|
name: "BMB: BMad Builder - Agent, Workflow and Module Builder"
|
||||||
header: "BMad Optimized Builder (BoMB) Module Configuration"
|
header: "BMad Optimized Builder (BoMB) Module Configuration"
|
||||||
subheader: "Configure the settings for the BoMB Factory!\nThe agent, workflow and module builder for BMad™ "
|
subheader: "Configure the settings for the BoMB Factory!\nThe agent, workflow and module builder for BMAD™"
|
||||||
default_selected: false # This module will not be selected by default for new installations
|
default_selected: false # This module will not be selected by default for new installations
|
||||||
|
|
||||||
# Variables from Core Config inserted:
|
# Variables from Core Config inserted:
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ agent:
|
||||||
description: Guided Research scoped to market, domain, competitive analysis, or technical research (optional)
|
description: Guided Research scoped to market, domain, competitive analysis, or technical research (optional)
|
||||||
|
|
||||||
- trigger: product-brief
|
- trigger: product-brief
|
||||||
exec: "{project-root}/_bmad/bmm/workflows/1-analysis/create-product-brief/workflow.md"
|
exec: "{project-root}/_bmad/bmm/workflows/1-analysis/product-brief/workflow.md"
|
||||||
description: Create a Product Brief (recommended input for PRD)
|
description: Create a Product Brief (recommended input for PRD)
|
||||||
|
|
||||||
- trigger: document-project
|
- trigger: document-project
|
||||||
|
|
|
||||||
|
|
@ -23,19 +23,19 @@ agent:
|
||||||
description: Get workflow status or initialize a workflow if not already done (optional)
|
description: Get workflow status or initialize a workflow if not already done (optional)
|
||||||
|
|
||||||
- trigger: create-architecture
|
- trigger: create-architecture
|
||||||
exec: "{project-root}/_bmad/bmm/workflows/3-solutioning/create-architecture/workflow.md"
|
exec: "{project-root}/_bmad/bmm/workflows/3-solutioning/architecture/workflow.md"
|
||||||
description: Create an Architecture Document to Guide Development of a PRD (required for BMad Method projects)
|
description: Create an Architecture Document to Guide Development of a PRD (required for BMad Method projects)
|
||||||
|
|
||||||
- trigger: implementation-readiness
|
- trigger: implementation-readiness
|
||||||
exec: "{project-root}/_bmad/bmm/workflows/3-solutioning/check-implementation-readiness/workflow.md"
|
exec: "{project-root}/_bmad/bmm/workflows/3-solutioning/implementation-readiness/workflow.md"
|
||||||
description: Validate PRD, UX, Architecture, Epics and stories aligned (Optional but recommended before development)
|
description: Validate PRD, UX, Architecture, Epics and stories aligned (Optional but recommended before development)
|
||||||
|
|
||||||
- trigger: create-excalidraw-diagram
|
- trigger: create-excalidraw-diagram
|
||||||
workflow: "{project-root}/_bmad/bmm/workflows/excalidraw-diagrams/create-diagram/workflow.yaml"
|
workflow: "{project-root}/_bmad/bmm/workflows/diagrams/create-diagram/workflow.yaml"
|
||||||
description: Create system architecture or technical diagram (Excalidraw) (Use any time you need a diagram)
|
description: Create system architecture or technical diagram (Excalidraw) (Use any time you need a diagram)
|
||||||
|
|
||||||
- trigger: create-excalidraw-dataflow
|
- trigger: create-excalidraw-dataflow
|
||||||
workflow: "{project-root}/_bmad/bmm/workflows/excalidraw-diagrams/create-dataflow/workflow.yaml"
|
workflow: "{project-root}/_bmad/bmm/workflows/diagrams/create-dataflow/workflow.yaml"
|
||||||
description: Create data flow diagram (Excalidraw) (Use any time you need a diagram)
|
description: Create data flow diagram (Excalidraw) (Use any time you need a diagram)
|
||||||
|
|
||||||
- trigger: party-mode
|
- trigger: party-mode
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ agent:
|
||||||
description: Create Epics and User Stories from PRD (Required for BMad Method flow AFTER the Architecture is completed)
|
description: Create Epics and User Stories from PRD (Required for BMad Method flow AFTER the Architecture is completed)
|
||||||
|
|
||||||
- trigger: implementation-readiness
|
- trigger: implementation-readiness
|
||||||
exec: "{project-root}/_bmad/bmm/workflows/3-solutioning/check-implementation-readiness/workflow.md"
|
exec: "{project-root}/_bmad/bmm/workflows/3-solutioning/implementation-readiness/workflow.md"
|
||||||
description: Validate PRD, UX, Architecture, Epics and stories aligned (Optional but recommended before development)
|
description: Validate PRD, UX, Architecture, Epics and stories aligned (Optional but recommended before development)
|
||||||
|
|
||||||
- trigger: correct-course
|
- trigger: correct-course
|
||||||
|
|
|
||||||
|
|
@ -30,15 +30,15 @@ agent:
|
||||||
description: Generate Mermaid diagrams (architecture, sequence, flow, ER, class, state)
|
description: Generate Mermaid diagrams (architecture, sequence, flow, ER, class, state)
|
||||||
|
|
||||||
- trigger: create-excalidraw-flowchart
|
- trigger: create-excalidraw-flowchart
|
||||||
workflow: "{project-root}/_bmad/bmm/workflows/excalidraw-diagrams/create-flowchart/workflow.yaml"
|
workflow: "{project-root}/_bmad/bmm/workflows/diagrams/create-flowchart/workflow.yaml"
|
||||||
description: Create Excalidraw flowchart for processes and logic flows
|
description: Create Excalidraw flowchart for processes and logic flows
|
||||||
|
|
||||||
- trigger: create-excalidraw-diagram
|
- trigger: create-excalidraw-diagram
|
||||||
workflow: "{project-root}/_bmad/bmm/workflows/excalidraw-diagrams/create-diagram/workflow.yaml"
|
workflow: "{project-root}/_bmad/bmm/workflows/diagrams/create-diagram/workflow.yaml"
|
||||||
description: Create Excalidraw system architecture or technical diagram
|
description: Create Excalidraw system architecture or technical diagram
|
||||||
|
|
||||||
- trigger: create-excalidraw-dataflow
|
- trigger: create-excalidraw-dataflow
|
||||||
workflow: "{project-root}/_bmad/bmm/workflows/excalidraw-diagrams/create-dataflow/workflow.yaml"
|
workflow: "{project-root}/_bmad/bmm/workflows/diagrams/create-dataflow/workflow.yaml"
|
||||||
description: Create Excalidraw data flow diagram
|
description: Create Excalidraw data flow diagram
|
||||||
|
|
||||||
- trigger: validate-doc
|
- trigger: validate-doc
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ agent:
|
||||||
description: Validate UX Specification and Design Artifacts
|
description: Validate UX Specification and Design Artifacts
|
||||||
|
|
||||||
- trigger: create-excalidraw-wireframe
|
- trigger: create-excalidraw-wireframe
|
||||||
workflow: "{project-root}/_bmad/bmm/workflows/excalidraw-diagrams/create-wireframe/workflow.yaml"
|
workflow: "{project-root}/_bmad/bmm/workflows/diagrams/create-wireframe/workflow.yaml"
|
||||||
description: Create website or app wireframe (Excalidraw)
|
description: Create website or app wireframe (Excalidraw)
|
||||||
|
|
||||||
- trigger: party-mode
|
- trigger: party-mode
|
||||||
|
|
|
||||||
|
|
@ -35,22 +35,23 @@ planning_artifacts: # Phase 1-3 artifacts
|
||||||
default: "{output_folder}/project-planning-artifacts"
|
default: "{output_folder}/project-planning-artifacts"
|
||||||
result: "{project-root}/{value}"
|
result: "{project-root}/{value}"
|
||||||
|
|
||||||
implementation_artifacts: # Phase 4 artifacts and quick-dev flow output
|
implementation_artifacts: # Phase 4 artifacts
|
||||||
prompt: "Where should implementation artifacts be stored?\n(sprint status, individual story files and reviews, retrospectives, Quick Flow output)"
|
prompt: "Where should implementation artifacts be stored?\n - Such as: (sprint status, individual story files and reviews, retrospectives, Quick Flow output)"
|
||||||
default: "{output_folder}/implementation-artifacts"
|
default: "{output_folder}/implementation-artifacts"
|
||||||
result: "{project-root}/{value}"
|
result: "{project-root}/{value}"
|
||||||
|
|
||||||
project_knowledge: # Artifacts from research, document-project output, other long lived accurate knowledge
|
project_knowledge: # Artifacts from research, document-project output, other long lived accurate kn
|
||||||
prompt: "Where should non-ephemeral project knowledge be searched for and stored\n(docs, research, references)?"
|
prompt: "Where should non-ephemeral project knowledge be stored (docs, research, references)?"
|
||||||
default: "docs"
|
default: "docs"
|
||||||
result: "{project-root}/{value}"
|
result: "{project-root}/{value}"
|
||||||
# tea_use_mcp_enhancements:
|
|
||||||
# prompt: "Test Architect Playwright MCP capabilities (healing, exploratory, verification) are optionally available.\nYou will have to setup your MCPs yourself; refer to test-architecture.md for hints.\nWould you like to enable MCP enhancements in Test Architect?"
|
|
||||||
# default: false
|
|
||||||
# result: "{value}"
|
|
||||||
|
|
||||||
# tea_use_playwright_utils:
|
tea_use_mcp_enhancements:
|
||||||
# prompt:
|
prompt: "Enable Test Architect Playwright MCP capabilities (healing, exploratory, verification)?\nYou have to setup your MCPs yourself; refer to test-architecture.md for hints."
|
||||||
# - "Are you using playwright-utils (@seontechnologies/playwright-utils) in your project?\nYou must install packages yourself, or use test architect's *framework command."
|
default: false
|
||||||
# default: false
|
result: "{value}"
|
||||||
# result: "{value}"
|
|
||||||
|
tea_use_playwright_utils:
|
||||||
|
prompt:
|
||||||
|
- "Are you using playwright-utils (@seontechnologies/playwright-utils) in your project?\nYou must install packages yourself, or use test architect's *framework command."
|
||||||
|
default: false
|
||||||
|
result: "{value}"
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
# Powered by BMAD™ Core
|
||||||
name: bmmcc
|
name: bmmcc
|
||||||
short-title: BMM Claude Code Sub Module
|
short-title: BMM Claude Code Sub Module
|
||||||
author: Brian (BMad) Madison
|
author: Brian (BMad) Madison
|
||||||
|
|
|
||||||
|
|
@ -4,16 +4,16 @@
|
||||||
#
|
#
|
||||||
# The installer will:
|
# The installer will:
|
||||||
# 1. Ask users if they want to install subagents (all/selective/none)
|
# 1. Ask users if they want to install subagents (all/selective/none)
|
||||||
# 2. Ask where to install (project-level .claude/agents/_bmad/ or user-level ~/.claude/agents/_bmad/)
|
# 2. Ask where to install (project-level .claude/agents/{bmad_folder}/ or user-level ~/.claude/agents/{bmad_folder}/)
|
||||||
# 3. Only inject content related to selected subagents
|
# 3. Only inject content related to selected subagents
|
||||||
# 4. Templates stay in _bmad/ directory and are referenced from there
|
# 4. Templates stay in {bmad_folder}/ directory and are referenced from there
|
||||||
# 5. Injections are placed at specific sections where each subagent is most valuable
|
# 5. Injections are placed at specific sections where each subagent is most valuable
|
||||||
|
|
||||||
injections:
|
injections:
|
||||||
# ===== PRD WORKFLOW INJECTIONS =====
|
# ===== PRD WORKFLOW INJECTIONS =====
|
||||||
|
|
||||||
# PRD Subagent Instructions
|
# PRD Subagent Instructions
|
||||||
- file: "_bmad/bmm/workflows/prd/instructions.md"
|
- file: "{bmad_folder}/bmm/workflows/prd/instructions.md"
|
||||||
point: "prd-subagent-instructions"
|
point: "prd-subagent-instructions"
|
||||||
requires: "all-prd-subagents"
|
requires: "all-prd-subagents"
|
||||||
content: |
|
content: |
|
||||||
|
|
@ -25,7 +25,7 @@ injections:
|
||||||
- <CRITICAL>Use `bmm-technical-decisions-curator` to capture all technical mentions</CRITICAL>
|
- <CRITICAL>Use `bmm-technical-decisions-curator` to capture all technical mentions</CRITICAL>
|
||||||
|
|
||||||
# PRD Requirements Analysis
|
# PRD Requirements Analysis
|
||||||
- file: "_bmad/bmm/workflows/prd/instructions.md"
|
- file: "{bmad_folder}/bmm/workflows/prd/instructions.md"
|
||||||
point: "prd-requirements-analysis"
|
point: "prd-requirements-analysis"
|
||||||
requires: "requirements-analyst"
|
requires: "requirements-analyst"
|
||||||
content: |
|
content: |
|
||||||
|
|
@ -33,7 +33,7 @@ injections:
|
||||||
**Subagent Hint**: Use `bmm-requirements-analyst` to validate requirements are testable and complete.
|
**Subagent Hint**: Use `bmm-requirements-analyst` to validate requirements are testable and complete.
|
||||||
|
|
||||||
# PRD User Journey Mapping
|
# PRD User Journey Mapping
|
||||||
- file: "_bmad/bmm/workflows/prd/instructions.md"
|
- file: "{bmad_folder}/bmm/workflows/prd/instructions.md"
|
||||||
point: "prd-user-journey"
|
point: "prd-user-journey"
|
||||||
requires: "user-journey-mapper"
|
requires: "user-journey-mapper"
|
||||||
content: |
|
content: |
|
||||||
|
|
@ -41,7 +41,7 @@ injections:
|
||||||
**Subagent Hint**: Use `bmm-user-journey-mapper` to map all user types and their value paths.
|
**Subagent Hint**: Use `bmm-user-journey-mapper` to map all user types and their value paths.
|
||||||
|
|
||||||
# PRD Epic Optimization
|
# PRD Epic Optimization
|
||||||
- file: "_bmad/bmm/workflows/prd/instructions.md"
|
- file: "{bmad_folder}/bmm/workflows/prd/instructions.md"
|
||||||
point: "prd-epic-optimization"
|
point: "prd-epic-optimization"
|
||||||
requires: "epic-optimizer"
|
requires: "epic-optimizer"
|
||||||
content: |
|
content: |
|
||||||
|
|
@ -49,7 +49,7 @@ injections:
|
||||||
**Subagent Hint**: Use `bmm-epic-optimizer` to validate epic boundaries deliver coherent value.
|
**Subagent Hint**: Use `bmm-epic-optimizer` to validate epic boundaries deliver coherent value.
|
||||||
|
|
||||||
# PRD Document Review
|
# PRD Document Review
|
||||||
- file: "_bmad/bmm/workflows/prd/instructions.md"
|
- file: "{bmad_folder}/bmm/workflows/prd/instructions.md"
|
||||||
point: "prd-checklist-review"
|
point: "prd-checklist-review"
|
||||||
requires: "document-reviewer"
|
requires: "document-reviewer"
|
||||||
content: |
|
content: |
|
||||||
|
|
@ -57,7 +57,7 @@ injections:
|
||||||
**Subagent Hint**: Use `bmm-document-reviewer` to validate PRD completeness before finalizing.
|
**Subagent Hint**: Use `bmm-document-reviewer` to validate PRD completeness before finalizing.
|
||||||
|
|
||||||
# Technical Decisions Curator
|
# Technical Decisions Curator
|
||||||
- file: "_bmad/bmm/workflows/prd/instructions.md"
|
- file: "{bmad_folder}/bmm/workflows/prd/instructions.md"
|
||||||
point: "technical-decisions-curator"
|
point: "technical-decisions-curator"
|
||||||
requires: "technical-decisions-curator"
|
requires: "technical-decisions-curator"
|
||||||
content: |
|
content: |
|
||||||
|
|
@ -71,7 +71,7 @@ injections:
|
||||||
# ===== MARKET RESEARCH TEMPLATE INJECTIONS =====
|
# ===== MARKET RESEARCH TEMPLATE INJECTIONS =====
|
||||||
|
|
||||||
# Market TAM/SAM/SOM Calculations
|
# Market TAM/SAM/SOM Calculations
|
||||||
- file: "_bmad/bmm/templates/market.md"
|
- file: "{bmad_folder}/bmm/templates/market.md"
|
||||||
point: "market-tam-calculations"
|
point: "market-tam-calculations"
|
||||||
requires: "data-analyst"
|
requires: "data-analyst"
|
||||||
content: |
|
content: |
|
||||||
|
|
@ -82,7 +82,7 @@ injections:
|
||||||
</llm>
|
</llm>
|
||||||
|
|
||||||
# Market Trends Analysis
|
# Market Trends Analysis
|
||||||
- file: "_bmad/bmm/templates/market.md"
|
- file: "{bmad_folder}/bmm/templates/market.md"
|
||||||
point: "market-trends-analysis"
|
point: "market-trends-analysis"
|
||||||
requires: "trend-spotter"
|
requires: "trend-spotter"
|
||||||
content: |
|
content: |
|
||||||
|
|
@ -93,7 +93,7 @@ injections:
|
||||||
</llm>
|
</llm>
|
||||||
|
|
||||||
# Market Customer Personas
|
# Market Customer Personas
|
||||||
- file: "_bmad/bmm/templates/market.md"
|
- file: "{bmad_folder}/bmm/templates/market.md"
|
||||||
point: "market-customer-segments"
|
point: "market-customer-segments"
|
||||||
requires: "user-researcher"
|
requires: "user-researcher"
|
||||||
content: |
|
content: |
|
||||||
|
|
@ -104,7 +104,7 @@ injections:
|
||||||
</llm>
|
</llm>
|
||||||
|
|
||||||
# Market Research Review
|
# Market Research Review
|
||||||
- file: "_bmad/bmm/templates/market.md"
|
- file: "{bmad_folder}/bmm/templates/market.md"
|
||||||
point: "market-executive-summary"
|
point: "market-executive-summary"
|
||||||
requires: "document-reviewer"
|
requires: "document-reviewer"
|
||||||
content: |
|
content: |
|
||||||
|
|
@ -116,7 +116,7 @@ injections:
|
||||||
# ===== COMPETITOR ANALYSIS TEMPLATE INJECTIONS =====
|
# ===== COMPETITOR ANALYSIS TEMPLATE INJECTIONS =====
|
||||||
|
|
||||||
# Competitor Intelligence Gathering
|
# Competitor Intelligence Gathering
|
||||||
- file: "_bmad/bmm/templates/competitor.md"
|
- file: "{bmad_folder}/bmm/templates/competitor.md"
|
||||||
point: "competitor-intelligence"
|
point: "competitor-intelligence"
|
||||||
requires: "market-researcher"
|
requires: "market-researcher"
|
||||||
content: |
|
content: |
|
||||||
|
|
@ -127,7 +127,7 @@ injections:
|
||||||
</llm>
|
</llm>
|
||||||
|
|
||||||
# Competitor Technical Analysis
|
# Competitor Technical Analysis
|
||||||
- file: "_bmad/bmm/templates/competitor.md"
|
- file: "{bmad_folder}/bmm/templates/competitor.md"
|
||||||
point: "competitor-tech-stack"
|
point: "competitor-tech-stack"
|
||||||
requires: "technical-evaluator"
|
requires: "technical-evaluator"
|
||||||
content: |
|
content: |
|
||||||
|
|
@ -138,7 +138,7 @@ injections:
|
||||||
</llm>
|
</llm>
|
||||||
|
|
||||||
# Competitor Metrics Analysis
|
# Competitor Metrics Analysis
|
||||||
- file: "_bmad/bmm/templates/competitor.md"
|
- file: "{bmad_folder}/bmm/templates/competitor.md"
|
||||||
point: "competitor-metrics"
|
point: "competitor-metrics"
|
||||||
requires: "data-analyst"
|
requires: "data-analyst"
|
||||||
content: |
|
content: |
|
||||||
|
|
@ -148,7 +148,7 @@ injections:
|
||||||
</llm>
|
</llm>
|
||||||
|
|
||||||
# Competitor Analysis Review
|
# Competitor Analysis Review
|
||||||
- file: "_bmad/bmm/templates/competitor.md"
|
- file: "{bmad_folder}/bmm/templates/competitor.md"
|
||||||
point: "competitor-executive-summary"
|
point: "competitor-executive-summary"
|
||||||
requires: "document-reviewer"
|
requires: "document-reviewer"
|
||||||
content: |
|
content: |
|
||||||
|
|
@ -160,7 +160,7 @@ injections:
|
||||||
# ===== PROJECT BRIEF TEMPLATE INJECTIONS =====
|
# ===== PROJECT BRIEF TEMPLATE INJECTIONS =====
|
||||||
|
|
||||||
# Brief Problem Validation
|
# Brief Problem Validation
|
||||||
- file: "_bmad/bmm/templates/brief.md"
|
- file: "{bmad_folder}/bmm/templates/brief.md"
|
||||||
point: "brief-problem-validation"
|
point: "brief-problem-validation"
|
||||||
requires: "market-researcher"
|
requires: "market-researcher"
|
||||||
content: |
|
content: |
|
||||||
|
|
@ -170,7 +170,7 @@ injections:
|
||||||
</llm>
|
</llm>
|
||||||
|
|
||||||
# Brief Target User Analysis
|
# Brief Target User Analysis
|
||||||
- file: "_bmad/bmm/templates/brief.md"
|
- file: "{bmad_folder}/bmm/templates/brief.md"
|
||||||
point: "brief-user-analysis"
|
point: "brief-user-analysis"
|
||||||
requires: "user-researcher"
|
requires: "user-researcher"
|
||||||
content: |
|
content: |
|
||||||
|
|
@ -180,7 +180,7 @@ injections:
|
||||||
</llm>
|
</llm>
|
||||||
|
|
||||||
# Brief Success Metrics
|
# Brief Success Metrics
|
||||||
- file: "_bmad/bmm/templates/brief.md"
|
- file: "{bmad_folder}/bmm/templates/brief.md"
|
||||||
point: "brief-success-metrics"
|
point: "brief-success-metrics"
|
||||||
requires: "data-analyst"
|
requires: "data-analyst"
|
||||||
content: |
|
content: |
|
||||||
|
|
@ -190,7 +190,7 @@ injections:
|
||||||
</llm>
|
</llm>
|
||||||
|
|
||||||
# Brief Technical Feasibility
|
# Brief Technical Feasibility
|
||||||
- file: "_bmad/bmm/templates/brief.md"
|
- file: "{bmad_folder}/bmm/templates/brief.md"
|
||||||
point: "brief-technical-feasibility"
|
point: "brief-technical-feasibility"
|
||||||
requires: "technical-evaluator"
|
requires: "technical-evaluator"
|
||||||
content: |
|
content: |
|
||||||
|
|
@ -200,7 +200,7 @@ injections:
|
||||||
</llm>
|
</llm>
|
||||||
|
|
||||||
# Brief Requirements Extraction
|
# Brief Requirements Extraction
|
||||||
- file: "_bmad/bmm/templates/brief.md"
|
- file: "{bmad_folder}/bmm/templates/brief.md"
|
||||||
point: "brief-requirements"
|
point: "brief-requirements"
|
||||||
requires: "requirements-analyst"
|
requires: "requirements-analyst"
|
||||||
content: |
|
content: |
|
||||||
|
|
@ -210,7 +210,7 @@ injections:
|
||||||
</llm>
|
</llm>
|
||||||
|
|
||||||
# Brief Document Review
|
# Brief Document Review
|
||||||
- file: "_bmad/bmm/templates/brief.md"
|
- file: "{bmad_folder}/bmm/templates/brief.md"
|
||||||
point: "brief-final-review"
|
point: "brief-final-review"
|
||||||
requires: "document-reviewer"
|
requires: "document-reviewer"
|
||||||
content: |
|
content: |
|
||||||
|
|
|
||||||
|
|
@ -84,4 +84,4 @@ To test subagent installation:
|
||||||
2. Select BMM module and Claude Code
|
2. Select BMM module and Claude Code
|
||||||
3. Verify prompts appear for subagent selection
|
3. Verify prompts appear for subagent selection
|
||||||
4. Check `.claude/agents/` for installed subagents
|
4. Check `.claude/agents/` for installed subagents
|
||||||
5. Verify injection points are replaced in `.claude/commands/_bmad/` and the various tasks and templates under `_bmad/...`
|
5. Verify injection points are replaced in `.claude/commands/{bmad_folder}/` and the various tasks and templates under `{bmad_folder}/...`
|
||||||
|
|
|
||||||
|
Can't render this file because it has a wrong number of fields in line 2.
|
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
name: create-epics-and-stories
|
name: create-epics-stories
|
||||||
description: 'Transform PRD requirements and Architecture decisions into comprehensive stories organized by user value. This workflow requires completed PRD + Architecture documents (UX recommended if UI exists) and breaks down requirements into implementation-ready epics and user stories that incorporate all available technical and design context. Creates detailed, actionable stories with complete acceptance criteria for development teams.'
|
description: 'Transform PRD requirements and Architecture decisions into comprehensive stories organized by user value. This workflow requires completed PRD + Architecture documents (UX recommended if UI exists) and breaks down requirements into implementation-ready epics and user stories that incorporate all available technical and design context. Creates detailed, actionable stories with complete acceptance criteria for development teams.'
|
||||||
web_bundle: true
|
web_bundle: true
|
||||||
---
|
---
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,8 @@ config_source: "{project-root}/_bmad/bmm/config.yaml"
|
||||||
output_folder: "{config_source}:output_folder"
|
output_folder: "{config_source}:output_folder"
|
||||||
|
|
||||||
# Workflow components
|
# Workflow components
|
||||||
installed_path: "{project-root}/_bmad/bmm/workflows/excalidraw-diagrams/create-dataflow"
|
installed_path: "{project-root}/_bmad/bmm/workflows/diagrams/create-dataflow"
|
||||||
shared_path: "{project-root}/_bmad/bmm/workflows/excalidraw-diagrams/_shared"
|
shared_path: "{project-root}/_bmad/bmm/workflows/diagrams/_shared"
|
||||||
instructions: "{installed_path}/instructions.md"
|
instructions: "{installed_path}/instructions.md"
|
||||||
validation: "{installed_path}/checklist.md"
|
validation: "{installed_path}/checklist.md"
|
||||||
|
|
||||||
|
|
@ -21,7 +21,7 @@ templates: "{shared_path}/excalidraw-templates.yaml"
|
||||||
library: "{shared_path}/excalidraw-library.json"
|
library: "{shared_path}/excalidraw-library.json"
|
||||||
|
|
||||||
# Output file (respects user's configured output_folder)
|
# Output file (respects user's configured output_folder)
|
||||||
default_output_file: "{output_folder}/excalidraw-diagrams/dataflow-{timestamp}.excalidraw"
|
default_output_file: "{output_folder}/diagrams/dataflow-{timestamp}.excalidraw"
|
||||||
|
|
||||||
standalone: true
|
standalone: true
|
||||||
web_bundle: false
|
web_bundle: false
|
||||||
|
|
@ -7,8 +7,8 @@ config_source: "{project-root}/_bmad/bmm/config.yaml"
|
||||||
output_folder: "{config_source}:output_folder"
|
output_folder: "{config_source}:output_folder"
|
||||||
|
|
||||||
# Workflow components
|
# Workflow components
|
||||||
installed_path: "{project-root}/_bmad/bmm/workflows/excalidraw-diagrams/create-diagram"
|
installed_path: "{project-root}/_bmad/bmm/workflows/diagrams/create-diagram"
|
||||||
shared_path: "{project-root}/_bmad/bmm/workflows/excalidraw-diagrams/_shared"
|
shared_path: "{project-root}/_bmad/bmm/workflows/diagrams/_shared"
|
||||||
instructions: "{installed_path}/instructions.md"
|
instructions: "{installed_path}/instructions.md"
|
||||||
validation: "{installed_path}/checklist.md"
|
validation: "{installed_path}/checklist.md"
|
||||||
|
|
||||||
|
|
@ -21,7 +21,7 @@ templates: "{shared_path}/excalidraw-templates.yaml"
|
||||||
library: "{shared_path}/excalidraw-library.json"
|
library: "{shared_path}/excalidraw-library.json"
|
||||||
|
|
||||||
# Output file (respects user's configured output_folder)
|
# Output file (respects user's configured output_folder)
|
||||||
default_output_file: "{output_folder}/excalidraw-diagrams/diagram-{timestamp}.excalidraw"
|
default_output_file: "{output_folder}/diagrams/diagram-{timestamp}.excalidraw"
|
||||||
|
|
||||||
standalone: true
|
standalone: true
|
||||||
web_bundle: false
|
web_bundle: false
|
||||||
|
|
@ -7,8 +7,8 @@ config_source: "{project-root}/_bmad/bmm/config.yaml"
|
||||||
output_folder: "{config_source}:output_folder"
|
output_folder: "{config_source}:output_folder"
|
||||||
|
|
||||||
# Workflow components
|
# Workflow components
|
||||||
installed_path: "{project-root}/_bmad/bmm/workflows/excalidraw-diagrams/create-flowchart"
|
installed_path: "{project-root}/_bmad/bmm/workflows/diagrams/create-flowchart"
|
||||||
shared_path: "{project-root}/_bmad/bmm/workflows/excalidraw-diagrams/_shared"
|
shared_path: "{project-root}/_bmad/bmm/workflows/diagrams/_shared"
|
||||||
instructions: "{installed_path}/instructions.md"
|
instructions: "{installed_path}/instructions.md"
|
||||||
validation: "{installed_path}/checklist.md"
|
validation: "{installed_path}/checklist.md"
|
||||||
|
|
||||||
|
|
@ -21,7 +21,7 @@ templates: "{shared_path}/excalidraw-templates.yaml"
|
||||||
library: "{shared_path}/excalidraw-library.json"
|
library: "{shared_path}/excalidraw-library.json"
|
||||||
|
|
||||||
# Output file (respects user's configured output_folder)
|
# Output file (respects user's configured output_folder)
|
||||||
default_output_file: "{output_folder}/excalidraw-diagrams/flowchart-{timestamp}.excalidraw"
|
default_output_file: "{output_folder}/diagrams/flowchart-{timestamp}.excalidraw"
|
||||||
|
|
||||||
standalone: true
|
standalone: true
|
||||||
web_bundle: false
|
web_bundle: false
|
||||||
|
|
@ -7,8 +7,8 @@ config_source: "{project-root}/_bmad/bmm/config.yaml"
|
||||||
output_folder: "{config_source}:output_folder"
|
output_folder: "{config_source}:output_folder"
|
||||||
|
|
||||||
# Workflow components
|
# Workflow components
|
||||||
installed_path: "{project-root}/_bmad/bmm/workflows/excalidraw-diagrams/create-wireframe"
|
installed_path: "{project-root}/_bmad/bmm/workflows/diagrams/create-wireframe"
|
||||||
shared_path: "{project-root}/_bmad/bmm/workflows/excalidraw-diagrams/_shared"
|
shared_path: "{project-root}/_bmad/bmm/workflows/diagrams/_shared"
|
||||||
instructions: "{installed_path}/instructions.md"
|
instructions: "{installed_path}/instructions.md"
|
||||||
validation: "{installed_path}/checklist.md"
|
validation: "{installed_path}/checklist.md"
|
||||||
|
|
||||||
|
|
@ -21,7 +21,7 @@ templates: "{shared_path}/excalidraw-templates.yaml"
|
||||||
library: "{shared_path}/excalidraw-library.json"
|
library: "{shared_path}/excalidraw-library.json"
|
||||||
|
|
||||||
# Output file (respects user's configured output_folder)
|
# Output file (respects user's configured output_folder)
|
||||||
default_output_file: "{output_folder}/excalidraw-diagrams/wireframe-{timestamp}.excalidraw"
|
default_output_file: "{output_folder}/diagrams/wireframe-{timestamp}.excalidraw"
|
||||||
|
|
||||||
standalone: true
|
standalone: true
|
||||||
web_bundle: false
|
web_bundle: false
|
||||||
|
|
@ -14,10 +14,6 @@ agent:
|
||||||
communication_style: Speaks like a bard weaving an epic tale - flowery, whimsical, every sentence enraptures and draws you deeper
|
communication_style: Speaks like a bard weaving an epic tale - flowery, whimsical, every sentence enraptures and draws you deeper
|
||||||
principles: Powerful narratives leverage timeless human truths. Find the authentic story. Make the abstract concrete through vivid details.
|
principles: Powerful narratives leverage timeless human truths. Find the authentic story. Make the abstract concrete through vivid details.
|
||||||
|
|
||||||
critical_actions:
|
|
||||||
- "Load COMPLETE file {agent_sidecar_folder}/storyteller-sidecar/story-preferences.md and review remember the User Preferences"
|
|
||||||
- "Load COMPLETE file {agent_sidecar_folder}/storyteller-sidecar/stories-told.md and review the history of stories created for this user"
|
|
||||||
|
|
||||||
menu:
|
menu:
|
||||||
- trigger: story
|
- trigger: story
|
||||||
exec: "{project-root}/_bmad/cis/workflows/storytelling/workflow.yaml"
|
exec: "{project-root}/_bmad/cis/workflows/storytelling/workflow.yaml"
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
# Story Record Template
|
|
||||||
|
|
||||||
Purpose: Record a log detailing the stories I have crafted over time for the user.
|
|
||||||
|
|
||||||
## Narratives Told Record
|
|
||||||
|
|
||||||
<!-- track stories created metadata with the user over time -->
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
# Story Record Template
|
|
||||||
|
|
||||||
Purpose: Record a log of learned users story telling or story building preferences.
|
|
||||||
|
|
||||||
## User Preferences
|
|
||||||
|
|
||||||
<!-- record any user preferences about story crafting the user prefers -->
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
code: cis
|
code: cis
|
||||||
name: "CIS: Creative Innovation Suite"
|
name: "CIS: Creative Innovation Suite"
|
||||||
header: "Creative Innovation Suite (CIS) Module"
|
header: "Creative Innovation Suite (CIS) Module"
|
||||||
subheader: "No custom configuration required - uses Core settings only"
|
subheader: "No Configuration needed - uses Core Config only."
|
||||||
default_selected: false # This module will not be selected by default for new installations
|
default_selected: false # This module will not be selected by default for new installations
|
||||||
|
|
||||||
# Variables from Core Config inserted:
|
# Variables from Core Config inserted:
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,11 @@ module.exports = {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle reinstall by setting force flag
|
||||||
|
if (config.actionType === 'reinstall') {
|
||||||
|
config._requestedReinstall = true;
|
||||||
|
}
|
||||||
|
|
||||||
// Regular install/update flow
|
// Regular install/update flow
|
||||||
const result = await installer.install(config);
|
const result = await installer.install(config);
|
||||||
|
|
||||||
|
|
@ -50,11 +55,14 @@ module.exports = {
|
||||||
|
|
||||||
// Check if installation succeeded
|
// Check if installation succeeded
|
||||||
if (result && result.success) {
|
if (result && result.success) {
|
||||||
|
console.log(chalk.green('\n✨ Installation complete!'));
|
||||||
|
console.log(chalk.cyan('BMAD Core and Selected Modules have been installed to:'), chalk.bold(result.path));
|
||||||
|
console.log(chalk.yellow('\nThank you for helping test the early release version of the new BMad Core and BMad Method!'));
|
||||||
|
console.log(chalk.cyan('Stable Beta coming soon - please read the full README.md and linked documentation to get started!'));
|
||||||
|
|
||||||
// Run AgentVibes installer if needed
|
// Run AgentVibes installer if needed
|
||||||
if (result.needsAgentVibes) {
|
if (result.needsAgentVibes) {
|
||||||
// Add some spacing before AgentVibes setup
|
console.log(chalk.magenta('\n🎙️ AgentVibes TTS Setup'));
|
||||||
console.log('');
|
|
||||||
console.log(chalk.magenta('🎙️ AgentVibes TTS Setup'));
|
|
||||||
console.log(chalk.cyan('AgentVibes provides voice synthesis for BMAD agents with:'));
|
console.log(chalk.cyan('AgentVibes provides voice synthesis for BMAD agents with:'));
|
||||||
console.log(chalk.dim(' • ElevenLabs AI (150+ premium voices)'));
|
console.log(chalk.dim(' • ElevenLabs AI (150+ premium voices)'));
|
||||||
console.log(chalk.dim(' • Piper TTS (50+ free voices)\n'));
|
console.log(chalk.dim(' • Piper TTS (50+ free voices)\n'));
|
||||||
|
|
@ -83,7 +91,6 @@ module.exports = {
|
||||||
shell: true,
|
shell: true,
|
||||||
});
|
});
|
||||||
console.log(chalk.green('\n✓ AgentVibes installation complete'));
|
console.log(chalk.green('\n✓ AgentVibes installation complete'));
|
||||||
console.log(chalk.cyan('\n✨ BMAD with TTS is ready to use!'));
|
|
||||||
} catch {
|
} catch {
|
||||||
console.log(chalk.yellow('\n⚠ AgentVibes installation was interrupted or failed'));
|
console.log(chalk.yellow('\n⚠ AgentVibes installation was interrupted or failed'));
|
||||||
console.log(chalk.cyan('You can run it manually later with:'));
|
console.log(chalk.cyan('You can run it manually later with:'));
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,7 @@ module.exports = {
|
||||||
options: [],
|
options: [],
|
||||||
action: async () => {
|
action: async () => {
|
||||||
try {
|
try {
|
||||||
const result = await installer.getAvailableModules();
|
const modules = await installer.getAvailableModules();
|
||||||
const { modules, customModules } = result;
|
|
||||||
|
|
||||||
console.log(chalk.cyan('\n📦 Available BMAD Modules:\n'));
|
console.log(chalk.cyan('\n📦 Available BMAD Modules:\n'));
|
||||||
|
|
||||||
for (const module of modules) {
|
for (const module of modules) {
|
||||||
|
|
@ -21,16 +19,6 @@ module.exports = {
|
||||||
console.log();
|
console.log();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (customModules && customModules.length > 0) {
|
|
||||||
console.log(chalk.cyan('\n🔧 Custom Modules:\n'));
|
|
||||||
for (const module of customModules) {
|
|
||||||
console.log(chalk.bold(` ${module.id}`));
|
|
||||||
console.log(chalk.dim(` ${module.description}`));
|
|
||||||
console.log(chalk.dim(` Version: ${module.version}`));
|
|
||||||
console.log();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(chalk.red('Error:'), error.message);
|
console.error(chalk.red('Error:'), error.message);
|
||||||
|
|
|
||||||
|
|
@ -127,6 +127,10 @@ class ConfigCollector {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (foundAny) {
|
||||||
|
console.log(chalk.cyan('\n📋 Found existing BMAD module configurations'));
|
||||||
|
}
|
||||||
|
|
||||||
return foundAny;
|
return foundAny;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -250,26 +254,6 @@ class ConfigCollector {
|
||||||
const configKeys = Object.keys(moduleConfig).filter((key) => key !== 'prompt');
|
const configKeys = Object.keys(moduleConfig).filter((key) => key !== 'prompt');
|
||||||
const existingKeys = this.existingConfig && this.existingConfig[moduleName] ? Object.keys(this.existingConfig[moduleName]) : [];
|
const existingKeys = this.existingConfig && this.existingConfig[moduleName] ? Object.keys(this.existingConfig[moduleName]) : [];
|
||||||
|
|
||||||
// Check if this module has no configuration keys at all (like CIS)
|
|
||||||
// Filter out metadata fields and only count actual config objects
|
|
||||||
const metadataFields = new Set(['code', 'name', 'header', 'subheader', 'default_selected']);
|
|
||||||
const actualConfigKeys = configKeys.filter((key) => !metadataFields.has(key));
|
|
||||||
const hasNoConfig = actualConfigKeys.length === 0;
|
|
||||||
|
|
||||||
// If module has no config keys at all, handle it specially
|
|
||||||
if (hasNoConfig && moduleConfig.subheader) {
|
|
||||||
// Add blank line for better readability (matches other modules)
|
|
||||||
console.log();
|
|
||||||
const moduleDisplayName = moduleConfig.header || `${moduleName.toUpperCase()} Module`;
|
|
||||||
|
|
||||||
// Display the module name in color first (matches other modules)
|
|
||||||
console.log(chalk.cyan('?') + ' ' + chalk.magenta(moduleDisplayName));
|
|
||||||
|
|
||||||
// Show the subheader since there's no configuration to ask about
|
|
||||||
console.log(chalk.dim(` ✓ ${moduleConfig.subheader}`));
|
|
||||||
return false; // No new fields
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find new interactive fields (with prompt)
|
// Find new interactive fields (with prompt)
|
||||||
const newKeys = configKeys.filter((key) => {
|
const newKeys = configKeys.filter((key) => {
|
||||||
const item = moduleConfig[key];
|
const item = moduleConfig[key];
|
||||||
|
|
@ -318,12 +302,11 @@ class ConfigCollector {
|
||||||
this.allAnswers[`${moduleName}_user_name`] = this.getDefaultUsername();
|
this.allAnswers[`${moduleName}_user_name`] = this.getDefaultUsername();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Show "no config" message for modules with no new questions
|
||||||
|
CLIUtils.displayModuleNoConfig(moduleName, moduleConfig.header, moduleConfig.subheader);
|
||||||
|
return false; // No new fields
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show "no config" message for modules with no new questions (that have config keys)
|
|
||||||
console.log(chalk.dim(` ✓ ${moduleName.toUpperCase()} module already up to date`));
|
|
||||||
return false; // No new fields
|
|
||||||
|
|
||||||
// If we have new fields (interactive or static), process them
|
// If we have new fields (interactive or static), process them
|
||||||
if (newKeys.length > 0 || newStaticKeys.length > 0) {
|
if (newKeys.length > 0 || newStaticKeys.length > 0) {
|
||||||
const questions = [];
|
const questions = [];
|
||||||
|
|
@ -356,7 +339,7 @@ class ConfigCollector {
|
||||||
Object.assign(allAnswers, promptedAnswers);
|
Object.assign(allAnswers, promptedAnswers);
|
||||||
} else if (newStaticKeys.length > 0) {
|
} else if (newStaticKeys.length > 0) {
|
||||||
// Only static fields, no questions - show no config message
|
// Only static fields, no questions - show no config message
|
||||||
console.log(chalk.dim(` ✓ ${moduleName.toUpperCase()} module configuration updated`));
|
CLIUtils.displayModuleNoConfig(moduleName, moduleConfig.header, moduleConfig.subheader);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store all answers for cross-referencing
|
// Store all answers for cross-referencing
|
||||||
|
|
@ -575,60 +558,21 @@ class ConfigCollector {
|
||||||
// Collect all answers (static + prompted)
|
// Collect all answers (static + prompted)
|
||||||
let allAnswers = { ...staticAnswers };
|
let allAnswers = { ...staticAnswers };
|
||||||
|
|
||||||
// If there are questions to ask, prompt for accepting defaults vs customizing
|
// Display appropriate header based on whether there are questions
|
||||||
if (questions.length > 0) {
|
if (questions.length > 0) {
|
||||||
// Get friendly module name from config or use uppercase module name
|
CLIUtils.displayModuleConfigHeader(moduleName, moduleConfig.header, moduleConfig.subheader);
|
||||||
const moduleDisplayName = moduleConfig.header || `${moduleName.toUpperCase()} Module`;
|
console.log(); // Line break before questions
|
||||||
|
const promptedAnswers = await inquirer.prompt(questions);
|
||||||
|
|
||||||
// Add blank line for better readability
|
// Merge prompted answers with static answers
|
||||||
console.log();
|
Object.assign(allAnswers, promptedAnswers);
|
||||||
|
|
||||||
// Display the module name in color first
|
|
||||||
console.log(chalk.cyan('?') + ' ' + chalk.magenta(moduleDisplayName));
|
|
||||||
|
|
||||||
// Ask user if they want to accept defaults or customize on the next line
|
|
||||||
const { customize } = await inquirer.prompt([
|
|
||||||
{
|
|
||||||
type: 'confirm',
|
|
||||||
name: 'customize',
|
|
||||||
message: 'Accept Defaults (no to customize)?',
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (customize) {
|
|
||||||
// Accept defaults - only ask questions that have NO default value
|
|
||||||
const questionsWithoutDefaults = questions.filter((q) => q.default === undefined || q.default === null || q.default === '');
|
|
||||||
|
|
||||||
if (questionsWithoutDefaults.length > 0) {
|
|
||||||
console.log(chalk.dim(`\n Asking required questions for ${moduleName.toUpperCase()}...`));
|
|
||||||
const promptedAnswers = await inquirer.prompt(questionsWithoutDefaults);
|
|
||||||
Object.assign(allAnswers, promptedAnswers);
|
|
||||||
}
|
|
||||||
|
|
||||||
// For questions with defaults that weren't asked, we need to process them with their default values
|
|
||||||
const questionsWithDefaults = questions.filter((q) => q.default !== undefined && q.default !== null && q.default !== '');
|
|
||||||
for (const question of questionsWithDefaults) {
|
|
||||||
// Skip function defaults - these are dynamic and will be evaluated later
|
|
||||||
if (typeof question.default === 'function') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
allAnswers[question.name] = question.default;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Customize - ask all questions
|
|
||||||
console.log(chalk.dim(`\n Configuring ${moduleName.toUpperCase()}...`));
|
|
||||||
const promptedAnswers = await inquirer.prompt(questions);
|
|
||||||
Object.assign(allAnswers, promptedAnswers);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store all answers for cross-referencing
|
// Store all answers for cross-referencing
|
||||||
Object.assign(this.allAnswers, allAnswers);
|
Object.assign(this.allAnswers, allAnswers);
|
||||||
|
|
||||||
// Process all answers (both static and prompted)
|
// Process all answers (both static and prompted)
|
||||||
// Always process if we have any answers or static answers
|
if (Object.keys(allAnswers).length > 0) {
|
||||||
if (Object.keys(allAnswers).length > 0 || Object.keys(staticAnswers).length > 0) {
|
|
||||||
const answers = allAnswers;
|
const answers = allAnswers;
|
||||||
|
|
||||||
// Process answers and build result values
|
// Process answers and build result values
|
||||||
|
|
@ -727,68 +671,8 @@ class ConfigCollector {
|
||||||
|
|
||||||
// No longer display completion boxes - keep output clean
|
// No longer display completion boxes - keep output clean
|
||||||
} else {
|
} else {
|
||||||
// No questions for this module - show completion message with header if available
|
// No questions for this module - show completion message
|
||||||
const moduleDisplayName = moduleConfig.header || `${moduleName.toUpperCase()} Module`;
|
CLIUtils.displayModuleNoConfig(moduleName, moduleConfig.header, moduleConfig.subheader);
|
||||||
|
|
||||||
// Check if this module has NO configuration keys at all (like CIS)
|
|
||||||
// Filter out metadata fields and only count actual config objects
|
|
||||||
const metadataFields = new Set(['code', 'name', 'header', 'subheader', 'default_selected']);
|
|
||||||
const actualConfigKeys = configKeys.filter((key) => !metadataFields.has(key));
|
|
||||||
const hasNoConfig = actualConfigKeys.length === 0;
|
|
||||||
|
|
||||||
if (hasNoConfig && (moduleConfig.subheader || moduleConfig.header)) {
|
|
||||||
// Module explicitly has no configuration - show with special styling
|
|
||||||
// Add blank line for better readability (matches other modules)
|
|
||||||
console.log();
|
|
||||||
|
|
||||||
// Display the module name in color first (matches other modules)
|
|
||||||
console.log(chalk.cyan('?') + ' ' + chalk.magenta(moduleDisplayName));
|
|
||||||
|
|
||||||
// Ask user if they want to accept defaults or customize on the next line
|
|
||||||
const { customize } = await inquirer.prompt([
|
|
||||||
{
|
|
||||||
type: 'confirm',
|
|
||||||
name: 'customize',
|
|
||||||
message: 'Accept Defaults (no to customize)?',
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Show the subheader if available, otherwise show a default message
|
|
||||||
if (moduleConfig.subheader) {
|
|
||||||
console.log(chalk.dim(` ✓ ${moduleConfig.subheader}`));
|
|
||||||
} else {
|
|
||||||
console.log(chalk.dim(` ✓ No custom configuration required`));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Module has config but just no questions to ask
|
|
||||||
console.log(chalk.dim(` ✓ ${moduleName.toUpperCase()} module configured`));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we have no collected config for this module, but we have a module schema,
|
|
||||||
// ensure we have at least an empty object
|
|
||||||
if (!this.collectedConfig[moduleName]) {
|
|
||||||
this.collectedConfig[moduleName] = {};
|
|
||||||
|
|
||||||
// If we accepted defaults and have no answers, we still need to check
|
|
||||||
// if there are any static values in the schema that should be applied
|
|
||||||
if (moduleConfig) {
|
|
||||||
for (const key of Object.keys(moduleConfig)) {
|
|
||||||
if (key !== 'prompt' && moduleConfig[key] && typeof moduleConfig[key] === 'object') {
|
|
||||||
const item = moduleConfig[key];
|
|
||||||
// For static items (no prompt, just result), apply the result
|
|
||||||
if (!item.prompt && item.result) {
|
|
||||||
// Apply any placeholder replacements to the result
|
|
||||||
let result = item.result;
|
|
||||||
if (typeof result === 'string') {
|
|
||||||
result = this.replacePlaceholders(result, moduleName, moduleConfig);
|
|
||||||
}
|
|
||||||
this.collectedConfig[moduleName][key] = result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,10 +40,7 @@ class CustomModuleCache {
|
||||||
*/
|
*/
|
||||||
async updateCacheManifest(manifest) {
|
async updateCacheManifest(manifest) {
|
||||||
const yaml = require('yaml');
|
const yaml = require('yaml');
|
||||||
// Clean the manifest to remove any non-serializable values
|
const content = yaml.stringify(manifest, {
|
||||||
const cleanManifest = structuredClone(manifest);
|
|
||||||
|
|
||||||
const content = yaml.stringify(cleanManifest, {
|
|
||||||
indent: 2,
|
indent: 2,
|
||||||
lineWidth: 0,
|
lineWidth: 0,
|
||||||
sortKeys: false,
|
sortKeys: false,
|
||||||
|
|
@ -147,18 +144,12 @@ class CustomModuleCache {
|
||||||
const sourceHash = await this.calculateHash(sourcePath);
|
const sourceHash = await this.calculateHash(sourcePath);
|
||||||
const cacheHash = await this.calculateHash(cacheDir);
|
const cacheHash = await this.calculateHash(cacheDir);
|
||||||
|
|
||||||
// Update manifest - don't store absolute paths for portability
|
// Update manifest - don't store originalPath for source control friendliness
|
||||||
// Clean metadata to remove absolute paths
|
|
||||||
const cleanMetadata = { ...metadata };
|
|
||||||
if (cleanMetadata.sourcePath) {
|
|
||||||
delete cleanMetadata.sourcePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
cacheManifest[moduleId] = {
|
cacheManifest[moduleId] = {
|
||||||
originalHash: sourceHash,
|
originalHash: sourceHash,
|
||||||
cacheHash: cacheHash,
|
cacheHash: cacheHash,
|
||||||
cachedAt: new Date().toISOString(),
|
cachedAt: new Date().toISOString(),
|
||||||
...cleanMetadata,
|
...metadata,
|
||||||
};
|
};
|
||||||
|
|
||||||
await this.updateCacheManifest(cacheManifest);
|
await this.updateCacheManifest(cacheManifest);
|
||||||
|
|
|
||||||
|
|
@ -61,10 +61,7 @@ class IdeConfigManager {
|
||||||
configuration: configuration || {},
|
configuration: configuration || {},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Clean the config to remove any non-serializable values (like functions)
|
const yamlContent = yaml.stringify(configData, {
|
||||||
const cleanConfig = structuredClone(configData);
|
|
||||||
|
|
||||||
const yamlContent = yaml.stringify(cleanConfig, {
|
|
||||||
indent: 2,
|
indent: 2,
|
||||||
lineWidth: 0,
|
lineWidth: 0,
|
||||||
sortKeys: false,
|
sortKeys: false,
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ class Installer {
|
||||||
this.dependencyResolver = new DependencyResolver();
|
this.dependencyResolver = new DependencyResolver();
|
||||||
this.configCollector = new ConfigCollector();
|
this.configCollector = new ConfigCollector();
|
||||||
this.ideConfigManager = new IdeConfigManager();
|
this.ideConfigManager = new IdeConfigManager();
|
||||||
this.installedFiles = new Set(); // Track all installed files
|
this.installedFiles = []; // Track all installed files
|
||||||
this.ttsInjectedFiles = []; // Track files with TTS injection applied
|
this.ttsInjectedFiles = []; // Track files with TTS injection applied
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -394,20 +394,11 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
// Clone config to avoid mutating the caller's object
|
// Clone config to avoid mutating the caller's object
|
||||||
const config = { ...originalConfig };
|
const config = { ...originalConfig };
|
||||||
|
|
||||||
// Check if core config was already collected in UI
|
// Display BMAD logo
|
||||||
const hasCoreConfig = config.coreConfig && Object.keys(config.coreConfig).length > 0;
|
CLIUtils.displayLogo();
|
||||||
|
|
||||||
// Only display logo if core config wasn't already collected (meaning we're not continuing from UI)
|
// Display welcome message
|
||||||
if (hasCoreConfig) {
|
CLIUtils.displaySection('BMAD™ Installation', 'Version ' + require(path.join(getProjectRoot(), 'package.json')).version);
|
||||||
// Core config was already collected in UI, show smooth continuation
|
|
||||||
// Don't clear screen, just continue flow
|
|
||||||
} else {
|
|
||||||
// Display BMAD logo
|
|
||||||
CLIUtils.displayLogo();
|
|
||||||
|
|
||||||
// Display welcome message
|
|
||||||
CLIUtils.displaySection('BMad™ Installation', 'Version ' + require(path.join(getProjectRoot(), 'package.json')).version);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note: Legacy V4 detection now happens earlier in UI.promptInstall()
|
// Note: Legacy V4 detection now happens earlier in UI.promptInstall()
|
||||||
// before any config collection, so we don't need to check again here
|
// before any config collection, so we don't need to check again here
|
||||||
|
|
@ -415,7 +406,7 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
const projectDir = path.resolve(config.directory);
|
const projectDir = path.resolve(config.directory);
|
||||||
|
|
||||||
// If core config was pre-collected (from interactive mode), use it
|
// If core config was pre-collected (from interactive mode), use it
|
||||||
if (config.coreConfig && Object.keys(config.coreConfig).length > 0) {
|
if (config.coreConfig) {
|
||||||
this.configCollector.collectedConfig.core = config.coreConfig;
|
this.configCollector.collectedConfig.core = config.coreConfig;
|
||||||
// Also store in allAnswers for cross-referencing
|
// Also store in allAnswers for cross-referencing
|
||||||
this.configCollector.allAnswers = {};
|
this.configCollector.allAnswers = {};
|
||||||
|
|
@ -426,20 +417,12 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
|
|
||||||
// Collect configurations for modules (skip if quick update already collected them)
|
// Collect configurations for modules (skip if quick update already collected them)
|
||||||
let moduleConfigs;
|
let moduleConfigs;
|
||||||
let customModulePaths = new Map();
|
|
||||||
|
|
||||||
if (config._quickUpdate) {
|
if (config._quickUpdate) {
|
||||||
// Quick update already collected all configs, use them directly
|
// Quick update already collected all configs, use them directly
|
||||||
moduleConfigs = this.configCollector.collectedConfig;
|
moduleConfigs = this.configCollector.collectedConfig;
|
||||||
|
|
||||||
// For quick update, populate customModulePaths from _customModuleSources
|
|
||||||
if (config._customModuleSources) {
|
|
||||||
for (const [moduleId, customInfo] of config._customModuleSources) {
|
|
||||||
customModulePaths.set(moduleId, customInfo.sourcePath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Build custom module paths map from customContent
|
// Build custom module paths map from customContent
|
||||||
|
const customModulePaths = new Map();
|
||||||
|
|
||||||
// Handle selectedFiles (from existing install path or manual directory input)
|
// Handle selectedFiles (from existing install path or manual directory input)
|
||||||
if (config.customContent && config.customContent.selected && config.customContent.selectedFiles) {
|
if (config.customContent && config.customContent.selected && config.customContent.selectedFiles) {
|
||||||
|
|
@ -452,13 +435,6 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle new custom content sources from UI
|
|
||||||
if (config.customContent && config.customContent.sources) {
|
|
||||||
for (const source of config.customContent.sources) {
|
|
||||||
customModulePaths.set(source.id, source.path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle cachedModules (from new install path where modules are cached)
|
// Handle cachedModules (from new install path where modules are cached)
|
||||||
// Only include modules that were actually selected for installation
|
// Only include modules that were actually selected for installation
|
||||||
if (config.customContent && config.customContent.cachedModules) {
|
if (config.customContent && config.customContent.cachedModules) {
|
||||||
|
|
@ -480,33 +456,17 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get list of all modules including custom modules
|
// Get list of all modules including custom modules
|
||||||
// Order: core first, then official modules, then custom modules
|
const allModulesForConfig = [...(config.modules || [])];
|
||||||
const allModulesForConfig = ['core'];
|
|
||||||
|
|
||||||
// Add official modules (excluding core and any custom modules)
|
|
||||||
const officialModules = (config.modules || []).filter((m) => m !== 'core' && !customModulePaths.has(m));
|
|
||||||
allModulesForConfig.push(...officialModules);
|
|
||||||
|
|
||||||
// Add custom modules at the end
|
|
||||||
for (const [moduleId] of customModulePaths) {
|
for (const [moduleId] of customModulePaths) {
|
||||||
if (!allModulesForConfig.includes(moduleId)) {
|
if (!allModulesForConfig.includes(moduleId)) {
|
||||||
allModulesForConfig.push(moduleId);
|
allModulesForConfig.push(moduleId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if core was already collected in UI
|
// Regular install - collect configurations (core was already collected in UI.promptInstall if interactive)
|
||||||
if (config.coreConfig && Object.keys(config.coreConfig).length > 0) {
|
moduleConfigs = await this.configCollector.collectAllConfigurations(allModulesForConfig, path.resolve(config.directory), {
|
||||||
// Core already collected, skip it in config collection
|
customModulePaths,
|
||||||
const modulesWithoutCore = allModulesForConfig.filter((m) => m !== 'core');
|
});
|
||||||
moduleConfigs = await this.configCollector.collectAllConfigurations(modulesWithoutCore, path.resolve(config.directory), {
|
|
||||||
customModulePaths,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// Core not collected yet, include it
|
|
||||||
moduleConfigs = await this.configCollector.collectAllConfigurations(allModulesForConfig, path.resolve(config.directory), {
|
|
||||||
customModulePaths,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always use _bmad as the folder name
|
// Always use _bmad as the folder name
|
||||||
|
|
@ -519,7 +479,6 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
// Set bmad folder name on module manager and IDE manager for placeholder replacement
|
// Set bmad folder name on module manager and IDE manager for placeholder replacement
|
||||||
this.moduleManager.setBmadFolderName(bmadFolderName);
|
this.moduleManager.setBmadFolderName(bmadFolderName);
|
||||||
this.moduleManager.setCoreConfig(moduleConfigs.core || {});
|
this.moduleManager.setCoreConfig(moduleConfigs.core || {});
|
||||||
this.moduleManager.setCustomModulePaths(customModulePaths);
|
|
||||||
this.ideManager.setBmadFolderName(bmadFolderName);
|
this.ideManager.setBmadFolderName(bmadFolderName);
|
||||||
|
|
||||||
// Tool selection will be collected after we determine if it's a reinstall/update/new install
|
// Tool selection will be collected after we determine if it's a reinstall/update/new install
|
||||||
|
|
@ -570,7 +529,9 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
|
|
||||||
// Check if user already decided what to do (from early menu in ui.js)
|
// Check if user already decided what to do (from early menu in ui.js)
|
||||||
let action = null;
|
let action = null;
|
||||||
if (config.actionType === 'update') {
|
if (config._requestedReinstall) {
|
||||||
|
action = 'reinstall';
|
||||||
|
} else if (config.actionType === 'update') {
|
||||||
action = 'update';
|
action = 'update';
|
||||||
} else {
|
} else {
|
||||||
// Fallback: Ask the user (backwards compatibility for other code paths)
|
// Fallback: Ask the user (backwards compatibility for other code paths)
|
||||||
|
|
@ -582,7 +543,41 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
action = promptResult.action;
|
action = promptResult.action;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action === 'update') {
|
if (action === 'cancel') {
|
||||||
|
console.log('Installation cancelled.');
|
||||||
|
return { success: false, cancelled: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action === 'reinstall') {
|
||||||
|
// Warn about destructive operation
|
||||||
|
console.log(chalk.red.bold('\n⚠️ WARNING: This is a destructive operation!'));
|
||||||
|
console.log(chalk.red('All custom files and modifications in the bmad directory will be lost.'));
|
||||||
|
|
||||||
|
const inquirer = require('inquirer');
|
||||||
|
const { confirmReinstall } = await inquirer.prompt([
|
||||||
|
{
|
||||||
|
type: 'confirm',
|
||||||
|
name: 'confirmReinstall',
|
||||||
|
message: chalk.yellow('Are you sure you want to delete and reinstall?'),
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!confirmReinstall) {
|
||||||
|
console.log('Installation cancelled.');
|
||||||
|
return { success: false, cancelled: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remember previously configured IDEs before deleting
|
||||||
|
config._previouslyConfiguredIdes = existingInstall.ides || [];
|
||||||
|
|
||||||
|
// Remove existing installation
|
||||||
|
await fs.remove(bmadDir);
|
||||||
|
console.log(chalk.green('✓ Removed existing installation\n'));
|
||||||
|
|
||||||
|
// Mark this as a full reinstall so we re-collect IDE configurations
|
||||||
|
config._isFullReinstall = true;
|
||||||
|
} else if (action === 'update') {
|
||||||
// Store that we're updating for later processing
|
// Store that we're updating for later processing
|
||||||
config._isUpdate = true;
|
config._isUpdate = true;
|
||||||
config._existingInstall = existingInstall;
|
config._existingInstall = existingInstall;
|
||||||
|
|
@ -738,26 +733,6 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
spinner.text = 'Creating directory structure...';
|
spinner.text = 'Creating directory structure...';
|
||||||
await this.createDirectoryStructure(bmadDir);
|
await this.createDirectoryStructure(bmadDir);
|
||||||
|
|
||||||
// Cache custom modules if any
|
|
||||||
if (customModulePaths && customModulePaths.size > 0) {
|
|
||||||
spinner.text = 'Caching custom modules...';
|
|
||||||
const { CustomModuleCache } = require('./custom-module-cache');
|
|
||||||
const customCache = new CustomModuleCache(bmadDir);
|
|
||||||
|
|
||||||
for (const [moduleId, sourcePath] of customModulePaths) {
|
|
||||||
const cachedInfo = await customCache.cacheModule(moduleId, sourcePath, {
|
|
||||||
sourcePath: sourcePath, // Store original path for updates
|
|
||||||
});
|
|
||||||
|
|
||||||
// Update the customModulePaths to use the cached location
|
|
||||||
customModulePaths.set(moduleId, cachedInfo.cachePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update module manager with the cached paths
|
|
||||||
this.moduleManager.setCustomModulePaths(customModulePaths);
|
|
||||||
spinner.succeed('Custom modules cached');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get project root
|
// Get project root
|
||||||
const projectRoot = getProjectRoot();
|
const projectRoot = getProjectRoot();
|
||||||
|
|
||||||
|
|
@ -815,20 +790,6 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
|
|
||||||
const modulesToInstall = allModules;
|
const modulesToInstall = allModules;
|
||||||
|
|
||||||
// For dependency resolution, we only need regular modules (not custom modules)
|
|
||||||
// Custom modules are already installed in _bmad and don't need dependency resolution from source
|
|
||||||
const regularModulesForResolution = allModules.filter((module) => {
|
|
||||||
// Check if this is a custom module
|
|
||||||
const isCustom =
|
|
||||||
customModulePaths.has(module) ||
|
|
||||||
(finalCustomContent && finalCustomContent.cachedModules && finalCustomContent.cachedModules.some((cm) => cm.id === module)) ||
|
|
||||||
(finalCustomContent &&
|
|
||||||
finalCustomContent.selected &&
|
|
||||||
finalCustomContent.selectedFiles &&
|
|
||||||
finalCustomContent.selectedFiles.some((f) => f.includes(module)));
|
|
||||||
return !isCustom;
|
|
||||||
});
|
|
||||||
|
|
||||||
// For dependency resolution, we need to pass the project root
|
// For dependency resolution, we need to pass the project root
|
||||||
// Create a temporary module manager that knows about custom content locations
|
// Create a temporary module manager that knows about custom content locations
|
||||||
const tempModuleManager = new ModuleManager({
|
const tempModuleManager = new ModuleManager({
|
||||||
|
|
@ -836,12 +797,24 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
bmadDir: bmadDir, // Pass bmadDir so we can check cache
|
bmadDir: bmadDir, // Pass bmadDir so we can check cache
|
||||||
});
|
});
|
||||||
|
|
||||||
const resolution = await this.dependencyResolver.resolve(projectRoot, regularModulesForResolution, {
|
// Make sure custom modules are discoverable
|
||||||
|
if (config.customContent && config.customContent.selected && config.customContent.selectedFiles) {
|
||||||
|
// The dependency resolver needs to know about these modules
|
||||||
|
// We'll handle custom modules separately in the installation loop
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolution = await this.dependencyResolver.resolve(projectRoot, allModules, {
|
||||||
verbose: config.verbose,
|
verbose: config.verbose,
|
||||||
moduleManager: tempModuleManager,
|
moduleManager: tempModuleManager,
|
||||||
});
|
});
|
||||||
|
|
||||||
spinner.succeed('Dependencies resolved');
|
if (config.verbose) {
|
||||||
|
spinner.succeed('Dependencies resolved');
|
||||||
|
} else {
|
||||||
|
spinner.succeed('Dependencies resolved');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Core is already installed above, skip if included in resolution
|
||||||
|
|
||||||
// Install modules with their dependencies
|
// Install modules with their dependencies
|
||||||
if (allModules && allModules.length > 0) {
|
if (allModules && allModules.length > 0) {
|
||||||
|
|
@ -854,9 +827,7 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
}
|
}
|
||||||
installedModuleNames.add(moduleName);
|
installedModuleNames.add(moduleName);
|
||||||
|
|
||||||
// Show appropriate message based on whether this is a quick update
|
spinner.start(`Installing module: ${moduleName}...`);
|
||||||
const isQuickUpdate = config._quickUpdate || false;
|
|
||||||
spinner.start(`${isQuickUpdate ? 'Updating' : 'Installing'} module: ${moduleName}...`);
|
|
||||||
|
|
||||||
// Check if this is a custom module
|
// Check if this is a custom module
|
||||||
let isCustomModule = false;
|
let isCustomModule = false;
|
||||||
|
|
@ -909,36 +880,103 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isCustomModule && customInfo) {
|
if (isCustomModule && customInfo) {
|
||||||
// Custom modules are now installed via ModuleManager just like standard modules
|
// Install custom module using CustomHandler but as a proper module
|
||||||
// The custom module path should already be in customModulePaths from earlier setup
|
const customHandler = new CustomHandler();
|
||||||
if (!customModulePaths.has(moduleName) && customInfo.path) {
|
|
||||||
customModulePaths.set(moduleName, customInfo.path);
|
// Install to module directory instead of custom directory
|
||||||
this.moduleManager.setCustomModulePaths(customModulePaths);
|
const moduleTargetPath = path.join(bmadDir, moduleName);
|
||||||
}
|
await fs.ensureDir(moduleTargetPath);
|
||||||
|
|
||||||
// Get collected config for this custom module (from module.yaml prompts)
|
// Get collected config for this custom module (from module.yaml prompts)
|
||||||
const collectedModuleConfig = moduleConfigs[moduleName] || {};
|
const collectedModuleConfig = moduleConfigs[moduleName] || {};
|
||||||
|
|
||||||
// Use ModuleManager to install the custom module
|
const result = await customHandler.install(
|
||||||
await this.moduleManager.install(
|
customInfo.path,
|
||||||
moduleName,
|
path.join(bmadDir, 'temp-custom'),
|
||||||
bmadDir,
|
{ ...config.coreConfig, ...customInfo.config, ...collectedModuleConfig, _bmadDir: bmadDir },
|
||||||
(filePath) => {
|
(filePath) => {
|
||||||
this.installedFiles.add(filePath);
|
// Track installed files with correct path
|
||||||
},
|
const relativePath = path.relative(path.join(bmadDir, 'temp-custom'), filePath);
|
||||||
{
|
const finalPath = path.join(moduleTargetPath, relativePath);
|
||||||
isCustom: true,
|
this.installedFiles.push(finalPath);
|
||||||
moduleConfig: collectedModuleConfig,
|
|
||||||
isQuickUpdate: config._quickUpdate || false,
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// ModuleManager installs directly to the target directory, no need to move files
|
// Move from temp-custom to actual module directory
|
||||||
|
const tempCustomPath = path.join(bmadDir, 'temp-custom');
|
||||||
|
if (await fs.pathExists(tempCustomPath)) {
|
||||||
|
const customDir = path.join(tempCustomPath, 'custom');
|
||||||
|
if (await fs.pathExists(customDir)) {
|
||||||
|
// Move contents to module directory
|
||||||
|
const items = await fs.readdir(customDir);
|
||||||
|
const movedItems = [];
|
||||||
|
try {
|
||||||
|
for (const item of items) {
|
||||||
|
const srcPath = path.join(customDir, item);
|
||||||
|
const destPath = path.join(moduleTargetPath, item);
|
||||||
|
|
||||||
|
// If destination exists, remove it first (or we could merge)
|
||||||
|
if (await fs.pathExists(destPath)) {
|
||||||
|
await fs.remove(destPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
await fs.move(srcPath, destPath);
|
||||||
|
movedItems.push({ src: srcPath, dest: destPath });
|
||||||
|
}
|
||||||
|
} catch (moveError) {
|
||||||
|
// Rollback: restore any successfully moved items
|
||||||
|
for (const moved of movedItems) {
|
||||||
|
try {
|
||||||
|
await fs.move(moved.dest, moved.src);
|
||||||
|
} catch {
|
||||||
|
// Best-effort rollback - log if it fails
|
||||||
|
console.error(`Failed to rollback ${moved.dest} during cleanup`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Error(`Failed to move custom module files: ${moveError.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await fs.remove(tempCustomPath);
|
||||||
|
} catch (cleanupError) {
|
||||||
|
// Non-fatal: temp directory cleanup failed but files were moved successfully
|
||||||
|
console.warn(`Warning: Could not clean up temp directory: ${cleanupError.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create module config (include collected config from module.yaml prompts)
|
// Create module config (include collected config from module.yaml prompts)
|
||||||
await this.generateModuleConfigs(bmadDir, {
|
await this.generateModuleConfigs(bmadDir, {
|
||||||
[moduleName]: { ...config.coreConfig, ...customInfo.config, ...collectedModuleConfig },
|
[moduleName]: { ...config.coreConfig, ...customInfo.config, ...collectedModuleConfig },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Store custom module info for later manifest update
|
||||||
|
if (!config._customModulesToTrack) {
|
||||||
|
config._customModulesToTrack = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// For cached modules, use appropriate path handling
|
||||||
|
let sourcePath;
|
||||||
|
if (useCache) {
|
||||||
|
// Check if we have cached modules info (from initial install)
|
||||||
|
if (finalCustomContent && finalCustomContent.cachedModules) {
|
||||||
|
sourcePath = finalCustomContent.cachedModules.find((m) => m.id === moduleName)?.relativePath;
|
||||||
|
} else {
|
||||||
|
// During update, the sourcePath is already cache-relative if it starts with _config
|
||||||
|
sourcePath =
|
||||||
|
customInfo.sourcePath && customInfo.sourcePath.startsWith('_config')
|
||||||
|
? customInfo.sourcePath
|
||||||
|
: path.relative(bmadDir, customInfo.path || customInfo.sourcePath);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sourcePath = path.resolve(customInfo.path || customInfo.sourcePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
config._customModulesToTrack.push({
|
||||||
|
id: customInfo.id,
|
||||||
|
name: customInfo.name,
|
||||||
|
sourcePath: sourcePath,
|
||||||
|
installDate: new Date().toISOString(),
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
// Regular module installation
|
// Regular module installation
|
||||||
// Special case for core module
|
// Special case for core module
|
||||||
|
|
@ -949,7 +987,7 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
spinner.succeed(`Module ${isQuickUpdate ? 'updated' : 'installed'}: ${moduleName}`);
|
spinner.succeed(`Module installed: ${moduleName}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Install partial modules (only dependencies)
|
// Install partial modules (only dependencies)
|
||||||
|
|
@ -971,7 +1009,69 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// All content is now installed as modules - no separate custom content handling needed
|
// Install custom content if provided AND selected
|
||||||
|
// Process custom content that wasn't installed as modules
|
||||||
|
// This is now handled in the module installation loop above
|
||||||
|
// This section is kept for backward compatibility with any custom content
|
||||||
|
// that doesn't have a module structure
|
||||||
|
const remainingCustomContent = [];
|
||||||
|
if (
|
||||||
|
config.customContent &&
|
||||||
|
config.customContent.hasCustomContent &&
|
||||||
|
config.customContent.customPath &&
|
||||||
|
config.customContent.selected &&
|
||||||
|
config.customContent.selectedFiles
|
||||||
|
) {
|
||||||
|
// Filter out custom modules that were already installed
|
||||||
|
const customHandler = new CustomHandler();
|
||||||
|
for (const customFile of config.customContent.selectedFiles) {
|
||||||
|
const customInfo = await customHandler.getCustomInfo(customFile, projectDir);
|
||||||
|
|
||||||
|
// Skip if this was installed as a module
|
||||||
|
if (!customInfo || !customInfo.id || !allModules.includes(customInfo.id)) {
|
||||||
|
remainingCustomContent.push(customFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (remainingCustomContent.length > 0) {
|
||||||
|
spinner.start('Installing remaining custom content...');
|
||||||
|
const customHandler = new CustomHandler();
|
||||||
|
|
||||||
|
// Use the remaining files
|
||||||
|
const customFiles = remainingCustomContent;
|
||||||
|
|
||||||
|
if (customFiles.length > 0) {
|
||||||
|
console.log(chalk.cyan(`\n Found ${customFiles.length} custom content file(s):`));
|
||||||
|
for (const customFile of customFiles) {
|
||||||
|
const customInfo = await customHandler.getCustomInfo(customFile, projectDir);
|
||||||
|
if (customInfo) {
|
||||||
|
console.log(chalk.dim(` • ${customInfo.name} (${customInfo.relativePath})`));
|
||||||
|
|
||||||
|
// Install the custom content
|
||||||
|
const result = await customHandler.install(
|
||||||
|
customInfo.path,
|
||||||
|
bmadDir,
|
||||||
|
{ ...config.coreConfig, ...customInfo.config },
|
||||||
|
(filePath) => {
|
||||||
|
// Track installed files
|
||||||
|
this.installedFiles.push(filePath);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result.errors.length > 0) {
|
||||||
|
console.log(chalk.yellow(` ⚠️ ${result.errors.length} error(s) occurred`));
|
||||||
|
for (const error of result.errors) {
|
||||||
|
console.log(chalk.dim(` - ${error}`));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log(chalk.green(` ✓ Installed ${result.agentsInstalled} agents, ${result.workflowsInstalled} workflows`));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
spinner.succeed('Custom content installed');
|
||||||
|
}
|
||||||
|
|
||||||
// Generate clean config.yaml files for each installed module
|
// Generate clean config.yaml files for each installed module
|
||||||
spinner.start('Generating module configurations...');
|
spinner.start('Generating module configurations...');
|
||||||
|
|
@ -984,10 +1084,12 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
|
|
||||||
// Pre-register manifest files that will be created (except files-manifest.csv to avoid recursion)
|
// Pre-register manifest files that will be created (except files-manifest.csv to avoid recursion)
|
||||||
const cfgDir = path.join(bmadDir, '_config');
|
const cfgDir = path.join(bmadDir, '_config');
|
||||||
this.installedFiles.add(path.join(cfgDir, 'manifest.yaml'));
|
this.installedFiles.push(
|
||||||
this.installedFiles.add(path.join(cfgDir, 'workflow-manifest.csv'));
|
path.join(cfgDir, 'manifest.yaml'),
|
||||||
this.installedFiles.add(path.join(cfgDir, 'agent-manifest.csv'));
|
path.join(cfgDir, 'workflow-manifest.csv'),
|
||||||
this.installedFiles.add(path.join(cfgDir, 'task-manifest.csv'));
|
path.join(cfgDir, 'agent-manifest.csv'),
|
||||||
|
path.join(cfgDir, 'task-manifest.csv'),
|
||||||
|
);
|
||||||
|
|
||||||
// Generate CSV manifests for workflows, agents, tasks AND ALL FILES with hashes BEFORE IDE setup
|
// Generate CSV manifests for workflows, agents, tasks AND ALL FILES with hashes BEFORE IDE setup
|
||||||
spinner.start('Generating workflow and agent manifests...');
|
spinner.start('Generating workflow and agent manifests...');
|
||||||
|
|
@ -1011,12 +1113,18 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
modulesForCsvPreserve = config._preserveModules ? [...allModules, ...config._preserveModules] : allModules;
|
modulesForCsvPreserve = config._preserveModules ? [...allModules, ...config._preserveModules] : allModules;
|
||||||
}
|
}
|
||||||
|
|
||||||
const manifestStats = await manifestGen.generateManifests(bmadDir, allModulesForManifest, [...this.installedFiles], {
|
const manifestStats = await manifestGen.generateManifests(bmadDir, allModulesForManifest, this.installedFiles, {
|
||||||
ides: config.ides || [],
|
ides: config.ides || [],
|
||||||
preservedModules: modulesForCsvPreserve, // Scan these from installed bmad/ dir
|
preservedModules: modulesForCsvPreserve, // Scan these from installed bmad/ dir
|
||||||
});
|
});
|
||||||
|
|
||||||
// Custom modules are now included in the main modules list - no separate tracking needed
|
// Add custom modules to manifest (now that it exists)
|
||||||
|
if (config._customModulesToTrack && config._customModulesToTrack.length > 0) {
|
||||||
|
spinner.text = 'Storing custom module sources...';
|
||||||
|
for (const customModule of config._customModulesToTrack) {
|
||||||
|
await this.manifest.addCustomModule(bmadDir, customModule);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
spinner.succeed(
|
spinner.succeed(
|
||||||
`Manifests generated: ${manifestStats.workflows} workflows, ${manifestStats.agents} agents, ${manifestStats.tasks} tasks, ${manifestStats.tools} tools, ${manifestStats.files} files`,
|
`Manifests generated: ${manifestStats.workflows} workflows, ${manifestStats.agents} agents, ${manifestStats.tasks} tasks, ${manifestStats.tools} tools, ${manifestStats.files} files`,
|
||||||
|
|
@ -1077,24 +1185,24 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
console.log = originalLog;
|
console.log = originalLog;
|
||||||
|
|
||||||
if (spinner.isSpinning) {
|
if (spinner.isSpinning) {
|
||||||
spinner.succeed(`Configured: ${validIdes.join(', ')}`);
|
spinner.succeed(`Configured ${validIdes.length} IDE${validIdes.length > 1 ? 's' : ''}`);
|
||||||
} else {
|
} else {
|
||||||
console.log(chalk.green(`✓ Configured: ${validIdes.join(', ')}`));
|
console.log(chalk.green(`✓ Configured ${validIdes.length} IDE${validIdes.length > 1 ? 's' : ''}`));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Copy IDE-specific documentation (only for valid IDEs)
|
||||||
|
const validIdesForDocs = (config.ides || []).filter((ide) => ide && typeof ide === 'string');
|
||||||
|
if (validIdesForDocs.length > 0) {
|
||||||
|
spinner.start('Copying IDE documentation...');
|
||||||
|
await this.copyIdeDocumentation(validIdesForDocs, bmadDir);
|
||||||
|
spinner.succeed('IDE documentation copied');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run module-specific installers after IDE setup
|
// Run module-specific installers after IDE setup
|
||||||
spinner.start('Running module-specific installers...');
|
spinner.start('Running module-specific installers...');
|
||||||
|
|
||||||
// Create a conditional logger based on verbose mode
|
|
||||||
const verboseMode = process.env.BMAD_VERBOSE_INSTALL === 'true' || config.verbose;
|
|
||||||
const moduleLogger = {
|
|
||||||
log: (msg) => (verboseMode ? console.log(msg) : {}), // Only log in verbose mode
|
|
||||||
error: (msg) => console.error(msg), // Always show errors
|
|
||||||
warn: (msg) => console.warn(msg), // Always show warnings
|
|
||||||
};
|
|
||||||
|
|
||||||
// Run core module installer if core was installed
|
// Run core module installer if core was installed
|
||||||
if (config.installCore || resolution.byModule.core) {
|
if (config.installCore || resolution.byModule.core) {
|
||||||
spinner.text = 'Running core module installer...';
|
spinner.text = 'Running core module installer...';
|
||||||
|
|
@ -1103,7 +1211,11 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
installedIDEs: config.ides || [],
|
installedIDEs: config.ides || [],
|
||||||
moduleConfig: moduleConfigs.core || {},
|
moduleConfig: moduleConfigs.core || {},
|
||||||
coreConfig: moduleConfigs.core || {},
|
coreConfig: moduleConfigs.core || {},
|
||||||
logger: moduleLogger,
|
logger: {
|
||||||
|
log: (msg) => console.log(msg),
|
||||||
|
error: (msg) => console.error(msg),
|
||||||
|
warn: (msg) => console.warn(msg),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1117,7 +1229,11 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
installedIDEs: config.ides || [],
|
installedIDEs: config.ides || [],
|
||||||
moduleConfig: moduleConfigs[moduleName] || {},
|
moduleConfig: moduleConfigs[moduleName] || {},
|
||||||
coreConfig: moduleConfigs.core || {},
|
coreConfig: moduleConfigs.core || {},
|
||||||
logger: moduleLogger,
|
logger: {
|
||||||
|
log: (msg) => console.log(msg),
|
||||||
|
error: (msg) => console.error(msg),
|
||||||
|
warn: (msg) => console.warn(msg),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1185,20 +1301,20 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
if (customFiles.length > 0) {
|
if (customFiles.length > 0) {
|
||||||
console.log(chalk.cyan(`\n📁 Custom files preserved: ${customFiles.length}`));
|
console.log(chalk.cyan(`\n📁 Custom files preserved: ${customFiles.length}`));
|
||||||
console.log(chalk.dim('The following custom files were found and restored:\n'));
|
console.log(chalk.dim('The following custom files were found and restored:\n'));
|
||||||
for (const customFile of customFiles) {
|
for (const file of customFiles) {
|
||||||
const relativePath = path.relative(projectDir, customFile);
|
console.log(chalk.dim(` - ${path.relative(bmadDir, file)}`));
|
||||||
console.log(chalk.dim(` • ${relativePath}`));
|
|
||||||
}
|
}
|
||||||
|
console.log('');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (modifiedFiles.length > 0) {
|
if (modifiedFiles.length > 0) {
|
||||||
console.log(chalk.yellow(`\n⚠️ User modified files detected: ${modifiedFiles.length}`));
|
console.log(chalk.yellow(`\n⚠️ Modified files detected: ${modifiedFiles.length}`));
|
||||||
console.log(
|
console.log(chalk.dim('The following files were modified and backed up with .bak extension:\n'));
|
||||||
chalk.dim(
|
for (const file of modifiedFiles) {
|
||||||
'\nThese user modified files have been updated with the new version, search the project for .bak files that had your customizations.',
|
console.log(chalk.dim(` - ${file.relativePath} → ${file.relativePath}.bak`));
|
||||||
),
|
}
|
||||||
);
|
console.log(chalk.dim('\nThese files have been updated with the new version.'));
|
||||||
console.log(chalk.dim('Remove these .bak files it no longer needed\n'));
|
console.log(chalk.dim('Review the .bak files to see your changes and merge if needed.\n'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Display completion message
|
// Display completion message
|
||||||
|
|
@ -1251,44 +1367,12 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
|
|
||||||
// Check for custom modules with missing sources before update
|
// Check for custom modules with missing sources before update
|
||||||
const customModuleSources = new Map();
|
const customModuleSources = new Map();
|
||||||
|
|
||||||
// Check manifest for backward compatibility
|
|
||||||
if (existingInstall.customModules) {
|
if (existingInstall.customModules) {
|
||||||
for (const customModule of existingInstall.customModules) {
|
for (const customModule of existingInstall.customModules) {
|
||||||
customModuleSources.set(customModule.id, customModule);
|
customModuleSources.set(customModule.id, customModule);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Also check cache directory
|
|
||||||
const cacheDir = path.join(bmadDir, '_config', 'custom');
|
|
||||||
if (await fs.pathExists(cacheDir)) {
|
|
||||||
const cachedModules = await fs.readdir(cacheDir, { withFileTypes: true });
|
|
||||||
|
|
||||||
for (const cachedModule of cachedModules) {
|
|
||||||
if (cachedModule.isDirectory()) {
|
|
||||||
const moduleId = cachedModule.name;
|
|
||||||
|
|
||||||
// Skip if we already have this module
|
|
||||||
if (customModuleSources.has(moduleId)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const cachedPath = path.join(cacheDir, moduleId);
|
|
||||||
|
|
||||||
// Check if this is actually a custom module (has module.yaml)
|
|
||||||
const moduleYamlPath = path.join(cachedPath, 'module.yaml');
|
|
||||||
if (await fs.pathExists(moduleYamlPath)) {
|
|
||||||
customModuleSources.set(moduleId, {
|
|
||||||
id: moduleId,
|
|
||||||
name: moduleId,
|
|
||||||
sourcePath: path.join('_config', 'custom', moduleId), // Relative path
|
|
||||||
cached: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (customModuleSources.size > 0) {
|
if (customModuleSources.size > 0) {
|
||||||
spinner.stop();
|
spinner.stop();
|
||||||
console.log(chalk.yellow('\nChecking custom module sources before update...'));
|
console.log(chalk.yellow('\nChecking custom module sources before update...'));
|
||||||
|
|
@ -1442,11 +1526,8 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
coreSection = '\n# Core Configuration Values\n';
|
coreSection = '\n# Core Configuration Values\n';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean the config to remove any non-serializable values (like functions)
|
|
||||||
const cleanConfig = structuredClone(finalConfig);
|
|
||||||
|
|
||||||
// Convert config to YAML
|
// Convert config to YAML
|
||||||
let yamlContent = yaml.stringify(cleanConfig, {
|
let yamlContent = yaml.stringify(finalConfig, {
|
||||||
indent: 2,
|
indent: 2,
|
||||||
lineWidth: 0,
|
lineWidth: 0,
|
||||||
minContentWidth: 0,
|
minContentWidth: 0,
|
||||||
|
|
@ -1481,7 +1562,7 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
await fs.writeFile(configPath, content.endsWith('\n') ? content : content + '\n', 'utf8');
|
await fs.writeFile(configPath, content.endsWith('\n') ? content : content + '\n', 'utf8');
|
||||||
|
|
||||||
// Track the config file in installedFiles
|
// Track the config file in installedFiles
|
||||||
this.installedFiles.add(configPath);
|
this.installedFiles.push(configPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1520,7 +1601,7 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
moduleName,
|
moduleName,
|
||||||
bmadDir,
|
bmadDir,
|
||||||
(filePath) => {
|
(filePath) => {
|
||||||
this.installedFiles.add(filePath);
|
this.installedFiles.push(filePath);
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
skipModuleInstaller: true, // We'll run it later after IDE setup
|
skipModuleInstaller: true, // We'll run it later after IDE setup
|
||||||
|
|
@ -1557,7 +1638,7 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
|
|
||||||
if (await fs.pathExists(sourcePath)) {
|
if (await fs.pathExists(sourcePath)) {
|
||||||
await this.copyFileWithPlaceholderReplacement(sourcePath, targetPath, this.bmadFolderName || 'bmad');
|
await this.copyFileWithPlaceholderReplacement(sourcePath, targetPath, this.bmadFolderName || 'bmad');
|
||||||
this.installedFiles.add(targetPath);
|
this.installedFiles.push(targetPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1573,7 +1654,7 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
|
|
||||||
if (await fs.pathExists(sourcePath)) {
|
if (await fs.pathExists(sourcePath)) {
|
||||||
await this.copyFileWithPlaceholderReplacement(sourcePath, targetPath, this.bmadFolderName || 'bmad');
|
await this.copyFileWithPlaceholderReplacement(sourcePath, targetPath, this.bmadFolderName || 'bmad');
|
||||||
this.installedFiles.add(targetPath);
|
this.installedFiles.push(targetPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1589,7 +1670,7 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
|
|
||||||
if (await fs.pathExists(sourcePath)) {
|
if (await fs.pathExists(sourcePath)) {
|
||||||
await this.copyFileWithPlaceholderReplacement(sourcePath, targetPath, this.bmadFolderName || 'bmad');
|
await this.copyFileWithPlaceholderReplacement(sourcePath, targetPath, this.bmadFolderName || 'bmad');
|
||||||
this.installedFiles.add(targetPath);
|
this.installedFiles.push(targetPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1605,7 +1686,7 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
|
|
||||||
if (await fs.pathExists(sourcePath)) {
|
if (await fs.pathExists(sourcePath)) {
|
||||||
await this.copyFileWithPlaceholderReplacement(sourcePath, targetPath, this.bmadFolderName || 'bmad');
|
await this.copyFileWithPlaceholderReplacement(sourcePath, targetPath, this.bmadFolderName || 'bmad');
|
||||||
this.installedFiles.add(targetPath);
|
this.installedFiles.push(targetPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1620,7 +1701,7 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
|
|
||||||
if (await fs.pathExists(dataPath)) {
|
if (await fs.pathExists(dataPath)) {
|
||||||
await this.copyFileWithPlaceholderReplacement(dataPath, targetPath, this.bmadFolderName || 'bmad');
|
await this.copyFileWithPlaceholderReplacement(dataPath, targetPath, this.bmadFolderName || 'bmad');
|
||||||
this.installedFiles.add(targetPath);
|
this.installedFiles.push(targetPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1719,7 +1800,7 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
}
|
}
|
||||||
|
|
||||||
// Track the installed file
|
// Track the installed file
|
||||||
this.installedFiles.add(targetFile);
|
this.installedFiles.push(targetFile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1795,9 +1876,7 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
const genericTemplatePath = getSourcePath('utility', 'agent-components', 'agent.customize.template.yaml');
|
const genericTemplatePath = getSourcePath('utility', 'agent-components', 'agent.customize.template.yaml');
|
||||||
if (await fs.pathExists(genericTemplatePath)) {
|
if (await fs.pathExists(genericTemplatePath)) {
|
||||||
await this.copyFileWithPlaceholderReplacement(genericTemplatePath, customizePath, this.bmadFolderName || 'bmad');
|
await this.copyFileWithPlaceholderReplacement(genericTemplatePath, customizePath, this.bmadFolderName || 'bmad');
|
||||||
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
|
console.log(chalk.dim(` Created customize: ${moduleName}-${agentName}.customize.yaml`));
|
||||||
console.log(chalk.dim(` Created customize: ${moduleName}-${agentName}.customize.yaml`));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2007,17 +2086,12 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
|
|
||||||
// Compile using the same compiler as initial installation
|
// Compile using the same compiler as initial installation
|
||||||
const { compileAgent } = require('../../../lib/agent/compiler');
|
const { compileAgent } = require('../../../lib/agent/compiler');
|
||||||
const result = await compileAgent(yamlContent, answers, agentName, path.relative(bmadDir, targetMdPath), {
|
const { xml } = await compileAgent(yamlContent, answers, agentName, path.relative(bmadDir, targetMdPath), {
|
||||||
config: coreConfig,
|
config: coreConfig,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check if compilation succeeded
|
|
||||||
if (!result || !result.xml) {
|
|
||||||
throw new Error(`Failed to compile agent ${agentName}: No XML returned from compiler`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace _bmad with actual folder name if needed
|
// Replace _bmad with actual folder name if needed
|
||||||
const finalXml = result.xml.replaceAll('_bmad', path.basename(bmadDir));
|
const finalXml = xml.replaceAll('_bmad', path.basename(bmadDir));
|
||||||
|
|
||||||
// Write the rebuilt .md file with POSIX-compliant final newline
|
// Write the rebuilt .md file with POSIX-compliant final newline
|
||||||
const content = finalXml.endsWith('\n') ? finalXml : finalXml + '\n';
|
const content = finalXml.endsWith('\n') ? finalXml : finalXml + '\n';
|
||||||
|
|
@ -2148,12 +2222,6 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
} else {
|
} else {
|
||||||
// Selective update - preserve user modifications
|
// Selective update - preserve user modifications
|
||||||
await this.fileOps.syncDirectory(sourcePath, targetPath);
|
await this.fileOps.syncDirectory(sourcePath, targetPath);
|
||||||
|
|
||||||
// Recompile agents (#1133)
|
|
||||||
const { ModuleManager } = require('../modules/manager');
|
|
||||||
const moduleManager = new ModuleManager();
|
|
||||||
await moduleManager.compileModuleAgents(sourcePath, targetPath, 'core', bmadDir);
|
|
||||||
await this.processAgentFiles(targetPath, 'core');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2184,10 +2252,8 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
const configuredIdes = existingInstall.ides || [];
|
const configuredIdes = existingInstall.ides || [];
|
||||||
const projectRoot = path.dirname(bmadDir);
|
const projectRoot = path.dirname(bmadDir);
|
||||||
|
|
||||||
// Get custom module sources from manifest and cache
|
// Get custom module sources from manifest
|
||||||
const customModuleSources = new Map();
|
const customModuleSources = new Map();
|
||||||
|
|
||||||
// First check manifest for backward compatibility
|
|
||||||
if (existingInstall.customModules) {
|
if (existingInstall.customModules) {
|
||||||
for (const customModule of existingInstall.customModules) {
|
for (const customModule of existingInstall.customModules) {
|
||||||
// Ensure we have an absolute sourcePath
|
// Ensure we have an absolute sourcePath
|
||||||
|
|
@ -2218,37 +2284,6 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Also check cache directory for any modules not in manifest
|
|
||||||
const cacheDir = path.join(bmadDir, '_config', 'custom');
|
|
||||||
if (await fs.pathExists(cacheDir)) {
|
|
||||||
const cachedModules = await fs.readdir(cacheDir, { withFileTypes: true });
|
|
||||||
|
|
||||||
for (const cachedModule of cachedModules) {
|
|
||||||
if (cachedModule.isDirectory()) {
|
|
||||||
const moduleId = cachedModule.name;
|
|
||||||
|
|
||||||
// Skip if we already have this module from manifest
|
|
||||||
if (customModuleSources.has(moduleId)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const cachedPath = path.join(cacheDir, moduleId);
|
|
||||||
|
|
||||||
// Check if this is actually a custom module (has module.yaml)
|
|
||||||
const moduleYamlPath = path.join(cachedPath, 'module.yaml');
|
|
||||||
if (await fs.pathExists(moduleYamlPath)) {
|
|
||||||
// For quick update, we always rebuild from cache
|
|
||||||
customModuleSources.set(moduleId, {
|
|
||||||
id: moduleId,
|
|
||||||
name: moduleId, // We'll read the actual name if needed
|
|
||||||
sourcePath: cachedPath,
|
|
||||||
cached: true, // Flag to indicate this is from cache
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load saved IDE configurations
|
// Load saved IDE configurations
|
||||||
const savedIdeConfigs = await this.ideConfigManager.loadAllIdeConfigs(bmadDir);
|
const savedIdeConfigs = await this.ideConfigManager.loadAllIdeConfigs(bmadDir);
|
||||||
|
|
||||||
|
|
@ -2530,7 +2565,11 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
type: 'list',
|
type: 'list',
|
||||||
name: 'action',
|
name: 'action',
|
||||||
message: 'What would you like to do?',
|
message: 'What would you like to do?',
|
||||||
choices: [{ name: 'Update existing installation', value: 'update' }],
|
choices: [
|
||||||
|
{ name: 'Update existing installation', value: 'update' },
|
||||||
|
{ name: 'Remove and reinstall', value: 'reinstall' },
|
||||||
|
{ name: 'Cancel', value: 'cancel' },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
@ -2698,10 +2737,14 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
const installedFilesMap = new Map();
|
const installedFilesMap = new Map();
|
||||||
for (const fileEntry of existingFilesManifest) {
|
for (const fileEntry of existingFilesManifest) {
|
||||||
if (fileEntry.path) {
|
if (fileEntry.path) {
|
||||||
const absolutePath = path.join(bmadDir, fileEntry.path);
|
// Paths are relative to bmadDir. Legacy manifests incorrectly prefixed 'bmad/' -
|
||||||
|
// strip it if present. This is safe because no real path inside bmadDir would
|
||||||
|
// start with 'bmad/' (you'd never have _bmad/bmad/... as an actual structure).
|
||||||
|
const relativePath = fileEntry.path.startsWith('bmad/') ? fileEntry.path.slice(5) : fileEntry.path;
|
||||||
|
const absolutePath = path.join(bmadDir, relativePath);
|
||||||
installedFilesMap.set(path.normalize(absolutePath), {
|
installedFilesMap.set(path.normalize(absolutePath), {
|
||||||
hash: fileEntry.hash,
|
hash: fileEntry.hash,
|
||||||
relativePath: fileEntry.path,
|
relativePath: relativePath,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2753,6 +2796,7 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip config.yaml files - these are regenerated on each install/update
|
// Skip config.yaml files - these are regenerated on each install/update
|
||||||
|
// Users should use _config/agents/ override files instead
|
||||||
if (fileName === 'config.yaml') {
|
if (fileName === 'config.yaml') {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -2775,6 +2819,8 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// If manifest doesn't have hashes, we can't detect modifications
|
||||||
|
// so we just skip files that are in the manifest
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
|
|
@ -2904,7 +2950,7 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
}
|
}
|
||||||
|
|
||||||
await fs.writeFile(configPath, configContent, 'utf8');
|
await fs.writeFile(configPath, configContent, 'utf8');
|
||||||
this.installedFiles.add(configPath); // Track agent config files
|
this.installedFiles.push(configPath); // Track agent config files
|
||||||
createdCount++;
|
createdCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2970,6 +3016,25 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
return nodes;
|
return nodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy IDE-specific documentation to BMAD docs
|
||||||
|
* @param {Array} ides - List of selected IDEs
|
||||||
|
* @param {string} bmadDir - BMAD installation directory
|
||||||
|
*/
|
||||||
|
async copyIdeDocumentation(ides, bmadDir) {
|
||||||
|
const docsDir = path.join(bmadDir, 'docs');
|
||||||
|
await fs.ensureDir(docsDir);
|
||||||
|
|
||||||
|
for (const ide of ides) {
|
||||||
|
const sourceDocPath = path.join(getProjectRoot(), 'docs', 'ide-info', `${ide}.md`);
|
||||||
|
const targetDocPath = path.join(docsDir, `${ide}-instructions.md`);
|
||||||
|
|
||||||
|
if (await fs.pathExists(sourceDocPath)) {
|
||||||
|
await this.copyFileWithPlaceholderReplacement(sourceDocPath, targetDocPath, this.bmadFolderName || 'bmad');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle missing custom module sources interactively
|
* Handle missing custom module sources interactively
|
||||||
* @param {Map} customModuleSources - Map of custom module ID to info
|
* @param {Map} customModuleSources - Map of custom module ID to info
|
||||||
|
|
@ -2994,23 +3059,13 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
info: customInfo,
|
info: customInfo,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// For cached modules that are missing, we just skip them without prompting
|
customModulesWithMissingSources.push({
|
||||||
if (customInfo.cached) {
|
id: moduleId,
|
||||||
// Skip cached modules without prompting
|
name: customInfo.name,
|
||||||
keptModulesWithoutSources.push({
|
sourcePath: customInfo.sourcePath,
|
||||||
id: moduleId,
|
relativePath: customInfo.relativePath,
|
||||||
name: customInfo.name,
|
info: customInfo,
|
||||||
cached: true,
|
});
|
||||||
});
|
|
||||||
} else {
|
|
||||||
customModulesWithMissingSources.push({
|
|
||||||
id: moduleId,
|
|
||||||
name: customInfo.name,
|
|
||||||
sourcePath: customInfo.sourcePath,
|
|
||||||
relativePath: customInfo.relativePath,
|
|
||||||
info: customInfo,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,17 +38,14 @@ class ManifestGenerator {
|
||||||
// Scan the bmad directory to find all actually installed modules
|
// Scan the bmad directory to find all actually installed modules
|
||||||
const installedModules = await this.scanInstalledModules(bmadDir);
|
const installedModules = await this.scanInstalledModules(bmadDir);
|
||||||
|
|
||||||
// Since custom modules are now installed the same way as regular modules,
|
// Deduplicate modules list to prevent duplicates
|
||||||
// we don't need to exclude them from manifest generation
|
this.modules = [...new Set(['core', ...selectedModules, ...preservedModules, ...installedModules])];
|
||||||
const allModules = [...new Set(['core', ...selectedModules, ...preservedModules, ...installedModules])];
|
this.updatedModules = [...new Set(['core', ...selectedModules, ...installedModules])]; // All installed modules get rescanned
|
||||||
|
|
||||||
this.modules = allModules;
|
|
||||||
this.updatedModules = allModules; // Include ALL modules (including custom) for scanning
|
|
||||||
|
|
||||||
// For CSV manifests, we need to include ALL modules that are installed
|
// For CSV manifests, we need to include ALL modules that are installed
|
||||||
// preservedModules controls which modules stay as-is in the CSV (don't get rescanned)
|
// preservedModules controls which modules stay as-is in the CSV (don't get rescanned)
|
||||||
// But all modules should be included in the final manifest
|
// But all modules should be included in the final manifest
|
||||||
this.preservedModules = allModules; // Include ALL modules (including custom)
|
this.preservedModules = [...new Set([...preservedModules, ...selectedModules, ...installedModules])]; // Include all installed modules
|
||||||
this.bmadDir = bmadDir;
|
this.bmadDir = bmadDir;
|
||||||
this.bmadFolderName = path.basename(bmadDir); // Get the actual folder name (e.g., '_bmad' or 'bmad')
|
this.bmadFolderName = path.basename(bmadDir); // Get the actual folder name (e.g., '_bmad' or 'bmad')
|
||||||
this.allInstalledFiles = installedFiles;
|
this.allInstalledFiles = installedFiles;
|
||||||
|
|
@ -457,20 +454,33 @@ class ManifestGenerator {
|
||||||
async writeMainManifest(cfgDir) {
|
async writeMainManifest(cfgDir) {
|
||||||
const manifestPath = path.join(cfgDir, 'manifest.yaml');
|
const manifestPath = path.join(cfgDir, 'manifest.yaml');
|
||||||
|
|
||||||
|
// Read existing manifest to preserve custom modules
|
||||||
|
let existingCustomModules = [];
|
||||||
|
if (await fs.pathExists(manifestPath)) {
|
||||||
|
try {
|
||||||
|
const existingContent = await fs.readFile(manifestPath, 'utf8');
|
||||||
|
const existingManifest = yaml.parse(existingContent);
|
||||||
|
if (existingManifest && existingManifest.customModules) {
|
||||||
|
existingCustomModules = existingManifest.customModules;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// If we can't read the existing manifest, continue without preserving custom modules
|
||||||
|
console.warn('Warning: Could not read existing manifest to preserve custom modules');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const manifest = {
|
const manifest = {
|
||||||
installation: {
|
installation: {
|
||||||
version: packageJson.version,
|
version: packageJson.version,
|
||||||
installDate: new Date().toISOString(),
|
installDate: new Date().toISOString(),
|
||||||
lastUpdated: new Date().toISOString(),
|
lastUpdated: new Date().toISOString(),
|
||||||
},
|
},
|
||||||
modules: this.modules, // Include ALL modules (standard and custom)
|
modules: this.modules,
|
||||||
|
customModules: existingCustomModules, // Preserve custom modules
|
||||||
ides: this.selectedIdes,
|
ides: this.selectedIdes,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Clean the manifest to remove any non-serializable values
|
const yamlStr = yaml.stringify(manifest, {
|
||||||
const cleanManifest = structuredClone(manifest);
|
|
||||||
|
|
||||||
const yamlStr = yaml.stringify(cleanManifest, {
|
|
||||||
indent: 2,
|
indent: 2,
|
||||||
lineWidth: 0,
|
lineWidth: 0,
|
||||||
sortKeys: false,
|
sortKeys: false,
|
||||||
|
|
|
||||||
|
|
@ -28,10 +28,7 @@ class Manifest {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Write YAML manifest
|
// Write YAML manifest
|
||||||
// Clean the manifest data to remove any non-serializable values
|
const yamlContent = yaml.stringify(manifestData, {
|
||||||
const cleanManifestData = structuredClone(manifestData);
|
|
||||||
|
|
||||||
const yamlContent = yaml.stringify(cleanManifestData, {
|
|
||||||
indent: 2,
|
indent: 2,
|
||||||
lineWidth: 0,
|
lineWidth: 0,
|
||||||
sortKeys: false,
|
sortKeys: false,
|
||||||
|
|
@ -62,8 +59,8 @@ class Manifest {
|
||||||
version: manifestData.installation?.version,
|
version: manifestData.installation?.version,
|
||||||
installDate: manifestData.installation?.installDate,
|
installDate: manifestData.installation?.installDate,
|
||||||
lastUpdated: manifestData.installation?.lastUpdated,
|
lastUpdated: manifestData.installation?.lastUpdated,
|
||||||
modules: manifestData.modules || [], // All modules (standard and custom)
|
modules: manifestData.modules || [],
|
||||||
customModules: manifestData.customModules || [], // Keep for backward compatibility
|
customModules: manifestData.customModules || [],
|
||||||
ides: manifestData.ides || [],
|
ides: manifestData.ides || [],
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -95,17 +92,15 @@ class Manifest {
|
||||||
installDate: manifest.installDate,
|
installDate: manifest.installDate,
|
||||||
lastUpdated: manifest.lastUpdated,
|
lastUpdated: manifest.lastUpdated,
|
||||||
},
|
},
|
||||||
modules: manifest.modules || [], // All modules (standard and custom)
|
modules: manifest.modules || [],
|
||||||
|
customModules: manifest.customModules || [],
|
||||||
ides: manifest.ides || [],
|
ides: manifest.ides || [],
|
||||||
};
|
};
|
||||||
|
|
||||||
const manifestPath = path.join(bmadDir, '_config', 'manifest.yaml');
|
const manifestPath = path.join(bmadDir, '_config', 'manifest.yaml');
|
||||||
await fs.ensureDir(path.dirname(manifestPath));
|
await fs.ensureDir(path.dirname(manifestPath));
|
||||||
|
|
||||||
// Clean the manifest data to remove any non-serializable values
|
const yamlContent = yaml.stringify(manifestData, {
|
||||||
const cleanManifestData = structuredClone(manifestData);
|
|
||||||
|
|
||||||
const yamlContent = yaml.stringify(cleanManifestData, {
|
|
||||||
indent: 2,
|
indent: 2,
|
||||||
lineWidth: 0,
|
lineWidth: 0,
|
||||||
sortKeys: false,
|
sortKeys: false,
|
||||||
|
|
|
||||||
|
|
@ -320,10 +320,7 @@ class CustomHandler {
|
||||||
if (await fs.pathExists(genericTemplatePath)) {
|
if (await fs.pathExists(genericTemplatePath)) {
|
||||||
let templateContent = await fs.readFile(genericTemplatePath, 'utf8');
|
let templateContent = await fs.readFile(genericTemplatePath, 'utf8');
|
||||||
await fs.writeFile(customizePath, templateContent, 'utf8');
|
await fs.writeFile(customizePath, templateContent, 'utf8');
|
||||||
// Only show customize creation in verbose mode
|
console.log(chalk.dim(` Created customize: custom-${agentName}.customize.yaml`));
|
||||||
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
|
|
||||||
console.log(chalk.dim(` Created customize: custom-${agentName}.customize.yaml`));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -344,14 +341,11 @@ class CustomHandler {
|
||||||
fileTrackingCallback(targetMdPath);
|
fileTrackingCallback(targetMdPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only show compilation details in verbose mode
|
console.log(
|
||||||
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
|
chalk.dim(
|
||||||
console.log(
|
` Compiled agent: ${agentName} -> ${path.relative(targetAgentsPath, targetMdPath)}${hasSidecar ? ' (with sidecar)' : ''}`,
|
||||||
chalk.dim(
|
),
|
||||||
` Compiled agent: ${agentName} -> ${path.relative(targetAgentsPath, targetMdPath)}${hasSidecar ? ' (with sidecar)' : ''}`,
|
);
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn(chalk.yellow(` Failed to compile agent ${agentName}:`, error.message));
|
console.warn(chalk.yellow(` Failed to compile agent ${agentName}:`, error.message));
|
||||||
results.errors.push(`Failed to compile agent ${agentName}: ${error.message}`);
|
results.errors.push(`Failed to compile agent ${agentName}: ${error.message}`);
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ class BaseIdeSetup {
|
||||||
* @returns {string} The activation header text
|
* @returns {string} The activation header text
|
||||||
*/
|
*/
|
||||||
async getAgentCommandHeader() {
|
async getAgentCommandHeader() {
|
||||||
const headerPath = getSourcePath('utility', 'agent-components', 'agent-command-header.md');
|
const headerPath = path.join(getSourcePath(), 'src', 'utility', 'agent-components', 'agent-command-header.md');
|
||||||
return await fs.readFile(headerPath, 'utf8');
|
return await fs.readFile(headerPath, 'utf8');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,22 +45,12 @@ class RooSetup extends BaseIdeSetup {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// artifact.sourcePath contains the full path to the agent file
|
// Read the actual agent file from _bmad for metadata extraction (installed agents are .md files)
|
||||||
if (!artifact.sourcePath) {
|
const agentPath = path.join(bmadDir, artifact.module, 'agents', `${artifact.name}.md`);
|
||||||
console.error(`Error: Missing sourcePath for artifact ${artifact.name} from module ${artifact.module}`);
|
const content = await this.readFile(agentPath);
|
||||||
console.error(`Artifact object:`, artifact);
|
|
||||||
throw new Error(`Missing sourcePath for agent: ${artifact.name}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const content = await this.readFile(artifact.sourcePath);
|
|
||||||
|
|
||||||
// Create command file that references the actual _bmad agent
|
// Create command file that references the actual _bmad agent
|
||||||
await this.createCommandFile(
|
await this.createCommandFile({ module: artifact.module, name: artifact.name, path: agentPath }, content, commandPath, projectDir);
|
||||||
{ module: artifact.module, name: artifact.name, path: artifact.sourcePath },
|
|
||||||
content,
|
|
||||||
commandPath,
|
|
||||||
projectDir,
|
|
||||||
);
|
|
||||||
|
|
||||||
addedCount++;
|
addedCount++;
|
||||||
console.log(chalk.green(` ✓ Added command: ${commandName}`));
|
console.log(chalk.green(` ✓ Added command: ${commandName}`));
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,6 @@ class ModuleManager {
|
||||||
this.xmlHandler = new XmlHandler();
|
this.xmlHandler = new XmlHandler();
|
||||||
this.bmadFolderName = 'bmad'; // Default, can be overridden
|
this.bmadFolderName = 'bmad'; // Default, can be overridden
|
||||||
this.scanProjectForModules = options.scanProjectForModules !== false; // Default to true for backward compatibility
|
this.scanProjectForModules = options.scanProjectForModules !== false; // Default to true for backward compatibility
|
||||||
this.customModulePaths = new Map(); // Initialize custom module paths
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -48,14 +47,6 @@ class ModuleManager {
|
||||||
this.coreConfig = coreConfig;
|
this.coreConfig = coreConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Set custom module paths for priority lookup
|
|
||||||
* @param {Map<string, string>} customModulePaths - Map of module ID to source path
|
|
||||||
*/
|
|
||||||
setCustomModulePaths(customModulePaths) {
|
|
||||||
this.customModulePaths = customModulePaths;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copy a file and replace _bmad placeholder with actual folder name
|
* Copy a file and replace _bmad placeholder with actual folder name
|
||||||
* @param {string} sourcePath - Source file path
|
* @param {string} sourcePath - Source file path
|
||||||
|
|
@ -343,50 +334,66 @@ class ModuleManager {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find the source path for a module by searching all possible locations
|
* Find the source path for a module by searching all possible locations
|
||||||
* @param {string} moduleCode - Code of the module to find (from module.yaml)
|
* @param {string} moduleName - Name of the module to find
|
||||||
* @returns {string|null} Path to the module source or null if not found
|
* @returns {string|null} Path to the module source or null if not found
|
||||||
*/
|
*/
|
||||||
async findModuleSource(moduleCode) {
|
async findModuleSource(moduleName) {
|
||||||
const projectRoot = getProjectRoot();
|
const projectRoot = getProjectRoot();
|
||||||
|
|
||||||
// First check custom module paths if they exist
|
// First, check src/modules
|
||||||
if (this.customModulePaths && this.customModulePaths.has(moduleCode)) {
|
const srcModulePath = path.join(this.modulesSourcePath, moduleName);
|
||||||
return this.customModulePaths.get(moduleCode);
|
if (await fs.pathExists(srcModulePath)) {
|
||||||
|
// Check if this looks like a module (has module.yaml)
|
||||||
|
const moduleConfigPath = path.join(srcModulePath, 'module.yaml');
|
||||||
|
const installerConfigPath = path.join(srcModulePath, '_module-installer', 'module.yaml');
|
||||||
|
|
||||||
|
if ((await fs.pathExists(moduleConfigPath)) || (await fs.pathExists(installerConfigPath))) {
|
||||||
|
return srcModulePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also check for custom.yaml in src/modules/_module-installer
|
||||||
|
const customConfigPath = path.join(srcModulePath, '_module-installer', 'custom.yaml');
|
||||||
|
if (await fs.pathExists(customConfigPath)) {
|
||||||
|
return srcModulePath;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search in src/modules by READING module.yaml files to match by code
|
// If not found in src/modules, search the entire project
|
||||||
if (await fs.pathExists(this.modulesSourcePath)) {
|
const allModulePaths = await this.findModulesInProject();
|
||||||
const entries = await fs.readdir(this.modulesSourcePath, { withFileTypes: true });
|
for (const modulePath of allModulePaths) {
|
||||||
for (const entry of entries) {
|
if (path.basename(modulePath) === moduleName) {
|
||||||
if (entry.isDirectory()) {
|
return modulePath;
|
||||||
const modulePath = path.join(this.modulesSourcePath, entry.name);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Read module.yaml to get the code
|
// Also check by module ID (not just folder name)
|
||||||
const moduleConfigPath = path.join(modulePath, 'module.yaml');
|
// Need to read configs to match by ID
|
||||||
const installerConfigPath = path.join(modulePath, '_module-installer', 'module.yaml');
|
for (const modulePath of allModulePaths) {
|
||||||
const customConfigPath = path.join(modulePath, '_module-installer', 'custom.yaml');
|
const moduleConfigPath = path.join(modulePath, 'module.yaml');
|
||||||
|
const installerConfigPath = path.join(modulePath, '_module-installer', 'module.yaml');
|
||||||
|
const customConfigPath = path.join(modulePath, '_module-installer', 'custom.yaml');
|
||||||
|
const rootCustomConfigPath = path.join(modulePath, 'custom.yaml');
|
||||||
|
|
||||||
let configPath = null;
|
let configPath = null;
|
||||||
if (await fs.pathExists(moduleConfigPath)) {
|
if (await fs.pathExists(moduleConfigPath)) {
|
||||||
configPath = moduleConfigPath;
|
configPath = moduleConfigPath;
|
||||||
} else if (await fs.pathExists(installerConfigPath)) {
|
} else if (await fs.pathExists(installerConfigPath)) {
|
||||||
configPath = installerConfigPath;
|
configPath = installerConfigPath;
|
||||||
} else if (await fs.pathExists(customConfigPath)) {
|
} else if (await fs.pathExists(customConfigPath)) {
|
||||||
configPath = customConfigPath;
|
configPath = customConfigPath;
|
||||||
}
|
} else if (await fs.pathExists(rootCustomConfigPath)) {
|
||||||
|
configPath = rootCustomConfigPath;
|
||||||
if (configPath) {
|
}
|
||||||
try {
|
|
||||||
const configContent = await fs.readFile(configPath, 'utf8');
|
if (configPath) {
|
||||||
const config = yaml.parse(configContent);
|
try {
|
||||||
if (config.code === moduleCode) {
|
const configContent = await fs.readFile(configPath, 'utf8');
|
||||||
return modulePath;
|
const config = yaml.parse(configContent);
|
||||||
}
|
if (config.code === moduleName) {
|
||||||
} catch (error) {
|
return modulePath;
|
||||||
// Continue to next module if parse fails
|
|
||||||
console.warn(`Warning: Failed to parse module config at ${configPath}: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Failed to parse module.yaml at ${configPath}: ${error.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -396,7 +403,7 @@ class ModuleManager {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Install a module
|
* Install a module
|
||||||
* @param {string} moduleName - Code of the module to install (from module.yaml)
|
* @param {string} moduleName - Name of the module to install
|
||||||
* @param {string} bmadDir - Target bmad directory
|
* @param {string} bmadDir - Target bmad directory
|
||||||
* @param {Function} fileTrackingCallback - Optional callback to track installed files
|
* @param {Function} fileTrackingCallback - Optional callback to track installed files
|
||||||
* @param {Object} options - Additional installation options
|
* @param {Object} options - Additional installation options
|
||||||
|
|
@ -410,10 +417,7 @@ class ModuleManager {
|
||||||
|
|
||||||
// Check if source module exists
|
// Check if source module exists
|
||||||
if (!sourcePath) {
|
if (!sourcePath) {
|
||||||
// Provide a more user-friendly error message
|
throw new Error(`Module '${moduleName}' not found in any source location`);
|
||||||
throw new Error(
|
|
||||||
`Source for module '${moduleName}' is not available. It will be retained but cannot be updated without its source files.`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if this is a custom module and read its custom.yaml values
|
// Check if this is a custom module and read its custom.yaml values
|
||||||
|
|
@ -447,6 +451,7 @@ class ModuleManager {
|
||||||
|
|
||||||
// Check if already installed
|
// Check if already installed
|
||||||
if (await fs.pathExists(targetPath)) {
|
if (await fs.pathExists(targetPath)) {
|
||||||
|
console.log(chalk.yellow(`Module '${moduleName}' already installed, updating...`));
|
||||||
await fs.remove(targetPath);
|
await fs.remove(targetPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -502,10 +507,6 @@ class ModuleManager {
|
||||||
} else {
|
} else {
|
||||||
// Selective update - preserve user modifications
|
// Selective update - preserve user modifications
|
||||||
await this.syncModule(sourcePath, targetPath);
|
await this.syncModule(sourcePath, targetPath);
|
||||||
|
|
||||||
// Recompile agents (#1133)
|
|
||||||
await this.compileModuleAgents(sourcePath, targetPath, moduleName, bmadDir);
|
|
||||||
await this.processAgentFiles(targetPath, moduleName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -818,10 +819,7 @@ class ModuleManager {
|
||||||
const genericTemplatePath = getSourcePath('utility', 'agent-components', 'agent.customize.template.yaml');
|
const genericTemplatePath = getSourcePath('utility', 'agent-components', 'agent.customize.template.yaml');
|
||||||
if (await fs.pathExists(genericTemplatePath)) {
|
if (await fs.pathExists(genericTemplatePath)) {
|
||||||
await this.copyFileWithPlaceholderReplacement(genericTemplatePath, customizePath);
|
await this.copyFileWithPlaceholderReplacement(genericTemplatePath, customizePath);
|
||||||
// Only show customize creation in verbose mode
|
console.log(chalk.dim(` Created customize: ${moduleName}-${agentName}.customize.yaml`));
|
||||||
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
|
|
||||||
console.log(chalk.dim(` Created customize: ${moduleName}-${agentName}.customize.yaml`));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store original hash for modification detection
|
// Store original hash for modification detection
|
||||||
const crypto = require('node:crypto');
|
const crypto = require('node:crypto');
|
||||||
|
|
@ -843,10 +841,7 @@ class ModuleManager {
|
||||||
|
|
||||||
// Write back to manifest
|
// Write back to manifest
|
||||||
const yaml = require('yaml');
|
const yaml = require('yaml');
|
||||||
// Clean the manifest data to remove any non-serializable values
|
const updatedContent = yaml.stringify(manifestData, {
|
||||||
const cleanManifestData = structuredClone(manifestData);
|
|
||||||
|
|
||||||
const updatedContent = yaml.stringify(cleanManifestData, {
|
|
||||||
indent: 2,
|
indent: 2,
|
||||||
lineWidth: 0,
|
lineWidth: 0,
|
||||||
});
|
});
|
||||||
|
|
@ -910,14 +905,9 @@ class ModuleManager {
|
||||||
await fs.writeFile(targetMdPath, xml, 'utf8');
|
await fs.writeFile(targetMdPath, xml, 'utf8');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only show compilation details in verbose mode
|
console.log(
|
||||||
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
|
chalk.dim(` Compiled agent: ${agentName} -> ${path.relative(targetPath, targetMdPath)}${hasSidecar ? ' (with sidecar)' : ''}`),
|
||||||
console.log(
|
);
|
||||||
chalk.dim(
|
|
||||||
` Compiled agent: ${agentName} -> ${path.relative(targetPath, targetMdPath)}${hasSidecar ? ' (with sidecar)' : ''}`,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn(chalk.yellow(` Failed to compile agent ${agentName}:`, error.message));
|
console.warn(chalk.yellow(` Failed to compile agent ${agentName}:`, error.message));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -346,12 +346,6 @@ async function compileAgent(yamlContent, answers = {}, agentName = '', targetPat
|
||||||
|
|
||||||
// Replace {bmad_memory} in XML content
|
// Replace {bmad_memory} in XML content
|
||||||
let xml = await compileToXml(cleanYaml, agentName, targetPath);
|
let xml = await compileToXml(cleanYaml, agentName, targetPath);
|
||||||
|
|
||||||
// Ensure xml is a string before attempting replaceAll
|
|
||||||
if (typeof xml !== 'string') {
|
|
||||||
throw new TypeError('compileToXml did not return a string');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (finalAnswers.bmad_memory) {
|
if (finalAnswers.bmad_memory) {
|
||||||
xml = xml.replaceAll('{bmad_memory}', finalAnswers.bmad_memory);
|
xml = xml.replaceAll('{bmad_memory}', finalAnswers.bmad_memory);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,8 @@ class UI {
|
||||||
*/
|
*/
|
||||||
async promptInstall() {
|
async promptInstall() {
|
||||||
CLIUtils.displayLogo();
|
CLIUtils.displayLogo();
|
||||||
|
const version = CLIUtils.getVersion();
|
||||||
|
CLIUtils.displaySection('BMAD™ Setup', `Build More, Architect Dreams v${version}`);
|
||||||
|
|
||||||
const confirmedDirectory = await this.getConfirmedDirectory();
|
const confirmedDirectory = await this.getConfirmedDirectory();
|
||||||
|
|
||||||
|
|
@ -38,22 +40,19 @@ class UI {
|
||||||
let legacyBmadPath = null;
|
let legacyBmadPath = null;
|
||||||
|
|
||||||
// First check for legacy .bmad folder (instead of _bmad)
|
// First check for legacy .bmad folder (instead of _bmad)
|
||||||
// Only check if directory exists
|
const entries = await fs.readdir(confirmedDirectory, { withFileTypes: true });
|
||||||
if (await fs.pathExists(confirmedDirectory)) {
|
for (const entry of entries) {
|
||||||
const entries = await fs.readdir(confirmedDirectory, { withFileTypes: true });
|
if (entry.isDirectory() && entry.name === '.bmad') {
|
||||||
for (const entry of entries) {
|
hasLegacyBmadFolder = true;
|
||||||
if (entry.isDirectory() && entry.name === '.bmad') {
|
legacyBmadPath = path.join(confirmedDirectory, '.bmad');
|
||||||
hasLegacyBmadFolder = true;
|
bmadDir = legacyBmadPath;
|
||||||
legacyBmadPath = path.join(confirmedDirectory, '.bmad');
|
|
||||||
bmadDir = legacyBmadPath;
|
|
||||||
|
|
||||||
// Check if it has _cfg folder
|
// Check if it has _cfg folder
|
||||||
const cfgPath = path.join(legacyBmadPath, '_cfg');
|
const cfgPath = path.join(legacyBmadPath, '_cfg');
|
||||||
if (await fs.pathExists(cfgPath)) {
|
if (await fs.pathExists(cfgPath)) {
|
||||||
hasLegacyCfg = true;
|
hasLegacyCfg = true;
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -131,36 +130,6 @@ class UI {
|
||||||
// Check if there's an existing BMAD installation (after any folder renames)
|
// Check if there's an existing BMAD installation (after any folder renames)
|
||||||
const hasExistingInstall = await fs.pathExists(bmadDir);
|
const hasExistingInstall = await fs.pathExists(bmadDir);
|
||||||
|
|
||||||
// Collect IDE tool selection early - we need this to know if we should ask about TTS
|
|
||||||
let toolSelection;
|
|
||||||
let agentVibesConfig = { enabled: false, alreadyInstalled: false };
|
|
||||||
let claudeCodeSelected = false;
|
|
||||||
|
|
||||||
if (!hasExistingInstall) {
|
|
||||||
// For new installations, collect IDE selection first
|
|
||||||
// We don't have modules yet, so pass empty array
|
|
||||||
toolSelection = await this.promptToolSelection(confirmedDirectory, []);
|
|
||||||
|
|
||||||
// Check if Claude Code was selected
|
|
||||||
claudeCodeSelected = toolSelection.ides && toolSelection.ides.includes('claude-code');
|
|
||||||
|
|
||||||
// If Claude Code was selected, ask about TTS
|
|
||||||
if (claudeCodeSelected) {
|
|
||||||
const { enableTts } = await inquirer.prompt([
|
|
||||||
{
|
|
||||||
type: 'confirm',
|
|
||||||
name: 'enableTts',
|
|
||||||
message: 'Claude Code supports TTS (Text-to-Speech). Would you like to enable it?',
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (enableTts) {
|
|
||||||
agentVibesConfig = { enabled: true, alreadyInstalled: false };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Always ask for custom content, but we'll handle it differently for new installs
|
// Always ask for custom content, but we'll handle it differently for new installs
|
||||||
let customContentConfig = { hasCustomContent: false };
|
let customContentConfig = { hasCustomContent: false };
|
||||||
if (hasExistingInstall) {
|
if (hasExistingInstall) {
|
||||||
|
|
@ -177,37 +146,19 @@ class UI {
|
||||||
|
|
||||||
// Only show action menu if there's an existing installation
|
// Only show action menu if there's an existing installation
|
||||||
if (hasExistingInstall) {
|
if (hasExistingInstall) {
|
||||||
// Get version information
|
|
||||||
const { existingInstall } = await this.getExistingInstallation(confirmedDirectory);
|
|
||||||
const packageJsonPath = path.join(__dirname, '../../../package.json');
|
|
||||||
const currentVersion = require(packageJsonPath).version;
|
|
||||||
const installedVersion = existingInstall.version || 'unknown';
|
|
||||||
|
|
||||||
// Build menu choices dynamically
|
|
||||||
const choices = [];
|
|
||||||
|
|
||||||
// Always show Quick Update first (allows refreshing installation even on same version)
|
|
||||||
if (installedVersion !== 'unknown') {
|
|
||||||
choices.push({
|
|
||||||
name: `Quick Update (v${installedVersion} → v${currentVersion})`,
|
|
||||||
value: 'quick-update',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Common actions
|
|
||||||
choices.push(
|
|
||||||
{ name: 'Modify BMAD Installation', value: 'update' },
|
|
||||||
{ name: 'Add / Update Custom Content', value: 'add-custom' },
|
|
||||||
{ name: 'Rebuild Agents', value: 'compile' },
|
|
||||||
);
|
|
||||||
|
|
||||||
const promptResult = await inquirer.prompt([
|
const promptResult = await inquirer.prompt([
|
||||||
{
|
{
|
||||||
type: 'list',
|
type: 'list',
|
||||||
name: 'actionType',
|
name: 'actionType',
|
||||||
message: 'What would you like to do?',
|
message: 'What would you like to do?',
|
||||||
choices: choices,
|
choices: [
|
||||||
default: choices[0].value, // Use the first option as default
|
{ name: 'Quick Update (Settings Preserved)', value: 'quick-update' },
|
||||||
|
{ 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' },
|
||||||
|
{ name: 'Compile Agents (Quick rebuild of all agent .md files)', value: 'compile' },
|
||||||
|
{ name: 'Cancel', value: 'cancel' },
|
||||||
|
],
|
||||||
|
default: 'quick-update',
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
@ -224,56 +175,6 @@ class UI {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle add custom content separately
|
|
||||||
if (actionType === 'add-custom') {
|
|
||||||
customContentConfig = await this.promptCustomContentSource();
|
|
||||||
// After adding custom content, continue to select additional modules
|
|
||||||
const { installedModuleIds } = await this.getExistingInstallation(confirmedDirectory);
|
|
||||||
|
|
||||||
// Ask if user wants to add additional modules
|
|
||||||
const { wantsMoreModules } = await inquirer.prompt([
|
|
||||||
{
|
|
||||||
type: 'confirm',
|
|
||||||
name: 'wantsMoreModules',
|
|
||||||
message: 'Do you want to add any additional modules?',
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
let selectedModules = [];
|
|
||||||
if (wantsMoreModules) {
|
|
||||||
const moduleChoices = await this.getModuleChoices(installedModuleIds, customContentConfig);
|
|
||||||
selectedModules = await this.selectModules(moduleChoices);
|
|
||||||
|
|
||||||
// Process custom content selection
|
|
||||||
const selectedCustomContent = selectedModules.filter((mod) => mod.startsWith('__CUSTOM_CONTENT__'));
|
|
||||||
|
|
||||||
if (selectedCustomContent.length > 0) {
|
|
||||||
customContentConfig.selected = true;
|
|
||||||
customContentConfig.selectedFiles = selectedCustomContent.map((mod) => mod.replace('__CUSTOM_CONTENT__', ''));
|
|
||||||
|
|
||||||
// Convert to module IDs
|
|
||||||
const customContentModuleIds = [];
|
|
||||||
const customHandler = new CustomHandler();
|
|
||||||
for (const customFile of customContentConfig.selectedFiles) {
|
|
||||||
const customInfo = await customHandler.getCustomInfo(customFile);
|
|
||||||
if (customInfo) {
|
|
||||||
customContentModuleIds.push(customInfo.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
selectedModules = [...selectedModules.filter((mod) => !mod.startsWith('__CUSTOM_CONTENT__')), ...customContentModuleIds];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
actionType: 'update',
|
|
||||||
directory: confirmedDirectory,
|
|
||||||
installCore: false, // Don't reinstall core
|
|
||||||
modules: selectedModules,
|
|
||||||
customContent: customContentConfig,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle agent compilation separately
|
// Handle agent compilation separately
|
||||||
if (actionType === 'compile') {
|
if (actionType === 'compile') {
|
||||||
return {
|
return {
|
||||||
|
|
@ -282,133 +183,86 @@ class UI {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// If actionType === 'update', handle it with the new flow
|
// Handle cancel
|
||||||
// Return early with modify configuration
|
if (actionType === 'cancel') {
|
||||||
if (actionType === 'update') {
|
|
||||||
// Get existing installation info
|
|
||||||
const { installedModuleIds } = await this.getExistingInstallation(confirmedDirectory);
|
|
||||||
|
|
||||||
console.log(chalk.dim(` Found existing modules: ${[...installedModuleIds].join(', ')}`));
|
|
||||||
const { changeModuleSelection } = await inquirer.prompt([
|
|
||||||
{
|
|
||||||
type: 'confirm',
|
|
||||||
name: 'changeModuleSelection',
|
|
||||||
message: 'Change which modules are installed?',
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
let selectedModules = [];
|
|
||||||
if (changeModuleSelection) {
|
|
||||||
// Show module selection with existing modules pre-selected
|
|
||||||
const moduleChoices = await this.getModuleChoices(new Set(installedModuleIds), { hasCustomContent: false });
|
|
||||||
selectedModules = await this.selectModules(moduleChoices, [...installedModuleIds]);
|
|
||||||
} else {
|
|
||||||
selectedModules = [...installedModuleIds];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get tool selection
|
|
||||||
const toolSelection = await this.promptToolSelection(confirmedDirectory, selectedModules);
|
|
||||||
|
|
||||||
// TTS configuration - ask right after tool selection (matches new install flow)
|
|
||||||
const hasClaudeCode = toolSelection.ides && toolSelection.ides.includes('claude-code');
|
|
||||||
let enableTts = false;
|
|
||||||
|
|
||||||
if (hasClaudeCode) {
|
|
||||||
const { enableTts: enable } = await inquirer.prompt([
|
|
||||||
{
|
|
||||||
type: 'confirm',
|
|
||||||
name: 'enableTts',
|
|
||||||
message: 'Claude Code supports TTS (Text-to-Speech). Would you like to enable it?',
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
enableTts = enable;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Core config with existing defaults (ask after TTS)
|
|
||||||
const coreConfig = await this.collectCoreConfig(confirmedDirectory);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
actionType: 'update',
|
actionType: 'cancel',
|
||||||
directory: confirmedDirectory,
|
directory: confirmedDirectory,
|
||||||
installCore: true,
|
|
||||||
modules: selectedModules,
|
|
||||||
ides: toolSelection.ides,
|
|
||||||
skipIde: toolSelection.skipIde,
|
|
||||||
coreConfig: coreConfig,
|
|
||||||
customContent: { hasCustomContent: false },
|
|
||||||
enableAgentVibes: enableTts,
|
|
||||||
agentVibesInstalled: false,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
|
||||||
|
// If actionType === 'update' or 'reinstall', continue with normal flow below
|
||||||
|
}
|
||||||
|
|
||||||
|
const { installedModuleIds } = await this.getExistingInstallation(confirmedDirectory);
|
||||||
|
const coreConfig = await this.collectCoreConfig(confirmedDirectory);
|
||||||
|
|
||||||
|
// Custom content will be handled during installation phase
|
||||||
|
// Store the custom content config for later use
|
||||||
|
if (customContentConfig._shouldAsk) {
|
||||||
|
delete customContentConfig._shouldAsk;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
const moduleChoices = await this.getModuleChoices(installedModuleIds, customContentConfig);
|
||||||
|
selectedModules = await this.selectModules(moduleChoices);
|
||||||
|
|
||||||
|
// Check which custom content items were selected
|
||||||
|
const selectedCustomContent = selectedModules.filter((mod) => mod.startsWith('__CUSTOM_CONTENT__'));
|
||||||
|
|
||||||
|
if (selectedCustomContent.length > 0) {
|
||||||
|
customContentConfig.selected = true;
|
||||||
|
customContentConfig.selectedFiles = selectedCustomContent.map((mod) => mod.replace('__CUSTOM_CONTENT__', ''));
|
||||||
|
|
||||||
|
// Convert 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Filter out custom content markers and add module IDs
|
||||||
|
selectedModules = [...selectedModules.filter((mod) => !mod.startsWith('__CUSTOM_CONTENT__')), ...customContentModuleIds];
|
||||||
|
} else if (customContentConfig.hasCustomContent) {
|
||||||
|
// User provided custom content but didn't select any
|
||||||
|
customContentConfig.selected = false;
|
||||||
|
customContentConfig.selectedFiles = [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This section is only for new installations (update returns early above)
|
// Prompt for AgentVibes TTS integration
|
||||||
const { installedModuleIds } = await this.getExistingInstallation(confirmedDirectory);
|
const agentVibesConfig = await this.promptAgentVibes(confirmedDirectory);
|
||||||
|
|
||||||
// Ask about official modules for new installations
|
// Collect IDE tool selection AFTER configuration prompts (fixes Windows/PowerShell hang)
|
||||||
const { wantsOfficialModules } = await inquirer.prompt([
|
// This allows text-based prompts to complete before the checkbox prompt
|
||||||
{
|
const toolSelection = await this.promptToolSelection(confirmedDirectory, selectedModules);
|
||||||
type: 'confirm',
|
|
||||||
name: 'wantsOfficialModules',
|
|
||||||
message: 'Will you be installing any official modules (BMad Method, BMad Builder, Creative Innovation Suite)?',
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
let selectedOfficialModules = [];
|
// No more screen clearing - keep output flowing
|
||||||
if (wantsOfficialModules) {
|
|
||||||
const moduleChoices = await this.getModuleChoices(installedModuleIds, { hasCustomContent: false });
|
|
||||||
selectedOfficialModules = await this.selectModules(moduleChoices);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ask about custom content
|
|
||||||
const { wantsCustomContent } = await inquirer.prompt([
|
|
||||||
{
|
|
||||||
type: 'confirm',
|
|
||||||
name: 'wantsCustomContent',
|
|
||||||
message: 'Will you be installing any locally stored custom content?',
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (wantsCustomContent) {
|
|
||||||
customContentConfig = await this.promptCustomContentSource();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store the selected modules for later
|
|
||||||
customContentConfig._selectedOfficialModules = selectedOfficialModules;
|
|
||||||
|
|
||||||
// Build the final list of selected modules
|
|
||||||
let selectedModules = customContentConfig._selectedOfficialModules || [];
|
|
||||||
|
|
||||||
// Add custom content modules if any were selected
|
|
||||||
if (customContentConfig && customContentConfig.selectedModuleIds) {
|
|
||||||
selectedModules = [...selectedModules, ...customContentConfig.selectedModuleIds];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove core if it's in the list (it's always installed)
|
|
||||||
selectedModules = selectedModules.filter((m) => m !== 'core');
|
|
||||||
|
|
||||||
// Tool selection (already done for new installs at the beginning)
|
|
||||||
if (!toolSelection) {
|
|
||||||
toolSelection = await this.promptToolSelection(confirmedDirectory, selectedModules);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collect configurations for new installations
|
|
||||||
const coreConfig = await this.collectCoreConfig(confirmedDirectory);
|
|
||||||
|
|
||||||
// TTS already handled at the beginning for new installs
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
actionType: 'install',
|
actionType: actionType || 'update', // Preserve reinstall or update action
|
||||||
directory: confirmedDirectory,
|
directory: confirmedDirectory,
|
||||||
installCore: true,
|
installCore: true, // Always install core
|
||||||
modules: selectedModules,
|
modules: selectedModules,
|
||||||
|
// IDE selection collected after config, will be configured later
|
||||||
ides: toolSelection.ides,
|
ides: toolSelection.ides,
|
||||||
skipIde: toolSelection.skipIde,
|
skipIde: toolSelection.skipIde,
|
||||||
coreConfig: coreConfig,
|
coreConfig: coreConfig, // Pass collected core config to installer
|
||||||
|
// Custom content configuration
|
||||||
customContent: customContentConfig,
|
customContent: customContentConfig,
|
||||||
enableAgentVibes: agentVibesConfig.enabled,
|
enableAgentVibes: agentVibesConfig.enabled,
|
||||||
agentVibesInstalled: agentVibesConfig.alreadyInstalled,
|
agentVibesInstalled: agentVibesConfig.alreadyInstalled,
|
||||||
|
|
@ -498,6 +352,8 @@ class UI {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CLIUtils.displaySection('Tool Integration', 'Select AI coding assistants and IDEs to configure');
|
||||||
|
|
||||||
let answers;
|
let answers;
|
||||||
let userConfirmedNoTools = false;
|
let userConfirmedNoTools = false;
|
||||||
|
|
||||||
|
|
@ -509,7 +365,7 @@ class UI {
|
||||||
name: 'ides',
|
name: 'ides',
|
||||||
message: 'Select tools to configure:',
|
message: 'Select tools to configure:',
|
||||||
choices: ideChoices,
|
choices: ideChoices,
|
||||||
pageSize: 30,
|
pageSize: 15,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
@ -535,8 +391,9 @@ class UI {
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (goBack) {
|
if (goBack) {
|
||||||
// Re-display a message before looping back
|
// Re-display the section header before looping back
|
||||||
console.log();
|
console.log();
|
||||||
|
CLIUtils.displaySection('Tool Integration', 'Select AI coding assistants and IDEs to configure');
|
||||||
} else {
|
} else {
|
||||||
// User explicitly chose to proceed without tools
|
// User explicitly chose to proceed without tools
|
||||||
userConfirmedNoTools = true;
|
userConfirmedNoTools = true;
|
||||||
|
|
@ -626,26 +483,69 @@ class UI {
|
||||||
* @param {Object} result - Installation result
|
* @param {Object} result - Installation result
|
||||||
*/
|
*/
|
||||||
showInstallSummary(result) {
|
showInstallSummary(result) {
|
||||||
// Clean, simple completion message
|
CLIUtils.displaySection('Installation Complete', 'BMAD™ has been successfully installed');
|
||||||
console.log('\n' + chalk.green.bold('✨ BMAD is ready to use!'));
|
|
||||||
|
|
||||||
// Show installation summary in a simple format
|
const summary = [
|
||||||
console.log(chalk.dim(`Installed to: ${result.path}`));
|
`📁 Installation Path: ${result.path}`,
|
||||||
if (result.modules && result.modules.length > 0) {
|
`📦 Modules Installed: ${result.modules?.length > 0 ? result.modules.join(', ') : 'core only'}`,
|
||||||
console.log(chalk.dim(`Modules: ${result.modules.join(', ')}`));
|
`🔧 Tools Configured: ${result.ides?.length > 0 ? result.ides.join(', ') : 'none'}`,
|
||||||
}
|
];
|
||||||
|
|
||||||
|
// Add AgentVibes TTS info if enabled
|
||||||
if (result.agentVibesEnabled) {
|
if (result.agentVibesEnabled) {
|
||||||
console.log(chalk.dim(`TTS: Enabled`));
|
summary.push(`🎤 AgentVibes TTS: Enabled`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TTS injection info (simplified)
|
CLIUtils.displayBox(summary.join('\n\n'), {
|
||||||
|
borderColor: 'green',
|
||||||
|
borderStyle: 'round',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Display TTS injection details if present
|
||||||
if (result.ttsInjectedFiles && result.ttsInjectedFiles.length > 0) {
|
if (result.ttsInjectedFiles && result.ttsInjectedFiles.length > 0) {
|
||||||
console.log(chalk.dim(`\n💡 TTS enabled for ${result.ttsInjectedFiles.length} agent(s)`));
|
console.log('\n' + chalk.cyan.bold('═══════════════════════════════════════════════════'));
|
||||||
console.log(chalk.dim(' Agents will now speak when using AgentVibes'));
|
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:'));
|
||||||
|
console.log(chalk.cyan(' ~/_bmad-tts-backups/\n'));
|
||||||
|
console.log(chalk.dim(' To restore original files (removes TTS instructions):'));
|
||||||
|
console.log(chalk.cyan(` bmad-tts-injector.sh --restore ${result.path}\n`));
|
||||||
|
|
||||||
|
console.log(chalk.cyan('💡 BMAD agents will now speak when activated!'));
|
||||||
|
console.log(chalk.dim(' Ensure AgentVibes is installed: https://agentvibes.org'));
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(chalk.yellow('\nThank you for helping test the early release version of the new BMad Core and BMad Method!'));
|
console.log('\n' + chalk.green.bold('✨ BMAD is ready to use!'));
|
||||||
console.log(chalk.cyan('Stable Beta coming soon - please read the full README.md and linked documentation to get started!'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -675,8 +575,8 @@ class UI {
|
||||||
const { Installer } = require('../installers/lib/core/installer');
|
const { Installer } = require('../installers/lib/core/installer');
|
||||||
const detector = new Detector();
|
const detector = new Detector();
|
||||||
const installer = new Installer();
|
const installer = new Installer();
|
||||||
const bmadDirResult = await installer.findBmadDir(directory);
|
const bmadDir = await installer.findBmadDir(directory);
|
||||||
const existingInstall = await detector.detect(bmadDirResult.bmadDir);
|
const existingInstall = await detector.detect(bmadDir);
|
||||||
const installedModuleIds = new Set(existingInstall.modules.map((mod) => mod.id));
|
const installedModuleIds = new Set(existingInstall.modules.map((mod) => mod.id));
|
||||||
|
|
||||||
return { existingInstall, installedModuleIds };
|
return { existingInstall, installedModuleIds };
|
||||||
|
|
@ -695,9 +595,7 @@ class UI {
|
||||||
// Now collect with existing values as defaults (false = don't skip loading, true = skip completion message)
|
// Now collect with existing values as defaults (false = don't skip loading, true = skip completion message)
|
||||||
await configCollector.collectModuleConfig('core', directory, false, true);
|
await configCollector.collectModuleConfig('core', directory, false, true);
|
||||||
|
|
||||||
const coreConfig = configCollector.collectedConfig.core;
|
return configCollector.collectedConfig.core;
|
||||||
// Ensure we always have a core config object, even if empty
|
|
||||||
return coreConfig || {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -792,14 +690,15 @@ class UI {
|
||||||
* @param {Array} moduleChoices - Available module choices
|
* @param {Array} moduleChoices - Available module choices
|
||||||
* @returns {Array} Selected module IDs
|
* @returns {Array} Selected module IDs
|
||||||
*/
|
*/
|
||||||
async selectModules(moduleChoices, defaultSelections = []) {
|
async selectModules(moduleChoices) {
|
||||||
|
CLIUtils.displaySection('Module Selection', 'Choose the BMAD modules to install');
|
||||||
|
|
||||||
const moduleAnswer = await inquirer.prompt([
|
const moduleAnswer = await inquirer.prompt([
|
||||||
{
|
{
|
||||||
type: 'checkbox',
|
type: 'checkbox',
|
||||||
name: 'modules',
|
name: 'modules',
|
||||||
message: 'Select modules to install:',
|
message: 'Select modules to install:',
|
||||||
choices: moduleChoices,
|
choices: moduleChoices,
|
||||||
default: defaultSelections,
|
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
@ -846,13 +745,12 @@ class UI {
|
||||||
// Check for any bmad installation (any folder with _config/manifest.yaml)
|
// Check for any bmad installation (any folder with _config/manifest.yaml)
|
||||||
const { Installer } = require('../installers/lib/core/installer');
|
const { Installer } = require('../installers/lib/core/installer');
|
||||||
const installer = new Installer();
|
const installer = new Installer();
|
||||||
const bmadResult = await installer.findBmadDir(directory);
|
const bmadDir = await installer.findBmadDir(directory);
|
||||||
const hasBmadInstall =
|
const hasBmadInstall = (await fs.pathExists(bmadDir)) && (await fs.pathExists(path.join(bmadDir, '_config', 'manifest.yaml')));
|
||||||
(await fs.pathExists(bmadResult.bmadDir)) && (await fs.pathExists(path.join(bmadResult.bmadDir, '_config', 'manifest.yaml')));
|
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
chalk.gray(`Directory exists and contains ${files.length} item(s)`) +
|
chalk.gray(`Directory exists and contains ${files.length} item(s)`) +
|
||||||
(hasBmadInstall ? chalk.yellow(` including existing BMAD installation (${path.basename(bmadResult.bmadDir)})`) : ''),
|
(hasBmadInstall ? chalk.yellow(` including existing BMAD installation (${path.basename(bmadDir)})`) : ''),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
console.log(chalk.gray('Directory exists and is empty'));
|
console.log(chalk.gray('Directory exists and is empty'));
|
||||||
|
|
@ -1147,57 +1045,6 @@ class UI {
|
||||||
return (await fs.pathExists(hookPath)) && (await fs.pathExists(playTtsPath));
|
return (await fs.pathExists(hookPath)) && (await fs.pathExists(playTtsPath));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Load existing configurations to use as defaults
|
|
||||||
* @param {string} directory - Installation directory
|
|
||||||
* @returns {Object} Existing configurations
|
|
||||||
*/
|
|
||||||
async loadExistingConfigurations(directory) {
|
|
||||||
const configs = {
|
|
||||||
hasCustomContent: false,
|
|
||||||
coreConfig: {},
|
|
||||||
ideConfig: { ides: [], skipIde: false },
|
|
||||||
agentVibesConfig: { enabled: false, alreadyInstalled: false },
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Load core config
|
|
||||||
configs.coreConfig = await this.collectCoreConfig(directory);
|
|
||||||
|
|
||||||
// Load IDE configuration
|
|
||||||
const configuredIdes = await this.getConfiguredIdes(directory);
|
|
||||||
if (configuredIdes.length > 0) {
|
|
||||||
configs.ideConfig.ides = configuredIdes;
|
|
||||||
configs.ideConfig.skipIde = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load AgentVibes configuration
|
|
||||||
const agentVibesInstalled = await this.checkAgentVibesInstalled(directory);
|
|
||||||
configs.agentVibesConfig = { enabled: agentVibesInstalled, alreadyInstalled: agentVibesInstalled };
|
|
||||||
|
|
||||||
return configs;
|
|
||||||
} catch {
|
|
||||||
// If loading fails, return empty configs
|
|
||||||
console.warn('Warning: Could not load existing configurations');
|
|
||||||
return configs;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get configured IDEs from existing installation
|
|
||||||
* @param {string} directory - Installation directory
|
|
||||||
* @returns {Array} List of configured IDEs
|
|
||||||
*/
|
|
||||||
async getConfiguredIdes(directory) {
|
|
||||||
const { Detector } = require('../installers/lib/core/detector');
|
|
||||||
const { Installer } = require('../installers/lib/core/installer');
|
|
||||||
const detector = new Detector();
|
|
||||||
const installer = new Installer();
|
|
||||||
const bmadResult = await installer.findBmadDir(directory);
|
|
||||||
const existingInstall = await detector.detect(bmadResult.bmadDir);
|
|
||||||
return existingInstall.ides || [];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prompt for custom content for existing installations
|
* Prompt for custom content for existing installations
|
||||||
* @returns {Object} Custom content configuration
|
* @returns {Object} Custom content configuration
|
||||||
|
|
@ -1337,141 +1184,6 @@ class UI {
|
||||||
return { hasCustomContent: false };
|
return { hasCustomContent: false };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Prompt user for custom content source location
|
|
||||||
* @returns {Object} Custom content configuration
|
|
||||||
*/
|
|
||||||
async promptCustomContentSource() {
|
|
||||||
const customContentConfig = { hasCustomContent: true, sources: [] };
|
|
||||||
|
|
||||||
// Keep asking for more sources until user is done
|
|
||||||
while (true) {
|
|
||||||
// First ask if user wants to add another module or continue
|
|
||||||
if (customContentConfig.sources.length > 0) {
|
|
||||||
const { action } = await inquirer.prompt([
|
|
||||||
{
|
|
||||||
type: 'list',
|
|
||||||
name: 'action',
|
|
||||||
message: 'Would you like to:',
|
|
||||||
choices: [
|
|
||||||
{ name: 'Add another custom module', value: 'add' },
|
|
||||||
{ name: 'Continue with installation', value: 'continue' },
|
|
||||||
],
|
|
||||||
default: 'continue',
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (action === 'continue') {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let sourcePath;
|
|
||||||
let isValid = false;
|
|
||||||
|
|
||||||
while (!isValid) {
|
|
||||||
const { path: inputPath } = await inquirer.prompt([
|
|
||||||
{
|
|
||||||
type: 'input',
|
|
||||||
name: 'path',
|
|
||||||
message: 'Enter the path to your custom content folder (or press Enter to cancel):',
|
|
||||||
validate: async (input) => {
|
|
||||||
// Allow empty input to cancel
|
|
||||||
if (!input || input.trim() === '') {
|
|
||||||
return true; // Allow empty to exit
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Expand the path
|
|
||||||
const expandedPath = this.expandUserPath(input.trim());
|
|
||||||
|
|
||||||
// Check if path exists
|
|
||||||
if (!(await fs.pathExists(expandedPath))) {
|
|
||||||
return 'Path does not exist';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if it's a directory
|
|
||||||
const stat = await fs.stat(expandedPath);
|
|
||||||
if (!stat.isDirectory()) {
|
|
||||||
return 'Path must be a directory';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for module.yaml in the root
|
|
||||||
const moduleYamlPath = path.join(expandedPath, 'module.yaml');
|
|
||||||
if (!(await fs.pathExists(moduleYamlPath))) {
|
|
||||||
return 'Directory must contain a module.yaml file in the root';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to parse the module.yaml to get the module ID
|
|
||||||
try {
|
|
||||||
const yaml = require('yaml');
|
|
||||||
const content = await fs.readFile(moduleYamlPath, 'utf8');
|
|
||||||
const moduleData = yaml.parse(content);
|
|
||||||
if (!moduleData.code) {
|
|
||||||
return 'module.yaml must contain a "code" field for the module ID';
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
return 'Invalid module.yaml file: ' + error.message;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} catch (error) {
|
|
||||||
return 'Error validating path: ' + error.message;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
// If user pressed Enter without typing anything, exit the loop
|
|
||||||
if (!inputPath || inputPath.trim() === '') {
|
|
||||||
// If we have no modules yet, return false for no custom content
|
|
||||||
if (customContentConfig.sources.length === 0) {
|
|
||||||
return { hasCustomContent: false };
|
|
||||||
}
|
|
||||||
return customContentConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
sourcePath = this.expandUserPath(inputPath);
|
|
||||||
isValid = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read module.yaml to get module info
|
|
||||||
const yaml = require('yaml');
|
|
||||||
const moduleYamlPath = path.join(sourcePath, 'module.yaml');
|
|
||||||
const moduleContent = await fs.readFile(moduleYamlPath, 'utf8');
|
|
||||||
const moduleData = yaml.parse(moduleContent);
|
|
||||||
|
|
||||||
// Add to sources
|
|
||||||
customContentConfig.sources.push({
|
|
||||||
path: sourcePath,
|
|
||||||
id: moduleData.code,
|
|
||||||
name: moduleData.name || moduleData.code,
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(chalk.green(`✓ Confirmed local custom module: ${moduleData.name || moduleData.code}`));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ask if user wants to add these to the installation
|
|
||||||
const { shouldInstall } = await inquirer.prompt([
|
|
||||||
{
|
|
||||||
type: 'confirm',
|
|
||||||
name: 'shouldInstall',
|
|
||||||
message: `Install ${customContentConfig.sources.length} custom module(s) now?`,
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (shouldInstall) {
|
|
||||||
customContentConfig.selected = true;
|
|
||||||
// Store paths to module.yaml files, not directories
|
|
||||||
customContentConfig.selectedFiles = customContentConfig.sources.map((s) => path.join(s.path, 'module.yaml'));
|
|
||||||
// Also include module IDs for installation
|
|
||||||
customContentConfig.selectedModuleIds = customContentConfig.sources.map((s) => s.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
return customContentConfig;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { UI };
|
module.exports = { UI };
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue