@openparachute/vault 0.2.3 → 0.3.0-rc.1
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/.claude/settings.local.json +8 -0
- package/CHANGELOG.md +70 -0
- package/CLAUDE.md +17 -7
- package/README.md +169 -136
- package/core/src/core.test.ts +603 -19
- package/core/src/indexed-fields.test.ts +285 -0
- package/core/src/indexed-fields.ts +238 -0
- package/core/src/mcp.ts +127 -6
- package/core/src/notes.ts +157 -11
- package/core/src/query-operators.ts +174 -0
- package/core/src/schema.ts +69 -2
- package/core/src/store.ts +92 -0
- package/core/src/tag-schemas.ts +5 -0
- package/core/src/types.ts +29 -1
- package/docs/HTTP_API.md +105 -1
- package/package/package.json +32 -0
- package/package.json +2 -2
- package/src/auth.test.ts +83 -114
- package/src/auth.ts +68 -6
- package/src/backup-launchd.ts +1 -1
- package/src/backup.test.ts +1 -1
- package/src/backup.ts +18 -17
- package/src/cli.ts +179 -121
- package/src/config-triggers.test.ts +49 -0
- package/src/config.test.ts +317 -2
- package/src/config.ts +420 -40
- package/src/context.test.ts +136 -0
- package/src/context.ts +115 -0
- package/src/daemon.ts +17 -16
- package/src/doctor.test.ts +9 -7
- package/src/launchd.test.ts +1 -1
- package/src/launchd.ts +6 -6
- package/src/mcp-http.ts +75 -21
- package/src/mcp-install.test.ts +125 -0
- package/src/mcp-install.ts +60 -0
- package/src/mcp-tools.ts +34 -96
- package/src/module-config.ts +109 -0
- package/src/oauth.test.ts +345 -57
- package/src/oauth.ts +155 -35
- package/src/published.test.ts +2 -2
- package/src/routes.ts +209 -33
- package/src/routing.test.ts +817 -300
- package/src/routing.ts +204 -202
- package/src/scopes.test.ts +136 -0
- package/src/scopes.ts +105 -0
- package/src/scribe-env.test.ts +49 -0
- package/src/scribe-env.ts +33 -0
- package/src/server.ts +57 -5
- package/src/services-manifest.test.ts +140 -0
- package/src/services-manifest.ts +99 -0
- package/src/systemd.ts +3 -3
- package/src/token-store.ts +42 -9
- package/src/transcription-worker.test.ts +583 -0
- package/src/transcription-worker.ts +346 -0
- package/src/triggers.test.ts +191 -1
- package/src/triggers.ts +17 -2
- package/src/vault.test.ts +693 -77
- package/src/version.test.ts +1 -1
package/src/cli.ts
CHANGED
|
@@ -4,16 +4,16 @@
|
|
|
4
4
|
* Parachute Vault CLI.
|
|
5
5
|
*
|
|
6
6
|
* Usage:
|
|
7
|
-
* parachute
|
|
8
|
-
* parachute
|
|
9
|
-
* parachute
|
|
10
|
-
* parachute
|
|
11
|
-
* parachute
|
|
12
|
-
* parachute
|
|
13
|
-
* parachute
|
|
14
|
-
* parachute
|
|
15
|
-
* parachute
|
|
16
|
-
* parachute
|
|
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 {
|
|
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,
|
|
@@ -78,6 +80,7 @@ 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";
|
|
80
82
|
import { getVaultStore } from "./vault-store.ts";
|
|
83
|
+
import { upsertService, ServicesManifestError } from "./services-manifest.ts";
|
|
81
84
|
import {
|
|
82
85
|
hasOwnerPassword,
|
|
83
86
|
setOwnerPassword,
|
|
@@ -103,7 +106,7 @@ import {
|
|
|
103
106
|
|
|
104
107
|
const args = process.argv.slice(2);
|
|
105
108
|
|
|
106
|
-
// Support both `parachute
|
|
109
|
+
// Support both `parachute-vault <cmd>` and `parachute <cmd>` patterns
|
|
107
110
|
let command: string;
|
|
108
111
|
let cmdArgs: string[];
|
|
109
112
|
|
|
@@ -119,6 +122,17 @@ if (args[0] === "vault") {
|
|
|
119
122
|
// Commands
|
|
120
123
|
// ---------------------------------------------------------------------------
|
|
121
124
|
|
|
125
|
+
// Pre-0.3 installs kept vault state directly under ~/.parachute/. Run the
|
|
126
|
+
// migration unconditionally so any command (including read-only ones like
|
|
127
|
+
// `doctor` and `url`) picks up the relocated state on first post-upgrade run.
|
|
128
|
+
// No-op when no legacy paths exist. Skipped for `help` and `version`, which
|
|
129
|
+
// are expected to produce *only* their documented output — and because scripts
|
|
130
|
+
// piping `parachute-vault --version` shouldn't get migration chatter on stderr.
|
|
131
|
+
const SKIP_MIGRATION = new Set(["help", "--help", "-h", "version", "--version", "-v"]);
|
|
132
|
+
if (!SKIP_MIGRATION.has(command)) {
|
|
133
|
+
migrateFromLegacyLayout();
|
|
134
|
+
}
|
|
135
|
+
|
|
122
136
|
switch (command) {
|
|
123
137
|
case "init":
|
|
124
138
|
await cmdInit();
|
|
@@ -188,7 +202,7 @@ switch (command) {
|
|
|
188
202
|
case "--version":
|
|
189
203
|
case "-v":
|
|
190
204
|
// Intentionally minimal — just the version string on stdout. Scripts
|
|
191
|
-
// (and `parachute
|
|
205
|
+
// (and `parachute-vault doctor` in a future check) rely on this being
|
|
192
206
|
// a bare-number line; anything else belongs in `vault status`.
|
|
193
207
|
console.log(pkg.version);
|
|
194
208
|
break;
|
|
@@ -249,6 +263,32 @@ async function cmdInit() {
|
|
|
249
263
|
}
|
|
250
264
|
writeGlobalConfig(globalConfig);
|
|
251
265
|
|
|
266
|
+
// 2a. Register in the shared services manifest so the @openparachute/cli
|
|
267
|
+
// dispatcher can discover this service and its health endpoint. Upserts
|
|
268
|
+
// by name, preserving entries for other services. Non-fatal on failure —
|
|
269
|
+
// init can complete without the manifest, just with a warning.
|
|
270
|
+
//
|
|
271
|
+
// `paths[0]` is the canonical mount point — CLI uses it for the
|
|
272
|
+
// `.well-known/parachute.json` URL and for `parachute expose`. Advertise
|
|
273
|
+
// `/vault/<default_vault>` so MCP clients land at the scoped endpoint.
|
|
274
|
+
// When no default vault exists yet (multi-vault, no fallback), fall back
|
|
275
|
+
// to "/" — the CLI can detect and prompt.
|
|
276
|
+
const servicePath = globalConfig.default_vault
|
|
277
|
+
? `/vault/${globalConfig.default_vault}`
|
|
278
|
+
: "/";
|
|
279
|
+
try {
|
|
280
|
+
upsertService({
|
|
281
|
+
name: "parachute-vault",
|
|
282
|
+
port: globalConfig.port || DEFAULT_PORT,
|
|
283
|
+
paths: [servicePath],
|
|
284
|
+
health: "/health",
|
|
285
|
+
version: pkg.version,
|
|
286
|
+
});
|
|
287
|
+
} catch (err) {
|
|
288
|
+
const msg = err instanceof ServicesManifestError ? err.message : String(err);
|
|
289
|
+
console.log(` Warning: could not update ~/.parachute/services.json: ${msg}`);
|
|
290
|
+
}
|
|
291
|
+
|
|
252
292
|
// 2b. Migrate existing legacy keys into per-vault token tables
|
|
253
293
|
for (const v of listVaults()) {
|
|
254
294
|
try {
|
|
@@ -297,7 +337,7 @@ async function cmdInit() {
|
|
|
297
337
|
}
|
|
298
338
|
if (serverPath) {
|
|
299
339
|
console.log(` Server path: ${serverPath}`);
|
|
300
|
-
console.log(` Wrapper: ~/.parachute/start.sh`);
|
|
340
|
+
console.log(` Wrapper: ~/.parachute/vault/start.sh`);
|
|
301
341
|
}
|
|
302
342
|
console.log(` Listening on http://0.0.0.0:${globalConfig.port || DEFAULT_PORT}`);
|
|
303
343
|
|
|
@@ -326,8 +366,8 @@ async function cmdInit() {
|
|
|
326
366
|
}
|
|
327
367
|
|
|
328
368
|
console.log(`\nNext steps:`);
|
|
329
|
-
console.log(` parachute
|
|
330
|
-
console.log(` parachute
|
|
369
|
+
console.log(` parachute-vault status check everything is running`);
|
|
370
|
+
console.log(` parachute-vault config view/edit configuration`);
|
|
331
371
|
}
|
|
332
372
|
|
|
333
373
|
async function promptForOwnerPassword(purpose: string): Promise<boolean> {
|
|
@@ -339,7 +379,7 @@ async function promptForOwnerPassword(purpose: string): Promise<boolean> {
|
|
|
339
379
|
while (true) {
|
|
340
380
|
const pw = await askPassword(" Password (or leave blank to skip)");
|
|
341
381
|
if (!pw) {
|
|
342
|
-
console.log(" Skipped — you can set one later with `parachute
|
|
382
|
+
console.log(" Skipped — you can set one later with `parachute-vault set-password`.");
|
|
343
383
|
return false;
|
|
344
384
|
}
|
|
345
385
|
|
|
@@ -391,13 +431,13 @@ async function cmdSetPassword(args: string[]) {
|
|
|
391
431
|
}
|
|
392
432
|
|
|
393
433
|
// ---------------------------------------------------------------------------
|
|
394
|
-
// 2FA — parachute
|
|
434
|
+
// 2FA — parachute-vault 2fa [enroll | disable | backup-codes | status]
|
|
395
435
|
// ---------------------------------------------------------------------------
|
|
396
436
|
|
|
397
437
|
async function confirmOwnerPassword(purpose: string): Promise<boolean> {
|
|
398
438
|
const hash = getOwnerPasswordHash();
|
|
399
439
|
if (!hash) {
|
|
400
|
-
console.error("No owner password is set. Run: parachute
|
|
440
|
+
console.error("No owner password is set. Run: parachute-vault set-password");
|
|
401
441
|
return false;
|
|
402
442
|
}
|
|
403
443
|
console.log(purpose);
|
|
@@ -460,14 +500,14 @@ async function cmd2fa(args: string[]) {
|
|
|
460
500
|
console.log(`2FA: enabled (${getBackupCodeCount()} backup code(s) remaining)`);
|
|
461
501
|
} else {
|
|
462
502
|
console.log("2FA: not enabled");
|
|
463
|
-
console.log(" Enable with: parachute
|
|
503
|
+
console.log(" Enable with: parachute-vault 2fa enroll");
|
|
464
504
|
}
|
|
465
505
|
return;
|
|
466
506
|
}
|
|
467
507
|
|
|
468
508
|
if (sub === "enroll") {
|
|
469
509
|
if (!hasOwnerPassword()) {
|
|
470
|
-
console.error("Set an owner password first: parachute
|
|
510
|
+
console.error("Set an owner password first: parachute-vault set-password");
|
|
471
511
|
process.exit(1);
|
|
472
512
|
}
|
|
473
513
|
if (hasTotpEnrolled()) {
|
|
@@ -511,7 +551,7 @@ async function cmd2fa(args: string[]) {
|
|
|
511
551
|
console.log(` Incorrect code. (${2 - attempt} attempt(s) left)`);
|
|
512
552
|
}
|
|
513
553
|
if (!confirmed) {
|
|
514
|
-
console.error("Enrollment failed — rolling back. Re-run `parachute
|
|
554
|
+
console.error("Enrollment failed — rolling back. Re-run `parachute-vault 2fa enroll` to try again.");
|
|
515
555
|
disableTotp();
|
|
516
556
|
process.exit(1);
|
|
517
557
|
}
|
|
@@ -539,7 +579,7 @@ async function cmd2fa(args: string[]) {
|
|
|
539
579
|
|
|
540
580
|
if (sub === "backup-codes") {
|
|
541
581
|
if (!hasTotpEnrolled()) {
|
|
542
|
-
console.error("2FA is not enabled. Run: parachute
|
|
582
|
+
console.error("2FA is not enabled. Run: parachute-vault 2fa enroll");
|
|
543
583
|
process.exit(1);
|
|
544
584
|
}
|
|
545
585
|
if (!(await confirmForTwoFactor("Confirm ownership to regenerate backup codes:"))) {
|
|
@@ -554,14 +594,14 @@ async function cmd2fa(args: string[]) {
|
|
|
554
594
|
}
|
|
555
595
|
|
|
556
596
|
console.error(`Unknown 2fa command: ${sub}`);
|
|
557
|
-
console.error("Usage: parachute
|
|
597
|
+
console.error("Usage: parachute-vault 2fa [status | enroll | disable | backup-codes]");
|
|
558
598
|
process.exit(1);
|
|
559
599
|
}
|
|
560
600
|
|
|
561
601
|
function cmdCreate(args: string[]) {
|
|
562
602
|
const name = args[0];
|
|
563
603
|
if (!name) {
|
|
564
|
-
console.error("Usage: parachute
|
|
604
|
+
console.error("Usage: parachute-vault create <name>");
|
|
565
605
|
process.exit(1);
|
|
566
606
|
}
|
|
567
607
|
|
|
@@ -570,9 +610,9 @@ function cmdCreate(args: string[]) {
|
|
|
570
610
|
process.exit(1);
|
|
571
611
|
}
|
|
572
612
|
if (name === "list") {
|
|
573
|
-
// Reserved —
|
|
574
|
-
//
|
|
575
|
-
//
|
|
613
|
+
// Reserved — keeps the "list" vault name out of play even though per-vault
|
|
614
|
+
// routes now live under /vault/<name>/ and no longer collide with the
|
|
615
|
+
// /vaults/list discovery endpoint.
|
|
576
616
|
console.error(`"list" is a reserved vault name.`);
|
|
577
617
|
process.exit(1);
|
|
578
618
|
}
|
|
@@ -610,13 +650,13 @@ function cmdCreate(args: string[]) {
|
|
|
610
650
|
console.log(` ${defaultNote}`);
|
|
611
651
|
}
|
|
612
652
|
console.log();
|
|
613
|
-
console.log(`To add MCP to Claude: parachute
|
|
653
|
+
console.log(`To add MCP to Claude: parachute-vault mcp-install ${name}`);
|
|
614
654
|
}
|
|
615
655
|
|
|
616
656
|
function cmdList() {
|
|
617
657
|
const vaults = listVaults();
|
|
618
658
|
if (vaults.length === 0) {
|
|
619
|
-
console.log("No vaults. Run: parachute
|
|
659
|
+
console.log("No vaults. Run: parachute-vault init");
|
|
620
660
|
return;
|
|
621
661
|
}
|
|
622
662
|
|
|
@@ -637,7 +677,7 @@ function cmdMcpInstall(_args: string[]) {
|
|
|
637
677
|
function cmdRemove(args: string[]) {
|
|
638
678
|
const name = args[0];
|
|
639
679
|
if (!name) {
|
|
640
|
-
console.error("Usage: parachute
|
|
680
|
+
console.error("Usage: parachute-vault remove <name>");
|
|
641
681
|
process.exit(1);
|
|
642
682
|
}
|
|
643
683
|
|
|
@@ -651,7 +691,7 @@ function cmdRemove(args: string[]) {
|
|
|
651
691
|
if (!force) {
|
|
652
692
|
console.log(`This will permanently delete vault "${name}" and all its data.`);
|
|
653
693
|
console.log(` Path: ${vaultDir(name)}`);
|
|
654
|
-
console.log(`\nTo confirm: parachute
|
|
694
|
+
console.log(`\nTo confirm: parachute-vault remove ${name} --yes`);
|
|
655
695
|
return;
|
|
656
696
|
}
|
|
657
697
|
|
|
@@ -680,7 +720,7 @@ function cmdRemove(args: string[]) {
|
|
|
680
720
|
async function cmdConfig(args: string[]) {
|
|
681
721
|
const subcmd = args[0];
|
|
682
722
|
|
|
683
|
-
// parachute
|
|
723
|
+
// parachute-vault config — show current config
|
|
684
724
|
if (!subcmd) {
|
|
685
725
|
loadEnvFile();
|
|
686
726
|
const env = readEnvFile();
|
|
@@ -693,7 +733,7 @@ async function cmdConfig(args: string[]) {
|
|
|
693
733
|
console.log();
|
|
694
734
|
|
|
695
735
|
if (Object.keys(env).length === 0) {
|
|
696
|
-
console.log(" No env vars set. Use: parachute
|
|
736
|
+
console.log(" No env vars set. Use: parachute-vault config set <key> <value>");
|
|
697
737
|
} else {
|
|
698
738
|
for (const [key, val] of Object.entries(env)) {
|
|
699
739
|
// Mask sensitive values
|
|
@@ -707,46 +747,46 @@ async function cmdConfig(args: string[]) {
|
|
|
707
747
|
return;
|
|
708
748
|
}
|
|
709
749
|
|
|
710
|
-
// parachute
|
|
750
|
+
// parachute-vault config set <key> <value>
|
|
711
751
|
if (subcmd === "set") {
|
|
712
752
|
const key = args[1];
|
|
713
753
|
const value = args.slice(2).join(" ");
|
|
714
754
|
if (!key || !value) {
|
|
715
|
-
console.error("Usage: parachute
|
|
755
|
+
console.error("Usage: parachute-vault config set <key> <value>");
|
|
716
756
|
process.exit(1);
|
|
717
757
|
}
|
|
718
758
|
setEnvVar(key, value);
|
|
719
759
|
console.log(`Set ${key}=${key.includes("KEY") ? value.slice(0, 8) + "..." : value}`);
|
|
720
|
-
console.log("Restart the daemon to apply: parachute
|
|
760
|
+
console.log("Restart the daemon to apply: parachute-vault restart");
|
|
721
761
|
return;
|
|
722
762
|
}
|
|
723
763
|
|
|
724
|
-
// parachute
|
|
764
|
+
// parachute-vault config unset <key>
|
|
725
765
|
if (subcmd === "unset") {
|
|
726
766
|
const key = args[1];
|
|
727
767
|
if (!key) {
|
|
728
|
-
console.error("Usage: parachute
|
|
768
|
+
console.error("Usage: parachute-vault config unset <key>");
|
|
729
769
|
process.exit(1);
|
|
730
770
|
}
|
|
731
771
|
unsetEnvVar(key);
|
|
732
772
|
console.log(`Removed ${key}`);
|
|
733
|
-
console.log("Restart the daemon to apply: parachute
|
|
773
|
+
console.log("Restart the daemon to apply: parachute-vault restart");
|
|
734
774
|
return;
|
|
735
775
|
}
|
|
736
776
|
|
|
737
777
|
console.error(`Unknown config command: ${subcmd}`);
|
|
738
|
-
console.error("Usage: parachute
|
|
778
|
+
console.error("Usage: parachute-vault config [set <key> <value> | unset <key>]");
|
|
739
779
|
process.exit(1);
|
|
740
780
|
}
|
|
741
781
|
|
|
742
782
|
// ---------------------------------------------------------------------------
|
|
743
|
-
// Tokens — parachute
|
|
783
|
+
// Tokens — parachute-vault tokens [create | list | revoke]
|
|
744
784
|
// ---------------------------------------------------------------------------
|
|
745
785
|
|
|
746
786
|
function cmdTokens(args: string[]) {
|
|
747
787
|
const subcmd = args[0];
|
|
748
788
|
|
|
749
|
-
// parachute
|
|
789
|
+
// parachute-vault tokens — list all tokens (across all vaults)
|
|
750
790
|
if (!subcmd || subcmd === "list") {
|
|
751
791
|
const vaults = listVaults();
|
|
752
792
|
let anyTokens = false;
|
|
@@ -773,12 +813,12 @@ function cmdTokens(args: string[]) {
|
|
|
773
813
|
}
|
|
774
814
|
|
|
775
815
|
if (!anyTokens) {
|
|
776
|
-
console.log("No tokens found. Create one: parachute
|
|
816
|
+
console.log("No tokens found. Create one: parachute-vault tokens create");
|
|
777
817
|
}
|
|
778
818
|
return;
|
|
779
819
|
}
|
|
780
820
|
|
|
781
|
-
// parachute
|
|
821
|
+
// parachute-vault tokens create --vault <name> [--permission full|read]
|
|
782
822
|
// [--expires <duration>] [--label <label>]
|
|
783
823
|
if (subcmd === "create") {
|
|
784
824
|
const vaultFlag = args.indexOf("--vault");
|
|
@@ -832,11 +872,11 @@ function cmdTokens(args: string[]) {
|
|
|
832
872
|
return;
|
|
833
873
|
}
|
|
834
874
|
|
|
835
|
-
// parachute
|
|
875
|
+
// parachute-vault tokens revoke <token-id> --vault <name>
|
|
836
876
|
if (subcmd === "revoke") {
|
|
837
877
|
const tokenId = args[1];
|
|
838
878
|
if (!tokenId) {
|
|
839
|
-
console.error("Usage: parachute
|
|
879
|
+
console.error("Usage: parachute-vault tokens revoke <token-id> --vault <name>");
|
|
840
880
|
process.exit(1);
|
|
841
881
|
}
|
|
842
882
|
|
|
@@ -860,7 +900,7 @@ function cmdTokens(args: string[]) {
|
|
|
860
900
|
}
|
|
861
901
|
|
|
862
902
|
console.error(`Unknown tokens command: ${subcmd}`);
|
|
863
|
-
console.error("Usage: parachute
|
|
903
|
+
console.error("Usage: parachute-vault tokens [create | list | revoke <id>]");
|
|
864
904
|
process.exit(1);
|
|
865
905
|
}
|
|
866
906
|
|
|
@@ -1029,7 +1069,7 @@ async function cmdUninstall(argsList: string[]) {
|
|
|
1029
1069
|
if (wipe) {
|
|
1030
1070
|
console.log("`--wipe` will ALSO remove vaults, .env, config.yaml, and daemon logs.\n");
|
|
1031
1071
|
} else {
|
|
1032
|
-
console.log("User data (~/.parachute/
|
|
1072
|
+
console.log("User data (~/.parachute/vault/) is left alone.\n");
|
|
1033
1073
|
}
|
|
1034
1074
|
|
|
1035
1075
|
// Scripted `--yes --wipe` bypasses both interactive confirms. That's the
|
|
@@ -1039,7 +1079,7 @@ async function cmdUninstall(argsList: string[]) {
|
|
|
1039
1079
|
// miss this; interactive users already see the prompts.
|
|
1040
1080
|
if (skipPrompts && wipe) {
|
|
1041
1081
|
const ts = new Date().toISOString();
|
|
1042
|
-
const targets = [
|
|
1082
|
+
const targets = [DATA_DIR, ENV_PATH, GLOBAL_CONFIG_PATH, LOG_PATH, ERR_PATH].join(", ");
|
|
1043
1083
|
console.log(`[${ts}] scripted destructive wipe: ${targets}`);
|
|
1044
1084
|
}
|
|
1045
1085
|
|
|
@@ -1083,7 +1123,7 @@ async function cmdUninstall(argsList: string[]) {
|
|
|
1083
1123
|
// Inventory what's actually on disk. Paths that don't exist are a
|
|
1084
1124
|
// silent no-op on removal, but we also skip listing them so the
|
|
1085
1125
|
// "would be removed" summary doesn't lie to the user.
|
|
1086
|
-
const vaultsExist = existsSync(
|
|
1126
|
+
const vaultsExist = existsSync(DATA_DIR);
|
|
1087
1127
|
const envExists = existsSync(ENV_PATH);
|
|
1088
1128
|
const configExists = existsSync(GLOBAL_CONFIG_PATH);
|
|
1089
1129
|
const logExists = existsSync(LOG_PATH);
|
|
@@ -1094,7 +1134,7 @@ async function cmdUninstall(argsList: string[]) {
|
|
|
1094
1134
|
console.log("No user data to remove.");
|
|
1095
1135
|
} else {
|
|
1096
1136
|
console.log("\nUser data that would be removed:");
|
|
1097
|
-
if (vaultsExist) console.log(` ${
|
|
1137
|
+
if (vaultsExist) console.log(` ${DATA_DIR} (per-vault SQLite data)`);
|
|
1098
1138
|
if (envExists) console.log(` ${ENV_PATH} (.env config + secrets)`);
|
|
1099
1139
|
if (configExists) console.log(` ${GLOBAL_CONFIG_PATH} (global config)`);
|
|
1100
1140
|
if (logExists) console.log(` ${LOG_PATH} (daemon log)`);
|
|
@@ -1108,7 +1148,7 @@ async function cmdUninstall(argsList: string[]) {
|
|
|
1108
1148
|
doWipe = await confirm("Delete this data? (cannot be undone)", false);
|
|
1109
1149
|
}
|
|
1110
1150
|
if (doWipe) {
|
|
1111
|
-
if (vaultsExist) rmSync(
|
|
1151
|
+
if (vaultsExist) rmSync(DATA_DIR, { recursive: true, force: true });
|
|
1112
1152
|
if (envExists) rmSync(ENV_PATH, { force: true });
|
|
1113
1153
|
if (configExists) rmSync(GLOBAL_CONFIG_PATH, { force: true });
|
|
1114
1154
|
if (logExists) rmSync(LOG_PATH, { force: true });
|
|
@@ -1120,7 +1160,7 @@ async function cmdUninstall(argsList: string[]) {
|
|
|
1120
1160
|
}
|
|
1121
1161
|
}
|
|
1122
1162
|
|
|
1123
|
-
console.log("\nDone. To reinstall: `parachute
|
|
1163
|
+
console.log("\nDone. To reinstall: `parachute-vault init`.");
|
|
1124
1164
|
}
|
|
1125
1165
|
|
|
1126
1166
|
interface DoctorCheck {
|
|
@@ -1139,7 +1179,7 @@ async function cmdDoctor() {
|
|
|
1139
1179
|
name: "server-path pointer",
|
|
1140
1180
|
status: "fail",
|
|
1141
1181
|
detail: `missing: ${SERVER_PATH_FILE}`,
|
|
1142
|
-
fix: "Run `parachute
|
|
1182
|
+
fix: "Run `parachute-vault init` to create it.",
|
|
1143
1183
|
});
|
|
1144
1184
|
} else {
|
|
1145
1185
|
const pointed = readServerPathPointer();
|
|
@@ -1148,14 +1188,14 @@ async function cmdDoctor() {
|
|
|
1148
1188
|
name: "server-path pointer",
|
|
1149
1189
|
status: "fail",
|
|
1150
1190
|
detail: `empty: ${SERVER_PATH_FILE}`,
|
|
1151
|
-
fix: "Run `parachute
|
|
1191
|
+
fix: "Run `parachute-vault init` to rewrite it.",
|
|
1152
1192
|
});
|
|
1153
1193
|
} else if (!existsSync(pointed)) {
|
|
1154
1194
|
checks.push({
|
|
1155
1195
|
name: "server.ts at pointer target",
|
|
1156
1196
|
status: "fail",
|
|
1157
1197
|
detail: `points to ${pointed}, which does not exist`,
|
|
1158
|
-
fix: "Run `parachute
|
|
1198
|
+
fix: "Run `parachute-vault init` from the current repo location.",
|
|
1159
1199
|
});
|
|
1160
1200
|
} else {
|
|
1161
1201
|
checks.push({
|
|
@@ -1173,7 +1213,7 @@ async function cmdDoctor() {
|
|
|
1173
1213
|
name: "wrapper script",
|
|
1174
1214
|
status: "fail",
|
|
1175
1215
|
detail: `missing: ${WRAPPER_PATH}`,
|
|
1176
|
-
fix: "Run `parachute
|
|
1216
|
+
fix: "Run `parachute-vault init`.",
|
|
1177
1217
|
});
|
|
1178
1218
|
} else {
|
|
1179
1219
|
checks.push({ name: "wrapper script", status: "pass", detail: WRAPPER_PATH });
|
|
@@ -1186,7 +1226,7 @@ async function cmdDoctor() {
|
|
|
1186
1226
|
name: "launchd agent",
|
|
1187
1227
|
status: loaded ? "pass" : "warn",
|
|
1188
1228
|
detail: loaded ? "loaded" : "not loaded",
|
|
1189
|
-
fix: loaded ? undefined : "Run `parachute
|
|
1229
|
+
fix: loaded ? undefined : "Run `parachute-vault init` or `parachute-vault restart`.",
|
|
1190
1230
|
});
|
|
1191
1231
|
} else if (isSystemdAvailable()) {
|
|
1192
1232
|
const active = await isServiceActive();
|
|
@@ -1194,7 +1234,7 @@ async function cmdDoctor() {
|
|
|
1194
1234
|
name: "systemd service",
|
|
1195
1235
|
status: active ? "pass" : "warn",
|
|
1196
1236
|
detail: active ? "active" : "not active",
|
|
1197
|
-
fix: active ? undefined : "Run `parachute
|
|
1237
|
+
fix: active ? undefined : "Run `parachute-vault init` or `parachute-vault restart`.",
|
|
1198
1238
|
});
|
|
1199
1239
|
}
|
|
1200
1240
|
|
|
@@ -1225,7 +1265,7 @@ async function cmdDoctor() {
|
|
|
1225
1265
|
name: "MCP entry in ~/.claude.json",
|
|
1226
1266
|
status: "warn",
|
|
1227
1267
|
detail: mcpEntry.reason,
|
|
1228
|
-
fix: "Run `parachute
|
|
1268
|
+
fix: "Run `parachute-vault mcp-install` to register the vault with Claude.",
|
|
1229
1269
|
});
|
|
1230
1270
|
} else {
|
|
1231
1271
|
checks.push({
|
|
@@ -1248,7 +1288,7 @@ async function cmdDoctor() {
|
|
|
1248
1288
|
name: "MCP URL port matches vault",
|
|
1249
1289
|
status: "warn",
|
|
1250
1290
|
detail: `MCP URL port ${mcpEntry.port ?? "(unparseable)"} ≠ vault port ${port}`,
|
|
1251
|
-
fix: "Re-run `parachute
|
|
1291
|
+
fix: "Re-run `parachute-vault mcp-install` to refresh the MCP URL.",
|
|
1252
1292
|
});
|
|
1253
1293
|
}
|
|
1254
1294
|
|
|
@@ -1268,7 +1308,7 @@ async function cmdDoctor() {
|
|
|
1268
1308
|
name: "MCP URL reachable",
|
|
1269
1309
|
status: "warn",
|
|
1270
1310
|
detail: reach.detail,
|
|
1271
|
-
fix: "Start the daemon: `parachute
|
|
1311
|
+
fix: "Start the daemon: `parachute-vault restart` (or `init` if not yet installed).",
|
|
1272
1312
|
});
|
|
1273
1313
|
}
|
|
1274
1314
|
}
|
|
@@ -1297,7 +1337,7 @@ async function cmdDoctor() {
|
|
|
1297
1337
|
name: `port ${port} availability`,
|
|
1298
1338
|
status: "warn",
|
|
1299
1339
|
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
|
|
1340
|
+
fix: "Stop the conflicting process, or set a different PORT in ~/.parachute/vault/.env and re-run `parachute-vault init`.",
|
|
1301
1341
|
});
|
|
1302
1342
|
break;
|
|
1303
1343
|
case "unknown":
|
|
@@ -1319,7 +1359,7 @@ async function cmdDoctor() {
|
|
|
1319
1359
|
name: "backup agent",
|
|
1320
1360
|
status: loaded ? "pass" : "warn",
|
|
1321
1361
|
detail: loaded ? `loaded (schedule: ${backupCfg.schedule})` : `not loaded (schedule: ${backupCfg.schedule})`,
|
|
1322
|
-
fix: loaded ? undefined : `Re-run \`parachute
|
|
1362
|
+
fix: loaded ? undefined : `Re-run \`parachute-vault backup --schedule ${backupCfg.schedule}\` to reinstall the agent.`,
|
|
1323
1363
|
});
|
|
1324
1364
|
}
|
|
1325
1365
|
|
|
@@ -1332,7 +1372,7 @@ async function cmdDoctor() {
|
|
|
1332
1372
|
name: "backup destinations",
|
|
1333
1373
|
status: "warn",
|
|
1334
1374
|
detail: "schedule is active but no destinations configured",
|
|
1335
|
-
fix: "Edit ~/.parachute/config.yaml and add at least one destination under `backup.destinations`.",
|
|
1375
|
+
fix: "Edit ~/.parachute/vault/config.yaml and add at least one destination under `backup.destinations`.",
|
|
1336
1376
|
});
|
|
1337
1377
|
} else {
|
|
1338
1378
|
for (const dest of backupCfg.destinations) {
|
|
@@ -1341,7 +1381,7 @@ async function cmdDoctor() {
|
|
|
1341
1381
|
name: `backup destination (${dest.kind})`,
|
|
1342
1382
|
status: res.ok ? "pass" : "warn",
|
|
1343
1383
|
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.",
|
|
1384
|
+
fix: res.ok ? undefined : "Ensure the path exists and is writable, or update it in ~/.parachute/vault/config.yaml.",
|
|
1345
1385
|
});
|
|
1346
1386
|
}
|
|
1347
1387
|
}
|
|
@@ -1361,12 +1401,12 @@ async function cmdDoctor() {
|
|
|
1361
1401
|
const hasWarn = checks.some((c) => c.status === "warn");
|
|
1362
1402
|
console.log();
|
|
1363
1403
|
if (hasFailure) {
|
|
1364
|
-
console.log("doctor: problems found (exit 1). See `parachute
|
|
1404
|
+
console.log("doctor: problems found (exit 1). See `parachute-vault status` for runtime details.");
|
|
1365
1405
|
process.exit(1);
|
|
1366
1406
|
} else if (hasWarn) {
|
|
1367
|
-
console.log("doctor: warnings only. `parachute
|
|
1407
|
+
console.log("doctor: warnings only. `parachute-vault status` has live runtime detail.");
|
|
1368
1408
|
} else {
|
|
1369
|
-
console.log("doctor: all checks passed. For live runtime state: `parachute
|
|
1409
|
+
console.log("doctor: all checks passed. For live runtime state: `parachute-vault status`.");
|
|
1370
1410
|
}
|
|
1371
1411
|
}
|
|
1372
1412
|
|
|
@@ -1377,7 +1417,7 @@ function cmdUrl() {
|
|
|
1377
1417
|
|
|
1378
1418
|
/**
|
|
1379
1419
|
* Resolve the vault's port the way `status`, `restart`, `url`, and `doctor`
|
|
1380
|
-
* all need to agree on: env override (~/.parachute/.env) wins, then
|
|
1420
|
+
* all need to agree on: env override (~/.parachute/vault/.env) wins, then
|
|
1381
1421
|
* config.yaml, then DEFAULT_PORT. Sources .env as a side effect so callers
|
|
1382
1422
|
* running this before any env read still see PORT.
|
|
1383
1423
|
*/
|
|
@@ -1398,7 +1438,7 @@ type McpEntryLookup =
|
|
|
1398
1438
|
/**
|
|
1399
1439
|
* Read `~/.claude.json` and return the shape of the `parachute-vault` MCP
|
|
1400
1440
|
* 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>/
|
|
1441
|
+
* daemon — `{ type: "http", url: "http://127.0.0.1:<port>/vault/<name>/mcp" }`
|
|
1402
1442
|
* — so we parse the URL's port for the port-match check.
|
|
1403
1443
|
*
|
|
1404
1444
|
* Invariant: the check is NON-fatal. A missing ~/.claude.json is a warn,
|
|
@@ -1573,7 +1613,7 @@ async function describeProcess(pid: number): Promise<string | null> {
|
|
|
1573
1613
|
}
|
|
1574
1614
|
|
|
1575
1615
|
// ---------------------------------------------------------------------------
|
|
1576
|
-
// Backup — parachute
|
|
1616
|
+
// Backup — parachute-vault backup [--schedule <freq> | status]
|
|
1577
1617
|
// ---------------------------------------------------------------------------
|
|
1578
1618
|
|
|
1579
1619
|
async function cmdBackup(args: string[]) {
|
|
@@ -1588,7 +1628,7 @@ async function cmdBackup(args: string[]) {
|
|
|
1588
1628
|
if (schedFlag !== -1) {
|
|
1589
1629
|
const raw = args[schedFlag + 1];
|
|
1590
1630
|
if (!raw) {
|
|
1591
|
-
console.error("Usage: parachute
|
|
1631
|
+
console.error("Usage: parachute-vault backup --schedule <hourly|daily|weekly|manual>");
|
|
1592
1632
|
process.exit(1);
|
|
1593
1633
|
}
|
|
1594
1634
|
if (raw !== "hourly" && raw !== "daily" && raw !== "weekly" && raw !== "manual") {
|
|
@@ -1606,7 +1646,7 @@ async function cmdBackup(args: string[]) {
|
|
|
1606
1646
|
async function cmdBackupRun() {
|
|
1607
1647
|
const cfg = readGlobalConfig().backup ?? defaultBackupConfig();
|
|
1608
1648
|
if (cfg.destinations.length === 0) {
|
|
1609
|
-
console.error("No backup destinations configured. Edit ~/.parachute/config.yaml:");
|
|
1649
|
+
console.error("No backup destinations configured. Edit ~/.parachute/vault/config.yaml:");
|
|
1610
1650
|
console.error(" backup:");
|
|
1611
1651
|
console.error(" destinations:");
|
|
1612
1652
|
console.error(" - kind: local");
|
|
@@ -1648,7 +1688,7 @@ async function cmdBackupSchedule(schedule: BackupSchedule) {
|
|
|
1648
1688
|
if (schedule === "manual") {
|
|
1649
1689
|
await uninstallBackupAgent();
|
|
1650
1690
|
console.log("Schedule: manual — backup agent removed.");
|
|
1651
|
-
console.log("Run `parachute
|
|
1691
|
+
console.log("Run `parachute-vault backup` to trigger a backup on demand.");
|
|
1652
1692
|
return;
|
|
1653
1693
|
}
|
|
1654
1694
|
|
|
@@ -1656,7 +1696,7 @@ async function cmdBackupSchedule(schedule: BackupSchedule) {
|
|
|
1656
1696
|
console.log(`Schedule set to: ${schedule}`);
|
|
1657
1697
|
console.log();
|
|
1658
1698
|
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`.");
|
|
1699
|
+
console.log("Edit ~/.parachute/vault/config.yaml and add at least one destination under `backup.destinations`.");
|
|
1660
1700
|
}
|
|
1661
1701
|
|
|
1662
1702
|
await installBackupAgent(schedule);
|
|
@@ -1759,7 +1799,7 @@ async function cmdImport(args: string[]) {
|
|
|
1759
1799
|
sourcePath = positional[0] ?? "";
|
|
1760
1800
|
|
|
1761
1801
|
if (!sourcePath) {
|
|
1762
|
-
console.error("Usage: parachute
|
|
1802
|
+
console.error("Usage: parachute-vault import <path> [--vault <name>] [--dry-run]");
|
|
1763
1803
|
console.error("\nImports an Obsidian vault into Parachute Vault.");
|
|
1764
1804
|
console.error("\nOptions:");
|
|
1765
1805
|
console.error(" --vault <name> Target vault (default: 'default')");
|
|
@@ -1778,7 +1818,7 @@ async function cmdImport(args: string[]) {
|
|
|
1778
1818
|
// Verify vault exists
|
|
1779
1819
|
const config = readVaultConfig(vaultName);
|
|
1780
1820
|
if (!config) {
|
|
1781
|
-
console.error(`Vault "${vaultName}" not found. Run: parachute
|
|
1821
|
+
console.error(`Vault "${vaultName}" not found. Run: parachute-vault create ${vaultName}`);
|
|
1782
1822
|
process.exit(1);
|
|
1783
1823
|
}
|
|
1784
1824
|
|
|
@@ -1865,7 +1905,7 @@ async function cmdExport(args: string[]) {
|
|
|
1865
1905
|
outputPath = positional[0] ?? "";
|
|
1866
1906
|
|
|
1867
1907
|
if (!outputPath) {
|
|
1868
|
-
console.error("Usage: parachute
|
|
1908
|
+
console.error("Usage: parachute-vault export <output-path> [--vault <name>]");
|
|
1869
1909
|
console.error("\nExports a Parachute Vault as Obsidian-compatible markdown files.");
|
|
1870
1910
|
process.exit(1);
|
|
1871
1911
|
}
|
|
@@ -1945,12 +1985,16 @@ function installMcpConfig(apiKey?: string) {
|
|
|
1945
1985
|
}
|
|
1946
1986
|
}
|
|
1947
1987
|
|
|
1948
|
-
// Single HTTP MCP entry — use per-vault endpoint so pvt_ tokens work
|
|
1988
|
+
// Single HTTP MCP entry — use per-vault endpoint so pvt_ tokens work.
|
|
1989
|
+
// Pick the URL that matches the OAuth issuer vault will advertise, in this
|
|
1990
|
+
// order: explicit hub origin env > active tailnet/public exposure >
|
|
1991
|
+
// loopback. Otherwise a strict MCP client (Claude Code) hits a loopback URL
|
|
1992
|
+
// whose discovery issuer points at the hub and rejects on origin mismatch
|
|
1993
|
+
// (RFC 8414).
|
|
1949
1994
|
const defaultVault = globalConfig.default_vault || "default";
|
|
1950
|
-
const
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
};
|
|
1995
|
+
const { url: mcpUrl, source } = chooseMcpUrl(defaultVault, port);
|
|
1996
|
+
console.log(`MCP URL: ${mcpUrl} (${source})`);
|
|
1997
|
+
const mcpEntry: Record<string, unknown> = { type: "http", url: mcpUrl };
|
|
1954
1998
|
if (apiKey) {
|
|
1955
1999
|
mcpEntry.headers = { Authorization: `Bearer ${apiKey}` };
|
|
1956
2000
|
}
|
|
@@ -1979,58 +2023,72 @@ function usage() {
|
|
|
1979
2023
|
console.log(`
|
|
1980
2024
|
Parachute Vault — self-hosted knowledge graph
|
|
1981
2025
|
|
|
2026
|
+
If you installed via the Parachute CLI, prefer the wrapper commands for
|
|
2027
|
+
lifecycle — \`parachute start vault\`, \`parachute stop vault\`,
|
|
2028
|
+
\`parachute status\` — and use the vault-direct commands below for setup,
|
|
2029
|
+
data, and debugging.
|
|
2030
|
+
|
|
2031
|
+
── Standard use ───────────────────────────────────────────────────────
|
|
2032
|
+
|
|
1982
2033
|
Setup:
|
|
1983
|
-
parachute
|
|
1984
|
-
parachute
|
|
1985
|
-
parachute
|
|
1986
|
-
parachute vault uninstall [--wipe] [--yes]
|
|
2034
|
+
parachute-vault init Set up everything (one command, idempotent)
|
|
2035
|
+
parachute-vault doctor Diagnose install/config issues
|
|
2036
|
+
parachute-vault uninstall [--wipe] [--yes]
|
|
1987
2037
|
Remove daemon + MCP entry; --wipe also removes vaults, .env,
|
|
1988
2038
|
config.yaml, and daemon logs (vault.log, vault.err).
|
|
1989
2039
|
--yes skips prompts (DANGEROUS with --wipe: no confirmation).
|
|
1990
|
-
parachute
|
|
2040
|
+
parachute-vault url Print the local server URL (for scripts)
|
|
1991
2041
|
parachute --version Print the installed version (alias: -v, version)
|
|
1992
2042
|
|
|
1993
2043
|
Vaults:
|
|
1994
|
-
parachute
|
|
1995
|
-
parachute
|
|
1996
|
-
parachute
|
|
1997
|
-
parachute
|
|
2044
|
+
parachute-vault create <name> Create a new vault
|
|
2045
|
+
parachute-vault list List all vaults
|
|
2046
|
+
parachute-vault remove <name> [--yes] Remove a vault
|
|
2047
|
+
parachute-vault mcp-install Add vault MCP to Claude
|
|
1998
2048
|
|
|
1999
2049
|
Tokens:
|
|
2000
|
-
parachute
|
|
2001
|
-
parachute
|
|
2002
|
-
parachute
|
|
2003
|
-
parachute
|
|
2004
|
-
parachute
|
|
2005
|
-
parachute
|
|
2006
|
-
parachute
|
|
2050
|
+
parachute-vault tokens List all tokens
|
|
2051
|
+
parachute-vault tokens create Create a full-access token in the default vault
|
|
2052
|
+
parachute-vault tokens create --vault <name> Create a token in a specific vault
|
|
2053
|
+
parachute-vault tokens create --read Read-only token
|
|
2054
|
+
parachute-vault tokens create --label x Set a label
|
|
2055
|
+
parachute-vault tokens create --expires 30d Expiring token
|
|
2056
|
+
parachute-vault tokens revoke <token-id> Revoke a token (default vault)
|
|
2007
2057
|
|
|
2008
2058
|
OAuth:
|
|
2009
|
-
parachute
|
|
2010
|
-
parachute
|
|
2011
|
-
parachute
|
|
2012
|
-
parachute
|
|
2013
|
-
parachute
|
|
2014
|
-
parachute
|
|
2059
|
+
parachute-vault set-password Set/change the owner password (for consent page)
|
|
2060
|
+
parachute-vault set-password --clear Remove the owner password
|
|
2061
|
+
parachute-vault 2fa status Show 2FA state
|
|
2062
|
+
parachute-vault 2fa enroll Enable TOTP 2FA (QR + backup codes)
|
|
2063
|
+
parachute-vault 2fa disable Disable 2FA (requires password)
|
|
2064
|
+
parachute-vault 2fa backup-codes Regenerate backup codes
|
|
2015
2065
|
|
|
2016
2066
|
Config:
|
|
2017
|
-
parachute
|
|
2018
|
-
parachute
|
|
2019
|
-
parachute
|
|
2067
|
+
parachute-vault config Show current configuration
|
|
2068
|
+
parachute-vault config set <key> <val> Set a config value
|
|
2069
|
+
parachute-vault config unset <key> Remove a config value
|
|
2020
2070
|
|
|
2021
2071
|
Backup:
|
|
2022
|
-
parachute
|
|
2023
|
-
parachute
|
|
2024
|
-
parachute
|
|
2072
|
+
parachute-vault backup One-shot backup to configured destinations
|
|
2073
|
+
parachute-vault backup --schedule <freq> hourly | daily | weekly | manual (macOS launchd)
|
|
2074
|
+
parachute-vault backup status Show schedule, last run, destinations, next run
|
|
2025
2075
|
|
|
2026
2076
|
Import/Export:
|
|
2027
|
-
parachute
|
|
2028
|
-
parachute
|
|
2029
|
-
parachute
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2077
|
+
parachute-vault import <path> Import an Obsidian vault
|
|
2078
|
+
parachute-vault import <path> --dry-run Preview import without writing
|
|
2079
|
+
parachute-vault export <path> Export vault as Obsidian markdown
|
|
2080
|
+
|
|
2081
|
+
── Advanced / standalone ──────────────────────────────────────────────
|
|
2082
|
+
|
|
2083
|
+
Direct daemon controls. For normal use, prefer the Parachute CLI wrappers
|
|
2084
|
+
— they add PID tracking, log rotation, and cross-service \`parachute status\`
|
|
2085
|
+
visibility. Use these when running vault without the CLI or when debugging.
|
|
2086
|
+
|
|
2087
|
+
parachute-vault serve Run server in the foreground (no PID tracking).
|
|
2088
|
+
Prefer \`parachute start vault\` for managed lifecycle.
|
|
2089
|
+
parachute-vault status Vault-only daemon status.
|
|
2090
|
+
Prefer \`parachute status\` for a cross-service view.
|
|
2091
|
+
parachute-vault logs Stream server logs
|
|
2092
|
+
parachute-vault restart Restart the daemon
|
|
2035
2093
|
`);
|
|
2036
2094
|
}
|