@drakulavich/kesha-voice-kit 1.19.0-beta.1 → 1.19.0-beta.2
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 +17 -3
- package/completions/kesha.bash +3 -2
- package/completions/kesha.fish +5 -0
- package/completions/kesha.zsh +8 -1
- package/docs/diagnostic-logs.md +93 -0
- package/man/kesha.1 +11 -1
- package/package.json +4 -5
- package/src/cli/dispatch.ts +7 -0
- package/src/cli/install.ts +54 -15
- package/src/cli/logs.ts +107 -0
- package/src/cli/main.ts +46 -1
- package/src/cli/say.ts +34 -0
- package/src/cli/support-bundle.ts +10 -1
- package/src/cli.ts +1 -0
- package/src/diagnostic-events.ts +14 -0
- package/src/diagnostic-log.ts +338 -0
- package/src/diagnostic-paths.ts +22 -0
- package/src/doctor.ts +86 -25
- package/src/fluid-kokoro-cache.ts +47 -0
- package/src/install-plan.ts +23 -13
- package/src/shell-artifacts.ts +2 -0
- package/src/status.ts +8 -15
- package/src/support-bundle.ts +47 -0
- package/scripts/postinstall.cjs +0 -25
package/README.md
CHANGED
|
@@ -96,10 +96,10 @@ Each segment gets a `speaker` integer (cluster ID, stable within one file). Linu
|
|
|
96
96
|
|
|
97
97
|
## Text-to-speech
|
|
98
98
|
|
|
99
|
-
Kesha speaks back via Kokoro-82M (English) and Vosk-TTS (Russian) — voice auto-picks from the text's language. On darwin-arm64, Kokoro uses FluidAudio CoreML instead of ONNX:
|
|
99
|
+
Kesha speaks back via Kokoro-82M (English) and Vosk-TTS (Russian) — voice auto-picks from the text's language. On darwin-arm64, Kokoro uses FluidAudio CoreML instead of ONNX; FluidAudio stores that CoreML cache at `~/.cache/fluidaudio/Models/kokoro`, outside Kesha's pinned model cache:
|
|
100
100
|
|
|
101
101
|
```bash
|
|
102
|
-
kesha install --tts #
|
|
102
|
+
kesha install --tts # TTS, opt-in; Darwin Kokoro uses FluidAudio cache
|
|
103
103
|
kesha say "Hello, world" > hello.wav
|
|
104
104
|
kesha say "Привет, мир" > privet.wav # auto-routes (Milena on darwin, ru-vosk-m02 elsewhere)
|
|
105
105
|
```
|
|
@@ -288,7 +288,7 @@ are missing.
|
|
|
288
288
|
| SpeechBrain ECAPA-TDNN | Audio language detection | ~86MB | [HuggingFace](https://huggingface.co/speechbrain/lang-id-voxlingua107-ecapa) |
|
|
289
289
|
| Apple NLLanguageRecognizer | Text language detection | built-in | macOS system framework |
|
|
290
290
|
| Silero VAD v5 (opt-in) | Voice activity detection | ~2.3MB | [snakers4/silero-vad](https://github.com/snakers4/silero-vad) |
|
|
291
|
-
| Kokoro-82M / Vosk-TTS (opt-in) | Text-to-speech | ~990MB | [FluidAudio Kokoro](https://github.com/FluidInference/FluidAudio) on darwin-arm64; ONNX Kokoro elsewhere · [Vosk-TTS](https://github.com/alphacep/vosk-tts) |
|
|
291
|
+
| Kokoro-82M / Vosk-TTS (opt-in) | Text-to-speech | ~990MB | [FluidAudio Kokoro](https://github.com/FluidInference/FluidAudio) on darwin-arm64 (FluidAudio cache, not Kesha-verified); ONNX Kokoro elsewhere · [Vosk-TTS](https://github.com/alphacep/vosk-tts) |
|
|
292
292
|
|
|
293
293
|
All models run through `kesha-engine` — a Rust binary using [FluidAudio](https://github.com/FluidInference/FluidAudio) (CoreML) on Apple Silicon and [ort](https://github.com/pykeio/ort) (ONNX Runtime) on other platforms.
|
|
294
294
|
|
|
@@ -321,9 +321,23 @@ Kesha can collect local diagnostics without downloading models or mutating cache
|
|
|
321
321
|
```bash
|
|
322
322
|
kesha doctor --json --redact
|
|
323
323
|
kesha support-bundle --output kesha-support.tar.gz
|
|
324
|
+
kesha support-bundle --include-logs --output kesha-support-with-logs.tar.gz
|
|
325
|
+
kesha logs status
|
|
326
|
+
kesha logs status --json
|
|
324
327
|
```
|
|
325
328
|
|
|
326
329
|
`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.
|
|
330
|
+
Diagnostic log contents are excluded by default; pass `--include-logs` to add a
|
|
331
|
+
bounded tail of Kesha's privacy-safe NDJSON diagnostic log.
|
|
332
|
+
|
|
333
|
+
`kesha logs` manages local, rotated diagnostic logs for troubleshooting. Logs
|
|
334
|
+
default to `retain-on-failure` and also support explicit `off` and `on` modes.
|
|
335
|
+
Use `kesha logs status --json` for a stable machine-readable status contract
|
|
336
|
+
covering the active path, mode, size, rotation settings, and rotated-file list.
|
|
337
|
+
They use content-free NDJSON events: command/stage names, versions, durations,
|
|
338
|
+
exit codes, and coarse audio metadata only. They must not store audio,
|
|
339
|
+
transcripts, input text, generated speech text, file names, full paths, raw
|
|
340
|
+
stdout/stderr, environment variables, tokens, or URLs. See [Diagnostic logs](docs/diagnostic-logs.md).
|
|
327
341
|
|
|
328
342
|
## Local Stats privacy and lifecycle
|
|
329
343
|
|
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 init install manpage record say stats status support-bundle"
|
|
9
|
+
commands="completions doctor init install logs manpage record say stats status support-bundle"
|
|
10
10
|
|
|
11
11
|
if [[ "$COMP_CWORD" -eq 1 ]]; then
|
|
12
12
|
if [[ "$cur" == -* ]]; then
|
|
@@ -22,12 +22,13 @@ _kesha_completion() {
|
|
|
22
22
|
doctor) opts="--help -h --json --redact" ;;
|
|
23
23
|
init) opts="--help -h --coreml --onnx --no-cache --plan --yes --tts --vad --diarize" ;;
|
|
24
24
|
install) opts="--help -h --coreml --onnx --no-cache --plan --tts --vad --diarize" ;;
|
|
25
|
+
logs) opts="--help -h --json" ;;
|
|
25
26
|
manpage) opts="--help -h" ;;
|
|
26
27
|
record) opts="--help -h --out --max-seconds --debug" ;;
|
|
27
28
|
say) opts="--help -h --voice --lang --out --rate --list-voices --ssml --format --bitrate --sample-rate --no-expand-abbrev --verbose --debug" ;;
|
|
28
29
|
stats) opts="--help -h --format" ;;
|
|
29
30
|
status) opts="--help -h --disk" ;;
|
|
30
|
-
support-bundle) opts="--help -h --output" ;;
|
|
31
|
+
support-bundle) opts="--help -h --output --include-logs" ;;
|
|
31
32
|
*) opts="--help -h --version -v --json --toon --timestamps --speakers --include-errors --verbose --format --lang --debug --vad --no-vad" ;;
|
|
32
33
|
esac
|
|
33
34
|
|
package/completions/kesha.fish
CHANGED
|
@@ -4,6 +4,7 @@ complete -c kesha -n '__fish_use_subcommand' -a 'completions' -d 'Print shell co
|
|
|
4
4
|
complete -c kesha -n '__fish_use_subcommand' -a 'doctor' -d 'Collect support diagnostics without changing local state'
|
|
5
5
|
complete -c kesha -n '__fish_use_subcommand' -a 'init' -d 'Interactive setup guide for Kesha features'
|
|
6
6
|
complete -c kesha -n '__fish_use_subcommand' -a 'install' -d 'Download inference engine and models'
|
|
7
|
+
complete -c kesha -n '__fish_use_subcommand' -a 'logs' -d 'Manage local privacy-safe diagnostic logs'
|
|
7
8
|
complete -c kesha -n '__fish_use_subcommand' -a 'manpage' -d 'Print the kesha(1) manpage'
|
|
8
9
|
complete -c kesha -n '__fish_use_subcommand' -a 'record' -d 'Record microphone audio to a WAV file'
|
|
9
10
|
complete -c kesha -n '__fish_use_subcommand' -a 'say' -d 'Synthesize speech from text (TTS). Writes audio to stdout (or --out file). Defaults to WAV; use --format ogg-opus for messenger-ready voice notes.'
|
|
@@ -50,6 +51,9 @@ complete -c kesha -n '__fish_seen_subcommand_from install' -l plan -d 'Show down
|
|
|
50
51
|
complete -c kesha -n '__fish_seen_subcommand_from install' -l tts -d 'Also install TTS models (Kokoro EN + Vosk-TTS RU, ~990MB)'
|
|
51
52
|
complete -c kesha -n '__fish_seen_subcommand_from install' -l vad -d 'Also install Silero VAD (~2.3MB) for long-audio preprocessing'
|
|
52
53
|
complete -c kesha -n '__fish_seen_subcommand_from install' -l diarize -d 'Also install the Sortformer streaming-diarization model (~245MB, darwin-arm64 only — #199)'
|
|
54
|
+
complete -c kesha -n '__fish_seen_subcommand_from logs' -l help -d 'Show help'
|
|
55
|
+
complete -c kesha -n '__fish_seen_subcommand_from logs' -s h -d 'Show help'
|
|
56
|
+
complete -c kesha -n '__fish_seen_subcommand_from logs' -l json -d 'Output status as JSON'
|
|
53
57
|
complete -c kesha -n '__fish_seen_subcommand_from manpage' -l help -d 'Show help'
|
|
54
58
|
complete -c kesha -n '__fish_seen_subcommand_from manpage' -s h -d 'Show help'
|
|
55
59
|
complete -c kesha -n '__fish_seen_subcommand_from record' -l help -d 'Show help'
|
|
@@ -80,3 +84,4 @@ complete -c kesha -n '__fish_seen_subcommand_from status' -l disk -d 'Include re
|
|
|
80
84
|
complete -c kesha -n '__fish_seen_subcommand_from support-bundle' -l help -d 'Show help'
|
|
81
85
|
complete -c kesha -n '__fish_seen_subcommand_from support-bundle' -s h -d 'Show help'
|
|
82
86
|
complete -c kesha -n '__fish_seen_subcommand_from support-bundle' -l output -r -d 'Write archive to this .tar.gz path'
|
|
87
|
+
complete -c kesha -n '__fish_seen_subcommand_from support-bundle' -l include-logs -d 'Include a bounded tail of privacy-safe diagnostic logs'
|
package/completions/kesha.zsh
CHANGED
|
@@ -8,6 +8,7 @@ _kesha() {
|
|
|
8
8
|
'doctor:Collect support diagnostics without changing local state'
|
|
9
9
|
'init:Interactive setup guide for Kesha features'
|
|
10
10
|
'install:Download inference engine and models'
|
|
11
|
+
'logs:Manage local privacy-safe diagnostic logs'
|
|
11
12
|
'manpage:Print the kesha(1) manpage'
|
|
12
13
|
'record:Record microphone audio to a WAV file'
|
|
13
14
|
'say:Synthesize speech from text (TTS). Writes audio to stdout (or --out file). Defaults to WAV; use --format ogg-opus for messenger-ready voice notes.'
|
|
@@ -73,6 +74,11 @@ _kesha() {
|
|
|
73
74
|
'--vad[Also install Silero VAD (~2.3MB) for long-audio preprocessing]' \
|
|
74
75
|
'--diarize[Also install the Sortformer streaming-diarization model (~245MB, darwin-arm64 only — #199)]'
|
|
75
76
|
;;
|
|
77
|
+
logs)
|
|
78
|
+
_arguments '--help[Show help]' \
|
|
79
|
+
'-h[Show help]' \
|
|
80
|
+
'--json[Output status as JSON]'
|
|
81
|
+
;;
|
|
76
82
|
manpage)
|
|
77
83
|
_arguments '--help[Show help]' \
|
|
78
84
|
'-h[Show help]'
|
|
@@ -113,7 +119,8 @@ _kesha() {
|
|
|
113
119
|
support-bundle)
|
|
114
120
|
_arguments '--help[Show help]' \
|
|
115
121
|
'-h[Show help]' \
|
|
116
|
-
'--output=[Write archive to this .tar.gz path]:output:'
|
|
122
|
+
'--output=[Write archive to this .tar.gz path]:output:' \
|
|
123
|
+
'--include-logs[Include a bounded tail of privacy-safe diagnostic logs]'
|
|
117
124
|
;;
|
|
118
125
|
*)
|
|
119
126
|
_arguments '--help[Show help]' \
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Diagnostic Logs
|
|
2
|
+
|
|
3
|
+
Kesha diagnostic logs are local, rotated NDJSON event logs for install, engine,
|
|
4
|
+
and runtime troubleshooting. They are separate from Kesha Stats: Stats stores
|
|
5
|
+
aggregate timings in SQLite after explicit opt-in, while diagnostic logs keep a
|
|
6
|
+
small local ring buffer of structured events.
|
|
7
|
+
|
|
8
|
+
Diagnostic logs default to `retain-on-failure`: passing commands leave no log
|
|
9
|
+
file, while failed commands can preserve a small local trace for debugging.
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
kesha logs status
|
|
13
|
+
kesha logs status --json
|
|
14
|
+
kesha logs enable
|
|
15
|
+
kesha logs disable
|
|
16
|
+
kesha logs mode retain-on-failure
|
|
17
|
+
kesha logs path
|
|
18
|
+
kesha logs reset
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Modes
|
|
22
|
+
|
|
23
|
+
Diagnostic logs follow a Playwright-style retention model:
|
|
24
|
+
|
|
25
|
+
- `off`: do not record diagnostic events.
|
|
26
|
+
- `on`: append events to the rotated local log immediately.
|
|
27
|
+
- `retain-on-failure`: buffer events for the current command and write them only
|
|
28
|
+
if the command fails. This is the default.
|
|
29
|
+
|
|
30
|
+
`kesha logs enable` is shorthand for `kesha logs mode on`; `kesha logs disable`
|
|
31
|
+
is shorthand for `kesha logs mode off`. `retain-on-failure` keeps passing runs
|
|
32
|
+
artifact-free while failed runs keep enough context to debug.
|
|
33
|
+
|
|
34
|
+
`kesha logs status --json` prints the same local status as a stable JSON object:
|
|
35
|
+
`dir`, `activePath`, `statePath`, `exists`, `activeSizeBytes`, `rotatedFiles`,
|
|
36
|
+
`totalSizeBytes`, `mode`, `maxBytes`, and `retain`.
|
|
37
|
+
|
|
38
|
+
## Privacy Contract
|
|
39
|
+
|
|
40
|
+
Diagnostic logs are content-free. Event payloads are built from an allowlist of
|
|
41
|
+
typed fields such as command name, app version, platform, backend, feature flags,
|
|
42
|
+
stage names, exit codes, duration numbers, and stable error codes.
|
|
43
|
+
|
|
44
|
+
When enabled, `kesha install`, `kesha <audio>`, and `kesha say` record command
|
|
45
|
+
lifecycle events such as `command.start`, `input.audio`, `input.missing`,
|
|
46
|
+
`engine.exit`, and `command.finish`. These events use only counts, booleans,
|
|
47
|
+
format extensions, duration milliseconds, and bucket labels. In the default
|
|
48
|
+
`retain-on-failure` mode, successful runs still leave no log file behind.
|
|
49
|
+
|
|
50
|
+
Diagnostic logs must not store:
|
|
51
|
+
|
|
52
|
+
- audio bytes
|
|
53
|
+
- transcripts
|
|
54
|
+
- input text or generated speech text
|
|
55
|
+
- file names, basenames, or full file paths
|
|
56
|
+
- raw stdout or stderr
|
|
57
|
+
- environment variables
|
|
58
|
+
- URLs, tokens, secrets, API keys, or cloud identifiers
|
|
59
|
+
- model files or model contents
|
|
60
|
+
|
|
61
|
+
For audio shape, logs should use coarse metadata such as extension, duration
|
|
62
|
+
bucket, size bucket, sample-rate bucket, or channel count. Do not log a path or
|
|
63
|
+
basename even when it looks harmless; names like `therapy-session.m4a` can be
|
|
64
|
+
private.
|
|
65
|
+
|
|
66
|
+
## Storage
|
|
67
|
+
|
|
68
|
+
Default paths:
|
|
69
|
+
|
|
70
|
+
- macOS: `~/Library/Logs/kesha/kesha.ndjson`
|
|
71
|
+
- Linux: `${XDG_STATE_HOME:-~/.local/state}/kesha/logs/kesha.ndjson`
|
|
72
|
+
- Windows: `%LOCALAPPDATA%\kesha\logs\kesha.ndjson`
|
|
73
|
+
|
|
74
|
+
Set `KESHA_LOG_DIR` to override the directory. The active file is
|
|
75
|
+
`kesha.ndjson`; rotated files are named `kesha.1.ndjson`, `kesha.2.ndjson`, and
|
|
76
|
+
so on.
|
|
77
|
+
|
|
78
|
+
The first implementation rotates at 10 MB and keeps 5 rotated files. `kesha logs
|
|
79
|
+
reset` deletes Kesha log files but preserves the selected mode.
|
|
80
|
+
|
|
81
|
+
## Support Bundles
|
|
82
|
+
|
|
83
|
+
`kesha support-bundle` does not include diagnostic log contents by default. Use
|
|
84
|
+
`kesha support-bundle --include-logs` to add a bounded tail of the active
|
|
85
|
+
already-sanitized NDJSON log when a support issue needs recent command events.
|
|
86
|
+
Recommended flow for a bug report:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
kesha logs status
|
|
90
|
+
kesha logs mode retain-on-failure
|
|
91
|
+
# reproduce the failure
|
|
92
|
+
kesha support-bundle --include-logs --output kesha-support-with-logs.tar.gz
|
|
93
|
+
```
|
package/man/kesha.1
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
.TH KESHA 1 "May 2026" "kesha 1.19.0-beta.
|
|
1
|
+
.TH KESHA 1 "May 2026" "kesha 1.19.0-beta.2" "User Commands"
|
|
2
2
|
.SH NAME
|
|
3
3
|
kesha \- open-source local voice toolkit
|
|
4
4
|
.SH SYNOPSIS
|
|
@@ -23,6 +23,9 @@ Interactive setup guide for Kesha features
|
|
|
23
23
|
.B install
|
|
24
24
|
Download inference engine and models
|
|
25
25
|
.TP
|
|
26
|
+
.B logs
|
|
27
|
+
Manage local privacy\-safe diagnostic logs
|
|
28
|
+
.TP
|
|
26
29
|
.B manpage
|
|
27
30
|
Print the kesha(1) manpage
|
|
28
31
|
.TP
|
|
@@ -129,6 +132,10 @@ Also install Silero VAD (~2.3MB) for long\-audio preprocessing
|
|
|
129
132
|
.TP
|
|
130
133
|
.B \-\-diarize
|
|
131
134
|
Also install the Sortformer streaming\-diarization model (~245MB, darwin\-arm64 only — #199)
|
|
135
|
+
.SS logs
|
|
136
|
+
.TP
|
|
137
|
+
.B \-\-json
|
|
138
|
+
Output status as JSON
|
|
132
139
|
.SS record
|
|
133
140
|
.TP
|
|
134
141
|
.B \-\-out
|
|
@@ -188,6 +195,9 @@ Include recursive cache disk usage
|
|
|
188
195
|
.TP
|
|
189
196
|
.B \-\-output
|
|
190
197
|
Write archive to this .tar.gz path
|
|
198
|
+
.TP
|
|
199
|
+
.B \-\-include\-logs
|
|
200
|
+
Include a bounded tail of privacy\-safe diagnostic logs
|
|
191
201
|
.SH FILES
|
|
192
202
|
.TP
|
|
193
203
|
.B completions/kesha.bash
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@drakulavich/kesha-voice-kit",
|
|
3
|
-
"version": "1.19.0-beta.
|
|
3
|
+
"version": "1.19.0-beta.2",
|
|
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": {
|
|
@@ -19,9 +19,9 @@
|
|
|
19
19
|
"tsconfig.json",
|
|
20
20
|
"openclaw.plugin.json",
|
|
21
21
|
"openclaw-plugin.cjs",
|
|
22
|
+
"docs/diagnostic-logs.md",
|
|
22
23
|
"docs/product-positioning.md",
|
|
23
24
|
"SKILL.md",
|
|
24
|
-
"scripts/postinstall.cjs",
|
|
25
25
|
"LICENSE",
|
|
26
26
|
"NOTICES.md",
|
|
27
27
|
"README.md"
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
]
|
|
33
33
|
},
|
|
34
34
|
"keshaEngine": {
|
|
35
|
-
"version": "1.19.0-beta.
|
|
35
|
+
"version": "1.19.0-beta.2"
|
|
36
36
|
},
|
|
37
37
|
"scripts": {
|
|
38
38
|
"test": "bun test",
|
|
@@ -49,8 +49,7 @@
|
|
|
49
49
|
"check": "bun run lint && bun run check:versions && bun run test:cli-fast",
|
|
50
50
|
"check:workflows": "bun .github/scripts/check-workflows.ts",
|
|
51
51
|
"check:release-manifest": "bun .github/scripts/release-manifest.mjs --check",
|
|
52
|
-
"check:versions": "bun .github/scripts/check-versions.ts"
|
|
53
|
-
"postinstall": "node scripts/postinstall.cjs"
|
|
52
|
+
"check:versions": "bun .github/scripts/check-versions.ts"
|
|
54
53
|
},
|
|
55
54
|
"keywords": [
|
|
56
55
|
"asr",
|
package/src/cli/dispatch.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { completionsCommand } from "./completions";
|
|
|
6
6
|
import { doctorCommand } from "./doctor";
|
|
7
7
|
import { initCommand } from "./init";
|
|
8
8
|
import { installCommand } from "./install";
|
|
9
|
+
import { logsCommand } from "./logs";
|
|
9
10
|
import { manpageCommand } from "./manpage";
|
|
10
11
|
import { recordCommand } from "./record";
|
|
11
12
|
import { sayCommand } from "./say";
|
|
@@ -18,6 +19,7 @@ const SUBCOMMANDS = [
|
|
|
18
19
|
"doctor",
|
|
19
20
|
"init",
|
|
20
21
|
"install",
|
|
22
|
+
"logs",
|
|
21
23
|
"status",
|
|
22
24
|
"record",
|
|
23
25
|
"say",
|
|
@@ -54,6 +56,11 @@ export async function runCli(rawArgs = process.argv.slice(2)): Promise<void> {
|
|
|
54
56
|
return;
|
|
55
57
|
}
|
|
56
58
|
|
|
59
|
+
if (firstArg === "logs") {
|
|
60
|
+
await runMain(logsCommand, { rawArgs: restArgs });
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
57
64
|
if (firstArg === "manpage") {
|
|
58
65
|
await runMain(manpageCommand, { rawArgs: restArgs });
|
|
59
66
|
return;
|
package/src/cli/install.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { renderInstallPlan } from "../install-plan";
|
|
|
5
5
|
import { maybeAskForStar } from "../star";
|
|
6
6
|
import { log } from "../log";
|
|
7
7
|
import { packageVersion } from "../package-info";
|
|
8
|
+
import { createDiagnosticLogSession, type DiagnosticLogSession } from "../diagnostic-log";
|
|
8
9
|
|
|
9
10
|
export interface InstallCommandArgs {
|
|
10
11
|
coreml: boolean;
|
|
@@ -46,6 +47,28 @@ function defaultBackendForPlatform(): string | undefined {
|
|
|
46
47
|
return undefined;
|
|
47
48
|
}
|
|
48
49
|
|
|
50
|
+
type InstallDiagnosticErrorKind = "validation_failed" | "install_failed";
|
|
51
|
+
|
|
52
|
+
function finishInstallDiagnostic(
|
|
53
|
+
diagnosticLog: DiagnosticLogSession | null,
|
|
54
|
+
startedAt: number,
|
|
55
|
+
status: "success" | "failed",
|
|
56
|
+
errorKind?: InstallDiagnosticErrorKind,
|
|
57
|
+
): void {
|
|
58
|
+
if (!diagnosticLog) return;
|
|
59
|
+
try {
|
|
60
|
+
diagnosticLog.event("command.finish", {
|
|
61
|
+
command: "install",
|
|
62
|
+
status,
|
|
63
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
64
|
+
...(errorKind ? { errorKind } : {}),
|
|
65
|
+
});
|
|
66
|
+
diagnosticLog.finish(status);
|
|
67
|
+
} catch (err) {
|
|
68
|
+
log.debug(`install diagnostic log finish dropped: ${err instanceof Error ? err.message : String(err)}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
49
72
|
export async function performInstall(
|
|
50
73
|
noCache: boolean,
|
|
51
74
|
backend?: string,
|
|
@@ -58,26 +81,42 @@ export async function performInstall(
|
|
|
58
81
|
log.info(await renderInstallPlan({ noCache, backend, tts, vad, diarize }));
|
|
59
82
|
return;
|
|
60
83
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
);
|
|
66
|
-
process.exit(1);
|
|
67
|
-
}
|
|
68
|
-
const platformBackend = defaultBackendForPlatform();
|
|
69
|
-
if (backend && !process.env.KESHA_ENGINE_BIN && platformBackend && backend !== platformBackend) {
|
|
70
|
-
log.error(
|
|
71
|
-
`Requested backend "${backend}" is not available on this platform; ` +
|
|
72
|
-
`the release engine uses "${platformBackend}".`,
|
|
73
|
-
);
|
|
74
|
-
process.exit(1);
|
|
75
|
-
}
|
|
84
|
+
|
|
85
|
+
let diagnosticLog: DiagnosticLogSession | null = null;
|
|
86
|
+
let errorKind: InstallDiagnosticErrorKind = "install_failed";
|
|
87
|
+
const startedAt = performance.now();
|
|
76
88
|
try {
|
|
89
|
+
diagnosticLog = createDiagnosticLogSession();
|
|
90
|
+
diagnosticLog.event("command.start", {
|
|
91
|
+
command: "install",
|
|
92
|
+
backend: backend ?? "auto",
|
|
93
|
+
noCache,
|
|
94
|
+
tts,
|
|
95
|
+
vad,
|
|
96
|
+
diarize,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
if (diarize && !(process.platform === "darwin" && process.arch === "arm64")) {
|
|
100
|
+
errorKind = "validation_failed";
|
|
101
|
+
throw new Error(
|
|
102
|
+
"--diarize is currently darwin-arm64 only " +
|
|
103
|
+
"(see https://github.com/drakulavich/kesha-voice-kit/issues/199).",
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
const platformBackend = defaultBackendForPlatform();
|
|
107
|
+
if (backend && !process.env.KESHA_ENGINE_BIN && platformBackend && backend !== platformBackend) {
|
|
108
|
+
errorKind = "validation_failed";
|
|
109
|
+
throw new Error(
|
|
110
|
+
`Requested backend "${backend}" is not available on this platform; ` +
|
|
111
|
+
`the release engine uses "${platformBackend}".`,
|
|
112
|
+
);
|
|
113
|
+
}
|
|
77
114
|
await downloadEngine(noCache, backend, { tts, vad, diarize });
|
|
78
115
|
await maybeAskForStar(getEngineBinPath(), packageVersion, log);
|
|
116
|
+
finishInstallDiagnostic(diagnosticLog, startedAt, "success");
|
|
79
117
|
} catch (err: unknown) {
|
|
80
118
|
const message = err instanceof Error ? err.message : String(err);
|
|
119
|
+
finishInstallDiagnostic(diagnosticLog, startedAt, "failed", errorKind);
|
|
81
120
|
log.error(message);
|
|
82
121
|
process.exit(1);
|
|
83
122
|
}
|
package/src/cli/logs.ts
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { defineCommand } from "citty";
|
|
2
|
+
import {
|
|
3
|
+
getDiagnosticLogStatus,
|
|
4
|
+
humanBytes,
|
|
5
|
+
parseDiagnosticLogMode,
|
|
6
|
+
resetDiagnosticLogs,
|
|
7
|
+
resolveDiagnosticLogPath,
|
|
8
|
+
setDiagnosticLogMode,
|
|
9
|
+
} from "../diagnostic-log";
|
|
10
|
+
import { log } from "../log";
|
|
11
|
+
|
|
12
|
+
interface LogsCommandArgs {
|
|
13
|
+
action?: string;
|
|
14
|
+
value?: string;
|
|
15
|
+
json?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const logsCommand = defineCommand({
|
|
19
|
+
meta: {
|
|
20
|
+
name: "logs",
|
|
21
|
+
description: "Manage local privacy-safe diagnostic logs",
|
|
22
|
+
},
|
|
23
|
+
args: {
|
|
24
|
+
action: {
|
|
25
|
+
type: "positional",
|
|
26
|
+
required: false,
|
|
27
|
+
description: "Action: status | enable | disable | mode | path | reset",
|
|
28
|
+
},
|
|
29
|
+
value: {
|
|
30
|
+
type: "positional",
|
|
31
|
+
required: false,
|
|
32
|
+
description: "Action value: off | on | retain-on-failure",
|
|
33
|
+
},
|
|
34
|
+
json: {
|
|
35
|
+
type: "boolean",
|
|
36
|
+
description: "Output status as JSON",
|
|
37
|
+
default: false,
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
run({ args }: { args: LogsCommandArgs }) {
|
|
41
|
+
const action = args.action ?? "status";
|
|
42
|
+
if (args.json && action !== "status") {
|
|
43
|
+
log.error("usage: kesha logs status --json");
|
|
44
|
+
process.exit(2);
|
|
45
|
+
}
|
|
46
|
+
switch (action) {
|
|
47
|
+
case "enable": {
|
|
48
|
+
const status = setDiagnosticLogMode("on");
|
|
49
|
+
log.success("Kesha diagnostic logs enabled");
|
|
50
|
+
log.info(`Mode: ${status.mode}`);
|
|
51
|
+
log.info(`Path: ${status.activePath}`);
|
|
52
|
+
log.info(`Rotation: ${humanBytes(status.maxBytes)}, keep ${status.retain}`);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
case "disable": {
|
|
56
|
+
const status = setDiagnosticLogMode("off");
|
|
57
|
+
log.info("Kesha diagnostic logs disabled");
|
|
58
|
+
log.info(`Mode: ${status.mode}`);
|
|
59
|
+
log.info(`Path: ${status.activePath}`);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
case "status": {
|
|
63
|
+
const status = getDiagnosticLogStatus();
|
|
64
|
+
if (args.json) {
|
|
65
|
+
console.log(JSON.stringify(status, null, 2));
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
log.info(`Kesha diagnostic logs: ${status.mode === "off" ? "disabled" : "enabled"}`);
|
|
69
|
+
log.info(`Mode: ${status.mode}`);
|
|
70
|
+
log.info(`Path: ${status.activePath}`);
|
|
71
|
+
log.info(`Size: ${humanBytes(status.totalSizeBytes)}`);
|
|
72
|
+
log.info(`Rotated files: ${status.rotatedFiles.length}`);
|
|
73
|
+
log.info(`Rotation: ${humanBytes(status.maxBytes)}, keep ${status.retain}`);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
case "mode": {
|
|
77
|
+
if (!args.value) {
|
|
78
|
+
const status = getDiagnosticLogStatus();
|
|
79
|
+
log.info(`Kesha diagnostic log mode: ${status.mode}`);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const mode = parseDiagnosticLogMode(args.value);
|
|
83
|
+
if (!mode) {
|
|
84
|
+
log.error("usage: kesha logs mode <off|on|retain-on-failure>");
|
|
85
|
+
process.exit(2);
|
|
86
|
+
}
|
|
87
|
+
const status = setDiagnosticLogMode(mode);
|
|
88
|
+
log.info(`Kesha diagnostic log mode set to ${status.mode}`);
|
|
89
|
+
log.info(`Path: ${status.activePath}`);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
case "path": {
|
|
93
|
+
log.info(resolveDiagnosticLogPath());
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
case "reset": {
|
|
97
|
+
const result = resetDiagnosticLogs();
|
|
98
|
+
log.info(`Kesha diagnostic logs reset: ${result.deleted} file(s), ${humanBytes(result.bytes)} deleted`);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
default:
|
|
102
|
+
log.error(`unknown logs action '${action}'`);
|
|
103
|
+
log.warn("supported: enable, disable, mode, status, path, reset");
|
|
104
|
+
process.exit(2);
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
});
|
package/src/cli/main.ts
CHANGED
|
@@ -18,6 +18,8 @@ import { artifactFromFile, createStatsRecorder } from "../stats";
|
|
|
18
18
|
import { createPercentProgress } from "../progress";
|
|
19
19
|
import { getPendingSignalExitCode, waitForPendingSignalCleanup } from "../process-tree";
|
|
20
20
|
import type { TranscriptionSegment } from "../types";
|
|
21
|
+
import { createDiagnosticLogSession } from "../diagnostic-log";
|
|
22
|
+
import { diagnosticSizeBucket } from "../diagnostic-events";
|
|
21
23
|
|
|
22
24
|
interface MainCommandArgs {
|
|
23
25
|
_: string[];
|
|
@@ -149,6 +151,7 @@ export const mainCommand = defineCommand({
|
|
|
149
151
|
" doctor Collect support diagnostics.\n" +
|
|
150
152
|
" init Interactive setup guide for Kesha features.\n" +
|
|
151
153
|
" install Download engine and models.\n" +
|
|
154
|
+
" logs Manage local privacy-safe diagnostic logs.\n" +
|
|
152
155
|
" manpage Print the kesha(1) manpage.\n" +
|
|
153
156
|
" record Record microphone audio to a WAV file.\n" +
|
|
154
157
|
" status Inspect installed backend.\n" +
|
|
@@ -253,6 +256,7 @@ export const mainCommand = defineCommand({
|
|
|
253
256
|
process.exit(2);
|
|
254
257
|
}
|
|
255
258
|
const vadMode = vad ? "on" : noVad ? "off" : "auto";
|
|
259
|
+
const outputFormat = wantsJson ? "json" : wantsToon ? "toon" : wantsTranscript ? "transcript" : "text";
|
|
256
260
|
|
|
257
261
|
if (files.length === 0) {
|
|
258
262
|
log.info(
|
|
@@ -260,6 +264,7 @@ export const mainCommand = defineCommand({
|
|
|
260
264
|
" kesha completions <bash|zsh|fish>\n" +
|
|
261
265
|
" kesha doctor [--json] [--redact]\n" +
|
|
262
266
|
" kesha install [--no-cache]\n" +
|
|
267
|
+
" kesha logs [enable|disable|mode|status|path|reset]\n" +
|
|
263
268
|
" kesha manpage\n" +
|
|
264
269
|
" kesha record --out path.wav [--max-seconds 120]\n" +
|
|
265
270
|
" kesha status\n" +
|
|
@@ -274,6 +279,17 @@ export const mainCommand = defineCommand({
|
|
|
274
279
|
const results: TranscribeResult[] = [];
|
|
275
280
|
const errors: TranscribeErrorRecord[] = [];
|
|
276
281
|
const stats = createStatsRecorder("transcribe");
|
|
282
|
+
const diagnosticLog = createDiagnosticLogSession();
|
|
283
|
+
diagnosticLog.event("command.start", {
|
|
284
|
+
command: "transcribe",
|
|
285
|
+
itemCount: files.length,
|
|
286
|
+
outputFormat,
|
|
287
|
+
vadMode,
|
|
288
|
+
timestamps: args.timestamps,
|
|
289
|
+
speakers: args.speakers,
|
|
290
|
+
includeErrors: args["include-errors"],
|
|
291
|
+
hasExpectedLang: Boolean(args.lang),
|
|
292
|
+
});
|
|
277
293
|
|
|
278
294
|
const wantsLangId = !!(args.lang || args.verbose || wantsJson || wantsToon || wantsTranscript);
|
|
279
295
|
const reportProgress = shouldReportTranscribeProgress({
|
|
@@ -286,12 +302,20 @@ export const mainCommand = defineCommand({
|
|
|
286
302
|
if (!existsSync(file)) {
|
|
287
303
|
hasError = true;
|
|
288
304
|
stats.recordError("input", new Error("File not found"), "file_not_found");
|
|
305
|
+
diagnosticLog.event("input.missing", { command: "transcribe" });
|
|
289
306
|
errors.push({ file, code: "file_not_found", message: "File not found" });
|
|
290
307
|
log.error(`${file}: File not found`);
|
|
291
308
|
continue;
|
|
292
309
|
}
|
|
293
310
|
const inputArtifact = artifactFromFile(file, "input_audio");
|
|
294
|
-
if (inputArtifact)
|
|
311
|
+
if (inputArtifact) {
|
|
312
|
+
stats.recordArtifact(inputArtifact);
|
|
313
|
+
diagnosticLog.event("input.audio", {
|
|
314
|
+
command: "transcribe",
|
|
315
|
+
format: inputArtifact.format || null,
|
|
316
|
+
sizeBucket: diagnosticSizeBucket(inputArtifact.sizeBytes),
|
|
317
|
+
});
|
|
318
|
+
}
|
|
295
319
|
|
|
296
320
|
const startedAt = performance.now();
|
|
297
321
|
let progress: ReturnType<typeof createPercentProgress> | null = null;
|
|
@@ -368,11 +392,24 @@ export const mainCommand = defineCommand({
|
|
|
368
392
|
result.segments = segments;
|
|
369
393
|
}
|
|
370
394
|
results.push(result);
|
|
395
|
+
diagnosticLog.event("engine.exit", {
|
|
396
|
+
command: "transcribe",
|
|
397
|
+
status: "success",
|
|
398
|
+
durationMs: sttTimeMs,
|
|
399
|
+
segmentCount: segments.length,
|
|
400
|
+
ranAudioLangId: audioResult !== null,
|
|
401
|
+
ranTxtLangId: textLanguage !== undefined,
|
|
402
|
+
});
|
|
371
403
|
progress?.finish(`Transcribed ${file}`);
|
|
372
404
|
} catch (err: unknown) {
|
|
373
405
|
progress?.stop();
|
|
374
406
|
hasError = true;
|
|
375
407
|
stats.recordError("transcribe", err);
|
|
408
|
+
diagnosticLog.event("engine.exit", {
|
|
409
|
+
command: "transcribe",
|
|
410
|
+
status: "failed",
|
|
411
|
+
errorKind: "transcribe_failed",
|
|
412
|
+
});
|
|
376
413
|
const message = err instanceof Error ? err.message : String(err);
|
|
377
414
|
errors.push({ file, code: "transcribe_failed", message });
|
|
378
415
|
log.error(`${file}: ${message}`);
|
|
@@ -392,6 +429,14 @@ export const mainCommand = defineCommand({
|
|
|
392
429
|
}
|
|
393
430
|
|
|
394
431
|
stats.finish(hasError ? "failed" : "success", files.length);
|
|
432
|
+
diagnosticLog.event("command.finish", {
|
|
433
|
+
command: "transcribe",
|
|
434
|
+
status: hasError ? "failed" : "success",
|
|
435
|
+
itemCount: files.length,
|
|
436
|
+
resultCount: results.length,
|
|
437
|
+
errorCount: errors.length,
|
|
438
|
+
});
|
|
439
|
+
diagnosticLog.finish(hasError ? "failed" : "success");
|
|
395
440
|
|
|
396
441
|
if (hasError) {
|
|
397
442
|
const signalExitCode = getPendingSignalExitCode();
|