agentvibes 2.16.0 → 2.17.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.
Files changed (41) hide show
  1. package/.claude/config/audio-effects.cfg +1 -1
  2. package/.claude/config/audio-effects.cfg.sample +52 -0
  3. package/.claude/hooks/audio-processor.sh +2 -2
  4. package/.claude/hooks/bmad-voice-manager.sh +152 -5
  5. package/.claude/hooks/effects-manager.sh +268 -0
  6. package/.claude/hooks/learn-manager.sh +7 -7
  7. package/.claude/hooks/personality-manager.sh +35 -24
  8. package/.claude/hooks/piper-installer.sh +2 -2
  9. package/.claude/hooks/provider-manager.sh +4 -4
  10. package/.claude/hooks/replay-target-audio.sh +1 -1
  11. package/.claude/hooks/speed-manager.sh +4 -4
  12. package/.claude/hooks/voice-manager.sh +50 -47
  13. package/.claude/personalities/angry.md +2 -4
  14. package/.claude/personalities/annoying.md +2 -4
  15. package/.claude/personalities/crass.md +2 -4
  16. package/.claude/personalities/dramatic.md +2 -4
  17. package/.claude/personalities/dry-humor.md +1 -3
  18. package/.claude/personalities/flirty.md +2 -4
  19. package/.claude/personalities/funny.md +2 -4
  20. package/.claude/personalities/grandpa.md +1 -3
  21. package/.claude/personalities/millennial.md +2 -4
  22. package/.claude/personalities/moody.md +2 -4
  23. package/.claude/personalities/normal.md +2 -4
  24. package/.claude/personalities/pirate.md +2 -4
  25. package/.claude/personalities/poetic.md +2 -4
  26. package/.claude/personalities/professional.md +2 -4
  27. package/.claude/personalities/rapper.md +1 -3
  28. package/.claude/personalities/robot.md +2 -4
  29. package/.claude/personalities/sarcastic.md +2 -4
  30. package/.claude/personalities/sassy.md +1 -3
  31. package/.claude/personalities/surfer-dude.md +2 -4
  32. package/.claude/personalities/zen.md +2 -4
  33. package/README.md +146 -10
  34. package/mcp-server/server.py +120 -17
  35. package/package.json +1 -1
  36. package/src/cli/list-personalities.js +110 -0
  37. package/src/cli/list-voices.js +114 -0
  38. package/src/commands/install-mcp.js +50 -34
  39. package/src/installer.js +1157 -450
  40. package/src/utils/dependency-checker.js +403 -0
  41. package/src/utils/list-formatter.js +194 -0
package/src/installer.js CHANGED
@@ -71,6 +71,566 @@ const packageJson = JSON.parse(
71
71
  );
72
72
  const VERSION = packageJson.version;
73
73
 
