agentvibes 5.7.7 → 5.10.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 (46) hide show
  1. package/.agentvibes/config.json +0 -2
  2. package/.claude/config/audio-effects.cfg +4 -4
  3. package/.claude/config/background-music-enabled.txt +1 -0
  4. package/.claude/github-star-reminder.txt +1 -1
  5. package/.claude/hooks/play-tts-piper.sh +20 -13
  6. package/.claude/hooks/play-tts-ssh-remote.sh +2 -2
  7. package/.claude/hooks/voice-manager.sh +6 -0
  8. package/.claude/hooks-windows/audio-cache-utils.ps1 +119 -119
  9. package/.claude/hooks-windows/bmad-speak.ps1 +9 -38
  10. package/.claude/hooks-windows/play-tts-soprano.ps1 +13 -2
  11. package/.claude/hooks-windows/play-tts-windows-piper.ps1 +22 -16
  12. package/.mcp.json +13 -9
  13. package/README.md +33 -2
  14. package/RELEASE_NOTES.md +80 -0
  15. package/mcp-server/server.py +17 -7
  16. package/package.json +2 -2
  17. package/src/commands/install-mcp.js +270 -16
  18. package/src/console/app.js +3 -3
  19. package/src/console/audio-env.js +4 -1
  20. package/src/console/tabs/agents-tab.js +89 -66
  21. package/src/console/tabs/music-tab.js +4 -3
  22. package/src/console/tabs/receiver-tab.js +13 -13
  23. package/src/console/tabs/settings-tab.js +2 -2
  24. package/src/console/tabs/setup-tab.js +291 -47
  25. package/src/console/tabs/voices-tab.js +17 -5
  26. package/src/console/widgets/personality-picker.js +2 -2
  27. package/src/console/widgets/reverb-picker.js +1 -1
  28. package/src/installer.js +32 -27
  29. package/src/services/provider-service.js +1 -1
  30. package/src/services/tts-engine-service.js +2 -2
  31. package/src/utils/audio-duration-validator.js +2 -2
  32. package/src/utils/list-formatter.js +9 -3
  33. package/src/utils/platform-resolver.js +369 -0
  34. package/src/utils/provider-validator.js +9 -9
  35. package/.agentvibes/install-manifest.json +0 -442
  36. package/.claude/config/background-music-position.txt +0 -27
  37. package/.claude/config/background-music-volume.txt +0 -1
  38. package/.claude/config/background-music.cfg +0 -1
  39. package/.claude/config/background-music.txt +0 -1
  40. package/.claude/config/reverb-level.txt +0 -1
  41. package/.claude/config/tts-speech-rate.txt +0 -1
  42. package/.claude/config/tts-verbosity.txt +0 -1
  43. package/.claude/hooks/bmad-party-manager.sh +0 -225
  44. package/.claude/hooks/stop.sh +0 -38
  45. package/.claude/piper-voices-dir.txt +0 -1
  46. /package/.claude/audio/tracks/{CelestialVelvet.mp3 → celestial_velvet.mp3} +0 -0
@@ -17,6 +17,7 @@ import { formatReverbState, formatTrackName, formatVoiceName } from '../widgets/
17
17
  import {
18
18
  PIPER_VOICES_DIR, SAMPLE_PHRASES,
19
19
  parseMultiSpeaker, scanInstalledVoices, getVoiceMeta, genderIconTag,
20
+ getFavorites, getThumbsDown, toggleThumbsUp, toggleThumbsDown,
20
21
  } from './voices-tab.js';
21
22
  import { buildAudioEnv, detectWavPlayer, detectRemoteLlm } from '../audio-env.js';
22
23
  import { destroyList } from '../widgets/destroy-list.js';
@@ -694,7 +695,7 @@ ${_tl('bmadDesc')}
694
695
  left: 2,
695
696
  right: 2,
696
697
  tags: true,
697
- content: '{white-fg}[↑↓] Navigate [Enter] Edit [Tab] → Preview/Save [Esc] Cancel{/white-fg}',
698
+ content: '{white-fg}[↑↓] Navigate [Enter] Edit [Tab] → Buttons [Esc] Close{/white-fg}',
698
699
  style: { bg: COLORS.contentBg },
699
700
  });
700
701
 
@@ -754,10 +755,12 @@ ${_tl('bmadDesc')}
754
755
  refreshDisplay();
755
756
  });
756
757
 
