agentvibes 2.0.6 → 2.0.7

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.
@@ -1,16 +1,17 @@
1
1
  ---
2
- description: Preview ElevenLabs TTS voices by playing audio samples
2
+ description: Preview TTS voices by playing audio samples (provider-aware)
3
3
  argument-hint: [voice_name|first|last] [N]
4
4
  ---
5
5
 
6
- Preview ElevenLabs TTS voices by playing audio samples.
6
+ Preview TTS voices by playing audio samples from your active provider.
7
7
 
8
8
  Usage examples:
9
9
  - `/agent-vibes:preview` - Preview first 3 voices (default)
10
10
  - `/agent-vibes:preview 5` - Preview first 5 voices
11
- - `/agent-vibes:preview Jessica` - Preview Jessica Anne Bogart voice
11
+ - `/agent-vibes:preview Jessica` - Preview Jessica Anne Bogart voice (ElevenLabs)
12
+ - `/agent-vibes:preview lessac` - Preview Lessac voice (Piper)
12
13
  - `/agent-vibes:preview "Northern Terry"` - Preview Northern Terry voice
13
14
  - `/agent-vibes:preview first 10` - Preview first 10 voices
14
15
  - `/agent-vibes:preview last 5` - Preview last 5 voices
15
16
 
16
- !bash .claude/hooks/voice-manager.sh preview $ARGUMENTS
17
+ !bash .claude/hooks/provider-commands.sh preview $ARGUMENTS
@@ -139,52 +139,54 @@ get_best_voice_for_language() {
139
139
  echo "${LANGUAGE_VOICES[$lang]}"
140
140
  }
141
141
 
142
- # Main command handler
143
- case "${1:-}" in
144
- set)
145
- if [[ -z "$2" ]]; then
146
- echo "Usage: language-manager.sh set <language>"
142
+ # Main command handler - only run if script is executed directly, not sourced
143
+ if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
144
+ case "${1:-}" in
145
+ set)
146
+ if [[ -z "$2" ]]; then
147
+ echo "Usage: language-manager.sh set <language>"
148
+ exit 1
149
+ fi
150
+ set_language "$2"
151
+ ;;
152
+ get)
153
+ get_language
154
+ ;;
155
+ code)
156
+ get_language_code
157
+ ;;
158
+ check-voice)
159
+ if [[ -z "$2" ]]; then
160
+ echo "Usage: language-manager.sh check-voice <voice-name>"
161
+ exit 1
162
+ fi
163
+ if is_voice_multilingual "$2"; then
164
+ echo "yes"
165
+ else
166
+ echo "no"
167
+ fi
168
+ ;;
169
+ best-voice)
170
+ get_best_voice_for_language
171
+ ;;
172
+ list)
173
+ echo "Supported languages and recommended voices:"
174
+ echo ""
175
+ for lang in "${!LANGUAGE_VOICES[@]}"; do
176
+ printf "%-15s → %s\n" "$lang" "${LANGUAGE_VOICES[$lang]}"
177
+ done | sort
178
+ ;;
179
+ *)
180
+ echo "AgentVibes Language Manager"
181
+ echo ""
182
+ echo "Usage:"
183
+ echo " language-manager.sh set <language> Set language"
184
+ echo " language-manager.sh get Get current language"
185
+ echo " language-manager.sh code Get language code only"
186
+ echo " language-manager.sh check-voice <name> Check if voice is multilingual"
187
+ echo " language-manager.sh best-voice Get best voice for current language"
188
+ echo " language-manager.sh list List all supported languages"
147
189
  exit 1
148
- fi
149
- set_language "$2"
150
- ;;
151
- get)
152
- get_language
153
- ;;
154
- code)
155
- get_language_code
156
- ;;
157
- check-voice)
158
- if [[ -z "$2" ]]; then
159
- echo "Usage: language-manager.sh check-voice <voice-name>"
160
- exit 1
161
- fi
162
- if is_voice_multilingual "$2"; then
163
- echo "yes"
164
- else
165
- echo "no"
166
- fi
167
- ;;
168
- best-voice)
169
- get_best_voice_for_language
170
- ;;
171
- list)
172
- echo "Supported languages and recommended voices:"
173
- echo ""
174
- for lang in "${!LANGUAGE_VOICES[@]}"; do
175
- printf "%-15s → %s\n" "$lang" "${LANGUAGE_VOICES[$lang]}"
176
- done | sort
177
- ;;
178
- *)
179
- echo "AgentVibes Language Manager"
180
- echo ""
181
- echo "Usage:"
182
- echo " language-manager.sh set <language> Set language"
183
- echo " language-manager.sh get Get current language"
184
- echo " language-manager.sh code Get language code only"
185
- echo " language-manager.sh check-voice <name> Check if voice is multilingual"
186
- echo " language-manager.sh best-voice Get best voice for current language"
187
- echo " language-manager.sh list List all supported languages"
188
- exit 1
189
- ;;
190
- esac
190
+ ;;
191
+ esac
192
+ fi
@@ -32,7 +32,7 @@ DEFAULT_VOICE="en_US-lessac-medium"
32
32
  VOICE_MODEL=""
33
33
 
34
34
  # Get current language setting
35
- CURRENT_LANGUAGE=$(get_current_language)
35
+ CURRENT_LANGUAGE=$(get_language_code)
36
36
 
