agentvibes 5.10.1 → 5.11.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 (160) hide show
  1. package/.agentvibes/config.json +15 -1
  2. package/.agentvibes/install-manifest.json +342 -0
  3. package/.claude/audio/ui/CREDITS.txt +16 -0
  4. package/.claude/audio/ui/bling-success.wav +0 -0
  5. package/.claude/commands/agent-vibes/receiver.md +64 -0
  6. package/.claude/commands/agent-vibes-bmad-voices.md +117 -117
  7. package/.claude/commands/agent-vibes-rdp.md +24 -24
  8. package/.claude/config/audio-effects.cfg +5 -4
  9. package/.claude/config/audio-effects.cfg.sample +52 -52
  10. package/.claude/config/background-music-enabled.txt +1 -1
  11. package/.claude/config/background-music-position.txt +27 -0
  12. package/.claude/config/background-music-volume.txt +1 -0
  13. package/.claude/config/background-music.cfg +1 -0
  14. package/.claude/config/background-music.txt +1 -0
  15. package/.claude/config/language.txt +1 -0
  16. package/.claude/config/personality.txt +1 -0
  17. package/.claude/config/reverb-level.txt +1 -0
  18. package/.claude/config/tts-speech-rate.txt +1 -0
  19. package/.claude/config/tts-verbosity.txt +1 -0
  20. package/.claude/docs/TERMUX_SETUP.md +408 -408
  21. package/.claude/github-star-reminder.txt +1 -1
  22. package/.claude/hooks/audio-cache-utils.sh +0 -0
  23. package/.claude/hooks/audio-processor.sh +57 -18
  24. package/.claude/hooks/background-music-manager.sh +0 -0
  25. package/.claude/hooks/bmad-party-speak.sh +0 -0
  26. package/.claude/hooks/bmad-speak-enhanced.sh +0 -0
  27. package/.claude/hooks/bmad-speak.sh +0 -0
  28. package/.claude/hooks/bmad-tts-injector.sh +0 -0
  29. package/.claude/hooks/bmad-voice-manager.sh +0 -0
  30. package/.claude/hooks/clawdbot-receiver-SECURE.sh +0 -0
  31. package/.claude/hooks/clawdbot-receiver.sh +0 -0
  32. package/.claude/hooks/clean-audio-cache.sh +0 -0
  33. package/.claude/hooks/cleanup-cache.sh +0 -0
  34. package/.claude/hooks/configure-rdp-mode.sh +0 -0
  35. package/.claude/hooks/download-extra-voices.sh +0 -0
  36. package/.claude/hooks/effects-manager.sh +0 -0
  37. package/.claude/hooks/github-star-reminder.sh +0 -0
  38. package/.claude/hooks/kokoro-installer.sh +117 -0
  39. package/.claude/hooks/kokoro-server.py +219 -0
  40. package/.claude/hooks/kokoro-tts.py +141 -0
  41. package/.claude/hooks/language-manager.sh +0 -0
  42. package/.claude/hooks/learn-manager.sh +0 -0
  43. package/.claude/hooks/macos-voice-manager.sh +0 -0
  44. package/.claude/hooks/migrate-background-music.sh +0 -0
  45. package/.claude/hooks/migrate-to-agentvibes.sh +0 -0
  46. package/.claude/hooks/optimize-background-music.sh +0 -0
  47. package/.claude/hooks/path-resolver.sh +0 -0
  48. package/.claude/hooks/personality-manager.sh +0 -0
  49. package/.claude/hooks/piper-download-voices.sh +39 -16
  50. package/.claude/hooks/piper-installer.sh +11 -6
  51. package/.claude/hooks/piper-multispeaker-registry.sh +0 -0
  52. package/.claude/hooks/piper-voice-manager.sh +6 -6
  53. package/.claude/hooks/play-tts-agentvibes-receiver-for-voiceless-connections.sh +3 -1
  54. package/.claude/hooks/play-tts-agentvibes-receiver.sh +1 -0
  55. package/.claude/hooks/play-tts-elevenlabs.sh +360 -0
  56. package/.claude/hooks/play-tts-enhanced.sh +0 -0
  57. package/.claude/hooks/play-tts-kokoro.sh +127 -0
  58. package/.claude/hooks/play-tts-macos.sh +0 -0
  59. package/.claude/hooks/play-tts-piper.sh +0 -0
  60. package/.claude/hooks/play-tts-soprano.sh +0 -0
  61. package/.claude/hooks/play-tts-ssh-remote.sh +9 -2
  62. package/.claude/hooks/play-tts-termux-ssh.sh +3 -1
  63. package/.claude/hooks/play-tts-windows-receiver.sh +0 -0
  64. package/.claude/hooks/play-tts.sh +78 -2
  65. package/.claude/hooks/prepare-release.sh +0 -0
  66. package/.claude/hooks/provider-commands.sh +71 -22
  67. package/.claude/hooks/provider-manager.sh +15 -7
  68. package/.claude/hooks/replay-target-audio.sh +0 -0
  69. package/.claude/hooks/requirements.txt +6 -6
  70. package/.claude/hooks/sentiment-manager.sh +0 -0
  71. package/.claude/hooks/session-start-tts.sh +0 -0
  72. package/.claude/hooks/soprano-gradio-synth.py +139 -139
  73. package/.claude/hooks/speed-manager.sh +0 -0
  74. package/.claude/hooks/stop-tts.sh +0 -0
  75. package/.claude/hooks/termux-installer.sh +0 -0
  76. package/.claude/hooks/translate-manager.sh +0 -0
  77. package/.claude/hooks/translator.py +237 -237
  78. package/.claude/hooks/tts-queue-worker.sh +0 -0
  79. package/.claude/hooks/tts-queue.sh +0 -0
  80. package/.claude/hooks/verbosity-manager.sh +0 -0
  81. package/.claude/hooks/voice-manager.sh +0 -0
  82. package/.claude/hooks-windows/audio-cache-utils.ps1.user.bak +119 -0
  83. package/.claude/hooks-windows/kokoro-server.py +219 -0
  84. package/.claude/hooks-windows/kokoro-tts.py +107 -0
  85. package/.claude/hooks-windows/play-tts-kokoro.ps1 +191 -0
  86. package/.claude/hooks-windows/play-tts.ps1 +29 -1
  87. package/.claude/hooks-windows/soprano-gradio-synth.py +153 -153
  88. package/.claude/hooks-windows/soprano-gradio-synth.py.user.bak +153 -0
  89. package/.claude/piper-voices-dir.txt +1 -0
  90. package/.claude/verbosity.txt +1 -1
  91. package/.clawdbot/README.md +105 -105
  92. package/.mcp.json +6 -19
  93. package/README.md +12 -86
  94. package/RELEASE_NOTES.md +60 -0
  95. package/WINDOWS-SETUP.md +208 -208
  96. package/bin/agent-vibes +39 -39
  97. package/bin/agentvibes-voice-browser.js +0 -0
  98. package/bin/agentvibes.js +0 -0
  99. package/bin/mcp-server.js +121 -121
  100. package/bin/mcp-server.sh +0 -0
  101. package/bin/test-bmad-pr +78 -78
  102. package/mcp-server/QUICK_START.md +203 -203
  103. package/mcp-server/README.md +345 -345
  104. package/mcp-server/WINDOWS_SETUP.md +0 -0
  105. package/mcp-server/examples/claude_desktop_config.json +11 -11
  106. package/mcp-server/examples/claude_desktop_config_piper.json +9 -9
  107. package/mcp-server/examples/custom_instructions.md +169 -169
  108. package/mcp-server/install-deps.js +0 -0
  109. package/mcp-server/server.py +1807 -1807
  110. package/mcp-server/test_server.py +0 -0
  111. package/package.json +4 -3
  112. package/src/cli/list-personalities.js +110 -110
  113. package/src/cli/list-voices.js +114 -114
  114. package/src/commands/bmad-voices.js +394 -394
  115. package/src/commands/install-mcp.js +3 -3
  116. package/src/console/app.js +25 -9
  117. package/src/console/audio-env.js +85 -1
  118. package/src/console/brand-colors.js +13 -13
  119. package/src/console/constants/personalities.js +44 -44
  120. package/src/console/navigation.js +4 -0
  121. package/src/console/tabs/agents-tab.js +12 -6
  122. package/src/console/tabs/help-tab.js +314 -314
  123. package/src/console/tabs/music-tab.js +7 -25
  124. package/src/console/tabs/readme-tab.js +272 -272
  125. package/src/console/tabs/setup-tab.js +1704 -120
  126. package/src/console/tabs/voices-tab.js +72 -88
  127. package/src/console/widgets/destroy-list.js +25 -25
  128. package/src/console/widgets/format-utils.js +14 -2
  129. package/src/console/widgets/help-bar.js +55 -0
  130. package/src/console/widgets/notice.js +55 -55
  131. package/src/console/widgets/reverb-picker.js +429 -41
  132. package/src/console/widgets/track-picker.js +60 -51
  133. package/src/i18n/de.js +2 -1
  134. package/src/i18n/en.js +1 -0
  135. package/src/i18n/es.js +2 -1
  136. package/src/i18n/fr.js +2 -1
  137. package/src/i18n/hi.js +2 -1
  138. package/src/i18n/ja.js +2 -1
  139. package/src/i18n/ko.js +2 -1
  140. package/src/i18n/pt.js +2 -1
  141. package/src/i18n/strings.js +9 -9
  142. package/src/i18n/zh-CN.js +2 -1
  143. package/src/installer/language-screen.js +31 -31
  144. package/src/installer/music-file-input.js +304 -304
  145. package/src/installer.js +95 -7
  146. package/src/services/config-service.js +264 -264
  147. package/src/services/language-service.js +47 -47
  148. package/src/services/provider-service.js +35 -0
  149. package/src/services/provider-voice-catalog.js +126 -0
  150. package/src/services/tts-engine-service.js +51 -2
  151. package/src/utils/audio-duration-validator.js +53 -10
  152. package/src/utils/audio-format-validator.js +277 -277
  153. package/src/utils/dependency-checker.js +469 -469
  154. package/src/utils/file-ownership-verifier.js +358 -358
  155. package/src/utils/music-file-validator.js +285 -285
  156. package/src/utils/preview-list-prompt.js +144 -136
  157. package/src/utils/provider-validator.js +70 -0
  158. package/src/utils/secure-music-storage.js +412 -412
  159. package/templates/agentvibes-receiver.sh +235 -231
  160. package/templates/audio/welcome-music.mp3 +0 -0
