@drakulavich/kesha-voice-kit 1.18.4 → 1.18.6

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 CHANGED
@@ -15,7 +15,7 @@
15
15
 
16
16
  - **Transcribe locally** — 25 languages, up to ~19x faster than Whisper on Apple Silicon, ~2.5x on CPU
17
17
  - **Speak back** — Kokoro (EN), Vosk-TTS (RU), macOS system voices, and SSML preview
18
- - **Plug into agents** — ship voice workflows as CLI commands or an <a href="https://github.com/openclaw/openclaw">OpenClaw</a> skill
18
+ - **Plug into agents** — ship voice workflows as CLI commands or an <a href="https://github.com/openclaw/openclaw">OpenClaw</a> skill; setup docs: [OpenClaw](docs/openclaw.md), [Hermes](docs/hermes.md)
19
19
  - **Small Rust engine** — single ~20MB binary, no ffmpeg, no Python, no native Node addons
20
20
 
21
21
  See [Product positioning](docs/product-positioning.md) for supported workflows, non-goals, maturity labels, and the platform matrix.
@@ -51,139 +51,10 @@ kesha audio.ogg # transcript to stdout
51
51
 
52
52
  Air-gapped or behind a corporate mirror? See [docs/model-mirror.md](docs/model-mirror.md).
53
53
 
54
- ## Shell Completions and Manpage
55
-
56
- The npm package includes bash, zsh, and fish completions plus `kesha(1)`.
57
- The CLI can print the packaged files, so install paths do not depend on the
58
- Bun global package layout:
59
-
60
- ```bash
61
- # bash
62
- mkdir -p ~/.local/share/bash-completion/completions
63
- kesha completions bash > ~/.local/share/bash-completion/completions/kesha
64
-
65
- # zsh
66
- mkdir -p ~/.zsh/completions
67
- kesha completions zsh > ~/.zsh/completions/_kesha
68
- # add to ~/.zshrc once: fpath=(~/.zsh/completions $fpath); autoload -Uz compinit; compinit
69
-
70
- # fish
71
- mkdir -p ~/.config/fish/completions
72
- kesha completions fish > ~/.config/fish/completions/kesha.fish
73
-
74
- # manpage
75
- mkdir -p ~/.local/share/man/man1
76
- kesha manpage > ~/.local/share/man/man1/kesha.1
77
- mandb ~/.local/share/man 2>/dev/null || true
78
- ```
79
-
80
- ## Homebrew Install
81
-
82
- Homebrew installs the Bun-based CLI wrapper. Engine and model downloads remain
83
- explicit:
84
-
85
- ```bash
86
- brew tap oven-sh/bun
87
- brew install drakulavich/tap/kesha-voice-kit
88
- kesha install
89
- kesha audio.ogg
90
- ```
91
-
92
- See [Homebrew install](docs/homebrew.md) for package scope and maintainer
93
- validation.
94
-
95
- ## Support diagnostics
96
-
97
- Kesha can collect local diagnostics without downloading models or mutating cache state:
98
-
99
- ```bash
100
- kesha doctor --json --redact
101
- kesha support-bundle --output kesha-support.tar.gz
102
- ```
103
-
104
- `support-bundle` creates a redacted `.tar.gz` archive for GitHub issues. It includes runtime, engine, cache, optional-component, Stats status, and known Kesha environment settings. It does not include audio, transcripts, model files, or the Stats database.
105
-
106
- ## Local Stats privacy and lifecycle
107
-
108
- Kesha Stats is disabled by default. When you opt in with `kesha stats enable`,
109
- Kesha writes a local SQLite database only on your machine:
110
-
111
- ```bash
112
- kesha stats status
113
- kesha stats week
114
- kesha stats errors
115
- kesha stats export --format json # or csv
116
- kesha stats retention 30 # default: 90 days
117
- kesha stats retention off # keep until reset
118
- kesha stats reset # delete recorded stats rows
119
- kesha stats vacuum # compact the SQLite file
120
- ```
121
-
122
- The database stores content-free operational records only: command name
123
- (`transcribe` or `say`), timestamps, success/failure status, app version, item
124
- count, anonymous stage timings, input/output artifact kind, file extension,
125
- size, optional duration/sample-rate/channel counts, and sanitized error
126
- class/code/message.
127
-
128
- Stats never stores audio bytes, transcripts, input text, generated speech text,
129
- file names, full file paths, raw stdout/stderr, environment variables, model
130
- files, API tokens, or cloud identifiers. `support-bundle` reports Stats status
131
- only; it never includes the Stats SQLite database.
132
-
133
- By default, Stats prunes rows older than 90 days before writing or exporting
134
- data. Use `kesha stats retention <days>` to change the TTL or `kesha stats
135
- retention off` to disable TTL pruning. `kesha stats reset` deletes recorded
136
- runs, artifacts, timings, and errors while preserving settings such as enabled
137
- state and retention.
138
-
139
- ## Nix Install
140
-
141
- Alternative reproducible-build path on `aarch64-darwin` / `x86_64-linux`:
142
-
143
- ```bash
144
- nix run github:drakulavich/kesha-voice-kit -- install # downloads models (engine is bundled)
145
- nix run github:drakulavich/kesha-voice-kit -- audio.ogg # transcribe
146
- ```
147
-
148
- Full recipes (one-liner, profile install, engine-only, dev shell) live in [docs/nix-install.md](docs/nix-install.md).
149
-
150
- ## Docker
151
-
152
- Linux x64 CLI image, published to GHCR:
153
-
154
- ```bash
155
- docker run --rm \
156
- -v kesha-cache:/cache/kesha \
157
- -v "$PWD:/work" -w /work \
158
- ghcr.io/drakulavich/kesha-voice-kit:latest install
159
-
160
- docker run --rm \
161
- -v kesha-cache:/cache/kesha \
162
- -v "$PWD:/work" -w /work \
163
- ghcr.io/drakulavich/kesha-voice-kit:latest audio.ogg
164
- ```
165
-
166
- The image keeps model downloads and the engine cache under `/cache/kesha`.
167
- Mount that path as a named volume so `kesha install`, TTS models, VAD, and future
168
- runs reuse the same cache. `compose.yml` provides the same layout:
169
-
170
- ```bash
171
- docker compose run --rm kesha install
172
- docker compose run --rm kesha audio.ogg
173
- ```
174
-
175
- ## Linux Packages
176
-
177
- Stable engine releases also publish `.deb` and `.rpm` packages for Linux x64.
178
- They install the standalone CLI wrapper; engine and model downloads remain explicit:
179
-
180
- ```bash
181
- kesha install
182
- kesha audio.ogg
183
- ```
54
+ ## Requirements
184
55
 
