agentvibes 2.0.9 → 2.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. package/.claude/commands/agent-vibes/bmad.md +203 -0
  2. package/.claude/github-star-reminder.txt +1 -0
  3. package/.claude/hooks/bmad-tts-injector.sh +333 -0
  4. package/.claude/hooks/bmad-voice-manager.sh +34 -0
  5. package/.claude/hooks/check-output-style.sh +2 -2
  6. package/.claude/hooks/github-star-reminder.sh +94 -0
  7. package/.claude/hooks/personality-manager.sh +2 -2
  8. package/.claude/hooks/piper-installer.sh +194 -0
  9. package/.claude/hooks/play-tts-elevenlabs.sh +30 -3
  10. package/.claude/hooks/play-tts-local-wrapper.sh +44 -0
  11. package/.claude/hooks/play-tts-piper.sh +10 -2
  12. package/.claude/hooks/play-tts-remote.sh +81 -0
  13. package/.claude/hooks/play-tts.sh +34 -0
  14. package/.claude/hooks/provider-commands.sh +30 -1
  15. package/.claude/hooks/voice-manager.sh +2 -2
  16. package/.claude/output-styles/agent-vibes.md +52 -36
  17. package/README.md +2 -2
  18. package/RELEASE_NOTES.md +412 -0
  19. package/agentvibes.org/.claude/commands/agent-vibes/add.md +21 -0
  20. package/agentvibes.org/.claude/commands/agent-vibes/agent-vibes.md +68 -0
  21. package/agentvibes.org/.claude/commands/agent-vibes/commands.json +53 -0
  22. package/agentvibes.org/.claude/commands/agent-vibes/get.md +9 -0
  23. package/agentvibes.org/.claude/commands/agent-vibes/list.md +13 -0
  24. package/agentvibes.org/.claude/commands/agent-vibes/personality.md +79 -0
  25. package/agentvibes.org/.claude/commands/agent-vibes/preview.md +16 -0
  26. package/agentvibes.org/.claude/commands/agent-vibes/provider.md +54 -0
  27. package/agentvibes.org/.claude/commands/agent-vibes/replay.md +19 -0
  28. package/agentvibes.org/.claude/commands/agent-vibes/sample.md +12 -0
  29. package/agentvibes.org/.claude/commands/agent-vibes/sentiment.md +52 -0
  30. package/agentvibes.org/.claude/commands/agent-vibes/set-language.md +47 -0
  31. package/agentvibes.org/.claude/commands/agent-vibes/set-pretext.md +65 -0
  32. package/agentvibes.org/.claude/commands/agent-vibes/switch.md +53 -0
  33. package/agentvibes.org/.claude/commands/agent-vibes/update.md +20 -0
  34. package/agentvibes.org/.claude/commands/agent-vibes/version.md +10 -0
  35. package/agentvibes.org/.claude/commands/agent-vibes/whoami.md +7 -0
  36. package/agentvibes.org/.claude/hooks/bmad-voice-manager.sh +278 -0
  37. package/agentvibes.org/.claude/hooks/language-manager.sh +190 -0
  38. package/agentvibes.org/.claude/hooks/personality-manager.sh +279 -0
  39. package/agentvibes.org/.claude/hooks/piper-download-voices.sh +133 -0
  40. package/agentvibes.org/.claude/hooks/piper-voice-manager.sh +227 -0
  41. package/agentvibes.org/.claude/hooks/play-tts-elevenlabs.sh +201 -0
  42. package/agentvibes.org/.claude/hooks/play-tts-piper.sh +175 -0
  43. package/agentvibes.org/.claude/hooks/play-tts.sh +138 -0
  44. package/agentvibes.org/.claude/hooks/provider-commands.sh +374 -0
  45. package/agentvibes.org/.claude/hooks/provider-manager.sh +196 -0
  46. package/agentvibes.org/.claude/hooks/sentiment-manager.sh +163 -0
  47. package/agentvibes.org/.claude/hooks/voice-manager.sh +349 -0
  48. package/agentvibes.org/.claude/hooks/voices-config.sh +33 -0
  49. package/agentvibes.org/.claude/journal/2025-10-07.html +373 -0
  50. package/agentvibes.org/.claude/journal/index.html +91 -0
  51. package/agentvibes.org/.claude/output-styles/agent-vibes.md +203 -0
  52. package/agentvibes.org/.claude/personalities/angry.md +16 -0
  53. package/agentvibes.org/.claude/personalities/annoying.md +16 -0
  54. package/agentvibes.org/.claude/personalities/crass.md +16 -0
  55. package/agentvibes.org/.claude/personalities/dramatic.md +16 -0
  56. package/agentvibes.org/.claude/personalities/dry-humor.md +52 -0
  57. package/agentvibes.org/.claude/personalities/flirty.md +22 -0
  58. package/agentvibes.org/.claude/personalities/funny.md +16 -0
  59. package/agentvibes.org/.claude/personalities/grandpa.md +34 -0
  60. package/agentvibes.org/.claude/personalities/millennial.md +16 -0
  61. package/agentvibes.org/.claude/personalities/moody.md +16 -0
  62. package/agentvibes.org/.claude/personalities/normal.md +18 -0
  63. package/agentvibes.org/.claude/personalities/pirate.md +16 -0
  64. package/agentvibes.org/.claude/personalities/poetic.md +16 -0
  65. package/agentvibes.org/.claude/personalities/professional.md +16 -0
  66. package/agentvibes.org/.claude/personalities/robot.md +16 -0
  67. package/agentvibes.org/.claude/personalities/sarcastic.md +40 -0
  68. package/agentvibes.org/.claude/personalities/sassy.md +16 -0
  69. package/agentvibes.org/.claude/personalities/surfer-dude.md +16 -0
  70. package/agentvibes.org/.claude/personalities/zen.md +16 -0
  71. package/agentvibes.org/.mcp-minimal.json +60 -0
  72. package/agentvibes.org/CHANGELOG.md +56 -0
  73. package/agentvibes.org/README.md +93 -0
  74. package/agentvibes.org/app/(auth)/layout.tsx +15 -0
  75. package/agentvibes.org/app/(auth)/reset-password/page.tsx +45 -0
  76. package/agentvibes.org/app/(auth)/signin/page.tsx +82 -0
  77. package/agentvibes.org/app/(auth)/signup/page.tsx +104 -0
  78. package/agentvibes.org/app/(default)/layout.tsx +31 -0
  79. package/agentvibes.org/app/(default)/page.tsx +20 -0
  80. package/agentvibes.org/app/api/hello/route.ts +3 -0
  81. package/agentvibes.org/app/css/additional-styles/theme.css +82 -0
  82. package/agentvibes.org/app/css/additional-styles/utility-patterns.css +55 -0
  83. package/agentvibes.org/app/css/style.css +100 -0
  84. package/agentvibes.org/app/layout.tsx +63 -0
  85. package/agentvibes.org/components/cta.tsx +58 -0
  86. package/agentvibes.org/components/features.tsx +256 -0
  87. package/agentvibes.org/components/hero-home.tsx +133 -0
  88. package/agentvibes.org/components/modal-video.tsx +137 -0
  89. package/agentvibes.org/components/page-illustration.tsx +55 -0
  90. package/agentvibes.org/components/spotlight.tsx +77 -0
  91. package/agentvibes.org/components/testimonials.tsx +282 -0
  92. package/agentvibes.org/components/ui/footer.tsx +82 -0
  93. package/agentvibes.org/components/ui/header.tsx +53 -0
  94. package/agentvibes.org/components/ui/logo.tsx +10 -0
  95. package/agentvibes.org/components/workflows.tsx +176 -0
  96. package/agentvibes.org/next.config.js +4 -0
  97. package/agentvibes.org/package-lock.json +1974 -0
  98. package/agentvibes.org/package.json +30 -0
  99. package/agentvibes.org/pnpm-lock.yaml +1141 -0
  100. package/agentvibes.org/postcss.config.js +5 -0
  101. package/agentvibes.org/public/audio/02-sarcastic.mp3 +0 -0
  102. package/agentvibes.org/public/audio/03-angry.mp3 +0 -0
  103. package/agentvibes.org/public/audio/04-grandpa.mp3 +0 -0
  104. package/agentvibes.org/public/audio/05-sarcastic-example2.mp3 +0 -0
  105. package/agentvibes.org/public/audio/french-rachel.mp3 +0 -0
  106. package/agentvibes.org/public/audio/spanish-antoni.mp3 +0 -0
  107. package/agentvibes.org/public/favicon.ico +0 -0
  108. package/agentvibes.org/public/fonts/nacelle-italic.woff2 +0 -0
  109. package/agentvibes.org/public/fonts/nacelle-regular.woff2 +0 -0
  110. package/agentvibes.org/public/fonts/nacelle-semibold.woff2 +0 -0
  111. package/agentvibes.org/public/fonts/nacelle-semibolditalic.woff2 +0 -0
  112. package/agentvibes.org/public/images/blurred-shape-gray.svg +1 -0
  113. package/agentvibes.org/public/images/blurred-shape.svg +1 -0
  114. package/agentvibes.org/public/images/client-logo-01.svg +1 -0
  115. package/agentvibes.org/public/images/client-logo-02.svg +1 -0
  116. package/agentvibes.org/public/images/client-logo-03.svg +1 -0
  117. package/agentvibes.org/public/images/client-logo-04.svg +1 -0
  118. package/agentvibes.org/public/images/client-logo-05.svg +1 -0
  119. package/agentvibes.org/public/images/client-logo-06.svg +1 -0
  120. package/agentvibes.org/public/images/client-logo-07.svg +1 -0
  121. package/agentvibes.org/public/images/client-logo-08.svg +1 -0
  122. package/agentvibes.org/public/images/client-logo-09.svg +1 -0
  123. package/agentvibes.org/public/images/features.png +0 -0
  124. package/agentvibes.org/public/images/footer-illustration.svg +1 -0
  125. package/agentvibes.org/public/images/hero-image-01.jpg +0 -0
  126. package/agentvibes.org/public/images/logo.svg +1 -0
  127. package/agentvibes.org/public/images/page-illustration.svg +1 -0
  128. package/agentvibes.org/public/images/secondary-illustration.svg +1 -0
  129. package/agentvibes.org/public/images/testimonial-01.jpg +0 -0
  130. package/agentvibes.org/public/images/testimonial-02.jpg +0 -0
  131. package/agentvibes.org/public/images/testimonial-03.jpg +0 -0
  132. package/agentvibes.org/public/images/testimonial-04.jpg +0 -0
  133. package/agentvibes.org/public/images/testimonial-05.jpg +0 -0
  134. package/agentvibes.org/public/images/testimonial-06.jpg +0 -0
  135. package/agentvibes.org/public/images/testimonial-07.jpg +0 -0
  136. package/agentvibes.org/public/images/testimonial-08.jpg +0 -0
  137. package/agentvibes.org/public/images/testimonial-09.jpg +0 -0
  138. package/agentvibes.org/public/images/workflow-01.png +0 -0
  139. package/agentvibes.org/public/images/workflow-02.png +0 -0
  140. package/agentvibes.org/public/images/workflow-03.png +0 -0
  141. package/agentvibes.org/public/videos/video.mp4 +0 -0
  142. package/agentvibes.org/tsconfig.json +28 -0
  143. package/agentvibes.org/utils/useMasonry.tsx +67 -0
  144. package/agentvibes.org/utils/useMousePosition.tsx +27 -0
  145. package/docs/REMOTE_TTS_SETUP.md +190 -0
  146. package/package.json +2 -2
  147. package/src/installer.js +193 -9
  148. package/test/helpers/test-helper.bash +4 -2
  149. package/test/unit/personality-manager.bats +16 -4
  150. package/test/unit/personality-voice-mapping.bats +15 -6
  151. package/test/unit/play-tts.bats +0 -9
  152. package/.claude/commands/agent-vibes-bmad.md +0 -132
