wyrm-mcp 7.5.2 → 7.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE CHANGED
@@ -18,8 +18,9 @@ Use of the official Wyrm builds and the Wyrm hosted services is governed by the
18
18
  Wyrm Terms of Service and requires a Ghost Protocol account.
19
19
 
20
20
  HISTORICAL NOTE: versions of Wyrm published on or before 2026-06-14 (the npm
21
- package "wyrm-mcp" at version 7.2.1 and earlier) were released under
22
- AGPL-3.0-or-later; that license continues to apply to those specific versions.
21
+ package "wyrm-mcp" at version 7.2.1 and earlier) were released as open source
22
+ under the license text shipped inside those versions' packages; that license
23
+ continues to apply to those specific versions only.
23
24
  ALL SUBSEQUENT VERSIONS ARE PROPRIETARY AND ARE NOT OPEN SOURCE.
24
25
 
25
26
  Commercial and licensing inquiries: ryan@ghosts.lk
package/NOTICE CHANGED
@@ -7,8 +7,8 @@ Protocol (Pvt) Ltd, Colombo, Sri Lanka.
7
7
 
8
8
  LICENSE: Proprietary — see the LICENSE file. No open-source license is granted for
9
9
  this or any version published after 2026-06-14. Versions of "wyrm-mcp" at 7.2.1 and
10
- earlier were released under AGPL-3.0-or-later, which continues to apply to those
11
- specific versions only. Use of the official builds and the Wyrm hosted services
10
+ earlier were released as open source under the license text shipped inside those
11
+ versions' packages, which continues to apply to those specific versions only. Use of the official builds and the Wyrm hosted services
12
12
  requires a Ghost Protocol account and is governed by the Wyrm Terms of Service.
13
13
 
14
14
  Trademarks: "Wyrm", "Ghost Protocol", and the silver-dragon mark are trademarks of