185
- See [Linux packages](docs/linux-packages.md) for install commands and package
186
- scope.
56
+ - [Bun](https://bun.sh) >= 1.3
57
+ - macOS arm64, Linux x64, or Windows x64
187
58
 
188
59
  ## Speech-to-text
189
60
 
@@ -265,6 +136,96 @@ Honored when `<prosody rate>` wraps the whole utterance. Mid-utterance prosody w
265
136
 
266
137
  macOS system voices, SSML, voice listing, and the full voice catalogue: [docs/tts.md](docs/tts.md).
267
138
 
139
+ ## Homebrew Install
140
+
141
+ Homebrew installs the Bun-based CLI wrapper. Engine and model downloads remain
142
+ explicit:
143
+
144
+ ```bash
145
+ brew tap oven-sh/bun
146
+ brew install drakulavich/tap/kesha-voice-kit
147
+ kesha install
148
+ kesha audio.ogg
149
+ ```
150
+
151
+ See [Homebrew install](docs/homebrew.md) for package scope and maintainer
152
+ validation.
153
+
154
+ ## Linux Packages
155
+
156
+ Stable engine releases also publish `.deb` and `.rpm` packages for Linux x64.
157
+ They install the standalone CLI wrapper; engine and model downloads remain explicit:
158
+
159
+ ```bash
160
+ kesha install
161
+ kesha audio.ogg
162
+ ```
163
+
164
+ See [Linux packages](docs/linux-packages.md) for install commands and package
165
+ scope.
166
+
167
+ ## Docker
168
+
169
+ Linux x64 CLI image, published to GHCR:
170
+
171
+ ```bash
172
+ docker run --rm \
173
+ -v kesha-cache:/cache/kesha \
174
+ -v "$PWD:/work" -w /work \
175
+ ghcr.io/drakulavich/kesha-voice-kit:latest install
176
+
177
+ docker run --rm \
178
+ -v kesha-cache:/cache/kesha \
179
+ -v "$PWD:/work" -w /work \
180
+ ghcr.io/drakulavich/kesha-voice-kit:latest audio.ogg
181
+ ```
182
+
183
+ The image keeps model downloads and the engine cache under `/cache/kesha`.
184
+ Mount that path as a named volume so `kesha install`, TTS models, VAD, and future
185
+ runs reuse the same cache. `compose.yml` provides the same layout:
186
+
187
+ ```bash
188
+ docker compose run --rm kesha install
189
+ docker compose run --rm kesha audio.ogg
190
+ ```
191
+
192
+ ## Nix Install
193
+
194
+ Alternative reproducible-build path on `aarch64-darwin` / `x86_64-linux`:
195
+
196
+ ```bash
197
+ nix run github:drakulavich/kesha-voice-kit -- install # downloads models (engine is bundled)
198
+ nix run github:drakulavich/kesha-voice-kit -- audio.ogg # transcribe
199
+ ```
200
+
201
+ Full recipes (one-liner, profile install, engine-only, dev shell) live in [docs/nix-install.md](docs/nix-install.md).
202
+
203
+ ## Shell Completions and Manpage
204
+
205
+ The npm package includes bash, zsh, and fish completions plus `kesha(1)`.
206
+ The CLI can print the packaged files, so install paths do not depend on the
207
+ Bun global package layout:
208
+
209
+ ```bash
210
+ # bash
211
+ mkdir -p ~/.local/share/bash-completion/completions
212
+ kesha completions bash > ~/.local/share/bash-completion/completions/kesha
213
+
214
+ # zsh
215
+ mkdir -p ~/.zsh/completions
216
+ kesha completions zsh > ~/.zsh/completions/_kesha
217
+ # add to ~/.zshrc once: fpath=(~/.zsh/completions $fpath); autoload -Uz compinit; compinit
218
+
219
+ # fish
220
+ mkdir -p ~/.config/fish/completions
221
+ kesha completions fish > ~/.config/fish/completions/kesha.fish
222
+
223
+ # manpage
224
+ mkdir -p ~/.local/share/man/man1
225
+ kesha manpage > ~/.local/share/man/man1/kesha.1
226
+ mandb ~/.local/share/man 2>/dev/null || true
227
+ ```
228
+
268
229
  ## Performance
269
230
 
270
231
  > **Up to ~19x faster than Whisper** on Apple Silicon (M2), **~2.5x faster** on CPU
@@ -309,10 +270,49 @@ await downloadModel(); // install engine + models
309
270
  const text = await transcribe("audio.ogg"); // transcribe
310
271
  ```
311
272
 
312
- ## Requirements
273
+ ## Support diagnostics
313
274
 
314
- - [Bun](https://bun.sh) >= 1.3
315
- - macOS arm64, Linux x64, or Windows x64
275
+ Kesha can collect local diagnostics without downloading models or mutating cache state:
276
+
277
+ ```bash
278
+ kesha doctor --json --redact
279
+ kesha support-bundle --output kesha-support.tar.gz
280
+ ```
281
+
282
+ `support-bundle` creates a redacted `.tar.gz` archive for GitHub issues. It includes runtime, engine, cache, optional-component, Stats status, and known Kesha environment settings. It does not include audio, transcripts, model files, or the Stats database.
283
+
284
+ ## Local Stats privacy and lifecycle
285
+
286
+ Kesha Stats is disabled by default. When you opt in with `kesha stats enable`,
287
+ Kesha writes a local SQLite database only on your machine:
288
+
289
+ ```bash
290
+ kesha stats status
291
+ kesha stats week
292
+ kesha stats errors
293
+ kesha stats export --format json # or csv
294
+ kesha stats retention 30 # default: 90 days
295
+ kesha stats retention off # keep until reset
296
+ kesha stats reset # delete recorded stats rows
297
+ kesha stats vacuum # compact the SQLite file
298
+ ```
299
+
300
+ The database stores content-free operational records only: command name
301
+ (`transcribe` or `say`), timestamps, success/failure status, app version, item
302
+ count, anonymous stage timings, input/output artifact kind, file extension,
303
+ size, optional duration/sample-rate/channel counts, and sanitized error
304
+ class/code/message.
305
+
306
+ Stats never stores audio bytes, transcripts, input text, generated speech text,
307
+ file names, full file paths, raw stdout/stderr, environment variables, model
308
+ files, API tokens, or cloud identifiers. `support-bundle` reports Stats status
309
+ only; it never includes the Stats SQLite database.
310
+
311
+ By default, Stats prunes rows older than 90 days before writing or exporting
312
+ data. Use `kesha stats retention <days>` to change the TTL or `kesha stats
313
+ retention off` to disable TTL pruning. `kesha stats reset` deletes recorded
314
+ runs, artifacts, timings, and errors while preserving settings such as enabled
315
+ state and retention.
316
316
 
317
317
  ## Contributing
318
318
 
package/SKILL.md CHANGED
@@ -34,12 +34,30 @@ Install the plugin, then explicitly route OpenClaw audio understanding through t
34
34
  bun add -g @drakulavich/kesha-voice-kit
35
35
  kesha install
36
36
  openclaw plugins install @drakulavich/kesha-voice-kit
37
- openclaw config set tools.media.audio.enabled true
38
- openclaw config set tools.media.audio.models \
39
- '[{"type":"cli","command":"kesha","args":["--format","transcript","{{MediaPath}}"],"timeoutSeconds":15}]'
37
+ openclaw config patch --stdin <<'JSON5'
38
+ {
39
+ tools: {
40
+ media: {
41
+ audio: {
42
+ enabled: true,
43
+ models: [
44
+ {
45
+ type: "cli",
46
+ command: "kesha",
47
+ args: ["{{MediaPath}}"],
48
+ timeoutSeconds: 15,
49
+ },
50
+ ],
51
+ echoTranscript: true,
52
+ echoFormat: '🦜 "{transcript}"',
53
+ },
54
+ },
55
+ },
56
+ }
57
+ JSON5
40
58
  ```
41
59
 
42
- Use `--format transcript` for OpenClaw's normal voice-message path: stdout is compact text plus language metadata, while progress and errors stay off the transcript payload.
60
+ Use Kesha's default output for OpenClaw's normal voice-message path: stdout is the bare transcript text, while progress and errors stay off the transcript payload. The default setup echoes each transcript back to chat as `🦜 "{transcript}"` before the agent responds.
43
61
 
44
62
  For agents that need timestamped segments, switch the model entry to JSON output and allow a longer timeout:
45
63
 
@@ -55,6 +73,8 @@ which kesha
55
73
  kesha status
56
74
  openclaw plugins list
57
75
  openclaw config get tools.media.audio.models
76
+ openclaw config get tools.media.audio.echoTranscript
77
+ openclaw config get tools.media.audio.echoFormat
58
78
  ```
59
79
 
60
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.
package/man/kesha.1 CHANGED
@@ -1,4 +1,4 @@
1
- .TH KESHA 1 "May 2026" "kesha 1.18.4" "User Commands"
1
+ .TH KESHA 1 "May 2026" "kesha 1.18.6" "User Commands"
2
2
  .SH NAME
3
3
  kesha \- open-source local voice toolkit
4
4
  .SH SYNOPSIS
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drakulavich/kesha-voice-kit",
3
- "version": "1.18.4",
3
+ "version": "1.18.6",
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.18.4"
35
+ "version": "1.18.6"
36
36
  },
37
37
  "scripts": {
38
38
  "test": "bun test",
package/src/cli/main.ts CHANGED
@@ -17,6 +17,7 @@ import { formatToonOutput } from "../toon";
17
17
  import { artifactFromFile, createStatsRecorder } from "../stats";
18
18
  import { createPercentProgress } from "../progress";
19
19
  import { getPendingSignalExitCode, waitForPendingSignalCleanup } from "../process-tree";
20
+ import type { TranscriptionSegment } from "../types";
20
21
 
21
22
  interface MainCommandArgs {
22
23
  _: string[];
@@ -117,6 +118,25 @@ export function shouldReportTranscribeProgress(input: {
117
118
  return input.stderrIsTty || !input.stdoutIsTty;
118
119
  }
119
120
 
121
+ const AUDIO_LANG_ID_LONG_AUDIO_THRESHOLD_SECONDS = 10 * 60;
122
+
123
+ export function estimateTranscriptDurationSeconds(segments: TranscriptionSegment[]): number | null {
124
+ const duration = segments.reduce((max, segment) => {
125
+ const end = Number(segment.end);
126
+ return Number.isFinite(end) && end > max ? end : max;
127
+ }, 0);
128
+ return duration > 0 ? duration : null;
129
+ }
130
+
131
+ export function shouldRunAudioLanguageDetection(input: {
132
+ wantsLangId: boolean;
133
+ transcriptDurationSeconds: number | null;
134
+ }): boolean {
135
+ if (!input.wantsLangId) return false;
136
+ if (input.transcriptDurationSeconds === null) return true;
137
+ return input.transcriptDurationSeconds <= AUDIO_LANG_ID_LONG_AUDIO_THRESHOLD_SECONDS;
138
+ }
139
+
120
140
  export const mainCommand = defineCommand({
121
141
  meta: {
122
142
  name: "kesha",
@@ -285,32 +305,25 @@ export const mainCommand = defineCommand({
285
305
  estimatedTotalMs: args.speakers ? 60 * 60 * 1000 : 30 * 60 * 1000,
286
306
  })
287
307
  : null;
288
- // Run audio lang-id and transcription concurrently, but do not leave
289
- // the sibling engine process alive if one side fails first.
290
- const engineAbort = new AbortController();
291
- const audioPromise = wantsLangId
292
- ? stats.timeStage("lang_id_audio", () =>
293
- detectAudioLanguageEngine(file, { signal: engineAbort.signal })
294
- )
295
- : Promise.resolve(null);
296
- const transcriptPromise = stats.timeStage("transcribe", () =>
308
+ const transcript = await stats.timeStage("transcribe", () =>
297
309
  transcribeWithSegments(file, {
298
310
  vad: vadMode,
299
- signal: engineAbort.signal,
300
311
  timestamps: args.timestamps,
301
312
  speakers: args.speakers,
302
313
  })
303
314
  );
304
- let audioResult: LangDetectResult | null;
305
- let transcript: Awaited<ReturnType<typeof transcribeWithSegments>>;
306
- try {
307
- [audioResult, transcript] = await Promise.all([audioPromise, transcriptPromise]);
308
- } catch (err) {
309
- engineAbort.abort();
310
- await Promise.allSettled([audioPromise, transcriptPromise]);
311
- throw err;
312
- }
313
315
  const { text, segments } = transcript;
316
+ const transcriptDurationSeconds = estimateTranscriptDurationSeconds(segments);
317
+
318
+ let audioResult: LangDetectResult | null = null;
319
+ if (shouldRunAudioLanguageDetection({ wantsLangId, transcriptDurationSeconds })) {
320
+ audioResult = await stats.timeStage("lang_id_audio", () => detectAudioLanguageEngine(file));
321
+ } else if (wantsLangId) {
322
+ log.debug(
323
+ `skip lang_id_audio for ${file}: transcript duration ${transcriptDurationSeconds?.toFixed(1)}s exceeds ` +
324
+ `${AUDIO_LANG_ID_LONG_AUDIO_THRESHOLD_SECONDS}s`,
325
+ );
326
+ }
314
327
 
315
328
  let audioLanguage: LangDetectResult | undefined;
316
329
  if (audioResult && audioResult.code) {
package/src/cli.ts CHANGED
@@ -18,7 +18,9 @@ export {
18
18
  mainCommand,
19
19
  detectLanguage,
20
20
  checkLanguageMismatch,
21
+ estimateTranscriptDurationSeconds,
21
22
  resolveOutputFormat,
23
+ shouldRunAudioLanguageDetection,
22
24
  shouldReportTranscribeProgress,
23
25
  } from "./cli/main";
24
26
  export type { ResolvedOutputFormat } from "./cli/main";