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.
- package/.claude/commands/agent-vibes/provider.md +0 -0
- package/.claude/config/background-music-position.txt +27 -0
- package/.claude/config/background-music-volume.txt +1 -1
- package/.claude/config/background-music.cfg +1 -0
- package/.claude/config/background-music.txt +1 -0
- package/.claude/config/tts-speech-rate.txt +1 -0
- package/.claude/config/tts-verbosity.txt +1 -0
- package/.claude/github-star-reminder.txt +1 -0
- package/.claude/hooks/audio-cache-utils.sh +0 -0
- package/.claude/hooks/audio-processor.sh +0 -0
- package/.claude/hooks/background-music-manager.sh +0 -0
- package/.claude/hooks/bmad-party-manager.sh +225 -0
- package/.claude/hooks/bmad-speak-enhanced.sh +0 -0
- package/.claude/hooks/bmad-speak.sh +0 -0
- package/.claude/hooks/bmad-tts-injector.sh +0 -0
- package/.claude/hooks/bmad-voice-manager.sh +0 -0
- package/.claude/hooks/clawdbot-receiver-SECURE.sh +0 -0
- package/.claude/hooks/clawdbot-receiver.sh +0 -0
- package/.claude/hooks/clean-audio-cache.sh +0 -0
- package/.claude/hooks/cleanup-cache.sh +0 -0
- package/.claude/hooks/configure-rdp-mode.sh +0 -0
- package/.claude/hooks/download-extra-voices.sh +0 -0
- package/.claude/hooks/effects-manager.sh +0 -0
- package/.claude/hooks/github-star-reminder.sh +0 -0
- package/.claude/hooks/language-manager.sh +0 -0
- package/.claude/hooks/learn-manager.sh +0 -0
- package/.claude/hooks/macos-voice-manager.sh +0 -0
- package/.claude/hooks/migrate-background-music.sh +0 -0
- package/.claude/hooks/migrate-to-agentvibes.sh +0 -0
- package/.claude/hooks/optimize-background-music.sh +0 -0
- package/.claude/hooks/personality-manager.sh +0 -0
- package/.claude/hooks/piper-download-voices.sh +0 -0
- package/.claude/hooks/piper-installer.sh +0 -0
- package/.claude/hooks/piper-multispeaker-registry.sh +0 -0
- package/.claude/hooks/piper-voice-manager.sh +0 -0
- package/.claude/hooks/play-tts-enhanced.sh +0 -0
- package/.claude/hooks/play-tts-macos.sh +0 -0
- package/.claude/hooks/play-tts-piper.sh +0 -0
- package/.claude/hooks/play-tts-soprano.sh +0 -0
- package/.claude/hooks/play-tts-ssh-remote.sh +0 -0
- package/.claude/hooks/play-tts-termux-ssh.sh +0 -0
- package/.claude/hooks/play-tts.sh +1 -1
- package/.claude/hooks/prepare-release.sh +0 -0
- package/.claude/hooks/provider-commands.sh +0 -0
- package/.claude/hooks/provider-manager.sh +2 -2
- package/.claude/hooks/replay-target-audio.sh +0 -0
- package/.claude/hooks/sentiment-manager.sh +0 -0
- package/.claude/hooks/session-start-tts.sh +0 -0
- package/.claude/hooks/soprano-gradio-synth.py +0 -0
- package/.claude/hooks/speed-manager.sh +0 -0
- package/.claude/hooks/stop.sh +38 -0
- package/.claude/hooks/termux-installer.sh +0 -0
- package/.claude/hooks/translate-manager.sh +0 -0
- package/.claude/hooks/translator.py +0 -0
- package/.claude/hooks/tts-queue-worker.sh +0 -0
- package/.claude/hooks/tts-queue.sh +0 -0
- package/.claude/hooks/verbosity-manager.sh +0 -0
- package/.claude/hooks/voice-manager.sh +0 -0
- package/.claude/piper-voices-dir.txt +1 -0
- package/.mcp.json +34 -0
- package/README.md +10 -25
- package/RELEASE_NOTES.md +78 -0
- package/bin/agent-vibes +21 -26
- package/bin/mcp-server.js +0 -0
- package/bin/mcp-server.sh +0 -0
- package/bin/test-bmad-pr +0 -0
- package/mcp-server/WINDOWS_SETUP.md +0 -0
- package/mcp-server/install-deps.js +0 -0
- package/mcp-server/test_server.py +0 -0
- package/package.json +1 -1
- package/setup-windows.ps1 +61 -13
- package/src/installer.js +87 -12
- package/src/utils/provider-validator.js +456 -0
- package/templates/agentvibes-receiver.sh +0 -0
- package/templates/audio/welcome-music.mp3 +0 -0
- package/.claude/config/background-music-default.txt +0 -1
- package/.claude/config/background-music-enabled.txt +0 -1
- 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
|