agentvibes 3.5.5 → 3.5.8

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.
Files changed (78) hide show
  1. package/.claude/commands/agent-vibes/provider.md +0 -0
  2. package/.claude/config/background-music-position.txt +27 -0
  3. package/.claude/config/background-music-volume.txt +1 -1
  4. package/.claude/config/background-music.cfg +1 -0
  5. package/.claude/config/background-music.txt +1 -0
  6. package/.claude/config/tts-speech-rate.txt +1 -0
  7. package/.claude/config/tts-verbosity.txt +1 -0
  8. package/.claude/github-star-reminder.txt +1 -0
  9. package/.claude/hooks/audio-cache-utils.sh +0 -0
  10. package/.claude/hooks/audio-processor.sh +0 -0
  11. package/.claude/hooks/background-music-manager.sh +0 -0
  12. package/.claude/hooks/bmad-party-manager.sh +225 -0
  13. package/.claude/hooks/bmad-speak-enhanced.sh +0 -0
  14. package/.claude/hooks/bmad-speak.sh +0 -0
  15. package/.claude/hooks/bmad-tts-injector.sh +0 -0
  16. package/.claude/hooks/bmad-voice-manager.sh +0 -0
  17. package/.claude/hooks/clawdbot-receiver-SECURE.sh +0 -0
  18. package/.claude/hooks/clawdbot-receiver.sh +0 -0
  19. package/.claude/hooks/clean-audio-cache.sh +0 -0
  20. package/.claude/hooks/cleanup-cache.sh +0 -0
  21. package/.claude/hooks/configure-rdp-mode.sh +0 -0
  22. package/.claude/hooks/download-extra-voices.sh +0 -0
  23. package/.claude/hooks/effects-manager.sh +0 -0
  24. package/.claude/hooks/github-star-reminder.sh +0 -0
  25. package/.claude/hooks/language-manager.sh +0 -0
  26. package/.claude/hooks/learn-manager.sh +0 -0
  27. package/.claude/hooks/macos-voice-manager.sh +0 -0
  28. package/.claude/hooks/migrate-background-music.sh +0 -0
  29. package/.claude/hooks/migrate-to-agentvibes.sh +0 -0
  30. package/.claude/hooks/optimize-background-music.sh +0 -0
  31. package/.claude/hooks/personality-manager.sh +0 -0
  32. package/.claude/hooks/piper-download-voices.sh +0 -0
  33. package/.claude/hooks/piper-installer.sh +0 -0
  34. package/.claude/hooks/piper-multispeaker-registry.sh +0 -0
  35. package/.claude/hooks/piper-voice-manager.sh +0 -0
  36. package/.claude/hooks/play-tts-enhanced.sh +0 -0
  37. package/.claude/hooks/play-tts-macos.sh +0 -0
  38. package/.claude/hooks/play-tts-piper.sh +0 -0
  39. package/.claude/hooks/play-tts-soprano.sh +0 -0
  40. package/.claude/hooks/play-tts-ssh-remote.sh +0 -0
  41. package/.claude/hooks/play-tts-termux-ssh.sh +0 -0
  42. package/.claude/hooks/play-tts.sh +1 -1
  43. package/.claude/hooks/prepare-release.sh +0 -0
  44. package/.claude/hooks/provider-commands.sh +0 -0
  45. package/.claude/hooks/provider-manager.sh +2 -2
  46. package/.claude/hooks/replay-target-audio.sh +0 -0
  47. package/.claude/hooks/sentiment-manager.sh +0 -0
  48. package/.claude/hooks/session-start-tts.sh +0 -0
  49. package/.claude/hooks/soprano-gradio-synth.py +0 -0
  50. package/.claude/hooks/speed-manager.sh +0 -0
  51. package/.claude/hooks/stop.sh +38 -0
  52. package/.claude/hooks/termux-installer.sh +0 -0
  53. package/.claude/hooks/translate-manager.sh +0 -0
  54. package/.claude/hooks/translator.py +0 -0
  55. package/.claude/hooks/tts-queue-worker.sh +0 -0
  56. package/.claude/hooks/tts-queue.sh +0 -0
  57. package/.claude/hooks/verbosity-manager.sh +0 -0
  58. package/.claude/hooks/voice-manager.sh +0 -0
  59. package/.claude/piper-voices-dir.txt +1 -0
  60. package/.mcp.json +34 -0
  61. package/README.md +10 -25
  62. package/RELEASE_NOTES.md +78 -0
  63. package/bin/agent-vibes +21 -26
  64. package/bin/mcp-server.js +0 -0
  65. package/bin/mcp-server.sh +0 -0
  66. package/bin/test-bmad-pr +0 -0
  67. package/mcp-server/WINDOWS_SETUP.md +0 -0
  68. package/mcp-server/install-deps.js +0 -0
  69. package/mcp-server/test_server.py +0 -0
  70. package/package.json +1 -1
  71. package/setup-windows.ps1 +61 -13
  72. package/src/installer.js +87 -12
  73. package/src/utils/provider-validator.js +456 -0
  74. package/templates/agentvibes-receiver.sh +0 -0
  75. package/templates/audio/welcome-music.mp3 +0 -0
  76. package/.claude/config/background-music-default.txt +0 -1
  77. package/.claude/config/background-music-enabled.txt +0 -1
  78. package/.claude/config/reverb-level.txt +0 -1
