@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/core/src/core.test.ts +7 -7
- package/core/src/mcp.ts +1 -1
- package/core/src/schema.ts +5 -5
- package/core/src/tag-hierarchy.ts +1 -1
- package/core/src/tag-schemas.ts +2 -2
- package/core/src/types.ts +1 -1
- package/package.json +1 -1
- package/src/admin-spa.ts +1 -2
- package/src/auth.ts +1 -1
- package/src/cli.ts +53 -317
- package/src/export-watch.ts +1 -1
- package/src/routes.ts +3 -3
- package/src/routing.test.ts +2 -2
- package/src/routing.ts +1 -1
- package/src/server.ts +0 -31
- package/src/tag-scope.ts +3 -3
- package/src/token-store.ts +2 -2
- package/src/transcription/build.test.ts +5 -86
- package/src/transcription/build.ts +7 -87
- package/src/transcription/capability.test.ts +0 -25
- package/src/transcription/capability.ts +2 -27
- package/src/transcription/select.test.ts +1 -166
- package/src/transcription/select.ts +1 -234
- package/src/vault.test.ts +1 -1
- package/src/transcription/download.test.ts +0 -101
- package/src/transcription/download.ts +0 -71
- package/src/transcription/install-python.test.ts +0 -366
- package/src/transcription/install-python.ts +0 -471
- package/src/transcription/providers/onnx-asr.test.ts +0 -229
- package/src/transcription/providers/onnx-asr.ts +0 -239
- package/src/transcription/providers/parakeet-mlx.test.ts +0 -290
- package/src/transcription/providers/parakeet-mlx.ts +0 -242
- package/src/transcription/tiers.test.ts +0 -197
- package/src/transcription/tiers.ts +0 -184
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
|
|
3553
|
-
* provider
|
|
3554
|
-
* (scribe
|
|
3555
|
-
*
|
|
3556
|
-
*
|
|
3557
|
-
*
|
|
3558
|
-
*
|
|
3559
|
-
*
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
|
3579
|
+
const provider = takeArgValue(args, "--provider").value ?? "transcribe-cpp";
|
|
3608
3580
|
|
|
3609
|
-
if (
|
|
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
|
-
|
|
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
|
|
3873
|
-
//
|
|
3874
|
-
//
|
|
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
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
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 +
|
|
4074
|
-
|
|
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
|
|
4096
|
-
|
|
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(` (
|
|
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
|
-
|
|
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
|
|
5079
|
-
OS/arch + RAM,
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
pick without downloading
|
|
5087
|
-
RAM
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
|
|
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
|
|
package/src/export-watch.ts
CHANGED
|
@@ -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 `
|
|
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
|
|
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
|
-
*
|
|
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.
|
|
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(
|
package/src/routing.test.ts
CHANGED
|
@@ -1631,7 +1631,7 @@ describe("scope enforcement on /api/*", () => {
|
|
|
1631
1631
|
expect(res.status).toBe(401);
|
|
1632
1632
|
});
|
|
1633
1633
|
|
|
1634
|
-
// ----- tag-scoped tokens (
|
|
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
|
-
//
|
|
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 (
|
|
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 (
|
|
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
|
|
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
|
|
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`
|
package/src/token-store.ts
CHANGED
|
@@ -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
|
-
*
|
|
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
|
-
*
|
|
304
|
+
* patterns/tag-scoped-tokens.md.
|
|
305
305
|
*/
|
|
306
306
|
export function findTokensReferencingTag(
|
|
307
307
|
db: Database,
|