agentvibes 5.6.8 → 5.7.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 (128) hide show
  1. package/.agentvibes/config.json +2 -0
  2. package/.claude/commands/agent-vibes-bmad-voices.md +117 -117
  3. package/.claude/commands/agent-vibes-rdp.md +24 -24
  4. package/.claude/config/audio-effects.cfg +2 -2
  5. package/.claude/config/background-music-position.txt +0 -1
  6. package/.claude/docs/TERMUX_SETUP.md +408 -408
  7. package/.claude/github-star-reminder.txt +1 -1
  8. package/.claude/hooks/audio-cache-utils.sh +0 -0
  9. package/.claude/hooks/audio-processor.sh +0 -0
  10. package/.claude/hooks/background-music-manager.sh +0 -0
  11. package/.claude/hooks/bmad-party-manager.sh +225 -0
  12. package/.claude/hooks/bmad-party-speak.sh +0 -0
  13. package/.claude/hooks/bmad-speak-enhanced.sh +0 -0
  14. package/.claude/hooks/bmad-speak.sh +0 -0
  15. package/.claude/hooks/bmad-tts-injector.sh +49 -21
  16. package/.claude/hooks/bmad-voice-manager.sh +0 -0
  17. package/.claude/hooks/clawdbot-receiver-SECURE.sh +0 -0
  18. package/.claude/hooks/clawdbot-receiver.sh +0 -0
  19. package/.claude/hooks/clean-audio-cache.sh +0 -0
  20. package/.claude/hooks/cleanup-cache.sh +0 -0
  21. package/.claude/hooks/configure-rdp-mode.sh +0 -0
  22. package/.claude/hooks/download-extra-voices.sh +0 -0
  23. package/.claude/hooks/effects-manager.sh +0 -0
  24. package/.claude/hooks/github-star-reminder.sh +0 -0
  25. package/.claude/hooks/language-manager.sh +0 -0
  26. package/.claude/hooks/learn-manager.sh +0 -0
  27. package/.claude/hooks/macos-voice-manager.sh +0 -0
  28. package/.claude/hooks/migrate-background-music.sh +0 -0
  29. package/.claude/hooks/migrate-to-agentvibes.sh +0 -0
  30. package/.claude/hooks/optimize-background-music.sh +0 -0
  31. package/.claude/hooks/path-resolver.sh +0 -0
  32. package/.claude/hooks/personality-manager.sh +0 -0
  33. package/.claude/hooks/piper-download-voices.sh +0 -0
  34. package/.claude/hooks/piper-installer.sh +0 -0
  35. package/.claude/hooks/piper-multispeaker-registry.sh +0 -0
  36. package/.claude/hooks/piper-voice-manager.sh +0 -0
  37. package/.claude/hooks/play-tts-agentvibes-receiver-for-voiceless-connections.sh +0 -0
  38. package/.claude/hooks/play-tts-enhanced.sh +0 -0
  39. package/.claude/hooks/play-tts-macos.sh +0 -0
  40. package/.claude/hooks/play-tts-piper.sh +1 -1
  41. package/.claude/hooks/play-tts-soprano.sh +0 -0
  42. package/.claude/hooks/play-tts-ssh-remote.sh +0 -0
  43. package/.claude/hooks/play-tts-termux-ssh.sh +0 -0
  44. package/.claude/hooks/play-tts-windows-receiver.sh +0 -0
  45. package/.claude/hooks/play-tts.sh +4 -0
  46. package/.claude/hooks/prepare-release.sh +0 -0
  47. package/.claude/hooks/provider-commands.sh +16 -4
  48. package/.claude/hooks/provider-manager.sh +38 -0
  49. package/.claude/hooks/replay-target-audio.sh +0 -0
  50. package/.claude/hooks/sentiment-manager.sh +0 -0
  51. package/.claude/hooks/session-start-tts.sh +0 -0
  52. package/.claude/hooks/soprano-gradio-synth.py +0 -0
  53. package/.claude/hooks/speed-manager.sh +0 -0
  54. package/.claude/hooks/stop-tts.sh +0 -0
  55. package/.claude/hooks/stop.sh +38 -0
  56. package/.claude/hooks/termux-installer.sh +0 -0
  57. package/.claude/hooks/translate-manager.sh +0 -0
  58. package/.claude/hooks/translator.py +0 -0
  59. package/.claude/hooks/tts-queue-worker.sh +0 -0
  60. package/.claude/hooks/tts-queue.sh +0 -0
  61. package/.claude/hooks/verbosity-manager.sh +0 -0
  62. package/.claude/hooks/voice-manager.sh +50 -2
  63. package/.claude/hooks-windows/audio-cache-utils.ps1 +119 -119
  64. package/.claude/hooks-windows/play-tts.ps1 +34 -1
  65. package/.claude/hooks-windows/tts-watcher.ps1 +122 -0
  66. package/.claude/piper-voices-dir.txt +1 -0
  67. package/.clawdbot/README.md +105 -105
  68. package/.mcp.json +14 -5
  69. package/README.md +10 -2
  70. package/RELEASE_NOTES.md +61 -0
  71. package/WINDOWS-SETUP.md +208 -208
  72. package/bin/agent-vibes +39 -39
  73. package/bin/agentvibes-voice-browser.js +59 -4
  74. package/bin/agentvibes.js +0 -0
  75. package/bin/mcp-server.js +121 -121
  76. package/bin/mcp-server.sh +0 -0
  77. package/bin/test-bmad-pr +78 -78
  78. package/mcp-server/QUICK_START.md +203 -203
  79. package/mcp-server/README.md +345 -345
  80. package/mcp-server/WINDOWS_SETUP.md +260 -260
  81. package/mcp-server/docs/troubleshooting-audio.md +313 -313
  82. package/mcp-server/examples/claude_desktop_config.json +11 -11
  83. package/mcp-server/examples/claude_desktop_config_piper.json +9 -9
  84. package/mcp-server/examples/custom_instructions.md +169 -169
  85. package/mcp-server/install-deps.js +177 -130
  86. package/mcp-server/server.py +1797 -1787
  87. package/mcp-server/test_server.py +0 -0
  88. package/package.json +1 -1
  89. package/src/bmad-detector.js +85 -71
  90. package/src/cli/list-personalities.js +110 -110
  91. package/src/cli/list-voices.js +114 -114
  92. package/src/commands/bmad-voices.js +394 -394
  93. package/src/commands/install-mcp.js +476 -476
  94. package/src/console/brand-colors.js +13 -13
  95. package/src/console/constants/personalities.js +44 -44
  96. package/src/console/tabs/help-tab.js +314 -314
  97. package/src/console/tabs/music-tab.js +18 -2
  98. package/src/console/tabs/readme-tab.js +272 -272
  99. package/src/console/widgets/destroy-list.js +25 -25
  100. package/src/console/widgets/notice.js +55 -55
  101. package/src/console/widgets/personality-picker.js +213 -213
  102. package/src/i18n/de.js +202 -202
  103. package/src/i18n/es.js +202 -202
  104. package/src/i18n/fr.js +202 -202
  105. package/src/i18n/hi.js +202 -202
  106. package/src/i18n/ja.js +202 -202
  107. package/src/i18n/ko.js +202 -202
  108. package/src/i18n/pt.js +202 -202
  109. package/src/i18n/strings.js +54 -54
  110. package/src/i18n/zh-CN.js +202 -202
  111. package/src/installer/language-screen.js +31 -31
  112. package/src/installer/music-file-input.js +304 -304
  113. package/src/installer.js +70 -7
  114. package/src/services/agent-voice-store.js +59 -12
  115. package/src/services/config-service.js +264 -264
  116. package/src/services/language-service.js +47 -47
  117. package/src/services/provider-service.js +143 -143
  118. package/src/utils/audio-duration-validator.js +298 -298
  119. package/src/utils/audio-format-validator.js +277 -277
  120. package/src/utils/dependency-checker.js +469 -469
  121. package/src/utils/file-ownership-verifier.js +358 -358
  122. package/src/utils/list-formatter.js +194 -194
  123. package/src/utils/music-file-validator.js +285 -285
  124. package/src/utils/preview-list-prompt.js +136 -136
  125. package/src/utils/secure-music-storage.js +412 -412
  126. package/templates/agentvibes-receiver.sh +0 -0
  127. package/templates/audio/welcome-music.mp3 +0 -0
  128. package/.claude/hooks/play-tts-agentvibes-receiver.sh +0 -1
