@drakulavich/kesha-voice-kit 1.18.6 → 1.19.0-beta.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 +44 -0
- package/SKILL.md +33 -6
- package/completions/kesha.bash +2 -1
- package/completions/kesha.fish +11 -0
- package/completions/kesha.zsh +13 -0
- package/man/kesha.1 +29 -1
- package/package.json +2 -2
- package/src/cli/dispatch.ts +7 -0
- package/src/cli/init.ts +273 -0
- package/src/cli/install.ts +19 -5
- package/src/cli/main.ts +1 -0
- package/src/cli.ts +11 -0
- package/src/doctor.ts +7 -8
- package/src/engine-install.ts +45 -16
- package/src/install-plan.ts +5 -7
- package/src/shell-artifacts.ts +2 -0
package/README.md
CHANGED
|
@@ -236,6 +236,50 @@ Compared against Whisper `large-v3-turbo` — all engines auto-detect language.
|
|
|
236
236
|
|
|
237
237
|
See [BENCHMARK.md](BENCHMARK.md) for the full per-file breakdown (Russian + English).
|
|
238
238
|
|
|
239
|
+
## Architecture
|
|
240
|
+
|
|
241
|
+
```text
|
|
242
|
+
Users / agents
|
|
243
|
+
shell | scripts | OpenClaw | Hermes | Raycast | @drakulavich/kesha-voice-kit/core
|
|
244
|
+
|
|
|
245
|
+
v
|
|
246
|
+
+------------------------------- Kesha CLI -------------------------------+
|
|
247
|
+
| Bun + TypeScript wrapper |
|
|
248
|
+
| - parses commands and formats stdout/stderr |
|
|
249
|
+
| - installs pinned engine/model assets only when explicitly requested |
|
|
250
|
+
| - keeps cache, support bundles, and local Stats in the CLI |
|
|
251
|
+
+-----------------------------------+-------------------------------------+
|
|
252
|
+
|
|
|
253
|
+
| spawns one local process
|
|
254
|
+
v
|
|
255
|
+
+----------------------------- kesha-engine ------------------------------+
|
|
256
|
+
| Rust binary, no cloud calls, no Python, no ffmpeg |
|
|
257
|
+
| |
|
|
258
|
+
| Audio input Text input Diagnostics |
|
|
259
|
+
| WAV/MP3/OGG/FLAC/AAC/M4A plain text / SSML status/support |
|
|
260
|
+
| | | | |
|
|
261
|
+
| v v v |
|
|
262
|
+
| Symphonia decode TTS preprocessing runtime probes |
|
|
263
|
+
| | | |
|
|
264
|
+
| +--> optional VAD +--> voice routing |
|
|
265
|
+
| | + diarization | Kokoro / Vosk / macOS voices |
|
|
266
|
+
| | | |
|
|
267
|
+
| +--> audio lang ID +--> speech synthesis |
|
|
268
|
+
| | SpeechBrain ONNX |
|
|
269
|
+
| | |
|
|
270
|
+
| +--> ASR backend |
|
|
271
|
+
| CoreML on Apple Silicon |
|
|
272
|
+
| ONNX Runtime on Linux/Windows/fallback |
|
|
273
|
+
+-----------------------------------+-------------------------------------+
|
|
274
|
+
|
|
|
275
|
+
v
|
|
276
|
+
transcript | JSON/TOON | WAV | local diagnostics
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Cache boundary: `kesha install` and opt-in feature installs populate the local
|
|
280
|
+
cache; ordinary transcription and speech commands fail fast if required assets
|
|
281
|
+
are missing.
|
|
282
|
+
|
|
239
283
|
## What's Inside
|
|
240
284
|
|
|
241
285
|
| Model | Task | Size | Source |
|
package/SKILL.md
CHANGED
|
@@ -22,8 +22,8 @@ Local voice toolkit: transcribe voice messages to text, synthesize speech, detec
|
|
|
22
22
|
## When to use
|
|
23
23
|
|
|
24
24
|
- **Voice memo arrived** (Telegram, WhatsApp, Slack, Signal .ogg/.opus/.m4a): transcribe with `kesha --json <path>` and branch on the detected language.
|
|
25
|
-
- **Need to
|
|
26
|
-
- **Need
|
|
25
|
+
- **Need to send a voice note (Telegram, WhatsApp, Signal, Discord)**: synthesize directly into messenger-native OGG/Opus with `kesha say --format ogg-opus --out reply.ogg "<text>"`. Default is mono 24 kHz @ 32 kbps - what Telegram `sendVoice` expects. No WAV redirect and no `ffmpeg` round-trip.
|
|
26
|
+
- **Need local file playback/debug output**: WAV is still available with `kesha say --out reply.wav "<text>"`, but do not use WAV for Telegram voice replies. Auto-routes by detected language (Kokoro-82M for English, Vosk-TTS for Russian). On darwin-arm64, English Kokoro uses FluidAudio CoreML instead of ONNX. For other languages and ~180 more voices use `--voice macos-*` on macOS (zero model download).
|
|
27
27
|
- **Need to detect what language a file is in** before choosing a pipeline: `kesha --json audio.ogg` returns both audio-based and text-based language detection with confidence scores.
|
|
28
28
|
|
|
29
29
|
## OpenClaw plugin setup
|
|
@@ -79,6 +79,33 @@ openclaw config get tools.media.audio.echoFormat
|
|
|
79
79
|
|
|
80
80
|
Do not rely on `openclaw.plugin.json` to patch `tools.media.audio.models`; OpenClaw ignores non-schema fields such as `configPatch`. Keep the CLI route in user config.
|
|
81
81
|
|
|
82
|
+
For OpenClaw TTS replies, route the local TTS provider to Kesha OGG/Opus output. This is the Telegram-safe path:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
openclaw config patch --stdin <<'JSON5'
|
|
86
|
+
{
|
|
87
|
+
messages: {
|
|
88
|
+
tts: {
|
|
89
|
+
auto: "always",
|
|
90
|
+
provider: "tts-local-cli",
|
|
91
|
+
providers: {
|
|
92
|
+
"tts-local-cli": {
|
|
93
|
+
command: "kesha",
|
|
94
|
+
args: ["say", "--format", "ogg-opus", "--out", "{{OutputPath}}", "{{Text}}"],
|
|
95
|
+
outputFormat: "opus",
|
|
96
|
+
timeoutMs: 120000,
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
}
|
|
102
|
+
JSON5
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
When invoking Kesha manually from an OpenClaw flow, write OGG/Opus into an OpenClaw-owned temp path, for example `kesha say --format ogg-opus --out /tmp/openclaw/reply.ogg "<text>"`, after ensuring the directory exists. The configured `tts-local-cli` provider should use OpenClaw's `{{OutputPath}}` placeholder instead of a hardcoded path.
|
|
106
|
+
|
|
107
|
+
Do not configure OpenClaw Telegram TTS as `kesha say "<text>" > reply.wav`; that creates a WAV file and will not render as a native Telegram voice note.
|
|
108
|
+
|
|
82
109
|
## STT: transcribe audio
|
|
83
110
|
|
|
84
111
|
```bash
|
|
@@ -135,14 +162,14 @@ kesha say --voice macos-de-DE "Guten Tag" > de.wav # any macOS system voice —
|
|
|
135
162
|
kesha say --list-voices # Kokoro + Vosk-TTS + ~180 macos-* voices
|
|
136
163
|
```
|
|
137
164
|
|
|
138
|
-
Output: WAV mono float32 by default. `--out <path>` writes to a file instead of stdout.
|
|
165
|
+
Output: WAV mono float32 by default. `--out <path>` writes to a file instead of stdout. For Telegram/OpenClaw replies, prefer `--format ogg-opus --out reply.ogg` or the OpenClaw-provided `{{OutputPath}}`.
|
|
139
166
|
|
|
140
167
|
**Voice notes (Telegram / WhatsApp / Signal / Discord):** add `--format ogg-opus` to emit OGG/Opus directly — the format messenger APIs render as a native voice message:
|
|
141
168
|
|
|
142
169
|
```bash
|
|
143
|
-
kesha say
|
|
144
|
-
kesha say
|
|
145
|
-
kesha say
|
|
170
|
+
kesha say --format ogg-opus --out reply.ogg "Hello there" # 24 kHz @ 32 kbps mono - Telegram-grade
|
|
171
|
+
kesha say --voice ru-vosk-m02 --format ogg-opus --out reply.ogg "Привет" # Russian voice note
|
|
172
|
+
kesha say --format ogg-opus --bitrate 16000 --out tiny.ogg "Hi" # tinier file, intelligible but lossy
|
|
146
173
|
```
|
|
147
174
|
|
|
148
175
|
Format is also inferred from `--out` extension (`.ogg` / `.opus` / `.oga` → OGG/Opus). `--bitrate` (6 000–510 000 bps) and `--sample-rate` (8 000 / 12 000 / 16 000 / 24 000 / 48 000 Hz) tune the encoder.
|
package/completions/kesha.bash
CHANGED
|
@@ -6,7 +6,7 @@ _kesha_completion() {
|
|
|
6
6
|
COMPREPLY=()
|
|
7
7
|
cur="${COMP_WORDS[COMP_CWORD]}"
|
|
8
8
|
command="${COMP_WORDS[1]}"
|
|
9
|
-
commands="completions doctor install manpage record say stats status support-bundle"
|
|
9
|
+
commands="completions doctor init install manpage record say stats status support-bundle"
|
|
10
10
|
|
|
11
11
|
if [[ "$COMP_CWORD" -eq 1 ]]; then
|
|
12
12
|
if [[ "$cur" == -* ]]; then
|
|
@@ -20,6 +20,7 @@ _kesha_completion() {
|
|
|
20
20
|
case "$command" in
|
|
21
21
|
completions) opts="--help -h" ;;
|
|
22
22
|
doctor) opts="--help -h --json --redact" ;;
|
|
23
|
+
init) opts="--help -h --coreml --onnx --no-cache --plan --yes --tts --vad --diarize" ;;
|
|
23
24
|
install) opts="--help -h --coreml --onnx --no-cache --plan --tts --vad --diarize" ;;
|
|
24
25
|
manpage) opts="--help -h" ;;
|
|
25
26
|
record) opts="--help -h --out --max-seconds --debug" ;;
|
package/completions/kesha.fish
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
complete -c kesha -f
|
|
3
3
|
complete -c kesha -n '__fish_use_subcommand' -a 'completions' -d 'Print shell completion script for bash, zsh, or fish'
|
|
4
4
|
complete -c kesha -n '__fish_use_subcommand' -a 'doctor' -d 'Collect support diagnostics without changing local state'
|
|
5
|
+
complete -c kesha -n '__fish_use_subcommand' -a 'init' -d 'Interactive setup guide for Kesha features'
|
|
5
6
|
complete -c kesha -n '__fish_use_subcommand' -a 'install' -d 'Download inference engine and models'
|
|
6
7
|
complete -c kesha -n '__fish_use_subcommand' -a 'manpage' -d 'Print the kesha(1) manpage'
|
|
7
8
|
complete -c kesha -n '__fish_use_subcommand' -a 'record' -d 'Record microphone audio to a WAV file'
|
|
@@ -30,6 +31,16 @@ complete -c kesha -n '__fish_seen_subcommand_from doctor' -l help -d 'Show help'
|
|
|
30
31
|
complete -c kesha -n '__fish_seen_subcommand_from doctor' -s h -d 'Show help'
|
|
31
32
|
complete -c kesha -n '__fish_seen_subcommand_from doctor' -l json -d 'Output diagnostics as JSON'
|
|
32
33
|
complete -c kesha -n '__fish_seen_subcommand_from doctor' -l redact -d 'Redact secrets and user-home paths from diagnostic output'
|
|
34
|
+
complete -c kesha -n '__fish_seen_subcommand_from init' -l help -d 'Show help'
|
|
35
|
+
complete -c kesha -n '__fish_seen_subcommand_from init' -s h -d 'Show help'
|
|
36
|
+
complete -c kesha -n '__fish_seen_subcommand_from init' -l coreml -d 'Preselect CoreML backend (macOS arm64)'
|
|
37
|
+
complete -c kesha -n '__fish_seen_subcommand_from init' -l onnx -d 'Preselect ONNX backend'
|
|
38
|
+
complete -c kesha -n '__fish_seen_subcommand_from init' -l no-cache -d 'Re-download even if cached'
|
|
39
|
+
complete -c kesha -n '__fish_seen_subcommand_from init' -l plan -d 'Show the selected install plan without downloading'
|
|
40
|
+
complete -c kesha -n '__fish_seen_subcommand_from init' -l yes -d 'Accept defaults and run without prompts'
|
|
41
|
+
complete -c kesha -n '__fish_seen_subcommand_from init' -l tts -d 'Preselect TTS models (Kokoro EN + Vosk-TTS RU, ~990MB)'
|
|
42
|
+
complete -c kesha -n '__fish_seen_subcommand_from init' -l vad -d 'Preselect Silero VAD (~2.3MB) for long-audio preprocessing'
|
|
43
|
+
complete -c kesha -n '__fish_seen_subcommand_from init' -l diarize -d 'Preselect Sortformer speaker diarization (~245MB, darwin-arm64 only)'
|
|
33
44
|
complete -c kesha -n '__fish_seen_subcommand_from install' -l help -d 'Show help'
|
|
34
45
|
complete -c kesha -n '__fish_seen_subcommand_from install' -s h -d 'Show help'
|
|
35
46
|
complete -c kesha -n '__fish_seen_subcommand_from install' -l coreml -d 'Force CoreML backend (macOS arm64)'
|
package/completions/kesha.zsh
CHANGED
|
@@ -6,6 +6,7 @@ _kesha() {
|
|
|
6
6
|
commands=(
|
|
7
7
|
'completions:Print shell completion script for bash, zsh, or fish'
|
|
8
8
|
'doctor:Collect support diagnostics without changing local state'
|
|
9
|
+
'init:Interactive setup guide for Kesha features'
|
|
9
10
|
'install:Download inference engine and models'
|
|
10
11
|
'manpage:Print the kesha(1) manpage'
|
|
11
12
|
'record:Record microphone audio to a WAV file'
|
|
@@ -49,6 +50,18 @@ _kesha() {
|
|
|
49
50
|
'--json[Output diagnostics as JSON]' \
|
|
50
51
|
'--redact[Redact secrets and user-home paths from diagnostic output]'
|
|
51
52
|
;;
|
|
53
|
+
init)
|
|
54
|
+
_arguments '--help[Show help]' \
|
|
55
|
+
'-h[Show help]' \
|
|
56
|
+
'--coreml[Preselect CoreML backend (macOS arm64)]' \
|
|
57
|
+
'--onnx[Preselect ONNX backend]' \
|
|
58
|
+
'--no-cache[Re-download even if cached]' \
|
|
59
|
+
'--plan[Show the selected install plan without downloading]' \
|
|
60
|
+
'--yes[Accept defaults and run without prompts]' \
|
|
61
|
+
'--tts[Preselect TTS models (Kokoro EN + Vosk-TTS RU, ~990MB)]' \
|
|
62
|
+
'--vad[Preselect Silero VAD (~2.3MB) for long-audio preprocessing]' \
|
|
63
|
+
'--diarize[Preselect Sortformer speaker diarization (~245MB, darwin-arm64 only)]'
|
|
64
|
+
;;
|
|
52
65
|
install)
|
|
53
66
|
_arguments '--help[Show help]' \
|
|
54
67
|
'-h[Show help]' \
|
package/man/kesha.1
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
.TH KESHA 1 "May 2026" "kesha 1.
|
|
1
|
+
.TH KESHA 1 "May 2026" "kesha 1.19.0-beta.1" "User Commands"
|
|
2
2
|
.SH NAME
|
|
3
3
|
kesha \- open-source local voice toolkit
|
|
4
4
|
.SH SYNOPSIS
|
|
@@ -17,6 +17,9 @@ Print shell completion script for bash, zsh, or fish
|
|
|
17
17
|
.B doctor
|
|
18
18
|
Collect support diagnostics without changing local state
|
|
19
19
|
.TP
|
|
20
|
+
.B init
|
|
21
|
+
Interactive setup guide for Kesha features
|
|
22
|
+
.TP
|
|
20
23
|
.B install
|
|
21
24
|
Download inference engine and models
|
|
22
25
|
.TP
|
|
@@ -79,6 +82,31 @@ Output diagnostics as JSON
|
|
|
79
82
|
.TP
|
|
80
83
|
.B \-\-redact
|
|
81
84
|
Redact secrets and user\-home paths from diagnostic output
|
|
85
|
+
.SS init
|
|
86
|
+
.TP
|
|
87
|
+
.B \-\-coreml
|
|
88
|
+
Preselect CoreML backend (macOS arm64)
|
|
89
|
+
.TP
|
|
90
|
+
.B \-\-onnx
|
|
91
|
+
Preselect ONNX backend
|
|
92
|
+
.TP
|
|
93
|
+
.B \-\-no\-cache
|
|
94
|
+
Re\-download even if cached
|
|
95
|
+
.TP
|
|
96
|
+
.B \-\-plan
|
|
97
|
+
Show the selected install plan without downloading
|
|
98
|
+
.TP
|
|
99
|
+
.B \-\-yes
|
|
100
|
+
Accept defaults and run without prompts
|
|
101
|
+
.TP
|
|
102
|
+
.B \-\-tts
|
|
103
|
+
Preselect TTS models (Kokoro EN + Vosk\-TTS RU, ~990MB)
|
|
104
|
+
.TP
|
|
105
|
+
.B \-\-vad
|
|
106
|
+
Preselect Silero VAD (~2.3MB) for long\-audio preprocessing
|
|
107
|
+
.TP
|
|
108
|
+
.B \-\-diarize
|
|
109
|
+
Preselect Sortformer speaker diarization (~245MB, darwin\-arm64 only)
|
|
82
110
|
.SS install
|
|
83
111
|
.TP
|
|
84
112
|
.B \-\-coreml
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@drakulavich/kesha-voice-kit",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.19.0-beta.1",
|
|
4
4
|
"description": "Give your local tools a voice: fast STT in 25 languages (~19x faster than Whisper on Apple Silicon, ONNX CPU fallback), TTS via Kokoro/Vosk/macOS voices, VAD + 107-language detection. Rust engine, Bun CLI, OpenClaw skill.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
]
|
|
33
33
|
},
|
|
34
34
|
"keshaEngine": {
|
|
35
|
-
"version": "1.
|
|
35
|
+
"version": "1.19.0-beta.1"
|
|
36
36
|
},
|
|
37
37
|
"scripts": {
|
|
38
38
|
"test": "bun test",
|
package/src/cli/dispatch.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { log } from "../log";
|
|
|
4
4
|
import { suggestCommand } from "../suggest-command";
|
|
5
5
|
import { completionsCommand } from "./completions";
|
|
6
6
|
import { doctorCommand } from "./doctor";
|
|
7
|
+
import { initCommand } from "./init";
|
|
7
8
|
import { installCommand } from "./install";
|
|
8
9
|
import { manpageCommand } from "./manpage";
|
|
9
10
|
import { recordCommand } from "./record";
|
|
@@ -15,6 +16,7 @@ import { mainCommand } from "./main";
|
|
|
15
16
|
|
|
16
17
|
const SUBCOMMANDS = [
|
|
17
18
|
"doctor",
|
|
19
|
+
"init",
|
|
18
20
|
"install",
|
|
19
21
|
"status",
|
|
20
22
|
"record",
|
|
@@ -37,6 +39,11 @@ export async function runCli(rawArgs = process.argv.slice(2)): Promise<void> {
|
|
|
37
39
|
return;
|
|
38
40
|
}
|
|
39
41
|
|
|
42
|
+
if (firstArg === "init") {
|
|
43
|
+
await runMain(initCommand, { rawArgs: restArgs });
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
40
47
|
if (firstArg === "completions") {
|
|
41
48
|
await runMain(completionsCommand, { rawArgs: restArgs });
|
|
42
49
|
return;
|
package/src/cli/init.ts
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { defineCommand } from "citty";
|
|
2
|
+
import { createInterface } from "node:readline/promises";
|
|
3
|
+
import { stdin as input, stdout as output } from "node:process";
|
|
4
|
+
import { renderInstallPlan } from "../install-plan";
|
|
5
|
+
import { log } from "../log";
|
|
6
|
+
import { performInstall, resolveBackendFlag, resolveNoCacheFlag } from "./install";
|
|
7
|
+
|
|
8
|
+
export interface InitCommandArgs {
|
|
9
|
+
coreml: boolean;
|
|
10
|
+
onnx: boolean;
|
|
11
|
+
"no-cache": boolean;
|
|
12
|
+
noCache?: boolean;
|
|
13
|
+
no_cache?: boolean;
|
|
14
|
+
tts: boolean;
|
|
15
|
+
vad: boolean;
|
|
16
|
+
diarize: boolean;
|
|
17
|
+
plan: boolean;
|
|
18
|
+
yes: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface InitSelection {
|
|
22
|
+
noCache: boolean;
|
|
23
|
+
backend?: string;
|
|
24
|
+
tts: boolean;
|
|
25
|
+
vad: boolean;
|
|
26
|
+
diarize: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface PromptApi {
|
|
30
|
+
question(prompt: string): Promise<string>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function canInstallDiarizeOnPlatform(
|
|
34
|
+
platform = process.platform,
|
|
35
|
+
arch = process.arch,
|
|
36
|
+
): boolean {
|
|
37
|
+
return platform === "darwin" && arch === "arm64";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function resolveInitSelection(
|
|
41
|
+
args: InitCommandArgs,
|
|
42
|
+
backend = resolveBackendFlag(args.coreml, args.onnx),
|
|
43
|
+
noCache = resolveNoCacheFlag(args),
|
|
44
|
+
): InitSelection {
|
|
45
|
+
return {
|
|
46
|
+
noCache,
|
|
47
|
+
backend,
|
|
48
|
+
tts: args.tts,
|
|
49
|
+
vad: args.vad,
|
|
50
|
+
diarize: args.diarize,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function initInstallArgs(selection: InitSelection): string[] {
|
|
55
|
+
return [
|
|
56
|
+
"kesha",
|
|
57
|
+
"install",
|
|
58
|
+
selection.noCache ? "--no-cache" : "",
|
|
59
|
+
selection.backend === "coreml" ? "--coreml" : "",
|
|
60
|
+
selection.backend === "onnx" ? "--onnx" : "",
|
|
61
|
+
selection.tts ? "--tts" : "",
|
|
62
|
+
selection.vad ? "--vad" : "",
|
|
63
|
+
selection.diarize ? "--diarize" : "",
|
|
64
|
+
].filter(Boolean);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function initSuggestionCommands(
|
|
68
|
+
selection: InitSelection,
|
|
69
|
+
canDiarize = canInstallDiarizeOnPlatform(),
|
|
70
|
+
): string[][] {
|
|
71
|
+
const variants: InitSelection[] = [
|
|
72
|
+
selection,
|
|
73
|
+
{ ...selection, tts: false, vad: true, diarize: false },
|
|
74
|
+
{ ...selection, tts: true, vad: true, diarize: false },
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
if (canDiarize) {
|
|
78
|
+
variants.push({ ...selection, tts: false, vad: true, diarize: true });
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const seen = new Set<string>();
|
|
82
|
+
return variants.map(initInstallArgs).filter((command) => {
|
|
83
|
+
const key = command.join("\0");
|
|
84
|
+
if (seen.has(key)) return false;
|
|
85
|
+
seen.add(key);
|
|
86
|
+
return true;
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function omitUnsupportedDiarize(
|
|
91
|
+
selection: InitSelection,
|
|
92
|
+
canDiarize = canInstallDiarizeOnPlatform(),
|
|
93
|
+
): InitSelection {
|
|
94
|
+
return selection.diarize && !canDiarize ? { ...selection, diarize: false } : selection;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function renderInitOverview(canDiarize = canInstallDiarizeOnPlatform()): string {
|
|
98
|
+
const lines = [
|
|
99
|
+
"Kesha init",
|
|
100
|
+
"",
|
|
101
|
+
"Kesha is a local voice toolkit. The base install downloads the engine, speech-to-text models, and language detection models.",
|
|
102
|
+
"",
|
|
103
|
+
"Optional features:",
|
|
104
|
+
" - Text-to-speech: enables `kesha say` with Kokoro English and Vosk-TTS Russian voices (~990MB).",
|
|
105
|
+
" - VAD: skips silence in long audio and improves meeting, lecture, and podcast transcripts (~2.3MB).",
|
|
106
|
+
canDiarize
|
|
107
|
+
? " - Speaker diarization: labels speakers in JSON/TOON transcript segments (~245MB, darwin-arm64)."
|
|
108
|
+
: " - Speaker diarization: labels speakers, but the install is currently darwin-arm64 only.",
|
|
109
|
+
"",
|
|
110
|
+
"Nothing downloads until you confirm the final install plan.",
|
|
111
|
+
];
|
|
112
|
+
return `${lines.join("\n")}\n`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export async function promptInitSelection(
|
|
116
|
+
args: InitCommandArgs,
|
|
117
|
+
prompt: PromptApi,
|
|
118
|
+
backend = resolveBackendFlag(args.coreml, args.onnx),
|
|
119
|
+
canDiarize = canInstallDiarizeOnPlatform(),
|
|
120
|
+
noCache = resolveNoCacheFlag(args),
|
|
121
|
+
): Promise<InitSelection> {
|
|
122
|
+
const tts = await askYesNo(prompt, "Install text-to-speech models for `kesha say`?", args.tts);
|
|
123
|
+
const vad = await askYesNo(prompt, "Install VAD for long or silence-heavy audio?", args.vad);
|
|
124
|
+
let diarize = false;
|
|
125
|
+
if (canDiarize) {
|
|
126
|
+
diarize = await askYesNo(prompt, "Install speaker diarization for `--speakers`?", args.diarize);
|
|
127
|
+
} else if (args.diarize) {
|
|
128
|
+
log.warn("--diarize is currently darwin-arm64 only; omitting it from the interactive install.");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
noCache,
|
|
133
|
+
backend,
|
|
134
|
+
tts,
|
|
135
|
+
vad,
|
|
136
|
+
diarize,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function askYesNo(prompt: PromptApi, message: string, defaultValue: boolean): Promise<boolean> {
|
|
141
|
+
const suffix = defaultValue ? "Y/n" : "y/N";
|
|
142
|
+
for (;;) {
|
|
143
|
+
const answer = (await prompt.question(`${message} [${suffix}] `)).trim().toLowerCase();
|
|
144
|
+
if (answer === "") return defaultValue;
|
|
145
|
+
if (answer === "y" || answer === "yes") return true;
|
|
146
|
+
if (answer === "n" || answer === "no") return false;
|
|
147
|
+
log.warn("Please answer yes or no.");
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async function printPlan(selection: InitSelection): Promise<void> {
|
|
152
|
+
log.info(
|
|
153
|
+
await renderInstallPlan({
|
|
154
|
+
noCache: selection.noCache,
|
|
155
|
+
backend: selection.backend,
|
|
156
|
+
tts: selection.tts,
|
|
157
|
+
vad: selection.vad,
|
|
158
|
+
diarize: selection.diarize,
|
|
159
|
+
}),
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async function runNonInteractive(selection: InitSelection): Promise<void> {
|
|
164
|
+
const canDiarize = canInstallDiarizeOnPlatform();
|
|
165
|
+
const printableSelection = omitUnsupportedDiarize(selection, canDiarize);
|
|
166
|
+
if (selection.diarize && !canDiarize) {
|
|
167
|
+
log.warn("--diarize is currently darwin-arm64 only; omitting it from non-interactive examples.");
|
|
168
|
+
}
|
|
169
|
+
log.info(renderInitOverview(canDiarize));
|
|
170
|
+
await printPlan(printableSelection);
|
|
171
|
+
log.info("Run one of these commands from an interactive terminal:");
|
|
172
|
+
for (const command of initSuggestionCommands(printableSelection, canDiarize)) {
|
|
173
|
+
log.info(` ${command.join(" ")}`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export const initCommand = defineCommand({
|
|
178
|
+
meta: {
|
|
179
|
+
name: "init",
|
|
180
|
+
description: "Interactive setup guide for Kesha features",
|
|
181
|
+
},
|
|
182
|
+
args: {
|
|
183
|
+
coreml: {
|
|
184
|
+
type: "boolean",
|
|
185
|
+
description: "Preselect CoreML backend (macOS arm64)",
|
|
186
|
+
default: false,
|
|
187
|
+
},
|
|
188
|
+
onnx: {
|
|
189
|
+
type: "boolean",
|
|
190
|
+
description: "Preselect ONNX backend",
|
|
191
|
+
default: false,
|
|
192
|
+
},
|
|
193
|
+
"no-cache": {
|
|
194
|
+
type: "boolean",
|
|
195
|
+
description: "Re-download even if cached",
|
|
196
|
+
default: false,
|
|
197
|
+
},
|
|
198
|
+
plan: {
|
|
199
|
+
type: "boolean",
|
|
200
|
+
description: "Show the selected install plan without downloading",
|
|
201
|
+
default: false,
|
|
202
|
+
},
|
|
203
|
+
yes: {
|
|
204
|
+
type: "boolean",
|
|
205
|
+
description: "Accept defaults and run without prompts",
|
|
206
|
+
default: false,
|
|
207
|
+
},
|
|
208
|
+
tts: {
|
|
209
|
+
type: "boolean",
|
|
210
|
+
description: "Preselect TTS models (Kokoro EN + Vosk-TTS RU, ~990MB)",
|
|
211
|
+
default: false,
|
|
212
|
+
},
|
|
213
|
+
vad: {
|
|
214
|
+
type: "boolean",
|
|
215
|
+
description: "Preselect Silero VAD (~2.3MB) for long-audio preprocessing",
|
|
216
|
+
default: false,
|
|
217
|
+
},
|
|
218
|
+
diarize: {
|
|
219
|
+
type: "boolean",
|
|
220
|
+
description: "Preselect Sortformer speaker diarization (~245MB, darwin-arm64 only)",
|
|
221
|
+
default: false,
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
async run({ args, rawArgs }: { args: InitCommandArgs; rawArgs: string[] }) {
|
|
225
|
+
const backend = resolveBackendFlag(args.coreml, args.onnx);
|
|
226
|
+
const noCache = resolveNoCacheFlag(args, rawArgs);
|
|
227
|
+
const selection = resolveInitSelection(args, backend, noCache);
|
|
228
|
+
|
|
229
|
+
if (args.plan) {
|
|
230
|
+
log.info(renderInitOverview());
|
|
231
|
+
await printPlan(selection);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (args.yes) {
|
|
236
|
+
const installSelection = omitUnsupportedDiarize(selection);
|
|
237
|
+
if (selection.diarize && !installSelection.diarize) {
|
|
238
|
+
log.warn("--diarize is currently darwin-arm64 only; omitting it from the --yes install.");
|
|
239
|
+
}
|
|
240
|
+
await performInstall(
|
|
241
|
+
installSelection.noCache,
|
|
242
|
+
installSelection.backend,
|
|
243
|
+
installSelection.tts,
|
|
244
|
+
installSelection.vad,
|
|
245
|
+
installSelection.diarize,
|
|
246
|
+
);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const stdinIsTty = process.stdin.isTTY === true;
|
|
251
|
+
const stdoutIsTty = process.stdout.isTTY === true;
|
|
252
|
+
if (!stdinIsTty || !stdoutIsTty) {
|
|
253
|
+
await runNonInteractive(selection);
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
log.info(renderInitOverview());
|
|
258
|
+
const rl = createInterface({ input, output });
|
|
259
|
+
try {
|
|
260
|
+
const prompted = await promptInitSelection(args, rl, backend, canInstallDiarizeOnPlatform(), noCache);
|
|
261
|
+
log.info("");
|
|
262
|
+
await printPlan(prompted);
|
|
263
|
+
const confirmed = await askYesNo(rl, `Run \`${initInstallArgs(prompted).join(" ")}\` now?`, true);
|
|
264
|
+
if (!confirmed) {
|
|
265
|
+
log.info(`Skipped install. Run later: ${initInstallArgs(prompted).join(" ")}`);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
await performInstall(prompted.noCache, prompted.backend, prompted.tts, prompted.vad, prompted.diarize);
|
|
269
|
+
} finally {
|
|
270
|
+
rl.close();
|
|
271
|
+
}
|
|
272
|
+
},
|
|
273
|
+
});
|
package/src/cli/install.ts
CHANGED
|
@@ -6,17 +6,31 @@ import { maybeAskForStar } from "../star";
|
|
|
6
6
|
import { log } from "../log";
|
|
7
7
|
import { packageVersion } from "../package-info";
|
|
8
8
|
|
|
9
|
-
interface InstallCommandArgs {
|
|
9
|
+
export interface InstallCommandArgs {
|
|
10
10
|
coreml: boolean;
|
|
11
11
|
onnx: boolean;
|
|
12
12
|
"no-cache": boolean;
|
|
13
|
+
noCache?: boolean;
|
|
14
|
+
no_cache?: boolean;
|
|
13
15
|
tts: boolean;
|
|
14
16
|
vad: boolean;
|
|
15
17
|
diarize: boolean;
|
|
16
18
|
plan: boolean;
|
|
17
19
|
}
|
|
18
20
|
|
|
19
|
-
function
|
|
21
|
+
export function resolveNoCacheFlag(
|
|
22
|
+
args: Pick<InstallCommandArgs, "no-cache" | "noCache" | "no_cache">,
|
|
23
|
+
rawArgs: string[] = [],
|
|
24
|
+
): boolean {
|
|
25
|
+
return (
|
|
26
|
+
rawArgs.includes("--no-cache") ||
|
|
27
|
+
args["no-cache"] === true ||
|
|
28
|
+
args.noCache === true ||
|
|
29
|
+
args.no_cache === true
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function resolveBackendFlag(coreml: boolean, onnx: boolean): string | undefined {
|
|
20
34
|
if (coreml && onnx) {
|
|
21
35
|
log.error('Choose only one backend: "--coreml" or "--onnx".');
|
|
22
36
|
process.exit(1);
|
|
@@ -32,7 +46,7 @@ function defaultBackendForPlatform(): string | undefined {
|
|
|
32
46
|
return undefined;
|
|
33
47
|
}
|
|
34
48
|
|
|
35
|
-
async function performInstall(
|
|
49
|
+
export async function performInstall(
|
|
36
50
|
noCache: boolean,
|
|
37
51
|
backend?: string,
|
|
38
52
|
tts = false,
|
|
@@ -111,8 +125,8 @@ export const installCommand = defineCommand({
|
|
|
111
125
|
default: false,
|
|
112
126
|
},
|
|
113
127
|
},
|
|
114
|
-
async run({ args }: { args: InstallCommandArgs }) {
|
|
128
|
+
async run({ args, rawArgs }: { args: InstallCommandArgs; rawArgs: string[] }) {
|
|
115
129
|
const backend = resolveBackendFlag(args.coreml, args.onnx);
|
|
116
|
-
await performInstall(args
|
|
130
|
+
await performInstall(resolveNoCacheFlag(args, rawArgs), backend, args.tts, args.vad, args.diarize, args.plan);
|
|
117
131
|
},
|
|
118
132
|
});
|
package/src/cli/main.ts
CHANGED
|
@@ -147,6 +147,7 @@ export const mainCommand = defineCommand({
|
|
|
147
147
|
"Commands:\n" +
|
|
148
148
|
" completions Print shell completion script.\n" +
|
|
149
149
|
" doctor Collect support diagnostics.\n" +
|
|
150
|
+
" init Interactive setup guide for Kesha features.\n" +
|
|
150
151
|
" install Download engine and models.\n" +
|
|
151
152
|
" manpage Print the kesha(1) manpage.\n" +
|
|
152
153
|
" record Record microphone audio to a WAV file.\n" +
|
package/src/cli.ts
CHANGED
|
@@ -5,6 +5,17 @@
|
|
|
5
5
|
// public surface is unchanged.
|
|
6
6
|
export { doctorCommand } from "./cli/doctor";
|
|
7
7
|
export { completionsCommand } from "./cli/completions";
|
|
8
|
+
export {
|
|
9
|
+
canInstallDiarizeOnPlatform,
|
|
10
|
+
initCommand,
|
|
11
|
+
initInstallArgs,
|
|
12
|
+
initSuggestionCommands,
|
|
13
|
+
omitUnsupportedDiarize,
|
|
14
|
+
promptInitSelection,
|
|
15
|
+
renderInitOverview,
|
|
16
|
+
resolveInitSelection,
|
|
17
|
+
} from "./cli/init";
|
|
18
|
+
export type { InitCommandArgs, InitSelection } from "./cli/init";
|
|
8
19
|
export { installCommand } from "./cli/install";
|
|
9
20
|
export { manpageCommand } from "./cli/manpage";
|
|
10
21
|
export { recordCommand, resolveRecordArgs } from "./cli/record";
|
package/src/doctor.ts
CHANGED
|
@@ -257,20 +257,19 @@ function collectOptionalComponents(redact: boolean): OptionalComponent[] {
|
|
|
257
257
|
...pathSummary(join(cache, "models/vosk-ru")),
|
|
258
258
|
},
|
|
259
259
|
{
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
260
|
+
// Diarization (#199) and Kokoro (#207) run in-engine now (native
|
|
261
|
+
// fluidaudio-rs) — no Swift sidecar binaries. The Sortformer model is
|
|
262
|
+
// the only installable diarization artifact; Kokoro is covered by the
|
|
263
|
+
// "TTS (Kokoro)" entry above.
|
|
264
|
+
name: "Diarization (Sortformer)",
|
|
265
|
+
note: "enabled with `kesha install --diarize` (darwin-arm64); runs in-engine",
|
|
266
|
+
...pathSummary(join(cache, "models/diarize")),
|
|
263
267
|
},
|
|
264
268
|
{
|
|
265
269
|
name: "AVSpeech sidecar",
|
|
266
270
|
note: "macOS voices",
|
|
267
271
|
...pathSummary(join(sidecarDir, "say-avspeech")),
|
|
268
272
|
},
|
|
269
|
-
{
|
|
270
|
-
name: "FluidAudio Kokoro sidecar",
|
|
271
|
-
note: "darwin-arm64 Kokoro",
|
|
272
|
-
...pathSummary(join(sidecarDir, "kesha-kokoro")),
|
|
273
|
-
},
|
|
274
273
|
{
|
|
275
274
|
name: "Text language sidecar",
|
|
276
275
|
note: "darwin text language fast path",
|
package/src/engine-install.ts
CHANGED
|
@@ -69,20 +69,9 @@ const SIDECARS: SidecarSpec[] = [
|
|
|
69
69
|
availableHint: "macOS voices available",
|
|
70
70
|
unavailableHint: "macos-* voices unavailable",
|
|
71
71
|
},
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
displayName: "Diarization sidecar",
|
|
76
|
-
availableHint: "--speakers available",
|
|
77
|
-
unavailableHint: "--speakers unavailable",
|
|
78
|
-
},
|
|
79
|
-
{
|
|
80
|
-
fileBasename: "kesha-kokoro",
|
|
81
|
-
assetName: "kesha-kokoro-darwin-arm64",
|
|
82
|
-
displayName: "FluidAudio Kokoro sidecar",
|
|
83
|
-
availableHint: "Kokoro uses FluidAudio CoreML",
|
|
84
|
-
unavailableHint: "en-* Kokoro voices unavailable on Darwin",
|
|
85
|
-
},
|
|
72
|
+
// Kokoro TTS (#207) and speaker diarization (#199) no longer ship as Swift
|
|
73
|
+
// sidecars — both run in-engine via the native `fluidaudio-rs` binding. Only
|
|
74
|
+
// the AVSpeech and text-lang sidecars remain.
|
|
86
75
|
{
|
|
87
76
|
// Runtime resolver looks for plain `kesha-textlang` next to the engine
|
|
88
77
|
// (see `rust/src/text_lang.rs::helper_path`), not the platform-suffixed
|
|
@@ -99,6 +88,40 @@ const SIDECARS: SidecarSpec[] = [
|
|
|
99
88
|
},
|
|
100
89
|
];
|
|
101
90
|
|
|
91
|
+
const RETIRED_SIDECAR_FILENAMES = [
|
|
92
|
+
// Historical installed filenames.
|
|
93
|
+
"kesha-kokoro",
|
|
94
|
+
"kesha-diarize",
|
|
95
|
+
// Historical release-asset filenames. Keep these explicit: AVSpeech and
|
|
96
|
+
// text-lang helpers are still active and must not be swept up by a glob.
|
|
97
|
+
"kesha-kokoro-darwin-arm64",
|
|
98
|
+
"kesha-diarize-darwin-arm64",
|
|
99
|
+
];
|
|
100
|
+
|
|
101
|
+
export function cleanupRetiredSidecars(engineDir: string): string[] {
|
|
102
|
+
const removed: string[] = [];
|
|
103
|
+
|
|
104
|
+
for (const filename of RETIRED_SIDECAR_FILENAMES) {
|
|
105
|
+
const path = join(engineDir, filename);
|
|
106
|
+
if (!existsSync(path)) continue;
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
rmSync(path);
|
|
110
|
+
removed.push(filename);
|
|
111
|
+
} catch (e) {
|
|
112
|
+
log.warn(
|
|
113
|
+
`Could not remove retired sidecar ${filename} (${e instanceof Error ? e.message : e}); continuing.`,
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (removed.length > 0) {
|
|
119
|
+
log.success(`Removed retired sidecars: ${removed.join(", ")}.`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return removed;
|
|
123
|
+
}
|
|
124
|
+
|
|
102
125
|
/**
|
|
103
126
|
* Make a freshly-downloaded Mach-O runnable on macOS 15+ Sequoia.
|
|
104
127
|
*
|
|
@@ -232,8 +255,10 @@ async function downloadSidecar(
|
|
|
232
255
|
|
|
233
256
|
async function warmDarwinKokoro(binPath: string): Promise<void> {
|
|
234
257
|
if (process.platform !== "darwin" || process.arch !== "arm64") return;
|
|
235
|
-
|
|
236
|
-
|
|
258
|
+
// Kokoro now runs in-engine (FluidAudio CoreML, system_kokoro) — warm it by
|
|
259
|
+
// exercising the engine's own `say`, not a sidecar. The first synthesis
|
|
260
|
+
// compiles/fetches FluidAudio's CoreML Kokoro cache.
|
|
261
|
+
if (!existsSync(binPath)) return;
|
|
237
262
|
|
|
238
263
|
const outPath = join(tmpdir(), `kesha-kokoro-warmup-${process.pid}.wav`);
|
|
239
264
|
log.progress("Warming FluidAudio Kokoro CoreML cache...");
|
|
@@ -499,6 +524,10 @@ export async function downloadEngine(
|
|
|
499
524
|
await warmDarwinKokoro(binPath);
|
|
500
525
|
}
|
|
501
526
|
|
|
527
|
+
if (canWriteEngineDir) {
|
|
528
|
+
cleanupRetiredSidecars(engineDir);
|
|
529
|
+
}
|
|
530
|
+
|
|
502
531
|
log.success("Backend installed successfully.");
|
|
503
532
|
return binPath;
|
|
504
533
|
}
|
package/src/install-plan.ts
CHANGED
|
@@ -81,10 +81,10 @@ const DIARIZE_FILES: PlanFile[] = [
|
|
|
81
81
|
},
|
|
82
82
|
];
|
|
83
83
|
|
|
84
|
+
// Kokoro (#207) and diarization (#199) are in-engine now (native fluidaudio-rs),
|
|
85
|
+
// so only AVSpeech and text-lang ship as separate Swift-sidecar assets.
|
|
84
86
|
const DARWIN_SIDECARS: ReleaseAssetSpec[] = [
|
|
85
87
|
{ assetName: "say-avspeech-darwin-arm64", sizeBytes: 63_056 },
|
|
86
|
-
{ assetName: "kesha-diarize-darwin-arm64", sizeBytes: 7_228_000 },
|
|
87
|
-
{ assetName: "kesha-kokoro-darwin-arm64", sizeBytes: 7_189_648 },
|
|
88
88
|
{ assetName: "kesha-textlang-darwin-arm64", sizeBytes: 57_648 },
|
|
89
89
|
];
|
|
90
90
|
|
|
@@ -123,8 +123,6 @@ function engineAssetForPlatform(): ReleaseAssetSpec | null {
|
|
|
123
123
|
|
|
124
124
|
function sidecarFilename(assetName: string): string {
|
|
125
125
|
if (assetName === "say-avspeech-darwin-arm64") return "say-avspeech";
|
|
126
|
-
if (assetName === "kesha-diarize-darwin-arm64") return "kesha-diarize-darwin-arm64";
|
|
127
|
-
if (assetName === "kesha-kokoro-darwin-arm64") return "kesha-kokoro";
|
|
128
126
|
if (assetName === "kesha-textlang-darwin-arm64") return "kesha-textlang";
|
|
129
127
|
return assetName;
|
|
130
128
|
}
|
|
@@ -234,11 +232,11 @@ export async function renderInstallPlan(options: InstallPlanOptions = {}): Promi
|
|
|
234
232
|
);
|
|
235
233
|
components.push({
|
|
236
234
|
name: "TTS Kokoro EN",
|
|
237
|
-
source: "FluidAudio CoreML
|
|
235
|
+
source: "FluidAudio CoreML (in-engine)",
|
|
238
236
|
sizeBytes: 0,
|
|
239
|
-
cached: existsSync(
|
|
237
|
+
cached: existsSync(binPath),
|
|
240
238
|
refresh: false,
|
|
241
|
-
note: "no Kokoro ONNX model is downloaded by Kesha on darwin-arm64; warm-up may compile/update FluidAudio's CoreML cache",
|
|
239
|
+
note: "Kokoro runs in-engine via FluidAudio CoreML; no Kokoro ONNX model is downloaded by Kesha on darwin-arm64; warm-up may compile/update FluidAudio's CoreML cache",
|
|
242
240
|
});
|
|
243
241
|
} else {
|
|
244
242
|
components.push(
|
package/src/shell-artifacts.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ArgDef, ArgsDef, CommandDef, CommandMeta, Resolvable } from "citty";
|
|
2
2
|
import { completionsCommand } from "./cli/completions";
|
|
3
3
|
import { doctorCommand } from "./cli/doctor";
|
|
4
|
+
import { initCommand } from "./cli/init";
|
|
4
5
|
import { installCommand } from "./cli/install";
|
|
5
6
|
import { mainCommand } from "./cli/main";
|
|
6
7
|
import { manpageCommand } from "./cli/manpage";
|
|
@@ -44,6 +45,7 @@ export interface ShellArtifact {
|
|
|
44
45
|
const CLI_COMMANDS: CliCommand[] = [
|
|
45
46
|
{ name: "completions", command: completionsCommand },
|
|
46
47
|
{ name: "doctor", command: doctorCommand },
|
|
48
|
+
{ name: "init", command: initCommand },
|
|
47
49
|
{ name: "install", command: installCommand },
|
|
48
50
|
{ name: "manpage", command: manpageCommand },
|
|
49
51
|
{ name: "record", command: recordCommand },
|