wyrm-mcp 8.6.0 → 8.7.0
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/BENCHMARKS.md +4 -1
- package/README.md +17 -5
- package/dist/autoconfig.js +30 -26
- package/dist/bridge.js +1 -1
- package/dist/cli.js +15 -13
- package/dist/cloud/cli.js +3 -3
- package/dist/cloud/crypto.js +1 -1
- package/dist/handlers/companion.js +10 -10
- package/dist/handlers/session.js +23 -21
- package/dist/http-fast.js +7 -7
- package/dist/index.js +2 -2
- package/dist/license-refresh.js +1 -0
- package/dist/license.js +2 -2
- package/dist/live-embedding-state.js +2 -0
- package/dist/providers/bundled-model.js +4 -0
- package/dist/providers/bundled.js +1 -0
- package/dist/providers/embedding-provider.js +1 -1
- package/dist/providers/wordpiece.js +1 -0
- package/dist/reindex.js +1 -1
- package/dist/revocations.js +1 -0
- package/dist/setup-wizard.js +1 -1
- package/dist/setup.js +4 -3
- package/dist/statusline.js +11 -11
- package/dist/tool-manifest-v2.json +1 -1
- package/dist/tool-manifest.json +1 -1
- package/dist/vector-init.js +1 -1
- package/dist/version-check.js +5 -5
- package/dist/wyrm-cli.js +144 -138
- package/dist/wyrm-manifest.json +1 -1
- package/package.json +10 -3
- package/scripts/hooks/wyrm-run-auto.mjs +142 -0
- package/scripts/hooks/wyrm-session-prune.mjs +3 -0
- package/scripts/hooks/wyrm-session-rehydrate.mjs +38 -11
- package/scripts/hooks/wyrm-tool-call-trace.mjs +3 -0
- package/scripts/postinstall.cjs +11 -66
package/BENCHMARKS.md
CHANGED
|
@@ -14,16 +14,19 @@ Questions with no evidence turns (LoCoMo categories 3 & 5 — adversarial / worl
|
|
|
14
14
|
|
|
15
15
|
## Results — real LoCoMo (1,982 evidence QA, k=10)
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
First verified on **wyrm-mcp 7.3.0**; rows added since then self-document their own version and measurement date (see the bundled-tier row below, 8.7.0).
|
|
18
18
|
|
|
19
19
|
| Tier | recall@1 | recall@5 | recall@10 | MRR@10 | Properties |
|
|
20
20
|
|------|:--------:|:--------:|:---------:|:------:|------------|
|
|
21
21
|
| **no-LLM FTS floor** | 29.5% | **52.4%** | **59.9%** | 0.393 | offline · zero-cloud · zero-LLM · CI-gated · reproducible anywhere |
|
|
22
22
|
| vector-only (local) | 28.8% | 58.7% | 69.5% | 0.413 | local Ollama embeddings only |
|
|
23
23
|
| **local hybrid** (FTS ⊕ vectors) | 32.8% | **60.3%** | **72.2%** | 0.447 | + local Ollama `nomic-embed-text` — still zero-cloud, advisory (varies by machine) |
|
|
24
|
+
| bundled ONNX int8 (default install) | 32.5% | 60.0% | 72.0% | 0.443 | + bundled `nomic-embed-text-v1.5-int8` (no Ollama, no setup) — within 0.3/0.2 pts of the Ollama hybrid row above; zero-config default as of 8.7.0. Bundled runs in its own vector scope; cross-provider similarity vs Ollama measured mean 0.868 (informational; spaces isolated by model id). |
|
|
24
25
|
|
|
25
26
|
The lift is monotonic and honest: **recall@10 climbs 59.9% → 69.5% → 72.2%** as you add local vectors on top of the lexical floor. The **FTS floor is the claim that matters**: it needs no model server, no API key, no network — clone the repo and you get the same numbers. The hybrid tier shows the lift from local dense vectors (the embedding model runs on *your* box; nothing leaves it). Wyrm's production `recall` uses a tuned convex score-blend (cand=50, α=0.7) that nudges hybrid recall@5 to ~62%; the table above reports the simpler RRF fusion the standalone harness ships, so the numbers match the reproduction command exactly.
|
|
26
27
|
|
|
28
|
+
The bundled row swaps only the embedder (int8 ONNX in-process, no Ollama) into the exact same harness — same RRF fusion, same cand=25, same evidence-based scoring — measured with `npm run eval:bundled` (`bench/locomo-hybrid.mjs --provider bundled`) on 2026-08-14: recall lands within the ship gate (±1.5 pts of 60.3/72.2). It does this from its own vector scope, not a shared one: bundled ONNX (int8) and Ollama's GGUF conversion of nomic-embed-text are independently-built, each individually sane, but measured mean cosine 0.868 apart (`bench/parity-bundled.mjs` / `npm run bench:parity`, informational, not gated) — too far apart to mix under one model id without silently degrading recall, which is why the bundled provider is model-scoped as `nomic-embed-text-v1.5-int8` rather than sharing Ollama's `nomic-embed-text` id. Retrieval recall still lands on target because it depends on relative ranking within each backend's own space, not on the two backends occupying the identical vector space.
|
|
29
|
+
|
|
27
30
|
### no-LLM FTS floor — by category (evidence-bearing only)
|
|
28
31
|
|
|
29
32
|
| Category | n | recall@1 | recall@5 | recall@10 | MRR@10 |
|
package/README.md
CHANGED
|
@@ -70,11 +70,11 @@ Most memory tools store successes. Wyrm also records dead-ends and blocks the re
|
|
|
70
70
|
|
|
71
71
|
### Recall that finds things by meaning, not just keywords
|
|
72
72
|
|
|
73
|
-
`wyrm_recall` runs keyword search (FTS5) and semantic search over a vector index, fuses them, and reranks. It is hybrid by default, no configuration required
|
|
73
|
+
`wyrm_recall` runs keyword search (FTS5) and semantic search over a vector index, fuses them, and reranks. It is hybrid by default, no configuration required, no account, and no hosted call: a small local embedding model downloads automatically the first time you run `wyrm-setup`. It also weighs recency, temporal cues, and confirmed reuse, so the memory that fits the moment ranks first. On a real-set LoCoMo benchmark committed in the repo, the deterministic no-LLM floor is recall@5 52.4% / recall@10 59.9%, and the bundled local model lifts that to recall@5 60.0% / recall@10 72.0% (in line with the published local-hybrid reference of 60.3% / 72.2%, see BENCHMARKS.md). For maximum accuracy, `wyrm upgrade` moves to hosted NVIDIA NIM retrieval (below).
|
|
74
74
|
|
|
75
75
|
### Local-first, and honest about egress
|
|
76
76
|
|
|
77
|
-
By default
|
|
77
|
+
By default no memory data leaves your machine. The database is a single SQLite file at `~/.wyrm/wyrm.db`. The only default outbound calls are a daily npm version poll and, on installs with a license key, a signed revocation-list fetch — both listed with their off switches in [`docs/EGRESS.md`](../../docs/EGRESS.md); the free tier's one-time model download is a plain fetch that sends nothing. When you do opt into a hosted embedding path, Wyrm reports exactly what left and where, in a determinism receipt and on its health endpoint. The privacy claim is one you can verify from the runtime, not just the docs.
|
|
78
78
|
|
|
79
79
|
### Every write leaves a receipt
|
|
80
80
|
|
|
@@ -90,12 +90,14 @@ Every artifact is tagged with where it came from: you, an agent, an import, or a
|
|
|
90
90
|
|
|
91
91
|
## NVIDIA NIM retrieval (optional)
|
|
92
92
|
|
|
93
|
-
|
|
93
|
+
Local vectors are on by default. NIM is the further step up, for embeddings and reranking, when accuracy is worth a hosted call. On a retrieval benchmark committed in the repo, recall@1 moved from 33% on the local baseline to 47% with NIM embeddings and 52% with NIM reranking added. It is an explicit opt-in, off by default, and the egress is disclosed on every call.
|
|
94
|
+
|
|
95
|
+
The guided path is `wyrm upgrade`: a free API key, masked key entry, a live call to validate the key before anything is written, and an optional one-time reindex of existing memories on the new tier. To configure it by hand instead:
|
|
94
96
|
|
|
95
97
|
```bash
|
|
96
98
|
export WYRM_VECTOR_PROVIDER=nim
|
|
97
99
|
export WYRM_RERANK_PROVIDER=nim
|
|
98
|
-
export
|
|
100
|
+
export NVIDIA_API_KEY=nvapi-...
|
|
99
101
|
```
|
|
100
102
|
|
|
101
103
|
<sub>Ghost Protocol (Pvt) Ltd is a member of NVIDIA Inception.</sub>
|
|
@@ -106,7 +108,17 @@ Claude (Code, desktop, web) · Cursor · GitHub Copilot · Windsurf · Codex, an
|
|
|
106
108
|
|
|
107
109
|
## Requirements
|
|
108
110
|
|
|
109
|
-
Node.js 22 or newer.
|
|
111
|
+
Node.js 22 or newer. Semantic recall is on by default via a small bundled local model (`wyrm-setup` downloads it, no account needed); `wyrm upgrade` adds hosted NVIDIA NIM retrieval for maximum accuracy. Already running Ollama for this? Existing setups keep working unchanged.
|
|
112
|
+
|
|
113
|
+
## Pricing
|
|
114
|
+
|
|
115
|
+
Wyrm is **free to use forever** — the local memory, the firewall, the recall, all of it. Paid plans add cloud sync across devices, encrypted snapshots, shared team memory, and support:
|
|
116
|
+
|
|
117
|
+
- **Pro** — $29/mo · cloud sync, encrypted snapshots, analytics, priority support
|
|
118
|
+
- **Team** — $199/mo · shared team memory, up to 25 seats, admin dashboard
|
|
119
|
+
- **Enterprise** — $499/mo · unlimited seats, SSO/SAML, custom SLA, on-premise option
|
|
120
|
+
|
|
121
|
+
Plans and self-serve checkout: **[account.ghosts.lk/pricing](https://account.ghosts.lk/pricing)**. Custom or on-premise, email [ryan@ghosts.lk](mailto:ryan@ghosts.lk).
|
|
110
122
|
|
|
111
123
|
## Community and feedback
|
|
112
124
|
|
package/dist/autoconfig.js
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
|
-
import{existsSync as d,readFileSync as p,writeFileSync as l,mkdirSync as P,copyFileSync as
|
|
1
|
+
import{existsSync as d,readFileSync as p,writeFileSync as l,mkdirSync as P,copyFileSync as q,chmodSync as j}from"fs";import{homedir as m,platform as C}from"os";import{join as c,dirname as w,resolve as V}from"path";import{fileURLToPath as N}from"url";import{spawnSync as v}from"child_process";function Q(){const e=m(),t=C(),o=t==="darwin"?c(e,"Library","Application Support","Code","User"):t==="win32"?c(e,"AppData","Roaming","Code","User"):c(e,".config","Code","User"),n=t==="darwin"?c(e,"Library","Application Support","Code - Insiders","User"):t==="win32"?c(e,"AppData","Roaming","Code - Insiders","User"):c(e,".config","Code - Insiders","User"),s=t==="darwin"?c(e,"Library","Application Support","Claude"):t==="win32"?c(e,"AppData","Roaming","Claude"):c(e,".config","claude"),r=t==="darwin"?c(e,".cursor"):t==="win32"?c(e,".cursor"):c(e,".cursor"),i=t==="darwin"?c(e,".codeium","windsurf"):t==="win32"?c(e,".codeium","windsurf"):c(e,".codeium","windsurf"),u=t==="darwin"?c(e,".config","zed"):t==="win32"?c(e,"AppData","Roaming","Zed"):c(e,".config","zed"),a=c(e,".continue");return[{id:"vscode-copilot",name:"VS Code (Copilot)",icon:"\u{1F4BB}",configPath:c(o,"settings.json"),configFormat:"vscode",mcpKey:"mcp.servers",detected:!1,configured:!1},{id:"vscode-insiders",name:"VS Code Insiders",icon:"\u{1F7E2}",configPath:c(n,"settings.json"),configFormat:"vscode",mcpKey:"mcp.servers",detected:!1,configured:!1},{id:"claude-desktop",name:"Claude Desktop",icon:"\u{1F916}",configPath:c(s,"claude_desktop_config.json"),configFormat:"mcp-json",mcpKey:"mcpServers",detected:!1,configured:!1},{id:"claude-code",name:"Claude Code",icon:"\u2733\uFE0F",configPath:c(e,".claude.json"),configFormat:"mcp-json",mcpKey:"mcpServers",detected:!1,configured:!1},{id:"cursor",name:"Cursor",icon:"\u{1F4D0}",configPath:c(r,"mcp.json"),configFormat:"mcp-json",mcpKey:"mcpServers",detected:!1,configured:!1},{id:"windsurf",name:"Windsurf",icon:"\u{1F3C4}",configPath:c(i,"mcp_config.json"),configFormat:"mcp-json",mcpKey:"mcpServers",detected:!1,configured:!1},{id:"zed",name:"Zed",icon:"\u26A1",configPath:c(u,"settings.json"),configFormat:"zed",mcpKey:"context_servers",detected:!1,configured:!1},{id:"continue",name:"Continue",icon:"\u{1F504}",configPath:c(a,"config.json"),configFormat:"mcp-json",mcpKey:"mcpServers",detected:!1,configured:!1}]}function Z(e){try{const t=C()==="win32"?"where":"which",o=v(t,[e],{encoding:"utf-8",timeout:5e3});return o.status===0&&!!o.stdout&&o.stdout.trim().length>0}catch{return!1}}function b(){const e=Q();for(const t of e){if(t.id==="claude-code")t.detected=d(t.configPath)||Z("claude");else{const o=w(t.configPath);t.detected=d(o)}if(t.detected&&d(t.configPath))try{const o=p(t.configPath,"utf-8"),n=$(o);t.configured=ee(n,t)}catch{t.configured=!1}t.detected&&(t.version=X(t))}return e}function X(e){try{switch(e.id){case"vscode-copilot":case"vscode-insiders":{const t=e.id==="vscode-insiders"?"code-insiders":"code",o=v(t,["--version"],{encoding:"utf-8",timeout:5e3});if(o.stdout)return o.stdout.split(`
|
|
2
2
|
`)[0];break}case"cursor":{const t=v("cursor",["--version"],{encoding:"utf-8",timeout:5e3});if(t.stdout)return t.stdout.split(`
|
|
3
3
|
`)[0];break}case"claude-code":{const t=v("claude",["--version"],{encoding:"utf-8",timeout:5e3});if(t.stdout)return t.stdout.trim().split(`
|
|
4
|
-
`)[0];break}}}catch{}}function ee(e,t){switch(t.configFormat){case"vscode":return e.mcp?.servers?.wyrm!==void 0;case"mcp-json":return e[t.mcpKey]?.wyrm!==void 0;case"zed":return e[t.mcpKey]?.wyrm!==void 0;default:return!1}}function
|
|
5
|
-
`,"utf-8");const u=e.configured?"updated":"configured";return e.configured=!0,{client:e,action:u,message:`${e.name} ${u==="configured"?"configured":"updated"} successfully`,backup:r}}catch(o){return{client:e,action:"failed",message:`Failed to configure ${e.name}: ${o}`}}}function
|
|
6
|
-
`,"utf-8"),
|
|
7
|
-
`,"utf-8"),
|
|
8
|
-
`,"utf-8"),{client:t,action:
|
|
9
|
-
`,"utf-8"),{client:t,action:"configured",message:"
|
|
10
|
-
`,"utf-8"),{client:t,action:"configured",message:"
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
`,"utf-8"),{client:
|
|
14
|
-
`,"utf-8")}
|
|
4
|
+
`)[0];break}}}catch{}}function ee(e,t){switch(t.configFormat){case"vscode":return e.mcp?.servers?.wyrm!==void 0;case"mcp-json":return e[t.mcpKey]?.wyrm!==void 0;case"zed":return e[t.mcpKey]?.wyrm!==void 0;default:return!1}}function T(){try{const n=v("which",["wyrm-mcp"],{encoding:"utf-8",timeout:5e3});if(n.stdout?.trim())return n.stdout.trim()}catch{}try{const s=v("npm",["root","-g"],{encoding:"utf-8",timeout:5e3}).stdout?.trim();if(s){const r=c(s,"wyrm-mcp","dist","index.js");if(d(r))return r}}catch{}const e=V(__dirname,"index.js");if(d(e))return e;const t=c(m(),".wyrm","node_modules","wyrm-mcp","dist","index.js");if(d(t))return t;const o=[c(m(),"Git Projects","Wyrm","packages","mcp-server","dist","index.js"),c(m(),"projects","Wyrm","packages","mcp-server","dist","index.js"),c(m(),"dev","Wyrm","packages","mcp-server","dist","index.js")];for(const n of o)if(d(n))return n;return"wyrm-mcp"}function D(){return c(m(),".wyrm","wyrm.db")}function ye(e="mcp-json",t){const o=t?.serverPath??T(),n=t?.dbPath??D(),s=o==="wyrm-mcp",r={command:s?"npx":"node",args:s?["wyrm-mcp"]:[o],env:{WYRM_DB_PATH:n}};switch(e){case"stdio":return r;case"vscode":return{mcp:{servers:{wyrm:r}}};default:return{mcpServers:{wyrm:r}}}}function te(e,t){const o=t.serverPath,n=o==="wyrm-mcp",s=o.endsWith(".js");switch(e.configFormat){case"vscode":return{command:n?"npx":"node",args:n?["wyrm-mcp"]:[o],env:{WYRM_DB_PATH:t.dbPath}};case"mcp-json":return{command:n?"npx":"node",args:n?["wyrm-mcp"]:[o],env:{WYRM_DB_PATH:t.dbPath}};case"zed":return{command:n?"npx":"node",args:n?["wyrm-mcp"]:[o],env:{WYRM_DB_PATH:t.dbPath}};default:return{}}}function U(e,t){if(!e.detected)return{client:e,action:"skipped",message:`${e.name} not detected`};try{const o=w(e.configPath);d(o)||P(o,{recursive:!0});let n={},s=!1;if(d(e.configPath))try{const a=p(e.configPath,"utf-8");n=$(a),s=!0}catch{n={}}let r;if(s){r=`${e.configPath}.wyrm-backup`,l(r,p(e.configPath),{mode:384});try{j(r,384)}catch{}}const i=te(e,t);switch(e.configFormat){case"vscode":{n.mcp||(n.mcp={});const a=n.mcp;a.servers||(a.servers={});const f=a.servers;f.wyrm=i;break}case"mcp-json":{n[e.mcpKey]||(n[e.mcpKey]={});const a=n[e.mcpKey];a.wyrm=i;break}case"zed":{n[e.mcpKey]||(n[e.mcpKey]={});const a=n[e.mcpKey];a.wyrm=i;break}}l(e.configPath,JSON.stringify(n,null,2)+`
|
|
5
|
+
`,"utf-8");const u=e.configured?"updated":"configured";return e.configured=!0,{client:e,action:u,message:`${e.name} ${u==="configured"?"configured":"updated"} successfully`,backup:r}}catch(o){return{client:e,action:"failed",message:`Failed to configure ${e.name}: ${o}`}}}function we(e,t){try{if(!d(e.configPath))return{client:e,action:"skipped",message:"No config file"};const o=p(e.configPath,"utf-8");let n;try{n=$(o)}catch{return{client:e,action:"failed",message:"Config file is not valid JSON; refusing to touch it"}}const r=(e.configFormat==="vscode"?n.mcp?.servers:n[e.mcpKey])?.wyrm;if(!r)return{client:e,action:"skipped",message:"Wyrm not configured in this client"};const i=`${e.configPath}.wyrm-backup`;l(i,o,{encoding:"utf-8",mode:384});try{j(i,384)}catch{}return r.env={...r.env,...t},l(e.configPath,JSON.stringify(n,null,2)+`
|
|
6
|
+
`,"utf-8"),{client:e,action:"updated",message:`env: ${Object.keys(t).join(", ")}`,backup:i}}catch(o){return{client:e,action:"failed",message:String(o)}}}function oe(e){if(!e.detected||!e.configured)return{client:e,action:"skipped",message:`${e.name} not configured`};try{if(!d(e.configPath))return{client:e,action:"skipped",message:`${e.name} config not found`};const t=p(e.configPath,"utf-8"),o=$(t),n=`${e.configPath}.wyrm-backup`;l(n,t,{mode:384});try{j(n,384)}catch{}switch(e.configFormat){case"vscode":{const r=o.mcp?.servers;r&&delete r.wyrm;break}case"mcp-json":case"zed":{const s=o[e.mcpKey];s&&delete s.wyrm;break}}return l(e.configPath,JSON.stringify(o,null,2)+`
|
|
7
|
+
`,"utf-8"),e.configured=!1,{client:e,action:"configured",message:`Removed Wyrm from ${e.name}`,backup:n}}catch(t){return{client:e,action:"failed",message:`Failed to remove from ${e.name}: ${t}`}}}function A(){return{id:"claude-code-hooks",name:"Claude Code (auto-memory)",icon:"\u{1FA9D}",configPath:c(m(),".claude","settings.json"),configFormat:"mcp-json",mcpKey:"",detected:!0,configured:!1}}const R=[{script:"wyrm-session-capture.mjs",events:["SessionEnd","PreCompact"]},{script:"wyrm-session-rehydrate.mjs",events:["SessionStart"]},{script:"wyrm-session-prune.mjs",events:["SessionEnd"]},{script:"wyrm-tool-call-trace.mjs",events:["PostToolUse"]},{script:"wyrm-run-auto.mjs",events:["SessionStart",{event:"PreToolUse",matcher:"Task"},"SessionEnd"]}];function ne(){const e=c(m(),".claude");if(!d(e))return null;const t=A();try{const o=c(e,"hooks");d(o)||P(o,{recursive:!0});const n=w(N(import.meta.url)),s=c(n,"..","scripts","hooks"),r=t.configPath;let i={};if(d(r))try{i=JSON.parse(p(r,"utf-8"))}catch{i={}}const u=i.hooks??{};let a=!1;const f=[];for(const h of R){const g=c(s,h.script);if(!d(g)){f.push(h.script);continue}const W=c(o,h.script);q(g,W);try{j(W,493)}catch{}const O=`node ${W}`;for(const k of h.events){const x=typeof k=="string"?k:k.event,M=typeof k=="string"?void 0:k.matcher,E=Array.isArray(u[x])?u[x]:[];if(!E.some(S=>Array.isArray(S.hooks)&&S.hooks.some(Y=>Y.command===O))){const S=M?{matcher:M,hooks:[{type:"command",command:O,timeout:30}]}:{hooks:[{type:"command",command:O,timeout:30}]};E.push(S),a=!0}u[x]=E}}i.hooks=u,l(r,JSON.stringify(i,null,2)+`
|
|
8
|
+
`,"utf-8"),t.configured=!0;const y=f.length?` (missing from package: ${f.join(", ")})`:"";return{client:t,action:a?"configured":"skipped",message:(a?"auto-memory hooks installed":"already installed")+y}}catch(o){return{client:t,action:"failed",message:o instanceof Error?o.message:String(o)}}}function ve(){const e=c(m(),".claude");if(!d(e))return null;const t=A();try{const o=w(N(import.meta.url)),n=`node ${c(o,"wyrm-statusline.js")}`,s=t.configPath;let r={};if(d(s))try{r=JSON.parse(p(s,"utf-8"))}catch{r={}}const i=r.statusLine;if(i?.command&&!/wyrm-statusline/.test(i.command))return{client:t,action:"skipped",message:`a non-Wyrm statusLine is already set (${i.command}); not overwriting`};const u=i?.command===n;return r.statusLine={type:"command",command:n,padding:0},l(s,JSON.stringify(r,null,2)+`
|
|
9
|
+
`,"utf-8"),{client:t,action:u?"skipped":"configured",message:u?"buddy statusline already installed":"Wyrm buddy statusline installed \u2014 persistent in the Claude Code TUI"}}catch(o){return{client:t,action:"failed",message:o instanceof Error?o.message:String(o)}}}function ke(){const e=c(m(),".claude","settings.json");if(!d(e))return null;const t=A();try{const o=JSON.parse(p(e,"utf-8")),n=o.statusLine;return n?.command&&/wyrm-statusline/.test(n.command)?(delete o.statusLine,l(e,JSON.stringify(o,null,2)+`
|
|
10
|
+
`,"utf-8"),{client:t,action:"configured",message:"Wyrm statusline removed"}):{client:t,action:"skipped",message:"no Wyrm statusline set"}}catch(o){return{client:t,action:"failed",message:o instanceof Error?o.message:String(o)}}}function re(){const e=c(m(),".claude","settings.json");if(!d(e))return null;const t=A();try{const o=JSON.parse(p(e,"utf-8")),n=c(m(),".claude","hooks"),s=new Set(R.map(u=>`node ${c(n,u.script)}`)),r=new Set(R.flatMap(u=>u.events.map(a=>typeof a=="string"?a:a.event))),i=o.hooks;if(i){for(const u of r)Array.isArray(i[u])&&(i[u]=i[u].map(a=>({...a,hooks:Array.isArray(a.hooks)?a.hooks.filter(f=>!s.has(f.command??"")):a.hooks})).filter(a=>!Array.isArray(a.hooks)||a.hooks.length>0),i[u].length===0&&delete i[u]);Object.keys(i).length===0&&delete o.hooks}return l(e,JSON.stringify(o,null,2)+`
|
|
11
|
+
`,"utf-8"),{client:t,action:"configured",message:"auto-memory hooks removed"}}catch(o){return{client:t,action:"failed",message:o instanceof Error?o.message:String(o)}}}const L=/wyrm-guard/,se=["Bash","Edit|Write|MultiEdit|NotebookEdit"];function _(e){return e.settingsPath??process.env.CLAUDE_SETTINGS??c(m(),".claude","settings.json")}function J(e){return{id:"claude-code-guard",name:"Claude Code (failure firewall)",icon:"\u{1F6E1}\uFE0F",configPath:e,configFormat:"mcp-json",mcpKey:"",detected:!0,configured:!1}}function B(e){return C()==="win32"?`"${e.replace(/"/g,'""')}"`:`'${e.replace(/'/g,"'\\''")}'`}function ie(){const e=w(N(import.meta.url)),t=c(e,"wyrm-guard.js");if(d(t))return`node ${B(t)}`;try{const o=C()==="win32"?"where":"which",s=v(o,["wyrm-guard"],{encoding:"utf-8",timeout:5e3}).stdout?.trim().split(`
|
|
12
|
+
`)[0]?.trim();if(s)return B(s)}catch{}return null}function I(e){const t=e.hooks;if(!t||typeof t!="object"||Array.isArray(t))return!1;let o=!1;for(const n of Object.keys(t)){const s=t[n];if(!Array.isArray(s))continue;let r=!1;const i=[];for(const u of s){const a=u;if(!a||typeof a!="object"||!Array.isArray(a.hooks)){i.push(u);continue}const f=a.hooks.filter(y=>!L.test(y?.command??""));if(f.length===a.hooks.length){i.push(u);continue}o=!0,r=!0,f.length>0&&i.push({...a,hooks:f})}r&&(i.length===0?delete t[n]:t[n]=i)}return o&&Object.keys(t).length===0&&delete e.hooks,o}function ce(e={}){if(!!!(e.settingsPath??process.env.CLAUDE_SETTINGS)&&!d(c(m(),".claude")))return{client:J(_(e)),action:"skipped",message:"failure firewall: ~/.claude not found on this machine (needs the Claude Code CLI). Not armed. Install Claude Code then re-run `wyrm guard`"};const o=_(e),n=J(o);try{const s=e.guardCommand??ie();if(!s)return{client:n,action:"skipped",message:"wyrm-guard bin not found (npm i -g wyrm-mcp, or build dist/) \u2014 firewall hooks not installed"};P(w(o),{recursive:!0});let r={};if(d(o))try{const g=JSON.parse(p(o,"utf-8"));if(typeof g!="object"||g===null||Array.isArray(g))throw new Error("not an object");r=g}catch{return{client:n,action:"failed",message:`${o} is not valid JSON \u2014 refusing to overwrite it (fix or remove the file, then re-run)`}}const i=r.hooks;if(i!==void 0&&(typeof i!="object"||i===null||Array.isArray(i)))return{client:n,action:"failed",message:`${o} "hooks" has an unexpected shape (not a plain object) \u2014 refusing to modify (fix the file, then re-run)`};const u=i?.PreToolUse;if(u!==void 0&&!Array.isArray(u))return{client:n,action:"failed",message:`${o} hooks.PreToolUse has an unexpected shape (not an array) \u2014 refusing to modify (fix the file, then re-run)`};const a=JSON.stringify(r);I(r);const f=r.hooks??{},y=Array.isArray(f.PreToolUse)?f.PreToolUse:[];for(const g of se)y.push({matcher:g,hooks:[{type:"command",command:s,timeout:1}]});f.PreToolUse=y,r.hooks=f;const h=JSON.stringify(r)!==a;return h&&l(o,JSON.stringify(r,null,2)+`
|
|
13
|
+
`,"utf-8"),n.configured=!0,{client:n,action:h?"configured":"skipped",message:h?"failure-firewall hooks installed (PreToolUse: Bash + Edit|Write|MultiEdit|NotebookEdit)":"failure-firewall hooks already installed"}}catch(s){return{client:n,action:"failed",message:s instanceof Error?s.message:String(s)}}}function ae(e={}){const t=_(e);if(!d(t))return null;const o=J(t);try{let n;try{const r=JSON.parse(p(t,"utf-8"));if(typeof r!="object"||r===null||Array.isArray(r))throw new Error("not an object");n=r}catch{return{client:o,action:"failed",message:`${t} is not valid JSON \u2014 not touching it`}}return I(n)?(l(t,JSON.stringify(n,null,2)+`
|
|
14
|
+
`,"utf-8"),{client:o,action:"configured",message:"failure-firewall hooks removed"}):{client:o,action:"skipped",message:"no wyrm-guard hooks installed"}}catch(n){return{client:o,action:"failed",message:n instanceof Error?n.message:String(n)}}}function Pe(e={}){const t=_(e),o=[];try{const s=JSON.parse(p(t,"utf-8")).hooks??{};for(const r of Object.values(s))if(Array.isArray(r))for(const i of r)for(const u of i?.hooks??[]){const a=u?.command;a&&L.test(a)&&o.push(a)}}catch{}return{settingsPath:t,installed:o.length>0,commands:o}}function ue(e){const t={serverPath:e?.serverPath||T(),dbPath:e?.dbPath||D(),httpPort:e?.httpPort},o=c(m(),".wyrm");d(o)||P(o,{recursive:!0}),H(t);const n=b(),s=[];for(const u of n)s.push(U(u,t));const r=ne();r&&s.push(r);const i=ce();return i&&s.push(i),s}function be(){const e=b(),t=[];for(const s of e)t.push(oe(s));const o=re();o&&t.push(o);const n=ae();return n&&t.push(n),t}function de(e,t){const o={serverPath:t?.serverPath||T(),dbPath:t?.dbPath||D(),httpPort:t?.httpPort};H(o);const n=b(),s=[];for(const r of n)e.includes(r.id)&&s.push(U(r,o));return s}function H(e){const t=c(m(),".wyrm","wyrm-config.json"),o=b().filter(s=>s.configured).map(s=>s.id),n={version:"3.0.0",serverPath:e.serverPath,dbPath:e.dbPath,httpPort:e.httpPort,configuredClients:o,lastSetup:new Date().toISOString(),autoUpdate:!0};l(t,JSON.stringify(n,null,2)+`
|
|
15
|
+
`,"utf-8")}function G(){const e=c(m(),".wyrm","wyrm-config.json");if(!d(e))return null;try{return JSON.parse(p(e,"utf-8"))}catch{return null}}function Se(){const e=G();return e?de(e.configuredClients,{serverPath:e.serverPath,dbPath:e.dbPath,httpPort:e.httpPort}):ue()}const z="<!-- wyrm:start -->",K="<!-- wyrm:end -->",F=`${z}
|
|
15
16
|
## Wyrm Memory + Agent (auto-managed \u2014 do not edit this section)
|
|
16
17
|
|
|
17
18
|
**Session start** \u2014 call \`wyrm_session_prime\` first to load project context (ground truths, open quests, recent sessions, scaffolds) before responding.
|
|
@@ -35,20 +36,23 @@ import{existsSync as d,readFileSync as p,writeFileSync as l,mkdirSync as P,copyF
|
|
|
35
36
|
**Keep wyrm-mcp current** \u2014 \`wyrm_check_update\` returns whether a newer version is on npm (cached 24h). When one is, surface it to the operator and offer \`wyrm_self_update\` (with \`confirm:true\`) to upgrade.
|
|
36
37
|
|
|
37
38
|
**Buddy companion** \u2014 call \`wyrm_buddy\` for warm, data-grounded check-ins. At session start (after \`wyrm_session_prime\`) for long-running work, and when the operator completes a quest or hits a milestone. The buddy speaks in a chosen persona but every claim sources a real DB row \u2014 speak it verbatim, never invent additional encouragement. Auto-federates with other registered MCPs that expose \`*_buddy\` tools.
|
|
38
|
-
${
|
|
39
|
+
${K}`;function je(e,t){const o={injected:[],skipped:[],errors:[]},n=(r,i)=>{try{const u=w(r);if(d(u)||P(u,{recursive:!0}),d(r)){const a=p(r,"utf-8"),f=a.indexOf(z),y=a.indexOf(K);if(f!==-1&&y!==-1&&y>f){const h=a.slice(0,f),g=a.slice(y+K.length);l(r,`${h}${F}${g}`,"utf-8")}else{const h=a.endsWith(`
|
|
39
40
|
`)?`
|
|
40
41
|
`:`
|
|
41
42
|
|
|
42
|
-
`;l(r,`${a}${h}${
|
|
43
|
-
`,"utf-8")}}else l(r,`${
|
|
44
|
-
`,"utf-8");o.injected.push(
|
|
45
|
-
|
|
46
|
-
`;s
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
`;
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
43
|
+
`;l(r,`${a}${h}${F}
|
|
44
|
+
`,"utf-8")}}else l(r,`${F}
|
|
45
|
+
`,"utf-8");o.injected.push(i)}catch(u){o.errors.push(`${i}: ${u}`)}},s=t.length===0?["copilot","cursor"]:t;for(const r of s){const i=r.toLowerCase();if(i==="copilot"||i==="vscode-copilot"){const u=c(e,".github","copilot-instructions.md");n(u,".github/copilot-instructions.md")}else if(i==="cursor"){const u=c(e,".cursor","rules");n(u,".cursor/rules")}else o.skipped.push(r)}return o}function $(e){let t="",o=!1;for(let n=0;n<e.length;n++){const s=e[n];if(o){if(t+=s,s==="\\"){t+=e[++n]??"";continue}s==='"'&&(o=!1);continue}if(s==='"'){o=!0,t+=s;continue}if(s==="/"&&e[n+1]==="/"){for(;n<e.length&&e[n]!==`
|
|
46
|
+
`;)n++;t+=`
|
|
47
|
+
`;continue}if(s==="/"&&e[n+1]==="*"){for(n+=2;n<e.length&&!(e[n]==="*"&&e[n+1]==="/");)n++;n+=1;continue}if(s===","){let r=n+1;for(;;){for(;r<e.length&&/\s/.test(e[r]);)r++;if(e[r]==="/"&&e[r+1]==="/"){for(;r<e.length&&e[r]!==`
|
|
48
|
+
`;)r++;continue}if(e[r]==="/"&&e[r+1]==="*"){for(r+=2;r<e.length&&!(e[r]==="*"&&e[r+1]==="/");)r++;r+=2;continue}break}if(e[r]==="}"||e[r]==="]")continue}t+=s}return JSON.parse(t)}function Ce(){const e=b(),t=e.filter(r=>r.detected),o=e.filter(r=>r.configured);let n=`\u{F115D} Wyrm Auto-Configure Status
|
|
49
|
+
|
|
50
|
+
`;n+=` Detected: ${t.length}/${e.length} AI clients
|
|
51
|
+
`,n+=` Configured: ${o.length}/${t.length} clients
|
|
52
|
+
|
|
53
|
+
`;for(const r of e){const i=r.detected?r.configured?" \u25CF":" \u25D0":" \u25CB",u=r.version?` (${r.version})`:"",a=r.detected?r.configured?"configured \u2713":"detected \u2014 not configured":"not found";n+=`${i} ${r.icon} ${r.name}${u}: ${a}
|
|
54
|
+
`}const s=G();return s&&(n+=`
|
|
55
|
+
Server: ${s.serverPath}
|
|
56
|
+
`,n+=` DB: ${s.dbPath}
|
|
57
|
+
`,n+=` Last: ${new Date(s.lastSetup).toLocaleString()}
|
|
58
|
+
`),n}export{F as WYRM_INJECT_BLOCK,we as applyWyrmEnvToClient,ue as autoConfigureAll,U as configureClient,de as configureSpecific,b as detectClients,ye as exportServerConfig,T as findWyrmServerPath,D as getDefaultDbPath,Ce as getStatusSummary,je as injectSystemPrompt,ne as installClaudeCodeHooks,ve as installClaudeStatusline,ce as installWyrmGuardHooks,G as loadWyrmMeta,$ as parseJsonWithComments,B as quoteHookPath,Se as reconfAll,re as removeClaudeCodeHooks,ke as removeClaudeStatusline,be as removeFromAll,oe as removeFromClient,ae as removeWyrmGuardHooks,ie as resolveWyrmGuardCommand,Pe as wyrmGuardHookStatus};
|
package/dist/bridge.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{readFileSync as h,writeFileSync as D,readdirSync as k,existsSync as F}from"fs";import{createHash as _}from"crypto";import{join as y}from"path";import{homedir as g}from"os";import{MemoryArtifacts as I}from"./memory-artifacts.js";import{recordWrite as R}from"./receipts.js";import{buildRenderPlan as L,renderToDisk as b,renderForClient as q,makeRenderDeps as A}from"./render-target.js";import{makeBridgeDeps as x,sweepProject as $}from"./reverse-bridge.js";function B(){return process.env.WYRM_BRIDGE_CONFIG??y(g(),".wyrm","bridge.json")}function C(){return process.env.WYRM_BRIDGE_STATE??y(g(),".wyrm","bridge-state.json")}function ee(){try{return JSON.parse(h(B(),"utf8"))}catch{return null}}function W(){try{return JSON.parse(h(C(),"utf8"))}catch{return{}}}function v(t){try{D(C(),JSON.stringify(t,null,1))}catch{}}function H(t,i,r){const n=r??y(g(),".wyrm","remember-distill-state.json");if(!F(n))return 0;let c=0;try{const e=JSON.parse(h(n,"utf8"));for(const[d,a]of Object.entries(e)){const s=`${t}:${d}`;i[s]||(i[s]=a,c++)}}catch{}return c}const N=t=>_("sha1").update(t).digest("hex");function J(t,i,r,n,c={}){const e={source:r.name,type:"remember",stored:0,dropped:0,queued:0,rendered:0},d=r.windowDays&&r.windowDays>=1?r.windowDays:7,a=r.maxPerRun&&r.maxPerRun>=1?r.maxPerRun:80,s=t.prepare("SELECT id FROM projects WHERE path = ?").get(r.projectPath);if(!s)return e.note=`no project at ${r.projectPath}`,e;const p=new Date(Date.now()-d*864e5);let u=[];try{u=k(r.dir).filter(o=>/^today-\d{4}-\d{2}-\d{2}(\.done)?\.md$/.test(o)).map(o=>({f:o,date:o.match(/\d{4}-\d{2}-\d{2}/)[0]})).filter(({date:o})=>new Date(o+"T00:00:00")>=p).sort((o,m)=>m.date.localeCompare(o.date))}catch{return e.note=`cannot read ${r.dir}`,e}let f=0;for(const{f:o,date:m}of u){const S=`${r.name}:today-${m}`,w=new Set(n[S]??[]);let E="";try{E=h(y(r.dir,o),"utf8")}catch{continue}const O=E.split(/^## /m).slice(1).map(l=>"## "+l.trim());for(const l of O){if(f>=a)break;if(l.length<30)continue;const j=N(l);if(w.has(j))continue;if(c.dryRun){f++,e.stored++;continue}const P=`[bridge:${r.name} ${m}] ${l}`.slice(0,2400),T=i.add(s.id,{kind:"pattern",problem:P,confidence:.75,needsReview:0,createdBy:`bridge:${r.name}`});R(t,{tool:"wyrm_bridge",outcome:"stored",refTable:"mem",refId:T.id,source:`bridge:${r.name}`,projectId:s.id}),w.add(j),f++,e.stored++}n[S]=[...w]}return f>=a&&(e.note=`hit per-run cap ${a} \u2014 remainder next run`),e}async function M(t,i,r,n={}){const c={source:i.name,type:"render",stored:0,dropped:0,queued:0,rendered:0},e=t.prepare("SELECT id, name, path FROM projects WHERE path = ?").get(i.projectPath);if(!e)return c.note=`no project at ${i.projectPath}`,c;const d=i.clients??[],a=A(t);try{const s=L(a,e,r),p={"MEMORY.md":s.memoryMd};for(const f of d){const o=q(f,s.model,r);p[o.relPath]=o.block}const u=await $(x(t),{id:e.id,path:e.path},p,{dryRun:n.dryRun});c.queued+=u.added,u.added>0&&R(t,{tool:"wyrm_bridge",outcome:"queued",reason:`render guard \u2014 ${u.added} human edit(s) harvested to review before overwrite`,source:`bridge:${i.name}`,projectId:e.id})}catch{}if(!n.dryRun){const{writes:s}=b(a,e,r,{clients:d});c.rendered=s.filter(p=>p.action!=="skipped").length}return c}async function G(t,i,r={}){const n={source:i.name,type:"reverse",stored:0,dropped:0,queued:0,rendered:0},c=t.prepare("SELECT id, path FROM projects WHERE path = ?").get(i.projectPath);if(!c)return n.note=`no project at ${i.projectPath}`,n;const e=await $(x(t),c,{},{dryRun:r.dryRun});return n.queued=e.added,n.dropped=e.skipped,e.added>0&&R(t,{tool:"wyrm_bridge",outcome:"queued",reason:`reverse sweep \u2014 ${e.added} native-file edit(s) queued for review`,source:`bridge:${i.name}`,projectId:c.id}),n}async function te(t,i,r,n={}){const c=new I(t),e=W(),d=[];for(const a of i.sources)n.only&&a.name!==n.only||(a.type==="remember"?(H(a.name,e)>0&&v(e),d.push(J(t,c,a,e,n))):a.type==="render"?d.push(await M(t,a,r,n)):d.push(await G(t,a,n)));return n.dryRun||v(e),d}async function re(t,i,r,n={}){const c=Math.max(2,n.intervalSec??15)*1e3,e=i.sources.filter(p=>p.type==="render");if(e.length===0)return;const d=new Map;for(const p of e){const u=t.prepare("SELECT id FROM projects WHERE path = ?").get(p.projectPath);u&&d.set(p.name,u.id)}let a=n.fromCursor??t.prepare("SELECT COALESCE(MAX(cursor), 0) AS c FROM events").get().c,s=0;for(;n.iterations==null||s<n.iterations;){s++,await new Promise(o=>setTimeout(o,c));const p=t.prepare("SELECT DISTINCT project_id FROM events WHERE cursor > ?").all(a);if(a=t.prepare("SELECT COALESCE(MAX(cursor), 0) AS c FROM events").get().c,p.length===0)continue;const u=new Set(p.map(o=>String(o.project_id))),f=[];for(const o of e){const m=d.get(o.name);if(!(m==null||!u.has(String(m))))try{f.push(await M(t,o,r()))}catch{}}f.length>0&&n.onCycle?.(f)}}export{H as adoptLegacyDistillState,B as configPath,ee as loadConfig,W as loadState,te as runBridge,J as runRememberSource,M as runRenderSource,G as runReverseSource,v as saveState,C as statePath,re as watchBridge};
|
|
1
|
+
import{readFileSync as h,writeFileSync as D,readdirSync as k,existsSync as F}from"fs";import{createHash as _}from"crypto";import{join as y}from"path";import{homedir as g}from"os";import{MemoryArtifacts as I}from"./memory-artifacts.js";import{recordWrite as R}from"./receipts.js";import{buildRenderPlan as L,renderToDisk as b,renderForClient as q,makeRenderDeps as A}from"./render-target.js";import{makeBridgeDeps as x,sweepProject as $}from"./reverse-bridge.js";function B(){return process.env.WYRM_BRIDGE_CONFIG??y(g(),".wyrm","bridge.json")}function C(){return process.env.WYRM_BRIDGE_STATE??y(g(),".wyrm","bridge-state.json")}function ee(){try{return JSON.parse(h(B(),"utf8"))}catch{return null}}function W(){try{return JSON.parse(h(C(),"utf8"))}catch{return{}}}function v(t){try{D(C(),JSON.stringify(t,null,1))}catch{}}function H(t,i,r){const n=r??y(g(),".wyrm","remember-distill-state.json");if(!F(n))return 0;let c=0;try{const e=JSON.parse(h(n,"utf8"));for(const[d,a]of Object.entries(e)){const s=`${t}:${d}`;i[s]||(i[s]=a,c++)}}catch{}return c}const N=t=>_("sha1").update(t).digest("hex");function J(t,i,r,n,c={}){const e={source:r.name,type:"remember",stored:0,dropped:0,queued:0,rendered:0},d=r.windowDays&&r.windowDays>=1?r.windowDays:7,a=r.maxPerRun&&r.maxPerRun>=1?r.maxPerRun:80,s=t.prepare("SELECT id FROM projects WHERE path = ?").get(r.projectPath);if(!s)return e.note=`no project at ${r.projectPath}`,e;const p=new Date(Date.now()-d*864e5);let u=[];try{u=k(r.dir).filter(o=>/^today-\d{4}-\d{2}-\d{2}(\.done)?\.md$/.test(o)).map(o=>({f:o,date:o.match(/\d{4}-\d{2}-\d{2}/)[0]})).filter(({date:o})=>new Date(o+"T00:00:00")>=p).sort((o,m)=>m.date.localeCompare(o.date))}catch{return e.note=`cannot read ${r.dir}`,e}let f=0;for(const{f:o,date:m}of u){const S=`${r.name}:today-${m}`,w=new Set(n[S]??[]);let E="";try{E=h(y(r.dir,o),"utf8")}catch{continue}const O=E.split(/^## /m).slice(1).map(l=>"## "+l.trim());for(const l of O){if(f>=a)break;if(l.length<30)continue;const j=N(l);if(w.has(j))continue;if(c.dryRun){f++,e.stored++;continue}const P=`[bridge:${r.name} ${m}] ${l}`.slice(0,2400),T=i.add(s.id,{kind:"pattern",problem:P,confidence:.75,needsReview:0,createdBy:`bridge:${r.name}`});R(t,{tool:"wyrm_bridge",outcome:"stored",refTable:"mem",refId:T.id,source:`bridge:${r.name}`,projectId:s.id}),w.add(j),f++,e.stored++}n[S]=[...w]}return f>=a&&(e.note=`hit per-run cap ${a} \u2014 remainder next run`),e}async function M(t,i,r,n={}){const c={source:i.name,type:"render",stored:0,dropped:0,queued:0,rendered:0},e=t.prepare("SELECT id, name, path FROM projects WHERE path = ?").get(i.projectPath);if(!e)return c.note=`no project at ${i.projectPath}`,c;const d=i.clients??[],a=A(t);try{const s=L(a,e,r),p={"MEMORY.md":s.memoryMd};for(const f of d){const o=q(f,s.model,r);p[o.relPath]=o.block}const u=await $(x(t),{id:e.id,path:e.path},p,{dryRun:n.dryRun});c.queued+=u.added,u.added>0&&R(t,{tool:"wyrm_bridge",outcome:"queued",reason:`render guard \u2014 ${u.added} human edit(s) harvested to review before overwrite`,source:`bridge:${i.name}`,projectId:e.id})}catch{}if(!n.dryRun){const{writes:s}=b(a,e,r,{clients:d});c.rendered=s.filter(p=>p.action!=="skipped").length}return c}async function G(t,i,r={}){const n={source:i.name,type:"reverse",stored:0,dropped:0,queued:0,rendered:0},c=t.prepare("SELECT id, path FROM projects WHERE path = ?").get(i.projectPath);if(!c)return n.note=`no project at ${i.projectPath}`,n;const e=await $(x(t),c,{},{dryRun:r.dryRun});return n.queued=e.added,n.dropped=e.skipped,e.added>0&&R(t,{tool:"wyrm_bridge",outcome:"queued",reason:`reverse sweep \u2014 ${e.added} native-file edit(s) queued for review`,source:`bridge:${i.name}`,projectId:c.id}),n}async function te(t,i,r,n={}){const c=new I(t),e=W(),d=[];for(const a of i.sources)n.only&&a.name!==n.only||(a.type==="remember"?(H(a.name,e)>0&&v(e),d.push(J(t,c,a,e,n))):a.type==="render"?d.push(await M(t,a,r,n)):d.push(await G(t,a,n)));return n.dryRun||v(e),d}async function re(t,i,r,n={}){const c=Math.max(2,n.intervalSec??15)*1e3,e=i.sources.filter(p=>p.type==="render");if(e.length===0)return;const d=new Map;for(const p of e){const u=t.prepare("SELECT id FROM projects WHERE path = ?").get(p.projectPath);u&&d.set(p.name,u.id)}let a=n.fromCursor??t.prepare("SELECT COALESCE(MAX(cursor), 0) AS c FROM events").get().c,s=0;for(;n.iterations==null||s<n.iterations;){s++,await new Promise(o=>{setTimeout(o,c)});const p=t.prepare("SELECT DISTINCT project_id FROM events WHERE cursor > ?").all(a);if(a=t.prepare("SELECT COALESCE(MAX(cursor), 0) AS c FROM events").get().c,p.length===0)continue;const u=new Set(p.map(o=>String(o.project_id))),f=[];for(const o of e){const m=d.get(o.name);if(!(m==null||!u.has(String(m))))try{f.push(await M(t,o,r()))}catch{}}f.length>0&&n.onCycle?.(f)}}export{H as adoptLegacyDistillState,B as configPath,ee as loadConfig,W as loadState,te as runBridge,J as runRememberSource,M as runRenderSource,G as runReverseSource,v as saveState,C as statePath,re as watchBridge};
|
package/dist/cli.js
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
1
|
-
const t={reset:"\x1B[0m",bold:"\x1B[1m",dim:"\x1B[2m",italic:"\x1B[3m",underline:"\x1B[4m",black:"\x1B[30m",red:"\x1B[31m",green:"\x1B[32m",yellow:"\x1B[33m",blue:"\x1B[34m",magenta:"\x1B[35m",cyan:"\x1B[36m",white:"\x1B[37m",brightRed:"\x1B[91m",brightGreen:"\x1B[92m",brightYellow:"\x1B[93m",brightBlue:"\x1B[94m",brightMagenta:"\x1B[95m",brightCyan:"\x1B[96m",bgRed:"\x1B[41m",bgGreen:"\x1B[42m",bgYellow:"\x1B[43m",bgBlue:"\x1B[44m",bgMagenta:"\x1B[45m",bgCyan:"\x1B[46m"},
|
|
1
|
+
import{readFileSync as b}from"fs";import{dirname as f,join as x}from"path";import{fileURLToPath as w}from"url";function g(){try{const e=f(w(import.meta.url));return JSON.parse(b(x(e,"..","package.json"),"utf-8")).version??"dev"}catch{return"dev"}}const t={reset:"\x1B[0m",bold:"\x1B[1m",dim:"\x1B[2m",italic:"\x1B[3m",underline:"\x1B[4m",black:"\x1B[30m",red:"\x1B[31m",green:"\x1B[32m",yellow:"\x1B[33m",blue:"\x1B[34m",magenta:"\x1B[35m",cyan:"\x1B[36m",white:"\x1B[37m",brightRed:"\x1B[91m",brightGreen:"\x1B[92m",brightYellow:"\x1B[93m",brightBlue:"\x1B[94m",brightMagenta:"\x1B[95m",brightCyan:"\x1B[96m",bgRed:"\x1B[41m",bgGreen:"\x1B[42m",bgYellow:"\x1B[43m",bgBlue:"\x1B[44m",bgMagenta:"\x1B[45m",bgCyan:"\x1B[46m"},l={bold:e=>`${t.bold}${e}${t.reset}`,dim:e=>`${t.dim}${e}${t.reset}`,red:e=>`${t.red}${e}${t.reset}`,green:e=>`${t.green}${e}${t.reset}`,yellow:e=>`${t.yellow}${e}${t.reset}`,blue:e=>`${t.blue}${e}${t.reset}`,magenta:e=>`${t.magenta}${e}${t.reset}`,cyan:e=>`${t.cyan}${e}${t.reset}`,primary:e=>`${t.brightMagenta}${e}${t.reset}`,success:e=>`${t.brightGreen}${e}${t.reset}`,warning:e=>`${t.brightYellow}${e}${t.reset}`,error:e=>`${t.brightRed}${e}${t.reset}`},m={dragon:"\u{F115D}",fire:"\u{1F525}",scroll:"\u{1F4DC}",sword:"\u2694\uFE0F",shield:"\u{1F6E1}\uFE0F",treasure:"\u{1F48E}",quest:"\u{1F3AF}",check:"\u2713",cross:"\u2717",arrow:"\u2192",bullet:"\u2022",star:"\u2605",warning:"\u26A0\uFE0F",error:"\u274C",success:"\u2705",info:"\u2139\uFE0F",clock:"\u{1F550}",folder:"\u{1F4C1}",file:"\u{1F4C4}",lock:"\u{1F512}",unlock:"\u{1F513}",sync:"\u{1F504}",search:"\u{1F50D}",sparkle:"\u2728"},R={critical:"\u{1F534}",high:"\u{1F7E0}",medium:"\u{1F7E1}",low:"\u{1F7E2}"};function v(){return`
|
|
2
2
|
${t.brightMagenta}\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557${t.reset}
|
|
3
3
|
${t.brightMagenta}\u2588\u2588\u2551 \u2588\u2588\u2551\u255A\u2588\u2588\u2557 \u2588\u2588\u2554\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2551${t.reset}
|
|
4
4
|
${t.brightMagenta}\u2588\u2588\u2551 \u2588\u2557 \u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2554\u2588\u2588\u2588\u2588\u2554\u2588\u2588\u2551${t.reset}
|
|
5
5
|
${t.brightMagenta}\u2588\u2588\u2551\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u255A\u2588\u2588\u2554\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551\u255A\u2588\u2588\u2554\u255D\u2588\u2588\u2551${t.reset}
|
|
6
6
|
${t.brightMagenta}\u255A\u2588\u2588\u2588\u2554\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2550\u255D \u2588\u2588\u2551${t.reset}
|
|
7
7
|
${t.brightMagenta} \u255A\u2550\u2550\u255D\u255A\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D${t.reset}
|
|
8
|
-
${t.dim} Persistent AI Memory System
|
|
8
|
+
${t.dim} Persistent AI Memory System v${g()}${t.reset}
|
|
9
9
|
${t.dim} ghosts.lk${t.reset}
|
|
10
|
-
|
|
11
|
-
`),
|
|
12
|
-
`,o&&(
|
|
13
|
-
`);for(const
|
|
14
|
-
`;return o&&(
|
|
15
|
-
`),o=Math.max(e.length,...
|
|
16
|
-
`;
|
|
17
|
-
`,
|
|
18
|
-
`;for(const c of s
|
|
19
|
-
`;return
|
|
20
|
-
${
|
|
10
|
+
`}function k(){return`${t.brightMagenta}\u{F115D} Wyrm v${g()}${t.reset}`}class E{frames=["\u280B","\u2819","\u2839","\u2838","\u283C","\u2834","\u2826","\u2827","\u2807","\u280F"];current=0;interval=null;text;constructor(r="Loading..."){this.text=r}start(){this.interval=setInterval(()=>{process.stdout.write(`\r${t.cyan}${this.frames[this.current]}${t.reset} ${this.text}`),this.current=(this.current+1)%this.frames.length},80)}update(r){this.text=r}succeed(r){this.stop(),console.log(`\r${l.success("\u2713")} ${r||this.text}`)}fail(r){this.stop(),console.log(`\r${l.error("\u2717")} ${r||this.text}`)}warn(r){this.stop(),console.log(`\r${l.warning("!")} ${r||this.text}`)}stop(){this.interval&&(clearInterval(this.interval),this.interval=null,process.stdout.write("\r\x1B[K"))}}class j{total;current=0;width;label;constructor(r,n="Progress",i=30){this.total=r,this.width=i,this.label=n}update(r,n){this.current=r,n&&(this.label=n),this.render()}increment(r){this.update(this.current+1,r)}render(){const r=Math.min(100,Math.floor(this.current/this.total*100)),n=Math.floor(this.current/this.total*this.width),i=this.width-n,o=`${t.brightMagenta}${"\u2588".repeat(n)}${t.dim}${"\u2591".repeat(i)}${t.reset}`,s=`${r}%`.padStart(4);process.stdout.write(`\r${this.label} ${o} ${s} (${this.current}/${this.total})`)}complete(){this.update(this.total),console.log()}}function I(e,r,n={}){const{padding:i=2,border:o=!0}=n,s=e.map((a,$)=>{const h=Math.max(...r.map(u=>(u[$]||"").length));return Math.max(a.length,h)}),c=(a,$)=>a.padEnd($),p=s.map(a=>"\u2500".repeat(a+i)).join(o?"\u253C":" ");let d="";o&&(d+=`${t.dim}\u250C${s.map(a=>"\u2500".repeat(a+i)).join("\u252C")}\u2510${t.reset}
|
|
11
|
+
`),d+=(o?`${t.dim}\u2502${t.reset}`:"")+e.map((a,$)=>` ${l.bold(c(a,s[$]))} `).join(o?`${t.dim}\u2502${t.reset}`:"")+(o?`${t.dim}\u2502${t.reset}`:"")+`
|
|
12
|
+
`,o&&(d+=`${t.dim}\u251C${p}\u2524${t.reset}
|
|
13
|
+
`);for(const a of r)d+=(o?`${t.dim}\u2502${t.reset}`:"")+a.map(($,h)=>` ${c($||"",s[h])} `).join(o?`${t.dim}\u2502${t.reset}`:"")+(o?`${t.dim}\u2502${t.reset}`:"")+`
|
|
14
|
+
`;return o&&(d+=`${t.dim}\u2514${s.map(a=>"\u2500".repeat(a+i)).join("\u2534")}\u2518${t.reset}`),d}function C(e,r,n=50){const i=r.split(`
|
|
15
|
+
`),o=Math.max(e.length,...i.map(c=>c.length),n);let s=`${t.dim}\u256D${"\u2500".repeat(o+2)}\u256E${t.reset}
|
|
16
|
+
`;s+=`${t.dim}\u2502${t.reset} ${l.bold(e.padEnd(o))} ${t.dim}\u2502${t.reset}
|
|
17
|
+
`,s+=`${t.dim}\u251C${"\u2500".repeat(o+2)}\u2524${t.reset}
|
|
18
|
+
`;for(const c of i)s+=`${t.dim}\u2502${t.reset} ${c.padEnd(o)} ${t.dim}\u2502${t.reset}
|
|
19
|
+
`;return s+=`${t.dim}\u2570${"\u2500".repeat(o+2)}\u256F${t.reset}`,s}function N(e){if(e===0)return"0 B";const r=1024,n=["B","KB","MB","GB","TB"],i=Math.floor(Math.log(e)/Math.log(r));return`${(e/Math.pow(r,i)).toFixed(2)} ${n[i]}`}function L(e){return e<1e3?`${e}ms`:e<6e4?`${(e/1e3).toFixed(1)}s`:e<36e5?`${Math.floor(e/6e4)}m ${Math.floor(e%6e4/1e3)}s`:`${Math.floor(e/36e5)}h ${Math.floor(e%36e5/6e4)}m`}function P(e){console.log(`
|
|
20
|
+
${l.bold(l.primary(`\u25B6 ${e}`))}`),console.log(`${t.dim}${"\u2500".repeat(e.length+4)}${t.reset}`)}function G(e){console.log(`${m.success} ${l.success(e)}`)}function A(e){console.log(`${m.error} ${l.error(e)}`)}function F(e){console.log(`${m.warning} ${l.warning(e)}`)}function T(e){console.log(`${m.info} ${e}`)}class M extends Error{constructor(){super("readSecret canceled"),this.name="ReadSecretCanceled"}}async function Y(e,r={}){const n=r.input??process.stdin,i=r.output??process.stdout,o=typeof n.setRawMode=="function";i.write(e),o&&n.setRawMode(!0),n.resume();let s="";return new Promise((c,p)=>{const d=h=>{for(const u of h){if(u===13||u===10){$(),i.write(`
|
|
21
|
+
`),c(s);return}if(u===3){$(),p(new M);return}if(u===4){$(),i.write(`
|
|
22
|
+
`),c(s);return}if(u===127||u===8){s.length>0&&(s=s.slice(0,-1),i.write("\b \b"));continue}s+=String.fromCharCode(u),i.write("*")}},a=()=>{$(),c(s)};function $(){o&&n.setRawMode(!1),n.pause(),n.removeListener("data",d),n.removeListener("end",a)}n.on("data",d),n.on("end",a)})}export{v as BANNER,k as MINI_BANNER,j as ProgressBar,M as ReadSecretCanceled,E as Spinner,l as c,t as colors,C as formatBox,N as formatBytes,L as formatDuration,I as formatTable,m as icons,A as printError,T as printInfo,P as printSection,G as printSuccess,F as printWarning,R as priorityIcons,Y as readSecret};
|
package/dist/cloud/cli.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{randomBytes as A}from"node:crypto";import{writeFileSync as
|
|
1
|
+
import{randomBytes as A}from"node:crypto";import{writeFileSync as O}from"node:fs";import{hostname as P}from"node:os";import{resolve as L,dirname as W,join as M}from"node:path";import{fileURLToPath as F}from"node:url";import{createInterface as B}from"node:readline";import{CloudClient as d,CloudError as w,DEFAULT_BASE as v,SESSION_FILE as $,loadSession as g,saveSession as C,clearSession as k}from"./client.js";import{getMasterKey as U,keyExists as S,keyFilePath as a,decrypt as j}from"./crypto.js";import{computeMachineFp as I,classifySession as G}from"./machine-id.js";import{runSync as H,CopiedSessionError as Y}from"./sync-engine.js";import{SovereignSyncManager as q,restartIntervalMinutes as K}from"./sync-daemon.js";import{initializeLicense as J,hasFeature as z,getTier as V}from"../license.js";function b(){J(),z("cloud_backup")||(console.error("\u{F115D} Wyrm Cloud sync requires a Pro license or higher."),console.error(` Current tier: ${V()}`),console.error(" Upgrade: https://ghosts.lk/wyrm"),console.error(" Already paid? Activate your license (wyrm_license tool, or place"),console.error(" the signed JSON at ~/.wyrm/license.json) and retry."),process.exit(1))}function _(e){return e<1024?`${e} B`:e<1024*1024?`${(e/1024).toFixed(1)} KiB`:e<1024*1024*1024?`${(e/(1024*1024)).toFixed(1)} MiB`:`${(e/(1024*1024*1024)).toFixed(2)} GiB`}function E(e){return e?new Date(e).toISOString().replace("T"," ").slice(0,16)+" UTC":"\u2014"}async function ke(e){const r=e[0],n=e.slice(1);try{switch(r){case void 0:case"help":case"--help":case"-h":return Q();case"login":return await X(n);case"logout":return await Z();case"status":return await ee();case"doctor":return oe();case"devices":return await se(n);case"sync":return await ne(n);case"daemon":return await re(n);case"export":return await ce(n);case"delete":return await le(n);case"recovery":return await ie(n);default:console.error(`Unknown subcommand: wyrm cloud ${r}`),console.error("Try `wyrm cloud help`"),process.exit(1)}}catch(s){if(s instanceof Y&&process.exit(1),s instanceof w)s.status===401?console.error("\u2718 Not authenticated. Run `wyrm cloud login`."):s.status===403?console.error(`\u2718 Forbidden: ${s.message}`):s.status===0?(console.error(`\u2718 ${s.message}`),console.error(" Check your internet connection. If wyrm.ghosts.lk is unreachable, see status at https://www.cloudflarestatus.com")):s.status===501?console.error(`\u2718 Service not configured: ${s.message}`):console.error(`\u2718 ${s.message} (HTTP ${s.status})`);else if(s instanceof Error){const t=s.message;t.includes("Not logged in")?console.error("\u2718 Not logged in. Run `wyrm cloud login`."):t.includes("Wyrm Cloud key")?console.error(`\u2718 Encryption-key error: ${t}`):console.error(`\u2718 ${t}`)}else console.error("\u2718 Unexpected error:",s);process.exit(1)}}function Q(){console.log(`\u{F115D} wyrm cloud \u2014 sync your Wyrm memory across devices
|
|
2
2
|
|
|
3
3
|
USAGE:
|
|
4
4
|
wyrm cloud <subcommand> [args]
|
|
@@ -37,5 +37,5 @@ CONSTITUTION:
|
|
|
37
37
|
DOMAIN:
|
|
38
38
|
https://wyrm.ghosts.lk
|
|
39
39
|
`)}async function X(e){const r=g();if(r)try{await new d(r.base,r.session).me(),console.error("Already logged in. Run `wyrm cloud logout` first to switch accounts."),process.exit(1)}catch(c){c instanceof w&&c.status===401||(console.error("Already logged in. Run `wyrm cloud logout` first to switch accounts."),process.exit(1)),console.log("Previous session expired; starting a fresh login."),k()}const n=new d(v),s=A(32).toString("hex");let t;try{t=await n.cliInit(s)}catch(c){console.error(`\u2718 Could not start login: ${c instanceof Error?c.message:c}`),process.exit(1)}console.log(""),console.log("\u{F115D} Sign in to Wyrm Cloud"),console.log(""),console.log(" 1. Open this URL on any device:"),console.log(` ${t.browser_url}`),console.log(""),console.log(" 2. Type this code when prompted:"),console.log(` ${t.code}`),console.log(""),console.log(" 3. Sign in with Google or GitHub."),console.log(""),console.log(` Waiting (expires in ${Math.round(t.expires_in_sec/60)} min)\u2026`);const l=Date.now()+t.expires_in_sec*1e3;let i,u;for(;Date.now()<l;){await new Promise(c=>{setTimeout(c,3e3)});try{const c=await n.cliPoll(s);if(c.ready&&c.session){i=c.session,u=c.account_id;break}}catch(c){console.error(`
|
|
40
|
-
\u2718 Login failed: ${c instanceof Error?c.message:c}`),process.exit(1)}}(!i||!u)&&(console.error("\n\u2718 Login timed out. Run `wyrm cloud login` again."),process.exit(1)),C({base:v,session:i,account_id:u,created_at:Date.now()});const y=new d(v,i);let p="";try{p=(await y.me()).account.email}catch{}const f=`${
|
|
41
|
-
\u26A0 Logged in but could not register device: ${c instanceof Error?c.message:c}`),console.error(" Re-run `wyrm cloud login` to retry.");return}console.log(""),console.log(`\u2713 Signed in as ${p}`),console.log(`\u2713 Device registered: ${f}`),console.log(`\u2713 Session saved to ${$}`);const R=!S();if(U(),R?(console.log(`\u2713 Encryption key generated at ${a()} (0600)`),console.log(""),console.log(" \u26A0 Back up the key file. Losing it means losing access to your synced data."),console.log(" The Wyrm Cloud server CANNOT recover it \u2014 that's the operator-owns-data"),console.log(" guarantee. Run `wyrm cloud recovery show` now to get a 24-word phrase,"),console.log(" or copy the key file to a password manager / encrypted backup.")):console.log(`\u2713 Encryption key reused from ${a()}`),m)try{const c=await y.syncStatus();if(c.deltas>0){const T=(await y.syncPull(m,0,1)).deltas[0];if(T){let x=!0;try{j(T.payload_b64)}catch{x=!1}x||(console.log(""),console.log(" \u26A0 THIS DEVICE CANNOT READ THE ACCOUNT'S DATA YET"),console.log(` Login succeeded, but the key at ${a()} cannot decrypt`),console.log(` the ${c.deltas} delta(s) already on this account. They were encrypted`),console.log(" with a different master key (e.g. this is an org account whose other"),console.log(" devices hold the shared key). Until the keys match, sync will move"),console.log(" 0 rows in BOTH directions \u2014 a connected pipe that carries nothing."),console.log(""),console.log(" Fix: adopt the account key on this device \u2014"),console.log(" wyrm cloud recovery restore (needs the account's 24-word phrase)"),console.log(` Heads-up: restore OVERWRITES ${a()}. If this device also has`),console.log(" its OWN separate cloud data, back that key up first, or run the org"),console.log(" sync under a separate WYRM home so both keys coexist."))}}}catch{}console.log(""),console.log("Next: `wyrm cloud sync` to push your memory.")}async function Z(){const e=g();if(!e){console.log("Not logged in.");return}try{await new d(e.base,e.session).logout(),console.log("\u2713 Session revoked server-side.")}catch(r){console.error(`\u26A0 Could not revoke server-side: ${r instanceof Error?r.message:r}`),console.error(" Local session cleared anyway.")}k(),console.log(`\u2713 Local session removed (${$}).`),console.log(" Note: ~/.wyrm/cloud.key kept. Delete manually if you want a clean slate.")}async function ee(){const e=g();if(!e){console.log("Not logged in. Run `wyrm cloud login`.");return}const r=new d(e.base,e.session);try{const n=await r.me(),s=await r.syncStatus();console.log(""),console.log(`\u{F115D} Wyrm Cloud \u2014 ${n.account.email}`),console.log(""),console.log(` Tier: ${s.tier}`),console.log(` Storage: ${_(s.storage.used_bytes)} / ${_(s.storage.limit_bytes)} (${s.storage.percent}%)`),console.log(` Deltas: ${s.deltas}`),console.log(` Devices: ${s.devices}`),console.log(` Providers:${n.identities.map(t=>" "+t.provider).join(",")}`),console.log(""),console.log(` This machine: ${e.device_name??"(not registered)"}`),console.log(` Device ID: ${e.device_id?e.device_id.slice(0,8)+"\u2026":"(none)"}`),console.log(` Session age: ${Math.round((Date.now()-e.created_at)/(1440*60*1e3))} day(s)`),D(e),console.log(""),S()?s.deltas>0&&(console.log(` Key: ${a()} (0600)`),console.log(` \u26A0 Back this file up. Losing it = losing access to your ${s.deltas} deltas.`),console.log("")):(console.log(" \u26A0 ENCRYPTION KEY MISSING"),console.log(` Expected at: ${a()}`),console.log(" Without this file, deltas already pushed cannot be decrypted."),console.log(" If you have a backup, restore it before running sync."),console.log(""))}catch(n){throw n instanceof w&&n.status===401&&(console.error("Session expired or invalid. Run `wyrm cloud login` again."),process.exit(1)),n}}function D(e){const r=G(e.machine_fp);r.state==="match"?console.log(" Identity: \u2713 machine fingerprint matches (not a copied session)"):r.state==="adopt"?console.log(" Identity: \u2014 no fingerprint yet (pre-7.0.3 session; adopted on next sync)"):(console.log(" Identity: \u26A0 FINGERPRINT MISMATCH \u2014 this session looks COPIED"),console.log(` stored ${r.stored.slice(0,12)}\u2026 vs this machine ${r.current.slice(0,12)}\u2026`),console.log(" device_id collision \u2192 cross-device pull returns 0 silently."),console.log(" Fix: rm ~/.wyrm/cloud.json ~/.wyrm/cloud-cursor.json (KEEP cloud.key),"),console.log(" then `wyrm cloud login`. (Or `wyrm cloud sync --force` if intended.)"))}function oe(){const e=g();if(console.log(""),console.log("\u{F115D} Wyrm Cloud \u2014 device identity doctor"),console.log(""),!e){console.log(" Not logged in. Run `wyrm cloud login`."),console.log("");return}console.log(` Account: ${e.email??e.account_id.slice(0,8)+"\u2026"}`),console.log(` Device ID: ${e.device_id?e.device_id.slice(0,8)+"\u2026":"(none \u2014 re-run `wyrm cloud login`)"}`),console.log(` Server: ${e.base}`),console.log(` Stored fp: ${e.machine_fp?e.machine_fp.slice(0,12)+"\u2026":"(none \u2014 pre-7.0.3 session)"}`),console.log(` This box fp: ${I().slice(0,12)}\u2026`),D(e),console.log(""),console.log(" Only ~/.wyrm/cloud.key is meant to be shared across your machines."),console.log(" cloud.json + cloud-cursor.json + machine-id are per-device."),console.log("")}async function se(e){const r=e[0],n=d.fromSession();if(r==="revoke"){const l=e[1];l||(console.error("Usage: wyrm cloud devices revoke <device-id>"),process.exit(1)),await n.revokeDevice(l),console.log(`\u2713 Device ${l.slice(0,8)}\u2026 revoked.`);return}const{devices:s}=await n.listDevices(),o=g()?.device_id;console.log(""),console.log(`\u{F115D} Devices (${s.length})`),console.log("");for(const l of s){const i=l.id===o?"\u2605":" ",u=l.revoked?"(revoked)":"";console.log(` ${i} ${l.id.slice(0,8)}\u2026 ${l.name.padEnd(30)} last_seen=${E(l.last_seen_at)} ${u}`)}console.log(""),console.log(" \u2605 = this machine"),console.log(" Revoke: wyrm cloud devices revoke <device-id>"),console.log("")}async function ne(e){b();const r=e.includes("--dry-run"),n=e.includes("--all"),s=e.includes("--force");S()||(console.error("\u26A0 Encryption key not found at",a()),console.error(" A new key will be generated, but it will NOT decrypt deltas"),console.error(" pushed by other devices using a different key. If you have a backup,"),console.error(" restore it to ~/.wyrm/cloud.key (0600) before running sync."),console.error(" Continuing in 5 seconds \u2014 press Ctrl+C to abort."),await new Promise(o=>{setTimeout(o,5e3)})),n&&(console.log("\u{1F310} --all mode: syncing every semantic table (ignoring visibility flags)."),console.log(" Skipped: per-device logs, FTS shadows, sync internals."),console.log(""));const t=await H({dryRun:r,all:n,force:s});if(console.log(""),console.log("\u{F115D} Sync complete"),console.log(""),console.log(` Pushed: ${t.pushed} row(s)${r?" (dry run)":""}`),console.log(` Pulled: ${t.pulled} row(s)`),console.log(` Deleted: ${t.deleted_local} row(s) (peer tombstones)`),t.quarantined&&t.quarantined>0&&console.log(` Quarantined: ${t.quarantined} undecryptable delta(s) set aside (see errors)`),t.errors.length>0){console.log(""),console.log(" Errors:");for(const o of t.errors)console.log(` - ${o}`)}console.log("")}function te(){const e=M(W(F(import.meta.url)),"sync-daemon-entrypoint.js");return new q(e)}async function re(e){b();const r=e[0]??"status",n=e.find(o=>o.startsWith("--interval=")),s=n?Number(n.split("=")[1]):void 0,t=te();switch(r){case"start":{g()||(console.error("\u2718 Not logged in. Run `wyrm cloud login` first."),process.exit(1));const o=t.start({interval_minutes:s});o.ok||(console.error(`\u{F115D} sovereign-sync daemon: ${o.reason??"failed to start"}${o.pid?` (pid ${o.pid})`:""}`),process.exit(o.pid?0:1)),console.log(`\u{F115D} \u2713 sovereign-sync daemon started (pid ${o.pid}). Logs: ~/.wyrm/wyrm-sovereign-sync.log`);return}case"stop":{const o=t.stop();console.log(o.ok?"\u{F115D} \u2713 sovereign-sync daemon stopped":`\u{F115D} sovereign-sync daemon: ${o.reason??"failed to stop"}`);return}case"restart":{const o=K(s,t.status().state?.interval_ms);t.stop(),await new Promise(i=>{setTimeout(i,500)});const l=t.start({interval_minutes:o});console.log(l.ok?`\u{F115D} \u2713 sovereign-sync daemon restarted (pid ${l.pid}, every ${o} min)`:`\u{F115D} restart failed: ${l.reason??"unknown"}`);return}case"status":{const o=t.status();console.log("\u{F115D} Sovereign-sync daemon"),console.log(` Running: ${o.running?`\u2713 yes (pid ${o.pid})`:"\u2717 no"}`),o.state?.interval_ms&&console.log(` Interval: every ${o.state.interval_ms/6e4} min`),o.state?.started_ts&&console.log(` Started: ${E(o.state.started_ts)}`),o.state?.last_tick_ts&&(console.log(` Last tick: ${E(o.state.last_tick_ts)} (exit ${o.state.last_tick_exit??"\u2014"})`),o.state.last_tick_summary&&console.log(` Last result: ${o.state.last_tick_summary}`));return}default:console.error(`Unknown daemon action: ${r}. Use: start, stop, restart, status`),process.exit(1)}}async function ce(e){b();const r=d.fromSession(),n=e.find(l=>!l.startsWith("-")),s=L(n??`wyrm-cloud-export-${new Date().toISOString().slice(0,10)}.json`);console.log(" Fetching account export\u2026");const t=await r.accountExport(),o=JSON.stringify(t,null,2);P(s,o,{mode:384}),console.log(""),console.log(`\u2713 Exported to ${s}`),console.log(""),console.log(` Account: ${t.account.email} (${t.account.id.slice(0,8)}\u2026)`),console.log(` Devices: ${t.devices.length}`),console.log(` Identities: ${t.identities.length}`),console.log(` Orgs: ${t.orgs.length}`),console.log(` Deltas: ${t.deltas.length}`),console.log(` Size: ${_(o.length)}`),console.log(""),console.log(" Deltas are still AES-256-GCM ciphertext. To decrypt them you"),console.log(` need the key at ${a()}. Back that up alongside this file.`),console.log("")}async function le(e){e.includes("--confirm")||(console.error("\u2718 This permanently deletes your Wyrm Cloud account."),console.error(" Re-run with --confirm to proceed:"),console.error(" wyrm cloud delete --confirm"),process.exit(1));const n=g();n||(console.error("\u2718 Not logged in. Nothing to delete."),process.exit(1)),console.log(""),console.log("\u26A0 You are about to PERMANENTLY DELETE your Wyrm Cloud account."),console.log(""),console.log(` Account: ${n.email??n.account_id}`),console.log(` Server: ${n.base}`),console.log(""),console.log(" This wipes all encrypted blobs, devices, identities, and org"),console.log(" memberships you own. The local ~/.wyrm/cloud.key is kept so"),console.log(" any backups remain decryptable. This action CANNOT be undone."),console.log(""),(await N(' Type "DELETE my account" to confirm: ')).trim()!=="DELETE my account"&&(console.error("\u2718 Confirmation phrase did not match. Aborting."),process.exit(1)),await new d(n.base,n.session).accountDelete(),k(),console.log(""),console.log("\u2713 Account deleted on server."),console.log(`\u2713 Local session removed (${$}).`),console.log(` Kept: ${a()} (in case you need to decrypt an old export).`),console.log("")}function N(e){return new Promise(r=>{const n=B({input:process.stdin,output:process.stdout});n.question(e,s=>{n.close(),r(s)})})}async function ie(e){const r=e[0];if(r==="show"){const{keyToMnemonic:n}=await import("./recovery.js"),t=n().split(" ");console.log(""),console.log("\u{1F511} Wyrm Cloud recovery phrase (BIP39, 24 words)"),console.log("");for(let o=0;o<4;o++){const l=t.slice(o*6,o*6+6).map((i,u)=>`${String(o*6+u+1).padStart(2)}. ${i.padEnd(10)}`).join(" ");console.log(` ${l}`)}console.log(""),console.log(" Write these down on PAPER and keep them somewhere safe."),console.log(" Anyone with this phrase can decrypt your synced Wyrm memory."),console.log(" Wyrm Cloud has NO COPY of this. Lose it = lose your data."),console.log("");return}if(r==="restore"){const n=e.includes("--overwrite");console.log(""),console.log("\u{1F511} Restore master key from BIP39 recovery phrase"),console.log(""),console.log(" Enter your 24 words separated by spaces (case-insensitive)."),console.log(" Press Enter when done."),console.log("");const s=await N(" > "),{mnemonicToKey:t}=await import("./recovery.js"),{path:o,overwrote:l}=t(s,{overwrite:n});console.log(""),console.log(`\u2713 Master key restored to ${o} (0600)`),l&&console.log(" Replaced existing key (you passed --overwrite)."),console.log(" Run `wyrm cloud sync` to pull deltas with the restored key."),console.log("");return}console.error("Usage:"),console.error(" wyrm cloud recovery show"),console.error(" wyrm cloud recovery restore [--overwrite]"),process.exit(1)}export{ke as cmdCloud};
|
|
40
|
+
\u2718 Login failed: ${c instanceof Error?c.message:c}`),process.exit(1)}}(!i||!u)&&(console.error("\n\u2718 Login timed out. Run `wyrm cloud login` again."),process.exit(1)),C({base:v,session:i,account_id:u,created_at:Date.now()});const y=new d(v,i);let p="";try{p=(await y.me()).account.email}catch{}const f=`${P()} (${process.platform})`;let m;try{m=(await y.registerDevice(f)).device.id;const h=g();h&&C({...h,email:p,device_id:m,device_name:f,machine_fp:I()})}catch(c){console.error(`
|
|
41
|
+
\u26A0 Logged in but could not register device: ${c instanceof Error?c.message:c}`),console.error(" Re-run `wyrm cloud login` to retry.");return}console.log(""),console.log(`\u2713 Signed in as ${p}`),console.log(`\u2713 Device registered: ${f}`),console.log(`\u2713 Session saved to ${$}`);const R=!S();if(U(),R?(console.log(`\u2713 Encryption key generated at ${a()} (0600)`),console.log(""),console.log(" \u26A0 Back up the key file. Losing it means losing access to your synced data."),console.log(" The Wyrm Cloud server CANNOT recover it \u2014 that's the operator-owns-data"),console.log(" guarantee. Run `wyrm cloud recovery show` now to get a 24-word phrase,"),console.log(" or copy the key file to a password manager / encrypted backup.")):console.log(`\u2713 Encryption key reused from ${a()}`),m)try{const c=await y.syncStatus();if(c.deltas>0){const x=(await y.syncPull(m,0,1)).deltas[0];if(x){let T=!0;try{j(x.payload_b64)}catch{T=!1}T||(console.log(""),console.log(" \u26A0 THIS DEVICE CANNOT READ THE ACCOUNT'S DATA YET"),console.log(` Login succeeded, but the key at ${a()} cannot decrypt`),console.log(` the ${c.deltas} delta(s) already on this account. They were encrypted`),console.log(" with a different master key (e.g. this is an org account whose other"),console.log(" devices hold the shared key). Until the keys match, sync will move"),console.log(" 0 rows in BOTH directions \u2014 a connected pipe that carries nothing."),console.log(""),console.log(" Fix: adopt the account key on this device \u2014"),console.log(" wyrm cloud recovery restore (needs the account's 24-word phrase)"),console.log(` Heads-up: restore OVERWRITES ${a()}. If this device also has`),console.log(" its OWN separate cloud data, back that key up first, or run the org"),console.log(" sync under a separate WYRM home so both keys coexist."))}}}catch{}console.log(""),console.log("Next: `wyrm cloud sync` to push your memory.")}async function Z(){const e=g();if(!e){console.log("Not logged in.");return}try{await new d(e.base,e.session).logout(),console.log("\u2713 Session revoked server-side.")}catch(r){console.error(`\u26A0 Could not revoke server-side: ${r instanceof Error?r.message:r}`),console.error(" Local session cleared anyway.")}k(),console.log(`\u2713 Local session removed (${$}).`),console.log(" Note: ~/.wyrm/cloud.key kept. Delete manually if you want a clean slate.")}async function ee(){const e=g();if(!e){console.log("Not logged in. Run `wyrm cloud login`.");return}const r=new d(e.base,e.session);try{const n=await r.me(),s=await r.syncStatus();console.log(""),console.log(`\u{F115D} Wyrm Cloud \u2014 ${n.account.email}`),console.log(""),console.log(` Tier: ${s.tier}`),console.log(` Storage: ${_(s.storage.used_bytes)} / ${_(s.storage.limit_bytes)} (${s.storage.percent}%)`),console.log(` Deltas: ${s.deltas}`),console.log(` Devices: ${s.devices}`),console.log(` Providers:${n.identities.map(t=>" "+t.provider).join(",")}`),console.log(""),console.log(` This machine: ${e.device_name??"(not registered)"}`),console.log(` Device ID: ${e.device_id?e.device_id.slice(0,8)+"\u2026":"(none)"}`),console.log(` Session age: ${Math.round((Date.now()-e.created_at)/(1440*60*1e3))} day(s)`),D(e),console.log(""),S()?s.deltas>0&&(console.log(` Key: ${a()} (0600)`),console.log(` \u26A0 Back this file up. Losing it = losing access to your ${s.deltas} deltas.`),console.log("")):(console.log(" \u26A0 ENCRYPTION KEY MISSING"),console.log(` Expected at: ${a()}`),console.log(" Without this file, deltas already pushed cannot be decrypted."),console.log(" If you have a backup, restore it before running sync."),console.log(""))}catch(n){throw n instanceof w&&n.status===401&&(console.error("Session expired or invalid. Run `wyrm cloud login` again."),process.exit(1)),n}}function D(e){const r=G(e.machine_fp);r.state==="match"?console.log(" Identity: \u2713 machine fingerprint matches (not a copied session)"):r.state==="adopt"?console.log(" Identity: \u2014 no fingerprint yet (pre-7.0.3 session; adopted on next sync)"):(console.log(" Identity: \u26A0 FINGERPRINT MISMATCH \u2014 this session looks COPIED"),console.log(` stored ${r.stored.slice(0,12)}\u2026 vs this machine ${r.current.slice(0,12)}\u2026`),console.log(" device_id collision \u2192 cross-device pull returns 0 silently."),console.log(" Fix: rm ~/.wyrm/cloud.json ~/.wyrm/cloud-cursor.json (KEEP cloud.key),"),console.log(" then `wyrm cloud login`. (Or `wyrm cloud sync --force` if intended.)"))}function oe(){const e=g();if(console.log(""),console.log("\u{F115D} Wyrm Cloud \u2014 device identity doctor"),console.log(""),!e){console.log(" Not logged in. Run `wyrm cloud login`."),console.log("");return}console.log(` Account: ${e.email??e.account_id.slice(0,8)+"\u2026"}`),console.log(` Device ID: ${e.device_id?e.device_id.slice(0,8)+"\u2026":"(none \u2014 re-run `wyrm cloud login`)"}`),console.log(` Server: ${e.base}`),console.log(` Stored fp: ${e.machine_fp?e.machine_fp.slice(0,12)+"\u2026":"(none \u2014 pre-7.0.3 session)"}`),console.log(` This box fp: ${I().slice(0,12)}\u2026`),D(e),console.log(""),console.log(" Only ~/.wyrm/cloud.key is meant to be shared across your machines."),console.log(" cloud.json + cloud-cursor.json + machine-id are per-device."),console.log("")}async function se(e){const r=e[0],n=d.fromSession();if(r==="revoke"){const l=e[1];l||(console.error("Usage: wyrm cloud devices revoke <device-id>"),process.exit(1)),await n.revokeDevice(l),console.log(`\u2713 Device ${l.slice(0,8)}\u2026 revoked.`);return}const{devices:s}=await n.listDevices(),o=g()?.device_id;console.log(""),console.log(`\u{F115D} Devices (${s.length})`),console.log("");for(const l of s){const i=l.id===o?"\u2605":" ",u=l.revoked?"(revoked)":"";console.log(` ${i} ${l.id.slice(0,8)}\u2026 ${l.name.padEnd(30)} last_seen=${E(l.last_seen_at)} ${u}`)}console.log(""),console.log(" \u2605 = this machine"),console.log(" Revoke: wyrm cloud devices revoke <device-id>"),console.log("")}async function ne(e){b();const r=e.includes("--dry-run"),n=e.includes("--all"),s=e.includes("--force");S()||(console.error("\u26A0 Encryption key not found at",a()),console.error(" A new key will be generated, but it will NOT decrypt deltas"),console.error(" pushed by other devices using a different key. If you have a backup,"),console.error(" restore it to ~/.wyrm/cloud.key (0600) before running sync."),console.error(" Continuing in 5 seconds \u2014 press Ctrl+C to abort."),await new Promise(o=>{setTimeout(o,5e3)})),n&&(console.log("\u{1F310} --all mode: syncing every semantic table (ignoring visibility flags)."),console.log(" Skipped: per-device logs, FTS shadows, sync internals."),console.log(""));const t=await H({dryRun:r,all:n,force:s});if(console.log(""),console.log("\u{F115D} Sync complete"),console.log(""),console.log(` Pushed: ${t.pushed} row(s)${r?" (dry run)":""}`),console.log(` Pulled: ${t.pulled} row(s)`),console.log(` Deleted: ${t.deleted_local} row(s) (peer tombstones)`),t.quarantined&&t.quarantined>0&&console.log(` Quarantined: ${t.quarantined} undecryptable delta(s) set aside (see errors)`),t.errors.length>0){console.log(""),console.log(" Errors:");for(const o of t.errors)console.log(` - ${o}`)}console.log("")}function te(){const e=M(W(F(import.meta.url)),"sync-daemon-entrypoint.js");return new q(e)}async function re(e){b();const r=e[0]??"status",n=e.find(o=>o.startsWith("--interval="));let s;if(n){const o=Number(n.split("=")[1]);(!Number.isFinite(o)||o<=0)&&(console.error("\u2716 --interval= expects a positive number of minutes, got "+JSON.stringify(n)),process.exit(1)),s=o}const t=te();switch(r){case"start":{g()||(console.error("\u2718 Not logged in. Run `wyrm cloud login` first."),process.exit(1));const o=t.start({interval_minutes:s});o.ok||(console.error(`\u{F115D} sovereign-sync daemon: ${o.reason??"failed to start"}${o.pid?` (pid ${o.pid})`:""}`),process.exit(o.pid?0:1)),console.log(`\u{F115D} \u2713 sovereign-sync daemon started (pid ${o.pid}). Logs: ~/.wyrm/wyrm-sovereign-sync.log`);return}case"stop":{const o=t.stop();console.log(o.ok?"\u{F115D} \u2713 sovereign-sync daemon stopped":`\u{F115D} sovereign-sync daemon: ${o.reason??"failed to stop"}`);return}case"restart":{const o=K(s,t.status().state?.interval_ms);t.stop(),await new Promise(i=>{setTimeout(i,500)});const l=t.start({interval_minutes:o});console.log(l.ok?`\u{F115D} \u2713 sovereign-sync daemon restarted (pid ${l.pid}, every ${o} min)`:`\u{F115D} restart failed: ${l.reason??"unknown"}`);return}case"status":{const o=t.status();console.log("\u{F115D} Sovereign-sync daemon"),console.log(` Running: ${o.running?`\u2713 yes (pid ${o.pid})`:"\u2717 no"}`),o.state?.interval_ms&&console.log(` Interval: every ${o.state.interval_ms/6e4} min`),o.state?.started_ts&&console.log(` Started: ${E(o.state.started_ts)}`),o.state?.last_tick_ts&&(console.log(` Last tick: ${E(o.state.last_tick_ts)} (exit ${o.state.last_tick_exit??"\u2014"})`),o.state.last_tick_summary&&console.log(` Last result: ${o.state.last_tick_summary}`));return}default:console.error(`Unknown daemon action: ${r}. Use: start, stop, restart, status`),process.exit(1)}}async function ce(e){b();const r=d.fromSession(),n=e.find(l=>!l.startsWith("-")),s=L(n??`wyrm-cloud-export-${new Date().toISOString().slice(0,10)}.json`);console.log(" Fetching account export\u2026");const t=await r.accountExport(),o=JSON.stringify(t,null,2);O(s,o,{mode:384}),console.log(""),console.log(`\u2713 Exported to ${s}`),console.log(""),console.log(` Account: ${t.account.email} (${t.account.id.slice(0,8)}\u2026)`),console.log(` Devices: ${t.devices.length}`),console.log(` Identities: ${t.identities.length}`),console.log(` Orgs: ${t.orgs.length}`),console.log(` Deltas: ${t.deltas.length}`),console.log(` Size: ${_(o.length)}`),console.log(""),console.log(" Deltas are still AES-256-GCM ciphertext. To decrypt them you"),console.log(` need the key at ${a()}. Back that up alongside this file.`),console.log("")}async function le(e){e.includes("--confirm")||(console.error("\u2718 This permanently deletes your Wyrm Cloud account."),console.error(" Re-run with --confirm to proceed:"),console.error(" wyrm cloud delete --confirm"),process.exit(1));const n=g();n||(console.error("\u2718 Not logged in. Nothing to delete."),process.exit(1)),console.log(""),console.log("\u26A0 You are about to PERMANENTLY DELETE your Wyrm Cloud account."),console.log(""),console.log(` Account: ${n.email??n.account_id}`),console.log(` Server: ${n.base}`),console.log(""),console.log(" This wipes all encrypted blobs, devices, identities, and org"),console.log(" memberships you own. The local ~/.wyrm/cloud.key is kept so"),console.log(" any backups remain decryptable. This action CANNOT be undone."),console.log(""),(await N(' Type "DELETE my account" to confirm: ')).trim()!=="DELETE my account"&&(console.error("\u2718 Confirmation phrase did not match. Aborting."),process.exit(1)),await new d(n.base,n.session).accountDelete(),k(),console.log(""),console.log("\u2713 Account deleted on server."),console.log(`\u2713 Local session removed (${$}).`),console.log(` Kept: ${a()} (in case you need to decrypt an old export).`),console.log("")}function N(e){return new Promise(r=>{const n=B({input:process.stdin,output:process.stdout});n.question(e,s=>{n.close(),r(s)})})}async function ie(e){const r=e[0];if(r==="show"){const{keyToMnemonic:n}=await import("./recovery.js"),t=n().split(" ");console.log(""),console.log("\u{1F511} Wyrm Cloud recovery phrase (BIP39, 24 words)"),console.log("");for(let o=0;o<4;o++){const l=t.slice(o*6,o*6+6).map((i,u)=>`${String(o*6+u+1).padStart(2)}. ${i.padEnd(10)}`).join(" ");console.log(` ${l}`)}console.log(""),console.log(" Write these down on PAPER and keep them somewhere safe."),console.log(" Anyone with this phrase can decrypt your synced Wyrm memory."),console.log(" Wyrm Cloud has NO COPY of this. Lose it = lose your data."),console.log("");return}if(r==="restore"){const n=e.includes("--overwrite");console.log(""),console.log("\u{1F511} Restore master key from BIP39 recovery phrase"),console.log(""),console.log(" Enter your 24 words separated by spaces (case-insensitive)."),console.log(" Press Enter when done."),console.log("");const s=await N(" > "),{mnemonicToKey:t}=await import("./recovery.js"),{path:o,overwrote:l}=t(s,{overwrite:n});console.log(""),console.log(`\u2713 Master key restored to ${o} (0600)`),l&&console.log(" Replaced existing key (you passed --overwrite)."),console.log(" Run `wyrm cloud sync` to pull deltas with the restored key."),console.log("");return}console.error("Usage:"),console.error(" wyrm cloud recovery show"),console.error(" wyrm cloud recovery restore [--overwrite]"),process.exit(1)}export{ke as cmdCloud};
|
package/dist/cloud/crypto.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{randomBytes as h,createCipheriv as
|
|
1
|
+
import{randomBytes as h,createCipheriv as d,createDecipheriv as m}from"node:crypto";import{readFileSync as g,writeFileSync as E,existsSync as l,chmodSync as w}from"node:fs";import{join as v}from"node:path";import{CLOUD_DIR as x}from"./client.js";const r=v(x,"cloud.key"),u=1,c=32,s=12;function y(){if(l(r)){const e=g(r);if(e.length!==c)throw new Error(`Wyrm Cloud key at ${r} has wrong length (${e.length}, expected ${c}). Refusing to proceed.`);return e}const o=h(c);try{E(r,o,{flag:"wx",mode:384})}catch(e){if(e.code==="EEXIST"){const t=g(r);if(t.length!==c)throw new Error(`Wyrm Cloud key at ${r} has wrong length (${t.length}, expected ${c}). Refusing to proceed.`);return t}throw e}try{w(r,384)}catch{}return o}function C(o){const e=y(),t=h(s),n=d("aes-256-gcm",e,t),i=Buffer.concat([n.update(o,"utf-8"),n.final()]),a=n.getAuthTag();return Buffer.concat([Buffer.from([u]),t,i,a]).toString("base64")}function I(o){const e=y(),t=Buffer.from(o,"base64");if(t.length<1+s+16)throw new Error(`Wyrm Cloud envelope too short (${t.length} bytes).`);const n=t.readUInt8(0);if(n!==u)throw new Error(`Wyrm Cloud envelope version ${n} unsupported (this client speaks ${u}). Upgrade wyrm-mcp.`);const i=t.subarray(1,1+s),a=t.subarray(t.length-16),p=t.subarray(1+s,t.length-16),f=m("aes-256-gcm",e,i);return f.setAuthTag(a),Buffer.concat([f.update(p),f.final()]).toString("utf-8")}function T(){return r}function _(){return l(r)}export{I as decrypt,C as encrypt,y as getMasterKey,_ as keyExists,T as keyFilePath};
|