@@ -223,6 +223,8 @@ done
223
223
 
224
224
  # LLM settings win over agent-name settings
225
225
  [[ -n "$LLM_REVERB" ]] && SOX_EFFECTS="$LLM_REVERB"
226
+ # Allow caller to override reverb for one-shot preview (e.g. effects picker Space preview)
227
+ [[ -n "${AGENTVIBES_REVERB_OVERRIDE:-}" ]] && SOX_EFFECTS="$AGENTVIBES_REVERB_OVERRIDE"
226
228
  [[ -n "$LLM_BG_FILE" ]] && BG_FILE="$LLM_BG_FILE"
227
229
  [[ -n "$LLM_BG_VOLUME" ]] && BG_VOLUME="$LLM_BG_VOLUME"
228
230
 
@@ -274,7 +276,7 @@ if [[ -z "$PROVIDER" ]]; then
274
276
  fi
275
277
  # Validate — only known TTS providers (not transport providers like ssh-remote)
276
278
  case "${PROVIDER:-}" in
277
- piper|soprano|macos|windows-sapi) ;;
279
+ piper|soprano|macos|windows-sapi|kokoro|elevenlabs) ;;
278
280
  *) PROVIDER="piper" ;;
279
281
  esac
280
282
 
@@ -334,7 +336,12 @@ echo "Sending to $SSH_HOST..." >&2
334
336
  # Build SSH args — use explicit key/port from config if available, else rely on ~/.ssh/config
