agentvibes 5.13.0 → 5.14.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 (82) hide show
  1. package/.claude/audio/ui/CREDITS.txt +16 -16
  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.sample +52 -52
  5. package/.claude/docs/TERMUX_SETUP.md +408 -408
  6. package/.claude/github-star-reminder.txt +1 -1
  7. package/.claude/hooks/kokoro-server.py +219 -219
  8. package/.claude/hooks/kokoro-tts.py +141 -141
  9. package/.claude/hooks/party-stage-roster.py +328 -328
  10. package/.claude/hooks/play-tts-kokoro.sh +7 -2
  11. package/.claude/hooks/play-tts-ssh-remote.sh +38 -3
  12. package/.claude/hooks/requirements.txt +6 -6
  13. package/.claude/hooks/soprano-gradio-synth.py +139 -139
  14. package/.claude/hooks/translator.py +237 -237
  15. package/.claude/hooks-windows/kokoro-server.py +219 -219
  16. package/.claude/hooks-windows/kokoro-tts.py +107 -107
  17. package/.claude/hooks-windows/play-tts.ps1 +17 -0
  18. package/.claude/hooks-windows/soprano-gradio-synth.py +153 -153
  19. package/.claude/verbosity.txt +1 -1
  20. package/.clawdbot/README.md +105 -105
  21. package/CLAUDE.md +84 -176
  22. package/README.md +27 -2
  23. package/RELEASE_NOTES.md +86 -0
  24. package/WINDOWS-SETUP.md +208 -208
  25. package/bin/agent-vibes +39 -39
  26. package/bin/mcp-server.js +121 -121
  27. package/bin/test-bmad-pr +78 -78
  28. package/mcp-server/QUICK_START.md +203 -203
  29. package/mcp-server/README.md +345 -345
  30. package/mcp-server/examples/claude_desktop_config.json +11 -11
  31. package/mcp-server/examples/claude_desktop_config_piper.json +9 -9
  32. package/mcp-server/examples/custom_instructions.md +169 -169
  33. package/mcp-server/install-deps.js +12 -2
  34. package/mcp-server/server.py +2111 -2085
  35. package/mcp-server/test_mcp_correctness.py +504 -504
  36. package/mcp-server/test_windows_script_parity.py +339 -339
  37. package/package.json +7 -4
  38. package/setup-ssh-receiver.ps1 +259 -0
  39. package/setup-windows.ps1 +39 -29
  40. package/src/cli/list-personalities.js +110 -110
  41. package/src/commands/bmad-voices.js +394 -394
  42. package/src/console/brand-colors.js +13 -13
  43. package/src/console/constants/personalities.js +44 -44
  44. package/src/console/tabs/agents-tab.js +14 -4
  45. package/src/console/tabs/help-tab.js +314 -314
  46. package/src/console/tabs/readme-tab.js +272 -272
  47. package/src/console/tabs/settings-tab.js +31 -6
  48. package/src/console/tabs/setup-tab.js +154 -32
  49. package/src/console/tabs/voices-tab.js +48 -6
  50. package/src/console/widgets/destroy-list.js +25 -25
  51. package/src/console/widgets/notice.js +55 -55
  52. package/src/installer/language-screen.js +31 -31
  53. package/src/installer/music-file-input.js +304 -304
  54. package/src/installer.js +103 -43
  55. package/src/services/language-service.js +47 -47
  56. package/src/services/llm-provider-service.js +4 -1
  57. package/src/services/provider-voice-catalog.js +9 -5
  58. package/src/utils/audio-format-validator.js +277 -277
  59. package/src/utils/dependency-checker.js +469 -469
  60. package/src/utils/file-ownership-verifier.js +358 -358
  61. package/src/utils/music-file-validator.js +285 -285
  62. package/src/utils/preview-list-prompt.js +144 -144
  63. package/src/utils/secure-music-storage.js +412 -412
  64. package/templates/agentvibes-receiver.ps1 +311 -0
  65. package/templates/agentvibes-receiver.sh +297 -297
  66. package/voice-assignments.json +8244 -8244
  67. package/.agentvibes/config.json +0 -17
  68. package/.agentvibes/install-manifest.json +0 -342
  69. package/.claude/config/audio-effects.cfg +0 -7
  70. package/.claude/config/audio-effects.cfg.bak-kokoro +0 -7
  71. package/.claude/config/background-music-enabled.txt +0 -1
  72. package/.claude/config/background-music-position.txt +0 -27
  73. package/.claude/config/background-music-volume.txt +0 -1
  74. package/.claude/config/background-music.cfg +0 -1
  75. package/.claude/config/background-music.txt +0 -1
  76. package/.claude/config/language.txt +0 -1
  77. package/.claude/config/personality.txt +0 -1
  78. package/.claude/config/reverb-level.txt +0 -1
  79. package/.claude/config/tts-speech-rate.txt +0 -1
  80. package/.claude/config/tts-verbosity.txt +0 -1
  81. package/.claude/hooks-windows/audio-cache-utils.ps1.user.bak +0 -119
  82. package/.claude/hooks-windows/soprano-gradio-synth.py.user.bak +0 -153
