agentvibes 5.12.0-alpha.0 → 5.13.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/activation-instructions +54 -54
- package/.claude/commands/agent-vibes/commands.json +0 -20
- package/.claude/commands/agent-vibes/unmute.md +6 -2
- 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 +6 -6
- package/.claude/config/audio-effects.cfg.bak-kokoro +7 -0
- 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/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/agentvibes-session-id.sh +69 -0
- package/.claude/hooks/bmad-party-speak.sh +20 -4
- package/.claude/hooks/bmad-speak.sh +60 -2
- package/.claude/hooks/bmad-tts-injector.sh +20 -1
- package/.claude/hooks/bmad-voice-manager.sh +25 -3
- package/.claude/hooks/clawdbot-receiver-SECURE.sh +21 -2
- package/.claude/hooks/clawdbot-receiver.sh +19 -1
- package/.claude/hooks/elevenlabs-voices.sh +62 -0
- package/.claude/hooks/kokoro-installer.sh +20 -10
- package/.claude/hooks/language-manager.sh +10 -3
- package/.claude/hooks/party-set-room.sh +71 -0
- package/.claude/hooks/party-stage-roster.py +328 -0
- package/.claude/hooks/personality-manager.sh +19 -2
- package/.claude/hooks/piper-voice-manager.sh +3 -2
- package/.claude/hooks/play-tts-agentvibes-receiver-for-voiceless-connections.sh +24 -5
- package/.claude/hooks/play-tts-elevenlabs.sh +38 -118
- package/.claude/hooks/play-tts-kokoro.sh +33 -6
- package/.claude/hooks/play-tts-soprano.sh +3 -2
- package/.claude/hooks/play-tts-ssh-remote.sh +72 -27
- package/.claude/hooks/play-tts-termux-ssh.sh +5 -4
- package/.claude/hooks/play-tts.sh +86 -69
- package/.claude/hooks/provider-catalog.json +352 -0
- package/.claude/hooks/provider-catalog.sh +161 -0
- package/.claude/hooks/provider-commands.sh +2 -1
- package/.claude/hooks/provider-manager.sh +47 -9
- package/.claude/hooks/python-resolver.sh +117 -0
- package/.claude/hooks/session-id.sh +56 -0
- package/.claude/hooks/session-start-tts.sh +39 -0
- package/.claude/hooks/speed-manager.sh +1 -1
- package/.claude/hooks/translate-manager.sh +3 -2
- package/.claude/hooks/translator.py +1 -1
- package/.claude/hooks/voice-manager.sh +242 -10
- package/.claude/hooks-windows/audio-cache-utils.ps1.user.bak +119 -0
- package/.claude/hooks-windows/language-manager.ps1 +7 -1
- package/.claude/hooks-windows/personality-manager.ps1 +16 -1
- package/.claude/hooks-windows/play-tts-kokoro.ps1 +20 -4
- package/.claude/hooks-windows/play-tts.ps1 +32 -3
- package/.claude/hooks-windows/provider-catalog.ps1 +140 -0
- package/.claude/hooks-windows/provider-manager.ps1 +63 -8
- package/.claude/hooks-windows/soprano-gradio-synth.py.user.bak +153 -0
- package/.claude/hooks-windows/tts-watcher.ps1 +84 -8
- package/.claude/hooks-windows/voice-manager-windows.ps1 +49 -0
- package/.claude/piper-voices-dir.txt +1 -0
- package/.clawdbot/README.md +105 -105
- package/.mcp.json +15 -0
- package/LICENSE +190 -190
- package/README.md +20 -6
- package/RELEASE_NOTES.md +81 -0
- package/WINDOWS-SETUP.md +208 -208
- package/bin/agent-vibes +39 -39
- package/bin/mcp-server.js +121 -121
- package/bin/test-bmad-pr +78 -78
- package/mcp-server/QUICK_START.md +203 -203
- package/mcp-server/README.md +345 -345
- package/mcp-server/examples/claude_desktop_config.json +11 -11
- package/mcp-server/examples/claude_desktop_config_piper.json +9 -9
- package/mcp-server/examples/custom_instructions.md +169 -169
- package/mcp-server/pyproject.toml +52 -52
- package/mcp-server/requirements.txt +2 -2
- package/mcp-server/server.py +146 -49
- package/mcp-server/test_mcp_correctness.py +20 -2
- package/mcp-server/test_server.py +395 -395
- package/mcp-server/test_windows_script_parity.py +0 -2
- package/package.json +1 -1
- package/src/cli/list-personalities.js +110 -110
- package/src/cli/list-voices.js +115 -11
- package/src/commands/bmad-voices.js +394 -394
- package/src/console/app.js +1 -9
- package/src/console/bling.js +71 -0
- package/src/console/brand-colors.js +13 -13
- package/src/console/constants/personalities.js +44 -44
- package/src/console/footer-config.js +0 -4
- package/src/console/music-preview.js +79 -0
- package/src/console/navigation.js +0 -1
- package/src/console/tabs/agents-tab.js +14 -2
- package/src/console/tabs/help-tab.js +314 -314
- package/src/console/tabs/music-tab.js +81 -5
- package/src/console/tabs/placeholder-tab.js +0 -2
- package/src/console/tabs/readme-tab.js +272 -272
- package/src/console/tabs/settings-tab.js +207 -14
- package/src/console/tabs/setup-tab.js +39 -36
- package/src/console/tabs/voices-tab.js +102 -23
- package/src/console/widgets/destroy-list.js +25 -25
- package/src/console/widgets/notice.js +55 -55
- package/src/console/widgets/track-picker.js +82 -0
- package/src/installer/language-screen.js +31 -31
- package/src/installer/music-file-input.js +304 -304
- package/src/installer.js +124 -10
- package/src/services/language-service.js +47 -47
- package/src/services/navigation-service.js +1 -1
- package/src/services/provider-catalog.js +412 -0
- package/src/services/provider-voice-catalog.js +52 -73
- package/src/services/tts-engine-service.js +29 -0
- package/src/utils/audio-format-validator.js +277 -277
- package/src/utils/dependency-checker.js +469 -469
- package/src/utils/file-ownership-verifier.js +358 -358
- package/src/utils/music-file-validator.js +285 -285
- package/src/utils/preview-list-prompt.js +144 -144
- package/src/utils/provider-validator.js +62 -12
- package/src/utils/secure-music-storage.js +412 -412
- package/voice-assignments.json +8244 -8244
- package/.claude/commands/agent-vibes/language.md +0 -23
- package/.claude/commands/agent-vibes/learn.md +0 -67
- package/.claude/commands/agent-vibes/replay-target.md +0 -14
- package/.claude/commands/agent-vibes/target-voice.md +0 -26
- package/.claude/commands/agent-vibes/target.md +0 -30
- package/.claude/hooks/learn-manager.sh +0 -492
- package/.claude/hooks/replay-target-audio.sh +0 -95
- package/.claude/hooks-windows/learn-manager.ps1 +0 -241
|
@@ -42,7 +42,6 @@ HOOKS_LINUX = PROJECT_ROOT / ".claude" / "hooks"
|
|
|
42
42
|
# The auto-conversion (server.py:902-903) converts .sh -> .ps1
|
|
43
43
|
MCP_HARDCODED_SCRIPTS = [
|
|
44
44
|
"provider-manager",
|
|
45
|
-
"learn-manager",
|
|
46
45
|
"speed-manager",
|
|
47
46
|
"download-extra-voices",
|
|
48
47
|
"verbosity-manager",
|
|
@@ -159,7 +158,6 @@ def test_run_script_error_for_missing():
|
|
|
159
158
|
MCP_TOOL_SCRIPT_MAP = {
|
|
160
159
|
"set_speed": "speed-manager",
|
|
161
160
|
"get_speed": "speed-manager",
|
|
162
|
-
"set_learn_mode": "learn-manager",
|
|
163
161
|
"set_verbosity": "verbosity-manager",
|
|
164
162
|
"get_verbosity": "verbosity-manager",
|
|
165
163
|
"mute": "verbosity-manager",
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "agentvibes",
|
|
4
|
-
"version": "5.
|
|
4
|
+
"version": "5.13.0",
|
|
5
5
|
"description": "Now your AI Agents can finally talk back! Professional TTS voice for Claude Code, Claude Desktop (via MCP), and Clawdbot with multi-provider support.",
|
|
6
6
|
"homepage": "https://agentvibes.org",
|
|
7
7
|
"keywords": [
|
|
@@ -1,110 +1,110 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Personality List Display - Beautiful multi-column personality listing
|
|
4
|
-
* Called by personality-manager.sh to display personalities with boxen formatting
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { formatPersonalitiesList } from '../utils/list-formatter.js';
|
|
8
|
-
import fs from 'fs';
|
|
9
|
-
import path from 'path';
|
|
10
|
-
import os from 'os';
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Get personality description from markdown file
|
|
14
|
-
*/
|
|
15
|
-
function getPersonalityDescription(filePath) {
|
|
16
|
-
try {
|
|
17
|
-
const content = fs.readFileSync(filePath, 'utf8');
|
|
18
|
-
|
|
19
|
-
// Try to extract description from frontmatter or first paragraph
|
|
20
|
-
const descMatch = content.match(/description:\s*(.+)/i);
|
|
21
|
-
if (descMatch) {
|
|
22
|
-
return descMatch[1].trim();
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// Try to get first line after frontmatter
|
|
26
|
-
const lines = content.split('\n');
|
|
27
|
-
let inFrontmatter = false;
|
|
28
|
-
let frontmatterCount = 0;
|
|
29
|
-
|
|
30
|
-
for (const line of lines) {
|
|
31
|
-
if (line.trim() === '---') {
|
|
32
|
-
frontmatterCount++;
|
|
33
|
-
inFrontmatter = frontmatterCount === 1;
|
|
34
|
-
continue;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (!inFrontmatter && frontmatterCount >= 2 && line.trim()) {
|
|
38
|
-
// First non-empty line after frontmatter
|
|
39
|
-
return line.trim().replace(/^#+\s*/, '').substring(0, 50);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return '';
|
|
44
|
-
} catch (error) {
|
|
45
|
-
return '';
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Get all personalities from directory
|
|
51
|
-
*/
|
|
52
|
-
function getPersonalities(personalitiesDir, currentPersonality) {
|
|
53
|
-
const personalities = [];
|
|
54
|
-
|
|
55
|
-
if (!fs.existsSync(personalitiesDir)) {
|
|
56
|
-
return personalities;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const files = fs.readdirSync(personalitiesDir);
|
|
60
|
-
for (const file of files) {
|
|
61
|
-
if (file.endsWith('.md')) {
|
|
62
|
-
const name = path.basename(file, '.md');
|
|
63
|
-
const filePath = path.join(personalitiesDir, file);
|
|
64
|
-
const description = getPersonalityDescription(filePath);
|
|
65
|
-
|
|
66
|
-
personalities.push({
|
|
67
|
-
name,
|
|
68
|
-
description,
|
|
69
|
-
current: name === currentPersonality
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Add special 'random' option
|
|
75
|
-
personalities.push({
|
|
76
|
-
name: 'random',
|
|
77
|
-
description: 'Picks randomly each time',
|
|
78
|
-
current: currentPersonality === 'random'
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
return personalities.sort((a, b) => {
|
|
82
|
-
// Keep 'random' at the end
|
|
83
|
-
if (a.name === 'random') return 1;
|
|
84
|
-
if (b.name === 'random') return -1;
|
|
85
|
-
return a.name.localeCompare(b.name);
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Main function
|
|
91
|
-
*/
|
|
92
|
-
function main() {
|
|
93
|
-
const args = process.argv.slice(2);
|
|
94
|
-
|
|
95
|
-
// Parse arguments
|
|
96
|
-
const personalitiesDir = args[0] || path.join(os.homedir(), '.claude', 'personalities');
|
|
97
|
-
const currentPersonality = args[1] || 'normal';
|
|
98
|
-
|
|
99
|
-
const personalities = getPersonalities(personalitiesDir, currentPersonality);
|
|
100
|
-
|
|
101
|
-
// Display with boxen
|
|
102
|
-
const output = formatPersonalitiesList(personalities, {
|
|
103
|
-
columns: 2,
|
|
104
|
-
showUsage: true
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
console.log(output);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
main();
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Personality List Display - Beautiful multi-column personality listing
|
|
4
|
+
* Called by personality-manager.sh to display personalities with boxen formatting
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { formatPersonalitiesList } from '../utils/list-formatter.js';
|
|
8
|
+
import fs from 'fs';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
import os from 'os';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Get personality description from markdown file
|
|
14
|
+
*/
|
|
15
|
+
function getPersonalityDescription(filePath) {
|
|
16
|
+
try {
|
|
17
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
18
|
+
|
|
19
|
+
// Try to extract description from frontmatter or first paragraph
|
|
20
|
+
const descMatch = content.match(/description:\s*(.+)/i);
|
|
21
|
+
if (descMatch) {
|
|
22
|
+
return descMatch[1].trim();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Try to get first line after frontmatter
|
|
26
|
+
const lines = content.split('\n');
|
|
27
|
+
let inFrontmatter = false;
|
|
28
|
+
let frontmatterCount = 0;
|
|
29
|
+
|
|
30
|
+
for (const line of lines) {
|
|
31
|
+
if (line.trim() === '---') {
|
|
32
|
+
frontmatterCount++;
|
|
33
|
+
inFrontmatter = frontmatterCount === 1;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!inFrontmatter && frontmatterCount >= 2 && line.trim()) {
|
|
38
|
+
// First non-empty line after frontmatter
|
|
39
|
+
return line.trim().replace(/^#+\s*/, '').substring(0, 50);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return '';
|
|
44
|
+
} catch (error) {
|
|
45
|
+
return '';
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Get all personalities from directory
|
|
51
|
+
*/
|
|
52
|
+
function getPersonalities(personalitiesDir, currentPersonality) {
|
|
53
|
+
const personalities = [];
|
|
54
|
+
|
|
55
|
+
if (!fs.existsSync(personalitiesDir)) {
|
|
56
|
+
return personalities;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const files = fs.readdirSync(personalitiesDir);
|
|
60
|
+
for (const file of files) {
|
|
61
|
+
if (file.endsWith('.md')) {
|
|
62
|
+
const name = path.basename(file, '.md');
|
|
63
|
+
const filePath = path.join(personalitiesDir, file);
|
|
64
|
+
const description = getPersonalityDescription(filePath);
|
|
65
|
+
|
|
66
|
+
personalities.push({
|
|
67
|
+
name,
|
|
68
|
+
description,
|
|
69
|
+
current: name === currentPersonality
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Add special 'random' option
|
|
75
|
+
personalities.push({
|
|
76
|
+
name: 'random',
|
|
77
|
+
description: 'Picks randomly each time',
|
|
78
|
+
current: currentPersonality === 'random'
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
return personalities.sort((a, b) => {
|
|
82
|
+
// Keep 'random' at the end
|
|
83
|
+
if (a.name === 'random') return 1;
|
|
84
|
+
if (b.name === 'random') return -1;
|
|
85
|
+
return a.name.localeCompare(b.name);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Main function
|
|
91
|
+
*/
|
|
92
|
+
function main() {
|
|
93
|
+
const args = process.argv.slice(2);
|
|
94
|
+
|
|
95
|
+
// Parse arguments
|
|
96
|
+
const personalitiesDir = args[0] || path.join(os.homedir(), '.claude', 'personalities');
|
|
97
|
+
const currentPersonality = args[1] || 'normal';
|
|
98
|
+
|
|
99
|
+
const personalities = getPersonalities(personalitiesDir, currentPersonality);
|
|
100
|
+
|
|
101
|
+
// Display with boxen
|
|
102
|
+
const output = formatPersonalitiesList(personalities, {
|
|
103
|
+
columns: 2,
|
|
104
|
+
showUsage: true
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
console.log(output);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
main();
|
package/src/cli/list-voices.js
CHANGED
|
@@ -7,8 +7,18 @@
|
|
|
7
7
|
import { formatVoicesList } from '../utils/list-formatter.js';
|
|
8
8
|
import fs from 'fs';
|
|
9
9
|
import path from 'path';
|
|
10
|
+
import { fileURLToPath } from 'url';
|
|
10
11
|
import { execFileSync } from 'child_process';
|
|
11
12
|
import os from 'os';
|
|
13
|
+
import {
|
|
14
|
+
KOKORO_VOICE_IDS,
|
|
15
|
+
kokoroGender,
|
|
16
|
+
ELEVENLABS_VOICES,
|
|
17
|
+
} from '../services/provider-voice-catalog.js';
|
|
18
|
+
import {
|
|
19
|
+
listVoices as catalogListVoices,
|
|
20
|
+
getProvider,
|
|
21
|
+
} from '../services/provider-catalog.js';
|
|
12
22
|
|
|
13
23
|
/**
|
|
14
24
|
* Get Piper voices from voice directory
|
|
@@ -71,6 +81,55 @@ function getMacOSVoices(currentVoice) {
|
|
|
71
81
|
return voices;
|
|
72
82
|
}
|
|
73
83
|
|
|
84
|
+
/**
|
|
85
|
+
* Kokoro voice ids follow `<lang><sex>_name`. The first char is the language.
|
|
86
|
+
* Map it to a human-readable language label for the listing.
|
|
87
|
+
*/
|
|
88
|
+
const KOKORO_LANG_LABELS = {
|
|
89
|
+
a: 'en-US', b: 'en-GB', j: 'ja', z: 'zh', e: 'es',
|
|
90
|
+
f: 'fr', h: 'hi', i: 'it', p: 'pt-BR', k: 'ko',
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Get Kokoro voices from the canonical catalog (KOKORO_VOICE_IDS).
|
|
95
|
+
* Kokoro's voice set is a fixed catalog, not discovered on disk.
|
|
96
|
+
*/
|
|
97
|
+
function getKokoroVoices(currentVoice) {
|
|
98
|
+
return KOKORO_VOICE_IDS.map((id) => ({
|
|
99
|
+
name: id,
|
|
100
|
+
lang: KOKORO_LANG_LABELS[id[0]] || '',
|
|
101
|
+
gender: kokoroGender(id),
|
|
102
|
+
current: id === currentVoice,
|
|
103
|
+
}));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Get ElevenLabs voices from the canonical catalog (ELEVENLABS_VOICES).
|
|
108
|
+
* The listing shows the friendly name; matching against currentVoice accepts
|
|
109
|
+
* either the friendly name or the raw voice_id.
|
|
110
|
+
*/
|
|
111
|
+
function getElevenLabsVoices(currentVoice) {
|
|
112
|
+
return ELEVENLABS_VOICES.map((v) => ({
|
|
113
|
+
name: v.name,
|
|
114
|
+
lang: v.lang || '',
|
|
115
|
+
gender: v.gender || '',
|
|
116
|
+
current: v.name === currentVoice || v.id === currentVoice,
|
|
117
|
+
}));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Get Soprano voices from the canonical catalog. Soprano is voiceModel `single`
|
|
122
|
+
* (design §3.1): exactly one canonical voice, soprano-default — no picker.
|
|
123
|
+
*/
|
|
124
|
+
function getSopranoVoices(currentVoice) {
|
|
125
|
+
return catalogListVoices('soprano').map((v) => ({
|
|
126
|
+
name: v.id,
|
|
127
|
+
lang: '',
|
|
128
|
+
gender: v.gender || '',
|
|
129
|
+
current: v.id === currentVoice || currentVoice === '' || currentVoice === 'soprano',
|
|
130
|
+
}));
|
|
131
|
+
}
|
|
132
|
+
|
|
74
133
|
/**
|
|
75
134
|
* Extract language code from voice name
|
|
76
135
|
*/
|
|
@@ -79,6 +138,55 @@ function extractLanguage(voiceName) {
|
|
|
79
138
|
return match ? match[1] : '';
|
|
80
139
|
}
|
|
81
140
|
|
|
141
|
+
/**
|
|
142
|
+
* Discovered providers list-voices enumerates HERE, each via its own platform
|
|
143
|
+
* discovery path (piper: `*.onnx` disk glob; macos: `say -v ?`). Other discovered
|
|
144
|
+
* providers (windows-piper / windows-sapi) are enumerated by the Windows lister,
|
|
145
|
+
* not this Unix/darwin CLI, so they fall to the honest "no voice list" label —
|
|
146
|
+
* preserving pre-AVI-S9.5 output. Adding a record with a static/name-to-id/single
|
|
147
|
+
* voiceModel adds a list arm for free (no edit here needed).
|
|
148
|
+
*/
|
|
149
|
+
const LISTABLE_DISCOVERED = new Set(['piper', 'macos']);
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Resolve a provider token to its voice list + display label by iterating the
|
|
153
|
+
* Provider Catalog (src/services/provider-catalog.js) and branching ONLY on the
|
|
154
|
+
* record's `voiceModel` — replacing the former four hardcoded per-provider
|
|
155
|
+
* equality branches (AVI-S9.5 / design row 19). Unknown tokens keep the honest
|
|
156
|
+
* "no voice list available" label from AVI-S8.1.
|
|
157
|
+
*
|
|
158
|
+
* @param {string} provider
|
|
159
|
+
* @param {string} currentVoice
|
|
160
|
+
* @param {string} voiceDir
|
|
161
|
+
* @returns {{ voices: object[], providerName: string }}
|
|
162
|
+
*/
|
|
163
|
+
function selectVoices(provider, currentVoice, voiceDir) {
|
|
164
|
+
const record = getProvider(provider);
|
|
165
|
+
if (!record) {
|
|
166
|
+
return { voices: [], providerName: `${provider} (no voice list available)` };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Label preserves the pre-existing strings exactly (macOS uses "TTS", not the
|
|
170
|
+
// catalog display name "macOS Say"); all others equal record.displayName.
|
|
171
|
+
const providerName = record.id === 'macos' ? 'macOS TTS' : record.displayName;
|
|
172
|
+
|
|
173
|
+
switch (record.voiceModel) {
|
|
174
|
+
case 'static': // kokoro
|
|
175
|
+
return { voices: getKokoroVoices(currentVoice), providerName };
|
|
176
|
+
case 'name-to-id': // elevenlabs
|
|
177
|
+
return { voices: getElevenLabsVoices(currentVoice), providerName };
|
|
178
|
+
case 'single': // soprano
|
|
179
|
+
return { voices: getSopranoVoices(currentVoice), providerName };
|
|
180
|
+
case 'discovered':
|
|
181
|
+
default:
|
|
182
|
+
if (record.id === 'piper') return { voices: getPiperVoices(voiceDir, currentVoice), providerName };
|
|
183
|
+
if (record.id === 'macos') return { voices: getMacOSVoices(currentVoice), providerName };
|
|
184
|
+
// A discovered provider without a discovery path on this platform: label
|
|
185
|
+
// it honestly instead of rendering an empty list under a wrong provider.
|
|
186
|
+
return { voices: [], providerName: `${provider} (no voice list available)` };
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
82
190
|
/**
|
|
83
191
|
* Main function
|
|
84
192
|
*/
|
|
@@ -90,16 +198,7 @@ function main() {
|
|
|
90
198
|
const currentVoice = args[1] || '';
|
|
91
199
|
const voiceDir = args[2] || '';
|
|
92
200
|
|
|
93
|
-
|
|
94
|
-
let providerName = 'Piper TTS';
|
|
95
|
-
|
|
96
|
-
if (provider === 'piper') {
|
|
97
|
-
voices = getPiperVoices(voiceDir, currentVoice);
|
|
98
|
-
providerName = 'Piper TTS';
|
|
99
|
-
} else if (provider === 'macos') {
|
|
100
|
-
voices = getMacOSVoices(currentVoice);
|
|
101
|
-
providerName = 'macOS TTS';
|
|
102
|
-
}
|
|
201
|
+
const { voices, providerName } = selectVoices(provider, currentVoice, voiceDir);
|
|
103
202
|
|
|
104
203
|
// Display with boxen
|
|
105
204
|
const output = formatVoicesList(voices, {
|
|
@@ -111,4 +210,9 @@ function main() {
|
|
|
111
210
|
console.log(output);
|
|
112
211
|
}
|
|
113
212
|
|
|
114
|
-
|
|
213
|
+
// Only run when invoked as a CLI (keeps selectVoices importable by tests).
|
|
214
|
+
const _invokedDirectly = process.argv[1] &&
|
|
215
|
+
path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
216
|
+
if (_invokedDirectly) main();
|
|
217
|
+
|
|
218
|
+
export { selectVoices, LISTABLE_DISCOVERED };
|