agentvibes 4.2.0 โ†’ 4.4.1

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 (219) hide show
  1. package/.agentvibes/bmad/bmad-voices.md +69 -69
  2. package/.agentvibes/config.json +12 -0
  3. package/.claude/activation-instructions +54 -54
  4. package/.claude/audio/tracks/README.md +52 -52
  5. package/.claude/commands/agent-vibes/add.md +21 -21
  6. package/.claude/commands/agent-vibes/agent-vibes.md +101 -101
  7. package/.claude/commands/agent-vibes/agent.md +79 -79
  8. package/.claude/commands/agent-vibes/background-music.md +111 -111
  9. package/.claude/commands/agent-vibes/bmad.md +198 -198
  10. package/.claude/commands/agent-vibes/clean.md +18 -18
  11. package/.claude/commands/agent-vibes/cleanup.md +18 -18
  12. package/.claude/commands/agent-vibes/commands.json +145 -145
  13. package/.claude/commands/agent-vibes/effects.md +97 -97
  14. package/.claude/commands/agent-vibes/get.md +9 -9
  15. package/.claude/commands/agent-vibes/hide.md +91 -91
  16. package/.claude/commands/agent-vibes/language.md +23 -23
  17. package/.claude/commands/agent-vibes/learn.md +67 -67
  18. package/.claude/commands/agent-vibes/list.md +13 -13
  19. package/.claude/commands/agent-vibes/mute.md +37 -37
  20. package/.claude/commands/agent-vibes/preview.md +17 -17
  21. package/.claude/commands/agent-vibes/provider.md +68 -68
  22. package/.claude/commands/agent-vibes/replay-target.md +14 -14
  23. package/.claude/commands/agent-vibes/sample.md +12 -12
  24. package/.claude/commands/agent-vibes/set-favorite-voice.md +84 -84
  25. package/.claude/commands/agent-vibes/set-pretext.md +65 -65
  26. package/.claude/commands/agent-vibes/set-speed.md +41 -41
  27. package/.claude/commands/agent-vibes/show.md +84 -84
  28. package/.claude/commands/agent-vibes/switch.md +87 -87
  29. package/.claude/commands/agent-vibes/target-voice.md +26 -26
  30. package/.claude/commands/agent-vibes/target.md +30 -30
  31. package/.claude/commands/agent-vibes/translate.md +68 -68
  32. package/.claude/commands/agent-vibes/unmute.md +45 -45
  33. package/.claude/commands/agent-vibes/verbosity.md +89 -89
  34. package/.claude/commands/agent-vibes/whoami.md +7 -7
  35. package/.claude/commands/agent-vibes-bmad-voices.md +117 -117
  36. package/.claude/commands/agent-vibes-rdp.md +24 -24
  37. package/.claude/config/agentvibes.json +1 -0
  38. package/.claude/config/audio-effects.cfg +2 -2
  39. package/.claude/config/audio-effects.cfg.sample +52 -52
  40. package/.claude/config/background-music-volume.txt +1 -0
  41. package/.claude/config/intro-text.txt +1 -0
  42. package/.claude/config/piper-speech-rate.txt +4 -0
  43. package/.claude/config/piper-target-speech-rate.txt +1 -0
  44. package/.claude/config/reverb-level.txt +1 -0
  45. package/.claude/config/tts-speech-rate.txt +4 -0
  46. package/.claude/config/tts-target-speech-rate.txt +1 -0
  47. package/.claude/docs/TERMUX_SETUP.md +408 -408
  48. package/.claude/github-star-reminder.txt +1 -1
  49. package/.claude/hooks/README-TTS-QUEUE.md +135 -135
  50. package/.claude/hooks/audio-cache-utils.sh +246 -246
  51. package/.claude/hooks/audio-processor.sh +433 -433
  52. package/.claude/hooks/background-music-manager.sh +404 -404
  53. package/.claude/hooks/bmad-speak-enhanced.sh +165 -165
  54. package/.claude/hooks/bmad-speak.sh +269 -269
  55. package/.claude/hooks/bmad-tts-injector.sh +568 -568
  56. package/.claude/hooks/bmad-voice-manager.sh +928 -928
  57. package/.claude/hooks/clawdbot-receiver-SECURE.sh +129 -129
  58. package/.claude/hooks/clawdbot-receiver.sh +107 -107
  59. package/.claude/hooks/clean-audio-cache.sh +22 -22
  60. package/.claude/hooks/cleanup-cache.sh +106 -106
  61. package/.claude/hooks/configure-rdp-mode.sh +137 -137
  62. package/.claude/hooks/download-extra-voices.sh +244 -244
  63. package/.claude/hooks/effects-manager.sh +268 -268
  64. package/.claude/hooks/github-star-reminder.sh +154 -154
  65. package/.claude/hooks/language-manager.sh +362 -362
  66. package/.claude/hooks/learn-manager.sh +492 -492
  67. package/.claude/hooks/macos-voice-manager.sh +205 -205
  68. package/.claude/hooks/migrate-background-music.sh +125 -125
  69. package/.claude/hooks/migrate-to-agentvibes.sh +161 -161
  70. package/.claude/hooks/optimize-background-music.sh +87 -87
  71. package/.claude/hooks/path-resolver.sh +60 -60
  72. package/.claude/hooks/personality-manager.sh +448 -448
  73. package/.claude/hooks/piper-download-voices.sh +225 -225
  74. package/.claude/hooks/piper-installer.sh +292 -292
  75. package/.claude/hooks/piper-multispeaker-registry.sh +171 -171
  76. package/.claude/hooks/piper-voice-manager.sh +24 -3
  77. package/.claude/hooks/play-tts-agentvibes-receiver-for-voiceless-connections.sh +90 -90
  78. package/.claude/hooks/play-tts-enhanced.sh +105 -105
  79. package/.claude/hooks/play-tts-macos.sh +368 -368
  80. package/.claude/hooks/play-tts-piper.sh +679 -679
  81. package/.claude/hooks/play-tts-soprano.sh +356 -356
  82. package/.claude/hooks/play-tts-ssh-remote.sh +167 -167
  83. package/.claude/hooks/play-tts-termux-ssh.sh +169 -169
  84. package/.claude/hooks/play-tts.sh +301 -301
  85. package/.claude/hooks/prepare-release.sh +54 -54
  86. package/.claude/hooks/provider-commands.sh +617 -617
  87. package/.claude/hooks/provider-manager.sh +399 -399
  88. package/.claude/hooks/replay-target-audio.sh +95 -95
  89. package/.claude/hooks/requirements.txt +6 -6
  90. package/.claude/hooks/sentiment-manager.sh +201 -201
  91. package/.claude/hooks/session-start-tts.sh +81 -81
  92. package/.claude/hooks/soprano-gradio-synth.py +139 -139
  93. package/.claude/hooks/speed-manager.sh +291 -291
  94. package/.claude/hooks/stop-tts.sh +84 -84
  95. package/.claude/hooks/termux-installer.sh +261 -261
  96. package/.claude/hooks/translate-manager.sh +341 -341
  97. package/.claude/hooks/translator.py +237 -237
  98. package/.claude/hooks/tts-queue-worker.sh +145 -145
  99. package/.claude/hooks/tts-queue.sh +165 -165
  100. package/.claude/hooks/verbosity-manager.sh +178 -178
  101. package/.claude/hooks/voice-manager.sh +548 -548
  102. package/.claude/hooks-windows/audio-cache-utils.ps1 +119 -119
  103. package/.claude/hooks-windows/background-music-manager.ps1 +348 -0
  104. package/.claude/hooks-windows/clean-audio-cache.ps1 +53 -0
  105. package/.claude/hooks-windows/download-extra-voices.ps1 +185 -0
  106. package/.claude/hooks-windows/effects-manager.ps1 +294 -0
  107. package/.claude/hooks-windows/language-manager.ps1 +193 -0
  108. package/.claude/hooks-windows/learn-manager.ps1 +241 -0
  109. package/.claude/hooks-windows/personality-manager.ps1 +266 -0
  110. package/.claude/hooks-windows/play-tts-piper.ps1 +209 -0
  111. package/.claude/hooks-windows/play-tts-sapi.ps1 +108 -0
  112. package/.claude/hooks-windows/play-tts-soprano.ps1 +159 -158
  113. package/.claude/hooks-windows/play-tts-windows-piper.ps1 +50 -5
  114. package/.claude/hooks-windows/play-tts-windows-sapi.ps1 +108 -108
  115. package/.claude/hooks-windows/play-tts.ps1 +344 -266
  116. package/.claude/hooks-windows/provider-manager.ps1 +29 -10
  117. package/.claude/hooks-windows/session-start-tts.ps1 +124 -124
  118. package/.claude/hooks-windows/soprano-gradio-synth.py +153 -153
  119. package/.claude/hooks-windows/speed-manager.ps1 +166 -0
  120. package/.claude/hooks-windows/verbosity-manager.ps1 +119 -0
  121. package/.claude/hooks-windows/voice-manager-windows.ps1 +92 -8
  122. package/.claude/output-styles/agent-vibes.md +202 -202
  123. package/.claude/personalities/angry.md +14 -14
  124. package/.claude/personalities/annoying.md +14 -14
  125. package/.claude/personalities/crass.md +14 -14
  126. package/.claude/personalities/dramatic.md +14 -14
  127. package/.claude/personalities/dry-humor.md +50 -50
  128. package/.claude/personalities/flirty.md +20 -20
  129. package/.claude/personalities/funny.md +14 -14
  130. package/.claude/personalities/grandpa.md +32 -32
  131. package/.claude/personalities/millennial.md +14 -14
  132. package/.claude/personalities/moody.md +14 -14
  133. package/.claude/personalities/normal.md +16 -16
  134. package/.claude/personalities/pirate.md +14 -14
  135. package/.claude/personalities/poetic.md +14 -14
  136. package/.claude/personalities/professional.md +14 -14
  137. package/.claude/personalities/rapper.md +55 -55
  138. package/.claude/personalities/robot.md +14 -14
  139. package/.claude/personalities/sarcastic.md +38 -38
  140. package/.claude/personalities/sassy.md +14 -14
  141. package/.claude/personalities/surfer-dude.md +14 -14
  142. package/.claude/personalities/zen.md +14 -14
  143. package/.claude/settings.json +15 -15
  144. package/.claude/verbosity.txt +1 -1
  145. package/.clawdbot/README.md +105 -105
  146. package/.clawdbot/skill/SKILL.md +241 -241
  147. package/.mcp.json +12 -0
  148. package/CLAUDE.md +170 -170
  149. package/README.md +2029 -2007
  150. package/RELEASE_NOTES.md +1310 -1203
  151. package/WINDOWS-SETUP.md +208 -208
  152. package/bin/agent-vibes +39 -39
  153. package/bin/agentvibes-voice-browser.js +1840 -1840
  154. package/bin/agentvibes.js +48 -2
  155. package/bin/mcp-server.js +121 -121
  156. package/bin/mcp-server.sh +206 -206
  157. package/bin/test-bmad-pr +78 -78
  158. package/mcp-server/QUICK_START.md +203 -203
  159. package/mcp-server/README.md +345 -345
  160. package/mcp-server/WINDOWS_SETUP.md +260 -260
  161. package/mcp-server/docs/troubleshooting-audio.md +313 -313
  162. package/mcp-server/examples/claude_desktop_config.json +11 -11
  163. package/mcp-server/examples/claude_desktop_config_piper.json +9 -9
  164. package/mcp-server/examples/custom_instructions.md +169 -169
  165. package/mcp-server/install-deps.js +130 -130
  166. package/mcp-server/pyproject.toml +52 -52
  167. package/mcp-server/requirements.txt +2 -2
  168. package/mcp-server/server.py +1465 -1453
  169. package/mcp-server/test_server.py +395 -395
  170. package/mcp-server/test_windows_script_parity.py +336 -0
  171. package/package.json +110 -110
  172. package/setup-windows.ps1 +815 -815
  173. package/src/bmad-detector.js +71 -71
  174. package/src/cli/list-personalities.js +110 -110
  175. package/src/cli/list-voices.js +114 -114
  176. package/src/commands/bmad-voices.js +394 -394
  177. package/src/commands/install-mcp.js +476 -476
  178. package/src/console/app.js +824 -824
  179. package/src/console/audio-env.js +20 -1
  180. package/src/console/brand-colors.js +13 -13
  181. package/src/console/constants/personalities.js +44 -44
  182. package/src/console/footer-config.js +50 -50
  183. package/src/console/modals/modal-overlay.js +247 -247
  184. package/src/console/navigation.js +62 -62
  185. package/src/console/tabs/agents-tab.js +1684 -1516
  186. package/src/console/tabs/help-tab.js +261 -261
  187. package/src/console/tabs/install-tab.js +1007 -991
  188. package/src/console/tabs/music-tab.js +22 -8
  189. package/src/console/tabs/placeholder-tab.js +53 -53
  190. package/src/console/tabs/readme-tab.js +267 -267
  191. package/src/console/tabs/receiver-tab.js +1472 -1212
  192. package/src/console/tabs/settings-tab.js +208 -84
  193. package/src/console/tabs/voices-tab.js +100 -21
  194. package/src/console/widgets/destroy-list.js +25 -25
  195. package/src/console/widgets/format-utils.js +89 -89
  196. package/src/console/widgets/notice.js +55 -55
  197. package/src/console/widgets/personality-picker.js +185 -185
  198. package/src/console/widgets/reverb-picker.js +94 -94
  199. package/src/console/widgets/track-picker.js +285 -285
  200. package/src/installer/music-file-input.js +304 -304
  201. package/src/installer.js +5895 -5829
  202. package/src/services/agent-voice-store.js +423 -423
  203. package/src/services/config-service.js +264 -264
  204. package/src/services/navigation-service.js +123 -123
  205. package/src/services/provider-service.js +143 -132
  206. package/src/services/verbosity-service.js +157 -157
  207. package/src/utils/audio-duration-validator.js +298 -298
  208. package/src/utils/audio-format-validator.js +277 -277
  209. package/src/utils/dependency-checker.js +469 -466
  210. package/src/utils/file-ownership-verifier.js +358 -358
  211. package/src/utils/list-formatter.js +194 -194
  212. package/src/utils/music-file-validator.js +285 -285
  213. package/src/utils/preview-list-prompt.js +136 -136
  214. package/src/utils/provider-validator.js +96 -12
  215. package/src/utils/secure-music-storage.js +412 -412
  216. package/templates/agentvibes-receiver.sh +482 -482
  217. package/templates/audio/welcome-music.mp3 +0 -0
  218. package/voice-assignments.json +8244 -8244
  219. package/.claude/config/background-music-position.txt +0 -1