757
- const closeBtn = _modalBtn('Cancel', 42, _closeModal);
758
+ const saveBtn = _modalBtn('Save', 41, () => { try { _autoSaveAgent(); } catch {} _closeModal(); });
759
+
760
+ const closeBtn = _modalBtn('Close', 50, _closeModal);
758
761
 
759
762
  // Blinking █ cursor + preview spinner — reusable across all modal buttons
760
- const btnBlink = attachBtnBlink([previewBtn, resetBtn, closeBtn], screen);
763
+ const btnBlink = attachBtnBlink([previewBtn, resetBtn, saveBtn, closeBtn], screen);
761
764
 
762
765
  function _closeModal() {
763
766
  if (_closed) return;
@@ -868,6 +871,7 @@ ${_tl('bmadDesc')}
868
871
  fieldList.key(['escape', 'q', 'Q'], _closeModal);
869
872
  previewBtn.key(['escape', 'q', 'Q'], _closeModal);
870
873
  resetBtn.key(['escape', 'q', 'Q'], _closeModal);
874
+ saveBtn.key(['escape', 'q', 'Q'], _closeModal);
871
875
  closeBtn.key(['escape', 'q', 'Q'], _closeModal);
872
876
 
873
877
  // Tab + arrow navigation within modal
@@ -895,18 +899,27 @@ ${_tl('bmadDesc')}
895
899
  // Wrap: up on buttons → back to field list
896
900
  previewBtn.key(['up'], () => { fieldList.focus(); fieldList.select(FIELDS.length - 1); screen.render(); });
897
901
  resetBtn.key(['up'], () => { fieldList.focus(); fieldList.select(FIELDS.length - 1); screen.render(); });
902
+ saveBtn.key(['up'], () => { fieldList.focus(); fieldList.select(FIELDS.length - 1); screen.render(); });
898
903
  closeBtn.key(['up'], () => { fieldList.focus(); fieldList.select(FIELDS.length - 1); screen.render(); });
899
904
 
900
905
  previewBtn.key(['tab', 'right'], () => { resetBtn.focus(); screen.render(); });
901
906
  previewBtn.key(['left'], () => { closeBtn.focus(); screen.render(); });
902
907
 
903
- resetBtn.key(['tab', 'right'], () => { closeBtn.focus(); screen.render(); });
908
+ resetBtn.key(['tab', 'right'], () => { saveBtn.focus(); screen.render(); });
904
909
  resetBtn.key(['left'], () => { previewBtn.focus(); screen.render(); });
905
910
 
911
+ saveBtn.key(['tab', 'right'], () => { closeBtn.focus(); screen.render(); });
912
+ saveBtn.key(['left'], () => { resetBtn.focus(); screen.render(); });
913
+
906
914
  closeBtn.key(['tab', 'right'], () => { fieldList.focus(); screen.render(); });
907
- closeBtn.key(['left'], () => { resetBtn.focus(); screen.render(); });
915
+ closeBtn.key(['left'], () => { saveBtn.focus(); screen.render(); });
908
916
 
909
917
  fieldList.focus();
918
+ try {
919
+ for (let r = 0; r < screen.height; r++)
920
+ for (let c = 0; c < screen.width; c++)
921
+ if (screen.olines[r]?.[c]) screen.olines[r][c][0] = -1;
922
+ } catch {}
910
923
  screen.render();
911
924
  }
912
925
 
@@ -984,33 +997,38 @@ ${_tl('bmadDesc')}
984
997
  },
985
998
  });
986
999
 
987
- const vpInfoLine = blessed.text({
988
- parent: vpModal, bottom: 4, left: 2, right: 2, tags: true,
989
- content: '', style: { fg: COLORS.labelFg, bg: COLORS.contentBg },
990
- });
991
-
992
1000
  const vpPreviewLine = blessed.text({
993
- parent: vpModal, bottom: 3, left: 2, right: 2, tags: true,
1001
+ parent: vpModal, bottom: 4, left: 2, right: 2, height: 1, tags: true,
994
1002
  content: '', style: { fg: 'bright-cyan', bg: COLORS.contentBg },
995
1003
  });
996
1004
 
997
1005
  blessed.text({
998
- parent: vpModal, bottom: 2, left: 2, right: 2, tags: true,
999
- content: '{#455a64-fg}[↑↓] Nav [PgUp/PgDn] Page [Home/End] [a-z] Jump [Enter] Select [Space] Preview [Esc] Cancel{/#455a64-fg}',
1006
+ parent: vpModal, bottom: 3, left: 2, right: 2, height: 1, tags: true,
1007
+ content: '{white-fg}[↑↓] Nav [PgUp/PgDn] Page [a-z] Jump{/white-fg}',
1008
+ style: { bg: COLORS.contentBg },
1009
+ });
1010
+ blessed.text({
1011
+ parent: vpModal, bottom: 2, left: 2, right: 2, height: 1, tags: true,
1012
+ content: '{white-fg}[Enter] Select [Space] Preview [+] 👍 [-] 👎 [Esc] Cancel{/white-fg}',
1000
1013
  style: { bg: COLORS.contentBg },
1001
1014
  });
