agentvibes 4.4.1 → 4.5.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agentvibes/config.json +4 -4
- package/.claude/config/audio-effects.cfg +1 -0
- package/.claude/config/background-music-enabled.txt +1 -0
- package/.claude/config/reverb-level.txt +1 -1
- package/.claude/github-star-reminder.txt +1 -1
- package/.claude/hooks/audio-processor.sh +1 -1
- package/.claude/hooks/bmad-speak.sh +16 -2
- package/.claude/hooks-windows/bmad-speak.ps1 +200 -0
- package/.claude/hooks-windows/play-tts-piper.ps1 +3 -4
- package/.claude/hooks-windows/play-tts-sapi.ps1 +3 -4
- package/.claude/hooks-windows/play-tts-soprano.ps1 +2 -3
- package/.claude/hooks-windows/play-tts-termux-ssh.ps1 +138 -0
- package/.claude/hooks-windows/play-tts.ps1 +14 -6
- package/.claude/hooks-windows/provider-manager.ps1 +16 -1
- package/CLAUDE.md +4 -0
- package/README.md +39 -9
- package/RELEASE_NOTES.md +78 -0
- package/bin/agent-vibes +1 -1
- package/bin/agentvibes-voice-browser.js +1 -1
- package/bin/bmad-speak.js +52 -0
- package/bin/mcp-server.js +1 -1
- package/bin/test-bmad-pr +1 -1
- package/package.json +1 -1
- package/setup-windows.ps1 +4 -4
- package/src/console/app.js +63 -12
- package/src/console/navigation.js +5 -2
- package/src/console/tabs/agents-tab.js +72 -76
- package/src/console/tabs/help-tab.js +107 -54
- package/src/console/tabs/install-tab.js +132 -56
- package/src/console/tabs/music-tab.js +1039 -1011
- package/src/console/tabs/placeholder-tab.js +27 -0
- package/src/console/tabs/readme-tab.js +9 -7
- package/src/console/tabs/receiver-tab.js +23 -12
- package/src/console/tabs/settings-tab.js +4001 -3783
- package/src/console/tabs/voices-tab.js +1680 -1653
- package/src/console/widgets/personality-picker.js +35 -7
- package/src/console/widgets/reverb-picker.js +9 -6
- package/src/console/widgets/track-picker.js +7 -2
- package/src/i18n/de.js +203 -0
- package/src/i18n/en.js +203 -0
- package/src/i18n/es.js +203 -0
- package/src/i18n/fr.js +203 -0
- package/src/i18n/hi.js +203 -0
- package/src/i18n/ja.js +203 -0
- package/src/i18n/ko.js +203 -0
- package/src/i18n/pt.js +203 -0
- package/src/i18n/strings.js +54 -0
- package/src/i18n/zh-CN.js +203 -0
- package/src/installer/language-screen.js +31 -0
- package/src/installer.js +79 -25
- package/src/services/language-service.js +47 -0
- package/src/utils/file-ownership-verifier.js +2 -2
- package/src/utils/provider-validator.js +9 -13
- package/.claude/hooks-windows/play-tts-windows-piper.ps1 +0 -209
- package/.claude/hooks-windows/play-tts-windows-sapi.ps1 +0 -108
package/.agentvibes/config.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"backgroundMusic": {
|
|
3
3
|
"track": "agent_vibes_cumbia_v1_loop.mp3",
|
|
4
|
-
"enabled":
|
|
5
|
-
"volume":
|
|
4
|
+
"enabled": true,
|
|
5
|
+
"volume": 10
|
|
6
6
|
},
|
|
7
|
+
"voice": "en_US-libritts-high::Ella",
|
|
7
8
|
"provider": "piper",
|
|
8
|
-
"voice": "en_US-libritts-high::Evan-8",
|
|
9
9
|
"effects": {
|
|
10
|
-
"reverbPreset": "
|
|
10
|
+
"reverbPreset": "light"
|
|
11
11
|
}
|
|
12
12
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
true
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
light
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
20260329
|
|
@@ -177,6 +177,20 @@ if [[ -n "$AGENT_ID" ]] && [[ -f "$VOICE_MAP_FILE" ]]; then
|
|
|
177
177
|
IFS='|' read -r PROFILE_VOICE PROFILE_PRETEXT PROFILE_REVERB PROFILE_PERSONALITY PROFILE_MUSIC_TRACK PROFILE_MUSIC_VOLUME PROFILE_MUSIC_ENABLED <<< "$_ALL_FIELDS"
|
|
178
178
|
fi
|
|
179
179
|
|
|
180
|
+
# Read global background music volume as fallback (stored as 0.0-1.0, convert to 0-100 integer)
|
|
181
|
+
_BG_VOL_FILE="${CLAUDE_PROJECT_DIR:-$PROJECT_ROOT}/.claude/config/background-music-volume.txt"
|
|
182
|
+
if [[ ! -f "$_BG_VOL_FILE" ]]; then
|
|
183
|
+
_BG_VOL_FILE="$HOME/.claude/config/background-music-volume.txt"
|
|
184
|
+
fi
|
|
185
|
+
if [[ -f "$_BG_VOL_FILE" ]]; then
|
|
186
|
+
GLOBAL_BG_VOLUME=$(_BG_VOL_RAW=$(cat "$_BG_VOL_FILE") node -e "
|
|
187
|
+
const v = parseFloat(process.env._BG_VOL_RAW);
|
|
188
|
+
process.stdout.write(isNaN(v) ? '20' : String(Math.round(v * 100)));
|
|
189
|
+
" 2>/dev/null || echo "20")
|
|
190
|
+
else
|
|
191
|
+
GLOBAL_BG_VOLUME=20
|
|
192
|
+
fi
|
|
193
|
+
|
|
180
194
|
# Fallback to bmad-voice-manager.sh if no profile voice found
|
|
181
195
|
AGENT_VOICE="$PROFILE_VOICE"
|
|
182
196
|
AGENT_INTRO="$PROFILE_PRETEXT"
|
|
@@ -203,7 +217,7 @@ if [[ -n "$PROFILE_REVERB" ]] || [[ -n "$PROFILE_PERSONALITY" ]] || [[ -n "$PROF
|
|
|
203
217
|
# Write profile as JSON for reliable parsing downstream
|
|
204
218
|
# SECURITY: Pass values via env vars to prevent shell injection
|
|
205
219
|
_P_REVERB="$PROFILE_REVERB" _P_PERSONALITY="$PROFILE_PERSONALITY" \
|
|
206
|
-
_P_MUSIC_TRACK="$PROFILE_MUSIC_TRACK" _P_MUSIC_VOL="${PROFILE_MUSIC_VOLUME
|
|
220
|
+
_P_MUSIC_TRACK="$PROFILE_MUSIC_TRACK" _P_MUSIC_VOL="${PROFILE_MUSIC_VOLUME:-$GLOBAL_BG_VOLUME}" \
|
|
207
221
|
_P_MUSIC_ENABLED="$PROFILE_MUSIC_ENABLED" \
|
|
208
222
|
_P_OUTFILE="$TEMP_PROFILE" node -e "
|
|
209
223
|
const p = {};
|
|
@@ -211,7 +225,7 @@ if [[ -n "$PROFILE_REVERB" ]] || [[ -n "$PROFILE_PERSONALITY" ]] || [[ -n "$PROF
|
|
|
211
225
|
if (process.env._P_PERSONALITY) p.personality = process.env._P_PERSONALITY;
|
|
212
226
|
if (process.env._P_MUSIC_TRACK) p.backgroundMusic = {
|
|
213
227
|
track: process.env._P_MUSIC_TRACK,
|
|
214
|
-
volume: parseInt(process.env._P_MUSIC_VOL) ||
|
|
228
|
+
volume: parseInt(process.env._P_MUSIC_VOL) || 20,
|
|
215
229
|
enabled: process.env._P_MUSIC_ENABLED === 'true'
|
|
216
230
|
};
|
|
217
231
|
require('fs').writeFileSync(process.env._P_OUTFILE, JSON.stringify(p), { mode: 0o600 });
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
#
|
|
2
|
+
# File: .claude/hooks-windows/bmad-speak.ps1
|
|
3
|
+
#
|
|
4
|
+
# AgentVibes BMAD Voice Integration - Windows
|
|
5
|
+
# Maps BMAD agent display names or agent IDs to voices and triggers TTS.
|
|
6
|
+
# Windows port of .claude/hooks/bmad-speak.sh
|
|
7
|
+
#
|
|
8
|
+
# Usage: bmad-speak.ps1 "Agent Name" "dialogue text"
|
|
9
|
+
# bmad-speak.ps1 "agent-id" "dialogue text"
|
|
10
|
+
#
|
|
11
|
+
|
|
12
|
+
param(
|
|
13
|
+
[Parameter(Mandatory = $true, Position = 0)]
|
|
14
|
+
[string]$AgentNameOrId,
|
|
15
|
+
|
|
16
|
+
[Parameter(Mandatory = $true, Position = 1)]
|
|
17
|
+
[string]$Dialogue
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
21
|
+
$ClaudeDir = Split-Path -Parent $ScriptDir
|
|
22
|
+
$ProjectRoot = Split-Path -Parent $ClaudeDir
|
|
23
|
+
|
|
24
|
+
# When running as global script, prefer CLAUDE_PROJECT_DIR for project root
|
|
25
|
+
if ($env:CLAUDE_PROJECT_DIR -and (Test-Path "$env:CLAUDE_PROJECT_DIR\_bmad")) {
|
|
26
|
+
$ProjectRoot = $env:CLAUDE_PROJECT_DIR
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
# Strip markdown formatting — prevent SAPI/Piper from speaking asterisks literally
|
|
30
|
+
$Dialogue = $Dialogue -replace '\*\*', '' -replace '\*', '' -replace '`', ''
|
|
31
|
+
$Dialogue = $Dialogue -replace '\\!', '!' -replace '\\\$', '$'
|
|
32
|
+
|
|
33
|
+
# Check if party mode is disabled
|
|
34
|
+
$PartyModeDisabledFlag = Join-Path $ProjectRoot ".agentvibes\bmad\bmad-party-mode-disabled.flag"
|
|
35
|
+
if (Test-Path $PartyModeDisabledFlag) {
|
|
36
|
+
exit 0
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
# Check if BMAD is installed
|
|
40
|
+
$ManifestFile = Join-Path $ProjectRoot "_bmad\_config\agent-manifest.csv"
|
|
41
|
+
if (-not (Test-Path $ManifestFile)) {
|
|
42
|
+
exit 0
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
# ---------------------------------------------------------------------------
|
|
46
|
+
# Read bmad-voice-map.json for per-agent profile
|
|
47
|
+
# Prefer project-local voice map, fall back to global
|
|
48
|
+
$VoiceMapLocal = Join-Path $ProjectRoot ".agentvibes\bmad-voice-map.json"
|
|
49
|
+
$VoiceMapGlobal = Join-Path $env:USERPROFILE ".agentvibes\bmad-voice-map.json"
|
|
50
|
+
$VoiceMapFile = if (Test-Path $VoiceMapLocal) { $VoiceMapLocal } else { $VoiceMapGlobal }
|
|
51
|
+
|
|
52
|
+
$AgentVoice = ""
|
|
53
|
+
$AgentPersonality = ""
|
|
54
|
+
$AgentBgEnabled = $false
|
|
55
|
+
$AgentBgTrack = ""
|
|
56
|
+
$AgentId = $null
|
|
57
|
+
|
|
58
|
+
# Read global background music volume (stored as 0.0-1.0 float)
|
|
59
|
+
$_BgVolFile = Join-Path $ProjectRoot ".claude\config\background-music-volume.txt"
|
|
60
|
+
if (-not (Test-Path $_BgVolFile)) {
|
|
61
|
+
$_BgVolFile = Join-Path $env:USERPROFILE ".claude\config\background-music-volume.txt"
|
|
62
|
+
}
|
|
63
|
+
if (Test-Path $_BgVolFile) {
|
|
64
|
+
$_BgVolRaw = (Get-Content $_BgVolFile -Raw -ErrorAction SilentlyContinue).Trim()
|
|
65
|
+
$_BgVolParsed = 0.0
|
|
66
|
+
if ([double]::TryParse($_BgVolRaw, [System.Globalization.NumberStyles]::Float, [System.Globalization.CultureInfo]::InvariantCulture, [ref]$_BgVolParsed)) {
|
|
67
|
+
$AgentBgVolume = "{0:F2}" -f $_BgVolParsed
|
|
68
|
+
} else {
|
|
69
|
+
$AgentBgVolume = "0.20"
|
|
70
|
+
}
|
|
71
|
+
} else {
|
|
72
|
+
$AgentBgVolume = "0.20"
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (Test-Path $VoiceMapFile) {
|
|
76
|
+
try {
|
|
77
|
+
$VoiceMap = Get-Content $VoiceMapFile -Raw | ConvertFrom-Json
|
|
78
|
+
|
|
79
|
+
# Resolve agent ID: match canonical ID or display name prefix
|
|
80
|
+
$ManifestRows = Import-Csv $ManifestFile -Encoding UTF8
|
|
81
|
+
foreach ($row in $ManifestRows) {
|
|
82
|
+
$id = ($row.PSObject.Properties | Select-Object -First 1).Value -replace '^"|"$', ''
|
|
83
|
+
$display = ($row.PSObject.Properties | Select-Object -Skip 1 -First 1).Value -replace '^"|"$', ''
|
|
84
|
+
if ($id -ieq $AgentNameOrId -or $display -like "$AgentNameOrId*") {
|
|
85
|
+
$AgentId = $id
|
|
86
|
+
break
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if ($AgentId -and $VoiceMap.agents.$AgentId) {
|
|
91
|
+
$Profile = $VoiceMap.agents.$AgentId
|
|
92
|
+
if ($Profile.voice) { $AgentVoice = $Profile.voice }
|
|
93
|
+
if ($Profile.personality) { $AgentPersonality = $Profile.personality }
|
|
94
|
+
if ($Profile.backgroundMusic) {
|
|
95
|
+
$AgentBgEnabled = [bool]$Profile.backgroundMusic.enabled
|
|
96
|
+
if ($Profile.backgroundMusic.track) { $AgentBgTrack = $Profile.backgroundMusic.track }
|
|
97
|
+
if ($null -ne $Profile.backgroundMusic.volume) {
|
|
98
|
+
# Voice map stores 0-100; audio-effects.cfg uses 0.0-1.0
|
|
99
|
+
$AgentBgVolume = "{0:F2}" -f ([double]$Profile.backgroundMusic.volume / 100.0)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
} catch {
|
|
104
|
+
# Silently degrade — TTS will still play with global settings
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
# ---------------------------------------------------------------------------
|
|
109
|
+
# Locate play-tts.ps1 — prefer project-local, fall back to global
|
|
110
|
+
$PlayTtsLocal = Join-Path $ProjectRoot ".claude\hooks-windows\play-tts.ps1"
|
|
111
|
+
$PlayTtsGlobal = Join-Path $env:USERPROFILE ".claude\hooks-windows\play-tts.ps1"
|
|
112
|
+
$PlayTtsScript = if (Test-Path $PlayTtsLocal) { $PlayTtsLocal } else { $PlayTtsGlobal }
|
|
113
|
+
|
|
114
|
+
if (-not (Test-Path $PlayTtsScript)) {
|
|
115
|
+
exit 0
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
# ---------------------------------------------------------------------------
|
|
119
|
+
# Determine which .claude config dir play-tts.ps1 will read.
|
|
120
|
+
# play-tts.ps1 checks CLAUDE_PROJECT_DIR first — match that logic exactly.
|
|
121
|
+
$TtsClaudeDir = if ($env:CLAUDE_PROJECT_DIR -and (Test-Path "$env:CLAUDE_PROJECT_DIR\.claude")) {
|
|
122
|
+
"$env:CLAUDE_PROJECT_DIR\.claude"
|
|
123
|
+
} else {
|
|
124
|
+
$ClaudeDir # ~/.claude (this script's own ClaudeDir)
|
|
125
|
+
}
|
|
126
|
+
$TtsConfigDir = Join-Path $TtsClaudeDir "config"
|
|
127
|
+
|
|
128
|
+
# ---------------------------------------------------------------------------
|
|
129
|
+
# Apply per-agent personality override if set
|
|
130
|
+
$OldPersonality = ""
|
|
131
|
+
$PersonalityFile = Join-Path $TtsClaudeDir "config\personality.txt"
|
|
132
|
+
if ($AgentPersonality -and (Test-Path (Split-Path $PersonalityFile -Parent))) {
|
|
133
|
+
if (Test-Path $PersonalityFile) {
|
|
134
|
+
$OldPersonality = (Get-Content $PersonalityFile -Raw).Trim()
|
|
135
|
+
}
|
|
136
|
+
Set-Content $PersonalityFile $AgentPersonality -NoNewline
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
# ---------------------------------------------------------------------------
|
|
140
|
+
# Temporarily patch background music config for this agent.
|
|
141
|
+
# The caller (bmad-party-speak.ps1) holds a named mutex so only one speak
|
|
142
|
+
# call runs at a time — these file patches are safe from concurrent clobber.
|
|
143
|
+
$BgEnabledFile = Join-Path $TtsConfigDir "background-music-enabled.txt"
|
|
144
|
+
$AudioEffectsCfg = Join-Path $TtsConfigDir "audio-effects.cfg"
|
|
145
|
+
$OldBgEnabled = $null
|
|
146
|
+
$TempCfgLine = ""
|
|
147
|
+
|
|
148
|
+
if ($AgentBgEnabled -and $AgentBgTrack) {
|
|
149
|
+
# Save + enable background music
|
|
150
|
+
if (Test-Path $BgEnabledFile) {
|
|
151
|
+
$OldBgEnabled = (Get-Content $BgEnabledFile -Raw -ErrorAction SilentlyContinue).Trim()
|
|
152
|
+
}
|
|
153
|
+
Set-Content $BgEnabledFile "true" -NoNewline
|
|
154
|
+
|
|
155
|
+
# Prepend agent line to audio-effects.cfg so play-tts.ps1 finds it first
|
|
156
|
+
# Format: AGENT_NAME|SOX_EFFECTS|BACKGROUND_FILE|BACKGROUND_VOLUME
|
|
157
|
+
$TempCfgLine = "${AgentId}||${AgentBgTrack}|${AgentBgVolume}"
|
|
158
|
+
$env:AGENTVIBES_AGENT_NAME = $AgentId
|
|
159
|
+
$existingCfg = if (Test-Path $AudioEffectsCfg) {
|
|
160
|
+
Get-Content $AudioEffectsCfg -Raw -ErrorAction SilentlyContinue
|
|
161
|
+
} else { "" }
|
|
162
|
+
Set-Content $AudioEffectsCfg "${TempCfgLine}`n${existingCfg}" -NoNewline
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
try {
|
|
166
|
+
# Speak with agent's voice (or global voice if none configured)
|
|
167
|
+
if ($AgentVoice) {
|
|
168
|
+
& powershell -NoProfile -ExecutionPolicy Bypass -File $PlayTtsScript $Dialogue $AgentVoice
|
|
169
|
+
} else {
|
|
170
|
+
& powershell -NoProfile -ExecutionPolicy Bypass -File $PlayTtsScript $Dialogue
|
|
171
|
+
}
|
|
172
|
+
} finally {
|
|
173
|
+
# Restore personality
|
|
174
|
+
if ($AgentPersonality -and $PersonalityFile) {
|
|
175
|
+
if ($OldPersonality) {
|
|
176
|
+
Set-Content $PersonalityFile $OldPersonality -NoNewline
|
|
177
|
+
} elseif (Test-Path $PersonalityFile) {
|
|
178
|
+
Remove-Item $PersonalityFile -Force -ErrorAction SilentlyContinue
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
# Restore background music config
|
|
183
|
+
if ($AgentBgEnabled -and $AgentBgTrack) {
|
|
184
|
+
if ($null -ne $OldBgEnabled) {
|
|
185
|
+
Set-Content $BgEnabledFile $OldBgEnabled -NoNewline
|
|
186
|
+
} elseif (Test-Path $BgEnabledFile) {
|
|
187
|
+
Remove-Item $BgEnabledFile -Force -ErrorAction SilentlyContinue
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
# Remove the prepended agent line from audio-effects.cfg
|
|
191
|
+
if (Test-Path $AudioEffectsCfg) {
|
|
192
|
+
$cfgRaw = Get-Content $AudioEffectsCfg -Raw -ErrorAction SilentlyContinue
|
|
193
|
+
$escaped = [regex]::Escape($TempCfgLine)
|
|
194
|
+
$cfgRaw = $cfgRaw -replace "^${escaped}\r?\n?", ""
|
|
195
|
+
Set-Content $AudioEffectsCfg $cfgRaw -NoNewline
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
$env:AGENTVIBES_AGENT_NAME = ""
|
|
199
|
+
}
|
|
200
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#
|
|
2
|
-
# File: .claude/hooks-windows/play-tts-
|
|
2
|
+
# File: .claude/hooks-windows/play-tts-piper.ps1
|
|
3
3
|
#
|
|
4
4
|
# AgentVibes - Windows Piper TTS Provider
|
|
5
5
|
# High-quality neural TTS using Piper.exe
|
|
@@ -162,9 +162,8 @@ $Text = $Text -replace '[{}<>|`~^;]', ''
|
|
|
162
162
|
$Text = $Text -replace '\s+', ' '
|
|
163
163
|
$Text = $Text.Trim()
|
|
164
164
|
|
|
165
|
-
# Create audio file path
|
|
166
|
-
$
|
|
167
|
-
$AudioFile = "$AudioDir\tts-$Timestamp.wav"
|
|
165
|
+
# Create audio file path — SECURITY: use random name instead of predictable timestamp (#130)
|
|
166
|
+
$AudioFile = "$AudioDir\tts-$([System.IO.Path]::GetRandomFileName() -replace '\..*').wav"
|
|
168
167
|
|
|
169
168
|
# Synthesize with Piper
|
|
170
169
|
try {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#
|
|
2
|
-
# File: .claude/hooks-windows/play-tts-
|
|
2
|
+
# File: .claude/hooks-windows/play-tts-sapi.ps1
|
|
3
3
|
#
|
|
4
4
|
# AgentVibes - Windows SAPI TTS Provider (Zero Dependencies)
|
|
5
5
|
# Uses built-in Windows System.Speech API
|
|
@@ -73,9 +73,8 @@ $Text = $Text.Trim()
|
|
|
73
73
|
# Get actual voice name (after selection or default)
|
|
74
74
|
$ActualVoice = $synth.Voice.Name
|
|
75
75
|
|
|
76
|
-
# Create audio file path
|
|
77
|
-
$
|
|
78
|
-
$AudioFile = "$AudioDir\tts-$Timestamp.wav"
|
|
76
|
+
# Create audio file path — SECURITY: use random name instead of predictable timestamp (#130)
|
|
77
|
+
$AudioFile = "$AudioDir\tts-$([System.IO.Path]::GetRandomFileName() -replace '\..*').wav"
|
|
79
78
|
|
|
80
79
|
# Save to WAV file with proper resource cleanup
|
|
81
80
|
$player = $null
|
|
@@ -53,9 +53,8 @@ if (-not (Test-Path $AudioDir)) {
|
|
|
53
53
|
New-Item -ItemType Directory -Path $AudioDir -Force | Out-Null
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
$
|
|
58
|
-
$TempFile = Join-Path $AudioDir "tts-$timestamp-$random.wav"
|
|
56
|
+
# SECURITY: use random name instead of predictable timestamp (#130)
|
|
57
|
+
$TempFile = Join-Path $AudioDir "tts-$([System.IO.Path]::GetRandomFileName() -replace '\..*').wav"
|
|
59
58
|
|
|
60
59
|
# Check WebUI server
|
|
61
60
|
function Test-WebUI {
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
#
|
|
2
|
+
# File: .claude/hooks-windows/play-tts-termux-ssh.ps1
|
|
3
|
+
#
|
|
4
|
+
# AgentVibes - Finally, your AI Agents can Talk Back! Text-to-Speech WITH personality for AI Assistants!
|
|
5
|
+
# Website: https://agentvibes.org
|
|
6
|
+
# Repository: https://github.com/paulpreibisch/AgentVibes
|
|
7
|
+
#
|
|
8
|
+
# Co-created by Paul Preibisch with Claude AI
|
|
9
|
+
# Copyright (c) 2025 Paul Preibisch
|
|
10
|
+
#
|
|
11
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
12
|
+
# you may not use this file except in compliance with the License.
|
|
13
|
+
# You may obtain a copy of the License at
|
|
14
|
+
#
|
|
15
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
16
|
+
#
|
|
17
|
+
# ---
|
|
18
|
+
#
|
|
19
|
+
# @fileoverview Termux SSH TTS Provider (Windows) - Android TTS via SSH tunnel
|
|
20
|
+
# @context Enables TTS output on Android devices when connected via SSH from Windows
|
|
21
|
+
# @architecture SSH-based remote TTS invocation using termux-tts-speak on Android
|
|
22
|
+
# @dependencies ssh.exe (OpenSSH for Windows), termux-tts-speak (on Android), termux-api (on Android)
|
|
23
|
+
# @entrypoints Called by play-tts.ps1 router when provider=termux-ssh
|
|
24
|
+
# @patterns Remote TTS invocation, SSH host alias configuration, graceful fallback
|
|
25
|
+
# @related play-tts.ps1, provider-manager.ps1
|
|
26
|
+
#
|
|
27
|
+
# SETUP INSTRUCTIONS:
|
|
28
|
+
# ===================
|
|
29
|
+
# 1. On Android device (Termux):
|
|
30
|
+
# - Install: pkg install termux-api openssh
|
|
31
|
+
# - Install Termux:API app from F-Droid or Google Play
|
|
32
|
+
# - Start SSH server: sshd
|
|
33
|
+
#
|
|
34
|
+
# 2. On Windows:
|
|
35
|
+
# - Add to %USERPROFILE%\.ssh\config:
|
|
36
|
+
# Host android
|
|
37
|
+
# HostName <your-android-ip>
|
|
38
|
+
# User <your-termux-username>
|
|
39
|
+
# Port 8022
|
|
40
|
+
# IdentityFile ~/.ssh/id_rsa
|
|
41
|
+
#
|
|
42
|
+
# 3. Configure AgentVibes:
|
|
43
|
+
# - echo android > %USERPROFILE%\.claude\termux-ssh-host.txt
|
|
44
|
+
# OR
|
|
45
|
+
# - echo android > .claude\termux-ssh-host.txt (project-local)
|
|
46
|
+
#
|
|
47
|
+
# 4. Set provider:
|
|
48
|
+
# - echo termux-ssh > %USERPROFILE%\.claude\tts-provider.txt
|
|
49
|
+
#
|
|
50
|
+
|
|
51
|
+
param(
|
|
52
|
+
[Parameter(Mandatory = $true, Position = 0)]
|
|
53
|
+
[string]$Text,
|
|
54
|
+
|
|
55
|
+
[Parameter(Mandatory = $false, Position = 1)]
|
|
56
|
+
[string]$VoiceOverride # Not used for termux-ssh, kept for interface compatibility
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# Resolve ClaudeDir (project-local preferred, fallback to global)
|
|
60
|
+
$ScriptPath = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
61
|
+
$ProjectClaudeDir = Split-Path -Parent $ScriptPath
|
|
62
|
+
if (Test-Path (Join-Path $ProjectClaudeDir "config")) {
|
|
63
|
+
$ClaudeDir = $ProjectClaudeDir
|
|
64
|
+
} else {
|
|
65
|
+
$ClaudeDir = "$env:USERPROFILE\.claude"
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
# @function Get-SshHost
|
|
69
|
+
# @intent Determine SSH host alias for Android device
|
|
70
|
+
# @why Allows users to configure their own SSH connection without hardcoded values
|
|
71
|
+
# Priority: env var > project config > script-dir config > global config
|
|
72
|
+
function Get-SshHost {
|
|
73
|
+
if ($env:TERMUX_SSH_HOST) { return $env:TERMUX_SSH_HOST }
|
|
74
|
+
|
|
75
|
+
$projectFile = Join-Path $ClaudeDir "termux-ssh-host.txt"
|
|
76
|
+
if (Test-Path $projectFile) {
|
|
77
|
+
$val = (Get-Content $projectFile -Raw).Trim()
|
|
78
|
+
if ($val) { return $val }
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
$globalFile = "$env:USERPROFILE\.claude\termux-ssh-host.txt"
|
|
82
|
+
if (Test-Path $globalFile) {
|
|
83
|
+
$val = (Get-Content $globalFile -Raw).Trim()
|
|
84
|
+
if ($val) { return $val }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return ""
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
# @function Test-SshConnection
|
|
91
|
+
# @intent Quick reachability check before sending TTS
|
|
92
|
+
# @why Prevents long hangs if Android is offline
|
|
93
|
+
function Test-SshConnection {
|
|
94
|
+
param([string]$SshTarget)
|
|
95
|
+
try {
|
|
96
|
+
$null = ssh -o ConnectTimeout=2 -o BatchMode=yes $SshTarget "echo ok" 2>&1
|
|
97
|
+
return $LASTEXITCODE -eq 0
|
|
98
|
+
} catch {
|
|
99
|
+
return $false
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
# --- Main ---
|
|
104
|
+
|
|
105
|
+
if (-not $Text) {
|
|
106
|
+
Write-Host "[ERROR] No text provided for TTS" -ForegroundColor Red
|
|
107
|
+
exit 1
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
$SshAlias = Get-SshHost
|
|
111
|
+
|
|
112
|
+
if (-not $SshAlias) {
|
|
113
|
+
Write-Host "[ERROR] Termux SSH provider not configured" -ForegroundColor Red
|
|
114
|
+
Write-Host " Set SSH host alias in one of:" -ForegroundColor Yellow
|
|
115
|
+
Write-Host " Environment: `$env:TERMUX_SSH_HOST = 'android'" -ForegroundColor Yellow
|
|
116
|
+
Write-Host " Global: echo android > `"$env:USERPROFILE\.claude\termux-ssh-host.txt`"" -ForegroundColor Yellow
|
|
117
|
+
Write-Host " Project: echo android > .claude\termux-ssh-host.txt" -ForegroundColor Yellow
|
|
118
|
+
exit 1
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (-not (Test-SshConnection -SshTarget $SshAlias)) {
|
|
122
|
+
Write-Host "[WARNING] Cannot connect to SSH host '$SshAlias' - Android offline?" -ForegroundColor Yellow
|
|
123
|
+
exit 1
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
# Escape single quotes for safe shell transmission
|
|
127
|
+
$SafeText = $Text -replace "'", "'\'''"
|
|
128
|
+
|
|
129
|
+
# Send TTS to Android asynchronously (audio plays on device)
|
|
130
|
+
$job = Start-Job -ScriptBlock {
|
|
131
|
+
param($alias, $text)
|
|
132
|
+
ssh -o ConnectTimeout=5 $alias "termux-tts-speak '$text'" 2>&1
|
|
133
|
+
} -ArgumentList $SshAlias, $SafeText
|
|
134
|
+
|
|
135
|
+
Write-Host "[OK] TTS sent to Android ($SshAlias) via SSH" -ForegroundColor Green
|
|
136
|
+
|
|
137
|
+
# Output empty string — audio plays on Android, not locally
|
|
138
|
+
Write-Output ""
|
|
@@ -14,15 +14,20 @@ param(
|
|
|
14
14
|
)
|
|
15
15
|
|
|
16
16
|
# Configuration paths
|
|
17
|
-
#
|
|
17
|
+
# Priority: CLAUDE_PROJECT_DIR env var → script's parent project → user profile
|
|
18
18
|
$ScriptPath = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
19
|
-
$ProjectClaudeDir = Join-Path (Split-Path -Parent (Split-Path -Parent $ScriptPath)) ".claude"
|
|
20
19
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
$ClaudeDir = $ProjectClaudeDir
|
|
20
|
+
if ($env:CLAUDE_PROJECT_DIR -and (Test-Path "$env:CLAUDE_PROJECT_DIR\.claude")) {
|
|
21
|
+
$ClaudeDir = "$env:CLAUDE_PROJECT_DIR\.claude"
|
|
24
22
|
} else {
|
|
25
|
-
$
|
|
23
|
+
$PackageClaudeDir = Join-Path (Split-Path -Parent (Split-Path -Parent $ScriptPath)) ".claude"
|
|
24
|
+
if (Test-Path "$env:USERPROFILE\.claude\tts-provider.txt") {
|
|
25
|
+
$ClaudeDir = "$env:USERPROFILE\.claude"
|
|
26
|
+
} elseif (Test-Path $PackageClaudeDir) {
|
|
27
|
+
$ClaudeDir = $PackageClaudeDir
|
|
28
|
+
} else {
|
|
29
|
+
$ClaudeDir = "$env:USERPROFILE\.claude"
|
|
30
|
+
}
|
|
26
31
|
}
|
|
27
32
|
|
|
28
33
|
$HooksDir = "$ClaudeDir\hooks-windows"
|
|
@@ -56,6 +61,9 @@ switch ($ActiveProvider) {
|
|
|
56
61
|
"soprano" {
|
|
57
62
|
$ProviderScript = "$HooksDir\play-tts-soprano.ps1"
|
|
58
63
|
}
|
|
64
|
+
"termux-ssh" {
|
|
65
|
+
$ProviderScript = "$HooksDir\play-tts-termux-ssh.ps1"
|
|
66
|
+
}
|
|
59
67
|
default {
|
|
60
68
|
Write-Host "[ERROR] Unknown provider: $ActiveProvider" -ForegroundColor Red
|
|
61
69
|
Write-Host "Use: .\provider-manager.ps1 list" -ForegroundColor Yellow
|
|
@@ -22,7 +22,7 @@ if (Test-Path (Join-Path $ProjectClaudeDir "config")) {
|
|
|
22
22
|
$ClaudeDir = "$env:USERPROFILE\.claude"
|
|
23
23
|
}
|
|
24
24
|
$ProviderFile = "$ClaudeDir\tts-provider.txt"
|
|
25
|
-
$ValidProviders = @('piper', 'sapi', 'soprano')
|
|
25
|
+
$ValidProviders = @('piper', 'sapi', 'soprano', 'termux-ssh')
|
|
26
26
|
# Backwards compat: normalize old names
|
|
27
27
|
if ($Provider -eq 'windows-piper') { $Provider = 'piper' }
|
|
28
28
|
if ($Provider -eq 'windows-sapi') { $Provider = 'sapi' }
|
|
@@ -91,6 +91,21 @@ function Get-AvailableProviders {
|
|
|
91
91
|
installed = $sopranoInstalled
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
# Check if termux-ssh is configured (ssh.exe available + host configured)
|
|
95
|
+
$termuxSshConfigured = $false
|
|
96
|
+
$sshExe = Get-Command ssh.exe -ErrorAction SilentlyContinue
|
|
97
|
+
if ($sshExe) {
|
|
98
|
+
$hostFile = "$ClaudeDir\termux-ssh-host.txt"
|
|
99
|
+
$globalHostFile = "$env:USERPROFILE\.claude\termux-ssh-host.txt"
|
|
100
|
+
$termuxSshConfigured = (Test-Path $hostFile) -or (Test-Path $globalHostFile) -or ($env:TERMUX_SSH_HOST -ne "")
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
$available += @{
|
|
104
|
+
name = "termux-ssh"
|
|
105
|
+
description = "Android Termux SSH TTS (plays on Android via termux-tts-speak)"
|
|
106
|
+
installed = $termuxSshConfigured
|
|
107
|
+
}
|
|
108
|
+
|
|
94
109
|
return $available
|
|
95
110
|
}
|
|
96
111
|
|
package/CLAUDE.md
CHANGED
|
@@ -8,6 +8,10 @@
|
|
|
8
8
|
|
|
9
9
|
AgentVibes is a Text-to-Speech system for AI assistants with personality support.
|
|
10
10
|
|
|
11
|
+
### Time Estimates
|
|
12
|
+
|
|
13
|
+
Always estimate in **AI time** — Paul does not write code, that's Claude's job. "30 minutes" means 30 human minutes; the AI equivalent is seconds to a few minutes. When giving estimates, say things like "~10 minutes of AI work" or "quick — a few minutes tops."
|
|
14
|
+
|
|
11
15
|
### Project Uses BMAD Methodology
|
|
12
16
|
|
|
13
17
|
This project follows **BMAD (BMM - Business Model Methodology)** for all story development:
|
package/README.md
CHANGED
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
>
|
|
5
5
|
> 🌐 **[agentvibes.org](https://agentvibes.org)**
|
|
6
6
|
>
|
|
7
|
-
> Professional text-to-speech for **Claude Code**, **Claude Desktop**,
|
|
7
|
+
> Professional text-to-speech for **Claude Code**, **Claude Desktop**, and **OpenClaw** - **Soprano** (Neural), **Piper TTS** (Free!), **macOS Say** (Built-in!), or **Windows SAPI** (Zero Setup!)
|
|
8
8
|
|
|
9
9
|
[](https://www.npmjs.com/package/agentvibes)
|
|
10
10
|
[](https://github.com/paulpreibisch/AgentVibes/actions/workflows/test.yml)
|
|
11
11
|
[](https://github.com/paulpreibisch/AgentVibes/actions/workflows/publish.yml)
|
|
12
12
|
[](https://opensource.org/licenses/Apache-2.0)
|
|
13
13
|
|
|
14
|
-
**Author**: Paul Preibisch ([@997Fire](https://x.com/997Fire)) | **Version**: v4.
|
|
14
|
+
**Author**: Paul Preibisch ([@997Fire](https://x.com/997Fire)) | **Version**: v4.5
|
|
15
15
|
|
|
16
16
|
---
|
|
17
17
|
|
|
@@ -36,11 +36,41 @@
|
|
|
36
36
|
|
|
37
37
|
**AgentVibes adds lively voice narration to your Claude AI sessions!**
|
|
38
38
|
|
|
39
|
-
Whether you're coding in Claude Code, chatting in Claude Desktop,
|
|
39
|
+
Whether you're coding in Claude Code, chatting in Claude Desktop, or running OpenClaw — AgentVibes brings AI to life with professional voices and personalities.
|
|
40
40
|
|
|
41
41
|
---
|
|
42
42
|
|
|
43
|
-
## 🌟 NEW IN v4.
|
|
43
|
+
## 🌟 NEW IN v4.5 — "Speak Every Language" Release
|
|
44
|
+
|
|
45
|
+
### 🌍 Multilingual TUI — 9 Languages
|
|
46
|
+
|
|
47
|
+
Every screen, button, and label in `npx agentvibes` is now fully translated:
|
|
48
|
+
|
|
49
|
+
- **English, Spanish, French, German, Portuguese, Japanese, Korean, Chinese (Simplified), Italian**
|
|
50
|
+
- Language selection on first launch — pick your language before anything else
|
|
51
|
+
- Language sub-tab in Settings — switch live, no restart needed
|
|
52
|
+
- All tab labels, buttons, footer hints, status messages, and BMAD/Receiver tabs translated
|
|
53
|
+
- Per-language i18n files (`src/i18n/en.js`, `es.js`, `fr.js`, ...) with English fallback
|
|
54
|
+
|
|
55
|
+
### 🪟 Windows Security Hardening
|
|
56
|
+
|
|
57
|
+
- **Unpredictable temp files** — `randomUUID()` replaces `Date.now()` in all temp filenames (JS + PowerShell)
|
|
58
|
+
- **No shell injection** — `spawnSync` replaces `execSync(..., { shell: true })` for `which` lookups
|
|
59
|
+
- **Smart music player detection** — `detectMp3Player()` replaces hardcoded `ffplay` on Windows
|
|
60
|
+
- **Boolean fix** — `isWindowsTerminal` now returns `true/false`, not the `WT_SESSION` UUID string
|
|
61
|
+
|
|
62
|
+
### 🎙️ Cross-Platform BMAD Speak
|
|
63
|
+
|
|
64
|
+
BMAD (Build More Architect Dreams) is an AI multi-agent framework where specialized agents — Architect, PM, Developer, QA, and Analyst — collaborate to build software. With this release, every agent in a BMAD party mode session now speaks aloud with their own unique voice, personality, and music on Windows — making each role instantly recognizable.
|
|
65
|
+
|
|
66
|
+
- `bmad-speak.js` — cross-platform entry point; auto-routes to PowerShell on Windows or bash on Mac/Linux
|
|
67
|
+
- `bmad-speak.ps1` — native Windows BMAD speak with per-agent personality routing
|
|
68
|
+
|
|
69
|
+
### 🧪 600 Tests, Zero Failures
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## 🌟 v4.4 — Full Platform Parity Release
|
|
44
74
|
|
|
45
75
|
### 🪟 Windows MCP Parity — 27/27 Tools Working
|
|
46
76
|
|
|
@@ -294,7 +324,7 @@ Configure via: `npx agentvibes` → Music tab
|
|
|
294
324
|
- 🎭 **Multi-Provider Support** - Soprano (neural), Piper TTS (50+ free voices), macOS Say (100+ built-in), or Windows SAPI
|
|
295
325
|
- 🎙️ **27+ Professional AI Voices** - Character voices, accents, and unique personalities
|
|
296
326
|
- 🎙️ **Verbosity Control** - Choose how much Claude speaks (LOW, MEDIUM, HIGH)
|
|
297
|
-
- 🎙️ **AgentVibes MCP** - Natural language control ("Switch to Aria voice") for Claude Code
|
|
327
|
+
- 🎙️ **AgentVibes MCP** - Natural language control ("Switch to Aria voice") for Claude Code & Desktop
|
|
298
328
|
- 🔊 **SSH Audio Optimization** - Auto-detects remote sessions and eliminates static (VS Code Remote SSH, cloud dev)
|
|
299
329
|
|
|
300
330
|
**🎭 Personalization:**
|
|
@@ -345,7 +375,7 @@ All 50+ Piper voices AgentVibes provides are sourced from Hugging Face's open-so
|
|
|
345
375
|
### AgentVibes MCP (Natural Language Control)
|
|
346
376
|
- [🎙️ AgentVibes MCP Overview](#%EF%B8%8F-agentvibes-mcp) - **Easiest way** - Natural language commands
|
|
347
377
|
- [For Claude Desktop](docs/mcp-setup.md#for-claude-desktop) - Windows/WSL setup, Python requirements
|
|
348
|
-
|
|
378
|
+
|
|
349
379
|
- [For Claude Code](docs/mcp-setup.md#for-claude-code) - Project-specific setup
|
|
350
380
|
|
|
351
381
|
### Core Features
|
|
@@ -473,9 +503,9 @@ We've now enhanced this capability by adding an MCP (Model Context Protocol) ser
|
|
|
473
503
|
|
|
474
504
|
Setting it up is straightforward: just add the MCP server to your Claude Code configuration files.
|
|
475
505
|
|
|
476
|
-
But the convenience doesn't stop there. With the MCP server in place, Claude Desktop can now use Agent Vibes too!
|
|
506
|
+
But the convenience doesn't stop there. With the MCP server in place, Claude Desktop can now use Agent Vibes too!
|
|
477
507
|
|
|
478
|
-
We're thrilled about this expansion because it means Claude Desktop
|
|
508
|
+
We're thrilled about this expansion because it means Claude Desktop can finally talk back as well!
|
|
479
509
|
|
|
480
510
|
If you decide to use the MCP server on Claude Desktop, after configuration, give Claude Desktop this command: "every time i give you a command, speak the acknowledgement using agentvibes and the confirmation about what you completed, when done"—and watch the magic happen!
|
|
481
511
|
|
|
@@ -483,7 +513,7 @@ If you decide to use the MCP server on Claude Desktop, after configuration, give
|
|
|
483
513
|
|
|
484
514
|
Just say "Switch to Aria voice" or "Speak in Spanish" instead of typing commands.
|
|
485
515
|
|
|
486
|
-
**Works in:** Claude Desktop, Claude Code
|
|
516
|
+
**Works in:** Claude Desktop, Claude Code
|
|
487
517
|
|
|
488
518
|
**[→ View Complete MCP Setup Guide](docs/mcp-setup.md)** - Full setup for all platforms, configuration examples, available tools, and MCP vs slash commands comparison
|
|
489
519
|
|