agentvibes 2.17.6 → 2.17.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 +1 @@
1
- 20251215
1
+ 20251218
@@ -110,8 +110,8 @@ AGENT_FOR_EFFECTS="${DISPLAY_NAME:-$AGENT_NAME_OR_ID}"
110
110
  AGENT_VOICE=""
111
111
  AGENT_INTRO=""
112
112
  if [[ -n "$AGENT_ID" ]] && [[ -f "$SCRIPT_DIR/bmad-voice-manager.sh" ]]; then
113
- AGENT_VOICE=$("$SCRIPT_DIR/bmad-voice-manager.sh" get-voice "$AGENT_ID" 2>/dev/null || echo "")
114
- AGENT_INTRO=$("$SCRIPT_DIR/bmad-voice-manager.sh" get-intro "$AGENT_ID" 2>/dev/null || echo "")
113
+ AGENT_VOICE=$(cd "$PROJECT_ROOT" && "$SCRIPT_DIR/bmad-voice-manager.sh" get-voice "$AGENT_ID" 2>/dev/null || echo "")
114
+ AGENT_INTRO=$(cd "$PROJECT_ROOT" && "$SCRIPT_DIR/bmad-voice-manager.sh" get-intro "$AGENT_ID" 2>/dev/null || echo "")
115
115
  fi
116
116
 
117
117
  # Prepend intro text if configured
@@ -90,8 +90,8 @@ AGENT_ID=$(map_to_agent_id "$AGENT_NAME_OR_ID")
90
90
  AGENT_VOICE=""
91
91
  AGENT_INTRO=""
92
92
  if [[ -n "$AGENT_ID" ]] && [[ -f "$SCRIPT_DIR/bmad-voice-manager.sh" ]]; then
93
- AGENT_VOICE=$("$SCRIPT_DIR/bmad-voice-manager.sh" get-voice "$AGENT_ID" 2>/dev/null)
94
- AGENT_INTRO=$("$SCRIPT_DIR/bmad-voice-manager.sh" get-intro "$AGENT_ID" 2>/dev/null)
93
+ AGENT_VOICE=$(cd "$PROJECT_ROOT" && "$SCRIPT_DIR/bmad-voice-manager.sh" get-voice "$AGENT_ID" 2>/dev/null)
94
+ AGENT_INTRO=$(cd "$PROJECT_ROOT" && "$SCRIPT_DIR/bmad-voice-manager.sh" get-intro "$AGENT_ID" 2>/dev/null)
95
95
  fi
96
96
 
97
97
  # Prepend intro text if configured (e.g., "John, Product Manager here. [dialogue]")
