agentvibes 3.3.0 → 3.4.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/src/installer.js CHANGED
@@ -99,6 +99,85 @@ function detectAndNotifyTermux() {
99
99
  return false;
100
100
  }
101
101
 
102
+ /**
103
+ * Check if PulseAudio tunnel is active
104
+ * @returns {boolean} True if PULSE_SERVER points to a TCP connection
105
+ */
106
+ function hasPulseAudioTunnel() {
107
+ return process.env.PULSE_SERVER &&
108
+ process.env.PULSE_SERVER.toLowerCase().startsWith('tcp:');
109
+ }
110
+
111
+ /**
112
+ * Detect system capabilities for smart provider recommendations
113
+ * @returns {Promise<Object>} System info including GPU, memory, platform
114
+ */
115
+ async function detectSystemCapabilities() {
116
+ const isMacOS = process.platform === 'darwin';
117
+ const isAndroid = isTermux();
118
+ let hasGPU = false;
119
+ let totalRAM = 0;
120
+
121
+ try {
122
+ // Detect NVIDIA GPU
123
+ try {
124
+ execSync('nvidia-smi --query-gpu=name --format=csv,noheader', {
125
+ stdio: 'pipe',
126
+ timeout: 5000 // 5 second timeout
127
+ });
128
+ hasGPU = true;
129
+ } catch (e) {
130
+ // No NVIDIA GPU or timeout
131
+ }
132
+
133
+ // Detect total RAM (in MB)
134
+ if (isMacOS) {
135
+ const output = execSync('sysctl hw.memsize', {
136
+ encoding: 'utf8',
137
+ timeout: 3000 // 3 second timeout
138
+ });
139
+ const parts = output.split(':');
140
+ if (parts.length < 2) {
141
+ throw new Error('Unexpected sysctl output format');
142
+ }
143
+ const bytes = parseInt(parts[1].trim(), 10);
144
+ if (isNaN(bytes)) {
145
+ throw new Error('Failed to parse memory size');
146
+ }
147
+ totalRAM = Math.floor(bytes / (1024 * 1024));
148
+ } else {
149
+ const output = execSync('cat /proc/meminfo | grep MemTotal', {
150
+ encoding: 'utf8',
151
+ timeout: 3000 // 3 second timeout
152
+ });
153
+ const parts = output.split(':');
154
+ if (parts.length < 2) {
155
+ throw new Error('Unexpected meminfo output format');
156
+ }
157
+ const memParts = parts[1].trim().split(' ');
158
+ if (memParts.length < 1) {
159
+ throw new Error('Unexpected meminfo value format');
160
+ }
161
+ const kb = parseInt(memParts[0], 10);
162
+ if (isNaN(kb)) {
163
+ throw new Error('Failed to parse memory size');
164
+ }
165
+ totalRAM = Math.floor(kb / 1024);
166
+ }
167
+ } catch (e) {
168
+ // Fallback: assume 4GB if detection fails
169
+ totalRAM = 4096;
170
+ }
171
+
172
+ return {
173
+ hasGPU,
174
+ lowMemory: totalRAM < 4096,
175
+ totalRAM,
176
+ isMacOS,
177
+ isAndroid
178
+ };
179
+ }
180
+
102
181
  /**
103
182
  * Detect environment type for smart installation defaults
104
183
  * @returns {string} - 'DESKTOP', 'PHONE', or 'VOICELESS'
@@ -109,18 +188,24 @@ function detectEnvironment() {
109
188
  return 'PHONE';
110
189
  }
111
190
 
112
- // Check for audio devices
191
+ // Check for audio devices (local hardware)
113
192
  const hasAudio = checkAudioDevices();
114
193
 
194
+ // Check if PulseAudio tunnel is active (e.g., tcp:hostname:port)
195
+ // This provides working audio over SSH connections
196
+ const hasTunnel = hasPulseAudioTunnel();
197
+
115
198
  // Check if in SSH session
116
199
  const isSSH = process.env.SSH_CONNECTION || process.env.SSH_CLIENT || process.env.SSH_TTY;
117
200
 
118
- // Voiceless: No audio OR in SSH session
119
- if (!hasAudio || isSSH) {
201
+ // Voiceless: No audio devices AND no PulseAudio tunnel
202
+ // (SSH status doesn't matter - what matters is whether audio works)
203
+ if (!hasAudio && !hasTunnel) {
120
204
  return 'VOICELESS';
121
205
  }
122
206
 
123
- // Desktop: Has audio, not SSH, not Termux
207
+ // Desktop: Has working audio (local devices OR PulseAudio tunnel)
208
+ // This includes SSH sessions with PulseAudio tunnels
124
209
  return 'DESKTOP';
125
210
  }
126
211
 
@@ -475,6 +560,9 @@ async function collectConfiguration(options = {}) {
475
560
  const pageOffset = options.pageOffset || 0;
476
561
  const totalPages = options.totalPages || sectionPages;
477
562
 
563
+ // Cache system capabilities to avoid duplicate detection
564
+ let systemInfoCache = null;
565
+
478
566
  console.clear();
479
567
  console.log(chalk.cyan.bold('\n⚙️ Configuration Setup\n'));
480
568
  console.log(chalk.white('Please configure your AgentVibes installation.\n'));
@@ -536,24 +624,76 @@ async function collectConfiguration(options = {}) {
536
624
  }
537
625
  ));
538
626
  }
539
- // Desktop: Standard provider selection
627
+ // Desktop: Smart provider selection with system detection
540
628
  else {
629
+ // Detect system capabilities for smart recommendations (cached to avoid duplicate calls)
630
+ if (!systemInfoCache) {
631
+ systemInfoCache = await detectSystemCapabilities();
632
+ }
633
+ const systemInfo = systemInfoCache;
634
+
635
+ // Detect audio method (local devices vs PulseAudio tunnel)
636
+ const hasLocalAudio = checkAudioDevices();
637
+ const hasTunnel = hasPulseAudioTunnel();
638
+
639
+ // Context-aware header message
640
+ let audioHeader = '';
641
+ let audioSubtext = '';
642
+ if (hasLocalAudio && hasTunnel) {
643
+ audioHeader = chalk.green.bold('🔊 Audio Output Detected!\n\n');
644
+ audioSubtext = chalk.white('Local speakers + PulseAudio tunnel detected. Choose your TTS engine:\n\n');
645
+ } else if (hasTunnel) {
646
+ audioHeader = chalk.blue.bold('🌐 PulseAudio Tunnel Detected!\n\n');
647
+ audioSubtext = chalk.white('Audio will play through your PulseAudio tunnel. Choose your TTS engine:\n\n');
648
+ } else {
649
+ audioHeader = chalk.green.bold('🔊 Audio Output Detected!\n\n');
650
+ audioSubtext = chalk.white('Your system has speakers. Choose your TTS engine:\n\n');
651
+ }
652
+
653
+ // Build recommendation message
654
+ let recommendation = '';
655
+ if (systemInfo.hasGPU && !systemInfo.isMacOS) {
656
+ recommendation = chalk.yellow('💡 Recommendation: Soprano\n') +
657
+ chalk.gray(' Your GPU will run Soprano 2000x faster than CPU!\n') +
658
+ chalk.gray(' Perfect for high-volume TTS or real-time applications.\n\n');
659
+ } else if (systemInfo.lowMemory && !systemInfo.isMacOS) {
660
+ const ramGB = systemInfo.totalRAM / 1024;
661
+ const ramDisplay = ramGB < 1
662
+ ? `${systemInfo.totalRAM}MB`
663
+ : `${Math.floor(ramGB)}GB`;
664
+ recommendation = chalk.yellow('💡 Recommendation: Soprano\n') +
665
+ chalk.gray(` Your system has limited RAM (${ramDisplay}).\n`) +
666
+ chalk.gray(' Soprano uses <1GB vs Piper\'s 2-3GB.\n\n');
667
+ } else if (systemInfo.isMacOS) {
668
+ recommendation = chalk.yellow('💡 Recommendation: macOS Say\n') +
669
+ chalk.gray(' Built-in, zero setup, 100+ voices included.\n') +
670
+ chalk.gray(' Best choice for macOS users.\n\n');
671
+ } else {
672
+ recommendation = chalk.yellow('💡 Recommendation: Piper\n') +
673
+ chalk.gray(' Most versatile: 50+ voices, 18+ languages.\n') +
674
+ chalk.gray(' Great for multi-language projects and variety.\n\n');
675
+ }
676
+
541
677
  console.log(boxen(
542
- chalk.white('Text-to-Speech (TTS) converts Claude\'s text responses into spoken audio.\n\n') +
543
- chalk.white('Choose your Text-to-Speech provider.\n\n') +
544
- (isMacOS ? chalk.yellow('🍎 macOS Say\n') +
545
- chalk.gray(' Built-in to macOS\n') +
546
- chalk.gray(' Zero setup required\n') +
547
- chalk.gray(' • 40+ system voices\n\n') : '') +
678
+ audioHeader +
679
+ audioSubtext +
680
+ recommendation +
681
+ chalk.white('Available Providers:\n\n') +
682
+ (systemInfo.isMacOS ? chalk.yellow('🍎 macOS Say\n') +
683
+ chalk.gray(' • Built-in, zero setup, 100+ voices\n\n') : '') +
684
+ chalk.magenta('⚡ Soprano TTS\n') +
685
+ chalk.gray(' • Ultra-fast: 20x CPU, 2000x GPU\n') +
686
+ chalk.gray(' • 1 premium English voice\n') +
687
+ chalk.gray(' • <1GB memory footprint\n\n') +
548
688
  chalk.green('🆓 Piper TTS\n') +
689
+ chalk.gray(' • 50+ voices, 18+ languages\n') +
549
690
  chalk.gray(' • Free & offline\n') +
550
- chalk.gray(' • 50+ Hugging Face AI voices\n') +
551
691
  chalk.gray(' • Human-like speech quality'),
552
692
  {
553
693
  padding: 1,
554
694
  margin: { top: 0, bottom: 0, left: 0, right: 0 },
555
695
  borderStyle: 'round',
556
- borderColor: 'gray',
696
+ borderColor: 'green',
557
697
  width: 80
558
698
  }
559
699
  ));
@@ -565,41 +705,98 @@ async function collectConfiguration(options = {}) {
565
705
  // VOICELESS SERVER: Prioritize remote audio options
566
706
  if (environment === 'VOICELESS') {
567
707
  providerChoices.push({
568
- name: chalk.green('📱 My phone/tablet via SSH') + chalk.yellow(' (Recommended)'),
569
- value: 'termux-ssh'
708
+ name: chalk.green('📱 AgentVibes Receiver (Text SSH → Device)') + chalk.yellow(' (Recommended)'),
709
+ value: 'termux-ssh',
710
+ short: 'SSH-Remote'
570
711
  });
571
712
  providerChoices.push({
572
- name: chalk.blue('🔊 Another computer via PulseAudio'),
573
- value: 'ssh-pulseaudio'
713
+ name: chalk.blue('🔊 PulseAudio Tunnel (Audio → TCP → Speakers)'),
714
+ value: 'ssh-pulseaudio',
715
+ short: 'PulseAudio'
574
716
  });
575
717
  providerChoices.push({
576
- name: chalk.gray('🔇 No audio (silent mode)'),
577
- value: 'silent'
718
+ name: chalk.gray('🔇 Silent Mode (No TTS)'),
719
+ value: 'silent',
720
+ short: 'Silent'
578
721
  });
579
722
  }
580
723
  // PHONE/TERMUX: Receiver mode or local playback
581
724
  else if (environment === 'PHONE') {
582
725
  providerChoices.push({
583
- name: chalk.green('🎵 Receiver mode') + chalk.gray(' - Receive TTS from remote server'),
584
- value: 'piper-receiver'
726
+ name: chalk.green('📱 Receiver Mode (Remote Server → This Phone)') + chalk.yellow(' (Recommended)'),
727
+ value: 'piper-receiver',
728
+ short: 'Receiver'
585
729
  });
586
730
  providerChoices.push({
587
- name: chalk.blue('🎤 Local playback only') + chalk.gray(' - Use AgentVibes on this device'),
588
- value: 'piper'
731
+ name: chalk.blue('🔊 Local Playback (This Device Only)'),
732
+ value: 'piper',
733
+ short: 'Local'
589
734
  });
590
735
  }
591
- // DESKTOP: Standard provider options
736
+ // DESKTOP: Smart provider ordering
592
737
  else {
593
- if (isMacOS) {
738
+ // Reuse cached system info from earlier detection
739
+ const systemInfo = systemInfoCache || await detectSystemCapabilities();
740
+
741
+ // Smart ordering based on system capabilities
742
+ if (systemInfo.hasGPU && !systemInfo.isMacOS) {
743
+ // GPU detected: Soprano first
744
+ providerChoices.push({
745
+ name: chalk.magenta('⚡ Soprano TTS') + chalk.yellow(' (Recommended for your GPU)') +
746
+ chalk.gray(' - 2000x real-time'),
747
+ value: 'soprano',
748
+ short: 'Soprano'
749
+ });
750
+ providerChoices.push({
751
+ name: chalk.green('🆓 Piper TTS') + chalk.gray(' - 50+ voices, 18+ languages'),
752
+ value: 'piper',
753
+ short: 'Piper'
754
+ });
755
+ } else if (systemInfo.lowMemory && !systemInfo.isMacOS) {
756
+ // Low memory: Soprano first
594
757
  providerChoices.push({
595
- name: chalk.yellow('🍎 macOS Say (Recommended)'),
596
- value: 'macos'
758
+ name: chalk.magenta('⚡ Soprano TTS') + chalk.yellow(' (Best for low memory)') +
759
+ chalk.gray(' - <1GB'),
760
+ value: 'soprano',
761
+ short: 'Soprano'
762
+ });
763
+ providerChoices.push({
764
+ name: chalk.green('🆓 Piper TTS') + chalk.gray(' - 50+ voices (uses 2-3GB RAM)'),
765
+ value: 'piper',
766
+ short: 'Piper'
767
+ });
768
+ } else if (systemInfo.isMacOS) {
769
+ // macOS: System voice first
770
+ providerChoices.push({
771
+ name: chalk.yellow('🍎 macOS Say') + chalk.yellow(' (Recommended)') +
772
+ chalk.gray(' - Zero setup, 100+ built-in voices'),
773
+ value: 'macos',
774
+ short: 'macOS Say'
775
+ });
776
+ providerChoices.push({
777
+ name: chalk.magenta('⚡ Soprano TTS') + chalk.gray(' - Ultra-fast, 1 premium voice'),
778
+ value: 'soprano',
779
+ short: 'Soprano'
780
+ });
781
+ providerChoices.push({
782
+ name: chalk.green('🆓 Piper TTS') + chalk.gray(' - 50+ voices, 18+ languages'),
783
+ value: 'piper',
784
+ short: 'Piper'
785
+ });
786
+ } else {
787
+ // Standard: Piper first (most versatile)
788
+ providerChoices.push({
789
+ name: chalk.green('🆓 Piper TTS') + chalk.yellow(' (Recommended)') +
790
+ chalk.gray(' - 50+ voices, versatile'),
791
+ value: 'piper',
792
+ short: 'Piper'
793
+ });
794
+ providerChoices.push({
795
+ name: chalk.magenta('⚡ Soprano TTS') + chalk.gray(' - Ultra-fast, 1 premium voice'),
796
+ value: 'soprano',
797
+ short: 'Soprano'
597
798
  });
598
799
  }
599
- providerChoices.push({
600
- name: chalk.green('🆓 Piper TTS (Free, Offline)'),
601
- value: 'piper'
602
- });
603
800
  }
604
801
 
605
802
  providerChoices.push(new inquirer.Separator());
@@ -677,8 +874,8 @@ async function collectConfiguration(options = {}) {
677
874
  }
678
875
  }
679
876
 
680
- // If Termux SSH selected, show setup guide
681
- if (config.provider === 'termux-ssh' || config.provider === 'ssh-pulseaudio') {
877
+ // If SSH-Remote selected, show setup guide (NOT PulseAudio!)
878
+ if (config.provider === 'termux-ssh' || config.provider === 'ssh-remote') {
682
879
  console.log('\n' + boxen(
683
880
  chalk.cyan.bold('📱 AgentVibes Receiver Setup\n\n') +
684
881
  chalk.white('What is Receiver Mode?\n') +
@@ -745,12 +942,63 @@ async function collectConfiguration(options = {}) {
745
942
  }
746
943
  }
747
944
 
945
+ // If PulseAudio selected, show different setup (BUG FIX!)
946
+ if (config.provider === 'ssh-pulseaudio' || config.provider === 'pulseaudio') {
947
+ console.log('\n' + boxen(
948
+ chalk.blue.bold('🔊 PulseAudio Tunnel Setup\n\n') +
949
+ chalk.white('What is PulseAudio Tunnel?\n') +
950
+ chalk.gray('Server generates audio and streams it via TCP to your speakers.\n\n') +
951
+ chalk.white('How it Works:\n') +
952
+ chalk.gray('1. Server: Generates TTS audio (Piper/Soprano/macOS Say)\n') +
953
+ chalk.gray('2. Server: Streams AUDIO via TCP tunnel (port 14713)\n') +
954
+ chalk.gray('3. Your device: PulseAudio receives and plays audio\n\n') +
955
+ chalk.white('Requirements:\n') +
956
+ chalk.yellow('⚠️ PulseAudio must be installed on BOTH devices\n') +
957
+ chalk.yellow('⚠️ SSH tunnel or network route to port 14713\n\n') +
958
+ chalk.white('Manual Setup Required:\n') +
959
+ chalk.gray('On Server:\n') +
960
+ chalk.white(' export PULSE_SERVER=tcp:localhost:14713\n') +
961
+ chalk.white(' (Add to ~/.bashrc for persistence)\n\n') +
962
+ chalk.gray('On Your Local Machine:\n') +
963
+ chalk.white(' ssh -R 14713:localhost:4713 your-server\n\n') +
964
+ chalk.cyan('📖 Full guide: ') + chalk.blue('docs/remote-audio-setup.md\n\n') +
965
+ chalk.yellow('💡 Tip: ') + chalk.gray('PulseAudio works best on local networks.\n') +
966
+ chalk.gray(' For mobile/remote, consider SSH-Remote instead.'),
967
+ {
968
+ padding: 1,
969
+ margin: { top: 0, bottom: 0, left: 0, right: 0 },
970
+ borderStyle: 'round',
971
+ borderColor: 'blue',
972
+ width: 80
973
+ }
974
+ ));
975
+ console.log('');
976
+ }
977
+
748
978
  } else if (currentPage === 2) {
749
979
  // Page 3: Voice Selection
980
+ // Provider-aware voice selection introduction
981
+ let voiceIntroMessage = '';
982
+ if (config.provider === 'soprano') {
983
+ voiceIntroMessage = chalk.white('Soprano Voice Configuration\n\n') +
984
+ chalk.gray('Soprano has a single premium neural voice.\n') +
985
+ chalk.gray('Voice details and specifications shown below.');
986
+ } else if (config.provider === 'piper') {
987
+ voiceIntroMessage = chalk.white('Choose a default voice for your AgentVibes.\n\n') +
988
+ chalk.gray('Piper offers 50+ voices in 18+ languages.\n') +
989
+ chalk.gray('You can change this anytime with: ') + chalk.cyan('/agent-vibes:voice switch <name>');
990
+ } else if (config.provider === 'macos') {
991
+ voiceIntroMessage = chalk.white('Choose a default voice for your AgentVibes.\n\n') +
992
+ chalk.gray('macOS includes 100+ built-in voices.\n') +
993
+ chalk.gray('You can change this anytime with: ') + chalk.cyan('/agent-vibes:voice switch <name>');
994
+ } else {
995
+ voiceIntroMessage = chalk.white('Choose a default voice for your AgentVibes.\n\n') +
996
+ chalk.gray('This will be used when no specific voice is configured.\n') +
997
+ chalk.gray('You can change this anytime with: ') + chalk.cyan('/agent-vibes:voice switch <name>');
998
+ }
999
+
750
1000
  console.log(boxen(
751
- chalk.white('Choose a default voice for your AgentVibes.\n\n') +
752
- chalk.gray('This will be used when no specific voice is configured.\n') +
753
- chalk.gray('You can change this anytime with: ') + chalk.cyan('/agent-vibes:voice switch <name>'),
1001
+ voiceIntroMessage,
754
1002
  {
755
1003
  padding: 1,
756
1004
  margin: { top: 0, bottom: 0, left: 0, right: 0 },
@@ -840,6 +1088,36 @@ async function collectConfiguration(options = {}) {
840
1088
  continue;
841
1089
  }
842
1090
 
1091
+ } else if (config.provider === 'soprano') {
1092
+ // Soprano TTS - single voice model
1093
+ console.log(boxen(
1094
+ chalk.magenta.bold('⚡ Soprano TTS Voice\n\n') +
1095
+ chalk.white('Soprano is a single-speaker neural TTS model.\n\n') +
1096
+ chalk.cyan('Voice Details:\n') +
1097
+ chalk.gray(' • Model: ') + chalk.white('Soprano-1.1-80M\n') +
1098
+ chalk.gray(' • Language: ') + chalk.white('English (en_US)\n') +
1099
+ chalk.gray(' • Gender: ') + chalk.white('Female\n') +
1100
+ chalk.gray(' • Quality: ') + chalk.white('Premium neural voice\n') +
1101
+ chalk.gray(' • Speed: ') + chalk.white('20x CPU, 2000x GPU (if available)\n\n') +
1102
+ chalk.yellow('💡 ') + chalk.white('Only one voice available - automatically selected.\n') +
1103
+ chalk.gray(' For multiple voices, consider switching to Piper (50+ voices).'),
1104
+ {
1105
+ padding: 1,
1106
+ margin: { top: 0, bottom: 0, left: 0, right: 0 },
1107
+ borderStyle: 'round',
1108
+ borderColor: 'magenta',
1109
+ width: 80
1110
+ }
1111
+ ));
1112
+
1113
+ // Auto-set the single Soprano voice
1114
+ config.defaultVoice = 'soprano-default';
1115
+ console.log(chalk.green('\n✓ Voice: Soprano-1.1-80M (auto-selected)\n'));
1116
+
1117
+ // Auto-advance to next page
1118
+ currentPage++;
1119
+ continue;
1120
+
843
1121
  } else if (config.provider === 'termux-ssh' || config.provider === 'ssh-pulseaudio') {
844
1122
  // Termux SSH - voices are managed on Android device
845
1123
  console.log(boxen(
@@ -1242,18 +1520,19 @@ function showWelcome() {
1242
1520
  * Shown during install and update commands
1243
1521
  */
