enclave-link 0.2.3 → 0.2.5

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
@@ -5,7 +5,7 @@
5
5
  </picture>
6
6
  </h1>
7
7
 
8
- Your AI agent needs your Netflix password. Instead of asking in chat, it sends you a link. You open it, type the password (or let your password manager fill it) and tap **Send**. It's locked in your browser before it leaves, so only your agent can open it. The agent keeps it in a vault of its own and uses it later without ever seeing it.
8
+ Your AI agent needs your Netflix password. Instead of asking in chat, it sends you a link. You type the password (or your password manager fills it) and tap **Send**. It's locked in your browser before it leaves, so only your agent can open it. The agent keeps it in a vault of its own, next to 2FA codes and passkeys, and uses it later without seeing it.
9
9
 
10
10
  <p>
11
11
  <picture>
@@ -34,7 +34,7 @@ It works out of the box with a hosted relay. No accounts, no keys to set up, not
34
34
  npx skills add AVGVSTVS96/enclave
35
35
  ```
36
36
 
37
- The skill teaches the agent to send a link instead of asking in chat, and runs enclave with `npx`, so there's nothing else to install.
37
+ It runs enclave with `npx`, so there's nothing else to install.
38
38
 
39
39
  **Agents that use MCP** get the same thing as tools. Add the server below, and add this line to the agent's instructions, because tools alone don't stop a model from asking in chat:
40
40
 
@@ -63,7 +63,7 @@ claude mcp add enclave -- npx -y enclave-link mcp
63
63
  codex mcp add enclave -- npx -y enclave-link mcp
64
64
  ```
65
65
 
66
- Codex stops a tool after 60 seconds by default. `wait_for_secret` waits up to 25 minutes a call (the agent calls it again if the person needs longer), so add this under `[mcp_servers.enclave]` in `~/.codex/config.toml`:
66
+ Codex stops a tool after 60 seconds by default, and `wait_for_secret` can wait 25 minutes, so add this under `[mcp_servers.enclave]` in `~/.codex/config.toml`:
67
67
 
68
68
  ```toml
69
69
  tool_timeout_sec = 1800
@@ -188,7 +188,7 @@ Run `goose configure`, choose **Add Extension → Command-line Extension**, ente
188
188
 
189
189
  </details>
190
190
 
191
- **ChatGPT, Claude.ai and the Gemini and Grok apps** can't use enclave yet. They run in the cloud with no computer of yours behind them, and enclave keeps the vault on the computer where the agent runs. What would make them work is an enclave server you run yourself that the app reaches over remote MCP; it doesn't exist yet. Their desktop and coding-agent versions above work today.
191
+ **ChatGPT, Claude.ai and the Gemini and Grok apps** can't use enclave yet: they run in the cloud, and the vault lives on the computer where the agent runs. Their desktop and coding-agent versions above work.
192
192
 
193
193
  **Your own code** can use the TypeScript SDK; see [SDK](#sdk).
194
194
 
@@ -232,11 +232,9 @@ A field named `passkey` holds a passkey, and the agent signs in with it in Chrom
232
232
  enclave passkey GitHub # then click the site's passkey sign-in
233
233
  ```
234
234
 
235
- `enclave passkey` arms the passkey for one sign-in, for up to 5 minutes. When the site asks for a passkey, enclave's Chrome extension hands the request to `enclave sign`, which signs it inside the vault and disarms the passkey. The private key never enters Chrome.
235
+ `enclave passkey` arms the passkey for one sign-in, for up to 5 minutes. When the site asks for a passkey, enclave's Chrome extension hands the request to `enclave sign`, which signs it inside the vault and disarms the passkey. The private key never leaves the vault: `enclave get` and `enclave run` refuse passkey fields.
236
236
 
237
- enclave signs the way Proton Pass does, with a signature counter of 0 every time. Sites read 0 as "this authenticator doesn't count", so a passkey keeps working from Proton and from enclave side by side.
238
-
239
- A passkey's private key never comes out: `enclave get` and `enclave run` refuse passkey fields, and only `enclave sign` uses them.
237
+ A passkey keeps working in Proton Pass and enclave side by side.
240
238
 
241
239
  Install the extension once per machine, from a clone of this repo, with Google Chrome on Linux. It's force-installed through Chrome's policy, so it loads in every profile:
242
240
 
@@ -252,9 +250,9 @@ enclave import proton # every vault
252
250
  enclave import proton Work # or just the ones you name
