agentvibes 4.0.1 → 4.4.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/bmad/bmad-voices.md +69 -69
- package/.agentvibes/config.json +12 -0
- package/.claude/activation-instructions +54 -54
- package/.claude/audio/tracks/README.md +52 -52
- package/.claude/commands/agent-vibes/add.md +21 -21
- package/.claude/commands/agent-vibes/agent-vibes.md +101 -101
- package/.claude/commands/agent-vibes/agent.md +79 -79
- package/.claude/commands/agent-vibes/background-music.md +111 -111
- package/.claude/commands/agent-vibes/bmad.md +198 -198
- package/.claude/commands/agent-vibes/clean.md +18 -18
- package/.claude/commands/agent-vibes/cleanup.md +18 -18
- package/.claude/commands/agent-vibes/commands.json +145 -145
- package/.claude/commands/agent-vibes/effects.md +97 -97
- package/.claude/commands/agent-vibes/get.md +9 -9
- package/.claude/commands/agent-vibes/hide.md +91 -91
- package/.claude/commands/agent-vibes/language.md +23 -23
- package/.claude/commands/agent-vibes/learn.md +67 -67
- package/.claude/commands/agent-vibes/list.md +13 -13
- package/.claude/commands/agent-vibes/mute.md +37 -37
- package/.claude/commands/agent-vibes/preview.md +17 -17
- package/.claude/commands/agent-vibes/provider.md +68 -68
- package/.claude/commands/agent-vibes/replay-target.md +14 -14
- package/.claude/commands/agent-vibes/sample.md +12 -12
- package/.claude/commands/agent-vibes/set-favorite-voice.md +84 -84
- package/.claude/commands/agent-vibes/set-pretext.md +65 -65
- package/.claude/commands/agent-vibes/set-speed.md +41 -41
- package/.claude/commands/agent-vibes/show.md +84 -84
- package/.claude/commands/agent-vibes/switch.md +87 -87
- package/.claude/commands/agent-vibes/target-voice.md +26 -26
- package/.claude/commands/agent-vibes/target.md +30 -30
- package/.claude/commands/agent-vibes/translate.md +68 -68
- package/.claude/commands/agent-vibes/unmute.md +45 -45
- package/.claude/commands/agent-vibes/verbosity.md +89 -89
- package/.claude/commands/agent-vibes/whoami.md +7 -7
- package/.claude/commands/agent-vibes-bmad-voices.md +117 -117
- package/.claude/commands/agent-vibes-rdp.md +24 -24
- package/.claude/config/agentvibes.json +1 -0
- package/.claude/config/audio-effects.cfg +3 -2
- package/.claude/config/audio-effects.cfg.sample +52 -52
- package/.claude/config/background-music-volume.txt +1 -0
- package/.claude/config/intro-text.txt +1 -0
- package/.claude/config/piper-speech-rate.txt +4 -0
- package/.claude/config/piper-target-speech-rate.txt +1 -0
- package/.claude/config/reverb-level.txt +1 -0
- package/.claude/config/tts-speech-rate.txt +4 -0
- package/.claude/config/tts-target-speech-rate.txt +1 -0
- package/.claude/docs/TERMUX_SETUP.md +408 -408
- package/.claude/github-star-reminder.txt +1 -1
- package/.claude/hooks/README-TTS-QUEUE.md +135 -135
- package/.claude/hooks/audio-cache-utils.sh +246 -246
- package/.claude/hooks/audio-processor.sh +433 -389
- package/.claude/hooks/background-music-manager.sh +404 -404
- package/.claude/hooks/bmad-speak-enhanced.sh +165 -165
- package/.claude/hooks/bmad-speak.sh +269 -112
- package/.claude/hooks/bmad-tts-injector.sh +568 -568
- package/.claude/hooks/bmad-voice-manager.sh +928 -928
- package/.claude/hooks/clawdbot-receiver-SECURE.sh +129 -129
- package/.claude/hooks/clawdbot-receiver.sh +107 -107
- package/.claude/hooks/clean-audio-cache.sh +22 -22
- package/.claude/hooks/cleanup-cache.sh +106 -106
- package/.claude/hooks/configure-rdp-mode.sh +137 -137
- package/.claude/hooks/download-extra-voices.sh +244 -244
- package/.claude/hooks/effects-manager.sh +268 -268
- package/.claude/hooks/github-star-reminder.sh +154 -154
- package/.claude/hooks/language-manager.sh +362 -362
- package/.claude/hooks/learn-manager.sh +492 -492
- package/.claude/hooks/macos-voice-manager.sh +205 -205
- package/.claude/hooks/migrate-background-music.sh +125 -125
- package/.claude/hooks/migrate-to-agentvibes.sh +161 -161
- package/.claude/hooks/optimize-background-music.sh +87 -87
- package/.claude/hooks/path-resolver.sh +60 -60
- package/.claude/hooks/personality-manager.sh +448 -448
- package/.claude/hooks/piper-download-voices.sh +225 -225
- package/.claude/hooks/piper-installer.sh +292 -292
- package/.claude/hooks/piper-multispeaker-registry.sh +171 -171
- package/.claude/hooks/piper-voice-manager.sh +24 -3
- package/.claude/hooks/play-tts-agentvibes-receiver-for-voiceless-connections.sh +90 -90
- package/.claude/hooks/play-tts-enhanced.sh +105 -70
- package/.claude/hooks/play-tts-macos.sh +368 -345
- package/.claude/hooks/play-tts-piper.sh +679 -578
- package/.claude/hooks/play-tts-soprano.sh +356 -320
- package/.claude/hooks/play-tts-ssh-remote.sh +167 -88
- package/.claude/hooks/play-tts-termux-ssh.sh +169 -169
- package/.claude/hooks/play-tts.sh +301 -298
- package/.claude/hooks/prepare-release.sh +54 -54
- package/.claude/hooks/provider-commands.sh +617 -617
- package/.claude/hooks/provider-manager.sh +399 -399
- package/.claude/hooks/replay-target-audio.sh +95 -95
- package/.claude/hooks/requirements.txt +6 -6
- package/.claude/hooks/sentiment-manager.sh +201 -201
- package/.claude/hooks/session-start-tts.sh +81 -71
- package/.claude/hooks/soprano-gradio-synth.py +139 -139
- package/.claude/hooks/speed-manager.sh +291 -291
- package/.claude/hooks/stop-tts.sh +84 -0
- package/.claude/hooks/termux-installer.sh +261 -261
- package/.claude/hooks/translate-manager.sh +341 -341
- package/.claude/hooks/translator.py +237 -237
- package/.claude/hooks/tts-queue-worker.sh +145 -114
- package/.claude/hooks/tts-queue.sh +165 -136
- package/.claude/hooks/verbosity-manager.sh +178 -178
- package/.claude/hooks/voice-manager.sh +548 -544
- package/.claude/hooks-windows/audio-cache-utils.ps1 +119 -119
- package/.claude/hooks-windows/background-music-manager.ps1 +348 -0
- package/.claude/hooks-windows/clean-audio-cache.ps1 +53 -0
- package/.claude/hooks-windows/download-extra-voices.ps1 +185 -0
- package/.claude/hooks-windows/effects-manager.ps1 +294 -0
- package/.claude/hooks-windows/language-manager.ps1 +193 -0
- package/.claude/hooks-windows/learn-manager.ps1 +241 -0
- package/.claude/hooks-windows/personality-manager.ps1 +266 -0
- package/.claude/hooks-windows/play-tts-piper.ps1 +209 -0
- package/.claude/hooks-windows/play-tts-sapi.ps1 +108 -0
- package/.claude/hooks-windows/play-tts-soprano.ps1 +159 -158
- package/.claude/hooks-windows/play-tts-windows-piper.ps1 +50 -5
- package/.claude/hooks-windows/play-tts-windows-sapi.ps1 +108 -108
- package/.claude/hooks-windows/play-tts.ps1 +344 -266
- package/.claude/hooks-windows/provider-manager.ps1 +29 -10
- package/.claude/hooks-windows/session-start-tts.ps1 +124 -124
- package/.claude/hooks-windows/soprano-gradio-synth.py +153 -153
- package/.claude/hooks-windows/speed-manager.ps1 +166 -0
- package/.claude/hooks-windows/verbosity-manager.ps1 +119 -0
- package/.claude/hooks-windows/voice-manager-windows.ps1 +92 -8
- package/.claude/output-styles/agent-vibes.md +202 -202
- package/.claude/personalities/angry.md +14 -14
- package/.claude/personalities/annoying.md +14 -14
- package/.claude/personalities/crass.md +14 -14
- package/.claude/personalities/dramatic.md +14 -14
- package/.claude/personalities/dry-humor.md +50 -50
- package/.claude/personalities/flirty.md +20 -20
- package/.claude/personalities/funny.md +14 -14
- package/.claude/personalities/grandpa.md +32 -32
- package/.claude/personalities/millennial.md +14 -14
- package/.claude/personalities/moody.md +14 -14
- package/.claude/personalities/normal.md +16 -16
- package/.claude/personalities/pirate.md +14 -14
- package/.claude/personalities/poetic.md +14 -14
- package/.claude/personalities/professional.md +14 -14
- package/.claude/personalities/rapper.md +55 -55
- package/.claude/personalities/robot.md +14 -14
- package/.claude/personalities/sarcastic.md +38 -38
- package/.claude/personalities/sassy.md +14 -14
- package/.claude/personalities/surfer-dude.md +14 -14
- package/.claude/personalities/zen.md +14 -14
- package/.claude/settings.json +15 -15
- package/.claude/verbosity.txt +1 -1
- package/.clawdbot/README.md +105 -105
- package/.clawdbot/skill/SKILL.md +241 -241
- package/.mcp.json +12 -0
- package/CLAUDE.md +170 -181
- package/README.md +2029 -1909
- package/RELEASE_NOTES.md +1310 -66
- package/WINDOWS-SETUP.md +208 -208
- package/bin/agent-vibes +39 -39
- package/bin/agentvibes-voice-browser.js +1840 -1826
- package/bin/agentvibes.js +48 -2
- package/bin/mcp-server.js +121 -121
- package/bin/mcp-server.sh +206 -206
- 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 +260 -260
- package/mcp-server/docs/troubleshooting-audio.md +313 -313
- 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 +130 -130
- package/mcp-server/pyproject.toml +52 -52
- package/mcp-server/requirements.txt +2 -2
- package/mcp-server/server.py +1465 -1417
- package/mcp-server/test_server.py +395 -395
- package/mcp-server/test_windows_script_parity.py +336 -0
- package/package.json +110 -112
- package/setup-windows.ps1 +815 -815
- package/src/bmad-detector.js +71 -71
- 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 +476 -476
- package/src/console/app.js +824 -806
- package/src/console/audio-env.js +20 -1
- package/src/console/brand-colors.js +13 -13
- package/src/console/constants/personalities.js +44 -0
- package/src/console/footer-config.js +50 -46
- package/src/console/modals/modal-overlay.js +247 -247
- package/src/console/navigation.js +62 -61
- package/src/console/tabs/agents-tab.js +1684 -369
- package/src/console/tabs/help-tab.js +261 -261
- package/src/console/tabs/install-tab.js +1007 -991
- package/src/console/tabs/music-tab.js +22 -8
- package/src/console/tabs/placeholder-tab.js +53 -46
- package/src/console/tabs/readme-tab.js +267 -267
- package/src/console/tabs/receiver-tab.js +1472 -0
- package/src/console/tabs/settings-tab.js +185 -402
- package/src/console/tabs/voices-tab.js +100 -21
- package/src/console/widgets/destroy-list.js +25 -0
- package/src/console/widgets/format-utils.js +89 -0
- package/src/console/widgets/notice.js +55 -0
- package/src/console/widgets/personality-picker.js +185 -0
- package/src/console/widgets/reverb-picker.js +94 -0
- package/src/console/widgets/track-picker.js +285 -0
- package/src/installer/music-file-input.js +304 -304
- package/src/installer.js +5882 -5777
- package/src/services/agent-voice-store.js +423 -163
- package/src/services/config-service.js +264 -264
- package/src/services/navigation-service.js +123 -123
- package/src/services/provider-service.js +132 -132
- package/src/services/verbosity-service.js +157 -157
- package/src/utils/audio-duration-validator.js +298 -298
- package/src/utils/audio-format-validator.js +277 -277
- package/src/utils/dependency-checker.js +469 -466
- package/src/utils/file-ownership-verifier.js +358 -358
- package/src/utils/list-formatter.js +194 -194
- package/src/utils/music-file-validator.js +285 -275
- package/src/utils/preview-list-prompt.js +136 -136
- package/src/utils/provider-validator.js +96 -12
- package/src/utils/secure-music-storage.js +412 -412
- package/templates/agentvibes-receiver.sh +482 -162
- package/templates/audio/welcome-music.mp3 +0 -0
- package/voice-assignments.json +8244 -8244
- package/.claude/config/background-music-position.txt +0 -1
|
@@ -1,399 +1,399 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
#
|
|
3
|
-
# File: .claude/hooks/provider-manager.sh
|
|
4
|
-
#
|
|
5
|
-
# AgentVibes - Finally, your AI Agents can Talk Back! Text-to-Speech WITH personality for AI Assistants!
|
|
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
|
-
# DISCLAIMER: This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
25
|
-
# express or implied, including but not limited to the warranties of
|
|
26
|
-
# merchantability, fitness for a particular purpose and noninfringement.
|
|
27
|
-
# In no event shall the authors or copyright holders be liable for any claim,
|
|
28
|
-
# damages or other liability, whether in an action of contract, tort or
|
|
29
|
-
# otherwise, arising from, out of or in connection with the software or the
|
|
30
|
-
# use or other dealings in the software.
|
|
31
|
-
#
|
|
32
|
-
# ---
|
|
33
|
-
#
|
|
34
|
-
# @fileoverview TTS Provider Management Functions
|
|
35
|
-
# @context Core provider abstraction layer for multi-provider TTS system
|
|
36
|
-
# @architecture Provides functions to get/set/list/validate TTS providers
|
|
37
|
-
# @dependencies None - pure bash implementation
|
|
38
|
-
# @entrypoints Sourced by play-tts.sh and provider management commands
|
|
39
|
-
# @patterns File-based state management with project-local and global fallback
|
|
40
|
-
# @related play-tts.sh, play-tts-piper.sh, provider-commands.sh
|
|
41
|
-
#
|
|
42
|
-
|
|
43
|
-
# @function get_provider_config_path
|
|
44
|
-
# @intent Determine path to tts-provider.txt file
|
|
45
|
-
# @why Supports both project-local (.claude/) and global (~/.claude/) storage
|
|
46
|
-
# @returns Echoes path to provider config file
|
|
47
|
-
# @exitcode 0=always succeeds
|
|
48
|
-
# @sideeffects None
|
|
49
|
-
# @edgecases Creates parent directory if missing
|
|
50
|
-
get_provider_config_path() {
|
|
51
|
-
local provider_file
|
|
52
|
-
|
|
53
|
-
# Check project-local first
|
|
54
|
-
if [[ -n "${CLAUDE_PROJECT_DIR:-}" ]] && [[ -d "$CLAUDE_PROJECT_DIR/.claude" ]]; then
|
|
55
|
-
provider_file="$CLAUDE_PROJECT_DIR/.claude/tts-provider.txt"
|
|
56
|
-
else
|
|
57
|
-
# Search up directory tree for .claude/
|
|
58
|
-
local current_dir="$PWD"
|
|
59
|
-
while [[ "$current_dir" != "/" ]]; do
|
|
60
|
-
if [[ -d "$current_dir/.claude" ]]; then
|
|
61
|
-
provider_file="$current_dir/.claude/tts-provider.txt"
|
|
62
|
-
break
|
|
63
|
-
fi
|
|
64
|
-
current_dir=$(dirname "$current_dir")
|
|
65
|
-
done
|
|
66
|
-
|
|
67
|
-
# Fallback to global if no project .claude found
|
|
68
|
-
if [[ -z "$provider_file" ]]; then
|
|
69
|
-
provider_file="$HOME/.claude/tts-provider.txt"
|
|
70
|
-
fi
|
|
71
|
-
fi
|
|
72
|
-
|
|
73
|
-
echo "$provider_file"
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
# @function get_active_provider
|
|
77
|
-
# @intent Read currently active TTS provider from config file
|
|
78
|
-
# @why Central function for determining which provider to use
|
|
79
|
-
# @returns Echoes provider name (e.g., "piper", "macos")
|
|
80
|
-
# @exitcode 0=success
|
|
81
|
-
# @sideeffects None
|
|
82
|
-
# @edgecases Returns "piper" if file missing or empty (default)
|
|
83
|
-
get_active_provider() {
|
|
84
|
-
local provider_file
|
|
85
|
-
provider_file=$(get_provider_config_path)
|
|
86
|
-
|
|
87
|
-
# Read provider from file, default to piper if not found
|
|
88
|
-
if [[ -f "$provider_file" ]]; then
|
|
89
|
-
local provider
|
|
90
|
-
provider=$(cat "$provider_file" | tr -d '[:space:]')
|
|
91
|
-
if [[ -n "$provider" ]]; then
|
|
92
|
-
echo "$provider"
|
|
93
|
-
return 0
|
|
94
|
-
fi
|
|
95
|
-
fi
|
|
96
|
-
|
|
97
|
-
# Default to piper (free, offline)
|
|
98
|
-
echo "piper"
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
# @function set_active_provider
|
|
102
|
-
# @intent Write active provider to config file
|
|
103
|
-
# @why Allows runtime provider switching without restart
|
|
104
|
-
# @param $1 {string} provider - Provider name (e.g., "piper", "macos")
|
|
105
|
-
# @returns None (outputs success/error message)
|
|
106
|
-
# @exitcode 0=success, 1=invalid provider
|
|
107
|
-
# @sideeffects Writes to tts-provider.txt file
|
|
108
|
-
# @edgecases Creates file and parent directory if missing
|
|
109
|
-
set_active_provider() {
|
|
110
|
-
local provider="$1"
|
|
111
|
-
|
|
112
|
-
if [[ -z "$provider" ]]; then
|
|
113
|
-
echo "❌ Error: Provider name required"
|
|
114
|
-
echo "Usage: set_active_provider <provider_name>"
|
|
115
|
-
return 1
|
|
116
|
-
fi
|
|
117
|
-
|
|
118
|
-
# Validate provider exists
|
|
119
|
-
if ! validate_provider "$provider"; then
|
|
120
|
-
echo "❌ Error: Provider '$provider' not found"
|
|
121
|
-
echo "Available providers:"
|
|
122
|
-
list_providers
|
|
123
|
-
return 1
|
|
124
|
-
fi
|
|
125
|
-
|
|
126
|
-
local provider_file
|
|
127
|
-
provider_file=$(get_provider_config_path)
|
|
128
|
-
|
|
129
|
-
# Create directory if it doesn't exist
|
|
130
|
-
mkdir -p "$(dirname "$provider_file")"
|
|
131
|
-
|
|
132
|
-
# Write provider to file
|
|
133
|
-
echo "$provider" > "$provider_file"
|
|
134
|
-
|
|
135
|
-
# Reset voice when switching providers to avoid incompatible voices
|
|
136
|
-
local voice_file
|
|
137
|
-
if [[ -n "${CLAUDE_PROJECT_DIR:-}" ]] && [[ -d "$CLAUDE_PROJECT_DIR/.claude" ]]; then
|
|
138
|
-
voice_file="$CLAUDE_PROJECT_DIR/.claude/tts-voice.txt"
|
|
139
|
-
else
|
|
140
|
-
voice_file="$HOME/.claude/tts-voice.txt"
|
|
141
|
-
fi
|
|
142
|
-
|
|
143
|
-
# Migrate voice to equivalent in new provider
|
|
144
|
-
local current_voice=""
|
|
145
|
-
if [[ -f "$voice_file" ]]; then
|
|
146
|
-
# Strip only leading/trailing whitespace and newlines, preserve internal spaces
|
|
147
|
-
current_voice=$(cat "$voice_file" | tr -d '\n\r' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
|
|
148
|
-
fi
|
|
149
|
-
|
|
150
|
-
local new_voice
|
|
151
|
-
new_voice=$(migrate_voice_to_provider "$current_voice" "$provider")
|
|
152
|
-
|
|
153
|
-
# Write new voice to file
|
|
154
|
-
echo "$new_voice" > "$voice_file"
|
|
155
|
-
|
|
156
|
-
if [[ -n "$current_voice" ]] && [[ "$current_voice" != "$new_voice" ]]; then
|
|
157
|
-
echo "✓ Active provider set to: $provider"
|
|
158
|
-
echo "🔄 Voice migrated: $current_voice → $new_voice"
|
|
159
|
-
else
|
|
160
|
-
echo "✓ Active provider set to: $provider (voice: $new_voice)"
|
|
161
|
-
fi
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
# @function migrate_voice_to_provider
|
|
165
|
-
# @intent Migrate a voice from one provider to an equivalent in the target provider
|
|
166
|
-
# @why Users shouldn't have to manually reconfigure voices when switching providers
|
|
167
|
-
# @param $1 {string} current_voice - Current voice name (may be from any provider)
|
|
168
|
-
# @param $2 {string} target_provider - Target provider to migrate to
|
|
169
|
-
# @returns Echoes equivalent voice name for target provider
|
|
170
|
-
# @exitcode 0=always succeeds (returns default if no mapping found)
|
|
171
|
-
# @sideeffects None
|
|
172
|
-
# @edgecases Returns provider default if voice not found in mapping table
|
|
173
|
-
migrate_voice_to_provider() {
|
|
174
|
-
local current_voice="$1"
|
|
175
|
-
local target_provider="$2"
|
|
176
|
-
|
|
177
|
-
# Voice mapping table: Piper <-> macOS equivalents
|
|
178
|
-
# Format: "piper_voice:macos_voice"
|
|
179
|
-
local voice_mappings=(
|
|
180
|
-
"en_US-amy-medium:Samantha"
|
|
181
|
-
"en_US-ryan-high:Alex"
|
|
182
|
-
"en_GB-alan-medium:Daniel"
|
|
183
|
-
"en_US-kristin-medium:Victoria"
|
|
184
|
-
"en_US-lessac-medium:Samantha"
|
|
185
|
-
"en_US-joe-medium:Alex"
|
|
186
|
-
"en_US-arctic-medium:Alex"
|
|
187
|
-
"en_US-danny-low:Alex"
|
|
188
|
-
)
|
|
189
|
-
|
|
190
|
-
# Default voices by provider
|
|
191
|
-
local piper_default="en_US-lessac-medium"
|
|
192
|
-
local macos_default="Samantha"
|
|
193
|
-
local soprano_default="soprano-default" # Single voice — no selection needed
|
|
194
|
-
|
|
195
|
-
# Soprano has a single voice, so migration is straightforward
|
|
196
|
-
if [[ "$target_provider" == "soprano" ]]; then
|
|
197
|
-
echo "$soprano_default"
|
|
198
|
-
return 0
|
|
199
|
-
fi
|
|
200
|
-
|
|
201
|
-
# If no current voice, return default for target provider
|
|
202
|
-
if [[ -z "$current_voice" ]]; then
|
|
203
|
-
case "$target_provider" in
|
|
204
|
-
piper) echo "$piper_default" ;;
|
|
205
|
-
macos) echo "$macos_default" ;;
|
|
206
|
-
*) echo "$piper_default" ;;
|
|
207
|
-
esac
|
|
208
|
-
return 0
|
|
209
|
-
fi
|
|
210
|
-
|
|
211
|
-
# If migrating FROM Soprano, return default for target provider
|
|
212
|
-
if [[ "$current_voice" == "soprano-default" ]]; then
|
|
213
|
-
case "$target_provider" in
|
|
214
|
-
piper) echo "$piper_default" ;;
|
|
215
|
-
macos) echo "$macos_default" ;;
|
|
216
|
-
*) echo "$piper_default" ;;
|
|
217
|
-
esac
|
|
218
|
-
return 0
|
|
219
|
-
fi
|
|
220
|
-
|
|
221
|
-
# Convert to lowercase for case-insensitive comparison (portable)
|
|
222
|
-
local current_voice_lower
|
|
223
|
-
current_voice_lower=$(echo "$current_voice" | tr '[:upper:]' '[:lower:]')
|
|
224
|
-
|
|
225
|
-
# Search for mapping
|
|
226
|
-
for mapping in "${voice_mappings[@]}"; do
|
|
227
|
-
# Parse two-part mapping: piper:macos
|
|
228
|
-
local piper_voice="${mapping%%:*}"
|
|
229
|
-
local macos_voice="${mapping#*:}"
|
|
230
|
-
|
|
231
|
-
local piper_voice_lower macos_voice_lower
|
|
232
|
-
piper_voice_lower=$(echo "$piper_voice" | tr '[:upper:]' '[:lower:]')
|
|
233
|
-
macos_voice_lower=$(echo "$macos_voice" | tr '[:upper:]' '[:lower:]')
|
|
234
|
-
|
|
235
|
-
case "$target_provider" in
|
|
236
|
-
piper)
|
|
237
|
-
# Switching to Piper: look for macOS voice match
|
|
238
|
-
if [[ "$current_voice_lower" == "$macos_voice_lower" ]]; then
|
|
239
|
-
echo "$piper_voice"
|
|
240
|
-
return 0
|
|
241
|
-
fi
|
|
242
|
-
# Already a Piper voice? Keep it if valid format
|
|
243
|
-
if [[ "$current_voice" =~ ^[a-z]{2}_ ]]; then
|
|
244
|
-
echo "$current_voice"
|
|
245
|
-
return 0
|
|
246
|
-
fi
|
|
247
|
-
;;
|
|
248
|
-
macos)
|
|
249
|
-
# Switching to macOS: look for Piper voice match
|
|
250
|
-
if [[ "$current_voice_lower" == "$piper_voice_lower" ]]; then
|
|
251
|
-
echo "$macos_voice"
|
|
252
|
-
return 0
|
|
253
|
-
fi
|
|
254
|
-
# Already a macOS voice? Keep it
|
|
255
|
-
# macOS voices are typically single capitalized words
|
|
256
|
-
if [[ "$current_voice" =~ ^[A-Z][a-z]+$ ]]; then
|
|
257
|
-
echo "$current_voice"
|
|
258
|
-
return 0
|
|
259
|
-
fi
|
|
260
|
-
;;
|
|
261
|
-
esac
|
|
262
|
-
done
|
|
263
|
-
|
|
264
|
-
# No mapping found - return default for target provider
|
|
265
|
-
case "$target_provider" in
|
|
266
|
-
piper) echo "$piper_default" ;;
|
|
267
|
-
macos) echo "$macos_default" ;;
|
|
268
|
-
*) echo "$piper_default" ;;
|
|
269
|
-
esac
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
# @function list_providers
|
|
273
|
-
# @intent List all available TTS providers
|
|
274
|
-
# @why Discover which providers are installed
|
|
275
|
-
# @returns Echoes provider names (one per line)
|
|
276
|
-
# @exitcode 0=success
|
|
277
|
-
# @sideeffects None
|
|
278
|
-
# @edgecases Returns empty if no play-tts-*.sh files found
|
|
279
|
-
list_providers() {
|
|
280
|
-
local script_dir
|
|
281
|
-
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
282
|
-
|
|
283
|
-
# Find all play-tts-*.sh files
|
|
284
|
-
local providers=()
|
|
285
|
-
shopt -s nullglob # Handle case where no files match
|
|
286
|
-
for file in "$script_dir"/play-tts-*.sh; do
|
|
287
|
-
if [[ -f "$file" ]] && [[ "$file" != *"play-tts.sh" ]]; then
|
|
288
|
-
# Extract provider name from filename (play-tts-piper.sh -> piper)
|
|
289
|
-
local basename
|
|
290
|
-
basename=$(basename "$file")
|
|
291
|
-
local provider
|
|
292
|
-
provider="${basename#play-tts-}"
|
|
293
|
-
provider="${provider%.sh}"
|
|
294
|
-
providers+=("$provider")
|
|
295
|
-
fi
|
|
296
|
-
done
|
|
297
|
-
shopt -u nullglob
|
|
298
|
-
|
|
299
|
-
# Output providers
|
|
300
|
-
if [[ ${#providers[@]} -eq 0 ]]; then
|
|
301
|
-
echo "⚠️ No providers found"
|
|
302
|
-
return 0
|
|
303
|
-
fi
|
|
304
|
-
|
|
305
|
-
for provider in "${providers[@]}"; do
|
|
306
|
-
echo "$provider"
|
|
307
|
-
done
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
# @function validate_provider
|
|
311
|
-
# @intent Check if provider implementation exists
|
|
312
|
-
# @why Prevent errors from switching to non-existent provider
|
|
313
|
-
# @param $1 {string} provider - Provider name to validate
|
|
314
|
-
# @returns None
|
|
315
|
-
# @exitcode 0=provider exists, 1=provider not found
|
|
316
|
-
# @sideeffects None
|
|
317
|
-
# @edgecases Checks for corresponding play-tts-*.sh file
|
|
318
|
-
validate_provider() {
|
|
319
|
-
local provider="$1"
|
|
320
|
-
|
|
321
|
-
if [[ -z "$provider" ]]; then
|
|
322
|
-
return 1
|
|
323
|
-
fi
|
|
324
|
-
|
|
325
|
-
local script_dir
|
|
326
|
-
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
327
|
-
local provider_script="$script_dir/play-tts-${provider}.sh"
|
|
328
|
-
|
|
329
|
-
[[ -f "$provider_script" ]]
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
# @function get_provider_script_path
|
|
333
|
-
# @intent Get absolute path to provider implementation script
|
|
334
|
-
# @why Used by router to execute provider-specific logic
|
|
335
|
-
# @param $1 {string} provider - Provider name
|
|
336
|
-
# @returns Echoes absolute path to play-tts-*.sh file
|
|
337
|
-
# @exitcode 0=success, 1=provider not found
|
|
338
|
-
# @sideeffects None
|
|
339
|
-
get_provider_script_path() {
|
|
340
|
-
local provider="$1"
|
|
341
|
-
|
|
342
|
-
if [[ -z "$provider" ]]; then
|
|
343
|
-
echo "❌ Error: Provider name required" >&2
|
|
344
|
-
return 1
|
|
345
|
-
fi
|
|
346
|
-
|
|
347
|
-
local script_dir
|
|
348
|
-
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
349
|
-
local provider_script="$script_dir/play-tts-${provider}.sh"
|
|
350
|
-
|
|
351
|
-
if [[ ! -f "$provider_script" ]]; then
|
|
352
|
-
echo "❌ Error: Provider '$provider' not found at $provider_script" >&2
|
|
353
|
-
return 1
|
|
354
|
-
fi
|
|
355
|
-
|
|
356
|
-
echo "$provider_script"
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
# AI NOTE: This file provides the core abstraction layer for multi-provider TTS.
|
|
360
|
-
# All provider state is managed through simple text files for simplicity and reliability.
|
|
361
|
-
# Project-local configuration takes precedence over global to support per-project providers.
|
|
362
|
-
|
|
363
|
-
# Command-line interface (when script is executed, not sourced)
|
|
364
|
-
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
|
365
|
-
case "${1:-}" in
|
|
366
|
-
get)
|
|
367
|
-
get_active_provider
|
|
368
|
-
;;
|
|
369
|
-
switch|set)
|
|
370
|
-
if [[ -z "${2:-}" ]]; then
|
|
371
|
-
echo "❌ Error: Provider name required"
|
|
372
|
-
echo "Usage: $0 switch <provider>"
|
|
373
|
-
exit 1
|
|
374
|
-
fi
|
|
375
|
-
set_active_provider "$2"
|
|
376
|
-
;;
|
|
377
|
-
list)
|
|
378
|
-
list_providers
|
|
379
|
-
;;
|
|
380
|
-
validate)
|
|
381
|
-
if [[ -z "${2:-}" ]]; then
|
|
382
|
-
echo "❌ Error: Provider name required"
|
|
383
|
-
echo "Usage: $0 validate <provider>"
|
|
384
|
-
exit 1
|
|
385
|
-
fi
|
|
386
|
-
validate_provider "$2"
|
|
387
|
-
;;
|
|
388
|
-
*)
|
|
389
|
-
echo "Usage: $0 {get|switch|list|validate} [provider]"
|
|
390
|
-
echo ""
|
|
391
|
-
echo "Commands:"
|
|
392
|
-
echo " get - Show active provider"
|
|
393
|
-
echo " switch <name> - Switch to provider"
|
|
394
|
-
echo " list - List available providers"
|
|
395
|
-
echo " validate <name> - Check if provider exists"
|
|
396
|
-
exit 1
|
|
397
|
-
;;
|
|
398
|
-
esac
|
|
399
|
-
fi
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# File: .claude/hooks/provider-manager.sh
|
|
4
|
+
#
|
|
5
|
+
# AgentVibes - Finally, your AI Agents can Talk Back! Text-to-Speech WITH personality for AI Assistants!
|
|
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
|
+
# DISCLAIMER: This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
25
|
+
# express or implied, including but not limited to the warranties of
|
|
26
|
+
# merchantability, fitness for a particular purpose and noninfringement.
|
|
27
|
+
# In no event shall the authors or copyright holders be liable for any claim,
|
|
28
|
+
# damages or other liability, whether in an action of contract, tort or
|
|
29
|
+
# otherwise, arising from, out of or in connection with the software or the
|
|
30
|
+
# use or other dealings in the software.
|
|
31
|
+
#
|
|
32
|
+
# ---
|
|
33
|
+
#
|
|
34
|
+
# @fileoverview TTS Provider Management Functions
|
|
35
|
+
# @context Core provider abstraction layer for multi-provider TTS system
|
|
36
|
+
# @architecture Provides functions to get/set/list/validate TTS providers
|
|
37
|
+
# @dependencies None - pure bash implementation
|
|
38
|
+
# @entrypoints Sourced by play-tts.sh and provider management commands
|
|
39
|
+
# @patterns File-based state management with project-local and global fallback
|
|
40
|
+
# @related play-tts.sh, play-tts-piper.sh, provider-commands.sh
|
|
41
|
+
#
|
|
42
|
+
|
|
43
|
+
# @function get_provider_config_path
|
|
44
|
+
# @intent Determine path to tts-provider.txt file
|
|
45
|
+
# @why Supports both project-local (.claude/) and global (~/.claude/) storage
|
|
46
|
+
# @returns Echoes path to provider config file
|
|
47
|
+
# @exitcode 0=always succeeds
|
|
48
|
+
# @sideeffects None
|
|
49
|
+
# @edgecases Creates parent directory if missing
|
|
50
|
+
get_provider_config_path() {
|
|
51
|
+
local provider_file
|
|
52
|
+
|
|
53
|
+
# Check project-local first
|
|
54
|
+
if [[ -n "${CLAUDE_PROJECT_DIR:-}" ]] && [[ -d "$CLAUDE_PROJECT_DIR/.claude" ]]; then
|
|
55
|
+
provider_file="$CLAUDE_PROJECT_DIR/.claude/tts-provider.txt"
|
|
56
|
+
else
|
|
57
|
+
# Search up directory tree for .claude/
|
|
58
|
+
local current_dir="$PWD"
|
|
59
|
+
while [[ "$current_dir" != "/" ]]; do
|
|
60
|
+
if [[ -d "$current_dir/.claude" ]]; then
|
|
61
|
+
provider_file="$current_dir/.claude/tts-provider.txt"
|
|
62
|
+
break
|
|
63
|
+
fi
|
|
64
|
+
current_dir=$(dirname "$current_dir")
|
|
65
|
+
done
|
|
66
|
+
|
|
67
|
+
# Fallback to global if no project .claude found
|
|
68
|
+
if [[ -z "$provider_file" ]]; then
|
|
69
|
+
provider_file="$HOME/.claude/tts-provider.txt"
|
|
70
|
+
fi
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
echo "$provider_file"
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
# @function get_active_provider
|
|
77
|
+
# @intent Read currently active TTS provider from config file
|
|
78
|
+
# @why Central function for determining which provider to use
|
|
79
|
+
# @returns Echoes provider name (e.g., "piper", "macos")
|
|
80
|
+
# @exitcode 0=success
|
|
81
|
+
# @sideeffects None
|
|
82
|
+
# @edgecases Returns "piper" if file missing or empty (default)
|
|
83
|
+
get_active_provider() {
|
|
84
|
+
local provider_file
|
|
85
|
+
provider_file=$(get_provider_config_path)
|
|
86
|
+
|
|
87
|
+
# Read provider from file, default to piper if not found
|
|
88
|
+
if [[ -f "$provider_file" ]]; then
|
|
89
|
+
local provider
|
|
90
|
+
provider=$(cat "$provider_file" | tr -d '[:space:]')
|
|
91
|
+
if [[ -n "$provider" ]]; then
|
|
92
|
+
echo "$provider"
|
|
93
|
+
return 0
|
|
94
|
+
fi
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
# Default to piper (free, offline)
|
|
98
|
+
echo "piper"
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
# @function set_active_provider
|
|
102
|
+
# @intent Write active provider to config file
|
|
103
|
+
# @why Allows runtime provider switching without restart
|
|
104
|
+
# @param $1 {string} provider - Provider name (e.g., "piper", "macos")
|
|
105
|
+
# @returns None (outputs success/error message)
|
|
106
|
+
# @exitcode 0=success, 1=invalid provider
|
|
107
|
+
# @sideeffects Writes to tts-provider.txt file
|
|
108
|
+
# @edgecases Creates file and parent directory if missing
|
|
109
|
+
set_active_provider() {
|
|
110
|
+
local provider="$1"
|
|
111
|
+
|
|
112
|
+
if [[ -z "$provider" ]]; then
|
|
113
|
+
echo "❌ Error: Provider name required"
|
|
114
|
+
echo "Usage: set_active_provider <provider_name>"
|
|
115
|
+
return 1
|
|
116
|
+
fi
|
|
117
|
+
|
|
118
|
+
# Validate provider exists
|
|
119
|
+
if ! validate_provider "$provider"; then
|
|
120
|
+
echo "❌ Error: Provider '$provider' not found"
|
|
121
|
+
echo "Available providers:"
|
|
122
|
+
list_providers
|
|
123
|
+
return 1
|
|
124
|
+
fi
|
|
125
|
+
|
|
126
|
+
local provider_file
|
|
127
|
+
provider_file=$(get_provider_config_path)
|
|
128
|
+
|
|
129
|
+
# Create directory if it doesn't exist
|
|
130
|
+
mkdir -p "$(dirname "$provider_file")"
|
|
131
|
+
|
|
132
|
+
# Write provider to file
|
|
133
|
+
echo "$provider" > "$provider_file"
|
|
134
|
+
|
|
135
|
+
# Reset voice when switching providers to avoid incompatible voices
|
|
136
|
+
local voice_file
|
|
137
|
+
if [[ -n "${CLAUDE_PROJECT_DIR:-}" ]] && [[ -d "$CLAUDE_PROJECT_DIR/.claude" ]]; then
|
|
138
|
+
voice_file="$CLAUDE_PROJECT_DIR/.claude/tts-voice.txt"
|
|
139
|
+
else
|
|
140
|
+
voice_file="$HOME/.claude/tts-voice.txt"
|
|
141
|
+
fi
|
|
142
|
+
|
|
143
|
+
# Migrate voice to equivalent in new provider
|
|
144
|
+
local current_voice=""
|
|
145
|
+
if [[ -f "$voice_file" ]]; then
|
|
146
|
+
# Strip only leading/trailing whitespace and newlines, preserve internal spaces
|
|
147
|
+
current_voice=$(cat "$voice_file" | tr -d '\n\r' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
|
|
148
|
+
fi
|
|
149
|
+
|
|
150
|
+
local new_voice
|
|
151
|
+
new_voice=$(migrate_voice_to_provider "$current_voice" "$provider")
|
|
152
|
+
|
|
153
|
+
# Write new voice to file
|
|
154
|
+
echo "$new_voice" > "$voice_file"
|
|
155
|
+
|
|
156
|
+
if [[ -n "$current_voice" ]] && [[ "$current_voice" != "$new_voice" ]]; then
|
|
157
|
+
echo "✓ Active provider set to: $provider"
|
|
158
|
+
echo "🔄 Voice migrated: $current_voice → $new_voice"
|
|
159
|
+
else
|
|
160
|
+
echo "✓ Active provider set to: $provider (voice: $new_voice)"
|
|
161
|
+
fi
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
# @function migrate_voice_to_provider
|
|
165
|
+
# @intent Migrate a voice from one provider to an equivalent in the target provider
|
|
166
|
+
# @why Users shouldn't have to manually reconfigure voices when switching providers
|
|
167
|
+
# @param $1 {string} current_voice - Current voice name (may be from any provider)
|
|
168
|
+
# @param $2 {string} target_provider - Target provider to migrate to
|
|
169
|
+
# @returns Echoes equivalent voice name for target provider
|
|
170
|
+
# @exitcode 0=always succeeds (returns default if no mapping found)
|
|
171
|
+
# @sideeffects None
|
|
172
|
+
# @edgecases Returns provider default if voice not found in mapping table
|
|
173
|
+
migrate_voice_to_provider() {
|
|
174
|
+
local current_voice="$1"
|
|
175
|
+
local target_provider="$2"
|
|
176
|
+
|
|
177
|
+
# Voice mapping table: Piper <-> macOS equivalents
|
|
178
|
+
# Format: "piper_voice:macos_voice"
|
|
179
|
+
local voice_mappings=(
|
|
180
|
+
"en_US-amy-medium:Samantha"
|
|
181
|
+
"en_US-ryan-high:Alex"
|
|
182
|
+
"en_GB-alan-medium:Daniel"
|
|
183
|
+
"en_US-kristin-medium:Victoria"
|
|
184
|
+
"en_US-lessac-medium:Samantha"
|
|
185
|
+
"en_US-joe-medium:Alex"
|
|
186
|
+
"en_US-arctic-medium:Alex"
|
|
187
|
+
"en_US-danny-low:Alex"
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
# Default voices by provider
|
|
191
|
+
local piper_default="en_US-lessac-medium"
|
|
192
|
+
local macos_default="Samantha"
|
|
193
|
+
local soprano_default="soprano-default" # Single voice — no selection needed
|
|
194
|
+
|
|
195
|
+
# Soprano has a single voice, so migration is straightforward
|
|
196
|
+
if [[ "$target_provider" == "soprano" ]]; then
|
|
197
|
+
echo "$soprano_default"
|
|
198
|
+
return 0
|
|
199
|
+
fi
|
|
200
|
+
|
|
201
|
+
# If no current voice, return default for target provider
|
|
202
|
+
if [[ -z "$current_voice" ]]; then
|
|
203
|
+
case "$target_provider" in
|
|
204
|
+
piper) echo "$piper_default" ;;
|
|
205
|
+
macos) echo "$macos_default" ;;
|
|
206
|
+
*) echo "$piper_default" ;;
|
|
207
|
+
esac
|
|
208
|
+
return 0
|
|
209
|
+
fi
|
|
210
|
+
|
|
211
|
+
# If migrating FROM Soprano, return default for target provider
|
|
212
|
+
if [[ "$current_voice" == "soprano-default" ]]; then
|
|
213
|
+
case "$target_provider" in
|
|
214
|
+
piper) echo "$piper_default" ;;
|
|
215
|
+
macos) echo "$macos_default" ;;
|
|
216
|
+
*) echo "$piper_default" ;;
|
|
217
|
+
esac
|
|
218
|
+
return 0
|
|
219
|
+
fi
|
|
220
|
+
|
|
221
|
+
# Convert to lowercase for case-insensitive comparison (portable)
|
|
222
|
+
local current_voice_lower
|
|
223
|
+
current_voice_lower=$(echo "$current_voice" | tr '[:upper:]' '[:lower:]')
|
|
224
|
+
|
|
225
|
+
# Search for mapping
|
|
226
|
+
for mapping in "${voice_mappings[@]}"; do
|
|
227
|
+
# Parse two-part mapping: piper:macos
|
|
228
|
+
local piper_voice="${mapping%%:*}"
|
|
229
|
+
local macos_voice="${mapping#*:}"
|
|
230
|
+
|
|
231
|
+
local piper_voice_lower macos_voice_lower
|
|
232
|
+
piper_voice_lower=$(echo "$piper_voice" | tr '[:upper:]' '[:lower:]')
|
|
233
|
+
macos_voice_lower=$(echo "$macos_voice" | tr '[:upper:]' '[:lower:]')
|
|
234
|
+
|
|
235
|
+
case "$target_provider" in
|
|
236
|
+
piper)
|
|
237
|
+
# Switching to Piper: look for macOS voice match
|
|
238
|
+
if [[ "$current_voice_lower" == "$macos_voice_lower" ]]; then
|
|
239
|
+
echo "$piper_voice"
|
|
240
|
+
return 0
|
|
241
|
+
fi
|
|
242
|
+
# Already a Piper voice? Keep it if valid format
|
|
243
|
+
if [[ "$current_voice" =~ ^[a-z]{2}_ ]]; then
|
|
244
|
+
echo "$current_voice"
|
|
245
|
+
return 0
|
|
246
|
+
fi
|
|
247
|
+
;;
|
|
248
|
+
macos)
|
|
249
|
+
# Switching to macOS: look for Piper voice match
|
|
250
|
+
if [[ "$current_voice_lower" == "$piper_voice_lower" ]]; then
|
|
251
|
+
echo "$macos_voice"
|
|
252
|
+
return 0
|
|
253
|
+
fi
|
|
254
|
+
# Already a macOS voice? Keep it
|
|
255
|
+
# macOS voices are typically single capitalized words
|
|
256
|
+
if [[ "$current_voice" =~ ^[A-Z][a-z]+$ ]]; then
|
|
257
|
+
echo "$current_voice"
|
|
258
|
+
return 0
|
|
259
|
+
fi
|
|
260
|
+
;;
|
|
261
|
+
esac
|
|
262
|
+
done
|
|
263
|
+
|
|
264
|
+
# No mapping found - return default for target provider
|
|
265
|
+
case "$target_provider" in
|
|
266
|
+
piper) echo "$piper_default" ;;
|
|
267
|
+
macos) echo "$macos_default" ;;
|
|
268
|
+
*) echo "$piper_default" ;;
|
|
269
|
+
esac
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
# @function list_providers
|
|
273
|
+
# @intent List all available TTS providers
|
|
274
|
+
# @why Discover which providers are installed
|
|
275
|
+
# @returns Echoes provider names (one per line)
|
|
276
|
+
# @exitcode 0=success
|
|
277
|
+
# @sideeffects None
|
|
278
|
+
# @edgecases Returns empty if no play-tts-*.sh files found
|
|
279
|
+
list_providers() {
|
|
280
|
+
local script_dir
|
|
281
|
+
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
282
|
+
|
|
283
|
+
# Find all play-tts-*.sh files
|
|
284
|
+
local providers=()
|
|
285
|
+
shopt -s nullglob # Handle case where no files match
|
|
286
|
+
for file in "$script_dir"/play-tts-*.sh; do
|
|
287
|
+
if [[ -f "$file" ]] && [[ "$file" != *"play-tts.sh" ]]; then
|
|
288
|
+
# Extract provider name from filename (play-tts-piper.sh -> piper)
|
|
289
|
+
local basename
|
|
290
|
+
basename=$(basename "$file")
|
|
291
|
+
local provider
|
|
292
|
+
provider="${basename#play-tts-}"
|
|
293
|
+
provider="${provider%.sh}"
|
|
294
|
+
providers+=("$provider")
|
|
295
|
+
fi
|
|
296
|
+
done
|
|
297
|
+
shopt -u nullglob
|
|
298
|
+
|
|
299
|
+
# Output providers
|
|
300
|
+
if [[ ${#providers[@]} -eq 0 ]]; then
|
|
301
|
+
echo "⚠️ No providers found"
|
|
302
|
+
return 0
|
|
303
|
+
fi
|
|
304
|
+
|
|
305
|
+
for provider in "${providers[@]}"; do
|
|
306
|
+
echo "$provider"
|
|
307
|
+
done
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
# @function validate_provider
|
|
311
|
+
# @intent Check if provider implementation exists
|
|
312
|
+
# @why Prevent errors from switching to non-existent provider
|
|
313
|
+
# @param $1 {string} provider - Provider name to validate
|
|
314
|
+
# @returns None
|
|
315
|
+
# @exitcode 0=provider exists, 1=provider not found
|
|
316
|
+
# @sideeffects None
|
|
317
|
+
# @edgecases Checks for corresponding play-tts-*.sh file
|
|
318
|
+
validate_provider() {
|
|
319
|
+
local provider="$1"
|
|
320
|
+
|
|
321
|
+
if [[ -z "$provider" ]]; then
|
|
322
|
+
return 1
|
|
323
|
+
fi
|
|
324
|
+
|
|
325
|
+
local script_dir
|
|
326
|
+
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
327
|
+
local provider_script="$script_dir/play-tts-${provider}.sh"
|
|
328
|
+
|
|
329
|
+
[[ -f "$provider_script" ]]
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
# @function get_provider_script_path
|
|
333
|
+
# @intent Get absolute path to provider implementation script
|
|
334
|
+
# @why Used by router to execute provider-specific logic
|
|
335
|
+
# @param $1 {string} provider - Provider name
|
|
336
|
+
# @returns Echoes absolute path to play-tts-*.sh file
|
|
337
|
+
# @exitcode 0=success, 1=provider not found
|
|
338
|
+
# @sideeffects None
|
|
339
|
+
get_provider_script_path() {
|
|
340
|
+
local provider="$1"
|
|
341
|
+
|
|
342
|
+
if [[ -z "$provider" ]]; then
|
|
343
|
+
echo "❌ Error: Provider name required" >&2
|
|
344
|
+
return 1
|
|
345
|
+
fi
|
|
346
|
+
|
|
347
|
+
local script_dir
|
|
348
|
+
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
349
|
+
local provider_script="$script_dir/play-tts-${provider}.sh"
|
|
350
|
+
|
|
351
|
+
if [[ ! -f "$provider_script" ]]; then
|
|
352
|
+
echo "❌ Error: Provider '$provider' not found at $provider_script" >&2
|
|
353
|
+
return 1
|
|
354
|
+
fi
|
|
355
|
+
|
|
356
|
+
echo "$provider_script"
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
# AI NOTE: This file provides the core abstraction layer for multi-provider TTS.
|
|
360
|
+
# All provider state is managed through simple text files for simplicity and reliability.
|
|
361
|
+
# Project-local configuration takes precedence over global to support per-project providers.
|
|
362
|
+
|
|
363
|
+
# Command-line interface (when script is executed, not sourced)
|
|
364
|
+
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
|
365
|
+
case "${1:-}" in
|
|
366
|
+
get)
|
|
367
|
+
get_active_provider
|
|
368
|
+
;;
|
|
369
|
+
switch|set)
|
|
370
|
+
if [[ -z "${2:-}" ]]; then
|
|
371
|
+
echo "❌ Error: Provider name required"
|
|
372
|
+
echo "Usage: $0 switch <provider>"
|
|
373
|
+
exit 1
|
|
374
|
+
fi
|
|
375
|
+
set_active_provider "$2"
|
|
376
|
+
;;
|
|
377
|
+
list)
|
|
378
|
+
list_providers
|
|
379
|
+
;;
|
|
380
|
+
validate)
|
|
381
|
+
if [[ -z "${2:-}" ]]; then
|
|
382
|
+
echo "❌ Error: Provider name required"
|
|
383
|
+
echo "Usage: $0 validate <provider>"
|
|
384
|
+
exit 1
|
|
385
|
+
fi
|
|
386
|
+
validate_provider "$2"
|
|
387
|
+
;;
|
|
388
|
+
*)
|
|
389
|
+
echo "Usage: $0 {get|switch|list|validate} [provider]"
|
|
390
|
+
echo ""
|
|
391
|
+
echo "Commands:"
|
|
392
|
+
echo " get - Show active provider"
|
|
393
|
+
echo " switch <name> - Switch to provider"
|
|
394
|
+
echo " list - List available providers"
|
|
395
|
+
echo " validate <name> - Check if provider exists"
|
|
396
|
+
exit 1
|
|
397
|
+
;;
|
|
398
|
+
esac
|
|
399
|
+
fi
|