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.
- package/.agentvibes/config.json +3 -12
- 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 +4 -5
- package/.claude/config/audio-effects.cfg.sample +52 -52
- package/.claude/config/background-music-enabled.txt +1 -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-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 +0 -0
- 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 +20 -13
- 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 +0 -0
- package/.claude/hooks/prepare-release.sh +0 -0
- package/.claude/hooks/provider-commands.sh +0 -0
- package/.claude/hooks/provider-manager.sh +0 -0
- package/.claude/hooks/replay-target-audio.sh +0 -0
- package/.claude/hooks/requirements.txt +6 -6
- package/.claude/hooks/sentiment-manager.sh +0 -0
- package/.claude/hooks/session-start-tts.sh +0 -0
- package/.claude/hooks/soprano-gradio-synth.py +139 -139
- package/.claude/hooks/speed-manager.sh +0 -0
- package/.claude/hooks/stop-tts.sh +0 -0
- package/.claude/hooks/termux-installer.sh +0 -0
- package/.claude/hooks/translate-manager.sh +0 -0
- package/.claude/hooks/translator.py +237 -237
- 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 +6 -0
- package/.claude/hooks-windows/play-tts-windows-piper.ps1 +22 -16
- package/.claude/hooks-windows/soprano-gradio-synth.py +153 -153
- package/.claude/verbosity.txt +1 -1
- package/.clawdbot/README.md +105 -105
- package/.mcp.json +19 -6
- package/README.md +1 -1
- package/WINDOWS-SETUP.md +208 -208
- package/bin/agent-vibes +39 -39
- package/bin/agentvibes-voice-browser.js +0 -0
- 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 +0 -0
- 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 +0 -0
- package/mcp-server/server.py +1807 -1797
- package/mcp-server/test_server.py +0 -0
- package/package.json +2 -2
- 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 +730 -476
- package/src/console/app.js +3 -3
- package/src/console/brand-colors.js +13 -13
- package/src/console/constants/personalities.js +44 -44
- package/src/console/tabs/agents-tab.js +6 -6
- package/src/console/tabs/help-tab.js +314 -314
- package/src/console/tabs/music-tab.js +1 -1
- package/src/console/tabs/readme-tab.js +272 -272
- package/src/console/tabs/receiver-tab.js +13 -13
- package/src/console/tabs/settings-tab.js +2 -2
- package/src/console/tabs/setup-tab.js +10 -10
- package/src/console/tabs/voices-tab.js +4 -4
- package/src/console/widgets/destroy-list.js +25 -25
- package/src/console/widgets/notice.js +55 -55
- package/src/console/widgets/personality-picker.js +2 -2
- package/src/console/widgets/reverb-picker.js +1 -1
- 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 +32 -27
- 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/services/tts-engine-service.js +2 -2
- 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 +200 -194
- package/src/utils/music-file-validator.js +285 -285
- package/src/utils/platform-resolver.js +369 -0
- package/src/utils/preview-list-prompt.js +136 -136
- package/src/utils/provider-validator.js +9 -9
- package/src/utils/secure-music-storage.js +412 -412
- package/templates/agentvibes-receiver.sh +231 -231
- package/templates/audio/welcome-music.mp3 +0 -0
- package/.agentvibes/install-manifest.json +0 -330
- package/.claude/config/background-music-position.txt +0 -27
- package/.claude/config/background-music-volume.txt +0 -1
- package/.claude/config/background-music.cfg +0 -1
- package/.claude/config/background-music.txt +0 -1
- package/.claude/config/language.txt +0 -1
- package/.claude/config/reverb-level.txt +0 -1
- package/.claude/config/tts-speech-rate.txt +0 -1
- package/.claude/config/tts-verbosity.txt +0 -1
- package/.claude/hooks/play-tts-agentvibes-receiver.sh +0 -1
- package/.claude/hooks-windows/audio-cache-utils.ps1.user.bak +0 -119
- package/.claude/hooks-windows/soprano-gradio-synth.py.user.bak +0 -153
- 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,
|