agentvibes 4.4.1 → 4.5.7
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/audio-effects.cfg +1 -0
- package/.claude/config/background-music-enabled.txt +1 -0
- package/.claude/config/reverb-level.txt +1 -1
- package/.claude/github-star-reminder.txt +1 -1
- package/.claude/hooks/audio-processor.sh +1 -1
- package/.claude/hooks/bmad-speak.sh +16 -2
- package/.claude/hooks-windows/bmad-speak.ps1 +200 -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 +78 -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 +63 -12
- package/src/console/navigation.js +5 -2
- package/src/console/tabs/agents-tab.js +72 -76
- package/src/console/tabs/help-tab.js +107 -54
- package/src/console/tabs/install-tab.js +132 -56
- package/src/console/tabs/music-tab.js +1039 -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 +7 -2
- package/src/i18n/de.js +203 -0
- package/src/i18n/en.js +203 -0
- package/src/i18n/es.js +203 -0
- package/src/i18n/fr.js +203 -0
- package/src/i18n/hi.js +203 -0
- package/src/i18n/ja.js +203 -0
- package/src/i18n/ko.js +203 -0
- package/src/i18n/pt.js +203 -0
- package/src/i18n/strings.js +54 -0
- package/src/i18n/zh-CN.js +203 -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
|
@@ -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
|
|
|
@@ -324,7 +319,7 @@ export function createAgentsTab(screen, services) {
|
|
|
324
319
|
}
|
|
325
320
|
|
|
326
321
|
const resetBtn = _createBtn('[X] Reset', () => {
|
|
327
|
-
const agent = _agents[agentList.selected];
|
|
322
|
+
const agent = _agents[agentList.selected ?? 0];
|
|
328
323
|
if (agent) {
|
|
329
324
|
voiceStore.resetAgentProfile(agent.id);
|
|
330
325
|
refreshDisplay();
|
|
@@ -379,7 +374,7 @@ export function createAgentsTab(screen, services) {
|
|
|
379
374
|
? formatTrackName(profile.backgroundMusic.track)
|
|
380
375
|
: '(global)')).padEnd(COL_MUSIC).slice(0, COL_MUSIC);
|
|
381
376
|
const vol = profile.backgroundMusic?.enabled
|
|
382
|
-
? ` ${profile.backgroundMusic.volume ??
|
|
377
|
+
? ` ${profile.backgroundMusic.volume ?? 20}%`.padEnd(COL_VOL)
|
|
383
378
|
: ' — ';
|
|
384
379
|
const pretext = ' ' + (profile.pretext || '(default)').slice(0, COL_PRETEXT - 1);
|
|
385
380
|
return ` ${icon}${name}${voice}${gender}${provider}${reverb}${music}${vol} ${pretext}`;
|
|
@@ -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
|
|
|
@@ -532,7 +513,7 @@ export function createAgentsTab(screen, services) {
|
|
|
532
513
|
personality: profile.personality || globalCfg.personality || 'none',
|
|
533
514
|
backgroundMusic: {
|
|
534
515
|
track: profile.backgroundMusic?.track || globalCfg.backgroundMusic?.track || '',
|
|
535
|
-
volume: profile.backgroundMusic?.volume ?? globalCfg.backgroundMusic?.volume ??
|
|
516
|
+
volume: profile.backgroundMusic?.volume ?? globalCfg.backgroundMusic?.volume ?? 20,
|
|
536
517
|
enabled: profile.backgroundMusic?.enabled ?? globalCfg.backgroundMusic?.enabled ?? false,
|
|
537
518
|
},
|
|
538
519
|
});
|
|
@@ -553,7 +534,7 @@ export function createAgentsTab(screen, services) {
|
|
|
553
534
|
personality: profile.personality || globalCfg.personality || 'none',
|
|
554
535
|
backgroundMusic: {
|
|
555
536
|
track: profile.backgroundMusic?.track || globalCfg.backgroundMusic?.track || '',
|
|
556
|
-
volume: profile.backgroundMusic?.volume ?? globalCfg.backgroundMusic?.volume ??
|
|
537
|
+
volume: profile.backgroundMusic?.volume ?? globalCfg.backgroundMusic?.volume ?? 20,
|
|
557
538
|
enabled: profile.backgroundMusic?.enabled ?? globalCfg.backgroundMusic?.enabled ?? false,
|
|
558
539
|
},
|
|
559
540
|
};
|
|
@@ -676,7 +657,7 @@ export function createAgentsTab(screen, services) {
|
|
|
676
657
|
if (draft.reverbPreset !== (globalCfg.effects?.reverbPreset || 'light')) toSave.reverbPreset = draft.reverbPreset;
|
|
677
658
|
if (draft.personality !== (globalCfg.personality || 'none')) toSave.personality = draft.personality;
|
|
678
659
|
if (draft.backgroundMusic.track !== (globalCfg.backgroundMusic?.track || '') ||
|
|
679
|
-
draft.backgroundMusic.volume !== (globalCfg.backgroundMusic?.volume ??
|
|
660
|
+
draft.backgroundMusic.volume !== (globalCfg.backgroundMusic?.volume ?? 20) ||
|
|
680
661
|
draft.backgroundMusic.enabled !== (globalCfg.backgroundMusic?.enabled ?? false)) {
|
|
681
662
|
toSave.backgroundMusic = draft.backgroundMusic;
|
|
682
663
|
}
|
|
@@ -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);
|
|
@@ -1289,7 +1275,7 @@ export function createAgentsTab(screen, services) {
|
|
|
1289
1275
|
const track = shuffled[i % shuffled.length];
|
|
1290
1276
|
const existing = voiceStore.getAgentProfile(agent.id);
|
|
1291
1277
|
voiceStore.setAgentProfile(agent.id, {
|
|
1292
|
-
backgroundMusic: { track, volume: existing.backgroundMusic?.volume ??
|
|
1278
|
+
backgroundMusic: { track, volume: existing.backgroundMusic?.volume ?? 20, enabled: true },
|
|
1293
1279
|
});
|
|
1294
1280
|
});
|
|
1295
1281
|
return true;
|
|
@@ -1387,7 +1373,7 @@ export function createAgentsTab(screen, services) {
|
|
|
1387
1373
|
|
|
1388
1374
|
case 'setMusic':
|
|
1389
1375
|
_closeMenu(() => {
|
|
1390
|
-
openTrackPicker(screen, '',
|
|
1376
|
+
openTrackPicker(screen, '', 20, (track, volume) => {
|
|
1391
1377
|
_agents.forEach(agent => {
|
|
1392
1378
|
const p = voiceStore.getAgentProfile(agent.id);
|
|
1393
1379
|
voiceStore.setAgentProfile(agent.id, {
|
|
@@ -1403,7 +1389,7 @@ export function createAgentsTab(screen, services) {
|
|
|
1403
1389
|
|
|
1404
1390
|
case 'setVolume':
|
|
1405
1391
|
_closeMenu(() => {
|
|
1406
|
-
const curVol = voiceStore.getAgentProfile(_agents[0]?.id)?.backgroundMusic?.volume ??
|
|
1392
|
+
const curVol = voiceStore.getAgentProfile(_agents[0]?.id)?.backgroundMusic?.volume ?? 20;
|
|
1407
1393
|
openVolumeInput(screen, curVol, (volume) => {
|
|
1408
1394
|
_agents.forEach(agent => {
|
|
1409
1395
|
const p = voiceStore.getAgentProfile(agent.id);
|
|
@@ -1518,7 +1504,7 @@ export function createAgentsTab(screen, services) {
|
|
|
1518
1504
|
// Key bindings
|
|
1519
1505
|
|
|
1520
1506
|
agentList.key(['x', 'X'], () => {
|
|
1521
|
-
const agent = _agents[agentList.selected];
|
|
1507
|
+
const agent = _agents[agentList.selected ?? 0];
|
|
1522
1508
|
if (agent) {
|
|
1523
1509
|
voiceStore.resetAgentProfile(agent.id);
|
|
1524
1510
|
refreshDisplay();
|
|
@@ -1527,12 +1513,12 @@ export function createAgentsTab(screen, services) {
|
|
|
1527
1513
|
|
|
1528
1514
|
|
|
1529
1515
|
agentList.key(['enter'], () => {
|
|
1530
|
-
const agent = _agents[agentList.selected];
|
|
1516
|
+
const agent = _agents[agentList.selected ?? 0];
|
|
1531
1517
|
if (agent) _openAgentDetailPanel(agent);
|
|
1532
1518
|
});
|
|
1533
1519
|
|
|
1534
1520
|
agentList.key(['space'], () => {
|
|
1535
|
-
const agent = _agents[agentList.selected];
|
|
1521
|
+
const agent = _agents[agentList.selected ?? 0];
|
|
1536
1522
|
if (agent) _sampleAgent(agent);
|
|
1537
1523
|
});
|
|
1538
1524
|
|
|
@@ -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() {
|