agentvibes 4.4.1 → 4.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agentvibes/config.json +4 -4
- package/.claude/config/reverb-level.txt +1 -1
- package/.claude/github-star-reminder.txt +1 -1
- package/.claude/hooks-windows/bmad-speak.ps1 +112 -0
- package/.claude/hooks-windows/play-tts-piper.ps1 +3 -4
- package/.claude/hooks-windows/play-tts-sapi.ps1 +3 -4
- package/.claude/hooks-windows/play-tts-soprano.ps1 +2 -3
- package/.claude/hooks-windows/play-tts-termux-ssh.ps1 +138 -0
- package/.claude/hooks-windows/play-tts.ps1 +14 -6
- package/.claude/hooks-windows/provider-manager.ps1 +16 -1
- package/CLAUDE.md +4 -0
- package/README.md +39 -9
- package/RELEASE_NOTES.md +39 -0
- package/bin/agent-vibes +1 -1
- package/bin/agentvibes-voice-browser.js +1 -1
- package/bin/bmad-speak.js +52 -0
- package/bin/mcp-server.js +1 -1
- package/bin/test-bmad-pr +1 -1
- package/package.json +1 -1
- package/setup-windows.ps1 +4 -4
- package/src/console/app.js +58 -11
- package/src/console/tabs/agents-tab.js +61 -65
- package/src/console/tabs/help-tab.js +107 -54
- package/src/console/tabs/install-tab.js +107 -47
- package/src/console/tabs/music-tab.js +1030 -1011
- package/src/console/tabs/placeholder-tab.js +27 -0
- package/src/console/tabs/readme-tab.js +9 -7
- package/src/console/tabs/receiver-tab.js +23 -12
- package/src/console/tabs/settings-tab.js +4001 -3783
- package/src/console/tabs/voices-tab.js +1680 -1653
- package/src/console/widgets/personality-picker.js +35 -7
- package/src/console/widgets/reverb-picker.js +9 -6
- package/src/console/widgets/track-picker.js +6 -1
- package/src/i18n/de.js +201 -0
- package/src/i18n/en.js +201 -0
- package/src/i18n/es.js +201 -0
- package/src/i18n/fr.js +201 -0
- package/src/i18n/hi.js +201 -0
- package/src/i18n/ja.js +201 -0
- package/src/i18n/ko.js +201 -0
- package/src/i18n/pt.js +201 -0
- package/src/i18n/strings.js +54 -0
- package/src/i18n/zh-CN.js +201 -0
- package/src/installer/language-screen.js +31 -0
- package/src/installer.js +79 -25
- package/src/services/language-service.js +47 -0
- package/src/utils/file-ownership-verifier.js +2 -2
- package/src/utils/provider-validator.js +9 -13
- package/.claude/hooks-windows/play-tts-windows-piper.ps1 +0 -209
- package/.claude/hooks-windows/play-tts-windows-sapi.ps1 +0 -108
package/setup-windows.ps1
CHANGED
|
@@ -283,8 +283,8 @@ if (-not (Test-Path $HooksDir)) {
|
|
|
283
283
|
$HookScriptInfo = @(
|
|
284
284
|
@{ Name = "play-tts.ps1"; Desc = "Main TTS router - dispatches to active provider" },
|
|
285
285
|
@{ Name = "play-tts-soprano.ps1"; Desc = "Soprano neural voice provider (fastest)" },
|
|
286
|
-
@{ Name = "play-tts-
|
|
287
|
-
@{ Name = "play-tts-
|
|
286
|
+
@{ Name = "play-tts-piper.ps1"; Desc = "Piper offline neural voice provider" },
|
|
287
|
+
@{ Name = "play-tts-sapi.ps1"; Desc = "Windows built-in SAPI voice provider" },
|
|
288
288
|
@{ Name = "provider-manager.ps1"; Desc = "Switch between TTS providers" },
|
|
289
289
|
@{ Name = "voice-manager-windows.ps1"; Desc = "Browse and select voice models" },
|
|
290
290
|
@{ Name = "audio-cache-utils.ps1"; Desc = "Manage TTS audio file cache" },
|
|
@@ -765,11 +765,11 @@ try {
|
|
|
765
765
|
}
|
|
766
766
|
}
|
|
767
767
|
"piper" {
|
|
768
|
-
& "$HooksDir\play-tts-
|
|
768
|
+
& "$HooksDir\play-tts-piper.ps1" $TestMessage | Out-Null
|
|
769
769
|
Write-Ok "Piper TTS is working"
|
|
770
770
|
}
|
|
771
771
|
"sapi" {
|
|
772
|
-
& "$HooksDir\play-tts-
|
|
772
|
+
& "$HooksDir\play-tts-sapi.ps1" $TestMessage | Out-Null
|
|
773
773
|
Write-Ok "Windows SAPI is working"
|
|
774
774
|
}
|
|
775
775
|
}
|
package/src/console/app.js
CHANGED
|
@@ -14,7 +14,9 @@ import { fileURLToPath } from 'node:url';
|
|
|
14
14
|
import { spawnSync, execFileSync } from 'node:child_process';
|
|
15
15
|
import { NavigationService, TAB_ORDER } from '../services/navigation-service.js';
|
|
16
16
|
import { setupNavigation } from './navigation.js';
|
|
17
|
-
import { createPlaceholderTab, TAB_DISPLAY_LABELS, TAB_SHORTCUT_KEYS } from './tabs/placeholder-tab.js';
|
|
17
|
+
import { createPlaceholderTab, TAB_DISPLAY_LABELS, TAB_SHORTCUT_KEYS, getTabLabel } from './tabs/placeholder-tab.js';
|
|
18
|
+
import { LanguageService } from '../services/language-service.js';
|
|
19
|
+
import { t } from '../i18n/strings.js';
|
|
18
20
|
import { FOOTER_CONFIG, DEFAULT_FOOTER_COLOR } from './footer-config.js';
|
|
19
21
|
import { createModalOverlay } from './modals/modal-overlay.js';
|
|
20
22
|
import { BRAND_PINK } from './brand-colors.js';
|
|
@@ -169,7 +171,7 @@ export class AgentVibesConsole {
|
|
|
169
171
|
});
|
|
170
172
|
|
|
171
173
|
// Row 1: subtitle
|
|
172
|
-
blessed.text({
|
|
174
|
+
this._headerSubtitleText = blessed.text({
|
|
173
175
|
parent: this.headerBox,
|
|
174
176
|
top: 1,
|
|
175
177
|
left: 2,
|
|
@@ -180,7 +182,7 @@ export class AgentVibesConsole {
|
|
|
180
182
|
});
|
|
181
183
|
|
|
182
184
|
// Row 1: Quit shortcut — left-anchored after "Customization Tool" (18 chars at left:2)
|
|
183
|
-
blessed.text({
|
|
185
|
+
this._headerQuitText = blessed.text({
|
|
184
186
|
parent: this.headerBox,
|
|
185
187
|
top: 1,
|
|
186
188
|
left: 22,
|
|
@@ -291,9 +293,11 @@ export class AgentVibesConsole {
|
|
|
291
293
|
|
|
292
294
|
// One box per tab — direct screen children at absolute top:3. No tag parsing, no wrapping.
|
|
293
295
|
this._tabItems = {};
|
|
296
|
+
this._tabItemXOffsets = {}; // track x positions for label refresh
|
|
294
297
|
let xOffset = 1;
|
|
295
298
|
for (const id of TAB_ORDER) {
|
|
296
|
-
const
|
|
299
|
+
const lang = this._languageService?.getLang() ?? 'en';
|
|
300
|
+
const label = getTabLabel(id, lang);
|
|
297
301
|
const shortcutKey = TAB_SHORTCUT_KEYS[id] || label[0];
|
|
298
302
|
const text = ` [${shortcutKey}] ${label} `;
|
|
299
303
|
const el = blessed.box({
|
|
@@ -304,11 +308,13 @@ export class AgentVibesConsole {
|
|
|
304
308
|
height: 1,
|
|
305
309
|
content: text,
|
|
306
310
|
tags: false,
|
|
311
|
+
wrap: false,
|
|
307
312
|
keys: true,
|
|
308
313
|
focusable: true,
|
|
309
314
|
style: { fg: COLORS.focusCyan, bg: COLORS.tabBarBg },
|
|
310
315
|
});
|
|
311
316
|
this._tabItems[id] = el;
|
|
317
|
+
this._tabItemXOffsets[id] = xOffset;
|
|
312
318
|
xOffset += text.length + 1; // 1-space gap between tabs
|
|
313
319
|
}
|
|
314
320
|
|
|
@@ -359,9 +365,8 @@ export class AgentVibesConsole {
|
|
|
359
365
|
const el = this._tabItems[tabIds[i]];
|
|
360
366
|
|
|
361
367
|
// Blinking block cursor: replace trailing space with █, toggle at 500ms
|
|
362
|
-
|
|
363
|
-
const
|
|
364
|
-
const _blockContent = _baseContent.slice(0, -1) + '█';
|
|
368
|
+
// Always derive from current el.content so language changes are preserved.
|
|
369
|
+
const _getBaseContent = () => el.content.replace(/█$/, ' ');
|
|
365
370
|
let _cursorInterval = null;
|
|
366
371
|
let _cursorOn = false;
|
|
367
372
|
|
|
@@ -369,18 +374,21 @@ export class AgentVibesConsole {
|
|
|
369
374
|
el.style.fg = 'white';
|
|
370
375
|
el.style.bg = '#9c27b0'; // purple — cursor on this tab item
|
|
371
376
|
_cursorOn = true;
|
|
372
|
-
|
|
377
|
+
const _base = _getBaseContent();
|
|
378
|
+
const _block = _base.slice(0, -1) + '█';
|
|
379
|
+
el.setContent(_block);
|
|
373
380
|
this.screen.render();
|
|
374
381
|
if (_cursorInterval) { clearInterval(_cursorInterval); _cursorInterval = null; }
|
|
375
382
|
_cursorInterval = setInterval(() => {
|
|
376
383
|
_cursorOn = !_cursorOn;
|
|
377
|
-
|
|
384
|
+
const b = _getBaseContent();
|
|
385
|
+
el.setContent(_cursorOn ? b.slice(0, -1) + '█' : b);
|
|
378
386
|
this.screen.render();
|
|
379
387
|
}, 500);
|
|
380
388
|
});
|
|
381
389
|
el.on('blur', () => {
|
|
382
390
|
if (_cursorInterval) { clearInterval(_cursorInterval); _cursorInterval = null; }
|
|
383
|
-
el.setContent(
|
|
391
|
+
el.setContent(_getBaseContent());
|
|
384
392
|
// navigationService set up after _createTabBar, but blur fires lazily — safe
|
|
385
393
|
this._updateTabBar(this.navigationService?.getActiveTab() ?? tabIds[0]);
|
|
386
394
|
this.screen.render();
|
|
@@ -474,13 +482,47 @@ export class AgentVibesConsole {
|
|
|
474
482
|
}
|
|
475
483
|
}
|
|
476
484
|
|
|
485
|
+
// ---------------------------------------------------------------------------
|
|
486
|
+
// Private: Refresh all chrome strings (header subtitle, tab bar labels) when lang changes
|
|
487
|
+
|
|
488
|
+
_refreshChrome(lang) {
|
|
489
|
+
// Update header subtitle "Customization Tool"
|
|
490
|
+
if (this._headerSubtitleText) {
|
|
491
|
+
this._headerSubtitleText.setContent(`{green-fg}${t(lang, 'customizationTool')}{/green-fg}`);
|
|
492
|
+
}
|
|
493
|
+
if (this._headerQuitText) {
|
|
494
|
+
this._headerQuitText.setContent(`{#ef9a9a-fg}${t(lang, 'quitLabel')}{/#ef9a9a-fg}`);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// Update tab bar item labels — resize and reposition to fit translated labels
|
|
498
|
+
let xOffset = 1;
|
|
499
|
+
for (const id of TAB_ORDER) {
|
|
500
|
+
const el = this._tabItems?.[id];
|
|
501
|
+
if (!el) continue;
|
|
502
|
+
const label = getTabLabel(id, lang);
|
|
503
|
+
const shortcutKey = TAB_SHORTCUT_KEYS[id] || label[0];
|
|
504
|
+
const text = ` [${shortcutKey}] ${label} `;
|
|
505
|
+
el.left = xOffset;
|
|
506
|
+
el.width = text.length;
|
|
507
|
+
el.setContent(text);
|
|
508
|
+
xOffset += text.length + 1;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// Update active tab's footer text if it supports language-aware footer
|
|
512
|
+
const activeId = this.navigationService?.getActiveTab();
|
|
513
|
+
if (activeId) this._updateContextFooter(activeId);
|
|
514
|
+
|
|
515
|
+
this.screen.render();
|
|
516
|
+
}
|
|
517
|
+
|
|
477
518
|
// ---------------------------------------------------------------------------
|
|
478
519
|
// Private: Render tab bar content string for given active tab
|
|
479
520
|
// (kept as a pure helper for unit tests; real rendering uses _updateTabBar)
|
|
480
521
|
|
|
481
522
|
_renderTabBarContent(activeTabId) {
|
|
523
|
+
const lang = this._languageService?.getLang() ?? 'en';
|
|
482
524
|
return TAB_ORDER.map(id => {
|
|
483
|
-
const label =
|
|
525
|
+
const label = getTabLabel(id, lang);
|
|
484
526
|
const shortcutKey = TAB_SHORTCUT_KEYS[id] || label[0];
|
|
485
527
|
if (id === activeTabId) {
|
|
486
528
|
return `{bold}{white-fg}[${shortcutKey}] ${label}{/white-fg}{/bold}`;
|
|
@@ -614,9 +656,14 @@ export class AgentVibesConsole {
|
|
|
614
656
|
const providerService = new ProviderService(configService);
|
|
615
657
|
this._configService = configService;
|
|
616
658
|
this._providerService = providerService;
|
|
659
|
+
const languageService = new LanguageService();
|
|
660
|
+
this._languageService = languageService;
|
|
661
|
+
// Refresh UI chrome when language changes
|
|
662
|
+
languageService.onChange(lang => this._refreshChrome(lang));
|
|
617
663
|
const services = {
|
|
618
664
|
configService,
|
|
619
665
|
providerService,
|
|
666
|
+
languageService,
|
|
620
667
|
navigationService: this.navigationService,
|
|
621
668
|
updateHeaderStatus: () => this._updateHeaderStatus(),
|
|
622
669
|
focusMainTabBar: () => {
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
import { buildAudioEnv, detectWavPlayer } from '../audio-env.js';
|
|
22
22
|
import { destroyList } from '../widgets/destroy-list.js';
|
|
23
23
|
import { BRAND_PINK } from '../brand-colors.js';
|
|
24
|
+
import { t } from '../../i18n/strings.js';
|
|
24
25
|
import crypto from 'node:crypto';
|
|
25
26
|
import fs from 'node:fs';
|
|
26
27
|
import os from 'node:os';
|
|
@@ -57,8 +58,8 @@ const COLORS = {
|
|
|
57
58
|
linkFg: 'bright-cyan',
|
|
58
59
|
};
|
|
59
60
|
|
|
60
|
-
const
|
|
61
|
-
const
|
|
61
|
+
const _FOOTER_BMAD_EN = '[↑↓/jk] Navigate [Space] Preview [Enter] Configure [A] Auto-assign [B] Bulk [X] Reset [Q] Quit';
|
|
62
|
+
const _FOOTER_NOBMAD_EN = '[Tab] Switch Tab [Q] Quit';
|
|
62
63
|
|
|
63
64
|
const _modalTitle = (text) => ` {${BRAND_PINK}-fg}${text}{/${BRAND_PINK}-fg} `;
|
|
64
65
|
|
|
@@ -85,53 +86,43 @@ function createTestStub() {
|
|
|
85
86
|
hide: () => {},
|
|
86
87
|
onFocus: () => {},
|
|
87
88
|
onBlur: () => {},
|
|
88
|
-
getFooterText: () =>
|
|
89
|
+
getFooterText: () => _FOOTER_BMAD_EN,
|
|
89
90
|
getFooterColor: () => COLORS.footerBg,
|
|
90
91
|
};
|
|
91
92
|
}
|
|
92
93
|
|
|
93
94
|
// ---------------------------------------------------------------------------
|
|
94
|
-
// No-BMAD onboarding content
|
|
95
95
|
|
|
96
|
-
|
|
96
|
+
/**
|
|
97
|
+
* Create the Agents tab component.
|
|
98
|
+
*/
|
|
99
|
+
export function createAgentsTab(screen, services) {
|
|
100
|
+
if (IS_TEST) return createTestStub();
|
|
101
|
+
|
|
102
|
+
const { configService, providerService, focusMainTabBar, navigationService, languageService } = services;
|
|
103
|
+
const _tl = (key) => languageService ? languageService.t(key) : t('en', key);
|
|
97
104
|
|
|
98
|
-
|
|
105
|
+
function _buildOnboardingText() {
|
|
106
|
+
return `{bold}{#ce93d8-fg}${_tl('bmadTitle')}{/#ce93d8-fg}{/bold}
|
|
99
107
|
|
|
100
|
-
|
|
101
|
-
framework module within the BMad Method Ecosystem that helps you build
|
|
102
|
-
software through the whole process from ideation and planning all the way
|
|
103
|
-
through agentic implementation. It provides specialized AI agents, guided
|
|
104
|
-
workflows, and intelligent planning that adapts to your project's
|
|
105
|
-
complexity, whether you're fixing a bug or building an enterprise platform.
|
|
108
|
+
{bold}${_tl('bmadWhatIsHeader')}{/bold}
|
|
106
109
|
|
|
107
|
-
|
|
108
|
-
Cursor, or GitHub Copilot, you're ready to get started.
|
|
110
|
+
${_tl('bmadDesc')}
|
|
109
111
|
|
|
110
112
|
|
|
111
|
-
{bold}
|
|
113
|
+
{bold}${_tl('bmadInstallHeader')}{/bold}
|
|
112
114
|
|
|
113
115
|
{bright-cyan-fg}npx bmad-method install{/bright-cyan-fg}
|
|
114
116
|
|
|
115
117
|
|
|
116
|
-
{bold}
|
|
118
|
+
{bold}${_tl('bmadLearnMoreHeader')}{/bold}
|
|
117
119
|
|
|
118
120
|
{bright-cyan-fg}https://docs.bmad-method.org/{/bright-cyan-fg}
|
|
119
121
|
{bright-cyan-fg}https://github.com/bmad-code-org/BMAD-METHOD{/bright-cyan-fg}
|
|
120
122
|
|
|
121
123
|
|
|
122
|
-
{#90a4ae-fg}
|
|
123
|
-
|
|
124
|
-
music independently.{/#90a4ae-fg}`;
|
|
125
|
-
|
|
126
|
-
// ---------------------------------------------------------------------------
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Create the Agents tab component.
|
|
130
|
-
*/
|
|
131
|
-
export function createAgentsTab(screen, services) {
|
|
132
|
-
if (IS_TEST) return createTestStub();
|
|
133
|
-
|
|
134
|
-
const { configService, providerService, focusMainTabBar, navigationService } = services;
|
|
124
|
+
{#90a4ae-fg}${_tl('bmadInstalledNote')}{/#90a4ae-fg}`;
|
|
125
|
+
}
|
|
135
126
|
const voiceStore = new AgentVoiceStore();
|
|
136
127
|
|
|
137
128
|
// Capture cwd once at construction (L1 fix)
|
|
@@ -183,10 +174,14 @@ export function createAgentsTab(screen, services) {
|
|
|
183
174
|
keys: true,
|
|
184
175
|
vi: true,
|
|
185
176
|
mouse: true,
|
|
186
|
-
content:
|
|
177
|
+
content: _buildOnboardingText(),
|
|
187
178
|
style: { fg: COLORS.labelFg, bg: COLORS.contentBg },
|
|
188
179
|
});
|
|
189
180
|
|
|
181
|
+
onboardingBox.key(['escape'], () => {
|
|
182
|
+
if (typeof focusMainTabBar === 'function') { focusMainTabBar(); screen.render(); }
|
|
183
|
+
});
|
|
184
|
+
|
|
190
185
|
// -------------------------------------------------------------------------
|
|
191
186
|
// BMAD state — section header
|
|
192
187
|
|
|
@@ -486,20 +481,6 @@ export function createAgentsTab(screen, services) {
|
|
|
486
481
|
}
|
|
487
482
|
}
|
|
488
483
|
|
|
489
|
-
// -------------------------------------------------------------------------
|
|
490
|
-
// Resolve piper binary — shared helper to avoid duplication (#153)
|
|
491
|
-
|
|
492
|
-
function _resolvePiperBin() {
|
|
493
|
-
if (process.platform !== 'win32' || process.env.WSL_DISTRO_NAME) return 'piper';
|
|
494
|
-
const localAppData = process.env.LOCALAPPDATA ||
|
|
495
|
-
(process.env.USERPROFILE ? path.join(process.env.USERPROFILE, 'AppData', 'Local') : null);
|
|
496
|
-
if (localAppData) {
|
|
497
|
-
const exePath = path.join(localAppData, 'Programs', 'Piper', 'piper.exe');
|
|
498
|
-
if (fs.existsSync(exePath)) return exePath;
|
|
499
|
-
}
|
|
500
|
-
return 'piper';
|
|
501
|
-
}
|
|
502
|
-
|
|
503
484
|
// -------------------------------------------------------------------------
|
|
504
485
|
// Kill any playing preview
|
|
505
486
|
|
|
@@ -935,7 +916,16 @@ export function createAgentsTab(screen, services) {
|
|
|
935
916
|
const tempWav = _secureTempWav('vp');
|
|
936
917
|
const phrase = SAMPLE_PHRASES[Math.floor(Math.random() * SAMPLE_PHRASES.length)];
|
|
937
918
|
|
|
938
|
-
|
|
919
|
+
// Resolve piper binary (on Windows, find piper.exe)
|
|
920
|
+
let _piperBin = 'piper';
|
|
921
|
+
if (_isWin) {
|
|
922
|
+
const _lad = process.env.LOCALAPPDATA ||
|
|
923
|
+
(process.env.USERPROFILE ? path.join(process.env.USERPROFILE, 'AppData', 'Local') : null);
|
|
924
|
+
if (_lad) {
|
|
925
|
+
const _ep = path.join(_lad, 'Programs', 'Piper', 'piper.exe');
|
|
926
|
+
if (fs.existsSync(_ep)) _piperBin = _ep;
|
|
927
|
+
}
|
|
928
|
+
}
|
|
939
929
|
|
|
940
930
|
const args = ['--model', voicePath, '--output_file', tempWav];
|
|
941
931
|
if (_ms.speakerId != null) args.push('--speaker', String(_ms.speakerId));
|
|
@@ -1112,7 +1102,15 @@ export function createAgentsTab(screen, services) {
|
|
|
1112
1102
|
/** Windows-native sample: piper.exe → wav → detectWavPlayer */
|
|
1113
1103
|
function _sampleWithPiperDirect(gen, voiceId, phrase) {
|
|
1114
1104
|
const _spawnEnv = buildAudioEnv();
|
|
1115
|
-
|
|
1105
|
+
|
|
1106
|
+
// Resolve piper binary
|
|
1107
|
+
let piperBin = 'piper';
|
|
1108
|
+
const localAppData = process.env.LOCALAPPDATA ||
|
|
1109
|
+
(process.env.USERPROFILE ? path.join(process.env.USERPROFILE, 'AppData', 'Local') : null);
|
|
1110
|
+
if (localAppData) {
|
|
1111
|
+
const exePath = path.join(localAppData, 'Programs', 'Piper', 'piper.exe');
|
|
1112
|
+
if (fs.existsSync(exePath)) piperBin = exePath;
|
|
1113
|
+
}
|
|
1116
1114
|
|
|
1117
1115
|
// Resolve voice model path
|
|
1118
1116
|
const ms = parseMultiSpeaker(voiceId);
|
|
@@ -1138,7 +1136,6 @@ export function createAgentsTab(screen, services) {
|
|
|
1138
1136
|
_playingProcess = piper;
|
|
1139
1137
|
|
|
1140
1138
|
piper.on('exit', (code) => {
|
|
1141
|
-
// Generation changed — another preview was triggered; clean up silently
|
|
1142
1139
|
if (gen !== _playGeneration) {
|
|
1143
1140
|
try { fs.unlinkSync(tempWav); } catch {}
|
|
1144
1141
|
return;
|
|
@@ -1150,12 +1147,6 @@ export function createAgentsTab(screen, services) {
|
|
|
1150
1147
|
return;
|
|
1151
1148
|
}
|
|
1152
1149
|
|
|
1153
|
-
// Re-check generation after piper exit to close the race window (#154)
|
|
1154
|
-
if (gen !== _playGeneration) {
|
|
1155
|
-
try { fs.unlinkSync(tempWav); } catch {}
|
|
1156
|
-
return;
|
|
1157
|
-
}
|
|
1158
|
-
|
|
1159
1150
|
// Play the synthesized wav
|
|
1160
1151
|
const wavPlayer = detectWavPlayer(_spawnEnv);
|
|
1161
1152
|
if (!wavPlayer) {
|
|
@@ -1184,7 +1175,6 @@ export function createAgentsTab(screen, services) {
|
|
|
1184
1175
|
|
|
1185
1176
|
piper.on('error', () => {
|
|
1186
1177
|
if (gen === _playGeneration) { _playingProcess = null; _stopSpinner(); }
|
|
1187
|
-
try { fs.unlinkSync(tempWav); } catch {}
|
|
1188
1178
|
});
|
|
1189
1179
|
}
|
|
1190
1180
|
|
|
@@ -1202,12 +1192,10 @@ export function createAgentsTab(screen, services) {
|
|
|
1202
1192
|
|
|
1203
1193
|
// Common first-name → gender map for gender-aware auto-assign.
|
|
1204
1194
|
// Only needs to cover names likely used as BMAD agent display names.
|
|
1205
|
-
// Ambiguous names (sam, charlie, dana, max, pat, etc.) are intentionally
|
|
1206
|
-
// omitted so they fall through to the gender-neutral 'other' pool (#156).
|
|
1207
1195
|
const _NAME_GENDER = {
|
|
1208
1196
|
// Female
|
|
1209
1197
|
amelia: 'Female', amy: 'Female', anna: 'Female', betty: 'Female',
|
|
1210
|
-
claire: 'Female', emma: 'Female', faye: 'Female',
|
|
1198
|
+
claire: 'Female', dana: 'Female', emma: 'Female', faye: 'Female',
|
|
1211
1199
|
grace: 'Female', heather: 'Female', ivy: 'Female', jane: 'Female',
|
|
1212
1200
|
jenny: 'Female', julia: 'Female', kate: 'Female', laura: 'Female',
|
|
1213
1201
|
lily: 'Female', maria: 'Female', mary: 'Female', nina: 'Female',
|
|
@@ -1216,12 +1204,12 @@ export function createAgentsTab(screen, services) {
|
|
|
1216
1204
|
wendy: 'Female', zoe: 'Female',
|
|
1217
1205
|
// Male
|
|
1218
1206
|
alan: 'Male', barry: 'Male', bob: 'Male', carl: 'Male',
|
|
1219
|
-
dan: 'Male', david: 'Male', eric: 'Male',
|
|
1207
|
+
charlie: 'Male', dan: 'Male', david: 'Male', eric: 'Male',
|
|
1220
1208
|
frank: 'Male', george: 'Male', hank: 'Male', jack: 'Male',
|
|
1221
1209
|
james: 'Male', joe: 'Male', john: 'Male', kevin: 'Male',
|
|
1222
|
-
leo: 'Male', mark: 'Male', murat: 'Male',
|
|
1210
|
+
leo: 'Male', mark: 'Male', max: 'Male', murat: 'Male',
|
|
1223
1211
|
nick: 'Male', oscar: 'Male', paul: 'Male', ray: 'Male',
|
|
1224
|
-
ryan: 'Male', saif: 'Male', steve: 'Male',
|
|
1212
|
+
ryan: 'Male', saif: 'Male', sam: 'Male', steve: 'Male',
|
|
1225
1213
|
tom: 'Male', victor: 'Male', winston: 'Male', zach: 'Male',
|
|
1226
1214
|
};
|
|
1227
1215
|
|
|
@@ -1250,17 +1238,15 @@ export function createAgentsTab(screen, services) {
|
|
|
1250
1238
|
// Assign matching-gender voices first, then fall back to any available
|
|
1251
1239
|
function assignGroup(agents, preferredPool, fallbackPools) {
|
|
1252
1240
|
const allPools = [preferredPool, ...fallbackPools];
|
|
1253
|
-
let reuseIdx = 0;
|
|
1254
1241
|
agents.forEach(agent => {
|
|
1255
1242
|
let voice = null;
|
|
1256
1243
|
for (const pool of allPools) {
|
|
1257
1244
|
voice = pool.find(v => !usedVoices.has(v));
|
|
1258
1245
|
if (voice) break;
|
|
1259
1246
|
}
|
|
1260
|
-
// If all unique voices exhausted,
|
|
1247
|
+
// If all unique voices exhausted, reuse from preferred pool
|
|
1261
1248
|
if (!voice && preferredPool.length > 0) {
|
|
1262
|
-
voice = preferredPool[
|
|
1263
|
-
reuseIdx++;
|
|
1249
|
+
voice = preferredPool[usedVoices.size % preferredPool.length];
|
|
1264
1250
|
}
|
|
1265
1251
|
if (voice) {
|
|
1266
1252
|
usedVoices.add(voice);
|
|
@@ -1642,6 +1628,16 @@ export function createAgentsTab(screen, services) {
|
|
|
1642
1628
|
if (typeof focusMainTabBar === 'function') { focusMainTabBar(); screen.render(); }
|
|
1643
1629
|
});
|
|
1644
1630
|
|
|
1631
|
+
// -------------------------------------------------------------------------
|
|
1632
|
+
// Language change handler
|
|
1633
|
+
|
|
1634
|
+
if (languageService) {
|
|
1635
|
+
languageService.onChange(() => {
|
|
1636
|
+
onboardingBox.setContent(_buildOnboardingText());
|
|
1637
|
+
screen.render();
|
|
1638
|
+
});
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1645
1641
|
// -------------------------------------------------------------------------
|
|
1646
1642
|
// Tab Component Contract
|
|
1647
1643
|
|
|
@@ -1674,7 +1670,7 @@ export function createAgentsTab(screen, services) {
|
|
|
1674
1670
|
},
|
|
1675
1671
|
|
|
1676
1672
|
getFooterText() {
|
|
1677
|
-
return _bmadDetected ?
|
|
1673
|
+
return _bmadDetected ? _tl('bmadFooterBmad') : _tl('bmadFooterNobmad');
|
|
1678
1674
|
},
|
|
1679
1675
|
|
|
1680
1676
|
getFooterColor() {
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
* Features: keyboard shortcuts reference, two sections, [/] search.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
import { t } from '../../i18n/strings.js';
|
|
12
|
+
|
|
11
13
|
const IS_TEST = process.env.AGENTVIBES_TEST_MODE === 'true';
|
|
12
14
|
|
|
13
15
|
let blessed;
|
|
@@ -28,65 +30,60 @@ const COLORS = {
|
|
|
28
30
|
footerBg: '#607d8b', // Gray — Help tab footer
|
|
29
31
|
};
|
|
30
32
|
|
|
31
|
-
const FOOTER_TEXT = '[↑↓/jk] Scroll [/] Search [PgUp/PgDn] Page [S/V/M/A/R] Tab [Q] Quit';
|
|
32
|
-
|
|
33
33
|
// ---------------------------------------------------------------------------
|
|
34
|
-
// Keyboard shortcuts data
|
|
35
|
-
|
|
36
|
-
const SHORTCUT_SECTIONS = Object.freeze([
|
|
37
|
-
{
|
|
38
|
-
title: 'Global Shortcuts',
|
|
39
|
-
shortcuts: [
|
|
40
|
-
{ key: 'Q', desc: 'Quit the console' },
|
|
41
|
-
{ key: 'Ctrl+C', desc: 'Force quit' },
|
|
42
|
-
{ key: 'S', desc: 'Switch to Settings tab' },
|
|
43
|
-
{ key: 'V', desc: 'Switch to Voices tab' },
|
|
44
|
-
{ key: 'M', desc: 'Switch to Music tab' },
|
|
45
|
-
{ key: 'R', desc: 'Switch to Readme tab' },
|
|
46
|
-
{ key: 'H', desc: 'Switch to Help tab' },
|
|
47
|
-
{ key: 'I', desc: 'Switch to Install tab' },
|
|
48
|
-
{ key: 'Esc', desc: 'Close modal / go back' },
|
|
49
|
-
],
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
title: 'Navigation Shortcuts',
|
|
53
|
-
shortcuts: [
|
|
54
|
-
{ key: '↑↓ / j k', desc: 'Navigate lists' },
|
|
55
|
-
{ key: 'Enter', desc: 'Select / activate' },
|
|
56
|
-
{ key: 'Space', desc: 'Toggle / preview' },
|
|
57
|
-
{ key: 'Tab', desc: 'Next button' },
|
|
58
|
-
{ key: 'Shift+Tab', desc: 'Previous button' },
|
|
59
|
-
{ key: '/', desc: 'Open search/filter' },
|
|
60
|
-
{ key: 'F', desc: 'Toggle favorites filter (Voices/Music)' },
|
|
61
|
-
{ key: '*', desc: 'Toggle favorite (Music tab)' },
|
|
62
|
-
{ key: 'M', desc: 'Toggle music on/off (Music tab)' },
|
|
63
|
-
|
|
64
|
-
],
|
|
65
|
-
},
|
|
66
|
-
{
|
|
67
|
-
title: 'Tab Color Guide',
|
|
68
|
-
shortcuts: [
|
|
69
|
-
{ key: 'Blue (#2196f3)', desc: 'Settings tab footer' },
|
|
70
|
-
{ key: 'Teal (#00695c)', desc: 'Voices tab footer' },
|
|
71
|
-
{ key: 'Orange (#ff9800)', desc: 'Music tab footer' },
|
|
72
|
-
|
|
73
|
-
{ key: 'Dark (#455a64)', desc: 'Readme tab footer' },
|
|
74
|
-
{ key: 'Gray (#607d8b)', desc: 'Help tab footer' },
|
|
75
|
-
{ key: 'Indigo (#3f51b5)', desc: 'Install tab footer' },
|
|
76
|
-
],
|
|
77
|
-
},
|
|
78
|
-
]);
|
|
79
34
|
|
|
80
35
|
/**
|
|
81
36
|
* Return all shortcut sections.
|
|
82
37
|
* @returns {{ title: string, shortcuts: { key: string, desc: string }[] }[]}
|
|
83
38
|
*/
|
|
84
39
|
export function getShortcutSections() {
|
|
85
|
-
return [
|
|
40
|
+
return [
|
|
41
|
+
{
|
|
42
|
+
title: 'Global Shortcuts',
|
|
43
|
+
shortcuts: [
|
|
44
|
+
{ key: 'Q', desc: 'Quit the console' },
|
|
45
|
+
{ key: 'Ctrl+C', desc: 'Force quit' },
|
|
46
|
+
{ key: 'S', desc: 'Switch to Settings tab' },
|
|
47
|
+
{ key: 'V', desc: 'Switch to Voices tab' },
|
|
48
|
+
{ key: 'M', desc: 'Switch to Music tab' },
|
|
49
|
+
{ key: 'R', desc: 'Switch to Readme tab' },
|
|
50
|
+
{ key: 'H', desc: 'Switch to Help tab' },
|
|
51
|
+
{ key: 'I', desc: 'Switch to Install tab' },
|
|
52
|
+
{ key: 'Esc', desc: 'Close modal / go back' },
|
|
53
|
+
],
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
title: 'Navigation Shortcuts',
|
|
57
|
+
shortcuts: [
|
|
58
|
+
{ key: '↑↓ / j k', desc: 'Navigate lists' },
|
|
59
|
+
{ key: 'Enter', desc: 'Select / activate' },
|
|
60
|
+
{ key: 'Space', desc: 'Toggle / preview' },
|
|
61
|
+
{ key: 'Tab', desc: 'Next button' },
|
|
62
|
+
{ key: 'Shift+Tab', desc: 'Previous button' },
|
|
63
|
+
{ key: '/', desc: 'Open search/filter' },
|
|
64
|
+
{ key: 'F', desc: 'Toggle favorites filter (Voices/Music)' },
|
|
65
|
+
{ key: '*', desc: 'Toggle favorite (Music tab)' },
|
|
66
|
+
{ key: 'M', desc: 'Toggle music on/off (Music tab)' },
|
|
67
|
+
],
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
title: 'Tab Color Guide',
|
|
71
|
+
shortcuts: [
|
|
72
|
+
{ key: 'Blue (#2196f3)', desc: 'Settings tab footer' },
|
|
73
|
+
{ key: 'Teal (#00695c)', desc: 'Voices tab footer' },
|
|
74
|
+
{ key: 'Orange (#ff9800)', desc: 'Music tab footer' },
|
|
75
|
+
{ key: 'Dark (#455a64)', desc: 'Readme tab footer' },
|
|
76
|
+
{ key: 'Gray (#607d8b)', desc: 'Help tab footer' },
|
|
77
|
+
{ key: 'Indigo (#3f51b5)', desc: 'Install tab footer' },
|
|
78
|
+
],
|
|
79
|
+
},
|
|
80
|
+
];
|
|
86
81
|
}
|
|
87
82
|
|
|
88
83
|
// ---------------------------------------------------------------------------
|
|
89
84
|
|
|
85
|
+
const _FOOTER_TEXT_EN = '[↑↓/jk] Scroll [/] Search [PgUp/PgDn] Page [S/V/M/A/R] Tab [Q] Quit';
|
|
86
|
+
|
|
90
87
|
function createTestStub() {
|
|
91
88
|
return {
|
|
92
89
|
box: {},
|
|
@@ -94,7 +91,7 @@ function createTestStub() {
|
|
|
94
91
|
hide: () => {},
|
|
95
92
|
onFocus: () => {},
|
|
96
93
|
onBlur: () => {},
|
|
97
|
-
getFooterText: () =>
|
|
94
|
+
getFooterText: () => _FOOTER_TEXT_EN,
|
|
98
95
|
getFooterColor: () => COLORS.footerBg,
|
|
99
96
|
};
|
|
100
97
|
}
|
|
@@ -111,7 +108,52 @@ function createTestStub() {
|
|
|
111
108
|
export function createHelpTab(screen, services) {
|
|
112
109
|
if (IS_TEST) return createTestStub();
|
|
113
110
|
|
|
114
|
-
const { focusMainTabBar } = services;
|
|
111
|
+
const { focusMainTabBar, languageService } = services;
|
|
112
|
+
const _tl = (key) => languageService ? languageService.t(key) : t('en', key);
|
|
113
|
+
|
|
114
|
+
function _buildSections() {
|
|
115
|
+
return [
|
|
116
|
+
{
|
|
117
|
+
title: _tl('helpSectionGlobal'),
|
|
118
|
+
shortcuts: [
|
|
119
|
+
{ key: 'Q', desc: _tl('helpQuit') },
|
|
120
|
+
{ key: 'Ctrl+C', desc: _tl('helpForceQuit') },
|
|
121
|
+
{ key: 'S', desc: _tl('helpSwitchSettings') },
|
|
122
|
+
{ key: 'V', desc: _tl('helpSwitchVoices') },
|
|
123
|
+
{ key: 'M', desc: _tl('helpSwitchMusic') },
|
|
124
|
+
{ key: 'R', desc: _tl('helpSwitchReadme') },
|
|
125
|
+
{ key: 'H', desc: _tl('helpSwitchHelp') },
|
|
126
|
+
{ key: 'I', desc: _tl('helpSwitchInstall') },
|
|
127
|
+
{ key: 'Esc', desc: _tl('helpCloseModal') },
|
|
128
|
+
],
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
title: _tl('helpSectionNavigation'),
|
|
132
|
+
shortcuts: [
|
|
133
|
+
{ key: '↑↓ / j k', desc: _tl('helpNavigateLists') },
|
|
134
|
+
{ key: 'Enter', desc: _tl('helpSelectActivate') },
|
|
135
|
+
{ key: 'Space', desc: _tl('helpTogglePreview') },
|
|
136
|
+
{ key: 'Tab', desc: _tl('helpNextButton') },
|
|
137
|
+
{ key: 'Shift+Tab', desc: _tl('helpPrevButton') },
|
|
138
|
+
{ key: '/', desc: _tl('helpOpenSearch') },
|
|
139
|
+
{ key: 'F', desc: _tl('helpToggleFavFilter') },
|
|
140
|
+
{ key: '*', desc: _tl('helpToggleFav') },
|
|
141
|
+
{ key: 'M', desc: _tl('helpToggleMusic') },
|
|
142
|
+
],
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
title: _tl('helpSectionColors'),
|
|
146
|
+
shortcuts: [
|
|
147
|
+
{ key: 'Blue (#2196f3)', desc: _tl('helpColorSettings') },
|
|
148
|
+
{ key: 'Teal (#00695c)', desc: _tl('helpColorVoices') },
|
|
149
|
+
{ key: 'Orange (#ff9800)', desc: _tl('helpColorMusic') },
|
|
150
|
+
{ key: 'Dark (#455a64)', desc: _tl('helpColorReadme') },
|
|
151
|
+
{ key: 'Gray (#607d8b)', desc: _tl('helpColorHelp') },
|
|
152
|
+
{ key: 'Indigo (#3f51b5)', desc: _tl('helpColorInstall') },
|
|
153
|
+
],
|
|
154
|
+
},
|
|
155
|
+
];
|
|
156
|
+
}
|
|
115
157
|
|
|
116
158
|
// -------------------------------------------------------------------------
|
|
117
159
|
// Container
|
|
@@ -133,7 +175,7 @@ export function createHelpTab(screen, services) {
|
|
|
133
175
|
|
|
134
176
|
function _buildContent(filterText) {
|
|
135
177
|
const lines = [];
|
|
136
|
-
for (const section of
|
|
178
|
+
for (const section of _buildSections()) {
|
|
137
179
|
lines.push(`{bold}{#546e7a-fg}── ${section.title} ${'─'.repeat(Math.max(0, 60 - section.title.length))}{/#546e7a-fg}{/bold}`);
|
|
138
180
|
for (const { key, desc } of section.shortcuts) {
|
|
139
181
|
const displayKey = key.padEnd(20);
|
|
@@ -183,11 +225,11 @@ export function createHelpTab(screen, services) {
|
|
|
183
225
|
style: { fg: COLORS.keyFg, bg: '#1a3a5c', focus: { bg: '#245a80' } },
|
|
184
226
|
});
|
|
185
227
|
|
|
186
|
-
blessed.text({
|
|
228
|
+
const searchLabel = blessed.text({
|
|
187
229
|
parent: box,
|
|
188
230
|
bottom: 2,
|
|
189
231
|
left: 2,
|
|
190
|
-
content: '
|
|
232
|
+
content: _tl('helpSearchLabel'),
|
|
191
233
|
style: { fg: COLORS.descFg, bg: COLORS.contentBg },
|
|
192
234
|
});
|
|
193
235
|
|
|
@@ -226,6 +268,17 @@ export function createHelpTab(screen, services) {
|
|
|
226
268
|
if (typeof focusMainTabBar === 'function') { focusMainTabBar(); screen.render(); }
|
|
227
269
|
});
|
|
228
270
|
|
|
271
|
+
// -------------------------------------------------------------------------
|
|
272
|
+
// Language change handler
|
|
273
|
+
|
|
274
|
+
if (languageService) {
|
|
275
|
+
languageService.onChange(() => {
|
|
276
|
+
scrollBox.setContent(_buildContent(''));
|
|
277
|
+
searchLabel.setContent(_tl('helpSearchLabel'));
|
|
278
|
+
screen.render();
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
229
282
|
// -------------------------------------------------------------------------
|
|
230
283
|
// Tab Component Contract
|
|
231
284
|
|
|
@@ -251,7 +304,7 @@ export function createHelpTab(screen, services) {
|
|
|
251
304
|
onBlur() {},
|
|
252
305
|
|
|
253
306
|
getFooterText() {
|
|
254
|
-
return
|
|
307
|
+
return _tl('helpFooter');
|
|
255
308
|
},
|
|
256
309
|
|
|
257
310
|
getFooterColor() {
|