agentvibes 4.2.0 ā 4.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agentvibes/bmad/bmad-voices.md +69 -69
- package/.agentvibes/config.json +12 -0
- package/.claude/activation-instructions +54 -54
- package/.claude/audio/tracks/README.md +52 -52
- package/.claude/commands/agent-vibes/add.md +21 -21
- package/.claude/commands/agent-vibes/agent-vibes.md +101 -101
- package/.claude/commands/agent-vibes/agent.md +79 -79
- package/.claude/commands/agent-vibes/background-music.md +111 -111
- package/.claude/commands/agent-vibes/bmad.md +198 -198
- package/.claude/commands/agent-vibes/clean.md +18 -18
- package/.claude/commands/agent-vibes/cleanup.md +18 -18
- package/.claude/commands/agent-vibes/commands.json +145 -145
- package/.claude/commands/agent-vibes/effects.md +97 -97
- package/.claude/commands/agent-vibes/get.md +9 -9
- package/.claude/commands/agent-vibes/hide.md +91 -91
- package/.claude/commands/agent-vibes/language.md +23 -23
- package/.claude/commands/agent-vibes/learn.md +67 -67
- package/.claude/commands/agent-vibes/list.md +13 -13
- package/.claude/commands/agent-vibes/mute.md +37 -37
- package/.claude/commands/agent-vibes/preview.md +17 -17
- package/.claude/commands/agent-vibes/provider.md +68 -68
- package/.claude/commands/agent-vibes/replay-target.md +14 -14
- package/.claude/commands/agent-vibes/sample.md +12 -12
- package/.claude/commands/agent-vibes/set-favorite-voice.md +84 -84
- package/.claude/commands/agent-vibes/set-pretext.md +65 -65
- package/.claude/commands/agent-vibes/set-speed.md +41 -41
- package/.claude/commands/agent-vibes/show.md +84 -84
- package/.claude/commands/agent-vibes/switch.md +87 -87
- package/.claude/commands/agent-vibes/target-voice.md +26 -26
- package/.claude/commands/agent-vibes/target.md +30 -30
- package/.claude/commands/agent-vibes/translate.md +68 -68
- package/.claude/commands/agent-vibes/unmute.md +45 -45
- package/.claude/commands/agent-vibes/verbosity.md +89 -89
- package/.claude/commands/agent-vibes/whoami.md +7 -7
- package/.claude/commands/agent-vibes-bmad-voices.md +117 -117
- package/.claude/commands/agent-vibes-rdp.md +24 -24
- package/.claude/config/agentvibes.json +1 -0
- package/.claude/config/audio-effects.cfg +2 -2
- package/.claude/config/audio-effects.cfg.sample +52 -52
- package/.claude/config/background-music-volume.txt +1 -0
- package/.claude/config/intro-text.txt +1 -0
- package/.claude/config/piper-speech-rate.txt +4 -0
- package/.claude/config/piper-target-speech-rate.txt +1 -0
- package/.claude/config/reverb-level.txt +1 -0
- package/.claude/config/tts-speech-rate.txt +4 -0
- package/.claude/config/tts-target-speech-rate.txt +1 -0
- package/.claude/docs/TERMUX_SETUP.md +408 -408
- package/.claude/github-star-reminder.txt +1 -1
- package/.claude/hooks/README-TTS-QUEUE.md +135 -135
- package/.claude/hooks/audio-cache-utils.sh +246 -246
- package/.claude/hooks/audio-processor.sh +433 -433
- package/.claude/hooks/background-music-manager.sh +404 -404
- package/.claude/hooks/bmad-speak-enhanced.sh +165 -165
- package/.claude/hooks/bmad-speak.sh +269 -269
- package/.claude/hooks/bmad-tts-injector.sh +568 -568
- package/.claude/hooks/bmad-voice-manager.sh +928 -928
- package/.claude/hooks/clawdbot-receiver-SECURE.sh +129 -129
- package/.claude/hooks/clawdbot-receiver.sh +107 -107
- package/.claude/hooks/clean-audio-cache.sh +22 -22
- package/.claude/hooks/cleanup-cache.sh +106 -106
- package/.claude/hooks/configure-rdp-mode.sh +137 -137
- package/.claude/hooks/download-extra-voices.sh +244 -244
- package/.claude/hooks/effects-manager.sh +268 -268
- package/.claude/hooks/github-star-reminder.sh +154 -154
- package/.claude/hooks/language-manager.sh +362 -362
- package/.claude/hooks/learn-manager.sh +492 -492
- package/.claude/hooks/macos-voice-manager.sh +205 -205
- package/.claude/hooks/migrate-background-music.sh +125 -125
- package/.claude/hooks/migrate-to-agentvibes.sh +161 -161
- package/.claude/hooks/optimize-background-music.sh +87 -87
- package/.claude/hooks/path-resolver.sh +60 -60
- package/.claude/hooks/personality-manager.sh +448 -448
- package/.claude/hooks/piper-download-voices.sh +225 -225
- package/.claude/hooks/piper-installer.sh +292 -292
- package/.claude/hooks/piper-multispeaker-registry.sh +171 -171
- package/.claude/hooks/piper-voice-manager.sh +24 -3
- package/.claude/hooks/play-tts-agentvibes-receiver-for-voiceless-connections.sh +90 -90
- package/.claude/hooks/play-tts-enhanced.sh +105 -105
- package/.claude/hooks/play-tts-macos.sh +368 -368
- package/.claude/hooks/play-tts-piper.sh +679 -679
- package/.claude/hooks/play-tts-soprano.sh +356 -356
- package/.claude/hooks/play-tts-ssh-remote.sh +167 -167
- package/.claude/hooks/play-tts-termux-ssh.sh +169 -169
- package/.claude/hooks/play-tts.sh +301 -301
- package/.claude/hooks/prepare-release.sh +54 -54
- package/.claude/hooks/provider-commands.sh +617 -617
- package/.claude/hooks/provider-manager.sh +399 -399
- package/.claude/hooks/replay-target-audio.sh +95 -95
- package/.claude/hooks/requirements.txt +6 -6
- package/.claude/hooks/sentiment-manager.sh +201 -201
- package/.claude/hooks/session-start-tts.sh +81 -81
- package/.claude/hooks/soprano-gradio-synth.py +139 -139
- package/.claude/hooks/speed-manager.sh +291 -291
- package/.claude/hooks/stop-tts.sh +84 -84
- package/.claude/hooks/termux-installer.sh +261 -261
- package/.claude/hooks/translate-manager.sh +341 -341
- package/.claude/hooks/translator.py +237 -237
- package/.claude/hooks/tts-queue-worker.sh +145 -145
- package/.claude/hooks/tts-queue.sh +165 -165
- package/.claude/hooks/verbosity-manager.sh +178 -178
- package/.claude/hooks/voice-manager.sh +548 -548
- package/.claude/hooks-windows/audio-cache-utils.ps1 +119 -119
- package/.claude/hooks-windows/background-music-manager.ps1 +348 -0
- package/.claude/hooks-windows/clean-audio-cache.ps1 +53 -0
- package/.claude/hooks-windows/download-extra-voices.ps1 +185 -0
- package/.claude/hooks-windows/effects-manager.ps1 +294 -0
- package/.claude/hooks-windows/language-manager.ps1 +193 -0
- package/.claude/hooks-windows/learn-manager.ps1 +241 -0
- package/.claude/hooks-windows/personality-manager.ps1 +266 -0
- package/.claude/hooks-windows/play-tts-piper.ps1 +209 -0
- package/.claude/hooks-windows/play-tts-sapi.ps1 +108 -0
- package/.claude/hooks-windows/play-tts-soprano.ps1 +159 -158
- package/.claude/hooks-windows/play-tts-windows-piper.ps1 +50 -5
- package/.claude/hooks-windows/play-tts-windows-sapi.ps1 +108 -108
- package/.claude/hooks-windows/play-tts.ps1 +344 -266
- package/.claude/hooks-windows/provider-manager.ps1 +29 -10
- package/.claude/hooks-windows/session-start-tts.ps1 +124 -124
- package/.claude/hooks-windows/soprano-gradio-synth.py +153 -153
- package/.claude/hooks-windows/speed-manager.ps1 +166 -0
- package/.claude/hooks-windows/verbosity-manager.ps1 +119 -0
- package/.claude/hooks-windows/voice-manager-windows.ps1 +92 -8
- package/.claude/output-styles/agent-vibes.md +202 -202
- package/.claude/personalities/angry.md +14 -14
- package/.claude/personalities/annoying.md +14 -14
- package/.claude/personalities/crass.md +14 -14
- package/.claude/personalities/dramatic.md +14 -14
- package/.claude/personalities/dry-humor.md +50 -50
- package/.claude/personalities/flirty.md +20 -20
- package/.claude/personalities/funny.md +14 -14
- package/.claude/personalities/grandpa.md +32 -32
- package/.claude/personalities/millennial.md +14 -14
- package/.claude/personalities/moody.md +14 -14
- package/.claude/personalities/normal.md +16 -16
- package/.claude/personalities/pirate.md +14 -14
- package/.claude/personalities/poetic.md +14 -14
- package/.claude/personalities/professional.md +14 -14
- package/.claude/personalities/rapper.md +55 -55
- package/.claude/personalities/robot.md +14 -14
- package/.claude/personalities/sarcastic.md +38 -38
- package/.claude/personalities/sassy.md +14 -14
- package/.claude/personalities/surfer-dude.md +14 -14
- package/.claude/personalities/zen.md +14 -14
- package/.claude/settings.json +15 -15
- package/.claude/verbosity.txt +1 -1
- package/.clawdbot/README.md +105 -105
- package/.clawdbot/skill/SKILL.md +241 -241
- package/.mcp.json +12 -0
- package/CLAUDE.md +170 -170
- package/README.md +2029 -2007
- package/RELEASE_NOTES.md +1310 -1203
- package/WINDOWS-SETUP.md +208 -208
- package/bin/agent-vibes +39 -39
- package/bin/agentvibes-voice-browser.js +1840 -1840
- package/bin/agentvibes.js +48 -2
- package/bin/mcp-server.js +121 -121
- package/bin/mcp-server.sh +206 -206
- package/bin/test-bmad-pr +78 -78
- package/mcp-server/QUICK_START.md +203 -203
- package/mcp-server/README.md +345 -345
- package/mcp-server/WINDOWS_SETUP.md +260 -260
- package/mcp-server/docs/troubleshooting-audio.md +313 -313
- package/mcp-server/examples/claude_desktop_config.json +11 -11
- package/mcp-server/examples/claude_desktop_config_piper.json +9 -9
- package/mcp-server/examples/custom_instructions.md +169 -169
- package/mcp-server/install-deps.js +130 -130
- package/mcp-server/pyproject.toml +52 -52
- package/mcp-server/requirements.txt +2 -2
- package/mcp-server/server.py +1465 -1453
- package/mcp-server/test_server.py +395 -395
- package/mcp-server/test_windows_script_parity.py +336 -0
- package/package.json +110 -110
- package/setup-windows.ps1 +815 -815
- package/src/bmad-detector.js +71 -71
- package/src/cli/list-personalities.js +110 -110
- package/src/cli/list-voices.js +114 -114
- package/src/commands/bmad-voices.js +394 -394
- package/src/commands/install-mcp.js +476 -476
- package/src/console/app.js +824 -824
- package/src/console/audio-env.js +20 -1
- package/src/console/brand-colors.js +13 -13
- package/src/console/constants/personalities.js +44 -44
- package/src/console/footer-config.js +50 -50
- package/src/console/modals/modal-overlay.js +247 -247
- package/src/console/navigation.js +62 -62
- package/src/console/tabs/agents-tab.js +1684 -1516
- package/src/console/tabs/help-tab.js +261 -261
- package/src/console/tabs/install-tab.js +1007 -991
- package/src/console/tabs/music-tab.js +22 -8
- package/src/console/tabs/placeholder-tab.js +53 -53
- package/src/console/tabs/readme-tab.js +267 -267
- package/src/console/tabs/receiver-tab.js +1472 -1212
- package/src/console/tabs/settings-tab.js +208 -84
- package/src/console/tabs/voices-tab.js +100 -21
- package/src/console/widgets/destroy-list.js +25 -25
- package/src/console/widgets/format-utils.js +89 -89
- package/src/console/widgets/notice.js +55 -55
- package/src/console/widgets/personality-picker.js +185 -185
- package/src/console/widgets/reverb-picker.js +94 -94
- package/src/console/widgets/track-picker.js +285 -285
- package/src/installer/music-file-input.js +304 -304
- package/src/installer.js +5895 -5829
- package/src/services/agent-voice-store.js +423 -423
- package/src/services/config-service.js +264 -264
- package/src/services/navigation-service.js +123 -123
- package/src/services/provider-service.js +143 -132
- package/src/services/verbosity-service.js +157 -157
- package/src/utils/audio-duration-validator.js +298 -298
- package/src/utils/audio-format-validator.js +277 -277
- package/src/utils/dependency-checker.js +469 -466
- package/src/utils/file-ownership-verifier.js +358 -358
- package/src/utils/list-formatter.js +194 -194
- package/src/utils/music-file-validator.js +285 -285
- package/src/utils/preview-list-prompt.js +136 -136
- package/src/utils/provider-validator.js +96 -12
- package/src/utils/secure-music-storage.js +412 -412
- package/templates/agentvibes-receiver.sh +482 -482
- package/templates/audio/welcome-music.mp3 +0 -0
- package/voice-assignments.json +8244 -8244
- package/.claude/config/background-music-position.txt +0 -1
|
@@ -1,394 +1,394 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* File: src/commands/bmad-voices.js
|
|
5
|
-
*
|
|
6
|
-
* AgentVibes - BMAD Voice Management Commands
|
|
7
|
-
*
|
|
8
|
-
* Provides CLI commands for managing BMAD agent voice assignments.
|
|
9
|
-
* These commands make it easy to preview, list, and assign voices
|
|
10
|
-
* without manually editing CSV files.
|
|
11
|
-
*
|
|
12
|
-
* Co-created by Paul Preibisch with Claude AI
|
|
13
|
-
* Copyright (c) 2025 Paul Preibisch
|
|
14
|
-
* Licensed under the Apache License, Version 2.0
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import { execFileSync } from 'node:child_process';
|
|
18
|
-
import path from 'node:path';
|
|
19
|
-
import fs from 'node:fs/promises';
|
|
20
|
-
import chalk from 'chalk';
|
|
21
|
-
|
|
22
|
-
// Default BMAD agent voice assignments
|
|
23
|
-
const DEFAULT_VOICE_ASSIGNMENTS = [
|
|
24
|
-
{ agent_id: 'pm', voice_name: 'en_US-ryan-high', description: 'Professional male' },
|
|
25
|
-
{ agent_id: 'architect', voice_name: 'en_US-danny-low', description: 'Deep male' },
|
|
26
|
-
{ agent_id: 'dev', voice_name: 'en_US-joe-medium', description: 'Casual male' },
|
|
27
|
-
{ agent_id: 'analyst', voice_name: 'en_US-amy-medium', description: 'Articulate female' },
|
|
28
|
-
{ agent_id: 'ux-designer', voice_name: 'en_US-kristin-medium', description: 'Warm female' },
|
|
29
|
-
{ agent_id: 'tea', voice_name: 'en_US-lessac-medium', description: 'Balanced neutral' },
|
|
30
|
-
{ agent_id: 'sm', voice_name: 'en_US-bryce-medium', description: 'Energetic male' },
|
|
31
|
-
{ agent_id: 'tech-writer', voice_name: 'en_US-kathleen-low', description: 'Clear female' },
|
|
32
|
-
{ agent_id: 'frame-expert', voice_name: 'en_US-kusal-medium', description: 'Precise male' },
|
|
33
|
-
{ agent_id: 'bmad-master', voice_name: 'en_US-libritts_r-high', description: 'Rich commanding' },
|
|
34
|
-
];
|
|
35
|
-
|
|
36
|
-
// All available Piper voices with descriptions
|
|
37
|
-
const PIPER_VOICES = {
|
|
38
|
-
'en_US-ryan-high': 'Professional, clear male voice',
|
|
39
|
-
'en_US-danny-low': 'Deep, authoritative male voice',
|
|
40
|
-
'en_US-joe-medium': 'Casual, friendly male voice',
|
|
41
|
-
'en_US-amy-medium': 'Articulate female voice',
|
|
42
|
-
'en_US-kristin-medium': 'Warm, professional female voice',
|
|
43
|
-
'en_US-lessac-medium': 'Balanced, neutral voice',
|
|
44
|
-
'en_US-bryce-medium': 'Energetic male voice',
|
|
45
|
-
'en_US-kathleen-low': 'Clear, measured female voice',
|
|
46
|
-
'en_US-kusal-medium': 'Precise male voice',
|
|
47
|
-
'en_US-libritts_r-high': 'Rich, commanding voice',
|
|
48
|
-
'en_US-hfc_female-medium': 'Clear female voice',
|
|
49
|
-
'en_US-hfc_male-medium': 'Clear male voice',
|
|
50
|
-
'en_US-arctic-medium': 'Crisp, neutral voice',
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Get the working directory (handles npx context)
|
|
55
|
-
*/
|
|
56
|
-
function getWorkingDirectory() {
|
|
57
|
-
return process.env.INIT_CWD || process.cwd();
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Check if BMAD is installed
|
|
62
|
-
*/
|
|
63
|
-
async function isBmadInstalled() {
|
|
64
|
-
const targetDir = getWorkingDirectory();
|
|
65
|
-
const bmadPath = path.join(targetDir, '.bmad');
|
|
66
|
-
|
|
67
|
-
try {
|
|
68
|
-
await fs.access(bmadPath);
|
|
69
|
-
return true;
|
|
70
|
-
} catch {
|
|
71
|
-
// Try legacy location
|
|
72
|
-
const legacyPath = path.join(targetDir, 'bmad');
|
|
73
|
-
try {
|
|
74
|
-
await fs.access(legacyPath);
|
|
75
|
-
return true;
|
|
76
|
-
} catch {
|
|
77
|
-
return false;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Get BMAD folder path (.bmad or bmad)
|
|
84
|
-
*/
|
|
85
|
-
async function getBmadPath() {
|
|
86
|
-
const targetDir = getWorkingDirectory();
|
|
87
|
-
const modernPath = path.join(targetDir, '.bmad');
|
|
88
|
-
const legacyPath = path.join(targetDir, 'bmad');
|
|
89
|
-
|
|
90
|
-
try {
|
|
91
|
-
await fs.access(modernPath);
|
|
92
|
-
return modernPath;
|
|
93
|
-
} catch {
|
|
94
|
-
try {
|
|
95
|
-
await fs.access(legacyPath);
|
|
96
|
-
return legacyPath;
|
|
97
|
-
} catch {
|
|
98
|
-
return null;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Read BMAD agent voice assignments
|
|
105
|
-
*/
|
|
106
|
-
async function readVoiceAssignments() {
|
|
107
|
-
const bmadPath = await getBmadPath();
|
|
108
|
-
if (!bmadPath) {
|
|
109
|
-
return null;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const csvPath = path.join(bmadPath, '_cfg', 'agent-voice-map.csv');
|
|
113
|
-
|
|
114
|
-
try {
|
|
115
|
-
const content = await fs.readFile(csvPath, 'utf8');
|
|
116
|
-
const lines = content.trim().split('\n');
|
|
117
|
-
const assignments = [];
|
|
118
|
-
|
|
119
|
-
// Skip header
|
|
120
|
-
for (let i = 1; i < lines.length; i++) {
|
|
121
|
-
const [agent_id, voice_name] = lines[i].split(',');
|
|
122
|
-
if (agent_id && voice_name) {
|
|
123
|
-
assignments.push({ agent_id, voice_name });
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
return assignments;
|
|
128
|
-
} catch {
|
|
129
|
-
return null;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Write BMAD agent voice assignments
|
|
135
|
-
*/
|
|
136
|
-
async function writeVoiceAssignments(assignments) {
|
|
137
|
-
const bmadPath = await getBmadPath();
|
|
138
|
-
if (!bmadPath) {
|
|
139
|
-
throw new Error('BMAD not found');
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
const cfgDir = path.join(bmadPath, '_cfg');
|
|
143
|
-
const csvPath = path.join(cfgDir, 'agent-voice-map.csv');
|
|
144
|
-
|
|
145
|
-
// Ensure directory exists
|
|
146
|
-
await fs.mkdir(cfgDir, { recursive: true });
|
|
147
|
-
|
|
148
|
-
// Build CSV content
|
|
149
|
-
const lines = ['agent_id,voice_name'];
|
|
150
|
-
for (const { agent_id, voice_name } of assignments) {
|
|
151
|
-
lines.push(`${agent_id},${voice_name}`);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
await fs.writeFile(csvPath, lines.join('\n') + '\n', 'utf8');
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Find matching voice name using fuzzy matching
|
|
159
|
-
* Supports partial matches like "ryan" ā "en_US-ryan-high"
|
|
160
|
-
*/
|
|
161
|
-
function findVoiceMatch(input) {
|
|
162
|
-
const lowerInput = input.toLowerCase();
|
|
163
|
-
|
|
164
|
-
// Exact match
|
|
165
|
-
if (PIPER_VOICES[input]) {
|
|
166
|
-
return input;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// Find voices containing the input string
|
|
170
|
-
const matches = Object.keys(PIPER_VOICES).filter(voice =>
|
|
171
|
-
voice.toLowerCase().includes(lowerInput)
|
|
172
|
-
);
|
|
173
|
-
|
|
174
|
-
if (matches.length === 1) {
|
|
175
|
-
return matches[0];
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
if (matches.length > 1) {
|
|
179
|
-
// Return the shortest match (most specific)
|
|
180
|
-
// Initial value prevents TypeError if array were empty
|
|
181
|
-
return matches.reduce((a, b) => (a.length <= b.length ? a : b), matches[0]);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
return null;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Command: preview-voice <voice-name>
|
|
189
|
-
* Preview a voice with sample text
|
|
190
|
-
*/
|
|
191
|
-
export async function previewVoice(voiceName, options = {}) {
|
|
192
|
-
const text = options.text || 'Hello! This is a voice preview for AgentVibes.';
|
|
193
|
-
|
|
194
|
-
// Try fuzzy matching
|
|
195
|
-
const matchedVoice = findVoiceMatch(voiceName);
|
|
196
|
-
|
|
197
|
-
if (!matchedVoice) {
|
|
198
|
-
console.error(chalk.red(`\nā Voice "${voiceName}" not found.`));
|
|
199
|
-
console.error(chalk.gray(' View available voices: npx agentvibes list-available-voices\n'));
|
|
200
|
-
process.exit(1);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
if (matchedVoice !== voiceName) {
|
|
204
|
-
console.log(chalk.yellow(`\nš” Matched "${voiceName}" to "${matchedVoice}"\n`));
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
console.log(chalk.cyan(`š Previewing voice: ${matchedVoice}\n`));
|
|
208
|
-
console.log(chalk.gray(` Text: "${text}"\n`));
|
|
209
|
-
|
|
210
|
-
const targetDir = getWorkingDirectory();
|
|
211
|
-
const playTtsPath = path.join(targetDir, '.claude', 'hooks', 'play-tts.sh');
|
|
212
|
-
|
|
213
|
-
try {
|
|
214
|
-
await fs.access(playTtsPath);
|
|
215
|
-
} catch {
|
|
216
|
-
console.error(chalk.red('ā AgentVibes not installed in this directory.'));
|
|
217
|
-
console.error(chalk.gray(' Run: npx agentvibes install\n'));
|
|
218
|
-
process.exit(1);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
try {
|
|
222
|
-
// Security: Use execFileSync with array args to prevent command injection
|
|
223
|
-
execFileSync('bash', [playTtsPath, text, matchedVoice], {
|
|
224
|
-
stdio: 'inherit',
|
|
225
|
-
cwd: targetDir,
|
|
226
|
-
});
|
|
227
|
-
} catch (error) {
|
|
228
|
-
console.error(chalk.red('\nā Failed to play voice preview'));
|
|
229
|
-
console.error(chalk.gray(` Voice: ${matchedVoice}`));
|
|
230
|
-
console.error(chalk.gray(` Error: ${error.message}\n`));
|
|
231
|
-
process.exit(1);
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* Command: list-available-voices
|
|
237
|
-
* Show all available voices grouped by provider
|
|
238
|
-
*/
|
|
239
|
-
export async function listAvailableVoices() {
|
|
240
|
-
console.log(chalk.cyan('\nš Available Voices:\n'));
|
|
241
|
-
|
|
242
|
-
console.log(chalk.white.bold('Piper TTS Voices (Free):'));
|
|
243
|
-
console.log(chalk.gray('ā'.repeat(60)));
|
|
244
|
-
|
|
245
|
-
for (const [voice, description] of Object.entries(PIPER_VOICES)) {
|
|
246
|
-
console.log(chalk.cyan(` ${voice.padEnd(30)}`) + chalk.gray(description));
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
console.log('');
|
|
250
|
-
console.log(chalk.gray('š” Tip: Preview voices with:'));
|
|
251
|
-
console.log(chalk.white(' npx agentvibes preview-voice <voice-name>'));
|
|
252
|
-
console.log('');
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
/**
|
|
256
|
-
* Command: list-bmad-assigned-voices
|
|
257
|
-
* Show all BMAD agents with their current voice assignments
|
|
258
|
-
*/
|
|
259
|
-
export async function listBmadAssignedVoices() {
|
|
260
|
-
if (!(await isBmadInstalled())) {
|
|
261
|
-
console.error(chalk.red('\nā BMAD not found in this directory.'));
|
|
262
|
-
console.error(chalk.gray(' Install BMAD first: npx bmad install\n'));
|
|
263
|
-
process.exit(1);
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
const assignments = await readVoiceAssignments();
|
|
267
|
-
|
|
268
|
-
if (!assignments || assignments.length === 0) {
|
|
269
|
-
console.error(chalk.yellow('\nā ļø No voice assignments found.'));
|
|
270
|
-
console.error(chalk.gray(' Create assignments with: npx agentvibes reset-bmad-voices\n'));
|
|
271
|
-
process.exit(1);
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
console.log(chalk.cyan('\nšļø BMAD Agent Voice Assignments:\n'));
|
|
275
|
-
console.log(chalk.gray('ā'.repeat(70)));
|
|
276
|
-
|
|
277
|
-
for (const { agent_id, voice_name } of assignments) {
|
|
278
|
-
const description = PIPER_VOICES[voice_name] || 'Unknown voice';
|
|
279
|
-
const paddedAgent = agent_id.padEnd(20);
|
|
280
|
-
console.log(
|
|
281
|
-
chalk.white(` ${paddedAgent}`) +
|
|
282
|
-
chalk.cyan('ā ') +
|
|
283
|
-
chalk.yellow(voice_name.padEnd(30)) +
|
|
284
|
-
chalk.gray(`(${description})`)
|
|
285
|
-
);
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
console.log('');
|
|
289
|
-
console.log(chalk.gray('š” Tip: Change assignments with:'));
|
|
290
|
-
console.log(chalk.white(' npx agentvibes assign-voice <agent-id> <voice-name>'));
|
|
291
|
-
console.log('');
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
/**
|
|
295
|
-
* Command: assign-voice <agent-id> <voice-name>
|
|
296
|
-
* Assign a voice to a specific BMAD agent
|
|
297
|
-
*/
|
|
298
|
-
export async function assignVoice(agentId, voiceName) {
|
|
299
|
-
if (!(await isBmadInstalled())) {
|
|
300
|
-
console.error(chalk.red('\nā BMAD not found in this directory.'));
|
|
301
|
-
console.error(chalk.gray(' Install BMAD first: npx bmad install\n'));
|
|
302
|
-
process.exit(1);
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
// Validate voice exists
|
|
306
|
-
if (!PIPER_VOICES[voiceName]) {
|
|
307
|
-
console.error(chalk.red(`\nā Voice "${voiceName}" not found.`));
|
|
308
|
-
console.error(chalk.gray(' View available voices: npx agentvibes list-available-voices\n'));
|
|
309
|
-
process.exit(1);
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
let assignments = await readVoiceAssignments();
|
|
313
|
-
|
|
314
|
-
if (!assignments) {
|
|
315
|
-
console.log(chalk.yellow('ā ļø No voice assignments found. Creating default assignments...\n'));
|
|
316
|
-
assignments = DEFAULT_VOICE_ASSIGNMENTS.map(({ agent_id, voice_name }) => ({
|
|
317
|
-
agent_id,
|
|
318
|
-
voice_name,
|
|
319
|
-
}));
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
// Find and update assignment
|
|
323
|
-
const existing = assignments.find(a => a.agent_id === agentId);
|
|
324
|
-
|
|
325
|
-
if (existing) {
|
|
326
|
-
const oldVoice = existing.voice_name;
|
|
327
|
-
existing.voice_name = voiceName;
|
|
328
|
-
console.log(chalk.green(`\nā Updated ${agentId} voice assignment:`));
|
|
329
|
-
console.log(chalk.gray(` ${oldVoice} ā ${voiceName}\n`));
|
|
330
|
-
} else {
|
|
331
|
-
assignments.push({ agent_id: agentId, voice_name: voiceName });
|
|
332
|
-
console.log(chalk.green(`\nā Created new voice assignment for ${agentId}:`));
|
|
333
|
-
console.log(chalk.gray(` Voice: ${voiceName}\n`));
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
await writeVoiceAssignments(assignments);
|
|
337
|
-
|
|
338
|
-
console.log(chalk.gray('š” Test the voice with:'));
|
|
339
|
-
console.log(chalk.white(` npx agentvibes preview-voice ${voiceName}\n`));
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
/**
|
|
343
|
-
* Command: reset-bmad-voices
|
|
344
|
-
* Reset all BMAD agents to default voice assignments
|
|
345
|
-
*/
|
|
346
|
-
export async function resetBmadVoices(options = {}) {
|
|
347
|
-
if (!(await isBmadInstalled())) {
|
|
348
|
-
console.error(chalk.red('\nā BMAD not found in this directory.'));
|
|
349
|
-
console.error(chalk.gray(' Install BMAD first: npx bmad install\n'));
|
|
350
|
-
process.exit(1);
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
console.log(chalk.yellow('\nā ļø Reset BMAD Voice Assignments\n'));
|
|
354
|
-
console.log(chalk.gray('This will reset all agent voices to defaults:\n'));
|
|
355
|
-
|
|
356
|
-
for (const { agent_id, voice_name, description } of DEFAULT_VOICE_ASSIGNMENTS) {
|
|
357
|
-
console.log(chalk.gray(` ${agent_id.padEnd(15)} ā ${voice_name.padEnd(25)} (${description})`));
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
console.log('');
|
|
361
|
-
|
|
362
|
-
if (!options.yes) {
|
|
363
|
-
const readline = await import('node:readline');
|
|
364
|
-
const rl = readline.createInterface({
|
|
365
|
-
input: process.stdin,
|
|
366
|
-
output: process.stdout,
|
|
367
|
-
});
|
|
368
|
-
|
|
369
|
-
const answer = await new Promise(resolve => {
|
|
370
|
-
rl.question(chalk.cyan('Continue with reset? (y/N): '), answer => {
|
|
371
|
-
rl.close();
|
|
372
|
-
resolve(answer);
|
|
373
|
-
});
|
|
374
|
-
});
|
|
375
|
-
|
|
376
|
-
if (answer.toLowerCase() !== 'y') {
|
|
377
|
-
console.log(chalk.red('\nā Reset cancelled.\n'));
|
|
378
|
-
process.exit(0);
|
|
379
|
-
}
|
|
380
|
-
} else {
|
|
381
|
-
console.log(chalk.green('ā Auto-confirmed (--yes flag)\n'));
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
const assignments = DEFAULT_VOICE_ASSIGNMENTS.map(({ agent_id, voice_name }) => ({
|
|
385
|
-
agent_id,
|
|
386
|
-
voice_name,
|
|
387
|
-
}));
|
|
388
|
-
|
|
389
|
-
await writeVoiceAssignments(assignments);
|
|
390
|
-
|
|
391
|
-
console.log(chalk.green('\nā Voice assignments reset to defaults!\n'));
|
|
392
|
-
console.log(chalk.gray('š” View assignments with:'));
|
|
393
|
-
console.log(chalk.white(' npx agentvibes list-bmad-assigned-voices\n'));
|
|
394
|
-
}
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* File: src/commands/bmad-voices.js
|
|
5
|
+
*
|
|
6
|
+
* AgentVibes - BMAD Voice Management Commands
|
|
7
|
+
*
|
|
8
|
+
* Provides CLI commands for managing BMAD agent voice assignments.
|
|
9
|
+
* These commands make it easy to preview, list, and assign voices
|
|
10
|
+
* without manually editing CSV files.
|
|
11
|
+
*
|
|
12
|
+
* Co-created by Paul Preibisch with Claude AI
|
|
13
|
+
* Copyright (c) 2025 Paul Preibisch
|
|
14
|
+
* Licensed under the Apache License, Version 2.0
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { execFileSync } from 'node:child_process';
|
|
18
|
+
import path from 'node:path';
|
|
19
|
+
import fs from 'node:fs/promises';
|
|
20
|
+
import chalk from 'chalk';
|
|
21
|
+
|
|
22
|
+
// Default BMAD agent voice assignments
|
|
23
|
+
const DEFAULT_VOICE_ASSIGNMENTS = [
|
|
24
|
+
{ agent_id: 'pm', voice_name: 'en_US-ryan-high', description: 'Professional male' },
|
|
25
|
+
{ agent_id: 'architect', voice_name: 'en_US-danny-low', description: 'Deep male' },
|
|
26
|
+
{ agent_id: 'dev', voice_name: 'en_US-joe-medium', description: 'Casual male' },
|
|
27
|
+
{ agent_id: 'analyst', voice_name: 'en_US-amy-medium', description: 'Articulate female' },
|
|
28
|
+
{ agent_id: 'ux-designer', voice_name: 'en_US-kristin-medium', description: 'Warm female' },
|
|
29
|
+
{ agent_id: 'tea', voice_name: 'en_US-lessac-medium', description: 'Balanced neutral' },
|
|
30
|
+
{ agent_id: 'sm', voice_name: 'en_US-bryce-medium', description: 'Energetic male' },
|
|
31
|
+
{ agent_id: 'tech-writer', voice_name: 'en_US-kathleen-low', description: 'Clear female' },
|
|
32
|
+
{ agent_id: 'frame-expert', voice_name: 'en_US-kusal-medium', description: 'Precise male' },
|
|
33
|
+
{ agent_id: 'bmad-master', voice_name: 'en_US-libritts_r-high', description: 'Rich commanding' },
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
// All available Piper voices with descriptions
|
|
37
|
+
const PIPER_VOICES = {
|
|
38
|
+
'en_US-ryan-high': 'Professional, clear male voice',
|
|
39
|
+
'en_US-danny-low': 'Deep, authoritative male voice',
|
|
40
|
+
'en_US-joe-medium': 'Casual, friendly male voice',
|
|
41
|
+
'en_US-amy-medium': 'Articulate female voice',
|
|
42
|
+
'en_US-kristin-medium': 'Warm, professional female voice',
|
|
43
|
+
'en_US-lessac-medium': 'Balanced, neutral voice',
|
|
44
|
+
'en_US-bryce-medium': 'Energetic male voice',
|
|
45
|
+
'en_US-kathleen-low': 'Clear, measured female voice',
|
|
46
|
+
'en_US-kusal-medium': 'Precise male voice',
|
|
47
|
+
'en_US-libritts_r-high': 'Rich, commanding voice',
|
|
48
|
+
'en_US-hfc_female-medium': 'Clear female voice',
|
|
49
|
+
'en_US-hfc_male-medium': 'Clear male voice',
|
|
50
|
+
'en_US-arctic-medium': 'Crisp, neutral voice',
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Get the working directory (handles npx context)
|
|
55
|
+
*/
|
|
56
|
+
function getWorkingDirectory() {
|
|
57
|
+
return process.env.INIT_CWD || process.cwd();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Check if BMAD is installed
|
|
62
|
+
*/
|
|
63
|
+
async function isBmadInstalled() {
|
|
64
|
+
const targetDir = getWorkingDirectory();
|
|
65
|
+
const bmadPath = path.join(targetDir, '.bmad');
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
await fs.access(bmadPath);
|
|
69
|
+
return true;
|
|
70
|
+
} catch {
|
|
71
|
+
// Try legacy location
|
|
72
|
+
const legacyPath = path.join(targetDir, 'bmad');
|
|
73
|
+
try {
|
|
74
|
+
await fs.access(legacyPath);
|
|
75
|
+
return true;
|
|
76
|
+
} catch {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Get BMAD folder path (.bmad or bmad)
|
|
84
|
+
*/
|
|
85
|
+
async function getBmadPath() {
|
|
86
|
+
const targetDir = getWorkingDirectory();
|
|
87
|
+
const modernPath = path.join(targetDir, '.bmad');
|
|
88
|
+
const legacyPath = path.join(targetDir, 'bmad');
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
await fs.access(modernPath);
|
|
92
|
+
return modernPath;
|
|
93
|
+
} catch {
|
|
94
|
+
try {
|
|
95
|
+
await fs.access(legacyPath);
|
|
96
|
+
return legacyPath;
|
|
97
|
+
} catch {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Read BMAD agent voice assignments
|
|
105
|
+
*/
|
|
106
|
+
async function readVoiceAssignments() {
|
|
107
|
+
const bmadPath = await getBmadPath();
|
|
108
|
+
if (!bmadPath) {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const csvPath = path.join(bmadPath, '_cfg', 'agent-voice-map.csv');
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
const content = await fs.readFile(csvPath, 'utf8');
|
|
116
|
+
const lines = content.trim().split('\n');
|
|
117
|
+
const assignments = [];
|
|
118
|
+
|
|
119
|
+
// Skip header
|
|
120
|
+
for (let i = 1; i < lines.length; i++) {
|
|
121
|
+
const [agent_id, voice_name] = lines[i].split(',');
|
|
122
|
+
if (agent_id && voice_name) {
|
|
123
|
+
assignments.push({ agent_id, voice_name });
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return assignments;
|
|
128
|
+
} catch {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Write BMAD agent voice assignments
|
|
135
|
+
*/
|
|
136
|
+
async function writeVoiceAssignments(assignments) {
|
|
137
|
+
const bmadPath = await getBmadPath();
|
|
138
|
+
if (!bmadPath) {
|
|
139
|
+
throw new Error('BMAD not found');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const cfgDir = path.join(bmadPath, '_cfg');
|
|
143
|
+
const csvPath = path.join(cfgDir, 'agent-voice-map.csv');
|
|
144
|
+
|
|
145
|
+
// Ensure directory exists
|
|
146
|
+
await fs.mkdir(cfgDir, { recursive: true });
|
|
147
|
+
|
|
148
|
+
// Build CSV content
|
|
149
|
+
const lines = ['agent_id,voice_name'];
|
|
150
|
+
for (const { agent_id, voice_name } of assignments) {
|
|
151
|
+
lines.push(`${agent_id},${voice_name}`);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
await fs.writeFile(csvPath, lines.join('\n') + '\n', 'utf8');
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Find matching voice name using fuzzy matching
|
|
159
|
+
* Supports partial matches like "ryan" ā "en_US-ryan-high"
|
|
160
|
+
*/
|
|
161
|
+
function findVoiceMatch(input) {
|
|
162
|
+
const lowerInput = input.toLowerCase();
|
|
163
|
+
|
|
164
|
+
// Exact match
|
|
165
|
+
if (PIPER_VOICES[input]) {
|
|
166
|
+
return input;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Find voices containing the input string
|
|
170
|
+
const matches = Object.keys(PIPER_VOICES).filter(voice =>
|
|
171
|
+
voice.toLowerCase().includes(lowerInput)
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
if (matches.length === 1) {
|
|
175
|
+
return matches[0];
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (matches.length > 1) {
|
|
179
|
+
// Return the shortest match (most specific)
|
|
180
|
+
// Initial value prevents TypeError if array were empty
|
|
181
|
+
return matches.reduce((a, b) => (a.length <= b.length ? a : b), matches[0]);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Command: preview-voice <voice-name>
|
|
189
|
+
* Preview a voice with sample text
|
|
190
|
+
*/
|
|
191
|
+
export async function previewVoice(voiceName, options = {}) {
|
|
192
|
+
const text = options.text || 'Hello! This is a voice preview for AgentVibes.';
|
|
193
|
+
|
|
194
|
+
// Try fuzzy matching
|
|
195
|
+
const matchedVoice = findVoiceMatch(voiceName);
|
|
196
|
+
|
|
197
|
+
if (!matchedVoice) {
|
|
198
|
+
console.error(chalk.red(`\nā Voice "${voiceName}" not found.`));
|
|
199
|
+
console.error(chalk.gray(' View available voices: npx agentvibes list-available-voices\n'));
|
|
200
|
+
process.exit(1);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (matchedVoice !== voiceName) {
|
|
204
|
+
console.log(chalk.yellow(`\nš” Matched "${voiceName}" to "${matchedVoice}"\n`));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
console.log(chalk.cyan(`š Previewing voice: ${matchedVoice}\n`));
|
|
208
|
+
console.log(chalk.gray(` Text: "${text}"\n`));
|
|
209
|
+
|
|
210
|
+
const targetDir = getWorkingDirectory();
|
|
211
|
+
const playTtsPath = path.join(targetDir, '.claude', 'hooks', 'play-tts.sh');
|
|
212
|
+
|
|
213
|
+
try {
|
|
214
|
+
await fs.access(playTtsPath);
|
|
215
|
+
} catch {
|
|
216
|
+
console.error(chalk.red('ā AgentVibes not installed in this directory.'));
|
|
217
|
+
console.error(chalk.gray(' Run: npx agentvibes install\n'));
|
|
218
|
+
process.exit(1);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
try {
|
|
222
|
+
// Security: Use execFileSync with array args to prevent command injection
|
|
223
|
+
execFileSync('bash', [playTtsPath, text, matchedVoice], {
|
|
224
|
+
stdio: 'inherit',
|
|
225
|
+
cwd: targetDir,
|
|
226
|
+
});
|
|
227
|
+
} catch (error) {
|
|
228
|
+
console.error(chalk.red('\nā Failed to play voice preview'));
|
|
229
|
+
console.error(chalk.gray(` Voice: ${matchedVoice}`));
|
|
230
|
+
console.error(chalk.gray(` Error: ${error.message}\n`));
|
|
231
|
+
process.exit(1);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Command: list-available-voices
|
|
237
|
+
* Show all available voices grouped by provider
|
|
238
|
+
*/
|
|
239
|
+
export async function listAvailableVoices() {
|
|
240
|
+
console.log(chalk.cyan('\nš Available Voices:\n'));
|
|
241
|
+
|
|
242
|
+
console.log(chalk.white.bold('Piper TTS Voices (Free):'));
|
|
243
|
+
console.log(chalk.gray('ā'.repeat(60)));
|
|
244
|
+
|
|
245
|
+
for (const [voice, description] of Object.entries(PIPER_VOICES)) {
|
|
246
|
+
console.log(chalk.cyan(` ${voice.padEnd(30)}`) + chalk.gray(description));
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
console.log('');
|
|
250
|
+
console.log(chalk.gray('š” Tip: Preview voices with:'));
|
|
251
|
+
console.log(chalk.white(' npx agentvibes preview-voice <voice-name>'));
|
|
252
|
+
console.log('');
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Command: list-bmad-assigned-voices
|
|
257
|
+
* Show all BMAD agents with their current voice assignments
|
|
258
|
+
*/
|
|
259
|
+
export async function listBmadAssignedVoices() {
|
|
260
|
+
if (!(await isBmadInstalled())) {
|
|
261
|
+
console.error(chalk.red('\nā BMAD not found in this directory.'));
|
|
262
|
+
console.error(chalk.gray(' Install BMAD first: npx bmad install\n'));
|
|
263
|
+
process.exit(1);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const assignments = await readVoiceAssignments();
|
|
267
|
+
|
|
268
|
+
if (!assignments || assignments.length === 0) {
|
|
269
|
+
console.error(chalk.yellow('\nā ļø No voice assignments found.'));
|
|
270
|
+
console.error(chalk.gray(' Create assignments with: npx agentvibes reset-bmad-voices\n'));
|
|
271
|
+
process.exit(1);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
console.log(chalk.cyan('\nšļø BMAD Agent Voice Assignments:\n'));
|
|
275
|
+
console.log(chalk.gray('ā'.repeat(70)));
|
|
276
|
+
|
|
277
|
+
for (const { agent_id, voice_name } of assignments) {
|
|
278
|
+
const description = PIPER_VOICES[voice_name] || 'Unknown voice';
|
|
279
|
+
const paddedAgent = agent_id.padEnd(20);
|
|
280
|
+
console.log(
|
|
281
|
+
chalk.white(` ${paddedAgent}`) +
|
|
282
|
+
chalk.cyan('ā ') +
|
|
283
|
+
chalk.yellow(voice_name.padEnd(30)) +
|
|
284
|
+
chalk.gray(`(${description})`)
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
console.log('');
|
|
289
|
+
console.log(chalk.gray('š” Tip: Change assignments with:'));
|
|
290
|
+
console.log(chalk.white(' npx agentvibes assign-voice <agent-id> <voice-name>'));
|
|
291
|
+
console.log('');
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Command: assign-voice <agent-id> <voice-name>
|
|
296
|
+
* Assign a voice to a specific BMAD agent
|
|
297
|
+
*/
|
|
298
|
+
export async function assignVoice(agentId, voiceName) {
|
|
299
|
+
if (!(await isBmadInstalled())) {
|
|
300
|
+
console.error(chalk.red('\nā BMAD not found in this directory.'));
|
|
301
|
+
console.error(chalk.gray(' Install BMAD first: npx bmad install\n'));
|
|
302
|
+
process.exit(1);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Validate voice exists
|
|
306
|
+
if (!PIPER_VOICES[voiceName]) {
|
|
307
|
+
console.error(chalk.red(`\nā Voice "${voiceName}" not found.`));
|
|
308
|
+
console.error(chalk.gray(' View available voices: npx agentvibes list-available-voices\n'));
|
|
309
|
+
process.exit(1);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
let assignments = await readVoiceAssignments();
|
|
313
|
+
|
|
314
|
+
if (!assignments) {
|
|
315
|
+
console.log(chalk.yellow('ā ļø No voice assignments found. Creating default assignments...\n'));
|
|
316
|
+
assignments = DEFAULT_VOICE_ASSIGNMENTS.map(({ agent_id, voice_name }) => ({
|
|
317
|
+
agent_id,
|
|
318
|
+
voice_name,
|
|
319
|
+
}));
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Find and update assignment
|
|
323
|
+
const existing = assignments.find(a => a.agent_id === agentId);
|
|
324
|
+
|
|
325
|
+
if (existing) {
|
|
326
|
+
const oldVoice = existing.voice_name;
|
|
327
|
+
existing.voice_name = voiceName;
|
|
328
|
+
console.log(chalk.green(`\nā Updated ${agentId} voice assignment:`));
|
|
329
|
+
console.log(chalk.gray(` ${oldVoice} ā ${voiceName}\n`));
|
|
330
|
+
} else {
|
|
331
|
+
assignments.push({ agent_id: agentId, voice_name: voiceName });
|
|
332
|
+
console.log(chalk.green(`\nā Created new voice assignment for ${agentId}:`));
|
|
333
|
+
console.log(chalk.gray(` Voice: ${voiceName}\n`));
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
await writeVoiceAssignments(assignments);
|
|
337
|
+
|
|
338
|
+
console.log(chalk.gray('š” Test the voice with:'));
|
|
339
|
+
console.log(chalk.white(` npx agentvibes preview-voice ${voiceName}\n`));
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Command: reset-bmad-voices
|
|
344
|
+
* Reset all BMAD agents to default voice assignments
|
|
345
|
+
*/
|
|
346
|
+
export async function resetBmadVoices(options = {}) {
|
|
347
|
+
if (!(await isBmadInstalled())) {
|
|
348
|
+
console.error(chalk.red('\nā BMAD not found in this directory.'));
|
|
349
|
+
console.error(chalk.gray(' Install BMAD first: npx bmad install\n'));
|
|
350
|
+
process.exit(1);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
console.log(chalk.yellow('\nā ļø Reset BMAD Voice Assignments\n'));
|
|
354
|
+
console.log(chalk.gray('This will reset all agent voices to defaults:\n'));
|
|
355
|
+
|
|
356
|
+
for (const { agent_id, voice_name, description } of DEFAULT_VOICE_ASSIGNMENTS) {
|
|
357
|
+
console.log(chalk.gray(` ${agent_id.padEnd(15)} ā ${voice_name.padEnd(25)} (${description})`));
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
console.log('');
|
|
361
|
+
|
|
362
|
+
if (!options.yes) {
|
|
363
|
+
const readline = await import('node:readline');
|
|
364
|
+
const rl = readline.createInterface({
|
|
365
|
+
input: process.stdin,
|
|
366
|
+
output: process.stdout,
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
const answer = await new Promise(resolve => {
|
|
370
|
+
rl.question(chalk.cyan('Continue with reset? (y/N): '), answer => {
|
|
371
|
+
rl.close();
|
|
372
|
+
resolve(answer);
|
|
373
|
+
});
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
if (answer.toLowerCase() !== 'y') {
|
|
377
|
+
console.log(chalk.red('\nā Reset cancelled.\n'));
|
|
378
|
+
process.exit(0);
|
|
379
|
+
}
|
|
380
|
+
} else {
|
|
381
|
+
console.log(chalk.green('ā Auto-confirmed (--yes flag)\n'));
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const assignments = DEFAULT_VOICE_ASSIGNMENTS.map(({ agent_id, voice_name }) => ({
|
|
385
|
+
agent_id,
|
|
386
|
+
voice_name,
|
|
387
|
+
}));
|
|
388
|
+
|
|
389
|
+
await writeVoiceAssignments(assignments);
|
|
390
|
+
|
|
391
|
+
console.log(chalk.green('\nā Voice assignments reset to defaults!\n'));
|
|
392
|
+
console.log(chalk.gray('š” View assignments with:'));
|
|
393
|
+
console.log(chalk.white(' npx agentvibes list-bmad-assigned-voices\n'));
|
|
394
|
+
}
|