335
337
  SSH_ARGS=()
336
338
  [[ -n "$SSH_KEY" && -f "$SSH_KEY" ]] && SSH_ARGS+=(-i "$SSH_KEY")
337
- [[ -n "$SSH_PORT" ]] && SSH_ARGS+=(-p "$SSH_PORT")
339
+ # Only pass -p for an explicit, non-default port. Port 22 is ssh's universal
340
+ # default, so forcing "-p 22" is at best a no-op and at worst overrides the
341
+ # real port of an ~/.ssh/config Host alias (e.g. laptop-win -> Port 45217),
342
+ # silently delivering the payload to the wrong port. Empty or "22" means
343
+ # "use ssh / ~/.ssh/config defaults" -- never force it.
344
+ if [[ -n "$SSH_PORT" && "$SSH_PORT" != "22" ]]; then SSH_ARGS+=(-p "$SSH_PORT"); fi
338
345
 
339
346
  # ForceCommand receiver: SSH_ORIGINAL_COMMAND passes the payload directly.
340
347
  # Run ssh inside the backgrounded subshell so its exit code is reachable via $?
@@ -165,7 +165,9 @@ SAFE_TEXT="${TEXT//\'/\'\\\'\'}"
165
165
  # Build SSH args — use explicit key/port from config if available, else rely on ~/.ssh/config
166
166
  SSH_ARGS=(-o ConnectTimeout=5)
167
167
  [[ -n "$SSH_KEY" && -f "$SSH_KEY" ]] && SSH_ARGS+=(-i "$SSH_KEY")
168
- [[ -n "$SSH_PORT" ]] && SSH_ARGS+=(-p "$SSH_PORT")
168
+ # Never force "-p 22" (ssh default) -- it would override an ~/.ssh/config
169
+ # Host alias port (e.g. laptop-win -> 45217). Empty/22 => defer to ssh config.
170
+ if [[ -n "$SSH_PORT" && "$SSH_PORT" != "22" ]]; then SSH_ARGS+=(-p "$SSH_PORT"); fi
169
171
 
170
172
  # Send TTS command to Android device via SSH
171
173
  # Use termux-tts-speak for native Android TTS
File without changes
@@ -77,6 +77,15 @@ fi
77
77
 
78
78
  export PROJECT_ROOT # Export for child scripts
79
79
 
80
+ # Suppress audio when tests are running in a separate shell.
81
+ # AGENTVIBES_TEST_MODE only propagates to child processes of the test runner;
82
+ # external callers (e.g. Claude Code hooks) don't inherit it.
83
+ # scripts/run-tests.sh writes this marker file so all play-tts.sh invocations —
84
+ # regardless of which process spawns them — stay silent during test runs.
85
+ if [[ -f "${HOME}/.agentvibes-tests-running" ]]; then
86
+ exit 0
87
+ fi
88
+
80
89
  # Check if muted (persists across sessions)
81
90
  # Project settings always override global settings:
82
91
  # - .claude/agentvibes-unmuted = project explicitly unmuted (overrides global mute)
@@ -197,8 +206,8 @@ if [[ -n "$LLM_PROVIDER" ]]; then
197
206
  done < "$_cfg"
198
207
  fi
199
208
  done
200
- # Apply LLM voice (only if no explicit voice override)
201
- if [[ -n "$_LLM_VOICE" && -z "$VOICE_OVERRIDE" ]]; then
209
+ # Apply LLM voice (only if no explicit voice override, and not in effects-preview mode)
210
+ if [[ -n "$_LLM_VOICE" && -z "$VOICE_OVERRIDE" && -z "${AGENTVIBES_EFFECTS_PREVIEW:-}" ]]; then
202
211
  VOICE_OVERRIDE="$_LLM_VOICE"
