agentvibes 4.2.0 → 4.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (219) hide show
  1. package/.agentvibes/bmad/bmad-voices.md +69 -69
  2. package/.agentvibes/config.json +12 -0
  3. package/.claude/activation-instructions +54 -54
  4. package/.claude/audio/tracks/README.md +52 -52
  5. package/.claude/commands/agent-vibes/add.md +21 -21
  6. package/.claude/commands/agent-vibes/agent-vibes.md +101 -101
  7. package/.claude/commands/agent-vibes/agent.md +79 -79
  8. package/.claude/commands/agent-vibes/background-music.md +111 -111
  9. package/.claude/commands/agent-vibes/bmad.md +198 -198
  10. package/.claude/commands/agent-vibes/clean.md +18 -18
  11. package/.claude/commands/agent-vibes/cleanup.md +18 -18
  12. package/.claude/commands/agent-vibes/commands.json +145 -145
  13. package/.claude/commands/agent-vibes/effects.md +97 -97
  14. package/.claude/commands/agent-vibes/get.md +9 -9
  15. package/.claude/commands/agent-vibes/hide.md +91 -91
  16. package/.claude/commands/agent-vibes/language.md +23 -23
  17. package/.claude/commands/agent-vibes/learn.md +67 -67
  18. package/.claude/commands/agent-vibes/list.md +13 -13
  19. package/.claude/commands/agent-vibes/mute.md +37 -37
  20. package/.claude/commands/agent-vibes/preview.md +17 -17
  21. package/.claude/commands/agent-vibes/provider.md +68 -68
  22. package/.claude/commands/agent-vibes/replay-target.md +14 -14
  23. package/.claude/commands/agent-vibes/sample.md +12 -12
  24. package/.claude/commands/agent-vibes/set-favorite-voice.md +84 -84
  25. package/.claude/commands/agent-vibes/set-pretext.md +65 -65
  26. package/.claude/commands/agent-vibes/set-speed.md +41 -41
  27. package/.claude/commands/agent-vibes/show.md +84 -84
  28. package/.claude/commands/agent-vibes/switch.md +87 -87
  29. package/.claude/commands/agent-vibes/target-voice.md +26 -26
  30. package/.claude/commands/agent-vibes/target.md +30 -30
  31. package/.claude/commands/agent-vibes/translate.md +68 -68
  32. package/.claude/commands/agent-vibes/unmute.md +45 -45
  33. package/.claude/commands/agent-vibes/verbosity.md +89 -89
  34. package/.claude/commands/agent-vibes/whoami.md +7 -7
  35. package/.claude/commands/agent-vibes-bmad-voices.md +117 -117
  36. package/.claude/commands/agent-vibes-rdp.md +24 -24
  37. package/.claude/config/agentvibes.json +1 -0
  38. package/.claude/config/audio-effects.cfg +2 -2
  39. package/.claude/config/audio-effects.cfg.sample +52 -52
  40. package/.claude/config/background-music-volume.txt +1 -0
  41. package/.claude/config/intro-text.txt +1 -0
  42. package/.claude/config/piper-speech-rate.txt +4 -0
  43. package/.claude/config/piper-target-speech-rate.txt +1 -0
  44. package/.claude/config/reverb-level.txt +1 -0
  45. package/.claude/config/tts-speech-rate.txt +4 -0
  46. package/.claude/config/tts-target-speech-rate.txt +1 -0
  47. package/.claude/docs/TERMUX_SETUP.md +408 -408
  48. package/.claude/github-star-reminder.txt +1 -1
  49. package/.claude/hooks/README-TTS-QUEUE.md +135 -135
  50. package/.claude/hooks/audio-cache-utils.sh +246 -246
  51. package/.claude/hooks/audio-processor.sh +433 -433
  52. package/.claude/hooks/background-music-manager.sh +404 -404
  53. package/.claude/hooks/bmad-speak-enhanced.sh +165 -165
  54. package/.claude/hooks/bmad-speak.sh +269 -269
  55. package/.claude/hooks/bmad-tts-injector.sh +568 -568
  56. package/.claude/hooks/bmad-voice-manager.sh +928 -928
  57. package/.claude/hooks/clawdbot-receiver-SECURE.sh +129 -129
  58. package/.claude/hooks/clawdbot-receiver.sh +107 -107
  59. package/.claude/hooks/clean-audio-cache.sh +22 -22
  60. package/.claude/hooks/cleanup-cache.sh +106 -106
  61. package/.claude/hooks/configure-rdp-mode.sh +137 -137
  62. package/.claude/hooks/download-extra-voices.sh +244 -244
  63. package/.claude/hooks/effects-manager.sh +268 -268
  64. package/.claude/hooks/github-star-reminder.sh +154 -154
  65. package/.claude/hooks/language-manager.sh +362 -362
  66. package/.claude/hooks/learn-manager.sh +492 -492
  67. package/.claude/hooks/macos-voice-manager.sh +205 -205
  68. package/.claude/hooks/migrate-background-music.sh +125 -125
  69. package/.claude/hooks/migrate-to-agentvibes.sh +161 -161
  70. package/.claude/hooks/optimize-background-music.sh +87 -87
  71. package/.claude/hooks/path-resolver.sh +60 -60
  72. package/.claude/hooks/personality-manager.sh +448 -448
  73. package/.claude/hooks/piper-download-voices.sh +225 -225
  74. package/.claude/hooks/piper-installer.sh +292 -292
  75. package/.claude/hooks/piper-multispeaker-registry.sh +171 -171
  76. package/.claude/hooks/piper-voice-manager.sh +24 -3
  77. package/.claude/hooks/play-tts-agentvibes-receiver-for-voiceless-connections.sh +90 -90
  78. package/.claude/hooks/play-tts-enhanced.sh +105 -105
  79. package/.claude/hooks/play-tts-macos.sh +368 -368
  80. package/.claude/hooks/play-tts-piper.sh +679 -679
  81. package/.claude/hooks/play-tts-soprano.sh +356 -356
  82. package/.claude/hooks/play-tts-ssh-remote.sh +167 -167
  83. package/.claude/hooks/play-tts-termux-ssh.sh +169 -169
  84. package/.claude/hooks/play-tts.sh +301 -301
  85. package/.claude/hooks/prepare-release.sh +54 -54
  86. package/.claude/hooks/provider-commands.sh +617 -617
  87. package/.claude/hooks/provider-manager.sh +399 -399
  88. package/.claude/hooks/replay-target-audio.sh +95 -95
  89. package/.claude/hooks/requirements.txt +6 -6
  90. package/.claude/hooks/sentiment-manager.sh +201 -201
  91. package/.claude/hooks/session-start-tts.sh +81 -81
  92. package/.claude/hooks/soprano-gradio-synth.py +139 -139
  93. package/.claude/hooks/speed-manager.sh +291 -291
  94. package/.claude/hooks/stop-tts.sh +84 -84
  95. package/.claude/hooks/termux-installer.sh +261 -261
  96. package/.claude/hooks/translate-manager.sh +341 -341
  97. package/.claude/hooks/translator.py +237 -237
  98. package/.claude/hooks/tts-queue-worker.sh +145 -145
  99. package/.claude/hooks/tts-queue.sh +165 -165
  100. package/.claude/hooks/verbosity-manager.sh +178 -178
  101. package/.claude/hooks/voice-manager.sh +548 -548
  102. package/.claude/hooks-windows/audio-cache-utils.ps1 +119 -119
  103. package/.claude/hooks-windows/background-music-manager.ps1 +348 -0
  104. package/.claude/hooks-windows/clean-audio-cache.ps1 +53 -0
  105. package/.claude/hooks-windows/download-extra-voices.ps1 +185 -0
  106. package/.claude/hooks-windows/effects-manager.ps1 +294 -0
  107. package/.claude/hooks-windows/language-manager.ps1 +193 -0
  108. package/.claude/hooks-windows/learn-manager.ps1 +241 -0
  109. package/.claude/hooks-windows/personality-manager.ps1 +266 -0
  110. package/.claude/hooks-windows/play-tts-piper.ps1 +209 -0
  111. package/.claude/hooks-windows/play-tts-sapi.ps1 +108 -0
  112. package/.claude/hooks-windows/play-tts-soprano.ps1 +159 -158
  113. package/.claude/hooks-windows/play-tts-windows-piper.ps1 +50 -5
  114. package/.claude/hooks-windows/play-tts-windows-sapi.ps1 +108 -108
  115. package/.claude/hooks-windows/play-tts.ps1 +344 -266
  116. package/.claude/hooks-windows/provider-manager.ps1 +29 -10
  117. package/.claude/hooks-windows/session-start-tts.ps1 +124 -124
  118. package/.claude/hooks-windows/soprano-gradio-synth.py +153 -153
  119. package/.claude/hooks-windows/speed-manager.ps1 +166 -0
  120. package/.claude/hooks-windows/verbosity-manager.ps1 +119 -0
  121. package/.claude/hooks-windows/voice-manager-windows.ps1 +92 -8
  122. package/.claude/output-styles/agent-vibes.md +202 -202
  123. package/.claude/personalities/angry.md +14 -14
  124. package/.claude/personalities/annoying.md +14 -14
  125. package/.claude/personalities/crass.md +14 -14
  126. package/.claude/personalities/dramatic.md +14 -14
  127. package/.claude/personalities/dry-humor.md +50 -50
  128. package/.claude/personalities/flirty.md +20 -20
  129. package/.claude/personalities/funny.md +14 -14
  130. package/.claude/personalities/grandpa.md +32 -32
  131. package/.claude/personalities/millennial.md +14 -14
  132. package/.claude/personalities/moody.md +14 -14
  133. package/.claude/personalities/normal.md +16 -16
  134. package/.claude/personalities/pirate.md +14 -14
  135. package/.claude/personalities/poetic.md +14 -14
  136. package/.claude/personalities/professional.md +14 -14
  137. package/.claude/personalities/rapper.md +55 -55
  138. package/.claude/personalities/robot.md +14 -14
  139. package/.claude/personalities/sarcastic.md +38 -38
  140. package/.claude/personalities/sassy.md +14 -14
  141. package/.claude/personalities/surfer-dude.md +14 -14
  142. package/.claude/personalities/zen.md +14 -14
  143. package/.claude/settings.json +15 -15
  144. package/.claude/verbosity.txt +1 -1
  145. package/.clawdbot/README.md +105 -105
  146. package/.clawdbot/skill/SKILL.md +241 -241
  147. package/.mcp.json +12 -0
  148. package/CLAUDE.md +170 -170
  149. package/README.md +2029 -2007
  150. package/RELEASE_NOTES.md +1310 -1203
  151. package/WINDOWS-SETUP.md +208 -208
  152. package/bin/agent-vibes +39 -39
  153. package/bin/agentvibes-voice-browser.js +1840 -1840
  154. package/bin/agentvibes.js +48 -2
  155. package/bin/mcp-server.js +121 -121
  156. package/bin/mcp-server.sh +206 -206
  157. package/bin/test-bmad-pr +78 -78
  158. package/mcp-server/QUICK_START.md +203 -203
  159. package/mcp-server/README.md +345 -345
  160. package/mcp-server/WINDOWS_SETUP.md +260 -260
  161. package/mcp-server/docs/troubleshooting-audio.md +313 -313
  162. package/mcp-server/examples/claude_desktop_config.json +11 -11
  163. package/mcp-server/examples/claude_desktop_config_piper.json +9 -9
  164. package/mcp-server/examples/custom_instructions.md +169 -169
  165. package/mcp-server/install-deps.js +130 -130
  166. package/mcp-server/pyproject.toml +52 -52
  167. package/mcp-server/requirements.txt +2 -2
  168. package/mcp-server/server.py +1465 -1453
  169. package/mcp-server/test_server.py +395 -395
  170. package/mcp-server/test_windows_script_parity.py +336 -0
  171. package/package.json +110 -110
  172. package/setup-windows.ps1 +815 -815
  173. package/src/bmad-detector.js +71 -71
  174. package/src/cli/list-personalities.js +110 -110
  175. package/src/cli/list-voices.js +114 -114
  176. package/src/commands/bmad-voices.js +394 -394
  177. package/src/commands/install-mcp.js +476 -476
  178. package/src/console/app.js +824 -824
  179. package/src/console/audio-env.js +20 -1
  180. package/src/console/brand-colors.js +13 -13
  181. package/src/console/constants/personalities.js +44 -44
  182. package/src/console/footer-config.js +50 -50
  183. package/src/console/modals/modal-overlay.js +247 -247
  184. package/src/console/navigation.js +62 -62
  185. package/src/console/tabs/agents-tab.js +1684 -1516
  186. package/src/console/tabs/help-tab.js +261 -261
  187. package/src/console/tabs/install-tab.js +1007 -991
  188. package/src/console/tabs/music-tab.js +22 -8
  189. package/src/console/tabs/placeholder-tab.js +53 -53
  190. package/src/console/tabs/readme-tab.js +267 -267
  191. package/src/console/tabs/receiver-tab.js +1472 -1212
  192. package/src/console/tabs/settings-tab.js +208 -84
  193. package/src/console/tabs/voices-tab.js +100 -21
  194. package/src/console/widgets/destroy-list.js +25 -25
  195. package/src/console/widgets/format-utils.js +89 -89
  196. package/src/console/widgets/notice.js +55 -55
  197. package/src/console/widgets/personality-picker.js +185 -185
  198. package/src/console/widgets/reverb-picker.js +94 -94
  199. package/src/console/widgets/track-picker.js +285 -285
  200. package/src/installer/music-file-input.js +304 -304
  201. package/src/installer.js +5895 -5829
  202. package/src/services/agent-voice-store.js +423 -423
  203. package/src/services/config-service.js +264 -264
  204. package/src/services/navigation-service.js +123 -123
  205. package/src/services/provider-service.js +143 -132
  206. package/src/services/verbosity-service.js +157 -157
  207. package/src/utils/audio-duration-validator.js +298 -298
  208. package/src/utils/audio-format-validator.js +277 -277
  209. package/src/utils/dependency-checker.js +469 -466
  210. package/src/utils/file-ownership-verifier.js +358 -358
  211. package/src/utils/list-formatter.js +194 -194
  212. package/src/utils/music-file-validator.js +285 -285
  213. package/src/utils/preview-list-prompt.js +136 -136
  214. package/src/utils/provider-validator.js +96 -12
  215. package/src/utils/secure-music-storage.js +412 -412
  216. package/templates/agentvibes-receiver.sh +482 -482
  217. package/templates/audio/welcome-music.mp3 +0 -0
  218. package/voice-assignments.json +8244 -8244
  219. package/.claude/config/background-music-position.txt +0 -1