@@ -0,0 +1,311 @@
1
+ #
2
+ # File: agentvibes-receiver.ps1
3
+ # Location: Install to $env:USERPROFILE\.agentvibes\play-remote.ps1
4
+ #
5
+ # AgentVibes Windows TTS Receiver
6
+ # Receives base64 JSON payload via stdin (from SSH ForceCommand),
7
+ # decodes it, configures voice/music, and delegates to play-tts.ps1.
8
+ #
9
+ # Payload format (same as Linux receiver):
10
+ # Base64-encoded JSON: { text, voice, effects, music, volume, project, pretext, speed, provider }
11
+ #
12
+ # Usage (SSH ForceCommand in sshd_config):
13
+ # ForceCommand powershell -NoProfile -ExecutionPolicy Bypass -File C:\Users\Paul\.agentvibes\play-remote.ps1
14
+ #
15
+
16
+ param(
17
+ [Parameter(Position = 0)]
18
+ [string]$EncodedPayload = ""
19
+ )
20
+
21
+ # Paths - __OWNER_HOME__ is replaced at install time by setup-ssh-receiver.ps1
22
+ # with the installing user's home directory (e.g. C:\Users\Paul).
23
+ # This is necessary because sshd runs the ForceCommand as the SSH user
24
+ # (e.g. agentvibes-receiver), whose $env:USERPROFILE is a different directory.
25
+ $OwnerHome = "__OWNER_HOME__"
26
+ if (-not (Test-Path "$OwnerHome\.agentvibes")) {
27
+ Write-Output "Error: Receiver not installed properly - run setup-ssh-receiver.ps1"
28
+ exit 1
29
+ }
30
+ $AgentVibesDir = "$OwnerHome\.agentvibes"
31
+ $ClaudeDir = "$OwnerHome\.claude"
32
+ $HooksDir = "$ClaudeDir\hooks-windows"
33
+ $ConfigDir = "$ClaudeDir\config"
34
+ $LogFile = "$AgentVibesDir\receiver.log"
35
+ $PlayTtsScript = "$HooksDir\play-tts.ps1"
36
+
37
+ # Ensure directories exist
38
+ foreach ($dir in @($AgentVibesDir, $ConfigDir)) {
39
+ if (-not (Test-Path $dir)) {
40
+ New-Item -ItemType Directory -Path $dir -Force | Out-Null
41
+ }
42
+ }
43
+
44
+ # Generate log ID
45
+ $LogId = '{0:x4}' -f (Get-Random -Maximum 65536)
46
+
47
+ function Write-Log {
48
+ param([string]$Status, [string]$Detail = "")
49
+ $timestamp = Get-Date -Format 'yyyy-MM-ddTHH:mm:ss'
50
+ $senderIp = if ($env:SSH_CLIENT) { ($env:SSH_CLIENT -split ' ')[0] } else { "local" }
51
+ $preview = if ($script:Text.Length -gt 200) { $script:Text.Substring(0, 200) } else { $script:Text }
52
+ $logLine = "$timestamp|$Status|$($script:Project)|$($script:Voice)|$preview|$Detail|$senderIp|$LogId"
53
+ Add-Content -Path $LogFile -Value $logLine -ErrorAction SilentlyContinue
54
+ }
55
+
56
+ # ---------------------------------------------------------------------------
57
+ # Read payload from stdin or argument
58
+ # ---------------------------------------------------------------------------
59
+
60
+ if (-not $EncodedPayload) {
61
+ # SSH ForceCommand stores the client's requested command in SSH_ORIGINAL_COMMAND
62
+ $EncodedPayload = if ($env:SSH_ORIGINAL_COMMAND) { $env:SSH_ORIGINAL_COMMAND.Trim() } else { "" }
63
+ }
64
+ if (-not $EncodedPayload) {
65
+ # Fallback: read from stdin (direct invocation or piped input)
66
+ $EncodedPayload = [Console]::In.ReadToEnd().Trim()
67
+ }
68
+
69
+ if (-not $EncodedPayload) {
70
+ Write-Output "Error: No payload provided"
71
+ exit 1
72
+ }
73
+
74
+ # Validate base64 format
75
+ if ($EncodedPayload -notmatch '^[A-Za-z0-9+/=]+$') {
76
+ Write-Output "Error: Payload must be base64-encoded"
77
+ exit 1
78
+ }
79
+
80
+ # Decode base64
81
+ try {
82
+ $decoded = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($EncodedPayload))
83
+ } catch {
84
+ Write-Output "Error: Failed to decode base64 payload"
85
+ exit 1
86
+ }
87
+
88
+ # ---------------------------------------------------------------------------
89
+ # Parse JSON payload
90
+ # ---------------------------------------------------------------------------
91
+
92
+ $script:Text = ""
93
+ $script:Voice = "en_US-lessac-medium"
94
+ $SoxEffects = ""
95
+ $BgFile = ""
96
+ # TODO(AVI-S8.6): generate this constant from the shared JSON source of truth.
97
+ $BgVolume = "0.20"
98
+ $script:Project = "unknown"
99
+ # Sender's absolute project path — forwarded to the watcher so the avatar can
100
+ # show/learn the real remote folder (paired with $script:Project).
101
+ $ProjectPath = ""
102
+ $Pretext = ""
103
+ $Speed = ""
104
+ $Provider = "piper"
105
+ $Llm = "default"
106
+ $Mute = "false"
107
+ $Language = ""
108
+ # kind: "speak" (default — synthesize text) or "music" (play a standalone
109
+ # background-music track, no speech). Set by the Music-tab remote preview.
110
+ $Kind = "speak"
111
+
112
+ if ($decoded.TrimStart().StartsWith('{')) {
113
+ # JSON payload
114
+ try {
115
+ $json = $decoded | ConvertFrom-Json
116
+ if ($json.text) { $script:Text = $json.text }
117
+ if ($json.voice) { $script:Voice = $json.voice }
118
+ if ($json.effects) { $SoxEffects = $json.effects }
119
+ if ($json.music) { $BgFile = $json.music }
120
+ if ($json.volume) { $BgVolume = $json.volume }
121
+ if ($json.project) { $script:Project = $json.project }
122
+ if ($json.projectPath) { $ProjectPath = $json.projectPath }
123
+ if ($json.pretext) { $Pretext = $json.pretext }
124
+ if ($json.speed) { $Speed = $json.speed }
125
+ if ($json.provider) { $Provider = $json.provider }
126
+ if ($json.llm) { $Llm = $json.llm }
127
+ # Forwarded as strings by the sender ("true"/"false"); ConvertFrom-Json
128
+ # may hand back a real boolean, so stringify defensively.
129
+ if ($null -ne $json.mute) { $Mute = ([string]$json.mute).ToLower() }
130
+ if ($json.language) { $Language = $json.language }
131
+ if ($json.kind) { $Kind = ([string]$json.kind).ToLower() }
132
+ } catch {
133
+ Write-Output "Error: Failed to parse JSON payload"
134
+ exit 1
135
+ }
136
+ } else {
137
+ # Legacy plain text
138
+ $script:Text = $decoded
139
+ }
140
+
141
+ # Validate text. Music-only previews carry no speech — they require a track
142
+ # (the "music" field) instead of text.
143
+ if ($Kind -eq 'music') {
144
+ if (-not $BgFile) {
145
+ Write-Output "Error: music payload has no track"
146
+ exit 1
147
+ }
148
+ } elseif ($Kind -eq 'music-stop') {
149
+ # Stop request carries neither text nor a track — nothing to validate.
150
+ } elseif (-not $script:Text) {
151
+ Write-Output "Error: No text in payload"
152
+ exit 1
153
+ }
154
+
155
+ # Validate voice format
156
+ if ($script:Voice -notmatch '^[a-zA-Z0-9_\-\. ]+$' -and $script:Voice -notmatch '^[a-zA-Z0-9_\-\. ]+::[a-zA-Z0-9_\-\. ]+$') {
157
+ $script:Voice = "en_US-lessac-medium"
158
+ }
159
+
160
+ # Validate provider
161
+ if ($Provider -notin @("piper", "soprano", "kokoro", "windows-sapi", "windows-piper", "macos")) {
162
+ $Provider = "piper"
163
+ }
164
+
165
+ # Validate LLM name - only safe identifier chars (mirrors play-tts.ps1 check)
166
+ if ($Llm -and $Llm -notmatch '^[a-zA-Z0-9][a-zA-Z0-9_-]*$') {
167
+ $Llm = "default"
168
+ }
169
+
170
+ # Music-only preview: the track must be a bare .mp3 filename (no path parts) —
171
+ # the watcher resolves it against ~/.claude/audio/tracks/ with a containment check.
172
+ if ($Kind -eq 'music' -and $BgFile -notmatch '^[A-Za-z0-9._][A-Za-z0-9._ \-]*\.mp3$') {
173
+ Write-Output "Error: invalid music track name"
174
+ exit 1
175
+ }
176
+
177
+ # Validate volume is numeric
178
+ if ($BgVolume -notmatch '^\d+\.?\d*$') {
179
+ $BgVolume = "0.20"
180
+ }
181
+
182
+ # Prepend pretext
183
+ if ($Pretext) {
184
+ $script:Text = "$Pretext. $($script:Text)"
185
+ }
186
+
187
+ # ---------------------------------------------------------------------------
188
+ # RECEIVER-AUTHORITATIVE: mute safety-OR (map §C, R17)
189
+ # ---------------------------------------------------------------------------
190
+ # effective_mute = receiver_local_mute OR forwarded_mute. If EITHER the sender
191
+ # (forwarded $Mute -eq "true") or THIS receiver host is muted, stay silent. A
192
+ # receiver-local mute always vetoes; the forwarded mute is a subordinate DEFAULT
193
+ # that can also veto. We NEVER force-unmute a muted receiver.
194
+ $ReceiverMuteFile = "$ClaudeDir\tts-muted.txt"
195
+ $ReceiverLocalMuted = $false
196
+ if (Test-Path $ReceiverMuteFile) {
197
+ if ((Get-Content $ReceiverMuteFile -Raw -ErrorAction SilentlyContinue).Trim() -eq "true") {
198
+ $ReceiverLocalMuted = $true
199
+ }
200
+ }
201
+ $EffectiveMute = ($Mute -eq "true") -or $ReceiverLocalMuted
202
+
203
+ # ---------------------------------------------------------------------------
204
+ # RECEIVER-AUTHORITATIVE: language fallback (map §D, R18)
205
+ # ---------------------------------------------------------------------------
206
+ # The receiver's OWN language config wins; otherwise use the forwarded language
207
+ # as the default. Non-destructive: we do NOT overwrite the receiver's own
208
+ # tts-language.txt — the effective value only rides along in the queue JSON.
209
+ $EffectiveLanguage = $Language
210
+ foreach ($lf in @("$ClaudeDir\tts-language.txt", "$ConfigDir\tts-language.txt")) {
211
+ if (Test-Path $lf) {
212
+ $rl = (Get-Content $lf -Raw -ErrorAction SilentlyContinue)
213
+ if ($rl) { $rl = $rl.Trim() }
214
+ if ($rl) { $EffectiveLanguage = $rl; break } # receiver's own config wins
215
+ }
216
+ }
217
+
218
+ Write-Log "RECEIVED" "provider=$Provider effects=$SoxEffects music=$BgFile mute=$Mute/$EffectiveMute lang=$EffectiveLanguage"
219
+
220
+ # RECEIVER-AUTHORITATIVE mute veto: if either side muted, do not queue/play.
221
+ if ($EffectiveMute) {
222
+ Write-Log "MUTED" "forwarded=$Mute receiverLocal=$ReceiverLocalMuted"
223
+ Write-Output "Muted (forwarded=$Mute, receiver-local=$ReceiverLocalMuted) - not playing"
224
+ exit 0
225
+ }
226
+
227
+ # ---------------------------------------------------------------------------
228
+ # Readiness bling — a short cue on the RECEIVER, played BEFORE we generate/play,
229
+ # so a listener knows audio is incoming (kokoro synth + track load have latency).
230
+ # Standardizes the UX across voice AND music: bling first, then the audio. A
231
+ # music-stop carries no audio, so it gets no cue. Best-effort + synchronous (the
232
+ # cue is ~0.7s and must finish before the track so they don't overlap); a missing
233
+ # wav or player failure must NEVER block or fail the actual playback.
234
+ # ---------------------------------------------------------------------------
235
+ if ($Kind -eq 'music') {
236
+ $blingWav = "$ClaudeDir\audio\ui\bling-success.wav"
237
+ if (-not (Test-Path $blingWav)) { $blingWav = "$env:USERPROFILE\.agentvibes\bling-success.wav" }
238
+ if (Test-Path $blingWav) {
239
+ try {
240
+ $ffb = Get-Command ffplay -ErrorAction SilentlyContinue
241
+ if ($ffb) { & $ffb.Source -autoexit -nodisp -loglevel quiet "$blingWav" 2>$null }
242
+ else { (New-Object System.Media.SoundPlayer "$blingWav").PlaySync() }
243
+ } catch { Write-Log "WARN" "readiness bling failed: $($_.Exception.Message)" }
244
+ }
245
+ }
246
+
247
+ # ---------------------------------------------------------------------------
248
+ # Configure voice for play-tts.ps1
249
+ # ---------------------------------------------------------------------------
250
+
251
+ # Write voice to tts-voice.txt so play-tts-piper.ps1 picks it up
252
+ # Format: "voiceId" or "voiceId::SpeakerName-ID"
253
+ $voiceFile = "$ClaudeDir\tts-voice.txt"
254
+ Set-Content -Path $voiceFile -Value $script:Voice -NoNewline -ErrorAction SilentlyContinue
255
+
256
+ # Background music and effects are now forwarded through the queue JSON
257
+ # (music/volume/effects fields) and applied via AGENTVIBES_OVERRIDE_*
258
+ # env vars in the watcher. No need to mutate audio-effects.cfg here.
259
+
260
+ # Set speed if specified
261
+ if ($Speed -and $Speed -match '^\d+\.?\d*$') {
262
+ Set-Content -Path "$ConfigDir\tts-speed.txt" -Value $Speed -NoNewline -ErrorAction SilentlyContinue
263
+ }
264
+
265
+ # ---------------------------------------------------------------------------
266
+ # Drop into queue for the user-session watcher to play
267
+ # ---------------------------------------------------------------------------
268
+ #
269
+ # CRITICAL: SSH receiver runs in Windows session 0 (sshd service), which has
270
+ # NO access to audio devices. Calling play-tts.ps1 directly here would run
271
+ # silently - synthesis succeeds, but PlaySync writes to a null audio sink.
272
+ #
273
+ # Instead, write a JSON request to ~/.agentvibes/tts-queue/. The watcher
274
+ # (tts-watcher.ps1, started by start-watcher.vbs in the user session via the
275
+ # Startup folder shortcut) polls this dir and runs play-tts.ps1 with audio
276
+ # device access.
277
+
278
+ $QueueDir = "$OwnerHome\.agentvibes\tts-queue"
279
+ if (-not (Test-Path $QueueDir)) {
280
+ New-Item -ItemType Directory -Path $QueueDir -Force | Out-Null
281
+ }
282
+
283
+ $ReqId = [Guid]::NewGuid().ToString().Substring(0, 8)
284
+ $ReqFile = "$QueueDir\req-$ReqId.json"
285
+ # Pass ALL per-call overrides through the queue so the watcher applies
286
+ # them without mutating audio-effects.cfg (avoids race conditions).
287
+ $ReqJson = @{
288
+ id = $ReqId
289
+ text = $script:Text
290
+ voice = $script:Voice
291
+ music = $BgFile
292
+ volume = $BgVolume
293
+ effects = $SoxEffects
294
+ speed = $Speed
295
+ provider = $Provider
296
+ llm = $Llm
297
+ language = $EffectiveLanguage
298
+ kind = $Kind
299
+ project = $script:Project
300
+ projectPath = $ProjectPath
301
+ } | ConvertTo-Json -Compress
302
+
303
+ try {
304
+ [System.IO.File]::WriteAllText($ReqFile, $ReqJson, [System.Text.UTF8Encoding]::new($false))
305
+ Write-Log "QUEUED" "id=$ReqId voice=$($script:Voice)"
306
+ Write-Output "Queued for playback: $ReqId"
307
+ } catch {
308
+ Write-Log "ERROR" "Queue write failed: $_"
309
+ Write-Output "Error: failed to queue TTS request: $_"
310
+ exit 1
311
+ }