agentvibes 5.1.4 → 5.2.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 (69) hide show
  1. package/.agentvibes/config.json +23 -13
  2. package/.claude/commands/agent-vibes/verbosity.md +98 -89
  3. package/.claude/config/audio-effects.cfg +4 -1
  4. package/.claude/hooks/audio-cache-utils.sh +246 -246
  5. package/.claude/hooks/background-music-manager.sh +404 -404
  6. package/.claude/hooks/bmad-speak-enhanced.sh +165 -165
  7. package/.claude/hooks/bmad-speak.sh +290 -290
  8. package/.claude/hooks/bmad-tts-injector.sh +568 -568
  9. package/.claude/hooks/bmad-voice-manager.sh +928 -928
  10. package/.claude/hooks/clawdbot-receiver-SECURE.sh +129 -129
  11. package/.claude/hooks/clawdbot-receiver.sh +107 -107
  12. package/.claude/hooks/clean-audio-cache.sh +22 -22
  13. package/.claude/hooks/cleanup-cache.sh +106 -106
  14. package/.claude/hooks/configure-rdp-mode.sh +137 -137
  15. package/.claude/hooks/download-extra-voices.sh +244 -244
  16. package/.claude/hooks/effects-manager.sh +268 -268
  17. package/.claude/hooks/github-star-reminder.sh +154 -154
  18. package/.claude/hooks/language-manager.sh +362 -362
  19. package/.claude/hooks/learn-manager.sh +492 -492
  20. package/.claude/hooks/macos-voice-manager.sh +205 -205
  21. package/.claude/hooks/migrate-background-music.sh +125 -125
  22. package/.claude/hooks/migrate-to-agentvibes.sh +161 -161
  23. package/.claude/hooks/optimize-background-music.sh +87 -87
  24. package/.claude/hooks/path-resolver.sh +60 -60
  25. package/.claude/hooks/personality-manager.sh +448 -448
  26. package/.claude/hooks/piper-download-voices.sh +233 -225
  27. package/.claude/hooks/piper-installer.sh +292 -292
  28. package/.claude/hooks/piper-multispeaker-registry.sh +171 -171
  29. package/.claude/hooks/piper-voice-manager.sh +125 -0
  30. package/.claude/hooks/play-tts-agentvibes-receiver-for-voiceless-connections.sh +97 -90
  31. package/.claude/hooks/play-tts-enhanced.sh +105 -105
  32. package/.claude/hooks/play-tts-piper.sh +16 -5
  33. package/.claude/hooks/play-tts-ssh-remote.sh +168 -167
  34. package/.claude/hooks/play-tts-termux-ssh.sh +169 -169
  35. package/.claude/hooks/play-tts.sh +35 -14
  36. package/.claude/hooks/prepare-release.sh +54 -54
  37. package/.claude/hooks/provider-commands.sh +617 -617
  38. package/.claude/hooks/provider-manager.sh +399 -399
  39. package/.claude/hooks/replay-target-audio.sh +95 -95
  40. package/.claude/hooks/sentiment-manager.sh +201 -201
  41. package/.claude/hooks/session-start-tts.sh +4 -1
  42. package/.claude/hooks/speed-manager.sh +291 -291
  43. package/.claude/hooks/stop-tts.sh +84 -84
  44. package/.claude/hooks/termux-installer.sh +261 -261
  45. package/.claude/hooks/translate-manager.sh +341 -341
  46. package/.claude/hooks/tts-queue-worker.sh +145 -145
  47. package/.claude/hooks/tts-queue.sh +165 -165
  48. package/.claude/hooks/verbosity-manager.sh +185 -178
  49. package/.claude/hooks/voice-manager.sh +552 -548
  50. package/.claude/hooks-windows/download-extra-voices.ps1 +243 -185
  51. package/.claude/hooks-windows/play-tts-piper.ps1 +7 -2
  52. package/.claude/hooks-windows/play-tts.ps1 +9 -3
  53. package/.claude/hooks-windows/session-start-tts.ps1 +2 -1
  54. package/.claude/hooks-windows/verbosity-manager.ps1 +126 -119
  55. package/README.md +19 -2
  56. package/RELEASE_NOTES.md +74 -0
  57. package/bin/agentvibes-voice-browser.js +1939 -1840
  58. package/bin/mcp-server.sh +206 -206
  59. package/mcp-server/server.py +87 -15
  60. package/package.json +1 -1
  61. package/src/console/tabs/receiver-tab.js +1527 -1483
  62. package/src/console/tabs/settings-tab.js +2 -2
  63. package/src/console/tabs/setup-tab.js +112 -31
  64. package/src/console/tabs/voices-tab.js +130 -13
  65. package/src/i18n/en.js +202 -202
  66. package/src/installer.js +79 -213
  67. package/src/services/llm-provider-service.js +126 -75
  68. package/src/services/verbosity-service.js +159 -157
  69. package/templates/agentvibes-receiver.sh +3 -2
