@openparachute/vault 0.6.5-rc.2 → 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/do-param-cap.test.ts +161 -0
- package/core/src/links.ts +8 -13
- package/core/src/notes.ts +33 -15
- package/core/src/onboarding.ts +14 -296
- package/core/src/seed-packs.test.ts +191 -0
- package/core/src/seed-packs.ts +559 -0
- package/core/src/sql-in.test.ts +58 -0
- package/core/src/sql-in.ts +76 -0
- package/core/src/transcription/provider.ts +141 -0
- package/core/src/vault-projection.ts +1 -1
- package/core/src/wikilinks.ts +10 -4
- package/package.json +1 -1
- package/src/add-pack.test.ts +142 -0
- package/src/cli.ts +542 -7
- package/src/onboarding-seed.test.ts +126 -40
- package/src/onboarding-seed.ts +41 -46
- package/src/routes.ts +17 -0
- package/src/server.ts +77 -31
- package/src/transcription/build.test.ts +224 -0
- package/src/transcription/build.ts +252 -0
- package/src/transcription/capability.test.ts +93 -0
- package/src/transcription/capability.ts +71 -0
- package/src/transcription/install.test.ts +167 -0
- package/src/transcription/install.ts +296 -0
- package/src/transcription/providers/scribe-http.test.ts +195 -0
- package/src/transcription/providers/scribe-http.ts +144 -0
- package/src/transcription/providers/transcribe-cpp.test.ts +314 -0
- package/src/transcription/providers/transcribe-cpp.ts +293 -0
- package/src/transcription/select.test.ts +134 -0
- package/src/transcription/select.ts +164 -0
- package/src/transcription-worker.test.ts +44 -0
- package/src/transcription-worker.ts +57 -122
- package/src/vault-create.test.ts +38 -10
- package/src/vault.test.ts +48 -0
package/src/cli.ts
CHANGED
|
@@ -17,9 +17,9 @@
|
|
|
17
17
|
* parachute-vault status — show full status
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
import { resolve } from "path";
|
|
20
|
+
import { resolve, join, dirname } from "path";
|
|
21
21
|
import { homedir } from "os";
|
|
22
|
-
import { existsSync, readFileSync, writeFileSync, rmSync, mkdirSync } from "fs";
|
|
22
|
+
import { existsSync, readFileSync, writeFileSync, rmSync, mkdirSync, copyFileSync, chmodSync, readdirSync, statSync, renameSync } from "fs";
|
|
23
23
|
// JSON import — resolved at module load, works for both dev runs
|
|
24
24
|
// (`bun src/cli.ts …`) and the published package (`bunx @openparachute/vault`)
|
|
25
25
|
// because package.json ships at the root next to src/.
|
|
@@ -140,6 +140,28 @@ import {
|
|
|
140
140
|
verifyAndConsumeBackupCode,
|
|
141
141
|
getTotpSecret,
|
|
142
142
|
} from "./two-factor.ts";
|
|
143
|
+
import {
|
|
144
|
+
planInstall,
|
|
145
|
+
detectTotalRamBytes,
|
|
146
|
+
isSharedLibFile,
|
|
147
|
+
MODELS,
|
|
148
|
+
type InstallPlan,
|
|
149
|
+
type ModelChoice,
|
|
150
|
+
} from "./transcription/install.ts";
|
|
151
|
+
import {
|
|
152
|
+
resolveTranscribeCppPaths,
|
|
153
|
+
resolveTranscriptionProviderName,
|
|
154
|
+
transcribeCppInstalled,
|
|
155
|
+
readManifest,
|
|
156
|
+
type TranscribeCppManifest,
|
|
157
|
+
} from "./transcription/select.ts";
|
|
158
|
+
import {
|
|
159
|
+
buildTranscribeCli,
|
|
160
|
+
cliSourceUrl,
|
|
161
|
+
compilerInstallHint,
|
|
162
|
+
TRANSCRIBE_CPP_SOURCE_REF,
|
|
163
|
+
type CliBuildResult,
|
|
164
|
+
} from "./transcription/build.ts";
|
|
143
165
|
|
|
144
166
|
// ---------------------------------------------------------------------------
|
|
145
167
|
// Argument parsing
|
|
@@ -181,6 +203,9 @@ switch (command) {
|
|
|
181
203
|
case "create":
|
|
182
204
|
await cmdCreate(cmdArgs);
|
|
183
205
|
break;
|
|
206
|
+
case "add-pack":
|
|
207
|
+
await cmdAddPack(cmdArgs);
|
|
208
|
+
break;
|
|
184
209
|
case "list":
|
|
185
210
|
case "ls":
|
|
186
211
|
cmdList();
|
|
@@ -246,6 +271,9 @@ switch (command) {
|
|
|
246
271
|
case "schema":
|
|
247
272
|
await cmdSchema(cmdArgs);
|
|
248
273
|
break;
|
|
274
|
+
case "transcription":
|
|
275
|
+
await cmdTranscription(cmdArgs);
|
|
276
|
+
break;
|
|
249
277
|
case "help":
|
|
250
278
|
case "--help":
|
|
251
279
|
case "-h":
|
|
@@ -3496,6 +3524,390 @@ function printHistoryUsage(): void {
|
|
|
3496
3524
|
console.error(" --json Emit the history as JSON instead of the human-readable list");
|
|
3497
3525
|
}
|
|
3498
3526
|
|
|
3527
|
+
// ---------------------------------------------------------------------------
|
|
3528
|
+
// Transcription — `parachute-vault transcription <subcommand>` (scribe-fold 2a)
|
|
3529
|
+
// ---------------------------------------------------------------------------
|
|
3530
|
+
|
|
3531
|
+
/**
|
|
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).
|
|
3540
|
+
*
|
|
3541
|
+
* `parachute-vault transcription status` — show the configured provider +
|
|
3542
|
+
* install state.
|
|
3543
|
+
*/
|
|
3544
|
+
async function cmdTranscription(args: string[]) {
|
|
3545
|
+
const sub = args[0];
|
|
3546
|
+
if (sub === "install") {
|
|
3547
|
+
await cmdTranscriptionInstall(args.slice(1));
|
|
3548
|
+
return;
|
|
3549
|
+
}
|
|
3550
|
+
if (sub === "status" || sub === undefined) {
|
|
3551
|
+
cmdTranscriptionStatus();
|
|
3552
|
+
return;
|
|
3553
|
+
}
|
|
3554
|
+
console.error(`Unknown transcription subcommand: ${sub}`);
|
|
3555
|
+
console.error("Usage: parachute-vault transcription install [--dry-run] [--model <id>] [--provider <name>]");
|
|
3556
|
+
console.error(" parachute-vault transcription status");
|
|
3557
|
+
process.exit(1);
|
|
3558
|
+
}
|
|
3559
|
+
|
|
3560
|
+
/** Render an install plan for the human (shared by --dry-run + the real run). */
|
|
3561
|
+
function printTranscriptionPlan(plan: InstallPlan): void {
|
|
3562
|
+
console.log(`Host: ${plan.platform}/${plan.arch}, ${plan.totalRamGb}GB RAM`);
|
|
3563
|
+
if (!plan.supported) {
|
|
3564
|
+
console.log(`\n ${plan.refused ? "Refused" : "Unsupported"}: ${plan.reason}`);
|
|
3565
|
+
return;
|
|
3566
|
+
}
|
|
3567
|
+
const m = plan.model!;
|
|
3568
|
+
console.log(`Binary: ${plan.asset!.asset}`);
|
|
3569
|
+
console.log(`Model: ${m.name} (${m.file})`);
|
|
3570
|
+
console.log(` ${m.note}`);
|
|
3571
|
+
console.log(` ~${m.approxSizeMb}MB download, ~${m.approxRuntimeGb}GB peak RAM while transcribing`);
|
|
3572
|
+
}
|
|
3573
|
+
|
|
3574
|
+
async function cmdTranscriptionInstall(args: string[]) {
|
|
3575
|
+
const dryRun = args.includes("--dry-run") || args.includes("--plan");
|
|
3576
|
+
const force = args.includes("--force");
|
|
3577
|
+
const yes = args.includes("--yes") || args.includes("-y");
|
|
3578
|
+
const overrideModel = takeArgValue(args, "--model").value;
|
|
3579
|
+
const provider = takeArgValue(args, "--provider").value ?? "transcribe-cpp";
|
|
3580
|
+
|
|
3581
|
+
if (provider === "scribe-http") {
|
|
3582
|
+
// Just flip config back to the remote provider — no download.
|
|
3583
|
+
if (dryRun) {
|
|
3584
|
+
console.log("Would set TRANSCRIPTION_PROVIDER=scribe-http in the vault .env (remote provider; no download).");
|
|
3585
|
+
return;
|
|
3586
|
+
}
|
|
3587
|
+
setEnvVar("TRANSCRIPTION_PROVIDER", "scribe-http");
|
|
3588
|
+
console.log("Set TRANSCRIPTION_PROVIDER=scribe-http. Restart vault to apply. (Remote provider — no local binary/model needed.)");
|
|
3589
|
+
return;
|
|
3590
|
+
}
|
|
3591
|
+
if (provider !== "transcribe-cpp") {
|
|
3592
|
+
console.error(`Unknown --provider "${provider}". Valid: transcribe-cpp, scribe-http.`);
|
|
3593
|
+
process.exit(1);
|
|
3594
|
+
}
|
|
3595
|
+
|
|
3596
|
+
const plan = planInstall({
|
|
3597
|
+
platform: process.platform,
|
|
3598
|
+
arch: process.arch,
|
|
3599
|
+
totalRamBytes: detectTotalRamBytes(),
|
|
3600
|
+
overrideModel,
|
|
3601
|
+
});
|
|
3602
|
+
|
|
3603
|
+
console.log("Transcription install plan (transcribe-cpp — local, no Python):\n");
|
|
3604
|
+
printTranscriptionPlan(plan);
|
|
3605
|
+
|
|
3606
|
+
if (!plan.supported) {
|
|
3607
|
+
// Refused / unsupported is not a hard error — it's a steer to remote.
|
|
3608
|
+
console.log("\nNo local install performed. See the note above.");
|
|
3609
|
+
process.exit(dryRun ? 0 : 1);
|
|
3610
|
+
}
|
|
3611
|
+
|
|
3612
|
+
const paths = resolveTranscribeCppPaths();
|
|
3613
|
+
console.log(`\nInstall dir: ${paths.dir}`);
|
|
3614
|
+
|
|
3615
|
+
if (dryRun) {
|
|
3616
|
+
console.log("\n(--dry-run) No files downloaded.");
|
|
3617
|
+
return;
|
|
3618
|
+
}
|
|
3619
|
+
|
|
3620
|
+
if (!yes) {
|
|
3621
|
+
const ok = await confirm(`Download ~${plan.footprintMb}MB and install to ${paths.dir}?`, true);
|
|
3622
|
+
if (!ok) {
|
|
3623
|
+
console.log("Aborted.");
|
|
3624
|
+
return;
|
|
3625
|
+
}
|
|
3626
|
+
}
|
|
3627
|
+
|
|
3628
|
+
// ffmpeg hint (non-fatal — the transcode path checks at runtime).
|
|
3629
|
+
if (!Bun.which("ffmpeg")) {
|
|
3630
|
+
console.warn(
|
|
3631
|
+
"\n⚠ ffmpeg not found on PATH. transcribe-cli needs 16kHz mono WAV; non-WAV capture audio is transcoded via ffmpeg.\n" +
|
|
3632
|
+
" Install it: apt install ffmpeg (Debian/Ubuntu) | brew install ffmpeg (macOS)",
|
|
3633
|
+
);
|
|
3634
|
+
}
|
|
3635
|
+
|
|
3636
|
+
try {
|
|
3637
|
+
mkdirSync(paths.binDir, { recursive: true });
|
|
3638
|
+
mkdirSync(paths.libsDir, { recursive: true });
|
|
3639
|
+
mkdirSync(paths.modelsDir, { recursive: true });
|
|
3640
|
+
|
|
3641
|
+
// 1) Release library bundle → extract the runtime dylibs/sos into libs/.
|
|
3642
|
+
// transcribe.cpp v0.1.1 ships a LIBRARY, not a prebuilt transcribe-cli,
|
|
3643
|
+
// so a CLI is usually NOT placed here — `installTranscribeLibs` still
|
|
3644
|
+
// opportunistically places one if a (future) asset includes it.
|
|
3645
|
+
const existingLibs = (readdirSync(paths.libsDir) as string[]).filter(isSharedLibFile);
|
|
3646
|
+
let libFiles = existingLibs;
|
|
3647
|
+
if (existingLibs.length && !force) {
|
|
3648
|
+
console.log(`\n✓ runtime libraries already present (${paths.libsDir}) — skipping (use --force to re-download).`);
|
|
3649
|
+
} else {
|
|
3650
|
+
console.log(`\nDownloading ${plan.asset!.asset} …`);
|
|
3651
|
+
libFiles = await installTranscribeLibs(plan, paths);
|
|
3652
|
+
console.log(
|
|
3653
|
+
libFiles.length
|
|
3654
|
+
? `✓ Extracted ${libFiles.length} runtime library file(s) → ${paths.libsDir}`
|
|
3655
|
+
: `⚠ no shared libraries found in ${plan.asset!.asset} (the asset layout may have changed).`,
|
|
3656
|
+
);
|
|
3657
|
+
}
|
|
3658
|
+
|
|
3659
|
+
// 2) Model GGUF.
|
|
3660
|
+
const modelDest = join(paths.modelsDir, plan.model!.file);
|
|
3661
|
+
if (existsSync(modelDest) && !force) {
|
|
3662
|
+
console.log(`✓ model already present (${modelDest}) — skipping (use --force to re-download).`);
|
|
3663
|
+
} else {
|
|
3664
|
+
console.log(`Downloading model ${plan.model!.file} (~${plan.model!.approxSizeMb}MB) …`);
|
|
3665
|
+
await downloadTo(plan.model!.url, modelDest);
|
|
3666
|
+
}
|
|
3667
|
+
|
|
3668
|
+
// 3) Build transcribe-cli from source against the extracted dylibs.
|
|
3669
|
+
// transcribe.cpp v0.1.1 ships a LIBRARY, not a prebuilt CLI, so we
|
|
3670
|
+
// compile examples/cli/main.cpp + examples/common/wav.cpp against
|
|
3671
|
+
// libtranscribe (a tiny ~127KB compile — no ggml rebuild). Non-fatal:
|
|
3672
|
+
// any failure keeps the current provider + prints guidance.
|
|
3673
|
+
const buildResult = await maybeBuildTranscribeCli(paths, libFiles, force);
|
|
3674
|
+
|
|
3675
|
+
// 4) Manifest.
|
|
3676
|
+
const manifest: TranscribeCppManifest = {
|
|
3677
|
+
version: plan.asset!.asset,
|
|
3678
|
+
asset: plan.asset!.asset,
|
|
3679
|
+
binFile: "transcribe-cli",
|
|
3680
|
+
model: plan.model!.name,
|
|
3681
|
+
modelFile: plan.model!.file,
|
|
3682
|
+
libFiles,
|
|
3683
|
+
binBuiltFrom: buildResult?.ok ? TRANSCRIBE_CPP_SOURCE_REF : undefined,
|
|
3684
|
+
os: plan.platform,
|
|
3685
|
+
arch: plan.arch,
|
|
3686
|
+
ram_gb: plan.totalRamGb,
|
|
3687
|
+
installedAt: new Date().toISOString(),
|
|
3688
|
+
};
|
|
3689
|
+
writeFileSync(paths.manifestPath, JSON.stringify(manifest, null, 2));
|
|
3690
|
+
|
|
3691
|
+
// 5) CLI resolution + provider switch — honest, no silent success. `binPath`
|
|
3692
|
+
// resolves to TRANSCRIBE_CPP_BIN or the built binary; we activate
|
|
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.
|
|
3696
|
+
if (existsSync(paths.binPath)) {
|
|
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`);
|
|
3701
|
+
} else {
|
|
3702
|
+
console.log(`\n✓ Libraries + model installed to ${paths.dir} — transcription is NOT yet activated.`);
|
|
3703
|
+
console.log(noRunnableCliGuidance(paths, buildResult));
|
|
3704
|
+
}
|
|
3705
|
+
} catch (err) {
|
|
3706
|
+
console.error(`\nInstall failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
3707
|
+
console.error("The verb is idempotent — fix the issue and re-run. Or use the remote provider: parachute-vault transcription install --provider scribe-http");
|
|
3708
|
+
process.exit(1);
|
|
3709
|
+
}
|
|
3710
|
+
}
|
|
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
|
+
|
|
3719
|
+
/**
|
|
3720
|
+
* Download the release tarball, extract it, and place its runtime shared
|
|
3721
|
+
* libraries (libtranscribe + libggml `.dylib`/`.so`) into `paths.libsDir`,
|
|
3722
|
+
* returning the extracted lib filenames. transcribe.cpp v0.1.1 ships a LIBRARY,
|
|
3723
|
+
* not a prebuilt `transcribe-cli` — so we also look for a CLI binary (a future
|
|
3724
|
+
* asset may include one) and, if present, place it at `paths.binPath` + chmod +x
|
|
3725
|
+
* (the caller decides activation from whether a CLI exists). Uses system `tar`
|
|
3726
|
+
* (present on Linux + macOS). Kept out of the plan/dry-run path so tests never
|
|
3727
|
+
* hit the network.
|
|
3728
|
+
*/
|
|
3729
|
+
async function installTranscribeLibs(
|
|
3730
|
+
plan: InstallPlan,
|
|
3731
|
+
paths: ReturnType<typeof resolveTranscribeCppPaths>,
|
|
3732
|
+
): Promise<string[]> {
|
|
3733
|
+
const tmpRoot = join(paths.dir, ".dl");
|
|
3734
|
+
mkdirSync(tmpRoot, { recursive: true });
|
|
3735
|
+
const tarball = join(tmpRoot, plan.asset!.asset);
|
|
3736
|
+
await downloadTo(plan.asset!.url, tarball);
|
|
3737
|
+
|
|
3738
|
+
const extractDir = join(tmpRoot, "x");
|
|
3739
|
+
rmSync(extractDir, { recursive: true, force: true });
|
|
3740
|
+
mkdirSync(extractDir, { recursive: true });
|
|
3741
|
+
const tarProc = Bun.spawnSync(["tar", "-xzf", tarball, "-C", extractDir]);
|
|
3742
|
+
if (tarProc.exitCode !== 0) {
|
|
3743
|
+
throw new Error(`tar extract failed: ${new TextDecoder().decode(tarProc.stderr)}`);
|
|
3744
|
+
}
|
|
3745
|
+
|
|
3746
|
+
// Walk the extracted tree: keep the shared libs, opportunistically place a CLI.
|
|
3747
|
+
mkdirSync(paths.libsDir, { recursive: true });
|
|
3748
|
+
const libFiles: string[] = [];
|
|
3749
|
+
for (const rel of readdirSync(extractDir, { recursive: true }) as string[]) {
|
|
3750
|
+
const abs = join(extractDir, rel);
|
|
3751
|
+
if (!statSync(abs).isFile()) continue;
|
|
3752
|
+
const base = rel.split("/").pop()!;
|
|
3753
|
+
if (isSharedLibFile(base)) {
|
|
3754
|
+
copyFileSync(abs, join(paths.libsDir, base));
|
|
3755
|
+
libFiles.push(base);
|
|
3756
|
+
} else if (base === "transcribe-cli") {
|
|
3757
|
+
copyFileSync(abs, paths.binPath);
|
|
3758
|
+
chmodSync(paths.binPath, 0o755);
|
|
3759
|
+
}
|
|
3760
|
+
}
|
|
3761
|
+
rmSync(tmpRoot, { recursive: true, force: true });
|
|
3762
|
+
return libFiles;
|
|
3763
|
+
}
|
|
3764
|
+
|
|
3765
|
+
/**
|
|
3766
|
+
* Build `transcribe-cli` from source against the extracted dylibs, honoring
|
|
3767
|
+
* idempotency + the `TRANSCRIBE_CPP_BIN` operator override. Returns the build
|
|
3768
|
+
* result, or `null` when the build was intentionally skipped (already built /
|
|
3769
|
+
* operator-provided binary / no libs to link). Non-fatal — logs progress and
|
|
3770
|
+
* warnings; failures come back as a typed `{ ok:false }` the caller reports.
|
|
3771
|
+
*/
|
|
3772
|
+
async function maybeBuildTranscribeCli(
|
|
3773
|
+
paths: ReturnType<typeof resolveTranscribeCppPaths>,
|
|
3774
|
+
libFiles: string[],
|
|
3775
|
+
force: boolean,
|
|
3776
|
+
): Promise<CliBuildResult | null> {
|
|
3777
|
+
if (process.env.TRANSCRIBE_CPP_BIN?.trim()) {
|
|
3778
|
+
console.log(
|
|
3779
|
+
existsSync(paths.binPath)
|
|
3780
|
+
? `\n✓ Using operator-provided TRANSCRIBE_CPP_BIN=${paths.binPath} — skipping source build.`
|
|
3781
|
+
: `\n⚠ TRANSCRIBE_CPP_BIN=${paths.binPath} is set but no file exists there — skipping source build; nothing will be activated until it points at a real binary.`,
|
|
3782
|
+
);
|
|
3783
|
+
return null;
|
|
3784
|
+
}
|
|
3785
|
+
if (existsSync(paths.binPath) && !force) {
|
|
3786
|
+
console.log(`\n✓ transcribe-cli already built (${paths.binPath}) — skipping build (use --force to rebuild).`);
|
|
3787
|
+
return null;
|
|
3788
|
+
}
|
|
3789
|
+
if (libFiles.length === 0) {
|
|
3790
|
+
console.warn(`\n⚠ no runtime libraries were extracted — cannot build transcribe-cli. Skipping build.`);
|
|
3791
|
+
return null;
|
|
3792
|
+
}
|
|
3793
|
+
|
|
3794
|
+
console.log(`\nBuilding transcribe-cli from source (transcribe.cpp @ ${TRANSCRIBE_CPP_SOURCE_REF.slice(0, 12)}) …`);
|
|
3795
|
+
mkdirSync(paths.binDir, { recursive: true });
|
|
3796
|
+
const srcDir = join(paths.dir, ".src");
|
|
3797
|
+
const result = await buildTranscribeCli(
|
|
3798
|
+
{ srcDir, libsDir: paths.libsDir, binPath: paths.binPath },
|
|
3799
|
+
{
|
|
3800
|
+
platform: process.platform,
|
|
3801
|
+
which: (c) => Bun.which(c),
|
|
3802
|
+
fetchSource: async (files, dir) => {
|
|
3803
|
+
rmSync(dir, { recursive: true, force: true });
|
|
3804
|
+
for (const rel of files) {
|
|
3805
|
+
const dest = join(dir, rel);
|
|
3806
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
3807
|
+
await downloadTo(cliSourceUrl(rel), dest);
|
|
3808
|
+
}
|
|
3809
|
+
},
|
|
3810
|
+
compile: (cmd) => {
|
|
3811
|
+
const p = Bun.spawnSync(cmd);
|
|
3812
|
+
return { exitCode: p.exitCode ?? 1, stderr: new TextDecoder().decode(p.stderr) };
|
|
3813
|
+
},
|
|
3814
|
+
exists: existsSync,
|
|
3815
|
+
removeBin: (p) => rmSync(p, { force: true }),
|
|
3816
|
+
rename: (from, to) => renameSync(from, to),
|
|
3817
|
+
},
|
|
3818
|
+
);
|
|
3819
|
+
|
|
3820
|
+
if (result.ok) {
|
|
3821
|
+
chmodSync(paths.binPath, 0o755); // belt-and-suspenders; the compiler emits 0755
|
|
3822
|
+
rmSync(srcDir, { recursive: true, force: true }); // tidy — source is re-fetchable
|
|
3823
|
+
console.log(`✓ Built transcribe-cli → ${result.binPath}`);
|
|
3824
|
+
} else {
|
|
3825
|
+
// Keep srcDir on failure so the printed compile command can be re-run by hand.
|
|
3826
|
+
console.warn(`⚠ transcribe-cli build failed (${result.stage}): ${result.message}`);
|
|
3827
|
+
if (result.command) console.warn(` compile command tried:\n ${result.command}`);
|
|
3828
|
+
}
|
|
3829
|
+
return result;
|
|
3830
|
+
}
|
|
3831
|
+
|
|
3832
|
+
/**
|
|
3833
|
+
* The "no runnable CLI" guidance printed when activation can't proceed —
|
|
3834
|
+
* tailored to WHY (build failed at toolchain vs compile, or was skipped).
|
|
3835
|
+
*/
|
|
3836
|
+
function noRunnableCliGuidance(
|
|
3837
|
+
paths: ReturnType<typeof resolveTranscribeCppPaths>,
|
|
3838
|
+
build: CliBuildResult | null,
|
|
3839
|
+
): string {
|
|
3840
|
+
const lines: string[] = [];
|
|
3841
|
+
if (build && !build.ok && build.stage === "toolchain") {
|
|
3842
|
+
lines.push(
|
|
3843
|
+
`\n⚠ Could not build transcribe-cli — ${build.message}`,
|
|
3844
|
+
" To finish, either:",
|
|
3845
|
+
` • install a C++ compiler and re-run this verb (${compilerInstallHint(process.platform)}); OR`,
|
|
3846
|
+
" • build transcribe-cli yourself, point TRANSCRIBE_CPP_BIN at it, and re-run; OR",
|
|
3847
|
+
" • use the remote provider: parachute-vault transcription install --provider scribe-http",
|
|
3848
|
+
);
|
|
3849
|
+
} else if (build && !build.ok && build.stage === "fetch") {
|
|
3850
|
+
lines.push(
|
|
3851
|
+
`\n⚠ Could not fetch the transcribe-cli source — ${build.message}`,
|
|
3852
|
+
" The libraries + model are installed; only the source download failed (check network/proxy).",
|
|
3853
|
+
" To finish, either:",
|
|
3854
|
+
" • re-run this verb once connectivity is restored; OR",
|
|
3855
|
+
" • build transcribe-cli yourself, point TRANSCRIBE_CPP_BIN at it, and re-run; OR",
|
|
3856
|
+
" • use the remote provider: parachute-vault transcription install --provider scribe-http",
|
|
3857
|
+
);
|
|
3858
|
+
} else if (build && !build.ok) {
|
|
3859
|
+
lines.push(
|
|
3860
|
+
`\n⚠ transcribe-cli build failed (${build.stage}). The libraries + model are installed;`,
|
|
3861
|
+
" the CLI is not. To finish, either:",
|
|
3862
|
+
" • fix the toolchain and re-run this verb; OR",
|
|
3863
|
+
" • build transcribe-cli yourself (the compile command tried is printed above),",
|
|
3864
|
+
" point TRANSCRIBE_CPP_BIN at the result, and re-run; OR",
|
|
3865
|
+
" • use the remote provider: parachute-vault transcription install --provider scribe-http",
|
|
3866
|
+
);
|
|
3867
|
+
} else {
|
|
3868
|
+
// Build skipped (no libs, or an operator-provided binary that isn't present).
|
|
3869
|
+
lines.push(
|
|
3870
|
+
"\n⚠ No runnable transcribe-cli is available yet. To finish, either:",
|
|
3871
|
+
` • re-run this verb so it builds a transcribe-cli against ${paths.libsDir} (needs a C++ compiler); OR`,
|
|
3872
|
+
" • point TRANSCRIBE_CPP_BIN at a transcribe-cli you built, and re-run; OR",
|
|
3873
|
+
" • use the remote provider: parachute-vault transcription install --provider scribe-http",
|
|
3874
|
+
);
|
|
3875
|
+
}
|
|
3876
|
+
lines.push(" Your current provider is unchanged — check with `parachute-vault transcription status`.");
|
|
3877
|
+
return lines.join("\n");
|
|
3878
|
+
}
|
|
3879
|
+
|
|
3880
|
+
/** `parachute-vault transcription status` — provider + install state. */
|
|
3881
|
+
function cmdTranscriptionStatus(): void {
|
|
3882
|
+
const active = resolveTranscriptionProviderName();
|
|
3883
|
+
console.log(`Configured provider: ${active}`);
|
|
3884
|
+
const paths = resolveTranscribeCppPaths();
|
|
3885
|
+
const manifest = readManifest(paths.manifestPath);
|
|
3886
|
+
const cliPresent = existsSync(paths.binPath);
|
|
3887
|
+
const installed = transcribeCppInstalled(paths); // runnable CLI + model both present
|
|
3888
|
+
console.log(`transcribe-cpp runnable: ${installed ? "yes" : "no"}`);
|
|
3889
|
+
if (manifest) {
|
|
3890
|
+
console.log(` model: ${manifest.model} (${manifest.modelFile})`);
|
|
3891
|
+
console.log(
|
|
3892
|
+
` libs: ${paths.libsDir}${manifest.libFiles?.length ? ` (${manifest.libFiles.length} file(s))` : ""}`,
|
|
3893
|
+
);
|
|
3894
|
+
console.log(
|
|
3895
|
+
cliPresent
|
|
3896
|
+
? ` cli: ${paths.binPath}${manifest.binBuiltFrom ? ` (built from source @ ${manifest.binBuiltFrom.slice(0, 12)})` : ""}`
|
|
3897
|
+
: ` cli: NOT FOUND — re-run \`transcription install\` to build one (needs a C++ compiler), or set TRANSCRIBE_CPP_BIN`,
|
|
3898
|
+
);
|
|
3899
|
+
console.log(` ram: ${manifest.ram_gb}GB at install (${manifest.os}/${manifest.arch})`);
|
|
3900
|
+
} else {
|
|
3901
|
+
console.log(` (nothing installed — run: parachute-vault transcription install)`);
|
|
3902
|
+
}
|
|
3903
|
+
if (active === "transcribe-cpp" && !installed) {
|
|
3904
|
+
console.log(
|
|
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).",
|
|
3906
|
+
);
|
|
3907
|
+
}
|
|
3908
|
+
console.log(`\nAvailable models (override with --model): ${Object.values(MODELS).map((m: ModelChoice) => m.name).join(", ")}`);
|
|
3909
|
+
}
|
|
3910
|
+
|
|
3499
3911
|
// ---------------------------------------------------------------------------
|
|
3500
3912
|
// Schema maintenance — `parachute-vault schema <subcommand>`
|
|
3501
3913
|
// ---------------------------------------------------------------------------
|
|
@@ -3962,11 +4374,14 @@ async function createVault(
|
|
|
3962
4374
|
// row is written — vault is a pure hub resource-server post-0.5.0.
|
|
3963
4375
|
const seedStore = getVaultStore(name);
|
|
3964
4376
|
|
|
3965
|
-
// Seed the
|
|
3966
|
-
//
|
|
3967
|
-
//
|
|
3968
|
-
//
|
|
3969
|
-
//
|
|
4377
|
+
// Seed the default packs (welcome web + capture tag, Getting Started guide)
|
|
4378
|
+
// so the first minute shows a small living graph and a connected AI can read
|
|
4379
|
+
// the guide and help the operator set the vault up. Surface Starter is NOT
|
|
4380
|
+
// default-seeded — `parachute-vault add-pack surface-starter` adds it later.
|
|
4381
|
+
// Idempotent + best-effort: only writes notes that are absent, and never
|
|
4382
|
+
// fails the create. Runs BEFORE the mirror bootstrap so the seeded content
|
|
4383
|
+
// lands in the initial mirror commit. See src/onboarding-seed.ts +
|
|
4384
|
+
// core/src/seed-packs.ts.
|
|
3970
4385
|
await seedOnboardingNotesBestEffort(seedStore);
|
|
3971
4386
|
|
|
3972
4387
|
// Default new vaults to an internal live mirror (local git backup of the
|
|
@@ -4088,6 +4503,99 @@ function installMcpConfig(opts: InstallMcpConfigOpts): void {
|
|
|
4088
4503
|
writeFileSync(targetPath, JSON.stringify(config, null, 2) + "\n");
|
|
4089
4504
|
}
|
|
4090
4505
|
|
|
4506
|
+
/**
|
|
4507
|
+
* `parachute-vault add-pack <pack> [--vault <name>]` — apply a named seed
|
|
4508
|
+
* pack (starter notes + tags) to a vault.
|
|
4509
|
+
*
|
|
4510
|
+
* New vaults default-seed the `welcome` + `getting-started` packs at create;
|
|
4511
|
+
* this verb adds the opt-in ones (today: `surface-starter`) — or re-applies a
|
|
4512
|
+
* default pack to an older vault that predates it. Idempotent per item: a
|
|
4513
|
+
* note is created only when no note lives at its path (a re-run never
|
|
4514
|
+
* clobbers an edited note); tag upserts converge on the same rows.
|
|
4515
|
+
*
|
|
4516
|
+
* Unlike `import` (bulk stream writes, vault#323 daemon-busy guard), this is
|
|
4517
|
+
* a handful of tiny writes against a WAL database, so it runs fine next to a
|
|
4518
|
+
* live daemon; a write-lock collision (SQLITE_BUSY) is caught with a
|
|
4519
|
+
* retry-is-safe message instead of pre-emptively demanding a daemon stop.
|
|
4520
|
+
*/
|
|
4521
|
+
async function cmdAddPack(args: string[]) {
|
|
4522
|
+
let vaultName = "default";
|
|
4523
|
+
const positional: string[] = [];
|
|
4524
|
+
for (let i = 0; i < args.length; i++) {
|
|
4525
|
+
const arg = args[i]!;
|
|
4526
|
+
if (arg === "--vault") {
|
|
4527
|
+
const v = args[++i];
|
|
4528
|
+
if (!v) {
|
|
4529
|
+
console.error("--vault requires a value.");
|
|
4530
|
+
process.exit(1);
|
|
4531
|
+
}
|
|
4532
|
+
vaultName = v;
|
|
4533
|
+
} else {
|
|
4534
|
+
positional.push(arg);
|
|
4535
|
+
}
|
|
4536
|
+
}
|
|
4537
|
+
const packName = positional[0];
|
|
4538
|
+
|
|
4539
|
+
const { listSeedPacks, getSeedPack, applySeedPack } = await import(
|
|
4540
|
+
"../core/src/seed-packs.ts"
|
|
4541
|
+
);
|
|
4542
|
+
|
|
4543
|
+
const printPacks = () => {
|
|
4544
|
+
console.error("\nAvailable packs:");
|
|
4545
|
+
for (const p of listSeedPacks()) {
|
|
4546
|
+
console.error(` ${p.name.padEnd(17)} ${p.description}`);
|
|
4547
|
+
}
|
|
4548
|
+
};
|
|
4549
|
+
|
|
4550
|
+
if (!packName) {
|
|
4551
|
+
console.error("Usage: parachute-vault add-pack <pack> [--vault <name>]");
|
|
4552
|
+
printPacks();
|
|
4553
|
+
process.exit(1);
|
|
4554
|
+
}
|
|
4555
|
+
|
|
4556
|
+
const pack = getSeedPack(packName);
|
|
4557
|
+
if (!pack) {
|
|
4558
|
+
console.error(`Unknown pack: "${packName}"`);
|
|
4559
|
+
printPacks();
|
|
4560
|
+
process.exit(1);
|
|
4561
|
+
}
|
|
4562
|
+
|
|
4563
|
+
const config = readVaultConfig(vaultName);
|
|
4564
|
+
if (!config) {
|
|
4565
|
+
console.error(`Vault "${vaultName}" not found. Run: parachute-vault create ${vaultName}`);
|
|
4566
|
+
process.exit(1);
|
|
4567
|
+
}
|
|
4568
|
+
|
|
4569
|
+
const store = getVaultStore(vaultName);
|
|
4570
|
+
try {
|
|
4571
|
+
const result = await applySeedPack(store, pack);
|
|
4572
|
+
console.log(`Pack "${pack.name}" applied to vault "${vaultName}":`);
|
|
4573
|
+
for (const path of result.seededNotes) {
|
|
4574
|
+
console.log(` + note ${path}`);
|
|
4575
|
+
}
|
|
4576
|
+
for (const path of result.skippedNotes) {
|
|
4577
|
+
console.log(` = note ${path} (already exists — left untouched)`);
|
|
4578
|
+
}
|
|
4579
|
+
for (const tag of result.tags) {
|
|
4580
|
+
console.log(` ~ tag ${tag} (upserted)`);
|
|
4581
|
+
}
|
|
4582
|
+
if (result.seededNotes.length === 0 && result.tags.length === 0) {
|
|
4583
|
+
console.log(" Nothing to add — everything was already in place.");
|
|
4584
|
+
}
|
|
4585
|
+
} catch (err) {
|
|
4586
|
+
const message = (err as Error)?.message ?? String(err);
|
|
4587
|
+
if (message.includes("SQLITE_BUSY") || (err as { code?: string })?.code === "SQLITE_BUSY") {
|
|
4588
|
+
console.error(
|
|
4589
|
+
`error: the vault database is busy (a running daemon holds the write lock). ` +
|
|
4590
|
+
`add-pack is idempotent — re-run it, or stop the daemon first with: parachute stop vault`,
|
|
4591
|
+
);
|
|
4592
|
+
} else {
|
|
4593
|
+
console.error(`error: failed to apply pack "${pack.name}": ${message}`);
|
|
4594
|
+
}
|
|
4595
|
+
process.exit(1);
|
|
4596
|
+
}
|
|
4597
|
+
}
|
|
4598
|
+
|
|
4091
4599
|
function usage() {
|
|
4092
4600
|
console.log(`
|
|
4093
4601
|
Parachute Vault — self-hosted knowledge graph
|
|
@@ -4153,6 +4661,14 @@ Vaults:
|
|
|
4153
4661
|
opt-in upgrade). --no-mirror creates a bare vault with no mirror config.
|
|
4154
4662
|
Operators can flip the server-wide default with 'default_mirror: off' in
|
|
4155
4663
|
config.yaml (recommended for cloud / disk-constrained boxes).
|
|
4664
|
+
parachute-vault add-pack <pack> [--vault <name>]
|
|
4665
|
+
Add a named seed pack (starter notes + tags) to a vault
|
|
4666
|
+
(default vault: "default"). Run with no arguments to list
|
|
4667
|
+
the available packs. New vaults seed the "welcome" +
|
|
4668
|
+
"getting-started" packs automatically; "surface-starter"
|
|
4669
|
+
is opt-in via this command. Idempotent: notes are only
|
|
4670
|
+
created when absent — a re-run never clobbers an edited
|
|
4671
|
+
note.
|
|
4156
4672
|
parachute-vault list List all vaults
|
|
4157
4673
|
parachute-vault remove <name> [--yes] Remove a vault
|
|
4158
4674
|
parachute-vault mcp-install [--mint|--token <t>]
|
|
@@ -4298,6 +4814,25 @@ Schema maintenance:
|
|
|
4298
4814
|
Writes through strict enforcement (migrate
|
|
4299
4815
|
bypass). See \`schema migrate-field --help\`.
|
|
4300
4816
|
|
|
4817
|
+
Transcription (scribe-fold):
|
|
4818
|
+
parachute-vault transcription install [--dry-run] [--model <id>] [--force] [--yes]
|
|
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.
|
|
4835
|
+
|
|
4301
4836
|
── Advanced / standalone ──────────────────────────────────────────────
|
|
4302
4837
|
|
|
4303
4838
|
Direct daemon controls. For normal use, prefer the Parachute Hub wrappers
|