@openparachute/vault 0.2.4 → 0.3.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.
Files changed (102) hide show
  1. package/.claude/settings.local.json +2 -25
  2. package/CHANGELOG.md +64 -0
  3. package/CLAUDE.md +17 -7
  4. package/README.md +169 -136
  5. package/core/src/core.test.ts +591 -19
  6. package/core/src/hooks.ts +111 -3
  7. package/core/src/indexed-fields.test.ts +285 -0
  8. package/core/src/indexed-fields.ts +238 -0
  9. package/core/src/mcp.ts +127 -6
  10. package/core/src/notes.ts +153 -11
  11. package/core/src/query-operators.ts +174 -0
  12. package/core/src/schema.ts +69 -2
  13. package/core/src/store.ts +95 -1
  14. package/core/src/tag-schemas.ts +5 -0
  15. package/core/src/types.ts +28 -1
  16. package/docs/HTTP_API.md +105 -1
  17. package/docs/auth-model.md +340 -0
  18. package/package/package.json +32 -0
  19. package/package.json +2 -2
  20. package/src/auth.test.ts +83 -114
  21. package/src/auth.ts +68 -6
  22. package/src/backup-launchd.ts +1 -1
  23. package/src/backup.test.ts +1 -1
  24. package/src/backup.ts +18 -17
  25. package/src/bind.test.ts +28 -0
  26. package/src/bind.ts +19 -0
  27. package/src/cli.ts +228 -133
  28. package/src/config-triggers.test.ts +49 -0
  29. package/src/config.test.ts +317 -2
  30. package/src/config.ts +420 -40
  31. package/src/context.test.ts +136 -0
  32. package/src/context.ts +115 -0
  33. package/src/daemon.ts +17 -16
  34. package/src/doctor.test.ts +9 -7
  35. package/src/launchd.test.ts +1 -1
  36. package/src/launchd.ts +6 -6
  37. package/src/mcp-http.ts +75 -21
  38. package/src/mcp-install.test.ts +125 -0
  39. package/src/mcp-install.ts +60 -0
  40. package/src/mcp-tools.ts +34 -96
  41. package/src/module-config.ts +109 -0
  42. package/src/oauth.test.ts +345 -57
  43. package/src/oauth.ts +155 -35
  44. package/src/published.test.ts +2 -2
  45. package/src/routes.ts +209 -33
  46. package/src/routing.test.ts +817 -300
  47. package/src/routing.ts +204 -202
  48. package/src/scopes.test.ts +294 -0
  49. package/src/scopes.ts +253 -0
  50. package/src/scribe-env.test.ts +49 -0
  51. package/src/scribe-env.ts +33 -0
  52. package/src/server.ts +73 -9
  53. package/src/services-manifest.test.ts +140 -0
  54. package/src/services-manifest.ts +99 -0
  55. package/src/systemd.ts +3 -3
  56. package/src/token-store.ts +42 -9
  57. package/src/transcription-worker.test.ts +864 -0
  58. package/src/transcription-worker.ts +501 -0
  59. package/src/triggers.test.ts +191 -1
  60. package/src/triggers.ts +17 -2
  61. package/src/vault.test.ts +693 -77
  62. package/src/version.test.ts +1 -1
  63. package/.playwright-mcp/console-2026-04-14T04-17-25-395Z.log +0 -2
  64. package/.playwright-mcp/console-2026-04-14T04-18-11-767Z.log +0 -1
  65. package/.playwright-mcp/console-2026-04-14T04-19-07-733Z.log +0 -2
  66. package/.playwright-mcp/console-2026-04-14T04-20-45-440Z.log +0 -2
  67. package/.playwright-mcp/page-2026-04-14T04-17-25-536Z.yml +0 -1
  68. package/.playwright-mcp/page-2026-04-14T04-18-11-816Z.yml +0 -1
  69. package/.playwright-mcp/page-2026-04-14T04-18-31-674Z.yml +0 -211
  70. package/.playwright-mcp/page-2026-04-14T04-19-07-795Z.yml +0 -59
  71. package/.playwright-mcp/page-2026-04-14T04-19-36-239Z.yml +0 -232
  72. package/.playwright-mcp/page-2026-04-14T04-19-58-327Z.yml +0 -182
  73. package/.playwright-mcp/page-2026-04-14T04-20-10-517Z.yml +0 -91
  74. package/.playwright-mcp/page-2026-04-14T04-20-14-796Z.yml +0 -70
  75. package/.playwright-mcp/page-2026-04-14T04-20-45-509Z.yml +0 -59
  76. package/religions-abrahamic-filter.png +0 -0
  77. package/religions-buddhism-v2.png +0 -0
  78. package/religions-buddhism.png +0 -0
  79. package/religions-final.png +0 -0
  80. package/religions-v1.png +0 -0
  81. package/religions-v2.png +0 -0
  82. package/religions-zen.png +0 -0
  83. package/web/README.md +0 -73
  84. package/web/bun.lock +0 -827
  85. package/web/eslint.config.js +0 -23
  86. package/web/index.html +0 -15
  87. package/web/package.json +0 -36
  88. package/web/public/favicon.svg +0 -1
  89. package/web/public/icons.svg +0 -24
  90. package/web/src/App.tsx +0 -149
  91. package/web/src/Graph.tsx +0 -200
  92. package/web/src/NoteView.tsx +0 -155
  93. package/web/src/Sidebar.tsx +0 -186
  94. package/web/src/api.ts +0 -21
  95. package/web/src/index.css +0 -50
  96. package/web/src/main.tsx +0 -10
  97. package/web/src/types.ts +0 -37
  98. package/web/src/utils.ts +0 -107
  99. package/web/tsconfig.app.json +0 -25
  100. package/web/tsconfig.json +0 -7
  101. package/web/tsconfig.node.json +0 -24
  102. package/web/vite.config.ts +0 -16
package/src/cli.ts CHANGED
@@ -4,16 +4,16 @@
4
4
  * Parachute Vault CLI.
5
5
  *
6
6
  * Usage:
7
- * parachute vault init — set up everything, one command
8
- * parachute vault create <name> — create a new vault
9
- * parachute vault list — list all vaults
10
- * parachute vault mcp-install <name> — add vault MCP to ~/.claude.json
11
- * parachute vault remove <name> — remove a vault
12
- * parachute vault config — show all config
13
- * parachute vault config set <key> <val> — set a config value
14
- * parachute vault config unset <key> — remove a config value
15
- * parachute vault serve — run the server (foreground)
16
- * parachute vault status — show full status
7
+ * parachute-vault init — set up everything, one command
8
+ * parachute-vault create <name> — create a new vault
9
+ * parachute-vault list — list all vaults
10
+ * parachute-vault mcp-install <name> — add vault MCP to ~/.claude.json
11
+ * parachute-vault remove <name> — remove a vault
12
+ * parachute-vault config — show all config
13
+ * parachute-vault config set <key> <val> — set a config value
14
+ * parachute-vault config unset <key> — remove a config value
15
+ * parachute-vault serve — run the server (foreground)
16
+ * parachute-vault status — show full status
17
17
  */