@@ -1,266 +1,344 @@
1
- #
2
- # File: .claude/hooks-windows/play-tts.ps1
3
- #
4
- # AgentVibes - Windows TTS Router
5
- # Delegates to active provider (windows-sapi or windows-piper)
6
- #
7
-
8
- param(
9
- [Parameter(Mandatory = $true, Position = 0)]
10
- [string]$Text,
11
-
12
- [Parameter(Mandatory = $false, Position = 1)]
13
- [string]$VoiceOverride
14
- )
15
-
16
- # Configuration paths
17
- # First check if we're running from a project directory with .claude
18
- $ScriptPath = Split-Path -Parent $MyInvocation.MyCommand.Path
19
- $ProjectClaudeDir = Join-Path (Split-Path -Parent (Split-Path -Parent $ScriptPath)) ".claude"
20
-
21
- # Use project .claude if running from there, otherwise use user profile
22
- if (Test-Path $ProjectClaudeDir) {
23
- $ClaudeDir = $ProjectClaudeDir
24
- } else {
25
- $ClaudeDir = "$env:USERPROFILE\.claude"
26
- }
27
-
28
- $HooksDir = "$ClaudeDir\hooks-windows"
29
- $ProviderFile = "$ClaudeDir\tts-provider.txt"
30
- $MuteFile = "$ClaudeDir\tts-muted.txt"
31
-
32
- # Check if TTS is muted
33
- if (Test-Path $MuteFile) {
34
- $muteStatus = Get-Content $MuteFile -Raw
35
- if ($muteStatus.Trim() -eq "true") {
36
- exit 0
37
- }
38
- }
39
-
40
- # Determine active provider
41
- $ActiveProvider = "windows-sapi"
42
- if (Test-Path $ProviderFile) {
43
- $ActiveProvider = (Get-Content $ProviderFile -Raw).Trim()
44
- }
45
-
46
- # Validate and get provider script
47
- $ProviderScript = ""
48
-
49
- switch ($ActiveProvider) {
50
- "windows-sapi" {
51
- $ProviderScript = "$HooksDir\play-tts-windows-sapi.ps1"
52
- }
53
- "windows-piper" {
54
- $ProviderScript = "$HooksDir\play-tts-windows-piper.ps1"
55
- }
56
- "soprano" {
57
- $ProviderScript = "$HooksDir\play-tts-soprano.ps1"
58
- }
59
- default {
60
- Write-Host "[ERROR] Unknown provider: $ActiveProvider" -ForegroundColor Red
61
- Write-Host "Use: .\provider-manager.ps1 list" -ForegroundColor Yellow
62
- exit 1
63
- }
64
- }
65
-
66
- # Check if provider script exists
67
- if (-not (Test-Path $ProviderScript)) {
68
- Write-Host "[ERROR] Provider script not found: $ProviderScript" -ForegroundColor Red
69
- exit 1
70
- }
71
-
72
- # Check if background music is enabled
73
- $ConfigDir = "$ClaudeDir\config"
74
- $BgEnabled = $false
75
- $BgEnabledFile = "$ConfigDir\background-music-enabled.txt"
76
- if (Test-Path $BgEnabledFile) {
77
- $BgEnabled = (Get-Content $BgEnabledFile -Raw).Trim() -eq "true"
78
- }
79
-
80
- # Check if reverb is enabled (allowlist validation)
81
- $ReverbLevel = "off"
82
- $ReverbFile = "$ConfigDir\reverb-level.txt"
83
- if (Test-Path $ReverbFile) {
84
- $reverbVal = (Get-Content $ReverbFile -Raw).Trim()
85
- if ($reverbVal -in @("off", "light", "medium", "heavy", "cathedral")) {
86
- $ReverbLevel = $reverbVal
87
- }
88
- }
89
- $HasReverb = $ReverbLevel -ne "off"
90
-
91
- # Check ffmpeg availability for background music mixing or reverb
92
- $HasFfmpeg = $false
93
- if ($BgEnabled -or $HasReverb) {
94
- try {
95
- $null = Get-Command ffmpeg -ErrorAction Stop
96
- $HasFfmpeg = $true
97
- } catch {}
98
- }
99
-
100
- # If background music or reverb enabled and ffmpeg available, tell provider to skip playback
101
- if (($BgEnabled -or $HasReverb) -and $HasFfmpeg) {
102
- $env:AGENTVIBES_NO_PLAY = "1"
103
- }
104
-
105
- # Call the provider script
106
- try {
107
- if ($VoiceOverride) {
108
- $providerOutput = & $ProviderScript $Text $VoiceOverride 2>&1
109
- }
110
- else {
111
- $providerOutput = & $ProviderScript $Text 2>&1
112
- }
113
- # Show provider output
114
- $providerOutput | ForEach-Object { Write-Host $_ }
115
- }
116
- catch {
117
- Write-Host "[ERROR] TTS Error: $_" -ForegroundColor Red
118
- $env:AGENTVIBES_NO_PLAY = $null
119
- exit 1
120
- }
121
-
122
- # Apply reverb and/or mix with background music
123
- if (($BgEnabled -or $HasReverb) -and $HasFfmpeg) {
124
- $env:AGENTVIBES_NO_PLAY = $null
125
-
126
- # Find the most recent TTS wav file
127
- $AudioDir = "$ClaudeDir\audio"
128
- $RecentWav = Get-ChildItem -Path $AudioDir -Filter "tts-*.wav" -ErrorAction SilentlyContinue |
129
- Sort-Object LastWriteTime -Descending | Select-Object -First 1
130
-
131
- if ($RecentWav -and $RecentWav.Length -gt 0) {
132
- $voicePath = $RecentWav.FullName
133
-
134
- # Apply reverb if configured
135
- if ($HasReverb) {
136
- $reverbFilter = switch ($ReverbLevel) {
137
- "light" { "aecho=0.8:0.88:60:0.4" }
138
- "medium" { "aecho=0.8:0.88:60|120:0.4|0.3" }
139
- "heavy" { "aecho=0.8:0.88:60|120|180:0.4|0.3|0.2" }
140
- "cathedral" { "aecho=0.8:0.88:100|200|300|400:0.3|0.25|0.2|0.15" }
141
- default { "" }
142
- }
143
- if ($reverbFilter) {
144
- $reverbedFile = "$AudioDir\tts-reverbed.wav"
145
- $reverbArgs = "-y -i `"$voicePath`" -af `"$reverbFilter`" `"$reverbedFile`""
146
- $proc = Start-Process -FilePath "ffmpeg" -ArgumentList $reverbArgs -NoNewWindow -Wait -PassThru -RedirectStandardError "NUL"
147
- if ($proc.ExitCode -eq 0 -and (Test-Path $reverbedFile)) {
148
- $voicePath = $reverbedFile
149
- }
150
- }
151
- }
152
-
153
- # Mix with background music if enabled
154
- if ($BgEnabled) {
155
- # Get background track - default to bachata, or read from config
156
- $TracksDir = "$ClaudeDir\audio\tracks"
157
- $DefaultTrack = "agent_vibes_bachata_v1_loop.mp3"
158
- $DefaultTrackFile = "$ConfigDir\background-music-default.txt"
159
- if (Test-Path $DefaultTrackFile) {
160
- $configTrack = (Get-Content $DefaultTrackFile -Raw).Trim()
161
- # Validate: filename only, no path separators or traversal
162
- if ($configTrack -and $configTrack -match '^[a-zA-Z0-9_\-\.]+$') {
163
- $DefaultTrack = $configTrack
164
- }
165
- }
166
- $BgTrackPath = Join-Path $TracksDir $DefaultTrack
167
- # Path containment: verify resolved path stays within tracks directory
168
- $ResolvedBgTrack = [System.IO.Path]::GetFullPath($BgTrackPath)
169
- $ResolvedTracksDir = [System.IO.Path]::GetFullPath($TracksDir)
170
- if (-not $ResolvedBgTrack.StartsWith($ResolvedTracksDir + [System.IO.Path]::DirectorySeparatorChar)) {
171
- $BgTrackPath = Join-Path $TracksDir "agent_vibes_bachata_v1_loop.mp3"
172
- }
173
-
174
- # Get volume (default 0.25)
175
- $BgVolume = "0.25"
176
- $VolumeFile = "$ConfigDir\background-music-volume.txt"
177
- if (Test-Path $VolumeFile) {
178
- $vol = (Get-Content $VolumeFile -Raw).Trim()
179
- if ($vol -match '^\d+\.?\d*$') { $BgVolume = $vol }
180
- }
181
-
182
- if (Test-Path $BgTrackPath) {
183
- $MixedFile = $RecentWav.FullName -replace '\.wav$', '-mixed.wav'
184
-
185
- try {
186
- # Get voice duration to calculate total length
187
- $probArgs = "-v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 `"$voicePath`""
188
- $durationProc = Start-Process -FilePath "ffprobe" -ArgumentList $probArgs -NoNewWindow -Wait -PassThru -RedirectStandardError "NUL" -RedirectStandardOutput "$env:TEMP\agentvibes-duration.txt"
189
- $voiceDuration = 5 # default fallback
190
- if (Test-Path "$env:TEMP\agentvibes-duration.txt") {
191
- $durStr = (Get-Content "$env:TEMP\agentvibes-duration.txt" -Raw).Trim()
192
- if ($durStr -match '^\d+\.?\d*$') { $voiceDuration = [double]$durStr }
193
- Remove-Item "$env:TEMP\agentvibes-duration.txt" -Force -ErrorAction SilentlyContinue
194
- }
195
- $totalDuration = $voiceDuration + 4 # 2s intro + voice + 2s outro
196
- $fadeOutStart = $totalDuration - 2
197
-
198
- # Filter: music fades in 0.5s, voice delayed 2s, music fades out last 2s
199
- $filter = "[0:a]volume=${BgVolume},afade=t=in:d=0.5,afade=t=out:st=${fadeOutStart}:d=2[bg];[1:a]adelay=2000|2000,apad=pad_dur=2[voice];[bg][voice]amix=inputs=2:duration=longest:dropout_transition=2[out]"
200
-
201
- # Run ffmpeg - use Start-Process to avoid stderr issues with $ErrorActionPreference
202
- $ffmpegArgs = "-y -stream_loop -1 -i `"$BgTrackPath`" -i `"$voicePath`" -filter_complex `"$filter`" -map `"[out]`" -t $totalDuration `"$MixedFile`""
203
- $proc = Start-Process -FilePath "ffmpeg" -ArgumentList $ffmpegArgs -NoNewWindow -Wait -PassThru -RedirectStandardError "NUL"
204
-
205
- if ($proc.ExitCode -eq 0 -and (Test-Path $MixedFile) -and (Get-Item $MixedFile).Length -gt 0) {
206
- # Play the mixed audio
207
- $player = $null
208
- try {
209
- $player = New-Object System.Media.SoundPlayer $MixedFile
210
- $player.PlaySync()
211
- } catch {
212
- Write-Host "[WARNING] Mixed playback failed, playing voice only" -ForegroundColor Yellow
213
- $player2 = $null
214
- try {
215
- $player2 = New-Object System.Media.SoundPlayer $voicePath
216
- $player2.PlaySync()
217
- } finally {
218
- if ($player2) { $player2.Dispose() }
219
- }
220
- } finally {
221
- if ($player) { $player.Dispose() }
222
- }
223
- } else {
224
- # Mixing failed, play voice only
225
- $player = $null
226
- try {
227
- $player = New-Object System.Media.SoundPlayer $voicePath
228
- $player.PlaySync()
229
- } finally {
230
- if ($player) { $player.Dispose() }
231
- }
232
- }
233
- } catch {
234
- # ffmpeg failed, play voice only
235
- $player = $null
236
- try {
237
- $player = New-Object System.Media.SoundPlayer $voicePath
238
- $player.PlaySync()
239
- } finally {
240
- if ($player) { $player.Dispose() }
241
- }
242
- }
243
- } else {
244
- # No background track found, play voice only
245
- $player = $null
246
- try {
247
- $player = New-Object System.Media.SoundPlayer $voicePath
248
- $player.PlaySync()
249
- } finally {
250
- if ($player) { $player.Dispose() }
251
- }
252
- }
253
- } else {
254
- # No background music, play the (possibly reverbed) voice
255
- $player = $null
256
- try {
257
- $player = New-Object System.Media.SoundPlayer $voicePath
258
- $player.PlaySync()
259
- } finally {
260
- if ($player) { $player.Dispose() }
261
- }
262
- }
263
- }
264
- } else {
265
- $env:AGENTVIBES_NO_PLAY = $null
266
- }
1
+ #
2
+ # File: .claude/hooks-windows/play-tts.ps1
3
+ #
4
+ # AgentVibes - Windows TTS Router
5
+ # Delegates to active provider (windows-sapi or windows-piper)
6
+ #
7
+
8
+ param(
9
+ [Parameter(Mandatory = $true, Position = 0)]
10
+ [string]$Text,
11
+
12
+ [Parameter(Mandatory = $false, Position = 1)]
13
+ [string]$VoiceOverride
14
+ )
15
+
16
+ # Configuration paths
17
+ # First check if we're running from a project directory with .claude
18
+ $ScriptPath = Split-Path -Parent $MyInvocation.MyCommand.Path
19
+ $ProjectClaudeDir = Join-Path (Split-Path -Parent (Split-Path -Parent $ScriptPath)) ".claude"
20
+
21
+ # Use project .claude if running from there, otherwise use user profile
22
+ if (Test-Path $ProjectClaudeDir) {
23
+ $ClaudeDir = $ProjectClaudeDir
24
+ } else {
25
+ $ClaudeDir = "$env:USERPROFILE\.claude"
26
+ }
27
+
28
+ $HooksDir = "$ClaudeDir\hooks-windows"
29
+ $ProviderFile = "$ClaudeDir\tts-provider.txt"
30
+ $MuteFile = "$ClaudeDir\tts-muted.txt"
31
+
32
+ # Check if TTS is muted
33
+ if (Test-Path $MuteFile) {
34
+ $muteStatus = Get-Content $MuteFile -Raw
35
+ if ($muteStatus.Trim() -eq "true") {
36
+ exit 0
37
+ }
38
+ }
39
+
40
+ # Determine active provider
41
+ $ActiveProvider = "sapi"
42
+ if (Test-Path $ProviderFile) {
43
+ $ActiveProvider = (Get-Content $ProviderFile -Raw).Trim()
44
+ }
45
+
46
+ # Validate and get provider script
47
+ $ProviderScript = ""
48
+
49
+ switch ($ActiveProvider) {
50
+ { $_ -in "sapi", "windows-sapi" } {
51
+ $ProviderScript = "$HooksDir\play-tts-sapi.ps1"
52
+ }
53
+ { $_ -in "piper", "windows-piper" } {
54
+ $ProviderScript = "$HooksDir\play-tts-piper.ps1"
55
+ }
56
+ "soprano" {
57
+ $ProviderScript = "$HooksDir\play-tts-soprano.ps1"
58
+ }
59
+ default {
60
+ Write-Host "[ERROR] Unknown provider: $ActiveProvider" -ForegroundColor Red
61
+ Write-Host "Use: .\provider-manager.ps1 list" -ForegroundColor Yellow
62
+ exit 1
63
+ }
64
+ }
65
+
66
+ # Check if provider script exists
67
+ if (-not (Test-Path $ProviderScript)) {
68
+ Write-Host "[ERROR] Provider script not found: $ProviderScript" -ForegroundColor Red
69
+ exit 1
70
+ }
71
+
72
+ # Check if background music is enabled
73
+ # Primary source of truth: .agentvibes/config.json (used by TUI console)
74
+ # Fallback: .claude/config/background-music-enabled.txt (legacy PowerShell config)
75
+ $ConfigDir = "$ClaudeDir\config"
76
+ $BgEnabled = $false
77
+ $AgentVibesConfig = Join-Path (Split-Path -Parent $ClaudeDir) ".agentvibes\config.json"
78
+ if (Test-Path $AgentVibesConfig) {
79
+ try {
80
+ $json = Get-Content $AgentVibesConfig -Raw | ConvertFrom-Json
81
+ if ($json.backgroundMusic -and $null -ne $json.backgroundMusic.enabled) {
82
+ $BgEnabled = [bool]$json.backgroundMusic.enabled
83
+ }
84
+ } catch {
85
+ $BgEnabled = $false
86
+ }
87
+ } else {
88
+ # Fallback to legacy txt config
89
+ $BgEnabledFile = "$ConfigDir\background-music-enabled.txt"
90
+ if (Test-Path $BgEnabledFile) {
91
+ $BgEnabled = (Get-Content $BgEnabledFile -Raw).Trim() -eq "true"
92
+ }
93
+ }
94
+
95
+ # Check if reverb is enabled (allowlist validation)
96
+ $ReverbLevel = "off"
97
+ $ReverbFile = "$ConfigDir\reverb-level.txt"
98
+ if (Test-Path $ReverbFile) {
99
+ $reverbVal = (Get-Content $ReverbFile -Raw).Trim()
100
+ if ($reverbVal -in @("off", "light", "medium", "heavy", "cathedral")) {
101
+ $ReverbLevel = $reverbVal
102
+ }
103
+ }
104
+ $HasReverb = $ReverbLevel -ne "off"
105
+
106
+ # Check ffmpeg availability for background music mixing or reverb
107
+ # Refresh PATH from registry so newly-installed tools are found without shell restart
108
+ $HasFfmpeg = $false
109
+ if ($BgEnabled -or $HasReverb) {
110
+ try {
111
+ $null = Get-Command ffmpeg -ErrorAction Stop
112
+ $HasFfmpeg = $true
113
+ } catch {
114
+ # PATH may be stale (common after winget install); refresh from registry
115
+ $env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path", "User")
116
+ try {
117
+ $null = Get-Command ffmpeg -ErrorAction Stop
118
+ $HasFfmpeg = $true
119
+ } catch {}
120
+ }
121
+ }
122
+
123
+ # If background music or reverb enabled and ffmpeg available, tell provider to skip playback
124
+ if (($BgEnabled -or $HasReverb) -and $HasFfmpeg) {
125
+ $env:AGENTVIBES_NO_PLAY = "1"
126
+ }
127
+
128
+ # Call the provider script
129
+ # When post-processing (reverb/music), capture output preserving InformationRecord colors.
130
+ # Otherwise call directly so Write-Host colors pass through to the terminal.
131
+ $NeedsPostProcess = ($BgEnabled -or $HasReverb) -and $HasFfmpeg
132
+ try {
133
+ if ($NeedsPostProcess) {
134
+ if ($VoiceOverride) {
135
+ $providerOutput = & $ProviderScript $Text $VoiceOverride 6>&1 2>&1
136
+ } else {
137
+ $providerOutput = & $ProviderScript $Text 6>&1 2>&1
138
+ }
139
+ # Re-emit preserving colors from InformationRecords (Write-Host output)
140
+ foreach ($item in $providerOutput) {
141
+ if ($item -is [System.Management.Automation.InformationRecord]) {
142
+ $msg = $item.MessageData
143
+ if ($msg -is [System.Management.Automation.HostInformationMessage]) {
144
+ Write-Host $msg.Message -ForegroundColor $msg.ForegroundColor -NoNewline:$msg.NoNewLine
145
+ if (-not $msg.NoNewLine) { Write-Host }
146
+ } else {
147
+ Write-Host "$item"
148
+ }
149
+ } else {
150
+ Write-Host "$item"
151
+ }
152
+ }
153
+ } else {
154
+ if ($VoiceOverride) {
155
+ & $ProviderScript $Text $VoiceOverride
156
+ } else {
157
+ & $ProviderScript $Text
158
+ }
159
+ }
160
+ }
161
+ catch {
162
+ Write-Host "[ERROR] TTS Error: $_" -ForegroundColor Red
163
+ Remove-Item env:AGENTVIBES_NO_PLAY -ErrorAction SilentlyContinue
164
+ exit 1
165
+ }
166
+
167
+ # Apply reverb and/or mix with background music
168
+ if (($BgEnabled -or $HasReverb) -and $HasFfmpeg) {
169
+ Remove-Item env:AGENTVIBES_NO_PLAY -ErrorAction SilentlyContinue
170
+
171
+ # Find the most recent TTS wav file
172
+ $AudioDir = "$ClaudeDir\audio"
173
+ $RecentWav = Get-ChildItem -Path $AudioDir -Filter "tts-*.wav" -ErrorAction SilentlyContinue |
174
+ Sort-Object LastWriteTime -Descending | Select-Object -First 1
175
+
176
+ if ($RecentWav -and $RecentWav.Length -gt 0) {
177
+ $voicePath = $RecentWav.FullName
178
+
179
+ # Apply reverb if configured
180
+ if ($HasReverb) {
181
+ $reverbFilter = switch ($ReverbLevel) {
182
+ "light" { "aecho=0.8:0.88:60:0.4" }
183
+ "medium" { "aecho=0.8:0.88:60|120:0.4|0.3" }
184
+ "heavy" { "aecho=0.8:0.88:60|120|180:0.4|0.3|0.2" }
185
+ "cathedral" { "aecho=0.8:0.88:100|200|300|400:0.3|0.25|0.2|0.15" }
186
+ default { "" }
187
+ }
188
+ if ($reverbFilter) {
189
+ $reverbedFile = "$AudioDir\tts-reverbed.wav"
190
+ $reverbArgs = "-y -i `"$voicePath`" -af `"$reverbFilter`" `"$reverbedFile`""
191
+ $proc = Start-Process -FilePath "ffmpeg" -ArgumentList $reverbArgs -NoNewWindow -Wait -PassThru -RedirectStandardError "$env:TEMP\agentvibes-ffmpeg-stderr.txt"
192
+ if ($proc.ExitCode -eq 0 -and (Test-Path $reverbedFile)) {
193
+ $voicePath = $reverbedFile
194
+ }
195
+ }
196
+ }
197
+
198
+ # Mix with background music if enabled
199
+ if ($BgEnabled) {
200
+ # Read background track and volume from audio-effects.cfg (matches Linux behavior)
201
+ $TracksDir = "$ClaudeDir\audio\tracks"
202
+ $DefaultTrack = ""
203
+ $BgVolume = "0.25"
204
+ $AudioEffectsCfg = "$ConfigDir\audio-effects.cfg"
205
+
206
+ if (Test-Path $AudioEffectsCfg) {
207
+ # Try agent-specific config first, then fall back to default
208
+ # Format: AGENT_NAME|SOX_EFFECTS|BACKGROUND_FILE|BACKGROUND_VOLUME
209
+ $agentName = $env:AGENTVIBES_AGENT_NAME
210
+ $configLine = $null
211
+
212
+ $cfgLines = Get-Content $AudioEffectsCfg
213
+ if ($agentName) {
214
+ foreach ($line in $cfgLines) {
215
+ if ($line -match "^$([regex]::Escape($agentName))\|") {
216
+ $configLine = $line
217
+ break
218
+ }
219
+ }
220
+ }
221
+ # Fall back to default
222
+ if (-not $configLine) {
223
+ foreach ($line in $cfgLines) {
224
+ if ($line -match '^default\|') {
225
+ $configLine = $line
226
+ break
227
+ }
228
+ }
229
+ }
230
+
231
+ if ($configLine) {
232
+ $parts = $configLine -split '\|'
233
+ if ($parts.Length -ge 3 -and $parts[2]) {
234
+ $trackName = $parts[2].Trim()
235
+ # Validate: filename only, no path separators or traversal
236
+ if ($trackName -match '^[a-zA-Z0-9_\-\.]+$') {
237
+ $DefaultTrack = $trackName
238
+ }
239
+ }
240
+ if ($parts.Length -ge 4 -and $parts[3]) {
241
+ $volVal = $parts[3].Trim()
242
+ if ($volVal -match '^\d+\.?\d*$') { $BgVolume = $volVal }
243
+ }
244
+ }
245
+ }
246
+
247
+ # Fallback if no track found in config
248
+ if (-not $DefaultTrack) {
249
+ $DefaultTrack = "agent_vibes_celtic_harp_v1_loop.mp3"
250
+ }
251
+
252
+ $BgTrackPath = Join-Path $TracksDir $DefaultTrack
253
+ # Path containment: verify resolved path stays within tracks directory
254
+ $ResolvedBgTrack = [System.IO.Path]::GetFullPath($BgTrackPath)
255
+ $ResolvedTracksDir = [System.IO.Path]::GetFullPath($TracksDir)
256
+ if (-not $ResolvedBgTrack.StartsWith($ResolvedTracksDir + [System.IO.Path]::DirectorySeparatorChar)) {
257
+ $BgTrackPath = Join-Path $TracksDir "agent_vibes_celtic_harp_v1_loop.mp3"
258
+ }
259
+
260
+ if (Test-Path $BgTrackPath) {
261
+ $MixedFile = $RecentWav.FullName -replace '\.wav$', '-mixed.wav'
262
+
263
+ try {
264
+ # Get voice duration to calculate total length
265
+ $probArgs = "-v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 `"$voicePath`""
266
+ $durationProc = Start-Process -FilePath "ffprobe" -ArgumentList $probArgs -NoNewWindow -Wait -PassThru -RedirectStandardError "$env:TEMP\agentvibes-ffmpeg-stderr.txt" -RedirectStandardOutput "$env:TEMP\agentvibes-duration.txt"
267
+ $voiceDuration = 5 # default fallback
268
+ if (Test-Path "$env:TEMP\agentvibes-duration.txt") {
269
+ $durStr = (Get-Content "$env:TEMP\agentvibes-duration.txt" -Raw).Trim()
270
+ if ($durStr -match '^\d+\.?\d*$') { $voiceDuration = [double]$durStr }
271
+ Remove-Item "$env:TEMP\agentvibes-duration.txt" -Force -ErrorAction SilentlyContinue
272
+ }
273
+ $totalDuration = $voiceDuration + 4 # 2s intro + voice + 2s outro
274
+ $fadeOutStart = $totalDuration - 2
275
+
276
+ # Filter: music fades in 0.5s, voice delayed 2s, music fades out last 2s
277
+ $filter = "[0:a]volume=${BgVolume},afade=t=in:d=0.5,afade=t=out:st=${fadeOutStart}:d=2[bg];[1:a]adelay=2000|2000,apad=pad_dur=2[voice];[bg][voice]amix=inputs=2:duration=longest:dropout_transition=2[out]"
278
+
279
+ # Run ffmpeg - use Start-Process to avoid stderr issues with $ErrorActionPreference
280
+ $ffmpegArgs = "-y -stream_loop -1 -i `"$BgTrackPath`" -i `"$voicePath`" -filter_complex `"$filter`" -map `"[out]`" -t $totalDuration `"$MixedFile`""
281
+ $proc = Start-Process -FilePath "ffmpeg" -ArgumentList $ffmpegArgs -NoNewWindow -Wait -PassThru -RedirectStandardError "$env:TEMP\agentvibes-ffmpeg-stderr.txt"
282
+
283
+ if ($proc.ExitCode -eq 0 -and (Test-Path $MixedFile) -and (Get-Item $MixedFile).Length -gt 0) {
284
+ # Play the mixed audio
285
+ $player = $null
286
+ try {
287
+ $player = New-Object System.Media.SoundPlayer $MixedFile
288
+ $player.PlaySync()
289
+ } catch {
290
+ Write-Host "[WARNING] Mixed playback failed, playing voice only" -ForegroundColor Yellow
291
+ $player2 = $null
292
+ try {
293
+ $player2 = New-Object System.Media.SoundPlayer $voicePath
294
+ $player2.PlaySync()
295
+ } finally {
296
+ if ($player2) { $player2.Dispose() }
297
+ }
298
+ } finally {
299
+ if ($player) { $player.Dispose() }
300
+ }
301
+ } else {
302
+ # Mixing failed, play voice only
303
+ $player = $null
304
+ try {
305
+ $player = New-Object System.Media.SoundPlayer $voicePath
306
+ $player.PlaySync()
307
+ } finally {
308
+ if ($player) { $player.Dispose() }
309
+ }
310
+ }
311
+ } catch {
312
+ # ffmpeg failed, play voice only
313
+ $player = $null
314
+ try {
315
+ $player = New-Object System.Media.SoundPlayer $voicePath
316
+ $player.PlaySync()
317
+ } finally {
318
+ if ($player) { $player.Dispose() }
319
+ }
320
+ }
321
+ } else {
322
+ # No background track found, play voice only
323
+ $player = $null
324
+ try {
325
+ $player = New-Object System.Media.SoundPlayer $voicePath
326
+ $player.PlaySync()
327
+ } finally {
328
+ if ($player) { $player.Dispose() }
329
+ }
330
+ }
331
+ } else {
332
+ # No background music, play the (possibly reverbed) voice
333
+ $player = $null
334
+ try {
335
+ $player = New-Object System.Media.SoundPlayer $voicePath
336
+ $player.PlaySync()
337
+ } finally {
338
+ if ($player) { $player.Dispose() }
339
+ }
340
+ }
341
+ }
342
+ } else {
343
+ Remove-Item env:AGENTVIBES_NO_PLAY -ErrorAction SilentlyContinue
344
+ }