agentvibes 5.13.0 → 5.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/audio/ui/CREDITS.txt +16 -16
- 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.sample +52 -52
- package/.claude/docs/TERMUX_SETUP.md +408 -408
- package/.claude/github-star-reminder.txt +1 -1
- package/.claude/hooks/kokoro-server.py +219 -219
- package/.claude/hooks/kokoro-tts.py +141 -141
- package/.claude/hooks/party-stage-roster.py +328 -328
- package/.claude/hooks/play-tts-kokoro.sh +7 -2
- package/.claude/hooks/play-tts-ssh-remote.sh +38 -3
- package/.claude/hooks/requirements.txt +6 -6
- package/.claude/hooks/soprano-gradio-synth.py +139 -139
- package/.claude/hooks/translator.py +237 -237
- package/.claude/hooks-windows/kokoro-server.py +219 -219
- package/.claude/hooks-windows/kokoro-tts.py +107 -107
- package/.claude/hooks-windows/play-tts.ps1 +17 -0
- package/.claude/hooks-windows/soprano-gradio-synth.py +153 -153
- package/.claude/verbosity.txt +1 -1
- package/.clawdbot/README.md +105 -105
- package/CLAUDE.md +84 -176
- package/README.md +27 -2
- package/RELEASE_NOTES.md +86 -0
- package/WINDOWS-SETUP.md +208 -208
- package/bin/agent-vibes +39 -39
- package/bin/mcp-server.js +121 -121
- 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/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 +12 -2
- package/mcp-server/server.py +2111 -2085
- package/mcp-server/test_mcp_correctness.py +504 -504
- package/mcp-server/test_windows_script_parity.py +339 -339
- package/package.json +7 -4
- package/setup-ssh-receiver.ps1 +259 -0
- package/setup-windows.ps1 +39 -29
- package/src/cli/list-personalities.js +110 -110
- package/src/commands/bmad-voices.js +394 -394
- package/src/console/brand-colors.js +13 -13
- package/src/console/constants/personalities.js +44 -44
- package/src/console/tabs/agents-tab.js +14 -4
- package/src/console/tabs/help-tab.js +314 -314
- package/src/console/tabs/readme-tab.js +272 -272
- package/src/console/tabs/settings-tab.js +31 -6
- package/src/console/tabs/setup-tab.js +154 -32
- package/src/console/tabs/voices-tab.js +48 -6
- package/src/console/widgets/destroy-list.js +25 -25
- package/src/console/widgets/notice.js +55 -55
- package/src/installer/language-screen.js +31 -31
- package/src/installer/music-file-input.js +304 -304
- package/src/installer.js +103 -43
- package/src/services/language-service.js +47 -47
- package/src/services/llm-provider-service.js +4 -1
- package/src/services/provider-voice-catalog.js +9 -5
- 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/music-file-validator.js +285 -285
- package/src/utils/preview-list-prompt.js +144 -144
- package/src/utils/secure-music-storage.js +412 -412
- package/templates/agentvibes-receiver.ps1 +311 -0
- package/templates/agentvibes-receiver.sh +297 -297
- package/voice-assignments.json +8244 -8244
- package/.agentvibes/config.json +0 -17
- package/.agentvibes/install-manifest.json +0 -342
- package/.claude/config/audio-effects.cfg +0 -7
- package/.claude/config/audio-effects.cfg.bak-kokoro +0 -7
- package/.claude/config/background-music-enabled.txt +0 -1
- 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/personality.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-windows/audio-cache-utils.ps1.user.bak +0 -119
- package/.claude/hooks-windows/soprano-gradio-synth.py.user.bak +0 -153
|
@@ -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
|
+
}
|
|
@@ -21,7 +21,7 @@ import crypto from 'node:crypto';
|
|
|
21
21
|
import { spawn } from 'node:child_process';
|
|
22
22
|
import { fileURLToPath } from 'node:url';
|
|
23
23
|
import {
|
|
24
|
-
scanInstalledVoices, getVoiceMeta, genderIconTag, PIPER_VOICES_DIR,
|
|
24
|
+
scanInstalledVoices, getVoiceMeta, previewPhrase, genderIconTag, PIPER_VOICES_DIR, parseMultiSpeaker,
|
|
25
25
|
} from './voices-tab.js';
|
|
26
26
|
import { voicesForProvider } from '../../services/provider-voice-catalog.js';
|
|
27
27
|
import { LanguageService } from '../../services/language-service.js';
|
|
@@ -210,9 +210,12 @@ export function createSettingsTab(screen, services) {
|
|
|
210
210
|
if (dst === 'remote') {
|
|
211
211
|
const alias = configService?.getConfig?.()?.audio_ssh_alias ?? '';
|
|
212
212
|
const mode = configService?.getConfig?.()?.audio_stream_mode ?? 'text';
|
|
213
|
-
|
|
213
|
+
// Remote value in RED — audio leaves this machine (a deliberate,
|
|
214
|
+
// "here be dragons" destination). Rendered raw (see _refreshValues).
|
|
215
|
+
return `{red-fg}Remote (${alias || 'no alias'}) — ${mode === 'pulse' ? 'PulseAudio' : 'Text Only'}{/red-fg}`;
|
|
214
216
|
}
|
|
215
|
-
|
|
217
|
+
// Local value in GREEN — audio plays here, the safe default.
|
|
218
|
+
return '{green-fg}Local{/green-fg}';
|
|
216
219
|
},
|
|
217
220
|
desc: 'Play audio locally or stream to a remote host via SSH',
|
|
218
221
|
},
|
|
@@ -360,6 +363,10 @@ export function createSettingsTab(screen, services) {
|
|
|
360
363
|
const val = w.setting.getValue();
|
|
361
364
|
if (w.setting.isAction) {
|
|
362
365
|
w.valueWidget.setContent('');
|
|
366
|
+
} else if (w.setting.key === 'audioDst') {
|
|
367
|
+
// audioDst supplies its own colour (green=Local / red=Remote); don't
|
|
368
|
+
// wrap it in the default yellow.
|
|
369
|
+
w.valueWidget.setContent(val);
|
|
363
370
|
} else {
|
|
364
371
|
w.valueWidget.setContent(`{yellow-fg}${val}{/yellow-fg}`);
|
|
365
372
|
}
|
|
@@ -817,7 +824,8 @@ export function createSettingsTab(screen, services) {
|
|
|
817
824
|
if (_previewVoiceId === voiceId) { _killVP(); vpPreviewLine.setContent(''); _refreshVP(); return; }
|
|
818
825
|
_killVP();
|
|
819
826
|
|
|
820
|
-
|
|
827
|
+
// Announce the voice + its engine, consistent with every other picker.
|
|
828
|
+
const phrase = previewPhrase(voiceId);
|
|
821
829
|
|
|
822
830
|
if (_isWin) {
|
|
823
831
|
// Which engine is being previewed? (Default TTS Engine selector.)
|
|
@@ -854,7 +862,8 @@ export function createSettingsTab(screen, services) {
|
|
|
854
862
|
_refreshVP();
|
|
855
863
|
}
|
|
856
864
|
_previewProc = spawn('powershell', _psArgs, // NOSONAR
|
|
857
|
-
{ stdio: 'ignore', detached: false, windowsHide: true,
|
|
865
|
+
{ stdio: 'ignore', detached: false, windowsHide: true,
|
|
866
|
+
env: { ..._spawnEnv, AGENTVIBES_VOICE_SOURCE: 'audition' } });
|
|
858
867
|
_previewProc.on('exit', () => {
|
|
859
868
|
if (_previewVoiceId === voiceId) {
|
|
860
869
|
_previewVoiceId = null;
|
|
@@ -874,10 +883,26 @@ export function createSettingsTab(screen, services) {
|
|
|
874
883
|
const args = [playTtsScript, phrase, voiceId];
|
|
875
884
|
if (remoteLlm) args.push('--llm', remoteLlm);
|
|
876
885
|
|
|
886
|
+
// Mark this as an AUDITION so the resolver keeps the EXACT voice we're
|
|
887
|
+
// previewing (F1) instead of demoting it to the per-LLM/provider voice —
|
|
888
|
+
// essential for engines whose ids aren't self-identifying (SAPI/macOS).
|
|
889
|
+
// Also force the receiver engine to the previewed voice's engine for this
|
|
890
|
+
// one send, so a SAPI/macOS voice actually renders in its own engine.
|
|
891
|
+
let _pvEngine = configService?.getConfig?.()?.ttsEngine
|
|
892
|
+
|| providerService?.getActiveProvider?.() || 'piper';
|
|
893
|
+
// Guard: a transport (ssh-remote/agentvibes-receiver) is not an engine —
|
|
894
|
+
// fall back to piper so we never send the receiver a transport id as its
|
|
895
|
+
// engine (Fable review; mirrors voices-tab).
|
|
896
|
+
if (_pvEngine === 'ssh-remote' || _pvEngine === 'agentvibes-receiver') _pvEngine = 'piper';
|
|
877
897
|
_previewProc = spawn('bash', args, { // NOSONAR
|
|
878
898
|
stdio: 'ignore',
|
|
879
899
|
detached: true,
|
|
880
|
-
env: {
|
|
900
|
+
env: {
|
|
901
|
+
..._spawnEnv,
|
|
902
|
+
CLAUDE_PROJECT_DIR: _projectRoot,
|
|
903
|
+
AGENTVIBES_VOICE_SOURCE: 'audition',
|
|
904
|
+
AGENTVIBES_RECEIVER_PROVIDER_OVERRIDE: receiverProviderId(_pvEngine),
|
|
905
|
+
},
|
|
881
906
|
cwd: _projectRoot,
|
|
882
907
|
});
|
|
883
908
|
_previewVoiceId = voiceId;
|