@openparachute/vault 0.3.1 → 0.3.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/README.md +9 -5
- package/package.json +1 -1
- package/src/cli.ts +66 -25
- package/src/init-summary.test.ts +133 -0
- package/src/init-summary.ts +90 -0
- package/src/prompt.ts +25 -9
package/README.md
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
**Parachute Vault is a self-hosted knowledge graph that any AI can read and write, over the open [MCP](https://modelcontextprotocol.io) protocol.** Your notes, tags, links, and attachments live on your machine — in plain SQLite databases under `~/.parachute/`, not in a vendor's cloud.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Today it works with **Claude Code, Codex, Goose, OpenCode, and any other local MCP client** — same endpoint, your vault. Claude Code auto-wires on install; for the rest, point them at `http://127.0.0.1:1940/vault/default/mcp`.
|
|
6
|
+
|
|
7
|
+
Web AI connectors — **claude.ai**, **ChatGPT**, and **Gemini** — are coming in the next few weeks. Switch tools, keep your knowledge. No vendor lock-in, no re-import step when the next model lands.
|
|
6
8
|
|
|
7
9
|
## Quick start
|
|
8
10
|
|
|
@@ -20,7 +22,7 @@ bun install
|
|
|
20
22
|
bun src/cli.ts vault init
|
|
21
23
|
```
|
|
22
24
|
|
|
23
|
-
`vault init` creates a vault, generates an API key, starts a background daemon (launchd on Mac, systemd on Linux), and configures Claude Code's MCP — all in one command.
|
|
25
|
+
`vault init` creates a vault, generates an API key, starts a background daemon (launchd on Mac, systemd on Linux), and configures Claude Code's MCP — all in one command. Start a new Claude Code session and your vault's tools show up. For other local MCP clients (Codex, Goose, OpenCode, Cursor, Zed, Cline, your own agent), point them at `http://127.0.0.1:1940/vault/default/mcp` — the API key is printed once at init; save it for anything that isn't Claude Code.
|
|
24
26
|
|
|
25
27
|
For remote access from Claude Desktop or mobile apps, see [Deployment](#deployment) below.
|
|
26
28
|
|
|
@@ -88,11 +90,13 @@ The daemon binds `0.0.0.0:1940` (or whatever you set in `PORT`) and serves REST,
|
|
|
88
90
|
|
|
89
91
|
### Your API token
|
|
90
92
|
|
|
91
|
-
|
|
93
|
+
`vault init` asks two explicit questions: (1) install vault as an MCP server in `~/.claude.json`? (2) also surface the API token so you can paste it into other MCP clients (Codex, Goose, OpenCode, Cursor, Zed, Cline), scripts, or `curl`? Both default yes. Pass `--mcp` / `--no-mcp` and `--token` / `--no-token` for non-interactive installs.
|
|
94
|
+
|
|
95
|
+
If you said yes to (2), the `pvt_...` token 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? Just mint a new one: `parachute-vault tokens create`. Tokens are SHA-256 hashed at rest in each vault's `vault.db`.
|
|
92
96
|
|
|
93
|
-
### Owner password
|
|
97
|
+
### Owner password (for OAuth, coming soon)
|
|
94
98
|
|
|
95
|
-
|
|
99
|
+
`vault init` doesn't prompt for an owner password — the password is only needed for OAuth consent, which is what browser-based clients (claude.ai, ChatGPT, Claude Desktop) use, and those paths are coming in the next few weeks. When you're ready to expose the vault publicly, set one with `parachute-vault set-password` (and optionally `parachute-vault 2fa enroll`). See [Connecting a client → Owner password](#owner-password-needed-for-oauth).
|
|
96
100
|
|
|
97
101
|
## Connecting a client
|
|
98
102
|
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -49,6 +49,7 @@ import type { VaultConfig } from "./config.ts";
|
|
|
49
49
|
import { DATA_DIR } from "./config.ts";
|
|
50
50
|
import { installAgent, uninstallAgent, isAgentLoaded, restartAgent } from "./launchd.ts";
|
|
51
51
|
import { chooseMcpUrl } from "./mcp-install.ts";
|
|
52
|
+
import { buildInitSummaryLines } from "./init-summary.ts";
|
|
52
53
|
import {
|
|
53
54
|
runBackup,
|
|
54
55
|
readLastBackup,
|
|
@@ -225,8 +226,14 @@ async function cmdInit(args: string[] = []) {
|
|
|
225
226
|
// --no-mcp skips it without prompting. If both passed, --no-mcp wins
|
|
226
227
|
// (safer default). Neither → prompt in a TTY, default-yes in a
|
|
227
228
|
// non-TTY for back-compat with existing piped install scripts.
|
|
229
|
+
//
|
|
230
|
+
// --token / --no-token follow the same pattern for whether the API
|
|
231
|
+
// token is surfaced to the user at the end of init (for pasting into
|
|
232
|
+
// other MCP clients, scripts, or curl).
|
|
228
233
|
const flagMcpOn = args.includes("--mcp");
|
|
229
234
|
const flagMcpOff = args.includes("--no-mcp");
|
|
235
|
+
const flagTokenOn = args.includes("--token");
|
|
236
|
+
const flagTokenOff = args.includes("--no-token");
|
|
230
237
|
|
|
231
238
|
const isMac = process.platform === "darwin";
|
|
232
239
|
const isLinux = process.platform === "linux";
|
|
@@ -325,9 +332,16 @@ async function cmdInit(args: string[] = []) {
|
|
|
325
332
|
console.log();
|
|
326
333
|
}
|
|
327
334
|
|
|
328
|
-
// 5b.
|
|
335
|
+
// 5b. Owner password is only needed for OAuth consent (browser-based
|
|
336
|
+
// clients like claude.ai / ChatGPT / Claude Desktop). Those paths are
|
|
337
|
+
// coming in the next few weeks; until then, skip the prompt. Users who
|
|
338
|
+
// want to expose the vault publicly today can set one manually via
|
|
339
|
+
// `parachute-vault set-password`.
|
|
329
340
|
if (!hasOwnerPassword()) {
|
|
330
|
-
|
|
341
|
+
console.log();
|
|
342
|
+
console.log("Public exposure + web-AI connectors (claude.ai, ChatGPT, etc.) are coming soon.");
|
|
343
|
+
console.log(" When you're ready to expose this vault publicly, run:");
|
|
344
|
+
console.log(" parachute-vault set-password # required for OAuth consent");
|
|
331
345
|
}
|
|
332
346
|
|
|
333
347
|
// 6. Install daemon (platform-aware). Idempotent — safe to re-run after
|
|
@@ -361,11 +375,43 @@ async function cmdInit(args: string[] = []) {
|
|
|
361
375
|
} else if (flagMcpOn) {
|
|
362
376
|
addMcp = true;
|
|
363
377
|
} else if (process.stdin.isTTY) {
|
|
364
|
-
addMcp = await confirm("
|
|
378
|
+
addMcp = await confirm("Install Vault as an MCP server in Claude Code (~/.claude.json)?", true);
|
|
365
379
|
} else {
|
|
366
380
|
addMcp = true; // non-interactive: preserve the installable-via-pipe default
|
|
367
381
|
}
|
|
368
382
|
|
|
383
|
+
// 7b. Surface an API token for other clients? (Codex, Goose, OpenCode,
|
|
384
|
+
// Cursor, Zed, Cline, scripts, curl.) Same flag/TTY precedence as MCP.
|
|
385
|
+
// Note: a token is always minted when addMcp is true (it gets baked into
|
|
386
|
+
// the ~/.claude.json entry); this prompt controls whether that token is
|
|
387
|
+
// printed prominently at the end so the user can paste it elsewhere.
|
|
388
|
+
let addToken: boolean;
|
|
389
|
+
if (flagTokenOff) {
|
|
390
|
+
addToken = false;
|
|
391
|
+
} else if (flagTokenOn) {
|
|
392
|
+
addToken = true;
|
|
393
|
+
} else if (process.stdin.isTTY) {
|
|
394
|
+
addToken = await confirm(
|
|
395
|
+
"Generate an API token for other MCP clients (Codex, Goose, OpenCode, Cursor, Zed, Cline), scripts, or curl?",
|
|
396
|
+
true,
|
|
397
|
+
);
|
|
398
|
+
} else {
|
|
399
|
+
addToken = true; // non-interactive: default-yes matches addMcp default
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Mint a token if we need one (for the claude.json entry and/or for
|
|
403
|
+
// prominent display) and don't already have one from vault creation.
|
|
404
|
+
// Re-runs of init that opt in will mint a fresh token — old tokens
|
|
405
|
+
// continue to work; the user can `tokens revoke` the unused ones.
|
|
406
|
+
const defaultVault = globalConfig.default_vault || "default";
|
|
407
|
+
const needToken = addMcp || addToken;
|
|
408
|
+
if (needToken && !apiKey) {
|
|
409
|
+
const store = getVaultStore(defaultVault);
|
|
410
|
+
const { fullToken } = generateToken();
|
|
411
|
+
createToken(store.db, fullToken, { label: "init", permission: "full" });
|
|
412
|
+
apiKey = fullToken;
|
|
413
|
+
}
|
|
414
|
+
|
|
369
415
|
if (addMcp) {
|
|
370
416
|
installMcpConfig(apiKey);
|
|
371
417
|
console.log(` MCP server added to ~/.claude.json`);
|
|
@@ -375,30 +421,21 @@ async function cmdInit(args: string[] = []) {
|
|
|
375
421
|
}
|
|
376
422
|
|
|
377
423
|
// 8. Summary
|
|
378
|
-
console.log("\n---");
|
|
379
424
|
const port = globalConfig.port || DEFAULT_PORT;
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
console.log(`\nUsage examples:`);
|
|
392
|
-
console.log(` curl http://localhost:${port}/health`);
|
|
393
|
-
if (apiKey) {
|
|
394
|
-
console.log(` curl -H "Authorization: Bearer ${apiKey}" http://localhost:${port}/api/notes`);
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
console.log(`\nNext steps:`);
|
|
398
|
-
console.log(` parachute-vault status check everything is running`);
|
|
399
|
-
console.log(` parachute-vault config view/edit configuration`);
|
|
425
|
+
const mcpUrl = `http://127.0.0.1:${port}/vault/${defaultVault}/mcp`;
|
|
426
|
+
const lines = buildInitSummaryLines({
|
|
427
|
+
addMcp,
|
|
428
|
+
addToken,
|
|
429
|
+
apiKey,
|
|
430
|
+
configDir: CONFIG_DIR,
|
|
431
|
+
bindHost,
|
|
432
|
+
port,
|
|
433
|
+
mcpUrl,
|
|
434
|
+
});
|
|
435
|
+
for (const line of lines) console.log(line);
|
|
400
436
|
}
|
|
401
437
|
|
|
438
|
+
|
|
402
439
|
async function promptForOwnerPassword(purpose: string): Promise<boolean> {
|
|
403
440
|
console.log(`\n${purpose}`);
|
|
404
441
|
console.log(" Used on the OAuth consent page to authorize third-party clients");
|
|
@@ -2066,7 +2103,11 @@ data, and debugging.
|
|
|
2066
2103
|
── Standard use ───────────────────────────────────────────────────────
|
|
2067
2104
|
|
|
2068
2105
|
Setup:
|
|
2069
|
-
parachute-vault init [--mcp
|
|
2106
|
+
parachute-vault init [--mcp|--no-mcp] [--token|--no-token]
|
|
2107
|
+
Set up everything (one command, idempotent).
|
|
2108
|
+
--mcp/--no-mcp controls the Claude Code MCP entry;
|
|
2109
|
+
--token/--no-token controls whether an API token is
|
|
2110
|
+
printed for pasting into other MCP clients / scripts.
|
|
2070
2111
|
parachute-vault doctor Diagnose install/config issues
|
|
2071
2112
|
parachute-vault uninstall [--wipe] [--yes]
|
|
2072
2113
|
Remove daemon + MCP entry; --wipe also removes vaults, .env,
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for `buildInitSummaryLines` — the post-install summary printed at the
|
|
3
|
+
* end of `vault init`. The summary branches on the (addMcp, addToken) decision
|
|
4
|
+
* matrix; these tests cover all four cells plus the token surfacing /
|
|
5
|
+
* Bearer-example rules.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, test, expect } from "bun:test";
|
|
9
|
+
import { buildInitSummaryLines } from "./init-summary.ts";
|
|
10
|
+
|
|
11
|
+
const baseInput = {
|
|
12
|
+
configDir: "/tmp/parachute",
|
|
13
|
+
bindHost: "127.0.0.1",
|
|
14
|
+
port: 1940,
|
|
15
|
+
mcpUrl: "http://127.0.0.1:1940/vault/default/mcp",
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
function lines(addMcp: boolean, addToken: boolean, apiKey: string | undefined) {
|
|
19
|
+
return buildInitSummaryLines({ ...baseInput, addMcp, addToken, apiKey });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
describe("buildInitSummaryLines", () => {
|
|
23
|
+
describe("MCP=Y + token=Y (most common)", () => {
|
|
24
|
+
const out = lines(true, true, "pvt_abc123").join("\n");
|
|
25
|
+
|
|
26
|
+
test("prints token prominently", () => {
|
|
27
|
+
expect(out).toContain("Your API token: pvt_abc123");
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("notes token is baked into ~/.claude.json", () => {
|
|
31
|
+
expect(out).toContain("Baked into ~/.claude.json for Claude Code");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("includes save-it-now warning", () => {
|
|
35
|
+
expect(out).toContain("Won't be shown again — save it now.");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("includes Bearer curl example", () => {
|
|
39
|
+
expect(out).toContain(
|
|
40
|
+
'curl -H "Authorization: Bearer pvt_abc123" http://localhost:1940/api/notes',
|
|
41
|
+
);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("Next steps mentions starting a Claude Code session", () => {
|
|
45
|
+
expect(out).toContain("Start a new Claude Code session");
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe("MCP=Y + token=N (MCP wired, token not surfaced)", () => {
|
|
50
|
+
const out = lines(true, false, "pvt_secret").join("\n");
|
|
51
|
+
|
|
52
|
+
test("does not print the token prominently", () => {
|
|
53
|
+
expect(out).not.toContain("pvt_secret");
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("does not include the 'Baked into' bullet", () => {
|
|
57
|
+
expect(out).not.toContain("Baked into ~/.claude.json");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("includes the tokens-create-later hint", () => {
|
|
61
|
+
expect(out).toContain("Token in ~/.claude.json");
|
|
62
|
+
expect(out).toContain("parachute vault tokens create");
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("omits the Bearer curl example", () => {
|
|
66
|
+
expect(out).not.toContain("Authorization: Bearer");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("still shows the Claude-Code-session next step", () => {
|
|
70
|
+
expect(out).toContain("Start a new Claude Code session");
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe("MCP=N + token=Y (token only)", () => {
|
|
75
|
+
const out = lines(false, true, "pvt_xyz").join("\n");
|
|
76
|
+
|
|
77
|
+
test("prints token prominently", () => {
|
|
78
|
+
expect(out).toContain("Your API token: pvt_xyz");
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("omits the 'Baked into' bullet (no claude.json entry written)", () => {
|
|
82
|
+
expect(out).not.toContain("Baked into ~/.claude.json");
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("includes Bearer curl example", () => {
|
|
86
|
+
expect(out).toContain('Authorization: Bearer pvt_xyz');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("Next steps points at any local MCP client", () => {
|
|
90
|
+
expect(out).toContain("Point any local MCP client");
|
|
91
|
+
expect(out).toContain("http://127.0.0.1:1940/vault/default/mcp");
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("Next steps offers mcp-install as a way back", () => {
|
|
95
|
+
expect(out).toContain("parachute-vault mcp-install");
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
describe("MCP=N + token=N (unreachable)", () => {
|
|
100
|
+
const out = lines(false, false, undefined).join("\n");
|
|
101
|
+
|
|
102
|
+
test("warns the vault is unreachable", () => {
|
|
103
|
+
expect(out).toContain("your vault isn't reachable by any client");
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("points to both recovery paths", () => {
|
|
107
|
+
expect(out).toContain("parachute-vault mcp-install");
|
|
108
|
+
expect(out).toContain("parachute vault tokens create");
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("does not print any token", () => {
|
|
112
|
+
expect(out).not.toContain("Your API token:");
|
|
113
|
+
expect(out).not.toMatch(/pvt_/);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("omits the Bearer curl example", () => {
|
|
117
|
+
expect(out).not.toContain("Authorization: Bearer");
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("always prints Config: and Server: lines", () => {
|
|
122
|
+
for (const [addMcp, addToken] of [
|
|
123
|
+
[true, true],
|
|
124
|
+
[true, false],
|
|
125
|
+
[false, true],
|
|
126
|
+
[false, false],
|
|
127
|
+
] as const) {
|
|
128
|
+
const out = lines(addMcp, addToken, addMcp || addToken ? "pvt_k" : undefined).join("\n");
|
|
129
|
+
expect(out).toContain("Config: /tmp/parachute");
|
|
130
|
+
expect(out).toContain("Server: http://127.0.0.1:1940");
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
});
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure helper for `vault init`'s post-install summary. Extracted from cli.ts
|
|
3
|
+
* so the (addMcp, addToken) decision-matrix branches can be unit-tested
|
|
4
|
+
* without side-effects from importing the CLI entrypoint.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export type InitSummaryInput = {
|
|
8
|
+
addMcp: boolean;
|
|
9
|
+
addToken: boolean;
|
|
10
|
+
apiKey: string | undefined;
|
|
11
|
+
configDir: string;
|
|
12
|
+
bindHost: string;
|
|
13
|
+
port: number;
|
|
14
|
+
mcpUrl: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Build the post-install summary lines for `vault init`, branched on the
|
|
19
|
+
* (addMcp, addToken) decision matrix:
|
|
20
|
+
*
|
|
21
|
+
* Y, Y → token baked into claude.json + printed prominently
|
|
22
|
+
* Y, N → token baked into claude.json, hint about `tokens create`
|
|
23
|
+
* N, Y → token printed prominently, no claude.json entry
|
|
24
|
+
* N, N → warning: vault unreachable; both recovery paths listed
|
|
25
|
+
*/
|
|
26
|
+
export function buildInitSummaryLines(input: InitSummaryInput): string[] {
|
|
27
|
+
const { addMcp, addToken, apiKey, configDir, bindHost, port, mcpUrl } = input;
|
|
28
|
+
const lines: string[] = [];
|
|
29
|
+
lines.push("");
|
|
30
|
+
lines.push("---");
|
|
31
|
+
|
|
32
|
+
if (addMcp && addToken && apiKey) {
|
|
33
|
+
lines.push("");
|
|
34
|
+
lines.push(`Your API token: ${apiKey}`);
|
|
35
|
+
lines.push(` - Baked into ~/.claude.json for Claude Code ✓`);
|
|
36
|
+
lines.push(` - Paste into your other MCP client's config, or use as Authorization: Bearer <token>`);
|
|
37
|
+
lines.push(` - Won't be shown again — save it now.`);
|
|
38
|
+
} else if (addMcp && !addToken) {
|
|
39
|
+
lines.push("");
|
|
40
|
+
lines.push(
|
|
41
|
+
"Token in ~/.claude.json; run `parachute vault tokens create` later if you need one for other clients.",
|
|
42
|
+
);
|
|
43
|
+
} else if (!addMcp && addToken && apiKey) {
|
|
44
|
+
lines.push("");
|
|
45
|
+
lines.push(`Your API token: ${apiKey}`);
|
|
46
|
+
lines.push(` - Paste into your other MCP client's config, or use as Authorization: Bearer <token>`);
|
|
47
|
+
lines.push(` - Won't be shown again — save it now.`);
|
|
48
|
+
} else if (!addMcp && !addToken) {
|
|
49
|
+
lines.push("");
|
|
50
|
+
lines.push(
|
|
51
|
+
"You've skipped both MCP install and token generation — your vault isn't reachable by any client.",
|
|
52
|
+
);
|
|
53
|
+
lines.push(
|
|
54
|
+
" Add Claude Code later with `parachute-vault mcp-install`, or mint a token with `parachute vault tokens create`.",
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
lines.push("");
|
|
59
|
+
lines.push(`Config: ${configDir}`);
|
|
60
|
+
lines.push(`Server: http://${bindHost}:${port}`);
|
|
61
|
+
|
|
62
|
+
lines.push("");
|
|
63
|
+
lines.push(`Usage examples:`);
|
|
64
|
+
lines.push(` curl http://localhost:${port}/health`);
|
|
65
|
+
if (addToken && apiKey) {
|
|
66
|
+
lines.push(` curl -H "Authorization: Bearer ${apiKey}" http://localhost:${port}/api/notes`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
lines.push("");
|
|
70
|
+
lines.push(`Next steps:`);
|
|
71
|
+
if (addMcp) {
|
|
72
|
+
lines.push(` - Start a new Claude Code session — your Vault is already wired in. Try:`);
|
|
73
|
+
lines.push(` claude "Help me set up my parachute vault"`);
|
|
74
|
+
lines.push(` - Or point any other local MCP client (Codex, Goose, OpenCode, Cursor,`);
|
|
75
|
+
lines.push(` Zed, Cline, your own agent) at:`);
|
|
76
|
+
lines.push(` ${mcpUrl}`);
|
|
77
|
+
} else if (addToken) {
|
|
78
|
+
lines.push(` - Point any local MCP client (Codex, Goose, OpenCode, Cursor, Zed,`);
|
|
79
|
+
lines.push(` Cline, your own agent) at:`);
|
|
80
|
+
lines.push(` ${mcpUrl}`);
|
|
81
|
+
lines.push(` - Or add Claude Code back anytime: parachute-vault mcp-install`);
|
|
82
|
+
} else {
|
|
83
|
+
lines.push(` - Add Claude Code: parachute-vault mcp-install`);
|
|
84
|
+
lines.push(` - Mint a token: parachute vault tokens create`);
|
|
85
|
+
}
|
|
86
|
+
lines.push(` - Check status: parachute-vault status`);
|
|
87
|
+
lines.push(` - Edit config: parachute-vault config`);
|
|
88
|
+
|
|
89
|
+
return lines;
|
|
90
|
+
}
|
package/src/prompt.ts
CHANGED
|
@@ -74,36 +74,52 @@ export async function askPassword(question: string): Promise<string> {
|
|
|
74
74
|
}
|
|
75
75
|
};
|
|
76
76
|
|
|
77
|
+
// Batch visible output per data event. On Bun 1.2.x, per-char writes
|
|
78
|
+
// can appear in bursts (keystrokes echoing late or out of order);
|
|
79
|
+
// coalescing to a single write per data event keeps the visible
|
|
80
|
+
// stream in lock-step with the captured input.
|
|
77
81
|
const onData = (data: string) => {
|
|
78
82
|
try {
|
|
83
|
+
let toWrite = "";
|
|
84
|
+
let done = false;
|
|
85
|
+
let aborted = false;
|
|
79
86
|
for (const ch of data) {
|
|
80
87
|
// Enter — done
|
|
81
88
|
if (ch === "\r" || ch === "\n") {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
resolve(buf);
|
|
85
|
-
return;
|
|
89
|
+
done = true;
|
|
90
|
+
break;
|
|
86
91
|
}
|
|
87
92
|
// Ctrl-C — abort
|
|
88
93
|
if (ch === "\u0003") {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
process.exit(130);
|
|
94
|
+
aborted = true;
|
|
95
|
+
break;
|
|
92
96
|
}
|
|
93
97
|
// Backspace / DEL
|
|
94
98
|
if (ch === "\u0008" || ch === "\u007f") {
|
|
95
99
|
if (buf.length > 0) {
|
|
96
100
|
buf = buf.slice(0, -1);
|
|
97
|
-
|
|
101
|
+
toWrite += "\b \b";
|
|
98
102
|
}
|
|
99
103
|
continue;
|
|
100
104
|
}
|
|
101
105
|
// Printable
|
|
102
106
|
if (ch >= " ") {
|
|
103
107
|
buf += ch;
|
|
104
|
-
|
|
108
|
+
toWrite += "*";
|
|
105
109
|
}
|
|
106
110
|
}
|
|
111
|
+
if (toWrite) process.stdout.write(toWrite);
|
|
112
|
+
if (done) {
|
|
113
|
+
process.stdout.write("\n");
|
|
114
|
+
cleanup();
|
|
115
|
+
resolve(buf);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (aborted) {
|
|
119
|
+
process.stdout.write("\n");
|
|
120
|
+
cleanup();
|
|
121
|
+
process.exit(130);
|
|
122
|
+
}
|
|
107
123
|
} catch (err) {
|
|
108
124
|
cleanup();
|
|
109
125
|
reject(err);
|