agentvibes 5.11.2 → 5.12.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 (54) hide show
  1. package/.claude/config/audio-effects.cfg +1 -1
  2. package/.claude/config/audio-effects.cfg.bak-kokoro +7 -0
  3. package/.claude/github-star-reminder.txt +1 -1
  4. package/.claude/hooks/audio-processor.sh +1 -1
  5. package/.claude/hooks/background-music-manager.sh +2 -1
  6. package/.claude/hooks/bmad-speak-enhanced.sh +37 -22
  7. package/.claude/hooks/bmad-speak.sh +26 -8
  8. package/.claude/hooks/play-tts-agentvibes-receiver-for-voiceless-connections.sh +1 -1
  9. package/.claude/hooks/play-tts-kokoro.sh +9 -0
  10. package/.claude/hooks/play-tts-piper.sh +13 -1
  11. package/.claude/hooks/play-tts-ssh-remote.sh +117 -7
  12. package/.claude/hooks/play-tts-termux-ssh.sh +1 -1
  13. package/.claude/hooks/play-tts.sh +106 -13
  14. package/.claude/hooks-windows/background-music-manager.ps1 +2 -1
  15. package/.claude/hooks-windows/play-tts-kokoro.ps1 +14 -0
  16. package/.claude/hooks-windows/play-tts.ps1 +163 -30
  17. package/.claude/hooks-windows/tts-watcher.ps1 +55 -0
  18. package/.claude/hooks-windows/voice-manager-windows.ps1 +101 -1
  19. package/README.md +11 -6
  20. package/RELEASE_NOTES.md +67 -0
  21. package/bin/mcp-server.sh +6 -19
  22. package/bin/resolve-utterance.js +137 -0
  23. package/mcp-server/server.py +280 -99
  24. package/mcp-server/test_mcp_correctness.py +486 -0
  25. package/mcp-server/test_windows_script_parity.py +341 -336
  26. package/package.json +3 -2
  27. package/setup-windows.ps1 +867 -815
  28. package/src/console/app.js +14 -11
  29. package/src/console/footer-config.js +0 -4
  30. package/src/console/navigation.js +0 -1
  31. package/src/console/tabs/agents-tab.js +96 -11
  32. package/src/console/tabs/music-tab.js +108 -3
  33. package/src/console/tabs/placeholder-tab.js +0 -2
  34. package/src/console/tabs/settings-tab.js +41 -2
  35. package/src/console/tabs/setup-tab.js +34 -6
  36. package/src/console/tabs/voices-tab.js +19 -9
  37. package/src/console/widgets/track-picker.js +3 -3
  38. package/src/i18n/de.js +0 -1
  39. package/src/i18n/en.js +0 -1
  40. package/src/i18n/es.js +0 -1
  41. package/src/i18n/fr.js +0 -1
  42. package/src/i18n/hi.js +0 -1
  43. package/src/i18n/ja.js +0 -1
  44. package/src/i18n/ko.js +0 -1
  45. package/src/i18n/pt.js +0 -1
  46. package/src/i18n/zh-CN.js +0 -1
  47. package/src/installer.js +205 -15
  48. package/src/services/config-service.js +264 -264
  49. package/src/services/llm-provider-service.js +7 -7
  50. package/src/services/navigation-service.js +1 -1
  51. package/src/services/utterance-loader.js +280 -0
  52. package/src/services/utterance-resolver.js +526 -0
  53. package/templates/agentvibes-receiver.sh +74 -12
  54. package/voice-assignments.json +8245 -0