@@ -1 +1 @@
1
- import{getLicenseInfo as r,selfTestVerifier as n}from"./license.js";import{OFFICIAL_BUILD as e}from"./build-flags.js";const i=e||/^(1|true|yes|on)$/i.test(process.env.WYRM_REQUIRE_ACTIVATION??""),t=e?!n().ok:!1;if(t)try{console.error("Wyrm: integrity self-test FAILED \u2014 license verifier tampered; activation gate locked.")}catch{}function s(){return t?!1:!i||r().valid}function c(){return{content:[{type:"text",text:"\u{1F512} Wyrm requires a free account on this build.\n\nRun `wyrm login` in your terminal to sign in and activate (free). Your memory stays local \u2014 login just ties usage to an account so it can be managed.\n\nAlready logged in? Your activation may have expired \u2014 run `wyrm login` again to refresh."}],isError:!0}}export{i as REQUIRE_ACTIVATION,c as activationGateResult,s as activationSatisfied};
1
+ import{getLicenseInfo as t}from"./license.js";const e=/^(1|true|yes|on)$/i.test(process.env.WYRM_REQUIRE_ACTIVATION??"");function o(){return!e||t().valid}function i(){return{content:[{type:"text",text:"\u{1F512} This deployment requires an account (WYRM_REQUIRE_ACTIVATION is set).\n\nRun `wyrm login` in your terminal to sign in and activate. Your memory stays local \u2014 login just ties usage to an account so it can be managed.\n\nAlready logged in? Your activation may have expired \u2014 run `wyrm login` again to refresh."}],isError:!0}}export{e as REQUIRE_ACTIVATION,i as activationGateResult,o as activationSatisfied};
@@ -1,16 +1,17 @@
1
- import{existsSync as d,readFileSync as p,writeFileSync as l,mkdirSync as k,copyFileSync as Y,chmodSync as q}from"fs";import{homedir as m,platform as W}from"os";import{join as i,dirname as w,resolve as V}from"path";import{fileURLToPath as O}from"url";import{spawnSync as P}from"child_process";function Q(){const e=m(),t=W(),o=t==="darwin"?i(e,"Library","Application Support","Code","User"):t==="win32"?i(e,"AppData","Roaming","Code","User"):i(e,".config","Code","User"),s=t==="darwin"?i(e,"Library","Application Support","Code - Insiders","User"):t==="win32"?i(e,"AppData","Roaming","Code - Insiders","User"):i(e,".config","Code - Insiders","User"),n=t==="darwin"?i(e,"Library","Application Support","Claude"):t==="win32"?i(e,"AppData","Roaming","Claude"):i(e,".config","claude"),r=t==="darwin"?i(e,".cursor"):t==="win32"?i(e,".cursor"):i(e,".cursor"),c=t==="darwin"?i(e,".codeium","windsurf"):t==="win32"?i(e,".codeium","windsurf"):i(e,".codeium","windsurf"),u=t==="darwin"?i(e,".config","zed"):t==="win32"?i(e,"AppData","Roaming","Zed"):i(e,".config","zed"),a=i(e,".continue");return[{id:"vscode-copilot",name:"VS Code (Copilot)",icon:"\u{1F4BB}",configPath:i(o,"settings.json"),configFormat:"vscode",mcpKey:"mcp.servers",detected:!1,configured:!1},{id:"vscode-insiders",name:"VS Code Insiders",icon:"\u{1F7E2}",configPath:i(s,"settings.json"),configFormat:"vscode",mcpKey:"mcp.servers",detected:!1,configured:!1},{id:"claude-desktop",name:"Claude Desktop",icon:"\u{1F916}",configPath:i(n,"claude_desktop_config.json"),configFormat:"mcp-json",mcpKey:"mcpServers",detected:!1,configured:!1},{id:"cursor",name:"Cursor",icon:"\u{1F4D0}",configPath:i(r,"mcp.json"),configFormat:"mcp-json",mcpKey:"mcpServers",detected:!1,configured:!1},{id:"windsurf",name:"Windsurf",icon:"\u{1F3C4}",configPath:i(c,"mcp_config.json"),configFormat:"mcp-json",mcpKey:"mcpServers",detected:!1,configured:!1},{id:"zed",name:"Zed",icon:"\u26A1",configPath:i(u,"settings.json"),configFormat:"zed",mcpKey:"context_servers",detected:!1,configured:!1},{id:"continue",name:"Continue",icon:"\u{1F504}",configPath:i(a,"config.json"),configFormat:"mcp-json",mcpKey:"mcpServers",detected:!1,configured:!1}]}function S(){const e=Q();for(const t of e){const o=w(t.configPath);if(t.detected=d(o),t.detected&&d(t.configPath))try{const s=p(t.configPath,"utf-8"),n=R(s);t.configured=X(n,t)}catch{t.configured=!1}t.detected&&(t.version=Z(t))}return e}function Z(e){try{switch(e.id){case"vscode-copilot":case"vscode-insiders":{const t=e.id==="vscode-insiders"?"code-insiders":"code",o=P(t,["--version"],{encoding:"utf-8",timeout:5e3});if(o.stdout)return o.stdout.split(`
2
- `)[0];break}case"cursor":{const t=P("cursor",["--version"],{encoding:"utf-8",timeout:5e3});if(t.stdout)return t.stdout.split(`
3
- `)[0];break}}}catch{}}function X(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 K(){try{const s=P("which",["wyrm-mcp"],{encoding:"utf-8",timeout:5e3});if(s.stdout?.trim())return s.stdout.trim()}catch{}try{const n=P("npm",["root","-g"],{encoding:"utf-8",timeout:5e3}).stdout?.trim();if(n){const r=i(n,"wyrm-mcp","dist","index.js");if(d(r))return r}}catch{}const e=V(__dirname,"index.js");if(d(e))return e;const t=i(m(),".wyrm","node_modules","wyrm-mcp","dist","index.js");if(d(t))return t;const o=[i(m(),"Git Projects","Wyrm","packages","mcp-server","dist","index.js"),i(m(),"projects","Wyrm","packages","mcp-server","dist","index.js"),i(m(),"dev","Wyrm","packages","mcp-server","dist","index.js")];for(const s of o)if(d(s))return s;return"wyrm-mcp"}function U(){return i(m(),".wyrm","wyrm.db")}function ee(e,t){const o=t.serverPath,s=o==="wyrm-mcp",n=o.endsWith(".js");switch(e.configFormat){case"vscode":return{command:s?"npx":"node",args:s?["wyrm-mcp"]:[o],env:{WYRM_DB_PATH:t.dbPath}};case"mcp-json":return{command:s?"npx":"node",args:s?["wyrm-mcp"]:[o],env:{WYRM_DB_PATH:t.dbPath}};case"zed":return{command:s?"npx":"node",args:s?["wyrm-mcp"]:[o],env:{WYRM_DB_PATH:t.dbPath}};default:return{}}}function F(e,t){if(!e.detected)return{client:e,action:"skipped",message:`${e.name} not detected`};try{const o=w(e.configPath);d(o)||k(o,{recursive:!0});let s={},n=!1;if(d(e.configPath))try{const a=p(e.configPath,"utf-8");s=R(a),n=!0}catch{s={}}let r;n&&(r=`${e.configPath}.wyrm-backup`,l(r,p(e.configPath)));const c=ee(e,t);switch(e.configFormat){case"vscode":{s.mcp||(s.mcp={});const a=s.mcp;a.servers||(a.servers={});const f=a.servers;f.wyrm=c;break}case"mcp-json":{s[e.mcpKey]||(s[e.mcpKey]={});const a=s[e.mcpKey];a.wyrm=c;break}case"zed":{s[e.mcpKey]||(s[e.mcpKey]={});const a=s[e.mcpKey];a.wyrm=c;break}}l(e.configPath,JSON.stringify(s,null,2)+`
4
- `,"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 te(e){if(!e.detected||!e.configured)return{client:e,action:"skipped",message:`${e.name} not configured`};try{if(!d(e.configPath))return{client:e,action:"skipped",message:`${e.name} config not found`};const t=p(e.configPath,"utf-8"),o=R(t),s=`${e.configPath}.wyrm-backup`;switch(l(s,t),e.configFormat){case"vscode":{const r=o.mcp?.servers;r&&delete r.wyrm;break}case"mcp-json":case"zed":{const n=o[e.mcpKey];n&&delete n.wyrm;break}}return l(e.configPath,JSON.stringify(o,null,2)+`
5
- `,"utf-8"),e.configured=!1,{client:e,action:"configured",message:`Removed Wyrm from ${e.name}`,backup:s}}catch(t){return{client:e,action:"failed",message:`Failed to remove from ${e.name}: ${t}`}}}function A(){return{id:"claude-code-hooks",name:"Claude Code (auto-memory)",icon:"\u{1FA9D}",configPath:i(m(),".claude","settings.json"),configFormat:"mcp-json",mcpKey:"",detected:!0,configured:!1}}const E=[{script:"wyrm-session-capture.mjs",events:["SessionEnd","PreCompact"]},{script:"wyrm-session-rehydrate.mjs",events:["SessionStart"]},{script:"wyrm-session-prune.mjs",events:["SessionEnd"]},{script:"wyrm-tool-call-trace.mjs",events:["PostToolUse"]},{script:"wyrm-run-auto.mjs",events:["SessionStart",{event:"PreToolUse",matcher:"Task"},"SessionEnd"]}];function oe(){const e=i(m(),".claude");if(!d(e))return null;const t=A();try{const o=i(e,"hooks");d(o)||k(o,{recursive:!0});const s=w(O(import.meta.url)),n=i(s,"..","scripts","hooks"),r=t.configPath;let c={};if(d(r))try{c=JSON.parse(p(r,"utf-8"))}catch{c={}}const u=c.hooks??{};let a=!1;const f=[];for(const h of E){const g=i(n,h.script);if(!d(g)){f.push(h.script);continue}const _=i(o,h.script);Y(g,_);try{q(_,493)}catch{}const C=`node ${_}`;for(const v of h.events){const x=typeof v=="string"?v:v.event,J=typeof v=="string"?void 0:v.matcher,$=Array.isArray(u[x])?u[x]:[];if(!$.some(b=>Array.isArray(b.hooks)&&b.hooks.some(z=>z.command===C))){const b=J?{matcher:J,hooks:[{type:"command",command:C,timeout:30}]}:{hooks:[{type:"command",command:C,timeout:30}]};$.push(b),a=!0}u[x]=$}}c.hooks=u,l(r,JSON.stringify(c,null,2)+`
6
- `,"utf-8"),t.configured=!0;const y=f.length?` (missing from package: ${f.join(", ")})`:"";return{client:t,action:a?"configured":"skipped",message:(a?"auto-memory hooks installed":"already installed")+y}}catch(o){return{client:t,action:"failed",message:o instanceof Error?o.message:String(o)}}}function ge(){const e=i(m(),".claude");if(!d(e))return null;const t=A();try{const o=w(O(import.meta.url)),s=`node ${i(o,"wyrm-statusline.js")}`,n=t.configPath;let r={};if(d(n))try{r=JSON.parse(p(n,"utf-8"))}catch{r={}}const c=r.statusLine;if(c?.command&&!/wyrm-statusline/.test(c.command))return{client:t,action:"skipped",message:`a non-Wyrm statusLine is already set (${c.command}); not overwriting`};const u=c?.command===s;return r.statusLine={type:"command",command:s,padding:0},l(n,JSON.stringify(r,null,2)+`
7
- `,"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 ye(){const e=i(m(),".claude","settings.json");if(!d(e))return null;const t=A();try{const o=JSON.parse(p(e,"utf-8")),s=o.statusLine;return s?.command&&/wyrm-statusline/.test(s.command)?(delete o.statusLine,l(e,JSON.stringify(o,null,2)+`
8
- `,"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 se(){const e=i(m(),".claude","settings.json");if(!d(e))return null;const t=A();try{const o=JSON.parse(p(e,"utf-8")),s=i(m(),".claude","hooks"),n=new Set(E.map(u=>`node ${i(s,u.script)}`)),r=new Set(E.flatMap(u=>u.events.map(a=>typeof a=="string"?a:a.event))),c=o.hooks;if(c){for(const u of r)Array.isArray(c[u])&&(c[u]=c[u].map(a=>({...a,hooks:Array.isArray(a.hooks)?a.hooks.filter(f=>!n.has(f.command??"")):a.hooks})).filter(a=>!Array.isArray(a.hooks)||a.hooks.length>0),c[u].length===0&&delete c[u]);Object.keys(c).length===0&&delete o.hooks}return l(e,JSON.stringify(o,null,2)+`
9
- `,"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 M=/wyrm-guard/,re=["Bash","Edit|Write|MultiEdit|NotebookEdit"];function j(e){return e.settingsPath??process.env.CLAUDE_SETTINGS??i(m(),".claude","settings.json")}function T(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 L(e){return W()==="win32"?`"${e.replace(/"/g,'""')}"`:`'${e.replace(/'/g,"'\\''")}'`}function ne(){const e=w(O(import.meta.url)),t=i(e,"wyrm-guard.js");if(d(t))return`node ${L(t)}`;try{const o=W()==="win32"?"where":"which",n=P(o,["wyrm-guard"],{encoding:"utf-8",timeout:5e3}).stdout?.trim().split(`
10
- `)[0]?.trim();if(n)return L(n)}catch{}return null}function B(e){const t=e.hooks;if(!t||typeof t!="object"||Array.isArray(t))return!1;let o=!1;for(const s of Object.keys(t)){const n=t[s];if(!Array.isArray(n))continue;let r=!1;const c=[];for(const u of n){const a=u;if(!a||typeof a!="object"||!Array.isArray(a.hooks)){c.push(u);continue}const f=a.hooks.filter(y=>!M.test(y?.command??""));if(f.length===a.hooks.length){c.push(u);continue}o=!0,r=!0,f.length>0&&c.push({...a,hooks:f})}r&&(c.length===0?delete t[s]:t[s]=c)}return o&&Object.keys(t).length===0&&delete e.hooks,o}function ie(e={}){if(!!!(e.settingsPath??process.env.CLAUDE_SETTINGS)&&!d(i(m(),".claude")))return{client:T(j(e)),action:"skipped",message:"Claude Code not detected (~/.claude missing) \u2014 the failure firewall is NOT enforced on this machine; install Claude Code then re-run `wyrm guard`"};const o=j(e),s=T(o);try{const n=e.guardCommand??ne();if(!n)return{client:s,action:"skipped",message:"wyrm-guard bin not found (npm i -g wyrm-mcp, or build dist/) \u2014 firewall hooks not installed"};k(w(o),{recursive:!0});let r={};if(d(o))try{const g=JSON.parse(p(o,"utf-8"));if(typeof g!="object"||g===null||Array.isArray(g))throw new Error("not an object");r=g}catch{return{client:s,action:"failed",message:`${o} is not valid JSON \u2014 refusing to overwrite it (fix or remove the file, then re-run)`}}const c=r.hooks;if(c!==void 0&&(typeof c!="object"||c===null||Array.isArray(c)))return{client:s,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=c?.PreToolUse;if(u!==void 0&&!Array.isArray(u))return{client:s,action:"failed",message:`${o} hooks.PreToolUse has an unexpected shape (not an array) \u2014 refusing to modify (fix the file, then re-run)`};const a=JSON.stringify(r);B(r);const f=r.hooks??{},y=Array.isArray(f.PreToolUse)?f.PreToolUse:[];for(const g of re)y.push({matcher:g,hooks:[{type:"command",command:n,timeout:1}]});f.PreToolUse=y,r.hooks=f;const h=JSON.stringify(r)!==a;return h&&l(o,JSON.stringify(r,null,2)+`
11
- `,"utf-8"),s.configured=!0,{client:s,action:h?"configured":"skipped",message:h?"failure-firewall hooks installed (PreToolUse: Bash + Edit|Write|MultiEdit|NotebookEdit)":"failure-firewall hooks already installed"}}catch(n){return{client:s,action:"failed",message:n instanceof Error?n.message:String(n)}}}function ce(e={}){const t=j(e);if(!d(t))return null;const o=T(t);try{let s;try{const r=JSON.parse(p(t,"utf-8"));if(typeof r!="object"||r===null||Array.isArray(r))throw new Error("not an object");s=r}catch{return{client:o,action:"failed",message:`${t} is not valid JSON \u2014 not touching it`}}return B(s)?(l(t,JSON.stringify(s,null,2)+`
12
- `,"utf-8"),{client:o,action:"configured",message:"failure-firewall hooks removed"}):{client:o,action:"skipped",message:"no wyrm-guard hooks installed"}}catch(s){return{client:o,action:"failed",message:s instanceof Error?s.message:String(s)}}}function we(e={}){const t=j(e),o=[];try{const n=JSON.parse(p(t,"utf-8")).hooks??{};for(const r of Object.values(n))if(Array.isArray(r))for(const c of r)for(const u of c?.hooks??[]){const a=u?.command;a&&M.test(a)&&o.push(a)}}catch{}return{settingsPath:t,installed:o.length>0,commands:o}}function ae(e){const t={serverPath:e?.serverPath||K(),dbPath:e?.dbPath||U(),httpPort:e?.httpPort},o=i(m(),".wyrm");d(o)||k(o,{recursive:!0}),I(t);const s=S(),n=[];for(const u of s)n.push(F(u,t));const r=oe();r&&n.push(r);const c=ie();return c&&n.push(c),n}function ve(){const e=S(),t=[];for(const n of e)t.push(te(n));const o=se();o&&t.push(o);const s=ce();return s&&t.push(s),t}function ue(e,t){const o={serverPath:t?.serverPath||K(),dbPath:t?.dbPath||U(),httpPort:t?.httpPort};I(o);const s=S(),n=[];for(const r of s)e.includes(r.id)&&n.push(F(r,o));return n}function I(e){const t=i(m(),".wyrm","wyrm-config.json"),o=S().filter(n=>n.configured).map(n=>n.id),s={version:"3.0.0",serverPath:e.serverPath,dbPath:e.dbPath,httpPort:e.httpPort,configuredClients:o,lastSetup:new Date().toISOString(),autoUpdate:!0};l(t,JSON.stringify(s,null,2)+`
13
- `,"utf-8")}function H(){const e=i(m(),".wyrm","wyrm-config.json");if(!d(e))return null;try{return JSON.parse(p(e,"utf-8"))}catch{return null}}function ke(){const e=H();return e?ue(e.configuredClients,{serverPath:e.serverPath,dbPath:e.dbPath,httpPort:e.httpPort}):ae()}const G="<!-- wyrm:start -->",D="<!-- wyrm:end -->",N=`${G}
1
+ import{existsSync as d,readFileSync as p,writeFileSync as l,mkdirSync as P,copyFileSync as Y,chmodSync as q}from"fs";import{homedir as m,platform as j}from"os";import{join as i,dirname as w,resolve as V}from"path";import{fileURLToPath as O}from"url";import{spawnSync as v}from"child_process";function Q(){const e=m(),t=j(),o=t==="darwin"?i(e,"Library","Application Support","Code","User"):t==="win32"?i(e,"AppData","Roaming","Code","User"):i(e,".config","Code","User"),s=t==="darwin"?i(e,"Library","Application Support","Code - Insiders","User"):t==="win32"?i(e,"AppData","Roaming","Code - Insiders","User"):i(e,".config","Code - Insiders","User"),n=t==="darwin"?i(e,"Library","Application Support","Claude"):t==="win32"?i(e,"AppData","Roaming","Claude"):i(e,".config","claude"),r=t==="darwin"?i(e,".cursor"):t==="win32"?i(e,".cursor"):i(e,".cursor"),c=t==="darwin"?i(e,".codeium","windsurf"):t==="win32"?i(e,".codeium","windsurf"):i(e,".codeium","windsurf"),u=t==="darwin"?i(e,".config","zed"):t==="win32"?i(e,"AppData","Roaming","Zed"):i(e,".config","zed"),a=i(e,".continue");return[{id:"vscode-copilot",name:"VS Code (Copilot)",icon:"\u{1F4BB}",configPath:i(o,"settings.json"),configFormat:"vscode",mcpKey:"mcp.servers",detected:!1,configured:!1},{id:"vscode-insiders",name:"VS Code Insiders",icon:"\u{1F7E2}",configPath:i(s,"settings.json"),configFormat:"vscode",mcpKey:"mcp.servers",detected:!1,configured:!1},{id:"claude-desktop",name:"Claude Desktop",icon:"\u{1F916}",configPath:i(n,"claude_desktop_config.json"),configFormat:"mcp-json",mcpKey:"mcpServers",detected:!1,configured:!1},{id:"claude-code",name:"Claude Code",icon:"\u2733\uFE0F",configPath:i(e,".claude.json"),configFormat:"mcp-json",mcpKey:"mcpServers",detected:!1,configured:!1},{id:"cursor",name:"Cursor",icon:"\u{1F4D0}",configPath:i(r,"mcp.json"),configFormat:"mcp-json",mcpKey:"mcpServers",detected:!1,configured:!1},{id:"windsurf",name:"Windsurf",icon:"\u{1F3C4}",configPath:i(c,"mcp_config.json"),configFormat:"mcp-json",mcpKey:"mcpServers",detected:!1,configured:!1},{id:"zed",name:"Zed",icon:"\u26A1",configPath:i(u,"settings.json"),configFormat:"zed",mcpKey:"context_servers",detected:!1,configured:!1},{id:"continue",name:"Continue",icon:"\u{1F504}",configPath:i(a,"config.json"),configFormat:"mcp-json",mcpKey:"mcpServers",detected:!1,configured:!1}]}function Z(e){try{const t=j()==="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 S(){const e=Q();for(const t of e){if(t.id==="claude-code")t.detected=d(t.configPath)||Z("claude");else{const o=w(t.configPath);t.detected=d(o)}if(t.detected&&d(t.configPath))try{const o=p(t.configPath,"utf-8"),s=K(o);t.configured=ee(s,t)}catch{t.configured=!1}t.detected&&(t.version=X(t))}return e}function X(e){try{switch(e.id){case"vscode-copilot":case"vscode-insiders":{const t=e.id==="vscode-insiders"?"code-insiders":"code",o=v(t,["--version"],{encoding:"utf-8",timeout:5e3});if(o.stdout)return o.stdout.split(`
2
+ `)[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 ee(e,t){switch(t.configFormat){case"vscode":return e.mcp?.servers?.wyrm!==void 0;case"mcp-json":return e[t.mcpKey]?.wyrm!==void 0;case"zed":return e[t.mcpKey]?.wyrm!==void 0;default:return!1}}function E(){try{const s=v("which",["wyrm-mcp"],{encoding:"utf-8",timeout:5e3});if(s.stdout?.trim())return s.stdout.trim()}catch{}try{const n=v("npm",["root","-g"],{encoding:"utf-8",timeout:5e3}).stdout?.trim();if(n){const r=i(n,"wyrm-mcp","dist","index.js");if(d(r))return r}}catch{}const e=V(__dirname,"index.js");if(d(e))return e;const t=i(m(),".wyrm","node_modules","wyrm-mcp","dist","index.js");if(d(t))return t;const o=[i(m(),"Git Projects","Wyrm","packages","mcp-server","dist","index.js"),i(m(),"projects","Wyrm","packages","mcp-server","dist","index.js"),i(m(),"dev","Wyrm","packages","mcp-server","dist","index.js")];for(const s of o)if(d(s))return s;return"wyrm-mcp"}function T(){return i(m(),".wyrm","wyrm.db")}function ye(e="mcp-json",t){const o=t?.serverPath??E(),s=t?.dbPath??T(),n=o==="wyrm-mcp",r={command:n?"npx":"node",args:n?["wyrm-mcp"]:[o],env:{WYRM_DB_PATH:s}};switch(e){case"stdio":return r;case"vscode":return{mcp:{servers:{wyrm:r}}};default:return{mcpServers:{wyrm:r}}}}function te(e,t){const o=t.serverPath,s=o==="wyrm-mcp",n=o.endsWith(".js");switch(e.configFormat){case"vscode":return{command:s?"npx":"node",args:s?["wyrm-mcp"]:[o],env:{WYRM_DB_PATH:t.dbPath}};case"mcp-json":return{command:s?"npx":"node",args:s?["wyrm-mcp"]:[o],env:{WYRM_DB_PATH:t.dbPath}};case"zed":return{command:s?"npx":"node",args:s?["wyrm-mcp"]:[o],env:{WYRM_DB_PATH:t.dbPath}};default:return{}}}function M(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 s={},n=!1;if(d(e.configPath))try{const a=p(e.configPath,"utf-8");s=K(a),n=!0}catch{s={}}let r;n&&(r=`${e.configPath}.wyrm-backup`,l(r,p(e.configPath)));const c=te(e,t);switch(e.configFormat){case"vscode":{s.mcp||(s.mcp={});const a=s.mcp;a.servers||(a.servers={});const f=a.servers;f.wyrm=c;break}case"mcp-json":{s[e.mcpKey]||(s[e.mcpKey]={});const a=s[e.mcpKey];a.wyrm=c;break}case"zed":{s[e.mcpKey]||(s[e.mcpKey]={});const a=s[e.mcpKey];a.wyrm=c;break}}l(e.configPath,JSON.stringify(s,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 oe(e){if(!e.detected||!e.configured)return{client:e,action:"skipped",message:`${e.name} not configured`};try{if(!d(e.configPath))return{client:e,action:"skipped",message:`${e.name} config not found`};const t=p(e.configPath,"utf-8"),o=K(t),s=`${e.configPath}.wyrm-backup`;switch(l(s,t),e.configFormat){case"vscode":{const r=o.mcp?.servers;r&&delete r.wyrm;break}case"mcp-json":case"zed":{const n=o[e.mcpKey];n&&delete n.wyrm;break}}return l(e.configPath,JSON.stringify(o,null,2)+`
6
+ `,"utf-8"),e.configured=!1,{client:e,action:"configured",message:`Removed Wyrm from ${e.name}`,backup:s}}catch(t){return{client:e,action:"failed",message:`Failed to remove from ${e.name}: ${t}`}}}function A(){return{id:"claude-code-hooks",name:"Claude Code (auto-memory)",icon:"\u{1FA9D}",configPath:i(m(),".claude","settings.json"),configFormat:"mcp-json",mcpKey:"",detected:!0,configured:!1}}const D=[{script:"wyrm-session-capture.mjs",events:["SessionEnd","PreCompact"]},{script:"wyrm-session-rehydrate.mjs",events:["SessionStart"]},{script:"wyrm-session-prune.mjs",events:["SessionEnd"]},{script:"wyrm-tool-call-trace.mjs",events:["PostToolUse"]},{script:"wyrm-run-auto.mjs",events:["SessionStart",{event:"PreToolUse",matcher:"Task"},"SessionEnd"]}];function se(){const e=i(m(),".claude");if(!d(e))return null;const t=A();try{const o=i(e,"hooks");d(o)||P(o,{recursive:!0});const s=w(O(import.meta.url)),n=i(s,"..","scripts","hooks"),r=t.configPath;let c={};if(d(r))try{c=JSON.parse(p(r,"utf-8"))}catch{c={}}const u=c.hooks??{};let a=!1;const f=[];for(const h of D){const g=i(n,h.script);if(!d(g)){f.push(h.script);continue}const _=i(o,h.script);Y(g,_);try{q(_,493)}catch{}const C=`node ${_}`;for(const k of h.events){const $=typeof k=="string"?k:k.event,F=typeof k=="string"?void 0:k.matcher,W=Array.isArray(u[$])?u[$]:[];if(!W.some(b=>Array.isArray(b.hooks)&&b.hooks.some(z=>z.command===C))){const b=F?{matcher:F,hooks:[{type:"command",command:C,timeout:30}]}:{hooks:[{type:"command",command:C,timeout:30}]};W.push(b),a=!0}u[$]=W}}c.hooks=u,l(r,JSON.stringify(c,null,2)+`
7
+ `,"utf-8"),t.configured=!0;const y=f.length?` (missing from package: ${f.join(", ")})`:"";return{client:t,action:a?"configured":"skipped",message:(a?"auto-memory hooks installed":"already installed")+y}}catch(o){return{client:t,action:"failed",message:o instanceof Error?o.message:String(o)}}}function we(){const e=i(m(),".claude");if(!d(e))return null;const t=A();try{const o=w(O(import.meta.url)),s=`node ${i(o,"wyrm-statusline.js")}`,n=t.configPath;let r={};if(d(n))try{r=JSON.parse(p(n,"utf-8"))}catch{r={}}const c=r.statusLine;if(c?.command&&!/wyrm-statusline/.test(c.command))return{client:t,action:"skipped",message:`a non-Wyrm statusLine is already set (${c.command}); not overwriting`};const u=c?.command===s;return r.statusLine={type:"command",command:s,padding:0},l(n,JSON.stringify(r,null,2)+`
8
+ `,"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 ve(){const e=i(m(),".claude","settings.json");if(!d(e))return null;const t=A();try{const o=JSON.parse(p(e,"utf-8")),s=o.statusLine;return s?.command&&/wyrm-statusline/.test(s.command)?(delete o.statusLine,l(e,JSON.stringify(o,null,2)+`
9
+ `,"utf-8"),{client:t,action:"configured",message:"Wyrm statusline removed"}):{client:t,action:"skipped",message:"no Wyrm statusline set"}}catch(o){return{client:t,action:"failed",message:o instanceof Error?o.message:String(o)}}}function re(){const e=i(m(),".claude","settings.json");if(!d(e))return null;const t=A();try{const o=JSON.parse(p(e,"utf-8")),s=i(m(),".claude","hooks"),n=new Set(D.map(u=>`node ${i(s,u.script)}`)),r=new Set(D.flatMap(u=>u.events.map(a=>typeof a=="string"?a:a.event))),c=o.hooks;if(c){for(const u of r)Array.isArray(c[u])&&(c[u]=c[u].map(a=>({...a,hooks:Array.isArray(a.hooks)?a.hooks.filter(f=>!n.has(f.command??"")):a.hooks})).filter(a=>!Array.isArray(a.hooks)||a.hooks.length>0),c[u].length===0&&delete c[u]);Object.keys(c).length===0&&delete o.hooks}return l(e,JSON.stringify(o,null,2)+`
10
+ `,"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 U=/wyrm-guard/,ne=["Bash","Edit|Write|MultiEdit|NotebookEdit"];function x(e){return e.settingsPath??process.env.CLAUDE_SETTINGS??i(m(),".claude","settings.json")}function N(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 L(e){return j()==="win32"?`"${e.replace(/"/g,'""')}"`:`'${e.replace(/'/g,"'\\''")}'`}function ie(){const e=w(O(import.meta.url)),t=i(e,"wyrm-guard.js");if(d(t))return`node ${L(t)}`;try{const o=j()==="win32"?"where":"which",n=v(o,["wyrm-guard"],{encoding:"utf-8",timeout:5e3}).stdout?.trim().split(`
11
+ `)[0]?.trim();if(n)return L(n)}catch{}return null}function B(e){const t=e.hooks;if(!t||typeof t!="object"||Array.isArray(t))return!1;let o=!1;for(const s of Object.keys(t)){const n=t[s];if(!Array.isArray(n))continue;let r=!1;const c=[];for(const u of n){const a=u;if(!a||typeof a!="object"||!Array.isArray(a.hooks)){c.push(u);continue}const f=a.hooks.filter(y=>!U.test(y?.command??""));if(f.length===a.hooks.length){c.push(u);continue}o=!0,r=!0,f.length>0&&c.push({...a,hooks:f})}r&&(c.length===0?delete t[s]:t[s]=c)}return o&&Object.keys(t).length===0&&delete e.hooks,o}function ce(e={}){if(!!!(e.settingsPath??process.env.CLAUDE_SETTINGS)&&!d(i(m(),".claude")))return{client:N(x(e)),action:"skipped",message:"Claude Code not detected (~/.claude missing) \u2014 the failure firewall is NOT enforced on this machine; install Claude Code then re-run `wyrm guard`"};const o=x(e),s=N(o);try{const n=e.guardCommand??ie();if(!n)return{client:s,action:"skipped",message:"wyrm-guard bin not found (npm i -g wyrm-mcp, or build dist/) \u2014 firewall hooks not installed"};P(w(o),{recursive:!0});let r={};if(d(o))try{const g=JSON.parse(p(o,"utf-8"));if(typeof g!="object"||g===null||Array.isArray(g))throw new Error("not an object");r=g}catch{return{client:s,action:"failed",message:`${o} is not valid JSON \u2014 refusing to overwrite it (fix or remove the file, then re-run)`}}const c=r.hooks;if(c!==void 0&&(typeof c!="object"||c===null||Array.isArray(c)))return{client:s,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=c?.PreToolUse;if(u!==void 0&&!Array.isArray(u))return{client:s,action:"failed",message:`${o} hooks.PreToolUse has an unexpected shape (not an array) \u2014 refusing to modify (fix the file, then re-run)`};const a=JSON.stringify(r);B(r);const f=r.hooks??{},y=Array.isArray(f.PreToolUse)?f.PreToolUse:[];for(const g of ne)y.push({matcher:g,hooks:[{type:"command",command:n,timeout:1}]});f.PreToolUse=y,r.hooks=f;const h=JSON.stringify(r)!==a;return h&&l(o,JSON.stringify(r,null,2)+`
12
+ `,"utf-8"),s.configured=!0,{client:s,action:h?"configured":"skipped",message:h?"failure-firewall hooks installed (PreToolUse: Bash + Edit|Write|MultiEdit|NotebookEdit)":"failure-firewall hooks already installed"}}catch(n){return{client:s,action:"failed",message:n instanceof Error?n.message:String(n)}}}function ae(e={}){const t=x(e);if(!d(t))return null;const o=N(t);try{let s;try{const r=JSON.parse(p(t,"utf-8"));if(typeof r!="object"||r===null||Array.isArray(r))throw new Error("not an object");s=r}catch{return{client:o,action:"failed",message:`${t} is not valid JSON \u2014 not touching it`}}return B(s)?(l(t,JSON.stringify(s,null,2)+`
13
+ `,"utf-8"),{client:o,action:"configured",message:"failure-firewall hooks removed"}):{client:o,action:"skipped",message:"no wyrm-guard hooks installed"}}catch(s){return{client:o,action:"failed",message:s instanceof Error?s.message:String(s)}}}function ke(e={}){const t=x(e),o=[];try{const n=JSON.parse(p(t,"utf-8")).hooks??{};for(const r of Object.values(n))if(Array.isArray(r))for(const c of r)for(const u of c?.hooks??[]){const a=u?.command;a&&U.test(a)&&o.push(a)}}catch{}return{settingsPath:t,installed:o.length>0,commands:o}}function ue(e){const t={serverPath:e?.serverPath||E(),dbPath:e?.dbPath||T(),httpPort:e?.httpPort},o=i(m(),".wyrm");d(o)||P(o,{recursive:!0}),H(t);const s=S(),n=[];for(const u of s)n.push(M(u,t));const r=se();r&&n.push(r);const c=ce();return c&&n.push(c),n}function Pe(){const e=S(),t=[];for(const n of e)t.push(oe(n));const o=re();o&&t.push(o);const s=ae();return s&&t.push(s),t}function de(e,t){const o={serverPath:t?.serverPath||E(),dbPath:t?.dbPath||T(),httpPort:t?.httpPort};H(o);const s=S(),n=[];for(const r of s)e.includes(r.id)&&n.push(M(r,o));return n}function H(e){const t=i(m(),".wyrm","wyrm-config.json"),o=S().filter(n=>n.configured).map(n=>n.id),s={version:"3.0.0",serverPath:e.serverPath,dbPath:e.dbPath,httpPort:e.httpPort,configuredClients:o,lastSetup:new Date().toISOString(),autoUpdate:!0};l(t,JSON.stringify(s,null,2)+`
14
+ `,"utf-8")}function I(){const e=i(m(),".wyrm","wyrm-config.json");if(!d(e))return null;try{return JSON.parse(p(e,"utf-8"))}catch{return null}}function Se(){const e=I();return e?de(e.configuredClients,{serverPath:e.serverPath,dbPath:e.dbPath,httpPort:e.httpPort}):ue()}const G="<!-- wyrm:start -->",R="<!-- wyrm:end -->",J=`${G}
14
15
  ## Wyrm Memory + Agent (auto-managed \u2014 do not edit this section)
15
16
 
16
17
  **Session start** \u2014 call \`wyrm_session_prime\` first to load project context (ground truths, open quests, recent sessions, scaffolds) before responding.
@@ -34,20 +35,20 @@ import{existsSync as d,readFileSync as p,writeFileSync as l,mkdirSync as k,copyF
34
35
  **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.
35
36
 
36
37
  **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.
37
- ${D}`;function Pe(e,t){const o={injected:[],skipped:[],errors:[]},s=(r,c)=>{try{const u=w(r);if(d(u)||k(u,{recursive:!0}),d(r)){const a=p(r,"utf-8"),f=a.indexOf(G),y=a.indexOf(D);if(f!==-1&&y!==-1&&y>f){const h=a.slice(0,f),g=a.slice(y+D.length);l(r,`${h}${N}${g}`,"utf-8")}else{const h=a.endsWith(`
38
+ ${R}`;function be(e,t){const o={injected:[],skipped:[],errors:[]},s=(r,c)=>{try{const u=w(r);if(d(u)||P(u,{recursive:!0}),d(r)){const a=p(r,"utf-8"),f=a.indexOf(G),y=a.indexOf(R);if(f!==-1&&y!==-1&&y>f){const h=a.slice(0,f),g=a.slice(y+R.length);l(r,`${h}${J}${g}`,"utf-8")}else{const h=a.endsWith(`
38
39
  `)?`
39
40
  `:`
40
41
 
41
- `;l(r,`${a}${h}${N}
42
- `,"utf-8")}}else l(r,`${N}
43
- `,"utf-8");o.injected.push(c)}catch(u){o.errors.push(`${c}: ${u}`)}},n=t.length===0?["copilot","cursor"]:t;for(const r of n){const c=r.toLowerCase();if(c==="copilot"||c==="vscode-copilot"){const u=i(e,".github","copilot-instructions.md");s(u,".github/copilot-instructions.md")}else if(c==="cursor"){const u=i(e,".cursor","rules");s(u,".cursor/rules")}else o.skipped.push(r)}return o}function R(e){let t=e.replace(/\/\/.*$/gm,"");return t=t.replace(/\/\*[\s\S]*?\*\//g,""),t=t.replace(/,\s*([\]}])/g,"$1"),JSON.parse(t)}function Se(){const e=S(),t=e.filter(r=>r.detected),o=e.filter(r=>r.configured);let s=`\u{F115D} Wyrm Auto-Configure Status
42
+ `;l(r,`${a}${h}${J}
43
+ `,"utf-8")}}else l(r,`${J}
44
+ `,"utf-8");o.injected.push(c)}catch(u){o.errors.push(`${c}: ${u}`)}},n=t.length===0?["copilot","cursor"]:t;for(const r of n){const c=r.toLowerCase();if(c==="copilot"||c==="vscode-copilot"){const u=i(e,".github","copilot-instructions.md");s(u,".github/copilot-instructions.md")}else if(c==="cursor"){const u=i(e,".cursor","rules");s(u,".cursor/rules")}else o.skipped.push(r)}return o}function K(e){let t=e.replace(/\/\/.*$/gm,"");return t=t.replace(/\/\*[\s\S]*?\*\//g,""),t=t.replace(/,\s*([\]}])/g,"$1"),JSON.parse(t)}function je(){const e=S(),t=e.filter(r=>r.detected),o=e.filter(r=>r.configured);let s=`\u{F115D} Wyrm Auto-Configure Status
44
45
 
45
46
  `;s+=` Detected: ${t.length}/${e.length} AI clients
46
47
  `,s+=` Configured: ${o.length}/${t.length} clients
47
48
 
48
49
  `;for(const r of e){const c=r.detected?r.configured?" \u25CF":" \u25D0":" \u25CB",u=r.version?` (${r.version})`:"",a=r.detected?r.configured?"configured \u2713":"detected \u2014 not configured":"not found";s+=`${c} ${r.icon} ${r.name}${u}: ${a}
49
- `}const n=H();return n&&(s+=`
50
+ `}const n=I();return n&&(s+=`
50
51
  Server: ${n.serverPath}
51
52
  `,s+=` DB: ${n.dbPath}
52
53
  `,s+=` Last: ${new Date(n.lastSetup).toLocaleString()}
53
- `),s}export{N as WYRM_INJECT_BLOCK,ae as autoConfigureAll,F as configureClient,ue as configureSpecific,S as detectClients,K as findWyrmServerPath,U as getDefaultDbPath,Se as getStatusSummary,Pe as injectSystemPrompt,oe as installClaudeCodeHooks,ge as installClaudeStatusline,ie as installWyrmGuardHooks,H as loadWyrmMeta,L as quoteHookPath,ke as reconfAll,se as removeClaudeCodeHooks,ye as removeClaudeStatusline,ve as removeFromAll,te as removeFromClient,ce as removeWyrmGuardHooks,ne as resolveWyrmGuardCommand,we as wyrmGuardHookStatus};
54
+ `),s}export{J as WYRM_INJECT_BLOCK,ue as autoConfigureAll,M as configureClient,de as configureSpecific,S as detectClients,ye as exportServerConfig,E as findWyrmServerPath,T as getDefaultDbPath,je as getStatusSummary,be as injectSystemPrompt,se as installClaudeCodeHooks,we as installClaudeStatusline,ce as installWyrmGuardHooks,I as loadWyrmMeta,L as quoteHookPath,Se as reconfAll,re as removeClaudeCodeHooks,ve as removeClaudeStatusline,Pe as removeFromAll,oe as removeFromClient,ae as removeWyrmGuardHooks,ie as resolveWyrmGuardCommand,ke as wyrmGuardHookStatus};
package/dist/database.js CHANGED
@@ -1,4 +1,4 @@
1
- import k from"better-sqlite3";import{existsSync as u,mkdirSync as b,readdirSync as D,statSync as L,writeFileSync as I}from"fs";import{homedir as O}from"os";import{join as d,basename as A,resolve as M,normalize as w}from"path";import{spawnSync as R}from"child_process";import{getResilienceManager as v}from"./resilience.js";import{WyrmLogger as j}from"./logger.js";import{runMigrations as H,getSchemaVersion as F}from"./migrations.js";import{validateProjectPath as N,buildFtsMatchQuery as h}from"./security.js";import{emitEvent as m,eventsSince as W,subscribeEvents as U,isLiveMemoryEnabled as x,ingestRemoteEvent as P,eventsForPush as B,pruneEvents as q,getMeta as Y,setMeta as $}from"./events.js";import{getActor as C}from"./handlers/boundary.js";import{readSkillContent as y,slugify as G,skillContentSha as V}from"./skill-author.js";import{resolveProjectForPath as J}from"./project-resolve.js";function le(){return process.env.WYRM_DB_PATH??process.env.WYRM_DB??d(O(),".wyrm","wyrm.db")}class de{db;BATCH_SIZE=1e3;resilience;logger;dbPath;constructor(t){const e=d(O(),".wyrm");u(e)||b(e,{recursive:!0}),this.dbPath=t||d(e,"wyrm.db"),this.logger=new j,this.resilience=v(),this.db=this.initializeDatabase(this.dbPath),this.db.pragma("journal_mode = WAL"),this.db.pragma("synchronous = NORMAL"),this.db.pragma("cache_size = -64000"),this.db.pragma("temp_store = MEMORY"),this.db.pragma("busy_timeout = 5000"),this.db.pragma("mmap_size = 268435456"),this.db.pragma("page_size = 4096"),this.db.pragma("foreign_keys = ON");const s=H(this.db);s.length>0&&this.logger.info(`Applied ${s.length} migration(s), now at v${F(this.db)}`),this.recoverIncompleteOperations()}getDatabase(){return this.db}getDatabasePath(){return this.dbPath}initializeDatabase(t){const e=this.resilience.withRetrySync(()=>new k(t),"database_init",{maxAttempts:3,baseDelayMs:500});if(!e.success)throw this.logger.error("Failed to initialize database",{path:t,error:e.error?.message}),e.error||new Error("Database initialization failed");return e.data}recoverIncompleteOperations(){const t=this.resilience.getIncompleteOperations();for(const e of t)this.logger.warn("Found incomplete operation from previous session",{operation:e.operation,stage:e.stage,id:e.id}),e.operation==="batch_insert"&&this.logger.info("Batch insert was incomplete - data may need re-import"),this.resilience.completeCheckpoint(e.id)}addWatchDir(t,e=!0){return N(t),this.db.prepare(`
1
+ import{openDatabase as k}from"./storage/index.js";import{existsSync as u,mkdirSync as b,readdirSync as D,statSync as L,writeFileSync as I}from"fs";import{homedir as O}from"os";import{join as d,basename as A,resolve as M,normalize as w}from"path";import{spawnSync as R}from"child_process";import{getResilienceManager as v}from"./resilience.js";import{WyrmLogger as j}from"./logger.js";import{runMigrations as H,getSchemaVersion as F}from"./migrations.js";import{validateProjectPath as N,buildFtsMatchQuery as h}from"./security.js";import{emitEvent as m,eventsSince as W,subscribeEvents as U,isLiveMemoryEnabled as x,ingestRemoteEvent as P,eventsForPush as B,pruneEvents as q,getMeta as Y,setMeta as $}from"./events.js";import{getActor as C}from"./handlers/boundary.js";import{readSkillContent as y,slugify as G,skillContentSha as V}from"./skill-author.js";import{resolveProjectForPath as J}from"./project-resolve.js";function le(){return process.env.WYRM_DB_PATH??process.env.WYRM_DB??d(O(),".wyrm","wyrm.db")}class de{db;BATCH_SIZE=1e3;resilience;logger;dbPath;constructor(t){const e=d(O(),".wyrm");u(e)||b(e,{recursive:!0}),this.dbPath=t||d(e,"wyrm.db"),this.logger=new j,this.resilience=v(),this.db=this.initializeDatabase(this.dbPath),this.db.pragma("journal_mode = WAL"),this.db.pragma("synchronous = NORMAL"),this.db.pragma("cache_size = -64000"),this.db.pragma("temp_store = MEMORY"),this.db.pragma("busy_timeout = 5000"),this.db.pragma("mmap_size = 268435456"),this.db.pragma("page_size = 4096"),this.db.pragma("foreign_keys = ON");const s=H(this.db);s.length>0&&this.logger.info(`Applied ${s.length} migration(s), now at v${F(this.db)}`),this.recoverIncompleteOperations()}getDatabase(){return this.db}getDatabasePath(){return this.dbPath}initializeDatabase(t){const e=this.resilience.withRetrySync(()=>k(t),"database_init",{maxAttempts:3,baseDelayMs:500});if(!e.success)throw this.logger.error("Failed to initialize database",{path:t,error:e.error?.message}),e.error||new Error("Database initialization failed");return e.data}recoverIncompleteOperations(){const t=this.resilience.getIncompleteOperations();for(const e of t)this.logger.warn("Found incomplete operation from previous session",{operation:e.operation,stage:e.stage,id:e.id}),e.operation==="batch_insert"&&this.logger.info("Batch insert was incomplete - data may need re-import"),this.resilience.completeCheckpoint(e.id)}addWatchDir(t,e=!0){return N(t),this.db.prepare(`
2
2
  INSERT INTO watch_dirs (path, recursive)
3
3
  VALUES (?, ?)
4
4
  ON CONFLICT(path) DO UPDATE SET recursive = excluded.recursive
@@ -1,8 +1,8 @@
1
- import{TOOL_ANNOTATIONS as u}from"../tool-annotations.js";const h=[{name:"wyrm_ask",description:"Wyrm answers a natural-language question using its own data as context. Assembles relevant ground truths, sessions, artifacts, failures, symbols, then runs through Ollama (auto-detected on localhost:11434) or OpenAI (OPENAI_API_KEY fallback). Returns the answer + the context summary. If no LLM is reachable, returns the assembled raw context (degraded mode \u2014 explicit, not silent).",inputSchema:{type:"object",properties:{query:{type:"string",description:"Question in natural language"},projectPath:{type:"string",description:"Scope context to one project (optional)"},max_context_chars:{type:"number",description:"Soft cap on assembled context (default 12000, max 50000)"},model_override:{type:"string",description:"Force a specific model: 'ollama:llama3.2' or 'openai:gpt-4o-mini'"}},required:["query"]},annotations:u.wyrm_ask,aliases:[],handler:async(s,a)=>{const{db:i,subAgent:n}=a,{query:t,projectPath:e,max_context_chars:c,model_override:r}=s,p=e?i.getProject(e):null,o=await n.ask({query:t,project_id:p?.id??null,max_context_chars:c,model_override:r});return{content:[{type:"text",text:(o.degraded?`\u{F115D} Wyrm (${o.model}, ${o.context_chars} ctx chars, ${o.latency_ms}ms) \u2014 DEGRADED MODE: ${o.error}
1
+ import{TOOL_ANNOTATIONS as p}from"../tool-annotations.js";const y=[{name:"wyrm_ask",description:"Wyrm answers a natural-language question using its own data as context. Assembles relevant ground truths, sessions, artifacts, failures, symbols, then runs through one of four generation backends: Ollama (local, auto-detected on localhost:11434, the default), Claude (ANTHROPIC_API_KEY), NVIDIA NIM / Nemotron (NVIDIA_API_KEY), or OpenAI (OPENAI_API_KEY). Returns the answer + the context summary. If no LLM is reachable, returns the assembled raw context (degraded mode \u2014 explicit, not silent).",inputSchema:{type:"object",properties:{query:{type:"string",description:"Question in natural language"},projectPath:{type:"string",description:"Scope context to one project (optional)"},max_context_chars:{type:"number",description:"Soft cap on assembled context (default 12000, max 50000)"},model_override:{type:"string",description:"Force a backend/model: 'ollama:llama3.2', 'anthropic:claude-opus-4-8' (alias 'claude'), 'nim:nvidia/nemotron-3-ultra-550b' (alias 'nemotron'), or 'openai:gpt-4o-mini'"}},required:["query"]},annotations:p.wyrm_ask,aliases:[],handler:async(s,a)=>{const{db:i,subAgent:n}=a,{query:t,projectPath:e,max_context_chars:c,model_override:r}=s,u=e?i.getProject(e):null,o=await n.ask({query:t,project_id:u?.id??null,max_context_chars:c,model_override:r});return{content:[{type:"text",text:(o.degraded?`\u{F115D} Wyrm (${o.model}, ${o.context_chars} ctx chars, ${o.latency_ms}ms) \u2014 DEGRADED MODE: ${o.error}
2
2
 
3
3
  `:`\u{F115D} Wyrm (${o.model}, ${o.context_chars} ctx chars, ${o.latency_ms}ms${o.tokens_in?`, ${o.tokens_in}+${o.tokens_out} tok`:""}):
4
4
 
5
- `)+o.answer}]}}},{name:"wyrm_share",description:"Use to share a finding with the rest of the team - marks a row (session/quest/truth/artifact/edge) is_shared for the next sync push; make a record private again with wyrm_unshare when it must not leave this machine.",inputSchema:{type:"object",properties:{kind:{type:"string",enum:["session","quest","truth","artifact","edge"]},id:{type:"number"}},required:["kind","id"]},annotations:u.wyrm_share,aliases:[],handler:async(s,a)=>{const{federation:i}=a,{kind:n,id:t}=s;return{content:[{type:"text",text:i.share(n,t)?`\u{F115D} ${n}#${t} marked shared.`:`\u{F115D} ${n}#${t} not found.`}]}}},{name:"wyrm_unshare",description:"Revoke team visibility for a row. Local row stays; future syncs won't include it.",inputSchema:{type:"object",properties:{kind:{type:"string",enum:["session","quest","truth","artifact","edge"]},id:{type:"number"}},required:["kind","id"]},annotations:u.wyrm_unshare,aliases:[],handler:async(s,a)=>{const{federation:i}=a,{kind:n,id:t}=s;return{content:[{type:"text",text:i.unshare(n,t)?`\u{F115D} ${n}#${t} unshared.`:`\u{F115D} ${n}#${t} not found.`}]}}},{name:"wyrm_sync_conflicts",description:"List unresolved sync conflicts \u2014 rows where a remote push landed on top of a newer local edit. Each conflict has been auto-converted into a quest (tag 'sync-conflict') so the operator sees it on next session.",inputSchema:{type:"object",properties:{limit:{type:"number",description:"Max conflicts (default 50)"}}},annotations:u.wyrm_sync_conflicts,aliases:[],handler:async(s,a)=>{const{federation:i,sync:n}=a,{limit:t}=s,e=i.unresolvedConflicts(Math.min(Math.max(1,t??50),500));if(e.length===0)return{content:[{type:"text",text:"\u{F115D} No unresolved sync conflicts."}]};const c=e.map(r=>`\u2022 #${r.id} ${r.local_kind}#${r.local_id} from ${r.remote_origin??"?"} at ${r.detected_at}`+(r.quest_id?` \u2192 quest #${r.quest_id}`:"")).join(`
5
+ `)+o.answer}]}}},{name:"wyrm_share",description:"Use to share a finding with the rest of the team - marks a row (session/quest/truth/artifact/edge) is_shared for the next sync push; make a record private again with wyrm_unshare when it must not leave this machine.",inputSchema:{type:"object",properties:{kind:{type:"string",enum:["session","quest","truth","artifact","edge"]},id:{type:"number"}},required:["kind","id"]},annotations:p.wyrm_share,aliases:[],handler:async(s,a)=>{const{federation:i}=a,{kind:n,id:t}=s;return{content:[{type:"text",text:i.share(n,t)?`\u{F115D} ${n}#${t} marked shared.`:`\u{F115D} ${n}#${t} not found.`}]}}},{name:"wyrm_unshare",description:"Revoke team visibility for a row. Local row stays; future syncs won't include it.",inputSchema:{type:"object",properties:{kind:{type:"string",enum:["session","quest","truth","artifact","edge"]},id:{type:"number"}},required:["kind","id"]},annotations:p.wyrm_unshare,aliases:[],handler:async(s,a)=>{const{federation:i}=a,{kind:n,id:t}=s;return{content:[{type:"text",text:i.unshare(n,t)?`\u{F115D} ${n}#${t} unshared.`:`\u{F115D} ${n}#${t} not found.`}]}}},{name:"wyrm_sync_conflicts",description:"List unresolved sync conflicts \u2014 rows where a remote push landed on top of a newer local edit. Each conflict has been auto-converted into a quest (tag 'sync-conflict') so the operator sees it on next session.",inputSchema:{type:"object",properties:{limit:{type:"number",description:"Max conflicts (default 50)"}}},annotations:p.wyrm_sync_conflicts,aliases:[],handler:async(s,a)=>{const{federation:i,sync:n}=a,{limit:t}=s,e=i.unresolvedConflicts(Math.min(Math.max(1,t??50),500));if(e.length===0)return{content:[{type:"text",text:"\u{F115D} No unresolved sync conflicts."}]};const c=e.map(r=>`\u2022 #${r.id} ${r.local_kind}#${r.local_id} from ${r.remote_origin??"?"} at ${r.detected_at}`+(r.quest_id?` \u2192 quest #${r.quest_id}`:"")).join(`
6
6
  `);return{content:[{type:"text",text:`\u{F115D} ${e.length} unresolved conflict(s):
7
- ${c}`}]}}},{name:"wyrm_sync_resolve",description:"Resolve a sync conflict. Mode: 'kept_local' (drop remote), 'kept_remote' (overwrite local with remote payload), 'merged' (apply a hand-merged payload supplied by the caller).",inputSchema:{type:"object",properties:{conflict_id:{type:"number"},mode:{type:"string",enum:["kept_local","kept_remote","merged"]},merged_payload:{type:"object",description:"Required when mode='merged' \u2014 full row payload to write"}},required:["conflict_id","mode"]},annotations:u.wyrm_sync_resolve,aliases:[],handler:async(s,a)=>{const{federation:i}=a,{conflict_id:n,mode:t,merged_payload:e}=s;return t==="merged"&&!e?{content:[{type:"text",text:"merged_payload required for mode=merged"}],isError:!0}:{content:[{type:"text",text:i.resolveConflict(n,t,e)?`\u{F115D} Conflict #${n} resolved (${t}).`:`\u{F115D} Conflict #${n} not found or already resolved.`}]}}},{name:"wyrm_act",description:"Use to pursue a goal autonomously - the OODA loop: observe Wyrm context, orient, decide one whitelisted tool call, act, loop. Pass goal_id for an existing goal, or query+title to create and pursue ad-hoc.",inputSchema:{type:"object",properties:{goal_id:{type:"number"},query:{type:"string",description:"Ad-hoc"},title:{type:"string",description:"Ad-hoc goal"},projectPath:{type:"string"},max_steps:{type:"number",description:"Default 5, cap 50"},model_override:{type:"string",description:"ollama:model"}}},annotations:u.wyrm_act,aliases:[],handler:async(s,a)=>{const{agentLoop:i,db:n,goals:t}=a,e=s;let c=e.goal_id,r=!1;if(!c){if(!e.query)return{content:[{type:"text",text:"Either goal_id or query required"}],isError:!0};const l=e.projectPath?n.getProject(e.projectPath):null;c=t.set({project_id:l?.id??null,title:(e.title??e.query).slice(0,140),description:e.query,priority:"medium",max_iterations:Math.min(50,e.max_steps??5)+2}).id,r=!0}const p=await i.pursue(c,{max_steps:e.max_steps,model_override:e.model_override});if(p.length===0)return{content:[{type:"text",text:`Goal #${c} not active or already at max_iterations.`}],isError:!0};const o=p[p.length-1],d=[];d.push(`\u{F115D} Agent ran ${p.length} iteration(s) on goal #${c}${r?" (ad-hoc)":""}.`),d.push(`Final outcome: **${o.outcome}** (model: ${o.model}${o.degraded?", DEGRADED":""}, ${o.latency_ms}ms)`),d.push("");for(const l of p)d.push(`### Iter #${l.iteration_num} \u2014 ${l.outcome}`),l.decided.thought&&d.push(`> ${l.decided.thought}`),d.push(`Action: \`${l.decided.action}\``+(l.decided.args?` args=${JSON.stringify(l.decided.args).slice(0,200)}`:"")),d.push(`Result: ${l.action_result.slice(0,400)}`),d.push("");return{content:[{type:"text",text:d.join(`
8
- `)}]}}}];export{h as shareToolSpecs};
7
+ ${c}`}]}}},{name:"wyrm_sync_resolve",description:"Resolve a sync conflict. Mode: 'kept_local' (drop remote), 'kept_remote' (overwrite local with remote payload), 'merged' (apply a hand-merged payload supplied by the caller).",inputSchema:{type:"object",properties:{conflict_id:{type:"number"},mode:{type:"string",enum:["kept_local","kept_remote","merged"]},merged_payload:{type:"object",description:"Required when mode='merged' \u2014 full row payload to write"}},required:["conflict_id","mode"]},annotations:p.wyrm_sync_resolve,aliases:[],handler:async(s,a)=>{const{federation:i}=a,{conflict_id:n,mode:t,merged_payload:e}=s;return t==="merged"&&!e?{content:[{type:"text",text:"merged_payload required for mode=merged"}],isError:!0}:{content:[{type:"text",text:i.resolveConflict(n,t,e)?`\u{F115D} Conflict #${n} resolved (${t}).`:`\u{F115D} Conflict #${n} not found or already resolved.`}]}}},{name:"wyrm_act",description:"Use to pursue a goal autonomously - the OODA loop: observe Wyrm context, orient, decide one whitelisted tool call, act, loop. Pass goal_id for an existing goal, or query+title to create and pursue ad-hoc.",inputSchema:{type:"object",properties:{goal_id:{type:"number"},query:{type:"string",description:"Ad-hoc"},title:{type:"string",description:"Ad-hoc goal"},projectPath:{type:"string"},max_steps:{type:"number",description:"Default 5, cap 50"},model_override:{type:"string",description:"ollama:model"}}},annotations:p.wyrm_act,aliases:[],handler:async(s,a)=>{const{agentLoop:i,db:n,goals:t}=a,e=s;let c=e.goal_id,r=!1;if(!c){if(!e.query)return{content:[{type:"text",text:"Either goal_id or query required"}],isError:!0};const l=e.projectPath?n.getProject(e.projectPath):null;c=t.set({project_id:l?.id??null,title:(e.title??e.query).slice(0,140),description:e.query,priority:"medium",max_iterations:Math.min(50,e.max_steps??5)+2}).id,r=!0}const u=await i.pursue(c,{max_steps:e.max_steps,model_override:e.model_override});if(u.length===0)return{content:[{type:"text",text:`Goal #${c} not active or already at max_iterations.`}],isError:!0};const o=u[u.length-1],d=[];d.push(`\u{F115D} Agent ran ${u.length} iteration(s) on goal #${c}${r?" (ad-hoc)":""}.`),d.push(`Final outcome: **${o.outcome}** (model: ${o.model}${o.degraded?", DEGRADED":""}, ${o.latency_ms}ms)`),d.push("");for(const l of u)d.push(`### Iter #${l.iteration_num} \u2014 ${l.outcome}`),l.decided.thought&&d.push(`> ${l.decided.thought}`),d.push(`Action: \`${l.decided.action}\``+(l.decided.args?` args=${JSON.stringify(l.decided.args).slice(0,200)}`:"")),d.push(`Result: ${l.action_result.slice(0,400)}`),d.push("");return{content:[{type:"text",text:d.join(`
8
+ `)}]}}}];export{y as shareToolSpecs};
package/dist/license.js CHANGED
@@ -1,3 +1,3 @@
1
- import{createPrivateKey as S,createPublicKey as T,sign as w,verify as k,generateKeyPairSync as I,randomBytes as A}from"crypto";import{readFileSync as K,writeFileSync as h,mkdirSync as L,existsSync as m,chmodSync as b}from"fs";import{homedir as B}from"os";import{join as _}from"path";const x="ABCDEFGHJKMNPQRSTUVWXYZ234567890",N="WRM",F=4,R=4,l=_(B(),".wyrm"),c=_(l,"license.json"),n={free:["local_storage","all_tools","fts_search","unlimited_projects"],pro:["cloud_backup","encryption","analytics","priority_support"],team:["shared_memory","workspaces","admin_dashboard","slack_integration"],enterprise:["unlimited_seats","sso_saml","custom_sla","on_premise"]},d=["free","pro","team","enterprise"],M={free:1,pro:1,team:25,enterprise:-1},P=`-----BEGIN PUBLIC KEY-----
1
+ import{createPrivateKey as k,createPublicKey as I,sign as T,verify as w,generateKeyPairSync as L,randomBytes as A}from"crypto";import{readFileSync as K,writeFileSync as h,mkdirSync as b,existsSync as _,chmodSync as B}from"fs";import{homedir as F}from"os";import{join as x}from"path";import{OFFICIAL_BUILD as N}from"./build-flags.js";const g="ABCDEFGHJKMNPQRSTUVWXYZ234567890",R="WRM",D=4,M=4,l=x(F(),".wyrm"),c=x(l,"license.json"),n={free:["local_storage","all_tools","fts_search","unlimited_projects"],pro:["cloud_backup","encryption","analytics","priority_support"],team:["shared_memory","workspaces","admin_dashboard","slack_integration"],enterprise:["unlimited_seats","sso_saml","custom_sla","on_premise"]},d=["free","pro","team","enterprise"],P={free:1,pro:1,team:25,enterprise:-1},C=`-----BEGIN PUBLIC KEY-----
2
2
  MCowBQYDK2VwAyEA5WkbNjI9tclD+Mps13IzEXH5FhCBnblwy4+bjYJHpOk=
3
- -----END PUBLIC KEY-----`;let s=null,o="free",u=new Set(n.free);function D(e){const r=d.indexOf(e),i=[];for(let t=0;t<=r;t++)i.push(...n[d[t]]);return[...new Set(i)]}function g(e){return[e.key,e.product,e.tier,e.issued_to,e.issued_at,e.expires_at??"perpetual",String(e.max_devices),e.features.sort().join(","),e.hardware_id??""].join("|")}function Y(e){const r=A(e);let i="";for(let t=0;t<e;t++)i+=x[r[t]%x.length];return i}function G(){const{publicKey:e,privateKey:r}=I("ed25519",{publicKeyEncoding:{type:"spki",format:"pem"},privateKeyEncoding:{type:"pkcs8",format:"pem"}});return{publicKey:e,privateKey:r}}function C(){const e=[];for(let r=0;r<F;r++)e.push(Y(R));return`${N}-${e.join("-")}`}function $(e,r,i={}){const t=D(r);if(i.extraFeatures)for(const a of i.extraFeatures)t.includes(a)||t.push(a);return{key:C(),product:"wyrm",tier:r,issued_to:e,issued_at:new Date().toISOString(),expires_at:i.expiresAt??null,max_devices:i.maxDevices??M[r],features:t,hardware_id:i.hardwareId}}function X(e,r){const i=g(e),t=S(r),a=w(null,Buffer.from(i,"utf-8"),t);return{license:e,signature:a.toString("base64")}}function f(e,r){const i={valid:!1,tier:"free",features:[...n.free],expiresAt:null};try{const{license:t,signature:a}=e;if(t.product!=="wyrm")return{...i,error:"Invalid product identifier"};if(!d.includes(t.tier))return{...i,error:`Unknown tier: ${t.tier}`};const v=g(t),E=r??P;let p;try{p=T(E)}catch{return{...i,error:"Invalid public key"}}if(!k(null,Buffer.from(v,"utf-8"),p,Buffer.from(a,"base64")))return{...i,error:"Invalid signature \u2014 license may be tampered"};if(t.expires_at!==null){const y=new Date(t.expires_at);if(isNaN(y.getTime()))return{...i,error:"Malformed expiry date"};if(y.getTime()<Date.now())return{valid:!1,tier:t.tier,features:[...n.free],expiresAt:t.expires_at,error:`License expired on ${t.expires_at}`}}return{valid:!0,tier:t.tier,features:[...t.features],expiresAt:t.expires_at}}catch(t){const a=t instanceof Error?t.message:String(t);return{...i,error:`Verification failed: ${a}`}}}function z(){try{const e={license:{key:"WRM-KAT0-KAT0-KAT0-KAT0",product:"wyrm",tier:"enterprise",issued_to:"integrity@selftest",issued_at:"2026-01-01T00:00:00.000Z",expires_at:null,max_devices:-1,features:[...n.enterprise]},signature:Buffer.from("integrity-self-test-not-a-valid-signature").toString("base64")};return f(e).valid?{ok:!1,reason:"verifier_accepts_forged_signature"}:{ok:!0}}catch{return{ok:!0}}}function Q(e){return u.has(e)}function Z(){return o}function q(){if(!s)return{tier:"free",features:[...n.free],expiresAt:null,valid:!1,issuedTo:null,key:null};const e=f(s);return{tier:e.tier,features:e.features,expiresAt:e.expiresAt,valid:e.valid,issuedTo:s.license.issued_to,key:s.license.key}}function j(){try{if(!m(c))return null;const e=K(c,"utf-8"),r=JSON.parse(e);return!r.license||!r.signature||!r.license.key||!r.license.tier?null:r}catch{return null}}function O(e){m(l)||L(l,{recursive:!0});const r=JSON.stringify(e,null,2);h(c,r,{encoding:"utf-8",mode:384}),b(c,384)}function ee(e){let r;try{r=JSON.parse(e)}catch{return{valid:!1,tier:"free",features:[...n.free],expiresAt:null,error:"Invalid license format \u2014 expected JSON"}}if(!r.license||!r.signature)return{valid:!1,tier:"free",features:[...n.free],expiresAt:null,error:"Malformed license \u2014 missing license data or signature"};const i=f(r);return i.valid&&(O(r),s=r,o=i.tier,u=new Set(i.features)),i}function re(){const e=j();if(!e){s=null,o="free",u=new Set(n.free);return}const r=f(e);r.valid?(s=e,o=r.tier,u=new Set(r.features)):(s=null,o="free",u=new Set(n.free))}function te(){s=null,o="free",u=new Set(n.free)}export{M as TIER_DEVICE_LIMITS,n as TIER_FEATURES,ee as activateLicense,$ as createLicense,G as generateKeyPair,C as generateLicenseKey,q as getLicenseInfo,Z as getTier,Q as hasFeature,re as initializeLicense,j as loadLicense,te as resetLicense,D as resolveFeaturesForTier,O as saveLicense,z as selfTestVerifier,X as signLicense,f as verifyLicense};
3
+ -----END PUBLIC KEY-----`;let s=null,a="free",u=new Set(n.free);function Y(e){const r=d.indexOf(e),i=[];for(let t=0;t<=r;t++)i.push(...n[d[t]]);return[...new Set(i)]}function v(e){return[e.key,e.product,e.tier,e.issued_to,e.issued_at,e.expires_at??"perpetual",String(e.max_devices),e.features.sort().join(","),e.hardware_id??""].join("|")}function O(e){const r=A(e);let i="";for(let t=0;t<e;t++)i+=g[r[t]%g.length];return i}function Z(){const{publicKey:e,privateKey:r}=L("ed25519",{publicKeyEncoding:{type:"spki",format:"pem"},privateKeyEncoding:{type:"pkcs8",format:"pem"}});return{publicKey:e,privateKey:r}}function j(){const e=[];for(let r=0;r<D;r++)e.push(O(M));return`${R}-${e.join("-")}`}function q(e,r,i={}){const t=Y(r);if(i.extraFeatures)for(const o of i.extraFeatures)t.includes(o)||t.push(o);return{key:j(),product:"wyrm",tier:r,issued_to:e,issued_at:new Date().toISOString(),expires_at:i.expiresAt??null,max_devices:i.maxDevices??P[r],features:t,hardware_id:i.hardwareId}}function ee(e,r){const i=v(e),t=k(r),o=T(null,Buffer.from(i,"utf-8"),t);return{license:e,signature:o.toString("base64")}}function f(e,r){const i={valid:!1,tier:"free",features:[...n.free],expiresAt:null};try{const{license:t,signature:o}=e;if(t.product!=="wyrm")return{...i,error:"Invalid product identifier"};if(!d.includes(t.tier))return{...i,error:`Unknown tier: ${t.tier}`};const E=v(t),S=r??C;let m;try{m=I(S)}catch{return{...i,error:"Invalid public key"}}if(!w(null,Buffer.from(E,"utf-8"),m,Buffer.from(o,"base64")))return{...i,error:"Invalid signature \u2014 license may be tampered"};if(t.expires_at!==null){const y=new Date(t.expires_at);if(isNaN(y.getTime()))return{...i,error:"Malformed expiry date"};if(y.getTime()<Date.now())return{valid:!1,tier:t.tier,features:[...n.free],expiresAt:t.expires_at,error:`License expired on ${t.expires_at}`}}return{valid:!0,tier:t.tier,features:[...t.features],expiresAt:t.expires_at}}catch(t){const o=t instanceof Error?t.message:String(t);return{...i,error:`Verification failed: ${o}`}}}function U(){try{const e={license:{key:"WRM-KAT0-KAT0-KAT0-KAT0",product:"wyrm",tier:"enterprise",issued_to:"integrity@selftest",issued_at:"2026-01-01T00:00:00.000Z",expires_at:null,max_devices:-1,features:[...n.enterprise]},signature:Buffer.from("integrity-self-test-not-a-valid-signature").toString("base64")};return f(e).valid?{ok:!1,reason:"verifier_accepts_forged_signature"}:{ok:!0}}catch{return{ok:!0}}}let p=null;function V(){return p===null&&(p=N?!U().ok:!1),p}function re(e){return V()&&!n.free.includes(e)?!1:u.has(e)}function te(){return a}function ie(){if(!s)return{tier:"free",features:[...n.free],expiresAt:null,valid:!1,issuedTo:null,key:null};const e=f(s);return{tier:e.tier,features:e.features,expiresAt:e.expiresAt,valid:e.valid,issuedTo:s.license.issued_to,key:s.license.key}}function H(){try{if(!_(c))return null;const e=K(c,"utf-8"),r=JSON.parse(e);return!r.license||!r.signature||!r.license.key||!r.license.tier?null:r}catch{return null}}function J(e){_(l)||b(l,{recursive:!0});const r=JSON.stringify(e,null,2);h(c,r,{encoding:"utf-8",mode:384}),B(c,384)}function ne(e){let r;try{r=JSON.parse(e)}catch{return{valid:!1,tier:"free",features:[...n.free],expiresAt:null,error:"Invalid license format \u2014 expected JSON"}}if(!r.license||!r.signature)return{valid:!1,tier:"free",features:[...n.free],expiresAt:null,error:"Malformed license \u2014 missing license data or signature"};const i=f(r);return i.valid&&(J(r),s=r,a=i.tier,u=new Set(i.features)),i}function se(){const e=H();if(!e){s=null,a="free",u=new Set(n.free);return}const r=f(e);r.valid?(s=e,a=r.tier,u=new Set(r.features)):(s=null,a="free",u=new Set(n.free))}function oe(){s=null,a="free",u=new Set(n.free)}export{P as TIER_DEVICE_LIMITS,n as TIER_FEATURES,ne as activateLicense,q as createLicense,Z as generateKeyPair,j as generateLicenseKey,ie as getLicenseInfo,te as getTier,re as hasFeature,se as initializeLicense,H as loadLicense,oe as resetLicense,Y as resolveFeaturesForTier,J as saveLicense,U as selfTestVerifier,ee as signLicense,f as verifyLicense};
@@ -1,4 +1,4 @@
1
- import{spawn as h}from"child_process";const m=300*1e3,g=10,f=3e4;class _{db;opts;clients=new Map;maintenance;constructor(e,t={}){this.db=e,this.opts=t,this.maintenance=setInterval(()=>this.reapIdle(),3e4),this.maintenance.unref&&this.maintenance.unref()}register(e){return this.db.prepare(`
1
+ import{spawn as f}from"child_process";import{McpIntegrity as h}from"./mcp-integrity.js";const g=300*1e3,E=10,m=3e4;class C{db;opts;clients=new Map;maintenance;integrity=new h;constructor(e,t={}){this.db=e,this.opts=t,this.maintenance=setInterval(()=>this.reapIdle(),3e4),this.maintenance.unref&&this.maintenance.unref()}register(e){this.db.prepare(`
2
2
  INSERT INTO mcp_client_configs (server_name, command, args, env)
3
3
  VALUES (?, ?, ?, ?)
4
4
  ON CONFLICT(server_name) DO UPDATE SET
@@ -6,15 +6,15 @@ import{spawn as h}from"child_process";const m=300*1e3,g=10,f=3e4;class _{db;opts
6
6
  args = excluded.args,
7
7
  env = excluded.env,
8
8
  enabled = 1
9
- `).run(e.server_name,e.command,e.args?JSON.stringify(e.args):null,e.env?JSON.stringify(e.env):null),this.getConfig(e.server_name)}getConfig(e){return this.db.prepare("SELECT * FROM mcp_client_configs WHERE server_name = ?").get(e)??null}list(){return this.db.prepare("SELECT * FROM mcp_client_configs ORDER BY server_name").all()}disable(e){const t=this.db.prepare("UPDATE mcp_client_configs SET enabled = 0 WHERE server_name = ?").run(e);return this.closeClient(e),t.changes>0}delete(e){const t=this.db.prepare("DELETE FROM mcp_client_configs WHERE server_name = ?").run(e);return this.closeClient(e),t.changes>0}async call(e,t,s){const i=Date.now(),r=this.getConfig(e);if(!r){const n={ok:!1,server_name:e,tool_name:t,error:`Server '${e}' not registered. Use wyrm_mcp_register first.`,latency_ms:Date.now()-i};return this.logCall(n),n}if(!r.enabled){const n={ok:!1,server_name:e,tool_name:t,error:`Server '${e}' is disabled.`,latency_ms:Date.now()-i};return this.logCall(n),n}try{const n=await this.ensureClient(e,r),o=await this.rpc(n,"tools/call",{name:t,arguments:s});this.db.prepare(`
9
+ `).run(e.server_name,e.command,e.args?JSON.stringify(e.args):null,e.env?JSON.stringify(e.env):null);const t=this.getConfig(e.server_name);return this.integrity.pin({server_name:t.server_name,command:t.command,args:t.args,env:t.env}),t}getConfig(e){return this.db.prepare("SELECT * FROM mcp_client_configs WHERE server_name = ?").get(e)??null}list(){return this.db.prepare("SELECT * FROM mcp_client_configs ORDER BY server_name").all()}disable(e){const t=this.db.prepare("UPDATE mcp_client_configs SET enabled = 0 WHERE server_name = ?").run(e);return this.closeClient(e),t.changes>0}delete(e){const t=this.db.prepare("DELETE FROM mcp_client_configs WHERE server_name = ?").run(e);return this.closeClient(e),this.integrity.unpin(e),t.changes>0}async call(e,t,n){const r=Date.now(),i=this.getConfig(e);if(!i){const s={ok:!1,server_name:e,tool_name:t,error:`Server '${e}' not registered. Use wyrm_mcp_register first.`,latency_ms:Date.now()-r};return this.logCall(s),s}if(!i.enabled){const s={ok:!1,server_name:e,tool_name:t,error:`Server '${e}' is disabled.`,latency_ms:Date.now()-r};return this.logCall(s),s}try{const s=await this.ensureClient(e,i),o=await this.rpc(s,"tools/call",{name:t,arguments:n});this.db.prepare(`
10
10
  UPDATE mcp_client_configs
11
11
  SET last_used_at = datetime('now'),
12
12
  use_count = use_count + 1
13
13
  WHERE server_name = ?
14
- `).run(e);const l={ok:!0,server_name:e,tool_name:t,result:o,latency_ms:Date.now()-i};return this.logCall(l,s),l}catch(n){const o={ok:!1,server_name:e,tool_name:t,error:n.message,latency_ms:Date.now()-i};return this.logCall(o,s),o}}async listTools(e){const t=this.getConfig(e);if(!t)return null;try{const s=await this.ensureClient(e,t);return(await this.rpc(s,"tools/list",{})).tools??[]}catch{return null}}async shutdown(){this.maintenance&&clearInterval(this.maintenance);for(const e of Array.from(this.clients.keys()))this.closeClient(e)}logCall(e,t){try{this.db.prepare(`
14
+ `).run(e);const c={ok:!0,server_name:e,tool_name:t,result:o,latency_ms:Date.now()-r};return this.logCall(c,n),c}catch(s){const o={ok:!1,server_name:e,tool_name:t,error:s.message,latency_ms:Date.now()-r};return this.logCall(o,n),o}}async listTools(e){const t=this.getConfig(e);if(!t)return null;try{const n=await this.ensureClient(e,t);return(await this.rpc(n,"tools/list",{})).tools??[]}catch{return null}}async shutdown(){this.maintenance&&clearInterval(this.maintenance);for(const e of Array.from(this.clients.keys()))this.closeClient(e)}logCall(e,t){try{this.db.prepare(`
15
15
  INSERT INTO external_call_log
16
16
  (server_name, tool_name, args_summary, success, error_message, latency_ms)
17
17
  VALUES (?, ?, ?, ?, ?, ?)
18
- `).run(e.server_name,e.tool_name,t?JSON.stringify(t).slice(0,500):null,e.ok?1:0,e.error??null,e.latency_ms)}catch{}}reapIdle(){const e=this.opts.idleTimeoutMs??m,t=Date.now();for(const[s,i]of this.clients)t-i.lastUsedAt>e&&this.closeClient(s)}closeClient(e){const t=this.clients.get(e);if(t){for(const s of t.pendingByReqId.values())clearTimeout(s.timer),s.reject(new Error("Client closing"));try{t.proc.kill("SIGTERM")}catch{}this.clients.delete(e)}}async ensureClient(e,t){const s=this.clients.get(e);if(s&&!s.proc.killed)return s.lastUsedAt=Date.now(),s;const i=this.opts.maxClients??g;if(this.clients.size>=i){const c=Array.from(this.clients.entries()).sort(([,u],[,p])=>u.lastUsedAt-p.lastUsedAt)[0];c&&this.closeClient(c[0])}const r=t.args?JSON.parse(t.args):[],n=t.env?JSON.parse(t.env):{},o=["PATH","HOME","TMPDIR","TEMP","TMP","LANG","LC_ALL","LC_CTYPE","TERM","TZ","SHELL","USER","LOGNAME","NODE_PATH","SystemRoot","WINDIR","USERPROFILE","APPDATA","LOCALAPPDATA","PATHEXT","COMSPEC","PROGRAMFILES","PROGRAMDATA"],l={};for(const c of o){const u=process.env[c];u!==void 0&&(l[c]=u)}const d=h(t.command,r,{env:{...l,...n},stdio:["pipe","pipe","pipe"]}),a={proc:d,pendingByReqId:new Map,buffer:"",nextReqId:1,lastUsedAt:Date.now(),initialized:!1};return d.stdout.setEncoding("utf-8"),d.stdout.on("data",c=>this.handleStdout(a,c)),d.on("error",()=>this.closeClient(e)),d.on("exit",()=>this.closeClient(e)),this.clients.set(e,a),await this.rpc(a,"initialize",{protocolVersion:"2024-11-05",capabilities:{},clientInfo:{name:"wyrm-mcp",version:"5.0.0"}}),this.send(a,{jsonrpc:"2.0",method:"notifications/initialized"}),a.initialized=!0,a}handleStdout(e,t){e.buffer+=t;let s;for(;(s=e.buffer.indexOf(`
19
- `))>=0;){const i=e.buffer.slice(0,s).trim();if(e.buffer=e.buffer.slice(s+1),!!i)try{const r=JSON.parse(i);if(r.id!=null){const n=e.pendingByReqId.get(r.id);n&&(clearTimeout(n.timer),e.pendingByReqId.delete(r.id),r.error?n.reject(new Error(r.error.message)):n.resolve(r.result))}}catch{}}}send(e,t){!e.proc.stdin||e.proc.stdin.destroyed||e.proc.stdin.write(JSON.stringify(t)+`
20
- `)}rpc(e,t,s){const i=e.nextReqId++,r={jsonrpc:"2.0",id:i,method:t,params:s};return new Promise((n,o)=>{const l=setTimeout(()=>{e.pendingByReqId.delete(i),o(new Error(`MCP call timed out after ${f}ms`))},f);e.pendingByReqId.set(i,{resolve:n,reject:o,timer:l}),this.send(e,r)})}}export{_ as OutboundMcpClient};
18
+ `).run(e.server_name,e.tool_name,t?JSON.stringify(t).slice(0,500):null,e.ok?1:0,e.error??null,e.latency_ms)}catch{}}reapIdle(){const e=this.opts.idleTimeoutMs??g,t=Date.now();for(const[n,r]of this.clients)t-r.lastUsedAt>e&&this.closeClient(n)}closeClient(e){const t=this.clients.get(e);if(t){for(const n of t.pendingByReqId.values())clearTimeout(n.timer),n.reject(new Error("Client closing"));try{t.proc.kill("SIGTERM")}catch{}this.clients.delete(e)}}async ensureClient(e,t){const n=this.clients.get(e);if(n&&!n.proc.killed)return n.lastUsedAt=Date.now(),n;const r=this.opts.maxClients??E;if(this.clients.size>=r){const l=Array.from(this.clients.entries()).sort(([,u],[,p])=>u.lastUsedAt-p.lastUsedAt)[0];l&&this.closeClient(l[0])}this.integrity.verify({server_name:t.server_name,command:t.command,args:t.args,env:t.env});const i=t.args?JSON.parse(t.args):[],s=t.env?JSON.parse(t.env):{},o=["PATH","HOME","TMPDIR","TEMP","TMP","LANG","LC_ALL","LC_CTYPE","TERM","TZ","SHELL","USER","LOGNAME","NODE_PATH","SystemRoot","WINDIR","USERPROFILE","APPDATA","LOCALAPPDATA","PATHEXT","COMSPEC","PROGRAMFILES","PROGRAMDATA"],c={};for(const l of o){const u=process.env[l];u!==void 0&&(c[l]=u)}const d=f(t.command,i,{env:{...c,...s},stdio:["pipe","pipe","pipe"]}),a={proc:d,pendingByReqId:new Map,buffer:"",nextReqId:1,lastUsedAt:Date.now(),initialized:!1};return d.stdout.setEncoding("utf-8"),d.stdout.on("data",l=>this.handleStdout(a,l)),d.on("error",()=>this.closeClient(e)),d.on("exit",()=>this.closeClient(e)),this.clients.set(e,a),await this.rpc(a,"initialize",{protocolVersion:"2024-11-05",capabilities:{},clientInfo:{name:"wyrm-mcp",version:"5.0.0"}}),this.send(a,{jsonrpc:"2.0",method:"notifications/initialized"}),a.initialized=!0,a}handleStdout(e,t){e.buffer+=t;let n;for(;(n=e.buffer.indexOf(`
19
+ `))>=0;){const r=e.buffer.slice(0,n).trim();if(e.buffer=e.buffer.slice(n+1),!!r)try{const i=JSON.parse(r);if(i.id!=null){const s=e.pendingByReqId.get(i.id);s&&(clearTimeout(s.timer),e.pendingByReqId.delete(i.id),i.error?s.reject(new Error(i.error.message)):s.resolve(i.result))}}catch{}}}send(e,t){!e.proc.stdin||e.proc.stdin.destroyed||e.proc.stdin.write(JSON.stringify(t)+`
20
+ `)}rpc(e,t,n){const r=e.nextReqId++,i={jsonrpc:"2.0",id:r,method:t,params:n};return new Promise((s,o)=>{const c=setTimeout(()=>{e.pendingByReqId.delete(r),o(new Error(`MCP call timed out after ${m}ms`))},m);e.pendingByReqId.set(r,{resolve:s,reject:o,timer:c}),this.send(e,i)})}}export{C as OutboundMcpClient};
@@ -0,0 +1,2 @@
1
+ import*as n from"crypto";import*as r from"fs";import*as i from"path";import*as m from"os";class d{keyPath;pinsPath;key=null;warnedUnpinned=new Set;constructor(e=i.join(m.homedir(),".wyrm")){this.keyPath=i.join(e,"mcp-integrity.key"),this.pinsPath=i.join(e,"mcp-integrity.json")}loadKey(){if(this.key)return this.key;try{this.key=r.readFileSync(this.keyPath)}catch{r.mkdirSync(i.dirname(this.keyPath),{recursive:!0}),this.key=n.randomBytes(32),r.writeFileSync(this.keyPath,this.key,{mode:384})}return this.key}readPins(){try{return JSON.parse(r.readFileSync(this.pinsPath,"utf-8"))}catch{return{}}}writePins(e){r.mkdirSync(i.dirname(this.pinsPath),{recursive:!0}),r.writeFileSync(this.pinsPath,JSON.stringify(e,null,2),{mode:384})}mac(e){const t=JSON.stringify([e.server_name,e.command,e.args??null,e.env??null]);return n.createHmac("sha256",this.loadKey()).update(t).digest("hex")}pin(e){const t=this.readPins();t[e.server_name]=this.mac(e),this.writePins(t)}unpin(e){const t=this.readPins();e in t&&(delete t[e],this.writePins(t))}verify(e){const s=this.readPins()[e.server_name];if(s===void 0){this.warnedUnpinned.has(e.server_name)||(this.warnedUnpinned.add(e.server_name),process.stderr.write(`wyrm: MCP server "${e.server_name}" predates integrity pinning \u2014 pinning its current config now (trust-on-first-use)
2
+ `)),this.pin(e);return}const h=this.mac(e);if(!n.timingSafeEqual(Buffer.from(s,"hex"),Buffer.from(h,"hex"))){const a=new Error(`MCP server "${e.server_name}" config does not match its integrity pin \u2014 its command/args/env changed outside wyrm_mcp_register. Refusing to spawn. If this change is intentional, re-register the server with wyrm_mcp_register to re-pin it.`);throw a.code="WYRM_MCP_INTEGRITY",a}}}export{d as McpIntegrity};
package/dist/setup.js CHANGED
@@ -1,2 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import{detectClients as m,autoConfigureAll as u,removeFromAll as $,configureSpecific as p,reconfAll as h,getStatusSummary as y,findWyrmServerPath as b,getDefaultDbPath as v,loadWyrmMeta as k}from"./autoconfig.js";import{c as o,icons as r,BANNER as I,MINI_BANNER as i,printError as f,printSection as a}from"./cli.js";function w(s){const e=s.slice(2);if(e.length===0)return{command:"auto"};const n={command:"auto"};for(let l=0;l<e.length;l++)switch(e[l]){case"--check":case"-c":case"check":case"status":n.command="check";break;case"--remove":case"-r":case"remove":case"uninstall":n.command="remove";break;case"--only":case"-o":case"only":n.command="only",e[l+1]&&(n.clientIds=e[l+1].split(",").map(t=>t.trim()),l++);break;case"--reconf":case"--reconfigure":case"reconf":n.command="reconf";break;case"--list":case"-l":case"list":n.command="list";break;case"--server":case"-s":e[l+1]&&(n.serverPath=e[l+1],l++);break;case"--db":case"-d":e[l+1]&&(n.dbPath=e[l+1],l++);break;case"--help":case"-h":case"help":n.command="help";break}return n}function g(s){console.log("");const e=s.filter(c=>c.action==="configured"),n=s.filter(c=>c.action==="updated"),l=s.filter(c=>c.action==="skipped"),t=s.filter(c=>c.action==="failed");for(const c of e)console.log(` ${r.success} ${c.client.icon} ${o.green(c.client.name)} \u2014 ${o.success("configured")}`),c.backup&&console.log(` ${o.dim(`backup \u2192 ${c.backup}`)}`);for(const c of n)console.log(` ${r.sync} ${c.client.icon} ${o.blue(c.client.name)} \u2014 ${o.cyan("updated")}`),c.backup&&console.log(` ${o.dim(`backup \u2192 ${c.backup}`)}`);for(const c of l)console.log(` ${o.dim(` \u25CB ${c.client.icon} ${c.client.name} \u2014 ${c.message}`)}`);for(const c of t)console.log(` ${r.error} ${c.client.icon} ${o.red(c.client.name)} \u2014 ${o.error(c.message)}`);console.log("");const d=e.length+n.length;d>0?(console.log(` ${o.success(`${r.dragon} Wyrm connected to ${d} AI client(s)`)}`),console.log(` ${o.dim("Switch AIs anytime \u2014 run wyrm-setup again to reconnect")}`)):t.length>0?console.log(` ${o.error("Some configurations failed. Check errors above.")}`):console.log(` ${o.dim("No AI clients detected. Install one and try again.")}`),console.log("")}function A(){console.log(i),console.log(""),console.log(`${o.bold("Usage:")} wyrm-setup ${o.dim("[command] [options]")}`),console.log(""),console.log(`${o.bold("Commands:")}`),console.log(` ${o.cyan("(no args)")} Auto-detect and configure all AI clients`),console.log(` ${o.cyan("check")} Show current configuration status`),console.log(` ${o.cyan("list")} List all supported AI clients`),console.log(` ${o.cyan("remove")} Remove Wyrm from all AI clients`),console.log(` ${o.cyan("reconf")} Re-configure previously configured clients`),console.log(` ${o.cyan("only X,Y")} Configure specific clients only`),console.log(` ${o.cyan("help")} Show this help message`),console.log(""),console.log(`${o.bold("Options:")}`),console.log(` ${o.cyan("--server P")} Override Wyrm MCP server path`),console.log(` ${o.cyan("--db P")} Override Wyrm database path`),console.log(""),console.log(`${o.bold("Client IDs:")}`);const s=m();for(const e of s)console.log(` ${e.icon} ${o.cyan(e.id.padEnd(18))} ${e.name}`);console.log(""),console.log(`${o.bold("Examples:")}`),console.log(` wyrm-setup ${o.dim("# Auto-configure everything")}`),console.log(` wyrm-setup check ${o.dim("# See what's configured")}`),console.log(` wyrm-setup only vscode-copilot,cursor ${o.dim("# Only VS Code + Cursor")}`),console.log(` wyrm-setup --server /path/to/wyrm/dist/index.js ${o.dim("# Custom server path")}`),console.log(` wyrm-setup remove ${o.dim("# Remove from all clients")}`),console.log("")}function C(){console.log(i),console.log(""),a("Supported AI Clients"),console.log("");const s=m();for(const e of s){const n=e.detected?e.configured?o.success("\u25CF connected"):o.yellow("\u25D0 available"):o.dim("\u25CB not found"),l=e.version?` ${o.dim(`v${e.version}`)}`:"";console.log(` ${e.icon} ${o.bold(e.name.padEnd(20))} ${o.cyan(e.id.padEnd(18))} ${n}${l}`),console.log(` ${o.dim(`Config: ${e.configPath}`)}`)}console.log("")}async function P(){const s=w(process.argv);switch(s.command){case"help":A();break;case"check":{console.log(i),console.log(""),console.log(y());break}case"list":C();break;case"auto":{console.log(I),a("Auto-Configure"),console.log("");const e=s.serverPath||b(),n=s.dbPath||v();console.log(` ${r.sword} Server: ${o.cyan(e)}`),console.log(` ${r.treasure} DB: ${o.cyan(n)}`),console.log(""),console.log(` ${o.dim("Scanning for AI clients...")}`);const l=u({serverPath:e,dbPath:n});g(l);break}case"only":{(!s.clientIds||s.clientIds.length===0)&&(f("No client IDs specified. Use: wyrm-setup only vscode-copilot,cursor"),process.exit(1)),console.log(i),a(`Configure: ${s.clientIds.join(", ")}`);const e=p(s.clientIds,{serverPath:s.serverPath,dbPath:s.dbPath});g(e);break}case"remove":{console.log(i),a("Remove Wyrm from AI Clients"),console.log(""),console.log(` ${o.warning("Removing Wyrm configuration from all AI clients...")}`);const e=$(),n=e.filter(t=>t.action==="configured"),l=e.filter(t=>t.action==="skipped");for(const t of n)console.log(` ${r.check} ${t.client.icon} ${t.client.name} \u2014 removed`),t.backup&&console.log(` ${o.dim(`backup \u2192 ${t.backup}`)}`);for(const t of l)console.log(` ${o.dim(` \u25CB ${t.client.icon} ${t.client.name} \u2014 ${t.message}`)}`);console.log(""),n.length>0&&console.log(` ${o.success(`Removed from ${n.length} client(s). Run wyrm-setup to reconnect.`)}`),console.log("");break}case"reconf":{console.log(i),a("Re-Configure");const e=k();console.log(e?` ${o.dim(`Restoring config for: ${e.configuredClients.join(", ")}`)}`:` ${o.dim("No previous config found \u2014 running full auto-configure")}`);const n=h();g(n);break}}}P().catch(s=>{f(`Setup failed: ${s}`),process.exit(1)});
2
+ import{detectClients as m,autoConfigureAll as u,removeFromAll as p,configureSpecific as $,reconfAll as y,getStatusSummary as h,findWyrmServerPath as b,getDefaultDbPath as v,loadWyrmMeta as k,exportServerConfig as P}from"./autoconfig.js";import{c as o,icons as r,BANNER as C,MINI_BANNER as i,printError as f,printSection as a}from"./cli.js";function w(c){const e=c.slice(2);if(e.length===0)return{command:"auto"};const n={command:"auto"};for(let l=0;l<e.length;l++)switch(e[l]){case"--check":case"-c":case"check":case"status":n.command="check";break;case"--remove":case"-r":case"remove":case"uninstall":n.command="remove";break;case"--only":case"-o":case"only":n.command="only",e[l+1]&&(n.clientIds=e[l+1].split(",").map(t=>t.trim()),l++);break;case"--reconf":case"--reconfigure":case"reconf":n.command="reconf";break;case"--list":case"-l":case"list":n.command="list";break;case"--export":case"export":{n.command="export";const t=e[l+1];(t==="stdio"||t==="vscode"||t==="mcp-json")&&(n.exportFormat=t,l++);break}case"--server":case"-s":e[l+1]&&(n.serverPath=e[l+1],l++);break;case"--db":case"-d":e[l+1]&&(n.dbPath=e[l+1],l++);break;case"--help":case"-h":case"help":n.command="help";break}return n}function g(c){console.log("");const e=c.filter(s=>s.action==="configured"),n=c.filter(s=>s.action==="updated"),l=c.filter(s=>s.action==="skipped"),t=c.filter(s=>s.action==="failed");for(const s of e)console.log(` ${r.success} ${s.client.icon} ${o.green(s.client.name)} \u2014 ${o.success("configured")}`),s.backup&&console.log(` ${o.dim(`backup \u2192 ${s.backup}`)}`);for(const s of n)console.log(` ${r.sync} ${s.client.icon} ${o.blue(s.client.name)} \u2014 ${o.cyan("updated")}`),s.backup&&console.log(` ${o.dim(`backup \u2192 ${s.backup}`)}`);for(const s of l)console.log(` ${o.dim(` \u25CB ${s.client.icon} ${s.client.name} \u2014 ${s.message}`)}`);for(const s of t)console.log(` ${r.error} ${s.client.icon} ${o.red(s.client.name)} \u2014 ${o.error(s.message)}`);console.log("");const d=e.length+n.length;d>0?(console.log(` ${o.success(`${r.dragon} Wyrm connected to ${d} AI client(s)`)}`),console.log(` ${o.dim("Switch AIs anytime \u2014 run wyrm-setup again to reconnect")}`)):t.length>0?console.log(` ${o.error("Some configurations failed. Check errors above.")}`):console.log(` ${o.dim("No AI clients detected. Install one and try again.")}`),console.log("")}function I(){console.log(i),console.log(""),console.log(`${o.bold("Usage:")} wyrm-setup ${o.dim("[command] [options]")}`),console.log(""),console.log(`${o.bold("Commands:")}`),console.log(` ${o.cyan("(no args)")} Auto-detect and configure all AI clients`),console.log(` ${o.cyan("check")} Show current configuration status`),console.log(` ${o.cyan("list")} List all supported AI clients`),console.log(` ${o.cyan("remove")} Remove Wyrm from all AI clients`),console.log(` ${o.cyan("reconf")} Re-configure previously configured clients`),console.log(` ${o.cyan("only X,Y")} Configure specific clients only`),console.log(` ${o.cyan("export [fmt]")} Print a portable config (fmt: mcp-json|vscode|stdio) for any MCP host`),console.log(` ${o.cyan("help")} Show this help message`),console.log(""),console.log(`${o.bold("Options:")}`),console.log(` ${o.cyan("--server P")} Override Wyrm MCP server path`),console.log(` ${o.cyan("--db P")} Override Wyrm database path`),console.log(""),console.log(`${o.bold("Client IDs:")}`);const c=m();for(const e of c)console.log(` ${e.icon} ${o.cyan(e.id.padEnd(18))} ${e.name}`);console.log(""),console.log(`${o.bold("Examples:")}`),console.log(` wyrm-setup ${o.dim("# Auto-configure everything")}`),console.log(` wyrm-setup check ${o.dim("# See what's configured")}`),console.log(` wyrm-setup only vscode-copilot,cursor ${o.dim("# Only VS Code + Cursor")}`),console.log(` wyrm-setup --server /path/to/wyrm/dist/index.js ${o.dim("# Custom server path")}`),console.log(` wyrm-setup remove ${o.dim("# Remove from all clients")}`),console.log(` wyrm-setup export stdio > wyrm.json ${o.dim("# Portable config for any MCP host")}`),console.log("")}function A(){console.log(i),console.log(""),a("Supported AI Clients"),console.log("");const c=m();for(const e of c){const n=e.detected?e.configured?o.success("\u25CF connected"):o.yellow("\u25D0 available"):o.dim("\u25CB not found"),l=e.version?` ${o.dim(`v${e.version}`)}`:"";console.log(` ${e.icon} ${o.bold(e.name.padEnd(20))} ${o.cyan(e.id.padEnd(18))} ${n}${l}`),console.log(` ${o.dim(`Config: ${e.configPath}`)}`)}console.log("")}async function S(){const c=w(process.argv);switch(c.command){case"help":I();break;case"check":{console.log(i),console.log(""),console.log(h());break}case"list":A();break;case"auto":{console.log(C),a("Auto-Configure"),console.log("");const e=c.serverPath||b(),n=c.dbPath||v();console.log(` ${r.sword} Server: ${o.cyan(e)}`),console.log(` ${r.treasure} DB: ${o.cyan(n)}`),console.log(""),console.log(` ${o.dim("Scanning for AI clients...")}`);const l=u({serverPath:e,dbPath:n});g(l);break}case"only":{(!c.clientIds||c.clientIds.length===0)&&(f("No client IDs specified. Use: wyrm-setup only vscode-copilot,cursor"),process.exit(1)),console.log(i),a(`Configure: ${c.clientIds.join(", ")}`);const e=$(c.clientIds,{serverPath:c.serverPath,dbPath:c.dbPath});g(e);break}case"remove":{console.log(i),a("Remove Wyrm from AI Clients"),console.log(""),console.log(` ${o.warning("Removing Wyrm configuration from all AI clients...")}`);const e=p(),n=e.filter(t=>t.action==="configured"),l=e.filter(t=>t.action==="skipped");for(const t of n)console.log(` ${r.check} ${t.client.icon} ${t.client.name} \u2014 removed`),t.backup&&console.log(` ${o.dim(`backup \u2192 ${t.backup}`)}`);for(const t of l)console.log(` ${o.dim(` \u25CB ${t.client.icon} ${t.client.name} \u2014 ${t.message}`)}`);console.log(""),n.length>0&&console.log(` ${o.success(`Removed from ${n.length} client(s). Run wyrm-setup to reconnect.`)}`),console.log("");break}case"reconf":{console.log(i),a("Re-Configure");const e=k();console.log(e?` ${o.dim(`Restoring config for: ${e.configuredClients.join(", ")}`)}`:` ${o.dim("No previous config found \u2014 running full auto-configure")}`);const n=y();g(n);break}case"export":{const e=c.exportFormat??"mcp-json",n=P(e,{serverPath:c.serverPath,dbPath:c.dbPath});process.stderr.write(`${o.dim(`# Wyrm MCP config (${e}). Paste into any MCP-compatible host.`)}
3
+ `),process.stdout.write(JSON.stringify(n,null,2)+`
4
+ `);break}}}S().catch(c=>{f(`Setup failed: ${c}`),process.exit(1)});
@@ -0,0 +1,2 @@
1
+ import n from"better-sqlite3";import{NodeSqliteFacade as i}from"./node-sqlite-facade.js";let t=!1;function a(){try{return require("node:sqlite"),!0}catch{return!1}}function o(){if(process.env.WYRM_STORAGE==="node"){if(a())return"node";t||(t=!0,process.stderr.write(`wyrm: WYRM_STORAGE=node requested but node:sqlite is unavailable in this Node runtime; falling back to better-sqlite3
2
+ `))}return"better-sqlite3"}function s(e,r={}){return o()==="node"?new i(e,r):new n(e,r)}export{s as openDatabase,o as resolveBackend};
@@ -0,0 +1 @@
1
+ import*as h from"fs";const u={1:"SQLITE_ERROR",5:"SQLITE_BUSY",6:"SQLITE_LOCKED",8:"SQLITE_READONLY",10:"SQLITE_IOERR",11:"SQLITE_CORRUPT",13:"SQLITE_FULL",14:"SQLITE_CANTOPEN",19:"SQLITE_CONSTRAINT",21:"SQLITE_MISUSE"};function c(r){if(r&&typeof r=="object"){const t=r;if(typeof t.errcode=="number"){const e=u[t.errcode&255];e&&(t.code=e)}}throw r}function d(r){return typeof r=="bigint"&&r<=BigInt(Number.MAX_SAFE_INTEGER)?Number(r):r}class I{stmt;constructor(t){this.stmt=t}get(...t){try{return this.stmt.get(...t)}catch(e){c(e)}}all(...t){try{return this.stmt.all(...t)}catch(e){c(e)}}run(...t){try{const e=this.stmt.run(...t);return{changes:Number(e.changes),lastInsertRowid:d(e.lastInsertRowid)}}catch(e){c(e)}}*iterate(...t){try{yield*this.stmt.iterate(...t)}catch(e){c(e)}}safeIntegers(t=!0){return this.stmt.setReadBigInts(t),this}}class m{db;txDepth=0;name;open=!0;constructor(t,e={}){if(e.fileMustExist&&t!==":memory:"&&!h.existsSync(t)){const n=new Error(`unable to open database file: ${t}`);throw n.code="SQLITE_CANTOPEN",n}const{DatabaseSync:s}=require("node:sqlite");this.db=e.readonly?new s(t,{readOnly:!0}):new s(t),this.name=t}prepare(t){try{return new I(this.db.prepare(t))}catch(e){c(e)}}exec(t){try{return this.db.exec(t),this}catch(e){c(e)}}pragma(t,e){try{const s=this.db.prepare(`PRAGMA ${t}`).all();if(e?.simple){const n=s[0];return n===void 0?void 0:n[Object.keys(n)[0]]}return s}catch(s){c(s)}}transaction(t){const e=n=>(...E)=>{const a=this.txDepth>0,i=`wyrm_sp_${this.txDepth}`;this.exec(a?`SAVEPOINT ${i}`:n),this.txDepth++;try{const o=t(...E);return this.txDepth--,this.exec(a?`RELEASE ${i}`:"COMMIT"),o}catch(o){this.txDepth--;try{this.exec(a?`ROLLBACK TO ${i}; RELEASE ${i}`:"ROLLBACK")}catch{}throw o}};return Object.assign(e("BEGIN"),{immediate:e("BEGIN IMMEDIATE"),deferred:e("BEGIN DEFERRED"),exclusive:e("BEGIN EXCLUSIVE")})}close(){this.db.close(),this.open=!1}}export{m as NodeSqliteFacade};
package/dist/sub-agent.js CHANGED
@@ -1,43 +1,44 @@
1
- import{sanitizeFtsQuery as O}from"./security.js";const w="http://localhost:11434",g="llama3.2",L="gpt-4o-mini";class x{db;constructor(t){this.db=t}assembleContext(t,s,i){const l=[];let c=0;const r={},a=(n,e)=>{const o=e.length;return c+o>i?!1:(l.push({kind:n,text:e}),c+=o,r[n]=(r[n]??0)+1,!0)},u=O(t.slice(0,200));if(s!=null){const n=this.db.prepare(`
1
+ import{sanitizeFtsQuery as T}from"./security.js";const M="http://localhost:11434",v="llama3.2",R="gpt-4o-mini",S="claude-opus-4-8",D="nvidia/nemotron-3-ultra-550b",I="https://integrate.api.nvidia.com/v1",$="You are Wyrm, a project-memory assistant. Answer the user's question using ONLY the supplied context. If the context is insufficient, say so explicitly. Be terse.";class j{db;constructor(t){this.db=t}assembleContext(t,n,l){const u=[];let c=0;const a={},s=(i,e)=>{const o=e.length;return c+o>l?!1:(u.push({kind:i,text:e}),c+=o,a[i]=(a[i]??0)+1,!0)},r=T(t.slice(0,200));if(n!=null){const i=this.db.prepare(`
2
2
  SELECT category, key, value, rationale, confidence
3
3
  FROM ground_truths
4
4
  WHERE project_id = ? AND is_current = 1
5
5
  ORDER BY confidence DESC
6
6
  LIMIT 30
7
- `).all(s);for(const e of n){const o=`[truth] ${e.category}.${e.key} = ${e.value}${e.rationale?` (${e.rationale})`:""}`;if(!a("truth",o))break}}if(u)try{const n=this.db.prepare(`
7
+ `).all(n);for(const e of i){const o=`[truth] ${e.category}.${e.key} = ${e.value}${e.rationale?` (${e.rationale})`:""}`;if(!s("truth",o))break}}if(r)try{const i=this.db.prepare(`
8
8
  SELECT s.id, s.date, s.summary, s.objectives, s.completed
9
9
  FROM sessions s
10
10
  JOIN sessions_fts fts ON fts.rowid = s.id
11
11
  WHERE sessions_fts MATCH ?
12
- ${s!=null?"AND s.project_id = ?":""}
12
+ ${n!=null?"AND s.project_id = ?":""}
13
13
  ORDER BY s.date DESC
14
14
  LIMIT 8
15
- `).all(...s!=null?[u,s]:[u]);for(const e of n){const o=[e.summary,e.completed,e.objectives].filter(Boolean).join(" ").slice(0,600);if(o&&!a("session",`[session#${e.id} ${e.date}] ${o}`))break}}catch{}if(u)try{const n=this.db.prepare(`
15
+ `).all(...n!=null?[r,n]:[r]);for(const e of i){const o=[e.summary,e.completed,e.objectives].filter(Boolean).join(" ").slice(0,600);if(o&&!s("session",`[session#${e.id} ${e.date}] ${o}`))break}}catch{}if(r)try{const i=this.db.prepare(`
16
16
  SELECT ma.id, ma.problem, ma.validated_fix, ma.why_it_worked
17
17
  FROM memory_artifacts ma
18
18
  JOIN memory_artifacts_fts fts ON fts.rowid = ma.id
19
19
  WHERE memory_artifacts_fts MATCH ?
20
- ${s!=null?"AND ma.project_id = ?":""}
20
+ ${n!=null?"AND ma.project_id = ?":""}
21
21
  AND (ma.needs_review = 0 OR ma.needs_review IS NULL)
22
22
  LIMIT 6
23
- `).all(...s!=null?[u,s]:[u]);for(const e of n){const o=`[artifact#${e.id}] ${e.problem} \u2192 ${e.validated_fix??""}${e.why_it_worked?` (${e.why_it_worked})`:""}`;if(!a("artifact",o.slice(0,500)))break}}catch{}if(s!=null)try{const n=this.db.prepare(`
23
+ `).all(...n!=null?[r,n]:[r]);for(const e of i){const o=`[artifact#${e.id}] ${e.problem} \u2192 ${e.validated_fix??""}${e.why_it_worked?` (${e.why_it_worked})`:""}`;if(!s("artifact",o.slice(0,500)))break}}catch{}if(n!=null)try{const i=this.db.prepare(`
24
24
  SELECT id, scope, target, description, why_failed
25
25
  FROM failure_patterns
26
26
  WHERE resolved = 0 AND (project_id = ? OR project_id IS NULL)
27
27
  ORDER BY occurrences DESC, last_seen DESC
28
28
  LIMIT 5
29
- `).all(s);for(const e of n){const o=`[failure#${e.id} avoid] ${e.scope}:${e.target} \u2014 ${e.description}${e.why_failed?` (${e.why_failed})`:""}`;if(!a("failure",o.slice(0,400)))break}}catch{}try{const n=`%${t.replace(/[%_]/g,"").slice(0,50)}%`,e=this.db.prepare(`
29
+ `).all(n);for(const e of i){const o=`[failure#${e.id} avoid] ${e.scope}:${e.target} \u2014 ${e.description}${e.why_failed?` (${e.why_failed})`:""}`;if(!s("failure",o.slice(0,400)))break}}catch{}try{const i=`%${t.replace(/[%_]/g,"").slice(0,50)}%`,e=this.db.prepare(`
30
30
  SELECT symbol, kind, language, file_path, line, signature
31
31
  FROM symbol_index
32
32
  WHERE symbol LIKE ?
33
- ${s!=null?"AND project_id = ?":""}
33
+ ${n!=null?"AND project_id = ?":""}
34
34
  LIMIT 10
35
- `).all(...s!=null?[n,s]:[n]);for(const o of e){const m=`[symbol ${o.language}:${o.kind}] ${o.symbol} @ ${o.file_path}:${o.line}`;if(!a("symbol",m))break}}catch{}const _=Object.entries(r).map(([n,e])=>`${n}:${e}`).join(",");return{pieces:l,summary:_,chars:c}}async tryOllama(t,s,i,l){const c=this.buildPrompt(t,s);try{const r=await fetch(`${i.replace(/\/$/,"")}/api/generate`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({model:l,prompt:c,stream:!1}),signal:AbortSignal.timeout(6e4)});if(!r.ok)return null;const a=await r.json();return a.response?{answer:a.response.trim(),tokens_in:a.prompt_eval_count,tokens_out:a.eval_count}:null}catch{return null}}async tryOpenAI(t,s,i,l){try{const c=await fetch("https://api.openai.com/v1/chat/completions",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${i}`},body:JSON.stringify({model:l,messages:[{role:"system",content:"You are Wyrm, a project-memory assistant. Answer the user's question using ONLY the supplied context. If the context is insufficient, say so explicitly. Be terse."},{role:"user",content:this.buildPrompt(t,s)}],temperature:.2}),signal:AbortSignal.timeout(6e4)});if(!c.ok)return null;const r=await c.json(),a=r.choices?.[0]?.message?.content?.trim();return a?{answer:a,tokens_in:r.usage?.prompt_tokens,tokens_out:r.usage?.completion_tokens}:null}catch{return null}}buildPrompt(t,s){return["You are answering a question using the project memory below.","Cite specific items by their bracketed tags (e.g. [truth], [session#42], [failure#7]).","If the context does not contain the answer, say so plainly.","","=== CONTEXT ===",s,"=== END CONTEXT ===","",`Question: ${t}`,"","Answer:"].join(`
36
- `)}async ask(t){const s=Date.now(),i=Math.min(Math.max(2e3,t.max_context_chars??12e3),5e4),{pieces:l,summary:c,chars:r}=this.assembleContext(t.query,t.project_id,i),a=l.map(h=>h.text).join(`
37
- `),u=t.model_override?.split(":")??[],_=u[0],n=u[1],e=t.ollama_url??process.env.OLLAMA_URL??w,o=t.openai_api_key??process.env.OPENAI_API_KEY;let m=null,y="none";const $=_==="openai"?["openai","ollama"]:["ollama","openai"];for(const h of $){if(m)break;if(h==="ollama"){const d=_==="ollama"&&n||g,f=await this.tryOllama(t.query,a,e,d);f&&(m=f,y=`ollama:${d}`)}else if(h==="openai"&&o){const d=_==="openai"&&n||L,f=await this.tryOpenAI(t.query,a,o,d);f&&(m=f,y=`openai:${d}`)}}const b=Date.now()-s,p=m===null,E=m?.answer??this.degradedAnswer(t.query,l);try{this.db.prepare(`
35
+ `).all(...n!=null?[i,n]:[i]);for(const o of e){const g=`[symbol ${o.language}:${o.kind}] ${o.symbol} @ ${o.file_path}:${o.line}`;if(!s("symbol",g))break}}catch{}const y=Object.entries(a).map(([i,e])=>`${i}:${e}`).join(",");return{pieces:u,summary:y,chars:c}}async tryOllama(t,n,l,u){const c=this.buildPrompt(t,n);try{const a=await fetch(`${l.replace(/\/$/,"")}/api/generate`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({model:u,prompt:c,stream:!1}),signal:AbortSignal.timeout(6e4)});if(!a.ok)return null;const s=await a.json();return s.response?{answer:s.response.trim(),tokens_in:s.prompt_eval_count,tokens_out:s.eval_count}:null}catch{return null}}async tryOpenAI(t,n,l,u){try{const c=await fetch("https://api.openai.com/v1/chat/completions",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${l}`},body:JSON.stringify({model:u,messages:[{role:"system",content:"You are Wyrm, a project-memory assistant. Answer the user's question using ONLY the supplied context. If the context is insufficient, say so explicitly. Be terse."},{role:"user",content:this.buildPrompt(t,n)}],temperature:.2}),signal:AbortSignal.timeout(6e4)});if(!c.ok)return null;const a=await c.json(),s=a.choices?.[0]?.message?.content?.trim();return s?{answer:s,tokens_in:a.usage?.prompt_tokens,tokens_out:a.usage?.completion_tokens}:null}catch{return null}}async tryAnthropic(t,n,l,u){try{const c=await fetch("https://api.anthropic.com/v1/messages",{method:"POST",headers:{"Content-Type":"application/json","x-api-key":l,"anthropic-version":"2023-06-01"},body:JSON.stringify({model:u,max_tokens:4096,system:$,thinking:{type:"adaptive"},messages:[{role:"user",content:this.buildPrompt(t,n)}]}),signal:AbortSignal.timeout(12e4)});if(!c.ok)return null;const a=await c.json();if(a.stop_reason==="refusal")return null;const s=(a.content??[]).filter(r=>r.type==="text"&&r.text).map(r=>r.text.trim()).join(`
36
+ `).trim();return s?{answer:s,tokens_in:a.usage?.input_tokens,tokens_out:a.usage?.output_tokens}:null}catch{return null}}async tryNim(t,n,l,u,c){try{const a=await fetch(`${c.replace(/\/$/,"")}/chat/completions`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${l}`},body:JSON.stringify({model:u,messages:[{role:"system",content:$},{role:"user",content:this.buildPrompt(t,n)}],temperature:.2}),signal:AbortSignal.timeout(12e4)});if(!a.ok)return null;const s=await a.json();let r=s.choices?.[0]?.message?.content??"";return r=r.replace(/<think>[\s\S]*?<\/think>/g,"").trim(),r?{answer:r,tokens_in:s.usage?.prompt_tokens,tokens_out:s.usage?.completion_tokens}:null}catch{return null}}buildPrompt(t,n){return["You are answering a question using the project memory below.","Cite specific items by their bracketed tags (e.g. [truth], [session#42], [failure#7]).","If the context does not contain the answer, say so plainly.","","=== CONTEXT ===",n,"=== END CONTEXT ===","",`Question: ${t}`,"","Answer:"].join(`
37
+ `)}async ask(t){const n=Date.now(),l=Math.min(Math.max(2e3,t.max_context_chars??12e3),5e4),{pieces:u,summary:c,chars:a}=this.assembleContext(t.query,t.project_id,l),s=u.map(d=>d.text).join(`
38
+ `),r=t.model_override?.split(":")??[],y=r[0],i=r.slice(1).join(":")||void 0,o=y?{ollama:"ollama",openai:"openai",anthropic:"anthropic",claude:"anthropic",nim:"nim",nemotron:"nim",nvidia:"nim"}[y]:void 0,g=t.ollama_url??process.env.OLLAMA_URL??M,k=t.openai_api_key??process.env.OPENAI_API_KEY,A=t.anthropic_api_key??process.env.ANTHROPIC_API_KEY,b=t.nim_api_key??process.env.NVIDIA_API_KEY??process.env.WYRM_NIM_API_KEY,x=process.env.WYRM_NIM_CHAT_URL??I;let _=null,h="none";const O=["ollama","anthropic","nim","openai"],N=o?[o,...O.filter(d=>d!==o)]:O;for(const d of N){if(_)break;const f=m=>o===d&&i||m;if(d==="ollama"){const m=f(v),p=await this.tryOllama(t.query,s,g,m);p&&(_=p,h=`ollama:${m}`)}else if(d==="anthropic"&&A){const m=f(S),p=await this.tryAnthropic(t.query,s,A,m);p&&(_=p,h=`anthropic:${m}`)}else if(d==="nim"&&b){const m=f(D),p=await this.tryNim(t.query,s,b,m,x);p&&(_=p,h=`nim:${m}`)}else if(d==="openai"&&k){const m=f(R),p=await this.tryOpenAI(t.query,s,k,m);p&&(_=p,h=`openai:${m}`)}}const L=Date.now()-n,E=_===null,w=_?.answer??this.degradedAnswer(t.query,u);try{this.db.prepare(`
38
39
  INSERT INTO llm_query_log
39
40
  (query, project_id, context_summary, context_chars, response,
40
41
  model, tokens_in, tokens_out, latency_ms, error_message)
41
42
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
42
- `).run(t.query.slice(0,1e3),t.project_id??null,c,r,E.slice(0,4e3),y,m?.tokens_in??null,m?.tokens_out??null,b,p?"No LLM available \u2014 degraded to context-only":null)}catch{}return{query:t.query,answer:E,model:y,context_summary:c,context_chars:r,latency_ms:b,tokens_in:m?.tokens_in,tokens_out:m?.tokens_out,degraded:p,error:p?"No LLM available (no Ollama on localhost:11434 and no OPENAI_API_KEY). Returning raw context.":void 0}}degradedAnswer(t,s){if(s.length===0)return`_No matching context found in Wyrm for "${t}". No LLM available to synthesize._`;const i=[];i.push(`_LLM not available \u2014 returning ${s.length} matched context pieces:_`),i.push("");for(const l of s)i.push(l.text);return i.join(`
43
- `)}}export{x as SubAgent};
43
+ `).run(t.query.slice(0,1e3),t.project_id??null,c,a,w.slice(0,4e3),h,_?.tokens_in??null,_?.tokens_out??null,L,E?"No LLM available \u2014 degraded to context-only":null)}catch{}return{query:t.query,answer:w,model:h,context_summary:c,context_chars:a,latency_ms:L,tokens_in:_?.tokens_in,tokens_out:_?.tokens_out,degraded:E,error:E?"No LLM available (no Ollama on localhost:11434 and no OPENAI_API_KEY). Returning raw context.":void 0}}degradedAnswer(t,n){if(n.length===0)return`_No matching context found in Wyrm for "${t}". No LLM available to synthesize._`;const l=[];l.push(`_LLM not available \u2014 returning ${n.length} matched context pieces:_`),l.push("");for(const u of n)l.push(u.text);return l.join(`
44
+ `)}}export{j as SubAgent};
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "contract": "wyrm-cloud-memory-v2",
3
- "version": "7.5.2",
3
+ "version": "7.6.0",
4
4
  "derivedFrom": "standard tier MINUS device-local/egress/subprocess (src/cloud-profile.ts)",
5
5
  "count": 19,
6
6
  "tools": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "contract": "wyrm-cloud-memory-v1",
3
- "version": "7.5.2",
3
+ "version": "7.6.0",
4
4
  "tools": [
5
5
  {
6
6
  "name": "wyrm_recall",
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import P from"better-sqlite3";import{readFileSync as I,realpathSync as L}from"node:fs";import{homedir as O}from"node:os";import{basename as W,dirname as j,isAbsolute as R,join as G,relative as U,resolve as S,sep as B}from"node:path";import{fileURLToPath as Y}from"node:url";import{FailurePatterns as T,normalizeIdentity as A,STALE_ANCHOR_FLAG as F}from"./failure-patterns.js";import{LEGACY_ENVELOPE as H,runWithActor as V,sanitizeActorId as N}from"./handlers/boundary.js";import{resolveProjectForPath as z}from"./project-resolve.js";const w={exitCode:0,stdout:"",stderr:""},q=new Set(["Edit","Write","MultiEdit","NotebookEdit"]),J=3;function h(t){return typeof t=="string"&&t.length>0?t:null}function d(t,r){return t?t.replace(/[\x00-\x1f\x7f]/g," ").replace(/\s+/g," ").trim().slice(0,r):""}function K(t){const r=t.hook_event_name;if(r!==void 0&&r!=="PreToolUse")return null;const s=t.tool_name,c=t.tool_input;if(typeof s!="string"||typeof c!="object"||c===null)return null;const o=c,e=h(t.cwd);if(s==="Bash"){const a=h(o.command)?.trim();if(!a)return null;const l=a.slice(0,1e3);return{scopes:["command"],probeTarget:l,identity:[a],display:d(l,80),startDir:e,absPath:null}}if(q.has(s)){const a=h(o.file_path)??h(o.notebook_path);if(!a)return null;const l=o.edits,y=h(o.new_string)??h(o.content)??h(o.new_source)??(Array.isArray(l)?l.map(_=>h(_?.new_string)??"").join(" "):""),f=R(a)?S(a):e&&R(e)?S(e,a):null;return{scopes:["file","edit"],probeTarget:W(a).slice(0,200),identity:C([a,...f?[f]:[]]),display:d(a,80),startDir:f?j(f):e,absPath:f,content:y}}return null}function C(t){const r=new Set,s=[];for(const c of t){const o=A(c);o.length===0||r.has(o)||(r.add(o),s.push(c))}return s}function Q(t){if(t.recorded_by_agent==="legacy"&&t.run_id===null)return"";const r=t.run_id?` (run ${d(t.run_id,32)}${t.quarantine_scope==="run"?", run-quarantined":""})`:"";return` recorded by: ${d(t.recorded_by_agent,64)}${r}
2
+ import{openDatabase as P}from"./storage/index.js";import{readFileSync as I,realpathSync as L}from"node:fs";import{homedir as O}from"node:os";import{basename as W,dirname as j,isAbsolute as R,join as G,relative as U,resolve as S,sep as B}from"node:path";import{fileURLToPath as Y}from"node:url";import{FailurePatterns as T,normalizeIdentity as A,STALE_ANCHOR_FLAG as F}from"./failure-patterns.js";import{LEGACY_ENVELOPE as H,runWithActor as V,sanitizeActorId as N}from"./handlers/boundary.js";import{resolveProjectForPath as z}from"./project-resolve.js";const w={exitCode:0,stdout:"",stderr:""},q=new Set(["Edit","Write","MultiEdit","NotebookEdit"]),J=3;function h(t){return typeof t=="string"&&t.length>0?t:null}function d(t,r){return t?t.replace(/[\x00-\x1f\x7f]/g," ").replace(/\s+/g," ").trim().slice(0,r):""}function K(t){const r=t.hook_event_name;if(r!==void 0&&r!=="PreToolUse")return null;const s=t.tool_name,c=t.tool_input;if(typeof s!="string"||typeof c!="object"||c===null)return null;const o=c,e=h(t.cwd);if(s==="Bash"){const a=h(o.command)?.trim();if(!a)return null;const l=a.slice(0,1e3);return{scopes:["command"],probeTarget:l,identity:[a],display:d(l,80),startDir:e,absPath:null}}if(q.has(s)){const a=h(o.file_path)??h(o.notebook_path);if(!a)return null;const l=o.edits,y=h(o.new_string)??h(o.content)??h(o.new_source)??(Array.isArray(l)?l.map(_=>h(_?.new_string)??"").join(" "):""),f=R(a)?S(a):e&&R(e)?S(e,a):null;return{scopes:["file","edit"],probeTarget:W(a).slice(0,200),identity:C([a,...f?[f]:[]]),display:d(a,80),startDir:f?j(f):e,absPath:f,content:y}}return null}function C(t){const r=new Set,s=[];for(const c of t){const o=A(c);o.length===0||r.has(o)||(r.add(o),s.push(c))}return s}function Q(t){if(t.recorded_by_agent==="legacy"&&t.run_id===null)return"";const r=t.run_id?` (run ${d(t.run_id,32)}${t.quarantine_scope==="run"?", run-quarantined":""})`:"";return` recorded by: ${d(t.recorded_by_agent,64)}${r}
3
3
  `}function X(t,r,s){const c=t.description?` description: ${d(t.description,200)}
4
4
  `:"",o=t.why_failed?` failed because: ${d(t.why_failed,300)}
5
5
  `:"";return`wyrm-guard: BLOCKED \u2014 ${r==="exact"?"this exact action is a recorded unresolved failure":r==="content"?"this edit re-introduces the code of a recorded unresolved failure":"this exact target previously failed and the failure is unresolved"} (deterministic ${r} match).
@@ -12,5 +12,5 @@ and then retry.
12
12
  `:"")}function Z(t,r){const s=t.slice(0,J),c=s.map(e=>{const a=e.why_failed??e.description;return`- #${e.id} [${e.severity}/${e.scope}] ${d(e.target,80)} (seen ${e.occurrences}x, confidence ${e.confidence}): ${d(a,160)}`+(e.stale_anchor?`
13
13
  stale_anchor: ${e.stale_anchor}`:"")}),o=t.length-s.length;return["<wyrm-memory>",`wyrm-guard: ${t.length} recorded unresolved failure(s) look similar to \`${r}\` \u2014 check before proceeding:`,...c,...o>0?[`(+${o} more \u2014 wyrm_failure_list)`]:[],"If one applies, do not repeat it \u2014 adjust the approach or resolve it first (wyrm_failure_resolve).","</wyrm-memory>"].join(`
14
14
  `)}function tt(t,r){const s={hookSpecificOutput:{hookEventName:"PreToolUse",additionalContext:Z(t,r)}};return{exitCode:0,stdout:JSON.stringify(s)+`
15
- `,stderr:""}}function et(t,r=process.env){let s=null;try{const c=(r.WYRM_GUARD_MODE??"block").toLowerCase();if(c==="off"||!t||!t.trim())return w;const o=JSON.parse(t);if(typeof o!="object"||o===null)return w;const e=K(o);if(!e)return w;const a=r.WYRM_DB_PATH??G(O(),".wyrm","wyrm.db");s=new P(a,{readonly:!0,fileMustExist:!0}),s.pragma("busy_timeout = 100");const{id:l,root:y}=z(s,e.startDir),f=[...e.identity];y&&e.absPath&&e.absPath.startsWith(y+B)&&f.push(U(y,e.absPath));const _=N(r.WYRM_RUN_ID)??null,m=new T(s),g=new Set,p=[],v=C(f);for(const n of e.scopes)try{const i=m.checkVerdict(n,e.probeTarget,"",l,{callerRunId:_});for(const u of i.matches)g.has(u.id)||(g.add(u.id),p.push(u))}catch{}for(const n of e.scopes)for(const i of v)try{for(const u of m.targetIdentityMatches(n,i,l,{callerRunId:_}))g.has(u.id)||(g.add(u.id),p.push(m.toVerdictMatch(u,"target")))}catch{}let b=null;if(e.content&&e.scopes.includes("edit")&&/^(1|true|yes|on)$/i.test(r.WYRM_GUARD_CONTENT??""))try{const n=m.contentMatch(e.content,"edit",l);n&&!g.has(n.id)&&(g.add(n.id),b=m.toVerdictMatch(n,"target"))}catch{}if(p.length===0&&!b)return w;const D=new Set(v.map(n=>A(n))),M={exact:0,target:1,content:2},$=[...p.map(n=>({m:n,reason:n.match==="exact"?"exact":n.match==="target"||D.has(A(n.target))?"target":null})).filter(n=>n.reason!==null),...b?[{m:b,reason:"content"}]:[]].sort((n,i)=>M[n.reason]-M[i.reason]);let x=$,k=p;if($.length>0)try{const n=m.staleAnchorIds($.filter(i=>i.reason!=="content").map(i=>i.m),y);n.size>0&&(x=$.filter(i=>!n.has(i.m.id)),k=p.map(i=>n.has(i.id)?{...i,stale_anchor:F}:i))}catch{}if(x.length>0&&c!=="warn"){const n=x[0];try{const i=new P(a,{fileMustExist:!0});try{i.pragma("busy_timeout = 50");const u=N(r.WYRM_AGENT_ID);V(u!==null||_!==null?{agent_id:u,run_id:_,source:"env"}:H,()=>new T(i).recordBlock({blocked:!0,matches:[n.m],recorded_by_agent:n.m.recorded_by_agent,run_id:n.m.run_id,confidence:n.m.confidence},l))}finally{i.close()}}catch{}return{exitCode:2,stdout:"",stderr:X(n.m,n.reason,p.length-1)}}return tt(k,e.display)}catch(c){if(r.WYRM_GUARD_DEBUG==="1"){const o=c instanceof Error?c.message:String(c);return{exitCode:0,stdout:"",stderr:`wyrm-guard: internal error (fail-open): ${d(o,200)}
15
+ `,stderr:""}}function et(t,r=process.env){let s=null;try{const c=(r.WYRM_GUARD_MODE??"block").toLowerCase();if(c==="off"||!t||!t.trim())return w;const o=JSON.parse(t);if(typeof o!="object"||o===null)return w;const e=K(o);if(!e)return w;const a=r.WYRM_DB_PATH??G(O(),".wyrm","wyrm.db");s=P(a,{readonly:!0,fileMustExist:!0}),s.pragma("busy_timeout = 100");const{id:l,root:y}=z(s,e.startDir),f=[...e.identity];y&&e.absPath&&e.absPath.startsWith(y+B)&&f.push(U(y,e.absPath));const _=N(r.WYRM_RUN_ID)??null,m=new T(s),g=new Set,p=[],v=C(f);for(const n of e.scopes)try{const i=m.checkVerdict(n,e.probeTarget,"",l,{callerRunId:_});for(const u of i.matches)g.has(u.id)||(g.add(u.id),p.push(u))}catch{}for(const n of e.scopes)for(const i of v)try{for(const u of m.targetIdentityMatches(n,i,l,{callerRunId:_}))g.has(u.id)||(g.add(u.id),p.push(m.toVerdictMatch(u,"target")))}catch{}let b=null;if(e.content&&e.scopes.includes("edit")&&/^(1|true|yes|on)$/i.test(r.WYRM_GUARD_CONTENT??""))try{const n=m.contentMatch(e.content,"edit",l);n&&!g.has(n.id)&&(g.add(n.id),b=m.toVerdictMatch(n,"target"))}catch{}if(p.length===0&&!b)return w;const D=new Set(v.map(n=>A(n))),M={exact:0,target:1,content:2},$=[...p.map(n=>({m:n,reason:n.match==="exact"?"exact":n.match==="target"||D.has(A(n.target))?"target":null})).filter(n=>n.reason!==null),...b?[{m:b,reason:"content"}]:[]].sort((n,i)=>M[n.reason]-M[i.reason]);let x=$,k=p;if($.length>0)try{const n=m.staleAnchorIds($.filter(i=>i.reason!=="content").map(i=>i.m),y);n.size>0&&(x=$.filter(i=>!n.has(i.m.id)),k=p.map(i=>n.has(i.id)?{...i,stale_anchor:F}:i))}catch{}if(x.length>0&&c!=="warn"){const n=x[0];try{const i=P(a,{fileMustExist:!0});try{i.pragma("busy_timeout = 50");const u=N(r.WYRM_AGENT_ID);V(u!==null||_!==null?{agent_id:u,run_id:_,source:"env"}:H,()=>new T(i).recordBlock({blocked:!0,matches:[n.m],recorded_by_agent:n.m.recorded_by_agent,run_id:n.m.run_id,confidence:n.m.confidence},l))}finally{i.close()}}catch{}return{exitCode:2,stdout:"",stderr:X(n.m,n.reason,p.length-1)}}return tt(k,e.display)}catch(c){if(r.WYRM_GUARD_DEBUG==="1"){const o=c instanceof Error?c.message:String(c);return{exitCode:0,stdout:"",stderr:`wyrm-guard: internal error (fail-open): ${d(o,200)}
16
16
  `}}return w}finally{try{s?.close()}catch{}}}const rt=Y(import.meta.url);let E=!1;try{E=typeof process.argv[1]=="string"&&L(process.argv[1])===rt}catch{E=!1}if(E){let t="";try{t=I(0,"utf-8")}catch{t=""}const r=et(t);r.stdout&&process.stdout.write(r.stdout),r.stderr&&process.stderr.write(r.stderr),process.exit(r.exitCode)}export{et as runGuard};
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "contract": "wyrm-cloud-memory-v1",
3
- "version": "7.5.2",
3
+ "version": "7.6.0",
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": "7.5.2",
3
+ "version": "7.6.0",
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, live memory streams, run-attributed fleet memory — a structured SQLite memory on your machine, no cloud or LLM required. Claude / Copilot / Cursor / Windsurf / Codex.",
6
6
  "type": "module",
@@ -50,7 +50,9 @@
50
50
  "test:watch": "npm run test -- --watch",
51
51
  "lint": "eslint src --ext .ts",
52
52
  "clean": "rm -rf dist",
53
- "prepublishOnly": "npm run build"
53
+ "prepublishOnly": "npm run build",
54
+ "eval": "node bench/retrieval-eval.mjs",
55
+ "eval:gen": "node scripts/gen-eval-corpus.mjs"
54
56
  },
55
57
  "keywords": [
56
58
  "mcp",
@@ -85,7 +87,7 @@
85
87
  "agent-fleet"
86
88
  ],
87
89
  "author": "Ghost Protocol (Pvt) Ltd <ryan@ghosts.lk>",
88
- "license": "UNLICENSED",
90
+ "license": "SEE LICENSE IN LICENSE",
89
91
  "homepage": "https://ghosts.lk/wyrm",
90
92
  "repository": {
91
93
  "type": "git",
@@ -112,7 +114,8 @@
112
114
  "typescript": "^5.9.3"
113
115
  },
114
116
  "overrides": {
115
- "hono": "^4.12.29"
117
+ "hono": "^4.12.29",
118
+ "test-exclude": "^7.0.1"
116
119
  },
117
120
  "engines": {
118
121
  "node": ">=22"