203
212
  fi
204
213
  # Export LLM key for child scripts (process-local, not system-wide)
@@ -256,6 +265,17 @@ case "$ACTIVE_PROVIDER" in
256
265
  ;;
257
266
  esac
258
267
 
268
+ # AGENTVIBES_FORCE_PROVIDER: set by agentvibes-receiver.sh to honour the
269
+ # provider field in the incoming JSON payload (e.g. kokoro, elevenlabs).
270
+ # Only applies when the locally-configured provider is still the default "piper"
271
+ # so a receiver that has tts-provider.txt set keeps its own preference.
272
+ if [[ -n "${AGENTVIBES_FORCE_PROVIDER:-}" && "$ACTIVE_PROVIDER" == "piper" ]]; then
273
+ case "$AGENTVIBES_FORCE_PROVIDER" in
274
+ piper|soprano|macos|windows-sapi|kokoro|elevenlabs)
275
+ ACTIVE_PROVIDER="$AGENTVIBES_FORCE_PROVIDER" ;;
276
+ esac
277
+ fi
278
+
259
279
  # Per-LLM SSH override: if the current LLM has mode=remote in transport-config.json,
260
280
  # write a one-shot env override so play-tts-ssh-remote.sh uses that LLM's SSH config.
261
281
  _TRANSPORT_CFG="$HOME/.agentvibes/transport-config.json"
@@ -341,6 +361,43 @@ if [[ "${AGENTVIBES_VERBOSE:-0}" == "1" ]]; then
341
361
  echo "provider=${ACTIVE_PROVIDER}" >&2
342
362
  fi
343
363
 
364
+ # @function _play_windows_sapi
365
+ # @intent Speak via the built-in Windows SAPI engine from a POSIX shell
366
+ # @why On Windows git-bash the `sapi`/`windows-sapi` provider has no native bash
367
+ # player; delegate to the PowerShell SAPI script so the engine selector's
368
+ # `sapi` id no longer dead-ends in "Unknown provider".
369
+ # @param $1 text to speak
370
+ # @param $2 voice override (optional)
371
+ _play_windows_sapi() {
372
+ local text="$1"
373
+ local voice="${2:-}"
374
+
375
+ # Sibling hooks-windows/ holds the PowerShell players. Prefer the short name,
376
+ # fall back to the legacy windows-prefixed one.
377
+ local ps1="$SCRIPT_DIR/../hooks-windows/play-tts-sapi.ps1"
378
+ [[ -f "$ps1" ]] || ps1="$SCRIPT_DIR/../hooks-windows/play-tts-windows-sapi.ps1"
379
+ if [[ ! -f "$ps1" ]]; then
380
+ echo "❌ Windows SAPI player not found beside this hook" >&2
381
+ return 1
382
+ fi
383
+
384
+ local ps_exe=""
385
+ local c
386
+ for c in powershell.exe pwsh.exe powershell pwsh; do
387
+ if command -v "$c" >/dev/null 2>&1; then ps_exe="$c"; break; fi
388
+ done
389
+ if [[ -z "$ps_exe" ]]; then
390
+ echo "❌ Windows SAPI requires PowerShell (Windows only)" >&2
391
+ return 1
392
+ fi
393
+
394
+ # PowerShell needs a native Windows path, not a /c/... MSYS path.
395
+ local win_ps1="$ps1"
396
+ command -v cygpath >/dev/null 2>&1 && win_ps1="$(cygpath -w "$ps1")"
397
+
398
+ "$ps_exe" -NoProfile -ExecutionPolicy Bypass -File "$win_ps1" "$text" "$voice"
399
+ }
400
+
344
401
  # @function speak_text
345
402
  # @intent Route text to appropriate TTS provider
346
403
  # @why Reusable function for speaking, used by both single and learning modes
@@ -363,6 +420,15 @@ speak_text() {
363
420
  macos)
364
421
  bash "$SCRIPT_DIR/play-tts-macos.sh" "$text" "$voice"
365
422
  ;;
423
+ elevenlabs)
424
+ bash "$SCRIPT_DIR/play-tts-elevenlabs.sh" "$text" "$voice"
425
+ ;;
426
+ kokoro)
427
+ bash "$SCRIPT_DIR/play-tts-kokoro.sh" "$text" "$voice"
428
+ ;;
429
+ sapi|windows-sapi)
430
+ _play_windows_sapi "$text" "$voice"
431
+ ;;
366
432
  termux-ssh)
367
433
  bash "$SCRIPT_DIR/play-tts-termux-ssh.sh" "$text" "$voice"
368
434
  ;;
@@ -493,6 +559,16 @@ case "$ACTIVE_PROVIDER" in
493
559
  macos)
494
560
  exec bash "$SCRIPT_DIR/play-tts-macos.sh" "$TEXT" "$VOICE_OVERRIDE"
495
561
  ;;
562
+ elevenlabs)
563
+ exec bash "$SCRIPT_DIR/play-tts-elevenlabs.sh" "$TEXT" "$VOICE_OVERRIDE"
564
+ ;;
565
+ kokoro)
566
+ exec bash "$SCRIPT_DIR/play-tts-kokoro.sh" "$TEXT" "$VOICE_OVERRIDE"
567
+ ;;
568
+ sapi|windows-sapi)
569
+ _play_windows_sapi "$TEXT" "$VOICE_OVERRIDE"
570
+ exit $?
571
+ ;;
496
572
  termux-ssh)
