agentvibes 5.1.3 → 5.2.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/config.json +23 -13
- package/.claude/commands/agent-vibes/verbosity.md +98 -89
- package/.claude/config/audio-effects.cfg +6 -1
- package/.claude/hooks/bmad-speak.sh +2 -2
- package/.claude/hooks/piper-download-voices.sh +233 -225
- package/.claude/hooks/piper-installer.sh +1 -1
- package/.claude/hooks/piper-voice-manager.sh +125 -0
- package/.claude/hooks/play-tts-agentvibes-receiver-for-voiceless-connections.sh +97 -90
- package/.claude/hooks/play-tts-enhanced.sh +1 -1
- package/.claude/hooks/play-tts-piper.sh +16 -5
- package/.claude/hooks/play-tts-ssh-remote.sh +168 -167
- package/.claude/hooks/play-tts.sh +31 -9
- package/.claude/hooks/session-start-tts.sh +4 -1
- package/.claude/hooks/stop-tts.sh +1 -1
- package/.claude/hooks/verbosity-manager.sh +185 -178
- package/.claude/hooks-windows/download-extra-voices.ps1 +243 -185
- package/.claude/hooks-windows/play-tts-piper.ps1 +7 -2
- package/.claude/hooks-windows/play-tts.ps1 +219 -65
- package/.claude/hooks-windows/session-start-tts.ps1 +2 -1
- package/.claude/hooks-windows/verbosity-manager.ps1 +126 -119
- package/README.md +24 -1
- package/RELEASE_NOTES.md +113 -0
- package/bin/agentvibes-voice-browser.js +1939 -1840
- package/mcp-server/server.py +75 -25
- package/package.json +1 -1
- package/src/console/tabs/receiver-tab.js +1527 -1483
- package/src/console/tabs/settings-tab.js +2 -2
- package/src/console/tabs/setup-tab.js +122 -20
- package/src/console/tabs/voices-tab.js +130 -13
- package/src/i18n/en.js +202 -202
- package/src/installer.js +29 -25
- package/src/services/llm-provider-service.js +114 -11
- package/src/services/verbosity-service.js +159 -157
- package/templates/agentvibes-receiver.sh +3 -2
|
@@ -1,90 +1,97 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
#
|
|
3
|
-
# File: .claude/hooks/play-tts-agentvibes-receiver-for-voiceless-connections.sh
|
|
4
|
-
#
|
|
5
|
-
# AgentVibes - AgentVibes Receiver Provider (for voiceless connections)
|
|
6
|
-
# Sends text to a remote device via SSH for local AgentVibes playback.
|
|
7
|
-
# Use this when the AI agent runs on a server/headless machine that has no
|
|
8
|
-
# audio output — the remote device (laptop, phone, etc.) plays the audio.
|
|
9
|
-
#
|
|
10
|
-
# Copyright (c) 2025 Paul Preibisch
|
|
11
|
-
# Licensed under the Apache License, Version 2.0
|
|
12
|
-
#
|
|
13
|
-
|
|
14
|
-
set -euo pipefail
|
|
15
|
-
|
|
16
|
-
TEXT="${1:-}"
|
|
17
|
-
VOICE="${2:-en_US-lessac-medium}"
|
|
18
|
-
AGENT_NAME="${3:-default}"
|
|
19
|
-
|
|
20
|
-
# Validate required input
|
|
21
|
-
if [[ -z "$TEXT" ]]; then
|
|
22
|
-
echo "❌ No text provided" >&2
|
|
23
|
-
echo "Usage: $0 <text> [voice] [agent_name]" >&2
|
|
24
|
-
exit 1
|
|
25
|
-
fi
|
|
26
|
-
|
|
27
|
-
# Get script directory
|
|
28
|
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
29
|
-
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
30
|
-
|
|
31
|
-
# Get SSH host from config
|
|
32
|
-
SSH_HOST=$(cat "$PROJECT_ROOT/.claude/agentvibes-receiver-host.txt" 2>/dev/null || \
|
|
33
|
-
cat "$HOME/.claude/agentvibes-receiver-host.txt" 2>/dev/null || echo "")
|
|
34
|
-
|
|
35
|
-
if [[ -z "$SSH_HOST" ]]; then
|
|
36
|
-
echo "❌ AgentVibes Receiver host not configured" >&2
|
|
37
|
-
echo "💡 Set host: echo 'your-device' > ~/.claude/agentvibes-receiver-host.txt" >&2
|
|
38
|
-
exit 1
|
|
39
|
-
fi
|
|
40
|
-
|
|
41
|
-
# SECURITY: Validate SSH_HOST to prevent option injection
|
|
42
|
-
# Must be a valid hostname, IP address, or SSH config alias (alphanumeric, dots, hyphens, underscores)
|
|
43
|
-
if [[ ! "$SSH_HOST" =~ ^[a-zA-Z0-9][a-zA-Z0-9._-]*$ ]]; then
|
|
44
|
-
echo "❌ Invalid SSH host format: $SSH_HOST" >&2
|
|
45
|
-
echo "💡 Host must be alphanumeric (may contain dots, hyphens, underscores)" >&2
|
|
46
|
-
exit 1
|
|
47
|
-
fi
|
|
48
|
-
|
|
49
|
-
# SECURITY: Reject hosts starting with hyphen (SSH option injection)
|
|
50
|
-
if [[ "$SSH_HOST" == -* ]]; then
|
|
51
|
-
echo "❌ Invalid SSH host: cannot start with hyphen" >&2
|
|
52
|
-
exit 1
|
|
53
|
-
fi
|
|
54
|
-
|
|
55
|
-
# SECURITY: Validate VOICE
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
#
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# File: .claude/hooks/play-tts-agentvibes-receiver-for-voiceless-connections.sh
|
|
4
|
+
#
|
|
5
|
+
# AgentVibes - AgentVibes Receiver Provider (for voiceless connections)
|
|
6
|
+
# Sends text to a remote device via SSH for local AgentVibes playback.
|
|
7
|
+
# Use this when the AI agent runs on a server/headless machine that has no
|
|
8
|
+
# audio output — the remote device (laptop, phone, etc.) plays the audio.
|
|
9
|
+
#
|
|
10
|
+
# Copyright (c) 2025 Paul Preibisch
|
|
11
|
+
# Licensed under the Apache License, Version 2.0
|
|
12
|
+
#
|
|
13
|
+
|
|
14
|
+
set -euo pipefail
|
|
15
|
+
|
|
16
|
+
TEXT="${1:-}"
|
|
17
|
+
VOICE="${2:-en_US-lessac-medium}"
|
|
18
|
+
AGENT_NAME="${3:-default}"
|
|
19
|
+
|
|
20
|
+
# Validate required input
|
|
21
|
+
if [[ -z "$TEXT" ]]; then
|
|
22
|
+
echo "❌ No text provided" >&2
|
|
23
|
+
echo "Usage: $0 <text> [voice] [agent_name]" >&2
|
|
24
|
+
exit 1
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
# Get script directory
|
|
28
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
29
|
+
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
30
|
+
|
|
31
|
+
# Get SSH host from config
|
|
32
|
+
SSH_HOST=$(cat "$PROJECT_ROOT/.claude/agentvibes-receiver-host.txt" 2>/dev/null || \
|
|
33
|
+
cat "$HOME/.claude/agentvibes-receiver-host.txt" 2>/dev/null || echo "")
|
|
34
|
+
|
|
35
|
+
if [[ -z "$SSH_HOST" ]]; then
|
|
36
|
+
echo "❌ AgentVibes Receiver host not configured" >&2
|
|
37
|
+
echo "💡 Set host: echo 'your-device' > ~/.claude/agentvibes-receiver-host.txt" >&2
|
|
38
|
+
exit 1
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
# SECURITY: Validate SSH_HOST to prevent option injection
|
|
42
|
+
# Must be a valid hostname, IP address, or SSH config alias (alphanumeric, dots, hyphens, underscores)
|
|
43
|
+
if [[ ! "$SSH_HOST" =~ ^[a-zA-Z0-9][a-zA-Z0-9._-]*$ ]]; then
|
|
44
|
+
echo "❌ Invalid SSH host format: $SSH_HOST" >&2
|
|
45
|
+
echo "💡 Host must be alphanumeric (may contain dots, hyphens, underscores)" >&2
|
|
46
|
+
exit 1
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
# SECURITY: Reject hosts starting with hyphen (SSH option injection)
|
|
50
|
+
if [[ "$SSH_HOST" == -* ]]; then
|
|
51
|
+
echo "❌ Invalid SSH host: cannot start with hyphen" >&2
|
|
52
|
+
exit 1
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
# SECURITY: Validate VOICE (allow :: for multi-speaker, . for locale, space for names)
|
|
56
|
+
_voice_re='^[a-zA-Z0-9_.: -]+$'
|
|
57
|
+
if [[ ! "$VOICE" =~ $_voice_re ]]; then
|
|
58
|
+
echo "❌ Invalid voice format: $VOICE" >&2
|
|
59
|
+
exit 1
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
# SECURITY: Validate AGENT_NAME to prevent injection (alphanumeric, hyphens, underscores, spaces only)
|
|
63
|
+
if [[ ! "$AGENT_NAME" =~ ^[a-zA-Z0-9_\ -]+$ ]]; then
|
|
64
|
+
echo "❌ Invalid agent name format: $AGENT_NAME" >&2
|
|
65
|
+
exit 1
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
# SECURITY: Encode text and agent name as base64 to prevent command injection
|
|
69
|
+
# The receiver will decode these safely
|
|
70
|
+
# Probe for GNU base64 (-w 0), fall back to BSD (-b 0), then tr
|
|
71
|
+
if printf '' | base64 -w 0 >/dev/null 2>&1; then
|
|
72
|
+
ENCODED_TEXT=$(printf '%s' "$TEXT" | base64 -w 0)
|
|
73
|
+
ENCODED_AGENT=$(printf '%s' "$AGENT_NAME" | base64 -w 0)
|
|
74
|
+
else
|
|
75
|
+
ENCODED_TEXT=$(printf '%s' "$TEXT" | base64 -b 0 2>/dev/null || printf '%s' "$TEXT" | base64 | tr -d '\n')
|
|
76
|
+
ENCODED_AGENT=$(printf '%s' "$AGENT_NAME" | base64 -b 0 2>/dev/null || printf '%s' "$AGENT_NAME" | base64 | tr -d '\n')
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
# Send text to remote for local AgentVibes playback
|
|
80
|
+
echo "📱 Sending to $SSH_HOST for local playback..." >&2
|
|
81
|
+
|
|
82
|
+
# Try receiver scripts in order — single SSH call, no separate probe
|
|
83
|
+
# SECURITY: Base64-encoded values are safe to pass as arguments (no shell metacharacters)
|
|
84
|
+
ssh "$SSH_HOST" "
|
|
85
|
+
if [ -f ~/.agentvibes/play-remote.sh ]; then
|
|
86
|
+
bash ~/.agentvibes/play-remote.sh '$ENCODED_TEXT' '$VOICE' '$ENCODED_AGENT'
|
|
87
|
+
elif [ -f ~/.termux/agentvibes-play.sh ]; then
|
|
88
|
+
bash ~/.termux/agentvibes-play.sh '$ENCODED_TEXT' '$VOICE' '$ENCODED_AGENT'
|
|
89
|
+
else
|
|
90
|
+
echo 'Error: Receiver script not found' >&2
|
|
91
|
+
exit 1
|
|
92
|
+
fi
|
|
93
|
+
" &
|
|
94
|
+
SSH_PID=$!
|
|
95
|
+
|
|
96
|
+
echo "Sent to $SSH_HOST (PID: $SSH_PID)" >&2
|
|
97
|
+
exit 0
|
|
@@ -64,7 +64,7 @@ export AGENTVIBES_WAV_OUTPATH="${XDG_RUNTIME_DIR:-/tmp}/agentvibes-last-wav-$$.t
|
|
|
64
64
|
|
|
65
65
|
# Cleanup temp outpath file on exit
|
|
66
66
|
trap 'rm -f "$AGENTVIBES_WAV_OUTPATH"' EXIT
|
|
67
|
-
"$SCRIPT_DIR/play-tts.sh" "$TEXT" "$VOICE_OVERRIDE"
|
|
67
|
+
bash "$SCRIPT_DIR/play-tts.sh" "$TEXT" "$VOICE_OVERRIDE"
|
|
68
68
|
|
|
69
69
|
# Read the generated file path (written by play-tts-piper.sh via AGENTVIBES_WAV_OUTPATH)
|
|
70
70
|
GENERATED_FILE=""
|
|
@@ -232,18 +232,29 @@ if [[ "${AGENTVIBES_TEST_MODE:-false}" != "true" ]] && ! verify_voice "$VOICE_MO
|
|
|
232
232
|
echo " File size: ~25MB"
|
|
233
233
|
echo " Preview: https://huggingface.co/rhasspy/piper-voices"
|
|
234
234
|
echo ""
|
|
235
|
-
read -p " Download this voice model? [y/N]: " -n 1 -r
|
|
236
|
-
echo
|
|
237
235
|
|
|
238
|
-
|
|
236
|
+
# Auto-download when non-interactive (e.g. called from a hook)
|
|
237
|
+
if [[ ! -t 0 ]]; then
|
|
238
|
+
echo " Auto-downloading (non-interactive mode)..."
|
|
239
239
|
if ! download_voice "$VOICE_MODEL"; then
|
|
240
240
|
echo "❌ Failed to download voice model"
|
|
241
241
|
echo "Fix: Download manually or choose different voice"
|
|
242
242
|
exit 3
|
|
243
243
|
fi
|
|
244
244
|
else
|
|
245
|
-
|
|
246
|
-
|
|
245
|
+
read -p " Download this voice model? [y/N]: " -n 1 -r
|
|
246
|
+
echo
|
|
247
|
+
|
|
248
|
+
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
|
249
|
+
if ! download_voice "$VOICE_MODEL"; then
|
|
250
|
+
echo "❌ Failed to download voice model"
|
|
251
|
+
echo "Fix: Download manually or choose different voice"
|
|
252
|
+
exit 3
|
|
253
|
+
fi
|
|
254
|
+
else
|
|
255
|
+
echo "❌ Voice download cancelled"
|
|
256
|
+
exit 3
|
|
257
|
+
fi
|
|
247
258
|
fi
|
|
248
259
|
fi
|
|
249
260
|
|
|
@@ -1,167 +1,168 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
#
|
|
3
|
-
# File: .claude/hooks/play-tts-ssh-remote.sh
|
|
4
|
-
#
|
|
5
|
-
# AgentVibes - SSH-Remote TTS Provider (v2 — JSON payload)
|
|
6
|
-
# Sends text + effects config to remote device via SSH for local playback
|
|
7
|
-
#
|
|
8
|
-
# The sender reads local audio-effects.cfg and bundles everything into a
|
|
9
|
-
# single base64-encoded JSON payload. The receiver is a thin executor.
|
|
10
|
-
#
|
|
11
|
-
# Copyright (c) 2025 Paul Preibisch
|
|
12
|
-
# Licensed under the Apache License, Version 2.0
|
|
13
|
-
#
|
|
14
|
-
|
|
15
|
-
set -euo pipefail
|
|
16
|
-
|
|
17
|
-
TEXT="${1:-}"
|
|
18
|
-
VOICE="${2:-en_US-lessac-medium}"
|
|
19
|
-
AGENT_NAME="${3:-default}"
|
|
20
|
-
|
|
21
|
-
# Validate required input
|
|
22
|
-
if [[ -z "$TEXT" ]]; then
|
|
23
|
-
echo "Usage: $0 <text> [voice] [agent_name]" >&2
|
|
24
|
-
exit 1
|
|
25
|
-
fi
|
|
26
|
-
|
|
27
|
-
# Get script directory and project root
|
|
28
|
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
29
|
-
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
30
|
-
|
|
31
|
-
# Derive project name from directory
|
|
32
|
-
PROJECT_NAME=$(basename "$PROJECT_ROOT")
|
|
33
|
-
|
|
34
|
-
# ---------------------------------------------------------------------------
|
|
35
|
-
# Get SSH host from config
|
|
36
|
-
# ---------------------------------------------------------------------------
|
|
37
|
-
|
|
38
|
-
SSH_HOST=$(cat "$PROJECT_ROOT/.claude/ssh-remote-host.txt" 2>/dev/null || \
|
|
39
|
-
cat "$HOME/.claude/ssh-remote-host.txt" 2>/dev/null || echo "")
|
|
40
|
-
|
|
41
|
-
if [[ -z "$SSH_HOST" ]]; then
|
|
42
|
-
echo "SSH-Remote host not configured" >&2
|
|
43
|
-
echo "Set host: echo 'my-host' > .claude/ssh-remote-host.txt" >&2
|
|
44
|
-
exit 1
|
|
45
|
-
fi
|
|
46
|
-
|
|
47
|
-
# SECURITY: Validate SSH_HOST format
|
|
48
|
-
if [[ ! "$SSH_HOST" =~ ^[a-zA-Z0-9][a-zA-Z0-9._-]*$ ]]; then
|
|
49
|
-
echo "Invalid SSH host format: $SSH_HOST" >&2
|
|
50
|
-
exit 1
|
|
51
|
-
fi
|
|
52
|
-
|
|
53
|
-
# SECURITY: Validate VOICE
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
#
|
|
67
|
-
#
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
fi
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
#
|
|
98
|
-
#
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
fi
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
#
|
|
119
|
-
#
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
--arg
|
|
127
|
-
--arg
|
|
128
|
-
--arg
|
|
129
|
-
--arg
|
|
130
|
-
--arg
|
|
131
|
-
--arg
|
|
132
|
-
--arg
|
|
133
|
-
--arg
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
escaped_text
|
|
139
|
-
|
|
140
|
-
escaped_pretext
|
|
141
|
-
printf '
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
#
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
#
|
|
158
|
-
#
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# File: .claude/hooks/play-tts-ssh-remote.sh
|
|
4
|
+
#
|
|
5
|
+
# AgentVibes - SSH-Remote TTS Provider (v2 — JSON payload)
|
|
6
|
+
# Sends text + effects config to remote device via SSH for local playback
|
|
7
|
+
#
|
|
8
|
+
# The sender reads local audio-effects.cfg and bundles everything into a
|
|
9
|
+
# single base64-encoded JSON payload. The receiver is a thin executor.
|
|
10
|
+
#
|
|
11
|
+
# Copyright (c) 2025 Paul Preibisch
|
|
12
|
+
# Licensed under the Apache License, Version 2.0
|
|
13
|
+
#
|
|
14
|
+
|
|
15
|
+
set -euo pipefail
|
|
16
|
+
|
|
17
|
+
TEXT="${1:-}"
|
|
18
|
+
VOICE="${2:-en_US-lessac-medium}"
|
|
19
|
+
AGENT_NAME="${3:-default}"
|
|
20
|
+
|
|
21
|
+
# Validate required input
|
|
22
|
+
if [[ -z "$TEXT" ]]; then
|
|
23
|
+
echo "Usage: $0 <text> [voice] [agent_name]" >&2
|
|
24
|
+
exit 1
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
# Get script directory and project root
|
|
28
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
29
|
+
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
30
|
+
|
|
31
|
+
# Derive project name from directory
|
|
32
|
+
PROJECT_NAME=$(basename "$PROJECT_ROOT")
|
|
33
|
+
|
|
34
|
+
# ---------------------------------------------------------------------------
|
|
35
|
+
# Get SSH host from config
|
|
36
|
+
# ---------------------------------------------------------------------------
|
|
37
|
+
|
|
38
|
+
SSH_HOST=$(cat "$PROJECT_ROOT/.claude/ssh-remote-host.txt" 2>/dev/null || \
|
|
39
|
+
cat "$HOME/.claude/ssh-remote-host.txt" 2>/dev/null || echo "")
|
|
40
|
+
|
|
41
|
+
if [[ -z "$SSH_HOST" ]]; then
|
|
42
|
+
echo "SSH-Remote host not configured" >&2
|
|
43
|
+
echo "Set host: echo 'my-host' > .claude/ssh-remote-host.txt" >&2
|
|
44
|
+
exit 1
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
# SECURITY: Validate SSH_HOST format
|
|
48
|
+
if [[ ! "$SSH_HOST" =~ ^[a-zA-Z0-9][a-zA-Z0-9._-]*$ ]]; then
|
|
49
|
+
echo "Invalid SSH host format: $SSH_HOST" >&2
|
|
50
|
+
exit 1
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
# SECURITY: Validate VOICE (allow :: for multi-speaker, . for locale, space for names)
|
|
54
|
+
_voice_re='^[a-zA-Z0-9_.: -]+$'
|
|
55
|
+
if [[ ! "$VOICE" =~ $_voice_re ]]; then
|
|
56
|
+
echo "Invalid voice format: $VOICE" >&2
|
|
57
|
+
exit 1
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
# SECURITY: Validate AGENT_NAME
|
|
61
|
+
if [[ ! "$AGENT_NAME" =~ ^[a-zA-Z0-9_\ -]+$ ]]; then
|
|
62
|
+
echo "Invalid agent name format: $AGENT_NAME" >&2
|
|
63
|
+
exit 1
|
|
64
|
+
fi
|
|
65
|
+
|
|
66
|
+
# ---------------------------------------------------------------------------
|
|
67
|
+
# Read audio effects config for this agent
|
|
68
|
+
# ---------------------------------------------------------------------------
|
|
69
|
+
|
|
70
|
+
SOX_EFFECTS=""
|
|
71
|
+
BG_FILE=""
|
|
72
|
+
BG_VOLUME="0.10"
|
|
73
|
+
|
|
74
|
+
EFFECTS_CFG="$PROJECT_ROOT/.claude/config/audio-effects.cfg"
|
|
75
|
+
if [[ -f "$EFFECTS_CFG" ]]; then
|
|
76
|
+
CONFIG_LINE=$(grep "^${AGENT_NAME}|" "$EFFECTS_CFG" 2>/dev/null || \
|
|
77
|
+
grep "^default|" "$EFFECTS_CFG" 2>/dev/null || true)
|
|
78
|
+
if [[ -n "$CONFIG_LINE" ]]; then
|
|
79
|
+
IFS='|' read -r _ SOX_EFFECTS BG_FILE BG_VOLUME <<< "$CONFIG_LINE"
|
|
80
|
+
fi
|
|
81
|
+
fi
|
|
82
|
+
|
|
83
|
+
# Read pretext if configured
|
|
84
|
+
PRETEXT=""
|
|
85
|
+
PRETEXT_FILE="$PROJECT_ROOT/.agentvibes/config/pretext.txt"
|
|
86
|
+
if [[ -f "$PRETEXT_FILE" ]]; then
|
|
87
|
+
PRETEXT=$(cat "$PRETEXT_FILE" 2>/dev/null || true)
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
# Read speed if configured
|
|
91
|
+
SPEED=""
|
|
92
|
+
SPEED_FILE="$PROJECT_ROOT/.agentvibes/config/speed.txt"
|
|
93
|
+
if [[ -f "$SPEED_FILE" ]]; then
|
|
94
|
+
SPEED=$(cat "$SPEED_FILE" 2>/dev/null || true)
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
# Read the TTS provider the RECEIVER should use to generate audio.
|
|
98
|
+
# This is separate from the sender's own provider (which is "ssh-remote").
|
|
99
|
+
# Check receiver-provider.txt first, then fall back to "piper".
|
|
100
|
+
PROVIDER=""
|
|
101
|
+
RECEIVER_PROVIDER_FILE="$PROJECT_ROOT/.agentvibes/config/receiver-provider.txt"
|
|
102
|
+
if [[ -f "$RECEIVER_PROVIDER_FILE" ]]; then
|
|
103
|
+
PROVIDER=$(cat "$RECEIVER_PROVIDER_FILE" 2>/dev/null || true)
|
|
104
|
+
fi
|
|
105
|
+
# Also check home-level config
|
|
106
|
+
if [[ -z "$PROVIDER" ]]; then
|
|
107
|
+
RECEIVER_PROVIDER_FILE="$HOME/.agentvibes/config/receiver-provider.txt"
|
|
108
|
+
if [[ -f "$RECEIVER_PROVIDER_FILE" ]]; then
|
|
109
|
+
PROVIDER=$(cat "$RECEIVER_PROVIDER_FILE" 2>/dev/null || true)
|
|
110
|
+
fi
|
|
111
|
+
fi
|
|
112
|
+
# Validate — only known TTS providers (not transport providers like ssh-remote)
|
|
113
|
+
case "${PROVIDER:-}" in
|
|
114
|
+
piper|soprano|macos|windows-sapi) ;;
|
|
115
|
+
*) PROVIDER="piper" ;;
|
|
116
|
+
esac
|
|
117
|
+
|
|
118
|
+
# ---------------------------------------------------------------------------
|
|
119
|
+
# Build JSON payload
|
|
120
|
+
# ---------------------------------------------------------------------------
|
|
121
|
+
|
|
122
|
+
# SECURITY: Use jq if available for safe JSON construction, else manual escaping
|
|
123
|
+
build_json_payload() {
|
|
124
|
+
if command -v jq &>/dev/null; then
|
|
125
|
+
jq -n \
|
|
126
|
+
--arg text "$TEXT" \
|
|
127
|
+
--arg voice "$VOICE" \
|
|
128
|
+
--arg effects "$SOX_EFFECTS" \
|
|
129
|
+
--arg music "$BG_FILE" \
|
|
130
|
+
--arg volume "$BG_VOLUME" \
|
|
131
|
+
--arg project "$PROJECT_NAME" \
|
|
132
|
+
--arg pretext "$PRETEXT" \
|
|
133
|
+
--arg speed "$SPEED" \
|
|
134
|
+
--arg provider "$PROVIDER" \
|
|
135
|
+
'{text: $text, voice: $voice, effects: $effects, music: $music, volume: $volume, project: $project, pretext: $pretext, speed: $speed, provider: $provider}'
|
|
136
|
+
else
|
|
137
|
+
# Manual JSON — escape double quotes and backslashes in text
|
|
138
|
+
local escaped_text
|
|
139
|
+
escaped_text=$(printf '%s' "$TEXT" | sed 's/\\/\\\\/g; s/"/\\"/g; s/\t/\\t/g')
|
|
140
|
+
local escaped_pretext
|
|
141
|
+
escaped_pretext=$(printf '%s' "$PRETEXT" | sed 's/\\/\\\\/g; s/"/\\"/g')
|
|
142
|
+
printf '{"text":"%s","voice":"%s","effects":"%s","music":"%s","volume":"%s","project":"%s","pretext":"%s","speed":"%s","provider":"%s"}' \
|
|
143
|
+
"$escaped_text" "$VOICE" "$SOX_EFFECTS" "$BG_FILE" "$BG_VOLUME" "$PROJECT_NAME" "$escaped_pretext" "$SPEED" "$PROVIDER"
|
|
144
|
+
fi
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
JSON_PAYLOAD=$(build_json_payload)
|
|
148
|
+
|
|
149
|
+
# SECURITY: Base64-encode entire payload — safe for SSH transport
|
|
150
|
+
# base64 -w 0 is Linux (GNU coreutils), -b 0 is macOS (BSD)
|
|
151
|
+
if base64 --help 2>&1 | grep -q '\-w'; then
|
|
152
|
+
ENCODED_PAYLOAD=$(printf '%s' "$JSON_PAYLOAD" | base64 -w 0)
|
|
153
|
+
else
|
|
154
|
+
ENCODED_PAYLOAD=$(printf '%s' "$JSON_PAYLOAD" | base64 -b 0 2>/dev/null || printf '%s' "$JSON_PAYLOAD" | base64 | tr -d '\n')
|
|
155
|
+
fi
|
|
156
|
+
|
|
157
|
+
# ---------------------------------------------------------------------------
|
|
158
|
+
# Send to receiver via SSH (fire and forget — backgrounded)
|
|
159
|
+
# ---------------------------------------------------------------------------
|
|
160
|
+
|
|
161
|
+
echo "Sending to $SSH_HOST..." >&2
|
|
162
|
+
|
|
163
|
+
# ForceCommand receiver: SSH_ORIGINAL_COMMAND passes the payload directly
|
|
164
|
+
ssh "$SSH_HOST" "$ENCODED_PAYLOAD" &
|
|
165
|
+
SSH_PID=$!
|
|
166
|
+
|
|
167
|
+
echo "Sent to $SSH_HOST (PID: $SSH_PID)" >&2
|
|
168
|
+
exit 0
|