@@ -561,8 +561,55 @@ case "$1" in
561
561
  fi
562
562
  ;;
563
563
 
564
+ sample)
565
+ # Play a sample phrase with the specified voice (used by /agent-vibes:sample)
566
+ SAMPLE_VOICE="${2:-}"
567
+ if [[ -z "$SAMPLE_VOICE" ]]; then
568
+ echo "❌ Usage: /agent-vibes:sample <voice-name>"
569
+ echo ""
570
+ echo "Examples:"
571
+ echo " /agent-vibes:sample en_US-amy-medium"
572
+ echo " /agent-vibes:sample Ryan"
573
+ exit 1
574
+ fi
575
+
576
+ # Source provider-manager.sh first — get_active_provider and detect_routing_llm
577
+ # are both defined there; sourcing after calling them silently produces empty values.
578
+ source "$SCRIPT_DIR/provider-manager.sh" 2>/dev/null || true
579
+
580
+ ACTIVE_PROVIDER=$(get_active_provider)
581
+
582
+ # Friendly name resolution for Piper and transport providers
583
+ case "$ACTIVE_PROVIDER" in
584
+ piper|ssh-remote|agentvibes-receiver|termux-ssh)
585
+ source "$SCRIPT_DIR/piper-voice-manager.sh" 2>/dev/null || true
586
+ SAMPLE_META="$(realpath "$SCRIPT_DIR/../../.agentvibes/config/voice-metadata.json" 2>/dev/null || echo "")"
587
+ if [[ -f "$SAMPLE_META" ]] && command -v jq >/dev/null 2>&1; then
588
+ SAMPLE_RESOLVED=$(jq -r --arg n "$(to_lower "$SAMPLE_VOICE")" '
589
+ .voices | to_entries[] |
590
+ select(.key == $n or (.value.displayName | ascii_downcase) == $n) |
591
+ .value.id
592
+ ' "$SAMPLE_META" 2>/dev/null | head -1)
593
+ if [[ -n "$SAMPLE_RESOLVED" ]] && [[ "$SAMPLE_RESOLVED" =~ ^[a-zA-Z0-9_.:+-]+$ ]]; then
594
+ echo "🔍 Resolved '${SAMPLE_VOICE}' → '${SAMPLE_RESOLVED}'"
595
+ SAMPLE_VOICE="$SAMPLE_RESOLVED"
596
+ fi
597
+ fi
598
+ ;;
599
+ esac
600
+
601
+ # Detect routing LLM so SSH-remote setups forward audio to the receiver
602
+ SAMPLE_LLM=$(detect_routing_llm 2>/dev/null || echo "")
603
+ SAMPLE_LLM_ARG=()
604
+ [[ -n "$SAMPLE_LLM" ]] && SAMPLE_LLM_ARG=(--llm "$SAMPLE_LLM")
605
+
606
+ echo "🎤 Sampling voice: $SAMPLE_VOICE"
607
+ "$SCRIPT_DIR/play-tts.sh" "Hi, I'm ${SAMPLE_VOICE}. How does my voice sound?" \
608
+ "$SAMPLE_VOICE" "${SAMPLE_LLM_ARG[@]+"${SAMPLE_LLM_ARG[@]}"}"
609
+ ;;
610
+
564
611
  *)