@@ -1,145 +1,145 @@
1
- #!/usr/bin/env bash
2
- #
3
- # File: .claude/hooks/tts-queue-worker.sh
4
- #
5
- # TTS Queue Worker - Background process that plays queued TTS sequentially
6
- # Automatically exits when queue is empty for 5 seconds
7
-
8
- set -euo pipefail
9
-
10
- # Security: Use secure temp directory with restrictive permissions
11
- # Must match the logic in tts-queue.sh exactly
12
- if [[ -n "${XDG_RUNTIME_DIR:-}" ]] && [[ -d "$XDG_RUNTIME_DIR" ]]; then
13
- QUEUE_DIR="$XDG_RUNTIME_DIR/agentvibes-tts-queue"
14
- else
15
- # Fallback to user-specific temp directory
16
- QUEUE_DIR="/tmp/agentvibes-tts-queue-$(id -u)"
17
- fi
18
-
19
- # Security: Validate queue directory exists and has correct ownership
20
- if [[ ! -d "$QUEUE_DIR" ]]; then
21
- echo "Error: Queue directory does not exist: $QUEUE_DIR" >&2
22
- exit 1
23
- fi
24
-
25
- # Security: Verify we own the queue directory (prevent symlink attacks)
26
- if [[ "$(stat -c '%u' "$QUEUE_DIR" 2>/dev/null || stat -f '%u' "$QUEUE_DIR" 2>/dev/null)" != "$(id -u)" ]]; then
27
- echo "Error: Queue directory not owned by current user" >&2
28
- exit 1
29
- fi
30
-
31
- WORKER_PID_FILE="$QUEUE_DIR/worker.pid"
32
- IDLE_TIMEOUT=5 # Exit after 5 seconds of no new requests
33
-
34
- # Get script directory
35
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
36
-
37
- # Configurable delay between speakers (seconds)
38
- # Can be overridden by .claude/tts-speaker-delay.txt or ~/.claude/tts-speaker-delay.txt
39
- SPEAKER_DELAY=4 # Default: 4 seconds between speakers
40
-
41
- # Check for custom delay configuration
42
- if [[ -f ".claude/tts-speaker-delay.txt" ]]; then
43
- CUSTOM_DELAY=$(cat .claude/tts-speaker-delay.txt 2>/dev/null | tr -d '[:space:]')
44
- if [[ "$CUSTOM_DELAY" =~ ^[0-9]+$ ]]; then
45
- SPEAKER_DELAY=$CUSTOM_DELAY
46
- fi
47
- elif [[ -f "$HOME/.claude/tts-speaker-delay.txt" ]]; then
48
- CUSTOM_DELAY=$(cat "$HOME/.claude/tts-speaker-delay.txt" 2>/dev/null | tr -d '[:space:]')
49
- if [[ "$CUSTOM_DELAY" =~ ^[0-9]+$ ]]; then
50
- SPEAKER_DELAY=$CUSTOM_DELAY
51
- fi
52
- fi
53
-
54
- # Trap to clean up on exit
55
- trap 'rm -f "$WORKER_PID_FILE"' EXIT
56
-
57
- # Process queue items
58
- process_queue() {
59
- local idle_count=0
60
-
61
- while true; do
62
- # Find oldest queue item
63
- local queue_item=$(ls -1 "$QUEUE_DIR"/*.queue 2>/dev/null | sort | head -1)
64
-
65
- if [[ -z "$queue_item" ]]; then
66
- # Queue is empty, increment idle counter
67
- idle_count=$((idle_count + 1))
68
-
69
- if [[ $idle_count -ge $IDLE_TIMEOUT ]]; then
70
- # No new items for timeout period, exit worker
71
- exit 0
72
- fi
73
-
74
- # Wait for a new queue item — use inotifywait if available to avoid polling
75
- # Use a 1-second timeout (-t 1) so the idle counter still advances correctly
76
- if command -v inotifywait &>/dev/null; then
77
- inotifywait -q -e create -t 1 "$QUEUE_DIR" 2>/dev/null || true
78
- else
79
- sleep 1
80
- fi
81
- continue
82
- fi
83
-
84
- # Reset idle counter - we have work
85
- idle_count=0
86
-
87
- # Load queue item — explicit key=value parsing (SECURITY: never source untrusted files)
88
- TEXT_FILE=""
89
- VOICE=""
90
- AGENT=""
91
- PROFILE_PATH=""
92
- PLAY_WAV=""
93
- while IFS='=' read -r _key _val; do
94
- case "$_key" in
95
- TEXT_FILE) TEXT_FILE="$_val" ;;
96
- VOICE) VOICE="$_val" ;;
97
- AGENT) AGENT="$_val" ;;
98
- PROFILE_PATH) PROFILE_PATH="$_val" ;;
99
- PLAY_WAV) PLAY_WAV="$_val" ;;
100
- esac
101
- done < "$queue_item"
102
-
103
- # Check if this is a pre-generated WAV playback item
104
- if [[ -n "${PLAY_WAV:-}" ]] && [[ -f "$PLAY_WAV" ]]; then
105
- # Play the pre-generated WAV directly (synthesis already done by bmad-speak)
106
- if command -v paplay &>/dev/null; then
107
- paplay "$PLAY_WAV" 2>/dev/null || true
108
- elif command -v aplay &>/dev/null; then
109
- aplay -q "$PLAY_WAV" 2>/dev/null || true
110
- elif command -v ffplay &>/dev/null; then
111
- ffplay -nodisp -autoexit -loglevel quiet "$PLAY_WAV" 2>/dev/null || true
112
- fi
113
- else
114
- # Full TTS request — read text from companion file, use voice/agent directly
115
- TEXT=""
116
- if [[ -n "${TEXT_FILE:-}" ]] && [[ -f "$TEXT_FILE" ]]; then
117
- TEXT=$(cat "$TEXT_FILE")
118
- rm -f "$TEXT_FILE"
119
- fi
120
- AGENT_PROFILE="${PROFILE_PATH:-}"
121
-
122
- export AGENTVIBES_AGENT_PROFILE="$AGENT_PROFILE"
123
-
124
- if [[ -n "${VOICE:-}" ]]; then
125
- bash "$SCRIPT_DIR/play-tts.sh" "$TEXT" "${VOICE}" || true
126
- else
127
- bash "$SCRIPT_DIR/play-tts.sh" "$TEXT" || true
128
- fi
129
-
130
- if [[ -n "$AGENT_PROFILE" ]] && [[ -f "$AGENT_PROFILE" ]]; then
131
- rm -f "$AGENT_PROFILE"
132
- fi
133
- unset AGENTVIBES_AGENT_PROFILE
134
- fi
135
-
136
- # Add configurable pause between speakers for natural conversation flow
137
- sleep $SPEAKER_DELAY
138
-
139
- # Remove processed item and any companion text file
140
- rm -f "$queue_item" "${queue_item%.queue}.txt"
141
- done
142
- }
143
-
144
- # Start processing
145
- process_queue
1
+ #!/usr/bin/env bash
2
+ #
3
+ # File: .claude/hooks/tts-queue-worker.sh
4
+ #
5
+ # TTS Queue Worker - Background process that plays queued TTS sequentially
6
+ # Automatically exits when queue is empty for 5 seconds
7
+
8
+ set -euo pipefail
9
+
10
+ # Security: Use secure temp directory with restrictive permissions
11
+ # Must match the logic in tts-queue.sh exactly
12
+ if [[ -n "${XDG_RUNTIME_DIR:-}" ]] && [[ -d "$XDG_RUNTIME_DIR" ]]; then
13
+ QUEUE_DIR="$XDG_RUNTIME_DIR/agentvibes-tts-queue"
14
+ else
15
+ # Fallback to user-specific temp directory
16
+ QUEUE_DIR="/tmp/agentvibes-tts-queue-$(id -u)"
17
+ fi
18
+
19
+ # Security: Validate queue directory exists and has correct ownership
20
+ if [[ ! -d "$QUEUE_DIR" ]]; then
21
+ echo "Error: Queue directory does not exist: $QUEUE_DIR" >&2
22
+ exit 1
23
+ fi
24
+
25
+ # Security: Verify we own the queue directory (prevent symlink attacks)
26
+ if [[ "$(stat -c '%u' "$QUEUE_DIR" 2>/dev/null || stat -f '%u' "$QUEUE_DIR" 2>/dev/null)" != "$(id -u)" ]]; then
27
+ echo "Error: Queue directory not owned by current user" >&2
28
+ exit 1
29
+ fi
30
+
31
+ WORKER_PID_FILE="$QUEUE_DIR/worker.pid"
32
+ IDLE_TIMEOUT=5 # Exit after 5 seconds of no new requests
33
+
34
+ # Get script directory
35
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
36
+
37
+ # Configurable delay between speakers (seconds)
38
+ # Can be overridden by .claude/tts-speaker-delay.txt or ~/.claude/tts-speaker-delay.txt
39
+ SPEAKER_DELAY=4 # Default: 4 seconds between speakers
40
+
41
+ # Check for custom delay configuration
42
+ if [[ -f ".claude/tts-speaker-delay.txt" ]]; then
43
+ CUSTOM_DELAY=$(cat .claude/tts-speaker-delay.txt 2>/dev/null | tr -d '[:space:]')
44
+ if [[ "$CUSTOM_DELAY" =~ ^[0-9]+$ ]]; then
45
+ SPEAKER_DELAY=$CUSTOM_DELAY
46
+ fi
47
+ elif [[ -f "$HOME/.claude/tts-speaker-delay.txt" ]]; then
48
+ CUSTOM_DELAY=$(cat "$HOME/.claude/tts-speaker-delay.txt" 2>/dev/null | tr -d '[:space:]')
49
+ if [[ "$CUSTOM_DELAY" =~ ^[0-9]+$ ]]; then
50
+ SPEAKER_DELAY=$CUSTOM_DELAY
51
+ fi
52
+ fi
53
+
54
+ # Trap to clean up on exit
55
+ trap 'rm -f "$WORKER_PID_FILE"' EXIT
56
+
57
+ # Process queue items
58
+ process_queue() {
59
+ local idle_count=0
60
+
61
+ while true; do
62
+ # Find oldest queue item
63
+ local queue_item=$(ls -1 "$QUEUE_DIR"/*.queue 2>/dev/null | sort | head -1)
64
+
65
+ if [[ -z "$queue_item" ]]; then
66
+ # Queue is empty, increment idle counter
67
+ idle_count=$((idle_count + 1))
68
+
69
+ if [[ $idle_count -ge $IDLE_TIMEOUT ]]; then
70
+ # No new items for timeout period, exit worker
71
+ exit 0
72
+ fi
73
+
74
+ # Wait for a new queue item — use inotifywait if available to avoid polling
75
+ # Use a 1-second timeout (-t 1) so the idle counter still advances correctly
76
+ if command -v inotifywait &>/dev/null; then
77
+ inotifywait -q -e create -t 1 "$QUEUE_DIR" 2>/dev/null || true
78
+ else
79
+ sleep 1
80
+ fi
81
+ continue
82
+ fi
83
+
84
+ # Reset idle counter - we have work
85
+ idle_count=0
86
+
87
+ # Load queue item — explicit key=value parsing (SECURITY: never source untrusted files)
88
+ TEXT_FILE=""
89
+ VOICE=""
90
+ AGENT=""
91
+ PROFILE_PATH=""
92
+ PLAY_WAV=""
93
+ while IFS='=' read -r _key _val; do
94
+ case "$_key" in
95
+ TEXT_FILE) TEXT_FILE="$_val" ;;
96
+ VOICE) VOICE="$_val" ;;
97
+ AGENT) AGENT="$_val" ;;
98
+ PROFILE_PATH) PROFILE_PATH="$_val" ;;
99
+ PLAY_WAV) PLAY_WAV="$_val" ;;
100
+ esac
101
+ done < "$queue_item"
102
+
103
+ # Check if this is a pre-generated WAV playback item
104
+ if [[ -n "${PLAY_WAV:-}" ]] && [[ -f "$PLAY_WAV" ]]; then
105
+ # Play the pre-generated WAV directly (synthesis already done by bmad-speak)
106
+ if command -v paplay &>/dev/null; then
107
+ paplay "$PLAY_WAV" 2>/dev/null || true
108
+ elif command -v aplay &>/dev/null; then
109
+ aplay -q "$PLAY_WAV" 2>/dev/null || true
110
+ elif command -v ffplay &>/dev/null; then
111
+ ffplay -nodisp -autoexit -loglevel quiet "$PLAY_WAV" 2>/dev/null || true
112
+ fi
113
+ else
114
+ # Full TTS request — read text from companion file, use voice/agent directly
115
+ TEXT=""
116
+ if [[ -n "${TEXT_FILE:-}" ]] && [[ -f "$TEXT_FILE" ]]; then
117
+ TEXT=$(cat "$TEXT_FILE")
118
+ rm -f "$TEXT_FILE"
119
+ fi
120
+ AGENT_PROFILE="${PROFILE_PATH:-}"
121
+
122
+ export AGENTVIBES_AGENT_PROFILE="$AGENT_PROFILE"
123
+
124
+ if [[ -n "${VOICE:-}" ]]; then
125
+ bash "$SCRIPT_DIR/play-tts.sh" "$TEXT" "${VOICE}" || true
126
+ else
127
+ bash "$SCRIPT_DIR/play-tts.sh" "$TEXT" || true
128
+ fi
129
+
130
+ if [[ -n "$AGENT_PROFILE" ]] && [[ -f "$AGENT_PROFILE" ]]; then
131
+ rm -f "$AGENT_PROFILE"
132
+ fi
133
+ unset AGENTVIBES_AGENT_PROFILE
134
+ fi
135
+
136
+ # Add configurable pause between speakers for natural conversation flow
137
+ sleep $SPEAKER_DELAY
138
+
139
+ # Remove processed item and any companion text file
140
+ rm -f "$queue_item" "${queue_item%.queue}.txt"
141
+ done
142
+ }
143
+
144
+ # Start processing
145
+ process_queue
@@ -1,165 +1,165 @@
1
- #!/usr/bin/env bash
2
- #
3
- # File: .claude/hooks/tts-queue.sh
4
- #
5
- # TTS Queue Manager for Party Mode
6
- # Queues TTS requests and plays them sequentially in the background
7
- # This allows Claude to continue generating responses while audio plays in order
8
-
9
- set -euo pipefail
10
-
11
- # Security: Use secure temp directory with restrictive permissions
12
- # Check if XDG_RUNTIME_DIR is available (more secure than /tmp)
13
- if [[ -n "${XDG_RUNTIME_DIR:-}" ]] && [[ -d "$XDG_RUNTIME_DIR" ]]; then
14
- QUEUE_DIR="$XDG_RUNTIME_DIR/agentvibes-tts-queue"
15
- else
16
- # Fallback to user-specific temp directory
17
- QUEUE_DIR="/tmp/agentvibes-tts-queue-$(id -u)"
18
- fi
19
-
20
- QUEUE_LOCK="$QUEUE_DIR/queue.lock"
21
- WORKER_PID_FILE="$QUEUE_DIR/worker.pid"
22
-
23
- # Get script directory
24
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
25
-
26
- # Initialize queue directory with restrictive permissions
27
- if [[ ! -d "$QUEUE_DIR" ]]; then
28
- mkdir -p "$QUEUE_DIR"
29
- chmod 700 "$QUEUE_DIR" # Only owner can read/write/execute
30
- fi
31
-
32
- # @function add_to_queue
33
- # @intent Add a TTS request to the queue for sequential playback
34
- # @param $1 dialogue text
35
- # @param $2 voice name (optional)
36
- # @param $3 agent name (optional, for background music in party mode)
37
- # @param $4 agent profile path (optional, PID-scoped temp JSON with reverb/personality/music overrides)
38
- add_to_queue() {
39
- local text="$1"
40
- local voice="${2:-}"
41
- local agent="${3:-default}"
42
- local profile_path="${4:-}"
43
-
44
- # Create unique queue item with timestamp
45
- local timestamp=$(date +%s%N)
46
- local queue_file="$QUEUE_DIR/$timestamp.queue"
47
-
48
- # Write request to queue file using direct storage
49
- # Text is stored in a separate .txt file (handles newlines and special chars safely)
50
- # Voice and agent are simple identifiers with no special chars
51
- printf '%s' "$text" > "${queue_file%.queue}.txt"
52
- cat > "$queue_file" <<EOF
53
- TEXT_FILE=${queue_file%.queue}.txt
54
- VOICE=$voice
55
- AGENT=$agent
56
- PROFILE_PATH=$profile_path
57
- EOF
58
-
59
- # Start queue worker if not already running
60
- start_worker_if_needed
61
- }
62
-
63
- # @function start_worker_if_needed
64
- # @intent Start the queue worker process if it's not already running
65
- start_worker_if_needed() {
66
- # Security: Use file locking to prevent race condition
67
- # Open file descriptor 200 for locking
68
- exec 200>"$QUEUE_LOCK"
69
-
70
- # Acquire exclusive lock (flock -x) with timeout
71
- if ! flock -x -w 5 200; then
72
- echo "Warning: Could not acquire queue lock" >&2
73
- return 1
74
- fi
75
-
76
- # Check if worker is already running (within lock)
77
- if [[ -f "$WORKER_PID_FILE" ]]; then
78
- local pid=$(cat "$WORKER_PID_FILE")
79
- if kill -0 "$pid" 2>/dev/null; then
80
- # Worker is running, release lock and return
81
- flock -u 200
82
- exec 200>&-
83
- return 0
84
- fi
85
- fi
86
-
87
- # Start worker in background
88
- "$SCRIPT_DIR/tts-queue-worker.sh" &
89
- local worker_pid=$!
90
- echo $worker_pid > "$WORKER_PID_FILE"
91
-
92
- # Release lock
93
- flock -u 200
94
- exec 200>&-
95
- }
96
-
97
- # @function clear_queue
98
- # @intent Clear all pending TTS requests (emergency stop)
99
- clear_queue() {
100
- rm -f "$QUEUE_DIR"/*.queue
101
- echo "✅ Queue cleared"
102
- }
103
-
104
- # @function show_queue
105
- # @intent Display current queue status
106
- show_queue() {
107
- local count=$(ls -1 "$QUEUE_DIR"/*.queue 2>/dev/null | wc -l)
108
- echo "📊 Queue status: $count items pending"
109
-
110
- if [[ -f "$WORKER_PID_FILE" ]]; then
111
- local pid=$(cat "$WORKER_PID_FILE")
112
- if kill -0 "$pid" 2>/dev/null; then
113
- echo "✅ Worker process running (PID: $pid)"
114
- else
115
- echo "❌ Worker process not running"
116
- fi
117
- else
118
- echo "❌ Worker process not running"
119
- fi
120
- }
121
-
122
- # @function play_wav
123
- # @intent Queue a pre-generated WAV file for sequential playback
124
- # @param $1 path to WAV file
125
- play_wav() {
126
- local wav_file="$1"
127
- [[ -z "$wav_file" ]] && return 1
128
- [[ ! -f "$wav_file" ]] && return 1
129
-
130
- local timestamp=$(date +%s%N)
131
- local queue_file="$QUEUE_DIR/$timestamp.queue"
132
-
133
- # Write a playback-only queue item (no synthesis needed)
134
- cat > "$queue_file" <<EOF
135
- PLAY_WAV=$wav_file
136
- EOF
137
-
138
- start_worker_if_needed
139
- }
140
-
141
- # Main command dispatcher
142
- case "${1:-help}" in
143
- add)
144
- add_to_queue "${2:-}" "${3:-}" "${4:-default}" "${5:-}"
145
- ;;
146
- play)
147
- play_wav "${2:-}"
148
- ;;
149
- clear)
150
- clear_queue
151
- ;;
152
- status)
153
- show_queue
154
- ;;
155
- *)
156
- echo "Usage: tts-queue.sh {add|play|clear|status}"
157
- echo ""
158
- echo "Commands:"
159
- echo " add <text> [voice] [agent] Add TTS request to queue"
160
- echo " play <wav_file> Queue a pre-generated WAV for playback"
161
- echo " clear Clear all pending requests"
162
- echo " status Show queue status"
163
- exit 1
164
- ;;
165
- esac
1
+ #!/usr/bin/env bash
2
+ #
3
+ # File: .claude/hooks/tts-queue.sh
4
+ #
5
+ # TTS Queue Manager for Party Mode
6
+ # Queues TTS requests and plays them sequentially in the background
7
+ # This allows Claude to continue generating responses while audio plays in order
8
+
9
+ set -euo pipefail
10
+
11
+ # Security: Use secure temp directory with restrictive permissions
12
+ # Check if XDG_RUNTIME_DIR is available (more secure than /tmp)
13
+ if [[ -n "${XDG_RUNTIME_DIR:-}" ]] && [[ -d "$XDG_RUNTIME_DIR" ]]; then
14
+ QUEUE_DIR="$XDG_RUNTIME_DIR/agentvibes-tts-queue"
15
+ else
16
+ # Fallback to user-specific temp directory
17
+ QUEUE_DIR="/tmp/agentvibes-tts-queue-$(id -u)"
18
+ fi
19
+
20
+ QUEUE_LOCK="$QUEUE_DIR/queue.lock"
21
+ WORKER_PID_FILE="$QUEUE_DIR/worker.pid"
22
+
23
+ # Get script directory
24
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
25
+
26
+ # Initialize queue directory with restrictive permissions
27
+ if [[ ! -d "$QUEUE_DIR" ]]; then
28
+ mkdir -p "$QUEUE_DIR"
29
+ chmod 700 "$QUEUE_DIR" # Only owner can read/write/execute
30
+ fi
31
+
32
+ # @function add_to_queue
33
+ # @intent Add a TTS request to the queue for sequential playback
34
+ # @param $1 dialogue text
35
+ # @param $2 voice name (optional)
36
+ # @param $3 agent name (optional, for background music in party mode)
37
+ # @param $4 agent profile path (optional, PID-scoped temp JSON with reverb/personality/music overrides)
38
+ add_to_queue() {
39
+ local text="$1"
40
+ local voice="${2:-}"
41
+ local agent="${3:-default}"
42
+ local profile_path="${4:-}"
43
+
44
+ # Create unique queue item with timestamp
45
+ local timestamp=$(date +%s%N)
46
+ local queue_file="$QUEUE_DIR/$timestamp.queue"
47
+
48
+ # Write request to queue file using direct storage
49
+ # Text is stored in a separate .txt file (handles newlines and special chars safely)
50
+ # Voice and agent are simple identifiers with no special chars
51
+ printf '%s' "$text" > "${queue_file%.queue}.txt"
52
+ cat > "$queue_file" <<EOF
53
+ TEXT_FILE=${queue_file%.queue}.txt
54
+ VOICE=$voice
55
+ AGENT=$agent
56
+ PROFILE_PATH=$profile_path
57
+ EOF
58
+
59
+ # Start queue worker if not already running
60
+ start_worker_if_needed
61
+ }
62
+
63
+ # @function start_worker_if_needed
64
+ # @intent Start the queue worker process if it's not already running
65
+ start_worker_if_needed() {
66
+ # Security: Use file locking to prevent race condition
67
+ # Open file descriptor 200 for locking
68
+ exec 200>"$QUEUE_LOCK"
69
+
70
+ # Acquire exclusive lock (flock -x) with timeout
71
+ if ! flock -x -w 5 200; then
72
+ echo "Warning: Could not acquire queue lock" >&2
73
+ return 1
74
+ fi
75
+
76
+ # Check if worker is already running (within lock)
77
+ if [[ -f "$WORKER_PID_FILE" ]]; then
78
+ local pid=$(cat "$WORKER_PID_FILE")
79
+ if kill -0 "$pid" 2>/dev/null; then
80
+ # Worker is running, release lock and return
81
+ flock -u 200
82
+ exec 200>&-
83
+ return 0
84
+ fi
85
+ fi
86
+
87
+ # Start worker in background
88
+ "$SCRIPT_DIR/tts-queue-worker.sh" &
89
+ local worker_pid=$!
90
+ echo $worker_pid > "$WORKER_PID_FILE"
91
+
92
+ # Release lock
93
+ flock -u 200
94
+ exec 200>&-
95
+ }
96
+
97
+ # @function clear_queue
98
+ # @intent Clear all pending TTS requests (emergency stop)
99
+ clear_queue() {
100
+ rm -f "$QUEUE_DIR"/*.queue
101
+ echo "✅ Queue cleared"
102
+ }
103
+
104
+ # @function show_queue
105
+ # @intent Display current queue status
106
+ show_queue() {
107
+ local count=$(ls -1 "$QUEUE_DIR"/*.queue 2>/dev/null | wc -l)
108
+ echo "📊 Queue status: $count items pending"
109
+
110
+ if [[ -f "$WORKER_PID_FILE" ]]; then
111
+ local pid=$(cat "$WORKER_PID_FILE")
112
+ if kill -0 "$pid" 2>/dev/null; then
113
+ echo "✅ Worker process running (PID: $pid)"
114
+ else
115
+ echo "❌ Worker process not running"
116
+ fi
117
+ else
118
+ echo "❌ Worker process not running"
119
+ fi
120
+ }
121
+
122
+ # @function play_wav
123
+ # @intent Queue a pre-generated WAV file for sequential playback
124
+ # @param $1 path to WAV file
125
+ play_wav() {
126
+ local wav_file="$1"
127
+ [[ -z "$wav_file" ]] && return 1
128
+ [[ ! -f "$wav_file" ]] && return 1
129
+
130
+ local timestamp=$(date +%s%N)
131
+ local queue_file="$QUEUE_DIR/$timestamp.queue"
132
+
133
+ # Write a playback-only queue item (no synthesis needed)
134
+ cat > "$queue_file" <<EOF
135
+ PLAY_WAV=$wav_file
136
+ EOF
137
+
138
+ start_worker_if_needed
139
+ }
140
+
141
+ # Main command dispatcher
142
+ case "${1:-help}" in
143
+ add)
144
+ add_to_queue "${2:-}" "${3:-}" "${4:-default}" "${5:-}"
145
+ ;;
146
+ play)
147
+ play_wav "${2:-}"
148
+ ;;
149
+ clear)
150
+ clear_queue
151
+ ;;
152
+ status)
153
+ show_queue
154
+ ;;
155
+ *)
156
+ echo "Usage: tts-queue.sh {add|play|clear|status}"
157
+ echo ""
158
+ echo "Commands:"
159
+ echo " add <text> [voice] [agent] Add TTS request to queue"
160
+ echo " play <wav_file> Queue a pre-generated WAV for playback"
161
+ echo " clear Clear all pending requests"
162
+ echo " status Show queue status"
163
+ exit 1
164
+ ;;
165
+ esac