253
251
  ```
254
252
 
255
- copies [Proton Pass](https://proton.me/pass) in through [pass-cli](https://github.com/protonpass/pass-cli): logins, 2FA, passkeys, notes, cards, SSH keys and custom fields, one item per Proton item, named by its title (two items with the same title get the account or a number after it). Proton keeps everything. Run it again to bring in what changed: each Proton item keeps its name across imports, a renamed one follows its new title, one you deleted in Proton leaves enclave too, and items saved by link are left alone. `~/.enclave/proton.json` remembers which item is which, by Proton's IDs and names only.
253
+ copies [Proton Pass](https://proton.me/pass) in through [pass-cli](https://github.com/protonpass/pass-cli): logins, 2FA, passkeys, notes, cards, SSH keys and custom fields, one item per Proton item, named by its title. Proton keeps everything. Run it again to bring in what changed: names stay the same, renames follow, items deleted in Proton leave enclave too, and anything you saved yourself is kept.
256
254
 
257
- 2FA comes in as a `totp` field, so `enclave get <name> totp` gives the current code. Passkeys come in as a `passkey` field: Proton keeps each passkey's private key in the item, and enclave unpacks it the way Proton's own [export code](https://github.com/protonpass/proton-pass-common/blob/main/proton-pass-common/src/cxf/credentials/passkey.rs) does.
255
+ 2FA comes in as a `totp` field, so `enclave get <name> totp` gives the current code, and passkeys as a `passkey` field.
258
256
 
259
257
  ## CLI
260
258
 
@@ -329,7 +327,7 @@ The vault keeps each item encrypted in `~/.enclave/items`, sealed to the vault's
329
327
 
330
328
  What enclave can't protect against:
331
329
 
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).
330
+ - **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. 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).
333
331
  - **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.
334
332
  - **Phishing.** A link can say anyone is asking. Only send secrets to agents you asked for help.
335
333
  - **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.
@@ -11857,8 +11857,8 @@ function openVault({
11857
11857
  if (!target) continue;
11858
11858
  const item = toItem(entry.content);
11859
11859
  const previous = before[entry.id];
11860
- const proton = new Set(previous?.fields);
11861
- const existing = previous?.fields ? await store.get(previous.name) ?? {} : {};
11860
+ const existing = await store.get(previous?.name ?? target.name) ?? {};
11861
+ const proton = new Set(previous?.fields ?? (saved ? Object.keys(existing) : []));
11862
11862
  const added = Object.entries(existing).filter(([field]) => !proton.has(field) && !(field in item));
11863
11863
  imported.set(target.name, { ...Object.fromEntries(added), ...item });
11864
11864
  target.fields = Object.keys(item);
package/dist/enclave.js CHANGED
@@ -2,12 +2,12 @@
2
2
  import {
3
3
  signRequest,
4
4
  vault
5
- } from "./chunk-M5IM6SCO.js";
5
+ } from "./chunk-PB2UBW7U.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: ask a person for a secret through a one-time link, without ever seeing it
10
+ var usage = `enclave: ask a person for a secret by one-time link, and use it without 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
@@ -72,7 +72,7 @@ try {
72
72
  } else if (command === "rm" && name) {
73
73
  await vault.remove(name);
74
74
  } else if (command === "mcp") {
75
- const { serve } = await import("./mcp-VPAJVHAE.js");
75
+ const { serve } = await import("./mcp-LJPUESU6.js");
76
76
  await serve(vault);
77
77
  } else {
78
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-M5IM6SCO.js";
5
+ } from "./chunk-PB2UBW7U.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.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.",
16917
+ version: "0.2.5",
16918
+ description: "Your agent asks for a password by one-time link, not in chat, and uses it without seeing 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 for this vault alone, and it stays out of the conversation. Then call wait_for_secret.",
16971
+ description: "Use this whenever you need a password, API key, 2FA key or other secret from the person, instead of asking in chat. It returns a one-time link; what they type there is locked in their browser for this vault. 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(
@@ -16981,7 +16981,7 @@ async function serve(vault) {
16981
16981
  const { url } = await vault.ask(name, fields);
16982
16982
  if (server.server.getClientCapabilities()?.elicitation?.url) {
16983
16983
  const elicitationId = randomUUID();
16984
- const message = `Open this private link to enter ${name}. It's encrypted on your device and never passes through the chat.`;
16984
+ const message = `Open this link to enter ${name}. It's locked on this device before it's sent.`;
16985
16985
  const { action } = await server.server.elicitInput({ mode: "url", elicitationId, url, message });
16986
16986
  if (action === "accept") {
16987
16987
  elicitations.set(name, elicitationId);
@@ -17019,7 +17019,7 @@ It works once, for 24 hours. Then call wait_for_secret with name "${name}".`
17019
17019
  "run_with_secrets",
17020
17020
  {
17021
17021
  title: "Run a command with secrets",
17022
- description: 'Run a program with saved secrets in its environment. Their values are replaced with [hidden VAR] in the output, so you can use a secret without seeing it. Example: {"command": ["sh", "-c", "curl -H \\"Authorization: Bearer $KEY\\" https://api.example.com/me"], "secrets": {"KEY": "Example/api key"}}',
17022
+ description: 'Run a program with saved secrets in its environment. Their values are replaced with [hidden VAR] in the output, so you can use a secret without seeing it. A "totp" field gives its current 2FA code. Example: {"command": ["sh", "-c", "curl -H \\"Authorization: Bearer $KEY\\" https://api.example.com/me"], "secrets": {"KEY": "Example/api key"}}',
17023
17023
  inputSchema: {
17024
17024
  command: array(string()).min(1).describe(
17025
17025
  'The program and its arguments. Use ["sh", "-c", "\u2026"] for shell syntax, and refer to secrets as $VAR inside it.'
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "enclave-link",
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.",
3
+ "version": "0.2.5",
4
+ "description": "Your agent asks for a password by one-time link, not in chat, and uses it without seeing it.",
5
5
  "license": "MIT",
6
6
  "bin": {
7
7
  "enclave": "dist/enclave.js"