565
- echo "Usage: voice-manager.sh [list|switch|get|replay|whoami] [voice_name]"
612
+ echo "Usage: voice-manager.sh [list|switch|get|replay|whoami|sample] [voice_name]"
566
613
  echo ""
567
614
  echo "Commands:"
568
615
  echo " list - List all available voices"
@@ -570,6 +617,7 @@ case "$1" in
570
617
  echo " get - Get current voice name"
571
618
  echo " replay [N] - Replay Nth most recent audio (default: 1)"
572
619
  echo " whoami - Show current voice and personality"
620
+ echo " sample <voice_name> - Play sample audio with the given voice"
573
621
  exit 1
574
622
  ;;
575
- esac
623
+ esac
@@ -1,119 +1,119 @@
1
- #
2
- # File: .claude/hooks-windows/audio-cache-utils.ps1
3
- #
4
- # AgentVibes Audio Cache Utilities for Windows
5
- #
6
-
7
- param(
8
- [Parameter(Position = 0)]
9
- [ValidateSet('cleanup', 'stats', 'clear')]
10
- [string]$Command
11
- )
12
-
13
- # Detect project-local audio dir (same logic as TTS scripts)
14
- $ScriptPath = Split-Path -Parent $MyInvocation.MyCommand.Path
15
- $ProjectClaudeDir = Join-Path (Split-Path -Parent (Split-Path -Parent $ScriptPath)) ".claude"
16
- if (Test-Path $ProjectClaudeDir) {
17
- $AudioDir = "$ProjectClaudeDir\audio"
18
- } else {
19
- $AudioDir = "$env:USERPROFILE\.claude\audio"
20
- }
21
-
22
- function Ensure-AudioDir {
23
- if (-not (Test-Path $AudioDir)) {
24
- New-Item -ItemType Directory -Path $AudioDir -Force | Out-Null
25
- }
26
- }
27
-
28
- function Get-AudioCacheSize {
29
- Ensure-AudioDir
30
-
31
- if (-not (Test-Path $AudioDir)) {
32
- return 0
33
- }
34
-
35
- $files = Get-ChildItem -Path $AudioDir -Filter "*.wav" -ErrorAction SilentlyContinue
36
- $totalSize = 0
37
-
38
- foreach ($file in $files) {
39
- $totalSize += $file.Length
40
- }
41
-
42
- return $totalSize
43
- }
44
-
45
- function Format-FileSize {
46
- param([long]$Size)
47
-
48
- if ($Size -lt 1KB) { return "$Size B" }
49
- if ($Size -lt 1MB) { return "{0:N2} KB" -f ($Size / 1KB) }
50
- if ($Size -lt 1GB) { return "{0:N2} MB" -f ($Size / 1MB) }
51
- return "{0:N2} GB" -f ($Size / 1GB)
52
- }
53
-
54
- function Get-CacheStats {
55
- Ensure-AudioDir
56
-
57
- $files = Get-ChildItem -Path $AudioDir -Filter "*.wav" -ErrorAction SilentlyContinue | Measure-Object -Property Length -Sum
58
-
59
- $count = if ($files.Count -eq $null) { 0 } else { $files.Count }
60
- $totalSize = if ($files.Sum -eq $null) { 0 } else { $files.Sum }
61
-
62
- return @{
63
- FileCount = $count
64
- TotalSize = $totalSize
65
- FormattedSize = Format-FileSize $totalSize
66
- }
67
- }
68
-
69
- function Clear-Cache {
70
- Ensure-AudioDir
71
-
72
- $files = Get-ChildItem -Path $AudioDir -Filter "*.wav" -ErrorAction SilentlyContinue
73
-
74
- if ($files.Count -eq 0) {
75
- Write-Host "[OK] Cache already empty" -ForegroundColor Green
76
- return
77
- }
78
-
79
- $stats = Get-CacheStats
80
- Write-Host "[CLEANUP] Clearing $($stats.FileCount) audio files ($($stats.FormattedSize))" -ForegroundColor Yellow
81
-
82
- foreach ($file in $files) {
83
- Remove-Item $file.FullName -Force -ErrorAction SilentlyContinue
84
- }
85
-
86
- Write-Host "[OK] Cache cleared" -ForegroundColor Green
87
- }
88
-
89
- function Show-CacheStats {
90
- Ensure-AudioDir
91
-
92
- $stats = Get-CacheStats
93
-
94
- Write-Host ""
95
- Write-Host "[STATS] Audio Cache Statistics" -ForegroundColor Cyan
96
- Write-Host " Location: $AudioDir"
97
- Write-Host " Files: $($stats.FileCount)"
98
- Write-Host " Total Size: $($stats.FormattedSize)"
99
- Write-Host ""
100
- }
101
-
102
- # Main command routing
103
- switch ($Command) {
104
- 'stats' {
105
- Show-CacheStats
106
- }
107
-
108
- 'cleanup' {
109
- Clear-Cache
110
- }
111
-
112
- 'clear' {
113
- Clear-Cache
114
- }
115
-
116
- default {
117
- Show-CacheStats
118
- }
119
- }
1
+ #
2
+ # File: .claude/hooks-windows/audio-cache-utils.ps1
3
+ #
4
+ # AgentVibes Audio Cache Utilities for Windows
5
+ #
6
+
7
+ param(
8
+ [Parameter(Position = 0)]
9
+ [ValidateSet('cleanup', 'stats', 'clear')]
10
+ [string]$Command
11
+ )
12
+
13
+ # Detect project-local audio dir (same logic as TTS scripts)
14
+ $ScriptPath = Split-Path -Parent $MyInvocation.MyCommand.Path
15
+ $ProjectClaudeDir = Join-Path (Split-Path -Parent (Split-Path -Parent $ScriptPath)) ".claude"
16
+ if (Test-Path $ProjectClaudeDir) {
17
+ $AudioDir = "$ProjectClaudeDir\audio"
18
+ } else {
19
+ $AudioDir = "$env:USERPROFILE\.claude\audio"
20
+ }
21
+
22
+ function Ensure-AudioDir {
23
+ if (-not (Test-Path $AudioDir)) {
24
+ New-Item -ItemType Directory -Path $AudioDir -Force | Out-Null
25
+ }
26
+ }
27
+
28
+ function Get-AudioCacheSize {
29
+ Ensure-AudioDir
30
+
31
+ if (-not (Test-Path $AudioDir)) {
32
+ return 0
33
+ }
34
+
35
+ $files = Get-ChildItem -Path $AudioDir -Filter "*.wav" -ErrorAction SilentlyContinue
36
+ $totalSize = 0
37
+
38
+ foreach ($file in $files) {
39
+ $totalSize += $file.Length
40
+ }
41
+
42
+ return $totalSize
43
+ }
44
+
45
+ function Format-FileSize {
46
+ param([long]$Size)
47
+
48
+ if ($Size -lt 1KB) { return "$Size B" }
49
+ if ($Size -lt 1MB) { return "{0:N2} KB" -f ($Size / 1KB) }
50
+ if ($Size -lt 1GB) { return "{0:N2} MB" -f ($Size / 1MB) }
51
+ return "{0:N2} GB" -f ($Size / 1GB)
52
+ }
53
+
54
+ function Get-CacheStats {
55
+ Ensure-AudioDir
56
+
57
+ $files = Get-ChildItem -Path $AudioDir -Filter "*.wav" -ErrorAction SilentlyContinue | Measure-Object -Property Length -Sum
58
+
59
+ $count = if ($files.Count -eq $null) { 0 } else { $files.Count }
60
+ $totalSize = if ($files.Sum -eq $null) { 0 } else { $files.Sum }
61
+
62
+ return @{
63
+ FileCount = $count
64
+ TotalSize = $totalSize
65
+ FormattedSize = Format-FileSize $totalSize
66
+ }
67
+ }
68
+
69
+ function Clear-Cache {
70
+ Ensure-AudioDir
71
+
72
+ $files = Get-ChildItem -Path $AudioDir -Filter "*.wav" -ErrorAction SilentlyContinue
73
+
74
+ if ($files.Count -eq 0) {
75
+ Write-Host "[OK] Cache already empty" -ForegroundColor Green
76
+ return
77
+ }
78
+
79
+ $stats = Get-CacheStats
80
+ Write-Host "[CLEANUP] Clearing $($stats.FileCount) audio files ($($stats.FormattedSize))" -ForegroundColor Yellow
81
+
82
+ foreach ($file in $files) {
83
+ Remove-Item $file.FullName -Force -ErrorAction SilentlyContinue
84
+ }
85
+
86
+ Write-Host "[OK] Cache cleared" -ForegroundColor Green
87
+ }
88
+
89
+ function Show-CacheStats {
90
+ Ensure-AudioDir
91
+
92
+ $stats = Get-CacheStats
93
+
94
+ Write-Host ""
95
+ Write-Host "[STATS] Audio Cache Statistics" -ForegroundColor Cyan
96
+ Write-Host " Location: $AudioDir"
97
+ Write-Host " Files: $($stats.FileCount)"
98
+ Write-Host " Total Size: $($stats.FormattedSize)"
99
+ Write-Host ""
100
+ }
101
+
102
+ # Main command routing
103
+ switch ($Command) {
104
+ 'stats' {
105
+ Show-CacheStats
106
+ }
107
+
108
+ 'cleanup' {
109
+ Clear-Cache
110
+ }
111
+
112
+ 'clear' {
113
+ Clear-Cache
114
+ }
115
+
116
+ default {
117
+ Show-CacheStats
118
+ }
119
+ }
@@ -22,7 +22,17 @@ param(
22
22
  # CLAUDE_PROJECT_DIR value here so per-project config is found even when
23
23
  # Bash tool calls do not propagate CLAUDE_PROJECT_DIR to child processes.
24
24
  [Parameter(Mandatory = $false)]
25
- [string]$ProjectDir = ""
25
+ [string]$ProjectDir = "",
26
+
27
+ # Provider override from the remote sender (set by the watcher from the
28
+ # JSON payload's "provider" field). Overrides the local tts-provider.txt
29
+ # default so the Linux-side config fully controls which engine the Windows
30
+ # receiver uses — no Windows-side provider config needed.
31
+ # Per-LLM engine rows in audio-effects.cfg still take final priority for
32
+ # explicit Windows overrides (e.g. llm:copilot → windows-sapi).
33
+ # Accepts cross-platform aliases: "piper" = windows-piper, "sapi" = windows-sapi.
34
+ [Parameter(Mandatory = $false)]
35
+ [string]$ProviderOverride = ""
26
36
  )