497
573
  exec bash "$SCRIPT_DIR/play-tts-termux-ssh.sh" "$TEXT" "$VOICE_OVERRIDE"
498
574
  ;;
File without changes
@@ -63,13 +63,24 @@ is_language_supported() {
63
63
 
64
64
  case "$provider" in
65
65
  piper)
66
- # Piper only supports English natively
66
+ # Piper only supports English natively (multilingual models exist but aren't bundled)
67
67
  return 1
68
68
  ;;
69
69
  macos)
70
70
  # macOS has voices for 40+ languages built-in
71
71
  return 0
72
72
  ;;
73
+ elevenlabs)
74
+ # ElevenLabs eleven_multilingual_v2 supports 32+ languages
75
+ return 0
76
+ ;;
77
+ kokoro)
78
+ # Kokoro supports English, Japanese, Korean, French, German, Spanish, Portuguese, Hindi
79
+ case "$language" in
80
+ japanese|korean|french|german|spanish|portuguese|hindi) return 0 ;;
81
+ *) return 1 ;;
82
+ esac
83
+ ;;
73
84
  *)
74
85
  return 1
75
86
  ;;
@@ -88,48 +99,86 @@ provider_list() {
88
99
  is_macos=true
89
100
  fi
90
101
 
91
- echo "┌────────────────────────────────────────────────────────────┐"
92
- echo "│ Available TTS Providers │"
93
- echo "├────────────────────────────────────────────────────────────┤"
102
+ # Check installation status of optional providers
103
+ local kokoro_installed=false
104
+ python3 -c "import importlib.util,sys; sys.exit(0 if importlib.util.find_spec('kokoro') else 1)" 2>/dev/null && kokoro_installed=true
105
+
106
+ local elevenlabs_key_set=false
107
+ if [[ -n "${ELEVENLABS_API_KEY:-}" ]]; then
108
+ elevenlabs_key_set=true
109
+ elif [[ -f "${HOME}/.agentvibes/elevenlabs-key.txt" ]]; then
110
+ elevenlabs_key_set=true
111
+ fi
112
+
113
+ _active() { [[ "$current_provider" == "$1" ]]; }
114
+ _mark() { _active "$1" && echo "[ACTIVE]" || echo ""; }
115
+
116
+ echo "┌──────────────────────────────────────────────────────────────┐"
117
+ echo "│ Available TTS Providers │"
118
+ echo "├──────────────────────────────────────────────────────────────┤"
94
119
 
95
120
  # macOS Say (show first on macOS systems)
96
121
  if [[ "$is_macos" == true ]]; then
97
- if [[ "$current_provider" == "macos" ]]; then
98
- echo "│ ✓ macOS Say Built-in, free ⭐⭐⭐⭐ [ACTIVE] │"
122
+ if _active "macos"; then
123
+ echo "│ ✓ macOS Say Built-in, free ⭐⭐⭐⭐ [ACTIVE] │"
99
124
  else
100
- echo "│ macOS Say Built-in, free ⭐⭐⭐⭐ [RECOMMENDED] │"
125
+ echo "│ macOS Say Built-in, free ⭐⭐⭐⭐ [RECOMMENDED] │"
101
126
  fi
102
- echo "│ Cost: Free (built-in) │"
103
- echo "│ Platform: macOS only │"
104
- echo "│ Offline: Yes │"
105
- echo "│ │"
127
+ echo "│ Cost: Free (built-in) Platform: macOS Offline: Yes │"
128
+ echo "│ │"
106
129
  fi
107
130
 
108
131
  # Piper
109
- if [[ "$current_provider" == "piper" ]]; then
132
+ if _active "piper"; then
110
133
  echo "│ ✓ Piper TTS Free, offline ⭐⭐⭐⭐ [ACTIVE] │"
111
134
  else
112
- echo "│ Piper TTS Free, offline ⭐⭐⭐⭐ │"
135
+ echo "│ Piper TTS Free, offline ⭐⭐⭐⭐ │"
136
+ fi
137
+ echo "│ Cost: Free forever Platform: Linux/WSL Offline: Yes │"
138
+ echo "│ │"
139
+
140
+ # Kokoro
141
+ if _active "kokoro"; then
142
+ echo "│ ✓ Kokoro TTS Local neural ⭐⭐⭐⭐⭐ [ACTIVE] │"
143
+ else
144
+ if [[ "$kokoro_installed" == true ]]; then
145
+ echo "│ Kokoro TTS Local neural ⭐⭐⭐⭐⭐ [installed] │"
146
+ else
147
+ echo "│ Kokoro TTS Local neural ⭐⭐⭐⭐⭐ [not installed]│"
148
+ fi
149
+ fi
150
+ echo "│ Cost: Free 60+ voices 8 languages Offline: Yes │"
151
+ echo "│ Install: ${SCRIPT_DIR}/kokoro-installer.sh │"
152
+ echo "│ │"
153
+
154
+ # ElevenLabs
155
+ if _active "elevenlabs"; then
156
+ echo "│ ✓ ElevenLabs Premium cloud ⭐⭐⭐⭐⭐ [ACTIVE] │"
157
+ else
158
+ if [[ "$elevenlabs_key_set" == true ]]; then
159
+ echo "│ ElevenLabs Premium cloud ⭐⭐⭐⭐⭐ [key set] │"
160
+ else
161
+ echo "│ ElevenLabs Premium cloud ⭐⭐⭐⭐⭐ [needs key] │"
162
+ fi
113
163
  fi
114
- echo "│ Cost: Free forever │"
115
- echo "│ Platform: WSL, Linux only │"
116
- echo "│ Offline: Yes │"
164
+ echo "│ Cost: Free tier available 32+ languages Offline: No │"
165
+ echo "│ Key: export ELEVENLABS_API_KEY=your_key │"
166
+ echo "│ │"
117
167
 
118
168
  # macOS Say (show at end for non-macOS systems)
119
169
  if [[ "$is_macos" != true ]]; then
120
- echo "│ │"
121
- if [[ "$current_provider" == "macos" ]]; then
170
+ if _active "macos"; then
122
171
  echo "│ ✓ macOS Say Built-in, free ⭐⭐⭐⭐ [ACTIVE] │"
123
172
  else
124
173
  echo "│ macOS Say Built-in, free ⭐⭐⭐⭐ (macOS only) │"
125
174
  fi
126
- echo "│ Cost: Free (built-in) │"
127
- echo "│ Platform: macOS only │"
128
- echo "│ Offline: Yes │"
175
+ echo "│ Cost: Free (built-in) Platform: macOS only │"
176
+ echo "│ │"
129
177
  fi
130
178
 
131
- echo "└────────────────────────────────────────────────────────────┘"
179
+ echo "└──────────────────────────────────────────────────────────────┘"
132
180
  echo ""
181
+ echo "Switch: /agent-vibes:provider switch <name>"
133
182
  echo "Learn more: agentvibes.org/providers"
134
183
  }
