wyrm-mcp 8.4.0 → 8.5.1

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.
@@ -0,0 +1 @@
1
+ import{readFileSync as d}from"node:fs";import{homedir as p}from"node:os";import{join as R}from"node:path";const n={product:"Wyrm",fleetLabel:"Fleet",fleetTitle:"Fleet Channel",fleetGrove:"fleet-warroom",fleetBlurb:"The multi-agent coordination channel. Findings, claims, fix-receipts and verdicts flow as structured work between agents."};function L(){try{const t=process.env.WYRM_BRAND_FILE||R(p(),".wyrm","brand.json");return JSON.parse(d(t,"utf8"))}catch{return{}}}function F(t=!1){const e=L(),l=i=>{const r=process.env[i];return r&&r.trim()?r.trim():void 0},o=(i,r,_)=>l(i)??(r&&r.trim()?r.trim():void 0)??_,a=!!(e.fleetLabel||e.fleetTitle||e.fleet==="on"||e.profile),c=(l("WYRM_UI_FLEET")||e.fleet||(a?"on":"auto")).toLowerCase(),u=c==="on"?!0:c==="off"?!1:t,f=l("WYRM_UI_BRAND_MARK_URL")??e.markUrl,s=l("WYRM_UI_BRAND_MARK")??e.mark,m=f?`<img src="${E(f)}" alt="" class="logo-mark" />`:s?B(s):"";return{product:o("WYRM_UI_BRAND_NAME",e.product,n.product),markHtml:m,showFleet:u,fleetLabel:o("WYRM_UI_FLEET_LABEL",e.fleetLabel,n.fleetLabel),fleetTitle:o("WYRM_UI_FLEET_TITLE",e.fleetTitle,n.fleetTitle),fleetGrove:o("WYRM_UI_FLEET_GROVE",e.fleetGrove,n.fleetGrove),fleetBlurb:l("WYRM_UI_FLEET_BLURB")??(e.fleetBlurb&&e.fleetBlurb.trim()?e.fleetBlurb.trim():void 0)??n.fleetBlurb}}function E(t){return String(t).replace(/["'<>]/g,"").slice(0,300)}function B(t){return String(t).replace(/[<>&"']/g,"").slice(0,24)}export{F as resolveBranding};
package/dist/http-auth.js CHANGED
@@ -1,3 +1,3 @@
1
1
  import{existsSync as w,readFileSync as I,writeFileSync as u,mkdirSync as R}from"fs";import{join as y}from"path";import{homedir as x}from"os";import{randomBytes as O,createHash as T}from"crypto";import{WyrmLogger as k}from"./logger.js";const g=y(x(),".wyrm"),a=y(g,"http-config.json"),L={enabled:!0,requests:100,windowMs:6e4},S=["http://localhost:3333","http://127.0.0.1:3333"],d=new Map;let c=null;const i=new k;function m(e){return T("sha256").update(e).digest("hex")}function P(e,t){if(e.length!==t.length)return!1;let r=0;for(let n=0;n<e.length;n++)r|=e.charCodeAt(n)^t.charCodeAt(n);return r===0}function M(){if(w(g)||R(g,{recursive:!0,mode:448}),w(a))try{const r=JSON.parse(I(a,"utf-8"));return{apiKeyHash:r.apiKeyHash,allowedOrigins:r.allowedOrigins||S,rateLimit:{...L,...r.rateLimit},requireAuth:r.requireAuth??!0,devMode:process.env.WYRM_DEV==="true"}}catch(r){i.error("Failed to load HTTP config, regenerating",{error:r.message})}const e=O(32).toString("hex");i.info("Generated new API key for HTTP server"),console.log(`
2
2
  `+"\u2550".repeat(60)),console.log("\u{1F510} WYRM API KEY (save this securely, shown once):"),console.log(""),console.log(` ${e}`),console.log(""),console.log(" Use with: Authorization: Bearer <key>"),console.log("\u2550".repeat(60)+`
3
- `);const t={apiKeyHash:m(e),allowedOrigins:S,rateLimit:L,requireAuth:!0,devMode:process.env.WYRM_DEV==="true"};return u(a,JSON.stringify(t,null,2),{mode:384}),t}function s(){return c||(c=M()),c}function N(e){const t=s();if(t.devMode){const h=e.socket.remoteAddress;if(h==="127.0.0.1"||h==="::1"||h==="::ffff:127.0.0.1")return!0}const r=e.socket.remoteAddress??"",n=r==="127.0.0.1"||r==="::1"||r==="::ffff:127.0.0.1",o=(e.headers.host??"").split(":")[0].toLowerCase();if(n&&(o==="localhost"||o==="127.0.0.1"||o==="::1"||o==="[::1]")&&e.headers["x-wyrm-origin"]==="ui")return!0;if(!t.requireAuth)return i.warn("Authentication disabled - API is open"),!0;const l=e.headers.authorization;if(!l||!l.startsWith("Bearer "))return!1;const f=l.slice(7);if(!f||f.length<32)return!1;const C=m(f);return P(C,t.apiKeyHash)}function H(e){const t=s();if(!t.rateLimit.enabled)return{allowed:!0,remaining:1/0,resetAt:0};const r=e.socket.remoteAddress||"unknown",n=Date.now();if(d.size>1e4)for(const[A,l]of d)l.resetAt<n&&d.delete(A);let o=d.get(r);return(!o||o.resetAt<n)&&(o={count:0,resetAt:n+t.rateLimit.windowMs},d.set(r,o)),o.count++,{allowed:o.count<=t.rateLimit.requests,remaining:Math.max(0,t.rateLimit.requests-o.count),resetAt:o.resetAt}}function v(e){const t=s(),r=e.headers.origin;return r&&t.allowedOrigins.includes(r)?r:t.allowedOrigins[0]}function p(e){return{"Access-Control-Allow-Origin":v(e),"Access-Control-Allow-Methods":"GET, POST, OPTIONS","Access-Control-Allow-Headers":"Content-Type, Authorization","X-Content-Type-Options":"nosniff","X-Frame-Options":"DENY","Cache-Control":"no-store","Content-Security-Policy":"default-src 'none'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; connect-src 'self'; img-src 'self' data:; frame-ancestors 'none'","Referrer-Policy":"no-referrer","Permissions-Policy":"geolocation=(), microphone=(), camera=()","X-Robots-Tag":"noindex, nofollow"}}const D=["/health","/auth/status","/ui"];function J(e,t){if(e.method==="OPTIONS"){const o=p(e);return t.writeHead(204,o),t.end(),{error:!0}}const r=H(e);if(!r.allowed){const o=p(e);return t.writeHead(429,{...o,"X-RateLimit-Remaining":"0","X-RateLimit-Reset":String(r.resetAt),"Retry-After":String(Math.ceil((r.resetAt-Date.now())/1e3))}),t.end(JSON.stringify({error:"Rate limit exceeded"})),i.warn("Rate limit exceeded",{ip:e.socket.remoteAddress,path:e.url}),{error:!0}}const n=new URL(e.url||"/","http://localhost").pathname;if(D.includes(n))return{error:!1};if(!N(e)){const o=p(e);return t.writeHead(401,{...o,"WWW-Authenticate":'Bearer realm="Wyrm API"'}),t.end(JSON.stringify({error:"Unauthorized"})),i.warn("Authentication failed",{ip:e.socket.remoteAddress,path:e.url}),{error:!0}}return{error:!1}}function _(){const e=O(32).toString("hex"),t=s();return t.apiKeyHash=m(e),u(a,JSON.stringify(t,null,2),{mode:384}),c=t,i.info("API key regenerated"),e}function U(e){const t=s();t.allowedOrigins=e,u(a,JSON.stringify(t,null,2),{mode:384}),c=t,i.info("CORS origins updated",{origins:e})}function z(e,t){const r=s();r.rateLimit={enabled:!0,requests:e,windowMs:t},u(a,JSON.stringify(r,null,2),{mode:384}),c=r,i.info("Rate limit updated",{requests:e,windowMs:t})}function B(e){const t=s();t.requireAuth=e,u(a,JSON.stringify(t,null,2),{mode:384}),c=t,e||i.warn("Authentication disabled - API is now open")}function G(){const e=s();e.devMode=!0}function X(){const e=s();return{requireAuth:e.requireAuth,devMode:e.devMode,rateLimit:e.rateLimit,allowedOrigins:e.allowedOrigins}}export{J as authMiddleware,N as authenticate,H as checkRateLimit,G as enableDevMode,X as getAuthStatus,v as getCorsOrigin,p as getSecurityHeaders,_ as regenerateApiKey,U as setAllowedOrigins,z as setRateLimit,B as setRequireAuth};
3
+ `);const t={apiKeyHash:m(e),allowedOrigins:S,rateLimit:L,requireAuth:!0,devMode:process.env.WYRM_DEV==="true"};return u(a,JSON.stringify(t,null,2),{mode:384}),t}function s(){return c||(c=M()),c}function N(e){const t=s();if(t.devMode){const h=e.socket.remoteAddress;if(h==="127.0.0.1"||h==="::1"||h==="::ffff:127.0.0.1")return!0}const r=e.socket.remoteAddress??"",n=r==="127.0.0.1"||r==="::1"||r==="::ffff:127.0.0.1",o=(e.headers.host??"").split(":")[0].toLowerCase();if(n&&(o==="localhost"||o==="127.0.0.1"||o==="::1"||o==="[::1]")&&e.headers["x-wyrm-origin"]==="ui")return!0;if(!t.requireAuth)return i.warn("Authentication disabled - API is open"),!0;const l=e.headers.authorization;if(!l||!l.startsWith("Bearer "))return!1;const f=l.slice(7);if(!f||f.length<32)return!1;const C=m(f);return P(C,t.apiKeyHash)}function H(e){const t=s();if(!t.rateLimit.enabled)return{allowed:!0,remaining:1/0,resetAt:0};const r=e.socket.remoteAddress||"unknown",n=Date.now();if(d.size>1e4)for(const[A,l]of d)l.resetAt<n&&d.delete(A);let o=d.get(r);return(!o||o.resetAt<n)&&(o={count:0,resetAt:n+t.rateLimit.windowMs},d.set(r,o)),o.count++,{allowed:o.count<=t.rateLimit.requests,remaining:Math.max(0,t.rateLimit.requests-o.count),resetAt:o.resetAt}}function v(e){const t=s(),r=e.headers.origin;return r&&t.allowedOrigins.includes(r)?r:t.allowedOrigins[0]}function p(e){return{"Access-Control-Allow-Origin":v(e),"Access-Control-Allow-Methods":"GET, POST, OPTIONS","Access-Control-Allow-Headers":"Content-Type, Authorization","X-Content-Type-Options":"nosniff","X-Frame-Options":"DENY","Cache-Control":"no-store","Content-Security-Policy":"default-src 'none'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src https://fonts.gstatic.com; connect-src 'self'; img-src 'self' data:; frame-ancestors 'none'","Referrer-Policy":"no-referrer","Permissions-Policy":"geolocation=(), microphone=(), camera=()","X-Robots-Tag":"noindex, nofollow"}}const D=["/health","/auth/status","/ui"];function J(e,t){if(e.method==="OPTIONS"){const o=p(e);return t.writeHead(204,o),t.end(),{error:!0}}const r=H(e);if(!r.allowed){const o=p(e);return t.writeHead(429,{...o,"X-RateLimit-Remaining":"0","X-RateLimit-Reset":String(r.resetAt),"Retry-After":String(Math.ceil((r.resetAt-Date.now())/1e3))}),t.end(JSON.stringify({error:"Rate limit exceeded"})),i.warn("Rate limit exceeded",{ip:e.socket.remoteAddress,path:e.url}),{error:!0}}const n=new URL(e.url||"/","http://localhost").pathname;if(D.includes(n))return{error:!1};if(!N(e)){const o=p(e);return t.writeHead(401,{...o,"WWW-Authenticate":'Bearer realm="Wyrm API"'}),t.end(JSON.stringify({error:"Unauthorized"})),i.warn("Authentication failed",{ip:e.socket.remoteAddress,path:e.url}),{error:!0}}return{error:!1}}function _(){const e=O(32).toString("hex"),t=s();return t.apiKeyHash=m(e),u(a,JSON.stringify(t,null,2),{mode:384}),c=t,i.info("API key regenerated"),e}function U(e){const t=s();t.allowedOrigins=e,u(a,JSON.stringify(t,null,2),{mode:384}),c=t,i.info("CORS origins updated",{origins:e})}function z(e,t){const r=s();r.rateLimit={enabled:!0,requests:e,windowMs:t},u(a,JSON.stringify(r,null,2),{mode:384}),c=r,i.info("Rate limit updated",{requests:e,windowMs:t})}function B(e){const t=s();t.requireAuth=e,u(a,JSON.stringify(t,null,2),{mode:384}),c=t,e||i.warn("Authentication disabled - API is now open")}function G(){const e=s();e.devMode=!0}function X(){const e=s();return{requireAuth:e.requireAuth,devMode:e.devMode,rateLimit:e.rateLimit,allowedOrigins:e.allowedOrigins}}export{J as authMiddleware,N as authenticate,H as checkRateLimit,G as enableDevMode,X as getAuthStatus,v as getCorsOrigin,p as getSecurityHeaders,_ as regenerateApiKey,U as setAllowedOrigins,z as setRateLimit,B as setRequireAuth};
package/dist/http-fast.js CHANGED
@@ -1,31 +1,31 @@
1
1
  #!/usr/bin/env node
2
- import V from"http";import{WyrmDB as H}from"./database.js";import{cache as h,estimateTokens as z,truncateToTokens as Z,timed as G}from"./performance.js";import{authMiddleware as ee,getAuthStatus as N,getSecurityHeaders as L}from"./http-auth.js";import{WyrmLogger as te}from"./logger.js";import{sanitizeFtsQuery as W,validateProjectPath as re}from"./security.js";import{readFileSync as R,existsSync as S,readdirSync as ne,realpathSync as x}from"fs";import{homedir as se}from"os";import{dirname as U,join as _}from"path";import{fileURLToPath as oe}from"url";const q=_(U(oe(import.meta.url)),"..","ui","dragon-mark.svg");let v=null;import{getUIDashboardHTML as ae}from"./ui-dashboard.js";import{verifyLicense as B}from"./license.js";import{rerankConfigured as ce,rerankEgressHost as ie}from"./rerank.js";import{MemoryArtifacts as le}from"./memory-artifacts.js";import{createVectorStore as ue}from"./vectors.js";import{GroundTruths as de,computeStaleness as me}from"./intelligence.js";import{Causality as pe}from"./causality.js";import{wireTruthAutoCascade as fe}from"./truth-cascade.js";import{FailurePatterns as Ee}from"./failure-patterns.js";import{handleDaemonWrite as he}from"./daemon-write-endpoint.js";import{retryableWriteCause as Y,busyErrorBody as $}from"./sqlite-busy.js";import{sseFrame as ye,resolveStartCursor as J,SSE_KEEPALIVE as _e}from"./events-sse.js";import{readonlyBlocks as Se}from"./readonly-gate.js";import{resolveActorEnvelope as Te,runWithActor as ge}from"./handlers/boundary.js";const j=parseInt(process.env.WYRM_PORT||"3333")||3333,T=process.env.WYRM_UI_READONLY==="1",Oe=512*1024,M=500,Re=parseInt(process.env.WYRM_MAX_SSE_STREAMS||"64",10)||64,ve=parseInt(process.env.WYRM_MAX_SSE_PER_IP||"8",10)||8;let P=0;const g=new Map;let O=null,b=null,C=null,k=null;function o(){return O||(O=new H(process.env.WYRM_DB_PATH)),O}function I(){if(!b){b=new le(o().getDatabase());try{const e=process.env.WYRM_VECTOR_PROVIDER||"auto";e!=="none"&&b.setVectorStore(ue({provider:e},o().getDatabase()))}catch{}}return b}function be(){return C||(C=new de(o().getDatabase()),fe(C,new pe(o().getDatabase()))),C}function Ce(){return k||(k=new Ee(o().getDatabase())),k}function Le(e){const t=O;O=e,b=null,C=null,k=null;try{t?.close()}catch{}}function Q(){const e=[],t=se(),r=new Set;let n=[];try{n=ne(t)}catch{return e}n.sort((s,a)=>a.localeCompare(s));for(const s of n){if(s!==".wyrm"&&!s.startsWith(".wyrm-"))continue;let a;try{a=x(_(t,s))}catch{continue}if(r.has(a))continue;const i=_(a,"wyrm.db");if(!S(i))continue;r.add(a);let c="Local",l="free";try{const p=_(a,"license.json");if(S(p)){const d=JSON.parse(R(p,"utf-8")),m=B(d);c=d.license&&d.license.issued_to||c,l=m.valid?m.tier:"free"}else{const d=_(a,"cloud.json");S(d)&&(c=JSON.parse(R(d,"utf-8")).email||c)}}catch{}const u=s.replace(/^\.wyrm-?/,"")||"default";e.push({name:u,dbPath:i,account:c,tier:l})}return e}const D=new te({level:"info"});function y(e,t,r){const n=Number.parseInt(String(e??""),10);return!Number.isFinite(n)||n<1?t:Math.min(n,r)}function A(e){const t=e==null?"":String(e);if(t)return o().getProject(t)||o().getProjectByName(t)}function E(e,t,r,n=200){const s=JSON.stringify(r),a=L(t);e.writeHead(n,{"Content-Type":"application/json","Content-Length":Buffer.byteLength(s),"X-Tokens":String(z(r)),...a}),e.end(s)}function je(e,t,r){const n=L(t);e.writeHead(200,{...n,"Content-Type":"text/html; charset=utf-8","Content-Length":Buffer.byteLength(r),"Cache-Control":"no-store"}),e.end(r)}function Me(e){return new Promise((t,r)=>{if(e.method==="GET"){t({});return}let n="",s=0;e.on("data",a=>{if(s+=a.length,s>Oe){e.destroy(),r(new Error("Request body too large"));return}n+=a}),e.on("end",()=>{try{t(n?JSON.parse(n):{})}catch{r(new Error("Invalid JSON"))}}),e.on("error",r)})}const X={"GET /c":e=>{const t=`ctx:${e.p||"global"}`,r=h.get(t);if(r)return r;const n=e.p;let s;if(n){const a=o().getProject(n);if(!a)return{e:"not found"};const i=o().getPendingQuests(a.id),c=o().getRecentSessions(a.id,3);s={n:a.name,s:a.stack,q:i.length,qt:i.slice(0,5).map(l=>l.title),r:c[0]?.summary||c[0]?.notes?.slice(0,200)||null}}else{const a=o().getAllProjects(20),i=o().getAllPendingQuests();s={p:a.map(c=>({n:c.name,s:c.stack,q:o().getPendingQuests(c.id).length})),tq:i.length}}return h.set(t,s),s},"GET /p":()=>{const e=h.get("projects");if(e)return e;const r=o().getAllProjects(50).map(n=>({i:n.id,n:n.name,p:n.path,s:n.stack}));return h.set("projects",r),r},"POST /scan":e=>{const t=e.d||e.directory||process.env.HOME+"/Git Projects",r=re(t);return r?(h.invalidate(),{found:o().scanForProjects(r,!0).length}):{e:"path not allowed"}},"GET /q":e=>{const t=e.p;if(t){const r=o().getProject(t);return r?o().getPendingQuests(r.id).map(n=>({i:n.id,t:n.title,p:n.priority[0]})):{e:"not found"}}return o().getAllPendingQuests().slice(0,20).map(r=>({i:r.id,t:r.title,p:r.priority[0]}))},"POST /q":e=>{const t=e.p,r=o().getProject(t);return r?(h.invalidate("ctx"),{i:o().addQuest(r.id,e.t,e.d,e.pr||"medium").id}):{e:"not found"}},"POST /qc":e=>{const t=typeof e.i=="number"?e.i:parseInt(String(e.i),10);return!t||t<1?{e:"id required"}:(h.invalidate("ctx"),o().updateQuest(t,"completed"),{ok:1})},"POST /s":e=>{const t=e.p;let r=o().getProject(t);if(r||(o().scanForProjects(t,!1),r=o().getProject(t)),!r)return{e:"not found"};let n=o().getTodaySession(r.id);return n||(n=o().createSession(r.id,{objectives:e.o||"",notes:""})),h.invalidate("ctx"),{i:n.id,d:n.date}},"POST /su":e=>{const t=e.i;return h.invalidate("ctx"),o().updateSession(t,{notes:e.n,summary:e.s,completed:e.c}),{ok:1}},"POST /x":e=>{const t=e.p,r=o().getProject(t);return r?(o().setContext(r.id,e.k,e.v),h.invalidate("ctx"),{ok:1}):{e:"not found"}},"GET /x":e=>{const t=e.p,r=o().getProject(t);return r?o().getAllContext(r.id):{e:"not found"}},"POST /g":e=>{const t=e.k,r=e.v;return!t||typeof t!="string"?{e:"k (key) required"}:r==null?{e:"v (value) required"}:(o().setGlobalContext(t,String(r)),h.invalidate(),{ok:1})},"GET /g":()=>o().getAllGlobalContext(),"GET /s":e=>{const t=e.q;if(!t)return{e:"query required"};const r=W(t);return{q:o().searchQuests(r).slice(0,5).map(s=>({i:s.id,t:s.title})),s:o().searchSessions(r).slice(0,3).map(s=>({i:s.id,d:s.date})),p:o().searchProjects(r).slice(0,3).map(s=>({n:s.name,p:s.path}))}},"POST /d":e=>{const t=e.p,r=o().getProject(t);return r?{i:o().insertData(r.id,e.c,e.k,e.v,e.m).id}:{e:"not found"}},"GET /d":e=>{const t=e.p,r=o().getProject(t);if(!r)return{e:"not found"};const n=y(e.l,20,100);return o().queryData(r.id,e.c,n).map(s=>({k:s.key,v:Z(s.value,100)}))},"GET /d/search":e=>{const t=e.p,r=o().getProject(t);if(!r)return{e:"not found"};const n=e.q;if(!n)return{e:"q (query) required"};const s=e.c,a=y(e.l,10,100);let i=o().searchData(n,r.id);return s&&(i=i.filter(c=>c.category===s)),i.slice(0,a).map(c=>({k:c.key,v:c.value,c:c.category,m:c.metadata}))},"GET /d/agg":e=>{const t=e.p,r=o().getProject(t);if(!r)return{e:"not found"};const n=e.c;if(!n)return{e:"c (category) required"};const s=e.stack_key,a=e.scanner,i=y(e.l,1e3,1e4),c={};for(const l of o().queryData(r.id,n,i)){let u;try{u=JSON.parse(l.value)}catch{continue}if(s&&u.stack_key!==s||a&&u.scanner!==a)continue;const p=String(u.scanner??""),d=String(u.payload_hash??"");if(!p||!d)continue;const m=`${p}:${d}`,f=c[m]??{s:p,ph:d,sk:String(u.stack_key??""),h:0,t:0};f.t+=1,u.hit&&(f.h+=1),c[m]=f}return Object.values(c).map(l=>({...l,r:l.t>0?l.h/l.t:0})).sort((l,u)=>u.r-l.r||u.h-l.h)},"POST /batch":e=>{const t=e.ops;if(!t||!Array.isArray(t))return{e:"ops required"};if(t.length>M)return{e:`max ${M} ops per batch`};const r=performance.now();return{r:t.map(s=>{const a=`${s.m} ${s.r}`,i=X[a];if(!i)return{e:"unknown"};try{return{d:i(s.a||{})}}catch{return{e:"op failed"}}}),ms:Math.round(performance.now()-r)}},"POST /write":e=>he({db:o(),artifacts:I(),truths:be(),failures:Ce()},e),"GET /syms":e=>{const t=String(e.q??"").slice(0,200);if(!t)return[];const r=y(e.limit,20,200),n=o().getDatabase();try{return n.prepare(`
2
+ import z from"http";import{WyrmDB as W}from"./database.js";import{cache as h,estimateTokens as Z,truncateToTokens as ee,timed as G}from"./performance.js";import{authMiddleware as te,getAuthStatus as k,getSecurityHeaders as b}from"./http-auth.js";import{WyrmLogger as re}from"./logger.js";import{sanitizeFtsQuery as U,validateProjectPath as ne}from"./security.js";import{readFileSync as R,existsSync as y,readdirSync as se,realpathSync as x}from"fs";import{homedir as oe}from"os";import{dirname as q,join as S}from"path";import{fileURLToPath as ae}from"url";const B=S(q(ae(import.meta.url)),"..","ui","dragon-mark.svg");let v=null;import{getUIDashboardHTML as ie}from"./ui-dashboard.js";import{resolveBranding as I}from"./branding.js";import{verifyLicense as $}from"./license.js";import{rerankConfigured as ce,rerankEgressHost as le}from"./rerank.js";import{MemoryArtifacts as de}from"./memory-artifacts.js";import{createVectorStore as ue}from"./vectors.js";import{GroundTruths as pe,computeStaleness as me}from"./intelligence.js";import{Causality as fe}from"./causality.js";import{wireTruthAutoCascade as Ee}from"./truth-cascade.js";import{FailurePatterns as he}from"./failure-patterns.js";import{handleDaemonWrite as _e}from"./daemon-write-endpoint.js";import{retryableWriteCause as Y,busyErrorBody as J}from"./sqlite-busy.js";import{sseFrame as Se,resolveStartCursor as K,SSE_KEEPALIVE as ye}from"./events-sse.js";import{readonlyBlocks as ge}from"./readonly-gate.js";import{resolveActorEnvelope as Te,runWithActor as Oe}from"./handlers/boundary.js";const j=parseInt(process.env.WYRM_PORT||"3333")||3333,g=process.env.WYRM_UI_READONLY==="1",Re=512*1024,M=500,ve=parseInt(process.env.WYRM_MAX_SSE_STREAMS||"64",10)||64,Ce=parseInt(process.env.WYRM_MAX_SSE_PER_IP||"8",10)||8;let w=0;const T=new Map;let O=null,C=null,L=null,N=null;function a(){return O||(O=new W(process.env.WYRM_DB_PATH)),O}function A(){if(!C){C=new de(a().getDatabase());try{const e=process.env.WYRM_VECTOR_PROVIDER||"auto";e!=="none"&&C.setVectorStore(ue({provider:e},a().getDatabase()))}catch{}}return C}function Le(){return L||(L=new pe(a().getDatabase()),Ee(L,new fe(a().getDatabase()))),L}function be(){return N||(N=new he(a().getDatabase())),N}function je(e){const t=O;O=e,C=null,L=null,N=null;try{t?.close()}catch{}}function Q(){const e=[],t=oe(),r=new Set;let n=[];try{n=se(t)}catch{return e}n.sort((s,o)=>o.localeCompare(s));for(const s of n){if(s!==".wyrm"&&!s.startsWith(".wyrm-"))continue;let o;try{o=x(S(t,s))}catch{continue}if(r.has(o))continue;const d=S(o,"wyrm.db");if(!y(d))continue;r.add(o);let i="Local",l="free";try{const c=S(o,"license.json");if(y(c)){const u=JSON.parse(R(c,"utf-8")),m=$(u);i=u.license&&u.license.issued_to||i,l=m.valid?m.tier:"free"}else{const u=S(o,"cloud.json");y(u)&&(i=JSON.parse(R(u,"utf-8")).email||i)}}catch{}const p=s.replace(/^\.wyrm-?/,"")||"default";e.push({name:p,dbPath:d,account:i,tier:l})}return e}const D=new re({level:"info"});function _(e,t,r){const n=Number.parseInt(String(e??""),10);return!Number.isFinite(n)||n<1?t:Math.min(n,r)}function P(e){const t=e==null?"":String(e);if(t)return a().getProject(t)||a().getProjectByName(t)}function E(e,t,r,n=200){const s=JSON.stringify(r),o=b(t);e.writeHead(n,{"Content-Type":"application/json","Content-Length":Buffer.byteLength(s),"X-Tokens":String(Z(r)),...o}),e.end(s)}function Me(e,t,r){const n=b(t);e.writeHead(200,{...n,"Content-Type":"text/html; charset=utf-8","Content-Length":Buffer.byteLength(r),"Cache-Control":"no-store"}),e.end(r)}function Ne(e){return new Promise((t,r)=>{if(e.method==="GET"){t({});return}let n="",s=0;e.on("data",o=>{if(s+=o.length,s>Re){e.destroy(),r(new Error("Request body too large"));return}n+=o}),e.on("end",()=>{try{t(n?JSON.parse(n):{})}catch{r(new Error("Invalid JSON"))}}),e.on("error",r)})}const X={"GET /c":e=>{const t=`ctx:${e.p||"global"}`,r=h.get(t);if(r)return r;const n=e.p;let s;if(n){const o=a().getProject(n);if(!o)return{e:"not found"};const d=a().getPendingQuests(o.id),i=a().getRecentSessions(o.id,3);s={n:o.name,s:o.stack,q:d.length,qt:d.slice(0,5).map(l=>l.title),r:i[0]?.summary||i[0]?.notes?.slice(0,200)||null}}else{const o=a().getAllProjects(20),d=a().getAllPendingQuests();s={p:o.map(i=>({n:i.name,s:i.stack,q:a().getPendingQuests(i.id).length})),tq:d.length}}return h.set(t,s),s},"GET /p":()=>{const e=h.get("projects");if(e)return e;const r=a().getAllProjects(50).map(n=>({i:n.id,n:n.name,p:n.path,s:n.stack}));return h.set("projects",r),r},"POST /scan":e=>{const t=e.d||e.directory||process.env.HOME+"/Git Projects",r=ne(t);return r?(h.invalidate(),{found:a().scanForProjects(r,!0).length}):{e:"path not allowed"}},"GET /q":e=>{const t=e.p;if(t){const r=a().getProject(t);return r?a().getPendingQuests(r.id).map(n=>({i:n.id,t:n.title,p:n.priority[0]})):{e:"not found"}}return a().getAllPendingQuests().slice(0,20).map(r=>({i:r.id,t:r.title,p:r.priority[0]}))},"POST /q":e=>{const t=e.p,r=a().getProject(t);return r?(h.invalidate("ctx"),{i:a().addQuest(r.id,e.t,e.d,e.pr||"medium").id}):{e:"not found"}},"POST /qc":e=>{const t=typeof e.i=="number"?e.i:parseInt(String(e.i),10);return!t||t<1?{e:"id required"}:(h.invalidate("ctx"),a().updateQuest(t,"completed"),{ok:1})},"POST /s":e=>{const t=e.p;let r=a().getProject(t);if(r||(a().scanForProjects(t,!1),r=a().getProject(t)),!r)return{e:"not found"};let n=a().getTodaySession(r.id);return n||(n=a().createSession(r.id,{objectives:e.o||"",notes:""})),h.invalidate("ctx"),{i:n.id,d:n.date}},"POST /su":e=>{const t=e.i;return h.invalidate("ctx"),a().updateSession(t,{notes:e.n,summary:e.s,completed:e.c}),{ok:1}},"POST /x":e=>{const t=e.p,r=a().getProject(t);return r?(a().setContext(r.id,e.k,e.v),h.invalidate("ctx"),{ok:1}):{e:"not found"}},"GET /x":e=>{const t=e.p,r=a().getProject(t);return r?a().getAllContext(r.id):{e:"not found"}},"POST /g":e=>{const t=e.k,r=e.v;return!t||typeof t!="string"?{e:"k (key) required"}:r==null?{e:"v (value) required"}:(a().setGlobalContext(t,String(r)),h.invalidate(),{ok:1})},"GET /g":()=>a().getAllGlobalContext(),"GET /s":e=>{const t=e.q;if(!t)return{e:"query required"};const r=U(t);return{q:a().searchQuests(r).slice(0,5).map(s=>({i:s.id,t:s.title})),s:a().searchSessions(r).slice(0,3).map(s=>({i:s.id,d:s.date})),p:a().searchProjects(r).slice(0,3).map(s=>({n:s.name,p:s.path}))}},"POST /d":e=>{const t=e.p,r=a().getProject(t);return r?{i:a().insertData(r.id,e.c,e.k,e.v,e.m).id}:{e:"not found"}},"GET /d":e=>{const t=e.p,r=a().getProject(t);if(!r)return{e:"not found"};const n=_(e.l,20,100);return a().queryData(r.id,e.c,n).map(s=>({k:s.key,v:ee(s.value,100)}))},"GET /d/search":e=>{const t=e.p,r=a().getProject(t);if(!r)return{e:"not found"};const n=e.q;if(!n)return{e:"q (query) required"};const s=e.c,o=_(e.l,10,100);let d=a().searchData(n,r.id);return s&&(d=d.filter(i=>i.category===s)),d.slice(0,o).map(i=>({k:i.key,v:i.value,c:i.category,m:i.metadata}))},"GET /d/agg":e=>{const t=e.p,r=a().getProject(t);if(!r)return{e:"not found"};const n=e.c;if(!n)return{e:"c (category) required"};const s=e.stack_key,o=e.scanner,d=_(e.l,1e3,1e4),i={};for(const l of a().queryData(r.id,n,d)){let p;try{p=JSON.parse(l.value)}catch{continue}if(s&&p.stack_key!==s||o&&p.scanner!==o)continue;const c=String(p.scanner??""),u=String(p.payload_hash??"");if(!c||!u)continue;const m=`${c}:${u}`,f=i[m]??{s:c,ph:u,sk:String(p.stack_key??""),h:0,t:0};f.t+=1,p.hit&&(f.h+=1),i[m]=f}return Object.values(i).map(l=>({...l,r:l.t>0?l.h/l.t:0})).sort((l,p)=>p.r-l.r||p.h-l.h)},"POST /batch":e=>{const t=e.ops;if(!t||!Array.isArray(t))return{e:"ops required"};if(t.length>M)return{e:`max ${M} ops per batch`};const r=performance.now();return{r:t.map(s=>{const o=`${s.m} ${s.r}`,d=X[o];if(!d)return{e:"unknown"};try{return{d:d(s.a||{})}}catch{return{e:"op failed"}}}),ms:Math.round(performance.now()-r)}},"POST /write":e=>_e({db:a(),artifacts:A(),truths:Le(),failures:be()},e),"GET /syms":e=>{const t=String(e.q??"").slice(0,200);if(!t)return[];const r=_(e.limit,20,200),n=a().getDatabase();try{return n.prepare(`
3
3
  SELECT symbol, kind, language, file_path, line, signature, project_id
4
4
  FROM symbol_index
5
5
  WHERE symbol LIKE ?
6
6
  ORDER BY project_id, file_path, line
7
7
  LIMIT ?
8
- `).all(`%${t}%`,r)}catch{return[]}},"GET /failures":e=>{const t=String(e.target??"").slice(0,200),r=y(e.limit,10,100),n=o().getDatabase();try{return n.prepare(`
8
+ `).all(`%${t}%`,r)}catch{return[]}},"GET /failures":e=>{const t=String(e.target??"").slice(0,200),r=_(e.limit,10,100),n=a().getDatabase();try{return n.prepare(`
9
9
  SELECT id, scope, target, description, why_failed, occurrences,
10
10
  severity, last_seen
11
11
  FROM failure_patterns
12
12
  WHERE resolved = 0 AND (target = ? OR target LIKE ?)
13
13
  ORDER BY occurrences DESC, last_seen DESC
14
14
  LIMIT ?
15
- `).all(t,`%${t}%`,r)}catch{return[]}},"GET /truths":e=>{const t=String(e.q??"").slice(0,200),r=y(e.limit,5,50);if(!t)return[];const n=o().getDatabase();try{return n.prepare(`
15
+ `).all(t,`%${t}%`,r)}catch{return[]}},"GET /truths":e=>{const t=String(e.q??"").slice(0,200),r=_(e.limit,5,50);if(!t)return[];const n=a().getDatabase();try{return n.prepare(`
16
16
  SELECT category, key, value, rationale, confidence, ttl_days
17
17
  FROM ground_truths
18
18
  WHERE is_current = 1
19
19
  AND (key LIKE ? OR value LIKE ? OR rationale LIKE ?)
20
20
  ORDER BY confidence DESC
21
21
  LIMIT ?
22
- `).all(`%${t}%`,`%${t}%`,`%${t}%`,r)}catch{return[]}},"GET /routing":e=>{const t=String(e.task_type??e.t??"").slice(0,200).trim();if(!t)return{e:"task_type required"};const r=y(e.limit,10,100);let n;const s=e.p;if(s){const c=o().getProject(s);if(!c)return{e:"not found"};n=c.id}const a=e.since_days!=null?Number(e.since_days):void 0;return o().recallRouting(t,{projectId:n,limit:r,sinceDays:Number.isFinite(a)?a:void 0}).map(c=>({m:c.model_id,tools:c.tool_names,runs:c.runs,ok:c.successes,fail:c.failures,rate:Math.round(c.success_rate*1e3)/1e3,ms:c.avg_latency_ms,last:c.last_ts}))},"POST /routing":e=>{const t=String(e.task_type??"").slice(0,200).trim(),r=String(e.model_id??"").slice(0,200).trim();if(!t)return{e:"task_type required"};if(!r)return{e:"model_id required"};if(e.success===void 0||e.success===null)return{e:"success required"};let n;const s=e.p;if(s){const l=o().getProject(s);l&&(n=l.id)}const a=Array.isArray(e.tool_names)?e.tool_names.map(l=>String(l)):e.tool_names!=null?String(e.tool_names):null,i=e.latency_ms!=null?Number(e.latency_ms):null,c=e.success===!0||e.success===1||e.success==="1"||e.success==="true";try{return{i:o().recordRouting({projectId:n,taskType:t,modelId:r,toolNames:a,success:c,latencyMs:Number.isFinite(i)?i:null})}}catch(l){return{e:l.message}}},"GET /audit":e=>{const t=y(e.limit,50,500),r=e.kind,n=o().getDatabase();try{return r?n.prepare("SELECT * FROM audit_log WHERE event_kind = ? ORDER BY id DESC LIMIT ?").all(r,t):n.prepare("SELECT * FROM audit_log ORDER BY id DESC LIMIT ?").all(t)}catch{return[]}},"GET /sync/push":e=>{const t=e.since,r=y(e.limit,500,5e3),n=o().getDatabase(),s=[],a=t??"1970-01-01",i=[["session","sessions","created_at"],["quest","quests","created_at"],["truth","ground_truths","updated_at"],["artifact","memory_artifacts","last_accessed_at"],["edge","decision_edges","created_at"]],c=(()=>{try{return n.prepare("PRAGMA table_info(projects)").all().some(u=>u.name==="sync_policy")?"AND (project_id IS NULL OR project_id IN (SELECT id FROM projects WHERE sync_policy = 'team'))":""}catch{return""}})();for(const[l,u,p]of i){if(s.length>=r)break;try{const d=n.prepare(`SELECT * FROM ${u}
23
- WHERE is_shared = 1 ${c} AND ${p} > ?
24
- ORDER BY ${p} ASC
25
- LIMIT ?`).all(a,Math.max(1,r-s.length));for(const m of d)s.push({kind:l,id:m.id,project_id:m.project_id??null,updated_at:m[p]??null,payload:m})}catch{}}return{rows:s,count:s.length,cursor:new Date().toISOString()}},"GET /events":e=>{if(!o().liveMemoryEnabled())return{e:"live memory disabled",disabled:1};const t=A(e.project??e.p);if(!t)return{e:"not found"};const r=J(void 0,e.since),n=y(e.limit??e.l,100,1e3),a=e.shared==="1"||e.shared==="true"?o().eventsForPush(t.id,r,n):o().eventsSince(t.id,r,n),i=a.length?a[a.length-1].cursor:r;return{project:t.name,cursor:i,count:a.length,events:a}},"POST /events":e=>{if(!o().liveMemoryEnabled())return{e:"live memory disabled",disabled:1};const t=A(e.project??e.p);if(!t)return{e:"not found"};const r=Array.isArray(e.events)?e.events:e.event?[e.event]:[];if(r.length===0)return{e:"events[] required"};if(r.length>M)return{e:`max ${M} events per request`};let n=0,s=0,a=0;for(const i of r){const c=o().ingestRemoteEvent(t.id,i);c==="inserted"?n++:c==="echo"?a++:s++}return n>0&&h.invalidate("ctx"),{project:t.name,inserted:n,duplicate:s,echo:a}},"GET /stats":()=>{const{result:e,ms:t}=G(()=>o().getStats());return{...e,ms:t,cache:h.stats()}},"GET /health":async e=>{const t={ok:1,ts:Date.now()};if(!/^(1|true|yes)$/i.test(String(e.check_dependencies??"")))return t;const r={...t};try{const s=o().getDatabase().prepare("SELECT MAX(version) v FROM schema_versions").get().v;r.db={ok:1,migration:s}}catch{r.ok=0,r.db={ok:0}}try{const n=I().getVectorStore();if(n){const s=n.getProvider(),a=n.indexCoverage();r.vectors={provider:s.name,model:s.model,ready:await s.isReady(),indexed:a.activeCount,stale:a.stale?1:0,...a.stale?{indexed_under:Object.keys(a.otherModels),hint:"run wyrm_reindex"}:{},egress:s.remoteHost??"none"}}else r.vectors={provider:"none",ready:!1}}catch{r.vectors={ok:0}}return r.rerank={configured:ce()?1:0,provider:(process.env.WYRM_RERANK_PROVIDER||"llamacpp").trim()||"llamacpp",egress:ie()??"none"},r},"GET /auth/status":()=>{const e=N();return{auth:e.requireAuth?1:0,origins:e.allowedOrigins.length}},"GET /ui/stats":()=>{const e=o().getDatabase(),t=e.prepare("SELECT COUNT(*) as c FROM projects").get().c,r=e.prepare("SELECT COUNT(*) as c FROM sessions").get().c,n=e.prepare("SELECT COUNT(*) as c FROM quests WHERE status IN ('pending','in_progress')").get().c,s=e.prepare("SELECT COUNT(*) as c FROM data_lake").get().c,a=e.prepare("SELECT COUNT(*) as c FROM memory_artifacts WHERE needs_review = 0").get().c,i=e.prepare("SELECT COUNT(*) as c FROM memory_artifacts WHERE needs_review = 1").get().c,c=e.prepare("SELECT COUNT(*) as c FROM ground_truths WHERE is_current = 1").get().c,l=e.prepare(`
22
+ `).all(`%${t}%`,`%${t}%`,`%${t}%`,r)}catch{return[]}},"GET /routing":e=>{const t=String(e.task_type??e.t??"").slice(0,200).trim();if(!t)return{e:"task_type required"};const r=_(e.limit,10,100);let n;const s=e.p;if(s){const i=a().getProject(s);if(!i)return{e:"not found"};n=i.id}const o=e.since_days!=null?Number(e.since_days):void 0;return a().recallRouting(t,{projectId:n,limit:r,sinceDays:Number.isFinite(o)?o:void 0}).map(i=>({m:i.model_id,tools:i.tool_names,runs:i.runs,ok:i.successes,fail:i.failures,rate:Math.round(i.success_rate*1e3)/1e3,ms:i.avg_latency_ms,last:i.last_ts}))},"POST /routing":e=>{const t=String(e.task_type??"").slice(0,200).trim(),r=String(e.model_id??"").slice(0,200).trim();if(!t)return{e:"task_type required"};if(!r)return{e:"model_id required"};if(e.success===void 0||e.success===null)return{e:"success required"};let n;const s=e.p;if(s){const l=a().getProject(s);l&&(n=l.id)}const o=Array.isArray(e.tool_names)?e.tool_names.map(l=>String(l)):e.tool_names!=null?String(e.tool_names):null,d=e.latency_ms!=null?Number(e.latency_ms):null,i=e.success===!0||e.success===1||e.success==="1"||e.success==="true";try{return{i:a().recordRouting({projectId:n,taskType:t,modelId:r,toolNames:o,success:i,latencyMs:Number.isFinite(d)?d:null})}}catch(l){return{e:l.message}}},"GET /audit":e=>{const t=_(e.limit,50,500),r=e.kind,n=a().getDatabase();try{return r?n.prepare("SELECT * FROM audit_log WHERE event_kind = ? ORDER BY id DESC LIMIT ?").all(r,t):n.prepare("SELECT * FROM audit_log ORDER BY id DESC LIMIT ?").all(t)}catch{return[]}},"GET /sync/push":e=>{const t=e.since,r=_(e.limit,500,5e3),n=a().getDatabase(),s=[],o=t??"1970-01-01",d=[["session","sessions","created_at"],["quest","quests","created_at"],["truth","ground_truths","updated_at"],["artifact","memory_artifacts","last_accessed_at"],["edge","decision_edges","created_at"]],i=(()=>{try{return n.prepare("PRAGMA table_info(projects)").all().some(p=>p.name==="sync_policy")?"AND (project_id IS NULL OR project_id IN (SELECT id FROM projects WHERE sync_policy = 'team'))":""}catch{return""}})();for(const[l,p,c]of d){if(s.length>=r)break;try{const u=n.prepare(`SELECT * FROM ${p}
23
+ WHERE is_shared = 1 ${i} AND ${c} > ?
24
+ ORDER BY ${c} ASC
25
+ LIMIT ?`).all(o,Math.max(1,r-s.length));for(const m of u)s.push({kind:l,id:m.id,project_id:m.project_id??null,updated_at:m[c]??null,payload:m})}catch{}}return{rows:s,count:s.length,cursor:new Date().toISOString()}},"GET /events":e=>{if(!a().liveMemoryEnabled())return{e:"live memory disabled",disabled:1};const t=P(e.project??e.p);if(!t)return{e:"not found"};const r=K(void 0,e.since),n=_(e.limit??e.l,100,1e3),o=e.shared==="1"||e.shared==="true"?a().eventsForPush(t.id,r,n):a().eventsSince(t.id,r,n),d=o.length?o[o.length-1].cursor:r;return{project:t.name,cursor:d,count:o.length,events:o}},"POST /events":e=>{if(!a().liveMemoryEnabled())return{e:"live memory disabled",disabled:1};const t=P(e.project??e.p);if(!t)return{e:"not found"};const r=Array.isArray(e.events)?e.events:e.event?[e.event]:[];if(r.length===0)return{e:"events[] required"};if(r.length>M)return{e:`max ${M} events per request`};let n=0,s=0,o=0;for(const d of r){const i=a().ingestRemoteEvent(t.id,d);i==="inserted"?n++:i==="echo"?o++:s++}return n>0&&h.invalidate("ctx"),{project:t.name,inserted:n,duplicate:s,echo:o}},"GET /stats":()=>{const{result:e,ms:t}=G(()=>a().getStats());return{...e,ms:t,cache:h.stats()}},"GET /health":async e=>{const t={ok:1,ts:Date.now()};if(!/^(1|true|yes)$/i.test(String(e.check_dependencies??"")))return t;const r={...t};try{const s=a().getDatabase().prepare("SELECT MAX(version) v FROM schema_versions").get().v;r.db={ok:1,migration:s}}catch{r.ok=0,r.db={ok:0}}try{const n=A().getVectorStore();if(n){const s=n.getProvider(),o=n.indexCoverage();r.vectors={provider:s.name,model:s.model,ready:await s.isReady(),indexed:o.activeCount,stale:o.stale?1:0,...o.stale?{indexed_under:Object.keys(o.otherModels),hint:"run wyrm_reindex"}:{},egress:s.remoteHost??"none"}}else r.vectors={provider:"none",ready:!1}}catch{r.vectors={ok:0}}return r.rerank={configured:ce()?1:0,provider:(process.env.WYRM_RERANK_PROVIDER||"llamacpp").trim()||"llamacpp",egress:le()??"none"},r},"GET /auth/status":()=>{const e=k();return{auth:e.requireAuth?1:0,origins:e.allowedOrigins.length}},"GET /ui/stats":()=>{const e=a().getDatabase(),t=e.prepare("SELECT COUNT(*) as c FROM projects").get().c,r=e.prepare("SELECT COUNT(*) as c FROM sessions").get().c,n=e.prepare("SELECT COUNT(*) as c FROM quests WHERE status IN ('pending','in_progress')").get().c,s=e.prepare("SELECT COUNT(*) as c FROM data_lake").get().c,o=e.prepare("SELECT COUNT(*) as c FROM memory_artifacts WHERE needs_review = 0").get().c,d=e.prepare("SELECT COUNT(*) as c FROM memory_artifacts WHERE needs_review = 1").get().c,i=e.prepare("SELECT COUNT(*) as c FROM ground_truths WHERE is_current = 1").get().c,l=e.prepare(`
26
26
  SELECT date, summary, objectives FROM sessions
27
27
  ORDER BY created_at DESC LIMIT 8
28
- `).all();return{projects:t,sessions:r,active_quests:n,data_points:s,artifacts:a,review_queue:i,truths:c,recent_sessions:l}},"GET /ui/memories":e=>{const t=o().getDatabase(),r=Math.max(1,parseInt(String(e.page||"1"),10)),n=Math.min(100,Math.max(1,parseInt(String(e.limit||"20"),10))),s=e.kind?String(e.kind):"",a=e.search?String(e.search):"",i=(r-1)*n;let c,l,u,p;if(a){const f=W(a);s?(c=`
28
+ `).all();return{projects:t,sessions:r,active_quests:n,data_points:s,artifacts:o,review_queue:d,truths:i,recent_sessions:l}},"GET /ui/memories":e=>{const t=a().getDatabase(),r=Math.max(1,parseInt(String(e.page||"1"),10)),n=Math.min(100,Math.max(1,parseInt(String(e.limit||"20"),10))),s=e.kind?String(e.kind):"",o=e.search?String(e.search):"",d=(r-1)*n;let i,l,p,c;if(o){const f=U(o);s?(i=`
29
29
  SELECT ma.*, p.name AS project_name
30
30
  FROM memory_artifacts ma
31
31
  JOIN memory_artifacts_fts fts ON fts.rowid = ma.id
@@ -36,7 +36,7 @@ import V from"http";import{WyrmDB as H}from"./database.js";import{cache as h,est
36
36
  SELECT COUNT(*) as c FROM memory_artifacts ma
37
37
  JOIN memory_artifacts_fts fts ON fts.rowid = ma.id
38
38
  WHERE memory_artifacts_fts MATCH ? AND ma.kind = ?
39
- AND ma.supersedes_id IS NULL`,u=[f,s,n,i],p=[f,s]):(c=`
39
+ AND ma.supersedes_id IS NULL`,p=[f,s,n,d],c=[f,s]):(i=`
40
40
  SELECT ma.*, p.name AS project_name
41
41
  FROM memory_artifacts ma
42
42
  JOIN memory_artifacts_fts fts ON fts.rowid = ma.id
@@ -45,37 +45,37 @@ import V from"http";import{WyrmDB as H}from"./database.js";import{cache as h,est
45
45
  ORDER BY ma.confidence DESC LIMIT ? OFFSET ?`,l=`
46
46
  SELECT COUNT(*) as c FROM memory_artifacts ma
47
47
  JOIN memory_artifacts_fts fts ON fts.rowid = ma.id
48
- WHERE memory_artifacts_fts MATCH ? AND ma.supersedes_id IS NULL`,u=[f,n,i],p=[f])}else s?(c=`
48
+ WHERE memory_artifacts_fts MATCH ? AND ma.supersedes_id IS NULL`,p=[f,n,d],c=[f])}else s?(i=`
49
49
  SELECT ma.*, p.name AS project_name
50
50
  FROM memory_artifacts ma
51
51
  LEFT JOIN projects p ON ma.project_id = p.id
52
52
  WHERE ma.kind = ? AND ma.supersedes_id IS NULL
53
- ORDER BY ma.confidence DESC LIMIT ? OFFSET ?`,l="SELECT COUNT(*) as c FROM memory_artifacts WHERE kind = ? AND supersedes_id IS NULL",u=[s,n,i],p=[s]):(c=`
53
+ ORDER BY ma.confidence DESC LIMIT ? OFFSET ?`,l="SELECT COUNT(*) as c FROM memory_artifacts WHERE kind = ? AND supersedes_id IS NULL",p=[s,n,d],c=[s]):(i=`
54
54
  SELECT ma.*, p.name AS project_name
55
55
  FROM memory_artifacts ma
56
56
  LEFT JOIN projects p ON ma.project_id = p.id
57
57
  WHERE ma.supersedes_id IS NULL
58
- ORDER BY ma.confidence DESC LIMIT ? OFFSET ?`,l="SELECT COUNT(*) as c FROM memory_artifacts WHERE supersedes_id IS NULL",u=[n,i],p=[]);const d=t.prepare(c).all(...u),m=t.prepare(l).get(...p).c;return{items:d,total:m,page:r,limit:n}},"GET /ui/quests":()=>{const t=o().getDatabase().prepare(`
58
+ ORDER BY ma.confidence DESC LIMIT ? OFFSET ?`,l="SELECT COUNT(*) as c FROM memory_artifacts WHERE supersedes_id IS NULL",p=[n,d],c=[]);const u=t.prepare(i).all(...p),m=t.prepare(l).get(...c).c;return{items:u,total:m,page:r,limit:n}},"GET /ui/quests":()=>{const t=a().getDatabase().prepare(`
59
59
  SELECT q.id, q.title, q.status, q.priority, p.name AS project_name
60
60
  FROM quests q
61
61
  LEFT JOIN projects p ON q.project_id = p.id
62
62
  ORDER BY q.created_at DESC
63
- `).all(),r={pending:[],in_progress:[],completed:[],abandoned:[]};for(const n of t)r[n.status]&&r[n.status].push(n);return r},"GET /ui/truths":e=>{const t=o().getDatabase(),r=Math.max(1,parseInt(String(e.page||"1"),10)),n=Math.min(100,Math.max(1,parseInt(String(e.limit||"20"),10))),s=(r-1)*n,a=t.prepare(`
63
+ `).all(),r={pending:[],in_progress:[],completed:[],abandoned:[]};for(const n of t)r[n.status]&&r[n.status].push(n);return r},"GET /ui/warroom":()=>{const e=a().getDatabase(),t=(c,...u)=>{try{const m=e.prepare(c).get(...u);return m?Object.values(m)[0]??0:0}catch{return 0}},r=(c,...u)=>{try{return e.prepare(c).all(...u)}catch{return[]}},n=I(!1).fleetGrove,o=(()=>{try{return e.prepare("SELECT id FROM projects WHERE name = ? LIMIT 1").get(n)}catch{return}})()?.id??-1,d=r("SELECT agent_id, agent_kind, last_heartbeat, ttl_seconds, role FROM agent_presence ORDER BY last_heartbeat DESC LIMIT 24").map(c=>({agent:c.agent_id,kind:c.agent_kind,role:c.role,last:c.last_heartbeat,alive:(()=>{try{const u=(Date.now()-new Date(c.last_heartbeat.replace(" ","T")+"Z").getTime())/1e3;return u>=0&&u<(c.ttl_seconds||300)}catch{return!1}})()})),i={findings:t("SELECT COUNT(*) n FROM quests WHERE project_id = ? AND status = ?",o,"pending")+t("SELECT COUNT(*) n FROM failure_patterns WHERE project_id = ? AND resolved = 0",o),claimed:t("SELECT COUNT(*) n FROM quests WHERE project_id = ? AND agent_id IS NOT NULL AND status IN ('pending','in_progress')",o),fixed:t("SELECT COUNT(*) n FROM quests WHERE project_id = ? AND status = 'completed'",o)+t("SELECT COUNT(*) n FROM memory_artifacts WHERE project_id = ? AND needs_review = 0 AND tags LIKE '%receipt%'",o),verified:t("SELECT COUNT(*) n FROM memory_artifacts WHERE project_id = ? AND needs_review = 0 AND tags LIKE '%verdict%'",o)},l=[];for(const c of r("SELECT id, title, status, agent_id, COALESCE(updated_at, created_at) AS ts, COALESCE(description, '') AS description FROM quests WHERE project_id = ?",o))l.push({type:"request",id:c.id,title:c.title,status:c.status,agent:c.agent_id,ts:c.ts,detail:String(c.description).slice(0,240)});for(const c of r("SELECT id, COALESCE(problem,'') AS problem, COALESCE(validated_fix,'') AS validated_fix, COALESCE(tags,'') AS tags, created_at FROM memory_artifacts WHERE project_id = ? AND needs_review = 0 AND tags LIKE '%warroom%'",o)){const u=String(c.tags).includes("verdict")?"verdict":String(c.tags).includes("receipt")?"receipt":"note";l.push({type:u,id:c.id,title:String(c.problem).slice(0,120),status:"",agent:null,ts:c.created_at,detail:String(c.validated_fix).slice(0,240)})}for(const c of r("SELECT id, scope, target, COALESCE(description,'') AS description, last_seen, resolved FROM failure_patterns WHERE project_id = ?",o))l.push({type:"finding",id:c.id,title:`[${c.scope}] ${c.target}`,status:c.resolved?"resolved":"open",agent:null,ts:c.last_seen,detail:String(c.description).slice(0,240)});l.sort((c,u)=>String(u.ts).localeCompare(String(c.ts)));const p={failure_blocks:t("SELECT COUNT(*) n FROM failure_blocks"),tokens_saved:t("SELECT COALESCE(SUM(estimated_tokens),0) n FROM token_savings_log"),rows_shared:t("SELECT COUNT(*) n FROM memory_artifacts WHERE project_id = ? AND is_shared = 1",o)+t("SELECT COUNT(*) n FROM quests WHERE project_id = ? AND is_shared = 1",o)};return{grove:n,found:o!==-1,presence:d,loop:i,metrics:p,feed:l.slice(0,40)}},"GET /ui/truths":e=>{const t=a().getDatabase(),r=Math.max(1,parseInt(String(e.page||"1"),10)),n=Math.min(100,Math.max(1,parseInt(String(e.limit||"20"),10))),s=(r-1)*n,o=t.prepare(`
64
64
  SELECT gt.*, p.name AS project_name
65
65
  FROM ground_truths gt
66
66
  LEFT JOIN projects p ON gt.project_id = p.id
67
67
  WHERE gt.is_current = 1
68
68
  ORDER BY gt.updated_at DESC LIMIT ? OFFSET ?
69
- `).all(n,s),i=t.prepare("SELECT COUNT(*) as c FROM ground_truths WHERE is_current = 1").get().c;return{items:a.map(l=>({...l,staleness:me(l)})),total:i,page:r,limit:n}},"GET /ui/review":()=>({items:o().getDatabase().prepare(`
69
+ `).all(n,s),d=t.prepare("SELECT COUNT(*) as c FROM ground_truths WHERE is_current = 1").get().c;return{items:o.map(l=>({...l,staleness:me(l)})),total:d,page:r,limit:n}},"GET /ui/review":()=>({items:a().getDatabase().prepare(`
70
70
  SELECT ma.*, p.name AS project_name
71
71
  FROM memory_artifacts ma
72
72
  LEFT JOIN projects p ON ma.project_id = p.id
73
73
  WHERE ma.needs_review = 1
74
74
  ORDER BY ma.created_at DESC
75
- `).all()}),"GET /ui/account":()=>{const e=U(o().getDatabasePath());try{const t=_(e,"license.json");if(S(t)){const r=JSON.parse(R(t,"utf-8")),n=B(r);return{account:r.license&&r.license.issued_to||"unknown",tier:n.valid?n.tier:"free",valid:!!n.valid,expires:n.expiresAt||null,readonly:T}}}catch{}try{const t=_(e,"cloud.json");if(S(t))return{account:JSON.parse(R(t,"utf-8")).email||"Local",tier:"free",valid:!1,expires:null,readonly:T}}catch{}return{account:"Local",tier:"free",valid:!1,expires:null,readonly:T}},"GET /ui/impact":()=>{const e=o().getDatabase(),t=r=>{try{return e.prepare(r).get()?.n||0}catch{return 0}};return{failures_blocked:t("SELECT COALESCE(SUM(occurrences),0) AS n FROM failure_patterns"),tokens_saved:t("SELECT COALESCE(SUM(estimated_tokens),0) AS n FROM token_savings_log"),memories:t("SELECT COUNT(*) AS n FROM memory_artifacts WHERE supersedes_id IS NULL"),memories_week:t("SELECT COUNT(*) AS n FROM memory_artifacts WHERE created_at >= datetime('now','-7 days')"),truths:t("SELECT COUNT(*) AS n FROM ground_truths WHERE is_current = 1"),sessions:t("SELECT COUNT(*) AS n FROM sessions"),quests_completed:t("SELECT COUNT(*) AS n FROM quests WHERE status = 'completed'"),skills:t("SELECT COUNT(*) AS n FROM skills"),projects:t("SELECT COUNT(*) AS n FROM projects")}},"GET /ui/memory":e=>{const t=o().getDatabase(),r=parseInt(String(e&&e.id||"0"),10);if(!r)return{item:null};try{return{item:t.prepare(`SELECT ma.*, p.name AS project_name
75
+ `).all()}),"GET /ui/account":()=>{const e=q(a().getDatabasePath());try{const t=S(e,"license.json");if(y(t)){const r=JSON.parse(R(t,"utf-8")),n=$(r);return{account:r.license&&r.license.issued_to||"unknown",tier:n.valid?n.tier:"free",valid:!!n.valid,expires:n.expiresAt||null,readonly:g}}}catch{}try{const t=S(e,"cloud.json");if(y(t))return{account:JSON.parse(R(t,"utf-8")).email||"Local",tier:"free",valid:!1,expires:null,readonly:g}}catch{}return{account:"Local",tier:"free",valid:!1,expires:null,readonly:g}},"GET /ui/impact":()=>{const e=a().getDatabase(),t=r=>{try{return e.prepare(r).get()?.n||0}catch{return 0}};return{failures_blocked:t("SELECT COALESCE(SUM(occurrences),0) AS n FROM failure_patterns"),tokens_saved:t("SELECT COALESCE(SUM(estimated_tokens),0) AS n FROM token_savings_log"),memories:t("SELECT COUNT(*) AS n FROM memory_artifacts WHERE supersedes_id IS NULL"),memories_week:t("SELECT COUNT(*) AS n FROM memory_artifacts WHERE created_at >= datetime('now','-7 days')"),truths:t("SELECT COUNT(*) AS n FROM ground_truths WHERE is_current = 1"),sessions:t("SELECT COUNT(*) AS n FROM sessions"),quests_completed:t("SELECT COUNT(*) AS n FROM quests WHERE status = 'completed'"),skills:t("SELECT COUNT(*) AS n FROM skills"),projects:t("SELECT COUNT(*) AS n FROM projects")}},"GET /ui/memory":e=>{const t=a().getDatabase(),r=parseInt(String(e&&e.id||"0"),10);if(!r)return{item:null};try{return{item:t.prepare(`SELECT ma.*, p.name AS project_name
76
76
  FROM memory_artifacts ma LEFT JOIN projects p ON ma.project_id = p.id
77
- WHERE ma.id = ?`).get(r)||null}}catch{return{item:null}}},"GET /ui/recall":async e=>{const t=e.search?String(e.search):e.q?String(e.q):"";if(!t)return{items:[],total:0};const r=y(e.limit,6,25),n=e.kind?String(e.kind):void 0,a=(await I().recallHybridGlobal(t,{limit:r,kind:n})).map(i=>({...i.artifact,relevance:Math.round(i.relevance_score*1e3)/1e3,match_type:i.match_type}));return{items:a,total:a.length}},"GET /ui/context":async e=>{const t=e.search?String(e.search):e.q?String(e.q):"";if(!t)return{context:"",items:[],total:0,tokens:0};const r=y(e.limit,10,25),n=y(e.budget,900,2400),s=await I().recallHybridGlobal(t,{limit:r}),a=[],i=[];let c=0;for(const l of s){const u=l.artifact,p=String(u.problem??u.title??u.summary??u.key??u.kind??"").trim(),d=String(u.validated_fix??u.content??u.body??u.value??u.problem??"").replace(/\s+/g," ").trim(),m=`- ${p}${d?": "+d.slice(0,320):""}`,f=Math.ceil(m.length/4);c+f<=n?(a.push(m),c+=f):a.push(`- ${p.slice(0,64)} \u2026 (recall for detail)`),i.push({...u,relevance:Math.round(l.relevance_score*1e3)/1e3,match_type:l.match_type})}return{context:a.join(`
78
- `),tokens:c,items:i,total:i.length}},"GET /ui/skills":()=>{const e=o().getDatabase();try{const t=e.prepare(`SELECT name, description, category, tier, usage_count, is_active
79
- FROM skills ORDER BY category COLLATE NOCASE, name COLLATE NOCASE`).all();return{items:t,total:t.length}}catch{return{items:[],total:0}}},"GET /ui/homes":()=>{const e=Q();let t=o().getDatabasePath();try{t=x(t)}catch{}return{homes:e.map(r=>({...r,active:r.dbPath===t}))}},"POST /ui/switch":e=>{const t=String(e&&e.path||""),r=Q().find(n=>n.dbPath===t);if(!r)return{ok:!1,error:"unknown home"};try{return Le(new H(r.dbPath)),{ok:!0,account:r.account,tier:r.tier}}catch(n){return{ok:!1,error:n instanceof Error?n.message:String(n)}}}};function ke(e,t,r){if(!o().liveMemoryEnabled()){E(t,e,{e:"live memory disabled",disabled:1},503);return}const n=A(r.project??r.p);if(!n){E(t,e,{e:"not found"},404);return}const s=e.socket.remoteAddress||"unknown";if(P>=Re){E(t,e,{e:"too many concurrent streams",retry_after:5},503);return}if((g.get(s)??0)>=ve){E(t,e,{e:"too many concurrent streams from this client",retry_after:5},503);return}P++,g.set(s,(g.get(s)??0)+1);let a=J(e.headers["last-event-id"],r.since);const c=!(s==="127.0.0.1"||s==="::1"||s==="::ffff:127.0.0.1")||r.shared==="1"||r.shared==="true";t.writeHead(200,{...L(e),"Content-Type":"text/event-stream; charset=utf-8","Cache-Control":"no-cache, no-transform",Connection:"keep-alive","X-Accel-Buffering":"no"}),t.write(`retry: 3000
77
+ WHERE ma.id = ?`).get(r)||null}}catch{return{item:null}}},"GET /ui/recall":async e=>{const t=e.search?String(e.search):e.q?String(e.q):"";if(!t)return{items:[],total:0};const r=_(e.limit,6,25),n=e.kind?String(e.kind):void 0,o=(await A().recallHybridGlobal(t,{limit:r,kind:n})).map(d=>({...d.artifact,relevance:Math.round(d.relevance_score*1e3)/1e3,match_type:d.match_type}));return{items:o,total:o.length}},"GET /ui/context":async e=>{const t=e.search?String(e.search):e.q?String(e.q):"";if(!t)return{context:"",items:[],total:0,tokens:0};const r=_(e.limit,10,25),n=_(e.budget,900,2400),s=await A().recallHybridGlobal(t,{limit:r}),o=[],d=[];let i=0;for(const l of s){const p=l.artifact,c=String(p.problem??p.title??p.summary??p.key??p.kind??"").trim(),u=String(p.validated_fix??p.content??p.body??p.value??p.problem??"").replace(/\s+/g," ").trim(),m=`- ${c}${u?": "+u.slice(0,320):""}`,f=Math.ceil(m.length/4);i+f<=n?(o.push(m),i+=f):o.push(`- ${c.slice(0,64)} \u2026 (recall for detail)`),d.push({...p,relevance:Math.round(l.relevance_score*1e3)/1e3,match_type:l.match_type})}return{context:o.join(`
78
+ `),tokens:i,items:d,total:d.length}},"GET /ui/skills":()=>{const e=a().getDatabase();try{const t=e.prepare(`SELECT name, description, category, tier, usage_count, is_active
79
+ FROM skills ORDER BY category COLLATE NOCASE, name COLLATE NOCASE`).all();return{items:t,total:t.length}}catch{return{items:[],total:0}}},"GET /ui/homes":()=>{const e=Q();let t=a().getDatabasePath();try{t=x(t)}catch{}return{homes:e.map(r=>({...r,active:r.dbPath===t}))}},"POST /ui/switch":e=>{const t=String(e&&e.path||""),r=Q().find(n=>n.dbPath===t);if(!r)return{ok:!1,error:"unknown home"};try{return je(new W(r.dbPath)),{ok:!0,account:r.account,tier:r.tier}}catch(n){return{ok:!1,error:n instanceof Error?n.message:String(n)}}}};function Ae(e,t,r){if(!a().liveMemoryEnabled()){E(t,e,{e:"live memory disabled",disabled:1},503);return}const n=P(r.project??r.p);if(!n){E(t,e,{e:"not found"},404);return}const s=e.socket.remoteAddress||"unknown";if(w>=ve){E(t,e,{e:"too many concurrent streams",retry_after:5},503);return}if((T.get(s)??0)>=Ce){E(t,e,{e:"too many concurrent streams from this client",retry_after:5},503);return}w++,T.set(s,(T.get(s)??0)+1);let o=K(e.headers["last-event-id"],r.since);const i=!(s==="127.0.0.1"||s==="::1"||s==="::ffff:127.0.0.1")||r.shared==="1"||r.shared==="true";t.writeHead(200,{...b(e),"Content-Type":"text/event-stream; charset=utf-8","Cache-Control":"no-cache, no-transform",Connection:"keep-alive","X-Accel-Buffering":"no"}),t.write(`retry: 3000
80
80
 
81
- `);let l,u,p=!1;const d=()=>{if(p)return;p=!0,l&&clearInterval(l),u&&clearInterval(u),P--;const f=(g.get(s)??1)-1;f<=0?g.delete(s):g.set(s,f)},m=()=>{if(t.writableEnded){d();return}try{const f=c?o().eventsForPush(n.id,a,200):o().eventsSince(n.id,a,200);for(const F of f)a=F.cursor,t.write(ye(F))}catch{}};m(),l=setInterval(m,1e3),u=setInterval(()=>{try{t.write(_e)}catch{}},25e3),e.on("close",d),t.on("close",d),t.on("error",d)}const w=V.createServer(async(e,t)=>{if(!ee(e,t).error)try{const n=new URL(e.url||"/",`http://localhost:${j}`);if(T&&Se(e.method||"GET",n.pathname)){E(t,e,{e:"read-only mode",readonly:1},403);return}let s;try{s=await Me(e)}catch(d){if(d.message==="Invalid JSON"){E(t,e,{e:"Invalid JSON body"},400);return}throw d}if(n.searchParams.forEach((d,m)=>s[m]=d),e.method==="GET"&&n.pathname==="/dragon-mark.svg"){try{if(!v&&S(q)&&(v=R(q)),v){t.writeHead(200,{...L(e),"Content-Type":"image/svg+xml","Cache-Control":"public, max-age=86400","Content-Length":v.length}),t.end(v);return}}catch{}E(t,e,{e:"not found"},404);return}if(e.method==="GET"&&n.pathname==="/ui"){je(t,e,ae());return}if(e.method==="GET"&&n.pathname==="/events/stream"){ke(e,t,s);return}const a=n.pathname.match(/^\/ui\/review\/(\d+)\/(approve|reject)$/);if(e.method==="POST"&&a){const d=parseInt(a[1],10),m=a[2],f=o().getDatabase();m==="approve"?f.prepare("UPDATE memory_artifacts SET needs_review = 0 WHERE id = ?").run(d):f.prepare("DELETE FROM memory_artifacts WHERE id = ?").run(d),E(t,e,{ok:!0});return}const i=`${e.method} ${n.pathname}`,c=X[i];if(!c){if(n.pathname==="/"){E(t,e,{wyrm:"3.0",auth:N().requireAuth?"required":"disabled",tip:"GET /c for quick context"});return}E(t,e,{e:"not found"},404);return}const l=Te({header:e.headers["wyrm-actor"]}),{result:u,ms:p}=G(()=>ge(l,()=>c(s)));t.setHeader("X-Time-Ms",String(p)),u instanceof Promise?u.then(d=>E(t,e,d)).catch(d=>{const m=Y(d);if(m!==null){const f=$("http",m);t.setHeader("Retry-After",String(Math.ceil(f.error.retry_after_ms/1e3))),E(t,e,f,503);return}D.error("Fast API request failed",{path:e.url,error:d.message}),E(t,e,{e:"Internal server error"},500)}):E(t,e,u)}catch(n){const s=Y(n);if(s!==null){const a=$("http",s);t.setHeader("Retry-After",String(Math.ceil(a.error.retry_after_ms/1e3))),E(t,e,a,503);return}D.error("Fast API request failed",{path:e.url,error:n.message}),E(t,e,{e:"Internal server error"},500)}}),K=()=>{w.close(()=>{try{O?.close()}catch{}process.exit(0)})};process.on("SIGINT",K),process.on("SIGTERM",K);import{fileURLToPath as Ie}from"url";const De=Ie(import.meta.url);if(process.argv[1]===De){const e=process.env.WYRM_BIND_HOST||"127.0.0.1";w.listen(j,e,()=>{D.info("Wyrm Fast API started",{port:j,host:e}),console.log(`\u{F115D} Wyrm Fast API on ${e}:${j}`),console.log(` Auth: ${N().requireAuth?"required":"disabled"}`),T&&console.log(" Read-only: writes + off-box egress blocked (safe to expose)"),e!=="127.0.0.1"&&e!=="::1"&&e!=="localhost"&&(D.warn("Wyrm Fast API bound to a NON-loopback host \u2014 reachable beyond localhost; ensure auth is required",{host:e}),console.log(` \u26A0 bound to ${e}: reachable beyond localhost \u2014 make sure auth is required`),T||console.log(" \u26A0 not in read-only mode; set WYRM_UI_READONLY=1 for a public bind"))})}export{w as server};
81
+ `);let l,p,c=!1;const u=()=>{if(c)return;c=!0,l&&clearInterval(l),p&&clearInterval(p),w--;const f=(T.get(s)??1)-1;f<=0?T.delete(s):T.set(s,f)},m=()=>{if(t.writableEnded){u();return}try{const f=i?a().eventsForPush(n.id,o,200):a().eventsSince(n.id,o,200);for(const H of f)o=H.cursor,t.write(Se(H))}catch{}};m(),l=setInterval(m,1e3),p=setInterval(()=>{try{t.write(ye)}catch{}},25e3),e.on("close",u),t.on("close",u),t.on("error",u)}const F=z.createServer(async(e,t)=>{if(!te(e,t).error)try{const n=new URL(e.url||"/",`http://localhost:${j}`);if(g&&ge(e.method||"GET",n.pathname)){E(t,e,{e:"read-only mode",readonly:1},403);return}let s;try{s=await Ne(e)}catch(u){if(u.message==="Invalid JSON"){E(t,e,{e:"Invalid JSON body"},400);return}throw u}if(n.searchParams.forEach((u,m)=>s[m]=u),e.method==="GET"&&n.pathname==="/dragon-mark.svg"){try{if(!v&&y(B)&&(v=R(B)),v){t.writeHead(200,{...b(e),"Content-Type":"image/svg+xml","Cache-Control":"public, max-age=86400","Content-Length":v.length}),t.end(v);return}}catch{}E(t,e,{e:"not found"},404);return}if(e.method==="GET"&&n.pathname==="/ui"){{const u=I(!1);let m=!1;try{m=!!a().getDatabase().prepare("SELECT 1 FROM projects WHERE name = ? LIMIT 1").get(u.fleetGrove)}catch{}Me(t,e,ie(I(m)))}return}if(e.method==="GET"&&n.pathname==="/events/stream"){Ae(e,t,s);return}const o=n.pathname.match(/^\/ui\/review\/(\d+)\/(approve|reject)$/);if(e.method==="POST"&&o){const u=parseInt(o[1],10),m=o[2],f=a().getDatabase();m==="approve"?f.prepare("UPDATE memory_artifacts SET needs_review = 0 WHERE id = ?").run(u):f.prepare("DELETE FROM memory_artifacts WHERE id = ?").run(u),E(t,e,{ok:!0});return}const d=`${e.method} ${n.pathname}`,i=X[d];if(!i){if(n.pathname==="/"){E(t,e,{wyrm:"3.0",auth:k().requireAuth?"required":"disabled",tip:"GET /c for quick context"});return}E(t,e,{e:"not found"},404);return}const l=Te({header:e.headers["wyrm-actor"]}),{result:p,ms:c}=G(()=>Oe(l,()=>i(s)));t.setHeader("X-Time-Ms",String(c)),p instanceof Promise?p.then(u=>E(t,e,u)).catch(u=>{const m=Y(u);if(m!==null){const f=J("http",m);t.setHeader("Retry-After",String(Math.ceil(f.error.retry_after_ms/1e3))),E(t,e,f,503);return}D.error("Fast API request failed",{path:e.url,error:u.message}),E(t,e,{e:"Internal server error"},500)}):E(t,e,p)}catch(n){const s=Y(n);if(s!==null){const o=J("http",s);t.setHeader("Retry-After",String(Math.ceil(o.error.retry_after_ms/1e3))),E(t,e,o,503);return}D.error("Fast API request failed",{path:e.url,error:n.message}),E(t,e,{e:"Internal server error"},500)}}),V=()=>{F.close(()=>{try{O?.close()}catch{}process.exit(0)})};process.on("SIGINT",V),process.on("SIGTERM",V);import{fileURLToPath as De}from"url";const ke=De(import.meta.url);if(process.argv[1]===ke){const e=process.env.WYRM_BIND_HOST||"127.0.0.1";F.listen(j,e,()=>{D.info("Wyrm Fast API started",{port:j,host:e}),console.log(`\u{F115D} Wyrm Fast API on ${e}:${j}`),console.log(` Auth: ${k().requireAuth?"required":"disabled"}`),g&&console.log(" Read-only: writes + off-box egress blocked (safe to expose)"),e!=="127.0.0.1"&&e!=="::1"&&e!=="localhost"&&(D.warn("Wyrm Fast API bound to a NON-loopback host \u2014 reachable beyond localhost; ensure auth is required",{host:e}),console.log(` \u26A0 bound to ${e}: reachable beyond localhost \u2014 make sure auth is required`),g||console.log(" \u26A0 not in read-only mode; set WYRM_UI_READONLY=1 for a public bind"))})}export{F as server};
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "contract": "wyrm-cloud-memory-v2",
3
- "version": "8.4.0",
3
+ "version": "8.5.1",
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": "8.4.0",
3
+ "version": "8.5.1",
4
4
  "tools": [
5
5
  {
6
6
  "name": "wyrm_recall",
@@ -1,10 +1,10 @@
1
- function e(){return`<!DOCTYPE html>
1
+ import{resolveBranding as a}from"./branding.js";function r(t){const e=t??a(!1);return`<!DOCTYPE html>
2
2
  <html lang="en">
3
3
  <head>
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline' https://fonts.googleapis.com; font-src https://fonts.gstatic.com; connect-src 'self'; img-src 'self' data:; frame-ancestors 'none'">
7
- <title>Wyrm Dashboard</title>
7
+ <title>${e.product} Dashboard</title>
8
8
  <link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
9
  <link href="https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
10
10
  <style>
@@ -221,13 +221,70 @@ main { padding: calc(var(--nav-h) + 24px) 24px 24px; max-width: 1200px; margin:
221
221
  .modal-section .ms-body { color: var(--muted); font-size: 13px; white-space: pre-wrap; line-height: 1.65; }
222
222
  .item.clickable { cursor: pointer; }
223
223
  .item.clickable:hover { border-color: var(--accent-dim); }
224
+
225
+ /* \u2500\u2500 FLEET CHANNEL (operator-branded) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
226
+ .tab-warroom { color: var(--pos) !important; font-weight: 600; }
227
+ .tab-warroom.active { color: var(--pos) !important; background: rgba(52,211,153,0.10); }
228
+ .wr-head { display: flex; align-items: baseline; gap: 14px; margin-bottom: 4px; }
229
+ .wr-title { font-size: 22px; font-weight: 800; letter-spacing: -0.01em; }
230
+ .wr-grove { font-family: var(--mono); font-size: 11px; color: var(--muted); border: 1px solid var(--border); border-radius: 6px; padding: 3px 8px; }
231
+ .wr-live { display: inline-flex; align-items: center; gap: 6px; font-family: var(--mono); font-size: 11px; color: var(--pos); margin-left: auto; }
232
+ .wr-live .dot { width: 7px; height: 7px; border-radius: 50%; background: var(--pos); box-shadow: 0 0 8px var(--pos); animation: wrPulse 1.8s ease-in-out infinite; }
233
+ .wr-sub { color: var(--muted); font-size: 13px; margin-bottom: 22px; max-width: 640px; }
234
+ @keyframes wrPulse { 0%,100% { opacity: 1; } 50% { opacity: 0.35; } }
235
+
236
+ /* The signature: the loop pipeline. findings -> claimed -> fixed -> verified. */
237
+ .wr-loop { display: grid; grid-template-columns: 1fr auto 1fr auto 1fr auto 1fr; align-items: stretch; gap: 0; margin: 8px 0 30px; }
238
+ .wr-stage { background: linear-gradient(180deg, #0c0c0c, #070707); border: 1px solid var(--border); border-radius: 14px; padding: 20px 18px; position: relative; overflow: hidden; }
239
+ .wr-stage::before { content: ''; position: absolute; inset: 0 0 auto 0; height: 2px; background: var(--stage-c, var(--neu)); opacity: 0.5; }
240
+ .wr-stage .wr-k { font-size: 34px; font-weight: 800; color: var(--text); font-variant-numeric: tabular-nums; line-height: 1; }
241
+ .wr-stage .wr-l { font-family: var(--mono); font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.16em; color: var(--muted); margin-top: 8px; }
242
+ .wr-stage .wr-c { position: absolute; top: 16px; right: 16px; width: 8px; height: 8px; border-radius: 50%; background: var(--stage-c, var(--neu)); box-shadow: 0 0 10px var(--stage-c, var(--neu)); }
243
+ .wr-s-find { --stage-c: var(--neg); }
244
+ .wr-s-claim { --stage-c: var(--accent); }
245
+ .wr-s-fix { --stage-c: oklch(0.80 0.13 200); }
246
+ .wr-s-verify { --stage-c: var(--pos); }
247
+ /* Connectors carry a travelling pulse \u2014 work flowing down the pipe. */
248
+ .wr-conn { position: relative; align-self: center; width: 100%; min-width: 26px; height: 2px; background: var(--border); margin: 0 -1px; }
249
+ .wr-conn::after { content: ''; position: absolute; top: -1px; left: 0; width: 22px; height: 4px; border-radius: 4px; background: linear-gradient(90deg, transparent, var(--pos), transparent); animation: wrFlow 2.6s linear infinite; }
250
+ @keyframes wrFlow { from { left: -22px; opacity: 0; } 15% { opacity: 1; } 85% { opacity: 1; } to { left: 100%; opacity: 0; } }
251
+
252
+ /* Presence board \u2014 agent LEDs. */
253
+ .wr-presence { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 26px; }
254
+ .wr-agent { display: inline-flex; align-items: center; gap: 8px; background: var(--card); border: 1px solid var(--border); border-radius: 999px; padding: 6px 12px 6px 10px; font-size: 12px; }
255
+ .wr-agent .led { width: 8px; height: 8px; border-radius: 50%; background: var(--neu); }
256
+ .wr-agent.alive .led { background: var(--pos); box-shadow: 0 0 8px var(--pos); animation: wrPulse 2s ease-in-out infinite; }
257
+ .wr-agent .nm { font-family: var(--mono); color: var(--text); }
258
+ .wr-agent .kd { color: var(--muted); font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.08em; }
259
+ .wr-agent.dead { opacity: 0.5; }
260
+
261
+ /* Metrics strip. */
262
+ .wr-metrics { display: flex; gap: 26px; padding: 14px 20px; background: var(--card); border: 1px solid var(--border); border-radius: 12px; margin-bottom: 28px; }
263
+ .wr-metrics .m .mv { font-size: 20px; font-weight: 700; color: var(--pos); font-variant-numeric: tabular-nums; }
264
+ .wr-metrics .m .ml { font-family: var(--mono); font-size: 10px; text-transform: uppercase; letter-spacing: 0.12em; color: var(--muted); margin-top: 3px; }
265
+
266
+ /* Activity feed \u2014 type-coded left rail. */
267
+ .wr-feed { display: flex; flex-direction: column; gap: 10px; }
268
+ .wr-row { display: grid; grid-template-columns: auto 1fr auto; gap: 14px; align-items: start; background: var(--card); border: 1px solid var(--border); border-left: 3px solid var(--row-c, var(--neu)); border-radius: 10px; padding: 13px 16px; }
269
+ .wr-row .wr-badge { font-family: var(--mono); font-size: 9.5px; text-transform: uppercase; letter-spacing: 0.1em; color: var(--row-c, var(--neu)); border: 1px solid var(--row-c, var(--neu)); border-radius: 5px; padding: 3px 7px; align-self: start; white-space: nowrap; }
270
+ .wr-row .wr-t { font-weight: 600; color: var(--text); font-size: 13.5px; }
271
+ .wr-row .wr-d { color: var(--muted); font-size: 12px; margin-top: 4px; line-height: 1.55; }
272
+ .wr-row .wr-m { font-family: var(--mono); font-size: 10.5px; color: var(--muted); text-align: right; white-space: nowrap; }
273
+ .wr-row .wr-m .who { color: var(--accent); }
274
+ .wr-t-finding { --row-c: var(--neg); }
275
+ .wr-t-request { --row-c: var(--neu); }
276
+ .wr-t-receipt { --row-c: oklch(0.80 0.13 200); }
277
+ .wr-t-verdict { --row-c: var(--pos); }
278
+ .wr-empty { text-align: center; color: var(--muted); padding: 40px 0; font-family: var(--mono); font-size: 12px; }
279
+ @media (max-width: 760px) { .wr-loop { grid-template-columns: 1fr; gap: 10px; } .wr-conn { display: none; } }
224
280
  </style>
225
281
  </head>
226
282
  <body>
227
283
  <nav>
228
- <div class="logo"><img src="/dragon-mark.svg" alt="" class="logo-mark" />Wyrm</div>
284
+ <div class="logo">${e.markHtml}${e.product}</div>
229
285
  <div class="tabs" role="tablist">
230
286
  <button class="tab-btn active" data-tab="overview" role="tab">Overview</button>
287
+ ${e.showFleet?`<button class="tab-btn tab-warroom" data-tab="warroom" role="tab">${e.fleetLabel}</button>`:""}
231
288
  <button class="tab-btn" data-tab="impact" role="tab">Impact</button>
232
289
  <button class="tab-btn" data-tab="memories" role="tab">Memories</button>
233
290
  <button class="tab-btn" data-tab="quests" role="tab">Quests</button>
@@ -240,6 +297,7 @@ main { padding: calc(var(--nav-h) + 24px) 24px 24px; max-width: 1200px; margin:
240
297
  </nav>
241
298
  <main>
242
299
  <div id="pane-overview" class="pane active"></div>
300
+ ${e.showFleet?'<div id="pane-warroom" class="pane"></div>':""}
243
301
  <div id="pane-impact" class="pane"></div>
244
302
  <div id="pane-memories" class="pane"></div>
245
303
  <div id="pane-quests" class="pane"></div>
@@ -375,7 +433,7 @@ function staleBar(staleness) {
375
433
  }
376
434
 
377
435
  // \u2500\u2500 Router \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
378
- const TABS = ['overview', 'impact', 'memories', 'quests', 'truths', 'skills', 'review'];
436
+ const TABS = ['overview', ${e.showFleet?"'warroom', ":""}'impact', 'memories', 'quests', 'truths', 'skills', 'review'];
379
437
  let currentTab = '';
380
438
  // Set from GET /ui/account.readonly at boot (WYRM_UI_READONLY=1 on the server).
381
439
  // The server 403s every write regardless; this just hides the controls (UX).
@@ -406,6 +464,109 @@ document.querySelectorAll('.tab-btn').forEach(btn => {
406
464
  });
407
465
  window.addEventListener('hashchange', router);
408
466
 
467
+ // \u2500\u2500 Fleet channel (operator-branded) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
468
+ function wrRelTime(ts) {
469
+ try {
470
+ const then = new Date(String(ts).replace(' ', 'T') + (String(ts).includes('Z') ? '' : 'Z')).getTime();
471
+ const s = Math.max(0, (Date.now() - then) / 1000);
472
+ if (s < 60) return Math.floor(s) + 's ago';
473
+ if (s < 3600) return Math.floor(s / 60) + 'm ago';
474
+ if (s < 86400) return Math.floor(s / 3600) + 'h ago';
475
+ return Math.floor(s / 86400) + 'd ago';
476
+ } catch(e) { return String(ts || ''); }
477
+ }
478
+ function wrNum(n) { return (n == null ? 0 : n).toLocaleString(); }
479
+
480
+ async function loadWarroom() {
481
+ const pane = document.getElementById('pane-warroom');
482
+ clearEl(pane);
483
+
484
+ // Header
485
+ const head = el('div', 'wr-head');
486
+ head.appendChild(el('span', 'wr-title', ${JSON.stringify(e.fleetTitle)}));
487
+ head.appendChild(el('span', 'wr-grove', 'grove: ' + ${JSON.stringify(e.fleetGrove)}));
488
+ const live = el('span', 'wr-live'); live.appendChild(el('span', 'dot')); live.appendChild(el('span', null, 'LIVE'));
489
+ head.appendChild(live);
490
+ pane.appendChild(head);
491
+ pane.appendChild(el('div', 'wr-sub', ${JSON.stringify(e.fleetBlurb)}));
492
+
493
+ let data;
494
+ try { data = await apiFetch('/ui/warroom'); }
495
+ catch(e) { pane.appendChild(el('div', 'wr-empty', 'War room unavailable.')); return; }
496
+
497
+ if (!data.found) {
498
+ pane.appendChild(el('div', 'wr-empty', 'fleet-warroom grove not found on this device yet.'));
499
+ return;
500
+ }
501
+
502
+ // The loop pipeline (signature element)
503
+ const loop = el('div', 'wr-loop');
504
+ const stages = [
505
+ ['wr-s-find', 'findings', data.loop.findings, 'Findings'],
506
+ ['wr-s-claim', 'claimed', data.loop.claimed, 'Claimed'],
507
+ ['wr-s-fix', 'fixed', data.loop.fixed, 'Fixed'],
508
+ ['wr-s-verify', 'verified', data.loop.verified, 'Verified'],
509
+ ];
510
+ stages.forEach((st, i) => {
511
+ const node = el('div', 'wr-stage ' + st[0]);
512
+ node.appendChild(el('span', 'wr-c'));
513
+ node.appendChild(el('div', 'wr-k', String(st[2] ?? 0)));
514
+ node.appendChild(el('div', 'wr-l', st[3]));
515
+ loop.appendChild(node);
516
+ if (i < stages.length - 1) loop.appendChild(el('div', 'wr-conn'));
517
+ });
518
+ pane.appendChild(loop);
519
+
520
+ // Presence board
521
+ const pres = el('div', 'wr-presence');
522
+ if (!data.presence.length) {
523
+ const a = el('div', 'wr-agent dead'); a.appendChild(el('span', 'led'));
524
+ a.appendChild(el('span', 'nm', 'no agents on the board'));
525
+ pres.appendChild(a);
526
+ } else {
527
+ data.presence.forEach(p => {
528
+ const a = el('div', 'wr-agent ' + (p.alive ? 'alive' : 'dead'));
529
+ a.appendChild(el('span', 'led'));
530
+ a.appendChild(el('span', 'nm', p.agent));
531
+ a.appendChild(el('span', 'kd', p.kind + (p.role ? ' \xB7 ' + p.role : '')));
532
+ pres.appendChild(a);
533
+ });
534
+ }
535
+ pane.appendChild(pres);
536
+
537
+ // Metrics strip (the "does it help" numbers)
538
+ const met = el('div', 'wr-metrics');
539
+ [['repeats blocked', data.metrics.failure_blocks], ['tokens saved', data.metrics.tokens_saved], ['rows shared', data.metrics.rows_shared]].forEach(m => {
540
+ const c = el('div', 'm');
541
+ c.appendChild(el('div', 'mv', wrNum(m[1])));
542
+ c.appendChild(el('div', 'ml', m[0]));
543
+ met.appendChild(c);
544
+ });
545
+ pane.appendChild(met);
546
+
547
+ // Activity feed
548
+ pane.appendChild(el('div', 'section-title', 'Activity'));
549
+ const feed = el('div', 'wr-feed');
550
+ if (!data.feed.length) {
551
+ feed.appendChild(el('div', 'wr-empty', 'No war-room activity yet. The first finding a fleet agent files will appear here.'));
552
+ } else {
553
+ data.feed.forEach(r => {
554
+ const row = el('div', 'wr-row wr-t-' + (r.type || 'note'));
555
+ row.appendChild(el('span', 'wr-badge', r.type));
556
+ const mid = el('div');
557
+ mid.appendChild(el('div', 'wr-t', truncate(r.title, 90) + (r.status ? ' \xB7 ' + r.status : '')));
558
+ if (r.detail) mid.appendChild(el('div', 'wr-d', truncate(r.detail, 200)));
559
+ row.appendChild(mid);
560
+ const meta = el('div', 'wr-m');
561
+ if (r.agent) { const w = el('div'); w.appendChild(el('span', 'who', r.agent)); meta.appendChild(w); }
562
+ meta.appendChild(el('div', null, wrRelTime(r.ts)));
563
+ row.appendChild(meta);
564
+ feed.appendChild(row);
565
+ });
566
+ }
567
+ pane.appendChild(feed);
568
+ }
569
+
409
570
  // \u2500\u2500 Overview \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
410
571
  async function loadOverview() {
411
572
  const pane = document.getElementById('pane-overview');
@@ -1001,6 +1162,7 @@ function buildPagination(current, total, onPage) {
1001
1162
  // \u2500\u2500 Tab loader \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
1002
1163
  function loadTab(name) {
1003
1164
  if (name === 'overview') loadOverview();
1165
+ else if (name === 'warroom') loadWarroom();
1004
1166
  else if (name === 'impact') loadImpact();
1005
1167
  else if (name === 'memories') { initMemoriesPane(); loadMemories(memPage, memKind, memSearch); }
1006
1168
  else if (name === 'quests') loadQuests();
@@ -1016,4 +1178,4 @@ loadAccount().then(router);
1016
1178
  })();
1017
1179
  <\/script>
1018
1180
  </body>
1019
- </html>`}export{e as getUIDashboardHTML};
1181
+ </html>`}export{r as getUIDashboardHTML};
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "contract": "wyrm-cloud-memory-v1",
3
- "version": "8.4.0",
3
+ "version": "8.5.1",
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": "8.4.0",
3
+ "version": "8.5.1",
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",
@@ -17,7 +17,6 @@
17
17
  },
18
18
  "files": [
19
19
  "dist",
20
- "ui",
21
20
  "skills/wyrm-getting-started",
22
21
  "skills/wyrm-memory-and-recall",
23
22
  "skills/wyrm-nvidia-nim",