agentvibes 4.2.0 → 4.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (219) hide show
  1. package/.agentvibes/bmad/bmad-voices.md +69 -69
  2. package/.agentvibes/config.json +12 -0
  3. package/.claude/activation-instructions +54 -54
  4. package/.claude/audio/tracks/README.md +52 -52
  5. package/.claude/commands/agent-vibes/add.md +21 -21
  6. package/.claude/commands/agent-vibes/agent-vibes.md +101 -101
  7. package/.claude/commands/agent-vibes/agent.md +79 -79
  8. package/.claude/commands/agent-vibes/background-music.md +111 -111
  9. package/.claude/commands/agent-vibes/bmad.md +198 -198
  10. package/.claude/commands/agent-vibes/clean.md +18 -18
  11. package/.claude/commands/agent-vibes/cleanup.md +18 -18
  12. package/.claude/commands/agent-vibes/commands.json +145 -145
  13. package/.claude/commands/agent-vibes/effects.md +97 -97
  14. package/.claude/commands/agent-vibes/get.md +9 -9
  15. package/.claude/commands/agent-vibes/hide.md +91 -91
  16. package/.claude/commands/agent-vibes/language.md +23 -23
  17. package/.claude/commands/agent-vibes/learn.md +67 -67
  18. package/.claude/commands/agent-vibes/list.md +13 -13
  19. package/.claude/commands/agent-vibes/mute.md +37 -37
  20. package/.claude/commands/agent-vibes/preview.md +17 -17
  21. package/.claude/commands/agent-vibes/provider.md +68 -68
  22. package/.claude/commands/agent-vibes/replay-target.md +14 -14
  23. package/.claude/commands/agent-vibes/sample.md +12 -12
  24. package/.claude/commands/agent-vibes/set-favorite-voice.md +84 -84
  25. package/.claude/commands/agent-vibes/set-pretext.md +65 -65
  26. package/.claude/commands/agent-vibes/set-speed.md +41 -41
  27. package/.claude/commands/agent-vibes/show.md +84 -84
  28. package/.claude/commands/agent-vibes/switch.md +87 -87
  29. package/.claude/commands/agent-vibes/target-voice.md +26 -26
  30. package/.claude/commands/agent-vibes/target.md +30 -30
  31. package/.claude/commands/agent-vibes/translate.md +68 -68
  32. package/.claude/commands/agent-vibes/unmute.md +45 -45
  33. package/.claude/commands/agent-vibes/verbosity.md +89 -89
  34. package/.claude/commands/agent-vibes/whoami.md +7 -7
  35. package/.claude/commands/agent-vibes-bmad-voices.md +117 -117
  36. package/.claude/commands/agent-vibes-rdp.md +24 -24
  37. package/.claude/config/agentvibes.json +1 -0
  38. package/.claude/config/audio-effects.cfg +2 -2
  39. package/.claude/config/audio-effects.cfg.sample +52 -52
  40. package/.claude/config/background-music-volume.txt +1 -0
  41. package/.claude/config/intro-text.txt +1 -0
  42. package/.claude/config/piper-speech-rate.txt +4 -0
  43. package/.claude/config/piper-target-speech-rate.txt +1 -0
  44. package/.claude/config/reverb-level.txt +1 -0
  45. package/.claude/config/tts-speech-rate.txt +4 -0
  46. package/.claude/config/tts-target-speech-rate.txt +1 -0
  47. package/.claude/docs/TERMUX_SETUP.md +408 -408
  48. package/.claude/github-star-reminder.txt +1 -1
  49. package/.claude/hooks/README-TTS-QUEUE.md +135 -135
  50. package/.claude/hooks/audio-cache-utils.sh +246 -246
  51. package/.claude/hooks/audio-processor.sh +433 -433
  52. package/.claude/hooks/background-music-manager.sh +404 -404
  53. package/.claude/hooks/bmad-speak-enhanced.sh +165 -165
  54. package/.claude/hooks/bmad-speak.sh +269 -269
  55. package/.claude/hooks/bmad-tts-injector.sh +568 -568
  56. package/.claude/hooks/bmad-voice-manager.sh +928 -928
  57. package/.claude/hooks/clawdbot-receiver-SECURE.sh +129 -129
  58. package/.claude/hooks/clawdbot-receiver.sh +107 -107
  59. package/.claude/hooks/clean-audio-cache.sh +22 -22
  60. package/.claude/hooks/cleanup-cache.sh +106 -106
  61. package/.claude/hooks/configure-rdp-mode.sh +137 -137
  62. package/.claude/hooks/download-extra-voices.sh +244 -244
  63. package/.claude/hooks/effects-manager.sh +268 -268
  64. package/.claude/hooks/github-star-reminder.sh +154 -154
  65. package/.claude/hooks/language-manager.sh +362 -362
  66. package/.claude/hooks/learn-manager.sh +492 -492
  67. package/.claude/hooks/macos-voice-manager.sh +205 -205
  68. package/.claude/hooks/migrate-background-music.sh +125 -125
  69. package/.claude/hooks/migrate-to-agentvibes.sh +161 -161
  70. package/.claude/hooks/optimize-background-music.sh +87 -87
  71. package/.claude/hooks/path-resolver.sh +60 -60
  72. package/.claude/hooks/personality-manager.sh +448 -448
  73. package/.claude/hooks/piper-download-voices.sh +225 -225
  74. package/.claude/hooks/piper-installer.sh +292 -292
  75. package/.claude/hooks/piper-multispeaker-registry.sh +171 -171
  76. package/.claude/hooks/piper-voice-manager.sh +24 -3
  77. package/.claude/hooks/play-tts-agentvibes-receiver-for-voiceless-connections.sh +90 -90
  78. package/.claude/hooks/play-tts-enhanced.sh +105 -105
  79. package/.claude/hooks/play-tts-macos.sh +368 -368
  80. package/.claude/hooks/play-tts-piper.sh +679 -679
  81. package/.claude/hooks/play-tts-soprano.sh +356 -356
  82. package/.claude/hooks/play-tts-ssh-remote.sh +167 -167
  83. package/.claude/hooks/play-tts-termux-ssh.sh +169 -169
  84. package/.claude/hooks/play-tts.sh +301 -301
  85. package/.claude/hooks/prepare-release.sh +54 -54
  86. package/.claude/hooks/provider-commands.sh +617 -617
  87. package/.claude/hooks/provider-manager.sh +399 -399
  88. package/.claude/hooks/replay-target-audio.sh +95 -95
  89. package/.claude/hooks/requirements.txt +6 -6
  90. package/.claude/hooks/sentiment-manager.sh +201 -201
  91. package/.claude/hooks/session-start-tts.sh +81 -81
  92. package/.claude/hooks/soprano-gradio-synth.py +139 -139
  93. package/.claude/hooks/speed-manager.sh +291 -291
  94. package/.claude/hooks/stop-tts.sh +84 -84
  95. package/.claude/hooks/termux-installer.sh +261 -261
  96. package/.claude/hooks/translate-manager.sh +341 -341
  97. package/.claude/hooks/translator.py +237 -237
  98. package/.claude/hooks/tts-queue-worker.sh +145 -145
  99. package/.claude/hooks/tts-queue.sh +165 -165
  100. package/.claude/hooks/verbosity-manager.sh +178 -178
  101. package/.claude/hooks/voice-manager.sh +548 -548
  102. package/.claude/hooks-windows/audio-cache-utils.ps1 +119 -119
  103. package/.claude/hooks-windows/background-music-manager.ps1 +348 -0
  104. package/.claude/hooks-windows/clean-audio-cache.ps1 +53 -0
  105. package/.claude/hooks-windows/download-extra-voices.ps1 +185 -0
  106. package/.claude/hooks-windows/effects-manager.ps1 +294 -0
  107. package/.claude/hooks-windows/language-manager.ps1 +193 -0
  108. package/.claude/hooks-windows/learn-manager.ps1 +241 -0
  109. package/.claude/hooks-windows/personality-manager.ps1 +266 -0
  110. package/.claude/hooks-windows/play-tts-piper.ps1 +209 -0
  111. package/.claude/hooks-windows/play-tts-sapi.ps1 +108 -0
  112. package/.claude/hooks-windows/play-tts-soprano.ps1 +159 -158
  113. package/.claude/hooks-windows/play-tts-windows-piper.ps1 +50 -5
  114. package/.claude/hooks-windows/play-tts-windows-sapi.ps1 +108 -108
  115. package/.claude/hooks-windows/play-tts.ps1 +344 -266
  116. package/.claude/hooks-windows/provider-manager.ps1 +29 -10
  117. package/.claude/hooks-windows/session-start-tts.ps1 +124 -124
  118. package/.claude/hooks-windows/soprano-gradio-synth.py +153 -153
  119. package/.claude/hooks-windows/speed-manager.ps1 +166 -0
  120. package/.claude/hooks-windows/verbosity-manager.ps1 +119 -0
  121. package/.claude/hooks-windows/voice-manager-windows.ps1 +92 -8
  122. package/.claude/output-styles/agent-vibes.md +202 -202
  123. package/.claude/personalities/angry.md +14 -14
  124. package/.claude/personalities/annoying.md +14 -14
  125. package/.claude/personalities/crass.md +14 -14
  126. package/.claude/personalities/dramatic.md +14 -14
  127. package/.claude/personalities/dry-humor.md +50 -50
  128. package/.claude/personalities/flirty.md +20 -20
  129. package/.claude/personalities/funny.md +14 -14
  130. package/.claude/personalities/grandpa.md +32 -32
  131. package/.claude/personalities/millennial.md +14 -14
  132. package/.claude/personalities/moody.md +14 -14
  133. package/.claude/personalities/normal.md +16 -16
  134. package/.claude/personalities/pirate.md +14 -14
  135. package/.claude/personalities/poetic.md +14 -14
  136. package/.claude/personalities/professional.md +14 -14
  137. package/.claude/personalities/rapper.md +55 -55
  138. package/.claude/personalities/robot.md +14 -14
  139. package/.claude/personalities/sarcastic.md +38 -38
  140. package/.claude/personalities/sassy.md +14 -14
  141. package/.claude/personalities/surfer-dude.md +14 -14
  142. package/.claude/personalities/zen.md +14 -14
  143. package/.claude/settings.json +15 -15
  144. package/.claude/verbosity.txt +1 -1
  145. package/.clawdbot/README.md +105 -105
  146. package/.clawdbot/skill/SKILL.md +241 -241
  147. package/.mcp.json +12 -0
  148. package/CLAUDE.md +170 -170
  149. package/README.md +2029 -2007
  150. package/RELEASE_NOTES.md +1310 -1203
  151. package/WINDOWS-SETUP.md +208 -208
  152. package/bin/agent-vibes +39 -39
  153. package/bin/agentvibes-voice-browser.js +1840 -1840
  154. package/bin/agentvibes.js +48 -2
  155. package/bin/mcp-server.js +121 -121
  156. package/bin/mcp-server.sh +206 -206
  157. package/bin/test-bmad-pr +78 -78
  158. package/mcp-server/QUICK_START.md +203 -203
  159. package/mcp-server/README.md +345 -345
  160. package/mcp-server/WINDOWS_SETUP.md +260 -260
  161. package/mcp-server/docs/troubleshooting-audio.md +313 -313
  162. package/mcp-server/examples/claude_desktop_config.json +11 -11
  163. package/mcp-server/examples/claude_desktop_config_piper.json +9 -9
  164. package/mcp-server/examples/custom_instructions.md +169 -169
  165. package/mcp-server/install-deps.js +130 -130
  166. package/mcp-server/pyproject.toml +52 -52
  167. package/mcp-server/requirements.txt +2 -2
  168. package/mcp-server/server.py +1465 -1453
  169. package/mcp-server/test_server.py +395 -395
  170. package/mcp-server/test_windows_script_parity.py +336 -0
  171. package/package.json +110 -110
  172. package/setup-windows.ps1 +815 -815
  173. package/src/bmad-detector.js +71 -71
  174. package/src/cli/list-personalities.js +110 -110
  175. package/src/cli/list-voices.js +114 -114
  176. package/src/commands/bmad-voices.js +394 -394
  177. package/src/commands/install-mcp.js +476 -476
  178. package/src/console/app.js +824 -824
  179. package/src/console/audio-env.js +20 -1
  180. package/src/console/brand-colors.js +13 -13
  181. package/src/console/constants/personalities.js +44 -44
  182. package/src/console/footer-config.js +50 -50
  183. package/src/console/modals/modal-overlay.js +247 -247
  184. package/src/console/navigation.js +62 -62
  185. package/src/console/tabs/agents-tab.js +1684 -1516
  186. package/src/console/tabs/help-tab.js +261 -261
  187. package/src/console/tabs/install-tab.js +1007 -991
  188. package/src/console/tabs/music-tab.js +22 -8
  189. package/src/console/tabs/placeholder-tab.js +53 -53
  190. package/src/console/tabs/readme-tab.js +267 -267
  191. package/src/console/tabs/receiver-tab.js +1472 -1212
  192. package/src/console/tabs/settings-tab.js +208 -84
  193. package/src/console/tabs/voices-tab.js +100 -21
  194. package/src/console/widgets/destroy-list.js +25 -25
  195. package/src/console/widgets/format-utils.js +89 -89
  196. package/src/console/widgets/notice.js +55 -55
  197. package/src/console/widgets/personality-picker.js +185 -185
  198. package/src/console/widgets/reverb-picker.js +94 -94
  199. package/src/console/widgets/track-picker.js +285 -285
  200. package/src/installer/music-file-input.js +304 -304
  201. package/src/installer.js +5895 -5829
  202. package/src/services/agent-voice-store.js +423 -423
  203. package/src/services/config-service.js +264 -264
  204. package/src/services/navigation-service.js +123 -123
  205. package/src/services/provider-service.js +143 -132
  206. package/src/services/verbosity-service.js +157 -157
  207. package/src/utils/audio-duration-validator.js +298 -298
  208. package/src/utils/audio-format-validator.js +277 -277
  209. package/src/utils/dependency-checker.js +469 -466
  210. package/src/utils/file-ownership-verifier.js +358 -358
  211. package/src/utils/list-formatter.js +194 -194
  212. package/src/utils/music-file-validator.js +285 -285
  213. package/src/utils/preview-list-prompt.js +136 -136
  214. package/src/utils/provider-validator.js +96 -12
  215. package/src/utils/secure-music-storage.js +412 -412
  216. package/templates/agentvibes-receiver.sh +482 -482
  217. package/templates/audio/welcome-music.mp3 +0 -0
  218. package/voice-assignments.json +8244 -8244
  219. package/.claude/config/background-music-position.txt +0 -1
