agentvibes 5.0.0 → 5.1.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.
@@ -20,7 +20,7 @@ import os from 'node:os';
20
20
  import crypto from 'node:crypto';
21
21
  import { spawn } from 'node:child_process';
22
22
  import {
23
- scanInstalledVoices, getVoiceMeta, PIPER_VOICES_DIR, SAMPLE_PHRASES, parseMultiSpeaker,
23
+ scanInstalledVoices, getVoiceMeta, genderIconTag, PIPER_VOICES_DIR, SAMPLE_PHRASES, parseMultiSpeaker,
24
24
  } from './voices-tab.js';
25
25
  import { LanguageService } from '../../services/language-service.js';
26
26
  import { SUPPORTED_LANGUAGES, t } from '../../i18n/strings.js';
@@ -525,7 +525,6 @@ export function createSettingsTab(screen, services) {
525
525
  navigationService?.openModal();
526
526
 
527
527
  let _allVoices = [];
528
- let _filterText = '';
529
528
  let _previewProc = null;
530
529
  let _previewVoiceId = null;
531
530
  let _vpClosed = false;
@@ -567,26 +566,16 @@ export function createSettingsTab(screen, services) {
567
566
  });
568
567
  vpModal.setFront();
569
568
 
569
+ const COL_N = 30;
570
+ const COL_G = 4;
570
571
  blessed.text({
571
- parent: vpModal, top: 1, left: 2,
572
- content: 'Search:', style: { fg: COLORS.labelFg, bg: COLORS.contentBg },
573
- });
574
- const vpSearch = blessed.textbox({
575
- parent: vpModal, top: 1, left: 11, width: 40, height: 1,
576
- inputOnFocus: true, keys: true,
577
- style: { fg: COLORS.valueFg, bg: 'blue', focus: { bg: 'cyan' } },
578
- });
579
-
580
- const COL_N = 28;
581
- const COL_G = 10;
582
- blessed.text({
583
- parent: vpModal, top: 2, left: 6, tags: true,
584
- content: `{cyan-fg}${'Name'.padEnd(COL_N)}${'Gender'.padEnd(COL_G)}Provider{/cyan-fg}`,
572
+ parent: vpModal, top: 1, left: 6, tags: true,
573
+ content: `{cyan-fg}${'Name'.padEnd(COL_N)}{/cyan-fg}{magenta-fg}♀{/magenta-fg}/{bright-cyan-fg}♂{/bright-cyan-fg} {cyan-fg}Provider{/cyan-fg}`,
585
574
  style: { bg: COLORS.contentBg },
586
575
  });
587
576
 