@@ -247,18 +247,19 @@ inject_tts() {
247
247
  if [[ -n "$agent_voice" ]]; then
248
248
  tts_step=" <step n=\"4.5\" tts=\"agentvibes\">🎤 AGENTVIBES TTS INJECTION:
249
249
  - Create context: echo \"${agent_id}\" > .bmad-agent-context (Bash tool)
250
- - Speak greeting: .claude/hooks/play-tts.sh \"Hello! I'm ready to help you.\" \"${agent_voice}\" (Bash tool)
250
+ - Speak greeting: bash .claude/hooks/play-tts.sh \"Hello! I'm ready to help you.\" \"${agent_voice}\" (Bash tool)
251
251
  - CRITICAL: Before EVERY response, scan for questions/prompts and speak them using Bash tool</step>"
252
252
  else
253
253
  tts_step=" <step n=\"4.5\" tts=\"agentvibes\">🎤 AGENTVIBES TTS INJECTION:
254
254
  - Create context: echo \"${agent_id}\" > .bmad-agent-context (Bash tool)
255
- - Speak greeting: .claude/hooks/play-tts.sh \"Hello! I'm ready to help you.\" (Bash tool)
255
+ - Speak greeting: bash .claude/hooks/play-tts.sh \"Hello! I'm ready to help you.\" (Bash tool)
256
256
  - CRITICAL: Before EVERY response, scan for questions/prompts and speak them using Bash tool</step>"
257
257
  fi
258
258
 
259
- # Insert after step 4 (greeting) - only first match
259
+ # Insert after step 4 - only first match
260
+ # Note: Greeting is in step 5, but we inject after step 4 for proper ordering
260
261
  awk -v tts="$tts_step" '
261
- !done && /<step n="4">.*[Gg]reet/ {
262
+ !done && /<step n="4">/ {
262
263
  print
263
264
  print tts
264
265
  done=1
@@ -283,7 +284,7 @@ inject_tts() {
283
284
  # No changes - step 4 pattern didn't match
284
285
  rm "$agent_file.tmp"
285
286
  mv "$agent_file.backup-pre-tts" "$agent_file"
286
- echo -e "${RED}❌ Could not find step 4 with greeting in: $(basename "$agent_file")${NC}"
287
+ echo -e "${RED}❌ Could not find step 4 in: $(basename "$agent_file")${NC}"
287
288
  return 1
288
289
  fi
289
290
 
@@ -293,12 +294,12 @@ inject_tts() {
293
294
  if [[ -n "$agent_voice" ]]; then
294
295
  activation_injection=" - # AGENTVIBES-TTS-INJECTION: STEP 3.5 - Announce activation with TTS
295
296
  - STEP 3.5a: Use Bash tool to create context file: echo \"${agent_id}\" > .bmad-agent-context
296
- - STEP 3.5b: Use Bash tool to speak: .claude/hooks/play-tts.sh \"Agent ${agent_id} activated and ready\" \"${agent_voice}\"
297
+ - STEP 3.5b: Use Bash tool to speak: bash .claude/hooks/play-tts.sh \"Agent ${agent_id} activated and ready\" \"${agent_voice}\"
297
298
  - AGENTVIBES-TTS-INJECTION: Before every response, scan for questions and speak them using Bash tool"
298
299
  else
299
300
  activation_injection=" - # AGENTVIBES-TTS-INJECTION: STEP 3.5 - Announce activation with TTS
300
301
  - STEP 3.5a: Use Bash tool to create context file: echo \"${agent_id}\" > .bmad-agent-context
301
- - STEP 3.5b: Use Bash tool to speak: .claude/hooks/play-tts.sh \"Agent ${agent_id} activated and ready\"
302
+ - STEP 3.5b: Use Bash tool to speak: bash .claude/hooks/play-tts.sh \"Agent ${agent_id} activated and ready\"
302
303
  - AGENTVIBES-TTS-INJECTION: Before every response, scan for questions and speak them using Bash tool"
303
304
  fi
304
305
 
@@ -1,11 +1,10 @@
1
1
  #!/usr/bin/env bash
2
+ set -euo pipefail
2
3
  #
3
4
  # File: .claude/hooks/session-start-tts.sh
4
5
  #
5
- # AgentVibes SessionStart Hook - Injects TTS Protocol Instructions
6
- #
7
- # This hook outputs TTS protocol instructions to stdout, which Claude Code
8
- # adds to the conversation context at session start.
6
+ # AgentVibes SessionStart Hook - Optimized (Issue #80, Phase 1)
7
+ # Token target: ~250 (down from ~500)
9
8
  #
10
9
 
11
10
  # Fix locale warnings
@@ -24,50 +23,31 @@ fi
24
23
  SENTIMENT=$(cat .claude/tts-sentiment.txt 2>/dev/null || cat ~/.claude/tts-sentiment.txt 2>/dev/null || echo "")
25
24
  PERSONALITY=$(cat .claude/tts-personality.txt 2>/dev/null || cat ~/.claude/tts-personality.txt 2>/dev/null || echo "normal")
26
25
 
27
- # Determine which to use
26
+ # Determine style
28
27
  if [[ -n "$SENTIMENT" ]]; then
29
- STYLE_MODE="sentiment"
30
- STYLE_NAME="$SENTIMENT"
28
+ STYLE="$SENTIMENT"
31
29
  else
32
- STYLE_MODE="personality"
33
- STYLE_NAME="$PERSONALITY"
30
+ STYLE="$PERSONALITY"
34
31
  fi
35
32
 
36
33
  # Get verbosity level (Issue #32)
37
34
  VERBOSITY=$(cat .claude/tts-verbosity.txt 2>/dev/null || cat ~/.claude/tts-verbosity.txt 2>/dev/null || echo "low")
38
35
 
39
- # Output TTS protocol instructions to stdout
40
- # Claude Code will add this to the conversation context
36
+ # Output optimized TTS protocol
41
37
  cat <<'EOF'
42
38
 
43
39
  # AgentVibes TTS Protocol
44
40
 
45
41
  **CRITICAL: You MUST execute TTS at TWO points for EVERY user interaction:**
46
42
 
47
- ## 1. ACKNOWLEDGMENT (Start of Every Task)
48
- Immediately after receiving ANY user message:
49
- 1. Generate a brief, unique acknowledgment message
50
- 2. Execute: `Bash: .claude/hooks/play-tts.sh "[your message]"`
51
- 3. Then proceed with the work
52
-
53
- ## 2. COMPLETION (End of Every Task)
54
- After finishing the task:
55
- 1. Generate a brief, unique completion message
56
- 2. Execute: `Bash: .claude/hooks/play-tts.sh "[your message]"`
57
-
58
- ## Rules
59
- - Use Bash tool to execute play-tts.sh
60
- - Keep messages under 150 characters
61
- - Generate unique messages each time
62
- - Use relative path: .claude/hooks/play-tts.sh
63
- - This is NOT optional - follow for EVERY interaction
43
+ 1. **Acknowledgment** - Start of task: `Bash: .claude/hooks/play-tts.sh "[action]"`
44
+ 2. **Completion** - End of task: `Bash: .claude/hooks/play-tts.sh "[result + key details]"`
64
45
 
65
- ## Example
46
+ **Example:**
66
47
  ```
67
- User: "check git status"
68
- [Bash: .claude/hooks/play-tts.sh "I'll check that for you."]
69
- [... do work ...]
70
- [Bash: .claude/hooks/play-tts.sh "Done! All clean."]
48
+ [Bash: .claude/hooks/play-tts.sh "Checking git status"]
49
+ [work...]
50
+ [Bash: .claude/hooks/play-tts.sh "Repository is clean, no changes"]
71
51
  ```
72
52
 
73
53
  EOF
@@ -76,66 +56,46 @@ EOF
76
56
  case "$VERBOSITY" in
77
57
  low)
78
58
  cat <<'EOF'
79
- ## Verbosity: LOW (Minimal)
80
- - Speak only at acknowledgment (start) and completion (end)
81
- - Do NOT speak reasoning, decisions, or findings during work
82
- - Keep it quiet and focused
59
+ ## Verbosity: LOW
60
+ - Acknowledgment: Action only
61
+ - Completion: Result + errors only
62
+ - Skip: Reasoning, decisions
83
63
 
84
64
  EOF
85
65
  ;;
86
66
 
87
67
  medium)
88
68
  cat <<'EOF'
89
- ## Verbosity: MEDIUM (Balanced)
90
- - Speak at acknowledgment and completion (always)
91
- - Also speak major decisions and key findings during work
92
- - Use emoji markers for automatic TTS:
93
- 🤔 [decision text] - Major decisions (e.g., "🤔 I'll use grep to search all files")
94
- ✓ [finding text] - Key findings (e.g., "✓ Found 12 instances at line 1323")
95
-
96
- Example:
97
- ```
98
- User: "Find all TODO comments"
99
- [TTS: Acknowledgment]
100
- 🤔 I'll use grep to search for TODO comments
101
- [Work happens...]
102
- ✓ Found 12 TODO comments across 5 files
103
- [TTS: Completion]
104
- ```
69
+ ## Verbosity: MEDIUM
70
+ - Acknowledgment: Action + key approach
71
+ - Completion: Result + important decisions
72
+ - Include: Major choices only
105
73
 
106
74
  EOF
107
75
  ;;
108
76
 
109
77
  high)
110
78
  cat <<'EOF'
111
- ## Verbosity: HIGH (Maximum Transparency)
112
- - Speak acknowledgment and completion (always)
113
- - Speak ALL reasoning, decisions, and findings as you work
114
- - Use emoji markers for automatic TTS:
115
- 💭 [reasoning text] - Thought process (e.g., "💭 Let me search for all instances")
116
- 🤔 [decision text] - Decisions (e.g., "🤔 I'll use grep for this")
117
- ✓ [finding text] - Findings (e.g., "✓ Found it at line 1323")
118
-
119
- Example:
120
- ```
121
- User: "Find all TODO comments"
122
- [TTS: Acknowledgment]
123
- 💭 Let me search through the codebase for TODO comments
124
- 🤔 I'll use the Grep tool with pattern "TODO"
125
- [Grep runs...]
126
- ✓ Found 12 TODO comments across 5 files
127
- 💭 Let me organize these results by file
128
- [Processing...]
129
- [TTS: Completion]
130
- ```
131
-
132
- IMPORTANT: Use emoji markers naturally in your reasoning text. They trigger automatic TTS.
79
+ ## Verbosity: HIGH
80
+ - Acknowledgment: Action + approach + why
81
+ - Completion: Result + decisions + trade-offs
82
+ - Include: Full reasoning, alternatives
133
83
 
134
84
  EOF
135
85
  ;;
136
86
  esac
137
87
 
138
- # Add current style and verbosity info
139
- echo "Current Style: ${STYLE_NAME} (${STYLE_MODE})"
140
- echo "Current Verbosity: ${VERBOSITY}"
141
- echo ""
88
+ # Add style info and rules
89
+ cat << EOF
90
+ ## Style: $STYLE
91
+
92
+ ## Rules
93
+ 1. Never skip acknowledgment TTS
94
+ 2. Never skip completion TTS
95
+ 3. Match verbosity level
96
+ 4. Keep under 150 chars
97
+ 5. Always include errors
98
+
99
+ Quick Ref: low=action+result | medium=+key decisions | high=+full reasoning
100
+
101
+ EOF
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.17.5
14
+ **Author**: Paul Preibisch ([@997Fire](https://x.com/997Fire)) | **Version**: v2.17.7
15
15
 
16
16
  ---
17
17
 
@@ -114,15 +114,15 @@ All 50+ Piper voices AgentVibes provides are sourced from Hugging Face's open-so
114
114
 
115
115
  ## 📰 Latest Release
116
116
 
117
- **[v2.17.5 - Installer UX Improvements](https://github.com/paulpreibisch/AgentVibes/releases/tag/v2.17.5)**
117
+ **[v2.17.7 - BMAD Party Mode Voice Fix](https://github.com/paulpreibisch/AgentVibes/releases/tag/v2.17.7)** 🎭
118
118
 
119
- AgentVibes v2.17.5 improves the installation experience with a complete installer UX overhaul. This release introduces intelligent page navigation with previews, beautiful two-column layouts for all summary screens, and optimized vertical spacing throughout.
119
+ AgentVibes v2.17.7 fixes a critical bug preventing BMAD party mode agents from using their unique voices. All agents were incorrectly using the same default voice instead of their assigned voices (Mary/Kristin, John/Ryan, Winston/Alan, etc.). This release also includes 50% token optimization for the session-start TTS hook.
120
120
 
121
121
  **Key Highlights:**
122
- - 📱 **Page Navigation Previews** - See next/previous page titles in navigation menu (e.g., "Next → (Voice Selection)")
123
- - 🎨 **Beautiful Two-Column Layouts** - Slash commands, hooks, personalities, and music displayed in organized columns
124
- - **Emoji Page Titles** - Each configuration page has descriptive icons (🔧 System Dependencies, 🎤 Voice Selection, etc.)
125
- - 📏 **Optimized Spacing** - Eliminated excessive vertical spacing for cleaner, more compact display
122
+ - 🎭 **Party Mode Voice Fix** - All BMAD agents now speak with their unique assigned voices
123
+ - **Token Optimization** - Session-start TTS hook reduced from ~500 to ~250 tokens (50% reduction)
124
+ - 🎤 **Interactive TTS Prompt** - Installer automatically detects BMAD and offers voice injection
125
+ - 🔧 **Hook Path Improvements** - Fixed TTS injector to use correct path resolution
126
126
 
127
127
  💡 **Tip:** If `npx agentvibes` shows an older version or missing commands, clear your npm cache: `npm cache clean --force && npx agentvibes@latest --help`
128
128
 
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.17.6",
4
+ "version": "2.17.7",
5
5
  "description": "Now your AI Agents can finally talk back! Professional TTS voice for Claude Code and Claude Desktop (via MCP) with multi-provider support.",
6
6
  "homepage": "https://agentvibes.org",
7
7
  "keywords": [
package/src/installer.js CHANGED
@@ -823,17 +823,17 @@ function showWelcome() {
823
823
  * Shown during install and update commands
824
824
  */
825
825
  function getReleaseInfoBoxen() {
826
- return chalk.cyan.bold('📦 AgentVibes v2.17.5 - Installer UX Improvements\n\n') +
826
+ return chalk.cyan.bold('📦 AgentVibes v2.17.7 - BMAD Party Mode Voice Fix\n\n') +
827
827
  chalk.green.bold('🎙️ WHAT\'S NEW:\n\n') +
828
- chalk.cyan('AgentVibes v2.17.5 improves the installation experience with a complete installer\n') +
829
- chalk.cyan('UX overhaul. This release introduces intelligent page navigation with previews,\n') +
830
- chalk.cyan('beautiful two-column layouts for all summary screens, and optimized vertical\n') +
831
- chalk.cyan('spacing throughout.\n\n') +
828
+ chalk.cyan('AgentVibes v2.17.7 fixes a critical bug preventing BMAD party mode agents from\n') +
829
+ chalk.cyan('using their unique voices. All agents were incorrectly using the same default voice\n') +
830
+ chalk.cyan('instead of their assigned voices (Mary/Kristin, John/Ryan, Winston/Alan, etc.).\n') +
831
+ chalk.cyan('This release also includes 50% token optimization for session-start hook.\n\n') +
832
832
  chalk.green.bold('✨ KEY HIGHLIGHTS:\n\n') +
833
- chalk.gray(' 📱 Page Navigation Previews - See next/previous page titles in navigation menu\n') +
834
- chalk.gray(' 🎨 Beautiful Two-Column Layouts - Commands, hooks, personalities displayed in organized columns\n') +
835
- chalk.gray(' Emoji Page Titles - Descriptive icons for each configuration page\n') +
836
- chalk.gray(' 📏 Optimized Spacing - Eliminated excessive vertical spacing for cleaner display\n\n') +
833
+ chalk.gray(' 🎭 Party Mode Voice Fix - All BMAD agents now speak with unique assigned voices\n') +
834
+ chalk.gray(' Token Optimization - Session-start TTS hook reduced from ~500 to ~250 tokens\n') +
835
+ chalk.gray(' 🎤 Interactive TTS Prompt - Installer detects BMAD and offers voice injection\n') +
836
+ chalk.gray(' 🔧 Hook Path Improvements - Fixed TTS injector path resolution\n\n') +
837
837
  chalk.gray('📖 Full Release Notes: RELEASE_NOTES.md\n') +
838
838
  chalk.gray('🌐 Website: https://agentvibes.org\n') +
839
839
  chalk.gray('📦 Repository: https://github.com/paulpreibisch/AgentVibes\n\n') +
@@ -2444,9 +2444,10 @@ async function detectAndMigrateOldConfig(targetDir, spinner) {
2444
2444
  /**
2445
2445
  * Handle BMAD integration (detection and TTS injection)
2446
2446
  * @param {string} targetDir - Target installation directory
2447
+ * @param {Object} options - Installation options (e.g., yes flag for non-interactive)
2447
2448
  * @returns {Promise<Object>} BMAD detection result
2448
2449
  */
2449
- async function handleBmadIntegration(targetDir) {
2450
+ async function handleBmadIntegration(targetDir, options = {}) {
2450
2451
  const bmadDetection = await detectBMAD(targetDir);
2451
2452
  const bmadDetected = bmadDetection.installed;
2452
2453
 
@@ -2485,6 +2486,55 @@ async function handleBmadIntegration(targetDir) {
2485
2486
  // Create default voice assignments if they don't exist
2486
2487
  await createDefaultBmadVoiceAssignmentsProactive(targetDir);
2487
2488
 
2489
+ // Prompt user to inject TTS into BMAD agents (or auto-inject with --yes flag)
2490
+ let enableTtsInjection = options.yes; // Auto-enable with --yes flag
2491
+
2492
+ if (!options.yes) {
2493
+ console.log(''); // Add spacing
2494
+ console.log(chalk.cyan.bold('🎤 AgentVibes TTS Integration for BMAD Agents\n'));
2495
+ console.log(chalk.white('AgentVibes can inject Text-to-Speech into your BMAD agents'));
2496
+ console.log(chalk.white('so each agent speaks with their own unique voice!\n'));
2497
+ console.log(chalk.gray('What this does:'));
2498
+ console.log(chalk.gray(' • Modifies agent activation instructions to include TTS'));
2499
+ console.log(chalk.gray(' • Each agent gets a unique voice (e.g., Mary, John, Winston)'));
2500
+ console.log(chalk.gray(' • Agents will speak when activated and during responses'));
2501
+ console.log(chalk.gray(' • Creates backups before making any changes\n'));
2502
+ console.log(chalk.cyan('Agents that will get unique voices:'));
2503
+ console.log(chalk.gray(' • Mary (analyst) → Female voice'));
2504
+ console.log(chalk.gray(' • John (pm) → Male voice'));
2505
+ console.log(chalk.gray(' • Winston (architect) → British voice'));
2506
+ console.log(chalk.gray(' • And 6+ more agents...\n'));
2507
+ console.log(chalk.yellow('You can disable this later with:'));
2508
+ console.log(chalk.gray(' .claude/hooks/bmad-tts-injector.sh disable\n'));
2509
+
2510
+ const { enableTts } = await inquirer.prompt([{
2511
+ type: 'confirm',
2512
+ name: 'enableTts',
2513
+ message: chalk.yellow('Enable TTS for BMAD agents?'),
2514
+ default: true
2515
+ }]);
2516
+
2517
+ enableTtsInjection = enableTts;
2518
+ }
2519
+
2520
+ if (enableTtsInjection) {
2521
+ const injectorScript = path.join(claudeDir, 'hooks', 'bmad-tts-injector.sh');
2522
+ try {
2523
+ // Run bmad-tts-injector.sh enable
2524
+ execSync(`bash "${injectorScript}" enable`, {
2525
+ cwd: targetDir,
2526
+ stdio: 'inherit'
2527
+ });
2528
+ console.log(chalk.green('✅ TTS injection completed successfully'));
2529
+ } catch (error) {
2530
+ console.log(chalk.yellow('⚠️ TTS injection encountered issues'));
2531
+ console.log(chalk.gray(' You can retry manually with: .claude/hooks/bmad-tts-injector.sh enable'));
2532
+ }
2533
+ } else {
2534
+ console.log(chalk.gray(' Skipped TTS injection. You can enable it later with:'));
2535
+ console.log(chalk.gray(' .claude/hooks/bmad-tts-injector.sh enable'));
2536
+ }
2537
+
2488
2538
  console.log(chalk.green('✅ BMAD agents will use agent-specific voices via bmad-speak.sh hook'));
2489
2539
 
2490
2540
  return bmadDetection;
@@ -3185,7 +3235,7 @@ async function install(options = {}) {
3185
3235
  await createDefaultBmadVoiceAssignmentsProactive(targetDir);
3186
3236
 
3187
3237
  // Handle BMAD integration
3188
- const bmadDetection = await handleBmadIntegration(targetDir);
3238
+ const bmadDetection = await handleBmadIntegration(targetDir, options);
3189
3239
  const bmadDetected = bmadDetection.installed;
3190
3240
 
3191
3241
  if (bmadDetected) {
@@ -1,272 +0,0 @@
1
- #!/usr/bin/env bash
2
- #
3
- # File: .claude/hooks/play-tts.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, including but not limited to the warranties of
26
- # merchantability, fitness for a particular purpose and noninfringement.
27
- # In no event shall the authors or copyright holders be liable for any claim,
28
- # damages or other liability, whether in an action of contract, tort or
29
- # otherwise, arising from, out of or in connection with the software or the
30
- # use or other dealings in the software.
31
- #
32
- # ---
33
- #
34
- # @fileoverview TTS Provider Router with Translation and Language Learning Support
35
- # @context Routes TTS requests to active provider (Piper or macOS) with optional translation
36
- # @architecture Provider abstraction layer - single entry point for all TTS, handles translation and learning mode
37
- # @dependencies provider-manager.sh, play-tts-piper.sh, translator.py, translate-manager.sh, learn-manager.sh
38
- # @entrypoints Called by hooks, slash commands, personality-manager.sh, and all TTS features
39
- # @patterns Provider pattern - delegates to provider-specific implementations, auto-detects provider from voice name
40
- # @related provider-manager.sh, play-tts-piper.sh, learn-manager.sh, translate-manager.sh
41
- #
42
-
43
- # Fix locale warnings
44
- export LC_ALL=C
45
-
46
- # Get script directory (needed for mute file check)
47
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
48
- PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
49
-
50
- # Check if muted (persists across sessions)
51
- # Project settings always override global settings:
52
- # - .claude/agentvibes-unmuted = project explicitly unmuted (overrides global mute)
53
- # - .claude/agentvibes-muted = project muted (overrides global unmute)
54
- # - ~/.agentvibes-muted = global mute (only if no project-level setting)
55
- GLOBAL_MUTE_FILE="$HOME/.agentvibes-muted"
56
- PROJECT_MUTE_FILE="$PROJECT_ROOT/.claude/agentvibes-muted"
57
- PROJECT_UNMUTE_FILE="$PROJECT_ROOT/.claude/agentvibes-unmuted"
58
-
59
- # Check project-level settings first (project overrides global)
60
- if [[ -f "$PROJECT_UNMUTE_FILE" ]]; then
61
- # Project explicitly unmuted - ignore global mute
62
- : # Continue (do nothing, will not exit)
63
- elif [[ -f "$PROJECT_MUTE_FILE" ]]; then
64
- # Project explicitly muted
65
- if [[ -f "$GLOBAL_MUTE_FILE" ]]; then
66
- echo "🔇 TTS muted (project + global)"
67
- else
68
- echo "🔇 TTS muted (project)"
69
- fi
70
- exit 0
71
- elif [[ -f "$GLOBAL_MUTE_FILE" ]]; then
72
- # Global mute and no project-level override
73
- echo "🔇 TTS muted (global)"
74
- exit 0
75
- fi
76
-
77
- TEXT="$1"
78
- VOICE_OVERRIDE="$2" # Optional: voice name or ID
79
-
80
- # Security: Validate inputs
81
- if [[ -z "$TEXT" ]]; then
82
- echo "Error: No text provided" >&2
83
- exit 1
84
- fi
85
-
86
- # Security: Validate voice override doesn't contain dangerous characters
87
- if [[ -n "$VOICE_OVERRIDE" ]] && [[ "$VOICE_OVERRIDE" =~ [';|&$`<>(){}'] ]]; then
88
- echo "Error: Invalid characters in voice parameter" >&2
89
- exit 1
90
- fi
91
-
92
- # Remove backslash escaping that Claude might add for special chars
93
- # In single quotes these don't need escaping, but Claude sometimes adds backslashes
94
- TEXT="${TEXT//\\!/!}" # Remove \!
95
- TEXT="${TEXT//\\\$/\$}" # Remove \$
96
- TEXT="${TEXT//\\?/?}" # Remove \?
97
- TEXT="${TEXT//\\,/,}" # Remove \,
98
- TEXT="${TEXT//\\./.}" # Remove \. (keep the period)
99
- TEXT="${TEXT//\\\\/\\}" # Remove \\ (escaped backslash)
100
-
101
- # Source provider manager to get active provider
102
- source "$SCRIPT_DIR/provider-manager.sh"
103
-
104
- # Get active provider
105
- ACTIVE_PROVIDER=$(get_active_provider)
106
-
107
- # Show GitHub star reminder (once per day)
108
- "$SCRIPT_DIR/github-star-reminder.sh" 2>/dev/null || true
109
-
110
- # @function detect_voice_provider
111
- # @intent Auto-detect provider from voice name (for mixed-provider support)
112
- # @why Allow Piper for main language + macOS for target language
113
- # @param $1 voice name/ID
114
- # @returns Provider name (piper or macos)
115
- detect_voice_provider() {
116
- local voice="$1"
117
- # Piper voice names contain underscore and dash (e.g., es_ES-davefx-medium)
118
- if [[ "$voice" == *"_"*"-"* ]]; then
119
- echo "piper"
120
- else
121
- echo "$ACTIVE_PROVIDER"
122
- fi
123
- }
124
-
125
- # Override provider if voice indicates different provider (mixed-provider mode)
126
- if [[ -n "$VOICE_OVERRIDE" ]]; then
127
- DETECTED_PROVIDER=$(detect_voice_provider "$VOICE_OVERRIDE")
128
- if [[ "$DETECTED_PROVIDER" != "$ACTIVE_PROVIDER" ]]; then
129
- ACTIVE_PROVIDER="$DETECTED_PROVIDER"
130
- fi
131
- fi
132
-
133
- # @function speak_text
134
- # @intent Route text to appropriate TTS provider
135
- # @why Reusable function for speaking, used by both single and learning modes
136
- # @param $1 text to speak
137
- # @param $2 voice override (optional)
138
- # @param $3 provider override (optional)
139
- speak_text() {
140
- local text="$1"
141
- local voice="${2:-}"
142
- local provider="${3:-$ACTIVE_PROVIDER}"
143
-
144
- case "$provider" in
145
- piper)
146
- "$SCRIPT_DIR/play-tts-piper.sh" "$text" "$voice"
147
- ;;
148
- macos)
149
- "$SCRIPT_DIR/play-tts-macos.sh" "$text" "$voice"
150
- ;;
151
- *)
152
- echo "❌ Unknown provider: $provider" >&2
153
- return 1
154
- ;;
155
- esac
156
- }
157
-
158
- # Note: learn-manager.sh and translate-manager.sh are sourced inside their
159
- # respective handler functions to avoid triggering their main handlers
160
-
161
- # @function handle_learning_mode
162
- # @intent Speak in both main language and target language for learning
163
- # @why Issue #51 - Auto-translate and speak twice for immersive language learning
164
- # @returns 0 if learning mode handled, 1 if not in learning mode
165
- handle_learning_mode() {
166
- # Source learn-manager for learning mode functions
167
- source "$SCRIPT_DIR/learn-manager.sh" 2>/dev/null || return 1
168
-
169
- # Check if learning mode is enabled
170
- if ! is_learn_mode_enabled 2>/dev/null; then
171
- return 1
172
- fi
173
-
174
- local target_lang
175
- target_lang=$(get_target_language 2>/dev/null || echo "")
176
- local target_voice
177
- target_voice=$(get_target_voice 2>/dev/null || echo "")
178
-
179
- # Need both target language and voice for learning mode
180
- if [[ -z "$target_lang" ]] || [[ -z "$target_voice" ]]; then
181
- return 1
182
- fi
183
-
184
- # 1. Speak in main language (current voice)
185
- speak_text "$TEXT" "$VOICE_OVERRIDE" "$ACTIVE_PROVIDER"
186
-
187
- # 2. Auto-translate to target language
188
- local translated
189
- translated=$(python3 "$SCRIPT_DIR/translator.py" "$TEXT" "$target_lang" 2>/dev/null) || translated="$TEXT"
190
-
191
- # Small pause between languages
192
- sleep 0.5
193
-
194
- # 3. Speak translated text with target voice
195
- local target_provider
196
- target_provider=$(detect_voice_provider "$target_voice")
197
- speak_text "$translated" "$target_voice" "$target_provider"
198
-
199
- return 0
200
- }
201
-
202
- # @function handle_translation_mode
203
- # @intent Translate and speak in target language (non-learning mode)
204
- # @why Issue #50 - BMAD multi-language TTS support
205
- # @returns 0 if translation handled, 1 if not translating
206
- handle_translation_mode() {
207
- # Source translate-manager to get translation settings
208
- source "$SCRIPT_DIR/translate-manager.sh" 2>/dev/null || return 1
209
-
210
- # Check if translation is enabled
211
- if ! is_translation_enabled 2>/dev/null; then
212
- return 1
213
- fi
214
-
215
- local translate_to
216
- translate_to=$(get_translate_to 2>/dev/null || echo "")
217
-
218
- if [[ -z "$translate_to" ]] || [[ "$translate_to" == "english" ]]; then
219
- return 1
220
- fi
221
-
222
- # Translate text
223
- local translated
224
- translated=$(python3 "$SCRIPT_DIR/translator.py" "$TEXT" "$translate_to" 2>/dev/null) || translated="$TEXT"
225
-
226
- # Get voice for target language if no override specified
227
- local voice_to_use="$VOICE_OVERRIDE"
228
- if [[ -z "$voice_to_use" ]]; then
229
- source "$SCRIPT_DIR/language-manager.sh" 2>/dev/null || true
230
- voice_to_use=$(get_voice_for_language "$translate_to" "$ACTIVE_PROVIDER" 2>/dev/null || echo "")
231
- fi
232
-
233
- # Update provider if voice indicates different provider
234
- local provider_to_use="$ACTIVE_PROVIDER"
235
- if [[ -n "$voice_to_use" ]]; then
236
- provider_to_use=$(detect_voice_provider "$voice_to_use")
237
- fi
238
-
239
- # Speak translated text
240
- speak_text "$translated" "$voice_to_use" "$provider_to_use"
241
- return 0
242
- }
243
-
244
- # Mode priority:
245
- # 1. Learning mode (speaks twice: main + translated)
246
- # 2. Translation mode (speaks translated only)
247
- # 3. Normal mode (speaks as-is)
248
-
249
- # Try learning mode first (Issue #51)
250
- if handle_learning_mode; then
251
- exit 0
252
- fi
253
-
254
- # Try translation mode (Issue #50)
255
- if handle_translation_mode; then
256
- exit 0
257
- fi
258
-
259
- # Normal single-language mode - route to appropriate provider implementation
260
- case "$ACTIVE_PROVIDER" in
261
- piper)
262
- exec "$SCRIPT_DIR/play-tts-piper.sh" "$TEXT" "$VOICE_OVERRIDE"
263
- ;;
264
- macos)
265
- exec "$SCRIPT_DIR/play-tts-macos.sh" "$TEXT" "$VOICE_OVERRIDE"
266
- ;;
267
- *)
268
- echo "❌ Unknown provider: $ACTIVE_PROVIDER"
269
- echo " Run: /agent-vibes:provider list"
270
- exit 1
271
- ;;
272
- esac