agentvibes 4.2.0 → 4.4.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/bmad/bmad-voices.md +69 -69
- package/.agentvibes/config.json +12 -0
- package/.claude/activation-instructions +54 -54
- package/.claude/audio/tracks/README.md +52 -52
- package/.claude/commands/agent-vibes/add.md +21 -21
- package/.claude/commands/agent-vibes/agent-vibes.md +101 -101
- package/.claude/commands/agent-vibes/agent.md +79 -79
- package/.claude/commands/agent-vibes/background-music.md +111 -111
- package/.claude/commands/agent-vibes/bmad.md +198 -198
- package/.claude/commands/agent-vibes/clean.md +18 -18
- package/.claude/commands/agent-vibes/cleanup.md +18 -18
- package/.claude/commands/agent-vibes/commands.json +145 -145
- package/.claude/commands/agent-vibes/effects.md +97 -97
- package/.claude/commands/agent-vibes/get.md +9 -9
- package/.claude/commands/agent-vibes/hide.md +91 -91
- package/.claude/commands/agent-vibes/language.md +23 -23
- package/.claude/commands/agent-vibes/learn.md +67 -67
- package/.claude/commands/agent-vibes/list.md +13 -13
- package/.claude/commands/agent-vibes/mute.md +37 -37
- package/.claude/commands/agent-vibes/preview.md +17 -17
- package/.claude/commands/agent-vibes/provider.md +68 -68
- package/.claude/commands/agent-vibes/replay-target.md +14 -14
- package/.claude/commands/agent-vibes/sample.md +12 -12
- package/.claude/commands/agent-vibes/set-favorite-voice.md +84 -84
- package/.claude/commands/agent-vibes/set-pretext.md +65 -65
- package/.claude/commands/agent-vibes/set-speed.md +41 -41
- package/.claude/commands/agent-vibes/show.md +84 -84
- package/.claude/commands/agent-vibes/switch.md +87 -87
- package/.claude/commands/agent-vibes/target-voice.md +26 -26
- package/.claude/commands/agent-vibes/target.md +30 -30
- package/.claude/commands/agent-vibes/translate.md +68 -68
- package/.claude/commands/agent-vibes/unmute.md +45 -45
- package/.claude/commands/agent-vibes/verbosity.md +89 -89
- package/.claude/commands/agent-vibes/whoami.md +7 -7
- package/.claude/commands/agent-vibes-bmad-voices.md +117 -117
- package/.claude/commands/agent-vibes-rdp.md +24 -24
- package/.claude/config/agentvibes.json +1 -0
- package/.claude/config/audio-effects.cfg +2 -2
- package/.claude/config/audio-effects.cfg.sample +52 -52
- package/.claude/config/background-music-volume.txt +1 -0
- package/.claude/config/intro-text.txt +1 -0
- package/.claude/config/piper-speech-rate.txt +4 -0
- package/.claude/config/piper-target-speech-rate.txt +1 -0
- package/.claude/config/reverb-level.txt +1 -0
- package/.claude/config/tts-speech-rate.txt +4 -0
- package/.claude/config/tts-target-speech-rate.txt +1 -0
- package/.claude/docs/TERMUX_SETUP.md +408 -408
- package/.claude/github-star-reminder.txt +1 -1
- package/.claude/hooks/README-TTS-QUEUE.md +135 -135
- package/.claude/hooks/audio-cache-utils.sh +246 -246
- package/.claude/hooks/audio-processor.sh +433 -433
- package/.claude/hooks/background-music-manager.sh +404 -404
- package/.claude/hooks/bmad-speak-enhanced.sh +165 -165
- package/.claude/hooks/bmad-speak.sh +269 -269
- package/.claude/hooks/bmad-tts-injector.sh +568 -568
- package/.claude/hooks/bmad-voice-manager.sh +928 -928
- package/.claude/hooks/clawdbot-receiver-SECURE.sh +129 -129
- package/.claude/hooks/clawdbot-receiver.sh +107 -107
- package/.claude/hooks/clean-audio-cache.sh +22 -22
- package/.claude/hooks/cleanup-cache.sh +106 -106
- package/.claude/hooks/configure-rdp-mode.sh +137 -137
- package/.claude/hooks/download-extra-voices.sh +244 -244
- package/.claude/hooks/effects-manager.sh +268 -268
- package/.claude/hooks/github-star-reminder.sh +154 -154
- package/.claude/hooks/language-manager.sh +362 -362
- package/.claude/hooks/learn-manager.sh +492 -492
- package/.claude/hooks/macos-voice-manager.sh +205 -205
- package/.claude/hooks/migrate-background-music.sh +125 -125
- package/.claude/hooks/migrate-to-agentvibes.sh +161 -161
- package/.claude/hooks/optimize-background-music.sh +87 -87
- package/.claude/hooks/path-resolver.sh +60 -60
- package/.claude/hooks/personality-manager.sh +448 -448
- package/.claude/hooks/piper-download-voices.sh +225 -225
- package/.claude/hooks/piper-installer.sh +292 -292
- package/.claude/hooks/piper-multispeaker-registry.sh +171 -171
- package/.claude/hooks/piper-voice-manager.sh +24 -3
- package/.claude/hooks/play-tts-agentvibes-receiver-for-voiceless-connections.sh +90 -90
- package/.claude/hooks/play-tts-enhanced.sh +105 -105
- package/.claude/hooks/play-tts-macos.sh +368 -368
- package/.claude/hooks/play-tts-piper.sh +679 -679
- package/.claude/hooks/play-tts-soprano.sh +356 -356
- package/.claude/hooks/play-tts-ssh-remote.sh +167 -167
- package/.claude/hooks/play-tts-termux-ssh.sh +169 -169
- package/.claude/hooks/play-tts.sh +301 -301
- package/.claude/hooks/prepare-release.sh +54 -54
- package/.claude/hooks/provider-commands.sh +617 -617
- package/.claude/hooks/provider-manager.sh +399 -399
- package/.claude/hooks/replay-target-audio.sh +95 -95
- package/.claude/hooks/requirements.txt +6 -6
- package/.claude/hooks/sentiment-manager.sh +201 -201
- package/.claude/hooks/session-start-tts.sh +81 -81
- package/.claude/hooks/soprano-gradio-synth.py +139 -139
- package/.claude/hooks/speed-manager.sh +291 -291
- package/.claude/hooks/stop-tts.sh +84 -84
- package/.claude/hooks/termux-installer.sh +261 -261
- package/.claude/hooks/translate-manager.sh +341 -341
- package/.claude/hooks/translator.py +237 -237
- package/.claude/hooks/tts-queue-worker.sh +145 -145
- package/.claude/hooks/tts-queue.sh +165 -165
- package/.claude/hooks/verbosity-manager.sh +178 -178
- package/.claude/hooks/voice-manager.sh +548 -548
- package/.claude/hooks-windows/audio-cache-utils.ps1 +119 -119
- package/.claude/hooks-windows/background-music-manager.ps1 +348 -0
- package/.claude/hooks-windows/clean-audio-cache.ps1 +53 -0
- package/.claude/hooks-windows/download-extra-voices.ps1 +185 -0
- package/.claude/hooks-windows/effects-manager.ps1 +294 -0
- package/.claude/hooks-windows/language-manager.ps1 +193 -0
- package/.claude/hooks-windows/learn-manager.ps1 +241 -0
- package/.claude/hooks-windows/personality-manager.ps1 +266 -0
- package/.claude/hooks-windows/play-tts-piper.ps1 +209 -0
- package/.claude/hooks-windows/play-tts-sapi.ps1 +108 -0
- package/.claude/hooks-windows/play-tts-soprano.ps1 +159 -158
- package/.claude/hooks-windows/play-tts-windows-piper.ps1 +50 -5
- package/.claude/hooks-windows/play-tts-windows-sapi.ps1 +108 -108
- package/.claude/hooks-windows/play-tts.ps1 +344 -266
- package/.claude/hooks-windows/provider-manager.ps1 +29 -10
- package/.claude/hooks-windows/session-start-tts.ps1 +124 -124
- package/.claude/hooks-windows/soprano-gradio-synth.py +153 -153
- package/.claude/hooks-windows/speed-manager.ps1 +166 -0
- package/.claude/hooks-windows/verbosity-manager.ps1 +119 -0
- package/.claude/hooks-windows/voice-manager-windows.ps1 +92 -8
- package/.claude/output-styles/agent-vibes.md +202 -202
- package/.claude/personalities/angry.md +14 -14
- package/.claude/personalities/annoying.md +14 -14
- package/.claude/personalities/crass.md +14 -14
- package/.claude/personalities/dramatic.md +14 -14
- package/.claude/personalities/dry-humor.md +50 -50
- package/.claude/personalities/flirty.md +20 -20
- package/.claude/personalities/funny.md +14 -14
- package/.claude/personalities/grandpa.md +32 -32
- package/.claude/personalities/millennial.md +14 -14
- package/.claude/personalities/moody.md +14 -14
- package/.claude/personalities/normal.md +16 -16
- package/.claude/personalities/pirate.md +14 -14
- package/.claude/personalities/poetic.md +14 -14
- package/.claude/personalities/professional.md +14 -14
- package/.claude/personalities/rapper.md +55 -55
- package/.claude/personalities/robot.md +14 -14
- package/.claude/personalities/sarcastic.md +38 -38
- package/.claude/personalities/sassy.md +14 -14
- package/.claude/personalities/surfer-dude.md +14 -14
- package/.claude/personalities/zen.md +14 -14
- package/.claude/settings.json +15 -15
- package/.claude/verbosity.txt +1 -1
- package/.clawdbot/README.md +105 -105
- package/.clawdbot/skill/SKILL.md +241 -241
- package/.mcp.json +12 -0
- package/CLAUDE.md +170 -170
- package/README.md +2029 -2007
- package/RELEASE_NOTES.md +1310 -1203
- package/WINDOWS-SETUP.md +208 -208
- package/bin/agent-vibes +39 -39
- package/bin/agentvibes-voice-browser.js +1840 -1840
- package/bin/agentvibes.js +48 -2
- package/bin/mcp-server.js +121 -121
- package/bin/mcp-server.sh +206 -206
- 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 +260 -260
- package/mcp-server/docs/troubleshooting-audio.md +313 -313
- 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 +130 -130
- package/mcp-server/pyproject.toml +52 -52
- package/mcp-server/requirements.txt +2 -2
- package/mcp-server/server.py +1465 -1453
- package/mcp-server/test_server.py +395 -395
- package/mcp-server/test_windows_script_parity.py +336 -0
- package/package.json +110 -110
- package/setup-windows.ps1 +815 -815
- 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/app.js +824 -824
- package/src/console/audio-env.js +20 -1
- package/src/console/brand-colors.js +13 -13
- package/src/console/constants/personalities.js +44 -44
- package/src/console/footer-config.js +50 -50
- package/src/console/modals/modal-overlay.js +247 -247
- package/src/console/navigation.js +62 -62
- package/src/console/tabs/agents-tab.js +1684 -1516
- package/src/console/tabs/help-tab.js +261 -261
- package/src/console/tabs/install-tab.js +1007 -991
- package/src/console/tabs/music-tab.js +22 -8
- package/src/console/tabs/placeholder-tab.js +53 -53
- package/src/console/tabs/readme-tab.js +267 -267
- package/src/console/tabs/receiver-tab.js +1472 -1212
- package/src/console/tabs/settings-tab.js +208 -84
- package/src/console/tabs/voices-tab.js +100 -21
- package/src/console/widgets/destroy-list.js +25 -25
- package/src/console/widgets/format-utils.js +89 -89
- package/src/console/widgets/notice.js +55 -55
- package/src/console/widgets/personality-picker.js +185 -185
- package/src/console/widgets/reverb-picker.js +94 -94
- package/src/console/widgets/track-picker.js +285 -285
- package/src/installer/music-file-input.js +304 -304
- package/src/installer.js +5895 -5829
- package/src/services/agent-voice-store.js +423 -423
- package/src/services/config-service.js +264 -264
- package/src/services/navigation-service.js +123 -123
- package/src/services/provider-service.js +143 -132
- package/src/services/verbosity-service.js +157 -157
- 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 -466
- 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/provider-validator.js +96 -12
- package/src/utils/secure-music-storage.js +412 -412
- package/templates/agentvibes-receiver.sh +482 -482
- package/templates/audio/welcome-music.mp3 +0 -0
- package/voice-assignments.json +8244 -8244
- package/.claude/config/background-music-position.txt +0 -1
|
@@ -29,6 +29,35 @@ import { formatTrackName as _sharedFormatTrackName, formatReverbState as _shared
|
|
|
29
29
|
import { showNotice as _showNoticeWidget } from '../widgets/notice.js';
|
|
30
30
|
|
|
31
31
|
const IS_TEST = process.env.AGENTVIBES_TEST_MODE === 'true';
|
|
32
|
+
const _IS_WINDOWS = process.platform === 'win32' && !process.env.WSL_DISTRO_NAME;
|
|
33
|
+
|
|
34
|
+
/** Resolve piper binary path — uses exe on Windows, 'piper' in PATH on Unix */
|
|
35
|
+
function _resolvePiperBin() {
|
|
36
|
+
if (_IS_WINDOWS) {
|
|
37
|
+
const lad = process.env.LOCALAPPDATA ||
|
|
38
|
+
(process.env.USERPROFILE ? path.join(process.env.USERPROFILE, 'AppData', 'Local') : null);
|
|
39
|
+
if (lad) {
|
|
40
|
+
// Standalone binary install
|
|
41
|
+
const exe = path.join(lad, 'Programs', 'Piper', 'piper.exe');
|
|
42
|
+
if (fs.existsSync(exe)) return exe;
|
|
43
|
+
// pip-installed piper (Python Scripts directory)
|
|
44
|
+
const pyScripts = path.join(lad, 'Programs', 'Python');
|
|
45
|
+
try {
|
|
46
|
+
const pyDirs = fs.readdirSync(pyScripts).filter(d => d.startsWith('Python'));
|
|
47
|
+
for (const d of pyDirs) {
|
|
48
|
+
const pipExe = path.join(pyScripts, d, 'Scripts', 'piper.exe');
|
|
49
|
+
if (fs.existsSync(pipExe)) return pipExe;
|
|
50
|
+
}
|
|
51
|
+
} catch { /* no Python installs */ }
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return 'piper';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Build spawn options with Windows-safe defaults (no visible console, no detached) */
|
|
58
|
+
function _spawnOpts(env, extraOpts = {}) {
|
|
59
|
+
return { stdio: 'ignore', detached: !_IS_WINDOWS, windowsHide: true, env, ...extraOpts };
|
|
60
|
+
}
|
|
32
61
|
|
|
33
62
|
// Sanitize strings before passing as env vars to shell commands.
|
|
34
63
|
// Removes characters that could cause shell injection when expanded inside sh -c.
|
|
@@ -51,19 +80,19 @@ const _modalTitle = (text) => ` {${BRAND_PINK}-fg}${text}{/${BRAND_PINK}-fg} `;
|
|
|
51
80
|
|
|
52
81
|
const COLORS = {
|
|
53
82
|
contentBg: '#0a0e1a', // Near-black content background
|
|
54
|
-
sectionHdr: '
|
|
83
|
+
sectionHdr: 'bright-cyan', // Matches header "Agent" color
|
|
55
84
|
labelFg: '#e3f2fd', // Light blue text — labels
|
|
56
85
|
valueFg: '#ffff00', // Yellow — current values
|
|
57
86
|
btnDefault: '#37474f', // Dark slate — default button bg
|
|
58
|
-
btnFocus: '#
|
|
59
|
-
btnFocusFg: '#
|
|
87
|
+
btnFocus: '#2e7d32', // Green — focused/selected button bg
|
|
88
|
+
btnFocusFg: '#ffffff', // White — focused button text
|
|
60
89
|
btnPress: '#ff00ff', // Magenta — pressed button bg
|
|
61
90
|
btnChange: '#37474f', // Dark slate — Change buttons
|
|
62
91
|
btnTest: '#37474f', // Dark slate — Test buttons
|
|
63
92
|
btnEdit: '#37474f', // Dark slate — Edit buttons
|
|
64
93
|
btnEnableOn: '#37474f', // Dark slate — Enabled toggle
|
|
65
94
|
btnEnableOff: '#37474f', // Dark slate — Disabled toggle
|
|
66
|
-
borderFg: '
|
|
95
|
+
borderFg: 'bright-cyan', // Matches section headers
|
|
67
96
|
footerBg: '#2196f3', // Blue — settings footer
|
|
68
97
|
noticeFg: '#90a4ae', // Gray — stub notice text
|
|
69
98
|
};
|
|
@@ -413,9 +442,14 @@ export function createSettingsTab(screen, services) {
|
|
|
413
442
|
`play "${trackPath}" repeat 9999 vol ${volFraction}`,
|
|
414
443
|
`mpg123 -q --loop -1 "${trackPath}"`,
|
|
415
444
|
].join(' 2>/dev/null || ') + ' 2>/dev/null';
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
445
|
+
if (_IS_WINDOWS) {
|
|
446
|
+
const _mp3P = detectMp3Player(_testEnv);
|
|
447
|
+
_testMusicProc = _mp3P
|
|
448
|
+
? spawn(_mp3P.bin, _mp3P.args(trackPath), _spawnOpts(_testEnv))
|
|
449
|
+
: null;
|
|
450
|
+
} else {
|
|
451
|
+
_testMusicProc = spawn('sh', ['-c', musicCmd], _spawnOpts(_testEnv));
|
|
452
|
+
}
|
|
419
453
|
_testMusicProc.unref();
|
|
420
454
|
}
|
|
421
455
|
}
|
|
@@ -463,7 +497,7 @@ export function createSettingsTab(screen, services) {
|
|
|
463
497
|
`soprano "$_AV_PHRASE" -o "$_AV_WAV"`,
|
|
464
498
|
].join(' || ');
|
|
465
499
|
synthProc = spawn('sh', ['-c', cmd], {
|
|
466
|
-
stdio: 'ignore', detached: true, env: sopranoEnv,
|
|
500
|
+
stdio: 'ignore', detached: !_IS_WINDOWS, windowsHide: true, env: sopranoEnv,
|
|
467
501
|
});
|
|
468
502
|
} else {
|
|
469
503
|
const voiceId = providerService.getActiveVoiceId();
|
|
@@ -476,8 +510,8 @@ export function createSettingsTab(screen, services) {
|
|
|
476
510
|
}
|
|
477
511
|
const _piperArgs = ['--model', voicePath, '--output_file', tempWav];
|
|
478
512
|
if (_ms.speakerId != null) _piperArgs.push('--speaker', String(_ms.speakerId));
|
|
479
|
-
synthProc = spawn(
|
|
480
|
-
stdio: ['pipe', 'ignore', 'ignore'], detached: true, env: _testEnv,
|
|
513
|
+
synthProc = spawn(_resolvePiperBin(), _piperArgs, {
|
|
514
|
+
stdio: ['pipe', 'ignore', 'ignore'], detached: !_IS_WINDOWS, windowsHide: true, env: _testEnv,
|
|
481
515
|
});
|
|
482
516
|
synthProc.stdin.write(ttsInput + '\n');
|
|
483
517
|
synthProc.stdin.end();
|
|
@@ -528,7 +562,7 @@ export function createSettingsTab(screen, services) {
|
|
|
528
562
|
_setTestBtnsLabel('■ Stop');
|
|
529
563
|
const _wavPlayer1 = detectWavPlayer(_testEnv);
|
|
530
564
|
const playProc = _wavPlayer1
|
|
531
|
-
? spawn(_wavPlayer1.bin, _wavPlayer1.args(wavToPlay),
|
|
565
|
+
? spawn(_wavPlayer1.bin, _wavPlayer1.args(wavToPlay), _spawnOpts(_testEnv))
|
|
532
566
|
: null;
|
|
533
567
|
if (!playProc) { _killTest(); _restoreTestBtnsLabels(); return; }
|
|
534
568
|
_testVoiceProc = playProc;
|
|
@@ -633,9 +667,14 @@ export function createSettingsTab(screen, services) {
|
|
|
633
667
|
`mpg123 -q "${trackPath}"`,
|
|
634
668
|
].join(' 2>/dev/null || ') + ' 2>/dev/null';
|
|
635
669
|
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
670
|
+
if (_IS_WINDOWS) {
|
|
671
|
+
const _mp3P2 = detectMp3Player(_testEnv);
|
|
672
|
+
_musicTestProc = _mp3P2
|
|
673
|
+
? spawn(_mp3P2.bin, _mp3P2.args(trackPath), _spawnOpts(_testEnv))
|
|
674
|
+
: null;
|
|
675
|
+
} else {
|
|
676
|
+
_musicTestProc = spawn('sh', ['-c', cmd], _spawnOpts(_testEnv));
|
|
677
|
+
}
|
|
639
678
|
_musicTestProc.unref();
|
|
640
679
|
musicTestBtn.setContent('■ Stop');
|
|
641
680
|
screen.render();
|
|
@@ -728,7 +767,7 @@ export function createSettingsTab(screen, services) {
|
|
|
728
767
|
content: lbl, width: lbl.length, height: 1,
|
|
729
768
|
top: 0, left: _xOff,
|
|
730
769
|
keys: true, focusable: true,
|
|
731
|
-
style: { fg: '
|
|
770
|
+
style: { fg: 'bright-cyan', bg: '#263238' },
|
|
732
771
|
});
|
|
733
772
|
_subTabItemsMap[id] = item;
|
|
734
773
|
_xOff += lbl.length;
|
|
@@ -742,7 +781,7 @@ export function createSettingsTab(screen, services) {
|
|
|
742
781
|
item.style.bg = '#0288d1'; // light blue — active tab
|
|
743
782
|
item.style.bold = true;
|
|
744
783
|
} else {
|
|
745
|
-
item.style.fg = '
|
|
784
|
+
item.style.fg = 'bright-cyan';
|
|
746
785
|
item.style.bg = '#263238';
|
|
747
786
|
item.style.bold = false;
|
|
748
787
|
}
|
|
@@ -776,12 +815,24 @@ export function createSettingsTab(screen, services) {
|
|
|
776
815
|
});
|
|
777
816
|
}
|
|
778
817
|
|
|
818
|
+
// -------------------------------------------------------------------------
|
|
819
|
+
// Section header: ── Provider & Voice ──
|
|
820
|
+
|
|
821
|
+
const providerVoiceHeader = blessed.text({
|
|
822
|
+
parent: box,
|
|
823
|
+
top: 3,
|
|
824
|
+
left: 1,
|
|
825
|
+
content: '{bright-cyan-fg} 🎤 Provider & Voice {/bright-cyan-fg}',
|
|
826
|
+
tags: true,
|
|
827
|
+
style: { bg: COLORS.contentBg },
|
|
828
|
+
});
|
|
829
|
+
|
|
779
830
|
// -------------------------------------------------------------------------
|
|
780
831
|
// Provider row: label + value + [Switch] button
|
|
781
832
|
|
|
782
833
|
const providerLabel = blessed.text({
|
|
783
834
|
parent: box,
|
|
784
|
-
top:
|
|
835
|
+
top: 5,
|
|
785
836
|
left: 6,
|
|
786
837
|
content: 'Provider:',
|
|
787
838
|
style: { fg: COLORS.labelFg, bg: COLORS.contentBg },
|
|
@@ -789,7 +840,7 @@ export function createSettingsTab(screen, services) {
|
|
|
789
840
|
|
|
790
841
|
const providerValue = blessed.text({
|
|
791
842
|
parent: box,
|
|
792
|
-
top:
|
|
843
|
+
top: 5,
|
|
793
844
|
left: 22,
|
|
794
845
|
width: 26, // truncate before [Switch] at left:40
|
|
795
846
|
wrap: false,
|
|
@@ -805,7 +856,7 @@ export function createSettingsTab(screen, services) {
|
|
|
805
856
|
screen.render();
|
|
806
857
|
}, _restoreFocus);
|
|
807
858
|
});
|
|
808
|
-
switchBtn.top =
|
|
859
|
+
switchBtn.top = 5;
|
|
809
860
|
switchBtn.left = 52;
|
|
810
861
|
|
|
811
862
|
// -------------------------------------------------------------------------
|
|
@@ -813,7 +864,7 @@ export function createSettingsTab(screen, services) {
|
|
|
813
864
|
|
|
814
865
|
const voiceLabel = blessed.text({
|
|
815
866
|
parent: box,
|
|
816
|
-
top:
|
|
867
|
+
top: 7,
|
|
817
868
|
left: 6,
|
|
818
869
|
content: 'Current Voice:',
|
|
819
870
|
style: { fg: COLORS.labelFg, bg: COLORS.contentBg },
|
|
@@ -821,7 +872,7 @@ export function createSettingsTab(screen, services) {
|
|
|
821
872
|
|
|
822
873
|
const voiceValue = blessed.text({
|
|
823
874
|
parent: box,
|
|
824
|
-
top:
|
|
875
|
+
top: 7,
|
|
825
876
|
left: 22,
|
|
826
877
|
width: 26, // truncate before [Change] at left:40
|
|
827
878
|
wrap: false,
|
|
@@ -837,7 +888,7 @@ export function createSettingsTab(screen, services) {
|
|
|
837
888
|
screen.render();
|
|
838
889
|
}, _restoreFocus);
|
|
839
890
|
}, { bg: COLORS.btnChange });
|
|
840
|
-
changeBtn.top =
|
|
891
|
+
changeBtn.top = 7;
|
|
841
892
|
changeBtn.left = 52;
|
|
842
893
|
|
|
843
894
|
const playBtn = _createButton(box, screen, '▶ Play', COLORS, () => {
|
|
@@ -865,14 +916,19 @@ export function createSettingsTab(screen, services) {
|
|
|
865
916
|
if (!_samplePlaying) { try { fs.unlinkSync(tempWav); } catch {} return; }
|
|
866
917
|
if (code !== 0) {
|
|
867
918
|
_killSample(); playBtn.setContent('▶ Play'); _focusButton(playBtn);
|
|
919
|
+
_showNotice(screen, 'Voice synthesis failed — check voice model');
|
|
868
920
|
try { fs.unlinkSync(tempWav); } catch {}
|
|
869
921
|
return;
|
|
870
922
|
}
|
|
871
923
|
playBtn.setContent('■ Stop');
|
|
872
924
|
screen.render();
|
|
873
925
|
const _wavPlayer2 = detectWavPlayer(_sampleEnv);
|
|
874
|
-
if (!_wavPlayer2) {
|
|
875
|
-
|
|
926
|
+
if (!_wavPlayer2) {
|
|
927
|
+
_stopSpinner(); _killSample(); playBtn.setContent('▶ Play');
|
|
928
|
+
_showNotice(screen, 'No audio player found — install ffplay, sox, or mpv');
|
|
929
|
+
screen.render(); return;
|
|
930
|
+
}
|
|
931
|
+
const playProc = spawn(_wavPlayer2.bin, _wavPlayer2.args(tempWav), _spawnOpts(_sampleEnv));
|
|
876
932
|
_sampleProcess = playProc;
|
|
877
933
|
const _done = () => { _killSample(); playBtn.setContent('▶ Play'); _focusButton(playBtn); try { fs.unlinkSync(tempWav); } catch {} };
|
|
878
934
|
playProc.on('exit', _done);
|
|
@@ -973,7 +1029,7 @@ export function createSettingsTab(screen, services) {
|
|
|
973
1029
|
].join(' || ');
|
|
974
1030
|
|
|
975
1031
|
const soprano = spawn('sh', ['-c', cmd], {
|
|
976
|
-
stdio: 'ignore', detached: true, env: sopranoEnv,
|
|
1032
|
+
stdio: 'ignore', detached: !_IS_WINDOWS, windowsHide: true, env: sopranoEnv,
|
|
977
1033
|
});
|
|
978
1034
|
_sampleProcess = soprano;
|
|
979
1035
|
soprano.on('exit', (code) => {
|
|
@@ -986,31 +1042,67 @@ export function createSettingsTab(screen, services) {
|
|
|
986
1042
|
// Piper (default): pipe text via stdin
|
|
987
1043
|
_startSpinner(playBtn, 'Synthesizing…');
|
|
988
1044
|
const voiceId = providerService.getActiveVoiceId();
|
|
989
|
-
if (!voiceId) {
|
|
1045
|
+
if (!voiceId) {
|
|
1046
|
+
_stopSpinner(); _killSample(); playBtn.setContent('▶ Play');
|
|
1047
|
+
_showNotice(screen, 'No voice selected — choose a voice first');
|
|
1048
|
+
screen.render(); return;
|
|
1049
|
+
}
|
|
990
1050
|
const _ms2 = parseMultiSpeaker(voiceId);
|
|
991
1051
|
const voicePath = path.resolve(PIPER_VOICES_DIR, _ms2.model + '.onnx');
|
|
992
1052
|
const safeBase = path.resolve(PIPER_VOICES_DIR);
|
|
993
1053
|
if (!voicePath.startsWith(safeBase + path.sep) && voicePath !== safeBase) {
|
|
994
|
-
_stopSpinner(); _killSample(); playBtn.setContent('▶ Play');
|
|
1054
|
+
_stopSpinner(); _killSample(); playBtn.setContent('▶ Play');
|
|
1055
|
+
_showNotice(screen, 'Invalid voice path');
|
|
1056
|
+
screen.render(); return;
|
|
1057
|
+
}
|
|
1058
|
+
const piperBin2 = _resolvePiperBin();
|
|
1059
|
+
if (piperBin2 === 'piper') {
|
|
1060
|
+
// Bare command — verify it exists in PATH before spawning
|
|
1061
|
+
const whichCmd = _IS_WINDOWS ? 'where' : 'which';
|
|
1062
|
+
const whichResult = spawnSync(whichCmd, [_IS_WINDOWS ? 'piper.exe' : 'piper'], { stdio: 'pipe', env: _sampleEnv });
|
|
1063
|
+
if (whichResult.status !== 0) {
|
|
1064
|
+
_stopSpinner(); _killSample(); playBtn.setContent('▶ Play');
|
|
1065
|
+
_showNotice(screen, 'Piper not installed — run the installer or: pip install piper-tts');
|
|
1066
|
+
_focusButton(playBtn); screen.render(); return;
|
|
1067
|
+
}
|
|
995
1068
|
}
|
|
996
1069
|
const _piperArgs2 = ['--model', voicePath, '--output_file', tempWav];
|
|
997
1070
|
if (_ms2.speakerId != null) _piperArgs2.push('--speaker', String(_ms2.speakerId));
|
|
998
|
-
const piper = spawn(
|
|
999
|
-
stdio: ['pipe', 'ignore', '
|
|
1071
|
+
const piper = spawn(piperBin2, _piperArgs2, {
|
|
1072
|
+
stdio: ['pipe', 'ignore', 'pipe'], detached: !_IS_WINDOWS, windowsHide: true, env: _sampleEnv,
|
|
1000
1073
|
});
|
|
1074
|
+
let _piperStderr = '';
|
|
1075
|
+
piper.stderr.on('data', (d) => { _piperStderr += d.toString(); });
|
|
1001
1076
|
piper.stdin.write(phrase + '\n');
|
|
1002
1077
|
piper.stdin.end();
|
|
1003
1078
|
_sampleProcess = piper;
|
|
1004
|
-
piper.on('exit',
|
|
1005
|
-
|
|
1079
|
+
piper.on('exit', (code) => {
|
|
1080
|
+
if (code !== 0 && _piperStderr) {
|
|
1081
|
+
// Python tracebacks: actual error is the LAST non-empty line
|
|
1082
|
+
const lines = _piperStderr.split('\n').map(l => l.trim()).filter(Boolean);
|
|
1083
|
+
const errLine = lines[lines.length - 1] || lines[0] || 'unknown error';
|
|
1084
|
+
_stopSpinner();
|
|
1085
|
+
if (!_samplePlaying) { try { fs.unlinkSync(tempWav); } catch {} return; }
|
|
1086
|
+
_killSample(); playBtn.setContent('▶ Play'); _focusButton(playBtn);
|
|
1087
|
+
_showNotice(screen, errLine.length > 100 ? errLine.substring(0, 97) + '…' : errLine);
|
|
1088
|
+
try { fs.unlinkSync(tempWav); } catch {}
|
|
1089
|
+
return;
|
|
1090
|
+
}
|
|
1091
|
+
_onSynthDone(code);
|
|
1092
|
+
});
|
|
1093
|
+
piper.on('error', (e) => {
|
|
1094
|
+
_stopSpinner(); _killSample(); playBtn.setContent('▶ Play');
|
|
1095
|
+
_showNotice(screen, `Piper failed: ${e.message}`);
|
|
1096
|
+
_focusButton(playBtn);
|
|
1097
|
+
});
|
|
1006
1098
|
}
|
|
1007
1099
|
});
|
|
1008
|
-
playBtn.top =
|
|
1100
|
+
playBtn.top = 7;
|
|
1009
1101
|
playBtn.left = 64;
|
|
1010
1102
|
|
|
1011
1103
|
const voiceFileText = blessed.text({
|
|
1012
1104
|
parent: box,
|
|
1013
|
-
top:
|
|
1105
|
+
top: 8,
|
|
1014
1106
|
left: 22,
|
|
1015
1107
|
right: 2,
|
|
1016
1108
|
wrap: false,
|
|
@@ -1025,7 +1117,7 @@ export function createSettingsTab(screen, services) {
|
|
|
1025
1117
|
parent: box,
|
|
1026
1118
|
top: 3,
|
|
1027
1119
|
left: 1,
|
|
1028
|
-
content: '{
|
|
1120
|
+
content: '{bright-cyan-fg} ⚡ Audio Effects {/bright-cyan-fg}',
|
|
1029
1121
|
tags: true,
|
|
1030
1122
|
style: { bg: COLORS.contentBg },
|
|
1031
1123
|
});
|
|
@@ -1081,7 +1173,7 @@ export function createSettingsTab(screen, services) {
|
|
|
1081
1173
|
parent: box,
|
|
1082
1174
|
top: 7,
|
|
1083
1175
|
left: 1,
|
|
1084
|
-
content: '{
|
|
1176
|
+
content: '{bright-cyan-fg} 🎸 Background Music {/bright-cyan-fg}',
|
|
1085
1177
|
tags: true,
|
|
1086
1178
|
style: { bg: COLORS.contentBg },
|
|
1087
1179
|
});
|
|
@@ -1177,12 +1269,24 @@ export function createSettingsTab(screen, services) {
|
|
|
1177
1269
|
volumeChangeBtn.top = 11;
|
|
1178
1270
|
volumeChangeBtn.left = 52;
|
|
1179
1271
|
|
|
1272
|
+
// -------------------------------------------------------------------------
|
|
1273
|
+
// Section header: ── Style ──
|
|
1274
|
+
|
|
1275
|
+
const styleHeader = blessed.text({
|
|
1276
|
+
parent: box,
|
|
1277
|
+
top: 3,
|
|
1278
|
+
left: 1,
|
|
1279
|
+
content: '{bright-cyan-fg} 🎭 Style {/bright-cyan-fg}',
|
|
1280
|
+
tags: true,
|
|
1281
|
+
style: { bg: COLORS.contentBg },
|
|
1282
|
+
});
|
|
1283
|
+
|
|
1180
1284
|
// -------------------------------------------------------------------------
|
|
1181
1285
|
// Verbosity row: label + value + [Change] button
|
|
1182
1286
|
|
|
1183
1287
|
const verbosityLabel = blessed.text({
|
|
1184
1288
|
parent: box,
|
|
1185
|
-
top:
|
|
1289
|
+
top: 5,
|
|
1186
1290
|
left: 6,
|
|
1187
1291
|
content: 'Verbosity:',
|
|
1188
1292
|
style: { fg: COLORS.labelFg, bg: COLORS.contentBg },
|
|
@@ -1190,7 +1294,7 @@ export function createSettingsTab(screen, services) {
|
|
|
1190
1294
|
|
|
1191
1295
|
const verbosityValue = blessed.text({
|
|
1192
1296
|
parent: box,
|
|
1193
|
-
top:
|
|
1297
|
+
top: 5,
|
|
1194
1298
|
left: 22,
|
|
1195
1299
|
width: 26, // truncate before [Change] at left:40
|
|
1196
1300
|
wrap: false,
|
|
@@ -1201,12 +1305,12 @@ export function createSettingsTab(screen, services) {
|
|
|
1201
1305
|
const verbosityChangeBtn = _createButton(box, screen, 'Change', COLORS, () => {
|
|
1202
1306
|
_openVerbosityPicker(screen, configService, () => refreshDisplay(), _restoreFocus);
|
|
1203
1307
|
}, { bg: COLORS.btnChange });
|
|
1204
|
-
verbosityChangeBtn.top =
|
|
1308
|
+
verbosityChangeBtn.top = 5;
|
|
1205
1309
|
verbosityChangeBtn.left = 52;
|
|
1206
1310
|
|
|
1207
1311
|
const verbosityPathText = blessed.text({
|
|
1208
1312
|
parent: box,
|
|
1209
|
-
top:
|
|
1313
|
+
top: 6,
|
|
1210
1314
|
left: 22,
|
|
1211
1315
|
right: 2,
|
|
1212
1316
|
wrap: false,
|
|
@@ -1219,7 +1323,7 @@ export function createSettingsTab(screen, services) {
|
|
|
1219
1323
|
|
|
1220
1324
|
const personalityLabel = blessed.text({
|
|
1221
1325
|
parent: box,
|
|
1222
|
-
top:
|
|
1326
|
+
top: 7,
|
|
1223
1327
|
left: 6,
|
|
1224
1328
|
content: 'Personality:',
|
|
1225
1329
|
style: { fg: COLORS.labelFg, bg: COLORS.contentBg },
|
|
@@ -1227,7 +1331,7 @@ export function createSettingsTab(screen, services) {
|
|
|
1227
1331
|
|
|
1228
1332
|
const personalityValue = blessed.text({
|
|
1229
1333
|
parent: box,
|
|
1230
|
-
top:
|
|
1334
|
+
top: 7,
|
|
1231
1335
|
left: 22,
|
|
1232
1336
|
width: 26, // truncate before [Change] at left:40
|
|
1233
1337
|
wrap: false,
|
|
@@ -1241,7 +1345,7 @@ export function createSettingsTab(screen, services) {
|
|
|
1241
1345
|
refreshDisplay();
|
|
1242
1346
|
}, _restoreFocus);
|
|
1243
1347
|
}, { bg: COLORS.btnChange });
|
|
1244
|
-
personalityChangeBtn.top =
|
|
1348
|
+
personalityChangeBtn.top = 7;
|
|
1245
1349
|
personalityChangeBtn.left = 52;
|
|
1246
1350
|
|
|
1247
1351
|
const personalityTestBtn = _createButton(box, screen, '▶ Preview', COLORS, () => {
|
|
@@ -1252,12 +1356,12 @@ export function createSettingsTab(screen, services) {
|
|
|
1252
1356
|
: _buildPreviewPhrase();
|
|
1253
1357
|
_runTest(false, phrase);
|
|
1254
1358
|
}, { bg: COLORS.btnTest });
|
|
1255
|
-
personalityTestBtn.top =
|
|
1359
|
+
personalityTestBtn.top = 7;
|
|
1256
1360
|
personalityTestBtn.left = 64;
|
|
1257
1361
|
|
|
1258
1362
|
const personalityFileText = blessed.text({
|
|
1259
1363
|
parent: box,
|
|
1260
|
-
top:
|
|
1364
|
+
top: 8,
|
|
1261
1365
|
left: 22,
|
|
1262
1366
|
right: 2,
|
|
1263
1367
|
wrap: false,
|
|
@@ -1271,9 +1375,9 @@ export function createSettingsTab(screen, services) {
|
|
|
1271
1375
|
|
|
1272
1376
|
const introTextHeader = blessed.text({
|
|
1273
1377
|
parent: box,
|
|
1274
|
-
top:
|
|
1378
|
+
top: 10,
|
|
1275
1379
|
left: 1,
|
|
1276
|
-
content: '{
|
|
1380
|
+
content: '{bright-cyan-fg} ✍️ Intro Text {/bright-cyan-fg}',
|
|
1277
1381
|
tags: true,
|
|
1278
1382
|
style: { bg: COLORS.contentBg },
|
|
1279
1383
|
});
|
|
@@ -1283,7 +1387,7 @@ export function createSettingsTab(screen, services) {
|
|
|
1283
1387
|
|
|
1284
1388
|
const introTextLabel = blessed.text({
|
|
1285
1389
|
parent: box,
|
|
1286
|
-
top:
|
|
1390
|
+
top: 12,
|
|
1287
1391
|
left: 6,
|
|
1288
1392
|
content: 'Intro Text:',
|
|
1289
1393
|
style: { fg: COLORS.labelFg, bg: COLORS.contentBg },
|
|
@@ -1291,7 +1395,7 @@ export function createSettingsTab(screen, services) {
|
|
|
1291
1395
|
|
|
1292
1396
|
const introTextValue = blessed.text({
|
|
1293
1397
|
parent: box,
|
|
1294
|
-
top:
|
|
1398
|
+
top: 12,
|
|
1295
1399
|
left: 22,
|
|
1296
1400
|
width: 26, // truncate before [Edit] at left:40
|
|
1297
1401
|
wrap: false,
|
|
@@ -1302,19 +1406,19 @@ export function createSettingsTab(screen, services) {
|
|
|
1302
1406
|
const introEditBtn = _createButton(box, screen, 'Edit', COLORS, () => {
|
|
1303
1407
|
_openIntroTextEditor(screen, configService, () => { refreshDisplay(); }, _restoreFocus);
|
|
1304
1408
|
}, { bg: COLORS.btnEdit });
|
|
1305
|
-
introEditBtn.top =
|
|
1409
|
+
introEditBtn.top = 12;
|
|
1306
1410
|
introEditBtn.left = 52;
|
|
1307
1411
|
|
|
1308
1412
|
const introClearBtn = _createButton(box, screen, 'Clear', COLORS, () => {
|
|
1309
1413
|
configService.set('pretext', '');
|
|
1310
1414
|
refreshDisplay();
|
|
1311
1415
|
}, { bg: '#c62828' });
|
|
1312
|
-
introClearBtn.top =
|
|
1416
|
+
introClearBtn.top = 12;
|
|
1313
1417
|
introClearBtn.left = 64;
|
|
1314
1418
|
|
|
1315
1419
|
const introPathText = blessed.text({
|
|
1316
1420
|
parent: box,
|
|
1317
|
-
top:
|
|
1421
|
+
top: 13,
|
|
1318
1422
|
left: 22,
|
|
1319
1423
|
right: 2,
|
|
1320
1424
|
wrap: false,
|
|
@@ -1334,7 +1438,7 @@ export function createSettingsTab(screen, services) {
|
|
|
1334
1438
|
parent: box,
|
|
1335
1439
|
top: 3,
|
|
1336
1440
|
left: 2,
|
|
1337
|
-
content: '{
|
|
1441
|
+
content: '{bright-cyan-fg} 📡 Audio Destination {/bright-cyan-fg}',
|
|
1338
1442
|
tags: true,
|
|
1339
1443
|
style: { bg: COLORS.contentBg },
|
|
1340
1444
|
});
|
|
@@ -1463,7 +1567,7 @@ export function createSettingsTab(screen, services) {
|
|
|
1463
1567
|
const current = configService.getConfig().audio_stream_mode ?? 'text';
|
|
1464
1568
|
configService.set('audio_stream_mode', current === 'text' ? 'pulse' : 'text');
|
|
1465
1569
|
refreshDisplay();
|
|
1466
|
-
}, { bg: '#
|
|
1570
|
+
}, { bg: '#1565c0' }); // blue — distinct from green focus
|
|
1467
1571
|
audioStreamModeBtn.top = 7;
|
|
1468
1572
|
audioStreamModeBtn.left = 64;
|
|
1469
1573
|
audioStreamModeBtn.hide();
|
|
@@ -1487,7 +1591,7 @@ export function createSettingsTab(screen, services) {
|
|
|
1487
1591
|
parent: box,
|
|
1488
1592
|
top: 11,
|
|
1489
1593
|
left: 2,
|
|
1490
|
-
content: '{
|
|
1594
|
+
content: '{bright-cyan-fg} 💾 Config Storage {/bright-cyan-fg}',
|
|
1491
1595
|
tags: true,
|
|
1492
1596
|
style: { bg: COLORS.contentBg },
|
|
1493
1597
|
});
|
|
@@ -1553,7 +1657,7 @@ export function createSettingsTab(screen, services) {
|
|
|
1553
1657
|
refreshConfigDisplay();
|
|
1554
1658
|
_showNotice(screen, 'Settings Saved');
|
|
1555
1659
|
}, () => { _currentIdx = _buttons.indexOf(saveLocallyBtn); _focusButton(saveLocallyBtn); });
|
|
1556
|
-
}, { bg: '#
|
|
1660
|
+
}, { bg: '#1565c0' }); // blue — distinct from green focus
|
|
1557
1661
|
saveLocallyBtn.bottom = 0;
|
|
1558
1662
|
saveLocallyBtn.left = 46;
|
|
1559
1663
|
|
|
@@ -1581,6 +1685,7 @@ export function createSettingsTab(screen, services) {
|
|
|
1581
1685
|
// Widget groups for each sub-tab (used by _showSubTab to show/hide)
|
|
1582
1686
|
const _subTabWidgets = {
|
|
1583
1687
|
voice: [
|
|
1688
|
+
providerVoiceHeader,
|
|
1584
1689
|
providerLabel, providerValue, switchBtn,
|
|
1585
1690
|
voiceLabel, voiceValue, changeBtn, playBtn, voiceFileText,
|
|
1586
1691
|
],
|
|
@@ -1592,6 +1697,7 @@ export function createSettingsTab(screen, services) {
|
|
|
1592
1697
|
volumeLabel, volumeValue, volumeChangeBtn,
|
|
1593
1698
|
],
|
|
1594
1699
|
personality: [
|
|
1700
|
+
styleHeader,
|
|
1595
1701
|
verbosityLabel, verbosityValue, verbosityChangeBtn, verbosityPathText,
|
|
1596
1702
|
personalityLabel, personalityValue, personalityChangeBtn, personalityTestBtn, personalityFileText,
|
|
1597
1703
|
introTextHeader,
|
|
@@ -2052,7 +2158,7 @@ export function createSettingsTab(screen, services) {
|
|
|
2052
2158
|
audioSshValue.setContent(audioAlias || '(none)');
|
|
2053
2159
|
const streamMode = cfg.audio_stream_mode ?? 'text';
|
|
2054
2160
|
audioStreamModeBtn.setContent(streamMode === 'pulse' ? 'Streaming Pulse Audio' : 'Streaming Text Only ✓');
|
|
2055
|
-
audioStreamModeBtn.style.bg = streamMode === 'text' ? '#
|
|
2161
|
+
audioStreamModeBtn.style.bg = streamMode === 'text' ? '#1565c0' : COLORS.btnChange;
|
|
2056
2162
|
} else {
|
|
2057
2163
|
audioSshLabel.hide();
|
|
2058
2164
|
audioSshValue.hide();
|
|
@@ -2223,14 +2329,14 @@ function _createButton(parent, screen, label, COLORS, onClick, opts = {}) {
|
|
|
2223
2329
|
const _ALL_PROVIDERS = [
|
|
2224
2330
|
{ id: 'piper', name: 'Piper TTS', platforms: ['linux', 'darwin', 'win32'], desc: 'High-quality local neural TTS' },
|
|
2225
2331
|
{ id: 'soprano', name: 'Soprano', platforms: ['linux', 'darwin'], desc: 'Ultra-fast neural TTS (single voice)' },
|
|
2226
|
-
{ id: '
|
|
2332
|
+
{ id: 'sapi', name: 'Windows SAPI', platforms: ['win32'], desc: 'Windows built-in text-to-speech' },
|
|
2227
2333
|
{ id: 'macos', name: 'Mac Say', platforms: ['darwin'], desc: 'macOS built-in text-to-speech' },
|
|
2228
2334
|
];
|
|
2229
2335
|
|
|
2230
2336
|
const _INSTALL_CMDS = {
|
|
2231
2337
|
piper: ['pip install piper-tts', 'OR: pipx install piper-tts', '', 'Voices are downloaded separately:', 'Run: agentvibes install (then choose Piper)'],
|
|
2232
2338
|
soprano: ['pip install soprano-tts', 'OR: pipx install soprano-tts', '', 'Keep model loaded for fast synthesis:', 'soprano-webui'],
|
|
2233
|
-
|
|
2339
|
+
sapi: ['Built-in on Windows — no install required.', 'Only works in a native Windows shell,', 'not inside WSL. Use piper or soprano in WSL.'],
|
|
2234
2340
|
macos: ['Built-in on macOS — no install required.', 'The say command ships with every Mac.'],
|
|
2235
2341
|
};
|
|
2236
2342
|
|
|
@@ -2275,7 +2381,7 @@ function _openProviderPicker(screen, providerService, onSelect, onClose) {
|
|
|
2275
2381
|
// Environment header
|
|
2276
2382
|
blessed.text({
|
|
2277
2383
|
parent: modal, top: 0, left: 1, tags: true,
|
|
2278
|
-
content: `{
|
|
2384
|
+
content: `{bright-cyan-fg}🖥 Environment:{/bright-cyan-fg} {bold}${envLabel}{/bold}`,
|
|
2279
2385
|
style: { bg: COLORS.contentBg },
|
|
2280
2386
|
});
|
|
2281
2387
|
blessed.text({
|
|
@@ -2325,8 +2431,8 @@ function _openProviderPicker(screen, providerService, onSelect, onClose) {
|
|
|
2325
2431
|
_close(); onSelect(prov.id);
|
|
2326
2432
|
} else {
|
|
2327
2433
|
const lines = _INSTALL_CMDS[prov.id] ?? ['No instructions available.'];
|
|
2328
|
-
instrTitle.setContent(`{
|
|
2329
|
-
instrContent.setContent(lines.map(l => l ? `{
|
|
2434
|
+
instrTitle.setContent(`{bright-cyan-fg}Install — ${prov.name}:{/bright-cyan-fg}`);
|
|
2435
|
+
instrContent.setContent(lines.map(l => l ? `{bright-cyan-fg}${l}{/bright-cyan-fg}` : '').join('\n'));
|
|
2330
2436
|
screen.render();
|
|
2331
2437
|
}
|
|
2332
2438
|
});
|
|
@@ -2340,7 +2446,7 @@ function _openProviderPicker(screen, providerService, onSelect, onClose) {
|
|
|
2340
2446
|
|
|
2341
2447
|
const instrTitle = blessed.text({
|
|
2342
2448
|
parent: modal, top: 11, left: 1, width: 66, tags: true,
|
|
2343
|
-
content: '{
|
|
2449
|
+
content: '{bright-cyan-fg}Install instructions — click Install beside a provider:{/bright-cyan-fg}',
|
|
2344
2450
|
style: { bg: COLORS.contentBg },
|
|
2345
2451
|
});
|
|
2346
2452
|
const instrContent = blessed.text({
|
|
@@ -2452,7 +2558,7 @@ function _showSavePreview(screen, filePath, data, onConfirm, onClose) {
|
|
|
2452
2558
|
style: {
|
|
2453
2559
|
fg: '#e3f2fd',
|
|
2454
2560
|
bg: COLORS.contentBg,
|
|
2455
|
-
border: { fg: '
|
|
2561
|
+
border: { fg: 'bright-cyan' },
|
|
2456
2562
|
},
|
|
2457
2563
|
});
|
|
2458
2564
|
|
|
@@ -2471,7 +2577,7 @@ function _showSavePreview(screen, filePath, data, onConfirm, onClose) {
|
|
|
2471
2577
|
const okBtn = _createButton(modal, screen, 'OK — Save', COLORS, () => {
|
|
2472
2578
|
_close();
|
|
2473
2579
|
onConfirm();
|
|
2474
|
-
}, { bg: '#
|
|
2580
|
+
}, { bg: '#1565c0' });
|
|
2475
2581
|
okBtn.top = btnRow;
|
|
2476
2582
|
okBtn.left = midX + 2;
|
|
2477
2583
|
|
|
@@ -2540,7 +2646,7 @@ function _openTrackPicker(screen, configService, onSelect, onClose) {
|
|
|
2540
2646
|
const currentTrack = (configService.getConfig().backgroundMusic?.track ?? MUSIC_DEFAULTS.track);
|
|
2541
2647
|
const items = allItems.map(t =>
|
|
2542
2648
|
t.file === ADD_SENTINEL
|
|
2543
|
-
? ` {
|
|
2649
|
+
? ` {bright-cyan-fg}+ Add Custom Track{/bright-cyan-fg}`
|
|
2544
2650
|
: (t.file === currentTrack ? `● ${t.label}` : ` ${t.label}`)
|
|
2545
2651
|
);
|
|
2546
2652
|
const currentIdx = tracks.findIndex(t => t.file === currentTrack);
|
|
@@ -2779,9 +2885,14 @@ function _openVolumePicker(screen, configService, onSelect, onClose) {
|
|
|
2779
2885
|
`mpg123 -q "${trackPath}"`,
|
|
2780
2886
|
].join(' 2>/dev/null || ') + ' 2>/dev/null';
|
|
2781
2887
|
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2888
|
+
if (_IS_WINDOWS) {
|
|
2889
|
+
const _mp3P3 = detectMp3Player(_previewEnv);
|
|
2890
|
+
_previewProcess = _mp3P3
|
|
2891
|
+
? spawn(_mp3P3.bin, _mp3P3.args(trackPath), _spawnOpts(_previewEnv))
|
|
2892
|
+
: null;
|
|
2893
|
+
} else {
|
|
2894
|
+
_previewProcess = spawn('sh', ['-c', cmd], _spawnOpts(_previewEnv));
|
|
2895
|
+
}
|
|
2785
2896
|
_previewProcess.unref();
|
|
2786
2897
|
_refreshList();
|
|
2787
2898
|
|
|
@@ -2917,7 +3028,7 @@ function _openMusicBrowserModal(screen, configService, navigationService, onDone
|
|
|
2917
3028
|
fg: COLORS.labelFg,
|
|
2918
3029
|
bg: COLORS.contentBg,
|
|
2919
3030
|
border: { fg: COLORS.borderFg },
|
|
2920
|
-
selected: { bg: '#
|
|
3031
|
+
selected: { bg: '#2e7d32', fg: '#ffffff', bold: true },
|
|
2921
3032
|
item: { fg: COLORS.labelFg },
|
|
2922
3033
|
},
|
|
2923
3034
|
});
|
|
@@ -2930,7 +3041,7 @@ function _openMusicBrowserModal(screen, configService, navigationService, onDone
|
|
|
2930
3041
|
right: 2,
|
|
2931
3042
|
tags: true,
|
|
2932
3043
|
content: '',
|
|
2933
|
-
style: { fg: '
|
|
3044
|
+
style: { fg: 'bright-cyan', bg: COLORS.contentBg },
|
|
2934
3045
|
});
|
|
2935
3046
|
|
|
2936
3047
|
// ---- File location hint ----
|
|
@@ -3024,15 +3135,16 @@ function _openMusicBrowserModal(screen, configService, navigationService, onDone
|
|
|
3024
3135
|
|
|
3025
3136
|
const _mp3Player = detectMp3Player(_modalEnv);
|
|
3026
3137
|
if (!_mp3Player) return;
|
|
3138
|
+
const _isWin = process.platform === 'win32' && !process.env.WSL_DISTRO_NAME;
|
|
3027
3139
|
_previewProcess = spawn(_mp3Player.bin, _mp3Player.args(trackPath), {
|
|
3028
|
-
stdio: 'ignore', detached: true, env: _modalEnv,
|
|
3140
|
+
stdio: 'ignore', detached: !_isWin, windowsHide: true, env: _modalEnv,
|
|
3029
3141
|
});
|
|
3030
3142
|
_previewProcess.unref();
|
|
3031
3143
|
_previewTrackId = trackId;
|
|
3032
3144
|
|
|
3033
3145
|
const label = _allTracks.find(t => t.id === trackId)?.label ?? formatTrackLabel(trackId);
|
|
3034
3146
|
if (!_closed) {
|
|
3035
|
-
modalPreviewLine.setContent(`{
|
|
3147
|
+
modalPreviewLine.setContent(`{bright-cyan-fg}\u266A Previewing: ${label} (Space to stop){/bright-cyan-fg}`);
|
|
3036
3148
|
screen.render();
|
|
3037
3149
|
}
|
|
3038
3150
|
|
|
@@ -3327,7 +3439,7 @@ function _openVoiceBrowserModal(screen, providerService, configService, navigati
|
|
|
3327
3439
|
keys: true,
|
|
3328
3440
|
style: {
|
|
3329
3441
|
fg: COLORS.valueFg,
|
|
3330
|
-
bg: '#
|
|
3442
|
+
bg: '#1a3a5c',
|
|
3331
3443
|
focus: { bg: '#283593' },
|
|
3332
3444
|
},
|
|
3333
3445
|
});
|
|
@@ -3337,7 +3449,7 @@ function _openVoiceBrowserModal(screen, providerService, configService, navigati
|
|
|
3337
3449
|
parent: modal,
|
|
3338
3450
|
top: 2,
|
|
3339
3451
|
left: 6,
|
|
3340
|
-
content: `{
|
|
3452
|
+
content: `{bright-cyan-fg}${'Name'.padEnd(COL_NAME_W)}${'Gender'.padEnd(COL_GENDER_W)}Provider{/bright-cyan-fg}`,
|
|
3341
3453
|
tags: true,
|
|
3342
3454
|
style: { bg: COLORS.contentBg },
|
|
3343
3455
|
});
|
|
@@ -3358,7 +3470,7 @@ function _openVoiceBrowserModal(screen, providerService, configService, navigati
|
|
|
3358
3470
|
fg: COLORS.labelFg,
|
|
3359
3471
|
bg: COLORS.contentBg,
|
|
3360
3472
|
border: { fg: COLORS.borderFg },
|
|
3361
|
-
selected: { bg: '#
|
|
3473
|
+
selected: { bg: '#2e7d32', fg: '#ffffff', bold: true },
|
|
3362
3474
|
item: { fg: COLORS.labelFg },
|
|
3363
3475
|
},
|
|
3364
3476
|
});
|
|
@@ -3368,7 +3480,7 @@ function _openVoiceBrowserModal(screen, providerService, configService, navigati
|
|
|
3368
3480
|
parent: modal,
|
|
3369
3481
|
bottom: 5,
|
|
3370
3482
|
left: 2,
|
|
3371
|
-
content: `{
|
|
3483
|
+
content: `{bright-cyan-fg}── Voice Info ${'─'.repeat(50)}{/bright-cyan-fg}`,
|
|
3372
3484
|
tags: true,
|
|
3373
3485
|
style: { bg: COLORS.contentBg },
|
|
3374
3486
|
});
|
|
@@ -3390,7 +3502,7 @@ function _openVoiceBrowserModal(screen, providerService, configService, navigati
|
|
|
3390
3502
|
right: 2,
|
|
3391
3503
|
tags: true,
|
|
3392
3504
|
content: '',
|
|
3393
|
-
style: { fg: '
|
|
3505
|
+
style: { fg: 'bright-cyan', bg: COLORS.contentBg },
|
|
3394
3506
|
});
|
|
3395
3507
|
|
|
3396
3508
|
// ---- Key hint bar ----
|
|
@@ -3495,11 +3607,22 @@ function _openVoiceBrowserModal(screen, providerService, configService, navigati
|
|
|
3495
3607
|
const tempWav = path.join(os.tmpdir(), `agentvibes-preview-${Date.now()}.wav`);
|
|
3496
3608
|
const phrase = SAMPLE_PHRASES[Math.floor(Math.random() * SAMPLE_PHRASES.length)];
|
|
3497
3609
|
|
|
3610
|
+
const _isWinPreview = process.platform === 'win32' && !process.env.WSL_DISTRO_NAME;
|
|
3611
|
+
let _piperBin3 = 'piper';
|
|
3612
|
+
if (_isWinPreview) {
|
|
3613
|
+
const _lad = process.env.LOCALAPPDATA ||
|
|
3614
|
+
(process.env.USERPROFILE ? path.join(process.env.USERPROFILE, 'AppData', 'Local') : null);
|
|
3615
|
+
if (_lad) {
|
|
3616
|
+
const _exe = path.join(_lad, 'Programs', 'Piper', 'piper.exe');
|
|
3617
|
+
if (fs.existsSync(_exe)) _piperBin3 = _exe;
|
|
3618
|
+
}
|
|
3619
|
+
}
|
|
3498
3620
|
const _piperArgs3 = ['--model', voicePath, '--output_file', tempWav];
|
|
3499
3621
|
if (_ms3.speakerId != null) _piperArgs3.push('--speaker', String(_ms3.speakerId));
|
|
3500
|
-
const piper = spawn(
|
|
3622
|
+
const piper = spawn(_piperBin3, _piperArgs3, {
|
|
3501
3623
|
stdio: ['pipe', 'ignore', 'ignore'],
|
|
3502
|
-
detached:
|
|
3624
|
+
detached: !_isWinPreview,
|
|
3625
|
+
windowsHide: true,
|
|
3503
3626
|
env: _spawnEnv,
|
|
3504
3627
|
});
|
|
3505
3628
|
piper.stdin.write(phrase + '\n');
|
|
@@ -3508,7 +3631,7 @@ function _openVoiceBrowserModal(screen, providerService, configService, navigati
|
|
|
3508
3631
|
_playingProcess = piper;
|
|
3509
3632
|
_playingVoiceId = voiceId;
|
|
3510
3633
|
if (!_closed) {
|
|
3511
|
-
modalPreviewLine.setContent(`{
|
|
3634
|
+
modalPreviewLine.setContent(`{bright-cyan-fg}♪ Synthesizing: ${voiceId}…{/bright-cyan-fg}`);
|
|
3512
3635
|
screen.render();
|
|
3513
3636
|
}
|
|
3514
3637
|
|
|
@@ -3521,7 +3644,7 @@ function _openVoiceBrowserModal(screen, providerService, configService, navigati
|
|
|
3521
3644
|
_playingVoiceId = null;
|
|
3522
3645
|
_playingProcess = null;
|
|
3523
3646
|
if (!_closed) {
|
|
3524
|
-
modalPreviewLine.setContent('{
|
|
3647
|
+
modalPreviewLine.setContent('{bright-cyan-fg}♪ Preview failed (piper error — is piper installed?){/bright-cyan-fg}');
|
|
3525
3648
|
screen.render();
|
|
3526
3649
|
setTimeout(() => { if (!_closed) { modalPreviewLine.setContent(''); screen.render(); } }, 4000);
|
|
3527
3650
|
}
|
|
@@ -3532,13 +3655,14 @@ function _openVoiceBrowserModal(screen, providerService, configService, navigati
|
|
|
3532
3655
|
if (!_wavPlayer3) return;
|
|
3533
3656
|
const playProc = spawn(_wavPlayer3.bin, _wavPlayer3.args(tempWav), {
|
|
3534
3657
|
stdio: 'ignore',
|
|
3535
|
-
detached:
|
|
3658
|
+
detached: !_isWinPreview,
|
|
3659
|
+
windowsHide: true,
|
|
3536
3660
|
env: _spawnEnv,
|
|
3537
3661
|
});
|
|
3538
3662
|
_playingProcess = playProc;
|
|
3539
3663
|
|
|
3540
3664
|
if (!_closed) {
|
|
3541
|
-
modalPreviewLine.setContent(`{
|
|
3665
|
+
modalPreviewLine.setContent(`{bright-cyan-fg}♪ Playing: ${voiceId} (Space to stop){/bright-cyan-fg}`);
|
|
3542
3666
|
screen.render();
|
|
3543
3667
|
}
|
|
3544
3668
|
|
|
@@ -3563,7 +3687,7 @@ function _openVoiceBrowserModal(screen, providerService, configService, navigati
|
|
|
3563
3687
|
_playingVoiceId = null;
|
|
3564
3688
|
_playingProcess = null;
|
|
3565
3689
|
if (!_closed) {
|
|
3566
|
-
modalPreviewLine.setContent('{
|
|
3690
|
+
modalPreviewLine.setContent('{bright-cyan-fg}♪ Cannot find piper — install with: pipx install piper-tts{/bright-cyan-fg}');
|
|
3567
3691
|
screen.render();
|
|
3568
3692
|
setTimeout(() => { if (!_closed) { modalPreviewLine.setContent(''); screen.render(); } }, 4000);
|
|
3569
3693
|
}
|