27
37
 
28
38
  # Text-file handoff: the SSH receiver watcher writes long/special-char text to
@@ -99,6 +109,29 @@ switch ($ActiveProvider) {
99
109
  }
100
110
  }
101
111
 
112
+ # Apply remote provider override (from the JSON payload's "provider" field, passed
113
+ # by the SSH-receiver watcher via -ProviderOverride). This lets the Linux-side
114
+ # audio-effects.cfg row for llm:claude-code specify "piper" and have it honoured
115
+ # on Windows without requiring the Windows tts-provider.txt to be reconfigured.
116
+ # Priority: lower than per-LLM $_LlmEngine (audio-effects.cfg row, set later), higher
117
+ # than the global tts-provider.txt default set above.
118
+ if ($ProviderOverride) {
119
+ switch ($ProviderOverride) {
120
+ { $_ -in "windows-piper", "piper" } {
121
+ $ProviderScript = "$HooksDir\play-tts-piper.ps1"
122
+ if (-not (Test-Path $ProviderScript)) { $ProviderScript = "$HooksDir\play-tts-windows-piper.ps1" }
123
+ }
124
+ { $_ -in "windows-sapi", "sapi" } {
125
+ $ProviderScript = "$HooksDir\play-tts-sapi.ps1"
126
+ if (-not (Test-Path $ProviderScript)) { $ProviderScript = "$HooksDir\play-tts-windows-sapi.ps1" }
127
+ }
128
+ "soprano" { $ProviderScript = "$HooksDir\play-tts-soprano.ps1" }
129
+ default {
130
+ Write-Host "[WARNING] play-tts.ps1: Unknown ProviderOverride '$ProviderOverride' ignored" -ForegroundColor Yellow
131
+ }
132
+ }
133
+ }
134
+
102
135
  # Check if provider script exists
