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.
- package/.agentvibes/config.json +15 -1
- package/.agentvibes/install-manifest.json +342 -0
- package/.claude/audio/ui/CREDITS.txt +16 -0
- package/.claude/audio/ui/bling-success.wav +0 -0
- package/.claude/commands/agent-vibes/receiver.md +64 -0
- 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 +5 -4
- package/.claude/config/audio-effects.cfg.sample +52 -52
- package/.claude/config/background-music-enabled.txt +1 -1
- package/.claude/config/background-music-position.txt +27 -0
- package/.claude/config/background-music-volume.txt +1 -0
- package/.claude/config/background-music.cfg +1 -0
- package/.claude/config/background-music.txt +1 -0
- package/.claude/config/language.txt +1 -0
- package/.claude/config/personality.txt +1 -0
- package/.claude/config/reverb-level.txt +1 -0
- package/.claude/config/tts-speech-rate.txt +1 -0
- package/.claude/config/tts-verbosity.txt +1 -0
- package/.claude/docs/TERMUX_SETUP.md +408 -408
- package/.claude/github-star-reminder.txt +1 -1
- package/.claude/hooks/audio-cache-utils.sh +0 -0
- package/.claude/hooks/audio-processor.sh +57 -18
- package/.claude/hooks/background-music-manager.sh +0 -0
- package/.claude/hooks/bmad-party-speak.sh +0 -0
- package/.claude/hooks/bmad-speak-enhanced.sh +0 -0
- package/.claude/hooks/bmad-speak.sh +0 -0
- package/.claude/hooks/bmad-tts-injector.sh +0 -0
- package/.claude/hooks/bmad-voice-manager.sh +0 -0
- package/.claude/hooks/clawdbot-receiver-SECURE.sh +0 -0
- package/.claude/hooks/clawdbot-receiver.sh +0 -0
- package/.claude/hooks/clean-audio-cache.sh +0 -0
- package/.claude/hooks/cleanup-cache.sh +0 -0
- package/.claude/hooks/configure-rdp-mode.sh +0 -0
- package/.claude/hooks/download-extra-voices.sh +0 -0
- package/.claude/hooks/effects-manager.sh +0 -0
- package/.claude/hooks/github-star-reminder.sh +0 -0
- package/.claude/hooks/kokoro-installer.sh +117 -0
- package/.claude/hooks/kokoro-server.py +219 -0
- package/.claude/hooks/kokoro-tts.py +141 -0
- package/.claude/hooks/language-manager.sh +0 -0
- package/.claude/hooks/learn-manager.sh +0 -0
- package/.claude/hooks/macos-voice-manager.sh +0 -0
- package/.claude/hooks/migrate-background-music.sh +0 -0
- package/.claude/hooks/migrate-to-agentvibes.sh +0 -0
- package/.claude/hooks/optimize-background-music.sh +0 -0
- package/.claude/hooks/path-resolver.sh +0 -0
- package/.claude/hooks/personality-manager.sh +0 -0
- package/.claude/hooks/piper-download-voices.sh +39 -16
- package/.claude/hooks/piper-installer.sh +11 -6
- package/.claude/hooks/piper-multispeaker-registry.sh +0 -0
- package/.claude/hooks/piper-voice-manager.sh +6 -6
- package/.claude/hooks/play-tts-agentvibes-receiver-for-voiceless-connections.sh +3 -1
- package/.claude/hooks/play-tts-agentvibes-receiver.sh +1 -0
- package/.claude/hooks/play-tts-elevenlabs.sh +360 -0
- package/.claude/hooks/play-tts-enhanced.sh +0 -0
- package/.claude/hooks/play-tts-kokoro.sh +127 -0
- package/.claude/hooks/play-tts-macos.sh +0 -0
- package/.claude/hooks/play-tts-piper.sh +0 -0
- package/.claude/hooks/play-tts-soprano.sh +0 -0
- package/.claude/hooks/play-tts-ssh-remote.sh +9 -2
- package/.claude/hooks/play-tts-termux-ssh.sh +3 -1
- package/.claude/hooks/play-tts-windows-receiver.sh +0 -0
- package/.claude/hooks/play-tts.sh +78 -2
- package/.claude/hooks/prepare-release.sh +0 -0
- package/.claude/hooks/provider-commands.sh +71 -22
- package/.claude/hooks/provider-manager.sh +15 -7
- package/.claude/hooks/replay-target-audio.sh +0 -0
- package/.claude/hooks/requirements.txt +6 -6
- package/.claude/hooks/sentiment-manager.sh +0 -0
- package/.claude/hooks/session-start-tts.sh +0 -0
- package/.claude/hooks/soprano-gradio-synth.py +139 -139
- package/.claude/hooks/speed-manager.sh +0 -0
- package/.claude/hooks/stop-tts.sh +0 -0
- package/.claude/hooks/termux-installer.sh +0 -0
- package/.claude/hooks/translate-manager.sh +0 -0
- package/.claude/hooks/translator.py +237 -237
- package/.claude/hooks/tts-queue-worker.sh +0 -0
- package/.claude/hooks/tts-queue.sh +0 -0
- package/.claude/hooks/verbosity-manager.sh +0 -0
- package/.claude/hooks/voice-manager.sh +0 -0
- package/.claude/hooks-windows/audio-cache-utils.ps1.user.bak +119 -0
- package/.claude/hooks-windows/kokoro-server.py +219 -0
- package/.claude/hooks-windows/kokoro-tts.py +107 -0
- package/.claude/hooks-windows/play-tts-kokoro.ps1 +191 -0
- package/.claude/hooks-windows/play-tts.ps1 +29 -1
- package/.claude/hooks-windows/soprano-gradio-synth.py +153 -153
- package/.claude/hooks-windows/soprano-gradio-synth.py.user.bak +153 -0
- package/.claude/piper-voices-dir.txt +1 -0
- package/.claude/verbosity.txt +1 -1
- package/.clawdbot/README.md +105 -105
- package/.mcp.json +6 -19
- package/README.md +12 -86
- package/RELEASE_NOTES.md +60 -0
- package/WINDOWS-SETUP.md +208 -208
- package/bin/agent-vibes +39 -39
- package/bin/agentvibes-voice-browser.js +0 -0
- package/bin/agentvibes.js +0 -0
- package/bin/mcp-server.js +121 -121
- package/bin/mcp-server.sh +0 -0
- 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/WINDOWS_SETUP.md +0 -0
- 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 +0 -0
- package/mcp-server/server.py +1807 -1807
- package/mcp-server/test_server.py +0 -0
- package/package.json +4 -3
- package/src/cli/list-personalities.js +110 -110
- package/src/cli/list-voices.js +114 -114
- package/src/commands/bmad-voices.js +394 -394
- package/src/commands/install-mcp.js +3 -3
- package/src/console/app.js +25 -9
- package/src/console/audio-env.js +85 -1
- package/src/console/brand-colors.js +13 -13
- package/src/console/constants/personalities.js +44 -44
- package/src/console/navigation.js +4 -0
- package/src/console/tabs/agents-tab.js +12 -6
- package/src/console/tabs/help-tab.js +314 -314
- package/src/console/tabs/music-tab.js +7 -25
- package/src/console/tabs/readme-tab.js +272 -272
- package/src/console/tabs/setup-tab.js +1704 -120
- package/src/console/tabs/voices-tab.js +72 -88
- package/src/console/widgets/destroy-list.js +25 -25
- package/src/console/widgets/format-utils.js +14 -2
- package/src/console/widgets/help-bar.js +55 -0
- package/src/console/widgets/notice.js +55 -55
- package/src/console/widgets/reverb-picker.js +429 -41
- package/src/console/widgets/track-picker.js +60 -51
- package/src/i18n/de.js +2 -1
- package/src/i18n/en.js +1 -0
- package/src/i18n/es.js +2 -1
- package/src/i18n/fr.js +2 -1
- package/src/i18n/hi.js +2 -1
- package/src/i18n/ja.js +2 -1
- package/src/i18n/ko.js +2 -1
- package/src/i18n/pt.js +2 -1
- package/src/i18n/strings.js +9 -9
- package/src/i18n/zh-CN.js +2 -1
- package/src/installer/language-screen.js +31 -31
- package/src/installer/music-file-input.js +304 -304
- package/src/installer.js +95 -7
- package/src/services/config-service.js +264 -264
- package/src/services/language-service.js +47 -47
- package/src/services/provider-service.js +35 -0
- package/src/services/provider-voice-catalog.js +126 -0
- package/src/services/tts-engine-service.js +51 -2
- package/src/utils/audio-duration-validator.js +53 -10
- 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 -136
- package/src/utils/provider-validator.js +70 -0
- package/src/utils/secure-music-storage.js +412 -412
- package/templates/agentvibes-receiver.sh +235 -231
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
|
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 ⭐⭐⭐⭐
|
|
125
|
+
echo "│ macOS Say Built-in, free ⭐⭐⭐⭐ [RECOMMENDED] │"
|
|
101
126
|
fi
|
|
102
|
-
echo "│ Cost: Free (built-in)
|
|
103
|
-
echo "│
|
|
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
|
|
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
|
|
115
|
-
echo "│
|
|
116
|
-
echo "│
|
|
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
|
-
|
|
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 "│
|
|
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
|
-
#
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
|
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
|