agentvibes 5.13.1 → 5.15.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/audio/ui/CREDITS.txt +16 -16
- package/.claude/commands/agent-vibes-bmad-voices.md +117 -117
- package/.claude/commands/agent-vibes-rdp.md +24 -24
- package/.claude/config/audio-effects.cfg.sample +52 -52
- package/.claude/docs/TERMUX_SETUP.md +408 -408
- package/.claude/github-star-reminder.txt +1 -1
- package/.claude/hooks/kokoro-server.py +219 -219
- package/.claude/hooks/kokoro-tts.py +141 -141
- package/.claude/hooks/party-stage-roster.py +328 -328
- package/.claude/hooks/play-tts-kokoro.sh +7 -2
- package/.claude/hooks/play-tts-ssh-remote.sh +38 -3
- package/.claude/hooks/requirements.txt +6 -6
- package/.claude/hooks/soprano-gradio-synth.py +139 -139
- package/.claude/hooks/translator.py +237 -237
- package/.claude/hooks-windows/agentvibes-session-id.ps1 +83 -0
- package/.claude/hooks-windows/kokoro-server.py +219 -219
- package/.claude/hooks-windows/kokoro-tts.py +107 -107
- package/.claude/hooks-windows/play-tts.ps1 +112 -1
- package/.claude/hooks-windows/session-start-tts.ps1 +36 -0
- package/.claude/hooks-windows/soprano-gradio-synth.py +153 -153
- package/.claude/verbosity.txt +1 -1
- package/.clawdbot/README.md +105 -105
- package/CLAUDE.md +84 -176
- package/README.md +35 -2
- package/RELEASE_NOTES.md +139 -0
- package/WINDOWS-SETUP.md +208 -208
- package/bin/agent-vibes +39 -39
- package/bin/mcp-server.js +121 -121
- package/bin/test-bmad-pr +78 -78
- package/mcp-server/QUICK_START.md +203 -203
- package/mcp-server/README.md +345 -345
- package/mcp-server/examples/claude_desktop_config.json +11 -11
- package/mcp-server/examples/claude_desktop_config_piper.json +9 -9
- package/mcp-server/examples/custom_instructions.md +169 -169
- package/mcp-server/install-deps.js +12 -2
- package/mcp-server/server.py +2111 -2085
- package/mcp-server/test_mcp_correctness.py +504 -504
- package/mcp-server/test_windows_script_parity.py +339 -339
- package/package.json +6 -3
- package/setup-ssh-receiver.ps1 +259 -0
- package/setup-windows.ps1 +39 -29
- package/src/cli/list-personalities.js +110 -110
- package/src/commands/bmad-voices.js +394 -394
- package/src/console/brand-colors.js +13 -13
- package/src/console/constants/personalities.js +44 -44
- package/src/console/tabs/agents-tab.js +14 -4
- package/src/console/tabs/help-tab.js +314 -314
- package/src/console/tabs/readme-tab.js +272 -272
- package/src/console/tabs/settings-tab.js +31 -6
- package/src/console/tabs/setup-tab.js +154 -32
- package/src/console/tabs/voices-tab.js +48 -6
- package/src/console/widgets/destroy-list.js +25 -25
- package/src/console/widgets/notice.js +55 -55
- package/src/installer/language-screen.js +31 -31
- package/src/installer/music-file-input.js +304 -304
- package/src/installer.js +98 -37
- package/src/services/language-service.js +47 -47
- package/src/services/llm-provider-service.js +4 -1
- package/src/services/provider-voice-catalog.js +9 -5
- package/src/utils/audio-format-validator.js +277 -277
- package/src/utils/dependency-checker.js +469 -469
- package/src/utils/file-ownership-verifier.js +358 -358
- package/src/utils/music-file-validator.js +285 -285
- package/src/utils/preview-list-prompt.js +144 -144
- package/src/utils/secure-music-storage.js +412 -412
- package/templates/agentvibes-receiver.ps1 +311 -0
- package/templates/agentvibes-receiver.sh +297 -297
- package/voice-assignments.json +8244 -8244
- package/.agentvibes/config.json +0 -17
- package/.agentvibes/install-manifest.json +0 -362
- package/.claude/config/audio-effects.cfg +0 -7
- package/.claude/config/audio-effects.cfg.bak-kokoro +0 -7
- package/.claude/config/background-music-enabled.txt +0 -1
- package/.claude/config/background-music-position.txt +0 -27
- 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/language.txt +0 -1
- package/.claude/config/personality.txt +0 -1
- package/.claude/config/reverb-level.txt +0 -1
- package/.claude/config/tts-speech-rate.txt +0 -1
- package/.claude/config/tts-verbosity.txt +0 -1
- package/.claude/hooks-windows/audio-cache-utils.ps1.user.bak +0 -119
- package/.claude/hooks-windows/soprano-gradio-synth.py.user.bak +0 -153
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "agentvibes",
|
|
4
|
-
"version": "5.
|
|
4
|
+
"version": "5.15.0",
|
|
5
5
|
"description": "Now your AI Agents can finally talk back! Professional TTS voice for Claude Code, Claude Desktop (via MCP), and Clawdbot with multi-provider support.",
|
|
6
6
|
"homepage": "https://agentvibes.org",
|
|
7
7
|
"keywords": [
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
"bin/",
|
|
38
38
|
"src/",
|
|
39
39
|
"templates/*.sh",
|
|
40
|
+
"templates/*.ps1",
|
|
40
41
|
"templates/*.md",
|
|
41
42
|
"templates/audio/*.mp3",
|
|
42
43
|
"mcp-server/*.py",
|
|
@@ -56,7 +57,7 @@
|
|
|
56
57
|
".claude/output-styles/",
|
|
57
58
|
".claude/audio/tracks/",
|
|
58
59
|
".claude/audio/ui/",
|
|
59
|
-
".claude/config
|
|
60
|
+
".claude/config/*.sample",
|
|
60
61
|
".claude/docs/",
|
|
61
62
|
".claude/language-voices.yaml",
|
|
62
63
|
".claude/settings.json",
|
|
@@ -65,10 +66,12 @@
|
|
|
65
66
|
".claude/verbosity.txt",
|
|
66
67
|
".claude/github-star-reminder.txt",
|
|
67
68
|
".claude/audio/voice-samples/",
|
|
68
|
-
".agentvibes/",
|
|
69
|
+
".agentvibes/bmad/",
|
|
69
70
|
".clawdbot/",
|
|
70
71
|
".mcp.json",
|
|
72
|
+
"!**/*.user.bak",
|
|
71
73
|
"setup-windows.ps1",
|
|
74
|
+
"setup-ssh-receiver.ps1",
|
|
72
75
|
"WINDOWS-SETUP.md",
|
|
73
76
|
"README.md",
|
|
74
77
|
"RELEASE_NOTES.md",
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
#Requires -RunAsAdministrator
|
|
2
|
+
#
|
|
3
|
+
# AgentVibes SSH Receiver Setup (Windows)
|
|
4
|
+
# Run this in an ADMIN PowerShell:
|
|
5
|
+
# powershell -ExecutionPolicy Bypass -File setup-ssh-receiver.ps1
|
|
6
|
+
#
|
|
7
|
+
|
|
8
|
+
Write-Host "`n=== AgentVibes SSH Receiver Setup ===" -ForegroundColor Cyan
|
|
9
|
+
Write-Host ""
|
|
10
|
+
|
|
11
|
+
$Username = $env:USERNAME
|
|
12
|
+
$TailscaleIp = ""
|
|
13
|
+
try {
|
|
14
|
+
$TailscaleIp = (tailscale ip -4 2>$null).Trim()
|
|
15
|
+
} catch {}
|
|
16
|
+
|
|
17
|
+
if (-not $TailscaleIp) {
|
|
18
|
+
Write-Host "WARNING: Tailscale not found. Install Tailscale first for secure setup." -ForegroundColor Yellow
|
|
19
|
+
Write-Host " Falling back to listen on all interfaces." -ForegroundColor Yellow
|
|
20
|
+
$TailscaleIp = "0.0.0.0"
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
# Step 1: Stop sshd
|
|
24
|
+
Write-Host "[1/8] Stopping sshd..." -ForegroundColor Yellow
|
|
25
|
+
Stop-Service sshd -ErrorAction SilentlyContinue
|
|
26
|
+
|
|
27
|
+
# Step 2: Backup original config
|
|
28
|
+
Write-Host "[2/8] Backing up original sshd_config..." -ForegroundColor Yellow
|
|
29
|
+
$configPath = "C:\ProgramData\ssh\sshd_config"
|
|
30
|
+
$backupPath = "C:\ProgramData\ssh\sshd_config.bak"
|
|
31
|
+
if (Test-Path $configPath) {
|
|
32
|
+
# Only create the backup once — never overwrite it, so the user's original
|
|
33
|
+
# sshd_config is always recoverable even if this script is run multiple times.
|
|
34
|
+
if (-not (Test-Path $backupPath)) {
|
|
35
|
+
Copy-Item $configPath $backupPath
|
|
36
|
+
Write-Host " Backup: $backupPath" -ForegroundColor Gray
|
|
37
|
+
} else {
|
|
38
|
+
Write-Host " Backup already exists: $backupPath (skipped)" -ForegroundColor DarkGray
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
# Step 3: Deploy hardened config with user-specific values
|
|
43
|
+
Write-Host "[3/8] Deploying hardened sshd_config..." -ForegroundColor Yellow
|
|
44
|
+
$hardenedConfig = Join-Path $PSScriptRoot "templates\sshd_config_hardened"
|
|
45
|
+
if (Test-Path $hardenedConfig) {
|
|
46
|
+
$config = Get-Content $hardenedConfig -Raw
|
|
47
|
+
$config = $config -replace 'YourUsername', $Username
|
|
48
|
+
$config = $config -replace 'ListenAddress 0\.0\.0\.0', "ListenAddress $TailscaleIp"
|
|
49
|
+
$receiverPath = "C:\Users\$Username\.agentvibes\play-remote.ps1"
|
|
50
|
+
$config = $config -replace 'C:\\Users\\YourUsername\\\.agentvibes\\play-remote\.ps1', $receiverPath
|
|
51
|
+
Set-Content -Path $configPath -Value $config
|
|
52
|
+
Write-Host " Port: 45123" -ForegroundColor Green
|
|
53
|
+
Write-Host " Listen: $TailscaleIp" -ForegroundColor Green
|
|
54
|
+
Write-Host " Auth: Key-only, no passwords" -ForegroundColor Green
|
|
55
|
+
Write-Host " ForceCommand: Receiver script only, no shell" -ForegroundColor Green
|
|
56
|
+
} else {
|
|
57
|
+
Write-Host " ERROR: $hardenedConfig not found!" -ForegroundColor Red
|
|
58
|
+
exit 1
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
# Step 4: Install receiver script
|
|
62
|
+
Write-Host "[4/8] Installing receiver script..." -ForegroundColor Yellow
|
|
63
|
+
$agentvibesDir = "$env:USERPROFILE\.agentvibes"
|
|
64
|
+
if (-not (Test-Path $agentvibesDir)) {
|
|
65
|
+
New-Item -ItemType Directory -Path $agentvibesDir -Force | Out-Null
|
|
66
|
+
}
|
|
67
|
+
$receiverSrc = Join-Path $PSScriptRoot "templates\agentvibes-receiver.ps1"
|
|
68
|
+
if (Test-Path $receiverSrc) {
|
|
69
|
+
# Stamp the installing user's home path so the receiver can find hooks/config
|
|
70
|
+
# even when sshd runs it as a different user (e.g. agentvibes-receiver)
|
|
71
|
+
$content = Get-Content $receiverSrc -Raw
|
|
72
|
+
$content = $content -replace '__OWNER_HOME__', $env:USERPROFILE
|
|
73
|
+
Set-Content -Path "$agentvibesDir\play-remote.ps1" -Value $content
|
|
74
|
+
Write-Host " Installed to: $agentvibesDir\play-remote.ps1" -ForegroundColor Green
|
|
75
|
+
Write-Host " Owner home: $env:USERPROFILE" -ForegroundColor Gray
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
# Step 5: Grant agentvibes-receiver read access to required paths
|
|
79
|
+
Write-Host "[5/8] Granting agentvibes-receiver permissions..." -ForegroundColor Yellow
|
|
80
|
+
$ReceiverUser = "agentvibes-receiver"
|
|
81
|
+
$userExists = Get-LocalUser -Name $ReceiverUser -ErrorAction SilentlyContinue
|
|
82
|
+
if ($userExists) {
|
|
83
|
+
# Read+Execute on .agentvibes
|
|
84
|
+
icacls "$agentvibesDir" /grant "${ReceiverUser}:(OI)(CI)RX" /T /Q 2>$null
|
|
85
|
+
# Modify on tts-queue (receiver writes request files) - create if absent
|
|
86
|
+
$queueDir = "$agentvibesDir\tts-queue"
|
|
87
|
+
if (-not (Test-Path $queueDir)) {
|
|
88
|
+
New-Item -ItemType Directory -Path $queueDir -Force | Out-Null
|
|
89
|
+
}
|
|
90
|
+
icacls "$queueDir" /grant "${ReceiverUser}:(OI)(CI)M" /T /Q 2>$null
|
|
91
|
+
# Modify on receiver.log - touch if absent so icacls can target it
|
|
92
|
+
$logFile = "$agentvibesDir\receiver.log"
|
|
93
|
+
if (-not (Test-Path $logFile)) {
|
|
94
|
+
New-Item -ItemType File -Path $logFile -Force | Out-Null
|
|
95
|
+
}
|
|
96
|
+
icacls "$logFile" /grant "${ReceiverUser}:M" /Q 2>$null
|
|
97
|
+
# Targeted permissions on .claude subdirectories (not blanket Modify on all of .claude)
|
|
98
|
+
$claudeDir = "$env:USERPROFILE\.claude"
|
|
99
|
+
if (Test-Path $claudeDir) {
|
|
100
|
+
# RX on .claude root (needed for Test-Path checks)
|
|
101
|
+
icacls "$claudeDir" /grant "${ReceiverUser}:RX" /Q 2>$null
|
|
102
|
+
# RX on hooks (read + execute scripts)
|
|
103
|
+
$hooksDir = "$claudeDir\hooks-windows"
|
|
104
|
+
if (Test-Path $hooksDir) {
|
|
105
|
+
icacls "$hooksDir" /grant "${ReceiverUser}:(OI)(CI)RX" /T /Q 2>$null
|
|
106
|
+
}
|
|
107
|
+
# Modify on config (receiver writes voice, speed, music settings)
|
|
108
|
+
$configDir = "$claudeDir\config"
|
|
109
|
+
if (Test-Path $configDir) {
|
|
110
|
+
icacls "$configDir" /grant "${ReceiverUser}:(OI)(CI)M" /T /Q 2>$null
|
|
111
|
+
}
|
|
112
|
+
# Modify on audio output dir (receiver writes WAV files)
|
|
113
|
+
$audioDir = "$claudeDir\audio"
|
|
114
|
+
if (-not (Test-Path $audioDir)) {
|
|
115
|
+
New-Item -ItemType Directory -Path $audioDir -Force | Out-Null
|
|
116
|
+
}
|
|
117
|
+
icacls "$audioDir" /grant "${ReceiverUser}:(OI)(CI)M" /T /Q 2>$null
|
|
118
|
+
# Modify on tts-voice.txt and tts-provider.txt (receiver reads/writes these)
|
|
119
|
+
foreach ($txt in @("tts-voice.txt", "tts-provider.txt")) {
|
|
120
|
+
$f = "$claudeDir\$txt"
|
|
121
|
+
if (Test-Path $f) {
|
|
122
|
+
icacls "$f" /grant "${ReceiverUser}:M" /Q 2>$null
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
# Read+Execute on Piper install
|
|
127
|
+
$piperDir = "$env:LOCALAPPDATA\Programs\Piper"
|
|
128
|
+
if (Test-Path $piperDir) {
|
|
129
|
+
icacls "$piperDir" /grant "${ReceiverUser}:(OI)(CI)RX" /T /Q 2>$null
|
|
130
|
+
}
|
|
131
|
+
Write-Host " Granted $ReceiverUser access to .agentvibes, .claude, Piper" -ForegroundColor Green
|
|
132
|
+
} else {
|
|
133
|
+
Write-Host " SKIP: $ReceiverUser user not found (create it first)" -ForegroundColor Yellow
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
# Step 6: Firewall rule
|
|
137
|
+
Write-Host "[6/8] Adding firewall rule for port 45123 (Tailscale only)..." -ForegroundColor Yellow
|
|
138
|
+
$existingRule = Get-NetFirewallRule -Name "AgentVibes-SSH-Receiver" -ErrorAction SilentlyContinue
|
|
139
|
+
if ($existingRule) {
|
|
140
|
+
Remove-NetFirewallRule -Name "AgentVibes-SSH-Receiver"
|
|
141
|
+
}
|
|
142
|
+
New-NetFirewallRule -Name "AgentVibes-SSH-Receiver" `
|
|
143
|
+
-DisplayName "AgentVibes SSH Receiver" `
|
|
144
|
+
-Description "Allow AgentVibes TTS receiver on port 45123 from Tailscale only" `
|
|
145
|
+
-Direction Inbound `
|
|
146
|
+
-Protocol TCP `
|
|
147
|
+
-LocalPort 45123 `
|
|
148
|
+
-RemoteAddress 100.0.0.0/8 `
|
|
149
|
+
-Action Allow | Out-Null
|
|
150
|
+
Write-Host " Firewall: Allow TCP 45123 from 100.0.0.0/8 only" -ForegroundColor Green
|
|
151
|
+
|
|
152
|
+
# Step 7: Set up admin authorized keys
|
|
153
|
+
Write-Host "[7/8] Configuring authorized keys..." -ForegroundColor Yellow
|
|
154
|
+
$adminKeysFile = "C:\ProgramData\ssh\administrators_authorized_keys"
|
|
155
|
+
if (-not (Test-Path $adminKeysFile)) {
|
|
156
|
+
Write-Host " NOTE: No SSH keys found at $adminKeysFile" -ForegroundColor Yellow
|
|
157
|
+
Write-Host " Add your sender's public key there, then run:" -ForegroundColor Yellow
|
|
158
|
+
Write-Host " icacls `"$adminKeysFile`" /inheritance:r /grant `"SYSTEM:F`" /grant `"BUILTIN\Administrators:F`"" -ForegroundColor Cyan
|
|
159
|
+
} else {
|
|
160
|
+
& icacls $adminKeysFile /inheritance:r /grant "SYSTEM:F" /grant "BUILTIN\Administrators:F" 2>$null
|
|
161
|
+
}
|
|
162
|
+
# Step 7.5: Install user-session watcher (audio device access)
|
|
163
|
+
# CRITICAL: SSH receiver runs in session 0 (no audio). The receiver writes
|
|
164
|
+
# to ~/.agentvibes/tts-queue/, and a watcher running in the user's
|
|
165
|
+
# interactive session picks up requests and plays audio.
|
|
166
|
+
Write-Host "[7.5/8] Installing user-session watcher..." -ForegroundColor Yellow
|
|
167
|
+
# Copy watcher from repo source — single source of truth, no embedded heredoc
|
|
168
|
+
$sourceWatcher = Join-Path $PSScriptRoot ".claude\hooks-windows\tts-watcher.ps1"
|
|
169
|
+
if (-not (Test-Path $sourceWatcher)) {
|
|
170
|
+
Write-Host " ERROR: $sourceWatcher not found — run from the AgentVibes repo root" -ForegroundColor Red
|
|
171
|
+
exit 1
|
|
172
|
+
}
|
|
173
|
+
$watcherDestDir = "$env:USERPROFILE\.agentvibes"
|
|
174
|
+
if (-not (Test-Path $watcherDestDir)) { New-Item -ItemType Directory -Path $watcherDestDir -Force | Out-Null }
|
|
175
|
+
Copy-Item -Path $sourceWatcher -Destination "$watcherDestDir\tts-watcher.ps1" -Force
|
|
176
|
+
|
|
177
|
+
$vbsLauncher = @'
|
|
178
|
+
Set WshShell = CreateObject("WScript.Shell")
|
|
179
|
+
WshShell.Run "powershell.exe -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File """ & WshShell.ExpandEnvironmentStrings("%USERPROFILE%") & "\.agentvibes\tts-watcher.ps1""", 0, False
|
|
180
|
+
'@
|
|
181
|
+
Set-Content -Path "$env:USERPROFILE\.agentvibes\start-watcher.vbs" -Value $vbsLauncher -Encoding ASCII
|
|
182
|
+
|
|
183
|
+
# Install autostart shortcut in Startup folder
|
|
184
|
+
$startupDir = [Environment]::GetFolderPath('Startup')
|
|
185
|
+
Copy-Item -Path "$env:USERPROFILE\.agentvibes\start-watcher.vbs" -Destination "$startupDir\agentvibes-watcher.vbs" -Force
|
|
186
|
+
|
|
187
|
+
# Kill any existing watcher instances before launching - prevents double-playback
|
|
188
|
+
# if setup is run more than once (each run would otherwise add another watcher process).
|
|
189
|
+
$existingWatchers = Get-CimInstance Win32_Process | Where-Object { $_.CommandLine -like "*tts-watcher.ps1*" -and $_.Name -like "powershell*" }
|
|
190
|
+
if ($existingWatchers) {
|
|
191
|
+
$existingWatchers | ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }
|
|
192
|
+
Start-Sleep -Milliseconds 300 # Wait for processes to exit
|
|
193
|
+
Write-Host " Stopped $($existingWatchers.Count) existing watcher(s)" -ForegroundColor DarkYellow
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
# Launch it now so streaming works without requiring logout/login
|
|
197
|
+
Start-Process wscript.exe -ArgumentList "$env:USERPROFILE\.agentvibes\start-watcher.vbs" -WindowStyle Hidden
|
|
198
|
+
Write-Host " Watcher: installed + started + autostart enabled" -ForegroundColor Green
|
|
199
|
+
|
|
200
|
+
# Step 7.6: Install Kokoro daemon autostart (only when Kokoro is available)
|
|
201
|
+
# The Kokoro provider synthesizes via a resident HTTP daemon (kokoro-server.py)
|
|
202
|
+
# that keeps the model on the GPU. Without it, the FIRST receiver message after
|
|
203
|
+
# every reboot pays a ~12s cold model-load. A Startup-folder .vbs keeps it warm.
|
|
204
|
+
Write-Host "[7.6/8] Installing Kokoro daemon autostart (if Kokoro installed)..." -ForegroundColor Yellow
|
|
205
|
+
$pythonCmd = Get-Command python -ErrorAction SilentlyContinue
|
|
206
|
+
$kokoroAvailable = $false
|
|
207
|
+
if ($pythonCmd) {
|
|
208
|
+
# find_spec avoids importing torch (slow); exit 0 => kokoro importable
|
|
209
|
+
& $pythonCmd.Source -c "import importlib.util,sys; sys.exit(0 if importlib.util.find_spec('kokoro') else 1)" 2>$null
|
|
210
|
+
$kokoroAvailable = ($LASTEXITCODE -eq 0)
|
|
211
|
+
}
|
|
212
|
+
if ($kokoroAvailable) {
|
|
213
|
+
$pythonwPath = $pythonCmd.Source -replace 'python\.exe$', 'pythonw.exe'
|
|
214
|
+
if (-not (Test-Path $pythonwPath)) { $pythonwPath = $pythonCmd.Source } # fall back to python.exe
|
|
215
|
+
$kokoroVbs = @"
|
|
216
|
+
' AgentVibes - Kokoro TTS daemon launcher (autostart)
|
|
217
|
+
' Keeps the Kokoro model resident on the GPU so receiver TTS latency stays low.
|
|
218
|
+
Set WshShell = CreateObject("WScript.Shell")
|
|
219
|
+
pythonw = "$pythonwPath"
|
|
220
|
+
script = WshShell.ExpandEnvironmentStrings("%USERPROFILE%") & "\.claude\hooks-windows\kokoro-server.py"
|
|
221
|
+
WshShell.Run """" & pythonw & """ """ & script & """ 7855", 0, False
|
|
222
|
+
"@
|
|
223
|
+
Set-Content -Path "$env:USERPROFILE\.agentvibes\start-kokoro-server.vbs" -Value $kokoroVbs -Encoding ASCII
|
|
224
|
+
Copy-Item -Path "$env:USERPROFILE\.agentvibes\start-kokoro-server.vbs" -Destination "$startupDir\agentvibes-kokoro-server.vbs" -Force
|
|
225
|
+
# Kill only the managed daemon (this script's kokoro-server.py on the standard
|
|
226
|
+
# port 7855), then launch a fresh one so it's warm immediately. Scoping by the
|
|
227
|
+
# managed port avoids killing unrelated user-run kokoro-server.py daemons.
|
|
228
|
+
Get-CimInstance Win32_Process | Where-Object { $_.CommandLine -like "*kokoro-server.py*7855*" } |
|
|
229
|
+
ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }
|
|
230
|
+
Start-Process wscript.exe -ArgumentList "$env:USERPROFILE\.agentvibes\start-kokoro-server.vbs" -WindowStyle Hidden
|
|
231
|
+
Write-Host " Kokoro daemon: installed + started + autostart enabled (pythonw: $pythonwPath)" -ForegroundColor Green
|
|
232
|
+
} else {
|
|
233
|
+
Write-Host " SKIP: Kokoro not installed (pip install kokoro soundfile numpy to enable)" -ForegroundColor DarkYellow
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
# Step 8: Start sshd
|
|
237
|
+
Write-Host "[8/8] Starting sshd..." -ForegroundColor Yellow
|
|
238
|
+
Start-Service sshd
|
|
239
|
+
$status = (Get-Service sshd).Status
|
|
240
|
+
if ($status -eq "Running") {
|
|
241
|
+
Write-Host " sshd: Running" -ForegroundColor Green
|
|
242
|
+
} else {
|
|
243
|
+
Write-Host " sshd: $status (check Event Viewer)" -ForegroundColor Red
|
|
244
|
+
exit 1
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
Write-Host ""
|
|
248
|
+
Write-Host "=== Setup Complete ===" -ForegroundColor Cyan
|
|
249
|
+
Write-Host ""
|
|
250
|
+
Write-Host "Security:" -ForegroundColor White
|
|
251
|
+
Write-Host " Port: 45123" -ForegroundColor Gray
|
|
252
|
+
Write-Host " Interface: $TailscaleIp (Tailscale)" -ForegroundColor Gray
|
|
253
|
+
Write-Host " Auth: SSH key only" -ForegroundColor Gray
|
|
254
|
+
Write-Host " Access: Receiver script only (no shell)" -ForegroundColor Gray
|
|
255
|
+
Write-Host " Firewall: Tailscale IPs only (100.x.x.x)" -ForegroundColor Gray
|
|
256
|
+
Write-Host ""
|
|
257
|
+
Write-Host "To test from sender:" -ForegroundColor White
|
|
258
|
+
Write-Host " echo `"hello`" | base64 | ssh -p 45123 $Username@$TailscaleIp" -ForegroundColor Cyan
|
|
259
|
+
Write-Host ""
|
package/setup-windows.ps1
CHANGED
|
@@ -322,41 +322,51 @@ if (-not (Test-Path $HooksDir)) {
|
|
|
322
322
|
New-Item -ItemType Directory -Path $HooksDir -Force | Out-Null
|
|
323
323
|
}
|
|
324
324
|
|
|
325
|
-
#
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
325
|
+
# Friendly descriptions for the well-known scripts; any other *.ps1 in the
|
|
326
|
+
# source dir still gets installed (with a generic label) rather than dropped.
|
|
327
|
+
$HookDescriptions = @{
|
|
328
|
+
"play-tts.ps1" = "Main TTS router - dispatches to active provider"
|
|
329
|
+
"play-tts-soprano.ps1" = "Soprano neural voice provider (fastest)"
|
|
330
|
+
"play-tts-windows-piper.ps1" = "Piper offline neural voice provider"
|
|
331
|
+
"play-tts-windows-sapi.ps1" = "Windows built-in SAPI voice provider"
|
|
332
|
+
"provider-manager.ps1" = "Switch between TTS providers"
|
|
333
|
+
"voice-manager-windows.ps1" = "Browse and select voice models"
|
|
334
|
+
"audio-cache-utils.ps1" = "Manage TTS audio file cache"
|
|
335
|
+
"session-start-tts.ps1" = "Auto-activates TTS when Claude starts"
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
# One timestamp per run so this run's backups group together and never collide.
|
|
339
|
+
$BackupStamp = (Get-Date -Format 'yyyyMMdd-HHmmss')
|
|
340
|
+
|
|
341
|
+
# Copy EVERY hook script, not a hand-maintained whitelist — a partial list
|
|
342
|
+
# produced mixed-generation installs (new router + stale providers/watcher).
|
|
337
343
|
$CopiedCount = 0
|
|
338
|
-
foreach ($
|
|
339
|
-
$script = $
|
|
340
|
-
$SourceFile = Join-Path $SourceHooksDir $script
|
|
344
|
+
foreach ($SourceFile in (Get-ChildItem -Path $SourceHooksDir -Filter *.ps1 -File)) {
|
|
345
|
+
$script = $SourceFile.Name
|
|
341
346
|
$DestFile = Join-Path $HooksDir $script
|
|
347
|
+
$desc = if ($HookDescriptions.ContainsKey($script)) { $HookDescriptions[$script] } else { "AgentVibes hook script" }
|
|
342
348
|
|
|
343
|
-
if (
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
Write-Item "[OK]" $script $info.Desc
|
|
349
|
-
} else {
|
|
350
|
-
Copy-Item -Path $SourceFile -Destination $DestFile -Force
|
|
351
|
-
Write-Item "[OK]" $script $info.Desc
|
|
352
|
-
}
|
|
349
|
+
# Skip if source and destination are the same file (running from project root)
|
|
350
|
+
$resolvedSrc = $SourceFile.FullName
|
|
351
|
+
$resolvedDst = if (Test-Path $DestFile) { (Resolve-Path $DestFile).Path } else { "" }
|
|
352
|
+
if ($resolvedSrc -eq $resolvedDst) {
|
|
353
|
+
Write-Item "[OK]" $script $desc
|
|
353
354
|
$CopiedCount++
|
|
355
|
+
continue
|
|
354
356
|
}
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
357
|
+
|
|
358
|
+
# Non-Destructive rule: back up an existing, differing file before overwrite
|
|
359
|
+
# so a user-modified hook is always recoverable (timestamped, every run).
|
|
360
|
+
if (Test-Path $DestFile) {
|
|
361
|
+
$srcHash = (Get-FileHash -Path $resolvedSrc -Algorithm SHA256).Hash
|
|
362
|
+
$dstHash = (Get-FileHash -Path $DestFile -Algorithm SHA256).Hash
|
|
363
|
+
if ($srcHash -ne $dstHash) {
|
|
364
|
+
Copy-Item -Path $DestFile -Destination "$DestFile.user.bak.$BackupStamp" -ErrorAction SilentlyContinue
|
|
365
|
+
}
|
|
359
366
|
}
|
|
367
|
+
Copy-Item -Path $SourceFile.FullName -Destination $DestFile -Force
|
|
368
|
+
Write-Item "[OK]" $script $desc
|
|
369
|
+
$CopiedCount++
|
|
360
370
|
}
|
|
361
371
|
|
|
362
372
|
if ($CopiedCount -eq 0) {
|
|
@@ -1,110 +1,110 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Personality List Display - Beautiful multi-column personality listing
|
|
4
|
-
* Called by personality-manager.sh to display personalities with boxen formatting
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { formatPersonalitiesList } from '../utils/list-formatter.js';
|
|
8
|
-
import fs from 'fs';
|
|
9
|
-
import path from 'path';
|
|
10
|
-
import os from 'os';
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Get personality description from markdown file
|
|
14
|
-
*/
|
|
15
|
-
function getPersonalityDescription(filePath) {
|
|
16
|
-
try {
|
|
17
|
-
const content = fs.readFileSync(filePath, 'utf8');
|
|
18
|
-
|
|
19
|
-
// Try to extract description from frontmatter or first paragraph
|
|
20
|
-
const descMatch = content.match(/description:\s*(.+)/i);
|
|
21
|
-
if (descMatch) {
|
|
22
|
-
return descMatch[1].trim();
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// Try to get first line after frontmatter
|
|
26
|
-
const lines = content.split('\n');
|
|
27
|
-
let inFrontmatter = false;
|
|
28
|
-
let frontmatterCount = 0;
|
|
29
|
-
|
|
30
|
-
for (const line of lines) {
|
|
31
|
-
if (line.trim() === '---') {
|
|
32
|
-
frontmatterCount++;
|
|
33
|
-
inFrontmatter = frontmatterCount === 1;
|
|
34
|
-
continue;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (!inFrontmatter && frontmatterCount >= 2 && line.trim()) {
|
|
38
|
-
// First non-empty line after frontmatter
|
|
39
|
-
return line.trim().replace(/^#+\s*/, '').substring(0, 50);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return '';
|
|
44
|
-
} catch (error) {
|
|
45
|
-
return '';
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Get all personalities from directory
|
|
51
|
-
*/
|
|
52
|
-
function getPersonalities(personalitiesDir, currentPersonality) {
|
|
53
|
-
const personalities = [];
|
|
54
|
-
|
|
55
|
-
if (!fs.existsSync(personalitiesDir)) {
|
|
56
|
-
return personalities;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const files = fs.readdirSync(personalitiesDir);
|
|
60
|
-
for (const file of files) {
|
|
61
|
-
if (file.endsWith('.md')) {
|
|
62
|
-
const name = path.basename(file, '.md');
|
|
63
|
-
const filePath = path.join(personalitiesDir, file);
|
|
64
|
-
const description = getPersonalityDescription(filePath);
|
|
65
|
-
|
|
66
|
-
personalities.push({
|
|
67
|
-
name,
|
|
68
|
-
description,
|
|
69
|
-
current: name === currentPersonality
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Add special 'random' option
|
|
75
|
-
personalities.push({
|
|
76
|
-
name: 'random',
|
|
77
|
-
description: 'Picks randomly each time',
|
|
78
|
-
current: currentPersonality === 'random'
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
return personalities.sort((a, b) => {
|
|
82
|
-
// Keep 'random' at the end
|
|
83
|
-
if (a.name === 'random') return 1;
|
|
84
|
-
if (b.name === 'random') return -1;
|
|
85
|
-
return a.name.localeCompare(b.name);
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Main function
|
|
91
|
-
*/
|
|
92
|
-
function main() {
|
|
93
|
-
const args = process.argv.slice(2);
|
|
94
|
-
|
|
95
|
-
// Parse arguments
|
|
96
|
-
const personalitiesDir = args[0] || path.join(os.homedir(), '.claude', 'personalities');
|
|
97
|
-
const currentPersonality = args[1] || 'normal';
|
|
98
|
-
|
|
99
|
-
const personalities = getPersonalities(personalitiesDir, currentPersonality);
|
|
100
|
-
|
|
101
|
-
// Display with boxen
|
|
102
|
-
const output = formatPersonalitiesList(personalities, {
|
|
103
|
-
columns: 2,
|
|
104
|
-
showUsage: true
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
console.log(output);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
main();
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Personality List Display - Beautiful multi-column personality listing
|
|
4
|
+
* Called by personality-manager.sh to display personalities with boxen formatting
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { formatPersonalitiesList } from '../utils/list-formatter.js';
|
|
8
|
+
import fs from 'fs';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
import os from 'os';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Get personality description from markdown file
|
|
14
|
+
*/
|
|
15
|
+
function getPersonalityDescription(filePath) {
|
|
16
|
+
try {
|
|
17
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
18
|
+
|
|
19
|
+
// Try to extract description from frontmatter or first paragraph
|
|
20
|
+
const descMatch = content.match(/description:\s*(.+)/i);
|
|
21
|
+
if (descMatch) {
|
|
22
|
+
return descMatch[1].trim();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Try to get first line after frontmatter
|
|
26
|
+
const lines = content.split('\n');
|
|
27
|
+
let inFrontmatter = false;
|
|
28
|
+
let frontmatterCount = 0;
|
|
29
|
+
|
|
30
|
+
for (const line of lines) {
|
|
31
|
+
if (line.trim() === '---') {
|
|
32
|
+
frontmatterCount++;
|
|
33
|
+
inFrontmatter = frontmatterCount === 1;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!inFrontmatter && frontmatterCount >= 2 && line.trim()) {
|
|
38
|
+
// First non-empty line after frontmatter
|
|
39
|
+
return line.trim().replace(/^#+\s*/, '').substring(0, 50);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return '';
|
|
44
|
+
} catch (error) {
|
|
45
|
+
return '';
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Get all personalities from directory
|
|
51
|
+
*/
|
|
52
|
+
function getPersonalities(personalitiesDir, currentPersonality) {
|
|
53
|
+
const personalities = [];
|
|
54
|
+
|
|
55
|
+
if (!fs.existsSync(personalitiesDir)) {
|
|
56
|
+
return personalities;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const files = fs.readdirSync(personalitiesDir);
|
|
60
|
+
for (const file of files) {
|
|
61
|
+
if (file.endsWith('.md')) {
|
|
62
|
+
const name = path.basename(file, '.md');
|
|
63
|
+
const filePath = path.join(personalitiesDir, file);
|
|
64
|
+
const description = getPersonalityDescription(filePath);
|
|
65
|
+
|
|
66
|
+
personalities.push({
|
|
67
|
+
name,
|
|
68
|
+
description,
|
|
69
|
+
current: name === currentPersonality
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Add special 'random' option
|
|
75
|
+
personalities.push({
|
|
76
|
+
name: 'random',
|
|
77
|
+
description: 'Picks randomly each time',
|
|
78
|
+
current: currentPersonality === 'random'
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
return personalities.sort((a, b) => {
|
|
82
|
+
// Keep 'random' at the end
|
|
83
|
+
if (a.name === 'random') return 1;
|
|
84
|
+
if (b.name === 'random') return -1;
|
|
85
|
+
return a.name.localeCompare(b.name);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Main function
|
|
91
|
+
*/
|
|
92
|
+
function main() {
|
|
93
|
+
const args = process.argv.slice(2);
|
|
94
|
+
|
|
95
|
+
// Parse arguments
|
|
96
|
+
const personalitiesDir = args[0] || path.join(os.homedir(), '.claude', 'personalities');
|
|
97
|
+
const currentPersonality = args[1] || 'normal';
|
|
98
|
+
|
|
99
|
+
const personalities = getPersonalities(personalitiesDir, currentPersonality);
|
|
100
|
+
|
|
101
|
+
// Display with boxen
|
|
102
|
+
const output = formatPersonalitiesList(personalities, {
|
|
103
|
+
columns: 2,
|
|
104
|
+
showUsage: true
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
console.log(output);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
main();
|