agentvibes 5.3.0 → 5.5.0

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 (222) hide show
  1. package/.agentvibes/LITE-MODE.md +236 -0
  2. package/.agentvibes/README.md +136 -0
  3. package/.agentvibes/backup/session-start-tts.sh.20251210_212814 +141 -0
  4. package/.agentvibes/backups/agents/analyst_20260204_144958.md +78 -0
  5. package/.agentvibes/backups/agents/architect_20260204_144958.md +72 -0
  6. package/.agentvibes/backups/agents/dev_20260204_144958.md +74 -0
  7. package/.agentvibes/backups/agents/pm_20260204_144958.md +72 -0
  8. package/.agentvibes/backups/agents/quick-flow-solo-dev_20260204_144958.md +64 -0
  9. package/.agentvibes/backups/agents/sm_20260204_144958.md +87 -0
  10. package/.agentvibes/backups/agents/tea_20260204_144958.md +79 -0
  11. package/.agentvibes/backups/agents/tech-writer_20260204_144958.md +82 -0
  12. package/.agentvibes/backups/agents/ux-designer_20260204_144958.md +80 -0
  13. package/.agentvibes/bmad/bmad-voices.md +69 -69
  14. package/.agentvibes/config/README-personality-defaults.md +162 -0
  15. package/.agentvibes/config/mode.txt +1 -0
  16. package/.agentvibes/config/personality-voice-defaults.default.json +21 -0
  17. package/.agentvibes/config/save-audio.txt +1 -0
  18. package/.agentvibes/config/voice-metadata.json +160 -0
  19. package/.agentvibes/config.json +24 -15
  20. package/.agentvibes/hooks/help.sh +191 -0
  21. package/.agentvibes/hooks/post-tool-use-lite.sh +111 -0
  22. package/.agentvibes/hooks/save-audio-manager.sh +162 -0
  23. package/.agentvibes/hooks/session-start-full-optimized.sh +102 -0
  24. package/.agentvibes/hooks/session-start-full.sh +142 -0
  25. package/.agentvibes/hooks/session-start-lite-v2.sh +34 -0
  26. package/.agentvibes/hooks/session-start-lite.sh +29 -0
  27. package/.agentvibes/hooks/stop-lite.sh +115 -0
  28. package/.agentvibes/hooks/switch-mode.sh +215 -0
  29. package/.agentvibes/output-styles/audio-summary.md +30 -0
  30. package/.claude/activation-instructions +54 -54
  31. package/.claude/audio/voice-samples/piper/alan.wav +0 -0
  32. package/.claude/audio/voice-samples/piper/amy.wav +0 -0
  33. package/.claude/audio/voice-samples/piper/charlotte.wav +0 -0
  34. package/.claude/audio/voice-samples/piper/joe.wav +0 -0
  35. package/.claude/audio/voice-samples/piper/john.wav +0 -0
  36. package/.claude/audio/voice-samples/piper/katherine.wav +0 -0
  37. package/.claude/audio/voice-samples/piper/kristin.wav +0 -0
  38. package/.claude/audio/voice-samples/piper/linda.wav +0 -0
  39. package/.claude/audio/voice-samples/piper/marcus.wav +0 -0
  40. package/.claude/audio/voice-samples/piper/ryan.wav +0 -0
  41. package/.claude/commands/agent-vibes/add.md +21 -21
  42. package/.claude/commands/agent-vibes/agent-vibes.md +101 -101
  43. package/.claude/commands/agent-vibes/agent.md +79 -79
  44. package/.claude/commands/agent-vibes/background-music.md +111 -111
  45. package/.claude/commands/agent-vibes/bmad.md +198 -198
  46. package/.claude/commands/agent-vibes/clean.md +18 -18
  47. package/.claude/commands/agent-vibes/cleanup.md +18 -18
  48. package/.claude/commands/agent-vibes/commands.json +145 -145
  49. package/.claude/commands/agent-vibes/effects.md +97 -97
  50. package/.claude/commands/agent-vibes/get.md +9 -9
  51. package/.claude/commands/agent-vibes/hide.md +91 -91
  52. package/.claude/commands/agent-vibes/language.md +23 -23
  53. package/.claude/commands/agent-vibes/learn.md +67 -67
  54. package/.claude/commands/agent-vibes/list.md +13 -13
  55. package/.claude/commands/agent-vibes/mute.md +37 -37
  56. package/.claude/commands/agent-vibes/preview.md +17 -17
  57. package/.claude/commands/agent-vibes/provider.md +68 -68
  58. package/.claude/commands/agent-vibes/replay-target.md +14 -14
  59. package/.claude/commands/agent-vibes/sample.md +12 -12
  60. package/.claude/commands/agent-vibes/set-favorite-voice.md +84 -84
  61. package/.claude/commands/agent-vibes/set-pretext.md +65 -65
  62. package/.claude/commands/agent-vibes/set-speed.md +41 -41
  63. package/.claude/commands/agent-vibes/show.md +84 -84
  64. package/.claude/commands/agent-vibes/switch.md +87 -87
  65. package/.claude/commands/agent-vibes/target-voice.md +26 -26
  66. package/.claude/commands/agent-vibes/target.md +30 -30
  67. package/.claude/commands/agent-vibes/translate.md +68 -68
  68. package/.claude/commands/agent-vibes/unmute.md +45 -45
  69. package/.claude/commands/agent-vibes/whoami.md +7 -7
  70. package/.claude/commands/agent-vibes-bmad-voices.md +117 -117
  71. package/.claude/commands/agent-vibes-rdp.md +24 -24
  72. package/.claude/config/audio-effects.cfg +16 -11
  73. package/.claude/config/audio-effects.cfg.sample +52 -52
  74. package/.claude/config/background-music-position.txt +27 -0
  75. package/.claude/config/background-music-volume.txt +1 -1
  76. package/.claude/config/background-music.cfg +1 -0
  77. package/.claude/config/background-music.txt +1 -0
  78. package/.claude/config/tts-speech-rate.txt +1 -4
  79. package/.claude/config/tts-verbosity.txt +1 -0
  80. package/.claude/docs/TERMUX_SETUP.md +408 -408
  81. package/.claude/github-star-reminder.txt +1 -1
  82. package/.claude/hooks/README-TTS-QUEUE.md +135 -135
  83. package/.claude/hooks/audio-cache-utils.sh +0 -0
  84. package/.claude/hooks/audio-processor.sh +60 -14
  85. package/.claude/hooks/background-music-manager.sh +0 -0
  86. package/.claude/hooks/bmad-party-manager.sh +225 -0
  87. package/.claude/hooks/bmad-party-speak.sh +0 -0
  88. package/.claude/hooks/bmad-speak-enhanced.sh +0 -0
  89. package/.claude/hooks/bmad-speak.sh +12 -15
  90. package/.claude/hooks/bmad-tts-injector.sh +0 -0
  91. package/.claude/hooks/bmad-voice-manager.sh +0 -0
  92. package/.claude/hooks/clawdbot-receiver-SECURE.sh +25 -23
  93. package/.claude/hooks/clawdbot-receiver.sh +4 -28
  94. package/.claude/hooks/clean-audio-cache.sh +0 -0
  95. package/.claude/hooks/cleanup-cache.sh +0 -0
  96. package/.claude/hooks/configure-rdp-mode.sh +0 -0
  97. package/.claude/hooks/download-extra-voices.sh +0 -0
  98. package/.claude/hooks/effects-manager.sh +0 -0
  99. package/.claude/hooks/github-star-reminder.sh +0 -0
  100. package/.claude/hooks/language-manager.sh +0 -0
  101. package/.claude/hooks/learn-manager.sh +0 -0
  102. package/.claude/hooks/macos-voice-manager.sh +0 -0
  103. package/.claude/hooks/migrate-background-music.sh +0 -0
  104. package/.claude/hooks/migrate-to-agentvibes.sh +0 -0
  105. package/.claude/hooks/optimize-background-music.sh +0 -0
  106. package/.claude/hooks/personality-manager.sh +0 -0
  107. package/.claude/hooks/piper-download-voices.sh +0 -0
  108. package/.claude/hooks/piper-installer.sh +1 -1
  109. package/.claude/hooks/piper-multispeaker-registry.sh +0 -0
  110. package/.claude/hooks/piper-voice-manager.sh +0 -0
  111. package/.claude/hooks/play-tts-enhanced.sh +0 -0
  112. package/.claude/hooks/play-tts-macos.sh +6 -12
  113. package/.claude/hooks/play-tts-piper.sh +52 -81
  114. package/.claude/hooks/play-tts-soprano.sh +9 -43
  115. package/.claude/hooks/play-tts-ssh-remote.sh +43 -215
  116. package/.claude/hooks/play-tts-termux-ssh.sh +0 -0
  117. package/.claude/hooks/play-tts.sh +41 -20
  118. package/.claude/hooks/post-response.sh +41 -0
  119. package/.claude/hooks/prepare-release.sh +0 -0
  120. package/.claude/hooks/provider-commands.sh +0 -0
  121. package/.claude/hooks/provider-manager.sh +0 -0
  122. package/.claude/hooks/replay-target-audio.sh +0 -0
  123. package/.claude/hooks/requirements.txt +6 -6
  124. package/.claude/hooks/sentiment-manager.sh +0 -0
  125. package/.claude/hooks/session-start-tts.sh +56 -39
  126. package/.claude/hooks/soprano-gradio-synth.py +139 -139
  127. package/.claude/hooks/speed-manager.sh +0 -0
  128. package/.claude/hooks/stop.sh +63 -0
  129. package/.claude/hooks/termux-installer.sh +0 -0
  130. package/.claude/hooks/translate-manager.sh +0 -0
  131. package/.claude/hooks/translator.py +237 -237
  132. package/.claude/hooks/tts-queue-worker.sh +0 -0
  133. package/.claude/hooks/tts-queue.sh +0 -0
  134. package/.claude/hooks/verbosity-manager.sh +0 -0
  135. package/.claude/hooks/voice-manager.sh +26 -4
  136. package/.claude/hooks-windows/audio-cache-utils.ps1 +119 -119
  137. package/.claude/hooks-windows/bmad-party-speak.ps1 +278 -278
  138. package/.claude/hooks-windows/bmad-speak.ps1 +264 -264
  139. package/.claude/hooks-windows/clean-audio-cache.ps1 +53 -53
  140. package/.claude/hooks-windows/effects-manager.ps1 +294 -294
  141. package/.claude/hooks-windows/language-manager.ps1 +193 -193
  142. package/.claude/hooks-windows/learn-manager.ps1 +241 -241
  143. package/.claude/hooks-windows/personality-manager.ps1 +266 -266
  144. package/.claude/hooks-windows/play-tts-soprano.ps1 +5 -5
  145. package/.claude/hooks-windows/play-tts-termux-ssh.ps1 +138 -138
  146. package/.claude/hooks-windows/play-tts-windows-piper.ps1 +178 -0
  147. package/.claude/hooks-windows/play-tts-windows-sapi.ps1 +108 -0
  148. package/.claude/hooks-windows/play-tts.ps1 +265 -507
  149. package/.claude/hooks-windows/provider-manager.ps1 +158 -192
  150. package/.claude/hooks-windows/session-start-tts.ps1 +55 -46
  151. package/.claude/hooks-windows/soprano-gradio-synth.py +153 -153
  152. package/.claude/hooks-windows/speed-manager.ps1 +166 -166
  153. package/.claude/hooks-windows/voice-manager-windows.ps1 +176 -260
  154. package/.claude/output-styles/agent-vibes.md +202 -202
  155. package/.claude/personalities/angry.md +14 -14
  156. package/.claude/personalities/annoying.md +14 -14
  157. package/.claude/personalities/crass.md +14 -14
  158. package/.claude/personalities/dramatic.md +14 -14
  159. package/.claude/personalities/dry-humor.md +50 -50
  160. package/.claude/personalities/flirty.md +20 -20
  161. package/.claude/personalities/funny.md +14 -14
  162. package/.claude/personalities/grandpa.md +32 -32
  163. package/.claude/personalities/millennial.md +14 -14
  164. package/.claude/personalities/moody.md +14 -14
  165. package/.claude/personalities/normal.md +16 -16
  166. package/.claude/personalities/pirate.md +14 -14
  167. package/.claude/personalities/poetic.md +14 -14
  168. package/.claude/personalities/professional.md +14 -14
  169. package/.claude/personalities/rapper.md +55 -55
  170. package/.claude/personalities/robot.md +14 -14
  171. package/.claude/personalities/sarcastic.md +38 -38
  172. package/.claude/personalities/sassy.md +14 -14
  173. package/.claude/personalities/surfer-dude.md +14 -14
  174. package/.claude/personalities/zen.md +14 -14
  175. package/.claude/piper-voices-dir.txt +1 -0
  176. package/.claude/settings.json +25 -15
  177. package/.claude/verbosity.txt +1 -1
  178. package/.clawdbot/README.md +105 -105
  179. package/.clawdbot/skill/SKILL.md +149 -145
  180. package/.mcp.json +30 -11
  181. package/CLAUDE.md +170 -215
  182. package/README.md +207 -521
  183. package/RELEASE_NOTES.md +1172 -1976
  184. package/WINDOWS-SETUP.md +208 -208
  185. package/bin/agent-vibes +0 -0
  186. package/bin/agentvibes-voice-browser.js +64 -1289
  187. package/bin/agentvibes.js +28 -0
  188. package/bin/ensure-soprano-running.sh +43 -0
  189. package/bin/mcp-server.js +121 -121
  190. package/bin/mcp-server.sh +0 -0
  191. package/bin/test-bmad-pr +78 -78
  192. package/mcp-server/QUICK_START.md +203 -203
  193. package/mcp-server/README.md +345 -345
  194. package/mcp-server/WINDOWS_SETUP.md +260 -260
  195. package/mcp-server/docs/troubleshooting-audio.md +313 -313
  196. package/mcp-server/examples/claude_desktop_config.json +11 -11
  197. package/mcp-server/examples/claude_desktop_config_piper.json +9 -9
  198. package/mcp-server/examples/custom_instructions.md +169 -169
  199. package/mcp-server/install-deps.js +130 -130
  200. package/mcp-server/pyproject.toml +52 -52
  201. package/mcp-server/requirements.txt +2 -2
  202. package/mcp-server/server.py +1467 -1578
  203. package/mcp-server/test_server.py +395 -395
  204. package/package.json +1 -3
  205. package/setup-windows.ps1 +815 -815
  206. package/src/console/tabs/music-tab.js +5 -2
  207. package/src/console/tabs/voices-tab.js +71 -37
  208. package/src/installer.js +52 -5
  209. package/src/services/llm-provider-service.js +1 -1
  210. package/templates/agentvibes-receiver.sh +158 -483
  211. package/templates/audio/welcome-music.mp3 +0 -0
  212. package/.agentvibes/bmad-voice-map.json +0 -104
  213. package/.agentvibes/copilot-sessions.log +0 -4
  214. package/.claude/config/audio-effects-bmad.cfg +0 -50
  215. package/.claude/config/intro-text.txt +0 -1
  216. package/.claude/config/personality.txt +0 -1
  217. package/.claude/config/piper-speech-rate.txt +0 -4
  218. package/.claude/config/piper-target-speech-rate.txt +0 -1
  219. package/.claude/config/reverb-level.txt +0 -1
  220. package/.claude/config/tts-target-speech-rate.txt +0 -1
  221. package/voice-assignments.json +0 -8245
  222. /package/{.claude → .agentvibes}/config/agentvibes.json +0 -0
