agentvibes 5.7.7 → 5.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/.agentvibes/config.json +0 -2
  2. package/.claude/config/audio-effects.cfg +4 -4
  3. package/.claude/config/background-music-enabled.txt +1 -0
  4. package/.claude/github-star-reminder.txt +1 -1
  5. package/.claude/hooks/play-tts-piper.sh +20 -13
  6. package/.claude/hooks/play-tts-ssh-remote.sh +2 -2
  7. package/.claude/hooks/voice-manager.sh +6 -0
  8. package/.claude/hooks-windows/audio-cache-utils.ps1 +119 -119
  9. package/.claude/hooks-windows/bmad-speak.ps1 +9 -38
  10. package/.claude/hooks-windows/play-tts-soprano.ps1 +13 -2
  11. package/.claude/hooks-windows/play-tts-windows-piper.ps1 +22 -16
  12. package/.mcp.json +13 -9
  13. package/README.md +33 -2
  14. package/RELEASE_NOTES.md +80 -0
  15. package/mcp-server/server.py +17 -7
  16. package/package.json +2 -2
  17. package/src/commands/install-mcp.js +270 -16
  18. package/src/console/app.js +3 -3
  19. package/src/console/audio-env.js +4 -1
  20. package/src/console/tabs/agents-tab.js +89 -66
  21. package/src/console/tabs/music-tab.js +4 -3
  22. package/src/console/tabs/receiver-tab.js +13 -13
  23. package/src/console/tabs/settings-tab.js +2 -2
  24. package/src/console/tabs/setup-tab.js +291 -47
  25. package/src/console/tabs/voices-tab.js +17 -5
  26. package/src/console/widgets/personality-picker.js +2 -2
  27. package/src/console/widgets/reverb-picker.js +1 -1
  28. package/src/installer.js +32 -27
  29. package/src/services/provider-service.js +1 -1
  30. package/src/services/tts-engine-service.js +2 -2
  31. package/src/utils/audio-duration-validator.js +2 -2
  32. package/src/utils/list-formatter.js +9 -3
  33. package/src/utils/platform-resolver.js +369 -0
  34. package/src/utils/provider-validator.js +9 -9
  35. package/.agentvibes/install-manifest.json +0 -442
  36. package/.claude/config/background-music-position.txt +0 -27
  37. package/.claude/config/background-music-volume.txt +0 -1
  38. package/.claude/config/background-music.cfg +0 -1
  39. package/.claude/config/background-music.txt +0 -1
  40. package/.claude/config/reverb-level.txt +0 -1
  41. package/.claude/config/tts-speech-rate.txt +0 -1
  42. package/.claude/config/tts-verbosity.txt +0 -1
  43. package/.claude/hooks/bmad-party-manager.sh +0 -225
  44. package/.claude/hooks/stop.sh +0 -38
  45. package/.claude/piper-voices-dir.txt +0 -1
  46. /package/.claude/audio/tracks/{CelestialVelvet.mp3 → celestial_velvet.mp3} +0 -0
@@ -1,5 +1,3 @@
1
1
  {
2
- "audio_destination": "remote",
3
- "audio_ssh_alias": "laptop-win",
4
2
  "setupCompleted": true
5
3
  }
@@ -1,6 +1,6 @@
1
1
 
2
2
  llm:default|light||0.15|en_US-lessac-high||piper
