agentvibes 5.12.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/.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 +43 -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 +1 -1
- 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 +124 -10
- 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
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# provider-catalog.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
|
+
# Bash-3.2-safe catalog accessors (macOS ships bash 3.2 — no associative
|
|
9
|
+
# arrays, no case-modifying parameter expansions). Source this file; do not execute it.
|
|
10
|
+
|
|
11
|
+
AGENTVIBES_CATALOG_VERSION="5e717024f1250a8f"
|
|
12
|
+
|
|
13
|
+
# Guard against double-sourcing.
|
|
14
|
+
[ -n "${_AGENTVIBES_PROVIDER_CATALOG_LOADED:-}" ] && return 0
|
|
15
|
+
_AGENTVIBES_PROVIDER_CATALOG_LOADED=1
|
|
16
|
+
|
|
17
|
+
# Lowercase a string without case-modifying expansions (unavailable in bash 3.2).
|
|
18
|
+
_catalog_lc() { printf '%s' "${1:-}" | tr '[:upper:]' '[:lower:]'; }
|
|
19
|
+
|
|
20
|
+
# Resolve an id-or-alias to its canonical provider id (echoes it; 1 if unknown).
|
|
21
|
+
catalog_canonical_provider() {
|
|
22
|
+
case "$(_catalog_lc "${1:-}")" in
|
|
23
|
+
soprano) printf '%s' 'soprano' ;;
|
|
24
|
+
piper) printf '%s' 'piper' ;;
|
|
25
|
+
kokoro) printf '%s' 'kokoro' ;;
|
|
26
|
+
elevenlabs) printf '%s' 'elevenlabs' ;;
|
|
27
|
+
macos|macos-say|say) printf '%s' 'macos' ;;
|
|
28
|
+
windows-sapi|sapi) printf '%s' 'windows-sapi' ;;
|
|
29
|
+
windows-piper) printf '%s' 'windows-piper' ;;
|
|
30
|
+
*) return 1 ;;
|
|
31
|
+
esac
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
# Echo the default voice for a provider (empty string is valid — system default).
|
|
35
|
+
catalog_default_voice() {
|
|
36
|
+
local _p; _p="$(catalog_canonical_provider "${1:-}")" || return 1
|
|
37
|
+
case "$_p" in
|
|
38
|
+
soprano) printf '%s' 'soprano-default' ;;
|
|
39
|
+
piper) printf '%s' 'en_US-lessac-medium' ;;
|
|
40
|
+
kokoro) printf '%s' 'af_heart' ;;
|
|
41
|
+
elevenlabs) printf '%s' 'Sarah' ;;
|
|
42
|
+
macos) printf '%s' 'Samantha' ;;
|
|
43
|
+
windows-sapi) printf '%s' '' ;;
|
|
44
|
+
windows-piper) printf '%s' 'en_US-lessac-medium' ;;
|
|
45
|
+
esac
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
# Echo a human display name for a provider.
|
|
49
|
+
catalog_display_name() {
|
|
50
|
+
local _p; _p="$(catalog_canonical_provider "${1:-}")" || { printf '%s' "${1:-}"; return 0; }
|
|
51
|
+
case "$_p" in
|
|
52
|
+
soprano) printf '%s' 'Soprano TTS' ;;
|
|
53
|
+
piper) printf '%s' 'Piper TTS' ;;
|
|
54
|
+
kokoro) printf '%s' 'Kokoro TTS' ;;
|
|
55
|
+
elevenlabs) printf '%s' 'ElevenLabs' ;;
|
|
56
|
+
macos) printf '%s' 'macOS Say' ;;
|
|
57
|
+
windows-sapi) printf '%s' 'Windows SAPI' ;;
|
|
58
|
+
windows-piper) printf '%s' 'Piper TTS' ;;
|
|
59
|
+
esac
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
# Print the provider ids playable on a platform (unix|darwin|windows), one per line.
|
|
63
|
+
catalog_providers_for_platform() {
|
|
64
|
+
case "$(_catalog_lc "${1:-}")" in
|
|
65
|
+
unix)
|
|
66
|
+
printf '%s\n' 'soprano' 'piper' 'kokoro' 'elevenlabs'
|
|
67
|
+
;;
|
|
68
|
+
darwin)
|
|
69
|
+
printf '%s\n' 'soprano' 'piper' 'kokoro' 'elevenlabs' 'macos'
|
|
70
|
+
;;
|
|
71
|
+
windows)
|
|
72
|
+
printf '%s\n' 'soprano' 'kokoro' 'windows-sapi' 'windows-piper'
|
|
73
|
+
;;
|
|
74
|
+
*) return 1 ;;
|
|
75
|
+
esac
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
# Print a provider's listable voices (ids for kokoro, names for elevenlabs), one per line.
|
|
79
|
+
# Discovered providers (piper/macos/sapi) enumerate at runtime and print nothing here.
|
|
80
|
+
catalog_list_voices() {
|
|
81
|
+
local _p; _p="$(catalog_canonical_provider "${1:-}")" || return 1
|
|
82
|
+
case "$_p" in
|
|
83
|
+
soprano)
|
|
84
|
+
printf '%s\n' 'soprano-default'
|
|
85
|
+
;;
|
|
86
|
+
piper)
|
|
87
|
+
: # (none — enumerated at runtime)
|
|
88
|
+
;;
|
|
89
|
+
kokoro)
|
|
90
|
+
printf '%s\n' 'af_heart' 'af_alloy' 'af_aoede' 'af_bella' 'af_jessica' 'af_kore' 'af_nicole' 'af_nova' 'af_river' 'af_sarah' 'af_sky' 'am_adam' 'am_echo' 'am_eric' 'am_fenrir' 'am_liam' 'am_michael' 'am_onyx' 'am_puck' 'bf_alice' 'bf_emma' 'bf_isabella' 'bf_lily' 'bm_daniel' 'bm_fable' 'bm_george' 'bm_lewis' 'jf_alpha' 'jf_gongitsune' 'jf_nezumi' 'jf_tebukuro' 'jm_kumo' 'zf_xiaobei' 'zf_xiaoni' 'zf_xiaoxiao' 'zf_xiaoyi' 'zm_yunxi' 'zm_yunxia' 'zm_yunyang' 'ef_dora' 'em_alex' 'em_santa' 'ff_siwis' 'hf_alpha' 'hm_omega' 'if_sara' 'im_nicola' 'pf_dora' 'pm_alex' 'pm_santa' 'kf_alpha' 'km_hyunsu'
|
|
91
|
+
;;
|
|
92
|
+
elevenlabs)
|
|
93
|
+
printf '%s\n' 'Sarah' 'Roger' 'Laura' 'Charlie' 'George' 'Callum' 'River' 'Harry' 'Liam' 'Alice' 'Matilda' 'Will' 'Jessica' 'Eric' 'Bella' 'Chris' 'Brian' 'Daniel' 'Lily' 'Adam' 'Bill'
|
|
94
|
+
;;
|
|
95
|
+
macos)
|
|
96
|
+
: # (none — enumerated at runtime)
|
|
97
|
+
;;
|
|
98
|
+
windows-sapi)
|
|
99
|
+
: # (none — enumerated at runtime)
|
|
100
|
+
;;
|
|
101
|
+
windows-piper)
|
|
102
|
+
: # (none — enumerated at runtime)
|
|
103
|
+
;;
|
|
104
|
+
esac
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
# Validate a voice for a provider. Echoes the canonical voice and returns 0 on
|
|
108
|
+
# success; returns 1 (no output) on rejection. Mirrors validateVoice() in the JS SSOT.
|
|
109
|
+
catalog_validate_voice() {
|
|
110
|
+
local _p _v _lc; _p="$(catalog_canonical_provider "${1:-}")" || return 1
|
|
111
|
+
_v="${2:-}"; _lc="$(_catalog_lc "$_v")"
|
|
112
|
+
case "$_p" in
|
|
113
|
+
kokoro)
|
|
114
|
+
case "$_lc" in
|
|
115
|
+
'af_heart'|'af_alloy'|'af_aoede'|'af_bella'|'af_jessica'|'af_kore'|'af_nicole'|'af_nova'|'af_river'|'af_sarah'|'af_sky'|'am_adam'|'am_echo'|'am_eric'|'am_fenrir'|'am_liam'|'am_michael'|'am_onyx'|'am_puck'|'bf_alice'|'bf_emma'|'bf_isabella'|'bf_lily'|'bm_daniel'|'bm_fable'|'bm_george'|'bm_lewis'|'jf_alpha'|'jf_gongitsune'|'jf_nezumi'|'jf_tebukuro'|'jm_kumo'|'zf_xiaobei'|'zf_xiaoni'|'zf_xiaoxiao'|'zf_xiaoyi'|'zm_yunxi'|'zm_yunxia'|'zm_yunyang'|'ef_dora'|'em_alex'|'em_santa'|'ff_siwis'|'hf_alpha'|'hm_omega'|'if_sara'|'im_nicola'|'pf_dora'|'pm_alex'|'pm_santa'|'kf_alpha'|'km_hyunsu')
|
|
116
|
+
printf '%s' "$_lc"; return 0 ;;
|
|
117
|
+
esac
|
|
118
|
+
return 1 ;;
|
|
119
|
+
elevenlabs)
|
|
120
|
+
case "$_lc" in
|
|
121
|
+
'sarah') printf '%s' 'EXAVITQu4vr4xnSDxMaL'; return 0 ;;
|
|
122
|
+
'roger') printf '%s' 'CwhRBWXzGAHq8TQ4Fs17'; return 0 ;;
|
|
123
|
+
'laura') printf '%s' 'FGY2WhTYpPnrIDTdsKH5'; return 0 ;;
|
|
124
|
+
'charlie') printf '%s' 'IKne3meq5aSn9XLyUdCD'; return 0 ;;
|
|
125
|
+
'george') printf '%s' 'JBFqnCBsd6RMkjVDRZzb'; return 0 ;;
|
|
126
|
+
'callum') printf '%s' 'N2lVS1w4EtoT3dr4eOWO'; return 0 ;;
|
|
127
|
+
'river') printf '%s' 'SAz9YHcvj6GT2YYXdXww'; return 0 ;;
|
|
128
|
+
'harry') printf '%s' 'SOYHLrjzK2X1ezoPC6cr'; return 0 ;;
|
|
129
|
+
'liam') printf '%s' 'TX3LPaxmHKxFdv7VOQHJ'; return 0 ;;
|
|
130
|
+
'alice') printf '%s' 'Xb7hH8MSUJpSbSDYk0k2'; return 0 ;;
|
|
131
|
+
'matilda') printf '%s' 'XrExE9yKIg1WjnnlVkGX'; return 0 ;;
|
|
132
|
+
'will') printf '%s' 'bIHbv24MWmeRgasZH58o'; return 0 ;;
|
|
133
|
+
'jessica') printf '%s' 'cgSgspJ2msm6clMCkdW9'; return 0 ;;
|
|
134
|
+
'eric') printf '%s' 'cjVigY5qzO86Huf0OWal'; return 0 ;;
|
|
135
|
+
'bella') printf '%s' 'hpp4J3VqNfWAUOO0d1Us'; return 0 ;;
|
|
136
|
+
'chris') printf '%s' 'iP95p4xoKVk53GoZ742B'; return 0 ;;
|
|
137
|
+
'brian') printf '%s' 'nPczCjzI2devNBz1zQrb'; return 0 ;;
|
|
138
|
+
'daniel') printf '%s' 'onwK4e9ZLuTAKqWW03F9'; return 0 ;;
|
|
139
|
+
'lily') printf '%s' 'pFZP5JQG7iQjIQuC4Bku'; return 0 ;;
|
|
140
|
+
'adam') printf '%s' 'pNInz6obpgDQGcFmaJgB'; return 0 ;;
|
|
141
|
+
'bill') printf '%s' 'pqHfZKP75CvOlQylNhV4'; return 0 ;;
|
|
142
|
+
esac
|
|
143
|
+
[[ "$_v" =~ ^[A-Za-z0-9]{20}$ ]] && { printf '%s' "$_v"; return 0; }
|
|
144
|
+
return 1 ;;
|
|
145
|
+
soprano)
|
|
146
|
+
case "$_lc" in
|
|
147
|
+
''|soprano|soprano-default) printf '%s' 'soprano-default'; return 0 ;;
|
|
148
|
+
esac
|
|
149
|
+
return 1 ;;
|
|
150
|
+
piper|windows-piper)
|
|
151
|
+
if [[ "$_v" =~ ^[a-z]{2,3}_[A-Z]{2}- ]] || [[ "$_v" == *"::"* ]]; then
|
|
152
|
+
printf '%s' "$_v"; return 0
|
|
153
|
+
fi
|
|
154
|
+
return 1 ;;
|
|
155
|
+
macos|windows-sapi)
|
|
156
|
+
if [ -n "$_v" ]; then printf '%s' "$_v"; return 0; fi
|
|
157
|
+
[ "$_p" = windows-sapi ] && { printf '%s' ''; return 0; }
|
|
158
|
+
return 1 ;;
|
|
159
|
+
esac
|
|
160
|
+
return 1
|
|
161
|
+
}
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
45
45
|
source "$SCRIPT_DIR/provider-manager.sh"
|
|
46
46
|
source "$SCRIPT_DIR/language-manager.sh"
|
|
47
|
+
source "$SCRIPT_DIR/python-resolver.sh"
|
|
47
48
|
|
|
48
49
|
COMMAND="${1:-help}"
|
|
49
50
|
|
|
@@ -101,7 +102,7 @@ provider_list() {
|
|
|
101
102
|
|
|
102
103
|
# Check installation status of optional providers
|
|
103
104
|
local kokoro_installed=false
|
|
104
|
-
|
|
105
|
+
[[ -n "$PYTHON_BIN" ]] && "$PYTHON_BIN" -c "import importlib.util,sys; sys.exit(0 if importlib.util.find_spec('kokoro') else 1)" 2>/dev/null && kokoro_installed=true
|
|
105
106
|
|
|
106
107
|
local elevenlabs_key_set=false
|
|
107
108
|
if [[ -n "${ELEVENLABS_API_KEY:-}" ]]; then
|
|
@@ -73,6 +73,23 @@ get_provider_config_path() {
|
|
|
73
73
|
echo "$provider_file"
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
# @function _load_provider_catalog
|
|
77
|
+
# @intent Source the generated Provider Catalog (bash-3.2-safe SSOT accessors)
|
|
78
|
+
# @why Single source of truth for per-provider defaults (AVI-S9.4) — mirrors the
|
|
79
|
+
# identical pattern in voice-manager.sh (AVI-S9.3). FAIL-SAFE: callers probe
|
|
80
|
+
# `type catalog_* >/dev/null 2>&1` and fall back to legacy hardcoded literals
|
|
81
|
+
# when the artifact is missing (installed-tree skew). Defaults never break.
|
|
82
|
+
# @returns None (sources catalog_* functions into the current shell when present)
|
|
83
|
+
# @sideeffects None (double-sourcing is guarded inside provider-catalog.sh)
|
|
84
|
+
_load_provider_catalog() {
|
|
85
|
+
local _script_dir
|
|
86
|
+
_script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
87
|
+
if [[ -f "$_script_dir/provider-catalog.sh" ]]; then
|
|
88
|
+
# shellcheck source=/dev/null
|
|
89
|
+
source "$_script_dir/provider-catalog.sh" 2>/dev/null || true
|
|
90
|
+
fi
|
|
91
|
+
}
|
|
92
|
+
|
|
76
93
|
# @function get_active_provider
|
|
77
94
|
# @intent Read currently active TTS provider from config file
|
|
78
95
|
# @why Central function for determining which provider to use
|
|
@@ -187,12 +204,24 @@ migrate_voice_to_provider() {
|
|
|
187
204
|
"en_US-danny-low:Alex"
|
|
188
205
|
)
|
|
189
206
|
|
|
190
|
-
# Default voices by provider
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
local elevenlabs_default
|
|
195
|
-
|
|
207
|
+
# Default voices by provider — sourced from the generated Provider Catalog SSOT
|
|
208
|
+
# (AVI-S9.4, catalog_default_voice). FAIL-SAFE: legacy literals if the catalog
|
|
209
|
+
# artifact is missing (installed-tree skew) — mirrors AVI-S9.3's pattern.
|
|
210
|
+
_load_provider_catalog
|
|
211
|
+
local piper_default macos_default soprano_default elevenlabs_default kokoro_default
|
|
212
|
+
if type catalog_default_voice >/dev/null 2>&1; then
|
|
213
|
+
piper_default="$(catalog_default_voice piper)"
|
|
214
|
+
macos_default="$(catalog_default_voice macos)"
|
|
215
|
+
soprano_default="$(catalog_default_voice soprano)" # Single voice — no selection needed
|
|
216
|
+
elevenlabs_default="$(catalog_default_voice elevenlabs)"
|
|
217
|
+
kokoro_default="$(catalog_default_voice kokoro)"
|
|
218
|
+
else
|
|
219
|
+
piper_default="en_US-lessac-medium"
|
|
220
|
+
macos_default="Samantha"
|
|
221
|
+
soprano_default="soprano-default" # Single voice — no selection needed
|
|
222
|
+
elevenlabs_default="Sarah"
|
|
223
|
+
kokoro_default="af_heart"
|
|
224
|
+
fi
|
|
196
225
|
|
|
197
226
|
# Single-voice providers: migration is straightforward
|
|
198
227
|
case "$target_provider" in
|
|
@@ -214,7 +243,15 @@ migrate_voice_to_provider() {
|
|
|
214
243
|
return 0
|
|
215
244
|
fi
|
|
216
245
|
|
|
217
|
-
# If migrating FROM a single-voice provider, return default for target
|
|
246
|
+
# If migrating FROM a single-voice/fixed-shape provider, return default for target.
|
|
247
|
+
# NOTE ("Rachel" sentinel, AVI-S9.4): "Rachel" was ElevenLabs' hardcoded default
|
|
248
|
+
# before the 21-voice catalog trim (AVI-S8.1) — it is NOT a name-to-id match in
|
|
249
|
+
# today's catalog. This is READ-SIDE TOLERANCE ONLY (CLAUDE.md non-destructive
|
|
250
|
+
# rule): a user config that still says "Rachel" degrades gracefully to the
|
|
251
|
+
# target provider's default instead of erroring; we never rewrite the user's file.
|
|
252
|
+
if [[ "$current_voice" == "Rachel" ]]; then
|
|
253
|
+
echo "⚠️ '$current_voice' is a legacy ElevenLabs default no longer in the voice catalog; migrating to the $target_provider default." >&2
|
|
254
|
+
fi
|
|
218
255
|
if [[ "$current_voice" == "soprano-default" || "$current_voice" == "Rachel" || "$current_voice" =~ ^[a-z]{2}_[a-z0-9_]+$ ]]; then
|
|
219
256
|
case "$target_provider" in
|
|
220
257
|
piper) echo "$piper_default" ;;
|
|
@@ -372,6 +409,7 @@ get_provider_script_path() {
|
|
|
372
409
|
# @returns Echoes LLM name (e.g. "claude-code") or empty string if no remote routing configured
|
|
373
410
|
# @exitcode 0=always
|
|
374
411
|
detect_routing_llm() {
|
|
412
|
+
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/python-resolver.sh"
|
|
375
413
|
# Priority 1: AGENTVIBES_LLM_KEY env var (set during active TTS sessions)
|
|
376
414
|
if [[ -n "${AGENTVIBES_LLM_KEY:-}" ]] && [[ "$AGENTVIBES_LLM_KEY" =~ ^llm:([a-zA-Z0-9][a-zA-Z0-9_-]*)$ ]]; then
|
|
377
415
|
echo "${BASH_REMATCH[1]}"
|
|
@@ -380,9 +418,9 @@ detect_routing_llm() {
|
|
|
380
418
|
|
|
381
419
|
# Priority 2: First mode=remote entry in transport-config.json
|
|
382
420
|
local _transport_cfg="$HOME/.agentvibes/transport-config.json"
|
|
383
|
-
if [[ -f "$_transport_cfg" ]] &&
|
|
421
|
+
if [[ -f "$_transport_cfg" ]] && [[ -n "$PYTHON_BIN" ]]; then
|
|
384
422
|
local _remote_llm
|
|
385
|
-
_remote_llm=$(AGENTVIBES_CFG="$_transport_cfg"
|
|
423
|
+
_remote_llm=$(AGENTVIBES_CFG="$_transport_cfg" "$PYTHON_BIN" - <<'PYEOF'
|
|
386
424
|
import json, os, sys
|
|
387
425
|
try:
|
|
388
426
|
d = json.load(open(os.environ['AGENTVIBES_CFG'], encoding='utf-8'))
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# File: .claude/hooks/python-resolver.sh
|
|
4
|
+
#
|
|
5
|
+
# AgentVibes - Text-to-Speech WITH personality for AI Assistants
|
|
6
|
+
# Website: https://agentvibes.org
|
|
7
|
+
# Repository: https://github.com/paulpreibisch/AgentVibes
|
|
8
|
+
#
|
|
9
|
+
# Licensed under the Apache License, Version 2.0
|
|
10
|
+
#
|
|
11
|
+
# @fileoverview Locate a WORKING Python 3 interpreter across platforms.
|
|
12
|
+
# @context Windows git-bash frequently has NO `python3`/`python` on PATH even when
|
|
13
|
+
# Python is installed (the on-PATH WindowsApps entries are Microsoft Store
|
|
14
|
+
# execution-alias stubs that do not actually run). The old `command -v
|
|
15
|
+
# python3` guard therefore made hooks falsely report "not installed".
|
|
16
|
+
# @architecture Sourced (not executed) by hooks; sets and exports $PYTHON_BIN.
|
|
17
|
+
# $PYTHON_BIN is empty when no usable interpreter exists. Callers should
|
|
18
|
+
# test `[[ -n "$PYTHON_BIN" ]]` instead of `command -v python3`.
|
|
19
|
+
# @related play-tts.sh, play-tts-kokoro.sh, kokoro-installer.sh, provider-commands.sh
|
|
20
|
+
#
|
|
21
|
+
# Usage:
|
|
22
|
+
# source "$SCRIPT_DIR/python-resolver.sh"
|
|
23
|
+
# [[ -n "$PYTHON_BIN" ]] || { echo "No Python 3 found" >&2; exit 1; }
|
|
24
|
+
# "$PYTHON_BIN" my-script.py ...
|
|
25
|
+
#
|
|
26
|
+
# Override: set AGENTVIBES_PYTHON=/path/to/python to force a specific interpreter.
|
|
27
|
+
|
|
28
|
+
# Fast path: resolve once per process. Several hooks source this file
|
|
29
|
+
# transitively (e.g. play-tts.sh -> provider-manager.sh), so if PYTHON_BIN is
|
|
30
|
+
# already exported and the file still exists, TRUST it without re-launching
|
|
31
|
+
# Python — a fresh interpreter startup per source adds ~100ms of latency per
|
|
32
|
+
# utterance for nothing. We only pay the exec-validation cost during genuine
|
|
33
|
+
# discovery below, where the Windows Store stub actually needs rejecting.
|
|
34
|
+
if [[ -n "${PYTHON_BIN:-}" && -f "$PYTHON_BIN" ]]; then
|
|
35
|
+
export PYTHON_BIN
|
|
36
|
+
else
|
|
37
|
+
_AV_PY_CACHE="${HOME}/.claude/config/python-bin.txt"
|
|
38
|
+
|
|
39
|
+
# A candidate is valid only if it runs AND is Python 3. Running it filters out
|
|
40
|
+
# the Windows Store alias stub (which exits non-zero when given args); the
|
|
41
|
+
# version check rejects a lone python2 whose py3 syntax would crash at runtime.
|
|
42
|
+
_av_python_valid() {
|
|
43
|
+
[[ -n "${1:-}" ]] || return 1
|
|
44
|
+
"$1" -c 'import sys; sys.exit(0 if sys.version_info[0] >= 3 else 1)' >/dev/null 2>&1
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
_av_resolve_python() {
|
|
48
|
+
local cand resolved
|
|
49
|
+
|
|
50
|
+
# 1. Explicit override wins.
|
|
51
|
+
if [[ -n "${AGENTVIBES_PYTHON:-}" ]] && _av_python_valid "$AGENTVIBES_PYTHON"; then
|
|
52
|
+
printf '%s' "$AGENTVIBES_PYTHON"; return 0
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
# 2. Previously-cached hit (strip a stray CR from a CRLF-edited cache file).
|
|
56
|
+
if [[ -f "$_AV_PY_CACHE" ]]; then
|
|
57
|
+
cand="$(head -1 "$_AV_PY_CACHE" 2>/dev/null | tr -d '\r' || true)"
|
|
58
|
+
if _av_python_valid "$cand"; then printf '%s' "$cand"; return 0; fi
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
# 3. On PATH — but VALIDATE each (rejects the WindowsApps store stub).
|
|
62
|
+
for cand in python3 python python3.13 python3.12 python3.11 python3.10; do
|
|
63
|
+
resolved="$(command -v "$cand" 2>/dev/null || true)"
|
|
64
|
+
if [[ -n "$resolved" ]] && _av_python_valid "$resolved"; then
|
|
65
|
+
printf '%s' "$resolved"; return 0
|
|
66
|
+
fi
|
|
67
|
+
done
|
|
68
|
+
|
|
69
|
+
# 4. Windows `py` launcher — ask it for the real interpreter path. Python on
|
|
70
|
+
# Windows emits CRLF even to a pipe, so strip the trailing CR or the exec
|
|
71
|
+
# of "…python.exe\r" fails.
|
|
72
|
+
if command -v py >/dev/null 2>&1; then
|
|
73
|
+
cand="$(py -3 -c 'import sys; print(sys.executable)' 2>/dev/null | tr -d '\r' || true)"
|
|
74
|
+
if _av_python_valid "$cand"; then printf '%s' "$cand"; return 0; fi
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
# 5. Known Windows install locations, newest version first. Built with
|
|
78
|
+
# nullglob into an array so paths containing spaces ("C:\Program Files",
|
|
79
|
+
# "C:\Users\John Smith\…") survive — an unquoted *variable* glob would
|
|
80
|
+
# word-split them, which is exactly the case this resolver exists to fix.
|
|
81
|
+
# This file is SOURCED, so nullglob is saved and restored to avoid leaking
|
|
82
|
+
# the option into the calling hook.
|
|
83
|
+
local _av_restore_glob; _av_restore_glob="$(shopt -p nullglob 2>/dev/null || true)"
|
|
84
|
+
shopt -s nullglob 2>/dev/null || true
|
|
85
|
+
local -a _av_pys=(
|
|
86
|
+
"${LOCALAPPDATA:-$HOME/AppData/Local}/Programs/Python"/Python3*/python.exe
|
|
87
|
+
"$HOME/AppData/Local/Programs/Python"/Python3*/python.exe
|
|
88
|
+
/c/Python3*/python.exe
|
|
89
|
+
"/c/Program Files/Python3"*/python.exe
|
|
90
|
+
"/c/Program Files (x86)/Python3"*/python.exe
|
|
91
|
+
)
|
|
92
|
+
eval "${_av_restore_glob:-shopt -u nullglob}" 2>/dev/null || true
|
|
93
|
+
# Version-sort descending (Python312 before Python39 — a plain sort -r is
|
|
94
|
+
# lexical and would rank "39" above "312"); NUL-delimited so spacey paths
|
|
95
|
+
# stay intact. `sort -zrV` is GNU coreutils, which git-bash ships (this glob
|
|
96
|
+
# only ever matches under git-bash anyway).
|
|
97
|
+
if [[ ${#_av_pys[@]} -gt 0 ]]; then
|
|
98
|
+
while IFS= read -r -d '' cand; do
|
|
99
|
+
_av_python_valid "$cand" && { printf '%s' "$cand"; return 0; }
|
|
100
|
+
done < <(printf '%s\0' "${_av_pys[@]}" | sort -zrV 2>/dev/null)
|
|
101
|
+
fi
|
|
102
|
+
|
|
103
|
+
return 1
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
PYTHON_BIN="$(_av_resolve_python || true)"
|
|
107
|
+
export PYTHON_BIN
|
|
108
|
+
|
|
109
|
+
# Cache a fresh discovery (best-effort; never fatal). Only rewrite when it
|
|
110
|
+
# actually changed, to avoid needless disk writes on every hook invocation.
|
|
111
|
+
if [[ -n "$PYTHON_BIN" ]]; then
|
|
112
|
+
if [[ ! -f "$_AV_PY_CACHE" ]] || [[ "$(head -1 "$_AV_PY_CACHE" 2>/dev/null | tr -d '\r' || true)" != "$PYTHON_BIN" ]]; then
|
|
113
|
+
mkdir -p "$(dirname "$_AV_PY_CACHE")" 2>/dev/null || true
|
|
114
|
+
printf '%s\n' "$PYTHON_BIN" > "$_AV_PY_CACHE" 2>/dev/null || true
|
|
115
|
+
fi
|
|
116
|
+
fi
|
|
117
|
+
fi
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# File: .claude/hooks/session-id.sh
|
|
4
|
+
#
|
|
5
|
+
# AgentVibes - Canonical routing session-id helper.
|
|
6
|
+
#
|
|
7
|
+
# The receiver MULTIPLEXES forwarded TTS from many sources (multiple local
|
|
8
|
+
# projects + remote machines) keyed by a per-message identity. That identity
|
|
9
|
+
# MUST be stable and correctly derived, or the receiver merges unrelated
|
|
10
|
+
# sessions into one.
|
|
11
|
+
#
|
|
12
|
+
# This helper is the SINGLE source of truth for that routing id so every
|
|
13
|
+
# forward path (e.g. play-tts-ssh-remote.sh SSH) derives it identically:
|
|
14
|
+
#
|
|
15
|
+
# session id = basename(CLAUDE_PROJECT_DIR) when CLAUDE_PROJECT_DIR is set
|
|
16
|
+
# = basename(<fallback dir>) otherwise (passed dir, else PWD)
|
|
17
|
+
#
|
|
18
|
+
# It is intentionally NOT basename(install root): for a global install
|
|
19
|
+
# (~/.claude/hooks) that basename collapses to the HOME dir name (e.g.
|
|
20
|
+
# "administrator"), so every project on the machine would report the same id.
|
|
21
|
+
#
|
|
22
|
+
# NOTE: This is a PLAIN SLUG for routing — do NOT confuse it with
|
|
23
|
+
# agentvibes-session-id.sh, which produces a human SPOKEN identity string.
|
|
24
|
+
#
|
|
25
|
+
# Usage:
|
|
26
|
+
# source "$(dirname "${BASH_SOURCE[0]}")/session-id.sh"
|
|
27
|
+
# SESSION_ID="$(av_session_id "$FALLBACK_PROJECT_DIR")"
|
|
28
|
+
#
|
|
29
|
+
# bash-3.2 safe (macOS default bash). Sourced by scripts already in strict mode;
|
|
30
|
+
# defines a function only and does not alter shell options.
|
|
31
|
+
#
|
|
32
|
+
|
|
33
|
+
# av_session_id [fallback_dir]
|
|
34
|
+
# Prints the canonical routing session id (plain slug) to stdout.
|
|
35
|
+
# Prefers CLAUDE_PROJECT_DIR (the real user project injected via
|
|
36
|
+
# --project-dir); else the passed fallback dir; else PWD. Callers MUST pass a
|
|
37
|
+
# real project dir as the fallback — never the package/install root.
|
|
38
|
+
av_session_id() {
|
|
39
|
+
local _dir="${CLAUDE_PROJECT_DIR:-}"
|
|
40
|
+
if [[ -z "$_dir" ]]; then
|
|
41
|
+
_dir="${1:-$PWD}"
|
|
42
|
+
fi
|
|
43
|
+
# Strip trailing slashes so basename of "/a/b/" is "b", not "".
|
|
44
|
+
while [[ "$_dir" == */ && "${#_dir}" -gt 1 ]]; do
|
|
45
|
+
_dir="${_dir%/}"
|
|
46
|
+
done
|
|
47
|
+
local _base
|
|
48
|
+
_base="$(basename "$_dir" 2>/dev/null || printf '%s' "$_dir")"
|
|
49
|
+
# Slugify: collapse anything outside [A-Za-z0-9._-] to '-' so the id is safe
|
|
50
|
+
# as a JSON string and a stable map key on the receiver.
|
|
51
|
+
_base="$(printf '%s' "$_base" | tr -c 'A-Za-z0-9._-' '-')"
|
|
52
|
+
# Trim leading/trailing dashes introduced by slugification.
|
|
53
|
+
_base="${_base#-}"; _base="${_base%-}"
|
|
54
|
+
[[ -z "$_base" ]] && _base="unknown"
|
|
55
|
+
printf '%s' "$_base"
|
|
56
|
+
}
|
|
@@ -38,6 +38,45 @@ else
|
|
|
38
38
|
_PROJECT_CLAUDE_DIR="$(dirname "$SCRIPT_DIR")"
|
|
39
39
|
fi
|
|
40
40
|
|
|
41
|
+
# ---------------------------------------------------------------------------
|
|
42
|
+
# OPT-IN INJECTION GATE (Issue: global-install cacophony).
|
|
43
|
+
#
|
|
44
|
+
# Previously this hook injected the ~250-token TTS protocol into EVERY session
|
|
45
|
+
# where AgentVibes was found on disk. A GLOBAL install therefore made every open
|
|
46
|
+
# Claude session start speaking at once ("a cacophony of agents"). It also spent
|
|
47
|
+
# tokens on idle sessions that never wanted audio. Injection is now OPT-IN.
|
|
48
|
+
#
|
|
49
|
+
# Two distinct markers, kept SEPARATE on purpose:
|
|
50
|
+
# * agentvibes-unmuted / agentvibes-muted — the user's runtime mute choice,
|
|
51
|
+
# also read by play-tts.sh, where a project `unmuted` OVERRIDES a global mute.
|
|
52
|
+
# * agentvibes-enabled — "this project opted into injection" (written by a
|
|
53
|
+
# project install). It does NOT override a global mute; it only turns the
|
|
54
|
+
# protocol injection on. This separation is why a project install can no
|
|
55
|
+
# longer silently defeat a user's `~/.agentvibes-muted` global kill-switch.
|
|
56
|
+
#
|
|
57
|
+
# Precedence (mirrors play-tts.sh so injection and audio agree):
|
|
58
|
+
# 1. project agentvibes-unmuted → inject (explicit per-project ON)
|
|
59
|
+
# 2. project agentvibes-muted → silent
|
|
60
|
+
# 3. global ~/.agentvibes-muted → silent (the global kill-switch)
|
|
61
|
+
# 4. project agentvibes-enabled, OR global opt-in
|
|
62
|
+
# (~/.claude/agentvibes-enabled or ~/.claude/agentvibes-unmuted) → inject
|
|
63
|
+
# 5. otherwise → silent (opt-in default; zero tokens)
|
|
64
|
+
if [[ -f "$_PROJECT_CLAUDE_DIR/agentvibes-unmuted" ]]; then
|
|
65
|
+
: # explicit per-project enable — wins over a global mute, as in play-tts.sh
|
|
66
|
+
elif [[ -f "$_PROJECT_CLAUDE_DIR/agentvibes-muted" ]]; then
|
|
67
|
+
exit 0
|
|
68
|
+
elif [[ -f "$HOME/.agentvibes-muted" ]]; then
|
|
69
|
+
exit 0 # global kill-switch, no per-project override
|
|
70
|
+
elif [[ -f "$_PROJECT_CLAUDE_DIR/agentvibes-enabled" \
|
|
71
|
+
|| -f "$HOME/.claude/agentvibes-enabled" \
|
|
72
|
+
|| -f "$HOME/.claude/agentvibes-unmuted" ]]; then
|
|
73
|
+
: # opted in (project install marker, or a deliberate global opt-in)
|
|
74
|
+
else
|
|
75
|
+
# Not enabled for this project → inject nothing (zero tokens).
|
|
76
|
+
# Enable for this project with: /agent-vibes:unmute
|
|
77
|
+
exit 0
|
|
78
|
+
fi
|
|
79
|
+
|
|
41
80
|
# Build --project-dir flag to embed in TTS commands.
|
|
42
81
|
# Sanitize: strip any embedded quotes that would break shell quoting.
|
|
43
82
|
PROJECT_DIR_FLAG=""
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
# @dependencies .claude/config/tts-speech-rate.txt, .claude/config/tts-target-speech-rate.txt
|
|
38
38
|
# @entrypoints Called by /agent-vibes:set-speed slash command
|
|
39
39
|
# @patterns Provider-agnostic speed config, legacy file migration, random tongue twisters for testing
|
|
40
|
-
# @related play-tts.sh, play-tts-piper.sh, play-tts-macos.sh
|
|
40
|
+
# @related play-tts.sh, play-tts-piper.sh, play-tts-macos.sh
|
|
41
41
|
#
|
|
42
42
|
|
|
43
43
|
# Get script directory
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
# @dependencies translator.py, language-manager.sh, .bmad/core/config.yaml (optional)
|
|
33
33
|
# @entrypoints Called by /agent-vibes:translate commands and play-tts.sh
|
|
34
34
|
# @patterns Config cascade - manual override > BMAD config > default (no translation)
|
|
35
|
-
# @related translator.py, play-tts.sh, language-manager.sh
|
|
35
|
+
# @related translator.py, play-tts.sh, language-manager.sh
|
|
36
36
|
|
|
37
37
|
# Only set strict mode when executed directly, not when sourced
|
|
38
38
|
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
|
@@ -40,6 +40,7 @@ if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
|
|
40
40
|
fi
|
|
41
41
|
|
|
42
42
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
43
|
+
source "$SCRIPT_DIR/python-resolver.sh"
|
|
43
44
|
|
|
44
45
|
# Use PWD for project dir when called from project context, fall back to script-relative
|
|
45
46
|
if [[ -d "$PWD/.claude" ]]; then
|
|
@@ -173,7 +174,7 @@ translate_text() {
|
|
|
173
174
|
|
|
174
175
|
# Call translator.py
|
|
175
176
|
local translated
|
|
176
|
-
translated=$(
|
|
177
|
+
translated=$("$PYTHON_BIN" "$SCRIPT_DIR/translator.py" "$text" "$target" 2>/dev/null) || translated="$text"
|
|
177
178
|
|
|
178
179
|
echo "$translated"
|
|
179
180
|
}
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
# @dependencies deep-translator, langdetect (pip install deep-translator langdetect)
|
|
33
33
|
# @entrypoints CLI: python3 translator.py <text> <target_lang>, Library: from translator import translate
|
|
34
34
|
# @patterns Command pattern - supports translate, detect, and batch operations
|
|
35
|
-
# @related play-tts.sh,
|
|
35
|
+
# @related play-tts.sh, language-manager.sh
|
|
36
36
|
#
|
|
37
37
|
|
|
38
38
|
"""
|