18
18
 
19
19
  import { resolve } from "path";
@@ -25,6 +25,7 @@ import { existsSync, readFileSync, writeFileSync, rmSync, mkdirSync } from "fs";
25
25
  import pkg from "../package.json" with { type: "json" };
26
26
  import {
27
27
  ensureConfigDirSync,
28
+ migrateFromLegacyLayout,
28
29
  readVaultConfig,
29
30
  writeVaultConfig,
30
31
  readGlobalConfig,
@@ -45,8 +46,9 @@ import {
45
46
  GLOBAL_CONFIG_PATH,
46
47
  } from "./config.ts";
47
48
  import type { VaultConfig } from "./config.ts";
48
- import { VAULTS_DIR } from "./config.ts";
49
+ import { DATA_DIR } from "./config.ts";
49
50
  import { installAgent, uninstallAgent, isAgentLoaded, restartAgent } from "./launchd.ts";
51
+ import { chooseMcpUrl } from "./mcp-install.ts";
50
52
  import {
51
53
  runBackup,
52
54
  readLastBackup,
@@ -77,7 +79,9 @@ import {
77
79
  import { confirm, ask, askPassword, choose } from "./prompt.ts";
78
80
  import { generateToken, createToken, listTokens, revokeToken, migrateVaultKeys } from "./token-store.ts";
79
81
  import type { TokenPermission } from "./token-store.ts";
82
+ import { resolveCreateTokenFlags, VAULT_SCOPES } from "./scopes.ts";
80
83
  import { getVaultStore } from "./vault-store.ts";
84
+ import { upsertService, ServicesManifestError } from "./services-manifest.ts";
81
85
  import {
82
86
  hasOwnerPassword,
83
87
  setOwnerPassword,
@@ -103,7 +107,7 @@ import {
103
107
 
104
108
  const args = process.argv.slice(2);
105
109
 
106
- // Support both `parachute vault <cmd>` and `parachute <cmd>` patterns
110
+ // Support both `parachute-vault <cmd>` and `parachute <cmd>` patterns
107
111
  let command: string;
108
112
  let cmdArgs: string[];
109
113
 
@@ -119,9 +123,20 @@ if (args[0] === "vault") {
119
123
  // Commands
120
124
  // ---------------------------------------------------------------------------
121
125
 
126
+ // Pre-0.3 installs kept vault state directly under ~/.parachute/. Run the
127
+ // migration unconditionally so any command (including read-only ones like
128
+ // `doctor` and `url`) picks up the relocated state on first post-upgrade run.
129
+ // No-op when no legacy paths exist. Skipped for `help` and `version`, which
130
+ // are expected to produce *only* their documented output — and because scripts
131
+ // piping `parachute-vault --version` shouldn't get migration chatter on stderr.
132
+ const SKIP_MIGRATION = new Set(["help", "--help", "-h", "version", "--version", "-v"]);
133
+ if (!SKIP_MIGRATION.has(command)) {
134
+ migrateFromLegacyLayout();
135
+ }
136
+
122
137
  switch (command) {
123
138
  case "init":
124
- await cmdInit();
139
+ await cmdInit(cmdArgs);
125
140
  break;
126
141
  case "create":
127
142
  cmdCreate(cmdArgs);
@@ -188,7 +203,7 @@ switch (command) {
188
203
  case "--version":
189
204
  case "-v":
190
205
  // Intentionally minimal — just the version string on stdout. Scripts
191
- // (and `parachute vault doctor` in a future check) rely on this being
206
+ // (and `parachute-vault doctor` in a future check) rely on this being
192
207
  // a bare-number line; anything else belongs in `vault status`.
193
208
  console.log(pkg.version);
194
209
  break;
@@ -202,9 +217,16 @@ switch (command) {
202
217
  // Command implementations
203
218
  // ---------------------------------------------------------------------------
204
219
 
205
- async function cmdInit() {
220
+ async function cmdInit(args: string[] = []) {
206
221
  ensureConfigDirSync();
207
222
 
223
+ // Flags: --mcp installs MCP in ~/.claude.json without prompting;
224
+ // --no-mcp skips it without prompting. If both passed, --no-mcp wins
225
+ // (safer default). Neither → prompt in a TTY, default-yes in a
226
+ // non-TTY for back-compat with existing piped install scripts.
227
+ const flagMcpOn = args.includes("--mcp");
228
+ const flagMcpOff = args.includes("--no-mcp");
229
+
208
230
  const isMac = process.platform === "darwin";
209
231
  const isLinux = process.platform === "linux";
210
232
  const isFirstRun = !existsSync(ENV_PATH);
@@ -249,6 +271,32 @@ async function cmdInit() {
249
271
  }
250
272
  writeGlobalConfig(globalConfig);
251
273
 
274
+ // 2a. Register in the shared services manifest so the @openparachute/cli
275
+ // dispatcher can discover this service and its health endpoint. Upserts
276
+ // by name, preserving entries for other services. Non-fatal on failure —
277
+ // init can complete without the manifest, just with a warning.
278
+ //
279
+ // `paths[0]` is the canonical mount point — CLI uses it for the
280
+ // `.well-known/parachute.json` URL and for `parachute expose`. Advertise
281
+ // `/vault/<default_vault>` so MCP clients land at the scoped endpoint.
282
+ // When no default vault exists yet (multi-vault, no fallback), fall back
283
+ // to "/" — the CLI can detect and prompt.
284
+ const servicePath = globalConfig.default_vault
285
+ ? `/vault/${globalConfig.default_vault}`
286
+ : "/";
287
+ try {
288
+ upsertService({
289
+ name: "parachute-vault",
290
+ port: globalConfig.port || DEFAULT_PORT,
291
+ paths: [servicePath],
292
+ health: "/health",
293
+ version: pkg.version,
294
+ });
295
+ } catch (err) {
296
+ const msg = err instanceof ServicesManifestError ? err.message : String(err);
297
+ console.log(` Warning: could not update ~/.parachute/services.json: ${msg}`);
298
+ }
299
+
252
300
  // 2b. Migrate existing legacy keys into per-vault token tables
253
301
  for (const v of listVaults()) {
254
302
  try {
@@ -297,13 +345,32 @@ async function cmdInit() {
297
345
  }
298
346
  if (serverPath) {
299
347
  console.log(` Server path: ${serverPath}`);
300
- console.log(` Wrapper: ~/.parachute/start.sh`);
348
+ console.log(` Wrapper: ~/.parachute/vault/start.sh`);
301
349
  }
302
350
  console.log(` Listening on http://0.0.0.0:${globalConfig.port || DEFAULT_PORT}`);
303
351
 
304
- // 7. Install MCP for Claude Code (with token for auth)
305
- installMcpConfig(apiKey);
306
- console.log(` MCP server added to ~/.claude.json`);
352
+ // 7. Install MCP for Claude Code (with token for auth) — user confirms
353
+ // unless --mcp / --no-mcp explicitly passed. Writing to ~/.claude.json
354
+ // is a side effect some users don't want; default-yes in a TTY since
355
+ // most users installing vault want Claude Code to see it, but ask.
356
+ let addMcp: boolean;
357
+ if (flagMcpOff) {
358
+ addMcp = false;
359
+ } else if (flagMcpOn) {
360
+ addMcp = true;
361
+ } else if (process.stdin.isTTY) {
362
+ addMcp = await confirm("Add Vault MCP to Claude Code (~/.claude.json)?", true);
363
+ } else {
364
+ addMcp = true; // non-interactive: preserve the installable-via-pipe default
365
+ }
366
+
367
+ if (addMcp) {
368
+ installMcpConfig(apiKey);
369
+ console.log(` MCP server added to ~/.claude.json`);
370
+ } else {
371
+ console.log(" Skipped adding MCP to ~/.claude.json.");
372
+ console.log(" Run `parachute-vault mcp-install` later if you want it.");
373
+ }
307
374
 
308
375
  // 8. Summary
309
376
  console.log("\n---");
@@ -326,8 +393,8 @@ async function cmdInit() {
326
393
  }
327
394
 
328
395
  console.log(`\nNext steps:`);
329
- console.log(` parachute vault status check everything is running`);
330
- console.log(` parachute vault config view/edit configuration`);
396
+ console.log(` parachute-vault status check everything is running`);
397
+ console.log(` parachute-vault config view/edit configuration`);
331
398
  }
332
399
 
333
400
  async function promptForOwnerPassword(purpose: string): Promise<boolean> {
@@ -339,7 +406,7 @@ async function promptForOwnerPassword(purpose: string): Promise<boolean> {
339
406
  while (true) {
340
407
  const pw = await askPassword(" Password (or leave blank to skip)");
341
408
  if (!pw) {
342
- console.log(" Skipped — you can set one later with `parachute vault set-password`.");
409
+ console.log(" Skipped — you can set one later with `parachute-vault set-password`.");
343
410
  return false;
344
411
  }
345
412
 
@@ -391,13 +458,13 @@ async function cmdSetPassword(args: string[]) {
391
458
  }
392
459
 
393
460
  // ---------------------------------------------------------------------------
394
- // 2FA — parachute vault 2fa [enroll | disable | backup-codes | status]
461
+ // 2FA — parachute-vault 2fa [enroll | disable | backup-codes | status]
395
462
  // ---------------------------------------------------------------------------
396
463
 
397
464
  async function confirmOwnerPassword(purpose: string): Promise<boolean> {
398
465
  const hash = getOwnerPasswordHash();
399
466
  if (!hash) {
400
- console.error("No owner password is set. Run: parachute vault set-password");
467
+ console.error("No owner password is set. Run: parachute-vault set-password");
401
468
  return false;
402
469
  }
403
470
  console.log(purpose);
@@ -460,14 +527,14 @@ async function cmd2fa(args: string[]) {
460
527
  console.log(`2FA: enabled (${getBackupCodeCount()} backup code(s) remaining)`);
461
528
  } else {
462
529
  console.log("2FA: not enabled");
463
- console.log(" Enable with: parachute vault 2fa enroll");
530
+ console.log(" Enable with: parachute-vault 2fa enroll");
464
531
  }
465
532
  return;
466
533
  }
467
534
 
468
535
  if (sub === "enroll") {
469
536
  if (!hasOwnerPassword()) {
470
- console.error("Set an owner password first: parachute vault set-password");
537
+ console.error("Set an owner password first: parachute-vault set-password");
471
538
  process.exit(1);
472
539
  }
473
540
  if (hasTotpEnrolled()) {
@@ -511,7 +578,7 @@ async function cmd2fa(args: string[]) {
511
578
  console.log(` Incorrect code. (${2 - attempt} attempt(s) left)`);
512
579
  }
513
580
  if (!confirmed) {
514
- console.error("Enrollment failed — rolling back. Re-run `parachute vault 2fa enroll` to try again.");
581
+ console.error("Enrollment failed — rolling back. Re-run `parachute-vault 2fa enroll` to try again.");
515
582
  disableTotp();
516
583
  process.exit(1);
517
584
  }
@@ -539,7 +606,7 @@ async function cmd2fa(args: string[]) {
539
606
 
540
607
  if (sub === "backup-codes") {
541
608
  if (!hasTotpEnrolled()) {
542
- console.error("2FA is not enabled. Run: parachute vault 2fa enroll");
609
+ console.error("2FA is not enabled. Run: parachute-vault 2fa enroll");
543
610
  process.exit(1);
544
611
  }
545
612
  if (!(await confirmForTwoFactor("Confirm ownership to regenerate backup codes:"))) {
@@ -554,14 +621,14 @@ async function cmd2fa(args: string[]) {
554
621
  }
555
622
 
556
623
  console.error(`Unknown 2fa command: ${sub}`);
557
- console.error("Usage: parachute vault 2fa [status | enroll | disable | backup-codes]");
624
+ console.error("Usage: parachute-vault 2fa [status | enroll | disable | backup-codes]");
558
625
  process.exit(1);
559
626
  }
560
627
 
561
628
  function cmdCreate(args: string[]) {
562
629
  const name = args[0];
563
630
  if (!name) {
564
- console.error("Usage: parachute vault create <name>");
631
+ console.error("Usage: parachute-vault create <name>");
565
632
  process.exit(1);
566
633
  }
567
634
 
@@ -570,9 +637,9 @@ function cmdCreate(args: string[]) {
570
637
  process.exit(1);
571
638
  }
572
639
  if (name === "list") {
573
- // Reserved — /vaults/list is the public discovery endpoint. Allowing a
574
- // vault with this name would let its routes (/vaults/list/mcp, etc.) be
575
- // shadowed by the discovery handler.
640
+ // Reserved — keeps the "list" vault name out of play even though per-vault
641
+ // routes now live under /vault/<name>/ and no longer collide with the
642
+ // /vaults/list discovery endpoint.
576
643
  console.error(`"list" is a reserved vault name.`);
577
644
  process.exit(1);
578
645
  }
@@ -610,13 +677,13 @@ function cmdCreate(args: string[]) {
610
677
  console.log(` ${defaultNote}`);
611
678
  }
612
679
  console.log();
613
- console.log(`To add MCP to Claude: parachute vault mcp-install ${name}`);
680
+ console.log(`To add MCP to Claude: parachute-vault mcp-install ${name}`);
614
681
  }
615
682
 
616
683
  function cmdList() {
617
684
  const vaults = listVaults();
618
685
  if (vaults.length === 0) {
619
- console.log("No vaults. Run: parachute vault init");
686
+ console.log("No vaults. Run: parachute-vault init");
620
687
  return;
621
688
  }
622
689
 
@@ -637,7 +704,7 @@ function cmdMcpInstall(_args: string[]) {
637
704
  function cmdRemove(args: string[]) {
638
705
  const name = args[0];
639
706
  if (!name) {
640
- console.error("Usage: parachute vault remove <name>");
707
+ console.error("Usage: parachute-vault remove <name>");
641
708
  process.exit(1);
642
709
  }
643
710
 
@@ -651,7 +718,7 @@ function cmdRemove(args: string[]) {
651
718
  if (!force) {
652
719
  console.log(`This will permanently delete vault "${name}" and all its data.`);
653
720
  console.log(` Path: ${vaultDir(name)}`);
654
- console.log(`\nTo confirm: parachute vault remove ${name} --yes`);
721
+ console.log(`\nTo confirm: parachute-vault remove ${name} --yes`);
655
722
  return;
656
723
  }
657
724
 
@@ -680,7 +747,7 @@ function cmdRemove(args: string[]) {
680
747
  async function cmdConfig(args: string[]) {
681
748
  const subcmd = args[0];
682
749
 
683
- // parachute vault config — show current config
750
+ // parachute-vault config — show current config
684
751
  if (!subcmd) {
685
752
  loadEnvFile();
686
753
  const env = readEnvFile();
@@ -693,7 +760,7 @@ async function cmdConfig(args: string[]) {
693
760
  console.log();
694
761
 
695
762
  if (Object.keys(env).length === 0) {
696
- console.log(" No env vars set. Use: parachute vault config set <key> <value>");
763
+ console.log(" No env vars set. Use: parachute-vault config set <key> <value>");
697
764
  } else {
698
765
  for (const [key, val] of Object.entries(env)) {
699
766
  // Mask sensitive values
@@ -707,46 +774,46 @@ async function cmdConfig(args: string[]) {
707
774
  return;
708
775
  }
709
776
 
710
- // parachute vault config set <key> <value>
777
+ // parachute-vault config set <key> <value>
711
778
  if (subcmd === "set") {
712
779
  const key = args[1];
713
780
  const value = args.slice(2).join(" ");
714
781
  if (!key || !value) {
715
- console.error("Usage: parachute vault config set <key> <value>");
782
+ console.error("Usage: parachute-vault config set <key> <value>");
716
783
  process.exit(1);
717
784
  }
718
785
  setEnvVar(key, value);
719
786
  console.log(`Set ${key}=${key.includes("KEY") ? value.slice(0, 8) + "..." : value}`);
720
- console.log("Restart the daemon to apply: parachute vault restart");
787
+ console.log("Restart the daemon to apply: parachute-vault restart");
721
788
  return;
722
789
  }
723
790
 
724
- // parachute vault config unset <key>
791
+ // parachute-vault config unset <key>
725
792
  if (subcmd === "unset") {
726
793
  const key = args[1];
727
794
  if (!key) {
728
- console.error("Usage: parachute vault config unset <key>");
795
+ console.error("Usage: parachute-vault config unset <key>");
729
796
  process.exit(1);
730
797
  }
731
798
  unsetEnvVar(key);
732
799
  console.log(`Removed ${key}`);
733
- console.log("Restart the daemon to apply: parachute vault restart");
800
+ console.log("Restart the daemon to apply: parachute-vault restart");
734
801
  return;
735
802
  }
736
803
 
737
804
  console.error(`Unknown config command: ${subcmd}`);
738
- console.error("Usage: parachute vault config [set <key> <value> | unset <key>]");
805
+ console.error("Usage: parachute-vault config [set <key> <value> | unset <key>]");
739
806
  process.exit(1);
740
807
  }
741
808
 
742
809
  // ---------------------------------------------------------------------------
743
- // Tokens — parachute vault tokens [create | list | revoke]
810
+ // Tokens — parachute-vault tokens [create | list | revoke]
744
811
  // ---------------------------------------------------------------------------
745
812
 
746
813
  function cmdTokens(args: string[]) {
747
814
  const subcmd = args[0];
748
815
 
749
- // parachute vault tokens — list all tokens (across all vaults)
816
+ // parachute-vault tokens — list all tokens (across all vaults)
750
817
  if (!subcmd || subcmd === "list") {
751
818
  const vaults = listVaults();
752
819
  let anyTokens = false;
@@ -773,12 +840,13 @@ function cmdTokens(args: string[]) {
773
840
  }
774
841
 
775
842
  if (!anyTokens) {
776
- console.log("No tokens found. Create one: parachute vault tokens create");
843
+ console.log("No tokens found. Create one: parachute-vault tokens create");
777
844
  }
778
845
  return;
779
846
  }
780
847
 
781
- // parachute vault tokens create --vault <name> [--permission full|read]
848
+ // parachute-vault tokens create --vault <name>
849
+ // [--scope vault:read,vault:write | --read | --permission full|read]
782
850
  // [--expires <duration>] [--label <label>]
783
851
  if (subcmd === "create") {
784
852
  const vaultFlag = args.indexOf("--vault");
@@ -790,15 +858,17 @@ function cmdTokens(args: string[]) {
790
858
  process.exit(1);
791
859
  }
792
860
 
793
- // --read shorthand or --permission full|read
794
- const isReadShorthand = args.includes("--read");
795
- const permFlag = args.indexOf("--permission");
796
- const rawPerm = isReadShorthand ? "read" : (permFlag !== -1 ? args[permFlag + 1] : "full");
797
- const permission: TokenPermission = rawPerm === "read" ? "read" : "full";
798
- if (!["full", "read", "admin", "write"].includes(rawPerm)) {
799
- console.error(`Invalid permission: ${rawPerm}. Must be full or read.`);
861
+ // Combining --scope / --read / --permission is always an error: a
862
+ // user minting a token expects exactly one narrowing signal, and
863
+ // silently picking one would mint the opposite of what the other
864
+ // reading intended. See resolveCreateTokenFlags.
865
+ const resolved = resolveCreateTokenFlags(args);
866
+ if (resolved.error) {
867
+ console.error(resolved.error);
800
868
  process.exit(1);
801
869
  }
870
+ const scopes = resolved.scopes;
871
+ const permission: TokenPermission = resolved.permission;
802
872
 
803
873
  const expiresFlag = args.indexOf("--expires");
804
874
  let expiresAt: string | null = null;
@@ -819,12 +889,15 @@ function cmdTokens(args: string[]) {
819
889
  createToken(store.db, fullToken, {
820
890
  label,
821
891
  permission,
892
+ scopes,
822
893
  expires_at: expiresAt,
823
894
  });
824
895
 
896
+ const displayScopes = scopes ?? [...VAULT_SCOPES];
825
897
  console.log(`Created token for vault "${vaultName}":`);
826
898
  console.log(` Token: ${fullToken}`);
827
899
  console.log(` Permission: ${permission}`);
900
+ console.log(` Scopes: ${displayScopes.join(" ")}`);
828
901
  if (expiresAt) console.log(` Expires: ${expiresAt}`);
829
902
  console.log(` Label: ${label}`);
830
903
  console.log();
@@ -832,11 +905,11 @@ function cmdTokens(args: string[]) {
832
905
  return;
833
906
  }
834
907
 
835
- // parachute vault tokens revoke <token-id> --vault <name>
908
+ // parachute-vault tokens revoke <token-id> --vault <name>
836
909
  if (subcmd === "revoke") {
837
910
  const tokenId = args[1];
838
911
  if (!tokenId) {
839
- console.error("Usage: parachute vault tokens revoke <token-id> --vault <name>");
912
+ console.error("Usage: parachute-vault tokens revoke <token-id> --vault <name>");
840
913
  process.exit(1);
841
914
  }
842
915
 
@@ -860,7 +933,7 @@ function cmdTokens(args: string[]) {
860
933
  }
861
934
 
862
935
  console.error(`Unknown tokens command: ${subcmd}`);
863
- console.error("Usage: parachute vault tokens [create | list | revoke <id>]");
936
+ console.error("Usage: parachute-vault tokens [create | list | revoke <id>]");
864
937
  process.exit(1);
865
938
  }
866
939
 
@@ -1029,7 +1102,7 @@ async function cmdUninstall(argsList: string[]) {
1029
1102
  if (wipe) {
1030
1103
  console.log("`--wipe` will ALSO remove vaults, .env, config.yaml, and daemon logs.\n");
1031
1104
  } else {
1032
- console.log("User data (~/.parachute/vaults, ~/.parachute/.env) is left alone.\n");
1105
+ console.log("User data (~/.parachute/vault/) is left alone.\n");
1033
1106
  }
1034
1107
 
1035
1108
  // Scripted `--yes --wipe` bypasses both interactive confirms. That's the
@@ -1039,7 +1112,7 @@ async function cmdUninstall(argsList: string[]) {
1039
1112
  // miss this; interactive users already see the prompts.
1040
1113
  if (skipPrompts && wipe) {
1041
1114
  const ts = new Date().toISOString();
1042
- const targets = [VAULTS_DIR, ENV_PATH, GLOBAL_CONFIG_PATH, LOG_PATH, ERR_PATH].join(", ");
1115
+ const targets = [DATA_DIR, ENV_PATH, GLOBAL_CONFIG_PATH, LOG_PATH, ERR_PATH].join(", ");
1043
1116
  console.log(`[${ts}] scripted destructive wipe: ${targets}`);
1044
1117
  }
1045
1118
 
@@ -1083,7 +1156,7 @@ async function cmdUninstall(argsList: string[]) {
1083
1156
  // Inventory what's actually on disk. Paths that don't exist are a
1084
1157
  // silent no-op on removal, but we also skip listing them so the
1085
1158
  // "would be removed" summary doesn't lie to the user.
1086
- const vaultsExist = existsSync(VAULTS_DIR);
1159
+ const vaultsExist = existsSync(DATA_DIR);
1087
1160
  const envExists = existsSync(ENV_PATH);
1088
1161
  const configExists = existsSync(GLOBAL_CONFIG_PATH);
1089
1162
  const logExists = existsSync(LOG_PATH);
@@ -1094,7 +1167,7 @@ async function cmdUninstall(argsList: string[]) {
1094
1167
  console.log("No user data to remove.");
1095
1168
  } else {
1096
1169
  console.log("\nUser data that would be removed:");
1097
- if (vaultsExist) console.log(` ${VAULTS_DIR} (SQLite vaults)`);
1170
+ if (vaultsExist) console.log(` ${DATA_DIR} (per-vault SQLite data)`);
1098
1171
  if (envExists) console.log(` ${ENV_PATH} (.env config + secrets)`);
1099
1172
  if (configExists) console.log(` ${GLOBAL_CONFIG_PATH} (global config)`);
1100
1173
  if (logExists) console.log(` ${LOG_PATH} (daemon log)`);
@@ -1108,7 +1181,7 @@ async function cmdUninstall(argsList: string[]) {
1108
1181
  doWipe = await confirm("Delete this data? (cannot be undone)", false);
1109
1182
  }
1110
1183
  if (doWipe) {
1111
- if (vaultsExist) rmSync(VAULTS_DIR, { recursive: true, force: true });
1184
+ if (vaultsExist) rmSync(DATA_DIR, { recursive: true, force: true });
1112
1185
  if (envExists) rmSync(ENV_PATH, { force: true });
1113
1186
  if (configExists) rmSync(GLOBAL_CONFIG_PATH, { force: true });
1114
1187
  if (logExists) rmSync(LOG_PATH, { force: true });
@@ -1120,7 +1193,7 @@ async function cmdUninstall(argsList: string[]) {
1120
1193
  }
1121
1194
  }
1122
1195
 
1123
- console.log("\nDone. To reinstall: `parachute vault init`.");
1196
+ console.log("\nDone. To reinstall: `parachute-vault init`.");
1124
1197
  }
1125
1198
 
1126
1199
  interface DoctorCheck {
@@ -1139,7 +1212,7 @@ async function cmdDoctor() {
1139
1212
  name: "server-path pointer",
1140
1213
  status: "fail",
1141
1214
  detail: `missing: ${SERVER_PATH_FILE}`,
1142
- fix: "Run `parachute vault init` to create it.",
1215
+ fix: "Run `parachute-vault init` to create it.",
1143
1216
  });
1144
1217
  } else {
1145
1218
  const pointed = readServerPathPointer();
@@ -1148,14 +1221,14 @@ async function cmdDoctor() {
1148
1221
  name: "server-path pointer",
1149
1222
  status: "fail",
1150
1223
  detail: `empty: ${SERVER_PATH_FILE}`,
1151
- fix: "Run `parachute vault init` to rewrite it.",
1224
+ fix: "Run `parachute-vault init` to rewrite it.",
1152
1225
  });
1153
1226
  } else if (!existsSync(pointed)) {
1154
1227
  checks.push({
1155
1228
  name: "server.ts at pointer target",
1156
1229
  status: "fail",
1157
1230
  detail: `points to ${pointed}, which does not exist`,
1158
- fix: "Run `parachute vault init` from the current repo location.",
1231
+ fix: "Run `parachute-vault init` from the current repo location.",
1159
1232
  });
1160
1233
  } else {
1161
1234
  checks.push({
@@ -1173,7 +1246,7 @@ async function cmdDoctor() {
1173
1246
  name: "wrapper script",
1174
1247
  status: "fail",
1175
1248
  detail: `missing: ${WRAPPER_PATH}`,
1176
- fix: "Run `parachute vault init`.",
1249
+ fix: "Run `parachute-vault init`.",
1177
1250
  });
1178
1251
  } else {
1179
1252
  checks.push({ name: "wrapper script", status: "pass", detail: WRAPPER_PATH });
@@ -1186,7 +1259,7 @@ async function cmdDoctor() {
1186
1259
  name: "launchd agent",
1187
1260
  status: loaded ? "pass" : "warn",
1188
1261
  detail: loaded ? "loaded" : "not loaded",
1189
- fix: loaded ? undefined : "Run `parachute vault init` or `parachute vault restart`.",
1262
+ fix: loaded ? undefined : "Run `parachute-vault init` or `parachute-vault restart`.",
1190
1263
  });
1191
1264
  } else if (isSystemdAvailable()) {
1192
1265
  const active = await isServiceActive();
@@ -1194,7 +1267,7 @@ async function cmdDoctor() {
1194
1267
  name: "systemd service",
1195
1268
  status: active ? "pass" : "warn",
1196
1269
  detail: active ? "active" : "not active",
1197
- fix: active ? undefined : "Run `parachute vault init` or `parachute vault restart`.",
1270
+ fix: active ? undefined : "Run `parachute-vault init` or `parachute-vault restart`.",
1198
1271
  });
1199
1272
  }
1200
1273
 
@@ -1225,7 +1298,7 @@ async function cmdDoctor() {
1225
1298
  name: "MCP entry in ~/.claude.json",
1226
1299
  status: "warn",
1227
1300
  detail: mcpEntry.reason,
1228
- fix: "Run `parachute vault mcp-install` to register the vault with Claude.",
1301
+ fix: "Run `parachute-vault mcp-install` to register the vault with Claude.",
1229
1302
  });
1230
1303
  } else {
1231
1304
  checks.push({
@@ -1248,7 +1321,7 @@ async function cmdDoctor() {
1248
1321
  name: "MCP URL port matches vault",
1249
1322
  status: "warn",
1250
1323
  detail: `MCP URL port ${mcpEntry.port ?? "(unparseable)"} ≠ vault port ${port}`,
1251
- fix: "Re-run `parachute vault mcp-install` to refresh the MCP URL.",
1324
+ fix: "Re-run `parachute-vault mcp-install` to refresh the MCP URL.",
1252
1325
  });
1253
1326
  }
1254
1327
 
@@ -1268,7 +1341,7 @@ async function cmdDoctor() {
1268
1341
  name: "MCP URL reachable",
1269
1342
  status: "warn",
1270
1343
  detail: reach.detail,
1271
- fix: "Start the daemon: `parachute vault restart` (or `init` if not yet installed).",
1344
+ fix: "Start the daemon: `parachute-vault restart` (or `init` if not yet installed).",
1272
1345
  });
1273
1346
  }
1274
1347
  }
@@ -1297,7 +1370,7 @@ async function cmdDoctor() {
1297
1370
  name: `port ${port} availability`,
1298
1371
  status: "warn",
1299
1372
  detail: `port in use by non-vault process: ${collision.detail}`,
1300
- fix: "Stop the conflicting process, or set a different PORT in ~/.parachute/.env and re-run `parachute vault init`.",
1373
+ fix: "Stop the conflicting process, or set a different PORT in ~/.parachute/vault/.env and re-run `parachute-vault init`.",
1301
1374
  });
1302
1375
  break;
1303
1376
  case "unknown":
@@ -1319,7 +1392,7 @@ async function cmdDoctor() {
1319
1392
  name: "backup agent",
1320
1393
  status: loaded ? "pass" : "warn",
1321
1394
  detail: loaded ? `loaded (schedule: ${backupCfg.schedule})` : `not loaded (schedule: ${backupCfg.schedule})`,
1322
- fix: loaded ? undefined : `Re-run \`parachute vault backup --schedule ${backupCfg.schedule}\` to reinstall the agent.`,
1395
+ fix: loaded ? undefined : `Re-run \`parachute-vault backup --schedule ${backupCfg.schedule}\` to reinstall the agent.`,
1323
1396
  });
1324
1397
  }
1325
1398
 
@@ -1332,7 +1405,7 @@ async function cmdDoctor() {
1332
1405
  name: "backup destinations",
1333
1406
  status: "warn",
1334
1407
  detail: "schedule is active but no destinations configured",
1335
- fix: "Edit ~/.parachute/config.yaml and add at least one destination under `backup.destinations`.",
1408
+ fix: "Edit ~/.parachute/vault/config.yaml and add at least one destination under `backup.destinations`.",
1336
1409
  });
1337
1410
  } else {
1338
1411
  for (const dest of backupCfg.destinations) {
@@ -1341,7 +1414,7 @@ async function cmdDoctor() {
1341
1414
  name: `backup destination (${dest.kind})`,
1342
1415
  status: res.ok ? "pass" : "warn",
1343
1416
  detail: res.ok ? res.path : `${res.path}: ${res.error}`,
1344
- fix: res.ok ? undefined : "Ensure the path exists and is writable, or update it in ~/.parachute/config.yaml.",
1417
+ fix: res.ok ? undefined : "Ensure the path exists and is writable, or update it in ~/.parachute/vault/config.yaml.",
1345
1418
  });
1346
1419
  }
1347
1420
  }
@@ -1361,12 +1434,12 @@ async function cmdDoctor() {
1361
1434
  const hasWarn = checks.some((c) => c.status === "warn");
1362
1435
  console.log();
1363
1436
  if (hasFailure) {
1364
- console.log("doctor: problems found (exit 1). See `parachute vault status` for runtime details.");
1437
+ console.log("doctor: problems found (exit 1). See `parachute-vault status` for runtime details.");
1365
1438
  process.exit(1);
1366
1439
  } else if (hasWarn) {
1367
- console.log("doctor: warnings only. `parachute vault status` has live runtime detail.");
1440
+ console.log("doctor: warnings only. `parachute-vault status` has live runtime detail.");
1368
1441
  } else {
1369
- console.log("doctor: all checks passed. For live runtime state: `parachute vault status`.");
1442
+ console.log("doctor: all checks passed. For live runtime state: `parachute-vault status`.");
1370
1443
  }
1371
1444
  }
1372
1445
 
@@ -1377,7 +1450,7 @@ function cmdUrl() {
1377
1450
 
1378
1451
  /**
1379
1452
  * Resolve the vault's port the way `status`, `restart`, `url`, and `doctor`
1380
- * all need to agree on: env override (~/.parachute/.env) wins, then
1453
+ * all need to agree on: env override (~/.parachute/vault/.env) wins, then
1381
1454
  * config.yaml, then DEFAULT_PORT. Sources .env as a side effect so callers
1382
1455
  * running this before any env read still see PORT.
1383
1456
  */
@@ -1398,7 +1471,7 @@ type McpEntryLookup =
1398
1471
  /**
1399
1472
  * Read `~/.claude.json` and return the shape of the `parachute-vault` MCP
1400
1473
  * entry if present. The entry is always an HTTP MCP pointing at the local
1401
- * daemon — `{ type: "http", url: "http://127.0.0.1:<port>/vaults/<name>/mcp" }`
1474
+ * daemon — `{ type: "http", url: "http://127.0.0.1:<port>/vault/<name>/mcp" }`
1402
1475
  * — so we parse the URL's port for the port-match check.
1403
1476
  *
1404
1477
  * Invariant: the check is NON-fatal. A missing ~/.claude.json is a warn,
@@ -1573,7 +1646,7 @@ async function describeProcess(pid: number): Promise<string | null> {
1573
1646
  }
1574
1647
 
1575
1648
  // ---------------------------------------------------------------------------
1576
- // Backup — parachute vault backup [--schedule <freq> | status]
1649
+ // Backup — parachute-vault backup [--schedule <freq> | status]
1577
1650
  // ---------------------------------------------------------------------------
1578
1651
 
1579
1652
  async function cmdBackup(args: string[]) {
@@ -1588,7 +1661,7 @@ async function cmdBackup(args: string[]) {
1588
1661
  if (schedFlag !== -1) {
1589
1662
  const raw = args[schedFlag + 1];
1590
1663
  if (!raw) {
1591
- console.error("Usage: parachute vault backup --schedule <hourly|daily|weekly|manual>");
1664
+ console.error("Usage: parachute-vault backup --schedule <hourly|daily|weekly|manual>");
1592
1665
  process.exit(1);
1593
1666
  }
1594
1667
  if (raw !== "hourly" && raw !== "daily" && raw !== "weekly" && raw !== "manual") {
@@ -1606,7 +1679,7 @@ async function cmdBackup(args: string[]) {
1606
1679
  async function cmdBackupRun() {
1607
1680
  const cfg = readGlobalConfig().backup ?? defaultBackupConfig();
1608
1681
  if (cfg.destinations.length === 0) {
1609
- console.error("No backup destinations configured. Edit ~/.parachute/config.yaml:");
1682
+ console.error("No backup destinations configured. Edit ~/.parachute/vault/config.yaml:");
1610
1683
  console.error(" backup:");
1611
1684
  console.error(" destinations:");
1612
1685
  console.error(" - kind: local");
@@ -1648,7 +1721,7 @@ async function cmdBackupSchedule(schedule: BackupSchedule) {
1648
1721
  if (schedule === "manual") {
1649
1722
  await uninstallBackupAgent();
1650
1723
  console.log("Schedule: manual — backup agent removed.");
1651
- console.log("Run `parachute vault backup` to trigger a backup on demand.");
1724
+ console.log("Run `parachute-vault backup` to trigger a backup on demand.");
1652
1725
  return;
1653
1726
  }
1654
1727
 
@@ -1656,7 +1729,7 @@ async function cmdBackupSchedule(schedule: BackupSchedule) {
1656
1729
  console.log(`Schedule set to: ${schedule}`);
1657
1730
  console.log();
1658
1731
  console.log("WARNING: no destinations configured — scheduled runs will fail.");
1659
- console.log("Edit ~/.parachute/config.yaml and add at least one destination under `backup.destinations`.");
1732
+ console.log("Edit ~/.parachute/vault/config.yaml and add at least one destination under `backup.destinations`.");
1660
1733
  }
1661
1734
 
1662
1735
  await installBackupAgent(schedule);
@@ -1759,7 +1832,7 @@ async function cmdImport(args: string[]) {
1759
1832
  sourcePath = positional[0] ?? "";
1760
1833
 
1761
1834
  if (!sourcePath) {
1762
- console.error("Usage: parachute vault import <path> [--vault <name>] [--dry-run]");
1835
+ console.error("Usage: parachute-vault import <path> [--vault <name>] [--dry-run]");
1763
1836
  console.error("\nImports an Obsidian vault into Parachute Vault.");
1764
1837
  console.error("\nOptions:");
1765
1838
  console.error(" --vault <name> Target vault (default: 'default')");
@@ -1778,7 +1851,7 @@ async function cmdImport(args: string[]) {
1778
1851
  // Verify vault exists
1779
1852
  const config = readVaultConfig(vaultName);
1780
1853
  if (!config) {
1781
- console.error(`Vault "${vaultName}" not found. Run: parachute vault create ${vaultName}`);
1854
+ console.error(`Vault "${vaultName}" not found. Run: parachute-vault create ${vaultName}`);
1782
1855
  process.exit(1);
1783
1856
  }
1784
1857
 
@@ -1865,7 +1938,7 @@ async function cmdExport(args: string[]) {
1865
1938
  outputPath = positional[0] ?? "";
1866
1939
 
1867
1940
  if (!outputPath) {
1868
- console.error("Usage: parachute vault export <output-path> [--vault <name>]");
1941
+ console.error("Usage: parachute-vault export <output-path> [--vault <name>]");
1869
1942
  console.error("\nExports a Parachute Vault as Obsidian-compatible markdown files.");
1870
1943
  process.exit(1);
1871
1944
  }
@@ -1945,12 +2018,16 @@ function installMcpConfig(apiKey?: string) {
1945
2018
  }
1946
2019
  }
1947
2020
 
1948
- // Single HTTP MCP entry — use per-vault endpoint so pvt_ tokens work
2021
+ // Single HTTP MCP entry — use per-vault endpoint so pvt_ tokens work.
2022
+ // Pick the URL that matches the OAuth issuer vault will advertise, in this
2023
+ // order: explicit hub origin env > active tailnet/public exposure >
2024
+ // loopback. Otherwise a strict MCP client (Claude Code) hits a loopback URL
2025
+ // whose discovery issuer points at the hub and rejects on origin mismatch
2026
+ // (RFC 8414).
1949
2027
  const defaultVault = globalConfig.default_vault || "default";
1950
- const mcpEntry: Record<string, unknown> = {
1951
- type: "http",
1952
- url: `http://127.0.0.1:${port}/vaults/${defaultVault}/mcp`,
1953
- };
2028
+ const { url: mcpUrl, source } = chooseMcpUrl(defaultVault, port);
2029
+ console.log(`MCP URL: ${mcpUrl} (${source})`);
2030
+ const mcpEntry: Record<string, unknown> = { type: "http", url: mcpUrl };
1954
2031
  if (apiKey) {
1955
2032
  mcpEntry.headers = { Authorization: `Bearer ${apiKey}` };
1956
2033
  }
@@ -1979,58 +2056,76 @@ function usage() {
1979
2056
  console.log(`
1980
2057
  Parachute Vault — self-hosted knowledge graph
1981
2058
 
2059
+ If you installed via the Parachute CLI, prefer the wrapper commands for
2060
+ lifecycle — \`parachute start vault\`, \`parachute stop vault\`,
2061
+ \`parachute status\` — and use the vault-direct commands below for setup,
2062
+ data, and debugging.
2063
+
2064
+ ── Standard use ───────────────────────────────────────────────────────
2065
+
1982
2066
  Setup:
1983
- parachute vault init Set up everything (one command, idempotent)
1984
- parachute vault status Check what's running
1985
- parachute vault doctor Diagnose install/config issues
1986
- parachute vault uninstall [--wipe] [--yes]
2067
+ parachute-vault init [--mcp | --no-mcp] Set up everything (one command, idempotent)
2068
+ parachute-vault doctor Diagnose install/config issues
2069
+ parachute-vault uninstall [--wipe] [--yes]
1987
2070
  Remove daemon + MCP entry; --wipe also removes vaults, .env,
1988
2071
  config.yaml, and daemon logs (vault.log, vault.err).
1989
2072
  --yes skips prompts (DANGEROUS with --wipe: no confirmation).
1990
- parachute vault url Print the local server URL (for scripts)
2073
+ parachute-vault url Print the local server URL (for scripts)
1991
2074
  parachute --version Print the installed version (alias: -v, version)
1992
2075
 
1993
2076
  Vaults:
1994
- parachute vault create <name> Create a new vault
1995
- parachute vault list List all vaults
1996
- parachute vault remove <name> [--yes] Remove a vault
1997
- parachute vault mcp-install Add vault MCP to Claude
2077
+ parachute-vault create <name> Create a new vault
2078
+ parachute-vault list List all vaults
2079
+ parachute-vault remove <name> [--yes] Remove a vault
2080
+ parachute-vault mcp-install Add vault MCP to Claude
1998
2081
 
1999
2082
  Tokens:
2000
- parachute vault tokens List all tokens
2001
- parachute vault tokens create Create a full-access token in the default vault
2002
- parachute vault tokens create --vault <name> Create a token in a specific vault
2003
- parachute vault tokens create --read Read-only token
2004
- parachute vault tokens create --label x Set a label
2005
- parachute vault tokens create --expires 30d Expiring token
2006
- parachute vault tokens revoke <token-id> Revoke a token (default vault)
2083
+ parachute-vault tokens List all tokens
2084
+ parachute-vault tokens create Create a full-access token in the default vault
2085
+ parachute-vault tokens create --vault <name> Create a token in a specific vault
2086
+ parachute-vault tokens create --read Read-only token (shorthand for --scope vault:read)
2087
+ parachute-vault tokens create --scope vault:write
2088
+ Narrow the token's scopes. Accepts a comma-separated
2089
+ list or repeated --scope flags. Valid scopes:
2090
+ vault:read, vault:write, vault:admin.
2091
+ parachute-vault tokens create --label x Set a label
2092
+ parachute-vault tokens create --expires 30d Expiring token
2093
+ parachute-vault tokens revoke <token-id> Revoke a token (default vault)
2007
2094
 
2008
2095
  OAuth:
2009
- parachute vault set-password Set/change the owner password (for consent page)
2010
- parachute vault set-password --clear Remove the owner password
2011
- parachute vault 2fa status Show 2FA state
2012
- parachute vault 2fa enroll Enable TOTP 2FA (QR + backup codes)
2013
- parachute vault 2fa disable Disable 2FA (requires password)
2014
- parachute vault 2fa backup-codes Regenerate backup codes
2096
+ parachute-vault set-password Set/change the owner password (for consent page)
2097
+ parachute-vault set-password --clear Remove the owner password
2098
+ parachute-vault 2fa status Show 2FA state
2099
+ parachute-vault 2fa enroll Enable TOTP 2FA (QR + backup codes)
2100
+ parachute-vault 2fa disable Disable 2FA (requires password)
2101
+ parachute-vault 2fa backup-codes Regenerate backup codes
2015
2102
 
2016
2103
  Config:
2017
- parachute vault config Show current configuration
2018
- parachute vault config set <key> <val> Set a config value
2019
- parachute vault config unset <key> Remove a config value
2104
+ parachute-vault config Show current configuration
2105
+ parachute-vault config set <key> <val> Set a config value
2106
+ parachute-vault config unset <key> Remove a config value
2020
2107
 
2021
2108
  Backup:
2022
- parachute vault backup One-shot backup to configured destinations
2023
- parachute vault backup --schedule <freq> hourly | daily | weekly | manual (macOS launchd)
2024
- parachute vault backup status Show schedule, last run, destinations, next run
2109
+ parachute-vault backup One-shot backup to configured destinations
2110
+ parachute-vault backup --schedule <freq> hourly | daily | weekly | manual (macOS launchd)
2111
+ parachute-vault backup status Show schedule, last run, destinations, next run
2025
2112
 
2026
2113
  Import/Export:
2027
- parachute vault import <path> Import an Obsidian vault
2028
- parachute vault import <path> --dry-run Preview import without writing
2029
- parachute vault export <path> Export vault as Obsidian markdown
2030
-
2031
- Server:
2032
- parachute vault serve Run server (foreground)
2033
- parachute vault logs Stream server logs
2034
- parachute vault restart Restart the daemon
2114
+ parachute-vault import <path> Import an Obsidian vault
2115
+ parachute-vault import <path> --dry-run Preview import without writing
2116
+ parachute-vault export <path> Export vault as Obsidian markdown
2117
+
2118
+ ── Advanced / standalone ──────────────────────────────────────────────
2119
+
2120
+ Direct daemon controls. For normal use, prefer the Parachute CLI wrappers
2121
+ they add PID tracking, log rotation, and cross-service \`parachute status\`
2122
+ visibility. Use these when running vault without the CLI or when debugging.
2123
+
2124
+ parachute-vault serve Run server in the foreground (no PID tracking).
2125
+ Prefer \`parachute start vault\` for managed lifecycle.
2126
+ parachute-vault status Vault-only daemon status.
2127
+ Prefer \`parachute status\` for a cross-service view.
2128
+ parachute-vault logs Stream server logs
2129
+ parachute-vault restart Restart the daemon
2035
2130
  `);
2036
2131
  }