135
184
 
@@ -191,12 +191,18 @@ migrate_voice_to_provider() {
191
191
  local piper_default="en_US-lessac-medium"
192
192
  local macos_default="Samantha"
193
193
  local soprano_default="soprano-default" # Single voice — no selection needed
194
+ local elevenlabs_default="Rachel"
195
+ local kokoro_default="af_heart"
194
196
 
195
- # Soprano has a single voice, so migration is straightforward
196
- if [[ "$target_provider" == "soprano" ]]; then
197
- echo "$soprano_default"
198
- return 0
199
- fi
197
+ # Single-voice providers: migration is straightforward
198
+ case "$target_provider" in
199
+ soprano)
200
+ echo "$soprano_default"; return 0 ;;
201
+ elevenlabs)
202
+ echo "$elevenlabs_default"; return 0 ;;
203
+ kokoro)
204
+ echo "$kokoro_default"; return 0 ;;
205
+ esac
200
206
 
201
207
  # If no current voice, return default for target provider
202
208
  if [[ -z "$current_voice" ]]; then
@@ -208,8 +214,8 @@ migrate_voice_to_provider() {
208
214
  return 0
209
215
  fi
210
216
 
211
- # If migrating FROM Soprano, return default for target provider
212
- if [[ "$current_voice" == "soprano-default" ]]; then
217
+ # If migrating FROM a single-voice provider, return default for target provider
218
+ if [[ "$current_voice" == "soprano-default" || "$current_voice" == "Rachel" || "$current_voice" =~ ^[a-z]{2}_[a-z0-9_]+$ ]]; then
213
219
  case "$target_provider" in
214
220
  piper) echo "$piper_default" ;;
215
221
  macos) echo "$macos_default" ;;
@@ -265,6 +271,8 @@ migrate_voice_to_provider() {
265
271
  case "$target_provider" in
266
272
  piper) echo "$piper_default" ;;
267
273
  macos) echo "$macos_default" ;;
274
+ elevenlabs) echo "$elevenlabs_default" ;;
275
+ kokoro) echo "$kokoro_default" ;;
268
276
  *) echo "$piper_default" ;;
269
277
  esac
270
278
  }