74
+ /**
75
+ * Create header and footer for installer pages
76
+ * @param {string} pageTitle - Title of current page
77
+ * @param {number} currentPage - Current page number (0-indexed, relative to section)
78
+ * @param {number} totalPages - Total number of pages across entire installer
79
+ * @param {number} pageOffset - Offset to add to currentPage for global numbering
80
+ * @returns {Object} - Object with header and footer strings
81
+ */
82
+ function createPageHeaderFooter(pageTitle, currentPage, totalPages, pageOffset = 0) {
83
+ // Calculate consistent width for header
84
+ const boxWidth = 80;
85
+
86
+ // Header: Agent Vibes Installer + Version + Page Title + Page Number + Links
87
+ const agentText = chalk.cyan('Agent');
88
+ const vibesText = chalk.magentaBright('Vibes');
89
+ const globalPageNum = currentPage + pageOffset + 1; // Convert to 1-indexed and add offset
90
+ const pageNum = chalk.green(`Page ${globalPageNum}/${totalPages}`);
91
+ const website = chalk.gray('https://agentvibes.org');
92
+ const github = chalk.gray('https://github.com/paulpreibisch/AgentVibes');
93
+
94
+ const header = boxen(
95
+ `${agentText} ${vibesText} ${chalk.yellow(`v${VERSION}`)} ${chalk.white('Installer')} • ${pageNum}\n\n` +
96
+ `${chalk.cyan(pageTitle)}\n\n` +
97
+ `${website} • ${github}`,
98
+ {
99
+ padding: { top: 0, bottom: 0, left: 2, right: 2 },
100
+ borderStyle: 'round',
101
+ borderColor: 'cyan',
102
+ textAlignment: 'center',
103
+ width: boxWidth,
104
+ backgroundColor: '#1a1a1a'
105
+ }
106
+ );
107
+
108
+ // No separate footer needed - everything in header
109
+ const footer = '';
110
+
111
+ return { header, footer };
112
+ }
113
+
114
+ /**
115
+ * Display paginated installation content with Previous/Next navigation
116
+ * @param {Array} pages - Array of {title, content} objects to display
117
+ * @param {Object} options - Options for pagination (yes, continueLabel, pageOffset, totalPages, showPreviousOnFirst)
118
+ * @returns {Promise<void>}
119
+ */
120
+ async function showPaginatedContent(pages, options = {}) {
121
+ if (options.yes || pages.length === 0) {
122
+ // In non-interactive mode or no pages, just display all content
123
+ pages.forEach(page => console.log(page.content));
124
+ return;
125
+ }
126
+
127
+ const continueLabel = options.continueLabel || '✓ Continue with Installation';
128
+ const pageOffset = options.pageOffset || 0;
129
+ const totalPages = options.totalPages || pages.length;
130
+ const showPreviousOnFirst = options.showPreviousOnFirst || false;
131
+ let currentPage = 0;
132
+
133
+ while (currentPage >= 0 && currentPage < pages.length) {
134
+ // Clear screen and show current page with header/footer
135
+ console.clear();
136
+
137
+ const { header, footer } = createPageHeaderFooter(
138
+ pages[currentPage].title,
139
+ currentPage,
140
+ totalPages,
141
+ pageOffset
142
+ );
143
+
144
+ console.log(header);
145
+ console.log('');
146
+ console.log(pages[currentPage].content);
147
+
148
+ // Build navigation message with Previous/Next on same line
149
+ let navMessage = '';
150
+ if (currentPage > 0 && currentPage < pages.length - 1) {
151
+ navMessage = `${chalk.cyan('←')} Previous | Next ${chalk.cyan('→')} | ${continueLabel}`;
152
+ } else if (currentPage > 0) {
153
+ navMessage = `${chalk.cyan('←')} Previous | ${continueLabel}`;
154
+ } else if (currentPage < pages.length - 1) {
155
+ navMessage = `Next ${chalk.cyan('→')} | ${continueLabel}`;
156
+ } else {
157
+ navMessage = continueLabel;
158
+ }
159
+
160
+ // Build navigation choices with colors
161
+ const choices = [];
162
+ if (currentPage < pages.length - 1) {
163
+ choices.push({ name: chalk.green('Next →'), value: 'next' });
164
+ } else {
165
+ // Only show "Start Installation" on the last page
166
+ choices.push({ name: chalk.cyan(`✓ ${continueLabel.replace('✓ ', '')}`), value: 'continue' });
167
+ }
168
+ if (currentPage > 0 || showPreviousOnFirst) {
169
+ choices.push({ name: chalk.magentaBright('← Previous'), value: 'prev' });
170
+ }
171
+
172
+ console.log('');
173
+ console.log(footer);
174
+ console.log('');
175
+
176
+ // Show navigation prompt
177
+ const { action } = await inquirer.prompt([{
178
+ type: 'list',
179
+ name: 'action',
180
+ message: chalk.cyan(`📄 ${pages[currentPage].title}`),
181
+ choices,
182
+ default: currentPage < pages.length - 1 ? 'next' : 'continue'
183
+ }]);
184
+
185
+ if (action === 'prev') {
186
+ if (currentPage > 0) {
187
+ currentPage--;
188
+ } else if (showPreviousOnFirst) {
189
+ // User clicked Previous on first page - signal to caller
190
+ return 'prev';
191
+ }
192
+ } else if (action === 'next') {
193
+ currentPage++;
194
+ } else {
195
+ // Continue - exit loop
196
+ console.clear();
197
+ break;
198
+ }
199
+ }
200
+ }
201
+
202
+ /**
203
+ * Collect all configuration answers through paginated question flow
204
+ * @param {Object} options - Installation options (yes, pageOffset, totalPages)
205
+ * @returns {Promise<Object>} Configuration object with all answers
206
+ */
207
+ async function collectConfiguration(options = {}) {
208
+ const config = {
209
+ provider: null,
210
+ piperPath: null,
211
+ reverb: 'light',
212
+ backgroundMusic: {
213
+ enabled: true,
214
+ track: 'agentvibes_soft_flamenco_loop.mp3'
215
+ },
216
+ verbosity: 'high'
217
+ };
218
+
219
+ if (options.yes) {
220
+ // Non-interactive mode - use defaults
221
+ config.provider = process.platform === 'darwin' ? 'macos' : 'piper';
222
+ const homeDir = process.env.HOME || process.env.USERPROFILE;
223
+ config.piperPath = path.join(homeDir, '.claude', 'piper-voices');
224
+ return config;
225
+ }
226
+
227
+ let currentPage = 0;
228
+ const sectionPages = 4; // System Dependencies, Provider, Audio Settings, Verbosity
229
+ const pageOffset = options.pageOffset || 0;
230
+ const totalPages = options.totalPages || sectionPages;
231
+
232
+ console.clear();
233
+ console.log(chalk.cyan.bold('\n⚙️ Configuration Setup\n'));
234
+ console.log(chalk.white('Please configure your AgentVibes installation.\n'));
235
+ console.log(chalk.gray('Use arrow keys to navigate between pages.\n'));
236
+
237
+ while (currentPage >= 0 && currentPage < sectionPages) {
238
+ console.clear();
239
+
240
+ // Show header
241
+ const pageTitle = currentPage === 0 ? 'System Dependencies' :
242
+ currentPage === 1 ? 'TTS Provider Configuration' :
243
+ currentPage === 2 ? 'Audio Settings' :
244
+ 'Verbosity Settings';
245
+ const { header, footer } = createPageHeaderFooter(pageTitle, currentPage, totalPages, pageOffset);
246
+ console.log(header);
247
+ console.log('');
248
+
249
+ if (currentPage === 0) {
250
+ // Page 1: System Dependencies Check
251
+ const { checkDependencies } = await import('./utils/dependency-checker.js');
252
+ const depResults = checkDependencies();
253
+
254
+ let depContent = chalk.gray('System dependencies are tools AgentVibes needs to function properly.\n');
255
+ depContent += chalk.gray('Required tools must be installed, optional tools enable extra features.\n\n');
256
+
257
+ // Satisfied dependencies
258
+ if (depResults.core.node?.isCompatible) {
259
+ depContent += chalk.green(`✓ Node.js ${depResults.core.node.version}\n`);
260
+ }
261
+ if (depResults.core.python?.isCompatible) {
262
+ depContent += chalk.green(`✓ Python ${depResults.core.python.version}\n`);
263
+ }
264
+ if (depResults.core.bash?.isModern) {
265
+ depContent += chalk.green(`✓ Bash ${depResults.core.bash.version}\n`);
266
+ }
267
+ if (depResults.optional.curl) {
268
+ depContent += chalk.green('✓ curl\n');
269
+ }
270
+ if (depResults.optional.sox) {
271
+ depContent += chalk.green('✓ sox\n');
272
+ }
273
+ if (depResults.optional.ffmpeg) {
274
+ depContent += chalk.green('✓ ffmpeg\n');
275
+ }
276
+ if (depResults.optional.bc) {
277
+ depContent += chalk.green('✓ bc\n');
278
+ }
279
+ if (depResults.optional.flock) {
280
+ depContent += chalk.green('✓ flock\n');
281
+ }
282
+ if (depResults.optional.pipx) {
283
+ depContent += chalk.green('✓ pipx\n');
284
+ }
285
+ if (depResults.optional.audioPlayer) {
286
+ depContent += chalk.green('✓ audio player (paplay/aplay/mpv)\n');
287
+ }
288
+
289
+ // Missing dependencies
290
+ if (Object.keys(depResults.missing).length > 0) {
291
+ depContent += '\n' + chalk.gray('─'.repeat(50)) + '\n\n';
292
+ depContent += chalk.yellow.bold('Missing (Optional):\n\n');
293
+
294
+ if (depResults.missing.curl) depContent += chalk.yellow('⚠ curl - needed for downloads\n');
295
+ if (depResults.missing.sox) depContent += chalk.yellow('⚠ sox - audio effects\n');
296
+ if (depResults.missing.ffmpeg) depContent += chalk.yellow('⚠ ffmpeg - background music\n');
297
+ if (depResults.missing.bc) depContent += chalk.yellow('⚠ bc - audio calculations\n');
298
+ if (depResults.missing.flock) depContent += chalk.yellow('⚠ flock - TTS queue locking\n');
299
+ if (depResults.missing.pipx) depContent += chalk.yellow('⚠ pipx - Piper TTS installation\n');
300
+ if (depResults.missing.audioPlayer) depContent += chalk.yellow('⚠ audio player - playback\n');
301
+
302
+ depContent += '\n' + chalk.gray('TTS will still work without optional tools');
303
+
304
+ // Add install commands
305
+ const os = await import('os');
306
+ const { getInstallCommands } = await import('./utils/dependency-checker.js');
307
+ const platform = os.platform();
308
+ const installCmds = getInstallCommands(depResults.missing, platform);
309
+
310
+ if (installCmds.length > 0) {
311
+ depContent += '\n\n' + chalk.gray('─'.repeat(50)) + '\n\n';
312
+ depContent += chalk.cyan.bold('To Install Missing Tools:\n\n');
313
+
314
+ installCmds.forEach(({ label, command }) => {
315
+ depContent += chalk.cyan(`${label}:\n`);
316
+ depContent += chalk.white(` ${command}\n\n`);
317
+ });
318
+ }
319
+ }
320
+
321
+ const depsBoxen = boxen(depContent.trim(), {
322
+ padding: 1,
323
+ margin: 1,
324
+ borderStyle: 'round',
325
+ borderColor: Object.keys(depResults.missing).length > 0 ? 'yellow' : 'green',
326
+ title: chalk.bold('🔧 System Dependencies'),
327
+ titleAlignment: 'center'
328
+ });
329
+
330
+ console.log(depsBoxen);
331
+ } else if (currentPage === 1) {
332
+ // Page 2: TTS Provider & Voice Storage
333
+
334
+ // On non-macOS platforms, only Piper is available - auto-select it
335
+ if (process.platform !== 'darwin') {
336
+ console.log(boxen(
337
+ chalk.gray('Text-to-Speech (TTS) converts Claude\'s text responses into spoken audio.\n\n') +
338
+ chalk.white('Your TTS Provider:\n\n') +
339
+ chalk.green('🆓 Piper TTS (Free, Offline)\n') +
340
+ chalk.gray(' • 50+ Hugging Face AI voices\n') +
341
+ chalk.gray(' • Human-like speech quality\n') +
342
+ chalk.gray(' • No API key required\n\n') +
343
+ chalk.dim('(Automatically selected - only option for Linux/WSL)'),
344
+ {
345
+ padding: 1,
346
+ margin: 1,
347
+ borderStyle: 'round',
348
+ borderColor: 'gray',
349
+ title: chalk.bold('☺ TTS Provider'),
350
+ titleAlignment: 'center'
351
+ }
352
+ ));
353
+
354
+ config.provider = 'piper';
355
+
356
+ // No confirmation needed - just auto-continue
357
+ } else {
358
+ // macOS - show choice between macOS Say and Piper
359
+ console.log(boxen(
360
+ chalk.gray('Text-to-Speech (TTS) converts Claude\'s text responses into spoken audio.\n\n') +
361
+ chalk.white('Choose your Text-to-Speech provider.\n\n') +
362
+ chalk.yellow('🍎 macOS Say\n') +
363
+ chalk.gray(' • Built-in to macOS\n') +
364
+ chalk.gray(' • Zero setup required\n') +
365
+ chalk.gray(' • 40+ system voices\n\n') +
366
+ chalk.green('🆓 Piper TTS\n') +
367
+ chalk.gray(' • Free & offline\n') +
368
+ chalk.gray(' • 50+ Hugging Face AI voices\n') +
369
+ chalk.gray(' • Human-like speech quality'),
370
+ {
371
+ padding: 1,
372
+ margin: 1,
373
+ borderStyle: 'round',
374
+ borderColor: 'gray',
375
+ title: chalk.bold('☺ TTS Provider'),
376
+ titleAlignment: 'center'
377
+ }
378
+ ));
379
+
380
+ // Provider selection
381
+ const providerChoices = [
382
+ {
383
+ name: chalk.yellow('🍎 macOS Say (Recommended)'),
384
+ value: 'macos'
385
+ },
386
+ {
387
+ name: chalk.green('🆓 Piper TTS (Free, Offline)'),
388
+ value: 'piper'
389
+ },
390
+ new inquirer.Separator(),
391
+ {
392
+ name: chalk.magentaBright('← Back to Welcome'),
393
+ value: '__back__'
394
+ }
395
+ ];
396
+
397
+ const { provider } = await inquirer.prompt([{
398
+ type: 'list',
399
+ name: 'provider',
400
+ message: chalk.yellow('Select TTS provider:'),
401
+ choices: providerChoices,
402
+ default: config.provider || 'macos'
403
+ }]);
404
+
405
+ // Check if user wants to go back
406
+ if (provider === '__back__') {
407
+ return null;
408
+ }
409
+
410
+ config.provider = provider;
411
+ }
412
+
413
+ // If Piper selected, ask for voice storage location
414
+ if (config.provider === 'piper') {
415
+ const homeDir = process.env.HOME || process.env.USERPROFILE;
416
+ const defaultPiperPath = path.join(homeDir, '.claude', 'piper-voices');
417
+
418
+ // Check if voices already exist
419
+ const existingVoices = await checkExistingPiperVoices(defaultPiperPath);
420
+
421
+ if (!existingVoices.installed) {
422
+ console.log('\n' + boxen(
423
+ chalk.white('Piper voice models are ~25MB each.\n') +
424
+ chalk.white('They can be stored globally to be shared\n') +
425
+ chalk.white('across all your projects, or locally per project.'),
426
+ {
427
+ padding: 1,
428
+ margin: { top: 1, bottom: 1, left: 1, right: 1 },
429
+ borderStyle: 'round',
430
+ borderColor: 'gray',
431
+ title: chalk.bold('📁 Voice Storage'),
432
+ titleAlignment: 'center'
433
+ }
434
+ ));
435
+
436
+ const { piperPath } = await inquirer.prompt([{
437
+ type: 'input',
438
+ name: 'piperPath',
439
+ message: chalk.yellow('Where should Piper voice models be downloaded?'),
440
+ default: config.piperPath || defaultPiperPath,
441
+ validate: (input) => {
442
+ if (!input || input.trim() === '') {
443
+ return 'Please provide a valid path';
444
+ }
445
+ return true;
446
+ }
447
+ }]);
448
+
449
+ config.piperPath = piperPath;
450
+ } else {
451
+ config.piperPath = defaultPiperPath;
452
+ }
453
+ }
454
+
455
+ } else if (currentPage === 2) {
456
+ // Page 3: Audio Settings (Reverb + Background Music)
457
+ console.log(boxen(
458
+ chalk.white('Configure audio effects and background music for your Agents.\n\n') +
459
+ chalk.yellow('Reverb:\n') +
460
+ chalk.gray(' • 💧 Reverb adds room ambiance to TTS audio, making voices sound more natural\n') +
461
+ chalk.gray(' • Change anytime: ') + chalk.cyan('/agent-vibes:effects reverb off/light/medium/heavy/cathedral\n\n') +
462
+ chalk.yellow('Background Music:\n') +
463
+ chalk.gray(' • Optional ambient music during TTS\n') +
464
+ chalk.gray(' • 16 genre choices from Flamenco to City Pop\n') +
465
+ chalk.gray(' • Toggle: ') + chalk.cyan('/agent-vibes:background-music on/off\n') +
466
+ chalk.gray(' • Change track: ') + chalk.cyan('/agent-vibes:background-music set chillwave'),
467
+ {
468
+ padding: 1,
469
+ margin: 1,
470
+ borderStyle: 'round',
471
+ borderColor: 'gray',
472
+ title: chalk.bold('☺ Audio Effects'),
473
+ titleAlignment: 'center'
474
+ }
475
+ ));
476
+
477
+ const { reverbLevel } = await inquirer.prompt([{
478
+ type: 'list',
479
+ name: 'reverbLevel',
480
+ message: chalk.yellow('Select default reverb level:'),
481
+ choices: [
482
+ { name: 'Off (Dry, no reverb)', value: 'off' },
483
+ { name: 'Light (Small room) - Recommended', value: 'light' },
484
+ { name: 'Medium (Conference room)', value: 'medium' },
485
+ { name: 'Heavy (Large hall)', value: 'heavy' },
486
+ { name: 'Cathedral (Epic space)', value: 'cathedral' }
487
+ ],
488
+ default: config.reverb || 'light'
489
+ }]);
490
+
491
+ config.reverb = reverbLevel;
492
+
493
+ // Add spacing before next question
494
+ console.log('');
495
+
496
+ // Background music
497
+ console.log(chalk.gray('🎵 Background music plays ambient tracks during TTS for a more engaging experience.'));
498
+
499
+ const { enableMusic } = await inquirer.prompt([{
500
+ type: 'confirm',
501
+ name: 'enableMusic',
502
+ message: chalk.yellow('Enable background music for TTS?'),
503
+ default: config.backgroundMusic.enabled !== undefined ? config.backgroundMusic.enabled : true
504
+ }]);
505
+
506
+ config.backgroundMusic.enabled = enableMusic;
507
+
508
+ if (enableMusic) {
509
+ // Add spacing before track selection
510
+ console.log('');
511
+ console.log(chalk.gray('🎼 Choose your default background music genre (you can change this anytime).'));
512
+
513
+ const trackChoices = [
514
+ { name: '🎻 Soft Flamenco (Spanish guitar)', value: 'agentvibes_soft_flamenco_loop.mp3' },
515
+ { name: '🎺 Bachata (Latin - Romantic guitar & bongos)', value: 'agent_vibes_bachata_v1_loop.mp3' },
516
+ { name: '💃 Salsa (Latin - Upbeat brass & percussion)', value: 'agent_vibes_salsa_v2_loop.mp3' },
517
+ { name: '🎸 Cumbia (Latin - Accordion & drums)', value: 'agent_vibes_cumbia_v1_loop.mp3' },
518
+ { name: '🌸 Bossa Nova (Brazilian jazz)', value: 'agent_vibes_bossa_nova_v2_loop.mp3' },
519
+ { name: '🏙️ Japanese City Pop (80s synth)', value: 'agent_vibes_japanese_city_pop_v1_loop.mp3' },
520
+ { name: '🌊 Chillwave (Electronic ambient)', value: 'agent_vibes_chillwave_v2_loop.mp3' },
521
+ { name: '🎹 Dreamy House (Electronic dance)', value: 'dreamy_house_loop.mp3' },
522
+ { name: '🌙 Dark Chill Step (Electronic bass)', value: 'agent_vibes_dark_chill_step_loop.mp3' },
523
+ { name: '🕉️ Goa Trance (Psychedelic electronic)', value: 'agent_vibes_goa_trance_v2_loop.mp3' },
524
+ { name: '🎼 Harpsichord (Baroque classical)', value: 'agent_vibes_harpsichord_v2_loop.mp3' },
525
+ { name: '🎻 Celtic Harp (Irish traditional)', value: 'agent_vibes_celtic_harp_v1_loop.mp3' },
526
+ { name: '🌺 Hawaiian Slack Key Guitar', value: 'agent_vibes_hawaiian_slack_key_guitar_v2_loop.mp3' },
527
+ { name: '🏜️ Arabic Oud (Middle Eastern)', value: 'agent_vibes_arabic_v2_loop.mp3' },
528
+ { name: '🪘 Gnawa Ambient (North African)', value: 'agent_vibes_ganawa_ambient_v2_loop.mp3' },
529
+ { name: '🥁 Tabla Dream Pop (Indian percussion)', value: 'agent_vibes_tabla_dream_pop_v1_loop.mp3' }
530
+ ];
531
+
532
+ const { selectedTrack } = await inquirer.prompt([{
533
+ type: 'list',
534
+ name: 'selectedTrack',
535
+ message: chalk.yellow('Choose default background music track:'),
536
+ choices: trackChoices,
537
+ default: config.backgroundMusic.track || 'agentvibes_soft_flamenco_loop.mp3',
538
+ pageSize: 16
539
+ }]);
540
+
541
+ config.backgroundMusic.track = selectedTrack;
542
+ }
543
+
544
+ } else if (currentPage === 3) {
545
+ // Page 4: Verbosity Settings
546
+ console.log(boxen(
547
+ chalk.white('Choose how much Claude speaks during interactions.\n\n') +
548
+ chalk.yellow('🔊 High:\n') +
549
+ chalk.gray(' • Maximum transparency\n') +
550
+ chalk.gray(' • Speaks acknowledgments, reasoning, decisions, findings\n\n') +
551
+ chalk.yellow('🔉 Medium:\n') +
552
+ chalk.gray(' • Balanced approach\n') +
553
+ chalk.gray(' • Speaks acknowledgments and key updates\n\n') +
554
+ chalk.yellow('🔈 Low:\n') +
555
+ chalk.gray(' • Minimal notifications\n') +
556
+ chalk.gray(' • Only essential messages\n\n') +
557
+ chalk.gray('Change anytime: ') + chalk.cyan('/agent-vibes:verbosity <level>'),
558
+ {
559
+ padding: 1,
560
+ margin: 1,
561
+ borderStyle: 'round',
562
+ borderColor: 'gray',
563
+ title: chalk.bold('☺ TTS Verbosity'),
564
+ titleAlignment: 'center'
565
+ }
566
+ ));
567
+
568
+ console.log(chalk.gray('\n🔊 Verbosity controls how much Claude speaks during tasks (reasoning, findings, etc.).'));
569
+
570
+ const { verbosity } = await inquirer.prompt([{
571
+ type: 'list',
572
+ name: 'verbosity',
573
+ message: chalk.yellow('Select TTS verbosity level:'),
574
+ choices: [
575
+ { name: '🔊 High - Maximum transparency', value: 'high' },
576
+ { name: '🔉 Medium - Balanced', value: 'medium' },
577
+ { name: '🔈 Low - Minimal', value: 'low' }
578
+ ],
579
+ default: config.verbosity || 'high'
580
+ }]);
581
+
582
+ config.verbosity = verbosity;
583
+ }
584
+
585
+ // Add spacing before navigation
586
+ console.log('');
587
+
588
+ // Show footer before navigation
589
+ console.log('');
590
+ console.log(footer);
591
+ console.log('');
592
+
593
+ // Navigation
594
+ const navChoices = [];
595
+ if (currentPage < totalPages - 1) {
596
+ navChoices.push({ name: chalk.green('Next →'), value: 'next' });
597
+ } else {
598
+ navChoices.push({ name: chalk.cyan('✓ Continue to Installation'), value: 'continue' });
599
+ }
600
+
601
+ // Always show Previous button (on first page it goes back to welcome)
602
+ if (currentPage === 0) {
603
+ navChoices.push({ name: chalk.magentaBright('← Back to Welcome'), value: 'back' });
604
+ } else {
605
+ navChoices.push({ name: chalk.magentaBright('← Previous'), value: 'prev' });
606
+ }
607
+
608
+ const { action } = await inquirer.prompt([{
609
+ type: 'list',
610
+ name: 'action',
611
+ message: ' ',
612
+ choices: navChoices,
613
+ default: 'next'
614
+ }]);
615
+
616
+ if (action === 'back') {
617
+ // Return to welcome screen
618
+ console.clear();
619
+ return null; // Signal to caller to show welcome again
620
+ } else if (action === 'prev') {
621
+ currentPage--;
622
+ } else if (action === 'next') {
623
+ currentPage++;
624
+ } else {
625
+ // Continue - exit configuration
626
+ break;
627
+ }
628
+ }
629
+
630
+ console.clear();
631
+ return config;
632
+ }
633
+
74
634
  // Configure CLI