37
37
  if [[ -n "$VOICE_OVERRIDE" ]]; then
38
38
  # Use override if provided
@@ -1,138 +1,40 @@
1
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
2
  #
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
3
+ # @fileoverview TTS Provider Router
4
+ # @context Routes TTS requests to active provider (ElevenLabs or Piper)
5
+ # @architecture Provider abstraction layer - single entry point for all TTS
6
+ # @dependencies provider-manager.sh, play-tts-elevenlabs.sh, play-tts-piper.sh
7
+ # @entrypoints Called by hooks, slash commands, and personality-manager.sh
8
+ # @patterns Provider pattern - delegates to provider-specific implementations
9
+ # @related provider-manager.sh, play-tts-elevenlabs.sh, play-tts-piper.sh
9
10
  #
10
- # This allows different sessions to use different voices for easy identification!
11
11
 
12
12
  # Fix locale warnings
13
13
  export LC_ALL=C
14
14
 
15
15
  TEXT="$1"
16
- VOICE_OVERRIDE="$2" # Optional: voice name or direct voice ID
17
- API_KEY="${ELEVENLABS_API_KEY}"
16
+ VOICE_OVERRIDE="$2" # Optional: voice name or ID
18
17
 
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
18
+ # Get script directory
37
19
  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
20
 
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
21
+ # Source provider manager to get active provider
22
+ source "$SCRIPT_DIR/provider-manager.sh"
23
+
24
+ # Get active provider
25
+ ACTIVE_PROVIDER=$(get_active_provider)
26
+
27
+ # Route to appropriate provider implementation
28
+ case "$ACTIVE_PROVIDER" in
29
+ elevenlabs)
30
+ exec "$SCRIPT_DIR/play-tts-elevenlabs.sh" "$TEXT" "$VOICE_OVERRIDE"
31
+ ;;
32
+ piper)
33
+ exec "$SCRIPT_DIR/play-tts-piper.sh" "$TEXT" "$VOICE_OVERRIDE"
34
+ ;;
35
+ *)
36
+ echo "❌ Unknown provider: $ACTIVE_PROVIDER"
37
+ echo " Run: /agent-vibes:provider list"
38
+ exit 1
39
+ ;;
40
+ esac
@@ -292,10 +292,49 @@ provider_preview() {
292
292
  ;;
293
293
  piper)
294
294
  # Use the Piper voice manager's list functionality
295
- # For Piper, we'll list and play sample from available voices
296
295
  source "$SCRIPT_DIR/piper-voice-manager.sh"
297
296
 
298
- # Play intro announcement
297
+ # Check if a specific voice was requested
298
+ local voice_arg="$1"
299
+
300
+ if [[ -n "$voice_arg" ]]; then
301
+ # User requested a specific voice - check if it's a valid Piper voice
302
+ # Piper voice names are like: en_US-lessac-medium
303
+ # Try to find a matching voice model
304
+
305
+ # Check if the voice arg looks like a Piper model name (contains underscores/hyphens)
306
+ if [[ "$voice_arg" =~ ^[a-z]{2}_[A-Z]{2}- ]]; then
307
+ # Looks like a Piper voice model name
308
+ if verify_voice "$voice_arg"; then
309
+ echo "🎤 Previewing Piper voice: $voice_arg"
310
+ echo ""
311
+ "$SCRIPT_DIR/play-tts.sh" "Hello, this is the $voice_arg voice. How do you like it?" "$voice_arg"
312
+ else
313
+ echo "❌ Voice model not found: $voice_arg"
314
+ echo ""
315
+ echo "💡 Piper voice names look like: en_US-lessac-medium"
316
+ echo " Run /agent-vibes:list to see available Piper voices"
317
+ fi
318
+ else
319
+ # Looks like an ElevenLabs voice name (like "Antoni", "Jessica")
320
+ echo "❌ '$voice_arg' appears to be an ElevenLabs voice"
321
+ echo ""
322
+ echo "You're currently using Piper TTS (free provider)."
323
+ echo "Piper has different voices than ElevenLabs."
324
+ echo ""
325
+ echo "Options:"
326
+ echo " 1. Run /agent-vibes:list to see available Piper voices"
327
+ echo " 2. Switch to ElevenLabs: /agent-vibes:provider switch elevenlabs"
328
+ echo ""
329
+ echo "Popular Piper voices to try:"
330
+ echo " • en_US-lessac-medium (clear, professional)"
331
+ echo " • en_US-amy-medium (warm, friendly)"
332
+ echo " • en_US-joe-medium (casual, natural)"
333
+ fi
334
+ return
335
+ fi
336
+
337
+ # No specific voice - preview first 3 voices
299
338
  echo "🎤 Piper Preview of 3 people"
300
339
  echo ""
301
340
 