File without changes
@@ -1,6 +1,6 @@
1
- # AgentVibes TTS Hooks Requirements
2
- # Install with: pip install -r requirements.txt
3
-
4
- # Translation support for multi-language TTS and learning mode
5
- deep-translator>=1.11.4
6
- langdetect>=1.0.9
1
+ # AgentVibes TTS Hooks Requirements
2
+ # Install with: pip install -r requirements.txt
3
+
4
+ # Translation support for multi-language TTS and learning mode
5
+ deep-translator>=1.11.4
6
+ langdetect>=1.0.9
File without changes
File without changes
@@ -1,139 +1,139 @@
1
- #!/usr/bin/env python3
2
- #
3
- # File: .claude/hooks/soprano-gradio-synth.py
4
- #
5
- # AgentVibes - Finally, your AI Agents can Talk Back!
6
- # Website: https://agentvibes.org
7
- # Repository: https://github.com/paulpreibisch/AgentVibes
8
- #
9
- # Co-created by Paul Preibisch with Claude AI
10
- # Copyright (c) 2025 Paul Preibisch
11
- #
12
- # Licensed under the Apache License, Version 2.0 (the "License");
13
- # you may not use this file except in compliance with the License.
14
- # You may obtain a copy of the License at
15
- #
16
- # http://www.apache.org/licenses/LICENSE-2.0
17
- #
18
- # Unless required by applicable law or agreed to in writing, software
19
- # distributed under the License is distributed on an "AS IS" BASIS,
20
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21
- # See the License for the specific language governing permissions and
22
- # limitations under the License.
23
- #
24
- # ---
25
- #
26
- # Soprano Gradio WebUI synthesizer helper.
27
- # Calls the Soprano WebUI's Gradio API and saves the result as a WAV file.
28
- # Uses only Python stdlib (json, sys, urllib) — no extra dependencies.
29
- #
30
- # Usage: python3 soprano-gradio-synth.py "text to speak" output.wav [port]
31
- #
32
- """
33
- Soprano Gradio WebUI synthesizer helper for AgentVibes.
34
-
35
- Calls the Soprano WebUI's Gradio API via the Server-Sent Events (SSE) protocol
36
- and downloads the generated audio as a WAV file.
37
-
38
- Flow:
39
- 1. Submit generation request → get event_id
40
- 2. Poll SSE stream for audio file URL
41
- 3. Download WAV file to output path
42
-
43
- See: https://github.com/ekwek1/soprano
44
- """
45
- import json
46
- import sys
47
- import urllib.request
48
- import urllib.error
49
-
50
-
51
- def synth(text: str, output_path: str, port: int = 7860) -> None:
52
- base = f"http://127.0.0.1:{port}"
53
-
54
- # Step 1: Submit generation request
55
- # Args: text, temperature, top_p, repetition_penalty, chunk_size, streaming
56
- payload = json.dumps({
57
- "data": [text, 0.0, 0.95, 1.2, 1, False]
58
- }).encode()
59
-
60
- event_id = submit_request(base, payload)
61
-
62
- # Step 2: Poll SSE stream for audio file URL
63
- audio_url = poll_for_result(base, event_id)
64
-
65
- # Step 3: Download the audio file
66
- download_file(audio_url, output_path)
67
-
68
-
69
- def submit_request(base: str, payload: bytes) -> str:
70
- """Submit generation request to Gradio API, return event_id."""
71
- for api_base in ["/gradio_api/call", "/call"]:
72
- url = f"{base}{api_base}/generate_speech"
73
- req = urllib.request.Request(
74
- url,
75
- data=payload,
76
- headers={"Content-Type": "application/json"},
77
- )
78
- try:
79
- with urllib.request.urlopen(req, timeout=30) as resp:
80
- return json.loads(resp.read())["event_id"]
81
- except urllib.error.URLError:
82
- continue
83
-
84
- raise RuntimeError("Could not reach Soprano WebUI API")
85
-
86
-
87
- def poll_for_result(base: str, event_id: str) -> str:
88
- """Poll SSE endpoint until audio file URL is returned."""
89
- for api_base in ["/gradio_api/call", "/call"]:
90
- url = f"{base}{api_base}/generate_speech/{event_id}"
91
- req = urllib.request.Request(url)
92
- try:
93
- with urllib.request.urlopen(req, timeout=120) as resp:
94
- for raw_line in resp:
95
- line = raw_line.decode("utf-8", errors="replace").strip()
96
- if not line.startswith("data: "):
97
- continue
98
- try:
99
- parsed = json.loads(line[6:])
100
- except json.JSONDecodeError:
101
- continue
102
- # Response format: [{"path": "...", "url": "...", ...}, "status string"]
103
- if isinstance(parsed, list) and len(parsed) >= 1:
104
- audio = parsed[0]
105
- if isinstance(audio, dict) and "url" in audio:
106
- return audio["url"]
107
- break
108
- except urllib.error.URLError:
109
- continue
110
-
111
- raise RuntimeError("No audio URL in Soprano response")
112
-
113
-
114
- def download_file(url: str, output_path: str) -> None:
115
- """Download audio file from Gradio file server."""
116
- req = urllib.request.Request(url)
117
- with urllib.request.urlopen(req, timeout=30) as resp:
118
- with open(output_path, "wb") as f:
119
- while True:
120
- chunk = resp.read(8192)
121
- if not chunk:
122
- break
123
- f.write(chunk)
124
-
125
-
126
- if __name__ == "__main__":
127
- if len(sys.argv) < 3:
128
- print(f"Usage: {sys.argv[0]} \"text\" output.wav [port]", file=sys.stderr)
129
- sys.exit(1)
130
-
131
- text = sys.argv[1]
132
- output = sys.argv[2]
133
- port = int(sys.argv[3]) if len(sys.argv) > 3 else 7860
134
-
135
- try:
136
- synth(text, output, port)
137
- except Exception as e:
138
- print(f"Error: {e}", file=sys.stderr)
139
- sys.exit(1)
1
+ #!/usr/bin/env python3
2
+ #
3
+ # File: .claude/hooks/soprano-gradio-synth.py
4
+ #
5
+ # AgentVibes - Finally, your AI Agents can Talk Back!
6
+ # Website: https://agentvibes.org
7
+ # Repository: https://github.com/paulpreibisch/AgentVibes
8
+ #
9
+ # Co-created by Paul Preibisch with Claude AI
10
+ # Copyright (c) 2025 Paul Preibisch
11
+ #
12
+ # Licensed under the Apache License, Version 2.0 (the "License");
13
+ # you may not use this file except in compliance with the License.
14
+ # You may obtain a copy of the License at
15
+ #
16
+ # http://www.apache.org/licenses/LICENSE-2.0
17
+ #
18
+ # Unless required by applicable law or agreed to in writing, software
19
+ # distributed under the License is distributed on an "AS IS" BASIS,
20
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21
+ # See the License for the specific language governing permissions and
22
+ # limitations under the License.
23
+ #
24
+ # ---
25
+ #
26
+ # Soprano Gradio WebUI synthesizer helper.
27
+ # Calls the Soprano WebUI's Gradio API and saves the result as a WAV file.
28
+ # Uses only Python stdlib (json, sys, urllib) — no extra dependencies.
29
+ #
30
+ # Usage: python3 soprano-gradio-synth.py "text to speak" output.wav [port]
31
+ #
32
+ """
33
+ Soprano Gradio WebUI synthesizer helper for AgentVibes.
34
+
35
+ Calls the Soprano WebUI's Gradio API via the Server-Sent Events (SSE) protocol
36
+ and downloads the generated audio as a WAV file.
37
+
38
+ Flow:
39
+ 1. Submit generation request → get event_id
40
+ 2. Poll SSE stream for audio file URL
41
+ 3. Download WAV file to output path
42
+
43
+ See: https://github.com/ekwek1/soprano
44
+ """
45
+ import json
46
+ import sys
47
+ import urllib.request
48
+ import urllib.error
49
+
50
+
51
+ def synth(text: str, output_path: str, port: int = 7860) -> None:
52
+ base = f"http://127.0.0.1:{port}"
53
+
54
+ # Step 1: Submit generation request
55
+ # Args: text, temperature, top_p, repetition_penalty, chunk_size, streaming
56
+ payload = json.dumps({
57
+ "data": [text, 0.0, 0.95, 1.2, 1, False]
58
+ }).encode()
59
+
60
+ event_id = submit_request(base, payload)
61
+
62
+ # Step 2: Poll SSE stream for audio file URL
63
+ audio_url = poll_for_result(base, event_id)
64
+
65
+ # Step 3: Download the audio file
66
+ download_file(audio_url, output_path)
67
+
68
+
69
+ def submit_request(base: str, payload: bytes) -> str:
70
+ """Submit generation request to Gradio API, return event_id."""
71
+ for api_base in ["/gradio_api/call", "/call"]:
72
+ url = f"{base}{api_base}/generate_speech"
73
+ req = urllib.request.Request(
74
+ url,
75
+ data=payload,
76
+ headers={"Content-Type": "application/json"},
77
+ )
78
+ try:
79
+ with urllib.request.urlopen(req, timeout=30) as resp:
80
+ return json.loads(resp.read())["event_id"]
81
+ except urllib.error.URLError:
82
+ continue
83
+
84
+ raise RuntimeError("Could not reach Soprano WebUI API")
85
+
86
+
87
+ def poll_for_result(base: str, event_id: str) -> str:
88
+ """Poll SSE endpoint until audio file URL is returned."""
89
+ for api_base in ["/gradio_api/call", "/call"]:
90
+ url = f"{base}{api_base}/generate_speech/{event_id}"
91
+ req = urllib.request.Request(url)
92
+ try:
93
+ with urllib.request.urlopen(req, timeout=120) as resp:
94
+ for raw_line in resp:
95
+ line = raw_line.decode("utf-8", errors="replace").strip()
96
+ if not line.startswith("data: "):
97
+ continue
98
+ try:
99
+ parsed = json.loads(line[6:])
100
+ except json.JSONDecodeError:
101
+ continue
102
+ # Response format: [{"path": "...", "url": "...", ...}, "status string"]
103
+ if isinstance(parsed, list) and len(parsed) >= 1:
104
+ audio = parsed[0]
105
+ if isinstance(audio, dict) and "url" in audio:
106
+ return audio["url"]
107
+ break
108
+ except urllib.error.URLError:
109
+ continue
110
+
111
+ raise RuntimeError("No audio URL in Soprano response")
112
+
113
+
114
+ def download_file(url: str, output_path: str) -> None:
115
+ """Download audio file from Gradio file server."""
116
+ req = urllib.request.Request(url)
117
+ with urllib.request.urlopen(req, timeout=30) as resp:
118
+ with open(output_path, "wb") as f:
119
+ while True:
120
+ chunk = resp.read(8192)
121
+ if not chunk:
122
+ break
123
+ f.write(chunk)
124
+
125
+
126
+ if __name__ == "__main__":
127
+ if len(sys.argv) < 3:
128
+ print(f"Usage: {sys.argv[0]} \"text\" output.wav [port]", file=sys.stderr)
129
+ sys.exit(1)
130
+
131
+ text = sys.argv[1]
132
+ output = sys.argv[2]
133
+ port = int(sys.argv[3]) if len(sys.argv) > 3 else 7860
134
+
135
+ try:
136
+ synth(text, output, port)
137
+ except Exception as e:
138
+ print(f"Error: {e}", file=sys.stderr)
139
+ sys.exit(1)
File without changes
File without changes
File without changes
File without changes