enclave-link 0.2.1 → 0.2.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 CHANGED
@@ -26,8 +26,6 @@ hex saved Netflix: email, password
26
26
 
27
27
  It works out of the box with a hosted relay. No accounts, no keys to set up, nothing for the person to install.
28
28
 
29
- The vault also takes 2FA codes and passkeys. Bring in your whole [Proton Pass](#bring-your-proton-pass-vault) vault, and the agent types the current 2FA code or [signs in with a passkey](#passkeys) whose key never leaves the vault.
30
-
31
29
  ## Set up your agent
32
30
 
33
31
  **Agents with a shell** (Claude Code, Codex, Cursor, Gemini CLI, OpenCode and [30 more](https://github.com/vercel-labs/skills)) get it as a skill:
@@ -331,7 +329,7 @@ The vault keeps each item encrypted in `~/.enclave/items`, sealed to the vault's
331
329
 
332
330
  What enclave can't protect against:
333
331
 
334
- - **Whoever runs the relay sends you the page.** Your browser downloads the page, including the code that locks your password, from the relay each time you open a link. What the relay stores can't be read, but if someone took over the relay's Convex account, they could change the page to copy what you type before locking it. Every website that encrypts in your browser works this way. The page's script is under 100 lines in [`page/page.ts`](page/page.ts) plus the encryption in [`src/hpke.ts`](src/hpke.ts), served as one file with a strict Content Security Policy, so you can read exactly what it runs. If you'd rather trust nobody's relay, [host your own](#host-your-own).
332
+ - **Whoever runs the relay sends you the page.** Your browser downloads the page, including the code that locks your password, from the relay each time you open a link. What the relay stores can't be read, but if someone took over the relay's Convex account, they could change the page to copy what you type before locking it. Every website that encrypts in your browser works this way. The page's script is 128 lines in [`page/page.ts`](page/page.ts) plus the encryption in [`src/hpke.ts`](src/hpke.ts), served as one file with a strict Content Security Policy, so you can read exactly what it runs. If you'd rather trust nobody's relay, [host your own](#host-your-own).
335
333
  - **Anyone with the link can answer it first.** They still can't read anything, but they could send a fake answer. If that happens, the page says "This link doesn't work anymore" when you open it.
336
334
  - **Phishing.** A link can say anyone is asking. Only send secrets to agents you asked for help.
337
335
  - **An agent that wants to see a secret can.** enclave keeps secrets out of the chat and out of tool results, and `run` hides their exact values in output. But an agent with a shell on the same computer could still read its own vault. enclave stops secrets from leaking by accident, not a model that's trying.
@@ -374,7 +372,7 @@ npm run build # bundles the CLI, SDK and types into dist/
374
372
 
375
373
  | Path | What's there |
376
374
  | --- | --- |
377
- | `convex/` | The relay: requests, rate limits and two HTTP routes, in 133 lines |
375
+ | `convex/` | The relay: requests, rate limits and two HTTP routes, in 154 lines |
378
376
  | `page/` | The page people see and its two themes, built into one HTML file by `scripts/page.ts` |
379
377
  | `src/hpke.ts` | The encryption, shared by the page and the agent |
380
378
  | `src/vault.ts` | `ask`, `wait`, `get`, `run` and the rest of the SDK |
@@ -11851,9 +11851,18 @@ function openVault({
11851
11851
  taken
11852
11852
  ))
11853
11853
  after[byContent.get(content).id] = { name, vault: byContent.get(content).vault };
11854
- const imported = new Map(
11855
- entries.filter((entry) => after[entry.id]).map((entry) => [after[entry.id].name, toItem(entry.content)])
11856
- );
11854
+ const imported = /* @__PURE__ */ new Map();
11855
+ for (const entry of entries) {
11856
+ const target = after[entry.id];
11857
+ if (!target) continue;
11858
+ const item = toItem(entry.content);
11859
+ const previous = before[entry.id];
11860
+ const proton = new Set(previous?.fields);
11861
+ const existing = previous?.fields ? await store.get(previous.name) ?? {} : {};
11862
+ const added = Object.entries(existing).filter(([field]) => !proton.has(field) && !(field in item));
11863
+ imported.set(target.name, { ...Object.fromEntries(added), ...item });
11864
+ target.fields = Object.keys(item);
11865
+ }
11857
11866
  for (const [name, item] of imported) await store.put(name, item);
11858
11867
  const removed = Object.values(before).filter(({ name, vault: vault2 }) => read2.includes(vault2) && !imported.has(name)).map(({ name }) => name);
11859
11868
  for (const name of removed) await store.remove(name);
package/dist/enclave.js CHANGED
@@ -2,12 +2,12 @@
2
2
  import {
3
3
  signRequest,
4
4
  vault
5
- } from "./chunk-RHLID3KU.js";
5
+ } from "./chunk-M5IM6SCO.js";
6
6
  import "./chunk-7QIF4PCL.js";
7
7
 
8
8
  // src/cli.ts
9
9
  import { text } from "node:stream/consumers";
10
- var usage = `enclave: a vault for your agent. Ask a person for a secret through a one-time link, and use it without seeing it
10
+ var usage = `enclave: ask a person for a secret through a one-time link, without ever seeing it
11
11
 
12
12
  enclave ask <name> [field...] print a link asking for <name> (fields default to password)
13
13
  enclave wait <name> wait for the answer and save it to the vault
@@ -48,7 +48,9 @@ try {
48
48
  } else if (command === "get" && name && rest[0]) {
49
49
  process.stdout.write(await vault.get(name, rest[0]));
50
50
  } else if (command === "save" && name && rest[0]) {
51
- await vault.save(name, rest[0], (await text(process.stdin)).replace(/\r?\n$/, ""));
51
+ const value = (await text(process.stdin)).replace(/\r?\n$/, "");
52
+ if (!value) throw new Error(`nothing came in to save as ${name}'s ${rest[0]}`);
53
+ await vault.save(name, rest[0], value);
52
54
  console.log(`saved ${name}: ${rest[0]}`);
53
55
  } else if (command === "passkey" && name) {
54
56
  await vault.passkey(name, rest[0]);
@@ -70,7 +72,7 @@ try {
70
72
  } else if (command === "rm" && name) {
71
73
  await vault.remove(name);
72
74
  } else if (command === "mcp") {
73
- const { serve } = await import("./mcp-7TGVJUG4.js");
75
+ const { serve } = await import("./mcp-VPAJVHAE.js");
74
76
  await serve(vault);
75
77
  } else {
76
78
  console.error(usage);
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  Store,
3
3
  openVault,
4
4
  vault
5
- } from "./chunk-RHLID3KU.js";
5
+ } from "./chunk-M5IM6SCO.js";
6
6
  import "./chunk-7QIF4PCL.js";
7
7
  export {
8
8
  Store,
@@ -16914,8 +16914,8 @@ var StdioServerTransport = class {
16914
16914
  // package.json
16915
16915
  var package_default = {
16916
16916
  name: "enclave-link",
16917
- version: "0.2.1",
16918
- description: "A vault for your agent: ask for secrets by encrypted one-time link, use them unseen, 2FA and passkeys too",
16917
+ version: "0.2.3",
16918
+ description: "Ask a person for a secret through a one-time link. It's encrypted in their browser, and only your agent can open it.",
16919
16919
  license: "MIT",
16920
16920
  bin: {
16921
16921
  enclave: "dist/enclave.js"
@@ -16968,7 +16968,7 @@ async function serve(vault) {
16968
16968
  "request_secret",
16969
16969
  {
16970
16970
  title: "Ask for a secret",
16971
- description: "Use this whenever you need a password, API key, 2FA key or any other secret from the person, instead of asking for it in chat. It gives you a one-time link: they open it, type the secret, and it's encrypted in their browser so only this vault can open it, without it ever passing through the conversation. Then call wait_for_secret.",
16971
+ description: "Use this whenever you need a password, API key, 2FA key or any other secret from the person, instead of asking for it in chat. It gives you a one-time link: they open it, type the secret, and it's encrypted in their browser for this vault alone, and it stays out of the conversation. Then call wait_for_secret.",
16972
16972
  inputSchema: {
16973
16973
  name: string().describe('What the secret is for. The person sees it as the title, like "Netflix" or "OpenAI API key"'),
16974
16974
  fields: array(string()).optional().describe(
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "enclave-link",
3
- "version": "0.2.1",
4
- "description": "A vault for your agent: ask for secrets by encrypted one-time link, use them unseen, 2FA and passkeys too",
3
+ "version": "0.2.3",
4
+ "description": "Ask a person for a secret through a one-time link. It's encrypted in their browser, and only your agent can open it.",
5
5
  "license": "MIT",
6
6
  "bin": {
7
7
  "enclave": "dist/enclave.js"