@drakulavich/parakeet-cli 0.8.0 → 0.8.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/bin/parakeet.js +3 -3
- package/package.json +1 -1
- package/src/cli.ts +75 -33
- package/src/coreml-install.ts +23 -14
- package/src/status.ts +62 -12
package/bin/parakeet.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
import { runCli } from "../src/cli.ts";
|
|
3
|
+
|
|
4
|
+
await runCli();
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -19,20 +19,52 @@ export function checkLanguageMismatch(expected: string | undefined, detected: st
|
|
|
19
19
|
return `warning: expected language "${expected}" but detected "${detected}"`;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
export interface InstallOptions {
|
|
23
|
+
coreml: boolean;
|
|
24
|
+
onnx: boolean;
|
|
25
|
+
noCache: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface InstallCommandArgs {
|
|
29
|
+
coreml: boolean;
|
|
30
|
+
onnx: boolean;
|
|
31
|
+
"no-cache": boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface MainCommandArgs {
|
|
35
|
+
_: string[];
|
|
36
|
+
json: boolean;
|
|
37
|
+
lang?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
22
40
|
const pkg = await Bun.file(new URL("../package.json", import.meta.url)).json();
|
|
23
41
|
|
|
24
|
-
|
|
25
|
-
const { coreml, onnx
|
|
42
|
+
export function resolveInstallBackend(options: InstallOptions, macArm64 = isMacArm64()): "coreml" | "onnx" {
|
|
43
|
+
const { coreml, onnx } = options;
|
|
44
|
+
|
|
45
|
+
if (coreml && onnx) {
|
|
46
|
+
throw new Error('Choose only one backend: "--coreml" or "--onnx".');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (coreml) {
|
|
50
|
+
if (!macArm64) {
|
|
51
|
+
throw new Error("CoreML backend is only available on macOS Apple Silicon.");
|
|
52
|
+
}
|
|
53
|
+
return "coreml";
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (onnx) {
|
|
57
|
+
return "onnx";
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return macArm64 ? "coreml" : "onnx";
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function performInstall(options: InstallOptions) {
|
|
64
|
+
const { noCache } = options;
|
|
26
65
|
try {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
log.error("CoreML backend is only available on macOS Apple Silicon.");
|
|
30
|
-
process.exit(1);
|
|
31
|
-
}
|
|
32
|
-
await downloadCoreML(noCache);
|
|
33
|
-
} else if (onnx) {
|
|
34
|
-
await downloadModel(noCache);
|
|
35
|
-
} else if (isMacArm64()) {
|
|
66
|
+
const backend = resolveInstallBackend(options);
|
|
67
|
+
if (backend === "coreml") {
|
|
36
68
|
await downloadCoreML(noCache);
|
|
37
69
|
} else {
|
|
38
70
|
await downloadModel(noCache);
|
|
@@ -66,18 +98,29 @@ export const installCommand = defineCommand({
|
|
|
66
98
|
default: false,
|
|
67
99
|
},
|
|
68
100
|
},
|
|
69
|
-
async run({ args }) {
|
|
101
|
+
async run({ args }: { args: InstallCommandArgs }) {
|
|
70
102
|
await performInstall({ coreml: args.coreml, onnx: args.onnx, noCache: args["no-cache"] });
|
|
71
103
|
},
|
|
72
104
|
});
|
|
73
105
|
|
|
106
|
+
export const statusCommand = defineCommand({
|
|
107
|
+
meta: {
|
|
108
|
+
name: "status",
|
|
109
|
+
description: "Show backend installation status",
|
|
110
|
+
},
|
|
111
|
+
async run() {
|
|
112
|
+
await showStatus();
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
|
|
74
116
|
export const mainCommand = defineCommand({
|
|
75
117
|
meta: {
|
|
76
118
|
name: "parakeet",
|
|
77
119
|
version: pkg.version,
|
|
78
120
|
description:
|
|
79
121
|
"Fast local speech-to-text. 25 languages. CoreML on Apple Silicon, ONNX on CPU.\n" +
|
|
80
|
-
" Run 'parakeet install [--coreml | --onnx] [--no-cache]' to download models
|
|
122
|
+
" Run 'parakeet install [--coreml | --onnx] [--no-cache]' to download models.\n" +
|
|
123
|
+
" Run 'parakeet status' to inspect installed backends.",
|
|
81
124
|
},
|
|
82
125
|
args: {
|
|
83
126
|
json: {
|
|
@@ -90,25 +133,8 @@ export const mainCommand = defineCommand({
|
|
|
90
133
|
description: "Expected language code (ISO 639-1), warn if mismatch",
|
|
91
134
|
},
|
|
92
135
|
},
|
|
93
|
-
async run({ args }) {
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
// Manual subcommand routing: "parakeet install [flags]"
|
|
97
|
-
if (positional[0] === "install") {
|
|
98
|
-
const argv = process.argv;
|
|
99
|
-
const coreml = argv.includes("--coreml");
|
|
100
|
-
const onnx = argv.includes("--onnx");
|
|
101
|
-
const noCache = argv.includes("--no-cache");
|
|
102
|
-
await performInstall({ coreml, onnx, noCache });
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
if (positional[0] === "status") {
|
|
107
|
-
await showStatus();
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const files = positional;
|
|
136
|
+
async run({ args }: { args: MainCommandArgs }) {
|
|
137
|
+
const files = args._;
|
|
112
138
|
|
|
113
139
|
if (files.length === 0) {
|
|
114
140
|
log.info("Usage: parakeet <audio_file> [audio_file ...]\n parakeet install [--coreml | --onnx] [--no-cache]\n parakeet status");
|
|
@@ -144,6 +170,22 @@ export const mainCommand = defineCommand({
|
|
|
144
170
|
},
|
|
145
171
|
});
|
|
146
172
|
|
|
173
|
+
export async function runCli(rawArgs = process.argv.slice(2)): Promise<void> {
|
|
174
|
+
const [firstArg, ...restArgs] = rawArgs;
|
|
175
|
+
|
|
176
|
+
if (firstArg === "install") {
|
|
177
|
+
await runMain(installCommand, { rawArgs: restArgs });
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (firstArg === "status") {
|
|
182
|
+
await runMain(statusCommand, { rawArgs: restArgs });
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
await runMain(mainCommand, { rawArgs });
|
|
187
|
+
}
|
|
188
|
+
|
|
147
189
|
export type TranscribeResult = { file: string; text: string; lang: string };
|
|
148
190
|
|
|
149
191
|
export function formatTextOutput(results: TranscribeResult[]): string {
|
|
@@ -160,5 +202,5 @@ export function formatJsonOutput(results: TranscribeResult[]): string {
|
|
|
160
202
|
}
|
|
161
203
|
|
|
162
204
|
if (import.meta.main) {
|
|
163
|
-
|
|
205
|
+
await runCli();
|
|
164
206
|
}
|
package/src/coreml-install.ts
CHANGED
|
@@ -85,6 +85,18 @@ export function getCoreMLLatestDownloadURL(): string {
|
|
|
85
85
|
return `https://github.com/${GITHUB_REPO}/releases/latest/download/${COREML_BINARY_NAME}`;
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
export function isUnreleasedVersion(version: string): boolean {
|
|
89
|
+
return version === "0.0.0" || version.includes("-");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function getCoreMLBinaryDownloadCandidates(version: string): string[] {
|
|
93
|
+
const versionUrl = getCoreMLDownloadURL(version);
|
|
94
|
+
if (isUnreleasedVersion(version)) {
|
|
95
|
+
return [getCoreMLLatestDownloadURL(), versionUrl];
|
|
96
|
+
}
|
|
97
|
+
return [versionUrl];
|
|
98
|
+
}
|
|
99
|
+
|
|
88
100
|
export function getCoreMLInstallState(opts?: {
|
|
89
101
|
binPath?: string;
|
|
90
102
|
exists?: (path: string) => boolean;
|
|
@@ -165,24 +177,21 @@ export function classifyCoreMLInstallProbe(exitCode: number, stdout: string): Co
|
|
|
165
177
|
}
|
|
166
178
|
|
|
167
179
|
async function fetchCoreMLBinary(): Promise<Response> {
|
|
168
|
-
const latestUrl = getCoreMLLatestDownloadURL();
|
|
169
|
-
let res = await fetch(latestUrl, { redirect: "follow" });
|
|
170
|
-
|
|
171
|
-
if (res.ok) {
|
|
172
|
-
return res;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
180
|
const pkg = await Bun.file(new URL("../package.json", import.meta.url)).json();
|
|
176
|
-
const
|
|
177
|
-
res = await fetch(versionUrl, { redirect: "follow" });
|
|
181
|
+
const version = typeof pkg.version === "string" ? pkg.version : "unknown";
|
|
178
182
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
)
|
|
183
|
+
let lastStatus: number | null = null;
|
|
184
|
+
for (const url of getCoreMLBinaryDownloadCandidates(version)) {
|
|
185
|
+
const res = await fetch(url, { redirect: "follow" });
|
|
186
|
+
if (res.ok) {
|
|
187
|
+
return res;
|
|
188
|
+
}
|
|
189
|
+
lastStatus = res.status;
|
|
183
190
|
}
|
|
184
191
|
|
|
185
|
-
|
|
192
|
+
throw new Error(
|
|
193
|
+
`Failed to download CoreML binary${lastStatus ? ` (HTTP ${lastStatus})` : ""}\n Requested package version: ${version}\n Fix: Check https://github.com/drakulavich/parakeet-cli/releases for available versions\n Or install the ONNX backend instead: parakeet install --onnx`,
|
|
194
|
+
);
|
|
186
195
|
}
|
|
187
196
|
|
|
188
197
|
export function getCoreMLInstallStatus(
|
package/src/status.ts
CHANGED
|
@@ -4,6 +4,9 @@ import { getCoreMLInstallState, getCoreMLInstallStatus, getCoreMLSupportDir, typ
|
|
|
4
4
|
import { log } from "./log";
|
|
5
5
|
import pc from "picocolors";
|
|
6
6
|
|
|
7
|
+
export type StatusCoreMLState = CoreMLInstallState | "n/a" | "probe-failed";
|
|
8
|
+
export type StatusPlatform = "mac-arm64" | "other";
|
|
9
|
+
|
|
7
10
|
export function formatStatusLine(
|
|
8
11
|
label: string,
|
|
9
12
|
path: string | null,
|
|
@@ -18,17 +21,25 @@ export function formatStatusLine(
|
|
|
18
21
|
|
|
19
22
|
export interface StatusInfo {
|
|
20
23
|
onnx: boolean;
|
|
21
|
-
coreml:
|
|
24
|
+
coreml: StatusCoreMLState;
|
|
22
25
|
ffmpeg: boolean;
|
|
26
|
+
platform: StatusPlatform;
|
|
23
27
|
}
|
|
24
28
|
|
|
25
29
|
export function collectSuggestions(info: StatusInfo): string[] {
|
|
26
30
|
const suggestions: string[] = [];
|
|
27
31
|
|
|
28
|
-
if (info.
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
+
if (info.platform === "mac-arm64") {
|
|
33
|
+
if (info.coreml === "missing") {
|
|
34
|
+
suggestions.push(`Run "parakeet install --coreml" to install the CoreML backend.`);
|
|
35
|
+
} else if (info.coreml === "binary-only") {
|
|
36
|
+
suggestions.push(`Run "parakeet install --coreml" to download CoreML models.`);
|
|
37
|
+
} else if (info.coreml === "stale-binary") {
|
|
38
|
+
suggestions.push(`Run "parakeet install --coreml --no-cache" to refresh the incompatible CoreML binary.`);
|
|
39
|
+
} else if (info.coreml === "probe-failed") {
|
|
40
|
+
suggestions.push(`Run "parakeet install --coreml --no-cache" to refresh the CoreML backend and restore status checks.`);
|
|
41
|
+
}
|
|
42
|
+
return suggestions;
|
|
32
43
|
}
|
|
33
44
|
|
|
34
45
|
if (!info.onnx) {
|
|
@@ -71,29 +82,67 @@ function defaultDeps(): StatusDeps {
|
|
|
71
82
|
};
|
|
72
83
|
}
|
|
73
84
|
|
|
85
|
+
function getCoreMLBinaryDisplay(state: StatusCoreMLState): { installed: boolean; missingLabel: string } {
|
|
86
|
+
switch (state) {
|
|
87
|
+
case "ready":
|
|
88
|
+
case "binary-only":
|
|
89
|
+
return { installed: true, missingLabel: "not installed" };
|
|
90
|
+
case "stale-binary":
|
|
91
|
+
return { installed: false, missingLabel: "stale binary" };
|
|
92
|
+
case "probe-failed":
|
|
93
|
+
return { installed: false, missingLabel: "probe failed" };
|
|
94
|
+
case "missing":
|
|
95
|
+
case "n/a":
|
|
96
|
+
return { installed: false, missingLabel: "not installed" };
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function getCoreMLModelsDisplay(state: StatusCoreMLState): { installed: boolean; missingLabel: string } {
|
|
101
|
+
switch (state) {
|
|
102
|
+
case "ready":
|
|
103
|
+
return { installed: true, missingLabel: "not installed" };
|
|
104
|
+
case "stale-binary":
|
|
105
|
+
return { installed: false, missingLabel: "reinstall required" };
|
|
106
|
+
case "probe-failed":
|
|
107
|
+
return { installed: false, missingLabel: "status unknown" };
|
|
108
|
+
case "binary-only":
|
|
109
|
+
case "missing":
|
|
110
|
+
case "n/a":
|
|
111
|
+
return { installed: false, missingLabel: "not installed" };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
74
115
|
export async function showStatus(deps?: Partial<StatusDeps>): Promise<void> {
|
|
75
116
|
const d = { ...defaultDeps(), ...deps };
|
|
76
117
|
|
|
77
118
|
const isMac = d.isMacArm64();
|
|
119
|
+
const platform: StatusPlatform = isMac ? "mac-arm64" : "other";
|
|
78
120
|
|
|
79
121
|
// CoreML status
|
|
80
|
-
let coremlState:
|
|
122
|
+
let coremlState: StatusCoreMLState = "n/a";
|
|
123
|
+
let coremlProbeError: string | null = null;
|
|
81
124
|
if (isMac) {
|
|
82
125
|
const binPath = d.getCoreMLBinPath();
|
|
83
126
|
try {
|
|
84
127
|
coremlState = d.getCoreMLState(binPath);
|
|
85
|
-
} catch {
|
|
86
|
-
coremlState = "
|
|
128
|
+
} catch (error: unknown) {
|
|
129
|
+
coremlState = "probe-failed";
|
|
130
|
+
coremlProbeError = error instanceof Error ? error.message : String(error);
|
|
87
131
|
}
|
|
88
132
|
|
|
89
133
|
log.info("CoreML (macOS Apple Silicon):");
|
|
90
|
-
const
|
|
91
|
-
log.info(formatStatusLine("Binary",
|
|
134
|
+
const binaryDisplay = getCoreMLBinaryDisplay(coremlState);
|
|
135
|
+
log.info(formatStatusLine("Binary", coremlState === "missing" ? null : binPath, binaryDisplay.installed, binaryDisplay.missingLabel));
|
|
92
136
|
|
|
93
|
-
const
|
|
137
|
+
const modelsDisplay = getCoreMLModelsDisplay(coremlState);
|
|
94
138
|
const modelDir = d.getCoreMLSupportDir();
|
|
95
|
-
|
|
139
|
+
const modelsPath = (coremlState === "ready" || coremlState === "stale-binary" || coremlState === "probe-failed") ? modelDir : null;
|
|
140
|
+
log.info(formatStatusLine("Models", modelsPath, modelsDisplay.installed, modelsDisplay.missingLabel));
|
|
96
141
|
log.info("");
|
|
142
|
+
|
|
143
|
+
if (coremlProbeError) {
|
|
144
|
+
log.warn(`CoreML status probe failed: ${coremlProbeError}`);
|
|
145
|
+
}
|
|
97
146
|
}
|
|
98
147
|
|
|
99
148
|
// ONNX status
|
|
@@ -117,6 +166,7 @@ export async function showStatus(deps?: Partial<StatusDeps>): Promise<void> {
|
|
|
117
166
|
onnx: onnxInstalled,
|
|
118
167
|
coreml: coremlState,
|
|
119
168
|
ffmpeg: !!ffmpegPath,
|
|
169
|
+
platform,
|
|
120
170
|
});
|
|
121
171
|
|
|
122
172
|
for (const suggestion of suggestions) {
|