@drakulavich/kesha-voice-kit 1.30.0 → 2.0.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/README.md +5 -5
- package/SKILL.md +1 -1
- package/completions/kesha.bash +1 -1
- package/completions/kesha.fish +0 -1
- package/completions/kesha.zsh +3 -1
- package/docs/diagnostic-logs.md +36 -3
- package/docs/product-positioning.md +3 -3
- package/model-plan.json +130 -0
- package/package.json +21 -7
- package/src/cache-layout.ts +78 -8
- package/src/cli/action-result.ts +2 -1
- package/src/cli/command-session.ts +14 -3
- package/src/cli/completions.ts +4 -2
- package/src/cli/dispatch.ts +37 -5
- package/src/cli/init.ts +4 -4
- package/src/cli/install.ts +9 -6
- package/src/cli/main.ts +61 -46
- package/src/cli/mcp.ts +2 -0
- package/src/cli/options.ts +79 -0
- package/src/cli/record.ts +2 -1
- package/src/cli/say.ts +92 -11
- package/src/cli/stats.ts +9 -4
- package/src/cli/support-bundle.ts +3 -3
- package/src/cli.ts +1 -1
- package/src/diagnostic-log.ts +2 -9
- package/src/doctor.ts +62 -11
- package/src/engine/events.ts +20 -1
- package/src/engine/spawn.ts +114 -0
- package/src/engine-health.ts +25 -56
- package/src/engine-install.ts +230 -109
- package/src/engine-targets.ts +53 -5
- package/src/engine.ts +87 -102
- package/src/fluid-roots.ts +5 -0
- package/src/format.ts +11 -5
- package/src/install-lock.ts +45 -11
- package/src/install-plan.ts +96 -48
- package/src/kokoro-ane.ts +67 -4
- package/src/language-routing.ts +51 -0
- package/src/lib.ts +96 -61
- package/src/log.ts +2 -0
- package/src/mcp/audio-output.ts +2 -2
- package/src/mcp/tools.ts +10 -14
- package/src/package-info.ts +28 -2
- package/src/paths.ts +2 -2
- package/src/process-tree.ts +70 -18
- package/src/semver.mjs +7 -10
- package/src/shell-artifacts.ts +4 -3
- package/src/state-paths.ts +84 -0
- package/src/stats.ts +2 -10
- package/src/status.ts +39 -42
- package/src/synth.ts +58 -79
- package/src/transcribe.ts +18 -3
- package/src/voice-inventory.ts +51 -0
- package/src/voice-routing.ts +97 -6
- package/tsconfig.json +1 -1
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
- **Small Rust engine** — single ~65MB binary, no ffmpeg, no Python, no native Node addons
|
|
20
20
|
|
|
21
21
|
<p align="center">
|
|
22
|
-
<img src="https://github.com/drakulavich/kesha-voice-kit/raw/main/demo.
|
|
22
|
+
<img src="https://github.com/drakulavich/kesha-voice-kit/raw/main/docs/assets/demo.webp" alt="kesha demo: an English voice message transcribed, a Russian one as JSON with automatic language detection, then text-to-speech into a wav" width="800">
|
|
23
23
|
</p>
|
|
24
24
|
|
|
25
25
|
## Quick Start
|
|
@@ -138,10 +138,10 @@ Full per-file breakdown (Russian + English): [BENCHMARK.md](BENCHMARK.md). The C
|
|
|
138
138
|
|
|
139
139
|
All of these install the Bun CLI wrapper; engine + models still download explicitly via `kesha install`. (Nix is the exception — it currently builds only the engine from source; see below.)
|
|
140
140
|
|
|
141
|
-
- **Homebrew** — `brew install drakulavich/tap/kesha-voice-kit` · [docs/
|
|
142
|
-
- **Linux packages** (`.deb`/`.rpm`, x64) — published on
|
|
141
|
+
- **Homebrew** — `brew install drakulavich/tap/kesha-voice-kit` · [docs/distribution.md](docs/distribution.md#homebrew)
|
|
142
|
+
- **Linux packages** (`.deb`/`.rpm`, x64) — published on stable releases, see [docs/distribution.md](docs/distribution.md#linux-packages)
|
|
143
143
|
- **Docker** (GHCR image) — [docs/docker.md](docs/docker.md)
|
|
144
|
-
- **Nix** (`aarch64-darwin` / `x86_64-linux`) — builds the engine from source (`nix build github:drakulavich/kesha-voice-kit#kesha-engine`). The full `kesha` CLI via `nix run` / `nix profile install` is **not yet available** — it needs a maintainer with Nix to populate a build hash ([#946](https://github.com/drakulavich/kesha-voice-kit/issues/946)). · [docs/
|
|
144
|
+
- **Nix** (`aarch64-darwin` / `x86_64-linux`) — builds the engine from source (`nix build github:drakulavich/kesha-voice-kit#kesha-engine`). The full `kesha` CLI via `nix run` / `nix profile install` is **not yet available** — it needs a maintainer with Nix to populate a build hash ([#946](https://github.com/drakulavich/kesha-voice-kit/issues/946)). · [docs/distribution.md](docs/distribution.md#nix)
|
|
145
145
|
- **Shell completions + manpage** — `kesha completions bash|zsh|fish` and `kesha manpage` print the packaged files to install wherever your shell expects them.
|
|
146
146
|
|
|
147
147
|
## Integrations
|
|
@@ -158,7 +158,7 @@ All of these install the Bun CLI wrapper; engine + models still download explici
|
|
|
158
158
|
- [Use cases](docs/use-cases.md) — copy-paste recipes (transcribe a meeting, speak from OpenClaw, run offline, move the cache).
|
|
159
159
|
- [Product positioning](docs/product-positioning.md) — supported workflows, non-goals, maturity labels, platform matrix.
|
|
160
160
|
- [Changelog](CHANGELOG.md) — every release, with the behaviour changes spelled out.
|
|
161
|
-
- **Diagnostics:** `kesha doctor`, `kesha support-bundle` (redacted `.tar.gz` for issues), and `kesha logs` produce local, content-free diagnostics — see [docs/diagnostic-logs.md](docs/diagnostic-logs.md). Every failure prints a stable `error [CODE]: …` line and a documented [process exit code](docs/errors.md#process-exit-codes).
|
|
161
|
+
- **Diagnostics:** `kesha doctor`, `kesha support-bundle` (redacted `.tar.gz` for issues), and `kesha logs` produce local, content-free diagnostics — see [docs/diagnostic-logs.md](docs/diagnostic-logs.md). One variable, `KESHA_HOME`, moves everything Kesha writes (models, logs, stats) into a directory of your choice — see [where Kesha keeps its files](docs/diagnostic-logs.md#where-kesha-keeps-its-files). Every failure prints a stable `error [CODE]: …` line and a documented [process exit code](docs/errors.md#process-exit-codes).
|
|
162
162
|
- **Scripting & CI:** `--json` (or `--toon`) for machine-readable output, `--include-errors` (with either) to get per-file failures on stdout alongside the results, `--quiet`/`-q` to silence progress, and `--no-color` (or `NO_COLOR=1`) for plain logs. Colors switch off automatically when `CI=true`.
|
|
163
163
|
- **Privacy / Local Stats:** Stats are **off by default** and fully local. Opt in with `kesha stats enable` to record content-free operational metrics in a local SQLite database — never networked, never storing audio, transcripts, text, or paths. Full commands & lifecycle: [docs/local-stats.md](docs/local-stats.md).
|
|
164
164
|
|
package/SKILL.md
CHANGED
|
@@ -236,7 +236,7 @@ No system deps — English G2P is embedded (`misaki-rs`); Russian G2P is bundled
|
|
|
236
236
|
|
|
237
237
|
**Speech-to-text (25):** Bulgarian, Croatian, Czech, Danish, Dutch, English, Estonian, Finnish, French, German, Greek, Hungarian, Italian, Latvian, Lithuanian, Maltese, Polish, Portuguese, Romanian, Russian, Slovak, Slovenian, Spanish, Swedish, Ukrainian.
|
|
238
238
|
|
|
239
|
-
**Text-to-speech (9):** English (Kokoro-82M; FluidAudio CoreML on darwin-arm64, ONNX elsewhere), Russian (Vosk-TTS, 5 baked-in speakers — default `ru-vosk-m02`), Spanish, French, Italian, Portuguese (Kokoro — CharsiuG2P on ONNX builds, FluidAudio G2P on darwin-arm64), plus Hindi, Japanese, Chinese (Kokoro, darwin-arm64 only). `hi`/`ja`
|
|
239
|
+
**Text-to-speech (9):** English (Kokoro-82M; FluidAudio CoreML on darwin-arm64, ONNX elsewhere), Russian (Vosk-TTS, 5 baked-in speakers — default `ru-vosk-m02`), Spanish, French, Italian, Portuguese (Kokoro — CharsiuG2P on ONNX builds, FluidAudio G2P on darwin-arm64), plus Hindi, Japanese, Chinese (Kokoro, darwin-arm64 only). The `hi`/`ja` Kokoro voices read romanized Latin input only; on macOS, text that is mostly Devanagari or kana/kanji with no `--voice` routes to an installed AVSpeech voice (Lekha for `hi-IN`, Otoya for `ja-JP`), while an explicit `--voice hi-*`/`ja-*` refuses them with `E_SCRIPT_UNSUPPORTED`; non-macOS builds have no `hi`/`ja` voices, so the text is refused by the default English voice (`E_SCRIPT_UNSUPPORTED`) and `--voice hi-*`/`ja-*` is `E_VOICE_UNKNOWN` — see [#492](https://github.com/drakulavich/kesha-voice-kit/issues/492). Any macOS system voice is also available via `--voice macos-*`.
|
|
240
240
|
|
|
241
241
|
## Performance
|
|
242
242
|
|
package/completions/kesha.bash
CHANGED
package/completions/kesha.fish
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
# fish completion for kesha.
|
|
2
|
-
complete -c kesha -f
|
|
3
2
|
complete -c kesha -n '__fish_use_subcommand' -a 'completions' -d 'Print shell completion script for bash, zsh, or fish'
|
|
4
3
|
complete -c kesha -n '__fish_use_subcommand' -a 'doctor' -d 'Collect support diagnostics without changing local state'
|
|
5
4
|
complete -c kesha -n '__fish_use_subcommand' -a 'init' -d 'Interactive setup guide for Kesha features'
|
package/completions/kesha.zsh
CHANGED
|
@@ -40,6 +40,7 @@ _kesha() {
|
|
|
40
40
|
'--no-color[Disable ANSI colors (also via NO_COLOR=1; auto-off when CI=true)]'
|
|
41
41
|
else
|
|
42
42
|
_describe -t commands 'kesha command' commands
|
|
43
|
+
_files
|
|
43
44
|
fi
|
|
44
45
|
return
|
|
45
46
|
fi
|
|
@@ -155,7 +156,8 @@ _kesha() {
|
|
|
155
156
|
'--vad[Force Silero VAD preprocessing (kesha install --vad first). Without this, VAD auto-engages on audio ≥ 120s.]' \
|
|
156
157
|
'--no-vad[Force full-file ASR for short/medium files; long audio fails early. Incompatible with --speakers]' \
|
|
157
158
|
'--quiet[Suppress progress output; print only results and errors]' \
|
|
158
|
-
'--no-color[Disable ANSI colors (also via NO_COLOR=1; auto-off when CI=true)]'
|
|
159
|
+
'--no-color[Disable ANSI colors (also via NO_COLOR=1; auto-off when CI=true)]' \
|
|
160
|
+
'*:audio file:_files'
|
|
159
161
|
;;
|
|
160
162
|
esac
|
|
161
163
|
}
|
package/docs/diagnostic-logs.md
CHANGED
|
@@ -43,9 +43,11 @@ stage names, exit codes, duration numbers, and stable error codes.
|
|
|
43
43
|
|
|
44
44
|
When enabled, `kesha install`, `kesha <audio>`, and `kesha say` record command
|
|
45
45
|
lifecycle events such as `command.start`, `input.audio`, `input.missing`,
|
|
46
|
-
`engine.exit`,
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
`engine.exit`, `engine.debug` (the Engine's own `KESHA_DEBUG` timeline, carrying
|
|
47
|
+
its event name and typed fields but never its message text), and `command.finish`.
|
|
48
|
+
These events use only counts, booleans, format extensions, duration milliseconds,
|
|
49
|
+
and bucket labels. In the default `retain-on-failure` mode, successful runs still
|
|
50
|
+
leave no log file behind.
|
|
49
51
|
|
|
50
52
|
Diagnostic logs must not store:
|
|
51
53
|
|
|
@@ -75,6 +77,37 @@ Set `KESHA_LOG_DIR` to override the directory. The active file is
|
|
|
75
77
|
`kesha.ndjson`; rotated files are named `kesha.1.ndjson`, `kesha.2.ndjson`, and
|
|
76
78
|
so on.
|
|
77
79
|
|
|
80
|
+
## Where Kesha keeps its files
|
|
81
|
+
|
|
82
|
+
Kesha writes to four places. Each resolves by the same rule: its own variable
|
|
83
|
+
when set, otherwise the path under `KESHA_HOME` when that is set, otherwise the
|
|
84
|
+
platform default. `KESHA_HOME` uses one layout on every platform, so a test run,
|
|
85
|
+
a CI job or a second profile needs exactly one variable to leave your real
|
|
86
|
+
models, logs and Stats untouched. Nothing is moved when you set it: a fresh
|
|
87
|
+
`KESHA_HOME` starts with an empty cache, and `kesha install` fills it. Two roots FluidAudio owns stay outside it on darwin-arm64: the Kokoro ANE bundles and voice packs under `~/.cache/fluidaudio`, and its Silero VAD copy under `~/Library/Application Support/FluidAudio`; `status --disk` reports both as external roots, and `kesha install --tts` stages into the first regardless of `KESHA_HOME`.
|
|
88
|
+
|
|
89
|
+
| What | Default (macOS / Windows / Linux) | Under `KESHA_HOME` | Own variable |
|
|
90
|
+
|---|---|---|---|
|
|
91
|
+
| Model cache: engine, models, `recordings/` | `~/.cache/kesha` everywhere | `<home>/cache` | `KESHA_CACHE_DIR` |
|
|
92
|
+
| Diagnostic log directory | `~/Library/Logs/kesha` / `%LOCALAPPDATA%\kesha\logs` / `$XDG_STATE_HOME/kesha/logs` | `<home>/logs` | `KESHA_LOG_DIR` |
|
|
93
|
+
| Stats DB | `~/Library/Application Support/kesha/stats.sqlite` / `%APPDATA%\kesha\stats.sqlite` / `$XDG_DATA_HOME/kesha/stats.sqlite` | `<home>/stats.sqlite` | `KESHA_STATS_DB` |
|
|
94
|
+
| MCP audio (`kesha mcp` synthesis output) | `<tmpdir>/kesha-mcp` | `<home>/mcp-audio` | none |
|
|
95
|
+
|
|
96
|
+
`kesha status --json` and `kesha doctor --json` report every path together with
|
|
97
|
+
the rule that decided it (`default`, `KESHA_HOME`, or the variable name), so an
|
|
98
|
+
isolated run can be verified from one command:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
KESHA_HOME=/tmp/kesha-ci kesha status --json | jq .paths
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
An empty variable counts as unset; a relative path is resolved against the
|
|
105
|
+
working directory when the command starts. The engine itself reads only
|
|
106
|
+
`KESHA_CACHE_DIR`, so the CLI hands it the resolved cache root under that name
|
|
107
|
+
whenever `KESHA_HOME` decided it. FluidAudio's own Silero VAD copy under
|
|
108
|
+
`~/Library/Application Support/FluidAudio` is placed by FluidAudio and stays
|
|
109
|
+
outside `KESHA_HOME`; `kesha status --disk` lists it as an external root.
|
|
110
|
+
|
|
78
111
|
The first implementation rotates at 10 MB and keeps 5 rotated files. `kesha logs
|
|
79
112
|
reset` deletes Kesha log files but preserves the selected mode.
|
|
80
113
|
|
|
@@ -27,7 +27,7 @@ The product promise is not "one model for every audio problem." The promise is a
|
|
|
27
27
|
| Integrate with OpenClaw as a local voice model | Stable integration surface, user-configured route | `docs/openclaw.md` |
|
|
28
28
|
| Integrate with Hermes Agent as local STT/TTS commands | Stable CLI surface, user-configured route | `docs/hermes.md` |
|
|
29
29
|
| Use Raycast actions for selected-file transcription or clipboard speech | Beta, macOS only | `raycast/` extension |
|
|
30
|
-
| Install through Nix instead of Bun/npm | Beta, selected systems | `docs/
|
|
30
|
+
| Install through Nix instead of Bun/npm | Beta, selected systems | `docs/distribution.md#nix` |
|
|
31
31
|
|
|
32
32
|
## Non-goals
|
|
33
33
|
|
|
@@ -63,8 +63,8 @@ The product promise is not "one model for every audio problem." The promise is a
|
|
|
63
63
|
| STT | Supported, CoreML path | Not shipped | Supported, ONNX CPU path | Supported, ONNX CPU path | `aarch64-darwin`, `x86_64-linux` |
|
|
64
64
|
| Audio language detection | Supported | Not shipped | Supported | Supported | Supported where the flake builds |
|
|
65
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: Kokoro (`en` `es` `fr` `it` `pt`) | Supported, FluidAudio/CoreML in release builds | Not shipped | Supported, ONNX path | Supported, ONNX path | Supported except where noted in `docs/
|
|
67
|
-
| TTS: Russian Vosk-TTS | Supported | Not shipped | Supported | Supported | Supported except where noted in `docs/
|
|
66
|
+
| TTS: Kokoro (`en` `es` `fr` `it` `pt`) | Supported, FluidAudio/CoreML in release builds | Not shipped | Supported, ONNX path | Supported, ONNX path | Supported except where noted in `docs/distribution.md#nix` |
|
|
67
|
+
| TTS: Russian Vosk-TTS | Supported | Not shipped | Supported | Supported | Supported except where noted in `docs/distribution.md#nix` |
|
|
68
68
|
| TTS: Kokoro (`hi` `ja` `zh`) | Supported, darwin-arm64 only | Not shipped | Not supported | Not supported | `aarch64-darwin` only |
|
|
69
69
|
| macOS system voices | Supported | Not shipped | Not applicable | Not applicable | Supported on `aarch64-darwin` |
|
|
70
70
|
| Speaker diarization | Preview, darwin-arm64 only | Not supported | Not supported | Not supported | Not wired into the Nix build yet |
|
package/model-plan.json
CHANGED
|
@@ -34,6 +34,136 @@
|
|
|
34
34
|
{ "relPath": "models/vosk-ru/bert/model.onnx", "sizeBytes": 654361598 },
|
|
35
35
|
{ "relPath": "models/vosk-ru/bert/vocab.txt", "sizeBytes": 1780720 }
|
|
36
36
|
],
|
|
37
|
+
"aneEn": [
|
|
38
|
+
{ "relPath": "KokoroAlbert.mlmodelc/analytics/coremldata.bin", "sizeBytes": 243 },
|
|
39
|
+
{ "relPath": "KokoroAlbert.mlmodelc/coremldata.bin", "sizeBytes": 433 },
|
|
40
|
+
{ "relPath": "KokoroAlbert.mlmodelc/metadata.json", "sizeBytes": 2480 },
|
|
41
|
+
{ "relPath": "KokoroAlbert.mlmodelc/model.mil", "sizeBytes": 101485 },
|
|
42
|
+
{ "relPath": "KokoroAlbert.mlmodelc/weights/weight.bin", "sizeBytes": 5718848 },
|
|
43
|
+
{ "relPath": "KokoroPostAlbert.mlmodelc/analytics/coremldata.bin", "sizeBytes": 243 },
|
|
44
|
+
{ "relPath": "KokoroPostAlbert.mlmodelc/coremldata.bin", "sizeBytes": 556 },
|
|
45
|
+
{ "relPath": "KokoroPostAlbert.mlmodelc/metadata.json", "sizeBytes": 4171 },
|
|
46
|
+
{ "relPath": "KokoroPostAlbert.mlmodelc/model.mil", "sizeBytes": 60047 },
|
|
47
|
+
{ "relPath": "KokoroPostAlbert.mlmodelc/weights/weight.bin", "sizeBytes": 13806464 },
|
|
48
|
+
{ "relPath": "KokoroAlignment.mlmodelc/analytics/coremldata.bin", "sizeBytes": 243 },
|
|
49
|
+
{ "relPath": "KokoroAlignment.mlmodelc/coremldata.bin", "sizeBytes": 484 },
|
|
50
|
+
{ "relPath": "KokoroAlignment.mlmodelc/metadata.json", "sizeBytes": 3021 },
|
|
51
|
+
{ "relPath": "KokoroAlignment.mlmodelc/model.mil", "sizeBytes": 8194 },
|
|
52
|
+
{ "relPath": "KokoroAlignment.mlmodelc/weights/weight.bin", "sizeBytes": 4128 },
|
|
53
|
+
{ "relPath": "KokoroProsody.mlmodelc/analytics/coremldata.bin", "sizeBytes": 243 },
|
|
54
|
+
{ "relPath": "KokoroProsody.mlmodelc/coremldata.bin", "sizeBytes": 421 },
|
|
55
|
+
{ "relPath": "KokoroProsody.mlmodelc/metadata.json", "sizeBytes": 2721 },
|
|
56
|
+
{ "relPath": "KokoroProsody.mlmodelc/model.mil", "sizeBytes": 79705 },
|
|
57
|
+
{ "relPath": "KokoroProsody.mlmodelc/weights/weight.bin", "sizeBytes": 8454272 },
|
|
58
|
+
{ "relPath": "KokoroNoise_v2.mlmodelc/analytics/coremldata.bin", "sizeBytes": 243 },
|
|
59
|
+
{ "relPath": "KokoroNoise_v2.mlmodelc/coremldata.bin", "sizeBytes": 440 },
|
|
60
|
+
{ "relPath": "KokoroNoise_v2.mlmodelc/metadata.json", "sizeBytes": 3020 },
|
|
61
|
+
{ "relPath": "KokoroNoise_v2.mlmodelc/model.mil", "sizeBytes": 93152 },
|
|
62
|
+
{ "relPath": "KokoroNoise_v2.mlmodelc/weights/weight.bin", "sizeBytes": 4580160 },
|
|
63
|
+
{ "relPath": "KokoroVocoder.mlmodelc/analytics/coremldata.bin", "sizeBytes": 243 },
|
|
64
|
+
{ "relPath": "KokoroVocoder.mlmodelc/coremldata.bin", "sizeBytes": 626 },
|
|
65
|
+
{ "relPath": "KokoroVocoder.mlmodelc/metadata.json", "sizeBytes": 4331 },
|
|
66
|
+
{ "relPath": "KokoroVocoder.mlmodelc/model.mil", "sizeBytes": 309000 },
|
|
67
|
+
{ "relPath": "KokoroVocoder.mlmodelc/weights/weight.bin", "sizeBytes": 48889920 },
|
|
68
|
+
{ "relPath": "KokoroTail_v2.mlmodelc/analytics/coremldata.bin", "sizeBytes": 243 },
|
|
69
|
+
{ "relPath": "KokoroTail_v2.mlmodelc/coremldata.bin", "sizeBytes": 392 },
|
|
70
|
+
{ "relPath": "KokoroTail_v2.mlmodelc/metadata.json", "sizeBytes": 1872 },
|
|
71
|
+
{ "relPath": "KokoroTail_v2.mlmodelc/model.mil", "sizeBytes": 7014 },
|
|
72
|
+
{ "relPath": "KokoroTail_v2.mlmodelc/weights/weight.bin", "sizeBytes": 81088 },
|
|
73
|
+
{ "relPath": "vocab.json", "sizeBytes": 1416 },
|
|
74
|
+
{ "relPath": "af_heart.bin", "sizeBytes": 522240 }
|
|
75
|
+
],
|
|
76
|
+
"kokoroG2p": [
|
|
77
|
+
{ "relPath": "G2PEncoder.mlmodelc/analytics/coremldata.bin", "sizeBytes": 243 },
|
|
78
|
+
{ "relPath": "G2PEncoder.mlmodelc/coremldata.bin", "sizeBytes": 398 },
|
|
79
|
+
{ "relPath": "G2PEncoder.mlmodelc/metadata.json", "sizeBytes": 2034 },
|
|
80
|
+
{ "relPath": "G2PEncoder.mlmodelc/model.mil", "sizeBytes": 20392 },
|
|
81
|
+
{ "relPath": "G2PEncoder.mlmodelc/weights/weight.bin", "sizeBytes": 694592 },
|
|
82
|
+
{ "relPath": "G2PDecoder.mlmodelc/analytics/coremldata.bin", "sizeBytes": 243 },
|
|
83
|
+
{ "relPath": "G2PDecoder.mlmodelc/coremldata.bin", "sizeBytes": 545 },
|
|
84
|
+
{ "relPath": "G2PDecoder.mlmodelc/metadata.json", "sizeBytes": 3064 },
|
|
85
|
+
{ "relPath": "G2PDecoder.mlmodelc/model.mil", "sizeBytes": 19737 },
|
|
86
|
+
{ "relPath": "G2PDecoder.mlmodelc/weights/weight.bin", "sizeBytes": 828030 },
|
|
87
|
+
{ "relPath": "g2p_vocab.json", "sizeBytes": 1665 },
|
|
88
|
+
{ "relPath": "us_lexicon_cache.json", "sizeBytes": 10444631 }
|
|
89
|
+
],
|
|
90
|
+
"aneVoices": [
|
|
91
|
+
{ "relPath": "af_alloy.bin", "sizeBytes": 522240 },
|
|
92
|
+
{ "relPath": "af_aoede.bin", "sizeBytes": 522240 },
|
|
93
|
+
{ "relPath": "af_bella.bin", "sizeBytes": 522240 },
|
|
94
|
+
{ "relPath": "af_jessica.bin", "sizeBytes": 522240 },
|
|
95
|
+
{ "relPath": "af_kore.bin", "sizeBytes": 522240 },
|
|
96
|
+
{ "relPath": "af_nicole.bin", "sizeBytes": 522240 },
|
|
97
|
+
{ "relPath": "af_nova.bin", "sizeBytes": 522240 },
|
|
98
|
+
{ "relPath": "af_river.bin", "sizeBytes": 522240 },
|
|
99
|
+
{ "relPath": "af_sarah.bin", "sizeBytes": 522240 },
|
|
100
|
+
{ "relPath": "af_sky.bin", "sizeBytes": 522240 },
|
|
101
|
+
{ "relPath": "am_adam.bin", "sizeBytes": 522240 },
|
|
102
|
+
{ "relPath": "am_echo.bin", "sizeBytes": 522240 },
|
|
103
|
+
{ "relPath": "am_eric.bin", "sizeBytes": 522240 },
|
|
104
|
+
{ "relPath": "am_fenrir.bin", "sizeBytes": 522240 },
|
|
105
|
+
{ "relPath": "am_liam.bin", "sizeBytes": 522240 },
|
|
106
|
+
{ "relPath": "am_michael.bin", "sizeBytes": 522240 },
|
|
107
|
+
{ "relPath": "am_onyx.bin", "sizeBytes": 522240 },
|
|
108
|
+
{ "relPath": "am_puck.bin", "sizeBytes": 522240 },
|
|
109
|
+
{ "relPath": "am_santa.bin", "sizeBytes": 522240 },
|
|
110
|
+
{ "relPath": "bm_lewis.bin", "sizeBytes": 522240 },
|
|
111
|
+
{ "relPath": "em_alex.bin", "sizeBytes": 522240 },
|
|
112
|
+
{ "relPath": "ff_siwis.bin", "sizeBytes": 522240 },
|
|
113
|
+
{ "relPath": "hm_omega.bin", "sizeBytes": 522240 },
|
|
114
|
+
{ "relPath": "im_nicola.bin", "sizeBytes": 522240 },
|
|
115
|
+
{ "relPath": "jm_kumo.bin", "sizeBytes": 522240 },
|
|
116
|
+
{ "relPath": "pm_alex.bin", "sizeBytes": 522240 }
|
|
117
|
+
],
|
|
118
|
+
"aneZh": [
|
|
119
|
+
{ "relPath": "KokoroAlbert.mlmodelc/analytics/coremldata.bin", "sizeBytes": 243 },
|
|
120
|
+
{ "relPath": "KokoroAlbert.mlmodelc/coremldata.bin", "sizeBytes": 433 },
|
|
121
|
+
{ "relPath": "KokoroAlbert.mlmodelc/metadata.json", "sizeBytes": 2480 },
|
|
122
|
+
{ "relPath": "KokoroAlbert.mlmodelc/model.mil", "sizeBytes": 101485 },
|
|
123
|
+
{ "relPath": "KokoroAlbert.mlmodelc/weights/weight.bin", "sizeBytes": 5718848 },
|
|
124
|
+
{ "relPath": "KokoroPostAlbert.mlmodelc/analytics/coremldata.bin", "sizeBytes": 243 },
|
|
125
|
+
{ "relPath": "KokoroPostAlbert.mlmodelc/coremldata.bin", "sizeBytes": 556 },
|
|
126
|
+
{ "relPath": "KokoroPostAlbert.mlmodelc/metadata.json", "sizeBytes": 4171 },
|
|
127
|
+
{ "relPath": "KokoroPostAlbert.mlmodelc/model.mil", "sizeBytes": 60047 },
|
|
128
|
+
{ "relPath": "KokoroPostAlbert.mlmodelc/weights/weight.bin", "sizeBytes": 13806464 },
|
|
129
|
+
{ "relPath": "KokoroAlignment.mlmodelc/analytics/coremldata.bin", "sizeBytes": 243 },
|
|
130
|
+
{ "relPath": "KokoroAlignment.mlmodelc/coremldata.bin", "sizeBytes": 484 },
|
|
131
|
+
{ "relPath": "KokoroAlignment.mlmodelc/metadata.json", "sizeBytes": 3021 },
|
|
132
|
+
{ "relPath": "KokoroAlignment.mlmodelc/model.mil", "sizeBytes": 8194 },
|
|
133
|
+
{ "relPath": "KokoroAlignment.mlmodelc/weights/weight.bin", "sizeBytes": 4128 },
|
|
134
|
+
{ "relPath": "KokoroProsody.mlmodelc/analytics/coremldata.bin", "sizeBytes": 243 },
|
|
135
|
+
{ "relPath": "KokoroProsody.mlmodelc/coremldata.bin", "sizeBytes": 422 },
|
|
136
|
+
{ "relPath": "KokoroProsody.mlmodelc/metadata.json", "sizeBytes": 2721 },
|
|
137
|
+
{ "relPath": "KokoroProsody.mlmodelc/model.mil", "sizeBytes": 79705 },
|
|
138
|
+
{ "relPath": "KokoroProsody.mlmodelc/weights/weight.bin", "sizeBytes": 8454272 },
|
|
139
|
+
{ "relPath": "KokoroNoise_v2.mlmodelc/analytics/coremldata.bin", "sizeBytes": 243 },
|
|
140
|
+
{ "relPath": "KokoroNoise_v2.mlmodelc/coremldata.bin", "sizeBytes": 440 },
|
|
141
|
+
{ "relPath": "KokoroNoise_v2.mlmodelc/metadata.json", "sizeBytes": 3091 },
|
|
142
|
+
{ "relPath": "KokoroNoise_v2.mlmodelc/model.mil", "sizeBytes": 93152 },
|
|
143
|
+
{ "relPath": "KokoroNoise_v2.mlmodelc/weights/weight.bin", "sizeBytes": 4580160 },
|
|
144
|
+
{ "relPath": "KokoroVocoder.mlmodelc/analytics/coremldata.bin", "sizeBytes": 243 },
|
|
145
|
+
{ "relPath": "KokoroVocoder.mlmodelc/coremldata.bin", "sizeBytes": 627 },
|
|
146
|
+
{ "relPath": "KokoroVocoder.mlmodelc/metadata.json", "sizeBytes": 4331 },
|
|
147
|
+
{ "relPath": "KokoroVocoder.mlmodelc/model.mil", "sizeBytes": 308997 },
|
|
148
|
+
{ "relPath": "KokoroVocoder.mlmodelc/weights/weight.bin", "sizeBytes": 48889920 },
|
|
149
|
+
{ "relPath": "KokoroTail_v2.mlmodelc/analytics/coremldata.bin", "sizeBytes": 243 },
|
|
150
|
+
{ "relPath": "KokoroTail_v2.mlmodelc/coremldata.bin", "sizeBytes": 392 },
|
|
151
|
+
{ "relPath": "KokoroTail_v2.mlmodelc/metadata.json", "sizeBytes": 1872 },
|
|
152
|
+
{ "relPath": "KokoroTail_v2.mlmodelc/model.mil", "sizeBytes": 7014 },
|
|
153
|
+
{ "relPath": "KokoroTail_v2.mlmodelc/weights/weight.bin", "sizeBytes": 81088 },
|
|
154
|
+
{ "relPath": "vocab.json", "sizeBytes": 1796 },
|
|
155
|
+
{ "relPath": "voices/zf_001.bin", "sizeBytes": 522240 },
|
|
156
|
+
{ "relPath": "voices/zm_050.bin", "sizeBytes": 522240 },
|
|
157
|
+
{ "relPath": "g2pw/g2pw.mlmodelc/analytics/coremldata.bin", "sizeBytes": 243 },
|
|
158
|
+
{ "relPath": "g2pw/g2pw.mlmodelc/coremldata.bin", "sizeBytes": 515 },
|
|
159
|
+
{ "relPath": "g2pw/g2pw.mlmodelc/metadata.json", "sizeBytes": 3717 },
|
|
160
|
+
{ "relPath": "g2pw/g2pw.mlmodelc/model.mil", "sizeBytes": 245446 },
|
|
161
|
+
{ "relPath": "g2pw/g2pw.mlmodelc/weights/weight.bin", "sizeBytes": 159294962 }
|
|
162
|
+
],
|
|
163
|
+
"aneZhG2p": [
|
|
164
|
+
{ "relPath": "g2p/pinyin_phrases.bin", "sizeBytes": 9552083 },
|
|
165
|
+
{ "relPath": "g2p/pinyin_single.bin", "sizeBytes": 480169 }
|
|
166
|
+
],
|
|
37
167
|
"diarize": [
|
|
38
168
|
{ "relPath": "models/diarize/SortformerNvidiaLow_v2.mlpackage/Manifest.json", "sizeBytes": 617 },
|
|
39
169
|
{ "relPath": "models/diarize/SortformerNvidiaLow_v2.mlpackage/Data/com.apple.CoreML/model.mlmodel", "sizeBytes": 856521 },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@drakulavich/kesha-voice-kit",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
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": {
|
|
@@ -39,9 +39,6 @@
|
|
|
39
39
|
"openclawVersion": "2026.5.27"
|
|
40
40
|
}
|
|
41
41
|
},
|
|
42
|
-
"keshaEngine": {
|
|
43
|
-
"version": "1.25.0"
|
|
44
|
-
},
|
|
45
42
|
"scripts": {
|
|
46
43
|
"test": "bun test --timeout 30000",
|
|
47
44
|
"test:watch": "bun test --timeout 30000 --watch",
|
|
@@ -61,7 +58,7 @@
|
|
|
61
58
|
"check:release-manifest": "bun .github/scripts/release-manifest.mjs --check",
|
|
62
59
|
"check:versions": "bun .github/scripts/check-versions.ts",
|
|
63
60
|
"check:engine-targets": "bun .github/scripts/check-engine-targets.ts",
|
|
64
|
-
"
|
|
61
|
+
"check:file-sizes": "bun .github/scripts/check-file-sizes.ts"
|
|
65
62
|
},
|
|
66
63
|
"keywords": [
|
|
67
64
|
"asr",
|
|
@@ -89,8 +86,6 @@
|
|
|
89
86
|
"bun": ">=1.3.0"
|
|
90
87
|
},
|
|
91
88
|
"devDependencies": {
|
|
92
|
-
"@hughescr/stryker-bun-runner": "^1.3.8",
|
|
93
|
-
"@stryker-mutator/core": "^9.6.1",
|
|
94
89
|
"@types/bun": "latest",
|
|
95
90
|
"fast-xml-parser": "^5.8.0",
|
|
96
91
|
"typescript": "^7.0.2",
|
|
@@ -111,5 +106,24 @@
|
|
|
111
106
|
"fast-uri": "^3.1.5",
|
|
112
107
|
"ip-address": "^10.3.1",
|
|
113
108
|
"smol-toml": "1.7.2"
|
|
109
|
+
},
|
|
110
|
+
"kesha": {
|
|
111
|
+
"engine": {
|
|
112
|
+
"version": "2.0.0",
|
|
113
|
+
"sha256": {
|
|
114
|
+
"kesha-engine-darwin-arm64": "2d75bc773fb81917a0dd996124c0e18eea3b15ecc8f04fe274d41c68bda9276c",
|
|
115
|
+
"kesha-engine-linux-x64": "09082a1f3eb07d8f8051806a057669f098856d84541f087a1e614ca80d8235b2",
|
|
116
|
+
"kesha-engine-windows-x64.exe": "de86f350bfd3d076cf339d99d892605b1a37a99085e8ff4c8ef87057618193f3",
|
|
117
|
+
"say-avspeech-darwin-arm64": "a3aa5b75ddff68a48f1158e838310b37cac72ef86841d9cfcdf1ee233cb7451a",
|
|
118
|
+
"kesha-textlang-darwin-arm64": "e45e2822248b14dd72aa79e2def8fa6b9c07c97423f11c841508769225d72da4"
|
|
119
|
+
},
|
|
120
|
+
"size": {
|
|
121
|
+
"kesha-engine-darwin-arm64": 73623152,
|
|
122
|
+
"kesha-engine-linux-x64": 67392296,
|
|
123
|
+
"kesha-engine-windows-x64.exe": 66550272,
|
|
124
|
+
"say-avspeech-darwin-arm64": 64208,
|
|
125
|
+
"kesha-textlang-darwin-arm64": 57648
|
|
126
|
+
}
|
|
127
|
+
}
|
|
114
128
|
}
|
|
115
129
|
}
|
package/src/cache-layout.ts
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { existsSync, realpathSync, statSync } from "fs";
|
|
2
|
+
import { dirname, join, resolve, sep } from "path";
|
|
3
|
+
import { dirSizeBytes } from "./diagnostic-paths";
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
/** The sidecar executables the engine spawns from its own directory. */
|
|
6
|
+
const SIDECAR_FILES = ["say-avspeech", "kesha-textlang"] as const;
|
|
7
|
+
|
|
8
|
+
export interface CacheComponentSize {
|
|
4
9
|
label: string;
|
|
5
10
|
path: string;
|
|
11
|
+
exists: boolean;
|
|
12
|
+
sizeBytes: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface EngineFootprint {
|
|
16
|
+
path: string;
|
|
17
|
+
members: string[];
|
|
18
|
+
sizeOf: (path: string) => number;
|
|
6
19
|
}
|
|
7
20
|
|
|
8
21
|
/**
|
|
@@ -18,6 +31,56 @@ export function isInsideDir(child: string, parent: string): boolean {
|
|
|
18
31
|
return resolvedChild === resolvedParent || resolvedChild.startsWith(`${resolvedParent}${sep}`);
|
|
19
32
|
}
|
|
20
33
|
|
|
34
|
+
/**
|
|
35
|
+
* What the Engine row owns (#1313). A managed install owns all of `<cache>/engine`. An
|
|
36
|
+
* overridden binary (`KESHA_ENGINE_BIN`, a Nix store path) shares its directory with software
|
|
37
|
+
* Kesha does not own, so only the binary and the sidecars beside it are counted — its
|
|
38
|
+
* grandparent may be `/usr/local`.
|
|
39
|
+
*/
|
|
40
|
+
function engineFootprint(binPath: string, cacheRoot: string): EngineFootprint {
|
|
41
|
+
const managedRoot = join(cacheRoot, "engine");
|
|
42
|
+
if (resolve(dirname(binPath)) === resolve(managedRoot, "bin")) {
|
|
43
|
+
return { path: managedRoot, members: [managedRoot], sizeOf: dirSizeBytes };
|
|
44
|
+
}
|
|
45
|
+
const binDir = dirname(binPath);
|
|
46
|
+
return {
|
|
47
|
+
path: binPath,
|
|
48
|
+
members: [binPath, ...SIDECAR_FILES.map((f) => join(binDir, f))],
|
|
49
|
+
sizeOf: regularFileBytes,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** A sidecar name that is (or links to) a directory is not ours to walk. */
|
|
54
|
+
function regularFileBytes(path: string): number {
|
|
55
|
+
try {
|
|
56
|
+
const st = statSync(path);
|
|
57
|
+
return st.isFile() ? st.size : 0;
|
|
58
|
+
} catch {
|
|
59
|
+
return 0;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function footprintBytes(engine: EngineFootprint, members: string[]): number {
|
|
64
|
+
return members.reduce((n, p) => n + engine.sizeOf(p), 0);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** The cache plus whatever engine bytes live outside it, each counted once (#790). */
|
|
68
|
+
export function cacheTotalBytes(cacheRoot: string, binPath: string): number {
|
|
69
|
+
const engine = engineFootprint(binPath, cacheRoot);
|
|
70
|
+
const realCache = existingPath(cacheRoot);
|
|
71
|
+
const outside = engine.members.filter((m) => !isInsideDir(existingPath(m), realCache));
|
|
72
|
+
return dirSizeBytes(cacheRoot) + footprintBytes(engine, outside);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Compared by target, so an outside symlink into the cache is not counted twice. */
|
|
76
|
+
function existingPath(path: string): string {
|
|
77
|
+
try {
|
|
78
|
+
return realpathSync(path);
|
|
79
|
+
} catch {
|
|
80
|
+
return resolve(path);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
21
84
|
/**
|
|
22
85
|
* The Kesha-managed directories both `kesha status --disk` and `kesha doctor` report, in one
|
|
23
86
|
* place so a new model dir cannot be added to only one of them. The two ASR rows are mutually
|
|
@@ -26,13 +89,19 @@ export function isInsideDir(child: string, parent: string): boolean {
|
|
|
26
89
|
* Whatever stayed in FluidAudio's own trees is outside this cache and is reported separately
|
|
27
90
|
* by `fluidExternalRoots`.
|
|
28
91
|
*/
|
|
29
|
-
export function
|
|
92
|
+
export function cacheComponents(
|
|
30
93
|
cacheRoot: string,
|
|
31
|
-
|
|
94
|
+
binPath: string,
|
|
32
95
|
coreml: boolean,
|
|
33
|
-
):
|
|
34
|
-
|
|
35
|
-
|
|
96
|
+
): CacheComponentSize[] {
|
|
97
|
+
const engine = engineFootprint(binPath, cacheRoot);
|
|
98
|
+
const engineRow = {
|
|
99
|
+
label: "Engine",
|
|
100
|
+
path: engine.path,
|
|
101
|
+
exists: existsSync(engine.path),
|
|
102
|
+
sizeBytes: footprintBytes(engine, engine.members),
|
|
103
|
+
};
|
|
104
|
+
const modelRows = [
|
|
36
105
|
...(coreml
|
|
37
106
|
? [{ label: "FluidAudio (in cache)", path: join(cacheRoot, "fluidaudio") }]
|
|
38
107
|
: [{ label: "ASR (Parakeet)", path: join(cacheRoot, "models/parakeet-tdt-v3") }]),
|
|
@@ -40,5 +109,6 @@ export function cacheComponentPaths(
|
|
|
40
109
|
{ label: "VAD (Silero)", path: join(cacheRoot, "models/silero-vad") },
|
|
41
110
|
{ label: "TTS (Kokoro)", path: join(cacheRoot, "models/kokoro-82m") },
|
|
42
111
|
{ label: "TTS (Vosk)", path: join(cacheRoot, "models/vosk-ru") },
|
|
43
|
-
];
|
|
112
|
+
].map((row) => ({ ...row, exists: existsSync(row.path), sizeBytes: dirSizeBytes(row.path) }));
|
|
113
|
+
return [engineRow, ...modelRows];
|
|
44
114
|
}
|
package/src/cli/action-result.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { log } from "../log";
|
|
2
|
+
import { renderInvalidArg } from "./options";
|
|
2
3
|
|
|
3
4
|
export type ActionMessage = { level: "success" | "info" | "warn"; text: string };
|
|
4
5
|
|
|
@@ -9,7 +10,7 @@ export type ActionResult =
|
|
|
9
10
|
/** Renders an action's messages, writing `stdout` verbatim; a rejected action exits 2. */
|
|
10
11
|
export function emitActionResult(result: ActionResult): void {
|
|
11
12
|
if (!result.ok) {
|
|
12
|
-
log.error(result.error);
|
|
13
|
+
log.error(renderInvalidArg(result.error));
|
|
13
14
|
if (result.hint) log.warn(result.hint);
|
|
14
15
|
process.exit(2);
|
|
15
16
|
}
|
|
@@ -50,9 +50,8 @@ export async function runCommandSession(
|
|
|
50
50
|
setEngineDebugSink((event) =>
|
|
51
51
|
session.diagnosticLog.event("engine.debug", {
|
|
52
52
|
t_ms: event.t_ms,
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
fields: event.fields === undefined ? null : JSON.stringify(event.fields),
|
|
53
|
+
engineEvent: event.event ?? null,
|
|
54
|
+
...engineDebugFields(event.fields),
|
|
56
55
|
}),
|
|
57
56
|
);
|
|
58
57
|
let outcome: CommandOutcome;
|
|
@@ -74,6 +73,18 @@ export async function runCommandSession(
|
|
|
74
73
|
return outcome;
|
|
75
74
|
}
|
|
76
75
|
|
|
76
|
+
// The message is prose and `event` is a reserved log field, so only the engine's typed fields ride along (Exploratory S9-F5).
|
|
77
|
+
function engineDebugFields(fields: unknown): DiagnosticLogFields {
|
|
78
|
+
const out: DiagnosticLogFields = {};
|
|
79
|
+
if (!fields || typeof fields !== "object" || Array.isArray(fields)) return out;
|
|
80
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
81
|
+
if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
82
|
+
out[key] = value;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return out;
|
|
86
|
+
}
|
|
87
|
+
|
|
77
88
|
function closeSessionQuietly(
|
|
78
89
|
session: CommandSession,
|
|
79
90
|
command: StatsCommandName,
|
package/src/cli/completions.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { defineCommand } from "citty";
|
|
2
2
|
import { log } from "../log";
|
|
3
|
+
import { renderInvalidArg } from "./options";
|
|
3
4
|
// Inlined because import.meta.url escapes the embedded filesystem in the compiled .deb/.rpm binary (#914).
|
|
4
5
|
import bashCompletions from "../../completions/kesha.bash" with { type: "text" };
|
|
5
6
|
import zshCompletions from "../../completions/kesha.zsh" with { type: "text" };
|
|
@@ -29,14 +30,15 @@ export const completionsCommand = defineCommand({
|
|
|
29
30
|
args: {
|
|
30
31
|
shell: {
|
|
31
32
|
type: "positional",
|
|
32
|
-
required:
|
|
33
|
+
required: false,
|
|
33
34
|
description: "Shell: bash | zsh | fish",
|
|
34
35
|
},
|
|
35
36
|
},
|
|
36
37
|
async run({ args }: { args: CompletionsCommandArgs }) {
|
|
37
38
|
const shell = args.shell;
|
|
38
39
|
if (!shell || !isShell(shell)) {
|
|
39
|
-
log.error(
|
|
40
|
+
log.error(renderInvalidArg(shell ? `unknown shell '${shell}' (bash, zsh or fish)` : "missing shell (bash, zsh or fish)"));
|
|
41
|
+
process.stderr.write("usage: kesha completions <bash|zsh|fish>\n");
|
|
40
42
|
process.exit(2);
|
|
41
43
|
}
|
|
42
44
|
process.stdout.write(SHELL_SCRIPTS[shell]);
|
package/src/cli/dispatch.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { runMain, type CommandDef } from "citty";
|
|
1
|
+
import { runMain, type ArgsDef, type CommandDef } from "citty";
|
|
2
2
|
import { existsSync } from "fs";
|
|
3
3
|
import { log } from "../log";
|
|
4
4
|
import { guardStdoutWrites } from "../stdout-pipe";
|
|
5
5
|
import { suggestCommand } from "../suggest-command";
|
|
6
6
|
import { applyCliContext, resolveCliContext } from "./context";
|
|
7
|
+
import { rejectUnknownOptions, renderInvalidArg } from "./options";
|
|
7
8
|
|
|
8
9
|
// Lazy loaders so a cold CLI spawn transpiles only the invoked command, not the whole graph (#568).
|
|
9
10
|
// `CommandDef<any>`: citty's generic is invariant in the arg shape and each command has its own schema.
|
|
@@ -89,18 +90,42 @@ export function unknownCommandMessages(token: string, subcommandKeys: string[]):
|
|
|
89
90
|
return { errorLine: `unknown command '${token}'`, warnLines };
|
|
90
91
|
}
|
|
91
92
|
|
|
93
|
+
async function resolveArgsDef(command: CommandDef<any>): Promise<ArgsDef> {
|
|
94
|
+
const args = command.args;
|
|
95
|
+
return (typeof args === "function" ? await args() : await args) ?? {};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** `kesha --json record --out j.wav` is `record --json --out j.wav`: flag-shaped tokens before a subcommand name move behind it (S3-F3). */
|
|
99
|
+
export function hoistLeadingFlags(rawArgs: string[], subcommandKeys: string[]): string[] {
|
|
100
|
+
let i = 0;
|
|
101
|
+
while (i < rawArgs.length && rawArgs[i]!.startsWith("-") && rawArgs[i] !== "--") i++;
|
|
102
|
+
const name = rawArgs[i];
|
|
103
|
+
if (i === 0 || name === undefined || !subcommandKeys.includes(name)) return rawArgs;
|
|
104
|
+
return [name, ...rawArgs.slice(0, i), ...rawArgs.slice(i + 1)];
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** A subcommand name that a valued leading flag hid from hoisting, unless a file of that name exists. */
|
|
108
|
+
function misplacedSubcommand(rawArgs: string[], subcommandKeys: string[]): string | undefined {
|
|
109
|
+
const end = rawArgs.indexOf("--");
|
|
110
|
+
return rawArgs.slice(0, end === -1 ? undefined : end).find((arg) => subcommandKeys.includes(arg) && !existsSync(arg));
|
|
111
|
+
}
|
|
112
|
+
|
|
92
113
|
export async function runCli(argv = process.argv.slice(2)): Promise<void> {
|
|
93
114
|
guardStdoutWrites();
|
|
94
115
|
const context = resolveCliContext(argv);
|
|
95
116
|
applyCliContext(context);
|
|
96
117
|
|
|
97
|
-
const [firstArg, ...restArgs] = context.rawArgs;
|
|
98
118
|
const subcommandKeys = SUBCOMMAND_NAMES;
|
|
119
|
+
const rawArgs = hoistLeadingFlags(context.rawArgs, subcommandKeys);
|
|
120
|
+
const [firstArg, ...restArgs] = rawArgs;
|
|
99
121
|
|
|
100
122
|
switch (classifyFirstArg(firstArg, subcommandKeys)) {
|
|
101
|
-
case "subcommand":
|
|
102
|
-
|
|
123
|
+
case "subcommand": {
|
|
124
|
+
const command = await SUBCOMMANDS[firstArg!]!();
|
|
125
|
+
rejectUnknownOptions(restArgs, await resolveArgsDef(command));
|
|
126
|
+
await runMain(command, { rawArgs: restArgs });
|
|
103
127
|
return;
|
|
128
|
+
}
|
|
104
129
|
|
|
105
130
|
case "unknown": {
|
|
106
131
|
// Extensionless existing files are valid transcription inputs; bare non-path tokens are likely command typos.
|
|
@@ -112,8 +137,15 @@ export async function runCli(argv = process.argv.slice(2)): Promise<void> {
|
|
|
112
137
|
}
|
|
113
138
|
|
|
114
139
|
default: {
|
|
140
|
+
const misplaced = misplacedSubcommand(rawArgs, subcommandKeys);
|
|
141
|
+
if (misplaced !== undefined) {
|
|
142
|
+
log.error(renderInvalidArg(`put global flags after the subcommand: kesha ${misplaced} ...`));
|
|
143
|
+
process.exit(2);
|
|
144
|
+
}
|
|
115
145
|
const { createMainCommand } = await import("./main");
|
|
116
|
-
|
|
146
|
+
const command = createMainCommand(context);
|
|
147
|
+
rejectUnknownOptions(rawArgs, await resolveArgsDef(command));
|
|
148
|
+
await runMain(command, { rawArgs });
|
|
117
149
|
}
|
|
118
150
|
}
|
|
119
151
|
}
|