agentvibes 3.5.10-alpha.0 → 4.0.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/bmad/bmad-voices-enabled.flag +0 -0
- package/.agentvibes/bmad/bmad-voices.md +69 -0
- package/.claude/config/audio-effects.cfg +1 -1
- package/.claude/config/background-music-position.txt +1 -27
- package/.claude/github-star-reminder.txt +1 -1
- package/.claude/hooks/audio-processor.sh +32 -17
- package/.claude/hooks/bmad-speak-enhanced.sh +5 -5
- package/.claude/hooks/bmad-speak.sh +4 -4
- package/.claude/hooks/bmad-voice-manager.sh +8 -8
- package/.claude/hooks/clawdbot-receiver-SECURE.sh +23 -25
- package/.claude/hooks/clawdbot-receiver.sh +28 -4
- package/.claude/hooks/language-manager.sh +1 -1
- package/.claude/hooks/path-resolver.sh +60 -0
- package/.claude/hooks/play-tts-agentvibes-receiver-for-voiceless-connections.sh +90 -0
- package/.claude/hooks/play-tts-piper.sh +82 -24
- package/.claude/hooks/play-tts-ssh-remote.sh +13 -15
- package/.claude/hooks/play-tts.sh +16 -5
- package/.claude/hooks/session-start-tts.sh +26 -56
- package/.claude/hooks/soprano-gradio-synth.py +1 -1
- package/.claude/hooks/verbosity-manager.sh +10 -4
- package/.claude/settings.json +1 -1
- package/CLAUDE.md +129 -104
- package/README.md +418 -17
- package/RELEASE_NOTES.md +60 -1036
- package/bin/agentvibes-voice-browser.js +1827 -0
- package/bin/agentvibes.js +100 -0
- package/mcp-server/server.py +67 -3
- package/package.json +9 -2
- package/src/console/app.js +806 -0
- package/src/console/audio-env.js +123 -0
- package/src/console/brand-colors.js +13 -0
- package/src/console/footer-config.js +42 -0
- package/src/console/modals/.gitkeep +0 -0
- package/src/console/modals/modal-overlay.js +247 -0
- package/src/console/navigation.js +60 -0
- package/src/console/tabs/.gitkeep +0 -0
- package/src/console/tabs/agents-tab.js +369 -0
- package/src/console/tabs/help-tab.js +261 -0
- package/src/console/tabs/install-tab.js +990 -0
- package/src/console/tabs/music-tab.js +997 -0
- package/src/console/tabs/placeholder-tab.js +45 -0
- package/src/console/tabs/readme-tab.js +267 -0
- package/src/console/tabs/settings-tab.js +3949 -0
- package/src/console/tabs/voices-tab.js +1574 -0
- package/src/installer/music-file-input.js +304 -0
- package/src/installer.js +1104 -704
- package/src/services/.gitkeep +0 -0
- package/src/services/agent-voice-store.js +163 -0
- package/src/services/config-service.js +240 -0
- package/src/services/navigation-service.js +123 -0
- package/src/services/provider-service.js +132 -0
- package/src/services/verbosity-service.js +157 -0
- package/src/utils/audio-duration-validator.js +298 -0
- package/src/utils/audio-format-validator.js +277 -0
- package/src/utils/dependency-checker.js +3 -3
- package/src/utils/file-ownership-verifier.js +358 -0
- package/src/utils/music-file-validator.js +275 -0
- package/src/utils/preview-list-prompt.js +136 -0
- package/src/utils/secure-music-storage.js +412 -0
- package/templates/agentvibes-receiver.sh +11 -7
- package/voice-assignments.json +8245 -0
- package/.claude/config/background-music-volume.txt +0 -1
- package/.claude/config/background-music.cfg +0 -1
- package/.claude/config/background-music.txt +0 -1
- package/.claude/config/tts-speech-rate.txt +0 -1
- package/.claude/config/tts-verbosity.txt +0 -1
- package/.claude/hooks/bmad-party-manager.sh +0 -225
- package/.claude/hooks/stop.sh +0 -38
- package/.claude/piper-voices-dir.txt +0 -1
- package/.mcp.json +0 -34
|
File without changes
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
---
|
|
2
|
+
plugin: bmad-voices
|
|
3
|
+
version: 2.0.0
|
|
4
|
+
enabled: true
|
|
5
|
+
description: Provider-aware voice mappings for BMAD agents
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# BMAD Voice Plugin
|
|
9
|
+
|
|
10
|
+
This plugin automatically assigns voices to BMAD agents based on their role and active TTS provider.
|
|
11
|
+
|
|
12
|
+
## Agent Voice Mappings (Provider-Aware)
|
|
13
|
+
|
|
14
|
+
| Agent ID | Agent Name | Intro | Piper TTS Voice | Piper Voice | Personality |
|
|
15
|
+
|----------|------------|-------|------------------|-------------|-------------|
|
|
16
|
+
| pm | John (Product Manager) | John, Product Manager here | Matthew Schmitz | en_US-john-medium | professional |
|
|
17
|
+
| dev | Amelia (Developer) | Amelia, Developer here | Aria | en_US-amy-medium | normal |
|
|
18
|
+
| analyst | Mary (Business Analyst) | Mary, Business Analyst here | Jessica Anne Bogart | en_US-kristin-medium | normal |
|
|
19
|
+
| architect | Winston (Architect) | Winston, Architect here | Michael | en_GB-alan-medium | normal |
|
|
20
|
+
| sm | Bob (Scrum Master) | Bob, Scrum Master here | Matthew Schmitz | en_US-joe-medium | professional |
|
|
21
|
+
| tea | Murat (Test Architect) | Murat, Test Architect here | Michael | en_US-kusal-medium | normal |
|
|
22
|
+
| tech-writer | Paige (Technical Writer) | Paige, Technical Writer here | Aria | en_US-lessac-medium | normal |
|
|
23
|
+
| ux-designer | Sally (UX Designer) | Sally, UX Designer here | Jessica Anne Bogart | en_US-lessac-high | normal |
|
|
24
|
+
| frame-expert | Saif (Visual Designer) | Saif, Visual Designer here | Matthew Schmitz | en_GB-alan-medium | normal |
|
|
25
|
+
| bmad-master | BMad Master | BMad Master here | Michael | en_US-libritts-high | zen |
|
|
26
|
+
| quick-flow-solo-dev | Barry (Quick Flow Solo Dev) | Barry, Quick Flow Solo Dev here | Matthew Schmitz | en_US-john-medium | professional |
|
|
27
|
+
|
|
28
|
+
## How It Works
|
|
29
|
+
|
|
30
|
+
The voice manager automatically selects the appropriate voice based on your active TTS provider:
|
|
31
|
+
- **Piper TTS active**: Uses voices from the "Piper TTS Voice" column
|
|
32
|
+
- **Piper active**: Uses voices from the "Piper Voice" column
|
|
33
|
+
|
|
34
|
+
This ensures BMAD agents work seamlessly regardless of which provider you're using.
|
|
35
|
+
|
|
36
|
+
### Supports Both Display Names and Agent IDs
|
|
37
|
+
|
|
38
|
+
The `bmad-speak.sh` script accepts both formats:
|
|
39
|
+
|
|
40
|
+
**Party Mode** (multiple agents, uses display names):
|
|
41
|
+
```bash
|
|
42
|
+
.claude/hooks/bmad-speak.sh "Winston" "I recommend microservices for scalability"
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**Individual Agents** (single agent sessions, uses agent IDs):
|
|
46
|
+
```bash
|
|
47
|
+
.claude/hooks/bmad-speak.sh "architect" "I recommend microservices for scalability"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Both formats map to the same voice configuration based on the agent ID in the table above. This allows BMAD to use customizable display names while maintaining stable voice mappings.
|
|
51
|
+
|
|
52
|
+
## How to Edit
|
|
53
|
+
|
|
54
|
+
Simply edit the table above to change voice mappings. The format is:
|
|
55
|
+
- **Agent ID**: Must match BMAD's `agent.id` field (pm, dev, qa, etc.)
|
|
56
|
+
- **Agent Name**: Display name (for reference only)
|
|
57
|
+
- **Intro**: Text spoken before agent's message (e.g., "John, Product Manager here"). Leave empty to disable.
|
|
58
|
+
- **Piper TTS Voice**: Voice name for Piper TTS provider
|
|
59
|
+
- **Piper Voice**: Voice model name for Piper provider
|
|
60
|
+
- **Personality**: Optional personality to apply (or "normal" for none)
|
|
61
|
+
|
|
62
|
+
## Commands
|
|
63
|
+
|
|
64
|
+
- `/agent-vibes:bmad enable` - Enable BMAD voice plugin
|
|
65
|
+
- `/agent-vibes:bmad disable` - Disable BMAD voice plugin
|
|
66
|
+
- `/agent-vibes:bmad status` - Show plugin status
|
|
67
|
+
- `/agent-vibes:bmad edit` - Open this file for editing
|
|
68
|
+
- `/agent-vibes:bmad list` - List all agent voice mappings
|
|
69
|
+
- `/agent-vibes:bmad set <agent-id> <piper-voice> <piper-voice> [personality]` - Set voices for specific agent
|
|
@@ -49,4 +49,4 @@ BMad Master|reverb 50 60 100 pitch -100|agentvibes_soft_flamenco_loop.mp3|0.30
|
|
|
49
49
|
_party_mode|compand 0.3,1 6:-70,-60,-20|agent_vibes_dark_chill_step_loop.mp3|0.40
|
|
50
50
|
|||
|
|
51
51
|
# Default (no agent specified) - clean with Bachata background|||
|
|
52
|
-
default|reverb
|
|
52
|
+
default|reverb 40 50 70|agent_vibes_celtic_harp_v1_loop.mp3|0.30
|
|
@@ -1,27 +1 @@
|
|
|
1
|
-
|
|
2
|
-
Agent Vibes Japanese City Pop v1.mp3:29.392744
|
|
3
|
-
Agent Vibes ChillWave v2.mp3:22.154467
|
|
4
|
-
Agent Vibes Bossa Nova v2.mp3:23.733424
|
|
5
|
-
Agent Vibes Tabla Dream Pop v1.mp3:19.101043
|
|
6
|
-
Agent Vibes Hawaiian slack key guitar v2.mp3:36.381950
|
|
7
|
-
AgentVibes Soft Flamenco.mp3:23.160000
|
|
8
|
-
Agent Vibes Arabic v2.mp3:21.922268
|
|
9
|
-
Agent Vibes Goa Trance v2.mp3:55.953741
|
|
10
|
-
Agent Vibes Ganawa Ambient v2.mp3:39.680205
|
|
11
|
-
Agent Vibes Celtic Harp v1.mp3:42.190476
|
|
12
|
-
Agent Vibes Harpsichord v2.mp3:21.739410
|
|
13
|
-
Agent Vibes Japanese City Pop v1-loop.mp3:13.917551
|
|
14
|
-
Agent Vibes Hawaiian slack key guitar v2-loop.mp3:12.977143
|
|
15
|
-
Agent Vibes Ganawa Ambient v2-loop.mp3:.00000000000000000002815996
|
|
16
|
-
Agent Vibes Tabla Dream Pop v1-loop.mp3:.00000000000000000009067943
|
|
17
|
-
Agent Vibes ChillWave v2-loop.mp3:.00000000000000000007080511
|
|
18
|
-
Agent Vibes Harpsichord v2-loop.mp3:.00000000000000000013140818
|
|
19
|
-
agent_vibes_japanese_city_pop_v1_loop.mp3:6.054512
|
|
20
|
-
agent_vibes_bossa_nova_v2_loop.mp3:5.369524
|
|
21
|
-
agent_vibes_salsa_v2_loop.mp3:9.972790
|
|
22
|
-
agent_vibes_cumbia_v1_loop.mp3:5.717823
|
|
23
|
-
agent_vibes_arabic_v2_loop.mp3:.00000000000000000006132724
|
|
24
|
-
agent_vibes_chillwave_v2_loop.mp3:14.628390
|
|
25
|
-
agent_vibes_bachata_v1_loop.mp3:.00000000000000000005344000
|
|
26
|
-
agentvibes_soft_flamenco_loop.mp3:.00000000000000000006934441
|
|
27
|
-
agent_vibes_goa_trance_v2_loop.mp3:.00000000000000000002499918
|
|
1
|
+
agent_vibes_celtic_harp_v1_loop.mp3:12.341678
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
20260309
|
|
@@ -39,18 +39,19 @@ OUTPUT_FILE="${3:-}"
|
|
|
39
39
|
CONFIG_FILE="$(cd "$SCRIPT_DIR/.." && pwd)/config/audio-effects.cfg"
|
|
40
40
|
BACKGROUNDS_DIR="$(cd "$SCRIPT_DIR/../audio" && pwd)/tracks"
|
|
41
41
|
ENABLED_FILE="$(cd "$SCRIPT_DIR/.." && pwd)/config/background-music-enabled.txt"
|
|
42
|
+
GLOBAL_ENABLED_FILE="$HOME/.claude/config/background-music-enabled.txt"
|
|
42
43
|
|
|
43
|
-
# Check if background music is
|
|
44
|
+
# Check if background music is enabled (project-local, then global fallback)
|
|
44
45
|
is_background_music_enabled() {
|
|
45
|
-
|
|
46
|
-
if [[
|
|
46
|
+
local enabled=""
|
|
47
|
+
if [[ -f "$ENABLED_FILE" ]]; then
|
|
48
|
+
enabled=$(cat "$ENABLED_FILE" 2>/dev/null | tr -d '[:space:]')
|
|
49
|
+
elif [[ -f "$GLOBAL_ENABLED_FILE" ]]; then
|
|
50
|
+
enabled=$(cat "$GLOBAL_ENABLED_FILE" 2>/dev/null | tr -d '[:space:]')
|
|
51
|
+
else
|
|
47
52
|
return 1 # Disabled by default
|
|
48
53
|
fi
|
|
49
54
|
|
|
50
|
-
# Read the enabled flag
|
|
51
|
-
local enabled
|
|
52
|
-
enabled=$(cat "$ENABLED_FILE" 2>/dev/null | tr -d '[:space:]')
|
|
53
|
-
|
|
54
55
|
# Return 0 (true) if enabled, 1 (false) otherwise
|
|
55
56
|
[[ "$enabled" == "true" ]]
|
|
56
57
|
}
|
|
@@ -87,9 +88,9 @@ get_agent_config() {
|
|
|
87
88
|
return
|
|
88
89
|
fi
|
|
89
90
|
|
|
90
|
-
# Try exact match first
|
|
91
|
+
# Try exact match first (use awk for safe literal matching)
|
|
91
92
|
local config
|
|
92
|
-
config=$(
|
|
93
|
+
config=$(awk -F'|' -v agent="$agent" 'tolower($1) == tolower(agent)' "$CONFIG_FILE" 2>/dev/null | head -1)
|
|
93
94
|
|
|
94
95
|
# Fall back to default
|
|
95
96
|
if [[ -z "$config" ]]; then
|
|
@@ -119,6 +120,16 @@ apply_sox_effects() {
|
|
|
119
120
|
return 0
|
|
120
121
|
fi
|
|
121
122
|
|
|
123
|
+
# Validate effects contain only allowed sox effect names and numeric params
|
|
124
|
+
local allowed_effects="gain|reverb|echo|chorus|flanger|phaser|tremolo|overdrive|bass|treble|equalizer|highpass|lowpass|bandpass|vol|speed|tempo|pitch|rate|pad|silence|trim|fade|norm|loudness|compand|contrast|delay|repeat|stat|remix"
|
|
125
|
+
for word in $effects; do
|
|
126
|
+
if ! [[ "$word" =~ ^-?[0-9]*\.?[0-9]+$ ]] && ! echo "$word" | grep -qiE "^($allowed_effects)$"; then
|
|
127
|
+
echo "Warning: Invalid sox effect '$word', skipping effects" >&2
|
|
128
|
+
cp "$input" "$output"
|
|
129
|
+
return 0
|
|
130
|
+
fi
|
|
131
|
+
done
|
|
132
|
+
|
|
122
133
|
# Apply effects - note: effects string is intentionally unquoted to allow word splitting
|
|
123
134
|
# shellcheck disable=SC2086
|
|
124
135
|
sox "$input" "$output" $effects 2>/dev/null || {
|
|
@@ -140,7 +151,7 @@ get_background_position() {
|
|
|
140
151
|
bg_name=$(basename "$bg_file")
|
|
141
152
|
|
|
142
153
|
if [[ -f "$POSITION_FILE" ]]; then
|
|
143
|
-
|
|
154
|
+
awk -F: -v name="$bg_name" '$1 == name {print $2}' "$POSITION_FILE" 2>/dev/null | tr -d '[:space:]' | tail -1
|
|
144
155
|
else
|
|
145
156
|
echo "0"
|
|
146
157
|
fi
|
|
@@ -158,12 +169,14 @@ save_background_position() {
|
|
|
158
169
|
|
|
159
170
|
mkdir -p "$(dirname "$POSITION_FILE")"
|
|
160
171
|
|
|
161
|
-
# Remove old entry and add new one
|
|
172
|
+
# Remove old entry and add new one (atomic update via temp file + mv)
|
|
173
|
+
local tmp_pos
|
|
174
|
+
tmp_pos=$(mktemp "${POSITION_FILE}.XXXXXX")
|
|
162
175
|
if [[ -f "$POSITION_FILE" ]]; then
|
|
163
|
-
grep -v "^${bg_name}:" "$POSITION_FILE" > "$
|
|
164
|
-
mv "${POSITION_FILE}.tmp" "$POSITION_FILE"
|
|
176
|
+
grep -v "^${bg_name}:" "$POSITION_FILE" > "$tmp_pos" 2>/dev/null || true
|
|
165
177
|
fi
|
|
166
|
-
echo "${bg_name}:${position}" >> "$
|
|
178
|
+
echo "${bg_name}:${position}" >> "$tmp_pos"
|
|
179
|
+
mv "$tmp_pos" "$POSITION_FILE"
|
|
167
180
|
}
|
|
168
181
|
|
|
169
182
|
# @function mix_background
|
|
@@ -336,9 +349,11 @@ main() {
|
|
|
336
349
|
temp_effects=$(mktemp "$TEMP_DIR/effects-XXXXXX.wav")
|
|
337
350
|
temp_final=$(mktemp "$TEMP_DIR/final-XXXXXX.wav")
|
|
338
351
|
|
|
339
|
-
# Clean up on exit - use
|
|
340
|
-
|
|
341
|
-
|
|
352
|
+
# Clean up on exit - use a cleanup function to avoid trap injection
|
|
353
|
+
_cleanup_effects="$temp_effects"
|
|
354
|
+
_cleanup_final="$temp_final"
|
|
355
|
+
cleanup() { rm -f "$_cleanup_effects" "$_cleanup_final"; }
|
|
356
|
+
trap cleanup EXIT
|
|
342
357
|
|
|
343
358
|
# Step 1: Apply sox effects
|
|
344
359
|
if [[ -n "$sox_effects" ]]; then
|
|
@@ -39,7 +39,7 @@ if [[ -f "$PROJECT_ROOT/.agentvibes/bmad/bmad-party-mode-disabled.flag" ]]; then
|
|
|
39
39
|
fi
|
|
40
40
|
|
|
41
41
|
# Check if BMAD is installed
|
|
42
|
-
if [[ ! -f "$PROJECT_ROOT
|
|
42
|
+
if [[ ! -f "$PROJECT_ROOT/_bmad/_config/agent-manifest.csv" ]]; then
|
|
43
43
|
exit 0
|
|
44
44
|
fi
|
|
45
45
|
|
|
@@ -48,13 +48,13 @@ map_to_agent_id() {
|
|
|
48
48
|
local name_or_id="$1"
|
|
49
49
|
|
|
50
50
|
# If it looks like a file path, extract the agent ID
|
|
51
|
-
if [[ "$name_or_id" =~
|
|
51
|
+
if [[ "$name_or_id" =~ _?\.?bmad/.*/agents/([^/]+)\.md$ ]]; then
|
|
52
52
|
echo "${BASH_REMATCH[1]}"
|
|
53
53
|
return
|
|
54
54
|
fi
|
|
55
55
|
|
|
56
56
|
# Check if it's already an agent ID
|
|
57
|
-
local direct_match=$(grep -i "^\"*${name_or_id}\"*," "$PROJECT_ROOT
|
|
57
|
+
local direct_match=$(grep -i "^\"*${name_or_id}\"*," "$PROJECT_ROOT/_bmad/_config/agent-manifest.csv" | head -1)
|
|
58
58
|
if [[ -n "$direct_match" ]]; then
|
|
59
59
|
echo "$name_or_id"
|
|
60
60
|
return
|
|
@@ -73,7 +73,7 @@ map_to_agent_id() {
|
|
|
73
73
|
exit
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
|
-
' "$PROJECT_ROOT
|
|
76
|
+
' "$PROJECT_ROOT/_bmad/_config/agent-manifest.csv")
|
|
77
77
|
|
|
78
78
|
echo "$agent_id"
|
|
79
79
|
}
|
|
@@ -94,7 +94,7 @@ get_display_name() {
|
|
|
94
94
|
exit
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
|
-
' "$PROJECT_ROOT
|
|
97
|
+
' "$PROJECT_ROOT/_bmad/_config/agent-manifest.csv")
|
|
98
98
|
|
|
99
99
|
echo "$display_name"
|
|
100
100
|
}
|
|
@@ -34,7 +34,7 @@ if [[ -f "$PROJECT_ROOT/.agentvibes/bmad/bmad-party-mode-disabled.flag" ]]; then
|
|
|
34
34
|
fi
|
|
35
35
|
|
|
36
36
|
# Check if BMAD is installed
|
|
37
|
-
if [[ ! -f "$PROJECT_ROOT
|
|
37
|
+
if [[ ! -f "$PROJECT_ROOT/_bmad/_config/agent-manifest.csv" ]]; then
|
|
38
38
|
exit 0
|
|
39
39
|
fi
|
|
40
40
|
|
|
@@ -44,14 +44,14 @@ map_to_agent_id() {
|
|
|
44
44
|
|
|
45
45
|
# If it looks like a file path (.bmad/*/agents/*.md), extract the agent ID
|
|
46
46
|
# Example: .bmad/bmm/agents/pm.md -> pm
|
|
47
|
-
if [[ "$name_or_id" =~
|
|
47
|
+
if [[ "$name_or_id" =~ _?\.?bmad/.*/agents/([^/]+)\.md$ ]]; then
|
|
48
48
|
echo "${BASH_REMATCH[1]}"
|
|
49
49
|
return
|
|
50
50
|
fi
|
|
51
51
|
|
|
52
52
|
# First check if it's already an agent ID (column 1 of manifest)
|
|
53
53
|
# CSV format: name,displayName,title,icon,role,...
|
|
54
|
-
local direct_match=$(grep -i "^\"*${name_or_id}\"*," "$PROJECT_ROOT
|
|
54
|
+
local direct_match=$(grep -i "^\"*${name_or_id}\"*," "$PROJECT_ROOT/_bmad/_config/agent-manifest.csv" | head -1)
|
|
55
55
|
if [[ -n "$direct_match" ]]; then
|
|
56
56
|
# Already an agent ID, pass through
|
|
57
57
|
echo "$name_or_id"
|
|
@@ -78,7 +78,7 @@ map_to_agent_id() {
|
|
|
78
78
|
exit
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
|
-
' "$PROJECT_ROOT
|
|
81
|
+
' "$PROJECT_ROOT/_bmad/_config/agent-manifest.csv")
|
|
82
82
|
|
|
83
83
|
echo "$agent_id"
|
|
84
84
|
}
|
|
@@ -281,10 +281,10 @@ sync_intros_from_manifest() {
|
|
|
281
281
|
bmad_voice_map="bmad/_cfg/agent-voice-map.csv"
|
|
282
282
|
fi
|
|
283
283
|
|
|
284
|
-
if [[ -f "
|
|
285
|
-
manifest_file="
|
|
286
|
-
elif [[ -f "
|
|
287
|
-
manifest_file="
|
|
284
|
+
if [[ -f "_bmad/_config/agent-manifest.csv" ]]; then
|
|
285
|
+
manifest_file="_bmad/_config/agent-manifest.csv"
|
|
286
|
+
elif [[ -f "_bmad/_config/agent-manifest.csv" ]]; then
|
|
287
|
+
manifest_file="_bmad/_config/agent-manifest.csv"
|
|
288
288
|
fi
|
|
289
289
|
|
|
290
290
|
# Both files must exist for sync to work
|
|
@@ -404,10 +404,10 @@ get_agent_intro() {
|
|
|
404
404
|
if [[ -z "$intro" ]] || [[ "$intro" == "Hello! Ready to help with the discussion." ]]; then
|
|
405
405
|
# Try to get display name from agent-manifest.csv
|
|
406
406
|
local manifest_file=""
|
|
407
|
-
if [[ -f "
|
|
408
|
-
manifest_file="
|
|
409
|
-
elif [[ -f "
|
|
410
|
-
manifest_file="
|
|
407
|
+
if [[ -f "_bmad/_config/agent-manifest.csv" ]]; then
|
|
408
|
+
manifest_file="_bmad/_config/agent-manifest.csv"
|
|
409
|
+
elif [[ -f "_bmad/_config/agent-manifest.csv" ]]; then
|
|
410
|
+
manifest_file="_bmad/_config/agent-manifest.csv"
|
|
411
411
|
fi
|
|
412
412
|
|
|
413
413
|
if [[ -n "$manifest_file" ]]; then
|
|
@@ -26,20 +26,19 @@ VOICE="${2:-en_US-lessac-medium}"
|
|
|
26
26
|
ENCODED_AGENT="${3:-}"
|
|
27
27
|
ENCODED_INTRO="${4:-}"
|
|
28
28
|
|
|
29
|
-
# SECURITY: Whitelist of allowed voice names
|
|
30
|
-
ALLOWED_VOICES="en_US-amy-medium|en_US-lessac-medium|es_ES-mls_9972-low|es_ES-davefx-medium|en_US-joe-medium"
|
|
31
|
-
|
|
32
29
|
# Validate inputs
|
|
33
30
|
if [[ -z "$ENCODED_TEXT" ]]; then
|
|
34
|
-
echo "
|
|
31
|
+
echo "Error: No encoded text provided" >&2
|
|
35
32
|
echo "Usage: $0 <base64_text> <voice> <base64_agent_name> [base64_intro]" >&2
|
|
36
33
|
exit 1
|
|
37
34
|
fi
|
|
38
35
|
|
|
39
|
-
# SECURITY: Validate
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
36
|
+
# SECURITY: Validate base64 format (reject shell metacharacters)
|
|
37
|
+
[[ "$ENCODED_TEXT" =~ ^[A-Za-z0-9+/=]+$ ]] || { echo "Error: invalid base64 text" >&2; exit 1; }
|
|
38
|
+
|
|
39
|
+
# SECURITY: Validate VOICE parameter format (alphanumeric, hyphens, underscores only)
|
|
40
|
+
if [[ ! "$VOICE" =~ ^[a-zA-Z0-9_-]+$ ]]; then
|
|
41
|
+
echo "Error: Invalid voice name format: $VOICE" >&2
|
|
43
42
|
exit 1
|
|
44
43
|
fi
|
|
45
44
|
|
|
@@ -58,31 +57,30 @@ fi
|
|
|
58
57
|
|
|
59
58
|
DECODED_AGENT="default"
|
|
60
59
|
if [[ -n "$ENCODED_AGENT" ]]; then
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
echo "⚠️ Invalid agent name format, using 'default'" >&2
|
|
66
|
-
DECODED_AGENT="default"
|
|
60
|
+
# SECURITY: Validate base64 format before decoding
|
|
61
|
+
[[ "$ENCODED_AGENT" =~ ^[A-Za-z0-9+/=]+$ ]] || ENCODED_AGENT=""
|
|
62
|
+
if [[ -n "$ENCODED_AGENT" ]]; then
|
|
63
|
+
DECODED_AGENT=$(echo -n "$ENCODED_AGENT" | base64 -d 2>/dev/null) || DECODED_AGENT="default"
|
|
67
64
|
fi
|
|
68
|
-
|
|
65
|
+
|
|
66
|
+
# SECURITY: Validate agent name format (alphanumeric, dash, underscore only)
|
|
67
|
+
[[ "$DECODED_AGENT" =~ ^[a-zA-Z0-9_-]+$ ]] || DECODED_AGENT="default"
|
|
68
|
+
|
|
69
69
|
# SECURITY: Enforce length limit on agent name
|
|
70
|
-
|
|
71
|
-
echo "⚠️ Agent name too long, using 'default'" >&2
|
|
72
|
-
DECODED_AGENT="default"
|
|
73
|
-
fi
|
|
70
|
+
[[ ${#DECODED_AGENT} -le 50 ]] || DECODED_AGENT="default"
|
|
74
71
|
fi
|
|
75
72
|
|
|
76
73
|
# Decode and prepend intro if provided
|
|
77
74
|
DECODED_INTRO=""
|
|
78
75
|
if [[ -n "$ENCODED_INTRO" ]]; then
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
echo "⚠️ Intro message too long, truncating" >&2
|
|
84
|
-
DECODED_INTRO="${DECODED_INTRO:0:200}"
|
|
76
|
+
# SECURITY: Validate base64 format before decoding
|
|
77
|
+
[[ "$ENCODED_INTRO" =~ ^[A-Za-z0-9+/=]+$ ]] || ENCODED_INTRO=""
|
|
78
|
+
if [[ -n "$ENCODED_INTRO" ]]; then
|
|
79
|
+
DECODED_INTRO=$(echo -n "$ENCODED_INTRO" | base64 -d 2>/dev/null) || DECODED_INTRO=""
|
|
85
80
|
fi
|
|
81
|
+
|
|
82
|
+
# SECURITY: Enforce length limit on intro message
|
|
83
|
+
[[ ${#DECODED_INTRO} -le 200 ]] || DECODED_INTRO="${DECODED_INTRO:0:200}"
|
|
86
84
|
fi
|
|
87
85
|
|
|
88
86
|
# Prepend intro to text if configured
|
|
@@ -30,26 +30,50 @@ ENCODED_INTRO="${4:-}"
|
|
|
30
30
|
|
|
31
31
|
# Validate inputs
|
|
32
32
|
if [[ -z "$ENCODED_TEXT" ]]; then
|
|
33
|
-
echo "
|
|
33
|
+
echo "Error: No encoded text provided" >&2
|
|
34
34
|
echo "Usage: $0 <base64_text> <voice> <base64_agent_name> [base64_intro]" >&2
|
|
35
35
|
exit 1
|
|
36
36
|
fi
|
|
37
37
|
|
|
38
|
+
# SECURITY: Validate base64 format (reject shell metacharacters)
|
|
39
|
+
[[ "$ENCODED_TEXT" =~ ^[A-Za-z0-9+/=]+$ ]] || { echo "Error: invalid base64 text" >&2; exit 1; }
|
|
40
|
+
|
|
41
|
+
# SECURITY: Validate voice name format (alphanumeric, hyphens, underscores only)
|
|
42
|
+
[[ "$VOICE" =~ ^[a-zA-Z0-9_-]+$ ]] || { echo "Error: invalid voice format" >&2; exit 1; }
|
|
43
|
+
|
|
38
44
|
# SECURITY: Decode base64 safely
|
|
39
45
|
DECODED_TEXT=$(echo -n "$ENCODED_TEXT" | base64 -d 2>/dev/null) || {
|
|
40
|
-
echo "
|
|
46
|
+
echo "Error: Failed to decode text (invalid base64)" >&2
|
|
41
47
|
exit 1
|
|
42
48
|
}
|
|
43
49
|
|
|
50
|
+
# SECURITY: Enforce length limit on decoded text (10KB max)
|
|
51
|
+
if [[ ${#DECODED_TEXT} -gt 10000 ]]; then
|
|
52
|
+
echo "Error: Decoded text too long (${#DECODED_TEXT} chars, max 10000)" >&2
|
|
53
|
+
exit 1
|
|
54
|
+
fi
|
|
55
|
+
|
|
44
56
|
DECODED_AGENT="default"
|
|
45
57
|
if [[ -n "$ENCODED_AGENT" ]]; then
|
|
46
|
-
|
|
58
|
+
[[ "$ENCODED_AGENT" =~ ^[A-Za-z0-9+/=]+$ ]] || ENCODED_AGENT=""
|
|
59
|
+
if [[ -n "$ENCODED_AGENT" ]]; then
|
|
60
|
+
DECODED_AGENT=$(echo -n "$ENCODED_AGENT" | base64 -d 2>/dev/null) || DECODED_AGENT="default"
|
|
61
|
+
fi
|
|
62
|
+
# Validate agent name format (alphanumeric, dash, underscore only)
|
|
63
|
+
[[ "$DECODED_AGENT" =~ ^[a-zA-Z0-9_-]+$ ]] || DECODED_AGENT="default"
|
|
64
|
+
# Enforce length limit
|
|
65
|
+
[[ ${#DECODED_AGENT} -le 50 ]] || DECODED_AGENT="default"
|
|
47
66
|
fi
|
|
48
67
|
|
|
49
68
|
# Decode and prepend intro if provided
|
|
50
69
|
DECODED_INTRO=""
|
|
51
70
|
if [[ -n "$ENCODED_INTRO" ]]; then
|
|
52
|
-
|
|
71
|
+
[[ "$ENCODED_INTRO" =~ ^[A-Za-z0-9+/=]+$ ]] || ENCODED_INTRO=""
|
|
72
|
+
if [[ -n "$ENCODED_INTRO" ]]; then
|
|
73
|
+
DECODED_INTRO=$(echo -n "$ENCODED_INTRO" | base64 -d 2>/dev/null) || DECODED_INTRO=""
|
|
74
|
+
fi
|
|
75
|
+
# Enforce length limit on intro (200 chars max)
|
|
76
|
+
[[ ${#DECODED_INTRO} -le 200 ]] || DECODED_INTRO="${DECODED_INTRO:0:200}"
|
|
53
77
|
fi
|
|
54
78
|
|
|
55
79
|
# Prepend intro to text if configured
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
|
|
43
43
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
44
44
|
|
|
45
|
-
if [[ -n "$CLAUDE_PROJECT_DIR" ]] && [[ -d "$CLAUDE_PROJECT_DIR/.claude" ]]; then
|
|
45
|
+
if [[ -n "${CLAUDE_PROJECT_DIR:-}" ]] && [[ -d "${CLAUDE_PROJECT_DIR:-}/.claude" ]]; then
|
|
46
46
|
# MCP context: Use the project directory where MCP was invoked
|
|
47
47
|
CLAUDE_DIR="$CLAUDE_PROJECT_DIR/.claude"
|
|
48
48
|
else
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# File: .claude/hooks/path-resolver.sh
|
|
4
|
+
#
|
|
5
|
+
# AgentVibes Path Resolver Utility - Robust path resolution for all hooks
|
|
6
|
+
# Handles: symlinks, working directory changes, non-standard installations
|
|
7
|
+
#
|
|
8
|
+
# Usage in other scripts:
|
|
9
|
+
# source "$(dirname "${BASH_SOURCE[0]}")/path-resolver.sh"
|
|
10
|
+
# # Now use: $PROJECT_ROOT, $HOOKS_DIR, $SCRIPT_DIR
|
|
11
|
+
#
|
|
12
|
+
|
|
13
|
+
set -euo pipefail
|
|
14
|
+
|
|
15
|
+
# Resolve the actual script location (handles symlinks)
|
|
16
|
+
# This function must be called from the sourcing script
|
|
17
|
+
_resolve_agentvibes_paths() {
|
|
18
|
+
local calling_script="$1"
|
|
19
|
+
|
|
20
|
+
# Get real path (resolve symlinks)
|
|
21
|
+
local script_path
|
|
22
|
+
if command -v readlink &>/dev/null; then
|
|
23
|
+
script_path="$(readlink -f "$calling_script")"
|
|
24
|
+
else
|
|
25
|
+
# Fallback for systems without readlink -f
|
|
26
|
+
script_path="$(cd "$(dirname "$calling_script")" && pwd)/$(basename "$calling_script")"
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
local script_dir="$(dirname "$script_path")"
|
|
30
|
+
|
|
31
|
+
# Find PROJECT_ROOT by searching up for .claude/hooks directory
|
|
32
|
+
# This is resilient to non-standard installations
|
|
33
|
+
local current_dir="$script_dir"
|
|
34
|
+
local project_root=""
|
|
35
|
+
|
|
36
|
+
while [[ "$current_dir" != "/" ]]; do
|
|
37
|
+
if [[ -d "$current_dir/.claude/hooks" ]]; then
|
|
38
|
+
# Found .claude/hooks - PROJECT_ROOT is 2 levels up
|
|
39
|
+
project_root="$(dirname "$(dirname "$current_dir")")"
|
|
40
|
+
break
|
|
41
|
+
fi
|
|
42
|
+
current_dir="$(dirname "$current_dir")"
|
|
43
|
+
done
|
|
44
|
+
|
|
45
|
+
# Validate we found a valid project root
|
|
46
|
+
if [[ -z "$project_root" ]] || [[ ! -d "$project_root/.claude/hooks" ]]; then
|
|
47
|
+
echo "❌ ERROR: Could not locate AgentVibes installation" >&2
|
|
48
|
+
echo " Script: $script_path" >&2
|
|
49
|
+
return 1
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
# Export paths for use in sourcing script
|
|
53
|
+
export SCRIPT_PATH="$script_path"
|
|
54
|
+
export SCRIPT_DIR="$script_dir"
|
|
55
|
+
export HOOKS_DIR="$project_root/.claude/hooks"
|
|
56
|
+
export PROJECT_ROOT="$project_root"
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
# Call the resolver with the calling script
|
|
60
|
+
_resolve_agentvibes_paths "${BASH_SOURCE[1]}"
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# File: .claude/hooks/play-tts-agentvibes-receiver-for-voiceless-connections.sh
|
|
4
|
+
#
|
|
5
|
+
# AgentVibes - AgentVibes Receiver Provider (for voiceless connections)
|
|
6
|
+
# Sends text to a remote device via SSH for local AgentVibes playback.
|
|
7
|
+
# Use this when the AI agent runs on a server/headless machine that has no
|
|
8
|
+
# audio output — the remote device (laptop, phone, etc.) plays the audio.
|
|
9
|
+
#
|
|
10
|
+
# Copyright (c) 2025 Paul Preibisch
|
|
11
|
+
# Licensed under the Apache License, Version 2.0
|
|
12
|
+
#
|
|
13
|
+
|
|
14
|
+
set -euo pipefail
|
|
15
|
+
|
|
16
|
+
TEXT="${1:-}"
|
|
17
|
+
VOICE="${2:-en_US-lessac-medium}"
|
|
18
|
+
AGENT_NAME="${3:-default}"
|
|
19
|
+
|
|
20
|
+
# Validate required input
|
|
21
|
+
if [[ -z "$TEXT" ]]; then
|
|
22
|
+
echo "❌ No text provided" >&2
|
|
23
|
+
echo "Usage: $0 <text> [voice] [agent_name]" >&2
|
|
24
|
+
exit 1
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
# Get script directory
|
|
28
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
29
|
+
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
30
|
+
|
|
31
|
+
# Get SSH host from config
|
|
32
|
+
SSH_HOST=$(cat "$PROJECT_ROOT/.claude/agentvibes-receiver-host.txt" 2>/dev/null || \
|
|
33
|
+
cat "$HOME/.claude/agentvibes-receiver-host.txt" 2>/dev/null || echo "")
|
|
34
|
+
|
|
35
|
+
if [[ -z "$SSH_HOST" ]]; then
|
|
36
|
+
echo "❌ AgentVibes Receiver host not configured" >&2
|
|
37
|
+
echo "💡 Set host: echo 'your-device' > ~/.claude/agentvibes-receiver-host.txt" >&2
|
|
38
|
+
exit 1
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
# SECURITY: Validate SSH_HOST to prevent option injection
|
|
42
|
+
# Must be a valid hostname, IP address, or SSH config alias (alphanumeric, dots, hyphens, underscores)
|
|
43
|
+
if [[ ! "$SSH_HOST" =~ ^[a-zA-Z0-9][a-zA-Z0-9._-]*$ ]]; then
|
|
44
|
+
echo "❌ Invalid SSH host format: $SSH_HOST" >&2
|
|
45
|
+
echo "💡 Host must be alphanumeric (may contain dots, hyphens, underscores)" >&2
|
|
46
|
+
exit 1
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
# SECURITY: Reject hosts starting with hyphen (SSH option injection)
|
|
50
|
+
if [[ "$SSH_HOST" == -* ]]; then
|
|
51
|
+
echo "❌ Invalid SSH host: cannot start with hyphen" >&2
|
|
52
|
+
exit 1
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
# SECURITY: Validate VOICE to prevent injection (alphanumeric, hyphens, underscores only)
|
|
56
|
+
if [[ ! "$VOICE" =~ ^[a-zA-Z0-9_-]+$ ]]; then
|
|
57
|
+
echo "❌ Invalid voice format: $VOICE" >&2
|
|
58
|
+
exit 1
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
# SECURITY: Validate AGENT_NAME to prevent injection (alphanumeric, hyphens, underscores, spaces only)
|
|
62
|
+
if [[ ! "$AGENT_NAME" =~ ^[a-zA-Z0-9_\ -]+$ ]]; then
|
|
63
|
+
echo "❌ Invalid agent name format: $AGENT_NAME" >&2
|
|
64
|
+
exit 1
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
# SECURITY: Encode text and agent name as base64 to prevent command injection
|
|
68
|
+
# The receiver will decode these safely
|
|
69
|
+
ENCODED_TEXT=$(printf '%s' "$TEXT" | base64 -w 0)
|
|
70
|
+
ENCODED_AGENT=$(printf '%s' "$AGENT_NAME" | base64 -w 0)
|
|
71
|
+
|
|
72
|
+
# Send text to remote for local AgentVibes playback
|
|
73
|
+
echo "📱 Sending to $SSH_HOST for local playback..." >&2
|
|
74
|
+
|
|
75
|
+
# Try receiver scripts in order — single SSH call, no separate probe
|
|
76
|
+
# SECURITY: Base64-encoded values are safe to pass as arguments (no shell metacharacters)
|
|
77
|
+
ssh "$SSH_HOST" "
|
|
78
|
+
if [ -f ~/.agentvibes/play-remote.sh ]; then
|
|
79
|
+
bash ~/.agentvibes/play-remote.sh '$ENCODED_TEXT' '$VOICE' '$ENCODED_AGENT'
|
|
80
|
+
elif [ -f ~/.termux/agentvibes-play.sh ]; then
|
|
81
|
+
bash ~/.termux/agentvibes-play.sh '$ENCODED_TEXT' '$VOICE' '$ENCODED_AGENT'
|
|
82
|
+
else
|
|
83
|
+
echo 'Error: Receiver script not found' >&2
|
|
84
|
+
exit 1
|
|
85
|
+
fi
|
|
86
|
+
" &
|
|
87
|
+
SSH_PID=$!
|
|
88
|
+
|
|
89
|
+
echo "Sent to $SSH_HOST (PID: $SSH_PID)" >&2
|
|
90
|
+
exit 0
|