@@ -29,6 +29,35 @@ import { formatTrackName as _sharedFormatTrackName, formatReverbState as _shared
29
29
  import { showNotice as _showNoticeWidget } from '../widgets/notice.js';
30
30
 
31
31
  const IS_TEST = process.env.AGENTVIBES_TEST_MODE === 'true';
32
+ const _IS_WINDOWS = process.platform === 'win32' && !process.env.WSL_DISTRO_NAME;
33
+
34
+ /** Resolve piper binary path — uses exe on Windows, 'piper' in PATH on Unix */
35
+ function _resolvePiperBin() {
36
+ if (_IS_WINDOWS) {
37
+ const lad = process.env.LOCALAPPDATA ||
38
+ (process.env.USERPROFILE ? path.join(process.env.USERPROFILE, 'AppData', 'Local') : null);
39
+ if (lad) {
40
+ // Standalone binary install
41
+ const exe = path.join(lad, 'Programs', 'Piper', 'piper.exe');
42
+ if (fs.existsSync(exe)) return exe;
43
+ // pip-installed piper (Python Scripts directory)
44
+ const pyScripts = path.join(lad, 'Programs', 'Python');
45
+ try {
46
+ const pyDirs = fs.readdirSync(pyScripts).filter(d => d.startsWith('Python'));
47
+ for (const d of pyDirs) {
48
+ const pipExe = path.join(pyScripts, d, 'Scripts', 'piper.exe');
49
+ if (fs.existsSync(pipExe)) return pipExe;
50
+ }
51
+ } catch { /* no Python installs */ }
52
+ }
53
+ }
54
+ return 'piper';
55
+ }
56
+
57
+ /** Build spawn options with Windows-safe defaults (no visible console, no detached) */
58
+ function _spawnOpts(env, extraOpts = {}) {
59
+ return { stdio: 'ignore', detached: !_IS_WINDOWS, windowsHide: true, env, ...extraOpts };
60
+ }
32
61
 
33
62
  // Sanitize strings before passing as env vars to shell commands.
34
63
  // Removes characters that could cause shell injection when expanded inside sh -c.
@@ -51,19 +80,19 @@ const _modalTitle = (text) => ` {${BRAND_PINK}-fg}${text}{/${BRAND_PINK}-fg} `;
51
80
 
52
81
  const COLORS = {
53
82
  contentBg: '#0a0e1a', // Near-black content background
54
- sectionHdr: '#7986cb', // Light blue section dividers
83
+ sectionHdr: 'bright-cyan', // Matches header "Agent" color
55
84
  labelFg: '#e3f2fd', // Light blue text — labels
56
85
  valueFg: '#ffff00', // Yellow — current values
57
86
  btnDefault: '#37474f', // Dark slate — default button bg
58
- btnFocus: '#00e5ff', // Cyan — focused button bg
59
- btnFocusFg: '#000000', // Black — focused button text
87
+ btnFocus: '#2e7d32', // Green — focused/selected button bg
88
+ btnFocusFg: '#ffffff', // White — focused button text
60
89
  btnPress: '#ff00ff', // Magenta — pressed button bg
61
90
  btnChange: '#37474f', // Dark slate — Change buttons
62
91
  btnTest: '#37474f', // Dark slate — Test buttons
63
92
  btnEdit: '#37474f', // Dark slate — Edit buttons
64
93
  btnEnableOn: '#37474f', // Dark slate — Enabled toggle
65
94
  btnEnableOff: '#37474f', // Dark slate — Disabled toggle
66
- borderFg: '#7986cb', // Light blue — borders
95
+ borderFg: 'bright-cyan', // Matches section headers
67
96
  footerBg: '#2196f3', // Blue — settings footer
68
97
  noticeFg: '#90a4ae', // Gray — stub notice text
69
98
  };
@@ -413,9 +442,14 @@ export function createSettingsTab(screen, services) {
413
442
  `play "${trackPath}" repeat 9999 vol ${volFraction}`,
414
443
  `mpg123 -q --loop -1 "${trackPath}"`,
415
444
  ].join(' 2>/dev/null || ') + ' 2>/dev/null';
416
- _testMusicProc = spawn('sh', ['-c', musicCmd], {
417
- stdio: 'ignore', detached: true, env: _testEnv,
418
- });
445
+ if (_IS_WINDOWS) {
446
+ const _mp3P = detectMp3Player(_testEnv);
447
+ _testMusicProc = _mp3P
448
+ ? spawn(_mp3P.bin, _mp3P.args(trackPath), _spawnOpts(_testEnv))
449
+ : null;
450
+ } else {
451
+ _testMusicProc = spawn('sh', ['-c', musicCmd], _spawnOpts(_testEnv));
452
+ }
419
453
  _testMusicProc.unref();
420
454
  }