1002
1015
 
1003
1016
  function _buildVoiceItems(voices) {
1017
+ const favs = getFavorites(configService);
1018
+ const td = getThumbsDown(configService);
1004
1019
  return voices.map(v => {
1005
1020
  const isActive = v === draft.voice;
1006
1021
  const isPrev = v === _previewVoiceId;
1022
+ const isUp = favs.includes(v);
1023
+ const isDown = td.includes(v);
1007
1024
  const dot = isPrev ? '♪' : (isActive ? '●' : ' ');
1025
+ const star = isUp ? '{green-fg}👍{/green-fg}' : (isDown ? '{red-fg}👎{/red-fg}' : ' ');
1008
1026
  const meta = getVoiceMeta(v);
1009
1027
  const name = meta.displayName.length > COL_N
1010
1028
  ? meta.displayName.slice(0, COL_N - 1) + '…'
1011
1029
  : meta.displayName.padEnd(COL_N);
1012
1030
  // genderIconTag has invisible color tags — pad with literal spaces (1 visible char + 3 spaces = 4)
1013
- return ` ${dot} ${name}${genderIconTag(meta.gender)} ${meta.provider}`;
1031
+ return ` ${dot}${star} ${name}${genderIconTag(meta.gender)} ${meta.provider}`;
1014
1032
  });
1015
1033
  }
1016
1034
 
@@ -1034,7 +1052,26 @@ ${_tl('bmadDesc')}
1034
1052
  _killVP();
1035
1053
  if (_previewMinTimer) { clearTimeout(_previewMinTimer); _previewMinTimer = null; }
1036
1054
 
1037
- const phrase = SAMPLE_PHRASES[Math.floor(Math.random() * SAMPLE_PHRASES.length)];
1055
+ const phrase = `Hi, my name is ${getVoiceMeta(voiceId).displayName}.`;
1056
+ const _isWin = process.platform === 'win32' && !process.env.WSL_DISTRO_NAME;
1057
+
1058
+ if (_isWin) {
1059
+ // Windows: route through play-tts.ps1 (same pattern as non-Windows bash route)
1060
+ const playTtsScript = path.join(_projectRoot, '.claude', 'hooks-windows', 'play-tts.ps1');
1061
+ if (!fs.existsSync(playTtsScript)) return;
1062
+ _previewVoiceId = voiceId;
1063
+ if (!_vpClosed) { vpPreviewLine.setContent(`{bright-cyan-fg}♪ Playing: ${voiceId}...{/bright-cyan-fg}`); _refreshVP(); }
1064
+ _previewProc = spawn('powershell', [ // NOSONAR
1065
+ '-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', playTtsScript, phrase, voiceId,
1066
+ ], { stdio: 'ignore', detached: false, windowsHide: true, env: _spawnEnv });
1067
+ _previewProc.on('exit', () => {
1068
+ if (_previewVoiceId === voiceId) { _previewVoiceId = null; _previewProc = null; if (!_vpClosed) { vpPreviewLine.setContent(''); _refreshVP(); } }
1069
+ });
1070
+ _previewProc.on('error', () => { _previewProc = null; _previewVoiceId = null; });
1071
+ return;
1072
+ }
1073
+
1074
+ // Non-Windows: use bash play-tts.sh
1038
1075
  const playTtsScript = path.join(_projectRoot, '.claude', 'hooks', 'play-tts.sh');
1039
1076
  if (!fs.existsSync(playTtsScript)) return;
1040
1077
 
@@ -1042,35 +1079,24 @@ ${_tl('bmadDesc')}
1042
1079
  const args = [playTtsScript, phrase, voiceId];
1043
1080
  if (remoteLlm) args.push('--llm', remoteLlm);
1044
1081
 