package/setup-windows.ps1 CHANGED
@@ -1,815 +1,867 @@
1
- #
2
- # File: setup-windows.ps1
3
- #
4
- # AgentVibes Windows Native Setup Script
5
- # Installs and configures AgentVibes for Windows
6
- #
7
- # Usage:
8
- # .\setup-windows.ps1 # Interactive setup
9
- # .\setup-windows.ps1 -Provider soprano # Skip provider prompt
10
- #
11
-
12
- param(
13
- [Parameter(Mandatory = $false)]
14
- [ValidateSet('soprano', 'piper', 'sapi')]
15
- [string]$Provider
16
- )
17
-
18
- $ErrorActionPreference = "Stop"
19
-
20
- # ── UI Helper Functions ─────────────────────────────────────
21
-
22
- $BoxWidth = 58
23
-
24
- function Write-BoxTop {
25
- Write-Host " $([char]0x256D)$([string]::new([char]0x2500, $BoxWidth))$([char]0x256E)" -ForegroundColor Cyan
26
- }
27
-
28
- function Write-BoxBottom {
29
- Write-Host " $([char]0x2570)$([string]::new([char]0x2500, $BoxWidth))$([char]0x256F)" -ForegroundColor Cyan
30
- }
31
-
32
- function Write-BoxLine([string]$text, [string]$color = "White") {
33
- $padding = $BoxWidth - $text.Length
34
- if ($padding -lt 0) { $padding = 0; $text = $text.Substring(0, $BoxWidth) }
35
- Write-Host " $([char]0x2502)" -ForegroundColor Cyan -NoNewline
36
- Write-Host "$text$([string]::new(' ', $padding))" -ForegroundColor $color -NoNewline
37
- Write-Host "$([char]0x2502)" -ForegroundColor Cyan
38
- }
39
-
40
- function Write-BoxEmpty {
41
- Write-BoxLine ([string]::new(' ', $BoxWidth)) "White"
42
- }
43
-
44
- function Write-Section([string]$label) {
45
- Write-Host ""
46
- Write-Host " $([string]::new([char]0x2501, 56))" -ForegroundColor Yellow
47
- Write-Host " $label" -ForegroundColor Yellow
48
- Write-Host " $([string]::new([char]0x2501, 56))" -ForegroundColor Yellow
49
- Write-Host ""
50
- }
51
-
52
- function Write-Ok([string]$text) {
53
- Write-Host " [OK] $text" -ForegroundColor Green
54
- }
55
-
56
- function Write-Warn([string]$text) {
57
- Write-Host " [!!] $text" -ForegroundColor Yellow
58
- }
59
-
60
- function Write-Err([string]$text) {
61
- Write-Host " [XX] $text" -ForegroundColor Red
62
- }
63
-
64
- function Write-Info([string]$text) {
65
- Write-Host " $text" -ForegroundColor Gray
66
- }
67
-
68
- function Write-Item([string]$icon, [string]$name, [string]$desc) {
69
- $nameWidth = 30
70
- $paddedName = $name.PadRight($nameWidth)
71
- Write-Host " $icon " -ForegroundColor Green -NoNewline
72
- Write-Host $paddedName -ForegroundColor White -NoNewline
73
- Write-Host $desc -ForegroundColor DarkGray
74
- }
75
-
76
- # ── Read Version ────────────────────────────────────────────
77
-
78
- $Version = "unknown"
79
- $ScriptDir = $PSScriptRoot
80
- $PackageJson = Join-Path $ScriptDir "package.json"
81
- if (Test-Path $PackageJson) {
82
- try {
83
- $pkg = Get-Content $PackageJson -Raw | ConvertFrom-Json
84
- $Version = $pkg.version
85
- } catch {}
86
- }
87
-
88
- # ── Banner ──────────────────────────────────────────────────
89
-
90
- # ANSI Shadow figlet art matching the Node.js installer
91
- $AgentArt = @(
92
- " █████╗ ██████╗ ███████╗███╗ ██╗████████╗",
93
- "██╔══██╗██╔════╝ ██╔════╝████╗ ██║╚══██╔══╝",
94
- "███████║██║ ███╗█████╗ ██╔██╗ ██║ ██║ ",
95
- "██╔══██║██║ ██║██╔══╝ ██║╚██╗██║ ██║ ",
96
- "██║ ██║╚██████╔╝███████╗██║ ╚████║ ██║ ",
97
- "╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═══╝ ╚═╝ "
98
- )
99
- $VibesArt = @(
100
- "██╗ ██╗██╗██████╗ ███████╗███████╗",
101
- "██║ ██║██║██╔══██╗██╔════╝██╔════╝",
102
- "██║ ██║██║██████╔╝█████╗ ███████╗",
103
- "╚██╗ ██╔╝██║██╔══██╗██╔══╝ ╚════██║",
104
- " ╚████╔╝ ██║██████╔╝███████╗███████║",
105
- " ╚═══╝ ╚═╝╚═════╝ ╚══════╝╚══════╝"
106
- )
107
-
108
- Write-Host ""
109
- for ($i = 0; $i -lt $AgentArt.Count; $i++) {
110
- Write-Host $AgentArt[$i] -ForegroundColor Cyan -NoNewline
111
- Write-Host " " -NoNewline
112
- Write-Host $VibesArt[$i] -ForegroundColor Magenta
113
- }
114
- Write-Host ""
115
- Write-BoxTop
116
- Write-BoxEmpty
117
- Write-BoxLine " Windows Native Setup v$Version" "Cyan"
118
- Write-BoxEmpty
119
- Write-BoxLine " Now your AI Agents can finally talk back!" "White"
120
- Write-BoxLine " Professional TTS for Claude Code on Windows" "DarkGray"
121
- Write-BoxEmpty
122
- Write-BoxLine " https://agentvibes.org" "DarkCyan"
123
- Write-BoxBottom
124
- Write-Host ""
125
-
126
- # ── What's New ──────────────────────────────────────────────
127
-
128
- Write-Host " WHAT'S NEW in v$Version" -ForegroundColor Green
129
- Write-Host ""
130
- Write-Host " Native Windows support with three TTS providers (Soprano neural," -ForegroundColor Cyan
131
- Write-Host " Piper offline, Windows SAPI), background music from 16 genre tracks," -ForegroundColor Cyan
132
- Write-Host " reverb effects via ffmpeg, and verbosity control." -ForegroundColor Cyan
133
- Write-Host ""
134
- Write-Host " Thanks to @nathanchase (Soprano), @alexeyv (Windows SAPI)," -ForegroundColor Yellow
135
- Write-Host " @bmadcode (BMAD Method)!" -ForegroundColor Yellow
136
- Write-Host ""
137
- Write-Host " KEY HIGHLIGHTS:" -ForegroundColor Green
138
- Write-Host " [*] Native Windows TTS - Soprano, Piper, SAPI. No WSL needed!" -ForegroundColor Gray
139
- Write-Host " [*] Background Music - 16 genre tracks (Bachata, Flamenco, etc.)" -ForegroundColor Gray
140
- Write-Host " [*] Reverb & Effects - 5 reverb levels via ffmpeg aecho filter" -ForegroundColor Gray
141
- Write-Host " [*] Verbosity Control - High, Medium, or Low transparency" -ForegroundColor Gray
142
- Write-Host " [*] Beautiful Installer - Figlet banner, provider detection" -ForegroundColor Gray
143
- Write-Host " [*] 93/93 Tests Passing - 46 Windows + 47 cross-platform" -ForegroundColor Gray
144
- Write-Host ""
145
-
146
- # ── Node.js Detection ──────────────────────────────────────
147
-
148
- try {
149
- $nodeVersion = & node --version 2>$null
150
- if ($nodeVersion) {
151
- Write-Host " Node.js detected " -ForegroundColor Gray -NoNewline
152
- Write-Host "($nodeVersion)" -ForegroundColor Green
153
- Write-Host ""
154
- Write-Host " Tip: For a richer install experience, you can also run:" -ForegroundColor DarkGray
155
- Write-Host " npx agentvibes install" -ForegroundColor Cyan
156
- Write-Host ""
157
- $choice = Read-Host " Launch the Node.js installer instead? (y/N)"
158
- if ($choice -eq 'y' -or $choice -eq 'Y') {
159
- Write-Host ""
160
- Write-Host " Launching Node.js installer..." -ForegroundColor Cyan
161
- Write-Host ""
162
- $installerPath = Join-Path $ScriptDir "src\installer.js"
163
- if (Test-Path $installerPath) {
164
- & node $installerPath install
165
- } else {
166
- & npx agentvibes install
167
- }
168
- exit 0
169
- }
170
- Write-Host ""
171
- Write-Host " Continuing with PowerShell installer..." -ForegroundColor Gray
172
- }
173
- } catch {}
174
-
175
- # ── PowerShell Version Check ───────────────────────────────
176
-
177
- Write-Section "System Check"
178
-
179
- $PSVersionRequired = [Version]"5.1"
180
- if ($PSVersionTable.PSVersion -lt $PSVersionRequired) {
181
- Write-Err "PowerShell 5.1+ required (found $($PSVersionTable.PSVersion))"
182
- exit 1
183
- }
184
- Write-Ok "PowerShell $($PSVersionTable.PSVersion)"
185
-
186
- # ── Detect Project Root ─────────────────────────────────────
187
-
188
- if ($ScriptDir -match '[\\/]node_modules[\\/]agentvibes$') {
189
- $ProjectRoot = (Resolve-Path (Join-Path $ScriptDir "..\..")).Path
190
- Write-Ok "Detected npm package in: $ProjectRoot"
191
- } else {
192
- $ProjectRoot = $ScriptDir
193
- Write-Ok "Project root: $ProjectRoot"
194
- }
195
-
196
- # ── Setup Directories ───────────────────────────────────────
197
-
198
- Write-Section "Setting Up Directories"
199
-
200
- Write-Host " These directories store AgentVibes config and data:" -ForegroundColor Gray
201
- Write-Host ""
202
-
203
- $ProjectClaudeDir = "$ProjectRoot\.claude"
204
- $HooksDir = "$ProjectClaudeDir\hooks-windows"
205
- $UserClaudeDir = "$env:USERPROFILE\.claude"
206
- $AudioDir = "$UserClaudeDir\audio"
207
- $VoicesDir = "$UserClaudeDir\piper-voices"
208
- $PiperDir = "$env:LOCALAPPDATA\Programs\Piper"
209
- $PiperExe = "$PiperDir\piper.exe"
210
-
211
- # Directory info: path, description, category
212
- $DirInfo = @(
213
- @{ Path = $ProjectClaudeDir; Desc = "Project settings and hooks config"; Cat = "Project" },
214
- @{ Path = $HooksDir; Desc = "PowerShell TTS hook scripts"; Cat = "Project" },
215
- @{ Path = $UserClaudeDir; Desc = "Global AgentVibes configuration"; Cat = "User Profile" },
216
- @{ Path = $AudioDir; Desc = "TTS audio file cache"; Cat = "User Profile" },
217
- @{ Path = $VoicesDir; Desc = "Piper voice model files"; Cat = "User Profile" },
218
- @{ Path = $PiperDir; Desc = "Piper TTS engine binary"; Cat = "Applications" }
219
- )
220
-
221
- $CurrentCat = ""
222
- foreach ($entry in $DirInfo) {
223
- if ($entry.Cat -ne $CurrentCat) {
224
- if ($CurrentCat -ne "") { Write-Host "" }
225
- Write-Host " $($entry.Cat)" -ForegroundColor Cyan
226
- $CurrentCat = $entry.Cat
227
- }
228
-
229
- $dir = $entry.Path
230
- $existed = Test-Path $dir
231
- if (-not $existed) {
232
- New-Item -ItemType Directory -Path $dir -Force | Out-Null
233
- }
234
-
235
- # Show relative path where possible
236
- $displayPath = $dir
237
- if ($dir.StartsWith($ProjectRoot)) {
238
- $displayPath = $dir.Substring($ProjectRoot.Length + 1)
239
- } elseif ($dir.StartsWith($env:USERPROFILE)) {
240
- $displayPath = "~" + $dir.Substring($env:USERPROFILE.Length)
241
- } elseif ($dir.StartsWith($env:LOCALAPPDATA)) {
242
- $displayPath = "%LOCALAPPDATA%" + $dir.Substring($env:LOCALAPPDATA.Length)
243
- }
244
-
245
- $status = if ($existed) { "[OK]" } else { "[OK]" }
246
- $action = if ($existed) { "exists" } else { "created" }
247
-
248
- Write-Host " $status " -ForegroundColor Green -NoNewline
249
- Write-Host $displayPath.PadRight(32) -ForegroundColor White -NoNewline
250
- Write-Host $entry.Desc -ForegroundColor DarkGray
251
- }
252
-
253
- # ── Install Hook Scripts ────────────────────────────────────
254
-
255
- Write-Section "Installing TTS Scripts"
256
-
257
- Write-Host " These scripts enable Claude Code to speak on Windows:" -ForegroundColor Gray
258
- Write-Host ""
259
-
260
- $SourceHooksDir = Join-Path $ScriptDir ".claude\hooks-windows"
261
-
262
- # Validate source path is within the script directory (path traversal prevention)
263
- $ResolvedSource = (Resolve-Path -Path $SourceHooksDir -ErrorAction SilentlyContinue).Path
264
- $ResolvedScript = (Resolve-Path -Path $ScriptDir).Path
265
-
266
- if (-not $ResolvedSource -or -not $ResolvedSource.StartsWith($ResolvedScript)) {
267
- Write-Err "Hook scripts source path is outside the project directory"
268
- exit 1
269
- }
270
-
271
- if (-not (Test-Path $SourceHooksDir)) {
272
- Write-Err "Hook scripts not found at: $SourceHooksDir"
273
- Write-Info "Make sure you're running this from the AgentVibes project directory"
274
- exit 1
275
- }
276
-
277
- # Ensure destination hooks directory exists
278
- if (-not (Test-Path $HooksDir)) {
279
- New-Item -ItemType Directory -Path $HooksDir -Force | Out-Null
280
- }
281
-
282
- # Script info: filename, description
283
- $HookScriptInfo = @(
284
- @{ Name = "play-tts.ps1"; Desc = "Main TTS router - dispatches to active provider" },
285
- @{ Name = "play-tts-soprano.ps1"; Desc = "Soprano neural voice provider (fastest)" },
286
- @{ Name = "play-tts-windows-piper.ps1"; Desc = "Piper offline neural voice provider" },
287
- @{ Name = "play-tts-windows-sapi.ps1"; Desc = "Windows built-in SAPI voice provider" },
288
- @{ Name = "provider-manager.ps1"; Desc = "Switch between TTS providers" },
289
- @{ Name = "voice-manager-windows.ps1"; Desc = "Browse and select voice models" },
290
- @{ Name = "audio-cache-utils.ps1"; Desc = "Manage TTS audio file cache" },
291
- @{ Name = "session-start-tts.ps1"; Desc = "Auto-activates TTS when Claude starts" }
292
- )
293
-
294
- $CopiedCount = 0
295
- foreach ($info in $HookScriptInfo) {
296
- $script = $info.Name
297
- $SourceFile = Join-Path $SourceHooksDir $script
298
- $DestFile = Join-Path $HooksDir $script
299
-
300
- if (Test-Path $SourceFile) {
301
- # Skip if source and destination are the same file (running from project root)
302
- $resolvedSrc = (Resolve-Path $SourceFile).Path
303
- $resolvedDst = if (Test-Path $DestFile) { (Resolve-Path $DestFile).Path } else { "" }
304
- if ($resolvedSrc -eq $resolvedDst) {
305
- Write-Item "[OK]" $script $info.Desc
306
- } else {
307
- Copy-Item -Path $SourceFile -Destination $DestFile -Force
308
- Write-Item "[OK]" $script $info.Desc
309
- }
310
- $CopiedCount++
311
- }
312
- else {
313
- Write-Host " [!!] " -ForegroundColor Yellow -NoNewline
314
- Write-Host "$($script.PadRight(30))" -ForegroundColor White -NoNewline
315
- Write-Host "not found" -ForegroundColor Yellow
316
- }
317
- }
318
-
319
- if ($CopiedCount -eq 0) {
320
- Write-Err "No hook scripts were installed"
321
- exit 1
322
- }
323
-
324
- Write-Host ""
325
- Write-Host " Location: " -ForegroundColor Gray -NoNewline
326
- Write-Host ".claude\hooks-windows\" -ForegroundColor Cyan
327
- Write-Ok "Installed $CopiedCount TTS scripts"
328
-
329
- # ── Provider Selection ──────────────────────────────────────
330
-
331
- Write-Section "Choose Your TTS Provider"
332
-
333
- # Check if pip is available
334
- $PipAvailable = $false
335
- try {
336
- $pipTest = & pip --version 2>$null
337
- if ($pipTest) { $PipAvailable = $true }
338
- } catch {}
339
-
340
- # Check Soprano availability
341
- $SopranoAvailable = $false
342
- try {
343
- $response = Invoke-WebRequest -Uri "http://127.0.0.1:7860/api/predict" `
344
- -Method POST -UseBasicParsing -TimeoutSec 2 `
345
- -ContentType "application/json" `
346
- -Body '{"data":["test",1.0],"fn_index":0}' `
347
- -ErrorAction SilentlyContinue
348
- if ($response.StatusCode -eq 200) { $SopranoAvailable = $true }
349
- } catch {}
350
-
351
- # Check if pip has soprano-tts (only if pip is available)
352
- $SopranoInstalled = $false
353
- if ($PipAvailable) {
354
- try {
355
- $pipResult = & pip show soprano-tts 2>$null
356
- if ($pipResult) { $SopranoInstalled = $true }
357
- } catch {}
358
- }
359
-
360
- if (-not $Provider) {
361
- Write-Host " [1] Soprano (Best Quality)" -ForegroundColor White
362
- Write-Host " Ultra-fast neural TTS, 80M parameter model" -ForegroundColor DarkGray
363
- if ($SopranoAvailable) {
364
- Write-Host " Status: " -ForegroundColor DarkGray -NoNewline
365
- Write-Host "Running on port 7860" -ForegroundColor Green
366
- } elseif ($SopranoInstalled) {
367
- Write-Host " Status: " -ForegroundColor DarkGray -NoNewline
368
- Write-Host "Installed (not running)" -ForegroundColor Yellow
369
- } else {
370
- Write-Host " Requires: pip install soprano-tts" -ForegroundColor DarkGray
371
- }
372
- Write-Host ""
373
-
374
- Write-Host " [2] Piper (Recommended Offline)" -ForegroundColor White
375
- Write-Host " High quality neural voices, works fully offline" -ForegroundColor DarkGray
376
- if (Test-Path $PiperExe) {
377
- Write-Host " Status: " -ForegroundColor DarkGray -NoNewline
378
- Write-Host "Installed" -ForegroundColor Green
379
- } else {
380
- Write-Host " Downloads: ~100MB (piper.exe + voice model)" -ForegroundColor DarkGray
381
- }
382
- Write-Host ""
383
-
384
- Write-Host " [3] Windows SAPI (Zero Setup)" -ForegroundColor White
385
- Write-Host " Built-in Windows voices (David, Zira, Mark)" -ForegroundColor DarkGray
386
- Write-Host " No download required" -ForegroundColor DarkGray
387
- Write-Host ""
388
-
389
- $choice = Read-Host " Enter choice (1-3, default: 1)"
390
-
391
- switch ($choice) {
392
- "2" { $Provider = "piper" }
393
- "3" { $Provider = "sapi" }
394
- default { $Provider = "soprano" }
395
- }
396
- }
397
-
398
- $ProviderDisplayName = switch ($Provider) {
399
- "soprano" { "Soprano TTS" }
400
- "piper" { "Piper TTS" }
401
- "sapi" { "Windows SAPI" }
402
- }
403
-
404
- Write-Host ""
405
- Write-Ok "Selected: $ProviderDisplayName"
406
-
407
- # ── Install Soprano if selected ─────────────────────────────
408
-
409
- if ($Provider -eq "soprano") {
410
- Write-Section "Soprano Setup"
411
-
412
- if ($SopranoAvailable) {
413
- Write-Ok "Soprano server running on port 7860"
414
- } elseif ($SopranoInstalled) {
415
- Write-Warn "Soprano is installed but not running"
416
- Write-Info "Start it with: soprano-tts --share"
417
- Write-Info "Or run it in WSL and forward port 7860"
418
- } else {
419
- Write-Warn "Soprano TTS not detected"
420
- Write-Host ""
421
-
422
- if (-not $PipAvailable) {
423
- Write-Error "pip is not available on this system"
424
- Write-Info "Please install Python 3 with pip, then run:"
425
- Write-Host " pip install soprano-tts" -ForegroundColor Cyan
426
- Write-Host ""
427
- } else {
428
- # Offer to install Soprano
429
- $installChoice = Read-Host "Would you like to install Soprano now? (y/n, default: y)"
430
-
431
- if ($installChoice -eq "" -or $installChoice -eq "y" -or $installChoice -eq "Y") {
432
- Write-Info "Installing Soprano TTS..."
433
- Write-Host ""
434
-
435
- try {
436
- & pip install soprano-tts 2>&1 | Tee-Object -Variable pipOutput | Write-Host
437
-
438
- # Re-check if installation succeeded
439
- $SopranoInstalled = $false
440
- try {
441
- $pipResult = & pip show soprano-tts 2>$null
442
- if ($pipResult) { $SopranoInstalled = $true }
443
- } catch {}
444
-
445
- if ($SopranoInstalled) {
446
- Write-Ok "Soprano TTS installed successfully!"
447
- Write-Info "Start it with: soprano-tts --share"
448
- } else {
449
- Write-Error "Installation may have failed. Please check the output above."
450
- Write-Info "You can try installing manually: pip install soprano-tts"
451
- }
452
- } catch {
453
- Write-Error "Installation failed: $_"
454
- Write-Info "Please install manually: pip install soprano-tts"
455
- }
456
- } else {
457
- Write-Host ""
458
- Write-Host " To install Soprano manually:" -ForegroundColor White
459
- Write-Host " pip install soprano-tts" -ForegroundColor Cyan
460
- Write-Host ""
461
- Write-Host " Or use Soprano in WSL with port forwarding:" -ForegroundColor White
462
- Write-Host " ssh -L 7860:localhost:7860 your-wsl-host" -ForegroundColor Cyan
463
- Write-Host ""
464
- Write-Info "AgentVibes will work once Soprano is accessible on port 7860"
465
- }
466
- }
467
- }
468
- }
469
-
470
- # ── Install Piper if selected ───────────────────────────────
471
-
472
- if ($Provider -eq "piper") {
473
- Write-Section "Piper Installation"
474
-
475
- if (Test-Path $PiperExe) {
476
- Write-Ok "Piper already installed at: $PiperDir"
477
- }
478
- else {
479
- Write-Host " Downloading Piper TTS engine from GitHub..." -ForegroundColor White
480
-
481
- $DownloadUrl = "https://github.com/rhasspy/piper/releases/download/2023.11.14-2/piper_windows_amd64.zip"
482
- $DownloadFile = "$env:TEMP\piper-windows.zip"
483
-
484
- try {
485
- Write-Info $DownloadUrl
486
-
487
- [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
488
- Invoke-WebRequest -Uri $DownloadUrl -OutFile $DownloadFile -UseBasicParsing -ErrorAction Stop
489
-
490
- Write-Ok "Downloaded ($('{0:N2}' -f ((Get-Item $DownloadFile).Length / 1MB)) MB)"
491
-
492
- Write-Host " Extracting..." -ForegroundColor Gray
493
- $TempExtractDir = "$env:TEMP\piper-extract"
494
- Remove-Item -Path $TempExtractDir -Recurse -Force -ErrorAction SilentlyContinue
495
- Expand-Archive -Path $DownloadFile -DestinationPath $TempExtractDir -Force
496
-
497
- # Find piper.exe recursively
498
- $PiperFound = Get-ChildItem -Path $TempExtractDir -Name "piper.exe" -Recurse
499
-
500
- if ($PiperFound) {
501
- $PiperExePath = (Get-ChildItem -Path $TempExtractDir -Filter "piper.exe" -Recurse).FullName | Select-Object -First 1
502
- $PiperSourceDir = Split-Path -Parent $PiperExePath
503
-
504
- Copy-Item -Path "$PiperSourceDir\*" -Destination $PiperDir -Recurse -Force
505
-
506
- if (Test-Path $PiperExe) {
507
- Write-Ok "Piper installed to: $PiperDir"
508
- }
509
- else {
510
- throw "Failed to copy Piper executable"
511
- }
512
- }
513
- else {
514
- throw "piper.exe not found in extracted files"
515
- }
516
-
517
- # Cleanup
518
- Remove-Item -Path $TempExtractDir -Recurse -Force -ErrorAction SilentlyContinue
519
- Remove-Item $DownloadFile -Force -ErrorAction SilentlyContinue
520
- }
521
- catch {
522
- Write-Err "Failed to install Piper: $_"
523
- Write-Info "Please check your internet connection and try again"
524
- exit 1
525
- }
526
- }
527
-
528
- # Download default voice
529
- Write-Host ""
530
- Write-Host " Setting up default voice model..." -ForegroundColor White
531
-
532
- $DefaultVoice = "en_US-ryan-high"
533
- $VoiceDir = $VoicesDir
534
-
535
- if ((Test-Path "$VoiceDir\$DefaultVoice.onnx") -and (Test-Path "$VoiceDir\$DefaultVoice.onnx.json")) {
536
- Write-Ok "Default voice ($DefaultVoice) already downloaded"
537
- }
538
- else {
539
- Write-Host " Downloading $DefaultVoice..." -ForegroundColor Gray
540
-
541
- try {
542
- $HFBase = "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/ryan/high"
543
-
544
- [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
545
-
546
- Write-Host " Downloading model file..." -ForegroundColor Gray
547
- Invoke-WebRequest -Uri "$HFBase/$DefaultVoice.onnx" `
548
- -OutFile "$VoiceDir\$DefaultVoice.onnx" `
549
- -UseBasicParsing -ErrorAction Stop
550
-
551
- Write-Host " Downloading config file..." -ForegroundColor Gray
552
- Invoke-WebRequest -Uri "$HFBase/$DefaultVoice.onnx.json" `
553
- -OutFile "$VoiceDir\$DefaultVoice.onnx.json" `
554
- -UseBasicParsing -ErrorAction Stop
555
-
556
- Write-Ok "Voice model downloaded: $DefaultVoice"
557
- }
558
- catch {
559
- Write-Warn "Could not download voice: $_"
560
- Write-Info "You can download voices manually later"
561
- }
562
- }
563
- }
564
-
565
- # ── Set Active Provider ─────────────────────────────────────
566
-
567
- Write-Section "Configuring Provider"
568
-
569
- $ProviderFile = "$ProjectClaudeDir\tts-provider.txt"
570
- $ProviderName = switch ($Provider) {
571
- "soprano" { "soprano" }
572
- "piper" { "windows-piper" }
573
- "sapi" { "windows-sapi" }
574
- }
575
-
576
- Set-Content -Path $ProviderFile -Value $ProviderName -NoNewline
577
- Write-Ok "Active provider set to: $ProviderName"
578
- Write-Info "Config file: .claude\tts-provider.txt"
579
-
580
- # ── Background Music Selection ─────────────────────────
581
-
582
- Write-Section "Background Music"
583
-
584
- Write-Host " Play background music under TTS voice output?" -ForegroundColor Gray
585
- Write-Host " Requires ffmpeg (mixing voice + music)." -ForegroundColor DarkGray
586
- Write-Host ""
587
-
588
- # Check ffmpeg availability
589
- $HasFfmpeg = $false
590
- try {
591
- $null = Get-Command ffmpeg -ErrorAction Stop
592
- $HasFfmpeg = $true
593
- } catch {}
594
-
595
- $BgMusicEnabled = $false
596
- $BgMusicTrack = "agent_vibes_bachata_v1_loop.mp3"
597
- $BgMusicDisplayName = "Off"
598
-
599
- if (-not $HasFfmpeg) {
600
- Write-Warn "ffmpeg not found - background music requires ffmpeg"
601
- Write-Info "Install ffmpeg and re-run setup to enable background music"
602
- } else {
603
- Write-Host " [1] Yes - Enable background music (Recommended)" -ForegroundColor White
604
- Write-Host " [2] No - Voice only" -ForegroundColor White
605
- Write-Host ""
606
-
607
- $bgChoice = Read-Host " Enter choice (1-2, default: 1)"
608
-
609
- if ($bgChoice -eq "2") {
610
- $BgMusicEnabled = $false
611
- $BgMusicDisplayName = "Off"
612
- Write-Host ""
613
- Write-Ok "Background music: Disabled"
614
- } else {
615
- $BgMusicEnabled = $true
616
-
617
- # Track list with filenames and display info
618
- $Tracks = @(
619
- @{ File = "agentvibes_soft_flamenco_loop.mp3"; Name = "Soft Flamenco"; Desc = "Spanish guitar" },
620
- @{ File = "agent_vibes_bachata_v1_loop.mp3"; Name = "Bachata"; Desc = "Latin rhythm" },
621
- @{ File = "agent_vibes_bossa_nova_v2_loop.mp3"; Name = "Bossa Nova"; Desc = "Brazilian jazz" },
622
- @{ File = "agent_vibes_salsa_v2_loop.mp3"; Name = "Salsa"; Desc = "Latin dance" },
623
- @{ File = "agent_vibes_cumbia_v1_loop.mp3"; Name = "Cumbia"; Desc = "Colombian folk" },
624
- @{ File = "agent_vibes_japanese_city_pop_v1_loop.mp3"; Name = "Japanese City Pop"; Desc = "80s synth" },
625
- @{ File = "agent_vibes_chillwave_v2_loop.mp3"; Name = "Chillwave"; Desc = "Electronic ambient" },
626
- @{ File = "dreamy_house_loop.mp3"; Name = "Dreamy House"; Desc = "Electronic dance" },
627
- @{ File = "agent_vibes_dark_chill_step_loop.mp3"; Name = "Dark Chill Step"; Desc = "Electronic bass" },
628
- @{ File = "agent_vibes_goa_trance_v2_loop.mp3"; Name = "Goa Trance"; Desc = "Psychedelic electronic" },
629
- @{ File = "agent_vibes_harpsichord_v2_loop.mp3"; Name = "Harpsichord"; Desc = "Baroque classical" },
630
- @{ File = "agent_vibes_celtic_harp_v1_loop.mp3"; Name = "Celtic Harp"; Desc = "Irish traditional" },
631
- @{ File = "agent_vibes_hawaiian_slack_key_guitar_v2_loop.mp3"; Name = "Hawaiian Slack Key"; Desc = "Island guitar" },
632
- @{ File = "agent_vibes_arabic_v2_loop.mp3"; Name = "Arabic Oud"; Desc = "Middle Eastern" },
633
- @{ File = "agent_vibes_ganawa_ambient_v2_loop.mp3"; Name = "Gnawa Ambient"; Desc = "North African" },
634
- @{ File = "agent_vibes_tabla_dream_pop_v1_loop.mp3"; Name = "Tabla Dream Pop"; Desc = "Indian percussion" }
635
- )
636
-
637
- Write-Host ""
638
- Write-Host " Choose a background music genre:" -ForegroundColor White
639
- Write-Host ""
640
-
641
- for ($i = 0; $i -lt $Tracks.Count; $i++) {
642
- $num = ($i + 1).ToString().PadLeft(2)
643
- $trackName = $Tracks[$i].Name.PadRight(22)
644
- Write-Host " [$num] " -ForegroundColor White -NoNewline
645
- Write-Host $trackName -ForegroundColor Cyan -NoNewline
646
- Write-Host $Tracks[$i].Desc -ForegroundColor DarkGray
647
- }
648
-
649
- Write-Host ""
650
- $trackChoice = Read-Host " Enter choice (1-16, default: 2)"
651
-
652
- $trackIndex = 1 # default to Bachata (index 1)
653
- if ($trackChoice -match '^\d+$') {
654
- $parsed = [int]$trackChoice
655
- if ($parsed -ge 1 -and $parsed -le $Tracks.Count) {
656
- $trackIndex = $parsed - 1
657
- }
658
- }
659
-
660
- $BgMusicTrack = $Tracks[$trackIndex].File
661
- $BgMusicDisplayName = $Tracks[$trackIndex].Name
662
-
663
- Write-Host ""
664
- Write-Ok "Background music: $BgMusicDisplayName"
665
- }
666
- }
667
-
668
- # Write background music config
669
- $ConfigDir = "$ProjectClaudeDir\config"
670
- if (-not (Test-Path $ConfigDir)) {
671
- New-Item -ItemType Directory -Path $ConfigDir -Force | Out-Null
672
- }
673
-
674
- $bgEnabledValue = if ($BgMusicEnabled) { "true" } else { "false" }
675
- Set-Content -Path "$ConfigDir\background-music-enabled.txt" -Value $bgEnabledValue -NoNewline
676
- Set-Content -Path "$ConfigDir\background-music-default.txt" -Value $BgMusicTrack -NoNewline
677
- Set-Content -Path "$ConfigDir\background-music-volume.txt" -Value "0.10" -NoNewline
678
-
679
- # ── Audio Effects (Reverb) ─────────────────────────────
680
-
681
- Write-Section "Audio Effects"
682
-
683
- Write-Host " Add reverb effect to voice output?" -ForegroundColor Gray
684
- Write-Host ""
685
-
686
- $ReverbLevel = "off"
687
- $ReverbDisplayName = "Off"
688
-
689
- if (-not $HasFfmpeg) {
690
- Write-Warn "ffmpeg not found - reverb requires ffmpeg"
691
- Write-Info "Install ffmpeg and re-run setup to enable reverb"
692
- } else {
693
- Write-Host " [1] Off Dry, no reverb" -ForegroundColor White
694
- Write-Host " [2] Light Small room (Recommended)" -ForegroundColor White
695
- Write-Host " [3] Medium Conference room" -ForegroundColor White
696
- Write-Host " [4] Heavy Large hall" -ForegroundColor White
697
- Write-Host " [5] Cathedral Epic space" -ForegroundColor White
698
- Write-Host ""
699
-
700
- $reverbChoice = Read-Host " Enter choice (1-5, default: 2)"
701
-
702
- switch ($reverbChoice) {
703
- "1" { $ReverbLevel = "off"; $ReverbDisplayName = "Off" }
704
- "3" { $ReverbLevel = "medium"; $ReverbDisplayName = "Medium" }
705
- "4" { $ReverbLevel = "heavy"; $ReverbDisplayName = "Heavy" }
706
- "5" { $ReverbLevel = "cathedral"; $ReverbDisplayName = "Cathedral" }
707
- default { $ReverbLevel = "light"; $ReverbDisplayName = "Light" }
708
- }
709
-
710
- Write-Host ""
711
- Write-Ok "Reverb: $ReverbDisplayName"
712
- }
713
-
714
- # Write reverb config
715
- Set-Content -Path "$ConfigDir\reverb-level.txt" -Value $ReverbLevel -NoNewline
716
-
717
- # ── Verbosity / Transparency ──────────────────────────
718
-
719
- Write-Section "TTS Verbosity"
720
-
721
- Write-Host " How much should Claude speak during interactions?" -ForegroundColor Gray
722
- Write-Host ""
723
-
724
- Write-Host " [1] High Full reasoning, decisions, trade-offs" -ForegroundColor White
725
- Write-Host " [2] Medium Key updates and acknowledgments" -ForegroundColor White
726
- Write-Host " [3] Low Only essential messages" -ForegroundColor White
727
- Write-Host ""
728
-
729
- $verbChoice = Read-Host " Enter choice (1-3, default: 1)"
730
-
731
- $VerbosityLevel = "high"
732
- $VerbosityDisplayName = "High"
733
-
734
- switch ($verbChoice) {
735
- "2" { $VerbosityLevel = "medium"; $VerbosityDisplayName = "Medium" }
736
- "3" { $VerbosityLevel = "low"; $VerbosityDisplayName = "Low" }
737
- default { $VerbosityLevel = "high"; $VerbosityDisplayName = "High" }
738
- }
739
-
740
- Write-Host ""
741
- Write-Ok "Verbosity: $VerbosityDisplayName"
742
-
743
- # Write verbosity config
744
- Set-Content -Path "$ProjectClaudeDir\tts-verbosity.txt" -Value $VerbosityLevel -NoNewline
745
-
746
- # ── Test TTS ────────────────────────────────────────────────
747
-
748
- Write-Section "Testing TTS"
749
-
750
- Write-Host " Running a quick TTS test..." -ForegroundColor Gray
751
- Write-Host ""
752
-
753
- $TestMessage = "Agent Vibes here. Setup complete, ready to speak on Windows."
754
-
755
- try {
756
- switch ($Provider) {
757
- "soprano" {
758
- if ($SopranoAvailable) {
759
- & "$HooksDir\play-tts-soprano.ps1" $TestMessage | Out-Null
760
- Write-Ok "Soprano TTS is working"
761
- } else {
762
- Write-Warn "Soprano not running - skipping audio test"
763
- Write-Info "Start Soprano, then test with:"
764
- Write-Info ".\.claude\hooks-windows\play-tts.ps1 'Hello'"
765
- }
766
- }
767
- "piper" {
768
- & "$HooksDir\play-tts-windows-piper.ps1" $TestMessage | Out-Null
769
- Write-Ok "Piper TTS is working"
770
- }
771
- "sapi" {
772
- & "$HooksDir\play-tts-windows-sapi.ps1" $TestMessage | Out-Null
773
- Write-Ok "Windows SAPI is working"
774
- }
775
- }
776
- }
777
- catch {
778
- Write-Warn "TTS test failed: $_"
779
- Write-Info "AgentVibes should still work - check troubleshooting in WINDOWS-SETUP.md"
780
- }
781
-
782
- # ── Completion ──────────────────────────────────────────────
783
-
784
- Write-Host ""
785
- Write-BoxTop
786
- Write-BoxEmpty
787
- Write-BoxLine " Setup Complete!" "Green"
788
- Write-BoxEmpty
789
- Write-BoxLine " Provider: $ProviderDisplayName" "White"
790
- Write-BoxLine " Background: $BgMusicDisplayName" "White"
791
- Write-BoxLine " Reverb: $ReverbDisplayName" "White"
792
- Write-BoxLine " Verbosity: $VerbosityDisplayName" "White"
793
- Write-BoxLine " Version: $Version" "DarkGray"
794
- Write-BoxEmpty
795
- Write-BoxBottom
796
-
797
- Write-Host ""
798
- Write-Host " Quick Start Commands:" -ForegroundColor Cyan
799
- Write-Host ""
800
- Write-Host " Test TTS" -ForegroundColor White
801
- Write-Host " .\.claude\hooks-windows\play-tts.ps1 ""Hello from AgentVibes""" -ForegroundColor Gray
802
- Write-Host ""
803
- Write-Host " List voices" -ForegroundColor White
804
- Write-Host " .\.claude\hooks-windows\voice-manager-windows.ps1 list" -ForegroundColor Gray
805
- Write-Host ""
806
- Write-Host " Switch provider" -ForegroundColor White
807
- Write-Host " .\.claude\hooks-windows\provider-manager.ps1 set soprano" -ForegroundColor Gray
808
- Write-Host ""
809
- Write-Host " List providers" -ForegroundColor White
810
- Write-Host " .\.claude\hooks-windows\provider-manager.ps1 list" -ForegroundColor Gray
811
- Write-Host ""
812
-
813
- Write-Host " Documentation:" -ForegroundColor Cyan
814
- Write-Host " Setup Guide: WINDOWS-SETUP.md" -ForegroundColor Gray
815
- Write-Host ""
1
+ #
2
+ # File: setup-windows.ps1
3
+ #
4
+ # AgentVibes Windows Native Setup Script
5
+ # Installs and configures AgentVibes for Windows
6
+ #
7
+ # Usage:
8
+ # .\setup-windows.ps1 # Interactive setup
9
+ # .\setup-windows.ps1 -Provider soprano # Skip provider prompt
10
+ #
11
+
12
+ param(
13
+ [Parameter(Mandatory = $false)]
14
+ [ValidateSet('soprano', 'piper', 'sapi')]
15
+ [string]$Provider
16
+ )
17
+
18
+ $ErrorActionPreference = "Stop"
19
+
20
+ # ── UI Helper Functions ─────────────────────────────────────
21
+
22
+ $BoxWidth = 58
23
+
24
+ function Write-BoxTop {
25
+ Write-Host " $([char]0x256D)$([string]::new([char]0x2500, $BoxWidth))$([char]0x256E)" -ForegroundColor Cyan
26
+ }
27
+
28
+ function Write-BoxBottom {
29
+ Write-Host " $([char]0x2570)$([string]::new([char]0x2500, $BoxWidth))$([char]0x256F)" -ForegroundColor Cyan
30
+ }
31
+
32
+ function Write-BoxLine([string]$text, [string]$color = "White") {
33
+ $padding = $BoxWidth - $text.Length
34
+ if ($padding -lt 0) { $padding = 0; $text = $text.Substring(0, $BoxWidth) }
35
+ Write-Host " $([char]0x2502)" -ForegroundColor Cyan -NoNewline
36
+ Write-Host "$text$([string]::new(' ', $padding))" -ForegroundColor $color -NoNewline
37
+ Write-Host "$([char]0x2502)" -ForegroundColor Cyan
38
+ }
39
+
40
+ function Write-BoxEmpty {
41
+ Write-BoxLine ([string]::new(' ', $BoxWidth)) "White"
42
+ }
43
+
44
+ function Write-Section([string]$label) {
45
+ Write-Host ""
46
+ Write-Host " $([string]::new([char]0x2501, 56))" -ForegroundColor Yellow
47
+ Write-Host " $label" -ForegroundColor Yellow
48
+ Write-Host " $([string]::new([char]0x2501, 56))" -ForegroundColor Yellow
49
+ Write-Host ""
50
+ }
51
+
52
+ function Write-Ok([string]$text) {
53
+ Write-Host " [OK] $text" -ForegroundColor Green
54
+ }
55
+
56
+ function Write-Warn([string]$text) {
57
+ Write-Host " [!!] $text" -ForegroundColor Yellow
58
+ }
59
+
60
+ function Write-Err([string]$text) {
61
+ Write-Host " [XX] $text" -ForegroundColor Red
62
+ }
63
+
64
+ function Write-Info([string]$text) {
65
+ Write-Host " $text" -ForegroundColor Gray
66
+ }
67
+
68
+ function Write-Item([string]$icon, [string]$name, [string]$desc) {
69
+ $nameWidth = 30
70
+ $paddedName = $name.PadRight($nameWidth)
71
+ Write-Host " $icon " -ForegroundColor Green -NoNewline
72
+ Write-Host $paddedName -ForegroundColor White -NoNewline
73
+ Write-Host $desc -ForegroundColor DarkGray
74
+ }
75
+
76
+ # Non-Destructive Configuration Rule (CLAUDE.md): setup must never clobber a
77
+ # user-config value the Node installer deliberately preserves across re-runs.
78
+ # Writes the value ONLY when the file is absent; otherwise keeps the existing
79
+ # choice and reports it. Pass -Force for the rare case where an overwrite is
80
+ # genuinely intended.
81
+ function Set-ConfigValueSafe {
82
+ param(
83
+ [Parameter(Mandatory)][string]$Path,
84
+ [Parameter(Mandatory)][AllowEmptyString()][string]$Value,
85
+ [string]$Label = (Split-Path $Path -Leaf),
86
+ [switch]$Force
87
+ )
88
+ if ((Test-Path $Path) -and -not $Force) {
89
+ $current = (Get-Content -Path $Path -Raw -ErrorAction SilentlyContinue)
90
+ if ($null -ne $current) { $current = $current.Trim() }
91
+ Write-Info "Keeping existing $Label ($current)"
92
+ return
93
+ }
94
+ Set-Content -Path $Path -Value $Value -NoNewline
95
+ }
96
+
97
+ # For values driven by an interactive prompt: honor the user's EXPLICIT answer
98
+ # (non-empty $RawInput) this run, but if they just pressed Enter to accept the
99
+ # default — or the prompt was skipped entirely (e.g. ffmpeg missing) — keep any
100
+ # existing value rather than silently overwriting it with the default. This
101
+ # resolves the bug where the script printed the user's new choice yet wrote the
102
+ # default: honor real input, preserve config on blind-Enter / skipped prompts.
103
+ function Set-ConfigFromChoice {
104
+ param(
105
+ [Parameter(Mandatory)][string]$Path,
106
+ [Parameter(Mandatory)][AllowEmptyString()][string]$Value,
107
+ [Parameter(Mandatory)][AllowEmptyString()][string]$RawInput,
108
+ [string]$Label = (Split-Path $Path -Leaf)
109
+ )
110
+ if ([string]::IsNullOrWhiteSpace($RawInput) -and (Test-Path $Path)) {
111
+ $current = (Get-Content -Path $Path -Raw -ErrorAction SilentlyContinue)
112
+ if ($null -ne $current) { $current = $current.Trim() }
113
+ Write-Info "Keeping existing $Label ($current)"
114
+ return
115
+ }
116
+ Set-Content -Path $Path -Value $Value -NoNewline
117
+ }
118
+
119
+ # ── Read Version ────────────────────────────────────────────
120
+
121
+ $Version = "unknown"
122
+ $ScriptDir = $PSScriptRoot
123
+ $PackageJson = Join-Path $ScriptDir "package.json"
124
+ if (Test-Path $PackageJson) {
125
+ try {
126
+ $pkg = Get-Content $PackageJson -Raw | ConvertFrom-Json
127
+ $Version = $pkg.version
128
+ } catch {}
129
+ }
130
+
131
+ # ── Banner ──────────────────────────────────────────────────
132
+
133
+ # ANSI Shadow figlet art matching the Node.js installer
134
+ $AgentArt = @(
135
+ " █████╗ ██████╗ ███████╗███╗ ██╗████████╗",
136
+ "██╔══██╗██╔════╝ ██╔════╝████╗ ██║╚══██╔══╝",
137
+ "███████║██║ ███╗█████╗ ██╔██╗ ██║ ██║ ",
138
+ "██╔══██║██║ ██║██╔══╝ ██║╚██╗██║ ██║ ",
139
+ "██║ ██║╚██████╔╝███████╗██║ ╚████║ ██║ ",
140
+ "╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═══╝ ╚═╝ "
141
+ )
142
+ $VibesArt = @(
143
+ "██╗ ██╗██╗██████╗ ███████╗███████╗",
144
+ "██║ ██║██║██╔══██╗██╔════╝██╔════╝",
145
+ "██║ ██║██║██████╔╝█████╗ ███████╗",
146
+ "╚██╗ ██╔╝██║██╔══██╗██╔══╝ ╚════██║",
147
+ " ╚████╔╝ ██║██████╔╝███████╗███████║",
148
+ " ╚═══╝ ╚═╝╚═════╝ ╚══════╝╚══════╝"
149
+ )
150
+
151
+ Write-Host ""
152
+ for ($i = 0; $i -lt $AgentArt.Count; $i++) {
153
+ Write-Host $AgentArt[$i] -ForegroundColor Cyan -NoNewline
154
+ Write-Host " " -NoNewline
155
+ Write-Host $VibesArt[$i] -ForegroundColor Magenta
156
+ }
157
+ Write-Host ""
158
+ Write-BoxTop
159
+ Write-BoxEmpty
160
+ Write-BoxLine " Windows Native Setup v$Version" "Cyan"
161
+ Write-BoxEmpty
162
+ Write-BoxLine " Now your AI Agents can finally talk back!" "White"
163
+ Write-BoxLine " Professional TTS for Claude Code on Windows" "DarkGray"
164
+ Write-BoxEmpty
165
+ Write-BoxLine " https://agentvibes.org" "DarkCyan"
166
+ Write-BoxBottom
167
+ Write-Host ""
168
+
169
+ # ── What's New ──────────────────────────────────────────────
170
+
171
+ Write-Host " WHAT'S NEW in v$Version" -ForegroundColor Green
172
+ Write-Host ""
173
+ Write-Host " Native Windows support with three TTS providers (Soprano neural," -ForegroundColor Cyan
174
+ Write-Host " Piper offline, Windows SAPI), background music from 16 genre tracks," -ForegroundColor Cyan
175
+ Write-Host " reverb effects via ffmpeg, and verbosity control." -ForegroundColor Cyan
176
+ Write-Host ""
177
+ Write-Host " Thanks to @nathanchase (Soprano), @alexeyv (Windows SAPI)," -ForegroundColor Yellow
178
+ Write-Host " @bmadcode (BMAD Method)!" -ForegroundColor Yellow
179
+ Write-Host ""
180
+ Write-Host " KEY HIGHLIGHTS:" -ForegroundColor Green
181
+ Write-Host " [*] Native Windows TTS - Soprano, Piper, SAPI. No WSL needed!" -ForegroundColor Gray
182
+ Write-Host " [*] Background Music - 16 genre tracks (Bachata, Flamenco, etc.)" -ForegroundColor Gray
183
+ Write-Host " [*] Reverb & Effects - 5 reverb levels via ffmpeg aecho filter" -ForegroundColor Gray
184
+ Write-Host " [*] Verbosity Control - High, Medium, or Low transparency" -ForegroundColor Gray
185
+ Write-Host " [*] Beautiful Installer - Figlet banner, provider detection" -ForegroundColor Gray
186
+ Write-Host " [*] 93/93 Tests Passing - 46 Windows + 47 cross-platform" -ForegroundColor Gray
187
+ Write-Host ""
188
+
189
+ # ── Node.js Detection ──────────────────────────────────────
190
+
191
+ try {
192
+ $nodeVersion = & node --version 2>$null
193
+ if ($nodeVersion) {
194
+ Write-Host " Node.js detected " -ForegroundColor Gray -NoNewline
195
+ Write-Host "($nodeVersion)" -ForegroundColor Green
196
+ Write-Host ""
197
+ Write-Host " Tip: For a richer install experience, you can also run:" -ForegroundColor DarkGray
198
+ Write-Host " npx agentvibes install" -ForegroundColor Cyan
199
+ Write-Host ""
200
+ $choice = Read-Host " Launch the Node.js installer instead? (y/N)"
201
+ if ($choice -eq 'y' -or $choice -eq 'Y') {
202
+ Write-Host ""
203
+ Write-Host " Launching Node.js installer..." -ForegroundColor Cyan
204
+ Write-Host ""
205
+ $installerPath = Join-Path $ScriptDir "src\installer.js"
206
+ if (Test-Path $installerPath) {
207
+ & node $installerPath install
208
+ } else {
209
+ & npx agentvibes install
210
+ }
211
+ exit 0
212
+ }
213
+ Write-Host ""
214
+ Write-Host " Continuing with PowerShell installer..." -ForegroundColor Gray
215
+ }
216
+ } catch {}
217
+
218
+ # ── PowerShell Version Check ───────────────────────────────
219
+
220
+ Write-Section "System Check"
221
+
222
+ $PSVersionRequired = [Version]"5.1"
223
+ if ($PSVersionTable.PSVersion -lt $PSVersionRequired) {
224
+ Write-Err "PowerShell 5.1+ required (found $($PSVersionTable.PSVersion))"
225
+ exit 1
226
+ }
227
+ Write-Ok "PowerShell $($PSVersionTable.PSVersion)"
228
+
229
+ # ── Detect Project Root ─────────────────────────────────────
230
+
231
+ if ($ScriptDir -match '[\\/]node_modules[\\/]agentvibes$') {
232
+ $ProjectRoot = (Resolve-Path (Join-Path $ScriptDir "..\..")).Path
233
+ Write-Ok "Detected npm package in: $ProjectRoot"
234
+ } else {
235
+ $ProjectRoot = $ScriptDir
236
+ Write-Ok "Project root: $ProjectRoot"
237
+ }
238
+
239
+ # ── Setup Directories ───────────────────────────────────────
240
+
241
+ Write-Section "Setting Up Directories"
242
+
243
+ Write-Host " These directories store AgentVibes config and data:" -ForegroundColor Gray
244
+ Write-Host ""
245
+
246
+ $ProjectClaudeDir = "$ProjectRoot\.claude"
247
+ $HooksDir = "$ProjectClaudeDir\hooks-windows"
248
+ $UserClaudeDir = "$env:USERPROFILE\.claude"
249
+ $AudioDir = "$UserClaudeDir\audio"
250
+ $VoicesDir = "$UserClaudeDir\piper-voices"
251
+ $PiperDir = "$env:LOCALAPPDATA\Programs\Piper"
252
+ $PiperExe = "$PiperDir\piper.exe"
253
+
254
+ # Directory info: path, description, category
255
+ $DirInfo = @(
256
+ @{ Path = $ProjectClaudeDir; Desc = "Project settings and hooks config"; Cat = "Project" },
257
+ @{ Path = $HooksDir; Desc = "PowerShell TTS hook scripts"; Cat = "Project" },
258
+ @{ Path = $UserClaudeDir; Desc = "Global AgentVibes configuration"; Cat = "User Profile" },
259
+ @{ Path = $AudioDir; Desc = "TTS audio file cache"; Cat = "User Profile" },
260
+ @{ Path = $VoicesDir; Desc = "Piper voice model files"; Cat = "User Profile" },
261
+ @{ Path = $PiperDir; Desc = "Piper TTS engine binary"; Cat = "Applications" }
262
+ )
263
+
264
+ $CurrentCat = ""
265
+ foreach ($entry in $DirInfo) {
266
+ if ($entry.Cat -ne $CurrentCat) {
267
+ if ($CurrentCat -ne "") { Write-Host "" }
268
+ Write-Host " $($entry.Cat)" -ForegroundColor Cyan
269
+ $CurrentCat = $entry.Cat
270
+ }
271
+
272
+ $dir = $entry.Path
273
+ $existed = Test-Path $dir
274
+ if (-not $existed) {
275
+ New-Item -ItemType Directory -Path $dir -Force | Out-Null
276
+ }
277
+
278
+ # Show relative path where possible
279
+ $displayPath = $dir
280
+ if ($dir.StartsWith($ProjectRoot)) {
281
+ $displayPath = $dir.Substring($ProjectRoot.Length + 1)
282
+ } elseif ($dir.StartsWith($env:USERPROFILE)) {
283
+ $displayPath = "~" + $dir.Substring($env:USERPROFILE.Length)
284
+ } elseif ($dir.StartsWith($env:LOCALAPPDATA)) {
285
+ $displayPath = "%LOCALAPPDATA%" + $dir.Substring($env:LOCALAPPDATA.Length)
286
+ }
287
+
288
+ $status = if ($existed) { "[OK]" } else { "[OK]" }
289
+ $action = if ($existed) { "exists" } else { "created" }
290
+
291
+ Write-Host " $status " -ForegroundColor Green -NoNewline
292
+ Write-Host $displayPath.PadRight(32) -ForegroundColor White -NoNewline
293
+ Write-Host $entry.Desc -ForegroundColor DarkGray
294
+ }
295
+
296
+ # ── Install Hook Scripts ────────────────────────────────────
297
+
298
+ Write-Section "Installing TTS Scripts"
299
+
300
+ Write-Host " These scripts enable Claude Code to speak on Windows:" -ForegroundColor Gray
301
+ Write-Host ""
302
+
303
+ $SourceHooksDir = Join-Path $ScriptDir ".claude\hooks-windows"
304
+
305
+ # Validate source path is within the script directory (path traversal prevention)
306
+ $ResolvedSource = (Resolve-Path -Path $SourceHooksDir -ErrorAction SilentlyContinue).Path
307
+ $ResolvedScript = (Resolve-Path -Path $ScriptDir).Path
308
+
309
+ if (-not $ResolvedSource -or -not $ResolvedSource.StartsWith($ResolvedScript)) {
310
+ Write-Err "Hook scripts source path is outside the project directory"
311
+ exit 1
312
+ }
313
+
314
+ if (-not (Test-Path $SourceHooksDir)) {
315
+ Write-Err "Hook scripts not found at: $SourceHooksDir"
316
+ Write-Info "Make sure you're running this from the AgentVibes project directory"
317
+ exit 1
318
+ }
319
+
320
+ # Ensure destination hooks directory exists
321
+ if (-not (Test-Path $HooksDir)) {
322
+ New-Item -ItemType Directory -Path $HooksDir -Force | Out-Null
323
+ }
324
+
325
+ # Script info: filename, description
326
+ $HookScriptInfo = @(
327
+ @{ Name = "play-tts.ps1"; Desc = "Main TTS router - dispatches to active provider" },
328
+ @{ Name = "play-tts-soprano.ps1"; Desc = "Soprano neural voice provider (fastest)" },
329
+ @{ Name = "play-tts-windows-piper.ps1"; Desc = "Piper offline neural voice provider" },
330
+ @{ Name = "play-tts-windows-sapi.ps1"; Desc = "Windows built-in SAPI voice provider" },
331
+ @{ Name = "provider-manager.ps1"; Desc = "Switch between TTS providers" },
332
+ @{ Name = "voice-manager-windows.ps1"; Desc = "Browse and select voice models" },
333
+ @{ Name = "audio-cache-utils.ps1"; Desc = "Manage TTS audio file cache" },
334
+ @{ Name = "session-start-tts.ps1"; Desc = "Auto-activates TTS when Claude starts" }
335
+ )
336
+
337
+ $CopiedCount = 0
338
+ foreach ($info in $HookScriptInfo) {
339
+ $script = $info.Name
340
+ $SourceFile = Join-Path $SourceHooksDir $script
341
+ $DestFile = Join-Path $HooksDir $script
342
+
343
+ if (Test-Path $SourceFile) {
344
+ # Skip if source and destination are the same file (running from project root)
345
+ $resolvedSrc = (Resolve-Path $SourceFile).Path
346
+ $resolvedDst = if (Test-Path $DestFile) { (Resolve-Path $DestFile).Path } else { "" }
347
+ if ($resolvedSrc -eq $resolvedDst) {
348
+ Write-Item "[OK]" $script $info.Desc
349
+ } else {
350
+ Copy-Item -Path $SourceFile -Destination $DestFile -Force
351
+ Write-Item "[OK]" $script $info.Desc
352
+ }
353
+ $CopiedCount++
354
+ }
355
+ else {
356
+ Write-Host " [!!] " -ForegroundColor Yellow -NoNewline
357
+ Write-Host "$($script.PadRight(30))" -ForegroundColor White -NoNewline
358
+ Write-Host "not found" -ForegroundColor Yellow
359
+ }
360
+ }
361
+
362
+ if ($CopiedCount -eq 0) {
363
+ Write-Err "No hook scripts were installed"
364
+ exit 1
365
+ }
366
+
367
+ Write-Host ""
368
+ Write-Host " Location: " -ForegroundColor Gray -NoNewline
369
+ Write-Host ".claude\hooks-windows\" -ForegroundColor Cyan
370
+ Write-Ok "Installed $CopiedCount TTS scripts"
371
+
372
+ # ── Provider Selection ──────────────────────────────────────
373
+
374
+ Write-Section "Choose Your TTS Provider"
375
+
376
+ # Check if pip is available
377
+ $PipAvailable = $false
378
+ try {
379
+ $pipTest = & pip --version 2>$null
380
+ if ($pipTest) { $PipAvailable = $true }
381
+ } catch {}
382
+
383
+ # Check Soprano availability
384
+ $SopranoAvailable = $false
385
+ try {
386
+ $response = Invoke-WebRequest -Uri "http://127.0.0.1:7860/api/predict" `
387
+ -Method POST -UseBasicParsing -TimeoutSec 2 `
388
+ -ContentType "application/json" `
389
+ -Body '{"data":["test",1.0],"fn_index":0}' `
390
+ -ErrorAction SilentlyContinue
391
+ if ($response.StatusCode -eq 200) { $SopranoAvailable = $true }
392
+ } catch {}
393
+
394
+ # Check if pip has soprano-tts (only if pip is available)
395
+ $SopranoInstalled = $false
396
+ if ($PipAvailable) {
397
+ try {
398
+ $pipResult = & pip show soprano-tts 2>$null
399
+ if ($pipResult) { $SopranoInstalled = $true }
400
+ } catch {}
401
+ }
402
+
403
+ if (-not $Provider) {
404
+ Write-Host " [1] Soprano (Best Quality)" -ForegroundColor White
405
+ Write-Host " Ultra-fast neural TTS, 80M parameter model" -ForegroundColor DarkGray
406
+ if ($SopranoAvailable) {
407
+ Write-Host " Status: " -ForegroundColor DarkGray -NoNewline
408
+ Write-Host "Running on port 7860" -ForegroundColor Green
409
+ } elseif ($SopranoInstalled) {
410
+ Write-Host " Status: " -ForegroundColor DarkGray -NoNewline
411
+ Write-Host "Installed (not running)" -ForegroundColor Yellow
412
+ } else {
413
+ Write-Host " Requires: pip install soprano-tts" -ForegroundColor DarkGray
414
+ }
415
+ Write-Host ""
416
+
417
+ Write-Host " [2] Piper (Recommended Offline)" -ForegroundColor White
418
+ Write-Host " High quality neural voices, works fully offline" -ForegroundColor DarkGray
419
+ if (Test-Path $PiperExe) {
420
+ Write-Host " Status: " -ForegroundColor DarkGray -NoNewline
421
+ Write-Host "Installed" -ForegroundColor Green
422
+ } else {
423
+ Write-Host " Downloads: ~100MB (piper.exe + voice model)" -ForegroundColor DarkGray
424
+ }
425
+ Write-Host ""
426
+
427
+ Write-Host " [3] Windows SAPI (Zero Setup)" -ForegroundColor White
428
+ Write-Host " Built-in Windows voices (David, Zira, Mark)" -ForegroundColor DarkGray
429
+ Write-Host " No download required" -ForegroundColor DarkGray
430
+ Write-Host ""
431
+
432
+ $choice = Read-Host " Enter choice (1-3, default: 1)"
433
+
434
+ switch ($choice) {
435
+ "2" { $Provider = "piper" }
436
+ "3" { $Provider = "sapi" }
437
+ default { $Provider = "soprano" }
438
+ }
439
+ }
440
+
441
+ $ProviderDisplayName = switch ($Provider) {
442
+ "soprano" { "Soprano TTS" }
443
+ "piper" { "Piper TTS" }
444
+ "sapi" { "Windows SAPI" }
445
+ }
446
+
447
+ Write-Host ""
448
+ Write-Ok "Selected: $ProviderDisplayName"
449
+
450
+ # ── Install Soprano if selected ─────────────────────────────
451
+
452
+ if ($Provider -eq "soprano") {
453
+ Write-Section "Soprano Setup"
454
+
455
+ if ($SopranoAvailable) {
456
+ Write-Ok "Soprano server running on port 7860"
457
+ } elseif ($SopranoInstalled) {
458
+ Write-Warn "Soprano is installed but not running"
459
+ Write-Info "Start it with: soprano-tts --share"
460
+ Write-Info "Or run it in WSL and forward port 7860"
461
+ } else {
462
+ Write-Warn "Soprano TTS not detected"
463
+ Write-Host ""
464
+
465
+ if (-not $PipAvailable) {
466
+ Write-Error "pip is not available on this system"
467
+ Write-Info "Please install Python 3 with pip, then run:"
468
+ Write-Host " pip install soprano-tts" -ForegroundColor Cyan
469
+ Write-Host ""
470
+ } else {
471
+ # Offer to install Soprano
472
+ $installChoice = Read-Host "Would you like to install Soprano now? (y/n, default: y)"
473
+
474
+ if ($installChoice -eq "" -or $installChoice -eq "y" -or $installChoice -eq "Y") {
475
+ Write-Info "Installing Soprano TTS..."
476
+ Write-Host ""
477
+
478
+ try {
479
+ & pip install soprano-tts 2>&1 | Tee-Object -Variable pipOutput | Write-Host
480
+
481
+ # Re-check if installation succeeded
482
+ $SopranoInstalled = $false
483
+ try {
484
+ $pipResult = & pip show soprano-tts 2>$null
485
+ if ($pipResult) { $SopranoInstalled = $true }
486
+ } catch {}
487
+
488
+ if ($SopranoInstalled) {
489
+ Write-Ok "Soprano TTS installed successfully!"
490
+ Write-Info "Start it with: soprano-tts --share"
491
+ } else {
492
+ Write-Error "Installation may have failed. Please check the output above."
493
+ Write-Info "You can try installing manually: pip install soprano-tts"
494
+ }
495
+ } catch {
496
+ Write-Error "Installation failed: $_"
497
+ Write-Info "Please install manually: pip install soprano-tts"
498
+ }
499
+ } else {
500
+ Write-Host ""
501
+ Write-Host " To install Soprano manually:" -ForegroundColor White
502
+ Write-Host " pip install soprano-tts" -ForegroundColor Cyan
503
+ Write-Host ""
504
+ Write-Host " Or use Soprano in WSL with port forwarding:" -ForegroundColor White
505
+ Write-Host " ssh -L 7860:localhost:7860 your-wsl-host" -ForegroundColor Cyan
506
+ Write-Host ""
507
+ Write-Info "AgentVibes will work once Soprano is accessible on port 7860"
508
+ }
509
+ }
510
+ }
511
+ }
512
+
513
+ # ── Install Piper if selected ───────────────────────────────
514
+
515
+ if ($Provider -eq "piper") {
516
+ Write-Section "Piper Installation"
517
+
518
+ if (Test-Path $PiperExe) {
519
+ Write-Ok "Piper already installed at: $PiperDir"
520
+ }
521
+ else {
522
+ Write-Host " Downloading Piper TTS engine from GitHub..." -ForegroundColor White
523
+
524
+ $DownloadUrl = "https://github.com/rhasspy/piper/releases/download/2023.11.14-2/piper_windows_amd64.zip"
525
+ $DownloadFile = "$env:TEMP\piper-windows.zip"
526
+
527
+ try {
528
+ Write-Info $DownloadUrl
529
+
530
+ [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
531
+ Invoke-WebRequest -Uri $DownloadUrl -OutFile $DownloadFile -UseBasicParsing -ErrorAction Stop
532
+
533
+ Write-Ok "Downloaded ($('{0:N2}' -f ((Get-Item $DownloadFile).Length / 1MB)) MB)"
534
+
535
+ Write-Host " Extracting..." -ForegroundColor Gray
536
+ $TempExtractDir = "$env:TEMP\piper-extract"
537
+ Remove-Item -Path $TempExtractDir -Recurse -Force -ErrorAction SilentlyContinue
538
+ Expand-Archive -Path $DownloadFile -DestinationPath $TempExtractDir -Force
539
+
540
+ # Find piper.exe recursively
541
+ $PiperFound = Get-ChildItem -Path $TempExtractDir -Name "piper.exe" -Recurse
542
+
543
+ if ($PiperFound) {
544
+ $PiperExePath = (Get-ChildItem -Path $TempExtractDir -Filter "piper.exe" -Recurse).FullName | Select-Object -First 1
545
+ $PiperSourceDir = Split-Path -Parent $PiperExePath
546
+
547
+ Copy-Item -Path "$PiperSourceDir\*" -Destination $PiperDir -Recurse -Force
548
+
549
+ if (Test-Path $PiperExe) {
550
+ Write-Ok "Piper installed to: $PiperDir"
551
+ }
552
+ else {
553
+ throw "Failed to copy Piper executable"
554
+ }
555
+ }
556
+ else {
557
+ throw "piper.exe not found in extracted files"
558
+ }
559
+
560
+ # Cleanup
561
+ Remove-Item -Path $TempExtractDir -Recurse -Force -ErrorAction SilentlyContinue
562
+ Remove-Item $DownloadFile -Force -ErrorAction SilentlyContinue
563
+ }
564
+ catch {
565
+ Write-Err "Failed to install Piper: $_"
566
+ Write-Info "Please check your internet connection and try again"
567
+ exit 1
568
+ }
569
+ }
570
+
571
+ # Download default voice
572
+ Write-Host ""
573
+ Write-Host " Setting up default voice model..." -ForegroundColor White
574
+
575
+ $DefaultVoice = "en_US-ryan-high"
576
+ $VoiceDir = $VoicesDir
577
+
578
+ if ((Test-Path "$VoiceDir\$DefaultVoice.onnx") -and (Test-Path "$VoiceDir\$DefaultVoice.onnx.json")) {
579
+ Write-Ok "Default voice ($DefaultVoice) already downloaded"
580
+ }
581
+ else {
582
+ Write-Host " Downloading $DefaultVoice..." -ForegroundColor Gray
583
+
584
+ try {
585
+ $HFBase = "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/ryan/high"
586
+
587
+ [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
588
+
589
+ Write-Host " Downloading model file..." -ForegroundColor Gray
590
+ Invoke-WebRequest -Uri "$HFBase/$DefaultVoice.onnx" `
591
+ -OutFile "$VoiceDir\$DefaultVoice.onnx" `
592
+ -UseBasicParsing -ErrorAction Stop
593
+
594
+ Write-Host " Downloading config file..." -ForegroundColor Gray
595
+ Invoke-WebRequest -Uri "$HFBase/$DefaultVoice.onnx.json" `
596
+ -OutFile "$VoiceDir\$DefaultVoice.onnx.json" `
597
+ -UseBasicParsing -ErrorAction Stop
598
+
599
+ Write-Ok "Voice model downloaded: $DefaultVoice"
600
+ }
601
+ catch {
602
+ Write-Warn "Could not download voice: $_"
603
+ Write-Info "You can download voices manually later"
604
+ }
605
+ }
606
+ }
607
+
608
+ # ── Set Active Provider ─────────────────────────────────────
609
+
610
+ Write-Section "Configuring Provider"
611
+
612
+ $ProviderFile = "$ProjectClaudeDir\tts-provider.txt"
613
+ $ProviderName = switch ($Provider) {
614
+ "soprano" { "soprano" }
615
+ "piper" { "windows-piper" }
616
+ "sapi" { "windows-sapi" }
617
+ }
618
+
619
+ Set-Content -Path $ProviderFile -Value $ProviderName -NoNewline
620
+ Write-Ok "Active provider set to: $ProviderName"
621
+ Write-Info "Config file: .claude\tts-provider.txt"
622
+
623
+ # ── Background Music Selection ─────────────────────────
624
+
625
+ Write-Section "Background Music"
626
+
627
+ Write-Host " Play background music under TTS voice output?" -ForegroundColor Gray
628
+ Write-Host " Requires ffmpeg (mixing voice + music)." -ForegroundColor DarkGray
629
+ Write-Host ""
630
+
631
+ # Check ffmpeg availability
632
+ $HasFfmpeg = $false
633
+ try {
634
+ $null = Get-Command ffmpeg -ErrorAction Stop
635
+ $HasFfmpeg = $true
636
+ } catch {}
637
+
638
+ $BgMusicEnabled = $false
639
+ $BgMusicTrack = "agent_vibes_bachata_v1_loop.mp3"
640
+ $BgMusicDisplayName = "Off"
641
+ $bgChoice = "" # user's raw answer to the enable prompt ('' = skipped/default)
642
+ $trackChoice = "" # user's raw answer to the track prompt ('' = skipped/default)
643
+
644
+ if (-not $HasFfmpeg) {
645
+ Write-Warn "ffmpeg not found - background music requires ffmpeg"
646
+ Write-Info "Install ffmpeg and re-run setup to enable background music"
647
+ } else {
648
+ Write-Host " [1] Yes - Enable background music (Recommended)" -ForegroundColor White
649
+ Write-Host " [2] No - Voice only" -ForegroundColor White
650
+ Write-Host ""
651
+
652
+ $bgChoice = Read-Host " Enter choice (1-2, default: 1)"
653
+
654
+ if ($bgChoice -eq "2") {
655
+ $BgMusicEnabled = $false
656
+ $BgMusicDisplayName = "Off"
657
+ Write-Host ""
658
+ Write-Ok "Background music: Disabled"
659
+ } else {
660
+ $BgMusicEnabled = $true
661
+
662
+ # Track list with filenames and display info
663
+ $Tracks = @(
664
+ @{ File = "agentvibes_soft_flamenco_loop.mp3"; Name = "Soft Flamenco"; Desc = "Spanish guitar" },
665
+ @{ File = "agent_vibes_bachata_v1_loop.mp3"; Name = "Bachata"; Desc = "Latin rhythm" },
666
+ @{ File = "agent_vibes_bossa_nova_v2_loop.mp3"; Name = "Bossa Nova"; Desc = "Brazilian jazz" },
667
+ @{ File = "agent_vibes_salsa_v2_loop.mp3"; Name = "Salsa"; Desc = "Latin dance" },
668
+ @{ File = "agent_vibes_cumbia_v1_loop.mp3"; Name = "Cumbia"; Desc = "Colombian folk" },
669
+ @{ File = "agent_vibes_japanese_city_pop_v1_loop.mp3"; Name = "Japanese City Pop"; Desc = "80s synth" },
670
+ @{ File = "agent_vibes_chillwave_v2_loop.mp3"; Name = "Chillwave"; Desc = "Electronic ambient" },
671
+ @{ File = "dreamy_house_loop.mp3"; Name = "Dreamy House"; Desc = "Electronic dance" },
672
+ @{ File = "agent_vibes_dark_chill_step_loop.mp3"; Name = "Dark Chill Step"; Desc = "Electronic bass" },
673
+ @{ File = "agent_vibes_goa_trance_v2_loop.mp3"; Name = "Goa Trance"; Desc = "Psychedelic electronic" },
674
+ @{ File = "agent_vibes_harpsichord_v2_loop.mp3"; Name = "Harpsichord"; Desc = "Baroque classical" },
675
+ @{ File = "agent_vibes_celtic_harp_v1_loop.mp3"; Name = "Celtic Harp"; Desc = "Irish traditional" },
676
+ @{ File = "agent_vibes_hawaiian_slack_key_guitar_v2_loop.mp3"; Name = "Hawaiian Slack Key"; Desc = "Island guitar" },
677
+ @{ File = "agent_vibes_arabic_v2_loop.mp3"; Name = "Arabic Oud"; Desc = "Middle Eastern" },
678
+ @{ File = "agent_vibes_ganawa_ambient_v2_loop.mp3"; Name = "Gnawa Ambient"; Desc = "North African" },
679
+ @{ File = "agent_vibes_tabla_dream_pop_v1_loop.mp3"; Name = "Tabla Dream Pop"; Desc = "Indian percussion" }
680
+ )
681
+
682
+ Write-Host ""
683
+ Write-Host " Choose a background music genre:" -ForegroundColor White
684
+ Write-Host ""
685
+
686
+ for ($i = 0; $i -lt $Tracks.Count; $i++) {
687
+ $num = ($i + 1).ToString().PadLeft(2)
688
+ $trackName = $Tracks[$i].Name.PadRight(22)
689
+ Write-Host " [$num] " -ForegroundColor White -NoNewline
690
+ Write-Host $trackName -ForegroundColor Cyan -NoNewline
691
+ Write-Host $Tracks[$i].Desc -ForegroundColor DarkGray
692
+ }
693
+
694
+ Write-Host ""
695
+ $trackChoice = Read-Host " Enter choice (1-16, default: 2)"
696
+
697
+ $trackIndex = 1 # default to Bachata (index 1)
698
+ if ($trackChoice -match '^\d+$') {
699
+ $parsed = [int]$trackChoice
700
+ if ($parsed -ge 1 -and $parsed -le $Tracks.Count) {
701
+ $trackIndex = $parsed - 1
702
+ }
703
+ }
704
+
705
+ $BgMusicTrack = $Tracks[$trackIndex].File
706
+ $BgMusicDisplayName = $Tracks[$trackIndex].Name
707
+
708
+ Write-Host ""
709
+ Write-Ok "Background music: $BgMusicDisplayName"
710
+ }
711
+ }
712
+
713
+ # Write background music config
714
+ $ConfigDir = "$ProjectClaudeDir\config"
715
+ if (-not (Test-Path $ConfigDir)) {
716
+ New-Item -ItemType Directory -Path $ConfigDir -Force | Out-Null
717
+ }
718
+
719
+ $bgEnabledValue = if ($BgMusicEnabled) { "true" } else { "false" }
720
+ # Honor the user's explicit answers this run; preserve existing config when the
721
+ # prompt was skipped (no ffmpeg) or accepted by pressing Enter.
722
+ Set-ConfigFromChoice -Path "$ConfigDir\background-music-enabled.txt" -Value $bgEnabledValue -RawInput $bgChoice -Label "background music enabled"
723
+ Set-ConfigFromChoice -Path "$ConfigDir\background-music-default.txt" -Value $BgMusicTrack -RawInput $trackChoice -Label "background music track"
724
+ # Volume is not interactively prompted (hardcoded 20% per project rule; full
725
+ # sweep in AVI-S8.4) preserve a user's custom volume across re-runs.
726
+ Set-ConfigValueSafe -Path "$ConfigDir\background-music-volume.txt" -Value "0.20" -Label "background music volume"
727
+
728
+ # ── Audio Effects (Reverb) ─────────────────────────────
729
+
730
+ Write-Section "Audio Effects"
731
+
732
+ Write-Host " Add reverb effect to voice output?" -ForegroundColor Gray
733
+ Write-Host ""
734
+
735
+ $ReverbLevel = "off"
736
+ $ReverbDisplayName = "Off"
737
+
738
+ if (-not $HasFfmpeg) {
739
+ Write-Warn "ffmpeg not found - reverb requires ffmpeg"
740
+ Write-Info "Install ffmpeg and re-run setup to enable reverb"
741
+ } else {
742
+ Write-Host " [1] Off Dry, no reverb" -ForegroundColor White
743
+ Write-Host " [2] Light Small room (Recommended)" -ForegroundColor White
744
+ Write-Host " [3] Medium Conference room" -ForegroundColor White
745
+ Write-Host " [4] Heavy Large hall" -ForegroundColor White
746
+ Write-Host " [5] Cathedral Epic space" -ForegroundColor White
747
+ Write-Host ""
748
+
749
+ $reverbChoice = Read-Host " Enter choice (1-5, default: 2)"
750
+
751
+ switch ($reverbChoice) {
752
+ "1" { $ReverbLevel = "off"; $ReverbDisplayName = "Off" }
753
+ "3" { $ReverbLevel = "medium"; $ReverbDisplayName = "Medium" }
754
+ "4" { $ReverbLevel = "heavy"; $ReverbDisplayName = "Heavy" }
755
+ "5" { $ReverbLevel = "cathedral"; $ReverbDisplayName = "Cathedral" }
756
+ default { $ReverbLevel = "light"; $ReverbDisplayName = "Light" }
757
+ }
758
+
759
+ Write-Host ""
760
+ Write-Ok "Reverb: $ReverbDisplayName"
761
+ }
762
+
763
+ # Write reverb config honor an explicit choice; preserve existing on skip/Enter.
764
+ # ($reverbChoice is unset when the prompt was skipped for missing ffmpeg.)
765
+ if (-not (Get-Variable -Name reverbChoice -ErrorAction SilentlyContinue)) { $reverbChoice = "" }
766
+ Set-ConfigFromChoice -Path "$ConfigDir\reverb-level.txt" -Value $ReverbLevel -RawInput $reverbChoice -Label "reverb level"
767
+
768
+ # ── Verbosity / Transparency ──────────────────────────
769
+
770
+ Write-Section "TTS Verbosity"
771
+
772
+ Write-Host " How much should Claude speak during interactions?" -ForegroundColor Gray
773
+ Write-Host ""
774
+
775
+ Write-Host " [1] High Full reasoning, decisions, trade-offs" -ForegroundColor White
776
+ Write-Host " [2] Medium Key updates and acknowledgments" -ForegroundColor White
777
+ Write-Host " [3] Low Only essential messages" -ForegroundColor White
778
+ Write-Host ""
779
+
780
+ $verbChoice = Read-Host " Enter choice (1-3, default: 1)"
781
+
782
+ $VerbosityLevel = "high"
783
+ $VerbosityDisplayName = "High"
784
+
785
+ switch ($verbChoice) {
786
+ "2" { $VerbosityLevel = "medium"; $VerbosityDisplayName = "Medium" }
787
+ "3" { $VerbosityLevel = "low"; $VerbosityDisplayName = "Low" }
788
+ default { $VerbosityLevel = "high"; $VerbosityDisplayName = "High" }
789
+ }
790
+
791
+ Write-Host ""
792
+ Write-Ok "Verbosity: $VerbosityDisplayName"
793
+
794
+ # Write verbosity config — verbosity is always prompted, so honor the answer;
795
+ # preserve existing only when the user pressed Enter to accept the default.
796
+ Set-ConfigFromChoice -Path "$ProjectClaudeDir\tts-verbosity.txt" -Value $VerbosityLevel -RawInput $verbChoice -Label "TTS verbosity"
797
+
798
+ # ── Test TTS ────────────────────────────────────────────────
799
+
800
+ Write-Section "Testing TTS"
801
+
802
+ Write-Host " Running a quick TTS test..." -ForegroundColor Gray
803
+ Write-Host ""
804
+
805
+ $TestMessage = "Agent Vibes here. Setup complete, ready to speak on Windows."
806
+
807
+ try {
808
+ switch ($Provider) {
809
+ "soprano" {
810
+ if ($SopranoAvailable) {
811
+ & "$HooksDir\play-tts-soprano.ps1" $TestMessage | Out-Null
812
+ Write-Ok "Soprano TTS is working"
813
+ } else {
814
+ Write-Warn "Soprano not running - skipping audio test"
815
+ Write-Info "Start Soprano, then test with:"
816
+ Write-Info ".\.claude\hooks-windows\play-tts.ps1 'Hello'"
817
+ }
818
+ }
819
+ "piper" {
820
+ & "$HooksDir\play-tts-windows-piper.ps1" $TestMessage | Out-Null
821
+ Write-Ok "Piper TTS is working"
822
+ }
823
+ "sapi" {
824
+ & "$HooksDir\play-tts-windows-sapi.ps1" $TestMessage | Out-Null
825
+ Write-Ok "Windows SAPI is working"
826
+ }
827
+ }
828
+ }
829
+ catch {
830
+ Write-Warn "TTS test failed: $_"
831
+ Write-Info "AgentVibes should still work - check troubleshooting in WINDOWS-SETUP.md"
832
+ }
833
+
834
+ # ── Completion ──────────────────────────────────────────────
835
+
836
+ Write-Host ""
837
+ Write-BoxTop
838
+ Write-BoxEmpty
839
+ Write-BoxLine " Setup Complete!" "Green"
840
+ Write-BoxEmpty
841
+ Write-BoxLine " Provider: $ProviderDisplayName" "White"
842
+ Write-BoxLine " Background: $BgMusicDisplayName" "White"
843
+ Write-BoxLine " Reverb: $ReverbDisplayName" "White"
844
+ Write-BoxLine " Verbosity: $VerbosityDisplayName" "White"
845
+ Write-BoxLine " Version: $Version" "DarkGray"
846
+ Write-BoxEmpty
847
+ Write-BoxBottom
848
+
849
+ Write-Host ""
850
+ Write-Host " Quick Start Commands:" -ForegroundColor Cyan
851
+ Write-Host ""
852
+ Write-Host " Test TTS" -ForegroundColor White
853
+ Write-Host " .\.claude\hooks-windows\play-tts.ps1 ""Hello from AgentVibes""" -ForegroundColor Gray
854
+ Write-Host ""
855
+ Write-Host " List voices" -ForegroundColor White
856
+ Write-Host " .\.claude\hooks-windows\voice-manager-windows.ps1 list" -ForegroundColor Gray
857
+ Write-Host ""
858
+ Write-Host " Switch provider" -ForegroundColor White
859
+ Write-Host " .\.claude\hooks-windows\provider-manager.ps1 set soprano" -ForegroundColor Gray
860
+ Write-Host ""
861
+ Write-Host " List providers" -ForegroundColor White
862
+ Write-Host " .\.claude\hooks-windows\provider-manager.ps1 list" -ForegroundColor Gray
863
+ Write-Host ""
864
+
865
+ Write-Host " Documentation:" -ForegroundColor Cyan
866
+ Write-Host " Setup Guide: WINDOWS-SETUP.md" -ForegroundColor Gray
867
+ Write-Host ""