agentvibes 5.13.1 → 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.
- package/.claude/audio/ui/CREDITS.txt +16 -16
- package/.claude/commands/agent-vibes-bmad-voices.md +117 -117
- package/.claude/commands/agent-vibes-rdp.md +24 -24
- package/.claude/config/audio-effects.cfg.sample +52 -52
- package/.claude/docs/TERMUX_SETUP.md +408 -408
- package/.claude/github-star-reminder.txt +1 -1
- package/.claude/hooks/kokoro-server.py +219 -219
- package/.claude/hooks/kokoro-tts.py +141 -141
- package/.claude/hooks/party-stage-roster.py +328 -328
- package/.claude/hooks/play-tts-kokoro.sh +7 -2
- package/.claude/hooks/play-tts-ssh-remote.sh +38 -3
- package/.claude/hooks/requirements.txt +6 -6
- package/.claude/hooks/soprano-gradio-synth.py +139 -139
- package/.claude/hooks/translator.py +237 -237
- package/.claude/hooks-windows/kokoro-server.py +219 -219
- package/.claude/hooks-windows/kokoro-tts.py +107 -107
- package/.claude/hooks-windows/play-tts.ps1 +17 -0
- package/.claude/hooks-windows/soprano-gradio-synth.py +153 -153
- package/.claude/verbosity.txt +1 -1
- package/.clawdbot/README.md +105 -105
- package/CLAUDE.md +84 -176
- package/README.md +27 -2
- package/RELEASE_NOTES.md +68 -0
- package/WINDOWS-SETUP.md +208 -208
- package/bin/agent-vibes +39 -39
- package/bin/mcp-server.js +121 -121
- package/bin/test-bmad-pr +78 -78
- package/mcp-server/QUICK_START.md +203 -203
- package/mcp-server/README.md +345 -345
- package/mcp-server/examples/claude_desktop_config.json +11 -11
- package/mcp-server/examples/claude_desktop_config_piper.json +9 -9
- package/mcp-server/examples/custom_instructions.md +169 -169
- package/mcp-server/install-deps.js +12 -2
- package/mcp-server/server.py +2111 -2085
- package/mcp-server/test_mcp_correctness.py +504 -504
- package/mcp-server/test_windows_script_parity.py +339 -339
- package/package.json +6 -3
- package/setup-ssh-receiver.ps1 +259 -0
- package/setup-windows.ps1 +39 -29
- package/src/cli/list-personalities.js +110 -110
- package/src/commands/bmad-voices.js +394 -394
- package/src/console/brand-colors.js +13 -13
- package/src/console/constants/personalities.js +44 -44
- package/src/console/tabs/agents-tab.js +14 -4
- package/src/console/tabs/help-tab.js +314 -314
- package/src/console/tabs/readme-tab.js +272 -272
- package/src/console/tabs/settings-tab.js +31 -6
- package/src/console/tabs/setup-tab.js +154 -32
- package/src/console/tabs/voices-tab.js +48 -6
- package/src/console/widgets/destroy-list.js +25 -25
- package/src/console/widgets/notice.js +55 -55
- package/src/installer/language-screen.js +31 -31
- package/src/installer/music-file-input.js +304 -304
- package/src/installer.js +91 -35
- package/src/services/language-service.js +47 -47
- package/src/services/llm-provider-service.js +4 -1
- package/src/services/provider-voice-catalog.js +9 -5
- package/src/utils/audio-format-validator.js +277 -277
- package/src/utils/dependency-checker.js +469 -469
- package/src/utils/file-ownership-verifier.js +358 -358
- package/src/utils/music-file-validator.js +285 -285
- package/src/utils/preview-list-prompt.js +144 -144
- package/src/utils/secure-music-storage.js +412 -412
- package/templates/agentvibes-receiver.ps1 +311 -0
- package/templates/agentvibes-receiver.sh +297 -297
- package/voice-assignments.json +8244 -8244
- package/.agentvibes/config.json +0 -17
- package/.agentvibes/install-manifest.json +0 -362
- package/.claude/config/audio-effects.cfg +0 -7
- package/.claude/config/audio-effects.cfg.bak-kokoro +0 -7
- package/.claude/config/background-music-enabled.txt +0 -1
- package/.claude/config/background-music-position.txt +0 -27
- package/.claude/config/background-music-volume.txt +0 -1
- package/.claude/config/background-music.cfg +0 -1
- package/.claude/config/background-music.txt +0 -1
- package/.claude/config/language.txt +0 -1
- package/.claude/config/personality.txt +0 -1
- package/.claude/config/reverb-level.txt +0 -1
- package/.claude/config/tts-speech-rate.txt +0 -1
- package/.claude/config/tts-verbosity.txt +0 -1
- package/.claude/hooks-windows/audio-cache-utils.ps1.user.bak +0 -119
- 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
|
+
}
|