agentvibes 4.2.0 → 4.4.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/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 +152 -79
- 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 +5882 -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 +132 -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
|
@@ -1,136 +1,136 @@
|
|
|
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
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
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
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export default createPreviewListPrompt;
|
|
@@ -62,10 +62,11 @@ export async function validateProvider(providerName) {
|
|
|
62
62
|
return await validatePiperInstallation();
|
|
63
63
|
case 'macos':
|
|
64
64
|
return await validateMacOSProvider();
|
|
65
|
+
case 'sapi':
|
|
65
66
|
case 'windows-sapi':
|
|
66
67
|
return await validateWindowsSAPI();
|
|
67
68
|
case 'windows-piper':
|
|
68
|
-
return await
|
|
69
|
+
return await validateWindowsPiperInstallation();
|
|
69
70
|
case 'termux-ssh':
|
|
70
71
|
case 'ssh-remote':
|
|
71
72
|
case 'ssh-pulseaudio':
|
|
@@ -111,7 +112,10 @@ async function validatePipxProvider(providerName, packageName) {
|
|
|
111
112
|
checkedLocations.push('pipx venv');
|
|
112
113
|
|
|
113
114
|
// Check Python package installations (comprehensive version detection)
|
|
114
|
-
|
|
115
|
+
// On Windows, 'py' is the standard Python launcher and often the only one in PATH
|
|
116
|
+
const pythonCommands = process.platform === 'win32'
|
|
117
|
+
? ['py', 'python', 'python3', 'python3.12', 'python3.11', 'python3.10', 'python3.9', 'python3.8']
|
|
118
|
+
: ['python3', 'python', 'python3.12', 'python3.11', 'python3.10', 'python3.9', 'python3.8'];
|
|
115
119
|
|
|
116
120
|
for (const pythonCmd of pythonCommands) {
|
|
117
121
|
try {
|
|
@@ -210,6 +214,43 @@ export async function validateWindowsSAPI() {
|
|
|
210
214
|
return { installed: true, message: 'Windows SAPI available' };
|
|
211
215
|
}
|
|
212
216
|
|
|
217
|
+
/**
|
|
218
|
+
* Validate Windows Piper TTS installation
|
|
219
|
+
* Checks for piper.exe at the standard Windows install location
|
|
220
|
+
* @returns {Promise<{installed: boolean, message: string, checkedLocations?: string[], error?: string}>}
|
|
221
|
+
*/
|
|
222
|
+
export async function validateWindowsPiperInstallation() {
|
|
223
|
+
if (process.platform !== 'win32') {
|
|
224
|
+
return await validatePiperInstallation(); // Fall back to Unix checks
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const checkedLocations = [];
|
|
228
|
+
|
|
229
|
+
// Check the standard Windows install path
|
|
230
|
+
const localAppData = process.env.LOCALAPPDATA ||
|
|
231
|
+
(process.env.USERPROFILE ? path.join(process.env.USERPROFILE, 'AppData', 'Local') : null);
|
|
232
|
+
if (localAppData) {
|
|
233
|
+
const piperExe = path.join(localAppData, 'Programs', 'Piper', 'piper.exe');
|
|
234
|
+
checkedLocations.push(piperExe);
|
|
235
|
+
if (fs.existsSync(piperExe)) {
|
|
236
|
+
return { installed: true, message: 'Piper TTS detected (Windows exe)', checkedLocations };
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Also check PATH in case user installed it differently
|
|
241
|
+
if (commandExistsInPath('piper')) {
|
|
242
|
+
return { installed: true, message: 'Piper TTS detected (in PATH)', checkedLocations: ['PATH'] };
|
|
243
|
+
}
|
|
244
|
+
checkedLocations.push('PATH');
|
|
245
|
+
|
|
246
|
+
return {
|
|
247
|
+
installed: false,
|
|
248
|
+
message: `Piper TTS is not installed on your system (checked: ${checkedLocations.join(', ')})`,
|
|
249
|
+
error: 'WINDOWS_PIPER_NOT_FOUND',
|
|
250
|
+
checkedLocations
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
213
254
|
/**
|
|
214
255
|
* Test if a provider works at runtime (more thorough check)
|
|
215
256
|
* Attempts to actually use the provider for a brief moment
|
|
@@ -328,10 +369,11 @@ export function getProviderInstallCommand(providerName) {
|
|
|
328
369
|
* @returns {object} {success: boolean, message: string, command?: string, verified?: boolean}
|
|
329
370
|
*/
|
|
330
371
|
export async function attemptProviderInstallation(providerName) {
|
|
331
|
-
// Whitelist approach - only allow known providers
|
|
372
|
+
// Whitelist approach - only allow known providers
|
|
332
373
|
const providers = {
|
|
333
374
|
soprano: 'soprano-tts',
|
|
334
|
-
piper: 'piper-tts'
|
|
375
|
+
piper: 'piper-tts',
|
|
376
|
+
'windows-piper': 'piper-windows-exe'
|
|
335
377
|
};
|
|
336
378
|
|
|
337
379
|
const pkgName = providers[providerName];
|
|
@@ -339,9 +381,22 @@ export async function attemptProviderInstallation(providerName) {
|
|
|
339
381
|
return { success: false, message: `Unknown provider: ${providerName}` };
|
|
340
382
|
}
|
|
341
383
|
|
|
384
|
+
// Windows Piper: download exe instead of pip install
|
|
385
|
+
// The actual download is handled by checkAndInstallPiperWindows() in installer.js.
|
|
386
|
+
// Signal success here so the installer flow continues to that function.
|
|
387
|
+
if (providerName === 'windows-piper') {
|
|
388
|
+
return {
|
|
389
|
+
success: true,
|
|
390
|
+
message: 'Windows Piper will be downloaded during installation',
|
|
391
|
+
command: 'checkAndInstallPiperWindows()',
|
|
392
|
+
verified: false // Verification happens after the actual download
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const isWindows = process.platform === 'win32' && !process.env.WSL_DISTRO_NAME;
|
|
397
|
+
|
|
342
398
|
// Strategy 1: Try regular pip install (using spawnSync for correct API usage)
|
|
343
399
|
try {
|
|
344
|
-
// Show installation in progress
|
|
345
400
|
console.log(` Attempting: pip install ${pkgName}...`);
|
|
346
401
|
const result = spawnSync('pip', ['install', pkgName], {
|
|
347
402
|
stdio: 'inherit',
|
|
@@ -351,7 +406,6 @@ export async function attemptProviderInstallation(providerName) {
|
|
|
351
406
|
if (result.error || result.status !== 0) {
|
|
352
407
|
// Strategy 1 failed - continue to Strategy 2
|
|
353
408
|
} else {
|
|
354
|
-
// Verify installation actually worked (proves it's installed)
|
|
355
409
|
const validation = await validateProvider(providerName);
|
|
356
410
|
if (validation.installed) {
|
|
357
411
|
return {
|
|
@@ -377,9 +431,8 @@ export async function attemptProviderInstallation(providerName) {
|
|
|
377
431
|
});
|
|
378
432
|
|
|
379
433
|
if (result.error || result.status !== 0) {
|
|
380
|
-
//
|
|
434
|
+
// Strategy 2 failed
|
|
381
435
|
} else {
|
|
382
|
-
// Verify installation actually worked (proves it's installed)
|
|
383
436
|
const validation = await validateProvider(providerName);
|
|
384
437
|
if (validation.installed) {
|
|
385
438
|
return {
|
|
@@ -393,11 +446,41 @@ export async function attemptProviderInstallation(providerName) {
|
|
|
393
446
|
return { success: true, message: `Successfully installed via pipx`, command: `pipx install ${pkgName}` };
|
|
394
447
|
}
|
|
395
448
|
} catch (error) {
|
|
396
|
-
//
|
|
449
|
+
// Strategy 2 failed
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Strategy 3 (Windows only): Try 'py -m pip' which is the standard Windows Python launcher
|
|
453
|
+
if (isWindows) {
|
|
454
|
+
try {
|
|
455
|
+
console.log(` Attempting: py -m pip install ${pkgName}...`);
|
|
456
|
+
const result = spawnSync('py', ['-m', 'pip', 'install', pkgName], {
|
|
457
|
+
stdio: 'inherit',
|
|
458
|
+
timeout: 60000
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
if (!result.error && result.status === 0) {
|
|
462
|
+
const validation = await validateProvider(providerName);
|
|
463
|
+
if (validation.installed) {
|
|
464
|
+
return {
|
|
465
|
+
success: true,
|
|
466
|
+
message: `Successfully installed via py -m pip`,
|
|
467
|
+
command: `py -m pip install ${pkgName}`,
|
|
468
|
+
verified: true
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
return { success: true, message: `Successfully installed via py -m pip`, command: `py -m pip install ${pkgName}` };
|
|
473
|
+
}
|
|
474
|
+
} catch (error) {
|
|
475
|
+
// Strategy 3 failed
|
|
476
|
+
}
|
|
397
477
|
}
|
|
398
478
|
|
|
399
|
-
//
|
|
400
|
-
|
|
479
|
+
// All strategies failed
|
|
480
|
+
const installHint = isWindows
|
|
481
|
+
? `Installation failed. Please install manually:\n py -m pip install ${pkgName}\n or\n pip install ${pkgName}`
|
|
482
|
+
: `Installation failed. Please install manually:\n pip install ${pkgName}\n or\n pipx install ${pkgName}`;
|
|
483
|
+
return { success: false, message: installHint };
|
|
401
484
|
}
|
|
402
485
|
|
|
403
486
|
/**
|
|
@@ -460,8 +543,9 @@ export function getProviderDisplayName(providerName) {
|
|
|
460
543
|
soprano: 'Soprano TTS',
|
|
461
544
|
piper: 'Piper TTS',
|
|
462
545
|
macos: 'macOS Say',
|
|
546
|
+
sapi: 'Windows SAPI',
|
|
463
547
|
'windows-sapi': 'Windows SAPI',
|
|
464
|
-
'windows-piper': '
|
|
548
|
+
'windows-piper': 'Piper TTS'
|
|
465
549
|
};
|
|
466
550
|
|
|
467
551
|
return names[providerName] || providerName;
|