@openparachute/vault 0.7.4 → 0.7.5-rc.5

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.
@@ -0,0 +1,228 @@
1
+ /**
2
+ * The whisper.cpp install planner.
3
+ *
4
+ * Pure, so the whole host matrix is exercised without downloading anything.
5
+ * The cases that matter are the ones where a wrong answer is expensive: a
6
+ * platform with no prebuilt binaries must REFUSE with instructions rather than
7
+ * half-install, and an already-installed binary must short-circuit rather than
8
+ * triggering another package-manager round trip.
9
+ */
10
+
11
+ import { describe, expect, test } from "bun:test";
12
+ import { DEFAULT_MODEL_ID } from "./models.ts";
13
+ import {
14
+ describeWhisperPlan,
15
+ planBinaryStrategy,
16
+ planWhisperInstall,
17
+ WHISPER_CPP_VERSION,
18
+ } from "./install-whisper-cpp.ts";
19
+
20
+ const GB = 1024 * 1024 * 1024;
21
+ const env = { PARACHUTE_HOME: "/ph" } as NodeJS.ProcessEnv;
22
+
23
+ describe("planBinaryStrategy", () => {
24
+ test("macOS → Homebrew (upstream ships no macOS CLI tarball)", () => {
25
+ const s = planWhisperInstall(undefined, {
26
+ platform: "darwin",
27
+ arch: "arm64",
28
+ totalRamBytes: 16 * GB,
29
+ env,
30
+ }).binary;
31
+ expect(s.kind).toBe("homebrew");
32
+ expect(s.kind === "homebrew" && s.formula).toBe("whisper-cpp");
33
+ });
34
+
35
+ test("macOS Intel also gets Homebrew — the formula covers both arches", () => {
36
+ const s = planWhisperInstall(undefined, {
37
+ platform: "darwin",
38
+ arch: "x64",
39
+ totalRamBytes: 16 * GB,
40
+ env,
41
+ }).binary;
42
+ expect(s.kind).toBe("homebrew");
43
+ });
44
+
45
+ test("Linux x64 → the x64 release tarball, pinned to a version", () => {
46
+ const s = planWhisperInstall(undefined, {
47
+ platform: "linux",
48
+ arch: "x64",
49
+ totalRamBytes: 8 * GB,
50
+ env,
51
+ }).binary;
52
+ expect(s.kind).toBe("tarball");
53
+ if (s.kind === "tarball") {
54
+ expect(s.assetName).toBe("whisper-bin-ubuntu-x64.tar.gz");
55
+ expect(s.url).toContain(`v${WHISPER_CPP_VERSION}`);
56
+ }
57
+ });
58
+
59
+ test("Linux arm64 → the arm64 tarball", () => {
60
+ const s = planWhisperInstall(undefined, {
61
+ platform: "linux",
62
+ arch: "arm64",
63
+ totalRamBytes: 8 * GB,
64
+ env,
65
+ }).binary;
66
+ expect(s.kind === "tarball" && s.assetName).toBe("whisper-bin-ubuntu-arm64.tar.gz");
67
+ });
68
+
69
+ test("an exotic Linux arch REFUSES with a pointer at the override", () => {
70
+ const s = planWhisperInstall(undefined, {
71
+ platform: "linux",
72
+ arch: "riscv64",
73
+ totalRamBytes: 8 * GB,
74
+ env,
75
+ }).binary;
76
+ expect(s.kind).toBe("unsupported");
77
+ expect(s.kind === "unsupported" && s.reason).toMatch(/WHISPER_CPP_BIN_DIR/);
78
+ });
79
+
80
+ test("an unknown platform refuses rather than half-installing", () => {
81
+ const plan = planWhisperInstall(undefined, {
82
+ platform: "freebsd",
83
+ arch: "x64",
84
+ totalRamBytes: 8 * GB,
85
+ env,
86
+ });
87
+ expect(plan.supported).toBe(false);
88
+ expect(plan.binary.kind).toBe("unsupported");
89
+ });
90
+
91
+ test("an already-installed binary short-circuits every package manager", () => {
92
+ const plan = planWhisperInstall(undefined, {
93
+ platform: "darwin",
94
+ arch: "arm64",
95
+ totalRamBytes: 16 * GB,
96
+ env,
97
+ resolveExisting: () => "/opt/homebrew/bin/parakeet-cli",
98
+ });
99
+ expect(plan.binary.kind).toBe("already-present");
100
+ expect(plan.supported).toBe(true);
101
+ });
102
+ });
103
+
104
+ describe("model selection", () => {
105
+ test("a capable box gets the recommended Parakeet", () => {
106
+ const p = planWhisperInstall(undefined, {
107
+ platform: "linux",
108
+ arch: "x64",
109
+ totalRamBytes: 8 * GB,
110
+ env,
111
+ });
112
+ expect(p.model.id).toBe(DEFAULT_MODEL_ID);
113
+ expect(p.modelExplicit).toBe(false);
114
+ });
115
+
116
+ test("a 2 GB VPS still gets Parakeet, not a step down to Whisper Tiny", () => {
117
+ // The common cheap-VPS tier. Parakeet q4 is 339 MB and comfortably fits;
118
+ // dropping to Whisper Tiny here would be a large accuracy loss for
119
+ // headroom the box didn't need.
120
+ const p = planWhisperInstall(undefined, {
121
+ platform: "linux",
122
+ arch: "x64",
123
+ totalRamBytes: 2 * GB,
124
+ env,
125
+ });
126
+ expect(p.model.engine).toBe("parakeet");
127
+ });
128
+
129
+ test("a 1 GB box steps down rather than swapping itself to death", () => {
130
+ const p = planWhisperInstall(undefined, {
131
+ platform: "linux",
132
+ arch: "x64",
133
+ totalRamBytes: 1 * GB,
134
+ env,
135
+ });
136
+ expect(p.model.sizeMb).toBeLessThan(150);
137
+ });
138
+
139
+ test("an explicit model is honoured over the RAM pick, with a warning", () => {
140
+ const p = planWhisperInstall("whisper-large-v3-turbo", {
141
+ platform: "linux",
142
+ arch: "x64",
143
+ totalRamBytes: 2 * GB,
144
+ env,
145
+ });
146
+ expect(p.model.id).toBe("whisper-large-v3-turbo");
147
+ expect(p.modelExplicit).toBe(true);
148
+ // Honoured, but the operator is told what they're in for.
149
+ expect(p.ramWarning).toBeTruthy();
150
+ expect(p.ramWarning).toMatch(/may swap or run slowly/);
151
+ });
152
+
153
+ test("no warning when the box comfortably fits the model", () => {
154
+ const p = planWhisperInstall(undefined, {
155
+ platform: "linux",
156
+ arch: "x64",
157
+ totalRamBytes: 32 * GB,
158
+ env,
159
+ });
160
+ expect(p.ramWarning).toBeUndefined();
161
+ });
162
+
163
+ test("an unknown model id is refused, NOT silently defaulted", () => {
164
+ // Silently installing something else would leave the operator convinced
165
+ // they're running a model they aren't.
166
+ const p = planWhisperInstall("whisper-enormous", {
167
+ platform: "linux",
168
+ arch: "x64",
169
+ totalRamBytes: 8 * GB,
170
+ env,
171
+ });
172
+ expect(p.supported).toBe(false);
173
+ expect(p.binary.kind === "unsupported" && p.binary.reason).toMatch(/unknown model id/);
174
+ });
175
+
176
+ test("paths honour PARACHUTE_HOME", () => {
177
+ const p = planWhisperInstall(undefined, {
178
+ platform: "linux",
179
+ arch: "x64",
180
+ totalRamBytes: 8 * GB,
181
+ env,
182
+ });
183
+ expect(p.modelPath.startsWith("/ph/transcription/models/")).toBe(true);
184
+ expect(p.binDir).toBe("/ph/transcription/bin");
185
+ });
186
+ });
187
+
188
+ describe("describeWhisperPlan", () => {
189
+ test("names the model, its size, and where it goes", () => {
190
+ const out = describeWhisperPlan(
191
+ planWhisperInstall(undefined, {
192
+ platform: "linux",
193
+ arch: "x64",
194
+ totalRamBytes: 8 * GB,
195
+ env,
196
+ }),
197
+ ).join("\n");
198
+ expect(out).toMatch(/Parakeet/);
199
+ expect(out).toMatch(/396 MB/);
200
+ expect(out).toMatch(/\/ph\/transcription\/models/);
201
+ });
202
+
203
+ test("an unsupported host explains itself rather than printing a plan", () => {
204
+ const out = describeWhisperPlan(
205
+ planWhisperInstall(undefined, {
206
+ platform: "freebsd",
207
+ arch: "x64",
208
+ totalRamBytes: 8 * GB,
209
+ env,
210
+ }),
211
+ ).join("\n");
212
+ expect(out).toMatch(/UNSUPPORTED/);
213
+ expect(out).toMatch(/WHISPER_CPP_BIN_DIR/);
214
+ });
215
+
216
+ test("the macOS line tells you what brew actually gives you", () => {
217
+ const out = describeWhisperPlan(
218
+ planWhisperInstall(undefined, {
219
+ platform: "darwin",
220
+ arch: "arm64",
221
+ totalRamBytes: 16 * GB,
222
+ env,
223
+ }),
224
+ ).join("\n");
225
+ expect(out).toMatch(/brew install whisper-cpp/);
226
+ expect(out).toMatch(/whisper-cli \+ parakeet-cli/);
227
+ });
228
+ });
@@ -0,0 +1,219 @@
1
+ /**
2
+ * `parachute-vault transcription install` for the whisper.cpp provider.
3
+ *
4
+ * Split into a PURE planner and a thin executor. The planner takes the host's
5
+ * platform/arch/RAM and the operator's flags and answers "what would happen" —
6
+ * no network, no writes — so `--dry-run` and the tests exercise the whole
7
+ * decision matrix without downloading a byte.
8
+ *
9
+ * ## How the binaries arrive, per platform
10
+ *
11
+ * **macOS** — Homebrew, and only Homebrew. whisper.cpp publishes release
12
+ * tarballs for Linux and Windows but NOT macOS (the macOS artifact is an
13
+ * `.xcframework`, which is for embedding in an app, not a CLI to spawn). The
14
+ * formula is bottled for arm64 Tahoe/Sequoia/Sonoma, so `brew install
15
+ * whisper-cpp` is a fast binary download rather than a source build. With no
16
+ * brew on the box we refuse and say so plainly — pretending to have another
17
+ * route would just fail later and more confusingly.
18
+ *
19
+ * **Linux** — the release tarball, extracted into our own managed directory
20
+ * (`<root>/transcription/bin`) rather than anywhere system-wide. The tarball
21
+ * carries `whisper-cli`, `parakeet-cli`, and the `libggml*`/`libwhisper` shared
22
+ * objects they link against, so the whole set moves together and we never
23
+ * fight a distro's package manager.
24
+ *
25
+ * ## Why it verifies before declaring success
26
+ *
27
+ * The provider this replaces was configured by an install verb that never
28
+ * checked whether the thing it configured could run — which is exactly how
29
+ * `TRANSCRIPTION_PROVIDER=transcribe-cpp` came to point at a CLI that has
30
+ * never existed. So the last step here actually SPAWNS the CLI against a
31
+ * generated test clip and reads the transcript back. `TRANSCRIPTION_PROVIDER`
32
+ * flips only after that passes. An install that can't transcribe is an install
33
+ * that failed, and it should say so at install time rather than silently at
34
+ * the first voice memo.
35
+ */
36
+
37
+ import { totalmem } from "os";
38
+ import { join } from "path";
39
+ import {
40
+ DEFAULT_MODEL_ID,
41
+ findModel,
42
+ pickDefaultModel,
43
+ type TranscriptionModel,
44
+ } from "./models.ts";
45
+ import { binaryNameFor, managedBinDir, managedModelDir } from "./resolve-binary.ts";
46
+
47
+ /** whisper.cpp release the Linux tarballs are pulled from. */
48
+ export const WHISPER_CPP_VERSION = "1.9.1";
49
+
50
+ /** How the CLI binaries get onto this box. */
51
+ export type BinaryStrategy =
52
+ | { kind: "homebrew"; formula: string }
53
+ | { kind: "tarball"; url: string; assetName: string }
54
+ | { kind: "already-present"; path: string }
55
+ | { kind: "unsupported"; reason: string };
56
+
57
+ export interface WhisperInstallPlan {
58
+ platform: string;
59
+ arch: string;
60
+ totalRamGb: number;
61
+ /** The model to fetch + activate. */
62
+ model: TranscriptionModel;
63
+ /** Whether the operator named the model explicitly (skips the RAM warning). */
64
+ modelExplicit: boolean;
65
+ /** How to obtain `whisper-cli` / `parakeet-cli`. */
66
+ binary: BinaryStrategy;
67
+ /** Absolute destination for the model file. */
68
+ modelPath: string;
69
+ /** Directory binaries land in (tarball strategy) or are found in. */
70
+ binDir: string;
71
+ /** True when the plan can proceed. */
72
+ supported: boolean;
73
+ /** Set when the box has less RAM than the chosen model wants. */
74
+ ramWarning?: string;
75
+ }
76
+
77
+ export interface PlanDeps {
78
+ platform?: string;
79
+ arch?: string;
80
+ totalRamBytes?: number;
81
+ env?: NodeJS.ProcessEnv;
82
+ /** Existing binary probe — lets the plan report "already present". */
83
+ resolveExisting?: (engine: "whisper" | "parakeet") => string | undefined;
84
+ }
85
+
86
+ /**
87
+ * Decide how to get the binaries for this host.
88
+ *
89
+ * `already-present` short-circuits everything: an operator who ran `brew
90
+ * install whisper-cpp` themselves, or who is re-running install, shouldn't
91
+ * trigger another package-manager round trip.
92
+ */
93
+ export function planBinaryStrategy(
94
+ model: TranscriptionModel,
95
+ deps: PlanDeps = {},
96
+ ): BinaryStrategy {
97
+ const platform = deps.platform ?? process.platform;
98
+ const arch = deps.arch ?? process.arch;
99
+
100
+ const existing = deps.resolveExisting?.(model.engine);
101
+ if (existing) return { kind: "already-present", path: existing };
102
+
103
+ if (platform === "darwin") {
104
+ // No macOS CLI tarball exists upstream — the macOS artifact is an
105
+ // xcframework for embedding, not a spawnable binary. Homebrew is the path.
106
+ return { kind: "homebrew", formula: "whisper-cpp" };
107
+ }
108
+
109
+ if (platform === "linux") {
110
+ const assetArch = arch === "arm64" || arch === "aarch64" ? "arm64" : arch === "x64" ? "x64" : null;
111
+ if (!assetArch) {
112
+ return {
113
+ kind: "unsupported",
114
+ reason: `no prebuilt whisper.cpp binaries for linux/${arch} — build whisper.cpp yourself and point WHISPER_CPP_BIN_DIR at the result`,
115
+ };
116
+ }
117
+ const assetName = `whisper-bin-ubuntu-${assetArch}.tar.gz`;
118
+ return {
119
+ kind: "tarball",
120
+ assetName,
121
+ url: `https://github.com/ggml-org/whisper.cpp/releases/download/v${WHISPER_CPP_VERSION}/${assetName}`,
122
+ };
123
+ }
124
+
125
+ return {
126
+ kind: "unsupported",
127
+ reason: `${platform} isn't supported for automatic install — install whisper.cpp yourself and point WHISPER_CPP_BIN_DIR at the directory holding ${binaryNameFor(model.engine)}`,
128
+ };
129
+ }
130
+
131
+ /**
132
+ * Build the full plan. Pure — safe to call for `--dry-run`.
133
+ *
134
+ * `modelId` undefined means "pick for this box": `pickDefaultModel` steps down
135
+ * from the recommended Parakeet on a machine that can't comfortably hold it.
136
+ * An explicitly-named model is honoured even when it's a stretch for the RAM,
137
+ * with a warning — the operator may know something we don't.
138
+ */
139
+ export function planWhisperInstall(
140
+ modelId: string | undefined,
141
+ deps: PlanDeps = {},
142
+ ): WhisperInstallPlan {
143
+ const platform = deps.platform ?? process.platform;
144
+ const arch = deps.arch ?? process.arch;
145
+ const totalRamBytes = deps.totalRamBytes ?? totalmem();
146
+ const totalRamMb = Math.round(totalRamBytes / 1024 / 1024);
147
+ const env = deps.env ?? process.env;
148
+
149
+ const explicit = modelId !== undefined;
150
+ const model = explicit
151
+ ? findModel(modelId)
152
+ : pickDefaultModel(totalRamMb);
153
+
154
+ if (!model) {
155
+ // Unknown id — surface it as unsupported rather than silently defaulting,
156
+ // so a typo doesn't quietly install something else.
157
+ const fallback = findModel(DEFAULT_MODEL_ID)!;
158
+ return {
159
+ platform,
160
+ arch,
161
+ totalRamGb: Math.round((totalRamMb / 1024) * 10) / 10,
162
+ model: fallback,
163
+ modelExplicit: true,
164
+ binary: { kind: "unsupported", reason: `unknown model id "${modelId}"` },
165
+ modelPath: join(managedModelDir(env), fallback.filename),
166
+ binDir: managedBinDir(env),
167
+ supported: false,
168
+ };
169
+ }
170
+
171
+ const binary = planBinaryStrategy(model, deps);
172
+ const ramWarning =
173
+ totalRamMb < model.minRamMb
174
+ ? `${model.label} wants about ${Math.round(model.minRamMb / 1024)} GB of RAM and this box reports ${Math.round((totalRamMb / 1024) * 10) / 10} GB — it may swap or run slowly.`
175
+ : undefined;
176
+
177
+ return {
178
+ platform,
179
+ arch,
180
+ totalRamGb: Math.round((totalRamMb / 1024) * 10) / 10,
181
+ model,
182
+ modelExplicit: explicit,
183
+ binary,
184
+ modelPath: join(managedModelDir(env), model.filename),
185
+ binDir:
186
+ binary.kind === "already-present"
187
+ ? binary.path.slice(0, binary.path.lastIndexOf("/"))
188
+ : managedBinDir(env),
189
+ supported: binary.kind !== "unsupported",
190
+ ramWarning,
191
+ };
192
+ }
193
+
194
+ /** Human-readable plan, shared by `--dry-run` and the real run. */
195
+ export function describeWhisperPlan(plan: WhisperInstallPlan): string[] {
196
+ const lines: string[] = [];
197
+ lines.push(`Host: ${plan.platform}/${plan.arch}, ${plan.totalRamGb} GB RAM`);
198
+ lines.push(`Model: ${plan.model.label} (${plan.model.sizeMb} MB)`);
199
+ lines.push(` ${plan.model.note}`);
200
+ lines.push(` → ${plan.modelPath}`);
201
+
202
+ switch (plan.binary.kind) {
203
+ case "already-present":
204
+ lines.push(`Engine: ${binaryNameFor(plan.model.engine)} already installed at ${plan.binary.path}`);
205
+ break;
206
+ case "homebrew":
207
+ lines.push(`Engine: brew install ${plan.binary.formula} (installs whisper-cli + parakeet-cli)`);
208
+ break;
209
+ case "tarball":
210
+ lines.push(`Engine: download ${plan.binary.assetName}`);
211
+ lines.push(` → ${plan.binDir}`);
212
+ break;
213
+ case "unsupported":
214
+ lines.push(`Engine: UNSUPPORTED — ${plan.binary.reason}`);
215
+ break;
216
+ }
217
+ if (plan.ramWarning) lines.push(`Note: ${plan.ramWarning}`);
218
+ return lines;
219
+ }
@@ -0,0 +1,244 @@
1
+ /**
2
+ * The install executor.
3
+ *
4
+ * Driven through the spawn/download seams so no package manager runs and
5
+ * nothing is fetched. The behaviours worth pinning are the ones that decide
6
+ * whether an operator ends up with a working install or a convincing-looking
7
+ * broken one: refusing when brew is absent instead of emitting exit 127,
8
+ * never trusting a partial download, and failing the whole install when
9
+ * verification can't transcribe.
10
+ */
11
+
12
+ import { afterEach, describe, expect, test } from "bun:test";
13
+ import { mkdtempSync, rmSync } from "fs";
14
+ import { tmpdir } from "os";
15
+ import { join } from "path";
16
+ import { planWhisperInstall } from "./install-whisper-cpp.ts";
17
+ import {
18
+ ensureBinaries,
19
+ ensureModel,
20
+ silentWav16kMono,
21
+ verifyTranscription,
22
+ } from "./install-whisper-exec.ts";
23
+ import type { SpawnRunner } from "./providers/transcribe-cpp.ts";
24
+
25
+ const GB = 1024 * 1024 * 1024;
26
+ const env = { PARACHUTE_HOME: "/ph" } as NodeJS.ProcessEnv;
27
+
28
+ const macPlan = () =>
29
+ planWhisperInstall(undefined, { platform: "darwin", arch: "arm64", totalRamBytes: 16 * GB, env });
30
+ const tmpHomes: string[] = [];
31
+ /** A Linux plan rooted in a real temp dir — the executor genuinely mkdirs. */
32
+ const linuxPlan = () => {
33
+ const home = mkdtempSync(join(tmpdir(), "wcpp-install-"));
34
+ tmpHomes.push(home);
35
+ return planWhisperInstall(undefined, {
36
+ platform: "linux",
37
+ arch: "x64",
38
+ totalRamBytes: 8 * GB,
39
+ env: { PARACHUTE_HOME: home } as NodeJS.ProcessEnv,
40
+ });
41
+ };
42
+
43
+ afterEach(() => {
44
+ for (const h of tmpHomes.splice(0)) rmSync(h, { recursive: true, force: true });
45
+ });
46
+
47
+ /** A spawn that answers per-command from a lookup. */
48
+ function spawnFor(map: Record<string, { exitCode?: number; stdout?: string; stderr?: string }>): SpawnRunner {
49
+ return async (cmd) => {
50
+ const key = Object.keys(map).find((k) => cmd.join(" ").includes(k));
51
+ const r = key ? map[key]! : {};
52
+ return { exitCode: r.exitCode ?? 0, stdout: r.stdout ?? "", stderr: r.stderr ?? "" };
53
+ };
54
+ }
55
+
56
+ describe("ensureBinaries — macOS", () => {
57
+ test("no Homebrew → honest refusal, and brew install is never attempted", async () => {
58
+ // The alternative is an opaque exit 127 the operator has to decode.
59
+ let attempted = false;
60
+ const spawn: SpawnRunner = async (cmd) => {
61
+ if (cmd[1] === "install") attempted = true;
62
+ return { exitCode: cmd[1] === "--version" ? 1 : 0, stdout: "", stderr: "" };
63
+ };
64
+ const r = await ensureBinaries(macPlan(), { spawn });
65
+ expect(r.ok).toBe(false);
66
+ expect(r.message).toMatch(/Homebrew isn't installed/);
67
+ expect(r.message).toMatch(/brew\.sh/);
68
+ // And it explains WHY there's no alternative, so this doesn't read as laziness.
69
+ expect(r.message).toMatch(/no macOS CLI tarball|xcframework/);
70
+ expect(attempted).toBe(false);
71
+ });
72
+
73
+ test("brew present → installs the formula", async () => {
74
+ const r = await ensureBinaries(macPlan(), { spawn: spawnFor({}) });
75
+ expect(r.ok).toBe(true);
76
+ expect(r.message).toMatch(/whisper-cli \+ parakeet-cli/);
77
+ });
78
+
79
+ test("a failing brew install surfaces brew's own stderr", async () => {
80
+ const r = await ensureBinaries(macPlan(), {
81
+ spawn: spawnFor({ "brew install": { exitCode: 1, stderr: "No available formula" } }),
82
+ });
83
+ expect(r.ok).toBe(false);
84
+ expect(r.message).toMatch(/No available formula/);
85
+ });
86
+ });
87
+
88
+ describe("ensureBinaries — Linux", () => {
89
+ test("downloads + extracts, then confirms the binary really landed", async () => {
90
+ const seen: string[] = [];
91
+ // Model the real sequence: the binary is ABSENT before extraction and
92
+ // present after. A flat `true` would short-circuit at the already-present
93
+ // check and never download at all.
94
+ let extracted = false;
95
+ const r = await ensureBinaries(linuxPlan(), {
96
+ spawn: async (cmd) => {
97
+ if (cmd[0] === "tar") extracted = true;
98
+ return { exitCode: 0, stdout: "", stderr: "" };
99
+ },
100
+ download: async (url) => {
101
+ seen.push(url);
102
+ },
103
+ existsImpl: (p) => extracted && p.endsWith("parakeet-cli"),
104
+ });
105
+ expect(r.ok).toBe(true);
106
+ expect(seen[0]).toContain("whisper-bin-ubuntu-x64.tar.gz");
107
+ });
108
+
109
+ test("extraction that doesn't produce the binary is a FAILURE, not a shrug", async () => {
110
+ // Otherwise the install reports success and the first transcription is
111
+ // the thing that discovers the truth.
112
+ const r = await ensureBinaries(linuxPlan(), {
113
+ spawn: spawnFor({}),
114
+ download: async () => {},
115
+ existsImpl: () => false,
116
+ });
117
+ expect(r.ok).toBe(false);
118
+ expect(r.message).toMatch(/isn't in/);
119
+ });
120
+
121
+ test("a tar failure surfaces tar's stderr", async () => {
122
+ const r = await ensureBinaries(linuxPlan(), {
123
+ spawn: spawnFor({ tar: { exitCode: 2, stderr: "unexpected EOF" } }),
124
+ download: async () => {},
125
+ existsImpl: () => false,
126
+ });
127
+ expect(r.ok).toBe(false);
128
+ expect(r.message).toMatch(/unexpected EOF/);
129
+ });
130
+ });
131
+
132
+ describe("ensureBinaries — short circuits", () => {
133
+ test("already-present skips every package manager", async () => {
134
+ let spawned = false;
135
+ const plan = planWhisperInstall(undefined, {
136
+ platform: "darwin",
137
+ arch: "arm64",
138
+ totalRamBytes: 16 * GB,
139
+ env,
140
+ resolveExisting: () => "/opt/homebrew/bin/parakeet-cli",
141
+ });
142
+ const r = await ensureBinaries(plan, {
143
+ spawn: async () => {
144
+ spawned = true;
145
+ return { exitCode: 0, stdout: "", stderr: "" };
146
+ },
147
+ });
148
+ expect(r.ok).toBe(true);
149
+ expect(spawned).toBe(false);
150
+ });
151
+
152
+ test("an unsupported host fails with the planner's reason", async () => {
153
+ const plan = planWhisperInstall(undefined, {
154
+ platform: "freebsd",
155
+ arch: "x64",
156
+ totalRamBytes: 8 * GB,
157
+ env,
158
+ });
159
+ const r = await ensureBinaries(plan, { spawn: spawnFor({}) });
160
+ expect(r.ok).toBe(false);
161
+ expect(r.message).toMatch(/WHISPER_CPP_BIN_DIR/);
162
+ });
163
+ });
164
+
165
+ describe("ensureModel", () => {
166
+ test("an existing model isn't re-downloaded", async () => {
167
+ let downloads = 0;
168
+ const r = await ensureModel(linuxPlan(), {
169
+ existsImpl: () => true,
170
+ download: async () => {
171
+ downloads += 1;
172
+ },
173
+ });
174
+ expect(r.ok).toBe(true);
175
+ expect(downloads).toBe(0);
176
+ });
177
+
178
+ test("downloads to a .part first — a truncated file must never look complete", async () => {
179
+ // A half-file at the real path would pass the readiness probe and then
180
+ // fail mysteriously at the first transcription.
181
+ const targets: string[] = [];
182
+ await ensureModel(linuxPlan(), {
183
+ existsImpl: () => false,
184
+ download: async (_url, dest) => {
185
+ targets.push(dest);
186
+ // Materialise it so the rename can succeed.
187
+ await Bun.write(dest, "x");
188
+ },
189
+ }).catch(() => {});
190
+ expect(targets[0]).toMatch(/\.part$/);
191
+ });
192
+ });
193
+
194
+ describe("verifyTranscription", () => {
195
+ test("a clean exit means verified", async () => {
196
+ const r = await verifyTranscription(linuxPlan(), "/bin/parakeet-cli", { spawn: spawnFor({}) });
197
+ expect(r.ok).toBe(true);
198
+ });
199
+
200
+ test("a non-zero exit FAILS the install and names the likely cause", async () => {
201
+ const r = await verifyTranscription(linuxPlan(), "/bin/parakeet-cli", {
202
+ spawn: spawnFor({ "parakeet-cli": { exitCode: 1, stderr: "failed to load model" } }),
203
+ });
204
+ expect(r.ok).toBe(false);
205
+ expect(r.message).toMatch(/truncated download or a model\/binary mismatch/);
206
+ expect(r.message).toMatch(/failed to load model/);
207
+ });
208
+
209
+ test("engine picks the flags — whisper needs -nt, parakeet has no such flag", async () => {
210
+ let seen: string[] = [];
211
+ const capture: SpawnRunner = async (cmd) => {
212
+ seen = cmd;
213
+ return { exitCode: 0, stdout: "", stderr: "" };
214
+ };
215
+ await verifyTranscription(
216
+ planWhisperInstall("whisper-base.en", { platform: "linux", arch: "x64", totalRamBytes: 8 * GB, env }),
217
+ "/bin/whisper-cli",
218
+ { spawn: capture },
219
+ );
220
+ expect(seen).toContain("-nt");
221
+
222
+ await verifyTranscription(linuxPlan(), "/bin/parakeet-cli", { spawn: capture });
223
+ expect(seen).not.toContain("-nt");
224
+ });
225
+ });
226
+
227
+ describe("silentWav16kMono", () => {
228
+ test("is a valid RIFF/WAVE header at 16 kHz mono 16-bit", () => {
229
+ // Hand-built so verification doesn't depend on ffmpeg — the very tool
230
+ // whose absence we're trying to report clearly elsewhere.
231
+ const wav = silentWav16kMono(1);
232
+ const dv = new DataView(wav.buffer);
233
+ expect(String.fromCharCode(...wav.slice(0, 4))).toBe("RIFF");
234
+ expect(String.fromCharCode(...wav.slice(8, 12))).toBe("WAVE");
235
+ expect(dv.getUint16(22, true)).toBe(1); // mono
236
+ expect(dv.getUint32(24, true)).toBe(16000); // sample rate
237
+ expect(dv.getUint16(34, true)).toBe(16); // bits
238
+ expect(wav.length).toBe(44 + 16000 * 2);
239
+ });
240
+
241
+ test("a zero/negative duration still yields a structurally valid file", () => {
242
+ expect(silentWav16kMono(0).length).toBeGreaterThan(44);
243
+ });
244
+ });