agentvibes 5.1.4 → 5.2.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.
Files changed (69) hide show
  1. package/.agentvibes/config.json +23 -13
  2. package/.claude/commands/agent-vibes/verbosity.md +98 -89
  3. package/.claude/config/audio-effects.cfg +4 -1
  4. package/.claude/hooks/audio-cache-utils.sh +246 -246
  5. package/.claude/hooks/background-music-manager.sh +404 -404
  6. package/.claude/hooks/bmad-speak-enhanced.sh +165 -165
  7. package/.claude/hooks/bmad-speak.sh +290 -290
  8. package/.claude/hooks/bmad-tts-injector.sh +568 -568
  9. package/.claude/hooks/bmad-voice-manager.sh +928 -928
  10. package/.claude/hooks/clawdbot-receiver-SECURE.sh +129 -129
  11. package/.claude/hooks/clawdbot-receiver.sh +107 -107
  12. package/.claude/hooks/clean-audio-cache.sh +22 -22
  13. package/.claude/hooks/cleanup-cache.sh +106 -106
  14. package/.claude/hooks/configure-rdp-mode.sh +137 -137
  15. package/.claude/hooks/download-extra-voices.sh +244 -244
  16. package/.claude/hooks/effects-manager.sh +268 -268
  17. package/.claude/hooks/github-star-reminder.sh +154 -154
  18. package/.claude/hooks/language-manager.sh +362 -362
  19. package/.claude/hooks/learn-manager.sh +492 -492
  20. package/.claude/hooks/macos-voice-manager.sh +205 -205
  21. package/.claude/hooks/migrate-background-music.sh +125 -125
  22. package/.claude/hooks/migrate-to-agentvibes.sh +161 -161
  23. package/.claude/hooks/optimize-background-music.sh +87 -87
  24. package/.claude/hooks/path-resolver.sh +60 -60
  25. package/.claude/hooks/personality-manager.sh +448 -448
  26. package/.claude/hooks/piper-download-voices.sh +233 -225
  27. package/.claude/hooks/piper-installer.sh +292 -292
  28. package/.claude/hooks/piper-multispeaker-registry.sh +171 -171
  29. package/.claude/hooks/piper-voice-manager.sh +125 -0
  30. package/.claude/hooks/play-tts-agentvibes-receiver-for-voiceless-connections.sh +97 -90
  31. package/.claude/hooks/play-tts-enhanced.sh +105 -105
  32. package/.claude/hooks/play-tts-piper.sh +16 -5
  33. package/.claude/hooks/play-tts-ssh-remote.sh +168 -167
  34. package/.claude/hooks/play-tts-termux-ssh.sh +169 -169
  35. package/.claude/hooks/play-tts.sh +35 -14
  36. package/.claude/hooks/prepare-release.sh +54 -54
  37. package/.claude/hooks/provider-commands.sh +617 -617
  38. package/.claude/hooks/provider-manager.sh +399 -399
  39. package/.claude/hooks/replay-target-audio.sh +95 -95
  40. package/.claude/hooks/sentiment-manager.sh +201 -201
  41. package/.claude/hooks/session-start-tts.sh +4 -1
  42. package/.claude/hooks/speed-manager.sh +291 -291
  43. package/.claude/hooks/stop-tts.sh +84 -84
  44. package/.claude/hooks/termux-installer.sh +261 -261
  45. package/.claude/hooks/translate-manager.sh +341 -341
  46. package/.claude/hooks/tts-queue-worker.sh +145 -145
  47. package/.claude/hooks/tts-queue.sh +165 -165
  48. package/.claude/hooks/verbosity-manager.sh +185 -178
  49. package/.claude/hooks/voice-manager.sh +552 -548
  50. package/.claude/hooks-windows/download-extra-voices.ps1 +243 -185
  51. package/.claude/hooks-windows/play-tts-piper.ps1 +7 -2
  52. package/.claude/hooks-windows/play-tts.ps1 +9 -3
  53. package/.claude/hooks-windows/session-start-tts.ps1 +2 -1
  54. package/.claude/hooks-windows/verbosity-manager.ps1 +126 -119
  55. package/README.md +19 -2
  56. package/RELEASE_NOTES.md +74 -0
  57. package/bin/agentvibes-voice-browser.js +1939 -1840
  58. package/bin/mcp-server.sh +206 -206
  59. package/mcp-server/server.py +87 -15
  60. package/package.json +1 -1
  61. package/src/console/tabs/receiver-tab.js +1527 -1483
  62. package/src/console/tabs/settings-tab.js +2 -2
  63. package/src/console/tabs/setup-tab.js +112 -31
  64. package/src/console/tabs/voices-tab.js +130 -13
  65. package/src/i18n/en.js +202 -202
  66. package/src/installer.js +79 -213
  67. package/src/services/llm-provider-service.js +126 -75
  68. package/src/services/verbosity-service.js +159 -157
  69. package/templates/agentvibes-receiver.sh +3 -2