1244
1522
  function getReleaseInfoBoxen() {
1245
- return chalk.cyan.bold('📦 AgentVibes v3.3.0 - Remote Audio Revolution\n\n') +
1523
+ return chalk.cyan.bold('📦 AgentVibes v3.4.0 - Soprano TTS & Security Hardening\n\n') +
1246
1524
  chalk.green.bold('🎙️ WHAT\'S NEW:\n\n') +
1247
- chalk.cyan('AgentVibes v3.3.0 unleashes the platform across remote servers, mobile devices, and OpenClaw\n') +
1248
- chalk.cyan('deployments. Stream TTS from voiceless servers to your phone via SSH-PulseAudio tunneling,\n') +
1249
- chalk.cyan('run native Piper on Android/Termux, and manage audio with intelligent size-based auto-cleanup.\n') +
1250
- chalk.cyan('Perfect for turning any server into a voice-enabled AI assistant.\n\n') +
1525
+ chalk.cyan('AgentVibes v3.4.0 introduces Soprano TTS - an 80M parameter neural provider with 20x CPU\n') +
1526
+ chalk.cyan('and 2000x GPU acceleration - plus comprehensive security hardening (timeouts, bounds checking,\n') +
1527
+ chalk.cyan('NaN validation) and intelligent environment detection that recognizes PulseAudio tunnels as\n') +
1528
+ chalk.cyan('working audio for remote scenarios. Enhanced installer provides GPU-based recommendations.\n\n') +
1529
+ chalk.yellow('🙏 Special thanks to community member @nathanchase for contributing Soprano TTS!\n\n') +
1251
1530
  chalk.green.bold('✨ KEY HIGHLIGHTS:\n\n') +
1252
- chalk.gray(' 📱 AgentVibes Receiver - Stream audio from voiceless servers to phones/laptops\n') +
1253
- chalk.gray(' 🌐 Voiceless Server Support - Generate TTS on cloud servers (AWS, GCP, Azure)\n') +
1254
- chalk.gray(' 🎤 Voice Management Skill - 50+ voices, multi-provider support for OpenClaw\n') +
1255
- chalk.gray(' 🛡️ Smart Auto-Cleanup - Size-based cache management prevents disk bloat\n') +
1256
- chalk.gray(' 🎨 Enhanced Display - Color-coded output with real-time cache metrics\n\n') +
1531
+ chalk.gray(' Soprano TTS Provider - Ultra-fast neural TTS with GPU acceleration (thanks @nathanchase!)\n') +
1532
+ chalk.gray(' 🛡️ Security Hardening - 9.5/10 score with timeouts and comprehensive validation\n') +
1533
+ chalk.gray(' 🌐 Environment Intelligence - PulseAudio tunnel auto-detection for SSH scenarios\n') +
1534
+ chalk.gray(' 🎯 Smart Recommendations - GPU/RAM-based provider suggestions\n') +
1535
+ chalk.gray(' 🧪 260/260 Tests Passing - Complete test coverage with fixed edge cases\n\n') +
1257
1536
  chalk.gray('📖 Full Release Notes: RELEASE_NOTES.md\n') +
1258
1537
  chalk.gray('🌐 Website: https://agentvibes.org\n') +
1259
1538
  chalk.gray('📦 Repository: https://github.com/paulpreibisch/AgentVibes\n\n') +
@@ -1572,6 +1851,12 @@ async function promptProviderSelection(options) {
1572
1851
  value: 'piper',
1573
1852
  });
