@openparachute/vault 0.5.2-rc.1 → 0.5.2-rc.3
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/package.json +1 -1
- package/src/cli.ts +167 -65
- package/src/init-summary.test.ts +77 -5
- package/src/init-summary.ts +37 -19
- package/src/vault-create.test.ts +104 -9
- package/web/ui/dist/assets/{index-BKYNb2II.js → index-C4Sth1Tq.js} +11 -11
- package/web/ui/dist/index.html +1 -1
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -56,6 +56,7 @@ import {
|
|
|
56
56
|
buildMcpConfigJson,
|
|
57
57
|
buildMcpEntryPlan,
|
|
58
58
|
chooseHubOrigin,
|
|
59
|
+
chooseMcpUrl,
|
|
59
60
|
detectInstallContext,
|
|
60
61
|
mintHubJwt,
|
|
61
62
|
readOperatorToken,
|
|
@@ -478,11 +479,16 @@ async function cmdInit(args: string[] = []) {
|
|
|
478
479
|
addMcp = true; // non-interactive: preserve the installable-via-pipe default
|
|
479
480
|
}
|
|
480
481
|
|
|
481
|
-
// 7b.
|
|
482
|
-
// Cursor, Zed, Cline, scripts, curl.)
|
|
483
|
-
//
|
|
484
|
-
//
|
|
485
|
-
//
|
|
482
|
+
// 7b. Mint an API token for the header-auth / script use case? (Codex,
|
|
483
|
+
// Goose, OpenCode, Cursor, Zed, Cline, scripts, curl.)
|
|
484
|
+
//
|
|
485
|
+
// vault#442: default vault auth is per-user OAuth — the Claude Code MCP entry
|
|
486
|
+
// is written WITHOUT a baked bearer, so the first connection does browser
|
|
487
|
+
// sign-in. We mint a token ONLY when the operator explicitly opts in
|
|
488
|
+
// (`--token`, or "yes" at the prompt), and then it's scope-narrow
|
|
489
|
+
// (`vault:<name>:read`), NEVER admin. `--no-token` (and the non-interactive
|
|
490
|
+
// default) skips minting entirely — no auto-mint, no noisy mint-failure on a
|
|
491
|
+
// fresh vault.
|
|
486
492
|
let addToken: boolean;
|
|
487
493
|
if (flagTokenOff) {
|
|
488
494
|
addToken = false;
|
|
@@ -490,25 +496,22 @@ async function cmdInit(args: string[] = []) {
|
|
|
490
496
|
addToken = true;
|
|
491
497
|
} else if (process.stdin.isTTY) {
|
|
492
498
|
addToken = await confirm(
|
|
493
|
-
"
|
|
494
|
-
|
|
499
|
+
"Also mint a header-auth API token for non-OAuth clients / scripts (Codex, Goose, curl)? (OAuth works without one)",
|
|
500
|
+
false,
|
|
495
501
|
);
|
|
496
502
|
} else {
|
|
497
|
-
addToken =
|
|
503
|
+
addToken = false; // non-interactive default: OAuth-first, no auto-mint
|
|
498
504
|
}
|
|
499
505
|
|
|
500
|
-
// Mint a token
|
|
501
|
-
//
|
|
502
|
-
//
|
|
503
|
-
//
|
|
504
|
-
//
|
|
505
|
-
//
|
|
506
|
-
// summary — the operator runs `mcp-install` once a hub is up, or sets
|
|
507
|
-
// VAULT_AUTH_TOKEN.
|
|
506
|
+
// Mint a scope-narrow token ONLY when explicitly opted in and we don't
|
|
507
|
+
// already have one from vault creation. vault#282 Stage 2: vault no longer
|
|
508
|
+
// mints pvt_* tokens — this is a hub JWT via the operator.token → hub
|
|
509
|
+
// mint-token path (`mintBootstrapCredential`), scoped `vault:<name>:read`.
|
|
510
|
+
// When no hub is reachable, `apiKey` stays undefined and we carry the
|
|
511
|
+
// guidance to the summary. The default (OAuth) path never reaches here.
|
|
508
512
|
const defaultVault = globalConfig.default_vault || "default";
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
const credential = await mintBootstrapCredential(defaultVault);
|
|
513
|
+
if (addToken && !apiKey) {
|
|
514
|
+
const credential = await mintBootstrapCredential(defaultVault, "read");
|
|
512
515
|
apiKey = credential.token ?? undefined;
|
|
513
516
|
credentialGuidance = credential.guidance;
|
|
514
517
|
if (!apiKey) console.log(` ${credential.guidance}`);
|
|
@@ -517,10 +520,9 @@ async function cmdInit(args: string[] = []) {
|
|
|
517
520
|
if (addMcp) {
|
|
518
521
|
// Goes through `buildMcpEntryPlan` for entryKey + url so this path shares
|
|
519
522
|
// the writer-side invariant with `executeMcpInstall` — a future URL-shape
|
|
520
|
-
// change can't drift between init and mcp-install.
|
|
521
|
-
//
|
|
522
|
-
//
|
|
523
|
-
// hub is up).
|
|
523
|
+
// change can't drift between init and mcp-install. By default NO bearer is
|
|
524
|
+
// baked (vault#442 — OAuth on first connect); a bearer is embedded only
|
|
525
|
+
// when the operator explicitly opted into a scope-narrow token above.
|
|
524
526
|
const target = resolveInstallTarget("user");
|
|
525
527
|
const { entryKey, url, source } = buildMcpEntryPlan({
|
|
526
528
|
vaultName: defaultVault,
|
|
@@ -535,6 +537,9 @@ async function cmdInit(args: string[] = []) {
|
|
|
535
537
|
});
|
|
536
538
|
console.log(`MCP URL: ${url} (${source})`);
|
|
537
539
|
console.log(` MCP server added to ~/.claude.json`);
|
|
540
|
+
if (!apiKey) {
|
|
541
|
+
console.log(` No token baked in — you'll sign in via OAuth on first connect.`);
|
|
542
|
+
}
|
|
538
543
|
} else {
|
|
539
544
|
console.log(" Skipped adding MCP to ~/.claude.json.");
|
|
540
545
|
console.log(" Run `parachute-vault mcp-install` later if you want it.");
|
|
@@ -551,6 +556,7 @@ async function cmdInit(args: string[] = []) {
|
|
|
551
556
|
bindHost,
|
|
552
557
|
port,
|
|
553
558
|
mcpUrl,
|
|
559
|
+
vaultName: defaultVault,
|
|
554
560
|
noTokenGuidance: credentialGuidance,
|
|
555
561
|
});
|
|
556
562
|
for (const line of lines) console.log(line);
|
|
@@ -825,12 +831,53 @@ async function cmdCreate(args: string[]) {
|
|
|
825
831
|
// even when the server-wide `default_mirror` knob is `internal`. Parity for
|
|
826
832
|
// operators who want one bare vault without flipping the global default.
|
|
827
833
|
const noMirror = args.includes("--no-mirror");
|
|
828
|
-
|
|
829
|
-
//
|
|
830
|
-
//
|
|
831
|
-
//
|
|
832
|
-
//
|
|
833
|
-
|
|
834
|
+
|
|
835
|
+
// --- Auth opt-in (vault#442). Default = per-user OAuth, NO token minted. ---
|
|
836
|
+
// `--mint` opts into a scope-narrow hub JWT for the header-auth / script
|
|
837
|
+
// use case; `--scope read|write` (default read) picks the verb. `:admin` is
|
|
838
|
+
// intentionally NOT accepted from the create flow. `--token <bearer>` is the
|
|
839
|
+
// paste path — use an existing bearer instead of minting. The two are
|
|
840
|
+
// mutually exclusive.
|
|
841
|
+
const wantMint = args.includes("--mint");
|
|
842
|
+
const createTokenArg = takeArgValue(args, "--token");
|
|
843
|
+
if (createTokenArg.missingValue) {
|
|
844
|
+
console.error("--token requires a value (the bearer token to embed).");
|
|
845
|
+
process.exit(1);
|
|
846
|
+
}
|
|
847
|
+
const pastedToken = createTokenArg.value;
|
|
848
|
+
if (wantMint && pastedToken !== undefined) {
|
|
849
|
+
console.error("--mint and --token are mutually exclusive.");
|
|
850
|
+
process.exit(1);
|
|
851
|
+
}
|
|
852
|
+
const createScopeArg = takeArgValue(args, "--scope");
|
|
853
|
+
if (createScopeArg.missingValue) {
|
|
854
|
+
console.error("--scope requires a value: read or write.");
|
|
855
|
+
process.exit(1);
|
|
856
|
+
}
|
|
857
|
+
const rawCreateVerb = createScopeArg.value ?? "read";
|
|
858
|
+
if (rawCreateVerb !== "read" && rawCreateVerb !== "write") {
|
|
859
|
+
console.error(
|
|
860
|
+
`--scope must be "read" or "write" for create (admin is minted out-of-band via \`mcp-install --scope vault:admin\`). Got: ${rawCreateVerb}.`,
|
|
861
|
+
);
|
|
862
|
+
process.exit(1);
|
|
863
|
+
}
|
|
864
|
+
const mintVerb: "read" | "write" | undefined = wantMint ? rawCreateVerb : undefined;
|
|
865
|
+
|
|
866
|
+
// Greedy strip of any `--*` token (and any `--flag value` pairs we consumed
|
|
867
|
+
// above) to recover the positional vault name. `--json`, `--no-mirror`,
|
|
868
|
+
// `--mint`, `--token <v>`, `--scope <v>` are recognized; any other `--foo` is
|
|
869
|
+
// silently dropped, and the value following a recognized value-flag is
|
|
870
|
+
// skipped so it can't be mistaken for the vault name.
|
|
871
|
+
const VALUE_FLAGS = new Set(["--token", "--scope"]);
|
|
872
|
+
const positional: string[] = [];
|
|
873
|
+
for (let i = 0; i < args.length; i++) {
|
|
874
|
+
const a = args[i]!;
|
|
875
|
+
if (a.startsWith("--")) {
|
|
876
|
+
if (VALUE_FLAGS.has(a)) i++; // skip the flag's value
|
|
877
|
+
continue;
|
|
878
|
+
}
|
|
879
|
+
positional.push(a);
|
|
880
|
+
}
|
|
834
881
|
const name = positional[0];
|
|
835
882
|
if (!name) {
|
|
836
883
|
console.error("Usage: parachute-vault create <name> [--json]");
|
|
@@ -863,7 +910,18 @@ async function cmdCreate(args: string[]) {
|
|
|
863
910
|
|
|
864
911
|
ensureConfigDirSync();
|
|
865
912
|
const wasFirst = listVaults().length === 0;
|
|
866
|
-
const credential = await createVault(name,
|
|
913
|
+
const credential = await createVault(name, {
|
|
914
|
+
...(noMirror ? { enableMirror: false } : {}),
|
|
915
|
+
...(mintVerb ? { mintVerb } : {}),
|
|
916
|
+
});
|
|
917
|
+
|
|
918
|
+
// `--token <bearer>` paste path: the operator supplied their own bearer
|
|
919
|
+
// instead of minting one. Surface it as the credential token so the
|
|
920
|
+
// downstream JSON / human / summary copy treats it the same as a minted one.
|
|
921
|
+
const effectiveToken = pastedToken ?? credential.token;
|
|
922
|
+
const effectiveGuidance = pastedToken
|
|
923
|
+
? "Using the bearer you supplied via --token."
|
|
924
|
+
: credential.guidance;
|
|
867
925
|
|
|
868
926
|
// If this is the only vault now, make it the default so unscoped routes
|
|
869
927
|
// (/mcp, /api/*, /oauth/*) target it. Avoids the "single vault named
|
|
@@ -897,17 +955,19 @@ async function cmdCreate(args: string[]) {
|
|
|
897
955
|
log: () => {}, // CLI create has its own status lines.
|
|
898
956
|
});
|
|
899
957
|
|
|
958
|
+
const endpoint = chooseMcpUrl(name, globalConfig.port || DEFAULT_PORT).url;
|
|
959
|
+
|
|
900
960
|
if (jsonMode) {
|
|
901
961
|
// Contract (hub's admin-vaults.ts requires `typeof token === "string"`):
|
|
902
|
-
// emit
|
|
903
|
-
//
|
|
904
|
-
//
|
|
905
|
-
//
|
|
906
|
-
//
|
|
962
|
+
// emit a token string when one was minted/pasted. vault#442 default is
|
|
963
|
+
// per-user OAuth — no token is minted — so `token` is the empty string and
|
|
964
|
+
// `token_guidance` carries the OAuth-first connect path. (Hub's admin SPA
|
|
965
|
+
// already handles the empty-token case + has its own session-cookie admin
|
|
966
|
+
// mint path, so it doesn't depend on create minting a token.)
|
|
907
967
|
const payload = {
|
|
908
968
|
name,
|
|
909
|
-
token:
|
|
910
|
-
token_guidance:
|
|
969
|
+
token: effectiveToken ?? "",
|
|
970
|
+
token_guidance: effectiveGuidance,
|
|
911
971
|
paths: {
|
|
912
972
|
vault_dir: vaultDir(name),
|
|
913
973
|
vault_db: vaultDbPath(name),
|
|
@@ -921,18 +981,20 @@ async function cmdCreate(args: string[]) {
|
|
|
921
981
|
|
|
922
982
|
console.log(`Vault "${name}" created.`);
|
|
923
983
|
console.log(` Path: ${vaultDir(name)}`);
|
|
924
|
-
if (
|
|
925
|
-
console.log(` API token: ${
|
|
926
|
-
console.log(` ${
|
|
984
|
+
if (effectiveToken) {
|
|
985
|
+
console.log(` API token: ${effectiveToken}`);
|
|
986
|
+
console.log(` ${effectiveGuidance}`);
|
|
927
987
|
console.log(` Save this — it will not be shown again.`);
|
|
928
988
|
} else {
|
|
929
|
-
console.log(` ${
|
|
989
|
+
console.log(` ${effectiveGuidance}`);
|
|
930
990
|
}
|
|
931
991
|
if (defaultNote) {
|
|
932
992
|
console.log(` ${defaultNote}`);
|
|
933
993
|
}
|
|
934
994
|
console.log();
|
|
935
|
-
console.log(`
|
|
995
|
+
console.log(`Connect your AI: claude mcp add --transport http parachute-${name} ${endpoint}`);
|
|
996
|
+
console.log(` (no token needed — you'll sign in on first use)`);
|
|
997
|
+
console.log(`Need a header-auth token for a script? parachute auth mint-token --scope vault:${name}:read`);
|
|
936
998
|
}
|
|
937
999
|
|
|
938
1000
|
function cmdList() {
|
|
@@ -3274,30 +3336,40 @@ async function firstChangedNoteTitle(
|
|
|
3274
3336
|
/**
|
|
3275
3337
|
* Outcome of bootstrapping a fresh vault's first credential (vault#282 Stage 2).
|
|
3276
3338
|
*
|
|
3277
|
-
* Vault no longer mints `pvt_*` tokens.
|
|
3278
|
-
* a hub-issued JWT
|
|
3279
|
-
*
|
|
3280
|
-
*
|
|
3281
|
-
*
|
|
3339
|
+
* Vault no longer mints `pvt_*` tokens. When a token IS minted (explicit opt-in
|
|
3340
|
+
* only — vault#442), it's a hub-issued JWT scoped narrow (`vault:<name>:read`
|
|
3341
|
+
* or `:write`, NEVER `:admin`), minted via the same operator.token → hub
|
|
3342
|
+
* mint-token path `mcp-install --mint` uses (cli.ts ~`cmdMcpInstall`). When no
|
|
3343
|
+
* hub is reachable (standalone install, no operator.token, or no real hub
|
|
3344
|
+
* origin), `token` is null and `guidance` carries the operator's next step.
|
|
3282
3345
|
*/
|
|
3283
3346
|
interface VaultCredential {
|
|
3284
|
-
/** Hub-issued JWT scoped
|
|
3347
|
+
/** Hub-issued JWT scoped narrow (read/write), or null when not minted / no hub reachable. */
|
|
3285
3348
|
token: string | null;
|
|
3286
3349
|
/** Human-readable note: how the token was issued, or why it wasn't. */
|
|
3287
3350
|
guidance: string;
|
|
3288
3351
|
}
|
|
3289
3352
|
|
|
3290
3353
|
/**
|
|
3291
|
-
* Mint
|
|
3354
|
+
* Mint a scope-narrow credential for a vault (explicit opt-in — vault#442).
|
|
3355
|
+
*
|
|
3356
|
+
* Default vault auth is per-user OAuth (browser sign-in on first MCP connect);
|
|
3357
|
+
* tokens are only for the header-auth / script use case and are minted ONLY
|
|
3358
|
+
* when explicitly requested. Decision (vault#442): the create/init flow NEVER
|
|
3359
|
+
* auto-mints, and when a token IS requested it's scope-narrow — `verb` is
|
|
3360
|
+
* `read` (default) or `write`, NEVER `admin`. (Admin tokens, when truly
|
|
3361
|
+
* needed, are minted out-of-band via `mcp-install --scope vault:admin` against
|
|
3362
|
+
* a hub running hub#449, or the hub admin SPA's own session-cookie path.)
|
|
3292
3363
|
*
|
|
3293
|
-
*
|
|
3294
|
-
*
|
|
3295
|
-
* return it as the bootstrap credential — preserving the `create --json`
|
|
3296
|
-
* `token` string contract hub's admin-vaults.ts requires. When no hub is
|
|
3364
|
+
* When a hub is reachable (operator.token present AND a real hub origin
|
|
3365
|
+
* resolves), mint a `vault:<name>:<verb>` hub JWT and return it. When no hub is
|
|
3297
3366
|
* reachable, return `token: null` plus explicit standalone guidance. There is
|
|
3298
3367
|
* no local pvt_* fallback anymore.
|
|
3299
3368
|
*/
|
|
3300
|
-
async function mintBootstrapCredential(
|
|
3369
|
+
async function mintBootstrapCredential(
|
|
3370
|
+
name: string,
|
|
3371
|
+
verb: "read" | "write" = "read",
|
|
3372
|
+
): Promise<VaultCredential> {
|
|
3301
3373
|
const operatorToken = readOperatorToken();
|
|
3302
3374
|
if (!operatorToken) {
|
|
3303
3375
|
return {
|
|
@@ -3322,7 +3394,7 @@ async function mintBootstrapCredential(name: string): Promise<VaultCredential> {
|
|
|
3322
3394
|
const result = await mintHubJwt({
|
|
3323
3395
|
hubOrigin: hub.url,
|
|
3324
3396
|
operatorToken,
|
|
3325
|
-
scope: `vault:${name}
|
|
3397
|
+
scope: `vault:${name}:${verb}`,
|
|
3326
3398
|
subject: "parachute-vault-bootstrap",
|
|
3327
3399
|
});
|
|
3328
3400
|
if ("kind" in result) {
|
|
@@ -3333,7 +3405,7 @@ async function mintBootstrapCredential(name: string): Promise<VaultCredential> {
|
|
|
3333
3405
|
return {
|
|
3334
3406
|
token: null,
|
|
3335
3407
|
guidance:
|
|
3336
|
-
`No token issued — ${detail}. Verify the hub is running
|
|
3408
|
+
`No token issued — ${detail}. Verify the hub is running, ` +
|
|
3337
3409
|
"then run `parachute-vault mcp-install`, or set VAULT_AUTH_TOKEN.",
|
|
3338
3410
|
};
|
|
3339
3411
|
}
|
|
@@ -3344,13 +3416,24 @@ async function mintBootstrapCredential(name: string): Promise<VaultCredential> {
|
|
|
3344
3416
|
}
|
|
3345
3417
|
|
|
3346
3418
|
/**
|
|
3347
|
-
* Create a vault's config + DB
|
|
3419
|
+
* Create a vault's config + DB.
|
|
3348
3420
|
*
|
|
3349
|
-
*
|
|
3350
|
-
*
|
|
3351
|
-
* `
|
|
3421
|
+
* Default vault auth is per-user OAuth (vault#442) — create does NOT mint or
|
|
3422
|
+
* bake in any token. Returns a `VaultCredential` whose `token` is null and
|
|
3423
|
+
* whose `guidance` points at the OAuth-first connect path. A scope-narrow
|
|
3424
|
+
* token is minted only when the caller passes `mintVerb` (the explicit
|
|
3425
|
+
* header-auth / script opt-in: `read` default or `write`, NEVER `admin`). The
|
|
3426
|
+
* DB is created lazily via `getVaultStore` so migrations + schema run; we never
|
|
3427
|
+
* write any pvt_* row.
|
|
3352
3428
|
*/
|
|
3353
3429
|
interface CreateVaultOptions {
|
|
3430
|
+
/**
|
|
3431
|
+
* Opt-in token mint (vault#442). Unset → no token is minted; the vault uses
|
|
3432
|
+
* per-user OAuth on first MCP connect. Set to `read`/`write` → mint a
|
|
3433
|
+
* scope-narrow `vault:<name>:<verb>` hub JWT for the header-auth / script
|
|
3434
|
+
* use case. `admin` is intentionally NOT accepted here.
|
|
3435
|
+
*/
|
|
3436
|
+
mintVerb?: "read" | "write";
|
|
3354
3437
|
/**
|
|
3355
3438
|
* Override the server-wide `default_mirror` knob for this one create.
|
|
3356
3439
|
* `--no-mirror` on `parachute-vault create` sets this to `false` so the
|
|
@@ -3454,7 +3537,19 @@ async function createVault(
|
|
|
3454
3537
|
}
|
|
3455
3538
|
}
|
|
3456
3539
|
|
|
3457
|
-
|
|
3540
|
+
// vault#442: default to per-user OAuth — do NOT auto-mint or bake in a
|
|
3541
|
+
// shared token. Only mint when the caller explicitly opted in (header-auth /
|
|
3542
|
+
// script use case), and then scope-narrow (read/write, never admin).
|
|
3543
|
+
if (opts.mintVerb) {
|
|
3544
|
+
return mintBootstrapCredential(name, opts.mintVerb);
|
|
3545
|
+
}
|
|
3546
|
+
return {
|
|
3547
|
+
token: null,
|
|
3548
|
+
guidance:
|
|
3549
|
+
"No token minted — this vault uses per-user OAuth (sign in on first connect). " +
|
|
3550
|
+
"Need a header-auth token for a script? Run " +
|
|
3551
|
+
`\`parachute auth mint-token --scope vault:${name}:read\` (or \`:write\`).`,
|
|
3552
|
+
};
|
|
3458
3553
|
}
|
|
3459
3554
|
|
|
3460
3555
|
interface InstallMcpConfigOpts {
|
|
@@ -3536,9 +3631,11 @@ Setup:
|
|
|
3536
3631
|
parachute-vault init [--mcp|--no-mcp] [--token|--no-token] [--vault-name <name>]
|
|
3537
3632
|
[--autostart|--no-autostart]
|
|
3538
3633
|
Set up everything (one command, idempotent).
|
|
3539
|
-
--mcp/--no-mcp controls the Claude Code MCP entry
|
|
3540
|
-
|
|
3541
|
-
|
|
3634
|
+
--mcp/--no-mcp controls the Claude Code MCP entry (written
|
|
3635
|
+
for per-user OAuth by default — no baked token; sign in on
|
|
3636
|
+
first connect). --token opts into ALSO minting a scope-narrow
|
|
3637
|
+
header-auth token (vault:<name>:read) for non-OAuth clients /
|
|
3638
|
+
scripts; --no-token (the default) skips minting entirely.
|
|
3542
3639
|
--vault-name skips the prompt and names the vault
|
|
3543
3640
|
(lowercase alphanumeric, hyphens, underscores;
|
|
3544
3641
|
omit to be prompted interactively, default "default").
|
|
@@ -3558,8 +3655,13 @@ Setup:
|
|
|
3558
3655
|
parachute --version Print the installed version (alias: -v, version)
|
|
3559
3656
|
|
|
3560
3657
|
Vaults:
|
|
3561
|
-
parachute-vault create <name> [--json] [--no-mirror]
|
|
3658
|
+
parachute-vault create <name> [--json] [--no-mirror] [--mint [--scope read|write]] [--token <bearer>]
|
|
3562
3659
|
Create a new vault (--json: emit { name, token, paths, set_as_default }).
|
|
3660
|
+
Default auth is per-user OAuth — NO token is minted; connect with
|
|
3661
|
+
"claude mcp add --transport http parachute-<name> <endpoint>" and sign in
|
|
3662
|
+
on first use. --mint opts into a scope-narrow hub JWT for the header-auth /
|
|
3663
|
+
script case (--scope read [default] | write — admin is NOT mintable from
|
|
3664
|
+
create); --token <bearer> pastes an existing bearer instead of minting.
|
|
3563
3665
|
New vaults default to an internal live mirror — a local git backup of
|
|
3564
3666
|
the markdown projection (backup on by default; GitHub off-site is an
|
|
3565
3667
|
opt-in upgrade). --no-mirror creates a bare vault with no mirror config.
|
package/src/init-summary.test.ts
CHANGED
|
@@ -13,6 +13,7 @@ const baseInput = {
|
|
|
13
13
|
bindHost: "127.0.0.1",
|
|
14
14
|
port: 1940,
|
|
15
15
|
mcpUrl: "http://127.0.0.1:1940/vault/default/mcp",
|
|
16
|
+
vaultName: "default",
|
|
16
17
|
};
|
|
17
18
|
|
|
18
19
|
function lines(addMcp: boolean, addToken: boolean, apiKey: string | undefined) {
|
|
@@ -96,16 +97,70 @@ describe("buildInitSummaryLines", () => {
|
|
|
96
97
|
});
|
|
97
98
|
});
|
|
98
99
|
|
|
99
|
-
|
|
100
|
+
// vault#442: the DEFAULT init path — MCP wired, NO token minted (per-user
|
|
101
|
+
// OAuth). The summary must LEAD with the OAuth connect path, never mint, and
|
|
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
|
+
});
|
|
113
|
+
|
|
114
|
+
test("shows the OAuth `claude mcp add` command for other clients", () => {
|
|
115
|
+
expect(out).toContain(
|
|
116
|
+
"claude mcp add --transport http parachute-vault http://127.0.0.1:1940/vault/default/mcp",
|
|
117
|
+
);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("offers the scope-narrow opt-in mint for scripts (full vault:<name>:read, never admin)", () => {
|
|
121
|
+
// Must be the three-segment named-resource form the hub mint-token model
|
|
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");
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("does NOT print or imply any minted token", () => {
|
|
130
|
+
expect(out).not.toContain("Your API token:");
|
|
131
|
+
expect(out).not.toContain("Baked into ~/.claude.json");
|
|
132
|
+
expect(out).not.toContain("Authorization: Bearer");
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("does NOT surface the old no-token-issued failure copy", () => {
|
|
136
|
+
expect(out).not.toContain("No token issued");
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("threads a non-default vault name into the mint-token scope", () => {
|
|
140
|
+
const out2 = buildInitSummaryLines({
|
|
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");
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
describe("MCP=N + token=N (OAuth default, Claude Code not wired)", () => {
|
|
100
154
|
const out = lines(false, false, undefined).join("\n");
|
|
101
155
|
|
|
102
|
-
test("
|
|
103
|
-
expect(out).toContain("
|
|
156
|
+
test("frames skipping the MCP entry as OAuth-first, not 'unreachable'", () => {
|
|
157
|
+
expect(out).toContain("uses per-user OAuth, no token needed");
|
|
158
|
+
expect(out).not.toContain("your vault isn't reachable by any client");
|
|
104
159
|
});
|
|
105
160
|
|
|
106
|
-
test("points to
|
|
161
|
+
test("points to mcp-install (no token-minting framing)", () => {
|
|
107
162
|
expect(out).toContain("parachute-vault mcp-install");
|
|
108
|
-
expect(out).toContain("mints a hub JWT");
|
|
163
|
+
expect(out).not.toContain("mints a hub JWT");
|
|
109
164
|
});
|
|
110
165
|
|
|
111
166
|
test("does not print any token", () => {
|
|
@@ -118,6 +173,23 @@ describe("buildInitSummaryLines", () => {
|
|
|
118
173
|
});
|
|
119
174
|
});
|
|
120
175
|
|
|
176
|
+
// Explicit opt-in but no hub reachable to mint (vault#282 Stage 2 path,
|
|
177
|
+
// reached only when the operator passes --token without a hub).
|
|
178
|
+
describe("MCP=N + token=Y but no hub (opt-in mint failed)", () => {
|
|
179
|
+
const out = buildInitSummaryLines({
|
|
180
|
+
...baseInput,
|
|
181
|
+
addMcp: false,
|
|
182
|
+
addToken: true,
|
|
183
|
+
apiKey: undefined,
|
|
184
|
+
noTokenGuidance: "No token issued — hub unreachable.",
|
|
185
|
+
}).join("\n");
|
|
186
|
+
|
|
187
|
+
test("surfaces the no-token-issued guidance + recovery", () => {
|
|
188
|
+
expect(out).toContain("No token issued");
|
|
189
|
+
expect(out).toContain("parachute-vault mcp-install");
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
121
193
|
test("always prints Config: and Server: lines", () => {
|
|
122
194
|
for (const [addMcp, addToken] of [
|
|
123
195
|
[true, true],
|
package/src/init-summary.ts
CHANGED
|
@@ -12,6 +12,13 @@ export type InitSummaryInput = {
|
|
|
12
12
|
bindHost: string;
|
|
13
13
|
port: number;
|
|
14
14
|
mcpUrl: string;
|
|
15
|
+
/**
|
|
16
|
+
* The default vault's name — used to emit the three-segment
|
|
17
|
+
* `vault:<vaultName>:read` scope in the OAuth-first mint-token suggestion
|
|
18
|
+
* (the hub mint-token model requires the named-resource form;
|
|
19
|
+
* a bare `vault:read` would mint a malformed scope). vault#442/#443.
|
|
20
|
+
*/
|
|
21
|
+
vaultName: string;
|
|
15
22
|
/**
|
|
16
23
|
* Guidance from the bootstrap-credential step when no token could be issued
|
|
17
24
|
* (standalone install, no hub reachable — vault#282 Stage 2). Surfaced when
|
|
@@ -23,44 +30,54 @@ export type InitSummaryInput = {
|
|
|
23
30
|
|
|
24
31
|
/**
|
|
25
32
|
* Build the post-install summary lines for `vault init`, branched on the
|
|
26
|
-
* (addMcp, addToken, apiKey) decision matrix.
|
|
27
|
-
*
|
|
28
|
-
* is
|
|
33
|
+
* (addMcp, addToken, apiKey) decision matrix.
|
|
34
|
+
*
|
|
35
|
+
* vault#442: the DEFAULT is per-user OAuth — no token is minted, and the
|
|
36
|
+
* Claude Code MCP entry is written without a baked bearer (browser sign-in on
|
|
37
|
+
* first connect). A token is minted only on explicit opt-in (`addToken`), and
|
|
38
|
+
* then scope-narrow. Branches:
|
|
29
39
|
*
|
|
30
|
-
* addMcp,
|
|
31
|
-
* addMcp,
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* !addMcp, !
|
|
40
|
+
* addMcp, !apiKey → OAuth-first: connect, sign in on first use
|
|
41
|
+
* addMcp, addToken, apiKey → token baked into claude.json + printed
|
|
42
|
+
* addMcp, !addToken, apiKey → token baked into claude.json, hint
|
|
43
|
+
* !addMcp, addToken, apiKey → token printed prominently
|
|
44
|
+
* !addMcp, addToken, !apiKey → opted into a token but no hub reachable
|
|
45
|
+
* !addMcp, !addToken → OAuth-first: add Claude Code later
|
|
35
46
|
*/
|
|
36
47
|
export function buildInitSummaryLines(input: InitSummaryInput): string[] {
|
|
37
|
-
const { addMcp, addToken, apiKey, configDir, bindHost, port, mcpUrl, noTokenGuidance } = input;
|
|
48
|
+
const { addMcp, addToken, apiKey, configDir, bindHost, port, mcpUrl, vaultName, noTokenGuidance } = input;
|
|
38
49
|
const lines: string[] = [];
|
|
39
50
|
lines.push("");
|
|
40
51
|
lines.push("---");
|
|
41
52
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
if (addMcp && addToken && apiKey) {
|
|
53
|
+
if (addMcp && apiKey && addToken) {
|
|
45
54
|
lines.push("");
|
|
46
55
|
lines.push(`Your API token: ${apiKey}`);
|
|
47
56
|
lines.push(` - Baked into ~/.claude.json for Claude Code ✓`);
|
|
48
57
|
lines.push(` - Paste into your other MCP client's config, or use as Authorization: Bearer <token>`);
|
|
49
58
|
lines.push(` - Won't be shown again — save it now.`);
|
|
50
|
-
} else if (addMcp &&
|
|
59
|
+
} else if (addMcp && apiKey && !addToken) {
|
|
51
60
|
lines.push("");
|
|
52
61
|
lines.push(
|
|
53
62
|
"Token in ~/.claude.json; run `parachute-vault mcp-install` later if you need one for other clients.",
|
|
54
63
|
);
|
|
64
|
+
} else if (addMcp && !apiKey) {
|
|
65
|
+
// vault#442 default: OAuth-first. The MCP entry is wired without a bearer —
|
|
66
|
+
// Claude Code signs in via browser OAuth on first connect. No token needed.
|
|
67
|
+
lines.push("");
|
|
68
|
+
lines.push("Connect your AI — no token needed, you'll sign in on first use:");
|
|
69
|
+
lines.push(` Claude Code is already wired in (~/.claude.json) — just start a session.`);
|
|
70
|
+
lines.push(` Other clients: claude mcp add --transport http parachute-vault ${mcpUrl}`);
|
|
71
|
+
lines.push(` Need a header-auth token for a script? parachute auth mint-token --scope vault:${vaultName}:read`);
|
|
55
72
|
} else if (!addMcp && addToken && apiKey) {
|
|
56
73
|
lines.push("");
|
|
57
74
|
lines.push(`Your API token: ${apiKey}`);
|
|
58
75
|
lines.push(` - Paste into your other MCP client's config, or use as Authorization: Bearer <token>`);
|
|
59
76
|
lines.push(` - Won't be shown again — save it now.`);
|
|
60
|
-
} else if (
|
|
61
|
-
//
|
|
62
|
-
// Stage 2 — vault no longer mints local pvt_* tokens). Surface
|
|
63
|
-
// the recovery paths.
|
|
77
|
+
} else if (!addMcp && addToken && !apiKey) {
|
|
78
|
+
// Explicitly opted into a token but no hub was reachable to mint one
|
|
79
|
+
// (vault#282 Stage 2 — vault no longer mints local pvt_* tokens). Surface
|
|
80
|
+
// why and the recovery paths.
|
|
64
81
|
lines.push("");
|
|
65
82
|
lines.push(
|
|
66
83
|
noTokenGuidance ??
|
|
@@ -73,12 +90,13 @@ export function buildInitSummaryLines(input: InitSummaryInput): string[] {
|
|
|
73
90
|
" or set VAULT_AUTH_TOKEN for an operator-channel bearer.",
|
|
74
91
|
);
|
|
75
92
|
} else if (!addMcp && !addToken) {
|
|
93
|
+
// OAuth-first, but the operator skipped wiring Claude Code too.
|
|
76
94
|
lines.push("");
|
|
77
95
|
lines.push(
|
|
78
|
-
"
|
|
96
|
+
"Skipped the Claude Code MCP entry. Add it anytime — it uses per-user OAuth, no token needed:",
|
|
79
97
|
);
|
|
80
98
|
lines.push(
|
|
81
|
-
"
|
|
99
|
+
" parachute-vault mcp-install",
|
|
82
100
|
);
|
|
83
101
|
}
|
|
84
102
|
|