@openparachute/vault 0.6.0-rc.1 → 0.6.0
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/.parachute/module.json +14 -3
- package/README.md +7 -7
- package/core/src/core.test.ts +279 -26
- package/core/src/expand-visibility.test.ts +102 -0
- package/core/src/expand.ts +31 -3
- package/core/src/indexed-fields.ts +1 -1
- package/core/src/link-count.test.ts +301 -0
- package/core/src/links.ts +97 -2
- package/core/src/mcp.ts +201 -33
- package/core/src/notes.ts +44 -8
- package/core/src/obsidian-alignment.test.ts +375 -0
- package/core/src/obsidian.ts +234 -14
- package/core/src/portable-md.test.ts +40 -0
- package/core/src/portable-md.ts +142 -16
- package/core/src/schema.ts +58 -11
- package/core/src/store.ts +69 -22
- package/core/src/tag-expand-axis.test.ts +301 -0
- package/core/src/tag-hierarchy.ts +80 -0
- package/core/src/tag-schemas.ts +61 -46
- package/core/src/triggers-store.test.ts +100 -0
- package/core/src/triggers-store.ts +165 -0
- package/core/src/types.ts +68 -4
- package/core/src/vault-projection.ts +20 -0
- package/core/src/wikilinks.ts +2 -2
- package/package.json +2 -3
- package/src/admin-spa.test.ts +100 -10
- package/src/admin-spa.ts +48 -3
- package/src/auth-hub-jwt.test.ts +8 -1
- package/src/auth-status.ts +2 -2
- package/src/auth.test.ts +39 -3
- package/src/auth.ts +31 -2
- package/src/auto-transcribe.test.ts +51 -0
- package/src/auto-transcribe.ts +24 -6
- package/src/autostart.test.ts +75 -0
- package/src/autostart.ts +84 -0
- package/src/cli.ts +434 -140
- package/src/config.test.ts +109 -0
- package/src/config.ts +157 -10
- package/src/export-watch.test.ts +23 -0
- package/src/export-watch.ts +14 -0
- package/src/git-preflight.test.ts +70 -0
- package/src/git-preflight.ts +68 -0
- package/src/hub-jwt.test.ts +75 -2
- package/src/hub-jwt.ts +43 -6
- package/src/init-summary.test.ts +120 -5
- package/src/init-summary.ts +67 -25
- package/src/live-match.test.ts +198 -0
- package/src/live-match.ts +310 -0
- package/src/mcp-install.test.ts +93 -0
- package/src/mcp-install.ts +106 -0
- package/src/mcp-tools.ts +80 -7
- package/src/mirror-config.test.ts +14 -0
- package/src/mirror-config.ts +11 -0
- package/src/mirror-import.test.ts +110 -0
- package/src/mirror-import.ts +71 -13
- package/src/mirror-manager.test.ts +51 -0
- package/src/mirror-manager.ts +73 -11
- package/src/mirror-routes.test.ts +463 -1
- package/src/mirror-routes.ts +474 -4
- package/src/oauth-discovery.test.ts +55 -0
- package/src/oauth-discovery.ts +24 -5
- package/src/routes.ts +696 -121
- package/src/routing.test.ts +451 -5
- package/src/routing.ts +113 -5
- package/src/scopes.ts +1 -1
- package/src/server.ts +66 -4
- package/src/storage.test.ts +162 -0
- package/src/subscribe.test.ts +588 -0
- package/src/subscribe.ts +248 -0
- package/src/subscriptions.ts +295 -0
- package/src/tag-expand-routes.test.ts +45 -0
- package/src/tag-scope.ts +68 -1
- package/src/token-store.ts +7 -7
- package/src/transcription-worker.test.ts +471 -5
- package/src/transcription-worker.ts +212 -44
- package/src/triggers-api.test.ts +533 -0
- package/src/triggers-api.ts +295 -0
- package/src/triggers.ts +93 -7
- package/src/usage.test.ts +362 -0
- package/src/usage.ts +318 -0
- package/src/vault-create.test.ts +340 -12
- package/src/vault-name.test.ts +61 -3
- package/src/vault-name.ts +62 -14
- package/src/vault-remove.test.ts +187 -0
- package/src/vault-store.ts +10 -3
- package/src/vault.test.ts +1353 -62
- package/web/ui/dist/assets/index-CGL256oe.js +60 -0
- package/web/ui/dist/assets/index-J0pVP7I-.css +1 -0
- package/web/ui/dist/index.html +2 -2
- package/web/ui/dist/assets/index-DBe8Xiah.css +0 -1
- package/web/ui/dist/assets/index-DDRo6F4u.js +0 -60
package/src/cli.ts
CHANGED
|
@@ -56,8 +56,11 @@ import {
|
|
|
56
56
|
buildMcpConfigJson,
|
|
57
57
|
buildMcpEntryPlan,
|
|
58
58
|
chooseHubOrigin,
|
|
59
|
+
chooseMcpUrl,
|
|
60
|
+
detectHubPresence,
|
|
59
61
|
detectInstallContext,
|
|
60
62
|
mintHubJwt,
|
|
63
|
+
noOperatorTokenGuidance,
|
|
61
64
|
readOperatorToken,
|
|
62
65
|
removeMcpConfig,
|
|
63
66
|
resolveInstallTarget,
|
|
@@ -101,7 +104,15 @@ import { resolveBindHostname } from "./bind.ts";
|
|
|
101
104
|
import { listTokens, revokeToken, migrateVaultKeys } from "./token-store.ts";
|
|
102
105
|
import { VAULT_SCOPES } from "./scopes.ts";
|
|
103
106
|
import { validateVaultName, decideInitVaultName } from "./vault-name.ts";
|
|
107
|
+
import { decideAutostart } from "./autostart.ts";
|
|
104
108
|
import { getVaultStore } from "./vault-store.ts";
|
|
109
|
+
import {
|
|
110
|
+
defaultMirrorConfig,
|
|
111
|
+
resolveMirrorPath,
|
|
112
|
+
writeMirrorConfigForVault,
|
|
113
|
+
type MirrorConfig,
|
|
114
|
+
} from "./mirror-config.ts";
|
|
115
|
+
import { bootstrapInternalMirror } from "./mirror-manager.ts";
|
|
105
116
|
import { selfRegister } from "./self-register.ts";
|
|
106
117
|
import {
|
|
107
118
|
hasOwnerPassword,
|
|
@@ -265,12 +276,16 @@ async function cmdInit(args: string[] = []) {
|
|
|
265
276
|
const flagMcpOff = args.includes("--no-mcp");
|
|
266
277
|
const flagTokenOn = args.includes("--token");
|
|
267
278
|
const flagTokenOff = args.includes("--no-token");
|
|
268
|
-
// --autostart / --no-autostart toggle daemon registration.
|
|
269
|
-
// (
|
|
270
|
-
//
|
|
271
|
-
//
|
|
272
|
-
//
|
|
273
|
-
//
|
|
279
|
+
// --autostart / --no-autostart toggle daemon registration. The default is
|
|
280
|
+
// context-aware (resolved by decideAutostart below): ON for standalone
|
|
281
|
+
// deploys, but OFF when a hub supervisor is detected, since the hub owns
|
|
282
|
+
// vault's lifecycle and a launchd/systemd unit would race it for :1940
|
|
283
|
+
// (ParachuteComputer/parachute-hub#580). --no-autostart always skips
|
|
284
|
+
// registering AND removes any prior registration — for CI, dev sandboxes,
|
|
285
|
+
// Docker, or any environment where another supervisor manages the process.
|
|
286
|
+
// --autostart forces registration even under a hub (logged with a warning).
|
|
287
|
+
// --no-autostart wins over --autostart on the same command line
|
|
288
|
+
// (safer-default precedence).
|
|
274
289
|
const flagAutostartOn = args.includes("--autostart");
|
|
275
290
|
const flagAutostartOff = args.includes("--no-autostart");
|
|
276
291
|
|
|
@@ -407,37 +422,73 @@ async function cmdInit(args: string[] = []) {
|
|
|
407
422
|
// a folder move; this refreshes ~/.parachute/server-path and bounces the
|
|
408
423
|
// daemon so the new location takes effect immediately.
|
|
409
424
|
//
|
|
410
|
-
// Autostart precedence
|
|
411
|
-
//
|
|
412
|
-
//
|
|
413
|
-
//
|
|
414
|
-
// 3. Existing config.autostart
|
|
415
|
-
// 4.
|
|
425
|
+
// Autostart precedence is resolved by `decideAutostart` (pure, unit-tested):
|
|
426
|
+
// 1. --no-autostart on this run → false (and persisted)
|
|
427
|
+
// 2. --autostart on this run → true (and persisted; warns
|
|
428
|
+
// if a supervised hub was seen)
|
|
429
|
+
// 3. Existing config.autostart → that value
|
|
430
|
+
// 4. Hub present, no flag, no persisted val → false (the hub supervisor
|
|
431
|
+
// owns the lifecycle — #580)
|
|
432
|
+
// 5. Default → true (standalone deploys
|
|
433
|
+
// genuinely need a daemon)
|
|
416
434
|
// When false: skip register AND uninstall any prior registration so the
|
|
417
|
-
//
|
|
435
|
+
// decision's intent ("don't auto-start / don't auto-restart") matches reality
|
|
418
436
|
// even if a previous run had registered a daemon.
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
437
|
+
//
|
|
438
|
+
// The hub probe runs only when neither flag was passed AND no value is
|
|
439
|
+
// persisted — i.e. only when the hub signal can actually change the outcome.
|
|
440
|
+
// It targets the hub's fixed loopback port (1939 / $PARACHUTE_HUB_PORT) and
|
|
441
|
+
// never throws (see detectHubPresence). We skip it when a flag/persisted
|
|
442
|
+
// value already decides, to avoid an 800ms wait on a flagged run.
|
|
443
|
+
//
|
|
444
|
+
// False-positive risk: a stale expose-state / leftover PARACHUTE_HUB_ORIGIN
|
|
445
|
+
// makes detectHubPresence return true on a genuinely hubless box, so init
|
|
446
|
+
// silently skips registering a daemon. Narrow + accepted — recover with
|
|
447
|
+
// `parachute-vault init --autostart`. The pre-decided guard below means any
|
|
448
|
+
// explicit flag or persisted value never even reaches the probe.
|
|
449
|
+
const autostartPreDecided =
|
|
450
|
+
flagAutostartOff || flagAutostartOn || typeof globalConfig.autostart === "boolean";
|
|
451
|
+
const hubPresentForAutostart = autostartPreDecided ? false : await detectHubPresence();
|
|
452
|
+
const autostartDecision = decideAutostart({
|
|
453
|
+
flagOn: flagAutostartOn,
|
|
454
|
+
flagOff: flagAutostartOff,
|
|
455
|
+
persisted: globalConfig.autostart,
|
|
456
|
+
hubPresent: hubPresentForAutostart,
|
|
457
|
+
});
|
|
458
|
+
const autostartEnabled = autostartDecision.enabled;
|
|
424
459
|
|
|
425
|
-
if (
|
|
460
|
+
if (autostartDecision.persist) {
|
|
426
461
|
globalConfig.autostart = autostartEnabled;
|
|
427
462
|
writeGlobalConfig(globalConfig);
|
|
428
463
|
}
|
|
429
464
|
|
|
430
465
|
let serverPath: string | null = null;
|
|
431
466
|
if (!autostartEnabled) {
|
|
432
|
-
|
|
467
|
+
if (autostartDecision.reason === "hub-default-off") {
|
|
468
|
+
console.log(
|
|
469
|
+
"Hub supervisor detected — not registering a separate daemon. The hub manages vault's lifecycle.",
|
|
470
|
+
);
|
|
471
|
+
console.log(" To force a standalone daemon anyway: parachute-vault init --autostart");
|
|
472
|
+
} else {
|
|
473
|
+
console.log("Autostart disabled — skipping daemon registration.");
|
|
474
|
+
}
|
|
433
475
|
if (isMac) {
|
|
434
476
|
await uninstallAgent();
|
|
435
477
|
} else if (isLinux && isSystemdAvailable()) {
|
|
436
478
|
await uninstallSystemdService();
|
|
437
479
|
}
|
|
438
480
|
console.log(" To run vault: parachute-vault serve (or use your own supervisor)");
|
|
439
|
-
|
|
481
|
+
if (autostartDecision.reason !== "hub-default-off") {
|
|
482
|
+
console.log(" To re-enable: parachute-vault init --autostart");
|
|
483
|
+
}
|
|
440
484
|
} else {
|
|
485
|
+
if (autostartDecision.overrodeHub) {
|
|
486
|
+
console.log(
|
|
487
|
+
"Warning: a supervised hub was detected, but --autostart was passed — registering a "
|
|
488
|
+
+ "standalone daemon anyway. This can race the hub supervisor for the vault port; "
|
|
489
|
+
+ "prefer letting the hub manage vault unless you know you need both.",
|
|
490
|
+
);
|
|
491
|
+
}
|
|
441
492
|
console.log("Installing daemon...");
|
|
442
493
|
if (isMac) {
|
|
443
494
|
({ serverPath } = await installAgent());
|
|
@@ -471,11 +522,16 @@ async function cmdInit(args: string[] = []) {
|
|
|
471
522
|
addMcp = true; // non-interactive: preserve the installable-via-pipe default
|
|
472
523
|
}
|
|
473
524
|
|
|
474
|
-
// 7b.
|
|
475
|
-
// Cursor, Zed, Cline, scripts, curl.)
|
|
476
|
-
//
|
|
477
|
-
//
|
|
478
|
-
//
|
|
525
|
+
// 7b. Mint an API token for the header-auth / script use case? (Codex,
|
|
526
|
+
// Goose, OpenCode, Cursor, Zed, Cline, scripts, curl.)
|
|
527
|
+
//
|
|
528
|
+
// vault#442: default vault auth is per-user OAuth — the Claude Code MCP entry
|
|
529
|
+
// is written WITHOUT a baked bearer, so the first connection does browser
|
|
530
|
+
// sign-in. We mint a token ONLY when the operator explicitly opts in
|
|
531
|
+
// (`--token`, or "yes" at the prompt), and then it's scope-narrow
|
|
532
|
+
// (`vault:<name>:read`), NEVER admin. `--no-token` (and the non-interactive
|
|
533
|
+
// default) skips minting entirely — no auto-mint, no noisy mint-failure on a
|
|
534
|
+
// fresh vault.
|
|
479
535
|
let addToken: boolean;
|
|
480
536
|
if (flagTokenOff) {
|
|
481
537
|
addToken = false;
|
|
@@ -483,25 +539,22 @@ async function cmdInit(args: string[] = []) {
|
|
|
483
539
|
addToken = true;
|
|
484
540
|
} else if (process.stdin.isTTY) {
|
|
485
541
|
addToken = await confirm(
|
|
486
|
-
"
|
|
487
|
-
|
|
542
|
+
"Also mint a header-auth API token for non-OAuth clients / scripts (Codex, Goose, curl)? (OAuth works without one)",
|
|
543
|
+
false,
|
|
488
544
|
);
|
|
489
545
|
} else {
|
|
490
|
-
addToken =
|
|
546
|
+
addToken = false; // non-interactive default: OAuth-first, no auto-mint
|
|
491
547
|
}
|
|
492
548
|
|
|
493
|
-
// Mint a token
|
|
494
|
-
//
|
|
495
|
-
//
|
|
496
|
-
//
|
|
497
|
-
//
|
|
498
|
-
//
|
|
499
|
-
// summary — the operator runs `mcp-install` once a hub is up, or sets
|
|
500
|
-
// VAULT_AUTH_TOKEN.
|
|
549
|
+
// Mint a scope-narrow token ONLY when explicitly opted in and we don't
|
|
550
|
+
// already have one from vault creation. vault#282 Stage 2: vault no longer
|
|
551
|
+
// mints pvt_* tokens — this is a hub JWT via the operator.token → hub
|
|
552
|
+
// mint-token path (`mintBootstrapCredential`), scoped `vault:<name>:read`.
|
|
553
|
+
// When no hub is reachable, `apiKey` stays undefined and we carry the
|
|
554
|
+
// guidance to the summary. The default (OAuth) path never reaches here.
|
|
501
555
|
const defaultVault = globalConfig.default_vault || "default";
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
const credential = await mintBootstrapCredential(defaultVault);
|
|
556
|
+
if (addToken && !apiKey) {
|
|
557
|
+
const credential = await mintBootstrapCredential(defaultVault, "read");
|
|
505
558
|
apiKey = credential.token ?? undefined;
|
|
506
559
|
credentialGuidance = credential.guidance;
|
|
507
560
|
if (!apiKey) console.log(` ${credential.guidance}`);
|
|
@@ -510,10 +563,9 @@ async function cmdInit(args: string[] = []) {
|
|
|
510
563
|
if (addMcp) {
|
|
511
564
|
// Goes through `buildMcpEntryPlan` for entryKey + url so this path shares
|
|
512
565
|
// the writer-side invariant with `executeMcpInstall` — a future URL-shape
|
|
513
|
-
// change can't drift between init and mcp-install.
|
|
514
|
-
//
|
|
515
|
-
//
|
|
516
|
-
// hub is up).
|
|
566
|
+
// change can't drift between init and mcp-install. By default NO bearer is
|
|
567
|
+
// baked (vault#442 — OAuth on first connect); a bearer is embedded only
|
|
568
|
+
// when the operator explicitly opted into a scope-narrow token above.
|
|
517
569
|
const target = resolveInstallTarget("user");
|
|
518
570
|
const { entryKey, url, source } = buildMcpEntryPlan({
|
|
519
571
|
vaultName: defaultVault,
|
|
@@ -528,6 +580,9 @@ async function cmdInit(args: string[] = []) {
|
|
|
528
580
|
});
|
|
529
581
|
console.log(`MCP URL: ${url} (${source})`);
|
|
530
582
|
console.log(` MCP server added to ~/.claude.json`);
|
|
583
|
+
if (!apiKey) {
|
|
584
|
+
console.log(` No token baked in — you'll sign in via OAuth on first connect.`);
|
|
585
|
+
}
|
|
531
586
|
} else {
|
|
532
587
|
console.log(" Skipped adding MCP to ~/.claude.json.");
|
|
533
588
|
console.log(" Run `parachute-vault mcp-install` later if you want it.");
|
|
@@ -536,6 +591,11 @@ async function cmdInit(args: string[] = []) {
|
|
|
536
591
|
// 8. Summary
|
|
537
592
|
const port = globalConfig.port || DEFAULT_PORT;
|
|
538
593
|
const mcpUrl = `http://127.0.0.1:${port}/vault/${defaultVault}/mcp`;
|
|
594
|
+
// Probe whether a hub is present so the summary's "opted into a token but
|
|
595
|
+
// none minted" copy reflects reality: under a hub the vault is reachable via
|
|
596
|
+
// browser OAuth even with no header-auth token (#445). Only matters for the
|
|
597
|
+
// !apiKey branches; cheap + best-effort (never throws).
|
|
598
|
+
const hubPresent = !apiKey ? await detectHubPresence() : true;
|
|
539
599
|
const lines = buildInitSummaryLines({
|
|
540
600
|
addMcp,
|
|
541
601
|
addToken,
|
|
@@ -544,7 +604,9 @@ async function cmdInit(args: string[] = []) {
|
|
|
544
604
|
bindHost,
|
|
545
605
|
port,
|
|
546
606
|
mcpUrl,
|
|
607
|
+
vaultName: defaultVault,
|
|
547
608
|
noTokenGuidance: credentialGuidance,
|
|
609
|
+
hubPresent,
|
|
548
610
|
});
|
|
549
611
|
for (const line of lines) console.log(line);
|
|
550
612
|
}
|
|
@@ -814,27 +876,73 @@ async function cmdCreate(args: string[]) {
|
|
|
814
876
|
// POST /vaults shells out to this CLI and parses stdout). Errors still go
|
|
815
877
|
// to stderr as plain text and exit nonzero — callers branch on exit code.
|
|
816
878
|
const jsonMode = args.includes("--json");
|
|
817
|
-
//
|
|
818
|
-
//
|
|
819
|
-
//
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
879
|
+
// `--no-mirror` opts THIS create out of the default internal live mirror
|
|
880
|
+
// even when the server-wide `default_mirror` knob is `internal`. Parity for
|
|
881
|
+
// operators who want one bare vault without flipping the global default.
|
|
882
|
+
const noMirror = args.includes("--no-mirror");
|
|
883
|
+
|
|
884
|
+
// --- Auth opt-in (vault#442). Default = per-user OAuth, NO token minted. ---
|
|
885
|
+
// `--mint` opts into a scope-narrow hub JWT for the header-auth / script
|
|
886
|
+
// use case; `--scope read|write` (default read) picks the verb. `:admin` is
|
|
887
|
+
// intentionally NOT accepted from the create flow. `--token <bearer>` is the
|
|
888
|
+
// paste path — use an existing bearer instead of minting. The two are
|
|
889
|
+
// mutually exclusive.
|
|
890
|
+
const wantMint = args.includes("--mint");
|
|
891
|
+
const createTokenArg = takeArgValue(args, "--token");
|
|
892
|
+
if (createTokenArg.missingValue) {
|
|
893
|
+
console.error("--token requires a value (the bearer token to embed).");
|
|
894
|
+
process.exit(1);
|
|
895
|
+
}
|
|
896
|
+
const pastedToken = createTokenArg.value;
|
|
897
|
+
if (wantMint && pastedToken !== undefined) {
|
|
898
|
+
console.error("--mint and --token are mutually exclusive.");
|
|
899
|
+
process.exit(1);
|
|
900
|
+
}
|
|
901
|
+
const createScopeArg = takeArgValue(args, "--scope");
|
|
902
|
+
if (createScopeArg.missingValue) {
|
|
903
|
+
console.error("--scope requires a value: read or write.");
|
|
904
|
+
process.exit(1);
|
|
905
|
+
}
|
|
906
|
+
const rawCreateVerb = createScopeArg.value ?? "read";
|
|
907
|
+
if (rawCreateVerb !== "read" && rawCreateVerb !== "write") {
|
|
908
|
+
console.error(
|
|
909
|
+
`--scope must be "read" or "write" for create (admin is minted out-of-band via \`mcp-install --scope vault:admin\`). Got: ${rawCreateVerb}.`,
|
|
910
|
+
);
|
|
911
|
+
process.exit(1);
|
|
912
|
+
}
|
|
913
|
+
const mintVerb: "read" | "write" | undefined = wantMint ? rawCreateVerb : undefined;
|
|
914
|
+
|
|
915
|
+
// Greedy strip of any `--*` token (and any `--flag value` pairs we consumed
|
|
916
|
+
// above) to recover the positional vault name. `--json`, `--no-mirror`,
|
|
917
|
+
// `--mint`, `--token <v>`, `--scope <v>` are recognized; any other `--foo` is
|
|
918
|
+
// silently dropped, and the value following a recognized value-flag is
|
|
919
|
+
// skipped so it can't be mistaken for the vault name.
|
|
920
|
+
const VALUE_FLAGS = new Set(["--token", "--scope"]);
|
|
921
|
+
const positional: string[] = [];
|
|
922
|
+
for (let i = 0; i < args.length; i++) {
|
|
923
|
+
const a = args[i]!;
|
|
924
|
+
if (a.startsWith("--")) {
|
|
925
|
+
if (VALUE_FLAGS.has(a)) i++; // skip the flag's value
|
|
926
|
+
continue;
|
|
927
|
+
}
|
|
928
|
+
positional.push(a);
|
|
929
|
+
}
|
|
823
930
|
const name = positional[0];
|
|
824
931
|
if (!name) {
|
|
825
932
|
console.error("Usage: parachute-vault create <name> [--json]");
|
|
826
933
|
process.exit(1);
|
|
827
934
|
}
|
|
828
935
|
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
936
|
+
// One validator for every name-minting edge (2026-06-09 hub-module-boundary
|
|
937
|
+
// migration B2). cmdCreate used to carry its own inline charset check plus a
|
|
938
|
+
// hardcoded `"list"` reservation that had drifted from `validateVaultName`'s
|
|
939
|
+
// set — a vault named `admin`/`new`/`assets` could enter through `create`
|
|
940
|
+
// and capture a reserved route (`/vault/admin` is the daemon-level admin
|
|
941
|
+
// mount as of B3). Consuming the shared validator also picks up its 2–32
|
|
942
|
+
// length rule, aligning `create` with `init`, the env var, and hub's wizard.
|
|
943
|
+
const nameValidation = validateVaultName(name);
|
|
944
|
+
if (!nameValidation.ok) {
|
|
945
|
+
console.error(nameValidation.error);
|
|
838
946
|
process.exit(1);
|
|
839
947
|
}
|
|
840
948
|
|
|
@@ -846,7 +954,18 @@ async function cmdCreate(args: string[]) {
|
|
|
846
954
|
|
|
847
955
|
ensureConfigDirSync();
|
|
848
956
|
const wasFirst = listVaults().length === 0;
|
|
849
|
-
const credential = await createVault(name
|
|
957
|
+
const credential = await createVault(name, {
|
|
958
|
+
...(noMirror ? { enableMirror: false } : {}),
|
|
959
|
+
...(mintVerb ? { mintVerb } : {}),
|
|
960
|
+
});
|
|
961
|
+
|
|
962
|
+
// `--token <bearer>` paste path: the operator supplied their own bearer
|
|
963
|
+
// instead of minting one. Surface it as the credential token so the
|
|
964
|
+
// downstream JSON / human / summary copy treats it the same as a minted one.
|
|
965
|
+
const effectiveToken = pastedToken ?? credential.token;
|
|
966
|
+
const effectiveGuidance = pastedToken
|
|
967
|
+
? "Using the bearer you supplied via --token."
|
|
968
|
+
: credential.guidance;
|
|
850
969
|
|
|
851
970
|
// If this is the only vault now, make it the default so unscoped routes
|
|
852
971
|
// (/mcp, /api/*, /oauth/*) target it. Avoids the "single vault named
|
|
@@ -880,17 +999,19 @@ async function cmdCreate(args: string[]) {
|
|
|
880
999
|
log: () => {}, // CLI create has its own status lines.
|
|
881
1000
|
});
|
|
882
1001
|
|
|
1002
|
+
const endpoint = chooseMcpUrl(name, globalConfig.port || DEFAULT_PORT).url;
|
|
1003
|
+
|
|
883
1004
|
if (jsonMode) {
|
|
884
1005
|
// Contract (hub's admin-vaults.ts requires `typeof token === "string"`):
|
|
885
|
-
// emit
|
|
886
|
-
//
|
|
887
|
-
//
|
|
888
|
-
//
|
|
889
|
-
//
|
|
1006
|
+
// emit a token string when one was minted/pasted. vault#442 default is
|
|
1007
|
+
// per-user OAuth — no token is minted — so `token` is the empty string and
|
|
1008
|
+
// `token_guidance` carries the OAuth-first connect path. (Hub's admin SPA
|
|
1009
|
+
// already handles the empty-token case + has its own session-cookie admin
|
|
1010
|
+
// mint path, so it doesn't depend on create minting a token.)
|
|
890
1011
|
const payload = {
|
|
891
1012
|
name,
|
|
892
|
-
token:
|
|
893
|
-
token_guidance:
|
|
1013
|
+
token: effectiveToken ?? "",
|
|
1014
|
+
token_guidance: effectiveGuidance,
|
|
894
1015
|
paths: {
|
|
895
1016
|
vault_dir: vaultDir(name),
|
|
896
1017
|
vault_db: vaultDbPath(name),
|
|
@@ -904,18 +1025,20 @@ async function cmdCreate(args: string[]) {
|
|
|
904
1025
|
|
|
905
1026
|
console.log(`Vault "${name}" created.`);
|
|
906
1027
|
console.log(` Path: ${vaultDir(name)}`);
|
|
907
|
-
if (
|
|
908
|
-
console.log(` API token: ${
|
|
909
|
-
console.log(` ${
|
|
1028
|
+
if (effectiveToken) {
|
|
1029
|
+
console.log(` API token: ${effectiveToken}`);
|
|
1030
|
+
console.log(` ${effectiveGuidance}`);
|
|
910
1031
|
console.log(` Save this — it will not be shown again.`);
|
|
911
1032
|
} else {
|
|
912
|
-
console.log(` ${
|
|
1033
|
+
console.log(` ${effectiveGuidance}`);
|
|
913
1034
|
}
|
|
914
1035
|
if (defaultNote) {
|
|
915
1036
|
console.log(` ${defaultNote}`);
|
|
916
1037
|
}
|
|
917
1038
|
console.log();
|
|
918
|
-
console.log(`
|
|
1039
|
+
console.log(`Connect your AI: claude mcp add --transport http parachute-${name} ${endpoint}`);
|
|
1040
|
+
console.log(` (no token needed — you'll sign in on first use)`);
|
|
1041
|
+
console.log(`Need a header-auth token for a script? parachute auth mint-token --scope vault:${name}:read`);
|
|
919
1042
|
}
|
|
920
1043
|
|
|
921
1044
|
function cmdList() {
|
|
@@ -1447,20 +1570,52 @@ function cmdRemove(args: string[]) {
|
|
|
1447
1570
|
// Keep default_vault in sync. If the removed vault was the default, either
|
|
1448
1571
|
// promote the remaining vault (if exactly one) or clear the setting.
|
|
1449
1572
|
const globalConfig = readGlobalConfig();
|
|
1573
|
+
const remaining = listVaults();
|
|
1574
|
+
let configDirty = false;
|
|
1450
1575
|
if (globalConfig.default_vault === name) {
|
|
1451
|
-
const remaining = listVaults();
|
|
1452
1576
|
if (remaining.length === 1) {
|
|
1453
1577
|
globalConfig.default_vault = remaining[0];
|
|
1454
|
-
writeGlobalConfig(globalConfig);
|
|
1455
1578
|
console.log(` Default vault is now "${remaining[0]}".`);
|
|
1456
1579
|
} else {
|
|
1457
1580
|
delete globalConfig.default_vault;
|
|
1458
|
-
writeGlobalConfig(globalConfig);
|
|
1459
1581
|
if (remaining.length > 1) {
|
|
1460
1582
|
console.log(` Cleared default_vault — set one with: editor ${CONFIG_DIR}/config.yaml`);
|
|
1461
1583
|
}
|
|
1462
1584
|
}
|
|
1585
|
+
configDirty = true;
|
|
1463
1586
|
}
|
|
1587
|
+
|
|
1588
|
+
// Last-vault marker (2026-06-09 hub-module-boundary migration, B1's
|
|
1589
|
+
// CLI-side improvement). Server boot auto-creates `default` when zero
|
|
1590
|
+
// vaults exist — without the marker, an operator who explicitly emptied
|
|
1591
|
+
// the server would find a freshly-credentialed `default` resurrected on
|
|
1592
|
+
// the next restart. Fresh installs never carry the marker (no config.yaml
|
|
1593
|
+
// at all), so Docker / hub-install first-run auto-create is preserved.
|
|
1594
|
+
if (remaining.length === 0 && globalConfig.auto_create !== false) {
|
|
1595
|
+
globalConfig.auto_create = false;
|
|
1596
|
+
configDirty = true;
|
|
1597
|
+
console.log(
|
|
1598
|
+
` Last vault removed — wrote auto_create: false to ${GLOBAL_CONFIG_PATH} so the` +
|
|
1599
|
+
` server won't auto-recreate "default" on next boot. Create a vault with:` +
|
|
1600
|
+
` parachute-vault create <name>`,
|
|
1601
|
+
);
|
|
1602
|
+
}
|
|
1603
|
+
if (configDirty) writeGlobalConfig(globalConfig);
|
|
1604
|
+
|
|
1605
|
+
// Refresh services.json so the removed vault's /vault/<name> path drops
|
|
1606
|
+
// out of the parachute-vault row immediately — the same selfRegister
|
|
1607
|
+
// refresh cmdCreate does (#208). Without this, the hub's well-known
|
|
1608
|
+
// fan-out kept advertising the deleted vault until the next server boot.
|
|
1609
|
+
// Note: with zero vaults remaining, selfRegister falls back to the
|
|
1610
|
+
// manifest's canonical paths (`/vault/default`) — the same row a
|
|
1611
|
+
// subsequent boot would write — so CLI-remove and boot agree on the
|
|
1612
|
+
// zero-vault registration shape. Warnings go to stderr; status lines stay
|
|
1613
|
+
// ours.
|
|
1614
|
+
selfRegister({
|
|
1615
|
+
version: pkg.version,
|
|
1616
|
+
warn: (msg) => console.error(`Warning: ${msg}`),
|
|
1617
|
+
log: () => {},
|
|
1618
|
+
});
|
|
1464
1619
|
}
|
|
1465
1620
|
|
|
1466
1621
|
async function cmdConfig(args: string[]) {
|
|
@@ -1586,15 +1741,15 @@ function cmdTokens(args: string[]) {
|
|
|
1586
1741
|
return;
|
|
1587
1742
|
}
|
|
1588
1743
|
|
|
1589
|
-
// `tokens create` was removed at 0.
|
|
1744
|
+
// `tokens create` was removed at 0.5.0 (vault#282 Stage 2). Vault no longer
|
|
1590
1745
|
// mints its own (pvt_*) tokens — it's a pure hub resource-server. Tokens are
|
|
1591
1746
|
// now hub-issued JWTs: run `parachute-vault mcp-install` to mint + wire one
|
|
1592
1747
|
// for an MCP client, or `parachute auth mint-token --scope vault:<name>:<verb>`
|
|
1593
1748
|
// for scripts. `tokens list` / `tokens revoke` remain for cleaning up any
|
|
1594
|
-
// vestigial pre-0.
|
|
1749
|
+
// vestigial pre-0.5.0 rows.
|
|
1595
1750
|
if (subcmd === "create") {
|
|
1596
1751
|
console.error(
|
|
1597
|
-
"`parachute-vault tokens create` was removed at 0.
|
|
1752
|
+
"`parachute-vault tokens create` was removed at 0.5.0 — vault no longer mints its own tokens.\n" +
|
|
1598
1753
|
" Mint a hub-issued JWT instead:\n" +
|
|
1599
1754
|
" parachute-vault mcp-install --scope vault:<verb> # wire an MCP client\n" +
|
|
1600
1755
|
" parachute auth mint-token --scope vault:<name>:<verb> # for scripts\n" +
|
|
@@ -2760,34 +2915,17 @@ async function cmdImport(args: string[]) {
|
|
|
2760
2915
|
return;
|
|
2761
2916
|
}
|
|
2762
2917
|
|
|
2763
|
-
// Import into vault — use
|
|
2764
|
-
//
|
|
2918
|
+
// Import into vault — use the shared `importObsidianNotes` adapter
|
|
2919
|
+
// (obsidian.ts). It uses createNoteRaw to skip per-note wikilink sync,
|
|
2920
|
+
// id-aware upsert with a path-conflict guard, intra-batch collision
|
|
2921
|
+
// dedup, per-note error isolation, and timestamp preservation. The
|
|
2922
|
+
// single wikilink pass runs below, after all notes exist.
|
|
2923
|
+
const { importObsidianNotes } = await import("../core/src/obsidian.ts");
|
|
2765
2924
|
const store = getVaultStore(vaultName);
|
|
2766
|
-
|
|
2767
|
-
let skipped = 0;
|
|
2768
|
-
|
|
2769
|
-
for (const note of notes) {
|
|
2770
|
-
// Skip if a note with this path already exists
|
|
2771
|
-
const existing = await store.getNoteByPath(note.path);
|
|
2772
|
-
if (existing) {
|
|
2773
|
-
skipped++;
|
|
2774
|
-
continue;
|
|
2775
|
-
}
|
|
2925
|
+
const { imported, skipped } = await importObsidianNotes(store, notes);
|
|
2776
2926
|
|
|
2777
|
-
// Build metadata from frontmatter (excluding tags, already extracted)
|
|
2778
|
-
const metadata = Object.keys(note.frontmatter).length > 0 ? note.frontmatter : undefined;
|
|
2779
|
-
|
|
2780
|
-
await store.createNoteRaw(note.content, {
|
|
2781
|
-
path: note.path,
|
|
2782
|
-
tags: note.tags.length > 0 ? note.tags : undefined,
|
|
2783
|
-
metadata: metadata as Record<string, unknown>,
|
|
2784
|
-
});
|
|
2785
|
-
imported++;
|
|
2786
|
-
}
|
|
2787
|
-
|
|
2788
|
-
// Single-pass wikilink sync after all notes exist
|
|
2789
2927
|
console.log(`\nImported ${imported} notes into vault "${vaultName}"`);
|
|
2790
|
-
if (skipped > 0) console.log(`Skipped ${skipped} notes (path already exists)`);
|
|
2928
|
+
if (skipped > 0) console.log(`Skipped ${skipped} notes (path already exists or conflict)`);
|
|
2791
2929
|
|
|
2792
2930
|
if (imported > 0) {
|
|
2793
2931
|
const linkResult = await store.syncAllWikilinks();
|
|
@@ -3274,38 +3412,58 @@ async function firstChangedNoteTitle(
|
|
|
3274
3412
|
/**
|
|
3275
3413
|
* Outcome of bootstrapping a fresh vault's first credential (vault#282 Stage 2).
|
|
3276
3414
|
*
|
|
3277
|
-
* Vault no longer mints `pvt_*` tokens.
|
|
3278
|
-
* a hub-issued JWT
|
|
3279
|
-
*
|
|
3280
|
-
*
|
|
3281
|
-
*
|
|
3415
|
+
* Vault no longer mints `pvt_*` tokens. When a token IS minted (explicit opt-in
|
|
3416
|
+
* only — vault#442), it's a hub-issued JWT scoped narrow (`vault:<name>:read`
|
|
3417
|
+
* or `:write`, NEVER `:admin`), minted via the same operator.token → hub
|
|
3418
|
+
* mint-token path `mcp-install --mint` uses (cli.ts ~`cmdMcpInstall`). When no
|
|
3419
|
+
* hub is reachable (standalone install, no operator.token, or no real hub
|
|
3420
|
+
* origin), `token` is null and `guidance` carries the operator's next step.
|
|
3282
3421
|
*/
|
|
3283
3422
|
interface VaultCredential {
|
|
3284
|
-
/** Hub-issued JWT scoped
|
|
3423
|
+
/** Hub-issued JWT scoped narrow (read/write), or null when not minted / no hub reachable. */
|
|
3285
3424
|
token: string | null;
|
|
3286
3425
|
/** Human-readable note: how the token was issued, or why it wasn't. */
|
|
3287
3426
|
guidance: string;
|
|
3288
3427
|
}
|
|
3289
3428
|
|
|
3290
3429
|
/**
|
|
3291
|
-
* Mint
|
|
3430
|
+
* Mint a scope-narrow credential for a vault (explicit opt-in — vault#442).
|
|
3431
|
+
*
|
|
3432
|
+
* Default vault auth is per-user OAuth (browser sign-in on first MCP connect);
|
|
3433
|
+
* tokens are only for the header-auth / script use case and are minted ONLY
|
|
3434
|
+
* when explicitly requested. Decision (vault#442): the create/init flow NEVER
|
|
3435
|
+
* auto-mints, and when a token IS requested it's scope-narrow — `verb` is
|
|
3436
|
+
* `read` (default) or `write`, NEVER `admin`. (Admin tokens, when truly
|
|
3437
|
+
* needed, are minted out-of-band via `mcp-install --scope vault:admin` against
|
|
3438
|
+
* a hub running hub#449, or the hub admin SPA's own session-cookie path.)
|
|
3292
3439
|
*
|
|
3293
|
-
*
|
|
3294
|
-
*
|
|
3295
|
-
* return it as the bootstrap credential — preserving the `create --json`
|
|
3296
|
-
* `token` string contract hub's admin-vaults.ts requires. When no hub is
|
|
3440
|
+
* When a hub is reachable (operator.token present AND a real hub origin
|
|
3441
|
+
* resolves), mint a `vault:<name>:<verb>` hub JWT and return it. When no hub is
|
|
3297
3442
|
* reachable, return `token: null` plus explicit standalone guidance. There is
|
|
3298
3443
|
* no local pvt_* fallback anymore.
|
|
3299
3444
|
*/
|
|
3300
|
-
async function mintBootstrapCredential(
|
|
3445
|
+
async function mintBootstrapCredential(
|
|
3446
|
+
name: string,
|
|
3447
|
+
verb: "read" | "write" = "read",
|
|
3448
|
+
/**
|
|
3449
|
+
* Test seam — injectable hub-presence probe. Defaults to the live
|
|
3450
|
+
* `detectHubPresence` (loopback `/health` + configured-origin check). Lets
|
|
3451
|
+
* tests drive both branches of the no-operator-token copy without a real hub.
|
|
3452
|
+
*/
|
|
3453
|
+
detectHub: typeof detectHubPresence = detectHubPresence,
|
|
3454
|
+
): Promise<VaultCredential> {
|
|
3301
3455
|
const operatorToken = readOperatorToken();
|
|
3302
3456
|
if (!operatorToken) {
|
|
3457
|
+
// No operator.token. Two very different worlds, identical symptom:
|
|
3458
|
+
// (a) Hub running on a fresh box — the token isn't minted until the
|
|
3459
|
+
// admin wizard creates the first admin user (hub init Step 1.5 is a
|
|
3460
|
+
// no-op until then). NOTHING to do here; the old "install the hub …"
|
|
3461
|
+
// copy is circular (this very flow was spawned *by* the hub). #445.
|
|
3462
|
+
// (b) Genuinely standalone — no hub at all. The original guidance holds.
|
|
3463
|
+
const hubPresent = await detectHub();
|
|
3303
3464
|
return {
|
|
3304
3465
|
token: null,
|
|
3305
|
-
guidance:
|
|
3306
|
-
"No token issued — no hub operator token at ~/.parachute/operator.token. " +
|
|
3307
|
-
"Install the hub (`bun add -g @openparachute/hub` + `parachute init`) and re-run, " +
|
|
3308
|
-
"or set VAULT_AUTH_TOKEN for an operator-channel bearer.",
|
|
3466
|
+
guidance: noOperatorTokenGuidance(hubPresent),
|
|
3309
3467
|
};
|
|
3310
3468
|
}
|
|
3311
3469
|
const port = readGlobalConfig().port || DEFAULT_PORT;
|
|
@@ -3322,7 +3480,7 @@ async function mintBootstrapCredential(name: string): Promise<VaultCredential> {
|
|
|
3322
3480
|
const result = await mintHubJwt({
|
|
3323
3481
|
hubOrigin: hub.url,
|
|
3324
3482
|
operatorToken,
|
|
3325
|
-
scope: `vault:${name}
|
|
3483
|
+
scope: `vault:${name}:${verb}`,
|
|
3326
3484
|
subject: "parachute-vault-bootstrap",
|
|
3327
3485
|
});
|
|
3328
3486
|
if ("kind" in result) {
|
|
@@ -3333,7 +3491,7 @@ async function mintBootstrapCredential(name: string): Promise<VaultCredential> {
|
|
|
3333
3491
|
return {
|
|
3334
3492
|
token: null,
|
|
3335
3493
|
guidance:
|
|
3336
|
-
`No token issued — ${detail}. Verify the hub is running
|
|
3494
|
+
`No token issued — ${detail}. Verify the hub is running, ` +
|
|
3337
3495
|
"then run `parachute-vault mcp-install`, or set VAULT_AUTH_TOKEN.",
|
|
3338
3496
|
};
|
|
3339
3497
|
}
|
|
@@ -3344,13 +3502,76 @@ async function mintBootstrapCredential(name: string): Promise<VaultCredential> {
|
|
|
3344
3502
|
}
|
|
3345
3503
|
|
|
3346
3504
|
/**
|
|
3347
|
-
* Create a vault's config + DB
|
|
3505
|
+
* Create a vault's config + DB.
|
|
3348
3506
|
*
|
|
3349
|
-
*
|
|
3350
|
-
*
|
|
3351
|
-
* `
|
|
3507
|
+
* Default vault auth is per-user OAuth (vault#442) — create does NOT mint or
|
|
3508
|
+
* bake in any token. Returns a `VaultCredential` whose `token` is null and
|
|
3509
|
+
* whose `guidance` points at the OAuth-first connect path. A scope-narrow
|
|
3510
|
+
* token is minted only when the caller passes `mintVerb` (the explicit
|
|
3511
|
+
* header-auth / script opt-in: `read` default or `write`, NEVER `admin`). The
|
|
3512
|
+
* DB is created lazily via `getVaultStore` so migrations + schema run; we never
|
|
3513
|
+
* write any pvt_* row.
|
|
3514
|
+
*/
|
|
3515
|
+
interface CreateVaultOptions {
|
|
3516
|
+
/**
|
|
3517
|
+
* Opt-in token mint (vault#442). Unset → no token is minted; the vault uses
|
|
3518
|
+
* per-user OAuth on first MCP connect. Set to `read`/`write` → mint a
|
|
3519
|
+
* scope-narrow `vault:<name>:<verb>` hub JWT for the header-auth / script
|
|
3520
|
+
* use case. `admin` is intentionally NOT accepted here.
|
|
3521
|
+
*/
|
|
3522
|
+
mintVerb?: "read" | "write";
|
|
3523
|
+
/**
|
|
3524
|
+
* Override the server-wide `default_mirror` knob for this one create.
|
|
3525
|
+
* `--no-mirror` on `parachute-vault create` sets this to `false` so the
|
|
3526
|
+
* vault is created with no mirror config even when the knob is `internal`.
|
|
3527
|
+
* Unset → fall back to the `default_mirror` global config knob (default
|
|
3528
|
+
* `internal`).
|
|
3529
|
+
*/
|
|
3530
|
+
enableMirror?: boolean;
|
|
3531
|
+
/**
|
|
3532
|
+
* Test seam threaded straight into `bootstrapInternalMirror` (default
|
|
3533
|
+
* `Bun.which`). Inject a fn returning `null` to exercise the
|
|
3534
|
+
* git-not-installed best-effort path without uninstalling git from the
|
|
3535
|
+
* test host.
|
|
3536
|
+
*/
|
|
3537
|
+
which?: (cmd: string) => string | null;
|
|
3538
|
+
}
|
|
3539
|
+
|
|
3540
|
+
/**
|
|
3541
|
+
* The History / "Live Mirror" preset, written at create time when the
|
|
3542
|
+
* `default_mirror` knob resolves to `internal`. Matches the History preset
|
|
3543
|
+
* the admin SPA's VaultMirror page applies:
|
|
3544
|
+
* `{enabled:true, location:internal, sync_mode:events, auto_commit:true,
|
|
3545
|
+
* auto_push:false}`.
|
|
3546
|
+
* Built on top of `defaultMirrorConfig()` so the non-preset fields
|
|
3547
|
+
* (commit_template, safety_net_seconds) stay canonical.
|
|
3548
|
+
*/
|
|
3549
|
+
function historyPresetMirrorConfig(): MirrorConfig {
|
|
3550
|
+
return {
|
|
3551
|
+
...defaultMirrorConfig(),
|
|
3552
|
+
enabled: true,
|
|
3553
|
+
location: "internal",
|
|
3554
|
+
sync_mode: "events",
|
|
3555
|
+
auto_commit: true,
|
|
3556
|
+
auto_push: false,
|
|
3557
|
+
};
|
|
3558
|
+
}
|
|
3559
|
+
|
|
3560
|
+
/**
|
|
3561
|
+
* Resolve whether a freshly created vault should get the internal mirror.
|
|
3562
|
+
* Precedence: explicit per-create override (`--no-mirror`) → server-wide
|
|
3563
|
+
* `default_mirror` knob (default `internal`).
|
|
3352
3564
|
*/
|
|
3353
|
-
|
|
3565
|
+
function shouldEnableCreateTimeMirror(opts: CreateVaultOptions): boolean {
|
|
3566
|
+
if (opts.enableMirror !== undefined) return opts.enableMirror;
|
|
3567
|
+
// Default to "internal" when the knob is unset — backup-on-by-default.
|
|
3568
|
+
return (readGlobalConfig().default_mirror ?? "internal") === "internal";
|
|
3569
|
+
}
|
|
3570
|
+
|
|
3571
|
+
async function createVault(
|
|
3572
|
+
name: string,
|
|
3573
|
+
opts: CreateVaultOptions = {},
|
|
3574
|
+
): Promise<VaultCredential> {
|
|
3354
3575
|
const config: VaultConfig = {
|
|
3355
3576
|
name,
|
|
3356
3577
|
api_keys: [],
|
|
@@ -3359,9 +3580,62 @@ async function createVault(name: string): Promise<VaultCredential> {
|
|
|
3359
3580
|
writeVaultConfig(config);
|
|
3360
3581
|
|
|
3361
3582
|
// Touch the store so the vault's SQLite DB + schema are created. No token
|
|
3362
|
-
// row is written — vault is a pure hub resource-server post-0.
|
|
3583
|
+
// row is written — vault is a pure hub resource-server post-0.5.0.
|
|
3363
3584
|
getVaultStore(name);
|
|
3364
|
-
|
|
3585
|
+
|
|
3586
|
+
// Default new vaults to an internal live mirror (local git backup of the
|
|
3587
|
+
// markdown projection). Backup-on-by-default; GitHub off-site backup is an
|
|
3588
|
+
// opt-in upgrade layered on top later. Opt out via the `default_mirror: off`
|
|
3589
|
+
// global knob (operators on git-less / disk-constrained / cloud boxes) or
|
|
3590
|
+
// the `--no-mirror` flag (this one create only).
|
|
3591
|
+
//
|
|
3592
|
+
// BEST-EFFORT, NON-FATAL: write the mirror config first (so the operator's
|
|
3593
|
+
// intent persists even if git is absent), then attempt the bootstrap. A
|
|
3594
|
+
// git-less box leaves the config written but inactive + logs an actionable
|
|
3595
|
+
// hint — it must NEVER fail the vault create. Create-time ONLY: existing
|
|
3596
|
+
// vaults are never retroactively migrated.
|
|
3597
|
+
if (shouldEnableCreateTimeMirror(opts)) {
|
|
3598
|
+
const mirrorConfig = historyPresetMirrorConfig();
|
|
3599
|
+
writeMirrorConfigForVault(name, mirrorConfig);
|
|
3600
|
+
const mirrorPath = resolveMirrorPath(vaultDir(name), mirrorConfig);
|
|
3601
|
+
if (mirrorPath) {
|
|
3602
|
+
try {
|
|
3603
|
+
const result = await bootstrapInternalMirror(mirrorPath, opts.which);
|
|
3604
|
+
if (!result.ok) {
|
|
3605
|
+
// git-not-installed (or refuse-to-clobber) — config stays written,
|
|
3606
|
+
// mirror just isn't active yet. Surface an actionable line; the
|
|
3607
|
+
// vault create succeeds regardless.
|
|
3608
|
+
console.error(
|
|
3609
|
+
`Note: local git backup configured but not yet active — ${result.error} ` +
|
|
3610
|
+
`Install git to activate; the backup turns on automatically on the next vault restart.`,
|
|
3611
|
+
);
|
|
3612
|
+
}
|
|
3613
|
+
} catch (err) {
|
|
3614
|
+
// Defense-in-depth: bootstrapInternalMirror already converts the
|
|
3615
|
+
// git-missing case into a non-throwing { ok:false } result, but a
|
|
3616
|
+
// truly unexpected throw must still not fail the create.
|
|
3617
|
+
console.error(
|
|
3618
|
+
`Note: local git backup configured but bootstrap hit an unexpected error ` +
|
|
3619
|
+
`(${(err as Error).message ?? err}). The vault was still created; ` +
|
|
3620
|
+
`the backup will retry on the next vault restart.`,
|
|
3621
|
+
);
|
|
3622
|
+
}
|
|
3623
|
+
}
|
|
3624
|
+
}
|
|
3625
|
+
|
|
3626
|
+
// vault#442: default to per-user OAuth — do NOT auto-mint or bake in a
|
|
3627
|
+
// shared token. Only mint when the caller explicitly opted in (header-auth /
|
|
3628
|
+
// script use case), and then scope-narrow (read/write, never admin).
|
|
3629
|
+
if (opts.mintVerb) {
|
|
3630
|
+
return mintBootstrapCredential(name, opts.mintVerb);
|
|
3631
|
+
}
|
|
3632
|
+
return {
|
|
3633
|
+
token: null,
|
|
3634
|
+
guidance:
|
|
3635
|
+
"No token minted — this vault uses per-user OAuth (sign in on first connect). " +
|
|
3636
|
+
"Need a header-auth token for a script? Run " +
|
|
3637
|
+
`\`parachute auth mint-token --scope vault:${name}:read\` (or \`:write\`).`,
|
|
3638
|
+
};
|
|
3365
3639
|
}
|
|
3366
3640
|
|
|
3367
3641
|
interface InstallMcpConfigOpts {
|
|
@@ -3443,19 +3717,28 @@ Setup:
|
|
|
3443
3717
|
parachute-vault init [--mcp|--no-mcp] [--token|--no-token] [--vault-name <name>]
|
|
3444
3718
|
[--autostart|--no-autostart]
|
|
3445
3719
|
Set up everything (one command, idempotent).
|
|
3446
|
-
--mcp/--no-mcp controls the Claude Code MCP entry
|
|
3447
|
-
|
|
3448
|
-
|
|
3720
|
+
--mcp/--no-mcp controls the Claude Code MCP entry (written
|
|
3721
|
+
for per-user OAuth by default — no baked token; sign in on
|
|
3722
|
+
first connect). --token opts into ALSO minting a scope-narrow
|
|
3723
|
+
header-auth token (vault:<name>:read) for non-OAuth clients /
|
|
3724
|
+
scripts; --no-token (the default) skips minting entirely.
|
|
3449
3725
|
--vault-name skips the prompt and names the vault
|
|
3450
3726
|
(lowercase alphanumeric, hyphens, underscores;
|
|
3451
3727
|
omit to be prompted interactively, default "default").
|
|
3452
|
-
--autostart
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3728
|
+
--autostart registers vault with launchd / systemd so
|
|
3729
|
+
it starts on boot AND auto-restarts on crash; it forces
|
|
3730
|
+
registration even when a hub supervisor is detected
|
|
3731
|
+
(logged with a warning). --no-autostart skips daemon
|
|
3732
|
+
registration AND uninstalls any prior registration — for
|
|
3733
|
+
CI, dev sandboxes, Docker, or environments where another
|
|
3734
|
+
supervisor manages the process. Default: register when
|
|
3735
|
+
standalone, but skip when a hub is detected (the hub
|
|
3736
|
+
supervisor owns vault's lifecycle). An explicit flag
|
|
3737
|
+
persists in config.yaml as 'autostart: true|false'.
|
|
3738
|
+
Upgrade note: a box with a persisted 'autostart: true'
|
|
3739
|
+
(from an earlier explicit --autostart) keeps registering
|
|
3740
|
+
even under a hub — run init --no-autostart once to clear
|
|
3741
|
+
it and let the hub manage vault.
|
|
3459
3742
|
parachute-vault doctor Diagnose install/config issues
|
|
3460
3743
|
parachute-vault uninstall [--wipe] [--yes]
|
|
3461
3744
|
Remove daemon + MCP entry; --wipe also removes vaults, .env,
|
|
@@ -3465,7 +3748,18 @@ Setup:
|
|
|
3465
3748
|
parachute --version Print the installed version (alias: -v, version)
|
|
3466
3749
|
|
|
3467
3750
|
Vaults:
|
|
3468
|
-
parachute-vault create <name> [--json]
|
|
3751
|
+
parachute-vault create <name> [--json] [--no-mirror] [--mint [--scope read|write]] [--token <bearer>]
|
|
3752
|
+
Create a new vault (--json: emit { name, token, paths, set_as_default }).
|
|
3753
|
+
Default auth is per-user OAuth — NO token is minted; connect with
|
|
3754
|
+
"claude mcp add --transport http parachute-<name> <endpoint>" and sign in
|
|
3755
|
+
on first use. --mint opts into a scope-narrow hub JWT for the header-auth /
|
|
3756
|
+
script case (--scope read [default] | write — admin is NOT mintable from
|
|
3757
|
+
create); --token <bearer> pastes an existing bearer instead of minting.
|
|
3758
|
+
New vaults default to an internal live mirror — a local git backup of
|
|
3759
|
+
the markdown projection (backup on by default; GitHub off-site is an
|
|
3760
|
+
opt-in upgrade). --no-mirror creates a bare vault with no mirror config.
|
|
3761
|
+
Operators can flip the server-wide default with 'default_mirror: off' in
|
|
3762
|
+
config.yaml (recommended for cloud / disk-constrained boxes).
|
|
3469
3763
|
parachute-vault list List all vaults
|
|
3470
3764
|
parachute-vault remove <name> [--yes] Remove a vault
|
|
3471
3765
|
parachute-vault mcp-install [--mint|--token <t>]
|
|
@@ -3539,7 +3833,7 @@ Vaults:
|
|
|
3539
3833
|
Tokens (vault#282 Stage 2 — vault is a pure hub resource-server; it no longer
|
|
3540
3834
|
mints its own tokens. Mint a hub-issued JWT with \`parachute-vault mcp-install\`
|
|
3541
3835
|
or \`parachute auth mint-token --scope vault:<name>:<verb>\`. \`list\` / \`revoke\`
|
|
3542
|
-
below operate on any vestigial pre-0.
|
|
3836
|
+
below operate on any vestigial pre-0.5.0 rows for cleanup.):
|
|
3543
3837
|
parachute-vault tokens List vault-DB tokens (every vault)
|
|
3544
3838
|
parachute-vault tokens list --vault <name> List tokens for one vault only
|
|
3545
3839
|
parachute-vault tokens revoke <token-id> Revoke a vestigial token (default vault)
|