1574
1853
 
1854
+ // Soprano TTS (all platforms)
1855
+ choices.push({
1856
+ name: chalk.magenta('⚡ Soprano TTS (Ultra-Fast)') + chalk.gray(' - 1 premium voice, 20x faster, <1GB memory'),
1857
+ value: 'soprano',
1858
+ });
1859
+
1575
1860
  // Termux SSH (all platforms)
1576
1861
  choices.push({
1577
1862
  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'),
@@ -3351,7 +3636,14 @@ async function install(options = {}) {
3351
3636
  const preInstallPages = [];
3352
3637
 
3353
3638
  // Page 1: Configuration Summary
3354
- const providerLabels = { piper: 'Piper TTS', macos: 'macOS Say', 'termux-ssh': 'Termux SSH (Android)' };
3639
+ const providerLabels = {
3640
+ piper: 'Piper TTS',
3641
+ macos: 'macOS Say',
3642
+ soprano: 'Soprano TTS',
3643
+ 'termux-ssh': 'Termux SSH (Android)',
3644
+ 'ssh-pulseaudio': 'PulseAudio Tunnel',
3645
+ pulseaudio: 'PulseAudio Tunnel'
3646
+ };
3355
3647
  const reverbLabels = {
3356
3648
  off: 'Off',
3357
3649
  light: 'Light',
@@ -1 +0,0 @@
1
- 0.30
@@ -1 +0,0 @@
1
- enabled
@@ -1 +0,0 @@
1
- enabled
@@ -1 +0,0 @@
1
- 1.3
@@ -1 +0,0 @@
1
- high