agentvibes 1.1.2 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/commands/agent-vibes/provider.md +54 -0
- package/.claude/hooks/language-manager.sh +7 -2
- package/.claude/hooks/personality-manager.sh +7 -3
- 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 +18 -1
- 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/hooks/sentiment-manager.sh +7 -3
- package/.claude/hooks/voice-manager.sh +11 -5
- package/.claude/language-voices.yaml +372 -0
- package/.claude/personalities/backups/angry.md.backup-20251005 +16 -0
- package/.claude/personalities/backups/annoying.md.backup-20251005 +16 -0
- package/.claude/personalities/backups/crass.md.backup-20251005 +16 -0
- package/.claude/personalities/backups/dramatic.md.backup-20251005 +16 -0
- package/.claude/personalities/backups/dry-humor.md.backup-20251005 +52 -0
- package/.claude/personalities/backups/flirty.md.backup-20251005 +22 -0
- package/.claude/personalities/backups/funny.md.backup-20251005 +16 -0
- package/.claude/personalities/backups/grandpa.md.backup-20251005 +34 -0
- package/.claude/personalities/backups/millennial.md.backup-20251005 +16 -0
- package/.claude/personalities/backups/moody.md.backup-20251005 +16 -0
- package/.claude/personalities/backups/normal.md.backup-20251005 +18 -0
- package/.claude/personalities/backups/pirate.md.backup-20251005 +16 -0
- package/.claude/personalities/backups/poetic.md.backup-20251005 +16 -0
- package/.claude/personalities/backups/professional.md.backup-20251005 +16 -0
- package/.claude/personalities/backups/robot.md.backup-20251005 +16 -0
- package/.claude/personalities/backups/sarcastic.md.backup-20251005 +40 -0
- package/.claude/personalities/backups/sassy.md.backup-20251005 +16 -0
- package/.claude/personalities/backups/surfer-dude.md.backup-20251005 +16 -0
- package/.claude/personalities/backups/zen.md.backup-20251005 +16 -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/README.md +229 -28
- package/RELEASE_NOTES.md +114 -0
- package/RELEASE_NOTES_V2.md +482 -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 +97 -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 +44 -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 +3 -2
- package/scripts/piper-voice/README.md +145 -0
- package/scripts/piper-voice/wsl-install.sh +193 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# @fileoverview Piper TTS Provider Implementation
|
|
4
|
+
# @context Free, offline neural TTS for WSL/Linux
|
|
5
|
+
# @architecture Implements provider contract for Piper binary
|
|
6
|
+
# @dependencies piper (pipx), piper-voice-manager.sh, mpv/aplay
|
|
7
|
+
# @entrypoints Called by play-tts.sh router
|
|
8
|
+
# @patterns Provider contract: text/voice → audio file path
|
|
9
|
+
# @related play-tts.sh, piper-voice-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 model name
|
|
17
|
+
|
|
18
|
+
# Source voice manager and language manager
|
|
19
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
20
|
+
source "$SCRIPT_DIR/piper-voice-manager.sh"
|
|
21
|
+
source "$SCRIPT_DIR/language-manager.sh"
|
|
22
|
+
|
|
23
|
+
# Default voice for Piper
|
|
24
|
+
DEFAULT_VOICE="en_US-lessac-medium"
|
|
25
|
+
|
|
26
|
+
# @function determine_voice_model
|
|
27
|
+
# @intent Resolve voice name to Piper model name with language support
|
|
28
|
+
# @why Support voice override, language-specific voices, and default fallback
|
|
29
|
+
# @param Uses global: $VOICE_OVERRIDE
|
|
30
|
+
# @returns Sets $VOICE_MODEL global variable
|
|
31
|
+
# @sideeffects None
|
|
32
|
+
VOICE_MODEL=""
|
|
33
|
+
|
|
34
|
+
# Get current language setting
|
|
35
|
+
CURRENT_LANGUAGE=$(get_current_language)
|
|
36
|
+
|
|
37
|
+
if [[ -n "$VOICE_OVERRIDE" ]]; then
|
|
38
|
+
# Use override if provided
|
|
39
|
+
VOICE_MODEL="$VOICE_OVERRIDE"
|
|
40
|
+
echo "🎤 Using voice: $VOICE_OVERRIDE (session-specific)"
|
|
41
|
+
else
|
|
42
|
+
# Try to get language-specific voice
|
|
43
|
+
LANG_VOICE=$(get_voice_for_language "$CURRENT_LANGUAGE" "piper" 2>/dev/null)
|
|
44
|
+
|
|
45
|
+
if [[ -n "$LANG_VOICE" ]]; then
|
|
46
|
+
VOICE_MODEL="$LANG_VOICE"
|
|
47
|
+
echo "🌍 Using $CURRENT_LANGUAGE voice: $LANG_VOICE (Piper)"
|
|
48
|
+
else
|
|
49
|
+
# Use default voice
|
|
50
|
+
VOICE_MODEL="$DEFAULT_VOICE"
|
|
51
|
+
fi
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
# @function validate_inputs
|
|
55
|
+
# @intent Check required parameters
|
|
56
|
+
# @why Fail fast with clear errors if inputs missing
|
|
57
|
+
# @exitcode 1=missing text, 2=missing piper binary
|
|
58
|
+
if [[ -z "$TEXT" ]]; then
|
|
59
|
+
echo "Usage: $0 \"text to speak\" [voice_model_name]"
|
|
60
|
+
exit 1
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
# Check if Piper is installed
|
|
64
|
+
if ! command -v piper &> /dev/null; then
|
|
65
|
+
echo "❌ Error: Piper TTS not installed"
|
|
66
|
+
echo "Install with: pipx install piper-tts"
|
|
67
|
+
echo "Or run: .claude/hooks/piper-installer.sh"
|
|
68
|
+
exit 2
|
|
69
|
+
fi
|
|
70
|
+
|
|
71
|
+
# @function ensure_voice_downloaded
|
|
72
|
+
# @intent Download voice model if not cached
|
|
73
|
+
# @why Provide seamless experience with automatic downloads
|
|
74
|
+
# @param Uses global: $VOICE_MODEL
|
|
75
|
+
# @sideeffects Downloads voice model files
|
|
76
|
+
# @edgecases Prompts user for consent before downloading
|
|
77
|
+
if ! verify_voice "$VOICE_MODEL"; then
|
|
78
|
+
echo "📥 Voice model not found: $VOICE_MODEL"
|
|
79
|
+
echo " File size: ~25MB"
|
|
80
|
+
echo " Preview: https://huggingface.co/rhasspy/piper-voices"
|
|
81
|
+
echo ""
|
|
82
|
+
read -p " Download this voice model? [y/N]: " -n 1 -r
|
|
83
|
+
echo
|
|
84
|
+
|
|
85
|
+
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
|
86
|
+
if ! download_voice "$VOICE_MODEL"; then
|
|
87
|
+
echo "❌ Failed to download voice model"
|
|
88
|
+
echo "Fix: Download manually or choose different voice"
|
|
89
|
+
exit 3
|
|
90
|
+
fi
|
|
91
|
+
else
|
|
92
|
+
echo "❌ Voice download cancelled"
|
|
93
|
+
exit 3
|
|
94
|
+
fi
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
# Get voice model path
|
|
98
|
+
VOICE_PATH=$(get_voice_path "$VOICE_MODEL")
|
|
99
|
+
if [[ $? -ne 0 ]]; then
|
|
100
|
+
echo "❌ Voice model path not found: $VOICE_MODEL"
|
|
101
|
+
exit 3
|
|
102
|
+
fi
|
|
103
|
+
|
|
104
|
+
# @function determine_audio_directory
|
|
105
|
+
# @intent Find appropriate directory for audio file storage
|
|
106
|
+
# @why Supports project-local and global storage
|
|
107
|
+
# @returns Sets $AUDIO_DIR global variable
|
|
108
|
+
if [[ -n "$CLAUDE_PROJECT_DIR" ]]; then
|
|
109
|
+
AUDIO_DIR="$CLAUDE_PROJECT_DIR/.claude/audio"
|
|
110
|
+
else
|
|
111
|
+
# Fallback: try to find .claude directory in current path
|
|
112
|
+
CURRENT_DIR="$PWD"
|
|
113
|
+
while [[ "$CURRENT_DIR" != "/" ]]; do
|
|
114
|
+
if [[ -d "$CURRENT_DIR/.claude" ]]; then
|
|
115
|
+
AUDIO_DIR="$CURRENT_DIR/.claude/audio"
|
|
116
|
+
break
|
|
117
|
+
fi
|
|
118
|
+
CURRENT_DIR=$(dirname "$CURRENT_DIR")
|
|
119
|
+
done
|
|
120
|
+
# Final fallback to global if no project .claude found
|
|
121
|
+
if [[ -z "$AUDIO_DIR" ]]; then
|
|
122
|
+
AUDIO_DIR="$HOME/.claude/audio"
|
|
123
|
+
fi
|
|
124
|
+
fi
|
|
125
|
+
|
|
126
|
+
mkdir -p "$AUDIO_DIR"
|
|
127
|
+
TEMP_FILE="$AUDIO_DIR/tts-$(date +%s).wav"
|
|
128
|
+
|
|
129
|
+
# @function synthesize_with_piper
|
|
130
|
+
# @intent Generate speech using Piper TTS
|
|
131
|
+
# @why Provides free, offline TTS alternative
|
|
132
|
+
# @param Uses globals: $TEXT, $VOICE_PATH
|
|
133
|
+
# @returns Creates WAV file at $TEMP_FILE
|
|
134
|
+
# @exitcode 0=success, 4=synthesis error
|
|
135
|
+
# @sideeffects Creates audio file
|
|
136
|
+
# @edgecases Handles piper errors, invalid models
|
|
137
|
+
echo "$TEXT" | piper --model "$VOICE_PATH" --output_file "$TEMP_FILE" 2>/dev/null
|
|
138
|
+
|
|
139
|
+
if [[ ! -f "$TEMP_FILE" ]] || [[ ! -s "$TEMP_FILE" ]]; then
|
|
140
|
+
echo "❌ Failed to synthesize speech with Piper"
|
|
141
|
+
echo "Voice model: $VOICE_MODEL"
|
|
142
|
+
echo "Check that voice model is valid"
|
|
143
|
+
exit 4
|
|
144
|
+
fi
|
|
145
|
+
|
|
146
|
+
# @function add_silence_padding
|
|
147
|
+
# @intent Add silence to prevent WSL audio static
|
|
148
|
+
# @why WSL audio subsystem cuts off first ~200ms
|
|
149
|
+
# @param Uses global: $TEMP_FILE
|
|
150
|
+
# @returns Updates $TEMP_FILE to padded version
|
|
151
|
+
# @sideeffects Modifies audio file
|
|
152
|
+
# AI NOTE: Use ffmpeg if available, otherwise skip padding (degraded experience)
|
|
153
|
+
if command -v ffmpeg &> /dev/null; then
|
|
154
|
+
PADDED_FILE="$AUDIO_DIR/tts-padded-$(date +%s).wav"
|
|
155
|
+
# Add 200ms of silence at the beginning
|
|
156
|
+
ffmpeg -f lavfi -i anullsrc=r=44100:cl=stereo:d=0.2 -i "$TEMP_FILE" \
|
|
157
|
+
-filter_complex "[0:a][1:a]concat=n=2:v=0:a=1[out]" \
|
|
158
|
+
-map "[out]" -y "$PADDED_FILE" 2>/dev/null
|
|
159
|
+
|
|
160
|
+
if [[ -f "$PADDED_FILE" ]]; then
|
|
161
|
+
rm -f "$TEMP_FILE"
|
|
162
|
+
TEMP_FILE="$PADDED_FILE"
|
|
163
|
+
fi
|
|
164
|
+
fi
|
|
165
|
+
|
|
166
|
+
# @function play_audio
|
|
167
|
+
# @intent Play generated audio using available player
|
|
168
|
+
# @why Support multiple audio players
|
|
169
|
+
# @param Uses global: $TEMP_FILE
|
|
170
|
+
# @sideeffects Plays audio in background
|
|
171
|
+
# Play audio (WSL/Linux) in background
|
|
172
|
+
(mpv "$TEMP_FILE" 2>/dev/null || aplay "$TEMP_FILE" 2>/dev/null || paplay "$TEMP_FILE" 2>/dev/null) &
|
|
173
|
+
|
|
174
|
+
echo "🎵 Saved to: $TEMP_FILE"
|
|
175
|
+
echo "🎤 Voice used: $VOICE_MODEL (Piper TTS)"
|
|
@@ -109,8 +109,25 @@ curl -s -X POST "https://api.elevenlabs.io/v1/text-to-speech/${VOICE_ID}" \
|
|
|
109
109
|
-d "{\"text\":\"${TEXT}\",\"model_id\":\"eleven_monolingual_v1\",\"voice_settings\":{\"stability\":0.5,\"similarity_boost\":0.75}}" \
|
|
110
110
|
-o "${TEMP_FILE}"
|
|
111
111
|
|
|
112
|
-
#
|
|
112
|
+
# Add silence padding to prevent WSL audio static
|
|
113
113
|
if [ -f "${TEMP_FILE}" ]; then
|
|
114
|
+
# Check if ffmpeg is available for adding padding
|
|
115
|
+
if command -v ffmpeg &> /dev/null; then
|
|
116
|
+
PADDED_FILE="$AUDIO_DIR/tts-padded-$(date +%s).mp3"
|
|
117
|
+
# Add 200ms of silence at the beginning to prevent static
|
|
118
|
+
ffmpeg -f lavfi -i anullsrc=r=44100:cl=stereo:d=0.2 -i "${TEMP_FILE}" \
|
|
119
|
+
-filter_complex "[0:a][1:a]concat=n=2:v=0:a=1[out]" \
|
|
120
|
+
-map "[out]" -y "${PADDED_FILE}" 2>/dev/null
|
|
121
|
+
|
|
122
|
+
if [ -f "${PADDED_FILE}" ]; then
|
|
123
|
+
# Use padded file and clean up original
|
|
124
|
+
rm -f "${TEMP_FILE}"
|
|
125
|
+
TEMP_FILE="${PADDED_FILE}"
|
|
126
|
+
fi
|
|
127
|
+
# If padding failed, just use original file
|
|
128
|
+
fi
|
|
129
|
+
|
|
130
|
+
# Play audio (WSL/Linux) in background to avoid blocking
|
|
114
131
|
(paplay "${TEMP_FILE}" 2>/dev/null || aplay "${TEMP_FILE}" 2>/dev/null || mpg123 "${TEMP_FILE}" 2>/dev/null) &
|
|
115
132
|
# Keep temp files for later review - cleaned up weekly by cron
|
|
116
133
|
echo "🎵 Saved to: ${TEMP_FILE}"
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Quick TTS playback script with session-specific voice support
|
|
3
|
+
# Usage: play-tts.sh "Text to speak" [voice_name_or_id]
|
|
4
|
+
#
|
|
5
|
+
# Examples:
|
|
6
|
+
# play-tts.sh "Hello world" # Uses default voice from voice manager
|
|
7
|
+
# play-tts.sh "Hello world" "Sarah" # Uses Sarah voice by name
|
|
8
|
+
# play-tts.sh "Hello world" "KTPVrSVAEUSJRClDzBw7" # Uses voice by direct ID
|
|
9
|
+
#
|
|
10
|
+
# This allows different sessions to use different voices for easy identification!
|
|
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
|
+
|
|
40
|
+
# Determine which voice to use
|
|
41
|
+
VOICE_ID=""
|
|
42
|
+
|
|
43
|
+
if [[ -n "$VOICE_OVERRIDE" ]]; then
|
|
44
|
+
# Check if override is a voice name (lookup in mapping)
|
|
45
|
+
if [[ -n "${VOICES[$VOICE_OVERRIDE]}" ]]; then
|
|
46
|
+
VOICE_ID="${VOICES[$VOICE_OVERRIDE]}"
|
|
47
|
+
echo "🎤 Using voice: $VOICE_OVERRIDE (session-specific)"
|
|
48
|
+
# Check if override looks like a voice ID (alphanumeric string ~20 chars)
|
|
49
|
+
elif [[ "$VOICE_OVERRIDE" =~ ^[a-zA-Z0-9]{15,30}$ ]]; then
|
|
50
|
+
VOICE_ID="$VOICE_OVERRIDE"
|
|
51
|
+
echo "🎤 Using custom voice ID (session-specific)"
|
|
52
|
+
else
|
|
53
|
+
echo "⚠️ Unknown voice '$VOICE_OVERRIDE', using default"
|
|
54
|
+
fi
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
# If no override or invalid override, use default from voice manager
|
|
58
|
+
if [[ -z "$VOICE_ID" ]]; then
|
|
59
|
+
VOICE_MANAGER_SCRIPT="$(dirname "$0")/voice-manager.sh"
|
|
60
|
+
if [[ -f "$VOICE_MANAGER_SCRIPT" ]]; then
|
|
61
|
+
VOICE_NAME=$("$VOICE_MANAGER_SCRIPT" get)
|
|
62
|
+
VOICE_ID="${VOICES[$VOICE_NAME]}"
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
# Final fallback to Cowboy Bob default
|
|
66
|
+
if [[ -z "$VOICE_ID" ]]; then
|
|
67
|
+
echo "⚠️ No voice configured, using Cowboy Bob default"
|
|
68
|
+
VOICE_ID="${VOICES[Cowboy Bob]}"
|
|
69
|
+
fi
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
if [ -z "$TEXT" ]; then
|
|
73
|
+
echo "Usage: $0 \"text to speak\""
|
|
74
|
+
exit 1
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
if [ -z "$API_KEY" ]; then
|
|
78
|
+
echo "Error: ELEVENLABS_API_KEY not set"
|
|
79
|
+
exit 1
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
# Create audio file in project-local storage
|
|
83
|
+
# Use project directory if available, otherwise fall back to global
|
|
84
|
+
if [[ -n "$CLAUDE_PROJECT_DIR" ]]; then
|
|
85
|
+
AUDIO_DIR="$CLAUDE_PROJECT_DIR/.claude/audio"
|
|
86
|
+
else
|
|
87
|
+
# Fallback: try to find .claude directory in current path
|
|
88
|
+
CURRENT_DIR="$PWD"
|
|
89
|
+
while [[ "$CURRENT_DIR" != "/" ]]; do
|
|
90
|
+
if [[ -d "$CURRENT_DIR/.claude" ]]; then
|
|
91
|
+
AUDIO_DIR="$CURRENT_DIR/.claude/audio"
|
|
92
|
+
break
|
|
93
|
+
fi
|
|
94
|
+
CURRENT_DIR=$(dirname "$CURRENT_DIR")
|
|
95
|
+
done
|
|
96
|
+
# Final fallback to global if no project .claude found
|
|
97
|
+
if [[ -z "$AUDIO_DIR" ]]; then
|
|
98
|
+
AUDIO_DIR="$HOME/.claude/audio"
|
|
99
|
+
fi
|
|
100
|
+
fi
|
|
101
|
+
|
|
102
|
+
mkdir -p "$AUDIO_DIR"
|
|
103
|
+
TEMP_FILE="$AUDIO_DIR/tts-$(date +%s).mp3"
|
|
104
|
+
|
|
105
|
+
# Generate audio
|
|
106
|
+
curl -s -X POST "https://api.elevenlabs.io/v1/text-to-speech/${VOICE_ID}" \
|
|
107
|
+
-H "xi-api-key: ${API_KEY}" \
|
|
108
|
+
-H "Content-Type: application/json" \
|
|
109
|
+
-d "{\"text\":\"${TEXT}\",\"model_id\":\"eleven_monolingual_v1\",\"voice_settings\":{\"stability\":0.5,\"similarity_boost\":0.75}}" \
|
|
110
|
+
-o "${TEMP_FILE}"
|
|
111
|
+
|
|
112
|
+
# Add silence padding to prevent WSL audio static
|
|
113
|
+
if [ -f "${TEMP_FILE}" ]; then
|
|
114
|
+
# Check if ffmpeg is available for adding padding
|
|
115
|
+
if command -v ffmpeg &> /dev/null; then
|
|
116
|
+
PADDED_FILE="$AUDIO_DIR/tts-padded-$(date +%s).mp3"
|
|
117
|
+
# Add 200ms of silence at the beginning to prevent static
|
|
118
|
+
ffmpeg -f lavfi -i anullsrc=r=44100:cl=stereo:d=0.2 -i "${TEMP_FILE}" \
|
|
119
|
+
-filter_complex "[0:a][1:a]concat=n=2:v=0:a=1[out]" \
|
|
120
|
+
-map "[out]" -y "${PADDED_FILE}" 2>/dev/null
|
|
121
|
+
|
|
122
|
+
if [ -f "${PADDED_FILE}" ]; then
|
|
123
|
+
# Use padded file and clean up original
|
|
124
|
+
rm -f "${TEMP_FILE}"
|
|
125
|
+
TEMP_FILE="${PADDED_FILE}"
|
|
126
|
+
fi
|
|
127
|
+
# If padding failed, just use original file
|
|
128
|
+
fi
|
|
129
|
+
|
|
130
|
+
# Play audio (WSL/Linux) in background to avoid blocking
|
|
131
|
+
(paplay "${TEMP_FILE}" 2>/dev/null || aplay "${TEMP_FILE}" 2>/dev/null || mpg123 "${TEMP_FILE}" 2>/dev/null) &
|
|
132
|
+
# Keep temp files for later review - cleaned up weekly by cron
|
|
133
|
+
echo "🎵 Saved to: ${TEMP_FILE}"
|
|
134
|
+
echo "🎤 Voice used: ${VOICE_NAME} (${VOICE_ID})"
|
|
135
|
+
else
|
|
136
|
+
echo "Failed to generate audio"
|
|
137
|
+
exit 1
|
|
138
|
+
fi
|