588
577
  const vpList = blessed.list({
589
- parent: vpModal, top: 3, left: 2, right: 2, bottom: 5,
578
+ parent: vpModal, top: 2, left: 2, right: 2, bottom: 5,
590
579
  keys: true, vi: true, mouse: true,
591
580
  border: { type: 'line' },
592
581
  scrollbar: { ch: '|', style: { fg: 'cyan' } },
@@ -606,16 +595,10 @@ export function createSettingsTab(screen, services) {
606
595
 
607
596
  blessed.text({
608
597
  parent: vpModal, bottom: 2, left: 2, right: 2, tags: true,
609
- content: '{white-fg}[↑↓/jk] Navigate [Enter] Select [Space] Preview [/] Search [Esc] Cancel{/white-fg}',
598
+ content: '{white-fg}[↑↓] Nav [PgUp/PgDn] Page [Home/End] [a-z] Jump [Enter] Select [Space] Preview [Esc] Cancel{/white-fg}',
610
599
  style: { bg: COLORS.contentBg },
611
600
  });
612
601
 
613
- function _getFiltered() {
614
- if (!_filterText) return _allVoices;
615
- const f = _filterText.toLowerCase();
616
- return _allVoices.filter(v => v.toLowerCase().includes(f));
617
- }
618
-
619
602
  function _buildItems(voices) {
620
603
  const currentVoice = providerService?.getActiveVoiceId() ?? '';
621
604
  return voices.map(v => {
@@ -626,7 +609,8 @@ export function createSettingsTab(screen, services) {
626
609
  const name = meta.displayName.length > COL_N
627
610
  ? meta.displayName.slice(0, COL_N - 1) + '…'
628
611
  : meta.displayName.padEnd(COL_N);
629
- return ` ${dot} ${name}${meta.gender.padEnd(COL_G)}${meta.provider}`;
612
+ // genderIconTag has invisible color tags — pad with literal spaces (1 visible char + 3 spaces = 4)
613
+ return ` ${dot} ${name}${genderIconTag(meta.gender)} ${meta.provider}`;
630
614
  });
631
615
  }
632
616
 
@@ -635,8 +619,10 @@ export function createSettingsTab(screen, services) {
635
619
  const savedIdx = vpList.selected ?? 0;
636
620
  const savedScroll = vpList.childBase ?? 0;
637
621
  _allVoices = scanInstalledVoices();
638
- const filtered = _getFiltered();
639
- const items = _buildItems(filtered);
622
+ // Sort by display name so the first-letter quick jump is intuitive
623
+ _allVoices.sort((a, b) => getVoiceMeta(a).displayName.localeCompare(
624
+ getVoiceMeta(b).displayName, undefined, { sensitivity: 'base' }));
625
+ const items = _buildItems(_allVoices);
640
626
  vpList.setItems(items.length > 0 ? items : [' (no voices found)']);
641
627
  vpList.select(Math.min(savedIdx, items.length - 1));
642
628
  vpList.childBase = Math.min(savedScroll, Math.max(0, items.length - (vpList.height - 2)));
@@ -704,14 +690,8 @@ export function createSettingsTab(screen, services) {
704
690
  piper.on('error', () => { _previewProc = null; _previewVoiceId = null; });
705
691
  }
706
692
 
707
- vpSearch.on('keypress', () => {
708
- setTimeout(() => { _filterText = vpSearch.getValue().trim(); _refreshVP(); }, 0);
709
- });
710
- vpSearch.key(['escape'], () => { vpList.focus(); screen.render(); });
711
- vpList.key(['/'], () => { vpSearch.clearValue(); vpSearch.focus(); screen.render(); });
712
693
  vpList.key(['enter'], () => {
713
- const filtered = _getFiltered();
714
- const sel = filtered[vpList.selected];
694
+ const sel = _allVoices[vpList.selected];
715
695
  if (sel) {
716
696
  if (providerService) providerService.setActiveVoice(sel);
717
697
  else configService.set('voice', sel);
@@ -719,15 +699,37 @@ export function createSettingsTab(screen, services) {
719
699
  _closeVP();
720
700
  });
721
701
  vpList.key(['space'], () => {
722
- const filtered = _getFiltered();
723
- const sel = filtered[vpList.selected];
702
+ const sel = _allVoices[vpList.selected];
724
703
  if (sel) _previewVoice(sel);
725
704
  });
726
705
  vpList.key(['escape', 'q'], _closeVP);
727
706
 
707
+ // PageUp / PageDown / Home / End navigation
708
+ const _pageSize = () => Math.max(1, (vpList.height ?? 10) - 2);
709
+ vpList.key(['pageup'], () => { vpList.up(_pageSize()); screen.render(); });
710
+ vpList.key(['pagedown'], () => { vpList.down(_pageSize()); screen.render(); });
711
+ vpList.key(['home'], () => { vpList.select(0); screen.render(); });
712
+ vpList.key(['end'], () => { vpList.select(Math.max(0, _allVoices.length - 1)); screen.render(); });
713
+
714
+ // First-letter quick jump: typing 'a' jumps to the first voice starting
715
+ // with A. Block keys reserved by the list widget (vi nav, cancel) so
716
+ // they don't get swallowed: q (cancel), j/k/g/h/l (vi navigation).
717
+ const _vpJumpBlocked = new Set(['j', 'k', 'g', 'h', 'l', 'q']);
718
+ vpList.on('keypress', (ch, key) => {
719
+ if (!ch || key?.ctrl || key?.meta) return;
720
+ if (!/^[a-zA-Z]$/.test(ch)) return;
721
+ const target = ch.toLowerCase();
722
+ if (_vpJumpBlocked.has(target)) return;
723
+ const idx = _allVoices.findIndex(v => {
724
+ const name = getVoiceMeta(v).displayName.toLowerCase();
725
+ return name.startsWith(target);
726
+ });
727
+ if (idx >= 0) { vpList.select(idx); screen.render(); }
728
+ });
729
+
728
730
  _refreshVP();
729
731
  const currentVoice = providerService?.getActiveVoiceId() ?? '';
730
- const activeIdx = _getFiltered().indexOf(currentVoice);
732
+ const activeIdx = _allVoices.indexOf(currentVoice);
731
733
  if (activeIdx >= 0) vpList.select(activeIdx);
732
734
  vpList.focus();
733
735
  screen.render();