agentvibes 5.9.0 → 5.10.1
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 +3 -12
- 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 +4 -5
- package/.claude/config/audio-effects.cfg.sample +52 -52
- package/.claude/config/background-music-enabled.txt +1 -1
- package/.claude/docs/TERMUX_SETUP.md +408 -408
- package/.claude/github-star-reminder.txt +1 -1
- 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-speak.sh +0 -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/path-resolver.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-agentvibes-receiver-for-voiceless-connections.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 +20 -13
- 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-windows-receiver.sh +0 -0
- package/.claude/hooks/play-tts.sh +0 -0
- package/.claude/hooks/prepare-release.sh +0 -0
- package/.claude/hooks/provider-commands.sh +0 -0
- package/.claude/hooks/provider-manager.sh +0 -0
- package/.claude/hooks/replay-target-audio.sh +0 -0
- package/.claude/hooks/requirements.txt +6 -6
- package/.claude/hooks/sentiment-manager.sh +0 -0
- package/.claude/hooks/session-start-tts.sh +0 -0
- package/.claude/hooks/soprano-gradio-synth.py +139 -139
- package/.claude/hooks/speed-manager.sh +0 -0
- package/.claude/hooks/stop-tts.sh +0 -0
- package/.claude/hooks/termux-installer.sh +0 -0
- package/.claude/hooks/translate-manager.sh +0 -0
- package/.claude/hooks/translator.py +237 -237
- 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 +6 -0
- package/.claude/hooks-windows/play-tts-windows-piper.ps1 +22 -16
- package/.claude/hooks-windows/soprano-gradio-synth.py +153 -153
- package/.claude/verbosity.txt +1 -1
- package/.clawdbot/README.md +105 -105
- package/.mcp.json +19 -6
- package/README.md +1 -1
- package/WINDOWS-SETUP.md +208 -208
- package/bin/agent-vibes +39 -39
- package/bin/agentvibes-voice-browser.js +0 -0
- package/bin/agentvibes.js +0 -0
- package/bin/mcp-server.js +121 -121
- package/bin/mcp-server.sh +0 -0
- 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/WINDOWS_SETUP.md +0 -0
- 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 +0 -0
- package/mcp-server/server.py +1807 -1797
- package/mcp-server/test_server.py +0 -0
- package/package.json +2 -2
- package/src/cli/list-personalities.js +110 -110
- package/src/cli/list-voices.js +114 -114
- package/src/commands/bmad-voices.js +394 -394
- package/src/commands/install-mcp.js +730 -476
- package/src/console/app.js +3 -3
- package/src/console/brand-colors.js +13 -13
- package/src/console/constants/personalities.js +44 -44
- package/src/console/tabs/agents-tab.js +6 -6
- package/src/console/tabs/help-tab.js +314 -314
- package/src/console/tabs/music-tab.js +1 -1
- package/src/console/tabs/readme-tab.js +272 -272
- package/src/console/tabs/receiver-tab.js +13 -13
- package/src/console/tabs/settings-tab.js +2 -2
- package/src/console/tabs/setup-tab.js +10 -10
- package/src/console/tabs/voices-tab.js +4 -4
- package/src/console/widgets/destroy-list.js +25 -25
- package/src/console/widgets/notice.js +55 -55
- package/src/console/widgets/personality-picker.js +2 -2
- package/src/console/widgets/reverb-picker.js +1 -1
- package/src/i18n/de.js +202 -202
- package/src/i18n/es.js +202 -202
- package/src/i18n/fr.js +202 -202
- package/src/i18n/hi.js +202 -202
- package/src/i18n/ja.js +202 -202
- package/src/i18n/ko.js +202 -202
- package/src/i18n/pt.js +202 -202
- package/src/i18n/strings.js +54 -54
- package/src/i18n/zh-CN.js +202 -202
- package/src/installer/language-screen.js +31 -31
- package/src/installer/music-file-input.js +304 -304
- package/src/installer.js +32 -27
- package/src/services/config-service.js +264 -264
- package/src/services/language-service.js +47 -47
- package/src/services/provider-service.js +143 -143
- package/src/services/tts-engine-service.js +2 -2
- package/src/utils/audio-duration-validator.js +298 -298
- 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/list-formatter.js +200 -194
- package/src/utils/music-file-validator.js +285 -285
- package/src/utils/platform-resolver.js +369 -0
- package/src/utils/preview-list-prompt.js +136 -136
- package/src/utils/provider-validator.js +9 -9
- package/src/utils/secure-music-storage.js +412 -412
- package/templates/agentvibes-receiver.sh +231 -231
- package/templates/audio/welcome-music.mp3 +0 -0
- package/.agentvibes/install-manifest.json +0 -330
- 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/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/play-tts-agentvibes-receiver.sh +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/.claude/piper-voices-dir.txt +0 -1
|
@@ -1,476 +1,730 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* AgentVibes MCP Server Installer
|
|
4
|
-
*
|
|
5
|
-
* Interactive installer for setting up AgentVibes MCP server with Claude Desktop
|
|
6
|
-
* Handles platform-specific installation (Windows/Mac/Linux)
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import inquirer from 'inquirer';
|
|
10
|
-
import { execSync, execFileSync } from 'child_process';
|
|
11
|
-
import fs from 'fs';
|
|
12
|
-
import path from 'path';
|
|
13
|
-
import os from 'os';
|
|
14
|
-
import chalk from 'chalk';
|
|
15
|
-
import ora from 'ora';
|
|
16
|
-
import boxen from 'boxen';
|
|
17
|
-
import { checkDependencies, displayMissingDependencies } from '../utils/dependency-checker.js';
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
return
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
const
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
));
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
const
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* AgentVibes MCP Server Installer
|
|
4
|
+
*
|
|
5
|
+
* Interactive installer for setting up AgentVibes MCP server with Claude Desktop
|
|
6
|
+
* Handles platform-specific installation (Windows/Mac/Linux)
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import inquirer from 'inquirer';
|
|
10
|
+
import { execSync, execFileSync } from 'child_process';
|
|
11
|
+
import fs from 'fs';
|
|
12
|
+
import path from 'path';
|
|
13
|
+
import os from 'os';
|
|
14
|
+
import chalk from 'chalk';
|
|
15
|
+
import ora from 'ora';
|
|
16
|
+
import boxen from 'boxen';
|
|
17
|
+
import { checkDependencies, displayMissingDependencies } from '../utils/dependency-checker.js';
|
|
18
|
+
|
|
19
|
+
// ─── Platform helpers ──────────────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
function commandExists(cmd) {
|
|
22
|
+
try {
|
|
23
|
+
const finder = process.platform === 'win32' ? 'where' : 'which';
|
|
24
|
+
execFileSync(finder, [cmd], { stdio: 'pipe' });
|
|
25
|
+
return true;
|
|
26
|
+
} catch {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function brewExists() {
|
|
32
|
+
return commandExists('brew');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Add Homebrew bin dirs to the current process's PATH so newly installed
|
|
36
|
+
// binaries are discoverable without restarting the shell.
|
|
37
|
+
function augmentPathForBrew() {
|
|
38
|
+
const brewPaths = ['/opt/homebrew/bin', '/usr/local/bin'];
|
|
39
|
+
const existing = new Set((process.env.PATH || '').split(path.delimiter));
|
|
40
|
+
const toAdd = brewPaths.filter(p => !existing.has(p));
|
|
41
|
+
if (toAdd.length > 0) {
|
|
42
|
+
process.env.PATH = [...toAdd, process.env.PATH].join(path.delimiter);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Install pipx via the platform package manager and augment PATH afterward.
|
|
47
|
+
async function ensurePipx(platform) {
|
|
48
|
+
if (platform === 'darwin') {
|
|
49
|
+
if (!brewExists()) {
|
|
50
|
+
console.log(chalk.yellow('Homebrew not found. Install it from https://brew.sh, then re-run.\n'));
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
console.log(chalk.cyan('📦 Installing pipx via Homebrew...\n'));
|
|
54
|
+
try {
|
|
55
|
+
execFileSync('brew', ['install', 'pipx'], { stdio: 'inherit', env: process.env }); // NOSONAR
|
|
56
|
+
augmentPathForBrew();
|
|
57
|
+
try { execFileSync('pipx', ['ensurepath'], { stdio: 'pipe', env: process.env }); } catch { /* non-fatal */ } // NOSONAR
|
|
58
|
+
return true;
|
|
59
|
+
} catch {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
} else if (platform === 'linux') {
|
|
63
|
+
console.log(chalk.cyan('📦 Installing pipx via apt-get...\n'));
|
|
64
|
+
try {
|
|
65
|
+
execFileSync('sudo', ['apt-get', 'install', '-y', 'pipx'], { stdio: 'inherit', env: process.env }); // NOSONAR
|
|
66
|
+
// Augment PATH with ~/.local/bin so pipx and piper are findable in this process
|
|
67
|
+
const localBin = path.join(os.homedir(), '.local', 'bin');
|
|
68
|
+
const existingParts = new Set((process.env.PATH || '').split(path.delimiter));
|
|
69
|
+
if (!existingParts.has(localBin)) {
|
|
70
|
+
process.env.PATH = localBin + path.delimiter + process.env.PATH;
|
|
71
|
+
}
|
|
72
|
+
try { execFileSync('pipx', ['ensurepath'], { stdio: 'pipe', env: process.env }); } catch { /* non-fatal */ } // NOSONAR
|
|
73
|
+
return true;
|
|
74
|
+
} catch {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Auto-install missing optional deps (ffmpeg, sox, pipx, etc.) via brew / apt.
|
|
82
|
+
async function autoInstallOptionalDeps(missing, platform) {
|
|
83
|
+
const installable = ['ffmpeg', 'sox', 'pipx', 'flock', 'curl', 'bc'];
|
|
84
|
+
const toInstall = installable.filter(dep => missing[dep]);
|
|
85
|
+
if (toInstall.length === 0) return;
|
|
86
|
+
|
|
87
|
+
if (platform === 'darwin') {
|
|
88
|
+
const brewMap = { ffmpeg: 'ffmpeg', sox: 'sox', pipx: 'pipx', flock: 'util-linux', curl: 'curl', bc: 'bc' };
|
|
89
|
+
const packages = toInstall.map(d => brewMap[d]).filter(Boolean);
|
|
90
|
+
if (packages.length === 0) return;
|
|
91
|
+
console.log(chalk.cyan(`\n📦 Homebrew: brew install ${packages.join(' ')}\n`));
|
|
92
|
+
try {
|
|
93
|
+
execFileSync('brew', ['install', ...packages], { stdio: 'inherit', env: process.env }); // NOSONAR
|
|
94
|
+
augmentPathForBrew();
|
|
95
|
+
} catch {
|
|
96
|
+
console.log(chalk.yellow('⚠️ Some Homebrew packages may have failed — continuing\n'));
|
|
97
|
+
}
|
|
98
|
+
} else if (platform === 'linux') {
|
|
99
|
+
const aptMap = { ffmpeg: ['ffmpeg'], sox: ['sox', 'libsox-fmt-mp3'], pipx: ['pipx'], flock: ['util-linux'], curl: ['curl'], bc: ['bc'] };
|
|
100
|
+
const packages = toInstall.flatMap(d => aptMap[d] || []);
|
|
101
|
+
if (packages.length === 0) return;
|
|
102
|
+
console.log(chalk.cyan(`\n📦 apt-get: sudo apt-get install -y ${packages.join(' ')}\n`));
|
|
103
|
+
try {
|
|
104
|
+
execFileSync('sudo', ['apt-get', 'update', '-qq'], { stdio: 'pipe', env: process.env }); // NOSONAR
|
|
105
|
+
execFileSync('sudo', ['apt-get', 'install', '-y', ...packages], { stdio: 'inherit', env: process.env }); // NOSONAR
|
|
106
|
+
} catch {
|
|
107
|
+
console.log(chalk.yellow('⚠️ Some apt packages may have failed — continuing\n'));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Check if WSL is installed on Windows
|
|
114
|
+
*/
|
|
115
|
+
function checkWSL() {
|
|
116
|
+
try {
|
|
117
|
+
// Security: Use execFileSync with array args to prevent command injection
|
|
118
|
+
execFileSync('wsl', ['--version'], { stdio: 'pipe' });
|
|
119
|
+
return true;
|
|
120
|
+
} catch {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Check if Python is available
|
|
127
|
+
*/
|
|
128
|
+
function checkPython() {
|
|
129
|
+
const commands = ['python3', 'python', 'py'];
|
|
130
|
+
|
|
131
|
+
for (const cmd of commands) {
|
|
132
|
+
try {
|
|
133
|
+
// Security: Use execFileSync with array args to prevent command injection
|
|
134
|
+
const version = execFileSync(cmd, ['--version'], { encoding: 'utf8', stdio: 'pipe' });
|
|
135
|
+
return { available: true, command: cmd, version: version.trim() };
|
|
136
|
+
} catch {
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return { available: false };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Check if Python MCP package is installed
|
|
146
|
+
*/
|
|
147
|
+
function checkMCPPackage(pythonCmd) {
|
|
148
|
+
try {
|
|
149
|
+
// Security: Use execFileSync with array args to prevent command injection
|
|
150
|
+
execFileSync(pythonCmd, ['-c', 'import mcp'], { stdio: 'pipe' });
|
|
151
|
+
return true;
|
|
152
|
+
} catch {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Get Claude Desktop config path for current platform
|
|
159
|
+
*/
|
|
160
|
+
function getClaudeConfigPath() {
|
|
161
|
+
const platform = os.platform();
|
|
162
|
+
|
|
163
|
+
switch (platform) {
|
|
164
|
+
case 'darwin': // macOS
|
|
165
|
+
return path.join(os.homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
|
|
166
|
+
case 'win32': // Windows
|
|
167
|
+
return path.join(os.homedir(), 'AppData', 'Roaming', 'Claude', 'claude_desktop_config.json');
|
|
168
|
+
default: // Linux
|
|
169
|
+
return path.join(os.homedir(), '.config', 'Claude', 'claude_desktop_config.json');
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Get AgentVibes installation directory
|
|
175
|
+
*/
|
|
176
|
+
function getAgentVibesDir() {
|
|
177
|
+
// Try to find AgentVibes directory
|
|
178
|
+
// 1. Current directory
|
|
179
|
+
if (fs.existsSync('./.claude/hooks/play-tts.sh')) {
|
|
180
|
+
return process.cwd();
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// 2. Parent directory
|
|
184
|
+
const parentDir = path.resolve(process.cwd(), '..');
|
|
185
|
+
if (fs.existsSync(path.join(parentDir, '.claude/hooks/play-tts.sh'))) {
|
|
186
|
+
return parentDir;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// 3. Ask user
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Update Claude Desktop configuration
|
|
195
|
+
*/
|
|
196
|
+
function updateClaudeConfig(agentVibesPath, provider, apiKey = null) {
|
|
197
|
+
const configPath = getClaudeConfigPath();
|
|
198
|
+
const platform = os.platform();
|
|
199
|
+
|
|
200
|
+
// Create config directory if it doesn't exist
|
|
201
|
+
const configDir = path.dirname(configPath);
|
|
202
|
+
if (!fs.existsSync(configDir)) {
|
|
203
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Read existing config or create new one
|
|
207
|
+
let config = { mcpServers: {} };
|
|
208
|
+
if (fs.existsSync(configPath)) {
|
|
209
|
+
const content = fs.readFileSync(configPath, 'utf8');
|
|
210
|
+
config = JSON.parse(content);
|
|
211
|
+
if (!config.mcpServers) {
|
|
212
|
+
config.mcpServers = {};
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Prepare MCP server config
|
|
217
|
+
let serverPath = path.join(agentVibesPath, 'mcp-server', 'server.py');
|
|
218
|
+
|
|
219
|
+
if (platform === 'win32') {
|
|
220
|
+
// Windows: Use WSL
|
|
221
|
+
serverPath = serverPath.replace(/\\/g, '/').replace(/^([A-Z]):/, (match, drive) => {
|
|
222
|
+
return `/mnt/${drive.toLowerCase()}`;
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
config.mcpServers.agentvibes = {
|
|
226
|
+
command: 'wsl',
|
|
227
|
+
args: ['python3', serverPath],
|
|
228
|
+
env: {}
|
|
229
|
+
};
|
|
230
|
+
} else {
|
|
231
|
+
// macOS/Linux: Use native Python
|
|
232
|
+
config.mcpServers.agentvibes = {
|
|
233
|
+
command: 'python3',
|
|
234
|
+
args: [serverPath],
|
|
235
|
+
env: {}
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
// Write config atomically to prevent race conditions (TOCTOU)
|
|
241
|
+
// Write to temp file first, then rename atomically
|
|
242
|
+
const tempPath = `${configPath}.tmp.${process.pid}`;
|
|
243
|
+
try {
|
|
244
|
+
fs.writeFileSync(tempPath, JSON.stringify(config, null, 2), { mode: 0o600 });
|
|
245
|
+
fs.renameSync(tempPath, configPath);
|
|
246
|
+
} catch (error) {
|
|
247
|
+
// Clean up temp file if rename fails
|
|
248
|
+
try { fs.unlinkSync(tempPath); } catch { /* ignore cleanup errors */ }
|
|
249
|
+
throw error;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return configPath;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Install Piper TTS
|
|
257
|
+
*/
|
|
258
|
+
async function installPiper(useWSL = false) {
|
|
259
|
+
const platform = os.platform();
|
|
260
|
+
|
|
261
|
+
// Ensure pipx is present before attempting piper-tts install
|
|
262
|
+
if (!useWSL && !commandExists('pipx')) {
|
|
263
|
+
console.log(chalk.yellow('\n⚠️ pipx not found — installing it first...\n'));
|
|
264
|
+
const ok = await ensurePipx(platform);
|
|
265
|
+
if (!ok) {
|
|
266
|
+
console.log(chalk.red('❌ Could not install pipx automatically.'));
|
|
267
|
+
if (platform === 'darwin') {
|
|
268
|
+
console.log(chalk.cyan(' brew install pipx'));
|
|
269
|
+
} else {
|
|
270
|
+
console.log(chalk.cyan(' sudo apt install pipx'));
|
|
271
|
+
}
|
|
272
|
+
console.log(chalk.gray(' Then re-run this installer.\n'));
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
console.log(chalk.green('✓ pipx ready\n'));
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
console.log(chalk.cyan('\n📦 Installing Piper TTS via pipx...\n'));
|
|
279
|
+
try {
|
|
280
|
+
// Security: execFileSync with array args prevents command injection
|
|
281
|
+
if (useWSL) {
|
|
282
|
+
execFileSync('wsl', ['pipx', 'install', 'piper-tts'], { stdio: 'inherit' });
|
|
283
|
+
} else {
|
|
284
|
+
execFileSync('pipx', ['install', 'piper-tts'], { stdio: 'inherit', env: process.env }); // NOSONAR
|
|
285
|
+
}
|
|
286
|
+
console.log(chalk.green('\n✅ Piper TTS installed!\n'));
|
|
287
|
+
return true;
|
|
288
|
+
} catch (error) {
|
|
289
|
+
console.log(chalk.red(`\n❌ Failed to install Piper TTS: ${error.message}\n`));
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Install Python MCP package
|
|
296
|
+
*/
|
|
297
|
+
async function installMCPPackage(pythonCmd, useWSL = false) {
|
|
298
|
+
const spinner = ora('Installing Python MCP package...').start();
|
|
299
|
+
|
|
300
|
+
try {
|
|
301
|
+
// Security: Use execFileSync with array args to prevent command injection
|
|
302
|
+
if (useWSL) {
|
|
303
|
+
execFileSync('wsl', [pythonCmd, '-m', 'pip', 'install', '--break-system-packages', 'mcp'], { stdio: 'pipe' });
|
|
304
|
+
} else {
|
|
305
|
+
execFileSync(pythonCmd, ['-m', 'pip', 'install', '--user', 'mcp'], { stdio: 'pipe' });
|
|
306
|
+
}
|
|
307
|
+
spinner.succeed('Python MCP package installed successfully!');
|
|
308
|
+
return true;
|
|
309
|
+
} catch (error) {
|
|
310
|
+
spinner.fail('Failed to install Python MCP package');
|
|
311
|
+
console.error(chalk.red(`\n❌ Error: ${error.message}`));
|
|
312
|
+
return false;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Main installer
|
|
318
|
+
*/
|
|
319
|
+
/**
|
|
320
|
+
* Check system dependencies and handle missing ones
|
|
321
|
+
* @returns {Promise<void>}
|
|
322
|
+
*/
|
|
323
|
+
async function checkSystemDependencies() {
|
|
324
|
+
console.log(chalk.bold('🔍 Step 1: Checking system dependencies...\n'));
|
|
325
|
+
|
|
326
|
+
const depResults = checkDependencies();
|
|
327
|
+
const hasMissingDeps = displayMissingDependencies(depResults);
|
|
328
|
+
|
|
329
|
+
if (!hasMissingDeps) {
|
|
330
|
+
console.log(chalk.green('✓ All dependencies installed!\n'));
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const platform = os.platform();
|
|
335
|
+
const hasCoreMissing = depResults.missing.node || depResults.missing.python || depResults.missing.bash;
|
|
336
|
+
|
|
337
|
+
if (hasCoreMissing) {
|
|
338
|
+
console.log(chalk.red('\n❌ Critical dependencies are missing. Please install them before continuing.\n'));
|
|
339
|
+
process.exit(1);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Optional deps missing — offer auto-install on Mac/Linux
|
|
343
|
+
const canAutoInstall = (platform === 'darwin' && brewExists()) || platform === 'linux';
|
|
344
|
+
|
|
345
|
+
if (canAutoInstall) {
|
|
346
|
+
const mgr = platform === 'darwin' ? 'Homebrew' : 'apt-get';
|
|
347
|
+
const { doInstall } = await inquirer.prompt([{
|
|
348
|
+
type: 'confirm',
|
|
349
|
+
name: 'doInstall',
|
|
350
|
+
message: `Install missing optional dependencies now using ${mgr}?`,
|
|
351
|
+
default: true
|
|
352
|
+
}]);
|
|
353
|
+
|
|
354
|
+
if (doInstall) {
|
|
355
|
+
await autoInstallOptionalDeps(depResults.missing, platform);
|
|
356
|
+
const recheckResults = checkDependencies();
|
|
357
|
+
const stillMissing = Object.values(recheckResults.missing).some(Boolean);
|
|
358
|
+
if (stillMissing) {
|
|
359
|
+
console.log(chalk.yellow('⚠️ Some dependencies could not be installed automatically.\n'));
|
|
360
|
+
displayMissingDependencies(recheckResults);
|
|
361
|
+
} else {
|
|
362
|
+
console.log(chalk.green('✓ All optional dependencies installed\n'));
|
|
363
|
+
}
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
} else if (platform === 'darwin' && !brewExists()) {
|
|
367
|
+
console.log(chalk.yellow('ℹ️ Homebrew not found — install it from https://brew.sh to get ffmpeg and other tools automatically.\n'));
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Fall through: ask to continue without the missing deps
|
|
371
|
+
const { continueAnyway } = await inquirer.prompt([{
|
|
372
|
+
type: 'confirm',
|
|
373
|
+
name: 'continueAnyway',
|
|
374
|
+
message: 'Some optional dependencies are missing. Continue anyway?',
|
|
375
|
+
default: true
|
|
376
|
+
}]);
|
|
377
|
+
|
|
378
|
+
if (!continueAnyway) {
|
|
379
|
+
console.log(chalk.yellow('\nInstallation cancelled. Install the missing dependencies and try again.\n'));
|
|
380
|
+
process.exit(0);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Locate AgentVibes installation directory
|
|
386
|
+
* @param {boolean} isWindows - Whether running on Windows
|
|
387
|
+
* @returns {Promise<string>} AgentVibes directory path
|
|
388
|
+
*/
|
|
389
|
+
async function locateAgentVibesDir(isWindows) {
|
|
390
|
+
console.log(chalk.bold('📁 Step 2: Locating AgentVibes installation...\n'));
|
|
391
|
+
|
|
392
|
+
let agentVibesDir = getAgentVibesDir();
|
|
393
|
+
|
|
394
|
+
if (!agentVibesDir) {
|
|
395
|
+
const { customPath } = await inquirer.prompt([{
|
|
396
|
+
type: 'input',
|
|
397
|
+
name: 'customPath',
|
|
398
|
+
message: 'Enter the path to your AgentVibes installation:',
|
|
399
|
+
default: isWindows ? 'C:\\Users\\USERNAME\\AgentVibes' : '~/AgentVibes',
|
|
400
|
+
validate: (input) => {
|
|
401
|
+
const expanded = input.replace(/^~/, os.homedir());
|
|
402
|
+
if (fs.existsSync(path.join(expanded, '.claude/hooks/play-tts.sh'))) {
|
|
403
|
+
return true;
|
|
404
|
+
}
|
|
405
|
+
return 'AgentVibes not found at this path. Please check and try again.';
|
|
406
|
+
}
|
|
407
|
+
}]);
|
|
408
|
+
|
|
409
|
+
agentVibesDir = customPath.replace(/^~/, os.homedir());
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
console.log(chalk.green(`✓ Found AgentVibes at: ${agentVibesDir}\n`));
|
|
413
|
+
return agentVibesDir;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Check and setup WSL on Windows
|
|
418
|
+
* @returns {Promise<void>}
|
|
419
|
+
*/
|
|
420
|
+
async function setupWindowsWSL() {
|
|
421
|
+
console.log(chalk.bold('🪟 Step 3: Windows environment setup...\n'));
|
|
422
|
+
|
|
423
|
+
const hasWSL = checkWSL();
|
|
424
|
+
|
|
425
|
+
if (hasWSL) {
|
|
426
|
+
console.log(chalk.green('✓ WSL is installed\n'));
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
console.log(chalk.yellow('⚠️ WSL (Windows Subsystem for Linux) is required but not installed.'));
|
|
431
|
+
const { installWSL } = await inquirer.prompt([{
|
|
432
|
+
type: 'confirm',
|
|
433
|
+
name: 'installWSL',
|
|
434
|
+
message: 'Install WSL now? (Requires restart)',
|
|
435
|
+
default: true
|
|
436
|
+
}]);
|
|
437
|
+
|
|
438
|
+
if (!installWSL) {
|
|
439
|
+
console.log(chalk.red('\n❌ WSL is required for AgentVibes MCP server on Windows'));
|
|
440
|
+
process.exit(1);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
console.log(chalk.cyan('\n📦 Installing WSL...'));
|
|
444
|
+
try {
|
|
445
|
+
// Security: Use execFileSync with array args to prevent command injection
|
|
446
|
+
execFileSync('wsl', ['--install'], { stdio: 'inherit' });
|
|
447
|
+
console.log(chalk.green('\n✅ WSL installed successfully!'));
|
|
448
|
+
console.log(chalk.yellow('⚠️ Please restart your computer and run this installer again.'));
|
|
449
|
+
process.exit(0);
|
|
450
|
+
} catch (error) {
|
|
451
|
+
console.error(chalk.red('\n❌ Failed to install WSL'));
|
|
452
|
+
console.error(chalk.yellow('Please install WSL manually: https://aka.ms/wsl'));
|
|
453
|
+
process.exit(1);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Select and install TTS provider
|
|
459
|
+
* @param {boolean} isWindows - Whether running on Windows
|
|
460
|
+
* @returns {Promise<string>} Selected provider name
|
|
461
|
+
*/
|
|
462
|
+
async function setupTTSProvider(isWindows) {
|
|
463
|
+
console.log(chalk.bold('🎤 Step 4: Choose TTS provider...\n'));
|
|
464
|
+
|
|
465
|
+
const { provider } = await inquirer.prompt([{
|
|
466
|
+
type: 'list',
|
|
467
|
+
name: 'provider',
|
|
468
|
+
message: 'Select your preferred TTS provider:',
|
|
469
|
+
choices: [
|
|
470
|
+
{
|
|
471
|
+
name: 'Piper TTS (Free, Offline, Open Source) - Recommended',
|
|
472
|
+
value: 'piper',
|
|
473
|
+
short: 'Piper'
|
|
474
|
+
},
|
|
475
|
+
{
|
|
476
|
+
name: 'macOS TTS (Native macOS text-to-speech)',
|
|
477
|
+
value: 'macos',
|
|
478
|
+
short: 'macOS'
|
|
479
|
+
}
|
|
480
|
+
]
|
|
481
|
+
}]);
|
|
482
|
+
|
|
483
|
+
if (provider === 'piper') {
|
|
484
|
+
console.log(chalk.cyan('\n📦 Installing Piper TTS...'));
|
|
485
|
+
await installPiper(isWindows);
|
|
486
|
+
} else if (provider === 'macos') {
|
|
487
|
+
console.log(chalk.cyan('\n✅ macOS TTS uses native system voices - no installation needed'));
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
return provider;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Setup Python dependencies
|
|
495
|
+
* @param {boolean} isWindows - Whether running on Windows
|
|
496
|
+
* @returns {Promise<void>}
|
|
497
|
+
*/
|
|
498
|
+
async function setupPythonDependencies(isWindows) {
|
|
499
|
+
console.log(chalk.bold('\n🐍 Step 5: Installing Python dependencies...\n'));
|
|
500
|
+
|
|
501
|
+
const pythonCheck = isWindows
|
|
502
|
+
? { available: true, command: 'python3' } // WSL Python
|
|
503
|
+
: checkPython();
|
|
504
|
+
|
|
505
|
+
if (!pythonCheck.available) {
|
|
506
|
+
console.error(chalk.red('❌ Python not found!'));
|
|
507
|
+
console.log(chalk.yellow('Please install Python 3.10+ from https://python.org'));
|
|
508
|
+
process.exit(1);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
console.log(chalk.green(`✓ Python found: ${pythonCheck.version || 'python3'}\n`));
|
|
512
|
+
|
|
513
|
+
// Check and install MCP package
|
|
514
|
+
const hasMCP = isWindows
|
|
515
|
+
? false // Always install in WSL
|
|
516
|
+
: checkMCPPackage(pythonCheck.command);
|
|
517
|
+
|
|
518
|
+
if (!hasMCP) {
|
|
519
|
+
await installMCPPackage(pythonCheck.command, isWindows);
|
|
520
|
+
} else {
|
|
521
|
+
console.log(chalk.green('✓ Python MCP package already installed\n'));
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* Interactive voice download step shown after Piper installs.
|
|
527
|
+
* Offers starter, libritts-900, full pack, or skip.
|
|
528
|
+
* @param {string} agentVibesDir - Path to the AgentVibes installation directory
|
|
529
|
+
* @returns {Promise<void>}
|
|
530
|
+
*/
|
|
531
|
+
async function downloadPiperVoices(agentVibesDir) {
|
|
532
|
+
console.log(chalk.bold('\n🎙️ Step 4b: Download Voice Models\n'));
|
|
533
|
+
|
|
534
|
+
// Skip if voices are already present — respect PIPER_VOICES_DIR override so we
|
|
535
|
+
// check the same directory the runtime will use.
|
|
536
|
+
const homeDir = os.homedir();
|
|
537
|
+
const voiceDir = process.env.PIPER_VOICES_DIR || path.join(homeDir, '.claude', 'piper-voices');
|
|
538
|
+
try {
|
|
539
|
+
if (fs.existsSync(voiceDir)) {
|
|
540
|
+
const onnxFiles = fs.readdirSync(voiceDir).filter(f => f.endsWith('.onnx'));
|
|
541
|
+
// Only count voices where both .onnx and .onnx.json exist and are non-empty
|
|
542
|
+
const completeVoices = onnxFiles.filter(f => {
|
|
543
|
+
try {
|
|
544
|
+
const onnxStat = fs.statSync(path.join(voiceDir, f));
|
|
545
|
+
const jsonStat = fs.statSync(path.join(voiceDir, f + '.json'));
|
|
546
|
+
return onnxStat.size > 0 && jsonStat.size > 0;
|
|
547
|
+
} catch { return false; }
|
|
548
|
+
});
|
|
549
|
+
if (completeVoices.length > 0) {
|
|
550
|
+
console.log(chalk.green(`✓ ${completeVoices.length} voice model(s) already installed — skipping download\n`));
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
} catch { /* ignore, fall through to download prompt */ }
|
|
555
|
+
|
|
556
|
+
console.log(chalk.gray('Piper needs at least one voice model to speak.\n'));
|
|
557
|
+
|
|
558
|
+
const { voiceChoice } = await inquirer.prompt([{
|
|
559
|
+
type: 'list',
|
|
560
|
+
name: 'voiceChoice',
|
|
561
|
+
message: 'Which voices would you like to download?',
|
|
562
|
+
choices: [
|
|
563
|
+
{
|
|
564
|
+
name: chalk.cyan('Starter voice') + chalk.gray(' — en_US-lessac-medium (~13 MB, download in seconds)'),
|
|
565
|
+
value: 'starter',
|
|
566
|
+
short: 'Starter'
|
|
567
|
+
},
|
|
568
|
+
{
|
|
569
|
+
name: chalk.cyan('LibriTTS 900-speaker pack') + chalk.gray(' — en_US-libritts-high (~57 MB, 900 unique named speakers)'),
|
|
570
|
+
value: 'libritts',
|
|
571
|
+
short: 'LibriTTS 900'
|
|
572
|
+
},
|
|
573
|
+
{
|
|
574
|
+
name: chalk.cyan('Full pack') + chalk.gray(' — 11 voices including LibriTTS (~250 MB, all BMAD agent voices)'),
|
|
575
|
+
value: 'full',
|
|
576
|
+
short: 'Full pack'
|
|
577
|
+
},
|
|
578
|
+
{
|
|
579
|
+
name: chalk.gray('Skip — download voices later with /agent-vibes:add'),
|
|
580
|
+
value: 'skip',
|
|
581
|
+
short: 'Skip'
|
|
582
|
+
}
|
|
583
|
+
]
|
|
584
|
+
}]);
|
|
585
|
+
|
|
586
|
+
if (voiceChoice === 'skip') {
|
|
587
|
+
console.log(chalk.yellow('\n⚠️ No voices downloaded.'));
|
|
588
|
+
console.log(chalk.gray(' Download voices anytime with: ') + chalk.cyan('/agent-vibes:add\n'));
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
const voiceManagerScript = path.join(agentVibesDir, '.claude', 'hooks', 'piper-voice-manager.sh');
|
|
593
|
+
const fullPackScript = path.join(agentVibesDir, '.claude', 'hooks', 'piper-download-voices.sh');
|
|
594
|
+
|
|
595
|
+
if (voiceChoice === 'starter') {
|
|
596
|
+
console.log(chalk.cyan('\n📥 Downloading en_US-lessac-medium (~13 MB)...\n'));
|
|
597
|
+
try {
|
|
598
|
+
// Use positional params ($1/$2) so the script path is never string-interpolated
|
|
599
|
+
execFileSync('bash', ['-c', 'source "$1" && download_voice "$2"', '--', voiceManagerScript, 'en_US-lessac-medium'], { // NOSONAR
|
|
600
|
+
stdio: 'inherit',
|
|
601
|
+
env: process.env
|
|
602
|
+
});
|
|
603
|
+
console.log(chalk.green('\n✅ Starter voice ready!\n'));
|
|
604
|
+
} catch {
|
|
605
|
+
console.log(chalk.yellow('\n⚠️ Download failed — try later with: /agent-vibes:add\n'));
|
|
606
|
+
}
|
|
607
|
+
} else if (voiceChoice === 'libritts') {
|
|
608
|
+
console.log(chalk.cyan('\n📥 Downloading LibriTTS 900-speaker pack (~57 MB)...\n'));
|
|
609
|
+
console.log(chalk.gray(' This gives you 900 individually named speakers to choose from.\n'));
|
|
610
|
+
try {
|
|
611
|
+
execFileSync('bash', ['-c', 'source "$1" && download_voice "$2"', '--', voiceManagerScript, 'en_US-libritts-high'], { // NOSONAR
|
|
612
|
+
stdio: 'inherit',
|
|
613
|
+
env: process.env
|
|
614
|
+
});
|
|
615
|
+
console.log(chalk.green('\n✅ LibriTTS 900-speaker pack ready!'));
|
|
616
|
+
console.log(chalk.gray(' Browse speakers with: ') + chalk.cyan('/agent-vibes:list\n'));
|
|
617
|
+
} catch {
|
|
618
|
+
console.log(chalk.yellow('\n⚠️ Download failed — try later with: /agent-vibes:add\n'));
|
|
619
|
+
}
|
|
620
|
+
} else if (voiceChoice === 'full') {
|
|
621
|
+
console.log(chalk.cyan('\n📥 Downloading full voice pack (~250 MB)...\n'));
|
|
622
|
+
console.log(chalk.gray(' This includes all BMAD agent voices plus LibriTTS 900 speakers.\n'));
|
|
623
|
+
try {
|
|
624
|
+
if (fs.existsSync(fullPackScript)) {
|
|
625
|
+
execFileSync('bash', [fullPackScript, '--yes'], { // NOSONAR
|
|
626
|
+
stdio: 'inherit',
|
|
627
|
+
env: process.env
|
|
628
|
+
});
|
|
629
|
+
console.log(chalk.green('\n✅ Full voice pack downloaded!\n'));
|
|
630
|
+
} else {
|
|
631
|
+
console.log(chalk.yellow(' Download script not found at: ' + fullPackScript));
|
|
632
|
+
console.log(chalk.yellow(' Try: /agent-vibes:add\n'));
|
|
633
|
+
}
|
|
634
|
+
} catch {
|
|
635
|
+
console.log(chalk.yellow('\n⚠️ Download failed — try later with: /agent-vibes:add\n'));
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
/**
|
|
641
|
+
* Display welcome banner
|
|
642
|
+
* @param {string} platform - Platform name
|
|
643
|
+
*/
|
|
644
|
+
function showWelcomeBanner(platform) {
|
|
645
|
+
console.log(boxen(
|
|
646
|
+
chalk.bold.cyan('AgentVibes MCP Server Installer') + '\n\n' +
|
|
647
|
+
'Give Claude Desktop a voice! 🎤',
|
|
648
|
+
{
|
|
649
|
+
padding: 1,
|
|
650
|
+
margin: 1,
|
|
651
|
+
borderStyle: 'round',
|
|
652
|
+
borderColor: 'cyan'
|
|
653
|
+
}
|
|
654
|
+
));
|
|
655
|
+
|
|
656
|
+
const platformLabel = platform === 'win32' ? 'Windows' : platform === 'darwin' ? 'macOS' : 'Linux';
|
|
657
|
+
console.log(chalk.gray(`Platform: ${platformLabel}\n`));
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
/**
|
|
661
|
+
* Display success message
|
|
662
|
+
* @param {string} configPath - Config file path
|
|
663
|
+
* @param {string} provider - Provider name
|
|
664
|
+
*/
|
|
665
|
+
function showSuccessMessage(configPath, provider) {
|
|
666
|
+
console.log(boxen(
|
|
667
|
+
chalk.bold.green('✅ Installation Complete!') + '\n\n' +
|
|
668
|
+
chalk.white('Next steps:\n') +
|
|
669
|
+
chalk.cyan('1. Restart Claude Desktop\n') +
|
|
670
|
+
chalk.cyan('2. Try: "Say hello using text to speech"\n') +
|
|
671
|
+
chalk.cyan('3. Enjoy your talking Claude! 🎤'),
|
|
672
|
+
{
|
|
673
|
+
padding: 1,
|
|
674
|
+
margin: 1,
|
|
675
|
+
borderStyle: 'round',
|
|
676
|
+
borderColor: 'green'
|
|
677
|
+
}
|
|
678
|
+
));
|
|
679
|
+
|
|
680
|
+
console.log(chalk.gray('\nConfiguration saved to:'));
|
|
681
|
+
console.log(chalk.gray(` ${configPath}\n`));
|
|
682
|
+
|
|
683
|
+
if (provider === 'piper') {
|
|
684
|
+
console.log(chalk.gray('Browse voices: ') + chalk.cyan('/agent-vibes:list') + '\n');
|
|
685
|
+
console.log(chalk.gray('Add more voices: ') + chalk.cyan('/agent-vibes:add') + '\n');
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
export async function installMCP() {
|
|
690
|
+
const platform = os.platform();
|
|
691
|
+
const isWindows = platform === 'win32';
|
|
692
|
+
|
|
693
|
+
showWelcomeBanner(platform);
|
|
694
|
+
|
|
695
|
+
// Step 1: Check system dependencies
|
|
696
|
+
await checkSystemDependencies();
|
|
697
|
+
|
|
698
|
+
// Step 2: Find AgentVibes directory
|
|
699
|
+
const agentVibesDir = await locateAgentVibesDir(isWindows);
|
|
700
|
+
|
|
701
|
+
// Step 3: Windows-specific checks
|
|
702
|
+
if (isWindows) {
|
|
703
|
+
await setupWindowsWSL();
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// Step 4: Choose TTS provider
|
|
707
|
+
const provider = await setupTTSProvider(isWindows);
|
|
708
|
+
|
|
709
|
+
// Step 4b: Download voices if Piper was selected
|
|
710
|
+
if (provider === 'piper') {
|
|
711
|
+
await downloadPiperVoices(agentVibesDir);
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
// Step 5: Install Python dependencies
|
|
715
|
+
await setupPythonDependencies(isWindows);
|
|
716
|
+
|
|
717
|
+
// Step 6: Configure provider in AgentVibes
|
|
718
|
+
console.log(chalk.bold('⚙️ Step 6: Configuring AgentVibes...\n'));
|
|
719
|
+
const providerFile = path.join(agentVibesDir, '.claude', 'tts-provider.txt');
|
|
720
|
+
fs.writeFileSync(providerFile, provider);
|
|
721
|
+
console.log(chalk.green(`✓ Set provider to: ${provider}\n`));
|
|
722
|
+
|
|
723
|
+
// Step 7: Update Claude Desktop config
|
|
724
|
+
console.log(chalk.bold('📝 Step 7: Updating Claude Desktop configuration...\n'));
|
|
725
|
+
const configPath = updateClaudeConfig(agentVibesDir, provider);
|
|
726
|
+
console.log(chalk.green(`✓ Updated: ${configPath}\n`));
|
|
727
|
+
|
|
728
|
+
// Success!
|
|
729
|
+
showSuccessMessage(configPath, provider);
|
|
730
|
+
}
|