agentvibes 5.7.5 → 5.7.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,7 +4,13 @@
4
4
  # Location: User installs to ~/.termux/agentvibes-play.sh or ~/.agentvibes/play-remote.sh
5
5
  #
6
6
  # AgentVibes SSH-TTS Receiver
7
- # Receives text from remote server via SSH, plays with local AgentVibes
7
+ # Receives base64-encoded JSON payload from remote server via SSH,
8
+ # decodes it, applies voice/music/effects, and plays with local AgentVibes.
9
+ #
10
+ # Payload format: base64({"text","voice","effects","music","volume","pretext","speed","provider","llm"})
11
+ #
12
+ # SSH ForceCommand: SSH_ORIGINAL_COMMAND carries the base64 payload.
13
+ # Direct invocation: pass the payload as first argument.
8
14
  #
9
15
  # Installation:
10
16
  # curl -sSL https://raw.githubusercontent.com/paulpreibisch/AgentVibes/main/scripts/install-ssh-receiver.sh | bash
@@ -22,47 +28,141 @@ if [[ "${1:-}" == "--" ]]; then
22
28
  shift
23
29
  fi
24
30
 
25
- TEXT="${1:-}"
26
- VOICE="${2:-en_US-ryan-high}"
27
- MUSIC="${3:-}"
28
- REVERB="${4:-}"
31
+ # ---------------------------------------------------------------------------
32
+ # Resolve encoded payload: arg → SSH_ORIGINAL_COMMAND → stdin (legacy)
33
+ # ---------------------------------------------------------------------------
29
34
 
30
- if [[ -z "$TEXT" ]]; then
31
- echo "❌ No text provided" >&2
32
- echo "Usage: $0 [--] <text> [voice] [music] [reverb]" >&2
35
+ ENCODED_PAYLOAD="${1:-}"
36
+ if [[ -z "$ENCODED_PAYLOAD" ]]; then
37
+ ENCODED_PAYLOAD="${SSH_ORIGINAL_COMMAND:-}"
38
+ fi
39
+ if [[ -z "$ENCODED_PAYLOAD" ]]; then
40
+ ENCODED_PAYLOAD=$(cat 2>/dev/null || true)
41
+ fi
42
+
43
+ if [[ -z "$ENCODED_PAYLOAD" ]]; then
44
+ echo "❌ No payload provided" >&2
45
+ echo "Usage: $0 <base64-json-payload>" >&2
33
46
  exit 1
34
47
  fi
35
48
 