421
455
  }
@@ -463,7 +497,7 @@ export function createSettingsTab(screen, services) {
463
497
  `soprano "$_AV_PHRASE" -o "$_AV_WAV"`,
464
498
  ].join(' || ');
465
499
  synthProc = spawn('sh', ['-c', cmd], {
466
- stdio: 'ignore', detached: true, env: sopranoEnv,
500
+ stdio: 'ignore', detached: !_IS_WINDOWS, windowsHide: true, env: sopranoEnv,
467
501
  });
468
502
  } else {
469
503
  const voiceId = providerService.getActiveVoiceId();
@@ -476,8 +510,8 @@ export function createSettingsTab(screen, services) {
476
510
  }
477
511
  const _piperArgs = ['--model', voicePath, '--output_file', tempWav];
478
512
  if (_ms.speakerId != null) _piperArgs.push('--speaker', String(_ms.speakerId));
479
- synthProc = spawn('piper', _piperArgs, {
480
- stdio: ['pipe', 'ignore', 'ignore'], detached: true, env: _testEnv,
513
+ synthProc = spawn(_resolvePiperBin(), _piperArgs, {
514
+ stdio: ['pipe', 'ignore', 'ignore'], detached: !_IS_WINDOWS, windowsHide: true, env: _testEnv,
481
515
  });
482
516
  synthProc.stdin.write(ttsInput + '\n');
483
517
  synthProc.stdin.end();
@@ -528,7 +562,7 @@ export function createSettingsTab(screen, services) {
528
562
  _setTestBtnsLabel('■ Stop');
529
563
  const _wavPlayer1 = detectWavPlayer(_testEnv);
530
564
  const playProc = _wavPlayer1
531
- ? spawn(_wavPlayer1.bin, _wavPlayer1.args(wavToPlay), { stdio: 'ignore', detached: true, env: _testEnv })
565
+ ? spawn(_wavPlayer1.bin, _wavPlayer1.args(wavToPlay), _spawnOpts(_testEnv))
532
566
  : null;
533
567
  if (!playProc) { _killTest(); _restoreTestBtnsLabels(); return; }
534
568
  _testVoiceProc = playProc;
@@ -633,9 +667,14 @@ export function createSettingsTab(screen, services) {
633
667
  `mpg123 -q "${trackPath}"`,
634
668
  ].join(' 2>/dev/null || ') + ' 2>/dev/null';
635
669
 
636
- _musicTestProc = spawn('sh', ['-c', cmd], {
637
- stdio: 'ignore', detached: true, env: _testEnv,
638
- });
670
+ if (_IS_WINDOWS) {
671
+ const _mp3P2 = detectMp3Player(_testEnv);
672
+ _musicTestProc = _mp3P2
673
+ ? spawn(_mp3P2.bin, _mp3P2.args(trackPath), _spawnOpts(_testEnv))
674
+ : null;
675
+ } else {
676
+ _musicTestProc = spawn('sh', ['-c', cmd], _spawnOpts(_testEnv));
677
+ }
639
678
  _musicTestProc.unref();
640
679
  musicTestBtn.setContent('■ Stop');
641
680
  screen.render();
@@ -728,7 +767,7 @@ export function createSettingsTab(screen, services) {
728
767
  content: lbl, width: lbl.length, height: 1,
729
768
  top: 0, left: _xOff,
730
769
  keys: true, focusable: true,
731
- style: { fg: '#00e5ff', bg: '#263238' },
770
+ style: { fg: 'bright-cyan', bg: '#263238' },
732
771
  });
733
772
  _subTabItemsMap[id] = item;
734
773
  _xOff += lbl.length;
@@ -742,7 +781,7 @@ export function createSettingsTab(screen, services) {
742
781
  item.style.bg = '#0288d1'; // light blue — active tab
743
782
  item.style.bold = true;
744
783
  } else {
745
- item.style.fg = '#00e5ff';
784
+ item.style.fg = 'bright-cyan';
746
785
  item.style.bg = '#263238';
747
786
  item.style.bold = false;
748
787
  }
@@ -776,12 +815,24 @@ export function createSettingsTab(screen, services) {
776
815
  });
777
816
  }
778
817
 
818
+ // -------------------------------------------------------------------------
819
+ // Section header: ── Provider & Voice ──
820
+
821
+ const providerVoiceHeader = blessed.text({
822
+ parent: box,
823
+ top: 3,
824
+ left: 1,
825
+ content: '{bright-cyan-fg} 🎤 Provider & Voice {/bright-cyan-fg}',
826
+ tags: true,
827
+ style: { bg: COLORS.contentBg },
828
+ });
829
+
779
830
  // -------------------------------------------------------------------------
780
831
  // Provider row: label + value + [Switch] button
781
832
 
782
833
  const providerLabel = blessed.text({
783
834
  parent: box,
784
- top: 3,
835
+ top: 5,
785
836
  left: 6,
786
837
  content: 'Provider:',
787
838
  style: { fg: COLORS.labelFg, bg: COLORS.contentBg },
@@ -789,7 +840,7 @@ export function createSettingsTab(screen, services) {
789
840
 
790
841
  const providerValue = blessed.text({
791
842
  parent: box,
792
- top: 3,
843
+ top: 5,
793
844
  left: 22,
794
845
  width: 26, // truncate before [Switch] at left:40
795
846
  wrap: false,
@@ -805,7 +856,7 @@ export function createSettingsTab(screen, services) {
805
856
  screen.render();
806
857
  }, _restoreFocus);
807
858
  });
808
- switchBtn.top = 3;
859
+ switchBtn.top = 5;
809
860
  switchBtn.left = 52;
810
861
 
811
862
  // -------------------------------------------------------------------------
@@ -813,7 +864,7 @@ export function createSettingsTab(screen, services) {
813
864
 
814
865
  const voiceLabel = blessed.text({
815
866
  parent: box,
816
- top: 5,
867
+ top: 7,
817
868
  left: 6,
818
869
  content: 'Current Voice:',
819
870
  style: { fg: COLORS.labelFg, bg: COLORS.contentBg },
@@ -821,7 +872,7 @@ export function createSettingsTab(screen, services) {
821
872
 
822
873
  const voiceValue = blessed.text({
823
874
  parent: box,
824
- top: 5,
875
+ top: 7,
825
876
  left: 22,
826
877
  width: 26, // truncate before [Change] at left:40
827
878
  wrap: false,
@@ -837,7 +888,7 @@ export function createSettingsTab(screen, services) {
837
888
  screen.render();
838
889
  }, _restoreFocus);
839
890
  }, { bg: COLORS.btnChange });
840
- changeBtn.top = 5;
891
+ changeBtn.top = 7;
841
892
  changeBtn.left = 52;
842
893
 
843
894
  const playBtn = _createButton(box, screen, '▶ Play', COLORS, () => {
@@ -865,14 +916,19 @@ export function createSettingsTab(screen, services) {
865
916
  if (!_samplePlaying) { try { fs.unlinkSync(tempWav); } catch {} return; }
866
917
  if (code !== 0) {
867
918
  _killSample(); playBtn.setContent('▶ Play'); _focusButton(playBtn);
919
+ _showNotice(screen, 'Voice synthesis failed — check voice model');
868
920
  try { fs.unlinkSync(tempWav); } catch {}
869
921
  return;
870
922
  }
871
923
  playBtn.setContent('■ Stop');
872
924
  screen.render();
873
925
  const _wavPlayer2 = detectWavPlayer(_sampleEnv);
874
- if (!_wavPlayer2) { _stopSpinner(); _killSample(); playBtn.setContent('▶ Play'); screen.render(); return; }
875
- const playProc = spawn(_wavPlayer2.bin, _wavPlayer2.args(tempWav), { stdio: 'ignore', detached: true, env: _sampleEnv });
926
+ if (!_wavPlayer2) {
927
+ _stopSpinner(); _killSample(); playBtn.setContent('▶ Play');
928
+ _showNotice(screen, 'No audio player found — install ffplay, sox, or mpv');
929
+ screen.render(); return;
930
+ }
931
+ const playProc = spawn(_wavPlayer2.bin, _wavPlayer2.args(tempWav), _spawnOpts(_sampleEnv));
876
932
  _sampleProcess = playProc;
877
933
  const _done = () => { _killSample(); playBtn.setContent('▶ Play'); _focusButton(playBtn); try { fs.unlinkSync(tempWav); } catch {} };
878
934
  playProc.on('exit', _done);
@@ -973,7 +1029,7 @@ export function createSettingsTab(screen, services) {
973
1029
  ].join(' || ');
974
1030
 
975
1031
  const soprano = spawn('sh', ['-c', cmd], {
976
- stdio: 'ignore', detached: true, env: sopranoEnv,
1032
+ stdio: 'ignore', detached: !_IS_WINDOWS, windowsHide: true, env: sopranoEnv,
977
1033
  });
978
1034
  _sampleProcess = soprano;
979
1035
  soprano.on('exit', (code) => {
@@ -986,31 +1042,67 @@ export function createSettingsTab(screen, services) {
986
1042
  // Piper (default): pipe text via stdin
987
1043
  _startSpinner(playBtn, 'Synthesizing…');
988
1044
  const voiceId = providerService.getActiveVoiceId();
989
- if (!voiceId) { _stopSpinner(); _killSample(); playBtn.setContent('▶ Play'); screen.render(); return; }
1045
+ if (!voiceId) {
1046
+ _stopSpinner(); _killSample(); playBtn.setContent('▶ Play');
1047
+ _showNotice(screen, 'No voice selected — choose a voice first');
1048
+ screen.render(); return;
1049
+ }
990
1050
  const _ms2 = parseMultiSpeaker(voiceId);
991
1051
  const voicePath = path.resolve(PIPER_VOICES_DIR, _ms2.model + '.onnx');
992
1052
  const safeBase = path.resolve(PIPER_VOICES_DIR);
993
1053
  if (!voicePath.startsWith(safeBase + path.sep) && voicePath !== safeBase) {
994
- _stopSpinner(); _killSample(); playBtn.setContent('▶ Play'); screen.render(); return;
1054
+ _stopSpinner(); _killSample(); playBtn.setContent('▶ Play');
1055
+ _showNotice(screen, 'Invalid voice path');
1056
+ screen.render(); return;
1057
+ }
1058
+ const piperBin2 = _resolvePiperBin();
1059
+ if (piperBin2 === 'piper') {
1060
+ // Bare command — verify it exists in PATH before spawning
1061
+ const whichCmd = _IS_WINDOWS ? 'where' : 'which';
1062
+ const whichResult = spawnSync(whichCmd, [_IS_WINDOWS ? 'piper.exe' : 'piper'], { stdio: 'pipe', env: _sampleEnv });
1063
+ if (whichResult.status !== 0) {
1064
+ _stopSpinner(); _killSample(); playBtn.setContent('▶ Play');
1065
+ _showNotice(screen, 'Piper not installed — run the installer or: pip install piper-tts');
1066
+ _focusButton(playBtn); screen.render(); return;
1067
+ }
995
1068
  }
996
1069
  const _piperArgs2 = ['--model', voicePath, '--output_file', tempWav];
997
1070
  if (_ms2.speakerId != null) _piperArgs2.push('--speaker', String(_ms2.speakerId));
998
- const piper = spawn('piper', _piperArgs2, {
999
- stdio: ['pipe', 'ignore', 'ignore'], detached: true, env: _sampleEnv,
1071
+ const piper = spawn(piperBin2, _piperArgs2, {
1072
+ stdio: ['pipe', 'ignore', 'pipe'], detached: !_IS_WINDOWS, windowsHide: true, env: _sampleEnv,
1000
1073
  });
1074
+ let _piperStderr = '';
1075
+ piper.stderr.on('data', (d) => { _piperStderr += d.toString(); });
1001
1076
  piper.stdin.write(phrase + '\n');
1002
1077
  piper.stdin.end();
1003
1078
  _sampleProcess = piper;
1004
- piper.on('exit', _onSynthDone);
1005
- piper.on('error', () => { _stopSpinner(); _killSample(); playBtn.setContent('▶ Play'); _focusButton(playBtn); });
1079
+ piper.on('exit', (code) => {
1080
+ if (code !== 0 && _piperStderr) {
1081
+ // Python tracebacks: actual error is the LAST non-empty line
1082
+ const lines = _piperStderr.split('\n').map(l => l.trim()).filter(Boolean);
1083
+ const errLine = lines[lines.length - 1] || lines[0] || 'unknown error';
1084
+ _stopSpinner();
1085
+ if (!_samplePlaying) { try { fs.unlinkSync(tempWav); } catch {} return; }
1086
+ _killSample(); playBtn.setContent('▶ Play'); _focusButton(playBtn);
1087
+ _showNotice(screen, errLine.length > 100 ? errLine.substring(0, 97) + '…' : errLine);
1088
+ try { fs.unlinkSync(tempWav); } catch {}
1089
+ return;
1090
+ }
1091
+ _onSynthDone(code);
1092
+ });
1093
+ piper.on('error', (e) => {
1094
+ _stopSpinner(); _killSample(); playBtn.setContent('▶ Play');
1095
+ _showNotice(screen, `Piper failed: ${e.message}`);
1096
+ _focusButton(playBtn);
1097
+ });
1006
1098
  }
1007
1099
  });
1008
- playBtn.top = 5;
1100
+ playBtn.top = 7;
1009
1101
  playBtn.left = 64;
1010
1102
 
1011
1103
  const voiceFileText = blessed.text({
1012
1104
  parent: box,
1013
- top: 6,
1105
+ top: 8,
1014
1106
  left: 22,
1015
1107
  right: 2,
1016
1108
  wrap: false,
@@ -1025,7 +1117,7 @@ export function createSettingsTab(screen, services) {
1025
1117
  parent: box,
1026
1118
  top: 3,
1027
1119
  left: 1,
1028
- content: '{#7986cb-fg} ⚡ Audio Effects {/#7986cb-fg}',
1120
+ content: '{bright-cyan-fg} ⚡ Audio Effects {/bright-cyan-fg}',
1029
1121
  tags: true,
1030
1122
  style: { bg: COLORS.contentBg },
1031
1123
  });
@@ -1081,7 +1173,7 @@ export function createSettingsTab(screen, services) {
1081
1173
  parent: box,
1082
1174
  top: 7,
1083
1175
  left: 1,
1084
- content: '{#7986cb-fg} 🎸 Background Music {/#7986cb-fg}',
1176
+ content: '{bright-cyan-fg} 🎸 Background Music {/bright-cyan-fg}',
1085
1177
  tags: true,
1086
1178
  style: { bg: COLORS.contentBg },
1087
1179
  });
@@ -1177,12 +1269,24 @@ export function createSettingsTab(screen, services) {
1177
1269
  volumeChangeBtn.top = 11;
1178
1270
  volumeChangeBtn.left = 52;
1179
1271
 
1272
+ // -------------------------------------------------------------------------
1273
+ // Section header: ── Style ──
1274
+
1275
+ const styleHeader = blessed.text({
1276
+ parent: box,
1277
+ top: 3,
1278
+ left: 1,
1279
+ content: '{bright-cyan-fg} 🎭 Style {/bright-cyan-fg}',
1280
+ tags: true,
1281
+ style: { bg: COLORS.contentBg },
1282
+ });
1283
+
1180
1284
  // -------------------------------------------------------------------------
1181
1285
  // Verbosity row: label + value + [Change] button
1182
1286
 
1183
1287
  const verbosityLabel = blessed.text({
1184
1288
  parent: box,
1185
- top: 3,
1289
+ top: 5,
1186
1290
  left: 6,
1187
1291
  content: 'Verbosity:',
1188
1292
  style: { fg: COLORS.labelFg, bg: COLORS.contentBg },
@@ -1190,7 +1294,7 @@ export function createSettingsTab(screen, services) {
1190
1294
 
1191
1295
  const verbosityValue = blessed.text({
1192
1296
  parent: box,
1193
- top: 3,
1297
+ top: 5,
1194
1298
  left: 22,
1195
1299
  width: 26, // truncate before [Change] at left:40
1196
1300
  wrap: false,
@@ -1201,12 +1305,12 @@ export function createSettingsTab(screen, services) {
1201
1305
  const verbosityChangeBtn = _createButton(box, screen, 'Change', COLORS, () => {
1202
1306
  _openVerbosityPicker(screen, configService, () => refreshDisplay(), _restoreFocus);
1203
1307
  }, { bg: COLORS.btnChange });
1204
- verbosityChangeBtn.top = 3;
1308
+ verbosityChangeBtn.top = 5;
1205
1309
  verbosityChangeBtn.left = 52;
1206
1310
 
1207
1311
  const verbosityPathText = blessed.text({
1208
1312
  parent: box,
1209
- top: 4,
1313
+ top: 6,
1210
1314
  left: 22,
1211
1315
  right: 2,
1212
1316
  wrap: false,
@@ -1219,7 +1323,7 @@ export function createSettingsTab(screen, services) {
1219
1323
 
1220
1324
  const personalityLabel = blessed.text({
1221
1325
  parent: box,
1222
- top: 5,
1326
+ top: 7,
1223
1327
  left: 6,
1224
1328
  content: 'Personality:',
1225
1329
  style: { fg: COLORS.labelFg, bg: COLORS.contentBg },
@@ -1227,7 +1331,7 @@ export function createSettingsTab(screen, services) {
1227
1331
 
1228
1332
  const personalityValue = blessed.text({
1229
1333
  parent: box,
1230
- top: 5,
1334
+ top: 7,
1231
1335
  left: 22,
1232
1336
  width: 26, // truncate before [Change] at left:40
1233
1337
  wrap: false,
@@ -1241,7 +1345,7 @@ export function createSettingsTab(screen, services) {
1241
1345
  refreshDisplay();
1242
1346
  }, _restoreFocus);
1243
1347
  }, { bg: COLORS.btnChange });
1244
- personalityChangeBtn.top = 5;
1348
+ personalityChangeBtn.top = 7;
1245
1349
  personalityChangeBtn.left = 52;
1246
1350
 
1247
1351
  const personalityTestBtn = _createButton(box, screen, '▶ Preview', COLORS, () => {
@@ -1252,12 +1356,12 @@ export function createSettingsTab(screen, services) {
1252
1356
  : _buildPreviewPhrase();
1253
1357
  _runTest(false, phrase);
1254
1358
  }, { bg: COLORS.btnTest });
1255
- personalityTestBtn.top = 5;
1359
+ personalityTestBtn.top = 7;
1256
1360
  personalityTestBtn.left = 64;
1257
1361
 
1258
1362
  const personalityFileText = blessed.text({
1259
1363
  parent: box,
1260
- top: 6,
1364
+ top: 8,
1261
1365
  left: 22,
1262
1366
  right: 2,
1263
1367
  wrap: false,
@@ -1271,9 +1375,9 @@ export function createSettingsTab(screen, services) {
1271
1375
 
1272
1376
  const introTextHeader = blessed.text({
1273
1377
  parent: box,
1274
- top: 8,
1378
+ top: 10,
1275
1379
  left: 1,
1276
- content: '{#7986cb-fg} ✍️ Intro Text {/#7986cb-fg}',
1380
+ content: '{bright-cyan-fg} ✍️ Intro Text {/bright-cyan-fg}',
1277
1381
  tags: true,
1278
1382
  style: { bg: COLORS.contentBg },
1279
1383
  });
@@ -1283,7 +1387,7 @@ export function createSettingsTab(screen, services) {
1283
1387
 
1284
1388
  const introTextLabel = blessed.text({
1285
1389
  parent: box,
1286
- top: 10,
1390
+ top: 12,
1287
1391
  left: 6,
1288
1392
  content: 'Intro Text:',
1289
1393
  style: { fg: COLORS.labelFg, bg: COLORS.contentBg },
@@ -1291,7 +1395,7 @@ export function createSettingsTab(screen, services) {
1291
1395
 
1292
1396
  const introTextValue = blessed.text({
1293
1397
  parent: box,
1294
- top: 10,
1398
+ top: 12,
1295
1399
  left: 22,
1296
1400
  width: 26, // truncate before [Edit] at left:40
1297
1401
  wrap: false,
@@ -1302,19 +1406,19 @@ export function createSettingsTab(screen, services) {
1302
1406
  const introEditBtn = _createButton(box, screen, 'Edit', COLORS, () => {
1303
1407
  _openIntroTextEditor(screen, configService, () => { refreshDisplay(); }, _restoreFocus);
1304
1408
  }, { bg: COLORS.btnEdit });
1305
- introEditBtn.top = 10;
1409
+ introEditBtn.top = 12;
1306
1410
  introEditBtn.left = 52;
1307
1411
 
1308
1412
  const introClearBtn = _createButton(box, screen, 'Clear', COLORS, () => {
1309
1413
  configService.set('pretext', '');
1310
1414
  refreshDisplay();
1311
1415
  }, { bg: '#c62828' });
1312
- introClearBtn.top = 10;
1416
+ introClearBtn.top = 12;
1313
1417
  introClearBtn.left = 64;
1314
1418
 
1315
1419
  const introPathText = blessed.text({
1316
1420
  parent: box,
1317
- top: 11,
1421
+ top: 13,
1318
1422
  left: 22,
1319
1423
  right: 2,
1320
1424
  wrap: false,
@@ -1334,7 +1438,7 @@ export function createSettingsTab(screen, services) {
1334
1438
  parent: box,
1335
1439
  top: 3,
1336
1440
  left: 2,
1337
- content: '{#7986cb-fg} 📡 Audio Destination {/#7986cb-fg}',
1441
+ content: '{bright-cyan-fg} 📡 Audio Destination {/bright-cyan-fg}',
1338
1442
  tags: true,
1339
1443
  style: { bg: COLORS.contentBg },
1340
1444
  });
@@ -1463,7 +1567,7 @@ export function createSettingsTab(screen, services) {
1463
1567
  const current = configService.getConfig().audio_stream_mode ?? 'text';
1464
1568
  configService.set('audio_stream_mode', current === 'text' ? 'pulse' : 'text');
1465
1569
  refreshDisplay();
1466
- }, { bg: '#2e7d32' }); // green = recommended
1570
+ }, { bg: '#1565c0' }); // blue — distinct from green focus
1467
1571
  audioStreamModeBtn.top = 7;
1468
1572
  audioStreamModeBtn.left = 64;
1469
1573
  audioStreamModeBtn.hide();
@@ -1487,7 +1591,7 @@ export function createSettingsTab(screen, services) {
1487
1591
  parent: box,
1488
1592
  top: 11,
1489
1593
  left: 2,
1490
- content: '{#7986cb-fg} 💾 Config Storage {/#7986cb-fg}',
1594
+ content: '{bright-cyan-fg} 💾 Config Storage {/bright-cyan-fg}',
1491
1595
  tags: true,
1492
1596
  style: { bg: COLORS.contentBg },
1493
1597
  });
@@ -1553,7 +1657,7 @@ export function createSettingsTab(screen, services) {
1553
1657
  refreshConfigDisplay();
1554
1658
  _showNotice(screen, 'Settings Saved');
1555
1659
  }, () => { _currentIdx = _buttons.indexOf(saveLocallyBtn); _focusButton(saveLocallyBtn); });
1556
- }, { bg: '#2e7d32' }); // green
1660
+ }, { bg: '#1565c0' }); // blue — distinct from green focus
1557
1661
  saveLocallyBtn.bottom = 0;
1558
1662
  saveLocallyBtn.left = 46;
1559
1663
 
@@ -1581,6 +1685,7 @@ export function createSettingsTab(screen, services) {
1581
1685
  // Widget groups for each sub-tab (used by _showSubTab to show/hide)
1582
1686
  const _subTabWidgets = {
1583
1687
  voice: [
1688
+ providerVoiceHeader,
1584
1689
  providerLabel, providerValue, switchBtn,
1585
1690
  voiceLabel, voiceValue, changeBtn, playBtn, voiceFileText,
1586
1691
  ],
@@ -1592,6 +1697,7 @@ export function createSettingsTab(screen, services) {
1592
1697
  volumeLabel, volumeValue, volumeChangeBtn,
1593
1698
  ],
1594
1699
  personality: [
1700
+ styleHeader,
1595
1701
  verbosityLabel, verbosityValue, verbosityChangeBtn, verbosityPathText,
1596
1702
  personalityLabel, personalityValue, personalityChangeBtn, personalityTestBtn, personalityFileText,
1597
1703
  introTextHeader,
@@ -2052,7 +2158,7 @@ export function createSettingsTab(screen, services) {
2052
2158
  audioSshValue.setContent(audioAlias || '(none)');
2053
2159
  const streamMode = cfg.audio_stream_mode ?? 'text';
2054
2160
  audioStreamModeBtn.setContent(streamMode === 'pulse' ? 'Streaming Pulse Audio' : 'Streaming Text Only ✓');
2055
- audioStreamModeBtn.style.bg = streamMode === 'text' ? '#2e7d32' : COLORS.btnChange;
2161
+ audioStreamModeBtn.style.bg = streamMode === 'text' ? '#1565c0' : COLORS.btnChange;
2056
2162
  } else {
2057
2163
  audioSshLabel.hide();
2058
2164
  audioSshValue.hide();
@@ -2223,14 +2329,14 @@ function _createButton(parent, screen, label, COLORS, onClick, opts = {}) {
2223
2329
  const _ALL_PROVIDERS = [
2224
2330
  { id: 'piper', name: 'Piper TTS', platforms: ['linux', 'darwin', 'win32'], desc: 'High-quality local neural TTS' },
2225
2331
  { id: 'soprano', name: 'Soprano', platforms: ['linux', 'darwin'], desc: 'Ultra-fast neural TTS (single voice)' },
2226
- { id: 'windows-sapi', name: 'Windows SAPI', platforms: ['win32'], desc: 'Windows built-in text-to-speech' },
2332
+ { id: 'sapi', name: 'Windows SAPI', platforms: ['win32'], desc: 'Windows built-in text-to-speech' },
2227
2333
  { id: 'macos', name: 'Mac Say', platforms: ['darwin'], desc: 'macOS built-in text-to-speech' },
2228
2334
  ];
2229
2335
 
2230
2336
  const _INSTALL_CMDS = {
2231
2337
  piper: ['pip install piper-tts', 'OR: pipx install piper-tts', '', 'Voices are downloaded separately:', 'Run: agentvibes install (then choose Piper)'],
2232
2338
  soprano: ['pip install soprano-tts', 'OR: pipx install soprano-tts', '', 'Keep model loaded for fast synthesis:', 'soprano-webui'],
2233
- 'windows-sapi': ['Built-in on Windows — no install required.', 'Only works in a native Windows shell,', 'not inside WSL. Use piper or soprano in WSL.'],
2339
+ sapi: ['Built-in on Windows — no install required.', 'Only works in a native Windows shell,', 'not inside WSL. Use piper or soprano in WSL.'],
2234
2340
  macos: ['Built-in on macOS — no install required.', 'The say command ships with every Mac.'],
2235
2341
  };
2236
2342
 
@@ -2275,7 +2381,7 @@ function _openProviderPicker(screen, providerService, onSelect, onClose) {
2275
2381
  // Environment header
2276
2382
  blessed.text({
2277
2383
  parent: modal, top: 0, left: 1, tags: true,
2278
- content: `{#00e5ff-fg}🖥 Environment:{/#00e5ff-fg} {bold}${envLabel}{/bold}`,
2384
+ content: `{bright-cyan-fg}🖥 Environment:{/bright-cyan-fg} {bold}${envLabel}{/bold}`,
2279
2385
  style: { bg: COLORS.contentBg },
2280
2386
  });
2281
2387
  blessed.text({
@@ -2325,8 +2431,8 @@ function _openProviderPicker(screen, providerService, onSelect, onClose) {
2325
2431
  _close(); onSelect(prov.id);
2326
2432
  } else {
2327
2433
  const lines = _INSTALL_CMDS[prov.id] ?? ['No instructions available.'];
2328
- instrTitle.setContent(`{#7986cb-fg}Install — ${prov.name}:{/#7986cb-fg}`);
2329
- instrContent.setContent(lines.map(l => l ? `{#00e5ff-fg}${l}{/#00e5ff-fg}` : '').join('\n'));
2434
+ instrTitle.setContent(`{bright-cyan-fg}Install — ${prov.name}:{/bright-cyan-fg}`);
2435
+ instrContent.setContent(lines.map(l => l ? `{bright-cyan-fg}${l}{/bright-cyan-fg}` : '').join('\n'));
2330
2436
  screen.render();
2331
2437
  }
2332
2438
  });
@@ -2340,7 +2446,7 @@ function _openProviderPicker(screen, providerService, onSelect, onClose) {
2340
2446
 
2341
2447
  const instrTitle = blessed.text({
2342
2448
  parent: modal, top: 11, left: 1, width: 66, tags: true,
2343
- content: '{#7986cb-fg}Install instructions — click Install beside a provider:{/#7986cb-fg}',
2449
+ content: '{bright-cyan-fg}Install instructions — click Install beside a provider:{/bright-cyan-fg}',
2344
2450
  style: { bg: COLORS.contentBg },
2345
2451
  });
2346
2452
  const instrContent = blessed.text({
@@ -2452,7 +2558,7 @@ function _showSavePreview(screen, filePath, data, onConfirm, onClose) {
2452
2558
  style: {
2453
2559
  fg: '#e3f2fd',
2454
2560
  bg: COLORS.contentBg,
2455
- border: { fg: '#00e5ff' },
2561
+ border: { fg: 'bright-cyan' },
2456
2562
  },
2457
2563
  });
2458
2564
 
@@ -2471,7 +2577,7 @@ function _showSavePreview(screen, filePath, data, onConfirm, onClose) {
2471
2577
  const okBtn = _createButton(modal, screen, 'OK — Save', COLORS, () => {
2472
2578
  _close();
2473
2579
  onConfirm();
2474
- }, { bg: '#2e7d32' });
2580
+ }, { bg: '#1565c0' });
2475
2581
  okBtn.top = btnRow;
2476
2582
  okBtn.left = midX + 2;
2477
2583
 
@@ -2540,7 +2646,7 @@ function _openTrackPicker(screen, configService, onSelect, onClose) {
2540
2646
  const currentTrack = (configService.getConfig().backgroundMusic?.track ?? MUSIC_DEFAULTS.track);
2541
2647
  const items = allItems.map(t =>
2542
2648
  t.file === ADD_SENTINEL
2543
- ? ` {#00e5ff-fg}+ Add Custom Track{/#00e5ff-fg}`
2649
+ ? ` {bright-cyan-fg}+ Add Custom Track{/bright-cyan-fg}`
2544
2650
  : (t.file === currentTrack ? `● ${t.label}` : ` ${t.label}`)
2545
2651
  );
2546
2652
  const currentIdx = tracks.findIndex(t => t.file === currentTrack);
@@ -2779,9 +2885,14 @@ function _openVolumePicker(screen, configService, onSelect, onClose) {
2779
2885
  `mpg123 -q "${trackPath}"`,
2780
2886
  ].join(' 2>/dev/null || ') + ' 2>/dev/null';
2781
2887
 
2782
- _previewProcess = spawn('sh', ['-c', cmd], {
2783
- stdio: 'ignore', detached: true, env: _previewEnv,
2784
- });
2888
+ if (_IS_WINDOWS) {
2889
+ const _mp3P3 = detectMp3Player(_previewEnv);
2890
+ _previewProcess = _mp3P3
2891
+ ? spawn(_mp3P3.bin, _mp3P3.args(trackPath), _spawnOpts(_previewEnv))
2892
+ : null;
2893
+ } else {
2894
+ _previewProcess = spawn('sh', ['-c', cmd], _spawnOpts(_previewEnv));
2895
+ }
2785
2896
  _previewProcess.unref();
2786
2897
  _refreshList();
2787
2898
 
@@ -2917,7 +3028,7 @@ function _openMusicBrowserModal(screen, configService, navigationService, onDone
2917
3028
  fg: COLORS.labelFg,
2918
3029
  bg: COLORS.contentBg,
2919
3030
  border: { fg: COLORS.borderFg },
2920
- selected: { bg: '#1a237e', fg: '#00e5ff', bold: true },
3031
+ selected: { bg: '#2e7d32', fg: '#ffffff', bold: true },
2921
3032
  item: { fg: COLORS.labelFg },
2922
3033
  },
2923
3034
  });
@@ -2930,7 +3041,7 @@ function _openMusicBrowserModal(screen, configService, navigationService, onDone
2930
3041
  right: 2,
2931
3042
  tags: true,
2932
3043
  content: '',
2933
- style: { fg: '#00e5ff', bg: COLORS.contentBg },
3044
+ style: { fg: 'bright-cyan', bg: COLORS.contentBg },
2934
3045
  });
2935
3046
 
2936
3047
  // ---- File location hint ----
@@ -3024,15 +3135,16 @@ function _openMusicBrowserModal(screen, configService, navigationService, onDone
3024
3135
 
3025
3136
  const _mp3Player = detectMp3Player(_modalEnv);
3026
3137
  if (!_mp3Player) return;
3138
+ const _isWin = process.platform === 'win32' && !process.env.WSL_DISTRO_NAME;
3027
3139
  _previewProcess = spawn(_mp3Player.bin, _mp3Player.args(trackPath), {
3028
- stdio: 'ignore', detached: true, env: _modalEnv,
3140
+ stdio: 'ignore', detached: !_isWin, windowsHide: true, env: _modalEnv,
3029
3141
  });
3030
3142
  _previewProcess.unref();
3031
3143
  _previewTrackId = trackId;
3032
3144
 
3033
3145
  const label = _allTracks.find(t => t.id === trackId)?.label ?? formatTrackLabel(trackId);
3034
3146
  if (!_closed) {
3035
- modalPreviewLine.setContent(`{#00e5ff-fg}\u266A Previewing: ${label} (Space to stop){/#00e5ff-fg}`);
3147
+ modalPreviewLine.setContent(`{bright-cyan-fg}\u266A Previewing: ${label} (Space to stop){/bright-cyan-fg}`);
3036
3148
  screen.render();
3037
3149
  }
3038
3150
 
@@ -3327,7 +3439,7 @@ function _openVoiceBrowserModal(screen, providerService, configService, navigati
3327
3439
  keys: true,
3328
3440
  style: {
3329
3441
  fg: COLORS.valueFg,
3330
- bg: '#1a237e',
3442
+ bg: '#1a3a5c',
3331
3443
  focus: { bg: '#283593' },
3332
3444
  },
3333
3445
  });
@@ -3337,7 +3449,7 @@ function _openVoiceBrowserModal(screen, providerService, configService, navigati
3337
3449
  parent: modal,
3338
3450
  top: 2,
3339
3451
  left: 6,
3340
- content: `{#7986cb-fg}${'Name'.padEnd(COL_NAME_W)}${'Gender'.padEnd(COL_GENDER_W)}Provider{/#7986cb-fg}`,
3452
+ content: `{bright-cyan-fg}${'Name'.padEnd(COL_NAME_W)}${'Gender'.padEnd(COL_GENDER_W)}Provider{/bright-cyan-fg}`,
3341
3453
  tags: true,
3342
3454
  style: { bg: COLORS.contentBg },
3343
3455
  });
@@ -3358,7 +3470,7 @@ function _openVoiceBrowserModal(screen, providerService, configService, navigati
3358
3470
  fg: COLORS.labelFg,
3359
3471
  bg: COLORS.contentBg,
3360
3472
  border: { fg: COLORS.borderFg },
3361
- selected: { bg: '#1a237e', fg: '#00e5ff', bold: true },
3473
+ selected: { bg: '#2e7d32', fg: '#ffffff', bold: true },
3362
3474
  item: { fg: COLORS.labelFg },
3363
3475
  },
3364
3476
  });
@@ -3368,7 +3480,7 @@ function _openVoiceBrowserModal(screen, providerService, configService, navigati
3368
3480
  parent: modal,
3369
3481
  bottom: 5,
3370
3482
  left: 2,
3371
- content: `{#7986cb-fg}── Voice Info ${'─'.repeat(50)}{/#7986cb-fg}`,
3483
+ content: `{bright-cyan-fg}── Voice Info ${'─'.repeat(50)}{/bright-cyan-fg}`,
3372
3484
  tags: true,
3373
3485
  style: { bg: COLORS.contentBg },
3374
3486
  });
@@ -3390,7 +3502,7 @@ function _openVoiceBrowserModal(screen, providerService, configService, navigati
3390
3502
  right: 2,
3391
3503
  tags: true,
3392
3504
  content: '',
3393
- style: { fg: '#00e5ff', bg: COLORS.contentBg },
3505
+ style: { fg: 'bright-cyan', bg: COLORS.contentBg },
3394
3506
  });
3395
3507
 
3396
3508
  // ---- Key hint bar ----
@@ -3495,11 +3607,22 @@ function _openVoiceBrowserModal(screen, providerService, configService, navigati
3495
3607
  const tempWav = path.join(os.tmpdir(), `agentvibes-preview-${Date.now()}.wav`);
3496
3608
  const phrase = SAMPLE_PHRASES[Math.floor(Math.random() * SAMPLE_PHRASES.length)];
3497
3609
 
3610
+ const _isWinPreview = process.platform === 'win32' && !process.env.WSL_DISTRO_NAME;
3611
+ let _piperBin3 = 'piper';
3612
+ if (_isWinPreview) {
3613
+ const _lad = process.env.LOCALAPPDATA ||
3614
+ (process.env.USERPROFILE ? path.join(process.env.USERPROFILE, 'AppData', 'Local') : null);
3615
+ if (_lad) {
3616
+ const _exe = path.join(_lad, 'Programs', 'Piper', 'piper.exe');
3617
+ if (fs.existsSync(_exe)) _piperBin3 = _exe;
3618
+ }
3619
+ }
3498
3620
  const _piperArgs3 = ['--model', voicePath, '--output_file', tempWav];
3499
3621
  if (_ms3.speakerId != null) _piperArgs3.push('--speaker', String(_ms3.speakerId));
3500
- const piper = spawn('piper', _piperArgs3, {
3622
+ const piper = spawn(_piperBin3, _piperArgs3, {
3501
3623
  stdio: ['pipe', 'ignore', 'ignore'],
3502
- detached: true,
3624
+ detached: !_isWinPreview,
3625
+ windowsHide: true,
3503
3626
  env: _spawnEnv,
3504
3627
  });
3505
3628
  piper.stdin.write(phrase + '\n');
@@ -3508,7 +3631,7 @@ function _openVoiceBrowserModal(screen, providerService, configService, navigati
3508
3631
  _playingProcess = piper;
3509
3632
  _playingVoiceId = voiceId;
3510
3633
  if (!_closed) {
3511
- modalPreviewLine.setContent(`{#00e5ff-fg}♪ Synthesizing: ${voiceId}…{/#00e5ff-fg}`);
3634
+ modalPreviewLine.setContent(`{bright-cyan-fg}♪ Synthesizing: ${voiceId}…{/bright-cyan-fg}`);
3512
3635
  screen.render();
3513
3636
  }
3514
3637
 
@@ -3521,7 +3644,7 @@ function _openVoiceBrowserModal(screen, providerService, configService, navigati
3521
3644
  _playingVoiceId = null;
3522
3645
  _playingProcess = null;
3523
3646
  if (!_closed) {
3524
- modalPreviewLine.setContent('{#00e5ff-fg}♪ Preview failed (piper error — is piper installed?){/#00e5ff-fg}');
3647
+ modalPreviewLine.setContent('{bright-cyan-fg}♪ Preview failed (piper error — is piper installed?){/bright-cyan-fg}');
3525
3648
  screen.render();
3526
3649
  setTimeout(() => { if (!_closed) { modalPreviewLine.setContent(''); screen.render(); } }, 4000);
3527
3650
  }
@@ -3532,13 +3655,14 @@ function _openVoiceBrowserModal(screen, providerService, configService, navigati
3532
3655
  if (!_wavPlayer3) return;
3533
3656
  const playProc = spawn(_wavPlayer3.bin, _wavPlayer3.args(tempWav), {
3534
3657
  stdio: 'ignore',
3535
- detached: true,
3658
+ detached: !_isWinPreview,
3659
+ windowsHide: true,
3536
3660
  env: _spawnEnv,
3537
3661
  });
3538
3662
  _playingProcess = playProc;
3539
3663
 
3540
3664
  if (!_closed) {
3541
- modalPreviewLine.setContent(`{#00e5ff-fg}♪ Playing: ${voiceId} (Space to stop){/#00e5ff-fg}`);
3665
+ modalPreviewLine.setContent(`{bright-cyan-fg}♪ Playing: ${voiceId} (Space to stop){/bright-cyan-fg}`);
3542
3666
  screen.render();
3543
3667
  }
3544
3668
 
@@ -3563,7 +3687,7 @@ function _openVoiceBrowserModal(screen, providerService, configService, navigati
3563
3687
  _playingVoiceId = null;
3564
3688
  _playingProcess = null;
3565
3689
  if (!_closed) {
3566
- modalPreviewLine.setContent('{#00e5ff-fg}♪ Cannot find piper — install with: pipx install piper-tts{/#00e5ff-fg}');
3690
+ modalPreviewLine.setContent('{bright-cyan-fg}♪ Cannot find piper — install with: pipx install piper-tts{/bright-cyan-fg}');
3567
3691
  screen.render();
3568
3692
  setTimeout(() => { if (!_closed) { modalPreviewLine.setContent(''); screen.render(); } }, 4000);
3569
3693
  }