agentvibes 5.9.0 → 5.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/.agentvibes/config.json +3 -12
  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 +4 -5
  5. package/.claude/config/audio-effects.cfg.sample +52 -52
  6. package/.claude/config/background-music-enabled.txt +1 -1
  7. package/.claude/docs/TERMUX_SETUP.md +408 -408
  8. package/.claude/github-star-reminder.txt +1 -1
  9. package/.claude/hooks/audio-cache-utils.sh +0 -0
  10. package/.claude/hooks/audio-processor.sh +0 -0
  11. package/.claude/hooks/background-music-manager.sh +0 -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 +0 -0
  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 +20 -13
  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 +0 -0
  46. package/.claude/hooks/prepare-release.sh +0 -0
  47. package/.claude/hooks/provider-commands.sh +0 -0
  48. package/.claude/hooks/provider-manager.sh +0 -0
  49. package/.claude/hooks/replay-target-audio.sh +0 -0
  50. package/.claude/hooks/requirements.txt +6 -6
  51. package/.claude/hooks/sentiment-manager.sh +0 -0
  52. package/.claude/hooks/session-start-tts.sh +0 -0
  53. package/.claude/hooks/soprano-gradio-synth.py +139 -139
  54. package/.claude/hooks/speed-manager.sh +0 -0
  55. package/.claude/hooks/stop-tts.sh +0 -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 +237 -237
  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 +6 -0
  63. package/.claude/hooks-windows/play-tts-windows-piper.ps1 +22 -16
  64. package/.claude/hooks-windows/soprano-gradio-synth.py +153 -153
  65. package/.claude/verbosity.txt +1 -1
  66. package/.clawdbot/README.md +105 -105
  67. package/.mcp.json +19 -6
  68. package/README.md +1 -1
  69. package/WINDOWS-SETUP.md +208 -208
  70. package/bin/agent-vibes +39 -39
  71. package/bin/agentvibes-voice-browser.js +0 -0
  72. package/bin/agentvibes.js +0 -0
  73. package/bin/mcp-server.js +121 -121
  74. package/bin/mcp-server.sh +0 -0
  75. package/bin/test-bmad-pr +78 -78
  76. package/mcp-server/QUICK_START.md +203 -203
  77. package/mcp-server/README.md +345 -345
  78. package/mcp-server/WINDOWS_SETUP.md +0 -0
  79. package/mcp-server/examples/claude_desktop_config.json +11 -11
  80. package/mcp-server/examples/claude_desktop_config_piper.json +9 -9
  81. package/mcp-server/examples/custom_instructions.md +169 -169
  82. package/mcp-server/install-deps.js +0 -0
  83. package/mcp-server/server.py +1807 -1797
  84. package/mcp-server/test_server.py +0 -0
  85. package/package.json +2 -2
  86. package/src/cli/list-personalities.js +110 -110
  87. package/src/cli/list-voices.js +114 -114
  88. package/src/commands/bmad-voices.js +394 -394
  89. package/src/commands/install-mcp.js +730 -476
  90. package/src/console/app.js +3 -3
  91. package/src/console/brand-colors.js +13 -13
  92. package/src/console/constants/personalities.js +44 -44
  93. package/src/console/tabs/agents-tab.js +6 -6
  94. package/src/console/tabs/help-tab.js +314 -314
  95. package/src/console/tabs/music-tab.js +1 -1
  96. package/src/console/tabs/readme-tab.js +272 -272
  97. package/src/console/tabs/receiver-tab.js +13 -13
  98. package/src/console/tabs/settings-tab.js +2 -2
  99. package/src/console/tabs/setup-tab.js +10 -10
  100. package/src/console/tabs/voices-tab.js +4 -4
  101. package/src/console/widgets/destroy-list.js +25 -25
  102. package/src/console/widgets/notice.js +55 -55
  103. package/src/console/widgets/personality-picker.js +2 -2
  104. package/src/console/widgets/reverb-picker.js +1 -1
  105. package/src/i18n/de.js +202 -202
  106. package/src/i18n/es.js +202 -202
  107. package/src/i18n/fr.js +202 -202
  108. package/src/i18n/hi.js +202 -202
  109. package/src/i18n/ja.js +202 -202
  110. package/src/i18n/ko.js +202 -202
  111. package/src/i18n/pt.js +202 -202
  112. package/src/i18n/strings.js +54 -54
  113. package/src/i18n/zh-CN.js +202 -202
  114. package/src/installer/language-screen.js +31 -31
  115. package/src/installer/music-file-input.js +304 -304
  116. package/src/installer.js +32 -27
  117. package/src/services/config-service.js +264 -264
  118. package/src/services/language-service.js +47 -47
  119. package/src/services/provider-service.js +143 -143
  120. package/src/services/tts-engine-service.js +2 -2
  121. package/src/utils/audio-duration-validator.js +298 -298
  122. package/src/utils/audio-format-validator.js +277 -277
  123. package/src/utils/dependency-checker.js +469 -469
  124. package/src/utils/file-ownership-verifier.js +358 -358
  125. package/src/utils/list-formatter.js +200 -194
  126. package/src/utils/music-file-validator.js +285 -285
  127. package/src/utils/platform-resolver.js +369 -0
  128. package/src/utils/preview-list-prompt.js +136 -136
  129. package/src/utils/provider-validator.js +9 -9
  130. package/src/utils/secure-music-storage.js +412 -412
  131. package/templates/agentvibes-receiver.sh +231 -231
  132. package/templates/audio/welcome-music.mp3 +0 -0
  133. package/.agentvibes/install-manifest.json +0 -330
  134. package/.claude/config/background-music-position.txt +0 -27
  135. package/.claude/config/background-music-volume.txt +0 -1
  136. package/.claude/config/background-music.cfg +0 -1
  137. package/.claude/config/background-music.txt +0 -1
  138. package/.claude/config/language.txt +0 -1
  139. package/.claude/config/reverb-level.txt +0 -1
  140. package/.claude/config/tts-speech-rate.txt +0 -1
  141. package/.claude/config/tts-verbosity.txt +0 -1
  142. package/.claude/hooks/play-tts-agentvibes-receiver.sh +0 -1
  143. package/.claude/hooks-windows/audio-cache-utils.ps1.user.bak +0 -119
  144. package/.claude/hooks-windows/soprano-gradio-synth.py.user.bak +0 -153
  145. package/.claude/piper-voices-dir.txt +0 -1
