agentvibes 5.12.0-alpha.0 → 5.13.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/activation-instructions +54 -54
- package/.claude/commands/agent-vibes/commands.json +0 -20
- package/.claude/commands/agent-vibes/unmute.md +6 -2
- 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 +6 -6
- package/.claude/config/audio-effects.cfg.bak-kokoro +7 -0
- 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/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/agentvibes-session-id.sh +69 -0
- package/.claude/hooks/bmad-party-speak.sh +20 -4
- package/.claude/hooks/bmad-speak.sh +60 -2
- package/.claude/hooks/bmad-tts-injector.sh +20 -1
- package/.claude/hooks/bmad-voice-manager.sh +25 -3
- package/.claude/hooks/clawdbot-receiver-SECURE.sh +21 -2
- package/.claude/hooks/clawdbot-receiver.sh +19 -1
- package/.claude/hooks/elevenlabs-voices.sh +62 -0
- package/.claude/hooks/kokoro-installer.sh +20 -10
- package/.claude/hooks/language-manager.sh +10 -3
- package/.claude/hooks/party-set-room.sh +71 -0
- package/.claude/hooks/party-stage-roster.py +328 -0
- package/.claude/hooks/personality-manager.sh +19 -2
- package/.claude/hooks/piper-voice-manager.sh +3 -2
- package/.claude/hooks/play-tts-agentvibes-receiver-for-voiceless-connections.sh +24 -5
- package/.claude/hooks/play-tts-elevenlabs.sh +38 -118
- package/.claude/hooks/play-tts-kokoro.sh +33 -6
- package/.claude/hooks/play-tts-soprano.sh +3 -2
- package/.claude/hooks/play-tts-ssh-remote.sh +72 -27
- package/.claude/hooks/play-tts-termux-ssh.sh +5 -4
- package/.claude/hooks/play-tts.sh +86 -69
- package/.claude/hooks/provider-catalog.json +352 -0
- package/.claude/hooks/provider-catalog.sh +161 -0
- package/.claude/hooks/provider-commands.sh +2 -1
- package/.claude/hooks/provider-manager.sh +47 -9
- package/.claude/hooks/python-resolver.sh +117 -0
- package/.claude/hooks/session-id.sh +56 -0
- package/.claude/hooks/session-start-tts.sh +39 -0
- package/.claude/hooks/speed-manager.sh +1 -1
- package/.claude/hooks/translate-manager.sh +3 -2
- package/.claude/hooks/translator.py +1 -1
- package/.claude/hooks/voice-manager.sh +242 -10
- package/.claude/hooks-windows/audio-cache-utils.ps1.user.bak +119 -0
- package/.claude/hooks-windows/language-manager.ps1 +7 -1
- package/.claude/hooks-windows/personality-manager.ps1 +16 -1
- package/.claude/hooks-windows/play-tts-kokoro.ps1 +20 -4
- package/.claude/hooks-windows/play-tts.ps1 +32 -3
- package/.claude/hooks-windows/provider-catalog.ps1 +140 -0
- package/.claude/hooks-windows/provider-manager.ps1 +63 -8
- package/.claude/hooks-windows/soprano-gradio-synth.py.user.bak +153 -0
- package/.claude/hooks-windows/tts-watcher.ps1 +84 -8
- package/.claude/hooks-windows/voice-manager-windows.ps1 +49 -0
- package/.claude/piper-voices-dir.txt +1 -0
- package/.clawdbot/README.md +105 -105
- package/.mcp.json +15 -0
- package/LICENSE +190 -190
- package/README.md +20 -6
- package/RELEASE_NOTES.md +81 -0
- package/WINDOWS-SETUP.md +208 -208
- package/bin/agent-vibes +39 -39
- package/bin/mcp-server.js +121 -121
- package/bin/test-bmad-pr +78 -78
- package/mcp-server/QUICK_START.md +203 -203
- package/mcp-server/README.md +345 -345
- package/mcp-server/examples/claude_desktop_config.json +11 -11
- package/mcp-server/examples/claude_desktop_config_piper.json +9 -9
- package/mcp-server/examples/custom_instructions.md +169 -169
- package/mcp-server/pyproject.toml +52 -52
- package/mcp-server/requirements.txt +2 -2
- package/mcp-server/server.py +146 -49
- package/mcp-server/test_mcp_correctness.py +20 -2
- package/mcp-server/test_server.py +395 -395
- package/mcp-server/test_windows_script_parity.py +0 -2
- package/package.json +1 -1
- package/src/cli/list-personalities.js +110 -110
- package/src/cli/list-voices.js +115 -11
- package/src/commands/bmad-voices.js +394 -394
- package/src/console/app.js +1 -9
- package/src/console/bling.js +71 -0
- package/src/console/brand-colors.js +13 -13
- package/src/console/constants/personalities.js +44 -44
- package/src/console/footer-config.js +0 -4
- package/src/console/music-preview.js +79 -0
- package/src/console/navigation.js +0 -1
- package/src/console/tabs/agents-tab.js +14 -2
- package/src/console/tabs/help-tab.js +314 -314
- package/src/console/tabs/music-tab.js +81 -5
- package/src/console/tabs/placeholder-tab.js +0 -2
- package/src/console/tabs/readme-tab.js +272 -272
- package/src/console/tabs/settings-tab.js +207 -14
- package/src/console/tabs/setup-tab.js +39 -36
- package/src/console/tabs/voices-tab.js +102 -23
- package/src/console/widgets/destroy-list.js +25 -25
- package/src/console/widgets/notice.js +55 -55
- package/src/console/widgets/track-picker.js +82 -0
- package/src/installer/language-screen.js +31 -31
- package/src/installer/music-file-input.js +304 -304
- package/src/installer.js +124 -10
- package/src/services/language-service.js +47 -47
- package/src/services/navigation-service.js +1 -1
- package/src/services/provider-catalog.js +412 -0
- package/src/services/provider-voice-catalog.js +52 -73
- package/src/services/tts-engine-service.js +29 -0
- package/src/utils/audio-format-validator.js +277 -277
- package/src/utils/dependency-checker.js +469 -469
- package/src/utils/file-ownership-verifier.js +358 -358
- package/src/utils/music-file-validator.js +285 -285
- package/src/utils/preview-list-prompt.js +144 -144
- package/src/utils/provider-validator.js +62 -12
- package/src/utils/secure-music-storage.js +412 -412
- package/voice-assignments.json +8244 -8244
- package/.claude/commands/agent-vibes/language.md +0 -23
- package/.claude/commands/agent-vibes/learn.md +0 -67
- package/.claude/commands/agent-vibes/replay-target.md +0 -14
- package/.claude/commands/agent-vibes/target-voice.md +0 -26
- package/.claude/commands/agent-vibes/target.md +0 -30
- package/.claude/hooks/learn-manager.sh +0 -492
- package/.claude/hooks/replay-target-audio.sh +0 -95
- package/.claude/hooks-windows/learn-manager.ps1 +0 -241
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# File: .claude/hooks/python-resolver.sh
|
|
4
|
+
#
|
|
5
|
+
# AgentVibes - Text-to-Speech WITH personality for AI Assistants
|
|
6
|
+
# Website: https://agentvibes.org
|
|
7
|
+
# Repository: https://github.com/paulpreibisch/AgentVibes
|
|
8
|
+
#
|
|
9
|
+
# Licensed under the Apache License, Version 2.0
|
|
10
|
+
#
|
|
11
|
+
# @fileoverview Locate a WORKING Python 3 interpreter across platforms.
|
|
12
|
+
# @context Windows git-bash frequently has NO `python3`/`python` on PATH even when
|
|
13
|
+
# Python is installed (the on-PATH WindowsApps entries are Microsoft Store
|
|
14
|
+
# execution-alias stubs that do not actually run). The old `command -v
|
|
15
|
+
# python3` guard therefore made hooks falsely report "not installed".
|
|
16
|
+
# @architecture Sourced (not executed) by hooks; sets and exports $PYTHON_BIN.
|
|
17
|
+
# $PYTHON_BIN is empty when no usable interpreter exists. Callers should
|
|
18
|
+
# test `[[ -n "$PYTHON_BIN" ]]` instead of `command -v python3`.
|
|
19
|
+
# @related play-tts.sh, play-tts-kokoro.sh, kokoro-installer.sh, provider-commands.sh
|
|
20
|
+
#
|
|
21
|
+
# Usage:
|
|
22
|
+
# source "$SCRIPT_DIR/python-resolver.sh"
|
|
23
|
+
# [[ -n "$PYTHON_BIN" ]] || { echo "No Python 3 found" >&2; exit 1; }
|
|
24
|
+
# "$PYTHON_BIN" my-script.py ...
|
|
25
|
+
#
|
|
26
|
+
# Override: set AGENTVIBES_PYTHON=/path/to/python to force a specific interpreter.
|
|
27
|
+
|
|
28
|
+
# Fast path: resolve once per process. Several hooks source this file
|
|
29
|
+
# transitively (e.g. play-tts.sh -> provider-manager.sh), so if PYTHON_BIN is
|
|
30
|
+
# already exported and the file still exists, TRUST it without re-launching
|
|
31
|
+
# Python — a fresh interpreter startup per source adds ~100ms of latency per
|
|
32
|
+
# utterance for nothing. We only pay the exec-validation cost during genuine
|
|
33
|
+
# discovery below, where the Windows Store stub actually needs rejecting.
|
|
34
|
+
if [[ -n "${PYTHON_BIN:-}" && -f "$PYTHON_BIN" ]]; then
|
|
35
|
+
export PYTHON_BIN
|
|
36
|
+
else
|
|
37
|
+
_AV_PY_CACHE="${HOME}/.claude/config/python-bin.txt"
|
|
38
|
+
|
|
39
|
+
# A candidate is valid only if it runs AND is Python 3. Running it filters out
|
|
40
|
+
# the Windows Store alias stub (which exits non-zero when given args); the
|
|
41
|
+
# version check rejects a lone python2 whose py3 syntax would crash at runtime.
|
|
42
|
+
_av_python_valid() {
|
|
43
|
+
[[ -n "${1:-}" ]] || return 1
|
|
44
|
+
"$1" -c 'import sys; sys.exit(0 if sys.version_info[0] >= 3 else 1)' >/dev/null 2>&1
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
_av_resolve_python() {
|
|
48
|
+
local cand resolved
|
|
49
|
+
|
|
50
|
+
# 1. Explicit override wins.
|
|
51
|
+
if [[ -n "${AGENTVIBES_PYTHON:-}" ]] && _av_python_valid "$AGENTVIBES_PYTHON"; then
|
|
52
|
+
printf '%s' "$AGENTVIBES_PYTHON"; return 0
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
# 2. Previously-cached hit (strip a stray CR from a CRLF-edited cache file).
|
|
56
|
+
if [[ -f "$_AV_PY_CACHE" ]]; then
|
|
57
|
+
cand="$(head -1 "$_AV_PY_CACHE" 2>/dev/null | tr -d '\r' || true)"
|
|
58
|
+
if _av_python_valid "$cand"; then printf '%s' "$cand"; return 0; fi
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
# 3. On PATH — but VALIDATE each (rejects the WindowsApps store stub).
|
|
62
|
+
for cand in python3 python python3.13 python3.12 python3.11 python3.10; do
|
|
63
|
+
resolved="$(command -v "$cand" 2>/dev/null || true)"
|
|
64
|
+
if [[ -n "$resolved" ]] && _av_python_valid "$resolved"; then
|
|
65
|
+
printf '%s' "$resolved"; return 0
|
|
66
|
+
fi
|
|
67
|
+
done
|
|
68
|
+
|
|
69
|
+
# 4. Windows `py` launcher — ask it for the real interpreter path. Python on
|
|
70
|
+
# Windows emits CRLF even to a pipe, so strip the trailing CR or the exec
|
|
71
|
+
# of "…python.exe\r" fails.
|
|
72
|
+
if command -v py >/dev/null 2>&1; then
|
|
73
|
+
cand="$(py -3 -c 'import sys; print(sys.executable)' 2>/dev/null | tr -d '\r' || true)"
|
|
74
|
+
if _av_python_valid "$cand"; then printf '%s' "$cand"; return 0; fi
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
# 5. Known Windows install locations, newest version first. Built with
|
|
78
|
+
# nullglob into an array so paths containing spaces ("C:\Program Files",
|
|
79
|
+
# "C:\Users\John Smith\…") survive — an unquoted *variable* glob would
|
|
80
|
+
# word-split them, which is exactly the case this resolver exists to fix.
|
|
81
|
+
# This file is SOURCED, so nullglob is saved and restored to avoid leaking
|
|
82
|
+
# the option into the calling hook.
|
|
83
|
+
local _av_restore_glob; _av_restore_glob="$(shopt -p nullglob 2>/dev/null || true)"
|
|
84
|
+
shopt -s nullglob 2>/dev/null || true
|
|
85
|
+
local -a _av_pys=(
|
|
86
|
+
"${LOCALAPPDATA:-$HOME/AppData/Local}/Programs/Python"/Python3*/python.exe
|
|
87
|
+
"$HOME/AppData/Local/Programs/Python"/Python3*/python.exe
|
|
88
|
+
/c/Python3*/python.exe
|
|
89
|
+
"/c/Program Files/Python3"*/python.exe
|
|
90
|
+
"/c/Program Files (x86)/Python3"*/python.exe
|
|
91
|
+
)
|
|
92
|
+
eval "${_av_restore_glob:-shopt -u nullglob}" 2>/dev/null || true
|
|
93
|
+
# Version-sort descending (Python312 before Python39 — a plain sort -r is
|
|
94
|
+
# lexical and would rank "39" above "312"); NUL-delimited so spacey paths
|
|
95
|
+
# stay intact. `sort -zrV` is GNU coreutils, which git-bash ships (this glob
|
|
96
|
+
# only ever matches under git-bash anyway).
|
|
97
|
+
if [[ ${#_av_pys[@]} -gt 0 ]]; then
|
|
98
|
+
while IFS= read -r -d '' cand; do
|
|
99
|
+
_av_python_valid "$cand" && { printf '%s' "$cand"; return 0; }
|
|
100
|
+
done < <(printf '%s\0' "${_av_pys[@]}" | sort -zrV 2>/dev/null)
|
|
101
|
+
fi
|
|
102
|
+
|
|
103
|
+
return 1
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
PYTHON_BIN="$(_av_resolve_python || true)"
|
|
107
|
+
export PYTHON_BIN
|
|
108
|
+
|
|
109
|
+
# Cache a fresh discovery (best-effort; never fatal). Only rewrite when it
|
|
110
|
+
# actually changed, to avoid needless disk writes on every hook invocation.
|
|
111
|
+
if [[ -n "$PYTHON_BIN" ]]; then
|
|
112
|
+
if [[ ! -f "$_AV_PY_CACHE" ]] || [[ "$(head -1 "$_AV_PY_CACHE" 2>/dev/null | tr -d '\r' || true)" != "$PYTHON_BIN" ]]; then
|
|
113
|
+
mkdir -p "$(dirname "$_AV_PY_CACHE")" 2>/dev/null || true
|
|
114
|
+
printf '%s\n' "$PYTHON_BIN" > "$_AV_PY_CACHE" 2>/dev/null || true
|
|
115
|
+
fi
|
|
116
|
+
fi
|
|
117
|
+
fi
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# File: .claude/hooks/session-id.sh
|
|
4
|
+
#
|
|
5
|
+
# AgentVibes - Canonical routing session-id helper.
|
|
6
|
+
#
|
|
7
|
+
# The receiver MULTIPLEXES forwarded TTS from many sources (multiple local
|
|
8
|
+
# projects + remote machines) keyed by a per-message identity. That identity
|
|
9
|
+
# MUST be stable and correctly derived, or the receiver merges unrelated
|
|
10
|
+
# sessions into one.
|
|
11
|
+
#
|
|
12
|
+
# This helper is the SINGLE source of truth for that routing id so every
|
|
13
|
+
# forward path (e.g. play-tts-ssh-remote.sh SSH) derives it identically:
|
|
14
|
+
#
|
|
15
|
+
# session id = basename(CLAUDE_PROJECT_DIR) when CLAUDE_PROJECT_DIR is set
|
|
16
|
+
# = basename(<fallback dir>) otherwise (passed dir, else PWD)
|
|
17
|
+
#
|
|
18
|
+
# It is intentionally NOT basename(install root): for a global install
|
|
19
|
+
# (~/.claude/hooks) that basename collapses to the HOME dir name (e.g.
|
|
20
|
+
# "administrator"), so every project on the machine would report the same id.
|
|
21
|
+
#
|
|
22
|
+
# NOTE: This is a PLAIN SLUG for routing — do NOT confuse it with
|
|
23
|
+
# agentvibes-session-id.sh, which produces a human SPOKEN identity string.
|
|
24
|
+
#
|
|
25
|
+
# Usage:
|
|
26
|
+
# source "$(dirname "${BASH_SOURCE[0]}")/session-id.sh"
|
|
27
|
+
# SESSION_ID="$(av_session_id "$FALLBACK_PROJECT_DIR")"
|
|
28
|
+
#
|
|
29
|
+
# bash-3.2 safe (macOS default bash). Sourced by scripts already in strict mode;
|
|
30
|
+
# defines a function only and does not alter shell options.
|
|
31
|
+
#
|
|
32
|
+
|
|
33
|
+
# av_session_id [fallback_dir]
|
|
34
|
+
# Prints the canonical routing session id (plain slug) to stdout.
|
|
35
|
+
# Prefers CLAUDE_PROJECT_DIR (the real user project injected via
|
|
36
|
+
# --project-dir); else the passed fallback dir; else PWD. Callers MUST pass a
|
|
37
|
+
# real project dir as the fallback — never the package/install root.
|
|
38
|
+
av_session_id() {
|
|
39
|
+
local _dir="${CLAUDE_PROJECT_DIR:-}"
|
|
40
|
+
if [[ -z "$_dir" ]]; then
|
|
41
|
+
_dir="${1:-$PWD}"
|
|
42
|
+
fi
|
|
43
|
+
# Strip trailing slashes so basename of "/a/b/" is "b", not "".
|
|
44
|
+
while [[ "$_dir" == */ && "${#_dir}" -gt 1 ]]; do
|
|
45
|
+
_dir="${_dir%/}"
|
|
46
|
+
done
|
|
47
|
+
local _base
|
|
48
|
+
_base="$(basename "$_dir" 2>/dev/null || printf '%s' "$_dir")"
|
|
49
|
+
# Slugify: collapse anything outside [A-Za-z0-9._-] to '-' so the id is safe
|
|
50
|
+
# as a JSON string and a stable map key on the receiver.
|
|
51
|
+
_base="$(printf '%s' "$_base" | tr -c 'A-Za-z0-9._-' '-')"
|
|
52
|
+
# Trim leading/trailing dashes introduced by slugification.
|
|
53
|
+
_base="${_base#-}"; _base="${_base%-}"
|
|
54
|
+
[[ -z "$_base" ]] && _base="unknown"
|
|
55
|
+
printf '%s' "$_base"
|
|
56
|
+
}
|
|
@@ -38,6 +38,45 @@ else
|
|
|
38
38
|
_PROJECT_CLAUDE_DIR="$(dirname "$SCRIPT_DIR")"
|
|
39
39
|
fi
|
|
40
40
|
|
|
41
|
+
# ---------------------------------------------------------------------------
|
|
42
|
+
# OPT-IN INJECTION GATE (Issue: global-install cacophony).
|
|
43
|
+
#
|
|
44
|
+
# Previously this hook injected the ~250-token TTS protocol into EVERY session
|
|
45
|
+
# where AgentVibes was found on disk. A GLOBAL install therefore made every open
|
|
46
|
+
# Claude session start speaking at once ("a cacophony of agents"). It also spent
|
|
47
|
+
# tokens on idle sessions that never wanted audio. Injection is now OPT-IN.
|
|
48
|
+
#
|
|
49
|
+
# Two distinct markers, kept SEPARATE on purpose:
|
|
50
|
+
# * agentvibes-unmuted / agentvibes-muted — the user's runtime mute choice,
|
|
51
|
+
# also read by play-tts.sh, where a project `unmuted` OVERRIDES a global mute.
|
|
52
|
+
# * agentvibes-enabled — "this project opted into injection" (written by a
|
|
53
|
+
# project install). It does NOT override a global mute; it only turns the
|
|
54
|
+
# protocol injection on. This separation is why a project install can no
|
|
55
|
+
# longer silently defeat a user's `~/.agentvibes-muted` global kill-switch.
|
|
56
|
+
#
|
|
57
|
+
# Precedence (mirrors play-tts.sh so injection and audio agree):
|
|
58
|
+
# 1. project agentvibes-unmuted → inject (explicit per-project ON)
|
|
59
|
+
# 2. project agentvibes-muted → silent
|
|
60
|
+
# 3. global ~/.agentvibes-muted → silent (the global kill-switch)
|
|
61
|
+
# 4. project agentvibes-enabled, OR global opt-in
|
|
62
|
+
# (~/.claude/agentvibes-enabled or ~/.claude/agentvibes-unmuted) → inject
|
|
63
|
+
# 5. otherwise → silent (opt-in default; zero tokens)
|
|
64
|
+
if [[ -f "$_PROJECT_CLAUDE_DIR/agentvibes-unmuted" ]]; then
|
|
65
|
+
: # explicit per-project enable — wins over a global mute, as in play-tts.sh
|
|
66
|
+
elif [[ -f "$_PROJECT_CLAUDE_DIR/agentvibes-muted" ]]; then
|
|
67
|
+
exit 0
|
|
68
|
+
elif [[ -f "$HOME/.agentvibes-muted" ]]; then
|
|
69
|
+
exit 0 # global kill-switch, no per-project override
|
|
70
|
+
elif [[ -f "$_PROJECT_CLAUDE_DIR/agentvibes-enabled" \
|
|
71
|
+
|| -f "$HOME/.claude/agentvibes-enabled" \
|
|
72
|
+
|| -f "$HOME/.claude/agentvibes-unmuted" ]]; then
|
|
73
|
+
: # opted in (project install marker, or a deliberate global opt-in)
|
|
74
|
+
else
|
|
75
|
+
# Not enabled for this project → inject nothing (zero tokens).
|
|
76
|
+
# Enable for this project with: /agent-vibes:unmute
|
|
77
|
+
exit 0
|
|
78
|
+
fi
|
|
79
|
+
|
|
41
80
|
# Build --project-dir flag to embed in TTS commands.
|
|
42
81
|
# Sanitize: strip any embedded quotes that would break shell quoting.
|
|
43
82
|
PROJECT_DIR_FLAG=""
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
# @dependencies .claude/config/tts-speech-rate.txt, .claude/config/tts-target-speech-rate.txt
|
|
38
38
|
# @entrypoints Called by /agent-vibes:set-speed slash command
|
|
39
39
|
# @patterns Provider-agnostic speed config, legacy file migration, random tongue twisters for testing
|
|
40
|
-
# @related play-tts.sh, play-tts-piper.sh, play-tts-macos.sh
|
|
40
|
+
# @related play-tts.sh, play-tts-piper.sh, play-tts-macos.sh
|
|
41
41
|
#
|
|
42
42
|
|
|
43
43
|
# Get script directory
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
# @dependencies translator.py, language-manager.sh, .bmad/core/config.yaml (optional)
|
|
33
33
|
# @entrypoints Called by /agent-vibes:translate commands and play-tts.sh
|
|
34
34
|
# @patterns Config cascade - manual override > BMAD config > default (no translation)
|
|
35
|
-
# @related translator.py, play-tts.sh, language-manager.sh
|
|
35
|
+
# @related translator.py, play-tts.sh, language-manager.sh
|
|
36
36
|
|
|
37
37
|
# Only set strict mode when executed directly, not when sourced
|
|
38
38
|
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
|
@@ -40,6 +40,7 @@ if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
|
|
40
40
|
fi
|
|
41
41
|
|
|
42
42
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
43
|
+
source "$SCRIPT_DIR/python-resolver.sh"
|
|
43
44
|
|
|
44
45
|
# Use PWD for project dir when called from project context, fall back to script-relative
|
|
45
46
|
if [[ -d "$PWD/.claude" ]]; then
|
|
@@ -173,7 +174,7 @@ translate_text() {
|
|
|
173
174
|
|
|
174
175
|
# Call translator.py
|
|
175
176
|
local translated
|
|
176
|
-
translated=$(
|
|
177
|
+
translated=$("$PYTHON_BIN" "$SCRIPT_DIR/translator.py" "$text" "$target" 2>/dev/null) || translated="$text"
|
|
177
178
|
|
|
178
179
|
echo "$translated"
|
|
179
180
|
}
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
# @dependencies deep-translator, langdetect (pip install deep-translator langdetect)
|
|
33
33
|
# @entrypoints CLI: python3 translator.py <text> <target_lang>, Library: from translator import translate
|
|
34
34
|
# @patterns Command pattern - supports translate, detect, and batch operations
|
|
35
|
-
# @related play-tts.sh,
|
|
35
|
+
# @related play-tts.sh, language-manager.sh
|
|
36
36
|
#
|
|
37
37
|
|
|
38
38
|
"""
|
|
@@ -43,6 +43,18 @@ to_lower() {
|
|
|
43
43
|
echo "$1" | tr '[:upper:]' '[:lower:]'
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
# Source the generated Provider Catalog (bash-3.2-safe SSOT accessors) when present.
|
|
47
|
+
# FAIL-SAFE: consumers probe `type catalog_* >/dev/null 2>&1` and fall back to the
|
|
48
|
+
# legacy regex/hardcoded behavior when the artifact is missing (installed-tree skew)
|
|
49
|
+
# — mirrors play-tts.sh's PLAN_OK legacy fallback. Switch/list never break on a
|
|
50
|
+
# missing catalog. Double-sourcing is guarded inside provider-catalog.sh.
|
|
51
|
+
_load_provider_catalog() {
|
|
52
|
+
if [[ -f "$SCRIPT_DIR/provider-catalog.sh" ]]; then
|
|
53
|
+
# shellcheck source=/dev/null
|
|
54
|
+
source "$SCRIPT_DIR/provider-catalog.sh" 2>/dev/null || true
|
|
55
|
+
fi
|
|
56
|
+
}
|
|
57
|
+
|
|
46
58
|
# Determine target .claude directory based on context
|
|
47
59
|
# Priority:
|
|
48
60
|
# 1. CLAUDE_PROJECT_DIR env var (set by MCP for project-specific settings)
|
|
@@ -82,13 +94,26 @@ get_default_voice() {
|
|
|
82
94
|
|
|
83
95
|
case "$active_provider" in
|
|
84
96
|
piper)
|
|
85
|
-
|
|
97
|
+
# Piper default sourced from the generated Provider Catalog (SSOT, AVI-S9.4).
|
|
98
|
+
# FAIL-SAFE: legacy literal if the catalog artifact is missing (installed-tree skew).
|
|
99
|
+
_load_provider_catalog
|
|
100
|
+
if type catalog_default_voice >/dev/null 2>&1; then
|
|
101
|
+
catalog_default_voice piper
|
|
102
|
+
else
|
|
103
|
+
echo "en_US-lessac-medium" # Piper default (legacy fallback)
|
|
104
|
+
fi
|
|
86
105
|
;;
|
|
87
106
|
macos)
|
|
88
107
|
echo "Samantha" # macOS default
|
|
89
108
|
;;
|
|
90
109
|
*)
|
|
91
|
-
|
|
110
|
+
# Default to Piper — same catalog-sourced value, same fail-safe.
|
|
111
|
+
_load_provider_catalog
|
|
112
|
+
if type catalog_default_voice >/dev/null 2>&1; then
|
|
113
|
+
catalog_default_voice piper
|
|
114
|
+
else
|
|
115
|
+
echo "en_US-lessac-medium" # Default to Piper (legacy fallback)
|
|
116
|
+
fi
|
|
92
117
|
;;
|
|
93
118
|
esac
|
|
94
119
|
}
|
|
@@ -103,7 +128,7 @@ case "$1" in
|
|
|
103
128
|
|
|
104
129
|
ACTIVE_PROVIDER="piper" # default
|
|
105
130
|
if [ -f "$PROVIDER_FILE" ]; then
|
|
106
|
-
ACTIVE_PROVIDER=$(
|
|
131
|
+
ACTIVE_PROVIDER=$(tr -d '[:space:]' < "$PROVIDER_FILE")
|
|
107
132
|
fi
|
|
108
133
|
|
|
109
134
|
CURRENT_VOICE=$(cat "$VOICE_FILE" 2>/dev/null || get_default_voice)
|
|
@@ -170,12 +195,76 @@ case "$1" in
|
|
|
170
195
|
fi
|
|
171
196
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
172
197
|
fi
|
|
198
|
+
elif [[ "$ACTIVE_PROVIDER" == "kokoro" ]]; then
|
|
199
|
+
if [[ -f "$FORMATTER" ]] && command -v node &> /dev/null; then
|
|
200
|
+
node "$FORMATTER" "kokoro" "$CURRENT_VOICE"
|
|
201
|
+
else
|
|
202
|
+
echo "🎤 Available Kokoro TTS Voices:"
|
|
203
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
204
|
+
_load_provider_catalog
|
|
205
|
+
if type catalog_list_voices >/dev/null 2>&1; then
|
|
206
|
+
# All ~54 ids from the SSOT, one per line (catalog order = grouped by lang prefix).
|
|
207
|
+
catalog_list_voices kokoro | sed 's/^/ /'
|
|
208
|
+
else
|
|
209
|
+
# FAIL-SAFE legacy path (catalog artifact missing) — degraded, never dead.
|
|
210
|
+
echo " af_heart af_bella af_nicole af_sarah af_sky (American, female)"
|
|
211
|
+
echo " am_adam am_michael am_onyx am_puck (American, male)"
|
|
212
|
+
echo " bf_emma bf_lily bm_george bm_daniel (British)"
|
|
213
|
+
echo " jf_alpha (Japanese) zf_xiaoxiao (Mandarin) ef_dora (Spanish)"
|
|
214
|
+
fi
|
|
215
|
+
echo " (current: $CURRENT_VOICE)"
|
|
216
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
217
|
+
fi
|
|
218
|
+
elif [[ "$ACTIVE_PROVIDER" == "elevenlabs" ]]; then
|
|
219
|
+
if [[ -f "$FORMATTER" ]] && command -v node &> /dev/null; then
|
|
220
|
+
node "$FORMATTER" "elevenlabs" "$CURRENT_VOICE"
|
|
221
|
+
else
|
|
222
|
+
echo "🎤 Available ElevenLabs Voices:"
|
|
223
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
224
|
+
if [[ -f "$SCRIPT_DIR/elevenlabs-voices.sh" ]]; then
|
|
225
|
+
# shellcheck source=/dev/null
|
|
226
|
+
source "$SCRIPT_DIR/elevenlabs-voices.sh"
|
|
227
|
+
elevenlabs_voice_names | sort | sed 's/^/ /'
|
|
228
|
+
fi
|
|
229
|
+
echo " (current: $CURRENT_VOICE)"
|
|
230
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
231
|
+
fi
|
|
232
|
+
elif [[ "$ACTIVE_PROVIDER" == "soprano" ]]; then
|
|
233
|
+
# Soprano is a single-voice provider (voiceModel: single) — list its one
|
|
234
|
+
# canonical voice from the generated Provider Catalog (SSOT, AVI-S9.4).
|
|
235
|
+
if [[ -f "$FORMATTER" ]] && command -v node &> /dev/null; then
|
|
236
|
+
node "$FORMATTER" "soprano" "$CURRENT_VOICE"
|
|
237
|
+
else
|
|
238
|
+
echo "🎤 Available Soprano TTS Voices:"
|
|
239
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
240
|
+
_load_provider_catalog
|
|
241
|
+
if type catalog_list_voices >/dev/null 2>&1; then
|
|
242
|
+
catalog_list_voices soprano | sed 's/^/ /'
|
|
243
|
+
else
|
|
244
|
+
# FAIL-SAFE legacy path (catalog artifact missing): single canonical voice.
|
|
245
|
+
echo " soprano-default"
|
|
246
|
+
fi
|
|
247
|
+
echo " (current: $CURRENT_VOICE)"
|
|
248
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
249
|
+
fi
|
|
173
250
|
else
|
|
174
251
|
echo "❌ Unknown provider: $ACTIVE_PROVIDER"
|
|
175
252
|
echo ""
|
|
176
253
|
echo "Available providers:"
|
|
177
|
-
|
|
178
|
-
|
|
254
|
+
# Derived from the generated Provider Catalog (SSOT, AVI-S9.4) so a new
|
|
255
|
+
# provider never silently goes missing from this help text again.
|
|
256
|
+
_load_provider_catalog
|
|
257
|
+
if type catalog_providers_for_platform >/dev/null 2>&1; then
|
|
258
|
+
while IFS= read -r _pid; do
|
|
259
|
+
echo " - $_pid ($(catalog_display_name "$_pid"))"
|
|
260
|
+
done < <(catalog_providers_for_platform unix)
|
|
261
|
+
else
|
|
262
|
+
# FAIL-SAFE legacy path (catalog artifact missing).
|
|
263
|
+
echo " - piper (Free, Offline)"
|
|
264
|
+
echo " - macos (Built-in, macOS only)"
|
|
265
|
+
echo " - kokoro (Neural, local)"
|
|
266
|
+
echo " - elevenlabs (Cloud, API key)"
|
|
267
|
+
fi
|
|
179
268
|
echo ""
|
|
180
269
|
echo "Switch provider with: /agent-vibes:provider switch piper"
|
|
181
270
|
fi
|
|
@@ -221,7 +310,7 @@ case "$1" in
|
|
|
221
310
|
|
|
222
311
|
ACTIVE_PROVIDER="piper" # default
|
|
223
312
|
if [[ -n "$PROVIDER_FILE" ]]; then
|
|
224
|
-
ACTIVE_PROVIDER=$(
|
|
313
|
+
ACTIVE_PROVIDER=$(tr -d '[:space:]' < "$PROVIDER_FILE")
|
|
225
314
|
fi
|
|
226
315
|
|
|
227
316
|
# Voice lookup strategy depends on active provider
|
|
@@ -363,12 +452,123 @@ case "$1" in
|
|
|
363
452
|
echo "Download extra voices with: /agent-vibes:provider download"
|
|
364
453
|
exit 1
|
|
365
454
|
fi
|
|
455
|
+
elif [[ "$ACTIVE_PROVIDER" == "kokoro" ]]; then
|
|
456
|
+
# Kokoro switch-time validation via the generated Provider Catalog (SSOT):
|
|
457
|
+
# MEMBERSHIP + canonical case-fold (AF_HEART → af_heart), so a typo like
|
|
458
|
+
# `af_hart` is rejected HERE and never reaches tts-voice.txt to die silently
|
|
459
|
+
# at the model. Escape hatch AGENTVIBES_ALLOW_UNLISTED_VOICE=1 bypasses
|
|
460
|
+
# MEMBERSHIP (not shape) so a voice from a newer kokoro model still switches.
|
|
461
|
+
# FAIL-SAFE: if the catalog artifact is missing (installed-tree skew) we fall
|
|
462
|
+
# back to the legacy shape-only regex — degraded, never dead.
|
|
463
|
+
_load_provider_catalog
|
|
464
|
+
KOKORO_VOICE_LC="$(to_lower "$VOICE_NAME")"
|
|
465
|
+
if type catalog_validate_voice >/dev/null 2>&1; then
|
|
466
|
+
_KOKORO_CANON="$(catalog_validate_voice kokoro "$VOICE_NAME" 2>/dev/null || true)"
|
|
467
|
+
if [[ -n "$_KOKORO_CANON" ]]; then
|
|
468
|
+
FOUND="$_KOKORO_CANON"
|
|
469
|
+
elif [[ "${AGENTVIBES_ALLOW_UNLISTED_VOICE:-}" == "1" ]] && [[ "$KOKORO_VOICE_LC" =~ ^[a-z]{2}_[a-z0-9_]+$ ]]; then
|
|
470
|
+
echo "⚠️ '$VOICE_NAME' is not in the shipped Kokoro catalog; allowing it (AGENTVIBES_ALLOW_UNLISTED_VOICE=1)." >&2
|
|
471
|
+
FOUND="$KOKORO_VOICE_LC"
|
|
472
|
+
else
|
|
473
|
+
echo "❌ Kokoro voice not found: $VOICE_NAME"
|
|
474
|
+
echo ""
|
|
475
|
+
echo "That is not a known Kokoro voice. Examples:"
|
|
476
|
+
echo " - af_heart, af_bella, af_nicole (American English, female)"
|
|
477
|
+
echo " - am_adam, am_michael (American English, male)"
|
|
478
|
+
echo " - bf_emma, bf_lily (British English, female)"
|
|
479
|
+
echo " - bm_george, bm_daniel (British English, male)"
|
|
480
|
+
echo " - jf_alpha (Japanese), zf_xiaoxiao (Mandarin), ef_dora (Spanish)"
|
|
481
|
+
echo ""
|
|
482
|
+
echo "Set AGENTVIBES_ALLOW_UNLISTED_VOICE=1 to allow a newer-model voice."
|
|
483
|
+
echo "List all Kokoro voices with: /agent-vibes:list"
|
|
484
|
+
exit 1
|
|
485
|
+
fi
|
|
486
|
+
else
|
|
487
|
+
# FAIL-SAFE legacy path (catalog artifact missing): shape-only regex.
|
|
488
|
+
if [[ "$KOKORO_VOICE_LC" =~ ^[a-z]{2}_[a-z0-9_]+$ ]]; then
|
|
489
|
+
FOUND="$KOKORO_VOICE_LC"
|
|
490
|
+
else
|
|
491
|
+
echo "❌ Kokoro voice not found: $VOICE_NAME"
|
|
492
|
+
echo ""
|
|
493
|
+
echo "Kokoro voice ids look like <lang><sex>_name. Examples:"
|
|
494
|
+
echo " - af_heart, af_bella, af_nicole (American English, female)"
|
|
495
|
+
echo " - am_adam, am_michael (American English, male)"
|
|
496
|
+
echo " - bf_emma, bf_lily (British English, female)"
|
|
497
|
+
echo " - bm_george, bm_daniel (British English, male)"
|
|
498
|
+
echo " - jf_alpha (Japanese), zf_xiaoxiao (Mandarin), ef_dora (Spanish)"
|
|
499
|
+
echo ""
|
|
500
|
+
echo "List all Kokoro voices with: /agent-vibes:list"
|
|
501
|
+
exit 1
|
|
502
|
+
fi
|
|
503
|
+
fi
|
|
504
|
+
elif [[ "$ACTIVE_PROVIDER" == "elevenlabs" ]]; then
|
|
505
|
+
# ElevenLabs voice lookup — resolve a friendly name (case-insensitive) OR a
|
|
506
|
+
# raw voice_id to the canonical voice_id and save THAT (play-tts-elevenlabs.sh
|
|
507
|
+
# accepts a raw id directly). Resolution is bash-native via the shared catalog
|
|
508
|
+
# (elevenlabs-voices.sh) — the SAME source the synth hook uses — so it works
|
|
509
|
+
# in installed deployments with no Node dependency.
|
|
510
|
+
EL_RESOLVED=""
|
|
511
|
+
if [[ -f "$SCRIPT_DIR/elevenlabs-voices.sh" ]]; then
|
|
512
|
+
# shellcheck source=/dev/null
|
|
513
|
+
source "$SCRIPT_DIR/elevenlabs-voices.sh"
|
|
514
|
+
EL_RESOLVED="$(elevenlabs_resolve_voice "$VOICE_NAME" 2>/dev/null || true)"
|
|
515
|
+
elif [[ "$VOICE_NAME" =~ ^[A-Za-z0-9]{20}$ ]]; then
|
|
516
|
+
# Catalog missing but the input is already a raw voice_id — accept it.
|
|
517
|
+
EL_RESOLVED="$VOICE_NAME"
|
|
518
|
+
fi
|
|
519
|
+
if [[ -n "$EL_RESOLVED" ]]; then
|
|
520
|
+
FOUND="$EL_RESOLVED"
|
|
521
|
+
else
|
|
522
|
+
echo "❌ ElevenLabs voice not found: $VOICE_NAME"
|
|
523
|
+
echo ""
|
|
524
|
+
echo "ElevenLabs voices include: Sarah, Roger, Laura, George, Callum, Alice,"
|
|
525
|
+
echo "Matilda, Will, Jessica, Eric, Brian, Daniel, Lily, Bill (or a raw voice_id)."
|
|
526
|
+
echo ""
|
|
527
|
+
echo "List all ElevenLabs voices with: /agent-vibes:list"
|
|
528
|
+
exit 1
|
|
529
|
+
fi
|
|
530
|
+
elif [[ "$ACTIVE_PROVIDER" == "soprano" ]]; then
|
|
531
|
+
# Soprano switch-time validation via the generated Provider Catalog (SSOT):
|
|
532
|
+
# voiceModel `single` — accepts '', 'soprano', 'soprano-default' (case-insensitive)
|
|
533
|
+
# and canonicalizes to 'soprano-default'; anything else is rejected here so a
|
|
534
|
+
# typo never reaches tts-voice.txt. FAIL-SAFE: if the catalog artifact is
|
|
535
|
+
# missing, the same fixed rule is applied hardcoded (soprano has exactly one
|
|
536
|
+
# voice — this rule never drifts).
|
|
537
|
+
_load_provider_catalog
|
|
538
|
+
SOPRANO_CANON=""
|
|
539
|
+
if type catalog_validate_voice >/dev/null 2>&1; then
|
|
540
|
+
SOPRANO_CANON="$(catalog_validate_voice soprano "$VOICE_NAME" 2>/dev/null || true)"
|
|
541
|
+
else
|
|
542
|
+
case "$(to_lower "$VOICE_NAME")" in
|
|
543
|
+
''|soprano|soprano-default) SOPRANO_CANON="soprano-default" ;;
|
|
544
|
+
esac
|
|
545
|
+
fi
|
|
546
|
+
if [[ -n "$SOPRANO_CANON" ]]; then
|
|
547
|
+
FOUND="$SOPRANO_CANON"
|
|
548
|
+
else
|
|
549
|
+
echo "❌ Soprano voice not found: $VOICE_NAME"
|
|
550
|
+
echo ""
|
|
551
|
+
echo "Soprano TTS has a single voice: soprano-default"
|
|
552
|
+
echo "(accepts '', 'soprano', or 'soprano-default')."
|
|
553
|
+
exit 1
|
|
554
|
+
fi
|
|
366
555
|
else
|
|
367
556
|
echo "❌ Unknown provider: $ACTIVE_PROVIDER"
|
|
368
557
|
echo ""
|
|
369
558
|
echo "Available providers:"
|
|
370
|
-
|
|
371
|
-
|
|
559
|
+
# Derived from the generated Provider Catalog (SSOT, AVI-S9.4).
|
|
560
|
+
_load_provider_catalog
|
|
561
|
+
if type catalog_providers_for_platform >/dev/null 2>&1; then
|
|
562
|
+
while IFS= read -r _pid; do
|
|
563
|
+
echo " - $_pid ($(catalog_display_name "$_pid"))"
|
|
564
|
+
done < <(catalog_providers_for_platform unix)
|
|
565
|
+
else
|
|
566
|
+
# FAIL-SAFE legacy path (catalog artifact missing).
|
|
567
|
+
echo " - piper (Free, Offline)"
|
|
568
|
+
echo " - macos (Built-in, macOS only)"
|
|
569
|
+
echo " - kokoro (Neural, local)"
|
|
570
|
+
echo " - elevenlabs (Cloud, API key)"
|
|
571
|
+
fi
|
|
372
572
|
echo ""
|
|
373
573
|
echo "Switch provider with: /agent-vibes:provider switch piper"
|
|
374
574
|
exit 1
|
|
@@ -412,7 +612,7 @@ case "$1" in
|
|
|
412
612
|
fi
|
|
413
613
|
|
|
414
614
|
if [ -f "$PROVIDER_FILE" ]; then
|
|
415
|
-
ACTIVE_PROVIDER=$(
|
|
615
|
+
ACTIVE_PROVIDER=$(tr -d '[:space:]' < "$PROVIDER_FILE")
|
|
416
616
|
if [[ "$ACTIVE_PROVIDER" == "piper" ]]; then
|
|
417
617
|
echo "Provider: Piper TTS (Free, Offline)"
|
|
418
618
|
elif [[ "$ACTIVE_PROVIDER" == "ssh-remote" ]]; then
|
|
@@ -421,6 +621,10 @@ case "$1" in
|
|
|
421
621
|
echo "Provider: Piper TTS (via AgentVibes Receiver)"
|
|
422
622
|
elif [[ "$ACTIVE_PROVIDER" == "macos" ]]; then
|
|
423
623
|
echo "Provider: macOS Say (Built-in, Free)"
|
|
624
|
+
elif [[ "$ACTIVE_PROVIDER" == "kokoro" ]]; then
|
|
625
|
+
echo "Provider: Kokoro TTS (Neural, Local)"
|
|
626
|
+
elif [[ "$ACTIVE_PROVIDER" == "elevenlabs" ]]; then
|
|
627
|
+
echo "Provider: ElevenLabs (Cloud, API Key)"
|
|
424
628
|
else
|
|
425
629
|
echo "Provider: $ACTIVE_PROVIDER"
|
|
426
630
|
fi
|
|
@@ -466,7 +670,7 @@ case "$1" in
|
|
|
466
670
|
|
|
467
671
|
ACTIVE_PROVIDER="piper" # default
|
|
468
672
|
if [ -f "$PROVIDER_FILE" ]; then
|
|
469
|
-
ACTIVE_PROVIDER=$(
|
|
673
|
+
ACTIVE_PROVIDER=$(tr -d '[:space:]' < "$PROVIDER_FILE")
|
|
470
674
|
fi
|
|
471
675
|
|
|
472
676
|
if [[ "$ACTIVE_PROVIDER" == "piper" || "$ACTIVE_PROVIDER" == "ssh-remote" || "$ACTIVE_PROVIDER" == "agentvibes-receiver" ]]; then
|
|
@@ -487,6 +691,34 @@ case "$1" in
|
|
|
487
691
|
else
|
|
488
692
|
echo "(macOS voices only available on macOS)"
|
|
489
693
|
fi
|
|
694
|
+
elif [[ "$ACTIVE_PROVIDER" == "kokoro" ]]; then
|
|
695
|
+
# Kokoro's catalog is fixed; list ALL ids from the generated Provider Catalog (SSOT).
|
|
696
|
+
_load_provider_catalog
|
|
697
|
+
if type catalog_list_voices >/dev/null 2>&1; then
|
|
698
|
+
catalog_list_voices kokoro | sort
|
|
699
|
+
else
|
|
700
|
+
# FAIL-SAFE legacy path (catalog artifact missing): representative subset.
|
|
701
|
+
printf '%s\n' af_heart af_bella af_nicole af_sarah af_sky \
|
|
702
|
+
am_adam am_michael am_onyx am_puck \
|
|
703
|
+
bf_emma bf_lily bm_george bm_daniel \
|
|
704
|
+
jf_alpha zf_xiaoxiao ef_dora | sort
|
|
705
|
+
fi
|
|
706
|
+
elif [[ "$ACTIVE_PROVIDER" == "elevenlabs" ]]; then
|
|
707
|
+
if [[ -f "$SCRIPT_DIR/elevenlabs-voices.sh" ]]; then
|
|
708
|
+
# shellcheck source=/dev/null
|
|
709
|
+
source "$SCRIPT_DIR/elevenlabs-voices.sh"
|
|
710
|
+
elevenlabs_voice_names | sort
|
|
711
|
+
fi
|
|
712
|
+
elif [[ "$ACTIVE_PROVIDER" == "soprano" ]]; then
|
|
713
|
+
# Soprano's catalog is a single fixed voice; list it from the generated
|
|
714
|
+
# Provider Catalog (SSOT, AVI-S9.4).
|
|
715
|
+
_load_provider_catalog
|
|
716
|
+
if type catalog_list_voices >/dev/null 2>&1; then
|
|
717
|
+
catalog_list_voices soprano
|
|
718
|
+
else
|
|
719
|
+
# FAIL-SAFE legacy path (catalog artifact missing).
|
|
720
|
+
echo "soprano-default"
|
|
721
|
+
fi
|
|
490
722
|
else
|
|
491
723
|
echo "(Unknown provider: $ACTIVE_PROVIDER)"
|
|
492
724
|
fi
|