agentvibes 5.11.2 → 5.12.0-alpha.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/.agentvibes/config.json +1 -15
- package/.claude/activation-instructions +54 -54
- 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 +5 -5
- 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-processor.sh +1 -1
- package/.claude/hooks/background-music-manager.sh +2 -1
- package/.claude/hooks/bmad-speak-enhanced.sh +37 -22
- package/.claude/hooks/bmad-speak.sh +26 -8
- package/.claude/hooks/play-tts-agentvibes-receiver-for-voiceless-connections.sh +1 -1
- package/.claude/hooks/play-tts-kokoro.sh +9 -0
- package/.claude/hooks/play-tts-piper.sh +13 -1
- package/.claude/hooks/play-tts-ssh-remote.sh +78 -5
- package/.claude/hooks/play-tts-termux-ssh.sh +1 -1
- package/.claude/hooks/play-tts.sh +85 -4
- package/.claude/hooks-windows/background-music-manager.ps1 +2 -1
- package/.claude/hooks-windows/play-tts-kokoro.ps1 +14 -0
- package/.claude/hooks-windows/play-tts.ps1 +163 -30
- package/.claude/hooks-windows/voice-manager-windows.ps1 +101 -1
- package/.clawdbot/README.md +105 -105
- package/LICENSE +190 -190
- package/RELEASE_NOTES.md +29 -0
- package/WINDOWS-SETUP.md +208 -208
- package/bin/agent-vibes +39 -39
- package/bin/mcp-server.js +121 -121
- package/bin/mcp-server.sh +6 -19
- package/bin/resolve-utterance.js +137 -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/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/pyproject.toml +52 -52
- package/mcp-server/requirements.txt +2 -2
- package/mcp-server/server.py +280 -99
- package/mcp-server/test_mcp_correctness.py +486 -0
- package/mcp-server/test_server.py +395 -395
- package/mcp-server/test_windows_script_parity.py +341 -336
- package/package.json +3 -2
- package/setup-windows.ps1 +867 -815
- 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/console/app.js +13 -2
- package/src/console/brand-colors.js +13 -13
- package/src/console/constants/personalities.js +44 -44
- package/src/console/tabs/agents-tab.js +82 -9
- package/src/console/tabs/help-tab.js +314 -314
- package/src/console/tabs/music-tab.js +7 -1
- package/src/console/tabs/readme-tab.js +272 -272
- package/src/console/tabs/settings-tab.js +29 -1
- package/src/console/tabs/setup-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/track-picker.js +3 -3
- package/src/i18n/de.js +0 -1
- package/src/i18n/en.js +0 -1
- package/src/i18n/es.js +0 -1
- package/src/i18n/fr.js +0 -1
- package/src/i18n/hi.js +0 -1
- package/src/i18n/ja.js +0 -1
- package/src/i18n/ko.js +0 -1
- package/src/i18n/pt.js +0 -1
- package/src/i18n/zh-CN.js +0 -1
- package/src/installer/language-screen.js +31 -31
- package/src/installer/music-file-input.js +304 -304
- package/src/installer.js +205 -15
- package/src/services/config-service.js +264 -264
- package/src/services/language-service.js +47 -47
- package/src/services/llm-provider-service.js +7 -7
- package/src/services/utterance-loader.js +280 -0
- package/src/services/utterance-resolver.js +526 -0
- 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.sh +74 -12
- package/voice-assignments.json +8245 -0
- package/.agentvibes/install-manifest.json +0 -342
- 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/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
- package/.claude/piper-voices-dir.txt +0 -1
- package/.mcp.json +0 -22
|
@@ -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
|
+
}
|
|
@@ -127,6 +127,9 @@ export function createSettingsTab(screen, services) {
|
|
|
127
127
|
|
|
128
128
|
const { configService, providerService, navigationService, focusMainTabBar, languageService } = services;
|
|
129
129
|
|
|
130
|
+
// Capture cwd once at construction (matches agents-tab.js pattern)
|
|
131
|
+
const _projectRoot = process.cwd();
|
|
132
|
+
|
|
130
133
|
// ── Container ────────────────────────────────────────────────────────────
|
|
131
134
|
|
|
132
135
|
const box = blessed.box({
|
|
@@ -646,6 +649,31 @@ export function createSettingsTab(screen, services) {
|
|
|
646
649
|
_killVP();
|
|
647
650
|
|
|
648
651
|
const phrase = SAMPLE_PHRASES[Math.floor(Math.random() * SAMPLE_PHRASES.length)]; // NOSONAR
|
|
652
|
+
|
|
653
|
+
if (_isWin) {
|
|
654
|
+
// Windows: route through play-tts.ps1 (same pattern as non-Windows bash route)
|
|
655
|
+
const playTtsScript = path.join(_projectRoot, '.claude', 'hooks-windows', 'play-tts.ps1');
|
|
656
|
+
if (!fs.existsSync(playTtsScript)) return;
|
|
657
|
+
_previewVoiceId = voiceId;
|
|
658
|
+
if (!_vpClosed) {
|
|
659
|
+
vpPreviewLine.setContent(`{cyan-fg}♪ Playing: ${voiceId}...{/cyan-fg}`);
|
|
660
|
+
_refreshVP();
|
|
661
|
+
}
|
|
662
|
+
_previewProc = spawn('powershell', [ // NOSONAR
|
|
663
|
+
'-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', playTtsScript, phrase, voiceId,
|
|
664
|
+
], { stdio: 'ignore', detached: false, windowsHide: true, env: _spawnEnv });
|
|
665
|
+
_previewProc.on('exit', () => {
|
|
666
|
+
if (_previewVoiceId === voiceId) {
|
|
667
|
+
_previewVoiceId = null;
|
|
668
|
+
_previewProc = null;
|
|
669
|
+
if (!_vpClosed) { vpPreviewLine.setContent(''); _refreshVP(); }
|
|
670
|
+
}
|
|
671
|
+
});
|
|
672
|
+
_previewProc.on('error', () => { _previewProc = null; _previewVoiceId = null; });
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
// Non-Windows: use bash play-tts.sh
|
|
649
677
|
const playTtsScript = path.join(_projectRoot, '.claude', 'hooks', 'play-tts.sh');
|
|
650
678
|
if (!fs.existsSync(playTtsScript)) return;
|
|
651
679
|
|
|
@@ -656,7 +684,7 @@ export function createSettingsTab(screen, services) {
|
|
|
656
684
|
_previewProc = spawn('bash', args, { // NOSONAR
|
|
657
685
|
stdio: 'ignore',
|
|
658
686
|
detached: true,
|
|
659
|
-
env: _spawnEnv,
|
|
687
|
+
env: { ..._spawnEnv, CLAUDE_PROJECT_DIR: _projectRoot },
|
|
660
688
|
cwd: _projectRoot,
|
|
661
689
|
});
|
|
662
690
|
_previewVoiceId = voiceId;
|
|
@@ -1144,7 +1144,7 @@ export function createSetupTab(screen, services) {
|
|
|
1144
1144
|
pretext: llmConfig.pretext || '',
|
|
1145
1145
|
reverbPreset: llmConfig.effects || 'off',
|
|
1146
1146
|
bgTrack: llmConfig.bgTrack || '',
|
|
1147
|
-
bgVolume: llmConfig.bgVolume || '0.
|
|
1147
|
+
bgVolume: llmConfig.bgVolume || '0.20',
|
|
1148
1148
|
// Hermes SSH fields
|
|
1149
1149
|
mode: currentCfg.mode === 'remote' ? 'remote' : 'local',
|
|
1150
1150
|
sshKey: currentCfg.sshKey || '',
|
|
@@ -1330,7 +1330,7 @@ export function createSetupTab(screen, services) {
|
|
|
1330
1330
|
const previewBtn = _modalBtn('Preview', 4, _playPreview);
|
|
1331
1331
|
const resetBtn = _modalBtn('Reset', 18, () => {
|
|
1332
1332
|
draft.ttsEngine = ''; draft.voice = ''; draft.pretext = '';
|
|
1333
|
-
draft.reverbPreset = 'off'; draft.bgTrack = ''; draft.bgVolume = '0.
|
|
1333
|
+
draft.reverbPreset = 'off'; draft.bgTrack = ''; draft.bgVolume = '0.20';
|
|
1334
1334
|
_autoSave();
|
|
1335
1335
|
fieldList.setItems(_fieldItems());
|
|
1336
1336
|
fieldList.focus();
|
|
@@ -2036,7 +2036,7 @@ export function createSetupTab(screen, services) {
|
|
|
2036
2036
|
pretext: config.pretext || defaultPretext[llmKey] || '',
|
|
2037
2037
|
reverbPreset: config.effects || 'off',
|
|
2038
2038
|
bgTrack: config.bgTrack || '',
|
|
2039
|
-
bgVolume: config.bgVolume || '0.
|
|
2039
|
+
bgVolume: config.bgVolume || '0.20',
|
|
2040
2040
|
// SSH destination fields
|
|
2041
2041
|
mode: sshCfg.mode === 'remote' ? 'remote' : 'local',
|
|
2042
2042
|
sshKey: sshCfg.sshKey || '',
|
|
@@ -2329,7 +2329,7 @@ export function createSetupTab(screen, services) {
|
|
|
2329
2329
|
draft.pretext = defaultPretext[llmKey] || '';
|
|
2330
2330
|
draft.reverbPreset = 'off';
|
|
2331
2331
|
draft.bgTrack = '';
|
|
2332
|
-
draft.bgVolume = '0.
|
|
2332
|
+
draft.bgVolume = '0.20';
|
|
2333
2333
|
draft.mode = 'local';
|
|
2334
2334
|
draft.connType = 'manual';
|
|
2335
2335
|
draft.sshKey = '';
|