@@ -1,368 +1,368 @@
1
- #!/usr/bin/env bash
2
- #
3
- # File: .claude/hooks/play-tts-macos.sh
4
- #
5
- # AgentVibes - Finally, your AI Agents can Talk Back! Text-to-Speech WITH personality for AI Assistants!
6
- # Website: https://agentvibes.org
7
- # Repository: https://github.com/paulpreibisch/AgentVibes
8
- #
9
- # Co-created by Paul Preibisch with Claude AI
10
- # Copyright (c) 2025 Paul Preibisch
11
- #
12
- # Licensed under the Apache License, Version 2.0 (the "License");
13
- # you may not use this file except in compliance with the License.
14
- # You may obtain a copy of the License at
15
- #
16
- # http://www.apache.org/licenses/LICENSE-2.0
17
- #
18
- # Unless required by applicable law or agreed to in writing, software
19
- # distributed under the License is distributed on an "AS IS" BASIS,
20
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21
- # See the License for the specific language governing permissions and
22
- # limitations under the License.
23
- #
24
- # DISCLAIMER: This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
25
- # express or implied. Use at your own risk. See the Apache License for details.
26
- #
27
- # ---
28
- #
29
- # @fileoverview macOS Say Provider Implementation - Native macOS TTS using the 'say' command
30
- # @context Provides zero-dependency, offline TTS for macOS users using built-in Apple voices
31
- # @architecture Implements provider interface contract for macOS 'say' command integration
32
- # @dependencies macOS only (Darwin), say command (built-in), afplay (built-in)
33
- # @entrypoints Called by play-tts.sh router when provider=macos
34
- # @patterns Provider contract: text/voice โ†’ audio file path, voice validation, platform guard
35
- # @related play-tts.sh, macos-voice-manager.sh, provider-manager.sh
36
- #
37
-
38
- # Platform guard - fail fast on non-macOS systems
39
- if [[ "$(uname -s)" != "Darwin" ]]; then
40
- echo "โŒ Error: macOS provider only works on macOS"
41
- echo " Current platform: $(uname -s)"
42
- echo ""
43
- echo " Switch to a different provider:"
44
- echo " /agent-vibes:provider switch piper"
45
- exit 1
46
- fi
47
-
48
- TEXT="$1"
49
- VOICE_OVERRIDE="$2" # Optional: voice name (e.g., "Samantha", "Daniel")
50
-
51
- # Strip emojis, asterisks, and markdown formatting
52
- TEXT=$(printf '%s' "$TEXT" | perl -CSD -pe '
53
- s/[\x{1F300}-\x{1F9FF}]//g;
54
- s/[\x{2600}-\x{27BF}]//g;
55
- s/[\x{FE00}-\x{FE0F}]//g;
56
- s/[\x{200D}]//g;
57
- s/[\x{2500}-\x{257F}]//g;
58
- s/[\x{2580}-\x{259F}]//g;
59
- s/\*+//g; s/#+\s*//g; s/`//g; s/~+//g; s/^\s*[-]\s*//g;
60
- ')
61
-
62
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
63
-
64
- # Source audio cache utilities
65
- source "$SCRIPT_DIR/audio-cache-utils.sh"
66
-
67
- # Default voice for macOS
68
- DEFAULT_VOICE="Samantha"
69
-
70
- # Common macOS voices with descriptions
71
- # These are typically available on all macOS systems
72
- # Using simple list format for bash 3.x compatibility (macOS default)
73
- show_common_voices() {
74
- echo " Alex - American English male (enhanced)"
75
- echo " Daniel - British English male (enhanced)"
76
- echo " Fiona - Scottish English female (enhanced)"
77
- echo " Karen - Australian English female (enhanced)"
78
- echo " Moira - Irish English female (enhanced)"
79
- echo " Samantha - American English female (enhanced)"
80
- echo " Tessa - South African English female (enhanced)"
81
- echo " Veena - Indian English female (enhanced)"
82
- echo " Victoria - American English female"
83
- }
84
-
85
- # @function get_voice_file_path
86
- # @intent Determine path to voice configuration file
87
- # @returns Echoes path to tts-voice.txt
88
- get_voice_file_path() {
89
- local voice_file=""
90
-
91
- if [[ -n "$CLAUDE_PROJECT_DIR" ]] && [[ -f "$CLAUDE_PROJECT_DIR/.claude/tts-voice.txt" ]]; then
92
- voice_file="$CLAUDE_PROJECT_DIR/.claude/tts-voice.txt"
93
- elif [[ -f "$SCRIPT_DIR/../tts-voice.txt" ]]; then
94
- voice_file="$SCRIPT_DIR/../tts-voice.txt"
95
- elif [[ -f "$HOME/.claude/tts-voice.txt" ]]; then
96
- voice_file="$HOME/.claude/tts-voice.txt"
97
- fi
98
-
99
- echo "$voice_file"
100
- }
101
-
102
- # @function determine_voice
103
- # @intent Resolve which voice to use
104
- # @returns Sets $VOICE_NAME global variable
105
- VOICE_NAME=""
106
-
107
- if [[ -n "$VOICE_OVERRIDE" ]]; then
108
- VOICE_NAME="$VOICE_OVERRIDE"
109
- echo "๐ŸŽค Using voice: $VOICE_OVERRIDE (session-specific)"
110
- else
111
- VOICE_FILE=$(get_voice_file_path)
112
-
113
- if [[ -n "$VOICE_FILE" ]] && [[ -f "$VOICE_FILE" ]]; then
114
- FILE_VOICE=$(cat "$VOICE_FILE" 2>/dev/null | tr -d '\n\r')
115
- if [[ -n "$FILE_VOICE" ]]; then
116
- VOICE_NAME="$FILE_VOICE"
117
- fi
118
- fi
119
-
120
- # Fallback to default if no voice set
121
- if [[ -z "$VOICE_NAME" ]]; then
122
- VOICE_NAME="$DEFAULT_VOICE"
123
- fi
124
- fi
125
-
126
- # @function validate_inputs
127
- # @intent Check required parameters
128
- if [[ -z "$TEXT" ]]; then
129
- echo "Usage: $0 \"text to speak\" [voice_name]"
130
- echo ""
131
- echo "Common voices (run 'say -v ?' for full list):"
132
- show_common_voices
133
- exit 1
134
- fi
135
-
136
- # @function validate_voice
137
- # @intent Check if the specified voice exists on this system
138
- # @param $1 Voice name to validate
139
- # @returns 0 if valid, 1 if not found
140
- validate_voice() {
141
- local voice="$1"
142
- say -v ? 2>/dev/null | grep -qi "^${voice} "
143
- }
144
-
145
- # Validate voice exists (case-insensitive search)
146
- if ! validate_voice "$VOICE_NAME"; then
147
- echo "โš ๏ธ Voice '$VOICE_NAME' not found on this system"
148
- echo " Falling back to default: $DEFAULT_VOICE"
149
- VOICE_NAME="$DEFAULT_VOICE"
150
-
151
- # If default also doesn't exist, try to find any English voice
152
- if ! validate_voice "$VOICE_NAME"; then
153
- VOICE_NAME=$(say -v ? 2>/dev/null | grep -i "en_" | head -1 | awk '{print $1}')
154
- if [[ -z "$VOICE_NAME" ]]; then
155
- echo "โŒ No English voices found on this system"
156
- exit 2
157
- fi
158
- echo " Using first available English voice: $VOICE_NAME"
159
- fi
160
- fi
161
-
162
- # @function determine_audio_directory
163
- # @intent Find appropriate directory for audio file storage
164
- if [[ -n "$CLAUDE_PROJECT_DIR" ]]; then
165
- AUDIO_DIR="$CLAUDE_PROJECT_DIR/.claude/audio"
166
- else
167
- CURRENT_DIR="$PWD"
168
- while [[ "$CURRENT_DIR" != "/" ]]; do
169
- if [[ -d "$CURRENT_DIR/.claude" ]]; then
170
- AUDIO_DIR="$CURRENT_DIR/.claude/audio"
171
- break
172
- fi
173
- CURRENT_DIR=$(dirname "$CURRENT_DIR")
174
- done
175
- if [[ -z "$AUDIO_DIR" ]]; then
176
- AUDIO_DIR="$HOME/.claude/audio"
177
- fi
178
- fi
179
-
180
- mkdir -p "$AUDIO_DIR"
181
-
182
- # SECURITY: Use mktemp for unpredictable filenames (#130)
183
- TEMP_FILE=$(mktemp "$AUDIO_DIR/tts-XXXXXX.aiff")
184
- FINAL_FILE=$(mktemp "$AUDIO_DIR/tts-padded-XXXXXX.wav")
185
-
186
- # @function get_speech_rate
187
- # @intent Determine speech rate for synthesis
188
- # @returns Speech rate value (words per minute, default ~175-200)
189
- get_speech_rate() {
190
- local rate_config=""
191
-
192
- # Check for rate config file
193
- if [[ -f "$SCRIPT_DIR/../config/tts-speech-rate.txt" ]]; then
194
- rate_config="$SCRIPT_DIR/../config/tts-speech-rate.txt"
195
- elif [[ -f "$HOME/.claude/config/tts-speech-rate.txt" ]]; then
196
- rate_config="$HOME/.claude/config/tts-speech-rate.txt"
197
- fi
198
-
199
- if [[ -n "$rate_config" ]]; then
200
- local user_speed=$(cat "$rate_config" 2>/dev/null | grep -v '^#' | grep -v '^$' | tail -1)
201
- # Convert multiplier to words per minute (base ~200 WPM)
202
- # User: 0.5=slower, 1.0=normal, 2.0=faster
203
- echo "scale=0; 200 * $user_speed / 1" | bc -l 2>/dev/null || echo "200"
204
- return
205
- fi
206
-
207
- # Default: 200 WPM (normal rate)
208
- echo "200"
209
- }
210
-
211
- SPEECH_RATE=$(get_speech_rate)
212
-
213
- # @function synthesize_with_say
214
- # @intent Generate speech using macOS 'say' command
215
- # @returns Creates audio file at $TEMP_FILE
216
- echo "$TEXT" | say -v "$VOICE_NAME" -r "$SPEECH_RATE" -o "$TEMP_FILE" 2>/dev/null
217
-
218
- if [[ ! -f "$TEMP_FILE" ]] || [[ ! -s "$TEMP_FILE" ]]; then
219
- echo "โŒ Failed to synthesize speech with macOS say command"
220
- echo "Voice: $VOICE_NAME"
221
- exit 3
222
- fi
223
-
224
- # @function convert_and_pad_audio
225
- # @intent Convert AIFF to WAV and add silence padding for consistency
226
- # @why Maintains consistent audio format across providers
227
- if command -v ffmpeg &> /dev/null; then
228
- # Add 200ms of silence at the beginning and convert to WAV
229
- ffmpeg -f lavfi -i anullsrc=r=44100:cl=stereo:d=0.2 -i "$TEMP_FILE" \
230
- -filter_complex "[0:a][1:a]concat=n=2:v=0:a=1[out]" \
231
- -map "[out]" -y "$FINAL_FILE" 2>/dev/null
232
-
233
- if [[ -f "$FINAL_FILE" ]]; then
234
- rm -f "$TEMP_FILE"
235
- TEMP_FILE="$FINAL_FILE"
236
- fi
237
- else
238
- # No ffmpeg - use AIFF directly (rename for consistency)
239
- FINAL_FILE=$(mktemp "$AUDIO_DIR/tts-padded-XXXXXX.aiff")
240
- mv "$TEMP_FILE" "$FINAL_FILE"
241
- TEMP_FILE="$FINAL_FILE"
242
- fi
243
-
244
- # @function play_audio
245
- # @intent Play generated audio - via PulseAudio tunnel for SSH, afplay for local
246
- # SECURITY: Use user-isolated lock directory (#129)
247
- _LOCK_DIR="${XDG_RUNTIME_DIR:-/tmp/agentvibes-$(id -u)}"
248
- mkdir -p "$_LOCK_DIR"
249
- chmod 700 "$_LOCK_DIR"
250
- LOCK_FILE="$_LOCK_DIR/agentvibes-audio.lock"
251
-
252
- # Auto-remove stale lock files (older than 30 seconds)
253
- if [ -f "$LOCK_FILE" ]; then
254
- _lock_mtime=$(stat -f %m "$LOCK_FILE" 2>/dev/null || echo 0)
255
- _lock_age=$(( $(date +%s) - _lock_mtime ))
256
- if [[ $_lock_age -gt 30 ]]; then
257
- rm -f "$LOCK_FILE"
258
- fi
259
- fi
260
-
261
- # Wait for previous audio to finish (max 30 seconds)
262
- for i in {1..60}; do
263
- if [ ! -f "$LOCK_FILE" ]; then
264
- break
265
- fi
266
- sleep 0.5
267
- done
268
-
269
- # Create lock and play audio
270
- touch "$LOCK_FILE"
271
-
272
- # Get audio duration for proper lock timing
273
- if command -v ffprobe &> /dev/null; then
274
- DURATION=$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$TEMP_FILE" 2>/dev/null)
275
- DURATION=${DURATION%.*} # Round to integer
276
- else
277
- # Estimate duration based on text length (~150 WPM)
278
- WORD_COUNT=$(echo "$TEXT" | wc -w)
279
- DURATION=$(( (WORD_COUNT * 60 / 150) + 1 ))
280
- fi
281
- DURATION=${DURATION:-2} # Default to 2 seconds if detection fails
282
-
283
- # Play audio in background (skip if in test mode or no-playback mode)
284
- # AGENTVIBES_NO_PLAYBACK: Set to "true" to generate audio without playing (for post-processing)
285
- if [[ "${AGENTVIBES_TEST_MODE:-false}" != "true" ]] && [[ "${AGENTVIBES_NO_PLAYBACK:-false}" != "true" ]]; then
286
- # Check if we're in an SSH session with PulseAudio tunnel available
287
- if [[ -n "$SSH_CONNECTION" ]] && [[ -n "$PULSE_SERVER" ]]; then
288
- # Use paplay to send audio through PulseAudio tunnel to remote machine
289
- if command -v /opt/homebrew/bin/paplay &> /dev/null; then
290
- /opt/homebrew/bin/paplay "$TEMP_FILE" >/dev/null 2>&1 &
291
- PLAYER_PID=$!
292
- echo "๐Ÿ”Š Playing via PulseAudio tunnel"
293
- else
294
- echo "โš ๏ธ paplay not found - install pulseaudio for SSH audio"
295
- afplay "$TEMP_FILE" >/dev/null 2>&1 &
296
- PLAYER_PID=$!
297
- fi
298
- else
299
- # Local session - use native macOS player
300
- afplay "$TEMP_FILE" >/dev/null 2>&1 &
301
- PLAYER_PID=$!
302
- fi
303
- fi
304
-
305
- # Wait for audio to finish, then release lock
306
- (sleep $DURATION; rm -f "$LOCK_FILE") &
307
- disown
308
-
309
- # Get audio cache stats
310
- AUDIO_DIR_PATH=$(get_audio_dir)
311
- FILE_COUNT=$(count_tts_files "$AUDIO_DIR_PATH")
312
- SIZE_BYTES=$(calculate_tts_size_bytes "$AUDIO_DIR_PATH")
313
- SIZE_HUMAN=$(bytes_to_human "$SIZE_BYTES")
314
-
315
- # Color codes
316
- BLUE='\033[0;34m'
317
- YELLOW='\033[1;33m'
318
- PURPLE='\033[0;35m'
319
- LIGHT_PURPLE='\033[1;35m'
320
- RED='\033[0;31m'
321
- GREEN='\033[0;32m'
322
- ORANGE='\033[0;33m'
323
- WHITE='\033[1;37m'
324
- MAGENTA='\033[0;35m'
325
- CYAN='\033[0;36m'
326
- GOLD='\033[38;5;226m'
327
- NC='\033[0m'
328
-
329
- # Dynamic color coding based on cache size
330
- # Green: < 500MB (small)
331
- # Yellow: 500MB - 3GB (lots)
332
- # Red: > 3GB (extreme)
333
- CACHE_COLOR=$GREEN
334
- if [[ $SIZE_BYTES -gt 3221225472 ]]; then # > 3GB
335
- CACHE_COLOR=$RED
336
- elif [[ $SIZE_BYTES -gt 524288000 ]]; then # > 500MB
337
- CACHE_COLOR=$YELLOW
338
- fi
339
-
340
- # Display with file count and auto-clean indicator
341
- # Get auto-clean threshold for display
342
- AUTO_CLEAN_THRESHOLD=$(get_auto_clean_threshold)
343
- echo -e "${WHITE}๐Ÿ’พ Saved to:${NC} ${CYAN}$TEMP_FILE${NC} ${WHITE}๐Ÿ“ฆ${NC} ${YELLOW}$FILE_COUNT${NC} ${CACHE_COLOR}$SIZE_HUMAN${NC} ${WHITE}๐Ÿงน${NC}${GOLD}[${AUTO_CLEAN_THRESHOLD}mb]${NC}"
344
-
345
- # Auto-cleanup check - delete oldest files if over size threshold
346
- THRESHOLD_MB=$(get_auto_clean_threshold)
347
- if [[ $SIZE_BYTES -gt $((THRESHOLD_MB * 1048576)) ]]; then
348
- DELETED=$(auto_clean_old_files "$AUDIO_DIR_PATH" "$THRESHOLD_MB")
349
- if [[ $DELETED -gt 0 ]]; then
350
- echo -e "${ORANGE}๐Ÿงน Auto-cleaned $DELETED files${NC}"
351
- fi
352
- fi
353
-
354
- echo -e "${CYAN}๐ŸŽค Voice used:${NC} ${WHITE}$VOICE_NAME (macOS Say)${NC}"
355
-
356
- # Show personality if configured
357
- PERSONALITY=$(cat "$PROJECT_ROOT/.claude/tts-personality.txt" 2>/dev/null || cat "$HOME/.claude/tts-personality.txt" 2>/dev/null || echo "")
358
- if [[ -n "$PERSONALITY" ]] && [[ "$PERSONALITY" != "none" ]] && [[ "$PERSONALITY" != "normal" ]]; then
359
- echo -e "${GOLD}๐Ÿ’ซ Personality:${NC} ${WHITE}$PERSONALITY${NC}"
360
- fi
361
-
362
- # Check audio folder size and warn if getting large
363
- if [[ -d "$AUDIO_DIR_PATH" ]]; then
364
- AUDIO_SIZE=$(du -sm "$AUDIO_DIR_PATH" 2>/dev/null | cut -f1)
365
- if [[ -n "$AUDIO_SIZE" ]] && [[ "$AUDIO_SIZE" -gt 100 ]]; then
366
- echo -e "\033[0;31mโš ๏ธ Audio cache is ${AUDIO_SIZE}MB - Run: /agent-vibes:cleanup\033[0m"
367
- fi
368
- fi
1
+ #!/usr/bin/env bash
2
+ #
3
+ # File: .claude/hooks/play-tts-macos.sh
4
+ #
5
+ # AgentVibes - Finally, your AI Agents can Talk Back! Text-to-Speech WITH personality for AI Assistants!
6
+ # Website: https://agentvibes.org
7
+ # Repository: https://github.com/paulpreibisch/AgentVibes
8
+ #
9
+ # Co-created by Paul Preibisch with Claude AI
10
+ # Copyright (c) 2025 Paul Preibisch
11
+ #
12
+ # Licensed under the Apache License, Version 2.0 (the "License");
13
+ # you may not use this file except in compliance with the License.
14
+ # You may obtain a copy of the License at
15
+ #
16
+ # http://www.apache.org/licenses/LICENSE-2.0
17
+ #
18
+ # Unless required by applicable law or agreed to in writing, software
19
+ # distributed under the License is distributed on an "AS IS" BASIS,
20
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21
+ # See the License for the specific language governing permissions and
22
+ # limitations under the License.
23
+ #
24
+ # DISCLAIMER: This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
25
+ # express or implied. Use at your own risk. See the Apache License for details.
26
+ #
27
+ # ---
28
+ #
29
+ # @fileoverview macOS Say Provider Implementation - Native macOS TTS using the 'say' command
30
+ # @context Provides zero-dependency, offline TTS for macOS users using built-in Apple voices
31
+ # @architecture Implements provider interface contract for macOS 'say' command integration
32
+ # @dependencies macOS only (Darwin), say command (built-in), afplay (built-in)
33
+ # @entrypoints Called by play-tts.sh router when provider=macos
34
+ # @patterns Provider contract: text/voice โ†’ audio file path, voice validation, platform guard
35
+ # @related play-tts.sh, macos-voice-manager.sh, provider-manager.sh
36
+ #
37
+
38
+ # Platform guard - fail fast on non-macOS systems
39
+ if [[ "$(uname -s)" != "Darwin" ]]; then
40
+ echo "โŒ Error: macOS provider only works on macOS"
41
+ echo " Current platform: $(uname -s)"
42
+ echo ""
43
+ echo " Switch to a different provider:"
44
+ echo " /agent-vibes:provider switch piper"
45
+ exit 1
46
+ fi
47
+
48
+ TEXT="$1"
49
+ VOICE_OVERRIDE="$2" # Optional: voice name (e.g., "Samantha", "Daniel")
50
+
51
+ # Strip emojis, asterisks, and markdown formatting
52
+ TEXT=$(printf '%s' "$TEXT" | perl -CSD -pe '
53
+ s/[\x{1F300}-\x{1F9FF}]//g;
54
+ s/[\x{2600}-\x{27BF}]//g;
55
+ s/[\x{FE00}-\x{FE0F}]//g;
56
+ s/[\x{200D}]//g;
57
+ s/[\x{2500}-\x{257F}]//g;
58
+ s/[\x{2580}-\x{259F}]//g;
59
+ s/\*+//g; s/#+\s*//g; s/`//g; s/~+//g; s/^\s*[-]\s*//g;
60
+ ')
61
+
62
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
63
+
64
+ # Source audio cache utilities
65
+ source "$SCRIPT_DIR/audio-cache-utils.sh"
66
+
67
+ # Default voice for macOS
68
+ DEFAULT_VOICE="Samantha"
69
+
70
+ # Common macOS voices with descriptions
71
+ # These are typically available on all macOS systems
72
+ # Using simple list format for bash 3.x compatibility (macOS default)
73
+ show_common_voices() {
74
+ echo " Alex - American English male (enhanced)"
75
+ echo " Daniel - British English male (enhanced)"
76
+ echo " Fiona - Scottish English female (enhanced)"
77
+ echo " Karen - Australian English female (enhanced)"
78
+ echo " Moira - Irish English female (enhanced)"
79
+ echo " Samantha - American English female (enhanced)"
80
+ echo " Tessa - South African English female (enhanced)"
81
+ echo " Veena - Indian English female (enhanced)"
82
+ echo " Victoria - American English female"
83
+ }
84
+
85
+ # @function get_voice_file_path
86
+ # @intent Determine path to voice configuration file
87
+ # @returns Echoes path to tts-voice.txt
88
+ get_voice_file_path() {
89
+ local voice_file=""
90
+
91
+ if [[ -n "$CLAUDE_PROJECT_DIR" ]] && [[ -f "$CLAUDE_PROJECT_DIR/.claude/tts-voice.txt" ]]; then
92
+ voice_file="$CLAUDE_PROJECT_DIR/.claude/tts-voice.txt"
93
+ elif [[ -f "$SCRIPT_DIR/../tts-voice.txt" ]]; then
94
+ voice_file="$SCRIPT_DIR/../tts-voice.txt"
95
+ elif [[ -f "$HOME/.claude/tts-voice.txt" ]]; then
96
+ voice_file="$HOME/.claude/tts-voice.txt"
97
+ fi
98
+
99
+ echo "$voice_file"
100
+ }
101
+
102
+ # @function determine_voice
103
+ # @intent Resolve which voice to use
104
+ # @returns Sets $VOICE_NAME global variable
105
+ VOICE_NAME=""
106
+
107
+ if [[ -n "$VOICE_OVERRIDE" ]]; then
108
+ VOICE_NAME="$VOICE_OVERRIDE"
109
+ echo "๐ŸŽค Using voice: $VOICE_OVERRIDE (session-specific)"
110
+ else
111
+ VOICE_FILE=$(get_voice_file_path)
112
+
113
+ if [[ -n "$VOICE_FILE" ]] && [[ -f "$VOICE_FILE" ]]; then
114
+ FILE_VOICE=$(cat "$VOICE_FILE" 2>/dev/null | tr -d '\n\r')
115
+ if [[ -n "$FILE_VOICE" ]]; then
116
+ VOICE_NAME="$FILE_VOICE"
117
+ fi
118
+ fi
119
+
120
+ # Fallback to default if no voice set
121
+ if [[ -z "$VOICE_NAME" ]]; then
122
+ VOICE_NAME="$DEFAULT_VOICE"
123
+ fi
124
+ fi
125
+
126
+ # @function validate_inputs
127
+ # @intent Check required parameters
128
+ if [[ -z "$TEXT" ]]; then
129
+ echo "Usage: $0 \"text to speak\" [voice_name]"
130
+ echo ""
131
+ echo "Common voices (run 'say -v ?' for full list):"
132
+ show_common_voices
133
+ exit 1
134
+ fi
135
+
136
+ # @function validate_voice
137
+ # @intent Check if the specified voice exists on this system
138
+ # @param $1 Voice name to validate
139
+ # @returns 0 if valid, 1 if not found
140
+ validate_voice() {
141
+ local voice="$1"
142
+ say -v ? 2>/dev/null | grep -qi "^${voice} "
143
+ }
144
+
145
+ # Validate voice exists (case-insensitive search)
146
+ if ! validate_voice "$VOICE_NAME"; then
147
+ echo "โš ๏ธ Voice '$VOICE_NAME' not found on this system"
148
+ echo " Falling back to default: $DEFAULT_VOICE"
149
+ VOICE_NAME="$DEFAULT_VOICE"
150
+
151
+ # If default also doesn't exist, try to find any English voice
152
+ if ! validate_voice "$VOICE_NAME"; then
153
+ VOICE_NAME=$(say -v ? 2>/dev/null | grep -i "en_" | head -1 | awk '{print $1}')
154
+ if [[ -z "$VOICE_NAME" ]]; then
155
+ echo "โŒ No English voices found on this system"
156
+ exit 2
157
+ fi
158
+ echo " Using first available English voice: $VOICE_NAME"
159
+ fi
160
+ fi
161
+
162
+ # @function determine_audio_directory
163
+ # @intent Find appropriate directory for audio file storage
164
+ if [[ -n "$CLAUDE_PROJECT_DIR" ]]; then
165
+ AUDIO_DIR="$CLAUDE_PROJECT_DIR/.claude/audio"
166
+ else
167
+ CURRENT_DIR="$PWD"
168
+ while [[ "$CURRENT_DIR" != "/" ]]; do
169
+ if [[ -d "$CURRENT_DIR/.claude" ]]; then
170
+ AUDIO_DIR="$CURRENT_DIR/.claude/audio"
171
+ break
172
+ fi
173
+ CURRENT_DIR=$(dirname "$CURRENT_DIR")
174
+ done
175
+ if [[ -z "$AUDIO_DIR" ]]; then
176
+ AUDIO_DIR="$HOME/.claude/audio"
177
+ fi
178
+ fi
179
+
180
+ mkdir -p "$AUDIO_DIR"
181
+
182
+ # SECURITY: Use mktemp for unpredictable filenames (#130)
183
+ TEMP_FILE=$(mktemp "$AUDIO_DIR/tts-XXXXXX.aiff")
184
+ FINAL_FILE=$(mktemp "$AUDIO_DIR/tts-padded-XXXXXX.wav")
185
+
186
+ # @function get_speech_rate
187
+ # @intent Determine speech rate for synthesis
188
+ # @returns Speech rate value (words per minute, default ~175-200)
189
+ get_speech_rate() {
190
+ local rate_config=""
191
+
192
+ # Check for rate config file
193
+ if [[ -f "$SCRIPT_DIR/../config/tts-speech-rate.txt" ]]; then
194
+ rate_config="$SCRIPT_DIR/../config/tts-speech-rate.txt"
195
+ elif [[ -f "$HOME/.claude/config/tts-speech-rate.txt" ]]; then
196
+ rate_config="$HOME/.claude/config/tts-speech-rate.txt"
197
+ fi
198
+
199
+ if [[ -n "$rate_config" ]]; then
200
+ local user_speed=$(cat "$rate_config" 2>/dev/null | grep -v '^#' | grep -v '^$' | tail -1)
201
+ # Convert multiplier to words per minute (base ~200 WPM)
202
+ # User: 0.5=slower, 1.0=normal, 2.0=faster
203
+ echo "scale=0; 200 * $user_speed / 1" | bc -l 2>/dev/null || echo "200"
204
+ return
205
+ fi
206
+
207
+ # Default: 200 WPM (normal rate)
208
+ echo "200"
209
+ }
210
+
211
+ SPEECH_RATE=$(get_speech_rate)
212
+
213
+ # @function synthesize_with_say
214
+ # @intent Generate speech using macOS 'say' command
215
+ # @returns Creates audio file at $TEMP_FILE
216
+ echo "$TEXT" | say -v "$VOICE_NAME" -r "$SPEECH_RATE" -o "$TEMP_FILE" 2>/dev/null
217
+
218
+ if [[ ! -f "$TEMP_FILE" ]] || [[ ! -s "$TEMP_FILE" ]]; then
219
+ echo "โŒ Failed to synthesize speech with macOS say command"
220
+ echo "Voice: $VOICE_NAME"
221
+ exit 3
222
+ fi
223
+
224
+ # @function convert_and_pad_audio
225
+ # @intent Convert AIFF to WAV and add silence padding for consistency
226
+ # @why Maintains consistent audio format across providers
227
+ if command -v ffmpeg &> /dev/null; then
228
+ # Add 200ms of silence at the beginning and convert to WAV
229
+ ffmpeg -f lavfi -i anullsrc=r=44100:cl=stereo:d=0.2 -i "$TEMP_FILE" \
230
+ -filter_complex "[0:a][1:a]concat=n=2:v=0:a=1[out]" \
231
+ -map "[out]" -y "$FINAL_FILE" 2>/dev/null
232
+
233
+ if [[ -f "$FINAL_FILE" ]]; then
234
+ rm -f "$TEMP_FILE"
235
+ TEMP_FILE="$FINAL_FILE"
236
+ fi
237
+ else
238
+ # No ffmpeg - use AIFF directly (rename for consistency)
239
+ FINAL_FILE=$(mktemp "$AUDIO_DIR/tts-padded-XXXXXX.aiff")
240
+ mv "$TEMP_FILE" "$FINAL_FILE"
241
+ TEMP_FILE="$FINAL_FILE"
242
+ fi
243
+
244
+ # @function play_audio
245
+ # @intent Play generated audio - via PulseAudio tunnel for SSH, afplay for local
246
+ # SECURITY: Use user-isolated lock directory (#129)
247
+ _LOCK_DIR="${XDG_RUNTIME_DIR:-/tmp/agentvibes-$(id -u)}"
248
+ mkdir -p "$_LOCK_DIR"
249
+ chmod 700 "$_LOCK_DIR"
250
+ LOCK_FILE="$_LOCK_DIR/agentvibes-audio.lock"
251
+
252
+ # Auto-remove stale lock files (older than 30 seconds)
253
+ if [ -f "$LOCK_FILE" ]; then
254
+ _lock_mtime=$(stat -f %m "$LOCK_FILE" 2>/dev/null || echo 0)
255
+ _lock_age=$(( $(date +%s) - _lock_mtime ))
256
+ if [[ $_lock_age -gt 30 ]]; then
257
+ rm -f "$LOCK_FILE"
258
+ fi
259
+ fi
260
+
261
+ # Wait for previous audio to finish (max 30 seconds)
262
+ for i in {1..60}; do
263
+ if [ ! -f "$LOCK_FILE" ]; then
264
+ break
265
+ fi
266
+ sleep 0.5
267
+ done
268
+
269
+ # Create lock and play audio
270
+ touch "$LOCK_FILE"
271
+
272
+ # Get audio duration for proper lock timing
273
+ if command -v ffprobe &> /dev/null; then
274
+ DURATION=$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$TEMP_FILE" 2>/dev/null)
275
+ DURATION=${DURATION%.*} # Round to integer
276
+ else
277
+ # Estimate duration based on text length (~150 WPM)
278
+ WORD_COUNT=$(echo "$TEXT" | wc -w)
279
+ DURATION=$(( (WORD_COUNT * 60 / 150) + 1 ))
280
+ fi
281
+ DURATION=${DURATION:-2} # Default to 2 seconds if detection fails
282
+
283
+ # Play audio in background (skip if in test mode or no-playback mode)
284
+ # AGENTVIBES_NO_PLAYBACK: Set to "true" to generate audio without playing (for post-processing)
285
+ if [[ "${AGENTVIBES_TEST_MODE:-false}" != "true" ]] && [[ "${AGENTVIBES_NO_PLAYBACK:-false}" != "true" ]]; then
286
+ # Check if we're in an SSH session with PulseAudio tunnel available
287
+ if [[ -n "$SSH_CONNECTION" ]] && [[ -n "$PULSE_SERVER" ]]; then
288
+ # Use paplay to send audio through PulseAudio tunnel to remote machine
289
+ if command -v /opt/homebrew/bin/paplay &> /dev/null; then
290
+ /opt/homebrew/bin/paplay "$TEMP_FILE" >/dev/null 2>&1 &
291
+ PLAYER_PID=$!
292
+ echo "๐Ÿ”Š Playing via PulseAudio tunnel"
293
+ else
294
+ echo "โš ๏ธ paplay not found - install pulseaudio for SSH audio"
295
+ afplay "$TEMP_FILE" >/dev/null 2>&1 &
296
+ PLAYER_PID=$!
297
+ fi
298
+ else
299
+ # Local session - use native macOS player
300
+ afplay "$TEMP_FILE" >/dev/null 2>&1 &
301
+ PLAYER_PID=$!
302
+ fi
303
+ fi
304
+
305
+ # Wait for audio to finish, then release lock
306
+ (sleep $DURATION; rm -f "$LOCK_FILE") &
307
+ disown
308
+
309
+ # Get audio cache stats
310
+ AUDIO_DIR_PATH=$(get_audio_dir)
311
+ FILE_COUNT=$(count_tts_files "$AUDIO_DIR_PATH")
312
+ SIZE_BYTES=$(calculate_tts_size_bytes "$AUDIO_DIR_PATH")
313
+ SIZE_HUMAN=$(bytes_to_human "$SIZE_BYTES")
314
+
315
+ # Color codes
316
+ BLUE='\033[0;34m'
317
+ YELLOW='\033[1;33m'
318
+ PURPLE='\033[0;35m'
319
+ LIGHT_PURPLE='\033[1;35m'
320
+ RED='\033[0;31m'
321
+ GREEN='\033[0;32m'
322
+ ORANGE='\033[0;33m'
323
+ WHITE='\033[1;37m'
324
+ MAGENTA='\033[0;35m'
325
+ CYAN='\033[0;36m'
326
+ GOLD='\033[38;5;226m'
327
+ NC='\033[0m'
328
+
329
+ # Dynamic color coding based on cache size
330
+ # Green: < 500MB (small)
331
+ # Yellow: 500MB - 3GB (lots)
332
+ # Red: > 3GB (extreme)
333
+ CACHE_COLOR=$GREEN
334
+ if [[ $SIZE_BYTES -gt 3221225472 ]]; then # > 3GB
335
+ CACHE_COLOR=$RED
336
+ elif [[ $SIZE_BYTES -gt 524288000 ]]; then # > 500MB
337
+ CACHE_COLOR=$YELLOW
338
+ fi
339
+
340
+ # Display with file count and auto-clean indicator
341
+ # Get auto-clean threshold for display
342
+ AUTO_CLEAN_THRESHOLD=$(get_auto_clean_threshold)
343
+ echo -e "${WHITE}๐Ÿ’พ Saved to:${NC} ${CYAN}$TEMP_FILE${NC} ${WHITE}๐Ÿ“ฆ${NC} ${YELLOW}$FILE_COUNT${NC} ${CACHE_COLOR}$SIZE_HUMAN${NC} ${WHITE}๐Ÿงน${NC}${GOLD}[${AUTO_CLEAN_THRESHOLD}mb]${NC}"
344
+
345
+ # Auto-cleanup check - delete oldest files if over size threshold
346
+ THRESHOLD_MB=$(get_auto_clean_threshold)
347
+ if [[ $SIZE_BYTES -gt $((THRESHOLD_MB * 1048576)) ]]; then
348
+ DELETED=$(auto_clean_old_files "$AUDIO_DIR_PATH" "$THRESHOLD_MB")
349
+ if [[ $DELETED -gt 0 ]]; then
350
+ echo -e "${ORANGE}๐Ÿงน Auto-cleaned $DELETED files${NC}"
351
+ fi
352
+ fi
353
+
354
+ echo -e "${CYAN}๐ŸŽค Voice used:${NC} ${WHITE}$VOICE_NAME (macOS Say)${NC}"
355
+
356
+ # Show personality if configured
357
+ PERSONALITY=$(cat "$PROJECT_ROOT/.claude/tts-personality.txt" 2>/dev/null || cat "$HOME/.claude/tts-personality.txt" 2>/dev/null || echo "")
358
+ if [[ -n "$PERSONALITY" ]] && [[ "$PERSONALITY" != "none" ]] && [[ "$PERSONALITY" != "normal" ]]; then
359
+ echo -e "${GOLD}๐Ÿ’ซ Personality:${NC} ${WHITE}$PERSONALITY${NC}"
360
+ fi
361
+
362
+ # Check audio folder size and warn if getting large
363
+ if [[ -d "$AUDIO_DIR_PATH" ]]; then
364
+ AUDIO_SIZE=$(du -sm "$AUDIO_DIR_PATH" 2>/dev/null | cut -f1)
365
+ if [[ -n "$AUDIO_SIZE" ]] && [[ "$AUDIO_SIZE" -gt 100 ]]; then
366
+ echo -e "\033[0;31mโš ๏ธ Audio cache is ${AUDIO_SIZE}MB - Run: /agent-vibes:cleanup\033[0m"
367
+ fi
368
+ fi