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
@@ -32,11 +32,11 @@ const COLORS = {
32
32
  sectionHdr: '#00897b', // Teal — section headers for Voices tab
33
33
  labelFg: '#e3f2fd',
34
34
  valueFg: '#f06292', // Light magenta — brand color
35
- activeFg: '#00e5ff', // Cyan — active voice
35
+ activeFg: 'bright-cyan', // Cyan — active voice
36
36
  favoriteFg: '#ffff00', // Yellow — favorite star
37
37
  btnDefault: '#00695c', // Teal — Voices tab buttons
38
- btnFocus: '#00e5ff',
39
- btnFocusFg: '#000000',
38
+ btnFocus: '#2e7d32', // Green — focused/selected
39
+ btnFocusFg: '#ffffff',
40
40
  btnPress: '#ff00ff',
41
41
  borderFg: '#00897b',
42
42
  footerBg: '#00695c', // Teal — Voices tab footer
@@ -513,7 +513,7 @@ export function createVoicesTab(screen, services) {
513
513
  keys: true,
514
514
  style: {
515
515
  fg: COLORS.valueFg,
516
- bg: '#1a237e',
516
+ bg: '#1a3a5c',
517
517
  focus: { bg: '#283593' },
518
518
  },
519
519
  });
@@ -549,7 +549,7 @@ export function createVoicesTab(screen, services) {
549
549
  fg: COLORS.labelFg,
550
550
  bg: COLORS.contentBg,
551
551
  border: { fg: COLORS.borderFg },
552
- selected: { bg: '#1a237e', fg: COLORS.activeFg, bold: true },
552
+ selected: { bg: '#2e7d32', fg: '#ffffff', bold: true },
553
553
  item: { fg: COLORS.labelFg },
554
554
  },
555
555
  });