@@ -1,928 +1,928 @@
1
- #!/usr/bin/env bash
2
- #
3
- # File: .claude/hooks/bmad-voice-manager.sh
4
- #
5
- # AgentVibes - Finally, your AI Agents can Talk Back! Text-to-Speech WITH personality for AI Assistants!
6
- # Website: https://agentvibes.org
7
- # Repository: https://github.com/paulpreibisch/AgentVibes
8
- #
9
- # Co-created by Paul Preibisch with Claude AI
10
- # Copyright (c) 2025 Paul Preibisch
11
- #
12
- # Licensed under the Apache License, Version 2.0 (the "License");
13
- # you may not use this file except in compliance with the License.
14
- # You may obtain a copy of the License at
15
- #
16
- # http://www.apache.org/licenses/LICENSE-2.0
17
- #
18
- # Unless required by applicable law or agreed to in writing, software
19
- # distributed under the License is distributed on an "AS IS" BASIS,
20
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21
- # See the License for the specific language governing permissions and
22
- # limitations under the License.
23
- #
24
- # DISCLAIMER: This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
25
- # express or implied, including but not limited to the warranties of
26
- # merchantability, fitness for a particular purpose and noninfringement.
27
- # In no event shall the authors or copyright holders be liable for any claim,
28
- # damages or other liability, whether in an action of contract, tort or
29
- # otherwise, arising from, out of or in connection with the software or the
30
- # use or other dealings in the software.
31
- #
32
- # ---
33
- #
34
- # @fileoverview BMAD Voice Plugin Manager - Maps BMAD agents to unique TTS voices
35
- # @context Enables each BMAD agent to have its own distinct voice for multi-agent sessions
36
- # @architecture Markdown table-based voice mapping with enable/disable flag, auto-detection of BMAD
37
- # @dependencies .claude/config/bmad-voices.md (voice mappings), bmad-tts-injector.sh, .bmad-core/ (BMAD installation)
38
- # @entrypoints Called by /agent-vibes:bmad commands, auto-enabled on BMAD detection
39
- # @patterns Plugin architecture, auto-enable on dependency detection, state backup/restore on toggle
40
- # @related bmad-tts-injector.sh, .claude/config/bmad-voices.md, .bmad-agent-context file
41
-
42
- CONFIG_DIR=".agentvibes/bmad"
43
- VOICE_CONFIG_FILE="$CONFIG_DIR/bmad-voices.md"
44
- ENABLED_FLAG="$CONFIG_DIR/bmad-voices-enabled.flag"
45
-
46
- # AI NOTE: Auto-enable pattern - When BMAD is detected via install-manifest.yaml,
47
- # automatically enable the voice plugin to provide seamless multi-agent voice support.
48
- # This avoids requiring manual plugin activation after BMAD installation.
49
- # Supports both BMAD v4 (.bmad-core/) and v6-alpha (bmad/) directory structures.
50
-
51
- # @function detect_bmad_version
52
- # @intent Detect BMAD installation and return version number
53
- # @why Support both v4 and v6-alpha installations with different directory structures
54
- # @param None
55
- # @returns Echoes version number (4, 6, or 0 for not installed) to stdout
56
- # @exitcode 0=detected, 1=not installed
57
- # @sideeffects None
58
- # @edgecases Checks v6 first (newer version), falls back to v4
59
- # @calledby auto_enable_if_bmad_detected, get_bmad_config_path
60
- # @calls None
61
- detect_bmad_version() {
62
- if [[ -f ".bmad/_cfg/manifest.yaml" ]]; then
63
- # v6 detected (standard path with dot prefix)
64
- echo "6"
65
- return 0
66
- elif [[ -f "bmad/_cfg/manifest.yaml" ]]; then
67
- # v6 detected (alternative path without dot prefix)
68
- echo "6"
69
- return 0
70
- elif [[ -f ".bmad-core/install-manifest.yaml" ]]; then
71
- # v4 detected
72
- echo "4"
73
- return 0
74
- else
75
- # Not installed
76
- echo "0"
77
- return 1
78
- fi
79
- }
80
-
81
- # @function get_bmad_config_path
82
- # @intent Get BMAD configuration file path based on detected version
83
- # @why v4 and v6 use different directory structures for config files
84
- # @param None
85
- # @returns Echoes config path to stdout, empty string if not installed
86
- # @exitcode 0=path returned, 1=not installed
87
- # @sideeffects None
88
- # @edgecases Returns empty string if BMAD not detected
89
- # @calledby Commands that need to read BMAD config (future use)
90
- # @calls detect_bmad_version
91
- get_bmad_config_path() {
92
- local version=$(detect_bmad_version)
93
-
94
- if [[ "$version" == "6" ]]; then
95
- # Check both possible v6 paths
96
- if [[ -f ".bmad/core/config.yaml" ]]; then
97
- echo ".bmad/core/config.yaml"
98
- else
99
- echo "bmad/core/config.yaml"
100
- fi
101
- return 0
102
- elif [[ "$version" == "4" ]]; then
103
- echo ".bmad-core/config.yaml"
104
- return 0
105
- else
106
- echo ""
107
- return 1
108
- fi
109
- }
110
-
111
- # @function auto_enable_if_bmad_detected
112
- # @intent Automatically enable BMAD voice plugin when BMAD framework is detected
113
- # @why Provide seamless integration - users shouldn't need to manually enable voice mapping
114
- # @param None
115
- # @returns None
116
- # @exitcode 0=auto-enabled, 1=not enabled (already enabled or BMAD not detected)
117
- # @sideeffects Creates enabled flag file, creates plugin directory
118
- # @edgecases Only auto-enables if plugin not already enabled, silent operation
119
- # @calledby get_agent_voice
120
- # @calls mkdir, touch, detect_bmad_version
121
- auto_enable_if_bmad_detected() {
122
- local version=$(detect_bmad_version)
123
-
124
- # Check if BMAD is installed (any version) and plugin not already enabled
125
- if [[ "$version" != "0" ]] && [[ ! -f "$ENABLED_FLAG" ]]; then
126
- # BMAD detected but plugin not enabled - enable it silently
127
- mkdir -p "$CONFIG_DIR"
128
- touch "$ENABLED_FLAG"
129
- return 0
130
- fi
131
- return 1
132
- }
133
-
134
- # @function get_agent_voice
135
- # @intent Retrieve TTS voice assigned to specific BMAD agent (provider-aware)
136
- # @why Each BMAD agent needs unique voice for multi-agent conversation differentiation
137
- # @param $1 {string} agent_id - BMAD agent identifier (pm, dev, qa, architect, etc.)
138
- # @returns Echoes voice name to stdout, empty string if plugin disabled or agent not found
139
- # @exitcode Always 0
140
- # @sideeffects May auto-enable plugin if BMAD detected
141
- # @edgecases Returns empty string if plugin disabled/missing, parses markdown table syntax
142
- # @calledby bmad-tts-injector.sh, play-tts.sh when BMAD agent is active
143
- # @calls auto_enable_if_bmad_detected, grep, awk, sed
144
- # @version 2.0.0 - Now provider-aware: returns Piper or macOS voice based on active provider
145
- get_agent_voice() {
146
- local agent_id="$1"
147
-
148
- # Check for BMAD v6 CSV file first (preferred, loose coupling)
149
- # If this exists, use it directly without requiring plugin enable flag
150
- # Support both .bmad (standard) and bmad (alternative) paths
151
- local bmad_voice_map=""
152
- if [[ -f ".bmad/_cfg/agent-voice-map.csv" ]]; then
153
- bmad_voice_map=".bmad/_cfg/agent-voice-map.csv"
154
- elif [[ -f "bmad/_cfg/agent-voice-map.csv" ]]; then
155
- bmad_voice_map="bmad/_cfg/agent-voice-map.csv"
156
- fi
157
-
158
- if [[ -n "$bmad_voice_map" ]]; then
159
- # Read from BMAD's standard _cfg directory
160
- # CSV format: agent_id,voice_name
161
- local voice=$(grep "^$agent_id," "$bmad_voice_map" | cut -d',' -f2)
162
-
163
- # If voice is empty or generic (same for all), use defaults
164
- if [[ -n "$voice" ]] && [[ "$voice" != "en_US-lessac-medium" ]]; then
165
- echo "$voice"
166
- return
167
- fi
168
- # If empty or generic, fall through to defaults below
169
- fi
170
-
171
- # Default voice mappings (hardcoded fallback when CSV is missing or has generic values)
172
- # These match the BMAD-METHOD defaults for consistency
173
- case "$agent_id" in
174
- bmad-master)
175
- echo "en_US-lessac-medium"
176
- return
177
- ;;
178
- analyst)
179
- echo "en_US-kristin-medium"
180
- return
181
- ;;
182
- architect)
183
- echo "en_GB-alan-medium"
184
- return
185
- ;;
186
- dev)
187
- echo "en_US-joe-medium"
188
- return
189
- ;;
190
- pm)
191
- echo "en_US-ryan-high"
192
- return
193
- ;;
194
- quick-flow-solo-dev)
195
- echo "en_US-joe-medium"
196
- return
197
- ;;
198
- sm)
199
- echo "en_US-amy-medium"
200
- return
201
- ;;
202
- tea)
203
- echo "en_US-kusal-medium"
204
- return
205
- ;;
206
- tech-writer)
207
- echo "en_US-kristin-medium"
208
- return
209
- ;;
210
- ux-designer)
211
- echo "en_US-kristin-medium"
212
- return
213
- ;;
214
- frame-expert)
215
- echo "en_GB-alan-medium"
216
- return
217
- ;;
218
- esac
219
-
220
- # Auto-enable if BMAD is detected (for legacy markdown config)
221
- auto_enable_if_bmad_detected
222
-
223
- if [[ ! -f "$ENABLED_FLAG" ]]; then
224
- echo "" # Plugin disabled
225
- return
226
- fi
227
-
228
- # Fallback to legacy markdown config file
229
- if [[ ! -f "$VOICE_CONFIG_FILE" ]]; then
230
- echo "" # Plugin file missing
231
- return
232
- fi
233
-
234
- # Detect active TTS provider
235
- local provider_file=""
236
- if [[ -f ".claude/tts-provider.txt" ]]; then
237
- provider_file=".claude/tts-provider.txt"
238
- elif [[ -f "$HOME/.claude/tts-provider.txt" ]]; then
239
- provider_file="$HOME/.claude/tts-provider.txt"
240
- fi
241
-
242
- local active_provider="piper" # default
243
- if [[ -n "$provider_file" ]] && [[ -f "$provider_file" ]]; then
244
- active_provider=$(cat "$provider_file")
245
- fi
246
-
247
- # Extract voice from markdown table based on provider
248
- # Table: Agent ID | Agent Name | Intro | Piper Voice | macOS Voice | Personality
249
- # AWK columns: $1=empty | $2=ID | $3=Name | $4=Intro | $5=Piper | $6=macOS | $7=Personality
250
- local column=5 # Default to Piper (AWK column 5)
251
- if [[ "$active_provider" == "piper" ]]; then
252
- column=6 # Use Piper (AWK column 6)
253
- fi
254
-
255
- local voice=$(grep "^| $agent_id " "$VOICE_CONFIG_FILE" | \
256
- awk -F'|' "{print \$$column}" | \
257
- sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
258
-
259
- echo "$voice"
260
- }
261
-
262
- # @function sync_intros_from_manifest
263
- # @intent Synchronize generic intros in agent-voice-map.csv with displayNames from agent-manifest.csv
264
- # @why Until BMAD PR 987 merges, CSV has generic "Hello! Ready to help with the discussion." intros
265
- # @param None (operates on .bmad/_cfg files)
266
- # @returns 0 on success, 1 on error
267
- # @exitcode 0 on success, 1 if files missing or sync fails
268
- # @sideeffects Updates agent-voice-map.csv, creates .backup on first run, writes .bmad-csv-sync-timestamp
269
- # @edgecases Only updates EXACT match of generic intro, preserves all custom intros, idempotent
270
- # @calledby get_agent_intro (lazy trigger on manifest change)
271
- # @calls grep, cut, sed, stat, date
272
- # @version 2.17.4 - Safe CSV sync utility that preserves user customizations
273
- sync_intros_from_manifest() {
274
- # Locate the CSV and manifest files
275
- local bmad_voice_map=""
276
- local manifest_file=""
277
-
278
- if [[ -f ".bmad/_cfg/agent-voice-map.csv" ]]; then
279
- bmad_voice_map=".bmad/_cfg/agent-voice-map.csv"
280
- elif [[ -f "bmad/_cfg/agent-voice-map.csv" ]]; then
281
- bmad_voice_map="bmad/_cfg/agent-voice-map.csv"
282
- fi
283
-
284
- if [[ -f "_bmad/_config/agent-manifest.csv" ]]; then
285
- manifest_file="_bmad/_config/agent-manifest.csv"
286
- elif [[ -f "_bmad/_config/agent-manifest.csv" ]]; then
287
- manifest_file="_bmad/_config/agent-manifest.csv"
288
- fi
289
-
290
- # Both files must exist for sync to work
291
- if [[ -z "$bmad_voice_map" ]] || [[ -z "$manifest_file" ]]; then
292
- return 1
293
- fi
294
-
295
- # Check if sync is needed based on manifest timestamp
296
- local timestamp_file="${bmad_voice_map%/*}/.bmad-csv-sync-timestamp"
297
- local manifest_mtime=$(stat -c '%Y' "$manifest_file" 2>/dev/null || stat -f '%m' "$manifest_file" 2>/dev/null)
298
-
299
- if [[ -f "$timestamp_file" ]]; then
300
- local last_sync=$(cat "$timestamp_file" 2>/dev/null || echo "0")
301
- if [[ "$manifest_mtime" -le "$last_sync" ]]; then
302
- # Manifest hasn't changed since last sync
303
- return 0
304
- fi
305
- fi
306
-
307
- # Create backup on first sync
308
- if [[ ! -f "${bmad_voice_map}.backup" ]]; then
309
- cp "$bmad_voice_map" "${bmad_voice_map}.backup"
310
- fi
311
-
312
- # Build a temp file with synced intros
313
- local temp_file="${bmad_voice_map}.tmp"
314
- local generic_intro="Hello! Ready to help with the discussion."
315
-
316
- # Read header
317
- head -n 1 "$bmad_voice_map" > "$temp_file"
318
-
319
- # Process each agent entry
320
- tail -n +2 "$bmad_voice_map" | while IFS=, read -r agent voice intro; do
321
- # Remove quotes from intro
322
- intro=$(echo "$intro" | sed 's/^"//;s/"$//')
323
-
324
- # Only update if intro is the exact generic placeholder
325
- if [[ "$intro" == "$generic_intro" ]]; then
326
- # Look up displayName and title from manifest using awk for proper CSV parsing
327
- # CSV format: name,displayName,title,icon,role,...
328
- local manifest_data=$(grep "^\"*${agent}\"*," "$manifest_file" | awk -F'","' '{
329
- gsub(/^"/, "", $2);
330
- gsub(/"$/, "", $3);
331
- print $2 "|" $3
332
- }')
333
-
334
- local display_name=$(echo "$manifest_data" | cut -d'|' -f1)
335
- local title=$(echo "$manifest_data" | cut -d'|' -f2)
336
-
337
- if [[ -n "$display_name" ]] && [[ -n "$title" ]]; then
338
- # Generate intro like PR 987: "Hi! I'm [Name], your [Title]."
339
- intro="Hi! I'm ${display_name}, your ${title}."
340
- elif [[ -n "$display_name" ]]; then
341
- # Fallback if title missing
342
- intro="${display_name} here"
343
- fi
344
- fi
345
-
346
- # Write the line (preserving custom intros, updating generic ones)
347
- echo "${agent},${voice},\"${intro}\""
348
- done >> "$temp_file"
349
-
350
- # Replace original with synced version
351
- mv "$temp_file" "$bmad_voice_map"
352
-
353
- # Update timestamp
354
- echo "$manifest_mtime" > "$timestamp_file"
355
-
356
- return 0
357
- }
358
-
359
- # @function get_agent_intro
360
- # @intent Retrieve intro text for BMAD agent (spoken before their message)
361
- # @why Helps users identify which agent is speaking in party mode
362
- # @param $1 {string} agent_id - BMAD agent identifier
363
- # @returns Echoes intro text to stdout, empty string if not configured
364
- # @exitcode Always 0
365
- # @sideeffects Triggers CSV sync on first call or manifest change
366
- # @edgecases Returns empty string if plugin file missing, parses column 3 of CSV or markdown table
367
- # @calledby bmad-speak.sh for agent identification in party mode
368
- # @calls sync_intros_from_manifest, grep, awk, sed, cut
369
- # @version 2.2.1 - Added lazy CSV sync trigger
370
- get_agent_intro() {
371
- local agent_id="$1"
372
-
373
- # Check for BMAD v6 CSV file first (preferred, loose coupling)
374
- # If this exists, use it directly without requiring plugin enable flag
375
- # Support both .bmad (standard) and bmad (alternative) paths
376
- local bmad_voice_map=""
377
- if [[ -f ".bmad/_cfg/agent-voice-map.csv" ]]; then
378
- bmad_voice_map=".bmad/_cfg/agent-voice-map.csv"
379
- elif [[ -f "bmad/_cfg/agent-voice-map.csv" ]]; then
380
- bmad_voice_map="bmad/_cfg/agent-voice-map.csv"
381
- fi
382
-
383
- # Lazy trigger: sync intros from manifest if needed
384
- if [[ -n "$bmad_voice_map" ]]; then
385
- sync_intros_from_manifest
386
- fi
387
-
388
- if [[ -n "$bmad_voice_map" ]]; then
389
- # Read from BMAD's standard _cfg directory
390
- # CSV format: agent,voice,intro
391
- # Use awk to properly handle quoted CSV fields (intro may contain commas)
392
- local intro=$(grep "^$agent_id," "$bmad_voice_map" | awk -F',' '{
393
- # Extract field 3 onwards (intro may span multiple comma-separated parts)
394
- intro = $3;
395
- for (i = 4; i <= NF; i++) {
396
- intro = intro "," $i;
397
- }
398
- gsub(/^"/, "", intro);
399
- gsub(/"$/, "", intro);
400
- print intro;
401
- }')
402
-
403
- # If intro is empty or generic, fall back to agent display name from manifest
404
- if [[ -z "$intro" ]] || [[ "$intro" == "Hello! Ready to help with the discussion." ]]; then
405
- # Try to get display name from agent-manifest.csv
406
- local manifest_file=""
407
- if [[ -f "_bmad/_config/agent-manifest.csv" ]]; then
408
- manifest_file="_bmad/_config/agent-manifest.csv"
409
- elif [[ -f "_bmad/_config/agent-manifest.csv" ]]; then
410
- manifest_file="_bmad/_config/agent-manifest.csv"
411
- fi
412
-
413
- if [[ -n "$manifest_file" ]]; then
414
- # Extract displayName (column 2) where name (column 1) matches agent_id
415
- # CSV format: name,displayName,title,icon,role,...
416
- local display_name=$(grep "^\"*${agent_id}\"*," "$manifest_file" | cut -d',' -f2 | sed 's/^"//;s/"$//')
417
- if [[ -n "$display_name" ]]; then
418
- intro="$display_name here"
419
- fi
420
- fi
421
- fi
422
-
423
- # If we got an intro, return it
424
- if [[ -n "$intro" ]]; then
425
- echo "$intro"
426
- return
427
- fi
428
- # Otherwise fall through to hardcoded defaults below
429
- fi
430
-
431
- # Hardcoded default intro mappings (final fallback)
432
- # These match the BMAD-METHOD agent display names for consistency
433
- case "$agent_id" in
434
- bmad-master)
435
- echo "BMad Master here"
436
- return
437
- ;;
438
- analyst)
439
- echo "Mary here"
440
- return
441
- ;;
442
- architect)
443
- echo "Winston here"
444
- return
445
- ;;
446
- dev)
447
- echo "Amelia here"
448
- return
449
- ;;
450
- pm)
451
- echo "John here"
452
- return
453
- ;;
454
- quick-flow-solo-dev)
455
- echo "Barry here"
456
- return
457
- ;;
458
- sm)
459
- echo "Bob here"
460
- return
461
- ;;
462
- tea)
463
- echo "Murat here"
464
- return
465
- ;;
466
- tech-writer)
467
- echo "Paige here"
468
- return
469
- ;;
470
- ux-designer)
471
- echo "Sally here"
472
- return
473
- ;;
474
- frame-expert)
475
- echo "Frame Expert here"
476
- return
477
- ;;
478
- esac
479
-
480
- # Fallback to legacy markdown config file
481
- if [[ ! -f "$VOICE_CONFIG_FILE" ]]; then
482
- echo ""
483
- return
484
- fi
485
-
486
- # AWK column 4 = Intro text
487
- local intro=$(grep "^| $agent_id " "$VOICE_CONFIG_FILE" | \
488
- awk -F'|' '{print $4}' | \
489
- sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
490
-
491
- echo "$intro"
492
- }
493
-
494
- # @function get_agent_personality
495
- # @intent Retrieve TTS personality assigned to specific BMAD agent
496
- # @why Agents may have distinct speaking styles (friendly, professional, energetic, etc.)
497
- # @param $1 {string} agent_id - BMAD agent identifier
498
- # @returns Echoes personality name to stdout, empty string if not found
499
- # @exitcode Always 0
500
- # @sideeffects None
501
- # @edgecases Returns empty string if plugin file missing, parses column 6 of markdown table
502
- # @calledby bmad-tts-injector.sh for personality-aware voice synthesis
503
- # @calls grep, awk, sed
504
- # @version 2.0.0 - Updated to column 6 (was 5) due to new provider-aware format
505
- get_agent_personality() {
506
- local agent_id="$1"
507
-
508
- if [[ ! -f "$VOICE_CONFIG_FILE" ]]; then
509
- echo ""
510
- return
511
- fi
512
-
513
- # AWK column 7 = Personality
514
- local personality=$(grep "^| $agent_id " "$VOICE_CONFIG_FILE" | \
515
- awk -F'|' '{print $7}' | \
516
- sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
517
-
518
- echo "$personality"
519
- }
520
-
521
- # @function is_plugin_enabled
522
- # @intent Check if BMAD voice plugin is currently enabled
523
- # @why Allow conditional logic based on plugin state
524
- # @param None
525
- # @returns Echoes "true" or "false" to stdout
526
- # @exitcode Always 0
527
- # @sideeffects None
528
- # @edgecases None
529
- # @calledby show_status, enable_plugin, disable_plugin
530
- # @calls None (file existence check)
531
- is_plugin_enabled() {
532
- [[ -f "$ENABLED_FLAG" ]] && echo "true" || echo "false"
533
- }
534
-
535
- # @function enable_plugin
536
- # @intent Enable BMAD voice plugin and backup current voice settings
537
- # @why Allow users to switch to per-agent voices while preserving original configuration
538
- # @param None
539
- # @returns None
540
- # @exitcode Always 0
541
- # @sideeffects Creates flag file, backs up current voice/personality/sentiment to .bmad-previous-settings
542
- # @sideeffects Creates activation-instructions file for BMAD agents, calls bmad-tts-injector.sh
543
- # @edgecases Handles missing settings files gracefully with defaults
544
- # @calledby Main command dispatcher with "enable" argument
545
- # @calls mkdir, cat, source, list_mappings, bmad-tts-injector.sh
546
- enable_plugin() {
547
- mkdir -p "$CONFIG_DIR"
548
-
549
- # Save current settings before enabling
550
- BACKUP_FILE="$CONFIG_DIR/.bmad-previous-settings"
551
-
552
- # Save current voice
553
- if [[ -f ".claude/tts-voice.txt" ]]; then
554
- CURRENT_VOICE=$(cat .claude/tts-voice.txt 2>/dev/null)
555
- elif [[ -f "$HOME/.claude/tts-voice.txt" ]]; then
556
- CURRENT_VOICE=$(cat "$HOME/.claude/tts-voice.txt" 2>/dev/null)
557
- else
558
- CURRENT_VOICE="Aria"
559
- fi
560
-
561
- # Save current personality
562
- if [[ -f ".claude/tts-personality.txt" ]]; then
563
- CURRENT_PERSONALITY=$(cat .claude/tts-personality.txt 2>/dev/null)
564
- elif [[ -f "$HOME/.claude/tts-personality.txt" ]]; then
565
- CURRENT_PERSONALITY=$(cat "$HOME/.claude/tts-personality.txt" 2>/dev/null)
566
- else
567
- CURRENT_PERSONALITY="normal"
568
- fi
569
-
570
- # Save current sentiment
571
- if [[ -f ".claude/tts-sentiment.txt" ]]; then
572
- CURRENT_SENTIMENT=$(cat .claude/tts-sentiment.txt 2>/dev/null)
573
- elif [[ -f "$HOME/.claude/tts-sentiment.txt" ]]; then
574
- CURRENT_SENTIMENT=$(cat "$HOME/.claude/tts-sentiment.txt" 2>/dev/null)
575
- else
576
- CURRENT_SENTIMENT=""
577
- fi
578
-
579
- # Write backup
580
- cat > "$BACKUP_FILE" <<EOF
581
- VOICE=$CURRENT_VOICE
582
- PERSONALITY=$CURRENT_PERSONALITY
583
- SENTIMENT=$CURRENT_SENTIMENT
584
- EOF
585
-
586
- touch "$ENABLED_FLAG"
587
-
588
- # Create activation-instructions file for BMAD agents
589
- ACTIVATION_FILE=".claude/activation-instructions"
590
- if [[ ! -f "$ACTIVATION_FILE" ]]; then
591
- cat > "$ACTIVATION_FILE" <<'ACTIVATION_EOF'
592
- # BMAD Agent Activation Instructions
593
-
594
- When a BMAD agent activates, follow these steps EXACTLY:
595
-
596
- ## STEP 3.5a: Create BMAD Context File (CRITICAL)
597
-
598
- **IMMEDIATELY after agent identification, create the context file:**
599
-
600
- ```bash
601
- echo "$AGENT_ID" > .bmad-agent-context
602
- ```
603
-
604
- Where `$AGENT_ID` is one of:
605
- - `pm` (Product Manager)
606
- - `dev` (Developer)
607
- - `qa` (QA)
608
- - `architect` (Architect)
609
- - `po` (Product Owner)
610
- - `analyst` (Analyst)
611
- - `sm` (Scrum Master)
612
- - `ux-expert` (UX Expert)
613
- - `bmad-master` (BMAD Master)
614
- - `bmad-orchestrator` (Orchestrator)
615
-
616
- **Example:**
617
- ```bash
618
- # For Product Manager agent
619
- echo "pm" > .bmad-agent-context
620
- ```
621
-
622
- This file allows AgentVibes to:
623
- 1. Detect which BMAD agent is active
624
- 2. Look up the correct voice mapping
625
- 3. Automatically speak questions using the agent's assigned voice
626
-
627
- ## STEP 10: Clean Up on Exit
628
-
629
- **Before exiting the agent, remove the context file:**
630
-
631
- ```bash
632
- rm -f .bmad-agent-context
633
- ```
634
-
635
- This ensures voice switching only happens when an agent is active.
636
-
637
- ## Why This Matters
638
-
639
- Without the `.bmad-agent-context` file:
640
- - AgentVibes cannot detect which agent is active
641
- - Questions won't be spoken automatically
642
- - Voice switching won't work
643
- - The BMAD voice plugin becomes non-functional
644
-
645
- **This is MANDATORY for BMAD voice integration to work!**
646
- ACTIVATION_EOF
647
- echo "📝 Created activation instructions: $ACTIVATION_FILE"
648
- fi
649
-
650
- echo "✅ BMAD voice plugin enabled"
651
- echo "💾 Previous settings backed up:"
652
- echo " Voice: $CURRENT_VOICE"
653
- echo " Personality: $CURRENT_PERSONALITY"
654
- [[ -n "$CURRENT_SENTIMENT" ]] && echo " Sentiment: $CURRENT_SENTIMENT"
655
- echo ""
656
- list_mappings
657
-
658
- # Automatically inject TTS into BMAD agents
659
- echo ""
660
- echo "🎤 Automatically enabling TTS for BMAD agents..."
661
- echo ""
662
-
663
- # Get the directory where this script is located
664
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
665
-
666
- # Check if bmad-tts-injector.sh exists
667
- if [[ -f "$SCRIPT_DIR/bmad-tts-injector.sh" ]]; then
668
- # Run the TTS injector
669
- "$SCRIPT_DIR/bmad-tts-injector.sh" enable
670
- else
671
- echo "⚠️ TTS injector not found at: $SCRIPT_DIR/bmad-tts-injector.sh"
672
- echo " You can manually enable TTS with: /agent-vibes:bmad-tts enable"
673
- fi
674
- }
675
-
676
- # @function disable_plugin
677
- # @intent Disable BMAD voice plugin and restore previous voice settings
678
- # @why Allow users to return to single-voice mode with their original configuration
679
- # @param None
680
- # @returns None
681
- # @exitcode Always 0
682
- # @sideeffects Removes flag file, restores settings from backup, calls bmad-tts-injector.sh disable
683
- # @edgecases Handles missing backup file gracefully, warns user if no backup exists
684
- # @calledby Main command dispatcher with "disable" argument
685
- # @calls source, rm, echo, bmad-tts-injector.sh
686
- disable_plugin() {
687
- BACKUP_FILE="$CONFIG_DIR/.bmad-previous-settings"
688
-
689
- # Check if we have a backup to restore
690
- if [[ -f "$BACKUP_FILE" ]]; then
691
- source "$BACKUP_FILE"
692
-
693
- echo "❌ BMAD voice plugin disabled"
694
- echo "🔄 Restoring previous settings:"
695
- echo " Voice: $VOICE"
696
- echo " Personality: $PERSONALITY"
697
- [[ -n "$SENTIMENT" ]] && echo " Sentiment: $SENTIMENT"
698
-
699
- # Restore voice
700
- if [[ -n "$VOICE" ]]; then
701
- echo "$VOICE" > .claude/tts-voice.txt 2>/dev/null || echo "$VOICE" > "$HOME/.claude/tts-voice.txt"
702
- fi
703
-
704
- # Restore personality
705
- if [[ -n "$PERSONALITY" ]] && [[ "$PERSONALITY" != "normal" ]]; then
706
- echo "$PERSONALITY" > .claude/tts-personality.txt 2>/dev/null || echo "$PERSONALITY" > "$HOME/.claude/tts-personality.txt"
707
- fi
708
-
709
- # Restore sentiment
710
- if [[ -n "$SENTIMENT" ]]; then
711
- echo "$SENTIMENT" > .claude/tts-sentiment.txt 2>/dev/null || echo "$SENTIMENT" > "$HOME/.claude/tts-sentiment.txt"
712
- fi
713
-
714
- # Clean up backup
715
- rm -f "$BACKUP_FILE"
716
- else
717
- echo "❌ BMAD voice plugin disabled"
718
- echo "⚠️ No previous settings found to restore"
719
- echo "AgentVibes will use current voice/personality settings"
720
- fi
721
-
722
- rm -f "$ENABLED_FLAG"
723
-
724
- # Automatically remove TTS from BMAD agents
725
- echo ""
726
- echo "🔇 Automatically disabling TTS for BMAD agents..."
727
- echo ""
728
-
729
- # Get the directory where this script is located
730
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
731
-
732
- # Check if bmad-tts-injector.sh exists
733
- if [[ -f "$SCRIPT_DIR/bmad-tts-injector.sh" ]]; then
734
- # Run the TTS injector disable
735
- "$SCRIPT_DIR/bmad-tts-injector.sh" disable
736
- else
737
- echo "⚠️ TTS injector not found"
738
- echo " You can manually disable TTS with: /agent-vibes:bmad-tts disable"
739
- fi
740
- }
741
-
742
- # @function list_mappings
743
- # @intent Display all BMAD agent-to-voice mappings in readable format (provider-aware)
744
- # @why Help users see which voice is assigned to each agent based on active TTS provider
745
- # @param None
746
- # @returns None
747
- # @exitcode 0=success, 1=plugin file not found
748
- # @sideeffects Writes formatted output to stdout
749
- # @edgecases Parses markdown table format, skips header and separator rows
750
- # @calledby enable_plugin, show_status, main command dispatcher with "list"
751
- # @calls grep, sed, echo
752
- # @version 2.1.0 - Now provider-aware: shows Piper or macOS voices based on active provider
753
- list_mappings() {
754
- if [[ ! -f "$VOICE_CONFIG_FILE" ]]; then
755
- echo "❌ Plugin file not found: $VOICE_CONFIG_FILE"
756
- return 1
757
- fi
758
-
759
- # Detect active TTS provider
760
- local provider_file=""
761
- if [[ -f ".claude/tts-provider.txt" ]]; then
762
- provider_file=".claude/tts-provider.txt"
763
- elif [[ -f "$HOME/.claude/tts-provider.txt" ]]; then
764
- provider_file="$HOME/.claude/tts-provider.txt"
765
- fi
766
-
767
- local active_provider="piper" # default
768
- if [[ -n "$provider_file" ]] && [[ -f "$provider_file" ]]; then
769
- active_provider=$(cat "$provider_file")
770
- fi
771
-
772
- # Display provider info
773
- echo "📊 BMAD Agent Voice Mappings (Provider: $active_provider):"
774
- echo ""
775
-
776
- # Table: Agent ID | Agent Name | Intro | Piper Voice | macOS Voice | Personality
777
- # AWK columns: $1=empty | $2=ID | $3=Name | $4=Intro | $5=Piper | $6=macOS | $7=Personality
778
- local voice_column=5 # Default to Piper (AWK column 5)
779
- if [[ "$active_provider" == "piper" ]]; then
780
- voice_column=6 # Use Piper (AWK column 6)
781
- fi
782
-
783
- grep "^| " "$VOICE_CONFIG_FILE" | grep -v "Agent ID" | grep -v "^|---" | \
784
- while IFS='|' read -r line; do
785
- agent_id=$(echo "$line" | awk -F'|' '{print $2}' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
786
- name=$(echo "$line" | awk -F'|' '{print $3}' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
787
- voice=$(echo "$line" | awk -F'|' "{print \$$voice_column}" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
788
- personality=$(echo "$line" | awk -F'|' '{print $7}' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
789
-
790
- [[ -n "$agent_id" ]] && echo " $agent_id → $voice [$personality]"
791
- done
792
- }
793
-
794
- # @function set_agent_voice
795
- # @intent Update voice and personality mapping for specific BMAD agent
796
- # @why Allow customization of agent voices to user preferences
797
- # @param $1 {string} agent_id - BMAD agent identifier
798
- # @param $2 {string} voice - New voice name
799
- # @param $3 {string} personality - New personality (optional, defaults to "normal")
800
- # @returns None
801
- # @exitcode 0=success, 1=plugin file not found or agent not found
802
- # @sideeffects Modifies plugin file, creates .bak backup
803
- # @edgecases Validates agent exists before updating
804
- # @calledby Main command dispatcher with "set" argument
805
- # @calls grep, sed
806
- set_agent_voice() {
807
- local agent_id="$1"
808
- local voice="$2"
809
- local personality="${3:-normal}"
810
-
811
- if [[ ! -f "$VOICE_CONFIG_FILE" ]]; then
812
- echo "❌ Plugin file not found: $VOICE_CONFIG_FILE"
813
- return 1
814
- fi
815
-
816
- # Check if agent exists
817
- if ! grep -q "^| $agent_id " "$VOICE_CONFIG_FILE"; then
818
- echo "❌ Agent '$agent_id' not found in plugin"
819
- return 1
820
- fi
821
-
822
- # Update the voice and personality in the table
823
- sed -i.bak "s/^| $agent_id |.*| .* | .* |$/| $agent_id | $(grep "^| $agent_id " "$VOICE_CONFIG_FILE" | awk -F'|' '{print $3}') | $voice | $personality |/" "$VOICE_CONFIG_FILE"
824
-
825
- echo "✅ Updated $agent_id → $voice [$personality]"
826
- }
827
-
828
- # @function show_status
829
- # @intent Display plugin status, BMAD detection, and current voice mappings
830
- # @why Provide comprehensive overview of plugin state for troubleshooting
831
- # @param None
832
- # @returns None
833
- # @exitcode Always 0
834
- # @sideeffects Writes status information to stdout
835
- # @edgecases Checks for BMAD installation via manifest file
836
- # @calledby Main command dispatcher with "status" argument
837
- # @calls is_plugin_enabled, list_mappings
838
- show_status() {
839
- # Check for BMAD installation
840
- local bmad_installed="false"
841
- if [[ -f ".bmad-core/install-manifest.yaml" ]]; then
842
- bmad_installed="true"
843
- fi
844
-
845
- if [[ $(is_plugin_enabled) == "true" ]]; then
846
- echo "✅ BMAD voice plugin: ENABLED"
847
- if [[ "$bmad_installed" == "true" ]]; then
848
- echo "🔍 BMAD detected: Auto-enabled"
849
- fi
850
- else
851
- echo "❌ BMAD voice plugin: DISABLED"
852
- if [[ "$bmad_installed" == "true" ]]; then
853
- echo "⚠️ BMAD detected but plugin disabled (enable with: /agent-vibes-bmad enable)"
854
- fi
855
- fi
856
- echo ""
857
- list_mappings
858
- }
859
-
860
- # @function edit_plugin
861
- # @intent Open plugin configuration file for manual editing
862
- # @why Allow advanced users to modify voice mappings directly
863
- # @param None
864
- # @returns None
865
- # @exitcode 0=success, 1=plugin file not found
866
- # @sideeffects Displays file path and instructions
867
- # @edgecases Does not actually open editor, just provides guidance
868
- # @calledby Main command dispatcher with "edit" argument
869
- # @calls echo
870
- edit_plugin() {
871
- if [[ ! -f "$VOICE_CONFIG_FILE" ]]; then
872
- echo "❌ Plugin file not found: $VOICE_CONFIG_FILE"
873
- return 1
874
- fi
875
-
876
- echo "Opening $VOICE_CONFIG_FILE for editing..."
877
- echo "Edit the markdown table to change voice mappings"
878
- }
879
-
880
- # Main command dispatcher
881
- case "${1:-help}" in
882
- enable)
883
- enable_plugin
884
- ;;
885
- disable)
886
- disable_plugin
887
- ;;
888
- status)
889
- show_status
890
- ;;
891
- list)
892
- list_mappings
893
- ;;
894
- set)
895
- if [[ -z "$2" ]] || [[ -z "$3" ]]; then
896
- echo "Usage: bmad-voice-manager.sh set <agent-id> <voice> [personality]"
897
- exit 1
898
- fi
899
- set_agent_voice "$2" "$3" "$4"
900
- ;;
901
- get-voice)
902
- get_agent_voice "$2"
903
- ;;
904
- get-intro)
905
- get_agent_intro "$2"
906
- ;;
907
- get-personality)
908
- get_agent_personality "$2"
909
- ;;
910
- edit)
911
- edit_plugin
912
- ;;
913
- *)
914
- echo "Usage: bmad-voice-manager.sh {enable|disable|status|list|set|get-voice|get-intro|get-personality|edit}"
915
- echo ""
916
- echo "Commands:"
917
- echo " enable Enable BMAD voice plugin"
918
- echo " disable Disable BMAD voice plugin"
919
- echo " status Show plugin status and mappings"
920
- echo " list List all agent voice mappings"
921
- echo " set <id> <voice> Set voice for agent"
922
- echo " get-voice <id> Get voice for agent"
923
- echo " get-intro <id> Get intro text for agent"
924
- echo " get-personality <id> Get personality for agent"
925
- echo " edit Edit plugin configuration"
926
- exit 1
927
- ;;
928
- esac
1
+ #!/usr/bin/env bash
2
+ #
3
+ # File: .claude/hooks/bmad-voice-manager.sh
4
+ #
5
+ # AgentVibes - Finally, your AI Agents can Talk Back! Text-to-Speech WITH personality for AI Assistants!
6
+ # Website: https://agentvibes.org
7
+ # Repository: https://github.com/paulpreibisch/AgentVibes
8
+ #
9
+ # Co-created by Paul Preibisch with Claude AI
10
+ # Copyright (c) 2025 Paul Preibisch
11
+ #
12
+ # Licensed under the Apache License, Version 2.0 (the "License");
13
+ # you may not use this file except in compliance with the License.
14
+ # You may obtain a copy of the License at
15
+ #
16
+ # http://www.apache.org/licenses/LICENSE-2.0
17
+ #
18
+ # Unless required by applicable law or agreed to in writing, software
19
+ # distributed under the License is distributed on an "AS IS" BASIS,
20
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21
+ # See the License for the specific language governing permissions and
22
+ # limitations under the License.
23
+ #
24
+ # DISCLAIMER: This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
25
+ # express or implied, including but not limited to the warranties of
26
+ # merchantability, fitness for a particular purpose and noninfringement.
27
+ # In no event shall the authors or copyright holders be liable for any claim,
28
+ # damages or other liability, whether in an action of contract, tort or
29
+ # otherwise, arising from, out of or in connection with the software or the
30
+ # use or other dealings in the software.
31
+ #
32
+ # ---
33
+ #
34
+ # @fileoverview BMAD Voice Plugin Manager - Maps BMAD agents to unique TTS voices
35
+ # @context Enables each BMAD agent to have its own distinct voice for multi-agent sessions
36
+ # @architecture Markdown table-based voice mapping with enable/disable flag, auto-detection of BMAD
37
+ # @dependencies .claude/config/bmad-voices.md (voice mappings), bmad-tts-injector.sh, .bmad-core/ (BMAD installation)
38
+ # @entrypoints Called by /agent-vibes:bmad commands, auto-enabled on BMAD detection
39
+ # @patterns Plugin architecture, auto-enable on dependency detection, state backup/restore on toggle
40
+ # @related bmad-tts-injector.sh, .claude/config/bmad-voices.md, .bmad-agent-context file
41
+
42
+ CONFIG_DIR=".agentvibes/bmad"
43
+ VOICE_CONFIG_FILE="$CONFIG_DIR/bmad-voices.md"
44
+ ENABLED_FLAG="$CONFIG_DIR/bmad-voices-enabled.flag"
45
+
46
+ # AI NOTE: Auto-enable pattern - When BMAD is detected via install-manifest.yaml,
47
+ # automatically enable the voice plugin to provide seamless multi-agent voice support.
48
+ # This avoids requiring manual plugin activation after BMAD installation.
49
+ # Supports both BMAD v4 (.bmad-core/) and v6-alpha (bmad/) directory structures.
50
+
51
+ # @function detect_bmad_version
52
+ # @intent Detect BMAD installation and return version number
53
+ # @why Support both v4 and v6-alpha installations with different directory structures
54
+ # @param None
55
+ # @returns Echoes version number (4, 6, or 0 for not installed) to stdout
56
+ # @exitcode 0=detected, 1=not installed
57
+ # @sideeffects None
58
+ # @edgecases Checks v6 first (newer version), falls back to v4
59
+ # @calledby auto_enable_if_bmad_detected, get_bmad_config_path
60
+ # @calls None
61
+ detect_bmad_version() {
62
+ if [[ -f ".bmad/_cfg/manifest.yaml" ]]; then
63
+ # v6 detected (standard path with dot prefix)
64
+ echo "6"
65
+ return 0
66
+ elif [[ -f "bmad/_cfg/manifest.yaml" ]]; then
67
+ # v6 detected (alternative path without dot prefix)
68
+ echo "6"
69
+ return 0
70
+ elif [[ -f ".bmad-core/install-manifest.yaml" ]]; then
71
+ # v4 detected
72
+ echo "4"
73
+ return 0
74
+ else
75
+ # Not installed
76
+ echo "0"
77
+ return 1
78
+ fi
79
+ }
80
+
81
+ # @function get_bmad_config_path
82
+ # @intent Get BMAD configuration file path based on detected version
83
+ # @why v4 and v6 use different directory structures for config files
84
+ # @param None
85
+ # @returns Echoes config path to stdout, empty string if not installed
86
+ # @exitcode 0=path returned, 1=not installed
87
+ # @sideeffects None
88
+ # @edgecases Returns empty string if BMAD not detected
89
+ # @calledby Commands that need to read BMAD config (future use)
90
+ # @calls detect_bmad_version
91
+ get_bmad_config_path() {
92
+ local version=$(detect_bmad_version)
93
+
94
+ if [[ "$version" == "6" ]]; then
95
+ # Check both possible v6 paths
96
+ if [[ -f ".bmad/core/config.yaml" ]]; then
97
+ echo ".bmad/core/config.yaml"
98
+ else
99
+ echo "bmad/core/config.yaml"
100
+ fi
101
+ return 0
102
+ elif [[ "$version" == "4" ]]; then
103
+ echo ".bmad-core/config.yaml"
104
+ return 0
105
+ else
106
+ echo ""
107
+ return 1
108
+ fi
109
+ }
110
+
111
+ # @function auto_enable_if_bmad_detected
112
+ # @intent Automatically enable BMAD voice plugin when BMAD framework is detected
113
+ # @why Provide seamless integration - users shouldn't need to manually enable voice mapping
114
+ # @param None
115
+ # @returns None
116
+ # @exitcode 0=auto-enabled, 1=not enabled (already enabled or BMAD not detected)
117
+ # @sideeffects Creates enabled flag file, creates plugin directory
118
+ # @edgecases Only auto-enables if plugin not already enabled, silent operation
119
+ # @calledby get_agent_voice
120
+ # @calls mkdir, touch, detect_bmad_version
121
+ auto_enable_if_bmad_detected() {
122
+ local version=$(detect_bmad_version)
123
+
124
+ # Check if BMAD is installed (any version) and plugin not already enabled
125
+ if [[ "$version" != "0" ]] && [[ ! -f "$ENABLED_FLAG" ]]; then
126
+ # BMAD detected but plugin not enabled - enable it silently
127
+ mkdir -p "$CONFIG_DIR"
128
+ touch "$ENABLED_FLAG"
129
+ return 0
130
+ fi
131
+ return 1
132
+ }
133
+
134
+ # @function get_agent_voice
135
+ # @intent Retrieve TTS voice assigned to specific BMAD agent (provider-aware)
136
+ # @why Each BMAD agent needs unique voice for multi-agent conversation differentiation
137
+ # @param $1 {string} agent_id - BMAD agent identifier (pm, dev, qa, architect, etc.)
138
+ # @returns Echoes voice name to stdout, empty string if plugin disabled or agent not found
139
+ # @exitcode Always 0
140
+ # @sideeffects May auto-enable plugin if BMAD detected
141
+ # @edgecases Returns empty string if plugin disabled/missing, parses markdown table syntax
142
+ # @calledby bmad-tts-injector.sh, play-tts.sh when BMAD agent is active
143
+ # @calls auto_enable_if_bmad_detected, grep, awk, sed
144
+ # @version 2.0.0 - Now provider-aware: returns Piper or macOS voice based on active provider
145
+ get_agent_voice() {
146
+ local agent_id="$1"
147
+
148
+ # Check for BMAD v6 CSV file first (preferred, loose coupling)
149
+ # If this exists, use it directly without requiring plugin enable flag
150
+ # Support both .bmad (standard) and bmad (alternative) paths
151
+ local bmad_voice_map=""
152
+ if [[ -f ".bmad/_cfg/agent-voice-map.csv" ]]; then
153
+ bmad_voice_map=".bmad/_cfg/agent-voice-map.csv"
154
+ elif [[ -f "bmad/_cfg/agent-voice-map.csv" ]]; then
155
+ bmad_voice_map="bmad/_cfg/agent-voice-map.csv"
156
+ fi
157
+
158
+ if [[ -n "$bmad_voice_map" ]]; then
159
+ # Read from BMAD's standard _cfg directory
160
+ # CSV format: agent_id,voice_name
161
+ local voice=$(grep "^$agent_id," "$bmad_voice_map" | cut -d',' -f2)
162
+
163
+ # If voice is empty or generic (same for all), use defaults
164
+ if [[ -n "$voice" ]] && [[ "$voice" != "en_US-lessac-medium" ]]; then
165
+ echo "$voice"
166
+ return
167
+ fi
168
+ # If empty or generic, fall through to defaults below
169
+ fi
170
+
171
+ # Default voice mappings (hardcoded fallback when CSV is missing or has generic values)
172
+ # These match the BMAD-METHOD defaults for consistency
173
+ case "$agent_id" in
174
+ bmad-master)
175
+ echo "en_US-lessac-medium"
176
+ return
177
+ ;;
178
+ analyst)
179
+ echo "en_US-kristin-medium"
180
+ return
181
+ ;;
182
+ architect)
183
+ echo "en_GB-alan-medium"
184
+ return
185
+ ;;
186
+ dev)
187
+ echo "en_US-joe-medium"
188
+ return
189
+ ;;
190
+ pm)
191
+ echo "en_US-ryan-high"
192
+ return
193
+ ;;
194
+ quick-flow-solo-dev)
195
+ echo "en_US-joe-medium"
196
+ return
197
+ ;;
198
+ sm)
199
+ echo "en_US-amy-medium"
200
+ return
201
+ ;;
202
+ tea)
203
+ echo "en_US-kusal-medium"
204
+ return
205
+ ;;
206
+ tech-writer)
207
+ echo "en_US-kristin-medium"
208
+ return
209
+ ;;
210
+ ux-designer)
211
+ echo "en_US-kristin-medium"
212
+ return
213
+ ;;
214
+ frame-expert)
215
+ echo "en_GB-alan-medium"
216
+ return
217
+ ;;
218
+ esac
219
+
220
+ # Auto-enable if BMAD is detected (for legacy markdown config)
221
+ auto_enable_if_bmad_detected
222
+
223
+ if [[ ! -f "$ENABLED_FLAG" ]]; then
224
+ echo "" # Plugin disabled
225
+ return
226
+ fi
227
+
228
+ # Fallback to legacy markdown config file
229
+ if [[ ! -f "$VOICE_CONFIG_FILE" ]]; then
230
+ echo "" # Plugin file missing
231
+ return
232
+ fi
233
+
234
+ # Detect active TTS provider
235
+ local provider_file=""
236
+ if [[ -f ".claude/tts-provider.txt" ]]; then
237
+ provider_file=".claude/tts-provider.txt"
238
+ elif [[ -f "$HOME/.claude/tts-provider.txt" ]]; then
239
+ provider_file="$HOME/.claude/tts-provider.txt"
240
+ fi
241
+
242
+ local active_provider="piper" # default
243
+ if [[ -n "$provider_file" ]] && [[ -f "$provider_file" ]]; then
244
+ active_provider=$(cat "$provider_file")
245
+ fi
246
+
247
+ # Extract voice from markdown table based on provider
248
+ # Table: Agent ID | Agent Name | Intro | Piper Voice | macOS Voice | Personality
249
+ # AWK columns: $1=empty | $2=ID | $3=Name | $4=Intro | $5=Piper | $6=macOS | $7=Personality
250
+ local column=5 # Default to Piper (AWK column 5)
251
+ if [[ "$active_provider" == "piper" ]]; then
252
+ column=6 # Use Piper (AWK column 6)
253
+ fi
254
+
255
+ local voice=$(grep "^| $agent_id " "$VOICE_CONFIG_FILE" | \
256
+ awk -F'|' "{print \$$column}" | \
257
+ sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
258
+
259
+ echo "$voice"
260
+ }
261
+
262
+ # @function sync_intros_from_manifest
263
+ # @intent Synchronize generic intros in agent-voice-map.csv with displayNames from agent-manifest.csv
264
+ # @why Until BMAD PR 987 merges, CSV has generic "Hello! Ready to help with the discussion." intros
265
+ # @param None (operates on .bmad/_cfg files)
266
+ # @returns 0 on success, 1 on error
267
+ # @exitcode 0 on success, 1 if files missing or sync fails
268
+ # @sideeffects Updates agent-voice-map.csv, creates .backup on first run, writes .bmad-csv-sync-timestamp
269
+ # @edgecases Only updates EXACT match of generic intro, preserves all custom intros, idempotent
270
+ # @calledby get_agent_intro (lazy trigger on manifest change)
271
+ # @calls grep, cut, sed, stat, date
272
+ # @version 2.17.4 - Safe CSV sync utility that preserves user customizations
273
+ sync_intros_from_manifest() {
274
+ # Locate the CSV and manifest files
275
+ local bmad_voice_map=""
276
+ local manifest_file=""
277
+
278
+ if [[ -f ".bmad/_cfg/agent-voice-map.csv" ]]; then
279
+ bmad_voice_map=".bmad/_cfg/agent-voice-map.csv"
280
+ elif [[ -f "bmad/_cfg/agent-voice-map.csv" ]]; then
281
+ bmad_voice_map="bmad/_cfg/agent-voice-map.csv"
282
+ fi
283
+
284
+ if [[ -f "_bmad/_config/agent-manifest.csv" ]]; then
285
+ manifest_file="_bmad/_config/agent-manifest.csv"
286
+ elif [[ -f "_bmad/_config/agent-manifest.csv" ]]; then
287
+ manifest_file="_bmad/_config/agent-manifest.csv"
288
+ fi
289
+
290
+ # Both files must exist for sync to work
291
+ if [[ -z "$bmad_voice_map" ]] || [[ -z "$manifest_file" ]]; then
292
+ return 1
293
+ fi
294
+
295
+ # Check if sync is needed based on manifest timestamp
296
+ local timestamp_file="${bmad_voice_map%/*}/.bmad-csv-sync-timestamp"
297
+ local manifest_mtime=$(stat -c '%Y' "$manifest_file" 2>/dev/null || stat -f '%m' "$manifest_file" 2>/dev/null)
298
+
299
+ if [[ -f "$timestamp_file" ]]; then
300
+ local last_sync=$(cat "$timestamp_file" 2>/dev/null || echo "0")
301
+ if [[ "$manifest_mtime" -le "$last_sync" ]]; then
302
+ # Manifest hasn't changed since last sync
303
+ return 0
304
+ fi
305
+ fi
306
+
307
+ # Create backup on first sync
308
+ if [[ ! -f "${bmad_voice_map}.backup" ]]; then
309
+ cp "$bmad_voice_map" "${bmad_voice_map}.backup"
310
+ fi
311
+
312
+ # Build a temp file with synced intros
313
+ local temp_file="${bmad_voice_map}.tmp"
314
+ local generic_intro="Hello! Ready to help with the discussion."
315
+
316
+ # Read header
317
+ head -n 1 "$bmad_voice_map" > "$temp_file"
318
+
319
+ # Process each agent entry
320
+ tail -n +2 "$bmad_voice_map" | while IFS=, read -r agent voice intro; do
321
+ # Remove quotes from intro
322
+ intro=$(echo "$intro" | sed 's/^"//;s/"$//')
323
+
324
+ # Only update if intro is the exact generic placeholder
325
+ if [[ "$intro" == "$generic_intro" ]]; then
326
+ # Look up displayName and title from manifest using awk for proper CSV parsing
327
+ # CSV format: name,displayName,title,icon,role,...
328
+ local manifest_data=$(grep "^\"*${agent}\"*," "$manifest_file" | awk -F'","' '{
329
+ gsub(/^"/, "", $2);
330
+ gsub(/"$/, "", $3);
331
+ print $2 "|" $3
332
+ }')
333
+
334
+ local display_name=$(echo "$manifest_data" | cut -d'|' -f1)
335
+ local title=$(echo "$manifest_data" | cut -d'|' -f2)
336
+
337
+ if [[ -n "$display_name" ]] && [[ -n "$title" ]]; then
338
+ # Generate intro like PR 987: "Hi! I'm [Name], your [Title]."
339
+ intro="Hi! I'm ${display_name}, your ${title}."
340
+ elif [[ -n "$display_name" ]]; then
341
+ # Fallback if title missing
342
+ intro="${display_name} here"
343
+ fi
344
+ fi
345
+
346
+ # Write the line (preserving custom intros, updating generic ones)
347
+ echo "${agent},${voice},\"${intro}\""
348
+ done >> "$temp_file"
349
+
350
+ # Replace original with synced version
351
+ mv "$temp_file" "$bmad_voice_map"
352
+
353
+ # Update timestamp
354
+ echo "$manifest_mtime" > "$timestamp_file"
355
+
356
+ return 0
357
+ }
358
+
359
+ # @function get_agent_intro
360
+ # @intent Retrieve intro text for BMAD agent (spoken before their message)
361
+ # @why Helps users identify which agent is speaking in party mode
362
+ # @param $1 {string} agent_id - BMAD agent identifier
363
+ # @returns Echoes intro text to stdout, empty string if not configured
364
+ # @exitcode Always 0
365
+ # @sideeffects Triggers CSV sync on first call or manifest change
366
+ # @edgecases Returns empty string if plugin file missing, parses column 3 of CSV or markdown table
367
+ # @calledby bmad-speak.sh for agent identification in party mode
368
+ # @calls sync_intros_from_manifest, grep, awk, sed, cut
369
+ # @version 2.2.1 - Added lazy CSV sync trigger
370
+ get_agent_intro() {
371
+ local agent_id="$1"
372
+
373
+ # Check for BMAD v6 CSV file first (preferred, loose coupling)
374
+ # If this exists, use it directly without requiring plugin enable flag
375
+ # Support both .bmad (standard) and bmad (alternative) paths
376
+ local bmad_voice_map=""
377
+ if [[ -f ".bmad/_cfg/agent-voice-map.csv" ]]; then
378
+ bmad_voice_map=".bmad/_cfg/agent-voice-map.csv"
379
+ elif [[ -f "bmad/_cfg/agent-voice-map.csv" ]]; then
380
+ bmad_voice_map="bmad/_cfg/agent-voice-map.csv"
381
+ fi
382
+
383
+ # Lazy trigger: sync intros from manifest if needed
384
+ if [[ -n "$bmad_voice_map" ]]; then
385
+ sync_intros_from_manifest
386
+ fi
387
+
388
+ if [[ -n "$bmad_voice_map" ]]; then
389
+ # Read from BMAD's standard _cfg directory
390
+ # CSV format: agent,voice,intro
391
+ # Use awk to properly handle quoted CSV fields (intro may contain commas)
392
+ local intro=$(grep "^$agent_id," "$bmad_voice_map" | awk -F',' '{
393
+ # Extract field 3 onwards (intro may span multiple comma-separated parts)
394
+ intro = $3;
395
+ for (i = 4; i <= NF; i++) {
396
+ intro = intro "," $i;
397
+ }
398
+ gsub(/^"/, "", intro);
399
+ gsub(/"$/, "", intro);
400
+ print intro;
401
+ }')
402
+
403
+ # If intro is empty or generic, fall back to agent display name from manifest
404
+ if [[ -z "$intro" ]] || [[ "$intro" == "Hello! Ready to help with the discussion." ]]; then
405
+ # Try to get display name from agent-manifest.csv
406
+ local manifest_file=""
407
+ if [[ -f "_bmad/_config/agent-manifest.csv" ]]; then
408
+ manifest_file="_bmad/_config/agent-manifest.csv"
409
+ elif [[ -f "_bmad/_config/agent-manifest.csv" ]]; then
410
+ manifest_file="_bmad/_config/agent-manifest.csv"
411
+ fi
412
+
413
+ if [[ -n "$manifest_file" ]]; then
414
+ # Extract displayName (column 2) where name (column 1) matches agent_id
415
+ # CSV format: name,displayName,title,icon,role,...
416
+ local display_name=$(grep "^\"*${agent_id}\"*," "$manifest_file" | cut -d',' -f2 | sed 's/^"//;s/"$//')
417
+ if [[ -n "$display_name" ]]; then
418
+ intro="$display_name here"
419
+ fi
420
+ fi
421
+ fi
422
+
423
+ # If we got an intro, return it
424
+ if [[ -n "$intro" ]]; then
425
+ echo "$intro"
426
+ return
427
+ fi
428
+ # Otherwise fall through to hardcoded defaults below
429
+ fi
430
+
431
+ # Hardcoded default intro mappings (final fallback)
432
+ # These match the BMAD-METHOD agent display names for consistency
433
+ case "$agent_id" in
434
+ bmad-master)
435
+ echo "BMad Master here"
436
+ return
437
+ ;;
438
+ analyst)
439
+ echo "Mary here"
440
+ return
441
+ ;;
442
+ architect)
443
+ echo "Winston here"
444
+ return
445
+ ;;
446
+ dev)
447
+ echo "Amelia here"
448
+ return
449
+ ;;
450
+ pm)
451
+ echo "John here"
452
+ return
453
+ ;;
454
+ quick-flow-solo-dev)
455
+ echo "Barry here"
456
+ return
457
+ ;;
458
+ sm)
459
+ echo "Bob here"
460
+ return
461
+ ;;
462
+ tea)
463
+ echo "Murat here"
464
+ return
465
+ ;;
466
+ tech-writer)
467
+ echo "Paige here"
468
+ return
469
+ ;;
470
+ ux-designer)
471
+ echo "Sally here"
472
+ return
473
+ ;;
474
+ frame-expert)
475
+ echo "Frame Expert here"
476
+ return
477
+ ;;
478
+ esac
479
+
480
+ # Fallback to legacy markdown config file
481
+ if [[ ! -f "$VOICE_CONFIG_FILE" ]]; then
482
+ echo ""
483
+ return
484
+ fi
485
+
486
+ # AWK column 4 = Intro text
487
+ local intro=$(grep "^| $agent_id " "$VOICE_CONFIG_FILE" | \
488
+ awk -F'|' '{print $4}' | \
489
+ sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
490
+
491
+ echo "$intro"
492
+ }
493
+
494
+ # @function get_agent_personality
495
+ # @intent Retrieve TTS personality assigned to specific BMAD agent
496
+ # @why Agents may have distinct speaking styles (friendly, professional, energetic, etc.)
497
+ # @param $1 {string} agent_id - BMAD agent identifier
498
+ # @returns Echoes personality name to stdout, empty string if not found
499
+ # @exitcode Always 0
500
+ # @sideeffects None
501
+ # @edgecases Returns empty string if plugin file missing, parses column 6 of markdown table
502
+ # @calledby bmad-tts-injector.sh for personality-aware voice synthesis
503
+ # @calls grep, awk, sed
504
+ # @version 2.0.0 - Updated to column 6 (was 5) due to new provider-aware format
505
+ get_agent_personality() {
506
+ local agent_id="$1"
507
+
508
+ if [[ ! -f "$VOICE_CONFIG_FILE" ]]; then
509
+ echo ""
510
+ return
511
+ fi
512
+
513
+ # AWK column 7 = Personality
514
+ local personality=$(grep "^| $agent_id " "$VOICE_CONFIG_FILE" | \
515
+ awk -F'|' '{print $7}' | \
516
+ sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
517
+
518
+ echo "$personality"
519
+ }
520
+
521
+ # @function is_plugin_enabled
522
+ # @intent Check if BMAD voice plugin is currently enabled
523
+ # @why Allow conditional logic based on plugin state
524
+ # @param None
525
+ # @returns Echoes "true" or "false" to stdout
526
+ # @exitcode Always 0
527
+ # @sideeffects None
528
+ # @edgecases None
529
+ # @calledby show_status, enable_plugin, disable_plugin
530
+ # @calls None (file existence check)
531
+ is_plugin_enabled() {
532
+ [[ -f "$ENABLED_FLAG" ]] && echo "true" || echo "false"
533
+ }
534
+
535
+ # @function enable_plugin
536
+ # @intent Enable BMAD voice plugin and backup current voice settings
537
+ # @why Allow users to switch to per-agent voices while preserving original configuration
538
+ # @param None
539
+ # @returns None
540
+ # @exitcode Always 0
541
+ # @sideeffects Creates flag file, backs up current voice/personality/sentiment to .bmad-previous-settings
542
+ # @sideeffects Creates activation-instructions file for BMAD agents, calls bmad-tts-injector.sh
543
+ # @edgecases Handles missing settings files gracefully with defaults
544
+ # @calledby Main command dispatcher with "enable" argument
545
+ # @calls mkdir, cat, source, list_mappings, bmad-tts-injector.sh
546
+ enable_plugin() {
547
+ mkdir -p "$CONFIG_DIR"
548
+
549
+ # Save current settings before enabling
550
+ BACKUP_FILE="$CONFIG_DIR/.bmad-previous-settings"
551
+
552
+ # Save current voice
553
+ if [[ -f ".claude/tts-voice.txt" ]]; then
554
+ CURRENT_VOICE=$(cat .claude/tts-voice.txt 2>/dev/null)
555
+ elif [[ -f "$HOME/.claude/tts-voice.txt" ]]; then
556
+ CURRENT_VOICE=$(cat "$HOME/.claude/tts-voice.txt" 2>/dev/null)
557
+ else
558
+ CURRENT_VOICE="Aria"
559
+ fi
560
+
561
+ # Save current personality
562
+ if [[ -f ".claude/tts-personality.txt" ]]; then
563
+ CURRENT_PERSONALITY=$(cat .claude/tts-personality.txt 2>/dev/null)
564
+ elif [[ -f "$HOME/.claude/tts-personality.txt" ]]; then
565
+ CURRENT_PERSONALITY=$(cat "$HOME/.claude/tts-personality.txt" 2>/dev/null)
566
+ else
567
+ CURRENT_PERSONALITY="normal"
568
+ fi
569
+
570
+ # Save current sentiment
571
+ if [[ -f ".claude/tts-sentiment.txt" ]]; then
572
+ CURRENT_SENTIMENT=$(cat .claude/tts-sentiment.txt 2>/dev/null)
573
+ elif [[ -f "$HOME/.claude/tts-sentiment.txt" ]]; then
574
+ CURRENT_SENTIMENT=$(cat "$HOME/.claude/tts-sentiment.txt" 2>/dev/null)
575
+ else
576
+ CURRENT_SENTIMENT=""
577
+ fi
578
+
579
+ # Write backup
580
+ cat > "$BACKUP_FILE" <<EOF
581
+ VOICE=$CURRENT_VOICE
582
+ PERSONALITY=$CURRENT_PERSONALITY
583
+ SENTIMENT=$CURRENT_SENTIMENT
584
+ EOF
585
+
586
+ touch "$ENABLED_FLAG"
587
+
588
+ # Create activation-instructions file for BMAD agents
589
+ ACTIVATION_FILE=".claude/activation-instructions"
590
+ if [[ ! -f "$ACTIVATION_FILE" ]]; then
591
+ cat > "$ACTIVATION_FILE" <<'ACTIVATION_EOF'
592
+ # BMAD Agent Activation Instructions
593
+
594
+ When a BMAD agent activates, follow these steps EXACTLY:
595
+
596
+ ## STEP 3.5a: Create BMAD Context File (CRITICAL)
597
+
598
+ **IMMEDIATELY after agent identification, create the context file:**
599
+
600
+ ```bash
601
+ echo "$AGENT_ID" > .bmad-agent-context
602
+ ```
603
+
604
+ Where `$AGENT_ID` is one of:
605
+ - `pm` (Product Manager)
606
+ - `dev` (Developer)
607
+ - `qa` (QA)
608
+ - `architect` (Architect)
609
+ - `po` (Product Owner)
610
+ - `analyst` (Analyst)
611
+ - `sm` (Scrum Master)
612
+ - `ux-expert` (UX Expert)
613
+ - `bmad-master` (BMAD Master)
614
+ - `bmad-orchestrator` (Orchestrator)
615
+
616
+ **Example:**
617
+ ```bash
618
+ # For Product Manager agent
619
+ echo "pm" > .bmad-agent-context
620
+ ```
621
+
622
+ This file allows AgentVibes to:
623
+ 1. Detect which BMAD agent is active
624
+ 2. Look up the correct voice mapping
625
+ 3. Automatically speak questions using the agent's assigned voice
626
+
627
+ ## STEP 10: Clean Up on Exit
628
+
629
+ **Before exiting the agent, remove the context file:**
630
+
631
+ ```bash
632
+ rm -f .bmad-agent-context
633
+ ```
634
+
635
+ This ensures voice switching only happens when an agent is active.
636
+
637
+ ## Why This Matters
638
+
639
+ Without the `.bmad-agent-context` file:
640
+ - AgentVibes cannot detect which agent is active
641
+ - Questions won't be spoken automatically
642
+ - Voice switching won't work
643
+ - The BMAD voice plugin becomes non-functional
644
+
645
+ **This is MANDATORY for BMAD voice integration to work!**
646
+ ACTIVATION_EOF
647
+ echo "📝 Created activation instructions: $ACTIVATION_FILE"
648
+ fi
649
+
650
+ echo "✅ BMAD voice plugin enabled"
651
+ echo "💾 Previous settings backed up:"
652
+ echo " Voice: $CURRENT_VOICE"
653
+ echo " Personality: $CURRENT_PERSONALITY"
654
+ [[ -n "$CURRENT_SENTIMENT" ]] && echo " Sentiment: $CURRENT_SENTIMENT"
655
+ echo ""
656
+ list_mappings
657
+
658
+ # Automatically inject TTS into BMAD agents
659
+ echo ""
660
+ echo "🎤 Automatically enabling TTS for BMAD agents..."
661
+ echo ""
662
+
663
+ # Get the directory where this script is located
664
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
665
+
666
+ # Check if bmad-tts-injector.sh exists
667
+ if [[ -f "$SCRIPT_DIR/bmad-tts-injector.sh" ]]; then
668
+ # Run the TTS injector
669
+ "$SCRIPT_DIR/bmad-tts-injector.sh" enable
670
+ else
671
+ echo "⚠️ TTS injector not found at: $SCRIPT_DIR/bmad-tts-injector.sh"
672
+ echo " You can manually enable TTS with: /agent-vibes:bmad-tts enable"
673
+ fi
674
+ }
675
+
676
+ # @function disable_plugin
677
+ # @intent Disable BMAD voice plugin and restore previous voice settings
678
+ # @why Allow users to return to single-voice mode with their original configuration
679
+ # @param None
680
+ # @returns None
681
+ # @exitcode Always 0
682
+ # @sideeffects Removes flag file, restores settings from backup, calls bmad-tts-injector.sh disable
683
+ # @edgecases Handles missing backup file gracefully, warns user if no backup exists
684
+ # @calledby Main command dispatcher with "disable" argument
685
+ # @calls source, rm, echo, bmad-tts-injector.sh
686
+ disable_plugin() {
687
+ BACKUP_FILE="$CONFIG_DIR/.bmad-previous-settings"
688
+
689
+ # Check if we have a backup to restore
690
+ if [[ -f "$BACKUP_FILE" ]]; then
691
+ source "$BACKUP_FILE"
692
+
693
+ echo "❌ BMAD voice plugin disabled"
694
+ echo "🔄 Restoring previous settings:"
695
+ echo " Voice: $VOICE"
696
+ echo " Personality: $PERSONALITY"
697
+ [[ -n "$SENTIMENT" ]] && echo " Sentiment: $SENTIMENT"
698
+
699
+ # Restore voice
700
+ if [[ -n "$VOICE" ]]; then
701
+ echo "$VOICE" > .claude/tts-voice.txt 2>/dev/null || echo "$VOICE" > "$HOME/.claude/tts-voice.txt"
702
+ fi
703
+
704
+ # Restore personality
705
+ if [[ -n "$PERSONALITY" ]] && [[ "$PERSONALITY" != "normal" ]]; then
706
+ echo "$PERSONALITY" > .claude/tts-personality.txt 2>/dev/null || echo "$PERSONALITY" > "$HOME/.claude/tts-personality.txt"
707
+ fi
708
+
709
+ # Restore sentiment
710
+ if [[ -n "$SENTIMENT" ]]; then
711
+ echo "$SENTIMENT" > .claude/tts-sentiment.txt 2>/dev/null || echo "$SENTIMENT" > "$HOME/.claude/tts-sentiment.txt"
712
+ fi
713
+
714
+ # Clean up backup
715
+ rm -f "$BACKUP_FILE"
716
+ else
717
+ echo "❌ BMAD voice plugin disabled"
718
+ echo "⚠️ No previous settings found to restore"
719
+ echo "AgentVibes will use current voice/personality settings"
720
+ fi
721
+
722
+ rm -f "$ENABLED_FLAG"
723
+
724
+ # Automatically remove TTS from BMAD agents
725
+ echo ""
726
+ echo "🔇 Automatically disabling TTS for BMAD agents..."
727
+ echo ""
728
+
729
+ # Get the directory where this script is located
730
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
731
+
732
+ # Check if bmad-tts-injector.sh exists
733
+ if [[ -f "$SCRIPT_DIR/bmad-tts-injector.sh" ]]; then
734
+ # Run the TTS injector disable
735
+ "$SCRIPT_DIR/bmad-tts-injector.sh" disable
736
+ else
737
+ echo "⚠️ TTS injector not found"
738
+ echo " You can manually disable TTS with: /agent-vibes:bmad-tts disable"
739
+ fi
740
+ }
741
+
742
+ # @function list_mappings
743
+ # @intent Display all BMAD agent-to-voice mappings in readable format (provider-aware)
744
+ # @why Help users see which voice is assigned to each agent based on active TTS provider
745
+ # @param None
746
+ # @returns None
747
+ # @exitcode 0=success, 1=plugin file not found
748
+ # @sideeffects Writes formatted output to stdout
749
+ # @edgecases Parses markdown table format, skips header and separator rows
750
+ # @calledby enable_plugin, show_status, main command dispatcher with "list"
751
+ # @calls grep, sed, echo
752
+ # @version 2.1.0 - Now provider-aware: shows Piper or macOS voices based on active provider
753
+ list_mappings() {
754
+ if [[ ! -f "$VOICE_CONFIG_FILE" ]]; then
755
+ echo "❌ Plugin file not found: $VOICE_CONFIG_FILE"
756
+ return 1
757
+ fi
758
+
759
+ # Detect active TTS provider
760
+ local provider_file=""
761
+ if [[ -f ".claude/tts-provider.txt" ]]; then
762
+ provider_file=".claude/tts-provider.txt"
763
+ elif [[ -f "$HOME/.claude/tts-provider.txt" ]]; then
764
+ provider_file="$HOME/.claude/tts-provider.txt"
765
+ fi
766
+
767
+ local active_provider="piper" # default
768
+ if [[ -n "$provider_file" ]] && [[ -f "$provider_file" ]]; then
769
+ active_provider=$(cat "$provider_file")
770
+ fi
771
+
772
+ # Display provider info
773
+ echo "📊 BMAD Agent Voice Mappings (Provider: $active_provider):"
774
+ echo ""
775
+
776
+ # Table: Agent ID | Agent Name | Intro | Piper Voice | macOS Voice | Personality
777
+ # AWK columns: $1=empty | $2=ID | $3=Name | $4=Intro | $5=Piper | $6=macOS | $7=Personality
778
+ local voice_column=5 # Default to Piper (AWK column 5)
779
+ if [[ "$active_provider" == "piper" ]]; then
780
+ voice_column=6 # Use Piper (AWK column 6)
781
+ fi
782
+
783
+ grep "^| " "$VOICE_CONFIG_FILE" | grep -v "Agent ID" | grep -v "^|---" | \
784
+ while IFS='|' read -r line; do
785
+ agent_id=$(echo "$line" | awk -F'|' '{print $2}' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
786
+ name=$(echo "$line" | awk -F'|' '{print $3}' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
787
+ voice=$(echo "$line" | awk -F'|' "{print \$$voice_column}" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
788
+ personality=$(echo "$line" | awk -F'|' '{print $7}' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
789
+
790
+ [[ -n "$agent_id" ]] && echo " $agent_id → $voice [$personality]"
791
+ done
792
+ }
793
+
794
+ # @function set_agent_voice
795
+ # @intent Update voice and personality mapping for specific BMAD agent
796
+ # @why Allow customization of agent voices to user preferences
797
+ # @param $1 {string} agent_id - BMAD agent identifier
798
+ # @param $2 {string} voice - New voice name
799
+ # @param $3 {string} personality - New personality (optional, defaults to "normal")
800
+ # @returns None
801
+ # @exitcode 0=success, 1=plugin file not found or agent not found
802
+ # @sideeffects Modifies plugin file, creates .bak backup
803
+ # @edgecases Validates agent exists before updating
804
+ # @calledby Main command dispatcher with "set" argument
805
+ # @calls grep, sed
806
+ set_agent_voice() {
807
+ local agent_id="$1"
808
+ local voice="$2"
809
+ local personality="${3:-normal}"
810
+
811
+ if [[ ! -f "$VOICE_CONFIG_FILE" ]]; then
812
+ echo "❌ Plugin file not found: $VOICE_CONFIG_FILE"
813
+ return 1
814
+ fi
815
+
816
+ # Check if agent exists
817
+ if ! grep -q "^| $agent_id " "$VOICE_CONFIG_FILE"; then
818
+ echo "❌ Agent '$agent_id' not found in plugin"
819
+ return 1
820
+ fi
821
+
822
+ # Update the voice and personality in the table
823
+ sed -i.bak "s/^| $agent_id |.*| .* | .* |$/| $agent_id | $(grep "^| $agent_id " "$VOICE_CONFIG_FILE" | awk -F'|' '{print $3}') | $voice | $personality |/" "$VOICE_CONFIG_FILE"
824
+
825
+ echo "✅ Updated $agent_id → $voice [$personality]"
826
+ }
827
+
828
+ # @function show_status
829
+ # @intent Display plugin status, BMAD detection, and current voice mappings
830
+ # @why Provide comprehensive overview of plugin state for troubleshooting
831
+ # @param None
832
+ # @returns None
833
+ # @exitcode Always 0
834
+ # @sideeffects Writes status information to stdout
835
+ # @edgecases Checks for BMAD installation via manifest file
836
+ # @calledby Main command dispatcher with "status" argument
837
+ # @calls is_plugin_enabled, list_mappings
838
+ show_status() {
839
+ # Check for BMAD installation
840
+ local bmad_installed="false"
841
+ if [[ -f ".bmad-core/install-manifest.yaml" ]]; then
842
+ bmad_installed="true"
843
+ fi
844
+
845
+ if [[ $(is_plugin_enabled) == "true" ]]; then
846
+ echo "✅ BMAD voice plugin: ENABLED"
847
+ if [[ "$bmad_installed" == "true" ]]; then
848
+ echo "🔍 BMAD detected: Auto-enabled"
849
+ fi
850
+ else
851
+ echo "❌ BMAD voice plugin: DISABLED"
852
+ if [[ "$bmad_installed" == "true" ]]; then
853
+ echo "⚠️ BMAD detected but plugin disabled (enable with: /agent-vibes-bmad enable)"
854
+ fi
855
+ fi
856
+ echo ""
857
+ list_mappings
858
+ }
859
+
860
+ # @function edit_plugin
861
+ # @intent Open plugin configuration file for manual editing
862
+ # @why Allow advanced users to modify voice mappings directly
863
+ # @param None
864
+ # @returns None
865
+ # @exitcode 0=success, 1=plugin file not found
866
+ # @sideeffects Displays file path and instructions
867
+ # @edgecases Does not actually open editor, just provides guidance
868
+ # @calledby Main command dispatcher with "edit" argument
869
+ # @calls echo
870
+ edit_plugin() {
871
+ if [[ ! -f "$VOICE_CONFIG_FILE" ]]; then
872
+ echo "❌ Plugin file not found: $VOICE_CONFIG_FILE"
873
+ return 1
874
+ fi
875
+
876
+ echo "Opening $VOICE_CONFIG_FILE for editing..."
877
+ echo "Edit the markdown table to change voice mappings"
878
+ }
879
+
880
+ # Main command dispatcher
881
+ case "${1:-help}" in
882
+ enable)
883
+ enable_plugin
884
+ ;;
885
+ disable)
886
+ disable_plugin
887
+ ;;
888
+ status)
889
+ show_status
890
+ ;;
891
+ list)
892
+ list_mappings
893
+ ;;
894
+ set)
895
+ if [[ -z "$2" ]] || [[ -z "$3" ]]; then
896
+ echo "Usage: bmad-voice-manager.sh set <agent-id> <voice> [personality]"
897
+ exit 1
898
+ fi
899
+ set_agent_voice "$2" "$3" "$4"
900
+ ;;
901
+ get-voice)
902
+ get_agent_voice "$2"
903
+ ;;
904
+ get-intro)
905
+ get_agent_intro "$2"
906
+ ;;
907
+ get-personality)
908
+ get_agent_personality "$2"
909
+ ;;
910
+ edit)
911
+ edit_plugin
912
+ ;;
913
+ *)
914
+ echo "Usage: bmad-voice-manager.sh {enable|disable|status|list|set|get-voice|get-intro|get-personality|edit}"
915
+ echo ""
916
+ echo "Commands:"
917
+ echo " enable Enable BMAD voice plugin"
918
+ echo " disable Disable BMAD voice plugin"
919
+ echo " status Show plugin status and mappings"
920
+ echo " list List all agent voice mappings"
921
+ echo " set <id> <voice> Set voice for agent"
922
+ echo " get-voice <id> Get voice for agent"
923
+ echo " get-intro <id> Get intro text for agent"
924
+ echo " get-personality <id> Get personality for agent"
925
+ echo " edit Edit plugin configuration"
926
+ exit 1
927
+ ;;
928
+ esac