wyrm-mcp 9.1.1 → 9.1.2

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.
@@ -1,21 +1,21 @@
1
- import{existsSync as d,readFileSync as g,writeFileSync as p,mkdirSync as P,copyFileSync as X,chmodSync as C}from"fs";import{homedir as h,platform as b}from"os";import{join as a,dirname as w,resolve as ee}from"path";import{fileURLToPath as _}from"url";import{spawnSync as v}from"child_process";function te(){const e=h(),t=b(),o=t==="darwin"?a(e,"Library","Application Support","Code","User"):t==="win32"?a(e,"AppData","Roaming","Code","User"):a(e,".config","Code","User"),n=t==="darwin"?a(e,"Library","Application Support","Code - Insiders","User"):t==="win32"?a(e,"AppData","Roaming","Code - Insiders","User"):a(e,".config","Code - Insiders","User"),s=t==="darwin"?a(e,"Library","Application Support","Claude"):t==="win32"?a(e,"AppData","Roaming","Claude"):a(e,".config","claude"),r=t==="darwin"?a(e,".cursor"):t==="win32"?a(e,".cursor"):a(e,".cursor"),i=t==="darwin"?a(e,".codeium","windsurf"):t==="win32"?a(e,".codeium","windsurf"):a(e,".codeium","windsurf"),u=t==="darwin"?a(e,".config","zed"):t==="win32"?a(e,"AppData","Roaming","Zed"):a(e,".config","zed"),c=a(e,".continue");return[{id:"vscode-copilot",name:"VS Code (Copilot)",icon:"\u{1F4BB}",configPath:a(o,"settings.json"),configFormat:"vscode",mcpKey:"mcp.servers",detected:!1,configured:!1},{id:"vscode-insiders",name:"VS Code Insiders",icon:"\u{1F7E2}",configPath:a(n,"settings.json"),configFormat:"vscode",mcpKey:"mcp.servers",detected:!1,configured:!1},{id:"claude-desktop",name:"Claude Desktop",icon:"\u{1F916}",configPath:a(s,"claude_desktop_config.json"),configFormat:"mcp-json",mcpKey:"mcpServers",detected:!1,configured:!1},{id:"claude-code",name:"Claude Code",icon:"\u2733\uFE0F",configPath:a(e,".claude.json"),configFormat:"mcp-json",mcpKey:"mcpServers",detected:!1,configured:!1},{id:"cursor",name:"Cursor",icon:"\u{1F4D0}",configPath:a(r,"mcp.json"),configFormat:"mcp-json",mcpKey:"mcpServers",detected:!1,configured:!1},{id:"windsurf",name:"Windsurf",icon:"\u{1F3C4}",configPath:a(i,"mcp_config.json"),configFormat:"mcp-json",mcpKey:"mcpServers",detected:!1,configured:!1},{id:"zed",name:"Zed",icon:"\u26A1",configPath:a(u,"settings.json"),configFormat:"zed",mcpKey:"context_servers",detected:!1,configured:!1},{id:"continue",name:"Continue",icon:"\u{1F504}",configPath:a(c,"config.json"),configFormat:"mcp-json",mcpKey:"mcpServers",detected:!1,configured:!1}]}function oe(e){try{const t=b()==="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 A(){const e=te();for(const t of e){if(t.id==="claude-code")t.detected=d(t.configPath)||oe("claude");else{const o=w(t.configPath);t.detected=d(o)}if(t.detected&&d(t.configPath))try{const o=g(t.configPath,"utf-8"),n=E(o);t.configured=re(n,t)}catch{t.configured=!1}t.detected&&(t.version=ne(t))}return e}function ne(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
- `)[0];break}case"cursor":{const t=v("cursor",["--version"],{encoding:"utf-8",timeout:5e3});if(t.stdout)return t.stdout.split(`
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 re(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(){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=a(s,"wyrm-mcp","dist","index.js");if(d(r))return r}}catch{}const e=ee(__dirname,"index.js");if(d(e))return e;const t=a(h(),".wyrm","node_modules","wyrm-mcp","dist","index.js");if(d(t))return t;const o=[a(h(),"Git Projects","Wyrm","packages","mcp-server","dist","index.js"),a(h(),"projects","Wyrm","packages","mcp-server","dist","index.js"),a(h(),"dev","Wyrm","packages","mcp-server","dist","index.js")];for(const n of o)if(d(n))return n;return"wyrm-mcp"}function D(){return a(h(),".wyrm","wyrm.db")}function Ae(e="mcp-json",t){const o=t?.serverPath??M(),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 se(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 H(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 c=g(e.configPath,"utf-8");n=E(c),s=!0}catch{n={}}let r;if(s){r=`${e.configPath}.wyrm-backup`,p(r,g(e.configPath),{mode:384});try{C(r,384)}catch{}}const i=se(e,t);switch(e.configFormat){case"vscode":{n.mcp||(n.mcp={});const c=n.mcp;c.servers||(c.servers={});const f=c.servers;f.wyrm=i;break}case"mcp-json":{n[e.mcpKey]||(n[e.mcpKey]={});const c=n[e.mcpKey];c.wyrm=i;break}case"zed":{n[e.mcpKey]||(n[e.mcpKey]={});const c=n[e.mcpKey];c.wyrm=i;break}}p(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 je(e,t){try{if(!d(e.configPath))return{client:e,action:"skipped",message:"No config file"};const o=g(e.configPath,"utf-8");let n;try{n=E(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`;p(i,o,{encoding:"utf-8",mode:384});try{C(i,384)}catch{}return r.env={...r.env,...t},p(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 ie(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=g(e.configPath,"utf-8"),o=E(t),n=`${e.configPath}.wyrm-backup`;p(n,t,{mode:384});try{C(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 p(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 O(){return{id:"claude-code-hooks",name:"Claude Code (auto-memory)",icon:"\u{1FA9D}",configPath:a(h(),".claude","settings.json"),configFormat:"mcp-json",mcpKey:"",detected:!0,configured:!1}}const J=[{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"]}];function ce(){const e=a(h(),".claude");if(!d(e))return null;const t=O();try{const o=a(e,"hooks");d(o)||P(o,{recursive:!0});const n=w(_(import.meta.url)),s=a(n,"..","scripts","hooks"),r=t.configPath;let i={};if(d(r))try{i=JSON.parse(g(r,"utf-8"))}catch{i={}}const u=i.hooks??{};let c=!1;const f=[];for(const l of J){const y=a(s,l.script);if(!d(y)){f.push(l.script);continue}const T=a(o,l.script);X(y,T);try{C(T,493)}catch{}const W=`node ${T}`;for(const S of l.events){const N=typeof S=="string"?S:S.event,L=typeof S=="string"?void 0:S.matcher,x=Array.isArray(u[N])?u[N]:[];if(!x.some(j=>Array.isArray(j.hooks)&&j.hooks.some(Z=>Z.command===W))){const j=L?{matcher:L,hooks:[{type:"command",command:W,timeout:30}]}:{hooks:[{type:"command",command:W,timeout:30}]};x.push(j),c=!0}u[N]=x}}i.hooks=u,p(r,JSON.stringify(i,null,2)+`
8
- `,"utf-8"),t.configured=!0;const m=f.length?` (missing from package: ${f.join(", ")})`:"";return{client:t,action:c?"configured":"skipped",message:(c?"auto-memory hooks installed":"already installed")+m}}catch(o){return{client:t,action:"failed",message:o instanceof Error?o.message:String(o)}}}function Ce(){const e=a(h(),".claude");if(!d(e))return null;const t=O();try{const o=w(_(import.meta.url)),n=`node ${a(o,"wyrm-statusline.js")}`,s=t.configPath;let r={};if(d(s))try{r=JSON.parse(g(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},p(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 _e(){const e=a(h(),".claude","settings.json");if(!d(e))return null;const t=O();try{const o=JSON.parse(g(e,"utf-8")),n=o.statusLine;return n?.command&&/wyrm-statusline/.test(n.command)?(delete o.statusLine,p(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 ae(){const e=a(h(),".claude","settings.json");if(!d(e))return null;const t=O();try{const o=JSON.parse(g(e,"utf-8")),n=a(h(),".claude","hooks"),s=new Set(J.map(u=>`node ${a(n,u.script)}`)),r=new Set(J.flatMap(u=>u.events.map(c=>typeof c=="string"?c:c.event))),i=o.hooks;if(i){for(const u of r)Array.isArray(i[u])&&(i[u]=i[u].map(c=>({...c,hooks:Array.isArray(c.hooks)?c.hooks.filter(f=>!s.has(f.command??"")):c.hooks})).filter(c=>!Array.isArray(c.hooks)||c.hooks.length>0),i[u].length===0&&delete i[u]);Object.keys(i).length===0&&delete o.hooks}return p(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 I=/wyrm-guard/,G=5,ue=["Bash","Edit|Write|MultiEdit|NotebookEdit"];function k(e){return e.settingsPath??process.env.CLAUDE_SETTINGS??a(h(),".claude","settings.json")}function R(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 $(e){return b()==="win32"?`"${e.replace(/"/g,'""')}"`:`'${e.replace(/'/g,"'\\''")}'`}function de(){const e=w(_(import.meta.url)),t=a(e,"wyrm-guard.js");if(d(t))return`node ${$(t)}`;try{const o=b()==="win32"?"where":"which",s=v(o,["wyrm-guard"],{encoding:"utf-8",timeout:5e3}).stdout?.trim().split(`
12
- `)[0]?.trim();if(s)return $(s)}catch{}return null}function U(e,t){const o=e.hooks;if(!o||typeof o!="object"||Array.isArray(o))return!1;let n=!1;for(const s of Object.keys(o)){const r=o[s];if(!Array.isArray(r))continue;let i=!1;const u=[];for(const c of r){const f=c;if(!f||typeof f!="object"||!Array.isArray(f.hooks)){u.push(c);continue}const m=f.hooks.filter(l=>!t.test(l?.command??""));if(m.length===f.hooks.length){u.push(c);continue}n=!0,i=!0,m.length>0&&u.push({...f,hooks:m})}i&&(u.length===0?delete o[s]:o[s]=u)}return n&&Object.keys(o).length===0&&delete e.hooks,n}function z(e){return U(e,I)}function fe(e={}){if(!!!(e.settingsPath??process.env.CLAUDE_SETTINGS)&&!d(a(h(),".claude")))return{client:R(k(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=k(e),n=R(o);try{const s=e.guardCommand??de();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 y=JSON.parse(g(o,"utf-8"));if(typeof y!="object"||y===null||Array.isArray(y))throw new Error("not an object");r=y}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 c=JSON.stringify(r);z(r);const f=r.hooks??{},m=Array.isArray(f.PreToolUse)?f.PreToolUse:[];for(const y of ue)m.push({matcher:y,hooks:[{type:"command",command:s,timeout:G}]});f.PreToolUse=m,r.hooks=f;const l=JSON.stringify(r)!==c;return l&&p(o,JSON.stringify(r,null,2)+`
13
- `,"utf-8"),n.configured=!0,{client:n,action:l?"configured":"skipped",message:l?"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 me(e={}){const t=k(e);if(!d(t))return null;const o=R(t);try{let n;try{const r=JSON.parse(g(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 z(n)?(p(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 Oe(e={}){const t=k(e),o=[];try{const s=JSON.parse(g(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 c=u?.command;c&&I.test(c)&&o.push(c)}}catch{}return{settingsPath:t,installed:o.length>0,commands:o}}const K=/wyrm-memo/,le=[{event:"PreToolUse",matcher:"Read"},{event:"PreToolUse",matcher:"Bash"},{event:"PostToolUse",matcher:"Read"},{event:"PostToolUse",matcher:"Bash"}];function Y(e){return{id:"claude-code-memo",name:"Claude Code (token firewall)",icon:"",configPath:e,configFormat:"mcp-json",mcpKey:"",detected:!0,configured:!1}}function pe(){const e=w(_(import.meta.url)),t=a(e,"wyrm-memo.js");if(d(t))return`node ${$(t)}`;try{const o=b()==="win32"?"where":"which",s=v(o,["wyrm-memo"],{encoding:"utf-8",timeout:5e3}).stdout?.trim().split(`
15
- `)[0]?.trim();if(s)return $(s)}catch{}return null}function he(e={}){const t=k(e),o=!!(e.settingsPath??process.env.CLAUDE_SETTINGS),n=Y(t);if(!o&&!d(a(h(),".claude")))return{client:n,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??pe();if(!s)return{client:n,action:"skipped",message:"wyrm-memo bin not found (npm i -g wyrm-mcp, or build dist/). Token-firewall hooks not installed"};P(w(t),{recursive:!0});let r={};if(d(t))try{const m=JSON.parse(g(t,"utf-8"));if(typeof m!="object"||m===null||Array.isArray(m))throw new Error("not an object");r=m}catch{return{client:n,action:"failed",message:`${t} 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:`${t} "hooks" has an unexpected shape (not a plain object) \u2014 refusing to modify`};for(const m of["PreToolUse","PostToolUse"]){const l=i?.[m];if(l!==void 0&&!Array.isArray(l))return{client:n,action:"failed",message:`${t} hooks.${m} has an unexpected shape (not an array) \u2014 refusing to modify`}}const u=JSON.stringify(r);U(r,K);const c=r.hooks??{};for(const{event:m,matcher:l}of le){const y=Array.isArray(c[m])?c[m]:[];y.push({matcher:l,hooks:[{type:"command",command:s,timeout:G}]}),c[m]=y}r.hooks=c;const f=JSON.stringify(r)!==u;return f&&p(t,JSON.stringify(r,null,2)+`
16
- `,"utf-8"),n.configured=!0,{client:n,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:n,action:"failed",message:s instanceof Error?s.message:String(s)}}}function $e(e={}){const t=k(e);if(!d(t))return null;const o=Y(t);try{let n;try{const r=JSON.parse(g(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 U(n,K)?(p(t,JSON.stringify(n,null,2)+`
17
- `,"utf-8"),{client:o,action:"configured",message:"token-firewall hooks removed"}):{client:o,action:"skipped",message:"no wyrm-memo hooks installed"}}catch(n){return{client:o,action:"failed",message:n instanceof Error?n.message:String(n)}}}function Ee(e={}){const t=k(e),o=[];try{const s=JSON.parse(g(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 c=u?.command;c&&K.test(c)&&o.push(c)}}catch{}return{settingsPath:t,installed:o.length>0,commands:o}}function ge(e){const t={serverPath:e?.serverPath||M(),dbPath:e?.dbPath||D(),httpPort:e?.httpPort},o=a(h(),".wyrm");d(o)||P(o,{recursive:!0}),q(t);const n=A(),s=[];for(const c of n)s.push(H(c,t));const r=ce();r&&s.push(r);const i=fe();i&&s.push(i);const u=he();return u&&s.push(u),s}function Te(){const e=A(),t=[];for(const s of e)t.push(ie(s));const o=ae();o&&t.push(o);const n=me();return n&&t.push(n),t}function ye(e,t){const o={serverPath:t?.serverPath||M(),dbPath:t?.dbPath||D(),httpPort:t?.httpPort};q(o);const n=A(),s=[];for(const r of n)e.includes(r.id)&&s.push(H(r,o));return s}function q(e){const t=a(h(),".wyrm","wyrm-config.json"),o=A().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};p(t,JSON.stringify(n,null,2)+`
18
- `,"utf-8")}function V(){const e=a(h(),".wyrm","wyrm-config.json");if(!d(e))return null;try{return JSON.parse(g(e,"utf-8"))}catch{return null}}function We(){const e=V();return e?ye(e.configuredClients,{serverPath:e.serverPath,dbPath:e.dbPath,httpPort:e.httpPort}):ge()}const Q="<!-- wyrm:start -->",F="<!-- wyrm:end -->",B=`${Q}
1
+ import{existsSync as f,readFileSync as y,writeFileSync as p,mkdirSync as P,copyFileSync as X,chmodSync as C}from"fs";import{homedir as h,platform as b}from"os";import{join as a,dirname as v,resolve as ee}from"path";import{fileURLToPath as _}from"url";import{spawnSync as w}from"child_process";function te(){const e=h(),t=b(),o=t==="darwin"?a(e,"Library","Application Support","Code","User"):t==="win32"?a(e,"AppData","Roaming","Code","User"):a(e,".config","Code","User"),n=t==="darwin"?a(e,"Library","Application Support","Code - Insiders","User"):t==="win32"?a(e,"AppData","Roaming","Code - Insiders","User"):a(e,".config","Code - Insiders","User"),s=t==="darwin"?a(e,"Library","Application Support","Claude"):t==="win32"?a(e,"AppData","Roaming","Claude"):a(e,".config","claude"),r=t==="darwin"?a(e,".cursor"):t==="win32"?a(e,".cursor"):a(e,".cursor"),i=t==="darwin"?a(e,".codeium","windsurf"):t==="win32"?a(e,".codeium","windsurf"):a(e,".codeium","windsurf"),c=t==="darwin"?a(e,".config","zed"):t==="win32"?a(e,"AppData","Roaming","Zed"):a(e,".config","zed"),u=a(e,".continue");return[{id:"vscode-copilot",name:"VS Code (Copilot)",icon:"\u{1F4BB}",configPath:a(o,"settings.json"),configFormat:"vscode",mcpKey:"mcp.servers",detected:!1,configured:!1},{id:"vscode-insiders",name:"VS Code Insiders",icon:"\u{1F7E2}",configPath:a(n,"settings.json"),configFormat:"vscode",mcpKey:"mcp.servers",detected:!1,configured:!1},{id:"claude-desktop",name:"Claude Desktop",icon:"\u{1F916}",configPath:a(s,"claude_desktop_config.json"),configFormat:"mcp-json",mcpKey:"mcpServers",detected:!1,configured:!1},{id:"claude-code",name:"Claude Code",icon:"\u2733\uFE0F",configPath:a(e,".claude.json"),configFormat:"mcp-json",mcpKey:"mcpServers",detected:!1,configured:!1},{id:"cursor",name:"Cursor",icon:"\u{1F4D0}",configPath:a(r,"mcp.json"),configFormat:"mcp-json",mcpKey:"mcpServers",detected:!1,configured:!1},{id:"windsurf",name:"Windsurf",icon:"\u{1F3C4}",configPath:a(i,"mcp_config.json"),configFormat:"mcp-json",mcpKey:"mcpServers",detected:!1,configured:!1},{id:"zed",name:"Zed",icon:"\u26A1",configPath:a(c,"settings.json"),configFormat:"zed",mcpKey:"context_servers",detected:!1,configured:!1},{id:"continue",name:"Continue",icon:"\u{1F504}",configPath:a(u,"config.json"),configFormat:"mcp-json",mcpKey:"mcpServers",detected:!1,configured:!1}]}function oe(e){try{const t=b()==="win32"?"where":"which",o=w(t,[e],{encoding:"utf-8",timeout:5e3});return o.status===0&&!!o.stdout&&o.stdout.trim().length>0}catch{return!1}}function A(){const e=te();for(const t of e){if(t.id==="claude-code")t.detected=f(t.configPath)||oe("claude");else{const o=v(t.configPath);t.detected=f(o)}if(t.detected&&f(t.configPath))try{const o=y(t.configPath,"utf-8"),n=$(o);t.configured=re(n,t)}catch{t.configured=!1}t.detected&&(t.version=ne(t))}return e}function ne(e){try{switch(e.id){case"vscode-copilot":case"vscode-insiders":{const t=e.id==="vscode-insiders"?"code-insiders":"code",o=w(t,["--version"],{encoding:"utf-8",timeout:5e3});if(o.stdout)return o.stdout.split(`
2
+ `)[0];break}case"cursor":{const t=w("cursor",["--version"],{encoding:"utf-8",timeout:5e3});if(t.stdout)return t.stdout.split(`
3
+ `)[0];break}case"claude-code":{const t=w("claude",["--version"],{encoding:"utf-8",timeout:5e3});if(t.stdout)return t.stdout.trim().split(`
4
+ `)[0];break}}}catch{}}function re(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(){try{const n=w("which",["wyrm-mcp"],{encoding:"utf-8",timeout:5e3});if(n.stdout?.trim())return n.stdout.trim()}catch{}try{const s=w("npm",["root","-g"],{encoding:"utf-8",timeout:5e3}).stdout?.trim();if(s){const r=a(s,"wyrm-mcp","dist","index.js");if(f(r))return r}}catch{}const e=ee(__dirname,"index.js");if(f(e))return e;const t=a(h(),".wyrm","node_modules","wyrm-mcp","dist","index.js");if(f(t))return t;const o=[a(h(),"Git Projects","Wyrm","packages","mcp-server","dist","index.js"),a(h(),"projects","Wyrm","packages","mcp-server","dist","index.js"),a(h(),"dev","Wyrm","packages","mcp-server","dist","index.js")];for(const n of o)if(f(n))return n;return"wyrm-mcp"}function D(){return a(h(),".wyrm","wyrm.db")}function je(e="mcp-json",t){const o=t?.serverPath??M(),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 se(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 ie(e,t){if(!e||typeof e!="object"||Array.isArray(e))return{entry:t,preservedLauncher:!1};const o=e,n=o.env&&typeof o.env=="object"&&!Array.isArray(o.env)?o.env:{},s=t.env&&typeof t.env=="object"&&!Array.isArray(t.env)?t.env:{};if(o.command===t.command&&JSON.stringify(o.args??null)===JSON.stringify(t.args??null))return{entry:{...t,env:{...s,...n}},preservedLauncher:!1};const i={...n};for(const[c,u]of Object.entries(s))c in i||(i[c]=u);return{entry:{...o,env:i},preservedLauncher:!0}}function H(e,t){if(!e.detected)return{client:e,action:"skipped",message:`${e.name} not detected`};try{const o=v(e.configPath);f(o)||P(o,{recursive:!0});let n={},s=!1;if(f(e.configPath))try{const d=y(e.configPath,"utf-8");n=$(d),s=!0}catch{n={}}let r;if(s){r=`${e.configPath}.wyrm-backup`,p(r,y(e.configPath),{mode:384});try{C(r,384)}catch{}}const i=se(e,t);let c=!1;const u=(d,l)=>{const g=ie(d.wyrm,l);return d.wyrm=g.entry,g.preservedLauncher};switch(e.configFormat){case"vscode":{n.mcp||(n.mcp={});const d=n.mcp;d.servers||(d.servers={});const l=d.servers;c=u(l,i)||c;break}case"mcp-json":{n[e.mcpKey]||(n[e.mcpKey]={});const d=n[e.mcpKey];c=u(d,i)||c;break}case"zed":{n[e.mcpKey]||(n[e.mcpKey]={});const d=n[e.mcpKey];c=u(d,i)||c;break}}p(e.configPath,JSON.stringify(n,null,2)+`
5
+ `,"utf-8");const m=e.configured?"updated":"configured";return e.configured=!0,{client:e,action:m,message:`${e.name} ${m==="configured"?"configured":"updated"} successfully`+(c?" (kept your custom launch command; only missing env was added)":""),backup:r}}catch(o){return{client:e,action:"failed",message:`Failed to configure ${e.name}: ${o}`}}}function Ce(e,t){try{if(!f(e.configPath))return{client:e,action:"skipped",message:"No config file"};const o=y(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`;p(i,o,{encoding:"utf-8",mode:384});try{C(i,384)}catch{}return r.env={...r.env,...t},p(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 ce(e){if(!e.detected||!e.configured)return{client:e,action:"skipped",message:`${e.name} not configured`};try{if(!f(e.configPath))return{client:e,action:"skipped",message:`${e.name} config not found`};const t=y(e.configPath,"utf-8"),o=$(t),n=`${e.configPath}.wyrm-backup`;p(n,t,{mode:384});try{C(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 p(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 O(){return{id:"claude-code-hooks",name:"Claude Code (auto-memory)",icon:"\u{1FA9D}",configPath:a(h(),".claude","settings.json"),configFormat:"mcp-json",mcpKey:"",detected:!0,configured:!1}}const J=[{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"]}];function ae(){const e=a(h(),".claude");if(!f(e))return null;const t=O();try{const o=a(e,"hooks");f(o)||P(o,{recursive:!0});const n=v(_(import.meta.url)),s=a(n,"..","scripts","hooks"),r=t.configPath;let i={};if(f(r))try{i=JSON.parse(y(r,"utf-8"))}catch{i={}}const c=i.hooks??{};let u=!1;const m=[];for(const l of J){const g=a(s,l.script);if(!f(g)){m.push(l.script);continue}const N=a(o,l.script);X(g,N);try{C(N,493)}catch{}const T=`node ${N}`;for(const S of l.events){const W=typeof S=="string"?S:S.event,B=typeof S=="string"?void 0:S.matcher,x=Array.isArray(c[W])?c[W]:[];if(!x.some(j=>Array.isArray(j.hooks)&&j.hooks.some(Z=>Z.command===T))){const j=B?{matcher:B,hooks:[{type:"command",command:T,timeout:30}]}:{hooks:[{type:"command",command:T,timeout:30}]};x.push(j),u=!0}c[W]=x}}i.hooks=c,p(r,JSON.stringify(i,null,2)+`
8
+ `,"utf-8"),t.configured=!0;const d=m.length?` (missing from package: ${m.join(", ")})`:"";return{client:t,action:u?"configured":"skipped",message:(u?"auto-memory hooks installed":"already installed")+d}}catch(o){return{client:t,action:"failed",message:o instanceof Error?o.message:String(o)}}}function _e(){const e=a(h(),".claude");if(!f(e))return null;const t=O();try{const o=v(_(import.meta.url)),n=`node ${a(o,"wyrm-statusline.js")}`,s=t.configPath;let r={};if(f(s))try{r=JSON.parse(y(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 c=i?.command===n;return r.statusLine={type:"command",command:n,padding:0},p(s,JSON.stringify(r,null,2)+`
9
+ `,"utf-8"),{client:t,action:c?"skipped":"configured",message:c?"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 Oe(){const e=a(h(),".claude","settings.json");if(!f(e))return null;const t=O();try{const o=JSON.parse(y(e,"utf-8")),n=o.statusLine;return n?.command&&/wyrm-statusline/.test(n.command)?(delete o.statusLine,p(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 ue(){const e=a(h(),".claude","settings.json");if(!f(e))return null;const t=O();try{const o=JSON.parse(y(e,"utf-8")),n=a(h(),".claude","hooks"),s=new Set(J.map(c=>`node ${a(n,c.script)}`)),r=new Set(J.flatMap(c=>c.events.map(u=>typeof u=="string"?u:u.event))),i=o.hooks;if(i){for(const c of r)Array.isArray(i[c])&&(i[c]=i[c].map(u=>({...u,hooks:Array.isArray(u.hooks)?u.hooks.filter(m=>!s.has(m.command??"")):u.hooks})).filter(u=>!Array.isArray(u.hooks)||u.hooks.length>0),i[c].length===0&&delete i[c]);Object.keys(i).length===0&&delete o.hooks}return p(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 I=/wyrm-guard/,G=5,de=["Bash","Edit|Write|MultiEdit|NotebookEdit"];function k(e){return e.settingsPath??process.env.CLAUDE_SETTINGS??a(h(),".claude","settings.json")}function R(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 E(e){return b()==="win32"?`"${e.replace(/"/g,'""')}"`:`'${e.replace(/'/g,"'\\''")}'`}function fe(){const e=v(_(import.meta.url)),t=a(e,"wyrm-guard.js");if(f(t))return`node ${E(t)}`;try{const o=b()==="win32"?"where":"which",s=w(o,["wyrm-guard"],{encoding:"utf-8",timeout:5e3}).stdout?.trim().split(`
12
+ `)[0]?.trim();if(s)return E(s)}catch{}return null}function U(e,t){const o=e.hooks;if(!o||typeof o!="object"||Array.isArray(o))return!1;let n=!1;for(const s of Object.keys(o)){const r=o[s];if(!Array.isArray(r))continue;let i=!1;const c=[];for(const u of r){const m=u;if(!m||typeof m!="object"||!Array.isArray(m.hooks)){c.push(u);continue}const d=m.hooks.filter(l=>!t.test(l?.command??""));if(d.length===m.hooks.length){c.push(u);continue}n=!0,i=!0,d.length>0&&c.push({...m,hooks:d})}i&&(c.length===0?delete o[s]:o[s]=c)}return n&&Object.keys(o).length===0&&delete e.hooks,n}function z(e){return U(e,I)}function me(e={}){if(!!!(e.settingsPath??process.env.CLAUDE_SETTINGS)&&!f(a(h(),".claude")))return{client:R(k(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=k(e),n=R(o);try{const s=e.guardCommand??fe();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(v(o),{recursive:!0});let r={};if(f(o))try{const g=JSON.parse(y(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 c=i?.PreToolUse;if(c!==void 0&&!Array.isArray(c))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 u=JSON.stringify(r);z(r);const m=r.hooks??{},d=Array.isArray(m.PreToolUse)?m.PreToolUse:[];for(const g of de)d.push({matcher:g,hooks:[{type:"command",command:s,timeout:G}]});m.PreToolUse=d,r.hooks=m;const l=JSON.stringify(r)!==u;return l&&p(o,JSON.stringify(r,null,2)+`
13
+ `,"utf-8"),n.configured=!0,{client:n,action:l?"configured":"skipped",message:l?"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 le(e={}){const t=k(e);if(!f(t))return null;const o=R(t);try{let n;try{const r=JSON.parse(y(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 z(n)?(p(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 Ee(e={}){const t=k(e),o=[];try{const s=JSON.parse(y(t,"utf-8")).hooks??{};for(const r of Object.values(s))if(Array.isArray(r))for(const i of r)for(const c of i?.hooks??[]){const u=c?.command;u&&I.test(u)&&o.push(u)}}catch{}return{settingsPath:t,installed:o.length>0,commands:o}}const L=/wyrm-memo/,pe=[{event:"PreToolUse",matcher:"Read"},{event:"PreToolUse",matcher:"Bash"},{event:"PostToolUse",matcher:"Read"},{event:"PostToolUse",matcher:"Bash"}];function Y(e){return{id:"claude-code-memo",name:"Claude Code (token firewall)",icon:"",configPath:e,configFormat:"mcp-json",mcpKey:"",detected:!0,configured:!1}}function he(){const e=v(_(import.meta.url)),t=a(e,"wyrm-memo.js");if(f(t))return`node ${E(t)}`;try{const o=b()==="win32"?"where":"which",s=w(o,["wyrm-memo"],{encoding:"utf-8",timeout:5e3}).stdout?.trim().split(`
15
+ `)[0]?.trim();if(s)return E(s)}catch{}return null}function ge(e={}){const t=k(e),o=!!(e.settingsPath??process.env.CLAUDE_SETTINGS),n=Y(t);if(!o&&!f(a(h(),".claude")))return{client:n,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??he();if(!s)return{client:n,action:"skipped",message:"wyrm-memo bin not found (npm i -g wyrm-mcp, or build dist/). Token-firewall hooks not installed"};P(v(t),{recursive:!0});let r={};if(f(t))try{const d=JSON.parse(y(t,"utf-8"));if(typeof d!="object"||d===null||Array.isArray(d))throw new Error("not an object");r=d}catch{return{client:n,action:"failed",message:`${t} 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:`${t} "hooks" has an unexpected shape (not a plain object) \u2014 refusing to modify`};for(const d of["PreToolUse","PostToolUse"]){const l=i?.[d];if(l!==void 0&&!Array.isArray(l))return{client:n,action:"failed",message:`${t} hooks.${d} has an unexpected shape (not an array) \u2014 refusing to modify`}}const c=JSON.stringify(r);U(r,L);const u=r.hooks??{};for(const{event:d,matcher:l}of pe){const g=Array.isArray(u[d])?u[d]:[];g.push({matcher:l,hooks:[{type:"command",command:s,timeout:G}]}),u[d]=g}r.hooks=u;const m=JSON.stringify(r)!==c;return m&&p(t,JSON.stringify(r,null,2)+`
16
+ `,"utf-8"),n.configured=!0,{client:n,action:m?"configured":"skipped",message:m?"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:n,action:"failed",message:s instanceof Error?s.message:String(s)}}}function $e(e={}){const t=k(e);if(!f(t))return null;const o=Y(t);try{let n;try{const r=JSON.parse(y(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 U(n,L)?(p(t,JSON.stringify(n,null,2)+`
17
+ `,"utf-8"),{client:o,action:"configured",message:"token-firewall hooks removed"}):{client:o,action:"skipped",message:"no wyrm-memo hooks installed"}}catch(n){return{client:o,action:"failed",message:n instanceof Error?n.message:String(n)}}}function Ne(e={}){const t=k(e),o=[];try{const s=JSON.parse(y(t,"utf-8")).hooks??{};for(const r of Object.values(s))if(Array.isArray(r))for(const i of r)for(const c of i?.hooks??[]){const u=c?.command;u&&L.test(u)&&o.push(u)}}catch{}return{settingsPath:t,installed:o.length>0,commands:o}}function ye(e){const t={serverPath:e?.serverPath||M(),dbPath:e?.dbPath||D(),httpPort:e?.httpPort},o=a(h(),".wyrm");f(o)||P(o,{recursive:!0}),q(t);const n=A(),s=[];for(const u of n)s.push(H(u,t));const r=ae();r&&s.push(r);const i=me();i&&s.push(i);const c=ge();return c&&s.push(c),s}function Te(){const e=A(),t=[];for(const s of e)t.push(ce(s));const o=ue();o&&t.push(o);const n=le();return n&&t.push(n),t}function ve(e,t){const o={serverPath:t?.serverPath||M(),dbPath:t?.dbPath||D(),httpPort:t?.httpPort};q(o);const n=A(),s=[];for(const r of n)e.includes(r.id)&&s.push(H(r,o));return s}function q(e){const t=a(h(),".wyrm","wyrm-config.json"),o=A().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};p(t,JSON.stringify(n,null,2)+`
18
+ `,"utf-8")}function V(){const e=a(h(),".wyrm","wyrm-config.json");if(!f(e))return null;try{return JSON.parse(y(e,"utf-8"))}catch{return null}}function We(){const e=V();return e?ve(e.configuredClients,{serverPath:e.serverPath,dbPath:e.dbPath,httpPort:e.httpPort}):ye()}const Q="<!-- wyrm:start -->",K="<!-- wyrm:end -->",F=`${Q}
19
19
  ## Wyrm Memory + Agent (auto-managed \u2014 do not edit this section)
20
20
 
21
21
  **Session start** \u2014 call \`wyrm_session_prime\` first to load project context (ground truths, open quests, recent sessions, scaffolds) before responding.
@@ -39,23 +39,23 @@ import{existsSync as d,readFileSync as g,writeFileSync as p,mkdirSync as P,copyF
39
39
  **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
40
 
41
41
  **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
- ${F}`;function Ne(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 c=g(r,"utf-8"),f=c.indexOf(Q),m=c.indexOf(F);if(f!==-1&&m!==-1&&m>f){const l=c.slice(0,f),y=c.slice(m+F.length);p(r,`${l}${B}${y}`,"utf-8")}else{const l=c.endsWith(`
42
+ ${K}`;function xe(e,t){const o={injected:[],skipped:[],errors:[]},n=(r,i)=>{try{const c=v(r);if(f(c)||P(c,{recursive:!0}),f(r)){const u=y(r,"utf-8"),m=u.indexOf(Q),d=u.indexOf(K);if(m!==-1&&d!==-1&&d>m){const l=u.slice(0,m),g=u.slice(d+K.length);p(r,`${l}${F}${g}`,"utf-8")}else{const l=u.endsWith(`
43
43
  `)?`
44
44
  `:`
45
45
 
46
- `;p(r,`${c}${l}${B}
47
- `,"utf-8")}}else p(r,`${B}
48
- `,"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=a(e,".github","copilot-instructions.md");n(u,".github/copilot-instructions.md")}else if(i==="cursor"){const u=a(e,".cursor","rules");n(u,".cursor/rules")}else o.skipped.push(r)}return o}function E(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
+ `;p(r,`${u}${l}${F}
47
+ `,"utf-8")}}else p(r,`${F}
48
+ `,"utf-8");o.injected.push(i)}catch(c){o.errors.push(`${i}: ${c}`)}},s=t.length===0?["copilot","cursor"]:t;for(const r of s){const i=r.toLowerCase();if(i==="copilot"||i==="vscode-copilot"){const c=a(e,".github","copilot-instructions.md");n(c,".github/copilot-instructions.md")}else if(i==="cursor"){const c=a(e,".cursor","rules");n(c,".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]!==`
49
49
  `;)n++;t+=`
50
50
  `;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]!==`
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 xe(){const e=A(),t=e.filter(r=>r.detected),o=e.filter(r=>r.configured);let n=`\u{F115D} Wyrm Auto-Configure Status
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 Me(){const e=A(),t=e.filter(r=>r.detected),o=e.filter(r=>r.configured);let n=`\u{F115D} Wyrm Auto-Configure Status
52
52
 
53
53
  `;n+=` Detected: ${t.length}/${e.length} AI clients
54
54
  `,n+=` Configured: ${o.length}/${t.length} clients
55
55
 
56
- `;for(const r of e){const i=r.detected?r.configured?" \u25CF":" \u25D0":" \u25CB",u=r.version?` (${r.version})`:"",c=r.detected?r.configured?"configured \u2713":"detected \u2014 not configured":"not found";n+=`${i} ${r.icon} ${r.name}${u}: ${c}
56
+ `;for(const r of e){const i=r.detected?r.configured?" \u25CF":" \u25D0":" \u25CB",c=r.version?` (${r.version})`:"",u=r.detected?r.configured?"configured \u2713":"detected \u2014 not configured":"not found";n+=`${i} ${r.icon} ${r.name}${c}: ${u}
57
57
  `}const s=V();return s&&(n+=`
58
58
  Server: ${s.serverPath}
59
59
  `,n+=` DB: ${s.dbPath}
60
60
  `,n+=` Last: ${new Date(s.lastSetup).toLocaleString()}
61
- `),n}export{G as GUARD_HOOK_TIMEOUT_SEC,le as MEMO_HOOK_EVENTS,B as WYRM_INJECT_BLOCK,je as applyWyrmEnvToClient,ge as autoConfigureAll,H as configureClient,ye as configureSpecific,A as detectClients,Ae as exportServerConfig,M as findWyrmServerPath,D as getDefaultDbPath,xe as getStatusSummary,Ne as injectSystemPrompt,ce as installClaudeCodeHooks,Ce as installClaudeStatusline,fe as installWyrmGuardHooks,he as installWyrmMemoHooks,V as loadWyrmMeta,E as parseJsonWithComments,$ as quoteHookPath,We as reconfAll,ae as removeClaudeCodeHooks,_e as removeClaudeStatusline,Te as removeFromAll,ie as removeFromClient,me as removeWyrmGuardHooks,$e as removeWyrmMemoHooks,de as resolveWyrmGuardCommand,pe as resolveWyrmMemoCommand,Oe as wyrmGuardHookStatus,Ee as wyrmMemoHookStatus};
61
+ `),n}export{G as GUARD_HOOK_TIMEOUT_SEC,pe as MEMO_HOOK_EVENTS,F as WYRM_INJECT_BLOCK,Ce as applyWyrmEnvToClient,ye as autoConfigureAll,H as configureClient,ve as configureSpecific,A as detectClients,je as exportServerConfig,M as findWyrmServerPath,D as getDefaultDbPath,Me as getStatusSummary,xe as injectSystemPrompt,ae as installClaudeCodeHooks,_e as installClaudeStatusline,me as installWyrmGuardHooks,ge as installWyrmMemoHooks,V as loadWyrmMeta,ie as mergeServerEntry,$ as parseJsonWithComments,E as quoteHookPath,We as reconfAll,ue as removeClaudeCodeHooks,Oe as removeClaudeStatusline,Te as removeFromAll,ce as removeFromClient,le as removeWyrmGuardHooks,$e as removeWyrmMemoHooks,fe as resolveWyrmGuardCommand,he as resolveWyrmMemoCommand,Ee as wyrmGuardHookStatus,Ne as wyrmMemoHookStatus};
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "contract": "wyrm-cloud-memory-v2",
3
- "version": "9.1.1",
3
+ "version": "9.1.2",
4
4
  "derivedFrom": "standard tier MINUS device-local/egress/subprocess (src/cloud-profile.ts)",
5
5
  "count": 20,
6
6
  "tools": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "contract": "wyrm-cloud-memory-v1",
3
- "version": "9.1.1",
3
+ "version": "9.1.2",
4
4
  "tools": [
5
5
  {
6
6
  "name": "wyrm_recall",
package/dist/wyrm-cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import{join as W,dirname as X,resolve as Z,basename as We}from"path";import{homedir as le}from"os";import{existsSync as U,readFileSync as H,readdirSync as Ue,rmSync as Fe}from"fs";import{fileURLToPath as ee}from"url";import{spawnSync as te}from"child_process";import{createInterface as oe}from"readline";import{WyrmDB as qe}from"./database.js";import{MemoryArtifacts as B,queuedForReview as Ye}from"./memory-artifacts.js";import{GroundTruths as de}from"./intelligence.js";import{Causality as be}from"./causality.js";import{wireTruthAutoCascade as ve}from"./truth-cascade.js";import{Rehydration as Ve}from"./rehydration.js";import{makeRenderDeps as $e,buildRenderPlan as ue,renderToDisk as He,renderForClient as ke,detectClients as Be,ALL_RENDER_CLIENTS as pe,ALL_CLIENT_MARKERS as Ke}from"./render-target.js";import{classifyCapture as Ge}from"./capture.js";import{importFrom as Je,IMPORT_SOURCES as _e}from"./importers.js";import{c as d,colors as F,formatTable as I,printSection as S,printSuccess as k,printError as f,printWarning as Ee,icons as K,readSecret as ze,ReadSecretCanceled as Qe}from"./cli.js";import{buildDeterminismReceipt as Xe,attestation as Ze}from"./receipt.js";import{loadConnectorConfigs as Se,upsertConnectorConfig as et,runConnector as tt,runAllConnectors as ot}from"./connectors/index.js";import{readActor as st}from"./attribution.js";import{captureHealth as rt,captureEffectiveness as nt,recordSnapshot as me,getSnapshots as Re,trendFlags as it}from"./metrics.js";function A(){try{const u=X(ee(import.meta.url));return JSON.parse(H(W(u,"..","package.json"),"utf-8"))}catch{return{}}}function O(u,c){const s=typeof u=="string"?parseInt(u,10):NaN;return Number.isFinite(s)?s:c}function se(u,c){const s=typeof u=="string"?parseFloat(u):NaN;return Number.isFinite(s)?s:c}function Ce(){return process.env.WYRM_DB_PATH??W(le(),".wyrm","wyrm.db")}function R(){return new qe(Ce())}function x(u){const c=[],s={};let a=0;for(;a<u.length;){const r=u[a];if(r.startsWith("--")){const e=r.slice(2),o=u[a+1];o&&!o.startsWith("--")?(s[e]=o,a+=2):(s[e]=!0,a++)}else c.push(r),a++}return{positional:c,flags:s}}function N(u,c){return c?u.getDatabase().prepare("SELECT * FROM projects WHERE LOWER(name) LIKE LOWER(?) LIMIT 1").get(`%${c}%`)??null:null}async function at(u){const{positional:c,flags:s}=x(u),a=c[0];a||(f("Usage: wyrm search <query> [--project <name>] [--type all|memories|truths|quests|data]"),process.exit(1));const r=console.error;console.error=()=>{};const e=R();try{const o=e.getDatabase(),t=s.type??"all",n=s.project,i=(n?N(e,n):null)?.id;S(`Search: "${a}"`);const l=[];let m=null;if(t==="all"||t==="memories"){const{resolveEmbeddingState:g}=await import("./providers/embedding-provider.js"),h=g(),y=new B(o);if(h.resolved!=="none"){const{createVectorStore:b}=await import("./vectors.js"),C=process.env.WYRM_VECTOR_PROVIDER??"auto",E=b({provider:C},o);y.setVectorStore(E)}let w;const v={limit:20,onStats:b=>{w=b}},$=i!=null?await y.recallHybrid(i,a,v):await y.recallHybridGlobal(a,v);for(const b of $)l.push([`mem:${b.artifact.id}`,"memory",b.artifact.kind,b.artifact.problem.slice(0,80)]);if(h.resolved==="none")m="keyword-only (no vector provider; run: wyrm vectors download)";else{const b=w!==void 0||$.some(L=>L.match_type==="vector"||L.match_type==="hybrid"),C=y.getVectorStore(),E=typeof C?.getProvider=="function"?C.getProvider():void 0;let j=null;if(!b)if(!C||E?.name==="none")j=h.reason||"vector_store_unavailable";else{let L;try{L=C.indexCoverage()}catch{}if(L?.stale)j="stale_index";else{let q=-1;try{q=C.getStats().total}catch{}j=q===0?"empty_index":q<0?"index_state_unknown":"no_vector_hit"}}const T=Xe({vectorsAvailable:b,rerankActive:!1,embedModel:b?E?.model??h.model:null,rerankModel:null,embedEgressHost:b?E?.remoteHost??null:null,rerankEgressHost:null,ftsOnlyReason:j});m=Ze(T)}}if(t==="all"||t==="sessions")try{const g=i?`AND s.project_id = ${i}`:"",h=o.prepare(`
2
+ import{join as W,dirname as X,resolve as Z,basename as We}from"path";import{homedir as le}from"os";import{existsSync as U,readFileSync as H,readdirSync as Ue,rmSync as Fe}from"fs";import{fileURLToPath as ee}from"url";import{spawnSync as te}from"child_process";import{createInterface as oe}from"readline";import{WyrmDB as qe}from"./database.js";import{MemoryArtifacts as B,queuedForReview as Ye}from"./memory-artifacts.js";import{GroundTruths as de}from"./intelligence.js";import{Causality as be}from"./causality.js";import{wireTruthAutoCascade as ve}from"./truth-cascade.js";import{Rehydration as Ve}from"./rehydration.js";import{makeRenderDeps as $e,buildRenderPlan as ue,renderToDisk as He,renderForClient as ke,detectClients as Be,ALL_RENDER_CLIENTS as pe,ALL_CLIENT_MARKERS as Ke}from"./render-target.js";import{classifyCapture as Ge}from"./capture.js";import{importFrom as Je,IMPORT_SOURCES as Ee}from"./importers.js";import{c as d,colors as F,formatTable as D,printSection as S,printSuccess as k,printError as f,printWarning as _e,icons as K,readSecret as ze,ReadSecretCanceled as Qe}from"./cli.js";import{buildDeterminismReceipt as Xe,attestation as Ze}from"./receipt.js";import{loadConnectorConfigs as Se,upsertConnectorConfig as et,runConnector as tt,runAllConnectors as ot}from"./connectors/index.js";import{readActor as st}from"./attribution.js";import{captureHealth as rt,captureEffectiveness as nt,recordSnapshot as me,getSnapshots as Re,trendFlags as it}from"./metrics.js";function A(){try{const p=X(ee(import.meta.url));return JSON.parse(H(W(p,"..","package.json"),"utf-8"))}catch{return{}}}function O(p,c){const s=typeof p=="string"?parseInt(p,10):NaN;return Number.isFinite(s)?s:c}function se(p,c){const s=typeof p=="string"?parseFloat(p):NaN;return Number.isFinite(s)?s:c}function Ce(){return process.env.WYRM_DB_PATH??W(le(),".wyrm","wyrm.db")}function R(){return new qe(Ce())}function x(p){const c=[],s={};let a=0;for(;a<p.length;){const r=p[a];if(r.startsWith("--")){const e=r.slice(2),o=p[a+1];o&&!o.startsWith("--")?(s[e]=o,a+=2):(s[e]=!0,a++)}else c.push(r),a++}return{positional:c,flags:s}}function N(p,c){return c?p.getDatabase().prepare("SELECT * FROM projects WHERE LOWER(name) LIKE LOWER(?) LIMIT 1").get(`%${c}%`)??null:null}async function at(p){const{positional:c,flags:s}=x(p),a=c[0];a||(f("Usage: wyrm search <query> [--project <name>] [--type all|memories|truths|quests|data]"),process.exit(1));const r=console.error;console.error=()=>{};const e=R();try{const o=e.getDatabase(),t=s.type??"all",n=s.project,i=(n?N(e,n):null)?.id;S(`Search: "${a}"`);const l=[];let m=null;if(t==="all"||t==="memories"){const{resolveEmbeddingState:g}=await import("./providers/embedding-provider.js"),h=g(),y=new B(o);if(h.resolved!=="none"){const{createVectorStore:v}=await import("./vectors.js"),C=process.env.WYRM_VECTOR_PROVIDER??"auto",_=v({provider:C},o);y.setVectorStore(_)}let w;const b={limit:20,onStats:v=>{w=v}},$=i!=null?await y.recallHybrid(i,a,b):await y.recallHybridGlobal(a,b);for(const v of $)l.push([`mem:${v.artifact.id}`,"memory",v.artifact.kind,v.artifact.problem.slice(0,80)]);if(h.resolved==="none")m="keyword-only (no vector provider; run: wyrm vectors download)";else{const v=w!==void 0||$.some(L=>L.match_type==="vector"||L.match_type==="hybrid"),C=y.getVectorStore(),_=typeof C?.getProvider=="function"?C.getProvider():void 0;let j=null;if(!v)if(!C||_?.name==="none")j=h.reason||"vector_store_unavailable";else{let L;try{L=C.indexCoverage()}catch{}if(L?.stale)j="stale_index";else{let q=-1;try{q=C.getStats().total}catch{}j=q===0?"empty_index":q<0?"index_state_unknown":"no_vector_hit"}}const T=Xe({vectorsAvailable:v,rerankActive:!1,embedModel:v?_?.model??h.model:null,rerankModel:null,embedEgressHost:v?_?.remoteHost??null:null,rerankEgressHost:null,ftsOnlyReason:j});m=Ze(T)}}if(t==="all"||t==="sessions")try{const g=i?`AND s.project_id = ${i}`:"",h=o.prepare(`
3
3
  SELECT s.id, s.objectives, s.project_id FROM sessions s
4
4
  JOIN sessions_fts fts ON s.id = fts.rowid
5
5
  WHERE sessions_fts MATCH ? ${g}
@@ -14,20 +14,20 @@ import{join as W,dirname as X,resolve as Z,basename as We}from"path";import{home
14
14
  JOIN data_lake_fts fts ON d.id = fts.rowid
15
15
  WHERE data_lake_fts MATCH ? ${g}
16
16
  LIMIT 10
17
- `).all(a);for(const y of h)l.push([`data:${y.id}`,"data",y.category,y.value.slice(0,80)])}catch{}if(l.length===0){console.log(d.dim(` No results found for "${a}"`));try{const g=o.prepare("SELECT COUNT(*) AS n FROM memory_artifacts WHERE needs_review = 1").get().n;g>0&&(m=`${g} memor${g===1?"y is":"ies are"} waiting in the review queue and invisible to recall \u2014 run \`wyrm review\` (or \`wyrm review --approve-all\`)`)}catch{}}else console.log(I(["ID","Type","Subtype","Preview"],l)),console.log(d.dim(`
18
- ${l.length} result${l.length!==1?"s":""}`));m&&console.log(d.dim(` ${m}`))}finally{e.close(),console.error=r}}async function ct(u){const{flags:c}=x(u),s=c.type??"all",a=O(c.limit,20),r=c.project,e=R(),o=e.getDatabase(),n=(r?N(e,r):null)?.id,p=n?`WHERE project_id = ${n}`:"",i=n?`AND project_id = ${n}`:"";if(S("Wyrm Memory"),s==="all"||s==="memories"){S("Memories");const l=o.prepare(`
17
+ `).all(a);for(const y of h)l.push([`data:${y.id}`,"data",y.category,y.value.slice(0,80)])}catch{}if(l.length===0){console.log(d.dim(` No results found for "${a}"`));try{const g=o.prepare("SELECT COUNT(*) AS n FROM memory_artifacts WHERE needs_review = 1").get().n;g>0&&(m=`${g} memor${g===1?"y is":"ies are"} waiting in the review queue and invisible to recall \u2014 run \`wyrm review\` (or \`wyrm review --approve-all\`)`)}catch{}}else console.log(D(["ID","Type","Subtype","Preview"],l)),console.log(d.dim(`
18
+ ${l.length} result${l.length!==1?"s":""}`));m&&console.log(d.dim(` ${m}`))}finally{e.close(),console.error=r}}async function ct(p){const{flags:c}=x(p),s=c.type??"all",a=O(c.limit,20),r=c.project,e=R(),o=e.getDatabase(),n=(r?N(e,r):null)?.id,u=n?`WHERE project_id = ${n}`:"",i=n?`AND project_id = ${n}`:"";if(S("Wyrm Memory"),s==="all"||s==="memories"){S("Memories");const l=o.prepare(`
19
19
  SELECT id, kind, confidence, problem, tags FROM memory_artifacts
20
- ${p}
20
+ ${u}
21
21
  ORDER BY created_at DESC LIMIT ?
22
- `).all(a);if(l.length>0){const m=l.map(g=>[`mem:${g.id}`,g.kind,`${Math.round(g.confidence*100)}%`,g.problem.slice(0,60),g.tags?.slice(0,30)??""]);console.log(I(["ID","Kind","Conf","Preview","Tags"],m))}else console.log(d.dim(" No memories yet."))}if(s==="all"||s==="truths"){S("Ground Truths");const l=o.prepare(`
22
+ `).all(a);if(l.length>0){const m=l.map(g=>[`mem:${g.id}`,g.kind,`${Math.round(g.confidence*100)}%`,g.problem.slice(0,60),g.tags?.slice(0,30)??""]);console.log(D(["ID","Kind","Conf","Preview","Tags"],m))}else console.log(d.dim(" No memories yet."))}if(s==="all"||s==="truths"){S("Ground Truths");const l=o.prepare(`
23
23
  SELECT id, category, key, value FROM ground_truths
24
24
  WHERE is_current = 1 ${i}
25
25
  ORDER BY created_at DESC LIMIT ?
26
- `).all(a);if(l.length>0){const m=l.map(g=>[`truth:${g.id}`,g.category,g.key.slice(0,30),g.value.slice(0,60)]);console.log(I(["ID","Category","Key","Value"],m))}else console.log(d.dim(" No ground truths yet."))}if(s==="all"||s==="quests"){S("Quests");const l=o.prepare(`
26
+ `).all(a);if(l.length>0){const m=l.map(g=>[`truth:${g.id}`,g.category,g.key.slice(0,30),g.value.slice(0,60)]);console.log(D(["ID","Category","Key","Value"],m))}else console.log(d.dim(" No ground truths yet."))}if(s==="all"||s==="quests"){S("Quests");const l=o.prepare(`
27
27
  SELECT id, priority, title, status FROM quests
28
- ${p}
28
+ ${u}
29
29
  ORDER BY created_at DESC LIMIT ?
30
- `).all(a);if(l.length>0){const m=l.map(g=>[`quest:${g.id}`,g.priority,g.title.slice(0,60),g.status]);console.log(I(["ID","Priority","Title","Status"],m))}else console.log(d.dim(" No quests yet."))}e.close()}async function lt(u){const{positional:c}=x(u),s=c[0];s||(f("Usage: wyrm show <typed-id> (e.g. mem:41, quest:12, truth:7, data:5, session:3)"),process.exit(1));const[a,r]=s.split(":"),e=parseInt(r??"",10);(!a||isNaN(e))&&(f(`Invalid typed ID: ${s}. Format: type:number (e.g. mem:41)`),process.exit(1));const o=R(),t=o.getDatabase();switch(S(`${s}`),a){case"mem":{const n=t.prepare("SELECT * FROM memory_artifacts WHERE id = ?").get(e);if(!n){f(`Memory artifact ${e} not found`);break}console.log(d.bold("Kind: ")+n.kind),console.log(d.bold("Confidence:")+` ${Math.round(n.confidence*100)}%`),console.log(d.bold("Problem: ")+`
30
+ `).all(a);if(l.length>0){const m=l.map(g=>[`quest:${g.id}`,g.priority,g.title.slice(0,60),g.status]);console.log(D(["ID","Priority","Title","Status"],m))}else console.log(d.dim(" No quests yet."))}e.close()}async function lt(p){const{positional:c}=x(p),s=c[0];s||(f("Usage: wyrm show <typed-id> (e.g. mem:41, quest:12, truth:7, data:5, session:3)"),process.exit(1));const[a,r]=s.split(":"),e=parseInt(r??"",10);(!a||isNaN(e))&&(f(`Invalid typed ID: ${s}. Format: type:number (e.g. mem:41)`),process.exit(1));const o=R(),t=o.getDatabase();switch(S(`${s}`),a){case"mem":{const n=t.prepare("SELECT * FROM memory_artifacts WHERE id = ?").get(e);if(!n){f(`Memory artifact ${e} not found`);break}console.log(d.bold("Kind: ")+n.kind),console.log(d.bold("Confidence:")+` ${Math.round(n.confidence*100)}%`),console.log(d.bold("Problem: ")+`
31
31
  `+n.problem),n.validated_fix&&console.log(d.bold("Solution: ")+`
32
32
  `+n.validated_fix),n.why_it_worked&&console.log(d.bold("Why: ")+`
33
33
  `+n.why_it_worked),n.tags&&console.log(d.bold("Tags: ")+n.tags),console.log(d.bold("Created: ")+n.created_at);break}case"quest":{const n=t.prepare("SELECT * FROM quests WHERE id = ?").get(e);if(!n){f(`Quest ${e} not found`);break}console.log(d.bold("Title: ")+n.title),console.log(d.bold("Priority: ")+n.priority),console.log(d.bold("Status: ")+n.status),n.description&&console.log(d.bold("Desc: ")+`
@@ -37,58 +37,58 @@ import{join as W,dirname as X,resolve as Z,basename as We}from"path";import{home
37
37
  `+String(n.value).slice(0,500)),console.log(d.bold("Created: ")+n.created_at);break}case"session":{const n=t.prepare("SELECT * FROM sessions WHERE id = ?").get(e);if(!n){f(`Session ${e} not found`);break}console.log(d.bold("Date: ")+n.date),console.log(d.bold("Objectives: ")+`
38
38
  `+n.objectives),n.completed&&console.log(d.bold("Completed: ")+`
39
39
  `+n.completed),n.notes&&console.log(d.bold("Notes: ")+`
40
- `+n.notes);break}default:f(`Unknown type prefix: ${a}. Use mem|quest|truth|data|session`)}o.close()}async function dt(u){const{positional:c,flags:s}=x(u),a=c[0];a||(f('Usage: wyrm capture "<content>" [--project <name>] [--mode auto|quest|truth|memory]'),process.exit(1));const r=s.project,e=s.mode,o=R(),t=o.getDatabase();let n=null;if(r){const j=N(o,r);j||(f(`Project not found: ${r}`),console.log(d.dim(` create it: wyrm project add ${r} --path <dir>`)),o.close(),process.exit(1)),n=j.id}let p=Ge(a);e&&e!=="auto"&&(p={type:e,subtype:{quest:"quest",truth:"decision",memory:"pattern"}[e]??e,confidence:100,reasoning:`Mode override: ${e}`});const{type:i,subtype:l,confidence:m,reasoning:g}=p;(i==="quest"||i==="truth"||i==="memory")&&n===null&&(f('A --project is required to capture. Use: wyrm capture "<text>" --project <name>'),o.close(),process.exit(1));let h=0,y="",w=!1;const v=new B(t),$=new de(t);if(ve($,new be(t)),i==="quest")h=o.addQuest(n,a.slice(0,200),"","medium").id,y="quest";else if(i==="truth")if(e!=="truth"&&m<100){const j=v.add(n,{kind:"pattern",problem:a,confidence:m/100,needsReview:1});h=j.id,y="mem",w=j.needs_review===1}else h=$.set(n,{category:"decision",key:a.slice(0,60),value:a}).id,y="truth";else{const j=m>=75,T=v.add(n,{kind:l,problem:a,confidence:m/100,needsReview:j?0:1});h=T.id,y="mem",w=T.needs_review===1}const{recordWrite:b}=await import("./receipts.js"),C=b(t,{tool:"wyrm_capture",outcome:w?"queued":"stored",refTable:y,refId:h,reason:w?`needs_review (confidence ${m}%) \u2014 review to activate`:void 0,source:"cli",projectId:n??void 0});let E=null;if(y==="mem"){const{resolveEmbeddingState:j}=await import("./providers/embedding-provider.js");if(j().resolved!=="none")try{const{createVectorStore:L}=await import("./vectors.js"),q=process.env.WYRM_VECTOR_PROVIDER??"auto";E=await L({provider:q},t).addVector(a,"artifact",h,n)?"embedded for semantic recall":"not embedded yet (wyrm index rebuild will backfill)"}catch{E="not embedded yet (wyrm index rebuild will backfill)"}}o.close(),k(`Captured as ${i}: ${l}`),console.log(`${d.dim("Confidence:")} ${m}% | ${d.dim(g)}`),console.log(`${d.dim("ID:")} ${y}:${h}`),w&&console.log(`${K.warning} Stored for review \u2014 run ${d.cyan("wyrm review")} to activate`),E&&console.log(d.dim(` ${E}`)),console.log(`receipt: ${C.outcome} ${C.ref??""}${C.reason?` \u2014 ${C.reason}`:""}`)}async function ut(u){const{flags:c}=x(u);if(c.writes!==!0){f("Usage: wyrm digest --writes [--since <days>] [--json]"),process.exitCode=1;return}const s=Number(c.since)>=1?Number(c.since):1,{buildWriteDigest:a}=await import("./receipts.js"),r=R();try{const e=a(r.getDatabase(),s);if(c.json===!0){process.stdout.write(JSON.stringify(e)+`
41
- `);return}if(S(`Write digest \u2014 last ${e.since}`),console.log(`${d.bold("Total writes:")} ${e.total} ${d.bold("Outcomes:")} ${Object.entries(e.byOutcome).map(([o,t])=>`${o} ${t}`).join(" \xB7 ")||"none"}`),console.log(`${d.bold("Review queue:")} ${e.reviewQueueDepth} awaiting review${e.reviewQueueDepth>0?d.dim(" (invisible to recall until approved)"):""}`),e.byTool.length>0&&console.log(I(["Tool","Outcome","N"],e.byTool.map(o=>[o.tool,o.outcome,String(o.n)]))),e.reasons.length>0){console.log(d.bold("Non-stored reasons:"));for(const o of e.reasons)console.log(` ${o.outcome} \xD7${o.n} \u2014 ${o.reason.slice(0,110)}`)}e.bySource.length>0&&console.log(d.bold("By source: ")+e.bySource.map(o=>`${o.source} ${o.n}`).join(" \xB7 "))}finally{r.close()}}async function pt(u){const{flags:c}=x(u),s=O(c.session,0),a=c.path,r=c.project,e=O(c["max-chars"],6e3),o=c.json===!0,t=o||c.quiet===!0,n=console.log;console.log=()=>{};const p=(m,g)=>{if(g==null)return 0;try{return m.prepare("SELECT COUNT(*) AS n FROM memory_artifacts WHERE project_id = ?").get(g).n}catch{return 0}},i=m=>{process.stdout.write(JSON.stringify({brief:"",attached:{quests:0,truths:0,artifacts:0,failures:0},memories:p(l.getDatabase(),m)})+`
40
+ `+n.notes);break}default:f(`Unknown type prefix: ${a}. Use mem|quest|truth|data|session`)}o.close()}async function dt(p){const{positional:c,flags:s}=x(p),a=c[0];a||(f('Usage: wyrm capture "<content>" [--project <name>] [--mode auto|quest|truth|memory]'),process.exit(1));const r=s.project,e=s.mode,o=R(),t=o.getDatabase();let n=null;if(r){const j=N(o,r);j||(f(`Project not found: ${r}`),console.log(d.dim(` create it: wyrm project add ${r} --path <dir>`)),o.close(),process.exit(1)),n=j.id}let u=Ge(a);e&&e!=="auto"&&(u={type:e,subtype:{quest:"quest",truth:"decision",memory:"pattern"}[e]??e,confidence:100,reasoning:`Mode override: ${e}`});const{type:i,subtype:l,confidence:m,reasoning:g}=u;(i==="quest"||i==="truth"||i==="memory")&&n===null&&(f('A --project is required to capture. Use: wyrm capture "<text>" --project <name>'),o.close(),process.exit(1));let h=0,y="",w=!1;const b=new B(t),$=new de(t);if(ve($,new be(t)),i==="quest")h=o.addQuest(n,a.slice(0,200),"","medium").id,y="quest";else if(i==="truth")if(e!=="truth"&&m<100){const j=b.add(n,{kind:"pattern",problem:a,confidence:m/100,needsReview:1});h=j.id,y="mem",w=j.needs_review===1}else h=$.set(n,{category:"decision",key:a.slice(0,60),value:a}).id,y="truth";else{const j=m>=75,T=b.add(n,{kind:l,problem:a,confidence:m/100,needsReview:j?0:1});h=T.id,y="mem",w=T.needs_review===1}const{recordWrite:v}=await import("./receipts.js"),C=v(t,{tool:"wyrm_capture",outcome:w?"queued":"stored",refTable:y,refId:h,reason:w?`needs_review (confidence ${m}%) \u2014 review to activate`:void 0,source:"cli",projectId:n??void 0});let _=null;if(y==="mem"){const{resolveEmbeddingState:j}=await import("./providers/embedding-provider.js");if(j().resolved!=="none")try{const{createVectorStore:L}=await import("./vectors.js"),q=process.env.WYRM_VECTOR_PROVIDER??"auto";_=await L({provider:q},t).addVector(a,"artifact",h,n)?"embedded for semantic recall":"not embedded yet (wyrm index rebuild will backfill)"}catch{_="not embedded yet (wyrm index rebuild will backfill)"}}o.close(),k(`Captured as ${i}: ${l}`),console.log(`${d.dim("Confidence:")} ${m}% | ${d.dim(g)}`),console.log(`${d.dim("ID:")} ${y}:${h}`),w&&console.log(`${K.warning} Stored for review \u2014 run ${d.cyan("wyrm review")} to activate`),_&&console.log(d.dim(` ${_}`)),console.log(`receipt: ${C.outcome} ${C.ref??""}${C.reason?` \u2014 ${C.reason}`:""}`)}async function ut(p){const{flags:c}=x(p);if(c.writes!==!0){f("Usage: wyrm digest --writes [--since <days>] [--json]"),process.exitCode=1;return}const s=Number(c.since)>=1?Number(c.since):1,{buildWriteDigest:a}=await import("./receipts.js"),r=R();try{const e=a(r.getDatabase(),s);if(c.json===!0){process.stdout.write(JSON.stringify(e)+`
41
+ `);return}if(S(`Write digest \u2014 last ${e.since}`),console.log(`${d.bold("Total writes:")} ${e.total} ${d.bold("Outcomes:")} ${Object.entries(e.byOutcome).map(([o,t])=>`${o} ${t}`).join(" \xB7 ")||"none"}`),console.log(`${d.bold("Review queue:")} ${e.reviewQueueDepth} awaiting review${e.reviewQueueDepth>0?d.dim(" (invisible to recall until approved)"):""}`),e.byTool.length>0&&console.log(D(["Tool","Outcome","N"],e.byTool.map(o=>[o.tool,o.outcome,String(o.n)]))),e.reasons.length>0){console.log(d.bold("Non-stored reasons:"));for(const o of e.reasons)console.log(` ${o.outcome} \xD7${o.n} \u2014 ${o.reason.slice(0,110)}`)}e.bySource.length>0&&console.log(d.bold("By source: ")+e.bySource.map(o=>`${o.source} ${o.n}`).join(" \xB7 "))}finally{r.close()}}async function pt(p){const{flags:c}=x(p),s=O(c.session,0),a=c.path,r=c.project,e=O(c["max-chars"],6e3),o=c.json===!0,t=o||c.quiet===!0,n=console.log;console.log=()=>{};const u=(m,g)=>{if(g==null)return 0;try{return m.prepare("SELECT COUNT(*) AS n FROM memory_artifacts WHERE project_id = ?").get(g).n}catch{return 0}},i=m=>{process.stdout.write(JSON.stringify({brief:"",attached:{quests:0,truths:0,artifacts:0,failures:0},memories:u(l.getDatabase(),m)})+`
42
42
  `)},l=R();try{const m=new Ve(l.getDatabase());let g=s;if(g<=0){let w=a?l.getProject(a):void 0;if(!w&&r&&(w=N(l,r)??void 0),w||(w=l.getProject(process.cwd())),!w){if(o){i();return}t||process.stderr.write(`wyrm rehydrate: no Wyrm project for this directory, nothing to restore.
43
- `);return}const v=l.getRecentSessions(w.id,1);if(v.length===0){if(o){i(w.id);return}t||process.stderr.write(`wyrm rehydrate: project "${w.name}" has no prior sessions yet.
44
- `);return}g=v[0].id}const h=m.rehydrate(g);if(!h){if(o){i();return}t||process.stderr.write(`wyrm rehydrate: session ${g} not found.
43
+ `);return}const b=l.getRecentSessions(w.id,1);if(b.length===0){if(o){i(w.id);return}t||process.stderr.write(`wyrm rehydrate: project "${w.name}" has no prior sessions yet.
44
+ `);return}g=b[0].id}const h=m.rehydrate(g);if(!h){if(o){i();return}t||process.stderr.write(`wyrm rehydrate: session ${g} not found.
45
45
  `);return}let y=h.briefing_markdown;if(e>0&&y.length>e&&(y=y.slice(0,e)+`
46
46
 
47
- _... brief truncated at ${e} chars, run \`wyrm show session:${h.session_id}\` for the full record._`),o){const w=l.getDatabase().prepare("SELECT project_id FROM sessions WHERE id = ?").get(h.session_id),v=p(l.getDatabase(),w?.project_id);process.stdout.write(JSON.stringify({brief:y,attached:h.attached,memories:v})+`
47
+ _... brief truncated at ${e} chars, run \`wyrm show session:${h.session_id}\` for the full record._`),o){const w=l.getDatabase().prepare("SELECT project_id FROM sessions WHERE id = ?").get(h.session_id),b=u(l.getDatabase(),w?.project_id);process.stdout.write(JSON.stringify({brief:y,attached:h.attached,memories:b})+`
48
48
  `);return}process.stdout.write(y+`
49
- `)}finally{l.close(),console.log=n}}async function mt(u){const{flags:c}=x(u),s=typeof c.match=="string"?c.match:void 0,a=typeof c.dataset=="string"?c.dataset:void 0,r=c["older-than"]!==void 0?Number(c["older-than"]):void 0,e=c.yes===!0;if(!s&&!a&&r===void 0){f("wyrm forget needs a predicate \u2014 nothing is erased without one."),console.log(' by subject: wyrm forget --match "someone@example.com"'),console.log(" by source: wyrm forget --dataset ingested_transcripts (matches the `category` column)"),console.log(" by age: wyrm forget --older-than 365"),console.log(" add --yes to actually delete (dry run otherwise)."),process.exitCode=1;return}const o=R();try{const t=o.forgetData({match:s,dataset:a,olderThanDays:r,dryRun:!e});if(t.matched===0){k("Nothing matched \u2014 nothing to erase.");return}S(e?"Erased":"Would erase (dry run)"),console.log(` ${t.matched} row(s) in the data lake`);for(const n of t.samples)console.log(` ${n.created_at.slice(0,10)} ${n.category} ${n.key.slice(0,60)}`);t.samples.length<t.matched&&console.log(` \u2026 and ${t.matched-t.samples.length} more`),e?(k(`Deleted ${t.deleted} row(s). Full-text index updated by trigger; freed pages are overwritten (secure_delete is on).`),console.log(" NOTE: snapshots under ~/.wyrm/backups still contain the old rows. Erase or rotate them separately.")):(console.log(""),console.log(" Dry run \u2014 nothing was deleted. Re-run with --yes to erase."))}finally{o.close()}}async function ft(u){const{flags:c}=x(u),{runMetabolize:s}=await import("./metabolize.js"),a=R(),r=console.log;try{let e;if(typeof c.project=="string"){const t=N(a,c.project);if(!t){f(`Project not found: ${c.project}`),process.exitCode=1;return}e=t.id}const o=s(a.getDatabase(),{projectId:e,dryRun:c["dry-run"]===!0,near:c["no-near"]===!0?!1:void 0});console.log(`metabolize${o.dryRun?" (dry-run)":""} \u2014 scanned ${o.scanned} \xB7 exact-merged ${o.exactMerged} (superseded, not deleted) \xB7 near-dup candidates queued ${o.nearCandidatesQueued} \xB7 decayed ${o.decayed}`)}finally{a.close(),console.log=r}}async function gt(u){const{positional:c,flags:s}=x(u),a=c[0]??"stats",{backfillEntities:r}=await import("./entity-populate.js"),e=R(),o=console.log;try{const t=e.getDatabase();let n;if(typeof s.project=="string"){const p=N(e,s.project);if(!p){f(`Project not found: ${s.project}`),process.exitCode=1;return}n=p.id}if(a==="stats"){const p=i=>{if(n!=null){const m=i==="entities"?"SELECT COUNT(*) AS n FROM entities WHERE project_id = ?":"SELECT COUNT(*) AS n FROM relationships WHERE project_id = ?";return t.prepare(m).get(n).n}const l=i==="entities"?"SELECT COUNT(*) AS n FROM entities":"SELECT COUNT(*) AS n FROM relationships";return t.prepare(l).get().n};console.log(`entities: ${p("entities")} \xB7 relationships: ${p("relationships")}${n!=null?` (project ${n})`:" (all projects)"}`);return}if(a==="backfill"){const p=n!=null?[{id:n}]:t.prepare("SELECT id FROM projects").all();let i=0,l=0,m=0;for(const g of p){const h=r(t,g.id,{limit:O(s.limit,5e3)});i+=h.created,l+=h.linked,m+=h.artifactsScanned}console.log(`entities backfill \u2014 scanned ${m} artifact(s) across ${p.length} project(s): +${i} entities, +${l} co-occurrence edges`);return}f("Usage: wyrm entities <backfill|stats> [--project <name>] [--limit N]"),process.exitCode=1}finally{e.close(),console.log=o}}async function yt(u){const{positional:c,flags:s}=x(u),a=c[0],r=await import("./bridge.js");if(a==="init"){const{existsSync:e,writeFileSync:o}=await import("node:fs"),t=r.configPath();if(e(t)&&s.force!==!0){f(`${t} already exists (use --force to overwrite).`),process.exitCode=1;return}const n=(await import("node:os")).homedir(),p={sources:[{type:"remember",name:"remember",dir:`${n}/.remember`,projectPath:n,windowDays:7}]};o(t,JSON.stringify(p,null,2)),k(`Wrote ${t} (1 source: remember). Add render/reverse sources per project as needed.`);return}if(a==="status"){const e=r.loadConfig();if(!e){f(`No bridge config at ${r.configPath()} \u2014 run \`wyrm bridge init\`.`),process.exitCode=1;return}const o=r.loadState();S("Bridge sources");for(const t of e.sources){const n=Object.keys(o).filter(i=>i.startsWith(t.name+":")),p=n.reduce((i,l)=>i+o[l].length,0);console.log(` ${t.name} [${t.type}] \u2192 ${"projectPath"in t?t.projectPath:""}${t.type==="remember"?` (${n.length} files tracked, ${p} sections bridged)`:""}`)}return}if(a==="run"){const e=r.loadConfig();if(!e){f(`No bridge config at ${r.configPath()} \u2014 run \`wyrm bridge init\`.`),process.exitCode=1;return}const o=R(),t=console.log;try{const n={wyrm_version:A().version??"unknown",compiled_at:new Date().toISOString()},p=await r.runBridge(o.getDatabase(),e,n,{only:typeof s.source=="string"?s.source:void 0,dryRun:s["dry-run"]===!0});for(const i of p)console.log(`bridge:${i.source} [${i.type}]${s["dry-run"]===!0?" (dry-run)":""} \u2014 stored ${i.stored} \xB7 queued ${i.queued} \xB7 dropped ${i.dropped} \xB7 rendered ${i.rendered}${i.note?` \xB7 ${i.note}`:""}`);if(s.watch===!0){const i=Number(s.interval)>=2?Number(s.interval):15;console.log(`bridge watch \u2014 re-rendering on write events (poll ${i}s, Ctrl-C to stop)`),await r.watchBridge(o.getDatabase(),e,()=>({wyrm_version:A().version??"unknown",compiled_at:new Date().toISOString()}),{intervalSec:i,onCycle:l=>{for(const m of l)console.log(`bridge:${m.source} re-rendered (${m.rendered} file(s), ${m.queued} edit(s) harvested)`)}})}return}finally{o.close(),console.log=t}}f("Usage: wyrm bridge <init|status|run> [--source <name>] [--dry-run] [--force]"),process.exitCode=1}async function ht(u){const{positional:c,flags:s}=x(u);if(c[0]!=="log"){f("Usage: wyrm session log [--path <dir>] [--run <id>] [--objectives <t>] [--completed <t>] [--issues <t>] [--commits <t>] [--notes <t>]"),process.exitCode=1;return}const r=i=>typeof s[i]=="string"?s[i]:void 0,e=r("path"),o=r("project"),t=r("run")?.slice(0,64),n=console.log;console.log=()=>{};const p=R();try{let i=e?p.getProject(e):void 0;if(!i&&o&&(i=N(p,o)??void 0),i||(i=p.getProject(process.cwd())),!i){process.stderr.write(`wyrm session log: no Wyrm project for this directory, nothing recorded.
50
- `);return}const l={};for(const h of["objectives","completed","issues","commits","notes"]){const y=r(h);y&&(l[h]=y.slice(0,8e3))}const m=p.getDatabase();let g;t&&(g=m.prepare("SELECT id FROM sessions WHERE project_id = ? AND run_id = ? ORDER BY id DESC LIMIT 1").get(i.id,t)?.id),g?p.updateSession(g,l):(g=p.createSession(i.id,l).id,t&&m.prepare("UPDATE sessions SET run_id = ? WHERE id = ?").run(t,g)),process.stdout.write(`session:${g}
51
- `)}finally{p.close(),console.log=n}}async function wt(u){const{positional:c,flags:s}=x(u),a=c[0],r=p=>typeof s[p]=="string"?s[p]:void 0,{AgentPresence:e,processStartTime:o,presenceLiveness:t}=await import("./presence.js"),n=R();try{const p=new e(n.getDatabase());if(a==="announce"){const i=r("agent");if(!i){f("Usage: wyrm presence announce --agent <id> [--kind <kind>] [--project <name>|--path <dir>] [--quest <n>] [--pid <n>|--auto-pid] [--ttl <sec>] [--role <r>]"),process.exitCode=1;return}const l=r("path");let m=l?n.getProject(l):void 0;!m&&r("project")&&(m=N(n,r("project"))??void 0);const g=s["auto-pid"]===!0?process.ppid:Number(r("pid")),h={};let y="";if(Number.isInteger(g)&&g>0){const $=o(g);$?(h.pid=g,h.pid_start=$,y=` \xB7 pid ${g} (start ${$})`):y=` \xB7 pid ${g} unreadable \u2014 TTL fallback`}const w=Number(r("ttl")),v=p.announce({agent_id:i,agent_kind:r("kind")??"cli",project_id:m?.id??null,current_quest_id:r("quest")?Number(r("quest")):null,ttl_seconds:Number.isFinite(w)&&w>0?w:h.pid?86400:300,metadata:Object.keys(h).length?h:void 0,role:r("role")??null});process.stdout.write(`presence:${v.id} ${i}${y}
49
+ `)}finally{l.close(),console.log=n}}async function mt(p){const{flags:c}=x(p),s=typeof c.match=="string"?c.match:void 0,a=typeof c.dataset=="string"?c.dataset:void 0,r=c["older-than"]!==void 0?Number(c["older-than"]):void 0,e=c.yes===!0;if(!s&&!a&&r===void 0){f("wyrm forget needs a predicate \u2014 nothing is erased without one."),console.log(' by subject: wyrm forget --match "someone@example.com"'),console.log(" by source: wyrm forget --dataset ingested_transcripts (matches the `category` column)"),console.log(" by age: wyrm forget --older-than 365"),console.log(" add --yes to actually delete (dry run otherwise)."),process.exitCode=1;return}const o=R();try{const t=o.forgetData({match:s,dataset:a,olderThanDays:r,dryRun:!e});if(t.matched===0){k("Nothing matched \u2014 nothing to erase.");return}S(e?"Erased":"Would erase (dry run)"),console.log(` ${t.matched} row(s) in the data lake`);for(const n of t.samples)console.log(` ${n.created_at.slice(0,10)} ${n.category} ${n.key.slice(0,60)}`);t.samples.length<t.matched&&console.log(` \u2026 and ${t.matched-t.samples.length} more`),e?(k(`Deleted ${t.deleted} row(s). Full-text index updated by trigger; freed pages are overwritten (secure_delete is on).`),console.log(" NOTE: snapshots under ~/.wyrm/backups still contain the old rows. Erase or rotate them separately.")):(console.log(""),console.log(" Dry run \u2014 nothing was deleted. Re-run with --yes to erase."))}finally{o.close()}}async function ft(p){const{flags:c}=x(p),{runMetabolize:s}=await import("./metabolize.js"),a=R(),r=console.log;try{let e;if(typeof c.project=="string"){const t=N(a,c.project);if(!t){f(`Project not found: ${c.project}`),process.exitCode=1;return}e=t.id}const o=s(a.getDatabase(),{projectId:e,dryRun:c["dry-run"]===!0,near:c["no-near"]===!0?!1:void 0});console.log(`metabolize${o.dryRun?" (dry-run)":""} \u2014 scanned ${o.scanned} \xB7 exact-merged ${o.exactMerged} (superseded, not deleted) \xB7 near-dup candidates queued ${o.nearCandidatesQueued} \xB7 decayed ${o.decayed}`)}finally{a.close(),console.log=r}}async function gt(p){const{positional:c,flags:s}=x(p),a=c[0]??"stats",{backfillEntities:r}=await import("./entity-populate.js"),e=R(),o=console.log;try{const t=e.getDatabase();let n;if(typeof s.project=="string"){const u=N(e,s.project);if(!u){f(`Project not found: ${s.project}`),process.exitCode=1;return}n=u.id}if(a==="stats"){const u=i=>{if(n!=null){const m=i==="entities"?"SELECT COUNT(*) AS n FROM entities WHERE project_id = ?":"SELECT COUNT(*) AS n FROM relationships WHERE project_id = ?";return t.prepare(m).get(n).n}const l=i==="entities"?"SELECT COUNT(*) AS n FROM entities":"SELECT COUNT(*) AS n FROM relationships";return t.prepare(l).get().n};console.log(`entities: ${u("entities")} \xB7 relationships: ${u("relationships")}${n!=null?` (project ${n})`:" (all projects)"}`);return}if(a==="backfill"){const u=n!=null?[{id:n}]:t.prepare("SELECT id FROM projects").all();let i=0,l=0,m=0;for(const g of u){const h=r(t,g.id,{limit:O(s.limit,5e3)});i+=h.created,l+=h.linked,m+=h.artifactsScanned}console.log(`entities backfill \u2014 scanned ${m} artifact(s) across ${u.length} project(s): +${i} entities, +${l} co-occurrence edges`);return}f("Usage: wyrm entities <backfill|stats> [--project <name>] [--limit N]"),process.exitCode=1}finally{e.close(),console.log=o}}async function yt(p){const{positional:c,flags:s}=x(p),a=c[0],r=await import("./bridge.js");if(a==="init"){const{existsSync:e,writeFileSync:o}=await import("node:fs"),t=r.configPath();if(e(t)&&s.force!==!0){f(`${t} already exists (use --force to overwrite).`),process.exitCode=1;return}const n=(await import("node:os")).homedir(),u={sources:[{type:"remember",name:"remember",dir:`${n}/.remember`,projectPath:n,windowDays:7}]};o(t,JSON.stringify(u,null,2)),k(`Wrote ${t} (1 source: remember). Add render/reverse sources per project as needed.`);return}if(a==="status"){const e=r.loadConfig();if(!e){f(`No bridge config at ${r.configPath()} \u2014 run \`wyrm bridge init\`.`),process.exitCode=1;return}const o=r.loadState();S("Bridge sources");for(const t of e.sources){const n=Object.keys(o).filter(i=>i.startsWith(t.name+":")),u=n.reduce((i,l)=>i+o[l].length,0);console.log(` ${t.name} [${t.type}] \u2192 ${"projectPath"in t?t.projectPath:""}${t.type==="remember"?` (${n.length} files tracked, ${u} sections bridged)`:""}`)}return}if(a==="run"){const e=r.loadConfig();if(!e){f(`No bridge config at ${r.configPath()} \u2014 run \`wyrm bridge init\`.`),process.exitCode=1;return}const o=R(),t=console.log;try{const n={wyrm_version:A().version??"unknown",compiled_at:new Date().toISOString()},u=await r.runBridge(o.getDatabase(),e,n,{only:typeof s.source=="string"?s.source:void 0,dryRun:s["dry-run"]===!0});for(const i of u)console.log(`bridge:${i.source} [${i.type}]${s["dry-run"]===!0?" (dry-run)":""} \u2014 stored ${i.stored} \xB7 queued ${i.queued} \xB7 dropped ${i.dropped} \xB7 rendered ${i.rendered}${i.note?` \xB7 ${i.note}`:""}`);if(s.watch===!0){const i=Number(s.interval)>=2?Number(s.interval):15;console.log(`bridge watch \u2014 re-rendering on write events (poll ${i}s, Ctrl-C to stop)`),await r.watchBridge(o.getDatabase(),e,()=>({wyrm_version:A().version??"unknown",compiled_at:new Date().toISOString()}),{intervalSec:i,onCycle:l=>{for(const m of l)console.log(`bridge:${m.source} re-rendered (${m.rendered} file(s), ${m.queued} edit(s) harvested)`)}})}return}finally{o.close(),console.log=t}}f("Usage: wyrm bridge <init|status|run> [--source <name>] [--dry-run] [--force]"),process.exitCode=1}async function ht(p){const{positional:c,flags:s}=x(p);if(c[0]!=="log"){f("Usage: wyrm session log [--path <dir>] [--run <id>] [--objectives <t>] [--completed <t>] [--issues <t>] [--commits <t>] [--notes <t>]"),process.exitCode=1;return}const r=i=>typeof s[i]=="string"?s[i]:void 0,e=r("path"),o=r("project"),t=r("run")?.slice(0,64),n=console.log;console.log=()=>{};const u=R();try{let i=e?u.getProject(e):void 0;if(!i&&o&&(i=N(u,o)??void 0),i||(i=u.getProject(process.cwd())),!i){process.stderr.write(`wyrm session log: no Wyrm project for this directory, nothing recorded.
50
+ `);return}const l={};for(const h of["objectives","completed","issues","commits","notes"]){const y=r(h);y&&(l[h]=y.slice(0,8e3))}const m=u.getDatabase();let g;t&&(g=m.prepare("SELECT id FROM sessions WHERE project_id = ? AND run_id = ? ORDER BY id DESC LIMIT 1").get(i.id,t)?.id),g?u.updateSession(g,l):(g=u.createSession(i.id,l).id,t&&m.prepare("UPDATE sessions SET run_id = ? WHERE id = ?").run(t,g)),process.stdout.write(`session:${g}
51
+ `)}finally{u.close(),console.log=n}}async function wt(p){const{positional:c,flags:s}=x(p),a=c[0],r=u=>typeof s[u]=="string"?s[u]:void 0,{AgentPresence:e,processStartTime:o,presenceLiveness:t}=await import("./presence.js"),n=R();try{const u=new e(n.getDatabase());if(a==="announce"){const i=r("agent");if(!i){f("Usage: wyrm presence announce --agent <id> [--kind <kind>] [--project <name>|--path <dir>] [--quest <n>] [--pid <n>|--auto-pid] [--ttl <sec>] [--role <r>]"),process.exitCode=1;return}const l=r("path");let m=l?n.getProject(l):void 0;!m&&r("project")&&(m=N(n,r("project"))??void 0);const g=s["auto-pid"]===!0?process.ppid:Number(r("pid")),h={};let y="";if(Number.isInteger(g)&&g>0){const $=o(g);$?(h.pid=g,h.pid_start=$,y=` \xB7 pid ${g} (start ${$})`):y=` \xB7 pid ${g} unreadable \u2014 TTL fallback`}const w=Number(r("ttl")),b=u.announce({agent_id:i,agent_kind:r("kind")??"cli",project_id:m?.id??null,current_quest_id:r("quest")?Number(r("quest")):null,ttl_seconds:Number.isFinite(w)&&w>0?w:h.pid?86400:300,metadata:Object.keys(h).length?h:void 0,role:r("role")??null});process.stdout.write(`presence:${b.id} ${i}${y}
52
52
  `);return}if(a==="list"||a==="ls"||a===void 0){const i=n.getDatabase().prepare("SELECT * FROM agent_presence ORDER BY last_heartbeat DESC").all();if(i.length===0){process.stdout.write(`No agents on the board.
53
53
  `);return}const l={"alive-pid":"ALIVE (pid)","dead-pid":"DEAD (pid gone)","alive-ttl":"alive (ttl)",stale:"stale"};for(const m of i){const g=t(m);process.stdout.write(`${m.agent_id} [${m.agent_kind}] ${l[g]} hb ${m.last_heartbeat}${m.role?` role ${m.role}`:""}${m.current_quest_id?` quest #${m.current_quest_id}`:""}
54
- `)}return}if(a==="release"){const i=r("agent");if(!i){f("Usage: wyrm presence release --agent <id>"),process.exitCode=1;return}const l=p.release(i);process.stdout.write(l?`released ${i}
54
+ `)}return}if(a==="release"){const i=r("agent");if(!i){f("Usage: wyrm presence release --agent <id>"),process.exitCode=1;return}const l=u.release(i);process.stdout.write(l?`released ${i}
55
55
  `:`no presence row for ${i}
56
- `);return}f("Usage: wyrm presence <announce|list|release> [options]"),process.exitCode=1}finally{n.close()}}async function bt(u){const{flags:c}=x(u),s=c.path,a=c.project,r=c.out,e=c.brief===!0,o=c.force===!0,t=c.quiet===!0,n=(typeof c.client=="string"?c.client:"").split(",").map(g=>g.trim().toLowerCase()).filter(Boolean);let p=n.filter(g=>pe.includes(g));const i=n.filter(g=>!pe.includes(g));i.length>0&&(f(`Unknown --client value(s): ${i.join(", ")} (valid: ${pe.join("|")})`),process.exit(1));const l=console.log;e&&(console.log=()=>{});const m=R();try{let g=s?m.getProject(s):void 0;!g&&a&&(g=N(m,a)??void 0),g||(g=m.getProject(process.cwd())),g||(console.log=l,f("wyrm render: no Wyrm project for this directory (use --path or --project)."),process.exit(1));const h=$e(m.getDatabase()),y={wyrm_version:A().version??"unknown",compiled_at:new Date().toISOString()};if(e){const b=ue(h,g,y);console.log=l,process.stdout.write(b.sessionBrief+`
57
- `);return}const w=r??g.path;p.length===0&&n.length===0&&(p=Be(Ke.filter(b=>U(W(w,b)))));{const b=await import("./reverse-bridge.js"),C=ue(h,g,y),E={"MEMORY.md":C.memoryMd};for(const j of p){const T=ke(j,C.model,y);E[T.relPath]=T.block}try{const j=b.makeBridgeDeps(m.getDatabase()),T=await b.sweepProject(j,{id:g.id,path:g.path},E,{rootDir:w});T.added>0&&!t&&console.log(` ${K.warning} harvested ${T.added} human edit(s) \u2192 review queue before overwrite`)}catch{}}const{plan:v,writes:$}=He(h,g,y,{rootDir:w,clients:p,force:o});if(!t){k(`Rendered ${g.name} memory (${v.model.truths.length} truths, ${v.model.failures.length} failures, ${v.model.quests.length} quests, ${v.model.artifacts.length} patterns) to ${w}`);for(const b of $){const C=b.action==="created"?K.success:b.action==="updated"?K.info:K.warning;console.log(` ${C} ${b.action.padEnd(7)} ${b.path}${b.reason?` (${b.reason})`:""}`)}}}finally{m.close(),console.log=l}}async function vt(u){const{flags:c}=x(u),s=c.path,a=c.project,r=c.root,e=c["dry-run"]===!0||c.dry===!0,o=(typeof c.client=="string"?c.client:"").split(",").map(l=>l.trim().toLowerCase()).filter(Boolean),t=["claude","cursor","copilot","agents"],n=o.filter(l=>t.includes(l)),p=await import("./reverse-bridge.js"),i=R();try{let l=s?i.getProject(s):void 0;if(!l&&a&&(l=N(i,a)??void 0),l||(l=i.getProject(process.cwd())),!l){f("wyrm reverse-bridge: no Wyrm project for this directory (use --path or --project)."),process.exitCode=1;return}const m=$e(i.getDatabase()),g={wyrm_version:A().version??"unknown",compiled_at:new Date().toISOString()},h=ue(m,l,g),y={"MEMORY.md":h.memoryMd};for(const $ of n){const b=ke($,h.model,g);y[b.relPath]=b.block}const w=p.makeBridgeDeps(i.getDatabase()),v=await p.sweepProject(w,{id:l.id,path:l.path},y,{dryRun:e,rootDir:r});S(`Reverse bridge ${e?"(dry run) ":""}\u2014 ${v.added} candidate(s) queued, ${v.skipped} already present (${v.filesWithEdits}/${v.filesScanned} file(s) with edits)`);for(const $ of v.sample)console.log(` ${K.bullet} ${$}`);!e&&v.added>0&&k("Review with: wyrm review")}finally{i.close()}}async function $t(u){const c=x(u);if(typeof c.flags.from=="string"){await kt(c);return}const{positional:s,flags:a}=x(u.slice(1)),r=u[0],e=a.project;if(r==="git"){const o=O(a.last,20),t=R(),n=t.getDatabase();let p=null;if(e){const y=N(t,e);y||(f(`Project not found: ${e}`),t.close(),process.exit(1)),p=y.id}else{const y=n.prepare("SELECT p.* FROM projects p JOIN sessions s ON s.project_id = p.id ORDER BY s.created_at DESC LIMIT 1").get();y&&(p=y.id)}p||(f("No project found. Use --project <name>"),t.close(),process.exit(1));const i=te("git",["log","--pretty=format:%H%x1f%s%x1f%an%x1f%ai",`-${o}`],{cwd:process.cwd(),encoding:"utf-8",timeout:1e4,shell:!1});(i.error||i.status!==0)&&(f("git log failed. Make sure you are in a git repository."),t.close(),process.exit(1));const l=i.stdout.split(`
58
- `).filter(y=>y.trim()),m=new B(n);let g=0,h=0;for(const y of l){const[,w,v,$]=y.split(""),b=w??"";if(/^Merge /i.test(b)||/^(chore|bump|release|version)/i.test(b)){h++;continue}let C="pattern";/^fix(\(.+\))?:/i.test(b)?C="lesson":/^refactor(\(.+\))?:/i.test(b)&&(C="heuristic");const E=b.split(":")[0]??"commit";m.add(p,{kind:C,problem:b,whyItWorked:`Committed by ${v??"unknown"} on ${$??"unknown"}`,tags:["git","commit",E.toLowerCase()],confidence:.6,needsReview:1}),g++}t.close(),k(`Imported ${g} commits (${h} skipped). Run ${d.cyan("wyrm review")} to activate.`)}else if(r==="rules"){const o=s[0],t=a.format??"plain";o||(f("Usage: wyrm import rules <path> [--project <name>] [--format cursorrules|copilot|plain]"),process.exit(1)),U(o)||(f(`File not found: ${o}`),process.exit(1));const n=H(o,"utf-8"),p=R(),i=p.getDatabase();let l=null;if(e){const E=N(p,e);E||(f(`Project not found: ${e}`),p.close(),process.exit(1)),l=E.id}else{const E=i.prepare("SELECT p.* FROM projects p JOIN sessions s ON s.project_id = p.id ORDER BY s.created_at DESC LIMIT 1").get();E&&(l=E.id)}l||(f("No project found. Use --project <name>"),p.close(),process.exit(1));const m=o.split("/").pop()??"rules",g=["imported",t,m],h=n.split(/\n(?=#)/),y=n.split(/\n\n+/),w=(h.length>=y.length?h:y).map(E=>E.trim()).filter(E=>E.length>=15),v=new B(i),$=new de(i);ve($,new be(i));let b=0,C=0;for(const E of w)/\b(always|never|must|use|don't|avoid|prefer)\b/i.test(E)?($.set(l,{category:"constraint",key:E.slice(0,50).replace(/\n/g," "),value:E,source:m}),b++):(v.add(l,{kind:"heuristic",problem:E,tags:g,confidence:.7,needsReview:1}),C++);p.close(),k(`Imported ${b} ground truths + ${C} artifacts (pending review).`)}else f("Usage: wyrm import git [--project <name>] [--last N]"),f(" wyrm import rules <path> [--project <name>] [--format cursorrules|copilot|plain]"),f(` wyrm import --from ${_e.join("|")} <file.json> [--project <name>]`),process.exit(1)}async function kt(u){const c=u.flags.from,s=u.positional[0],a=u.flags.project,r=`Usage: wyrm import --from ${_e.join("|")} <file.json> [--project <name>]`;s||(f(r),process.exit(1)),U(s)||(f(`File not found: ${s}`),process.exit(1));let e;try{e=JSON.parse(H(s,"utf-8"))}catch(n){f(`Could not parse JSON from ${s}: ${n.message}`),process.exit(1)}let o;try{o=Je(c,e)}catch(n){f(n.message),process.exit(1)}const t=R();try{let n=a?N(t,a):null;if(n||(n=t.getProject(process.cwd())??null),!n){f(a?`Project not found: ${a}`:"wyrm import: no Wyrm project for this directory (pass --project <name>)."),process.exitCode=1;return}if(o.length===0){f(`No importable memories found in ${s} for source "${c}".`);return}const p=t.getDatabase(),i=new B(p);try{const{createVectorStore:m}=await import("./vectors.js"),g=process.env.WYRM_VECTOR_PROVIDER??"auto";i.setVectorStore(m({provider:g},p))}catch{}let l=0;for(const m of o)i.add(n.id,{kind:"lesson",problem:m.text,tags:m.tags,constraints:m.metadata?JSON.stringify(m.metadata):void 0,whyItWorked:`Imported from ${m.source}`,outcome:"neutral",confidence:.6,needsReview:1}),l++;k(`Imported ${l} ${l===1?"memory":"memories"} from ${c} into ${d.cyan(n.name)} review queue. Run ${d.cyan("wyrm review")} to vet (tagged ${d.dim(`imported_from:${o[0]?.source??c}`)}).`)}finally{t.close()}}async function _t(u){const{flags:c}=x(u),s=c.project,a=R(),r=a.getDatabase();if(S("Wyrm Statistics"),s){const e=N(a,s);e||(f(`Project not found: ${s}`),a.close(),process.exit(1));const o=a.getProjectStats(e.id),t=[["Sessions",String(o.sessions)],["Quests (pending)",String(o.quests.pending)],["Quests (completed)",String(o.quests.completed)],["Data Points",String(o.dataPoints)]];console.log(I(["Metric","Value"],t))}else{const e=a.getStats(),o=[["Projects",String(e.projects)],["Sessions",String(e.sessions)],["Quests",String(e.quests)],["Data Points",String(e.dataPoints)],["DB Size",e.dbSize]],t=r.prepare("SELECT COUNT(*) as n FROM memory_artifacts").get().n,n=r.prepare("SELECT COUNT(*) as n FROM ground_truths WHERE is_current = 1").get().n,p=r.prepare("SELECT COUNT(*) as n FROM memory_artifacts WHERE needs_review = 1").get().n;let i=0;try{i=r.prepare("SELECT COUNT(*) as n FROM failure_patterns WHERE resolved = 0").get().n}catch{}o.push(["Memories",String(t)],["Ground Truths",String(n)]),p>0&&o.push([" \u21B3 in review queue",`${p} (invisible to recall \u2014 wyrm review)`]),i>0&&o.push(["Unresolved failures",`${i} (the firewall is armed against these)`]),console.log(I(["Metric","Value"],o))}a.close()}async function Et(u){const{flags:c}=x(u),s=c.project,a=R(),r=a.getDatabase();if(c.auto!==void 0){const{isAutoApproveOn:i,setAutoApprove:l}=await import("./memory-artifacts.js"),m=String(c.auto).toLowerCase();try{m==="on"||m==="1"||m==="true"?(l(r,!0),k("Auto-approve ON: new memories skip the review queue and are recall-visible immediately.")):m==="off"||m==="0"||m==="false"?(l(r,!1),k("Auto-approve OFF: risky writes queue for wyrm review again.")):m==="status"||m==="true"?console.log(` auto-approve: ${i(r)?"on":"off"}${process.env.WYRM_AUTO_APPROVE?" (WYRM_AUTO_APPROVE env)":""}`):(f("usage: wyrm review --auto on|off|status"),process.exitCode=1)}finally{a.close()}return}if(c["approve-all"]===!0){const i=r.prepare(`UPDATE memory_artifacts SET needs_review = 0
56
+ `);return}f("Usage: wyrm presence <announce|list|release> [options]"),process.exitCode=1}finally{n.close()}}async function bt(p){const{flags:c}=x(p),s=c.path,a=c.project,r=c.out,e=c.brief===!0,o=c.force===!0,t=c.quiet===!0,n=(typeof c.client=="string"?c.client:"").split(",").map(g=>g.trim().toLowerCase()).filter(Boolean);let u=n.filter(g=>pe.includes(g));const i=n.filter(g=>!pe.includes(g));i.length>0&&(f(`Unknown --client value(s): ${i.join(", ")} (valid: ${pe.join("|")})`),process.exit(1));const l=console.log;e&&(console.log=()=>{});const m=R();try{let g=s?m.getProject(s):void 0;!g&&a&&(g=N(m,a)??void 0),g||(g=m.getProject(process.cwd())),g||(console.log=l,f("wyrm render: no Wyrm project for this directory (use --path or --project)."),process.exit(1));const h=$e(m.getDatabase()),y={wyrm_version:A().version??"unknown",compiled_at:new Date().toISOString()};if(e){const v=ue(h,g,y);console.log=l,process.stdout.write(v.sessionBrief+`
57
+ `);return}const w=r??g.path;u.length===0&&n.length===0&&(u=Be(Ke.filter(v=>U(W(w,v)))));{const v=await import("./reverse-bridge.js"),C=ue(h,g,y),_={"MEMORY.md":C.memoryMd};for(const j of u){const T=ke(j,C.model,y);_[T.relPath]=T.block}try{const j=v.makeBridgeDeps(m.getDatabase()),T=await v.sweepProject(j,{id:g.id,path:g.path},_,{rootDir:w});T.added>0&&!t&&console.log(` ${K.warning} harvested ${T.added} human edit(s) \u2192 review queue before overwrite`)}catch{}}const{plan:b,writes:$}=He(h,g,y,{rootDir:w,clients:u,force:o});if(!t){k(`Rendered ${g.name} memory (${b.model.truths.length} truths, ${b.model.failures.length} failures, ${b.model.quests.length} quests, ${b.model.artifacts.length} patterns) to ${w}`);for(const v of $){const C=v.action==="created"?K.success:v.action==="updated"?K.info:K.warning;console.log(` ${C} ${v.action.padEnd(7)} ${v.path}${v.reason?` (${v.reason})`:""}`)}}}finally{m.close(),console.log=l}}async function vt(p){const{flags:c}=x(p),s=c.path,a=c.project,r=c.root,e=c["dry-run"]===!0||c.dry===!0,o=(typeof c.client=="string"?c.client:"").split(",").map(l=>l.trim().toLowerCase()).filter(Boolean),t=["claude","cursor","copilot","agents"],n=o.filter(l=>t.includes(l)),u=await import("./reverse-bridge.js"),i=R();try{let l=s?i.getProject(s):void 0;if(!l&&a&&(l=N(i,a)??void 0),l||(l=i.getProject(process.cwd())),!l){f("wyrm reverse-bridge: no Wyrm project for this directory (use --path or --project)."),process.exitCode=1;return}const m=$e(i.getDatabase()),g={wyrm_version:A().version??"unknown",compiled_at:new Date().toISOString()},h=ue(m,l,g),y={"MEMORY.md":h.memoryMd};for(const $ of n){const v=ke($,h.model,g);y[v.relPath]=v.block}const w=u.makeBridgeDeps(i.getDatabase()),b=await u.sweepProject(w,{id:l.id,path:l.path},y,{dryRun:e,rootDir:r});S(`Reverse bridge ${e?"(dry run) ":""}\u2014 ${b.added} candidate(s) queued, ${b.skipped} already present (${b.filesWithEdits}/${b.filesScanned} file(s) with edits)`);for(const $ of b.sample)console.log(` ${K.bullet} ${$}`);!e&&b.added>0&&k("Review with: wyrm review")}finally{i.close()}}async function $t(p){const c=x(p);if(typeof c.flags.from=="string"){await kt(c);return}const{positional:s,flags:a}=x(p.slice(1)),r=p[0],e=a.project;if(r==="git"){const o=O(a.last,20),t=R(),n=t.getDatabase();let u=null;if(e){const y=N(t,e);y||(f(`Project not found: ${e}`),t.close(),process.exit(1)),u=y.id}else{const y=n.prepare("SELECT p.* FROM projects p JOIN sessions s ON s.project_id = p.id ORDER BY s.created_at DESC LIMIT 1").get();y&&(u=y.id)}u||(f("No project found. Use --project <name>"),t.close(),process.exit(1));const i=te("git",["log","--pretty=format:%H%x1f%s%x1f%an%x1f%ai",`-${o}`],{cwd:process.cwd(),encoding:"utf-8",timeout:1e4,shell:!1});(i.error||i.status!==0)&&(f("git log failed. Make sure you are in a git repository."),t.close(),process.exit(1));const l=i.stdout.split(`
58
+ `).filter(y=>y.trim()),m=new B(n);let g=0,h=0;for(const y of l){const[,w,b,$]=y.split(""),v=w??"";if(/^Merge /i.test(v)||/^(chore|bump|release|version)/i.test(v)){h++;continue}let C="pattern";/^fix(\(.+\))?:/i.test(v)?C="lesson":/^refactor(\(.+\))?:/i.test(v)&&(C="heuristic");const _=v.split(":")[0]??"commit";m.add(u,{kind:C,problem:v,whyItWorked:`Committed by ${b??"unknown"} on ${$??"unknown"}`,tags:["git","commit",_.toLowerCase()],confidence:.6,needsReview:1}),g++}t.close(),k(`Imported ${g} commits (${h} skipped). Run ${d.cyan("wyrm review")} to activate.`)}else if(r==="rules"){const o=s[0],t=a.format??"plain";o||(f("Usage: wyrm import rules <path> [--project <name>] [--format cursorrules|copilot|plain]"),process.exit(1)),U(o)||(f(`File not found: ${o}`),process.exit(1));const n=H(o,"utf-8"),u=R(),i=u.getDatabase();let l=null;if(e){const _=N(u,e);_||(f(`Project not found: ${e}`),u.close(),process.exit(1)),l=_.id}else{const _=i.prepare("SELECT p.* FROM projects p JOIN sessions s ON s.project_id = p.id ORDER BY s.created_at DESC LIMIT 1").get();_&&(l=_.id)}l||(f("No project found. Use --project <name>"),u.close(),process.exit(1));const m=o.split("/").pop()??"rules",g=["imported",t,m],h=n.split(/\n(?=#)/),y=n.split(/\n\n+/),w=(h.length>=y.length?h:y).map(_=>_.trim()).filter(_=>_.length>=15),b=new B(i),$=new de(i);ve($,new be(i));let v=0,C=0;for(const _ of w)/\b(always|never|must|use|don't|avoid|prefer)\b/i.test(_)?($.set(l,{category:"constraint",key:_.slice(0,50).replace(/\n/g," "),value:_,source:m}),v++):(b.add(l,{kind:"heuristic",problem:_,tags:g,confidence:.7,needsReview:1}),C++);u.close(),k(`Imported ${v} ground truths + ${C} artifacts (pending review).`)}else f("Usage: wyrm import git [--project <name>] [--last N]"),f(" wyrm import rules <path> [--project <name>] [--format cursorrules|copilot|plain]"),f(` wyrm import --from ${Ee.join("|")} <file.json> [--project <name>]`),process.exit(1)}async function kt(p){const c=p.flags.from,s=p.positional[0],a=p.flags.project,r=`Usage: wyrm import --from ${Ee.join("|")} <file.json> [--project <name>]`;s||(f(r),process.exit(1)),U(s)||(f(`File not found: ${s}`),process.exit(1));let e;try{e=JSON.parse(H(s,"utf-8"))}catch(n){f(`Could not parse JSON from ${s}: ${n.message}`),process.exit(1)}let o;try{o=Je(c,e)}catch(n){f(n.message),process.exit(1)}const t=R();try{let n=a?N(t,a):null;if(n||(n=t.getProject(process.cwd())??null),!n){f(a?`Project not found: ${a}`:"wyrm import: no Wyrm project for this directory (pass --project <name>)."),process.exitCode=1;return}if(o.length===0){f(`No importable memories found in ${s} for source "${c}".`);return}const u=t.getDatabase(),i=new B(u);try{const{createVectorStore:m}=await import("./vectors.js"),g=process.env.WYRM_VECTOR_PROVIDER??"auto";i.setVectorStore(m({provider:g},u))}catch{}let l=0;for(const m of o)i.add(n.id,{kind:"lesson",problem:m.text,tags:m.tags,constraints:m.metadata?JSON.stringify(m.metadata):void 0,whyItWorked:`Imported from ${m.source}`,outcome:"neutral",confidence:.6,needsReview:1}),l++;k(`Imported ${l} ${l===1?"memory":"memories"} from ${c} into ${d.cyan(n.name)} review queue. Run ${d.cyan("wyrm review")} to vet (tagged ${d.dim(`imported_from:${o[0]?.source??c}`)}).`)}finally{t.close()}}async function Et(p){const{flags:c}=x(p),s=c.project,a=R(),r=a.getDatabase();if(S("Wyrm Statistics"),s){const e=N(a,s);e||(f(`Project not found: ${s}`),a.close(),process.exit(1));const o=a.getProjectStats(e.id),t=[["Sessions",String(o.sessions)],["Quests (pending)",String(o.quests.pending)],["Quests (completed)",String(o.quests.completed)],["Data Points",String(o.dataPoints)]];console.log(D(["Metric","Value"],t))}else{const e=a.getStats(),o=[["Projects",String(e.projects)],["Sessions",String(e.sessions)],["Quests",String(e.quests)],["Data Points",String(e.dataPoints)],["DB Size",e.dbSize]],t=r.prepare("SELECT COUNT(*) as n FROM memory_artifacts").get().n,n=r.prepare("SELECT COUNT(*) as n FROM ground_truths WHERE is_current = 1").get().n,u=r.prepare("SELECT COUNT(*) as n FROM memory_artifacts WHERE needs_review = 1").get().n;let i=0;try{i=r.prepare("SELECT COUNT(*) as n FROM failure_patterns WHERE resolved = 0").get().n}catch{}o.push(["Memories",String(t)],["Ground Truths",String(n)]),u>0&&o.push([" \u21B3 in review queue",`${u} (invisible to recall \u2014 wyrm review)`]),i>0&&o.push(["Unresolved failures",`${i} (the firewall is armed against these)`]),console.log(D(["Metric","Value"],o))}a.close()}async function _t(p){const{flags:c}=x(p),s=c.project,a=R(),r=a.getDatabase();if(c.auto!==void 0){const{isAutoApproveOn:i,setAutoApprove:l}=await import("./memory-artifacts.js"),m=String(c.auto).toLowerCase();try{m==="on"||m==="1"||m==="true"?(l(r,!0),k("Auto-approve ON: new memories skip the review queue and are recall-visible immediately.")):m==="off"||m==="0"||m==="false"?(l(r,!1),k("Auto-approve OFF: risky writes queue for wyrm review again.")):m==="status"||m==="true"?console.log(` auto-approve: ${i(r)?"on":"off"}${process.env.WYRM_AUTO_APPROVE?" (WYRM_AUTO_APPROVE env)":""}`):(f("usage: wyrm review --auto on|off|status"),process.exitCode=1)}finally{a.close()}return}if(c["approve-all"]===!0){const i=r.prepare(`UPDATE memory_artifacts SET needs_review = 0
59
59
  WHERE needs_review = 1 AND source_trust IN ('operator', 'agent')`).run().changes,l=r.prepare(`SELECT COUNT(*) AS n FROM memory_artifacts
60
60
  WHERE needs_review = 1 AND source_trust NOT IN ('operator', 'agent')`).get().n;k(`Approved ${i} queued memor${i===1?"y":"ies"} (now recall-visible).`),l>0&&console.log(` ${l} left queued: imported/untrusted lanes need an explicit per-row review (wyrm review).`),a.close();return}let e=null;if(s){const i=N(a,s);i||(f(`Project not found: ${s}`),a.close(),process.exit(1)),e=i.id}const o=e?`AND project_id = ${e}`:"",t=r.prepare(`
61
61
  SELECT id, kind, problem FROM memory_artifacts
62
62
  WHERE needs_review = 1 ${o}
63
63
  ORDER BY created_at ASC
64
- `).all();if(t.length===0){console.log(d.dim(` No artifacts pending review${s?` for ${s}`:""}.`)),a.close();return}S(`Review Queue (${t.length} items)`);const n=oe({input:process.stdin,output:process.stdout}),p=i=>new Promise(l=>{n.question(i,l)});for(const i of t){console.log(`
65
- ${d.bold(`[${i.kind}] #${i.id}`)}`),console.log(d.dim("\u2500".repeat(60))),console.log(i.problem.slice(0,300)),console.log(d.dim("\u2500".repeat(60)));const m=(await p(`${d.cyan("[a]")}pprove / ${d.red("[r]")}eject / ${d.yellow("[s]")}kip? `)).trim().toLowerCase();m==="a"?(r.prepare("UPDATE memory_artifacts SET needs_review = 0, updated_at = datetime('now') WHERE id = ?").run(i.id),k(`Approved #${i.id}`)):m==="r"?(r.prepare("DELETE FROM memory_artifacts WHERE id = ?").run(i.id),console.log(`${K.cross} Rejected #${i.id}`)):console.log(d.dim(` Skipped #${i.id}`))}n.close(),a.close(),console.log(`
66
- Review complete.`)}async function St(u){const{positional:c,flags:s}=x(u),a=c[0];(!a||!["export","import","preview"].includes(a))&&(f("Usage: wyrm sync export --out <path> | wyrm sync import --from <path> | wyrm sync preview --from <path>"),process.exit(1));const{randomBytes:r,pbkdf2Sync:e,createCipheriv:o,createDecipheriv:t}=await import("crypto"),{readFileSync:n,writeFileSync:p,copyFileSync:i,unlinkSync:l,existsSync:m,chmodSync:g}=await import("fs"),{homedir:h}=await import("os"),{join:y}=await import("path"),w=(await import("better-sqlite3")).default;let v=process.env.WYRM_SYNC_PASSPHRASE??"";if(!v){const D=oe({input:process.stdin,output:process.stdout});v=await new Promise(P=>{process.stdout.write("Passphrase: "),process.stdin.isTTY&&process.stdin.setRawMode?.(!0),D.question("",V=>{process.stdin.isTTY&&process.stdin.setRawMode?.(!1),console.log(""),D.close(),P(V)})})}v||(f("Passphrase is required. Set WYRM_SYNC_PASSPHRASE or enter interactively."),process.exit(1));const $=y(h(),".wyrm"),b=R();if(a==="export"){const D=s.out;D||(f("--out <path> is required"),process.exit(1));const P=y($,"wyrm_cli_export_temp.db");try{const V=b.getDatabase();m(P)&&l(P),V.prepare("VACUUM INTO ?").run(P);const z=n(P),G=r(32),Q=r(16),Ie=e(v,G,6e5,32,"sha256"),ce=o("aes-256-gcm",Ie,Q),Oe=Buffer.concat([ce.update(z),ce.final()]),Pe=ce.getAuthTag(),Ae=Buffer.from("WYRM"),he=Buffer.alloc(1);he.writeUInt8(1,0);const we=Buffer.concat([Ae,he,G,Q,Pe,Oe]);p(D,we);try{g(D,384)}catch{}try{l(P)}catch{}const Le=(we.length/(1024*1024)).toFixed(2);k(`Exported to ${D} (${Le} MB)`)}catch(V){try{m(P)&&l(P)}catch{}f(`Export failed: ${V}`)}b.close();return}const C=s.from;C||(f("--from <path> is required"),process.exit(1));const E=n(C);E.subarray(0,4).toString("ascii")!=="WYRM"&&(f("Invalid Wyrm snapshot file."),process.exit(1));const j=E.readUInt8(4);j!==1&&(f(`Unsupported snapshot version: ${j}`),process.exit(1));const T=E.subarray(5,37),L=E.subarray(37,53),q=E.subarray(53,69),fe=E.subarray(69),ge=e(v,T,6e5,32,"sha256"),re=t("aes-256-gcm",ge,L);re.setAuthTag(q);let ne;try{ne=Buffer.concat([re.update(fe),re.final()])}catch{f("Decryption failed \u2014 wrong passphrase or corrupted file."),process.exit(1)}if(a==="preview"){const D=y($,"wyrm_cli_preview_temp.db");m(D)&&l(D),p(D,ne);try{const P=new w(D,{readonly:!0}),V=["projects","sessions","ground_truths","memory_artifacts","quests"];S("Snapshot Preview");const z=[];for(const G of V)try{const Q=P.prepare(`SELECT COUNT(*) as n FROM ${G}`).get();z.push([G,String(Q.n)])}catch{z.push([G,"?"])}console.log(I(["Table","Count"],z)),P.close()}catch(P){f(`Preview failed: ${P}`)}try{m(D)&&l(D)}catch{}b.close();return}const J=b.getDatabasePath(),ye=oe({input:process.stdin,output:process.stdout}),Me=await new Promise(D=>{ye.question("This will REPLACE your current database. Type CONFIRM to proceed: ",D)});if(ye.close(),Me.trim()!=="CONFIRM"){console.log(d.dim("Aborted.")),b.close();return}const De=new Date().toISOString().replace(/[:.]/g,"-"),ie=`${J}.backup.${De}`;i(J,ie),k(`Backed up to ${ie}`);const ae=y($,"wyrm_cli_restore_temp.db");p(ae,ne),b.getDatabase().close(),i(ae,J);for(const D of["-wal","-shm"])try{m(J+D)&&l(J+D)}catch{}try{l(ae)}catch{}k(`Restored from ${C}. Backup at ${ie}`)}async function Rt(u){const{flags:c}=x(u),s=c.project,a=c.path,r=se(c["min-confidence"],.3),e=O(c["older-than"],90),o=c["no-dry-run"]===!0,t=c.yes===!0,n=R(),p=n.getDatabase();let i=null;if(a||s){const y=a?n.getProject(a):N(n,s);y||(f(`Project not found: ${a??s}`),n.close(),process.exit(1)),i=y.id}const l=new B(p),{candidates:m}=l.pruneStale({projectId:i,minConfidence:r,olderThanDays:e,dryRun:!0});if(S(`Prune Candidates${o?" (LIVE DELETE)":" (dry-run)"}`),m.length===0){console.log(d.dim(" No artifacts match prune criteria.")),n.close();return}const g=m.map(y=>[String(y.id),y.kind,y.problem.slice(0,60),(y.confidence*100).toFixed(0)+"%",y.last_accessed_at??"never"]);if(console.log(I(["ID","Kind","Problem","Conf","Last Accessed"],g)),console.log(`
64
+ `).all();if(t.length===0){console.log(d.dim(` No artifacts pending review${s?` for ${s}`:""}.`)),a.close();return}S(`Review Queue (${t.length} items)`);const n=oe({input:process.stdin,output:process.stdout}),u=i=>new Promise(l=>{n.question(i,l)});for(const i of t){console.log(`
65
+ ${d.bold(`[${i.kind}] #${i.id}`)}`),console.log(d.dim("\u2500".repeat(60))),console.log(i.problem.slice(0,300)),console.log(d.dim("\u2500".repeat(60)));const m=(await u(`${d.cyan("[a]")}pprove / ${d.red("[r]")}eject / ${d.yellow("[s]")}kip? `)).trim().toLowerCase();m==="a"?(r.prepare("UPDATE memory_artifacts SET needs_review = 0, updated_at = datetime('now') WHERE id = ?").run(i.id),k(`Approved #${i.id}`)):m==="r"?(r.prepare("DELETE FROM memory_artifacts WHERE id = ?").run(i.id),console.log(`${K.cross} Rejected #${i.id}`)):console.log(d.dim(` Skipped #${i.id}`))}n.close(),a.close(),console.log(`
66
+ Review complete.`)}async function St(p){const{positional:c,flags:s}=x(p),a=c[0];(!a||!["export","import","preview"].includes(a))&&(f("Usage: wyrm sync export --out <path> | wyrm sync import --from <path> | wyrm sync preview --from <path>"),process.exit(1));const{randomBytes:r,pbkdf2Sync:e,createCipheriv:o,createDecipheriv:t}=await import("crypto"),{readFileSync:n,writeFileSync:u,copyFileSync:i,unlinkSync:l,existsSync:m,chmodSync:g}=await import("fs"),{homedir:h}=await import("os"),{join:y}=await import("path"),w=(await import("better-sqlite3")).default;let b=process.env.WYRM_SYNC_PASSPHRASE??"";if(!b){const I=oe({input:process.stdin,output:process.stdout});b=await new Promise(P=>{process.stdout.write("Passphrase: "),process.stdin.isTTY&&process.stdin.setRawMode?.(!0),I.question("",V=>{process.stdin.isTTY&&process.stdin.setRawMode?.(!1),console.log(""),I.close(),P(V)})})}b||(f("Passphrase is required. Set WYRM_SYNC_PASSPHRASE or enter interactively."),process.exit(1));const $=y(h(),".wyrm"),v=R();if(a==="export"){const I=s.out;I||(f("--out <path> is required"),process.exit(1));const P=y($,"wyrm_cli_export_temp.db");try{const V=v.getDatabase();m(P)&&l(P),V.prepare("VACUUM INTO ?").run(P);const z=n(P),G=r(32),Q=r(16),De=e(b,G,6e5,32,"sha256"),ce=o("aes-256-gcm",De,Q),Oe=Buffer.concat([ce.update(z),ce.final()]),Pe=ce.getAuthTag(),Ae=Buffer.from("WYRM"),he=Buffer.alloc(1);he.writeUInt8(1,0);const we=Buffer.concat([Ae,he,G,Q,Pe,Oe]);u(I,we);try{g(I,384)}catch{}try{l(P)}catch{}const Le=(we.length/(1024*1024)).toFixed(2);k(`Exported to ${I} (${Le} MB)`)}catch(V){try{m(P)&&l(P)}catch{}f(`Export failed: ${V}`)}v.close();return}const C=s.from;C||(f("--from <path> is required"),process.exit(1));const _=n(C);_.subarray(0,4).toString("ascii")!=="WYRM"&&(f("Invalid Wyrm snapshot file."),process.exit(1));const j=_.readUInt8(4);j!==1&&(f(`Unsupported snapshot version: ${j}`),process.exit(1));const T=_.subarray(5,37),L=_.subarray(37,53),q=_.subarray(53,69),fe=_.subarray(69),ge=e(b,T,6e5,32,"sha256"),re=t("aes-256-gcm",ge,L);re.setAuthTag(q);let ne;try{ne=Buffer.concat([re.update(fe),re.final()])}catch{f("Decryption failed \u2014 wrong passphrase or corrupted file."),process.exit(1)}if(a==="preview"){const I=y($,"wyrm_cli_preview_temp.db");m(I)&&l(I),u(I,ne);try{const P=new w(I,{readonly:!0}),V=["projects","sessions","ground_truths","memory_artifacts","quests"];S("Snapshot Preview");const z=[];for(const G of V)try{const Q=P.prepare(`SELECT COUNT(*) as n FROM ${G}`).get();z.push([G,String(Q.n)])}catch{z.push([G,"?"])}console.log(D(["Table","Count"],z)),P.close()}catch(P){f(`Preview failed: ${P}`)}try{m(I)&&l(I)}catch{}v.close();return}const J=v.getDatabasePath(),ye=oe({input:process.stdin,output:process.stdout}),Me=await new Promise(I=>{ye.question("This will REPLACE your current database. Type CONFIRM to proceed: ",I)});if(ye.close(),Me.trim()!=="CONFIRM"){console.log(d.dim("Aborted.")),v.close();return}const Ie=new Date().toISOString().replace(/[:.]/g,"-"),ie=`${J}.backup.${Ie}`;i(J,ie),k(`Backed up to ${ie}`);const ae=y($,"wyrm_cli_restore_temp.db");u(ae,ne),v.getDatabase().close(),i(ae,J);for(const I of["-wal","-shm"])try{m(J+I)&&l(J+I)}catch{}try{l(ae)}catch{}k(`Restored from ${C}. Backup at ${ie}`)}async function Rt(p){const{flags:c}=x(p),s=c.project,a=c.path,r=se(c["min-confidence"],.3),e=O(c["older-than"],90),o=c["no-dry-run"]===!0,t=c.yes===!0,n=R(),u=n.getDatabase();let i=null;if(a||s){const y=a?n.getProject(a):N(n,s);y||(f(`Project not found: ${a??s}`),n.close(),process.exit(1)),i=y.id}const l=new B(u),{candidates:m}=l.pruneStale({projectId:i,minConfidence:r,olderThanDays:e,dryRun:!0});if(S(`Prune Candidates${o?" (LIVE DELETE)":" (dry-run)"}`),m.length===0){console.log(d.dim(" No artifacts match prune criteria.")),n.close();return}const g=m.map(y=>[String(y.id),y.kind,y.problem.slice(0,60),(y.confidence*100).toFixed(0)+"%",y.last_accessed_at??"never"]);if(console.log(D(["ID","Kind","Problem","Conf","Last Accessed"],g)),console.log(`
67
67
  Total: ${m.length} candidate(s)`),!o){console.log(d.dim(`
68
- This is a dry run. Use --no-dry-run to delete (confirm each ID).`)),n.close();return}if(!t){const y=oe({input:process.stdin,output:process.stdout}),w=await new Promise(v=>{y.question(`
69
- Delete these ${m.length} artifact(s)? Type CONFIRM to proceed: `,v)});if(y.close(),w.trim()!=="CONFIRM"){console.log(d.dim("Aborted.")),n.close();return}}const h=l.deleteArtifacts(m.map(y=>y.id));k(`Deleted ${h} artifact(s).`),n.close()}async function Ct(u){const{positional:c,flags:s}=x(u),a=c[0]||"list",r=R();try{if(a==="list"||a==="ls"){const e=Se(r);if(e.length===0){S("Connectors"),console.log(`${F.dim} none configured. Add one with: wyrm connector add --name <n> --url <bridge-url> --workspace <w>${F.reset}`);return}S("Connectors");const o=e.map(t=>[t.name,t.source,t.enabled?"on":"off",t.workspace,Ht(t.baseUrl),t.allowlist.length?`${t.allowlist.length} chats`:"all"]);console.log(I(["Name","Source","Enabled","Workspace","Bridge","Allowlist"],o));return}if(a==="add"){const e=M(s.name),o=M(s.url),t=M(s.workspace);if(!e||!o||!t){f("usage: wyrm connector add --name <n> --url <bridge-url> --workspace <w> [--allowlist a,b] [--token T] [--disabled]"),process.exitCode=1;return}const n={name:e,source:"bridge",enabled:!s.disabled,baseUrl:o,workspace:t,allowlist:M(s.allowlist)?M(s.allowlist).split(",").map(p=>p.trim()).filter(Boolean):[],...M(s.token)?{authToken:M(s.token)}:{}};et(r,n),k(`Connector "${e}" saved (source=bridge, workspace=${t}). Token, if any, stored without being printed.`),console.log(`${F.dim} Tip: prefer the env var WYRM_CONNECTOR_TOKEN_${e.toUpperCase().replace(/[^A-Z0-9]/g,"_")} over --token.${F.reset}`);return}if(a==="sync"){const e=c[1]||M(s.name);if(e){const t=Se(r).find(p=>p.name===e);if(!t){f(`Connector not found: ${e}`),process.exitCode=1;return}const n=await tt(r,t);k(`${n.source}: fetched ${n.fetched}, ingested ${n.ingested}, skipped ${n.skipped} -> ${n.workspace}`);return}const o=await ot(r);if(o.length===0){console.log(`${F.dim} no enabled connectors.${F.reset}`);return}for(const t of o)"error"in t?f(`${t.name}: ${t.error}`):k(`${t.source}: fetched ${t.fetched}, ingested ${t.ingested}, skipped ${t.skipped} -> ${t.workspace}`);return}f(`Unknown connector subcommand: ${a} (use list | add | sync)`),process.exitCode=1}finally{r.close()}}function xe(){return H(0,"utf-8").trim()}async function xt(){const{initializeLicense:u,getLicenseInfo:c,getTier:s}=await import("./license.js");u();const{refreshRevocations:a}=await import("./revocations.js"),{refreshLicenseIfNeeded:r}=await import("./license-refresh.js");await a();const e=await r(),o=c();S("Wyrm License");const t=o.valid?"valid":o.error==="License revoked"?"REVOKED (running as free tier)":o.error&&/expired/i.test(o.error)?"EXPIRED (running as free tier)":o.key?`invalid (${o.error??"unknown reason"}) \u2014 running as free tier`:"free tier (no license key)",n=[["Tier",s()],["Status",t]];o.key&&(n.push(["Key",o.key]),n.push(["Issued to",o.issuedTo??"unknown"]),n.push(["Expires",o.expiresAt?new Date(o.expiresAt).toLocaleDateString():"never"])),n.push(["Features",o.features.join(", ")||"(free)"]),console.log(I(["Field","Value"],n)),!o.valid&&!o.key&&console.log(d.dim(`
68
+ This is a dry run. Use --no-dry-run to delete (confirm each ID).`)),n.close();return}if(!t){const y=oe({input:process.stdin,output:process.stdout}),w=await new Promise(b=>{y.question(`
69
+ Delete these ${m.length} artifact(s)? Type CONFIRM to proceed: `,b)});if(y.close(),w.trim()!=="CONFIRM"){console.log(d.dim("Aborted.")),n.close();return}}const h=l.deleteArtifacts(m.map(y=>y.id));k(`Deleted ${h} artifact(s).`),n.close()}async function Ct(p){const{positional:c,flags:s}=x(p),a=c[0]||"list",r=R();try{if(a==="list"||a==="ls"){const e=Se(r);if(e.length===0){S("Connectors"),console.log(`${F.dim} none configured. Add one with: wyrm connector add --name <n> --url <bridge-url> --workspace <w>${F.reset}`);return}S("Connectors");const o=e.map(t=>[t.name,t.source,t.enabled?"on":"off",t.workspace,Ht(t.baseUrl),t.allowlist.length?`${t.allowlist.length} chats`:"all"]);console.log(D(["Name","Source","Enabled","Workspace","Bridge","Allowlist"],o));return}if(a==="add"){const e=M(s.name),o=M(s.url),t=M(s.workspace);if(!e||!o||!t){f("usage: wyrm connector add --name <n> --url <bridge-url> --workspace <w> [--allowlist a,b] [--token T] [--disabled]"),process.exitCode=1;return}const n={name:e,source:"bridge",enabled:!s.disabled,baseUrl:o,workspace:t,allowlist:M(s.allowlist)?M(s.allowlist).split(",").map(u=>u.trim()).filter(Boolean):[],...M(s.token)?{authToken:M(s.token)}:{}};et(r,n),k(`Connector "${e}" saved (source=bridge, workspace=${t}). Token, if any, stored without being printed.`),console.log(`${F.dim} Tip: prefer the env var WYRM_CONNECTOR_TOKEN_${e.toUpperCase().replace(/[^A-Z0-9]/g,"_")} over --token.${F.reset}`);return}if(a==="sync"){const e=c[1]||M(s.name);if(e){const t=Se(r).find(u=>u.name===e);if(!t){f(`Connector not found: ${e}`),process.exitCode=1;return}const n=await tt(r,t);k(`${n.source}: fetched ${n.fetched}, ingested ${n.ingested}, skipped ${n.skipped} -> ${n.workspace}`);return}const o=await ot(r);if(o.length===0){console.log(`${F.dim} no enabled connectors.${F.reset}`);return}for(const t of o)"error"in t?f(`${t.name}: ${t.error}`):k(`${t.source}: fetched ${t.fetched}, ingested ${t.ingested}, skipped ${t.skipped} -> ${t.workspace}`);return}f(`Unknown connector subcommand: ${a} (use list | add | sync)`),process.exitCode=1}finally{r.close()}}function xe(){return H(0,"utf-8").trim()}async function xt(){const{initializeLicense:p,getLicenseInfo:c,getTier:s}=await import("./license.js");p();const{refreshRevocations:a}=await import("./revocations.js"),{refreshLicenseIfNeeded:r}=await import("./license-refresh.js");await a();const e=await r(),o=c();S("Wyrm License");const t=o.valid?"valid":o.error==="License revoked"?"REVOKED (running as free tier)":o.error&&/expired/i.test(o.error)?"EXPIRED (running as free tier)":o.key?`invalid (${o.error??"unknown reason"}) \u2014 running as free tier`:"free tier (no license key)",n=[["Tier",s()],["Status",t]];o.key&&(n.push(["Key",o.key]),n.push(["Issued to",o.issuedTo??"unknown"]),n.push(["Expires",o.expiresAt?new Date(o.expiresAt).toLocaleDateString():"never"])),n.push(["Features",o.features.join(", ")||"(free)"]),console.log(D(["Field","Value"],n)),!o.valid&&!o.key&&console.log(d.dim(`
70
70
  Activate with: wyrm login (free) \xB7 or: wyrm activate <license.json | key>`)),e==="refreshed"&&console.log(d.dim(`
71
71
  License refreshed automatically.`)),!o.valid&&o.error==="License revoked"&&console.log(d.dim(`
72
72
  This license has been revoked. Contact support if you believe this is a mistake.`)),console.log(d.dim(`
73
- \xA9 2026 Ghost Protocol (Pvt) Ltd \xB7 Proprietary \xB7 https://wyrm.ghosts.lk`)),console.log(d.dim(" Licensed under the Wyrm Terms of Service. No open-source license is granted."))}async function jt(){const u=(process.env.WYRM_ACCOUNT_URL??"https://account.ghosts.lk").replace(/\/$/,""),c=(()=>{try{return A().version??"unknown"}catch{return"unknown"}})();let s;try{const l=await fetch(`${u}/api/v1/cli/auth/start`,{method:"POST",headers:{"x-wyrm-version":c}});if(!l.ok)throw new Error(`HTTP ${l.status}`);s=await l.json()}catch(l){f(`Couldn't reach ${u} (${l instanceof Error?l.message:"network error"}).`),process.exitCode=1;return}const a=s.verification_uri_complete||s.verification_uri||`${u}/cli`;console.log(`
73
+ \xA9 2026 Ghost Protocol (Pvt) Ltd \xB7 Proprietary \xB7 https://wyrm.ghosts.lk`)),console.log(d.dim(" Licensed under the Wyrm Terms of Service. No open-source license is granted."))}async function jt(){const p=(process.env.WYRM_ACCOUNT_URL??"https://account.ghosts.lk").replace(/\/$/,""),c=(()=>{try{return A().version??"unknown"}catch{return"unknown"}})();let s;try{const l=await fetch(`${p}/api/v1/cli/auth/start`,{method:"POST",headers:{"x-wyrm-version":c}});if(!l.ok)throw new Error(`HTTP ${l.status}`);s=await l.json()}catch(l){f(`Couldn't reach ${p} (${l instanceof Error?l.message:"network error"}).`),process.exitCode=1;return}const a=s.verification_uri_complete||s.verification_uri||`${p}/cli`;console.log(`
74
74
  ${d.cyan("Sign in to activate Wyrm (free):")}`),console.log(` 1. Open ${d.cyan(a)}`),console.log(` 2. Approve the code ${d.cyan(s.user_code)}`),console.log(d.dim(`
75
- Waiting for approval\u2026 (Ctrl-C to cancel)`));const r=(s.interval??3)*1e3,e=Date.now()+(s.expires_in??600)*1e3;let o="";for(;Date.now()<e;){await new Promise(l=>{setTimeout(l,r)});try{const m=await(await fetch(`${u}/api/v1/cli/auth/poll`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({device_code:s.device_code})})).json();if(m.status==="approved"&&m.token){o=m.token;break}if(m.status==="denied"||m.error==="expired"){f("Login was denied or the code expired. Run `wyrm login` again."),process.exitCode=1;return}}catch{}}if(!o){f("Login timed out. Run `wyrm login` again."),process.exitCode=1;return}let t;try{const l=await fetch(`${u}/api/v1/license/free`,{method:"POST",headers:{authorization:`Bearer ${o}`,"x-wyrm-version":c}});if(!l.ok){const m=await l.json().catch(()=>({}));f(`Activation failed (${l.status}): ${m.hint||m.error||"unknown error"}`),process.exitCode=1;return}t=JSON.stringify(await l.json())}catch(l){f(`Activation request failed (${l instanceof Error?l.message:"network error"}).`),process.exitCode=1;return}const{saveAccountToken:n}=await import("./license-refresh.js");n(o);const{activateLicense:p}=await import("./license.js"),i=p(t);if(i.valid){k(`Signed in & activated \u2014 ${i.tier} tier (expires ${i.expiresAt??"never"}).`),console.log(d.dim(" Restart the Wyrm MCP server / daemon to apply."));const{refreshRevocations:l}=await import("./revocations.js");await l()}else f(`Activation failed: ${i.error??"unknown error"}`),process.exitCode=1}async function Tt(u){const{positional:c}=x(u),s=c[0];let a;s&&U(s)?a=H(s,"utf-8"):s?a=s:process.stdin.isTTY?(f("Usage: wyrm activate <license.json path | license JSON> (or pipe the JSON on stdin)"),process.exit(1)):a=xe();const{activateLicense:r}=await import("./license.js");try{const e=r(a);e.valid?(k(`License activated \u2014 ${e.tier} tier (${e.features.join(", ")})`),console.log(d.dim(" Restart Wyrm (MCP server / daemon) to apply all features."))):(f(`License activation failed: ${e.error??"unknown error"}`),process.exitCode=1)}catch{f("Invalid license format. Please verify your license key."),process.exitCode=1}}async function Nt(u){const{flags:c}=x(u),{runMaintenance:s}=await import("./maintenance.js"),{FailurePatterns:a}=await import("./failure-patterns.js"),{SessionSeen:r}=await import("./session-seen.js"),{AgentPresence:e}=await import("./presence.js"),o=R();try{const t=o.getDatabase(),n=O(c["archive-days"],0),p=s({db:o,sessionSeen:new r(t),failures:new a(t),presence:new e(t)},{vacuum:c.vacuum===!0,archiveDays:n>0?n:void 0});try{const{LAST_MAINTENANCE_KEY:i}=await import("./self-maintenance.js");t.prepare("INSERT INTO wyrm_meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").run(i,new Date().toISOString())}catch{}S("Maintenance complete");for(const i of p.lines)console.log(` - ${i}`);console.log(d.dim(`
76
- Database size: ${p.dbSize}`))}finally{o.close()}}async function Mt(u){const{positional:c,flags:s}=x(u),a=c[0]??"trend",r=A().version??null,e=R();try{const o=e.getDatabase();if(a==="snapshot"){const t=M(s.kind)||"all",n=[];if((t==="all"||t==="health")&&me(o,"health",rt(o),r)!=null&&n.push("health"),(t==="all"||t==="effectiveness")&&me(o,"effectiveness",nt(o),r)!=null&&n.push("effectiveness"),t==="retrieval"||s.full===!0){const p=await Ot();p&&me(o,"retrieval",p,r)!=null?n.push("retrieval"):p||console.log(d.dim(" retrieval: bench unavailable (run from the repo with bench/ present) \u2014 skipped"))}n.length?k(`metrics snapshot recorded: ${n.join(", ")} @ ${r??"unknown"}`):f("metrics snapshot: nothing recorded");return}if(a==="trend"){const t=M(s.kind),n=Number(M(s.limit))||14,p=t?[t]:["health","effectiveness","retrieval"];let i=!1;for(const l of p){const m=Re(o,l,n);if(!m.length)continue;i=!0,S(`${l} \u2014 ${m.length} snapshot(s)`);const g=Dt[l],h=m.slice().reverse().map(y=>[y.captured_at.slice(0,16),y.wyrm_version??"-",...g.map(w=>It(y.metrics[w]))]);console.log(I(["when","ver",...g],h));for(const y of it(m,l))console.log(" "+d.yellow("!")+" "+y)}i||(console.log(d.dim(" No metrics yet. Record the first snapshot: wyrm metrics snapshot")),console.log(d.dim(" (health + effectiveness are cheap; add --full for retrieval recall@k.)")));return}if(a==="show"){const t=M(s.kind)||"health",n=Re(o,t,1);if(!n.length){console.log(d.dim(` No ${t} snapshot yet.`));return}S(`latest ${t} @ ${n[0].captured_at} (${n[0].wyrm_version??"-"})`),console.log(JSON.stringify(n[0].metrics,null,2));return}f("Usage: wyrm metrics <snapshot [--kind all|health|effectiveness|retrieval] [--full] | trend [--kind K] [--limit N] | show [--kind K]>"),process.exitCode=1}finally{e.close()}}const Dt={health:["memories","vectors","vector_coverage_pct","review_queue","unresolved_failures","provider"],effectiveness:["failure_blocks","rehydrate_calls","recall_calls","tokens_saved_total"],retrieval:["recall_at_1","recall_at_10","mrr","mode","queries"]};function It(u){return u==null?"-":typeof u=="number"?Number.isInteger(u)?String(u):u.toFixed(3):String(u)}async function Ot(){try{const u=X(ee(import.meta.url)),c=W(u,"..","bench"),s=W(c,"lib","retrieval-eval.mjs"),a=W(c,"eval-corpus.json");if(!U(s)||!U(a))return null;const r=process.env.WYRM_VECTOR_PROVIDER||"none";process.env.WYRM_LIVE_MEMORY="0";const{evaluate:e}=await import(s),{WyrmDB:o}=await import("./database.js"),{MemoryArtifacts:t}=await import("./memory-artifacts.js"),n=JSON.parse(H(a,"utf8")),p=`/tmp/wyrm-metrics-eval-${process.pid}.db`,i=new o(p);let l;try{l=e(n,i,t,{k:10})}finally{i.close();for(const g of["","-wal","-shm"])try{Fe(p+g,{force:!0})}catch{}}const m=l.quality.overall;return{mode:r==="none"?"fts-floor":r,k:10,recall_at_1:m.recall_at_1??null,recall_at_10:m.recall_at_k??null,mrr:m.mrr??null,queries:m.n??l.performance?.queries??null,p50_ms:l.performance?.p50_ms??null,p95_ms:l.performance?.p95_ms??null}}catch{return null}}async function Pt(u){const c=console.error;console.error=()=>{};const s=R();let a=0;const r=(o,t)=>{console.log(` ${d.green("\u2714")} ${o} \u2014 ${d.dim(t)}`)},e=(o,t,n)=>{a++,console.log(` ${d.red("\u2716")} ${o} \u2014 ${t}`),console.log(` ${d.yellow("fix:")} ${n}`)};try{const o=s.getDatabase();S("wyrm doctor");const{tierForResolution:t}=await import("./providers/embedding-provider.js"),{effectiveEmbeddingState:n}=await import("./live-embedding-state.js"),p=n(o,process.cwd()),i=t(p),l=p.source==="server"?` \xB7 via running server (pid ${p.pid}${process.cwd()===p.cwd||process.cwd().startsWith(p.cwd.replace(/\/+$/,"")+"/")?"":`, ${p.cwd}`})`:"";p.reason===""&&p.resolved!=="local"?r("Embedding provider",`${p.resolved} (${p.model}) \xB7 tier ${i.n}/3 (${i.label})${p.egressHost?` \xB7 egress: ${p.egressHost}`:" \xB7 local"}${l}`):p.resolved==="local"?e("Embedding provider",`local-hash (hash-384) is NOT semantic \u2014 test-only vectors \xB7 tier ${i.n}/3 (${i.label})${l}`,p.fix||"use NVIDIA NIM: set WYRM_VECTOR_PROVIDER=nim + NVIDIA_API_KEY"):p.reason==="ollama_deprecated"?e("Embedding provider",`ollama (deprecated \u2014 leaves in the next major) \xB7 tier ${i.n}/3 (${i.label})${l}`,p.fix):e("Embedding provider",`none (${p.reason}) \xB7 tier ${i.n}/3 (${i.label})${l}`,p.fix),i.n===2&&console.log(` ${d.dim("next: wyrm upgrade \u2192 max recall (hosted NIM)")}`);const m=o.prepare("SELECT COUNT(*) AS n FROM memory_artifacts").get().n;if(p.resolved==="none"||p.resolved==="local"){let y=0;try{y=o.prepare("SELECT COUNT(*) AS n FROM vectors").get().n}catch{}e("Vector index",`${y} vector(s) / ${m} memories \u2014 recall is FTS5 keyword-only (~59.9% vs 72.2% hybrid recall@10)`,p.fix||"enable a provider, then: wyrm index rebuild")}else{let y=0;try{y=o.prepare("SELECT COUNT(DISTINCT content_id) AS n FROM vectors WHERE content_type = 'artifact' AND model = ?").get(p.model).n}catch{}if(y===0&&m>0)e("Vector index",`provider '${p.resolved}' is configured but 0 of ${m} memories are indexed`,"wyrm index rebuild");else{const w=m>0?Math.round(y/m*100):100;r("Vector index",`${y} of ${m} memories indexed under ${p.model} (${w}%)`),w<100&&console.log(` ${d.dim("next: wyrm index rebuild backfills the rest")}`)}}try{const y=o.prepare("SELECT COUNT(*) AS n FROM memory_artifacts_fts").get().n;y===m?r("FTS index",`${y}/${m} rows in sync`):e("FTS index",`${y} FTS rows vs ${m} memories \u2014 keyword recall is missing rows`,"INSERT INTO memory_artifacts_fts(memory_artifacts_fts) VALUES('rebuild') via wyrm maintenance")}catch{e("FTS index","memory_artifacts_fts unreadable","wyrm maintenance")}const g=o.prepare("SELECT COUNT(*) AS n FROM failure_patterns WHERE resolved = 0").get().n;r("Failure firewall",`${g} unresolved pattern(s) \xB7 matching = exact signature + FTS5 fuzzy (no vector tier in this build)`);const h=o.prepare("SELECT COUNT(*) AS n FROM memory_artifacts WHERE needs_review = 1").get().n;h>0?e("Review queue",`${h} memor${h===1?"y":"ies"} invisible to recall until approved`,"wyrm review"):r("Review queue","empty");try{const y=o.prepare("SELECT MAX(version) AS v FROM schema_versions").get().v;r("Schema",`migration v${y??0}`)}catch{e("Schema","schema_versions unreadable","wyrm maintenance")}try{const{lastMaintenanceAt:y}=await import("./self-maintenance.js"),w=y(o);if((process.env.WYRM_AUTO_MAINTENANCE??"1")==="0")e("Store upkeep","automatic maintenance is disabled (WYRM_AUTO_MAINTENANCE=0) \u2014 pruning and metabolize never run","unset WYRM_AUTO_MAINTENANCE, or schedule `wyrm maintenance` yourself");else if(!w)r("Store upkeep","automatic \u2014 first sweep runs on the next MCP server start");else{const $=(Date.now()-Date.parse(w))/36e5;$>72?e("Store upkeep",`last sweep ${Math.floor($/24)}d ago \u2014 the store is not being pruned`,"wyrm maintenance"):r("Store upkeep",`last sweep ${$<1?"under an hour":`${Math.floor($)}h`} ago (automatic, daily)`)}}catch{}try{const{wyrmMemoHookStatus:y}=await import("./autoconfig.js"),w=y(),v=(process.env.WYRM_MEMO_MODE??"observe").toLowerCase();if(!w.installed)r("Token firewall","not installed (optional) \u2014 `wyrm memo` records what you re-read in full");else{const $=o.prepare("SELECT COUNT(*) AS memos, COALESCE(SUM(hits),0) AS hits, COALESCE(SUM(tokens_avoided),0) AS tok FROM read_memos").get(),b=`${$.memos} memo(s), ${$.hits} repeat read(s), ~${$.tok} tokens`;v==="observe"&&$.hits>0?r("Token firewall",`${b} \u2014 observe mode, nothing withheld yet. Set WYRM_MEMO_MODE=warn to hand the map back instead`):r("Token firewall",`${v} mode \xB7 ${b}`)}}catch{}console.log(""),a===0?k("All checks passed \u2014 nothing is silently off."):(f(`${a} check(s) DEGRADED \u2014 the fixes above are exact.`),process.exitCode=1)}finally{s.close(),console.error=c}}const At=new Set(["nomic-embed-text","nomic-embed-text-v1.5-int8","nvidia/llama-nemotron-embed-1b-v2","nvidia/llama-3.2-nv-embedqa-1b-v2","nvidia/nv-embedqa-e5-v5","nvidia/nv-embedqa-mistral-7b-v2","nvidia/nv-embed-v1","text-embedding-3-small","text-embedding-3-large","hash-384","none"]);async function Lt(u){const{tierForResolution:c}=await import("./providers/embedding-provider.js"),{detectClients:s,loadWyrmMeta:a}=await import("./autoconfig.js"),r=l=>l.split(le()).join("~"),e=l=>l&&(At.has(l)?l:"custom"),o=A(),{effectiveEmbeddingState:t}=await import("./live-embedding-state.js"),n=console.error;console.error=()=>{};const p=R(),i=["## Wyrm Report",""];try{const l=p.getDatabase(),m=t(l,process.cwd()),g=c(m),h=b=>{try{return l.prepare(b).get().n}catch{return 0}};i.push(`version: ${o.version??"unknown"}`),i.push(`search tier: ${g.n}/3 (${g.label}) \xB7 provider: ${m.resolved}${m.model?` (${e(m.model)})`:""}${m.reason?` \xB7 reason: ${m.reason}`:""}${m.source==="server"?" \xB7 via running server":""}`),i.push(`corpus: ${h("SELECT COUNT(*) AS n FROM memory_artifacts")} memories \xB7 ${h("SELECT COUNT(*) AS n FROM vectors")} vectors \xB7 ${h("SELECT COUNT(*) AS n FROM ground_truths WHERE is_current = 1")} truths \xB7 ${h("SELECT COUNT(*) AS n FROM failure_patterns WHERE resolved = 0")} armed failures`);const y=l.prepare("SELECT date FROM sessions ORDER BY id DESC LIMIT 1").get();i.push(`last session: ${y?.date??"none yet"}`);const w=s().filter(b=>b.configured).map(b=>b.id);i.push(`clients: ${w.length?w.join(", "):"none configured"}`);const v=W(le(),".claude","hooks","wyrm-session-capture.mjs");i.push(`hooks: ${U(v)?"installed (Claude Code)":"not installed (model-driven capture on non-Claude-Code clients)"}`);const $=a();i.push(`setup: ${$?`clients ${$.configuredClients.join(", ")} (last setup ${$.lastSetup})`:"no setup metadata"}`),i.push(`db: ${r(Ce())}`)}finally{p.close(),console.error=n}console.log("```"),console.log(i.join(`
77
- `)),console.log("```")}async function Wt(u){const{positional:c,flags:s}=x(u),a=c[0]??"list",r=R();try{const e=r.getDatabase();if(a==="list"){const o=e.prepare("SELECT id, scope, target, occurrences, last_seen FROM failure_patterns WHERE resolved = 0 ORDER BY last_seen DESC LIMIT 25").all();if(S("Unresolved failures"),!o.length){console.log(d.dim(" none \u2014 the firewall has nothing active."));return}console.log(I(["ID","Scope","Target","Seen","Last seen"],o.map(t=>[String(t.id),t.scope,t.target.slice(0,48),String(t.occurrences),t.last_seen]))),console.log(d.dim(`
78
- resolve one: wyrm failure resolve <id>`));return}if(a==="resolve"){const o=Number(c[1]);if(!Number.isInteger(o)||o<=0){f('Usage: wyrm failure resolve <id> [--note "root cause fixed by ..."]'),process.exitCode=1;return}const t=M(s.note);(e.prepare("UPDATE failure_patterns SET resolved = 1, resolution_note = ?, last_seen = datetime('now') WHERE id = ? AND resolved = 0").run(t||"resolved via wyrm failure resolve",o).changes??0)>0?k(`Failure #${o} resolved \u2014 it will no longer flag.`):(f(`No unresolved failure #${o}.`),process.exitCode=1);return}f("Usage: wyrm failure <list|resolve <id> [--note N]>"),process.exitCode=1}finally{r.close()}}function je(u,c){const s=[],a=`--${c}`;for(let r=0;r<u.length;r++)if(u[r]===a&&r+1<u.length)for(const e of u[r+1].split(",")){const o=e.trim();if(o==="")continue;const t=Number(o);if(!Number.isInteger(t))throw new Error(`${a} must be a comma-separated list of integers (got "${o}")`);s.push(t)}return s}async function Ut(u,c){const{positional:s}=x(u),a="Usage: wyrm recall --propose <query> [--project <name>] [--run <id>] [--limit N]",r=typeof c.propose=="string"?c.propose:s[0];if(!r){f(a),process.exitCode=1;return}const e=M(c.limit);let o;if(e&&(o=Number(e),!Number.isInteger(o)||o<1)){f(`--limit must be a positive integer (got "${e}")`),process.exitCode=1;return}const{buildRecallProposal:t}=await import("./handlers/recall.js"),{FailurePatterns:n}=await import("./failure-patterns.js"),{sanitizeActorId:p}=await import("./handlers/boundary.js"),i=R();try{const l=M(c.project),m=l?N(i,l):null;if(l&&!m){f(`Project not found: ${l}`),process.exitCode=1;return}const g=M(c.run),h=p(g||process.env.WYRM_RUN_ID),y=i.getDatabase(),w=new B(y),v=new de(y),$=new n(y),b=await t({store:i,raw:()=>y,memory:w,truths:v,failures:$},{query:r,projectId:m?.id??null,runId:h,limit:o});S(`Recall proposal -- "${r}"`);const C=(E,j)=>{if(console.log(d.bold(`
79
- ${E} (${j.length})`)),j.length===0){console.log(d.dim(" (none)"));return}for(const T of j)console.log(` #${T.id} [${T.kind}/${T.outcome}] ${T.summary}`),console.log(d.dim(` conf ${(T.confidence*100).toFixed(0)}% | ${T.age_days}d old${T.project?` | ${T.project}`:""}`))};if(C("Prescriptions (muted by clean mode)",b.prescriptions),C("Firewall (never muted)",b.firewall),C("Ground truths",b.truths),b.prescriptions.length===0&&b.firewall.length===0&&b.truths.length===0){const E=Ye(y,m?.id??null);E>0&&(console.log(d.warning(`
80
- ${E} stored ${E===1?"memory is":"memories are"} held for review and excluded from recall.`)),console.log(d.dim(` Run \`wyrm review\` to approve ${E===1?"it":"them"}, or \`wyrm review --auto on\` to stop queueing.`)))}console.log(d.dim("\n Review-only -- use `wyrm recall --set` to mute/veto specific ids."))}finally{i.close()}}async function Ft(u){const{flags:c}=x(u);if(c.propose!==void 0)return Ut(u,c);const s="Usage: wyrm recall --set [--mode full|clean|custom] [--run <id>] [--mute <id>[,<id>...]] [--challenge <id>[,<id>...]] | wyrm recall --propose <query> [--project <name>] [--run <id>] [--limit N]";if(!c.set){f(s),process.exitCode=1;return}const{sanitizeActorId:a}=await import("./handlers/boundary.js"),{upsertRecallPolicy:r}=await import("./handlers/recall.js"),{RECALL_POLICY_MODES:e}=await import("./recall-policy.js"),o=M(c.run),t=a(o||process.env.WYRM_RUN_ID);if(!t){f("No run id: pass --run <id>, or export WYRM_RUN_ID (see `wyrm run start`)."),process.exitCode=1;return}let n;if(c.mode!==void 0){const m=M(c.mode);if(!e.includes(m)){f(`--mode must be one of: ${e.join(", ")} (got "${m}")`),process.exitCode=1;return}n=m}let p,i;try{p=je(u,"mute"),i=je(u,"challenge")}catch(m){f(m instanceof Error?m.message:String(m)),process.exitCode=1;return}const l=R();try{const m=r(l.getDatabase(),{runId:t,mode:n,muteIds:p.length?p:void 0,challengeTruthIds:i.length?i:void 0});k(`Recall policy for run ${t}: mode=${m.mode}`),console.log(d.dim(` Muted ids: ${m.mutedIds.length?m.mutedIds.join(", "):"(none)"}`)),console.log(d.dim(` Challenged truth ids: ${m.challengedTruthIds.length?m.challengedTruthIds.join(", "):"(none)"}`))}finally{l.close()}}async function qt(u){const{positional:c,flags:s}=x(u),a=c[0]??"list",r=R();try{if(a==="add"){const e=c[1],o=M(s.path);let t,n;if(o?(t=Z(o),n=e):e&&U(Z(e))?(t=Z(e),n=void 0):(t=Z(process.cwd()),n=e),!U(t)){f(`Path does not exist: ${t}`),process.exitCode=1;return}const p=M(s.name)||n||We(t),i=r.registerProject(p,t);k(`Project #${i.id} '${i.name}' registered at ${i.path}`);return}if(a==="list"){const e=r.getAllProjects(50);S("Projects"),console.log(I(["ID","Name","Path"],e.map(o=>[String(o.id),o.name,o.path])));return}f("Usage: wyrm project <add <name> [--path <dir>] | list>"),process.exitCode=1}finally{r.close()}}async function Yt(u){const{positional:c,flags:s}=x(u),a="Usage: wyrm index <setup|rebuild|status> [--provider auto|local|bundled|nim|openai|none] [--model M] [--project P] [--dry-run]";if(s.help===!0||typeof s.help=="string"){console.log(a);return}const r=c[0]??"status",{createVectorStore:e}=await import("./vectors.js"),t={provider:s.provider??process.env.WYRM_VECTOR_PROVIDER??"auto",model:s.model,apiKey:s["api-key"]??process.env.OPENAI_API_KEY,ollamaUrl:s["ollama-url"]};if(r==="setup"){const{createProvider:p}=await import("./providers/embedding-provider.js"),i=p(t);if(!await i.isReady()&&t.provider!=="none"){f(`Provider not ready: ${i.name}. Check the configuration and try again.`),process.exitCode=1;return}k(`Vector provider verified: ${i.name} (model ${i.model}, ${i.dimensions}d)`),console.log(d.dim(" The MCP server reads WYRM_VECTOR_PROVIDER (and provider-specific env) at boot \u2014")),console.log(d.dim(` set WYRM_VECTOR_PROVIDER=${i.name==="none"?"none":t.provider} in the server env, then: wyrm index rebuild`));return}const n=R();try{const p=n.getDatabase(),i=e(t,p);if(r==="status"){const l=i.getStats();S("Vector index");const m=[["Provider",l.provider],["Model",l.model],["Vectors",String(l.total)],...Object.entries(l.byType).map(([g,h])=>[` ${g}`,String(h)])];console.log(I(["Field","Value"],m));return}if(r==="rebuild"){const{reindexProjects:l}=await import("./reindex.js"),m=s["dry-run"]===!0,g=s.project;let h;if(g){const $=n.getProject(g)??N(n,g);if(!$){f(`Project not found: ${g}`),process.exitCode=1;return}h=[$.id]}else h=n.getAllProjects(1e3).map($=>$.id);const{indexed:y,skipped:w}=await l(p,i,h,{dryRun:m,onError:($,b)=>f(`${$}: ${JSON.stringify(b)}`)}),v=`Reindex ${m?"(dry run) ":""}\u2014 ${h.length} project(s), ${y} indexed, ${w} skipped`;if(y===0&&w>0){const{resolveEmbeddingState:$}=await import("./providers/embedding-provider.js"),b=$(t.provider);Ee(`${v}: ${b.reason||"embedding unavailable"}. fix: ${b.fix||"wyrm vectors download"}`)}else k(v);return}f(a),process.exitCode=1}finally{n.close()}}async function Vt(u){const{positional:c,flags:s}=x(u);if(s.help===!0||typeof s.help=="string"){console.log("Usage: wyrm vectors <download|status> [--force]");return}const a=c[0]??"status",{bundledModelPresent:r,downloadBundledModel:e,BUNDLED_MODEL_DIR:o,MANIFEST:t,BUNDLED_MODEL_SIZE_LABEL:n}=await import("./providers/bundled-model.js"),{resolveEmbeddingState:p,tierForResolution:i}=await import("./providers/embedding-provider.js");if(a==="status"){const y=console.error;console.error=()=>{};try{const{effectiveEmbeddingState:w}=await import("./live-embedding-state.js");let v=null,$=null;try{$=R(),v=$.getDatabase()}catch{v=null}const b=w(v,process.cwd());try{$?.close()}catch{}const C=i(b);S("wyrm vectors"),console.log(` provider: ${b.resolved}${b.model?` (${b.model})`:""}${b.source==="server"?` \xB7 via running server (pid ${b.pid})`:""}`),console.log(` tier: ${C.n}/3 (${C.label})`),console.log(` bundled model: ${r()?`present at ${o()}`:"not downloaded (run: wyrm vectors download)"}`)}finally{console.error=y}return}if(a!=="download"){f("Usage: wyrm vectors <download|status> [--force]"),process.exitCode=1;return}if(r()&&s.force!==!0){k("Bundled model already present.");return}S("Downloading bundled embedding model");const l=t.reduce((y,w)=>y+w.size,0);let m=-1;const g=await e({onProgress:(y,w,v)=>{const $=Math.floor(w/v*100);$!==m&&(m=$,process.stdout.write(`\r ${y}: ${$}% `))}});if(process.stdout.write(`
81
- `),!g.ok){f(`Download failed: ${g.error}`),console.log(" Recall stays keyword-only until this succeeds. Retry: wyrm vectors download"),process.exitCode=1;return}k(`Model ready (${n}). Vector recall is on.`);const{runBundledBackfill:h}=await import("./reindex.js");await h()}function M(u){return typeof u=="string"?u:""}function Ht(u){try{return new URL(u).host}catch{return u}}async function Bt(u){if(!process.stdin.isTTY){f("wyrm upgrade is interactive; run it in a terminal."),process.exitCode=1;return}const{tierForResolution:c}=await import("./providers/embedding-provider.js"),{effectiveEmbeddingState:s}=await import("./live-embedding-state.js");let a=null,r=null;try{r=R(),a=r.getDatabase()}catch{a=null}const e=s(a,process.cwd());try{r?.close()}catch{}const o=c(e);if(S("wyrm upgrade"),console.log(` Current: tier ${o.n}/3 (${o.label})${e.model?` \xB7 ${e.model}`:""}${e.source==="server"?` \xB7 via running server (pid ${e.pid})`:""}`),o.n===3){k("Already on the max-recall tier.");return}console.log(" Next: tier 3/3 (max recall), hosted NVIDIA NIM embeddings (2048d)."),console.log(" Published local hybrid row: 60.3/72.2 recall@5/@10; NIM lifts retrieval further (see BENCHMARKS.md)."),console.log(`
82
- 1. Get a free API key: ${d.cyan("https://build.nvidia.com")} (Get API Key on any embedding model page)`);let t;try{t=(await ze(" 2. Paste your key (nvapi-...): ")).trim()}catch(l){if(l instanceof Qe){console.log(""),process.exitCode=130;return}throw l}if(!t.startsWith("nvapi-")){f("That does not look like a NIM key (expected nvapi- prefix). Nothing written."),process.exitCode=1;return}process.stdout.write(" 3. Validating with a live embed call... ");const{NimProvider:n}=await import("./providers/embedding-provider.js");try{if(!(await new n(t).embed("wyrm connectivity probe")).length)throw new Error("empty embedding");console.log(d.green("ok"))}catch(l){console.log(d.red("failed")),f(`Key validation failed: ${l}. Nothing written.`),process.exitCode=1;return}const i=(await import("node:readline/promises")).createInterface({input:process.stdin,output:process.stdout});try{const{detectClients:l,applyWyrmEnvToClient:m}=await import("./autoconfig.js"),g=l().filter(v=>v.configured);if(!g.length){f("No configured clients found. Run wyrm-setup first."),process.exitCode=1;return}const h={WYRM_VECTOR_PROVIDER:"nim",WYRM_NIM_API_KEY:t};let y=0;for(const v of g){let $=!1;try{const C=(await import("node:fs")).readFileSync(v.configPath,"utf-8");$=/WYRM_VECTOR_PROVIDER/.test(C)}catch{}if($&&(await i.question(` ${v.name} already sets a provider. Overwrite with NIM? [y/N] `)).trim().toLowerCase()!=="y"){console.log(` ${d.dim("skipped")}`);continue}const b=m(v,h);b.action==="updated"&&y++,console.log(` ${b.action==="updated"?d.green("\u2714"):d.dim("\u25CB")} ${v.name}: ${b.message}`)}if(y===0&&(Ee("No client config was changed (all skipped or failed). The NIM tier is NOT active yet."),console.log(d.dim(" Re-run and answer y to overwrite, or set WYRM_VECTOR_PROVIDER=nim + WYRM_NIM_API_KEY in your client env by hand."))),(await i.question(" Reindex existing memories on the NIM tier now? [Y/n] ")).trim().toLowerCase()!=="n"){const v=R();try{const $=v.getDatabase(),{createVectorStore:b}=await import("./vectors.js"),{reindexProjects:C}=await import("./reindex.js"),E=b({provider:"nim",apiKey:t},$),j=v.getAllProjects(1e3).map(q=>q.id),{indexed:T,skipped:L}=await C($,E,j,{dryRun:!1});k(`Reindexed on NIM: ${T} indexed, ${L} skipped.`)}finally{v.close()}}else console.log(` ${d.dim("Skipped. New memories embed on NIM from now on; EXISTING memories stay findable by keyword and on their previous vector tier, but not with NIM-quality recall until you run: wyrm index rebuild")}`);y>0?k(`Upgrade complete (${y} client${y===1?"":"s"} updated). Restart your AI clients to pick up the new env.`):process.exitCode=1}finally{i.close()}}async function Kt(u){const{flags:c}=x(u),{getUpdateStatus:s}=await import("./version-check.js"),a=A().version??"0.0.0",r=R();let e;try{e=await s(r.getDatabase(),a,{force:c.force===!0||c.check===!0})}finally{r.close()}S("Wyrm update");const o=e.latest?e.lookupFailed?`${e.latest} (last known \u2014 registry unreachable now)`:e.latest:e.lookupFailed?"unknown \u2014 npm registry unreachable":"unknown (version check disabled)";if(console.log(I(["Field","Value"],[["Current",e.current],["Latest",o],["Update available",e.updateAvailable?"yes":e.lookupFailed&&!e.latest?"unknown":"no"],["Checked",`${e.checkedAt} (${e.source})`]])),e.lookupFailed&&!e.updateAvailable&&c.force!==!0){f('Could not reach the npm registry, so whether an update exists is UNKNOWN (not "up to date").'),console.log(d.dim(" Retry when online: wyrm update \xB7 or install directly: npm install -g wyrm-mcp@latest")),process.exitCode=2;return}if(c.check===!0)return;if(!e.updateAvailable&&c.force!==!0){console.log(d.dim(`
75
+ Waiting for approval\u2026 (Ctrl-C to cancel)`));const r=(s.interval??3)*1e3,e=Date.now()+(s.expires_in??600)*1e3;let o="";for(;Date.now()<e;){await new Promise(l=>{setTimeout(l,r)});try{const m=await(await fetch(`${p}/api/v1/cli/auth/poll`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({device_code:s.device_code})})).json();if(m.status==="approved"&&m.token){o=m.token;break}if(m.status==="denied"||m.error==="expired"){f("Login was denied or the code expired. Run `wyrm login` again."),process.exitCode=1;return}}catch{}}if(!o){f("Login timed out. Run `wyrm login` again."),process.exitCode=1;return}let t;try{const l=await fetch(`${p}/api/v1/license/free`,{method:"POST",headers:{authorization:`Bearer ${o}`,"x-wyrm-version":c}});if(!l.ok){const m=await l.json().catch(()=>({}));f(`Activation failed (${l.status}): ${m.hint||m.error||"unknown error"}`),process.exitCode=1;return}t=JSON.stringify(await l.json())}catch(l){f(`Activation request failed (${l instanceof Error?l.message:"network error"}).`),process.exitCode=1;return}const{saveAccountToken:n}=await import("./license-refresh.js");n(o);const{activateLicense:u}=await import("./license.js"),i=u(t);if(i.valid){k(`Signed in & activated \u2014 ${i.tier} tier (expires ${i.expiresAt??"never"}).`),console.log(d.dim(" Restart the Wyrm MCP server / daemon to apply."));const{refreshRevocations:l}=await import("./revocations.js");await l()}else f(`Activation failed: ${i.error??"unknown error"}`),process.exitCode=1}async function Tt(p){const{positional:c}=x(p),s=c[0];let a;s&&U(s)?a=H(s,"utf-8"):s?a=s:process.stdin.isTTY?(f("Usage: wyrm activate <license.json path | license JSON> (or pipe the JSON on stdin)"),process.exit(1)):a=xe();const{activateLicense:r}=await import("./license.js");try{const e=r(a);e.valid?(k(`License activated \u2014 ${e.tier} tier (${e.features.join(", ")})`),console.log(d.dim(" Restart Wyrm (MCP server / daemon) to apply all features."))):(f(`License activation failed: ${e.error??"unknown error"}`),process.exitCode=1)}catch{f("Invalid license format. Please verify your license key."),process.exitCode=1}}async function Nt(p){const{flags:c}=x(p),{runMaintenance:s}=await import("./maintenance.js"),{FailurePatterns:a}=await import("./failure-patterns.js"),{SessionSeen:r}=await import("./session-seen.js"),{AgentPresence:e}=await import("./presence.js"),o=R();try{const t=o.getDatabase(),n=O(c["archive-days"],0),u=s({db:o,sessionSeen:new r(t),failures:new a(t),presence:new e(t)},{vacuum:c.vacuum===!0,archiveDays:n>0?n:void 0});try{const{LAST_MAINTENANCE_KEY:i}=await import("./self-maintenance.js");t.prepare("INSERT INTO wyrm_meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").run(i,new Date().toISOString())}catch{}S("Maintenance complete");for(const i of u.lines)console.log(` - ${i}`);console.log(d.dim(`
76
+ Database size: ${u.dbSize}`))}finally{o.close()}}async function Mt(p){const{positional:c,flags:s}=x(p),a=c[0]??"trend",r=A().version??null,e=R();try{const o=e.getDatabase();if(a==="snapshot"){const t=M(s.kind)||"all",n=[];if((t==="all"||t==="health")&&me(o,"health",rt(o),r)!=null&&n.push("health"),(t==="all"||t==="effectiveness")&&me(o,"effectiveness",nt(o),r)!=null&&n.push("effectiveness"),t==="retrieval"||s.full===!0){const u=await Ot();u&&me(o,"retrieval",u,r)!=null?n.push("retrieval"):u||console.log(d.dim(" retrieval: bench unavailable (run from the repo with bench/ present) \u2014 skipped"))}n.length?k(`metrics snapshot recorded: ${n.join(", ")} @ ${r??"unknown"}`):f("metrics snapshot: nothing recorded");return}if(a==="trend"){const t=M(s.kind),n=Number(M(s.limit))||14,u=t?[t]:["health","effectiveness","retrieval"];let i=!1;for(const l of u){const m=Re(o,l,n);if(!m.length)continue;i=!0,S(`${l} \u2014 ${m.length} snapshot(s)`);const g=It[l],h=m.slice().reverse().map(y=>[y.captured_at.slice(0,16),y.wyrm_version??"-",...g.map(w=>Dt(y.metrics[w]))]);console.log(D(["when","ver",...g],h));for(const y of it(m,l))console.log(" "+d.yellow("!")+" "+y)}i||(console.log(d.dim(" No metrics yet. Record the first snapshot: wyrm metrics snapshot")),console.log(d.dim(" (health + effectiveness are cheap; add --full for retrieval recall@k.)")));return}if(a==="show"){const t=M(s.kind)||"health",n=Re(o,t,1);if(!n.length){console.log(d.dim(` No ${t} snapshot yet.`));return}S(`latest ${t} @ ${n[0].captured_at} (${n[0].wyrm_version??"-"})`),console.log(JSON.stringify(n[0].metrics,null,2));return}f("Usage: wyrm metrics <snapshot [--kind all|health|effectiveness|retrieval] [--full] | trend [--kind K] [--limit N] | show [--kind K]>"),process.exitCode=1}finally{e.close()}}const It={health:["memories","vectors","vector_coverage_pct","review_queue","unresolved_failures","provider"],effectiveness:["failure_blocks","rehydrate_calls","recall_calls","tokens_saved_total"],retrieval:["recall_at_1","recall_at_10","mrr","mode","queries"]};function Dt(p){return p==null?"-":typeof p=="number"?Number.isInteger(p)?String(p):p.toFixed(3):String(p)}async function Ot(){try{const p=X(ee(import.meta.url)),c=W(p,"..","bench"),s=W(c,"lib","retrieval-eval.mjs"),a=W(c,"eval-corpus.json");if(!U(s)||!U(a))return null;const r=process.env.WYRM_VECTOR_PROVIDER||"none";process.env.WYRM_LIVE_MEMORY="0";const{evaluate:e}=await import(s),{WyrmDB:o}=await import("./database.js"),{MemoryArtifacts:t}=await import("./memory-artifacts.js"),n=JSON.parse(H(a,"utf8")),u=`/tmp/wyrm-metrics-eval-${process.pid}.db`,i=new o(u);let l;try{l=e(n,i,t,{k:10})}finally{i.close();for(const g of["","-wal","-shm"])try{Fe(u+g,{force:!0})}catch{}}const m=l.quality.overall;return{mode:r==="none"?"fts-floor":r,k:10,recall_at_1:m.recall_at_1??null,recall_at_10:m.recall_at_k??null,mrr:m.mrr??null,queries:m.n??l.performance?.queries??null,p50_ms:l.performance?.p50_ms??null,p95_ms:l.performance?.p95_ms??null}}catch{return null}}async function Pt(p){const c=console.error;console.error=()=>{};const s=R();let a=0;const r=(o,t)=>{console.log(` ${d.green("\u2714")} ${o} \u2014 ${d.dim(t)}`)},e=(o,t,n)=>{a++,console.log(` ${d.red("\u2716")} ${o} \u2014 ${t}`),console.log(` ${d.yellow("fix:")} ${n}`)};try{const o=s.getDatabase();S("wyrm doctor");const{tierForResolution:t}=await import("./providers/embedding-provider.js"),{effectiveEmbeddingState:n}=await import("./live-embedding-state.js"),u=n(o,process.cwd()),i=t(u),l=u.source==="server"?` \xB7 via running server (pid ${u.pid}${process.cwd()===u.cwd||process.cwd().startsWith(u.cwd.replace(/\/+$/,"")+"/")?"":`, ${u.cwd}`})`:"";u.reason===""&&u.resolved!=="local"?r("Embedding provider",`${u.resolved} (${u.model}) \xB7 tier ${i.n}/3 (${i.label})${u.egressHost?` \xB7 egress: ${u.egressHost}`:" \xB7 local"}${l}`):u.resolved==="local"?e("Embedding provider",`local-hash (hash-384) is NOT semantic \u2014 test-only vectors \xB7 tier ${i.n}/3 (${i.label})${l}`,u.fix||"use NVIDIA NIM: set WYRM_VECTOR_PROVIDER=nim + NVIDIA_API_KEY"):u.reason==="ollama_deprecated"?e("Embedding provider",`ollama (deprecated \u2014 leaves in the next major) \xB7 tier ${i.n}/3 (${i.label})${l}`,u.fix):e("Embedding provider",`none (${u.reason}) \xB7 tier ${i.n}/3 (${i.label})${l}`,u.fix),i.n===2&&console.log(` ${d.dim("next: wyrm upgrade \u2192 max recall (hosted NIM)")}`);const m=o.prepare("SELECT COUNT(*) AS n FROM memory_artifacts").get().n;if(u.resolved==="none"||u.resolved==="local"){let y=0;try{y=o.prepare("SELECT COUNT(*) AS n FROM vectors").get().n}catch{}e("Vector index",`${y} vector(s) / ${m} memories \u2014 recall is FTS5 keyword-only (~59.9% vs 72.2% hybrid recall@10)`,u.fix||"enable a provider, then: wyrm index rebuild")}else{let y=0;try{y=o.prepare("SELECT COUNT(DISTINCT content_id) AS n FROM vectors WHERE content_type = 'artifact' AND model = ?").get(u.model).n}catch{}if(y===0&&m>0)e("Vector index",`provider '${u.resolved}' is configured but 0 of ${m} memories are indexed`,"wyrm index rebuild");else{const w=m>0?Math.round(y/m*100):100;let b;try{b=o.prepare("SELECT model, COUNT(DISTINCT content_id) AS n FROM vectors WHERE content_type = 'artifact' AND model <> ? GROUP BY model ORDER BY n DESC LIMIT 1").get(u.model)}catch{}b&&b.n>y?e("Vector index",`${y} of ${m} indexed under the ACTIVE model ${u.model} (${w}%), but ${b.n} are indexed under ${b.model} \u2014 the embedding provider changed and semantic recall cannot see those`,"restore the previous provider (check the MCP server env: WYRM_VECTOR_PROVIDER + its API key), or re-embed everything: wyrm index rebuild"):m>=50&&w<25?e("Vector index",`only ${y} of ${m} memories indexed under ${u.model} (${w}%) \u2014 most of the store is invisible to semantic recall`,"wyrm index rebuild"):r("Vector index",`${y} of ${m} memories indexed under ${u.model} (${w}%)`),w<100&&!b&&!(m>=50&&w<25)&&console.log(` ${d.dim("next: wyrm index rebuild backfills the rest")}`)}}try{const y=o.prepare("SELECT COUNT(*) AS n FROM memory_artifacts_fts").get().n;y===m?r("FTS index",`${y}/${m} rows in sync`):e("FTS index",`${y} FTS rows vs ${m} memories \u2014 keyword recall is missing rows`,"INSERT INTO memory_artifacts_fts(memory_artifacts_fts) VALUES('rebuild') via wyrm maintenance")}catch{e("FTS index","memory_artifacts_fts unreadable","wyrm maintenance")}const g=o.prepare("SELECT COUNT(*) AS n FROM failure_patterns WHERE resolved = 0").get().n;r("Failure firewall",`${g} unresolved pattern(s) \xB7 matching = exact signature + FTS5 fuzzy (no vector tier in this build)`);const h=o.prepare("SELECT COUNT(*) AS n FROM memory_artifacts WHERE needs_review = 1").get().n;h>0?e("Review queue",`${h} memor${h===1?"y":"ies"} invisible to recall until approved`,"wyrm review"):r("Review queue","empty");try{const y=o.prepare("SELECT MAX(version) AS v FROM schema_versions").get().v;r("Schema",`migration v${y??0}`)}catch{e("Schema","schema_versions unreadable","wyrm maintenance")}try{const{lastMaintenanceAt:y}=await import("./self-maintenance.js"),w=y(o);if((process.env.WYRM_AUTO_MAINTENANCE??"1")==="0")e("Store upkeep","automatic maintenance is disabled (WYRM_AUTO_MAINTENANCE=0) \u2014 pruning and metabolize never run","unset WYRM_AUTO_MAINTENANCE, or schedule `wyrm maintenance` yourself");else if(!w)r("Store upkeep","automatic \u2014 first sweep runs on the next MCP server start");else{const $=(Date.now()-Date.parse(w))/36e5;$>72?e("Store upkeep",`last sweep ${Math.floor($/24)}d ago \u2014 the store is not being pruned`,"wyrm maintenance"):r("Store upkeep",`last sweep ${$<1?"under an hour":`${Math.floor($)}h`} ago (automatic, daily)`)}}catch{}try{const{wyrmMemoHookStatus:y}=await import("./autoconfig.js"),w=y(),b=(process.env.WYRM_MEMO_MODE??"observe").toLowerCase();if(!w.installed)r("Token firewall","not installed (optional) \u2014 `wyrm memo` records what you re-read in full");else{const $=o.prepare("SELECT COUNT(*) AS memos, COALESCE(SUM(hits),0) AS hits, COALESCE(SUM(tokens_avoided),0) AS tok FROM read_memos").get(),v=`${$.memos} memo(s), ${$.hits} repeat read(s), ~${$.tok} tokens`;b==="observe"&&$.hits>0?r("Token firewall",`${v} \u2014 observe mode, nothing withheld yet. Set WYRM_MEMO_MODE=warn to hand the map back instead`):r("Token firewall",`${b} mode \xB7 ${v}`)}}catch{}console.log(""),a===0?k("All checks passed \u2014 nothing is silently off."):(f(`${a} check(s) DEGRADED \u2014 the fixes above are exact.`),process.exitCode=1)}finally{s.close(),console.error=c}}const At=new Set(["nomic-embed-text","nomic-embed-text-v1.5-int8","nvidia/llama-nemotron-embed-1b-v2","nvidia/llama-3.2-nv-embedqa-1b-v2","nvidia/nv-embedqa-e5-v5","nvidia/nv-embedqa-mistral-7b-v2","nvidia/nv-embed-v1","text-embedding-3-small","text-embedding-3-large","hash-384","none"]);async function Lt(p){const{tierForResolution:c}=await import("./providers/embedding-provider.js"),{detectClients:s,loadWyrmMeta:a}=await import("./autoconfig.js"),r=l=>l.split(le()).join("~"),e=l=>l&&(At.has(l)?l:"custom"),o=A(),{effectiveEmbeddingState:t}=await import("./live-embedding-state.js"),n=console.error;console.error=()=>{};const u=R(),i=["## Wyrm Report",""];try{const l=u.getDatabase(),m=t(l,process.cwd()),g=c(m),h=v=>{try{return l.prepare(v).get().n}catch{return 0}};i.push(`version: ${o.version??"unknown"}`),i.push(`search tier: ${g.n}/3 (${g.label}) \xB7 provider: ${m.resolved}${m.model?` (${e(m.model)})`:""}${m.reason?` \xB7 reason: ${m.reason}`:""}${m.source==="server"?" \xB7 via running server":""}`),i.push(`corpus: ${h("SELECT COUNT(*) AS n FROM memory_artifacts")} memories \xB7 ${h("SELECT COUNT(*) AS n FROM vectors")} vectors \xB7 ${h("SELECT COUNT(*) AS n FROM ground_truths WHERE is_current = 1")} truths \xB7 ${h("SELECT COUNT(*) AS n FROM failure_patterns WHERE resolved = 0")} armed failures`);const y=l.prepare("SELECT date FROM sessions ORDER BY id DESC LIMIT 1").get();i.push(`last session: ${y?.date??"none yet"}`);const w=s().filter(v=>v.configured).map(v=>v.id);i.push(`clients: ${w.length?w.join(", "):"none configured"}`);const b=W(le(),".claude","hooks","wyrm-session-capture.mjs");i.push(`hooks: ${U(b)?"installed (Claude Code)":"not installed (model-driven capture on non-Claude-Code clients)"}`);const $=a();i.push(`setup: ${$?`clients ${$.configuredClients.join(", ")} (last setup ${$.lastSetup})`:"no setup metadata"}`),i.push(`db: ${r(Ce())}`)}finally{u.close(),console.error=n}console.log("```"),console.log(i.join(`
77
+ `)),console.log("```")}async function Wt(p){const{positional:c,flags:s}=x(p),a=c[0]??"list",r=R();try{const e=r.getDatabase();if(a==="list"){const o=e.prepare("SELECT id, scope, target, occurrences, last_seen FROM failure_patterns WHERE resolved = 0 ORDER BY last_seen DESC LIMIT 25").all();if(S("Unresolved failures"),!o.length){console.log(d.dim(" none \u2014 the firewall has nothing active."));return}console.log(D(["ID","Scope","Target","Seen","Last seen"],o.map(t=>[String(t.id),t.scope,t.target.slice(0,48),String(t.occurrences),t.last_seen]))),console.log(d.dim(`
78
+ resolve one: wyrm failure resolve <id>`));return}if(a==="resolve"){const o=Number(c[1]);if(!Number.isInteger(o)||o<=0){f('Usage: wyrm failure resolve <id> [--note "root cause fixed by ..."]'),process.exitCode=1;return}const t=M(s.note);(e.prepare("UPDATE failure_patterns SET resolved = 1, resolution_note = ?, last_seen = datetime('now') WHERE id = ? AND resolved = 0").run(t||"resolved via wyrm failure resolve",o).changes??0)>0?k(`Failure #${o} resolved \u2014 it will no longer flag.`):(f(`No unresolved failure #${o}.`),process.exitCode=1);return}f("Usage: wyrm failure <list|resolve <id> [--note N]>"),process.exitCode=1}finally{r.close()}}function je(p,c){const s=[],a=`--${c}`;for(let r=0;r<p.length;r++)if(p[r]===a&&r+1<p.length)for(const e of p[r+1].split(",")){const o=e.trim();if(o==="")continue;const t=Number(o);if(!Number.isInteger(t))throw new Error(`${a} must be a comma-separated list of integers (got "${o}")`);s.push(t)}return s}async function Ut(p,c){const{positional:s}=x(p),a="Usage: wyrm recall --propose <query> [--project <name>] [--run <id>] [--limit N]",r=typeof c.propose=="string"?c.propose:s[0];if(!r){f(a),process.exitCode=1;return}const e=M(c.limit);let o;if(e&&(o=Number(e),!Number.isInteger(o)||o<1)){f(`--limit must be a positive integer (got "${e}")`),process.exitCode=1;return}const{buildRecallProposal:t}=await import("./handlers/recall.js"),{FailurePatterns:n}=await import("./failure-patterns.js"),{sanitizeActorId:u}=await import("./handlers/boundary.js"),i=R();try{const l=M(c.project),m=l?N(i,l):null;if(l&&!m){f(`Project not found: ${l}`),process.exitCode=1;return}const g=M(c.run),h=u(g||process.env.WYRM_RUN_ID),y=i.getDatabase(),w=new B(y),b=new de(y),$=new n(y),v=await t({store:i,raw:()=>y,memory:w,truths:b,failures:$},{query:r,projectId:m?.id??null,runId:h,limit:o});S(`Recall proposal -- "${r}"`);const C=(_,j)=>{if(console.log(d.bold(`
79
+ ${_} (${j.length})`)),j.length===0){console.log(d.dim(" (none)"));return}for(const T of j)console.log(` #${T.id} [${T.kind}/${T.outcome}] ${T.summary}`),console.log(d.dim(` conf ${(T.confidence*100).toFixed(0)}% | ${T.age_days}d old${T.project?` | ${T.project}`:""}`))};if(C("Prescriptions (muted by clean mode)",v.prescriptions),C("Firewall (never muted)",v.firewall),C("Ground truths",v.truths),v.prescriptions.length===0&&v.firewall.length===0&&v.truths.length===0){const _=Ye(y,m?.id??null);_>0&&(console.log(d.warning(`
80
+ ${_} stored ${_===1?"memory is":"memories are"} held for review and excluded from recall.`)),console.log(d.dim(` Run \`wyrm review\` to approve ${_===1?"it":"them"}, or \`wyrm review --auto on\` to stop queueing.`)))}console.log(d.dim("\n Review-only -- use `wyrm recall --set` to mute/veto specific ids."))}finally{i.close()}}async function Ft(p){const{flags:c}=x(p);if(c.propose!==void 0)return Ut(p,c);const s="Usage: wyrm recall --set [--mode full|clean|custom] [--run <id>] [--mute <id>[,<id>...]] [--challenge <id>[,<id>...]] | wyrm recall --propose <query> [--project <name>] [--run <id>] [--limit N]";if(!c.set){f(s),process.exitCode=1;return}const{sanitizeActorId:a}=await import("./handlers/boundary.js"),{upsertRecallPolicy:r}=await import("./handlers/recall.js"),{RECALL_POLICY_MODES:e}=await import("./recall-policy.js"),o=M(c.run),t=a(o||process.env.WYRM_RUN_ID);if(!t){f("No run id: pass --run <id>, or export WYRM_RUN_ID (see `wyrm run start`)."),process.exitCode=1;return}let n;if(c.mode!==void 0){const m=M(c.mode);if(!e.includes(m)){f(`--mode must be one of: ${e.join(", ")} (got "${m}")`),process.exitCode=1;return}n=m}let u,i;try{u=je(p,"mute"),i=je(p,"challenge")}catch(m){f(m instanceof Error?m.message:String(m)),process.exitCode=1;return}const l=R();try{const m=r(l.getDatabase(),{runId:t,mode:n,muteIds:u.length?u:void 0,challengeTruthIds:i.length?i:void 0});k(`Recall policy for run ${t}: mode=${m.mode}`),console.log(d.dim(` Muted ids: ${m.mutedIds.length?m.mutedIds.join(", "):"(none)"}`)),console.log(d.dim(` Challenged truth ids: ${m.challengedTruthIds.length?m.challengedTruthIds.join(", "):"(none)"}`))}finally{l.close()}}async function qt(p){const{positional:c,flags:s}=x(p),a=c[0]??"list",r=R();try{if(a==="add"){const e=c[1],o=M(s.path);let t,n;if(o?(t=Z(o),n=e):e&&U(Z(e))?(t=Z(e),n=void 0):(t=Z(process.cwd()),n=e),!U(t)){f(`Path does not exist: ${t}`),process.exitCode=1;return}const u=M(s.name)||n||We(t),i=r.registerProject(u,t);k(`Project #${i.id} '${i.name}' registered at ${i.path}`);return}if(a==="list"){const e=r.getAllProjects(50);S("Projects"),console.log(D(["ID","Name","Path"],e.map(o=>[String(o.id),o.name,o.path])));return}f("Usage: wyrm project <add <name> [--path <dir>] | list>"),process.exitCode=1}finally{r.close()}}async function Yt(p){const{positional:c,flags:s}=x(p),a="Usage: wyrm index <setup|rebuild|status> [--provider auto|local|bundled|nim|openai|none] [--model M] [--project P] [--dry-run]";if(s.help===!0||typeof s.help=="string"){console.log(a);return}const r=c[0]??"status",{createVectorStore:e}=await import("./vectors.js"),t={provider:s.provider??process.env.WYRM_VECTOR_PROVIDER??"auto",model:s.model,apiKey:s["api-key"]??process.env.OPENAI_API_KEY,ollamaUrl:s["ollama-url"]};if(r==="setup"){const{createProvider:u}=await import("./providers/embedding-provider.js"),i=u(t);if(!await i.isReady()&&t.provider!=="none"){f(`Provider not ready: ${i.name}. Check the configuration and try again.`),process.exitCode=1;return}k(`Vector provider verified: ${i.name} (model ${i.model}, ${i.dimensions}d)`),console.log(d.dim(" The MCP server reads WYRM_VECTOR_PROVIDER (and provider-specific env) at boot \u2014")),console.log(d.dim(` set WYRM_VECTOR_PROVIDER=${i.name==="none"?"none":t.provider} in the server env, then: wyrm index rebuild`));return}const n=R();try{const u=n.getDatabase(),i=e(t,u);if(r==="status"){const l=i.getStats();S("Vector index");const m=[["Provider",l.provider],["Model",l.model],["Vectors",String(l.total)],...Object.entries(l.byType).map(([g,h])=>[` ${g}`,String(h)])];console.log(D(["Field","Value"],m));return}if(r==="rebuild"){const{reindexProjects:l}=await import("./reindex.js"),m=s["dry-run"]===!0,g=s.project;let h;if(g){const $=n.getProject(g)??N(n,g);if(!$){f(`Project not found: ${g}`),process.exitCode=1;return}h=[$.id]}else h=n.getAllProjects(1e3).map($=>$.id);const{indexed:y,skipped:w}=await l(u,i,h,{dryRun:m,onError:($,v)=>f(`${$}: ${JSON.stringify(v)}`)}),b=`Reindex ${m?"(dry run) ":""}\u2014 ${h.length} project(s), ${y} indexed, ${w} skipped`;if(y===0&&w>0){const{resolveEmbeddingState:$}=await import("./providers/embedding-provider.js"),v=$(t.provider);_e(`${b}: ${v.reason||"embedding unavailable"}. fix: ${v.fix||"wyrm vectors download"}`)}else k(b);return}f(a),process.exitCode=1}finally{n.close()}}async function Vt(p){const{positional:c,flags:s}=x(p);if(s.help===!0||typeof s.help=="string"){console.log("Usage: wyrm vectors <download|status> [--force]");return}const a=c[0]??"status",{bundledModelPresent:r,downloadBundledModel:e,BUNDLED_MODEL_DIR:o,MANIFEST:t,BUNDLED_MODEL_SIZE_LABEL:n}=await import("./providers/bundled-model.js"),{resolveEmbeddingState:u,tierForResolution:i}=await import("./providers/embedding-provider.js");if(a==="status"){const y=console.error;console.error=()=>{};try{const{effectiveEmbeddingState:w}=await import("./live-embedding-state.js");let b=null,$=null;try{$=R(),b=$.getDatabase()}catch{b=null}const v=w(b,process.cwd());try{$?.close()}catch{}const C=i(v);S("wyrm vectors"),console.log(` provider: ${v.resolved}${v.model?` (${v.model})`:""}${v.source==="server"?` \xB7 via running server (pid ${v.pid})`:""}`),console.log(` tier: ${C.n}/3 (${C.label})`),console.log(` bundled model: ${r()?`present at ${o()}`:"not downloaded (run: wyrm vectors download)"}`)}finally{console.error=y}return}if(a!=="download"){f("Usage: wyrm vectors <download|status> [--force]"),process.exitCode=1;return}if(r()&&s.force!==!0){k("Bundled model already present.");return}S("Downloading bundled embedding model");const l=t.reduce((y,w)=>y+w.size,0);let m=-1;const g=await e({onProgress:(y,w,b)=>{const $=Math.floor(w/b*100);$!==m&&(m=$,process.stdout.write(`\r ${y}: ${$}% `))}});if(process.stdout.write(`
81
+ `),!g.ok){f(`Download failed: ${g.error}`),console.log(" Recall stays keyword-only until this succeeds. Retry: wyrm vectors download"),process.exitCode=1;return}k(`Model ready (${n}). Vector recall is on.`);const{runBundledBackfill:h}=await import("./reindex.js");await h()}function M(p){return typeof p=="string"?p:""}function Ht(p){try{return new URL(p).host}catch{return p}}async function Bt(p){if(!process.stdin.isTTY){f("wyrm upgrade is interactive; run it in a terminal."),process.exitCode=1;return}const{tierForResolution:c}=await import("./providers/embedding-provider.js"),{effectiveEmbeddingState:s}=await import("./live-embedding-state.js");let a=null,r=null;try{r=R(),a=r.getDatabase()}catch{a=null}const e=s(a,process.cwd());try{r?.close()}catch{}const o=c(e);if(S("wyrm upgrade"),console.log(` Current: tier ${o.n}/3 (${o.label})${e.model?` \xB7 ${e.model}`:""}${e.source==="server"?` \xB7 via running server (pid ${e.pid})`:""}`),o.n===3){k("Already on the max-recall tier.");return}console.log(" Next: tier 3/3 (max recall), hosted NVIDIA NIM embeddings (2048d)."),console.log(" Published local hybrid row: 60.3/72.2 recall@5/@10; NIM lifts retrieval further (see BENCHMARKS.md)."),console.log(`
82
+ 1. Get a free API key: ${d.cyan("https://build.nvidia.com")} (Get API Key on any embedding model page)`);let t;try{t=(await ze(" 2. Paste your key (nvapi-...): ")).trim()}catch(l){if(l instanceof Qe){console.log(""),process.exitCode=130;return}throw l}if(!t.startsWith("nvapi-")){f("That does not look like a NIM key (expected nvapi- prefix). Nothing written."),process.exitCode=1;return}process.stdout.write(" 3. Validating with a live embed call... ");const{NimProvider:n}=await import("./providers/embedding-provider.js");try{if(!(await new n(t).embed("wyrm connectivity probe")).length)throw new Error("empty embedding");console.log(d.green("ok"))}catch(l){console.log(d.red("failed")),f(`Key validation failed: ${l}. Nothing written.`),process.exitCode=1;return}const i=(await import("node:readline/promises")).createInterface({input:process.stdin,output:process.stdout});try{const{detectClients:l,applyWyrmEnvToClient:m}=await import("./autoconfig.js"),g=l().filter(b=>b.configured);if(!g.length){f("No configured clients found. Run wyrm-setup first."),process.exitCode=1;return}const h={WYRM_VECTOR_PROVIDER:"nim",WYRM_NIM_API_KEY:t};let y=0;for(const b of g){let $=!1;try{const C=(await import("node:fs")).readFileSync(b.configPath,"utf-8");$=/WYRM_VECTOR_PROVIDER/.test(C)}catch{}if($&&(await i.question(` ${b.name} already sets a provider. Overwrite with NIM? [y/N] `)).trim().toLowerCase()!=="y"){console.log(` ${d.dim("skipped")}`);continue}const v=m(b,h);v.action==="updated"&&y++,console.log(` ${v.action==="updated"?d.green("\u2714"):d.dim("\u25CB")} ${b.name}: ${v.message}`)}if(y===0&&(_e("No client config was changed (all skipped or failed). The NIM tier is NOT active yet."),console.log(d.dim(" Re-run and answer y to overwrite, or set WYRM_VECTOR_PROVIDER=nim + WYRM_NIM_API_KEY in your client env by hand."))),(await i.question(" Reindex existing memories on the NIM tier now? [Y/n] ")).trim().toLowerCase()!=="n"){const b=R();try{const $=b.getDatabase(),{createVectorStore:v}=await import("./vectors.js"),{reindexProjects:C}=await import("./reindex.js"),_=v({provider:"nim",apiKey:t},$),j=b.getAllProjects(1e3).map(q=>q.id),{indexed:T,skipped:L}=await C($,_,j,{dryRun:!1});k(`Reindexed on NIM: ${T} indexed, ${L} skipped.`)}finally{b.close()}}else console.log(` ${d.dim("Skipped. New memories embed on NIM from now on; EXISTING memories stay findable by keyword and on their previous vector tier, but not with NIM-quality recall until you run: wyrm index rebuild")}`);y>0?k(`Upgrade complete (${y} client${y===1?"":"s"} updated). Restart your AI clients to pick up the new env.`):process.exitCode=1}finally{i.close()}}async function Kt(p){const{flags:c}=x(p),{getUpdateStatus:s}=await import("./version-check.js"),a=A().version??"0.0.0",r=R();let e;try{e=await s(r.getDatabase(),a,{force:c.force===!0||c.check===!0})}finally{r.close()}S("Wyrm update");const o=e.latest?e.lookupFailed?`${e.latest} (last known \u2014 registry unreachable now)`:e.latest:e.lookupFailed?"unknown \u2014 npm registry unreachable":"unknown (version check disabled)";if(console.log(D(["Field","Value"],[["Current",e.current],["Latest",o],["Update available",e.updateAvailable?"yes":e.lookupFailed&&!e.latest?"unknown":"no"],["Checked",`${e.checkedAt} (${e.source})`]])),e.lookupFailed&&!e.updateAvailable&&c.force!==!0){f('Could not reach the npm registry, so whether an update exists is UNKNOWN (not "up to date").'),console.log(d.dim(" Retry when online: wyrm update \xB7 or install directly: npm install -g wyrm-mcp@latest")),process.exitCode=2;return}if(c.check===!0)return;if(!e.updateAvailable&&c.force!==!0){console.log(d.dim(`
83
83
  Already up to date. (Use --force to reinstall anyway.)`));return}console.log(d.dim(`
84
84
  Running: npm install -g wyrm-mcp@latest --allow-scripts=wyrm-mcp,better-sqlite3
85
- `));const t=te("npm",["install","-g","wyrm-mcp@latest","--allow-scripts=wyrm-mcp,better-sqlite3"],{stdio:"inherit",shell:!1});t.status===0?k("Updated. Restart your MCP clients to pick up the new binary."):(f(`npm install exited with ${t.status??"unknown"}`),process.exitCode=t.status??1)}async function Gt(u){const{positional:c,flags:s}=x(u),a=c[0],r=s.project??process.cwd();if(a==="inject"){const{injectSystemPrompt:e}=await import("./autoconfig.js"),o=typeof s.clients=="string"?s.clients.split(",").map(n=>n.trim()).filter(Boolean):[],t=e(r,o);S("System prompt injection");for(const n of t.injected)console.log(` + ${n}`);for(const n of t.skipped)console.log(d.dim(` o skipped (unknown client): ${n}`));for(const n of t.errors)f(n);t.injected.length>0&&k("AI clients in this project will now call wyrm_session_prime at conversation start."),t.errors.length>0&&(process.exitCode=1);return}if(a==="migrate"){const{migrateProject:e,renderMigrationReport:o}=await import("./migrate-prompt.js"),{WYRM_INJECT_BLOCK:t}=await import("./autoconfig.js"),n=s.apply===!0,p=e({projectPath:r,newBlock:t,apply:n});console.log(o(p,n));return}f("Usage: wyrm prompt inject [--project <path>] [--clients copilot,cursor] | wyrm prompt migrate [--project <path>] [--apply]"),process.exitCode=1}async function Jt(u){const{positional:c,flags:s}=x(u);(c[0]??"report")!=="report"&&(f("Usage: wyrm hours report --from YYYY-MM-DD --to YYYY-MM-DD [--project <name>] [--session-hours H] [--json]"),process.exit(1));const r=s.from,e=s.to;(!r||!e)&&(f("--from and --to are required (YYYY-MM-DD)"),process.exit(1));const{HourLedger:o}=await import("./hours.js"),t=R();try{const n=s.project,p=n?N(t,n):null;if(n&&!p){f(`Project not found: ${n}`),process.exitCode=1;return}const i=new o(t.getDatabase()).report({range_start:r,range_end:e,project_id:p?.id,default_session_hours:se(s["session-hours"],1)});if(s.json===!0){console.log(JSON.stringify(i,null,2));return}if(S(`Hours ${i.range.start} \u2192 ${i.range.end}`),i.by_project.length===0){console.log(d.dim(" No sessions in range."));return}console.log(I(["Project","Sessions","Hours"],i.by_project.map(l=>[l.project_name,String(l.session_count),l.hours.toFixed(2)]))),console.log(`
86
- Total: ${i.total_hours.toFixed(2)}h across ${i.entries.length} session(s)`+(i.estimated_sessions>0?d.dim(` (${i.estimated_sessions} estimated)`):""))}finally{t.close()}}async function zt(u){const{positional:c,flags:s}=x(u),a=c[0]??"generate",r=s.client,e=se(s.rate,NaN),o=s.from,t=s.to;(a!=="generate"||!r||!Number.isFinite(e)||!o||!t)&&(f('Usage: wyrm invoice generate --client <name> --rate <usd/hour> --from YYYY-MM-DD --to YYYY-MM-DD [--project <name>] [--number INV-X] [--currency USD] [--notes "\u2026"] [--out <path>]'),process.exit(1));const{HourLedger:n}=await import("./hours.js"),p=R();try{const i=s.project,l=i?N(p,i):null;if(i&&!l){f(`Project not found: ${i}`),process.exitCode=1;return}const m=new n(p.getDatabase()).invoice({client_name:r,hourly_rate_usd:e,range_start:o,range_end:t,project_id:l?.id,invoice_number:s.number,currency:s.currency,notes:s.notes,business_name:s["business-name"],business_address:s["business-address"],business_contact:s["business-contact"],client_address:s["client-address"],default_session_hours:se(s["session-hours"],1)}),g=s.out;if(g){const{writeFileSync:h}=await import("node:fs");h(g,m+`
85
+ `));const t=te("npm",["install","-g","wyrm-mcp@latest","--allow-scripts=wyrm-mcp,better-sqlite3"],{stdio:"inherit",shell:!1});t.status===0?k("Updated. Restart your MCP clients to pick up the new binary."):(f(`npm install exited with ${t.status??"unknown"}`),process.exitCode=t.status??1)}async function Gt(p){const{positional:c,flags:s}=x(p),a=c[0],r=s.project??process.cwd();if(a==="inject"){const{injectSystemPrompt:e}=await import("./autoconfig.js"),o=typeof s.clients=="string"?s.clients.split(",").map(n=>n.trim()).filter(Boolean):[],t=e(r,o);S("System prompt injection");for(const n of t.injected)console.log(` + ${n}`);for(const n of t.skipped)console.log(d.dim(` o skipped (unknown client): ${n}`));for(const n of t.errors)f(n);t.injected.length>0&&k("AI clients in this project will now call wyrm_session_prime at conversation start."),t.errors.length>0&&(process.exitCode=1);return}if(a==="migrate"){const{migrateProject:e,renderMigrationReport:o}=await import("./migrate-prompt.js"),{WYRM_INJECT_BLOCK:t}=await import("./autoconfig.js"),n=s.apply===!0,u=e({projectPath:r,newBlock:t,apply:n});console.log(o(u,n));return}f("Usage: wyrm prompt inject [--project <path>] [--clients copilot,cursor] | wyrm prompt migrate [--project <path>] [--apply]"),process.exitCode=1}async function Jt(p){const{positional:c,flags:s}=x(p);(c[0]??"report")!=="report"&&(f("Usage: wyrm hours report --from YYYY-MM-DD --to YYYY-MM-DD [--project <name>] [--session-hours H] [--json]"),process.exit(1));const r=s.from,e=s.to;(!r||!e)&&(f("--from and --to are required (YYYY-MM-DD)"),process.exit(1));const{HourLedger:o}=await import("./hours.js"),t=R();try{const n=s.project,u=n?N(t,n):null;if(n&&!u){f(`Project not found: ${n}`),process.exitCode=1;return}const i=new o(t.getDatabase()).report({range_start:r,range_end:e,project_id:u?.id,default_session_hours:se(s["session-hours"],1)});if(s.json===!0){console.log(JSON.stringify(i,null,2));return}if(S(`Hours ${i.range.start} \u2192 ${i.range.end}`),i.by_project.length===0){console.log(d.dim(" No sessions in range."));return}console.log(D(["Project","Sessions","Hours"],i.by_project.map(l=>[l.project_name,String(l.session_count),l.hours.toFixed(2)]))),console.log(`
86
+ Total: ${i.total_hours.toFixed(2)}h across ${i.entries.length} session(s)`+(i.estimated_sessions>0?d.dim(` (${i.estimated_sessions} estimated)`):""))}finally{t.close()}}async function zt(p){const{positional:c,flags:s}=x(p),a=c[0]??"generate",r=s.client,e=se(s.rate,NaN),o=s.from,t=s.to;(a!=="generate"||!r||!Number.isFinite(e)||!o||!t)&&(f('Usage: wyrm invoice generate --client <name> --rate <usd/hour> --from YYYY-MM-DD --to YYYY-MM-DD [--project <name>] [--number INV-X] [--currency USD] [--notes "\u2026"] [--out <path>]'),process.exit(1));const{HourLedger:n}=await import("./hours.js"),u=R();try{const i=s.project,l=i?N(u,i):null;if(i&&!l){f(`Project not found: ${i}`),process.exitCode=1;return}const m=new n(u.getDatabase()).invoice({client_name:r,hourly_rate_usd:e,range_start:o,range_end:t,project_id:l?.id,invoice_number:s.number,currency:s.currency,notes:s.notes,business_name:s["business-name"],business_address:s["business-address"],business_contact:s["business-contact"],client_address:s["client-address"],default_session_hours:se(s["session-hours"],1)}),g=s.out;if(g){const{writeFileSync:h}=await import("node:fs");h(g,m+`
87
87
  `,"utf-8"),k(`Invoice written to ${g}`)}else process.stdout.write(m+`
88
- `)}finally{p.close()}}async function Qt(u){const{positional:c,flags:s}=x(u),a=c[0]??"status",{AgentDaemon:r}=await import("./agent-daemon.js"),e=R();try{const o=new r(e.getDatabase());switch(a){case"init":case"start":{const t=Math.max(10,Math.min(O(s.interval,600),86400)),n=o.start({interval_seconds:t,max_steps:s["max-steps"]!==void 0&&O(s["max-steps"],0)||void 0,project_path:s.project,verbose:s.verbose===!0});if(!n.ok){f(`Agent init failed: ${n.error}`),process.exitCode=1;return}const p=n.status;k(`Agent ${p.pid!=null&&p.pid!==n.pid?"already running":"started"} \u2014 pid ${p.pid}`),console.log(` Interval: ${t}s \xB7 Active goals: ${p.active_goals} \xB7 Total iterations: ${p.total_iterations}`),console.log(d.dim(` Log: ${p.log_file}`));return}case"status":{const t=o.status();S("Wyrm agent"),console.log(t.running?` RUNNING \u2014 pid ${t.pid}${t.started_at?` (since ${t.started_at})`:""}`:" NOT RUNNING. Start it with: wyrm agent init"),console.log(` Active goals: ${t.active_goals} \xB7 Total iterations: ${t.total_iterations}`),t.last_action&&console.log(` Last action (${t.last_action.ran_at}): ${t.last_action.summary} [${t.last_action.result_status??"?"}]`),s.log===!0&&(console.log(`
89
- === Recent log ===`),console.log(o.recentLog(40)));return}case"stop":{const t=await o.stop({grace_ms:s.grace!==void 0?O(s.grace,3e3):void 0});if(!t.ok){f(`Stop failed: ${t.error}`),process.exitCode=1;return}k(t.was_running?`Agent stopped (was pid ${t.pid}). Goals remain in DB \u2014 wyrm agent init resumes.`:"Agent was not running. (No-op.)");return}case"restart":{const t=await o.restart({interval_seconds:s.interval!==void 0?O(s.interval,600):void 0,max_steps:s["max-steps"]!==void 0&&O(s["max-steps"],0)||void 0,project_path:s.project,verbose:s.verbose===!0});if(!t.ok){f(`Restart failed: ${t.error}`),process.exitCode=1;return}k(`Agent restarted \u2014 pid ${t.status.pid}. Active goals: ${t.status.active_goals}.`);return}default:f("Usage: wyrm agent <init|status|stop|restart> [--interval N] [--max-steps N] [--project <path>] [--verbose] [--log]"),process.exitCode=1}}finally{e.close()}}async function Xt(u){if(u.includes("--encrypt")){const{initializeLicense:a,hasFeature:r}=await import("./license.js");if(a(),!r("encryption")){f("Encryption setup requires a Pro license or higher. See: wyrm license"),process.exitCode=1;return}const{getCrypto:e,initializeCrypto:o}=await import("./crypto.js"),t=u.includes("--enable"),n=u.includes("--test");if(t){const i=process.env.WYRM_ENCRYPTION_KEY??(process.stdin.isTTY?"":xe());if(!i||i.length<8){f('Password must be at least 8 characters. Set WYRM_ENCRYPTION_KEY or pipe it: printf %s "$PW" | wyrm setup --encrypt --enable'),process.exitCode=1;return}o(i),k("Encryption enabled (AES-256-GCM, key derived via PBKDF2). Store your password safely \u2014 it cannot be recovered.");return}const p=e();if(n){if(!p.isEnabled()){f("Encryption not enabled. Run: wyrm setup --encrypt --enable"),process.exitCode=1;return}const i="Wyrm encryption test "+Date.now();p.decrypt(p.encrypt(i))===i?k("Encryption test PASSED (encrypt \u2192 decrypt roundtrip)."):(f("Encryption test FAILED."),process.exitCode=1);return}S("Encryption status"),console.log(` Enabled: ${p.isEnabled()?"yes \u2014 new data is encrypted at rest":"no"}`),console.log(" Algorithm: AES-256-GCM"),p.isEnabled()||console.log(d.dim(" Enable with: wyrm setup --encrypt --enable (password via WYRM_ENCRYPTION_KEY or stdin)"));return}const c=X(ee(import.meta.url)),s=te(process.execPath,[W(c,"setup.js"),...u],{stdio:"inherit",shell:!1});process.exitCode=s.status??0}async function Zt(u){const c="https://github.com/Ghosts-Protocol-Pvt-Ltd/wyrm-mcp",a=`- wyrm-mcp: ${A().version??"unknown"}
88
+ `)}finally{u.close()}}async function Qt(p){const{positional:c,flags:s}=x(p),a=c[0]??"status",{AgentDaemon:r}=await import("./agent-daemon.js"),e=R();try{const o=new r(e.getDatabase());switch(a){case"init":case"start":{const t=Math.max(10,Math.min(O(s.interval,600),86400)),n=o.start({interval_seconds:t,max_steps:s["max-steps"]!==void 0&&O(s["max-steps"],0)||void 0,project_path:s.project,verbose:s.verbose===!0});if(!n.ok){f(`Agent init failed: ${n.error}`),process.exitCode=1;return}const u=n.status;k(`Agent ${u.pid!=null&&u.pid!==n.pid?"already running":"started"} \u2014 pid ${u.pid}`),console.log(` Interval: ${t}s \xB7 Active goals: ${u.active_goals} \xB7 Total iterations: ${u.total_iterations}`),console.log(d.dim(` Log: ${u.log_file}`));return}case"status":{const t=o.status();S("Wyrm agent"),console.log(t.running?` RUNNING \u2014 pid ${t.pid}${t.started_at?` (since ${t.started_at})`:""}`:" NOT RUNNING. Start it with: wyrm agent init"),console.log(` Active goals: ${t.active_goals} \xB7 Total iterations: ${t.total_iterations}`),t.last_action&&console.log(` Last action (${t.last_action.ran_at}): ${t.last_action.summary} [${t.last_action.result_status??"?"}]`),s.log===!0&&(console.log(`
89
+ === Recent log ===`),console.log(o.recentLog(40)));return}case"stop":{const t=await o.stop({grace_ms:s.grace!==void 0?O(s.grace,3e3):void 0});if(!t.ok){f(`Stop failed: ${t.error}`),process.exitCode=1;return}k(t.was_running?`Agent stopped (was pid ${t.pid}). Goals remain in DB \u2014 wyrm agent init resumes.`:"Agent was not running. (No-op.)");return}case"restart":{const t=await o.restart({interval_seconds:s.interval!==void 0?O(s.interval,600):void 0,max_steps:s["max-steps"]!==void 0&&O(s["max-steps"],0)||void 0,project_path:s.project,verbose:s.verbose===!0});if(!t.ok){f(`Restart failed: ${t.error}`),process.exitCode=1;return}k(`Agent restarted \u2014 pid ${t.status.pid}. Active goals: ${t.status.active_goals}.`);return}default:f("Usage: wyrm agent <init|status|stop|restart> [--interval N] [--max-steps N] [--project <path>] [--verbose] [--log]"),process.exitCode=1}}finally{e.close()}}async function Xt(p){if(p.includes("--encrypt")){const{initializeLicense:a,hasFeature:r}=await import("./license.js");if(a(),!r("encryption")){f("Encryption setup requires a Pro license or higher. See: wyrm license"),process.exitCode=1;return}const{getCrypto:e,initializeCrypto:o}=await import("./crypto.js"),t=p.includes("--enable"),n=p.includes("--test");if(t){const i=process.env.WYRM_ENCRYPTION_KEY??(process.stdin.isTTY?"":xe());if(!i||i.length<8){f('Password must be at least 8 characters. Set WYRM_ENCRYPTION_KEY or pipe it: printf %s "$PW" | wyrm setup --encrypt --enable'),process.exitCode=1;return}o(i),k("Encryption enabled (AES-256-GCM, key derived via PBKDF2). Store your password safely \u2014 it cannot be recovered.");return}const u=e();if(n){if(!u.isEnabled()){f("Encryption not enabled. Run: wyrm setup --encrypt --enable"),process.exitCode=1;return}const i="Wyrm encryption test "+Date.now();u.decrypt(u.encrypt(i))===i?k("Encryption test PASSED (encrypt \u2192 decrypt roundtrip)."):(f("Encryption test FAILED."),process.exitCode=1);return}S("Encryption status"),console.log(` Enabled: ${u.isEnabled()?"yes \u2014 new data is encrypted at rest":"no"}`),console.log(" Algorithm: AES-256-GCM"),u.isEnabled()||console.log(d.dim(" Enable with: wyrm setup --encrypt --enable (password via WYRM_ENCRYPTION_KEY or stdin)"));return}const c=X(ee(import.meta.url)),s=te(process.execPath,[W(c,"setup.js"),...p],{stdio:"inherit",shell:!1});process.exitCode=s.status??0}async function Zt(p){const c="https://github.com/Ghosts-Protocol-Pvt-Ltd/wyrm-mcp",a=`- wyrm-mcp: ${A().version??"unknown"}
90
90
  - node: ${process.version}
91
- - platform: ${process.platform} ${process.arch}`,r=u.includes("--bug")?"bug":u.includes("--idea")||u.includes("--feature")?"idea":u.includes("--question")||u.includes("--ask")?"question":"feedback",e=u.filter(p=>!p.startsWith("--")).join(" ").trim(),o={bug:`**What happened**
91
+ - platform: ${process.platform} ${process.arch}`,r=p.includes("--bug")?"bug":p.includes("--idea")||p.includes("--feature")?"idea":p.includes("--question")||p.includes("--ask")?"question":"feedback",e=p.filter(u=>!u.startsWith("--")).join(" ").trim(),o={bug:`**What happened**
92
92
 
93
93
 
94
94
  **What you expected**
@@ -114,7 +114,7 @@ ${a}`,feedback:`**Your feedback** (what's working, what's rough)
114
114
 
115
115
 
116
116
  ---
117
- ${a}`},t={bug:"bug",idea:"enhancement",question:"question",feedback:"feedback"};let n;r==="question"?n=`${c}/discussions/new?category=q-a`+(e?`&title=${encodeURIComponent(e)}`:"")+`&body=${encodeURIComponent(o.question)}`:n=`${c}/issues/new?labels=${encodeURIComponent(t[r])}&title=${encodeURIComponent(`[${r}] ${e}`.trim())}&body=${encodeURIComponent(o[r])}`,console.log(""),console.log(" "+d.bold("Thanks for helping make Wyrm better.")),console.log(" Opening a prefilled report in your browser. If it does not open, use this link:"),console.log(""),console.log(" "+d.cyan(n)),console.log(""),console.log(d.dim(" Prefer email? ryan@ghosts.lk \xB7 Bug: wyrm feedback --bug Idea: --idea Question: --question")),console.log("");try{const{spawn:p}=await import("child_process"),i=process.platform,l=i==="darwin"?p("open",[n],{stdio:"ignore",detached:!0}):i==="win32"?p("cmd",["/c","start","",n],{stdio:"ignore",detached:!0}):p("xdg-open",[n],{stdio:"ignore",detached:!0});l.on("error",()=>{}),l.unref()}catch{}}function Te(){console.log(`
117
+ ${a}`},t={bug:"bug",idea:"enhancement",question:"question",feedback:"feedback"};let n;r==="question"?n=`${c}/discussions/new?category=q-a`+(e?`&title=${encodeURIComponent(e)}`:"")+`&body=${encodeURIComponent(o.question)}`:n=`${c}/issues/new?labels=${encodeURIComponent(t[r])}&title=${encodeURIComponent(`[${r}] ${e}`.trim())}&body=${encodeURIComponent(o[r])}`,console.log(""),console.log(" "+d.bold("Thanks for helping make Wyrm better.")),console.log(" Opening a prefilled report in your browser. If it does not open, use this link:"),console.log(""),console.log(" "+d.cyan(n)),console.log(""),console.log(d.dim(" Prefer email? ryan@ghosts.lk \xB7 Bug: wyrm feedback --bug Idea: --idea Question: --question")),console.log("");try{const{spawn:u}=await import("child_process"),i=process.platform,l=i==="darwin"?u("open",[n],{stdio:"ignore",detached:!0}):i==="win32"?u("cmd",["/c","start","",n],{stdio:"ignore",detached:!0}):u("xdg-open",[n],{stdio:"ignore",detached:!0});l.on("error",()=>{}),l.unref()}catch{}}function Te(){console.log(`
118
118
  ${F.brightMagenta}\u{F115D} Wyrm CLI v${A().version??"unknown"}${F.reset}
119
119
  ${F.dim}Persistent AI Memory System${F.reset}
120
120
 
@@ -201,15 +201,15 @@ ${d.bold("Examples:")}
201
201
  wyrm sync export --out ~/wyrm-backup.wyrm
202
202
  wyrm sync preview --from ~/wyrm-backup.wyrm
203
203
  wyrm prune --project MyApp --min-confidence 0.2 --older-than 30
204
- `)}const[,,Y,..._]=process.argv;process.env.WYRM_LOG_LEVEL||(process.env.WYRM_LOG_LEVEL="warn");function Ne(u){const c=u.ref_table?`${u.ref_table}${u.ref_id?"#"+u.ref_id:""}`:"",s=new Date(u.created_at).toLocaleTimeString();return`#${u.cursor} ${u.kind.padEnd(15)} ${c.padEnd(16)} ${st(u.actor)} ${s}`}async function eo(u){const{positional:c,flags:s}=x(u),a=c[0]||"since",r=R();try{if(!r.liveMemoryEnabled()){f("Live Memory is disabled (set WYRM_LIVE_MEMORY=1)."),process.exitCode=1;return}const e=(typeof s.project=="string"?s.project:"")||process.cwd(),o=r.getProject(e)??r.getProjectByName(e);if(!o){f(`Project not found: ${e}`),process.exitCode=1;return}if(a==="publish"){const t=c[1]||(typeof s.kind=="string"?s.kind:"");if(!t){f("usage: wyrm events publish <kind> --project <p> [--actor A] [--ref-table T --ref-id ID]"),process.exitCode=1;return}r.publishEvent({projectId:o.id,kind:t,refTable:typeof s["ref-table"]=="string"?s["ref-table"]:void 0,refId:typeof s["ref-id"]=="string"?s["ref-id"]:void 0,actor:typeof s.actor=="string"?s.actor:void 0}),k(`Event published (${t}) to ${o.name}`);return}if(a==="since"){const t=O(s.cursor,0),n=O(s.limit,50),p=r.eventsSince(o.id,t,n);for(const i of p)console.log(Ne(i));S(`${p.length} event(s) for '${o.name}' since cursor ${t}`);return}f("usage: wyrm events <publish|since> ..."),process.exitCode=1}finally{r.close()}}async function to(u){const{flags:c}=x(u),s=R();if(!s.liveMemoryEnabled()){f("Live Memory is disabled (set WYRM_LIVE_MEMORY=1)."),s.close(),process.exitCode=1;return}const a=(typeof c.project=="string"?c.project:"")||process.cwd(),r=s.getProject(a)??N(s,a);if(!r){f(`Project not found: ${a}`),s.close(),process.exitCode=1;return}const e=Math.max(250,O(c.interval,1e3));let o=O(c.since,s.subscribeEvents(r.id,1).cursor);S(`Watching '${r.name}' (cursor ${o}, every ${e}ms) \u2014 Ctrl-C to stop`);const t=()=>{try{for(const i of s.eventsSince(r.id,o,200))o=i.cursor,console.log(Ne(i))}catch{}};t();const n=setInterval(t,e),p=()=>{clearInterval(n);try{s.close()}catch{}process.exit(0)};process.on("SIGINT",p),process.on("SIGTERM",p)}async function oo(u){const{flags:c}=x(u),{embedAll:s,removeAll:a,statusAll:r}=await import("./priority-embed.js"),e={projectDir:typeof c.project=="string"?c.project:void 0,allClients:c.all===!0},o=t=>console.log(` ${String(t.result??t.status).padEnd(9)} [${t.scope}] ${t.file}`);if(c.status){S("Wyrm priority embedding \u2014 status"),r(e).forEach(o);return}if(c.remove){S("Wyrm priority embedding \u2014 removed"),a(e).forEach(o);return}S("Wyrm is now FIRST-PRIORITY memory"),s(e).forEach(o);try{const{installClaudeCodeHooks:t,installClaudeStatusline:n}=await import("./autoconfig.js");t()?k("Proactive hooks installed (SessionStart rehydrate + capture + tool-trace)."):console.log(" (Claude Code not detected \u2014 skipped hook install.)");const i=n();i&&k(`Buddy statusline: ${i.message}`)}catch{}k("Wyrm will now be read first, primed proactively, and shown in the TUI at all times.")}async function so(u){const{flags:c}=x(u),{harvestProjects:s}=await import("./harvest.js"),{MemoryArtifacts:a}=await import("./memory-artifacts.js"),{escapeLikePattern:r}=await import("./auto-capture.js"),e=R();try{const o=e.getDatabase(),t=new a(o),n={existsBySig:(w,v)=>!!o.prepare("SELECT 1 FROM memory_artifacts WHERE project_id = ? AND tags LIKE ? ESCAPE '\\' LIMIT 1").get(w,"%"+r(v)+"%"),addCandidate:(w,v)=>t.add(w,{kind:v.kind,problem:v.text,tags:[...v.tags,v.sig],confidence:v.confidence,needsReview:1,createdBy:"harvest"})},p=typeof c.project=="string"?c.project:void 0;let i;if(p){const w=e.getProject(p)??e.getProjectByName(p);if(!w){f(`Project not found: ${p}`),process.exitCode=1;return}i=[{id:w.id,name:w.name,path:w.path}]}else i=e.getAllProjects(500).map(w=>({id:w.id,name:w.name,path:w.path}));const l=c["dry-run"]===!0||c.dry===!0,m=c.code===!0||c["include-code"]===!0,{reports:g,totalAdded:h,totalSkipped:y}=s(n,i,{dryRun:l,gitLimit:O(c.limit,30),includeCode:m});S(`Harvest ${l?"(dry run) ":""}\u2014 ${h} candidate(s), ${y} already present (${i.length} project(s))`);for(const w of g.filter(v=>v.added>0).sort((v,$)=>$.added-v.added).slice(0,25))console.log(` +${String(w.added).padStart(3)} (skip ${w.skipped}) ${w.project}`);if(m&&!l){const{SymbolGraph:w}=await import("./symbols.js"),v=new w(o);let $=0,b=0;for(const C of i)try{const E=v.indexProject(C.id,C.path);$+=E.symbols,b+=E.files}catch{}console.log(` \u{1F4D0} Indexed ${$} code symbols (${b} files) \u2192 searchable via 'wyrm search'`)}!l&&h>0&&k("Review with: wyrm review")}finally{e.close()}}async function ro(u){const c=await import("./vault.js"),[s,...a]=u;try{switch(s){case"set":{const r=a[0];if(!r){f("usage: wyrm vault set <name> (the secret is read from STDIN, never argv)"),process.exitCode=1;return}if(process.stdin.isTTY){f(`pipe the secret in, e.g.: printf %s "$TOKEN" | wyrm vault set ${r}`),process.exitCode=1;return}const o=(await import("node:fs")).readFileSync(0,"utf8").replace(/\r?\n$/,"");if(!o){f("empty secret on stdin"),process.exitCode=1;return}c.vaultSet(r,o),k(`Stored "${r}" (AES-256-GCM). Use it without exposing it: wyrm vault exec ${r} -- <command>`);break}case"get":{const r=a[0];if(!r){f("usage: wyrm vault get <name>"),process.exitCode=1;return}const e=c.vaultGet(r);if(e===void 0){f(`no secret named "${r}"`),process.exitCode=1;return}process.stdout.write(e);break}case"list":case"ls":{const r=c.vaultList();if(!r.length){console.log("(vault is empty)");break}S(`Vault \u2014 ${r.length} secret(s)`);for(const e of r)console.log(" \u2022 "+e);break}case"rm":case"remove":case"delete":{const r=a[0];if(!r){f("usage: wyrm vault rm <name>"),process.exitCode=1;return}k(c.vaultRemove(r)?`Removed "${r}"`:`(no secret named "${r}")`);break}case"exec":{const r=a[0],e=a.indexOf("--");if(!r||e===-1||e+1>=a.length){f("usage: wyrm vault exec <name> [--as ENVVAR] -- <command...>"),process.exitCode=1;return}const o=a.slice(1,e),t=o.indexOf("--as"),n=t>=0?o[t+1]:r.toUpperCase().replace(/[^A-Z0-9]+/g,"_"),p=a.slice(e+1),i=c.vaultGet(r);if(i===void 0){f(`no secret named "${r}"`),process.exitCode=1;return}const l=te(p[0],p.slice(1),{stdio:"inherit",env:{...process.env,[n]:i}});process.exitCode=l.status??1;break}case"import-npm":{const r=await import("node:fs"),e=await import("node:os"),t=(await import("node:path")).join(e.homedir(),".npmrc");if(!r.existsSync(t)){f("~/.npmrc not found"),process.exitCode=1;return}const n=r.readFileSync(t,"utf8").match(/\/\/registry\.npmjs\.org\/:_authToken=(.+)/);if(!n){f("no npm authToken found in ~/.npmrc"),process.exitCode=1;return}c.vaultSet("npm-token",n[1].trim()),k('Imported npm token \u2192 vault as "npm-token". You can now scrub the plaintext from ~/.npmrc and use: wyrm vault exec npm-token --as NODE_AUTH_TOKEN -- npm publish');break}case"setup":{const r=c.vaultPaths();if(r.secure)k(`Vault is already secure (backend: ${r.backend}, ${r.count} secret(s)).`);else{const e=r.keychainAvailable?"keychain":"passphrase";if(e==="passphrase"&&!process.env.WYRM_VAULT_PASSPHRASE){f("No OS keychain on this host. Set WYRM_VAULT_PASSPHRASE, then re-run: wyrm vault setup"),process.exitCode=1;return}const o=c.vaultSecure({backend:e});k(`Vault secured: ${o.from} \u2192 ${o.to}${o.rotated?" (key rotated)":""}.`),o.keyfileShredded&&console.log(" plaintext vault.key: shredded \u2714"),o.backup&&console.log(` ciphertext backup: ${o.backup} (delete once confirmed)`)}S("Store & use credentials safely"),console.log(' store: printf %s "$TOKEN" | wyrm vault set <name> # reads STDIN \u2014 never argv/shell history'),console.log(" use: wyrm vault exec <name> --as ENV_VAR -- <cmd> # injected as env var, never printed"),console.log(" list: wyrm vault list inspect: wyrm vault info");break}case"secure":{const r=a.indexOf("--backend"),e=r>=0?a[r+1]:"keychain";if(e!=="keychain"&&e!=="passphrase"){f("usage: wyrm vault secure [--backend keychain|passphrase] [--no-rotate]"),process.exitCode=1;return}if(e==="passphrase"&&!process.env.WYRM_VAULT_PASSPHRASE){f("set WYRM_VAULT_PASSPHRASE before: wyrm vault secure --backend passphrase"),process.exitCode=1;return}const o=!a.includes("--no-rotate"),t=c.vaultSecure({backend:e,rotate:o});k(`Vault secured: ${t.from} \u2192 ${t.to}${t.rotated?" (key rotated)":""}.`),console.log(` secrets re-encrypted: ${t.secrets}`),t.keyfileShredded&&console.log(" plaintext vault.key: shredded \u2714"),t.backup&&console.log(` ciphertext backup: ${t.backup} (delete once you've confirmed)`),console.log(e==="keychain"?" master key now lives in the OS keychain \u2014 not on disk.":" master key now derived from WYRM_VAULT_PASSPHRASE \u2014 keep that set for future use.");break}case"info":{const r=c.vaultPaths();S("Vault"),console.log(` backend: ${r.backend}`),console.log(` secrets: ${r.count}`),console.log(` store: ${r.vault} (0600)`),console.log(` key: ${r.backend==="keyfile"?r.key+" (0600)":r.backend==="keychain"?"(OS keychain \u2014 no key on disk)":"(derived from WYRM_VAULT_PASSPHRASE \u2014 no key on disk)"}`),console.log(` secure: ${r.secure?"yes \u2014 key is not a plaintext file beside the ciphertext":"NO \u2014 key sits beside ciphertext"}`),r.secure||console.log(r.keychainAvailable?" \u26A0 run `wyrm vault secure` to move the key into the OS keychain.":" \u26A0 no OS keychain found \u2014 set WYRM_VAULT_PASSPHRASE and run `wyrm vault secure --backend passphrase`.");break}default:f("usage: wyrm vault <setup|set|get|list|rm|exec|import-npm|secure|info>"),process.exitCode=1}}catch(r){f(`vault: ${r.message}`),process.exitCode=1}}function no(){const u=X(ee(import.meta.url)),s=[W(u,"..","skills"),W(u,"..","..","skills")].find(r=>U(r));if(!s)return[];const a=[];for(const r of Ue(s,{withFileTypes:!0})){if(!r.isDirectory())continue;const e=W(s,r.name,"SKILL.md");if(!U(e))continue;let o=r.name,t="";try{const n=H(e,"utf8"),p=n.match(/^name:\s*(.+)$/m);if(p&&(o=p[1].trim()),/^description:\s*[|>]/m.test(n))t=((n.split(/^description:.*$/m)[1]??"").split(`
205
- `).find(m=>m.trim())??"").trim();else{const i=n.match(/^description:\s*(.+)$/m);i&&(t=i[1].trim())}}catch{}a.push({name:o,description:t})}return a.sort((r,e)=>r.name.localeCompare(e.name))}async function io(u){const c=u[0];if(!c||c==="help"||c==="--help"){console.log(`Usage:
204
+ `)}const[,,Y,...E]=process.argv;process.env.WYRM_LOG_LEVEL||(process.env.WYRM_LOG_LEVEL="warn");function Ne(p){const c=p.ref_table?`${p.ref_table}${p.ref_id?"#"+p.ref_id:""}`:"",s=new Date(p.created_at).toLocaleTimeString();return`#${p.cursor} ${p.kind.padEnd(15)} ${c.padEnd(16)} ${st(p.actor)} ${s}`}async function eo(p){const{positional:c,flags:s}=x(p),a=c[0]||"since",r=R();try{if(!r.liveMemoryEnabled()){f("Live Memory is disabled (set WYRM_LIVE_MEMORY=1)."),process.exitCode=1;return}const e=(typeof s.project=="string"?s.project:"")||process.cwd(),o=r.getProject(e)??r.getProjectByName(e);if(!o){f(`Project not found: ${e}`),process.exitCode=1;return}if(a==="publish"){const t=c[1]||(typeof s.kind=="string"?s.kind:"");if(!t){f("usage: wyrm events publish <kind> --project <p> [--actor A] [--ref-table T --ref-id ID]"),process.exitCode=1;return}r.publishEvent({projectId:o.id,kind:t,refTable:typeof s["ref-table"]=="string"?s["ref-table"]:void 0,refId:typeof s["ref-id"]=="string"?s["ref-id"]:void 0,actor:typeof s.actor=="string"?s.actor:void 0}),k(`Event published (${t}) to ${o.name}`);return}if(a==="since"){const t=O(s.cursor,0),n=O(s.limit,50),u=r.eventsSince(o.id,t,n);for(const i of u)console.log(Ne(i));S(`${u.length} event(s) for '${o.name}' since cursor ${t}`);return}f("usage: wyrm events <publish|since> ..."),process.exitCode=1}finally{r.close()}}async function to(p){const{flags:c}=x(p),s=R();if(!s.liveMemoryEnabled()){f("Live Memory is disabled (set WYRM_LIVE_MEMORY=1)."),s.close(),process.exitCode=1;return}const a=(typeof c.project=="string"?c.project:"")||process.cwd(),r=s.getProject(a)??N(s,a);if(!r){f(`Project not found: ${a}`),s.close(),process.exitCode=1;return}const e=Math.max(250,O(c.interval,1e3));let o=O(c.since,s.subscribeEvents(r.id,1).cursor);S(`Watching '${r.name}' (cursor ${o}, every ${e}ms) \u2014 Ctrl-C to stop`);const t=()=>{try{for(const i of s.eventsSince(r.id,o,200))o=i.cursor,console.log(Ne(i))}catch{}};t();const n=setInterval(t,e),u=()=>{clearInterval(n);try{s.close()}catch{}process.exit(0)};process.on("SIGINT",u),process.on("SIGTERM",u)}async function oo(p){const{flags:c}=x(p),{embedAll:s,removeAll:a,statusAll:r}=await import("./priority-embed.js"),e={projectDir:typeof c.project=="string"?c.project:void 0,allClients:c.all===!0},o=t=>console.log(` ${String(t.result??t.status).padEnd(9)} [${t.scope}] ${t.file}`);if(c.status){S("Wyrm priority embedding \u2014 status"),r(e).forEach(o);return}if(c.remove){S("Wyrm priority embedding \u2014 removed"),a(e).forEach(o);return}S("Wyrm is now FIRST-PRIORITY memory"),s(e).forEach(o);try{const{installClaudeCodeHooks:t,installClaudeStatusline:n}=await import("./autoconfig.js");t()?k("Proactive hooks installed (SessionStart rehydrate + capture + tool-trace)."):console.log(" (Claude Code not detected \u2014 skipped hook install.)");const i=n();i&&k(`Buddy statusline: ${i.message}`)}catch{}k("Wyrm will now be read first, primed proactively, and shown in the TUI at all times.")}async function so(p){const{flags:c}=x(p),{harvestProjects:s}=await import("./harvest.js"),{MemoryArtifacts:a}=await import("./memory-artifacts.js"),{escapeLikePattern:r}=await import("./auto-capture.js"),e=R();try{const o=e.getDatabase(),t=new a(o),n={existsBySig:(w,b)=>!!o.prepare("SELECT 1 FROM memory_artifacts WHERE project_id = ? AND tags LIKE ? ESCAPE '\\' LIMIT 1").get(w,"%"+r(b)+"%"),addCandidate:(w,b)=>t.add(w,{kind:b.kind,problem:b.text,tags:[...b.tags,b.sig],confidence:b.confidence,needsReview:1,createdBy:"harvest"})},u=typeof c.project=="string"?c.project:void 0;let i;if(u){const w=e.getProject(u)??e.getProjectByName(u);if(!w){f(`Project not found: ${u}`),process.exitCode=1;return}i=[{id:w.id,name:w.name,path:w.path}]}else i=e.getAllProjects(500).map(w=>({id:w.id,name:w.name,path:w.path}));const l=c["dry-run"]===!0||c.dry===!0,m=c.code===!0||c["include-code"]===!0,{reports:g,totalAdded:h,totalSkipped:y}=s(n,i,{dryRun:l,gitLimit:O(c.limit,30),includeCode:m});S(`Harvest ${l?"(dry run) ":""}\u2014 ${h} candidate(s), ${y} already present (${i.length} project(s))`);for(const w of g.filter(b=>b.added>0).sort((b,$)=>$.added-b.added).slice(0,25))console.log(` +${String(w.added).padStart(3)} (skip ${w.skipped}) ${w.project}`);if(m&&!l){const{SymbolGraph:w}=await import("./symbols.js"),b=new w(o);let $=0,v=0;for(const C of i)try{const _=b.indexProject(C.id,C.path);$+=_.symbols,v+=_.files}catch{}console.log(` \u{1F4D0} Indexed ${$} code symbols (${v} files) \u2192 searchable via 'wyrm search'`)}!l&&h>0&&k("Review with: wyrm review")}finally{e.close()}}async function ro(p){const c=await import("./vault.js"),[s,...a]=p;try{switch(s){case"set":{const r=a[0];if(!r){f("usage: wyrm vault set <name> (the secret is read from STDIN, never argv)"),process.exitCode=1;return}if(process.stdin.isTTY){f(`pipe the secret in, e.g.: printf %s "$TOKEN" | wyrm vault set ${r}`),process.exitCode=1;return}const o=(await import("node:fs")).readFileSync(0,"utf8").replace(/\r?\n$/,"");if(!o){f("empty secret on stdin"),process.exitCode=1;return}c.vaultSet(r,o),k(`Stored "${r}" (AES-256-GCM). Use it without exposing it: wyrm vault exec ${r} -- <command>`);break}case"get":{const r=a[0];if(!r){f("usage: wyrm vault get <name>"),process.exitCode=1;return}const e=c.vaultGet(r);if(e===void 0){f(`no secret named "${r}"`),process.exitCode=1;return}process.stdout.write(e);break}case"list":case"ls":{const r=c.vaultList();if(!r.length){console.log("(vault is empty)");break}S(`Vault \u2014 ${r.length} secret(s)`);for(const e of r)console.log(" \u2022 "+e);break}case"rm":case"remove":case"delete":{const r=a[0];if(!r){f("usage: wyrm vault rm <name>"),process.exitCode=1;return}k(c.vaultRemove(r)?`Removed "${r}"`:`(no secret named "${r}")`);break}case"exec":{const r=a[0],e=a.indexOf("--");if(!r||e===-1||e+1>=a.length){f("usage: wyrm vault exec <name> [--as ENVVAR] -- <command...>"),process.exitCode=1;return}const o=a.slice(1,e),t=o.indexOf("--as"),n=t>=0?o[t+1]:r.toUpperCase().replace(/[^A-Z0-9]+/g,"_"),u=a.slice(e+1),i=c.vaultGet(r);if(i===void 0){f(`no secret named "${r}"`),process.exitCode=1;return}const l=te(u[0],u.slice(1),{stdio:"inherit",env:{...process.env,[n]:i}});process.exitCode=l.status??1;break}case"import-npm":{const r=await import("node:fs"),e=await import("node:os"),t=(await import("node:path")).join(e.homedir(),".npmrc");if(!r.existsSync(t)){f("~/.npmrc not found"),process.exitCode=1;return}const n=r.readFileSync(t,"utf8").match(/\/\/registry\.npmjs\.org\/:_authToken=(.+)/);if(!n){f("no npm authToken found in ~/.npmrc"),process.exitCode=1;return}c.vaultSet("npm-token",n[1].trim()),k('Imported npm token \u2192 vault as "npm-token". You can now scrub the plaintext from ~/.npmrc and use: wyrm vault exec npm-token --as NODE_AUTH_TOKEN -- npm publish');break}case"setup":{const r=c.vaultPaths();if(r.secure)k(`Vault is already secure (backend: ${r.backend}, ${r.count} secret(s)).`);else{const e=r.keychainAvailable?"keychain":"passphrase";if(e==="passphrase"&&!process.env.WYRM_VAULT_PASSPHRASE){f("No OS keychain on this host. Set WYRM_VAULT_PASSPHRASE, then re-run: wyrm vault setup"),process.exitCode=1;return}const o=c.vaultSecure({backend:e});k(`Vault secured: ${o.from} \u2192 ${o.to}${o.rotated?" (key rotated)":""}.`),o.keyfileShredded&&console.log(" plaintext vault.key: shredded \u2714"),o.backup&&console.log(` ciphertext backup: ${o.backup} (delete once confirmed)`)}S("Store & use credentials safely"),console.log(' store: printf %s "$TOKEN" | wyrm vault set <name> # reads STDIN \u2014 never argv/shell history'),console.log(" use: wyrm vault exec <name> --as ENV_VAR -- <cmd> # injected as env var, never printed"),console.log(" list: wyrm vault list inspect: wyrm vault info");break}case"secure":{const r=a.indexOf("--backend"),e=r>=0?a[r+1]:"keychain";if(e!=="keychain"&&e!=="passphrase"){f("usage: wyrm vault secure [--backend keychain|passphrase] [--no-rotate]"),process.exitCode=1;return}if(e==="passphrase"&&!process.env.WYRM_VAULT_PASSPHRASE){f("set WYRM_VAULT_PASSPHRASE before: wyrm vault secure --backend passphrase"),process.exitCode=1;return}const o=!a.includes("--no-rotate"),t=c.vaultSecure({backend:e,rotate:o});k(`Vault secured: ${t.from} \u2192 ${t.to}${t.rotated?" (key rotated)":""}.`),console.log(` secrets re-encrypted: ${t.secrets}`),t.keyfileShredded&&console.log(" plaintext vault.key: shredded \u2714"),t.backup&&console.log(` ciphertext backup: ${t.backup} (delete once you've confirmed)`),console.log(e==="keychain"?" master key now lives in the OS keychain \u2014 not on disk.":" master key now derived from WYRM_VAULT_PASSPHRASE \u2014 keep that set for future use.");break}case"info":{const r=c.vaultPaths();S("Vault"),console.log(` backend: ${r.backend}`),console.log(` secrets: ${r.count}`),console.log(` store: ${r.vault} (0600)`),console.log(` key: ${r.backend==="keyfile"?r.key+" (0600)":r.backend==="keychain"?"(OS keychain \u2014 no key on disk)":"(derived from WYRM_VAULT_PASSPHRASE \u2014 no key on disk)"}`),console.log(` secure: ${r.secure?"yes \u2014 key is not a plaintext file beside the ciphertext":"NO \u2014 key sits beside ciphertext"}`),r.secure||console.log(r.keychainAvailable?" \u26A0 run `wyrm vault secure` to move the key into the OS keychain.":" \u26A0 no OS keychain found \u2014 set WYRM_VAULT_PASSPHRASE and run `wyrm vault secure --backend passphrase`.");break}default:f("usage: wyrm vault <setup|set|get|list|rm|exec|import-npm|secure|info>"),process.exitCode=1}}catch(r){f(`vault: ${r.message}`),process.exitCode=1}}function no(){const p=X(ee(import.meta.url)),s=[W(p,"..","skills"),W(p,"..","..","skills")].find(r=>U(r));if(!s)return[];const a=[];for(const r of Ue(s,{withFileTypes:!0})){if(!r.isDirectory())continue;const e=W(s,r.name,"SKILL.md");if(!U(e))continue;let o=r.name,t="";try{const n=H(e,"utf8"),u=n.match(/^name:\s*(.+)$/m);if(u&&(o=u[1].trim()),/^description:\s*[|>]/m.test(n))t=((n.split(/^description:.*$/m)[1]??"").split(`
205
+ `).find(m=>m.trim())??"").trim();else{const i=n.match(/^description:\s*(.+)$/m);i&&(t=i[1].trim())}}catch{}a.push({name:o,description:t})}return a.sort((r,e)=>r.name.localeCompare(e.name))}async function io(p){const c=p[0];if(!c||c==="help"||c==="--help"){console.log(`Usage:
206
206
  wyrm skill list show the bundled guides + your registered skills
207
207
  wyrm skill backfill-content read every skill's SKILL.md into the registry (idempotent)
208
208
  wyrm skill export <targetDir> [--all] materialize SKILL.md files from stored content
209
- wyrm skill share <name|--all|--tier T> [--public|--private] [--include-inactive] set cloud-sync visibility (single or bulk)`);return}if(c==="list"||c==="ls"){const a=no();if(S("Bundled skill guides (shipped in the box)"),a.length===0)console.log(d.yellow(" none found next to this install"));else{for(const e of a)console.log(` ${d.cyan(e.name)}`),e.description&&console.log(` ${d.dim(e.description)}`);console.log(d.dim("\n Read one: open its SKILL.md, or `wyrm skill export <dir>` to materialize all."))}const r=R();try{const e=r.listSkills(!0);if(S(`Registered skills in your memory (${e.length})`),e.length===0)console.log(d.dim(" none yet \u2014 register with wyrm_skill_register or author with wyrm_skill_create"));else{for(const o of e.slice(0,40))console.log(` ${d.cyan(o.name)}${o.tier?d.dim(" ["+o.tier+"]"):""}`);e.length>40&&console.log(d.dim(` \u2026 and ${e.length-40} more`))}}finally{r.close()}return}const s=R();try{if(c==="backfill-content"||c==="backfill"){S("Backfill SKILL.md content into the registry");const a=s.backfillSkillContent();k(`${a.filled} filled . ${a.unchanged} unchanged . ${a.missing} missing-file (of ${a.total} registered)`),a.missing>0&&console.log(d.yellow(` ${a.missing} skill(s) had no readable SKILL.md \u2014 re-run after restoring their files.`));return}if(c==="export"){const a=u[1];a||(f("Usage: wyrm skill export <targetDir> [--all]"),s.close(),process.exit(1));const r=u.includes("--all");S(`Export skills \u2192 ${a}`);const e=s.exportSkillContent(a,{includeInactive:r});k(`${e.written} SKILL.md written . ${e.skipped_no_content} skipped (no stored content) (of ${e.total} ${r?"total":"active"})`),e.skipped_no_content>0&&console.log(d.yellow(" Run `wyrm skill backfill-content` on the source machine to populate content first.")),e.collisions>0&&console.log(d.yellow(` ${e.collisions} slug collision(s) disambiguated with a name-hash suffix (distinct skills, same slug) \u2014 no content lost.`));return}if(c==="share"){const a=u.includes("--private")?"within":u.includes("--public")?"public":"org",r=u.includes("--include-inactive"),e=u.indexOf("--tier"),o=e>=0?u[e+1]:void 0;if(e>=0&&(!o||o.startsWith("--"))&&(f("Usage: wyrm skill share --tier <god|mega|atomic> [--public|--private] [--include-inactive]"),s.close(),process.exit(1)),u.includes("--all")||!!o){const i=s.setAllSkillsVisibility(a,{tier:o,includeInactive:r}),l=o?`tier '${o}'`:r?"all skills":"all active skills";k(`${i} skill(s) (${l}) visibility \u2192 '${a}'.`),console.log(a==="within"?" These skills will NOT egress on cloud sync (private).":` ${i} skills are now cloud-sync-eligible (they leave on the next \`wyrm cloud sync\`).`);return}const n=u[1];n||(f("Usage: wyrm skill share <name|--all|--tier <tier>> [--public|--private] [--include-inactive]"),s.close(),process.exit(1)),s.setSkillVisibility(n,a)||(f(`Skill not found: ${n}`),s.close(),process.exit(1)),k(`Skill "${n}" visibility \u2192 '${a}'.`),console.log(a==="within"?" This skill will NOT egress on cloud sync (private).":" This skill is now cloud-sync-eligible (it leaves on the next `wyrm cloud sync`).");return}f(`Unknown skill subcommand: ${c}`),process.exit(1)}finally{s.close()}}async function ao(u){const c=u[0]??"status",s=R(),a=s.getDatabase();if(a.prepare("PRAGMA table_info(projects)").all().some(e=>e.name==="sync_policy")||(f("Grove sync policy is not on this database yet (upgrade Wyrm so migrations apply)."),s.close(),process.exit(1)),c==="status"||c==="ls"||c==="list"){S("Grove sync policy + leak audit");const e=a.prepare("SELECT id, name, sync_policy FROM projects ORDER BY id").all(),o=["ground_truths","memory_artifacts","quests","design_tokens","design_references"],t=["ground_truths","memory_artifacts","quests","sessions","decision_edges"],n=(l,m,g)=>{let h=0;for(const y of l)try{h+=a.prepare(`SELECT COUNT(*) AS n FROM ${y} WHERE project_id = ? AND ${g}`).get(m).n}catch{}return h},p=[];let i=0;for(const l of e){const m=n(o,l.id,"cross_project_visibility IN ('org','public')"),g=n(t,l.id,"is_shared = 1"),h=l.sync_policy==="private"&&m+g>0;h&&i++;const y=h?d.red(`LEAK: ${m} promoted + ${g} shared in a PRIVATE grove`):`${m} promoted / ${g} shared`;p.push([String(l.id),l.name,l.sync_policy,y])}console.log(I(["#","Grove","sync_policy","rows eligible to leave"],p)),console.log(`
209
+ wyrm skill share <name|--all|--tier T> [--public|--private] [--include-inactive] set cloud-sync visibility (single or bulk)`);return}if(c==="list"||c==="ls"){const a=no();if(S("Bundled skill guides (shipped in the box)"),a.length===0)console.log(d.yellow(" none found next to this install"));else{for(const e of a)console.log(` ${d.cyan(e.name)}`),e.description&&console.log(` ${d.dim(e.description)}`);console.log(d.dim("\n Read one: open its SKILL.md, or `wyrm skill export <dir>` to materialize all."))}const r=R();try{const e=r.listSkills(!0);if(S(`Registered skills in your memory (${e.length})`),e.length===0)console.log(d.dim(" none yet \u2014 register with wyrm_skill_register or author with wyrm_skill_create"));else{for(const o of e.slice(0,40))console.log(` ${d.cyan(o.name)}${o.tier?d.dim(" ["+o.tier+"]"):""}`);e.length>40&&console.log(d.dim(` \u2026 and ${e.length-40} more`))}}finally{r.close()}return}const s=R();try{if(c==="backfill-content"||c==="backfill"){S("Backfill SKILL.md content into the registry");const a=s.backfillSkillContent();k(`${a.filled} filled . ${a.unchanged} unchanged . ${a.missing} missing-file (of ${a.total} registered)`),a.missing>0&&console.log(d.yellow(` ${a.missing} skill(s) had no readable SKILL.md \u2014 re-run after restoring their files.`));return}if(c==="export"){const a=p[1];a||(f("Usage: wyrm skill export <targetDir> [--all]"),s.close(),process.exit(1));const r=p.includes("--all");S(`Export skills \u2192 ${a}`);const e=s.exportSkillContent(a,{includeInactive:r});k(`${e.written} SKILL.md written . ${e.skipped_no_content} skipped (no stored content) (of ${e.total} ${r?"total":"active"})`),e.skipped_no_content>0&&console.log(d.yellow(" Run `wyrm skill backfill-content` on the source machine to populate content first.")),e.collisions>0&&console.log(d.yellow(` ${e.collisions} slug collision(s) disambiguated with a name-hash suffix (distinct skills, same slug) \u2014 no content lost.`));return}if(c==="share"){const a=p.includes("--private")?"within":p.includes("--public")?"public":"org",r=p.includes("--include-inactive"),e=p.indexOf("--tier"),o=e>=0?p[e+1]:void 0;if(e>=0&&(!o||o.startsWith("--"))&&(f("Usage: wyrm skill share --tier <god|mega|atomic> [--public|--private] [--include-inactive]"),s.close(),process.exit(1)),p.includes("--all")||!!o){const i=s.setAllSkillsVisibility(a,{tier:o,includeInactive:r}),l=o?`tier '${o}'`:r?"all skills":"all active skills";k(`${i} skill(s) (${l}) visibility \u2192 '${a}'.`),console.log(a==="within"?" These skills will NOT egress on cloud sync (private).":` ${i} skills are now cloud-sync-eligible (they leave on the next \`wyrm cloud sync\`).`);return}const n=p[1];n||(f("Usage: wyrm skill share <name|--all|--tier <tier>> [--public|--private] [--include-inactive]"),s.close(),process.exit(1)),s.setSkillVisibility(n,a)||(f(`Skill not found: ${n}`),s.close(),process.exit(1)),k(`Skill "${n}" visibility \u2192 '${a}'.`),console.log(a==="within"?" This skill will NOT egress on cloud sync (private).":" This skill is now cloud-sync-eligible (it leaves on the next `wyrm cloud sync`).");return}f(`Unknown skill subcommand: ${c}`),process.exit(1)}finally{s.close()}}async function ao(p){const c=p[0]??"status",s=R(),a=s.getDatabase();if(a.prepare("PRAGMA table_info(projects)").all().some(e=>e.name==="sync_policy")||(f("Grove sync policy is not on this database yet (upgrade Wyrm so migrations apply)."),s.close(),process.exit(1)),c==="status"||c==="ls"||c==="list"){S("Grove sync policy + leak audit");const e=a.prepare("SELECT id, name, sync_policy FROM projects ORDER BY id").all(),o=["ground_truths","memory_artifacts","quests","design_tokens","design_references"],t=["ground_truths","memory_artifacts","quests","sessions","decision_edges"],n=(l,m,g)=>{let h=0;for(const y of l)try{h+=a.prepare(`SELECT COUNT(*) AS n FROM ${y} WHERE project_id = ? AND ${g}`).get(m).n}catch{}return h},u=[];let i=0;for(const l of e){const m=n(o,l.id,"cross_project_visibility IN ('org','public')"),g=n(t,l.id,"is_shared = 1"),h=l.sync_policy==="private"&&m+g>0;h&&i++;const y=h?d.red(`LEAK: ${m} promoted + ${g} shared in a PRIVATE grove`):`${m} promoted / ${g} shared`;u.push([String(l.id),l.name,l.sync_policy,y])}console.log(D(["#","Grove","sync_policy","rows eligible to leave"],u)),console.log(`
210
210
  private = never replicates . cloud = your own cloud backup . team = federates to a team Wyrm`),i>0&&console.log(d.red(`
211
- ! ${i} private grove(s) hold rows marked to leave. Re-private those rows or change the grove lane.`)),s.close();return}if(c==="policy"||c==="set"){const e=u[1],o=u[2];(!e||!["private","cloud","team"].includes(o))&&(f("Usage: wyrm grove policy <project|id> <private|cloud|team>"),s.close(),process.exit(1));let t=N(s,e);!t&&/^\d+$/.test(e)&&(t=a.prepare("SELECT id, name FROM projects WHERE id = ?").get(Number(e))),t||(f(`Grove not found: ${e}`),s.close(),process.exit(1)),a.prepare("UPDATE projects SET sync_policy = ? WHERE id = ?").run(o,t.id),k(`Grove "${t.name}" set to '${o}'.`),o!=="private"&&console.log(` Rows still only leave when also marked ${o==="team"?"is_shared (team)":"org/public (cloud)"}. The grove is the outer gate.`),s.close();return}f(`Unknown grove subcommand: ${c}`),console.log(`Usage:
211
+ ! ${i} private grove(s) hold rows marked to leave. Re-private those rows or change the grove lane.`)),s.close();return}if(c==="policy"||c==="set"){const e=p[1],o=p[2];(!e||!["private","cloud","team"].includes(o))&&(f("Usage: wyrm grove policy <project|id> <private|cloud|team>"),s.close(),process.exit(1));let t=N(s,e);!t&&/^\d+$/.test(e)&&(t=a.prepare("SELECT id, name FROM projects WHERE id = ?").get(Number(e))),t||(f(`Grove not found: ${e}`),s.close(),process.exit(1)),a.prepare("UPDATE projects SET sync_policy = ? WHERE id = ?").run(o,t.id),k(`Grove "${t.name}" set to '${o}'.`),o!=="private"&&console.log(` Rows still only leave when also marked ${o==="team"?"is_shared (team)":"org/public (cloud)"}. The grove is the outer gate.`),s.close();return}f(`Unknown grove subcommand: ${c}`),console.log(`Usage:
212
212
  wyrm grove status
213
- wyrm grove policy <project|id> <private|cloud|team>`),s.close(),process.exit(1)}async function co(u){const c=u[0],{flags:s}=x(u.slice(1)),{ulid:a}=await import("./ulid.js"),{createRun:r,setRunStatus:e,registerAgent:o,getRun:t,getAgents:n}=await import("./handlers/run.js"),{sanitizeActorId:p}=await import("./handlers/boundary.js"),i=g=>typeof g=="string"?p(g):null,l=R(),m=l.getDatabase();try{switch(c){case"start":{const g=(typeof s.orchestrator=="string"?s.orchestrator:"cli").slice(0,200),h=i(s.agent)??p(g)??"cli",y=i(s.parent);if(y&&!t(m,y)){f(`Parent run not found: ${y}`),process.exitCode=1;return}const w=a();r(m,w,y,g),o(m,w,h,"orchestrator"),process.stdout.write(w+`
213
+ wyrm grove policy <project|id> <private|cloud|team>`),s.close(),process.exit(1)}async function co(p){const c=p[0],{flags:s}=x(p.slice(1)),{ulid:a}=await import("./ulid.js"),{createRun:r,setRunStatus:e,registerAgent:o,getRun:t,getAgents:n}=await import("./handlers/run.js"),{sanitizeActorId:u}=await import("./handlers/boundary.js"),i=g=>typeof g=="string"?u(g):null,l=R(),m=l.getDatabase();try{switch(c){case"start":{const g=(typeof s.orchestrator=="string"?s.orchestrator:"cli").slice(0,200),h=i(s.agent)??u(g)??"cli",y=i(s.parent);if(y&&!t(m,y)){f(`Parent run not found: ${y}`),process.exitCode=1;return}const w=a();r(m,w,y,g),o(m,w,h,"orchestrator"),process.stdout.write(w+`
214
214
  `),process.stderr.write(`Run ${w} started by ${g}.
215
- `);break}case"join":{const g=i(s.run),h=i(s.agent);if(!g||!h){f("Usage: wyrm run join --run <RUNID> --agent <ID> [--role <ROLE>]"),process.exitCode=1;return}if(!t(m,g)){f(`Run not found: ${g}`),process.exitCode=1;return}const y=typeof s.role=="string"?s.role.slice(0,200):null;o(m,g,h,y),k(`${h} joined run ${g}${y?` as ${y}`:""}.`);break}case"end":{const g=i(s.run);if(!g){f("Usage: wyrm run end --run <RUNID> [--status completed|failed|abandoned]"),process.exitCode=1;return}if(!t(m,g)){f(`Run not found: ${g}`),process.exitCode=1;return}const h=typeof s.status=="string"?s.status:"completed",w=["completed","failed","abandoned"].includes(h)?h:"completed";e(m,g,w),k(`Run ${g} ended (${w}).`);break}case"status":{const g=i(s.run);if(!g){f("Usage: wyrm run status --run <RUNID>"),process.exitCode=1;return}const h=t(m,g);if(!h){f(`Run not found: ${g}`),process.exitCode=1;return}const y=n(m,g);S(`Run ${h.run_id} [${h.status}]`),h.orchestrator&&console.log(d.bold("Orchestrator: ")+h.orchestrator),h.parent_run_id&&console.log(d.bold("Parent: ")+h.parent_run_id),console.log(d.bold("Created: ")+h.created_at),console.log(d.bold("Updated: ")+h.updated_at),y.length>0?console.log(I(["Agent","Role","Joined"],y.map(w=>[w.agent_id,w.role??"",w.joined_at]))):console.log(d.dim(" No agents registered."));break}default:f("Usage: wyrm run <start|join|end|status> [options]"),console.log(" wyrm run start [--orchestrator NAME] [--agent ID] [--parent RUNID]"),console.log(" wyrm run join --run RUNID --agent ID [--role ROLE]"),console.log(" wyrm run end --run RUNID [--status completed|failed|abandoned]"),console.log(" wyrm run status --run RUNID"),process.exitCode=1;return}}finally{l.close()}}if(Y==="--version"||Y==="-v"||Y==="version"){const u=A();console.log(`${u.name??"wyrm-mcp"} v${u.version??"unknown"}`)}else!Y||Y==="--help"||Y==="-h"||Y==="help"?Te():(async()=>{try{switch(Y){case"search":await at(_);break;case"ls":await ct(_);break;case"show":await lt(_);break;case"capture":await dt(_);break;case"rehydrate":await pt(_);break;case"session":await ht(_);break;case"presence":await wt(_);break;case"digest":await ut(_);break;case"bridge":await yt(_);break;case"metabolize":await ft(_);break;case"forget":await mt(_);break;case"entities":await gt(_);break;case"render":await bt(_);break;case"reverse-bridge":await vt(_);break;case"import":await $t(_);break;case"stats":await _t(_);break;case"review":await Et(_);break;case"sync":await St(_);break;case"cloud":{const{cmdCloud:u}=await import("./cloud/cli.js");await u(_);break}case"grove":await ao(_);break;case"run":await co(_);break;case"skill":await io(_);break;case"prune":await Rt(_);break;case"license":await xt();break;case"login":await jt();break;case"activate":await Tt(_);break;case"maintenance":await Nt(_);break;case"doctor":await Pt(_);break;case"report":await Lt(_);break;case"metrics":await Mt(_);break;case"failure":await Wt(_);break;case"recall":await Ft(_);break;case"project":await qt(_);break;case"index":await Yt(_);break;case"vectors":await Vt(_);break;case"update":await Kt(_);break;case"upgrade":await Bt(_);break;case"prompt":await Gt(_);break;case"hours":await Jt(_);break;case"invoice":await zt(_);break;case"agent":await Qt(_);break;case"feedback":await Zt(_);break;case"setup":await Xt(_);break;case"intro":{const{renderIntro:u}=await import("./visibility.js");console.log(u(A().version??"unknown"));break}case"events":await eo(_);break;case"watch":await to(_);break;case"embed":await oo(_);break;case"harvest":await so(_);break;case"vault":await ro(_);break;case"connector":case"connectors":await Ct(_);break;case"statusline":{const{installClaudeStatusline:u,removeClaudeStatusline:c}=await import("./autoconfig.js"),s=_.includes("--remove")?c():u();s?k(s.message):f("Claude Code not detected (~/.claude missing).");break}case"guard":{const{installWyrmGuardHooks:u,removeWyrmGuardHooks:c,wyrmGuardHookStatus:s}=await import("./autoconfig.js");if(_.includes("--status")){const r=s();if(console.log(` settings: ${r.settingsPath}`),r.installed){k(`wyrm-guard hooks installed (${r.commands.length} entr${r.commands.length===1?"y":"ies"})`);for(const e of r.commands)console.log(d.dim(` ${e}`))}else console.log(d.yellow(" wyrm-guard hooks not installed \u2014 run: wyrm guard"));break}const a=_.includes("--remove")||_.includes("--uninstall")?c():u();a?a.action==="failed"?(f(a.message),process.exitCode=1):k(a.message):f("Claude Code not detected (~/.claude missing).");break}case"memo":{const{installWyrmMemoHooks:u,removeWyrmMemoHooks:c,wyrmMemoHookStatus:s,getDefaultDbPath:a}=await import("./autoconfig.js");if(_.includes("--status")){const e=s();if(console.log(` settings: ${e.settingsPath}`),e.installed){k(`wyrm-memo hooks installed (${e.commands.length} entr${e.commands.length===1?"y":"ies"})`);for(const o of e.commands)console.log(d.dim(` ${o}`))}else console.log(d.yellow(" wyrm-memo hooks not installed \u2014 run: wyrm memo"));console.log(d.dim(` mode: ${(process.env.WYRM_MEMO_MODE??"observe").toLowerCase()} (WYRM_MEMO_MODE=off|observe|warn)`));break}if(_.includes("--report")){const{openDatabase:e}=await import("./storage/index.js"),{ReadMemos:o}=await import("./read-memo.js"),t=process.env.WYRM_DB_PATH??a(),n=e(t,{readonly:!0,fileMustExist:!0});try{const p=new o(n).summary(),i=n.prepare("SELECT path, hits, tokens_avoided, seen_count, last_seen FROM read_memos ORDER BY hits DESC, last_seen DESC LIMIT 10").all();k(`token firewall: ${p.memos} memo(s), ${p.hits} repeat read(s) seen, ~${p.tokensAvoided} tokens at stake`),console.log(d.dim(` mode: ${(process.env.WYRM_MEMO_MODE??"observe").toLowerCase()} \u2014 in observe mode nothing was withheld; the number is what warn mode would offer back`));for(const l of i)console.log(d.dim(` ${l.hits}x ~${l.tokens_avoided} tok seen ${l.seen_count}x ${l.last_seen} ${l.path}`))}finally{n.close()}break}const r=_.includes("--remove")||_.includes("--uninstall")?c():u();r?r.action==="failed"?(f(r.message),process.exitCode=1):k(r.message):f("Claude Code not detected (~/.claude missing).");break}case"ui":case"dashboard":_.includes("--ui")||_.push("--ui");case"serve":{const u=_.includes("--ui");if(u){const{enableDevMode:e}=await import("./http-auth.js");e()}const{server:c,primeLicenseRevocations:s}=await import("./http-fast.js");s();const a=parseInt(process.env.WYRM_PORT??process.env.PORT??"3333",10),r=process.env.WYRM_BIND_HOST||"127.0.0.1";c.listen(a,r,()=>{if(k(`Wyrm HTTP server running on ${r}:${a}`),process.env.WYRM_UI_READONLY==="1"&&console.log("\u{1F512} READ-ONLY mode: writes + off-box egress are blocked; safe to expose."),u){const e=`http://localhost:${a}/ui`;console.log(`\u{1F5A5}\uFE0F Dashboard: ${e}`),import("child_process").then(({spawn:o})=>{const t=process.platform;try{const n=t==="darwin"?o("open",[e],{stdio:"ignore",detached:!0}):t==="win32"?o("cmd",["/c","start","",e],{stdio:"ignore",detached:!0}):o("xdg-open",[e],{stdio:"ignore",detached:!0});n.on("error",()=>{}),n.unref()}catch{}}).catch(()=>{})}});break}default:f(`Unknown command: ${Y}`),Te(),process.exit(1)}}catch(u){f(String(u)),process.exit(1)}})();
215
+ `);break}case"join":{const g=i(s.run),h=i(s.agent);if(!g||!h){f("Usage: wyrm run join --run <RUNID> --agent <ID> [--role <ROLE>]"),process.exitCode=1;return}if(!t(m,g)){f(`Run not found: ${g}`),process.exitCode=1;return}const y=typeof s.role=="string"?s.role.slice(0,200):null;o(m,g,h,y),k(`${h} joined run ${g}${y?` as ${y}`:""}.`);break}case"end":{const g=i(s.run);if(!g){f("Usage: wyrm run end --run <RUNID> [--status completed|failed|abandoned]"),process.exitCode=1;return}if(!t(m,g)){f(`Run not found: ${g}`),process.exitCode=1;return}const h=typeof s.status=="string"?s.status:"completed",w=["completed","failed","abandoned"].includes(h)?h:"completed";e(m,g,w),k(`Run ${g} ended (${w}).`);break}case"status":{const g=i(s.run);if(!g){f("Usage: wyrm run status --run <RUNID>"),process.exitCode=1;return}const h=t(m,g);if(!h){f(`Run not found: ${g}`),process.exitCode=1;return}const y=n(m,g);S(`Run ${h.run_id} [${h.status}]`),h.orchestrator&&console.log(d.bold("Orchestrator: ")+h.orchestrator),h.parent_run_id&&console.log(d.bold("Parent: ")+h.parent_run_id),console.log(d.bold("Created: ")+h.created_at),console.log(d.bold("Updated: ")+h.updated_at),y.length>0?console.log(D(["Agent","Role","Joined"],y.map(w=>[w.agent_id,w.role??"",w.joined_at]))):console.log(d.dim(" No agents registered."));break}default:f("Usage: wyrm run <start|join|end|status> [options]"),console.log(" wyrm run start [--orchestrator NAME] [--agent ID] [--parent RUNID]"),console.log(" wyrm run join --run RUNID --agent ID [--role ROLE]"),console.log(" wyrm run end --run RUNID [--status completed|failed|abandoned]"),console.log(" wyrm run status --run RUNID"),process.exitCode=1;return}}finally{l.close()}}if(Y==="--version"||Y==="-v"||Y==="version"){const p=A();console.log(`${p.name??"wyrm-mcp"} v${p.version??"unknown"}`)}else!Y||Y==="--help"||Y==="-h"||Y==="help"?Te():(async()=>{try{switch(Y){case"search":await at(E);break;case"ls":await ct(E);break;case"show":await lt(E);break;case"capture":await dt(E);break;case"rehydrate":await pt(E);break;case"session":await ht(E);break;case"presence":await wt(E);break;case"digest":await ut(E);break;case"bridge":await yt(E);break;case"metabolize":await ft(E);break;case"forget":await mt(E);break;case"entities":await gt(E);break;case"render":await bt(E);break;case"reverse-bridge":await vt(E);break;case"import":await $t(E);break;case"stats":await Et(E);break;case"review":await _t(E);break;case"sync":await St(E);break;case"cloud":{const{cmdCloud:p}=await import("./cloud/cli.js");await p(E);break}case"grove":await ao(E);break;case"run":await co(E);break;case"skill":await io(E);break;case"prune":await Rt(E);break;case"license":await xt();break;case"login":await jt();break;case"activate":await Tt(E);break;case"maintenance":await Nt(E);break;case"doctor":await Pt(E);break;case"report":await Lt(E);break;case"metrics":await Mt(E);break;case"failure":await Wt(E);break;case"recall":await Ft(E);break;case"project":await qt(E);break;case"index":await Yt(E);break;case"vectors":await Vt(E);break;case"update":await Kt(E);break;case"upgrade":await Bt(E);break;case"prompt":await Gt(E);break;case"hours":await Jt(E);break;case"invoice":await zt(E);break;case"agent":await Qt(E);break;case"feedback":await Zt(E);break;case"setup":await Xt(E);break;case"intro":{const{renderIntro:p}=await import("./visibility.js");console.log(p(A().version??"unknown"));break}case"events":await eo(E);break;case"watch":await to(E);break;case"embed":await oo(E);break;case"harvest":await so(E);break;case"vault":await ro(E);break;case"connector":case"connectors":await Ct(E);break;case"statusline":{const{installClaudeStatusline:p,removeClaudeStatusline:c}=await import("./autoconfig.js"),s=E.includes("--remove")?c():p();s?k(s.message):f("Claude Code not detected (~/.claude missing).");break}case"guard":{const{installWyrmGuardHooks:p,removeWyrmGuardHooks:c,wyrmGuardHookStatus:s}=await import("./autoconfig.js");if(E.includes("--status")){const r=s();if(console.log(` settings: ${r.settingsPath}`),r.installed){k(`wyrm-guard hooks installed (${r.commands.length} entr${r.commands.length===1?"y":"ies"})`);for(const e of r.commands)console.log(d.dim(` ${e}`))}else console.log(d.yellow(" wyrm-guard hooks not installed \u2014 run: wyrm guard"));break}const a=E.includes("--remove")||E.includes("--uninstall")?c():p();a?a.action==="failed"?(f(a.message),process.exitCode=1):k(a.message):f("Claude Code not detected (~/.claude missing).");break}case"memo":{const{installWyrmMemoHooks:p,removeWyrmMemoHooks:c,wyrmMemoHookStatus:s,getDefaultDbPath:a}=await import("./autoconfig.js");if(E.includes("--status")){const e=s();if(console.log(` settings: ${e.settingsPath}`),e.installed){k(`wyrm-memo hooks installed (${e.commands.length} entr${e.commands.length===1?"y":"ies"})`);for(const o of e.commands)console.log(d.dim(` ${o}`))}else console.log(d.yellow(" wyrm-memo hooks not installed \u2014 run: wyrm memo"));console.log(d.dim(` mode: ${(process.env.WYRM_MEMO_MODE??"observe").toLowerCase()} (WYRM_MEMO_MODE=off|observe|warn)`));break}if(E.includes("--report")){const{openDatabase:e}=await import("./storage/index.js"),{ReadMemos:o}=await import("./read-memo.js"),t=process.env.WYRM_DB_PATH??a(),n=e(t,{readonly:!0,fileMustExist:!0});try{const u=new o(n).summary(),i=n.prepare("SELECT path, hits, tokens_avoided, seen_count, last_seen FROM read_memos ORDER BY hits DESC, last_seen DESC LIMIT 10").all();k(`token firewall: ${u.memos} memo(s), ${u.hits} repeat read(s) seen, ~${u.tokensAvoided} tokens at stake`),console.log(d.dim(` mode: ${(process.env.WYRM_MEMO_MODE??"observe").toLowerCase()} \u2014 in observe mode nothing was withheld; the number is what warn mode would offer back`));for(const l of i)console.log(d.dim(` ${l.hits}x ~${l.tokens_avoided} tok seen ${l.seen_count}x ${l.last_seen} ${l.path}`))}finally{n.close()}break}const r=E.includes("--remove")||E.includes("--uninstall")?c():p();r?r.action==="failed"?(f(r.message),process.exitCode=1):k(r.message):f("Claude Code not detected (~/.claude missing).");break}case"ui":case"dashboard":E.includes("--ui")||E.push("--ui");case"serve":{const p=E.includes("--ui");if(p){const{enableDevMode:e}=await import("./http-auth.js");e()}const{server:c,primeLicenseRevocations:s}=await import("./http-fast.js");s();const a=parseInt(process.env.WYRM_PORT??process.env.PORT??"3333",10),r=process.env.WYRM_BIND_HOST||"127.0.0.1";c.listen(a,r,()=>{if(k(`Wyrm HTTP server running on ${r}:${a}`),process.env.WYRM_UI_READONLY==="1"&&console.log("\u{1F512} READ-ONLY mode: writes + off-box egress are blocked; safe to expose."),p){const e=`http://localhost:${a}/ui`;console.log(`\u{1F5A5}\uFE0F Dashboard: ${e}`),import("child_process").then(({spawn:o})=>{const t=process.platform;try{const n=t==="darwin"?o("open",[e],{stdio:"ignore",detached:!0}):t==="win32"?o("cmd",["/c","start","",e],{stdio:"ignore",detached:!0}):o("xdg-open",[e],{stdio:"ignore",detached:!0});n.on("error",()=>{}),n.unref()}catch{}}).catch(()=>{})}});break}default:f(`Unknown command: ${Y}`),Te(),process.exit(1)}}catch(p){f(String(p)),process.exit(1)}})();
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "contract": "wyrm-cloud-memory-v1",
3
- "version": "9.1.1",
3
+ "version": "9.1.2",
4
4
  "generatedBy": "scripts/gen-tool-manifest.mjs (from the dist/ registry modules — never hand-edited)",
5
5
  "tiers": {
6
6
  "core": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wyrm-mcp",
3
- "version": "9.1.1",
3
+ "version": "9.1.2",
4
4
  "mcpName": "lk.ghosts/wyrm",
5
5
  "description": "Local-first persistent memory for AI agents over MCP. Ground truths, negative learning (recorded failures block repeats), decision causality, hybrid recall, negotiated recall (clean-mode injection you steer), live memory streams, run-attributed fleet memory \u2014 a structured SQLite memory on your machine, no cloud or LLM required. Claude / Copilot / Cursor / Windsurf / Codex.",
6
6
  "type": "module",