@@ -670,12 +670,24 @@ export function createVoicesTab(screen, services) {
670
670
  const tempWav = path.join(os.tmpdir(), `agentvibes-preview-${Date.now()}.wav`);
671
671
  const phrase = SAMPLE_PHRASES[Math.floor(Math.random() * SAMPLE_PHRASES.length)];
672
672
 
673
- // Synthesize: spawn piper in its own process group; pass text via stdin with newline
673
+ // Synthesize: spawn piper; on Windows use the exe path directly
674
+ const isWindows = process.platform === 'win32' && !process.env.WSL_DISTRO_NAME;
675
+ let piperBin = 'piper';
676
+ if (isWindows) {
677
+ const localAppData = process.env.LOCALAPPDATA ||
678
+ (process.env.USERPROFILE ? path.join(process.env.USERPROFILE, 'AppData', 'Local') : null);
679
+ if (localAppData) {
680
+ const exePath = path.join(localAppData, 'Programs', 'Piper', 'piper.exe');
681
+ if (fs.existsSync(exePath)) piperBin = exePath;
682
+ }
683
+ }
674
684
  const piperArgs = ['--model', voicePath, '--output_file', tempWav];
675
685
  if (ms.speakerId != null) piperArgs.push('--speaker', String(ms.speakerId));
676
- const piper = spawn('piper', piperArgs, {
686
+ // On Windows, avoid detached:true which opens a visible console window
687
+ const piper = spawn(piperBin, piperArgs, {
677
688
  stdio: ['pipe', 'ignore', 'ignore'],
678
- detached: true,
689
+ detached: !isWindows,
690
+ windowsHide: true,
679
691
  env: _spawnEnv,
680
692
  });
681
693
  piper.stdin.write(phrase + '\n');
@@ -716,7 +728,8 @@ export function createVoicesTab(screen, services) {
716
728
  }
717
729
  const playProc = spawn(_wavP.bin, _wavP.args(tempWav), {
718
730
  stdio: 'ignore',
719
- detached: true,
731
+ detached: !isWindows,
732
+ windowsHide: true,
720
733
  env: _spawnEnv,
721
734
  });
722
735
  // Race note: _playingVoiceId could change between piper exit and here
@@ -993,7 +1006,7 @@ export function createVoicesTab(screen, services) {
993
1006
  refreshDisplay();
994
1007
  _showVoiceChangedNotice(displayName);
995
1008
  });
996
- const okGlobalBtn = _makeBtn('Save Globally & Locally', '#2e7d32', 18, 5, () => {
1009
+ const okGlobalBtn = _makeBtn('Save Globally & Locally', '#1565c0', 18, 5, () => {
997
1010
  _activateVoice(voiceId);
998
1011
  _activateVoiceGlobal(voiceId);
999
1012
  refreshDisplay();
@@ -1100,26 +1113,91 @@ export function createVoicesTab(screen, services) {
1100
1113
  function _startDownload() {
1101
1114
  if (_downloading) return;
1102
1115
  _downloading = true;
1103
- statusLine.setContent(`{${COLORS.activeFg}-fg}⬇ Downloading ${modelToDownload}…{/${COLORS.activeFg}-fg}`);
1104
- screen.render();
1105
1116
 
1106
- // Use piper-voice-manager.sh download_voice function
1107
- const managerScript = path.resolve(process.cwd(), '.claude', 'hooks', 'piper-voice-manager.sh');
1108
- const dlProc = spawn('bash', ['-c', 'source "$1" && download_voice "$2"', '_', managerScript, modelToDownload], {
1109
- stdio: ['ignore', 'pipe', 'pipe'],
1110
- env: _spawnEnv,
1111
- });
1117
+ // Animated spinner
1118
+ const spinFrames = ['⠋', '', '', '', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
1119
+ let spinIdx = 0;
1120
+ let dlPhase = 'Downloading model';
1121
+ const progressBar = (pct) => {
1122
+ const filled = Math.round(pct / 5);
1123
+ const empty = 20 - filled;
1124
+ return '█'.repeat(filled) + '░'.repeat(empty);
1125
+ };
1126
+
1127
+ const spinTimer = setInterval(() => {
1128
+ spinIdx = (spinIdx + 1) % spinFrames.length;
1129
+ const frame = spinFrames[spinIdx];
1130
+ statusLine.setContent(
1131
+ `{${COLORS.activeFg}-fg}${frame} ${dlPhase}… ${modelToDownload}{/${COLORS.activeFg}-fg}`
1132
+ );
1133
+ screen.render();
1134
+ }, 100);
1135
+
1136
+ // Download voice model — use PowerShell on Windows, bash on Unix
1137
+ const packageRoot = path.resolve(__dirname, '..', '..', '..');
1138
+ const isWindows = process.platform === 'win32' && !process.env.WSL_DISTRO_NAME;
1139
+ let dlProc;
1140
+
1141
+ if (isWindows) {
1142
+ const piperVoicesDir = resolvePiperVoicesDir();
1143
+ const hfBase = 'https://huggingface.co/rhasspy/piper-voices/resolve/main';
1144
+ const match = modelToDownload.match(/^([a-z]{2})_([A-Z]{2})-([a-zA-Z0-9_]+)-([a-z]+)$/);
1145
+ let modelUrl, configUrl;
1146
+ if (match) {
1147
+ const [, lang, region, speaker, quality] = match;
1148
+ const hfPath = `${lang}/${lang}_${region}/${speaker}/${quality}`;
1149
+ modelUrl = `${hfBase}/${hfPath}/${modelToDownload}.onnx`;
1150
+ configUrl = `${hfBase}/${hfPath}/${modelToDownload}.onnx.json`;
1151
+ } else {
1152
+ const customBase = 'https://huggingface.co/agentvibes/piper-custom-voices/resolve/main';
1153
+ modelUrl = `${customBase}/${modelToDownload}.onnx`;
1154
+ configUrl = `${customBase}/${modelToDownload}.onnx.json`;
1155
+ }
1156
+ const modelFile = path.join(piperVoicesDir, `${modelToDownload}.onnx`);
1157
+ const configFile = path.join(piperVoicesDir, `${modelToDownload}.onnx.json`);
1158
+ // PowerShell script with progress reporting
1159
+ const psScript = `
1160
+ $ErrorActionPreference = 'Stop'
1161
+ $ProgressPreference = 'SilentlyContinue'
1162
+ $voicesDir = '${piperVoicesDir.replace(/'/g, "''")}'
1163
+ if (-not (Test-Path $voicesDir)) { New-Item -ItemType Directory -Path $voicesDir -Force | Out-Null }
1164
+ Write-Output 'PHASE:model'
1165
+ Invoke-WebRequest -Uri '${modelUrl}' -OutFile '${modelFile.replace(/'/g, "''")}' -ErrorAction Stop
1166
+ Write-Output 'PHASE:config'
1167
+ Invoke-WebRequest -Uri '${configUrl}' -OutFile '${configFile.replace(/'/g, "''")}' -ErrorAction Stop
1168
+ Write-Output 'PHASE:done'
1169
+ `;
1170
+ dlProc = spawn('powershell', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', psScript], {
1171
+ stdio: ['ignore', 'pipe', 'pipe'],
1172
+ env: _spawnEnv,
1173
+ });
1174
+ } else {
1175
+ const managerScript = path.resolve(packageRoot, '.claude', 'hooks', 'piper-voice-manager.sh');
1176
+ dlProc = spawn('bash', ['-c', 'source "$1" && download_voice "$2"', '_', managerScript, modelToDownload], {
1177
+ stdio: ['ignore', 'pipe', 'pipe'],
1178
+ env: _spawnEnv,
1179
+ });
1180
+ }
1112
1181
  _downloadProcess = dlProc;
1113
1182
 
1114
1183
  let output = '';
1115
- dlProc.stdout.on('data', (d) => { output += d.toString(); });
1184
+ dlProc.stdout.on('data', (d) => {
1185
+ const chunk = d.toString();
1186
+ output += chunk;
1187
+ // Update phase based on progress markers
1188
+ if (chunk.includes('PHASE:config') || chunk.includes('config file')) {
1189
+ dlPhase = 'Downloading config';
1190
+ } else if (chunk.includes('PHASE:done') || chunk.includes('successfully')) {
1191
+ dlPhase = 'Finishing up';
1192
+ }
1193
+ });
1116
1194
  dlProc.stderr.on('data', (d) => { output += d.toString(); });
1117
1195
 
1118
1196
  dlProc.on('exit', (code) => {
1197
+ clearInterval(spinTimer);
1119
1198
  _downloading = false;
1120
1199
  _downloadProcess = null;
1121
1200
  if (code === 0) {
1122
- // Patch speaker names for freshly downloaded LibriTTS model
1123
1201
  if (isLibriTTS) {
1124
1202
  patchLibriTTSSpeakerNames();
1125
1203
  _metaCache.clear();
@@ -1137,6 +1215,7 @@ export function createVoicesTab(screen, services) {
1137
1215
  });
1138
1216
 
1139
1217
  dlProc.on('error', () => {
1218
+ clearInterval(spinTimer);
1140
1219
  _downloading = false;
1141
1220
  _downloadProcess = null;
1142
1221
  statusLine.setContent(`{red-fg}✗ Could not run download script{/red-fg}`);
@@ -1235,7 +1314,7 @@ export function createVoicesTab(screen, services) {
1235
1314
  // Greyed-out row for uninstalled catalog voices
1236
1315
  return `{bright-black-fg} ${star} ${name}${gender.padEnd(COL_GENDER_W)}${provider}{/bright-black-fg}`;
1237
1316
  }
1238
- return ` ${star}${dot} ${name}${gender.padEnd(COL_GENDER_W)}${provider}${isPrev ? ' (playing)' : ''}`;
1317
+ return `{${COLORS.labelFg}-fg} ${star}${dot} ${name}${gender.padEnd(COL_GENDER_W)}${provider}${isPrev ? ' (playing)' : ''}{/${COLORS.labelFg}-fg}`;
1239
1318
  });
1240
1319
  }
1241
1320
 
@@ -1,25 +1,25 @@
1
- /**
2
- * AgentVibes TUI — Shared Widget: Modal Destroy Helper
3
- *
4
- * Force-invalidates blessed's olines buffer after destroying a modal widget.
5
- * Without this, blessed skips repainting cells where lines==olines and the
6
- * terminal retains stale modal content as ghost artifacts.
7
- */
8
-
9
- /**
10
- * Destroy a blessed list/box widget and force full screen repaint.
11
- *
12
- * @param {object} widget - blessed widget to destroy
13
- * @param {object} screen - blessed screen instance
14
- * @param {Function} [onClose] - optional callback after destruction
15
- */
16
- export function destroyList(widget, screen, onClose) {
17
- widget.destroy();
18
- try {
19
- for (let r = 0; r < screen.height; r++)
20
- for (let c = 0; c < screen.width; c++)
21
- if (screen.olines[r]?.[c]) screen.olines[r][c][0] = -1;
22
- } catch {}
23
- onClose?.();
24
- screen.render();
25
- }
1
+ /**
2
+ * AgentVibes TUI — Shared Widget: Modal Destroy Helper
3
+ *
4
+ * Force-invalidates blessed's olines buffer after destroying a modal widget.
5
+ * Without this, blessed skips repainting cells where lines==olines and the
6
+ * terminal retains stale modal content as ghost artifacts.
7
+ */
8
+
9
+ /**
10
+ * Destroy a blessed list/box widget and force full screen repaint.
11
+ *
12
+ * @param {object} widget - blessed widget to destroy
13
+ * @param {object} screen - blessed screen instance
14
+ * @param {Function} [onClose] - optional callback after destruction
15
+ */
16
+ export function destroyList(widget, screen, onClose) {
17
+ widget.destroy();
18
+ try {
19
+ for (let r = 0; r < screen.height; r++)
20
+ for (let c = 0; c < screen.width; c++)
21
+ if (screen.olines[r]?.[c]) screen.olines[r][c][0] = -1;
22
+ } catch {}
23
+ onClose?.();
24
+ screen.render();
25
+ }
@@ -1,89 +1,89 @@
1
- /**
2
- * AgentVibes TUI — Shared Format Utilities
3
- *
4
- * Pure formatting functions extracted from settings-tab.js to avoid
5
- * circular imports between widgets and tabs.
6
- */
7
-
8
- const TRACK_NAMES = Object.freeze({
9
- 'agentvibes_soft_flamenco_loop.mp3': '🎻 Soft Flamenco',
10
- 'agent_vibes_bachata_v1_loop.mp3': '🎺 Bachata',
11
- 'agent_vibes_salsa_v2_loop.mp3': '💃 Salsa',
12
- 'agent_vibes_cumbia_v1_loop.mp3': '🎸 Cumbia',
13
- 'agent_vibes_bossa_nova_v2_loop.mp3': '🌸 Bossa Nova',
14
- 'agent_vibes_japanese_city_pop_v1_loop.mp3': '🌆 Japanese City Pop',
15
- 'agent_vibes_chillwave_v2_loop.mp3': '🌊 Chillwave',
16
- 'agent_vibes_dark_chill_step_loop.mp3': '🌙 Dark Chill Step',
17
- 'agent_vibes_goa_trance_v2_loop.mp3': '🌀 Goa Trance',
18
- 'agent_vibes_harpsichord_v2_loop.mp3': '🎼 Harpsichord',
19
- 'agent_vibes_celtic_harp_v1_loop.mp3': '🎻 Celtic Harp',
20
- 'agent_vibes_hawaiian_slack_key_guitar_v2_loop.mp3': '🌺 Hawaiian Slack Key Guitar',
21
- 'agent_vibes_arabic_v2_loop.mp3': '🎵 Arabic Oud',
22
- 'agent_vibes_ganawa_ambient_v2_loop.mp3': '🪘 Gnawa Ambient',
23
- 'agent_vibes_tabla_dream_pop_v1_loop.mp3': '🥁 Tabla Dream Pop',
24
- });
25
-
26
- /**
27
- * @param {string} track - filename (e.g. 'agentvibes_soft_flamenco_loop.mp3')
28
- * @returns {string}
29
- */
30
- export function formatTrackName(track) {
31
- if (!track) return 'None';
32
- if (TRACK_NAMES[track]) return TRACK_NAMES[track];
33
- return track
34
- .replace(/\.[^.]+$/, '')
35
- .replace(/^agentvibes_|^agent_vibes_/, '')
36
- .replace(/_v\d+_loop$|_loop$|_v\d+$/, '')
37
- .replace(/_/g, ' ')
38
- .replace(/\b\w/g, c => c.toUpperCase());
39
- }
40
-
41
- /**
42
- * Beautify a raw voice identifier for display in narrow table columns.
43
- *
44
- * Examples:
45
- * 16Speakers::Rose_Ibex → Rose Ibex
46
- * 16Speakers::Emily_Cripps → Emily Cripps
47
- * en_US-kusal-medium → Kusal
48
- * en_US-lessac-high → Lessac
49
- * en_US-libritts_r-medium → Libritts R
50
- * kristin → Kristin
51
- *
52
- * @param {string} voice - raw voice identifier
53
- * @returns {string}
54
- */
55
- export function formatVoiceName(voice) {
56
- if (!voice) return '(global)';
57
-
58
- let name;
59
- if (voice.includes('::')) {
60
- // 16Speakers::Rose_Ibex → extract after '::'
61
- name = voice.split('::')[1];
62
- } else {
63
- const parts = voice.split('-');
64
- const QUALITIES = new Set(['high', 'medium', 'low']);
65
- if (parts.length >= 2 && /^[a-z]{2}_[A-Z]{2}$/.test(parts[0])) {
66
- // Strip locale prefix and quality suffix
67
- name = parts.slice(1).filter(p => !QUALITIES.has(p)).join(' ');
68
- } else {
69
- name = voice;
70
- }
71
- }
72
-
73
- // Replace underscores with spaces, title-case each word
74
- return name
75
- .replace(/_/g, ' ')
76
- .split(' ')
77
- .filter(Boolean)
78
- .map(w => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase())
79
- .join(' ') || '(global)';
80
- }
81
-
82
- /**
83
- * @param {string} preset - 'off' | 'light' | 'medium' | 'heavy' | 'cathedral'
84
- * @returns {string}
85
- */
86
- export function formatReverbState(preset) {
87
- const LABELS = { off: 'Off', light: 'Light (Small room)', medium: 'Medium (Conference room)', heavy: 'Heavy (Large hall)', cathedral: 'Cathedral (Epic space)' };
88
- return LABELS[preset] ?? LABELS.light;
89
- }
1
+ /**
2
+ * AgentVibes TUI — Shared Format Utilities
3
+ *
4
+ * Pure formatting functions extracted from settings-tab.js to avoid
5
+ * circular imports between widgets and tabs.
6
+ */
7
+
8
+ const TRACK_NAMES = Object.freeze({
9
+ 'agentvibes_soft_flamenco_loop.mp3': '🎻 Soft Flamenco',
10
+ 'agent_vibes_bachata_v1_loop.mp3': '🎺 Bachata',
11
+ 'agent_vibes_salsa_v2_loop.mp3': '💃 Salsa',
12
+ 'agent_vibes_cumbia_v1_loop.mp3': '🎸 Cumbia',
13
+ 'agent_vibes_bossa_nova_v2_loop.mp3': '🌸 Bossa Nova',
14
+ 'agent_vibes_japanese_city_pop_v1_loop.mp3': '🌆 Japanese City Pop',
15
+ 'agent_vibes_chillwave_v2_loop.mp3': '🌊 Chillwave',
16
+ 'agent_vibes_dark_chill_step_loop.mp3': '🌙 Dark Chill Step',
17
+ 'agent_vibes_goa_trance_v2_loop.mp3': '🌀 Goa Trance',
18
+ 'agent_vibes_harpsichord_v2_loop.mp3': '🎼 Harpsichord',
19
+ 'agent_vibes_celtic_harp_v1_loop.mp3': '🎻 Celtic Harp',
20
+ 'agent_vibes_hawaiian_slack_key_guitar_v2_loop.mp3': '🌺 Hawaiian Slack Key Guitar',
21
+ 'agent_vibes_arabic_v2_loop.mp3': '🎵 Arabic Oud',
22
+ 'agent_vibes_ganawa_ambient_v2_loop.mp3': '🪘 Gnawa Ambient',
23
+ 'agent_vibes_tabla_dream_pop_v1_loop.mp3': '🥁 Tabla Dream Pop',
24
+ });
25
+
26
+ /**
27
+ * @param {string} track - filename (e.g. 'agentvibes_soft_flamenco_loop.mp3')
28
+ * @returns {string}
29
+ */
30
+ export function formatTrackName(track) {
31
+ if (!track) return 'None';
32
+ if (TRACK_NAMES[track]) return TRACK_NAMES[track];
33
+ return track
34
+ .replace(/\.[^.]+$/, '')
35
+ .replace(/^agentvibes_|^agent_vibes_/, '')
36
+ .replace(/_v\d+_loop$|_loop$|_v\d+$/, '')
37
+ .replace(/_/g, ' ')
38
+ .replace(/\b\w/g, c => c.toUpperCase());
39
+ }
40
+
41
+ /**
42
+ * Beautify a raw voice identifier for display in narrow table columns.
43
+ *
44
+ * Examples:
45
+ * 16Speakers::Rose_Ibex → Rose Ibex
46
+ * 16Speakers::Emily_Cripps → Emily Cripps
47
+ * en_US-kusal-medium → Kusal
48
+ * en_US-lessac-high → Lessac
49
+ * en_US-libritts_r-medium → Libritts R
50
+ * kristin → Kristin
51
+ *
52
+ * @param {string} voice - raw voice identifier
53
+ * @returns {string}
54
+ */
55
+ export function formatVoiceName(voice) {
56
+ if (!voice) return '(global)';
57
+
58
+ let name;
59
+ if (voice.includes('::')) {
60
+ // 16Speakers::Rose_Ibex → extract after '::'
61
+ name = voice.split('::')[1];
62
+ } else {
63
+ const parts = voice.split('-');
64
+ const QUALITIES = new Set(['high', 'medium', 'low']);
65
+ if (parts.length >= 2 && /^[a-z]{2}_[A-Z]{2}$/.test(parts[0])) {
66
+ // Strip locale prefix and quality suffix
67
+ name = parts.slice(1).filter(p => !QUALITIES.has(p)).join(' ');
68
+ } else {
69
+ name = voice;
70
+ }
71
+ }
72
+
73
+ // Replace underscores with spaces, title-case each word
74
+ return name
75
+ .replace(/_/g, ' ')
76
+ .split(' ')
77
+ .filter(Boolean)
78
+ .map(w => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase())
79
+ .join(' ') || '(global)';
80
+ }
81
+
82
+ /**
83
+ * @param {string} preset - 'off' | 'light' | 'medium' | 'heavy' | 'cathedral'
84
+ * @returns {string}
85
+ */
86
+ export function formatReverbState(preset) {
87
+ const LABELS = { off: 'Off', light: 'Light (Small room)', medium: 'Medium (Conference room)', heavy: 'Heavy (Large hall)', cathedral: 'Cathedral (Epic space)' };
88
+ return LABELS[preset] ?? LABELS.light;
89
+ }
@@ -1,55 +1,55 @@
1
- /**
2
- * AgentVibes TUI — Shared Widget: Notice Toast
3
- *
4
- * Displays a short auto-dismissing notice modal centred on screen.
5
- * Usable from any tab; no settings-specific state required.
6
- */
7
-
8
- import { destroyList } from './destroy-list.js';
9
-
10
- const IS_TEST = process.env.AGENTVIBES_TEST_MODE === 'true';
11
- let blessed;
12
- if (!IS_TEST) {
13
- const { default: b } = await import('blessed');
14
- blessed = b;
15
- }
16
-
17
- /**
18
- * Show a temporary notice that auto-dismisses after 2.5 seconds.
19
- *
20
- * @param {object} screen - blessed screen instance
21
- * @param {string} message - text to display
22
- * @param {object} [opts]
23
- * @param {string} [opts.bg='#0a0e1a'] - background colour
24
- * @param {string} [opts.fg='#e3f2fd'] - foreground colour
25
- * @param {string} [opts.borderFg='#00e5ff'] - border colour
26
- * @param {number} [opts.durationMs=2500] - auto-dismiss delay in ms
27
- */
28
- export function showNotice(screen, message, opts = {}) {
29
- const bg = opts.bg ?? '#0a0e1a';
30
- const fg = opts.fg ?? '#e3f2fd';
31
- const borderFg = opts.borderFg ?? '#00e5ff';
32
- const durationMs = opts.durationMs ?? 2500;
33
-
34
- const width = Math.max(28, message.length + 6);
35
- const modal = blessed.box({
36
- parent: screen,
37
- top: 'center',
38
- left: 'center',
39
- width,
40
- height: 3,
41
- border: { type: 'line' },
42
- tags: true,
43
- content: `{center}${message}{/center}`,
44
- style: {
45
- fg,
46
- bg,
47
- border: { fg: borderFg },
48
- },
49
- });
50
- screen.render();
51
-
52
- setTimeout(() => {
53
- destroyList(modal, screen);
54
- }, durationMs);
55
- }
1
+ /**
2
+ * AgentVibes TUI — Shared Widget: Notice Toast
3
+ *
4
+ * Displays a short auto-dismissing notice modal centred on screen.
5
+ * Usable from any tab; no settings-specific state required.
6
+ */
7
+
8
+ import { destroyList } from './destroy-list.js';
9
+
10
+ const IS_TEST = process.env.AGENTVIBES_TEST_MODE === 'true';
11
+ let blessed;
12
+ if (!IS_TEST) {
13
+ const { default: b } = await import('blessed');
14
+ blessed = b;
15
+ }
16
+
17
+ /**
18
+ * Show a temporary notice that auto-dismisses after 2.5 seconds.
19
+ *
20
+ * @param {object} screen - blessed screen instance
21
+ * @param {string} message - text to display
22
+ * @param {object} [opts]
23
+ * @param {string} [opts.bg='#0a0e1a'] - background colour
24
+ * @param {string} [opts.fg='#e3f2fd'] - foreground colour
25
+ * @param {string} [opts.borderFg='bright-cyan'] - border colour
26
+ * @param {number} [opts.durationMs=2500] - auto-dismiss delay in ms
27
+ */
28
+ export function showNotice(screen, message, opts = {}) {
29
+ const bg = opts.bg ?? '#0a0e1a';
30
+ const fg = opts.fg ?? '#e3f2fd';
31
+ const borderFg = opts.borderFg ?? 'bright-cyan';
32
+ const durationMs = opts.durationMs ?? 2500;
33
+
34
+ const width = Math.max(28, message.length + 6);
35
+ const modal = blessed.box({
36
+ parent: screen,
37
+ top: 'center',
38
+ left: 'center',
39
+ width,
40
+ height: 3,
41
+ border: { type: 'line' },
42
+ tags: true,
43
+ content: `{center}${message}{/center}`,
44
+ style: {
45
+ fg,
46
+ bg,
47
+ border: { fg: borderFg },
48
+ },
49
+ });
50
+ screen.render();
51
+
52
+ setTimeout(() => {
53
+ destroyList(modal, screen);
54
+ }, durationMs);
55
+ }