103
136
  if (-not (Test-Path $ProviderScript)) {
104
137
  Write-Host "[ERROR] Provider script not found: $ProviderScript" -ForegroundColor Red
@@ -0,0 +1,122 @@
1
+ #
2
+ # File: .claude/hooks-windows/tts-watcher.ps1
3
+ #
4
+ # AgentVibes TTS Queue Watcher — runs in the user's interactive session for audio access.
5
+ # The SSH receiver (session 0, no audio) writes JSON request files to the queue; this
6
+ # watcher picks them up and calls play-tts.ps1 in the user's desktop session.
7
+ #
8
+ # Single-instance guard: exit immediately if another watcher instance is already running.
9
+ $mutex = New-Object System.Threading.Mutex($false, 'Global\AgentVibesTtsWatcher')
10
+ if (-not $mutex.WaitOne(0)) { $mutex.Dispose(); exit 0 }
11
+
12
+ $QueueDir = "$env:USERPROFILE\.agentvibes\tts-queue"
13
+ $LogFile = "$env:USERPROFILE\.agentvibes\watcher.log"
14
+ $PlayTts = "$env:USERPROFILE\.claude\hooks-windows\play-tts.ps1"
15
+ if (-not (Test-Path $QueueDir)) { New-Item -ItemType Directory -Path $QueueDir -Force | Out-Null }
16
+
17
+ function Write-WatcherLog {
18
+ param([string]$Level, [string]$Msg)
19
+ $ts = Get-Date -Format 'yyyy-MM-ddTHH:mm:ss'
20
+ Add-Content -Path $LogFile -Value "$ts [$Level] $Msg" -ErrorAction SilentlyContinue
21
+ }
22
+
23
+ Write-WatcherLog "INFO" "Watcher started. PlayTts=$PlayTts exists=$(Test-Path $PlayTts)"
24
+
25
+ try {
26
+ while ($true) {
27
+ $files = Get-ChildItem "$QueueDir\req-*.json" -ErrorAction SilentlyContinue | Sort-Object CreationTime
28
+ foreach ($f in $files) {
29
+ # Rename to proc-* before processing — crash recovery on restart
30
+ $procFile = $f.FullName -replace '\\req-', '\proc-'
31
+ try { Rename-Item $f.FullName $procFile -ErrorAction Stop } catch { continue }
32
+ try {
33
+ $req = Get-Content $procFile -Raw | ConvertFrom-Json
34
+ # Validate voice before passing to command line
35
+ $safeVoice = if ($req.voice -and $req.voice -match '^[a-zA-Z0-9_\-\. :]+$') { $req.voice } else { "" }
36
+ $env:CLAUDE_PROJECT_DIR = $env:USERPROFILE
37
+ $env:AGENTVIBES_NO_PRETEXT = "1"
38
+ # Use SetEnvironmentVariable to truly unset (assignment to $null leaves empty string)
39
+ if ($req.music) { $env:AGENTVIBES_OVERRIDE_MUSIC = $req.music }
40
+ else { [System.Environment]::SetEnvironmentVariable("AGENTVIBES_OVERRIDE_MUSIC", $null, "Process") }
41
+ if ($req.volume) { $env:AGENTVIBES_OVERRIDE_VOLUME = $req.volume }
42
+ else { [System.Environment]::SetEnvironmentVariable("AGENTVIBES_OVERRIDE_VOLUME", $null, "Process") }
43
+ if ($req.effects) { $env:AGENTVIBES_OVERRIDE_EFFECTS = $req.effects }
44
+ else { [System.Environment]::SetEnvironmentVariable("AGENTVIBES_OVERRIDE_EFFECTS", $null, "Process") }
45
+
46
+ if (Test-Path $PlayTts) {
47
+ # Play remote arrival prefix sound if configured
48
+ $prefixSoundFile = "$env:USERPROFILE\.agentvibes\remote-prefix-sound.txt"
49
+ if (Test-Path $prefixSoundFile) {
50
+ $prefixSound = (Get-Content $prefixSoundFile -Raw).Trim()
51
+ if ($prefixSound -and (Test-Path $prefixSound)) {
52
+ $ffplay = Get-Command ffplay -ErrorAction SilentlyContinue
53
+ if ($ffplay) {
54
+ & $ffplay.Source -autoexit -nodisp -loglevel quiet $prefixSound 2>$null
55
+ }
56
+ }
57
+ }
58
+
59
+ $tempText = Join-Path $env:TEMP "agentvibes-tts-$($req.id).txt"
60
+ try {
61
+ [System.IO.File]::WriteAllText($tempText, $req.text, [System.Text.UTF8Encoding]::new($false))
62
+ $env:AGENTVIBES_TEXT_FILE = $tempText
63
+
64
+ $llmArg = @()
65
+ if ($req.llm) {
66
+ if ($req.llm -match '^[a-zA-Z0-9][a-zA-Z0-9_-]*$') {
67
+ $llmArg = @('-llm', $req.llm)
68
+ } else {
69
+ Write-WatcherLog "WARN" "Invalid LLM name '$($req.llm)' - using default"
70
+ }
71
+ }
72
+
73
+ # Provider override: the Linux sender embeds its configured provider
74
+ # (from receiver-provider.txt / audio-effects.cfg ENGINE column) in the
75
+ # JSON payload so the Windows receiver uses the Linux-side engine choice.
76
+ $providerArg = @()
77
+ if ($req.provider) {
78
+ $safeProvider = $req.provider.Trim()
79
+ if ($safeProvider -match '^[a-zA-Z0-9][a-zA-Z0-9_-]*$') {
80
+ $providerArg = @('-ProviderOverride', $safeProvider)
81
+ } else {
82
+ Write-WatcherLog "WARN" "Invalid provider '$safeProvider' in payload - ignored"
83
+ }
84
+ }
85
+
86
+ Write-WatcherLog "INFO" "play-tts id=$($req.id) voice=$safeVoice llm=$($req.llm) provider=$safeProvider"
87
+ $playOutput = & powershell.exe -NoProfile -ExecutionPolicy Bypass -File $PlayTts "__from_file__" $safeVoice @llmArg @providerArg 2>&1
88
+ if ($LASTEXITCODE -ne 0) {
89
+ Write-WatcherLog "ERROR" "play-tts exit=$LASTEXITCODE id=$($req.id) output=$($playOutput -join ' | ')"
90
+ } else {
91
+ Write-WatcherLog "INFO" "play-tts ok exit=0 id=$($req.id)"
92
+ }
93
+ } finally {
94
+ [System.Environment]::SetEnvironmentVariable("AGENTVIBES_TEXT_FILE", $null, "Process")
95
+ Remove-Item $tempText -Force -ErrorAction SilentlyContinue
96
+ }
97
+ } else {
98
+ # Fallback: Windows SAPI (built-in, no installation required)
99
+ Write-WatcherLog "WARN" "play-tts.ps1 not found - using SAPI fallback for id=$($req.id)"
100
+ Add-Type -AssemblyName System.Speech
101
+ $synth = New-Object System.Speech.Synthesis.SpeechSynthesizer
102
+ $synth.Speak($req.text)
103
+ $synth.Dispose()
104
+ }
105
+ Remove-Item $procFile -Force -ErrorAction SilentlyContinue
106
+ } catch {
107
+ Write-WatcherLog "ERROR" "id=$($req.id) err=$_"
108
+ Remove-Item $procFile -Force -ErrorAction SilentlyContinue
109
+ }
110
+ }
111
+ # Crash recovery: re-queue any proc-* files left from a previous watcher crash
112
+ $stale = Get-ChildItem "$QueueDir\proc-*.json" -ErrorAction SilentlyContinue
113
+ foreach ($s in $stale) {
114
+ $recovered = $s.FullName -replace '\\proc-', '\req-'
115
+ try { Rename-Item $s.FullName $recovered -ErrorAction SilentlyContinue } catch {}
116
+ }
117
+ Start-Sleep -Milliseconds 200
118
+ }
119
+ } finally {
120
+ $mutex.ReleaseMutex()
121
+ $mutex.Dispose()
122
+ }
@@ -0,0 +1 @@
1
+ /home/administrator/.claude/piper-voices