@openparachute/vault 0.6.5-rc.12 → 0.6.5-rc.9

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/src/cli.ts CHANGED
@@ -152,20 +152,8 @@ import {
152
152
  resolveTranscribeCppPaths,
153
153
  resolveTranscriptionProviderName,
154
154
  transcribeCppInstalled,
155
- probeTranscribeCliRunnable,
156
155
  readManifest,
157
- transcriptionHomeDir,
158
- pythonVenvDir,
159
- pythonManifestPath,
160
- readPythonManifest,
161
- resolveParakeetMlxBin,
162
- resolveParakeetMlxModel,
163
- resolveOnnxAsrBin,
164
- resolveOnnxAsrModel,
165
- parakeetMlxInstalled,
166
- onnxAsrInstalled,
167
156
  type TranscribeCppManifest,
168
- type PythonInstallManifest,
169
157
  } from "./transcription/select.ts";
170
158
  import {
171
159
  buildTranscribeCli,
@@ -174,14 +162,6 @@ import {
174
162
  TRANSCRIBE_CPP_SOURCE_REF,
175
163
  type CliBuildResult,
176
164
  } from "./transcription/build.ts";
177
- import { downloadTo } from "./transcription/download.ts";
178
- import { selectDefaultProvider, NOMINAL_SLACK_GB, type TierPlan } from "./transcription/tiers.ts";
179
- import {
180
- PYTHON_PROVIDERS,
181
- buildDefaultPythonDeps,
182
- installPythonBackend,
183
- type PythonProviderName,
184
- } from "./transcription/install-python.ts";
185
165
 
186
166
  // ---------------------------------------------------------------------------
187
167
  // Argument parsing
@@ -3549,23 +3529,17 @@ function printHistoryUsage(): void {
3549
3529
  // ---------------------------------------------------------------------------
3550
3530
 
3551
3531
  /**
3552
- * `parachute-vault transcription install` — set up a LOCAL transcription
3553
- * provider. With no `--provider`, the ratified RAM/arch/OS tier table
3554
- * (scribe-fold Phase 2b, `tiers.ts`) picks the host's default:
3555
- *
3556
- * - macOS Apple Silicon 8GB+ parakeet-mlx (Python venv, MLX)
3557
- * - Linux 4GB+ → onnx-asr (Python venv, int8 ONNX)
3558
- * - Linux ~2GB → transcribe-cpp + small whisper GGUF
3559
- * - ~1GB / below floor → remote steer (scribe-http), never forced
3560
- *
3561
- * The pick + footprint is printed and the operator confirms (or overrides
3562
- * with `--provider`/`--model`). Every path is idempotent + non-fatal, and
3563
- * activation is HONEST: `TRANSCRIPTION_PROVIDER` flips only when a runnable
3564
- * binary actually exists (PRs #532/#533 precedent). `--dry-run`/`--plan`
3565
- * prints the selection without downloading.
3532
+ * `parachute-vault transcription install` — set up the local `transcribe-cpp`
3533
+ * provider: detect OS/arch + RAM, pick a tier-appropriate GGUF model
3534
+ * (scribe#82 fix Parakeet floored to ≥4GB), download the prebuilt
3535
+ * transcribe-cli release asset + the model into
3536
+ * `~/.parachute/transcription/`, and switch `TRANSCRIPTION_PROVIDER` in the
3537
+ * vault `.env`. Idempotent (skips already-present files unless `--force`),
3538
+ * non-fatal. `--dry-run`/`--plan` prints the selection without downloading
3539
+ * the path tests exercise (no network).
3566
3540
  *
3567
3541
  * `parachute-vault transcription status` — show the configured provider +
3568
- * per-provider install state.
3542
+ * install state.
3569
3543
  */
3570
3544
  async function cmdTranscription(args: string[]) {
3571
3545
  const sub = args[0];
@@ -3574,13 +3548,11 @@ async function cmdTranscription(args: string[]) {
3574
3548
  return;
3575
3549
  }
3576
3550
  if (sub === "status" || sub === undefined) {
3577
- await cmdTranscriptionStatus();
3551
+ cmdTranscriptionStatus();
3578
3552
  return;
3579
3553
  }
3580
3554
  console.error(`Unknown transcription subcommand: ${sub}`);
3581
- console.error(
3582
- "Usage: parachute-vault transcription install [--dry-run] [--model <id>] [--provider <transcribe-cpp|parakeet-mlx|onnx-asr|scribe-http>]",
3583
- );
3555
+ console.error("Usage: parachute-vault transcription install [--dry-run] [--model <id>] [--provider <name>]");
3584
3556
  console.error(" parachute-vault transcription status");
3585
3557
  process.exit(1);
3586
3558
  }
@@ -3604,9 +3576,9 @@ async function cmdTranscriptionInstall(args: string[]) {
3604
3576
  const force = args.includes("--force");
3605
3577
  const yes = args.includes("--yes") || args.includes("-y");
3606
3578
  const overrideModel = takeArgValue(args, "--model").value;
3607
- const providerArg = takeArgValue(args, "--provider").value;
3579
+ const provider = takeArgValue(args, "--provider").value ?? "transcribe-cpp";
3608
3580
 
3609
- if (providerArg === "scribe-http") {
3581
+ if (provider === "scribe-http") {
3610
3582
  // Just flip config back to the remote provider — no download.
3611
3583
  if (dryRun) {
3612
3584
  console.log("Would set TRANSCRIPTION_PROVIDER=scribe-http in the vault .env (remote provider; no download).");
@@ -3616,162 +3588,11 @@ async function cmdTranscriptionInstall(args: string[]) {
3616
3588
  console.log("Set TRANSCRIPTION_PROVIDER=scribe-http. Restart vault to apply. (Remote provider — no local binary/model needed.)");
3617
3589
  return;
3618
3590
  }
3619
-
3620
- let provider: string;
3621
- let tierModel: string | undefined;
3622
- if (providerArg) {
3623
- if (!["transcribe-cpp", "parakeet-mlx", "onnx-asr"].includes(providerArg)) {
3624
- console.error(
3625
- `Unknown --provider "${providerArg}". Valid: transcribe-cpp, parakeet-mlx, onnx-asr, scribe-http.`,
3626
- );
3627
- process.exit(1);
3628
- }
3629
- provider = providerArg;
3630
- } else {
3631
- // Auto-default: the ratified RAM/arch/OS tier table (tiers.ts). Probe the
3632
- // host, print the pick + footprint; the operator confirms below (or
3633
- // re-runs with --provider/--model to override).
3634
- const tier = selectDefaultProvider({
3635
- platform: process.platform,
3636
- arch: process.arch,
3637
- totalRamBytes: detectTotalRamBytes(),
3638
- });
3639
- printTierPick(tier);
3640
- if (tier.provider === "scribe-http") {
3641
- // Remote steer — never force a local install on a below-tier box.
3642
- console.log("\nNo local install performed. To point at a remote provider now:");
3643
- console.log(" parachute-vault transcription install --provider scribe-http");
3644
- if (tier.localOptIn) {
3645
- console.log(`\nLocal opt-in (explicit only — not auto-installed): ${tier.localOptIn.note}`);
3646
- }
3647
- process.exit(dryRun ? 0 : 1);
3648
- }
3649
- provider = tier.provider;
3650
- tierModel = tier.model;
3651
- console.log("");
3652
- }
3653
-
3654
- if (provider === "transcribe-cpp") {
3655
- // The tier's model pick (e.g. the Linux ~2GB whisper-small tier) seeds the
3656
- // transcribe-cpp flow; an explicit --model always wins.
3657
- await runTranscribeCppInstall({ dryRun, force, yes, overrideModel: overrideModel ?? tierModel });
3658
- return;
3659
- }
3660
- await runPythonProviderInstall(provider as PythonProviderName, {
3661
- dryRun,
3662
- force,
3663
- yes,
3664
- overrideModel,
3665
- });
3666
- }
3667
-
3668
- /** Print the tier-table pick for this host (the install auto-default). */
3669
- function printTierPick(tier: TierPlan): void {
3670
- console.log(`Host: ${tier.platform}/${tier.arch}, ${tier.totalRamGb}GB RAM`);
3671
- console.log(`Tier default: ${tier.provider}${tier.model ? ` (${tier.model})` : ""}`);
3672
- console.log(` ${tier.reason}`);
3673
- if (tier.approxDiskMb) {
3674
- console.log(` ~${tier.approxDiskMb}MB download${tier.peakRamNote ? `, ${tier.peakRamNote}` : ""}`);
3675
- }
3676
- console.log(" (override with --provider <transcribe-cpp|parakeet-mlx|onnx-asr|scribe-http>)");
3677
- }
3678
-
3679
- /**
3680
- * Install a Python-based local provider (parakeet-mlx / onnx-asr) into the
3681
- * managed venv under `~/.parachute/transcription/venv/` and — honestly —
3682
- * activate it only when a runnable binary verified (`install-python.ts` owns
3683
- * the apt/venv/warm-pull steps; activation + manifest stay here, mirroring
3684
- * the transcribe-cpp flow).
3685
- */
3686
- async function runPythonProviderInstall(
3687
- provider: PythonProviderName,
3688
- opts: { dryRun: boolean; force: boolean; yes: boolean; overrideModel?: string },
3689
- ): Promise<void> {
3690
- const spec = PYTHON_PROVIDERS[provider];
3691
- const venv = pythonVenvDir();
3692
- // `--model` wins; else env/manifest/ratified default. The pick lands in the
3693
- // install manifest, which the runtime model resolution reads back.
3694
- const model =
3695
- opts.overrideModel ??
3696
- (provider === "parakeet-mlx" ? resolveParakeetMlxModel() : resolveOnnxAsrModel());
3697
- const totalRamGb = Math.round((detectTotalRamBytes() / 2 ** 30) * 10) / 10;
3698
-
3699
- console.log(`Transcription install plan (${provider} — local, Python venv):\n`);
3700
- console.log(`Host: ${process.platform}/${process.arch}, ${totalRamGb}GB RAM`);
3701
- console.log(`Package: ${spec.pipTarget} → ${venv}`);
3702
- console.log(`Model: ${model}`);
3703
- console.log(` ~${spec.approxDiskMb}MB model download, ${spec.peakRamNote}`);
3704
-
3705
- if (opts.dryRun) {
3706
- // Preview the guards the real run would hit — pure, no side effects.
3707
- if (spec.platform && spec.platform !== process.platform) {
3708
- console.log(`\n Refused: ${provider} only runs on ${spec.platform}; this host is ${process.platform}.`);
3709
- } else if (spec.arch && spec.arch !== process.arch) {
3710
- console.log(`\n Refused: ${provider} needs ${spec.arch} (Apple Silicon); this host is ${process.arch}.`);
3711
- } else if (totalRamGb < spec.minRamGb - NOMINAL_SLACK_GB && !opts.force) {
3712
- console.log(`\n Refused: ${totalRamGb}GB RAM < the ~${spec.minRamGb}GB ${provider} floor. ${spec.ramFloorRationale}`);
3713
- }
3714
- console.log("\n(--dry-run) Nothing installed.");
3715
- return;
3716
- }
3717
-
3718
- if (!opts.yes) {
3719
- const ok = await confirm(
3720
- `Install ${spec.pipTarget} into ${venv} (model ~${spec.approxDiskMb}MB ${provider === "parakeet-mlx" ? "downloads on first use" : "warm-pulled now"})?`,
3721
- true,
3722
- );
3723
- if (!ok) {
3724
- console.log("Aborted.");
3725
- return;
3726
- }
3727
- }
3728
-
3729
- const outcome = await installPythonBackend(buildDefaultPythonDeps(), {
3730
- provider,
3731
- force: opts.force,
3732
- model,
3733
- });
3734
-
3735
- if (!outcome.ok || !outcome.binPath) {
3736
- console.error(`\n✗ ${outcome.summary}`);
3737
- console.error(
3738
- "Your current provider is unchanged — the verb is idempotent; fix the issue and re-run. Or use the remote provider: parachute-vault transcription install --provider scribe-http",
3739
- );
3591
+ if (provider !== "transcribe-cpp") {
3592
+ console.error(`Unknown --provider "${provider}". Valid: transcribe-cpp, scribe-http.`);
3740
3593
  process.exit(1);
3741
3594
  }
3742
3595
 
3743
- // Manifest + honest activation (a runnable binary exists — verified above).
3744
- const manifest: PythonInstallManifest = {
3745
- provider,
3746
- pipTarget: spec.pipTarget,
3747
- bin: outcome.binPath,
3748
- venv: outcome.venv ?? "",
3749
- model,
3750
- os: process.platform,
3751
- arch: process.arch,
3752
- ram_gb: totalRamGb,
3753
- installedAt: new Date().toISOString(),
3754
- };
3755
- mkdirSync(transcriptionHomeDir(), { recursive: true });
3756
- writeFileSync(pythonManifestPath(), JSON.stringify(manifest, null, 2));
3757
-
3758
- setEnvVar("TRANSCRIPTION_PROVIDER", provider);
3759
- console.log(`\n✓ Installed and activated → TRANSCRIPTION_PROVIDER=${provider} set in the vault .env.`);
3760
- console.log(` Binary: ${outcome.binPath}`);
3761
- console.log(` Model: ${model}`);
3762
- if (provider === "parakeet-mlx") {
3763
- console.log(` NOTE: the model (~${spec.approxDiskMb}MB) downloads into the HuggingFace cache on the first transcription.`);
3764
- }
3765
- console.log(` Restart vault to activate: parachute restart vault`);
3766
- }
3767
-
3768
- async function runTranscribeCppInstall(opts: {
3769
- dryRun: boolean;
3770
- force: boolean;
3771
- yes: boolean;
3772
- overrideModel?: string;
3773
- }) {
3774
- const { dryRun, force, yes, overrideModel } = opts;
3775
3596
  const plan = planInstall({
3776
3597
  platform: process.platform,
3777
3598
  arch: process.arch,
@@ -3869,25 +3690,14 @@ async function runTranscribeCppInstall(opts: {
3869
3690
 
3870
3691
  // 5) CLI resolution + provider switch — honest, no silent success. `binPath`
3871
3692
  // resolves to TRANSCRIBE_CPP_BIN or the built binary; we activate
3872
- // transcribe-cpp only when one actually EXECUTES (`--help` exits 0
3873
- // vault#534: an existsSync-only check activated Linux installs whose CLI
3874
- // exited 1 on every run). Otherwise we keep the current provider and
3875
- // report the gap rather than taking transcription offline behind a
3876
- // provider that can't run.
3693
+ // transcribe-cpp only when one actually exists, else we keep the current
3694
+ // provider and report the acquisition gap rather than taking
3695
+ // transcription offline behind a provider that can't run.
3877
3696
  if (existsSync(paths.binPath)) {
3878
- const probe = await probeTranscribeCliRunnable(paths.binPath);
3879
- if (probe.ok) {
3880
- setEnvVar("TRANSCRIPTION_PROVIDER", "transcribe-cpp");
3881
- console.log(`\n✓ Installed and activated TRANSCRIPTION_PROVIDER=transcribe-cpp set in the vault .env.`);
3882
- console.log(` CLI: ${paths.binPath} (verified runnable)`);
3883
- console.log(` Restart vault to activate: parachute restart vault`);
3884
- } else {
3885
- console.log(
3886
- `\n⚠ transcribe-cli exists at ${paths.binPath} but is NOT runnable (${probe.reason}).`,
3887
- );
3888
- console.log(" NOT activating transcribe-cpp — your current provider is unchanged.");
3889
- console.log(noRunnableCliGuidance(paths, buildResult));
3890
- }
3697
+ setEnvVar("TRANSCRIPTION_PROVIDER", "transcribe-cpp");
3698
+ console.log(`\n✓ Installed and activated → TRANSCRIPTION_PROVIDER=transcribe-cpp set in the vault .env.`);
3699
+ console.log(` CLI: ${paths.binPath}`);
3700
+ console.log(` Restart vault to activate: parachute restart vault`);
3891
3701
  } else {
3892
3702
  console.log(`\n✓ Libraries + model installed to ${paths.dir} — transcription is NOT yet activated.`);
3893
3703
  console.log(noRunnableCliGuidance(paths, buildResult));
@@ -3899,6 +3709,13 @@ async function runTranscribeCppInstall(opts: {
3899
3709
  }
3900
3710
  }
3901
3711
 
3712
+ /** Download a URL to a destination file (streamed via fetch → Bun.write). */
3713
+ async function downloadTo(url: string, dest: string): Promise<void> {
3714
+ const resp = await fetch(url, { redirect: "follow" });
3715
+ if (!resp.ok) throw new Error(`download failed (${resp.status}) for ${url}`);
3716
+ await Bun.write(dest, resp);
3717
+ }
3718
+
3902
3719
  /**
3903
3720
  * Download the release tarball, extract it, and place its runtime shared
3904
3721
  * libraries (libtranscribe + libggml `.dylib`/`.so`) into `paths.libsDir`,
@@ -3990,8 +3807,6 @@ async function maybeBuildTranscribeCli(
3990
3807
  await downloadTo(cliSourceUrl(rel), dest);
3991
3808
  }
3992
3809
  },
3993
- readFile: (p) => readFileSync(p, "utf8"),
3994
- writeFile: (p, content) => writeFileSync(p, content),
3995
3810
  compile: (cmd) => {
3996
3811
  const p = Bun.spawnSync(cmd);
3997
3812
  return { exitCode: p.exitCode ?? 1, stderr: new TextDecoder().decode(p.stderr) };
@@ -4040,14 +3855,6 @@ function noRunnableCliGuidance(
4040
3855
  " • build transcribe-cli yourself, point TRANSCRIBE_CPP_BIN at it, and re-run; OR",
4041
3856
  " • use the remote provider: parachute-vault transcription install --provider scribe-http",
4042
3857
  );
4043
- } else if (build && !build.ok && build.stage === "patch") {
4044
- lines.push(
4045
- `\n⚠ Could not apply the vault#534 backend-init source patch — ${build.message}`,
4046
- " The pinned upstream source no longer matches the patch anchor. This needs a code fix",
4047
- " (re-anchor or drop the patch), not a toolchain fix. Until then, either:",
4048
- " • build a transcribe-cli yourself, point TRANSCRIBE_CPP_BIN at it, and re-run; OR",
4049
- " • use the remote provider: parachute-vault transcription install --provider scribe-http",
4050
- );
4051
3858
  } else if (build && !build.ok) {
4052
3859
  lines.push(
4053
3860
  `\n⚠ transcribe-cli build failed (${build.stage}). The libraries + model are installed;`,
@@ -4070,39 +3877,15 @@ function noRunnableCliGuidance(
4070
3877
  return lines.join("\n");
4071
3878
  }
4072
3879
 
4073
- /** `parachute-vault transcription status` — provider + per-provider install state. */
4074
- async function cmdTranscriptionStatus(): Promise<void> {
3880
+ /** `parachute-vault transcription status` — provider + install state. */
3881
+ function cmdTranscriptionStatus(): void {
4075
3882
  const active = resolveTranscriptionProviderName();
4076
3883
  console.log(`Configured provider: ${active}`);
4077
-
4078
- // What the ratified tier table would pick for this host (install default).
4079
- const tier = selectDefaultProvider({
4080
- platform: process.platform,
4081
- arch: process.arch,
4082
- totalRamBytes: detectTotalRamBytes(),
4083
- });
4084
- console.log(
4085
- `Tier default for this host (${tier.platform}/${tier.arch}, ${tier.totalRamGb}GB): ${tier.provider}${tier.model ? ` (${tier.model})` : ""}`,
4086
- );
4087
-
4088
- // transcribe-cpp — prebuilt libs + GGUF model + built CLI. "runnable" is an
4089
- // EXECUTED verdict, not a stat: `--help` must actually exit 0 (vault#534 —
4090
- // an existsSync-only check said "yes" on Linux while the CLI exited 1 on
4091
- // every real transcription because its dlopen'd CPU backends never loaded).
4092
3884
  const paths = resolveTranscribeCppPaths();
4093
3885
  const manifest = readManifest(paths.manifestPath);
4094
3886
  const cliPresent = existsSync(paths.binPath);
4095
- const filesPresent = transcribeCppInstalled(paths); // CLI + model both on disk
4096
- let installed = false;
4097
- let notRunnableReason: string | undefined;
4098
- if (!filesPresent) {
4099
- notRunnableReason = !cliPresent ? "no transcribe-cli binary" : "no GGUF model on disk";
4100
- } else {
4101
- const probe = await probeTranscribeCliRunnable(paths.binPath);
4102
- installed = probe.ok;
4103
- notRunnableReason = probe.reason;
4104
- }
4105
- console.log(`\ntranscribe-cpp runnable: ${installed ? "yes" : `no (${notRunnableReason})`}`);
3887
+ const installed = transcribeCppInstalled(paths); // runnable CLI + model both present
3888
+ console.log(`transcribe-cpp runnable: ${installed ? "yes" : "no"}`);
4106
3889
  if (manifest) {
4107
3890
  console.log(` model: ${manifest.model} (${manifest.modelFile})`);
4108
3891
  console.log(
@@ -4115,56 +3898,14 @@ async function cmdTranscriptionStatus(): Promise<void> {
4115
3898
  );
4116
3899
  console.log(` ram: ${manifest.ram_gb}GB at install (${manifest.os}/${manifest.arch})`);
4117
3900
  } else {
4118
- console.log(` (not installed — run: parachute-vault transcription install --provider transcribe-cpp)`);
3901
+ console.log(` (nothing installed — run: parachute-vault transcription install)`);
4119
3902
  }
4120
-
4121
- // parakeet-mlx / onnx-asr — Python venv providers (scribe-fold Phase 2b).
4122
- const pyManifest = readPythonManifest(pythonManifestPath());
4123
- const pkBin = resolveParakeetMlxBin();
4124
- const pkRunnable = parakeetMlxInstalled();
4125
- console.log(`\nparakeet-mlx runnable: ${pkRunnable ? "yes" : "no"}`);
4126
- if (pkRunnable) {
4127
- console.log(` bin: ${pkBin}`);
4128
- console.log(` model: ${resolveParakeetMlxModel()} (HF cache; downloads on first use)`);
4129
- } else {
3903
+ if (active === "transcribe-cpp" && !installed) {
4130
3904
  console.log(
4131
- process.platform === "darwin" && process.arch === "arm64"
4132
- ? ` (not installed — run: parachute-vault transcription install${tier.provider === "parakeet-mlx" ? "" : " --provider parakeet-mlx"})`
4133
- : " (macOS Apple Silicon only)",
3905
+ "\n⚠ provider is transcribe-cpp but no runnable transcribe-cli is installed — transcription is offline until a CLI is available (TRANSCRIBE_CPP_BIN, or a build from source).",
4134
3906
  );
4135
3907
  }
4136
-
4137
- const oxBin = resolveOnnxAsrBin();
4138
- const oxRunnable = onnxAsrInstalled();
4139
- console.log(`onnx-asr runnable: ${oxRunnable ? "yes" : "no"}`);
4140
- if (oxRunnable) {
4141
- console.log(` bin: ${oxBin}`);
4142
- console.log(` model: ${resolveOnnxAsrModel()} (HF cache)`);
4143
- } else {
4144
- console.log(
4145
- ` (not installed — run: parachute-vault transcription install${tier.provider === "onnx-asr" ? "" : " --provider onnx-asr"})`,
4146
- );
4147
- }
4148
- if (pyManifest) {
4149
- console.log(
4150
- ` python install: ${pyManifest.provider} (${pyManifest.pipTarget}) @ ${pyManifest.installedAt}${pyManifest.venv ? ` — venv ${pyManifest.venv}` : ""}`,
4151
- );
4152
- }
4153
-
4154
- // Offline warning when the ACTIVE provider isn't runnable.
4155
- const activeRunnable =
4156
- active === "scribe-http" ||
4157
- (active === "transcribe-cpp" && installed) ||
4158
- (active === "parakeet-mlx" && pkRunnable) ||
4159
- (active === "onnx-asr" && oxRunnable);
4160
- if (!activeRunnable) {
4161
- console.log(
4162
- `\n⚠ provider is ${active} but no runnable ${active} install was found — transcription is offline until one is available (\`parachute-vault transcription install\`).`,
4163
- );
4164
- }
4165
- console.log(
4166
- `\nAvailable transcribe-cpp models (override with --model): ${Object.values(MODELS).map((m: ModelChoice) => m.name).join(", ")}`,
4167
- );
3908
+ console.log(`\nAvailable models (override with --model): ${Object.values(MODELS).map((m: ModelChoice) => m.name).join(", ")}`);
4168
3909
  }
4169
3910
 
4170
3911
  // ---------------------------------------------------------------------------
@@ -5075,27 +4816,22 @@ Schema maintenance:
5075
4816
 
5076
4817
  Transcription (scribe-fold):
5077
4818
  parachute-vault transcription install [--dry-run] [--model <id>] [--force] [--yes]
5078
- Install this host's tier-default LOCAL provider (probes
5079
- OS/arch + RAM, prints the pick + footprint, confirms):
5080
- macOS Apple Silicon 8GB+ parakeet-mlx (Python venv, MLX)
5081
- Linux 4GB+ → onnx-asr (Python venv, int8 ONNX)
5082
- Linux ~2GB → transcribe-cpp + small whisper GGUF
5083
- ~1GB / below floor → remote steer (never forced)
5084
- Activation is honest: TRANSCRIPTION_PROVIDER flips only when
5085
- a runnable binary exists. Idempotent; --dry-run prints the
5086
- pick without downloading; --force re-downloads / bypasses the
5087
- RAM floor on an explicit --provider; --yes skips the confirm.
5088
- parachute-vault transcription install --provider <name>
5089
- Override the tier default. transcribe-cpp downloads the
5090
- runtime libs + a GGUF (--model overrides the RAM-tier pick:
5091
- whisper-tiny.en | whisper-small.en | parakeet-tdt-0.6b-v3;
5092
- builds transcribe-cli from source needs a C++ compiler).
5093
- parakeet-mlx / onnx-asr install a Python venv under
5094
- ~/.parachute/transcription/venv (needs python3; apt-installs
5095
- python3/ffmpeg on Linux). scribe-http switches back to the
5096
- remote provider (no download).
5097
- parachute-vault transcription status Show the configured provider, this host's tier default, and
5098
- per-provider install state.
4819
+ Install the local transcribe-cpp provider — detects
4820
+ OS/arch + RAM, downloads the transcribe.cpp runtime
4821
+ libraries + a RAM-tier-appropriate GGUF model into
4822
+ ~/.parachute/transcription/. NOTE: v0.1.1 ships a library,
4823
+ not a CLI it activates (sets
4824
+ TRANSCRIPTION_PROVIDER=transcribe-cpp) only when a runnable
4825
+ transcribe-cli is available (TRANSCRIBE_CPP_BIN or a build
4826
+ from source), else it reports the gap. --dry-run prints the
4827
+ pick + footprint without downloading. --model overrides the
4828
+ RAM-tier choice (whisper-tiny.en | whisper-small.en |
4829
+ parakeet-tdt-0.6b-v3). --force re-downloads; --yes skips the
4830
+ confirm. Needs ffmpeg on PATH to transcode audio to 16kHz
4831
+ mono WAV (hinted if missing).
4832
+ parachute-vault transcription install --provider scribe-http
4833
+ Switch back to the remote scribe-http provider (no download).
4834
+ parachute-vault transcription status Show the configured provider + local install state.
5099
4835
 
5100
4836
  ── Advanced / standalone ──────────────────────────────────────────────
5101
4837
 
@@ -10,7 +10,7 @@
10
10
  * cursor captured at the *start* of the previous cycle. Vault writes are
11
11
  * HTTP-mediated and don't surface to filesystem watchers (the bun:sqlite DB
12
12
  * is opaque), so polling on `updated_at >= cursor` is the simplest robust
13
- * detection. See `docs/contracts/vault-portable-export.md`.
13
+ * detection. See `parachute-patterns/cookbook/vault-portable-export.md`.
14
14
  */
15
15
 
16
16
  import { ensureGitAvailable } from "./git-preflight.ts";
package/src/routes.ts CHANGED
@@ -2089,7 +2089,7 @@ export async function handleTags(
2089
2089
 
2090
2090
  // PUT /tags/:name — upsert tag identity row. Body accepts any combination
2091
2091
  // of { description, fields, relationships, parent_names }; omitted keys
2092
- // are preserved, explicit null clears. See docs/contracts/tag-data-model.md.
2092
+ // are preserved, explicit null clears. See patterns/tag-data-model.md.
2093
2093
  if (req.method === "PUT") {
2094
2094
  // Canonical-bare-tag guard (vault#XXX): normalize the upserted tag NAME so
2095
2095
  // the existing-field merge read (store.getTagSchema below) and the upsert
@@ -2114,7 +2114,7 @@ export async function handleTags(
2114
2114
  * the MCP `update-tag` tool relies on (omitted keys preserved). The
2115
2115
  * Schema editor (vault#283) sends the full map + `replace_fields: true`
2116
2116
  * so removing a field row actually deletes the field. See
2117
- * docs/contracts/tag-data-model.md.
2117
+ * patterns/tag-data-model.md.
2118
2118
  */
2119
2119
  replace_fields?: unknown;
2120
2120
  };
@@ -2211,7 +2211,7 @@ export async function handleTags(
2211
2211
  // Tag-scoped tokens reference root tags by name; deleting a referenced
2212
2212
  // tag would silently orphan the token's allowlist. Fail closed (409)
2213
2213
  // and name the offending tokens so the operator can revoke or re-mint
2214
- // before retrying. docs/contracts/tag-scoped-tokens.md §Dependencies.
2214
+ // before retrying. patterns/tag-scoped-tokens.md §Dependencies.
2215
2215
  const referenced_by = findTokensReferencingTag(store.db, tagName);
2216
2216
  if (referenced_by.length > 0) {
2217
2217
  return json(
@@ -1631,7 +1631,7 @@ describe("scope enforcement on /api/*", () => {
1631
1631
  expect(res.status).toBe(401);
1632
1632
  });
1633
1633
 
1634
- // ----- tag-scoped tokens (docs/contracts/tag-scoped-tokens.md) -----------------
1634
+ // ----- tag-scoped tokens (patterns/tag-scoped-tokens.md) -----------------
1635
1635
 
1636
1636
  /**
1637
1637
  * Mint a tag-scoped token. Mirrors `mintToken` above but threads
@@ -1745,7 +1745,7 @@ describe("scope enforcement on /api/*", () => {
1745
1745
  });
1746
1746
 
1747
1747
  // ----- Q6: orphan-sub-tag fail-open ------------------------------------
1748
- // docs/contracts/tag-scoped-tokens.md §Storage: when a sub-tag has no declared
1748
+ // patterns/tag-scoped-tokens.md §Storage: when a sub-tag has no declared
1749
1749
  // schema, the string-form root authorizes. Token allowlisted for `health`
1750
1750
  // must see `#health/food` even when no `_tags/health/food` schema exists.
1751
1751
 
package/src/routing.ts CHANGED
@@ -856,7 +856,7 @@ export async function route(
856
856
 
857
857
  const apiPath = apiMatch[1] ?? "";
858
858
 
859
- // Tag-scoped tokens (docs/contracts/tag-scoped-tokens.md): expand the token's
859
+ // Tag-scoped tokens (patterns/tag-scoped-tokens.md): expand the token's
860
860
  // root-tag allowlist into `{root} ∪ descendants(root)` once per request,
861
861
  // so handlers can intersect against the note's tag set without re-walking
862
862
  // the `_tags/<name>` hierarchy on every check. `tagScope.allowed` is null
package/src/server.ts CHANGED
@@ -31,18 +31,10 @@ import { assetsDir } from "./routes.ts";
31
31
  import { resolveScribeAuthToken, ensureScribeBearer } from "./scribe-env.ts";
32
32
  import { getCachedScribeUrl } from "./scribe-discovery.ts";
33
33
  import { TranscribeCppProvider } from "./transcription/providers/transcribe-cpp.ts";
34
- import { ParakeetMlxProvider } from "./transcription/providers/parakeet-mlx.ts";
35
- import { OnnxAsrProvider } from "./transcription/providers/onnx-asr.ts";
36
34
  import {
37
35
  resolveTranscriptionProviderName,
38
36
  resolveTranscribeCppPaths,
39
37
  transcribeCppInstalled,
40
- resolveParakeetMlxBin,
41
- resolveParakeetMlxModel,
42
- resolveOnnxAsrBin,
43
- resolveOnnxAsrModel,
44
- parakeetMlxInstalled,
45
- onnxAsrInstalled,
46
38
  } from "./transcription/select.ts";
47
39
  import { readEnvFile, setEnvVar } from "./config.ts";
48
40
  import { resolveBindHostname } from "./bind.ts";
@@ -166,29 +158,6 @@ if (providerName === "transcribe-cpp") {
166
158
  "[transcribe] TRANSCRIPTION_PROVIDER=transcribe-cpp but no runnable transcribe-cli + model installed — see `parachute-vault transcription status` (v0.1.1 ships a library, not a CLI; set TRANSCRIBE_CPP_BIN or build one)",
167
159
  );
168
160
  }
169
- } else if (providerName === "parakeet-mlx" || providerName === "onnx-asr") {
170
- // Python-based local providers (scribe-fold Phase 2b): subprocess the
171
- // parakeet-mlx / onnx-asr CLI. Only start the worker when a runnable binary
172
- // resolves (env override → managed venv → PATH) — otherwise every pending
173
- // item would terminal-fail with `missing_provider`. Cheap existsSync check
174
- // (no spawn), matching the providers' `available()`.
175
- const installed = providerName === "parakeet-mlx" ? parakeetMlxInstalled() : onnxAsrInstalled();
176
- if (installed) {
177
- const provider =
178
- providerName === "parakeet-mlx"
179
- ? new ParakeetMlxProvider({
180
- binPath: resolveParakeetMlxBin(),
181
- model: resolveParakeetMlxModel(),
182
- })
183
- : new OnnxAsrProvider({ binPath: resolveOnnxAsrBin(), model: resolveOnnxAsrModel() });
184
- transcriptionWorker = startTranscriptionWorker({ ...commonWorkerOpts, provider });
185
- wireTranscriptionWorker(transcriptionWorker);
186
- console.log(`[transcribe] worker started → ${providerName}`);
187
- } else {
188
- console.log(
189
- `[transcribe] TRANSCRIPTION_PROVIDER=${providerName} but no runnable ${providerName} binary found — run \`parachute-vault transcription install\` (see \`transcription status\`)`,
190
- );
191
- }
192
161
  } else {
193
162
  // Default scribe-http path — behavior-preserving (Phase 1). Start the worker
194
163
  // if scribe is discoverable; otherwise transcription is a no-op.
package/src/tag-scope.ts CHANGED
@@ -1,12 +1,12 @@
1
1
  /**
2
- * Tag-scope enforcement for tag-scoped tokens (docs/contracts/tag-scoped-tokens.md).
2
+ * Tag-scope enforcement for tag-scoped tokens (patterns/tag-scoped-tokens.md).
3
3
  *
4
4
  * A token's `scoped_tags` allowlist narrows its effective access to notes
5
5
  * carrying one of the allowlisted tags or a sub-tag thereof. The expansion
6
6
  * to descendants happens via the per-vault `_tags/<name>` config-note
7
7
  * hierarchy (see core/src/tag-hierarchy.ts).
8
8
  *
9
- * Auth check pseudocode (from docs/contracts/tag-scoped-tokens.md):
9
+ * Auth check pseudocode (from patterns/tag-scoped-tokens.md):
10
10
  *
11
11
  * if (!hasScope(token, ...)) return forbidden();
12
12
  * if (token.scoped_tags === null) return ok(); // unscoped
@@ -38,7 +38,7 @@ export async function expandTokenTagScope(
38
38
 
39
39
  /**
40
40
  * Return true iff the note's tag set intersects the expanded allowlist OR
41
- * — fail-open per docs/contracts/tag-scoped-tokens.md §Storage — any of the
41
+ * — fail-open per patterns/tag-scoped-tokens.md §Storage — any of the
42
42
  * note's tags has a string-form root inside `rawRoots`. The string-form
43
43
  * fallback covers the orphan-sub-tag case: a token allowlisted for
44
44
  * `health` should still see `#health/food` even when no `_tags/health/food`
@@ -54,7 +54,7 @@ export interface Token {
54
54
  * access is the intersection of `scopes` and notes carrying one of these
55
55
  * tags or a sub-tag thereof (hierarchy expansion via getTagDescendants).
56
56
  * NULL = unscoped, full vault access per `scopes`. See
57
- * docs/contracts/tag-scoped-tokens.md.
57
+ * patterns/tag-scoped-tokens.md.
58
58
  */
59
59
  scoped_tags: string[] | null;
60
60
  /**
@@ -301,7 +301,7 @@ export function markMcpMintLedgerRevoked(
301
301
  * Returns display ID + label pairs (no token-hash exposure) so error
302
302
  * envelopes can name the offending tokens for the operator. The match is
303
303
  * exact on the root name — `scoped_tags` only ever stores roots per
304
- * docs/contracts/tag-scoped-tokens.md.
304
+ * patterns/tag-scoped-tokens.md.
305
305
  */
306
306
  export function findTokensReferencingTag(
307
307
  db: Database,