agentvibes 5.12.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/.claude/commands/agent-vibes/commands.json +0 -20
- package/.claude/commands/agent-vibes/unmute.md +6 -2
- package/.claude/config/audio-effects.cfg +6 -6
- 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 +37 -29
- package/.claude/hooks/play-tts-termux-ssh.sh +5 -4
- package/.claude/hooks/play-tts.sh +66 -61
- 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/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/tts-watcher.ps1 +33 -12
- package/.claude/hooks-windows/voice-manager-windows.ps1 +49 -0
- package/.mcp.json +0 -7
- package/README.md +12 -3
- package/RELEASE_NOTES.md +43 -0
- package/mcp-server/server.py +146 -49
- package/mcp-server/test_mcp_correctness.py +20 -2
- package/mcp-server/test_windows_script_parity.py +0 -2
- package/package.json +1 -1
- package/src/cli/list-voices.js +218 -114
- package/src/console/bling.js +71 -0
- package/src/console/music-preview.js +79 -0
- package/src/console/tabs/music-tab.js +16 -39
- package/src/console/tabs/settings-tab.js +195 -13
- package/src/console/tabs/setup-tab.js +9 -34
- package/src/console/tabs/voices-tab.js +83 -14
- package/src/console/widgets/track-picker.js +82 -0
- package/src/installer.js +124 -10
- 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/provider-validator.js +62 -12
- 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
|
@@ -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
|
|
@@ -146,7 +146,13 @@ switch ($Command) {
|
|
|
146
146
|
Write-Output "Usage: language-manager.ps1 check-voice <voice-name>"
|
|
147
147
|
exit 1
|
|
148
148
|
}
|
|
149
|
-
|
|
149
|
+
# AVI-S9.6 AC1: trimmed to names that exist in the 21-voice ElevenLabs
|
|
150
|
+
# catalog (src/services/provider-catalog.js ELEVENLABS_VOICES) — the
|
|
151
|
+
# list previously included "Antoni"/"Rachel"/"Domi"/"Charlotte", none of
|
|
152
|
+
# which exist there (the same class of bug design row 21 found in the
|
|
153
|
+
# now-deleted learn-manager.sh). Parity-asserted against the catalog by
|
|
154
|
+
# test/unit/provider-catalog-conformance.test.js.
|
|
155
|
+
$multilingual = @("Bella", "Matilda")
|
|
150
156
|
if ($Arg1 -in $multilingual) { Write-Output "yes" } else { Write-Output "no" }
|
|
151
157
|
}
|
|
152
158
|
|
|
@@ -29,6 +29,13 @@ if ($env:CLAUDE_PROJECT_DIR -and (Test-Path "$env:CLAUDE_PROJECT_DIR\.claude"))
|
|
|
29
29
|
|
|
30
30
|
$PersonalityFile = Join-Path $ConfigClaudeDir "tts-personality.txt"
|
|
31
31
|
|
|
32
|
+
# Load the generated Provider Catalog (SSOT) for the Piper default voice.
|
|
33
|
+
# FAIL-SAFE: consumers probe `Get-Command Get-CatalogDefaultVoice` and fall back
|
|
34
|
+
# to the legacy literal when the artifact is missing (installed-tree skew) —
|
|
35
|
+
# mirrors voice-manager-windows.ps1 / play-tts-kokoro.ps1 (AVI-S9.3/9.4).
|
|
36
|
+
$CatalogPs1 = Join-Path $ScriptDir 'provider-catalog.ps1'
|
|
37
|
+
if (Test-Path $CatalogPs1) { . $CatalogPs1 }
|
|
38
|
+
|
|
32
39
|
function Get-PersonalityData {
|
|
33
40
|
param([string]$Personality, [string]$Field)
|
|
34
41
|
$file = Join-Path $PersonalitiesDir "$Personality.md"
|
|
@@ -116,7 +123,15 @@ switch ($Command) {
|
|
|
116
123
|
|
|
117
124
|
# Get assigned voice
|
|
118
125
|
$assignedVoice = Get-PersonalityData $personality "piper_voice"
|
|
119
|
-
if (-not $assignedVoice) {
|
|
126
|
+
if (-not $assignedVoice) {
|
|
127
|
+
# Fallback to default Piper voice — sourced from the generated Provider
|
|
128
|
+
# Catalog SSOT (AVI-S9.4). FAIL-SAFE: legacy literal if artifact missing.
|
|
129
|
+
$assignedVoice = $null
|
|
130
|
+
if (Get-Command Get-CatalogDefaultVoice -ErrorAction SilentlyContinue) {
|
|
131
|
+
$assignedVoice = Get-CatalogDefaultVoice -Provider 'piper'
|
|
132
|
+
}
|
|
133
|
+
if (-not $assignedVoice) { $assignedVoice = "en_US-lessac-medium" }
|
|
134
|
+
}
|
|
120
135
|
|
|
121
136
|
if ($assignedVoice) {
|
|
122
137
|
Write-Output "Switching to assigned voice: $assignedVoice"
|
|
@@ -27,6 +27,20 @@ $ScriptPath = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
|
27
27
|
$ProjectClaudeDir = Join-Path (Split-Path -Parent (Split-Path -Parent $ScriptPath)) ".claude"
|
|
28
28
|
if (Test-Path $ProjectClaudeDir) { $ClaudeDir = $ProjectClaudeDir } else { $ClaudeDir = "$env:USERPROFILE\.claude" }
|
|
29
29
|
|
|
30
|
+
# --- Default voice from the generated Provider Catalog (SSOT) -----------------
|
|
31
|
+
# FAIL-SAFE: keep the literal fallback for installed-tree skew (mirrors play-tts.sh
|
|
32
|
+
# PLAN_OK). The synth-time shape check below stays LENIENT (design §3.3) so a voice
|
|
33
|
+
# from a newer kokoro model still degrades audibly instead of bricking.
|
|
34
|
+
$KokoroDefault = "af_heart"
|
|
35
|
+
$CatalogPs1 = Join-Path $ScriptPath 'provider-catalog.ps1'
|
|
36
|
+
if (Test-Path $CatalogPs1) {
|
|
37
|
+
. $CatalogPs1
|
|
38
|
+
if (Get-Command Get-CatalogDefaultVoice -ErrorAction SilentlyContinue) {
|
|
39
|
+
$d = Get-CatalogDefaultVoice -Provider 'kokoro'
|
|
40
|
+
if ($d) { $KokoroDefault = $d }
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
30
44
|
$AudioDir = "$ClaudeDir\audio"
|
|
31
45
|
$ConfigDir = "$ClaudeDir\config"
|
|
32
46
|
if (-not (Test-Path $AudioDir)) { New-Item -ItemType Directory -Path $AudioDir -Force | Out-Null }
|
|
@@ -70,13 +84,15 @@ if ($VoiceOverride) {
|
|
|
70
84
|
}
|
|
71
85
|
# Kokoro voice ids look like af_heart, am_michael, bf_emma. Strip any ::display suffix.
|
|
72
86
|
if ($VoiceName -match '::') { $VoiceName = ($VoiceName -split '::')[0] }
|
|
73
|
-
if (-not $VoiceName) { $VoiceName =
|
|
87
|
+
if (-not $VoiceName) { $VoiceName = $KokoroDefault }
|
|
74
88
|
|
|
75
89
|
# Security: validate voice id (lowercase prefix + underscore + name).
|
|
76
|
-
#
|
|
90
|
+
# LENIENT shape check (design §3.3) — rejects path-traversal / injection but lets
|
|
91
|
+
# an unknown-but-shape-valid voice from a newer kokoro model through; falls back
|
|
92
|
+
# to the catalog default only on a shape violation.
|
|
77
93
|
if ($VoiceName -notmatch '^[a-z]{2}_[a-z0-9_]+$') {
|
|
78
|
-
Write-Host "[WARNING] Invalid Kokoro voice '$VoiceName' - falling back to
|
|
79
|
-
$VoiceName =
|
|
94
|
+
Write-Host "[WARNING] Invalid Kokoro voice '$VoiceName' - falling back to $KokoroDefault" -ForegroundColor Yellow
|
|
95
|
+
$VoiceName = $KokoroDefault
|
|
80
96
|
}
|
|
81
97
|
|
|
82
98
|
# --- Optional speed ----------------------------------------------------------
|
|
@@ -116,8 +116,9 @@ switch ($ActiveProvider) {
|
|
|
116
116
|
# by the SSH-receiver watcher via -ProviderOverride). This lets the Linux-side
|
|
117
117
|
# audio-effects.cfg row for llm:claude-code specify "piper" and have it honoured
|
|
118
118
|
# on Windows without requiring the Windows tts-provider.txt to be reconfigured.
|
|
119
|
-
# Priority:
|
|
120
|
-
#
|
|
119
|
+
# Priority: an explicit -ProviderOverride is AUTHORITATIVE — it wins over both the
|
|
120
|
+
# per-LLM $_LlmEngine column and the global tts-provider.txt default (see the
|
|
121
|
+
# $ProviderOverride guard in the engine-resolution block below).
|
|
121
122
|
if ($ProviderOverride) {
|
|
122
123
|
switch ($ProviderOverride) {
|
|
123
124
|
{ $_ -in "windows-piper", "piper" } {
|
|
@@ -308,8 +309,30 @@ $PlanVoiceIsOverride = $false
|
|
|
308
309
|
if ($ProviderOverride) {
|
|
309
310
|
# A fresh -ProviderOverride for THIS request must win over any stale/inherited
|
|
310
311
|
# AGENTVIBES_FORCE_PROVIDER in the environment (a validated allowlist only).
|
|
312
|
+
#
|
|
313
|
+
# The allowlist DERIVES from the Provider Catalog (SSOT): the Windows platform
|
|
314
|
+
# set PLUS the cross-platform forwarding aliases this script normalizes
|
|
315
|
+
# (piper→windows-piper, sapi→windows-sapi, and macos forwarded verbatim for
|
|
316
|
+
# SSH-relayed senders). FAIL-SAFE: the literal below is the legacy fallback
|
|
317
|
+
# used when the generated provider-catalog.ps1 is missing (installed-tree skew).
|
|
318
|
+
$ProviderOverrideAllowlist = @('piper','soprano','macos','windows-sapi','sapi','kokoro','windows-piper')
|
|
319
|
+
$__CatalogPs1 = Join-Path $ScriptPath 'provider-catalog.ps1'
|
|
320
|
+
if (Test-Path $__CatalogPs1) {
|
|
321
|
+
try {
|
|
322
|
+
. $__CatalogPs1
|
|
323
|
+
if (Get-Command Get-CatalogProvidersForPlatform -ErrorAction SilentlyContinue) {
|
|
324
|
+
$__WinSet = @(Get-CatalogProvidersForPlatform 'windows')
|
|
325
|
+
if ($__WinSet.Count -gt 0) {
|
|
326
|
+
# Windows synth providers + the forwarding aliases play-tts.ps1 normalizes.
|
|
327
|
+
$ProviderOverrideAllowlist = @($__WinSet + @('piper','sapi','macos') | Select-Object -Unique)
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
} catch {
|
|
331
|
+
# Keep the legacy fallback allowlist on any catalog load error.
|
|
332
|
+
}
|
|
333
|
+
}
|
|
311
334
|
switch ($ProviderOverride) {
|
|
312
|
-
{ $_ -in
|
|
335
|
+
{ $_ -in $ProviderOverrideAllowlist } {
|
|
313
336
|
$env:AGENTVIBES_FORCE_PROVIDER = $ProviderOverride
|
|
314
337
|
}
|
|
315
338
|
}
|
|
@@ -522,6 +545,12 @@ else {
|
|
|
522
545
|
# ENGINE column or the global tts-provider.txt default.
|
|
523
546
|
$ProviderScript = "$HooksDir\play-tts-kokoro.ps1"
|
|
524
547
|
}
|
|
548
|
+
elseif ($ProviderOverride) {
|
|
549
|
+
# An explicit -ProviderOverride (forwarded from the SSH payload's "provider"
|
|
550
|
+
# field, or a preview) is AUTHORITATIVE and was already applied above. Do NOT
|
|
551
|
+
# let the per-LLM ENGINE column override it, or a receiver whose llm row
|
|
552
|
+
# defaults to piper silently swallows a forwarded windows-sapi request.
|
|
553
|
+
}
|
|
525
554
|
elseif ($_LlmEngine) {
|
|
526
555
|
# Accept both canonical Windows names and the cross-platform aliases the TUI
|
|
527
556
|
# writes (e.g. "piper" saved on a Linux/WSL install that is later read on
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# provider-catalog.ps1 — GENERATED FILE. DO NOT EDIT.
|
|
2
|
+
# DO NOT EDIT — generated from src/services/provider-catalog.js
|
|
3
|
+
# Regenerate with: node scripts/generate-provider-catalog.mjs
|
|
4
|
+
# content-hash: sha256:5e717024f1250a8f54d07291256e532606dba7b4a15ab9589305d1af6bba7f35
|
|
5
|
+
# Dot-source this file to load the catalog accessors; do not execute it.
|
|
6
|
+
|
|
7
|
+
$script:AgentVibesCatalogVersion = '5e717024f1250a8f'
|
|
8
|
+
|
|
9
|
+
$script:CatalogDefaults = @{
|
|
10
|
+
'soprano' = 'soprano-default'
|
|
11
|
+
'piper' = 'en_US-lessac-medium'
|
|
12
|
+
'kokoro' = 'af_heart'
|
|
13
|
+
'elevenlabs' = 'Sarah'
|
|
14
|
+
'macos' = 'Samantha'
|
|
15
|
+
'windows-sapi' = ''
|
|
16
|
+
'windows-piper' = 'en_US-lessac-medium'
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
$script:CatalogDisplayNames = @{
|
|
20
|
+
'soprano' = 'Soprano TTS'
|
|
21
|
+
'piper' = 'Piper TTS'
|
|
22
|
+
'kokoro' = 'Kokoro TTS'
|
|
23
|
+
'elevenlabs' = 'ElevenLabs'
|
|
24
|
+
'macos' = 'macOS Say'
|
|
25
|
+
'windows-sapi' = 'Windows SAPI'
|
|
26
|
+
'windows-piper' = 'Piper TTS'
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
$script:CatalogAlias = @{
|
|
30
|
+
'macos-say' = 'macos'
|
|
31
|
+
'say' = 'macos'
|
|
32
|
+
'sapi' = 'windows-sapi'
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
$script:CatalogPlatform = @{
|
|
36
|
+
'unix' = @('soprano','piper','kokoro','elevenlabs')
|
|
37
|
+
'darwin' = @('soprano','piper','kokoro','elevenlabs','macos')
|
|
38
|
+
'windows' = @('soprano','kokoro','windows-sapi','windows-piper')
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
$script:CatalogVoices = @{
|
|
42
|
+
'soprano' = @('soprano-default')
|
|
43
|
+
'piper' = @()
|
|
44
|
+
'kokoro' = @('af_heart','af_alloy','af_aoede','af_bella','af_jessica','af_kore','af_nicole','af_nova','af_river','af_sarah','af_sky','am_adam','am_echo','am_eric','am_fenrir','am_liam','am_michael','am_onyx','am_puck','bf_alice','bf_emma','bf_isabella','bf_lily','bm_daniel','bm_fable','bm_george','bm_lewis','jf_alpha','jf_gongitsune','jf_nezumi','jf_tebukuro','jm_kumo','zf_xiaobei','zf_xiaoni','zf_xiaoxiao','zf_xiaoyi','zm_yunxi','zm_yunxia','zm_yunyang','ef_dora','em_alex','em_santa','ff_siwis','hf_alpha','hm_omega','if_sara','im_nicola','pf_dora','pm_alex','pm_santa','kf_alpha','km_hyunsu')
|
|
45
|
+
'elevenlabs' = @('Sarah','Roger','Laura','Charlie','George','Callum','River','Harry','Liam','Alice','Matilda','Will','Jessica','Eric','Bella','Chris','Brian','Daniel','Lily','Adam','Bill')
|
|
46
|
+
'macos' = @()
|
|
47
|
+
'windows-sapi' = @()
|
|
48
|
+
'windows-piper' = @()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
$script:CatalogElevenLabs = @{
|
|
52
|
+
'sarah' = 'EXAVITQu4vr4xnSDxMaL'
|
|
53
|
+
'roger' = 'CwhRBWXzGAHq8TQ4Fs17'
|
|
54
|
+
'laura' = 'FGY2WhTYpPnrIDTdsKH5'
|
|
55
|
+
'charlie' = 'IKne3meq5aSn9XLyUdCD'
|
|
56
|
+
'george' = 'JBFqnCBsd6RMkjVDRZzb'
|
|
57
|
+
'callum' = 'N2lVS1w4EtoT3dr4eOWO'
|
|
58
|
+
'river' = 'SAz9YHcvj6GT2YYXdXww'
|
|
59
|
+
'harry' = 'SOYHLrjzK2X1ezoPC6cr'
|
|
60
|
+
'liam' = 'TX3LPaxmHKxFdv7VOQHJ'
|
|
61
|
+
'alice' = 'Xb7hH8MSUJpSbSDYk0k2'
|
|
62
|
+
'matilda' = 'XrExE9yKIg1WjnnlVkGX'
|
|
63
|
+
'will' = 'bIHbv24MWmeRgasZH58o'
|
|
64
|
+
'jessica' = 'cgSgspJ2msm6clMCkdW9'
|
|
65
|
+
'eric' = 'cjVigY5qzO86Huf0OWal'
|
|
66
|
+
'bella' = 'hpp4J3VqNfWAUOO0d1Us'
|
|
67
|
+
'chris' = 'iP95p4xoKVk53GoZ742B'
|
|
68
|
+
'brian' = 'nPczCjzI2devNBz1zQrb'
|
|
69
|
+
'daniel' = 'onwK4e9ZLuTAKqWW03F9'
|
|
70
|
+
'lily' = 'pFZP5JQG7iQjIQuC4Bku'
|
|
71
|
+
'adam' = 'pNInz6obpgDQGcFmaJgB'
|
|
72
|
+
'bill' = 'pqHfZKP75CvOlQylNhV4'
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function Get-CatalogCanonicalProvider {
|
|
76
|
+
param([Parameter(Mandatory)][string]$Provider)
|
|
77
|
+
$key = $Provider.Trim().ToLowerInvariant()
|
|
78
|
+
if ($script:CatalogDefaults.ContainsKey($key)) { return $key }
|
|
79
|
+
if ($script:CatalogAlias.ContainsKey($key)) { return $script:CatalogAlias[$key] }
|
|
80
|
+
return $null
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function Get-CatalogDefaultVoice {
|
|
84
|
+
param([Parameter(Mandatory)][string]$Provider)
|
|
85
|
+
$p = Get-CatalogCanonicalProvider $Provider
|
|
86
|
+
if ($null -eq $p) { return $null }
|
|
87
|
+
return $script:CatalogDefaults[$p]
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function Get-CatalogDisplayName {
|
|
91
|
+
param([Parameter(Mandatory)][string]$Provider)
|
|
92
|
+
$p = Get-CatalogCanonicalProvider $Provider
|
|
93
|
+
if ($null -eq $p) { return $Provider }
|
|
94
|
+
return $script:CatalogDisplayNames[$p]
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function Get-CatalogProvidersForPlatform {
|
|
98
|
+
param([Parameter(Mandatory)][string]$Platform)
|
|
99
|
+
$key = $Platform.Trim().ToLowerInvariant()
|
|
100
|
+
if ($script:CatalogPlatform.ContainsKey($key)) { return $script:CatalogPlatform[$key] }
|
|
101
|
+
return @()
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function Get-CatalogVoices {
|
|
105
|
+
param([Parameter(Mandatory)][string]$Provider)
|
|
106
|
+
$p = Get-CatalogCanonicalProvider $Provider
|
|
107
|
+
if ($null -eq $p) { return @() }
|
|
108
|
+
return $script:CatalogVoices[$p]
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function Test-CatalogVoice {
|
|
112
|
+
param([Parameter(Mandatory)][string]$Provider, [Parameter(Mandatory)][AllowEmptyString()][string]$Voice)
|
|
113
|
+
$p = Get-CatalogCanonicalProvider $Provider
|
|
114
|
+
if ($null -eq $p) { return $null }
|
|
115
|
+
$lc = $Voice.Trim().ToLowerInvariant()
|
|
116
|
+
switch ($p) {
|
|
117
|
+
'kokoro' {
|
|
118
|
+
if ($script:CatalogVoices['kokoro'] -contains $lc) { return $lc }
|
|
119
|
+
return $null
|
|
120
|
+
}
|
|
121
|
+
'elevenlabs' {
|
|
122
|
+
if ($script:CatalogElevenLabs.ContainsKey($lc)) { return $script:CatalogElevenLabs[$lc] }
|
|
123
|
+
if ($Voice -match '^[A-Za-z0-9]{20}$') { return $Voice }
|
|
124
|
+
return $null
|
|
125
|
+
}
|
|
126
|
+
'soprano' {
|
|
127
|
+
if ($lc -eq '' -or $lc -eq 'soprano' -or $lc -eq 'soprano-default') { return 'soprano-default' }
|
|
128
|
+
return $null
|
|
129
|
+
}
|
|
130
|
+
{ $_ -eq 'piper' -or $_ -eq 'windows-piper' } {
|
|
131
|
+
if ($Voice -match '^[a-z]{2,3}_[A-Z]{2}-' -or $Voice -like '*::*') { return $Voice }
|
|
132
|
+
return $null
|
|
133
|
+
}
|
|
134
|
+
default {
|
|
135
|
+
if ($Voice -ne '') { return $Voice }
|
|
136
|
+
if ($p -eq 'windows-sapi') { return '' }
|
|
137
|
+
return $null
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|