75
635
  program
76
636
  .name('agentvibes')
@@ -124,39 +684,25 @@ function showWelcome() {
124
684
  * Display latest release information box
125
685
  * Shown during install and update commands
126
686
  */
127
- function showReleaseInfo() {
128
- console.log(
129
- boxen(
130
- chalk.white.bold('═══════════════════════════════════════════════════════════════\n') +
131
- chalk.cyan.bold(' 📦 AgentVibes v2.15.0 - Background Music & Audio Effects\n') +
132
- chalk.white.bold('═══════════════════════════════════════════════════════════════\n\n') +
133
- chalk.green.bold('🎙️ WHAT\'S NEW:\n\n') +
134
- chalk.cyan('AgentVibes v2.15.0 introduces a comprehensive background music system\n') +
135
- chalk.cyan('with 16 professionally-optimized tracks and per-agent audio effects.\n') +
136
- chalk.cyan('BMAD v6 integration adds YAML voice mappings for multi-agent conversations.\n') +
137
- chalk.cyan('Breaking: ElevenLabs removed (cost impractical for heavy daily use).\n\n') +
138
- chalk.green.bold(' KEY HIGHLIGHTS:\n\n') +
139
- chalk.gray(' 🎶 16 Background Music Tracks - Latin, World, Electronic, Classical\n') +
140
- chalk.gray(' 🎛️ Audio Effects Processor - Per-agent reverb, pitch, EQ, compression\n') +
141
- chalk.gray(' 🐛 TDD Bug Fix - Background music respects enabled/disabled flag\n') +
142
- chalk.gray(' 🎚️ Natural Language Control - "change to salsa" switches music\n') +
143
- chalk.gray(' 🤖 BMAD v6 Support - YAML voice mappings with auto-detection\n') +
144
- chalk.gray(' 🔊 Paplay Fix - Fixes choppy audio on Linux/WSL RDP connections\n') +
145
- chalk.gray(' ElevenLabs Removed - Migrate to free local Piper TTS\n\n') +
146
- chalk.white.bold('═══════════════════════════════════════════════════════════════\n\n') +
147
- chalk.gray('📖 Full Release Notes: RELEASE_NOTES.md\n') +
148
- chalk.gray('🌐 Website: https://agentvibes.org\n') +
149
- chalk.gray('📦 Repository: https://github.com/paulpreibisch/AgentVibes\n\n') +
150
- chalk.gray('Co-created by Paul Preibisch with Claude AI\n') +
151
- chalk.gray('Copyright © 2025 Paul Preibisch | Apache-2.0 License'),
152
- {
153
- padding: 1,
154
- margin: 1,
155
- borderStyle: 'round',
156
- borderColor: 'cyan',
157
- }
158
- )
159
- );
687
+ function getReleaseInfoBoxen() {
688
+ return chalk.cyan.bold('📦 AgentVibes v2.17.0 - Installer UX Revolution\n\n') +
689
+ chalk.green.bold('🎙️ WHAT\'S NEW:\n\n') +
690
+ chalk.cyan('AgentVibes v2.17.0 delivers a complete installer user experience transformation\n') +
691
+ chalk.cyan('with intelligent system dependency checking, paginated configuration flow, and\n') +
692
+ chalk.cyan('comprehensive inline help with command examples throughout.\n\n') +
693
+ chalk.green.bold(' KEY HIGHLIGHTS:\n\n') +
694
+ chalk.gray(' 🔧 System Dependency Checker - Validates Node, Python, bash, sox, ffmpeg, curl, etc.\n') +
695
+ chalk.gray(' 📄 Paginated Configuration - Beautiful headers with Agent Vibes branding on every page\n') +
696
+ chalk.gray(' 💡 Inline Help & Commands - TTS explanations and examples throughout installer\n') +
697
+ chalk.gray(' 🎵 New Music Track - Salsa v2 background music now available\n') +
698
+ chalk.gray(' 🎨 Professional UI - Consistent sunshine yellow styling and dynamic page numbering\n') +
699
+ chalk.gray(' 📋 Enhanced Navigation - Previous button, accurate page counts, README links\n') +
700
+ chalk.gray(' Quality Assurance - 140 tests passing, SonarCloud integration\n\n') +
701
+ chalk.gray('📖 Full Release Notes: RELEASE_NOTES.md\n') +
702
+ chalk.gray('🌐 Website: https://agentvibes.org\n') +
703
+ chalk.gray('📦 Repository: https://github.com/paulpreibisch/AgentVibes\n\n') +
704
+ chalk.gray('Co-created by Paul Preibisch with Claude AI\n') +
705
+ chalk.gray('Copyright © 2025 Paul Preibisch | Apache-2.0 License');
160
706
  }
161
707
 
162
708
  /**
@@ -206,28 +752,35 @@ async function playWelcomeDemo(targetDir, spinner, options = {}) {
206
752
  const mcpConfigPath = path.join(targetDir, '.mcp.json');
207
753
  const hasMcp = fsSync.existsSync(mcpConfigPath);
208
754
 
209
- // Build the welcome script
210
- let welcomeScript = `Welcome to Agent Vibes, the free software that enhances your developer experience and gives your agents a voice.
211
-
212
- We have added a lot of commands, but don't worry, you can hide them by typing /agent-vibes:hide, and :show to bring them back.`;
755
+ // Build the welcome script with colored commands
756
+ let welcomeScript = chalk.white('Welcome to Agent Vibes, the free software that enhances your developer experience and gives your agents a voice.\n\n');
757
+ welcomeScript += chalk.white('Now integrated with the B mad Method - Artificial Intelligence Driven Agile Development That Scales From Bug Fixes to Enterprise.\n\n');
758
+ welcomeScript += chalk.white('We have added a lot of commands, but don\'t worry, you can hide them by typing ');
759
+ welcomeScript += chalk.magentaBright('/agent-vibes:hide');
760
+ welcomeScript += chalk.white(', and ');
761
+ welcomeScript += chalk.magentaBright('/agent-vibes:show');
762
+ welcomeScript += chalk.white(' to bring them back.');
213
763
 
214
764
  if (!hasMcp) {
215
- welcomeScript += `
216
-
217
- To control Agent Vibes with natural language, install the MCP server. That way you can just say things like, "change my voice" or "mute the audio."`;
765
+ welcomeScript += chalk.white('\n\nTo control Agent Vibes with natural language, install the MCP server. That way you can just say things like, ');
766
+ welcomeScript += chalk.magentaBright('"change my voice"');
767
+ welcomeScript += chalk.white(' or ');
768
+ welcomeScript += chalk.magentaBright('"mute the audio"');
769
+ welcomeScript += chalk.white('.');
218
770
  }
219
771
 
220
- welcomeScript += `
221
-
222
- To change my personality, just type, "change personality to sarcastic."
223
-
224
- Or to change my voice, type, "try a different voice."
225
-
226
- We recently have added background music to your agents. You can turn it on or off by saying "Turn background music on" or "Turn background music off."
227
-
228
- Lastly, Agent Vibes is updated frequently. Use npx agentvibes update to keep up to date.
229
-
230
- We hope you have fun with Agent Vibes. Please consider giving us a GitHub star. Thank you.`;
772
+ welcomeScript += chalk.white('\n\nTo change my personality, just type, ');
773
+ welcomeScript += chalk.magentaBright('"change personality to sarcastic."');
774
+ welcomeScript += chalk.white('\n\nOr to change my voice, type, ');
775
+ welcomeScript += chalk.magentaBright('"try a different voice."');
776
+ welcomeScript += chalk.white('\n\nWe recently have added background music to your agents. You can turn it on or off by saying ');
777
+ welcomeScript += chalk.magentaBright('"Turn background music on"');
778
+ welcomeScript += chalk.white(' or ');
779
+ welcomeScript += chalk.magentaBright('"Turn background music off."');
780
+ welcomeScript += chalk.yellow('\n\n⭐ Please consider giving us a GitHub star! ') + chalk.yellow('https://github.com/paulpreibisch/agentvibes');
781
+ welcomeScript += chalk.white('\n\nLastly, Agent Vibes is updated frequently. Use ');
782
+ welcomeScript += chalk.magentaBright('npx agentvibes update');
783
+ welcomeScript += chalk.white(' to keep up to date.\n\nWe hope you have fun with Agent Vibes. Thank you!');
231
784
 
232
785
  // Stop spinner and display the welcome script in a box
233
786
  spinner.stop();
@@ -243,14 +796,14 @@ We hope you have fun with Agent Vibes. Please consider giving us a GitHub star.
243
796
  console.log(chalk.cyan('🎵 Playing welcome demo in background...\n'));
244
797
 
245
798
  try {
246
- // Play the audio in the background (non-blocking)
799
+ // Play the audio in the background (non-blocking) with reduced volume
247
800
  let args;
248
801
  if (audioPlayer === 'mpv') {
249
- args = ['--no-video', '--really-quiet', welcomeDemoAudio];
802
+ args = ['--no-video', '--really-quiet', '--volume=40', welcomeDemoAudio];
250
803
  } else if (audioPlayer === 'paplay') {
251
- args = [welcomeDemoAudio];
804
+ args = ['--volume=32768', welcomeDemoAudio]; // 50% volume (max is 65536)
252
805
  } else {
253
- args = [welcomeDemoAudio]; // afplay
806
+ args = ['--volume=0.4', welcomeDemoAudio]; // afplay - 40% volume
254
807
  }
255
808
 
256
809
  const audioProcess = spawn(audioPlayer, args, {
@@ -444,23 +997,41 @@ async function promptProviderSelection(options) {
444
997
  return 'piper';
445
998
  }
446
999
 
447
- console.log(chalk.cyan('🎭 Choose Your TTS Provider:\n'));
1000
+ // Auto-select if only one provider available
1001
+ if (!isMacOS) {
1002
+ // On Linux/WSL, only Piper is available - auto-select it
1003
+ console.log(boxen(
1004
+ chalk.bold('🎤 TTS Provider\n\n') +
1005
+ chalk.green('✓ Piper TTS (Free, Offline)\n') +
1006
+ chalk.gray(' 50+ Hugging Face AI voices\n') +
1007
+ chalk.gray(' Human-like speech quality\n') +
1008
+ chalk.gray(' No API key required'),
1009
+ {
1010
+ padding: 1,
1011
+ margin: 1,
1012
+ borderStyle: 'round',
1013
+ borderColor: 'green',
1014
+ title: chalk.bold('TTS Provider Auto-detected'),
1015
+ titleAlignment: 'center'
1016
+ }
1017
+ ));
1018
+ console.log('');
1019
+ return 'piper';
1020
+ }
448
1021
 
449
- // Build choices based on platform - macOS Say first on macOS
450
- const choices = [];
1022
+ // On macOS, both providers available - prompt user
1023
+ console.log(chalk.cyan('🎭 Choose Your TTS Provider:\n'));
451
1024
 
452
- if (isMacOS) {
453
- // On macOS, put macOS Say first as the recommended default
454
- choices.push({
1025
+ const choices = [
1026
+ {
455
1027
  name: chalk.yellow('🍎 macOS Say (Recommended)') + chalk.gray(' - Built-in, zero setup required'),
456
1028
  value: 'macos',
457
- });
458
- }
459
-
460
- choices.push({
461
- name: chalk.green('🆓 Piper TTS (Free, Offline)') + chalk.gray(' - 50+ Hugging Face AI voices, human-like speech'),
462
- value: 'piper',
463
- });
1029
+ },
1030
+ {
1031
+ name: chalk.green('🆓 Piper TTS (Free, Offline)') + chalk.gray(' - 50+ Hugging Face AI voices, human-like speech'),
1032
+ value: 'piper',
1033
+ }
1034
+ ];
464
1035
 
465
1036
  const { provider } = await inquirer.prompt([
466
1037
  {
@@ -468,7 +1039,7 @@ async function promptProviderSelection(options) {
468
1039
  name: 'provider',
469
1040
  message: 'Which TTS provider would you like to use?',
470
1041
  choices,
471
- default: isMacOS ? 'macos' : 'piper',
1042
+ default: 'macos',
472
1043
  },
473
1044
  ]);
474
1045
 
@@ -496,6 +1067,34 @@ async function checkExistingPiperVoices(voicesPath) {
496
1067
  return { installed: false, voices: [] };
497
1068
  }
498
1069
 
1070
+ /**
1071
+ * Prompt user to select default reverb level for TTS
1072
+ * @returns {Promise<string>} Selected reverb level
1073
+ */
1074
+ async function promptReverbSelection() {
1075
+ console.log(chalk.cyan('\n🎛️ Audio Effects Configuration:\n'));
1076
+ console.log(chalk.white(' Choose a default reverb level for TTS audio'));
1077
+ console.log(chalk.gray(' (Adds room/space ambiance to make voices sound more natural)\n'));
1078
+
1079
+ const { reverbLevel } = await inquirer.prompt([
1080
+ {
1081
+ type: 'list',
1082
+ name: 'reverbLevel',
1083
+ message: 'Select default reverb level:',
1084
+ choices: [
1085
+ { name: 'Off (Dry, no reverb)', value: 'off' },
1086
+ { name: 'Light (Small room) - Recommended', value: 'light' },
1087
+ { name: 'Medium (Conference room)', value: 'medium' },
1088
+ { name: 'Heavy (Large hall)', value: 'heavy' },
1089
+ { name: 'Cathedral (Epic space)', value: 'cathedral' },
1090
+ ],
1091
+ default: 'light',
1092
+ },
1093
+ ]);
1094
+
1095
+ return reverbLevel;
1096
+ }
1097
+
499
1098
  /**
500
1099
  * Handle Piper TTS configuration (voice storage location)
501
1100
  * Detects existing voice installations and skips download prompt if voices already exist
@@ -509,19 +1108,7 @@ async function handlePiperConfiguration() {
509
1108
  const existingVoices = await checkExistingPiperVoices(defaultPiperPath);
510
1109
 
511
1110
  if (existingVoices.installed) {
512
- console.log(chalk.green(`\n✓ Piper voices already installed at ${defaultPiperPath}`));
513
- console.log(chalk.gray(` Found ${existingVoices.voices.length} voice model(s):`));
514
-
515
- // Show first 5 voices, then indicate more if applicable
516
- const displayVoices = existingVoices.voices.slice(0, 5);
517
- displayVoices.forEach(voice => {
518
- console.log(chalk.gray(` • ${voice}`));
519
- });
520
- if (existingVoices.voices.length > 5) {
521
- console.log(chalk.gray(` ... and ${existingVoices.voices.length - 5} more`));
522
- }
523
-
524
- console.log(chalk.green('\n✓ Skipping download - using existing voices\n'));
1111
+ // Voices already installed - will be shown in Installation Summary boxen
525
1112
  return defaultPiperPath;
526
1113
  }
527
1114
 
@@ -553,7 +1140,7 @@ async function handlePiperConfiguration() {
553
1140
  * Copy command files to target directory
554
1141
  * @param {string} targetDir - Target installation directory
555
1142
  * @param {Object} spinner - Ora spinner instance
556
- * @returns {Promise<number>} Number of files copied
1143
+ * @returns {Promise<{count: number, boxen: string}>} Number of files copied and boxen content
557
1144
  */
558
1145
  async function copyCommandFiles(targetDir, spinner) {
559
1146
  spinner.start('Installing /agent-vibes slash commands...');
@@ -565,28 +1152,66 @@ async function copyCommandFiles(targetDir, spinner) {
565
1152
  await fs.mkdir(agentVibesCommandsDir, { recursive: true });
566
1153
 
567
1154
  const commandFiles = await fs.readdir(srcCommandsDir);
568
- console.log(chalk.cyan(`\n📋 Installing ${commandFiles.length} command files:`));
569
1155
 
1156
+ let installedCommands = [];
1157
+ let failedCommands = [];
570
1158
  let successCount = 0;
1159
+
571
1160
  for (const file of commandFiles) {
572
1161
  const srcPath = path.join(srcCommandsDir, file);
573
1162
  const destPath = path.join(agentVibesCommandsDir, file);
574
1163
  try {
575
1164
  await fs.copyFile(srcPath, destPath);
576
- console.log(chalk.gray(` ✓ agent-vibes/${file}`));
1165
+ installedCommands.push(file);
577
1166
  successCount++;
578
1167
  } catch (err) {
579
- console.log(chalk.yellow(` ⚠ Failed to copy ${file}: ${err.message}`));
1168
+ failedCommands.push({ file, error: err.message });
580
1169
  // Continue with other files
581
1170
  }
582
1171
  }
583
1172
 
584
1173
  if (successCount === commandFiles.length) {
585
- spinner.succeed(chalk.green('Installed /agent-vibes commands!\n'));
1174
+ spinner.succeed(chalk.green(`Installed ${successCount} slash commands!\n`));
586
1175
  } else {
587
1176
  spinner.warn(chalk.yellow(`Installed ${successCount}/${commandFiles.length} commands (some failed)\n`));
588
1177
  }
589
- return successCount;
1178
+
1179
+ // Create boxen content (don't print yet - will be shown in pagination)
1180
+ let content = chalk.bold(`${installedCommands.length} Slash Commands Installed\n\n`);
1181
+ content += chalk.gray('Slash commands are shortcuts you type in chat to trigger actions.\n');
1182
+ content += chalk.gray('Type them with a forward slash like: /agent-vibes:list\n\n');
1183
+
1184
+ installedCommands.forEach(file => {
1185
+ // Remove .md extension and format command name
1186
+ const commandName = file.replace('.md', '');
1187
+
1188
+ // Highlight show and hide commands in cyan
1189
+ if (commandName === 'agent-vibes:show' || commandName === 'agent-vibes:hide') {
1190
+ content += chalk.green('✓ ') + chalk.cyan(`/${commandName}\n`);
1191
+ } else {
1192
+ content += chalk.green(`✓ `) + chalk.yellow(`/${commandName}\n`);
1193
+ }
1194
+ });
1195
+
1196
+ // Add failures if any
1197
+ if (failedCommands.length > 0) {
1198
+ content += '\n' + chalk.gray('─'.repeat(60)) + '\n\n';
1199
+ content += chalk.yellow('⚠ Failed Commands\n\n');
1200
+ failedCommands.forEach(({ file, error }) => {
1201
+ content += chalk.gray(`✗ ${file}: ${error}\n`);
1202
+ });
1203
+ }
1204
+
1205
+ const boxenContent = boxen(content.trim(), {
1206
+ padding: 1,
1207
+ margin: 1,
1208
+ borderStyle: 'round',
1209
+ borderColor: successCount === commandFiles.length ? 'cyan' : 'yellow',
1210
+ title: chalk.bold('📋 Slash Commands'),
1211
+ titleAlignment: 'center'
1212
+ });
1213
+
1214
+ return { count: successCount, boxen: boxenContent };
590
1215
  } catch (err) {
591
1216
  spinner.fail(chalk.red(`Failed to install commands: ${err.message}`));
592
1217
  throw err;
@@ -597,7 +1222,7 @@ async function copyCommandFiles(targetDir, spinner) {
597
1222
  * Copy hook files to target directory
598
1223
  * @param {string} targetDir - Target installation directory
599
1224
  * @param {Object} spinner - Ora spinner instance
600
- * @returns {Promise<number>} Number of files copied
1225
+ * @returns {Promise<{count: number, boxen: string|null}>} Number of files copied and boxen content
601
1226
  */
602
1227
  async function copyHookFiles(targetDir, spinner) {
603
1228
  spinner.start('Installing TTS helper scripts...');
@@ -627,8 +1252,11 @@ async function copyHookFiles(targetDir, spinner) {
627
1252
  }
628
1253
  }
629
1254
 
630
- console.log(chalk.cyan(`🔧 Installing ${hookFiles.length} TTS scripts:`));
1255
+ spinner.start(`Installing ${hookFiles.length} TTS scripts...`);
631
1256
  let successCount = 0;
1257
+ let installedFiles = [];
1258
+ let failedFiles = [];
1259
+
632
1260
  for (const file of hookFiles) {
633
1261
  const srcPath = path.join(srcHooksDir, file);
634
1262
  const destPath = path.join(hooksDir, file);
@@ -638,14 +1266,13 @@ async function copyHookFiles(targetDir, spinner) {
638
1266
  if (file.endsWith('.sh')) {
639
1267
  // Security: Use more restrictive permissions (owner: rwx, group: r-x, others: ---)
640
1268
  await fs.chmod(destPath, 0o750);
641
- console.log(chalk.gray(` ✓ ${file} (executable)`));
1269
+ installedFiles.push({ name: file, executable: true });
642
1270
  } else {
643
- console.log(chalk.gray(` ✓ ${file}`));
1271
+ installedFiles.push({ name: file, executable: false });
644
1272
  }
645
1273
  successCount++;
646
1274
  } catch (err) {
647
- console.log(chalk.yellow(` ⚠ Failed to copy ${file}: ${err.message}`));
648
- // Continue with other files
1275
+ failedFiles.push({ name: file, error: err.message });
649
1276
  }
650
1277
  }
651
1278
 
@@ -654,7 +1281,41 @@ async function copyHookFiles(targetDir, spinner) {
654
1281
  } else {
655
1282
  spinner.warn(chalk.yellow(`Installed ${successCount}/${hookFiles.length} scripts (some failed)\n`));
656
1283
  }
657
- return successCount;
1284
+
1285
+ // Create boxen content (don't print yet - will be shown in pagination)
1286
+ let boxenContent = null;
1287
+ if (installedFiles.length > 0) {
1288
+ let content = chalk.bold(`${installedFiles.length} TTS Hook Scripts Installed\n\n`);
1289
+ content += chalk.gray('Hook scripts automatically run at key moments during your\n');
1290
+ content += chalk.gray('Claude Code sessions to provide TTS feedback and manage audio.\n\n');
1291
+ installedFiles.forEach(file => {
1292
+ content += chalk.green(`✓ ${file.name}`);
1293
+ if (file.executable) {
1294
+ content += chalk.gray(' (executable)');
1295
+ }
1296
+ content += '\n';
1297
+ });
1298
+
1299
+ if (failedFiles.length > 0) {
1300
+ content += '\n' + chalk.gray('─'.repeat(60)) + '\n\n';
1301
+ content += chalk.bold.yellow(`${failedFiles.length} Failed\n\n`);
1302
+ failedFiles.forEach(file => {
1303
+ content += chalk.yellow(`⚠ ${file.name}\n`);
1304
+ content += chalk.dim(` ${file.error}\n`);
1305
+ });
1306
+ }
1307
+
1308
+ boxenContent = boxen(content.trim(), {
1309
+ padding: 1,
1310
+ margin: 1,
1311
+ borderStyle: 'round',
1312
+ borderColor: 'green',
1313
+ title: chalk.bold('🔧 TTS Scripts'),
1314
+ titleAlignment: 'center'
1315
+ });
1316
+ }
1317
+
1318
+ return { count: successCount, boxen: boxenContent };
658
1319
  } catch (err) {
659
1320
  spinner.fail(chalk.red(`Failed to install hook scripts: ${err.message}`));
660
1321
  throw err;
@@ -665,7 +1326,7 @@ async function copyHookFiles(targetDir, spinner) {
665
1326
  * Copy personality files to target directory
666
1327
  * @param {string} targetDir - Target installation directory
667
1328
  * @param {Object} spinner - Ora spinner instance
668
- * @returns {Promise<number>} Number of files copied
1329
+ * @returns {Promise<{count: number, boxen: string|null}>} Number of files copied and boxen content
669
1330
  */
670
1331
  async function copyPersonalityFiles(targetDir, spinner) {
671
1332
  spinner.start('Installing personality templates...');
@@ -686,16 +1347,66 @@ async function copyPersonalityFiles(targetDir, spinner) {
686
1347
  }
687
1348
  }
688
1349
 
689
- console.log(chalk.cyan(`🎭 Installing ${personalityMdFiles.length} personality templates:`));
1350
+ spinner.start(`Installing ${personalityMdFiles.length} personality templates...`);
1351
+ let installedPersonalities = [];
1352
+
690
1353
  for (const file of personalityMdFiles) {
691
1354
  const srcPath = path.join(srcPersonalitiesDir, file);
692
1355
  const destPath = path.join(destPersonalitiesDir, file);
693
1356
  await fs.copyFile(srcPath, destPath);
694
- console.log(chalk.gray(` ✓ ${file}`));
1357
+ installedPersonalities.push(file);
695
1358
  }
696
1359
 
697
1360
  spinner.succeed(chalk.green('Installed personality templates!\n'));
698
- return personalityMdFiles.length;
1361
+
1362
+ // Create boxen content (don't print yet - will be shown in pagination)
1363
+ let boxenContent = null;
1364
+ if (installedPersonalities.length > 0) {
1365
+ let content = chalk.bold(`${installedPersonalities.length} Personality Templates Installed\n\n`);
1366
+ content += chalk.gray('Personalities change how Claude speaks - adding humor, emotion, or style.\n');
1367
+ content += chalk.gray('Change with: ') + chalk.yellow('/agent-vibes:personality <name>') + chalk.gray(' or say "change personality to sassy"\n\n');
1368
+
1369
+ // Map personalities to emojis
1370
+ const personalityEmojis = {
1371
+ 'angry': '😠',
1372
+ 'annoying': '😤',
1373
+ 'crass': '🤬',
1374
+ 'dramatic': '🎭',
1375
+ 'dry-humor': '😐',
1376
+ 'flirty': '😘',
1377
+ 'funny': '😂',
1378
+ 'grandpa': '👴',
1379
+ 'millennial': '🙄',
1380
+ 'moody': '😒',
1381
+ 'normal': '😊',
1382
+ 'pirate': '🏴‍☠️',
1383
+ 'poetic': '📜',
1384
+ 'professional': '👔',
1385
+ 'rapper': '🎤',
1386
+ 'robot': '🤖',
1387
+ 'sarcastic': '😏',
1388
+ 'sassy': '💁',
1389
+ 'surfer-dude': '🏄',
1390
+ 'zen': '🧘'
1391
+ };
1392
+
1393
+ installedPersonalities.forEach(file => {
1394
+ const name = file.replace('.md', '');
1395
+ const emoji = personalityEmojis[name] || '✨';
1396
+ content += chalk.green(`✓ ${emoji} ${name}\n`);
1397
+ });
1398
+
1399
+ boxenContent = boxen(content.trim(), {
1400
+ padding: 1,
1401
+ margin: 1,
1402
+ borderStyle: 'round',
1403
+ borderColor: 'magenta',
1404
+ title: chalk.bold('🎭 Personalities'),
1405
+ titleAlignment: 'center'
1406
+ });
1407
+ }
1408
+
1409
+ return { count: personalityMdFiles.length, boxen: boxenContent };
699
1410
  }
700
1411
 
701
1412
  // Output styles removed - deprecated in favor of SessionStart hook system
@@ -772,7 +1483,7 @@ async function copyBmadConfigFiles(targetDir, spinner) {
772
1483
  * Copy background music files to target directory
773
1484
  * @param {string} targetDir - Target installation directory
774
1485
  * @param {Object} spinner - Ora spinner instance
775
- * @returns {Promise<number>} Number of files copied
1486
+ * @returns {Promise<{count: number, boxen: string}>} Number of files copied and boxen content
776
1487
  */
777
1488
  async function copyBackgroundMusicFiles(targetDir, spinner) {
778
1489
  spinner.start('Installing background music tracks...');
@@ -805,12 +1516,6 @@ async function copyBackgroundMusicFiles(targetDir, spinner) {
805
1516
 
806
1517
  if (musicFiles.length > 0) {
807
1518
  spinner.succeed(chalk.green(`Installed ${musicFiles.length} background music track${musicFiles.length === 1 ? '' : 's'}!\n`));
808
- console.log(chalk.green(` • ${musicFiles.length} background music track${musicFiles.length === 1 ? '' : 's'} installed:`));
809
- musicFiles.forEach(track => {
810
- console.log(chalk.green(` ✓ ${track.name} (${track.size})`));
811
- console.log(chalk.gray(` ${track.path}`));
812
- });
813
- console.log(''); // Add blank line for spacing
814
1519
  } else {
815
1520
  spinner.info(chalk.yellow('No background music files found (optional)\n'));
816
1521
  }
@@ -818,7 +1523,42 @@ async function copyBackgroundMusicFiles(targetDir, spinner) {
818
1523
  spinner.info(chalk.yellow('No background music files found (optional)\n'));
819
1524
  }
820
1525
 
821
- return musicFiles.length;
1526
+ // Create boxen content (don't print yet - will be shown in pagination)
1527
+ if (musicFiles.length > 0) {
1528
+ let content = chalk.bold(`${musicFiles.length} Background Music Tracks Installed\n\n`);
1529
+
1530
+ content += chalk.cyan('Agents need to have fun too! 🎉 Spice things up with background music.\n\n');
1531
+
1532
+ content += chalk.white('💡 How to control background music:\n\n');
1533
+ content += chalk.cyan(' Slash Commands:\n');
1534
+ content += chalk.gray(' /agent-vibes:background-music on - Enable music\n');
1535
+ content += chalk.gray(' /agent-vibes:background-music off - Disable music\n');
1536
+ content += chalk.gray(' /agent-vibes:background-music set chillwave - Change track\n\n');
1537
+ content += chalk.cyan(' MCP Natural Language:\n');
1538
+ content += chalk.gray(' "turn on background music"\n');
1539
+ content += chalk.gray(' "change background music to chillwave"\n');
1540
+ content += chalk.gray(' "disable background music"\n\n');
1541
+
1542
+ content += chalk.gray('─'.repeat(70)) + '\n\n';
1543
+
1544
+ musicFiles.forEach(track => {
1545
+ content += chalk.green(`✓ ${track.name}`) + chalk.gray(` (${track.size})\n`);
1546
+ content += chalk.dim(` ${track.path}\n`);
1547
+ });
1548
+
1549
+ const boxenContent = boxen(content.trim(), {
1550
+ padding: 1,
1551
+ margin: 1,
1552
+ borderStyle: 'round',
1553
+ borderColor: 'green',
1554
+ title: chalk.bold('🎵 Background Music'),
1555
+ titleAlignment: 'center'
1556
+ });
1557
+
1558
+ return { count: musicFiles.length, boxen: boxenContent };
1559
+ }
1560
+
1561
+ return { count: 0, boxen: null };
822
1562
  }
823
1563
 
824
1564
  /**
@@ -1590,8 +2330,8 @@ async function updateAgentVibes(targetDir, options) {
1590
2330
 
1591
2331
  // Update hooks
1592
2332
  spinner.text = 'Updating TTS scripts...';
1593
- const hookFileCount = await copyHookFiles(targetDir, { start: () => {}, succeed: () => {}, info: () => {}, fail: () => {} });
1594
- console.log(chalk.green(`✓ Updated ${hookFileCount} TTS scripts`));
2333
+ const hookResult = await copyHookFiles(targetDir, { start: () => {}, succeed: () => {}, info: () => {}, fail: () => {} });
2334
+ console.log(chalk.green(`✓ Updated ${hookResult.count} TTS scripts`));
1595
2335
 
1596
2336
  // Update personalities
1597
2337
  spinner.text = 'Updating personality templates...';
@@ -1614,9 +2354,9 @@ async function updateAgentVibes(targetDir, options) {
1614
2354
  }
1615
2355
 
1616
2356
  // Update background music files
1617
- const backgroundMusicFileCount = await copyBackgroundMusicFiles(targetDir, { start: () => {}, succeed: () => {}, info: () => {}, fail: () => {} });
1618
- if (backgroundMusicFileCount > 0) {
1619
- console.log(chalk.green(`✓ Installed ${backgroundMusicFileCount} background music track${backgroundMusicFileCount === 1 ? '' : 's'}`));
2357
+ const backgroundMusicUpdateResult = await copyBackgroundMusicFiles(targetDir, { start: () => {}, succeed: () => {}, info: () => {}, fail: () => {} });
2358
+ if (backgroundMusicUpdateResult.count > 0) {
2359
+ console.log(chalk.green(`✓ Installed ${backgroundMusicUpdateResult.count} background music track${backgroundMusicUpdateResult.count === 1 ? '' : 's'}`));
1620
2360
  }
1621
2361
 
1622
2362
  // Update config files
@@ -1637,7 +2377,7 @@ async function updateAgentVibes(targetDir, options) {
1637
2377
 
1638
2378
  console.log(chalk.cyan('📦 Update Summary:'));
1639
2379
  console.log(chalk.white(` • ${commandFiles.length} commands updated`));
1640
- console.log(chalk.white(` • ${hookFileCount} TTS scripts updated`));
2380
+ console.log(chalk.white(` • ${hookResult.count} TTS scripts updated`));
1641
2381
  console.log(chalk.white(` • ${personalityResult.new + personalityResult.updated} personality templates (${personalityResult.new} new, ${personalityResult.updated} updated)`));
1642
2382
  if (pluginFileCount > 0) {
1643
2383
  console.log(chalk.white(` • ${pluginFileCount} BMAD plugin files updated`));
@@ -1659,79 +2399,181 @@ async function updateAgentVibes(targetDir, options) {
1659
2399
 
1660
2400
  // Installation function
1661
2401
  async function install(options = {}) {
1662
- showWelcome();
1663
-
1664
2402
  const currentDir = process.env.INIT_CWD || process.cwd();
1665
2403
 
1666
- console.log(chalk.cyan('\n📍 Installation Details:'));
1667
- console.log(chalk.gray(` Install location: ${currentDir}/.claude/`));
1668
- console.log(chalk.gray(` Package version: ${VERSION}`));
2404
+ // Global pagination constants (used throughout install flow)
2405
+ const configPages = 4; // System Dependencies + Provider + Audio + Verbosity
2406
+ const configOffset = 0;
2407
+
2408
+ // Loop to allow going back to welcome screen
2409
+ let userConfig = null;
2410
+ while (!userConfig) {
2411
+ showWelcome();
2412
+
2413
+ // Show release notes and recent changes after welcome banner
2414
+ console.log(getReleaseInfoBoxen());
2415
+ console.log('');
2416
+ await showRecentChanges(path.join(__dirname, '..'));
1669
2417
 
1670
- showReleaseInfo();
2418
+ console.log(chalk.cyan('\n📍 Installation Details:'));
2419
+ console.log(chalk.gray(` Install location: ${currentDir}/.claude/`));
2420
+ console.log(chalk.yellow(` Package version: ${VERSION}`));
1671
2421
 
1672
- // Provider selection
1673
- let selectedProvider = await promptProviderSelection(options);
1674
- let piperVoicesPath = null;
2422
+ // Prompt to continue (gives user time to read welcome banner)
2423
+ if (!options.yes) {
2424
+ console.log(''); // Add spacing before prompt
1675
2425
 
1676
- if (options.yes) {
1677
- console.log(chalk.green('✓ Auto-confirmed (--yes flag)'));
2426
+ const { continueToConfig } = await inquirer.prompt([{
2427
+ type: 'confirm',
2428
+ name: 'continueToConfig',
2429
+ message: chalk.yellow('Ready to configure AgentVibes?'),
2430
+ default: true
2431
+ }]);
2432
+
2433
+ if (!continueToConfig) {
2434
+ console.log(chalk.yellow('\n✋ Installation cancelled.'));
2435
+ process.exit(0);
2436
+ }
2437
+ }
2438
+
2439
+ // Collect configuration through paginated flow (totalPages will be updated later)
2440
+ // Returns null if user wants to go back to welcome
2441
+ userConfig = await collectConfiguration({
2442
+ ...options,
2443
+ pageOffset: configOffset,
2444
+ totalPages: configPages // Temporary, will show correct count later
2445
+ });
1678
2446
  }
1679
2447
 
1680
- // Handle provider-specific configuration
1681
- if (selectedProvider === 'piper' && !options.yes) {
1682
- piperVoicesPath = await handlePiperConfiguration();
2448
+ const selectedProvider = userConfig.provider;
2449
+ const piperVoicesPath = userConfig.piperPath;
2450
+ const targetDir = options.directory || currentDir;
2451
+
2452
+ // Collect pre-install information pages
2453
+ const preInstallPages = [];
2454
+
2455
+ // Page 1: Configuration Summary
2456
+ const providerLabels = { piper: 'Piper TTS', macos: 'macOS Say' };
2457
+ const reverbLabels = {
2458
+ off: 'Off',
2459
+ light: 'Light',
2460
+ medium: 'Medium',
2461
+ heavy: 'Heavy',
2462
+ cathedral: 'Cathedral'
2463
+ };
2464
+ const verbosityLabels = {
2465
+ high: 'High',
2466
+ medium: 'Medium',
2467
+ low: 'Low'
2468
+ };
2469
+
2470
+ let configContent = chalk.bold('Your Configuration\n\n');
2471
+ configContent += chalk.cyan('🎤 TTS Provider:\n');
2472
+ configContent += chalk.white(` ${providerLabels[selectedProvider]}\n`);
2473
+ if (selectedProvider === 'piper' && piperVoicesPath) {
2474
+ configContent += chalk.gray(` Voice storage: ${piperVoicesPath}\n`);
1683
2475
  }
2476
+ configContent += '\n';
2477
+ configContent += chalk.cyan('🎛️ Audio Settings:\n');
2478
+ configContent += chalk.white(` Reverb: ${reverbLabels[userConfig.reverb]}\n`);
2479
+ configContent += chalk.white(` Background Music: ${userConfig.backgroundMusic.enabled ? 'Enabled' : 'Disabled'}\n`);
2480
+ if (userConfig.backgroundMusic.enabled) {
2481
+ // Find the track name from the track choices
2482
+ const trackChoices = {
2483
+ 'agentvibes_soft_flamenco_loop.mp3': 'Soft Flamenco',
2484
+ 'agent_vibes_bachata_v1_loop.mp3': 'Bachata',
2485
+ 'agent_vibes_salsa_v2_loop.mp3': 'Salsa',
2486
+ 'agent_vibes_cumbia_v1_loop.mp3': 'Cumbia',
2487
+ 'agent_vibes_bossa_nova_v2_loop.mp3': 'Bossa Nova',
2488
+ 'agent_vibes_japanese_city_pop_v1_loop.mp3': 'Japanese City Pop',
2489
+ 'agent_vibes_chillwave_v2_loop.mp3': 'Chillwave',
2490
+ 'dreamy_house_loop.mp3': 'Dreamy House',
2491
+ 'agent_vibes_dark_chill_step_loop.mp3': 'Dark Chill Step',
2492
+ 'agent_vibes_goa_trance_v2_loop.mp3': 'Goa Trance',
2493
+ 'agent_vibes_harpsichord_v2_loop.mp3': 'Harpsichord',
2494
+ 'agent_vibes_celtic_harp_v1_loop.mp3': 'Celtic Harp',
2495
+ 'agent_vibes_hawaiian_slack_key_guitar_v2_loop.mp3': 'Hawaiian Slack Key Guitar',
2496
+ 'agent_vibes_arabic_v2_loop.mp3': 'Arabic Oud',
2497
+ 'agent_vibes_ganawa_ambient_v2_loop.mp3': 'Gnawa Ambient',
2498
+ 'agent_vibes_tabla_dream_pop_v1_loop.mp3': 'Tabla Dream Pop'
2499
+ };
2500
+ const trackName = trackChoices[userConfig.backgroundMusic.track] || userConfig.backgroundMusic.track;
2501
+ configContent += chalk.gray(` Default track: ${trackName}\n`);
2502
+ }
2503
+ configContent += '\n';
2504
+ configContent += chalk.cyan('🔊 Verbosity:\n');
2505
+ configContent += chalk.white(` ${verbosityLabels[userConfig.verbosity]}\n`);
1684
2506
 
2507
+ const configBoxen = boxen(configContent.trim(), {
2508
+ padding: 1,
2509
+ margin: 1,
2510
+ borderStyle: 'round',
2511
+ borderColor: 'green',
2512
+ title: chalk.bold('⚙️ Configuration Summary'),
2513
+ titleAlignment: 'center'
2514
+ });
2515
+ preInstallPages.push({
2516
+ title: 'Configuration Summary',
2517
+ content: configBoxen
2518
+ });
2519
+
2520
+ // Show pre-install info pages with pagination
1685
2521
  if (!options.yes) {
1686
- console.log(''); // Spacing
1687
- }
2522
+ console.log(chalk.cyan('\n📖 Installation Preview\n'));
2523
+ const preInstallOffset = 4; // After 4 config pages (System Dependencies + Provider + Audio + Verbosity)
2524
+ // For pre-install, estimate post-install pages (will be exact in post-install)
2525
+ const estimatedPostInstall = 7; // Typical: 5 summaries + 1 BMAD/recommendation + 1 complete
2526
+ const estimatedTotal = configPages + preInstallPages.length + estimatedPostInstall;
2527
+
2528
+ const result = await showPaginatedContent(preInstallPages, {
2529
+ ...options,
2530
+ continueLabel: '✓ Start Installation',
2531
+ pageOffset: preInstallOffset,
2532
+ totalPages: estimatedTotal,
2533
+ showPreviousOnFirst: true
2534
+ });
1688
2535
 
1689
- const targetDir = options.directory || currentDir;
2536
+ // If user went back from first pre-install page, restart configuration
2537
+ if (result === 'prev') {
2538
+ console.log(chalk.yellow('\n↩️ Returning to configuration...\n'));
2539
+ return install(options); // Restart the install function
2540
+ }
2541
+ }
1690
2542
 
1691
- // Confirm installation location
2543
+ // Ask if user wants to hear welcome message
1692
2544
  if (!options.yes) {
1693
- console.log(chalk.cyan('\n📂 Installation Location:\n'));
1694
- console.log(chalk.white(' AgentVibes will be installed in:'));
1695
- console.log(chalk.yellow(` ${targetDir}/.claude/\n`));
1696
- console.log(chalk.gray(' Why .claude/?'));
1697
- console.log(chalk.gray(' • Claude Code automatically discovers tools in .claude/ directories'));
1698
- console.log(chalk.gray(' • This makes slash commands and TTS features immediately available'));
1699
- console.log(chalk.gray(' • Project-specific installation keeps your setup isolated\n'));
1700
-
1701
- const { confirmLocation } = await inquirer.prompt([
2545
+ // Show header for this confirmation screen
2546
+ console.clear();
2547
+ const currentPageNum = 3 + preInstallPages.length;
2548
+ const { header } = createPageHeaderFooter('Installation Confirmation', 0, 15, currentPageNum);
2549
+ console.log(header);
2550
+ console.log('');
2551
+
2552
+ console.log(chalk.gray('Play audio welcome message from Paul, creator of AgentVibes.\n'));
2553
+
2554
+ const { playWelcome } = await inquirer.prompt([
1702
2555
  {
1703
2556
  type: 'confirm',
1704
- name: 'confirmLocation',
1705
- message: `Install AgentVibes in ${targetDir}/.claude/ ?`,
1706
- default: true,
2557
+ name: 'playWelcome',
2558
+ message: chalk.yellow('🎵 Listen to Welcome Message?'),
2559
+ default: false,
1707
2560
  },
1708
2561
  ]);
1709
2562
 
1710
- if (!confirmLocation) {
1711
- console.log(chalk.red('\n❌ Installation cancelled.\n'));
1712
- process.exit(0);
2563
+ if (playWelcome) {
2564
+ const spinner = ora('Playing welcome message...').start();
2565
+ await playWelcomeDemo(targetDir, spinner, options);
2566
+ spinner.succeed(chalk.green('Welcome message complete!'));
1713
2567
  }
1714
2568
  }
1715
2569
 
1716
- console.log(chalk.cyan('\n📦 What will be installed:'));
1717
- console.log(chalk.gray(` • 16 slash commands → ${targetDir}/.claude/commands/agent-vibes/`));
1718
- console.log(chalk.gray(` • Multi-provider TTS system (Piper TTS + macOS Say) → ${targetDir}/.claude/hooks/`));
1719
- console.log(chalk.gray(` • 19 personality templates → ${targetDir}/.claude/personalities/`));
1720
- console.log(chalk.gray(` • SessionStart hook for automatic TTS activation`));
1721
- console.log(chalk.gray(` • 50+ neural voices (Piper TTS - free & offline)`));
1722
- console.log(chalk.gray(` • 30+ language support with native voices`));
1723
- console.log(chalk.gray(` • BMAD integration for multi-agent sessions\n`));
1724
-
1725
- // Provider labels for display
1726
- const providerLabels = { piper: 'Piper TTS', macos: 'macOS Say' };
1727
-
1728
- // Final confirmation
2570
+ // Final confirmation after previewing
1729
2571
  if (!options.yes) {
1730
2572
  const { confirm } = await inquirer.prompt([
1731
2573
  {
1732
2574
  type: 'confirm',
1733
2575
  name: 'confirm',
1734
- message: chalk.yellow(`Proceed with installation using ${providerLabels[selectedProvider] || selectedProvider}?`),
2576
+ message: chalk.yellow(`\n✅ Ready to install AgentVibes with ${providerLabels[selectedProvider]}?`),
1735
2577
  default: true,
1736
2578
  },
1737
2579
  ]);
@@ -1780,12 +2622,12 @@ async function install(options = {}) {
1780
2622
  }
1781
2623
 
1782
2624
  // Copy all files using helper functions
1783
- const commandFileCount = await copyCommandFiles(targetDir, spinner);
1784
- const hookFileCount = await copyHookFiles(targetDir, spinner);
1785
- const personalityFileCount = await copyPersonalityFiles(targetDir, spinner);
2625
+ const commandResult = await copyCommandFiles(targetDir, spinner);
2626
+ const hookResult = await copyHookFiles(targetDir, spinner);
2627
+ const personalityResult = await copyPersonalityFiles(targetDir, spinner);
1786
2628
  const pluginFileCount = await copyPluginFiles(targetDir, spinner);
1787
2629
  const bmadConfigFileCount = await copyBmadConfigFiles(targetDir, spinner);
1788
- const backgroundMusicFileCount = await copyBackgroundMusicFiles(targetDir, spinner);
2630
+ const backgroundMusicResult = await copyBackgroundMusicFiles(targetDir, spinner);
1789
2631
  const configFileCount = await copyConfigFiles(targetDir, spinner);
1790
2632
 
1791
2633
  // Configure hooks and manifests
@@ -1816,7 +2658,7 @@ async function install(options = {}) {
1816
2658
  }
1817
2659
  await fs.writeFile(voiceConfigPath, defaultVoice);
1818
2660
 
1819
- spinner.succeed(chalk.green(`Provider set to: ${providerLabels[selectedProvider] || selectedProvider}\n`));
2661
+ spinner.succeed();
1820
2662
 
1821
2663
  // Detect and migrate old configuration
1822
2664
  await detectAndMigrateOldConfig(targetDir, spinner);
@@ -1842,68 +2684,12 @@ async function install(options = {}) {
1842
2684
  await checkAndInstallPiper(targetDir, options);
1843
2685
  }
1844
2686
 
1845
- // Display installation summary
1846
- console.log(chalk.cyan('📦 Installation Summary:'));
1847
- console.log(chalk.white(` • ${commandFileCount} slash commands installed`));
1848
- console.log(chalk.white(` • ${hookFileCount} TTS scripts installed`));
1849
- console.log(chalk.white(` • ${personalityFileCount} personality templates installed`));
1850
- console.log(chalk.white(` • SessionStart hook configured for automatic TTS`));
1851
- if (pluginFileCount > 0) {
1852
- console.log(chalk.white(` • ${pluginFileCount} BMAD plugin files installed`));
1853
- }
1854
- if (bmadConfigFileCount > 0) {
1855
- console.log(chalk.white(` • ${bmadConfigFileCount} BMAD config files installed`));
1856
- }
1857
- if (backgroundMusicFileCount > 0) {
1858
- console.log(chalk.white(` • ${backgroundMusicFileCount} background music track${backgroundMusicFileCount === 1 ? '' : 's'} installed`));
1859
- }
1860
-
1861
- console.log(chalk.white(` • Voice manager ready`));
1862
-
1863
- // Configure background music (if tracks were installed)
1864
- if (backgroundMusicFileCount > 0 && !options.yes) {
1865
- console.log(''); // Blank line for spacing
1866
-
1867
- const enableBackgroundMusic = await inquirer.prompt([{
1868
- type: 'confirm',
1869
- name: 'enable',
1870
- message: 'Enable background music for TTS?',
1871
- default: true
1872
- }]);
1873
-
1874
- if (enableBackgroundMusic.enable) {
1875
- // Define track choices with user-friendly names
1876
- const trackChoices = [
1877
- { name: '🎻 Soft Flamenco (Spanish guitar)', value: 'agentvibes_soft_flamenco_loop.mp3' },
1878
- { name: '🎺 Bachata (Latin - Romantic guitar & bongos)', value: 'agent_vibes_bachata_v1_loop.mp3' },
1879
- { name: '💃 Salsa (Latin - Upbeat brass & percussion)', value: 'agent_vibes_salsa_v2_loop.mp3' },
1880
- { name: '🎸 Cumbia (Latin - Accordion & drums)', value: 'agent_vibes_cumbia_v1_loop.mp3' },
1881
- { name: '🌸 Bossa Nova (Brazilian jazz)', value: 'agent_vibes_bossa_nova_v2_loop.mp3' },
1882
- { name: '🏙️ Japanese City Pop (80s synth)', value: 'agent_vibes_japanese_city_pop_v1_loop.mp3' },
1883
- { name: '🌊 Chillwave (Electronic ambient)', value: 'agent_vibes_chillwave_v2_loop.mp3' },
1884
- { name: '🎹 Dreamy House (Electronic dance)', value: 'dreamy_house_loop.mp3' },
1885
- { name: '🌙 Dark Chill Step (Electronic bass)', value: 'agent_vibes_dark_chill_step_loop.mp3' },
1886
- { name: '🕉️ Goa Trance (Psychedelic electronic)', value: 'agent_vibes_goa_trance_v2_loop.mp3' },
1887
- { name: '🎼 Harpsichord (Baroque classical)', value: 'agent_vibes_harpsichord_v2_loop.mp3' },
1888
- { name: '🎻 Celtic Harp (Irish traditional)', value: 'agent_vibes_celtic_harp_v1_loop.mp3' },
1889
- { name: '🌺 Hawaiian Slack Key Guitar', value: 'agent_vibes_hawaiian_slack_key_guitar_v2_loop.mp3' },
1890
- { name: '🏜️ Arabic Oud (Middle Eastern)', value: 'agent_vibes_arabic_v2_loop.mp3' },
1891
- { name: '🪘 Gnawa Ambient (North African)', value: 'agent_vibes_ganawa_ambient_v2_loop.mp3' },
1892
- { name: '🥁 Tabla Dream Pop (Indian percussion)', value: 'agent_vibes_tabla_dream_pop_v1_loop.mp3' }
1893
- ];
1894
-
1895
- const selectedTrack = await inquirer.prompt([{
1896
- type: 'list',
1897
- name: 'track',
1898
- message: 'Choose default background music track:',
1899
- choices: trackChoices,
1900
- default: 'agentvibes_soft_flamenco_loop.mp3'
1901
- }]);
1902
-
1903
- // Enable background music and set default track
1904
- const configDir = path.join(claudeDir, 'config');
1905
- await fs.mkdir(configDir, { recursive: true });
2687
+ // Apply background music configuration from userConfig
2688
+ if (backgroundMusicResult.count > 0) {
2689
+ const configDir = path.join(claudeDir, 'config');
2690
+ await fs.mkdir(configDir, { recursive: true });
1906
2691
 
2692
+ if (userConfig.backgroundMusic.enabled) {
1907
2693
  // Write enabled flag
1908
2694
  const enabledFile = path.join(configDir, 'background-music-enabled.txt');
1909
2695
  await fs.writeFile(enabledFile, 'true');
@@ -1915,57 +2701,22 @@ async function install(options = {}) {
1915
2701
  // Update the default entry with selected track
1916
2702
  audioEffectsContent = audioEffectsContent.replace(
1917
2703
  /^default\|([^|]*)\|([^|]*)\|(.*)$/m,
1918
- `default|$1|${selectedTrack.track}|$3`
2704
+ `default|$1|${userConfig.backgroundMusic.track}|$3`
1919
2705
  );
1920
2706
 
1921
2707
  await fs.writeFile(audioEffectsPath, audioEffectsContent);
1922
-
1923
- console.log(chalk.green(`\n✅ Background music enabled!`));
1924
- console.log(chalk.white(` Default track: ${trackChoices.find(t => t.value === selectedTrack.track)?.name || selectedTrack.track}`));
1925
- console.log('');
1926
- console.log(chalk.cyan('💡 Tip: Change background music anytime:'));
1927
- console.log(chalk.white(' • Change globally: /agent-vibes:background-music set-default <track>'));
1928
- console.log(chalk.white(' • Change per-agent: /agent-vibes:background-music set-agent <agent> <track>'));
1929
- console.log(chalk.white(' • List all tracks: /agent-vibes:background-music list'));
1930
- console.log(chalk.white(' • Disable: /agent-vibes:background-music off'));
1931
- } else {
1932
- console.log(chalk.gray('\nℹ️ Background music disabled. Enable later with: /agent-vibes:background-music on'));
1933
2708
  }
1934
2709
  }
1935
2710
 
1936
- // Configure verbosity level (if not using --yes flag)
1937
- if (!options.yes) {
1938
- console.log(''); // Blank line for spacing
1939
-
1940
- const verbosityChoice = await inquirer.prompt([{
1941
- type: 'list',
1942
- name: 'level',
1943
- message: 'Choose TTS verbosity level (how much Claude speaks):',
1944
- choices: [
1945
- { name: '🔊 High - Maximum transparency (speaks all reasoning, decisions, findings)', value: 'high' },
1946
- { name: '🔉 Medium - Balanced (speaks acknowledgments and key updates)', value: 'medium' },
1947
- { name: '🔈 Low - Minimal (only essential notifications)', value: 'low' }
1948
- ],
1949
- default: 'high'
1950
- }]);
1951
-
1952
- // Write verbosity level to config
1953
- const verbosityFile = path.join(claudeDir, 'tts-verbosity.txt');
1954
- await fs.writeFile(verbosityFile, verbosityChoice.level);
2711
+ // Apply reverb configuration from userConfig
2712
+ const selectedReverb = userConfig.reverb;
1955
2713
 
1956
- console.log(chalk.green(`\n✅ Verbosity set to: ${verbosityChoice.level}`));
2714
+ // Apply verbosity configuration from userConfig
2715
+ const verbosityFile = path.join(claudeDir, 'tts-verbosity.txt');
2716
+ await fs.writeFile(verbosityFile, userConfig.verbosity);
1957
2717
 
1958
- const verbosityDescriptions = {
1959
- high: 'Claude will speak acknowledgments, completions, reasoning, decisions, and findings',
1960
- medium: 'Claude will speak acknowledgments, completions, and key updates',
1961
- low: 'Claude will only speak essential notifications'
1962
- };
1963
-
1964
- console.log(chalk.white(` ${verbosityDescriptions[verbosityChoice.level]}`));
1965
- console.log('');
1966
- console.log(chalk.cyan('💡 Tip: Change verbosity anytime:'));
1967
- console.log(chalk.white(' • /agent-vibes:verbosity <low|medium|high>'));
1968
- }
2718
+ // Initialize piperVoicesBoxen outside the conditional for proper scoping
2719
+ let piperVoicesBoxen = null;
1969
2720
 
1970
2721
  if (selectedProvider === 'macos') {
1971
2722
  // macOS Say provider summary
@@ -2022,27 +2773,41 @@ async function install(options = {}) {
2022
2773
  missingVoices = commonVoices;
2023
2774
  }
2024
2775
 
2025
- console.log(chalk.white(` • 18 languages supported`));
2026
- console.log(chalk.green(` • No API key needed ✓`));
2027
-
2776
+ // Create Piper voices boxen (only if newly installed)
2028
2777
  if (installedVoices.length > 0) {
2029
2778
  // Separate newly installed from pre-existing voices
2030
2779
  const newlyInstalled = installedVoices.filter(v => !preExistingVoices.includes(v.name));
2031
2780
  const alreadyInstalled = installedVoices.filter(v => preExistingVoices.includes(v.name));
2032
2781
 
2782
+ // Only create boxen if there are newly installed voices
2033
2783
  if (newlyInstalled.length > 0) {
2034
- console.log(chalk.green(` • ${newlyInstalled.length} Piper voice${newlyInstalled.length === 1 ? '' : 's'} newly installed:`));
2784
+ let content = chalk.bold.green(`${newlyInstalled.length} Newly Installed\n\n`);
2035
2785
  newlyInstalled.forEach(voice => {
2036
- console.log(chalk.green(` ✓ ${voice.name} (${voice.size})`));
2037
- console.log(chalk.gray(` ${voice.path}`));
2786
+ content += chalk.green(`✓ ${voice.name}`) + chalk.gray(` (${voice.size})\n`);
2787
+ content += chalk.dim(` ${voice.path}\n`);
2038
2788
  });
2039
- }
2040
2789
 
2041
- if (alreadyInstalled.length > 0) {
2042
- console.log(chalk.gray(` • ${alreadyInstalled.length} Piper voice${alreadyInstalled.length === 1 ? '' : 's'} already installed:`));
2043
- alreadyInstalled.forEach(voice => {
2044
- console.log(chalk.gray(` ✓ ${voice.name} (${voice.size}) - already installed`));
2045
- console.log(chalk.gray(` ${voice.path}`));
2790
+ if (alreadyInstalled.length > 0) {
2791
+ content += '\n' + chalk.gray(''.repeat(60)) + '\n\n';
2792
+ content += chalk.bold.cyan(`${alreadyInstalled.length} Already Installed\n\n`);
2793
+ alreadyInstalled.forEach(voice => {
2794
+ content += chalk.cyan(`✓ ${voice.name}`) + chalk.gray(` (${voice.size})\n`);
2795
+ content += chalk.dim(` ${voice.path}\n`);
2796
+ });
2797
+ }
2798
+
2799
+ // Add additional info at the bottom of boxen
2800
+ content += '\n' + chalk.gray('─'.repeat(60)) + '\n\n';
2801
+ content += chalk.white('• 18 languages supported\n');
2802
+ content += chalk.green('• No API key needed ✓');
2803
+
2804
+ piperVoicesBoxen = boxen(content.trim(), {
2805
+ padding: 1,
2806
+ margin: 1,
2807
+ borderStyle: 'round',
2808
+ borderColor: 'cyan',
2809
+ title: chalk.bold(`🎤 Piper Voices (${installedVoices.length} total)`),
2810
+ titleAlignment: 'center'
2046
2811
  });
2047
2812
  }
2048
2813
  }
@@ -2057,150 +2822,29 @@ async function install(options = {}) {
2057
2822
  if (installedVoices.length === 0 && missingVoices.length === 0) {
2058
2823
  console.log(chalk.white(` • 50+ Piper neural voices available (free!)`));
2059
2824
  }
2060
-
2061
- // Show background music files
2062
- const tracksDir = path.join(targetDir, '.claude', 'audio', 'tracks');
2063
- try {
2064
- if (fsSync.existsSync(tracksDir)) {
2065
- const bgFiles = fsSync.readdirSync(tracksDir);
2066
- const musicFiles = bgFiles.filter(f => f.endsWith('.mp3') || f.endsWith('.wav'));
2067
- if (musicFiles.length > 0) {
2068
- console.log(chalk.white(` • ${musicFiles.length} background music track${musicFiles.length === 1 ? '' : 's'} available`));
2069
- }
2070
- }
2071
- } catch {
2072
- // Ignore errors
2073
- }
2074
2825
  }
2075
2826
  console.log('');
2076
2827
 
2077
- // Pause to let user review installation summary
2078
- if (!options.yes) {
2079
- await inquirer.prompt([
2080
- {
2081
- type: 'confirm',
2082
- name: 'continue',
2083
- message: chalk.cyan('📋 Review the installation summary above. Continue?'),
2084
- default: true,
2085
- }
2086
- ]);
2828
+ // Collect all boxens for pagination
2829
+ const pages = [];
2830
+ if (commandResult.boxen) {
2831
+ pages.push({ title: 'Summary: Slash Commands', content: commandResult.boxen });
2087
2832
  }
2088
-
2089
- // Show recent changes from git log or RELEASE_NOTES.md
2090
- try {
2091
- const { execSync } = await import('node:child_process');
2092
- const gitLog = execSync(
2093
- 'git log --oneline --no-decorate -5',
2094
- { cwd: path.join(__dirname, '..'), encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] }
2095
- ).trim();
2096
-
2097
- if (gitLog) {
2098
- console.log(chalk.cyan('📝 Recent Changes:\n'));
2099
- const commits = gitLog.split('\n');
2100
- commits.forEach(commit => {
2101
- const [hash, ...messageParts] = commit.split(' ');
2102
- const message = messageParts.join(' ');
2103
- console.log(chalk.gray(` ${hash}`) + ' ' + chalk.white(message));
2104
- });
2105
- console.log();
2106
- }
2107
- } catch (error) {
2108
- // Git not available or not a git repo - try RELEASE_NOTES.md
2109
- try {
2110
- const releaseNotesPath = path.join(__dirname, '..', 'RELEASE_NOTES.md');
2111
- const releaseNotes = await fs.readFile(releaseNotesPath, 'utf8');
2112
-
2113
- // Extract commits from "Recent Commits" section
2114
- const lines = releaseNotes.split('\n');
2115
- const commitsIndex = lines.findIndex(line => line.includes('## 📝 Recent Commits'));
2116
-
2117
- if (commitsIndex >= 0) {
2118
- console.log(chalk.cyan('📝 Recent Changes:\n'));
2119
-
2120
- // Find the code block with commits (between ``` markers)
2121
- let inCodeBlock = false;
2122
- for (let i = commitsIndex + 1; i < lines.length; i++) {
2123
- const line = lines[i];
2124
-
2125
- if (line.trim() === '```') {
2126
- if (inCodeBlock) break; // End of code block
2127
- inCodeBlock = true;
2128
- continue;
2129
- }
2130
-
2131
- if (inCodeBlock && line.trim()) {
2132
- // Security: Use bounded quantifiers to prevent ReDoS
2133
- // Match git short hash (7-12 hex chars) followed by single space and message
2134
- const match = line.match(/^([a-f0-9]{7,12}) (.*)$/);
2135
- if (match) {
2136
- const [, hash, message] = match;
2137
- console.log(chalk.gray(` ${hash}`) + ' ' + chalk.white(message));
2138
- }
2139
- }
2140
- }
2141
- console.log();
2142
- }
2143
- } catch {
2144
- // No release notes available
2145
- }
2833
+ if (backgroundMusicResult.boxen) {
2834
+ pages.push({ title: 'Summary: Background Music', content: backgroundMusicResult.boxen });
2146
2835
  }
2147
-
2148
- // Success message
2149
- console.log(
2150
- boxen(
2151
- chalk.green.bold('✨ Installation Complete! ✨\n\n') +
2152
- chalk.green('✅ AgentVibes TTS is now active via SessionStart hook!\n') +
2153
- chalk.gray(' (No additional setup needed - TTS protocol auto-loads on every session)\n\n') +
2154
- chalk.white('🎤 Available Commands:\n\n') +
2155
- chalk.cyan(' /agent-vibes') + chalk.gray(' .................... Show all commands\n') +
2156
- chalk.cyan(' /agent-vibes:list') + chalk.gray(' ............... List available voices\n') +
2157
- chalk.cyan(' /agent-vibes:preview') + chalk.gray(' ............ Preview voice samples\n') +
2158
- chalk.cyan(' /agent-vibes:switch <name>') + chalk.gray(' ...... Change active voice\n') +
2159
- chalk.cyan(' /agent-vibes:replay [N]') + chalk.gray(' ......... Replay last audio\n') +
2160
- chalk.cyan(' /agent-vibes:add <name> <id>') + chalk.gray(' .... Add custom voice\n') +
2161
- chalk.cyan(' /agent-vibes:whoami') + chalk.gray(' .............. Show current voice\n') +
2162
- chalk.cyan(' /agent-vibes:get') + chalk.gray(' ................. Get active voice\n') +
2163
- chalk.cyan(' /agent-vibes:sample <name>') + chalk.gray(' ...... Test a voice\n') +
2164
- chalk.cyan(' /agent-vibes:personality') + chalk.gray(' ......... Set personality\n') +
2165
- chalk.cyan(' /agent-vibes:sentiment') + chalk.gray(' ........... Set sentiment\n') +
2166
- chalk.cyan(' /agent-vibes:set-pretext') + chalk.gray(' ......... Set TTS prefix\n\n') +
2167
- chalk.yellow('🎵 Try: ') + chalk.cyan('/agent-vibes:preview') + chalk.yellow(' to hear the voices!\n\n') +
2168
- chalk.gray('📦 Repo: ') + chalk.cyan('https://github.com/paulpreibisch/AgentVibes'),
2169
- {
2170
- padding: 1,
2171
- margin: 1,
2172
- borderStyle: 'double',
2173
- borderColor: 'green',
2174
- }
2175
- )
2176
- );
2177
-
2178
- console.log(chalk.green.bold('\n✅ AgentVibes is Ready!'));
2179
- console.log(chalk.white(' TTS protocol automatically loads on every Claude session'));
2180
- console.log(chalk.gray(' via SessionStart hook - no additional setup needed!\n'));
2181
-
2182
- // Play welcome demo with harpsichord intro and reverb voice (opt-in only)
2183
- if (options.withAudio) {
2184
- await playWelcomeDemo(targetDir, spinner, options);
2836
+ if (hookResult.boxen) {
2837
+ pages.push({ title: 'Summary: TTS Scripts', content: hookResult.boxen });
2185
2838
  }
2186
-
2187
- console.log(chalk.cyan('🎤 Try these commands:'));
2188
- console.log(chalk.white(' • /agent-vibes:list') + chalk.gray(' - See all available voices'));
2189
- console.log(chalk.white(' • /agent-vibes:switch <name>') + chalk.gray(' - Change your voice'));
2190
- console.log(chalk.white(' • /agent-vibes:personality <style>') + chalk.gray(' - Set personality\n'));
2191
-
2192
- // Pause to let user review setup instructions
2193
- if (!options.yes) {
2194
- await inquirer.prompt([
2195
- {
2196
- type: 'confirm',
2197
- name: 'continue',
2198
- message: chalk.cyan('Continue?'),
2199
- default: true,
2200
- }
2201
- ]);
2839
+ if (personalityResult.boxen) {
2840
+ pages.push({ title: 'Summary: Personalities', content: personalityResult.boxen });
2841
+ }
2842
+ if (piperVoicesBoxen) {
2843
+ pages.push({ title: 'Summary: Piper Voices', content: piperVoicesBoxen });
2202
2844
  }
2203
2845
 
2846
+ // Recent Changes already shown on page 2 after welcome banner - no need to show again
2847
+
2204
2848
  // Handle MCP configuration - offer to create .mcp.json if not exists
2205
2849
  await handleMcpConfiguration(targetDir, options);
2206
2850
 
@@ -2216,41 +2860,104 @@ async function install(options = {}) {
2216
2860
  ? `v${bmadDetection.detailedVersion}`
2217
2861
  : 'v4';
2218
2862
 
2219
- console.log(
2220
- boxen(
2221
- chalk.green.bold(`🎉 BMAD-METHOD™ ${versionLabel} Detected!\n\n`) +
2222
- chalk.white.bold('We detected you ALREADY have the BMAD-METHOD™\n') +
2223
- chalk.white.bold('The Universal AI Agent Framework installed!\n\n') +
2224
- chalk.cyan('✨ Try the Party Mode command:\n') +
2225
- chalk.yellow.bold(' /bmad:core:workflows:party-mode\n\n') +
2226
- chalk.gray('AgentVibes will assign a unique voice to each agent\n') +
2227
- chalk.gray('while they help you with your project!\n\n') +
2228
- chalk.cyan('Other Commands:\n') +
2229
- chalk.gray(' • /agent-vibes:bmad status - View voice assignments\n') +
2230
- chalk.gray(' • /agent-vibes:bmad set <agent> <voice> - Customize voices'),
2231
- {
2232
- padding: 1,
2233
- margin: 1,
2234
- borderStyle: 'round',
2235
- borderColor: 'green',
2236
- }
2237
- )
2238
- );
2863
+ const bmadContent =
2864
+ chalk.green.bold(`🎉 BMAD-METHOD™ ${versionLabel} Detected!\n\n`) +
2865
+ chalk.white.bold('We detected you ALREADY have the BMAD-METHOD™\n') +
2866
+ chalk.white.bold('The Universal AI Agent Framework installed!\n\n') +
2867
+ chalk.cyan(' Try the Party Mode command:\n') +
2868
+ chalk.yellow.bold(' /bmad:core:workflows:party-mode\n\n') +
2869
+ chalk.gray('AgentVibes will assign a unique voice to each agent\n') +
2870
+ chalk.gray('while they help you with your project!\n\n') +
2871
+ chalk.cyan('Other Commands:\n') +
2872
+ chalk.gray(' /agent-vibes:bmad status - View voice assignments\n') +
2873
+ chalk.gray(' • /agent-vibes:bmad set <agent> <voice> - Customize voices');
2874
+
2875
+ pages.push({ title: 'BMAD Integration', content: bmadContent });
2239
2876
  } else {
2240
- console.log(
2241
- boxen(
2242
- chalk.cyan.bold('💡 We also Recommend:\n\n') +
2243
- chalk.white.bold('BMAD-METHOD™: Universal AI Agent Framework\n\n') +
2244
- chalk.gray('AgentVibes auto-detects BMAD and assigns voices to agents!\n\n') +
2245
- chalk.cyan('https://github.com/bmad-code-org/BMAD-METHOD'),
2246
- {
2247
- padding: 1,
2248
- margin: 1,
2249
- borderStyle: 'round',
2250
- borderColor: 'cyan',
2251
- }
2252
- )
2877
+ const bmadRecommendBoxen = boxen(
2878
+ chalk.cyan.bold('💡 We also Recommend:\n\n') +
2879
+ chalk.white.bold('BMAD-METHOD™: Universal AI Agent Framework\n\n') +
2880
+ chalk.gray('AgentVibes auto-detects BMAD and assigns voices to agents!\n\n') +
2881
+ chalk.cyan('https://github.com/bmad-code-org/BMAD-METHOD'),
2882
+ {
2883
+ padding: 1,
2884
+ margin: 1,
2885
+ borderStyle: 'round',
2886
+ borderColor: 'cyan',
2887
+ }
2253
2888
  );
2889
+
2890
+ pages.push({ title: 'Recommended Tools', content: bmadRecommendBoxen });
2891
+ }
2892
+
2893
+ // Apply reverb setting if not "off" (using dynamic import for ES modules)
2894
+ if (selectedReverb && selectedReverb !== 'off') {
2895
+ const effectsManagerPath = path.join(targetDir, '.claude', 'hooks', 'effects-manager.sh');
2896
+ try {
2897
+ execSync(`bash "${effectsManagerPath}" set-reverb ${selectedReverb} default`, {
2898
+ stdio: 'pipe',
2899
+ });
2900
+ } catch (error) {
2901
+ // Silent fail - will be shown in success message if needed
2902
+ }
2903
+ }
2904
+
2905
+ // Success message as final page (no boxen)
2906
+ const successContent =
2907
+ chalk.green.bold('✨ Installation Complete! ✨\n\n') +
2908
+ chalk.green('✅ AgentVibes TTS is now active via SessionStart hook!\n') +
2909
+ chalk.gray(' (No additional setup needed - TTS protocol auto-loads on every session)\n\n') +
2910
+ chalk.white('🎤 Available Commands:\n\n') +
2911
+ chalk.cyan(' /agent-vibes') + chalk.gray(' .................... Show all commands\n') +
2912
+ chalk.cyan(' /agent-vibes:list') + chalk.gray(' ............... List available voices\n') +
2913
+ chalk.cyan(' /agent-vibes:preview') + chalk.gray(' ............ Preview voice samples\n') +
2914
+ chalk.cyan(' /agent-vibes:switch <name>') + chalk.gray(' ...... Change active voice\n') +
2915
+ chalk.cyan(' /agent-vibes:replay [N]') + chalk.gray(' ......... Replay last audio\n') +
2916
+ chalk.cyan(' /agent-vibes:add <name> <id>') + chalk.gray(' .... Add custom voice\n') +
2917
+ chalk.cyan(' /agent-vibes:whoami') + chalk.gray(' .............. Show current voice\n') +
2918
+ chalk.cyan(' /agent-vibes:get') + chalk.gray(' ................. Get active voice\n') +
2919
+ chalk.cyan(' /agent-vibes:sample <name>') + chalk.gray(' ...... Test a voice\n') +
2920
+ chalk.cyan(' /agent-vibes:personality') + chalk.gray(' ......... Set personality\n') +
2921
+ chalk.cyan(' /agent-vibes:sentiment') + chalk.gray(' ........... Set sentiment\n') +
2922
+ chalk.cyan(' /agent-vibes:set-pretext') + chalk.gray(' ......... Set TTS prefix\n\n') +
2923
+ chalk.yellow('🎵 Try: ') + chalk.cyan('/agent-vibes:preview') + chalk.yellow(' to hear the voices!\n\n') +
2924
+ chalk.gray('📦 Repo: ') + chalk.cyan('https://github.com/paulpreibisch/AgentVibes\n') +
2925
+ chalk.gray('📖 Docs: ') + chalk.cyan('https://github.com/paulpreibisch/AgentVibes/blob/master/README.md');
2926
+
2927
+ pages.push({ title: 'Installation Complete', content: successContent });
2928
+
2929
+ // Show all pages with pagination navigation
2930
+ const postInstallOffset = configPages + preInstallPages.length; // After config + pre-install pages
2931
+ const actualTotalPages = configPages + preInstallPages.length + pages.length;
2932
+
2933
+ await showPaginatedContent(pages, {
2934
+ ...options,
2935
+ continueLabel: '✓ Installation Complete',
2936
+ pageOffset: postInstallOffset,
2937
+ totalPages: actualTotalPages
2938
+ });
2939
+
2940
+ // Final message after pagination
2941
+ console.log(chalk.green.bold('\n✅ AgentVibes is Ready!\n'));
2942
+
2943
+ // Check for .mcp.json file
2944
+ const mcpConfigPath = path.join(targetDir, '.mcp.json');
2945
+ const hasMcpConfig = fsSync.existsSync(mcpConfigPath);
2946
+
2947
+ if (hasMcpConfig) {
2948
+ console.log(chalk.white(' Launch Claude Code with MCP:'));
2949
+ console.log(chalk.cyan(' claude --mcp-config .mcp.json\n'));
2950
+ } else {
2951
+ console.log(chalk.white(' Start a new session to activate TTS.\n'));
2952
+ }
2953
+
2954
+ console.log(chalk.white(' • /agent-vibes:list') + chalk.gray(' - See all available voices'));
2955
+ console.log(chalk.white(' • /agent-vibes:switch <name>') + chalk.gray(' - Change your voice'));
2956
+ console.log(chalk.white(' • /agent-vibes:personality <style>') + chalk.gray(' - Set personality\n'));
2957
+
2958
+ // Play welcome demo with harpsichord intro and reverb voice (opt-in only)
2959
+ if (options.withAudio) {
2960
+ await playWelcomeDemo(targetDir, spinner, options);
2254
2961
  }
2255
2962
 
2256
2963
  } catch (error) {