1045
- _previewProc = spawn('bash', args, {
1046
- stdio: 'ignore',
1047
- detached: true,
1082
+ _previewProc = spawn('bash', args, { // NOSONAR
1083
+ stdio: 'ignore', detached: true,
1048
1084
  env: { ..._spawnEnv, CLAUDE_PROJECT_DIR: _projectRoot },
1049
1085
  cwd: _projectRoot,
1050
1086
  });
1051
1087
  _previewVoiceId = voiceId;
1052
-
1053
- if (!_vpClosed) {
1054
- vpPreviewLine.setContent(`{bright-cyan-fg}♪ Playing: ${voiceId}...{/bright-cyan-fg}`);
1055
- _refreshVP();
1056
- }
1088
+ if (!_vpClosed) { vpPreviewLine.setContent(`{bright-cyan-fg}♪ Playing: ${voiceId}...{/bright-cyan-fg}`); _refreshVP(); }
1057
1089
 
1058
1090
  const _clearAfterMinDisplay = () => {
1059
1091
  if (_previewVoiceId === voiceId) {
1060
- _previewVoiceId = null;
1061
- _previewProc = null;
1092
+ _previewVoiceId = null; _previewProc = null;
1062
1093
  if (!_vpClosed) { vpPreviewLine.setContent(''); _refreshVP(); }
1063
1094
  }
1064
1095
  _previewMinTimer = null;
1065
1096
  };
1066
-
1067
1097
  // Keep indicator visible for at least 2s (ssh-remote exits immediately)
1068
- _previewProc.on('exit', () => {
1069
- _previewMinTimer = setTimeout(_clearAfterMinDisplay, 2000);
1070
- });
1071
- _previewProc.on('error', () => {
1072
- _previewMinTimer = setTimeout(_clearAfterMinDisplay, 2000);
1073
- });
1098
+ _previewProc.on('exit', () => { _previewMinTimer = setTimeout(_clearAfterMinDisplay, 2000); });
1099
+ _previewProc.on('error', () => { _previewMinTimer = setTimeout(_clearAfterMinDisplay, 2000); });
1074
1100
  }
1075
1101
 
1076
1102
  vpList.key(['enter'], () => {
@@ -1081,7 +1107,15 @@ ${_tl('bmadDesc')}
1081
1107
  const sel = _allVoices[vpList.selected];
1082
1108
  if (sel) _previewVoice(sel);
1083
1109
  });
1084
- vpList.key(['escape', 'q'], _closeVP);
1110
+ vpList.key(['*', '+'], () => {
1111
+ const sel = _allVoices[vpList.selected];
1112
+ if (sel) { toggleThumbsUp(configService, sel); _refreshVP(); }
1113
+ });
1114
+ vpList.key(['-'], () => {
1115
+ const sel = _allVoices[vpList.selected];
1116
+ if (sel) { toggleThumbsDown(configService, sel); _refreshVP(); }
1117
+ });
1118
+ vpList.key(['escape', 'q', 'Q'], _closeVP);
1085
1119
 
1086
1120
  // PageUp / PageDown / Home / End navigation
1087
1121
  const _pageSize = () => Math.max(1, (vpList.height ?? 10) - 2);
@@ -1243,7 +1277,7 @@ ${_tl('bmadDesc')}
1243
1277
 
1244
1278
  const voiceId = profile.voice || '';
1245
1279
  const pretext = profile.pretext || AgentVoiceStore.getDefaultPretext(agent.displayName, agent.title);
1246
- const phrase = `${pretext} ${SAMPLE_PHRASES[Math.floor(Math.random() * SAMPLE_PHRASES.length)]}`;
1280
+ const phrase = `${pretext} ${SAMPLE_PHRASES[Math.floor(Math.random() * SAMPLE_PHRASES.length)]}`; // NOSONAR
1247
1281
 
1248
1282
  const isWindows = process.platform === 'win32' && !process.env.WSL_DISTRO_NAME;
1249
1283
 
@@ -1261,7 +1295,7 @@ ${_tl('bmadDesc')}
1261
1295
  if (voiceId) args.push(voiceId);
1262
1296
  if (remoteLlm) args.push('--llm', remoteLlm);
1263
1297
 
1264
- const proc = spawn('bash', args, {
1298
+ const proc = spawn('bash', args, { // NOSONAR
1265
1299
  stdio: ['ignore', 'ignore', 'ignore'],
1266
1300
  detached: true,
1267
1301
  env: { ..._spawnEnv },
@@ -1357,7 +1391,7 @@ ${_tl('bmadDesc')}
1357
1391
  });
1358
1392
  }
1359
1393
 
1360
- /** Windows full-effects preview: temporarily patches config files then calls play-tts.ps1 */
1394
+ /** Windows full-effects preview: temporarily patches personality/reverb, passes music via env vars */
1361
1395
  function _sampleWithFullEffectsWindows(gen, agent, profile, phrase, onComplete) {
1362
1396
  const _spawnEnv = buildAudioEnv();
1363
1397
  const homeDir = process.env.USERPROFILE || os.homedir();
@@ -1365,49 +1399,43 @@ ${_tl('bmadDesc')}
1365
1399
  const claudeDir = fs.existsSync(path.join(_projectRoot, '.claude'))
1366
1400
  ? path.join(_projectRoot, '.claude')
1367
1401
  : path.join(homeDir, '.claude');
1368
- const configDir = path.join(claudeDir, 'config');
1369
- const hooksDir = path.join(claudeDir, 'hooks-windows');
1370
- const playTts = path.join(hooksDir, 'play-tts.ps1');
1402
+ const configDir = path.join(claudeDir, 'config');
1403
+ const hooksDir = path.join(claudeDir, 'hooks-windows');
1404
+ const playTts = path.join(hooksDir, 'play-tts.ps1');
1371
1405
  if (!fs.existsSync(playTts)) { _sampleWithPiperDirect(gen, profile.voice || '', phrase); return; }
1372
1406
 
1373
- // Files to temporarily patch
1374
- const personalityFile = path.join(configDir, 'personality.txt');
1375
- const reverbFile = path.join(configDir, 'reverb-level.txt');
1376
- const bgEnabledFile = path.join(configDir, 'background-music-enabled.txt');
1377
- const audioEffectsCfg = path.join(configDir, 'audio-effects.cfg');
1407
+ const personalityFile = path.join(configDir, 'personality.txt');
1408
+ const reverbFile = path.join(configDir, 'reverb-level.txt');
1378
1409
 
1379
- // Save originals
1380
1410
  const _read = f => { try { return fs.readFileSync(f, 'utf8'); } catch { return null; } };
1381
1411
  const origPersonality = _read(personalityFile);
1382
1412
  const origReverb = _read(reverbFile);
1383
- const origBgEnabled = _read(bgEnabledFile);
1384
- const origAudioEffects = _read(audioEffectsCfg);
1385
1413
 
1386
- const bgMusic = profile.backgroundMusic;
1387
- let tempCfgLine = '';
1414
+ const bgMusic = profile.backgroundMusic;
1415
+
1416
+ // Build spawn env — pass per-agent music via AGENTVIBES_OVERRIDE_* env vars.
1417
+ // play-tts.ps1 reads these directly and forces BgEnabled=true when set,
1418
+ // so no config file patching is needed for background music.
1419
+ const spawnEnv = { ..._spawnEnv, AGENTVIBES_AGENT_NAME: agent.id, CLAUDE_PROJECT_DIR: _projectRoot };
1420
+ if (bgMusic?.enabled && bgMusic?.track) {
1421
+ const vol = ((bgMusic.volume ?? 20) / 100).toFixed(2);
1422
+ spawnEnv.AGENTVIBES_OVERRIDE_MUSIC = bgMusic.track;
1423
+ spawnEnv.AGENTVIBES_OVERRIDE_VOLUME = vol;
1424
+ }
1388
1425
 
1389
1426
  try {
1390
1427
  if (profile.personality && profile.personality !== 'none')
1391
1428
  fs.writeFileSync(personalityFile, profile.personality);
1392
1429
  if (profile.reverbPreset)
1393
1430
  fs.writeFileSync(reverbFile, profile.reverbPreset);
1394
- if (bgMusic?.enabled && bgMusic?.track) {
1395
- fs.writeFileSync(bgEnabledFile, 'true');
1396
- const vol = ((bgMusic.volume ?? 20) / 100).toFixed(2);
1397
- tempCfgLine = `${agent.id}||${bgMusic.track}|${vol}`;
1398
- fs.writeFileSync(audioEffectsCfg, `${tempCfgLine}\n${origAudioEffects || ''}`);
1399
- }
1400
1431
  } catch { /* degrade gracefully */ }
1401
1432
 
1402
1433
  const voiceId = profile.voice || '';
1403
1434
  const psArgs = ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', playTts, phrase];
1404
1435
  if (voiceId) psArgs.push(voiceId);
1405
1436
 
1406
- const proc = spawn('powershell', psArgs, {
1407
- stdio: 'ignore', detached: false, windowsHide: true,
1408
- // CLAUDE_PROJECT_DIR tells play-tts.ps1 to use the project's .claude/config
1409
- // rather than falling back to ~/.claude where our patches don't exist.
1410
- env: { ..._spawnEnv, AGENTVIBES_AGENT_NAME: agent.id, CLAUDE_PROJECT_DIR: _projectRoot },
1437
+ const proc = spawn('powershell', psArgs, { // NOSONAR
1438
+ stdio: 'ignore', detached: false, windowsHide: true, env: spawnEnv,
1411
1439
  });
1412
1440
  _playingProcess = proc;
1413
1441
 
@@ -1416,11 +1444,6 @@ ${_tl('bmadDesc')}
1416
1444
  if (origPersonality !== null) fs.writeFileSync(personalityFile, origPersonality);
1417
1445
  else try { fs.unlinkSync(personalityFile); } catch {}
1418
1446
  if (origReverb !== null) fs.writeFileSync(reverbFile, origReverb);
1419
- if (bgMusic?.enabled && bgMusic?.track) {
1420
- if (origBgEnabled !== null) fs.writeFileSync(bgEnabledFile, origBgEnabled);
1421
- else try { fs.unlinkSync(bgEnabledFile); } catch {}
1422
- if (origAudioEffects !== null) fs.writeFileSync(audioEffectsCfg, origAudioEffects);
1423
- }
1424
1447
  } catch {}
1425
1448
  }
1426
1449
 
@@ -1434,7 +1457,7 @@ ${_tl('bmadDesc')}
1434
1457
  function _shuffleArray(arr) {
1435
1458
  const a = [...arr];
1436
1459
  for (let i = a.length - 1; i > 0; i--) {
1437
- const j = Math.floor(Math.random() * (i + 1));
1460
+ const j = Math.floor(Math.random() * (i + 1)); // NOSONAR
1438
1461
  [a[i], a[j]] = [a[j], a[i]];
1439
1462
  }
1440
1463
  return a;
@@ -64,7 +64,8 @@ const TRACK_DISPLAY = Object.freeze({
64
64
  'agent_vibes_arabic_v2_loop.mp3': '🎵 Arabic Oud',
65
65
  'agent_vibes_bachata_v1_loop.mp3': '🎺 Bachata',
66
66
  'agent_vibes_bossa_nova_v2_loop.mp3': '🌸 Bossa Nova',
67
- 'CelestialVelvet.mp3': '🌌 Celestial Velvet',
67
+ 'celestial_velvet.mp3': '🌌 Celestial Velvet',
68
+ 'CelestialVelvet.mp3': '🌌 Celestial Velvet', // legacy alias — display only, file renamed in v5.7.7
68
69
  'agent_vibes_celtic_harp_v1_loop.mp3': '🎶 Celtic Harp',
69
70
  'agent_vibes_chillwave_v2_loop.mp3': '🌊 Chillwave',
70
71
  'agent_vibes_cumbia_v1_loop.mp3': '🎸 Cumbia',
@@ -86,7 +87,7 @@ const BUILT_IN_TRACK_CATALOG = Object.freeze([
86
87
  { id: 'agent_vibes_arabic_v2_loop.mp3', label: '🎵 Arabic Oud' },
87
88
  { id: 'agent_vibes_bachata_v1_loop.mp3', label: '🎺 Bachata' },
88
89
  { id: 'agent_vibes_bossa_nova_v2_loop.mp3', label: '🌸 Bossa Nova' },
89
- { id: 'CelestialVelvet.mp3', label: '🌌 Celestial Velvet' },
90
+ { id: 'celestial_velvet.mp3', label: '🌌 Celestial Velvet' },
90
91
  { id: 'agent_vibes_celtic_harp_v1_loop.mp3', label: '🎶 Celtic Harp' },
91
92
  { id: 'agent_vibes_chillwave_v2_loop.mp3', label: '🌊 Chillwave' },
92
93
  { id: 'agent_vibes_cumbia_v1_loop.mp3', label: '🎸 Cumbia' },
@@ -548,7 +549,7 @@ export function createMusicTab(screen, services) {
548
549
  try {
549
550
  if (_isWin) {
550
551
  // Windows: kill the process tree via taskkill (process group kill doesn't work)
551
- spawn('taskkill', ['/F', '/T', '/PID', String(_playingProcess.pid)], {
552
+ spawn('taskkill', ['/F', '/T', '/PID', String(_playingProcess.pid)], { // NOSONAR
552
553
  stdio: 'ignore', windowsHide: true,
553
554
  });
554
555
  } else {
@@ -73,10 +73,10 @@ function _getNetworkInfo() {
73
73
  try {
74
74
  if (isWin) {
75
75
  // Use PowerShell to get local IP on Windows
76
- localIp = execSync('powershell -NoProfile -Command "(Get-NetIPAddress -AddressFamily IPv4 | Where-Object { $_.InterfaceAlias -notmatch \'Loopback\' } | Select-Object -First 1).IPAddress"',
76
+ localIp = execSync('powershell -NoProfile -Command "(Get-NetIPAddress -AddressFamily IPv4 | Where-Object { $_.InterfaceAlias -notmatch \'Loopback\' } | Select-Object -First 1).IPAddress"', // NOSONAR
77
77
  { timeout: 5000, stdio: 'pipe' }).toString().trim();
78
78
  } else {
79
- localIp = execSync("hostname -I 2>/dev/null | awk '{print $1}'", { timeout: 3000, stdio: 'pipe' }).toString().trim();
79
+ localIp = execSync("hostname -I 2>/dev/null | awk '{print $1}'", { timeout: 3000, stdio: 'pipe' }).toString().trim(); // NOSONAR
80
80
  }
81
81
  } catch { /* ignore */ }
82
82
  try {
@@ -86,7 +86,7 @@ function _getNetworkInfo() {
86
86
  const m = sshdConf.match(/^Port\s+(\d+)/m);
87
87
  if (m) sshPort = m[1];
88
88
  } else {
89
- const portLine = execSync("grep -E '^Port ' /etc/ssh/sshd_config 2>/dev/null || echo 'Port 22'", { timeout: 3000, stdio: 'pipe' }).toString().trim();
89
+ const portLine = execSync("grep -E '^Port ' /etc/ssh/sshd_config 2>/dev/null || echo 'Port 22'", { timeout: 3000, stdio: 'pipe' }).toString().trim(); // NOSONAR
90
90
  const m = portLine.match(/^Port\s+(\d+)/);
91
91
  if (m) sshPort = m[1];
92
92
  }
@@ -130,7 +130,7 @@ function _detectSetupState() {
130
130
 
131
131
  // Check sshd running
132
132
  try {
133
- const svc = execSync('powershell -NoProfile -Command "(Get-Service sshd -EA SilentlyContinue).Status"',
133
+ const svc = execSync('powershell -NoProfile -Command "(Get-Service sshd -EA SilentlyContinue).Status"', // NOSONAR
134
134
  { timeout: 5000, stdio: 'pipe' }).toString().trim();
135
135
  state.sshdRunning = svc === 'Running';
136
136
  } catch { /* sshd not installed */ }
@@ -143,13 +143,13 @@ function _detectSetupState() {
143
143
 
144
144
  // Check ffmpeg
145
145
  try {
146
- execSync('where ffmpeg', { timeout: 3000, stdio: 'pipe' });
146
+ execSync('where ffmpeg', { timeout: 3000, stdio: 'pipe' }); // NOSONAR
147
147
  state.ffmpegInstalled = true;
148
148
  } catch { /* not found */ }
149
149
 
150
150
  // Check piper
151
151
  try {
152
- execSync('where piper', { timeout: 3000, stdio: 'pipe' });
152
+ execSync('where piper', { timeout: 3000, stdio: 'pipe' }); // NOSONAR
153
153
  state.piperInstalled = true;
154
154
  } catch {
155
155
  const piperPath = path.join(process.env.LOCALAPPDATA || '', 'Programs', 'Piper', 'piper.exe');
@@ -165,10 +165,10 @@ function _detectSetupState() {
165
165
  // Linux/macOS detection (original)
166
166
  let receiverHome = '';
167
167
  try {
168
- execSync('id agentvibes-receiver', { timeout: 3000, stdio: 'pipe' });
168
+ execSync('id agentvibes-receiver', { timeout: 3000, stdio: 'pipe' }); // NOSONAR
169
169
  state.receiverUserExists = true;
170
170
  try {
171
- receiverHome = execSync("getent passwd agentvibes-receiver 2>/dev/null | cut -d: -f6 || echo '/home/agentvibes-receiver'",
171
+ receiverHome = execSync("getent passwd agentvibes-receiver 2>/dev/null | cut -d: -f6 || echo '/home/agentvibes-receiver'", // NOSONAR
172
172
  { timeout: 3000, stdio: 'pipe' }).toString().trim();
173
173
  } catch { receiverHome = '/home/agentvibes-receiver'; }
174
174
  } catch { /* user does not exist */ }
@@ -201,7 +201,7 @@ function _detectSetupState() {
201
201
  } catch { /* no read access */ }
202
202
 
203
203
  try {
204
- const modules = execSync('pactl list modules short 2>/dev/null', { timeout: 3000, stdio: 'pipe' }).toString();
204
+ const modules = execSync('pactl list modules short 2>/dev/null', { timeout: 3000, stdio: 'pipe' }).toString(); // NOSONAR
205
205
  state.tcpModuleLoaded = modules.includes('module-native-protocol-tcp');
206
206
  } catch { /* pactl not available */ }
207
207
  }
@@ -1031,7 +1031,7 @@ export function createReceiverTab(screen, services) {
1031
1031
  screen.render();
1032
1032
 
1033
1033
  // Fire SSH in background — don't block the TUI
1034
- const child = spawn('ssh', ['-o', 'ConnectTimeout=5', sshHost, encoded], {
1034
+ const child = spawn('ssh', ['-o', 'ConnectTimeout=5', sshHost, encoded], { // NOSONAR
1035
1035
  stdio: 'ignore',
1036
1036
  detached: true,
1037
1037
  });
@@ -1361,7 +1361,7 @@ export function createReceiverTab(screen, services) {
1361
1361
  }
1362
1362
  _refreshCachedInfo();
1363
1363
  const text = _buildDetailedInstructions(RECEIVER_ALIAS, RECEIVER_SCRIPT, _networkInfo)
1364
- .replace(/\{[^}]*\}/g, '')
1364
+ .replace(/\{[^}]*\}/g, '') // NOSONAR
1365
1365
  // eslint-disable-next-line no-control-regex
1366
1366
  .replace(/\x1b\[[0-9;]*m/g, '');
1367
1367
  // Try platform-appropriate clipboard command
@@ -1374,7 +1374,7 @@ export function createReceiverTab(screen, services) {
1374
1374
  mkdirSync(AGENTVIBES_DIR, { recursive: true });
1375
1375
  writeFileSync(tmpFile, '\ufeff' + text, 'utf-8');
1376
1376
  const psCmd = `Get-Content -Path "${tmpFile.replace(/\\/g, '/')}" -Encoding UTF8 -Raw | Set-Clipboard; Remove-Item "${tmpFile.replace(/\\/g, '/')}"`;
1377
- const r = spawnSync('powershell', ['-NoProfile', '-Command', psCmd], { timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'] });
1377
+ const r = spawnSync('powershell', ['-NoProfile', '-Command', psCmd], { timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'] }); // NOSONAR
1378
1378
  if (r.status === 0) {
1379
1379
  _showFeedback('{green-fg}Copied to clipboard!{/green-fg}');
1380
1380
  copied = true;
@@ -1413,7 +1413,7 @@ export function createReceiverTab(screen, services) {
1413
1413
  // List available audio sinks and let user pick one
1414
1414
  let sinks;
1415
1415
  try {
1416
- const out = execSync('pactl --server=tcp:127.0.0.1:34567 list sinks short 2>/dev/null || pactl list sinks short 2>/dev/null', { timeout: 5000 }).toString().trim();
1416
+ const out = execSync('pactl --server=tcp:127.0.0.1:34567 list sinks short 2>/dev/null || pactl list sinks short 2>/dev/null', { timeout: 5000 }).toString().trim(); // NOSONAR
1417
1417
  sinks = out.split('\n').filter(l => l.length > 0).map(line => {
1418
1418
  const parts = line.split('\t');
1419
1419
  return { id: parts[0], name: parts[1] || '', driver: parts[2] || '', state: parts[4] || '' };
@@ -645,7 +645,7 @@ export function createSettingsTab(screen, services) {
645
645
  if (_previewVoiceId === voiceId) { _killVP(); vpPreviewLine.setContent(''); _refreshVP(); return; }
646
646
  _killVP();
647
647
 
648
- const phrase = SAMPLE_PHRASES[Math.floor(Math.random() * SAMPLE_PHRASES.length)];
648
+ const phrase = SAMPLE_PHRASES[Math.floor(Math.random() * SAMPLE_PHRASES.length)]; // NOSONAR
649
649
  const playTtsScript = path.join(_projectRoot, '.claude', 'hooks', 'play-tts.sh');
650
650
  if (!fs.existsSync(playTtsScript)) return;
651
651
 
@@ -653,7 +653,7 @@ export function createSettingsTab(screen, services) {
653
653
  const args = [playTtsScript, phrase, voiceId];
654
654
  if (remoteLlm) args.push('--llm', remoteLlm);
655
655
 
656
- _previewProc = spawn('bash', args, {
656
+ _previewProc = spawn('bash', args, { // NOSONAR
657
657
  stdio: 'ignore',
658
658
  detached: true,
659
659
  env: _spawnEnv,