36
- # SECURITY: If text is base64-encoded (from secure sender), decode it
37
- # Base64 text won't contain spaces or special chars, so we detect it heuristically
38
- if [[ "$TEXT" =~ ^[A-Za-z0-9+/]+=*$ ]] && [[ ${#TEXT} -gt 20 ]]; then
39
- DECODED=$(printf '%s' "$TEXT" | base64 -d 2>/dev/null) || DECODED=""
40
- if [[ -n "$DECODED" ]]; then
41
- TEXT="$DECODED"
42
- fi
49
+ # Reject oversized payloads (DoS guard: 64 KB is generous for any TTS request)
50
+ _MAX_PAYLOAD_BYTES=65536
51
+ if [[ ${#ENCODED_PAYLOAD} -gt $_MAX_PAYLOAD_BYTES ]]; then
52
+ echo " Payload too large (max ${_MAX_PAYLOAD_BYTES} bytes)" >&2
53
+ exit 1
43
54
  fi
44
55
 
45
- # SECURITY: Validate voice format (alphanumeric, hyphens, underscores only)
46
- if [[ ! "$VOICE" =~ ^[a-zA-Z0-9_-]+$ ]]; then
47
- echo "❌ Invalid voice format: $VOICE" >&2
56
+ # Validate it looks like base64
57
+ if [[ ! "$ENCODED_PAYLOAD" =~ ^[A-Za-z0-9+/=]+$ ]]; then
58
+ echo "❌ Payload must be base64-encoded" >&2
48
59
  exit 1
49
60
  fi
50
61
 
62
+ # Decode
63
+ DECODED=$(printf '%s' "$ENCODED_PAYLOAD" | base64 -d 2>/dev/null) || {
64
+ echo "❌ Failed to decode base64 payload" >&2; exit 1
65
+ }
66
+
67
+ # ---------------------------------------------------------------------------
68
+ # Parse JSON payload — python3 is required for JSON parsing
69
+ # ---------------------------------------------------------------------------
70
+
71
+ if ! command -v python3 &>/dev/null; then
72
+ echo "❌ python3 required for JSON parsing" >&2; exit 1
73
+ fi
74
+
75
+ # Parse all fields in one python call to avoid repeated process spawning
76
+ # and to log a clear error if the payload is malformed JSON.
77
+ _PARSE_OUT=$(python3 - "$DECODED" 2>&1 <<'PYEOF'
78
+ import json, sys
79
+ try:
80
+ d = json.loads(sys.argv[1])
81
+ for field in ("text","voice","music","volume","effects","pretext","speed","provider","llm"):
82
+ print(d.get(field, ""))
83
+ except json.JSONDecodeError as e:
84
+ print(f"JSON_PARSE_ERROR: {e}", file=sys.stderr)
85
+ sys.exit(1)
86
+ PYEOF
87
+ ) || { echo "❌ Invalid JSON in payload: $_PARSE_OUT" >&2; exit 1; }
88
+
89
+ # Assign fields from ordered output lines
90
+ TEXT=$( sed -n '1p' <<< "$_PARSE_OUT")
91
+ VOICE=$( sed -n '2p' <<< "$_PARSE_OUT")
92
+ MUSIC=$( sed -n '3p' <<< "$_PARSE_OUT")
93
+ VOLUME=$( sed -n '4p' <<< "$_PARSE_OUT")
94
+ EFFECTS=$( sed -n '5p' <<< "$_PARSE_OUT")
95
+ PRETEXT=$( sed -n '6p' <<< "$_PARSE_OUT")
96
+ SPEED=$( sed -n '7p' <<< "$_PARSE_OUT")
97
+ PROVIDER=$(sed -n '8p' <<< "$_PARSE_OUT")
98
+ LLM=$( sed -n '9p' <<< "$_PARSE_OUT")
99
+
100
+ # Fall back to defaults
101
+ [[ -z "$VOICE" ]] && VOICE="en_US-lessac-medium"
102
+ [[ -z "$VOLUME" ]] && VOLUME="0.10"
103
+ [[ -z "$PROVIDER" ]] && PROVIDER="piper"
104
+
105
+ # Validate text
106
+ if [[ -z "$TEXT" ]]; then
107
+ echo "❌ No text in payload" >&2; exit 1
108
+ fi
109
+
110
+ # SECURITY: Validate voice — letters, digits, underscore, hyphen, period, colon
111
+ if [[ ! "$VOICE" =~ ^[a-zA-Z0-9_.:/-]+$ ]]; then
112
+ echo "❌ Invalid voice format: $VOICE" >&2; exit 1
113
+ fi
114
+
115
+ # Validate volume is numeric
116
+ if [[ ! "$VOLUME" =~ ^[0-9]+\.?[0-9]*$ ]]; then
117
+ VOLUME="0.10"
118
+ fi
119
+
120
+ # Validate provider
121
+ case "$PROVIDER" in
122
+ piper|soprano|macos|windows-sapi) ;;
123
+ *) PROVIDER="piper" ;;
124
+ esac
125
+
126
+ # Prepend pretext if present (same logic as PS receiver)
127
+ if [[ -n "$PRETEXT" ]]; then
128
+ TEXT="${PRETEXT}. ${TEXT}"
129
+ fi
130
+
131
+ # ---------------------------------------------------------------------------
132
+ # Test mode: dump what would be played without calling play-tts
133
+ # ---------------------------------------------------------------------------
134
+
135
+ if [[ "${AGENTVIBES_TEST_MODE:-false}" == "true" ]]; then
136
+ python3 -c "
137
+ import json, sys
138
+ print(json.dumps({
139
+ 'text': sys.argv[1],
140
+ 'voice': sys.argv[2],
141
+ 'music': sys.argv[3],
142
+ 'volume': sys.argv[4],
143
+ 'effects': sys.argv[5],
144
+ 'pretext': sys.argv[6],
145
+ 'provider': sys.argv[7],
146
+ 'llm': sys.argv[8],
147
+ }, ensure_ascii=False))
148
+ " "$TEXT" "$VOICE" "$MUSIC" "$VOLUME" "$EFFECTS" "$PRETEXT" "$PROVIDER" "$LLM"
149
+ exit 0
150
+ fi
151
+
152
+ # ---------------------------------------------------------------------------
153
+ # Find AgentVibes installation
154
+ # ---------------------------------------------------------------------------
155
+
51
156
  # Suppress GitHub star reminders (receiver mode)
52
157
  export AGENTVIBES_NO_REMINDERS=1
53
158
 
54
- # Find AgentVibes installation
55
- # SECURITY: Uses controlled paths only, validates existence
56
159
  find_agentvibes() {
57
- # Try command lookup first
58
160
  if command -v agentvibes >/dev/null 2>&1; then
59
161
  local bin_path
60
162
  bin_path=$(which agentvibes)
61
- # Resolve if it's a symlink
62
163
  if [[ -L "$bin_path" ]]; then
63
164
  bin_path=$(readlink -f "$bin_path" 2>/dev/null || realpath "$bin_path" 2>/dev/null || echo "$bin_path")
64
165
  fi
65
- # SECURITY: Properly quote nested command substitutions
66
166
  local lib_path
67
167
  lib_path="$(dirname "$(dirname "$bin_path")")/lib/node_modules/agentvibes"
68
168
  if [[ -d "$lib_path" ]]; then
@@ -71,17 +171,14 @@ find_agentvibes() {
71
171
  fi
72
172
  fi
73
173
 
74
- # Check common npm global locations (controlled paths only)
75
174
  local search_paths=(
76
175
  "$HOME/.npm-global/lib/node_modules/agentvibes"
77
176
  "/usr/local/lib/node_modules/agentvibes"
78
- "/data/data/com.termux/files/usr/lib/node_modules/agentvibes" # Android Termux
177
+ "/data/data/com.termux/files/usr/lib/node_modules/agentvibes"
79
178
  )
80
179
 
81
- # Handle nvm paths separately to avoid glob issues
82
180
  if [[ -d "$HOME/.nvm/versions/node" ]]; then
83
181
  local nvm_path
84
- # SECURITY: Use find instead of unsafe glob expansion
85
182
  nvm_path=$(find "$HOME/.nvm/versions/node" -maxdepth 3 -type d -name "agentvibes" -path "*/lib/node_modules/*" 2>/dev/null | head -1)
86
183
  if [[ -n "$nvm_path" ]] && [[ -d "$nvm_path" ]]; then
87
184
  echo "$nvm_path"
@@ -89,11 +186,8 @@ find_agentvibes() {
89
186
  fi
90
187
  fi
91
188
 
92
- for path in "${search_paths[@]}"; do
93
- if [[ -d "$path" ]]; then
94
- echo "$path"
95
- return 0
96
- fi
189
+ for p in "${search_paths[@]}"; do
190
+ [[ -d "$p" ]] && { echo "$p"; return 0; }
97
191
  done
98
192
 
99
193
  return 1
@@ -110,48 +204,27 @@ fi
110
204
  PLAY_TTS="$AGENTVIBES_ROOT/.claude/hooks/play-tts.sh"
111
205
 
112
206
  if [[ ! -f "$PLAY_TTS" ]]; then
113
- echo "❌ play-tts.sh not found at $PLAY_TTS" >&2
114
- exit 1
207
+ echo "❌ play-tts.sh not found at $PLAY_TTS" >&2; exit 1
115
208
  fi
116
209
 
117
- # Configure audio effects and background music if provided
118
- # CRITICAL: Write to AGENTVIBES package config, not HOME config
119
- # The audio-processor reads from package config, not ~/.claude/config
120
- if [[ -n "$MUSIC" ]] || [[ -n "$REVERB" ]]; then
121
- CONFIG_DIR="$AGENTVIBES_ROOT/.claude/config"
122
- mkdir -p "$CONFIG_DIR"
123
-
124
- # Enable background music
125
- echo "true" > "$CONFIG_DIR/background-music-enabled.txt"
126
-
127
- # Clear position cache for fresh start
128
- rm -f "$CONFIG_DIR/background-music-position.txt"
129
-
130
- # Set background music track if provided
131
- if [[ -n "$MUSIC" ]]; then
132
- echo "$MUSIC" > "$CONFIG_DIR/background-music.txt"
133
- fi
134
-
135
- # Set audio effects config (default agent name is always "default")
136
- if [[ -n "$MUSIC" ]] && [[ -n "$REVERB" ]]; then
137
- echo "default|${REVERB}|${MUSIC}|0.10" > "$CONFIG_DIR/audio-effects.cfg"
138
- elif [[ -n "$REVERB" ]]; then
139
- echo "default|${REVERB}||0.0" > "$CONFIG_DIR/audio-effects.cfg"
140
- elif [[ -n "$MUSIC" ]]; then
141
- echo "default||${MUSIC}|0.10" > "$CONFIG_DIR/audio-effects.cfg"
142
- fi
143
- fi
210
+ # ---------------------------------------------------------------------------
211
+ # Apply music / effects overrides via env vars for play-tts.sh
212
+ # ---------------------------------------------------------------------------
213
+
214
+ [[ -n "$MUSIC" ]] && export AGENTVIBES_OVERRIDE_MUSIC="$MUSIC"
215
+ [[ -n "$VOLUME" ]] && export AGENTVIBES_OVERRIDE_VOLUME="$VOLUME"
216
+ [[ -n "$EFFECTS" ]] && export AGENTVIBES_OVERRIDE_EFFECTS="$EFFECTS"
217
+ [[ -n "$SPEED" ]] && export AGENTVIBES_OVERRIDE_SPEED="$SPEED"
144
218
 
145
- # Log for debugging (optional, comment out in production)
146
219
  if [[ "${AGENTVIBES_DEBUG:-0}" == "1" ]]; then
147
- echo "[DEBUG] AgentVibes root: $AGENTVIBES_ROOT" >&2
148
220
  echo "[DEBUG] Voice: $VOICE" >&2
149
221
  echo "[DEBUG] Music: ${MUSIC:-none}" >&2
150
- echo "[DEBUG] Reverb: ${REVERB:-none}" >&2
151
- echo "[DEBUG] Text length: ${#TEXT}" >&2
222
+ echo "[DEBUG] Volume: ${VOLUME:-none}" >&2
223
+ echo "[DEBUG] Effects: ${EFFECTS:-none}" >&2
224
+ echo "[DEBUG] Pretext: ${PRETEXT:-none}" >&2
225
+ echo "[DEBUG] Provider: $PROVIDER" >&2
152
226
  fi
153
227
 
154
- # Generate and play with full AgentVibes features
155
228
  echo "🎵 Playing via AgentVibes: ${TEXT:0:50}..." >&2
156
229
  bash "$PLAY_TTS" "$TEXT" "$VOICE"
157
230