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
|
@@ -1,144 +1,144 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Custom Inquirer List Prompt with Spacebar Preview
|
|
3
|
-
* Uses wrapper approach with readline keypress events
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import readline from 'node:readline';
|
|
7
|
-
import { execSync } from 'node:child_process';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Wrapper for inquirer list prompt that adds spacebar preview
|
|
11
|
-
* @param {Object} inquirer - Inquirer instance
|
|
12
|
-
* @param {Object} config - Prompt configuration
|
|
13
|
-
* @param {Function} config.onPreview - Callback for preview (receives selected value)
|
|
14
|
-
* @returns {Promise} Inquirer prompt promise
|
|
15
|
-
*/
|
|
16
|
-
export async function createPreviewListPrompt(inquirer, config) {
|
|
17
|
-
const { onPreview, ...promptConfig } = config;
|
|
18
|
-
|
|
19
|
-
// Set up keypress listener
|
|
20
|
-
let keypressListener = null;
|
|
21
|
-
|
|
22
|
-
// Track playing state
|
|
23
|
-
let currentlyPlaying = null;
|
|
24
|
-
let audioProcess = null;
|
|
25
|
-
|
|
26
|
-
// Initialize currentSelection to match the default value
|
|
27
|
-
let currentSelection = 0;
|
|
28
|
-
if (promptConfig.default) {
|
|
29
|
-
const defaultIndex = promptConfig.choices.findIndex(c => c.value === promptConfig.default);
|
|
30
|
-
if (defaultIndex !== -1) {
|
|
31
|
-
currentSelection = defaultIndex;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Function to stop currently playing audio
|
|
36
|
-
const stopAudio = () => {
|
|
37
|
-
// Kill the specific process if we have it
|
|
38
|
-
// SECURITY: Only kill our own process, never use pkill which affects all users
|
|
39
|
-
if (audioProcess) {
|
|
40
|
-
try {
|
|
41
|
-
audioProcess.kill('SIGKILL');
|
|
42
|
-
audioProcess = null;
|
|
43
|
-
} catch (e) {
|
|
44
|
-
// Process might have already finished or already killed
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
currentlyPlaying = null;
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
if (onPreview && process.stdin.isTTY) {
|
|
52
|
-
readline.emitKeypressEvents(process.stdin);
|
|
53
|
-
// SECURITY: Wrap in try-catch to prevent terminal corruption on error
|
|
54
|
-
try {
|
|
55
|
-
if (process.stdin.setRawMode) {
|
|
56
|
-
process.stdin.setRawMode(true);
|
|
57
|
-
}
|
|
58
|
-
} catch (e) {
|
|
59
|
-
// Failed to set raw mode, continue without it
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
keypressListener = async (str, key) => {
|
|
63
|
-
// Track current selection based on arrow keys
|
|
64
|
-
if (key && key.name === 'down') {
|
|
65
|
-
currentSelection = Math.min(currentSelection + 1, promptConfig.choices.length - 1);
|
|
66
|
-
} else if (key && key.name === 'up') {
|
|
67
|
-
currentSelection = Math.max(currentSelection - 1, 0);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
if (key && key.name === 'space') {
|
|
71
|
-
// Get the current item (don't filter - use actual index)
|
|
72
|
-
const currentChoice = promptConfig.choices[currentSelection];
|
|
73
|
-
|
|
74
|
-
// Only preview if it's a valid choice (not separator, not special item)
|
|
75
|
-
if (currentChoice && currentChoice.value && !currentChoice.value.startsWith('__')) {
|
|
76
|
-
|
|
77
|
-
// Toggle: if same voice pressed twice, stop it
|
|
78
|
-
if (currentlyPlaying === currentChoice.value) {
|
|
79
|
-
stopAudio();
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// CRITICAL: Stop previous voice BEFORE starting new one
|
|
84
|
-
if (currentlyPlaying) {
|
|
85
|
-
stopAudio();
|
|
86
|
-
// Small delay to ensure kill takes effect
|
|
87
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
currentlyPlaying = currentChoice.value;
|
|
91
|
-
|
|
92
|
-
// Call onPreview and store process handle immediately
|
|
93
|
-
try {
|
|
94
|
-
const result = await onPreview(currentChoice.value);
|
|
95
|
-
// Store the process handle - onPreview should return the spawn() result
|
|
96
|
-
audioProcess = result;
|
|
97
|
-
} catch (err) {
|
|
98
|
-
console.error(`[Preview] Error playing sample:`, err.message);
|
|
99
|
-
currentlyPlaying = null;
|
|
100
|
-
audioProcess = null;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
process.stdin.on('keypress', keypressListener);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
try {
|
|
110
|
-
// Run the standard list prompt
|
|
111
|
-
const result = await inquirer.prompt([{
|
|
112
|
-
...promptConfig,
|
|
113
|
-
type: 'list'
|
|
114
|
-
}]);
|
|
115
|
-
|
|
116
|
-
return result;
|
|
117
|
-
} finally {
|
|
118
|
-
// Stop any playing audio
|
|
119
|
-
stopAudio();
|
|
120
|
-
|
|
121
|
-
// Clean up keypress listener
|
|
122
|
-
if (keypressListener) {
|
|
123
|
-
process.stdin.removeListener('keypress', keypressListener);
|
|
124
|
-
// SECURITY: Wrap in try-catch to ensure cleanup always completes
|
|
125
|
-
try {
|
|
126
|
-
if (process.stdin.setRawMode) {
|
|
127
|
-
process.stdin.setRawMode(false);
|
|
128
|
-
}
|
|
129
|
-
} catch (e) {
|
|
130
|
-
// Failed to restore raw mode, but we're exiting anyway
|
|
131
|
-
}
|
|
132
|
-
// readline.emitKeypressEvents() put stdin into flowing mode; pause it so the
|
|
133
|
-
// open stdin handle no longer keeps the event loop alive after the prompt
|
|
134
|
-
// resolves (otherwise the process lingers until stdin is otherwise closed).
|
|
135
|
-
try {
|
|
136
|
-
process.stdin.pause();
|
|
137
|
-
} catch (e) {
|
|
138
|
-
// stdin may already be closed; nothing more to do
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
export default createPreviewListPrompt;
|
|
1
|
+
/**
|
|
2
|
+
* Custom Inquirer List Prompt with Spacebar Preview
|
|
3
|
+
* Uses wrapper approach with readline keypress events
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import readline from 'node:readline';
|
|
7
|
+
import { execSync } from 'node:child_process';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Wrapper for inquirer list prompt that adds spacebar preview
|
|
11
|
+
* @param {Object} inquirer - Inquirer instance
|
|
12
|
+
* @param {Object} config - Prompt configuration
|
|
13
|
+
* @param {Function} config.onPreview - Callback for preview (receives selected value)
|
|
14
|
+
* @returns {Promise} Inquirer prompt promise
|
|
15
|
+
*/
|
|
16
|
+
export async function createPreviewListPrompt(inquirer, config) {
|
|
17
|
+
const { onPreview, ...promptConfig } = config;
|
|
18
|
+
|
|
19
|
+
// Set up keypress listener
|
|
20
|
+
let keypressListener = null;
|
|
21
|
+
|
|
22
|
+
// Track playing state
|
|
23
|
+
let currentlyPlaying = null;
|
|
24
|
+
let audioProcess = null;
|
|
25
|
+
|
|
26
|
+
// Initialize currentSelection to match the default value
|
|
27
|
+
let currentSelection = 0;
|
|
28
|
+
if (promptConfig.default) {
|
|
29
|
+
const defaultIndex = promptConfig.choices.findIndex(c => c.value === promptConfig.default);
|
|
30
|
+
if (defaultIndex !== -1) {
|
|
31
|
+
currentSelection = defaultIndex;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Function to stop currently playing audio
|
|
36
|
+
const stopAudio = () => {
|
|
37
|
+
// Kill the specific process if we have it
|
|
38
|
+
// SECURITY: Only kill our own process, never use pkill which affects all users
|
|
39
|
+
if (audioProcess) {
|
|
40
|
+
try {
|
|
41
|
+
audioProcess.kill('SIGKILL');
|
|
42
|
+
audioProcess = null;
|
|
43
|
+
} catch (e) {
|
|
44
|
+
// Process might have already finished or already killed
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
currentlyPlaying = null;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
if (onPreview && process.stdin.isTTY) {
|
|
52
|
+
readline.emitKeypressEvents(process.stdin);
|
|
53
|
+
// SECURITY: Wrap in try-catch to prevent terminal corruption on error
|
|
54
|
+
try {
|
|
55
|
+
if (process.stdin.setRawMode) {
|
|
56
|
+
process.stdin.setRawMode(true);
|
|
57
|
+
}
|
|
58
|
+
} catch (e) {
|
|
59
|
+
// Failed to set raw mode, continue without it
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
keypressListener = async (str, key) => {
|
|
63
|
+
// Track current selection based on arrow keys
|
|
64
|
+
if (key && key.name === 'down') {
|
|
65
|
+
currentSelection = Math.min(currentSelection + 1, promptConfig.choices.length - 1);
|
|
66
|
+
} else if (key && key.name === 'up') {
|
|
67
|
+
currentSelection = Math.max(currentSelection - 1, 0);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (key && key.name === 'space') {
|
|
71
|
+
// Get the current item (don't filter - use actual index)
|
|
72
|
+
const currentChoice = promptConfig.choices[currentSelection];
|
|
73
|
+
|
|
74
|
+
// Only preview if it's a valid choice (not separator, not special item)
|
|
75
|
+
if (currentChoice && currentChoice.value && !currentChoice.value.startsWith('__')) {
|
|
76
|
+
|
|
77
|
+
// Toggle: if same voice pressed twice, stop it
|
|
78
|
+
if (currentlyPlaying === currentChoice.value) {
|
|
79
|
+
stopAudio();
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// CRITICAL: Stop previous voice BEFORE starting new one
|
|
84
|
+
if (currentlyPlaying) {
|
|
85
|
+
stopAudio();
|
|
86
|
+
// Small delay to ensure kill takes effect
|
|
87
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
currentlyPlaying = currentChoice.value;
|
|
91
|
+
|
|
92
|
+
// Call onPreview and store process handle immediately
|
|
93
|
+
try {
|
|
94
|
+
const result = await onPreview(currentChoice.value);
|
|
95
|
+
// Store the process handle - onPreview should return the spawn() result
|
|
96
|
+
audioProcess = result;
|
|
97
|
+
} catch (err) {
|
|
98
|
+
console.error(`[Preview] Error playing sample:`, err.message);
|
|
99
|
+
currentlyPlaying = null;
|
|
100
|
+
audioProcess = null;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
process.stdin.on('keypress', keypressListener);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
// Run the standard list prompt
|
|
111
|
+
const result = await inquirer.prompt([{
|
|
112
|
+
...promptConfig,
|
|
113
|
+
type: 'list'
|
|
114
|
+
}]);
|
|
115
|
+
|
|
116
|
+
return result;
|
|
117
|
+
} finally {
|
|
118
|
+
// Stop any playing audio
|
|
119
|
+
stopAudio();
|
|
120
|
+
|
|
121
|
+
// Clean up keypress listener
|
|
122
|
+
if (keypressListener) {
|
|
123
|
+
process.stdin.removeListener('keypress', keypressListener);
|
|
124
|
+
// SECURITY: Wrap in try-catch to ensure cleanup always completes
|
|
125
|
+
try {
|
|
126
|
+
if (process.stdin.setRawMode) {
|
|
127
|
+
process.stdin.setRawMode(false);
|
|
128
|
+
}
|
|
129
|
+
} catch (e) {
|
|
130
|
+
// Failed to restore raw mode, but we're exiting anyway
|
|
131
|
+
}
|
|
132
|
+
// readline.emitKeypressEvents() put stdin into flowing mode; pause it so the
|
|
133
|
+
// open stdin handle no longer keeps the event loop alive after the prompt
|
|
134
|
+
// resolves (otherwise the process lingers until stdin is otherwise closed).
|
|
135
|
+
try {
|
|
136
|
+
process.stdin.pause();
|
|
137
|
+
} catch (e) {
|
|
138
|
+
// stdin may already be closed; nothing more to do
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export default createPreviewListPrompt;
|
|
@@ -7,6 +7,64 @@ import { spawnSync } from 'node:child_process';
|
|
|
7
7
|
import path from 'node:path'; // For safe path operations and traversal prevention
|
|
8
8
|
import fs from 'node:fs'; // For checking file/directory existence
|
|
9
9
|
import os from 'node:os'; // For os.homedir() to prevent HOME injection attacks
|
|
10
|
+
import { listProviders, displayName as catalogDisplayName } from '../services/provider-catalog.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Canonical set of TTS synthesis providers AgentVibes knows how to use.
|
|
14
|
+
* SINGLE SOURCE OF TRUTH: every user-facing dispatcher (MCP set_provider
|
|
15
|
+
* allowlist, voice switch, voice list, Windows provider manager) must recognise
|
|
16
|
+
* these providers — platform-scoped where noted below. A provider that lives
|
|
17
|
+
* here but is missing from a dispatcher is the "silent drop to Piper / Unknown
|
|
18
|
+
* provider" class of bug (see AVI-S8.1). The dispatcher-parity conformance test
|
|
19
|
+
* asserts this invariant.
|
|
20
|
+
*
|
|
21
|
+
* Platform notes: `macos` is Darwin-only; `windows-sapi`/`windows-piper` are
|
|
22
|
+
* Windows-only. `kokoro` ships a play-tts runtime on BOTH Unix
|
|
23
|
+
* (play-tts-kokoro.sh) and PowerShell (play-tts-kokoro.ps1). `elevenlabs` ships
|
|
24
|
+
* a Unix runtime only (play-tts-elevenlabs.sh) — there is NO
|
|
25
|
+
* play-tts-elevenlabs.ps1 yet — so it is recognised by the portable (bash/JS)
|
|
26
|
+
* dispatchers but intentionally excluded from the Windows provider manager
|
|
27
|
+
* until a PowerShell runtime lands (AVI-S8.2).
|
|
28
|
+
* @type {readonly string[]}
|
|
29
|
+
*/
|
|
30
|
+
export const SUPPORTED_PROVIDERS = Object.freeze(listProviders().map((p) => p.id));
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Providers that use the SAME id on every OS and have at least a Unix (bash)
|
|
34
|
+
* runtime. Every PORTABLE dispatcher — the MCP set_provider allowlist,
|
|
35
|
+
* voice-manager.sh, and list-voices.js — must recognise these regardless of
|
|
36
|
+
* platform. DERIVED VIEW over the catalog: the providers with a Unix runtime
|
|
37
|
+
* whose voices are enumerable without disk (static/name-to-id) — i.e. kokoro and
|
|
38
|
+
* elevenlabs.
|
|
39
|
+
* @type {readonly string[]}
|
|
40
|
+
*/
|
|
41
|
+
export const CROSS_PLATFORM_PROVIDERS = Object.freeze(
|
|
42
|
+
listProviders()
|
|
43
|
+
.filter((p) => p.runtime.unix !== null && (p.voiceModel === 'static' || p.voiceModel === 'name-to-id'))
|
|
44
|
+
.map((p) => p.id),
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Subset of {@link CROSS_PLATFORM_PROVIDERS} that ALSO ships a PowerShell runtime
|
|
49
|
+
* (play-tts-*.ps1), so the Windows provider manager (provider-manager.ps1) must
|
|
50
|
+
* recognise them too. `elevenlabs` is excluded until play-tts-elevenlabs.ps1
|
|
51
|
+
* exists (AVI-S8.2). DERIVED VIEW over the catalog.
|
|
52
|
+
* @type {readonly string[]}
|
|
53
|
+
*/
|
|
54
|
+
export const WINDOWS_RUNTIME_PROVIDERS = Object.freeze(
|
|
55
|
+
listProviders()
|
|
56
|
+
.filter((p) => p.runtime.unix !== null && (p.voiceModel === 'static' || p.voiceModel === 'name-to-id') && p.runtime.windows !== null)
|
|
57
|
+
.map((p) => p.id),
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Is `name` a provider AgentVibes canonically supports?
|
|
62
|
+
* @param {string} name
|
|
63
|
+
* @returns {boolean}
|
|
64
|
+
*/
|
|
65
|
+
export function isKnownProvider(name) {
|
|
66
|
+
return SUPPORTED_PROVIDERS.includes(String(name || '').toLowerCase());
|
|
67
|
+
}
|
|
10
68
|
|
|
11
69
|
/**
|
|
12
70
|
* Helper: Check if command exists in PATH
|
|
@@ -603,16 +661,8 @@ function getPackageInfo(pkgName) {
|
|
|
603
661
|
* @returns {string} Display name
|
|
604
662
|
*/
|
|
605
663
|
export function getProviderDisplayName(providerName) {
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
elevenlabs: 'ElevenLabs',
|
|
611
|
-
macos: 'macOS Say',
|
|
612
|
-
sapi: 'Windows SAPI',
|
|
613
|
-
'windows-sapi': 'Windows SAPI',
|
|
614
|
-
'windows-piper': 'Piper TTS'
|
|
615
|
-
};
|
|
616
|
-
|
|
617
|
-
return names[providerName] || providerName;
|
|
664
|
+
// DERIVED VIEW: delegate to the Provider Catalog (single source of truth for
|
|
665
|
+
// display names). The catalog resolves aliases (e.g. 'sapi' → Windows SAPI) and
|
|
666
|
+
// passes unknown ids through unchanged, preserving this function's contract.
|
|
667
|
+
return catalogDisplayName(providerName);
|
|
618
668
|
}
|