@drakulavich/kesha-voice-kit 1.25.0 → 1.27.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/completions/kesha.bash +1 -1
- package/completions/kesha.fish +1 -0
- package/completions/kesha.zsh +2 -1
- package/docs/product-positioning.md +17 -15
- package/man/kesha.1 +3 -0
- package/package.json +7 -4
- package/src/cli/init.ts +63 -62
- package/src/cli/install.ts +86 -27
- package/src/cli/status.ts +1 -2
- package/src/doctor.ts +62 -5
- package/src/engine-install.ts +145 -50
- package/src/engine-targets.ts +48 -0
- package/src/engine.ts +1 -3
- package/src/fluid-asr-cache.ts +91 -0
- package/src/install-plan.ts +124 -33
- package/src/paths.ts +4 -2
- package/src/progress.ts +24 -2
- package/src/semver.d.mts +16 -0
- package/src/semver.mjs +87 -0
- package/src/star.ts +3 -6
- package/src/status.ts +36 -9
- package/src/__tests__/error-codes-drift.test.ts +0 -29
- package/src/__tests__/error-codes.test.ts +0 -25
- package/src/__tests__/stats-error-codes.test.ts +0 -14
package/README.md
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
|
|
25
25
|
## Quick Start
|
|
26
26
|
|
|
27
|
-
Runtime: **[Bun](https://bun.sh)** >= 1.3.0 · Platforms: macOS arm64, Linux x64
|
|
27
|
+
Runtime: **[Bun](https://bun.sh)** >= 1.3.0 · Platforms: macOS arm64, Linux x64, Windows x64. Linux and Windows run the ONNX engine — everything except microphone capture (`kesha record`), macOS system voices, speaker diarization, and text language detection, which need Apple frameworks.
|
|
28
28
|
|
|
29
29
|
```bash
|
|
30
30
|
# 1. Install Bun (skip if you have it) — Linux & macOS:
|
|
@@ -36,7 +36,8 @@ curl -fsSL https://bun.sh/install | bash # or: brew install oven-sh/bun/b
|
|
|
36
36
|
bun add -g @drakulavich/kesha-voice-kit
|
|
37
37
|
kesha --version # confirms `kesha` resolved on PATH
|
|
38
38
|
kesha install --plan # preview exact download/disk sizes first — downloads nothing
|
|
39
|
-
kesha install # ~2.5 GB
|
|
39
|
+
kesha install # ~2.5 GB on Linux/Windows; ~0.6 GB on Apple Silicon, whose CoreML
|
|
40
|
+
# engine uses a different, smaller model set. Explicit — never automatic.
|
|
40
41
|
# No progress bar during the model step; can take several minutes.
|
|
41
42
|
# Prefer a guided wizard? `kesha init` walks through the same choices interactively.
|
|
42
43
|
|
package/completions/kesha.bash
CHANGED
|
@@ -21,7 +21,7 @@ _kesha_completion() {
|
|
|
21
21
|
completions) opts="--help -h" ;;
|
|
22
22
|
doctor) opts="--help -h --json --redact" ;;
|
|
23
23
|
init) opts="--help -h --coreml --onnx --no-cache --plan --yes --tts --vad --diarize" ;;
|
|
24
|
-
install) opts="--help -h --coreml --onnx --no-cache --plan --tts --vad --diarize" ;;
|
|
24
|
+
install) opts="--help -h --coreml --onnx --no-cache --plan --tts --vad --diarize --engine-version" ;;
|
|
25
25
|
logs) opts="--help -h --json" ;;
|
|
26
26
|
manpage) opts="--help -h" ;;
|
|
27
27
|
mcp) opts="--help -h" ;;
|
package/completions/kesha.fish
CHANGED
|
@@ -54,6 +54,7 @@ complete -c kesha -n '__fish_seen_subcommand_from install' -l plan -d 'Show down
|
|
|
54
54
|
complete -c kesha -n '__fish_seen_subcommand_from install' -l tts -d 'Also install TTS models (English only; add codes for more, e.g. --tts en ru (preview sizes: kesha install --plan))'
|
|
55
55
|
complete -c kesha -n '__fish_seen_subcommand_from install' -l vad -d 'Also install Silero VAD (~2.3MB) for long-audio preprocessing'
|
|
56
56
|
complete -c kesha -n '__fish_seen_subcommand_from install' -l diarize -d 'Also install the Sortformer streaming-diarization model (~245MB, darwin-arm64 only — #199)'
|
|
57
|
+
complete -c kesha -n '__fish_seen_subcommand_from install' -l engine-version -r -d 'Install this exact engine release instead of the pinned one (expert; the next install returns to the pin)'
|
|
57
58
|
complete -c kesha -n '__fish_seen_subcommand_from logs' -l help -d 'Show help'
|
|
58
59
|
complete -c kesha -n '__fish_seen_subcommand_from logs' -s h -d 'Show help'
|
|
59
60
|
complete -c kesha -n '__fish_seen_subcommand_from logs' -l json -d 'Output status as JSON'
|
package/completions/kesha.zsh
CHANGED
|
@@ -75,7 +75,8 @@ _kesha() {
|
|
|
75
75
|
'--plan[Show download, disk, and warm-up plan without changing local state]' \
|
|
76
76
|
'--tts[Also install TTS models (English only; add codes for more, e.g. --tts en ru (preview sizes: kesha install --plan))]' \
|
|
77
77
|
'--vad[Also install Silero VAD (~2.3MB) for long-audio preprocessing]' \
|
|
78
|
-
'--diarize[Also install the Sortformer streaming-diarization model (~245MB, darwin-arm64 only — #199)]'
|
|
78
|
+
'--diarize[Also install the Sortformer streaming-diarization model (~245MB, darwin-arm64 only — #199)]' \
|
|
79
|
+
'--engine-version=[Install this exact engine release instead of the pinned one (expert; the next install returns to the pin)]:engine version:'
|
|
79
80
|
;;
|
|
80
81
|
logs)
|
|
81
82
|
_arguments '--help[Show help]' \
|
|
@@ -60,27 +60,29 @@ The product promise is not "one model for every audio problem." The promise is a
|
|
|
60
60
|
|
|
61
61
|
| Capability | macOS arm64 | macOS x64 | Linux x64 | Windows x64 | Nix path |
|
|
62
62
|
|---|---|---|---|---|---|
|
|
63
|
-
| STT | Supported, CoreML path | Not shipped | Supported, ONNX CPU path |
|
|
64
|
-
| Audio language detection | Supported | Not shipped | Supported |
|
|
65
|
-
| VAD | Supported with `kesha install --vad` | Not shipped | Supported with `kesha install --vad` |
|
|
66
|
-
| TTS: English Kokoro | Supported, FluidAudio/CoreML in release builds | Not shipped | Supported, ONNX path |
|
|
67
|
-
| TTS: Russian Vosk-TTS | Supported | Not shipped | Supported |
|
|
63
|
+
| STT | Supported, CoreML path | Not shipped | Supported, ONNX CPU path | Supported, ONNX CPU path | `aarch64-darwin`, `x86_64-linux` |
|
|
64
|
+
| Audio language detection | Supported | Not shipped | Supported | Supported | Supported where the flake builds |
|
|
65
|
+
| VAD | Supported with `kesha install --vad` | Not shipped | Supported with `kesha install --vad` | Supported with `kesha install --vad` | Supported where the engine path includes VAD assets |
|
|
66
|
+
| TTS: English Kokoro | Supported, FluidAudio/CoreML in release builds | Not shipped | Supported, ONNX path | Supported, ONNX path | Supported except where noted in `docs/nix-install.md` |
|
|
67
|
+
| TTS: Russian Vosk-TTS | Supported | Not shipped | Supported | Supported | Supported except where noted in `docs/nix-install.md` |
|
|
68
68
|
| macOS system voices | Supported | Not shipped | Not applicable | Not applicable | Supported on `aarch64-darwin` |
|
|
69
69
|
| Speaker diarization | Preview, darwin-arm64 only | Not supported | Not supported | Not supported | Not wired into the Nix build yet |
|
|
70
70
|
| Raycast extension | Supported | Not shipped | Not applicable | Not applicable | Not applicable |
|
|
71
|
-
| OpenClaw integration | Supported through CLI route | Not shipped | Supported through CLI route |
|
|
72
|
-
| Hermes Agent integration | Supported through command providers | Not shipped | Supported through command providers |
|
|
71
|
+
| OpenClaw integration | Supported through CLI route | Not shipped | Supported through CLI route | Supported through CLI route | Use the installed `kesha` command from the chosen path |
|
|
72
|
+
| Hermes Agent integration | Supported through command providers | Not shipped | Supported through command providers | Supported through command providers | Use the installed `kesha` command from the chosen path |
|
|
73
73
|
|
|
74
74
|
`macOS x64` means Intel Macs. Kesha does not currently publish a `darwin-x64` engine binary, so Intel Macs are intentionally marked as not shipped rather than implied to use the ONNX fallback.
|
|
75
75
|
|
|
76
|
-
`Windows x64`
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
install
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
76
|
+
`Windows x64` runs the same ONNX engine as Linux. The MSVC link failure behind
|
|
77
|
+
[issue #216](https://github.com/drakulavich/kesha-voice-kit/issues/216) was fixed by
|
|
78
|
+
vendoring the Vosk-TTS runtime under `rust/vendor/vosk-tts/`, and the CLI's install
|
|
79
|
+
refusal — which outlived that fix by about twenty minor versions — is gone. CI now runs
|
|
80
|
+
a cold `kesha install` on `windows-latest`, synthesises with both the Kokoro and Vosk
|
|
81
|
+
engines, and transcribes the result back, so the platform's row reflects a tested path
|
|
82
|
+
rather than a published binary nobody had run.
|
|
83
|
+
|
|
84
|
+
Microphone capture is the one Linux/Windows gap that is not about Apple frameworks:
|
|
85
|
+
`kesha record` is macOS-only, so pass an existing audio file instead.
|
|
84
86
|
|
|
85
87
|
## When to choose something else
|
|
86
88
|
|
package/man/kesha.1
CHANGED
|
@@ -141,6 +141,9 @@ Also install Silero VAD (~2.3MB) for long\-audio preprocessing
|
|
|
141
141
|
.TP
|
|
142
142
|
.B \-\-diarize
|
|
143
143
|
Also install the Sortformer streaming\-diarization model (~245MB, darwin\-arm64 only — #199)
|
|
144
|
+
.TP
|
|
145
|
+
.B \-\-engine\-version
|
|
146
|
+
Install this exact engine release instead of the pinned one (expert; the next install returns to the pin)
|
|
144
147
|
.SS logs
|
|
145
148
|
.TP
|
|
146
149
|
.B \-\-json
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@drakulavich/kesha-voice-kit",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.27.0-alpha.1",
|
|
4
4
|
"description": "Give your tools a voice — speech to text and back, 25 languages, up to ~19× faster than Whisper. On your machine.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
"SKILL.md",
|
|
26
26
|
"LICENSE",
|
|
27
27
|
"NOTICES.md",
|
|
28
|
-
"README.md"
|
|
28
|
+
"README.md",
|
|
29
|
+
"!src/__tests__"
|
|
29
30
|
],
|
|
30
31
|
"openclaw": {
|
|
31
32
|
"extensions": [
|
|
@@ -57,7 +58,8 @@
|
|
|
57
58
|
"check:workflows": "bun .github/scripts/check-workflows.ts",
|
|
58
59
|
"check:specs": "bunx @fission-ai/openspec@1.4.1 validate --specs --strict",
|
|
59
60
|
"check:release-manifest": "bun .github/scripts/release-manifest.mjs --check",
|
|
60
|
-
"check:versions": "bun .github/scripts/check-versions.ts"
|
|
61
|
+
"check:versions": "bun .github/scripts/check-versions.ts",
|
|
62
|
+
"check:engine-targets": "bun .github/scripts/check-engine-targets.ts"
|
|
61
63
|
},
|
|
62
64
|
"keywords": [
|
|
63
65
|
"asr",
|
|
@@ -102,6 +104,7 @@
|
|
|
102
104
|
},
|
|
103
105
|
"overrides": {
|
|
104
106
|
"hono": "^4.12.25",
|
|
105
|
-
"fast-uri": "^3.1.
|
|
107
|
+
"fast-uri": "^3.1.5",
|
|
108
|
+
"ip-address": "^10.3.1"
|
|
106
109
|
}
|
|
107
110
|
}
|
package/src/cli/init.ts
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { defineCommand } from "citty";
|
|
2
|
-
import {
|
|
3
|
-
import { stdin as input, stdout as output } from "node:process";
|
|
4
|
-
import { multiselect, isCancel, cancel } from "@clack/prompts";
|
|
2
|
+
import { confirm, multiselect, isCancel, cancel } from "@clack/prompts";
|
|
5
3
|
import { renderInstallPlan } from "../install-plan";
|
|
6
4
|
import { log } from "../log";
|
|
7
5
|
import { getEngineCapabilities } from "../engine";
|
|
8
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
performInstall,
|
|
8
|
+
resolveBackendFlag,
|
|
9
|
+
resolveNoCacheFlag,
|
|
10
|
+
TTS_LANG_FALLBACK,
|
|
11
|
+
unavailableBackendError,
|
|
12
|
+
} from "./install";
|
|
9
13
|
import type { SharedInstallArgs } from "./types";
|
|
10
14
|
|
|
11
15
|
const TTS_LANG_LABELS: Record<string, string> = {
|
|
@@ -23,6 +27,18 @@ const TTS_LANG_LABELS: Record<string, string> = {
|
|
|
23
27
|
/** Signature of the interactive TTS-language picker (injectable for tests). */
|
|
24
28
|
export type TtsLangPrompt = (preselect: string[]) => Promise<string[]>;
|
|
25
29
|
|
|
30
|
+
/** Signature of the interactive yes/no prompt (injectable for tests). */
|
|
31
|
+
export type ConfirmPrompt = (message: string, initialValue: boolean) => Promise<boolean>;
|
|
32
|
+
|
|
33
|
+
/** Ctrl-C at any prompt ends init without downloading, rather than falling through with a default. */
|
|
34
|
+
function exitIfCancelled<T>(answer: T | symbol): T {
|
|
35
|
+
if (isCancel(answer)) {
|
|
36
|
+
cancel("Init cancelled.");
|
|
37
|
+
process.exit(0);
|
|
38
|
+
}
|
|
39
|
+
return answer as T;
|
|
40
|
+
}
|
|
41
|
+
|
|
26
42
|
async function promptTtsLangs(preselect: string[]): Promise<string[]> {
|
|
27
43
|
const caps = await getEngineCapabilities();
|
|
28
44
|
const supported = caps?.tts?.languages.map((l) => l.code) ?? TTS_LANG_FALLBACK;
|
|
@@ -33,11 +49,12 @@ async function promptTtsLangs(preselect: string[]): Promise<string[]> {
|
|
|
33
49
|
initialValues: preselect.filter((l) => supported.includes(l)),
|
|
34
50
|
required: false,
|
|
35
51
|
});
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
52
|
+
return exitIfCancelled(selected);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** #677: keep every prompt on clack — its close() unpipes stdin, deadlocking any readline sharing it. */
|
|
56
|
+
async function promptConfirm(message: string, initialValue: boolean): Promise<boolean> {
|
|
57
|
+
return exitIfCancelled(await confirm({ message, initialValue }));
|
|
41
58
|
}
|
|
42
59
|
|
|
43
60
|
export interface InitCommandArgs extends SharedInstallArgs {
|
|
@@ -52,10 +69,6 @@ export interface InitSelection {
|
|
|
52
69
|
diarize: boolean;
|
|
53
70
|
}
|
|
54
71
|
|
|
55
|
-
interface PromptApi {
|
|
56
|
-
question(prompt: string): Promise<string>;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
72
|
export function canInstallDiarizeOnPlatform(
|
|
60
73
|
platform = process.platform,
|
|
61
74
|
arch = process.arch,
|
|
@@ -140,17 +153,17 @@ export function renderInitOverview(canDiarize = canInstallDiarizeOnPlatform()):
|
|
|
140
153
|
|
|
141
154
|
export async function promptInitSelection(
|
|
142
155
|
args: InitCommandArgs,
|
|
143
|
-
prompt:
|
|
156
|
+
prompt: ConfirmPrompt,
|
|
144
157
|
backend = resolveBackendFlag(args.coreml, args.onnx),
|
|
145
158
|
canDiarize = canInstallDiarizeOnPlatform(),
|
|
146
159
|
noCache = resolveNoCacheFlag(args),
|
|
147
160
|
promptTts: TtsLangPrompt = promptTtsLangs,
|
|
148
161
|
): Promise<InitSelection> {
|
|
149
162
|
const ttsLangs = await promptTts(args.tts ? ["en"] : []);
|
|
150
|
-
const vad = await
|
|
163
|
+
const vad = await prompt("Install VAD for long or silence-heavy audio?", args.vad);
|
|
151
164
|
let diarize = false;
|
|
152
165
|
if (canDiarize) {
|
|
153
|
-
diarize = await
|
|
166
|
+
diarize = await prompt("Install speaker diarization for `--speakers`?", args.diarize);
|
|
154
167
|
} else if (args.diarize) {
|
|
155
168
|
log.warn("--diarize is currently darwin-arm64 only; omitting it from the interactive install.");
|
|
156
169
|
}
|
|
@@ -164,18 +177,14 @@ export async function promptInitSelection(
|
|
|
164
177
|
};
|
|
165
178
|
}
|
|
166
179
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
log.warn("Please answer yes or no.");
|
|
180
|
+
/** Mirrors the guard in `performInstall`: a preview must not describe an install this platform rejects (#684). */
|
|
181
|
+
async function printPlan(selection: InitSelection): Promise<boolean> {
|
|
182
|
+
const backendError = unavailableBackendError(selection.backend);
|
|
183
|
+
if (backendError) {
|
|
184
|
+
log.error(backendError);
|
|
185
|
+
process.exitCode = 2;
|
|
186
|
+
return false;
|
|
175
187
|
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
async function printPlan(selection: InitSelection): Promise<void> {
|
|
179
188
|
log.info(
|
|
180
189
|
await renderInstallPlan({
|
|
181
190
|
noCache: selection.noCache,
|
|
@@ -185,6 +194,7 @@ async function printPlan(selection: InitSelection): Promise<void> {
|
|
|
185
194
|
diarize: selection.diarize,
|
|
186
195
|
}),
|
|
187
196
|
);
|
|
197
|
+
return true;
|
|
188
198
|
}
|
|
189
199
|
|
|
190
200
|
async function runNonInteractive(selection: InitSelection): Promise<void> {
|
|
@@ -194,7 +204,7 @@ async function runNonInteractive(selection: InitSelection): Promise<void> {
|
|
|
194
204
|
log.warn("--diarize is currently darwin-arm64 only; omitting it from non-interactive examples.");
|
|
195
205
|
}
|
|
196
206
|
log.info(renderInitOverview(canDiarize));
|
|
197
|
-
await printPlan(printableSelection);
|
|
207
|
+
if (!(await printPlan(printableSelection))) return;
|
|
198
208
|
log.info("Run one of these commands from an interactive terminal:");
|
|
199
209
|
for (const command of initSuggestionCommands(printableSelection, canDiarize)) {
|
|
200
210
|
log.info(` ${command.join(" ")}`);
|
|
@@ -264,13 +274,7 @@ export const initCommand = defineCommand({
|
|
|
264
274
|
if (selection.diarize && !installSelection.diarize) {
|
|
265
275
|
log.warn("--diarize is currently darwin-arm64 only; omitting it from the --yes install.");
|
|
266
276
|
}
|
|
267
|
-
await performInstall(
|
|
268
|
-
installSelection.noCache,
|
|
269
|
-
installSelection.backend,
|
|
270
|
-
installSelection.ttsLangs,
|
|
271
|
-
installSelection.vad,
|
|
272
|
-
installSelection.diarize,
|
|
273
|
-
);
|
|
277
|
+
await performInstall(installSelection);
|
|
274
278
|
return;
|
|
275
279
|
}
|
|
276
280
|
|
|
@@ -282,33 +286,30 @@ export const initCommand = defineCommand({
|
|
|
282
286
|
}
|
|
283
287
|
|
|
284
288
|
log.info(renderInitOverview());
|
|
285
|
-
const
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
}
|
|
310
|
-
} finally {
|
|
311
|
-
rl.close();
|
|
289
|
+
const prompted = await promptInitSelection(
|
|
290
|
+
args,
|
|
291
|
+
promptConfirm,
|
|
292
|
+
backend,
|
|
293
|
+
canInstallDiarizeOnPlatform(),
|
|
294
|
+
noCache,
|
|
295
|
+
);
|
|
296
|
+
log.info("");
|
|
297
|
+
if (!(await printPlan(prompted))) return;
|
|
298
|
+
const confirmed = await promptConfirm(
|
|
299
|
+
`Run \`${initInstallArgs(prompted).join(" ")}\` now?`,
|
|
300
|
+
true,
|
|
301
|
+
);
|
|
302
|
+
if (!confirmed) {
|
|
303
|
+
log.info(`Skipped install. Run later: ${initInstallArgs(prompted).join(" ")}`);
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
await performInstall(prompted);
|
|
307
|
+
// #523: end on something runnable, not on the install log.
|
|
308
|
+
log.info("");
|
|
309
|
+
log.success("Kesha is ready. Try:");
|
|
310
|
+
log.info(" kesha path/to/audio.ogg Transcribe a file.");
|
|
311
|
+
if (prompted.ttsLangs.length > 0) {
|
|
312
|
+
log.info(' kesha say "hello" Speak text (TTS).');
|
|
312
313
|
}
|
|
313
314
|
},
|
|
314
315
|
});
|
package/src/cli/install.ts
CHANGED
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
import { defineCommand } from "citty";
|
|
2
2
|
import { errorMessage } from "../error-utils";
|
|
3
|
-
import {
|
|
3
|
+
import { installEngine } from "../engine-install";
|
|
4
|
+
import { engineTarget } from "../engine-targets";
|
|
4
5
|
import { getEngineBinPath, getEngineCapabilities } from "../engine";
|
|
5
6
|
import { renderInstallPlan } from "../install-plan";
|
|
6
7
|
import { maybeAskForStar } from "../star";
|
|
7
8
|
import { log } from "../log";
|
|
8
9
|
import { packageVersion } from "../package-info";
|
|
10
|
+
import { isSemver } from "../semver.mjs";
|
|
9
11
|
import { createDiagnosticLogSession, type DiagnosticLogSession } from "../diagnostic-log";
|
|
10
12
|
import type { SharedInstallArgs } from "./types";
|
|
11
13
|
|
|
12
14
|
export interface InstallCommandArgs extends SharedInstallArgs {
|
|
13
15
|
/** Positional args after `install` — candidate TTS language codes. */
|
|
14
16
|
_?: string[];
|
|
17
|
+
/** `init` deliberately has no such flag: an override is an expert action, not a guided one. */
|
|
18
|
+
"engine-version"?: string;
|
|
19
|
+
engineVersion?: string;
|
|
15
20
|
}
|
|
16
21
|
|
|
17
22
|
/**
|
|
@@ -56,6 +61,23 @@ export function resolveTtsLangs(input: TtsArgInput, supported: string[] | undefi
|
|
|
56
61
|
return langs;
|
|
57
62
|
}
|
|
58
63
|
|
|
64
|
+
/**
|
|
65
|
+
* Validates `--engine-version` before any network call. Returns undefined when the flag is
|
|
66
|
+
* absent, so the Pinned Engine version stays the default; throws on anything the version
|
|
67
|
+
* drift gate would not accept either — one SemVer 2.0 definition across the repo.
|
|
68
|
+
*/
|
|
69
|
+
export function resolveEngineVersionFlag(raw: unknown): string | undefined {
|
|
70
|
+
if (raw === undefined || raw === null || raw === false) return undefined;
|
|
71
|
+
const value = String(raw).trim();
|
|
72
|
+
if (isSemver(value)) return value;
|
|
73
|
+
const hint = /^v\d/.test(value)
|
|
74
|
+
? ' Drop the leading "v" — that belongs to the release tag, not the version.'
|
|
75
|
+
: "";
|
|
76
|
+
throw new Error(
|
|
77
|
+
`--engine-version needs an exact SemVer 2.0 version like 1.24.8 or 1.24.8-alpha.1, got "${value}".${hint}`,
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
59
81
|
export function resolveNoCacheFlag(
|
|
60
82
|
args: Pick<InstallCommandArgs, "no-cache" | "noCache" | "no_cache">,
|
|
61
83
|
rawArgs: string[] = [],
|
|
@@ -90,10 +112,11 @@ export function resolveBackendFlag(coreml: boolean, onnx: boolean): string | und
|
|
|
90
112
|
return selection.backend;
|
|
91
113
|
}
|
|
92
114
|
|
|
93
|
-
function defaultBackendForPlatform(
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
115
|
+
export function defaultBackendForPlatform(
|
|
116
|
+
platform: string = process.platform,
|
|
117
|
+
arch: string = process.arch,
|
|
118
|
+
): string | undefined {
|
|
119
|
+
return engineTarget(platform, arch)?.backend;
|
|
97
120
|
}
|
|
98
121
|
|
|
99
122
|
type InstallDiagnosticErrorKind = "validation_failed" | "install_failed";
|
|
@@ -118,16 +141,42 @@ function finishInstallDiagnostic(
|
|
|
118
141
|
}
|
|
119
142
|
}
|
|
120
143
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
144
|
+
/** Null when the requested backend is installable here. `KESHA_ENGINE_BIN` opts out — the user supplied their own engine. */
|
|
145
|
+
export function unavailableBackendError(backend: string | undefined): string | null {
|
|
146
|
+
const platformBackend = defaultBackendForPlatform();
|
|
147
|
+
if (!backend || process.env.KESHA_ENGINE_BIN || !platformBackend) return null;
|
|
148
|
+
if (backend === platformBackend) return null;
|
|
149
|
+
return (
|
|
150
|
+
`Requested backend "${backend}" is not available on this platform; ` +
|
|
151
|
+
`the release engine uses "${platformBackend}".`
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export interface PerformInstallOptions {
|
|
156
|
+
noCache: boolean;
|
|
157
|
+
backend?: string;
|
|
158
|
+
ttsLangs: string[];
|
|
159
|
+
vad?: boolean;
|
|
160
|
+
diarize?: boolean;
|
|
161
|
+
plan?: boolean;
|
|
162
|
+
/** Engine release to install instead of the pin, for this invocation only. */
|
|
163
|
+
engineVersion?: string;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export async function performInstall(options: PerformInstallOptions) {
|
|
167
|
+
const { noCache, backend, ttsLangs, vad = false, diarize = false, plan = false, engineVersion } =
|
|
168
|
+
options;
|
|
169
|
+
// A plan for an unavailable backend previews what its own printed command rejects (#684).
|
|
170
|
+
const backendError = unavailableBackendError(backend);
|
|
129
171
|
if (plan) {
|
|
130
|
-
|
|
172
|
+
if (backendError) {
|
|
173
|
+
log.error(backendError);
|
|
174
|
+
process.exitCode = 2;
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
log.info(
|
|
178
|
+
await renderInstallPlan({ noCache, backend, ttsLangs, vad, diarize, engineVersion }),
|
|
179
|
+
);
|
|
131
180
|
return;
|
|
132
181
|
}
|
|
133
182
|
|
|
@@ -143,6 +192,7 @@ export async function performInstall(
|
|
|
143
192
|
tts: ttsLangs.length > 0,
|
|
144
193
|
vad,
|
|
145
194
|
diarize,
|
|
195
|
+
engineVersionOverride: engineVersion !== undefined,
|
|
146
196
|
});
|
|
147
197
|
|
|
148
198
|
if (diarize && !(process.platform === "darwin" && process.arch === "arm64")) {
|
|
@@ -152,15 +202,11 @@ export async function performInstall(
|
|
|
152
202
|
"(see https://github.com/drakulavich/kesha-voice-kit/issues/199).",
|
|
153
203
|
);
|
|
154
204
|
}
|
|
155
|
-
|
|
156
|
-
if (backend && !process.env.KESHA_ENGINE_BIN && platformBackend && backend !== platformBackend) {
|
|
205
|
+
if (backendError) {
|
|
157
206
|
errorKind = "validation_failed";
|
|
158
|
-
throw new Error(
|
|
159
|
-
`Requested backend "${backend}" is not available on this platform; ` +
|
|
160
|
-
`the release engine uses "${platformBackend}".`,
|
|
161
|
-
);
|
|
207
|
+
throw new Error(backendError);
|
|
162
208
|
}
|
|
163
|
-
await
|
|
209
|
+
await installEngine({ noCache, backend, ttsLangs, vad, diarize, version: engineVersion });
|
|
164
210
|
await maybeAskForStar(getEngineBinPath(), packageVersion, log);
|
|
165
211
|
finishInstallDiagnostic(diagnosticLog, startedAt, "success");
|
|
166
212
|
} catch (err: unknown) {
|
|
@@ -212,8 +258,20 @@ export const installCommand = defineCommand({
|
|
|
212
258
|
description: "Also install the Sortformer streaming-diarization model (~245MB, darwin-arm64 only — #199)",
|
|
213
259
|
default: false,
|
|
214
260
|
},
|
|
261
|
+
"engine-version": {
|
|
262
|
+
type: "string",
|
|
263
|
+
description:
|
|
264
|
+
"Install this exact engine release instead of the pinned one (expert; the next install returns to the pin)",
|
|
265
|
+
},
|
|
215
266
|
},
|
|
216
267
|
async run({ args, rawArgs }: { args: InstallCommandArgs; rawArgs: string[] }) {
|
|
268
|
+
let engineVersion: string | undefined;
|
|
269
|
+
try {
|
|
270
|
+
engineVersion = resolveEngineVersionFlag(args["engine-version"] ?? args.engineVersion);
|
|
271
|
+
} catch (err) {
|
|
272
|
+
log.error(errorMessage(err));
|
|
273
|
+
process.exit(2);
|
|
274
|
+
}
|
|
217
275
|
const backend = resolveBackendFlag(args.coreml, args.onnx);
|
|
218
276
|
const positionals = (args._ ?? []).map(String);
|
|
219
277
|
const caps = await getEngineCapabilities();
|
|
@@ -225,13 +283,14 @@ export const installCommand = defineCommand({
|
|
|
225
283
|
log.error(errorMessage(err));
|
|
226
284
|
process.exit(1);
|
|
227
285
|
}
|
|
228
|
-
await performInstall(
|
|
229
|
-
resolveNoCacheFlag(args, rawArgs),
|
|
286
|
+
await performInstall({
|
|
287
|
+
noCache: resolveNoCacheFlag(args, rawArgs),
|
|
230
288
|
backend,
|
|
231
289
|
ttsLangs,
|
|
232
|
-
args.vad,
|
|
233
|
-
args.diarize,
|
|
234
|
-
args.plan,
|
|
235
|
-
|
|
290
|
+
vad: args.vad,
|
|
291
|
+
diarize: args.diarize,
|
|
292
|
+
plan: args.plan,
|
|
293
|
+
engineVersion,
|
|
294
|
+
});
|
|
236
295
|
},
|
|
237
296
|
});
|
package/src/cli/status.ts
CHANGED
|
@@ -26,8 +26,7 @@ export const statusCommand = defineCommand({
|
|
|
26
26
|
async run({ args }: { args: StatusCommandArgs }) {
|
|
27
27
|
const report = await collectStatus({ disk: args.disk });
|
|
28
28
|
if (args.json) {
|
|
29
|
-
// `log.*` writes to stdout, so the payload must be the only thing printed
|
|
30
|
-
// the setup hint ships inside it instead of on stderr (#647).
|
|
29
|
+
// `log.*` writes to stdout, so the payload must be the only thing printed (#647).
|
|
31
30
|
console.log(JSON.stringify(report, null, 2));
|
|
32
31
|
return;
|
|
33
32
|
}
|