agentvibes 2.14.0 → 2.14.2
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/agent-vibes.md +33 -9
- package/.claude/commands/agent-vibes/provider.md +21 -7
- package/.claude/hooks/macos-voice-manager.sh +205 -0
- package/.claude/hooks/play-tts-macos.sh +270 -0
- package/.claude/hooks/provider-commands.sh +132 -3
- package/.claude/hooks/provider-manager.sh +57 -26
- package/.claude/hooks/voice-manager.sh +52 -10
- package/README.md +23 -14
- package/RELEASE_NOTES.md +106 -115
- package/github-star-reminder.txt +1 -0
- package/package.json +1 -1
- package/scripts/fix-wsl-audio.sh +106 -0
- package/src/installer.js +19 -19
- package/test/unit/provider-manager.bats +3 -1
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
description: ElevenLabs TTS voice management commands
|
|
3
3
|
---
|
|
4
4
|
|
|
5
|
-
# 🎤
|
|
5
|
+
# 🎤 AgentVibes Voice Management
|
|
6
6
|
|
|
7
|
-
Manage your
|
|
7
|
+
Manage your text-to-speech voices across multiple providers (ElevenLabs, Piper, macOS Say).
|
|
8
8
|
|
|
9
9
|
## Available Commands
|
|
10
10
|
|
|
@@ -49,9 +49,33 @@ Set a prefix word/phrase for all TTS messages
|
|
|
49
49
|
|
|
50
50
|
Saved locally in `.agentvibes/config/agentvibes.json`
|
|
51
51
|
|
|
52
|
-
##
|
|
52
|
+
## Provider Management
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
### `/agent-vibes:provider list`
|
|
55
|
+
Show all available TTS providers
|
|
56
|
+
|
|
57
|
+
### `/agent-vibes:provider switch <name>`
|
|
58
|
+
Switch between providers:
|
|
59
|
+
- `/agent-vibes:provider switch piper` - Free, offline (Linux/WSL)
|
|
60
|
+
- `/agent-vibes:provider switch elevenlabs` - Premium AI voices
|
|
61
|
+
- `/agent-vibes:provider switch macos` - Native macOS (Mac only)
|
|
62
|
+
|
|
63
|
+
### `/agent-vibes:provider info <name>`
|
|
64
|
+
Get details about a specific provider
|
|
65
|
+
|
|
66
|
+
## Providers
|
|
67
|
+
|
|
68
|
+
| Provider | Platform | Cost | Quality |
|
|
69
|
+
|----------|----------|------|---------|
|
|
70
|
+
| **macOS Say** | macOS only | Free (built-in) | ⭐⭐⭐⭐ |
|
|
71
|
+
| **Piper** | Linux/WSL | Free | ⭐⭐⭐⭐ |
|
|
72
|
+
| **ElevenLabs** | All | Free tier + paid | ⭐⭐⭐⭐⭐ |
|
|
73
|
+
|
|
74
|
+
On macOS, the native `say` provider is automatically detected and recommended.
|
|
75
|
+
|
|
76
|
+
## Getting Voice IDs (ElevenLabs)
|
|
77
|
+
|
|
78
|
+
To add your own custom ElevenLabs voices:
|
|
55
79
|
1. Go to https://elevenlabs.io/app/voice-library
|
|
56
80
|
2. Select or create a voice
|
|
57
81
|
3. Copy the voice ID (15-30 character alphanumeric string)
|
|
@@ -59,10 +83,10 @@ To add your own custom voices:
|
|
|
59
83
|
|
|
60
84
|
## Default Voices
|
|
61
85
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
-
|
|
65
|
-
|
|
66
|
-
|
|
86
|
+
**ElevenLabs:** Northern Terry, Grandpa Spuds Oxley, Ms. Walker, Ralf Eisend, Amy, Michael, Jessica Anne Bogart, Aria, Lutz Laugh, Dr. Von Fusion, Matthew Schmitz, Demon Monster, Cowboy Bob, Drill Sergeant
|
|
87
|
+
|
|
88
|
+
**Piper:** en_US-lessac-medium, en_US-amy-medium, en_GB-alan-medium, and many more
|
|
89
|
+
|
|
90
|
+
**macOS Say:** Samantha, Alex, Daniel, Victoria, Karen, Moira, and 100+ more built-in voices
|
|
67
91
|
|
|
68
92
|
Enjoy your TTS experience! 🎵
|
|
@@ -5,7 +5,7 @@ argument-hint: [command] [args...]
|
|
|
5
5
|
|
|
6
6
|
# Provider Management Commands
|
|
7
7
|
|
|
8
|
-
Manage TTS providers (ElevenLabs, Piper) - switch between providers, view details, and test.
|
|
8
|
+
Manage TTS providers (ElevenLabs, Piper, macOS Say) - switch between providers, view details, and test.
|
|
9
9
|
|
|
10
10
|
## Usage
|
|
11
11
|
|
|
@@ -30,8 +30,12 @@ Manage TTS providers (ElevenLabs, Piper) - switch between providers, view detail
|
|
|
30
30
|
# Switch to ElevenLabs (premium quality)
|
|
31
31
|
/agent-vibes:provider switch elevenlabs
|
|
32
32
|
|
|
33
|
+
# Switch to macOS Say (native macOS, free)
|
|
34
|
+
/agent-vibes:provider switch macos
|
|
35
|
+
|
|
33
36
|
# Get info about a provider
|
|
34
37
|
/agent-vibes:provider info piper
|
|
38
|
+
/agent-vibes:provider info macos
|
|
35
39
|
|
|
36
40
|
# Test current provider
|
|
37
41
|
/agent-vibes:provider test
|
|
@@ -42,12 +46,22 @@ Manage TTS providers (ElevenLabs, Piper) - switch between providers, view detail
|
|
|
42
46
|
|
|
43
47
|
## Provider Comparison
|
|
44
48
|
|
|
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 |
|
|
49
|
+
| Feature | ElevenLabs | Piper | macOS Say |
|
|
50
|
+
|---------|------------|-------|-----------|
|
|
51
|
+
| Quality | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
|
|
52
|
+
| Cost | Free tier + $5-22/mo | Free forever | Free (built-in) |
|
|
53
|
+
| Offline | No | Yes | Yes |
|
|
54
|
+
| Platform | All | WSL/Linux only | macOS only |
|
|
55
|
+
| Setup | API key required | Auto-downloads | Zero setup |
|
|
56
|
+
|
|
57
|
+
**macOS Say** is automatically detected and recommended on macOS systems. It uses the native `say` command with voices like Samantha, Alex, Daniel, and many more. Perfect for Mac users who want free, offline TTS with zero configuration.
|
|
58
|
+
|
|
59
|
+
### macOS Say Compatibility
|
|
60
|
+
|
|
61
|
+
- **Supported:** All macOS versions (Mac OS X 10.0+)
|
|
62
|
+
- **Voices:** 100+ built-in voices across 40+ languages
|
|
63
|
+
- **Enhanced voices:** Siri-quality voices available on macOS 10.14 Mojave and later
|
|
64
|
+
- **Note:** Available voices vary by macOS version; newer versions have more high-quality options
|
|
51
65
|
|
|
52
66
|
Learn more: agentvibes.org/providers
|
|
53
67
|
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# File: .claude/hooks/macos-voice-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. Use at your own risk. See the Apache License for details.
|
|
26
|
+
#
|
|
27
|
+
# ---
|
|
28
|
+
#
|
|
29
|
+
# @fileoverview macOS Voice Manager - Voice discovery and management for macOS 'say' command
|
|
30
|
+
# @context Provides voice listing, validation, and info for macOS native TTS
|
|
31
|
+
# @architecture Helper functions for play-tts-macos.sh provider
|
|
32
|
+
# @dependencies macOS only (Darwin), say command (built-in)
|
|
33
|
+
# @entrypoints Called by voice-manager.sh for provider-aware voice operations
|
|
34
|
+
# @patterns Voice enumeration via 'say -v ?', language filtering, quality tiers
|
|
35
|
+
# @related play-tts-macos.sh, voice-manager.sh, provider-manager.sh
|
|
36
|
+
#
|
|
37
|
+
|
|
38
|
+
# Platform guard
|
|
39
|
+
if [[ "$(uname -s)" != "Darwin" ]]; then
|
|
40
|
+
echo "❌ Error: macOS voice manager only works on macOS"
|
|
41
|
+
exit 1
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
# @function list_macos_voices
|
|
45
|
+
# @intent List all available macOS voices
|
|
46
|
+
# @returns Echoes voice names one per line
|
|
47
|
+
list_macos_voices() {
|
|
48
|
+
say -v ? 2>/dev/null | awk '{print $1}'
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
# @function list_macos_voices_detailed
|
|
52
|
+
# @intent List all voices with language info
|
|
53
|
+
# @returns Echoes "voice_name language_code" per line
|
|
54
|
+
list_macos_voices_detailed() {
|
|
55
|
+
say -v ? 2>/dev/null | while read -r line; do
|
|
56
|
+
local voice=$(echo "$line" | awk '{print $1}')
|
|
57
|
+
local lang=$(echo "$line" | awk '{print $2}')
|
|
58
|
+
echo "$voice $lang"
|
|
59
|
+
done
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
# @function list_english_voices
|
|
63
|
+
# @intent List only English voices
|
|
64
|
+
# @returns Echoes English voice names
|
|
65
|
+
list_english_voices() {
|
|
66
|
+
say -v ? 2>/dev/null | grep -i "en_" | awk '{print $1}'
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
# @function get_voice_language
|
|
70
|
+
# @intent Get the language code for a voice
|
|
71
|
+
# @param $1 Voice name
|
|
72
|
+
# @returns Echoes language code (e.g., "en_US")
|
|
73
|
+
get_voice_language() {
|
|
74
|
+
local voice="$1"
|
|
75
|
+
say -v ? 2>/dev/null | grep -i "^${voice} " | awk '{print $2}'
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
# @function validate_voice
|
|
79
|
+
# @intent Check if a voice exists on this system
|
|
80
|
+
# @param $1 Voice name
|
|
81
|
+
# @returns 0 if valid, 1 if not found
|
|
82
|
+
validate_voice() {
|
|
83
|
+
local voice="$1"
|
|
84
|
+
say -v ? 2>/dev/null | grep -qi "^${voice} "
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
# @function get_recommended_voices
|
|
88
|
+
# @intent Get list of recommended high-quality voices
|
|
89
|
+
# @returns Echoes recommended voice names
|
|
90
|
+
get_recommended_voices() {
|
|
91
|
+
# These are typically the highest quality voices on macOS
|
|
92
|
+
local recommended=("Samantha" "Alex" "Daniel" "Karen" "Moira" "Tessa" "Fiona" "Veena" "Victoria")
|
|
93
|
+
|
|
94
|
+
for voice in "${recommended[@]}"; do
|
|
95
|
+
if validate_voice "$voice"; then
|
|
96
|
+
echo "$voice"
|
|
97
|
+
fi
|
|
98
|
+
done
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
# @function get_voice_info
|
|
102
|
+
# @intent Get detailed info about a specific voice
|
|
103
|
+
# @param $1 Voice name
|
|
104
|
+
# @returns Echoes voice details
|
|
105
|
+
get_voice_info() {
|
|
106
|
+
local voice="$1"
|
|
107
|
+
|
|
108
|
+
if ! validate_voice "$voice"; then
|
|
109
|
+
echo "❌ Voice not found: $voice"
|
|
110
|
+
return 1
|
|
111
|
+
fi
|
|
112
|
+
|
|
113
|
+
local full_info=$(say -v ? 2>/dev/null | grep -i "^${voice} ")
|
|
114
|
+
local lang=$(echo "$full_info" | awk '{print $2}')
|
|
115
|
+
|
|
116
|
+
echo "Voice: $voice"
|
|
117
|
+
echo "Language: $lang"
|
|
118
|
+
echo "Provider: macOS Say (built-in)"
|
|
119
|
+
|
|
120
|
+
# Add description for known voices
|
|
121
|
+
case "$voice" in
|
|
122
|
+
Alex) echo "Description: American English male (enhanced)" ;;
|
|
123
|
+
Samantha) echo "Description: American English female (enhanced)" ;;
|
|
124
|
+
Victoria) echo "Description: American English female" ;;
|
|
125
|
+
Daniel) echo "Description: British English male (enhanced)" ;;
|
|
126
|
+
Karen) echo "Description: Australian English female (enhanced)" ;;
|
|
127
|
+
Moira) echo "Description: Irish English female (enhanced)" ;;
|
|
128
|
+
Tessa) echo "Description: South African English female (enhanced)" ;;
|
|
129
|
+
Fiona) echo "Description: Scottish English female (enhanced)" ;;
|
|
130
|
+
Veena) echo "Description: Indian English female (enhanced)" ;;
|
|
131
|
+
esac
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
# @function count_voices
|
|
135
|
+
# @intent Count total available voices
|
|
136
|
+
# @returns Echoes count
|
|
137
|
+
count_voices() {
|
|
138
|
+
say -v ? 2>/dev/null | wc -l | tr -d ' '
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
# @function count_english_voices
|
|
142
|
+
# @intent Count English voices
|
|
143
|
+
# @returns Echoes count
|
|
144
|
+
count_english_voices() {
|
|
145
|
+
say -v ? 2>/dev/null | grep -i "en_" | wc -l | tr -d ' '
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
# Command-line interface
|
|
149
|
+
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
|
150
|
+
case "${1:-}" in
|
|
151
|
+
list)
|
|
152
|
+
echo "🎤 Available macOS Voices:"
|
|
153
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
154
|
+
list_macos_voices_detailed | while read -r voice lang; do
|
|
155
|
+
printf " %-15s %s\n" "$voice" "$lang"
|
|
156
|
+
done
|
|
157
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
158
|
+
echo "Total: $(count_voices) voices"
|
|
159
|
+
;;
|
|
160
|
+
list-english)
|
|
161
|
+
echo "🎤 English macOS Voices:"
|
|
162
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
163
|
+
list_english_voices
|
|
164
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
165
|
+
echo "Total: $(count_english_voices) English voices"
|
|
166
|
+
;;
|
|
167
|
+
recommended)
|
|
168
|
+
echo "🌟 Recommended macOS Voices:"
|
|
169
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
170
|
+
get_recommended_voices
|
|
171
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
172
|
+
;;
|
|
173
|
+
info)
|
|
174
|
+
if [[ -z "$2" ]]; then
|
|
175
|
+
echo "Usage: $0 info <voice_name>"
|
|
176
|
+
exit 1
|
|
177
|
+
fi
|
|
178
|
+
get_voice_info "$2"
|
|
179
|
+
;;
|
|
180
|
+
validate)
|
|
181
|
+
if [[ -z "$2" ]]; then
|
|
182
|
+
echo "Usage: $0 validate <voice_name>"
|
|
183
|
+
exit 1
|
|
184
|
+
fi
|
|
185
|
+
if validate_voice "$2"; then
|
|
186
|
+
echo "✅ Voice '$2' is available"
|
|
187
|
+
exit 0
|
|
188
|
+
else
|
|
189
|
+
echo "❌ Voice '$2' is not available"
|
|
190
|
+
exit 1
|
|
191
|
+
fi
|
|
192
|
+
;;
|
|
193
|
+
*)
|
|
194
|
+
echo "Usage: $0 {list|list-english|recommended|info|validate} [voice_name]"
|
|
195
|
+
echo ""
|
|
196
|
+
echo "Commands:"
|
|
197
|
+
echo " list - List all available voices"
|
|
198
|
+
echo " list-english - List English voices only"
|
|
199
|
+
echo " recommended - List recommended high-quality voices"
|
|
200
|
+
echo " info <voice> - Get details about a voice"
|
|
201
|
+
echo " validate <voice> - Check if voice exists"
|
|
202
|
+
exit 1
|
|
203
|
+
;;
|
|
204
|
+
esac
|
|
205
|
+
fi
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# File: .claude/hooks/play-tts-macos.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. Use at your own risk. See the Apache License for details.
|
|
26
|
+
#
|
|
27
|
+
# ---
|
|
28
|
+
#
|
|
29
|
+
# @fileoverview macOS Say Provider Implementation - Native macOS TTS using the 'say' command
|
|
30
|
+
# @context Provides zero-dependency, offline TTS for macOS users using built-in Apple voices
|
|
31
|
+
# @architecture Implements provider interface contract for macOS 'say' command integration
|
|
32
|
+
# @dependencies macOS only (Darwin), say command (built-in), afplay (built-in)
|
|
33
|
+
# @entrypoints Called by play-tts.sh router when provider=macos
|
|
34
|
+
# @patterns Provider contract: text/voice → audio file path, voice validation, platform guard
|
|
35
|
+
# @related play-tts.sh, macos-voice-manager.sh, provider-manager.sh
|
|
36
|
+
#
|
|
37
|
+
|
|
38
|
+
# Platform guard - fail fast on non-macOS systems
|
|
39
|
+
if [[ "$(uname -s)" != "Darwin" ]]; then
|
|
40
|
+
echo "❌ Error: macOS provider only works on macOS"
|
|
41
|
+
echo " Current platform: $(uname -s)"
|
|
42
|
+
echo ""
|
|
43
|
+
echo " Switch to a different provider:"
|
|
44
|
+
echo " /agent-vibes:provider switch piper"
|
|
45
|
+
exit 1
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
TEXT="$1"
|
|
49
|
+
VOICE_OVERRIDE="$2" # Optional: voice name (e.g., "Samantha", "Daniel")
|
|
50
|
+
|
|
51
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
52
|
+
|
|
53
|
+
# Default voice for macOS
|
|
54
|
+
DEFAULT_VOICE="Samantha"
|
|
55
|
+
|
|
56
|
+
# Common macOS voices with descriptions
|
|
57
|
+
# These are typically available on all macOS systems
|
|
58
|
+
declare -A MACOS_VOICE_INFO
|
|
59
|
+
MACOS_VOICE_INFO=(
|
|
60
|
+
["Alex"]="American English male (enhanced)"
|
|
61
|
+
["Samantha"]="American English female (enhanced)"
|
|
62
|
+
["Victoria"]="American English female"
|
|
63
|
+
["Daniel"]="British English male (enhanced)"
|
|
64
|
+
["Karen"]="Australian English female (enhanced)"
|
|
65
|
+
["Moira"]="Irish English female (enhanced)"
|
|
66
|
+
["Tessa"]="South African English female (enhanced)"
|
|
67
|
+
["Fiona"]="Scottish English female (enhanced)"
|
|
68
|
+
["Veena"]="Indian English female (enhanced)"
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
# @function get_voice_file_path
|
|
72
|
+
# @intent Determine path to voice configuration file
|
|
73
|
+
# @returns Echoes path to tts-voice.txt
|
|
74
|
+
get_voice_file_path() {
|
|
75
|
+
local voice_file=""
|
|
76
|
+
|
|
77
|
+
if [[ -n "$CLAUDE_PROJECT_DIR" ]] && [[ -f "$CLAUDE_PROJECT_DIR/.claude/tts-voice.txt" ]]; then
|
|
78
|
+
voice_file="$CLAUDE_PROJECT_DIR/.claude/tts-voice.txt"
|
|
79
|
+
elif [[ -f "$SCRIPT_DIR/../tts-voice.txt" ]]; then
|
|
80
|
+
voice_file="$SCRIPT_DIR/../tts-voice.txt"
|
|
81
|
+
elif [[ -f "$HOME/.claude/tts-voice.txt" ]]; then
|
|
82
|
+
voice_file="$HOME/.claude/tts-voice.txt"
|
|
83
|
+
fi
|
|
84
|
+
|
|
85
|
+
echo "$voice_file"
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
# @function determine_voice
|
|
89
|
+
# @intent Resolve which voice to use
|
|
90
|
+
# @returns Sets $VOICE_NAME global variable
|
|
91
|
+
VOICE_NAME=""
|
|
92
|
+
|
|
93
|
+
if [[ -n "$VOICE_OVERRIDE" ]]; then
|
|
94
|
+
VOICE_NAME="$VOICE_OVERRIDE"
|
|
95
|
+
echo "🎤 Using voice: $VOICE_OVERRIDE (session-specific)"
|
|
96
|
+
else
|
|
97
|
+
VOICE_FILE=$(get_voice_file_path)
|
|
98
|
+
|
|
99
|
+
if [[ -n "$VOICE_FILE" ]] && [[ -f "$VOICE_FILE" ]]; then
|
|
100
|
+
FILE_VOICE=$(cat "$VOICE_FILE" 2>/dev/null | tr -d '\n\r')
|
|
101
|
+
if [[ -n "$FILE_VOICE" ]]; then
|
|
102
|
+
VOICE_NAME="$FILE_VOICE"
|
|
103
|
+
fi
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
# Fallback to default if no voice set
|
|
107
|
+
if [[ -z "$VOICE_NAME" ]]; then
|
|
108
|
+
VOICE_NAME="$DEFAULT_VOICE"
|
|
109
|
+
fi
|
|
110
|
+
fi
|
|
111
|
+
|
|
112
|
+
# @function validate_inputs
|
|
113
|
+
# @intent Check required parameters
|
|
114
|
+
if [[ -z "$TEXT" ]]; then
|
|
115
|
+
echo "Usage: $0 \"text to speak\" [voice_name]"
|
|
116
|
+
echo ""
|
|
117
|
+
echo "Available voices (run 'say -v ?' for full list):"
|
|
118
|
+
for voice in "${!MACOS_VOICE_INFO[@]}"; do
|
|
119
|
+
echo " $voice - ${MACOS_VOICE_INFO[$voice]}"
|
|
120
|
+
done | sort
|
|
121
|
+
exit 1
|
|
122
|
+
fi
|
|
123
|
+
|
|
124
|
+
# @function validate_voice
|
|
125
|
+
# @intent Check if the specified voice exists on this system
|
|
126
|
+
# @param $1 Voice name to validate
|
|
127
|
+
# @returns 0 if valid, 1 if not found
|
|
128
|
+
validate_voice() {
|
|
129
|
+
local voice="$1"
|
|
130
|
+
say -v ? 2>/dev/null | grep -qi "^${voice} "
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
# Validate voice exists (case-insensitive search)
|
|
134
|
+
if ! validate_voice "$VOICE_NAME"; then
|
|
135
|
+
echo "⚠️ Voice '$VOICE_NAME' not found on this system"
|
|
136
|
+
echo " Falling back to default: $DEFAULT_VOICE"
|
|
137
|
+
VOICE_NAME="$DEFAULT_VOICE"
|
|
138
|
+
|
|
139
|
+
# If default also doesn't exist, try to find any English voice
|
|
140
|
+
if ! validate_voice "$VOICE_NAME"; then
|
|
141
|
+
VOICE_NAME=$(say -v ? 2>/dev/null | grep -i "en_" | head -1 | awk '{print $1}')
|
|
142
|
+
if [[ -z "$VOICE_NAME" ]]; then
|
|
143
|
+
echo "❌ No English voices found on this system"
|
|
144
|
+
exit 2
|
|
145
|
+
fi
|
|
146
|
+
echo " Using first available English voice: $VOICE_NAME"
|
|
147
|
+
fi
|
|
148
|
+
fi
|
|
149
|
+
|
|
150
|
+
# @function determine_audio_directory
|
|
151
|
+
# @intent Find appropriate directory for audio file storage
|
|
152
|
+
if [[ -n "$CLAUDE_PROJECT_DIR" ]]; then
|
|
153
|
+
AUDIO_DIR="$CLAUDE_PROJECT_DIR/.claude/audio"
|
|
154
|
+
else
|
|
155
|
+
CURRENT_DIR="$PWD"
|
|
156
|
+
while [[ "$CURRENT_DIR" != "/" ]]; do
|
|
157
|
+
if [[ -d "$CURRENT_DIR/.claude" ]]; then
|
|
158
|
+
AUDIO_DIR="$CURRENT_DIR/.claude/audio"
|
|
159
|
+
break
|
|
160
|
+
fi
|
|
161
|
+
CURRENT_DIR=$(dirname "$CURRENT_DIR")
|
|
162
|
+
done
|
|
163
|
+
if [[ -z "$AUDIO_DIR" ]]; then
|
|
164
|
+
AUDIO_DIR="$HOME/.claude/audio"
|
|
165
|
+
fi
|
|
166
|
+
fi
|
|
167
|
+
|
|
168
|
+
mkdir -p "$AUDIO_DIR"
|
|
169
|
+
|
|
170
|
+
# Generate unique filename
|
|
171
|
+
TIMESTAMP=$(date +%s)
|
|
172
|
+
TEMP_FILE="$AUDIO_DIR/tts-${TIMESTAMP}.aiff"
|
|
173
|
+
FINAL_FILE="$AUDIO_DIR/tts-padded-${TIMESTAMP}.wav"
|
|
174
|
+
|
|
175
|
+
# @function get_speech_rate
|
|
176
|
+
# @intent Determine speech rate for synthesis
|
|
177
|
+
# @returns Speech rate value (words per minute, default ~175-200)
|
|
178
|
+
get_speech_rate() {
|
|
179
|
+
local rate_config=""
|
|
180
|
+
|
|
181
|
+
# Check for rate config file
|
|
182
|
+
if [[ -f "$SCRIPT_DIR/../config/tts-speech-rate.txt" ]]; then
|
|
183
|
+
rate_config="$SCRIPT_DIR/../config/tts-speech-rate.txt"
|
|
184
|
+
elif [[ -f "$HOME/.claude/config/tts-speech-rate.txt" ]]; then
|
|
185
|
+
rate_config="$HOME/.claude/config/tts-speech-rate.txt"
|
|
186
|
+
fi
|
|
187
|
+
|
|
188
|
+
if [[ -n "$rate_config" ]]; then
|
|
189
|
+
local user_speed=$(cat "$rate_config" 2>/dev/null | grep -v '^#' | grep -v '^$' | tail -1)
|
|
190
|
+
# Convert multiplier to words per minute (base ~200 WPM)
|
|
191
|
+
# User: 0.5=slower, 1.0=normal, 2.0=faster
|
|
192
|
+
echo "scale=0; 200 * $user_speed / 1" | bc -l 2>/dev/null || echo "200"
|
|
193
|
+
return
|
|
194
|
+
fi
|
|
195
|
+
|
|
196
|
+
# Default: 200 WPM (normal rate)
|
|
197
|
+
echo "200"
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
SPEECH_RATE=$(get_speech_rate)
|
|
201
|
+
|
|
202
|
+
# @function synthesize_with_say
|
|
203
|
+
# @intent Generate speech using macOS 'say' command
|
|
204
|
+
# @returns Creates audio file at $TEMP_FILE
|
|
205
|
+
echo "$TEXT" | say -v "$VOICE_NAME" -r "$SPEECH_RATE" -o "$TEMP_FILE" 2>/dev/null
|
|
206
|
+
|
|
207
|
+
if [[ ! -f "$TEMP_FILE" ]] || [[ ! -s "$TEMP_FILE" ]]; then
|
|
208
|
+
echo "❌ Failed to synthesize speech with macOS say command"
|
|
209
|
+
echo "Voice: $VOICE_NAME"
|
|
210
|
+
exit 3
|
|
211
|
+
fi
|
|
212
|
+
|
|
213
|
+
# @function convert_and_pad_audio
|
|
214
|
+
# @intent Convert AIFF to WAV and add silence padding for consistency
|
|
215
|
+
# @why Maintains consistent audio format across providers
|
|
216
|
+
if command -v ffmpeg &> /dev/null; then
|
|
217
|
+
# Add 200ms of silence at the beginning and convert to WAV
|
|
218
|
+
ffmpeg -f lavfi -i anullsrc=r=44100:cl=stereo:d=0.2 -i "$TEMP_FILE" \
|
|
219
|
+
-filter_complex "[0:a][1:a]concat=n=2:v=0:a=1[out]" \
|
|
220
|
+
-map "[out]" -y "$FINAL_FILE" 2>/dev/null
|
|
221
|
+
|
|
222
|
+
if [[ -f "$FINAL_FILE" ]]; then
|
|
223
|
+
rm -f "$TEMP_FILE"
|
|
224
|
+
TEMP_FILE="$FINAL_FILE"
|
|
225
|
+
fi
|
|
226
|
+
else
|
|
227
|
+
# No ffmpeg - use AIFF directly (rename for consistency)
|
|
228
|
+
FINAL_FILE="$AUDIO_DIR/tts-padded-${TIMESTAMP}.aiff"
|
|
229
|
+
mv "$TEMP_FILE" "$FINAL_FILE"
|
|
230
|
+
TEMP_FILE="$FINAL_FILE"
|
|
231
|
+
fi
|
|
232
|
+
|
|
233
|
+
# @function play_audio
|
|
234
|
+
# @intent Play generated audio using macOS native player
|
|
235
|
+
LOCK_FILE="/tmp/agentvibes-audio.lock"
|
|
236
|
+
|
|
237
|
+
# Wait for previous audio to finish (max 30 seconds)
|
|
238
|
+
for i in {1..60}; do
|
|
239
|
+
if [ ! -f "$LOCK_FILE" ]; then
|
|
240
|
+
break
|
|
241
|
+
fi
|
|
242
|
+
sleep 0.5
|
|
243
|
+
done
|
|
244
|
+
|
|
245
|
+
# Create lock and play audio
|
|
246
|
+
touch "$LOCK_FILE"
|
|
247
|
+
|
|
248
|
+
# Get audio duration for proper lock timing
|
|
249
|
+
if command -v ffprobe &> /dev/null; then
|
|
250
|
+
DURATION=$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$TEMP_FILE" 2>/dev/null)
|
|
251
|
+
DURATION=${DURATION%.*} # Round to integer
|
|
252
|
+
else
|
|
253
|
+
# Estimate duration based on text length (~150 WPM)
|
|
254
|
+
WORD_COUNT=$(echo "$TEXT" | wc -w)
|
|
255
|
+
DURATION=$(( (WORD_COUNT * 60 / 150) + 1 ))
|
|
256
|
+
fi
|
|
257
|
+
DURATION=${DURATION:-2} # Default to 2 seconds if detection fails
|
|
258
|
+
|
|
259
|
+
# Play audio in background (skip if in test mode)
|
|
260
|
+
if [[ "${AGENTVIBES_TEST_MODE:-false}" != "true" ]]; then
|
|
261
|
+
afplay "$TEMP_FILE" >/dev/null 2>&1 &
|
|
262
|
+
PLAYER_PID=$!
|
|
263
|
+
fi
|
|
264
|
+
|
|
265
|
+
# Wait for audio to finish, then release lock
|
|
266
|
+
(sleep $DURATION; rm -f "$LOCK_FILE") &
|
|
267
|
+
disown
|
|
268
|
+
|
|
269
|
+
echo "🎵 Saved to: $TEMP_FILE"
|
|
270
|
+
echo "🎤 Voice used: $VOICE_NAME (macOS Say)"
|