agentvibes 5.12.0 → 5.13.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/install-manifest.json +111 -91
- package/.claude/commands/agent-vibes/commands.json +0 -20
- package/.claude/commands/agent-vibes/unmute.md +6 -2
- package/.claude/config/audio-effects.cfg +6 -6
- 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 +37 -29
- package/.claude/hooks/play-tts-termux-ssh.sh +5 -4
- package/.claude/hooks/play-tts.sh +66 -61
- 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/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/tts-watcher.ps1 +33 -12
- package/.claude/hooks-windows/voice-manager-windows.ps1 +49 -0
- package/.mcp.json +0 -7
- package/README.md +12 -3
- package/RELEASE_NOTES.md +61 -0
- package/mcp-server/server.py +146 -49
- package/mcp-server/test_mcp_correctness.py +20 -2
- package/mcp-server/test_windows_script_parity.py +0 -2
- package/package.json +2 -2
- package/src/cli/list-voices.js +218 -114
- package/src/console/bling.js +71 -0
- package/src/console/music-preview.js +79 -0
- package/src/console/tabs/music-tab.js +16 -39
- package/src/console/tabs/settings-tab.js +195 -13
- package/src/console/tabs/setup-tab.js +9 -34
- package/src/console/tabs/voices-tab.js +83 -14
- package/src/console/widgets/track-picker.js +82 -0
- package/src/installer.js +136 -18
- 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/provider-validator.js +62 -12
- 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
|
@@ -18,6 +18,11 @@
|
|
|
18
18
|
set -euo pipefail
|
|
19
19
|
|
|
20
20
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
21
|
+
source "$SCRIPT_DIR/python-resolver.sh"
|
|
22
|
+
# Party speech is best-effort and this hook runs under `set -e`; with no Python
|
|
23
|
+
# the `"$PYTHON_BIN" …` calls below would be `: command not found` (127) and abort
|
|
24
|
+
# the hook, surfacing an error on every Agent call. Degrade silently instead.
|
|
25
|
+
[[ -n "$PYTHON_BIN" ]] || exit 0
|
|
21
26
|
LOCK_FILE="/tmp/agentvibes-party-queue.lock"
|
|
22
27
|
DEBUG_LOG="/tmp/agentvibes-party-debug.log"
|
|
23
28
|
|
|
@@ -33,7 +38,7 @@ _dbg "fired (stdin ${#raw} bytes)"
|
|
|
33
38
|
|
|
34
39
|
# --- Parse all needed fields in one python3 call (fixes M5: 3x subprocess, echo safety) ---
|
|
35
40
|
# Outputs: TOOL_NAME|DISPLAY_NAME|RESPONSE_TEXT (newlines in response encoded as \n literals)
|
|
36
|
-
parsed="$(printf '%s' "$raw" |
|
|
41
|
+
parsed="$(printf '%s' "$raw" | "$PYTHON_BIN" - <<'PYEOF'
|
|
37
42
|
import sys, json, re
|
|
38
43
|
|
|
39
44
|
try:
|
|
@@ -92,6 +97,12 @@ if [[ "$raw" != *"BMAD agent in a collaborative roundtable"* ]]; then
|
|
|
92
97
|
fi
|
|
93
98
|
_dbg "fingerprint HIT: display='$display_name' text_len=${#response_text}"
|
|
94
99
|
|
|
100
|
+
# Party marker (Phase 2): this hook alone knows -- from the roundtable
|
|
101
|
+
# fingerprint -- that we are in party mode. Export the marker so the child
|
|
102
|
+
# bmad-speak.sh can stage the cast on the first party line (stage-on-first-speak)
|
|
103
|
+
# without having to re-derive party context. Additive + harmless outside party.
|
|
104
|
+
export AGENTVIBES_PARTY_MODE=1
|
|
105
|
+
|
|
95
106
|
if [[ -z "$display_name" ]]; then
|
|
96
107
|
_dbg "skip: empty display_name"
|
|
97
108
|
exit 0
|
|
@@ -104,6 +115,11 @@ fi
|
|
|
104
115
|
# --- Resolve project root ---
|
|
105
116
|
project_root="${CLAUDE_PROJECT_DIR:-}"
|
|
106
117
|
|
|
118
|
+
# Thread the real project dir to the child speak path so its forwarded messages
|
|
119
|
+
# carry the correct routing session id (not the install/HOME basename). Export
|
|
120
|
+
# only when known so the no-project case is unchanged.
|
|
121
|
+
[[ -n "$project_root" ]] && export CLAUDE_PROJECT_DIR="$project_root"
|
|
122
|
+
|
|
107
123
|
# --- Find bmad-speak.sh (prefer project-local, fall back to global) ---
|
|
108
124
|
bmad_speak=""
|
|
109
125
|
if [[ -n "$project_root" && -f "$project_root/.claude/hooks/bmad-speak.sh" ]]; then
|
|
@@ -117,7 +133,7 @@ fi
|
|
|
117
133
|
agent_id="$display_name" # fallback
|
|
118
134
|
if [[ -n "$project_root" && -f "$project_root/_bmad/_config/agent-manifest.csv" ]]; then
|
|
119
135
|
manifest="$project_root/_bmad/_config/agent-manifest.csv"
|
|
120
|
-
matched="$(
|
|
136
|
+
matched="$("$PYTHON_BIN" - "$manifest" "$display_name" <<'PYEOF'
|
|
121
137
|
import sys, csv
|
|
122
138
|
manifest_path, target = sys.argv[1], sys.argv[2].lower()
|
|
123
139
|
try:
|
|
@@ -147,7 +163,7 @@ fi
|
|
|
147
163
|
case "$verbosity" in
|
|
148
164
|
low)
|
|
149
165
|
# First sentence — fall back to full text if no punctuation (fixes m1)
|
|
150
|
-
first="$(printf '%s' "$response_text" |
|
|
166
|
+
first="$(printf '%s' "$response_text" | "$PYTHON_BIN" -c "
|
|
151
167
|
import sys, re
|
|
152
168
|
t = sys.stdin.read()
|
|
153
169
|
m = re.match(r'^.*?[.!?]', t)
|
|
@@ -157,7 +173,7 @@ print(m.group(0) if m else t)
|
|
|
157
173
|
;;
|
|
158
174
|
medium)
|
|
159
175
|
# First 2 sentences — fall back to full text if no punctuation (fixes m1)
|
|
160
|
-
two="$(printf '%s' "$response_text" |
|
|
176
|
+
two="$(printf '%s' "$response_text" | "$PYTHON_BIN" -c "
|
|
161
177
|
import sys, re
|
|
162
178
|
t = sys.stdin.read()
|
|
163
179
|
parts = re.findall(r'.*?[.!?]', t)
|
|
@@ -49,6 +49,56 @@ if [[ ! -f "$PROJECT_ROOT/_bmad/_config/agent-manifest.csv" ]]; then
|
|
|
49
49
|
exit 0
|
|
50
50
|
fi
|
|
51
51
|
|
|
52
|
+
# ---------------------------------------------------------------------------
|
|
53
|
+
# Stage-on-first-speak: the FIRST party line for a session notifies a compatible
|
|
54
|
+
# receiver of the full party roster up front, rather than letting it learn the
|
|
55
|
+
# cast one line at a time. Everything here is additive + fail-safe: if python is
|
|
56
|
+
# missing or the notification errors, the line still speaks.
|
|
57
|
+
#
|
|
58
|
+
# Party context is detected via either signal:
|
|
59
|
+
# * AGENTVIBES_PARTY_MODE=1 -- the "party marker" bmad-party-speak.sh exports
|
|
60
|
+
# before it invokes this script (it alone knows the roundtable fingerprint).
|
|
61
|
+
# * the routing session id ends in "-bmad-party-mode" (the suffix the party
|
|
62
|
+
# stages under), for any flow that runs under that session directly.
|
|
63
|
+
#
|
|
64
|
+
# Idempotency: a per-session flag ~/.agentvibes/staged-<sessionid>.flag makes the
|
|
65
|
+
# doorbell fire ONCE per party, not once per line.
|
|
66
|
+
#
|
|
67
|
+
# Clearing the flag: there is no per-line clear (that would re-fire every line).
|
|
68
|
+
# `party-set-room.sh --clear` removes it (manual reset / room change), and a
|
|
69
|
+
# party-end / clear hook should remove it too. TODO: wire an automatic clear to
|
|
70
|
+
# a party-teardown hook if/when BMAD exposes one -- until then the flag persists
|
|
71
|
+
# for the life of the session, which is the correct once-per-party behavior.
|
|
72
|
+
if [[ "${AGENTVIBES_STAGE_ROSTER_DISABLED:-}" != "1" ]]; then
|
|
73
|
+
_sr_session=""
|
|
74
|
+
if [[ -f "$SCRIPT_DIR/session-id.sh" ]]; then
|
|
75
|
+
# shellcheck source=./session-id.sh
|
|
76
|
+
source "$SCRIPT_DIR/session-id.sh"
|
|
77
|
+
_sr_session="$(av_session_id "${CLAUDE_PROJECT_DIR:-$PROJECT_ROOT}")"
|
|
78
|
+
fi
|
|
79
|
+
if [[ "${AGENTVIBES_PARTY_MODE:-}" == "1" || "$_sr_session" == *-bmad-party-mode ]]; then
|
|
80
|
+
_sr_flag="$HOME/.agentvibes/staged-${_sr_session:-unknown}.flag"
|
|
81
|
+
if [[ -n "$_sr_session" && ! -f "$_sr_flag" ]]; then
|
|
82
|
+
# Claim the flag FIRST (atomic-ish) so parallel party lines don't each fire.
|
|
83
|
+
mkdir -p "$HOME/.agentvibes" 2>/dev/null || true
|
|
84
|
+
if ( set -o noclobber; : > "$_sr_flag" ) 2>/dev/null; then
|
|
85
|
+
_sr_python=""
|
|
86
|
+
if [[ -f "$SCRIPT_DIR/python-resolver.sh" ]]; then
|
|
87
|
+
# shellcheck source=./python-resolver.sh
|
|
88
|
+
source "$SCRIPT_DIR/python-resolver.sh"
|
|
89
|
+
_sr_python="${PYTHON_BIN:-}"
|
|
90
|
+
fi
|
|
91
|
+
if [[ -n "$_sr_python" && -f "$SCRIPT_DIR/party-stage-roster.py" ]]; then
|
|
92
|
+
# Fire-and-forget in the background; NEVER block or fail the line.
|
|
93
|
+
( "$_sr_python" "$SCRIPT_DIR/party-stage-roster.py" \
|
|
94
|
+
--project-root "${CLAUDE_PROJECT_DIR:-$PROJECT_ROOT}" \
|
|
95
|
+
--session-suffix bmad-party-mode >/dev/null 2>&1 || true ) &
|
|
96
|
+
fi
|
|
97
|
+
fi
|
|
98
|
+
fi
|
|
99
|
+
fi
|
|
100
|
+
fi
|
|
101
|
+
|
|
52
102
|
# ---------------------------------------------------------------------------
|
|
53
103
|
# Per-agent profile reader — reads from project .agentvibes/bmad-voice-map.json (falls back to global)
|
|
54
104
|
# Uses node for reliable JSON parsing (jq may not be installed)
|
|
@@ -289,10 +339,18 @@ trap 'rmdir "$SPEECH_LOCK" 2>/dev/null' EXIT
|
|
|
289
339
|
# has an empty TEMP_PROFILE, so the arg-3 heuristic in play-tts.sh alone would
|
|
290
340
|
# miss it. AGENT_VOICE is an agent-profile voice, always.
|
|
291
341
|
export AGENTVIBES_VOICE_SOURCE="agent-profile"
|
|
342
|
+
# Thread the real project dir through to play-tts.sh so any downstream forward
|
|
343
|
+
# (SSH-remote / avatar) derives the correct routing session id from the user's
|
|
344
|
+
# project — NOT the install/HOME basename. Only add the flag when the var is
|
|
345
|
+
# non-empty so the no-project case is unchanged. Mirrors session-start-tts.sh.
|
|
346
|
+
_PT_PROJECT_FLAG=()
|
|
347
|
+
if [[ -n "${CLAUDE_PROJECT_DIR:-}" ]]; then
|
|
348
|
+
_PT_PROJECT_FLAG=(--project-dir "$CLAUDE_PROJECT_DIR")
|
|
349
|
+
fi
|
|
292
350
|
if [[ -n "$AGENT_VOICE" ]]; then
|
|
293
|
-
bash "$SCRIPT_DIR/play-tts.sh" "$FULL_TEXT" "$AGENT_VOICE" "$TEMP_PROFILE"
|
|
351
|
+
bash "$SCRIPT_DIR/play-tts.sh" "$FULL_TEXT" "$AGENT_VOICE" "$TEMP_PROFILE" "${_PT_PROJECT_FLAG[@]+"${_PT_PROJECT_FLAG[@]}"}"
|
|
294
352
|
else
|
|
295
|
-
bash "$SCRIPT_DIR/play-tts.sh" "$FULL_TEXT" "" "$TEMP_PROFILE"
|
|
353
|
+
bash "$SCRIPT_DIR/play-tts.sh" "$FULL_TEXT" "" "$TEMP_PROFILE" "${_PT_PROJECT_FLAG[@]+"${_PT_PROJECT_FLAG[@]}"}"
|
|
296
354
|
fi
|
|
297
355
|
|
|
298
356
|
# Release lock
|
|
@@ -40,6 +40,25 @@ set -e # Exit on error
|
|
|
40
40
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
41
41
|
CLAUDE_DIR="$(dirname "$SCRIPT_DIR")"
|
|
42
42
|
|
|
43
|
+
# @function _piper_default_voice
|
|
44
|
+
# @intent Return the Provider Catalog's piper default voice (AVI-S9.6 AC2,
|
|
45
|
+
# design row 22), falling back to the legacy hardcoded literal when the
|
|
46
|
+
# generated .claude/hooks/provider-catalog.sh artifact is missing
|
|
47
|
+
# (installed-tree skew — mirrors the fail-safe probe pattern already
|
|
48
|
+
# used by voice-manager.sh / provider-manager.sh).
|
|
49
|
+
# @returns Echoes the default Piper voice id.
|
|
50
|
+
_piper_default_voice() {
|
|
51
|
+
if [[ -f "$SCRIPT_DIR/provider-catalog.sh" ]]; then
|
|
52
|
+
# shellcheck source=/dev/null
|
|
53
|
+
source "$SCRIPT_DIR/provider-catalog.sh" 2>/dev/null || true
|
|
54
|
+
fi
|
|
55
|
+
if type catalog_default_voice >/dev/null 2>&1; then
|
|
56
|
+
catalog_default_voice piper
|
|
57
|
+
else
|
|
58
|
+
echo "en_US-lessac-medium" # legacy fallback if the catalog artifact is missing
|
|
59
|
+
fi
|
|
60
|
+
}
|
|
61
|
+
|
|
43
62
|
# Colors for output
|
|
44
63
|
GREEN='\033[0;32m'
|
|
45
64
|
YELLOW='\033[1;33m'
|
|
@@ -193,7 +212,7 @@ map_voice_to_provider() {
|
|
|
193
212
|
# Map legacy voice names to Piper equivalents
|
|
194
213
|
case "$voice" in
|
|
195
214
|
"Jessica Anne Bogart"|"Aria")
|
|
196
|
-
|
|
215
|
+
_piper_default_voice
|
|
197
216
|
;;
|
|
198
217
|
"Matthew Schmitz"|"Archer"|"Michael")
|
|
199
218
|
echo "en_US-danny-low"
|
|
@@ -43,6 +43,27 @@ CONFIG_DIR=".agentvibes/bmad"
|
|
|
43
43
|
VOICE_CONFIG_FILE="$CONFIG_DIR/bmad-voices.md"
|
|
44
44
|
ENABLED_FLAG="$CONFIG_DIR/bmad-voices-enabled.flag"
|
|
45
45
|
|
|
46
|
+
# @function _piper_default_voice
|
|
47
|
+
# @intent Return the Provider Catalog's piper default voice (AVI-S9.6 AC2,
|
|
48
|
+
# design row 22), falling back to the legacy hardcoded literal when the
|
|
49
|
+
# generated .claude/hooks/provider-catalog.sh artifact is missing
|
|
50
|
+
# (installed-tree skew — mirrors the fail-safe probe pattern already
|
|
51
|
+
# used by voice-manager.sh / provider-manager.sh).
|
|
52
|
+
# @returns Echoes the default Piper voice id.
|
|
53
|
+
_piper_default_voice() {
|
|
54
|
+
local _script_dir
|
|
55
|
+
_script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
56
|
+
if [[ -f "$_script_dir/provider-catalog.sh" ]]; then
|
|
57
|
+
# shellcheck source=/dev/null
|
|
58
|
+
source "$_script_dir/provider-catalog.sh" 2>/dev/null || true
|
|
59
|
+
fi
|
|
60
|
+
if type catalog_default_voice >/dev/null 2>&1; then
|
|
61
|
+
catalog_default_voice piper
|
|
62
|
+
else
|
|
63
|
+
echo "en_US-lessac-medium" # legacy fallback if the catalog artifact is missing
|
|
64
|
+
fi
|
|
65
|
+
}
|
|
66
|
+
|
|
46
67
|
# AI NOTE: Auto-enable pattern - When BMAD is detected via install-manifest.yaml,
|
|
47
68
|
# automatically enable the voice plugin to provide seamless multi-agent voice support.
|
|
48
69
|
# This avoids requiring manual plugin activation after BMAD installation.
|
|
@@ -160,8 +181,9 @@ get_agent_voice() {
|
|
|
160
181
|
# CSV format: agent_id,voice_name
|
|
161
182
|
local voice=$(grep "^$agent_id," "$bmad_voice_map" | cut -d',' -f2)
|
|
162
183
|
|
|
163
|
-
# If voice is empty or generic (same for all), use defaults
|
|
164
|
-
|
|
184
|
+
# If voice is empty or generic (same for all), use defaults. "Generic"
|
|
185
|
+
# means the CSV still carries the catalog's piper default (AVI-S9.6 AC2).
|
|
186
|
+
if [[ -n "$voice" ]] && [[ "$voice" != "$(_piper_default_voice)" ]]; then
|
|
165
187
|
echo "$voice"
|
|
166
188
|
return
|
|
167
189
|
fi
|
|
@@ -172,7 +194,7 @@ get_agent_voice() {
|
|
|
172
194
|
# These match the BMAD-METHOD defaults for consistency
|
|
173
195
|
case "$agent_id" in
|
|
174
196
|
bmad-master)
|
|
175
|
-
|
|
197
|
+
_piper_default_voice
|
|
176
198
|
return
|
|
177
199
|
;;
|
|
178
200
|
analyst)
|
|
@@ -22,11 +22,30 @@
|
|
|
22
22
|
set -euo pipefail
|
|
23
23
|
|
|
24
24
|
ENCODED_TEXT="${1:-}"
|
|
25
|
-
|
|
25
|
+
|
|
26
|
+
# Default voice: prefer the Provider Catalog's piper default (AVI-S9.6 AC3,
|
|
27
|
+
# design row 23), falling back to the legacy literal if the generated catalog
|
|
28
|
+
# artifact is missing (installed-tree skew).
|
|
29
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
30
|
+
if [[ -f "$SCRIPT_DIR/provider-catalog.sh" ]]; then
|
|
31
|
+
# shellcheck source=/dev/null
|
|
32
|
+
source "$SCRIPT_DIR/provider-catalog.sh" 2>/dev/null || true
|
|
33
|
+
fi
|
|
34
|
+
if type catalog_default_voice >/dev/null 2>&1; then
|
|
35
|
+
_CATALOG_PIPER_DEFAULT="$(catalog_default_voice piper)"
|
|
36
|
+
else
|
|
37
|
+
_CATALOG_PIPER_DEFAULT="en_US-lessac-medium" # legacy fallback
|
|
38
|
+
fi
|
|
39
|
+
VOICE="${2:-$_CATALOG_PIPER_DEFAULT}"
|
|
26
40
|
ENCODED_AGENT="${3:-}"
|
|
27
41
|
ENCODED_INTRO="${4:-}"
|
|
28
42
|
|
|
29
|
-
# SECURITY: Whitelist of allowed voice names
|
|
43
|
+
# SECURITY: Whitelist of allowed voice names.
|
|
44
|
+
# DO NOT UNIFY with the Provider Catalog — this is a SECURITY control, not an
|
|
45
|
+
# inventory list. It is deliberately narrower than (and independent of) the
|
|
46
|
+
# catalog's piper voice set; syncing it to the catalog would silently widen
|
|
47
|
+
# what an untrusted remote SSH sender can request (design §8, provider-catalog.js
|
|
48
|
+
# module header).
|
|
30
49
|
ALLOWED_VOICES="en_US-amy-medium|en_US-lessac-medium|es_ES-mls_9972-low|es_ES-davefx-medium|en_US-joe-medium"
|
|
31
50
|
|
|
32
51
|
# Validate inputs
|
|
@@ -24,7 +24,25 @@
|
|
|
24
24
|
set -euo pipefail
|
|
25
25
|
|
|
26
26
|
ENCODED_TEXT="${1:-}"
|
|
27
|
-
|
|
27
|
+
|
|
28
|
+
# Default voice: prefer the Provider Catalog's piper default (AVI-S9.6 AC3,
|
|
29
|
+
# design row 23), falling back to the legacy literal if the generated catalog
|
|
30
|
+
# artifact is missing (installed-tree skew). NOTE: this receiver has no voice
|
|
31
|
+
# allowlist of its own — see clawdbot-receiver-SECURE.sh for the hardened
|
|
32
|
+
# variant, whose ALLOWED_VOICES security allowlist is intentionally NOT
|
|
33
|
+
# migrated to (or synced with) the catalog (a security allowlist must stay
|
|
34
|
+
# independently narrow — see provider-catalog.js module header).
|
|
35
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
36
|
+
if [[ -f "$SCRIPT_DIR/provider-catalog.sh" ]]; then
|
|
37
|
+
# shellcheck source=/dev/null
|
|
38
|
+
source "$SCRIPT_DIR/provider-catalog.sh" 2>/dev/null || true
|
|
39
|
+
fi
|
|
40
|
+
if type catalog_default_voice >/dev/null 2>&1; then
|
|
41
|
+
_CATALOG_PIPER_DEFAULT="$(catalog_default_voice piper)"
|
|
42
|
+
else
|
|
43
|
+
_CATALOG_PIPER_DEFAULT="en_US-lessac-medium" # legacy fallback
|
|
44
|
+
fi
|
|
45
|
+
VOICE="${2:-$_CATALOG_PIPER_DEFAULT}"
|
|
28
46
|
ENCODED_AGENT="${3:-}"
|
|
29
47
|
ENCODED_INTRO="${4:-}"
|
|
30
48
|
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# elevenlabs-voices.sh — GENERATED FILE. DO NOT EDIT.
|
|
4
|
+
# DO NOT EDIT — generated from src/services/provider-catalog.js
|
|
5
|
+
# Regenerate with: node scripts/generate-provider-catalog.mjs
|
|
6
|
+
# content-hash: sha256:5e717024f1250a8f54d07291256e532606dba7b4a15ab9589305d1af6bba7f35
|
|
7
|
+
#
|
|
8
|
+
# SINGLE SOURCE OF TRUTH for the ElevenLabs voice catalog on the shell side.
|
|
9
|
+
# Source it, do not execute it. Consumed by play-tts-elevenlabs.sh and
|
|
10
|
+
# voice-manager.sh. These are the ElevenLabs DEFAULT-LIBRARY voices — available to
|
|
11
|
+
# every API key (community/library voices that must be added to an account first are
|
|
12
|
+
# intentionally excluded so a switch can't fail at synth time).
|
|
13
|
+
|
|
14
|
+
# Guard against double-sourcing.
|
|
15
|
+
[ -n "${_AGENTVIBES_ELEVENLABS_VOICES_LOADED:-}" ] && return 0
|
|
16
|
+
_AGENTVIBES_ELEVENLABS_VOICES_LOADED=1
|
|
17
|
+
|
|
18
|
+
# Default voice used when none is configured.
|
|
19
|
+
ELEVENLABS_DEFAULT_VOICE="Sarah"
|
|
20
|
+
|
|
21
|
+
# Print the catalog voice names, one per line (bash-3.2-safe replacement for the
|
|
22
|
+
# old associative-array key expansion "${!ELEVENLABS_VOICE_IDS[@]}").
|
|
23
|
+
elevenlabs_voice_names() {
|
|
24
|
+
printf '%s\n' 'Sarah' 'Roger' 'Laura' 'Charlie' 'George' 'Callum' 'River' 'Harry' 'Liam' 'Alice' 'Matilda' 'Will' 'Jessica' 'Eric' 'Bella' 'Chris' 'Brian' 'Daniel' 'Lily' 'Adam' 'Bill'
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
# Resolve a user-supplied name (case-insensitive) OR a raw voice_id to a canonical
|
|
28
|
+
# voice_id. Echoes the id and returns 0 on success; returns 1 if the input matches
|
|
29
|
+
# no catalog name and is not a raw-id shape. bash-3.2-safe: case statement + tr fold
|
|
30
|
+
# (no associative arrays, no case-modifying expansions).
|
|
31
|
+
elevenlabs_resolve_voice() {
|
|
32
|
+
local query lc
|
|
33
|
+
query="${1:-}"
|
|
34
|
+
[ -z "$query" ] && return 1
|
|
35
|
+
lc="$(printf '%s' "$query" | tr '[:upper:]' '[:lower:]')"
|
|
36
|
+
case "$lc" in
|
|
37
|
+
'sarah') printf '%s' 'EXAVITQu4vr4xnSDxMaL'; return 0 ;;
|
|
38
|
+
'roger') printf '%s' 'CwhRBWXzGAHq8TQ4Fs17'; return 0 ;;
|
|
39
|
+
'laura') printf '%s' 'FGY2WhTYpPnrIDTdsKH5'; return 0 ;;
|
|
40
|
+
'charlie') printf '%s' 'IKne3meq5aSn9XLyUdCD'; return 0 ;;
|
|
41
|
+
'george') printf '%s' 'JBFqnCBsd6RMkjVDRZzb'; return 0 ;;
|
|
42
|
+
'callum') printf '%s' 'N2lVS1w4EtoT3dr4eOWO'; return 0 ;;
|
|
43
|
+
'river') printf '%s' 'SAz9YHcvj6GT2YYXdXww'; return 0 ;;
|
|
44
|
+
'harry') printf '%s' 'SOYHLrjzK2X1ezoPC6cr'; return 0 ;;
|
|
45
|
+
'liam') printf '%s' 'TX3LPaxmHKxFdv7VOQHJ'; return 0 ;;
|
|
46
|
+
'alice') printf '%s' 'Xb7hH8MSUJpSbSDYk0k2'; return 0 ;;
|
|
47
|
+
'matilda') printf '%s' 'XrExE9yKIg1WjnnlVkGX'; return 0 ;;
|
|
48
|
+
'will') printf '%s' 'bIHbv24MWmeRgasZH58o'; return 0 ;;
|
|
49
|
+
'jessica') printf '%s' 'cgSgspJ2msm6clMCkdW9'; return 0 ;;
|
|
50
|
+
'eric') printf '%s' 'cjVigY5qzO86Huf0OWal'; return 0 ;;
|
|
51
|
+
'bella') printf '%s' 'hpp4J3VqNfWAUOO0d1Us'; return 0 ;;
|
|
52
|
+
'chris') printf '%s' 'iP95p4xoKVk53GoZ742B'; return 0 ;;
|
|
53
|
+
'brian') printf '%s' 'nPczCjzI2devNBz1zQrb'; return 0 ;;
|
|
54
|
+
'daniel') printf '%s' 'onwK4e9ZLuTAKqWW03F9'; return 0 ;;
|
|
55
|
+
'lily') printf '%s' 'pFZP5JQG7iQjIQuC4Bku'; return 0 ;;
|
|
56
|
+
'adam') printf '%s' 'pNInz6obpgDQGcFmaJgB'; return 0 ;;
|
|
57
|
+
'bill') printf '%s' 'pqHfZKP75CvOlQylNhV4'; return 0 ;;
|
|
58
|
+
esac
|
|
59
|
+
# Already a raw ElevenLabs voice_id.
|
|
60
|
+
[[ "$query" =~ ^[A-Za-z0-9]{20}$ ]] && { printf '%s' "$query"; return 0; }
|
|
61
|
+
return 1
|
|
62
|
+
}
|
|
@@ -17,6 +17,10 @@
|
|
|
17
17
|
set -euo pipefail
|
|
18
18
|
export LC_ALL=C
|
|
19
19
|
|
|
20
|
+
# Resolve a working Python interpreter (Windows git-bash often has none on PATH).
|
|
21
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
22
|
+
source "$SCRIPT_DIR/python-resolver.sh"
|
|
23
|
+
|
|
20
24
|
NON_INTERACTIVE=false
|
|
21
25
|
CHECK_ONLY=false
|
|
22
26
|
|
|
@@ -32,9 +36,10 @@ REQUIRED_PACKAGES=("kokoro" "soundfile" "numpy")
|
|
|
32
36
|
# ---------------------------------------------------------------------------
|
|
33
37
|
# Check current installation state (use find_spec to avoid slow torch import)
|
|
34
38
|
check_kokoro() {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
39
|
+
[[ -n "$PYTHON_BIN" ]] || return 1
|
|
40
|
+
"$PYTHON_BIN" -c "import importlib.util; exit(0 if importlib.util.find_spec('kokoro') else 1)" 2>/dev/null && \
|
|
41
|
+
"$PYTHON_BIN" -c "import importlib.util; exit(0 if importlib.util.find_spec('soundfile') else 1)" 2>/dev/null && \
|
|
42
|
+
"$PYTHON_BIN" -c "import importlib.util; exit(0 if importlib.util.find_spec('numpy') else 1)" 2>/dev/null
|
|
38
43
|
}
|
|
39
44
|
|
|
40
45
|
if check_kokoro; then
|
|
@@ -70,10 +75,13 @@ if [[ "$NON_INTERACTIVE" != true ]]; then
|
|
|
70
75
|
fi
|
|
71
76
|
fi
|
|
72
77
|
|
|
73
|
-
# Check Python is available
|
|
74
|
-
if
|
|
75
|
-
echo "❌ Python 3 is required but
|
|
76
|
-
echo "
|
|
78
|
+
# Check Python is available (resolver handles Windows/no-PATH cases)
|
|
79
|
+
if [[ -z "$PYTHON_BIN" ]]; then
|
|
80
|
+
echo "❌ Python 3 is required but no interpreter was found" >&2
|
|
81
|
+
echo " Linux: sudo apt install python3 python3-pip" >&2
|
|
82
|
+
echo " macOS: brew install python3" >&2
|
|
83
|
+
echo " Windows: install from https://python.org (tick 'Add to PATH')" >&2
|
|
84
|
+
echo " Or set AGENTVIBES_PYTHON=/path/to/python if installed elsewhere." >&2
|
|
77
85
|
exit 1
|
|
78
86
|
fi
|
|
79
87
|
|
|
@@ -84,12 +92,14 @@ echo ""
|
|
|
84
92
|
# Try pip install with PEP 668 fallback to pipx/user install
|
|
85
93
|
_install_pkgs() {
|
|
86
94
|
local pkgs=("$@")
|
|
95
|
+
# Prefer the resolved interpreter's own pip so packages land where the check
|
|
96
|
+
# (and the synth) will actually import them from.
|
|
97
|
+
if "$PYTHON_BIN" -m pip install "${pkgs[@]}" 2>/dev/null; then return 0; fi
|
|
98
|
+
if "$PYTHON_BIN" -m pip install --user "${pkgs[@]}" 2>/dev/null; then return 0; fi
|
|
87
99
|
if pip install "${pkgs[@]}" 2>/dev/null; then return 0; fi
|
|
88
100
|
if pip install --user "${pkgs[@]}" 2>/dev/null; then return 0; fi
|
|
89
101
|
if pip3 install "${pkgs[@]}" 2>/dev/null; then return 0; fi
|
|
90
102
|
if pip3 install --user "${pkgs[@]}" 2>/dev/null; then return 0; fi
|
|
91
|
-
if python3 -m pip install "${pkgs[@]}" 2>/dev/null; then return 0; fi
|
|
92
|
-
if python3 -m pip install --user "${pkgs[@]}" 2>/dev/null; then return 0; fi
|
|
93
103
|
return 1
|
|
94
104
|
}
|
|
95
105
|
|
|
@@ -107,7 +117,7 @@ if _install_pkgs "${REQUIRED_PACKAGES[@]}"; then
|
|
|
107
117
|
echo "Switch to Kokoro: /agent-vibes:provider switch kokoro"
|
|
108
118
|
else
|
|
109
119
|
echo "⚠️ Packages installed but import check failed." >&2
|
|
110
|
-
echo " Try:
|
|
120
|
+
echo " Try: \"$PYTHON_BIN\" -c 'import kokoro'" >&2
|
|
111
121
|
exit 1
|
|
112
122
|
fi
|
|
113
123
|
else
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
# @dependencies provider-manager.sh for active provider detection, .claude/tts-language.txt for state
|
|
33
33
|
# @entrypoints Called by /agent-vibes:language commands, play-tts-*.sh for voice resolution
|
|
34
34
|
# @patterns Provider abstraction, language-to-voice mapping, backward compatibility with legacy LANGUAGE_VOICES
|
|
35
|
-
# @related play-tts-piper.sh, provider-manager.sh
|
|
35
|
+
# @related play-tts-piper.sh, provider-manager.sh
|
|
36
36
|
|
|
37
37
|
# Determine target .claude directory based on context
|
|
38
38
|
# Priority:
|
|
@@ -215,11 +215,18 @@ get_language_code() {
|
|
|
215
215
|
}
|
|
216
216
|
|
|
217
217
|
# Function to check if current voice supports language
|
|
218
|
+
# AVI-S9.6 AC1: this list previously included "Antoni", "Rachel", "Domi",
|
|
219
|
+
# "Charlotte" — none of which exist in the 21-voice ElevenLabs catalog
|
|
220
|
+
# (src/services/provider-catalog.js ELEVENLABS_VOICES), the same class of bug
|
|
221
|
+
# design row 21 flagged in the now-deleted learn-manager.sh. Trimmed to names
|
|
222
|
+
# that ARE in the catalog; test/unit/provider-catalog-conformance.test.js
|
|
223
|
+
# parity-asserts every entry here against the catalog so a future addition of
|
|
224
|
+
# a non-existent name fails the suite instead of shipping silently.
|
|
218
225
|
is_voice_multilingual() {
|
|
219
226
|
local voice="$1"
|
|
220
227
|
|
|
221
|
-
# List of multilingual voices
|
|
222
|
-
local multilingual_voices=("
|
|
228
|
+
# List of multilingual voices (must all exist in the ElevenLabs catalog)
|
|
229
|
+
local multilingual_voices=("Bella" "Matilda")
|
|
223
230
|
|
|
224
231
|
for mv in "${multilingual_voices[@]}"; do
|
|
225
232
|
if [[ "$voice" == "$mv" ]]; then
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# File: .claude/hooks/party-set-room.sh
|
|
4
|
+
#
|
|
5
|
+
# AgentVibes - record the chosen (non-default) party room for this session.
|
|
6
|
+
#
|
|
7
|
+
# party-stage-roster.py broadcasts the DEFAULT installed-agent room unless it
|
|
8
|
+
# knows a specific room was chosen. This tiny helper is the AgentVibes-side
|
|
9
|
+
# mechanism for recording that choice: it writes the active-room state file
|
|
10
|
+
#
|
|
11
|
+
# ~/.agentvibes/party-active-<sessionid>.json = {"group":"<group-id>"}
|
|
12
|
+
#
|
|
13
|
+
# keyed by the canonical routing session id (av_session_id = basename of
|
|
14
|
+
# CLAUDE_PROJECT_DIR). party-stage-roster.py reads this file and passes the
|
|
15
|
+
# group to resolve_party.py as --party, so a chosen non-default room gets
|
|
16
|
+
# broadcast instead of the default.
|
|
17
|
+
#
|
|
18
|
+
# EXTENSION POINT (we do NOT edit the BMAD skill): a party skill can call this
|
|
19
|
+
# from its user-config `activation_steps_append` when the user picks a room --
|
|
20
|
+
# that is the sanctioned way to wire this in without touching BMAD-owned files.
|
|
21
|
+
#
|
|
22
|
+
# Non-destructive: only creates ~/.agentvibes (mkdir -p) and writes/overwrites
|
|
23
|
+
# the one per-session state file it owns. Touches no user config.
|
|
24
|
+
#
|
|
25
|
+
# Usage: party-set-room.sh <group-id>
|
|
26
|
+
# party-set-room.sh --clear # remove the state file (back to default)
|
|
27
|
+
#
|
|
28
|
+
# bash-3.2 safe (macOS default bash).
|
|
29
|
+
|
|
30
|
+
set -euo pipefail
|
|
31
|
+
|
|
32
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
33
|
+
# shellcheck source=./session-id.sh
|
|
34
|
+
source "$SCRIPT_DIR/session-id.sh"
|
|
35
|
+
|
|
36
|
+
GROUP="${1:-}"
|
|
37
|
+
if [[ -z "$GROUP" ]]; then
|
|
38
|
+
echo "usage: party-set-room.sh <group-id> | --clear" >&2
|
|
39
|
+
exit 2
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
SESSION_ID="$(av_session_id "${CLAUDE_PROJECT_DIR:-$PWD}")"
|
|
43
|
+
STATE_DIR="$HOME/.agentvibes"
|
|
44
|
+
STATE_FILE="$STATE_DIR/party-active-${SESSION_ID}.json"
|
|
45
|
+
|
|
46
|
+
mkdir -p "$STATE_DIR"
|
|
47
|
+
|
|
48
|
+
if [[ "$GROUP" == "--clear" ]]; then
|
|
49
|
+
rm -f "$STATE_FILE"
|
|
50
|
+
# Also clear the stage-on-first-speak idempotency flag so the next party
|
|
51
|
+
# re-broadcasts its cast (this is the manual party-reset path).
|
|
52
|
+
rm -f "$STATE_DIR/staged-${SESSION_ID}.flag"
|
|
53
|
+
echo "party-set-room: cleared active room for session '$SESSION_ID' (back to default)."
|
|
54
|
+
exit 0
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
# Validate the group id: a group id is a slug (letters/digits/._-). Reject
|
|
58
|
+
# anything else so we never write attacker-controlled JSON.
|
|
59
|
+
if [[ ! "$GROUP" =~ ^[A-Za-z0-9._-]+$ ]]; then
|
|
60
|
+
echo "party-set-room: invalid group id '$GROUP' (allowed: A-Za-z0-9._-)" >&2
|
|
61
|
+
exit 2
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
# Write atomically so a concurrent reader never sees a half-written file.
|
|
65
|
+
_tmp="$(mktemp "$STATE_DIR/party-active-${SESSION_ID}.XXXXXX")" || exit 1
|
|
66
|
+
trap 'rm -f "$_tmp"' EXIT
|
|
67
|
+
printf '{"group":"%s"}\n' "$GROUP" > "$_tmp"
|
|
68
|
+
mv "$_tmp" "$STATE_FILE"
|
|
69
|
+
trap - EXIT
|
|
70
|
+
|
|
71
|
+
echo "party-set-room: session '$SESSION_ID' room set to '$GROUP'."
|