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.
- package/.claude/config/audio-effects.cfg +1 -1
- package/.claude/config/audio-effects.cfg.sample +52 -0
- package/.claude/hooks/audio-processor.sh +2 -2
- package/.claude/hooks/bmad-voice-manager.sh +152 -5
- package/.claude/hooks/effects-manager.sh +268 -0
- package/.claude/hooks/learn-manager.sh +7 -7
- package/.claude/hooks/personality-manager.sh +35 -24
- package/.claude/hooks/piper-installer.sh +2 -2
- package/.claude/hooks/provider-manager.sh +4 -4
- package/.claude/hooks/replay-target-audio.sh +1 -1
- package/.claude/hooks/speed-manager.sh +4 -4
- package/.claude/hooks/voice-manager.sh +50 -47
- package/.claude/personalities/angry.md +2 -4
- package/.claude/personalities/annoying.md +2 -4
- package/.claude/personalities/crass.md +2 -4
- package/.claude/personalities/dramatic.md +2 -4
- package/.claude/personalities/dry-humor.md +1 -3
- package/.claude/personalities/flirty.md +2 -4
- package/.claude/personalities/funny.md +2 -4
- package/.claude/personalities/grandpa.md +1 -3
- package/.claude/personalities/millennial.md +2 -4
- package/.claude/personalities/moody.md +2 -4
- package/.claude/personalities/normal.md +2 -4
- package/.claude/personalities/pirate.md +2 -4
- package/.claude/personalities/poetic.md +2 -4
- package/.claude/personalities/professional.md +2 -4
- package/.claude/personalities/rapper.md +1 -3
- package/.claude/personalities/robot.md +2 -4
- package/.claude/personalities/sarcastic.md +2 -4
- package/.claude/personalities/sassy.md +1 -3
- package/.claude/personalities/surfer-dude.md +2 -4
- package/.claude/personalities/zen.md +2 -4
- package/README.md +146 -10
- package/mcp-server/server.py +120 -17
- package/package.json +1 -1
- package/src/cli/list-personalities.js +110 -0
- package/src/cli/list-voices.js +114 -0
- package/src/commands/install-mcp.js +50 -34
- package/src/installer.js +1157 -450
- package/src/utils/dependency-checker.js +403 -0
- 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
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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 =
|
|
211
|
-
|
|
212
|
-
We have added a lot of commands, but don't worry, you can hide them by typing
|
|
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
|
-
|
|
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
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
450
|
-
|
|
1022
|
+
// On macOS, both providers available - prompt user
|
|
1023
|
+
console.log(chalk.cyan('🎭 Choose Your TTS Provider:\n'));
|
|
451
1024
|
|
|
452
|
-
|
|
453
|
-
|
|
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
|
-
|
|
461
|
-
|
|
462
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
1165
|
+
installedCommands.push(file);
|
|
577
1166
|
successCount++;
|
|
578
1167
|
} catch (err) {
|
|
579
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1269
|
+
installedFiles.push({ name: file, executable: true });
|
|
642
1270
|
} else {
|
|
643
|
-
|
|
1271
|
+
installedFiles.push({ name: file, executable: false });
|
|
644
1272
|
}
|
|
645
1273
|
successCount++;
|
|
646
1274
|
} catch (err) {
|
|
647
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1357
|
+
installedPersonalities.push(file);
|
|
695
1358
|
}
|
|
696
1359
|
|
|
697
1360
|
spinner.succeed(chalk.green('Installed personality templates!\n'));
|
|
698
|
-
|
|
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
|
-
|
|
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
|
|
1594
|
-
console.log(chalk.green(`✓ Updated ${
|
|
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
|
|
1618
|
-
if (
|
|
1619
|
-
console.log(chalk.green(`✓ Installed ${
|
|
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(` • ${
|
|
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
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
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
|
-
|
|
1677
|
-
|
|
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
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
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('');
|
|
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
|
-
|
|
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
|
-
//
|
|
2543
|
+
// Ask if user wants to hear welcome message
|
|
1692
2544
|
if (!options.yes) {
|
|
1693
|
-
|
|
1694
|
-
console.
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
console.log(
|
|
1698
|
-
console.log(
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
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: '
|
|
1705
|
-
message:
|
|
1706
|
-
default:
|
|
2557
|
+
name: 'playWelcome',
|
|
2558
|
+
message: chalk.yellow('🎵 Listen to Welcome Message?'),
|
|
2559
|
+
default: false,
|
|
1707
2560
|
},
|
|
1708
2561
|
]);
|
|
1709
2562
|
|
|
1710
|
-
if (
|
|
1711
|
-
|
|
1712
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
1784
|
-
const
|
|
1785
|
-
const
|
|
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
|
|
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(
|
|
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
|
-
//
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
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|${
|
|
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
|
-
//
|
|
1937
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1959
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2784
|
+
let content = chalk.bold.green(`${newlyInstalled.length} Newly Installed\n\n`);
|
|
2035
2785
|
newlyInstalled.forEach(voice => {
|
|
2036
|
-
|
|
2037
|
-
|
|
2786
|
+
content += chalk.green(`✓ ${voice.name}`) + chalk.gray(` (${voice.size})\n`);
|
|
2787
|
+
content += chalk.dim(` ${voice.path}\n`);
|
|
2038
2788
|
});
|
|
2039
|
-
}
|
|
2040
2789
|
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
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
|
-
//
|
|
2078
|
-
|
|
2079
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
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
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
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
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
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) {
|