@@ -0,0 +1,456 @@
1
+ /**
2
+ * @fileoverview Provider validation utility for AgentVibes
3
+ * Validates TTS provider availability at installation, switch, and runtime
4
+ */
5
+
6
+ import { execSync } from 'node:child_process';
7
+ import { spawnSync } from 'node:child_process';
8
+ import path from 'node:path'; // For safe path operations and traversal prevention
9
+ import fs from 'node:fs'; // For checking file/directory existence
10
+ import os from 'node:os'; // For os.homedir() to prevent HOME injection attacks
11
+
12
+ /**
13
+ * Validate a TTS provider's installation status
14
+ * @param {string} providerName - Provider name (soprano, piper, macos, windows-sapi, etc.)
15
+ * @returns {Promise<{installed: boolean, message: string, pythonVersion?: string, error?: string}>}
16
+ */
17
+ export async function validateProvider(providerName) {
18
+ switch (providerName) {
19
+ case 'soprano':
20
+ return await validateSopranoInstallation();
21
+ case 'piper':
22
+ return await validatePiperInstallation();
23
+ case 'macos':
24
+ return await validateMacOSProvider();
25
+ case 'windows-sapi':
26
+ return await validateWindowsSAPI();
27
+ case 'windows-piper':
28
+ return await validatePiperInstallation();
29
+ case 'termux-ssh':
30
+ case 'ssh-remote':
31
+ case 'ssh-pulseaudio':
32
+ case 'piper-receiver':
33
+ case 'silent':
34
+ // These don't require local provider installation
35
+ return { installed: true, message: 'Remote/Special provider - no local installation needed' };
36
+ default:
37
+ return { installed: false, message: `Unknown provider: ${providerName}`, error: 'UNKNOWN_PROVIDER' };
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Validate Soprano TTS installation
43
+ * Checks multiple Python versions since Soprano might be in non-default Python
44
+ * @returns {Promise<{installed: boolean, message: string, pythonVersion?: string, checkedCount?: number}>}
45
+ */
46
+ export async function validateSopranoInstallation() {
47
+ const checkedLocations = [];
48
+
49
+ // Check for pipx installation first (common for CLI tools)
50
+ // Use os.homedir() (not env var) to prevent HOME injection attacks
51
+ try {
52
+ const homeDir = os.homedir();
53
+ const sopranoVenvPath = path.join(homeDir, '.local', 'share', 'pipx', 'venvs', 'soprano-tts');
54
+
55
+ // Validate path is within home directory (prevent path traversal)
56
+ const resolvedPath = path.resolve(sopranoVenvPath);
57
+ const resolvedHome = path.resolve(homeDir);
58
+ if (!resolvedPath.startsWith(resolvedHome)) {
59
+ throw new Error('Path traversal detected');
60
+ }
61
+
62
+ if (fs.existsSync(sopranoVenvPath)) {
63
+ checkedLocations.push('pipx'); // Always track what was checked
64
+ return { installed: true, message: 'Soprano TTS detected (via pipx)', checkedLocations };
65
+ }
66
+ } catch (error) {
67
+ // If home directory check fails, fall through to Python checks
68
+ if (error.code !== 'ENOENT') {
69
+ console.error('[DEBUG] Pipx check error:', error.message);
70
+ }
71
+ }
72
+ checkedLocations.push('pipx');
73
+
74
+ // Comprehensive Python version detection
75
+ const pythonCommands = ['python3', 'python', 'python3.12', 'python3.11', 'python3.10', 'python3.9', 'python3.8'];
76
+
77
+ for (const pythonCmd of pythonCommands) {
78
+ try {
79
+ // Use array form to prevent command injection (security: CLAUDE.md)
80
+ const result = execSync(pythonCmd, ['-m', 'pip', 'show', 'soprano-tts'], {
81
+ encoding: 'utf8',
82
+ stdio: ['pipe', 'pipe', 'pipe'],
83
+ timeout: 10000
84
+ });
85
+
86
+ if (result && result.trim()) {
87
+ checkedLocations.push(pythonCmd); // Track which Python found it
88
+ return {
89
+ installed: true,
90
+ message: `Soprano TTS detected via ${pythonCmd}`,
91
+ pythonVersion: pythonCmd,
92
+ checkedCount: checkedLocations.length + pythonCommands.indexOf(pythonCmd)
93
+ };
94
+ }
95
+ } catch (error) {
96
+ // Continue to next Python version - errors expected when Python not in PATH or package missing
97
+ }
98
+ }
99
+
100
+ // Build list of Python versions checked
101
+ checkedLocations.push(...pythonCommands);
102
+
103
+ return {
104
+ installed: false,
105
+ message: `Soprano TTS is not installed on your system (checked: ${checkedLocations.join(', ')})`,
106
+ error: 'SOPRANO_NOT_FOUND',
107
+ checkedCount: checkedLocations.length
108
+ };
109
+ }
110
+
111
+ /**
112
+ * Validate Piper TTS installation
113
+ * Checks if Piper is available via pipx or direct installation
114
+ * Suppresses all error output for clean UX
115
+ * @returns {Promise<{installed: boolean, message: string}>}
116
+ */
117
+ export async function validatePiperInstallation() {
118
+ const checkedLocations = [];
119
+
120
+ // Check for piper binary with error suppression
121
+ try {
122
+ execSync('which piper 2>/dev/null', {
123
+ encoding: 'utf8',
124
+ shell: true,
125
+ stdio: ['pipe', 'pipe', 'pipe']
126
+ });
127
+ return { installed: true, message: 'Piper TTS detected (binary in PATH)' };
128
+ } catch (error) {
129
+ checkedLocations.push('PATH (piper binary)');
130
+ }
131
+
132
+ // Check for pipx installation (use venv directory check - more reliable than pipx list)
133
+ try {
134
+ const homeDir = os.homedir(); // Use os.homedir() not env var (security: prevent HOME injection)
135
+ const piperVenvPath = path.join(homeDir, '.local', 'share', 'pipx', 'venvs', 'piper-tts');
136
+
137
+ // Validate path is within home directory (prevent path traversal)
138
+ const resolvedPath = path.resolve(piperVenvPath);
139
+ const resolvedHome = path.resolve(homeDir);
140
+ if (!resolvedPath.startsWith(resolvedHome)) {
141
+ throw new Error('Path traversal detected');
142
+ }
143
+
144
+ if (fs.existsSync(piperVenvPath)) {
145
+ checkedLocations.push('pipx'); // Always track
146
+ return { installed: true, message: 'Piper TTS detected (via pipx)', checkedLocations };
147
+ }
148
+ } catch (error) {
149
+ if (error.code !== 'ENOENT') {
150
+ console.error('[DEBUG] Pipx check error:', error.message);
151
+ }
152
+ }
153
+ checkedLocations.push('pipx');
154
+
155
+ // Check if Python + piper-tts package installed using array form (security: prevent injection)
156
+ try {
157
+ const result = execSync('python3', ['-m', 'pip', 'show', 'piper-tts'], {
158
+ encoding: 'utf8',
159
+ stdio: ['pipe', 'pipe', 'pipe'],
160
+ timeout: 10000
161
+ });
162
+ if (result && result.trim()) {
163
+ checkedLocations.push('python3 pip'); // Track what found it
164
+ return { installed: true, message: 'Piper TTS detected (Python package)', checkedLocations };
165
+ }
166
+ } catch (error) {
167
+ checkedLocations.push('python3 pip');
168
+ }
169
+
170
+ return {
171
+ installed: false,
172
+ message: `Piper TTS is not installed on your system (checked: ${checkedLocations.join(', ')})`,
173
+ error: 'PIPER_NOT_FOUND'
174
+ };
175
+ }
176
+
177
+ /**
178
+ * Validate macOS Say (built-in)
179
+ * @returns {Promise<{installed: boolean, message: string}>}
180
+ */
181
+ export async function validateMacOSProvider() {
182
+ if (process.platform !== 'darwin') {
183
+ return {
184
+ installed: false,
185
+ message: 'macOS Say is only available on macOS (this is not a Mac)',
186
+ error: 'NOT_MACOS'
187
+ };
188
+ }
189
+
190
+ try {
191
+ execSync('which say 2>/dev/null', {
192
+ encoding: 'utf8',
193
+ shell: true,
194
+ stdio: ['pipe', 'pipe', 'pipe']
195
+ });
196
+ return { installed: true, message: 'macOS Say detected' };
197
+ } catch (error) {
198
+ // say command not found
199
+ }
200
+
201
+ return {
202
+ installed: false,
203
+ message: 'macOS Say is not installed on your system (checked: /usr/bin/say)',
204
+ error: 'MACOS_SAY_NOT_FOUND'
205
+ };
206
+ }
207
+
208
+ /**
209
+ * Validate Windows SAPI (built-in)
210
+ * @returns {Promise<{installed: boolean, message: string}>}
211
+ */
212
+ export async function validateWindowsSAPI() {
213
+ if (process.platform !== 'win32') {
214
+ return {
215
+ installed: false,
216
+ message: 'Windows SAPI only available on Windows',
217
+ error: 'NOT_WINDOWS'
218
+ };
219
+ }
220
+
221
+ // Windows SAPI is built-in, always available
222
+ return { installed: true, message: 'Windows SAPI available' };
223
+ }
224
+
225
+ /**
226
+ * Test if a provider works at runtime (more thorough check)
227
+ * Attempts to actually use the provider for a brief moment
228
+ * @param {string} providerName
229
+ * @returns {Promise<{working: boolean, error?: string}>}
230
+ */
231
+ export async function testProviderRuntime(providerName) {
232
+ switch (providerName) {
233
+ case 'soprano':
234
+ return await testSopranoRuntime();
235
+ case 'piper':
236
+ return await testPiperRuntime();
237
+ case 'macos':
238
+ return await testMacOSRuntime();
239
+ default:
240
+ return { working: true }; // Assume other providers work if installed
241
+ }
242
+ }
243
+
244
+ /**
245
+ * Test Soprano runtime (webui connectivity)
246
+ */
247
+ async function testSopranoRuntime() {
248
+ try {
249
+ // Try a quick soprano import check
250
+ const result = spawnSync('python3', ['-c', 'import soprano; print("OK")'], {
251
+ timeout: 5000,
252
+ encoding: 'utf8'
253
+ });
254
+
255
+ if (result.error || result.status !== 0) {
256
+ return {
257
+ working: false,
258
+ error: 'Soprano Python module import failed'
259
+ };
260
+ }
261
+
262
+ return { working: true };
263
+ } catch (e) {
264
+ return {
265
+ working: false,
266
+ error: e.message
267
+ };
268
+ }
269
+ }
270
+
271
+ /**
272
+ * Test Piper runtime
273
+ */
274
+ async function testPiperRuntime() {
275
+ try {
276
+ execSync('piper', ['--help'], {
277
+ encoding: 'utf8',
278
+ stdio: ['pipe', 'pipe', 'pipe'],
279
+ timeout: 5000
280
+ });
281
+ return { working: true };
282
+ } catch (e) {
283
+ return {
284
+ working: false,
285
+ error: 'Piper command execution failed'
286
+ };
287
+ }
288
+ }
289
+
290
+ /**
291
+ * Test macOS Say runtime
292
+ */
293
+ async function testMacOSRuntime() {
294
+ try {
295
+ execSync('say', ['-f', '/dev/null'], {
296
+ encoding: 'utf8',
297
+ timeout: 5000,
298
+ stdio: ['pipe', 'pipe', 'pipe']
299
+ });
300
+ return { working: true };
301
+ } catch (e) {
302
+ return {
303
+ working: false,
304
+ error: 'macOS Say command execution failed'
305
+ };
306
+ }
307
+ }
308
+
309
+ /**
310
+ * Get the appropriate pip/install command for a provider
311
+ * @param {string} providerName
312
+ * @returns {string} Installation command or empty string if N/A
313
+ */
314
+ export function getProviderInstallCommand(providerName) {
315
+ const commands = {
316
+ soprano: 'pip install soprano-tts',
317
+ piper: 'pip install piper-tts',
318
+ // macOS Say and Windows SAPI are built-in, no install needed
319
+ };
320
+
321
+ return commands[providerName] || '';
322
+ }
323
+
324
+ /**
325
+ * Attempt to install a provider with fallback strategies
326
+ * Handles PEP 668 protection by trying multiple installation methods
327
+ * @param {string} providerName - Provider name (soprano, piper)
328
+ * @returns {object} {success: boolean, message: string, command?: string, verified?: boolean}
329
+ */
330
+ export async function attemptProviderInstallation(providerName) {
331
+ // Whitelist approach - only allow known providers (MEDIUM #1 fix)
332
+ const providers = {
333
+ soprano: 'soprano-tts',
334
+ piper: 'piper-tts'
335
+ };
336
+
337
+ const pkgName = providers[providerName];
338
+ if (!pkgName) {
339
+ return { success: false, message: `Unknown provider: ${providerName}` };
340
+ }
341
+
342
+ // Strategy 1: Try regular pip install (using array form for security - CRITICAL #1 fix)
343
+ try {
344
+ // Show installation in progress
345
+ console.log(` Attempting: pip install ${pkgName}...`);
346
+ execSync('pip', ['install', pkgName], {
347
+ stdio: 'inherit',
348
+ timeout: 60000 // HIGH #1 fix - 60 second timeout
349
+ });
350
+
351
+ // Verify installation actually worked (proves it's installed)
352
+ const validation = await validateProvider(providerName);
353
+ if (validation.installed) {
354
+ return {
355
+ success: true,
356
+ message: `Successfully installed via pip`,
357
+ command: `pip install ${pkgName}`,
358
+ verified: true
359
+ };
360
+ }
361
+
362
+ return { success: true, message: `Successfully installed via pip`, command: `pip install ${pkgName}` };
363
+ } catch (error) {
364
+ // Strategy 1 failed - continue to Strategy 2
365
+ // Don't check specific error messages - just try next strategy (MEDIUM #3 fix)
366
+ }
367
+
368
+ // Strategy 2: Try pipx install (recommended for PEP 668 protection)
369
+ try {
370
+ console.log(` Attempting: pipx install ${pkgName}...`);
371
+ execSync('pipx', ['install', pkgName], {
372
+ stdio: 'inherit',
373
+ timeout: 60000 // HIGH #1 fix - 60 second timeout
374
+ });
375
+
376
+ // Verify installation actually worked (proves it's installed)
377
+ const validation = await validateProvider(providerName);
378
+ if (validation.installed) {
379
+ return {
380
+ success: true,
381
+ message: `Successfully installed via pipx`,
382
+ command: `pipx install ${pkgName}`,
383
+ verified: true
384
+ };
385
+ }
386
+
387
+ return { success: true, message: `Successfully installed via pipx`, command: `pipx install ${pkgName}` };
388
+ } catch (error) {
389
+ // Both strategies failed
390
+ }
391
+
392
+ // Both strategies failed - consistent error message (MEDIUM #2 fix)
393
+ return { success: false, message: `Installation failed. Please install manually:\n pip install ${pkgName}\n or\n pipx install ${pkgName}` };
394
+ }
395
+
396
+ /**
397
+ * Get package installation information (location and version)
398
+ * @param {string} pkgName - Package name to check
399
+ * @returns {object|null} {location, version} or null if not found
400
+ */
401
+ function getPackageInfo(pkgName) {
402
+ try {
403
+ // Small delay to ensure package is registered in pip
404
+ // (sometimes pip needs a moment to update its cache)
405
+ const output = execSync('pip', ['show', pkgName], {
406
+ encoding: 'utf8',
407
+ timeout: 10000,
408
+ stdio: ['pipe', 'pipe', 'pipe'] // Capture both stdout and stderr
409
+ });
410
+
411
+ // Parse pip show output
412
+ const info = {};
413
+ const lines = output.split('\n');
414
+
415
+ for (const line of lines) {
416
+ if (!line.trim()) continue;
417
+
418
+ const colonIndex = line.indexOf(':');
419
+ if (colonIndex === -1) continue;
420
+
421
+ const key = line.substring(0, colonIndex).trim();
422
+ const value = line.substring(colonIndex + 1).trim();
423
+
424
+ if (key === 'Location') {
425
+ info.location = value;
426
+ } else if (key === 'Version') {
427
+ info.version = value;
428
+ } else if (key === 'Name') {
429
+ info.name = value;
430
+ }
431
+ }
432
+
433
+ // Return info if we found at least version or location
434
+ return (info.version || info.location) ? info : null;
435
+ } catch (error) {
436
+ // Silently fail - package info is optional, not critical
437
+ return null;
438
+ }
439
+ }
440
+
441
+ /**
442
+ * Get user-friendly provider names
443
+ * @param {string} providerName
444
+ * @returns {string} Display name
445
+ */
446
+ export function getProviderDisplayName(providerName) {
447
+ const names = {
448
+ soprano: 'Soprano TTS',
449
+ piper: 'Piper TTS',
450
+ macos: 'macOS Say',
451
+ 'windows-sapi': 'Windows SAPI',
452
+ 'windows-piper': 'Windows Piper TTS'
453
+ };
454
+
455
+ return names[providerName] || providerName;
456
+ }
File without changes
File without changes
@@ -1 +0,0 @@
1
- agent_vibes_dark_chill_step_loop.mp3
@@ -1 +0,0 @@
1
- true
@@ -1 +0,0 @@
1
- medium