@@ -1,272 +1,272 @@
1
- /**
2
- * AgentVibes TUI Console — Readme Tab
3
- * Epic 13: Story 13.2
4
- *
5
- * Implements the Tab Component Contract:
6
- * createReadmeTab(screen, services) → { box, show, hide, onFocus, onBlur, getFooterText, getFooterColor }
7
- *
8
- * Features: renders README.md with styled markdown, scrolling, search.
9
- */
10
-
11
- import fs from 'node:fs';
12
- import path from 'node:path';
13
- import { t } from '../../i18n/strings.js';
14
-
15
- const IS_TEST = process.env.AGENTVIBES_TEST_MODE === 'true';
16
-
17
- let blessed;
18
- if (!IS_TEST) {
19
- const { default: b } = await import('blessed');
20
- blessed = b;
21
- }
22
-
23
- // ---------------------------------------------------------------------------
24
-
25
- const COLORS = {
26
- contentBg: '#0a0e1a',
27
- h1Fg: '#69f0ae', // Green — H1
28
- h2Fg: '#82b1ff', // Blue — H2
29
- h3Fg: '#80d8ff', // Light blue — H3
30
- codeFg: '#ffff00', // Yellow — code spans
31
- boldFg: '#e3f2fd', // Bright — bold text
32
- quoteFg: '#90a4ae', // Gray — blockquotes
33
- labelFg: '#e3f2fd',
34
- borderFg: '#455a64',
35
- footerBg: '#455a64', // Dark gray — Readme tab footer
36
- };
37
-
38
- const _FOOTER_TEXT_EN = '[↑↓/jk] Scroll [PgUp/PgDn] Page [/] Search [S/V/M/A/R] Tab [Q] Quit';
39
-
40
- // ---------------------------------------------------------------------------
41
- // Markdown renderer (story 13.2)
42
-
43
- /**
44
- * Render a single markdown line to blessed tagged string.
45
- * Handles: H1/H2/H3, bold (**), code spans (`), blockquotes (>), plain text.
46
- *
47
- * @param {string} line
48
- * @returns {string}
49
- */
50
- export function renderMarkdownLine(line) {
51
- // H1
52
- if (/^# /.test(line)) {
53
- const text = line.replace(/^# /, '');
54
- return `{bold}{${COLORS.h1Fg}-fg}${text}{/${COLORS.h1Fg}-fg}{/bold}`;
55
- }
56
- // H2
57
- if (/^## /.test(line)) {
58
- const text = line.replace(/^## /, '');
59
- return `{bold}{${COLORS.h2Fg}-fg}${text}{/${COLORS.h2Fg}-fg}{/bold}`;
60
- }
61
- // H3
62
- if (/^### /.test(line)) {
63
- const text = line.replace(/^### /, '');
64
- return `{${COLORS.h3Fg}-fg}${text}{/${COLORS.h3Fg}-fg}`;
65
- }
66
- // Blockquote
67
- if (/^> /.test(line)) {
68
- const text = line.replace(/^> /, '');
69
- return `{${COLORS.quoteFg}-fg}│ ${text}{/${COLORS.quoteFg}-fg}`;
70
- }
71
- // Horizontal rule
72
- if (/^---+$/.test(line.trim())) {
73
- return `{${COLORS.quoteFg}-fg}${'─'.repeat(66)}{/${COLORS.quoteFg}-fg}`;
74
- }
75
-
76
- // Inline: code spans, bold
77
- let result = line;
78
- // Escape existing blessed tags (literal braces in content)
79
- // Code spans: `text`
80
- result = result.replace(/`([^`]+)`/g, `{${COLORS.codeFg}-fg}$1{/${COLORS.codeFg}-fg}`);
81
- // Bold: **text**
82
- result = result.replace(/\*\*([^*]+)\*\*/g, `{bold}$1{/bold}`);
83
- // Italic: *text*
84
- result = result.replace(/\*([^*]+)\*/g, `{${COLORS.h3Fg}-fg}$1{/${COLORS.h3Fg}-fg}`);
85
-
86
- return result;
87
- }
88
-
89
- /**
90
- * Render full markdown content to blessed tagged multi-line string.
91
- * @param {string} markdown
92
- * @returns {string}
93
- */
94
- export function renderMarkdown(markdown) {
95
- return markdown
96
- .split('\n')
97
- .map(line => renderMarkdownLine(line))
98
- .join('\n');
99
- }
100
-
101
- // ---------------------------------------------------------------------------
102
-
103
- function createTestStub() {
104
- return {
105
- box: {},
106
- show: () => {},
107
- hide: () => {},
108
- onFocus: () => {},
109
- onBlur: () => {},
110
- getFooterText: () => _FOOTER_TEXT_EN,
111
- getFooterColor: () => COLORS.footerBg,
112
- };
113
- }
114
-
115
- // ---------------------------------------------------------------------------
116
-
117
- /**
118
- * Create the Readme tab component.
119
- *
120
- * @param {object} screen - Blessed screen instance
121
- * @param {object} services
122
- * @returns {{ box, show, hide, onFocus, onBlur, getFooterText, getFooterColor }}
123
- */
124
- export function createReadmeTab(screen, services) {
125
- if (IS_TEST) return createTestStub();
126
-
127
- const { focusMainTabBar, languageService } = services;
128
- const _tl = (key) => languageService ? languageService.t(key) : t('en', key);
129
-
130
- // -------------------------------------------------------------------------
131
- // Container
132
-
133
- const box = blessed.box({
134
- parent: screen,
135
- top: 5,
136
- left: 0,
137
- width: '100%',
138
- bottom: 2,
139
- hidden: true,
140
- style: { fg: COLORS.labelFg, bg: COLORS.contentBg },
141
- border: { type: 'line' },
142
- borderStyle: { fg: COLORS.borderFg },
143
- });
144
-
145
- // -------------------------------------------------------------------------
146
- // Load README.md
147
-
148
- function _loadReadme() {
149
- // Package root — works whether installed globally (node_modules/.bin) or run from source
150
- const pkgRoot = path.resolve(new URL(import.meta.url).pathname, '..', '..', '..', '..');
151
- const candidates = [
152
- path.resolve(process.cwd(), 'README.md'),
153
- path.resolve(process.cwd(), 'readme.md'),
154
- path.resolve(pkgRoot, 'README.md'), // AgentVibes package README fallback
155
- ];
156
- for (const p of candidates) {
157
- if (fs.existsSync(p)) {
158
- try {
159
- return fs.readFileSync(p, 'utf8');
160
- } catch {
161
- // Unreadable
162
- }
163
- }
164
- }
165
- return `# README\n\n${_tl('readmeNotFound')}`;
166
- }
167
-
168
- // -------------------------------------------------------------------------
169
- // Scrollable content
170
-
171
- const scrollBox = blessed.box({
172
- parent: box,
173
- top: 1,
174
- left: 2,
175
- width: '96%',
176
- bottom: 4,
177
- scrollable: true,
178
- alwaysScroll: true,
179
- tags: true,
180
- keys: true,
181
- vi: true,
182
- mouse: true,
183
- scrollbar: { ch: '│', style: { fg: COLORS.borderFg } },
184
- content: '',
185
- style: { fg: COLORS.labelFg, bg: COLORS.contentBg },
186
- });
187
-
188
- // Scroll indicator
189
- const scrollIndicator = blessed.text({
190
- parent: box,
191
- bottom: 2,
192
- right: 2,
193
- content: '',
194
- tags: true,
195
- style: { fg: COLORS.quoteFg, bg: COLORS.contentBg },
196
- });
197
-
198
- // -------------------------------------------------------------------------
199
- // Render
200
-
201
- function refreshContent() {
202
- const markdown = _loadReadme();
203
- const rendered = renderMarkdown(markdown);
204
- scrollBox.setContent(rendered);
205
- scrollIndicator.setContent(`{#607d8b-fg}${_tl('readmeScrollMore')}{/#607d8b-fg}`);
206
- screen.render();
207
- }
208
-
209
- // Scroll events update indicator
210
- scrollBox.on('scroll', () => {
211
- const atBottom = scrollBox.getScrollPerc() >= 99;
212
- scrollIndicator.setContent(
213
- atBottom ? '' : `{#607d8b-fg}${_tl('readmeScrollMore')}{/#607d8b-fg}`
214
- );
215
- screen.render();
216
- });
217
-
218
- // PgUp/PgDn
219
- scrollBox.key(['pageup'], () => {
220
- scrollBox.scroll(-10);
221
- screen.render();
222
- });
223
- scrollBox.key(['pagedown'], () => {
224
- scrollBox.scroll(10);
225
- screen.render();
226
- });
227
-
228
- // [↑] at top of content → jump to main header tab bar
229
- scrollBox.key(['up'], () => {
230
- if (scrollBox.getScroll() === 0 && typeof focusMainTabBar === 'function') {
231
- focusMainTabBar();
232
- }
233
- });
234
-
235
- // Escape → return to header tab bar
236
- scrollBox.key(['escape'], () => {
237
- if (typeof focusMainTabBar === 'function') { focusMainTabBar(); screen.render(); }
238
- });
239
-
240
- // -------------------------------------------------------------------------
241
- // Tab Component Contract
242
-
243
- return {
244
- box,
245
-
246
- show() {
247
- box.show();
248
- refreshContent();
249
- screen.render();
250
- },
251
-
252
- hide() {
253
- box.hide();
254
- screen.render();
255
- },
256
-
257
- onFocus() {
258
- scrollBox.focus();
259
- screen.render();
260
- },
261
-
262
- onBlur() {},
263
-
264
- getFooterText() {
265
- return _tl('readmeFooter');
266
- },
267
-
268
- getFooterColor() {
269
- return COLORS.footerBg;
270
- },
271
- };
272
- }
1
+ /**
2
+ * AgentVibes TUI Console — Readme Tab
3
+ * Epic 13: Story 13.2
4
+ *
5
+ * Implements the Tab Component Contract:
6
+ * createReadmeTab(screen, services) → { box, show, hide, onFocus, onBlur, getFooterText, getFooterColor }
7
+ *
8
+ * Features: renders README.md with styled markdown, scrolling, search.
9
+ */
10
+
11
+ import fs from 'node:fs';
12
+ import path from 'node:path';
13
+ import { t } from '../../i18n/strings.js';
14
+
15
+ const IS_TEST = process.env.AGENTVIBES_TEST_MODE === 'true';
16
+
17
+ let blessed;
18
+ if (!IS_TEST) {
19
+ const { default: b } = await import('blessed');
20
+ blessed = b;
21
+ }
22
+
23
+ // ---------------------------------------------------------------------------
24
+
25
+ const COLORS = {
26
+ contentBg: '#0a0e1a',
27
+ h1Fg: '#69f0ae', // Green — H1
28
+ h2Fg: '#82b1ff', // Blue — H2
29
+ h3Fg: '#80d8ff', // Light blue — H3
30
+ codeFg: '#ffff00', // Yellow — code spans
31
+ boldFg: '#e3f2fd', // Bright — bold text
32
+ quoteFg: '#90a4ae', // Gray — blockquotes
33
+ labelFg: '#e3f2fd',
34
+ borderFg: '#455a64',
35
+ footerBg: '#455a64', // Dark gray — Readme tab footer
36
+ };
37
+
38
+ const _FOOTER_TEXT_EN = '[↑↓/jk] Scroll [PgUp/PgDn] Page [/] Search [S/V/M/A/R] Tab [Q] Quit';
39
+
40
+ // ---------------------------------------------------------------------------
41
+ // Markdown renderer (story 13.2)
42
+
43
+ /**
44
+ * Render a single markdown line to blessed tagged string.
45
+ * Handles: H1/H2/H3, bold (**), code spans (`), blockquotes (>), plain text.
46
+ *
47
+ * @param {string} line
48
+ * @returns {string}
49
+ */
50
+ export function renderMarkdownLine(line) {
51
+ // H1
52
+ if (/^# /.test(line)) {
53
+ const text = line.replace(/^# /, '');
54
+ return `{bold}{${COLORS.h1Fg}-fg}${text}{/${COLORS.h1Fg}-fg}{/bold}`;
55
+ }
56
+ // H2
57
+ if (/^## /.test(line)) {
58
+ const text = line.replace(/^## /, '');
59
+ return `{bold}{${COLORS.h2Fg}-fg}${text}{/${COLORS.h2Fg}-fg}{/bold}`;
60
+ }
61
+ // H3
62
+ if (/^### /.test(line)) {
63
+ const text = line.replace(/^### /, '');
64
+ return `{${COLORS.h3Fg}-fg}${text}{/${COLORS.h3Fg}-fg}`;
65
+ }
66
+ // Blockquote
67
+ if (/^> /.test(line)) {
68
+ const text = line.replace(/^> /, '');
69
+ return `{${COLORS.quoteFg}-fg}│ ${text}{/${COLORS.quoteFg}-fg}`;
70
+ }
71
+ // Horizontal rule
72
+ if (/^---+$/.test(line.trim())) {
73
+ return `{${COLORS.quoteFg}-fg}${'─'.repeat(66)}{/${COLORS.quoteFg}-fg}`;
74
+ }
75
+
76
+ // Inline: code spans, bold
77
+ let result = line;
78
+ // Escape existing blessed tags (literal braces in content)
79
+ // Code spans: `text`
80
+ result = result.replace(/`([^`]+)`/g, `{${COLORS.codeFg}-fg}$1{/${COLORS.codeFg}-fg}`);
81
+ // Bold: **text**
82
+ result = result.replace(/\*\*([^*]+)\*\*/g, `{bold}$1{/bold}`);
83
+ // Italic: *text*
84
+ result = result.replace(/\*([^*]+)\*/g, `{${COLORS.h3Fg}-fg}$1{/${COLORS.h3Fg}-fg}`);
85
+
86
+ return result;
87
+ }
88
+
89
+ /**
90
+ * Render full markdown content to blessed tagged multi-line string.
91
+ * @param {string} markdown
92
+ * @returns {string}
93
+ */
94
+ export function renderMarkdown(markdown) {
95
+ return markdown
96
+ .split('\n')
97
+ .map(line => renderMarkdownLine(line))
98
+ .join('\n');
99
+ }
100
+
101
+ // ---------------------------------------------------------------------------
102
+
103
+ function createTestStub() {
104
+ return {
105
+ box: {},
106
+ show: () => {},
107
+ hide: () => {},
108
+ onFocus: () => {},
109
+ onBlur: () => {},
110
+ getFooterText: () => _FOOTER_TEXT_EN,
111
+ getFooterColor: () => COLORS.footerBg,
112
+ };
113
+ }
114
+
115
+ // ---------------------------------------------------------------------------
116
+
117
+ /**
118
+ * Create the Readme tab component.
119
+ *
120
+ * @param {object} screen - Blessed screen instance
121
+ * @param {object} services
122
+ * @returns {{ box, show, hide, onFocus, onBlur, getFooterText, getFooterColor }}
123
+ */
124
+ export function createReadmeTab(screen, services) {
125
+ if (IS_TEST) return createTestStub();
126
+
127
+ const { focusMainTabBar, languageService } = services;
128
+ const _tl = (key) => languageService ? languageService.t(key) : t('en', key);
129
+
130
+ // -------------------------------------------------------------------------
131
+ // Container
132
+
133
+ const box = blessed.box({
134
+ parent: screen,
135
+ top: 5,
136
+ left: 0,
137
+ width: '100%',
138
+ bottom: 2,
139
+ hidden: true,
140
+ style: { fg: COLORS.labelFg, bg: COLORS.contentBg },
141
+ border: { type: 'line' },
142
+ borderStyle: { fg: COLORS.borderFg },
143
+ });
144
+
145
+ // -------------------------------------------------------------------------
146
+ // Load README.md
147
+
148
+ function _loadReadme() {
149
+ // Package root — works whether installed globally (node_modules/.bin) or run from source
150
+ const pkgRoot = path.resolve(new URL(import.meta.url).pathname, '..', '..', '..', '..');
151
+ const candidates = [
152
+ path.resolve(process.cwd(), 'README.md'),
153
+ path.resolve(process.cwd(), 'readme.md'),
154
+ path.resolve(pkgRoot, 'README.md'), // AgentVibes package README fallback
155
+ ];
156
+ for (const p of candidates) {
157
+ if (fs.existsSync(p)) {
158
+ try {
159
+ return fs.readFileSync(p, 'utf8');
160
+ } catch {
161
+ // Unreadable
162
+ }
163
+ }
164
+ }
165
+ return `# README\n\n${_tl('readmeNotFound')}`;
166
+ }
167
+
168
+ // -------------------------------------------------------------------------
169
+ // Scrollable content
170
+
171
+ const scrollBox = blessed.box({
172
+ parent: box,
173
+ top: 1,
174
+ left: 2,
175
+ width: '96%',
176
+ bottom: 4,
177
+ scrollable: true,
178
+ alwaysScroll: true,
179
+ tags: true,
180
+ keys: true,
181
+ vi: true,
182
+ mouse: true,
183
+ scrollbar: { ch: '│', style: { fg: COLORS.borderFg } },
184
+ content: '',
185
+ style: { fg: COLORS.labelFg, bg: COLORS.contentBg },
186
+ });
187
+
188
+ // Scroll indicator
189
+ const scrollIndicator = blessed.text({
190
+ parent: box,
191
+ bottom: 2,
192
+ right: 2,
193
+ content: '',
194
+ tags: true,
195
+ style: { fg: COLORS.quoteFg, bg: COLORS.contentBg },
196
+ });
197
+
198
+ // -------------------------------------------------------------------------
199
+ // Render
200
+
201
+ function refreshContent() {
202
+ const markdown = _loadReadme();
203
+ const rendered = renderMarkdown(markdown);
204
+ scrollBox.setContent(rendered);
205
+ scrollIndicator.setContent(`{#607d8b-fg}${_tl('readmeScrollMore')}{/#607d8b-fg}`);
206
+ screen.render();
207
+ }
208
+
209
+ // Scroll events update indicator
210
+ scrollBox.on('scroll', () => {
211
+ const atBottom = scrollBox.getScrollPerc() >= 99;
212
+ scrollIndicator.setContent(
213
+ atBottom ? '' : `{#607d8b-fg}${_tl('readmeScrollMore')}{/#607d8b-fg}`
214
+ );
215
+ screen.render();
216
+ });
217
+
218
+ // PgUp/PgDn
219
+ scrollBox.key(['pageup'], () => {
220
+ scrollBox.scroll(-10);
221
+ screen.render();
222
+ });
223
+ scrollBox.key(['pagedown'], () => {
224
+ scrollBox.scroll(10);
225
+ screen.render();
226
+ });
227
+
228
+ // [↑] at top of content → jump to main header tab bar
229
+ scrollBox.key(['up'], () => {
230
+ if (scrollBox.getScroll() === 0 && typeof focusMainTabBar === 'function') {
231
+ focusMainTabBar();
232
+ }
233
+ });
234
+
235
+ // Escape → return to header tab bar
236
+ scrollBox.key(['escape'], () => {
237
+ if (typeof focusMainTabBar === 'function') { focusMainTabBar(); screen.render(); }
238
+ });
239
+
240
+ // -------------------------------------------------------------------------
241
+ // Tab Component Contract
242
+
243
+ return {
244
+ box,
245
+
246
+ show() {
247
+ box.show();
248
+ refreshContent();
249
+ screen.render();
250
+ },
251
+
252
+ hide() {
253
+ box.hide();
254
+ screen.render();
255
+ },
256
+
257
+ onFocus() {
258
+ scrollBox.focus();
259
+ screen.render();
260
+ },
261
+
262
+ onBlur() {},
263
+
264
+ getFooterText() {
265
+ return _tl('readmeFooter');
266
+ },
267
+
268
+ getFooterColor() {
269
+ return COLORS.footerBg;
270
+ },
271
+ };
272
+ }
@@ -73,10 +73,10 @@ function _getNetworkInfo() {
73
73
  try {
74
74
  if (isWin) {
75
75
  // Use PowerShell to get local IP on Windows
76
- localIp = execSync('powershell -NoProfile -Command "(Get-NetIPAddress -AddressFamily IPv4 | Where-Object { $_.InterfaceAlias -notmatch \'Loopback\' } | Select-Object -First 1).IPAddress"',
76
+ localIp = execSync('powershell -NoProfile -Command "(Get-NetIPAddress -AddressFamily IPv4 | Where-Object { $_.InterfaceAlias -notmatch \'Loopback\' } | Select-Object -First 1).IPAddress"', // NOSONAR
77
77
  { timeout: 5000, stdio: 'pipe' }).toString().trim();
78
78
  } else {
79
- localIp = execSync("hostname -I 2>/dev/null | awk '{print $1}'", { timeout: 3000, stdio: 'pipe' }).toString().trim();
79
+ localIp = execSync("hostname -I 2>/dev/null | awk '{print $1}'", { timeout: 3000, stdio: 'pipe' }).toString().trim(); // NOSONAR
80
80
  }
81
81
  } catch { /* ignore */ }
82
82
  try {
@@ -86,7 +86,7 @@ function _getNetworkInfo() {
86
86
  const m = sshdConf.match(/^Port\s+(\d+)/m);
87
87
  if (m) sshPort = m[1];
88
88
  } else {
89
- const portLine = execSync("grep -E '^Port ' /etc/ssh/sshd_config 2>/dev/null || echo 'Port 22'", { timeout: 3000, stdio: 'pipe' }).toString().trim();
89
+ const portLine = execSync("grep -E '^Port ' /etc/ssh/sshd_config 2>/dev/null || echo 'Port 22'", { timeout: 3000, stdio: 'pipe' }).toString().trim(); // NOSONAR
90
90
  const m = portLine.match(/^Port\s+(\d+)/);
91
91
  if (m) sshPort = m[1];
92
92
  }
@@ -130,7 +130,7 @@ function _detectSetupState() {
130
130
 
131
131
  // Check sshd running
132
132
  try {
133
- const svc = execSync('powershell -NoProfile -Command "(Get-Service sshd -EA SilentlyContinue).Status"',
133
+ const svc = execSync('powershell -NoProfile -Command "(Get-Service sshd -EA SilentlyContinue).Status"', // NOSONAR
134
134
  { timeout: 5000, stdio: 'pipe' }).toString().trim();
135
135
  state.sshdRunning = svc === 'Running';
136
136
  } catch { /* sshd not installed */ }
@@ -143,13 +143,13 @@ function _detectSetupState() {
143
143
 
144
144
  // Check ffmpeg
145
145
  try {
146
- execSync('where ffmpeg', { timeout: 3000, stdio: 'pipe' });
146
+ execSync('where ffmpeg', { timeout: 3000, stdio: 'pipe' }); // NOSONAR
147
147
  state.ffmpegInstalled = true;
148
148
  } catch { /* not found */ }
149
149
 
150
150
  // Check piper
151
151
  try {
152
- execSync('where piper', { timeout: 3000, stdio: 'pipe' });
152
+ execSync('where piper', { timeout: 3000, stdio: 'pipe' }); // NOSONAR
153
153
  state.piperInstalled = true;
154
154
  } catch {
155
155
  const piperPath = path.join(process.env.LOCALAPPDATA || '', 'Programs', 'Piper', 'piper.exe');
@@ -165,10 +165,10 @@ function _detectSetupState() {
165
165
  // Linux/macOS detection (original)
166
166
  let receiverHome = '';
167
167
  try {
168
- execSync('id agentvibes-receiver', { timeout: 3000, stdio: 'pipe' });
168
+ execSync('id agentvibes-receiver', { timeout: 3000, stdio: 'pipe' }); // NOSONAR
169
169
  state.receiverUserExists = true;
170
170
  try {
171
- receiverHome = execSync("getent passwd agentvibes-receiver 2>/dev/null | cut -d: -f6 || echo '/home/agentvibes-receiver'",
171
+ receiverHome = execSync("getent passwd agentvibes-receiver 2>/dev/null | cut -d: -f6 || echo '/home/agentvibes-receiver'", // NOSONAR
172
172
  { timeout: 3000, stdio: 'pipe' }).toString().trim();
173
173
  } catch { receiverHome = '/home/agentvibes-receiver'; }
174
174
  } catch { /* user does not exist */ }
@@ -201,7 +201,7 @@ function _detectSetupState() {
201
201
  } catch { /* no read access */ }
202
202
 
203
203
  try {
204
- const modules = execSync('pactl list modules short 2>/dev/null', { timeout: 3000, stdio: 'pipe' }).toString();
204
+ const modules = execSync('pactl list modules short 2>/dev/null', { timeout: 3000, stdio: 'pipe' }).toString(); // NOSONAR
205
205
  state.tcpModuleLoaded = modules.includes('module-native-protocol-tcp');
206
206
  } catch { /* pactl not available */ }
207
207
  }
@@ -1031,7 +1031,7 @@ export function createReceiverTab(screen, services) {
1031
1031
  screen.render();
1032
1032
 
1033
1033
  // Fire SSH in background — don't block the TUI
1034
- const child = spawn('ssh', ['-o', 'ConnectTimeout=5', sshHost, encoded], {
1034
+ const child = spawn('ssh', ['-o', 'ConnectTimeout=5', sshHost, encoded], { // NOSONAR
1035
1035
  stdio: 'ignore',
1036
1036
  detached: true,
1037
1037
  });
@@ -1361,7 +1361,7 @@ export function createReceiverTab(screen, services) {
1361
1361
  }
1362
1362
  _refreshCachedInfo();
1363
1363
  const text = _buildDetailedInstructions(RECEIVER_ALIAS, RECEIVER_SCRIPT, _networkInfo)
1364
- .replace(/\{[^}]*\}/g, '')
1364
+ .replace(/\{[^}]*\}/g, '') // NOSONAR
1365
1365
  // eslint-disable-next-line no-control-regex
1366
1366
  .replace(/\x1b\[[0-9;]*m/g, '');
1367
1367
  // Try platform-appropriate clipboard command
@@ -1374,7 +1374,7 @@ export function createReceiverTab(screen, services) {
1374
1374
  mkdirSync(AGENTVIBES_DIR, { recursive: true });
1375
1375
  writeFileSync(tmpFile, '\ufeff' + text, 'utf-8');
1376
1376
  const psCmd = `Get-Content -Path "${tmpFile.replace(/\\/g, '/')}" -Encoding UTF8 -Raw | Set-Clipboard; Remove-Item "${tmpFile.replace(/\\/g, '/')}"`;
1377
- const r = spawnSync('powershell', ['-NoProfile', '-Command', psCmd], { timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'] });
1377
+ const r = spawnSync('powershell', ['-NoProfile', '-Command', psCmd], { timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'] }); // NOSONAR
1378
1378
  if (r.status === 0) {
1379
1379
  _showFeedback('{green-fg}Copied to clipboard!{/green-fg}');
1380
1380
  copied = true;
@@ -1413,7 +1413,7 @@ export function createReceiverTab(screen, services) {
1413
1413
  // List available audio sinks and let user pick one
1414
1414
  let sinks;
1415
1415
  try {
1416
- const out = execSync('pactl --server=tcp:127.0.0.1:34567 list sinks short 2>/dev/null || pactl list sinks short 2>/dev/null', { timeout: 5000 }).toString().trim();
1416
+ const out = execSync('pactl --server=tcp:127.0.0.1:34567 list sinks short 2>/dev/null || pactl list sinks short 2>/dev/null', { timeout: 5000 }).toString().trim(); // NOSONAR
1417
1417
  sinks = out.split('\n').filter(l => l.length > 0).map(line => {
1418
1418
  const parts = line.split('\t');
1419
1419
  return { id: parts[0], name: parts[1] || '', driver: parts[2] || '', state: parts[4] || '' };
@@ -645,7 +645,7 @@ export function createSettingsTab(screen, services) {
645
645
  if (_previewVoiceId === voiceId) { _killVP(); vpPreviewLine.setContent(''); _refreshVP(); return; }
646
646
  _killVP();
647
647
 
648
- const phrase = SAMPLE_PHRASES[Math.floor(Math.random() * SAMPLE_PHRASES.length)];
648
+ const phrase = SAMPLE_PHRASES[Math.floor(Math.random() * SAMPLE_PHRASES.length)]; // NOSONAR
649
649
  const playTtsScript = path.join(_projectRoot, '.claude', 'hooks', 'play-tts.sh');
650
650
  if (!fs.existsSync(playTtsScript)) return;
651
651
 
@@ -653,7 +653,7 @@ export function createSettingsTab(screen, services) {
653
653
  const args = [playTtsScript, phrase, voiceId];
654
654
  if (remoteLlm) args.push('--llm', remoteLlm);
655
655
 
656
- _previewProc = spawn('bash', args, {
656
+ _previewProc = spawn('bash', args, { // NOSONAR
657
657
  stdio: 'ignore',
658
658
  detached: true,
659
659
  env: _spawnEnv,