wyrm-mcp 9.2.3 → 9.2.4-next.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/README.md +1 -1
- package/dist/autoconfig.js +33 -32
- package/dist/backup-cli.js +5 -5
- package/dist/backup.js +1 -1
- package/dist/cli.js +13 -13
- package/dist/erasure-cli.js +3 -3
- package/dist/handlers/companion.js +10 -10
- package/dist/handlers/events.js +11 -9
- package/dist/http-fast.js +5 -5
- package/dist/is-main.js +1 -0
- package/dist/live-embedding-state.js +2 -2
- package/dist/setup-wizard.js +4 -4
- package/dist/setup.js +2 -2
- package/dist/statusline-endpoint.js +1 -0
- package/dist/statusline.js +10 -10
- package/dist/storage/index.js +3 -3
- package/dist/tool-manifest-v2.json +1 -1
- package/dist/tool-manifest.json +1 -1
- package/dist/wyrm-cli.js +76 -75
- package/dist/wyrm-guard.js +9 -9
- package/dist/wyrm-manifest.json +1 -1
- package/dist/wyrm-memo.js +4 -4
- package/dist/wyrm-ui.js +7 -7
- package/package.json +4 -3
- package/scripts/hooks/wyrm-prompt-context.mjs +35 -4
- package/scripts/hooks/wyrm-run-auto.mjs +36 -4
- package/scripts/hooks/wyrm-session-capture.mjs +35 -4
- package/scripts/hooks/wyrm-session-prune.mjs +35 -4
- package/scripts/hooks/wyrm-session-rehydrate.mjs +68 -12
- package/scripts/hooks/wyrm-tool-call-trace.mjs +56 -6
- package/scripts/smoke-platform.mjs +162 -0
- package/dist/cors.js +0 -1
package/README.md
CHANGED
|
@@ -37,7 +37,7 @@ wyrm-setup # wire it into your AI clients, then restart them
|
|
|
37
37
|
|
|
38
38
|
> **If `npm install -g` fails with EACCES**, npm's global prefix is a directory you cannot write (often `/usr`). Install under a prefix you own instead, and put its `bin` on your PATH: `npm install -g --prefix ~/.npm-global wyrm-mcp`, then `export PATH="$HOME/.npm-global/bin:$PATH"`. `wyrm update` later updates that same install.
|
|
39
39
|
|
|
40
|
-
> **On npm v12+**, npm denies dependency install scripts by default, which skips `better-sqlite3`'s native build — the install *succeeds* but `wyrm` then fails with *"Could not locate the bindings file"*. Install with the build allow-listed instead: `npm install -g wyrm-mcp --allow-scripts=wyrm-mcp,better-sqlite3` (`wyrm update` already does this for you). See [TROUBLESHOOTING.md](https://github.com/Ghosts-Protocol-Pvt-Ltd/wyrm-mcp/blob/HEAD/TROUBLESHOOTING.md).
|
|
40
|
+
> **On npm v12+**, npm denies dependency install scripts by default, which skips `better-sqlite3`'s native build — the install *succeeds* but `wyrm` then fails with *"Could not locate the bindings file"*. Install with the build allow-listed instead: `npm install -g wyrm-mcp --allow-scripts=wyrm-mcp,better-sqlite3,onnxruntime-node,protobufjs` (`wyrm update` already does this for you). See [TROUBLESHOOTING.md](https://github.com/Ghosts-Protocol-Pvt-Ltd/wyrm-mcp/blob/HEAD/TROUBLESHOOTING.md).
|
|
41
41
|
|
|
42
42
|
Then, from inside your client, ask it to call `wyrm_capabilities` to confirm the connection. The everyday loop is four steps the agent runs on its own once the habit sets in:
|
|
43
43
|
|
package/dist/autoconfig.js
CHANGED
|
@@ -1,21 +1,22 @@
|
|
|
1
|
-
import{existsSync as
|
|
2
|
-
`)[0];break}case"cursor":{const t=
|
|
3
|
-
`)[0];break}case"claude-code":{const t=
|
|
4
|
-
`)[0];break}}}catch{}}function
|
|
5
|
-
`,"utf-8");const
|
|
6
|
-
`,"utf-8"),{client:e,action:"updated",message:`env: ${Object.keys(t).join(", ")}`,backup:i}}catch(
|
|
7
|
-
`,"utf-8"),e.configured=!1,{client:e,action:"configured",message:`Removed Wyrm from ${e.name}`,backup:
|
|
8
|
-
|
|
9
|
-
`,"utf-8"),{client:
|
|
10
|
-
`,"utf-8"),{client:t,action:"configured",message:"
|
|
11
|
-
`,"utf-8"),{client:t,action:"configured",message:"
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
`,"utf-8"),{client:o,action:"configured",message:"failure-firewall hooks
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
`,"utf-8"),{client:o,action:"configured",message:"token-firewall hooks
|
|
18
|
-
`,"utf-8")}
|
|
1
|
+
import{existsSync as l,readFileSync as h,writeFileSync as p,mkdirSync as j,copyFileSync as ce,chmodSync as N}from"fs";import{homedir as y,platform as v}from"os";import{join as c,dirname as w,resolve as ae}from"path";import{fileURLToPath as A}from"url";import{spawnSync as P}from"child_process";import{npmGlobalPrefixFromPath as ue}from"./cli.js";function de(){const e=y(),t=v(),n=t==="darwin"?c(e,"Library","Application Support","Code","User"):t==="win32"?c(e,"AppData","Roaming","Code","User"):c(e,".config","Code","User"),o=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"),a=t==="darwin"?c(e,".config","zed"):t==="win32"?c(e,"AppData","Roaming","Zed"):c(e,".config","zed"),d=c(e,".continue");return[{id:"vscode-copilot",name:"VS Code (Copilot)",icon:"\u{1F4BB}",configPath:c(n,"settings.json"),configFormat:"vscode",mcpKey:"mcp.servers",detected:!1,configured:!1},{id:"vscode-insiders",name:"VS Code Insiders",icon:"\u{1F7E2}",configPath:c(o,"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(a,"settings.json"),configFormat:"zed",mcpKey:"context_servers",detected:!1,configured:!1},{id:"continue",name:"Continue",icon:"\u{1F504}",configPath:c(d,"config.json"),configFormat:"mcp-json",mcpKey:"mcpServers",detected:!1,configured:!1}]}function fe(e){try{const t=v()==="win32"?"where":"which",n=P(t,[e],{encoding:"utf-8",timeout:5e3});return n.status===0&&!!n.stdout&&n.stdout.trim().length>0}catch{return!1}}function _(){const e=de();for(const t of e){if(t.id==="claude-code")t.detected=l(c(w(t.configPath),".claude"))||fe("claude");else{const n=w(t.configPath);t.detected=l(n)}if(t.detected&&l(t.configPath))try{const n=h(t.configPath,"utf-8"),o=T(n);t.configured=me(o,t)}catch{t.configured=!1}t.detected&&(t.version=le(t))}return e}function le(e){try{switch(e.id){case"vscode-copilot":case"vscode-insiders":{const t=e.id==="vscode-insiders"?"code-insiders":"code",n=P(t,["--version"],{encoding:"utf-8",timeout:5e3});if(n.stdout)return n.stdout.split(`
|
|
2
|
+
`)[0];break}case"cursor":{const t=P("cursor",["--version"],{encoding:"utf-8",timeout:5e3});if(t.stdout)return t.stdout.split(`
|
|
3
|
+
`)[0];break}case"claude-code":{const t=P("claude",["--version"],{encoding:"utf-8",timeout:5e3});if(t.stdout)return t.stdout.trim().split(`
|
|
4
|
+
`)[0];break}}}catch{}}function me(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 M(e=v()){if(e!=="win32"){try{const s=P("which",["wyrm-mcp"],{encoding:"utf-8",timeout:5e3});if(s.stdout?.trim())return s.stdout.trim()}catch{}try{const r=P("npm",["root","-g"],{encoding:"utf-8",timeout:5e3}).stdout?.trim();if(r){const i=c(r,"wyrm-mcp","dist","index.js");if(l(i))return i}}catch{}}const t=ae(w(A(import.meta.url)),"index.js");if(l(t))return t;const n=c(y(),".wyrm","node_modules","wyrm-mcp","dist","index.js");if(l(n))return n;const o=[c(y(),"Git Projects","Wyrm","packages","mcp-server","dist","index.js"),c(y(),"projects","Wyrm","packages","mcp-server","dist","index.js"),c(y(),"dev","Wyrm","packages","mcp-server","dist","index.js")];for(const s of o)if(l(s))return s;return"wyrm-mcp"}function U(){return c(y(),".wyrm","wyrm.db")}function Je(e="mcp-json",t){const n=t?.serverPath??M(),o=t?.dbPath??U(),s=n==="wyrm-mcp",r={command:s?"npx":"node",args:s?["wyrm-mcp"]:[n],env:{WYRM_DB_PATH:o}};switch(e){case"stdio":return r;case"vscode":return{mcp:{servers:{wyrm:r}}};default:return{mcpServers:{wyrm:r}}}}function he(e,t){const n=t.serverPath,o=n==="wyrm-mcp",s=n.endsWith(".js");switch(e.configFormat){case"vscode":return{command:o?"npx":"node",args:o?["wyrm-mcp"]:[n],env:{WYRM_DB_PATH:t.dbPath}};case"mcp-json":return{command:o?"npx":"node",args:o?["wyrm-mcp"]:[n],env:{WYRM_DB_PATH:t.dbPath}};case"zed":return{command:o?"npx":"node",args:o?["wyrm-mcp"]:[n],env:{WYRM_DB_PATH:t.dbPath}};default:return{}}}function pe(e,t){if(!e||typeof e!="object"||Array.isArray(e))return{entry:t,preservedLauncher:!1};const n=e,o=n.env&&typeof n.env=="object"&&!Array.isArray(n.env)?n.env:{},s=t.env&&typeof t.env=="object"&&!Array.isArray(t.env)?t.env:{};if(n.command===t.command&&JSON.stringify(n.args??null)===JSON.stringify(t.args??null))return{entry:{...t,env:{...s,...o}},preservedLauncher:!1};const i={...o};for(const[a,d]of Object.entries(s))a in i||(i[a]=d);return{entry:{...n,env:i},preservedLauncher:!0}}function q(e,t){if(!e.detected)return{client:e,action:"skipped",message:`${e.name} not detected`};try{const n=w(e.configPath);l(n)||j(n,{recursive:!0});let o={},s=!1;if(l(e.configPath))try{const u=h(e.configPath,"utf-8");o=T(u),s=!0}catch{o={}}let r;if(s){r=`${e.configPath}.wyrm-backup`,p(r,h(e.configPath),{mode:384});try{N(r,384)}catch{}}const i=he(e,t);let a=!1;const d=(u,m)=>{const g=pe(u.wyrm,m);return u.wyrm=g.entry,g.preservedLauncher};switch(e.configFormat){case"vscode":{o.mcp||(o.mcp={});const u=o.mcp;u.servers||(u.servers={});const m=u.servers;a=d(m,i)||a;break}case"mcp-json":{o[e.mcpKey]||(o[e.mcpKey]={});const u=o[e.mcpKey];a=d(u,i)||a;break}case"zed":{o[e.mcpKey]||(o[e.mcpKey]={});const u=o[e.mcpKey];a=d(u,i)||a;break}}p(e.configPath,JSON.stringify(o,null,2)+`
|
|
5
|
+
`,"utf-8");const f=e.configured?"updated":"configured";return e.configured=!0,{client:e,action:f,message:`${e.name} ${f==="configured"?"configured":"updated"} successfully`+(a?" (kept your custom launch command; only missing env was added)":""),backup:r}}catch(n){return{client:e,action:"failed",message:`Failed to configure ${e.name}: ${n}`}}}function Me(e,t){try{if(!l(e.configPath))return{client:e,action:"skipped",message:"No config file"};const n=h(e.configPath,"utf-8");let o;try{o=T(n)}catch{return{client:e,action:"failed",message:"Config file is not valid JSON; refusing to touch it"}}const r=(e.configFormat==="vscode"?o.mcp?.servers:o[e.mcpKey])?.wyrm;if(!r)return{client:e,action:"skipped",message:"Wyrm not configured in this client"};const i=`${e.configPath}.wyrm-backup`;p(i,n,{encoding:"utf-8",mode:384});try{N(i,384)}catch{}return r.env={...r.env,...t},p(e.configPath,JSON.stringify(o,null,2)+`
|
|
6
|
+
`,"utf-8"),{client:e,action:"updated",message:`env: ${Object.keys(t).join(", ")}`,backup:i}}catch(n){return{client:e,action:"failed",message:String(n)}}}function ge(e){if(!e.detected||!e.configured)return{client:e,action:"skipped",message:`${e.name} not configured`};try{if(!l(e.configPath))return{client:e,action:"skipped",message:`${e.name} config not found`};const t=h(e.configPath,"utf-8"),n=T(t),o=`${e.configPath}.wyrm-backup`;p(o,t,{mode:384});try{N(o,384)}catch{}switch(e.configFormat){case"vscode":{const r=n.mcp?.servers;r&&delete r.wyrm;break}case"mcp-json":case"zed":{const s=n[e.mcpKey];s&&delete s.wyrm;break}}return p(e.configPath,JSON.stringify(n,null,2)+`
|
|
7
|
+
`,"utf-8"),e.configured=!1,{client:e,action:"configured",message:`Removed Wyrm from ${e.name}`,backup:o}}catch(t){return{client:e,action:"failed",message:`Failed to remove from ${e.name}: ${t}`}}}function E(){return{id:"claude-code-hooks",name:"Claude Code (auto-memory)",icon:"\u{1FA9D}",configPath:c(y(),".claude","settings.json"),configFormat:"mcp-json",mcpKey:"",detected:!0,configured:!1}}const W=[{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"]},{script:"wyrm-citation-audit.mjs",events:["Stop"]},{script:"wyrm-prompt-context.mjs",events:["UserPromptSubmit"]}],R=/[$`%"]/;function z(e,t=v()){return t==="win32"?R.test(e)?null:`node ${O(e,t)}`:/^[\w@%+=:,./-]+$/.test(e)?`node ${e}`:`node ${O(e,t)}`}function Y(e,t=v()){return{current:z(e,t),legacy:`node ${e}`}}function ye(e,t,n){if(t.current===null)return!1;const o=t.current;let s=!1,r=!1;for(let i=0;i<e.length;i++){const a=e[i];if(!Array.isArray(a.hooks))continue;const d=a.hooks.length;a.hooks=a.hooks.filter(f=>f.command!==o&&f.command!==t.legacy?!0:s?(r=!0,!1):(s=!0,f.command!==o&&(f.command=o,r=!0),!0)),d>0&&a.hooks.length===0&&(e.splice(i,1),i--)}if(!s){const i={type:"command",command:o,timeout:30};e.push(n?{matcher:n,hooks:[i]}:{hooks:[i]}),r=!0}return r}function we(e={}){const t=e.claudeDir??c(y(),".claude");if(!l(t))return null;const n=E();e.claudeDir&&(n.configPath=c(t,"settings.json"));try{const o=c(t,"hooks");l(o)||j(o,{recursive:!0});const s=w(A(import.meta.url)),r=c(s,"..","scripts","hooks"),i=n.configPath;let a={};if(l(i))try{a=JSON.parse(h(i,"utf-8"))}catch{a={}}const d=a.hooks??{},f=c(o,"wyrm-cli.json"),u=JSON.stringify({cli:c(s,"wyrm.js")},null,2)+`
|
|
8
|
+
`;let m=!1;try{let k=!1;try{const S=JSON.parse(h(f,"utf-8"));k=typeof S.cli=="string"&&l(S.cli)}catch{}(ue(c(s,"wyrm.js"))!==null||!k)&&!(l(f)&&h(f,"utf-8")===u)&&(p(f,u,"utf-8"),m=!0)}catch{}let g=!1;const $=[],x=[];for(const k of W){const D=c(r,k.script);if(!l(D)){$.push(k.script);continue}const S=c(o,k.script);ce(D,S);try{N(S,493)}catch{}const H=Y(S);if(H.current===null){x.push(k.script);continue}for(const C of k.events){const J=typeof C=="string"?C:C.event,ie=typeof C=="string"?void 0:C.matcher,G=Array.isArray(d[J])?d[J]:[];ye(G,H,ie)&&(g=!0),d[J]=G}}if(a.hooks=d,p(i,JSON.stringify(a,null,2)+`
|
|
9
|
+
`,"utf-8"),x.length===W.length-$.length&&x.length>0)return{client:n,action:"failed",message:`${o} contains a character ($, \`, % or ") that no quoting makes safe in every Windows shell, so the hooks were not wired.`};n.configured=!0;const se=$.length?` (missing from package: ${$.join(", ")})`:"";return{client:n,action:g||m?"configured":"skipped",message:(g?"auto-memory hooks installed":m?"hooks already installed; CLI location refreshed":"already installed")+se}}catch(o){return{client:n,action:"failed",message:o instanceof Error?o.message:String(o)}}}function Ue(){const e=c(y(),".claude");if(!l(e))return null;const t=E();try{const n=w(A(import.meta.url)),o=z(c(n,"wyrm-statusline.js"));if(o===null)return{client:t,action:"failed",message:'the install path contains a character ($, `, % or ") that no quoting makes safe in every Windows shell, so the statusline was not set'};const s=t.configPath;let r={};if(l(s))try{r=JSON.parse(h(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 a=i?.command===o;return r.statusLine={type:"command",command:o,padding:0},p(s,JSON.stringify(r,null,2)+`
|
|
10
|
+
`,"utf-8"),{client:t,action:a?"skipped":"configured",message:a?"buddy statusline already installed":"Wyrm buddy statusline installed (persistent in the Claude Code TUI)"}}catch(n){return{client:t,action:"failed",message:n instanceof Error?n.message:String(n)}}}function Re(){const e=c(y(),".claude","settings.json");if(!l(e))return null;const t=E();try{const n=JSON.parse(h(e,"utf-8")),o=n.statusLine;return o?.command&&/wyrm-statusline/.test(o.command)?(delete n.statusLine,p(e,JSON.stringify(n,null,2)+`
|
|
11
|
+
`,"utf-8"),{client:t,action:"configured",message:"Wyrm statusline removed"}):{client:t,action:"skipped",message:"no Wyrm statusline set"}}catch(n){return{client:t,action:"failed",message:n instanceof Error?n.message:String(n)}}}function ve(e={}){const t=e.claudeDir??c(y(),".claude"),n=c(t,"settings.json");if(!l(n))return null;const o=E();e.claudeDir&&(o.configPath=n);try{const s=JSON.parse(h(n,"utf-8")),r=c(t,"hooks"),i=new Set(W.flatMap(f=>{const u=Y(c(r,f.script));return u.current===null?[u.legacy]:[u.current,u.legacy]})),a=new Set(W.flatMap(f=>f.events.map(u=>typeof u=="string"?u:u.event))),d=s.hooks;if(d){for(const f of a)Array.isArray(d[f])&&(d[f]=d[f].map(u=>({...u,hooks:Array.isArray(u.hooks)?u.hooks.filter(m=>!i.has(m.command??"")):u.hooks})).filter(u=>!Array.isArray(u.hooks)||u.hooks.length>0),d[f].length===0&&delete d[f]);Object.keys(d).length===0&&delete s.hooks}return p(n,JSON.stringify(s,null,2)+`
|
|
12
|
+
`,"utf-8"),{client:o,action:"configured",message:"auto-memory hooks removed"}}catch(s){return{client:o,action:"failed",message:s instanceof Error?s.message:String(s)}}}const V=/wyrm-guard/,Q=5,ke=["Bash","Edit|Write|MultiEdit|NotebookEdit"];function b(e){return e.settingsPath??process.env.CLAUDE_SETTINGS??c(y(),".claude","settings.json")}function L(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 O(e,t=v()){return t==="win32"?`"${e.replace(/\\/g,"/").replace(/"/g,'""')}"`:`'${e.replace(/'/g,"'\\''")}'`}function Z(e=v()){const t=w(A(import.meta.url));return e==="win32"&&R.test(t)?`the install path (${t}) contains a character ($, \`, % or ") that no quoting makes safe in every Windows shell, so the hook was not installed`:null}function X(e,t=v()){return t==="win32"&&R.test(e)?null:`node ${O(e,t)}`}function Pe(){const e=w(A(import.meta.url)),t=c(e,"wyrm-guard.js");if(l(t))return X(t);try{const n=v()==="win32"?"where":"which",s=P(n,["wyrm-guard"],{encoding:"utf-8",timeout:5e3}).stdout?.trim().split(`
|
|
13
|
+
`)[0]?.trim();if(s)return O(s)}catch{}return null}function K(e,t){const n=e.hooks;if(!n||typeof n!="object"||Array.isArray(n))return!1;let o=!1;for(const s of Object.keys(n)){const r=n[s];if(!Array.isArray(r))continue;let i=!1;const a=[];for(const d of r){const f=d;if(!f||typeof f!="object"||!Array.isArray(f.hooks)){a.push(d);continue}const u=f.hooks.filter(m=>!t.test(m?.command??""));if(u.length===f.hooks.length){a.push(d);continue}o=!0,i=!0,u.length>0&&a.push({...f,hooks:u})}i&&(a.length===0?delete n[s]:n[s]=a)}return o&&Object.keys(n).length===0&&delete e.hooks,o}function ee(e){return K(e,V)}function be(e={}){if(!!!(e.settingsPath??process.env.CLAUDE_SETTINGS)&&!l(c(y(),".claude")))return{client:L(b(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 n=b(e),o=L(n);try{const s=e.guardCommand??Pe();if(!s)return{client:o,action:"skipped",message:Z()??"wyrm-guard bin not found (npm i -g wyrm-mcp, or build dist/); firewall hooks not installed"};j(w(n),{recursive:!0});let r={};if(l(n))try{const g=JSON.parse(h(n,"utf-8"));if(typeof g!="object"||g===null||Array.isArray(g))throw new Error("not an object");r=g}catch{return{client:o,action:"failed",message:`${n} is not valid JSON; 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:o,action:"failed",message:`${n} "hooks" has an unexpected shape (not a plain object); refusing to modify (fix the file, then re-run)`};const a=i?.PreToolUse;if(a!==void 0&&!Array.isArray(a))return{client:o,action:"failed",message:`${n} hooks.PreToolUse has an unexpected shape (not an array); refusing to modify (fix the file, then re-run)`};const d=JSON.stringify(r);ee(r);const f=r.hooks??{},u=Array.isArray(f.PreToolUse)?f.PreToolUse:[];for(const g of ke)u.push({matcher:g,hooks:[{type:"command",command:s,timeout:Q}]});f.PreToolUse=u,r.hooks=f;const m=JSON.stringify(r)!==d;return m&&p(n,JSON.stringify(r,null,2)+`
|
|
14
|
+
`,"utf-8"),o.configured=!0,{client:o,action:m?"configured":"skipped",message:m?"failure-firewall hooks installed (PreToolUse: Bash + Edit|Write|MultiEdit|NotebookEdit)":"failure-firewall hooks already installed"}}catch(s){return{client:o,action:"failed",message:s instanceof Error?s.message:String(s)}}}function Se(e={}){const t=b(e);if(!l(t))return null;const n=L(t);try{let o;try{const r=JSON.parse(h(t,"utf-8"));if(typeof r!="object"||r===null||Array.isArray(r))throw new Error("not an object");o=r}catch{return{client:n,action:"failed",message:`${t} is not valid JSON; not touching it`}}return ee(o)?(p(t,JSON.stringify(o,null,2)+`
|
|
15
|
+
`,"utf-8"),{client:n,action:"configured",message:"failure-firewall hooks removed"}):{client:n,action:"skipped",message:"no wyrm-guard hooks installed"}}catch(o){return{client:n,action:"failed",message:o instanceof Error?o.message:String(o)}}}function Le(e={}){const t=b(e),n=[];try{const s=JSON.parse(h(t,"utf-8")).hooks??{};for(const r of Object.values(s))if(Array.isArray(r))for(const i of r)for(const a of i?.hooks??[]){const d=a?.command;d&&V.test(d)&&n.push(d)}}catch{}return{settingsPath:t,installed:n.length>0,commands:n}}const F=/wyrm-memo/,je=[{event:"PreToolUse",matcher:"Read"},{event:"PreToolUse",matcher:"Bash"},{event:"PostToolUse",matcher:"Read"},{event:"PostToolUse",matcher:"Bash"}];function te(e){return{id:"claude-code-memo",name:"Claude Code (token firewall)",icon:"",configPath:e,configFormat:"mcp-json",mcpKey:"",detected:!0,configured:!1}}function Ae(){const e=w(A(import.meta.url)),t=c(e,"wyrm-memo.js");if(l(t))return X(t);try{const n=v()==="win32"?"where":"which",s=P(n,["wyrm-memo"],{encoding:"utf-8",timeout:5e3}).stdout?.trim().split(`
|
|
16
|
+
`)[0]?.trim();if(s)return O(s)}catch{}return null}function Ce(e={}){const t=b(e),n=!!(e.settingsPath??process.env.CLAUDE_SETTINGS),o=te(t);if(!n&&!l(c(y(),".claude")))return{client:o,action:"skipped",message:"token firewall: ~/.claude not found on this machine (needs the Claude Code CLI). Not armed. Install Claude Code then re-run `wyrm memo`"};try{const s=e.memoCommand??Ae();if(!s)return{client:o,action:"skipped",message:Z()??"wyrm-memo bin not found (npm i -g wyrm-mcp, or build dist/). Token-firewall hooks not installed"};j(w(t),{recursive:!0});let r={};if(l(t))try{const u=JSON.parse(h(t,"utf-8"));if(typeof u!="object"||u===null||Array.isArray(u))throw new Error("not an object");r=u}catch{return{client:o,action:"failed",message:`${t} is not valid JSON; 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:o,action:"failed",message:`${t} "hooks" has an unexpected shape (not a plain object); refusing to modify`};for(const u of["PreToolUse","PostToolUse"]){const m=i?.[u];if(m!==void 0&&!Array.isArray(m))return{client:o,action:"failed",message:`${t} hooks.${u} has an unexpected shape (not an array); refusing to modify`}}const a=JSON.stringify(r);K(r,F);const d=r.hooks??{};for(const{event:u,matcher:m}of je){const g=Array.isArray(d[u])?d[u]:[];g.push({matcher:m,hooks:[{type:"command",command:s,timeout:Q}]}),d[u]=g}r.hooks=d;const f=JSON.stringify(r)!==a;return f&&p(t,JSON.stringify(r,null,2)+`
|
|
17
|
+
`,"utf-8"),o.configured=!0,{client:o,action:f?"configured":"skipped",message:f?"token-firewall hooks installed (PreToolUse + PostToolUse: Read, Bash). Mode: observe (set WYRM_MEMO_MODE=warn to hand maps back)":"token-firewall hooks already installed"}}catch(s){return{client:o,action:"failed",message:s instanceof Error?s.message:String(s)}}}function Ke(e={}){const t=b(e);if(!l(t))return null;const n=te(t);try{let o;try{const r=JSON.parse(h(t,"utf-8"));if(typeof r!="object"||r===null||Array.isArray(r))throw new Error("not an object");o=r}catch{return{client:n,action:"failed",message:`${t} is not valid JSON; not touching it`}}return K(o,F)?(p(t,JSON.stringify(o,null,2)+`
|
|
18
|
+
`,"utf-8"),{client:n,action:"configured",message:"token-firewall hooks removed"}):{client:n,action:"skipped",message:"no wyrm-memo hooks installed"}}catch(o){return{client:n,action:"failed",message:o instanceof Error?o.message:String(o)}}}function Fe(e={}){const t=b(e),n=[];try{const s=JSON.parse(h(t,"utf-8")).hooks??{};for(const r of Object.values(s))if(Array.isArray(r))for(const i of r)for(const a of i?.hooks??[]){const d=a?.command;d&&F.test(d)&&n.push(d)}}catch{}return{settingsPath:t,installed:n.length>0,commands:n}}function _e(e){const t={serverPath:e?.serverPath||M(),dbPath:e?.dbPath||U(),httpPort:e?.httpPort},n=c(y(),".wyrm");l(n)||j(n,{recursive:!0});const o=_(),s=[];for(const d of o)s.push(q(d,t));oe(t);const r=we();r&&s.push(r);const i=be();i&&s.push(i);const a=Ce();return a&&s.push(a),s}function Ie(){const e=_(),t=[];for(const s of e)t.push(ge(s));const n=ve();n&&t.push(n);const o=Se();return o&&t.push(o),t}function Oe(e,t){const n={serverPath:t?.serverPath||M(),dbPath:t?.dbPath||U(),httpPort:t?.httpPort},o=_(),s=[];for(const r of o)e.includes(r.id)&&s.push(q(r,n));return oe(n),s}function oe(e){const t=c(y(),".wyrm","wyrm-config.json"),n=_().filter(s=>s.configured).map(s=>s.id),o={version:"3.0.0",serverPath:e.serverPath,dbPath:e.dbPath,httpPort:e.httpPort,configuredClients:n,lastSetup:new Date().toISOString(),autoUpdate:!0};p(t,JSON.stringify(o,null,2)+`
|
|
19
|
+
`,"utf-8")}function ne(){const e=c(y(),".wyrm","wyrm-config.json");if(!l(e))return null;try{return JSON.parse(h(e,"utf-8"))}catch{return null}}function Be(){const e=ne();return e?Oe(e.configuredClients,{serverPath:e.serverPath,dbPath:e.dbPath,httpPort:e.httpPort}):_e()}const re="<!-- wyrm:start -->",I="<!-- wyrm:end -->",B=`${re}
|
|
19
20
|
## Wyrm Memory + Agent (auto-managed \u2014 do not edit this section)
|
|
20
21
|
|
|
21
22
|
**Session start** \u2014 call \`wyrm_session_prime\` first to load project context (ground truths, open quests, recent sessions, scaffolds) before responding.
|
|
@@ -39,23 +40,23 @@ import{existsSync as f,readFileSync as y,writeFileSync as p,mkdirSync as P,copyF
|
|
|
39
40
|
**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.
|
|
40
41
|
|
|
41
42
|
**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.
|
|
42
|
-
${
|
|
43
|
+
${I}`;function He(e,t){const n={injected:[],skipped:[],errors:[]},o=(r,i)=>{try{const a=w(r);if(l(a)||j(a,{recursive:!0}),l(r)){const d=h(r,"utf-8"),f=d.indexOf(re),u=d.indexOf(I);if(f!==-1&&u!==-1&&u>f){const m=d.slice(0,f),g=d.slice(u+I.length);p(r,`${m}${B}${g}`,"utf-8")}else{const m=d.endsWith(`
|
|
43
44
|
`)?`
|
|
44
45
|
`:`
|
|
45
46
|
|
|
46
|
-
`;p(r,`${
|
|
47
|
-
`,"utf-8")}}else p(r,`${
|
|
48
|
-
`,"utf-8");
|
|
49
|
-
`;)
|
|
50
|
-
`;continue}if(s==="/"&&e[
|
|
51
|
-
`;)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
|
|
47
|
+
`;p(r,`${d}${m}${B}
|
|
48
|
+
`,"utf-8")}}else p(r,`${B}
|
|
49
|
+
`,"utf-8");n.injected.push(i)}catch(a){n.errors.push(`${i}: ${a}`)}},s=t.length===0?["copilot","cursor"]:t;for(const r of s){const i=r.toLowerCase();if(i==="copilot"||i==="vscode-copilot"){const a=c(e,".github","copilot-instructions.md");o(a,".github/copilot-instructions.md")}else if(i==="cursor"){const a=c(e,".cursor","rules");o(a,".cursor/rules")}else n.skipped.push(r)}return n}function T(e){let t="",n=!1;for(let o=0;o<e.length;o++){const s=e[o];if(n){if(t+=s,s==="\\"){t+=e[++o]??"";continue}s==='"'&&(n=!1);continue}if(s==='"'){n=!0,t+=s;continue}if(s==="/"&&e[o+1]==="/"){for(;o<e.length&&e[o]!==`
|
|
50
|
+
`;)o++;t+=`
|
|
51
|
+
`;continue}if(s==="/"&&e[o+1]==="*"){for(o+=2;o<e.length&&!(e[o]==="*"&&e[o+1]==="/");)o++;o+=1;continue}if(s===","){let r=o+1;for(;;){for(;r<e.length&&/\s/.test(e[r]);)r++;if(e[r]==="/"&&e[r+1]==="/"){for(;r<e.length&&e[r]!==`
|
|
52
|
+
`;)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 Ge(){const e=_(),t=e.filter(r=>r.detected),n=e.filter(r=>r.configured);let o=`\u{F115D} Wyrm Auto-Configure Status
|
|
52
53
|
|
|
53
|
-
`;
|
|
54
|
-
`,
|
|
54
|
+
`;o+=` Detected: ${t.length}/${e.length} AI clients
|
|
55
|
+
`,o+=` Configured: ${n.length}/${t.length} clients
|
|
55
56
|
|
|
56
|
-
`;for(const r of e){const i=r.detected?r.configured?" \u25CF":" \u25D0":" \u25CB",
|
|
57
|
-
`}const s=
|
|
57
|
+
`;for(const r of e){const i=r.detected?r.configured?" \u25CF":" \u25D0":" \u25CB",a=r.version?` (${r.version})`:"",d=r.detected?r.configured?"configured \u2713":"detected, not configured":"not found";o+=`${i} ${r.icon} ${r.name}${a}: ${d}
|
|
58
|
+
`}const s=ne();return s&&(o+=`
|
|
58
59
|
Server: ${s.serverPath}
|
|
59
|
-
`,
|
|
60
|
-
`,
|
|
61
|
-
`),
|
|
60
|
+
`,o+=` DB: ${s.dbPath}
|
|
61
|
+
`,o+=` Last: ${new Date(s.lastSetup).toLocaleString()}
|
|
62
|
+
`),o}export{Q as GUARD_HOOK_TIMEOUT_SEC,je as MEMO_HOOK_EVENTS,B as WYRM_INJECT_BLOCK,Me as applyWyrmEnvToClient,_e as autoConfigureAll,q as configureClient,Oe as configureSpecific,_ as detectClients,Je as exportServerConfig,M as findWyrmServerPath,U as getDefaultDbPath,Ge as getStatusSummary,Y as hookCommandForms,z as hookNodeCommand,He as injectSystemPrompt,we as installClaudeCodeHooks,Ue as installClaudeStatusline,be as installWyrmGuardHooks,Ce as installWyrmMemoHooks,ne as loadWyrmMeta,ye as mergeHookCommand,pe as mergeServerEntry,T as parseJsonWithComments,O as quoteHookPath,Be as reconfAll,ve as removeClaudeCodeHooks,Re as removeClaudeStatusline,Ie as removeFromAll,ge as removeFromClient,Se as removeWyrmGuardHooks,Ke as removeWyrmMemoHooks,Pe as resolveWyrmGuardCommand,Ae as resolveWyrmMemoCommand,Le as wyrmGuardHookStatus,Fe as wyrmMemoHookStatus};
|
package/dist/backup-cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import{join as R,resolve as _,relative as M,isAbsolute as L}from"path";import{homedir as N}from"os";import{existsSync as T}from"fs";import{createInterface as F}from"readline";import{c as d,formatTable as v,printSection as k,printSuccess as S,printError as g,printWarning as C}from"./cli.js";import{createBackup as I,listBackups as U,pruneBackups as D,planRotation as O,restoreBackup as j,scanBackupHygiene as P,applyLegacyActions as H,defaultBackupDir as V,vaultBackupKey as
|
|
2
|
-
Dry run: nothing was deleted. Re-run without --dry-run to remove these.`));return}case"restore":{const s=a[1];if(!s){g(`usage: ${
|
|
3
|
-
Dry run: nothing was written. Re-run with --apply to restore.`));return}case"legacy":{const s=e.apply===!0&&e["dry-run"]!==!0,t=P({dbPath:l,dir:i});k(s?"Legacy backups":"Legacy backups (dry run)");const c=t.legacy.reduce((o,h)=>o+h.bytes,0);console.log(` ${t.legacy.length} database copy(ies) outside rotation, ${m(c)}`);for(const o of t.legacy)console.log(` ${f(o.path,t.home)} 0${o.mode.toString(8)} ${m(o.bytes)}${o.sidecars.length?` +${o.sidecars.length} sidecar(s)`:""}`);console.log(` ${t.exposed.length} item(s) readable by group or others`);for(const o of t.exposed)console.log(` ${f(o.path,t.home)} 0${o.mode.toString(8)}`);if(!s){console.log(d.dim(`
|
|
4
|
-
Dry run: nothing changed.`)),console.log(d.dim(" --apply tighten permissions (files 0600, folders 0700)")),console.log(d.dim(" --apply --adopt also move the copies into rotation, where prune ages them out")),console.log(d.dim(" --apply --delete delete the copies instead (asks you to type DELETE unless --yes)"));return}const y=e.delete===!0;if(y&&e.yes!==!0&&!await
|
|
1
|
+
import{join as R,resolve as _,relative as M,isAbsolute as L}from"path";import{homedir as N}from"os";import{existsSync as T}from"fs";import{createInterface as F}from"readline";import{c as d,formatTable as v,printSection as k,printSuccess as S,printError as g,printWarning as C}from"./cli.js";import{createBackup as I,listBackups as U,pruneBackups as D,planRotation as O,restoreBackup as j,scanBackupHygiene as P,applyLegacyActions as H,defaultBackupDir as V,vaultBackupKey as A,DEFAULT_KEEP_DAILY as z,DEFAULT_KEEP_WEEKLY as G}from"./backup.js";function q(n){const a=[],e={};for(let u=0;u<n.length;u++){const l=n[u];if(l.startsWith("--")){const i=n[u+1];i!==void 0&&!i.startsWith("--")?(e[l.slice(2)]=i,u++):e[l.slice(2)]=!0}else a.push(l)}return{positional:a,flags:e}}function J(){return process.env.WYRM_DB_PATH??R(N(),".wyrm","wyrm.db")}function W(n,a,e){const u=typeof n=="string"?n:a,l=u===void 0?NaN:parseInt(u,10);return Number.isFinite(l)&&l>=0?l:e}function m(n){return n<1024?`${n} B`:n<1024**2?`${(n/1024).toFixed(1)} KB`:n<1024**3?`${(n/1024**2).toFixed(1)} MB`:`${(n/1024**3).toFixed(2)} GB`}function Q(n){const a=(Date.now()-n.getTime())/36e5;return a<1?"under 1h":a<48?`${Math.floor(a)}h`:`${Math.floor(a/24)}d`}function Y(n){const a=typeof n["passphrase-env"]=="string"?n["passphrase-env"]:"WYRM_BACKUP_PASSPHRASE",e=process.env[a];if(!e)throw new Error(`set ${a} to the backup passphrase (read from the environment, never from the command line)`);return{source:"passphrase",secret:e}}function X(n){return a=>a==="vault"?A(!1):Y(n)}async function B(n,a){if(!process.stdin.isTTY)return!1;const e=F({input:process.stdin,output:process.stdout});try{return(await new Promise(l=>e.question(n,l))).trim()===a}finally{e.close()}}function f(n,a){const e=M(a,n);return e&&!e.startsWith("..")&&!L(e)?e:n}const K=["wyrm backup create [--encrypt | --passphrase-env <VAR>] [--label <name>] [--no-prune]","wyrm backup list","wyrm backup prune [--dry-run] [--keep-daily <n>] [--keep-weekly <n>] [--include-labelled]","wyrm backup restore <file> [--dry-run | --apply [--yes]] [--target <db>] [--passphrase-env <VAR>]","wyrm backup legacy [--dry-run | --apply [--adopt | --delete --yes]]","wyrm backup cloud [--prune <prefix> | --stale-days <n>] [--yes]"];async function re(n){const{positional:a,flags:e}=q(n),u=a[0]??"list",l=J(),i=typeof e.dir=="string"?_(e.dir):V(l),w=W(e["keep-daily"],process.env.WYRM_BACKUP_KEEP_DAILY,z),$=W(e["keep-weekly"],process.env.WYRM_BACKUP_KEEP_WEEKLY,G);try{switch(u){case"create":{const s=e.encrypt===!0?A(!0):typeof e["passphrase-env"]=="string"?Y(e):void 0,t=await I({dbPath:l,dir:i,label:typeof e.label=="string"?e.label:void 0,key:s});if(S(`Snapshot ${t.file} \xB7 ${m(t.bytes)} \xB7 ${t.encrypted?`encrypted (${s.source} key)`:"plaintext"} \xB7 mode 0600 in ${i}`),s||console.log(d.dim(" plaintext: anyone who can read this file can read the whole store. --encrypt keys it from the vault.")),e["no-prune"]!==!0){const c=D(i,{daily:w,weekly:$,dryRun:!1});c.remove.length>0&&console.log(` Rotation removed ${c.remove.length} older snapshot(s) (keeping ${w} daily, ${$} weekly).`)}return}case"list":case"ls":{const s=U(i);if(k(`Snapshots in ${i}`),s.length===0)console.log(d.dim(" none yet: wyrm backup create --encrypt"));else{const c=O(s,{daily:w,weekly:$}),y=new Set(c.keep.map(o=>o.path)),r=new Set(c.pinned.map(o=>o.path));console.log(v(["File","Size","Mode","Encrypted","Age","Rotation"],s.map(o=>[o.file,m(o.bytes),`0${o.mode.toString(8)}`,o.encrypted?"yes":"no",Q(o.at),r.has(o.path)?"pinned (labelled)":y.has(o.path)?"keep":"next prune"])))}const t=P({dbPath:l,dir:i});(t.legacy.length>0||t.exposed.length>0)&&C(`${t.legacy.length} database copy(ies) outside rotation, ${t.exposed.length} item(s) readable by others: wyrm backup legacy --dry-run`);return}case"prune":{const s=e["dry-run"]===!0,t=D(i,{daily:w,weekly:$,dryRun:s,includeLabelled:e["include-labelled"]===!0});k(s?"Rotation (dry run)":"Rotation"),console.log(` keep ${t.keep.length} (${w} daily, ${$} weekly, newest always)`),t.pinned.length>0&&console.log(` pinned ${t.pinned.length} labelled snapshot(s); --include-labelled rotates them too`);for(const c of t.remove)console.log(` ${s?"would remove":"removed"} ${c.file} ${m(c.bytes)}`);t.remove.length===0?console.log(d.dim(" nothing to remove")):s&&console.log(d.dim(`
|
|
2
|
+
Dry run: nothing was deleted. Re-run without --dry-run to remove these.`));return}case"restore":{const s=a[1];if(!s){g(`usage: ${K[3]}`),process.exitCode=1;return}const t=T(s)?_(s):R(i,s);if(!T(t)){g(`no snapshot at ${s} (also looked in ${i})`),process.exitCode=1;return}const c=e.apply===!0&&e["dry-run"]!==!0,y=typeof e.target=="string"?_(e.target):l;if(c&&e.yes!==!0&&!await B(`Restore ${f(t,i)} over ${y}? The current store is snapshotted first. Type RESTORE to continue: `,"RESTORE")){g("Restore not confirmed; nothing changed. Pass --yes to skip the prompt."),process.exitCode=1;return}const r=await j({file:t,targetPath:y,apply:c,workDir:R(i,".work"),keyFor:X(e),safetyDir:i});k(c?"Restored":"Restore check (dry run)"),console.log(` snapshot ${f(t,i)}${r.inspection.encrypted?" (encrypted)":""}`),console.log(` integrity ${r.inspection.integrity}`),console.log(` schema v${r.inspection.schemaVersion}`);const o=Object.keys(r.inspection.counts);console.log(v(["Table","Snapshot",r.current?"Current store":"Current store (absent)"],o.map(h=>[h,String(r.inspection.counts[h]),r.current?String(r.current[h]??"-"):"-"]))),c?(S(`Restored into ${y}; integrity ${r.targetIntegrity}, row counts match the snapshot.`),r.safetySnapshot&&console.log(` The store as it was before is ${f(r.safetySnapshot,i)}.`),console.log(d.dim(" Restart open Wyrm sessions so their servers read the restored store."))):console.log(d.dim(`
|
|
3
|
+
Dry run: nothing was written. Re-run with --apply to restore.`));return}case"legacy":{const s=e.apply===!0&&e["dry-run"]!==!0,t=P({dbPath:l,dir:i});k(s?"Legacy backups":"Legacy backups (dry run)");const c=t.legacy.reduce((o,h)=>o+h.bytes,0);console.log(` ${t.legacy.length} database copy(ies) outside rotation, ${m(c)}`);for(const o of t.legacy)console.log(` ${f(o.path,t.home)} 0${o.mode.toString(8)} ${m(o.bytes)}${o.sidecars.length?` +${o.sidecars.length} sidecar(s)`:""}`);process.platform==="win32"?console.log(" File permissions: not checked on Windows, where access is governed by ACLs rather than mode bits."):console.log(` ${t.exposed.length} item(s) readable by group or others`);for(const o of t.exposed)console.log(` ${f(o.path,t.home)} 0${o.mode.toString(8)}`);if(!s){console.log(d.dim(`
|
|
4
|
+
Dry run: nothing changed.`)),console.log(d.dim(" --apply tighten permissions (files 0600, folders 0700)")),console.log(d.dim(" --apply --adopt also move the copies into rotation, where prune ages them out")),console.log(d.dim(" --apply --delete delete the copies instead (asks you to type DELETE unless --yes)"));return}const y=e.delete===!0;if(y&&e.yes!==!0&&!await B(`Delete ${t.legacy.length} database copy(ies)? This cannot be undone. Type DELETE to continue: `,"DELETE")){g("Delete not confirmed; nothing was deleted. Pass --yes to skip the prompt."),process.exitCode=1;return}const r=H({dbPath:l,dir:i,tighten:!0,adopt:e.adopt===!0,deleteCopies:y});S(`Tightened ${r.tightened} item(s); adopted ${r.adopted.length}; deleted ${r.deleted.length}.`);for(const o of r.adopted)console.log(` ${f(o.from,t.home)} -> ${f(o.to,t.home)}`);for(const o of r.skipped)C(`skipped ${f(o.path,t.home)}: ${o.why}`);return}case"cloud":{const{WyrmCloudBackup:s}=await import("./cloud-backup.js"),{WYRM_R2_ENDPOINT:t,WYRM_R2_ACCESS_KEY:c,WYRM_R2_SECRET_KEY:y}=process.env;if(!t||!c||!y){g("cloud snapshots need WYRM_R2_ENDPOINT, WYRM_R2_ACCESS_KEY and WYRM_R2_SECRET_KEY (and WYRM_R2_BUCKET if not wyrm-backups)."),process.exitCode=1;return}const r=new s;r.configure({endpoint:t,bucket:process.env.WYRM_R2_BUCKET||"wyrm-backups",accessKeyId:c,secretAccessKey:y});const o=await r.listSnapshotPrefixes();k("Cloud snapshot prefixes"),console.log(v(["Prefix","Machine","Snapshots","Size","Newest"],o.map(p=>[p.prefix,p.current?"this machine":"other",String(p.snapshots),m(p.bytes),p.newest?p.newest.slice(0,10):"-"])));const h=typeof e["stale-days"]=="string"?parseInt(e["stale-days"],10):NaN,x=typeof e.prune=="string"?[e.prune]:Number.isFinite(h)&&h>0?o.filter(p=>!p.current&&p.newest&&Date.now()-Date.parse(p.newest)>h*864e5).map(p=>p.prefix):[];if(x.length===0){console.log(d.dim(`
|
|
5
5
|
Another prefix can be a second live machine. Prune one you have retired:`)),console.log(d.dim(" wyrm backup cloud --prune <prefix> or --stale-days <n> (dry run unless --yes)"));return}const E=e.yes===!0;for(const p of x){const b=await r.pruneSnapshotPrefix(p,{dryRun:!E});console.log(` ${E?"deleted":"would delete"} ${E?b.deleted:b.objects} object(s), ${m(b.bytes)} under ${p}${b.failed?` (${b.failed} failed)`:""}`)}E||console.log(d.dim(`
|
|
6
|
-
Dry run: nothing was deleted. Re-run with --yes to delete.`));return}default:g(`Unknown backup subcommand: ${u}`);for(const s of
|
|
6
|
+
Dry run: nothing was deleted. Re-run with --yes to delete.`));return}default:g(`Unknown backup subcommand: ${u}`);for(const s of K)console.log(` ${s}`);process.exitCode=1}}catch(s){g(s.message),process.exitCode=1}}export{re as cmdBackup,m as fmtBytes};
|
package/dist/backup.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{chmodSync as w,closeSync as k,createReadStream as I,existsSync as y,mkdirSync as rt,openSync as C,readdirSync as A,readSync as b,renameSync as M,statSync as v,unlinkSync as P,writeSync as T,copyFileSync as nt,lstatSync as $}from"fs";import{basename as K,dirname as W,join as d}from"path";import{createCipheriv as ot,createDecipheriv as st,randomBytes as R}from"crypto";import{openDatabase as E}from"./storage/index.js";import{VAULT_CIPHER as m,deriveVaultKey as at,vaultGet as q,vaultSet as ct}from"./vault.js";import{migrations as it}from"./migrations.js";import{bumpDeviceEpoch as lt}from"./lineage.js";import{assertStoreWritable as z}from"./store-open.js";const j=/^wyrm-(\d{8})-(\d{6})(?:-([a-z0-9][a-z0-9-]{0,31}))?\.db(\.enc)?$/,ut=7,ft=4,_="wyrm.backup.key",p=Buffer.from("WYRMBAK1","ascii"),U=16,O=p.length+1+U+m.ivLength,L=Buffer.from("SQLite format 3\0","binary"),V=".work";function Y(t,e=process.env){return e.WYRM_BACKUP_DIR||d(W(t),"backups")}function x(t){rt(t,{recursive:!0,mode:448});try{w(t,448)}catch{}return t}function g(t,e=2){return String(t).padStart(e,"0")}function F(t){return`${t.getUTCFullYear()}${g(t.getUTCMonth()+1)}${g(t.getUTCDate())}-${g(t.getUTCHours())}${g(t.getUTCMinutes())}${g(t.getUTCSeconds())}`}function ht(t){return t.toLowerCase().replace(/[^a-z0-9-]+/g,"-").replace(/^-+|-+$/g,"").slice(0,24)||"snapshot"}function H(t,e){const n=j.exec(e);if(!n)return null;const[,r,o,i,l]=n,s=new Date(Date.UTC(+r.slice(0,4),+r.slice(4,6)-1,+r.slice(6,8),+o.slice(0,2),+o.slice(2,4),+o.slice(4,6))),a=d(t,e);let c;try{c=$(a)}catch{return null}return c.isFile()?{file:e,path:a,at:s,bytes:c.size,mode:c.mode&511,encrypted:!!l,label:i??null}:null}function pt(t){if(!y(t))return[];const e=[];for(const n of A(t)){const r=H(t,n);r&&e.push(r)}return e.sort((n,r)=>r.at.getTime()-n.at.getTime()||r.file.localeCompare(n.file))}function S(t){for(const e of[t,`${t}-wal`,`${t}-shm`,`${t}-journal`])try{y(e)&&P(e)}catch{}}function Bt(t){let e=q(_);if(!e){if(!t)throw new Error(`this snapshot is vault-keyed, but the vault holds no ${_}`);e=R(m.keyLength).toString("hex"),ct(_,e)}return{source:"vault",secret:e}}function Q(t,e){if(t.source==="passphrase"){if(!t.secret)throw new Error("an empty passphrase cannot key a backup");return at(t.secret,e)}const n=Buffer.from(t.secret,"hex");if(n.length!==m.keyLength)throw new Error("the vault backup key is malformed");return n}async function yt(t,e,n){const r=R(U),o=R(m.ivLength),i=ot(m.algorithm,Q(n,r),o),l=C(e,"w",384);try{T(l,Buffer.concat([p,Buffer.from([n.source==="vault"?1:2]),r,o]));for await(const s of I(t))T(l,i.update(s));T(l,i.final()),T(l,i.getAuthTag())}finally{k(l)}}function dt(t){const e=C(t,"r");try{const n=Buffer.alloc(p.length+1);return b(e,n,0,n.length,0)<n.length||!n.subarray(0,p.length).equals(p)?null:n[p.length]===1?"vault":"passphrase"}finally{k(e)}}async function mt(t,e,n){const r=v(t).size;if(r<O+m.tagLength)throw new Error("encrypted snapshot is truncated");const o=C(t,"r"),i=Buffer.alloc(O),l=Buffer.alloc(m.tagLength);try{b(o,i,0,O,0),b(o,l,0,l.length,r-l.length)}finally{k(o)}if(!i.subarray(0,p.length).equals(p))throw new Error("not an encrypted Wyrm snapshot");const s=i[p.length]===1?"vault":"passphrase",a=i.subarray(p.length+1,p.length+1+U),c=i.subarray(p.length+1+U),f=st(m.algorithm,Q(n(s),a),c);f.setAuthTag(l);const u=C(e,"w",384);try{for await(const h of I(t,{start:O,end:r-l.length-1}))T(u,f.update(h));try{T(u,f.final())}catch{throw new Error("decryption failed: wrong key or passphrase, or the snapshot was altered")}}catch(h){throw k(u),S(e),h}k(u)}async function D(t,e){const n=E(t,{readonly:!0,fileMustExist:!0});try{typeof n.backup=="function"?await n.backup(e):n.prepare("VACUUM INTO ?").run(e)}finally{n.close()}}async function gt(t){if(!y(t.dbPath))throw new Error(`no store at ${t.dbPath}`);const e=x(t.dir),n=x(d(e,V)),r=t.now??new Date,o=t.label?ht(t.label):null,i=t.key?".db.enc":".db";let l=o,s=`wyrm-${F(r)}${l?`-${l}`:""}${i}`;for(let h=2;y(d(e,s));h++)l=`${o??"snapshot"}-${h}`,s=`wyrm-${F(r)}-${l}${i}`;const a=d(n,`${s}.partial`),c=d(e,s),f=process.umask(63);try{await wt(t.dbPath,a),t.key?await yt(a,c,t.key):M(a,c),w(c,384)}finally{S(a),process.umask(f)}const u=H(e,s);if(!u)throw new Error("snapshot written but could not be listed");return u}async function wt(t,e){const n=process.umask(63);try{S(e),await D(t,e);const r=E(e);let o="";try{r.pragma("journal_mode = DELETE"),o=String(r.pragma("quick_check",{simple:!0}))}finally{r.close()}if(o!=="ok")throw S(e),new Error(`the snapshot failed its integrity check (${o}); nothing was kept`);w(e,384)}finally{process.umask(n)}}async function kt(t,e={}){let n;try{const r=q(_);r&&(n={source:"vault",secret:r})}catch{}return gt({dbPath:t,dir:e.dir??Y(t),label:"pre-restore",key:n,now:e.now})}async function Nt(t,e){z(e);const n=process.umask(63);try{await D(t,e)}finally{process.umask(n)}const r=E(e);try{try{r.pragma("wal_checkpoint(TRUNCATE)")}catch{}return String(r.pragma("integrity_check",{simple:!0}))}finally{r.close()}}function Et(t){return`${t.getUTCFullYear()}${g(t.getUTCMonth()+1)}${g(t.getUTCDate())}`}function St(t){const e=new Date(Date.UTC(t.getUTCFullYear(),t.getUTCMonth(),t.getUTCDate())),n=e.getUTCDay()||7;e.setUTCDate(e.getUTCDate()+4-n);const r=e.getUTCFullYear(),o=Math.ceil(((e.getTime()-Date.UTC(r,0,1))/864e5+1)/7);return`${r}-W${g(o)}`}function G(t,e){return e||t.label===null||/^(snapshot|legacy)(-\d+)?$/.test(t.label)}function Ct(t,e={}){const n=Math.max(0,Math.floor(e.daily??ut)),r=Math.max(0,Math.floor(e.weekly??ft)),o=e.includeLabelled===!0,i=[...t].sort((u,h)=>h.at.getTime()-u.at.getTime()||h.file.localeCompare(u.file)),l=i.filter(u=>!G(u,o)),s=i.filter(u=>G(u,o)),a=new Set;s[0]&&a.add(s[0].path);const c=new Set,f=new Set;for(const u of s){const h=Et(u.at);!c.has(h)&&c.size<n&&(c.add(h),a.add(u.path));const N=St(u.at);!f.has(N)&&f.size<r&&(f.add(N),a.add(u.path))}return{keep:s.filter(u=>a.has(u.path)),remove:s.filter(u=>!a.has(u.path)),pinned:l}}function It(t,e){const n=Ct(pt(t),e);if(!e.dryRun)for(const r of n.remove)S(r.path);return n}const Tt={projects:"SELECT COUNT(*) AS n FROM projects",sessions:"SELECT COUNT(*) AS n FROM sessions",quests:"SELECT COUNT(*) AS n FROM quests",ground_truths:"SELECT COUNT(*) AS n FROM ground_truths",memory_artifacts:"SELECT COUNT(*) AS n FROM memory_artifacts",failure_patterns:"SELECT COUNT(*) AS n FROM failure_patterns",data_lake:"SELECT COUNT(*) AS n FROM data_lake",context:"SELECT COUNT(*) AS n FROM context",global_context:"SELECT COUNT(*) AS n FROM global_context",entities:"SELECT COUNT(*) AS n FROM entities",relationships:"SELECT COUNT(*) AS n FROM relationships",decision_edges:"SELECT COUNT(*) AS n FROM decision_edges",skills:"SELECT COUNT(*) AS n FROM skills",events:"SELECT COUNT(*) AS n FROM events",vectors:"SELECT COUNT(*) AS n FROM vectors",tool_call_log:"SELECT COUNT(*) AS n FROM tool_call_log",erasure_log:"SELECT COUNT(*) AS n FROM erasure_log",schema_versions:"SELECT COUNT(*) AS n FROM schema_versions"};function B(t){const e={};for(const[n,r]of Object.entries(Tt))try{e[n]=t.prepare(r).get().n}catch{}return e}function bt(t){try{return t.prepare("SELECT COALESCE(MAX(version), 0) AS v FROM schema_versions").get().v}catch{return 0}}const X=it.reduce((t,e)=>Math.max(t,e.version),0);async function J(t,e,n,r){const o=dt(t);if(!o)return r(t,!1);if(!n)throw new Error(`this snapshot is encrypted (${o}-keyed); supply its key`);const i=x(e),l=d(i,`${K(t)}.${process.pid}.restore`),s=process.umask(63);try{return await mt(t,l,n),await r(l,!0)}finally{S(l),process.umask(s)}}function Z(t,e,n){const r=Buffer.alloc(L.length),o=C(e,"r");try{b(o,r,0,r.length,0)}finally{k(o)}if(!r.equals(L))throw new Error("not a SQLite database");const i=E(e,{readonly:!0,fileMustExist:!0});try{return{file:t,encrypted:n,integrity:String(i.pragma("integrity_check",{simple:!0})),schemaVersion:bt(i),counts:B(i)}}finally{i.close()}}async function Pt(t,e){return J(t,e.workDir,e.keyFor,async(n,r)=>Z(t,n,r))}function $t(t,e){const n=new Set([...Object.keys(t),...Object.keys(e)]);for(const r of n)if(t[r]!==e[r])return!1;return!0}async function Kt(t){return J(t.file,t.workDir,t.keyFor,async(e,n)=>{const r=Z(t.file,e,n);if(r.integrity!=="ok")throw new Error(`the snapshot fails integrity_check (${r.integrity}); refusing to restore it`);if(r.schemaVersion>X)throw new Error(`the snapshot is at schema v${r.schemaVersion}, newer than this Wyrm (v${X}); upgrade Wyrm first`);let o=null;if(y(t.targetPath)){const c=E(t.targetPath,{readonly:!0,fileMustExist:!0});try{o=B(c)}finally{c.close()}}if(!t.apply)return{inspection:r,current:o,restored:!1,safetySnapshot:null,targetIntegrity:null};z(t.targetPath);let i=null;y(t.targetPath)&&(i=(await kt(t.targetPath,{dir:t.safetyDir,now:t.now})).path),await D(e,t.targetPath);const l=E(t.targetPath);let s="",a={};try{try{l.pragma("wal_checkpoint(TRUNCATE)")}catch{}s=String(l.pragma("integrity_check",{simple:!0})),a=B(l)}finally{l.close()}if(s!=="ok"||!$t(a,r.counts))throw new Error(`the restored store failed verification${i?`; the pre-restore snapshot is ${i}`:""}`);try{const c=E(t.targetPath);try{lt(c,"restored with wyrm backup restore")}finally{c.close()}}catch{}return{inspection:r,current:o,restored:!0,safetySnapshot:i,targetIntegrity:s}})}function tt(t){try{const e=C(t,"r");try{const n=Buffer.alloc(L.length);return b(e,n,0,n.length,0)===n.length&&n.equals(L)}finally{k(e)}}catch{return!1}}const et=/-(wal|shm|journal)$/;function _t(t){return["-wal","-shm","-journal"].map(e=>`${t}${e}`).filter(e=>y(e))}function Ut(t){const e=W(t.dbPath),n=t.dir??Y(t.dbPath),r=K(t.dbPath),o=[],i=(s,a)=>{const c=$(s),f=c.mode&511;return{path:s,kind:a,bytes:c.size,mode:f,exposed:(f&63)!==0,sidecars:a==="directory"?[]:_t(s)}};if(y(e))for(const s of A(e)){const a=d(e,s);let c;try{c=$(a)}catch{continue}!c.isFile()||s===r||et.test(s)||!(/\.bak|\.db$|\.db\.|\.sqlite/i.test(s)||s.startsWith(`${r}.`))||o.push(i(a,tt(a)?"loose-copy":"other-file"))}const l=(s,a)=>{if(!(!y(s)||a>3)){o.push(i(s,"directory"));for(const c of A(s)){if(a===0&&c===V)continue;const f=d(s,c);let u;try{u=$(f)}catch{continue}if(u.isDirectory()){l(f,a+1);continue}if(!u.isFile()||et.test(c))continue;const h=a===0&&j.test(c)?"rotation":tt(f)?"unrotated":"other-file";o.push(i(f,h))}}};return l(n,0),{home:e,dir:n,items:o,legacy:o.filter(s=>s.kind==="loose-copy"||s.kind==="unrotated"),exposed:o.filter(s=>s.exposed||s.sidecars.some(a=>($(a).mode&63)!==0))}}function Ot(t){return F(v(t).mtime)}function Wt(t){const e=Ut({dbPath:t.dbPath,dir:t.dir}),n={tightened:0,adopted:[],deleted:[],skipped:[]};if(t.tighten)for(const r of e.exposed)try{w(r.path,r.kind==="directory"?448:384);for(const o of r.sidecars)w(o,384);n.tightened++}catch(o){n.skipped.push({path:r.path,why:`chmod failed: ${o.message}`})}if(t.deleteCopies){for(const r of e.legacy)S(r.path),n.deleted.push(r.path);return n}if(t.adopt){const r=x(e.dir);for(const o of e.legacy){const i=`${o.path}-wal`;if(y(i)&&v(i).size>0){n.skipped.push({path:o.path,why:"its -wal file is not empty; open it once with sqlite3 to checkpoint, then adopt"});continue}const l=Ot(o.path);let s=`wyrm-${l}-legacy.db`;for(let c=2;y(d(r,s));c++)s=`wyrm-${l}-legacy-${c}.db`;const a=d(r,s);try{M(o.path,a)}catch{nt(o.path,a),P(o.path)}w(a,384);for(const c of o.sidecars){const f=c.slice(o.path.length);try{M(c,`${a}${f}`),w(`${a}${f}`,384)}catch{}}n.adopted.push({from:o.path,to:a})}}return n}export{j as BACKUP_FILE_RE,X as BINARY_SCHEMA_VERSION,ut as DEFAULT_KEEP_DAILY,ft as DEFAULT_KEEP_WEEKLY,_ as VAULT_BACKUP_SECRET,Wt as applyLegacyActions,gt as createBackup,Y as defaultBackupDir,dt as encryptedSource,x as ensurePrivateDir,Pt as inspectBackup,pt as listBackups,Ct as planRotation,It as pruneBackups,Kt as restoreBackup,Nt as restoreFileInto,B as rowCounts,Ut as scanBackupHygiene,kt as snapshotBeforeRestore,wt as snapshotToFile,Bt as vaultBackupKey};
|
|
1
|
+
import{chmodSync as w,closeSync as E,createReadStream as I,existsSync as y,mkdirSync as nt,openSync as C,readdirSync as A,readSync as b,renameSync as M,statSync as v,unlinkSync as P,writeSync as T,copyFileSync as ot,lstatSync as $}from"fs";import{basename as K,dirname as W,join as d}from"path";import{createCipheriv as st,createDecipheriv as at,randomBytes as R}from"crypto";import{openDatabase as k}from"./storage/index.js";import{VAULT_CIPHER as m,deriveVaultKey as ct,vaultGet as q,vaultSet as it}from"./vault.js";import{migrations as lt}from"./migrations.js";import{bumpDeviceEpoch as ut}from"./lineage.js";import{assertStoreWritable as z}from"./store-open.js";const j=/^wyrm-(\d{8})-(\d{6})(?:-([a-z0-9][a-z0-9-]{0,31}))?\.db(\.enc)?$/,ft=7,ht=4,_="wyrm.backup.key",p=Buffer.from("WYRMBAK1","ascii"),U=16,O=p.length+1+U+m.ivLength,L=Buffer.from("SQLite format 3\0","binary"),V=".work";function Y(t,e=process.env){return e.WYRM_BACKUP_DIR||d(W(t),"backups")}function x(t){nt(t,{recursive:!0,mode:448});try{w(t,448)}catch{}return t}function g(t,e=2){return String(t).padStart(e,"0")}function F(t){return`${t.getUTCFullYear()}${g(t.getUTCMonth()+1)}${g(t.getUTCDate())}-${g(t.getUTCHours())}${g(t.getUTCMinutes())}${g(t.getUTCSeconds())}`}function pt(t){return t.toLowerCase().replace(/[^a-z0-9-]+/g,"-").replace(/^-+|-+$/g,"").slice(0,24)||"snapshot"}function H(t,e){const n=j.exec(e);if(!n)return null;const[,r,o,i,l]=n,s=new Date(Date.UTC(+r.slice(0,4),+r.slice(4,6)-1,+r.slice(6,8),+o.slice(0,2),+o.slice(2,4),+o.slice(4,6))),a=d(t,e);let c;try{c=$(a)}catch{return null}return c.isFile()?{file:e,path:a,at:s,bytes:c.size,mode:c.mode&511,encrypted:!!l,label:i??null}:null}function yt(t){if(!y(t))return[];const e=[];for(const n of A(t)){const r=H(t,n);r&&e.push(r)}return e.sort((n,r)=>r.at.getTime()-n.at.getTime()||r.file.localeCompare(n.file))}function S(t){for(const e of[t,`${t}-wal`,`${t}-shm`,`${t}-journal`])try{y(e)&&P(e)}catch{}}function Nt(t){let e=q(_);if(!e){if(!t)throw new Error(`this snapshot is vault-keyed, but the vault holds no ${_}`);e=R(m.keyLength).toString("hex"),it(_,e)}return{source:"vault",secret:e}}function Q(t,e){if(t.source==="passphrase"){if(!t.secret)throw new Error("an empty passphrase cannot key a backup");return ct(t.secret,e)}const n=Buffer.from(t.secret,"hex");if(n.length!==m.keyLength)throw new Error("the vault backup key is malformed");return n}async function dt(t,e,n){const r=R(U),o=R(m.ivLength),i=st(m.algorithm,Q(n,r),o),l=C(e,"w",384);try{T(l,Buffer.concat([p,Buffer.from([n.source==="vault"?1:2]),r,o]));for await(const s of I(t))T(l,i.update(s));T(l,i.final()),T(l,i.getAuthTag())}finally{E(l)}}function mt(t){const e=C(t,"r");try{const n=Buffer.alloc(p.length+1);return b(e,n,0,n.length,0)<n.length||!n.subarray(0,p.length).equals(p)?null:n[p.length]===1?"vault":"passphrase"}finally{E(e)}}async function gt(t,e,n){const r=v(t).size;if(r<O+m.tagLength)throw new Error("encrypted snapshot is truncated");const o=C(t,"r"),i=Buffer.alloc(O),l=Buffer.alloc(m.tagLength);try{b(o,i,0,O,0),b(o,l,0,l.length,r-l.length)}finally{E(o)}if(!i.subarray(0,p.length).equals(p))throw new Error("not an encrypted Wyrm snapshot");const s=i[p.length]===1?"vault":"passphrase",a=i.subarray(p.length+1,p.length+1+U),c=i.subarray(p.length+1+U),f=at(m.algorithm,Q(n(s),a),c);f.setAuthTag(l);const u=C(e,"w",384);try{for await(const h of I(t,{start:O,end:r-l.length-1}))T(u,f.update(h));try{T(u,f.final())}catch{throw new Error("decryption failed: wrong key or passphrase, or the snapshot was altered")}}catch(h){throw E(u),S(e),h}E(u)}async function D(t,e){const n=k(t,{readonly:!0,fileMustExist:!0});try{typeof n.backup=="function"?await n.backup(e):n.prepare("VACUUM INTO ?").run(e)}finally{n.close()}}async function wt(t){if(!y(t.dbPath))throw new Error(`no store at ${t.dbPath}`);const e=x(t.dir),n=x(d(e,V)),r=t.now??new Date,o=t.label?pt(t.label):null,i=t.key?".db.enc":".db";let l=o,s=`wyrm-${F(r)}${l?`-${l}`:""}${i}`;for(let h=2;y(d(e,s));h++)l=`${o??"snapshot"}-${h}`,s=`wyrm-${F(r)}-${l}${i}`;const a=d(n,`${s}.partial`),c=d(e,s),f=process.umask(63);try{await Et(t.dbPath,a),t.key?await dt(a,c,t.key):M(a,c),w(c,384)}finally{S(a),process.umask(f)}const u=H(e,s);if(!u)throw new Error("snapshot written but could not be listed");return u}async function Et(t,e){const n=process.umask(63);try{S(e),await D(t,e);const r=k(e);let o="";try{r.pragma("journal_mode = DELETE"),o=String(r.pragma("quick_check",{simple:!0}))}finally{r.close()}if(o!=="ok")throw S(e),new Error(`the snapshot failed its integrity check (${o}); nothing was kept`);w(e,384)}finally{process.umask(n)}}async function kt(t,e={}){let n;try{const r=q(_);r&&(n={source:"vault",secret:r})}catch{}return wt({dbPath:t,dir:e.dir??Y(t),label:"pre-restore",key:n,now:e.now})}async function It(t,e){z(e);const n=process.umask(63);try{await D(t,e)}finally{process.umask(n)}const r=k(e);try{try{r.pragma("wal_checkpoint(TRUNCATE)")}catch{}return String(r.pragma("integrity_check",{simple:!0}))}finally{r.close()}}function St(t){return`${t.getUTCFullYear()}${g(t.getUTCMonth()+1)}${g(t.getUTCDate())}`}function Ct(t){const e=new Date(Date.UTC(t.getUTCFullYear(),t.getUTCMonth(),t.getUTCDate())),n=e.getUTCDay()||7;e.setUTCDate(e.getUTCDate()+4-n);const r=e.getUTCFullYear(),o=Math.ceil(((e.getTime()-Date.UTC(r,0,1))/864e5+1)/7);return`${r}-W${g(o)}`}function G(t,e){return e||t.label===null||/^(snapshot|legacy)(-\d+)?$/.test(t.label)}function Tt(t,e={}){const n=Math.max(0,Math.floor(e.daily??ft)),r=Math.max(0,Math.floor(e.weekly??ht)),o=e.includeLabelled===!0,i=[...t].sort((u,h)=>h.at.getTime()-u.at.getTime()||h.file.localeCompare(u.file)),l=i.filter(u=>!G(u,o)),s=i.filter(u=>G(u,o)),a=new Set;s[0]&&a.add(s[0].path);const c=new Set,f=new Set;for(const u of s){const h=St(u.at);!c.has(h)&&c.size<n&&(c.add(h),a.add(u.path));const N=Ct(u.at);!f.has(N)&&f.size<r&&(f.add(N),a.add(u.path))}return{keep:s.filter(u=>a.has(u.path)),remove:s.filter(u=>!a.has(u.path)),pinned:l}}function Pt(t,e){const n=Tt(yt(t),e);if(!e.dryRun)for(const r of n.remove)S(r.path);return n}const bt={projects:"SELECT COUNT(*) AS n FROM projects",sessions:"SELECT COUNT(*) AS n FROM sessions",quests:"SELECT COUNT(*) AS n FROM quests",ground_truths:"SELECT COUNT(*) AS n FROM ground_truths",memory_artifacts:"SELECT COUNT(*) AS n FROM memory_artifacts",failure_patterns:"SELECT COUNT(*) AS n FROM failure_patterns",data_lake:"SELECT COUNT(*) AS n FROM data_lake",context:"SELECT COUNT(*) AS n FROM context",global_context:"SELECT COUNT(*) AS n FROM global_context",entities:"SELECT COUNT(*) AS n FROM entities",relationships:"SELECT COUNT(*) AS n FROM relationships",decision_edges:"SELECT COUNT(*) AS n FROM decision_edges",skills:"SELECT COUNT(*) AS n FROM skills",events:"SELECT COUNT(*) AS n FROM events",vectors:"SELECT COUNT(*) AS n FROM vectors",tool_call_log:"SELECT COUNT(*) AS n FROM tool_call_log",erasure_log:"SELECT COUNT(*) AS n FROM erasure_log",schema_versions:"SELECT COUNT(*) AS n FROM schema_versions"};function B(t){const e={};for(const[n,r]of Object.entries(bt))try{e[n]=t.prepare(r).get().n}catch{}return e}function $t(t){try{return t.prepare("SELECT COALESCE(MAX(version), 0) AS v FROM schema_versions").get().v}catch{return 0}}const X=lt.reduce((t,e)=>Math.max(t,e.version),0);async function J(t,e,n,r){const o=mt(t);if(!o)return r(t,!1);if(!n)throw new Error(`this snapshot is encrypted (${o}-keyed); supply its key`);const i=x(e),l=d(i,`${K(t)}.${process.pid}.restore`),s=process.umask(63);try{return await gt(t,l,n),await r(l,!0)}finally{S(l),process.umask(s)}}function Z(t,e,n){const r=Buffer.alloc(L.length),o=C(e,"r");try{b(o,r,0,r.length,0)}finally{E(o)}if(!r.equals(L))throw new Error("not a SQLite database");const i=k(e,{readonly:!0,fileMustExist:!0});try{return{file:t,encrypted:n,integrity:String(i.pragma("integrity_check",{simple:!0})),schemaVersion:$t(i),counts:B(i)}}finally{i.close()}}async function Kt(t,e){return J(t,e.workDir,e.keyFor,async(n,r)=>Z(t,n,r))}function _t(t,e){const n=new Set([...Object.keys(t),...Object.keys(e)]);for(const r of n)if(t[r]!==e[r])return!1;return!0}async function Wt(t){return J(t.file,t.workDir,t.keyFor,async(e,n)=>{const r=Z(t.file,e,n);if(r.integrity!=="ok")throw new Error(`the snapshot fails integrity_check (${r.integrity}); refusing to restore it`);if(r.schemaVersion>X)throw new Error(`the snapshot is at schema v${r.schemaVersion}, newer than this Wyrm (v${X}); upgrade Wyrm first`);let o=null;if(y(t.targetPath)){const c=k(t.targetPath,{readonly:!0,fileMustExist:!0});try{o=B(c)}finally{c.close()}}if(!t.apply)return{inspection:r,current:o,restored:!1,safetySnapshot:null,targetIntegrity:null};z(t.targetPath);let i=null;y(t.targetPath)&&(i=(await kt(t.targetPath,{dir:t.safetyDir,now:t.now})).path),await D(e,t.targetPath);const l=k(t.targetPath);let s="",a={};try{try{l.pragma("wal_checkpoint(TRUNCATE)")}catch{}s=String(l.pragma("integrity_check",{simple:!0})),a=B(l)}finally{l.close()}if(s!=="ok"||!_t(a,r.counts))throw new Error(`the restored store failed verification${i?`; the pre-restore snapshot is ${i}`:""}`);try{const c=k(t.targetPath);try{ut(c,"restored with wyrm backup restore")}finally{c.close()}}catch{}return{inspection:r,current:o,restored:!0,safetySnapshot:i,targetIntegrity:s}})}function tt(t){try{const e=C(t,"r");try{const n=Buffer.alloc(L.length);return b(e,n,0,n.length,0)===n.length&&n.equals(L)}finally{E(e)}}catch{return!1}}const et=/-(wal|shm|journal)$/;function Ut(t){return["-wal","-shm","-journal"].map(e=>`${t}${e}`).filter(e=>y(e))}function rt(t,e=process.platform){return e==="win32"?!1:(t&63)!==0}function Ot(t){const e=W(t.dbPath),n=t.dir??Y(t.dbPath),r=K(t.dbPath),o=[],i=(s,a)=>{const c=$(s),f=c.mode&511;return{path:s,kind:a,bytes:c.size,mode:f,exposed:rt(f),sidecars:a==="directory"?[]:Ut(s)}};if(y(e))for(const s of A(e)){const a=d(e,s);let c;try{c=$(a)}catch{continue}!c.isFile()||s===r||et.test(s)||!(/\.bak|\.db$|\.db\.|\.sqlite/i.test(s)||s.startsWith(`${r}.`))||o.push(i(a,tt(a)?"loose-copy":"other-file"))}const l=(s,a)=>{if(!(!y(s)||a>3)){o.push(i(s,"directory"));for(const c of A(s)){if(a===0&&c===V)continue;const f=d(s,c);let u;try{u=$(f)}catch{continue}if(u.isDirectory()){l(f,a+1);continue}if(!u.isFile()||et.test(c))continue;const h=a===0&&j.test(c)?"rotation":tt(f)?"unrotated":"other-file";o.push(i(f,h))}}};return l(n,0),{home:e,dir:n,items:o,legacy:o.filter(s=>s.kind==="loose-copy"||s.kind==="unrotated"),exposed:o.filter(s=>s.exposed||s.sidecars.some(a=>rt($(a).mode)))}}function Lt(t){return F(v(t).mtime)}function qt(t){const e=Ot({dbPath:t.dbPath,dir:t.dir}),n={tightened:0,adopted:[],deleted:[],skipped:[]};if(t.tighten)for(const r of e.exposed)try{w(r.path,r.kind==="directory"?448:384);for(const o of r.sidecars)w(o,384);n.tightened++}catch(o){n.skipped.push({path:r.path,why:`chmod failed: ${o.message}`})}if(t.deleteCopies){for(const r of e.legacy)S(r.path),n.deleted.push(r.path);return n}if(t.adopt){const r=x(e.dir);for(const o of e.legacy){const i=`${o.path}-wal`;if(y(i)&&v(i).size>0){n.skipped.push({path:o.path,why:"its -wal file is not empty; open it once with sqlite3 to checkpoint, then adopt"});continue}const l=Lt(o.path);let s=`wyrm-${l}-legacy.db`;for(let c=2;y(d(r,s));c++)s=`wyrm-${l}-legacy-${c}.db`;const a=d(r,s);try{M(o.path,a)}catch{ot(o.path,a),P(o.path)}w(a,384);for(const c of o.sidecars){const f=c.slice(o.path.length);try{M(c,`${a}${f}`),w(`${a}${f}`,384)}catch{}}n.adopted.push({from:o.path,to:a})}}return n}export{j as BACKUP_FILE_RE,X as BINARY_SCHEMA_VERSION,ft as DEFAULT_KEEP_DAILY,ht as DEFAULT_KEEP_WEEKLY,_ as VAULT_BACKUP_SECRET,qt as applyLegacyActions,wt as createBackup,Y as defaultBackupDir,mt as encryptedSource,x as ensurePrivateDir,Kt as inspectBackup,yt as listBackups,rt as modeExposed,Tt as planRotation,Pt as pruneBackups,Wt as restoreBackup,It as restoreFileInto,B as rowCounts,Ot as scanBackupHygiene,kt as snapshotBeforeRestore,Et as snapshotToFile,Nt as vaultBackupKey};
|
package/dist/cli.js
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
import{readFileSync as
|
|
1
|
+
import{existsSync as b,readFileSync as w}from"fs";import{dirname as M,join as y,win32 as g}from"path";import{fileURLToPath as S}from"url";import{shouldUseColor as f}from"./color.js";function x(){try{const t=M(S(import.meta.url));return JSON.parse(w(y(t,"..","package.json"),"utf-8")).version??"dev"}catch{return"dev"}}const v=f(process.env,process.stdout.isTTY),C=t=>v?t:"",R={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"},e=Object.fromEntries(Object.entries(R).map(([t,n])=>[t,C(n)])),$={bold:t=>`${e.bold}${t}${e.reset}`,dim:t=>`${e.dim}${t}${e.reset}`,red:t=>`${e.red}${t}${e.reset}`,green:t=>`${e.green}${t}${e.reset}`,yellow:t=>`${e.yellow}${t}${e.reset}`,blue:t=>`${e.blue}${t}${e.reset}`,magenta:t=>`${e.magenta}${t}${e.reset}`,cyan:t=>`${e.cyan}${t}${e.reset}`,primary:t=>`${e.brightMagenta}${t}${e.reset}`,success:t=>`${e.brightGreen}${t}${e.reset}`,warning:t=>`${e.brightYellow}${t}${e.reset}`,error:t=>`${e.brightRed}${t}${e.reset}`},h={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"},L={critical:"\u{1F534}",high:"\u{1F7E0}",medium:"\u{1F7E1}",low:"\u{1F7E2}"};function N(){return`
|
|
2
2
|
${e.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${e.reset}
|
|
3
3
|
${e.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${e.reset}
|
|
4
4
|
${e.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${e.reset}
|
|
5
5
|
${e.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${e.reset}
|
|
6
6
|
${e.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${e.reset}
|
|
7
7
|
${e.brightMagenta} \u255A\u2550\u2550\u255D\u255A\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D${e.reset}
|
|
8
|
-
${e.dim} Persistent AI Memory System v${
|
|
8
|
+
${e.dim} Persistent AI Memory System v${x()}${e.reset}
|
|
9
9
|
${e.dim} ghosts.lk${e.reset}
|
|
10
|
-
`}function
|
|
11
|
-
`),
|
|
12
|
-
`,s&&(
|
|
13
|
-
`);for(const c of n)
|
|
14
|
-
`;return s&&(
|
|
10
|
+
`}function T(){return`${e.brightMagenta}\u{F115D} Wyrm v${x()}${e.reset}`}class _{frames=["\u280B","\u2819","\u2839","\u2838","\u283C","\u2834","\u2826","\u2827","\u2807","\u280F"];current=0;interval=null;text;constructor(n="Loading..."){this.text=n}start(){this.interval=setInterval(()=>{process.stdout.write(`\r${e.cyan}${this.frames[this.current]}${e.reset} ${this.text}`),this.current=(this.current+1)%this.frames.length},80)}update(n){this.text=n}succeed(n){this.stop(),console.log(`\r${$.success("\u2713")} ${n||this.text}`)}fail(n){this.stop(),console.log(`\r${$.error("\u2717")} ${n||this.text}`)}warn(n){this.stop(),console.log(`\r${$.warning("!")} ${n||this.text}`)}stop(){this.interval&&(clearInterval(this.interval),this.interval=null,process.stdout.write("\r\x1B[K"))}}class G{total;current=0;width;label;constructor(n,o="Progress",r=30){this.total=n,this.width=r,this.label=o}update(n,o){this.current=n,o&&(this.label=o),this.render()}increment(n){this.update(this.current+1,n)}render(){const n=Math.min(100,Math.floor(this.current/this.total*100)),o=Math.floor(this.current/this.total*this.width),r=this.width-o,s=`${e.brightMagenta}${"\u2588".repeat(o)}${e.dim}${"\u2591".repeat(r)}${e.reset}`,i=`${n}%`.padStart(4);process.stdout.write(`\r${this.label} ${s} ${i} (${this.current}/${this.total})`)}complete(){this.update(this.total),console.log()}}function O(t,n,o={}){const{padding:r=2,border:s=!0}=o,i=t.map((c,l)=>{const u=Math.max(...n.map(d=>(d[l]||"").length));return Math.max(c.length,u)}),a=(c,l)=>c.padEnd(l),p=i.map(c=>"\u2500".repeat(c+r)).join(s?"\u253C":" ");let m="";s&&(m+=`${e.dim}\u250C${i.map(c=>"\u2500".repeat(c+r)).join("\u252C")}\u2510${e.reset}
|
|
11
|
+
`),m+=(s?`${e.dim}\u2502${e.reset}`:"")+t.map((c,l)=>` ${$.bold(a(c,i[l]))} `).join(s?`${e.dim}\u2502${e.reset}`:"")+(s?`${e.dim}\u2502${e.reset}`:"")+`
|
|
12
|
+
`,s&&(m+=`${e.dim}\u251C${p}\u2524${e.reset}
|
|
13
|
+
`);for(const c of n)m+=(s?`${e.dim}\u2502${e.reset}`:"")+c.map((l,u)=>` ${a(l||"",i[u])} `).join(s?`${e.dim}\u2502${e.reset}`:"")+(s?`${e.dim}\u2502${e.reset}`:"")+`
|
|
14
|
+
`;return s&&(m+=`${e.dim}\u2514${i.map(c=>"\u2500".repeat(c+r)).join("\u2534")}\u2518${e.reset}`),m}function Y(t,n,o=50){const r=n.split(`
|
|
15
15
|
`),s=Math.max(t.length,...r.map(a=>a.length),o);let i=`${e.dim}\u256D${"\u2500".repeat(s+2)}\u256E${e.reset}
|
|
16
16
|
`;i+=`${e.dim}\u2502${e.reset} ${$.bold(t.padEnd(s))} ${e.dim}\u2502${e.reset}
|
|
17
17
|
`,i+=`${e.dim}\u251C${"\u2500".repeat(s+2)}\u2524${e.reset}
|
|
18
18
|
`;for(const a of r)i+=`${e.dim}\u2502${e.reset} ${a.padEnd(s)} ${e.dim}\u2502${e.reset}
|
|
19
|
-
`;return i+=`${e.dim}\u2570${"\u2500".repeat(s+2)}\u256F${e.reset}`,i}function
|
|
20
|
-
${$.bold($.primary(`\u25B6 ${t}`))}`),console.log(`${e.dim}${"\u2500".repeat(t.length+4)}${e.reset}`)}function
|
|
21
|
-
`)}}}const n=new Set,o=new Set;return{onProgress:(r,s,i)=>{n.has(r)||(n.add(r),t.write(` ${r}: downloading (${
|
|
19
|
+
`;return i+=`${e.dim}\u2570${"\u2500".repeat(s+2)}\u256F${e.reset}`,i}function j(t){if(t===0)return"0 B";const n=1024,o=["B","KB","MB","GB","TB"],r=Math.floor(Math.log(t)/Math.log(n));return`${(t/Math.pow(n,r)).toFixed(2)} ${o[r]}`}function D(t){return t<1e3?`${t}ms`:t<6e4?`${(t/1e3).toFixed(1)}s`:t<36e5?`${Math.floor(t/6e4)}m ${Math.floor(t%6e4/1e3)}s`:`${Math.floor(t/36e5)}h ${Math.floor(t%36e5/6e4)}m`}function F(t){console.log(`
|
|
20
|
+
${$.bold($.primary(`\u25B6 ${t}`))}`),console.log(`${e.dim}${"\u2500".repeat(t.length+4)}${e.reset}`)}function W(t){console.log(`${h.success} ${$.success(t)}`)}function K(t){console.error(`${h.error} ${$.error(t)}`)}function U(t){console.log(`${h.warning} ${$.warning(t)}`)}function q(t){console.log(`${h.info} ${t}`)}function B(t,n){const o=Array.from({length:t.length+1},(r,s)=>[s,...Array(n.length).fill(0)]);for(let r=1;r<=n.length;r++)o[0][r]=r;for(let r=1;r<=t.length;r++)for(let s=1;s<=n.length;s++){const i=t[r-1]===n[s-1]?0:1;o[r][s]=Math.min(o[r-1][s]+1,o[r][s-1]+1,o[r-1][s-1]+i),r>1&&s>1&&t[r-1]===n[s-2]&&t[r-2]===n[s-1]&&(o[r][s]=Math.min(o[r][s],o[r-2][s-2]+1))}return o[t.length][n.length]}function J(t,n,o=3){const r=t.toLowerCase(),s=r.length<=4?1:2;return[...new Set(n)].map(i=>({cand:i,dist:B(r,i.toLowerCase())})).filter(({cand:i,dist:a})=>a<=s||r.length>=3&&i.toLowerCase().startsWith(r)).sort((i,a)=>i.dist-a.dist||i.cand.localeCompare(a.cand)).slice(0,o).map(({cand:i})=>i)}function V(t,n=process.platform){const o=n==="win32"?/^(.*?)[\\/]node_modules[\\/]wyrm-mcp[\\/]/i.exec(t):/^(.*)\/lib\/node_modules\/wyrm-mcp\//.exec(t);return!o||!o[1]||/[\\/](_npx|node_modules)([\\/]|$)/.test(o[1])?null:o[1]}function z(t,n,o=b){if(t!=="win32")return{command:"npm",prefixArgs:[]};const r=g.join(g.dirname(n),"node_modules","npm","bin","npm-cli.js");return o(r)?{command:n,prefixArgs:[r]}:{command:"npm",prefixArgs:[]}}function H(t){if(t.isTTY){let r=-1;return{onProgress:(s,i,a)=>{const p=Math.floor(i/a*100);p!==r&&(r=p,t.write(`\r ${s}: ${p}% `))},finish:()=>{t.write(`
|
|
21
|
+
`)}}}const n=new Set,o=new Set;return{onProgress:(r,s,i)=>{n.has(r)||(n.add(r),t.write(` ${r}: downloading (${j(i)})
|
|
22
22
|
`)),s>=i&&!o.has(r)&&(o.add(r),t.write(` ${r}: done
|
|
23
|
-
`))},finish:()=>{}}}class
|
|
24
|
-
`),a(i);return}if(
|
|
25
|
-
`),a(i);return}if(
|
|
23
|
+
`))},finish:()=>{}}}class k extends Error{constructor(){super("readSecret canceled"),this.name="ReadSecretCanceled"}}async function Q(t,n={}){const o=n.input??process.stdin,r=n.output??process.stdout,s=typeof o.setRawMode=="function";r.write(t),s&&o.setRawMode(!0),o.resume();let i="";return new Promise((a,p)=>{const m=u=>{for(const d of u){if(d===13||d===10){l(),r.write(`
|
|
24
|
+
`),a(i);return}if(d===3){l(),p(new k);return}if(d===4){l(),r.write(`
|
|
25
|
+
`),a(i);return}if(d===127||d===8){i.length>0&&(i=i.slice(0,-1),r.write("\b \b"));continue}i+=String.fromCharCode(d),r.write("*")}},c=()=>{l(),a(i)};function l(){s&&o.setRawMode(!1),o.pause(),o.removeListener("data",m),o.removeListener("end",c)}o.on("data",m),o.on("end",c)})}export{N as BANNER,T as MINI_BANNER,G as ProgressBar,k as ReadSecretCanceled,_ as Spinner,$ as c,e as colors,H as createDownloadProgress,B as editDistance,Y as formatBox,j as formatBytes,D as formatDuration,O as formatTable,h as icons,V as npmGlobalPrefixFromPath,z as npmInvocation,K as printError,q as printInfo,F as printSection,W as printSuccess,U as printWarning,L as priorityIcons,Q as readSecret,f as shouldUseColor,J as suggestClosest};
|
package/dist/erasure-cli.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{c,formatTable as l,printSection as d,printSuccess as
|
|
2
|
-
Dry run: nothing was deleted. --yes erases these now, without waiting for the grace period.`)),console.log(c.dim(" Change the limit with WYRM_INGEST_RETAIN_DAYS=<days> (0 turns retention off).")))}function _(a,o){const
|
|
3
|
-
Dry run: nothing changed. ${s>0?"Re-run with --apply (add --vacuum to rewrite the file).":"Nothing to redact."}`));return}
|
|
1
|
+
import{c,formatTable as l,printSection as d,printSuccess as p,printWarning as g}from"./cli.js";import{enforceIngestRetention as m,ingestedCategories as h,RETENTION_GRACE_DAYS as y}from"./retention.js";import{redactBackfill as f}from"./redact-backfill.js";import{scanBackupHygiene as w,listBackups as b,defaultBackupDir as k}from"./backup.js";import{fmtBytes as v}from"./backup-cli.js";function R(a,o=" "){for(const[r,t]of Object.entries(a).sort((e,s)=>s[1]-e[1]))console.log(`${o}${String(t).padStart(6)} ${r}`)}function A(a,o){const r=typeof o.days=="string"?Number(o.days):void 0,t=o.yes===!0&&o["dry-run"]!==!0,e=m(a,{days:r,dryRun:!t,skipGrace:t,actor:"cli"});if(e.status==="disabled"){console.log("Ingest retention is off (WYRM_INGEST_RETAIN_DAYS=0), so nothing ages out.");return}d(t?`Ingest retention: erased (limit ${e.days}d)`:`Ingest retention (dry run, limit ${e.days}d)`),console.log(l(["Category",t?"Erased":"Rows past the limit"],e.categories.map(n=>[n.category,String(t?n.deleted:n.matched)])));const s={};for(const n of e.categories)for(const[i,u]of Object.entries(n.counts))s[i]=(s[i]??0)+u;if(Object.keys(s).length>0&&(console.log(t?" Removed, per table:":" Would remove, per table:"),R(s," ")),e.graceEndsAt){const n=Date.parse(e.graceEndsAt)<=Date.now();console.log(` Automatic enforcement by maintenance ${n?"has been active since":"starts"} ${e.graceEndsAt.slice(0,10)}.`)}else console.log(` Automatic enforcement has not started: the first maintenance run records a preview and waits ${y} days.`);t?(p(`Erased ${e.deleted} ingested row(s) and ${e.derived} derived row(s); the erasure is recorded in erasure_log.`),console.log(" Snapshots still hold the erased rows until they rotate out: wyrm backup prune, wyrm backup legacy.")):(console.log(c.dim(`
|
|
2
|
+
Dry run: nothing was deleted. --yes erases these now, without waiting for the grace period.`)),console.log(c.dim(" Change the limit with WYRM_INGEST_RETAIN_DAYS=<days> (0 turns retention off).")))}function _(a,o){const r=o.apply===!0&&o["dry-run"]!==!0,t=a.getDatabase(),e=f(t,{apply:r,ingestedCategories:h(a)});d(r?"Redaction back-fill":"Redaction back-fill (dry run)"),console.log(l(["Table","Scanned","Rows with credentials","Fields"],e.tables.map(n=>[n.table,String(n.scanned),String(n.rows),String(n.fields)])));const s=e.tables.reduce((n,i)=>n+i.rows,0);if(!r){console.log(c.dim(`
|
|
3
|
+
Dry run: nothing changed. ${s>0?"Re-run with --apply (add --vacuum to rewrite the file).":"Nothing to redact."}`));return}p(`Redacted ${s} row(s). Matched text is never printed.`),e.vectorsDropped>0&&console.log(` Dropped ${e.vectorsDropped} vector(s) built from the old text: wyrm index rebuild re-embeds them.`),o.vacuum===!0?(a.vacuum(),a.checkpoint(),console.log(" Vacuumed: the file is rewritten, so pages that held the old text are gone.")):s>0&&console.log(c.dim(" Freed pages may still hold the old text: wyrm maintenance redact --apply --vacuum rewrites the file.")),s>0&&g("Existing snapshots still hold the unredacted rows. Take a fresh one (wyrm backup create --encrypt), then remove the old ones (wyrm backup prune, wyrm backup legacy).")}function B(a){const o=w({dbPath:a}),r=[];if(process.platform==="win32"&&r.push({ok:!0,label:"Backup permissions",detail:"not checked on Windows (access there is governed by ACLs, not mode bits)"}),o.exposed.length>0&&r.push({ok:!1,label:"Backup permissions",detail:`${o.exposed.length} backup file(s) or folder(s) in the Wyrm home are readable by other users`,fix:"wyrm backup legacy --dry-run, then wyrm backup legacy --apply (files 0600, folders 0700)"}),o.legacy.length>0){const t=o.legacy.reduce((e,s)=>e+s.bytes,0);r.push({ok:!1,label:"Stray backups",detail:`${o.legacy.length} database copy(ies) outside rotation (${v(t)}); nothing ages them out, and they keep rows an erasure removed`,fix:"wyrm backup legacy --dry-run, then --apply --adopt to move them into rotation"})}if(r.length===0){const t=b(k(a));r.push(t.length>0?{ok:!0,label:"Backups",detail:`${t.length} rotated snapshot(s), newest ${Math.floor((Date.now()-t[0].at.getTime())/864e5)}d old, no stray or exposed copies`}:{ok:!0,label:"Backups",detail:"no snapshots yet (optional): wyrm backup create --encrypt"})}return r}export{A as cmdForgetRetention,_ as cmdMaintenanceRedact,B as doctorBackupRows,R as printErasureCounts};
|