agentvibes 2.17.8 ā 3.0.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/commands/prepare-release.md +39 -6
- package/.claude/commands/release.md +97 -43
- package/.claude/config/audio-effects.cfg +2 -2
- package/.claude/config/background-music-position.txt +1 -1
- package/.claude/config/background-music-volume.txt +1 -1
- package/.claude/config/background-music.txt +1 -0
- package/.claude/docs/TERMUX_SETUP.md +408 -0
- package/.claude/github-star-reminder.txt +1 -1
- package/.claude/hooks/play-tts-termux-ssh.sh +169 -0
- package/.claude/hooks/play-tts.sh +6 -0
- package/.mcp.json +4 -0
- package/README.md +66 -8
- package/RELEASE_NOTES.md +299 -0
- package/mcp-server/install-deps.js +18 -3
- package/mcp-server/server.py +26 -10
- package/mcp-server/test_server.py +65 -0
- package/package.json +6 -2
- package/src/installer.js +479 -131
- package/templates/activation-instructions-bmad.md +54 -0
- package/templates/audio/welcome-demo.wav +0 -0
- package/templates/audio/welcome-music.mp3 +0 -0
package/src/installer.js
CHANGED
|
@@ -335,6 +335,7 @@ async function collectConfiguration(options = {}) {
|
|
|
335
335
|
const config = {
|
|
336
336
|
provider: null,
|
|
337
337
|
piperPath: null,
|
|
338
|
+
sshHost: null,
|
|
338
339
|
defaultVoice: null,
|
|
339
340
|
reverb: 'light',
|
|
340
341
|
backgroundMusic: {
|
|
@@ -376,83 +377,75 @@ async function collectConfiguration(options = {}) {
|
|
|
376
377
|
} else if (currentPage === 1) {
|
|
377
378
|
// Page 2: TTS Provider & Voice Storage
|
|
378
379
|
|
|
379
|
-
//
|
|
380
|
-
|
|
381
|
-
console.log(boxen(
|
|
382
|
-
chalk.white('Text-to-Speech (TTS) converts Claude\'s text responses into spoken audio.\n\n') +
|
|
383
|
-
chalk.white('Your TTS Provider:\n\n') +
|
|
384
|
-
chalk.green('š Piper TTS (Free, Offline)\n') +
|
|
385
|
-
chalk.gray(' ⢠50+ Hugging Face AI voices\n') +
|
|
386
|
-
chalk.gray(' ⢠Human-like speech quality\n') +
|
|
387
|
-
chalk.gray(' ⢠No API key required\n\n') +
|
|
388
|
-
chalk.dim('(Automatically selected - only option for Linux/WSL)'),
|
|
389
|
-
{
|
|
390
|
-
padding: 1,
|
|
391
|
-
margin: { top: 0, bottom: 0, left: 0, right: 0 },
|
|
392
|
-
borderStyle: 'round',
|
|
393
|
-
borderColor: 'gray',
|
|
394
|
-
width: 80
|
|
395
|
-
}
|
|
396
|
-
));
|
|
380
|
+
// Show provider selection with all available options
|
|
381
|
+
const isMacOS = process.platform === 'darwin';
|
|
397
382
|
|
|
398
|
-
|
|
383
|
+
console.log(boxen(
|
|
384
|
+
chalk.white('Text-to-Speech (TTS) converts Claude\'s text responses into spoken audio.\n\n') +
|
|
385
|
+
chalk.white('Choose your Text-to-Speech provider.\n\n') +
|
|
386
|
+
(isMacOS ? chalk.yellow('š macOS Say\n') +
|
|
387
|
+
chalk.gray(' ⢠Built-in to macOS\n') +
|
|
388
|
+
chalk.gray(' ⢠Zero setup required\n') +
|
|
389
|
+
chalk.gray(' ⢠40+ system voices\n\n') : '') +
|
|
390
|
+
chalk.green('š Piper TTS\n') +
|
|
391
|
+
chalk.gray(' ⢠Free & offline\n') +
|
|
392
|
+
chalk.gray(' ⢠50+ Hugging Face AI voices\n') +
|
|
393
|
+
chalk.gray(' ⢠Human-like speech quality\n\n') +
|
|
394
|
+
chalk.blue('š± Termux SSH\n') +
|
|
395
|
+
chalk.gray(' ⢠Only use if project is on a Remote Server\n') +
|
|
396
|
+
chalk.gray(' ⢠Pushes audio to your Android Device via SSH\n') +
|
|
397
|
+
chalk.gray(' ⢠Native Android TTS\n') +
|
|
398
|
+
chalk.gray(' ⢠See: github.com/paulpreibisch/AgentVibes/blob/master/.claude/docs/TERMUX_SETUP.md'),
|
|
399
|
+
{
|
|
400
|
+
padding: 1,
|
|
401
|
+
margin: { top: 0, bottom: 0, left: 0, right: 0 },
|
|
402
|
+
borderStyle: 'round',
|
|
403
|
+
borderColor: 'gray',
|
|
404
|
+
width: 80
|
|
405
|
+
}
|
|
406
|
+
));
|
|
399
407
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
// macOS - show choice between macOS Say and Piper
|
|
403
|
-
console.log(boxen(
|
|
404
|
-
chalk.white('Text-to-Speech (TTS) converts Claude\'s text responses into spoken audio.\n\n') +
|
|
405
|
-
chalk.white('Choose your Text-to-Speech provider.\n\n') +
|
|
406
|
-
chalk.yellow('š macOS Say\n') +
|
|
407
|
-
chalk.gray(' ⢠Built-in to macOS\n') +
|
|
408
|
-
chalk.gray(' ⢠Zero setup required\n') +
|
|
409
|
-
chalk.gray(' ⢠40+ system voices\n\n') +
|
|
410
|
-
chalk.green('š Piper TTS\n') +
|
|
411
|
-
chalk.gray(' ⢠Free & offline\n') +
|
|
412
|
-
chalk.gray(' ⢠50+ Hugging Face AI voices\n') +
|
|
413
|
-
chalk.gray(' ⢠Human-like speech quality'),
|
|
414
|
-
{
|
|
415
|
-
padding: 1,
|
|
416
|
-
margin: { top: 0, bottom: 0, left: 0, right: 0 },
|
|
417
|
-
borderStyle: 'round',
|
|
418
|
-
borderColor: 'gray',
|
|
419
|
-
width: 80
|
|
420
|
-
}
|
|
421
|
-
));
|
|
408
|
+
// Provider selection
|
|
409
|
+
const providerChoices = [];
|
|
422
410
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
{
|
|
430
|
-
name: chalk.green('š Piper TTS (Free, Offline)'),
|
|
431
|
-
value: 'piper'
|
|
432
|
-
},
|
|
433
|
-
new inquirer.Separator(),
|
|
434
|
-
{
|
|
435
|
-
name: chalk.magentaBright('ā Back to Welcome'),
|
|
436
|
-
value: '__back__'
|
|
437
|
-
}
|
|
438
|
-
];
|
|
411
|
+
if (isMacOS) {
|
|
412
|
+
providerChoices.push({
|
|
413
|
+
name: chalk.yellow('š macOS Say (Recommended)'),
|
|
414
|
+
value: 'macos'
|
|
415
|
+
});
|
|
416
|
+
}
|
|
439
417
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
choices: providerChoices,
|
|
445
|
-
default: config.provider || 'macos'
|
|
446
|
-
}]);
|
|
418
|
+
providerChoices.push({
|
|
419
|
+
name: chalk.green('š Piper TTS (Free, Offline)'),
|
|
420
|
+
value: 'piper'
|
|
421
|
+
});
|
|
447
422
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
423
|
+
providerChoices.push({
|
|
424
|
+
name: chalk.blue('š± Termux SSH (Android)') + chalk.gray(' - Only choose if your project is on a remote server and you want audio sent to your Android device. See: github.com/paulpreibisch/AgentVibes/blob/master/.claude/docs/TERMUX_SETUP.md'),
|
|
425
|
+
value: 'termux-ssh'
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
providerChoices.push(new inquirer.Separator());
|
|
429
|
+
providerChoices.push({
|
|
430
|
+
name: chalk.magentaBright('ā Back to Welcome'),
|
|
431
|
+
value: '__back__'
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
const { provider } = await inquirer.prompt([{
|
|
435
|
+
type: 'list',
|
|
436
|
+
name: 'provider',
|
|
437
|
+
message: chalk.yellow('Select TTS provider:'),
|
|
438
|
+
choices: providerChoices,
|
|
439
|
+
default: config.provider || (isMacOS ? 'macos' : 'piper')
|
|
440
|
+
}]);
|
|
452
441
|
|
|
453
|
-
|
|
442
|
+
// Check if user wants to go back
|
|
443
|
+
if (provider === '__back__') {
|
|
444
|
+
return null;
|
|
454
445
|
}
|
|
455
446
|
|
|
447
|
+
config.provider = provider;
|
|
448
|
+
|
|
456
449
|
// If Piper selected, ask for voice storage location
|
|
457
450
|
if (config.provider === 'piper') {
|
|
458
451
|
const homeDir = process.env.HOME || process.env.USERPROFILE;
|
|
@@ -494,6 +487,59 @@ async function collectConfiguration(options = {}) {
|
|
|
494
487
|
}
|
|
495
488
|
}
|
|
496
489
|
|
|
490
|
+
// If Termux SSH selected, ask for SSH host alias
|
|
491
|
+
if (config.provider === 'termux-ssh') {
|
|
492
|
+
console.log('\n' + boxen(
|
|
493
|
+
chalk.white('Termux SSH requires an SSH host alias configured in ~/.ssh/config\n') +
|
|
494
|
+
chalk.white('Example: "android" pointing to your Android device\n\n') +
|
|
495
|
+
chalk.cyan('š Documentation:\n') +
|
|
496
|
+
chalk.gray(' github.com/paulpreibisch/AgentVibes/blob/master/.claude/docs/TERMUX_SETUP.md\n') +
|
|
497
|
+
chalk.gray(' After install: .claude/docs/TERMUX_SETUP.md\n\n') +
|
|
498
|
+
chalk.cyan('š Required Components:\n') +
|
|
499
|
+
chalk.gray(' ⢠Tailscale VPN: ') + chalk.blue('https://tailscale.com/download/android\n') +
|
|
500
|
+
chalk.gray(' ⢠F-Droid Store: ') + chalk.blue('https://f-droid.org\n') +
|
|
501
|
+
chalk.gray(' ⢠Termux App: ') + chalk.blue('https://f-droid.org/packages/com.termux\n') +
|
|
502
|
+
chalk.gray(' ⢠Termux:API: ') + chalk.blue('https://f-droid.org/packages/com.termux.api'),
|
|
503
|
+
{
|
|
504
|
+
padding: 1,
|
|
505
|
+
margin: { top: 0, bottom: 0, left: 0, right: 0 },
|
|
506
|
+
borderStyle: 'round',
|
|
507
|
+
borderColor: 'blue',
|
|
508
|
+
width: 80
|
|
509
|
+
}
|
|
510
|
+
));
|
|
511
|
+
|
|
512
|
+
const { configureNow } = await inquirer.prompt([{
|
|
513
|
+
type: 'confirm',
|
|
514
|
+
name: 'configureNow',
|
|
515
|
+
message: chalk.yellow('Configure SSH host alias now?'),
|
|
516
|
+
default: false
|
|
517
|
+
}]);
|
|
518
|
+
|
|
519
|
+
if (configureNow) {
|
|
520
|
+
const { sshHost } = await inquirer.prompt([{
|
|
521
|
+
type: 'input',
|
|
522
|
+
name: 'sshHost',
|
|
523
|
+
message: chalk.yellow('Enter your SSH host alias (e.g., "android"):'),
|
|
524
|
+
validate: (input) => {
|
|
525
|
+
if (!input || input.trim() === '') {
|
|
526
|
+
return 'Please provide a valid SSH host alias';
|
|
527
|
+
}
|
|
528
|
+
// Security: Basic validation - no spaces, no special chars that could cause issues
|
|
529
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(input.trim())) {
|
|
530
|
+
return 'SSH host alias should only contain letters, numbers, dashes, and underscores';
|
|
531
|
+
}
|
|
532
|
+
return true;
|
|
533
|
+
}
|
|
534
|
+
}]);
|
|
535
|
+
|
|
536
|
+
config.sshHost = sshHost.trim();
|
|
537
|
+
} else {
|
|
538
|
+
console.log(chalk.yellow('\nā ļø SSH host not configured - you can set it later:'));
|
|
539
|
+
console.log(chalk.gray(' echo "your-host-alias" > ~/.claude/termux-ssh-host.txt\n'));
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
497
543
|
} else if (currentPage === 2) {
|
|
498
544
|
// Page 3: Voice Selection
|
|
499
545
|
console.log(boxen(
|
|
@@ -588,10 +634,42 @@ async function collectConfiguration(options = {}) {
|
|
|
588
634
|
currentPage++;
|
|
589
635
|
continue;
|
|
590
636
|
}
|
|
637
|
+
|
|
638
|
+
} else if (config.provider === 'termux-ssh') {
|
|
639
|
+
// Termux SSH - voices are managed on Android device
|
|
640
|
+
console.log(boxen(
|
|
641
|
+
chalk.white('Android TTS voices are managed on your Android device.\n\n') +
|
|
642
|
+
chalk.gray('To configure voices:\n') +
|
|
643
|
+
chalk.gray(' 1. Open Android ') + chalk.cyan('Settings ā Accessibility ā Text-to-Speech\n') +
|
|
644
|
+
chalk.gray(' 2. Install voice engines from Play Store (e.g., Google TTS)\n') +
|
|
645
|
+
chalk.gray(' 3. Select your preferred engine and voice\n\n') +
|
|
646
|
+
chalk.yellow('AgentVibes will use your Android\'s selected TTS voice automatically.'),
|
|
647
|
+
{
|
|
648
|
+
padding: 1,
|
|
649
|
+
margin: { top: 0, bottom: 0, left: 0, right: 0 },
|
|
650
|
+
borderStyle: 'round',
|
|
651
|
+
borderColor: 'blue',
|
|
652
|
+
width: 80
|
|
653
|
+
}
|
|
654
|
+
));
|
|
655
|
+
|
|
656
|
+
console.log(chalk.green('\nā Android TTS will use device-configured voice\n'));
|
|
657
|
+
|
|
658
|
+
// Auto-advance to next page
|
|
659
|
+
currentPage++;
|
|
660
|
+
continue;
|
|
591
661
|
}
|
|
592
662
|
|
|
593
663
|
} else if (currentPage === 3) {
|
|
594
664
|
// Page 4: Audio Settings (Reverb + Background Music)
|
|
665
|
+
// Skip for termux-ssh - audio effects/background music don't work with SSH text-only TTS
|
|
666
|
+
if (config.provider === 'termux-ssh') {
|
|
667
|
+
console.log(chalk.yellow('ā Audio effects and background music are not available for Termux SSH provider'));
|
|
668
|
+
console.log(chalk.gray(' (Audio plays on Android device, not locally)\n'));
|
|
669
|
+
currentPage++;
|
|
670
|
+
continue;
|
|
671
|
+
}
|
|
672
|
+
|
|
595
673
|
console.log(boxen(
|
|
596
674
|
chalk.white('Configure audio effects and background music for your Agents.\n\n') +
|
|
597
675
|
chalk.yellow('Reverb:\n') +
|
|
@@ -823,17 +901,19 @@ function showWelcome() {
|
|
|
823
901
|
* Shown during install and update commands
|
|
824
902
|
*/
|
|
825
903
|
function getReleaseInfoBoxen() {
|
|
826
|
-
return chalk.cyan.bold('š¦ AgentVibes
|
|
904
|
+
return chalk.cyan.bold('š¦ AgentVibes v3.0.0 - Cross-Platform Remote Audio\n\n') +
|
|
827
905
|
chalk.green.bold('šļø WHAT\'S NEW:\n\n') +
|
|
828
|
-
chalk.cyan('AgentVibes
|
|
829
|
-
chalk.cyan('
|
|
830
|
-
chalk.cyan('
|
|
831
|
-
chalk.cyan('
|
|
906
|
+
chalk.cyan('AgentVibes v3.0.0 introduces the termux-ssh TTS provider, enabling true mobile-first\n') +
|
|
907
|
+
chalk.cyan('interactive conversations with Claude Code. Route TTS audio to your Android device via\n') +
|
|
908
|
+
chalk.cyan('SSHāwhether coding locally on your laptop or on remote serversāand have hands-free,\n') +
|
|
909
|
+
chalk.cyan('voice-driven conversations with Claude using just your phone. Transforms how developers\n') +
|
|
910
|
+
chalk.cyan('interact with AI assistants.\n\n') +
|
|
832
911
|
chalk.green.bold('⨠KEY HIGHLIGHTS:\n\n') +
|
|
833
|
-
chalk.gray('
|
|
834
|
-
chalk.gray('
|
|
835
|
-
chalk.gray('
|
|
836
|
-
chalk.gray('
|
|
912
|
+
chalk.gray(' š± Mobile-First AI Conversations - Fully interactive conversations with Claude Code\n') +
|
|
913
|
+
chalk.gray(' š» Local + Remote Development - Works for local coding and remote server development\n') +
|
|
914
|
+
chalk.gray(' š Tailscale Integration - Internet-wide access without port forwarding\n') +
|
|
915
|
+
chalk.gray(' š„ Demo Video - Watch it in action: https://youtu.be/ngLiA_KQtTA\n') +
|
|
916
|
+
chalk.gray(' šÆ Full MCP Compatibility - Complete integration with all MCP workflows\n\n') +
|
|
837
917
|
chalk.gray('š Full Release Notes: RELEASE_NOTES.md\n') +
|
|
838
918
|
chalk.gray('š Website: https://agentvibes.org\n') +
|
|
839
919
|
chalk.gray('š¦ Repository: https://github.com/paulpreibisch/AgentVibes\n\n') +
|
|
@@ -857,15 +937,15 @@ async function playWelcomeDemo(targetDir, spinner, options = {}) {
|
|
|
857
937
|
let audioPlayer = null;
|
|
858
938
|
|
|
859
939
|
try {
|
|
860
|
-
|
|
940
|
+
execFileSync('which', ['paplay'], { stdio: 'pipe' });
|
|
861
941
|
audioPlayer = 'paplay';
|
|
862
942
|
} catch {
|
|
863
943
|
try {
|
|
864
|
-
|
|
944
|
+
execFileSync('which', ['afplay'], { stdio: 'pipe' });
|
|
865
945
|
audioPlayer = 'afplay';
|
|
866
946
|
} catch {
|
|
867
947
|
try {
|
|
868
|
-
|
|
948
|
+
execFileSync('which', ['mpv'], { stdio: 'pipe' });
|
|
869
949
|
audioPlayer = 'mpv';
|
|
870
950
|
} catch {}
|
|
871
951
|
}
|
|
@@ -1116,9 +1196,9 @@ Without the \`.bmad-agent-context\` file:
|
|
|
1116
1196
|
// ============================================================================
|
|
1117
1197
|
|
|
1118
1198
|
/**
|
|
1119
|
-
* Prompt user to select TTS provider (Piper or
|
|
1199
|
+
* Prompt user to select TTS provider (Piper, macOS Say, or Termux SSH)
|
|
1120
1200
|
* @param {Object} options - Installation options
|
|
1121
|
-
* @returns {Promise<string>} Selected provider ('piper' or '
|
|
1201
|
+
* @returns {Promise<string>} Selected provider ('piper', 'macos', or 'termux-ssh')
|
|
1122
1202
|
*/
|
|
1123
1203
|
async function promptProviderSelection(options) {
|
|
1124
1204
|
const isMacOS = process.platform === 'darwin';
|
|
@@ -1133,41 +1213,30 @@ async function promptProviderSelection(options) {
|
|
|
1133
1213
|
return 'piper';
|
|
1134
1214
|
}
|
|
1135
1215
|
|
|
1136
|
-
//
|
|
1137
|
-
if (!isMacOS) {
|
|
1138
|
-
// On Linux/WSL, only Piper is available - auto-select it
|
|
1139
|
-
console.log(boxen(
|
|
1140
|
-
chalk.bold('š¤ TTS Provider\n\n') +
|
|
1141
|
-
chalk.green('ā Piper TTS (Free, Offline)\n') +
|
|
1142
|
-
chalk.gray(' 50+ Hugging Face AI voices\n') +
|
|
1143
|
-
chalk.gray(' Human-like speech quality\n') +
|
|
1144
|
-
chalk.gray(' No API key required'),
|
|
1145
|
-
{
|
|
1146
|
-
padding: 1,
|
|
1147
|
-
margin: 1,
|
|
1148
|
-
borderStyle: 'round',
|
|
1149
|
-
borderColor: 'green',
|
|
1150
|
-
title: chalk.bold('TTS Provider Auto-detected'),
|
|
1151
|
-
titleAlignment: 'center'
|
|
1152
|
-
}
|
|
1153
|
-
));
|
|
1154
|
-
console.log('');
|
|
1155
|
-
return 'piper';
|
|
1156
|
-
}
|
|
1157
|
-
|
|
1158
|
-
// On macOS, both providers available - prompt user
|
|
1216
|
+
// Always show all providers - let user choose
|
|
1159
1217
|
console.log(chalk.cyan('š Choose Your TTS Provider:\n'));
|
|
1160
1218
|
|
|
1161
|
-
const choices = [
|
|
1162
|
-
|
|
1219
|
+
const choices = [];
|
|
1220
|
+
|
|
1221
|
+
// macOS Say (only on macOS)
|
|
1222
|
+
if (isMacOS) {
|
|
1223
|
+
choices.push({
|
|
1163
1224
|
name: chalk.yellow('š macOS Say (Recommended)') + chalk.gray(' - Built-in, zero setup required'),
|
|
1164
1225
|
value: 'macos',
|
|
1165
|
-
}
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1226
|
+
});
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
// Piper TTS (all platforms)
|
|
1230
|
+
choices.push({
|
|
1231
|
+
name: chalk.green('š Piper TTS (Free, Offline)') + chalk.gray(' - 50+ Hugging Face AI voices, human-like speech'),
|
|
1232
|
+
value: 'piper',
|
|
1233
|
+
});
|
|
1234
|
+
|
|
1235
|
+
// Termux SSH (all platforms)
|
|
1236
|
+
choices.push({
|
|
1237
|
+
name: chalk.blue('š± Termux SSH (Android)') + chalk.gray(' - Only choose if your project is on a remote server and you want audio sent to your Android device. See: github.com/paulpreibisch/AgentVibes/blob/master/.claude/docs/TERMUX_SETUP.md'),
|
|
1238
|
+
value: 'termux-ssh',
|
|
1239
|
+
});
|
|
1171
1240
|
|
|
1172
1241
|
const { provider } = await inquirer.prompt([
|
|
1173
1242
|
{
|
|
@@ -1175,7 +1244,7 @@ async function promptProviderSelection(options) {
|
|
|
1175
1244
|
name: 'provider',
|
|
1176
1245
|
message: 'Which TTS provider would you like to use?',
|
|
1177
1246
|
choices,
|
|
1178
|
-
default: 'macos',
|
|
1247
|
+
default: isMacOS ? 'macos' : 'piper',
|
|
1179
1248
|
},
|
|
1180
1249
|
]);
|
|
1181
1250
|
|
|
@@ -1271,6 +1340,58 @@ async function handlePiperConfiguration() {
|
|
|
1271
1340
|
return piperPath;
|
|
1272
1341
|
}
|
|
1273
1342
|
|
|
1343
|
+
/**
|
|
1344
|
+
* Handle Termux SSH configuration (SSH host alias setup)
|
|
1345
|
+
* @returns {Promise<string|null>} SSH host alias or null if user skips
|
|
1346
|
+
*/
|
|
1347
|
+
async function handleTermuxSshConfiguration() {
|
|
1348
|
+
console.log(chalk.cyan('\nš± Termux SSH Configuration:\n'));
|
|
1349
|
+
console.log(chalk.gray(' Termux SSH requires an SSH host alias configured in ~/.ssh/config'));
|
|
1350
|
+
console.log(chalk.gray(' Example: "android" pointing to your Android device\n'));
|
|
1351
|
+
console.log(chalk.gray(' See documentation: .claude/docs/TERMUX_SETUP.md\n'));
|
|
1352
|
+
console.log(chalk.cyan(' š Required Components:\n'));
|
|
1353
|
+
console.log(chalk.gray(' ⢠Tailscale VPN: ') + chalk.blue('https://tailscale.com/download/android'));
|
|
1354
|
+
console.log(chalk.gray(' ⢠F-Droid Store: ') + chalk.blue('https://f-droid.org'));
|
|
1355
|
+
console.log(chalk.gray(' ⢠Termux App: ') + chalk.blue('https://f-droid.org/packages/com.termux'));
|
|
1356
|
+
console.log(chalk.gray(' ⢠Termux:API: ') + chalk.blue('https://f-droid.org/packages/com.termux.api\n'));
|
|
1357
|
+
|
|
1358
|
+
const { configureNow } = await inquirer.prompt([
|
|
1359
|
+
{
|
|
1360
|
+
type: 'confirm',
|
|
1361
|
+
name: 'configureNow',
|
|
1362
|
+
message: 'Do you want to configure the SSH host alias now?',
|
|
1363
|
+
default: false,
|
|
1364
|
+
},
|
|
1365
|
+
]);
|
|
1366
|
+
|
|
1367
|
+
if (!configureNow) {
|
|
1368
|
+
console.log(chalk.yellow('ā ļø SSH host not configured - you can set it later:'));
|
|
1369
|
+
console.log(chalk.gray(' echo "your-host-alias" > ~/.claude/termux-ssh-host.txt\n'));
|
|
1370
|
+
return null;
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
const { sshHost } = await inquirer.prompt([
|
|
1374
|
+
{
|
|
1375
|
+
type: 'input',
|
|
1376
|
+
name: 'sshHost',
|
|
1377
|
+
message: 'Enter your SSH host alias (e.g., "android"):',
|
|
1378
|
+
validate: (input) => {
|
|
1379
|
+
if (!input || input.trim() === '') {
|
|
1380
|
+
return 'Please provide a valid SSH host alias';
|
|
1381
|
+
}
|
|
1382
|
+
// Basic validation: no spaces, no special chars that could cause issues
|
|
1383
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(input.trim())) {
|
|
1384
|
+
return 'SSH host alias should only contain letters, numbers, dashes, and underscores';
|
|
1385
|
+
}
|
|
1386
|
+
return true;
|
|
1387
|
+
},
|
|
1388
|
+
},
|
|
1389
|
+
]);
|
|
1390
|
+
|
|
1391
|
+
const sshHostTrimmed = sshHost.trim();
|
|
1392
|
+
console.log(chalk.green(`ā SSH host alias set to: ${sshHostTrimmed}`));
|
|
1393
|
+
return sshHostTrimmed;
|
|
1394
|
+
}
|
|
1274
1395
|
|
|
1275
1396
|
/**
|
|
1276
1397
|
* Copy command files to target directory
|
|
@@ -2546,6 +2667,15 @@ async function handleBmadIntegration(targetDir, options = {}) {
|
|
|
2546
2667
|
*/
|
|
2547
2668
|
async function showRecentChanges(sourceDir) {
|
|
2548
2669
|
try {
|
|
2670
|
+
// Check if sourceDir actually has a .git directory
|
|
2671
|
+
const gitDir = path.join(sourceDir, '.git');
|
|
2672
|
+
const gitDirExists = await fs.access(gitDir).then(() => true).catch(() => false);
|
|
2673
|
+
|
|
2674
|
+
if (!gitDirExists) {
|
|
2675
|
+
// No .git directory - skip git log to avoid showing parent repo's commits
|
|
2676
|
+
throw new Error('No .git directory in package - using release notes');
|
|
2677
|
+
}
|
|
2678
|
+
|
|
2549
2679
|
const { execSync } = await import('node:child_process');
|
|
2550
2680
|
const gitLog = execSync( // NOSONAR - Safe: fixed command with controlled cwd, no user input
|
|
2551
2681
|
'git log --oneline --no-decorate -5',
|
|
@@ -2827,7 +2957,7 @@ async function install(options = {}) {
|
|
|
2827
2957
|
const preInstallPages = [];
|
|
2828
2958
|
|
|
2829
2959
|
// Page 1: Configuration Summary
|
|
2830
|
-
const providerLabels = { piper: 'Piper TTS', macos: 'macOS Say' };
|
|
2960
|
+
const providerLabels = { piper: 'Piper TTS', macos: 'macOS Say', 'termux-ssh': 'Termux SSH (Android)' };
|
|
2831
2961
|
const reverbLabels = {
|
|
2832
2962
|
off: 'Off',
|
|
2833
2963
|
light: 'Light',
|
|
@@ -2847,6 +2977,13 @@ async function install(options = {}) {
|
|
|
2847
2977
|
if (selectedProvider === 'piper' && piperVoicesPath) {
|
|
2848
2978
|
configContent += chalk.gray(` Voice storage: ${piperVoicesPath}\n`);
|
|
2849
2979
|
}
|
|
2980
|
+
if (selectedProvider === 'termux-ssh') {
|
|
2981
|
+
if (userConfig.sshHost) {
|
|
2982
|
+
configContent += chalk.gray(` SSH host: ${userConfig.sshHost}\n`);
|
|
2983
|
+
} else {
|
|
2984
|
+
configContent += chalk.yellow(` SSH host: Not configured (set later)\n`);
|
|
2985
|
+
}
|
|
2986
|
+
}
|
|
2850
2987
|
configContent += '\n';
|
|
2851
2988
|
configContent += chalk.cyan('šļø Audio Settings:\n');
|
|
2852
2989
|
configContent += chalk.white(` Reverb: ${reverbLabels[userConfig.reverb]}\n`);
|
|
@@ -2923,11 +3060,15 @@ async function install(options = {}) {
|
|
|
2923
3060
|
console.log(header);
|
|
2924
3061
|
console.log(configBoxen);
|
|
2925
3062
|
console.log('');
|
|
2926
|
-
|
|
3063
|
+
// Don't show welcome message text for termux-ssh (it won't work)
|
|
3064
|
+
if (userConfig.provider !== 'termux-ssh') {
|
|
3065
|
+
console.log(chalk.gray('Play audio welcome message from Paul, creator of AgentVibes.\n'));
|
|
3066
|
+
}
|
|
2927
3067
|
}
|
|
2928
3068
|
|
|
2929
3069
|
// Ask welcome message question BEFORE showing navigation
|
|
2930
|
-
|
|
3070
|
+
// Skip for termux-ssh - welcome audio plays locally, not on Android device
|
|
3071
|
+
if (!options.yes && userConfig.provider !== 'termux-ssh') {
|
|
2931
3072
|
// Ask if user wants to hear welcome message
|
|
2932
3073
|
const { playWelcome } = await inquirer.prompt([
|
|
2933
3074
|
{
|
|
@@ -2945,21 +3086,23 @@ async function install(options = {}) {
|
|
|
2945
3086
|
spinner.succeed(chalk.green('Welcome message complete!'));
|
|
2946
3087
|
console.log(''); // Spacing after completion
|
|
2947
3088
|
}
|
|
3089
|
+
} else if (!options.yes && userConfig.provider === 'termux-ssh') {
|
|
3090
|
+
console.log(chalk.yellow('ā Welcome message skipped (not available for Termux SSH)\n'));
|
|
3091
|
+
}
|
|
2948
3092
|
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
3093
|
+
// Now show navigation menu (Continue to installation)
|
|
3094
|
+
const { startInstall } = await inquirer.prompt([
|
|
3095
|
+
{
|
|
3096
|
+
type: 'confirm',
|
|
3097
|
+
name: 'startInstall',
|
|
3098
|
+
message: chalk.yellow('ā
Start Installation?'),
|
|
3099
|
+
default: true,
|
|
3100
|
+
},
|
|
3101
|
+
]);
|
|
2958
3102
|
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
}
|
|
3103
|
+
if (!startInstall) {
|
|
3104
|
+
console.log(chalk.red('\nā Installation cancelled.\n'));
|
|
3105
|
+
process.exit(0);
|
|
2963
3106
|
}
|
|
2964
3107
|
|
|
2965
3108
|
// User already confirmed by pressing "Start Installation", so no need for another confirmation
|
|
@@ -3023,6 +3166,11 @@ async function install(options = {}) {
|
|
|
3023
3166
|
await fs.writeFile(piperConfigPath, piperVoicesPath);
|
|
3024
3167
|
}
|
|
3025
3168
|
|
|
3169
|
+
if (selectedProvider === 'termux-ssh' && userConfig.sshHost) {
|
|
3170
|
+
const sshHostConfigPath = path.join(claudeDir, 'termux-ssh-host.txt');
|
|
3171
|
+
await fs.writeFile(sshHostConfigPath, userConfig.sshHost);
|
|
3172
|
+
}
|
|
3173
|
+
|
|
3026
3174
|
// Set default voice based on user selection or provider defaults
|
|
3027
3175
|
const voiceConfigPath = path.join(claudeDir, 'tts-voice.txt');
|
|
3028
3176
|
let defaultVoice = userConfig.defaultVoice;
|
|
@@ -3034,6 +3182,12 @@ async function install(options = {}) {
|
|
|
3034
3182
|
defaultVoice = 'en_US-ryan-high';
|
|
3035
3183
|
break;
|
|
3036
3184
|
case 'macos':
|
|
3185
|
+
defaultVoice = 'Samantha';
|
|
3186
|
+
break;
|
|
3187
|
+
case 'termux-ssh':
|
|
3188
|
+
// Android TTS voices are managed in Android settings, not here
|
|
3189
|
+
defaultVoice = 'android-system-default';
|
|
3190
|
+
break;
|
|
3037
3191
|
default:
|
|
3038
3192
|
defaultVoice = 'Samantha';
|
|
3039
3193
|
break;
|
|
@@ -3451,6 +3605,200 @@ program
|
|
|
3451
3605
|
);
|
|
3452
3606
|
});
|
|
3453
3607
|
|
|
3608
|
+
program
|
|
3609
|
+
.command('uninstall')
|
|
3610
|
+
.description('Uninstall AgentVibes from current project')
|
|
3611
|
+
.option('-d, --directory <path>', 'Installation directory (default: current directory)')
|
|
3612
|
+
.option('-y, --yes', 'Skip confirmation prompt (auto-confirm)')
|
|
3613
|
+
.option('--global', 'Also remove global configuration (~/.claude/, ~/.agentvibes/)')
|
|
3614
|
+
.option('--with-piper', 'Also remove Piper TTS installation (~/piper/)')
|
|
3615
|
+
.action(async (options) => {
|
|
3616
|
+
const currentDir = process.env.INIT_CWD || process.cwd();
|
|
3617
|
+
const targetDir = options.directory || currentDir;
|
|
3618
|
+
|
|
3619
|
+
showWelcome();
|
|
3620
|
+
|
|
3621
|
+
console.log(chalk.cyan('š Uninstall Details:'));
|
|
3622
|
+
console.log(chalk.gray(` Target directory: ${targetDir}`));
|
|
3623
|
+
console.log(chalk.gray(` Package version: ${VERSION}\n`));
|
|
3624
|
+
|
|
3625
|
+
// Check if installed
|
|
3626
|
+
const commandsDir = path.join(targetDir, '.claude', 'commands', 'agent-vibes');
|
|
3627
|
+
let isInstalled = false;
|
|
3628
|
+
try {
|
|
3629
|
+
await fs.access(commandsDir);
|
|
3630
|
+
isInstalled = true;
|
|
3631
|
+
} catch {}
|
|
3632
|
+
|
|
3633
|
+
if (!isInstalled) {
|
|
3634
|
+
console.log(chalk.yellow('ā ļø AgentVibes is not installed in this directory.'));
|
|
3635
|
+
console.log(chalk.gray(` Directory checked: ${targetDir}/.claude/`));
|
|
3636
|
+
console.log(chalk.gray(' Nothing to uninstall.\n'));
|
|
3637
|
+
process.exit(0);
|
|
3638
|
+
}
|
|
3639
|
+
|
|
3640
|
+
// Show what will be removed
|
|
3641
|
+
console.log(chalk.cyan('š¦ What will be removed:\n'));
|
|
3642
|
+
|
|
3643
|
+
const itemsToRemove = [];
|
|
3644
|
+
|
|
3645
|
+
// Project-level items
|
|
3646
|
+
console.log(chalk.white.bold(' Project Files:'));
|
|
3647
|
+
itemsToRemove.push({ path: '.claude/commands/agent-vibes/', desc: 'AgentVibes slash commands' });
|
|
3648
|
+
itemsToRemove.push({ path: '.claude/hooks/', desc: 'TTS scripts' });
|
|
3649
|
+
itemsToRemove.push({ path: '.claude/personalities/', desc: 'Personality templates' });
|
|
3650
|
+
itemsToRemove.push({ path: '.claude/output-styles/', desc: 'Output style templates' });
|
|
3651
|
+
itemsToRemove.push({ path: '.claude/audio/', desc: 'Audio cache' });
|
|
3652
|
+
itemsToRemove.push({ path: '.claude/tts-*.txt', desc: 'TTS configuration files' });
|
|
3653
|
+
itemsToRemove.push({ path: '.claude/*.json', desc: 'AgentVibes settings' });
|
|
3654
|
+
itemsToRemove.push({ path: '.agentvibes/', desc: 'BMAD integration files' });
|
|
3655
|
+
|
|
3656
|
+
for (const item of itemsToRemove) {
|
|
3657
|
+
console.log(chalk.gray(` ⢠${item.path}`));
|
|
3658
|
+
}
|
|
3659
|
+
|
|
3660
|
+
// Global items
|
|
3661
|
+
if (options.global) {
|
|
3662
|
+
console.log(chalk.white.bold('\n Global Files:'));
|
|
3663
|
+
console.log(chalk.gray(' ⢠~/.claude/ (global configuration)'));
|
|
3664
|
+
console.log(chalk.gray(' ⢠~/.agentvibes/ (global cache)'));
|
|
3665
|
+
}
|
|
3666
|
+
|
|
3667
|
+
// Piper TTS
|
|
3668
|
+
if (options.withPiper) {
|
|
3669
|
+
console.log(chalk.white.bold('\n TTS Engine:'));
|
|
3670
|
+
console.log(chalk.gray(' ⢠~/piper/ (Piper TTS installation)'));
|
|
3671
|
+
}
|
|
3672
|
+
|
|
3673
|
+
console.log('');
|
|
3674
|
+
|
|
3675
|
+
// Confirmation
|
|
3676
|
+
if (!options.yes) {
|
|
3677
|
+
const { confirm } = await inquirer.prompt([
|
|
3678
|
+
{
|
|
3679
|
+
type: 'confirm',
|
|
3680
|
+
name: 'confirm',
|
|
3681
|
+
message: chalk.yellow('Are you sure you want to uninstall AgentVibes?'),
|
|
3682
|
+
default: false,
|
|
3683
|
+
},
|
|
3684
|
+
]);
|
|
3685
|
+
|
|
3686
|
+
if (!confirm) {
|
|
3687
|
+
console.log(chalk.green('\nā Uninstall cancelled. AgentVibes remains installed.\n'));
|
|
3688
|
+
process.exit(0);
|
|
3689
|
+
}
|
|
3690
|
+
} else {
|
|
3691
|
+
console.log(chalk.gray('ā Auto-confirmed (--yes flag)\n'));
|
|
3692
|
+
}
|
|
3693
|
+
|
|
3694
|
+
const spinner = ora('Uninstalling AgentVibes...').start();
|
|
3695
|
+
|
|
3696
|
+
try {
|
|
3697
|
+
let removedCount = 0;
|
|
3698
|
+
|
|
3699
|
+
// Remove project-level files
|
|
3700
|
+
const projectPaths = [
|
|
3701
|
+
path.join(targetDir, '.claude', 'commands', 'agent-vibes'),
|
|
3702
|
+
path.join(targetDir, '.claude', 'hooks'),
|
|
3703
|
+
path.join(targetDir, '.claude', 'personalities'),
|
|
3704
|
+
path.join(targetDir, '.claude', 'output-styles'),
|
|
3705
|
+
path.join(targetDir, '.claude', 'audio'),
|
|
3706
|
+
path.join(targetDir, '.agentvibes'),
|
|
3707
|
+
];
|
|
3708
|
+
|
|
3709
|
+
for (const dirPath of projectPaths) {
|
|
3710
|
+
try {
|
|
3711
|
+
await fs.rm(dirPath, { recursive: true, force: true });
|
|
3712
|
+
removedCount++;
|
|
3713
|
+
} catch (err) {
|
|
3714
|
+
// Ignore if directory doesn't exist
|
|
3715
|
+
}
|
|
3716
|
+
}
|
|
3717
|
+
|
|
3718
|
+
// Remove TTS config files
|
|
3719
|
+
const configPatterns = [
|
|
3720
|
+
'tts-voice.txt',
|
|
3721
|
+
'tts-provider.txt',
|
|
3722
|
+
'tts-personality.txt',
|
|
3723
|
+
'tts-verbosity.txt',
|
|
3724
|
+
'tts-translate.txt',
|
|
3725
|
+
'tts-target-voice.txt',
|
|
3726
|
+
'tts-target-language.txt',
|
|
3727
|
+
'tts-language.txt',
|
|
3728
|
+
'personalities.json',
|
|
3729
|
+
'github-star-reminder.txt',
|
|
3730
|
+
'piper-voices-dir.txt',
|
|
3731
|
+
'verbosity.txt',
|
|
3732
|
+
];
|
|
3733
|
+
|
|
3734
|
+
for (const pattern of configPatterns) {
|
|
3735
|
+
const filePath = path.join(targetDir, '.claude', pattern);
|
|
3736
|
+
try {
|
|
3737
|
+
await fs.unlink(filePath);
|
|
3738
|
+
} catch (err) {
|
|
3739
|
+
// Ignore if file doesn't exist
|
|
3740
|
+
}
|
|
3741
|
+
}
|
|
3742
|
+
|
|
3743
|
+
// Remove global files if requested
|
|
3744
|
+
if (options.global) {
|
|
3745
|
+
const homedir = process.env.HOME || process.env.USERPROFILE;
|
|
3746
|
+
const globalPaths = [
|
|
3747
|
+
path.join(homedir, '.claude'),
|
|
3748
|
+
path.join(homedir, '.agentvibes'),
|
|
3749
|
+
];
|
|
3750
|
+
|
|
3751
|
+
for (const dirPath of globalPaths) {
|
|
3752
|
+
try {
|
|
3753
|
+
await fs.rm(dirPath, { recursive: true, force: true });
|
|
3754
|
+
removedCount++;
|
|
3755
|
+
} catch (err) {
|
|
3756
|
+
// Ignore if directory doesn't exist
|
|
3757
|
+
}
|
|
3758
|
+
}
|
|
3759
|
+
}
|
|
3760
|
+
|
|
3761
|
+
// Remove Piper TTS if requested
|
|
3762
|
+
if (options.withPiper) {
|
|
3763
|
+
const homedir = process.env.HOME || process.env.USERPROFILE;
|
|
3764
|
+
const piperPath = path.join(homedir, 'piper');
|
|
3765
|
+
|
|
3766
|
+
try {
|
|
3767
|
+
await fs.rm(piperPath, { recursive: true, force: true });
|
|
3768
|
+
removedCount++;
|
|
3769
|
+
} catch (err) {
|
|
3770
|
+
// Ignore if directory doesn't exist
|
|
3771
|
+
}
|
|
3772
|
+
}
|
|
3773
|
+
|
|
3774
|
+
spinner.succeed(chalk.green('Successfully uninstalled AgentVibes!\n'));
|
|
3775
|
+
|
|
3776
|
+
// Show summary
|
|
3777
|
+
console.log(
|
|
3778
|
+
boxen(
|
|
3779
|
+
chalk.green.bold('ā Uninstall Complete\n\n') +
|
|
3780
|
+
chalk.gray('AgentVibes has been removed from this project.\n') +
|
|
3781
|
+
(options.global ? chalk.gray('Global configuration has been removed.\n') : '') +
|
|
3782
|
+
(options.withPiper ? chalk.gray('Piper TTS has been removed.\n') : '') +
|
|
3783
|
+
chalk.gray('\nTo reinstall: ') + chalk.cyan('npx agentvibes install\n') +
|
|
3784
|
+
chalk.gray('\nWe\'d love to know why you uninstalled!\n') +
|
|
3785
|
+
chalk.gray('Share feedback: ') + chalk.cyan('https://github.com/paulpreibisch/AgentVibes/issues'),
|
|
3786
|
+
{
|
|
3787
|
+
padding: 1,
|
|
3788
|
+
margin: 1,
|
|
3789
|
+
borderStyle: 'round',
|
|
3790
|
+
borderColor: 'green',
|
|
3791
|
+
}
|
|
3792
|
+
)
|
|
3793
|
+
);
|
|
3794
|
+
|
|
3795
|
+
} catch (err) {
|
|
3796
|
+
spinner.fail(chalk.red('Failed to uninstall AgentVibes'));
|
|
3797
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
3798
|
+
process.exit(1);
|
|
3799
|
+
}
|
|
3800
|
+
});
|
|
3801
|
+
|
|
3454
3802
|
program
|
|
3455
3803
|
.command('status')
|
|
3456
3804
|
.description('Show installation status')
|