agentvibes 5.5.0 → 5.6.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 -30
- package/.claude/config/background-music-enabled.txt +1 -1
- package/.claude/config/background-music-position.txt +6 -6
- package/.claude/github-star-reminder.txt +1 -1
- package/.claude/hooks/play-tts-ssh-remote.sh +119 -42
- package/.claude/hooks/play-tts-windows-receiver.sh +31 -0
- package/.claude/hooks/stop.sh +2 -27
- package/.claude/hooks-windows/play-tts-windows-sapi.ps1 +108 -108
- package/.claude/hooks-windows/play-tts.ps1 +23 -7
- package/.claude/piper-voices-dir.txt +1 -1
- package/.clawdbot/skill/README.md +326 -0
- package/.mcp.json +17 -27
- package/README.md +73 -82
- package/RELEASE_NOTES.md +61 -0
- package/bin/agent-vibes +39 -39
- package/package.json +1 -1
- package/src/bmad-detector.js +71 -71
- package/src/cli/list-personalities.js +110 -110
- package/src/cli/list-voices.js +114 -114
- package/src/commands/bmad-voices.js +394 -394
- package/src/commands/install-mcp.js +476 -476
- package/src/console/brand-colors.js +13 -13
- package/src/console/constants/personalities.js +44 -44
- package/src/console/modals/modal-overlay.js +247 -247
- package/src/console/navigation.js +5 -1
- package/src/console/tabs/agents-tab.js +5 -5
- package/src/console/tabs/help-tab.js +314 -314
- package/src/console/tabs/readme-tab.js +272 -272
- package/src/console/tabs/setup-tab.js +32 -17
- package/src/console/tabs/voices-tab.js +2 -2
- package/src/console/widgets/destroy-list.js +25 -25
- package/src/console/widgets/notice.js +55 -55
- package/src/console/widgets/personality-picker.js +213 -213
- package/src/console/widgets/reverb-picker.js +97 -97
- package/src/console/widgets/track-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/services/agent-voice-store.js +420 -423
- package/src/services/config-service.js +264 -264
- package/src/services/language-service.js +47 -47
- package/src/services/llm-provider-service.js +11 -4
- package/src/services/navigation-service.js +34 -10
- package/src/services/provider-service.js +143 -143
- package/src/utils/audio-duration-validator.js +298 -298
- package/src/utils/audio-format-validator.js +277 -277
- package/src/utils/dependency-checker.js +469 -469
- package/src/utils/file-ownership-verifier.js +358 -358
- package/src/utils/list-formatter.js +194 -194
- package/src/utils/music-file-validator.js +285 -285
- package/src/utils/preview-list-prompt.js +136 -136
- package/src/utils/secure-music-storage.js +412 -412
- package/.agentvibes/LITE-MODE.md +0 -236
- package/.agentvibes/README.md +0 -136
- package/.agentvibes/backup/session-start-tts.sh.20251210_212814 +0 -141
- package/.agentvibes/backups/agents/analyst_20260204_144958.md +0 -78
- package/.agentvibes/backups/agents/architect_20260204_144958.md +0 -72
- package/.agentvibes/backups/agents/dev_20260204_144958.md +0 -74
- package/.agentvibes/backups/agents/pm_20260204_144958.md +0 -72
- package/.agentvibes/backups/agents/quick-flow-solo-dev_20260204_144958.md +0 -64
- package/.agentvibes/backups/agents/sm_20260204_144958.md +0 -87
- package/.agentvibes/backups/agents/tea_20260204_144958.md +0 -79
- package/.agentvibes/backups/agents/tech-writer_20260204_144958.md +0 -82
- package/.agentvibes/backups/agents/ux-designer_20260204_144958.md +0 -80
- package/.agentvibes/config/README-personality-defaults.md +0 -162
- package/.agentvibes/config/agentvibes.json +0 -1
- package/.agentvibes/config/mode.txt +0 -1
- package/.agentvibes/config/personality-voice-defaults.default.json +0 -21
- package/.agentvibes/config/save-audio.txt +0 -1
- package/.agentvibes/config/voice-metadata.json +0 -160
- package/.agentvibes/hooks/help.sh +0 -191
- package/.agentvibes/hooks/post-tool-use-lite.sh +0 -111
- package/.agentvibes/hooks/save-audio-manager.sh +0 -162
- package/.agentvibes/hooks/session-start-full-optimized.sh +0 -102
- package/.agentvibes/hooks/session-start-full.sh +0 -142
- package/.agentvibes/hooks/session-start-lite-v2.sh +0 -34
- package/.agentvibes/hooks/session-start-lite.sh +0 -29
- package/.agentvibes/hooks/stop-lite.sh +0 -115
- package/.agentvibes/hooks/switch-mode.sh +0 -215
- package/.agentvibes/output-styles/audio-summary.md +0 -30
- package/.claude/audio/voice-samples/piper/alan.wav +0 -0
- package/.claude/audio/voice-samples/piper/amy.wav +0 -0
- package/.claude/audio/voice-samples/piper/charlotte.wav +0 -0
- package/.claude/audio/voice-samples/piper/joe.wav +0 -0
- package/.claude/audio/voice-samples/piper/john.wav +0 -0
- package/.claude/audio/voice-samples/piper/katherine.wav +0 -0
- package/.claude/audio/voice-samples/piper/kristin.wav +0 -0
- package/.claude/audio/voice-samples/piper/linda.wav +0 -0
- package/.claude/audio/voice-samples/piper/marcus.wav +0 -0
- package/.claude/audio/voice-samples/piper/ryan.wav +0 -0
- package/.claude/hooks/post-response.sh +0 -41
- package/bin/ensure-soprano-running.sh +0 -43
|
@@ -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
|
+
}
|
|
@@ -725,7 +725,7 @@ export function createSetupTab(screen, services) {
|
|
|
725
725
|
// Guard against double-open (key repeat, double-click)
|
|
726
726
|
if (navigationService?.isModalOpen()) return;
|
|
727
727
|
let _closed = false;
|
|
728
|
-
navigationService?.openModal();
|
|
728
|
+
navigationService?.openModal(null, _closeModal);
|
|
729
729
|
|
|
730
730
|
const defaultPretext = {
|
|
731
731
|
'claude-code': 'Claude Code here',
|
|
@@ -1035,7 +1035,7 @@ export function createSetupTab(screen, services) {
|
|
|
1035
1035
|
}
|
|
1036
1036
|
});
|
|
1037
1037
|
|
|
1038
|
-
fieldList.key(['escape'], _closeModal);
|
|
1038
|
+
fieldList.key(['escape', 'q', 'Q'], _closeModal);
|
|
1039
1039
|
|
|
1040
1040
|
// Remove selection highlight when field list loses focus
|
|
1041
1041
|
fieldList.on('blur', () => {
|
|
@@ -1081,14 +1081,14 @@ export function createSetupTab(screen, services) {
|
|
|
1081
1081
|
allBtns[(i - 1 + allBtns.length) % allBtns.length].focus();
|
|
1082
1082
|
screen.render();
|
|
1083
1083
|
});
|
|
1084
|
-
allBtns[i].key(['escape'], _closeModal);
|
|
1084
|
+
allBtns[i].key(['escape', 'q', 'Q'], _closeModal);
|
|
1085
1085
|
allBtns[i].key(['up'], () => {
|
|
1086
1086
|
fieldList.focus();
|
|
1087
1087
|
screen.render();
|
|
1088
1088
|
});
|
|
1089
1089
|
}
|
|
1090
1090
|
|
|
1091
|
-
modal.key(['escape'], _closeModal);
|
|
1091
|
+
modal.key(['escape', 'q', 'Q'], _closeModal);
|
|
1092
1092
|
fieldList.focus();
|
|
1093
1093
|
screen.render();
|
|
1094
1094
|
}
|
|
@@ -1096,7 +1096,12 @@ export function createSetupTab(screen, services) {
|
|
|
1096
1096
|
// ── TTS Engine picker (for config modal) ──────────────────────────────────
|
|
1097
1097
|
|
|
1098
1098
|
function _openTtsEnginePicker(draft, onDone) {
|
|
1099
|
-
|
|
1099
|
+
function _closePicker() {
|
|
1100
|
+
navigationService?.closeModal();
|
|
1101
|
+
destroyList(picker, screen);
|
|
1102
|
+
onDone();
|
|
1103
|
+
}
|
|
1104
|
+
navigationService?.openModal(null, _closePicker);
|
|
1100
1105
|
|
|
1101
1106
|
const engines = getEngineStatuses();
|
|
1102
1107
|
const items = engines.map(e => {
|
|
@@ -1136,16 +1141,10 @@ export function createSetupTab(screen, services) {
|
|
|
1136
1141
|
} else {
|
|
1137
1142
|
draft.ttsEngine = engines[idx - 1].id;
|
|
1138
1143
|
}
|
|
1139
|
-
|
|
1140
|
-
destroyList(picker, screen);
|
|
1141
|
-
onDone();
|
|
1144
|
+
_closePicker();
|
|
1142
1145
|
});
|
|
1143
1146
|
|
|
1144
|
-
picker.key(['escape'],
|
|
1145
|
-
navigationService?.closeModal();
|
|
1146
|
-
destroyList(picker, screen);
|
|
1147
|
-
onDone();
|
|
1148
|
-
});
|
|
1147
|
+
picker.key(['escape', 'q', 'Q'], _closePicker);
|
|
1149
1148
|
|
|
1150
1149
|
picker.focus();
|
|
1151
1150
|
screen.render();
|
|
@@ -1162,7 +1161,7 @@ export function createSetupTab(screen, services) {
|
|
|
1162
1161
|
}
|
|
1163
1162
|
|
|
1164
1163
|
function _openVoicePickerForLlm(draft, onDone) {
|
|
1165
|
-
navigationService?.openModal();
|
|
1164
|
+
navigationService?.openModal(null, _closeVP);
|
|
1166
1165
|
|
|
1167
1166
|
let _allVoices = [];
|
|
1168
1167
|
let _previewProc = null;
|
|
@@ -1355,7 +1354,16 @@ export function createSetupTab(screen, services) {
|
|
|
1355
1354
|
|
|
1356
1355
|
piper.on('exit', (code) => {
|
|
1357
1356
|
if (_previewVoiceId !== voiceId) { try { fs.unlinkSync(tempWav); } catch {} return; }
|
|
1358
|
-
if (code !== 0) {
|
|
1357
|
+
if (code !== 0) {
|
|
1358
|
+
_previewProc = null; _previewVoiceId = null;
|
|
1359
|
+
if (!_vpClosed) {
|
|
1360
|
+
vpPreviewLine.setContent('{red-fg}♪ Preview failed — is Piper installed?{/red-fg}');
|
|
1361
|
+
screen.render();
|
|
1362
|
+
setTimeout(() => { if (!_vpClosed) { vpPreviewLine.setContent(''); screen.render(); } }, 4000);
|
|
1363
|
+
}
|
|
1364
|
+
try { fs.unlinkSync(tempWav); } catch {};
|
|
1365
|
+
return;
|
|
1366
|
+
}
|
|
1359
1367
|
const wp = detectWavPlayer(_spawnEnv);
|
|
1360
1368
|
if (!wp) return;
|
|
1361
1369
|
const pp = spawn(wp.bin, wp.args(tempWav), {
|
|
@@ -1371,7 +1379,14 @@ export function createSetupTab(screen, services) {
|
|
|
1371
1379
|
try { fs.unlinkSync(tempWav); } catch {}
|
|
1372
1380
|
});
|
|
1373
1381
|
});
|
|
1374
|
-
piper.on('error', () => {
|
|
1382
|
+
piper.on('error', () => {
|
|
1383
|
+
_previewProc = null; _previewVoiceId = null;
|
|
1384
|
+
if (!_vpClosed) {
|
|
1385
|
+
vpPreviewLine.setContent('{red-fg}♪ Cannot find Piper — install it first{/red-fg}');
|
|
1386
|
+
screen.render();
|
|
1387
|
+
setTimeout(() => { if (!_vpClosed) { vpPreviewLine.setContent(''); screen.render(); } }, 4000);
|
|
1388
|
+
}
|
|
1389
|
+
});
|
|
1375
1390
|
}
|
|
1376
1391
|
|
|
1377
1392
|
vpList.key(['enter'], () => {
|
|
@@ -1390,7 +1405,7 @@ export function createSetupTab(screen, services) {
|
|
|
1390
1405
|
const sel = _allVoices[vpList.selected];
|
|
1391
1406
|
if (sel) { toggleThumbsDown(configService, sel); _refreshVP(); }
|
|
1392
1407
|
});
|
|
1393
|
-
vpList.key(['escape', 'q'], _closeVP);
|
|
1408
|
+
vpList.key(['escape', 'q', 'Q'], _closeVP);
|
|
1394
1409
|
|
|
1395
1410
|
// PageUp / PageDown / Home / End navigation
|
|
1396
1411
|
const _pageSize = () => Math.max(1, (vpList.height ?? 10) - 2);
|
|
@@ -1349,7 +1349,7 @@ export function createVoicesTab(screen, services) {
|
|
|
1349
1349
|
okGlobalBtn.key(['left'], () => { okLocalBtn.focus(); screen.render(); });
|
|
1350
1350
|
okLocalBtn.key(['left'], () => { previewBtn.focus(); screen.render(); });
|
|
1351
1351
|
|
|
1352
|
-
modal.key(['escape', 'q'], _close);
|
|
1352
|
+
modal.key(['escape', 'q', 'Q'], _close);
|
|
1353
1353
|
|
|
1354
1354
|
modal.setFront();
|
|
1355
1355
|
okLocalBtn.focus();
|
|
@@ -1550,7 +1550,7 @@ export function createVoicesTab(screen, services) {
|
|
|
1550
1550
|
dlBtn.key(['left'], () => { cancelBtn.focus(); screen.render(); });
|
|
1551
1551
|
cancelBtn.key(['left'], () => { dlBtn.focus(); screen.render(); });
|
|
1552
1552
|
|
|
1553
|
-
modal.key(['escape', 'q'], () => { if (!_downloading) _close(); });
|
|
1553
|
+
modal.key(['escape', 'q', 'Q'], () => { if (!_downloading) _close(); });
|
|
1554
1554
|
|
|
1555
1555
|
modal.setFront();
|
|
1556
1556
|
dlBtn.focus();
|