agentvibes 5.6.8 → 5.7.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 +2 -0
- package/.claude/commands/agent-vibes-bmad-voices.md +117 -117
- package/.claude/commands/agent-vibes-rdp.md +24 -24
- package/.claude/config/audio-effects.cfg +2 -2
- package/.claude/config/background-music-position.txt +0 -1
- package/.claude/docs/TERMUX_SETUP.md +408 -408
- package/.claude/github-star-reminder.txt +1 -1
- package/.claude/hooks/audio-cache-utils.sh +0 -0
- package/.claude/hooks/audio-processor.sh +0 -0
- package/.claude/hooks/background-music-manager.sh +0 -0
- package/.claude/hooks/bmad-party-manager.sh +225 -0
- package/.claude/hooks/bmad-party-speak.sh +0 -0
- package/.claude/hooks/bmad-speak-enhanced.sh +0 -0
- package/.claude/hooks/bmad-speak.sh +0 -0
- package/.claude/hooks/bmad-tts-injector.sh +49 -21
- package/.claude/hooks/bmad-voice-manager.sh +0 -0
- package/.claude/hooks/clawdbot-receiver-SECURE.sh +0 -0
- package/.claude/hooks/clawdbot-receiver.sh +0 -0
- package/.claude/hooks/clean-audio-cache.sh +0 -0
- package/.claude/hooks/cleanup-cache.sh +0 -0
- package/.claude/hooks/configure-rdp-mode.sh +0 -0
- package/.claude/hooks/download-extra-voices.sh +0 -0
- package/.claude/hooks/effects-manager.sh +0 -0
- package/.claude/hooks/github-star-reminder.sh +0 -0
- package/.claude/hooks/language-manager.sh +0 -0
- package/.claude/hooks/learn-manager.sh +0 -0
- package/.claude/hooks/macos-voice-manager.sh +0 -0
- package/.claude/hooks/migrate-background-music.sh +0 -0
- package/.claude/hooks/migrate-to-agentvibes.sh +0 -0
- package/.claude/hooks/optimize-background-music.sh +0 -0
- package/.claude/hooks/path-resolver.sh +0 -0
- package/.claude/hooks/personality-manager.sh +0 -0
- package/.claude/hooks/piper-download-voices.sh +0 -0
- package/.claude/hooks/piper-installer.sh +0 -0
- package/.claude/hooks/piper-multispeaker-registry.sh +0 -0
- package/.claude/hooks/piper-voice-manager.sh +0 -0
- package/.claude/hooks/play-tts-agentvibes-receiver-for-voiceless-connections.sh +0 -0
- package/.claude/hooks/play-tts-enhanced.sh +0 -0
- package/.claude/hooks/play-tts-macos.sh +0 -0
- package/.claude/hooks/play-tts-piper.sh +1 -1
- package/.claude/hooks/play-tts-soprano.sh +0 -0
- package/.claude/hooks/play-tts-ssh-remote.sh +0 -0
- package/.claude/hooks/play-tts-termux-ssh.sh +0 -0
- package/.claude/hooks/play-tts-windows-receiver.sh +0 -0
- package/.claude/hooks/play-tts.sh +4 -0
- package/.claude/hooks/prepare-release.sh +0 -0
- package/.claude/hooks/provider-commands.sh +16 -4
- package/.claude/hooks/provider-manager.sh +38 -0
- package/.claude/hooks/replay-target-audio.sh +0 -0
- package/.claude/hooks/sentiment-manager.sh +0 -0
- package/.claude/hooks/session-start-tts.sh +0 -0
- package/.claude/hooks/soprano-gradio-synth.py +0 -0
- package/.claude/hooks/speed-manager.sh +0 -0
- package/.claude/hooks/stop-tts.sh +0 -0
- package/.claude/hooks/stop.sh +38 -0
- package/.claude/hooks/termux-installer.sh +0 -0
- package/.claude/hooks/translate-manager.sh +0 -0
- package/.claude/hooks/translator.py +0 -0
- package/.claude/hooks/tts-queue-worker.sh +0 -0
- package/.claude/hooks/tts-queue.sh +0 -0
- package/.claude/hooks/verbosity-manager.sh +0 -0
- package/.claude/hooks/voice-manager.sh +50 -2
- package/.claude/hooks-windows/audio-cache-utils.ps1 +119 -119
- package/.claude/hooks-windows/play-tts.ps1 +34 -1
- package/.claude/hooks-windows/tts-watcher.ps1 +122 -0
- package/.claude/piper-voices-dir.txt +1 -0
- package/.clawdbot/README.md +105 -105
- package/.mcp.json +14 -5
- package/README.md +10 -2
- package/RELEASE_NOTES.md +61 -0
- package/WINDOWS-SETUP.md +208 -208
- package/bin/agent-vibes +39 -39
- package/bin/agentvibes-voice-browser.js +59 -4
- package/bin/agentvibes.js +0 -0
- package/bin/mcp-server.js +121 -121
- package/bin/mcp-server.sh +0 -0
- package/bin/test-bmad-pr +78 -78
- package/mcp-server/QUICK_START.md +203 -203
- package/mcp-server/README.md +345 -345
- package/mcp-server/WINDOWS_SETUP.md +260 -260
- package/mcp-server/docs/troubleshooting-audio.md +313 -313
- package/mcp-server/examples/claude_desktop_config.json +11 -11
- package/mcp-server/examples/claude_desktop_config_piper.json +9 -9
- package/mcp-server/examples/custom_instructions.md +169 -169
- package/mcp-server/install-deps.js +177 -130
- package/mcp-server/server.py +1797 -1787
- package/mcp-server/test_server.py +0 -0
- package/package.json +1 -1
- package/src/bmad-detector.js +85 -71
- package/src/cli/list-personalities.js +110 -110
- package/src/cli/list-voices.js +114 -114
- package/src/commands/bmad-voices.js +394 -394
- package/src/commands/install-mcp.js +476 -476
- package/src/console/brand-colors.js +13 -13
- package/src/console/constants/personalities.js +44 -44
- package/src/console/tabs/help-tab.js +314 -314
- package/src/console/tabs/music-tab.js +18 -2
- package/src/console/tabs/readme-tab.js +272 -272
- package/src/console/widgets/destroy-list.js +25 -25
- package/src/console/widgets/notice.js +55 -55
- package/src/console/widgets/personality-picker.js +213 -213
- package/src/i18n/de.js +202 -202
- package/src/i18n/es.js +202 -202
- package/src/i18n/fr.js +202 -202
- package/src/i18n/hi.js +202 -202
- package/src/i18n/ja.js +202 -202
- package/src/i18n/ko.js +202 -202
- package/src/i18n/pt.js +202 -202
- package/src/i18n/strings.js +54 -54
- package/src/i18n/zh-CN.js +202 -202
- package/src/installer/language-screen.js +31 -31
- package/src/installer/music-file-input.js +304 -304
- package/src/installer.js +70 -7
- package/src/services/agent-voice-store.js +59 -12
- package/src/services/config-service.js +264 -264
- package/src/services/language-service.js +47 -47
- package/src/services/provider-service.js +143 -143
- package/src/utils/audio-duration-validator.js +298 -298
- package/src/utils/audio-format-validator.js +277 -277
- package/src/utils/dependency-checker.js +469 -469
- package/src/utils/file-ownership-verifier.js +358 -358
- package/src/utils/list-formatter.js +194 -194
- package/src/utils/music-file-validator.js +285 -285
- package/src/utils/preview-list-prompt.js +136 -136
- package/src/utils/secure-music-storage.js +412 -412
- package/templates/agentvibes-receiver.sh +0 -0
- package/templates/audio/welcome-music.mp3 +0 -0
- package/.claude/hooks/play-tts-agentvibes-receiver.sh +0 -1
|
@@ -1,314 +1,314 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AgentVibes TUI Console — Help Tab
|
|
3
|
-
* Epic 13: Story 13.1
|
|
4
|
-
*
|
|
5
|
-
* Implements the Tab Component Contract:
|
|
6
|
-
* createHelpTab(screen, services) → { box, show, hide, onFocus, onBlur, getFooterText, getFooterColor }
|
|
7
|
-
*
|
|
8
|
-
* Features: keyboard shortcuts reference, two sections, [/] search.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { t } from '../../i18n/strings.js';
|
|
12
|
-
|
|
13
|
-
const IS_TEST = process.env.AGENTVIBES_TEST_MODE === 'true';
|
|
14
|
-
|
|
15
|
-
let blessed;
|
|
16
|
-
if (!IS_TEST) {
|
|
17
|
-
const { default: b } = await import('blessed');
|
|
18
|
-
blessed = b;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// ---------------------------------------------------------------------------
|
|
22
|
-
|
|
23
|
-
const COLORS = {
|
|
24
|
-
contentBg: '#0a0e1a',
|
|
25
|
-
sectionHdr: '#546e7a', // Blue-gray — Help tab
|
|
26
|
-
labelFg: '#e3f2fd',
|
|
27
|
-
keyFg: '#ffff00', // Yellow — keyboard shortcuts
|
|
28
|
-
descFg: '#90a4ae', // Gray — descriptions
|
|
29
|
-
borderFg: '#607d8b',
|
|
30
|
-
footerBg: '#607d8b', // Gray — Help tab footer
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
// ---------------------------------------------------------------------------
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Return all shortcut sections.
|
|
37
|
-
* @returns {{ title: string, shortcuts: { key: string, desc: string }[] }[]}
|
|
38
|
-
*/
|
|
39
|
-
export function getShortcutSections() {
|
|
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
|
-
];
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// ---------------------------------------------------------------------------
|
|
84
|
-
|
|
85
|
-
const _FOOTER_TEXT_EN = '[↑↓/jk] Scroll [/] Search [PgUp/PgDn] Page [S/V/M/A/R] Tab [Q] Quit';
|
|
86
|
-
|
|
87
|
-
function createTestStub() {
|
|
88
|
-
return {
|
|
89
|
-
box: {},
|
|
90
|
-
show: () => {},
|
|
91
|
-
hide: () => {},
|
|
92
|
-
onFocus: () => {},
|
|
93
|
-
onBlur: () => {},
|
|
94
|
-
getFooterText: () => _FOOTER_TEXT_EN,
|
|
95
|
-
getFooterColor: () => COLORS.footerBg,
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// ---------------------------------------------------------------------------
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Create the Help tab component.
|
|
103
|
-
*
|
|
104
|
-
* @param {object} screen - Blessed screen instance
|
|
105
|
-
* @param {object} services
|
|
106
|
-
* @returns {{ box, show, hide, onFocus, onBlur, getFooterText, getFooterColor }}
|
|
107
|
-
*/
|
|
108
|
-
export function createHelpTab(screen, services) {
|
|
109
|
-
if (IS_TEST) return createTestStub();
|
|
110
|
-
|
|
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
|
-
}
|
|
157
|
-
|
|
158
|
-
// -------------------------------------------------------------------------
|
|
159
|
-
// Container
|
|
160
|
-
|
|
161
|
-
const box = blessed.box({
|
|
162
|
-
parent: screen,
|
|
163
|
-
top: 5,
|
|
164
|
-
left: 0,
|
|
165
|
-
width: '100%',
|
|
166
|
-
bottom: 2,
|
|
167
|
-
hidden: true,
|
|
168
|
-
style: { fg: COLORS.labelFg, bg: COLORS.contentBg },
|
|
169
|
-
border: { type: 'line' },
|
|
170
|
-
borderStyle: { fg: COLORS.borderFg },
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
// -------------------------------------------------------------------------
|
|
174
|
-
// Build content text
|
|
175
|
-
|
|
176
|
-
function _buildContent(filterText) {
|
|
177
|
-
const lines = [];
|
|
178
|
-
for (const section of _buildSections()) {
|
|
179
|
-
lines.push(`{bold}{#546e7a-fg}── ${section.title} ${'─'.repeat(Math.max(0, 60 - section.title.length))}{/#546e7a-fg}{/bold}`);
|
|
180
|
-
for (const { key, desc } of section.shortcuts) {
|
|
181
|
-
const displayKey = key.padEnd(20);
|
|
182
|
-
const displayDesc = desc;
|
|
183
|
-
if (filterText && !key.toLowerCase().includes(filterText) && !desc.toLowerCase().includes(filterText)) {
|
|
184
|
-
continue;
|
|
185
|
-
}
|
|
186
|
-
lines.push(` {${COLORS.keyFg}-fg}${displayKey}{/${COLORS.keyFg}-fg} {${COLORS.descFg}-fg}${displayDesc}{/${COLORS.descFg}-fg}`);
|
|
187
|
-
}
|
|
188
|
-
lines.push('');
|
|
189
|
-
}
|
|
190
|
-
return lines.join('\n');
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// -------------------------------------------------------------------------
|
|
194
|
-
// Scrollable content
|
|
195
|
-
|
|
196
|
-
const scrollBox = blessed.box({
|
|
197
|
-
parent: box,
|
|
198
|
-
top: 1,
|
|
199
|
-
left: 2,
|
|
200
|
-
width: '96%',
|
|
201
|
-
bottom: 4,
|
|
202
|
-
scrollable: true,
|
|
203
|
-
alwaysScroll: true,
|
|
204
|
-
tags: true,
|
|
205
|
-
keys: true,
|
|
206
|
-
vi: true,
|
|
207
|
-
mouse: true,
|
|
208
|
-
scrollbar: { ch: '│', style: { fg: COLORS.sectionHdr } },
|
|
209
|
-
content: _buildContent(''),
|
|
210
|
-
style: { fg: COLORS.labelFg, bg: COLORS.contentBg },
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
// -------------------------------------------------------------------------
|
|
214
|
-
// Search
|
|
215
|
-
|
|
216
|
-
const searchBox = blessed.textbox({
|
|
217
|
-
parent: box,
|
|
218
|
-
bottom: 2,
|
|
219
|
-
left: 10,
|
|
220
|
-
width: 30,
|
|
221
|
-
height: 1,
|
|
222
|
-
hidden: true,
|
|
223
|
-
inputOnFocus: true,
|
|
224
|
-
keys: true,
|
|
225
|
-
style: { fg: COLORS.keyFg, bg: '#1a3a5c', focus: { bg: '#245a80' } },
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
const searchLabel = blessed.text({
|
|
229
|
-
parent: box,
|
|
230
|
-
bottom: 2,
|
|
231
|
-
left: 2,
|
|
232
|
-
content: _tl('helpSearchLabel'),
|
|
233
|
-
style: { fg: COLORS.descFg, bg: COLORS.contentBg },
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
searchBox.on('keypress', () => {
|
|
237
|
-
setTimeout(() => {
|
|
238
|
-
const filter = searchBox.getValue().toLowerCase().trim();
|
|
239
|
-
scrollBox.setContent(_buildContent(filter));
|
|
240
|
-
screen.render();
|
|
241
|
-
}, 0);
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
searchBox.key(['escape'], () => {
|
|
245
|
-
searchBox.clearValue();
|
|
246
|
-
searchBox.hide();
|
|
247
|
-
scrollBox.setContent(_buildContent(''));
|
|
248
|
-
scrollBox.focus();
|
|
249
|
-
screen.render();
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
scrollBox.key(['/'], () => {
|
|
253
|
-
searchBox.show();
|
|
254
|
-
searchBox.clearValue();
|
|
255
|
-
searchBox.focus();
|
|
256
|
-
screen.render();
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
// [↑] at top of content → jump to main header tab bar
|
|
260
|
-
scrollBox.key(['up'], () => {
|
|
261
|
-
if (scrollBox.getScroll() === 0 && typeof focusMainTabBar === 'function') {
|
|
262
|
-
focusMainTabBar();
|
|
263
|
-
}
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
// Escape → return to header tab bar
|
|
267
|
-
scrollBox.key(['escape'], () => {
|
|
268
|
-
if (typeof focusMainTabBar === 'function') { focusMainTabBar(); screen.render(); }
|
|
269
|
-
});
|
|
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
|
-
|
|
282
|
-
// -------------------------------------------------------------------------
|
|
283
|
-
// Tab Component Contract
|
|
284
|
-
|
|
285
|
-
return {
|
|
286
|
-
box,
|
|
287
|
-
|
|
288
|
-
show() {
|
|
289
|
-
box.show();
|
|
290
|
-
scrollBox.setContent(_buildContent(''));
|
|
291
|
-
screen.render();
|
|
292
|
-
},
|
|
293
|
-
|
|
294
|
-
hide() {
|
|
295
|
-
box.hide();
|
|
296
|
-
screen.render();
|
|
297
|
-
},
|
|
298
|
-
|
|
299
|
-
onFocus() {
|
|
300
|
-
scrollBox.focus();
|
|
301
|
-
screen.render();
|
|
302
|
-
},
|
|
303
|
-
|
|
304
|
-
onBlur() {},
|
|
305
|
-
|
|
306
|
-
getFooterText() {
|
|
307
|
-
return _tl('helpFooter');
|
|
308
|
-
},
|
|
309
|
-
|
|
310
|
-
getFooterColor() {
|
|
311
|
-
return COLORS.footerBg;
|
|
312
|
-
},
|
|
313
|
-
};
|
|
314
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* AgentVibes TUI Console — Help Tab
|
|
3
|
+
* Epic 13: Story 13.1
|
|
4
|
+
*
|
|
5
|
+
* Implements the Tab Component Contract:
|
|
6
|
+
* createHelpTab(screen, services) → { box, show, hide, onFocus, onBlur, getFooterText, getFooterColor }
|
|
7
|
+
*
|
|
8
|
+
* Features: keyboard shortcuts reference, two sections, [/] search.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { t } from '../../i18n/strings.js';
|
|
12
|
+
|
|
13
|
+
const IS_TEST = process.env.AGENTVIBES_TEST_MODE === 'true';
|
|
14
|
+
|
|
15
|
+
let blessed;
|
|
16
|
+
if (!IS_TEST) {
|
|
17
|
+
const { default: b } = await import('blessed');
|
|
18
|
+
blessed = b;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
|
|
23
|
+
const COLORS = {
|
|
24
|
+
contentBg: '#0a0e1a',
|
|
25
|
+
sectionHdr: '#546e7a', // Blue-gray — Help tab
|
|
26
|
+
labelFg: '#e3f2fd',
|
|
27
|
+
keyFg: '#ffff00', // Yellow — keyboard shortcuts
|
|
28
|
+
descFg: '#90a4ae', // Gray — descriptions
|
|
29
|
+
borderFg: '#607d8b',
|
|
30
|
+
footerBg: '#607d8b', // Gray — Help tab footer
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Return all shortcut sections.
|
|
37
|
+
* @returns {{ title: string, shortcuts: { key: string, desc: string }[] }[]}
|
|
38
|
+
*/
|
|
39
|
+
export function getShortcutSections() {
|
|
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
|
+
];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
|
|
85
|
+
const _FOOTER_TEXT_EN = '[↑↓/jk] Scroll [/] Search [PgUp/PgDn] Page [S/V/M/A/R] Tab [Q] Quit';
|
|
86
|
+
|
|
87
|
+
function createTestStub() {
|
|
88
|
+
return {
|
|
89
|
+
box: {},
|
|
90
|
+
show: () => {},
|
|
91
|
+
hide: () => {},
|
|
92
|
+
onFocus: () => {},
|
|
93
|
+
onBlur: () => {},
|
|
94
|
+
getFooterText: () => _FOOTER_TEXT_EN,
|
|
95
|
+
getFooterColor: () => COLORS.footerBg,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Create the Help tab component.
|
|
103
|
+
*
|
|
104
|
+
* @param {object} screen - Blessed screen instance
|
|
105
|
+
* @param {object} services
|
|
106
|
+
* @returns {{ box, show, hide, onFocus, onBlur, getFooterText, getFooterColor }}
|
|
107
|
+
*/
|
|
108
|
+
export function createHelpTab(screen, services) {
|
|
109
|
+
if (IS_TEST) return createTestStub();
|
|
110
|
+
|
|
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
|
+
}
|
|
157
|
+
|
|
158
|
+
// -------------------------------------------------------------------------
|
|
159
|
+
// Container
|
|
160
|
+
|
|
161
|
+
const box = blessed.box({
|
|
162
|
+
parent: screen,
|
|
163
|
+
top: 5,
|
|
164
|
+
left: 0,
|
|
165
|
+
width: '100%',
|
|
166
|
+
bottom: 2,
|
|
167
|
+
hidden: true,
|
|
168
|
+
style: { fg: COLORS.labelFg, bg: COLORS.contentBg },
|
|
169
|
+
border: { type: 'line' },
|
|
170
|
+
borderStyle: { fg: COLORS.borderFg },
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// -------------------------------------------------------------------------
|
|
174
|
+
// Build content text
|
|
175
|
+
|
|
176
|
+
function _buildContent(filterText) {
|
|
177
|
+
const lines = [];
|
|
178
|
+
for (const section of _buildSections()) {
|
|
179
|
+
lines.push(`{bold}{#546e7a-fg}── ${section.title} ${'─'.repeat(Math.max(0, 60 - section.title.length))}{/#546e7a-fg}{/bold}`);
|
|
180
|
+
for (const { key, desc } of section.shortcuts) {
|
|
181
|
+
const displayKey = key.padEnd(20);
|
|
182
|
+
const displayDesc = desc;
|
|
183
|
+
if (filterText && !key.toLowerCase().includes(filterText) && !desc.toLowerCase().includes(filterText)) {
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
lines.push(` {${COLORS.keyFg}-fg}${displayKey}{/${COLORS.keyFg}-fg} {${COLORS.descFg}-fg}${displayDesc}{/${COLORS.descFg}-fg}`);
|
|
187
|
+
}
|
|
188
|
+
lines.push('');
|
|
189
|
+
}
|
|
190
|
+
return lines.join('\n');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// -------------------------------------------------------------------------
|
|
194
|
+
// Scrollable content
|
|
195
|
+
|
|
196
|
+
const scrollBox = blessed.box({
|
|
197
|
+
parent: box,
|
|
198
|
+
top: 1,
|
|
199
|
+
left: 2,
|
|
200
|
+
width: '96%',
|
|
201
|
+
bottom: 4,
|
|
202
|
+
scrollable: true,
|
|
203
|
+
alwaysScroll: true,
|
|
204
|
+
tags: true,
|
|
205
|
+
keys: true,
|
|
206
|
+
vi: true,
|
|
207
|
+
mouse: true,
|
|
208
|
+
scrollbar: { ch: '│', style: { fg: COLORS.sectionHdr } },
|
|
209
|
+
content: _buildContent(''),
|
|
210
|
+
style: { fg: COLORS.labelFg, bg: COLORS.contentBg },
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// -------------------------------------------------------------------------
|
|
214
|
+
// Search
|
|
215
|
+
|
|
216
|
+
const searchBox = blessed.textbox({
|
|
217
|
+
parent: box,
|
|
218
|
+
bottom: 2,
|
|
219
|
+
left: 10,
|
|
220
|
+
width: 30,
|
|
221
|
+
height: 1,
|
|
222
|
+
hidden: true,
|
|
223
|
+
inputOnFocus: true,
|
|
224
|
+
keys: true,
|
|
225
|
+
style: { fg: COLORS.keyFg, bg: '#1a3a5c', focus: { bg: '#245a80' } },
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
const searchLabel = blessed.text({
|
|
229
|
+
parent: box,
|
|
230
|
+
bottom: 2,
|
|
231
|
+
left: 2,
|
|
232
|
+
content: _tl('helpSearchLabel'),
|
|
233
|
+
style: { fg: COLORS.descFg, bg: COLORS.contentBg },
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
searchBox.on('keypress', () => {
|
|
237
|
+
setTimeout(() => {
|
|
238
|
+
const filter = searchBox.getValue().toLowerCase().trim();
|
|
239
|
+
scrollBox.setContent(_buildContent(filter));
|
|
240
|
+
screen.render();
|
|
241
|
+
}, 0);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
searchBox.key(['escape'], () => {
|
|
245
|
+
searchBox.clearValue();
|
|
246
|
+
searchBox.hide();
|
|
247
|
+
scrollBox.setContent(_buildContent(''));
|
|
248
|
+
scrollBox.focus();
|
|
249
|
+
screen.render();
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
scrollBox.key(['/'], () => {
|
|
253
|
+
searchBox.show();
|
|
254
|
+
searchBox.clearValue();
|
|
255
|
+
searchBox.focus();
|
|
256
|
+
screen.render();
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// [↑] at top of content → jump to main header tab bar
|
|
260
|
+
scrollBox.key(['up'], () => {
|
|
261
|
+
if (scrollBox.getScroll() === 0 && typeof focusMainTabBar === 'function') {
|
|
262
|
+
focusMainTabBar();
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
// Escape → return to header tab bar
|
|
267
|
+
scrollBox.key(['escape'], () => {
|
|
268
|
+
if (typeof focusMainTabBar === 'function') { focusMainTabBar(); screen.render(); }
|
|
269
|
+
});
|
|
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
|
+
|
|
282
|
+
// -------------------------------------------------------------------------
|
|
283
|
+
// Tab Component Contract
|
|
284
|
+
|
|
285
|
+
return {
|
|
286
|
+
box,
|
|
287
|
+
|
|
288
|
+
show() {
|
|
289
|
+
box.show();
|
|
290
|
+
scrollBox.setContent(_buildContent(''));
|
|
291
|
+
screen.render();
|
|
292
|
+
},
|
|
293
|
+
|
|
294
|
+
hide() {
|
|
295
|
+
box.hide();
|
|
296
|
+
screen.render();
|
|
297
|
+
},
|
|
298
|
+
|
|
299
|
+
onFocus() {
|
|
300
|
+
scrollBox.focus();
|
|
301
|
+
screen.render();
|
|
302
|
+
},
|
|
303
|
+
|
|
304
|
+
onBlur() {},
|
|
305
|
+
|
|
306
|
+
getFooterText() {
|
|
307
|
+
return _tl('helpFooter');
|
|
308
|
+
},
|
|
309
|
+
|
|
310
|
+
getFooterColor() {
|
|
311
|
+
return COLORS.footerBg;
|
|
312
|
+
},
|
|
313
|
+
};
|
|
314
|
+
}
|
|
@@ -225,6 +225,19 @@ function _setMusic(configService, update) {
|
|
|
225
225
|
configService.set('backgroundMusic', { ...current, ...update });
|
|
226
226
|
}
|
|
227
227
|
|
|
228
|
+
/**
|
|
229
|
+
* Sync the background-music-enabled.txt flag file so bash hooks see the change.
|
|
230
|
+
* The TUI stores enabled state in config.json, but audio-processor.sh and
|
|
231
|
+
* play-tts-piper.sh read from this .txt file — they must stay in sync.
|
|
232
|
+
* @param {boolean} enabled
|
|
233
|
+
*/
|
|
234
|
+
export function applyEnabledToFile(enabled) {
|
|
235
|
+
const enabledFile = path.join(process.cwd(), '.claude', 'config', 'background-music-enabled.txt');
|
|
236
|
+
try {
|
|
237
|
+
fs.writeFileSync(enabledFile, enabled ? 'true' : 'false', 'utf-8');
|
|
238
|
+
} catch { /* non-fatal */ }
|
|
239
|
+
}
|
|
240
|
+
|
|
228
241
|
/**
|
|
229
242
|
* Patch the 'default' entry in audio-effects.cfg to use the given track.
|
|
230
243
|
* play-tts-piper.sh reads the track from audio-effects.cfg (not from config.json),
|
|
@@ -431,7 +444,9 @@ export function createMusicTab(screen, services) {
|
|
|
431
444
|
|
|
432
445
|
const toggleBtn = _createBtn(_tl('musicToggleBtn'), () => {
|
|
433
446
|
const { enabled } = _getMusic(configService);
|
|
434
|
-
|
|
447
|
+
const next = !enabled;
|
|
448
|
+
_setMusic(configService, { enabled: next });
|
|
449
|
+
applyEnabledToFile(next); // sync bash hooks (audio-processor.sh reads this file)
|
|
435
450
|
refreshDisplay();
|
|
436
451
|
});
|
|
437
452
|
toggleBtn.bottom = 4;
|
|
@@ -772,8 +787,9 @@ export function createMusicTab(screen, services) {
|
|
|
772
787
|
}
|
|
773
788
|
|
|
774
789
|
function _saveLocally() {
|
|
775
|
-
_setMusic(configService, { track: trackId });
|
|
790
|
+
_setMusic(configService, { track: trackId, enabled: true });
|
|
776
791
|
applyTrackToAudioEffects(trackId);
|
|
792
|
+
applyEnabledToFile(true); // saving a track implies enabling music
|
|
777
793
|
refreshDisplay();
|
|
778
794
|
_showTrackChangedNotice(displayName);
|
|
779
795
|
}
|