@@ -1,278 +1,278 @@
1
- #
2
- # File: ~/.claude/hooks-windows/bmad-party-speak.ps1
3
- #
4
- # AgentVibes PostToolUse Hook - BMAD Party Mode TTS
5
- #
6
- # Fires after every Agent tool call. Detects BMAD party mode agents by
7
- # fingerprinting the prompt, extracts the agent display name, maps it to
8
- # the canonical agent ID via the manifest, then calls bmad-speak.ps1.
9
- #
10
- # Installed globally so it works in any BMAD project.
11
- # Uses $env:CLAUDE_PROJECT_DIR to locate the project manifest at runtime.
12
- #
13
-
14
- try {
15
- # --- Read stdin safely ---
16
- $raw = [Console]::In.ReadToEnd()
17
- if (-not $raw -or $raw.Trim() -eq "") { exit 0 }
18
-
19
- $data = $raw | ConvertFrom-Json
20
- if (-not $data) { exit 0 }
21
-
22
- # --- Only handle Agent tool ---
23
- if ($data.tool_name -ne "Agent") { exit 0 }
24
-
25
-
26
- # --- Extract prompt ---
27
- $prompt = $data.tool_input.prompt
28
- if (-not $prompt) { exit 0 }
29
-
30
- # --- Fingerprint: only fire for BMAD party mode agents ---
31
- if ($prompt -notmatch "BMAD agent in a collaborative roundtable") { exit 0 }
32
-
33
- # --- Extract display name from "You are {Name} (" ---
34
- if ($prompt -notmatch "You are ([A-Za-z]+)\s*\(") { exit 0 }
35
- $DisplayName = $Matches[1]
36
-
37
- # --- Extract response text ---
38
- $content = $data.tool_response.content
39
- if (-not $content -or $content.Count -eq 0) { exit 0 }
40
- $ResponseText = ($content | Where-Object { $_.type -eq "text" } | Select-Object -First 1).text
41
- if (-not $ResponseText) { exit 0 }
42
-
43
- # Strip leading icon + bold name header e.g. "📊 **Mary:**" or garbled "≡ƒôè **Mary:**"
44
- # Trim first so leading newlines don't defeat the ^ anchor (fixes M7)
45
- $ResponseText = $ResponseText.Trim()
46
- $ResponseText = $ResponseText -replace '^\S*\s*\*\*[^:]+:\*\*\s*', ''
47
- $ResponseText = $ResponseText.Trim()
48
- if (-not $ResponseText) { exit 0 }
49
-
50
- # Strip markdown so TTS doesn't speak asterisks, hashes, backticks, etc.
51
- $ResponseText = $ResponseText -replace '\*{1,3}', '' # bold, italic, bold-italic
52
- $ResponseText = $ResponseText -replace '`{1,3}[^`]*`{1,3}', '' # inline code / code blocks
53
- $ResponseText = $ResponseText -replace '#{1,6}\s*', '' # headings
54
- $ResponseText = $ResponseText -replace '_{1,2}', '' # underline/italic alt
55
- $ResponseText = $ResponseText -replace '\[([^\]]+)\]\([^)]+\)', '$1' # links → label only
56
- $ResponseText = $ResponseText -replace '!\[[^\]]*\]\([^)]+\)', '' # images
57
- $ResponseText = $ResponseText -replace '(?m)^\s*[-*+]\s+', '' # bullet list markers (multiline)
58
- $ResponseText = $ResponseText -replace '(?m)^\s*\d+\.\s+', '' # numbered list markers
59
- $ResponseText = $ResponseText -replace '\\([!$*_`\\])', '$1' # escaped markdown chars
60
- $ResponseText = $ResponseText.Trim()
61
- if (-not $ResponseText) { exit 0 }
62
-
63
- # --- Resolve paths ---
64
- $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
65
- $ProjectRoot = $env:CLAUDE_PROJECT_DIR
66
-
67
- # --- Find bmad-speak.ps1 (prefer project-local, fall back to global) ---
68
- $BmadSpeak = $null
69
- if ($ProjectRoot) {
70
- $local = Join-Path $ProjectRoot ".claude\hooks-windows\bmad-speak.ps1"
71
- if (Test-Path $local) { $BmadSpeak = $local }
72
- }
73
- if (-not $BmadSpeak) {
74
- $global = Join-Path $ScriptDir "bmad-speak.ps1"
75
- if (Test-Path $global) { $BmadSpeak = $global }
76
- }
77
- if (-not $BmadSpeak) { exit 0 }
78
-
79
- # --- Look up canonical agent ID from project manifest (fixes m2: UTF8 encoding) ---
80
- $AgentId = $DisplayName # fallback
81
- if ($ProjectRoot) {
82
- $ManifestFile = Join-Path $ProjectRoot "_bmad\_config\agent-manifest.csv"
83
- if (Test-Path $ManifestFile) {
84
- $rows = Import-Csv $ManifestFile -Encoding UTF8
85
- foreach ($row in $rows) {
86
- if ($row.displayName -ieq $DisplayName) {
87
- $AgentId = $row.name
88
- break
89
- }
90
- }
91
- }
92
- }
93
-
94
- # --- Apply verbosity truncation ---
95
- $Verbosity = "medium"
96
- $verbosityPaths = @(
97
- (Join-Path $env:USERPROFILE ".claude\tts-verbosity.txt")
98
- )
99
- if ($ProjectRoot) {
100
- $verbosityPaths = @((Join-Path $ProjectRoot ".claude\tts-verbosity.txt")) + $verbosityPaths
101
- }
102
- foreach ($p in $verbosityPaths) {
103
- if (Test-Path $p) {
104
- $v = (Get-Content $p -Raw -ErrorAction SilentlyContinue).Trim()
105
- if ($v) { $Verbosity = $v; break }
106
- }
107
- }
108
-
109
- switch ($Verbosity) {
110
- "low" {
111
- $sentences = [regex]::Split($ResponseText, '(?<=[.!?])\s+')
112
- # Fall back to full text if no sentence-ending punctuation (fixes m1)
113
- if ($sentences.Count -gt 0 -and $sentences[0]) { $ResponseText = $sentences[0] }
114
- }
115
- "medium" {
116
- $sentences = [regex]::Split($ResponseText, '(?<=[.!?])\s+')
117
- # Fall back to full text if no sentence-ending punctuation (fixes m1)
118
- $truncated = ($sentences | Select-Object -First 2) -join " "
119
- if ($truncated) { $ResponseText = $truncated }
120
- }
121
- # "high" = full text
122
- }
123
-
124
- # --- Pre-synthesize WAV before acquiring mutex so synthesis overlaps with previous agent's playback ---
125
- $PreSynthWav = $null
126
- try {
127
- # Resolve agent voice from voice map
128
- $VoiceMapLocal = if ($ProjectRoot) { Join-Path $ProjectRoot ".agentvibes\bmad-voice-map.json" } else { $null }
129
- $VoiceMapGlobal = Join-Path $env:USERPROFILE ".agentvibes\bmad-voice-map.json"
130
- $VoiceMapFile = if ($VoiceMapLocal -and (Test-Path $VoiceMapLocal)) { $VoiceMapLocal }
131
- elseif (Test-Path $VoiceMapGlobal) { $VoiceMapGlobal }
132
- else { $null }
133
-
134
- $AgentVoiceName = $null
135
- $SpeakerId = $null
136
- $AgentPretext = $null
137
- if ($VoiceMapFile) {
138
- $vm = Get-Content $VoiceMapFile -Raw | ConvertFrom-Json
139
- $profile = $vm.agents.$AgentId
140
- if ($profile -and $profile.pretext) { $AgentPretext = $profile.pretext }
141
- if ($profile -and $profile.voice) {
142
- $raw = $profile.voice
143
- if ($raw -match '::') {
144
- $parts = $raw -split '::'
145
- $AgentVoiceName = $parts[0]
146
- $SpeakerName = if ($parts.Length -ge 2) { $parts[1] } else { "" }
147
- # NOTE: The suffix number (e.g. "14" in "Yara-14") is a display disambiguator,
148
- # NOT the piper speaker index. Must look up real index from speaker_id_map.
149
- if ($SpeakerName) {
150
- $VoicesDir = "$env:USERPROFILE\.claude\piper-voices"
151
- $OnnxJsonPath = "$VoicesDir\$AgentVoiceName.onnx.json"
152
- if (Test-Path $OnnxJsonPath) {
153
- try {
154
- $onnxData = Get-Content $OnnxJsonPath -Raw -Encoding UTF8 | ConvertFrom-Json
155
- $speakerIdMap = $onnxData.speaker_id_map
156
- if ($speakerIdMap -and $speakerIdMap.PSObject.Properties[$SpeakerName]) {
157
- $SpeakerId = [string]$speakerIdMap.PSObject.Properties[$SpeakerName].Value
158
- }
159
- } catch { }
160
- }
161
- }
162
- } else {
163
- $AgentVoiceName = $raw
164
- }
165
- }
166
- }
167
-
168
- # Fallback: parse bmad-voices.md markdown table if no JSON voice map found
169
- if (-not $AgentPretext -and $ProjectRoot) {
170
- $VoicesMdPaths = @(
171
- (Join-Path $ProjectRoot ".agentvibes\bmad\bmad-voices.md"),
172
- (Join-Path $env:USERPROFILE ".agentvibes\bmad\bmad-voices.md")
173
- )
174
- foreach ($mdPath in $VoicesMdPaths) {
175
- if (-not (Test-Path $mdPath)) { continue }
176
- $mdLines = Get-Content $mdPath -Encoding UTF8
177
- # Strip bmad-agent- prefix for matching (manifest uses bmad-agent-pm, table uses pm)
178
- $shortId = $AgentId -replace '^bmad-agent-', ''
179
- foreach ($mdLine in $mdLines) {
180
- if ($mdLine -notmatch '^\|') { continue }
181
- if ($mdLine -match '^\|-') { continue } # separator row
182
- if ($mdLine -match 'Agent ID') { continue } # header row
183
- $cols = $mdLine -split '\|' | ForEach-Object { $_.Trim() }
184
- # cols: [0]=empty, [1]=Agent ID, [2]=Agent Name, [3]=Intro, [4]=Piper TTS Voice, [5]=Piper Voice, [6]=Personality
185
- if ($cols.Count -lt 6) { continue }
186
- $tableId = $cols[1]
187
- $tableName = $cols[2]
188
- if ($tableId -ieq $shortId -or $tableId -ieq $AgentId -or $tableName -like "*$DisplayName*") {
189
- if ($cols[3]) { $AgentPretext = $cols[3] }
190
- # Use Piper Voice column (index 5) for piper provider
191
- if (-not $AgentVoiceName -and $cols[5]) { $AgentVoiceName = $cols[5] }
192
- if ($cols.Count -ge 7 -and $cols[6] -and $cols[6] -ine 'normal') {
193
- # personality available for bmad-speak.ps1 downstream
194
- }
195
- break
196
- }
197
- }
198
- if ($AgentPretext) { break }
199
- }
200
- }
201
-
202
- # Locate piper
203
- $PiperExe = "$env:LOCALAPPDATA\Programs\Piper\piper.exe"
204
- if (-not (Test-Path $PiperExe)) {
205
- $found = Get-Command piper.exe -ErrorAction SilentlyContinue
206
- if ($found) { $PiperExe = $found.Source }
207
- }
208
-
209
- if (Test-Path $PiperExe) {
210
- $VoicesDir = "$env:USERPROFILE\.claude\piper-voices"
211
- # Fall back to first available voice if agent voice not found
212
- if (-not $AgentVoiceName) {
213
- $first = Get-ChildItem $VoicesDir -Filter "*.onnx" -ErrorAction SilentlyContinue | Select-Object -First 1
214
- if ($first) { $AgentVoiceName = $first.BaseName }
215
- }
216
- if ($AgentVoiceName -and ($AgentVoiceName -match '^[a-zA-Z0-9_\-\.]+$')) {
217
- $VoiceModel = Join-Path $VoicesDir "$AgentVoiceName.onnx"
218
- if (Test-Path $VoiceModel) {
219
- $AudioDir = "$env:USERPROFILE\.claude\audio"
220
- if (-not (Test-Path $AudioDir)) { New-Item -ItemType Directory -Path $AudioDir -Force | Out-Null }
221
- $PreSynthWav = Join-Path $AudioDir "tts-presynth-$([System.IO.Path]::GetRandomFileName() -replace '\..*').wav"
222
- $piperArgs = @("--model", $VoiceModel, "--output-file", $PreSynthWav)
223
- if ($SpeakerId) { $piperArgs += @("--speaker", $SpeakerId) }
224
- # Include pretext in pre-synthesis so it's spoken — bmad-speak.ps1 will
225
- # skip synthesis (AGENTVIBES_PRESYNTHESIZED_WAV set) so pretext must be here.
226
- $PreSynthText = if ($AgentPretext) { "$AgentPretext. $ResponseText" } else { $ResponseText }
227
- $PreSynthText | & $PiperExe @piperArgs 2>$null
228
- if (-not (Test-Path $PreSynthWav) -or (Get-Item $PreSynthWav).Length -eq 0) {
229
- $PreSynthWav = $null
230
- }
231
- }
232
- }
233
- }
234
- } catch {
235
- $PreSynthWav = $null # degrade gracefully — will synthesize inside mutex instead
236
- }
237
-
238
- # --- Speak with queue serialization (named mutex, cross-process) ---
239
- $mutex = New-Object System.Threading.Mutex($false, "AgentVibesPartyModeTTSQueue")
240
- try {
241
- $acquired = $false
242
- try {
243
- # WaitOne throws AbandonedMutexException if prior process crashed while holding it.
244
- # That exception means we DID acquire the mutex — treat it as success (fixes M2).
245
- # 600s timeout covers worst-case party-mode queue depth (9 agents x
246
- # ~60s of speech each). Shorter timeouts silently dropped the last
247
- # agents to speak. play-tts.ps1 bounds true stuck processes via its
248
- # playback watchdog, so waiting longer here is safe.
249
- $acquired = $mutex.WaitOne(600000)
250
- } catch [System.Threading.AbandonedMutexException] {
251
- $acquired = $true # abandoned = we now own it
252
- }
253
-
254
- if ($acquired) {
255
- try {
256
- # Pass pre-synthesized WAV path so play-tts.ps1 skips synthesis (reduces gap between agents)
257
- if ($PreSynthWav) { $env:AGENTVIBES_PRESYNTHESIZED_WAV = $PreSynthWav }
258
- # Pass positional args directly after -File (spaces handled by quoting via array)
259
- & powershell -NoProfile -ExecutionPolicy Bypass -File $BmadSpeak $AgentId $ResponseText
260
- } finally {
261
- $env:AGENTVIBES_PRESYNTHESIZED_WAV = ""
262
- if ($PreSynthWav -and (Test-Path $PreSynthWav)) {
263
- Remove-Item $PreSynthWav -Force -ErrorAction SilentlyContinue
264
- }
265
- $mutex.ReleaseMutex()
266
- }
267
- } else {
268
- # Timed out — log to stderr so it's visible in hook error output (fixes M6)
269
- [Console]::Error.WriteLine("[AgentVibes] Party mode TTS queue timeout for agent: $AgentId")
270
- }
271
- } finally {
272
- $mutex.Close()
273
- }
274
-
275
- } catch {
276
- # Silently exit — never block Claude
277
- exit 0
278
- }
1
+ #
2
+ # File: ~/.claude/hooks-windows/bmad-party-speak.ps1
3
+ #
4
+ # AgentVibes PostToolUse Hook - BMAD Party Mode TTS
5
+ #
6
+ # Fires after every Agent tool call. Detects BMAD party mode agents by
7
+ # fingerprinting the prompt, extracts the agent display name, maps it to
8
+ # the canonical agent ID via the manifest, then calls bmad-speak.ps1.
9
+ #
10
+ # Installed globally so it works in any BMAD project.
11
+ # Uses $env:CLAUDE_PROJECT_DIR to locate the project manifest at runtime.
12
+ #
13
+
14
+ try {
15
+ # --- Read stdin safely ---
16
+ $raw = [Console]::In.ReadToEnd()
17
+ if (-not $raw -or $raw.Trim() -eq "") { exit 0 }
18
+
19
+ $data = $raw | ConvertFrom-Json
20
+ if (-not $data) { exit 0 }
21
+
22
+ # --- Only handle Agent tool ---
23
+ if ($data.tool_name -ne "Agent") { exit 0 }
24
+
25
+
26
+ # --- Extract prompt ---
27
+ $prompt = $data.tool_input.prompt
28
+ if (-not $prompt) { exit 0 }
29
+
30
+ # --- Fingerprint: only fire for BMAD party mode agents ---
31
+ if ($prompt -notmatch "BMAD agent in a collaborative roundtable") { exit 0 }
32
+
33
+ # --- Extract display name from "You are {Name} (" ---
34
+ if ($prompt -notmatch "You are ([A-Za-z]+)\s*\(") { exit 0 }
35
+ $DisplayName = $Matches[1]
36
+
37
+ # --- Extract response text ---
38
+ $content = $data.tool_response.content
39
+ if (-not $content -or $content.Count -eq 0) { exit 0 }
40
+ $ResponseText = ($content | Where-Object { $_.type -eq "text" } | Select-Object -First 1).text
41
+ if (-not $ResponseText) { exit 0 }
42
+
43
+ # Strip leading icon + bold name header e.g. "📊 **Mary:**" or garbled "≡ƒôè **Mary:**"
44
+ # Trim first so leading newlines don't defeat the ^ anchor (fixes M7)
45
+ $ResponseText = $ResponseText.Trim()
46
+ $ResponseText = $ResponseText -replace '^\S*\s*\*\*[^:]+:\*\*\s*', ''
47
+ $ResponseText = $ResponseText.Trim()
48
+ if (-not $ResponseText) { exit 0 }
49
+
50
+ # Strip markdown so TTS doesn't speak asterisks, hashes, backticks, etc.
51
+ $ResponseText = $ResponseText -replace '\*{1,3}', '' # bold, italic, bold-italic
52
+ $ResponseText = $ResponseText -replace '`{1,3}[^`]*`{1,3}', '' # inline code / code blocks
53
+ $ResponseText = $ResponseText -replace '#{1,6}\s*', '' # headings
54
+ $ResponseText = $ResponseText -replace '_{1,2}', '' # underline/italic alt
55
+ $ResponseText = $ResponseText -replace '\[([^\]]+)\]\([^)]+\)', '$1' # links → label only
56
+ $ResponseText = $ResponseText -replace '!\[[^\]]*\]\([^)]+\)', '' # images
57
+ $ResponseText = $ResponseText -replace '(?m)^\s*[-*+]\s+', '' # bullet list markers (multiline)
58
+ $ResponseText = $ResponseText -replace '(?m)^\s*\d+\.\s+', '' # numbered list markers
59
+ $ResponseText = $ResponseText -replace '\\([!$*_`\\])', '$1' # escaped markdown chars
60
+ $ResponseText = $ResponseText.Trim()
61
+ if (-not $ResponseText) { exit 0 }
62
+
63
+ # --- Resolve paths ---
64
+ $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
65
+ $ProjectRoot = $env:CLAUDE_PROJECT_DIR
66
+
67
+ # --- Find bmad-speak.ps1 (prefer project-local, fall back to global) ---
68
+ $BmadSpeak = $null
69
+ if ($ProjectRoot) {
70
+ $local = Join-Path $ProjectRoot ".claude\hooks-windows\bmad-speak.ps1"
71
+ if (Test-Path $local) { $BmadSpeak = $local }
72
+ }
73
+ if (-not $BmadSpeak) {
74
+ $global = Join-Path $ScriptDir "bmad-speak.ps1"
75
+ if (Test-Path $global) { $BmadSpeak = $global }
76
+ }
77
+ if (-not $BmadSpeak) { exit 0 }
78
+
79
+ # --- Look up canonical agent ID from project manifest (fixes m2: UTF8 encoding) ---
80
+ $AgentId = $DisplayName # fallback
81
+ if ($ProjectRoot) {
82
+ $ManifestFile = Join-Path $ProjectRoot "_bmad\_config\agent-manifest.csv"
83
+ if (Test-Path $ManifestFile) {
84
+ $rows = Import-Csv $ManifestFile -Encoding UTF8
85
+ foreach ($row in $rows) {
86
+ if ($row.displayName -ieq $DisplayName) {
87
+ $AgentId = $row.name
88
+ break
89
+ }
90
+ }
91
+ }
92
+ }
93
+
94
+ # --- Apply verbosity truncation ---
95
+ $Verbosity = "medium"
96
+ $verbosityPaths = @(
97
+ (Join-Path $env:USERPROFILE ".claude\tts-verbosity.txt")
98
+ )
99
+ if ($ProjectRoot) {
100
+ $verbosityPaths = @((Join-Path $ProjectRoot ".claude\tts-verbosity.txt")) + $verbosityPaths
101
+ }
102
+ foreach ($p in $verbosityPaths) {
103
+ if (Test-Path $p) {
104
+ $v = (Get-Content $p -Raw -ErrorAction SilentlyContinue).Trim()
105
+ if ($v) { $Verbosity = $v; break }
106
+ }
107
+ }
108
+
109
+ switch ($Verbosity) {
110
+ "low" {
111
+ $sentences = [regex]::Split($ResponseText, '(?<=[.!?])\s+')
112
+ # Fall back to full text if no sentence-ending punctuation (fixes m1)
113
+ if ($sentences.Count -gt 0 -and $sentences[0]) { $ResponseText = $sentences[0] }
114
+ }
115
+ "medium" {
116
+ $sentences = [regex]::Split($ResponseText, '(?<=[.!?])\s+')
117
+ # Fall back to full text if no sentence-ending punctuation (fixes m1)
118
+ $truncated = ($sentences | Select-Object -First 2) -join " "
119
+ if ($truncated) { $ResponseText = $truncated }
120
+ }
121
+ # "high" = full text
122
+ }
123
+
124
+ # --- Pre-synthesize WAV before acquiring mutex so synthesis overlaps with previous agent's playback ---
125
+ $PreSynthWav = $null
126
+ try {
127
+ # Resolve agent voice from voice map
128
+ $VoiceMapLocal = if ($ProjectRoot) { Join-Path $ProjectRoot ".agentvibes\bmad-voice-map.json" } else { $null }
129
+ $VoiceMapGlobal = Join-Path $env:USERPROFILE ".agentvibes\bmad-voice-map.json"
130
+ $VoiceMapFile = if ($VoiceMapLocal -and (Test-Path $VoiceMapLocal)) { $VoiceMapLocal }
131
+ elseif (Test-Path $VoiceMapGlobal) { $VoiceMapGlobal }
132
+ else { $null }
133
+
134
+ $AgentVoiceName = $null
135
+ $SpeakerId = $null
136
+ $AgentPretext = $null
137
+ if ($VoiceMapFile) {
138
+ $vm = Get-Content $VoiceMapFile -Raw | ConvertFrom-Json
139
+ $profile = $vm.agents.$AgentId
140
+ if ($profile -and $profile.pretext) { $AgentPretext = $profile.pretext }
141
+ if ($profile -and $profile.voice) {
142
+ $raw = $profile.voice
143
+ if ($raw -match '::') {
144
+ $parts = $raw -split '::'
145
+ $AgentVoiceName = $parts[0]
146
+ $SpeakerName = if ($parts.Length -ge 2) { $parts[1] } else { "" }
147
+ # NOTE: The suffix number (e.g. "14" in "Yara-14") is a display disambiguator,
148
+ # NOT the piper speaker index. Must look up real index from speaker_id_map.
149
+ if ($SpeakerName) {
150
+ $VoicesDir = "$env:USERPROFILE\.claude\piper-voices"
151
+ $OnnxJsonPath = "$VoicesDir\$AgentVoiceName.onnx.json"
152
+ if (Test-Path $OnnxJsonPath) {
153
+ try {
154
+ $onnxData = Get-Content $OnnxJsonPath -Raw -Encoding UTF8 | ConvertFrom-Json
155
+ $speakerIdMap = $onnxData.speaker_id_map
156
+ if ($speakerIdMap -and $speakerIdMap.PSObject.Properties[$SpeakerName]) {
157
+ $SpeakerId = [string]$speakerIdMap.PSObject.Properties[$SpeakerName].Value
158
+ }
159
+ } catch { }
160
+ }
161
+ }
162
+ } else {
163
+ $AgentVoiceName = $raw
164
+ }
165
+ }
166
+ }
167
+
168
+ # Fallback: parse bmad-voices.md markdown table if no JSON voice map found
169
+ if (-not $AgentPretext -and $ProjectRoot) {
170
+ $VoicesMdPaths = @(
171
+ (Join-Path $ProjectRoot ".agentvibes\bmad\bmad-voices.md"),
172
+ (Join-Path $env:USERPROFILE ".agentvibes\bmad\bmad-voices.md")
173
+ )
174
+ foreach ($mdPath in $VoicesMdPaths) {
175
+ if (-not (Test-Path $mdPath)) { continue }
176
+ $mdLines = Get-Content $mdPath -Encoding UTF8
177
+ # Strip bmad-agent- prefix for matching (manifest uses bmad-agent-pm, table uses pm)
178
+ $shortId = $AgentId -replace '^bmad-agent-', ''
179
+ foreach ($mdLine in $mdLines) {
180
+ if ($mdLine -notmatch '^\|') { continue }
181
+ if ($mdLine -match '^\|-') { continue } # separator row
182
+ if ($mdLine -match 'Agent ID') { continue } # header row
183
+ $cols = $mdLine -split '\|' | ForEach-Object { $_.Trim() }
184
+ # cols: [0]=empty, [1]=Agent ID, [2]=Agent Name, [3]=Intro, [4]=Piper TTS Voice, [5]=Piper Voice, [6]=Personality
185
+ if ($cols.Count -lt 6) { continue }
186
+ $tableId = $cols[1]
187
+ $tableName = $cols[2]
188
+ if ($tableId -ieq $shortId -or $tableId -ieq $AgentId -or $tableName -like "*$DisplayName*") {
189
+ if ($cols[3]) { $AgentPretext = $cols[3] }
190
+ # Use Piper Voice column (index 5) for piper provider
191
+ if (-not $AgentVoiceName -and $cols[5]) { $AgentVoiceName = $cols[5] }
192
+ if ($cols.Count -ge 7 -and $cols[6] -and $cols[6] -ine 'normal') {
193
+ # personality available for bmad-speak.ps1 downstream
194
+ }
195
+ break
196
+ }
197
+ }
198
+ if ($AgentPretext) { break }
199
+ }
200
+ }
201
+
202
+ # Locate piper
203
+ $PiperExe = "$env:LOCALAPPDATA\Programs\Piper\piper.exe"
204
+ if (-not (Test-Path $PiperExe)) {
205
+ $found = Get-Command piper.exe -ErrorAction SilentlyContinue
206
+ if ($found) { $PiperExe = $found.Source }
207
+ }
208
+
209
+ if (Test-Path $PiperExe) {
210
+ $VoicesDir = "$env:USERPROFILE\.claude\piper-voices"
211
+ # Fall back to first available voice if agent voice not found
212
+ if (-not $AgentVoiceName) {
213
+ $first = Get-ChildItem $VoicesDir -Filter "*.onnx" -ErrorAction SilentlyContinue | Select-Object -First 1
214
+ if ($first) { $AgentVoiceName = $first.BaseName }
215
+ }
216
+ if ($AgentVoiceName -and ($AgentVoiceName -match '^[a-zA-Z0-9_\-\.]+$')) {
217
+ $VoiceModel = Join-Path $VoicesDir "$AgentVoiceName.onnx"
218
+ if (Test-Path $VoiceModel) {
219
+ $AudioDir = "$env:USERPROFILE\.claude\audio"
220
+ if (-not (Test-Path $AudioDir)) { New-Item -ItemType Directory -Path $AudioDir -Force | Out-Null }
221
+ $PreSynthWav = Join-Path $AudioDir "tts-presynth-$([System.IO.Path]::GetRandomFileName() -replace '\..*').wav"
222
+ $piperArgs = @("--model", $VoiceModel, "--output-file", $PreSynthWav)
223
+ if ($SpeakerId) { $piperArgs += @("--speaker", $SpeakerId) }
224
+ # Include pretext in pre-synthesis so it's spoken — bmad-speak.ps1 will
225
+ # skip synthesis (AGENTVIBES_PRESYNTHESIZED_WAV set) so pretext must be here.
226
+ $PreSynthText = if ($AgentPretext) { "$AgentPretext. $ResponseText" } else { $ResponseText }
227
+ $PreSynthText | & $PiperExe @piperArgs 2>$null
228
+ if (-not (Test-Path $PreSynthWav) -or (Get-Item $PreSynthWav).Length -eq 0) {
229
+ $PreSynthWav = $null
230
+ }
231
+ }
232
+ }
233
+ }
234
+ } catch {
235
+ $PreSynthWav = $null # degrade gracefully — will synthesize inside mutex instead
236
+ }
237
+
238
+ # --- Speak with queue serialization (named mutex, cross-process) ---
239
+ $mutex = New-Object System.Threading.Mutex($false, "AgentVibesPartyModeTTSQueue")
240
+ try {
241
+ $acquired = $false
242
+ try {
243
+ # WaitOne throws AbandonedMutexException if prior process crashed while holding it.
244
+ # That exception means we DID acquire the mutex — treat it as success (fixes M2).
245
+ # 600s timeout covers worst-case party-mode queue depth (9 agents x
246
+ # ~60s of speech each). Shorter timeouts silently dropped the last
247
+ # agents to speak. play-tts.ps1 bounds true stuck processes via its
248
+ # playback watchdog, so waiting longer here is safe.
249
+ $acquired = $mutex.WaitOne(600000)
250
+ } catch [System.Threading.AbandonedMutexException] {
251
+ $acquired = $true # abandoned = we now own it
252
+ }
253
+
254
+ if ($acquired) {
255
+ try {
256
+ # Pass pre-synthesized WAV path so play-tts.ps1 skips synthesis (reduces gap between agents)
257
+ if ($PreSynthWav) { $env:AGENTVIBES_PRESYNTHESIZED_WAV = $PreSynthWav }
258
+ # Pass positional args directly after -File (spaces handled by quoting via array)
259
+ & powershell -NoProfile -ExecutionPolicy Bypass -File $BmadSpeak $AgentId $ResponseText
260
+ } finally {
261
+ $env:AGENTVIBES_PRESYNTHESIZED_WAV = ""
262
+ if ($PreSynthWav -and (Test-Path $PreSynthWav)) {
263
+ Remove-Item $PreSynthWav -Force -ErrorAction SilentlyContinue
264
+ }
265
+ $mutex.ReleaseMutex()
266
+ }
267
+ } else {
268
+ # Timed out — log to stderr so it's visible in hook error output (fixes M6)
269
+ [Console]::Error.WriteLine("[AgentVibes] Party mode TTS queue timeout for agent: $AgentId")
270
+ }
271
+ } finally {
272
+ $mutex.Close()
273
+ }
274
+
275
+ } catch {
276
+ # Silently exit — never block Claude
277
+ exit 0
278
+ }