agentvibes 5.12.0-alpha.0 → 5.13.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 +15 -1
- package/.agentvibes/install-manifest.json +342 -0
- package/.claude/activation-instructions +54 -54
- package/.claude/commands/agent-vibes/commands.json +0 -20
- package/.claude/commands/agent-vibes/unmute.md +6 -2
- 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 +6 -6
- package/.claude/config/audio-effects.cfg.bak-kokoro +7 -0
- package/.claude/config/background-music-enabled.txt +1 -1
- package/.claude/config/background-music-position.txt +27 -0
- package/.claude/config/background-music-volume.txt +1 -0
- package/.claude/config/background-music.cfg +1 -0
- package/.claude/config/background-music.txt +1 -0
- package/.claude/config/language.txt +1 -0
- package/.claude/config/personality.txt +1 -0
- package/.claude/config/tts-speech-rate.txt +1 -0
- package/.claude/config/tts-verbosity.txt +1 -0
- package/.claude/docs/TERMUX_SETUP.md +408 -408
- package/.claude/github-star-reminder.txt +1 -1
- package/.claude/hooks/agentvibes-session-id.sh +69 -0
- package/.claude/hooks/bmad-party-speak.sh +20 -4
- package/.claude/hooks/bmad-speak.sh +60 -2
- package/.claude/hooks/bmad-tts-injector.sh +20 -1
- package/.claude/hooks/bmad-voice-manager.sh +25 -3
- package/.claude/hooks/clawdbot-receiver-SECURE.sh +21 -2
- package/.claude/hooks/clawdbot-receiver.sh +19 -1
- package/.claude/hooks/elevenlabs-voices.sh +62 -0
- package/.claude/hooks/kokoro-installer.sh +20 -10
- package/.claude/hooks/language-manager.sh +10 -3
- package/.claude/hooks/party-set-room.sh +71 -0
- package/.claude/hooks/party-stage-roster.py +328 -0
- package/.claude/hooks/personality-manager.sh +19 -2
- package/.claude/hooks/piper-voice-manager.sh +3 -2
- package/.claude/hooks/play-tts-agentvibes-receiver-for-voiceless-connections.sh +24 -5
- package/.claude/hooks/play-tts-elevenlabs.sh +38 -118
- package/.claude/hooks/play-tts-kokoro.sh +33 -6
- package/.claude/hooks/play-tts-soprano.sh +3 -2
- package/.claude/hooks/play-tts-ssh-remote.sh +72 -27
- package/.claude/hooks/play-tts-termux-ssh.sh +5 -4
- package/.claude/hooks/play-tts.sh +86 -69
- package/.claude/hooks/provider-catalog.json +352 -0
- package/.claude/hooks/provider-catalog.sh +161 -0
- package/.claude/hooks/provider-commands.sh +2 -1
- package/.claude/hooks/provider-manager.sh +47 -9
- package/.claude/hooks/python-resolver.sh +117 -0
- package/.claude/hooks/session-id.sh +56 -0
- package/.claude/hooks/session-start-tts.sh +39 -0
- package/.claude/hooks/speed-manager.sh +1 -1
- package/.claude/hooks/translate-manager.sh +3 -2
- package/.claude/hooks/translator.py +1 -1
- package/.claude/hooks/voice-manager.sh +242 -10
- package/.claude/hooks-windows/audio-cache-utils.ps1.user.bak +119 -0
- package/.claude/hooks-windows/language-manager.ps1 +7 -1
- package/.claude/hooks-windows/personality-manager.ps1 +16 -1
- package/.claude/hooks-windows/play-tts-kokoro.ps1 +20 -4
- package/.claude/hooks-windows/play-tts.ps1 +32 -3
- package/.claude/hooks-windows/provider-catalog.ps1 +140 -0
- package/.claude/hooks-windows/provider-manager.ps1 +63 -8
- package/.claude/hooks-windows/soprano-gradio-synth.py.user.bak +153 -0
- package/.claude/hooks-windows/tts-watcher.ps1 +84 -8
- package/.claude/hooks-windows/voice-manager-windows.ps1 +49 -0
- package/.claude/piper-voices-dir.txt +1 -0
- package/.clawdbot/README.md +105 -105
- package/.mcp.json +15 -0
- package/LICENSE +190 -190
- package/README.md +20 -6
- package/RELEASE_NOTES.md +81 -0
- package/WINDOWS-SETUP.md +208 -208
- package/bin/agent-vibes +39 -39
- package/bin/mcp-server.js +121 -121
- 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/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/pyproject.toml +52 -52
- package/mcp-server/requirements.txt +2 -2
- package/mcp-server/server.py +146 -49
- package/mcp-server/test_mcp_correctness.py +20 -2
- package/mcp-server/test_server.py +395 -395
- package/mcp-server/test_windows_script_parity.py +0 -2
- package/package.json +1 -1
- package/src/cli/list-personalities.js +110 -110
- package/src/cli/list-voices.js +115 -11
- package/src/commands/bmad-voices.js +394 -394
- package/src/console/app.js +1 -9
- package/src/console/bling.js +71 -0
- package/src/console/brand-colors.js +13 -13
- package/src/console/constants/personalities.js +44 -44
- package/src/console/footer-config.js +0 -4
- package/src/console/music-preview.js +79 -0
- package/src/console/navigation.js +0 -1
- package/src/console/tabs/agents-tab.js +14 -2
- package/src/console/tabs/help-tab.js +314 -314
- package/src/console/tabs/music-tab.js +81 -5
- package/src/console/tabs/placeholder-tab.js +0 -2
- package/src/console/tabs/readme-tab.js +272 -272
- package/src/console/tabs/settings-tab.js +207 -14
- package/src/console/tabs/setup-tab.js +39 -36
- package/src/console/tabs/voices-tab.js +102 -23
- package/src/console/widgets/destroy-list.js +25 -25
- package/src/console/widgets/notice.js +55 -55
- package/src/console/widgets/track-picker.js +82 -0
- package/src/installer/language-screen.js +31 -31
- package/src/installer/music-file-input.js +304 -304
- package/src/installer.js +124 -10
- package/src/services/language-service.js +47 -47
- package/src/services/navigation-service.js +1 -1
- package/src/services/provider-catalog.js +412 -0
- package/src/services/provider-voice-catalog.js +52 -73
- package/src/services/tts-engine-service.js +29 -0
- 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/music-file-validator.js +285 -285
- package/src/utils/preview-list-prompt.js +144 -144
- package/src/utils/provider-validator.js +62 -12
- package/src/utils/secure-music-storage.js +412 -412
- package/voice-assignments.json +8244 -8244
- package/.claude/commands/agent-vibes/language.md +0 -23
- package/.claude/commands/agent-vibes/learn.md +0 -67
- package/.claude/commands/agent-vibes/replay-target.md +0 -14
- package/.claude/commands/agent-vibes/target-voice.md +0 -26
- package/.claude/commands/agent-vibes/target.md +0 -30
- package/.claude/hooks/learn-manager.sh +0 -492
- package/.claude/hooks/replay-target-audio.sh +0 -95
- package/.claude/hooks-windows/learn-manager.ps1 +0 -241
|
@@ -31,28 +31,55 @@ SCRIPT_PATH="$(readlink -f "${BASH_SOURCE[0]}")"
|
|
|
31
31
|
SCRIPT_DIR="$(dirname "$SCRIPT_PATH")"
|
|
32
32
|
|
|
33
33
|
source "$SCRIPT_DIR/audio-cache-utils.sh"
|
|
34
|
+
source "$SCRIPT_DIR/python-resolver.sh"
|
|
34
35
|
|
|
35
36
|
if [[ -z "$TEXT" ]]; then
|
|
36
37
|
echo "Usage: $0 \"text to speak\" [voice]" >&2
|
|
37
38
|
exit 1
|
|
38
39
|
fi
|
|
39
40
|
|
|
41
|
+
# Distinguish "no Python at all" from "Python present but kokoro module missing"
|
|
42
|
+
# — the old check conflated them, so a Windows box where python3 simply wasn't on
|
|
43
|
+
# PATH wrongly reported "Kokoro not installed" (see python-resolver.sh).
|
|
44
|
+
if [[ -z "$PYTHON_BIN" ]]; then
|
|
45
|
+
echo "❌ No Python 3 interpreter found." >&2
|
|
46
|
+
echo " Kokoro needs Python 3. Install it (Windows: https://python.org, or 'py')," >&2
|
|
47
|
+
echo " or set AGENTVIBES_PYTHON=/path/to/python.exe if it's installed elsewhere." >&2
|
|
48
|
+
exit 2
|
|
49
|
+
fi
|
|
40
50
|
# Check kokoro is installed (use find_spec to avoid slow torch import)
|
|
41
|
-
if !
|
|
42
|
-
echo "❌ Kokoro TTS not installed
|
|
51
|
+
if ! "$PYTHON_BIN" -c "import importlib.util; exit(0 if importlib.util.find_spec('kokoro') else 1)" 2>/dev/null; then
|
|
52
|
+
echo "❌ Kokoro TTS module not installed for: $PYTHON_BIN" >&2
|
|
43
53
|
echo " Install with: ${SCRIPT_DIR}/kokoro-installer.sh" >&2
|
|
44
|
-
echo " Or manually: pip install kokoro
|
|
54
|
+
echo " Or manually: \"$PYTHON_BIN\" -m pip install kokoro soundfile numpy" >&2
|
|
45
55
|
exit 2
|
|
46
56
|
fi
|
|
47
57
|
|
|
48
58
|
# ---------------------------------------------------------------------------
|
|
49
59
|
# Resolve voice
|
|
60
|
+
# Default voice sourced from the generated Provider Catalog (SSOT) when present.
|
|
61
|
+
# FAIL-SAFE: keep the literal fallback for installed-tree skew (mirrors play-tts.sh
|
|
62
|
+
# PLAN_OK) — synth never breaks on a missing catalog.
|
|
50
63
|
DEFAULT_VOICE="af_heart"
|
|
64
|
+
if [[ -f "$SCRIPT_DIR/provider-catalog.sh" ]]; then
|
|
65
|
+
# shellcheck source=/dev/null
|
|
66
|
+
source "$SCRIPT_DIR/provider-catalog.sh" 2>/dev/null || true
|
|
67
|
+
if type catalog_default_voice >/dev/null 2>&1; then
|
|
68
|
+
_CATALOG_DEFAULT="$(catalog_default_voice kokoro 2>/dev/null || true)"
|
|
69
|
+
[[ -n "$_CATALOG_DEFAULT" ]] && DEFAULT_VOICE="$_CATALOG_DEFAULT"
|
|
70
|
+
fi
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
# Synth-time shape check (LENIENT per design §3.3): a voice from a NEWER kokoro
|
|
74
|
+
# model that the shipped catalog doesn't know must still degrade audibly, not
|
|
75
|
+
# brick — so this stays a shape check + loud-warning fallback, NOT membership.
|
|
76
|
+
# One literal, reused at both call sites below.
|
|
77
|
+
KOKORO_SHAPE_RE='^[a-z]{2}_[a-z0-9_]+$'
|
|
51
78
|
|
|
52
79
|
if [[ -n "$VOICE_OVERRIDE" ]]; then
|
|
53
80
|
# Accept kokoro-style voice IDs (2-letter prefix + underscore + name)
|
|
54
81
|
# e.g. af_heart, am_adam, bf_emma, bm_george
|
|
55
|
-
if [[ "$VOICE_OVERRIDE" =~
|
|
82
|
+
if [[ "$VOICE_OVERRIDE" =~ $KOKORO_SHAPE_RE ]]; then
|
|
56
83
|
VOICE="$VOICE_OVERRIDE"
|
|
57
84
|
else
|
|
58
85
|
echo "⚠️ Unrecognized kokoro voice '$VOICE_OVERRIDE', using $DEFAULT_VOICE" >&2
|
|
@@ -62,7 +89,7 @@ else
|
|
|
62
89
|
# Check voice manager
|
|
63
90
|
if [[ -f "$SCRIPT_DIR/voice-manager.sh" ]]; then
|
|
64
91
|
_CONFIGURED="$("$SCRIPT_DIR/voice-manager.sh" get 2>/dev/null || true)"
|
|
65
|
-
if [[ "$_CONFIGURED" =~
|
|
92
|
+
if [[ "$_CONFIGURED" =~ $KOKORO_SHAPE_RE ]]; then
|
|
66
93
|
VOICE="$_CONFIGURED"
|
|
67
94
|
else
|
|
68
95
|
VOICE="$DEFAULT_VOICE"
|
|
@@ -103,7 +130,7 @@ if [[ "${AGENTVIBES_TEST_MODE:-false}" == "true" ]]; then
|
|
|
103
130
|
fi
|
|
104
131
|
|
|
105
132
|
# Run synthesis — output path printed to stdout
|
|
106
|
-
RESULT=$(
|
|
133
|
+
RESULT=$("$PYTHON_BIN" "$SYNTH_SCRIPT" "$TEXT" "$VOICE" "$TEMP_WAV" "$SPEED" 2>&1) || {
|
|
107
134
|
echo "❌ Kokoro synthesis failed: $RESULT" >&2
|
|
108
135
|
exit 3
|
|
109
136
|
}
|
|
@@ -54,6 +54,7 @@ VOICE_OVERRIDE="$2" # Ignored — Soprano has a single voice, kept for provider
|
|
|
54
54
|
SCRIPT_PATH="$(readlink -f "${BASH_SOURCE[0]}")"
|
|
55
55
|
SCRIPT_DIR="$(dirname "$SCRIPT_PATH")"
|
|
56
56
|
source "$SCRIPT_DIR/audio-cache-utils.sh"
|
|
57
|
+
source "$SCRIPT_DIR/python-resolver.sh"
|
|
57
58
|
|
|
58
59
|
SOPRANO_PORT="${SOPRANO_PORT:-7860}"
|
|
59
60
|
SOPRANO_DEVICE="${SOPRANO_DEVICE:-auto}"
|
|
@@ -135,7 +136,7 @@ SYNTH_MODE=""
|
|
|
135
136
|
if check_webui_server; then
|
|
136
137
|
# Gradio WebUI mode — use Python helper for SSE protocol
|
|
137
138
|
SYNTH_MODE="webui"
|
|
138
|
-
|
|
139
|
+
"$PYTHON_BIN" "$SCRIPT_DIR/soprano-gradio-synth.py" "$TEXT" "$TEMP_FILE" "$SOPRANO_PORT" 2>/dev/null
|
|
139
140
|
elif check_api_server; then
|
|
140
141
|
# OpenAI-compatible API mode — direct curl
|
|
141
142
|
SYNTH_MODE="api"
|
|
@@ -151,7 +152,7 @@ fi
|
|
|
151
152
|
|
|
152
153
|
if [[ ! -f "$TEMP_FILE" ]] || [[ ! -s "$TEMP_FILE" ]]; then
|
|
153
154
|
echo "❌ Failed to synthesize speech with Soprano ($SYNTH_MODE mode)"
|
|
154
|
-
[[ "$SYNTH_MODE" == "webui" ]] && echo " Try:
|
|
155
|
+
[[ "$SYNTH_MODE" == "webui" ]] && echo " Try: $PYTHON_BIN $SCRIPT_DIR/soprano-gradio-synth.py \"test\" /tmp/test.wav $SOPRANO_PORT"
|
|
155
156
|
exit 4
|
|
156
157
|
fi
|
|
157
158
|
|
|
@@ -34,18 +34,60 @@ fi
|
|
|
34
34
|
# Default to "default" so remote always has an LLM key to look up
|
|
35
35
|
LLM_NAME="${LLM_NAME:-default}"
|
|
36
36
|
|
|
37
|
-
#
|
|
38
|
-
|
|
37
|
+
# Music-only preview mode: AGENTVIBES_MUSIC_ONLY=<track.mp3> tells the receiver to
|
|
38
|
+
# play a standalone background-music track (no speech). Used by the Music tab's
|
|
39
|
+
# remote preview so a track can be auditioned on the receiver. In this mode empty
|
|
40
|
+
# TEXT is allowed and the track name is forwarded as the "music" field with
|
|
41
|
+
# kind=music; the receiver resolves the file from its own ~/.claude/audio/tracks/.
|
|
42
|
+
PAYLOAD_KIND="speak"
|
|
43
|
+
MUSIC_ONLY_TRACK=""
|
|
44
|
+
if [[ -n "${AGENTVIBES_MUSIC_STOP:-}" ]]; then
|
|
45
|
+
# Stop whatever music preview is currently playing on the receiver.
|
|
46
|
+
PAYLOAD_KIND="music-stop"
|
|
47
|
+
elif [[ -n "${AGENTVIBES_MUSIC_ONLY:-}" ]]; then
|
|
48
|
+
# Only a bare .mp3 filename is allowed (no path separators, no leading dash) —
|
|
49
|
+
# the receiver resolves it against its own tracks dir, so reject path-like input.
|
|
50
|
+
# Spaces ARE allowed (many track names contain them, e.g. "Late Night Hip Hop.mp3");
|
|
51
|
+
# the name travels as a jq --arg-escaped JSON string, so a space is not injectable.
|
|
52
|
+
if [[ "$AGENTVIBES_MUSIC_ONLY" =~ ^[A-Za-z0-9._][A-Za-z0-9._\ -]*\.mp3$ ]]; then
|
|
53
|
+
PAYLOAD_KIND="music"
|
|
54
|
+
MUSIC_ONLY_TRACK="$AGENTVIBES_MUSIC_ONLY"
|
|
55
|
+
else
|
|
56
|
+
echo "Invalid music track name: $AGENTVIBES_MUSIC_ONLY" >&2
|
|
57
|
+
exit 1
|
|
58
|
+
fi
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
# Validate required input (music / music-stop modes carry no speech text)
|
|
62
|
+
if [[ "$PAYLOAD_KIND" == "music" || "$PAYLOAD_KIND" == "music-stop" ]]; then
|
|
63
|
+
TEXT="" # no speech in a music preview / stop
|
|
64
|
+
elif [[ -z "$TEXT" ]]; then
|
|
39
65
|
echo "Usage: $0 <text> [voice] [agent_name]" >&2
|
|
40
66
|
exit 1
|
|
41
67
|
fi
|
|
42
68
|
|
|
43
69
|
# Get script directory and project root
|
|
44
70
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
71
|
+
source "$SCRIPT_DIR/python-resolver.sh"
|
|
72
|
+
source "$SCRIPT_DIR/session-id.sh"
|
|
45
73
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
46
74
|
|
|
47
|
-
#
|
|
48
|
-
|
|
75
|
+
# Canonical routing session id. The receiver multiplexes forwarded TTS by this
|
|
76
|
+
# id, so it MUST derive from CLAUDE_PROJECT_DIR (the real user project injected
|
|
77
|
+
# via --project-dir), NOT basename(PROJECT_ROOT) — for a global install that
|
|
78
|
+
# basename collapses to the HOME dir name and merges every project into one
|
|
79
|
+
# session. Fallback (CLAUDE_PROJECT_DIR unset) is PROJECT_ROOT, preserving the
|
|
80
|
+
# legacy value for the no-project case.
|
|
81
|
+
SESSION_ID="$(av_session_id "$PROJECT_ROOT")"
|
|
82
|
+
# PROJECT_NAME is kept for receiver back-compat (the receiver reads "project");
|
|
83
|
+
# set it to the SAME corrected session id so both fields agree.
|
|
84
|
+
PROJECT_NAME="$SESSION_ID"
|
|
85
|
+
# Absolute path, forwarded so the receiver can show/learn the real remote folder.
|
|
86
|
+
if [[ -n "${CLAUDE_PROJECT_DIR:-}" ]]; then
|
|
87
|
+
PROJECT_PATH="$CLAUDE_PROJECT_DIR"
|
|
88
|
+
else
|
|
89
|
+
PROJECT_PATH="$PROJECT_ROOT"
|
|
90
|
+
fi
|
|
49
91
|
|
|
50
92
|
# ---------------------------------------------------------------------------
|
|
51
93
|
# Get SSH connection details from config
|
|
@@ -66,10 +108,10 @@ fi
|
|
|
66
108
|
# Priority 2: ~/.agentvibes/transport-config.json (ssh-remote section)
|
|
67
109
|
if [[ -z "$SSH_HOST" ]]; then
|
|
68
110
|
_TRANSPORT_CFG="$HOME/.agentvibes/transport-config.json"
|
|
69
|
-
if [[ -f "$_TRANSPORT_CFG" ]] &&
|
|
70
|
-
SSH_HOST=$(
|
|
71
|
-
SSH_KEY=$(
|
|
72
|
-
SSH_PORT=$(
|
|
111
|
+
if [[ -f "$_TRANSPORT_CFG" ]] && [[ -n "$PYTHON_BIN" ]]; then
|
|
112
|
+
SSH_HOST=$("$PYTHON_BIN" -c "import json,sys; d=json.load(open('$_TRANSPORT_CFG')); p=d.get('ssh-remote',{}); print(p.get('host',''))" 2>/dev/null || echo "")
|
|
113
|
+
SSH_KEY=$("$PYTHON_BIN" -c "import json,sys; d=json.load(open('$_TRANSPORT_CFG')); p=d.get('ssh-remote',{}); print(p.get('sshKey',''))" 2>/dev/null || echo "")
|
|
114
|
+
SSH_PORT=$("$PYTHON_BIN" -c "import json,sys; d=json.load(open('$_TRANSPORT_CFG')); p=d.get('ssh-remote',{}); print(p.get('port',''))" 2>/dev/null || echo "")
|
|
73
115
|
fi
|
|
74
116
|
fi
|
|
75
117
|
|
|
@@ -77,8 +119,8 @@ fi
|
|
|
77
119
|
# (config keyed by LLM name rather than 'ssh-remote', e.g. 'claude-code')
|
|
78
120
|
if [[ -z "$SSH_HOST" ]]; then
|
|
79
121
|
_TRANSPORT_CFG="$HOME/.agentvibes/transport-config.json"
|
|
80
|
-
if [[ -f "$_TRANSPORT_CFG" ]] &&
|
|
81
|
-
_remote_data=$(
|
|
122
|
+
if [[ -f "$_TRANSPORT_CFG" ]] && [[ -n "$PYTHON_BIN" ]]; then
|
|
123
|
+
_remote_data=$("$PYTHON_BIN" -c "
|
|
82
124
|
import json, sys
|
|
83
125
|
try:
|
|
84
126
|
d = json.load(open('$_TRANSPORT_CFG'))
|
|
@@ -129,10 +171,11 @@ if [[ -n "$SSH_PORT" ]] && [[ ! "$SSH_PORT" =~ ^[0-9]+$ ]]; then
|
|
|
129
171
|
fi
|
|
130
172
|
|
|
131
173
|
# SECURITY: Validate VOICE
|
|
132
|
-
# Allow letters, digits, underscore, hyphen, period, colon (for :: multi-speaker separator),
|
|
174
|
+
# Allow letters, digits, underscore, hyphen, period, colon (for :: multi-speaker separator),
|
|
175
|
+
# slash, and SPACES (Windows SAPI voice names like "Microsoft David Desktop").
|
|
133
176
|
# Voice is passed to the remote via base64-encoded JSON (jq --arg safely escapes it),
|
|
134
|
-
# so shell metacharacters are the only real risk.
|
|
135
|
-
if [[ ! "$VOICE" =~ ^[a-zA-Z0-9_
|
|
177
|
+
# so shell metacharacters are the only real risk — a space is not injectable.
|
|
178
|
+
if [[ ! "$VOICE" =~ ^[a-zA-Z0-9_.:\/\ -]+$ ]]; then
|
|
136
179
|
echo "Invalid voice format: $VOICE" >&2
|
|
137
180
|
exit 1
|
|
138
181
|
fi
|
|
@@ -316,9 +359,7 @@ fi
|
|
|
316
359
|
|
|
317
360
|
# ---------------------------------------------------------------------------
|
|
318
361
|
# Resolve LANGUAGE to forward (map §D 'language | Forward on SSH', R18).
|
|
319
|
-
#
|
|
320
|
-
# learning-mode target (tts-target-language.txt when tts-learn-mode.txt=="ON")
|
|
321
|
-
# > tts-language.txt.
|
|
362
|
+
# Source: tts-language.txt (set by language-manager.sh).
|
|
322
363
|
# Forwarded as a DEFAULT only — the receiver stays authoritative and its own
|
|
323
364
|
# language config (if any) wins. language-manager.sh stores tts-language.txt at
|
|
324
365
|
# the .claude/ root; the resolver contract tests use the .claude/config/ subdir,
|
|
@@ -326,14 +367,7 @@ fi
|
|
|
326
367
|
# ---------------------------------------------------------------------------
|
|
327
368
|
LANGUAGE=""
|
|
328
369
|
for _cdir in "$_RESOLVE_PROJECT_DIR/.claude" "$HOME/.claude"; do
|
|
329
|
-
#
|
|
330
|
-
if [[ -z "$LANGUAGE" && -f "$_cdir/tts-learn-mode.txt" ]]; then
|
|
331
|
-
if [[ "$(cat "$_cdir/tts-learn-mode.txt" 2>/dev/null || true)" == "ON" ]]; then
|
|
332
|
-
[[ -f "$_cdir/tts-target-language.txt" ]] && \
|
|
333
|
-
LANGUAGE=$(cat "$_cdir/tts-target-language.txt" 2>/dev/null || true)
|
|
334
|
-
fi
|
|
335
|
-
fi
|
|
336
|
-
# Priority 2: tts-language.txt (root or config/ subdir).
|
|
370
|
+
# tts-language.txt (root or config/ subdir).
|
|
337
371
|
if [[ -z "$LANGUAGE" && -f "$_cdir/tts-language.txt" ]]; then
|
|
338
372
|
LANGUAGE=$(cat "$_cdir/tts-language.txt" 2>/dev/null || true)
|
|
339
373
|
fi
|
|
@@ -364,25 +398,36 @@ build_json_payload() {
|
|
|
364
398
|
--arg effects "$SOX_EFFECTS" \
|
|
365
399
|
--arg music "$BG_FILE" \
|
|
366
400
|
--arg volume "$BG_VOLUME" \
|
|
401
|
+
--arg session "$SESSION_ID" \
|
|
367
402
|
--arg project "$PROJECT_NAME" \
|
|
403
|
+
--arg projectPath "$PROJECT_PATH" \
|
|
368
404
|
--arg pretext "$PRETEXT" \
|
|
369
405
|
--arg speed "$SPEED" \
|
|
370
406
|
--arg provider "$PROVIDER" \
|
|
371
407
|
--arg llm "$LLM_NAME" \
|
|
372
408
|
--arg mute "$MUTE" \
|
|
373
409
|
--arg language "$LANGUAGE" \
|
|
374
|
-
|
|
410
|
+
--arg kind "$PAYLOAD_KIND" \
|
|
411
|
+
'{text: $text, voice: $voice, effects: $effects, music: $music, volume: $volume, session: $session, project: $project, projectPath: $projectPath, pretext: $pretext, speed: $speed, provider: $provider, llm: $llm, mute: $mute, language: $language, kind: $kind}'
|
|
375
412
|
else
|
|
376
413
|
# Manual JSON — escape backslashes, quotes, control chars
|
|
377
414
|
local escaped_text
|
|
378
415
|
escaped_text=$(printf '%s' "$TEXT" | sed 's/\\/\\\\/g; s/"/\\"/g; s/\t/\\t/g' | tr '\n' ' ' | sed 's/\r//g')
|
|
379
416
|
local escaped_pretext
|
|
380
417
|
escaped_pretext=$(printf '%s' "$PRETEXT" | sed 's/\\/\\\\/g; s/"/\\"/g')
|
|
381
|
-
|
|
382
|
-
|
|
418
|
+
local escaped_project_path
|
|
419
|
+
escaped_project_path=$(printf '%s' "$PROJECT_PATH" | sed 's/\\/\\\\/g; s/"/\\"/g')
|
|
420
|
+
printf '{"text":"%s","voice":"%s","effects":"%s","music":"%s","volume":"%s","session":"%s","project":"%s","projectPath":"%s","pretext":"%s","speed":"%s","provider":"%s","llm":"%s","mute":"%s","language":"%s","kind":"%s"}' \
|
|
421
|
+
"$escaped_text" "$VOICE" "$SOX_EFFECTS" "$BG_FILE" "$BG_VOLUME" "$SESSION_ID" "$PROJECT_NAME" "$escaped_project_path" "$escaped_pretext" "$SPEED" "$PROVIDER" "$LLM_NAME" "$MUTE" "$LANGUAGE" "$PAYLOAD_KIND"
|
|
383
422
|
fi
|
|
384
423
|
}
|
|
385
424
|
|
|
425
|
+
# Music-only preview: force the track as the "music" field (overriding any
|
|
426
|
+
# per-LLM/agent background-music config) so the receiver plays exactly this track.
|
|
427
|
+
if [[ "$PAYLOAD_KIND" == "music" ]]; then
|
|
428
|
+
BG_FILE="$MUSIC_ONLY_TRACK"
|
|
429
|
+
fi
|
|
430
|
+
|
|
386
431
|
JSON_PAYLOAD=$(build_json_payload)
|
|
387
432
|
|
|
388
433
|
# SECURITY: Base64-encode entire payload — safe for SSH transport
|
|
@@ -68,6 +68,7 @@ VOICE_OVERRIDE="$2" # Not used for termux-ssh, but kept for interface compatibi
|
|
|
68
68
|
|
|
69
69
|
# Script directory
|
|
70
70
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
71
|
+
source "$SCRIPT_DIR/python-resolver.sh"
|
|
71
72
|
|
|
72
73
|
# @function get_ssh_host
|
|
73
74
|
# @intent Determine SSH host alias for Android device
|
|
@@ -119,10 +120,10 @@ SSH_KEY=""
|
|
|
119
120
|
SSH_PORT=""
|
|
120
121
|
|
|
121
122
|
_TRANSPORT_CFG="$HOME/.agentvibes/transport-config.json"
|
|
122
|
-
if [[ -f "$_TRANSPORT_CFG" ]] &&
|
|
123
|
-
_CFG_HOST=$(
|
|
124
|
-
SSH_KEY=$(
|
|
125
|
-
SSH_PORT=$(
|
|
123
|
+
if [[ -f "$_TRANSPORT_CFG" ]] && [[ -n "$PYTHON_BIN" ]]; then
|
|
124
|
+
_CFG_HOST=$("$PYTHON_BIN" -c "import json; d=json.load(open('$_TRANSPORT_CFG')); p=d.get('termux-ssh',{}); print(p.get('host',''))" 2>/dev/null || echo "")
|
|
125
|
+
SSH_KEY=$("$PYTHON_BIN" -c "import json; d=json.load(open('$_TRANSPORT_CFG')); p=d.get('termux-ssh',{}); print(p.get('sshKey',''))" 2>/dev/null || echo "")
|
|
126
|
+
SSH_PORT=$("$PYTHON_BIN" -c "import json; d=json.load(open('$_TRANSPORT_CFG')); p=d.get('termux-ssh',{}); print(p.get('port',''))" 2>/dev/null || echo "")
|
|
126
127
|
fi
|
|
127
128
|
|
|
128
129
|
if [[ -n "${_CFG_HOST:-}" ]]; then
|
|
@@ -31,13 +31,13 @@
|
|
|
31
31
|
#
|
|
32
32
|
# ---
|
|
33
33
|
#
|
|
34
|
-
# @fileoverview TTS Provider Router with Translation
|
|
34
|
+
# @fileoverview TTS Provider Router with Translation Support
|
|
35
35
|
# @context Routes TTS requests to active provider (Piper or macOS) with optional translation
|
|
36
|
-
# @architecture Provider abstraction layer - single entry point for all TTS, handles translation
|
|
37
|
-
# @dependencies provider-manager.sh, play-tts-piper.sh, translator.py, translate-manager.sh
|
|
36
|
+
# @architecture Provider abstraction layer - single entry point for all TTS, handles translation
|
|
37
|
+
# @dependencies provider-manager.sh, play-tts-piper.sh, translator.py, translate-manager.sh
|
|
38
38
|
# @entrypoints Called by hooks, slash commands, personality-manager.sh, and all TTS features
|
|
39
39
|
# @patterns Provider pattern - delegates to provider-specific implementations, auto-detects provider from voice name
|
|
40
|
-
# @related provider-manager.sh, play-tts-piper.sh,
|
|
40
|
+
# @related provider-manager.sh, play-tts-piper.sh, translate-manager.sh
|
|
41
41
|
#
|
|
42
42
|
# **IMPORTANT: This script should be called inline (NOT in background) in Bash tool**
|
|
43
43
|
# Wait for TTS playback to complete before continuing.
|
|
@@ -113,6 +113,12 @@ elif [[ -f "$GLOBAL_MUTE_FILE" ]]; then
|
|
|
113
113
|
exit 0
|
|
114
114
|
fi
|
|
115
115
|
|
|
116
|
+
# Resolve a working Python interpreter once (translator and transport config
|
|
117
|
+
# both need it). Sourced AFTER the mute exits so muted calls
|
|
118
|
+
# pay nothing. Windows git-bash frequently has no python3 on PATH — see
|
|
119
|
+
# python-resolver.sh; $PYTHON_BIN is empty when none is usable.
|
|
120
|
+
source "$SCRIPT_DIR/python-resolver.sh"
|
|
121
|
+
|
|
116
122
|
# Parse named flags (e.g. --llm) before positional arguments.
|
|
117
123
|
# This allows callers to pass: play-tts.sh --llm claude-code "text to speak"
|
|
118
124
|
# Named args are extracted; remaining positional args are shifted into $1/$2/$3.
|
|
@@ -241,6 +247,53 @@ if [[ -z "$_PRETEXT" ]]; then
|
|
|
241
247
|
fi
|
|
242
248
|
done
|
|
243
249
|
fi
|
|
250
|
+
# Dynamic session self-ID: a per-LLM PRETEXT containing the {{session}} token
|
|
251
|
+
# expands to "<LLM> on <Project> in <Terminal>" so multi-session users can tell
|
|
252
|
+
# which tab just spoke. Announced once per session (best-effort — see the key
|
|
253
|
+
# note below); on later utterances the token is dropped so it doesn't prefix
|
|
254
|
+
# every line. Opt-in — only fires when the pretext actually contains the token.
|
|
255
|
+
if [[ "$_PRETEXT" == *"{{session}}"* ]]; then
|
|
256
|
+
# Session key. There is no true Claude-session id available to a plain Bash
|
|
257
|
+
# command, so this is best-effort: a terminal-session id if the emulator
|
|
258
|
+
# exports one (stable for the tab's life), else the parent PID. Worst case the
|
|
259
|
+
# self-ID repeats or is skipped — cosmetic, and only when a pretext opts in.
|
|
260
|
+
_SID_RAW="${WT_SESSION:-${TERM_SESSION_ID:-${TMUX:-$PPID}}}"
|
|
261
|
+
_SID_KEY="$(printf '%s' "${LLM_PROVIDER:-}|$(basename "${CLAUDE_PROJECT_DIR:-$PROJECT_ROOT}")|$_SID_RAW" | tr -c 'A-Za-z0-9' '_')"
|
|
262
|
+
# Announce-marker dir: an OWNED subdir, per the project's secure-temp rule.
|
|
263
|
+
# If the dir exists but isn't ours (pre-created symlink attack on a shared
|
|
264
|
+
# host), skip the marker entirely and just strip the token — never chmod or
|
|
265
|
+
# write through someone else's path.
|
|
266
|
+
_ANN_BASE="${XDG_RUNTIME_DIR:-/tmp/agentvibes-$(id -u 2>/dev/null || echo 0)}"
|
|
267
|
+
_ANN_DIR="$_ANN_BASE/agentvibes-session"
|
|
268
|
+
_ANN_OK=0
|
|
269
|
+
if mkdir -p "$_ANN_DIR" 2>/dev/null && chmod 700 "$_ANN_DIR" 2>/dev/null; then
|
|
270
|
+
_ANN_OWNER="$(stat -c '%u' "$_ANN_DIR" 2>/dev/null || stat -f '%u' "$_ANN_DIR" 2>/dev/null || echo '')"
|
|
271
|
+
[[ -z "$_ANN_OWNER" || "$_ANN_OWNER" == "$(id -u 2>/dev/null || echo 0)" ]] && _ANN_OK=1
|
|
272
|
+
fi
|
|
273
|
+
_ANN_FILE="$_ANN_DIR/announced-$_SID_KEY"
|
|
274
|
+
# First utterance → replacement is the self-ID; later utterances → empty (drop).
|
|
275
|
+
if [[ "$_ANN_OK" == "1" && -f "$_ANN_FILE" ]]; then
|
|
276
|
+
_SESSION_ID=""
|
|
277
|
+
else
|
|
278
|
+
_SESSION_ID="$(bash "$SCRIPT_DIR/agentvibes-session-id.sh" "${LLM_PROVIDER:-claude-code}" "${CLAUDE_PROJECT_DIR:-$PROJECT_ROOT}" 2>/dev/null || true)"
|
|
279
|
+
if [[ "$_ANN_OK" == "1" ]]; then
|
|
280
|
+
touch "$_ANN_FILE" 2>/dev/null || true
|
|
281
|
+
# Bound growth: drop announce-markers older than a day (find is optional).
|
|
282
|
+
find "$_ANN_DIR" -maxdepth 1 -name 'announced-*' -mtime +1 -delete 2>/dev/null || true
|
|
283
|
+
fi
|
|
284
|
+
fi
|
|
285
|
+
if [[ -n "$_SESSION_ID" ]]; then
|
|
286
|
+
_PRETEXT="${_PRETEXT//"{{session}}"/$_SESSION_ID}"
|
|
287
|
+
else
|
|
288
|
+
# Empty replacement: also swallow one adjacent ", " so "Hey {{session}}, ready"
|
|
289
|
+
# becomes "Hey ready", not "Hey , ready"; then drop any bare token.
|
|
290
|
+
_PRETEXT="${_PRETEXT//"{{session}}, "/}"
|
|
291
|
+
_PRETEXT="${_PRETEXT//", {{session}}"/}"
|
|
292
|
+
_PRETEXT="${_PRETEXT//"{{session}}"/}"
|
|
293
|
+
fi
|
|
294
|
+
# Final tidy: collapse doubled/edge commas and runs of whitespace.
|
|
295
|
+
_PRETEXT="$(printf '%s' "$_PRETEXT" | sed -E 's/,[[:space:]]*,/,/g; s/^[[:space:]]*,[[:space:]]*//; s/[[:space:]]*,[[:space:]]*$//; s/[[:space:]]{2,}/ /g; s/^[[:space:]]+//; s/[[:space:]]+$//')"
|
|
296
|
+
fi
|
|
244
297
|
if [[ -n "$_PRETEXT" ]]; then
|
|
245
298
|
TEXT="${_PRETEXT}, ${TEXT}"
|
|
246
299
|
fi
|
|
@@ -329,7 +382,12 @@ case "$ACTIVE_PROVIDER" in
|
|
|
329
382
|
# Transport — keep it. The receiver's audio-effects.cfg picks the engine.
|
|
330
383
|
;;
|
|
331
384
|
*)
|
|
332
|
-
if [[
|
|
385
|
+
if [[ "$VOICE_OVERRIDE" =~ ^(af|am|bf|bm|jf|jm|kf|km|zf|zm|ff|fm|hf|hm|if|im|pf|pm|ef|em|nf|nm)_[a-zA-Z0-9]+$ ]]; then
|
|
386
|
+
# Kokoro-shaped voice override (e.g. af_heart) must win over the LLM row's
|
|
387
|
+
# engine column, else it is synthesized as a Piper voice, 404s downloading
|
|
388
|
+
# a model that doesn't exist, and stays silent.
|
|
389
|
+
ACTIVE_PROVIDER="kokoro"
|
|
390
|
+
elif [[ -n "$_LLM_ENGINE" ]]; then
|
|
333
391
|
ACTIVE_PROVIDER="$_LLM_ENGINE"
|
|
334
392
|
fi
|
|
335
393
|
;;
|
|
@@ -351,8 +409,8 @@ fi
|
|
|
351
409
|
_TRANSPORT_CFG="$HOME/.agentvibes/transport-config.json"
|
|
352
410
|
if [[ "$ACTIVE_PROVIDER" != "ssh-remote" && "$ACTIVE_PROVIDER" != "agentvibes-receiver" && "$ACTIVE_PROVIDER" != "termux-ssh" ]] \
|
|
353
411
|
&& [[ -n "$LLM_PROVIDER" && "$LLM_PROVIDER" != "default" ]] \
|
|
354
|
-
&& [[ -f "$_TRANSPORT_CFG" ]] &&
|
|
355
|
-
_LLM_SSH_MODE=$(AGENTVIBES_CFG="$_TRANSPORT_CFG" AGENTVIBES_KEY="$LLM_PROVIDER"
|
|
412
|
+
&& [[ -f "$_TRANSPORT_CFG" ]] && [[ -n "$PYTHON_BIN" ]]; then
|
|
413
|
+
_LLM_SSH_MODE=$(AGENTVIBES_CFG="$_TRANSPORT_CFG" AGENTVIBES_KEY="$LLM_PROVIDER" "$PYTHON_BIN" - <<'PYEOF'
|
|
356
414
|
import json, os, sys
|
|
357
415
|
try:
|
|
358
416
|
d = json.load(open(os.environ['AGENTVIBES_CFG'], encoding='utf-8'))
|
|
@@ -363,7 +421,7 @@ PYEOF
|
|
|
363
421
|
)
|
|
364
422
|
if [[ "$_LLM_SSH_MODE" == "remote" ]]; then
|
|
365
423
|
# Redirect this LLM's audio through ssh-remote using its own SSH config
|
|
366
|
-
_llm_remote_data=$(AGENTVIBES_CFG="$_TRANSPORT_CFG" AGENTVIBES_KEY="$LLM_PROVIDER"
|
|
424
|
+
_llm_remote_data=$(AGENTVIBES_CFG="$_TRANSPORT_CFG" AGENTVIBES_KEY="$LLM_PROVIDER" "$PYTHON_BIN" - <<'PYEOF'
|
|
367
425
|
import json, os, sys
|
|
368
426
|
try:
|
|
369
427
|
d = json.load(open(os.environ['AGENTVIBES_CFG'], encoding='utf-8'))
|
|
@@ -513,12 +571,17 @@ speak_text() {
|
|
|
513
571
|
termux-ssh)
|
|
514
572
|
bash "$SCRIPT_DIR/play-tts-termux-ssh.sh" "$text" "$voice"
|
|
515
573
|
;;
|
|
516
|
-
ssh-remote)
|
|
574
|
+
ssh-remote|agentvibes-receiver)
|
|
575
|
+
# Both route through the base64-JSON sender that the modern receiver
|
|
576
|
+
# (templates/agentvibes-receiver.ps1 / .sh, installed as play-remote.*)
|
|
577
|
+
# understands. The old voiceless-connections sender spoke a 3-positional-arg
|
|
578
|
+
# protocol for a legacy ~/.agentvibes/play-remote.sh receiver that no current
|
|
579
|
+
# receiver install produces, so it delivered a non-base64 command the modern
|
|
580
|
+
# receiver rejects ("Payload must be base64-encoded"). play-tts-ssh-remote.sh
|
|
581
|
+
# resolves the host from any transport-config mode=remote entry (Priority 2b),
|
|
582
|
+
# so the agentvibes-receiver section is found without an ssh-remote section.
|
|
517
583
|
bash "$SCRIPT_DIR/play-tts-ssh-remote.sh" "$text" "$voice" "" "${profile_file:-}"
|
|
518
584
|
;;
|
|
519
|
-
agentvibes-receiver)
|
|
520
|
-
bash "$SCRIPT_DIR/play-tts-agentvibes-receiver-for-voiceless-connections.sh" "$text" "$voice"
|
|
521
|
-
;;
|
|
522
585
|
*)
|
|
523
586
|
echo "❌ Unknown provider: $provider" >&2
|
|
524
587
|
return 1
|
|
@@ -526,50 +589,8 @@ speak_text() {
|
|
|
526
589
|
esac
|
|
527
590
|
}
|
|
528
591
|
|
|
529
|
-
# Note:
|
|
530
|
-
#
|
|
531
|
-
|
|
532
|
-
# @function handle_learning_mode
|
|
533
|
-
# @intent Speak in both main language and target language for learning
|
|
534
|
-
# @why Issue #51 - Auto-translate and speak twice for immersive language learning
|
|
535
|
-
# @returns 0 if learning mode handled, 1 if not in learning mode
|
|
536
|
-
handle_learning_mode() {
|
|
537
|
-
# Source learn-manager for learning mode functions
|
|
538
|
-
source "$SCRIPT_DIR/learn-manager.sh" 2>/dev/null || return 1
|
|
539
|
-
|
|
540
|
-
# Check if learning mode is enabled
|
|
541
|
-
if ! is_learn_mode_enabled 2>/dev/null; then
|
|
542
|
-
return 1
|
|
543
|
-
fi
|
|
544
|
-
|
|
545
|
-
local target_lang
|
|
546
|
-
target_lang=$(get_target_language 2>/dev/null || echo "")
|
|
547
|
-
local target_voice
|
|
548
|
-
target_voice=$(get_target_voice 2>/dev/null || echo "")
|
|
549
|
-
|
|
550
|
-
# Need both target language and voice for learning mode
|
|
551
|
-
if [[ -z "$target_lang" ]] || [[ -z "$target_voice" ]]; then
|
|
552
|
-
return 1
|
|
553
|
-
fi
|
|
554
|
-
|
|
555
|
-
# 1. Speak in main language (current voice)
|
|
556
|
-
speak_text "$TEXT" "$VOICE_OVERRIDE" "$ACTIVE_PROVIDER"
|
|
557
|
-
|
|
558
|
-
# 2. Auto-translate to target language
|
|
559
|
-
local translated
|
|
560
|
-
# SECURITY: Add timeout to prevent hanging (#134)
|
|
561
|
-
translated=$(timeout 5 python3 "$SCRIPT_DIR/translator.py" "$TEXT" "$target_lang" 2>/dev/null) || translated="$TEXT"
|
|
562
|
-
|
|
563
|
-
# Small pause between languages
|
|
564
|
-
sleep 0.5
|
|
565
|
-
|
|
566
|
-
# 3. Speak translated text with target voice
|
|
567
|
-
local target_provider
|
|
568
|
-
target_provider=$(detect_voice_provider "$target_voice")
|
|
569
|
-
speak_text "$translated" "$target_voice" "$target_provider"
|
|
570
|
-
|
|
571
|
-
return 0
|
|
572
|
-
}
|
|
592
|
+
# Note: translate-manager.sh is sourced inside its handler function to avoid
|
|
593
|
+
# triggering its main handler
|
|
573
594
|
|
|
574
595
|
# @function handle_translation_mode
|
|
575
596
|
# @intent Translate and speak in target language (non-learning mode)
|
|
@@ -594,7 +615,7 @@ handle_translation_mode() {
|
|
|
594
615
|
# Translate text
|
|
595
616
|
local translated
|
|
596
617
|
# SECURITY: Add timeout to prevent hanging (#134)
|
|
597
|
-
translated=$(timeout 5 python3 "$SCRIPT_DIR/translator.py" "$TEXT" "$translate_to" 2>/dev/null) || translated="$TEXT"
|
|
618
|
+
translated=$(timeout 5 "${PYTHON_BIN:-python3}" "$SCRIPT_DIR/translator.py" "$TEXT" "$translate_to" 2>/dev/null) || translated="$TEXT"
|
|
598
619
|
|
|
599
620
|
# Get voice for target language if no override specified
|
|
600
621
|
local voice_to_use="$VOICE_OVERRIDE"
|
|
@@ -615,14 +636,8 @@ handle_translation_mode() {
|
|
|
615
636
|
}
|
|
616
637
|
|
|
617
638
|
# Mode priority:
|
|
618
|
-
# 1.
|
|
619
|
-
# 2.
|
|
620
|
-
# 3. Normal mode (speaks as-is)
|
|
621
|
-
|
|
622
|
-
# Try learning mode first (Issue #51)
|
|
623
|
-
if handle_learning_mode; then
|
|
624
|
-
exit 0
|
|
625
|
-
fi
|
|
639
|
+
# 1. Translation mode (speaks translated only)
|
|
640
|
+
# 2. Normal mode (speaks as-is)
|
|
626
641
|
|
|
627
642
|
# Try translation mode (Issue #50)
|
|
628
643
|
if handle_translation_mode; then
|
|
@@ -653,12 +668,14 @@ case "$ACTIVE_PROVIDER" in
|
|
|
653
668
|
termux-ssh)
|
|
654
669
|
exec bash "$SCRIPT_DIR/play-tts-termux-ssh.sh" "$TEXT" "$VOICE_OVERRIDE"
|
|
655
670
|
;;
|
|
656
|
-
ssh-remote)
|
|
671
|
+
ssh-remote|agentvibes-receiver)
|
|
672
|
+
# Both route through the base64-JSON sender the modern receiver understands.
|
|
673
|
+
# The legacy voiceless-connections sender spoke a 3-arg protocol for an old
|
|
674
|
+
# ~/.agentvibes/play-remote.sh receiver that no current install produces, so it
|
|
675
|
+
# delivered a non-base64 command the modern receiver rejects with
|
|
676
|
+
# "Payload must be base64-encoded".
|
|
657
677
|
exec bash "$SCRIPT_DIR/play-tts-ssh-remote.sh" "$TEXT" "$VOICE_OVERRIDE" "" "${AGENT_PROFILE_FILE:-}"
|
|
658
678
|
;;
|
|
659
|
-
agentvibes-receiver)
|
|
660
|
-
exec bash "$SCRIPT_DIR/play-tts-agentvibes-receiver-for-voiceless-connections.sh" "$TEXT" "$VOICE_OVERRIDE"
|
|
661
|
-
;;
|
|
662
679
|
*)
|
|
663
680
|
echo "❌ Unknown provider: $ACTIVE_PROVIDER" >&2
|
|
664
681
|
echo " Run: /agent-vibes:provider list" >&2
|