@@ -0,0 +1 @@
1
+ /home/fire/.claude/piper-voices
package/README.md CHANGED
@@ -11,7 +11,7 @@
11
11
  [![Publish](https://github.com/paulpreibisch/AgentVibes/actions/workflows/publish.yml/badge.svg)](https://github.com/paulpreibisch/AgentVibes/actions/workflows/publish.yml)
12
12
  [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
13
13
 
14
- **Author**: Paul Preibisch ([@997Fire](https://x.com/997Fire)) | **Version**: v2.0.6
14
+ **Author**: Paul Preibisch ([@997Fire](https://x.com/997Fire)) | **Version**: v2.0.7
15
15
 
16
16
  ---
17
17
 
@@ -45,7 +45,7 @@
45
45
 
46
46
  ## 📰 Latest Release
47
47
 
48
- **[v2.0.0 - The Multi-Provider Revolution](https://github.com/paulpreibisch/AgentVibes/releases/tag/v2.0.6)** 🎉
48
+ **[v2.0.0 - The Multi-Provider Revolution](https://github.com/paulpreibisch/AgentVibes/releases/tag/v2.0.7)** 🎉
49
49
 
50
50
  The biggest update ever! **Multi-provider TTS support** (ElevenLabs + Piper TTS), **30+ languages**, expanded voice library (27+ voices), advanced sentiment system, enhanced BMAD integration, and comprehensive multilingual support. Choose between premium ElevenLabs voices or free offline Piper TTS!
51
51
 
package/RELEASE_NOTES.md CHANGED
@@ -1,5 +1,207 @@
1
1
  # 🎤 AgentVibes Release Notes
2
2
 
3
+ ## 📦 v2.0.7 - Bug Fixes & UX Improvements (2025-01-07)
4
+
5
+ ### 🤖 AI Summary
6
+
7
+ This patch release fixes critical issues with the voice preview command and significantly improves the installer UX. The `/agent-vibes:preview` command now correctly handles provider-specific voices and provides helpful guidance when users try to preview voices from the wrong provider. The installer adds interactive provider selection with automatic API key setup and shell configuration, making first-time setup much smoother.
8
+
9
+ ### 🐛 Bug Fixes
10
+
11
+ #### Voice Preview Command Fixed
12
+ - **Fixed provider-aware voice previewing** - The `/agent-vibes:preview` command now correctly routes through the provider system instead of directly calling ElevenLabs-specific code
13
+ - **Intelligent voice detection** - Detects when you try to preview an ElevenLabs voice (like "Antoni") while using Piper and provides helpful guidance with alternatives
14
+ - **Support for specific voice previews** - Can now preview individual Piper voices by model name (e.g., `/agent-vibes:preview en_US-lessac-medium`)
15
+ - **Fixed language-manager error** - Resolved issue where sourcing `language-manager.sh` would trigger unwanted command handler execution showing "AgentVibes Language Manager" usage text
16
+ - **Fixed function name mismatch** - Corrected `get_current_language` to `get_language_code` in play-tts-piper.sh
17
+
18
+ #### Provider Routing Improvements
19
+ - **Simplified play-tts.sh router** - Streamlined routing logic for cleaner provider delegation
20
+ - **Fixed provider routing** - Ensures TTS requests always route to the active provider correctly
21
+ - **Better error handling** - Clear, helpful messages when voice/provider mismatch occurs
22
+
23
+ ### ✨ Installer UX Enhancements
24
+
25
+ #### Interactive Provider Selection
26
+ - **Provider choice prompt** - Installer now asks which TTS provider you want (Piper or ElevenLabs) with clear descriptions
27
+ - **Automatic API key setup** - Detects your shell (bash/zsh) and offers to add ELEVENLABS_API_KEY to shell config file
28
+ - **Shell detection** - Intelligently detects whether you're using bash or zsh and configures the correct file
29
+ - **Multiple setup paths** - Choose between automatic shell config, manual setup, or skip API key configuration
30
+ - **Piper voices path configuration** - Added prompt for custom Piper voice storage location
31
+
32
+ #### Clearer Installation Messaging
33
+ - **Better location explanation** - Clear explanation of why AgentVibes installs in `.claude/` directory (Claude Code auto-discovery)
34
+ - **Removed confusing prompts** - Simplified installation directory selection to avoid confusion
35
+ - **Better confirmation flow** - Two-step confirmation: location first, then provider/installation
36
+ - **Installation summary** - Shows exactly what will be installed before proceeding
37
+
38
+ ### 🔧 Update Command Improvements
39
+
40
+ - **Fixed version display** - Update command now correctly shows v2.0.x instead of v1.1.3
41
+ - **Synced with install command** - Both install and update commands now show identical release notes and formatting
42
+ - **Directory filtering** - Properly filters out directories when counting hooks and personalities
43
+ - **Consistent formatting** - Matches install command's beautiful display style
44
+
45
+ ### 🛠️ Code Quality
46
+
47
+ - **Fixed undefined variable** - Replaced `srcPersonalityFiles` with correct variable name
48
+ - **Proper scope management** - Moved `piperVoicesPath` declaration to correct scope to avoid undefined errors
49
+ - **Command handler isolation** - Wrapped language-manager.sh case statement to only run when executed directly, not when sourced
50
+
51
+ ---
52
+
53
+ ### 📊 Changes Summary
54
+
55
+ **Files Modified:** 8 files
56
+ - `.claude/commands/agent-vibes/preview.md` - Provider-aware routing
57
+ - `.claude/hooks/language-manager.sh` - Command handler isolation fix
58
+ - `.claude/hooks/play-tts-piper.sh` - Function name correction
59
+ - `.claude/hooks/play-tts.sh` - Simplified router
60
+ - `.claude/hooks/provider-commands.sh` - Enhanced Piper preview support
61
+ - `src/installer.js` - Interactive setup & UX improvements
62
+ - `.claude/commands/release.md` - Documentation update
63
+ - `.claude/piper-voices-dir.txt` - Storage config
64
+
65
+ **Lines Changed:**
66
+ - Added: 275 lines
67
+ - Removed: 354 lines
68
+ - Net: -79 lines (cleaner codebase!)
69
+
70
+ ---
71
+
72
+ ### 🎯 What's Improved
73
+
74
+ #### For New Users
75
+ - **Much easier setup** - Interactive prompts guide you through provider selection and API key configuration
76
+ - **Clearer explanations** - Better messaging about where files are installed and why (Claude Code auto-discovery)
77
+ - **Faster onboarding** - Shell detection and automatic config file modification save manual steps
78
+
79
+ #### For Existing Users
80
+ - **Preview command works correctly** - No more language-manager errors when previewing voices
81
+ - **Provider switching is seamless** - Better error messages when voice/provider mismatch occurs
82
+ - **Update command is accurate** - Shows correct version and release notes instead of old v1.1.3
83
+
84
+ #### For Developers
85
+ - **Cleaner codebase** - Removed 79 lines of unnecessary code
86
+ - **Better separation of concerns** - Command handlers only run when appropriate
87
+ - **Improved maintainability** - More consistent code patterns across scripts
88
+
89
+ ---
90
+
91
+ ### 🔧 Technical Details
92
+
93
+ #### Provider Preview Architecture
94
+ The preview command now uses a three-tier detection system:
95
+
96
+ 1. **ElevenLabs Provider**: Routes to `voice-manager.sh preview` for ElevenLabs voice listing
97
+ 2. **Piper Provider with voice arg**:
98
+ - Detects Piper voice format (`en_US-*-medium`)
99
+ - Detects ElevenLabs voice names (shows helpful error with alternatives)
100
+ - Validates voice model exists before previewing
101
+ 3. **Piper Provider without args**: Shows first 3 sample voices (Lessac, Amy, Joe)
102
+
103
+ #### Language Manager Fix
104
+ The `language-manager.sh` script now checks if it's being executed directly vs sourced:
105
+
106
+ ```bash
107
+ if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
108
+ # Only run command handler when executed directly
109
+ case "${1:-}" in
110
+ set|get|code|check-voice|best-voice|list)
111
+ # Handle commands
112
+ ;;
113
+ esac
114
+ fi
115
+ ```
116
+
117
+ This prevents the case statement from executing when the script is sourced by other scripts like `play-tts-piper.sh`.
118
+
119
+ #### Installer Flow
120
+ ```
121
+ 1. Show installation details and location
122
+ 2. Provider selection (Piper/ElevenLabs)
123
+ ├─ If ElevenLabs: Check for API key
124
+ │ ├─ Detect shell (bash/zsh)
125
+ │ ├─ Offer to add to shell config
126
+ │ ├─ Manual setup option
127
+ │ └─ Skip option
128
+ └─ If Piper: Ask for voice storage path
129
+ 3. Explain .claude/ installation location with reasoning
130
+ 4. Confirm installation location
131
+ 5. Show installation summary
132
+ 6. Final confirmation
133
+ 7. Install all files
134
+ 8. Show success summary with next steps
135
+ ```
136
+
137
+ ---
138
+
139
+ ### 💡 Usage Examples
140
+
141
+ #### Preview Commands
142
+ ```bash
143
+ # Preview with Piper (no args = first 3 voices)
144
+ /agent-vibes:preview
145
+
146
+ # Preview specific Piper voice
147
+ /agent-vibes:preview en_US-lessac-medium
148
+
149
+ # Try to preview ElevenLabs voice while using Piper
150
+ /agent-vibes:preview Antoni
151
+ # ❌ 'Antoni' appears to be an ElevenLabs voice
152
+ # You're currently using Piper TTS (free provider).
153
+ # Options:
154
+ # 1. Run /agent-vibes:list to see available Piper voices
155
+ # 2. Switch to ElevenLabs: /agent-vibes:provider switch elevenlabs
156
+ ```
157
+
158
+ #### Installer Provider Selection
159
+ ```bash
160
+ npx agentvibes install
161
+
162
+ # 🎭 Choose Your TTS Provider:
163
+ # ? Which TTS provider would you like to use?
164
+ # 🆓 Piper TTS (Free, Offline) - 50+ neural voices, no API key needed
165
+ # 🎤 ElevenLabs (Premium) - 150+ AI voices, requires API key
166
+ ```
167
+
168
+ ---
169
+
170
+ ### 📦 Upgrade Notes
171
+
172
+ **From v2.0.6:**
173
+ ```bash
174
+ npm update -g agentvibes
175
+ # or
176
+ /agent-vibes:update
177
+ ```
178
+
179
+ **No breaking changes** - This is a pure bug fix and UX improvement release. All existing configurations, voices, personalities, and settings are preserved.
180
+
181
+ ---
182
+
183
+ ### 🙏 Credits
184
+
185
+ - **Voice Preview Fix**: Resolved GitHub issue reported by users experiencing language-manager errors
186
+ - **Provider Architecture**: Multi-provider system improvements continue to mature
187
+ - **Installer UX**: Community feedback on first-time setup experience led to these improvements
188
+
189
+ ---
190
+
191
+ ### 📚 Resources
192
+
193
+ - **Documentation**: https://agentvibes.org
194
+ - **GitHub**: https://github.com/paulpreibisch/AgentVibes
195
+ - **Issues**: https://github.com/paulpreibisch/AgentVibes/issues
196
+
197
+ ---
198
+
199
+ 🤖 Generated with [Claude Code](https://claude.com/claude-code)
200
+
201
+ Co-Authored-By: Claude <noreply@anthropic.com>
202
+
203
+ ---
204
+
3
205
  ## 📦 v1.1.3 - Symlink Support & Audio Fixes (2025-10-04)
4
206
 
5
207
  ### 🤖 AI Summary
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "agentvibes",
4
- "version": "2.0.6",
4
+ "version": "2.0.7",
5
5
  "description": "Bring your Claude Code sessions to life with voice! Professional TTS narration with multi-provider support.",
6
6
  "homepage": "https://agentvibes.org",
7
7
  "keywords": [
package/src/installer.js CHANGED
@@ -70,7 +70,7 @@ async function install(options = {}) {
70
70
  const currentDir = process.env.INIT_CWD || process.cwd();
71
71
 
72
72
  console.log(chalk.cyan('\n📍 Installation Details:'));
73
- console.log(chalk.gray(` Current directory: ${currentDir}`));
73
+ console.log(chalk.gray(` Install location: ${currentDir}/.claude/`));
74
74
  console.log(chalk.gray(` Package version: ${VERSION}`));
75
75
 
76
76
  // Show latest release notes from git log
@@ -97,6 +97,7 @@ async function install(options = {}) {
97
97
  // Provider selection prompt
98
98
  let selectedProvider = 'piper';
99
99
  let elevenLabsKey = process.env.ELEVENLABS_API_KEY;
100
+ let piperVoicesPath = null;
100
101
 
101
102
  if (!options.yes) {
102
103
  console.log(chalk.cyan('🎭 Choose Your TTS Provider:\n'));
@@ -123,7 +124,6 @@ async function install(options = {}) {
123
124
  selectedProvider = provider;
124
125
 
125
126
  // If Piper selected, ask for voice storage location
126
- let piperVoicesPath = null;
127
127
  if (selectedProvider === 'piper') {
128
128
  const homeDir = process.env.HOME || process.env.USERPROFILE;
129
129
  const defaultPiperPath = path.join(homeDir, '.claude', 'piper-voices');
@@ -284,34 +284,34 @@ async function install(options = {}) {
284
284
  }
285
285
  }
286
286
 
287
- // Ask for installation directory
288
- let targetDir = options.directory || currentDir;
287
+ // Use current directory for installation (where installer was run)
288
+ const targetDir = options.directory || currentDir;
289
289
 
290
+ // Explain why installing in .claude/ and confirm
290
291
  if (!options.yes) {
291
- console.log(chalk.cyan('\n📂 AgentVibes Installation Location:\n'));
292
- console.log(chalk.gray(' AgentVibes will be installed in the .claude/ subdirectory'));
293
- console.log(chalk.gray(' of your chosen location.\n'));
294
-
295
- const { installDir } = await inquirer.prompt([
292
+ console.log(chalk.cyan('\n📂 Installation Location:\n'));
293
+ console.log(chalk.white(' AgentVibes will be installed in:'));
294
+ console.log(chalk.yellow(` ${targetDir}/.claude/\n`));
295
+ console.log(chalk.gray(' Why .claude/?'));
296
+ console.log(chalk.gray(' • Claude Code automatically discovers tools in .claude/ directories'));
297
+ console.log(chalk.gray(' • This makes slash commands and TTS features immediately available'));
298
+ console.log(chalk.gray(' • Project-specific installation keeps your setup isolated\n'));
299
+
300
+ const { confirmLocation } = await inquirer.prompt([
296
301
  {
297
- type: 'input',
298
- name: 'installDir',
299
- message: 'Where should AgentVibes be installed?',
300
- default: currentDir,
301
- validate: (input) => {
302
- if (!input || input.trim() === '') {
303
- return 'Please provide a valid directory path';
304
- }
305
- return true;
306
- },
302
+ type: 'confirm',
303
+ name: 'confirmLocation',
304
+ message: `Install AgentVibes in ${targetDir}/.claude/ ?`,
305
+ default: true,
307
306
  },
308
307
  ]);
309
308
 
310
- targetDir = installDir;
311
- console.log(chalk.green(`✓ AgentVibes will be installed in: ${targetDir}/.claude/`));
309
+ if (!confirmLocation) {
310
+ console.log(chalk.red('\n❌ Installation cancelled.\n'));
311
+ process.exit(0);
312
+ }
312
313
  }
313
314
 
314
- // Show installation summary
315
315
  console.log(chalk.cyan('\n📦 What will be installed:'));
316
316
  console.log(chalk.gray(` • 16 slash commands → ${targetDir}/.claude/commands/agent-vibes/`));
317
317
  console.log(chalk.gray(` • Multi-provider TTS system (ElevenLabs + Piper TTS) → ${targetDir}/.claude/hooks/`));
@@ -322,13 +322,13 @@ async function install(options = {}) {
322
322
  console.log(chalk.gray(` • 30+ language support with native voices`));
323
323
  console.log(chalk.gray(` • BMAD integration for multi-agent sessions\n`));
324
324
 
325
- // Confirmation prompt (unless --yes flag is used)
325
+ // Final confirmation prompt (unless --yes flag is used)
326
326
  if (!options.yes) {
327
327
  const { confirm } = await inquirer.prompt([
328
328
  {
329
329
  type: 'confirm',
330
330
  name: 'confirm',
331
- message: chalk.yellow(`Install AgentVibes with ${selectedProvider === 'elevenlabs' ? 'ElevenLabs' : 'Piper TTS'} in ${targetDir}/.claude/ ?`),
331
+ message: chalk.yellow(`Proceed with installation using ${selectedProvider === 'elevenlabs' ? 'ElevenLabs' : 'Piper TTS'}?`),
332
332
  default: true,
333
333
  },
334
334
  ]);
@@ -727,84 +727,10 @@ program
727
727
  );
728
728
 
729
729
  console.log(chalk.cyan('📍 Update Details:'));
730
- console.log(chalk.white(` Current directory: ${currentDir}`));
731
- console.log(chalk.white(` Update location: ${targetDir}/.claude/ (project-local)`));
732
- console.log(chalk.white(` Package version: ${version}\n`));
733
-
734
- // Check if already installed
735
- const commandsDir = path.join(targetDir, '.claude', 'commands', 'agent-vibes');
736
- let isInstalled = false;
737
- try {
738
- await fs.access(commandsDir);
739
- isInstalled = true;
740
- } catch {}
741
-
742
- if (!isInstalled) {
743
- console.log(chalk.red('❌ AgentVibes is not installed in this directory.'));
744
- console.log(chalk.gray(' Run: node src/installer.js install\n'));
745
- process.exit(1);
746
- }
730
+ console.log(chalk.gray(` Update location: ${targetDir}/.claude/`));
731
+ console.log(chalk.gray(` Package version: ${version}`));
747
732
 
748
- // Show latest release notes from RELEASE_NOTES.md
749
- try {
750
- const releaseNotesPath = path.join(__dirname, '..', 'RELEASE_NOTES.md');
751
- const releaseNotes = await fs.readFile(releaseNotesPath, 'utf8');
752
-
753
- // Extract latest release summary
754
- const lines = releaseNotes.split('\n');
755
-
756
- // Find the first release version header
757
- const versionIndex = lines.findIndex(line => line.match(/^## 📦 v\d+\.\d+\.\d+/));
758
-
759
- if (versionIndex >= 0) {
760
- // Extract version
761
- const versionMatch = lines[versionIndex].match(/v(\d+\.\d+\.\d+)/);
762
- const version = versionMatch ? versionMatch[1] : 'unknown';
763
-
764
- // Find the AI Summary section
765
- const summaryIndex = lines.findIndex((line, idx) =>
766
- idx > versionIndex && line.includes('### 🤖 AI Summary')
767
- );
768
-
769
- if (summaryIndex >= 0) {
770
- console.log(chalk.cyan(`📰 Latest Release (v${version}):\n`));
771
-
772
- // Extract summary text (lines between AI Summary and next ###)
773
- let summaryText = '';
774
- for (let i = summaryIndex + 1; i < lines.length; i++) {
775
- const line = lines[i];
776
- if (line.startsWith('###') || line.startsWith('##')) break;
777
- if (line.trim()) {
778
- summaryText += line.trim() + ' ';
779
- }
780
- }
781
-
782
- // Wrap text at ~80 chars for better readability
783
- const words = summaryText.split(' ');
784
- let currentLine = '';
785
- const wrappedLines = [];
786
-
787
- words.forEach(word => {
788
- if ((currentLine + word).length > 80) {
789
- wrappedLines.push(currentLine.trim());
790
- currentLine = word + ' ';
791
- } else {
792
- currentLine += word + ' ';
793
- }
794
- });
795
- if (currentLine.trim()) wrappedLines.push(currentLine.trim());
796
-
797
- wrappedLines.forEach(line => {
798
- console.log(chalk.white(` ${line}`));
799
- });
800
- console.log();
801
- }
802
- }
803
- } catch {
804
- // Release notes not available - no problem
805
- }
806
-
807
- // Show latest commit messages
733
+ // Show latest release notes from git log
808
734
  try {
809
735
  const { execSync } = await import('node:child_process');
810
736
  const gitLog = execSync(
@@ -813,55 +739,33 @@ program
813
739
  ).trim();
814
740
 
815
741
  if (gitLog) {
816
- console.log(chalk.cyan('📝 Latest Commit Messages:\n'));
742
+ console.log(chalk.cyan('\n📰 Latest Release Notes:'));
817
743
  const commits = gitLog.split('\n');
818
744
  commits.forEach(commit => {
819
745
  const [hash, ...messageParts] = commit.split(' ');
820
746
  const message = messageParts.join(' ');
821
747
  console.log(chalk.gray(` ${hash}`) + ' ' + chalk.white(message));
822
748
  });
823
- console.log();
824
749
  }
825
750
  } catch (error) {
826
- // Git not available - try RELEASE_NOTES.md fallback
827
- try {
828
- const releaseNotesPath = path.join(__dirname, '..', 'RELEASE_NOTES.md');
829
- const releaseNotes = await fs.readFile(releaseNotesPath, 'utf8');
830
-
831
- // Extract commits from "Recent Commits" section
832
- const lines = releaseNotes.split('\n');
833
- const commitsIndex = lines.findIndex(line => line.includes('## 📝 Recent Commits'));
834
-
835
- if (commitsIndex >= 0) {
836
- console.log(chalk.cyan('📝 Latest Commit Messages:\n'));
837
-
838
- // Find the code block with commits (between ``` markers)
839
- let inCodeBlock = false;
840
- for (let i = commitsIndex + 1; i < lines.length; i++) {
841
- const line = lines[i];
751
+ // Git not available or not a git repo - skip release notes
752
+ }
842
753
 
843
- if (line.trim() === '```') {
844
- if (inCodeBlock) break; // End of code block
845
- inCodeBlock = true;
846
- continue;
847
- }
754
+ // Check if already installed
755
+ const commandsDir = path.join(targetDir, '.claude', 'commands', 'agent-vibes');
756
+ let isInstalled = false;
757
+ try {
758
+ await fs.access(commandsDir);
759
+ isInstalled = true;
760
+ } catch {}
848
761
 
849
- if (inCodeBlock && line.trim()) {
850
- // Parse commit line: "hash message"
851
- const match = line.match(/^([a-f0-9]+)\s+(.+)$/);
852
- if (match) {
853
- const [, hash, message] = match;
854
- console.log(chalk.gray(` ${hash}`) + ' ' + chalk.white(message));
855
- }
856
- }
857
- }
858
- console.log();
859
- }
860
- } catch {
861
- // No release notes available
862
- }
762
+ if (!isInstalled) {
763
+ console.log(chalk.red('\n❌ AgentVibes is not installed in this directory.'));
764
+ console.log(chalk.gray(' Run: npx agentvibes install\n'));
765
+ process.exit(1);
863
766
  }
864
767
 
768
+
865
769
  console.log(chalk.cyan('📦 What will be updated:'));
866
770
  console.log(chalk.gray(' • Slash commands (keep your customizations)'));
867
771
  console.log(chalk.gray(' • TTS scripts'));
@@ -911,11 +815,20 @@ program
911
815
  spinner.text = 'Updating TTS scripts...';
912
816
  const srcHooksDir = path.join(__dirname, '..', '.claude', 'hooks');
913
817
  const allHookFiles = await fs.readdir(srcHooksDir);
914
- // Only copy AgentVibes-related scripts, exclude project-specific files
915
- const hookFiles = allHookFiles.filter(file =>
916
- !file.includes('prepare-release') &&
917
- !file.startsWith('.')
918
- );
818
+
819
+ // Filter to only include files (not directories) and exclude project-specific files
820
+ const hookFiles = [];
821
+ for (const file of allHookFiles) {
822
+ const srcPath = path.join(srcHooksDir, file);
823
+ const stat = await fs.stat(srcPath);
824
+
825
+ if (stat.isFile() &&
826
+ file.endsWith('.sh') &&
827
+ !file.includes('prepare-release') &&
828
+ !file.startsWith('.')) {
829
+ hookFiles.push(file);
830
+ }
831
+ }
919
832
 
920
833
  for (const file of hookFiles) {
921
834
  const srcPath = path.join(srcHooksDir, file);
@@ -928,12 +841,20 @@ program
928
841
  // Update personalities (only add new ones, don't overwrite existing)
929
842
  spinner.text = 'Updating personality templates...';
930
843
  const srcPersonalitiesDir = path.join(__dirname, '..', '.claude', 'personalities');
931
- const srcPersonalityFiles = await fs.readdir(srcPersonalitiesDir);
844
+ const allPersonalityFiles = await fs.readdir(srcPersonalitiesDir);
932
845
  let newPersonalities = 0;
933
846
  let updatedPersonalities = 0;
934
847
 
935
- for (const file of srcPersonalityFiles) {
848
+ // Filter to only .md files, skip directories
849
+ for (const file of allPersonalityFiles) {
936
850
  const srcPath = path.join(srcPersonalitiesDir, file);
851
+ const stat = await fs.stat(srcPath);
852
+
853
+ // Only copy .md files, skip directories
854
+ if (!stat.isFile() || !file.endsWith('.md')) {
855
+ continue;
856
+ }
857
+
937
858
  const destPath = path.join(personalitiesDir, file);
938
859
 
939
860
  try {
@@ -966,44 +887,53 @@ program
966
887
  console.log(chalk.cyan('📦 Update Summary:'));
967
888
  console.log(chalk.white(` • ${commandFiles.length} commands updated`));
968
889
  console.log(chalk.white(` • ${hookFiles.length} TTS scripts updated`));
969
- console.log(chalk.white(` • ${srcPersonalityFiles.length} personality templates (${newPersonalities} new, ${updatedPersonalities} updated)`));
890
+ console.log(chalk.white(` • ${newPersonalities + updatedPersonalities} personality templates (${newPersonalities} new, ${updatedPersonalities} updated)`));
970
891
  console.log(chalk.white(` • ${outputStyleFiles.length} output styles updated\n`));
971
892
 
972
- // Show latest release notes from RELEASE_NOTES.md
893
+ // Show latest release notes from RELEASE_NOTES_V2.md (v2.0+) or RELEASE_NOTES.md (legacy)
973
894
  try {
974
- const releaseNotesPath = path.join(__dirname, '..', 'RELEASE_NOTES.md');
975
- const releaseNotes = await fs.readFile(releaseNotesPath, 'utf8');
976
-
977
- // Extract latest release summary
978
- const lines = releaseNotes.split('\n');
895
+ // Try v2.0 format first
896
+ let releaseNotesPath = path.join(__dirname, '..', 'RELEASE_NOTES_V2.md');
897
+ let releaseNotes;
898
+ let isV2Format = true;
979
899
 
980
- // Find the first release version header
981
- const versionIndex = lines.findIndex(line => line.match(/^## 📦 v\d+\.\d+\.\d+/));
900
+ try {
901
+ releaseNotes = await fs.readFile(releaseNotesPath, 'utf8');
902
+ } catch {
903
+ // Fallback to legacy format
904
+ releaseNotesPath = path.join(__dirname, '..', 'RELEASE_NOTES.md');
905
+ releaseNotes = await fs.readFile(releaseNotesPath, 'utf8');
906
+ isV2Format = false;
907
+ }
982
908
 
983
- if (versionIndex >= 0) {
984
- // Extract version
985
- const versionMatch = lines[versionIndex].match(/v(\d+\.\d+\.\d+)/);
986
- const version = versionMatch ? versionMatch[1] : 'unknown';
909
+ const lines = releaseNotes.split('\n');
987
910
 
988
- // Find the AI Summary section
989
- const summaryIndex = lines.findIndex((line, idx) =>
990
- idx > versionIndex && line.includes('### 🤖 AI Summary')
991
- );
911
+ if (isV2Format) {
912
+ // v2.0 format - extract summary from top of file
913
+ const packageVersion = packageJson.version;
914
+ console.log(chalk.cyan(`📰 Latest Release (v${packageVersion}):\n`));
992
915
 
993
- if (summaryIndex >= 0) {
994
- console.log(chalk.cyan(`📰 Latest Release (v${version}):\n`));
916
+ // Find content after "## 🚀 Major Features" line
917
+ let summaryText = '';
918
+ let foundMajorFeatures = false;
995
919
 
996
- // Extract summary text (lines between AI Summary and next ###)
997
- let summaryText = '';
998
- for (let i = summaryIndex + 1; i < lines.length; i++) {
999
- const line = lines[i];
1000
- if (line.startsWith('###') || line.startsWith('##')) break;
1001
- if (line.trim()) {
920
+ for (const line of lines) {
921
+ if (line.includes('## 🚀 Major Features')) {
922
+ foundMajorFeatures = true;
923
+ continue;
924
+ }
925
+ if (foundMajorFeatures) {
926
+ // Stop at next major heading or after 200 chars
927
+ if (line.startsWith('##') && !line.includes('Major Features')) break;
928
+ if (summaryText.length > 200) break;
929
+ if (line.trim() && !line.startsWith('#') && !line.startsWith('**Release Date')) {
1002
930
  summaryText += line.trim() + ' ';
1003
931
  }
1004
932
  }
933
+ }
1005
934
 
1006
- // Wrap text at ~80 chars for better readability
935
+ // Wrap text at ~80 chars
936
+ if (summaryText) {
1007
937
  const words = summaryText.split(' ');
1008
938
  let currentLine = '';
1009
939
  const wrappedLines = [];
@@ -1023,6 +953,50 @@ program
1023
953
  });
1024
954
  console.log();
1025
955
  }
956
+ } else {
957
+ // Legacy format (v1.x)
958
+ const versionIndex = lines.findIndex(line => line.match(/^## 📦 v\d+\.\d+\.\d+/));
959
+
960
+ if (versionIndex >= 0) {
961
+ const versionMatch = lines[versionIndex].match(/v(\d+\.\d+\.\d+)/);
962
+ const version = versionMatch ? versionMatch[1] : 'unknown';
963
+
964
+ const summaryIndex = lines.findIndex((line, idx) =>
965
+ idx > versionIndex && line.includes('### 🤖 AI Summary')
966
+ );
967
+
968
+ if (summaryIndex >= 0) {
969
+ console.log(chalk.cyan(`📰 Latest Release (v${version}):\n`));
970
+
971
+ let summaryText = '';
972
+ for (let i = summaryIndex + 1; i < lines.length; i++) {
973
+ const line = lines[i];
974
+ if (line.startsWith('###') || line.startsWith('##')) break;
975
+ if (line.trim()) {
976
+ summaryText += line.trim() + ' ';
977
+ }
978
+ }
979
+
980
+ const words = summaryText.split(' ');
981
+ let currentLine = '';
982
+ const wrappedLines = [];
983
+
984
+ words.forEach(word => {
985
+ if ((currentLine + word).length > 80) {
986
+ wrappedLines.push(currentLine.trim());
987
+ currentLine = word + ' ';
988
+ } else {
989
+ currentLine += word + ' ';
990
+ }
991
+ });
992
+ if (currentLine.trim()) wrappedLines.push(currentLine.trim());
993
+
994
+ wrappedLines.forEach(line => {
995
+ console.log(chalk.white(` ${line}`));
996
+ });
997
+ console.log();
998
+ }
999
+ }
1026
1000
  }
1027
1001
  } catch {
1028
1002
  // Release notes not available - no problem