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
@@ -0,0 +1,209 @@
1
+ #
2
+ # File: .claude/hooks-windows/play-tts-windows-piper.ps1
3
+ #
4
+ # AgentVibes - Windows Piper TTS Provider
5
+ # High-quality neural TTS using Piper.exe
6
+ #
7
+
8
+ param(
9
+ [Parameter(Mandatory = $true)]
10
+ [string]$Text,
11
+
12
+ [Parameter(Mandatory = $false)]
13
+ [string]$VoiceOverride
14
+ )
15
+
16
+ # Configuration paths
17
+ $ScriptPath = Split-Path -Parent $MyInvocation.MyCommand.Path
18
+ $ProjectClaudeDir = Join-Path (Split-Path -Parent (Split-Path -Parent $ScriptPath)) ".claude"
19
+
20
+ if (Test-Path $ProjectClaudeDir) {
21
+ $ClaudeDir = $ProjectClaudeDir
22
+ } else {
23
+ $ClaudeDir = "$env:USERPROFILE\.claude"
24
+ }
25
+
26
+ # Audio cache and voice config use project-local .claude
27
+ $AudioDir = "$ClaudeDir\audio"
28
+ # Try provider-specific file first, then generic tts-voice.txt (set by TUI)
29
+ $VoiceFile = "$ClaudeDir\tts-voice-piper.txt"
30
+ if (-not (Test-Path $VoiceFile)) {
31
+ $VoiceFile = "$ClaudeDir\tts-voice.txt"
32
+ }
33
+
34
+ # Voices and Piper binary are global (shared across projects, ~100MB+)
35
+ $UserClaudeDir = "$env:USERPROFILE\.claude"
36
+ $VoicesDir = "$UserClaudeDir\piper-voices"
37
+ # Try standard install location first, then fall back to PATH
38
+ $PiperExe = "$env:LOCALAPPDATA\Programs\Piper\piper.exe"
39
+ if (-not (Test-Path $PiperExe)) {
40
+ $found = Get-Command piper.exe -ErrorAction SilentlyContinue
41
+ if (-not $found) {
42
+ # PATH may be stale (SSH sessions inherit minimal PATH); refresh from registry
43
+ $env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path", "User")
44
+ $found = Get-Command piper.exe -ErrorAction SilentlyContinue
45
+ }
46
+ if ($found) { $PiperExe = $found.Source }
47
+ }
48
+
49
+ # Ensure directories exist
50
+ foreach ($dir in @($AudioDir, $VoicesDir)) {
51
+ if (-not (Test-Path $dir)) {
52
+ New-Item -ItemType Directory -Path $dir -Force | Out-Null
53
+ }
54
+ }
55
+
56
+ # Check if Piper is installed
57
+ if (-not (Test-Path $PiperExe)) {
58
+ Write-Host "[ERROR] Piper not found at: $PiperExe" -ForegroundColor Red
59
+ Write-Host "Run: .\setup-windows.ps1 to install Piper" -ForegroundColor Yellow
60
+ exit 1
61
+ }
62
+
63
+ # Determine voice to use
64
+ $VoiceName = ""
65
+
66
+ if ($VoiceOverride) {
67
+ $VoiceName = $VoiceOverride
68
+ }
69
+ elseif (Test-Path $VoiceFile) {
70
+ $VoiceName = (Get-Content $VoiceFile -Raw).Trim()
71
+ }
72
+
73
+ # Strip display name suffix (e.g. "en_US-libritts-high::Bella-9" -> "en_US-libritts-high")
74
+ # and extract speaker ID if present (works for both override and file)
75
+ if ($VoiceName -match '::') {
76
+ $parts = $VoiceName -split '::'
77
+ $VoiceName = $parts[0]
78
+ if ($parts.Length -ge 2 -and $parts[1] -match '-(\d+)$') {
79
+ $env:PIPER_SPEAKER = $Matches[1]
80
+ } else {
81
+ Remove-Item env:PIPER_SPEAKER -ErrorAction SilentlyContinue
82
+ }
83
+ } else {
84
+ # No multi-speaker syntax — clear any stale speaker env var
85
+ Remove-Item env:PIPER_SPEAKER -ErrorAction SilentlyContinue
86
+ }
87
+
88
+ # Default voice if not specified
89
+ # Prefer en_US-lessac-medium (bundled/commonly installed) over en_US-ryan-high
90
+ if (-not $VoiceName) {
91
+ $UserClaudePiperDir = "$env:USERPROFILE\.claude\piper-voices"
92
+ if (Test-Path "$UserClaudePiperDir\en_US-lessac-medium.onnx") {
93
+ $VoiceName = "en_US-lessac-medium"
94
+ } elseif (Test-Path "$UserClaudePiperDir\en_US-ryan-high.onnx") {
95
+ $VoiceName = "en_US-ryan-high"
96
+ } else {
97
+ # Fallback: use first available .onnx file, or default name for auto-download
98
+ $firstVoice = Get-ChildItem -Path $UserClaudePiperDir -Filter "*.onnx" -ErrorAction SilentlyContinue | Select-Object -First 1
99
+ if ($firstVoice) {
100
+ $VoiceName = $firstVoice.BaseName
101
+ } else {
102
+ $VoiceName = "en_US-lessac-medium"
103
+ }
104
+ }
105
+ }
106
+
107
+ # Security: Validate voice name to prevent path traversal
108
+ # Only allow alphanumeric, underscore, hyphen, and period
109
+ if ($VoiceName -notmatch '^[a-zA-Z0-9_\-\.]+$') {
110
+ Write-Host "[ERROR] Invalid voice name: $VoiceName" -ForegroundColor Red
111
+ exit 1
112
+ }
113
+
114
+ # Resolve voice model path and validate it stays within VoicesDir
115
+ $VoiceModelFile = [System.IO.Path]::GetFullPath("$VoicesDir\$VoiceName.onnx")
116
+ $VoiceJsonFile = [System.IO.Path]::GetFullPath("$VoicesDir\$VoiceName.onnx.json")
117
+ $ResolvedVoicesDir = [System.IO.Path]::GetFullPath($VoicesDir)
118
+ if (-not $VoiceModelFile.StartsWith($ResolvedVoicesDir)) {
119
+ Write-Host "[ERROR] Voice path outside voices directory" -ForegroundColor Red
120
+ exit 1
121
+ }
122
+
123
+ # Check if voice model exists, download if missing
124
+ if (-not (Test-Path $VoiceModelFile)) {
125
+ Write-Host "[DOWNLOAD] Voice model: $VoiceName" -ForegroundColor Yellow
126
+
127
+ # Try to download from Hugging Face
128
+ # Voice name format: {lang}_{region}-{speaker}-{quality}
129
+ # HF path format: {lang}/{lang}_{region}/{speaker}/{quality}/{voicename}.onnx
130
+ try {
131
+ # Parse voice name to build correct HF path
132
+ # e.g. en_US-ryan-high -> en/en_US/ryan/high/en_US-ryan-high.onnx
133
+ if ($VoiceName -match '^([a-z]{2})_([A-Z]{2})-([a-zA-Z0-9_]+)-([a-z]+)$') {
134
+ $Lang = $Matches[1]
135
+ $LangRegion = "$($Matches[1])_$($Matches[2])"
136
+ $Speaker = $Matches[3]
137
+ $Quality = $Matches[4]
138
+ $HFBase = "https://huggingface.co/rhasspy/piper-voices/resolve/main/$Lang/$LangRegion/$Speaker/$Quality"
139
+ } else {
140
+ # Fallback for non-standard voice names
141
+ $HFBase = "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/ryan/high"
142
+ }
143
+ $ModelUrl = "$HFBase/$VoiceName.onnx"
144
+ $JsonUrl = "$HFBase/$VoiceName.onnx.json"
145
+
146
+ Write-Host " Downloading model..." -ForegroundColor Cyan
147
+ Invoke-WebRequest -Uri $ModelUrl -OutFile $VoiceModelFile -ErrorAction Stop
148
+ Write-Host " Downloading config..." -ForegroundColor Cyan
149
+ Invoke-WebRequest -Uri $JsonUrl -OutFile $VoiceJsonFile -ErrorAction Stop
150
+ Write-Host "[OK] Voice model downloaded" -ForegroundColor Green
151
+ }
152
+ catch {
153
+ Write-Host "[ERROR] Failed to download voice model: $_" -ForegroundColor Red
154
+ Write-Host "Make sure you have internet connection" -ForegroundColor Yellow
155
+ exit 1
156
+ }
157
+ }
158
+
159
+ # Sanitize text for speech - strip only dangerous shell metacharacters
160
+ $Text = $Text -replace '\\', ' '
161
+ $Text = $Text -replace '[{}<>|`~^;]', ''
162
+ $Text = $Text -replace '\s+', ' '
163
+ $Text = $Text.Trim()
164
+
165
+ # Create audio file path
166
+ $Timestamp = Get-Date -Format 'yyyyMMdd-HHmmss-ffff'
167
+ $AudioFile = "$AudioDir\tts-$Timestamp.wav"
168
+
169
+ # Synthesize with Piper
170
+ try {
171
+ Write-Host "[SYNTH] Synthesizing with Piper..." -ForegroundColor Cyan
172
+
173
+ # Run Piper with text input
174
+ # Add --speaker for multi-speaker models (e.g. libritts-high with speaker 9)
175
+ $piperArgs = @("--model", $VoiceModelFile, "--output-file", $AudioFile)
176
+ if ($env:PIPER_SPEAKER) {
177
+ $piperArgs += @("--speaker", $env:PIPER_SPEAKER)
178
+ }
179
+ $Text | & $PiperExe @piperArgs 2>$null
180
+
181
+ if (-not (Test-Path $AudioFile)) {
182
+ Write-Host "[ERROR] Piper synthesis failed" -ForegroundColor Red
183
+ exit 1
184
+ }
185
+
186
+ # Display results
187
+ Write-Host "[OK] Saved to: $AudioFile" -ForegroundColor Green
188
+ Write-Host "[VOICE] Voice used: $VoiceName (Piper)" -ForegroundColor Green
189
+
190
+ # Play the audio using built-in Windows audio player (skip if AGENTVIBES_NO_PLAY is set)
191
+ if (-not $env:AGENTVIBES_NO_PLAY) {
192
+ $player = $null
193
+ try {
194
+ $player = New-Object System.Media.SoundPlayer $AudioFile
195
+ $player.PlaySync()
196
+ }
197
+ catch {
198
+ Write-Host "[WARNING] Could not play audio (SoundPlayer unavailable)" -ForegroundColor Yellow
199
+ Write-Host "Audio saved to: $AudioFile" -ForegroundColor Gray
200
+ }
201
+ finally {
202
+ if ($player) { $player.Dispose() }
203
+ }
204
+ }
205
+ }
206
+ catch {
207
+ Write-Host "[ERROR] Error running Piper: $_" -ForegroundColor Red
208
+ exit 1
209
+ }
@@ -0,0 +1,108 @@
1
+ #
2
+ # File: .claude/hooks-windows/play-tts-windows-sapi.ps1
3
+ #
4
+ # AgentVibes - Windows SAPI TTS Provider (Zero Dependencies)
5
+ # Uses built-in Windows System.Speech API
6
+ #
7
+
8
+ param(
9
+ [Parameter(Mandatory = $true)]
10
+ [string]$Text,
11
+
12
+ [Parameter(Mandatory = $false)]
13
+ [string]$VoiceOverride
14
+ )
15
+
16
+ # Configuration paths
17
+ $ScriptPath = Split-Path -Parent $MyInvocation.MyCommand.Path
18
+ $ProjectClaudeDir = Join-Path (Split-Path -Parent (Split-Path -Parent $ScriptPath)) ".claude"
19
+
20
+ if (Test-Path $ProjectClaudeDir) {
21
+ $ClaudeDir = $ProjectClaudeDir
22
+ } else {
23
+ $ClaudeDir = "$env:USERPROFILE\.claude"
24
+ }
25
+
26
+ $AudioDir = "$ClaudeDir\audio"
27
+ $VoiceFile = "$ClaudeDir\tts-voice-sapi.txt"
28
+
29
+ # Ensure directories exist
30
+ if (-not (Test-Path $AudioDir)) {
31
+ New-Item -ItemType Directory -Path $AudioDir -Force | Out-Null
32
+ }
33
+
34
+ # Load System.Speech assembly
35
+ try {
36
+ Add-Type -AssemblyName System.Speech
37
+ }
38
+ catch {
39
+ Write-Host "[ERROR] System.Speech assembly not available" -ForegroundColor Red
40
+ exit 1
41
+ }
42
+
43
+ # Determine voice to use
44
+ $VoiceName = ""
45
+
46
+ if ($VoiceOverride) {
47
+ $VoiceName = $VoiceOverride
48
+ }
49
+ elseif (Test-Path $VoiceFile) {
50
+ $VoiceName = (Get-Content $VoiceFile -Raw).Trim()
51
+ }
52
+
53
+ # Initialize speech synthesizer
54
+ $synth = New-Object System.Speech.Synthesis.SpeechSynthesizer
55
+
56
+ # Set voice if specified
57
+ if ($VoiceName) {
58
+ try {
59
+ $synth.SelectVoice($VoiceName)
60
+ }
61
+ catch {
62
+ Write-Host "[WARNING] Voice '$VoiceName' not found, using default" -ForegroundColor Yellow
63
+ }
64
+ }
65
+
66
+ # Sanitize text for speech - strip only dangerous shell metacharacters and SSML tags
67
+ $Text = $Text -replace '\\', ' '
68
+ $Text = $Text -replace '[{}<>|`~^;]', ''
69
+ $Text = $Text -replace '&[a-zA-Z]+;', ''
70
+ $Text = $Text -replace '\s+', ' '
71
+ $Text = $Text.Trim()
72
+
73
+ # Get actual voice name (after selection or default)
74
+ $ActualVoice = $synth.Voice.Name
75
+
76
+ # Create audio file path
77
+ $Timestamp = Get-Date -Format 'yyyyMMdd-HHmmss-ffff'
78
+ $AudioFile = "$AudioDir\tts-$Timestamp.wav"
79
+
80
+ # Save to WAV file with proper resource cleanup
81
+ $player = $null
82
+ try {
83
+ $synth.SetOutputToWaveFile($AudioFile)
84
+ $synth.Speak($Text)
85
+
86
+ # Display results
87
+ Write-Host "[OK] Saved to: $AudioFile" -ForegroundColor Green
88
+ Write-Host "[VOICE] Voice used: $ActualVoice (Windows SAPI)" -ForegroundColor Green
89
+
90
+ # Play the audio using built-in Windows audio player (skip if AGENTVIBES_NO_PLAY is set)
91
+ if (-not $env:AGENTVIBES_NO_PLAY) {
92
+ try {
93
+ $player = New-Object System.Media.SoundPlayer $AudioFile
94
+ $player.PlaySync()
95
+ }
96
+ catch {
97
+ Write-Host "[WARNING] Could not play audio (SoundPlayer unavailable)" -ForegroundColor Yellow
98
+ }
99
+ }
100
+ }
101
+ catch {
102
+ Write-Host "[ERROR] Error synthesizing speech: $_" -ForegroundColor Red
103
+ exit 1
104
+ }
105
+ finally {
106
+ if ($synth) { $synth.Dispose() }
107
+ if ($player) { $player.Dispose() }
108
+ }
@@ -1,158 +1,159 @@
1
- #
2
- # File: .claude/hooks-windows/play-tts-soprano.ps1
3
- #
4
- # AgentVibes - Soprano TTS Provider for Windows
5
- # Ultra-fast neural TTS via Soprano (80M params)
6
- #
7
- # Supports three modes (auto-detected in priority order):
8
- # 1. WebUI mode: Gradio WebUI running (soprano-webui), uses Python helper
9
- # 2. API mode: OpenAI-compatible server, uses Invoke-RestMethod
10
- # 3. CLI mode: Direct soprano command (reloads model each call, slowest)
11
- #
12
-
13
- param(
14
- [Parameter(Mandatory = $true, Position = 0)]
15
- [string]$Text,
16
-
17
- [Parameter(Mandatory = $false, Position = 1)]
18
- [string]$VoiceOverride # Ignored - Soprano has a single voice
19
- )
20
-
21
- $ErrorActionPreference = "Stop"
22
-
23
- $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
24
- # Validate port is numeric to prevent injection
25
- $SopranoPort = "7860"
26
- if ($env:SOPRANO_PORT -and $env:SOPRANO_PORT -match '^\d+$') {
27
- $portNum = [int]$env:SOPRANO_PORT
28
- if ($portNum -gt 0 -and $portNum -le 65535) {
29
- $SopranoPort = $env:SOPRANO_PORT
30
- }
31
- }
32
- $SopranoDevice = if ($env:SOPRANO_DEVICE) { $env:SOPRANO_DEVICE } else { "auto" }
33
-
34
- # Sanitize text for TTS - strip shell metacharacters and PS special chars
35
- $Text = $Text -replace '[\\`"{}$<>|~^;''()]', '' -replace '\s+', ' '
36
- $Text = $Text.Trim()
37
-
38
- if (-not $Text) {
39
- Write-Host "Usage: play-tts-soprano.ps1 'text to speak' [voice_override]"
40
- exit 1
41
- }
42
-
43
- # Determine audio directory
44
- $ProjectClaudeDir = Join-Path (Split-Path -Parent (Split-Path -Parent $ScriptDir)) ".claude"
45
- if (Test-Path $ProjectClaudeDir) {
46
- $AudioDir = Join-Path $ProjectClaudeDir "audio"
47
- } else {
48
- $AudioDir = "$env:USERPROFILE\.claude\audio"
49
- }
50
-
51
- if (-not (Test-Path $AudioDir)) {
52
- New-Item -ItemType Directory -Path $AudioDir -Force | Out-Null
53
- }
54
-
55
- $timestamp = Get-Date -Format "yyyyMMdd-HHmmss"
56
- $random = Get-Random -Maximum 9999
57
- $TempFile = Join-Path $AudioDir "tts-$timestamp-$random.wav"
58
-
59
- # Check WebUI server
60
- function Test-WebUI {
61
- try {
62
- $null = Invoke-WebRequest -Uri "http://127.0.0.1:${SopranoPort}/gradio_api/info" -TimeoutSec 2 -UseBasicParsing -ErrorAction Stop
63
- return $true
64
- } catch {
65
- try {
66
- $null = Invoke-WebRequest -Uri "http://127.0.0.1:${SopranoPort}/info" -TimeoutSec 2 -UseBasicParsing -ErrorAction Stop
67
- return $true
68
- } catch {
69
- return $false
70
- }
71
- }
72
- }
73
-
74
- # Check API server
75
- function Test-APIServer {
76
- try {
77
- $body = '{"input":"test"}'
78
- $null = Invoke-RestMethod -Uri "http://127.0.0.1:${SopranoPort}/v1/audio/speech" `
79
- -Method POST -ContentType "application/json" -Body $body -TimeoutSec 2 -ErrorAction Stop
80
- return $true
81
- } catch {
82
- return $false
83
- }
84
- }
85
-
86
- # Check CLI availability
87
- function Test-SopranoCLI {
88
- try {
89
- $null = Get-Command soprano -ErrorAction Stop
90
- return $true
91
- } catch {
92
- return $false
93
- }
94
- }
95
-
96
- # Synthesize speech
97
- $SynthMode = ""
98
-
99
- if (Test-WebUI) {
100
- # Gradio WebUI mode - use Python helper for SSE protocol
101
- $SynthMode = "webui"
102
- $pythonHelper = Join-Path $ScriptDir "soprano-gradio-synth.py"
103
- if (Test-Path $pythonHelper) {
104
- & python $pythonHelper $Text $TempFile $SopranoPort 2>$null
105
- } else {
106
- Write-Host "[ERROR] soprano-gradio-synth.py not found" -ForegroundColor Red
107
- exit 1
108
- }
109
- } elseif (Test-APIServer) {
110
- # OpenAI-compatible API mode
111
- $SynthMode = "api"
112
- # Build JSON safely using ConvertTo-Json to avoid injection
113
- $bodyObj = @{ input = $Text }
114
- $body = $bodyObj | ConvertTo-Json -Compress
115
- try {
116
- Invoke-RestMethod -Uri "http://127.0.0.1:${SopranoPort}/v1/audio/speech" `
117
- -Method POST -ContentType "application/json" -Body $body `
118
- -OutFile $TempFile -ErrorAction Stop
119
- } catch {
120
- Write-Host "[ERROR] API synthesis failed: $_" -ForegroundColor Red
121
- exit 4
122
- }
123
- } elseif (Test-SopranoCLI) {
124
- # CLI fallback - reloads model each call (slowest)
125
- $SynthMode = "cli"
126
- & soprano $Text -o $TempFile -d $SopranoDevice 2>$null
127
- } else {
128
- Write-Host "[ERROR] Soprano TTS not installed and no server running on port $SopranoPort" -ForegroundColor Red
129
- Write-Host ""
130
- Write-Host "Install: pip install soprano-tts" -ForegroundColor Yellow
131
- Write-Host " (GPU): pip install soprano-tts[lmdeploy]" -ForegroundColor Yellow
132
- Write-Host ""
133
- Write-Host "Start WebUI: soprano-webui" -ForegroundColor Yellow
134
- Write-Host "Start API: uvicorn soprano.server:app --host 127.0.0.1 --port $SopranoPort" -ForegroundColor Yellow
135
- exit 2
136
- }
137
-
138
- # Verify output
139
- if (-not (Test-Path $TempFile) -or (Get-Item $TempFile).Length -eq 0) {
140
- Write-Host "[ERROR] Failed to synthesize speech with Soprano ($SynthMode mode)" -ForegroundColor Red
141
- exit 4
142
- }
143
-
144
- # Play audio with proper resource cleanup (skip if AGENTVIBES_NO_PLAY is set)
145
- if (-not $env:AGENTVIBES_NO_PLAY) {
146
- $player = $null
147
- try {
148
- $player = New-Object System.Media.SoundPlayer $TempFile
149
- $player.PlaySync()
150
- } catch {
151
- Write-Host "[ERROR] Audio playback failed: $_" -ForegroundColor Red
152
- } finally {
153
- if ($player) { $player.Dispose() }
154
- }
155
- }
156
-
157
- Write-Host "Saved to: $TempFile"
158
- Write-Host "Voice: Soprano-1.1-80M (Soprano TTS, $SynthMode mode)"
1
+ #
2
+ # File: .claude/hooks-windows/play-tts-soprano.ps1
3
+ #
4
+ # AgentVibes - Soprano TTS Provider for Windows
5
+ # Ultra-fast neural TTS via Soprano (80M params)
6
+ #
7
+ # Supports three modes (auto-detected in priority order):
8
+ # 1. WebUI mode: Gradio WebUI running (soprano-webui), uses Python helper
9
+ # 2. API mode: OpenAI-compatible server, uses Invoke-RestMethod
10
+ # 3. CLI mode: Direct soprano command (reloads model each call, slowest)
11
+ #
12
+
13
+ param(
14
+ [Parameter(Mandatory = $true, Position = 0)]
15
+ [string]$Text,
16
+
17
+ [Parameter(Mandatory = $false, Position = 1)]
18
+ [string]$VoiceOverride # Ignored - Soprano has a single voice
19
+ )
20
+
21
+ $ErrorActionPreference = "Stop"
22
+
23
+ $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
24
+ # Validate port is numeric to prevent injection
25
+ $SopranoPort = "7860"
26
+ if ($env:SOPRANO_PORT -and $env:SOPRANO_PORT -match '^\d+$') {
27
+ $portNum = [int]$env:SOPRANO_PORT
28
+ if ($portNum -gt 0 -and $portNum -le 65535) {
29
+ $SopranoPort = $env:SOPRANO_PORT
30
+ }
31
+ }
32
+ $SopranoDevice = if ($env:SOPRANO_DEVICE) { $env:SOPRANO_DEVICE } else { "auto" }
33
+
34
+ # Sanitize text for TTS - strip only dangerous shell metacharacters
35
+ # Keep $, parens, quotes these are harmless for TTS text piped to stdin
36
+ $Text = $Text -replace '[\\`{}<>|~^;]', '' -replace '\s+', ' '
37
+ $Text = $Text.Trim()
38
+
39
+ if (-not $Text) {
40
+ Write-Host "Usage: play-tts-soprano.ps1 'text to speak' [voice_override]"
41
+ exit 1
42
+ }
43
+
44
+ # Determine audio directory
45
+ $ProjectClaudeDir = Join-Path (Split-Path -Parent (Split-Path -Parent $ScriptDir)) ".claude"
46
+ if (Test-Path $ProjectClaudeDir) {
47
+ $AudioDir = Join-Path $ProjectClaudeDir "audio"
48
+ } else {
49
+ $AudioDir = "$env:USERPROFILE\.claude\audio"
50
+ }
51
+
52
+ if (-not (Test-Path $AudioDir)) {
53
+ New-Item -ItemType Directory -Path $AudioDir -Force | Out-Null
54
+ }
55
+
56
+ $timestamp = Get-Date -Format "yyyyMMdd-HHmmss"
57
+ $random = Get-Random -Maximum 9999
58
+ $TempFile = Join-Path $AudioDir "tts-$timestamp-$random.wav"
59
+
60
+ # Check WebUI server
61
+ function Test-WebUI {
62
+ try {
63
+ $null = Invoke-WebRequest -Uri "http://127.0.0.1:${SopranoPort}/gradio_api/info" -TimeoutSec 2 -UseBasicParsing -ErrorAction Stop
64
+ return $true
65
+ } catch {
66
+ try {
67
+ $null = Invoke-WebRequest -Uri "http://127.0.0.1:${SopranoPort}/info" -TimeoutSec 2 -UseBasicParsing -ErrorAction Stop
68
+ return $true
69
+ } catch {
70
+ return $false
71
+ }
72
+ }
73
+ }
74
+
75
+ # Check API server
76
+ function Test-APIServer {
77
+ try {
78
+ $body = '{"input":"test"}'
79
+ $null = Invoke-RestMethod -Uri "http://127.0.0.1:${SopranoPort}/v1/audio/speech" `
80
+ -Method POST -ContentType "application/json" -Body $body -TimeoutSec 2 -ErrorAction Stop
81
+ return $true
82
+ } catch {
83
+ return $false
84
+ }
85
+ }
86
+
87
+ # Check CLI availability
88
+ function Test-SopranoCLI {
89
+ try {
90
+ $null = Get-Command soprano -ErrorAction Stop
91
+ return $true
92
+ } catch {
93
+ return $false
94
+ }
95
+ }
96
+
97
+ # Synthesize speech
98
+ $SynthMode = ""
99
+
100
+ if (Test-WebUI) {
101
+ # Gradio WebUI mode - use Python helper for SSE protocol
102
+ $SynthMode = "webui"
103
+ $pythonHelper = Join-Path $ScriptDir "soprano-gradio-synth.py"
104
+ if (Test-Path $pythonHelper) {
105
+ & python $pythonHelper $Text $TempFile $SopranoPort 2>$null
106
+ } else {
107
+ Write-Host "[ERROR] soprano-gradio-synth.py not found" -ForegroundColor Red
108
+ exit 1
109
+ }
110
+ } elseif (Test-APIServer) {
111
+ # OpenAI-compatible API mode
112
+ $SynthMode = "api"
113
+ # Build JSON safely using ConvertTo-Json to avoid injection
114
+ $bodyObj = @{ input = $Text }
115
+ $body = $bodyObj | ConvertTo-Json -Compress
116
+ try {
117
+ Invoke-RestMethod -Uri "http://127.0.0.1:${SopranoPort}/v1/audio/speech" `
118
+ -Method POST -ContentType "application/json" -Body $body `
119
+ -OutFile $TempFile -ErrorAction Stop
120
+ } catch {
121
+ Write-Host "[ERROR] API synthesis failed: $_" -ForegroundColor Red
122
+ exit 4
123
+ }
124
+ } elseif (Test-SopranoCLI) {
125
+ # CLI fallback - reloads model each call (slowest)
126
+ $SynthMode = "cli"
127
+ & soprano $Text -o $TempFile -d $SopranoDevice 2>$null
128
+ } else {
129
+ Write-Host "[ERROR] Soprano TTS not installed and no server running on port $SopranoPort" -ForegroundColor Red
130
+ Write-Host ""
131
+ Write-Host "Install: pip install soprano-tts" -ForegroundColor Yellow
132
+ Write-Host " (GPU): pip install soprano-tts[lmdeploy]" -ForegroundColor Yellow
133
+ Write-Host ""
134
+ Write-Host "Start WebUI: soprano-webui" -ForegroundColor Yellow
135
+ Write-Host "Start API: uvicorn soprano.server:app --host 127.0.0.1 --port $SopranoPort" -ForegroundColor Yellow
136
+ exit 2
137
+ }
138
+
139
+ # Verify output
140
+ if (-not (Test-Path $TempFile) -or (Get-Item $TempFile).Length -eq 0) {
141
+ Write-Host "[ERROR] Failed to synthesize speech with Soprano ($SynthMode mode)" -ForegroundColor Red
142
+ exit 4
143
+ }
144
+
145
+ # Play audio with proper resource cleanup (skip if AGENTVIBES_NO_PLAY is set)
146
+ if (-not $env:AGENTVIBES_NO_PLAY) {
147
+ $player = $null
148
+ try {
149
+ $player = New-Object System.Media.SoundPlayer $TempFile
150
+ $player.PlaySync()
151
+ } catch {
152
+ Write-Host "[ERROR] Audio playback failed: $_" -ForegroundColor Red
153
+ } finally {
154
+ if ($player) { $player.Dispose() }
155
+ }
156
+ }
157
+
158
+ Write-Host "Saved to: $TempFile"
159
+ Write-Host "Voice: Soprano-1.1-80M (Soprano TTS, $SynthMode mode)"