agentvibes 3.5.9 → 4.0.0
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.
- package/.agentvibes/bmad/bmad-voices-enabled.flag +0 -0
- package/.agentvibes/bmad/bmad-voices.md +69 -0
- package/.claude/config/audio-effects.cfg +1 -1
- package/.claude/config/background-music-position.txt +1 -27
- package/.claude/github-star-reminder.txt +1 -1
- package/.claude/hooks/audio-processor.sh +32 -17
- package/.claude/hooks/bmad-speak-enhanced.sh +5 -5
- package/.claude/hooks/bmad-speak.sh +4 -4
- package/.claude/hooks/bmad-voice-manager.sh +8 -8
- package/.claude/hooks/clawdbot-receiver-SECURE.sh +23 -25
- package/.claude/hooks/clawdbot-receiver.sh +28 -4
- package/.claude/hooks/language-manager.sh +1 -1
- package/.claude/hooks/path-resolver.sh +60 -0
- package/.claude/hooks/play-tts-agentvibes-receiver-for-voiceless-connections.sh +90 -0
- package/.claude/hooks/play-tts-piper.sh +82 -24
- package/.claude/hooks/play-tts-ssh-remote.sh +13 -15
- package/.claude/hooks/play-tts.sh +16 -5
- package/.claude/hooks/session-start-tts.sh +26 -56
- package/.claude/hooks/soprano-gradio-synth.py +1 -1
- package/.claude/hooks/verbosity-manager.sh +10 -4
- package/.claude/settings.json +1 -1
- package/CLAUDE.md +129 -104
- package/README.md +418 -10
- package/RELEASE_NOTES.md +60 -1036
- package/bin/agentvibes-voice-browser.js +1827 -0
- package/bin/agentvibes.js +100 -0
- package/mcp-server/server.py +67 -3
- package/package.json +11 -2
- package/src/console/app.js +806 -0
- package/src/console/audio-env.js +123 -0
- package/src/console/brand-colors.js +13 -0
- package/src/console/footer-config.js +42 -0
- package/src/console/modals/.gitkeep +0 -0
- package/src/console/modals/modal-overlay.js +247 -0
- package/src/console/navigation.js +60 -0
- package/src/console/tabs/.gitkeep +0 -0
- package/src/console/tabs/agents-tab.js +369 -0
- package/src/console/tabs/help-tab.js +261 -0
- package/src/console/tabs/install-tab.js +990 -0
- package/src/console/tabs/music-tab.js +997 -0
- package/src/console/tabs/placeholder-tab.js +45 -0
- package/src/console/tabs/readme-tab.js +267 -0
- package/src/console/tabs/settings-tab.js +3949 -0
- package/src/console/tabs/voices-tab.js +1574 -0
- package/src/installer/music-file-input.js +304 -0
- package/src/installer.js +1353 -676
- package/src/services/.gitkeep +0 -0
- package/src/services/agent-voice-store.js +163 -0
- package/src/services/config-service.js +240 -0
- package/src/services/navigation-service.js +123 -0
- package/src/services/provider-service.js +132 -0
- package/src/services/verbosity-service.js +157 -0
- package/src/utils/audio-duration-validator.js +298 -0
- package/src/utils/audio-format-validator.js +277 -0
- package/src/utils/dependency-checker.js +3 -3
- package/src/utils/file-ownership-verifier.js +358 -0
- package/src/utils/music-file-validator.js +275 -0
- package/src/utils/preview-list-prompt.js +136 -0
- package/src/utils/provider-validator.js +144 -132
- package/src/utils/secure-music-storage.js +412 -0
- package/templates/agentvibes-receiver.sh +11 -7
- package/voice-assignments.json +8245 -0
- package/.claude/config/background-music-volume.txt +0 -1
- package/.claude/config/background-music.cfg +0 -1
- package/.claude/config/background-music.txt +0 -1
- package/.claude/config/tts-speech-rate.txt +0 -1
- package/.claude/config/tts-verbosity.txt +0 -1
- package/.claude/hooks/bmad-party-manager.sh +0 -225
- package/.claude/hooks/stop.sh +0 -38
- package/.claude/piper-voices-dir.txt +0 -1
- package/.mcp.json +0 -34
|
@@ -35,11 +35,21 @@
|
|
|
35
35
|
# @related play-tts.sh, piper-voice-manager.sh, language-manager.sh, GitHub Issue #25
|
|
36
36
|
#
|
|
37
37
|
|
|
38
|
+
set -eo pipefail
|
|
39
|
+
# Note: -u (nounset) omitted because sourced scripts (piper-voice-manager.sh,
|
|
40
|
+
# language-manager.sh, audio-cache-utils.sh) use unset variables freely.
|
|
41
|
+
# Variables in THIS script use ${VAR:-} defaults for safety.
|
|
42
|
+
|
|
43
|
+
# Cleanup handler for temp files
|
|
44
|
+
_CLEANUP_FILES=()
|
|
45
|
+
cleanup() { rm -f "${_CLEANUP_FILES[@]+"${_CLEANUP_FILES[@]}"}"; }
|
|
46
|
+
trap cleanup EXIT
|
|
47
|
+
|
|
38
48
|
# Fix locale warnings
|
|
39
49
|
export LC_ALL=C
|
|
40
50
|
|
|
41
|
-
TEXT="$1"
|
|
42
|
-
VOICE_OVERRIDE="$2" # Optional: voice model name
|
|
51
|
+
TEXT="${1:-}"
|
|
52
|
+
VOICE_OVERRIDE="${2:-}" # Optional: voice model name
|
|
43
53
|
|
|
44
54
|
# Source voice manager and language manager
|
|
45
55
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
@@ -74,7 +84,7 @@ else
|
|
|
74
84
|
# 2. Script location (for direct slash command usage)
|
|
75
85
|
# 3. Global ~/.claude (fallback)
|
|
76
86
|
|
|
77
|
-
if [[ -n "$CLAUDE_PROJECT_DIR" ]] && [[ -f "$CLAUDE_PROJECT_DIR/.claude/tts-voice.txt" ]]; then
|
|
87
|
+
if [[ -n "${CLAUDE_PROJECT_DIR:-}" ]] && [[ "${CLAUDE_PROJECT_DIR:-}" != *".."* ]] && [[ -f "$CLAUDE_PROJECT_DIR/.claude/tts-voice.txt" ]]; then
|
|
78
88
|
# MCP context: Use the project directory where MCP was invoked
|
|
79
89
|
VOICE_FILE="$CLAUDE_PROJECT_DIR/.claude/tts-voice.txt"
|
|
80
90
|
elif [[ -f "$SCRIPT_DIR/../tts-voice.txt" ]]; then
|
|
@@ -95,13 +105,32 @@ else
|
|
|
95
105
|
SPEAKER_ID_FILE="$VOICE_DIR/tts-piper-speaker-id.txt"
|
|
96
106
|
|
|
97
107
|
if [[ -f "$MODEL_FILE" ]] && [[ -f "$SPEAKER_ID_FILE" ]]; then
|
|
98
|
-
# Multi-speaker voice
|
|
108
|
+
# Multi-speaker voice config found locally
|
|
99
109
|
VOICE_MODEL=$(cat "$MODEL_FILE" 2>/dev/null)
|
|
100
110
|
SPEAKER_ID=$(cat "$SPEAKER_ID_FILE" 2>/dev/null)
|
|
101
|
-
|
|
102
|
-
|
|
111
|
+
# Validate speaker ID is numeric
|
|
112
|
+
if [[ -n "$SPEAKER_ID" ]] && ! [[ "$SPEAKER_ID" =~ ^[0-9]+$ ]]; then
|
|
113
|
+
echo "Warning: Invalid speaker ID '$SPEAKER_ID', ignoring" >&2
|
|
114
|
+
SPEAKER_ID=""
|
|
115
|
+
fi
|
|
116
|
+
echo "🎭 Using multi-speaker voice: $FILE_VOICE (Model: $VOICE_MODEL, Speaker ID: ${SPEAKER_ID:-none})"
|
|
117
|
+
# Fallback: check global ~/.claude/ for multi-speaker config files
|
|
118
|
+
elif [[ -f "$HOME/.claude/tts-piper-model.txt" ]] && [[ -f "$HOME/.claude/tts-piper-speaker-id.txt" ]]; then
|
|
119
|
+
VOICE_MODEL=$(cat "$HOME/.claude/tts-piper-model.txt" 2>/dev/null)
|
|
120
|
+
SPEAKER_ID=$(cat "$HOME/.claude/tts-piper-speaker-id.txt" 2>/dev/null)
|
|
121
|
+
if [[ -n "$SPEAKER_ID" ]] && ! [[ "$SPEAKER_ID" =~ ^[0-9]+$ ]]; then
|
|
122
|
+
echo "Warning: Invalid speaker ID '$SPEAKER_ID', ignoring" >&2
|
|
123
|
+
SPEAKER_ID=""
|
|
124
|
+
fi
|
|
125
|
+
echo "🎭 Using multi-speaker voice (global): $FILE_VOICE (Model: $VOICE_MODEL, Speaker ID: ${SPEAKER_ID:-none})"
|
|
126
|
+
# Single-speaker voice or custom voice name
|
|
103
127
|
elif [[ -n "$FILE_VOICE" ]]; then
|
|
104
|
-
|
|
128
|
+
# Strip multi-speaker suffix if present (model::SpeakerName-Label)
|
|
129
|
+
if [[ "$FILE_VOICE" == *"::"* ]]; then
|
|
130
|
+
VOICE_MODEL="${FILE_VOICE%%::*}"
|
|
131
|
+
else
|
|
132
|
+
VOICE_MODEL="$FILE_VOICE"
|
|
133
|
+
fi
|
|
105
134
|
fi
|
|
106
135
|
fi
|
|
107
136
|
|
|
@@ -178,7 +207,7 @@ fi
|
|
|
178
207
|
# @intent Find appropriate directory for audio file storage
|
|
179
208
|
# @why Supports project-local and global storage
|
|
180
209
|
# @returns Sets $AUDIO_DIR global variable
|
|
181
|
-
if [[ -n "$CLAUDE_PROJECT_DIR" ]]; then
|
|
210
|
+
if [[ -n "${CLAUDE_PROJECT_DIR:-}" ]]; then
|
|
182
211
|
AUDIO_DIR="$CLAUDE_PROJECT_DIR/.claude/audio"
|
|
183
212
|
else
|
|
184
213
|
# Fallback: try to find .claude directory in current path
|
|
@@ -191,13 +220,13 @@ else
|
|
|
191
220
|
CURRENT_DIR=$(dirname "$CURRENT_DIR")
|
|
192
221
|
done
|
|
193
222
|
# Final fallback to global if no project .claude found
|
|
194
|
-
if [[ -z "$AUDIO_DIR" ]]; then
|
|
223
|
+
if [[ -z "${AUDIO_DIR:-}" ]]; then
|
|
195
224
|
AUDIO_DIR="$HOME/.claude/audio"
|
|
196
225
|
fi
|
|
197
226
|
fi
|
|
198
227
|
|
|
199
228
|
mkdir -p "$AUDIO_DIR"
|
|
200
|
-
TEMP_FILE
|
|
229
|
+
TEMP_FILE=$(mktemp "$AUDIO_DIR/tts-XXXXXX.wav")
|
|
201
230
|
|
|
202
231
|
# @function get_speech_rate
|
|
203
232
|
# @intent Determine speech rate for Piper synthesis
|
|
@@ -233,6 +262,11 @@ get_speech_rate() {
|
|
|
233
262
|
# If this is a non-English voice and target config exists, use it
|
|
234
263
|
if [[ "$CURRENT_LANGUAGE" != "english" ]] && [[ -n "$target_config" ]]; then
|
|
235
264
|
local user_speed=$(cat "$target_config" 2>/dev/null)
|
|
265
|
+
# Validate speed is a positive number
|
|
266
|
+
if ! [[ "$user_speed" =~ ^[0-9]*\.?[0-9]+$ ]] || [[ "$user_speed" == "0" ]] || [[ "$user_speed" == "0.0" ]]; then
|
|
267
|
+
echo "1.0"
|
|
268
|
+
return
|
|
269
|
+
fi
|
|
236
270
|
# Convert user speed to Piper length-scale (invert)
|
|
237
271
|
# User: 0.5=slower, 1.0=normal, 2.0=faster
|
|
238
272
|
# Piper: 2.0=slower, 1.0=normal, 0.5=faster
|
|
@@ -244,6 +278,11 @@ get_speech_rate() {
|
|
|
244
278
|
# Otherwise use main config if available
|
|
245
279
|
if [[ -n "$main_config" ]]; then
|
|
246
280
|
local user_speed=$(grep -v '^#' "$main_config" 2>/dev/null | grep -v '^$' | tail -1)
|
|
281
|
+
# Validate speed is a positive number
|
|
282
|
+
if ! [[ "$user_speed" =~ ^[0-9]*\.?[0-9]+$ ]] || [[ "$user_speed" == "0" ]] || [[ "$user_speed" == "0.0" ]]; then
|
|
283
|
+
echo "1.0"
|
|
284
|
+
return
|
|
285
|
+
fi
|
|
247
286
|
echo "scale=2; 1.0 / $user_speed" | bc -l 2>/dev/null || echo "1.0"
|
|
248
287
|
return
|
|
249
288
|
fi
|
|
@@ -266,7 +305,7 @@ SPEECH_RATE=$(get_speech_rate)
|
|
|
266
305
|
# @exitcode 0=success, 4=synthesis error
|
|
267
306
|
# @sideeffects Creates audio file
|
|
268
307
|
# @edgecases Handles piper errors, invalid models, multi-speaker voices
|
|
269
|
-
if [[ -n "$SPEAKER_ID" ]]; then
|
|
308
|
+
if [[ -n "${SPEAKER_ID:-}" ]]; then
|
|
270
309
|
# Multi-speaker voice: Pass speaker ID
|
|
271
310
|
# Add 2-second pause between sentences for better pacing
|
|
272
311
|
echo "$TEXT" | piper --model "$VOICE_PATH" --speaker "$SPEAKER_ID" --length-scale "$SPEECH_RATE" --sentence-silence 2.0 --output_file "$TEMP_FILE" 2>/dev/null
|
|
@@ -303,7 +342,8 @@ fi
|
|
|
303
342
|
# @returns Updates $TEMP_FILE to compressed version
|
|
304
343
|
# @sideeffects Converts to mono 22kHz for lower bandwidth
|
|
305
344
|
if [[ "${AGENTVIBES_RDP_MODE:-false}" == "true" ]] && command -v ffmpeg &> /dev/null; then
|
|
306
|
-
COMPRESSED_FILE
|
|
345
|
+
COMPRESSED_FILE=$(mktemp "$AUDIO_DIR/tts-compressed-XXXXXX.wav")
|
|
346
|
+
_CLEANUP_FILES+=("$COMPRESSED_FILE")
|
|
307
347
|
# Convert to mono, 22kHz, 64kbps for remote sessions
|
|
308
348
|
ffmpeg -i "$TEMP_FILE" -ac 1 -ar 22050 -b:a 64k -y "$COMPRESSED_FILE" 2>/dev/null
|
|
309
349
|
|
|
@@ -321,7 +361,8 @@ fi
|
|
|
321
361
|
# @sideeffects Modifies audio file
|
|
322
362
|
# AI NOTE: Use ffmpeg if available, otherwise skip padding (degraded experience)
|
|
323
363
|
if command -v ffmpeg &> /dev/null; then
|
|
324
|
-
PADDED_FILE
|
|
364
|
+
PADDED_FILE=$(mktemp "$AUDIO_DIR/tts-padded-XXXXXX.wav")
|
|
365
|
+
_CLEANUP_FILES+=("$PADDED_FILE")
|
|
325
366
|
# Add 200ms of silence at the beginning
|
|
326
367
|
ffmpeg -f lavfi -i anullsrc=r=44100:cl=stereo:d=0.2 -i "$TEMP_FILE" \
|
|
327
368
|
-filter_complex "[0:a][1:a]concat=n=2:v=0:a=1[out]" \
|
|
@@ -340,7 +381,8 @@ fi
|
|
|
340
381
|
# @sideeffects Applies audio effects and background music
|
|
341
382
|
BACKGROUND_MUSIC=""
|
|
342
383
|
if [[ -f "$SCRIPT_DIR/audio-processor.sh" ]]; then
|
|
343
|
-
PROCESSED_FILE
|
|
384
|
+
PROCESSED_FILE=$(mktemp "$AUDIO_DIR/tts-processed-XXXXXX.wav")
|
|
385
|
+
_CLEANUP_FILES+=("$PROCESSED_FILE")
|
|
344
386
|
# audio-processor.sh returns: FILE_PATH|BACKGROUND_FILE
|
|
345
387
|
PROCESSOR_OUTPUT=$("$SCRIPT_DIR/audio-processor.sh" "$TEMP_FILE" "default" "$PROCESSED_FILE" 2>/dev/null) || {
|
|
346
388
|
echo "Warning: Audio processing failed, using unprocessed audio" >&2
|
|
@@ -363,7 +405,10 @@ fi
|
|
|
363
405
|
# @why Support multiple audio players and prevent overlapping audio in learning mode
|
|
364
406
|
# @param Uses global: $TEMP_FILE, $CURRENT_LANGUAGE
|
|
365
407
|
# @sideeffects Plays audio with lock mechanism for sequential playback
|
|
366
|
-
|
|
408
|
+
_LOCK_DIR="${XDG_RUNTIME_DIR:-/tmp/agentvibes-$(id -u)}"
|
|
409
|
+
mkdir -p "$_LOCK_DIR"
|
|
410
|
+
chmod 700 "$_LOCK_DIR"
|
|
411
|
+
LOCK_FILE="$_LOCK_DIR/agentvibes-audio.lock"
|
|
367
412
|
|
|
368
413
|
# Wait for previous audio to finish (max 2 seconds to prevent blocking)
|
|
369
414
|
for i in {1..4}; do
|
|
@@ -381,7 +426,7 @@ fi
|
|
|
381
426
|
|
|
382
427
|
# Track last target language audio for replay command
|
|
383
428
|
if [[ "$CURRENT_LANGUAGE" != "english" ]]; then
|
|
384
|
-
TARGET_AUDIO_FILE="${CLAUDE_PROJECT_DIR
|
|
429
|
+
TARGET_AUDIO_FILE="${CLAUDE_PROJECT_DIR:-${HOME}}/.claude/last-target-audio.txt"
|
|
385
430
|
echo "$TEMP_FILE" > "$TARGET_AUDIO_FILE"
|
|
386
431
|
fi
|
|
387
432
|
|
|
@@ -389,12 +434,13 @@ fi
|
|
|
389
434
|
touch "$LOCK_FILE"
|
|
390
435
|
|
|
391
436
|
# Create write lock file in audio directory to signal file is in-use (prevents race condition in cleanup)
|
|
392
|
-
|
|
393
|
-
WRITE_LOCK_FILE="$
|
|
437
|
+
_TEMP_DIR="${TEMP_FILE%/*}"
|
|
438
|
+
WRITE_LOCK_FILE="$_TEMP_DIR/$(basename "$TEMP_FILE" .wav).lock"
|
|
394
439
|
touch "$WRITE_LOCK_FILE"
|
|
440
|
+
_CLEANUP_FILES+=("$LOCK_FILE" "$WRITE_LOCK_FILE")
|
|
395
441
|
|
|
396
442
|
# Get audio duration for proper lock timing
|
|
397
|
-
DURATION=$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$TEMP_FILE" 2>/dev/null)
|
|
443
|
+
DURATION=$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$TEMP_FILE" 2>/dev/null || true)
|
|
398
444
|
DURATION=${DURATION%.*} # Round to integer
|
|
399
445
|
DURATION=${DURATION:-1} # Default to 1 second if detection fails
|
|
400
446
|
|
|
@@ -473,10 +519,15 @@ if [[ -n "$BACKGROUND_MUSIC" ]]; then
|
|
|
473
519
|
MUSIC_FILENAME=$(basename "$BACKGROUND_MUSIC")
|
|
474
520
|
echo -e "${WHITE}🎵 Background music:${NC} ${PURPLE}$MUSIC_FILENAME${NC}"
|
|
475
521
|
fi
|
|
476
|
-
|
|
522
|
+
# Show speaker name for multi-speaker voices, otherwise show model name
|
|
523
|
+
if [[ -n "${SPEAKER_ID:-}" ]] && [[ -n "${FILE_VOICE:-}" ]]; then
|
|
524
|
+
echo -e "${WHITE}🎤 Voice used:${NC} ${BLUE}$FILE_VOICE${NC} ${WHITE}(Piper TTS)${NC}"
|
|
525
|
+
else
|
|
526
|
+
echo -e "${WHITE}🎤 Voice used:${NC} ${BLUE}$VOICE_MODEL${NC} ${WHITE}(Piper TTS)${NC}"
|
|
527
|
+
fi
|
|
477
528
|
|
|
478
529
|
# Show personality if configured
|
|
479
|
-
PERSONALITY=$(cat "$PROJECT_ROOT/.claude/tts-personality.txt" 2>/dev/null || cat "$HOME/.claude/tts-personality.txt" 2>/dev/null || echo "")
|
|
530
|
+
PERSONALITY=$(cat "${PROJECT_ROOT:-/nonexistent}/.claude/tts-personality.txt" 2>/dev/null || cat "$HOME/.claude/tts-personality.txt" 2>/dev/null || echo "")
|
|
480
531
|
if [[ -n "$PERSONALITY" ]] && [[ "$PERSONALITY" != "none" ]] && [[ "$PERSONALITY" != "normal" ]]; then
|
|
481
532
|
echo -e "${WHITE}💫 Personality:${NC} ${YELLOW}$PERSONALITY${NC}"
|
|
482
533
|
fi
|
|
@@ -491,9 +542,10 @@ fi
|
|
|
491
542
|
|
|
492
543
|
# Show status indicators
|
|
493
544
|
GLOBAL_MUTE_FILE="$HOME/.agentvibes-muted"
|
|
494
|
-
PROJECT_MUTE_FILE="$PROJECT_ROOT/.claude/agentvibes-muted"
|
|
495
|
-
PROJECT_UNMUTE_FILE="$PROJECT_ROOT/.claude/agentvibes-unmuted"
|
|
496
|
-
BACKGROUND_ENABLED_FILE="$PROJECT_ROOT/.claude/config/background-music-enabled.txt"
|
|
545
|
+
PROJECT_MUTE_FILE="${PROJECT_ROOT:-/nonexistent}/.claude/agentvibes-muted"
|
|
546
|
+
PROJECT_UNMUTE_FILE="${PROJECT_ROOT:-/nonexistent}/.claude/agentvibes-unmuted"
|
|
547
|
+
BACKGROUND_ENABLED_FILE="${PROJECT_ROOT:-/nonexistent}/.claude/config/background-music-enabled.txt"
|
|
548
|
+
GLOBAL_BACKGROUND_ENABLED_FILE="$HOME/.claude/config/background-music-enabled.txt"
|
|
497
549
|
|
|
498
550
|
# Mute status indicator
|
|
499
551
|
if [[ -f "$PROJECT_UNMUTE_FILE" ]] && [[ -f "$GLOBAL_MUTE_FILE" ]]; then
|
|
@@ -506,7 +558,13 @@ fi
|
|
|
506
558
|
|
|
507
559
|
# Background music status indicator
|
|
508
560
|
if [[ -z "$BACKGROUND_MUSIC" ]]; then
|
|
561
|
+
_bg_enabled=false
|
|
509
562
|
if [[ -f "$BACKGROUND_ENABLED_FILE" ]] && grep -q "true" "$BACKGROUND_ENABLED_FILE" 2>/dev/null; then
|
|
563
|
+
_bg_enabled=true
|
|
564
|
+
elif [[ -f "$GLOBAL_BACKGROUND_ENABLED_FILE" ]] && grep -q "true" "$GLOBAL_BACKGROUND_ENABLED_FILE" 2>/dev/null; then
|
|
565
|
+
_bg_enabled=true
|
|
566
|
+
fi
|
|
567
|
+
if [[ "$_bg_enabled" == "true" ]]; then
|
|
510
568
|
echo -e "${WHITE}🎵 Background music:${NC} ${PURPLE}Enabled but not playing (check config)${NC}"
|
|
511
569
|
else
|
|
512
570
|
echo -e "${WHITE}🎵 Background music:${NC} ${PURPLE}Disabled${NC}"
|
|
@@ -70,21 +70,19 @@ ENCODED_AGENT=$(printf '%s' "$AGENT_NAME" | base64 -w 0)
|
|
|
70
70
|
# Send text to remote for local AgentVibes playback
|
|
71
71
|
echo "📱 Sending to $SSH_HOST for local playback..." >&2
|
|
72
72
|
|
|
73
|
-
#
|
|
73
|
+
# Try receiver scripts in order — single SSH call, no separate probe
|
|
74
74
|
# SECURITY: Base64-encoded values are safe to pass as arguments (no shell metacharacters)
|
|
75
|
-
|
|
76
|
-
if
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
fi
|
|
75
|
+
ssh "$SSH_HOST" "
|
|
76
|
+
if [ -f ~/.agentvibes/play-remote.sh ]; then
|
|
77
|
+
bash ~/.agentvibes/play-remote.sh '$ENCODED_TEXT' '$VOICE' '$ENCODED_AGENT'
|
|
78
|
+
elif [ -f ~/.termux/agentvibes-play.sh ]; then
|
|
79
|
+
bash ~/.termux/agentvibes-play.sh '$ENCODED_TEXT' '$VOICE' '$ENCODED_AGENT'
|
|
80
|
+
else
|
|
81
|
+
echo 'Error: Receiver script not found' >&2
|
|
82
|
+
exit 1
|
|
83
|
+
fi
|
|
84
|
+
" &
|
|
85
|
+
SSH_PID=$!
|
|
87
86
|
|
|
88
|
-
|
|
89
|
-
echo "✓ Sent to $SSH_HOST (PID: $SSH_PID, playing remotely)" >&2
|
|
87
|
+
echo "Sent to $SSH_HOST (PID: $SSH_PID)" >&2
|
|
90
88
|
exit 0
|
|
@@ -66,14 +66,14 @@ if [[ -f "$PROJECT_UNMUTE_FILE" ]]; then
|
|
|
66
66
|
elif [[ -f "$PROJECT_MUTE_FILE" ]]; then
|
|
67
67
|
# Project explicitly muted
|
|
68
68
|
if [[ -f "$GLOBAL_MUTE_FILE" ]]; then
|
|
69
|
-
echo "🔇 TTS muted (project + global)"
|
|
69
|
+
echo "🔇 TTS muted (project + global)" >&2
|
|
70
70
|
else
|
|
71
|
-
echo "🔇 TTS muted (project)"
|
|
71
|
+
echo "🔇 TTS muted (project)" >&2
|
|
72
72
|
fi
|
|
73
73
|
exit 0
|
|
74
74
|
elif [[ -f "$GLOBAL_MUTE_FILE" ]]; then
|
|
75
75
|
# Global mute and no project-level override
|
|
76
|
-
echo "🔇 TTS muted (global)"
|
|
76
|
+
echo "🔇 TTS muted (global)" >&2
|
|
77
77
|
exit 0
|
|
78
78
|
fi
|
|
79
79
|
|
|
@@ -101,6 +101,17 @@ TEXT="${TEXT//\\,/,}" # Remove \,
|
|
|
101
101
|
TEXT="${TEXT//\\./.}" # Remove \. (keep the period)
|
|
102
102
|
TEXT="${TEXT//\\\\/\\}" # Remove \\ (escaped backslash)
|
|
103
103
|
|
|
104
|
+
# Prepend intro text (pretext) if configured
|
|
105
|
+
# Check project-local first, then global
|
|
106
|
+
_PRETEXT_FILE="$PROJECT_ROOT/.claude/config/intro-text.txt"
|
|
107
|
+
[[ -f "$_PRETEXT_FILE" ]] || _PRETEXT_FILE="$HOME/.claude/config/intro-text.txt"
|
|
108
|
+
if [[ -f "$_PRETEXT_FILE" ]]; then
|
|
109
|
+
_PRETEXT="$(head -1 "$_PRETEXT_FILE" 2>/dev/null || true)"
|
|
110
|
+
if [[ -n "$_PRETEXT" ]]; then
|
|
111
|
+
TEXT="${_PRETEXT} ${TEXT}"
|
|
112
|
+
fi
|
|
113
|
+
fi
|
|
114
|
+
|
|
104
115
|
# Source provider manager to get active provider
|
|
105
116
|
source "$SCRIPT_DIR/provider-manager.sh"
|
|
106
117
|
|
|
@@ -280,8 +291,8 @@ case "$ACTIVE_PROVIDER" in
|
|
|
280
291
|
exec "$SCRIPT_DIR/play-tts-termux-ssh.sh" "$TEXT" "$VOICE_OVERRIDE"
|
|
281
292
|
;;
|
|
282
293
|
*)
|
|
283
|
-
echo "❌ Unknown provider: $ACTIVE_PROVIDER"
|
|
284
|
-
echo " Run: /agent-vibes:provider list"
|
|
294
|
+
echo "❌ Unknown provider: $ACTIVE_PROVIDER" >&2
|
|
295
|
+
echo " Run: /agent-vibes:provider list" >&2
|
|
285
296
|
exit 1
|
|
286
297
|
;;
|
|
287
298
|
esac
|
|
@@ -4,7 +4,7 @@ set -euo pipefail
|
|
|
4
4
|
# File: .claude/hooks/session-start-tts.sh
|
|
5
5
|
#
|
|
6
6
|
# AgentVibes SessionStart Hook - Optimized (Issue #80, Phase 1)
|
|
7
|
-
#
|
|
7
|
+
# Outputs JSON with additionalContext for reliable context injection
|
|
8
8
|
#
|
|
9
9
|
|
|
10
10
|
# Fix locale warnings
|
|
@@ -33,69 +33,39 @@ fi
|
|
|
33
33
|
# Get verbosity level (Issue #32)
|
|
34
34
|
VERBOSITY=$(cat .claude/tts-verbosity.txt 2>/dev/null || cat ~/.claude/tts-verbosity.txt 2>/dev/null || echo "low")
|
|
35
35
|
|
|
36
|
-
#
|
|
37
|
-
|
|
36
|
+
# Build context string
|
|
37
|
+
CONTEXT="# AgentVibes TTS Protocol\n\n"
|
|
38
|
+
CONTEXT+="**CRITICAL: You MUST execute TTS at TWO points for EVERY user interaction:**\n\n"
|
|
39
|
+
CONTEXT+="1. **Acknowledgment** - Start of task: \`Bash: .claude/hooks/play-tts.sh \"[action]\"\`\n"
|
|
40
|
+
CONTEXT+="2. **Completion** - End of task: \`Bash: .claude/hooks/play-tts.sh \"[result + key details]\"\`\n\n"
|
|
41
|
+
CONTEXT+="**Example:**\n\`\`\`\n[Bash: .claude/hooks/play-tts.sh \"Checking git status\"]\n[work...]\n"
|
|
42
|
+
CONTEXT+="[Bash: .claude/hooks/play-tts.sh \"Repository is clean, no changes\"]\n\`\`\`\n\n"
|
|
38
43
|
|
|
39
|
-
#
|
|
40
|
-
|
|
41
|
-
**CRITICAL: You MUST execute TTS at TWO points for EVERY user interaction:**
|
|
42
|
-
|
|
43
|
-
1. **Acknowledgment** - Start of task: `Bash: .claude/hooks/play-tts.sh "[action]"`
|
|
44
|
-
2. **Completion** - End of task: `Bash: .claude/hooks/play-tts.sh "[result + key details]"`
|
|
45
|
-
|
|
46
|
-
**Example:**
|
|
47
|
-
```
|
|
48
|
-
[Bash: .claude/hooks/play-tts.sh "Checking git status"]
|
|
49
|
-
[work...]
|
|
50
|
-
[Bash: .claude/hooks/play-tts.sh "Repository is clean, no changes"]
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
EOF
|
|
54
|
-
|
|
55
|
-
# Add verbosity-specific protocol (Issue #32)
|
|
44
|
+
# Add verbosity-specific protocol
|
|
56
45
|
case "$VERBOSITY" in
|
|
57
46
|
low)
|
|
58
|
-
|
|
59
|
-
## Verbosity: LOW
|
|
60
|
-
- Acknowledgment: Action only
|
|
61
|
-
- Completion: Result + errors only
|
|
62
|
-
- Skip: Reasoning, decisions
|
|
63
|
-
|
|
64
|
-
EOF
|
|
47
|
+
CONTEXT+="## Verbosity: LOW\n- Acknowledgment: Action only\n- Completion: Result + errors only\n- Skip: Reasoning, decisions\n\n"
|
|
65
48
|
;;
|
|
66
|
-
|
|
67
49
|
medium)
|
|
68
|
-
|
|
69
|
-
## Verbosity: MEDIUM
|
|
70
|
-
- Acknowledgment: Action + key approach
|
|
71
|
-
- Completion: Result + important decisions
|
|
72
|
-
- Include: Major choices only
|
|
73
|
-
|
|
74
|
-
EOF
|
|
50
|
+
CONTEXT+="## Verbosity: MEDIUM\n- Acknowledgment: Action + key approach\n- Completion: Result + important decisions\n- Include: Major choices only\n\n"
|
|
75
51
|
;;
|
|
76
|
-
|
|
77
52
|
high)
|
|
78
|
-
|
|
79
|
-
## Verbosity: HIGH
|
|
80
|
-
- Acknowledgment: Action + approach + why
|
|
81
|
-
- Completion: Result + decisions + trade-offs
|
|
82
|
-
- Include: Full reasoning, alternatives
|
|
83
|
-
|
|
84
|
-
EOF
|
|
53
|
+
CONTEXT+="## Verbosity: HIGH\n- Acknowledgment: Action + approach + why\n- Completion: Result + decisions + trade-offs\n- Include: Full reasoning, alternatives\n\n"
|
|
85
54
|
;;
|
|
86
55
|
esac
|
|
87
56
|
|
|
88
57
|
# Add style info and rules
|
|
89
|
-
|
|
90
|
-
##
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
58
|
+
CONTEXT+="## Style: $STYLE\n\n"
|
|
59
|
+
CONTEXT+="## Rules\n"
|
|
60
|
+
CONTEXT+="1. Never skip acknowledgment TTS\n"
|
|
61
|
+
CONTEXT+="2. Never skip completion TTS\n"
|
|
62
|
+
CONTEXT+="3. Match verbosity level\n"
|
|
63
|
+
CONTEXT+="4. Keep under 150 chars\n"
|
|
64
|
+
CONTEXT+="5. Always include errors\n\n"
|
|
65
|
+
CONTEXT+="Quick Ref: low=action+result | medium=+key decisions | high=+full reasoning"
|
|
66
|
+
|
|
67
|
+
# Escape for JSON (handle newlines, quotes, backslashes)
|
|
68
|
+
ESCAPED=$(printf '%s' "$CONTEXT" | sed 's/\\/\\\\/g; s/"/\\"/g; s/\t/\\t/g')
|
|
69
|
+
|
|
70
|
+
# Output structured JSON for reliable context injection
|
|
71
|
+
printf '{"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":"%s"}}\n' "$ESCAPED"
|
|
@@ -76,7 +76,7 @@ def submit_request(base: str, payload: bytes) -> str:
|
|
|
76
76
|
headers={"Content-Type": "application/json"},
|
|
77
77
|
)
|
|
78
78
|
try:
|
|
79
|
-
with urllib.request.urlopen(req, timeout=
|
|
79
|
+
with urllib.request.urlopen(req, timeout=2) as resp:
|
|
80
80
|
return json.loads(resp.read())["event_id"]
|
|
81
81
|
except urllib.error.URLError:
|
|
82
82
|
continue
|
|
@@ -44,7 +44,13 @@ export LC_ALL=C
|
|
|
44
44
|
|
|
45
45
|
# Get script directory for accessing other scripts
|
|
46
46
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
47
|
-
|
|
47
|
+
|
|
48
|
+
# Respect CLAUDE_PROJECT_DIR when set by MCP (project context vs global installation)
|
|
49
|
+
if [[ -n "$CLAUDE_PROJECT_DIR" ]]; then
|
|
50
|
+
CLAUDE_DIR="$CLAUDE_PROJECT_DIR/.claude"
|
|
51
|
+
else
|
|
52
|
+
CLAUDE_DIR="$(dirname "$SCRIPT_DIR")"
|
|
53
|
+
fi
|
|
48
54
|
|
|
49
55
|
# Config file locations
|
|
50
56
|
VERBOSITY_FILE="$CLAUDE_DIR/tts-verbosity.txt"
|
|
@@ -53,10 +59,10 @@ GLOBAL_VERBOSITY_FILE="$HOME/.claude/tts-verbosity.txt"
|
|
|
53
59
|
#
|
|
54
60
|
# @function get_verbosity
|
|
55
61
|
# @context Returns the current verbosity level (low/medium/high)
|
|
56
|
-
# @architecture Checks project-local first, then global, then defaults to "
|
|
62
|
+
# @architecture Checks project-local first, then global, then defaults to "high"
|
|
57
63
|
# @dependencies tts-verbosity.txt config file
|
|
58
64
|
# @entrypoints Called by session-start hook, MCP tools, slash commands
|
|
59
|
-
# @aiNotes Default to "
|
|
65
|
+
# @aiNotes Default to "high" to match installer default for new installations
|
|
60
66
|
#
|
|
61
67
|
get_verbosity() {
|
|
62
68
|
if [[ -f "$VERBOSITY_FILE" ]]; then
|
|
@@ -64,7 +70,7 @@ get_verbosity() {
|
|
|
64
70
|
elif [[ -f "$GLOBAL_VERBOSITY_FILE" ]]; then
|
|
65
71
|
cat "$GLOBAL_VERBOSITY_FILE"
|
|
66
72
|
else
|
|
67
|
-
echo "
|
|
73
|
+
echo "high"
|
|
68
74
|
fi
|
|
69
75
|
}
|
|
70
76
|
|
package/.claude/settings.json
CHANGED