@@ -0,0 +1,196 @@
1
+ #!/bin/bash
2
+ #
3
+ # @fileoverview TTS Provider Management Functions
4
+ # @context Core provider abstraction layer for multi-provider TTS system
5
+ # @architecture Provides functions to get/set/list/validate TTS providers
6
+ # @dependencies None - pure bash implementation
7
+ # @entrypoints Sourced by play-tts.sh and provider management commands
8
+ # @patterns File-based state management with project-local and global fallback
9
+ # @related play-tts.sh, play-tts-elevenlabs.sh, GitHub Issue #25
10
+ #
11
+
12
+ # @function get_provider_config_path
13
+ # @intent Determine path to tts-provider.txt file
14
+ # @why Supports both project-local (.claude/) and global (~/.claude/) storage
15
+ # @returns Echoes path to provider config file
16
+ # @exitcode 0=always succeeds
17
+ # @sideeffects None
18
+ # @edgecases Creates parent directory if missing
19
+ get_provider_config_path() {
20
+ local provider_file
21
+
22
+ # Check project-local first
23
+ if [[ -n "$CLAUDE_PROJECT_DIR" ]] && [[ -d "$CLAUDE_PROJECT_DIR/.claude" ]]; then
24
+ provider_file="$CLAUDE_PROJECT_DIR/.claude/tts-provider.txt"
25
+ else
26
+ # Search up directory tree for .claude/
27
+ local current_dir="$PWD"
28
+ while [[ "$current_dir" != "/" ]]; do
29
+ if [[ -d "$current_dir/.claude" ]]; then
30
+ provider_file="$current_dir/.claude/tts-provider.txt"
31
+ break
32
+ fi
33
+ current_dir=$(dirname "$current_dir")
34
+ done
35
+
36
+ # Fallback to global if no project .claude found
37
+ if [[ -z "$provider_file" ]]; then
38
+ provider_file="$HOME/.claude/tts-provider.txt"
39
+ fi
40
+ fi
41
+
42
+ echo "$provider_file"
43
+ }
44
+
45
+ # @function get_active_provider
46
+ # @intent Read currently active TTS provider from config file
47
+ # @why Central function for determining which provider to use
48
+ # @returns Echoes provider name (e.g., "elevenlabs", "piper")
49
+ # @exitcode 0=success
50
+ # @sideeffects None
51
+ # @edgecases Returns "elevenlabs" if file missing or empty (default)
52
+ get_active_provider() {
53
+ local provider_file
54
+ provider_file=$(get_provider_config_path)
55
+
56
+ # Read provider from file, default to elevenlabs if not found
57
+ if [[ -f "$provider_file" ]]; then
58
+ local provider
59
+ provider=$(cat "$provider_file" | tr -d '[:space:]')
60
+ if [[ -n "$provider" ]]; then
61
+ echo "$provider"
62
+ return 0
63
+ fi
64
+ fi
65
+
66
+ # Default to elevenlabs
67
+ echo "elevenlabs"
68
+ }
69
+
70
+ # @function set_active_provider
71
+ # @intent Write active provider to config file
72
+ # @why Allows runtime provider switching without restart
73
+ # @param $1 {string} provider - Provider name (e.g., "elevenlabs", "piper")
74
+ # @returns None (outputs success/error message)
75
+ # @exitcode 0=success, 1=invalid provider
76
+ # @sideeffects Writes to tts-provider.txt file
77
+ # @edgecases Creates file and parent directory if missing
78
+ set_active_provider() {
79
+ local provider="$1"
80
+
81
+ if [[ -z "$provider" ]]; then
82
+ echo "❌ Error: Provider name required"
83
+ echo "Usage: set_active_provider <provider_name>"
84
+ return 1
85
+ fi
86
+
87
+ # Validate provider exists
88
+ if ! validate_provider "$provider"; then
89
+ echo "❌ Error: Provider '$provider' not found"
90
+ echo "Available providers:"
91
+ list_providers
92
+ return 1
93
+ fi
94
+
95
+ local provider_file
96
+ provider_file=$(get_provider_config_path)
97
+
98
+ # Create directory if it doesn't exist
99
+ mkdir -p "$(dirname "$provider_file")"
100
+
101
+ # Write provider to file
102
+ echo "$provider" > "$provider_file"
103
+
104
+ echo "✓ Active provider set to: $provider"
105
+ }
106
+
107
+ # @function list_providers
108
+ # @intent List all available TTS providers
109
+ # @why Discover which providers are installed
110
+ # @returns Echoes provider names (one per line)
111
+ # @exitcode 0=success
112
+ # @sideeffects None
113
+ # @edgecases Returns empty if no play-tts-*.sh files found
114
+ list_providers() {
115
+ local script_dir
116
+ script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
117
+
118
+ # Find all play-tts-*.sh files
119
+ local providers=()
120
+ shopt -s nullglob # Handle case where no files match
121
+ for file in "$script_dir"/play-tts-*.sh; do
122
+ if [[ -f "$file" ]] && [[ "$file" != *"play-tts.sh" ]]; then
123
+ # Extract provider name from filename (play-tts-elevenlabs.sh -> elevenlabs)
124
+ local basename
125
+ basename=$(basename "$file")
126
+ local provider
127
+ provider="${basename#play-tts-}"
128
+ provider="${provider%.sh}"
129
+ providers+=("$provider")
130
+ fi
131
+ done
132
+ shopt -u nullglob
133
+
134
+ # Output providers
135
+ if [[ ${#providers[@]} -eq 0 ]]; then
136
+ echo "⚠️ No providers found"
137
+ return 0
138
+ fi
139
+
140
+ for provider in "${providers[@]}"; do
141
+ echo "$provider"
142
+ done
143
+ }
144
+
145
+ # @function validate_provider
146
+ # @intent Check if provider implementation exists
147
+ # @why Prevent errors from switching to non-existent provider
148
+ # @param $1 {string} provider - Provider name to validate
149
+ # @returns None
150
+ # @exitcode 0=provider exists, 1=provider not found
151
+ # @sideeffects None
152
+ # @edgecases Checks for corresponding play-tts-*.sh file
153
+ validate_provider() {
154
+ local provider="$1"
155
+
156
+ if [[ -z "$provider" ]]; then
157
+ return 1
158
+ fi
159
+
160
+ local script_dir
161
+ script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
162
+ local provider_script="$script_dir/play-tts-${provider}.sh"
163
+
164
+ [[ -f "$provider_script" ]]
165
+ }
166
+
167
+ # @function get_provider_script_path
168
+ # @intent Get absolute path to provider implementation script
169
+ # @why Used by router to execute provider-specific logic
170
+ # @param $1 {string} provider - Provider name
171
+ # @returns Echoes absolute path to play-tts-*.sh file
172
+ # @exitcode 0=success, 1=provider not found
173
+ # @sideeffects None
174
+ get_provider_script_path() {
175
+ local provider="$1"
176
+
177
+ if [[ -z "$provider" ]]; then
178
+ echo "❌ Error: Provider name required" >&2
179
+ return 1
180
+ fi
181
+
182
+ local script_dir
183
+ script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
184
+ local provider_script="$script_dir/play-tts-${provider}.sh"
185
+
186
+ if [[ ! -f "$provider_script" ]]; then
187
+ echo "❌ Error: Provider '$provider' not found at $provider_script" >&2
188
+ return 1
189
+ fi
190
+
191
+ echo "$provider_script"
192
+ }
193
+
194
+ # AI NOTE: This file provides the core abstraction layer for multi-provider TTS.
195
+ # All provider state is managed through simple text files for simplicity and reliability.
196
+ # Project-local configuration takes precedence over global to support per-project providers.
@@ -0,0 +1,163 @@
1
+ #!/bin/bash
2
+ # Sentiment manager for AgentVibes - applies personality to current voice
3
+
4
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
+ PERSONALITIES_DIR="$SCRIPT_DIR/../personalities"
6
+
7
+ # Project-local file first, global fallback
8
+ # Use logical path (not physical) to handle symlinked .claude directories
9
+ # Script is at .claude/hooks/sentiment-manager.sh, so .claude is ..
10
+ CLAUDE_DIR="$(cd "$SCRIPT_DIR/.." 2>/dev/null && pwd)"
11
+
12
+ # Check if we have a project-local .claude directory
13
+ if [[ -d "$CLAUDE_DIR" ]] && [[ "$CLAUDE_DIR" != "$HOME/.claude" ]]; then
14
+ SENTIMENT_FILE="$CLAUDE_DIR/tts-sentiment.txt"
15
+ else
16
+ SENTIMENT_FILE="$HOME/.claude/tts-sentiment.txt"
17
+ fi
18
+
19
+ # Function to get personality data from markdown file
20
+ get_personality_data() {
21
+ local personality="$1"
22
+ local field="$2"
23
+ local file="$PERSONALITIES_DIR/${personality}.md"
24
+
25
+ if [[ ! -f "$file" ]]; then
26
+ return 1
27
+ fi
28
+
29
+ case "$field" in
30
+ description)
31
+ grep "^description:" "$file" | cut -d: -f2- | sed 's/^[[:space:]]*//;s/[[:space:]]*$//'
32
+ ;;
33
+ esac
34
+ }
35
+
36
+ # Function to list all available personalities
37
+ list_personalities() {
38
+ if [[ -d "$PERSONALITIES_DIR" ]]; then
39
+ for file in "$PERSONALITIES_DIR"/*.md; do
40
+ if [[ -f "$file" ]]; then
41
+ basename "$file" .md
42
+ fi
43
+ done
44
+ fi
45
+ }
46
+
47
+ case "$1" in
48
+ list)
49
+ echo "🎭 Available Sentiments:"
50
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
51
+
52
+ # Get current sentiment
53
+ CURRENT="none"
54
+ if [ -f "$SENTIMENT_FILE" ]; then
55
+ CURRENT=$(cat "$SENTIMENT_FILE")
56
+ fi
57
+
58
+ # List personalities from markdown files
59
+ echo "Available sentiment styles:"
60
+ for personality in $(list_personalities | sort); do
61
+ desc=$(get_personality_data "$personality" "description")
62
+ if [[ "$personality" == "$CURRENT" ]]; then
63
+ echo " ✓ $personality - $desc (current)"
64
+ else
65
+ echo " - $personality - $desc"
66
+ fi
67
+ done
68
+
69
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
70
+ echo ""
71
+ echo "Usage: /agent-vibes:sentiment <name>"
72
+ echo " /agent-vibes:sentiment clear"
73
+ ;;
74
+
75
+ set)
76
+ SENTIMENT="$2"
77
+
78
+ if [[ -z "$SENTIMENT" ]]; then
79
+ echo "❌ Please specify a sentiment name"
80
+ echo "Usage: $0 set <sentiment>"
81
+ exit 1
82
+ fi
83
+
84
+ # Check if sentiment file exists
85
+ if [[ ! -f "$PERSONALITIES_DIR/${SENTIMENT}.md" ]]; then
86
+ echo "❌ Sentiment not found: $SENTIMENT"
87
+ echo ""
88
+ echo "Available sentiments:"
89
+ for p in $(list_personalities | sort); do
90
+ echo " • $p"
91
+ done
92
+ exit 1
93
+ fi
94
+
95
+ # Save the sentiment (but don't change personality or voice)
96
+ echo "$SENTIMENT" > "$SENTIMENT_FILE"
97
+ echo "🎭 Sentiment set to: $SENTIMENT"
98
+ echo "🎤 Voice remains unchanged"
99
+ echo ""
100
+
101
+ # Make a sentiment-appropriate remark with TTS
102
+ TTS_SCRIPT="$SCRIPT_DIR/play-tts.sh"
103
+
104
+ # Try to get acknowledgment from personality file (sentiments use same personality files)
105
+ PERSONALITY_FILE_PATH="$PERSONALITIES_DIR/${SENTIMENT}.md"
106
+ REMARK=""
107
+
108
+ if [[ -f "$PERSONALITY_FILE_PATH" ]]; then
109
+ # Extract example responses from personality file (lines starting with "- ")
110
+ mapfile -t EXAMPLES < <(grep '^- "' "$PERSONALITY_FILE_PATH" | sed 's/^- "//; s/"$//')
111
+
112
+ if [[ ${#EXAMPLES[@]} -gt 0 ]]; then
113
+ # Pick a random example
114
+ REMARK="${EXAMPLES[$RANDOM % ${#EXAMPLES[@]}]}"
115
+ fi
116
+ fi
117
+
118
+ # Fallback if no examples found
119
+ if [[ -z "$REMARK" ]]; then
120
+ REMARK="Sentiment set to ${SENTIMENT} while maintaining current voice"
121
+ fi
122
+
123
+ echo "💬 $REMARK"
124
+ "$TTS_SCRIPT" "$REMARK"
125
+ ;;
126
+
127
+ get)
128
+ if [ -f "$SENTIMENT_FILE" ]; then
129
+ CURRENT=$(cat "$SENTIMENT_FILE")
130
+ echo "Current sentiment: $CURRENT"
131
+
132
+ desc=$(get_personality_data "$CURRENT" "description")
133
+ [[ -n "$desc" ]] && echo "Description: $desc"
134
+ else
135
+ echo "Current sentiment: none (voice personality only)"
136
+ fi
137
+ ;;
138
+
139
+ clear)
140
+ rm -f "$SENTIMENT_FILE"
141
+ echo "🎭 Sentiment cleared - using voice personality only"
142
+ ;;
143
+
144
+ *)
145
+ # If a single argument is provided and it's not a command, treat it as "set <sentiment>"
146
+ if [[ -n "$1" ]] && [[ -f "$PERSONALITIES_DIR/${1}.md" ]]; then
147
+ exec "$0" set "$1"
148
+ else
149
+ echo "AgentVibes Sentiment Manager"
150
+ echo ""
151
+ echo "Commands:"
152
+ echo " list - List all sentiments"
153
+ echo " set <name> - Set sentiment for current voice"
154
+ echo " get - Show current sentiment"
155
+ echo " clear - Clear sentiment"
156
+ echo ""
157
+ echo "Examples:"
158
+ echo " /agent-vibes:sentiment flirty # Add flirty style to current voice"
159
+ echo " /agent-vibes:sentiment sarcastic # Add sarcasm to current voice"
160
+ echo " /agent-vibes:sentiment clear # Remove sentiment"
161
+ fi
162
+ ;;
163
+ esac
@@ -0,0 +1,349 @@
1
+ #!/bin/bash
2
+ # Voice Manager - Handle voice switching and listing
3
+ # Usage: voice-manager.sh [list|switch|get] [voice_name]
4
+
5
+ # Get script directory (physical path for sourcing files)
6
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
7
+ source "$SCRIPT_DIR/voices-config.sh"
8
+
9
+ # Project-local file first, global fallback
10
+ # Use the logical path from BASH_SOURCE to find .claude directory
11
+ # This handles both normal installations and symlinked hooks directories correctly
12
+ SCRIPT_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
13
+ CLAUDE_DIR="$(dirname "$SCRIPT_PATH")"
14
+
15
+ # Check if we have a project-local .claude directory
16
+ if [[ -d "$CLAUDE_DIR" ]] && [[ "$CLAUDE_DIR" != "$HOME/.claude" ]]; then
17
+ VOICE_FILE="$CLAUDE_DIR/tts-voice.txt"
18
+ else
19
+ # Fallback to global
20
+ VOICE_FILE="$HOME/.claude/tts-voice.txt"
21
+ fi
22
+
23
+ case "$1" in
24
+ list)
25
+ echo "🎤 Available TTS Voices:"
26
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
27
+ CURRENT_VOICE=$(cat "$VOICE_FILE" 2>/dev/null || echo "Cowboy")
28
+ for voice in "${!VOICES[@]}"; do
29
+ if [ "$voice" = "$CURRENT_VOICE" ]; then
30
+ echo " ▶ $voice (current)"
31
+ else
32
+ echo " $voice"
33
+ fi
34
+ done | sort
35
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
36
+ echo ""
37
+ echo "Usage: voice-manager.sh switch <name>"
38
+ echo " voice-manager.sh preview"
39
+ ;;
40
+
41
+ preview)
42
+ # Get play-tts.sh path
43
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
44
+ TTS_SCRIPT="$SCRIPT_DIR/play-tts.sh"
45
+
46
+ # Check if a specific voice name was provided
47
+ if [[ -n "$2" ]] && [[ "$2" != "first" ]] && [[ "$2" != "last" ]] && ! [[ "$2" =~ ^[0-9]+$ ]]; then
48
+ # User specified a voice name
49
+ VOICE_NAME="$2"
50
+
51
+ # Check if voice exists
52
+ if [[ -n "${VOICES[$VOICE_NAME]}" ]]; then
53
+ echo "🎤 Previewing voice: ${VOICE_NAME}"
54
+ echo ""
55
+ "$TTS_SCRIPT" "Hello, this is ${VOICE_NAME}. How do you like my voice?" "${VOICE_NAME}"
56
+ else
57
+ echo "❌ Voice not found: ${VOICE_NAME}"
58
+ echo ""
59
+ echo "Available voices:"
60
+ for voice in "${!VOICES[@]}"; do
61
+ echo " • $voice"
62
+ done | sort
63
+ fi
64
+ exit 0
65
+ fi
66
+
67
+ # Original preview logic for first/last/number
68
+ echo "🎤 Voice Preview - Playing first 3 voices..."
69
+ echo ""
70
+
71
+ # Sort voices and preview first 3
72
+ VOICE_ARRAY=()
73
+ for voice in "${!VOICES[@]}"; do
74
+ VOICE_ARRAY+=("$voice")
75
+ done
76
+
77
+ # Sort the array
78
+ IFS=$'\n' SORTED_VOICES=($(sort <<<"${VOICE_ARRAY[*]}"))
79
+ unset IFS
80
+
81
+ # Play first 3 voices
82
+ COUNT=0
83
+ for voice in "${SORTED_VOICES[@]}"; do
84
+ if [ $COUNT -eq 3 ]; then
85
+ break
86
+ fi
87
+ echo "🔊 ${voice}..."
88
+ "$TTS_SCRIPT" "Hi, I'm ${voice}" "${VOICES[$voice]}"
89
+ sleep 0.5
90
+ COUNT=$((COUNT + 1))
91
+ done
92
+
93
+ echo ""
94
+ echo "Would you like to hear more? Reply 'yes' to continue."
95
+ ;;
96
+
97
+ switch)
98
+ VOICE_NAME="$2"
99
+ SILENT_MODE=false
100
+
101
+ # Check for --silent flag
102
+ if [[ "$2" == "--silent" ]] || [[ "$3" == "--silent" ]]; then
103
+ SILENT_MODE=true
104
+ # If --silent is first arg, voice name is in $3
105
+ [[ "$2" == "--silent" ]] && VOICE_NAME="$3"
106
+ fi
107
+
108
+ if [[ -z "$VOICE_NAME" ]]; then
109
+ # Show numbered list for selection
110
+ echo "🎤 Select a voice by number:"
111
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
112
+
113
+ # Get current voice
114
+ CURRENT="Cowboy Bob"
115
+ if [ -f "$VOICE_FILE" ]; then
116
+ CURRENT=$(cat "$VOICE_FILE")
117
+ fi
118
+
119
+ # Create array of voice names
120
+ VOICE_ARRAY=()
121
+ for voice in "${!VOICES[@]}"; do
122
+ VOICE_ARRAY+=("$voice")
123
+ done
124
+
125
+ # Sort the array
126
+ IFS=$'\n' SORTED_VOICES=($(sort <<<"${VOICE_ARRAY[*]}"))
127
+ unset IFS
128
+
129
+ # Display numbered list in two columns for compactness
130
+ HALF=$(( (${#SORTED_VOICES[@]} + 1) / 2 ))
131
+
132
+ for i in $(seq 0 $((HALF - 1))); do
133
+ NUM1=$((i + 1))
134
+ VOICE1="${SORTED_VOICES[$i]}"
135
+
136
+ # Format first column
137
+ if [[ "$VOICE1" == "$CURRENT" ]]; then
138
+ COL1=$(printf "%2d. %-20s ✓" "$NUM1" "$VOICE1")
139
+ else
140
+ COL1=$(printf "%2d. %-20s " "$NUM1" "$VOICE1")
141
+ fi
142
+
143
+ # Format second column if it exists
144
+ NUM2=$((i + HALF + 1))
145
+ if [[ $((i + HALF)) -lt ${#SORTED_VOICES[@]} ]]; then
146
+ VOICE2="${SORTED_VOICES[$((i + HALF))]}"
147
+ if [[ "$VOICE2" == "$CURRENT" ]]; then
148
+ COL2=$(printf "%2d. %-20s ✓" "$NUM2" "$VOICE2")
149
+ else
150
+ COL2=$(printf "%2d. %-20s " "$NUM2" "$VOICE2")
151
+ fi
152
+ echo " $COL1 $COL2"
153
+ else
154
+ echo " $COL1"
155
+ fi
156
+ done
157
+
158
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
159
+ echo ""
160
+ echo "Enter number (1-${#SORTED_VOICES[@]}) or voice name:"
161
+ echo "Usage: /agent-vibes:switch 5"
162
+ echo " /agent-vibes:switch \"Northern Terry\""
163
+ exit 0
164
+ fi
165
+
166
+ # Check if input is a number
167
+ if [[ "$VOICE_NAME" =~ ^[0-9]+$ ]]; then
168
+ # Get voice array
169
+ VOICE_ARRAY=()
170
+ for voice in "${!VOICES[@]}"; do
171
+ VOICE_ARRAY+=("$voice")
172
+ done
173
+
174
+ # Sort the array
175
+ IFS=$'\n' SORTED_VOICES=($(sort <<<"${VOICE_ARRAY[*]}"))
176
+ unset IFS
177
+
178
+ # Get voice by number (adjust for 0-based index)
179
+ INDEX=$((VOICE_NAME - 1))
180
+
181
+ if [[ $INDEX -ge 0 && $INDEX -lt ${#SORTED_VOICES[@]} ]]; then
182
+ VOICE_NAME="${SORTED_VOICES[$INDEX]}"
183
+ FOUND="${SORTED_VOICES[$INDEX]}"
184
+ else
185
+ echo "❌ Invalid number. Please choose between 1 and ${#SORTED_VOICES[@]}"
186
+ exit 1
187
+ fi
188
+ else
189
+ # Check if voice exists (case-insensitive)
190
+ FOUND=""
191
+ for voice in "${!VOICES[@]}"; do
192
+ if [[ "${voice,,}" == "${VOICE_NAME,,}" ]]; then
193
+ FOUND="$voice"
194
+ break
195
+ fi
196
+ done
197
+ fi
198
+
199
+ if [[ -z "$FOUND" ]]; then
200
+ echo "❌ Unknown voice: $VOICE_NAME"
201
+ echo ""
202
+ echo "Available voices:"
203
+ for voice in "${!VOICES[@]}"; do
204
+ echo " - $voice"
205
+ done | sort
206
+ exit 1
207
+ fi
208
+
209
+ echo "$FOUND" > "$VOICE_FILE"
210
+ echo "✅ Voice switched to: $FOUND"
211
+ echo "🎤 Voice ID: ${VOICES[$FOUND]}"
212
+
213
+ # Have the new voice introduce itself (unless silent mode)
214
+ if [[ "$SILENT_MODE" != "true" ]]; then
215
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
216
+ PLAY_TTS="$SCRIPT_DIR/play-tts.sh"
217
+ if [ -x "$PLAY_TTS" ]; then
218
+ "$PLAY_TTS" "Hi, I'm $FOUND. I'll be your voice assistant moving forward." "$FOUND" > /dev/null 2>&1 &
219
+ fi
220
+ fi
221
+ ;;
222
+
223
+ get)
224
+ if [ -f "$VOICE_FILE" ]; then
225
+ cat "$VOICE_FILE"
226
+ else
227
+ echo "Cowboy Bob"
228
+ fi
229
+ ;;
230
+
231
+ whoami)
232
+ echo "🎤 Current Voice Configuration"
233
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
234
+
235
+ # Get current voice
236
+ if [ -f "$VOICE_FILE" ]; then
237
+ CURRENT_VOICE=$(cat "$VOICE_FILE")
238
+ else
239
+ CURRENT_VOICE="Cowboy Bob"
240
+ fi
241
+ echo "Voice: $CURRENT_VOICE"
242
+
243
+ # Get current sentiment (priority)
244
+ if [ -f "$HOME/.claude/tts-sentiment.txt" ]; then
245
+ SENTIMENT=$(cat "$HOME/.claude/tts-sentiment.txt")
246
+ echo "Sentiment: $SENTIMENT (active)"
247
+
248
+ # Also show personality if set
249
+ if [ -f "$HOME/.claude/tts-personality.txt" ]; then
250
+ PERSONALITY=$(cat "$HOME/.claude/tts-personality.txt")
251
+ echo "Personality: $PERSONALITY (overridden by sentiment)"
252
+ fi
253
+ else
254
+ # No sentiment, check personality
255
+ if [ -f "$HOME/.claude/tts-personality.txt" ]; then
256
+ PERSONALITY=$(cat "$HOME/.claude/tts-personality.txt")
257
+ echo "Personality: $PERSONALITY (active)"
258
+ else
259
+ echo "Personality: normal"
260
+ fi
261
+ fi
262
+
263
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
264
+ ;;
265
+
266
+ list-simple)
267
+ # Simple list for AI to parse and display
268
+ for voice in "${!VOICES[@]}"; do
269
+ echo "$voice"
270
+ done | sort
271
+ ;;
272
+
273
+ replay)
274
+ # Replay recent TTS audio from history
275
+ # Use project-local directory with same logic as play-tts.sh
276
+ if [[ -n "$CLAUDE_PROJECT_DIR" ]]; then
277
+ AUDIO_DIR="$CLAUDE_PROJECT_DIR/.claude/audio"
278
+ else
279
+ # Fallback: try to find .claude directory in current path
280
+ CURRENT_DIR="$PWD"
281
+ while [[ "$CURRENT_DIR" != "/" ]]; do
282
+ if [[ -d "$CURRENT_DIR/.claude" ]]; then
283
+ AUDIO_DIR="$CURRENT_DIR/.claude/audio"
284
+ break
285
+ fi
286
+ CURRENT_DIR=$(dirname "$CURRENT_DIR")
287
+ done
288
+ # Final fallback to global if no project .claude found
289
+ if [[ -z "$AUDIO_DIR" ]]; then
290
+ AUDIO_DIR="$HOME/.claude/audio"
291
+ fi
292
+ fi
293
+
294
+ # Default to replay last audio (N=1)
295
+ N="${2:-1}"
296
+
297
+ # Validate N is a number
298
+ if ! [[ "$N" =~ ^[0-9]+$ ]]; then
299
+ echo "❌ Invalid argument. Please use a number (1-10)"
300
+ echo "Usage: /agent-vibes:replay [N]"
301
+ echo " N=1 - Last audio (default)"
302
+ echo " N=2 - Second-to-last"
303
+ echo " N=3 - Third-to-last"
304
+ exit 1
305
+ fi
306
+
307
+ # Check bounds
308
+ if [[ $N -lt 1 || $N -gt 10 ]]; then
309
+ echo "❌ Number out of range. Please choose 1-10"
310
+ exit 1
311
+ fi
312
+
313
+ # Get list of audio files sorted by time (newest first)
314
+ if [[ ! -d "$AUDIO_DIR" ]]; then
315
+ echo "❌ No audio history found"
316
+ echo "Audio files are stored in: $AUDIO_DIR"
317
+ exit 1
318
+ fi
319
+
320
+ # Get the Nth most recent file
321
+ AUDIO_FILE=$(ls -t "$AUDIO_DIR"/tts-*.mp3 2>/dev/null | sed -n "${N}p")
322
+
323
+ if [[ -z "$AUDIO_FILE" ]]; then
324
+ TOTAL=$(ls -t "$AUDIO_DIR"/tts-*.mp3 2>/dev/null | wc -l)
325
+ echo "❌ Audio #$N not found in history"
326
+ echo "Total audio files available: $TOTAL"
327
+ exit 1
328
+ fi
329
+
330
+ echo "🔊 Replaying audio #$N:"
331
+ echo " File: $(basename "$AUDIO_FILE")"
332
+ echo " Path: $AUDIO_FILE"
333
+
334
+ # Play the audio file in background
335
+ (paplay "$AUDIO_FILE" 2>/dev/null || aplay "$AUDIO_FILE" 2>/dev/null || mpg123 "$AUDIO_FILE" 2>/dev/null) &
336
+ ;;
337
+
338
+ *)
339
+ echo "Usage: voice-manager.sh [list|switch|get|replay|whoami] [voice_name]"
340
+ echo ""
341
+ echo "Commands:"
342
+ echo " list - List all available voices"
343
+ echo " switch <voice_name> - Switch to a different voice"
344
+ echo " get - Get current voice name"
345
+ echo " replay [N] - Replay Nth most recent audio (default: 1)"
346
+ echo " whoami - Show current voice and personality"
347
+ exit 1
348
+ ;;
349
+ esac