agentvibes 5.10.1 → 5.11.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 +15 -1
- package/.agentvibes/install-manifest.json +342 -0
- package/.claude/audio/ui/CREDITS.txt +16 -0
- package/.claude/audio/ui/bling-success.wav +0 -0
- package/.claude/commands/agent-vibes/receiver.md +64 -0
- 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 -4
- package/.claude/config/audio-effects.cfg.sample +52 -52
- package/.claude/config/background-music-enabled.txt +1 -1
- package/.claude/config/background-music-position.txt +27 -0
- package/.claude/config/background-music-volume.txt +1 -0
- package/.claude/config/background-music.cfg +1 -0
- package/.claude/config/background-music.txt +1 -0
- package/.claude/config/language.txt +1 -0
- package/.claude/config/personality.txt +1 -0
- package/.claude/config/reverb-level.txt +1 -0
- package/.claude/config/tts-speech-rate.txt +1 -0
- package/.claude/config/tts-verbosity.txt +1 -0
- 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 +57 -18
- 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/kokoro-installer.sh +117 -0
- package/.claude/hooks/kokoro-server.py +219 -0
- package/.claude/hooks/kokoro-tts.py +141 -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 +39 -16
- package/.claude/hooks/piper-installer.sh +11 -6
- package/.claude/hooks/piper-multispeaker-registry.sh +0 -0
- package/.claude/hooks/piper-voice-manager.sh +6 -6
- package/.claude/hooks/play-tts-agentvibes-receiver-for-voiceless-connections.sh +3 -1
- package/.claude/hooks/play-tts-agentvibes-receiver.sh +1 -0
- package/.claude/hooks/play-tts-elevenlabs.sh +360 -0
- package/.claude/hooks/play-tts-enhanced.sh +0 -0
- package/.claude/hooks/play-tts-kokoro.sh +127 -0
- package/.claude/hooks/play-tts-macos.sh +0 -0
- package/.claude/hooks/play-tts-piper.sh +0 -0
- package/.claude/hooks/play-tts-soprano.sh +0 -0
- package/.claude/hooks/play-tts-ssh-remote.sh +9 -2
- package/.claude/hooks/play-tts-termux-ssh.sh +3 -1
- package/.claude/hooks/play-tts-windows-receiver.sh +0 -0
- package/.claude/hooks/play-tts.sh +78 -2
- package/.claude/hooks/prepare-release.sh +0 -0
- package/.claude/hooks/provider-commands.sh +71 -22
- package/.claude/hooks/provider-manager.sh +15 -7
- 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 +0 -0
- package/.claude/hooks-windows/audio-cache-utils.ps1.user.bak +119 -0
- package/.claude/hooks-windows/kokoro-server.py +219 -0
- package/.claude/hooks-windows/kokoro-tts.py +107 -0
- package/.claude/hooks-windows/play-tts-kokoro.ps1 +191 -0
- package/.claude/hooks-windows/play-tts.ps1 +29 -1
- package/.claude/hooks-windows/soprano-gradio-synth.py +153 -153
- package/.claude/hooks-windows/soprano-gradio-synth.py.user.bak +153 -0
- package/.claude/piper-voices-dir.txt +1 -0
- package/.claude/verbosity.txt +1 -1
- package/.clawdbot/README.md +105 -105
- package/.mcp.json +6 -19
- package/README.md +12 -86
- package/RELEASE_NOTES.md +60 -0
- 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 -1807
- package/mcp-server/test_server.py +0 -0
- package/package.json +4 -3
- 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 +3 -3
- package/src/console/app.js +25 -9
- package/src/console/audio-env.js +85 -1
- package/src/console/brand-colors.js +13 -13
- package/src/console/constants/personalities.js +44 -44
- package/src/console/navigation.js +4 -0
- package/src/console/tabs/agents-tab.js +12 -6
- package/src/console/tabs/help-tab.js +314 -314
- package/src/console/tabs/music-tab.js +7 -25
- package/src/console/tabs/readme-tab.js +272 -272
- package/src/console/tabs/setup-tab.js +1704 -120
- package/src/console/tabs/voices-tab.js +72 -88
- package/src/console/widgets/destroy-list.js +25 -25
- package/src/console/widgets/format-utils.js +14 -2
- package/src/console/widgets/help-bar.js +55 -0
- package/src/console/widgets/notice.js +55 -55
- package/src/console/widgets/reverb-picker.js +429 -41
- package/src/console/widgets/track-picker.js +60 -51
- package/src/i18n/de.js +2 -1
- package/src/i18n/en.js +1 -0
- package/src/i18n/es.js +2 -1
- package/src/i18n/fr.js +2 -1
- package/src/i18n/hi.js +2 -1
- package/src/i18n/ja.js +2 -1
- package/src/i18n/ko.js +2 -1
- package/src/i18n/pt.js +2 -1
- package/src/i18n/strings.js +9 -9
- package/src/i18n/zh-CN.js +2 -1
- package/src/installer/language-screen.js +31 -31
- package/src/installer/music-file-input.js +304 -304
- package/src/installer.js +95 -7
- package/src/services/config-service.js +264 -264
- package/src/services/language-service.js +47 -47
- package/src/services/provider-service.js +35 -0
- package/src/services/provider-voice-catalog.js +126 -0
- package/src/services/tts-engine-service.js +51 -2
- package/src/utils/audio-duration-validator.js +53 -10
- 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 -136
- package/src/utils/provider-validator.js +70 -0
- package/src/utils/secure-music-storage.js +412 -412
- package/templates/agentvibes-receiver.sh +235 -231
- package/templates/audio/welcome-music.mp3 +0 -0
|
@@ -45,7 +45,7 @@ const COLORS = {
|
|
|
45
45
|
dimFg: '#455a64',
|
|
46
46
|
};
|
|
47
47
|
|
|
48
|
-
const FOOTER_TEXT = '[↑↓/jk] Navigate [Space] Preview [Enter] Select/Install [
|
|
48
|
+
const FOOTER_TEXT = '[↑↓/jk] Navigate [Space] Preview [Enter] Select/Install [f] Favorite ★ [/] Search';
|
|
49
49
|
/**
|
|
50
50
|
* Resolve the Piper voice storage directory using the same precedence as the
|
|
51
51
|
* shell-side get_voice_storage_dir() in piper-voice-manager.sh:
|
|
@@ -229,6 +229,64 @@ export function genderIconTag(gender) {
|
|
|
229
229
|
return '—';
|
|
230
230
|
}
|
|
231
231
|
|
|
232
|
+
// Fixed column widths shared by every provider's voice picker so the rows line up
|
|
233
|
+
// identically regardless of engine. `status` is in terminal cells (emoji count 2).
|
|
234
|
+
export const VOICE_ROW_COLS = Object.freeze({ status: 3, name: 14, lang: 6 });
|
|
235
|
+
|
|
236
|
+
const _TAG_RE = /\{[^}]*\}/g;
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Visible terminal-cell width of a blessed-tagged string. Color tags ({...}) are
|
|
240
|
+
* zero-width; astral-plane emoji (👍/👎, cp ≥ U+1F000) render two cells; the
|
|
241
|
+
* VS16 emoji-presentation selector (U+FE0F) adds none. BMP symbols (★ ♀ ♂ ♪ ✓ ☁)
|
|
242
|
+
* stay one cell. Used to pad the status column so emoji-bearing rows (Piper's
|
|
243
|
+
* thumbs) align with single-glyph rows (Kokoro/ElevenLabs).
|
|
244
|
+
* @param {string} str
|
|
245
|
+
* @returns {number}
|
|
246
|
+
*/
|
|
247
|
+
export function cellWidth(str) {
|
|
248
|
+
const plain = String(str).replace(_TAG_RE, '');
|
|
249
|
+
let w = 0;
|
|
250
|
+
for (const ch of plain) {
|
|
251
|
+
const cp = ch.codePointAt(0);
|
|
252
|
+
if (cp === 0xFE0F) continue; // variation selector — zero width
|
|
253
|
+
w += cp >= 0x1F000 ? 2 : 1; // astral emoji = 2 cells, everything else = 1
|
|
254
|
+
}
|
|
255
|
+
return w;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Canonical voice-row renderer used by ALL voice pickers (Piper, Kokoro,
|
|
260
|
+
* ElevenLabs) so their columns and per-column styling are identical:
|
|
261
|
+
* [status] [Name] [gender ♀/♂ colored] [language grey] [detail grey]
|
|
262
|
+
*
|
|
263
|
+
* @param {object} o
|
|
264
|
+
* @param {string} [o.status=' '] provider indicator slot (e.g. fav/cache marks); may contain tags
|
|
265
|
+
* @param {string} [o.name=''] voice display name
|
|
266
|
+
* @param {string} [o.gender=''] 'Female' | 'Male' | '' (drives the colored icon)
|
|
267
|
+
* @param {string} [o.lang=''] language tag (e.g. 'en-US', 'ja')
|
|
268
|
+
* @param {string} [o.detail=''] trailing description / id / provider (rendered grey)
|
|
269
|
+
* @returns {string} blessed tag-formatted row (host list must set tags:true)
|
|
270
|
+
*/
|
|
271
|
+
export function formatVoiceRow({ status = ' ', name = '', gender = '', lang = '', detail = '' } = {}) {
|
|
272
|
+
const { status: STATUS_W, name: NAME_W, lang: LANG_W } = VOICE_ROW_COLS;
|
|
273
|
+
// Pad the status to a fixed cell width so every provider's name column starts
|
|
274
|
+
// at the same offset, even when the status holds a 2-cell emoji (Piper thumbs).
|
|
275
|
+
const st = status + ' '.repeat(Math.max(0, STATUS_W - cellWidth(status)));
|
|
276
|
+
const nm = name.length > NAME_W ? name.slice(0, NAME_W - 1) + '…' : name.padEnd(NAME_W);
|
|
277
|
+
const lg = String(lang || '').padEnd(LANG_W);
|
|
278
|
+
const det = detail ? `{#9e9e9e-fg}${detail}{/#9e9e9e-fg}` : '';
|
|
279
|
+
return ` ${st} ${nm} ${genderIconTag(gender)} {#9e9e9e-fg}${lg}{/#9e9e9e-fg} ${det}`;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/** Standardized column header matching formatVoiceRow's layout. */
|
|
283
|
+
export function voiceRowHeader() {
|
|
284
|
+
const { status: STATUS_W, name: NAME_W, lang: LANG_W } = VOICE_ROW_COLS;
|
|
285
|
+
// Lead = the row's leading space + status column + the space after it.
|
|
286
|
+
const lead = ' '.repeat(1 + STATUS_W + 1);
|
|
287
|
+
return `${lead}{cyan-fg}${'Name'.padEnd(NAME_W)}{/cyan-fg} {cyan-fg}⚥{/cyan-fg} {cyan-fg}${'Lang'.padEnd(LANG_W)}{/cyan-fg} {cyan-fg}Detail{/cyan-fg}`;
|
|
288
|
+
}
|
|
289
|
+
|
|
232
290
|
// ---------------------------------------------------------------------------
|
|
233
291
|
// Pure helpers — exported for testability
|
|
234
292
|
|
|
@@ -776,7 +834,7 @@ export function createVoicesTab(screen, services) {
|
|
|
776
834
|
|
|
777
835
|
// -------------------------------------------------------------------------
|
|
778
836
|
// Hint text shown in previewLine when the list has focus and nothing is playing
|
|
779
|
-
const HINT_TEXT = '{white-fg}[Space] preview [Enter] select [
|
|
837
|
+
const HINT_TEXT = '{white-fg}[Space] preview [Enter] select [f] favorite ★ [/] search{/white-fg}';
|
|
780
838
|
let _listFocused = false;
|
|
781
839
|
|
|
782
840
|
// Inline selection hint appended to the currently highlighted voice row.
|
|
@@ -1185,49 +1243,10 @@ export function createVoicesTab(screen, services) {
|
|
|
1185
1243
|
return btn;
|
|
1186
1244
|
}
|
|
1187
1245
|
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
_activateVoice(selected);
|
|
1193
|
-
refreshDisplay();
|
|
1194
|
-
}
|
|
1195
|
-
});
|
|
1196
|
-
switchBtn.bottom = 4;
|
|
1197
|
-
switchBtn.left = 4;
|
|
1198
|
-
|
|
1199
|
-
const favoriteBtn = _createBtn(_tl('voicesFavoriteBtn'), () => {
|
|
1200
|
-
const voices = _getFilteredVoices();
|
|
1201
|
-
const selected = voices[voiceList.selected];
|
|
1202
|
-
if (selected) {
|
|
1203
|
-
toggleFavorite(configService, selected);
|
|
1204
|
-
refreshDisplay();
|
|
1205
|
-
}
|
|
1206
|
-
});
|
|
1207
|
-
favoriteBtn.bottom = 4;
|
|
1208
|
-
favoriteBtn.left = 22;
|
|
1209
|
-
|
|
1210
|
-
const installBtn = _createBtn(_tl('voicesDownloadBtn'), () => {
|
|
1211
|
-
const voices = _getFilteredVoices();
|
|
1212
|
-
const selected = voices[voiceList.selected];
|
|
1213
|
-
if (!selected) return;
|
|
1214
|
-
if (_isInstalled(selected)) {
|
|
1215
|
-
const notice = blessed.text({
|
|
1216
|
-
parent: box,
|
|
1217
|
-
top: 'center',
|
|
1218
|
-
left: 'center',
|
|
1219
|
-
content: 'Voice already installed. Scroll down to find uninstalled voices (greyed out).',
|
|
1220
|
-
tags: true,
|
|
1221
|
-
style: { fg: COLORS.noticeFg, bg: COLORS.contentBg },
|
|
1222
|
-
});
|
|
1223
|
-
screen.render();
|
|
1224
|
-
setTimeout(() => { notice.destroy(); screen.render(); }, 3000);
|
|
1225
|
-
} else {
|
|
1226
|
-
_openDownloadModal(selected);
|
|
1227
|
-
}
|
|
1228
|
-
});
|
|
1229
|
-
installBtn.bottom = 4;
|
|
1230
|
-
installBtn.left = 38;
|
|
1246
|
+
// The voice list is button-free: Enter selects (or downloads an uninstalled
|
|
1247
|
+
// Piper voice then selects), Space previews, and 'f'/'*' toggles the favorite
|
|
1248
|
+
// star. The old Switch/Favorite/Download button row was redundant with these
|
|
1249
|
+
// keys and has been removed.
|
|
1231
1250
|
|
|
1232
1251
|
// -------------------------------------------------------------------------
|
|
1233
1252
|
// "Voice Changed" notice — auto-dismisses after 2 s
|
|
@@ -1725,11 +1744,13 @@ export function createVoicesTab(screen, services) {
|
|
|
1725
1744
|
|
|
1726
1745
|
const active = providerService.getActiveVoiceId();
|
|
1727
1746
|
const favorites = getFavorites(configService);
|
|
1728
|
-
|
|
1747
|
+
// Thumbs-down was removed — only the favorite star remains. Pass an empty
|
|
1748
|
+
// set so any pre-existing downvotes in config no longer render.
|
|
1749
|
+
const thumbsDown = [];
|
|
1729
1750
|
const filtered = _getFilteredVoices();
|
|
1730
1751
|
const items = _buildListItems(filtered, active, favorites, thumbsDown);
|
|
1731
1752
|
|
|
1732
|
-
voiceList.setItems(items.length > 0 ? items : ['
|
|
1753
|
+
voiceList.setItems(items.length > 0 ? items : [' No voices installed — run: npx agentvibes voices download']);
|
|
1733
1754
|
const maxIdx = Math.max(0, (items.length > 0 ? items.length : 1) - 1);
|
|
1734
1755
|
voiceList.select(Math.min(savedIdx, maxIdx));
|
|
1735
1756
|
voiceList.childBase = Math.min(savedScroll, Math.max(0, (items.length > 0 ? items.length : 1) - (voiceList.height - 2)));
|
|
@@ -1816,21 +1837,13 @@ export function createVoicesTab(screen, services) {
|
|
|
1816
1837
|
});
|
|
1817
1838
|
|
|
1818
1839
|
// '*' or '+' in voiceList toggles thumbs-up
|
|
1819
|
-
|
|
1840
|
+
// 'f' / '*' / '+' toggle the favorite star. There is a single favorite
|
|
1841
|
+
// mechanism now (the star) — thumbs-up/thumbs-down was removed.
|
|
1842
|
+
voiceList.key(['f', '*', '+'], () => {
|
|
1820
1843
|
const voices = _getFilteredVoices();
|
|
1821
1844
|
const selected = voices[voiceList.selected];
|
|
1822
1845
|
if (selected) {
|
|
1823
|
-
|
|
1824
|
-
refreshDisplay();
|
|
1825
|
-
}
|
|
1826
|
-
});
|
|
1827
|
-
|
|
1828
|
-
// '-' in voiceList toggles thumbs-down
|
|
1829
|
-
voiceList.key(['-'], () => {
|
|
1830
|
-
const voices = _getFilteredVoices();
|
|
1831
|
-
const selected = voices[voiceList.selected];
|
|
1832
|
-
if (selected) {
|
|
1833
|
-
toggleThumbsDown(configService, selected);
|
|
1846
|
+
toggleFavorite(configService, selected);
|
|
1834
1847
|
refreshDisplay();
|
|
1835
1848
|
}
|
|
1836
1849
|
});
|
|
@@ -1961,32 +1974,6 @@ export function createVoicesTab(screen, services) {
|
|
|
1961
1974
|
}
|
|
1962
1975
|
});
|
|
1963
1976
|
|
|
1964
|
-
// -------------------------------------------------------------------------
|
|
1965
|
-
// Button-row keyboard navigation
|
|
1966
|
-
// ↓ at the last list item → descend into the button row (Switch Voice gets focus first)
|
|
1967
|
-
// Note: Tab is NOT used — navigation.js registers screen.key(['tab']) to cycle tabs,
|
|
1968
|
-
// so element.key(['tab']) + screen.key(['tab']) both fire simultaneously.
|
|
1969
|
-
voiceList.key(['down'], () => {
|
|
1970
|
-
const voices = _getFilteredVoices();
|
|
1971
|
-
if (voiceList.selected >= voices.length - 1) {
|
|
1972
|
-
switchBtn.focus();
|
|
1973
|
-
screen.render();
|
|
1974
|
-
}
|
|
1975
|
-
});
|
|
1976
|
-
|
|
1977
|
-
// ←/→ navigate between the three buttons
|
|
1978
|
-
switchBtn.key(['right'], () => { favoriteBtn.focus(); screen.render(); });
|
|
1979
|
-
favoriteBtn.key(['right'], () => { installBtn.focus(); screen.render(); });
|
|
1980
|
-
installBtn.key(['right'], () => { switchBtn.focus(); screen.render(); });
|
|
1981
|
-
switchBtn.key(['left'], () => { installBtn.focus(); screen.render(); });
|
|
1982
|
-
favoriteBtn.key(['left'], () => { switchBtn.focus(); screen.render(); });
|
|
1983
|
-
installBtn.key(['left'], () => { favoriteBtn.focus(); screen.render(); });
|
|
1984
|
-
|
|
1985
|
-
// ↑ or Escape from any button → back to voice list
|
|
1986
|
-
switchBtn.key(['up', 'escape'], () => { voiceList.focus(); screen.render(); });
|
|
1987
|
-
favoriteBtn.key(['up', 'escape'], () => { voiceList.focus(); screen.render(); });
|
|
1988
|
-
installBtn.key(['up', 'escape'], () => { voiceList.focus(); screen.render(); });
|
|
1989
|
-
|
|
1990
1977
|
// -------------------------------------------------------------------------
|
|
1991
1978
|
// Language refresh
|
|
1992
1979
|
|
|
@@ -1995,9 +1982,6 @@ export function createVoicesTab(screen, services) {
|
|
|
1995
1982
|
searchLabelText.setContent(_tl('searchLabel'));
|
|
1996
1983
|
colHeaderText.setContent(`{#00897b-fg}${_tl('voicesColName').padEnd(COL_NAME_W)}{/#00897b-fg}{magenta-fg}♀{/magenta-fg}/{bright-cyan-fg}♂{/bright-cyan-fg} {#00897b-fg}${_tl('voicesColProvider')}{/#00897b-fg}`);
|
|
1997
1984
|
voiceInfoHdr.setContent(`{#00897b-fg}${_tl('voicesInfoHeader')}${'─'.repeat(54)}{/#00897b-fg}`);
|
|
1998
|
-
switchBtn.setContent(_tl('voicesSwitchBtn'));
|
|
1999
|
-
favoriteBtn.setContent(_tl('voicesFavoriteBtn'));
|
|
2000
|
-
installBtn.setContent(_tl('voicesDownloadBtn'));
|
|
2001
1985
|
screen.render();
|
|
2002
1986
|
}
|
|
2003
1987
|
|
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AgentVibes TUI — Shared Widget: Modal Destroy Helper
|
|
3
|
-
*
|
|
4
|
-
* Force-invalidates blessed's olines buffer after destroying a modal widget.
|
|
5
|
-
* Without this, blessed skips repainting cells where lines==olines and the
|
|
6
|
-
* terminal retains stale modal content as ghost artifacts.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Destroy a blessed list/box widget and force full screen repaint.
|
|
11
|
-
*
|
|
12
|
-
* @param {object} widget - blessed widget to destroy
|
|
13
|
-
* @param {object} screen - blessed screen instance
|
|
14
|
-
* @param {Function} [onClose] - optional callback after destruction
|
|
15
|
-
*/
|
|
16
|
-
export function destroyList(widget, screen, onClose) {
|
|
17
|
-
widget.destroy();
|
|
18
|
-
try {
|
|
19
|
-
for (let r = 0; r < screen.height; r++)
|
|
20
|
-
for (let c = 0; c < screen.width; c++)
|
|
21
|
-
if (screen.olines[r]?.[c]) screen.olines[r][c][0] = -1;
|
|
22
|
-
} catch {}
|
|
23
|
-
onClose?.();
|
|
24
|
-
screen.render();
|
|
25
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* AgentVibes TUI — Shared Widget: Modal Destroy Helper
|
|
3
|
+
*
|
|
4
|
+
* Force-invalidates blessed's olines buffer after destroying a modal widget.
|
|
5
|
+
* Without this, blessed skips repainting cells where lines==olines and the
|
|
6
|
+
* terminal retains stale modal content as ghost artifacts.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Destroy a blessed list/box widget and force full screen repaint.
|
|
11
|
+
*
|
|
12
|
+
* @param {object} widget - blessed widget to destroy
|
|
13
|
+
* @param {object} screen - blessed screen instance
|
|
14
|
+
* @param {Function} [onClose] - optional callback after destruction
|
|
15
|
+
*/
|
|
16
|
+
export function destroyList(widget, screen, onClose) {
|
|
17
|
+
widget.destroy();
|
|
18
|
+
try {
|
|
19
|
+
for (let r = 0; r < screen.height; r++)
|
|
20
|
+
for (let c = 0; c < screen.width; c++)
|
|
21
|
+
if (screen.olines[r]?.[c]) screen.olines[r][c][0] = -1;
|
|
22
|
+
} catch {}
|
|
23
|
+
onClose?.();
|
|
24
|
+
screen.render();
|
|
25
|
+
}
|
|
@@ -96,6 +96,18 @@ export function formatVoiceName(voice) {
|
|
|
96
96
|
* @returns {string}
|
|
97
97
|
*/
|
|
98
98
|
export function formatReverbState(preset) {
|
|
99
|
-
const LABELS = {
|
|
100
|
-
|
|
99
|
+
const LABELS = {
|
|
100
|
+
off: 'Off',
|
|
101
|
+
light: 'Light Reverb',
|
|
102
|
+
medium: 'Medium Reverb',
|
|
103
|
+
heavy: 'Heavy Reverb',
|
|
104
|
+
cathedral: 'Cathedral',
|
|
105
|
+
'chorus-light': 'Light Chorus',
|
|
106
|
+
'chorus-deep': 'Deep Chorus',
|
|
107
|
+
'echo-short': 'Echo (short)',
|
|
108
|
+
'echo-long': 'Cave Echo',
|
|
109
|
+
warm: 'Warm',
|
|
110
|
+
radio: 'Radio',
|
|
111
|
+
};
|
|
112
|
+
return LABELS[preset] ?? 'Light Reverb';
|
|
101
113
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgentVibes TUI — Shared Widget: Picker Chrome Helpers
|
|
3
|
+
*
|
|
4
|
+
* Pure, blessed-free string builders shared by every selector modal so the
|
|
5
|
+
* control hints and titles look identical across the whole app. Keeping these
|
|
6
|
+
* free of any blessed import makes them trivially unit-testable (no
|
|
7
|
+
* AGENTVIBES_TEST_MODE gate needed).
|
|
8
|
+
*
|
|
9
|
+
* renderHelpBar([{ key, label }, ...]) -> a tag-formatted hint row
|
|
10
|
+
* selectorTitle('Voice') -> a standardized box label
|
|
11
|
+
*
|
|
12
|
+
* Color grammar (Paul's spec): square brackets grey, key word magenta,
|
|
13
|
+
* the '=' grey, the action label white.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const C_BRACKET = '#9e9e9e'; // grey — the [ ] around the key
|
|
17
|
+
const C_KEY = 'magenta'; // — the key word (Space, Enter, Esc…)
|
|
18
|
+
const C_EQ = '#9e9e9e'; // grey — the '=' sign
|
|
19
|
+
const C_LABEL = 'white'; // — the action label (preview, select…)
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Build one standardized help-bar row from a list of {key, label} hints.
|
|
23
|
+
* Only pass hints whose keys actually do something in this picker — the bar
|
|
24
|
+
* must never advertise a control that does not fire.
|
|
25
|
+
*
|
|
26
|
+
* @param {Array<{key: string, label: string}>} items
|
|
27
|
+
* @param {object} [opts]
|
|
28
|
+
* @param {string} [opts.sep=' '] separator between hints
|
|
29
|
+
* @returns {string} blessed tag-formatted string (host must set tags:true)
|
|
30
|
+
*/
|
|
31
|
+
export function renderHelpBar(items, opts = {}) {
|
|
32
|
+
const sep = opts.sep ?? ' ';
|
|
33
|
+
if (!Array.isArray(items)) return '';
|
|
34
|
+
return items
|
|
35
|
+
.filter((it) => it && it.key && it.label)
|
|
36
|
+
.map(({ key, label }) =>
|
|
37
|
+
`{${C_BRACKET}-fg}[{/${C_BRACKET}-fg}`
|
|
38
|
+
+ `{${C_KEY}-fg}${key}{/${C_KEY}-fg}`
|
|
39
|
+
+ `{${C_BRACKET}-fg}]{/${C_BRACKET}-fg} `
|
|
40
|
+
+ `{${C_EQ}-fg}={/${C_EQ}-fg} `
|
|
41
|
+
+ `{${C_LABEL}-fg}${label}{/${C_LABEL}-fg}`)
|
|
42
|
+
.join(sep);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Standardized box label for a selector modal:
|
|
47
|
+
* ' {bold}{cyan-fg}<name> Selector{/cyan-fg}{/bold} '
|
|
48
|
+
* Pass the noun only — e.g. selectorTitle('Voice') -> ' Voice Selector '.
|
|
49
|
+
*
|
|
50
|
+
* @param {string} name
|
|
51
|
+
* @returns {string}
|
|
52
|
+
*/
|
|
53
|
+
export function selectorTitle(name) {
|
|
54
|
+
return ` {bold}{cyan-fg}${name} Selector{/cyan-fg}{/bold} `;
|
|
55
|
+
}
|
|
@@ -1,55 +1,55 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AgentVibes TUI — Shared Widget: Notice Toast
|
|
3
|
-
*
|
|
4
|
-
* Displays a short auto-dismissing notice modal centred on screen.
|
|
5
|
-
* Usable from any tab; no settings-specific state required.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { destroyList } from './destroy-list.js';
|
|
9
|
-
|
|
10
|
-
const IS_TEST = process.env.AGENTVIBES_TEST_MODE === 'true';
|
|
11
|
-
let blessed;
|
|
12
|
-
if (!IS_TEST) {
|
|
13
|
-
const { default: b } = await import('blessed');
|
|
14
|
-
blessed = b;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Show a temporary notice that auto-dismisses after 2.5 seconds.
|
|
19
|
-
*
|
|
20
|
-
* @param {object} screen - blessed screen instance
|
|
21
|
-
* @param {string} message - text to display
|
|
22
|
-
* @param {object} [opts]
|
|
23
|
-
* @param {string} [opts.bg='#0a0e1a'] - background colour
|
|
24
|
-
* @param {string} [opts.fg='#e3f2fd'] - foreground colour
|
|
25
|
-
* @param {string} [opts.borderFg='bright-cyan'] - border colour
|
|
26
|
-
* @param {number} [opts.durationMs=2500] - auto-dismiss delay in ms
|
|
27
|
-
*/
|
|
28
|
-
export function showNotice(screen, message, opts = {}) {
|
|
29
|
-
const bg = opts.bg ?? '#0a0e1a';
|
|
30
|
-
const fg = opts.fg ?? '#e3f2fd';
|
|
31
|
-
const borderFg = opts.borderFg ?? 'bright-cyan';
|
|
32
|
-
const durationMs = opts.durationMs ?? 2500;
|
|
33
|
-
|
|
34
|
-
const width = Math.max(28, message.length + 6);
|
|
35
|
-
const modal = blessed.box({
|
|
36
|
-
parent: screen,
|
|
37
|
-
top: 'center',
|
|
38
|
-
left: 'center',
|
|
39
|
-
width,
|
|
40
|
-
height: 3,
|
|
41
|
-
border: { type: 'line' },
|
|
42
|
-
tags: true,
|
|
43
|
-
content: `{center}${message}{/center}`,
|
|
44
|
-
style: {
|
|
45
|
-
fg,
|
|
46
|
-
bg,
|
|
47
|
-
border: { fg: borderFg },
|
|
48
|
-
},
|
|
49
|
-
});
|
|
50
|
-
screen.render();
|
|
51
|
-
|
|
52
|
-
setTimeout(() => {
|
|
53
|
-
destroyList(modal, screen);
|
|
54
|
-
}, durationMs);
|
|
55
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* AgentVibes TUI — Shared Widget: Notice Toast
|
|
3
|
+
*
|
|
4
|
+
* Displays a short auto-dismissing notice modal centred on screen.
|
|
5
|
+
* Usable from any tab; no settings-specific state required.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { destroyList } from './destroy-list.js';
|
|
9
|
+
|
|
10
|
+
const IS_TEST = process.env.AGENTVIBES_TEST_MODE === 'true';
|
|
11
|
+
let blessed;
|
|
12
|
+
if (!IS_TEST) {
|
|
13
|
+
const { default: b } = await import('blessed');
|
|
14
|
+
blessed = b;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Show a temporary notice that auto-dismisses after 2.5 seconds.
|
|
19
|
+
*
|
|
20
|
+
* @param {object} screen - blessed screen instance
|
|
21
|
+
* @param {string} message - text to display
|
|
22
|
+
* @param {object} [opts]
|
|
23
|
+
* @param {string} [opts.bg='#0a0e1a'] - background colour
|
|
24
|
+
* @param {string} [opts.fg='#e3f2fd'] - foreground colour
|
|
25
|
+
* @param {string} [opts.borderFg='bright-cyan'] - border colour
|
|
26
|
+
* @param {number} [opts.durationMs=2500] - auto-dismiss delay in ms
|
|
27
|
+
*/
|
|
28
|
+
export function showNotice(screen, message, opts = {}) {
|
|
29
|
+
const bg = opts.bg ?? '#0a0e1a';
|
|
30
|
+
const fg = opts.fg ?? '#e3f2fd';
|
|
31
|
+
const borderFg = opts.borderFg ?? 'bright-cyan';
|
|
32
|
+
const durationMs = opts.durationMs ?? 2500;
|
|
33
|
+
|
|
34
|
+
const width = Math.max(28, message.length + 6);
|
|
35
|
+
const modal = blessed.box({
|
|
36
|
+
parent: screen,
|
|
37
|
+
top: 'center',
|
|
38
|
+
left: 'center',
|
|
39
|
+
width,
|
|
40
|
+
height: 3,
|
|
41
|
+
border: { type: 'line' },
|
|
42
|
+
tags: true,
|
|
43
|
+
content: `{center}${message}{/center}`,
|
|
44
|
+
style: {
|
|
45
|
+
fg,
|
|
46
|
+
bg,
|
|
47
|
+
border: { fg: borderFg },
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
screen.render();
|
|
51
|
+
|
|
52
|
+
setTimeout(() => {
|
|
53
|
+
destroyList(modal, screen);
|
|
54
|
+
}, durationMs);
|
|
55
|
+
}
|