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.
Files changed (128) hide show
  1. package/.agentvibes/config.json +2 -0
  2. package/.claude/commands/agent-vibes-bmad-voices.md +117 -117
  3. package/.claude/commands/agent-vibes-rdp.md +24 -24
  4. package/.claude/config/audio-effects.cfg +2 -2
  5. package/.claude/config/background-music-position.txt +0 -1
  6. package/.claude/docs/TERMUX_SETUP.md +408 -408
  7. package/.claude/github-star-reminder.txt +1 -1
  8. package/.claude/hooks/audio-cache-utils.sh +0 -0
  9. package/.claude/hooks/audio-processor.sh +0 -0
  10. package/.claude/hooks/background-music-manager.sh +0 -0
  11. package/.claude/hooks/bmad-party-manager.sh +225 -0
  12. package/.claude/hooks/bmad-party-speak.sh +0 -0
  13. package/.claude/hooks/bmad-speak-enhanced.sh +0 -0
  14. package/.claude/hooks/bmad-speak.sh +0 -0
  15. package/.claude/hooks/bmad-tts-injector.sh +49 -21
  16. package/.claude/hooks/bmad-voice-manager.sh +0 -0
  17. package/.claude/hooks/clawdbot-receiver-SECURE.sh +0 -0
  18. package/.claude/hooks/clawdbot-receiver.sh +0 -0
  19. package/.claude/hooks/clean-audio-cache.sh +0 -0
  20. package/.claude/hooks/cleanup-cache.sh +0 -0
  21. package/.claude/hooks/configure-rdp-mode.sh +0 -0
  22. package/.claude/hooks/download-extra-voices.sh +0 -0
  23. package/.claude/hooks/effects-manager.sh +0 -0
  24. package/.claude/hooks/github-star-reminder.sh +0 -0
  25. package/.claude/hooks/language-manager.sh +0 -0
  26. package/.claude/hooks/learn-manager.sh +0 -0
  27. package/.claude/hooks/macos-voice-manager.sh +0 -0
  28. package/.claude/hooks/migrate-background-music.sh +0 -0
  29. package/.claude/hooks/migrate-to-agentvibes.sh +0 -0
  30. package/.claude/hooks/optimize-background-music.sh +0 -0
  31. package/.claude/hooks/path-resolver.sh +0 -0
  32. package/.claude/hooks/personality-manager.sh +0 -0
  33. package/.claude/hooks/piper-download-voices.sh +0 -0
  34. package/.claude/hooks/piper-installer.sh +0 -0
  35. package/.claude/hooks/piper-multispeaker-registry.sh +0 -0
  36. package/.claude/hooks/piper-voice-manager.sh +0 -0
  37. package/.claude/hooks/play-tts-agentvibes-receiver-for-voiceless-connections.sh +0 -0
  38. package/.claude/hooks/play-tts-enhanced.sh +0 -0
  39. package/.claude/hooks/play-tts-macos.sh +0 -0
  40. package/.claude/hooks/play-tts-piper.sh +1 -1
  41. package/.claude/hooks/play-tts-soprano.sh +0 -0
  42. package/.claude/hooks/play-tts-ssh-remote.sh +0 -0
  43. package/.claude/hooks/play-tts-termux-ssh.sh +0 -0
  44. package/.claude/hooks/play-tts-windows-receiver.sh +0 -0
  45. package/.claude/hooks/play-tts.sh +4 -0
  46. package/.claude/hooks/prepare-release.sh +0 -0
  47. package/.claude/hooks/provider-commands.sh +16 -4
  48. package/.claude/hooks/provider-manager.sh +38 -0
  49. package/.claude/hooks/replay-target-audio.sh +0 -0
  50. package/.claude/hooks/sentiment-manager.sh +0 -0
  51. package/.claude/hooks/session-start-tts.sh +0 -0
  52. package/.claude/hooks/soprano-gradio-synth.py +0 -0
  53. package/.claude/hooks/speed-manager.sh +0 -0
  54. package/.claude/hooks/stop-tts.sh +0 -0
  55. package/.claude/hooks/stop.sh +38 -0
  56. package/.claude/hooks/termux-installer.sh +0 -0
  57. package/.claude/hooks/translate-manager.sh +0 -0
  58. package/.claude/hooks/translator.py +0 -0
  59. package/.claude/hooks/tts-queue-worker.sh +0 -0
  60. package/.claude/hooks/tts-queue.sh +0 -0
  61. package/.claude/hooks/verbosity-manager.sh +0 -0
  62. package/.claude/hooks/voice-manager.sh +50 -2
  63. package/.claude/hooks-windows/audio-cache-utils.ps1 +119 -119
  64. package/.claude/hooks-windows/play-tts.ps1 +34 -1
  65. package/.claude/hooks-windows/tts-watcher.ps1 +122 -0
  66. package/.claude/piper-voices-dir.txt +1 -0
  67. package/.clawdbot/README.md +105 -105
  68. package/.mcp.json +14 -5
  69. package/README.md +10 -2
  70. package/RELEASE_NOTES.md +61 -0
  71. package/WINDOWS-SETUP.md +208 -208
  72. package/bin/agent-vibes +39 -39
  73. package/bin/agentvibes-voice-browser.js +59 -4
  74. package/bin/agentvibes.js +0 -0
  75. package/bin/mcp-server.js +121 -121
  76. package/bin/mcp-server.sh +0 -0
  77. package/bin/test-bmad-pr +78 -78
  78. package/mcp-server/QUICK_START.md +203 -203
  79. package/mcp-server/README.md +345 -345
  80. package/mcp-server/WINDOWS_SETUP.md +260 -260
  81. package/mcp-server/docs/troubleshooting-audio.md +313 -313
  82. package/mcp-server/examples/claude_desktop_config.json +11 -11
  83. package/mcp-server/examples/claude_desktop_config_piper.json +9 -9
  84. package/mcp-server/examples/custom_instructions.md +169 -169
  85. package/mcp-server/install-deps.js +177 -130
  86. package/mcp-server/server.py +1797 -1787
  87. package/mcp-server/test_server.py +0 -0
  88. package/package.json +1 -1
  89. package/src/bmad-detector.js +85 -71
  90. package/src/cli/list-personalities.js +110 -110
  91. package/src/cli/list-voices.js +114 -114
  92. package/src/commands/bmad-voices.js +394 -394
  93. package/src/commands/install-mcp.js +476 -476
  94. package/src/console/brand-colors.js +13 -13
  95. package/src/console/constants/personalities.js +44 -44
  96. package/src/console/tabs/help-tab.js +314 -314
  97. package/src/console/tabs/music-tab.js +18 -2
  98. package/src/console/tabs/readme-tab.js +272 -272
  99. package/src/console/widgets/destroy-list.js +25 -25
  100. package/src/console/widgets/notice.js +55 -55
  101. package/src/console/widgets/personality-picker.js +213 -213
  102. package/src/i18n/de.js +202 -202
  103. package/src/i18n/es.js +202 -202
  104. package/src/i18n/fr.js +202 -202
  105. package/src/i18n/hi.js +202 -202
  106. package/src/i18n/ja.js +202 -202
  107. package/src/i18n/ko.js +202 -202
  108. package/src/i18n/pt.js +202 -202
  109. package/src/i18n/strings.js +54 -54
  110. package/src/i18n/zh-CN.js +202 -202
  111. package/src/installer/language-screen.js +31 -31
  112. package/src/installer/music-file-input.js +304 -304
  113. package/src/installer.js +70 -7
  114. package/src/services/agent-voice-store.js +59 -12
  115. package/src/services/config-service.js +264 -264
  116. package/src/services/language-service.js +47 -47
  117. package/src/services/provider-service.js +143 -143
  118. package/src/utils/audio-duration-validator.js +298 -298
  119. package/src/utils/audio-format-validator.js +277 -277
  120. package/src/utils/dependency-checker.js +469 -469
  121. package/src/utils/file-ownership-verifier.js +358 -358
  122. package/src/utils/list-formatter.js +194 -194
  123. package/src/utils/music-file-validator.js +285 -285
  124. package/src/utils/preview-list-prompt.js +136 -136
  125. package/src/utils/secure-music-storage.js +412 -412
  126. package/templates/agentvibes-receiver.sh +0 -0
  127. package/templates/audio/welcome-music.mp3 +0 -0
  128. 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
- _setMusic(configService, { enabled: !enabled });
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
  }