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
|
@@ -571,7 +571,7 @@ async function downloadPiperVoices(agentVibesDir) {
|
|
|
571
571
|
short: 'LibriTTS 900'
|
|
572
572
|
},
|
|
573
573
|
{
|
|
574
|
-
name: chalk.cyan('Full pack') + chalk.gray(' —
|
|
574
|
+
name: chalk.cyan('Full pack') + chalk.gray(' — 17 voices including LibriTTS (~360 MB, all voices-tab curated + BMAD agent voices)'),
|
|
575
575
|
value: 'full',
|
|
576
576
|
short: 'Full pack'
|
|
577
577
|
},
|
|
@@ -618,8 +618,8 @@ async function downloadPiperVoices(agentVibesDir) {
|
|
|
618
618
|
console.log(chalk.yellow('\n⚠️ Download failed — try later with: /agent-vibes:add\n'));
|
|
619
619
|
}
|
|
620
620
|
} else if (voiceChoice === 'full') {
|
|
621
|
-
console.log(chalk.cyan('\n📥 Downloading full voice pack (~
|
|
622
|
-
console.log(chalk.gray(' This includes all BMAD agent voices
|
|
621
|
+
console.log(chalk.cyan('\n📥 Downloading full voice pack (~360 MB)...\n'));
|
|
622
|
+
console.log(chalk.gray(' This includes all voices-tab curated voices, BMAD agent voices, and LibriTTS 900 speakers.\n'));
|
|
623
623
|
try {
|
|
624
624
|
if (fs.existsSync(fullPackScript)) {
|
|
625
625
|
execFileSync('bash', [fullPackScript, '--yes'], { // NOSONAR
|
package/src/console/app.js
CHANGED
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
|
|
10
10
|
import blessed from 'blessed';
|
|
11
11
|
import path from 'node:path';
|
|
12
|
-
import { readFileSync } from 'node:fs';
|
|
12
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
13
|
+
import os from 'node:os';
|
|
13
14
|
import { fileURLToPath } from 'node:url';
|
|
14
15
|
import { spawnSync, execFileSync } from 'node:child_process';
|
|
15
16
|
import { NavigationService, TAB_ORDER } from '../services/navigation-service.js';
|
|
@@ -630,21 +631,36 @@ export class AgentVibesConsole {
|
|
|
630
631
|
try { execFileSync('which', [bin], { stdio: 'ignore', timeout: 2000 }); return true; } // NOSONAR
|
|
631
632
|
catch { return false; }
|
|
632
633
|
};
|
|
634
|
+
const _pyHas = (mod) => {
|
|
635
|
+
try { execFileSync('python3', ['-c', `import ${mod}`], { stdio: 'ignore', timeout: 3000 }); return true; } // NOSONAR
|
|
636
|
+
catch { return false; }
|
|
637
|
+
};
|
|
638
|
+
const _elKeySet = () => {
|
|
639
|
+
if (process.env.ELEVENLABS_API_KEY) return true;
|
|
640
|
+
try {
|
|
641
|
+
const kf = path.join(os.homedir(), '.agentvibes', 'elevenlabs-key.txt');
|
|
642
|
+
return existsSync(kf) && readFileSync(kf, 'utf8').trim().length > 0;
|
|
643
|
+
} catch { return false; }
|
|
644
|
+
};
|
|
633
645
|
const detected = {
|
|
634
|
-
piper:
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
646
|
+
piper: _has('piper'),
|
|
647
|
+
kokoro: _pyHas('kokoro'),
|
|
648
|
+
elevenlabs: _elKeySet(),
|
|
649
|
+
soprano: _has('soprano'),
|
|
650
|
+
sapi: process.platform === 'win32',
|
|
651
|
+
macos: process.platform === 'darwin' && _has('say'),
|
|
638
652
|
};
|
|
639
653
|
|
|
640
654
|
// Build provider status badges: ● Name (green if detected, grey if not)
|
|
641
655
|
const on = (label) => `{green-fg}●{/green-fg} ${label}`;
|
|
642
656
|
const off = (label) => `{#546e7a-fg}● ${label}{/#546e7a-fg}`;
|
|
643
657
|
const badges = [
|
|
644
|
-
detected.piper
|
|
645
|
-
detected.
|
|
646
|
-
detected.
|
|
647
|
-
detected.
|
|
658
|
+
detected.piper ? on('Piper') : off('Piper'),
|
|
659
|
+
detected.kokoro ? on('Kokoro') : off('Kokoro'),
|
|
660
|
+
detected.elevenlabs ? on('ElevenLabs') : off('ElevenLabs'),
|
|
661
|
+
detected.soprano ? on('Soprano') : off('Soprano'),
|
|
662
|
+
detected.sapi ? on('Windows SAPI') : off('Windows SAPI'),
|
|
663
|
+
detected.macos ? on('Mac Say') : off('Mac Say'),
|
|
648
664
|
].join(' ');
|
|
649
665
|
|
|
650
666
|
const footer = blessed.box({
|
package/src/console/audio-env.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import fs from 'node:fs';
|
|
9
9
|
import path from 'node:path';
|
|
10
10
|
import os from 'node:os';
|
|
11
|
-
import { spawnSync } from 'node:child_process';
|
|
11
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Build a spawn environment with correct PULSE_SERVER handling.
|
|
@@ -151,6 +151,90 @@ export function detectMp3Player(env) {
|
|
|
151
151
|
return _detect(MP3_PLAYERS, env);
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
+
/**
|
|
155
|
+
* Returns true when running headless with PulseAudio over TCP.
|
|
156
|
+
* SDL2-based players (ffplay) fail in this configuration with exit 123.
|
|
157
|
+
*/
|
|
158
|
+
function _isHeadlessPulse(env) {
|
|
159
|
+
return !env.DISPLAY && typeof env.PULSE_SERVER === 'string' && env.PULSE_SERVER.startsWith('tcp:');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Spawn an MP3 player for the given track path.
|
|
164
|
+
*
|
|
165
|
+
* On headless servers with PulseAudio TCP, SDL2-based players (ffplay) fail
|
|
166
|
+
* because DISPLAY is not set. In that case uses ffmpeg→pacat pipe.
|
|
167
|
+
*
|
|
168
|
+
* Returns a process-like object with:
|
|
169
|
+
* .pid — primary process PID
|
|
170
|
+
* .kill() — stops playback, killing all spawned processes
|
|
171
|
+
* .on(event, cb) — supports 'exit' and 'error'
|
|
172
|
+
*
|
|
173
|
+
* Returns null if no suitable player or required binaries are unavailable.
|
|
174
|
+
*
|
|
175
|
+
* @param {string} trackPath - Absolute path to the MP3 file
|
|
176
|
+
* @param {Object} [env] - Environment (defaults to buildAudioEnv())
|
|
177
|
+
* @returns {{ pid: number, kill: Function, on: Function }|null}
|
|
178
|
+
*/
|
|
179
|
+
export function spawnMp3Player(trackPath, env) {
|
|
180
|
+
env = env ?? buildAudioEnv();
|
|
181
|
+
|
|
182
|
+
if (_isHeadlessPulse(env)) {
|
|
183
|
+
// Check required binaries are present.
|
|
184
|
+
// S4036: standard system media tools resolved via the user's local PATH on
|
|
185
|
+
// their own machine — no untrusted PATH, absolute paths aren't portable
|
|
186
|
+
// across distros/Homebrew/Nix. Risk accepted.
|
|
187
|
+
if (spawnSync('which', ['ffmpeg'], { stdio: 'ignore', env }).status !== 0) return null; // NOSONAR
|
|
188
|
+
if (spawnSync('which', ['pacat'], { stdio: 'ignore', env }).status !== 0) return null; // NOSONAR
|
|
189
|
+
|
|
190
|
+
const ff = spawn('ffmpeg', // NOSONAR — see S4036 note above
|
|
191
|
+
['-i', trackPath, '-f', 's16le', '-ac', '2', '-ar', '44100', 'pipe:1', '-loglevel', 'quiet'],
|
|
192
|
+
{ stdio: ['ignore', 'pipe', 'ignore'], env },
|
|
193
|
+
);
|
|
194
|
+
const pa = spawn('pacat', // NOSONAR — see S4036 note above
|
|
195
|
+
['--playback', '--format=s16le', '--channels=2', '--rate=44100'],
|
|
196
|
+
{ stdio: ['pipe', 'ignore', 'ignore'], env },
|
|
197
|
+
);
|
|
198
|
+
ff.stdout.on('error', () => {});
|
|
199
|
+
pa.stdin.on('error', () => {});
|
|
200
|
+
ff.stdout.pipe(pa.stdin);
|
|
201
|
+
|
|
202
|
+
const _listeners = { exit: [], error: [] };
|
|
203
|
+
pa.on('exit', (code, sig) => _listeners.exit.forEach(cb => cb(code, sig)));
|
|
204
|
+
ff.on('error', err => _listeners.error.forEach(cb => cb(err)));
|
|
205
|
+
pa.on('error', err => _listeners.error.forEach(cb => cb(err)));
|
|
206
|
+
|
|
207
|
+
return {
|
|
208
|
+
pid: ff.pid,
|
|
209
|
+
kill() { try { ff.kill(); } catch {} try { pa.kill(); } catch {} },
|
|
210
|
+
on(event, cb) { if (_listeners[event]) _listeners[event].push(cb); },
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const player = detectMp3Player(env);
|
|
215
|
+
if (!player) return null;
|
|
216
|
+
|
|
217
|
+
const isWin = process.platform === 'win32' && !process.env.WSL_DISTRO_NAME;
|
|
218
|
+
const proc = spawn(player.bin, player.args(trackPath), {
|
|
219
|
+
stdio: 'ignore', detached: !isWin, windowsHide: true, env,
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
return {
|
|
223
|
+
pid: proc.pid,
|
|
224
|
+
kill() {
|
|
225
|
+
try {
|
|
226
|
+
if (isWin) {
|
|
227
|
+
spawn('taskkill', ['/F', '/T', '/PID', String(proc.pid)], // NOSONAR
|
|
228
|
+
{ stdio: 'ignore', windowsHide: true });
|
|
229
|
+
} else {
|
|
230
|
+
process.kill(-proc.pid, 'SIGTERM');
|
|
231
|
+
}
|
|
232
|
+
} catch (e) { if (e.code !== 'ESRCH') { /* ignore */ } }
|
|
233
|
+
},
|
|
234
|
+
on(event, cb) { proc.on(event, cb); },
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
154
238
|
/**
|
|
155
239
|
* Detect the best available WAV player.
|
|
156
240
|
* On Windows, uses PowerShell SoundPlayer as built-in fallback.
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AgentVibes TUI — Brand Color Constants
|
|
3
|
-
*
|
|
4
|
-
* Single source of truth for the two primary brand colors.
|
|
5
|
-
* Change BRAND_PINK or BRAND_BLUE here to update every modal title,
|
|
6
|
-
* button, and the "Vibes" logotype across the entire TUI.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
/** Magenta-pink used for modal titles and the "Vibes" logotype. */
|
|
10
|
-
export const BRAND_PINK = '#f06292'; // Light magenta — Pink 300
|
|
11
|
-
|
|
12
|
-
/** Indigo blue used for default button backgrounds and primary accents. */
|
|
13
|
-
export const BRAND_BLUE = '#3949ab';
|
|
1
|
+
/**
|
|
2
|
+
* AgentVibes TUI — Brand Color Constants
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth for the two primary brand colors.
|
|
5
|
+
* Change BRAND_PINK or BRAND_BLUE here to update every modal title,
|
|
6
|
+
* button, and the "Vibes" logotype across the entire TUI.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** Magenta-pink used for modal titles and the "Vibes" logotype. */
|
|
10
|
+
export const BRAND_PINK = '#f06292'; // Light magenta — Pink 300
|
|
11
|
+
|
|
12
|
+
/** Indigo blue used for default button backgrounds and primary accents. */
|
|
13
|
+
export const BRAND_BLUE = '#3949ab';
|
|
@@ -1,44 +1,44 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AgentVibes — Canonical personality constants.
|
|
3
|
-
*
|
|
4
|
-
* Single source of truth for personality names and their associated emoji
|
|
5
|
-
* glyphs. All TUI modules (settings-tab, agents-tab, personality-picker …)
|
|
6
|
-
* import from here; src/installer.js maintains its own copy because it
|
|
7
|
-
* predates the TUI and uses a different module-load path.
|
|
8
|
-
*
|
|
9
|
-
* Exported:
|
|
10
|
-
* PERSONALITY_EMOJIS — Map of personality name → emoji string
|
|
11
|
-
* PERSONALITIES — Ordered array of personality names (canonical order
|
|
12
|
-
* used for picker lists)
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
export const PERSONALITY_EMOJIS = Object.freeze({
|
|
16
|
-
angry: '😠',
|
|
17
|
-
annoying: '😤',
|
|
18
|
-
crass: '🤬',
|
|
19
|
-
dramatic: '🎭',
|
|
20
|
-
'dry-humor': '😐',
|
|
21
|
-
flirty: '😘',
|
|
22
|
-
funny: '😂',
|
|
23
|
-
grandpa: '👴',
|
|
24
|
-
millennial: '🙄',
|
|
25
|
-
moody: '😒',
|
|
26
|
-
none: '😊',
|
|
27
|
-
normal: '😊',
|
|
28
|
-
pirate: '⚓',
|
|
29
|
-
poetic: '📜',
|
|
30
|
-
professional: '👔',
|
|
31
|
-
rapper: '🎤',
|
|
32
|
-
robot: '🤖',
|
|
33
|
-
sarcastic: '😏',
|
|
34
|
-
sassy: '💁',
|
|
35
|
-
'surfer-dude':'🏄',
|
|
36
|
-
zen: '🧘',
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
export const PERSONALITIES = Object.freeze([
|
|
40
|
-
'none', 'angry', 'annoying', 'crass', 'dramatic', 'dry-humor',
|
|
41
|
-
'flirty', 'funny', 'grandpa', 'millennial', 'moody', 'normal',
|
|
42
|
-
'pirate', 'poetic', 'professional', 'rapper', 'robot', 'sarcastic',
|
|
43
|
-
'sassy', 'surfer-dude', 'zen',
|
|
44
|
-
]);
|
|
1
|
+
/**
|
|
2
|
+
* AgentVibes — Canonical personality constants.
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth for personality names and their associated emoji
|
|
5
|
+
* glyphs. All TUI modules (settings-tab, agents-tab, personality-picker …)
|
|
6
|
+
* import from here; src/installer.js maintains its own copy because it
|
|
7
|
+
* predates the TUI and uses a different module-load path.
|
|
8
|
+
*
|
|
9
|
+
* Exported:
|
|
10
|
+
* PERSONALITY_EMOJIS — Map of personality name → emoji string
|
|
11
|
+
* PERSONALITIES — Ordered array of personality names (canonical order
|
|
12
|
+
* used for picker lists)
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export const PERSONALITY_EMOJIS = Object.freeze({
|
|
16
|
+
angry: '😠',
|
|
17
|
+
annoying: '😤',
|
|
18
|
+
crass: '🤬',
|
|
19
|
+
dramatic: '🎭',
|
|
20
|
+
'dry-humor': '😐',
|
|
21
|
+
flirty: '😘',
|
|
22
|
+
funny: '😂',
|
|
23
|
+
grandpa: '👴',
|
|
24
|
+
millennial: '🙄',
|
|
25
|
+
moody: '😒',
|
|
26
|
+
none: '😊',
|
|
27
|
+
normal: '😊',
|
|
28
|
+
pirate: '⚓',
|
|
29
|
+
poetic: '📜',
|
|
30
|
+
professional: '👔',
|
|
31
|
+
rapper: '🎤',
|
|
32
|
+
robot: '🤖',
|
|
33
|
+
sarcastic: '😏',
|
|
34
|
+
sassy: '💁',
|
|
35
|
+
'surfer-dude':'🏄',
|
|
36
|
+
zen: '🧘',
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
export const PERSONALITIES = Object.freeze([
|
|
40
|
+
'none', 'angry', 'annoying', 'crass', 'dramatic', 'dry-humor',
|
|
41
|
+
'flirty', 'funny', 'grandpa', 'millennial', 'moody', 'normal',
|
|
42
|
+
'pirate', 'poetic', 'professional', 'rapper', 'robot', 'sarcastic',
|
|
43
|
+
'sassy', 'surfer-dude', 'zen',
|
|
44
|
+
]);
|
|
@@ -40,8 +40,12 @@ const KEY_TO_TAB = {
|
|
|
40
40
|
export function setupNavigation(screen, navigationService, focusMainTabBar) {
|
|
41
41
|
// Tab switching shortcuts — one handler per key (both cases)
|
|
42
42
|
// When a modal is open, force-close all modals then switch tabs.
|
|
43
|
+
// Exception: if already on the target tab, pressing its shortcut is a no-op —
|
|
44
|
+
// the user is trying to do something within the tab (e.g. 'I' in setup means
|
|
45
|
+
// "install", not "re-navigate to setup and close all my pickers").
|
|
43
46
|
for (const [key, tabId] of Object.entries(KEY_TO_TAB)) {
|
|
44
47
|
screen.key([key], () => {
|
|
48
|
+
if (navigationService.getActiveTab() === tabId) return;
|
|
45
49
|
if (navigationService.isModalOpen()) {
|
|
46
50
|
navigationService.forceCloseAll();
|
|
47
51
|
setTimeout(() => navigationService.switchTab(tabId), 0);
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
getFavorites, getThumbsDown, toggleThumbsUp, toggleThumbsDown,
|
|
21
21
|
} from './voices-tab.js';
|
|
22
22
|
import { buildAudioEnv, detectWavPlayer, detectRemoteLlm } from '../audio-env.js';
|
|
23
|
+
import { voicesForProvider } from '../../services/provider-voice-catalog.js';
|
|
23
24
|
import { destroyList } from '../widgets/destroy-list.js';
|
|
24
25
|
import { BRAND_PINK } from '../brand-colors.js';
|
|
25
26
|
import { t } from '../../i18n/strings.js';
|
|
@@ -642,7 +643,7 @@ ${_tl('bmadDesc')}
|
|
|
642
643
|
const FIELDS = [
|
|
643
644
|
{ key: 'voice', label: 'Voice', getValue: () => draft.voice || '(global default)' },
|
|
644
645
|
{ key: 'pretext', label: 'Pretext', getValue: () => draft.pretext || '(default)' },
|
|
645
|
-
{ key: 'reverbPreset', label: '
|
|
646
|
+
{ key: 'reverbPreset', label: 'Audio Effects', getValue: () => formatReverbState(draft.reverbPreset) },
|
|
646
647
|
{ key: 'personality', label: 'Personality', getValue: () => {
|
|
647
648
|
const p = draft.personality;
|
|
648
649
|
const emoji = PERSONALITY_EMOJIS[p] || '';
|
|
@@ -1494,13 +1495,18 @@ ${_tl('bmadDesc')}
|
|
|
1494
1495
|
}
|
|
1495
1496
|
|
|
1496
1497
|
function _autoAssignVoices() {
|
|
1497
|
-
|
|
1498
|
+
// Pull the pool from whichever provider is currently active, not always Piper.
|
|
1499
|
+
// Agent profiles store only a voice id (the active provider is global), so
|
|
1500
|
+
// switching the provider and re-running auto-assign rebinds every agent to a
|
|
1501
|
+
// voice from that provider (Kokoro, ElevenLabs, soprano, …).
|
|
1502
|
+
const provider = providerService?.getActiveProvider?.() ?? 'piper';
|
|
1503
|
+
const installed = voicesForProvider(provider, { scanInstalledVoices, getVoiceMeta });
|
|
1498
1504
|
if (installed.length === 0) return false;
|
|
1499
1505
|
|
|
1500
|
-
//
|
|
1501
|
-
const femaleVoices = _shuffleArray(installed.filter(v =>
|
|
1502
|
-
const maleVoices = _shuffleArray(installed.filter(v =>
|
|
1503
|
-
const otherVoices = _shuffleArray(installed.filter(v => !['Male', 'Female'].includes(
|
|
1506
|
+
// installed is [{ id, gender }] — separate the ids by gender.
|
|
1507
|
+
const femaleVoices = _shuffleArray(installed.filter(v => v.gender === 'Female').map(v => v.id));
|
|
1508
|
+
const maleVoices = _shuffleArray(installed.filter(v => v.gender === 'Male').map(v => v.id));
|
|
1509
|
+
const otherVoices = _shuffleArray(installed.filter(v => !['Male', 'Female'].includes(v.gender)).map(v => v.id));
|
|
1504
1510
|
|
|
1505
1511
|
// Separate agents by gender
|
|
1506
1512
|
const femaleAgents = _agents.filter(a => _inferAgentGender(a) === 'Female');
|