agentvibes 2.0.1 → 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.
Files changed (113) hide show
  1. package/.claude/commands/agent-vibes/provider.md +54 -0
  2. package/.claude/hooks/piper-download-voices.sh +133 -0
  3. package/.claude/hooks/piper-voice-manager.sh +194 -0
  4. package/.claude/hooks/play-tts-elevenlabs.sh +201 -0
  5. package/.claude/hooks/play-tts-piper.sh +175 -0
  6. package/.claude/hooks/play-tts.sh.backup-20251005-163851 +138 -0
  7. package/.claude/hooks/provider-commands.sh +374 -0
  8. package/.claude/hooks/provider-manager.sh +196 -0
  9. package/.claude/language-voices.yaml +372 -0
  10. package/.claude/piper-voices/en_US-lessac-medium.onnx +0 -0
  11. package/.claude/piper-voices/en_US-lessac-medium.onnx.json +493 -0
  12. package/.mcp-minimal.json +53 -0
  13. package/README.md +2 -2
  14. package/agentvibes.org/.claude/commands/agent-vibes/add.md +21 -0
  15. package/agentvibes.org/.claude/commands/agent-vibes/agent-vibes.md +68 -0
  16. package/agentvibes.org/.claude/commands/agent-vibes/commands.json +53 -0
  17. package/agentvibes.org/.claude/commands/agent-vibes/get.md +9 -0
  18. package/agentvibes.org/.claude/commands/agent-vibes/list.md +13 -0
  19. package/agentvibes.org/.claude/commands/agent-vibes/personality.md +79 -0
  20. package/agentvibes.org/.claude/commands/agent-vibes/preview.md +16 -0
  21. package/agentvibes.org/.claude/commands/agent-vibes/provider.md +54 -0
  22. package/agentvibes.org/.claude/commands/agent-vibes/replay.md +19 -0
  23. package/agentvibes.org/.claude/commands/agent-vibes/sample.md +12 -0
  24. package/agentvibes.org/.claude/commands/agent-vibes/sentiment.md +52 -0
  25. package/agentvibes.org/.claude/commands/agent-vibes/set-language.md +47 -0
  26. package/agentvibes.org/.claude/commands/agent-vibes/set-pretext.md +65 -0
  27. package/agentvibes.org/.claude/commands/agent-vibes/switch.md +53 -0
  28. package/agentvibes.org/.claude/commands/agent-vibes/update.md +20 -0
  29. package/agentvibes.org/.claude/commands/agent-vibes/version.md +10 -0
  30. package/agentvibes.org/.claude/commands/agent-vibes/whoami.md +7 -0
  31. package/agentvibes.org/.claude/journal/2025-10-07.html +373 -0
  32. package/agentvibes.org/.claude/journal/index.html +91 -0
  33. package/agentvibes.org/.mcp-minimal.json +60 -0
  34. package/agentvibes.org/CHANGELOG.md +56 -0
  35. package/agentvibes.org/README.md +93 -0
  36. package/agentvibes.org/app/(auth)/layout.tsx +15 -0
  37. package/agentvibes.org/app/(auth)/reset-password/page.tsx +45 -0
  38. package/agentvibes.org/app/(auth)/signin/page.tsx +82 -0
  39. package/agentvibes.org/app/(auth)/signup/page.tsx +104 -0
  40. package/agentvibes.org/app/(default)/layout.tsx +31 -0
  41. package/agentvibes.org/app/(default)/page.tsx +20 -0
  42. package/agentvibes.org/app/api/hello/route.ts +3 -0
  43. package/agentvibes.org/app/css/additional-styles/theme.css +82 -0
  44. package/agentvibes.org/app/css/additional-styles/utility-patterns.css +55 -0
  45. package/agentvibes.org/app/css/style.css +100 -0
  46. package/agentvibes.org/app/layout.tsx +63 -0
  47. package/agentvibes.org/components/cta.tsx +58 -0
  48. package/agentvibes.org/components/features.tsx +256 -0
  49. package/agentvibes.org/components/hero-home.tsx +133 -0
  50. package/agentvibes.org/components/modal-video.tsx +137 -0
  51. package/agentvibes.org/components/page-illustration.tsx +55 -0
  52. package/agentvibes.org/components/spotlight.tsx +77 -0
  53. package/agentvibes.org/components/testimonials.tsx +282 -0
  54. package/agentvibes.org/components/ui/footer.tsx +82 -0
  55. package/agentvibes.org/components/ui/header.tsx +53 -0
  56. package/agentvibes.org/components/ui/logo.tsx +10 -0
  57. package/agentvibes.org/components/workflows.tsx +176 -0
  58. package/agentvibes.org/next.config.js +4 -0
  59. package/agentvibes.org/package-lock.json +1974 -0
  60. package/agentvibes.org/package.json +30 -0
  61. package/agentvibes.org/pnpm-lock.yaml +1141 -0
  62. package/agentvibes.org/postcss.config.js +5 -0
  63. package/agentvibes.org/public/audio/02-sarcastic.mp3 +0 -0
  64. package/agentvibes.org/public/audio/03-angry.mp3 +0 -0
  65. package/agentvibes.org/public/audio/04-grandpa.mp3 +0 -0
  66. package/agentvibes.org/public/audio/05-sarcastic-example2.mp3 +0 -0
  67. package/agentvibes.org/public/audio/french-rachel.mp3 +0 -0
  68. package/agentvibes.org/public/audio/spanish-antoni.mp3 +0 -0
  69. package/agentvibes.org/public/favicon.ico +0 -0
  70. package/agentvibes.org/public/fonts/nacelle-italic.woff2 +0 -0
  71. package/agentvibes.org/public/fonts/nacelle-regular.woff2 +0 -0
  72. package/agentvibes.org/public/fonts/nacelle-semibold.woff2 +0 -0
  73. package/agentvibes.org/public/fonts/nacelle-semibolditalic.woff2 +0 -0
  74. package/agentvibes.org/public/images/blurred-shape-gray.svg +1 -0
  75. package/agentvibes.org/public/images/blurred-shape.svg +1 -0
  76. package/agentvibes.org/public/images/client-logo-01.svg +1 -0
  77. package/agentvibes.org/public/images/client-logo-02.svg +1 -0
  78. package/agentvibes.org/public/images/client-logo-03.svg +1 -0
  79. package/agentvibes.org/public/images/client-logo-04.svg +1 -0
  80. package/agentvibes.org/public/images/client-logo-05.svg +1 -0
  81. package/agentvibes.org/public/images/client-logo-06.svg +1 -0
  82. package/agentvibes.org/public/images/client-logo-07.svg +1 -0
  83. package/agentvibes.org/public/images/client-logo-08.svg +1 -0
  84. package/agentvibes.org/public/images/client-logo-09.svg +1 -0
  85. package/agentvibes.org/public/images/features.png +0 -0
  86. package/agentvibes.org/public/images/footer-illustration.svg +1 -0
  87. package/agentvibes.org/public/images/hero-image-01.jpg +0 -0
  88. package/agentvibes.org/public/images/logo.svg +1 -0
  89. package/agentvibes.org/public/images/page-illustration.svg +1 -0
  90. package/agentvibes.org/public/images/secondary-illustration.svg +1 -0
  91. package/agentvibes.org/public/images/testimonial-01.jpg +0 -0
  92. package/agentvibes.org/public/images/testimonial-02.jpg +0 -0
  93. package/agentvibes.org/public/images/testimonial-03.jpg +0 -0
  94. package/agentvibes.org/public/images/testimonial-04.jpg +0 -0
  95. package/agentvibes.org/public/images/testimonial-05.jpg +0 -0
  96. package/agentvibes.org/public/images/testimonial-06.jpg +0 -0
  97. package/agentvibes.org/public/images/testimonial-07.jpg +0 -0
  98. package/agentvibes.org/public/images/testimonial-08.jpg +0 -0
  99. package/agentvibes.org/public/images/testimonial-09.jpg +0 -0
  100. package/agentvibes.org/public/images/workflow-01.png +0 -0
  101. package/agentvibes.org/public/images/workflow-02.png +0 -0
  102. package/agentvibes.org/public/images/workflow-03.png +0 -0
  103. package/agentvibes.org/public/videos/video.mp4 +0 -0
  104. package/agentvibes.org/tsconfig.json +28 -0
  105. package/agentvibes.org/utils/useMasonry.tsx +67 -0
  106. package/agentvibes.org/utils/useMousePosition.tsx +27 -0
  107. package/docs/ai-optimized-documentation-standards.md +306 -0
  108. package/docs/architecture/provider-system.md +574 -0
  109. package/docs/voice-mapping-format.md +218 -0
  110. package/package.json +1 -1
  111. package/scripts/piper-voice/README.md +145 -0
  112. package/scripts/piper-voice/wsl-install.sh +193 -0
  113. package/src/installer.js +20 -5
@@ -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