@openparachute/vault 0.6.1 → 0.6.2-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/README.md +6 -6
- package/package.json +1 -1
- package/src/cli.ts +90 -25
- package/src/init-summary.test.ts +125 -125
- package/src/init-summary.ts +89 -54
- package/src/init.test.ts +128 -0
- package/src/mirror-remote-guard.test.ts +269 -0
- package/src/mirror-remote-guard.ts +273 -0
- package/src/mirror-routes.test.ts +313 -0
- package/src/mirror-routes.ts +92 -6
- package/src/vault.test.ts +56 -0
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@ bun install
|
|
|
22
22
|
bun src/cli.ts vault init
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
`vault init` creates a vault,
|
|
25
|
+
`vault init` creates a vault, starts a background daemon (launchd on Mac, systemd on Linux), and prints how to connect your AI — the web setup wizard URL, your vault's connector URL (`http://127.0.0.1:1940/vault/default/mcp`), and a ready-to-paste `claude mcp add` command. It does **not** write any client config for you by default. Pass `--configure-claude-code` if you want init to add the Claude Code MCP entry; pass `--token` if you also need a header-auth API token for non-OAuth clients (Codex, Goose, OpenCode, Cursor, Zed, Cline, scripts, `curl`). OAuth-capable clients just need the connector URL and sign in on first connect.
|
|
26
26
|
|
|
27
27
|
For remote access from Claude Desktop or mobile apps, see [Deployment](#deployment) below.
|
|
28
28
|
|
|
@@ -93,11 +93,11 @@ The daemon binds `0.0.0.0:1940` (or whatever you set in `PORT`) and serves REST,
|
|
|
93
93
|
|
|
94
94
|
### `~/.claude.json`
|
|
95
95
|
|
|
96
|
-
`vault init` adds one entry — `mcpServers["parachute-vault"]` — pointing at `http://127.0.0.1:<port>/vault/<default-vault>/mcp`
|
|
96
|
+
When you opt in (`--configure-claude-code`), `vault init` adds one entry — `mcpServers["parachute-vault"]` — pointing at `http://127.0.0.1:<port>/vault/<default-vault>/mcp`. By default that entry uses OAuth (browser sign-in on first connect); add `--token` and init bakes a scope-narrow `Authorization: Bearer <hub-jwt>` header instead (a hub-minted JWT — vault#282 Stage 2). Next Claude Code session picks it up. See [Connecting a client](#connecting-a-client) for rotating that token or pointing it elsewhere.
|
|
97
97
|
|
|
98
98
|
### Your API token
|
|
99
99
|
|
|
100
|
-
`vault init` asks two explicit questions: (1)
|
|
100
|
+
`vault init` asks two explicit questions: (1) write the Claude Code MCP entry in `~/.claude.json`? (2) also mint + surface a header-auth API token so you can paste it into non-OAuth MCP clients (Codex, Goose, OpenCode, Cursor, Zed, Cline), scripts, or `curl`? **Both default no** — init's job is to get you to the web wizard and print the connector URL + a paste-ready `claude mcp add` command, not to write client config behind your back. Opt in with `--configure-claude-code` (alias `--mcp`) and/or `--token`; `--no-mcp` / `--no-token` are the explicit opt-outs for non-interactive installs.
|
|
101
101
|
|
|
102
102
|
If you said yes to (2), the hub-issued JWT is printed prominently at the end — it's the same token baked into `~/.claude.json` (if you also said yes to (1)). It's not stored anywhere retrievable — save it if you need it for `curl`, cron, or any other script. Lost it? Mint a fresh one with `parachute auth mint-token --scope vault:<name>:<verb>` (or rewire an MCP client with `parachute-vault mcp-install`, or use the admin SPA Tokens page). As of vault 0.5.0 (vault#282 Stage 2) vault no longer mints its own `pvt_*` tokens — minting is the hub's job.
|
|
103
103
|
|
|
@@ -126,7 +126,7 @@ As of 0.5.0 (vault#282 Stage 2) vault is a **pure hub resource-server**: both pa
|
|
|
126
126
|
|
|
127
127
|
### Claude Code
|
|
128
128
|
|
|
129
|
-
`vault init`
|
|
129
|
+
`vault init` does **not** touch `~/.claude.json` by default — connecting is self-serve (paste the `claude mcp add` command init prints, or add the connector in your client). To have init write the entry for you, pass `--configure-claude-code`. The entry it writes uses OAuth by default (browser sign-in on first connect); add `--token` and it bakes a scope-narrow hub-minted JWT instead:
|
|
130
130
|
|
|
131
131
|
```json
|
|
132
132
|
{
|
|
@@ -140,9 +140,9 @@ As of 0.5.0 (vault#282 Stage 2) vault is a **pure hub resource-server**: both pa
|
|
|
140
140
|
}
|
|
141
141
|
```
|
|
142
142
|
|
|
143
|
-
Where `{name}` is `default` on a fresh install, or whatever vault you pointed `vault init` at. **
|
|
143
|
+
Where `{name}` is `default` on a fresh install, or whatever vault you pointed `vault init` at. **With `--configure-claude-code --token`, the first MCP call needs no browser handoff** — Claude Code uses the baked-in token and the vault's tools show up in your next session. Without `--token`, the opted-in entry uses OAuth and Claude Code does a one-time browser sign-in on first connect. This is a deliberate trade: OAuth-first by default (no long-lived token sitting in a dotfile), with the baked-token path one flag away for an owner wiring their own machine.
|
|
144
144
|
|
|
145
|
-
To re-point Claude Code at a different vault, change `default_vault` in `~/.parachute/vault/config.yaml` and re-run `parachute-vault init
|
|
145
|
+
To re-point Claude Code at a different vault, change `default_vault` in `~/.parachute/vault/config.yaml` and re-run `parachute-vault init --configure-claude-code` (add `--token` to bake a fresh token). To rotate the token only, run `parachute-vault mcp-install` (defaults to `--mint`, which mints a fresh scope-narrow hub JWT via `~/.parachute/operator.token` and writes it into `~/.claude.json` with an `Authorization: Bearer …` header). See the [cookbook](#install-vault-mcp-into-a-client-config) section below for the full flag surface — token paste, scope narrowing, project-level install, multi-vault.
|
|
146
146
|
|
|
147
147
|
### Claude Desktop (OAuth)
|
|
148
148
|
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -57,6 +57,7 @@ import {
|
|
|
57
57
|
buildMcpEntryPlan,
|
|
58
58
|
chooseHubOrigin,
|
|
59
59
|
chooseMcpUrl,
|
|
60
|
+
DEFAULT_HUB_LOOPBACK_PORT,
|
|
60
61
|
detectHubPresence,
|
|
61
62
|
detectInstallContext,
|
|
62
63
|
mintHubJwt,
|
|
@@ -258,21 +259,60 @@ switch (command) {
|
|
|
258
259
|
// Command implementations
|
|
259
260
|
// ---------------------------------------------------------------------------
|
|
260
261
|
|
|
262
|
+
/**
|
|
263
|
+
* Resolve the origin to use for the web setup wizard link (`<origin>/admin/setup`).
|
|
264
|
+
*
|
|
265
|
+
* The wizard is served by the HUB, not by vault, so the loopback fallback must
|
|
266
|
+
* target the hub's fixed loopback port (1939 / $PARACHUTE_HUB_PORT) — NOT
|
|
267
|
+
* vault's listen port. `chooseHubOrigin` returns vault's loopback as its
|
|
268
|
+
* fallback, so we only reuse it when a real (env / expose-state) hub origin is
|
|
269
|
+
* configured; otherwise we synthesize the hub's loopback URL.
|
|
270
|
+
*
|
|
271
|
+
* `vaultPort` is vault's listen port — passed only so `chooseHubOrigin`'s
|
|
272
|
+
* loopback branch is well-formed; we discard that loopback URL in favor of the
|
|
273
|
+
* hub-port one.
|
|
274
|
+
*/
|
|
275
|
+
function resolveHubOriginForWizard(vaultPort: number): string {
|
|
276
|
+
const { url, source } = chooseHubOrigin(vaultPort);
|
|
277
|
+
if (source === "loopback") {
|
|
278
|
+
// Guard against a non-numeric PARACHUTE_HUB_PORT producing
|
|
279
|
+
// `http://127.0.0.1:NaN` — mirror detectHubPresence's Number.isFinite guard.
|
|
280
|
+
const envPort = process.env.PARACHUTE_HUB_PORT
|
|
281
|
+
? Number(process.env.PARACHUTE_HUB_PORT)
|
|
282
|
+
: undefined;
|
|
283
|
+
const hubPort = Number.isFinite(envPort) ? (envPort as number) : DEFAULT_HUB_LOOPBACK_PORT;
|
|
284
|
+
return `http://127.0.0.1:${hubPort}`;
|
|
285
|
+
}
|
|
286
|
+
return url;
|
|
287
|
+
}
|
|
288
|
+
|
|
261
289
|
async function cmdInit(args: string[] = []) {
|
|
262
290
|
ensureConfigDirSync();
|
|
263
291
|
|
|
264
|
-
//
|
|
265
|
-
//
|
|
266
|
-
//
|
|
267
|
-
//
|
|
292
|
+
// Writing the Claude Code MCP config (~/.claude.json) is now OPT-IN
|
|
293
|
+
// (2026-06-23). init's primary job is to get the operator to the hub's
|
|
294
|
+
// web setup wizard and SURFACE the self-serve connection info (connector
|
|
295
|
+
// URL + a ready-to-paste `claude mcp add` command), NOT to silently write
|
|
296
|
+
// a config file as a side effect of setup. The site no longer claims
|
|
297
|
+
// "Claude Code is auto-configured," so the install code stops doing it by
|
|
298
|
+
// default.
|
|
299
|
+
//
|
|
300
|
+
// Opt in with --configure-claude-code (aliases --mcp-install, --mcp) to
|
|
301
|
+
// have init write the entry for you. --no-mcp is retained as the explicit
|
|
302
|
+
// "definitely don't" form (and wins if both are passed — safer default).
|
|
303
|
+
// The standalone `parachute-vault mcp-install` command is unchanged — it
|
|
304
|
+
// remains the canonical explicit opt-in path.
|
|
268
305
|
//
|
|
269
|
-
// --token / --no-token
|
|
270
|
-
//
|
|
271
|
-
//
|
|
306
|
+
// --token / --no-token control whether init ALSO mints + surfaces a
|
|
307
|
+
// header-auth API token (for pasting into non-OAuth MCP clients, scripts,
|
|
308
|
+
// or curl). Default stays off.
|
|
272
309
|
//
|
|
273
310
|
// --vault-name <name> skips the name prompt for non-interactive installs
|
|
274
311
|
// (validated up front; exits non-zero on invalid input).
|
|
275
|
-
const flagMcpOn =
|
|
312
|
+
const flagMcpOn =
|
|
313
|
+
args.includes("--configure-claude-code") ||
|
|
314
|
+
args.includes("--mcp-install") ||
|
|
315
|
+
args.includes("--mcp");
|
|
276
316
|
const flagMcpOff = args.includes("--no-mcp");
|
|
277
317
|
const flagTokenOn = args.includes("--token");
|
|
278
318
|
const flagTokenOff = args.includes("--no-token");
|
|
@@ -507,19 +547,28 @@ async function cmdInit(args: string[] = []) {
|
|
|
507
547
|
const bindHost = resolveBindHostname(process.env);
|
|
508
548
|
console.log(` Listening on http://${bindHost}:${globalConfig.port || DEFAULT_PORT}`);
|
|
509
549
|
|
|
510
|
-
// 7.
|
|
511
|
-
//
|
|
512
|
-
//
|
|
513
|
-
//
|
|
550
|
+
// 7. Optionally write the Claude Code MCP config (~/.claude.json). This is
|
|
551
|
+
// OPT-IN as of 2026-06-23 (see the flag-parsing note above). init's job is
|
|
552
|
+
// to point the operator at the web wizard + surface the self-serve connect
|
|
553
|
+
// info; it does NOT write ~/.claude.json by default. Resolution:
|
|
554
|
+
// --no-mcp → false (explicit "don't")
|
|
555
|
+
// --configure-claude-code / --mcp → true (explicit opt-in)
|
|
556
|
+
// TTY, no flag → ask (default NO — opt-in, not -out)
|
|
557
|
+
// non-TTY, no flag → false (no silent side effect in
|
|
558
|
+
// piped installs; the connect info is
|
|
559
|
+
// printed for copy-paste instead)
|
|
514
560
|
let addMcp: boolean;
|
|
515
561
|
if (flagMcpOff) {
|
|
516
562
|
addMcp = false;
|
|
517
563
|
} else if (flagMcpOn) {
|
|
518
564
|
addMcp = true;
|
|
519
565
|
} else if (process.stdin.isTTY) {
|
|
520
|
-
addMcp = await confirm(
|
|
566
|
+
addMcp = await confirm(
|
|
567
|
+
"Also write the Claude Code MCP config now (~/.claude.json)? (you can always copy-paste the command below later)",
|
|
568
|
+
false,
|
|
569
|
+
);
|
|
521
570
|
} else {
|
|
522
|
-
addMcp =
|
|
571
|
+
addMcp = false; // non-interactive: no silent ~/.claude.json write
|
|
523
572
|
}
|
|
524
573
|
|
|
525
574
|
// 7b. Mint an API token for the header-auth / script use case? (Codex,
|
|
@@ -583,14 +632,22 @@ async function cmdInit(args: string[] = []) {
|
|
|
583
632
|
if (!apiKey) {
|
|
584
633
|
console.log(` No token baked in — you'll sign in via OAuth on first connect.`);
|
|
585
634
|
}
|
|
586
|
-
} else {
|
|
587
|
-
console.log(" Skipped adding MCP to ~/.claude.json.");
|
|
588
|
-
console.log(" Run `parachute-vault mcp-install` later if you want it.");
|
|
589
635
|
}
|
|
636
|
+
// No else: when the operator didn't opt in, the init summary below surfaces
|
|
637
|
+
// the connector URL + a copy-paste `claude mcp add` command instead of a
|
|
638
|
+
// "skipped" line — that's the self-serve path.
|
|
590
639
|
|
|
591
640
|
// 8. Summary
|
|
592
641
|
const port = globalConfig.port || DEFAULT_PORT;
|
|
593
|
-
|
|
642
|
+
// Connector URL surfaced for self-serve copy-paste. Hub-origin / expose-state
|
|
643
|
+
// aware (chooseMcpUrl), so it's the URL a remote Claude Code / other client
|
|
644
|
+
// actually reaches, not a bare loopback. The init-summary prints a
|
|
645
|
+
// ready-to-paste `claude mcp add ...` built from this.
|
|
646
|
+
const { url: connectorUrl } = chooseMcpUrl(defaultVault, port);
|
|
647
|
+
// Web setup wizard lives on the hub at <hub-origin>/admin/setup. Resolve the
|
|
648
|
+
// hub origin the same way (env / expose-state / loopback); the loopback form
|
|
649
|
+
// points at the co-located hub's fixed port (1939), not vault's listen port.
|
|
650
|
+
const wizardUrl = `${resolveHubOriginForWizard(port)}/admin/setup`;
|
|
594
651
|
// Probe whether a hub is present so the summary's "opted into a token but
|
|
595
652
|
// none minted" copy reflects reality: under a hub the vault is reachable via
|
|
596
653
|
// browser OAuth even with no header-auth token (#445). Only matters for the
|
|
@@ -603,7 +660,8 @@ async function cmdInit(args: string[] = []) {
|
|
|
603
660
|
configDir: CONFIG_DIR,
|
|
604
661
|
bindHost,
|
|
605
662
|
port,
|
|
606
|
-
mcpUrl,
|
|
663
|
+
mcpUrl: connectorUrl,
|
|
664
|
+
wizardUrl,
|
|
607
665
|
vaultName: defaultVault,
|
|
608
666
|
noTokenGuidance: credentialGuidance,
|
|
609
667
|
hubPresent,
|
|
@@ -3714,12 +3772,19 @@ data, and debugging.
|
|
|
3714
3772
|
── Standard use ───────────────────────────────────────────────────────
|
|
3715
3773
|
|
|
3716
3774
|
Setup:
|
|
3717
|
-
parachute-vault init [--
|
|
3718
|
-
[--autostart|--no-autostart]
|
|
3719
|
-
Set up everything (one command, idempotent).
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3775
|
+
parachute-vault init [--configure-claude-code|--no-mcp] [--token|--no-token]
|
|
3776
|
+
[--vault-name <name>] [--autostart|--no-autostart]
|
|
3777
|
+
Set up everything (one command, idempotent). init's
|
|
3778
|
+
job is to get you to the web setup wizard and surface
|
|
3779
|
+
your connector URL + a ready-to-paste \`claude mcp add\`
|
|
3780
|
+
command — it does NOT write the Claude Code MCP config
|
|
3781
|
+
(~/.claude.json) by default. Pass --configure-claude-code
|
|
3782
|
+
(alias --mcp-install / --mcp) to opt in and have init
|
|
3783
|
+
write that entry for you (per-user OAuth — no baked token;
|
|
3784
|
+
sign in on first connect); --no-mcp is the explicit "don't".
|
|
3785
|
+
The standalone \`parachute-vault mcp-install\` command remains
|
|
3786
|
+
the canonical way to wire Claude Code anytime.
|
|
3787
|
+
--token opts into ALSO minting a scope-narrow
|
|
3723
3788
|
header-auth token (vault:<name>:read) for non-OAuth clients /
|
|
3724
3789
|
scripts; --no-token (the default) skips minting entirely.
|
|
3725
3790
|
--vault-name skips the prompt and names the vault
|
package/src/init-summary.test.ts
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Tests for `buildInitSummaryLines` — the post-install summary printed at the
|
|
3
|
-
* end of `vault init`.
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* end of `vault init`.
|
|
4
|
+
*
|
|
5
|
+
* 2026-06-23 messaging realignment: the site no longer claims "Claude Code is
|
|
6
|
+
* auto-configured," and init no longer writes ~/.claude.json by default. The
|
|
7
|
+
* summary now (1) leads with the web setup wizard hand-off and (2) always
|
|
8
|
+
* surfaces the self-serve connect info — the connector URL plus a
|
|
9
|
+
* ready-to-paste `claude mcp add` command — so a Claude Code user opts in by
|
|
10
|
+
* copy-paste rather than via a silent side effect. These tests pin that copy
|
|
11
|
+
* across the (addMcp, addToken) decision matrix.
|
|
6
12
|
*/
|
|
7
13
|
|
|
8
14
|
import { describe, test, expect } from "bun:test";
|
|
@@ -13,6 +19,7 @@ const baseInput = {
|
|
|
13
19
|
bindHost: "127.0.0.1",
|
|
14
20
|
port: 1940,
|
|
15
21
|
mcpUrl: "http://127.0.0.1:1940/vault/default/mcp",
|
|
22
|
+
wizardUrl: "http://127.0.0.1:1939/admin/setup",
|
|
16
23
|
vaultName: "default",
|
|
17
24
|
};
|
|
18
25
|
|
|
@@ -21,161 +28,154 @@ function lines(addMcp: boolean, addToken: boolean, apiKey: string | undefined) {
|
|
|
21
28
|
}
|
|
22
29
|
|
|
23
30
|
describe("buildInitSummaryLines", () => {
|
|
24
|
-
|
|
25
|
-
|
|
31
|
+
// The wizard hand-off + self-serve connect info are the load-bearing pieces
|
|
32
|
+
// of the new messaging — they must appear in every branch.
|
|
33
|
+
describe("always surfaces the wizard + the copy-paste connect info", () => {
|
|
34
|
+
for (const [addMcp, addToken] of [
|
|
35
|
+
[true, true],
|
|
36
|
+
[true, false],
|
|
37
|
+
[false, true],
|
|
38
|
+
[false, false],
|
|
39
|
+
] as const) {
|
|
40
|
+
const out = lines(addMcp, addToken, addMcp || addToken ? "pvt_k" : undefined).join("\n");
|
|
26
41
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
42
|
+
test(`(addMcp=${addMcp}, addToken=${addToken}) prints the web wizard URL prominently`, () => {
|
|
43
|
+
expect(out).toContain("Finish setup in your browser:");
|
|
44
|
+
expect(out).toContain("http://127.0.0.1:1939/admin/setup");
|
|
45
|
+
});
|
|
30
46
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
47
|
+
test(`(addMcp=${addMcp}, addToken=${addToken}) surfaces the connector URL`, () => {
|
|
48
|
+
expect(out).toContain("http://127.0.0.1:1940/vault/default/mcp");
|
|
49
|
+
});
|
|
34
50
|
|
|
35
|
-
|
|
36
|
-
|
|
51
|
+
test(`(addMcp=${addMcp}, addToken=${addToken}) always prints Config: and Server: lines`, () => {
|
|
52
|
+
expect(out).toContain("Config: /tmp/parachute");
|
|
53
|
+
expect(out).toContain("Server: http://127.0.0.1:1940");
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// The new DEFAULT init path — no MCP write, no token minted (per-user OAuth).
|
|
59
|
+
// init pointed the operator at the wizard and surfaced the copy-paste connect
|
|
60
|
+
// info; it did NOT write ~/.claude.json.
|
|
61
|
+
describe("DEFAULT (addMcp=N, token=N) — wizard hand-off + copy-paste opt-in", () => {
|
|
62
|
+
const out = lines(false, false, undefined).join("\n");
|
|
63
|
+
|
|
64
|
+
test("does NOT claim Claude Code is already wired in", () => {
|
|
65
|
+
expect(out).not.toContain("already wired in");
|
|
66
|
+
expect(out).not.toContain("Baked into ~/.claude.json");
|
|
37
67
|
});
|
|
38
68
|
|
|
39
|
-
test("
|
|
69
|
+
test("offers the ready-to-paste `claude mcp add` opt-in command", () => {
|
|
40
70
|
expect(out).toContain(
|
|
41
|
-
|
|
71
|
+
"claude mcp add --transport http parachute-vault http://127.0.0.1:1940/vault/default/mcp",
|
|
42
72
|
);
|
|
43
73
|
});
|
|
44
74
|
|
|
45
|
-
test("
|
|
46
|
-
expect(out).toContain("
|
|
47
|
-
});
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
describe("MCP=Y + token=N (MCP wired, token not surfaced)", () => {
|
|
51
|
-
const out = lines(true, false, "pvt_secret").join("\n");
|
|
52
|
-
|
|
53
|
-
test("does not print the token prominently", () => {
|
|
54
|
-
expect(out).not.toContain("pvt_secret");
|
|
75
|
+
test("points at the guided installer as an alternative", () => {
|
|
76
|
+
expect(out).toContain("parachute-vault mcp-install");
|
|
55
77
|
});
|
|
56
78
|
|
|
57
|
-
test("
|
|
58
|
-
expect(out).
|
|
79
|
+
test("frames OAuth-first connect — no token needed", () => {
|
|
80
|
+
expect(out).toContain("no token needed, you'll sign in on first use");
|
|
59
81
|
});
|
|
60
82
|
|
|
61
|
-
test("
|
|
62
|
-
|
|
63
|
-
|
|
83
|
+
test("offers the scope-narrow opt-in mint for scripts (full vault:<name>:read, never admin)", () => {
|
|
84
|
+
// Must be the three-segment named-resource form the hub mint-token model
|
|
85
|
+
// requires — a bare `vault:read` would mint a malformed scope (vault#443).
|
|
86
|
+
expect(out).toContain("parachute auth mint-token --scope vault:default:read");
|
|
87
|
+
expect(out).not.toContain("vault:admin");
|
|
64
88
|
});
|
|
65
89
|
|
|
66
|
-
test("
|
|
90
|
+
test("does not print any token", () => {
|
|
91
|
+
expect(out).not.toContain("Your API token:");
|
|
92
|
+
expect(out).not.toMatch(/pvt_/);
|
|
67
93
|
expect(out).not.toContain("Authorization: Bearer");
|
|
68
94
|
});
|
|
69
95
|
|
|
70
|
-
test("
|
|
71
|
-
|
|
96
|
+
test("threads a non-default vault name into the mint-token scope + connector URL", () => {
|
|
97
|
+
const out2 = buildInitSummaryLines({
|
|
98
|
+
...baseInput,
|
|
99
|
+
vaultName: "journal",
|
|
100
|
+
mcpUrl: "http://127.0.0.1:1940/vault/journal/mcp",
|
|
101
|
+
addMcp: false,
|
|
102
|
+
addToken: false,
|
|
103
|
+
apiKey: undefined,
|
|
104
|
+
}).join("\n");
|
|
105
|
+
expect(out2).toContain("parachute auth mint-token --scope vault:journal:read");
|
|
106
|
+
expect(out2).toContain("http://127.0.0.1:1940/vault/journal/mcp");
|
|
72
107
|
});
|
|
73
108
|
});
|
|
74
109
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
test("prints token prominently", () => {
|
|
79
|
-
expect(out).toContain("Your API token: pvt_xyz");
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
test("omits the 'Baked into' bullet (no claude.json entry written)", () => {
|
|
83
|
-
expect(out).not.toContain("Baked into ~/.claude.json");
|
|
84
|
-
});
|
|
110
|
+
// Opt-in: operator passed --configure-claude-code, so init DID write the entry.
|
|
111
|
+
describe("opted into MCP write (addMcp=Y, token=N, OAuth)", () => {
|
|
112
|
+
const out = lines(true, false, undefined).join("\n");
|
|
85
113
|
|
|
86
|
-
test("
|
|
87
|
-
expect(out).toContain(
|
|
114
|
+
test("tells the user Claude Code is already wired in", () => {
|
|
115
|
+
expect(out).toContain("Claude Code is already wired in");
|
|
88
116
|
});
|
|
89
117
|
|
|
90
|
-
test("
|
|
91
|
-
expect(out).toContain("Point any local MCP client");
|
|
118
|
+
test("still surfaces the connector URL for other clients", () => {
|
|
92
119
|
expect(out).toContain("http://127.0.0.1:1940/vault/default/mcp");
|
|
93
120
|
});
|
|
94
121
|
|
|
95
|
-
test("
|
|
96
|
-
expect(out).toContain("
|
|
122
|
+
test("does NOT print or imply any minted token", () => {
|
|
123
|
+
expect(out).not.toContain("Your API token:");
|
|
124
|
+
expect(out).not.toContain("Authorization: Bearer");
|
|
97
125
|
});
|
|
98
126
|
});
|
|
99
127
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
// never surface the old "no token issued" failure copy.
|
|
103
|
-
describe("MCP=Y + no token (vault#442 OAuth default)", () => {
|
|
104
|
-
const out = lines(true, false, undefined).join("\n");
|
|
105
|
-
|
|
106
|
-
test("leads with the OAuth connect message — no token needed", () => {
|
|
107
|
-
expect(out).toContain("no token needed, you'll sign in on first use");
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
test("tells the user Claude Code is already wired in", () => {
|
|
111
|
-
expect(out).toContain("Claude Code is already wired in");
|
|
112
|
-
});
|
|
128
|
+
describe("opted into MCP write + token minted (addMcp=Y, token=Y)", () => {
|
|
129
|
+
const out = lines(true, true, "pvt_abc123").join("\n");
|
|
113
130
|
|
|
114
|
-
test("
|
|
115
|
-
expect(out).toContain(
|
|
116
|
-
"claude mcp add --transport http parachute-vault http://127.0.0.1:1940/vault/default/mcp",
|
|
117
|
-
);
|
|
131
|
+
test("prints token prominently", () => {
|
|
132
|
+
expect(out).toContain("Your API token: pvt_abc123");
|
|
118
133
|
});
|
|
119
134
|
|
|
120
|
-
test("
|
|
121
|
-
|
|
122
|
-
// requires — a bare `vault:read` would mint a malformed scope (vault#443).
|
|
123
|
-
expect(out).toContain("parachute auth mint-token --scope vault:default:read");
|
|
124
|
-
expect(out).not.toContain("--scope vault:read ");
|
|
125
|
-
expect(out).not.toMatch(/--scope vault:read$/m);
|
|
126
|
-
expect(out).not.toContain("vault:admin");
|
|
135
|
+
test("notes token is baked into ~/.claude.json", () => {
|
|
136
|
+
expect(out).toContain("Baked into ~/.claude.json for Claude Code");
|
|
127
137
|
});
|
|
128
138
|
|
|
129
|
-
test("
|
|
130
|
-
expect(out).
|
|
131
|
-
expect(out).not.toContain("Baked into ~/.claude.json");
|
|
132
|
-
expect(out).not.toContain("Authorization: Bearer");
|
|
139
|
+
test("includes save-it-now warning", () => {
|
|
140
|
+
expect(out).toContain("Won't be shown again — save it now.");
|
|
133
141
|
});
|
|
134
142
|
|
|
135
|
-
test("
|
|
136
|
-
expect(out).
|
|
143
|
+
test("includes Bearer curl example", () => {
|
|
144
|
+
expect(out).toContain(
|
|
145
|
+
'curl -H "Authorization: Bearer pvt_abc123" http://localhost:1940/api/notes',
|
|
146
|
+
);
|
|
137
147
|
});
|
|
138
148
|
|
|
139
|
-
test("
|
|
140
|
-
|
|
141
|
-
...baseInput,
|
|
142
|
-
vaultName: "journal",
|
|
143
|
-
mcpUrl: "http://127.0.0.1:1940/vault/journal/mcp",
|
|
144
|
-
addMcp: true,
|
|
145
|
-
addToken: false,
|
|
146
|
-
apiKey: undefined,
|
|
147
|
-
}).join("\n");
|
|
148
|
-
expect(out2).toContain("parachute auth mint-token --scope vault:journal:read");
|
|
149
|
-
expect(out2).not.toContain("vault:default:read");
|
|
149
|
+
test("Next steps mentions starting a Claude Code session", () => {
|
|
150
|
+
expect(out).toContain("Start a new Claude Code session");
|
|
150
151
|
});
|
|
151
152
|
});
|
|
152
153
|
|
|
153
|
-
describe("MCP=N
|
|
154
|
-
const out = lines(false,
|
|
154
|
+
describe("token only, no MCP write (addMcp=N, token=Y, minted)", () => {
|
|
155
|
+
const out = lines(false, true, "pvt_xyz").join("\n");
|
|
155
156
|
|
|
156
|
-
test("
|
|
157
|
-
expect(out).toContain("
|
|
158
|
-
expect(out).not.toContain("your vault isn't reachable by any client");
|
|
157
|
+
test("prints token prominently", () => {
|
|
158
|
+
expect(out).toContain("Your API token: pvt_xyz");
|
|
159
159
|
});
|
|
160
160
|
|
|
161
|
-
test("
|
|
162
|
-
expect(out).toContain("
|
|
163
|
-
expect(out).not.toContain("mints a hub JWT");
|
|
161
|
+
test("omits the 'Baked into' bullet (no claude.json entry written)", () => {
|
|
162
|
+
expect(out).not.toContain("Baked into ~/.claude.json");
|
|
164
163
|
});
|
|
165
164
|
|
|
166
|
-
test("
|
|
167
|
-
expect(out).
|
|
168
|
-
expect(out).not.toMatch(/pvt_/);
|
|
165
|
+
test("includes Bearer curl example", () => {
|
|
166
|
+
expect(out).toContain("Authorization: Bearer pvt_xyz");
|
|
169
167
|
});
|
|
170
168
|
|
|
171
|
-
test("
|
|
172
|
-
expect(out).
|
|
169
|
+
test("surfaces the connector URL + a copy-paste Claude Code opt-in", () => {
|
|
170
|
+
expect(out).toContain("http://127.0.0.1:1940/vault/default/mcp");
|
|
171
|
+
expect(out).toContain(
|
|
172
|
+
"claude mcp add --transport http parachute-vault http://127.0.0.1:1940/vault/default/mcp",
|
|
173
|
+
);
|
|
173
174
|
});
|
|
174
175
|
});
|
|
175
176
|
|
|
176
|
-
// Explicit opt-in but no hub reachable to mint (vault#282 Stage 2
|
|
177
|
-
|
|
178
|
-
describe("MCP=N + token=Y but no hub (opt-in mint failed, standalone)", () => {
|
|
177
|
+
// Explicit opt-in to a token but no hub reachable to mint (vault#282 Stage 2).
|
|
178
|
+
describe("token opt-in but no hub (standalone, mint failed)", () => {
|
|
179
179
|
const out = buildInitSummaryLines({
|
|
180
180
|
...baseInput,
|
|
181
181
|
addMcp: false,
|
|
@@ -198,12 +198,14 @@ describe("buildInitSummaryLines", () => {
|
|
|
198
198
|
test("does NOT claim the vault is reachable (no hub present)", () => {
|
|
199
199
|
expect(out).not.toContain("Your vault is still reachable");
|
|
200
200
|
});
|
|
201
|
+
|
|
202
|
+
test("still surfaces the connector URL for self-serve connect", () => {
|
|
203
|
+
expect(out).toContain("http://127.0.0.1:1940/vault/default/mcp");
|
|
204
|
+
});
|
|
201
205
|
});
|
|
202
206
|
|
|
203
|
-
// #445: opted into a token, none minted, but a HUB IS PRESENT.
|
|
204
|
-
|
|
205
|
-
// so the standalone "isn't reachable" framing would be false here.
|
|
206
|
-
describe("MCP=N + token=Y, no token minted, but hub present (#445)", () => {
|
|
207
|
+
// #445: opted into a token, none minted, but a HUB IS PRESENT.
|
|
208
|
+
describe("token opt-in, none minted, hub present (#445)", () => {
|
|
207
209
|
const out = buildInitSummaryLines({
|
|
208
210
|
...baseInput,
|
|
209
211
|
addMcp: false,
|
|
@@ -227,22 +229,20 @@ describe("buildInitSummaryLines", () => {
|
|
|
227
229
|
expect(out).not.toContain("Once a hub is running");
|
|
228
230
|
expect(out).not.toContain("VAULT_AUTH_TOKEN");
|
|
229
231
|
});
|
|
230
|
-
|
|
231
|
-
test("never claims the vault isn't reachable by any client", () => {
|
|
232
|
-
expect(out).not.toContain("isn't reachable by any client");
|
|
233
|
-
});
|
|
234
232
|
});
|
|
235
233
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
234
|
+
// Defensive: the summary must still render coherently if no wizard URL is
|
|
235
|
+
// supplied (e.g. an older caller / a hub-origin resolution failure).
|
|
236
|
+
test("omits the wizard hand-off cleanly when wizardUrl is absent", () => {
|
|
237
|
+
const out = buildInitSummaryLines({
|
|
238
|
+
...baseInput,
|
|
239
|
+
wizardUrl: undefined,
|
|
240
|
+
addMcp: false,
|
|
241
|
+
addToken: false,
|
|
242
|
+
apiKey: undefined,
|
|
243
|
+
}).join("\n");
|
|
244
|
+
expect(out).not.toContain("Finish setup in your browser:");
|
|
245
|
+
// The connect info is still surfaced.
|
|
246
|
+
expect(out).toContain("http://127.0.0.1:1940/vault/default/mcp");
|
|
247
247
|
});
|
|
248
248
|
});
|