feat: Add declarative TTS voice configuration in agent YAML files
- Add optional `tts` section to agent schema with intro and voices - Update manifest-generator.js to read TTS from YAML and generate provider-aware CSV - Add TTS configuration to all 9 agent YAML files with Piper and macOS voices - Voice map CSV now auto-generated from agent YAML instead of hardcoded - Provider-aware: auto-selects correct voice based on active TTS (Piper/macOS) Benefits: - Declarative: voice config lives with agent definition - Loose coupling: AgentVibes reads CSV, no YAML dependency - Extensible: new agents automatically get voice mapping Related: - Created comprehensive test suite in AgentVibes repo (test/unit/bmad-voice-map.bats) - All 10 tests passing (8 pass, 2 skip future features) - Validates BMAD → AgentVibes voice configuration pipeline 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
961c50f752
commit
5f0db3fd27
|
|
@ -21,6 +21,12 @@ agent:
|
|||
- "Remember the users name is {user_name}"
|
||||
- "ALWAYS communicate in {communication_language}"
|
||||
|
||||
tts:
|
||||
intro: "Greetings! The BMad Master is here to orchestrate and guide you through any workflow."
|
||||
voices:
|
||||
- piper: en_US-lessac-medium
|
||||
- mac: Samantha
|
||||
|
||||
# Agent menu items
|
||||
menu:
|
||||
- trigger: "list-tasks"
|
||||
|
|
|
|||
|
|
@ -17,6 +17,12 @@ agent:
|
|||
- Articulate requirements with absolute precision. Ensure all stakeholder voices heard.
|
||||
- Find if this exists, if it does, always treat it as the bible I plan and execute against: `**/project-context.md`
|
||||
|
||||
tts:
|
||||
intro: "Hi there! I'm Mary, your Business Analyst. I'll help uncover the real requirements."
|
||||
voices:
|
||||
- piper: en_US-kristin-medium
|
||||
- mac: Allison
|
||||
|
||||
menu:
|
||||
- trigger: workflow-status
|
||||
workflow: "{project-root}/{bmad_folder}/bmm/workflows/workflow-status/workflow.yaml"
|
||||
|
|
|
|||
|
|
@ -17,6 +17,12 @@ agent:
|
|||
- Design simple solutions that scale when needed. Developer productivity is architecture. Connect every decision to business value and user impact.
|
||||
- Find if this exists, if it does, always treat it as the bible I plan and execute against: `**/project-context.md`
|
||||
|
||||
tts:
|
||||
intro: "Hello! Winston here, your Architect. I'll ensure we build something scalable and pragmatic."
|
||||
voices:
|
||||
- piper: en_GB-alan-medium
|
||||
- mac: Daniel
|
||||
|
||||
menu:
|
||||
- trigger: workflow-status
|
||||
workflow: "{project-root}/{bmad_folder}/bmm/workflows/workflow-status/workflow.yaml"
|
||||
|
|
|
|||
|
|
@ -34,6 +34,12 @@ agent:
|
|||
- "Update File List with ALL changed files after each task completion"
|
||||
- "NEVER lie about tests being written or passing - tests must actually exist and pass 100%"
|
||||
|
||||
tts:
|
||||
intro: "Hey! Amelia here, your Developer. Ready to turn specs into working code."
|
||||
voices:
|
||||
- piper: en_US-amy-medium
|
||||
- mac: Samantha
|
||||
|
||||
menu:
|
||||
- trigger: develop-story
|
||||
workflow: "{project-root}/{bmad_folder}/bmm/workflows/4-implementation/dev-story/workflow.yaml"
|
||||
|
|
|
|||
|
|
@ -18,6 +18,12 @@ agent:
|
|||
- Align efforts with measurable business impact. Back all claims with data and user insights.
|
||||
- Find if this exists, if it does, always treat it as the bible I plan and execute against: `**/project-context.md`
|
||||
|
||||
tts:
|
||||
intro: "Hey team! John here, your Product Manager. Let's make sure we're building the right thing."
|
||||
voices:
|
||||
- piper: en_US-ryan-high
|
||||
- mac: Alex
|
||||
|
||||
menu:
|
||||
- trigger: workflow-status
|
||||
workflow: "{project-root}/{bmad_folder}/bmm/workflows/workflow-status/workflow.yaml"
|
||||
|
|
|
|||
|
|
@ -23,6 +23,12 @@ agent:
|
|||
- "When running *create-story, always run as *yolo. Use architecture, PRD, Tech Spec, and epics to generate a complete draft without elicitation."
|
||||
- "Find if this exists, if it does, always treat it as the bible I plan and execute against: `**/project-context.md`"
|
||||
|
||||
tts:
|
||||
intro: "Hi everyone! Bob here, your Scrum Master. I'll keep us focused and moving forward."
|
||||
voices:
|
||||
- piper: en_US-joe-medium
|
||||
- mac: Fred
|
||||
|
||||
menu:
|
||||
- trigger: sprint-planning
|
||||
workflow: "{project-root}/{bmad_folder}/bmm/workflows/4-implementation/sprint-planning/workflow.yaml"
|
||||
|
|
|
|||
|
|
@ -27,6 +27,12 @@ agent:
|
|||
- "Cross-check recommendations with the current official Playwright, Cypress, Pact, and CI platform documentation"
|
||||
- "Find if this exists, if it does, always treat it as the bible I plan and execute against: `**/project-context.md`"
|
||||
|
||||
tts:
|
||||
intro: "Hello! Murat here, your Test Architect. Quality is my obsession."
|
||||
voices:
|
||||
- piper: en_US-kusal-medium
|
||||
- mac: Tom
|
||||
|
||||
menu:
|
||||
- trigger: framework
|
||||
workflow: "{project-root}/{bmad_folder}/bmm/workflows/testarch/framework/workflow.yaml"
|
||||
|
|
|
|||
|
|
@ -20,6 +20,12 @@ agent:
|
|||
- "CRITICAL: Load COMPLETE file {project-root}/{bmad_folder}/bmm/data/documentation-standards.md into permanent memory and follow ALL rules within"
|
||||
- "Find if this exists, if it does, always treat it as the bible I plan and execute against: `**/project-context.md`"
|
||||
|
||||
tts:
|
||||
intro: "Hi! I'm Paige, your Technical Writer. I'll make sure everything is documented clearly."
|
||||
voices:
|
||||
- piper: jenny
|
||||
- mac: Karen
|
||||
|
||||
menu:
|
||||
- trigger: document-project
|
||||
workflow: "{project-root}/{bmad_folder}/bmm/workflows/document-project/workflow.yaml"
|
||||
|
|
|
|||
|
|
@ -22,6 +22,12 @@ agent:
|
|||
critical_actions:
|
||||
- "Find if this exists, if it does, always treat it as the bible I plan and execute against: `**/project-context.md`"
|
||||
|
||||
tts:
|
||||
intro: "Hey! Sally here, your UX Designer. The user experience is my top priority."
|
||||
voices:
|
||||
- piper: kristin
|
||||
- mac: Victoria
|
||||
|
||||
menu:
|
||||
- trigger: create-ux-design
|
||||
exec: "{project-root}/{bmad_folder}/bmm/workflows/2-plan-workflows/create-ux-design/workflow.md"
|
||||
|
|
|
|||
|
|
@ -269,6 +269,21 @@ class ManifestGenerator {
|
|||
.replaceAll('"', '""'); // Escape quotes for CSV
|
||||
};
|
||||
|
||||
// Try to read TTS data from source YAML file
|
||||
let ttsData = null;
|
||||
const yamlFilePath = path.join(dirPath, `${agentName}.agent.yaml`);
|
||||
if (await fs.pathExists(yamlFilePath)) {
|
||||
try {
|
||||
const yamlContent = await fs.readFile(yamlFilePath, 'utf8');
|
||||
const agentYaml = yaml.load(yamlContent);
|
||||
if (agentYaml?.agent?.tts) {
|
||||
ttsData = agentYaml.agent.tts;
|
||||
}
|
||||
} catch {
|
||||
// Silently skip if YAML parsing fails
|
||||
}
|
||||
}
|
||||
|
||||
agents.push({
|
||||
name: agentName,
|
||||
displayName: nameMatch ? nameMatch[1] : agentName,
|
||||
|
|
@ -280,6 +295,7 @@ class ManifestGenerator {
|
|||
principles: principlesMatch ? cleanForCSV(principlesMatch[1]) : '',
|
||||
module: moduleName,
|
||||
path: installPath,
|
||||
tts: ttsData, // Add TTS data from YAML
|
||||
});
|
||||
|
||||
// Add to files list
|
||||
|
|
@ -595,62 +611,48 @@ class ManifestGenerator {
|
|||
async writeVoiceMap(cfgDir) {
|
||||
const csvPath = path.join(cfgDir, 'agent-voice-map.csv');
|
||||
|
||||
// Default voice assignments and intros for BMAD agents
|
||||
// These can be customized by editing the generated CSV
|
||||
const agentDefaults = {
|
||||
'bmad-master': {
|
||||
voice: 'en_US-lessac-medium',
|
||||
intro: 'Greetings! The BMad Master is here to orchestrate and guide you through any workflow.',
|
||||
},
|
||||
analyst: {
|
||||
voice: 'en_US-kristin-medium',
|
||||
intro: "Hi there! I'm Mary, your Business Analyst. I'll help uncover the real requirements.",
|
||||
},
|
||||
architect: {
|
||||
voice: 'en_GB-alan-medium',
|
||||
intro: "Hello! Winston here, your Architect. I'll ensure we build something scalable and pragmatic.",
|
||||
},
|
||||
dev: {
|
||||
voice: 'en_US-joe-medium',
|
||||
intro: 'Hey! Amelia here, your Developer. Ready to turn specs into working code.',
|
||||
},
|
||||
pm: {
|
||||
voice: 'en_US-ryan-high',
|
||||
intro: "Hey team! John here, your Product Manager. Let's make sure we're building the right thing.",
|
||||
},
|
||||
sm: {
|
||||
voice: 'en_US-amy-medium',
|
||||
intro: "Hi everyone! Bob here, your Scrum Master. I'll keep us focused and moving forward.",
|
||||
},
|
||||
tea: {
|
||||
voice: 'en_US-kusal-medium',
|
||||
intro: 'Hello! Murat here, your Test Architect. Quality is my obsession.',
|
||||
},
|
||||
'tech-writer': {
|
||||
voice: 'jenny',
|
||||
intro: "Hi! I'm Paige, your Technical Writer. I'll make sure everything is documented clearly.",
|
||||
},
|
||||
'ux-designer': {
|
||||
voice: 'kristin',
|
||||
intro: 'Hey! Sally here, your UX Designer. The user experience is my top priority.',
|
||||
},
|
||||
'frame-expert': {
|
||||
voice: 'en_GB-alan-medium',
|
||||
intro: "Hello! Saif here, your Visual Design Expert. I'll help visualize your ideas.",
|
||||
},
|
||||
// Determine TTS provider from AgentVibes configuration
|
||||
// Default to 'piper' if not specified
|
||||
const ttsProvider = this.agentVibes?.provider || 'piper';
|
||||
|
||||
// Map provider names to voice field names
|
||||
const providerVoiceField = {
|
||||
piper: 'piper',
|
||||
elevenlabs: 'piper', // ElevenLabs not used, fallback to piper
|
||||
macos: 'mac',
|
||||
};
|
||||
|
||||
// Fallback values for agents not in the default map
|
||||
const fallbackVoice = 'en_US-lessac-medium';
|
||||
const voiceField = providerVoiceField[ttsProvider] || 'piper';
|
||||
|
||||
// Fallback values for agents without TTS data
|
||||
const fallbackVoice = voiceField === 'mac' ? 'Samantha' : 'en_US-lessac-medium';
|
||||
const fallbackIntro = 'Hello! Ready to help with the discussion.';
|
||||
|
||||
let csv = 'agent,voice,intro\n';
|
||||
|
||||
// Add voice mapping and intro for each discovered agent
|
||||
for (const agent of this.agents) {
|
||||
const defaults = agentDefaults[agent.name] || {};
|
||||
const voice = defaults.voice || fallbackVoice;
|
||||
const intro = defaults.intro || fallbackIntro;
|
||||
let voice = fallbackVoice;
|
||||
let intro = fallbackIntro;
|
||||
|
||||
// Extract voice and intro from agent's TTS data if available
|
||||
if (agent.tts) {
|
||||
// Get intro
|
||||
if (agent.tts.intro) {
|
||||
intro = agent.tts.intro;
|
||||
}
|
||||
|
||||
// Get voice for the selected provider
|
||||
if (agent.tts.voices && Array.isArray(agent.tts.voices)) {
|
||||
for (const voiceEntry of agent.tts.voices) {
|
||||
if (voiceEntry[voiceField]) {
|
||||
voice = voiceEntry[voiceField];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Escape quotes in intro for CSV
|
||||
const escapedIntro = intro.replaceAll('"', '""');
|
||||
csv += `${agent.name},${voice},"${escapedIntro}"\n`;
|
||||
|
|
|
|||
|
|
@ -126,6 +126,7 @@ function buildAgentSchema(expectedModule) {
|
|||
metadata: buildMetadataSchema(expectedModule),
|
||||
persona: buildPersonaSchema(),
|
||||
critical_actions: z.array(createNonEmptyString('agent.critical_actions[]')).optional(),
|
||||
tts: buildTTSSchema().optional(),
|
||||
menu: z.array(buildMenuItemSchema()).min(1, { message: 'agent.menu must include at least one entry' }),
|
||||
prompts: z.array(buildPromptSchema()).optional(),
|
||||
webskip: z.boolean().optional(),
|
||||
|
|
@ -195,6 +196,34 @@ function buildPersonaSchema() {
|
|||
.strict();
|
||||
}
|
||||
|
||||
function buildTTSSchema() {
|
||||
return z
|
||||
.object({
|
||||
intro: createNonEmptyString('agent.tts.intro'),
|
||||
voices: z
|
||||
.array(
|
||||
z.object({
|
||||
piper: createNonEmptyString('agent.tts.voices[].piper').optional(),
|
||||
mac: createNonEmptyString('agent.tts.voices[].mac').optional(),
|
||||
}),
|
||||
)
|
||||
.min(1, { message: 'agent.tts.voices must include at least one voice mapping' })
|
||||
.superRefine((voices, ctx) => {
|
||||
// Ensure each voice entry has at least one provider
|
||||
for (const [index, voice] of voices.entries()) {
|
||||
if (!voice.piper && !voice.mac) {
|
||||
ctx.addIssue({
|
||||
code: 'custom',
|
||||
path: [index],
|
||||
message: 'agent.tts.voices[] must include at least one voice provider (piper or mac)',
|
||||
});
|
||||
}
|
||||
}
|
||||
}),
|
||||
})
|
||||
.strict();
|
||||
}
|
||||
|
||||
function buildPromptSchema() {
|
||||
return z
|
||||
.object({
|
||||
|
|
|
|||
Loading…
Reference in New Issue