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 +3 -2
- package/NOTICE +2 -2
- package/dist/activation.js +1 -1
- package/dist/autoconfig.js +20 -19
- package/dist/database.js +1 -1
- package/dist/handlers/share.js +4 -4
- package/dist/license.js +2 -2
- package/dist/mcp-client.js +6 -6
- package/dist/mcp-integrity.js +2 -0
- package/dist/setup.js +3 -1
- package/dist/storage/index.js +2 -0
- package/dist/storage/node-sqlite-facade.js +1 -0
- package/dist/sub-agent.js +14 -13
- package/dist/tool-manifest-v2.json +1 -1
- package/dist/tool-manifest.json +1 -1
- package/dist/wyrm-guard.js +2 -2
- package/dist/wyrm-manifest.json +1 -1
- package/package.json +7 -4
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
|
|
22
|
-
|
|
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
|
|
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
|
package/dist/activation.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{getLicenseInfo as
|
|
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};
|
package/dist/autoconfig.js
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
import{existsSync as d,readFileSync as p,writeFileSync as l,mkdirSync as
|
|
2
|
-
`)[0];break}case"cursor":{const t=
|
|
3
|
-
`)[0];break}
|
|
4
|
-
|
|
5
|
-
`,"utf-8")
|
|
6
|
-
`,"utf-8"),
|
|
7
|
-
`,"utf-8"),{client:t,action:
|
|
8
|
-
`,"utf-8"),{client:t,action:"configured",message:"
|
|
9
|
-
`,"utf-8"),{client:t,action:"configured",message:"
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
`,"utf-8"),{client:
|
|
13
|
-
`,"utf-8")}
|
|
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
|
-
${
|
|
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}${
|
|
42
|
-
`,"utf-8")}}else l(r,`${
|
|
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
|
|
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=
|
|
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{
|
|
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
|
|
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
|
package/dist/handlers/share.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import{TOOL_ANNOTATIONS as
|
|
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:
|
|
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:
|
|
8
|
-
`)}]}}}];export{
|
|
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
|
|
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,
|
|
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};
|
package/dist/mcp-client.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{spawn as
|
|
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)
|
|
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
|
|
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??
|
|
19
|
-
`))>=0;){const
|
|
20
|
-
`)}rpc(e,t,
|
|
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
|
|
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
|
|
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(
|
|
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
|
-
${
|
|
12
|
+
${n!=null?"AND s.project_id = ?":""}
|
|
13
13
|
ORDER BY s.date DESC
|
|
14
14
|
LIMIT 8
|
|
15
|
-
`).all(...
|
|
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
|
-
${
|
|
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(...
|
|
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(
|
|
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
|
-
${
|
|
33
|
+
${n!=null?"AND project_id = ?":""}
|
|
34
34
|
LIMIT 10
|
|
35
|
-
`).all(...
|
|
36
|
-
`)}async
|
|
37
|
-
`)
|
|
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,
|
|
43
|
-
`)}}export{
|
|
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};
|
package/dist/tool-manifest.json
CHANGED
package/dist/wyrm-guard.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import P
|
|
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=
|
|
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};
|
package/dist/wyrm-manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wyrm-mcp",
|
|
3
|
-
"version": "7.
|
|
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": "
|
|
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"
|