agentvibes 5.3.0 → 5.4.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 (219) 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 +4 -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-speak-enhanced.sh +0 -0
  88. package/.claude/hooks/bmad-speak.sh +6 -13
  89. package/.claude/hooks/bmad-tts-injector.sh +0 -0
  90. package/.claude/hooks/bmad-voice-manager.sh +0 -0
  91. package/.claude/hooks/clawdbot-receiver-SECURE.sh +25 -23
  92. package/.claude/hooks/clawdbot-receiver.sh +4 -28
  93. package/.claude/hooks/clean-audio-cache.sh +0 -0
  94. package/.claude/hooks/cleanup-cache.sh +0 -0
  95. package/.claude/hooks/configure-rdp-mode.sh +0 -0
  96. package/.claude/hooks/download-extra-voices.sh +0 -0
  97. package/.claude/hooks/effects-manager.sh +0 -0
  98. package/.claude/hooks/github-star-reminder.sh +0 -0
  99. package/.claude/hooks/language-manager.sh +0 -0
  100. package/.claude/hooks/learn-manager.sh +0 -0
  101. package/.claude/hooks/macos-voice-manager.sh +0 -0
  102. package/.claude/hooks/migrate-background-music.sh +0 -0
  103. package/.claude/hooks/migrate-to-agentvibes.sh +0 -0
  104. package/.claude/hooks/optimize-background-music.sh +0 -0
  105. package/.claude/hooks/personality-manager.sh +0 -0
  106. package/.claude/hooks/piper-download-voices.sh +0 -0
  107. package/.claude/hooks/piper-installer.sh +1 -1
  108. package/.claude/hooks/piper-multispeaker-registry.sh +0 -0
  109. package/.claude/hooks/piper-voice-manager.sh +0 -0
  110. package/.claude/hooks/play-tts-enhanced.sh +0 -0
  111. package/.claude/hooks/play-tts-macos.sh +6 -12
  112. package/.claude/hooks/play-tts-piper.sh +50 -79
  113. package/.claude/hooks/play-tts-soprano.sh +9 -43
  114. package/.claude/hooks/play-tts-ssh-remote.sh +43 -215
  115. package/.claude/hooks/play-tts-termux-ssh.sh +0 -0
  116. package/.claude/hooks/play-tts.sh +31 -31
  117. package/.claude/hooks/post-response.sh +41 -0
  118. package/.claude/hooks/prepare-release.sh +0 -0
  119. package/.claude/hooks/provider-commands.sh +0 -0
  120. package/.claude/hooks/provider-manager.sh +0 -0
  121. package/.claude/hooks/replay-target-audio.sh +0 -0
  122. package/.claude/hooks/requirements.txt +6 -6
  123. package/.claude/hooks/sentiment-manager.sh +0 -0
  124. package/.claude/hooks/session-start-tts.sh +56 -39
  125. package/.claude/hooks/soprano-gradio-synth.py +139 -139
  126. package/.claude/hooks/speed-manager.sh +0 -0
  127. package/.claude/hooks/stop.sh +63 -0
  128. package/.claude/hooks/termux-installer.sh +0 -0
  129. package/.claude/hooks/translate-manager.sh +0 -0
  130. package/.claude/hooks/translator.py +237 -237
  131. package/.claude/hooks/tts-queue-worker.sh +0 -0
  132. package/.claude/hooks/tts-queue.sh +0 -0
  133. package/.claude/hooks/verbosity-manager.sh +0 -0
  134. package/.claude/hooks/voice-manager.sh +26 -4
  135. package/.claude/hooks-windows/audio-cache-utils.ps1 +119 -119
  136. package/.claude/hooks-windows/bmad-party-speak.ps1 +278 -278
  137. package/.claude/hooks-windows/bmad-speak.ps1 +264 -264
  138. package/.claude/hooks-windows/clean-audio-cache.ps1 +53 -53
  139. package/.claude/hooks-windows/effects-manager.ps1 +294 -294
  140. package/.claude/hooks-windows/language-manager.ps1 +193 -193
  141. package/.claude/hooks-windows/learn-manager.ps1 +241 -241
  142. package/.claude/hooks-windows/personality-manager.ps1 +266 -266
  143. package/.claude/hooks-windows/play-tts-soprano.ps1 +5 -5
  144. package/.claude/hooks-windows/play-tts-termux-ssh.ps1 +138 -138
  145. package/.claude/hooks-windows/play-tts-windows-piper.ps1 +164 -0
  146. package/.claude/hooks-windows/play-tts-windows-sapi.ps1 +108 -0
  147. package/.claude/hooks-windows/play-tts.ps1 +104 -513
  148. package/.claude/hooks-windows/provider-manager.ps1 +158 -192
  149. package/.claude/hooks-windows/session-start-tts.ps1 +55 -46
  150. package/.claude/hooks-windows/soprano-gradio-synth.py +153 -153
  151. package/.claude/hooks-windows/speed-manager.ps1 +166 -166
  152. package/.claude/hooks-windows/voice-manager-windows.ps1 +176 -260
  153. package/.claude/output-styles/agent-vibes.md +202 -202
  154. package/.claude/personalities/angry.md +14 -14
  155. package/.claude/personalities/annoying.md +14 -14
  156. package/.claude/personalities/crass.md +14 -14
  157. package/.claude/personalities/dramatic.md +14 -14
  158. package/.claude/personalities/dry-humor.md +50 -50
  159. package/.claude/personalities/flirty.md +20 -20
  160. package/.claude/personalities/funny.md +14 -14
  161. package/.claude/personalities/grandpa.md +32 -32
  162. package/.claude/personalities/millennial.md +14 -14
  163. package/.claude/personalities/moody.md +14 -14
  164. package/.claude/personalities/normal.md +16 -16
  165. package/.claude/personalities/pirate.md +14 -14
  166. package/.claude/personalities/poetic.md +14 -14
  167. package/.claude/personalities/professional.md +14 -14
  168. package/.claude/personalities/rapper.md +55 -55
  169. package/.claude/personalities/robot.md +14 -14
  170. package/.claude/personalities/sarcastic.md +38 -38
  171. package/.claude/personalities/sassy.md +14 -14
  172. package/.claude/personalities/surfer-dude.md +14 -14
  173. package/.claude/personalities/zen.md +14 -14
  174. package/.claude/piper-voices-dir.txt +1 -0
  175. package/.claude/settings.json +25 -15
  176. package/.claude/verbosity.txt +1 -1
  177. package/.clawdbot/README.md +105 -105
  178. package/.clawdbot/skill/SKILL.md +149 -145
  179. package/.mcp.json +30 -11
  180. package/CLAUDE.md +170 -215
  181. package/README.md +206 -525
  182. package/RELEASE_NOTES.md +1132 -1976
  183. package/WINDOWS-SETUP.md +208 -208
  184. package/bin/agent-vibes +0 -0
  185. package/bin/agentvibes-voice-browser.js +64 -1289
  186. package/bin/agentvibes.js +0 -0
  187. package/bin/ensure-soprano-running.sh +43 -0
  188. package/bin/mcp-server.js +121 -121
  189. package/bin/mcp-server.sh +0 -0
  190. package/bin/test-bmad-pr +78 -78
  191. package/mcp-server/QUICK_START.md +203 -203
  192. package/mcp-server/README.md +345 -345
  193. package/mcp-server/WINDOWS_SETUP.md +260 -260
  194. package/mcp-server/docs/troubleshooting-audio.md +313 -313
  195. package/mcp-server/examples/claude_desktop_config.json +11 -11
  196. package/mcp-server/examples/claude_desktop_config_piper.json +9 -9
  197. package/mcp-server/examples/custom_instructions.md +169 -169
  198. package/mcp-server/install-deps.js +130 -130
  199. package/mcp-server/pyproject.toml +52 -52
  200. package/mcp-server/requirements.txt +2 -2
  201. package/mcp-server/server.py +1451 -1578
  202. package/mcp-server/test_server.py +395 -395
  203. package/package.json +1 -3
  204. package/setup-windows.ps1 +815 -815
  205. package/src/installer.js +42 -5
  206. package/templates/agentvibes-receiver.sh +158 -483
  207. package/templates/audio/welcome-music.mp3 +0 -0
  208. package/.agentvibes/bmad-voice-map.json +0 -104
  209. package/.agentvibes/copilot-sessions.log +0 -4
  210. package/.claude/config/audio-effects-bmad.cfg +0 -50
  211. package/.claude/config/background-music-enabled.txt +0 -1
  212. package/.claude/config/intro-text.txt +0 -1
  213. package/.claude/config/personality.txt +0 -1
  214. package/.claude/config/piper-speech-rate.txt +0 -4
  215. package/.claude/config/piper-target-speech-rate.txt +0 -1
  216. package/.claude/config/reverb-level.txt +0 -1
  217. package/.claude/config/tts-target-speech-rate.txt +0 -1
  218. package/voice-assignments.json +0 -8245
  219. /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
+ }