3
- llm:claude-code|light|agent_vibes_celtic_harp_v1_loop.mp3|0.05|en_US-libritts-high::Nina-13|agentvibes here|piper
4
- llm:copilot|light|agent_vibes_bossa_nova_v2_loop.mp3|0.15|en_US-libritts-high::Anna-11|Copilot here|piper
5
- llm:codex|light|agent_vibes_chillwave_v2_loop.mp3|0.15|en_US-lessac-high|Codex here|piper
6
- llm:hermes|light|agent_vibes_bachata_v1_loop.mp3|0.15|en_US-libritts-high::Leo-8|Hermes here|piper
3
+ llm:claude-code|light|agent_vibes_chillwave_v2_loop.mp3|0.15|en_US-lessac-high||piper
4
+ llm:copilot|light|agent_vibes_bossa_nova_v2_loop.mp3|0.15|en_US-libritts-high::Anna-11||piper
5
+ llm:codex|light|agent_vibes_chillwave_v2_loop.mp3|0.15|en_US-lessac-high||piper
6
+ llm:hermes|light|agent_vibes_bachata_v1_loop.mp3|0.15|en_US-libritts-high::Leo-8||piper
@@ -1 +1 @@
1
- 20260517
1
+ 20260525
@@ -73,10 +73,8 @@ TEXT=$(printf '%s' "$TEXT" | perl -CSD -pe '
73
73
  s/^\s*[-]\s*//g; # list dashes
74
74
  ')
75
75
 
76
- # Source voice manager and language manager
77
- # Use readlink -f to handle symlinks correctly
78
- SCRIPT_PATH="$(readlink -f "${BASH_SOURCE[0]}")"
79
- SCRIPT_DIR="$(dirname "$SCRIPT_PATH")"
76
+ # cd-based resolution works on macOS (BSD readlink lacks -f) and Linux alike
77
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
80
78
  source "$SCRIPT_DIR/piper-voice-manager.sh"
81
79
  source "$SCRIPT_DIR/language-manager.sh"
82
80
  source "$SCRIPT_DIR/audio-cache-utils.sh"
@@ -226,11 +224,19 @@ if [[ -z "$TEXT" ]]; then
226
224
  fi
227
225
 
228
226
  # Augment PATH for non-interactive shells (pipx installs to ~/.local/bin which
229
- # interactive shells get via .bashrc/.zshrc, but Bash tool calls skip profile)
227
+ # interactive shells get via .bashrc/.zshrc, but Bash tool calls skip profile).
228
+ # Mac: add both Apple Silicon (/opt/homebrew) and Intel (/usr/local) Homebrew locations.
230
229
  export PATH="$HOME/.local/bin:$HOME/.local/share/pipx/venvs/piper-tts/bin:$PATH"
230
+ if [[ "$(uname -s 2>/dev/null)" == "Darwin" ]]; then
231
+ export PATH="/opt/homebrew/bin:/usr/local/bin:$PATH"
232
+ fi
233
+
234
+ # Resolve explicit piper binary path — avoids bare `piper` invocation failing when
235
+ # PATH augmentation above hasn't propagated into nested subshells.
236
+ PIPER_BIN=$(command -v piper 2>/dev/null || echo "")
231
237
 
232
238
  # Check if Piper is installed
233
- if ! command -v piper &> /dev/null; then
239
+ if [[ -z "$PIPER_BIN" ]]; then
234
240
  echo "❌ Error: Piper TTS not installed"
235
241
  echo "Install with: pipx install piper-tts"
236
242
  echo "Or run: .claude/hooks/piper-installer.sh"
@@ -309,6 +315,8 @@ else
309
315
  fi
310
316
 
311
317
  mkdir -p "$AUDIO_DIR"
318
+ # Normalize to canonical path (handles Git Bash /tmp→/c/Users/..., macOS /var→/private/var)
319
+ AUDIO_DIR=$(cd "$AUDIO_DIR" && pwd -P)
312
320
  _tmp=$(mktemp "$AUDIO_DIR/tts-XXXXXX"); TEMP_FILE="${_tmp}.wav"; mv "$_tmp" "$TEMP_FILE"
313
321
 
314
322
  # @function get_speech_rate
@@ -380,6 +388,10 @@ get_speech_rate() {
380
388
 
381
389
  SPEECH_RATE=$(get_speech_rate)
382
390
 
391
+ # Ensure piper log directory exists so stderr redirect never silently fails
392
+ _PIPER_LOG_DIR="${AGENTVIBES_LOG_DIR:-$HOME/.local/state/agentvibes/logs}"
393
+ mkdir -p "$_PIPER_LOG_DIR" 2>/dev/null || true
394
+
383
395
  # @function synthesize_with_piper
384
396
  # @intent Generate speech using Piper TTS
385
397
  # @why Provides free, offline TTS alternative
@@ -391,10 +403,10 @@ SPEECH_RATE=$(get_speech_rate)
391
403
  if [[ -n "${SPEAKER_ID:-}" ]]; then
392
404
  # Multi-speaker voice: Pass speaker ID
393
405
  # SECURITY: Use printf instead of echo for pipe safety (#134)
394
- printf '%s\n' "$TEXT" | piper --model "$VOICE_PATH" --speaker "$SPEAKER_ID" --length-scale "$SPEECH_RATE" --sentence-silence 2.0 --output_file "$TEMP_FILE" 2>/dev/null
406
+ printf '%s\n' "$TEXT" | "$PIPER_BIN" --model "$VOICE_PATH" --speaker "$SPEAKER_ID" --length-scale "$SPEECH_RATE" --sentence-silence 2.0 --output_file "$TEMP_FILE" 2>>"$_PIPER_LOG_DIR/piper.log"
395
407
  else
396
408
  # Single-speaker voice
397
- printf '%s\n' "$TEXT" | piper --model "$VOICE_PATH" --length-scale "$SPEECH_RATE" --sentence-silence 2.0 --output_file "$TEMP_FILE" 2>/dev/null
409
+ printf '%s\n' "$TEXT" | "$PIPER_BIN" --model "$VOICE_PATH" --length-scale "$SPEECH_RATE" --sentence-silence 2.0 --output_file "$TEMP_FILE" 2>>"$_PIPER_LOG_DIR/piper.log"
398
410
  fi
399
411
 
400
412
  if [[ ! -f "$TEMP_FILE" ]] || [[ ! -s "$TEMP_FILE" ]]; then
@@ -538,11 +550,6 @@ AUDIO_DIR="${TEMP_FILE%/*}"
538
550
  WRITE_LOCK_FILE="$AUDIO_DIR/$(basename "$TEMP_FILE" .wav).lock"
539
551
  touch "$WRITE_LOCK_FILE"
540
552
 
541
- # Get audio duration for proper lock timing
542
- DURATION=$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$TEMP_FILE" 2>/dev/null)
543
- DURATION=${DURATION%.*} # Round to integer
544
- DURATION=${DURATION:-1} # Default to 1 second if detection fails
545
-
546
553
  # Get audio duration for proper lock timing
547
554
  DURATION=$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$TEMP_FILE" 2>/dev/null || true)
548
555
  DURATION=${DURATION%.*} # Round to integer
@@ -60,7 +60,7 @@ SSH_PORT=""
60
60
  if [[ -n "${AGENTVIBES_SSH_HOST:-}" ]]; then
61
61
  SSH_HOST="$AGENTVIBES_SSH_HOST"
62
62
  SSH_KEY="${AGENTVIBES_SSH_KEY:-}"
63
- SSH_PORT="${AGENTVIBES_SSH_PORT:-22}"
63
+ SSH_PORT="${AGENTVIBES_SSH_PORT:-}"
64
64
  fi
65
65
 
66
66
  # Priority 2: ~/.agentvibes/transport-config.json (ssh-remote section)
@@ -340,7 +340,7 @@ SSH_ARGS=()
340
340
  # Run ssh inside the backgrounded subshell so its exit code is reachable via $?
341
341
  # (a `wait` from outside the spawning shell would error: "pid X is not a child").
342
342
  (
343
- ssh "${SSH_ARGS[@]}" "$SSH_HOST" "$ENCODED_PAYLOAD"
343
+ ssh -o ConnectTimeout=10 "${SSH_ARGS[@]}" "$SSH_HOST" "$ENCODED_PAYLOAD"
344
344
  _exit=$?
345
345
  if [[ $_exit -ne 0 ]]; then
346
346
  echo "$(date -Iseconds) [ERROR] SSH to $SSH_HOST failed (exit $_exit)" \
@@ -513,6 +513,12 @@ case "$1" in
513
513
  fi
514
514
  fi
515
515
 
516
+ # Normalize to canonical path so ls output is consistent across platforms
517
+ # (Git Bash resolves /tmp→/c/Users/..., macOS has /var→/private/var)
518
+ if [[ -d "$AUDIO_DIR" ]]; then
519
+ AUDIO_DIR=$(cd "$AUDIO_DIR" && pwd -P)
520
+ fi
521
+
516
522
  # Default to replay last audio (N=1)
517
523
  N="${2:-1}"
518
524
 
@@ -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
+ }
@@ -198,29 +198,13 @@ if ($AgentPersonality -and (Test-Path (Split-Path $PersonalityFile -Parent))) {
198
198
  }
199
199
 
200
200
  # ---------------------------------------------------------------------------
201
- # Temporarily patch background music config for this agent.
202
- # The caller (bmad-party-speak.ps1) holds a named mutex so only one speak
203
- # call runs at a time these file patches are safe from concurrent clobber.
204
- $BgEnabledFile = Join-Path $TtsConfigDir "background-music-enabled.txt"
205
- $AudioEffectsCfg = Join-Path $TtsConfigDir "audio-effects.cfg"
206
- $OldBgEnabled = $null
207
- $TempCfgLine = ""
208
-
201
+ # Apply per-agent background music via AGENTVIBES_OVERRIDE_* env vars.
202
+ # play-tts.ps1 reads these directly and forces BgEnabled=true when OVERRIDE_MUSIC
203
+ # is set no config file patching needed, and cleanup is automatic (env vars
204
+ # are scoped to the child process spawned by & powershell below).
209
205
  if ($AgentBgEnabled -and $AgentBgTrack) {
210
- # Save + enable background music
211
- if (Test-Path $BgEnabledFile) {
212
- $OldBgEnabled = (Get-Content $BgEnabledFile -Raw -ErrorAction SilentlyContinue).Trim()
213
- }
214
- Set-Content $BgEnabledFile "true" -NoNewline
215
-
216
- # Prepend agent line to audio-effects.cfg so play-tts.ps1 finds it first
217
- # Format: AGENT_NAME|SOX_EFFECTS|BACKGROUND_FILE|BACKGROUND_VOLUME
218
- $TempCfgLine = "${AgentId}||${AgentBgTrack}|${AgentBgVolume}"
219
- $env:AGENTVIBES_AGENT_NAME = $AgentId
220
- $existingCfg = if (Test-Path $AudioEffectsCfg) {
221
- Get-Content $AudioEffectsCfg -Raw -ErrorAction SilentlyContinue
222
- } else { "" }
223
- Set-Content $AudioEffectsCfg "${TempCfgLine}`n${existingCfg}" -NoNewline
206
+ $env:AGENTVIBES_OVERRIDE_MUSIC = $AgentBgTrack
207
+ $env:AGENTVIBES_OVERRIDE_VOLUME = $AgentBgVolume
224
208
  }
225
209
 
226
210
  try {
@@ -243,22 +227,9 @@ try {
243
227
  }
244
228
  }
245
229
 
246
- # Restore background music config
230
+ # Clear music override env vars (use SetEnvironmentVariable to fully remove, not just empty)
247
231
  if ($AgentBgEnabled -and $AgentBgTrack) {
248
- if ($null -ne $OldBgEnabled) {
249
- Set-Content $BgEnabledFile $OldBgEnabled -NoNewline
250
- } elseif (Test-Path $BgEnabledFile) {
251
- Remove-Item $BgEnabledFile -Force -ErrorAction SilentlyContinue
252
- }
253
-
254
- # Remove the prepended agent line from audio-effects.cfg
255
- if (Test-Path $AudioEffectsCfg) {
256
- $cfgRaw = Get-Content $AudioEffectsCfg -Raw -ErrorAction SilentlyContinue
257
- $escaped = [regex]::Escape($TempCfgLine)
258
- $cfgRaw = $cfgRaw -replace "^${escaped}\r?\n?", ""
259
- Set-Content $AudioEffectsCfg $cfgRaw -NoNewline
260
- }
261
-
262
- $env:AGENTVIBES_AGENT_NAME = ""
232
+ [System.Environment]::SetEnvironmentVariable("AGENTVIBES_OVERRIDE_MUSIC", $null, "Process")
233
+ [System.Environment]::SetEnvironmentVariable("AGENTVIBES_OVERRIDE_VOLUME", $null, "Process")
263
234
  }
264
235
  }
@@ -20,6 +20,13 @@ param(
20
20
 
21
21
  $ErrorActionPreference = "Stop"
22
22
 
23
+ # Append registry PATH entries so Python-installed scripts (soprano, soprano-webui) are
24
+ # found when spawned from Node.js. Prepend existing PATH to preserve runtime shims
25
+ # (nvm, conda, pyenv) that were added after the process started.
26
+ $env:Path = $env:Path + ";" +
27
+ [System.Environment]::GetEnvironmentVariable("Path", "Machine") + ";" +
28
+ [System.Environment]::GetEnvironmentVariable("Path", "User")
29
+
23
30
  $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
24
31
  # Validate port is numeric to prevent injection
25
32
  $SopranoPort = "7860"
@@ -101,7 +108,8 @@ if (Test-WebUI) {
101
108
  $SynthMode = "webui"
102
109
  $pythonHelper = Join-Path $ScriptDir "soprano-gradio-synth.py"
103
110
  if (Test-Path $pythonHelper) {
104
- & python $pythonHelper $Text $TempFile $SopranoPort 2>$null
111
+ # Wrap in try/catch: PS7.3+ throws on native non-zero exit with ErrorActionPreference=Stop
112
+ try { & python $pythonHelper $Text $TempFile $SopranoPort 2>$null } catch { }
105
113
  } else {
106
114
  Write-Host "[ERROR] soprano-gradio-synth.py not found" -ForegroundColor Red
107
115
  exit 1
@@ -123,7 +131,8 @@ if (Test-WebUI) {
123
131
  } elseif (Test-SopranoCLI) {
124
132
  # CLI fallback - reloads model each call (slowest)
125
133
  $SynthMode = "cli"
126
- & soprano $Text -o $TempFile -d $SopranoDevice 2>$null
134
+ # Wrap in try/catch: PS7.3+ throws on native non-zero exit with ErrorActionPreference=Stop
135
+ try { & soprano $Text -o $TempFile -d $SopranoDevice 2>$null } catch { }
127
136
  } else {
128
137
  Write-Host "[ERROR] Soprano TTS not installed and no server running on port $SopranoPort" -ForegroundColor Red
129
138
  Write-Host ""
@@ -156,3 +165,5 @@ if (-not $env:AGENTVIBES_NO_PLAY) {
156
165
 
157
166
  Write-Host "Saved to: $TempFile"
158
167
  Write-Host "Voice: Soprano-1.1-80M (Soprano TTS, $SynthMode mode)"
168
+ # Output bare path so play-tts.ps1 can capture it for reverb/bg-music processing
169
+ Write-Output $TempFile
@@ -62,15 +62,20 @@ if (-not $VoiceName) {
62
62
  }
63
63
 
64
64
  # Security: Validate voice name to prevent path traversal
65
- # Only allow alphanumeric, underscore, hyphen, and period
66
- if ($VoiceName -notmatch '^[a-zA-Z0-9_\-\.]+$') {
65
+ # Format: <model> or <model>::<speaker> for multi-speaker Piper models
66
+ # Only allow alphanumeric, underscore, hyphen, period, and the :: separator
67
+ if ($VoiceName -notmatch '^[a-zA-Z0-9_\-\.]+(::[a-zA-Z0-9_\-\.]+)?$') {
67
68
  Write-Host "[ERROR] Invalid voice name: $VoiceName" -ForegroundColor Red
68
69
  exit 1
69
70
  }
70
71
 
72
+ # Extract model name and optional speaker (multi-speaker format: model::Speaker)
73
+ $VoiceModel = ($VoiceName -split '::')[0]
74
+ $SpeakerName = if ($VoiceName -match '::(.+)$') { $Matches[1] } else { "" }
75
+
71
76
  # Resolve voice model path and validate it stays within VoicesDir
72
- $VoiceModelFile = [System.IO.Path]::GetFullPath("$VoicesDir\$VoiceName.onnx")
73
- $VoiceJsonFile = [System.IO.Path]::GetFullPath("$VoicesDir\$VoiceName.onnx.json")
77
+ $VoiceModelFile = [System.IO.Path]::GetFullPath("$VoicesDir\$VoiceModel.onnx")
78
+ $VoiceJsonFile = [System.IO.Path]::GetFullPath("$VoicesDir\$VoiceModel.onnx.json")
74
79
  $ResolvedVoicesDir = [System.IO.Path]::GetFullPath($VoicesDir)
75
80
  if (-not $VoiceModelFile.StartsWith($ResolvedVoicesDir)) {
76
81
  Write-Host "[ERROR] Voice path outside voices directory" -ForegroundColor Red
@@ -79,15 +84,15 @@ if (-not $VoiceModelFile.StartsWith($ResolvedVoicesDir)) {
79
84
 
80
85
  # Check if voice model exists, download if missing
81
86
  if (-not (Test-Path $VoiceModelFile)) {
82
- Write-Host "[DOWNLOAD] Voice model: $VoiceName" -ForegroundColor Yellow
87
+ Write-Host "[DOWNLOAD] Voice model: $VoiceModel" -ForegroundColor Yellow
83
88
 
84
89
  # Try to download from Hugging Face
85
90
  # Voice name format: {lang}_{region}-{speaker}-{quality}
86
91
  # HF path format: {lang}/{lang}_{region}/{speaker}/{quality}/{voicename}.onnx
87
92
  try {
88
- # Parse voice name to build correct HF path
93
+ # Parse model name to build correct HF path (strip ::Speaker suffix first)
89
94
  # e.g. en_US-ryan-high -> en/en_US/ryan/high/en_US-ryan-high.onnx
90
- if ($VoiceName -match '^([a-z]{2})_([A-Z]{2})-([a-zA-Z0-9_]+)-([a-z]+)$') {
95
+ if ($VoiceModel -match '^([a-z]{2})_([A-Z]{2})-([a-zA-Z0-9_]+)-([a-z]+)$') {
91
96
  $Lang = $Matches[1]
92
97
  $LangRegion = "$($Matches[1])_$($Matches[2])"
93
98
  $Speaker = $Matches[3]
@@ -97,8 +102,8 @@ if (-not (Test-Path $VoiceModelFile)) {
97
102
  # Fallback for non-standard voice names
98
103
  $HFBase = "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/ryan/high"
99
104
  }
100
- $ModelUrl = "$HFBase/$VoiceName.onnx"
101
- $JsonUrl = "$HFBase/$VoiceName.onnx.json"
105
+ $ModelUrl = "$HFBase/$VoiceModel.onnx"
106
+ $JsonUrl = "$HFBase/$VoiceModel.onnx.json"
102
107
 
103
108
  Write-Host " Downloading model..." -ForegroundColor Cyan
104
109
  Invoke-WebRequest -Uri $ModelUrl -OutFile $VoiceModelFile -ErrorAction Stop
@@ -127,11 +132,10 @@ $AudioFile = "$AudioDir\tts-$Timestamp.wav"
127
132
  try {
128
133
  Write-Host "[SYNTH] Synthesizing with Piper..." -ForegroundColor Cyan
129
134
 
130
- # Run Piper with text input
131
- $Text | & $PiperExe `
132
- --model $VoiceModelFile `
133
- --output-file $AudioFile `
134
- 2>$null
135
+ # Run Piper with text input; pass --speaker for multi-speaker models (model::Speaker format)
136
+ $piperArgs = @('--model', $VoiceModelFile, '--output-file', $AudioFile)
137
+ if ($SpeakerName) { $piperArgs += @('--speaker', $SpeakerName) }
138
+ $Text | & $PiperExe @piperArgs 2>$null
135
139
 
136
140
  if (-not (Test-Path $AudioFile)) {
137
141
  Write-Host "[ERROR] Piper synthesis failed" -ForegroundColor Red
@@ -151,12 +155,14 @@ try {
151
155
  if (-not $env:AGENTVIBES_NO_PLAY) {
152
156
  # Prefer ffplay: handles 22050 Hz → 48000 Hz resampling cleanly (SoundPlayer uses
153
157
  # WinMM's low-quality resampler which produces choppy audio at non-native rates).
154
- $ffplayPath = (Get-Command ffplay -ErrorAction SilentlyContinue)?.Source
158
+ $ffplayCmd = Get-Command ffplay -ErrorAction SilentlyContinue
159
+ $ffplayPath = if ($ffplayCmd) { $ffplayCmd.Source }
155
160
  if (-not $ffplayPath) {
156
161
  # SSH/watcher sessions may have a minimal PATH — refresh from registry
157
162
  $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" +
158
163
  [System.Environment]::GetEnvironmentVariable("Path","User")
159
- $ffplayPath = (Get-Command ffplay -ErrorAction SilentlyContinue)?.Source
164
+ $ffplayCmd = Get-Command ffplay -ErrorAction SilentlyContinue
165
+ $ffplayPath = if ($ffplayCmd) { $ffplayCmd.Source }
160
166
  }
161
167
  if ($ffplayPath) {
162
168
  & $ffplayPath -autoexit -nodisp -loglevel quiet $AudioFile 2>$null
package/.mcp.json CHANGED
@@ -1,20 +1,24 @@
1
1
  {
2
2
  "mcpServers": {
3
- "agentvibes": {
3
+ "firecrawl": {
4
+ "command": "npx",
5
+ "args": [
6
+ "firecrawl-mcp"
7
+ ]
8
+ },
9
+ "context7": {
4
10
  "command": "npx",
5
11
  "args": [
6
12
  "-y",
7
- "--package=agentvibes",
8
- "agentvibes-mcp-server"
13
+ "@upstash/context7-mcp"
9
14
  ],
10
15
  "env": {
11
- "AGENTVIBES_MCP_FALLBACK": "copilot"
16
+ "CONTEXT7_API_KEY": "${CONTEXT7_API_KEY}"
12
17
  }
13
- },
14
- "firecrawl": {
15
- "command": "npx",
16
- "args": [
17
- "firecrawl-mcp"
18
+ }
19
+ }
20
+ }
21
+ awl-mcp"
18
22
  ]
19
23
  },
20
24
  "context7": {
package/README.md CHANGED
@@ -11,7 +11,7 @@
11
11
  [![Publish](https://github.com/paulpreibisch/AgentVibes/actions/workflows/publish.yml/badge.svg)](https://github.com/paulpreibisch/AgentVibes/actions/workflows/publish.yml)
12
12
  [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
13
13
 
14
- **Author**: Paul Preibisch ([@997Fire](https://x.com/997Fire)) | **Version**: v5.7.7
14
+ **Author**: Paul Preibisch ([@997Fire](https://x.com/997Fire)) | **Version**: v5.10.0
15
15
 
16
16
  ---
17
17
 
@@ -40,7 +40,33 @@ Whether you're coding in Claude Code, chatting in Claude Desktop, using Warp Ter
40
40
 
41
41
  ---
42
42
 
43
- ## 🌟 NEW IN v5.7.7Party Mode Voice Restore + Polish
43
+ ## 🌟 NEW IN v5.9.0SSH Remote + Windows Home Directory Fixes
44
+
45
+ **SSH remote no longer hangs:** The SSH transport now applies a 10-second connection
46
+ timeout, so a stuck or unreachable remote host surfaces an error quickly instead of
47
+ blocking forever.
48
+
49
+ **Windows home directory detection fixed:** `detectRemoteLlm()` now uses null-coalescing
50
+ (`??`) to fall back to `os.homedir()` only when `HOME` is genuinely unset — safer than
51
+ the previous `||` which could misfire on an empty string.
52
+
53
+ ## v5.8.0 — Soprano Now Works + Voice Picker Fixed for All Engines
54
+
55
+ **Soprano TTS actually works now:** Soprano (our neural TTS engine) was silently broken
56
+ on Windows since launch — wrong binary name, stripped PATH, wav path sent to the wrong
57
+ output stream, and no auto-start for the WebUI server. All fixed. Install with
58
+ `pip install soprano-tts`, select Soprano in the setup tab, and AgentVibes handles
59
+ the rest.
60
+
61
+ **Voice picker now works for Windows SAPI and macOS Say:** Previously the picker showed
62
+ the entire Piper voice catalog even when SAPI or macOS Say was selected, and Space-bar
63
+ preview played through the wrong engine. The picker now shows exactly one item for each
64
+ native engine and previews through the correct binary.
65
+
66
+ **Auto-save no longer breaks your engine setting:** Saving an LLM config no longer silently
67
+ overwrites your chosen engine back to Piper.
68
+
69
+ ## v5.7.7 — Party Mode Voice Restore + Polish
44
70
 
45
71
  **Party mode agents now speak again:** BMAD `/party-mode` now reliably invokes the correct AgentVibes skill, and each agent's response is spoken aloud in their unique voice with per-agent music, pretext, and reverb — loaded automatically from `~/.agentvibes/bmad-voice-map.json`.
46
72
 
@@ -220,6 +246,11 @@ Replace the default background tracks with your own audio files for complete son
220
246
  - Automatic format detection
221
247
  - Duration warnings for non-optimal lengths
222
248
 
249
+ **Custom Track Naming Rules:**
250
+ - Use `snake_case` filenames only — e.g. `my_focus_music.mp3` ✅
251
+ - No spaces or uppercase letters in filenames — e.g. `My Focus Music.mp3` ❌
252
+ - Misnamed files will not appear in the music picker and will be skipped by the audio engine
253
+
223
254
  **Perfect for:**
224
255
  - 🎸 **Team Audio Branding** - Company theme music
225
256
  - 🎮 **Gaming Sessions** - Epic background tracks