agentvibes 2.0.3 → 2.0.5
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/provider.md +54 -0
- package/.claude/hooks/piper-download-voices.sh +133 -0
- package/.claude/hooks/piper-voice-manager.sh +194 -0
- package/.claude/hooks/play-tts-elevenlabs.sh +201 -0
- package/.claude/hooks/play-tts-piper.sh +175 -0
- package/.claude/hooks/play-tts.sh.backup-20251005-163851 +138 -0
- package/.claude/hooks/provider-commands.sh +374 -0
- package/.claude/hooks/provider-manager.sh +196 -0
- package/.claude/language-voices.yaml +372 -0
- package/.claude/piper-voices/en_US-lessac-medium.onnx +0 -0
- package/.claude/piper-voices/en_US-lessac-medium.onnx.json +493 -0
- package/.mcp-minimal.json +53 -0
- package/agentvibes.org/.claude/commands/agent-vibes/add.md +21 -0
- package/agentvibes.org/.claude/commands/agent-vibes/agent-vibes.md +68 -0
- package/agentvibes.org/.claude/commands/agent-vibes/commands.json +53 -0
- package/agentvibes.org/.claude/commands/agent-vibes/get.md +9 -0
- package/agentvibes.org/.claude/commands/agent-vibes/list.md +13 -0
- package/agentvibes.org/.claude/commands/agent-vibes/personality.md +79 -0
- package/agentvibes.org/.claude/commands/agent-vibes/preview.md +16 -0
- package/agentvibes.org/.claude/commands/agent-vibes/provider.md +54 -0
- package/agentvibes.org/.claude/commands/agent-vibes/replay.md +19 -0
- package/agentvibes.org/.claude/commands/agent-vibes/sample.md +12 -0
- package/agentvibes.org/.claude/commands/agent-vibes/sentiment.md +52 -0
- package/agentvibes.org/.claude/commands/agent-vibes/set-language.md +47 -0
- package/agentvibes.org/.claude/commands/agent-vibes/set-pretext.md +65 -0
- package/agentvibes.org/.claude/commands/agent-vibes/switch.md +53 -0
- package/agentvibes.org/.claude/commands/agent-vibes/update.md +20 -0
- package/agentvibes.org/.claude/commands/agent-vibes/version.md +10 -0
- package/agentvibes.org/.claude/commands/agent-vibes/whoami.md +7 -0
- package/agentvibes.org/.claude/journal/2025-10-07.html +373 -0
- package/agentvibes.org/.claude/journal/index.html +91 -0
- package/agentvibes.org/.mcp-minimal.json +60 -0
- package/agentvibes.org/CHANGELOG.md +56 -0
- package/agentvibes.org/README.md +93 -0
- package/agentvibes.org/app/(auth)/layout.tsx +15 -0
- package/agentvibes.org/app/(auth)/reset-password/page.tsx +45 -0
- package/agentvibes.org/app/(auth)/signin/page.tsx +82 -0
- package/agentvibes.org/app/(auth)/signup/page.tsx +104 -0
- package/agentvibes.org/app/(default)/layout.tsx +31 -0
- package/agentvibes.org/app/(default)/page.tsx +20 -0
- package/agentvibes.org/app/api/hello/route.ts +3 -0
- package/agentvibes.org/app/css/additional-styles/theme.css +82 -0
- package/agentvibes.org/app/css/additional-styles/utility-patterns.css +55 -0
- package/agentvibes.org/app/css/style.css +100 -0
- package/agentvibes.org/app/layout.tsx +63 -0
- package/agentvibes.org/components/cta.tsx +58 -0
- package/agentvibes.org/components/features.tsx +256 -0
- package/agentvibes.org/components/hero-home.tsx +133 -0
- package/agentvibes.org/components/modal-video.tsx +137 -0
- package/agentvibes.org/components/page-illustration.tsx +55 -0
- package/agentvibes.org/components/spotlight.tsx +77 -0
- package/agentvibes.org/components/testimonials.tsx +282 -0
- package/agentvibes.org/components/ui/footer.tsx +82 -0
- package/agentvibes.org/components/ui/header.tsx +53 -0
- package/agentvibes.org/components/ui/logo.tsx +10 -0
- package/agentvibes.org/components/workflows.tsx +176 -0
- package/agentvibes.org/next.config.js +4 -0
- package/agentvibes.org/package-lock.json +1974 -0
- package/agentvibes.org/package.json +30 -0
- package/agentvibes.org/pnpm-lock.yaml +1141 -0
- package/agentvibes.org/postcss.config.js +5 -0
- package/agentvibes.org/public/audio/02-sarcastic.mp3 +0 -0
- package/agentvibes.org/public/audio/03-angry.mp3 +0 -0
- package/agentvibes.org/public/audio/04-grandpa.mp3 +0 -0
- package/agentvibes.org/public/audio/05-sarcastic-example2.mp3 +0 -0
- package/agentvibes.org/public/audio/french-rachel.mp3 +0 -0
- package/agentvibes.org/public/audio/spanish-antoni.mp3 +0 -0
- package/agentvibes.org/public/favicon.ico +0 -0
- package/agentvibes.org/public/fonts/nacelle-italic.woff2 +0 -0
- package/agentvibes.org/public/fonts/nacelle-regular.woff2 +0 -0
- package/agentvibes.org/public/fonts/nacelle-semibold.woff2 +0 -0
- package/agentvibes.org/public/fonts/nacelle-semibolditalic.woff2 +0 -0
- package/agentvibes.org/public/images/blurred-shape-gray.svg +1 -0
- package/agentvibes.org/public/images/blurred-shape.svg +1 -0
- package/agentvibes.org/public/images/client-logo-01.svg +1 -0
- package/agentvibes.org/public/images/client-logo-02.svg +1 -0
- package/agentvibes.org/public/images/client-logo-03.svg +1 -0
- package/agentvibes.org/public/images/client-logo-04.svg +1 -0
- package/agentvibes.org/public/images/client-logo-05.svg +1 -0
- package/agentvibes.org/public/images/client-logo-06.svg +1 -0
- package/agentvibes.org/public/images/client-logo-07.svg +1 -0
- package/agentvibes.org/public/images/client-logo-08.svg +1 -0
- package/agentvibes.org/public/images/client-logo-09.svg +1 -0
- package/agentvibes.org/public/images/features.png +0 -0
- package/agentvibes.org/public/images/footer-illustration.svg +1 -0
- package/agentvibes.org/public/images/hero-image-01.jpg +0 -0
- package/agentvibes.org/public/images/logo.svg +1 -0
- package/agentvibes.org/public/images/page-illustration.svg +1 -0
- package/agentvibes.org/public/images/secondary-illustration.svg +1 -0
- package/agentvibes.org/public/images/testimonial-01.jpg +0 -0
- package/agentvibes.org/public/images/testimonial-02.jpg +0 -0
- package/agentvibes.org/public/images/testimonial-03.jpg +0 -0
- package/agentvibes.org/public/images/testimonial-04.jpg +0 -0
- package/agentvibes.org/public/images/testimonial-05.jpg +0 -0
- package/agentvibes.org/public/images/testimonial-06.jpg +0 -0
- package/agentvibes.org/public/images/testimonial-07.jpg +0 -0
- package/agentvibes.org/public/images/testimonial-08.jpg +0 -0
- package/agentvibes.org/public/images/testimonial-09.jpg +0 -0
- package/agentvibes.org/public/images/workflow-01.png +0 -0
- package/agentvibes.org/public/images/workflow-02.png +0 -0
- package/agentvibes.org/public/images/workflow-03.png +0 -0
- package/agentvibes.org/public/videos/video.mp4 +0 -0
- package/agentvibes.org/tsconfig.json +28 -0
- package/agentvibes.org/utils/useMasonry.tsx +67 -0
- package/agentvibes.org/utils/useMousePosition.tsx +27 -0
- package/docs/ai-optimized-documentation-standards.md +306 -0
- package/docs/architecture/provider-system.md +574 -0
- package/docs/voice-mapping-format.md +218 -0
- package/package.json +1 -1
- package/scripts/piper-voice/README.md +145 -0
- package/scripts/piper-voice/wsl-install.sh +193 -0
- package/src/installer.js +4 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Manage TTS providers (list, switch, info, test)
|
|
3
|
+
argument-hint: [command] [args...]
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Provider Management Commands
|
|
7
|
+
|
|
8
|
+
Manage TTS providers (ElevenLabs, Piper) - switch between providers, view details, and test.
|
|
9
|
+
|
|
10
|
+
## Usage
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
/agent-vibes:provider list # Show all available providers
|
|
14
|
+
/agent-vibes:provider switch <name> # Switch to a different provider
|
|
15
|
+
/agent-vibes:provider info <name> # Show detailed provider information
|
|
16
|
+
/agent-vibes:provider test # Test current provider
|
|
17
|
+
/agent-vibes:provider get # Show current active provider
|
|
18
|
+
/agent-vibes:provider help # Show this help
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Examples
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# List available providers
|
|
25
|
+
/agent-vibes:provider list
|
|
26
|
+
|
|
27
|
+
# Switch to Piper (free, offline)
|
|
28
|
+
/agent-vibes:provider switch piper
|
|
29
|
+
|
|
30
|
+
# Switch to ElevenLabs (premium quality)
|
|
31
|
+
/agent-vibes:provider switch elevenlabs
|
|
32
|
+
|
|
33
|
+
# Get info about a provider
|
|
34
|
+
/agent-vibes:provider info piper
|
|
35
|
+
|
|
36
|
+
# Test current provider
|
|
37
|
+
/agent-vibes:provider test
|
|
38
|
+
|
|
39
|
+
# Show current provider
|
|
40
|
+
/agent-vibes:provider get
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Provider Comparison
|
|
44
|
+
|
|
45
|
+
| Feature | ElevenLabs | Piper |
|
|
46
|
+
|---------|------------|-------|
|
|
47
|
+
| Quality | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
|
|
48
|
+
| Cost | Free tier + $5-22/mo | Free forever |
|
|
49
|
+
| Offline | No | Yes |
|
|
50
|
+
| Platform | All | WSL/Linux only |
|
|
51
|
+
|
|
52
|
+
Learn more: agentvibes.org/providers
|
|
53
|
+
|
|
54
|
+
!bash .claude/hooks/provider-commands.sh $ARGUMENTS
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# @fileoverview Piper Voice Model Downloader
|
|
4
|
+
# @context Downloads Piper TTS voice models from HuggingFace
|
|
5
|
+
# @purpose Batch download popular voices after installation
|
|
6
|
+
# @dependencies piper-voice-manager.sh, piper binary
|
|
7
|
+
# @usage ./piper-download-voices.sh [--yes|-y]
|
|
8
|
+
# --yes|-y: Skip confirmation prompt (auto-download)
|
|
9
|
+
#
|
|
10
|
+
|
|
11
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
12
|
+
source "$SCRIPT_DIR/piper-voice-manager.sh"
|
|
13
|
+
|
|
14
|
+
# Parse command line arguments
|
|
15
|
+
AUTO_YES=false
|
|
16
|
+
if [[ "$1" == "--yes" ]] || [[ "$1" == "-y" ]]; then
|
|
17
|
+
AUTO_YES=true
|
|
18
|
+
fi
|
|
19
|
+
|
|
20
|
+
# Common voice models to download
|
|
21
|
+
COMMON_VOICES=(
|
|
22
|
+
"en_US-lessac-medium" # Default, clear male
|
|
23
|
+
"en_US-amy-medium" # Warm female
|
|
24
|
+
"en_US-joe-medium" # Professional male
|
|
25
|
+
"en_US-ryan-high" # Expressive male
|
|
26
|
+
"en_US-libritts-high" # Premium quality
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
echo "🎙️ Piper Voice Model Downloader"
|
|
30
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
31
|
+
echo ""
|
|
32
|
+
echo "This will download the most commonly used Piper voice models."
|
|
33
|
+
echo "Each voice is approximately 25MB."
|
|
34
|
+
echo ""
|
|
35
|
+
|
|
36
|
+
# Check if piper is installed
|
|
37
|
+
if ! command -v piper &> /dev/null; then
|
|
38
|
+
echo "❌ Error: Piper TTS not installed"
|
|
39
|
+
echo "Install with: pipx install piper-tts"
|
|
40
|
+
exit 1
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
# Get storage directory
|
|
44
|
+
VOICE_DIR=$(get_voice_storage_dir)
|
|
45
|
+
|
|
46
|
+
echo "📂 Storage location: $VOICE_DIR"
|
|
47
|
+
echo ""
|
|
48
|
+
|
|
49
|
+
# Count already downloaded
|
|
50
|
+
ALREADY_DOWNLOADED=0
|
|
51
|
+
ALREADY_DOWNLOADED_LIST=()
|
|
52
|
+
NEED_DOWNLOAD=()
|
|
53
|
+
|
|
54
|
+
for voice in "${COMMON_VOICES[@]}"; do
|
|
55
|
+
if verify_voice "$voice" 2>/dev/null; then
|
|
56
|
+
((ALREADY_DOWNLOADED++))
|
|
57
|
+
ALREADY_DOWNLOADED_LIST+=("$voice")
|
|
58
|
+
else
|
|
59
|
+
NEED_DOWNLOAD+=("$voice")
|
|
60
|
+
fi
|
|
61
|
+
done
|
|
62
|
+
|
|
63
|
+
echo "📊 Status:"
|
|
64
|
+
echo " Already downloaded: $ALREADY_DOWNLOADED voice(s)"
|
|
65
|
+
echo " Need to download: ${#NEED_DOWNLOAD[@]} voice(s)"
|
|
66
|
+
echo ""
|
|
67
|
+
|
|
68
|
+
# Show already downloaded voices
|
|
69
|
+
if [[ $ALREADY_DOWNLOADED -gt 0 ]]; then
|
|
70
|
+
echo "✅ Already downloaded (skipped):"
|
|
71
|
+
for voice in "${ALREADY_DOWNLOADED_LIST[@]}"; do
|
|
72
|
+
echo " ✓ $voice"
|
|
73
|
+
done
|
|
74
|
+
echo ""
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
if [[ ${#NEED_DOWNLOAD[@]} -eq 0 ]]; then
|
|
78
|
+
echo "🎉 All common voices ready to use!"
|
|
79
|
+
exit 0
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
echo "Voices to download:"
|
|
83
|
+
for voice in "${NEED_DOWNLOAD[@]}"; do
|
|
84
|
+
echo " • $voice (~25MB)"
|
|
85
|
+
done
|
|
86
|
+
echo ""
|
|
87
|
+
|
|
88
|
+
# Ask for confirmation (skip if --yes flag provided)
|
|
89
|
+
if [[ "$AUTO_YES" == "false" ]]; then
|
|
90
|
+
read -p "Download ${#NEED_DOWNLOAD[@]} voice model(s)? [Y/n]: " -n 1 -r
|
|
91
|
+
echo
|
|
92
|
+
|
|
93
|
+
if [[ ! $REPLY =~ ^[Yy]$ ]] && [[ -n $REPLY ]]; then
|
|
94
|
+
echo "❌ Download cancelled"
|
|
95
|
+
exit 0
|
|
96
|
+
fi
|
|
97
|
+
else
|
|
98
|
+
echo "Auto-downloading ${#NEED_DOWNLOAD[@]} voice model(s)..."
|
|
99
|
+
echo ""
|
|
100
|
+
fi
|
|
101
|
+
|
|
102
|
+
# Download each voice
|
|
103
|
+
DOWNLOADED=0
|
|
104
|
+
FAILED=0
|
|
105
|
+
|
|
106
|
+
for voice in "${NEED_DOWNLOAD[@]}"; do
|
|
107
|
+
echo ""
|
|
108
|
+
echo "📥 Downloading: $voice..."
|
|
109
|
+
|
|
110
|
+
if download_voice "$voice"; then
|
|
111
|
+
((DOWNLOADED++))
|
|
112
|
+
echo "✅ Downloaded: $voice"
|
|
113
|
+
else
|
|
114
|
+
((FAILED++))
|
|
115
|
+
echo "❌ Failed: $voice"
|
|
116
|
+
fi
|
|
117
|
+
done
|
|
118
|
+
|
|
119
|
+
echo ""
|
|
120
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
121
|
+
echo "📊 Download Summary:"
|
|
122
|
+
echo " ✅ Successfully downloaded: $DOWNLOADED"
|
|
123
|
+
echo " ❌ Failed: $FAILED"
|
|
124
|
+
echo " 📦 Total voices available: $((ALREADY_DOWNLOADED + DOWNLOADED))"
|
|
125
|
+
echo ""
|
|
126
|
+
|
|
127
|
+
if [[ $DOWNLOADED -gt 0 ]]; then
|
|
128
|
+
echo "✨ Ready to use Piper TTS with downloaded voices!"
|
|
129
|
+
echo ""
|
|
130
|
+
echo "Try it:"
|
|
131
|
+
echo " /agent-vibes:provider switch piper"
|
|
132
|
+
echo " /agent-vibes:preview"
|
|
133
|
+
fi
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# @fileoverview Piper Voice Model Management
|
|
4
|
+
# @context Manages downloading, caching, and validating Piper ONNX voice models
|
|
5
|
+
# @architecture Voice model lifecycle management for Piper provider
|
|
6
|
+
# @dependencies curl, piper binary
|
|
7
|
+
# @entrypoints Sourced by play-tts-piper.sh and provider management commands
|
|
8
|
+
# @patterns HuggingFace model repository integration, file-based caching
|
|
9
|
+
# @related play-tts-piper.sh, provider-manager.sh, GitHub Issue #25
|
|
10
|
+
#
|
|
11
|
+
|
|
12
|
+
# Base URL for Piper voice models on HuggingFace
|
|
13
|
+
PIPER_VOICES_BASE_URL="https://huggingface.co/rhasspy/piper-voices/resolve/main"
|
|
14
|
+
|
|
15
|
+
# @function get_voice_storage_dir
|
|
16
|
+
# @intent Determine directory for storing Piper voice models
|
|
17
|
+
# @why Voice models are large (~25MB each) and should be shared globally across all projects
|
|
18
|
+
# @returns Echoes path to voice storage directory (~/.claude/piper-voices)
|
|
19
|
+
# @sideeffects Creates directory if it doesn't exist
|
|
20
|
+
# @architecture Always uses global storage to avoid redundant downloads per project
|
|
21
|
+
get_voice_storage_dir() {
|
|
22
|
+
# Always use global storage for voice models
|
|
23
|
+
# This prevents downloading 125MB+ of models into every project
|
|
24
|
+
local voice_dir="$HOME/.claude/piper-voices"
|
|
25
|
+
|
|
26
|
+
mkdir -p "$voice_dir"
|
|
27
|
+
echo "$voice_dir"
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
# @function verify_voice
|
|
31
|
+
# @intent Check if voice model files exist locally
|
|
32
|
+
# @why Avoid redundant downloads, detect missing models
|
|
33
|
+
# @param $1 {string} voice_name - Voice model name (e.g., en_US-lessac-medium)
|
|
34
|
+
# @returns None
|
|
35
|
+
# @exitcode 0=voice exists, 1=voice missing
|
|
36
|
+
# @sideeffects None
|
|
37
|
+
verify_voice() {
|
|
38
|
+
local voice_name="$1"
|
|
39
|
+
local voice_dir
|
|
40
|
+
voice_dir=$(get_voice_storage_dir)
|
|
41
|
+
|
|
42
|
+
local onnx_file="$voice_dir/${voice_name}.onnx"
|
|
43
|
+
local json_file="$voice_dir/${voice_name}.onnx.json"
|
|
44
|
+
|
|
45
|
+
[[ -f "$onnx_file" ]] && [[ -f "$json_file" ]]
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
# @function get_voice_path
|
|
49
|
+
# @intent Get absolute path to voice model ONNX file
|
|
50
|
+
# @why Piper binary requires full path to model file
|
|
51
|
+
# @param $1 {string} voice_name - Voice model name
|
|
52
|
+
# @returns Echoes path to .onnx file
|
|
53
|
+
# @exitcode 0=success, 1=voice not found
|
|
54
|
+
# @sideeffects None
|
|
55
|
+
get_voice_path() {
|
|
56
|
+
local voice_name="$1"
|
|
57
|
+
local voice_dir
|
|
58
|
+
voice_dir=$(get_voice_storage_dir)
|
|
59
|
+
|
|
60
|
+
local onnx_file="$voice_dir/${voice_name}.onnx"
|
|
61
|
+
|
|
62
|
+
if [[ ! -f "$onnx_file" ]]; then
|
|
63
|
+
echo "❌ Voice model not found: $voice_name" >&2
|
|
64
|
+
return 1
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
echo "$onnx_file"
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
# @function parse_voice_components
|
|
71
|
+
# @intent Extract language, locale, speaker, quality from voice name
|
|
72
|
+
# @why HuggingFace uses directory structure: lang/locale/speaker/quality
|
|
73
|
+
# @param $1 {string} voice_name - Voice name (e.g., en_US-lessac-medium)
|
|
74
|
+
# @returns Sets global variables: LANG, LOCALE, SPEAKER, QUALITY
|
|
75
|
+
# @sideeffects Sets global variables
|
|
76
|
+
# AI NOTE: Voice name format is: lang_LOCALE-speaker-quality
|
|
77
|
+
parse_voice_components() {
|
|
78
|
+
local voice_name="$1"
|
|
79
|
+
|
|
80
|
+
# Extract components from voice name
|
|
81
|
+
# Format: en_US-lessac-medium
|
|
82
|
+
# lang_LOCALE-speaker-quality
|
|
83
|
+
|
|
84
|
+
local lang_locale="${voice_name%%-*}" # en_US
|
|
85
|
+
local speaker_quality="${voice_name#*-}" # lessac-medium
|
|
86
|
+
|
|
87
|
+
LANG="${lang_locale%%_*}" # en
|
|
88
|
+
LOCALE="${lang_locale#*_}" # US
|
|
89
|
+
SPEAKER="${speaker_quality%%-*}" # lessac
|
|
90
|
+
QUALITY="${speaker_quality#*-}" # medium
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
# @function download_voice
|
|
94
|
+
# @intent Download Piper voice model from HuggingFace
|
|
95
|
+
# @why Provide free offline TTS voices
|
|
96
|
+
# @param $1 {string} voice_name - Voice model name
|
|
97
|
+
# @param $2 {string} lang_code - Language code (optional, inferred from voice_name)
|
|
98
|
+
# @returns None
|
|
99
|
+
# @exitcode 0=success, 1=download failed
|
|
100
|
+
# @sideeffects Downloads .onnx and .onnx.json files
|
|
101
|
+
# @edgecases Handles network failures, validates file integrity
|
|
102
|
+
download_voice() {
|
|
103
|
+
local voice_name="$1"
|
|
104
|
+
local lang_code="${2:-}"
|
|
105
|
+
|
|
106
|
+
local voice_dir
|
|
107
|
+
voice_dir=$(get_voice_storage_dir)
|
|
108
|
+
|
|
109
|
+
# Check if already downloaded
|
|
110
|
+
if verify_voice "$voice_name"; then
|
|
111
|
+
echo "✅ Voice already downloaded: $voice_name"
|
|
112
|
+
return 0
|
|
113
|
+
fi
|
|
114
|
+
|
|
115
|
+
# Parse voice components
|
|
116
|
+
parse_voice_components "$voice_name"
|
|
117
|
+
|
|
118
|
+
# Construct download URLs
|
|
119
|
+
# Path format: {language}/{language}_{locale}/{speaker}/{quality}/{speaker}-{quality}.onnx
|
|
120
|
+
local model_path="${LANG}/${LANG}_${LOCALE}/${SPEAKER}/${QUALITY}/${voice_name}"
|
|
121
|
+
local onnx_url="${PIPER_VOICES_BASE_URL}/${model_path}.onnx"
|
|
122
|
+
local json_url="${PIPER_VOICES_BASE_URL}/${model_path}.onnx.json"
|
|
123
|
+
|
|
124
|
+
echo "📥 Downloading Piper voice: $voice_name"
|
|
125
|
+
echo " Source: HuggingFace (rhasspy/piper-voices)"
|
|
126
|
+
echo " Size: ~25MB"
|
|
127
|
+
echo ""
|
|
128
|
+
|
|
129
|
+
# Download ONNX model
|
|
130
|
+
echo " Downloading model file..."
|
|
131
|
+
if ! curl -L --progress-bar -o "$voice_dir/${voice_name}.onnx" "$onnx_url"; then
|
|
132
|
+
echo "❌ Failed to download voice model"
|
|
133
|
+
rm -f "$voice_dir/${voice_name}.onnx"
|
|
134
|
+
return 1
|
|
135
|
+
fi
|
|
136
|
+
|
|
137
|
+
# Download JSON config
|
|
138
|
+
echo " Downloading config file..."
|
|
139
|
+
if ! curl -L -s -o "$voice_dir/${voice_name}.onnx.json" "$json_url"; then
|
|
140
|
+
echo "❌ Failed to download voice config"
|
|
141
|
+
rm -f "$voice_dir/${voice_name}.onnx" "$voice_dir/${voice_name}.onnx.json"
|
|
142
|
+
return 1
|
|
143
|
+
fi
|
|
144
|
+
|
|
145
|
+
# Verify file integrity (basic check - file size > 0)
|
|
146
|
+
if [[ ! -s "$voice_dir/${voice_name}.onnx" ]]; then
|
|
147
|
+
echo "❌ Downloaded file is empty or corrupt"
|
|
148
|
+
rm -f "$voice_dir/${voice_name}.onnx" "$voice_dir/${voice_name}.onnx.json"
|
|
149
|
+
return 1
|
|
150
|
+
fi
|
|
151
|
+
|
|
152
|
+
echo "✅ Voice downloaded successfully: $voice_name"
|
|
153
|
+
echo " Location: $voice_dir/${voice_name}.onnx"
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
# @function list_downloaded_voices
|
|
157
|
+
# @intent Show all locally cached voice models
|
|
158
|
+
# @why Help users see what voices they have available
|
|
159
|
+
# @returns Echoes voice names (one per line)
|
|
160
|
+
# @exitcode 0=success
|
|
161
|
+
# @sideeffects None
|
|
162
|
+
list_downloaded_voices() {
|
|
163
|
+
local voice_dir
|
|
164
|
+
voice_dir=$(get_voice_storage_dir)
|
|
165
|
+
|
|
166
|
+
echo "📦 Downloaded Piper Voices:"
|
|
167
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
168
|
+
|
|
169
|
+
local count=0
|
|
170
|
+
shopt -s nullglob
|
|
171
|
+
for onnx_file in "$voice_dir"/*.onnx; do
|
|
172
|
+
if [[ -f "$onnx_file" ]]; then
|
|
173
|
+
local voice_name
|
|
174
|
+
voice_name=$(basename "$onnx_file" .onnx)
|
|
175
|
+
local file_size
|
|
176
|
+
file_size=$(du -h "$onnx_file" | cut -f1)
|
|
177
|
+
echo " • $voice_name ($file_size)"
|
|
178
|
+
((count++))
|
|
179
|
+
fi
|
|
180
|
+
done
|
|
181
|
+
shopt -u nullglob
|
|
182
|
+
|
|
183
|
+
if [[ $count -eq 0 ]]; then
|
|
184
|
+
echo " (No voices downloaded yet)"
|
|
185
|
+
fi
|
|
186
|
+
|
|
187
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
188
|
+
echo "Total: $count voices"
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
# AI NOTE: This file manages the lifecycle of Piper voice models
|
|
192
|
+
# Voice models are ONNX files (~20-30MB each) downloaded from HuggingFace
|
|
193
|
+
# Files are cached locally to avoid repeated downloads
|
|
194
|
+
# Project-local storage preferred over global for isolation
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# @fileoverview ElevenLabs TTS Provider Implementation
|
|
4
|
+
# @context Provider-specific implementation for ElevenLabs API integration
|
|
5
|
+
# @architecture Part of multi-provider TTS system - implements provider interface
|
|
6
|
+
# @dependencies Requires ELEVENLABS_API_KEY, curl, ffmpeg, paplay/aplay/mpg123, jq
|
|
7
|
+
# @entrypoints Called by play-tts.sh router with ($1=text, $2=voice_name)
|
|
8
|
+
# @patterns Follows provider contract: accept text/voice, output audio file path
|
|
9
|
+
# @related play-tts.sh, provider-manager.sh, GitHub Issue #25
|
|
10
|
+
#
|
|
11
|
+
|
|
12
|
+
# Fix locale warnings
|
|
13
|
+
export LC_ALL=C
|
|
14
|
+
|
|
15
|
+
TEXT="$1"
|
|
16
|
+
VOICE_OVERRIDE="$2" # Optional: voice name or direct voice ID
|
|
17
|
+
API_KEY="${ELEVENLABS_API_KEY}"
|
|
18
|
+
|
|
19
|
+
# Check for project-local pretext configuration
|
|
20
|
+
CONFIG_DIR="${CLAUDE_PROJECT_DIR:-.}/.claude/config"
|
|
21
|
+
CONFIG_FILE="$CONFIG_DIR/agentvibes.json"
|
|
22
|
+
|
|
23
|
+
if [[ -f "$CONFIG_FILE" ]] && command -v jq &> /dev/null; then
|
|
24
|
+
PRETEXT=$(jq -r '.pretext // empty' "$CONFIG_FILE" 2>/dev/null)
|
|
25
|
+
if [[ -n "$PRETEXT" ]]; then
|
|
26
|
+
TEXT="$PRETEXT: $TEXT"
|
|
27
|
+
fi
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
# Limit text length to prevent API issues (max 500 chars for safety)
|
|
31
|
+
if [ ${#TEXT} -gt 500 ]; then
|
|
32
|
+
TEXT="${TEXT:0:497}..."
|
|
33
|
+
echo "⚠️ Text truncated to 500 characters for API safety"
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
# Source the single voice configuration file
|
|
37
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
38
|
+
source "$SCRIPT_DIR/voices-config.sh"
|
|
39
|
+
source "$SCRIPT_DIR/language-manager.sh"
|
|
40
|
+
|
|
41
|
+
# @function determine_voice_and_language
|
|
42
|
+
# @intent Resolve voice name/ID and language for multilingual support
|
|
43
|
+
# @why Supports both voice names and direct IDs, plus language-specific voices
|
|
44
|
+
# @param $VOICE_OVERRIDE {string} Voice name or ID (optional)
|
|
45
|
+
# @returns Sets $VOICE_ID and $LANGUAGE_CODE global variables
|
|
46
|
+
# @sideeffects None
|
|
47
|
+
# @edgecases Handles unknown voices, falls back to default
|
|
48
|
+
VOICE_ID=""
|
|
49
|
+
LANGUAGE_CODE="en" # Default to English
|
|
50
|
+
|
|
51
|
+
# Get current language setting
|
|
52
|
+
CURRENT_LANGUAGE=$(get_current_language)
|
|
53
|
+
|
|
54
|
+
# Get language code for API
|
|
55
|
+
LANGUAGE_CODE=$(get_language_code_for_name "$CURRENT_LANGUAGE")
|
|
56
|
+
[[ -z "$LANGUAGE_CODE" ]] && LANGUAGE_CODE="en"
|
|
57
|
+
|
|
58
|
+
if [[ -n "$VOICE_OVERRIDE" ]]; then
|
|
59
|
+
# Check if override is a voice name (lookup in mapping)
|
|
60
|
+
if [[ -n "${VOICES[$VOICE_OVERRIDE]}" ]]; then
|
|
61
|
+
VOICE_ID="${VOICES[$VOICE_OVERRIDE]}"
|
|
62
|
+
echo "🎤 Using voice: $VOICE_OVERRIDE (session-specific)"
|
|
63
|
+
# Check if override looks like a voice ID (alphanumeric string ~20 chars)
|
|
64
|
+
elif [[ "$VOICE_OVERRIDE" =~ ^[a-zA-Z0-9]{15,30}$ ]]; then
|
|
65
|
+
VOICE_ID="$VOICE_OVERRIDE"
|
|
66
|
+
echo "🎤 Using custom voice ID (session-specific)"
|
|
67
|
+
else
|
|
68
|
+
echo "⚠️ Unknown voice '$VOICE_OVERRIDE', trying language-specific voice"
|
|
69
|
+
fi
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
# If no override or invalid override, use language-specific voice
|
|
73
|
+
if [[ -z "$VOICE_ID" ]]; then
|
|
74
|
+
# Try to get voice for current language
|
|
75
|
+
LANG_VOICE=$(get_voice_for_language "$CURRENT_LANGUAGE" "elevenlabs" 2>/dev/null)
|
|
76
|
+
|
|
77
|
+
if [[ -n "$LANG_VOICE" ]] && [[ -n "${VOICES[$LANG_VOICE]}" ]]; then
|
|
78
|
+
VOICE_ID="${VOICES[$LANG_VOICE]}"
|
|
79
|
+
echo "🌍 Using $CURRENT_LANGUAGE voice: $LANG_VOICE"
|
|
80
|
+
else
|
|
81
|
+
# Fall back to voice manager
|
|
82
|
+
VOICE_MANAGER_SCRIPT="$(dirname "$0")/voice-manager.sh"
|
|
83
|
+
if [[ -f "$VOICE_MANAGER_SCRIPT" ]]; then
|
|
84
|
+
VOICE_NAME=$("$VOICE_MANAGER_SCRIPT" get)
|
|
85
|
+
VOICE_ID="${VOICES[$VOICE_NAME]}"
|
|
86
|
+
fi
|
|
87
|
+
|
|
88
|
+
# Final fallback to default
|
|
89
|
+
if [[ -z "$VOICE_ID" ]]; then
|
|
90
|
+
echo "⚠️ No voice configured, using default"
|
|
91
|
+
VOICE_ID="${VOICES[Aria]}"
|
|
92
|
+
fi
|
|
93
|
+
fi
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
# @function validate_inputs
|
|
97
|
+
# @intent Check required parameters and API key
|
|
98
|
+
# @why Fail fast with clear errors if inputs missing
|
|
99
|
+
# @exitcode 1=missing text, 2=missing API key
|
|
100
|
+
if [ -z "$TEXT" ]; then
|
|
101
|
+
echo "Usage: $0 \"text to speak\" [voice_name_or_id]"
|
|
102
|
+
exit 1
|
|
103
|
+
fi
|
|
104
|
+
|
|
105
|
+
if [ -z "$API_KEY" ]; then
|
|
106
|
+
echo "Error: ELEVENLABS_API_KEY not set"
|
|
107
|
+
echo "Set your API key: export ELEVENLABS_API_KEY=your_key_here"
|
|
108
|
+
exit 2
|
|
109
|
+
fi
|
|
110
|
+
|
|
111
|
+
# @function determine_audio_directory
|
|
112
|
+
# @intent Find appropriate directory for audio file storage
|
|
113
|
+
# @why Supports project-local and global storage
|
|
114
|
+
# @returns Sets $AUDIO_DIR global variable
|
|
115
|
+
# @sideeffects None
|
|
116
|
+
# @edgecases Handles missing directories, creates if needed
|
|
117
|
+
# AI NOTE: Check project dir first, then search up tree, finally fall back to global
|
|
118
|
+
if [[ -n "$CLAUDE_PROJECT_DIR" ]]; then
|
|
119
|
+
AUDIO_DIR="$CLAUDE_PROJECT_DIR/.claude/audio"
|
|
120
|
+
else
|
|
121
|
+
# Fallback: try to find .claude directory in current path
|
|
122
|
+
CURRENT_DIR="$PWD"
|
|
123
|
+
while [[ "$CURRENT_DIR" != "/" ]]; do
|
|
124
|
+
if [[ -d "$CURRENT_DIR/.claude" ]]; then
|
|
125
|
+
AUDIO_DIR="$CURRENT_DIR/.claude/audio"
|
|
126
|
+
break
|
|
127
|
+
fi
|
|
128
|
+
CURRENT_DIR=$(dirname "$CURRENT_DIR")
|
|
129
|
+
done
|
|
130
|
+
# Final fallback to global if no project .claude found
|
|
131
|
+
if [[ -z "$AUDIO_DIR" ]]; then
|
|
132
|
+
AUDIO_DIR="$HOME/.claude/audio"
|
|
133
|
+
fi
|
|
134
|
+
fi
|
|
135
|
+
|
|
136
|
+
mkdir -p "$AUDIO_DIR"
|
|
137
|
+
TEMP_FILE="$AUDIO_DIR/tts-$(date +%s).mp3"
|
|
138
|
+
|
|
139
|
+
# @function synthesize_with_elevenlabs
|
|
140
|
+
# @intent Call ElevenLabs API to generate speech
|
|
141
|
+
# @why Encapsulates API call with error handling
|
|
142
|
+
# @param Uses globals: $TEXT, $VOICE_ID, $API_KEY
|
|
143
|
+
# @returns Creates audio file at $TEMP_FILE
|
|
144
|
+
# @exitcode 0=success, 3=API error
|
|
145
|
+
# @sideeffects Creates MP3 file in audio directory
|
|
146
|
+
# @edgecases Handles network failures, API errors, rate limiting
|
|
147
|
+
# Choose model based on language
|
|
148
|
+
if [[ "$LANGUAGE_CODE" == "en" ]]; then
|
|
149
|
+
MODEL_ID="eleven_monolingual_v1"
|
|
150
|
+
else
|
|
151
|
+
MODEL_ID="eleven_multilingual_v2"
|
|
152
|
+
fi
|
|
153
|
+
|
|
154
|
+
curl -s -X POST "https://api.elevenlabs.io/v1/text-to-speech/${VOICE_ID}" \
|
|
155
|
+
-H "xi-api-key: ${API_KEY}" \
|
|
156
|
+
-H "Content-Type: application/json" \
|
|
157
|
+
-d "{\"text\":\"${TEXT}\",\"model_id\":\"${MODEL_ID}\",\"language_code\":\"${LANGUAGE_CODE}\",\"voice_settings\":{\"stability\":0.5,\"similarity_boost\":0.75}}" \
|
|
158
|
+
-o "${TEMP_FILE}"
|
|
159
|
+
|
|
160
|
+
# @function add_silence_padding
|
|
161
|
+
# @intent Add silence to beginning of audio to prevent WSL static
|
|
162
|
+
# @why WSL audio subsystem cuts off first ~200ms, causing static/clipping
|
|
163
|
+
# @param Uses global: $TEMP_FILE
|
|
164
|
+
# @returns Updates $TEMP_FILE to padded version
|
|
165
|
+
# @sideeffects Modifies audio file, removes original
|
|
166
|
+
# @edgecases Gracefully falls back to unpadded if ffmpeg unavailable
|
|
167
|
+
# Add silence padding to prevent WSL audio static
|
|
168
|
+
if [ -f "${TEMP_FILE}" ]; then
|
|
169
|
+
# Check if ffmpeg is available for adding padding
|
|
170
|
+
if command -v ffmpeg &> /dev/null; then
|
|
171
|
+
PADDED_FILE="$AUDIO_DIR/tts-padded-$(date +%s).mp3"
|
|
172
|
+
# Add 200ms of silence at the beginning to prevent static
|
|
173
|
+
ffmpeg -f lavfi -i anullsrc=r=44100:cl=stereo:d=0.2 -i "${TEMP_FILE}" \
|
|
174
|
+
-filter_complex "[0:a][1:a]concat=n=2:v=0:a=1[out]" \
|
|
175
|
+
-map "[out]" -y "${PADDED_FILE}" 2>/dev/null
|
|
176
|
+
|
|
177
|
+
if [ -f "${PADDED_FILE}" ]; then
|
|
178
|
+
# Use padded file and clean up original
|
|
179
|
+
rm -f "${TEMP_FILE}"
|
|
180
|
+
TEMP_FILE="${PADDED_FILE}"
|
|
181
|
+
fi
|
|
182
|
+
# If padding failed, just use original file
|
|
183
|
+
fi
|
|
184
|
+
|
|
185
|
+
# @function play_audio
|
|
186
|
+
# @intent Play generated audio file using available player
|
|
187
|
+
# @why Support multiple audio players (paplay, aplay, mpg123)
|
|
188
|
+
# @param Uses global: $TEMP_FILE
|
|
189
|
+
# @sideeffects Plays audio in background
|
|
190
|
+
# @edgecases Falls through players until one works
|
|
191
|
+
# Play audio (WSL/Linux) in background to avoid blocking
|
|
192
|
+
(paplay "${TEMP_FILE}" 2>/dev/null || aplay "${TEMP_FILE}" 2>/dev/null || mpg123 "${TEMP_FILE}" 2>/dev/null) &
|
|
193
|
+
|
|
194
|
+
# Keep temp files for later review - cleaned up weekly by cron
|
|
195
|
+
echo "🎵 Saved to: ${TEMP_FILE}"
|
|
196
|
+
echo "🎤 Voice used: ${VOICE_NAME} (${VOICE_ID})"
|
|
197
|
+
else
|
|
198
|
+
echo "❌ Failed to generate audio - API may be unavailable"
|
|
199
|
+
echo "Check your API key and network connection"
|
|
200
|
+
exit 3
|
|
201
|
+
fi
|