agentvibes 5.9.0 → 5.10.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.
- package/.agentvibes/config.json +3 -12
- package/.claude/commands/agent-vibes-bmad-voices.md +117 -117
- package/.claude/commands/agent-vibes-rdp.md +24 -24
- package/.claude/config/audio-effects.cfg +4 -5
- package/.claude/config/audio-effects.cfg.sample +52 -52
- package/.claude/config/background-music-enabled.txt +1 -1
- package/.claude/docs/TERMUX_SETUP.md +408 -408
- package/.claude/github-star-reminder.txt +1 -1
- package/.claude/hooks/audio-cache-utils.sh +0 -0
- package/.claude/hooks/audio-processor.sh +0 -0
- package/.claude/hooks/background-music-manager.sh +0 -0
- package/.claude/hooks/bmad-party-speak.sh +0 -0
- package/.claude/hooks/bmad-speak-enhanced.sh +0 -0
- package/.claude/hooks/bmad-speak.sh +0 -0
- package/.claude/hooks/bmad-tts-injector.sh +0 -0
- package/.claude/hooks/bmad-voice-manager.sh +0 -0
- package/.claude/hooks/clawdbot-receiver-SECURE.sh +0 -0
- package/.claude/hooks/clawdbot-receiver.sh +0 -0
- package/.claude/hooks/clean-audio-cache.sh +0 -0
- package/.claude/hooks/cleanup-cache.sh +0 -0
- package/.claude/hooks/configure-rdp-mode.sh +0 -0
- package/.claude/hooks/download-extra-voices.sh +0 -0
- package/.claude/hooks/effects-manager.sh +0 -0
- package/.claude/hooks/github-star-reminder.sh +0 -0
- package/.claude/hooks/language-manager.sh +0 -0
- package/.claude/hooks/learn-manager.sh +0 -0
- package/.claude/hooks/macos-voice-manager.sh +0 -0
- package/.claude/hooks/migrate-background-music.sh +0 -0
- package/.claude/hooks/migrate-to-agentvibes.sh +0 -0
- package/.claude/hooks/optimize-background-music.sh +0 -0
- package/.claude/hooks/path-resolver.sh +0 -0
- package/.claude/hooks/personality-manager.sh +0 -0
- package/.claude/hooks/piper-download-voices.sh +0 -0
- package/.claude/hooks/piper-installer.sh +0 -0
- package/.claude/hooks/piper-multispeaker-registry.sh +0 -0
- package/.claude/hooks/piper-voice-manager.sh +0 -0
- package/.claude/hooks/play-tts-agentvibes-receiver-for-voiceless-connections.sh +0 -0
- package/.claude/hooks/play-tts-enhanced.sh +0 -0
- package/.claude/hooks/play-tts-macos.sh +0 -0
- package/.claude/hooks/play-tts-piper.sh +20 -13
- package/.claude/hooks/play-tts-soprano.sh +0 -0
- package/.claude/hooks/play-tts-ssh-remote.sh +0 -0
- package/.claude/hooks/play-tts-termux-ssh.sh +0 -0
- package/.claude/hooks/play-tts-windows-receiver.sh +0 -0
- package/.claude/hooks/play-tts.sh +0 -0
- package/.claude/hooks/prepare-release.sh +0 -0
- package/.claude/hooks/provider-commands.sh +0 -0
- package/.claude/hooks/provider-manager.sh +0 -0
- package/.claude/hooks/replay-target-audio.sh +0 -0
- package/.claude/hooks/requirements.txt +6 -6
- package/.claude/hooks/sentiment-manager.sh +0 -0
- package/.claude/hooks/session-start-tts.sh +0 -0
- package/.claude/hooks/soprano-gradio-synth.py +139 -139
- package/.claude/hooks/speed-manager.sh +0 -0
- package/.claude/hooks/stop-tts.sh +0 -0
- package/.claude/hooks/termux-installer.sh +0 -0
- package/.claude/hooks/translate-manager.sh +0 -0
- package/.claude/hooks/translator.py +237 -237
- package/.claude/hooks/tts-queue-worker.sh +0 -0
- package/.claude/hooks/tts-queue.sh +0 -0
- package/.claude/hooks/verbosity-manager.sh +0 -0
- package/.claude/hooks/voice-manager.sh +6 -0
- package/.claude/hooks-windows/play-tts-windows-piper.ps1 +22 -16
- package/.claude/hooks-windows/soprano-gradio-synth.py +153 -153
- package/.claude/verbosity.txt +1 -1
- package/.clawdbot/README.md +105 -105
- package/.mcp.json +19 -6
- package/README.md +1 -1
- package/WINDOWS-SETUP.md +208 -208
- package/bin/agent-vibes +39 -39
- package/bin/agentvibes-voice-browser.js +0 -0
- package/bin/agentvibes.js +0 -0
- package/bin/mcp-server.js +121 -121
- package/bin/mcp-server.sh +0 -0
- package/bin/test-bmad-pr +78 -78
- package/mcp-server/QUICK_START.md +203 -203
- package/mcp-server/README.md +345 -345
- package/mcp-server/WINDOWS_SETUP.md +0 -0
- package/mcp-server/examples/claude_desktop_config.json +11 -11
- package/mcp-server/examples/claude_desktop_config_piper.json +9 -9
- package/mcp-server/examples/custom_instructions.md +169 -169
- package/mcp-server/install-deps.js +0 -0
- package/mcp-server/server.py +1807 -1797
- package/mcp-server/test_server.py +0 -0
- package/package.json +2 -2
- package/src/cli/list-personalities.js +110 -110
- package/src/cli/list-voices.js +114 -114
- package/src/commands/bmad-voices.js +394 -394
- package/src/commands/install-mcp.js +730 -476
- package/src/console/app.js +3 -3
- package/src/console/brand-colors.js +13 -13
- package/src/console/constants/personalities.js +44 -44
- package/src/console/tabs/agents-tab.js +6 -6
- package/src/console/tabs/help-tab.js +314 -314
- package/src/console/tabs/music-tab.js +1 -1
- package/src/console/tabs/readme-tab.js +272 -272
- package/src/console/tabs/receiver-tab.js +13 -13
- package/src/console/tabs/settings-tab.js +2 -2
- package/src/console/tabs/setup-tab.js +10 -10
- package/src/console/tabs/voices-tab.js +4 -4
- package/src/console/widgets/destroy-list.js +25 -25
- package/src/console/widgets/notice.js +55 -55
- package/src/console/widgets/personality-picker.js +2 -2
- package/src/console/widgets/reverb-picker.js +1 -1
- package/src/i18n/de.js +202 -202
- package/src/i18n/es.js +202 -202
- package/src/i18n/fr.js +202 -202
- package/src/i18n/hi.js +202 -202
- package/src/i18n/ja.js +202 -202
- package/src/i18n/ko.js +202 -202
- package/src/i18n/pt.js +202 -202
- package/src/i18n/strings.js +54 -54
- package/src/i18n/zh-CN.js +202 -202
- package/src/installer/language-screen.js +31 -31
- package/src/installer/music-file-input.js +304 -304
- package/src/installer.js +32 -27
- package/src/services/config-service.js +264 -264
- package/src/services/language-service.js +47 -47
- package/src/services/provider-service.js +143 -143
- package/src/services/tts-engine-service.js +2 -2
- package/src/utils/audio-duration-validator.js +298 -298
- package/src/utils/audio-format-validator.js +277 -277
- package/src/utils/dependency-checker.js +469 -469
- package/src/utils/file-ownership-verifier.js +358 -358
- package/src/utils/list-formatter.js +200 -194
- package/src/utils/music-file-validator.js +285 -285
- package/src/utils/platform-resolver.js +369 -0
- package/src/utils/preview-list-prompt.js +136 -136
- package/src/utils/provider-validator.js +9 -9
- package/src/utils/secure-music-storage.js +412 -412
- package/templates/agentvibes-receiver.sh +231 -231
- package/templates/audio/welcome-music.mp3 +0 -0
- package/.agentvibes/install-manifest.json +0 -330
- package/.claude/config/background-music-position.txt +0 -27
- 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/language.txt +0 -1
- package/.claude/config/reverb-level.txt +0 -1
- package/.claude/config/tts-speech-rate.txt +0 -1
- package/.claude/config/tts-verbosity.txt +0 -1
- package/.claude/hooks/play-tts-agentvibes-receiver.sh +0 -1
- package/.claude/hooks-windows/audio-cache-utils.ps1.user.bak +0 -119
- package/.claude/hooks-windows/soprano-gradio-synth.py.user.bak +0 -153
- package/.claude/piper-voices-dir.txt +0 -1
|
@@ -1,231 +1,231 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
#
|
|
3
|
-
# File: agentvibes-receiver.sh
|
|
4
|
-
# Location: User installs to ~/.termux/agentvibes-play.sh or ~/.agentvibes/play-remote.sh
|
|
5
|
-
#
|
|
6
|
-
# AgentVibes SSH-TTS Receiver
|
|
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.
|
|
14
|
-
#
|
|
15
|
-
# Installation:
|
|
16
|
-
# curl -sSL https://raw.githubusercontent.com/paulpreibisch/AgentVibes/main/scripts/install-ssh-receiver.sh | bash
|
|
17
|
-
# OR
|
|
18
|
-
# agentvibes install --ssh-receiver
|
|
19
|
-
#
|
|
20
|
-
# Copyright (c) 2025 Paul Preibisch
|
|
21
|
-
# Licensed under Apache-2.0
|
|
22
|
-
#
|
|
23
|
-
|
|
24
|
-
set -euo pipefail
|
|
25
|
-
|
|
26
|
-
# Handle -- argument separator (skip it if present)
|
|
27
|
-
if [[ "${1:-}" == "--" ]]; then
|
|
28
|
-
shift
|
|
29
|
-
fi
|
|
30
|
-
|
|
31
|
-
# ---------------------------------------------------------------------------
|
|
32
|
-
# Resolve encoded payload: arg → SSH_ORIGINAL_COMMAND → stdin (legacy)
|
|
33
|
-
# ---------------------------------------------------------------------------
|
|
34
|
-
|
|
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
|
|
46
|
-
exit 1
|
|
47
|
-
fi
|
|
48
|
-
|
|
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
|
|
54
|
-
fi
|
|
55
|
-
|
|
56
|
-
# Validate it looks like base64
|
|
57
|
-
if [[ ! "$ENCODED_PAYLOAD" =~ ^[A-Za-z0-9+/=]+$ ]]; then
|
|
58
|
-
echo "❌ Payload must be base64-encoded" >&2
|
|
59
|
-
exit 1
|
|
60
|
-
fi
|
|
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
|
-
|
|
156
|
-
# Suppress GitHub star reminders (receiver mode)
|
|
157
|
-
export AGENTVIBES_NO_REMINDERS=1
|
|
158
|
-
|
|
159
|
-
find_agentvibes() {
|
|
160
|
-
if command -v agentvibes >/dev/null 2>&1; then
|
|
161
|
-
local bin_path
|
|
162
|
-
bin_path=$(which agentvibes)
|
|
163
|
-
if [[ -L "$bin_path" ]]; then
|
|
164
|
-
bin_path=$(readlink -f "$bin_path" 2>/dev/null || realpath "$bin_path" 2>/dev/null || echo "$bin_path")
|
|
165
|
-
fi
|
|
166
|
-
local lib_path
|
|
167
|
-
lib_path="$(dirname "$(dirname "$bin_path")")/lib/node_modules/agentvibes"
|
|
168
|
-
if [[ -d "$lib_path" ]]; then
|
|
169
|
-
echo "$lib_path"
|
|
170
|
-
return 0
|
|
171
|
-
fi
|
|
172
|
-
fi
|
|
173
|
-
|
|
174
|
-
local search_paths=(
|
|
175
|
-
"$HOME/.npm-global/lib/node_modules/agentvibes"
|
|
176
|
-
"/usr/local/lib/node_modules/agentvibes"
|
|
177
|
-
"/data/data/com.termux/files/usr/lib/node_modules/agentvibes"
|
|
178
|
-
)
|
|
179
|
-
|
|
180
|
-
if [[ -d "$HOME/.nvm/versions/node" ]]; then
|
|
181
|
-
local nvm_path
|
|
182
|
-
nvm_path=$(find "$HOME/.nvm/versions/node" -maxdepth 3 -type d -name "agentvibes" -path "*/lib/node_modules/*" 2>/dev/null | head -1)
|
|
183
|
-
if [[ -n "$nvm_path" ]] && [[ -d "$nvm_path" ]]; then
|
|
184
|
-
echo "$nvm_path"
|
|
185
|
-
return 0
|
|
186
|
-
fi
|
|
187
|
-
fi
|
|
188
|
-
|
|
189
|
-
for p in "${search_paths[@]}"; do
|
|
190
|
-
[[ -d "$p" ]] && { echo "$p"; return 0; }
|
|
191
|
-
done
|
|
192
|
-
|
|
193
|
-
return 1
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
AGENTVIBES_ROOT=$(find_agentvibes)
|
|
197
|
-
|
|
198
|
-
if [[ -z "$AGENTVIBES_ROOT" ]]; then
|
|
199
|
-
echo "❌ AgentVibes not found" >&2
|
|
200
|
-
echo "💡 Install: npm install -g agentvibes" >&2
|
|
201
|
-
exit 1
|
|
202
|
-
fi
|
|
203
|
-
|
|
204
|
-
PLAY_TTS="$AGENTVIBES_ROOT/.claude/hooks/play-tts.sh"
|
|
205
|
-
|
|
206
|
-
if [[ ! -f "$PLAY_TTS" ]]; then
|
|
207
|
-
echo "❌ play-tts.sh not found at $PLAY_TTS" >&2; exit 1
|
|
208
|
-
fi
|
|
209
|
-
|
|
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"
|
|
218
|
-
|
|
219
|
-
if [[ "${AGENTVIBES_DEBUG:-0}" == "1" ]]; then
|
|
220
|
-
echo "[DEBUG] Voice: $VOICE" >&2
|
|
221
|
-
echo "[DEBUG] Music: ${MUSIC:-none}" >&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
|
|
226
|
-
fi
|
|
227
|
-
|
|
228
|
-
echo "🎵 Playing via AgentVibes: ${TEXT:0:50}..." >&2
|
|
229
|
-
bash "$PLAY_TTS" "$TEXT" "$VOICE"
|
|
230
|
-
|
|
231
|
-
exit 0
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# File: agentvibes-receiver.sh
|
|
4
|
+
# Location: User installs to ~/.termux/agentvibes-play.sh or ~/.agentvibes/play-remote.sh
|
|
5
|
+
#
|
|
6
|
+
# AgentVibes SSH-TTS Receiver
|
|
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.
|
|
14
|
+
#
|
|
15
|
+
# Installation:
|
|
16
|
+
# curl -sSL https://raw.githubusercontent.com/paulpreibisch/AgentVibes/main/scripts/install-ssh-receiver.sh | bash
|
|
17
|
+
# OR
|
|
18
|
+
# agentvibes install --ssh-receiver
|
|
19
|
+
#
|
|
20
|
+
# Copyright (c) 2025 Paul Preibisch
|
|
21
|
+
# Licensed under Apache-2.0
|
|
22
|
+
#
|
|
23
|
+
|
|
24
|
+
set -euo pipefail
|
|
25
|
+
|
|
26
|
+
# Handle -- argument separator (skip it if present)
|
|
27
|
+
if [[ "${1:-}" == "--" ]]; then
|
|
28
|
+
shift
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
# ---------------------------------------------------------------------------
|
|
32
|
+
# Resolve encoded payload: arg → SSH_ORIGINAL_COMMAND → stdin (legacy)
|
|
33
|
+
# ---------------------------------------------------------------------------
|
|
34
|
+
|
|
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
|
|
46
|
+
exit 1
|
|
47
|
+
fi
|
|
48
|
+
|
|
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
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
# Validate it looks like base64
|
|
57
|
+
if [[ ! "$ENCODED_PAYLOAD" =~ ^[A-Za-z0-9+/=]+$ ]]; then
|
|
58
|
+
echo "❌ Payload must be base64-encoded" >&2
|
|
59
|
+
exit 1
|
|
60
|
+
fi
|
|
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
|
+
|
|
156
|
+
# Suppress GitHub star reminders (receiver mode)
|
|
157
|
+
export AGENTVIBES_NO_REMINDERS=1
|
|
158
|
+
|
|
159
|
+
find_agentvibes() {
|
|
160
|
+
if command -v agentvibes >/dev/null 2>&1; then
|
|
161
|
+
local bin_path
|
|
162
|
+
bin_path=$(which agentvibes)
|
|
163
|
+
if [[ -L "$bin_path" ]]; then
|
|
164
|
+
bin_path=$(readlink -f "$bin_path" 2>/dev/null || realpath "$bin_path" 2>/dev/null || echo "$bin_path")
|
|
165
|
+
fi
|
|
166
|
+
local lib_path
|
|
167
|
+
lib_path="$(dirname "$(dirname "$bin_path")")/lib/node_modules/agentvibes"
|
|
168
|
+
if [[ -d "$lib_path" ]]; then
|
|
169
|
+
echo "$lib_path"
|
|
170
|
+
return 0
|
|
171
|
+
fi
|
|
172
|
+
fi
|
|
173
|
+
|
|
174
|
+
local search_paths=(
|
|
175
|
+
"$HOME/.npm-global/lib/node_modules/agentvibes"
|
|
176
|
+
"/usr/local/lib/node_modules/agentvibes"
|
|
177
|
+
"/data/data/com.termux/files/usr/lib/node_modules/agentvibes"
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
if [[ -d "$HOME/.nvm/versions/node" ]]; then
|
|
181
|
+
local nvm_path
|
|
182
|
+
nvm_path=$(find "$HOME/.nvm/versions/node" -maxdepth 3 -type d -name "agentvibes" -path "*/lib/node_modules/*" 2>/dev/null | head -1)
|
|
183
|
+
if [[ -n "$nvm_path" ]] && [[ -d "$nvm_path" ]]; then
|
|
184
|
+
echo "$nvm_path"
|
|
185
|
+
return 0
|
|
186
|
+
fi
|
|
187
|
+
fi
|
|
188
|
+
|
|
189
|
+
for p in "${search_paths[@]}"; do
|
|
190
|
+
[[ -d "$p" ]] && { echo "$p"; return 0; }
|
|
191
|
+
done
|
|
192
|
+
|
|
193
|
+
return 1
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
AGENTVIBES_ROOT=$(find_agentvibes)
|
|
197
|
+
|
|
198
|
+
if [[ -z "$AGENTVIBES_ROOT" ]]; then
|
|
199
|
+
echo "❌ AgentVibes not found" >&2
|
|
200
|
+
echo "💡 Install: npm install -g agentvibes" >&2
|
|
201
|
+
exit 1
|
|
202
|
+
fi
|
|
203
|
+
|
|
204
|
+
PLAY_TTS="$AGENTVIBES_ROOT/.claude/hooks/play-tts.sh"
|
|
205
|
+
|
|
206
|
+
if [[ ! -f "$PLAY_TTS" ]]; then
|
|
207
|
+
echo "❌ play-tts.sh not found at $PLAY_TTS" >&2; exit 1
|
|
208
|
+
fi
|
|
209
|
+
|
|
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"
|
|
218
|
+
|
|
219
|
+
if [[ "${AGENTVIBES_DEBUG:-0}" == "1" ]]; then
|
|
220
|
+
echo "[DEBUG] Voice: $VOICE" >&2
|
|
221
|
+
echo "[DEBUG] Music: ${MUSIC:-none}" >&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
|
|
226
|
+
fi
|
|
227
|
+
|
|
228
|
+
echo "🎵 Playing via AgentVibes: ${TEXT:0:50}..." >&2
|
|
229
|
+
bash "$PLAY_TTS" "$TEXT" "$VOICE"
|
|
230
|
+
|
|
231
|
+
exit 0
|
|
File without changes
|