wyrm-mcp 7.9.0 → 7.10.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/dist/bridge.js ADDED
@@ -0,0 +1 @@
1
+ import{readFileSync as y,writeFileSync as O,readdirSync as M,existsSync as _}from"fs";import{createHash as q}from"crypto";import{join as h}from"path";import{homedir as R}from"os";import{MemoryArtifacts as T}from"./memory-artifacts.js";import{recordWrite as g}from"./receipts.js";import{buildRenderPlan as B,renderToDisk as C,renderForClient as F,makeRenderDeps as I}from"./render-target.js";import{makeBridgeDeps as x,sweepProject as E}from"./reverse-bridge.js";function W(){return process.env.WYRM_BRIDGE_CONFIG??h(R(),".wyrm","bridge.json")}function P(){return process.env.WYRM_BRIDGE_STATE??h(R(),".wyrm","bridge-state.json")}function ee(){try{return JSON.parse(y(W(),"utf8"))}catch{return null}}function N(){try{return JSON.parse(y(P(),"utf8"))}catch{return{}}}function D(r){try{O(P(),JSON.stringify(r,null,1))}catch{}}function H(r,n,t){const o=t??h(R(),".wyrm","remember-distill-state.json");if(!_(o))return 0;let d=0;try{const e=JSON.parse(y(o,"utf8"));for(const[c,i]of Object.entries(e)){const s=`${r}:${c}`;n[s]||(n[s]=i,d++)}}catch{}return d}const J=r=>q("sha1").update(r).digest("hex");function L(r,n,t,o,d={}){const e={source:t.name,type:"remember",stored:0,dropped:0,queued:0,rendered:0},c=t.windowDays&&t.windowDays>=1?t.windowDays:7,i=t.maxPerRun&&t.maxPerRun>=1?t.maxPerRun:80,s=r.prepare("SELECT id FROM projects WHERE path = ?").get(t.projectPath);if(!s)return e.note=`no project at ${t.projectPath}`,e;const u=new Date(Date.now()-c*864e5);let p=[];try{p=M(t.dir).filter(a=>/^today-\d{4}-\d{2}-\d{2}(\.done)?\.md$/.test(a)).map(a=>({f:a,date:a.match(/\d{4}-\d{2}-\d{2}/)[0]})).filter(({date:a})=>new Date(a+"T00:00:00")>=u).sort((a,l)=>l.date.localeCompare(a.date))}catch{return e.note=`cannot read ${t.dir}`,e}let m=0;for(const{f:a,date:l}of p){const j=`${t.name}:today-${l}`,w=new Set(o[j]??[]);let S="";try{S=y(h(t.dir,a),"utf8")}catch{continue}const b=S.split(/^## /m).slice(1).map(f=>"## "+f.trim());for(const f of b){if(m>=i)break;if(f.length<30)continue;const $=J(f);if(w.has($))continue;if(d.dryRun){m++,e.stored++;continue}const k=`[bridge:${t.name} ${l}] ${f}`.slice(0,2400),v=n.add(s.id,{kind:"pattern",problem:k,confidence:.75,needsReview:0,createdBy:`bridge:${t.name}`});g(r,{tool:"wyrm_bridge",outcome:"stored",refTable:"mem",refId:v.id,source:`bridge:${t.name}`,projectId:s.id}),w.add($),m++,e.stored++}o[j]=[...w]}return m>=i&&(e.note=`hit per-run cap ${i} \u2014 remainder next run`),e}async function G(r,n,t,o={}){const d={source:n.name,type:"render",stored:0,dropped:0,queued:0,rendered:0},e=r.prepare("SELECT id, name, path FROM projects WHERE path = ?").get(n.projectPath);if(!e)return d.note=`no project at ${n.projectPath}`,d;const c=n.clients??[],i=I(r);try{const s=B(i,e,t),u={"MEMORY.md":s.memoryMd};for(const m of c){const a=F(m,s.model,t);u[a.relPath]=a.block}const p=await E(x(r),{id:e.id,path:e.path},u,{dryRun:o.dryRun});d.queued+=p.added,p.added>0&&g(r,{tool:"wyrm_bridge",outcome:"queued",reason:`render guard \u2014 ${p.added} human edit(s) harvested to review before overwrite`,source:`bridge:${n.name}`,projectId:e.id})}catch{}if(!o.dryRun){const{writes:s}=C(i,e,t,{clients:c});d.rendered=s.filter(u=>u.action!=="skipped").length}return d}async function Y(r,n,t={}){const o={source:n.name,type:"reverse",stored:0,dropped:0,queued:0,rendered:0},d=r.prepare("SELECT id, path FROM projects WHERE path = ?").get(n.projectPath);if(!d)return o.note=`no project at ${n.projectPath}`,o;const e=await E(x(r),d,{},{dryRun:t.dryRun});return o.queued=e.added,o.dropped=e.skipped,e.added>0&&g(r,{tool:"wyrm_bridge",outcome:"queued",reason:`reverse sweep \u2014 ${e.added} native-file edit(s) queued for review`,source:`bridge:${n.name}`,projectId:d.id}),o}async function te(r,n,t,o={}){const d=new T(r),e=N(),c=[];for(const i of n.sources)o.only&&i.name!==o.only||(i.type==="remember"?(H(i.name,e)>0&&D(e),c.push(L(r,d,i,e,o))):i.type==="render"?c.push(await G(r,i,t,o)):c.push(await Y(r,i,o)));return o.dryRun||D(e),c}export{H as adoptLegacyDistillState,W as configPath,ee as loadConfig,N as loadState,te as runBridge,L as runRememberSource,G as runRenderSource,Y as runReverseSource,D as saveState,P as statePath};
@@ -1,77 +1,78 @@
1
- import{join as B}from"path";import{TOOL_ANNOTATIONS as C}from"../tool-annotations.js";import{renderResult as S,withGlyph as T,renderErrorResponse as R,validationErrorBody as A}from"../render.js";import{ValidationError as K,asEnum as J}from"../validate.js";import{daemonOr as D}from"../daemon-writer.js";import{classifyCapture as Y}from"../capture.js";import{recordWrite as M}from"../receipts.js";import{extractCandidates as O,candidateToArtifact as F,escapeLikePattern as W}from"../auto-capture.js";import{parseTrace as Q,segmentsToText as X}from"../trace-harvest.js";import{getActor as G,runWithActor as V}from"./boundary.js";import{harvestProjects as z}from"../harvest.js";import{readFileSync as N,readdirSync as Z,statSync as L}from"fs";import{join as U}from"path";import{homedir as ee}from"os";import{readSpecDir as te,specTaskSignature as re}from"../spec-kit.js";const _e=[{name:"wyrm_capture",description:"Use to save anything worth keeping - the single write funnel. Auto-classifies notes, decisions, and lessons into quest, ground truth, or memory; risky writes queue for wyrm_review. mode=direct (store a proven pattern or lesson learned so future agents reuse it), extract (pull memories from a conversation transcript or freeform text), trace (harvest a session JSONL / tool-call log into run-tagged review candidates), session (close out a work session into reusable knowledge), artifacts (harvest a repo's READMEs and recent commits into durable facts), import source=git|pr|rules, spec (turn a tasks.md into tracked work items), checklist. Keywords: remember this, jot down, write down, future reference.",inputSchema:{type:"object",properties:{content:{type:"string",description:"classify/rules"},project_id:{type:"number",description:"classify/import/spec"},tags:{type:"array",items:{type:"string"}},mode:{type:"string",enum:["auto","quest","truth","memory","classify","direct","extract","trace","session","artifacts","import","spec","checklist"],description:"quest|truth|memory force the type"},source:{type:"string",enum:["git","pr","rules"],description:"import"},projectPath:{type:"string",description:"non-classify"},kind:{type:"string",description:"direct"},problem:{type:"string",description:"direct"},validatedFix:{type:"string",description:"direct"},text:{type:"string",description:"extract"},sessionId:{type:"string",description:"session"},candidates:{type:"array",items:{type:"object"},description:"session"},dryRun:{type:"boolean",description:"artifacts"},commits:{type:"array",items:{type:"object"},description:"import git"},title:{type:"string",description:"import pr"},body:{type:"string",description:"import pr"},specDir:{type:"string",description:"spec"},problemType:{type:"string",description:"checklist"},whenToUse:{type:"string",description:"checklist"},steps:{type:"array",items:{type:"string"},description:"checklist"}},required:[]},outputSchema:{type:"object",properties:{status:{type:"string",enum:["captured","queued_for_review"]},type:{type:"string"},subtype:{type:"string"},confidence:{type:"number"},reasoning:{type:"string"},id:{type:"integer"},ref:{type:"string"},needs_review:{type:"boolean"},reason:{type:"string"},artifact_id:{type:"integer"},conflicts_with:{type:"array"},advisory_conflicts:{type:"array"}},required:["status"]},annotations:C.wyrm_capture,aliases:[],handler:async(k,{store:v,raw:$,memory:m,truths:g,cache:w})=>{const{content:c,project_id:y,projectPath:o,tags:f,mode:n}=k;if(typeof c!="string"||!c.trim()){const r=k;return r.problem||r.validated_fix||r.validatedFix||r.kind?R(A("wyrm_capture","mode","you passed pattern/lesson fields (kind/problem/validated_fix) \u2014 set mode:'direct' to store them as a distilled artifact.")):R(A("wyrm_capture","content","content is required for auto capture (or use mode:'direct' with problem + validated_fix)."))}let u=Y(c);n&&n!=="auto"&&(u={type:n,subtype:{quest:"quest",truth:"decision",memory:"pattern"}[n]??n,confidence:100,reasoning:`Mode override: ${n}`});const{type:a,subtype:l,confidence:_,reasoning:i}=u,e=y??(o?v.getProject(o)?.id??null:null);let d=0,s="",b=!1,t;if(a==="quest"){if(e===null)return R(A("wyrm_capture","projectPath","is required for quest capture"));d=(await D("quest_add",e,{title:c.slice(0,200),description:"",priority:"medium",tags:f?.join(",")},()=>v.addQuest(e,c.slice(0,200),"","medium",f?.join(",")))).id,s="quest",w.invalidate("wyrm_all_quests"),w.invalidate("wyrm_stats")}else if(a==="truth"){if(e===null)return R(A("wyrm_capture","projectPath","is required for truth capture"));if(_>=80){const r=l??"other",q=g.getCurrent(e).filter(p=>p.category===r);if(q.length>0){const p={kind:"pattern",problem:`Potential conflict with ${q.length} existing truth(s) in category "${r}"`,validatedFix:c,whyItWorked:"Pending review \u2014 possible supersession",tags:f??[],confidence:_/100,needsReview:1},j=await D("artifact_add",e,p,()=>m.add(e,p)),I=M($(),{tool:"wyrm_capture",outcome:"queued",refTable:"mem",refId:j.id,reason:`conflict_check \u2014 possible supersession of ${q.length} truth(s) in "${r}"; invisible to recall until wyrm_review`,projectId:e});return S({status:"queued_for_review",reason:"conflict_check",artifact_id:j.id,conflicts_with:q.map(E=>({id:E.id,content:E.value.slice(0,80)})),receipt:I})}}if(n!=="truth"&&_<100){const r={kind:"pattern",problem:c,validatedFix:"",whyItWorked:"",tags:f??[],confidence:_/100,needsReview:1};d=(await D("artifact_add",e,r,()=>m.add(e,r))).id,s="mem",b=!0}else{const r={category:"decision",key:c.slice(0,60),value:c};d=(await D("truth_set",e,r,()=>g.set(e,r))).id,s="truth",w.invalidate("wyrm_truth_get"),w.invalidate("wyrm_context_build")}}else{if(e===null)return R(A("wyrm_capture","projectPath","is required for memory capture"));const r=_>=75,x={kind:l,problem:c,validatedFix:"",whyItWorked:"",tags:f??[],confidence:_/100,needsReview:r?0:1};if(d=(await D("artifact_add",e,x,()=>m.add(e,x))).id,s="mem",b=!r,l==="heuristic"&&_>=75&&e!==null)try{const p=c.replace(/['"*]/g," ").trim().split(/\s+/).slice(0,5).join(" "),j=$().prepare(`
1
+ import{join as B}from"path";import{createHash as K}from"crypto";import{TOOL_ANNOTATIONS as C}from"../tool-annotations.js";import{renderResult as S,withGlyph as E,renderErrorResponse as R,validationErrorBody as A}from"../render.js";import{ValidationError as J,asEnum as Y}from"../validate.js";import{daemonOr as L}from"../daemon-writer.js";import{classifyCapture as Q}from"../capture.js";import{recordWrite as D}from"../receipts.js";import{extractCandidates as O,candidateToArtifact as F,escapeLikePattern as M}from"../auto-capture.js";import{parseTrace as X,segmentsToText as G}from"../trace-harvest.js";import{getActor as V,runWithActor as z}from"./boundary.js";import{harvestProjects as Z}from"../harvest.js";import{readFileSync as N,readdirSync as ee,statSync as W}from"fs";import{join as U}from"path";import{homedir as te}from"os";import{readSpecDir as re,specTaskSignature as ie}from"../spec-kit.js";const ve=[{name:"wyrm_capture",description:"Use to save anything worth keeping - the single write funnel. Auto-classifies notes, decisions, and lessons into quest, ground truth, or memory; risky writes queue for wyrm_review. mode=direct (store a proven pattern or lesson learned so future agents reuse it), extract (pull memories from a conversation transcript or freeform text), trace (harvest a session JSONL / tool-call log into run-tagged review candidates), session (close out a work session into reusable knowledge), artifacts (harvest a repo's READMEs and recent commits into durable facts), import source=git|pr|rules, spec (turn a tasks.md into tracked work items), checklist. Keywords: remember this, jot down, write down, future reference.",inputSchema:{type:"object",properties:{content:{type:"string",description:"classify/rules"},project_id:{type:"number",description:"classify/import/spec"},tags:{type:"array",items:{type:"string"}},mode:{type:"string",enum:["auto","quest","truth","memory","classify","direct","extract","trace","session","artifacts","import","spec","checklist"],description:"quest|truth|memory force the type"},source:{type:"string",enum:["git","pr","rules"],description:"import"},projectPath:{type:"string",description:"non-classify"},kind:{type:"string",description:"direct"},problem:{type:"string",description:"direct"},validatedFix:{type:"string",description:"direct"},text:{type:"string",description:"extract"},sessionId:{type:"string",description:"session"},candidates:{type:"array",items:{type:"object"},description:"session"},dryRun:{type:"boolean",description:"artifacts"},commits:{type:"array",items:{type:"object"},description:"import git"},title:{type:"string",description:"import pr"},body:{type:"string",description:"import pr"},specDir:{type:"string",description:"spec"},problemType:{type:"string",description:"checklist"},whenToUse:{type:"string",description:"checklist"},steps:{type:"array",items:{type:"string"},description:"checklist"}},required:[]},outputSchema:{type:"object",properties:{status:{type:"string",enum:["captured","queued_for_review"]},type:{type:"string"},subtype:{type:"string"},confidence:{type:"number"},reasoning:{type:"string"},id:{type:"integer"},ref:{type:"string"},needs_review:{type:"boolean"},reason:{type:"string"},artifact_id:{type:"integer"},conflicts_with:{type:"array"},advisory_conflicts:{type:"array"}},required:["status"]},annotations:C.wyrm_capture,aliases:[],handler:async(k,{store:_,raw:$,memory:y,truths:m,cache:w})=>{const{content:a,project_id:f,projectPath:d,tags:l,mode:u}=k;if(typeof a!="string"||!a.trim()){const o=k;return o.problem||o.validated_fix||o.validatedFix||o.kind?R(A("wyrm_capture","mode","you passed pattern/lesson fields (kind/problem/validated_fix) \u2014 set mode:'direct' to store them as a distilled artifact.")):R(A("wyrm_capture","content","content is required for auto capture (or use mode:'direct' with problem + validated_fix)."))}let b=Q(a);u&&u!=="auto"&&(b={type:u,subtype:{quest:"quest",truth:"decision",memory:"pattern"}[u]??u,confidence:100,reasoning:`Mode override: ${u}`});const{type:i,subtype:t,confidence:g,reasoning:r}=b,e=f??(d?_.getProject(d)?.id??null:null);let c=0,n="",v=!1,s;if(i==="quest"){if(e===null)return R(A("wyrm_capture","projectPath","is required for quest capture"));c=(await L("quest_add",e,{title:a.slice(0,200),description:"",priority:"medium",tags:l?.join(",")},()=>_.addQuest(e,a.slice(0,200),"","medium",l?.join(",")))).id,n="quest",w.invalidate("wyrm_all_quests"),w.invalidate("wyrm_stats")}else if(i==="truth"){if(e===null)return R(A("wyrm_capture","projectPath","is required for truth capture"));if(g>=80){const o=t??"other",I=m.getCurrent(e).filter(p=>p.category===o);if(I.length>0){const p={kind:"pattern",problem:`Potential conflict with ${I.length} existing truth(s) in category "${o}"`,validatedFix:a,whyItWorked:"Pending review \u2014 possible supersession",tags:l??[],confidence:g/100,needsReview:1},j=await L("artifact_add",e,p,()=>y.add(e,p)),q=D($(),{tool:"wyrm_capture",outcome:"queued",refTable:"mem",refId:j.id,reason:`conflict_check \u2014 possible supersession of ${I.length} truth(s) in "${o}"; invisible to recall until wyrm_review`,projectId:e});return S({status:"queued_for_review",reason:"conflict_check",artifact_id:j.id,conflicts_with:I.map(T=>({id:T.id,content:T.value.slice(0,80)})),receipt:q})}}if(u!=="truth"&&g<100){const o={kind:"pattern",problem:a,validatedFix:"",whyItWorked:"",tags:l??[],confidence:g/100,needsReview:1};c=(await L("artifact_add",e,o,()=>y.add(e,o))).id,n="mem",v=!0}else{const o={category:"decision",key:a.slice(0,60),value:a};c=(await L("truth_set",e,o,()=>m.set(e,o))).id,n="truth",w.invalidate("wyrm_truth_get"),w.invalidate("wyrm_context_build")}}else{if(e===null)return R(A("wyrm_capture","projectPath","is required for memory capture"));const o=g>=75,x={kind:t,problem:a,validatedFix:"",whyItWorked:"",tags:l??[],confidence:g/100,needsReview:o?0:1};if(c=(await L("artifact_add",e,x,()=>y.add(e,x))).id,n="mem",v=!o,t==="heuristic"&&g>=75&&e!==null)try{const p=a.replace(/['"*]/g," ").trim().split(/\s+/).slice(0,5).join(" "),j=$().prepare(`
2
2
  SELECT a.id, a.problem FROM memory_artifacts a
3
3
  JOIN memory_artifacts_fts ON a.id = memory_artifacts_fts.rowid
4
4
  WHERE memory_artifacts_fts MATCH ? AND a.project_id = ? AND a.kind = 'heuristic' AND a.needs_review = 0
5
5
  LIMIT 3
6
- `).all(p,e);j.length>0&&(t=j.map(I=>({id:I.id,content:I.problem.slice(0,80)})))}catch{}}const h=M($(),{tool:"wyrm_capture",outcome:b?"queued":"stored",refTable:s,refId:d,reason:b?`needs_review (classifier confidence ${_}%) \u2014 invisible to recall until wyrm_review`:void 0,projectId:e??void 0}),P={status:"captured",type:a,subtype:l,confidence:_,reasoning:i,id:d,ref:`${s}:${d}`,needs_review:b,receipt:h,...t&&t.length>0?{advisory_conflicts:t}:{}};return S(P,(r,x)=>{let q=`Captured as ${r.type}: ${r.subtype}
7
- Confidence: ${r.confidence}% | ${r.reasoning}
8
- ID: ${r.ref}`;r.needs_review&&(q+=`
9
- `+T(x.warn,"Stored for review -- run `wyrm_review` to activate"));const p=r.advisory_conflicts;return p&&p.length>0&&(q+=`
6
+ `).all(p,e);j.length>0&&(s=j.map(q=>({id:q.id,content:q.problem.slice(0,80)})))}catch{}}const h=D($(),{tool:"wyrm_capture",outcome:v?"queued":"stored",refTable:n,refId:c,reason:v?`needs_review (classifier confidence ${g}%) \u2014 invisible to recall until wyrm_review`:void 0,projectId:e??void 0}),P={status:"captured",type:i,subtype:t,confidence:g,reasoning:r,id:c,ref:`${n}:${c}`,needs_review:v,receipt:h,...s&&s.length>0?{advisory_conflicts:s}:{}};return S(P,(o,x)=>{let I=`Captured as ${o.type}: ${o.subtype}
7
+ Confidence: ${o.confidence}% | ${o.reasoning}
8
+ ID: ${o.ref}`;o.needs_review&&(I+=`
9
+ `+E(x.warn,"Stored for review -- run `wyrm_review` to activate"));const p=o.advisory_conflicts;return p&&p.length>0&&(I+=`
10
10
 
11
- `+T(x.warn,`Advisory: ${p.length} similar heuristic(s) found:`)+`
11
+ `+E(x.warn,`Advisory: ${p.length} similar heuristic(s) found:`)+`
12
12
  `+p.map(j=>` ${x.bullet} [${j.id}] ${j.content}`).join(`
13
- `)),q})}},{name:"wyrm_remember",description:"Store a distilled knowledge artifact \u2014 proven patterns, lessons learned, anti-patterns, and reasoning traces. These are recalled automatically by wyrm_context_build to help AI models apply past knowledge to new tasks. Example: wyrm_remember({ projectPath: '/home/user/api', kind: 'anti_pattern', problem: 'Jest suite hung after adding the SSE server', validatedFix: 'close() the server in afterAll \u2014 its keep-alive timer held the event loop open', whyItWorked: 'open handles block jest exit', outcome: 'positive', tags: ['jest', 'sse'] })",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Project this knowledge belongs to"},kind:{type:"string",enum:["reasoning_trace","lesson","pattern","anti_pattern","heuristic"],description:"reasoning_trace: a solved problem with steps; lesson: a general insight; pattern: a proven approach; anti_pattern: what NOT to do; heuristic: a rule of thumb"},problem:{type:"string",description:"What was being solved or observed (be specific and searchable)"},constraints:{type:"string",description:"What conditions or constraints applied"},validatedFix:{type:"string",description:"What actually worked \u2014 the validated solution or approach"},whyItWorked:{type:"string",description:"The insight behind WHY this worked (key for transfer learning)"},outcome:{type:"string",enum:["positive","negative","neutral"],description:"Was this approach successful?"},tags:{type:"array",items:{type:"string"},description:"Searchable tags (e.g. ['auth', 'rate-limit', 'typescript'])"},confidence:{type:"number",description:"Confidence level 0.0\u20131.0 (default: 1.0)"},sourceSessionId:{type:"number",description:"Session ID where this was discovered (optional)"}},required:["projectPath","kind","problem"]},outputSchema:{type:"object",properties:{id:{type:"integer"},kind:{type:"string",enum:["reasoning_trace","lesson","pattern","anti_pattern","heuristic"]},problem:{type:"string"},validated_fix:{type:["string","null"]},why_it_worked:{type:["string","null"]},confidence:{type:"number"},tags:{type:["string","null"]}},required:["id","kind","problem","validated_fix","why_it_worked","confidence","tags"]},annotations:C.wyrm_remember,aliases:[],handler:async(k,{store:v,memory:$,raw:m})=>{const{projectPath:g,kind:w,problem:c,constraints:y,outcome:o,tags:f,confidence:n,sourceSessionId:u}=k,a=k,l=a.validatedFix??a.validated_fix,_=a.whyItWorked??a.why_it_worked,i=J("kind",w,["reasoning_trace","lesson","pattern","anti_pattern","heuristic"]);if(i===void 0)throw new K("kind","is required");const e=v.getProject(g);if(!e)return R(A("wyrm_remember","projectPath","project not found"));const d={kind:i,problem:c,constraints:y,validatedFix:l,whyItWorked:_,outcome:o,tags:f,confidence:n,sourceSessionId:u},s=await D("artifact_add",e.id,d,()=>$.add(e.id,d)),b=M(m(),{tool:"wyrm_remember",outcome:s.needs_review===1?"queued":"stored",refTable:"mem",refId:s.id,reason:s.needs_review===1?"needs_review \u2014 invisible to recall until approved (wyrm_review)":void 0,source:s.created_by,projectId:e.id}),t={id:s.id,kind:s.kind,problem:s.problem,validated_fix:s.validated_fix??null,why_it_worked:s.why_it_worked??null,confidence:s.confidence,tags:s.tags??null,receipt:b};return S(t,(h,P)=>{let r=T(P.brand,"**Memory Stored**")+`
14
-
15
- `;return r+=`${P.bullet} **Kind:** ${h.kind}
16
- `,r+=`${P.bullet} **ID:** ${h.id}
17
- `,r+=`${P.bullet} **Problem:** ${h.problem}
18
- `,h.validated_fix&&(r+=`${P.bullet} **Solution:** ${h.validated_fix}
19
- `),h.why_it_worked&&(r+=`${P.bullet} **Why it worked:** ${h.why_it_worked}
20
- `),r+=`${P.bullet} **Confidence:** ${(h.confidence*100).toFixed(0)}%
21
- `,h.tags&&(r+=`${P.bullet} **Tags:** ${h.tags}
22
- `),r+="\n_Use `wyrm_recall` to retrieve similar memories, or `wyrm_context_build` to assemble a task brief._",r})}},{name:"wyrm_auto_capture",description:"Auto-extract durable memories from freeform text (a session transcript, notes, a chat) into the REVIEW QUEUE. A LOCAL extractor \u2014 a configurable Ollama model (WYRM_EXTRACT_MODEL / the `model` arg) if set, else a deterministic heuristic \u2014 pulls out truths, failures, decisions, patterns, and lessons as needs_review candidates; approve/reject with wyrm_review (nothing auto-trusted). Closes the 'just talk, it remembers' gap without a cloud LLM. Deduped + idempotent. Example: wyrm_auto_capture({ projectPath: '/home/user/api', text: 'Deploy failed twice because wrangler.toml pointed at the old KV id; switched to the new binding and decided to pin binding names in CI.' })",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Project to file the extracted candidates under"},text:{type:"string",description:"Freeform text to extract durable memories from"},model:{type:"string",description:"Optional Ollama model for extraction (overrides WYRM_EXTRACT_MODEL); omit for deterministic extraction"}},required:["projectPath","text"]},outputSchema:{type:"object",properties:{method:{type:"string"},model:{type:["string","null"]},added:{type:"integer"},skipped:{type:"integer"},candidates:{type:"array",items:{type:"object",properties:{kind:{type:"string"},text:{type:"string"}},required:["kind","text"]}}},required:["method","model","added","skipped","candidates"]},annotations:C.wyrm_auto_capture,aliases:[],handler:async(k,{store:v,raw:$,memory:m,cache:g})=>{const{projectPath:w,text:c,model:y}=k,o=v.getProject(w);if(!o)return R(A("wyrm_auto_capture","projectPath","project not found"));if(!c||c.trim().length<20)return R(A("wyrm_auto_capture","text","must be at least 20 characters"));const{candidates:f,method:n,model:u}=await O(c,{model:y}),a=$();let l=0,_=0;for(const e of f){const d=F(e),s=d.tags[d.tags.length-1];if(a.prepare("SELECT 1 FROM memory_artifacts WHERE project_id = ? AND tags LIKE ? ESCAPE '\\' LIMIT 1").get(o.id,"%"+W(s)+"%")){_++,M(a,{tool:"wyrm_auto_capture",outcome:"dropped",reason:"dedup \u2014 identical extraction signature already present",source:"auto-extract",projectId:o.id});continue}const b=m.add(o.id,{kind:d.kind,problem:d.problem,tags:d.tags,confidence:d.confidence,needsReview:1,createdBy:"auto-extract"});M(a,{tool:"wyrm_auto_capture",outcome:"queued",refTable:"mem",refId:b.id,reason:"needs_review \u2014 auto-extracted candidate; invisible to recall until wyrm_review",source:"auto-extract",projectId:o.id}),l++}l>0&&g.invalidate();const i={method:n,model:u??null,added:l,skipped:_,receipt:{outcome:l>0?"queued":"dropped",reason:`${l} queued for review, ${_} dropped as duplicates`},candidates:f.slice(0,8).map(e=>({kind:e.kind,text:e.text.slice(0,80)}))};return S(i,(e,d)=>{const s=e.candidates.map(b=>` ${d.bullet} [${b.kind}] ${b.text}`).join(`
23
- `);return T(d.brand,`Auto-capture (${e.method}${e.model?` - ${e.model}`:""}) -- ${e.added} candidate(s) -> review queue, ${e.skipped} already present.`)+`
24
- ${s}
25
-
26
- Approve/reject with wyrm_review.`})}},{name:"wyrm_capture_trace",description:"Harvest a harness's working trace into run-tagged review-queue candidates \u2014 OFFLINE. Ingests a Claude Code session transcript (JSONL), a ~/.dragon/traces file/dir, or WYRM_TRACE_TOOL_CALLS event output. The SAME local extractor as auto-capture (Ollama WYRM_EXTRACT_MODEL if set, else deterministic \u2014 never a cloud LLM) pulls durable truths/failures/decisions/patterns/lessons; secrets (API keys, tokens, passwords) are REDACTED before anything is stored; candidates land needs_review=1 stamped with the run_id (arg or ambient envelope). Approve/reject with wyrm_review; deduped + idempotent. Pass `trace` (inline text) OR `path` (a trace file or the ~/.dragon/traces dir). Reached via wyrm_capture({ mode: 'trace' }).",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Project to file the candidates under"},trace:{type:"string",description:"Inline trace text (JSONL transcript / dragon records / tool-call events)"},path:{type:"string",description:"Path to a trace file, or the ~/.dragon/traces directory (newest file is read); omit ~ for absolute"},format:{type:"string",enum:["auto","claude-jsonl","dragon","tool-calls"],description:"Trace format (default auto-detect)"},run_id:{type:"string",description:"Tag candidates with this run (default: ambient envelope run_id)"},model:{type:"string",description:"Optional Ollama model for extraction (overrides WYRM_EXTRACT_MODEL); omit for deterministic"}},required:["projectPath"]},outputSchema:{type:"object",properties:{method:{type:"string"},model:{type:["string","null"]},format:{type:"string"},source:{type:"string"},run_id:{type:["string","null"]},segments:{type:"integer"},added:{type:"integer"},skipped:{type:"integer"},candidates:{type:"array",items:{type:"object",properties:{kind:{type:"string"},text:{type:"string"}},required:["kind","text"]}}},required:["method","model","format","source","run_id","segments","added","skipped","candidates"]},annotations:C.wyrm_capture_trace,aliases:[],handler:async(k,{store:v,raw:$,memory:m,cache:g})=>{const{projectPath:w,trace:c,path:y,format:o,run_id:f,model:n}=k,u=v.getProject(w);if(!u)return R(A("wyrm_capture_trace","projectPath","project not found"));const a=G(),l=typeof f=="string"&&f.trim()?f.trim().slice(0,64):a.run_id;let _="",i="inline";if(typeof c=="string"&&c.trim())_=c,i="inline";else if(typeof y=="string"&&y.trim()){let p=y.trim();(p==="~"||p.startsWith("~/"))&&(p=U(ee(),p.slice(1)));try{if(L(p).isDirectory()){const I=Z(p).map(E=>U(p,E)).filter(E=>{try{return L(E).isFile()}catch{return!1}}).sort((E,H)=>L(H).mtimeMs-L(E).mtimeMs);if(I.length===0)return R(A("wyrm_capture_trace","path","no trace files in directory"));_=N(I[0],"utf-8"),i=I[0]}else _=N(p,"utf-8"),i=p}catch(j){return R(A("wyrm_capture_trace","path",`could not read trace path: ${j.message}`))}}else return R(A("wyrm_capture_trace","trace","provide inline trace or path to trace file"));const{segments:e,format:d}=Q(_,o??"auto"),s=X(e);if(s.trim().length<20){const p={method:"deterministic",model:null,format:d,source:i,run_id:l,segments:e.length,added:0,skipped:0,candidates:[]};return S(p,(j,I)=>T(I.brand,`Trace harvest (${j.format}) -- no durable content extracted from ${j.segments} segment(s).`))}const{candidates:b,method:t,model:h}=await O(s,{model:n}),P=$();let r=0,x=0;for(const p of b){const j=F(p),I=j.tags[j.tags.length-1];if(P.prepare("SELECT 1 FROM memory_artifacts WHERE project_id = ? AND tags LIKE ? ESCAPE '\\' LIMIT 1").get(u.id,"%"+W(I)+"%")){x++;continue}const E=l?[...j.tags,"trace",`run:${l}`]:[...j.tags,"trace"];V({agent_id:a.agent_id,run_id:l,source:l?"param":a.source},()=>m.add(u.id,{kind:j.kind,problem:j.problem,tags:E,confidence:j.confidence,needsReview:1,createdBy:"trace-harvest"})),r++}r>0&&g.invalidate();const q={method:t,model:h??null,format:d,source:i,run_id:l,segments:e.length,added:r,skipped:x,candidates:b.slice(0,8).map(p=>({kind:p.kind,text:p.text.slice(0,80)}))};return S(q,(p,j)=>{const I=p.candidates.map(E=>` ${j.bullet} [${E.kind}] ${E.text}`).join(`
27
- `);return T(j.brand,`Trace harvest (${p.format}, ${p.method}${p.model?` - ${p.model}`:""}) -- ${p.segments} segment(s) -> ${p.added} candidate(s) to review queue, ${p.skipped} already present${p.run_id?` [run:${p.run_id}]`:""}.`)+(I?`
28
- ${I}`:"")+`
29
-
30
- Approve/reject with wyrm_review.`})}},{name:"wyrm_distill",description:"Distill a work session into candidate memory artifacts for review. Parses the session's objectives, decisions, and outcomes into structured artifacts (lessons, patterns, anti-patterns) and queues them for approval via wyrm_review. Use at session end to extract institutional knowledge.",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Project root path"},sessionId:{type:"string",description:"Session ID to distill"},candidates:{type:"array",description:"Pre-parsed candidate artifacts to store for review",items:{type:"object",properties:{kind:{type:"string",enum:["lesson","pattern","anti_pattern","heuristic","reasoning_trace"]},title:{type:"string"},content:{type:"string"},tags:{type:"array",items:{type:"string"}},confidence:{type:"number"}},required:["kind","title","content"]}}},required:["projectPath","candidates"]},outputSchema:{type:"object",properties:{count:{type:"integer"},artifacts:{type:"array",items:{type:"object",properties:{id:{type:"integer"},kind:{type:"string"},title:{type:"string"}},required:["id","kind","title"]}}},required:["count","artifacts"]},annotations:C.wyrm_distill,aliases:[],handler:(k,{store:v,memory:$})=>{const{projectPath:m,candidates:g}=k,w=v.getProject(m);if(!w)return{content:[{type:"text",text:`Project not found: ${m}`}],isError:!0};if(!g||g.length===0)return{content:[{type:"text",text:`**Distill**
31
-
32
- No candidates provided. Provide an array of candidate artifacts to distill.`}],isError:!0};const c=[];for(const o of g){const f=$.add(w.id,{kind:o.kind,problem:o.title,validatedFix:o.content,tags:o.tags??[],confidence:o.confidence??.7,needsReview:1});c.push({id:f.id,kind:o.kind,title:o.title})}const y={count:c.length,artifacts:c};return S(y,(o,f)=>{let n=T(f.brand,`**Distillation Complete** -- ${o.count} artifact${o.count!==1?"s":""} queued for review`)+`
33
-
34
- `;return n+=o.artifacts.map(u=>`${f.bullet} [#${u.id}] **${u.kind}**: ${u.title}`).join(`
35
- `),n+="\n\nUse `wyrm_review` with each ID to approve or reject.",n})}},{name:"wyrm_harvest",description:"Auto-populate memory from artifacts you already produce. Walks a project (or ALL registered projects) and harvests durable facts from its docs (README/CLAUDE/AGENTS/ARCHITECTURE) plus recent commit subjects (git log) into the REVIEW QUEUE \u2014 approve/reject with wyrm_review, nothing is auto-trusted. Idempotent (re-runs skip what's already harvested). Use dryRun to preview. The fix for a thin corpus.",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Harvest one project; omit to harvest ALL registered projects"},dryRun:{type:"boolean",description:"Preview what would be harvested without writing anything"},gitLimit:{type:"number",description:"How many recent commits to scan per project (default 30, max 200)"},includeCode:{type:"boolean",description:"Also harvest CODE: tech-stack facts from manifests (package.json/Cargo.toml/\u2026) + TODO/FIXME markers into the review queue, AND index code symbols (functions/classes/types) for search. Heavier \u2014 scope to one project for the full code pass."}}},outputSchema:{type:"object",properties:{dry_run:{type:"boolean"},projects_scanned:{type:"integer"},added:{type:"integer"},skipped:{type:"integer"},top:{type:"array",items:{type:"object",properties:{project:{type:"string"},added:{type:"integer"},skipped:{type:"integer"}},required:["project","added","skipped"]}},sample:{type:"array",items:{type:"string"}},code_symbols:{type:["object","null"],properties:{symbols:{type:"integer"},projects:{type:"integer"},files:{type:"integer"}},required:["symbols","projects","files"]}},required:["dry_run","projects_scanned","added","skipped","top","sample","code_symbols"]},annotations:C.wyrm_harvest,aliases:[],handler:(k,{store:v,raw:$,memory:m,symbols:g,cache:w})=>{const{projectPath:c,dryRun:y,gitLimit:o,includeCode:f}=k,n=$(),u={existsBySig:(t,h)=>!!n.prepare("SELECT 1 FROM memory_artifacts WHERE project_id = ? AND tags LIKE ? ESCAPE '\\' LIMIT 1").get(t,"%"+W(h)+"%"),addCandidate:(t,h)=>{m.add(t,{kind:h.kind,problem:h.text,tags:[...h.tags,h.sig],confidence:h.confidence,needsReview:1,createdBy:"harvest"})}};let a;if(c){const t=v.getProject(c);if(!t)return{content:[{type:"text",text:"Project not found"}],isError:!0};a=[{id:t.id,name:t.name,path:t.path}]}else a=v.getAllProjects(500).map(t=>({id:t.id,name:t.name,path:t.path}));const{reports:l,totalAdded:_,totalSkipped:i}=z(u,a,{dryRun:y,gitLimit:o,includeCode:f});!y&&_>0&&w.invalidate();let e=null;if(f&&!y){let t=0,h=0,P=0;for(const r of a)try{const x=g.indexProject(r.id,r.path);t+=x.files,h+=x.symbols,P++}catch{}e={symbols:h,projects:P,files:t}}const d=l.filter(t=>t.added>0).sort((t,h)=>h.added-t.added).slice(0,15).map(t=>({project:t.project,added:t.added,skipped:t.skipped})),s=l.flatMap(t=>t.sample??[]).slice(0,6),b={dry_run:!!y,projects_scanned:a.length,added:_,skipped:i,top:d,sample:s,code_symbols:e};return S(b,(t,h)=>{const P=t.code_symbols?`
36
- Indexed ${t.code_symbols.symbols} code symbols across ${t.code_symbols.projects} project(s) (${t.code_symbols.files} files) -> searchable via wyrm_symbol_search.`:"",r=t.top.map(q=>` +${String(q.added).padStart(3)} (skip ${q.skipped}) ${q.project}`).join(`
37
- `),x=t.sample.map(q=>` ${h.bullet} ${q}`).join(`
38
- `);return T(h.brand,`Harvest ${t.dry_run?"(dry run) ":""}-- ${t.added} candidate(s) -> review queue across ${t.projects_scanned} project(s); ${t.skipped} already present.`)+`${P}
39
- ${r}`+(x?`
13
+ `)),I})}},{name:"wyrm_remember",description:"Store a distilled knowledge artifact \u2014 proven patterns, lessons learned, anti-patterns, and reasoning traces. These are recalled automatically by wyrm_context_build to help AI models apply past knowledge to new tasks. Example: wyrm_remember({ projectPath: '/home/user/api', kind: 'anti_pattern', problem: 'Jest suite hung after adding the SSE server', validatedFix: 'close() the server in afterAll \u2014 its keep-alive timer held the event loop open', whyItWorked: 'open handles block jest exit', outcome: 'positive', tags: ['jest', 'sse'] })",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Project this knowledge belongs to"},kind:{type:"string",enum:["reasoning_trace","lesson","pattern","anti_pattern","heuristic"],description:"reasoning_trace: a solved problem with steps; lesson: a general insight; pattern: a proven approach; anti_pattern: what NOT to do; heuristic: a rule of thumb"},problem:{type:"string",description:"What was being solved or observed (be specific and searchable)"},constraints:{type:"string",description:"What conditions or constraints applied"},validatedFix:{type:"string",description:"What actually worked \u2014 the validated solution or approach"},whyItWorked:{type:"string",description:"The insight behind WHY this worked (key for transfer learning)"},outcome:{type:"string",enum:["positive","negative","neutral"],description:"Was this approach successful?"},tags:{type:"array",items:{type:"string"},description:"Searchable tags (e.g. ['auth', 'rate-limit', 'typescript'])"},confidence:{type:"number",description:"Confidence level 0.0\u20131.0 (default: 1.0)"},sourceSessionId:{type:"number",description:"Session ID where this was discovered (optional)"}},required:["projectPath","kind","problem"]},outputSchema:{type:"object",properties:{id:{type:"integer"},kind:{type:"string",enum:["reasoning_trace","lesson","pattern","anti_pattern","heuristic"]},problem:{type:"string"},validated_fix:{type:["string","null"]},why_it_worked:{type:["string","null"]},confidence:{type:"number"},tags:{type:["string","null"]}},required:["id","kind","problem","validated_fix","why_it_worked","confidence","tags"]},annotations:C.wyrm_remember,aliases:[],handler:async(k,{store:_,memory:$,raw:y})=>{const{projectPath:m,kind:w,problem:a,constraints:f,outcome:d,tags:l,confidence:u,sourceSessionId:b}=k,i=k,t=i.validatedFix??i.validated_fix,g=i.whyItWorked??i.why_it_worked,r=Y("kind",w,["reasoning_trace","lesson","pattern","anti_pattern","heuristic"]);if(r===void 0)throw new J("kind","is required");const e=_.getProject(m);if(!e)return R(A("wyrm_remember","projectPath","project not found"));const c={kind:r,problem:a,constraints:f,validatedFix:t,whyItWorked:g,outcome:d,tags:l,confidence:u,sourceSessionId:b},n=await L("artifact_add",e.id,c,()=>$.add(e.id,c)),v=D(y(),{tool:"wyrm_remember",outcome:n.needs_review===1?"queued":"stored",refTable:"mem",refId:n.id,reason:n.needs_review===1?"needs_review \u2014 invisible to recall until approved (wyrm_review)":void 0,source:n.created_by,projectId:e.id}),s={id:n.id,kind:n.kind,problem:n.problem,validated_fix:n.validated_fix??null,why_it_worked:n.why_it_worked??null,confidence:n.confidence,tags:n.tags??null,receipt:v};return S(s,(h,P)=>{let o=E(P.brand,"**Memory Stored**")+`
14
+
15
+ `;return o+=`${P.bullet} **Kind:** ${h.kind}
16
+ `,o+=`${P.bullet} **ID:** ${h.id}
17
+ `,o+=`${P.bullet} **Problem:** ${h.problem}
18
+ `,h.validated_fix&&(o+=`${P.bullet} **Solution:** ${h.validated_fix}
19
+ `),h.why_it_worked&&(o+=`${P.bullet} **Why it worked:** ${h.why_it_worked}
20
+ `),o+=`${P.bullet} **Confidence:** ${(h.confidence*100).toFixed(0)}%
21
+ `,h.tags&&(o+=`${P.bullet} **Tags:** ${h.tags}
22
+ `),o+="\n_Use `wyrm_recall` to retrieve similar memories, or `wyrm_context_build` to assemble a task brief._",o})}},{name:"wyrm_auto_capture",description:"Auto-extract durable memories from freeform text (a session transcript, notes, a chat) into the REVIEW QUEUE. A LOCAL extractor \u2014 a configurable Ollama model (WYRM_EXTRACT_MODEL / the `model` arg) if set, else a deterministic heuristic \u2014 pulls out truths, failures, decisions, patterns, and lessons as needs_review candidates; approve/reject with wyrm_review (nothing auto-trusted). Closes the 'just talk, it remembers' gap without a cloud LLM. Deduped + idempotent. Example: wyrm_auto_capture({ projectPath: '/home/user/api', text: 'Deploy failed twice because wrangler.toml pointed at the old KV id; switched to the new binding and decided to pin binding names in CI.' })",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Project to file the extracted candidates under"},text:{type:"string",description:"Freeform text to extract durable memories from"},model:{type:"string",description:"Optional Ollama model for extraction (overrides WYRM_EXTRACT_MODEL); omit for deterministic extraction"}},required:["projectPath","text"]},outputSchema:{type:"object",properties:{method:{type:"string"},model:{type:["string","null"]},added:{type:"integer"},skipped:{type:"integer"},candidates:{type:"array",items:{type:"object",properties:{kind:{type:"string"},text:{type:"string"}},required:["kind","text"]}}},required:["method","model","added","skipped","candidates"]},annotations:C.wyrm_auto_capture,aliases:[],handler:async(k,{store:_,raw:$,memory:y,cache:m})=>{const{projectPath:w,text:a,model:f}=k,d=_.getProject(w);if(!d)return R(A("wyrm_auto_capture","projectPath","project not found"));if(!a||a.trim().length<20)return R(A("wyrm_auto_capture","text","must be at least 20 characters"));const{candidates:l,method:u,model:b}=await O(a,{model:f}),i=$();let t=0,g=0;for(const e of l){const c=F(e),n=c.tags[c.tags.length-1];if(i.prepare("SELECT 1 FROM memory_artifacts WHERE project_id = ? AND tags LIKE ? ESCAPE '\\' LIMIT 1").get(d.id,"%"+M(n)+"%")){g++,D(i,{tool:"wyrm_auto_capture",outcome:"dropped",reason:"dedup \u2014 identical extraction signature already present",source:"auto-extract",projectId:d.id});continue}const v=y.add(d.id,{kind:c.kind,problem:c.problem,tags:c.tags,confidence:c.confidence,needsReview:1,createdBy:"auto-extract"});D(i,{tool:"wyrm_auto_capture",outcome:"queued",refTable:"mem",refId:v.id,reason:"needs_review \u2014 auto-extracted candidate; invisible to recall until wyrm_review",source:"auto-extract",projectId:d.id}),t++}t>0&&m.invalidate();const r={method:u,model:b??null,added:t,skipped:g,receipt:{outcome:t>0?"queued":"dropped",reason:`${t} queued for review, ${g} dropped as duplicates`},candidates:l.slice(0,8).map(e=>({kind:e.kind,text:e.text.slice(0,80)}))};return S(r,(e,c)=>{const n=e.candidates.map(v=>` ${c.bullet} [${v.kind}] ${v.text}`).join(`
23
+ `);return E(c.brand,`Auto-capture (${e.method}${e.model?` - ${e.model}`:""}) -- ${e.added} candidate(s) -> review queue, ${e.skipped} already present.`)+`
24
+ ${n}
25
+
26
+ Approve/reject with wyrm_review.`})}},{name:"wyrm_capture_trace",description:"Harvest a harness's working trace into run-tagged review-queue candidates \u2014 OFFLINE. Ingests a Claude Code session transcript (JSONL), a ~/.dragon/traces file/dir, or WYRM_TRACE_TOOL_CALLS event output. The SAME local extractor as auto-capture (Ollama WYRM_EXTRACT_MODEL if set, else deterministic \u2014 never a cloud LLM) pulls durable truths/failures/decisions/patterns/lessons; secrets (API keys, tokens, passwords) are REDACTED before anything is stored; candidates land needs_review=1 stamped with the run_id (arg or ambient envelope). Approve/reject with wyrm_review; deduped + idempotent. Pass `trace` (inline text) OR `path` (a trace file or the ~/.dragon/traces dir). Reached via wyrm_capture({ mode: 'trace' }).",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Project to file the candidates under"},trace:{type:"string",description:"Inline trace text (JSONL transcript / dragon records / tool-call events)"},path:{type:"string",description:"Path to a trace file, or the ~/.dragon/traces directory (newest file is read); omit ~ for absolute"},format:{type:"string",enum:["auto","claude-jsonl","dragon","tool-calls"],description:"Trace format (default auto-detect)"},run_id:{type:"string",description:"Tag candidates with this run (default: ambient envelope run_id)"},model:{type:"string",description:"Optional Ollama model for extraction (overrides WYRM_EXTRACT_MODEL); omit for deterministic"}},required:["projectPath"]},outputSchema:{type:"object",properties:{method:{type:"string"},model:{type:["string","null"]},format:{type:"string"},source:{type:"string"},run_id:{type:["string","null"]},segments:{type:"integer"},added:{type:"integer"},skipped:{type:"integer"},candidates:{type:"array",items:{type:"object",properties:{kind:{type:"string"},text:{type:"string"}},required:["kind","text"]}}},required:["method","model","format","source","run_id","segments","added","skipped","candidates"]},annotations:C.wyrm_capture_trace,aliases:[],handler:async(k,{store:_,raw:$,memory:y,cache:m})=>{const{projectPath:w,trace:a,path:f,format:d,run_id:l,model:u}=k,b=_.getProject(w);if(!b)return R(A("wyrm_capture_trace","projectPath","project not found"));const i=V(),t=typeof l=="string"&&l.trim()?l.trim().slice(0,64):i.run_id;let g="",r="inline";if(typeof a=="string"&&a.trim())g=a,r="inline";else if(typeof f=="string"&&f.trim()){let p=f.trim();(p==="~"||p.startsWith("~/"))&&(p=U(te(),p.slice(1)));try{if(W(p).isDirectory()){const q=ee(p).map(T=>U(p,T)).filter(T=>{try{return W(T).isFile()}catch{return!1}}).sort((T,H)=>W(H).mtimeMs-W(T).mtimeMs);if(q.length===0)return R(A("wyrm_capture_trace","path","no trace files in directory"));g=N(q[0],"utf-8"),r=q[0]}else g=N(p,"utf-8"),r=p}catch(j){return R(A("wyrm_capture_trace","path",`could not read trace path: ${j.message}`))}}else return R(A("wyrm_capture_trace","trace","provide inline trace or path to trace file"));const{segments:e,format:c}=X(g,d??"auto"),n=G(e);if(n.trim().length<20){const p={method:"deterministic",model:null,format:c,source:r,run_id:t,segments:e.length,added:0,skipped:0,candidates:[]};return S(p,(j,q)=>E(q.brand,`Trace harvest (${j.format}) -- no durable content extracted from ${j.segments} segment(s).`))}const{candidates:v,method:s,model:h}=await O(n,{model:u}),P=$();let o=0,x=0;for(const p of v){const j=F(p),q=j.tags[j.tags.length-1];if(P.prepare("SELECT 1 FROM memory_artifacts WHERE project_id = ? AND tags LIKE ? ESCAPE '\\' LIMIT 1").get(b.id,"%"+M(q)+"%")){x++;continue}const T=t?[...j.tags,"trace",`run:${t}`]:[...j.tags,"trace"];z({agent_id:i.agent_id,run_id:t,source:t?"param":i.source},()=>y.add(b.id,{kind:j.kind,problem:j.problem,tags:T,confidence:j.confidence,needsReview:1,createdBy:"trace-harvest"})),o++}o>0&&m.invalidate();const I={method:s,model:h??null,format:c,source:r,run_id:t,segments:e.length,added:o,skipped:x,candidates:v.slice(0,8).map(p=>({kind:p.kind,text:p.text.slice(0,80)}))};return S(I,(p,j)=>{const q=p.candidates.map(T=>` ${j.bullet} [${T.kind}] ${T.text}`).join(`
27
+ `);return E(j.brand,`Trace harvest (${p.format}, ${p.method}${p.model?` - ${p.model}`:""}) -- ${p.segments} segment(s) -> ${p.added} candidate(s) to review queue, ${p.skipped} already present${p.run_id?` [run:${p.run_id}]`:""}.`)+(q?`
28
+ ${q}`:"")+`
29
+
30
+ Approve/reject with wyrm_review.`})}},{name:"wyrm_distill",description:"Distill a work session into candidate memory artifacts for review. Parses the session's objectives, decisions, and outcomes into structured artifacts (lessons, patterns, anti-patterns) and queues them for approval via wyrm_review. Use at session end to extract institutional knowledge.",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Project root path"},sessionId:{type:"string",description:"Session ID to distill"},candidates:{type:"array",description:"Pre-parsed candidate artifacts to store for review",items:{type:"object",properties:{kind:{type:"string",enum:["lesson","pattern","anti_pattern","heuristic","reasoning_trace"]},title:{type:"string"},content:{type:"string"},tags:{type:"array",items:{type:"string"}},confidence:{type:"number"}},required:["kind","title","content"]}}},required:["projectPath","candidates"]},outputSchema:{type:"object",properties:{count:{type:"integer"},artifacts:{type:"array",items:{type:"object",properties:{id:{type:"integer"},kind:{type:"string"},title:{type:"string"}},required:["id","kind","title"]}}},required:["count","artifacts"]},annotations:C.wyrm_distill,aliases:[],handler:(k,{store:_,memory:$})=>{const{projectPath:y,candidates:m}=k,w=_.getProject(y);if(!w)return{content:[{type:"text",text:`Project not found: ${y}`}],isError:!0};if(!m||m.length===0)return{content:[{type:"text",text:`**Distill**
31
+
32
+ No candidates provided. Provide an array of candidate artifacts to distill.`}],isError:!0};const a=[];for(const d of m){const l=$.add(w.id,{kind:d.kind,problem:d.title,validatedFix:d.content,tags:d.tags??[],confidence:d.confidence??.7,needsReview:1});a.push({id:l.id,kind:d.kind,title:d.title})}const f={count:a.length,artifacts:a};return S(f,(d,l)=>{let u=E(l.brand,`**Distillation Complete** -- ${d.count} artifact${d.count!==1?"s":""} queued for review`)+`
33
+
34
+ `;return u+=d.artifacts.map(b=>`${l.bullet} [#${b.id}] **${b.kind}**: ${b.title}`).join(`
35
+ `),u+="\n\nUse `wyrm_review` with each ID to approve or reject.",u})}},{name:"wyrm_harvest",description:"Auto-populate memory from artifacts you already produce. Walks a project (or ALL registered projects) and harvests durable facts from its docs (README/CLAUDE/AGENTS/ARCHITECTURE) plus recent commit subjects (git log) into the REVIEW QUEUE \u2014 approve/reject with wyrm_review, nothing is auto-trusted. Idempotent (re-runs skip what's already harvested). Use dryRun to preview. The fix for a thin corpus.",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Harvest one project; omit to harvest ALL registered projects"},dryRun:{type:"boolean",description:"Preview what would be harvested without writing anything"},gitLimit:{type:"number",description:"How many recent commits to scan per project (default 30, max 200)"},includeCode:{type:"boolean",description:"Also harvest CODE: tech-stack facts from manifests (package.json/Cargo.toml/\u2026) + TODO/FIXME markers into the review queue, AND index code symbols (functions/classes/types) for search. Heavier \u2014 scope to one project for the full code pass."}}},outputSchema:{type:"object",properties:{dry_run:{type:"boolean"},projects_scanned:{type:"integer"},added:{type:"integer"},skipped:{type:"integer"},top:{type:"array",items:{type:"object",properties:{project:{type:"string"},added:{type:"integer"},skipped:{type:"integer"}},required:["project","added","skipped"]}},sample:{type:"array",items:{type:"string"}},code_symbols:{type:["object","null"],properties:{symbols:{type:"integer"},projects:{type:"integer"},files:{type:"integer"}},required:["symbols","projects","files"]}},required:["dry_run","projects_scanned","added","skipped","top","sample","code_symbols"]},annotations:C.wyrm_harvest,aliases:[],handler:(k,{store:_,raw:$,memory:y,symbols:m,cache:w})=>{const{projectPath:a,dryRun:f,gitLimit:d,includeCode:l}=k,u=$(),b={existsBySig:(s,h)=>!!u.prepare("SELECT 1 FROM memory_artifacts WHERE project_id = ? AND tags LIKE ? ESCAPE '\\' LIMIT 1").get(s,"%"+M(h)+"%"),addCandidate:(s,h)=>{y.add(s,{kind:h.kind,problem:h.text,tags:[...h.tags,h.sig],confidence:h.confidence,needsReview:1,createdBy:"harvest"})}};let i;if(a){const s=_.getProject(a);if(!s)return{content:[{type:"text",text:"Project not found"}],isError:!0};i=[{id:s.id,name:s.name,path:s.path}]}else i=_.getAllProjects(500).map(s=>({id:s.id,name:s.name,path:s.path}));const{reports:t,totalAdded:g,totalSkipped:r}=Z(b,i,{dryRun:f,gitLimit:d,includeCode:l});!f&&g>0&&w.invalidate();let e=null;if(l&&!f){let s=0,h=0,P=0;for(const o of i)try{const x=m.indexProject(o.id,o.path);s+=x.files,h+=x.symbols,P++}catch{}e={symbols:h,projects:P,files:s}}const c=t.filter(s=>s.added>0).sort((s,h)=>h.added-s.added).slice(0,15).map(s=>({project:s.project,added:s.added,skipped:s.skipped})),n=t.flatMap(s=>s.sample??[]).slice(0,6),v={dry_run:!!f,projects_scanned:i.length,added:g,skipped:r,top:c,sample:n,code_symbols:e};return S(v,(s,h)=>{const P=s.code_symbols?`
36
+ Indexed ${s.code_symbols.symbols} code symbols across ${s.code_symbols.projects} project(s) (${s.code_symbols.files} files) -> searchable via wyrm_symbol_search.`:"",o=s.top.map(I=>` +${String(I.added).padStart(3)} (skip ${I.skipped}) ${I.project}`).join(`
37
+ `),x=s.sample.map(I=>` ${h.bullet} ${I}`).join(`
38
+ `);return E(h.brand,`Harvest ${s.dry_run?"(dry run) ":""}-- ${s.added} candidate(s) -> review queue across ${s.projects_scanned} project(s); ${s.skipped} already present.`)+`${P}
39
+ ${o}`+(x?`
40
40
 
41
41
  Sample:
42
42
  ${x}`:"")+`
43
43
 
44
- Approve/reject with wyrm_review.`})}},{name:"wyrm_import_git",description:"Import a list of commits into the Wyrm memory review queue. Useful for onboarding a project's history or capturing recent work.",inputSchema:{type:"object",properties:{project_id:{type:"number",description:"Project ID"},commits:{type:"array",description:"Array of commit objects",items:{type:"object",properties:{message:{type:"string"},diff_summary:{type:"string"},author:{type:"string"},date:{type:"string"}},required:["message"]}},auto_approve:{type:"boolean",description:"Skip review queue and activate immediately (default: false)"}},required:["project_id","commits"]},outputSchema:{type:"object",properties:{captured:{type:"integer"},skipped:{type:"integer"},auto_approved:{type:"boolean"}},required:["captured","skipped","auto_approved"]},annotations:C.wyrm_import_git,aliases:[],handler:(k,{store:v,memory:$})=>{const{project_id:m,commits:g,auto_approve:w}=k;if(!v.getProjectById(m))return{content:[{type:"text",text:`Project ID ${m} not found.`}],isError:!0};let y=0,o=0;for(const n of g){const u=n.message??"";if(/^Merge /i.test(u)){o++;continue}if(/^(chore|bump|release|version)/i.test(u)){o++;continue}let a="pattern";/^fix(\(.+\))?:/i.test(u)?a="lesson":/^feat(\(.+\))?:/i.test(u)?a="pattern":/^refactor(\(.+\))?:/i.test(u)&&(a="heuristic");const l=u.split(":")[0]??"commit";$.add(m,{kind:a,problem:u,validatedFix:n.diff_summary??"",whyItWorked:`Committed by ${n.author??"unknown"} on ${n.date??"unknown"}`,tags:["git","commit",l.toLowerCase()],confidence:.6,needsReview:w?0:1}),y++}return S({captured:y,skipped:o,auto_approved:!!w},(n,u)=>T(u.brand,"**Git Import Complete**")+`
44
+ Approve/reject with wyrm_review.`})}},{name:"wyrm_import_git",description:"Import a list of commits into the Wyrm memory review queue. Useful for onboarding a project's history or capturing recent work.",inputSchema:{type:"object",properties:{project_id:{type:"number",description:"Project ID"},commits:{type:"array",description:"Array of commit objects",items:{type:"object",properties:{message:{type:"string"},diff_summary:{type:"string"},author:{type:"string"},date:{type:"string"}},required:["message"]}},auto_approve:{type:"boolean",description:"Skip review queue and activate immediately (default: false)"}},required:["project_id","commits"]},outputSchema:{type:"object",properties:{captured:{type:"integer"},skipped:{type:"integer"},auto_approved:{type:"boolean"}},required:["captured","skipped","auto_approved"]},annotations:C.wyrm_import_git,aliases:[],handler:(k,{store:_,memory:$,raw:y})=>{const{project_id:m,commits:w,auto_approve:a}=k;if(!_.getProjectById(m))return{content:[{type:"text",text:`Project ID ${m} not found.`}],isError:!0};let d=0,l=0;const u=y();for(const i of w){const t=i.message??"";if(/^Merge /i.test(t)){l++;continue}if(/^(chore|bump|release|version)/i.test(t)){l++;continue}const g="gi:"+K("sha1").update(`${t}
45
+ ${i.diff_summary??""}`).digest("hex").slice(0,16);if(u.prepare("SELECT 1 FROM memory_artifacts WHERE project_id = ? AND tags LIKE ? ESCAPE '\\' LIMIT 1").get(m,"%"+M(g)+"%")){l++,D(u,{tool:"wyrm_import_git",outcome:"dropped",reason:"dedup \u2014 this commit was already imported (gi: signature match)",source:"import:git",projectId:m});continue}let r="pattern";/^fix(\(.+\))?:/i.test(t)?r="lesson":/^feat(\(.+\))?:/i.test(t)?r="pattern":/^refactor(\(.+\))?:/i.test(t)&&(r="heuristic");const e=t.split(":")[0]??"commit",c=$.add(m,{kind:r,problem:t,validatedFix:i.diff_summary??"",whyItWorked:`Committed by ${i.author??"unknown"} on ${i.date??"unknown"}`,tags:["git","commit",e.toLowerCase(),g],confidence:.6,needsReview:a?0:1});D(u,{tool:"wyrm_import_git",outcome:a?"stored":"queued",refTable:"mem",refId:c.id,reason:a?void 0:"needs_review \u2014 imported trust lane; invisible to recall until wyrm_review",source:"import:git",projectId:m}),d++}return S({captured:d,skipped:l,auto_approved:!!a},(i,t)=>E(t.brand,"**Git Import Complete**")+`
45
46
 
46
- ${u.bullet} **Captured:** ${n.captured}
47
- ${u.bullet} **Skipped:** ${n.skipped} (merges/chores)
48
- ${u.bullet} **Review needed:** ${n.auto_approved?"No (auto-approved)":`Yes -- run \`wyrm_review\` to activate ${n.captured} artifact${n.captured!==1?"s":""}`}`)}},{name:"wyrm_import_pr",description:"Import a pull request (title, body, review comments) into the Wyrm memory review queue to extract patterns and heuristics.",inputSchema:{type:"object",properties:{project_id:{type:"number",description:"Project ID"},title:{type:"string",description:"PR title"},body:{type:"string",description:"PR description / body"},review_comments:{type:"array",items:{type:"string"},description:"Review comment strings"},auto_approve:{type:"boolean",description:"Skip review queue (default: false)"}},required:["project_id","title","body"]},outputSchema:{type:"object",properties:{created:{type:"integer"},auto_approved:{type:"boolean"}},required:["created","auto_approved"]},annotations:C.wyrm_import_pr,aliases:[],handler:(k,{store:v,memory:$})=>{const{project_id:m,title:g,body:w,review_comments:c,auto_approve:y}=k;if(!v.getProjectById(m))return{content:[{type:"text",text:`Project ID ${m} not found.`}],isError:!0};let f=0;const n=y?0:1;$.add(m,{kind:"pattern",problem:`${g}
47
+ ${t.bullet} **Captured:** ${i.captured}
48
+ ${t.bullet} **Skipped:** ${i.skipped} (merges/chores)
49
+ ${t.bullet} **Review needed:** ${i.auto_approved?"No (auto-approved)":`Yes -- run \`wyrm_review\` to activate ${i.captured} artifact${i.captured!==1?"s":""}`}`)}},{name:"wyrm_import_pr",description:"Import a pull request (title, body, review comments) into the Wyrm memory review queue to extract patterns and heuristics.",inputSchema:{type:"object",properties:{project_id:{type:"number",description:"Project ID"},title:{type:"string",description:"PR title"},body:{type:"string",description:"PR description / body"},review_comments:{type:"array",items:{type:"string"},description:"Review comment strings"},auto_approve:{type:"boolean",description:"Skip review queue (default: false)"}},required:["project_id","title","body"]},outputSchema:{type:"object",properties:{created:{type:"integer"},auto_approved:{type:"boolean"}},required:["created","auto_approved"]},annotations:C.wyrm_import_pr,aliases:[],handler:(k,{store:_,memory:$})=>{const{project_id:y,title:m,body:w,review_comments:a,auto_approve:f}=k;if(!_.getProjectById(y))return{content:[{type:"text",text:`Project ID ${y} not found.`}],isError:!0};let l=0;const u=f?0:1;$.add(y,{kind:"pattern",problem:`${m}
49
50
 
50
- ${w}`,validatedFix:"",whyItWorked:"",tags:["git","pr"],confidence:.65,needsReview:n}),f++;for(const a of c??[])/\b(should|must|always|never|prefer|avoid|use|don't)\b/i.test(a)&&($.add(m,{kind:"heuristic",problem:a,validatedFix:"",whyItWorked:"",tags:["git","pr","review"],confidence:.65,needsReview:n}),f++);return S({created:f,auto_approved:!!y},(a,l)=>T(l.brand,"**PR Import Complete**")+`
51
+ ${w}`,validatedFix:"",whyItWorked:"",tags:["git","pr"],confidence:.65,needsReview:u}),l++;for(const i of a??[])/\b(should|must|always|never|prefer|avoid|use|don't)\b/i.test(i)&&($.add(y,{kind:"heuristic",problem:i,validatedFix:"",whyItWorked:"",tags:["git","pr","review"],confidence:.65,needsReview:u}),l++);return S({created:l,auto_approved:!!f},(i,t)=>E(t.brand,"**PR Import Complete**")+`
51
52
 
52
- ${l.bullet} **Artifacts created:** ${a.created}
53
- ${l.bullet} **Review needed:** ${a.auto_approved?"No (auto-approved)":"Yes -- run `wyrm_review` to activate"}`)}},{name:"wyrm_import_rules",description:"Import a .cursorrules / copilot-instructions file into Wyrm. Parses by section and creates ground truths (for constraint/rule language) or memory artifacts (for general guidelines).",inputSchema:{type:"object",properties:{project_id:{type:"number",description:"Project ID"},content:{type:"string",description:"Full text of the rules file"},format:{type:"string",enum:["cursorrules","copilot","plain"],description:"Source format (default: plain)"},source_file:{type:"string",description:"Filename for provenance (optional)"}},required:["project_id","content"]},outputSchema:{type:"object",properties:{format:{type:"string"},source:{type:"string"},truths_created:{type:"integer"},artifacts_created:{type:"integer"}},required:["format","source","truths_created","artifacts_created"]},annotations:C.wyrm_import_rules,aliases:[],handler:(k,{store:v,memory:$,truths:m})=>{const{project_id:g,content:w,format:c,source_file:y}=k;if(!v.getProjectById(g))return{content:[{type:"text",text:`Project ID ${g} not found.`}],isError:!0};const f=c??"plain",n=y??"rules",u=["imported",f,n],a=w.split(/\n(?=#)/),l=w.split(/\n\n+/),_=(a.length>=l.length?a:l).map(s=>s.trim()).filter(s=>s.length>=15);let i=0,e=0;for(const s of _)/\b(always|never|must|use|don't|avoid|prefer)\b/i.test(s)?(m.set(g,{category:"constraint",key:s.slice(0,50).replace(/\n/g," "),value:s,source:n}),i++):($.add(g,{kind:"heuristic",problem:s,validatedFix:"",whyItWorked:"",tags:u,confidence:.7,needsReview:1}),e++);return S({format:f,source:n,truths_created:i,artifacts_created:e},(s,b)=>T(b.brand,`**Rules Import Complete** (${s.format})`)+`
53
+ ${t.bullet} **Artifacts created:** ${i.created}
54
+ ${t.bullet} **Review needed:** ${i.auto_approved?"No (auto-approved)":"Yes -- run `wyrm_review` to activate"}`)}},{name:"wyrm_import_rules",description:"Import a .cursorrules / copilot-instructions file into Wyrm. Parses by section and creates ground truths (for constraint/rule language) or memory artifacts (for general guidelines).",inputSchema:{type:"object",properties:{project_id:{type:"number",description:"Project ID"},content:{type:"string",description:"Full text of the rules file"},format:{type:"string",enum:["cursorrules","copilot","plain"],description:"Source format (default: plain)"},source_file:{type:"string",description:"Filename for provenance (optional)"}},required:["project_id","content"]},outputSchema:{type:"object",properties:{format:{type:"string"},source:{type:"string"},truths_created:{type:"integer"},artifacts_created:{type:"integer"}},required:["format","source","truths_created","artifacts_created"]},annotations:C.wyrm_import_rules,aliases:[],handler:(k,{store:_,memory:$,truths:y})=>{const{project_id:m,content:w,format:a,source_file:f}=k;if(!_.getProjectById(m))return{content:[{type:"text",text:`Project ID ${m} not found.`}],isError:!0};const l=a??"plain",u=f??"rules",b=["imported",l,u],i=w.split(/\n(?=#)/),t=w.split(/\n\n+/),g=(i.length>=t.length?i:t).map(n=>n.trim()).filter(n=>n.length>=15);let r=0,e=0;for(const n of g)/\b(always|never|must|use|don't|avoid|prefer)\b/i.test(n)?(y.set(m,{category:"constraint",key:n.slice(0,50).replace(/\n/g," "),value:n,source:u}),r++):($.add(m,{kind:"heuristic",problem:n,validatedFix:"",whyItWorked:"",tags:b,confidence:.7,needsReview:1}),e++);return S({format:l,source:u,truths_created:r,artifacts_created:e},(n,v)=>E(v.brand,`**Rules Import Complete** (${n.format})`)+`
54
55
 
55
- ${b.bullet} **Ground truths created:** ${s.truths_created} (active immediately)
56
- ${b.bullet} **Memory artifacts created:** ${s.artifacts_created} (pending review)
57
- ${b.bullet} **Source:** ${s.source}
56
+ ${v.bullet} **Ground truths created:** ${n.truths_created} (active immediately)
57
+ ${v.bullet} **Memory artifacts created:** ${n.artifacts_created} (pending review)
58
+ ${v.bullet} **Source:** ${n.source}
58
59
 
59
- Run \`wyrm_review\` to approve the memory artifacts.`)}},{name:"wyrm_spec_register",description:"Make spec-kit Wyrm-native. Reads a spec directory (spec.md title/summary + tasks.md task list) and creates one Wyrm quest per task, linked to a project, with a stored spec\u2192project link. Idempotent \u2014 re-running updates the same quests (deduped by a per-spec-task signature) instead of duplicating.",inputSchema:{type:"object",properties:{specDir:{type:"string",description:"Absolute path to the spec directory (e.g. '.../dragon-platform/specs/001-ghostmesh')"},projectPath:{type:"string",description:"Path of the Wyrm project to attach the spec + quests to (must already be registered)"},priority:{type:"string",enum:["critical","high","medium","low"],description:"Priority for the generated quests (default 'medium')"}},required:["specDir","projectPath"]},outputSchema:{type:"object",properties:{spec_dir:{type:"string"},title:{type:["string","null"]},project:{type:"string"},tasks_parsed:{type:"integer"},quests_created:{type:"integer"},quests_updated:{type:"integer"},quest_ids:{type:"array",items:{type:"integer"}}},required:["spec_dir","title","project","tasks_parsed","quests_created","quests_updated","quest_ids"]},annotations:C.wyrm_spec_register,aliases:[],handler:(k,{store:v,indexing:$})=>{const{specDir:m,projectPath:g,priority:w}=k,c=["specDir","projectPath"].filter(i=>{const e=k[i];return!e&&e!==0&&e!==!1});if(c.length>0)return{content:[{type:"text",text:`Missing required arguments: ${c.join(", ")}`}],isError:!0};const y=v.getProject(g);if(!y)return{content:[{type:"text",text:`**Spec Register**: Project not found at "${g}". Register it first (wyrm_scan_projects / wyrm_session_start).`}],isError:!0};const o=te(m);if(o.tasks.length===0){const i={spec_dir:m,title:o.title??null,project:y.name,tasks_parsed:0,quests_created:0,quests_updated:0,quest_ids:[]};return S(i,(e,d)=>T(d.brand,`**Spec Register**: No tasks found in ${B(e.spec_dir,"tasks.md")}. Nothing to create.`))}const f=w||"medium";let n=0,u=0;const a=[];for(const i of o.tasks){const e=re(m,i.id),d=`${i.id}: ${i.title}`,s=e,b=v.findQuestBySpecSignature(y.id,e);if(b)v.updateQuestFields(b.id,{title:d,tags:s}),a.push(b.id),u++;else{const t=v.addQuest(y.id,d,`spec-kit task from ${m}`,f,s);$()?.enqueue("quest",t.id,y.id),a.push(t.id),n++}}const l=v.upsertSpec(y.id,m,o.title,o.summary,o.tasks.length),_={spec_dir:l.spec_dir,title:l.title??null,project:y.name,tasks_parsed:o.tasks.length,quests_created:n,quests_updated:u,quest_ids:a};return S(_,(i,e)=>{let d=T(e.brand,"**Spec Registered**")+`
60
+ Run \`wyrm_review\` to approve the memory artifacts.`)}},{name:"wyrm_spec_register",description:"Make spec-kit Wyrm-native. Reads a spec directory (spec.md title/summary + tasks.md task list) and creates one Wyrm quest per task, linked to a project, with a stored spec\u2192project link. Idempotent \u2014 re-running updates the same quests (deduped by a per-spec-task signature) instead of duplicating.",inputSchema:{type:"object",properties:{specDir:{type:"string",description:"Absolute path to the spec directory (e.g. '.../dragon-platform/specs/001-ghostmesh')"},projectPath:{type:"string",description:"Path of the Wyrm project to attach the spec + quests to (must already be registered)"},priority:{type:"string",enum:["critical","high","medium","low"],description:"Priority for the generated quests (default 'medium')"}},required:["specDir","projectPath"]},outputSchema:{type:"object",properties:{spec_dir:{type:"string"},title:{type:["string","null"]},project:{type:"string"},tasks_parsed:{type:"integer"},quests_created:{type:"integer"},quests_updated:{type:"integer"},quest_ids:{type:"array",items:{type:"integer"}}},required:["spec_dir","title","project","tasks_parsed","quests_created","quests_updated","quest_ids"]},annotations:C.wyrm_spec_register,aliases:[],handler:(k,{store:_,indexing:$})=>{const{specDir:y,projectPath:m,priority:w}=k,a=["specDir","projectPath"].filter(r=>{const e=k[r];return!e&&e!==0&&e!==!1});if(a.length>0)return{content:[{type:"text",text:`Missing required arguments: ${a.join(", ")}`}],isError:!0};const f=_.getProject(m);if(!f)return{content:[{type:"text",text:`**Spec Register**: Project not found at "${m}". Register it first (wyrm_scan_projects / wyrm_session_start).`}],isError:!0};const d=re(y);if(d.tasks.length===0){const r={spec_dir:y,title:d.title??null,project:f.name,tasks_parsed:0,quests_created:0,quests_updated:0,quest_ids:[]};return S(r,(e,c)=>E(c.brand,`**Spec Register**: No tasks found in ${B(e.spec_dir,"tasks.md")}. Nothing to create.`))}const l=w||"medium";let u=0,b=0;const i=[];for(const r of d.tasks){const e=ie(y,r.id),c=`${r.id}: ${r.title}`,n=e,v=_.findQuestBySpecSignature(f.id,e);if(v)_.updateQuestFields(v.id,{title:c,tags:n}),i.push(v.id),b++;else{const s=_.addQuest(f.id,c,`spec-kit task from ${y}`,l,n);$()?.enqueue("quest",s.id,f.id),i.push(s.id),u++}}const t=_.upsertSpec(f.id,y,d.title,d.summary,d.tasks.length),g={spec_dir:t.spec_dir,title:t.title??null,project:f.name,tasks_parsed:d.tasks.length,quests_created:u,quests_updated:b,quest_ids:i};return S(g,(r,e)=>{let c=E(e.brand,"**Spec Registered**")+`
60
61
 
61
- `;return d+=`Spec: ${i.title||"(untitled)"}
62
- `,l.summary&&(d+=`Summary: ${l.summary}
63
- `),d+=`Dir: ${i.spec_dir}
64
- `,d+=`Project: ${i.project}
65
- `,d+=`Tasks parsed: ${i.tasks_parsed}
66
- `,d+=`Quests created: ${i.quests_created} - updated: ${i.quests_updated}
67
- `,d+=`
62
+ `;return c+=`Spec: ${r.title||"(untitled)"}
63
+ `,t.summary&&(c+=`Summary: ${t.summary}
64
+ `),c+=`Dir: ${r.spec_dir}
65
+ `,c+=`Project: ${r.project}
66
+ `,c+=`Tasks parsed: ${r.tasks_parsed}
67
+ `,c+=`Quests created: ${r.quests_created} - updated: ${r.quests_updated}
68
+ `,c+=`
68
69
  \`\`\`json
69
- ${JSON.stringify({spec:i.spec_dir,title:i.title,project:i.project,tasksParsed:i.tasks_parsed,questsCreated:i.quests_created,questsUpdated:i.quests_updated,questIds:i.quest_ids},null,2)}
70
- \`\`\``,d})}},{name:"wyrm_scaffold_save",description:"Save a reasoning scaffold \u2014 a structured checklist that guides step-by-step thinking for a specific problem type. Scaffolds are automatically surfaced by wyrm_context_build when the task matches, helping AI models apply proven reasoning patterns.",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Project path (optional, null for global scaffolds)"},problemType:{type:"string",description:"Short slug for the problem type (e.g. 'api-design', 'security-review', 'db-migration')"},description:{type:"string",description:"What kind of task this scaffold helps with"},whenToUse:{type:"string",description:"Conditions that indicate this scaffold should be applied"},steps:{type:"array",items:{type:"string"},description:"Ordered checklist steps to follow"},verificationSteps:{type:"array",items:{type:"string"},description:"Steps to verify the work is correct"},doNotApplyIf:{type:"string",description:"Conditions where this scaffold does NOT apply"},tags:{type:"array",items:{type:"string"},description:"Searchable tags"}},required:["problemType","description","whenToUse","steps"]},outputSchema:{type:"object",properties:{id:{type:"integer"},problem_type:{type:"string"},description:{type:"string"},steps:{type:"integer"}},required:["id","problem_type","description","steps"]},annotations:C.wyrm_scaffold_save,aliases:[],handler:(k,{store:v,scaffolds:$,cache:m})=>{const{projectPath:g,problemType:w,description:c,whenToUse:y,steps:o,verificationSteps:f,doNotApplyIf:n,tags:u}=k,a=g?v.getProject(g)?.id??void 0:void 0,l=$.save({projectId:a,problemType:w,description:c,whenToUse:y,steps:o,verificationSteps:f??void 0,doNotApplyIf:n?[n]:void 0,tags:u??[]});m.invalidate("wyrm_scaffold_get"),m.invalidate("wyrm_context_build");const _={id:l.id,problem_type:w,description:c,steps:o.length};return S(_,(i,e)=>T(e.brand,"**Reasoning Scaffold Saved**")+`
70
+ ${JSON.stringify({spec:r.spec_dir,title:r.title,project:r.project,tasksParsed:r.tasks_parsed,questsCreated:r.quests_created,questsUpdated:r.quests_updated,questIds:r.quest_ids},null,2)}
71
+ \`\`\``,c})}},{name:"wyrm_scaffold_save",description:"Save a reasoning scaffold \u2014 a structured checklist that guides step-by-step thinking for a specific problem type. Scaffolds are automatically surfaced by wyrm_context_build when the task matches, helping AI models apply proven reasoning patterns.",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Project path (optional, null for global scaffolds)"},problemType:{type:"string",description:"Short slug for the problem type (e.g. 'api-design', 'security-review', 'db-migration')"},description:{type:"string",description:"What kind of task this scaffold helps with"},whenToUse:{type:"string",description:"Conditions that indicate this scaffold should be applied"},steps:{type:"array",items:{type:"string"},description:"Ordered checklist steps to follow"},verificationSteps:{type:"array",items:{type:"string"},description:"Steps to verify the work is correct"},doNotApplyIf:{type:"string",description:"Conditions where this scaffold does NOT apply"},tags:{type:"array",items:{type:"string"},description:"Searchable tags"}},required:["problemType","description","whenToUse","steps"]},outputSchema:{type:"object",properties:{id:{type:"integer"},problem_type:{type:"string"},description:{type:"string"},steps:{type:"integer"}},required:["id","problem_type","description","steps"]},annotations:C.wyrm_scaffold_save,aliases:[],handler:(k,{store:_,scaffolds:$,cache:y})=>{const{projectPath:m,problemType:w,description:a,whenToUse:f,steps:d,verificationSteps:l,doNotApplyIf:u,tags:b}=k,i=m?_.getProject(m)?.id??void 0:void 0,t=$.save({projectId:i,problemType:w,description:a,whenToUse:f,steps:d,verificationSteps:l??void 0,doNotApplyIf:u?[u]:void 0,tags:b??[]});y.invalidate("wyrm_scaffold_get"),y.invalidate("wyrm_context_build");const g={id:t.id,problem_type:w,description:a,steps:d.length};return S(g,(r,e)=>E(e.brand,"**Reasoning Scaffold Saved**")+`
71
72
 
72
- ${e.bullet} **Problem Type:** ${i.problem_type}
73
- ${e.bullet} **Description:** ${i.description}
74
- ${e.bullet} **Steps:** ${i.steps} checklist items
75
- ${e.bullet} **ID:** ${i.id}
73
+ ${e.bullet} **Problem Type:** ${r.problem_type}
74
+ ${e.bullet} **Description:** ${r.description}
75
+ ${e.bullet} **Steps:** ${r.steps} checklist items
76
+ ${e.bullet} **ID:** ${r.id}
76
77
 
77
- This scaffold will be surfaced by \`wyrm_context_build\` when tasks match "${i.problem_type}".`)}}];export{_e as captureToolSpecs};
78
+ This scaffold will be surfaced by \`wyrm_context_build\` when tasks match "${r.problem_type}".`)}}];export{ve as captureToolSpecs};
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "contract": "wyrm-cloud-memory-v2",
3
- "version": "7.9.0",
3
+ "version": "7.10.0",
4
4
  "derivedFrom": "standard tier MINUS device-local/egress/subprocess (src/cloud-profile.ts)",
5
5
  "count": 19,
6
6
  "tools": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "contract": "wyrm-cloud-memory-v1",
3
- "version": "7.9.0",
3
+ "version": "7.10.0",
4
4
  "tools": [
5
5
  {
6
6
  "name": "wyrm_recall",
package/dist/wyrm-cli.js CHANGED
@@ -1,87 +1,87 @@
1
1
  #!/usr/bin/env node
2
- import{join as q,dirname as ne}from"path";import{homedir as Ie}from"os";import{existsSync as H,readFileSync as B,readdirSync as Pe}from"fs";import{fileURLToPath as re}from"url";import{spawnSync as z}from"child_process";import{createInterface as Q}from"readline";import{WyrmDB as Ne}from"./database.js";import{MemoryArtifacts as G}from"./memory-artifacts.js";import{GroundTruths as pe}from"./intelligence.js";import{Causality as ue}from"./causality.js";import{wireTruthAutoCascade as me}from"./truth-cascade.js";import{Rehydration as Me}from"./rehydration.js";import{makeRenderDeps as fe,buildRenderPlan as ie,renderToDisk as Ae,renderForClient as ge,detectClients as Oe,ALL_RENDER_CLIENTS as ce,ALL_CLIENT_MARKERS as Le}from"./render-target.js";import{classifyCapture as We}from"./capture.js";import{importFrom as Ue,IMPORT_SOURCES as ye}from"./importers.js";import{c as d,colors as N,formatTable as D,printSection as j,printSuccess as b,printError as m,icons as Y}from"./cli.js";import{loadConnectorConfigs as he,upsertConnectorConfig as qe,runConnector as Ye,runAllConnectors as Fe}from"./connectors/index.js";import{readActor as He}from"./attribution.js";function A(){try{const l=ne(re(import.meta.url));return JSON.parse(B(q(l,"..","package.json"),"utf-8"))}catch{return{}}}function I(l,a){const e=typeof l=="string"?parseInt(l,10):NaN;return Number.isFinite(e)?e:a}function Z(l,a){const e=typeof l=="string"?parseFloat(l):NaN;return Number.isFinite(e)?e:a}function Be(){return process.env.WYRM_DB_PATH??q(Ie(),".wyrm","wyrm.db")}function S(){return new Ne(Be())}function C(l){const a=[],e={};let s=0;for(;s<l.length;){const o=l[s];if(o.startsWith("--")){const t=o.slice(2),i=l[s+1];i&&!i.startsWith("--")?(e[t]=i,s+=2):(e[t]=!0,s++)}else a.push(o),s++}return{positional:a,flags:e}}function T(l,a){return a?l.getDatabase().prepare("SELECT * FROM projects WHERE LOWER(name) LIKE LOWER(?) LIMIT 1").get(`%${a}%`)??null:null}async function Ve(l){const{positional:a,flags:e}=C(l),s=a[0];s||(m("Usage: wyrm search <query> [--project <name>] [--type all|memories|truths|quests|data]"),process.exit(1));const o=S(),t=o.getDatabase(),i=e.type??"all",n=e.project,p=(n?T(o,n):null)?.id;j(`Search: "${s}"`);const c=[];if(i==="all"||i==="memories")try{const f=p?`AND m.project_id = ${p}`:"",g=t.prepare(`
2
+ import{join as q,dirname as ne}from"path";import{homedir as Te}from"os";import{existsSync as H,readFileSync as B,readdirSync as Ie}from"fs";import{fileURLToPath as re}from"url";import{spawnSync as z}from"child_process";import{createInterface as Q}from"readline";import{WyrmDB as Ne}from"./database.js";import{MemoryArtifacts as G}from"./memory-artifacts.js";import{GroundTruths as pe}from"./intelligence.js";import{Causality as ue}from"./causality.js";import{wireTruthAutoCascade as me}from"./truth-cascade.js";import{Rehydration as Me}from"./rehydration.js";import{makeRenderDeps as fe,buildRenderPlan as ie,renderToDisk as Ae,renderForClient as ge,detectClients as Oe,ALL_RENDER_CLIENTS as ce,ALL_CLIENT_MARKERS as Le}from"./render-target.js";import{classifyCapture as We}from"./capture.js";import{importFrom as Ue,IMPORT_SOURCES as ye}from"./importers.js";import{c as d,colors as N,formatTable as P,printSection as S,printSuccess as b,printError as m,icons as Y}from"./cli.js";import{loadConnectorConfigs as he,upsertConnectorConfig as qe,runConnector as Ye,runAllConnectors as Fe}from"./connectors/index.js";import{readActor as He}from"./attribution.js";function M(){try{const l=ne(re(import.meta.url));return JSON.parse(B(q(l,"..","package.json"),"utf-8"))}catch{return{}}}function T(l,a){const e=typeof l=="string"?parseInt(l,10):NaN;return Number.isFinite(e)?e:a}function Z(l,a){const e=typeof l=="string"?parseFloat(l):NaN;return Number.isFinite(e)?e:a}function Be(){return process.env.WYRM_DB_PATH??q(Te(),".wyrm","wyrm.db")}function _(){return new Ne(Be())}function C(l){const a=[],e={};let i=0;for(;i<l.length;){const o=l[i];if(o.startsWith("--")){const t=o.slice(2),r=l[i+1];r&&!r.startsWith("--")?(e[t]=r,i+=2):(e[t]=!0,i++)}else a.push(o),i++}return{positional:a,flags:e}}function D(l,a){return a?l.getDatabase().prepare("SELECT * FROM projects WHERE LOWER(name) LIKE LOWER(?) LIMIT 1").get(`%${a}%`)??null:null}async function Ve(l){const{positional:a,flags:e}=C(l),i=a[0];i||(m("Usage: wyrm search <query> [--project <name>] [--type all|memories|truths|quests|data]"),process.exit(1));const o=_(),t=o.getDatabase(),r=e.type??"all",s=e.project,p=(s?D(o,s):null)?.id;S(`Search: "${i}"`);const c=[];if(r==="all"||r==="memories")try{const f=p?`AND m.project_id = ${p}`:"",g=t.prepare(`
3
3
  SELECT m.id, m.kind, m.problem, m.project_id FROM memory_artifacts m
4
4
  JOIN memory_artifacts_fts fts ON m.id = fts.rowid
5
5
  WHERE memory_artifacts_fts MATCH ? ${f}
6
6
  LIMIT 20
7
- `).all(s);for(const u of g)c.push([`mem:${u.id}`,"memory",u.kind,u.problem.slice(0,80)])}catch{}if(i==="all"||i==="sessions")try{const f=p?`AND s.project_id = ${p}`:"",g=t.prepare(`
7
+ `).all(i);for(const u of g)c.push([`mem:${u.id}`,"memory",u.kind,u.problem.slice(0,80)])}catch{}if(r==="all"||r==="sessions")try{const f=p?`AND s.project_id = ${p}`:"",g=t.prepare(`
8
8
  SELECT s.id, s.objectives, s.project_id FROM sessions s
9
9
  JOIN sessions_fts fts ON s.id = fts.rowid
10
10
  WHERE sessions_fts MATCH ? ${f}
11
11
  LIMIT 10
12
- `).all(s);for(const u of g)c.push([`session:${u.id}`,"session","",u.objectives.slice(0,80)])}catch{}if(i==="all"||i==="quests")try{const f=p?`AND q.project_id = ${p}`:"",g=t.prepare(`
12
+ `).all(i);for(const u of g)c.push([`session:${u.id}`,"session","",u.objectives.slice(0,80)])}catch{}if(r==="all"||r==="quests")try{const f=p?`AND q.project_id = ${p}`:"",g=t.prepare(`
13
13
  SELECT q.id, q.title, q.priority, q.project_id FROM quests q
14
14
  JOIN quests_fts fts ON q.id = fts.rowid
15
15
  WHERE quests_fts MATCH ? ${f}
16
16
  LIMIT 10
17
- `).all(s);for(const u of g)c.push([`quest:${u.id}`,"quest",u.priority,u.title.slice(0,80)])}catch{}if(i==="all"||i==="data")try{const f=p?`AND d.project_id = ${p}`:"",g=t.prepare(`
17
+ `).all(i);for(const u of g)c.push([`quest:${u.id}`,"quest",u.priority,u.title.slice(0,80)])}catch{}if(r==="all"||r==="data")try{const f=p?`AND d.project_id = ${p}`:"",g=t.prepare(`
18
18
  SELECT d.id, d.category, d.key, d.value FROM data_lake d
19
19
  JOIN data_lake_fts fts ON d.id = fts.rowid
20
20
  WHERE data_lake_fts MATCH ? ${f}
21
21
  LIMIT 10
22
- `).all(s);for(const u of g)c.push([`data:${u.id}`,"data",u.category,u.value.slice(0,80)])}catch{}if(o.close(),c.length===0){console.log(d.dim(` No results found for "${s}"`));return}console.log(D(["ID","Type","Subtype","Preview"],c)),console.log(d.dim(`
23
- ${c.length} result${c.length!==1?"s":""}`))}async function Ke(l){const{flags:a}=C(l),e=a.type??"all",s=I(a.limit,20),o=a.project,t=S(),i=t.getDatabase(),r=(o?T(t,o):null)?.id,p=r?`WHERE project_id = ${r}`:"",c=r?`AND project_id = ${r}`:"";if(j("Wyrm Memory"),e==="all"||e==="memories"){j("Memories");const f=i.prepare(`
22
+ `).all(i);for(const u of g)c.push([`data:${u.id}`,"data",u.category,u.value.slice(0,80)])}catch{}if(o.close(),c.length===0){console.log(d.dim(` No results found for "${i}"`));return}console.log(P(["ID","Type","Subtype","Preview"],c)),console.log(d.dim(`
23
+ ${c.length} result${c.length!==1?"s":""}`))}async function Ke(l){const{flags:a}=C(l),e=a.type??"all",i=T(a.limit,20),o=a.project,t=_(),r=t.getDatabase(),n=(o?D(t,o):null)?.id,p=n?`WHERE project_id = ${n}`:"",c=n?`AND project_id = ${n}`:"";if(S("Wyrm Memory"),e==="all"||e==="memories"){S("Memories");const f=r.prepare(`
24
24
  SELECT id, kind, confidence, problem, tags FROM memory_artifacts
25
25
  ${p}
26
26
  ORDER BY created_at DESC LIMIT ?
27
- `).all(s);if(f.length>0){const g=f.map(u=>[`mem:${u.id}`,u.kind,`${Math.round(u.confidence*100)}%`,u.problem.slice(0,60),u.tags?.slice(0,30)??""]);console.log(D(["ID","Kind","Conf","Preview","Tags"],g))}else console.log(d.dim(" No memories yet."))}if(e==="all"||e==="truths"){j("Ground Truths");const f=i.prepare(`
27
+ `).all(i);if(f.length>0){const g=f.map(u=>[`mem:${u.id}`,u.kind,`${Math.round(u.confidence*100)}%`,u.problem.slice(0,60),u.tags?.slice(0,30)??""]);console.log(P(["ID","Kind","Conf","Preview","Tags"],g))}else console.log(d.dim(" No memories yet."))}if(e==="all"||e==="truths"){S("Ground Truths");const f=r.prepare(`
28
28
  SELECT id, category, key, value FROM ground_truths
29
29
  WHERE is_current = 1 ${c}
30
30
  ORDER BY created_at DESC LIMIT ?
31
- `).all(s);if(f.length>0){const g=f.map(u=>[`truth:${u.id}`,u.category,u.key.slice(0,30),u.value.slice(0,60)]);console.log(D(["ID","Category","Key","Value"],g))}else console.log(d.dim(" No ground truths yet."))}if(e==="all"||e==="quests"){j("Quests");const f=i.prepare(`
31
+ `).all(i);if(f.length>0){const g=f.map(u=>[`truth:${u.id}`,u.category,u.key.slice(0,30),u.value.slice(0,60)]);console.log(P(["ID","Category","Key","Value"],g))}else console.log(d.dim(" No ground truths yet."))}if(e==="all"||e==="quests"){S("Quests");const f=r.prepare(`
32
32
  SELECT id, priority, title, status FROM quests
33
33
  ${p}
34
34
  ORDER BY created_at DESC LIMIT ?
35
- `).all(s);if(f.length>0){const g=f.map(u=>[`quest:${u.id}`,u.priority,u.title.slice(0,60),u.status]);console.log(D(["ID","Priority","Title","Status"],g))}else console.log(d.dim(" No quests yet."))}t.close()}async function Ge(l){const{positional:a}=C(l),e=a[0];e||(m("Usage: wyrm show <typed-id> (e.g. mem:41, quest:12, truth:7, data:5, session:3)"),process.exit(1));const[s,o]=e.split(":"),t=parseInt(o??"",10);(!s||isNaN(t))&&(m(`Invalid typed ID: ${e}. Format: type:number (e.g. mem:41)`),process.exit(1));const i=S(),n=i.getDatabase();switch(j(`${e}`),s){case"mem":{const r=n.prepare("SELECT * FROM memory_artifacts WHERE id = ?").get(t);if(!r){m(`Memory artifact ${t} not found`);break}console.log(d.bold("Kind: ")+r.kind),console.log(d.bold("Confidence:")+` ${Math.round(r.confidence*100)}%`),console.log(d.bold("Problem: ")+`
36
- `+r.problem),r.validated_fix&&console.log(d.bold("Solution: ")+`
37
- `+r.validated_fix),r.why_it_worked&&console.log(d.bold("Why: ")+`
38
- `+r.why_it_worked),r.tags&&console.log(d.bold("Tags: ")+r.tags),console.log(d.bold("Created: ")+r.created_at);break}case"quest":{const r=n.prepare("SELECT * FROM quests WHERE id = ?").get(t);if(!r){m(`Quest ${t} not found`);break}console.log(d.bold("Title: ")+r.title),console.log(d.bold("Priority: ")+r.priority),console.log(d.bold("Status: ")+r.status),r.description&&console.log(d.bold("Desc: ")+`
39
- `+r.description),r.tags&&console.log(d.bold("Tags: ")+r.tags),console.log(d.bold("Created: ")+r.created_at);break}case"truth":{const r=n.prepare("SELECT * FROM ground_truths WHERE id = ?").get(t);if(!r){m(`Ground truth ${t} not found`);break}console.log(d.bold("Category: ")+r.category),console.log(d.bold("Key: ")+r.key),console.log(d.bold("Value: ")+`
40
- `+r.value),r.rationale&&console.log(d.bold("Rationale:")+`
41
- `+r.rationale),console.log(d.bold("Active: ")+(r.is_current?"Yes":"No (superseded)")),console.log(d.bold("Created: ")+r.created_at);break}case"data":{const r=n.prepare("SELECT * FROM data_lake WHERE id = ?").get(t);if(!r){m(`Data point ${t} not found`);break}console.log(d.bold("Category: ")+r.category),console.log(d.bold("Key: ")+r.key),console.log(d.bold("Value: ")+`
42
- `+String(r.value).slice(0,500)),console.log(d.bold("Created: ")+r.created_at);break}case"session":{const r=n.prepare("SELECT * FROM sessions WHERE id = ?").get(t);if(!r){m(`Session ${t} not found`);break}console.log(d.bold("Date: ")+r.date),console.log(d.bold("Objectives: ")+`
43
- `+r.objectives),r.completed&&console.log(d.bold("Completed: ")+`
44
- `+r.completed),r.notes&&console.log(d.bold("Notes: ")+`
45
- `+r.notes);break}default:m(`Unknown type prefix: ${s}. Use mem|quest|truth|data|session`)}i.close()}async function Je(l){const{positional:a,flags:e}=C(l),s=a[0];s||(m('Usage: wyrm capture "<content>" [--project <name>] [--mode auto|quest|truth|memory]'),process.exit(1));const o=e.project,t=e.mode,i=S(),n=i.getDatabase();let r=null;if(o){const k=T(i,o);k||(m(`Project not found: ${o}`),i.close(),process.exit(1)),r=k.id}let p=We(s);t&&t!=="auto"&&(p={type:t,subtype:{quest:"quest",truth:"decision",memory:"pattern"}[t]??t,confidence:100,reasoning:`Mode override: ${t}`});const{type:c,subtype:f,confidence:g,reasoning:u}=p;(c==="quest"||c==="truth"||c==="memory")&&r===null&&(m('A --project is required to capture. Use: wyrm capture "<text>" --project <name>'),i.close(),process.exit(1));let h=0,y="",w=!1;const $=new G(n),R=new pe(n);if(me(R,new ue(n)),c==="quest")h=i.addQuest(r,s.slice(0,200),"","medium").id,y="quest";else if(c==="truth")t!=="truth"&&g<100?(h=$.add(r,{kind:"pattern",problem:s,confidence:g/100,needsReview:1}).id,y="mem",w=!0):(h=R.set(r,{category:"decision",key:s.slice(0,60),value:s}).id,y="truth");else{const k=g>=75;h=$.add(r,{kind:f,problem:s,confidence:g/100,needsReview:k?0:1}).id,y="mem",k||(w=!0)}const{recordWrite:_}=await import("./receipts.js"),E=_(n,{tool:"wyrm_capture",outcome:w?"queued":"stored",refTable:y,refId:h,reason:w?`needs_review (classifier confidence ${g}%) \u2014 invisible to recall until reviewed`:void 0,source:"cli",projectId:r??void 0});i.close(),b(`Captured as ${c}: ${f}`),console.log(`${d.dim("Confidence:")} ${g}% | ${d.dim(u)}`),console.log(`${d.dim("ID:")} ${y}:${h}`),w&&console.log(`${Y.warning} Stored for review \u2014 run ${d.cyan("wyrm review")} to activate`),console.log(`receipt: ${E.outcome} ${E.ref??""}${E.reason?` \u2014 ${E.reason}`:""}`)}async function ze(l){const{flags:a}=C(l);if(a.writes!==!0){m("Usage: wyrm digest --writes [--since <days>] [--json]"),process.exitCode=1;return}const e=Number(a.since)>=1?Number(a.since):1,{buildWriteDigest:s}=await import("./receipts.js"),o=S();try{const t=s(o.getDatabase(),e);if(a.json===!0){process.stdout.write(JSON.stringify(t)+`
46
- `);return}if(j(`Write digest \u2014 last ${t.since}`),console.log(`${d.bold("Total writes:")} ${t.total} ${d.bold("Outcomes:")} ${Object.entries(t.byOutcome).map(([i,n])=>`${i} ${n}`).join(" \xB7 ")||"none"}`),console.log(`${d.bold("Review queue:")} ${t.reviewQueueDepth} awaiting review${t.reviewQueueDepth>0?d.dim(" (invisible to recall until approved)"):""}`),t.byTool.length>0&&console.log(D(["Tool","Outcome","N"],t.byTool.map(i=>[i.tool,i.outcome,String(i.n)]))),t.reasons.length>0){console.log(d.bold("Non-stored reasons:"));for(const i of t.reasons)console.log(` ${i.outcome} \xD7${i.n} \u2014 ${i.reason.slice(0,110)}`)}t.bySource.length>0&&console.log(d.bold("By source: ")+t.bySource.map(i=>`${i.source} ${i.n}`).join(" \xB7 "))}finally{o.close()}}async function Qe(l){const{flags:a}=C(l),e=I(a.session,0),s=a.path,o=a.project,t=I(a["max-chars"],6e3),i=a.quiet===!0,n=console.log;console.log=()=>{};const r=S();try{const p=new Me(r.getDatabase());let c=e;if(c<=0){let u=s?r.getProject(s):void 0;if(!u&&o&&(u=T(r,o)??void 0),u||(u=r.getProject(process.cwd())),!u){i||process.stderr.write(`wyrm rehydrate: no Wyrm project for this directory, nothing to restore.
47
- `);return}const h=r.getRecentSessions(u.id,1);if(h.length===0){i||process.stderr.write(`wyrm rehydrate: project "${u.name}" has no prior sessions yet.
48
- `);return}c=h[0].id}const f=p.rehydrate(c);if(!f){i||process.stderr.write(`wyrm rehydrate: session ${c} not found.
35
+ `).all(i);if(f.length>0){const g=f.map(u=>[`quest:${u.id}`,u.priority,u.title.slice(0,60),u.status]);console.log(P(["ID","Priority","Title","Status"],g))}else console.log(d.dim(" No quests yet."))}t.close()}async function Ge(l){const{positional:a}=C(l),e=a[0];e||(m("Usage: wyrm show <typed-id> (e.g. mem:41, quest:12, truth:7, data:5, session:3)"),process.exit(1));const[i,o]=e.split(":"),t=parseInt(o??"",10);(!i||isNaN(t))&&(m(`Invalid typed ID: ${e}. Format: type:number (e.g. mem:41)`),process.exit(1));const r=_(),s=r.getDatabase();switch(S(`${e}`),i){case"mem":{const n=s.prepare("SELECT * FROM memory_artifacts WHERE id = ?").get(t);if(!n){m(`Memory artifact ${t} not found`);break}console.log(d.bold("Kind: ")+n.kind),console.log(d.bold("Confidence:")+` ${Math.round(n.confidence*100)}%`),console.log(d.bold("Problem: ")+`
36
+ `+n.problem),n.validated_fix&&console.log(d.bold("Solution: ")+`
37
+ `+n.validated_fix),n.why_it_worked&&console.log(d.bold("Why: ")+`
38
+ `+n.why_it_worked),n.tags&&console.log(d.bold("Tags: ")+n.tags),console.log(d.bold("Created: ")+n.created_at);break}case"quest":{const n=s.prepare("SELECT * FROM quests WHERE id = ?").get(t);if(!n){m(`Quest ${t} not found`);break}console.log(d.bold("Title: ")+n.title),console.log(d.bold("Priority: ")+n.priority),console.log(d.bold("Status: ")+n.status),n.description&&console.log(d.bold("Desc: ")+`
39
+ `+n.description),n.tags&&console.log(d.bold("Tags: ")+n.tags),console.log(d.bold("Created: ")+n.created_at);break}case"truth":{const n=s.prepare("SELECT * FROM ground_truths WHERE id = ?").get(t);if(!n){m(`Ground truth ${t} not found`);break}console.log(d.bold("Category: ")+n.category),console.log(d.bold("Key: ")+n.key),console.log(d.bold("Value: ")+`
40
+ `+n.value),n.rationale&&console.log(d.bold("Rationale:")+`
41
+ `+n.rationale),console.log(d.bold("Active: ")+(n.is_current?"Yes":"No (superseded)")),console.log(d.bold("Created: ")+n.created_at);break}case"data":{const n=s.prepare("SELECT * FROM data_lake WHERE id = ?").get(t);if(!n){m(`Data point ${t} not found`);break}console.log(d.bold("Category: ")+n.category),console.log(d.bold("Key: ")+n.key),console.log(d.bold("Value: ")+`
42
+ `+String(n.value).slice(0,500)),console.log(d.bold("Created: ")+n.created_at);break}case"session":{const n=s.prepare("SELECT * FROM sessions WHERE id = ?").get(t);if(!n){m(`Session ${t} not found`);break}console.log(d.bold("Date: ")+n.date),console.log(d.bold("Objectives: ")+`
43
+ `+n.objectives),n.completed&&console.log(d.bold("Completed: ")+`
44
+ `+n.completed),n.notes&&console.log(d.bold("Notes: ")+`
45
+ `+n.notes);break}default:m(`Unknown type prefix: ${i}. Use mem|quest|truth|data|session`)}r.close()}async function Je(l){const{positional:a,flags:e}=C(l),i=a[0];i||(m('Usage: wyrm capture "<content>" [--project <name>] [--mode auto|quest|truth|memory]'),process.exit(1));const o=e.project,t=e.mode,r=_(),s=r.getDatabase();let n=null;if(o){const k=D(r,o);k||(m(`Project not found: ${o}`),r.close(),process.exit(1)),n=k.id}let p=We(i);t&&t!=="auto"&&(p={type:t,subtype:{quest:"quest",truth:"decision",memory:"pattern"}[t]??t,confidence:100,reasoning:`Mode override: ${t}`});const{type:c,subtype:f,confidence:g,reasoning:u}=p;(c==="quest"||c==="truth"||c==="memory")&&n===null&&(m('A --project is required to capture. Use: wyrm capture "<text>" --project <name>'),r.close(),process.exit(1));let h=0,y="",w=!1;const $=new G(s),R=new pe(s);if(me(R,new ue(s)),c==="quest")h=r.addQuest(n,i.slice(0,200),"","medium").id,y="quest";else if(c==="truth")t!=="truth"&&g<100?(h=$.add(n,{kind:"pattern",problem:i,confidence:g/100,needsReview:1}).id,y="mem",w=!0):(h=R.set(n,{category:"decision",key:i.slice(0,60),value:i}).id,y="truth");else{const k=g>=75;h=$.add(n,{kind:f,problem:i,confidence:g/100,needsReview:k?0:1}).id,y="mem",k||(w=!0)}const{recordWrite:j}=await import("./receipts.js"),E=j(s,{tool:"wyrm_capture",outcome:w?"queued":"stored",refTable:y,refId:h,reason:w?`needs_review (classifier confidence ${g}%) \u2014 invisible to recall until reviewed`:void 0,source:"cli",projectId:n??void 0});r.close(),b(`Captured as ${c}: ${f}`),console.log(`${d.dim("Confidence:")} ${g}% | ${d.dim(u)}`),console.log(`${d.dim("ID:")} ${y}:${h}`),w&&console.log(`${Y.warning} Stored for review \u2014 run ${d.cyan("wyrm review")} to activate`),console.log(`receipt: ${E.outcome} ${E.ref??""}${E.reason?` \u2014 ${E.reason}`:""}`)}async function ze(l){const{flags:a}=C(l);if(a.writes!==!0){m("Usage: wyrm digest --writes [--since <days>] [--json]"),process.exitCode=1;return}const e=Number(a.since)>=1?Number(a.since):1,{buildWriteDigest:i}=await import("./receipts.js"),o=_();try{const t=i(o.getDatabase(),e);if(a.json===!0){process.stdout.write(JSON.stringify(t)+`
46
+ `);return}if(S(`Write digest \u2014 last ${t.since}`),console.log(`${d.bold("Total writes:")} ${t.total} ${d.bold("Outcomes:")} ${Object.entries(t.byOutcome).map(([r,s])=>`${r} ${s}`).join(" \xB7 ")||"none"}`),console.log(`${d.bold("Review queue:")} ${t.reviewQueueDepth} awaiting review${t.reviewQueueDepth>0?d.dim(" (invisible to recall until approved)"):""}`),t.byTool.length>0&&console.log(P(["Tool","Outcome","N"],t.byTool.map(r=>[r.tool,r.outcome,String(r.n)]))),t.reasons.length>0){console.log(d.bold("Non-stored reasons:"));for(const r of t.reasons)console.log(` ${r.outcome} \xD7${r.n} \u2014 ${r.reason.slice(0,110)}`)}t.bySource.length>0&&console.log(d.bold("By source: ")+t.bySource.map(r=>`${r.source} ${r.n}`).join(" \xB7 "))}finally{o.close()}}async function Qe(l){const{flags:a}=C(l),e=T(a.session,0),i=a.path,o=a.project,t=T(a["max-chars"],6e3),r=a.quiet===!0,s=console.log;console.log=()=>{};const n=_();try{const p=new Me(n.getDatabase());let c=e;if(c<=0){let u=i?n.getProject(i):void 0;if(!u&&o&&(u=D(n,o)??void 0),u||(u=n.getProject(process.cwd())),!u){r||process.stderr.write(`wyrm rehydrate: no Wyrm project for this directory, nothing to restore.
47
+ `);return}const h=n.getRecentSessions(u.id,1);if(h.length===0){r||process.stderr.write(`wyrm rehydrate: project "${u.name}" has no prior sessions yet.
48
+ `);return}c=h[0].id}const f=p.rehydrate(c);if(!f){r||process.stderr.write(`wyrm rehydrate: session ${c} not found.
49
49
  `);return}let g=f.briefing_markdown;t>0&&g.length>t&&(g=g.slice(0,t)+`
50
50
 
51
51
  _... brief truncated at ${t} chars, run \`wyrm show session:${f.session_id}\` for the full record._`),process.stdout.write(g+`
52
- `)}finally{r.close(),console.log=n}}async function Ze(l){const{positional:a,flags:e}=C(l);if(a[0]!=="log"){m("Usage: wyrm session log [--path <dir>] [--run <id>] [--objectives <t>] [--completed <t>] [--issues <t>] [--commits <t>] [--notes <t>]"),process.exitCode=1;return}const o=c=>typeof e[c]=="string"?e[c]:void 0,t=o("path"),i=o("project"),n=o("run")?.slice(0,64),r=console.log;console.log=()=>{};const p=S();try{let c=t?p.getProject(t):void 0;if(!c&&i&&(c=T(p,i)??void 0),c||(c=p.getProject(process.cwd())),!c){process.stderr.write(`wyrm session log: no Wyrm project for this directory, nothing recorded.
53
- `);return}const f={};for(const h of["objectives","completed","issues","commits","notes"]){const y=o(h);y&&(f[h]=y.slice(0,8e3))}const g=p.getDatabase();let u;n&&(u=g.prepare("SELECT id FROM sessions WHERE project_id = ? AND run_id = ? ORDER BY id DESC LIMIT 1").get(c.id,n)?.id),u?p.updateSession(u,f):(u=p.createSession(c.id,f).id,n&&g.prepare("UPDATE sessions SET run_id = ? WHERE id = ?").run(n,u)),process.stdout.write(`session:${u}
54
- `)}finally{p.close(),console.log=r}}async function Xe(l){const{positional:a,flags:e}=C(l),s=a[0],o=p=>typeof e[p]=="string"?e[p]:void 0,{AgentPresence:t,processStartTime:i,presenceLiveness:n}=await import("./presence.js"),r=S();try{const p=new t(r.getDatabase());if(s==="announce"){const c=o("agent");if(!c){m("Usage: wyrm presence announce --agent <id> [--kind <kind>] [--project <name>|--path <dir>] [--quest <n>] [--pid <n>|--auto-pid] [--ttl <sec>] [--role <r>]"),process.exitCode=1;return}const f=o("path");let g=f?r.getProject(f):void 0;!g&&o("project")&&(g=T(r,o("project"))??void 0);const u=e["auto-pid"]===!0?process.ppid:Number(o("pid")),h={};let y="";if(Number.isInteger(u)&&u>0){const R=i(u);R?(h.pid=u,h.pid_start=R,y=` \xB7 pid ${u} (start ${R})`):y=` \xB7 pid ${u} unreadable \u2014 TTL fallback`}const w=Number(o("ttl")),$=p.announce({agent_id:c,agent_kind:o("kind")??"cli",project_id:g?.id??null,current_quest_id:o("quest")?Number(o("quest")):null,ttl_seconds:Number.isFinite(w)&&w>0?w:h.pid?86400:300,metadata:Object.keys(h).length?h:void 0,role:o("role")??null});process.stdout.write(`presence:${$.id} ${c}${y}
55
- `);return}if(s==="list"||s==="ls"||s===void 0){const c=r.getDatabase().prepare("SELECT * FROM agent_presence ORDER BY last_heartbeat DESC").all();if(c.length===0){process.stdout.write(`No agents on the board.
56
- `);return}const f={"alive-pid":"ALIVE (pid)","dead-pid":"DEAD (pid gone)","alive-ttl":"alive (ttl)",stale:"stale"};for(const g of c){const u=n(g);process.stdout.write(`${g.agent_id} [${g.agent_kind}] ${f[u]} hb ${g.last_heartbeat}${g.role?` role ${g.role}`:""}${g.current_quest_id?` quest #${g.current_quest_id}`:""}
57
- `)}return}if(s==="release"){const c=o("agent");if(!c){m("Usage: wyrm presence release --agent <id>"),process.exitCode=1;return}const f=p.release(c);process.stdout.write(f?`released ${c}
52
+ `)}finally{n.close(),console.log=s}}async function Ze(l){const{positional:a,flags:e}=C(l),i=a[0],o=await import("./bridge.js");if(i==="init"){const{existsSync:t,writeFileSync:r}=await import("node:fs"),s=o.configPath();if(t(s)&&e.force!==!0){m(`${s} already exists (use --force to overwrite).`),process.exitCode=1;return}const n=(await import("node:os")).homedir(),p={sources:[{type:"remember",name:"remember",dir:`${n}/.remember`,projectPath:n,windowDays:7}]};r(s,JSON.stringify(p,null,2)),b(`Wrote ${s} (1 source: remember). Add render/reverse sources per project as needed.`);return}if(i==="status"){const t=o.loadConfig();if(!t){m(`No bridge config at ${o.configPath()} \u2014 run \`wyrm bridge init\`.`),process.exitCode=1;return}const r=o.loadState();S("Bridge sources");for(const s of t.sources){const n=Object.keys(r).filter(c=>c.startsWith(s.name+":")),p=n.reduce((c,f)=>c+r[f].length,0);console.log(` ${s.name} [${s.type}] \u2192 ${"projectPath"in s?s.projectPath:""}${s.type==="remember"?` (${n.length} files tracked, ${p} sections bridged)`:""}`)}return}if(i==="run"){const t=o.loadConfig();if(!t){m(`No bridge config at ${o.configPath()} \u2014 run \`wyrm bridge init\`.`),process.exitCode=1;return}const r=_(),s=console.log;try{const n={wyrm_version:M().version??"unknown",compiled_at:new Date().toISOString()},p=await o.runBridge(r.getDatabase(),t,n,{only:typeof e.source=="string"?e.source:void 0,dryRun:e["dry-run"]===!0});for(const c of p)console.log(`bridge:${c.source} [${c.type}]${e["dry-run"]===!0?" (dry-run)":""} \u2014 stored ${c.stored} \xB7 queued ${c.queued} \xB7 dropped ${c.dropped} \xB7 rendered ${c.rendered}${c.note?` \xB7 ${c.note}`:""}`);return}finally{r.close(),console.log=s}}m("Usage: wyrm bridge <init|status|run> [--source <name>] [--dry-run] [--force]"),process.exitCode=1}async function Xe(l){const{positional:a,flags:e}=C(l);if(a[0]!=="log"){m("Usage: wyrm session log [--path <dir>] [--run <id>] [--objectives <t>] [--completed <t>] [--issues <t>] [--commits <t>] [--notes <t>]"),process.exitCode=1;return}const o=c=>typeof e[c]=="string"?e[c]:void 0,t=o("path"),r=o("project"),s=o("run")?.slice(0,64),n=console.log;console.log=()=>{};const p=_();try{let c=t?p.getProject(t):void 0;if(!c&&r&&(c=D(p,r)??void 0),c||(c=p.getProject(process.cwd())),!c){process.stderr.write(`wyrm session log: no Wyrm project for this directory, nothing recorded.
53
+ `);return}const f={};for(const h of["objectives","completed","issues","commits","notes"]){const y=o(h);y&&(f[h]=y.slice(0,8e3))}const g=p.getDatabase();let u;s&&(u=g.prepare("SELECT id FROM sessions WHERE project_id = ? AND run_id = ? ORDER BY id DESC LIMIT 1").get(c.id,s)?.id),u?p.updateSession(u,f):(u=p.createSession(c.id,f).id,s&&g.prepare("UPDATE sessions SET run_id = ? WHERE id = ?").run(s,u)),process.stdout.write(`session:${u}
54
+ `)}finally{p.close(),console.log=n}}async function et(l){const{positional:a,flags:e}=C(l),i=a[0],o=p=>typeof e[p]=="string"?e[p]:void 0,{AgentPresence:t,processStartTime:r,presenceLiveness:s}=await import("./presence.js"),n=_();try{const p=new t(n.getDatabase());if(i==="announce"){const c=o("agent");if(!c){m("Usage: wyrm presence announce --agent <id> [--kind <kind>] [--project <name>|--path <dir>] [--quest <n>] [--pid <n>|--auto-pid] [--ttl <sec>] [--role <r>]"),process.exitCode=1;return}const f=o("path");let g=f?n.getProject(f):void 0;!g&&o("project")&&(g=D(n,o("project"))??void 0);const u=e["auto-pid"]===!0?process.ppid:Number(o("pid")),h={};let y="";if(Number.isInteger(u)&&u>0){const R=r(u);R?(h.pid=u,h.pid_start=R,y=` \xB7 pid ${u} (start ${R})`):y=` \xB7 pid ${u} unreadable \u2014 TTL fallback`}const w=Number(o("ttl")),$=p.announce({agent_id:c,agent_kind:o("kind")??"cli",project_id:g?.id??null,current_quest_id:o("quest")?Number(o("quest")):null,ttl_seconds:Number.isFinite(w)&&w>0?w:h.pid?86400:300,metadata:Object.keys(h).length?h:void 0,role:o("role")??null});process.stdout.write(`presence:${$.id} ${c}${y}
55
+ `);return}if(i==="list"||i==="ls"||i===void 0){const c=n.getDatabase().prepare("SELECT * FROM agent_presence ORDER BY last_heartbeat DESC").all();if(c.length===0){process.stdout.write(`No agents on the board.
56
+ `);return}const f={"alive-pid":"ALIVE (pid)","dead-pid":"DEAD (pid gone)","alive-ttl":"alive (ttl)",stale:"stale"};for(const g of c){const u=s(g);process.stdout.write(`${g.agent_id} [${g.agent_kind}] ${f[u]} hb ${g.last_heartbeat}${g.role?` role ${g.role}`:""}${g.current_quest_id?` quest #${g.current_quest_id}`:""}
57
+ `)}return}if(i==="release"){const c=o("agent");if(!c){m("Usage: wyrm presence release --agent <id>"),process.exitCode=1;return}const f=p.release(c);process.stdout.write(f?`released ${c}
58
58
  `:`no presence row for ${c}
59
- `);return}m("Usage: wyrm presence <announce|list|release> [options]"),process.exitCode=1}finally{r.close()}}async function et(l){const{flags:a}=C(l),e=a.path,s=a.project,o=a.out,t=a.brief===!0,i=a.force===!0,n=a.quiet===!0,r=(typeof a.client=="string"?a.client:"").split(",").map(u=>u.trim().toLowerCase()).filter(Boolean);let p=r.filter(u=>ce.includes(u));const c=r.filter(u=>!ce.includes(u));c.length>0&&(m(`Unknown --client value(s): ${c.join(", ")} (valid: ${ce.join("|")})`),process.exit(1));const f=console.log;t&&(console.log=()=>{});const g=S();try{let u=e?g.getProject(e):void 0;!u&&s&&(u=T(g,s)??void 0),u||(u=g.getProject(process.cwd())),u||(console.log=f,m("wyrm render: no Wyrm project for this directory (use --path or --project)."),process.exit(1));const h=fe(g.getDatabase()),y={wyrm_version:A().version??"unknown",compiled_at:new Date().toISOString()};if(t){const _=ie(h,u,y);console.log=f,process.stdout.write(_.sessionBrief+`
60
- `);return}const w=o??u.path;p.length===0&&r.length===0&&(p=Oe(Le.filter(_=>H(q(w,_)))));{const _=await import("./reverse-bridge.js"),E=ie(h,u,y),k={"MEMORY.md":E.memoryMd};for(const L of p){const W=ge(L,E.model,y);k[W.relPath]=W.block}try{const L=_.makeBridgeDeps(g.getDatabase()),W=await _.sweepProject(L,{id:u.id,path:u.path},k,{rootDir:w});W.added>0&&!n&&console.log(` ${Y.warning} harvested ${W.added} human edit(s) \u2192 review queue before overwrite`)}catch{}}const{plan:$,writes:R}=Ae(h,u,y,{rootDir:w,clients:p,force:i});if(!n){b(`Rendered ${u.name} memory (${$.model.truths.length} truths, ${$.model.failures.length} failures, ${$.model.quests.length} quests, ${$.model.artifacts.length} patterns) to ${w}`);for(const _ of R){const E=_.action==="created"?Y.success:_.action==="updated"?Y.info:Y.warning;console.log(` ${E} ${_.action.padEnd(7)} ${_.path}${_.reason?` (${_.reason})`:""}`)}}}finally{g.close(),console.log=f}}async function tt(l){const{flags:a}=C(l),e=a.path,s=a.project,o=a.root,t=a["dry-run"]===!0||a.dry===!0,i=(typeof a.client=="string"?a.client:"").split(",").map(f=>f.trim().toLowerCase()).filter(Boolean),n=["claude","cursor","copilot","agents"],r=i.filter(f=>n.includes(f)),p=await import("./reverse-bridge.js"),c=S();try{let f=e?c.getProject(e):void 0;if(!f&&s&&(f=T(c,s)??void 0),f||(f=c.getProject(process.cwd())),!f){m("wyrm reverse-bridge: no Wyrm project for this directory (use --path or --project)."),process.exitCode=1;return}const g=fe(c.getDatabase()),u={wyrm_version:A().version??"unknown",compiled_at:new Date().toISOString()},h=ie(g,f,u),y={"MEMORY.md":h.memoryMd};for(const R of r){const _=ge(R,h.model,u);y[_.relPath]=_.block}const w=p.makeBridgeDeps(c.getDatabase()),$=await p.sweepProject(w,{id:f.id,path:f.path},y,{dryRun:t,rootDir:o});j(`Reverse bridge ${t?"(dry run) ":""}\u2014 ${$.added} candidate(s) queued, ${$.skipped} already present (${$.filesWithEdits}/${$.filesScanned} file(s) with edits)`);for(const R of $.sample)console.log(` ${Y.bullet} ${R}`);!t&&$.added>0&&b("Review with: wyrm review")}finally{c.close()}}async function ot(l){const a=C(l);if(typeof a.flags.from=="string"){await st(a);return}const{positional:e,flags:s}=C(l.slice(1)),o=l[0],t=s.project;if(o==="git"){const i=I(s.last,20),n=S(),r=n.getDatabase();let p=null;if(t){const y=T(n,t);y||(m(`Project not found: ${t}`),n.close(),process.exit(1)),p=y.id}else{const y=r.prepare("SELECT p.* FROM projects p JOIN sessions s ON s.project_id = p.id ORDER BY s.created_at DESC LIMIT 1").get();y&&(p=y.id)}p||(m("No project found. Use --project <name>"),n.close(),process.exit(1));const c=z("git",["log","--pretty=format:%H%x1f%s%x1f%an%x1f%ai",`-${i}`],{cwd:process.cwd(),encoding:"utf-8",timeout:1e4,shell:!1});(c.error||c.status!==0)&&(m("git log failed. Make sure you are in a git repository."),n.close(),process.exit(1));const f=c.stdout.split(`
61
- `).filter(y=>y.trim()),g=new G(r);let u=0,h=0;for(const y of f){const[,w,$,R]=y.split(""),_=w??"";if(/^Merge /i.test(_)||/^(chore|bump|release|version)/i.test(_)){h++;continue}let E="pattern";/^fix(\(.+\))?:/i.test(_)?E="lesson":/^refactor(\(.+\))?:/i.test(_)&&(E="heuristic");const k=_.split(":")[0]??"commit";g.add(p,{kind:E,problem:_,whyItWorked:`Committed by ${$??"unknown"} on ${R??"unknown"}`,tags:["git","commit",k.toLowerCase()],confidence:.6,needsReview:1}),u++}n.close(),b(`Imported ${u} commits (${h} skipped). Run ${d.cyan("wyrm review")} to activate.`)}else if(o==="rules"){const i=e[0],n=s.format??"plain";i||(m("Usage: wyrm import rules <path> [--project <name>] [--format cursorrules|copilot|plain]"),process.exit(1)),H(i)||(m(`File not found: ${i}`),process.exit(1));const r=B(i,"utf-8"),p=S(),c=p.getDatabase();let f=null;if(t){const k=T(p,t);k||(m(`Project not found: ${t}`),p.close(),process.exit(1)),f=k.id}else{const k=c.prepare("SELECT p.* FROM projects p JOIN sessions s ON s.project_id = p.id ORDER BY s.created_at DESC LIMIT 1").get();k&&(f=k.id)}f||(m("No project found. Use --project <name>"),p.close(),process.exit(1));const g=i.split("/").pop()??"rules",u=["imported",n,g],h=r.split(/\n(?=#)/),y=r.split(/\n\n+/),w=(h.length>=y.length?h:y).map(k=>k.trim()).filter(k=>k.length>=15),$=new G(c),R=new pe(c);me(R,new ue(c));let _=0,E=0;for(const k of w)/\b(always|never|must|use|don't|avoid|prefer)\b/i.test(k)?(R.set(f,{category:"constraint",key:k.slice(0,50).replace(/\n/g," "),value:k,source:g}),_++):($.add(f,{kind:"heuristic",problem:k,tags:u,confidence:.7,needsReview:1}),E++);p.close(),b(`Imported ${_} ground truths + ${E} artifacts (pending review).`)}else m("Usage: wyrm import git [--project <name>] [--last N]"),m(" wyrm import rules <path> [--project <name>] [--format cursorrules|copilot|plain]"),m(` wyrm import --from ${ye.join("|")} <file.json> [--project <name>]`),process.exit(1)}async function st(l){const a=l.flags.from,e=l.positional[0],s=l.flags.project,o=`Usage: wyrm import --from ${ye.join("|")} <file.json> [--project <name>]`;e||(m(o),process.exit(1)),H(e)||(m(`File not found: ${e}`),process.exit(1));let t;try{t=JSON.parse(B(e,"utf-8"))}catch(r){m(`Could not parse JSON from ${e}: ${r.message}`),process.exit(1)}let i;try{i=Ue(a,t)}catch(r){m(r.message),process.exit(1)}const n=S();try{let r=s?T(n,s):null;if(r||(r=n.getProject(process.cwd())??null),!r){m(s?`Project not found: ${s}`:"wyrm import: no Wyrm project for this directory (pass --project <name>)."),process.exitCode=1;return}if(i.length===0){m(`No importable memories found in ${e} for source "${a}".`);return}const p=n.getDatabase(),c=new G(p);try{const{createVectorStore:g}=await import("./vectors.js"),u=process.env.WYRM_VECTOR_PROVIDER??"auto";c.setVectorStore(g({provider:u},p))}catch{}let f=0;for(const g of i)c.add(r.id,{kind:"lesson",problem:g.text,tags:g.tags,constraints:g.metadata?JSON.stringify(g.metadata):void 0,whyItWorked:`Imported from ${g.source}`,outcome:"neutral",confidence:.6,needsReview:1}),f++;b(`Imported ${f} ${f===1?"memory":"memories"} from ${a} into ${d.cyan(r.name)} review queue. Run ${d.cyan("wyrm review")} to vet (tagged ${d.dim(`imported_from:${i[0]?.source??a}`)}).`)}finally{n.close()}}async function nt(l){const{flags:a}=C(l),e=a.project,s=S(),o=s.getDatabase();if(j("Wyrm Statistics"),e){const t=T(s,e);t||(m(`Project not found: ${e}`),s.close(),process.exit(1));const i=s.getProjectStats(t.id),n=[["Sessions",String(i.sessions)],["Quests (pending)",String(i.quests.pending)],["Quests (completed)",String(i.quests.completed)],["Data Points",String(i.dataPoints)]];console.log(D(["Metric","Value"],n))}else{const t=s.getStats(),i=[["Projects",String(t.projects)],["Sessions",String(t.sessions)],["Quests",String(t.quests)],["Data Points",String(t.dataPoints)],["DB Size",t.dbSize]],n=o.prepare("SELECT COUNT(*) as n FROM memory_artifacts").get().n,r=o.prepare("SELECT COUNT(*) as n FROM ground_truths WHERE is_current = 1").get().n;i.push(["Memories",String(n)],["Ground Truths",String(r)]),console.log(D(["Metric","Value"],i))}s.close()}async function rt(l){const{flags:a}=C(l),e=a.project,s=S(),o=s.getDatabase();let t=null;if(e){const c=T(s,e);c||(m(`Project not found: ${e}`),s.close(),process.exit(1)),t=c.id}const i=t?`AND project_id = ${t}`:"",n=o.prepare(`
59
+ `);return}m("Usage: wyrm presence <announce|list|release> [options]"),process.exitCode=1}finally{n.close()}}async function tt(l){const{flags:a}=C(l),e=a.path,i=a.project,o=a.out,t=a.brief===!0,r=a.force===!0,s=a.quiet===!0,n=(typeof a.client=="string"?a.client:"").split(",").map(u=>u.trim().toLowerCase()).filter(Boolean);let p=n.filter(u=>ce.includes(u));const c=n.filter(u=>!ce.includes(u));c.length>0&&(m(`Unknown --client value(s): ${c.join(", ")} (valid: ${ce.join("|")})`),process.exit(1));const f=console.log;t&&(console.log=()=>{});const g=_();try{let u=e?g.getProject(e):void 0;!u&&i&&(u=D(g,i)??void 0),u||(u=g.getProject(process.cwd())),u||(console.log=f,m("wyrm render: no Wyrm project for this directory (use --path or --project)."),process.exit(1));const h=fe(g.getDatabase()),y={wyrm_version:M().version??"unknown",compiled_at:new Date().toISOString()};if(t){const j=ie(h,u,y);console.log=f,process.stdout.write(j.sessionBrief+`
60
+ `);return}const w=o??u.path;p.length===0&&n.length===0&&(p=Oe(Le.filter(j=>H(q(w,j)))));{const j=await import("./reverse-bridge.js"),E=ie(h,u,y),k={"MEMORY.md":E.memoryMd};for(const L of p){const W=ge(L,E.model,y);k[W.relPath]=W.block}try{const L=j.makeBridgeDeps(g.getDatabase()),W=await j.sweepProject(L,{id:u.id,path:u.path},k,{rootDir:w});W.added>0&&!s&&console.log(` ${Y.warning} harvested ${W.added} human edit(s) \u2192 review queue before overwrite`)}catch{}}const{plan:$,writes:R}=Ae(h,u,y,{rootDir:w,clients:p,force:r});if(!s){b(`Rendered ${u.name} memory (${$.model.truths.length} truths, ${$.model.failures.length} failures, ${$.model.quests.length} quests, ${$.model.artifacts.length} patterns) to ${w}`);for(const j of R){const E=j.action==="created"?Y.success:j.action==="updated"?Y.info:Y.warning;console.log(` ${E} ${j.action.padEnd(7)} ${j.path}${j.reason?` (${j.reason})`:""}`)}}}finally{g.close(),console.log=f}}async function ot(l){const{flags:a}=C(l),e=a.path,i=a.project,o=a.root,t=a["dry-run"]===!0||a.dry===!0,r=(typeof a.client=="string"?a.client:"").split(",").map(f=>f.trim().toLowerCase()).filter(Boolean),s=["claude","cursor","copilot","agents"],n=r.filter(f=>s.includes(f)),p=await import("./reverse-bridge.js"),c=_();try{let f=e?c.getProject(e):void 0;if(!f&&i&&(f=D(c,i)??void 0),f||(f=c.getProject(process.cwd())),!f){m("wyrm reverse-bridge: no Wyrm project for this directory (use --path or --project)."),process.exitCode=1;return}const g=fe(c.getDatabase()),u={wyrm_version:M().version??"unknown",compiled_at:new Date().toISOString()},h=ie(g,f,u),y={"MEMORY.md":h.memoryMd};for(const R of n){const j=ge(R,h.model,u);y[j.relPath]=j.block}const w=p.makeBridgeDeps(c.getDatabase()),$=await p.sweepProject(w,{id:f.id,path:f.path},y,{dryRun:t,rootDir:o});S(`Reverse bridge ${t?"(dry run) ":""}\u2014 ${$.added} candidate(s) queued, ${$.skipped} already present (${$.filesWithEdits}/${$.filesScanned} file(s) with edits)`);for(const R of $.sample)console.log(` ${Y.bullet} ${R}`);!t&&$.added>0&&b("Review with: wyrm review")}finally{c.close()}}async function st(l){const a=C(l);if(typeof a.flags.from=="string"){await nt(a);return}const{positional:e,flags:i}=C(l.slice(1)),o=l[0],t=i.project;if(o==="git"){const r=T(i.last,20),s=_(),n=s.getDatabase();let p=null;if(t){const y=D(s,t);y||(m(`Project not found: ${t}`),s.close(),process.exit(1)),p=y.id}else{const y=n.prepare("SELECT p.* FROM projects p JOIN sessions s ON s.project_id = p.id ORDER BY s.created_at DESC LIMIT 1").get();y&&(p=y.id)}p||(m("No project found. Use --project <name>"),s.close(),process.exit(1));const c=z("git",["log","--pretty=format:%H%x1f%s%x1f%an%x1f%ai",`-${r}`],{cwd:process.cwd(),encoding:"utf-8",timeout:1e4,shell:!1});(c.error||c.status!==0)&&(m("git log failed. Make sure you are in a git repository."),s.close(),process.exit(1));const f=c.stdout.split(`
61
+ `).filter(y=>y.trim()),g=new G(n);let u=0,h=0;for(const y of f){const[,w,$,R]=y.split(""),j=w??"";if(/^Merge /i.test(j)||/^(chore|bump|release|version)/i.test(j)){h++;continue}let E="pattern";/^fix(\(.+\))?:/i.test(j)?E="lesson":/^refactor(\(.+\))?:/i.test(j)&&(E="heuristic");const k=j.split(":")[0]??"commit";g.add(p,{kind:E,problem:j,whyItWorked:`Committed by ${$??"unknown"} on ${R??"unknown"}`,tags:["git","commit",k.toLowerCase()],confidence:.6,needsReview:1}),u++}s.close(),b(`Imported ${u} commits (${h} skipped). Run ${d.cyan("wyrm review")} to activate.`)}else if(o==="rules"){const r=e[0],s=i.format??"plain";r||(m("Usage: wyrm import rules <path> [--project <name>] [--format cursorrules|copilot|plain]"),process.exit(1)),H(r)||(m(`File not found: ${r}`),process.exit(1));const n=B(r,"utf-8"),p=_(),c=p.getDatabase();let f=null;if(t){const k=D(p,t);k||(m(`Project not found: ${t}`),p.close(),process.exit(1)),f=k.id}else{const k=c.prepare("SELECT p.* FROM projects p JOIN sessions s ON s.project_id = p.id ORDER BY s.created_at DESC LIMIT 1").get();k&&(f=k.id)}f||(m("No project found. Use --project <name>"),p.close(),process.exit(1));const g=r.split("/").pop()??"rules",u=["imported",s,g],h=n.split(/\n(?=#)/),y=n.split(/\n\n+/),w=(h.length>=y.length?h:y).map(k=>k.trim()).filter(k=>k.length>=15),$=new G(c),R=new pe(c);me(R,new ue(c));let j=0,E=0;for(const k of w)/\b(always|never|must|use|don't|avoid|prefer)\b/i.test(k)?(R.set(f,{category:"constraint",key:k.slice(0,50).replace(/\n/g," "),value:k,source:g}),j++):($.add(f,{kind:"heuristic",problem:k,tags:u,confidence:.7,needsReview:1}),E++);p.close(),b(`Imported ${j} ground truths + ${E} artifacts (pending review).`)}else m("Usage: wyrm import git [--project <name>] [--last N]"),m(" wyrm import rules <path> [--project <name>] [--format cursorrules|copilot|plain]"),m(` wyrm import --from ${ye.join("|")} <file.json> [--project <name>]`),process.exit(1)}async function nt(l){const a=l.flags.from,e=l.positional[0],i=l.flags.project,o=`Usage: wyrm import --from ${ye.join("|")} <file.json> [--project <name>]`;e||(m(o),process.exit(1)),H(e)||(m(`File not found: ${e}`),process.exit(1));let t;try{t=JSON.parse(B(e,"utf-8"))}catch(n){m(`Could not parse JSON from ${e}: ${n.message}`),process.exit(1)}let r;try{r=Ue(a,t)}catch(n){m(n.message),process.exit(1)}const s=_();try{let n=i?D(s,i):null;if(n||(n=s.getProject(process.cwd())??null),!n){m(i?`Project not found: ${i}`:"wyrm import: no Wyrm project for this directory (pass --project <name>)."),process.exitCode=1;return}if(r.length===0){m(`No importable memories found in ${e} for source "${a}".`);return}const p=s.getDatabase(),c=new G(p);try{const{createVectorStore:g}=await import("./vectors.js"),u=process.env.WYRM_VECTOR_PROVIDER??"auto";c.setVectorStore(g({provider:u},p))}catch{}let f=0;for(const g of r)c.add(n.id,{kind:"lesson",problem:g.text,tags:g.tags,constraints:g.metadata?JSON.stringify(g.metadata):void 0,whyItWorked:`Imported from ${g.source}`,outcome:"neutral",confidence:.6,needsReview:1}),f++;b(`Imported ${f} ${f===1?"memory":"memories"} from ${a} into ${d.cyan(n.name)} review queue. Run ${d.cyan("wyrm review")} to vet (tagged ${d.dim(`imported_from:${r[0]?.source??a}`)}).`)}finally{s.close()}}async function rt(l){const{flags:a}=C(l),e=a.project,i=_(),o=i.getDatabase();if(S("Wyrm Statistics"),e){const t=D(i,e);t||(m(`Project not found: ${e}`),i.close(),process.exit(1));const r=i.getProjectStats(t.id),s=[["Sessions",String(r.sessions)],["Quests (pending)",String(r.quests.pending)],["Quests (completed)",String(r.quests.completed)],["Data Points",String(r.dataPoints)]];console.log(P(["Metric","Value"],s))}else{const t=i.getStats(),r=[["Projects",String(t.projects)],["Sessions",String(t.sessions)],["Quests",String(t.quests)],["Data Points",String(t.dataPoints)],["DB Size",t.dbSize]],s=o.prepare("SELECT COUNT(*) as n FROM memory_artifacts").get().n,n=o.prepare("SELECT COUNT(*) as n FROM ground_truths WHERE is_current = 1").get().n;r.push(["Memories",String(s)],["Ground Truths",String(n)]),console.log(P(["Metric","Value"],r))}i.close()}async function it(l){const{flags:a}=C(l),e=a.project,i=_(),o=i.getDatabase();let t=null;if(e){const c=D(i,e);c||(m(`Project not found: ${e}`),i.close(),process.exit(1)),t=c.id}const r=t?`AND project_id = ${t}`:"",s=o.prepare(`
62
62
  SELECT id, kind, problem FROM memory_artifacts
63
- WHERE needs_review = 1 ${i}
63
+ WHERE needs_review = 1 ${r}
64
64
  ORDER BY created_at ASC
65
- `).all();if(n.length===0){console.log(d.dim(` No artifacts pending review${e?` for ${e}`:""}.`)),s.close();return}j(`Review Queue (${n.length} items)`);const r=Q({input:process.stdin,output:process.stdout}),p=c=>new Promise(f=>{r.question(c,f)});for(const c of n){console.log(`
66
- ${d.bold(`[${c.kind}] #${c.id}`)}`),console.log(d.dim("\u2500".repeat(60))),console.log(c.problem.slice(0,300)),console.log(d.dim("\u2500".repeat(60)));const g=(await p(`${d.cyan("[a]")}pprove / ${d.red("[r]")}eject / ${d.yellow("[s]")}kip? `)).trim().toLowerCase();g==="a"?(o.prepare("UPDATE memory_artifacts SET needs_review = 0, updated_at = datetime('now') WHERE id = ?").run(c.id),b(`Approved #${c.id}`)):g==="r"?(o.prepare("DELETE FROM memory_artifacts WHERE id = ?").run(c.id),console.log(`${Y.cross} Rejected #${c.id}`)):console.log(d.dim(` Skipped #${c.id}`))}r.close(),s.close(),console.log(`
67
- Review complete.`)}async function it(l){const{positional:a,flags:e}=C(l),s=a[0];(!s||!["export","import","preview"].includes(s))&&(m("Usage: wyrm sync export --out <path> | wyrm sync import --from <path> | wyrm sync preview --from <path>"),process.exit(1));const{randomBytes:o,pbkdf2Sync:t,createCipheriv:i,createDecipheriv:n}=await import("crypto"),{readFileSync:r,writeFileSync:p,copyFileSync:c,unlinkSync:f,existsSync:g,chmodSync:u}=await import("fs"),{homedir:h}=await import("os"),{join:y}=await import("path"),w=(await import("better-sqlite3")).default;let $=process.env.WYRM_SYNC_PASSPHRASE??"";if(!$){const x=Q({input:process.stdin,output:process.stdout});$=await new Promise(P=>{process.stdout.write("Passphrase: "),process.stdin.isTTY&&process.stdin.setRawMode?.(!0),x.question("",U=>{process.stdin.isTTY&&process.stdin.setRawMode?.(!1),console.log(""),x.close(),P(U)})})}$||(m("Passphrase is required. Set WYRM_SYNC_PASSPHRASE or enter interactively."),process.exit(1));const R=y(h(),".wyrm"),_=S();if(s==="export"){const x=e.out;x||(m("--out <path> is required"),process.exit(1));const P=y(R,"wyrm_cli_export_temp.db");try{const U=_.getDatabase();g(P)&&f(P),U.prepare("VACUUM INTO ?").run(P);const K=r(P),F=o(32),J=o(16),Re=t($,F,6e5,32,"sha256"),se=i("aes-256-gcm",Re,J),Ee=Buffer.concat([se.update(K),se.final()]),xe=se.getAuthTag(),Te=Buffer.from("WYRM"),le=Buffer.alloc(1);le.writeUInt8(1,0);const de=Buffer.concat([Te,le,F,J,xe,Ee]);p(x,de);try{u(x,384)}catch{}try{f(P)}catch{}const De=(de.length/(1024*1024)).toFixed(2);b(`Exported to ${x} (${De} MB)`)}catch(U){try{g(P)&&f(P)}catch{}m(`Export failed: ${U}`)}_.close();return}const E=e.from;E||(m("--from <path> is required"),process.exit(1));const k=r(E);k.subarray(0,4).toString("ascii")!=="WYRM"&&(m("Invalid Wyrm snapshot file."),process.exit(1));const L=k.readUInt8(4);L!==1&&(m(`Unsupported snapshot version: ${L}`),process.exit(1));const W=k.subarray(5,37),ve=k.subarray(37,53),ke=k.subarray(53,69),_e=k.subarray(69),je=t($,W,6e5,32,"sha256"),X=n("aes-256-gcm",je,ve);X.setAuthTag(ke);let ee;try{ee=Buffer.concat([X.update(_e),X.final()])}catch{m("Decryption failed \u2014 wrong passphrase or corrupted file."),process.exit(1)}if(s==="preview"){const x=y(R,"wyrm_cli_preview_temp.db");g(x)&&f(x),p(x,ee);try{const P=new w(x,{readonly:!0}),U=["projects","sessions","ground_truths","memory_artifacts","quests"];j("Snapshot Preview");const K=[];for(const F of U)try{const J=P.prepare(`SELECT COUNT(*) as n FROM ${F}`).get();K.push([F,String(J.n)])}catch{K.push([F,"?"])}console.log(D(["Table","Count"],K)),P.close()}catch(P){m(`Preview failed: ${P}`)}try{g(x)&&f(x)}catch{}_.close();return}const V=_.getDatabasePath(),ae=Q({input:process.stdin,output:process.stdout}),Se=await new Promise(x=>{ae.question("This will REPLACE your current database. Type CONFIRM to proceed: ",x)});if(ae.close(),Se.trim()!=="CONFIRM"){console.log(d.dim("Aborted.")),_.close();return}const Ce=new Date().toISOString().replace(/[:.]/g,"-"),te=`${V}.backup.${Ce}`;c(V,te),b(`Backed up to ${te}`);const oe=y(R,"wyrm_cli_restore_temp.db");p(oe,ee),_.getDatabase().close(),c(oe,V);for(const x of["-wal","-shm"])try{g(V+x)&&f(V+x)}catch{}try{f(oe)}catch{}b(`Restored from ${E}. Backup at ${te}`)}async function ct(l){const{flags:a}=C(l),e=a.project,s=a.path,o=Z(a["min-confidence"],.3),t=I(a["older-than"],90),i=a["no-dry-run"]===!0,n=a.yes===!0,r=S(),p=r.getDatabase();let c=null;if(s||e){const y=s?r.getProject(s):T(r,e);y||(m(`Project not found: ${s??e}`),r.close(),process.exit(1)),c=y.id}const f=new G(p),{candidates:g}=f.pruneStale({projectId:c,minConfidence:o,olderThanDays:t,dryRun:!0});if(j(`Prune Candidates${i?" (LIVE DELETE)":" (dry-run)"}`),g.length===0){console.log(d.dim(" No artifacts match prune criteria.")),r.close();return}const u=g.map(y=>[String(y.id),y.kind,y.problem.slice(0,60),(y.confidence*100).toFixed(0)+"%",y.last_accessed_at??"never"]);if(console.log(D(["ID","Kind","Problem","Conf","Last Accessed"],u)),console.log(`
68
- Total: ${g.length} candidate(s)`),!i){console.log(d.dim(`
69
- This is a dry run. Use --no-dry-run to delete (confirm each ID).`)),r.close();return}if(!n){const y=Q({input:process.stdin,output:process.stdout}),w=await new Promise($=>{y.question(`
70
- Delete these ${g.length} artifact(s)? Type CONFIRM to proceed: `,$)});if(y.close(),w.trim()!=="CONFIRM"){console.log(d.dim("Aborted.")),r.close();return}}const h=f.deleteArtifacts(g.map(y=>y.id));b(`Deleted ${h} artifact(s).`),r.close()}async function at(l){const{positional:a,flags:e}=C(l),s=a[0]||"list",o=S();try{if(s==="list"||s==="ls"){const t=he(o);if(t.length===0){j("Connectors"),console.log(`${N.dim} none configured. Add one with: wyrm connector add --name <n> --url <bridge-url> --workspace <w>${N.reset}`);return}j("Connectors");const i=t.map(n=>[n.name,n.source,n.enabled?"on":"off",n.workspace,ft(n.baseUrl),n.allowlist.length?`${n.allowlist.length} chats`:"all"]);console.log(D(["Name","Source","Enabled","Workspace","Bridge","Allowlist"],i));return}if(s==="add"){const t=O(e.name),i=O(e.url),n=O(e.workspace);if(!t||!i||!n){m("usage: wyrm connector add --name <n> --url <bridge-url> --workspace <w> [--allowlist a,b] [--token T] [--disabled]"),process.exitCode=1;return}const r={name:t,source:"bridge",enabled:!e.disabled,baseUrl:i,workspace:n,allowlist:O(e.allowlist)?O(e.allowlist).split(",").map(p=>p.trim()).filter(Boolean):[],...O(e.token)?{authToken:O(e.token)}:{}};qe(o,r),b(`Connector "${t}" saved (source=bridge, workspace=${n}). Token, if any, stored without being printed.`),console.log(`${N.dim} Tip: prefer the env var WYRM_CONNECTOR_TOKEN_${t.toUpperCase().replace(/[^A-Z0-9]/g,"_")} over --token.${N.reset}`);return}if(s==="sync"){const t=a[1]||O(e.name);if(t){const n=he(o).find(p=>p.name===t);if(!n){m(`Connector not found: ${t}`),process.exitCode=1;return}const r=await Ye(o,n);b(`${r.source}: fetched ${r.fetched}, ingested ${r.ingested}, skipped ${r.skipped} -> ${r.workspace}`);return}const i=await Fe(o);if(i.length===0){console.log(`${N.dim} no enabled connectors.${N.reset}`);return}for(const n of i)"error"in n?m(`${n.name}: ${n.error}`):b(`${n.source}: fetched ${n.fetched}, ingested ${n.ingested}, skipped ${n.skipped} -> ${n.workspace}`);return}m(`Unknown connector subcommand: ${s} (use list | add | sync)`),process.exitCode=1}finally{o.close()}}function we(){return B(0,"utf-8").trim()}async function lt(){const{initializeLicense:l,getLicenseInfo:a,getTier:e}=await import("./license.js");l();const s=a();j("Wyrm License");const o=[["Tier",e()],["Status",s.valid?"valid":"free tier (no license key)"]];s.valid&&s.key&&(o.push(["Key",s.key]),o.push(["Issued to",s.issuedTo??"unknown"]),o.push(["Expires",s.expiresAt?new Date(s.expiresAt).toLocaleDateString():"never"])),o.push(["Features",s.features.join(", ")||"(free)"]),console.log(D(["Field","Value"],o)),s.valid||console.log(d.dim(`
65
+ `).all();if(s.length===0){console.log(d.dim(` No artifacts pending review${e?` for ${e}`:""}.`)),i.close();return}S(`Review Queue (${s.length} items)`);const n=Q({input:process.stdin,output:process.stdout}),p=c=>new Promise(f=>{n.question(c,f)});for(const c of s){console.log(`
66
+ ${d.bold(`[${c.kind}] #${c.id}`)}`),console.log(d.dim("\u2500".repeat(60))),console.log(c.problem.slice(0,300)),console.log(d.dim("\u2500".repeat(60)));const g=(await p(`${d.cyan("[a]")}pprove / ${d.red("[r]")}eject / ${d.yellow("[s]")}kip? `)).trim().toLowerCase();g==="a"?(o.prepare("UPDATE memory_artifacts SET needs_review = 0, updated_at = datetime('now') WHERE id = ?").run(c.id),b(`Approved #${c.id}`)):g==="r"?(o.prepare("DELETE FROM memory_artifacts WHERE id = ?").run(c.id),console.log(`${Y.cross} Rejected #${c.id}`)):console.log(d.dim(` Skipped #${c.id}`))}n.close(),i.close(),console.log(`
67
+ Review complete.`)}async function ct(l){const{positional:a,flags:e}=C(l),i=a[0];(!i||!["export","import","preview"].includes(i))&&(m("Usage: wyrm sync export --out <path> | wyrm sync import --from <path> | wyrm sync preview --from <path>"),process.exit(1));const{randomBytes:o,pbkdf2Sync:t,createCipheriv:r,createDecipheriv:s}=await import("crypto"),{readFileSync:n,writeFileSync:p,copyFileSync:c,unlinkSync:f,existsSync:g,chmodSync:u}=await import("fs"),{homedir:h}=await import("os"),{join:y}=await import("path"),w=(await import("better-sqlite3")).default;let $=process.env.WYRM_SYNC_PASSPHRASE??"";if(!$){const x=Q({input:process.stdin,output:process.stdout});$=await new Promise(I=>{process.stdout.write("Passphrase: "),process.stdin.isTTY&&process.stdin.setRawMode?.(!0),x.question("",U=>{process.stdin.isTTY&&process.stdin.setRawMode?.(!1),console.log(""),x.close(),I(U)})})}$||(m("Passphrase is required. Set WYRM_SYNC_PASSPHRASE or enter interactively."),process.exit(1));const R=y(h(),".wyrm"),j=_();if(i==="export"){const x=e.out;x||(m("--out <path> is required"),process.exit(1));const I=y(R,"wyrm_cli_export_temp.db");try{const U=j.getDatabase();g(I)&&f(I),U.prepare("VACUUM INTO ?").run(I);const K=n(I),F=o(32),J=o(16),Re=t($,F,6e5,32,"sha256"),se=r("aes-256-gcm",Re,J),Ee=Buffer.concat([se.update(K),se.final()]),xe=se.getAuthTag(),De=Buffer.from("WYRM"),le=Buffer.alloc(1);le.writeUInt8(1,0);const de=Buffer.concat([De,le,F,J,xe,Ee]);p(x,de);try{u(x,384)}catch{}try{f(I)}catch{}const Pe=(de.length/(1024*1024)).toFixed(2);b(`Exported to ${x} (${Pe} MB)`)}catch(U){try{g(I)&&f(I)}catch{}m(`Export failed: ${U}`)}j.close();return}const E=e.from;E||(m("--from <path> is required"),process.exit(1));const k=n(E);k.subarray(0,4).toString("ascii")!=="WYRM"&&(m("Invalid Wyrm snapshot file."),process.exit(1));const L=k.readUInt8(4);L!==1&&(m(`Unsupported snapshot version: ${L}`),process.exit(1));const W=k.subarray(5,37),ve=k.subarray(37,53),ke=k.subarray(53,69),je=k.subarray(69),Se=t($,W,6e5,32,"sha256"),X=s("aes-256-gcm",Se,ve);X.setAuthTag(ke);let ee;try{ee=Buffer.concat([X.update(je),X.final()])}catch{m("Decryption failed \u2014 wrong passphrase or corrupted file."),process.exit(1)}if(i==="preview"){const x=y(R,"wyrm_cli_preview_temp.db");g(x)&&f(x),p(x,ee);try{const I=new w(x,{readonly:!0}),U=["projects","sessions","ground_truths","memory_artifacts","quests"];S("Snapshot Preview");const K=[];for(const F of U)try{const J=I.prepare(`SELECT COUNT(*) as n FROM ${F}`).get();K.push([F,String(J.n)])}catch{K.push([F,"?"])}console.log(P(["Table","Count"],K)),I.close()}catch(I){m(`Preview failed: ${I}`)}try{g(x)&&f(x)}catch{}j.close();return}const V=j.getDatabasePath(),ae=Q({input:process.stdin,output:process.stdout}),_e=await new Promise(x=>{ae.question("This will REPLACE your current database. Type CONFIRM to proceed: ",x)});if(ae.close(),_e.trim()!=="CONFIRM"){console.log(d.dim("Aborted.")),j.close();return}const Ce=new Date().toISOString().replace(/[:.]/g,"-"),te=`${V}.backup.${Ce}`;c(V,te),b(`Backed up to ${te}`);const oe=y(R,"wyrm_cli_restore_temp.db");p(oe,ee),j.getDatabase().close(),c(oe,V);for(const x of["-wal","-shm"])try{g(V+x)&&f(V+x)}catch{}try{f(oe)}catch{}b(`Restored from ${E}. Backup at ${te}`)}async function at(l){const{flags:a}=C(l),e=a.project,i=a.path,o=Z(a["min-confidence"],.3),t=T(a["older-than"],90),r=a["no-dry-run"]===!0,s=a.yes===!0,n=_(),p=n.getDatabase();let c=null;if(i||e){const y=i?n.getProject(i):D(n,e);y||(m(`Project not found: ${i??e}`),n.close(),process.exit(1)),c=y.id}const f=new G(p),{candidates:g}=f.pruneStale({projectId:c,minConfidence:o,olderThanDays:t,dryRun:!0});if(S(`Prune Candidates${r?" (LIVE DELETE)":" (dry-run)"}`),g.length===0){console.log(d.dim(" No artifacts match prune criteria.")),n.close();return}const u=g.map(y=>[String(y.id),y.kind,y.problem.slice(0,60),(y.confidence*100).toFixed(0)+"%",y.last_accessed_at??"never"]);if(console.log(P(["ID","Kind","Problem","Conf","Last Accessed"],u)),console.log(`
68
+ Total: ${g.length} candidate(s)`),!r){console.log(d.dim(`
69
+ This is a dry run. Use --no-dry-run to delete (confirm each ID).`)),n.close();return}if(!s){const y=Q({input:process.stdin,output:process.stdout}),w=await new Promise($=>{y.question(`
70
+ Delete these ${g.length} artifact(s)? Type CONFIRM to proceed: `,$)});if(y.close(),w.trim()!=="CONFIRM"){console.log(d.dim("Aborted.")),n.close();return}}const h=f.deleteArtifacts(g.map(y=>y.id));b(`Deleted ${h} artifact(s).`),n.close()}async function lt(l){const{positional:a,flags:e}=C(l),i=a[0]||"list",o=_();try{if(i==="list"||i==="ls"){const t=he(o);if(t.length===0){S("Connectors"),console.log(`${N.dim} none configured. Add one with: wyrm connector add --name <n> --url <bridge-url> --workspace <w>${N.reset}`);return}S("Connectors");const r=t.map(s=>[s.name,s.source,s.enabled?"on":"off",s.workspace,gt(s.baseUrl),s.allowlist.length?`${s.allowlist.length} chats`:"all"]);console.log(P(["Name","Source","Enabled","Workspace","Bridge","Allowlist"],r));return}if(i==="add"){const t=O(e.name),r=O(e.url),s=O(e.workspace);if(!t||!r||!s){m("usage: wyrm connector add --name <n> --url <bridge-url> --workspace <w> [--allowlist a,b] [--token T] [--disabled]"),process.exitCode=1;return}const n={name:t,source:"bridge",enabled:!e.disabled,baseUrl:r,workspace:s,allowlist:O(e.allowlist)?O(e.allowlist).split(",").map(p=>p.trim()).filter(Boolean):[],...O(e.token)?{authToken:O(e.token)}:{}};qe(o,n),b(`Connector "${t}" saved (source=bridge, workspace=${s}). Token, if any, stored without being printed.`),console.log(`${N.dim} Tip: prefer the env var WYRM_CONNECTOR_TOKEN_${t.toUpperCase().replace(/[^A-Z0-9]/g,"_")} over --token.${N.reset}`);return}if(i==="sync"){const t=a[1]||O(e.name);if(t){const s=he(o).find(p=>p.name===t);if(!s){m(`Connector not found: ${t}`),process.exitCode=1;return}const n=await Ye(o,s);b(`${n.source}: fetched ${n.fetched}, ingested ${n.ingested}, skipped ${n.skipped} -> ${n.workspace}`);return}const r=await Fe(o);if(r.length===0){console.log(`${N.dim} no enabled connectors.${N.reset}`);return}for(const s of r)"error"in s?m(`${s.name}: ${s.error}`):b(`${s.source}: fetched ${s.fetched}, ingested ${s.ingested}, skipped ${s.skipped} -> ${s.workspace}`);return}m(`Unknown connector subcommand: ${i} (use list | add | sync)`),process.exitCode=1}finally{o.close()}}function we(){return B(0,"utf-8").trim()}async function dt(){const{initializeLicense:l,getLicenseInfo:a,getTier:e}=await import("./license.js");l();const i=a();S("Wyrm License");const o=[["Tier",e()],["Status",i.valid?"valid":"free tier (no license key)"]];i.valid&&i.key&&(o.push(["Key",i.key]),o.push(["Issued to",i.issuedTo??"unknown"]),o.push(["Expires",i.expiresAt?new Date(i.expiresAt).toLocaleDateString():"never"])),o.push(["Features",i.features.join(", ")||"(free)"]),console.log(P(["Field","Value"],o)),i.valid||console.log(d.dim(`
71
71
  Activate with: wyrm login (free) \xB7 or: wyrm activate <license.json | key>`)),console.log(d.dim(`
72
- \xA9 2026 Ghost Protocol (Pvt) Ltd \xB7 Proprietary \xB7 https://wyrm.ghosts.lk`)),console.log(d.dim(" Licensed under the Wyrm Terms of Service. No open-source license is granted."))}async function dt(){const l=(process.env.WYRM_ACCOUNT_URL??"https://account.ghosts.lk").replace(/\/$/,""),a=(()=>{try{return A().version??"unknown"}catch{return"unknown"}})();let e;try{const c=await fetch(`${l}/api/v1/cli/auth/start`,{method:"POST",headers:{"x-wyrm-version":a}});if(!c.ok)throw new Error(`HTTP ${c.status}`);e=await c.json()}catch(c){m(`Couldn't reach ${l} (${c instanceof Error?c.message:"network error"}).`),process.exitCode=1;return}const s=e.verification_uri_complete||e.verification_uri||`${l}/cli`;console.log(`
73
- ${d.cyan("Sign in to activate Wyrm (free):")}`),console.log(` 1. Open ${d.cyan(s)}`),console.log(` 2. Approve the code ${d.cyan(e.user_code)}`),console.log(d.dim(`
74
- Waiting for approval\u2026 (Ctrl-C to cancel)`));const o=(e.interval??3)*1e3,t=Date.now()+(e.expires_in??600)*1e3;let i="";for(;Date.now()<t;){await new Promise(c=>{setTimeout(c,o)});try{const f=await(await fetch(`${l}/api/v1/cli/auth/poll`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({device_code:e.device_code})})).json();if(f.status==="approved"&&f.token){i=f.token;break}if(f.status==="denied"||f.error==="expired"){m("Login was denied or the code expired. Run `wyrm login` again."),process.exitCode=1;return}}catch{}}if(!i){m("Login timed out. Run `wyrm login` again."),process.exitCode=1;return}let n;try{const c=await fetch(`${l}/api/v1/license/free`,{method:"POST",headers:{authorization:`Bearer ${i}`,"x-wyrm-version":a}});if(!c.ok){const f=await c.json().catch(()=>({}));m(`Activation failed (${c.status}): ${f.hint||f.error||"unknown error"}`),process.exitCode=1;return}n=JSON.stringify(await c.json())}catch(c){m(`Activation request failed (${c instanceof Error?c.message:"network error"}).`),process.exitCode=1;return}const{activateLicense:r}=await import("./license.js"),p=r(n);p.valid?(b(`Signed in & activated \u2014 ${p.tier} tier (expires ${p.expiresAt??"never"}).`),console.log(d.dim(" Restart the Wyrm MCP server / daemon to apply."))):(m(`Activation failed: ${p.error??"unknown error"}`),process.exitCode=1)}async function pt(l){const{positional:a}=C(l),e=a[0];let s;e&&H(e)?s=B(e,"utf-8"):e?s=e:process.stdin.isTTY?(m("Usage: wyrm activate <license.json path | license JSON> (or pipe the JSON on stdin)"),process.exit(1)):s=we();const{activateLicense:o}=await import("./license.js");try{const t=o(s);t.valid?(b(`License activated \u2014 ${t.tier} tier (${t.features.join(", ")})`),console.log(d.dim(" Restart Wyrm (MCP server / daemon) to apply all features."))):(m(`License activation failed: ${t.error??"unknown error"}`),process.exitCode=1)}catch{m("Invalid license format. Please verify your license key."),process.exitCode=1}}async function ut(l){const{flags:a}=C(l),{runMaintenance:e}=await import("./maintenance.js"),{FailurePatterns:s}=await import("./failure-patterns.js"),{SessionSeen:o}=await import("./session-seen.js"),{AgentPresence:t}=await import("./presence.js"),i=S();try{const n=i.getDatabase(),r=I(a["archive-days"],0),p=e({db:i,sessionSeen:new o(n),failures:new s(n),presence:new t(n)},{vacuum:a.vacuum===!0,archiveDays:r>0?r:void 0});j("Maintenance complete");for(const c of p.lines)console.log(` - ${c}`);console.log(d.dim(`
75
- Database size: ${p.dbSize}`))}finally{i.close()}}async function mt(l){const{positional:a,flags:e}=C(l),s=a[0]??"status",{createVectorStore:o}=await import("./vectors.js"),i={provider:e.provider??process.env.WYRM_VECTOR_PROVIDER??"auto",model:e.model,apiKey:e["api-key"]??process.env.OPENAI_API_KEY,ollamaUrl:e["ollama-url"]};if(s==="setup"){const{createProvider:r}=await import("./providers/embedding-provider.js"),p=r(i);if(!await p.isReady()&&i.provider!=="none"){m(`Provider not ready: ${p.name}. Check the configuration and try again.`),process.exitCode=1;return}b(`Vector provider verified: ${p.name} (model ${p.model}, ${p.dimensions}d)`),console.log(d.dim(" The MCP server reads WYRM_VECTOR_PROVIDER (and provider-specific env) at boot \u2014")),console.log(d.dim(` set WYRM_VECTOR_PROVIDER=${p.name==="none"?"none":i.provider} in the server env, then: wyrm index rebuild`));return}const n=S();try{const r=n.getDatabase(),p=o(i,r);if(s==="status"){const c=p.getStats();j("Vector index");const f=[["Provider",c.provider],["Model",c.model],["Vectors",String(c.total)],...Object.entries(c.byType).map(([g,u])=>[` ${g}`,String(u)])];console.log(D(["Field","Value"],f));return}if(s==="rebuild"){const{reindexProjects:c}=await import("./reindex.js"),f=e["dry-run"]===!0,g=e.project;let u;if(g){const w=n.getProject(g)??T(n,g);if(!w){m(`Project not found: ${g}`),process.exitCode=1;return}u=[w.id]}else u=n.getAllProjects(1e3).map(w=>w.id);const{indexed:h,skipped:y}=await c(r,p,u,{dryRun:f,onError:(w,$)=>m(`${w}: ${JSON.stringify($)}`)});b(`Reindex ${f?"(dry run) ":""}\u2014 ${u.length} project(s), ${h} indexed, ${y} skipped`);return}m("Usage: wyrm index <setup|rebuild|status> [--provider auto|local|ollama|openai|none] [--model M] [--project P] [--dry-run]"),process.exitCode=1}finally{n.close()}}function O(l){return typeof l=="string"?l:""}function ft(l){try{return new URL(l).host}catch{return l}}async function gt(l){const{flags:a}=C(l),{getUpdateStatus:e}=await import("./version-check.js"),s=A().version??"0.0.0",o=S();let t;try{t=await e(o.getDatabase(),s,{force:a.force===!0||a.check===!0})}finally{o.close()}if(j("Wyrm update"),console.log(D(["Field","Value"],[["Current",t.current],["Latest",t.latest??"unknown (offline?)"],["Update available",t.updateAvailable?"yes":"no"],["Checked",`${t.checkedAt} (${t.source})`]])),a.check===!0)return;if(!t.updateAvailable&&a.force!==!0){console.log(d.dim(`
72
+ \xA9 2026 Ghost Protocol (Pvt) Ltd \xB7 Proprietary \xB7 https://wyrm.ghosts.lk`)),console.log(d.dim(" Licensed under the Wyrm Terms of Service. No open-source license is granted."))}async function pt(){const l=(process.env.WYRM_ACCOUNT_URL??"https://account.ghosts.lk").replace(/\/$/,""),a=(()=>{try{return M().version??"unknown"}catch{return"unknown"}})();let e;try{const c=await fetch(`${l}/api/v1/cli/auth/start`,{method:"POST",headers:{"x-wyrm-version":a}});if(!c.ok)throw new Error(`HTTP ${c.status}`);e=await c.json()}catch(c){m(`Couldn't reach ${l} (${c instanceof Error?c.message:"network error"}).`),process.exitCode=1;return}const i=e.verification_uri_complete||e.verification_uri||`${l}/cli`;console.log(`
73
+ ${d.cyan("Sign in to activate Wyrm (free):")}`),console.log(` 1. Open ${d.cyan(i)}`),console.log(` 2. Approve the code ${d.cyan(e.user_code)}`),console.log(d.dim(`
74
+ Waiting for approval\u2026 (Ctrl-C to cancel)`));const o=(e.interval??3)*1e3,t=Date.now()+(e.expires_in??600)*1e3;let r="";for(;Date.now()<t;){await new Promise(c=>{setTimeout(c,o)});try{const f=await(await fetch(`${l}/api/v1/cli/auth/poll`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({device_code:e.device_code})})).json();if(f.status==="approved"&&f.token){r=f.token;break}if(f.status==="denied"||f.error==="expired"){m("Login was denied or the code expired. Run `wyrm login` again."),process.exitCode=1;return}}catch{}}if(!r){m("Login timed out. Run `wyrm login` again."),process.exitCode=1;return}let s;try{const c=await fetch(`${l}/api/v1/license/free`,{method:"POST",headers:{authorization:`Bearer ${r}`,"x-wyrm-version":a}});if(!c.ok){const f=await c.json().catch(()=>({}));m(`Activation failed (${c.status}): ${f.hint||f.error||"unknown error"}`),process.exitCode=1;return}s=JSON.stringify(await c.json())}catch(c){m(`Activation request failed (${c instanceof Error?c.message:"network error"}).`),process.exitCode=1;return}const{activateLicense:n}=await import("./license.js"),p=n(s);p.valid?(b(`Signed in & activated \u2014 ${p.tier} tier (expires ${p.expiresAt??"never"}).`),console.log(d.dim(" Restart the Wyrm MCP server / daemon to apply."))):(m(`Activation failed: ${p.error??"unknown error"}`),process.exitCode=1)}async function ut(l){const{positional:a}=C(l),e=a[0];let i;e&&H(e)?i=B(e,"utf-8"):e?i=e:process.stdin.isTTY?(m("Usage: wyrm activate <license.json path | license JSON> (or pipe the JSON on stdin)"),process.exit(1)):i=we();const{activateLicense:o}=await import("./license.js");try{const t=o(i);t.valid?(b(`License activated \u2014 ${t.tier} tier (${t.features.join(", ")})`),console.log(d.dim(" Restart Wyrm (MCP server / daemon) to apply all features."))):(m(`License activation failed: ${t.error??"unknown error"}`),process.exitCode=1)}catch{m("Invalid license format. Please verify your license key."),process.exitCode=1}}async function mt(l){const{flags:a}=C(l),{runMaintenance:e}=await import("./maintenance.js"),{FailurePatterns:i}=await import("./failure-patterns.js"),{SessionSeen:o}=await import("./session-seen.js"),{AgentPresence:t}=await import("./presence.js"),r=_();try{const s=r.getDatabase(),n=T(a["archive-days"],0),p=e({db:r,sessionSeen:new o(s),failures:new i(s),presence:new t(s)},{vacuum:a.vacuum===!0,archiveDays:n>0?n:void 0});S("Maintenance complete");for(const c of p.lines)console.log(` - ${c}`);console.log(d.dim(`
75
+ Database size: ${p.dbSize}`))}finally{r.close()}}async function ft(l){const{positional:a,flags:e}=C(l),i=a[0]??"status",{createVectorStore:o}=await import("./vectors.js"),r={provider:e.provider??process.env.WYRM_VECTOR_PROVIDER??"auto",model:e.model,apiKey:e["api-key"]??process.env.OPENAI_API_KEY,ollamaUrl:e["ollama-url"]};if(i==="setup"){const{createProvider:n}=await import("./providers/embedding-provider.js"),p=n(r);if(!await p.isReady()&&r.provider!=="none"){m(`Provider not ready: ${p.name}. Check the configuration and try again.`),process.exitCode=1;return}b(`Vector provider verified: ${p.name} (model ${p.model}, ${p.dimensions}d)`),console.log(d.dim(" The MCP server reads WYRM_VECTOR_PROVIDER (and provider-specific env) at boot \u2014")),console.log(d.dim(` set WYRM_VECTOR_PROVIDER=${p.name==="none"?"none":r.provider} in the server env, then: wyrm index rebuild`));return}const s=_();try{const n=s.getDatabase(),p=o(r,n);if(i==="status"){const c=p.getStats();S("Vector index");const f=[["Provider",c.provider],["Model",c.model],["Vectors",String(c.total)],...Object.entries(c.byType).map(([g,u])=>[` ${g}`,String(u)])];console.log(P(["Field","Value"],f));return}if(i==="rebuild"){const{reindexProjects:c}=await import("./reindex.js"),f=e["dry-run"]===!0,g=e.project;let u;if(g){const w=s.getProject(g)??D(s,g);if(!w){m(`Project not found: ${g}`),process.exitCode=1;return}u=[w.id]}else u=s.getAllProjects(1e3).map(w=>w.id);const{indexed:h,skipped:y}=await c(n,p,u,{dryRun:f,onError:(w,$)=>m(`${w}: ${JSON.stringify($)}`)});b(`Reindex ${f?"(dry run) ":""}\u2014 ${u.length} project(s), ${h} indexed, ${y} skipped`);return}m("Usage: wyrm index <setup|rebuild|status> [--provider auto|local|ollama|openai|none] [--model M] [--project P] [--dry-run]"),process.exitCode=1}finally{s.close()}}function O(l){return typeof l=="string"?l:""}function gt(l){try{return new URL(l).host}catch{return l}}async function yt(l){const{flags:a}=C(l),{getUpdateStatus:e}=await import("./version-check.js"),i=M().version??"0.0.0",o=_();let t;try{t=await e(o.getDatabase(),i,{force:a.force===!0||a.check===!0})}finally{o.close()}if(S("Wyrm update"),console.log(P(["Field","Value"],[["Current",t.current],["Latest",t.latest??"unknown (offline?)"],["Update available",t.updateAvailable?"yes":"no"],["Checked",`${t.checkedAt} (${t.source})`]])),a.check===!0)return;if(!t.updateAvailable&&a.force!==!0){console.log(d.dim(`
76
76
  Already up to date. (Use --force to reinstall anyway.)`));return}console.log(d.dim(`
77
77
  Running: npm install -g wyrm-mcp@latest
78
- `));const i=z("npm",["install","-g","wyrm-mcp@latest"],{stdio:"inherit",shell:!1});i.status===0?b("Updated. Restart your MCP clients to pick up the new binary."):(m(`npm install exited with ${i.status??"unknown"}`),process.exitCode=i.status??1)}async function yt(l){const{positional:a,flags:e}=C(l),s=a[0],o=e.project??process.cwd();if(s==="inject"){const{injectSystemPrompt:t}=await import("./autoconfig.js"),i=typeof e.clients=="string"?e.clients.split(",").map(r=>r.trim()).filter(Boolean):[],n=t(o,i);j("System prompt injection");for(const r of n.injected)console.log(` + ${r}`);for(const r of n.skipped)console.log(d.dim(` o skipped (unknown client): ${r}`));for(const r of n.errors)m(r);n.injected.length>0&&b("AI clients in this project will now call wyrm_session_prime at conversation start."),n.errors.length>0&&(process.exitCode=1);return}if(s==="migrate"){const{migrateProject:t,renderMigrationReport:i}=await import("./migrate-prompt.js"),{WYRM_INJECT_BLOCK:n}=await import("./autoconfig.js"),r=e.apply===!0,p=t({projectPath:o,newBlock:n,apply:r});console.log(i(p,r));return}m("Usage: wyrm prompt inject [--project <path>] [--clients copilot,cursor] | wyrm prompt migrate [--project <path>] [--apply]"),process.exitCode=1}async function ht(l){const{positional:a,flags:e}=C(l);(a[0]??"report")!=="report"&&(m("Usage: wyrm hours report --from YYYY-MM-DD --to YYYY-MM-DD [--project <name>] [--session-hours H] [--json]"),process.exit(1));const o=e.from,t=e.to;(!o||!t)&&(m("--from and --to are required (YYYY-MM-DD)"),process.exit(1));const{HourLedger:i}=await import("./hours.js"),n=S();try{const r=e.project,p=r?T(n,r):null;if(r&&!p){m(`Project not found: ${r}`),process.exitCode=1;return}const c=new i(n.getDatabase()).report({range_start:o,range_end:t,project_id:p?.id,default_session_hours:Z(e["session-hours"],1)});if(e.json===!0){console.log(JSON.stringify(c,null,2));return}if(j(`Hours ${c.range.start} \u2192 ${c.range.end}`),c.by_project.length===0){console.log(d.dim(" No sessions in range."));return}console.log(D(["Project","Sessions","Hours"],c.by_project.map(f=>[f.project_name,String(f.session_count),f.hours.toFixed(2)]))),console.log(`
79
- Total: ${c.total_hours.toFixed(2)}h across ${c.entries.length} session(s)`+(c.estimated_sessions>0?d.dim(` (${c.estimated_sessions} estimated)`):""))}finally{n.close()}}async function wt(l){const{positional:a,flags:e}=C(l),s=a[0]??"generate",o=e.client,t=Z(e.rate,NaN),i=e.from,n=e.to;(s!=="generate"||!o||!Number.isFinite(t)||!i||!n)&&(m('Usage: wyrm invoice generate --client <name> --rate <usd/hour> --from YYYY-MM-DD --to YYYY-MM-DD [--project <name>] [--number INV-X] [--currency USD] [--notes "\u2026"] [--out <path>]'),process.exit(1));const{HourLedger:r}=await import("./hours.js"),p=S();try{const c=e.project,f=c?T(p,c):null;if(c&&!f){m(`Project not found: ${c}`),process.exitCode=1;return}const g=new r(p.getDatabase()).invoice({client_name:o,hourly_rate_usd:t,range_start:i,range_end:n,project_id:f?.id,invoice_number:e.number,currency:e.currency,notes:e.notes,business_name:e["business-name"],business_address:e["business-address"],business_contact:e["business-contact"],client_address:e["client-address"],default_session_hours:Z(e["session-hours"],1)}),u=e.out;if(u){const{writeFileSync:h}=await import("node:fs");h(u,g+`
78
+ `));const r=z("npm",["install","-g","wyrm-mcp@latest"],{stdio:"inherit",shell:!1});r.status===0?b("Updated. Restart your MCP clients to pick up the new binary."):(m(`npm install exited with ${r.status??"unknown"}`),process.exitCode=r.status??1)}async function ht(l){const{positional:a,flags:e}=C(l),i=a[0],o=e.project??process.cwd();if(i==="inject"){const{injectSystemPrompt:t}=await import("./autoconfig.js"),r=typeof e.clients=="string"?e.clients.split(",").map(n=>n.trim()).filter(Boolean):[],s=t(o,r);S("System prompt injection");for(const n of s.injected)console.log(` + ${n}`);for(const n of s.skipped)console.log(d.dim(` o skipped (unknown client): ${n}`));for(const n of s.errors)m(n);s.injected.length>0&&b("AI clients in this project will now call wyrm_session_prime at conversation start."),s.errors.length>0&&(process.exitCode=1);return}if(i==="migrate"){const{migrateProject:t,renderMigrationReport:r}=await import("./migrate-prompt.js"),{WYRM_INJECT_BLOCK:s}=await import("./autoconfig.js"),n=e.apply===!0,p=t({projectPath:o,newBlock:s,apply:n});console.log(r(p,n));return}m("Usage: wyrm prompt inject [--project <path>] [--clients copilot,cursor] | wyrm prompt migrate [--project <path>] [--apply]"),process.exitCode=1}async function wt(l){const{positional:a,flags:e}=C(l);(a[0]??"report")!=="report"&&(m("Usage: wyrm hours report --from YYYY-MM-DD --to YYYY-MM-DD [--project <name>] [--session-hours H] [--json]"),process.exit(1));const o=e.from,t=e.to;(!o||!t)&&(m("--from and --to are required (YYYY-MM-DD)"),process.exit(1));const{HourLedger:r}=await import("./hours.js"),s=_();try{const n=e.project,p=n?D(s,n):null;if(n&&!p){m(`Project not found: ${n}`),process.exitCode=1;return}const c=new r(s.getDatabase()).report({range_start:o,range_end:t,project_id:p?.id,default_session_hours:Z(e["session-hours"],1)});if(e.json===!0){console.log(JSON.stringify(c,null,2));return}if(S(`Hours ${c.range.start} \u2192 ${c.range.end}`),c.by_project.length===0){console.log(d.dim(" No sessions in range."));return}console.log(P(["Project","Sessions","Hours"],c.by_project.map(f=>[f.project_name,String(f.session_count),f.hours.toFixed(2)]))),console.log(`
79
+ Total: ${c.total_hours.toFixed(2)}h across ${c.entries.length} session(s)`+(c.estimated_sessions>0?d.dim(` (${c.estimated_sessions} estimated)`):""))}finally{s.close()}}async function bt(l){const{positional:a,flags:e}=C(l),i=a[0]??"generate",o=e.client,t=Z(e.rate,NaN),r=e.from,s=e.to;(i!=="generate"||!o||!Number.isFinite(t)||!r||!s)&&(m('Usage: wyrm invoice generate --client <name> --rate <usd/hour> --from YYYY-MM-DD --to YYYY-MM-DD [--project <name>] [--number INV-X] [--currency USD] [--notes "\u2026"] [--out <path>]'),process.exit(1));const{HourLedger:n}=await import("./hours.js"),p=_();try{const c=e.project,f=c?D(p,c):null;if(c&&!f){m(`Project not found: ${c}`),process.exitCode=1;return}const g=new n(p.getDatabase()).invoice({client_name:o,hourly_rate_usd:t,range_start:r,range_end:s,project_id:f?.id,invoice_number:e.number,currency:e.currency,notes:e.notes,business_name:e["business-name"],business_address:e["business-address"],business_contact:e["business-contact"],client_address:e["client-address"],default_session_hours:Z(e["session-hours"],1)}),u=e.out;if(u){const{writeFileSync:h}=await import("node:fs");h(u,g+`
80
80
  `,"utf-8"),b(`Invoice written to ${u}`)}else process.stdout.write(g+`
81
- `)}finally{p.close()}}async function bt(l){const{positional:a,flags:e}=C(l),s=a[0]??"status",{AgentDaemon:o}=await import("./agent-daemon.js"),t=S();try{const i=new o(t.getDatabase());switch(s){case"init":case"start":{const n=Math.max(10,Math.min(I(e.interval,600),86400)),r=i.start({interval_seconds:n,max_steps:e["max-steps"]!==void 0&&I(e["max-steps"],0)||void 0,project_path:e.project,verbose:e.verbose===!0});if(!r.ok){m(`Agent init failed: ${r.error}`),process.exitCode=1;return}const p=r.status;b(`Agent ${p.pid!=null&&p.pid!==r.pid?"already running":"started"} \u2014 pid ${p.pid}`),console.log(` Interval: ${n}s \xB7 Active goals: ${p.active_goals} \xB7 Total iterations: ${p.total_iterations}`),console.log(d.dim(` Log: ${p.log_file}`));return}case"status":{const n=i.status();j("Wyrm agent"),console.log(n.running?` RUNNING \u2014 pid ${n.pid}${n.started_at?` (since ${n.started_at})`:""}`:" NOT RUNNING. Start it with: wyrm agent init"),console.log(` Active goals: ${n.active_goals} \xB7 Total iterations: ${n.total_iterations}`),n.last_action&&console.log(` Last action (${n.last_action.ran_at}): ${n.last_action.summary} [${n.last_action.result_status??"?"}]`),e.log===!0&&(console.log(`
82
- === Recent log ===`),console.log(i.recentLog(40)));return}case"stop":{const n=await i.stop({grace_ms:e.grace!==void 0?I(e.grace,3e3):void 0});if(!n.ok){m(`Stop failed: ${n.error}`),process.exitCode=1;return}b(n.was_running?`Agent stopped (was pid ${n.pid}). Goals remain in DB \u2014 wyrm agent init resumes.`:"Agent was not running. (No-op.)");return}case"restart":{const n=await i.restart({interval_seconds:e.interval!==void 0?I(e.interval,600):void 0,max_steps:e["max-steps"]!==void 0&&I(e["max-steps"],0)||void 0,project_path:e.project,verbose:e.verbose===!0});if(!n.ok){m(`Restart failed: ${n.error}`),process.exitCode=1;return}b(`Agent restarted \u2014 pid ${n.status.pid}. Active goals: ${n.status.active_goals}.`);return}default:m("Usage: wyrm agent <init|status|stop|restart> [--interval N] [--max-steps N] [--project <path>] [--verbose] [--log]"),process.exitCode=1}}finally{t.close()}}async function $t(l){if(l.includes("--encrypt")){const{initializeLicense:s,hasFeature:o}=await import("./license.js");if(s(),!o("encryption")){m("Encryption setup requires a Pro license or higher. See: wyrm license"),process.exitCode=1;return}const{getCrypto:t,initializeCrypto:i}=await import("./crypto.js"),n=l.includes("--enable"),r=l.includes("--test");if(n){const c=process.env.WYRM_ENCRYPTION_KEY??(process.stdin.isTTY?"":we());if(!c||c.length<8){m('Password must be at least 8 characters. Set WYRM_ENCRYPTION_KEY or pipe it: printf %s "$PW" | wyrm setup --encrypt --enable'),process.exitCode=1;return}i(c),b("Encryption enabled (AES-256-GCM, key derived via PBKDF2). Store your password safely \u2014 it cannot be recovered.");return}const p=t();if(r){if(!p.isEnabled()){m("Encryption not enabled. Run: wyrm setup --encrypt --enable"),process.exitCode=1;return}const c="Wyrm encryption test "+Date.now();p.decrypt(p.encrypt(c))===c?b("Encryption test PASSED (encrypt \u2192 decrypt roundtrip)."):(m("Encryption test FAILED."),process.exitCode=1);return}j("Encryption status"),console.log(` Enabled: ${p.isEnabled()?"yes \u2014 new data is encrypted at rest":"no"}`),console.log(" Algorithm: AES-256-GCM"),p.isEnabled()||console.log(d.dim(" Enable with: wyrm setup --encrypt --enable (password via WYRM_ENCRYPTION_KEY or stdin)"));return}const a=ne(re(import.meta.url)),e=z(process.execPath,[q(a,"setup.js"),...l],{stdio:"inherit",shell:!1});process.exitCode=e.status??0}async function vt(l){const a="https://github.com/Ghosts-Protocol-Pvt-Ltd/wyrm-mcp",s=`- wyrm-mcp: ${A().version??"unknown"}
81
+ `)}finally{p.close()}}async function $t(l){const{positional:a,flags:e}=C(l),i=a[0]??"status",{AgentDaemon:o}=await import("./agent-daemon.js"),t=_();try{const r=new o(t.getDatabase());switch(i){case"init":case"start":{const s=Math.max(10,Math.min(T(e.interval,600),86400)),n=r.start({interval_seconds:s,max_steps:e["max-steps"]!==void 0&&T(e["max-steps"],0)||void 0,project_path:e.project,verbose:e.verbose===!0});if(!n.ok){m(`Agent init failed: ${n.error}`),process.exitCode=1;return}const p=n.status;b(`Agent ${p.pid!=null&&p.pid!==n.pid?"already running":"started"} \u2014 pid ${p.pid}`),console.log(` Interval: ${s}s \xB7 Active goals: ${p.active_goals} \xB7 Total iterations: ${p.total_iterations}`),console.log(d.dim(` Log: ${p.log_file}`));return}case"status":{const s=r.status();S("Wyrm agent"),console.log(s.running?` RUNNING \u2014 pid ${s.pid}${s.started_at?` (since ${s.started_at})`:""}`:" NOT RUNNING. Start it with: wyrm agent init"),console.log(` Active goals: ${s.active_goals} \xB7 Total iterations: ${s.total_iterations}`),s.last_action&&console.log(` Last action (${s.last_action.ran_at}): ${s.last_action.summary} [${s.last_action.result_status??"?"}]`),e.log===!0&&(console.log(`
82
+ === Recent log ===`),console.log(r.recentLog(40)));return}case"stop":{const s=await r.stop({grace_ms:e.grace!==void 0?T(e.grace,3e3):void 0});if(!s.ok){m(`Stop failed: ${s.error}`),process.exitCode=1;return}b(s.was_running?`Agent stopped (was pid ${s.pid}). Goals remain in DB \u2014 wyrm agent init resumes.`:"Agent was not running. (No-op.)");return}case"restart":{const s=await r.restart({interval_seconds:e.interval!==void 0?T(e.interval,600):void 0,max_steps:e["max-steps"]!==void 0&&T(e["max-steps"],0)||void 0,project_path:e.project,verbose:e.verbose===!0});if(!s.ok){m(`Restart failed: ${s.error}`),process.exitCode=1;return}b(`Agent restarted \u2014 pid ${s.status.pid}. Active goals: ${s.status.active_goals}.`);return}default:m("Usage: wyrm agent <init|status|stop|restart> [--interval N] [--max-steps N] [--project <path>] [--verbose] [--log]"),process.exitCode=1}}finally{t.close()}}async function vt(l){if(l.includes("--encrypt")){const{initializeLicense:i,hasFeature:o}=await import("./license.js");if(i(),!o("encryption")){m("Encryption setup requires a Pro license or higher. See: wyrm license"),process.exitCode=1;return}const{getCrypto:t,initializeCrypto:r}=await import("./crypto.js"),s=l.includes("--enable"),n=l.includes("--test");if(s){const c=process.env.WYRM_ENCRYPTION_KEY??(process.stdin.isTTY?"":we());if(!c||c.length<8){m('Password must be at least 8 characters. Set WYRM_ENCRYPTION_KEY or pipe it: printf %s "$PW" | wyrm setup --encrypt --enable'),process.exitCode=1;return}r(c),b("Encryption enabled (AES-256-GCM, key derived via PBKDF2). Store your password safely \u2014 it cannot be recovered.");return}const p=t();if(n){if(!p.isEnabled()){m("Encryption not enabled. Run: wyrm setup --encrypt --enable"),process.exitCode=1;return}const c="Wyrm encryption test "+Date.now();p.decrypt(p.encrypt(c))===c?b("Encryption test PASSED (encrypt \u2192 decrypt roundtrip)."):(m("Encryption test FAILED."),process.exitCode=1);return}S("Encryption status"),console.log(` Enabled: ${p.isEnabled()?"yes \u2014 new data is encrypted at rest":"no"}`),console.log(" Algorithm: AES-256-GCM"),p.isEnabled()||console.log(d.dim(" Enable with: wyrm setup --encrypt --enable (password via WYRM_ENCRYPTION_KEY or stdin)"));return}const a=ne(re(import.meta.url)),e=z(process.execPath,[q(a,"setup.js"),...l],{stdio:"inherit",shell:!1});process.exitCode=e.status??0}async function kt(l){const a="https://github.com/Ghosts-Protocol-Pvt-Ltd/wyrm-mcp",i=`- wyrm-mcp: ${M().version??"unknown"}
83
83
  - node: ${process.version}
84
- - platform: ${process.platform} ${process.arch}`,o=l.includes("--bug")?"bug":l.includes("--idea")||l.includes("--feature")?"idea":l.includes("--question")||l.includes("--ask")?"question":"feedback",t=l.filter(p=>!p.startsWith("--")).join(" ").trim(),i={bug:`**What happened**
84
+ - platform: ${process.platform} ${process.arch}`,o=l.includes("--bug")?"bug":l.includes("--idea")||l.includes("--feature")?"idea":l.includes("--question")||l.includes("--ask")?"question":"feedback",t=l.filter(p=>!p.startsWith("--")).join(" ").trim(),r={bug:`**What happened**
85
85
 
86
86
 
87
87
  **What you expected**
@@ -92,23 +92,23 @@ Delete these ${g.length} artifact(s)? Type CONFIRM to proceed: `,$)});if(y.close
92
92
 
93
93
  ---
94
94
  _environment_
95
- ${s}`,idea:`**The idea**
95
+ ${i}`,idea:`**The idea**
96
96
 
97
97
 
98
98
  **Why it would help**
99
99
 
100
100
 
101
101
  ---
102
- ${s}`,question:`**Your question**
102
+ ${i}`,question:`**Your question**
103
103
 
104
104
 
105
105
  ---
106
- ${s}`,feedback:`**Your feedback** (what's working, what's rough)
106
+ ${i}`,feedback:`**Your feedback** (what's working, what's rough)
107
107
 
108
108
 
109
109
  ---
110
- ${s}`},n={bug:"bug",idea:"enhancement",question:"question",feedback:"feedback"};let r;o==="question"?r=`${a}/discussions/new?category=q-a`+(t?`&title=${encodeURIComponent(t)}`:"")+`&body=${encodeURIComponent(i.question)}`:r=`${a}/issues/new?labels=${encodeURIComponent(n[o])}&title=${encodeURIComponent(`[${o}] ${t}`.trim())}&body=${encodeURIComponent(i[o])}`,console.log(""),console.log(" "+d.bold("Thanks for helping make Wyrm better.")),console.log(" Opening a prefilled report in your browser. If it does not open, use this link:"),console.log(""),console.log(" "+d.cyan(r)),console.log(""),console.log(d.dim(" Prefer email? ryan@ghosts.lk \xB7 Bug: wyrm feedback --bug Idea: --idea Question: --question")),console.log("");try{const{spawn:p}=await import("child_process"),c=process.platform,f=c==="darwin"?p("open",[r],{stdio:"ignore",detached:!0}):c==="win32"?p("cmd",["/c","start","",r],{stdio:"ignore",detached:!0}):p("xdg-open",[r],{stdio:"ignore",detached:!0});f.on("error",()=>{}),f.unref()}catch{}}function be(){console.log(`
111
- ${N.brightMagenta}\u{F115D} Wyrm CLI v${A().version??"unknown"}${N.reset}
110
+ ${i}`},s={bug:"bug",idea:"enhancement",question:"question",feedback:"feedback"};let n;o==="question"?n=`${a}/discussions/new?category=q-a`+(t?`&title=${encodeURIComponent(t)}`:"")+`&body=${encodeURIComponent(r.question)}`:n=`${a}/issues/new?labels=${encodeURIComponent(s[o])}&title=${encodeURIComponent(`[${o}] ${t}`.trim())}&body=${encodeURIComponent(r[o])}`,console.log(""),console.log(" "+d.bold("Thanks for helping make Wyrm better.")),console.log(" Opening a prefilled report in your browser. If it does not open, use this link:"),console.log(""),console.log(" "+d.cyan(n)),console.log(""),console.log(d.dim(" Prefer email? ryan@ghosts.lk \xB7 Bug: wyrm feedback --bug Idea: --idea Question: --question")),console.log("");try{const{spawn:p}=await import("child_process"),c=process.platform,f=c==="darwin"?p("open",[n],{stdio:"ignore",detached:!0}):c==="win32"?p("cmd",["/c","start","",n],{stdio:"ignore",detached:!0}):p("xdg-open",[n],{stdio:"ignore",detached:!0});f.on("error",()=>{}),f.unref()}catch{}}function be(){console.log(`
111
+ ${N.brightMagenta}\u{F115D} Wyrm CLI v${M().version??"unknown"}${N.reset}
112
112
  ${N.dim}Persistent AI Memory System${N.reset}
113
113
 
114
114
  ${d.bold("Usage:")} wyrm <command> [options]
@@ -122,6 +122,7 @@ ${d.bold("Commands:")}
122
122
  ${d.cyan("session log")} [--run <id>] [--\u2026] Create/update this project's session record (rehydrate's write side)
123
123
  ${d.cyan("presence")} <announce|list|release> Agent presence board from the shell; --pid = process-identity liveness
124
124
  ${d.cyan("digest")} --writes [--since <days>] What Wyrm learned, queued, and refused \u2014 the offline write-receipt report
125
+ ${d.cyan("bridge")} <init|status|run> The substrate flow: remember/render/reverse lanes from one receipted config
125
126
  ${d.cyan("render")} [--client <list>] [--brief] Compile DB \u2192 MEMORY.md + topic files (zero-MCP-token memory)
126
127
  ${d.cyan("import git")} Import git log history to review queue
127
128
  ${d.cyan("import rules")} <path> Import .cursorrules / copilot-instructions file
@@ -180,15 +181,15 @@ ${d.bold("Examples:")}
180
181
  wyrm sync export --out ~/wyrm-backup.wyrm
181
182
  wyrm sync preview --from ~/wyrm-backup.wyrm
182
183
  wyrm prune --project MyApp --min-confidence 0.2 --older-than 30
183
- `)}const[,,M,...v]=process.argv;function $e(l){const a=l.ref_table?`${l.ref_table}${l.ref_id?"#"+l.ref_id:""}`:"",e=new Date(l.created_at).toLocaleTimeString();return`#${l.cursor} ${l.kind.padEnd(15)} ${a.padEnd(16)} ${He(l.actor)} ${e}`}async function kt(l){const{positional:a,flags:e}=C(l),s=a[0]||"since",o=S();try{if(!o.liveMemoryEnabled()){m("Live Memory is disabled (set WYRM_LIVE_MEMORY=1)."),process.exitCode=1;return}const t=(typeof e.project=="string"?e.project:"")||process.cwd(),i=o.getProject(t)??o.getProjectByName(t);if(!i){m(`Project not found: ${t}`),process.exitCode=1;return}if(s==="publish"){const n=a[1]||(typeof e.kind=="string"?e.kind:"");if(!n){m("usage: wyrm events publish <kind> --project <p> [--actor A] [--ref-table T --ref-id ID]"),process.exitCode=1;return}o.publishEvent({projectId:i.id,kind:n,refTable:typeof e["ref-table"]=="string"?e["ref-table"]:void 0,refId:typeof e["ref-id"]=="string"?e["ref-id"]:void 0,actor:typeof e.actor=="string"?e.actor:void 0}),b(`Event published (${n}) to ${i.name}`);return}if(s==="since"){const n=I(e.cursor,0),r=I(e.limit,50),p=o.eventsSince(i.id,n,r);for(const c of p)console.log($e(c));j(`${p.length} event(s) for '${i.name}' since cursor ${n}`);return}m("usage: wyrm events <publish|since> ..."),process.exitCode=1}finally{o.close()}}async function _t(l){const{flags:a}=C(l),e=S();if(!e.liveMemoryEnabled()){m("Live Memory is disabled (set WYRM_LIVE_MEMORY=1)."),e.close(),process.exitCode=1;return}const s=(typeof a.project=="string"?a.project:"")||process.cwd(),o=e.getProject(s)??T(e,s);if(!o){m(`Project not found: ${s}`),e.close(),process.exitCode=1;return}const t=Math.max(250,I(a.interval,1e3));let i=I(a.since,e.subscribeEvents(o.id,1).cursor);j(`Watching '${o.name}' (cursor ${i}, every ${t}ms) \u2014 Ctrl-C to stop`);const n=()=>{try{for(const c of e.eventsSince(o.id,i,200))i=c.cursor,console.log($e(c))}catch{}};n();const r=setInterval(n,t),p=()=>{clearInterval(r);try{e.close()}catch{}process.exit(0)};process.on("SIGINT",p),process.on("SIGTERM",p)}async function jt(l){const{flags:a}=C(l),{embedAll:e,removeAll:s,statusAll:o}=await import("./priority-embed.js"),t={projectDir:typeof a.project=="string"?a.project:void 0,allClients:a.all===!0},i=n=>console.log(` ${String(n.result??n.status).padEnd(9)} [${n.scope}] ${n.file}`);if(a.status){j("Wyrm priority embedding \u2014 status"),o(t).forEach(i);return}if(a.remove){j("Wyrm priority embedding \u2014 removed"),s(t).forEach(i);return}j("Wyrm is now FIRST-PRIORITY memory"),e(t).forEach(i);try{const{installClaudeCodeHooks:n,installClaudeStatusline:r}=await import("./autoconfig.js");n()?b("Proactive hooks installed (SessionStart rehydrate + capture + tool-trace)."):console.log(" (Claude Code not detected \u2014 skipped hook install.)");const c=r();c&&b(`Buddy statusline: ${c.message}`)}catch{}b("Wyrm will now be read first, primed proactively, and shown in the TUI at all times.")}async function St(l){const{flags:a}=C(l),{harvestProjects:e}=await import("./harvest.js"),{MemoryArtifacts:s}=await import("./memory-artifacts.js"),{escapeLikePattern:o}=await import("./auto-capture.js"),t=S();try{const i=t.getDatabase(),n=new s(i),r={existsBySig:(w,$)=>!!i.prepare("SELECT 1 FROM memory_artifacts WHERE project_id = ? AND tags LIKE ? ESCAPE '\\' LIMIT 1").get(w,"%"+o($)+"%"),addCandidate:(w,$)=>n.add(w,{kind:$.kind,problem:$.text,tags:[...$.tags,$.sig],confidence:$.confidence,needsReview:1,createdBy:"harvest"})},p=typeof a.project=="string"?a.project:void 0;let c;if(p){const w=t.getProject(p)??t.getProjectByName(p);if(!w){m(`Project not found: ${p}`),process.exitCode=1;return}c=[{id:w.id,name:w.name,path:w.path}]}else c=t.getAllProjects(500).map(w=>({id:w.id,name:w.name,path:w.path}));const f=a["dry-run"]===!0||a.dry===!0,g=a.code===!0||a["include-code"]===!0,{reports:u,totalAdded:h,totalSkipped:y}=e(r,c,{dryRun:f,gitLimit:I(a.limit,30),includeCode:g});j(`Harvest ${f?"(dry run) ":""}\u2014 ${h} candidate(s), ${y} already present (${c.length} project(s))`);for(const w of u.filter($=>$.added>0).sort(($,R)=>R.added-$.added).slice(0,25))console.log(` +${String(w.added).padStart(3)} (skip ${w.skipped}) ${w.project}`);if(g&&!f){const{SymbolGraph:w}=await import("./symbols.js"),$=new w(i);let R=0,_=0;for(const E of c)try{const k=$.indexProject(E.id,E.path);R+=k.symbols,_+=k.files}catch{}console.log(` \u{1F4D0} Indexed ${R} code symbols (${_} files) \u2192 searchable via 'wyrm search'`)}!f&&h>0&&b("Review with: wyrm review")}finally{t.close()}}async function Ct(l){const a=await import("./vault.js"),[e,...s]=l;try{switch(e){case"set":{const o=s[0];if(!o){m("usage: wyrm vault set <name> (the secret is read from STDIN, never argv)"),process.exitCode=1;return}if(process.stdin.isTTY){m(`pipe the secret in, e.g.: printf %s "$TOKEN" | wyrm vault set ${o}`),process.exitCode=1;return}const i=(await import("node:fs")).readFileSync(0,"utf8").replace(/\r?\n$/,"");if(!i){m("empty secret on stdin"),process.exitCode=1;return}a.vaultSet(o,i),b(`Stored "${o}" (AES-256-GCM). Use it without exposing it: wyrm vault exec ${o} -- <command>`);break}case"get":{const o=s[0];if(!o){m("usage: wyrm vault get <name>"),process.exitCode=1;return}const t=a.vaultGet(o);if(t===void 0){m(`no secret named "${o}"`),process.exitCode=1;return}process.stdout.write(t);break}case"list":case"ls":{const o=a.vaultList();if(!o.length){console.log("(vault is empty)");break}j(`Vault \u2014 ${o.length} secret(s)`);for(const t of o)console.log(" \u2022 "+t);break}case"rm":case"remove":case"delete":{const o=s[0];if(!o){m("usage: wyrm vault rm <name>"),process.exitCode=1;return}b(a.vaultRemove(o)?`Removed "${o}"`:`(no secret named "${o}")`);break}case"exec":{const o=s[0],t=s.indexOf("--");if(!o||t===-1||t+1>=s.length){m("usage: wyrm vault exec <name> [--as ENVVAR] -- <command...>"),process.exitCode=1;return}const i=s.slice(1,t),n=i.indexOf("--as"),r=n>=0?i[n+1]:o.toUpperCase().replace(/[^A-Z0-9]+/g,"_"),p=s.slice(t+1),c=a.vaultGet(o);if(c===void 0){m(`no secret named "${o}"`),process.exitCode=1;return}const f=z(p[0],p.slice(1),{stdio:"inherit",env:{...process.env,[r]:c}});process.exitCode=f.status??1;break}case"import-npm":{const o=await import("node:fs"),t=await import("node:os"),n=(await import("node:path")).join(t.homedir(),".npmrc");if(!o.existsSync(n)){m("~/.npmrc not found"),process.exitCode=1;return}const r=o.readFileSync(n,"utf8").match(/\/\/registry\.npmjs\.org\/:_authToken=(.+)/);if(!r){m("no npm authToken found in ~/.npmrc"),process.exitCode=1;return}a.vaultSet("npm-token",r[1].trim()),b('Imported npm token \u2192 vault as "npm-token". You can now scrub the plaintext from ~/.npmrc and use: wyrm vault exec npm-token --as NODE_AUTH_TOKEN -- npm publish');break}case"setup":{const o=a.vaultPaths();if(o.secure)b(`Vault is already secure (backend: ${o.backend}, ${o.count} secret(s)).`);else{const t=o.keychainAvailable?"keychain":"passphrase";if(t==="passphrase"&&!process.env.WYRM_VAULT_PASSPHRASE){m("No OS keychain on this host. Set WYRM_VAULT_PASSPHRASE, then re-run: wyrm vault setup"),process.exitCode=1;return}const i=a.vaultSecure({backend:t});b(`Vault secured: ${i.from} \u2192 ${i.to}${i.rotated?" (key rotated)":""}.`),i.keyfileShredded&&console.log(" plaintext vault.key: shredded \u2714"),i.backup&&console.log(` ciphertext backup: ${i.backup} (delete once confirmed)`)}j("Store & use credentials safely"),console.log(' store: printf %s "$TOKEN" | wyrm vault set <name> # reads STDIN \u2014 never argv/shell history'),console.log(" use: wyrm vault exec <name> --as ENV_VAR -- <cmd> # injected as env var, never printed"),console.log(" list: wyrm vault list inspect: wyrm vault info");break}case"secure":{const o=s.indexOf("--backend"),t=o>=0?s[o+1]:"keychain";if(t!=="keychain"&&t!=="passphrase"){m("usage: wyrm vault secure [--backend keychain|passphrase] [--no-rotate]"),process.exitCode=1;return}if(t==="passphrase"&&!process.env.WYRM_VAULT_PASSPHRASE){m("set WYRM_VAULT_PASSPHRASE before: wyrm vault secure --backend passphrase"),process.exitCode=1;return}const i=!s.includes("--no-rotate"),n=a.vaultSecure({backend:t,rotate:i});b(`Vault secured: ${n.from} \u2192 ${n.to}${n.rotated?" (key rotated)":""}.`),console.log(` secrets re-encrypted: ${n.secrets}`),n.keyfileShredded&&console.log(" plaintext vault.key: shredded \u2714"),n.backup&&console.log(` ciphertext backup: ${n.backup} (delete once you've confirmed)`),console.log(t==="keychain"?" master key now lives in the OS keychain \u2014 not on disk.":" master key now derived from WYRM_VAULT_PASSPHRASE \u2014 keep that set for future use.");break}case"info":{const o=a.vaultPaths();j("Vault"),console.log(` backend: ${o.backend}`),console.log(` secrets: ${o.count}`),console.log(` store: ${o.vault} (0600)`),console.log(` key: ${o.backend==="keyfile"?o.key+" (0600)":o.backend==="keychain"?"(OS keychain \u2014 no key on disk)":"(derived from WYRM_VAULT_PASSPHRASE \u2014 no key on disk)"}`),console.log(` secure: ${o.secure?"yes \u2014 key is not a plaintext file beside the ciphertext":"NO \u2014 key sits beside ciphertext"}`),o.secure||console.log(o.keychainAvailable?" \u26A0 run `wyrm vault secure` to move the key into the OS keychain.":" \u26A0 no OS keychain found \u2014 set WYRM_VAULT_PASSPHRASE and run `wyrm vault secure --backend passphrase`.");break}default:m("usage: wyrm vault <setup|set|get|list|rm|exec|import-npm|secure|info>"),process.exitCode=1}}catch(o){m(`vault: ${o.message}`),process.exitCode=1}}function Rt(){const l=ne(re(import.meta.url)),e=[q(l,"..","skills"),q(l,"..","..","skills")].find(o=>H(o));if(!e)return[];const s=[];for(const o of Pe(e,{withFileTypes:!0})){if(!o.isDirectory())continue;const t=q(e,o.name,"SKILL.md");if(!H(t))continue;let i=o.name,n="";try{const r=B(t,"utf8"),p=r.match(/^name:\s*(.+)$/m);if(p&&(i=p[1].trim()),/^description:\s*[|>]/m.test(r))n=((r.split(/^description:.*$/m)[1]??"").split(`
184
- `).find(g=>g.trim())??"").trim();else{const c=r.match(/^description:\s*(.+)$/m);c&&(n=c[1].trim())}}catch{}s.push({name:i,description:n})}return s.sort((o,t)=>o.name.localeCompare(t.name))}async function Et(l){const a=l[0];if(!a||a==="help"||a==="--help"){console.log(`Usage:
184
+ `)}const[,,A,...v]=process.argv;function $e(l){const a=l.ref_table?`${l.ref_table}${l.ref_id?"#"+l.ref_id:""}`:"",e=new Date(l.created_at).toLocaleTimeString();return`#${l.cursor} ${l.kind.padEnd(15)} ${a.padEnd(16)} ${He(l.actor)} ${e}`}async function jt(l){const{positional:a,flags:e}=C(l),i=a[0]||"since",o=_();try{if(!o.liveMemoryEnabled()){m("Live Memory is disabled (set WYRM_LIVE_MEMORY=1)."),process.exitCode=1;return}const t=(typeof e.project=="string"?e.project:"")||process.cwd(),r=o.getProject(t)??o.getProjectByName(t);if(!r){m(`Project not found: ${t}`),process.exitCode=1;return}if(i==="publish"){const s=a[1]||(typeof e.kind=="string"?e.kind:"");if(!s){m("usage: wyrm events publish <kind> --project <p> [--actor A] [--ref-table T --ref-id ID]"),process.exitCode=1;return}o.publishEvent({projectId:r.id,kind:s,refTable:typeof e["ref-table"]=="string"?e["ref-table"]:void 0,refId:typeof e["ref-id"]=="string"?e["ref-id"]:void 0,actor:typeof e.actor=="string"?e.actor:void 0}),b(`Event published (${s}) to ${r.name}`);return}if(i==="since"){const s=T(e.cursor,0),n=T(e.limit,50),p=o.eventsSince(r.id,s,n);for(const c of p)console.log($e(c));S(`${p.length} event(s) for '${r.name}' since cursor ${s}`);return}m("usage: wyrm events <publish|since> ..."),process.exitCode=1}finally{o.close()}}async function St(l){const{flags:a}=C(l),e=_();if(!e.liveMemoryEnabled()){m("Live Memory is disabled (set WYRM_LIVE_MEMORY=1)."),e.close(),process.exitCode=1;return}const i=(typeof a.project=="string"?a.project:"")||process.cwd(),o=e.getProject(i)??D(e,i);if(!o){m(`Project not found: ${i}`),e.close(),process.exitCode=1;return}const t=Math.max(250,T(a.interval,1e3));let r=T(a.since,e.subscribeEvents(o.id,1).cursor);S(`Watching '${o.name}' (cursor ${r}, every ${t}ms) \u2014 Ctrl-C to stop`);const s=()=>{try{for(const c of e.eventsSince(o.id,r,200))r=c.cursor,console.log($e(c))}catch{}};s();const n=setInterval(s,t),p=()=>{clearInterval(n);try{e.close()}catch{}process.exit(0)};process.on("SIGINT",p),process.on("SIGTERM",p)}async function _t(l){const{flags:a}=C(l),{embedAll:e,removeAll:i,statusAll:o}=await import("./priority-embed.js"),t={projectDir:typeof a.project=="string"?a.project:void 0,allClients:a.all===!0},r=s=>console.log(` ${String(s.result??s.status).padEnd(9)} [${s.scope}] ${s.file}`);if(a.status){S("Wyrm priority embedding \u2014 status"),o(t).forEach(r);return}if(a.remove){S("Wyrm priority embedding \u2014 removed"),i(t).forEach(r);return}S("Wyrm is now FIRST-PRIORITY memory"),e(t).forEach(r);try{const{installClaudeCodeHooks:s,installClaudeStatusline:n}=await import("./autoconfig.js");s()?b("Proactive hooks installed (SessionStart rehydrate + capture + tool-trace)."):console.log(" (Claude Code not detected \u2014 skipped hook install.)");const c=n();c&&b(`Buddy statusline: ${c.message}`)}catch{}b("Wyrm will now be read first, primed proactively, and shown in the TUI at all times.")}async function Ct(l){const{flags:a}=C(l),{harvestProjects:e}=await import("./harvest.js"),{MemoryArtifacts:i}=await import("./memory-artifacts.js"),{escapeLikePattern:o}=await import("./auto-capture.js"),t=_();try{const r=t.getDatabase(),s=new i(r),n={existsBySig:(w,$)=>!!r.prepare("SELECT 1 FROM memory_artifacts WHERE project_id = ? AND tags LIKE ? ESCAPE '\\' LIMIT 1").get(w,"%"+o($)+"%"),addCandidate:(w,$)=>s.add(w,{kind:$.kind,problem:$.text,tags:[...$.tags,$.sig],confidence:$.confidence,needsReview:1,createdBy:"harvest"})},p=typeof a.project=="string"?a.project:void 0;let c;if(p){const w=t.getProject(p)??t.getProjectByName(p);if(!w){m(`Project not found: ${p}`),process.exitCode=1;return}c=[{id:w.id,name:w.name,path:w.path}]}else c=t.getAllProjects(500).map(w=>({id:w.id,name:w.name,path:w.path}));const f=a["dry-run"]===!0||a.dry===!0,g=a.code===!0||a["include-code"]===!0,{reports:u,totalAdded:h,totalSkipped:y}=e(n,c,{dryRun:f,gitLimit:T(a.limit,30),includeCode:g});S(`Harvest ${f?"(dry run) ":""}\u2014 ${h} candidate(s), ${y} already present (${c.length} project(s))`);for(const w of u.filter($=>$.added>0).sort(($,R)=>R.added-$.added).slice(0,25))console.log(` +${String(w.added).padStart(3)} (skip ${w.skipped}) ${w.project}`);if(g&&!f){const{SymbolGraph:w}=await import("./symbols.js"),$=new w(r);let R=0,j=0;for(const E of c)try{const k=$.indexProject(E.id,E.path);R+=k.symbols,j+=k.files}catch{}console.log(` \u{1F4D0} Indexed ${R} code symbols (${j} files) \u2192 searchable via 'wyrm search'`)}!f&&h>0&&b("Review with: wyrm review")}finally{t.close()}}async function Rt(l){const a=await import("./vault.js"),[e,...i]=l;try{switch(e){case"set":{const o=i[0];if(!o){m("usage: wyrm vault set <name> (the secret is read from STDIN, never argv)"),process.exitCode=1;return}if(process.stdin.isTTY){m(`pipe the secret in, e.g.: printf %s "$TOKEN" | wyrm vault set ${o}`),process.exitCode=1;return}const r=(await import("node:fs")).readFileSync(0,"utf8").replace(/\r?\n$/,"");if(!r){m("empty secret on stdin"),process.exitCode=1;return}a.vaultSet(o,r),b(`Stored "${o}" (AES-256-GCM). Use it without exposing it: wyrm vault exec ${o} -- <command>`);break}case"get":{const o=i[0];if(!o){m("usage: wyrm vault get <name>"),process.exitCode=1;return}const t=a.vaultGet(o);if(t===void 0){m(`no secret named "${o}"`),process.exitCode=1;return}process.stdout.write(t);break}case"list":case"ls":{const o=a.vaultList();if(!o.length){console.log("(vault is empty)");break}S(`Vault \u2014 ${o.length} secret(s)`);for(const t of o)console.log(" \u2022 "+t);break}case"rm":case"remove":case"delete":{const o=i[0];if(!o){m("usage: wyrm vault rm <name>"),process.exitCode=1;return}b(a.vaultRemove(o)?`Removed "${o}"`:`(no secret named "${o}")`);break}case"exec":{const o=i[0],t=i.indexOf("--");if(!o||t===-1||t+1>=i.length){m("usage: wyrm vault exec <name> [--as ENVVAR] -- <command...>"),process.exitCode=1;return}const r=i.slice(1,t),s=r.indexOf("--as"),n=s>=0?r[s+1]:o.toUpperCase().replace(/[^A-Z0-9]+/g,"_"),p=i.slice(t+1),c=a.vaultGet(o);if(c===void 0){m(`no secret named "${o}"`),process.exitCode=1;return}const f=z(p[0],p.slice(1),{stdio:"inherit",env:{...process.env,[n]:c}});process.exitCode=f.status??1;break}case"import-npm":{const o=await import("node:fs"),t=await import("node:os"),s=(await import("node:path")).join(t.homedir(),".npmrc");if(!o.existsSync(s)){m("~/.npmrc not found"),process.exitCode=1;return}const n=o.readFileSync(s,"utf8").match(/\/\/registry\.npmjs\.org\/:_authToken=(.+)/);if(!n){m("no npm authToken found in ~/.npmrc"),process.exitCode=1;return}a.vaultSet("npm-token",n[1].trim()),b('Imported npm token \u2192 vault as "npm-token". You can now scrub the plaintext from ~/.npmrc and use: wyrm vault exec npm-token --as NODE_AUTH_TOKEN -- npm publish');break}case"setup":{const o=a.vaultPaths();if(o.secure)b(`Vault is already secure (backend: ${o.backend}, ${o.count} secret(s)).`);else{const t=o.keychainAvailable?"keychain":"passphrase";if(t==="passphrase"&&!process.env.WYRM_VAULT_PASSPHRASE){m("No OS keychain on this host. Set WYRM_VAULT_PASSPHRASE, then re-run: wyrm vault setup"),process.exitCode=1;return}const r=a.vaultSecure({backend:t});b(`Vault secured: ${r.from} \u2192 ${r.to}${r.rotated?" (key rotated)":""}.`),r.keyfileShredded&&console.log(" plaintext vault.key: shredded \u2714"),r.backup&&console.log(` ciphertext backup: ${r.backup} (delete once confirmed)`)}S("Store & use credentials safely"),console.log(' store: printf %s "$TOKEN" | wyrm vault set <name> # reads STDIN \u2014 never argv/shell history'),console.log(" use: wyrm vault exec <name> --as ENV_VAR -- <cmd> # injected as env var, never printed"),console.log(" list: wyrm vault list inspect: wyrm vault info");break}case"secure":{const o=i.indexOf("--backend"),t=o>=0?i[o+1]:"keychain";if(t!=="keychain"&&t!=="passphrase"){m("usage: wyrm vault secure [--backend keychain|passphrase] [--no-rotate]"),process.exitCode=1;return}if(t==="passphrase"&&!process.env.WYRM_VAULT_PASSPHRASE){m("set WYRM_VAULT_PASSPHRASE before: wyrm vault secure --backend passphrase"),process.exitCode=1;return}const r=!i.includes("--no-rotate"),s=a.vaultSecure({backend:t,rotate:r});b(`Vault secured: ${s.from} \u2192 ${s.to}${s.rotated?" (key rotated)":""}.`),console.log(` secrets re-encrypted: ${s.secrets}`),s.keyfileShredded&&console.log(" plaintext vault.key: shredded \u2714"),s.backup&&console.log(` ciphertext backup: ${s.backup} (delete once you've confirmed)`),console.log(t==="keychain"?" master key now lives in the OS keychain \u2014 not on disk.":" master key now derived from WYRM_VAULT_PASSPHRASE \u2014 keep that set for future use.");break}case"info":{const o=a.vaultPaths();S("Vault"),console.log(` backend: ${o.backend}`),console.log(` secrets: ${o.count}`),console.log(` store: ${o.vault} (0600)`),console.log(` key: ${o.backend==="keyfile"?o.key+" (0600)":o.backend==="keychain"?"(OS keychain \u2014 no key on disk)":"(derived from WYRM_VAULT_PASSPHRASE \u2014 no key on disk)"}`),console.log(` secure: ${o.secure?"yes \u2014 key is not a plaintext file beside the ciphertext":"NO \u2014 key sits beside ciphertext"}`),o.secure||console.log(o.keychainAvailable?" \u26A0 run `wyrm vault secure` to move the key into the OS keychain.":" \u26A0 no OS keychain found \u2014 set WYRM_VAULT_PASSPHRASE and run `wyrm vault secure --backend passphrase`.");break}default:m("usage: wyrm vault <setup|set|get|list|rm|exec|import-npm|secure|info>"),process.exitCode=1}}catch(o){m(`vault: ${o.message}`),process.exitCode=1}}function Et(){const l=ne(re(import.meta.url)),e=[q(l,"..","skills"),q(l,"..","..","skills")].find(o=>H(o));if(!e)return[];const i=[];for(const o of Ie(e,{withFileTypes:!0})){if(!o.isDirectory())continue;const t=q(e,o.name,"SKILL.md");if(!H(t))continue;let r=o.name,s="";try{const n=B(t,"utf8"),p=n.match(/^name:\s*(.+)$/m);if(p&&(r=p[1].trim()),/^description:\s*[|>]/m.test(n))s=((n.split(/^description:.*$/m)[1]??"").split(`
185
+ `).find(g=>g.trim())??"").trim();else{const c=n.match(/^description:\s*(.+)$/m);c&&(s=c[1].trim())}}catch{}i.push({name:r,description:s})}return i.sort((o,t)=>o.name.localeCompare(t.name))}async function xt(l){const a=l[0];if(!a||a==="help"||a==="--help"){console.log(`Usage:
185
186
  wyrm skill list show the bundled guides + your registered skills
186
187
  wyrm skill backfill-content read every skill's SKILL.md into the registry (idempotent)
187
188
  wyrm skill export <targetDir> [--all] materialize SKILL.md files from stored content
188
- wyrm skill share <name|--all|--tier T> [--public|--private] [--include-inactive] set cloud-sync visibility (single or bulk)`);return}if(a==="list"||a==="ls"){const s=Rt();if(j("Bundled skill guides (shipped in the box)"),s.length===0)console.log(d.yellow(" none found next to this install"));else{for(const t of s)console.log(` ${d.cyan(t.name)}`),t.description&&console.log(` ${d.dim(t.description)}`);console.log(d.dim("\n Read one: open its SKILL.md, or `wyrm skill export <dir>` to materialize all."))}const o=S();try{const t=o.listSkills(!0);if(j(`Registered skills in your memory (${t.length})`),t.length===0)console.log(d.dim(" none yet \u2014 register with wyrm_skill_register or author with wyrm_skill_create"));else{for(const i of t.slice(0,40))console.log(` ${d.cyan(i.name)}${i.tier?d.dim(" ["+i.tier+"]"):""}`);t.length>40&&console.log(d.dim(` \u2026 and ${t.length-40} more`))}}finally{o.close()}return}const e=S();try{if(a==="backfill-content"||a==="backfill"){j("Backfill SKILL.md content into the registry");const s=e.backfillSkillContent();b(`${s.filled} filled . ${s.unchanged} unchanged . ${s.missing} missing-file (of ${s.total} registered)`),s.missing>0&&console.log(d.yellow(` ${s.missing} skill(s) had no readable SKILL.md \u2014 re-run after restoring their files.`));return}if(a==="export"){const s=l[1];s||(m("Usage: wyrm skill export <targetDir> [--all]"),e.close(),process.exit(1));const o=l.includes("--all");j(`Export skills \u2192 ${s}`);const t=e.exportSkillContent(s,{includeInactive:o});b(`${t.written} SKILL.md written . ${t.skipped_no_content} skipped (no stored content) (of ${t.total} ${o?"total":"active"})`),t.skipped_no_content>0&&console.log(d.yellow(" Run `wyrm skill backfill-content` on the source machine to populate content first.")),t.collisions>0&&console.log(d.yellow(` ${t.collisions} slug collision(s) disambiguated with a name-hash suffix (distinct skills, same slug) \u2014 no content lost.`));return}if(a==="share"){const s=l.includes("--private")?"within":l.includes("--public")?"public":"org",o=l.includes("--include-inactive"),t=l.indexOf("--tier"),i=t>=0?l[t+1]:void 0;if(t>=0&&(!i||i.startsWith("--"))&&(m("Usage: wyrm skill share --tier <god|mega|atomic> [--public|--private] [--include-inactive]"),e.close(),process.exit(1)),l.includes("--all")||!!i){const c=e.setAllSkillsVisibility(s,{tier:i,includeInactive:o}),f=i?`tier '${i}'`:o?"all skills":"all active skills";b(`${c} skill(s) (${f}) visibility \u2192 '${s}'.`),console.log(s==="within"?" These skills will NOT egress on cloud sync (private).":` ${c} skills are now cloud-sync-eligible (they leave on the next \`wyrm cloud sync\`).`);return}const r=l[1];r||(m("Usage: wyrm skill share <name|--all|--tier <tier>> [--public|--private] [--include-inactive]"),e.close(),process.exit(1)),e.setSkillVisibility(r,s)||(m(`Skill not found: ${r}`),e.close(),process.exit(1)),b(`Skill "${r}" visibility \u2192 '${s}'.`),console.log(s==="within"?" This skill will NOT egress on cloud sync (private).":" This skill is now cloud-sync-eligible (it leaves on the next `wyrm cloud sync`).");return}m(`Unknown skill subcommand: ${a}`),process.exit(1)}finally{e.close()}}async function xt(l){const a=l[0]??"status",e=S(),s=e.getDatabase();if(s.prepare("PRAGMA table_info(projects)").all().some(t=>t.name==="sync_policy")||(m("Grove sync policy is not on this database yet (upgrade Wyrm so migrations apply)."),e.close(),process.exit(1)),a==="status"||a==="ls"||a==="list"){j("Grove sync policy + leak audit");const t=s.prepare("SELECT id, name, sync_policy FROM projects ORDER BY id").all(),i=["ground_truths","memory_artifacts","quests","design_tokens","design_references"],n=["ground_truths","memory_artifacts","quests","sessions","decision_edges"],r=(f,g,u)=>{let h=0;for(const y of f)try{h+=s.prepare(`SELECT COUNT(*) AS n FROM ${y} WHERE project_id = ? AND ${u}`).get(g).n}catch{}return h},p=[];let c=0;for(const f of t){const g=r(i,f.id,"cross_project_visibility IN ('org','public')"),u=r(n,f.id,"is_shared = 1"),h=f.sync_policy==="private"&&g+u>0;h&&c++;const y=h?d.red(`LEAK: ${g} promoted + ${u} shared in a PRIVATE grove`):`${g} promoted / ${u} shared`;p.push([String(f.id),f.name,f.sync_policy,y])}console.log(D(["#","Grove","sync_policy","rows eligible to leave"],p)),console.log(`
189
+ wyrm skill share <name|--all|--tier T> [--public|--private] [--include-inactive] set cloud-sync visibility (single or bulk)`);return}if(a==="list"||a==="ls"){const i=Et();if(S("Bundled skill guides (shipped in the box)"),i.length===0)console.log(d.yellow(" none found next to this install"));else{for(const t of i)console.log(` ${d.cyan(t.name)}`),t.description&&console.log(` ${d.dim(t.description)}`);console.log(d.dim("\n Read one: open its SKILL.md, or `wyrm skill export <dir>` to materialize all."))}const o=_();try{const t=o.listSkills(!0);if(S(`Registered skills in your memory (${t.length})`),t.length===0)console.log(d.dim(" none yet \u2014 register with wyrm_skill_register or author with wyrm_skill_create"));else{for(const r of t.slice(0,40))console.log(` ${d.cyan(r.name)}${r.tier?d.dim(" ["+r.tier+"]"):""}`);t.length>40&&console.log(d.dim(` \u2026 and ${t.length-40} more`))}}finally{o.close()}return}const e=_();try{if(a==="backfill-content"||a==="backfill"){S("Backfill SKILL.md content into the registry");const i=e.backfillSkillContent();b(`${i.filled} filled . ${i.unchanged} unchanged . ${i.missing} missing-file (of ${i.total} registered)`),i.missing>0&&console.log(d.yellow(` ${i.missing} skill(s) had no readable SKILL.md \u2014 re-run after restoring their files.`));return}if(a==="export"){const i=l[1];i||(m("Usage: wyrm skill export <targetDir> [--all]"),e.close(),process.exit(1));const o=l.includes("--all");S(`Export skills \u2192 ${i}`);const t=e.exportSkillContent(i,{includeInactive:o});b(`${t.written} SKILL.md written . ${t.skipped_no_content} skipped (no stored content) (of ${t.total} ${o?"total":"active"})`),t.skipped_no_content>0&&console.log(d.yellow(" Run `wyrm skill backfill-content` on the source machine to populate content first.")),t.collisions>0&&console.log(d.yellow(` ${t.collisions} slug collision(s) disambiguated with a name-hash suffix (distinct skills, same slug) \u2014 no content lost.`));return}if(a==="share"){const i=l.includes("--private")?"within":l.includes("--public")?"public":"org",o=l.includes("--include-inactive"),t=l.indexOf("--tier"),r=t>=0?l[t+1]:void 0;if(t>=0&&(!r||r.startsWith("--"))&&(m("Usage: wyrm skill share --tier <god|mega|atomic> [--public|--private] [--include-inactive]"),e.close(),process.exit(1)),l.includes("--all")||!!r){const c=e.setAllSkillsVisibility(i,{tier:r,includeInactive:o}),f=r?`tier '${r}'`:o?"all skills":"all active skills";b(`${c} skill(s) (${f}) visibility \u2192 '${i}'.`),console.log(i==="within"?" These skills will NOT egress on cloud sync (private).":` ${c} skills are now cloud-sync-eligible (they leave on the next \`wyrm cloud sync\`).`);return}const n=l[1];n||(m("Usage: wyrm skill share <name|--all|--tier <tier>> [--public|--private] [--include-inactive]"),e.close(),process.exit(1)),e.setSkillVisibility(n,i)||(m(`Skill not found: ${n}`),e.close(),process.exit(1)),b(`Skill "${n}" visibility \u2192 '${i}'.`),console.log(i==="within"?" This skill will NOT egress on cloud sync (private).":" This skill is now cloud-sync-eligible (it leaves on the next `wyrm cloud sync`).");return}m(`Unknown skill subcommand: ${a}`),process.exit(1)}finally{e.close()}}async function Dt(l){const a=l[0]??"status",e=_(),i=e.getDatabase();if(i.prepare("PRAGMA table_info(projects)").all().some(t=>t.name==="sync_policy")||(m("Grove sync policy is not on this database yet (upgrade Wyrm so migrations apply)."),e.close(),process.exit(1)),a==="status"||a==="ls"||a==="list"){S("Grove sync policy + leak audit");const t=i.prepare("SELECT id, name, sync_policy FROM projects ORDER BY id").all(),r=["ground_truths","memory_artifacts","quests","design_tokens","design_references"],s=["ground_truths","memory_artifacts","quests","sessions","decision_edges"],n=(f,g,u)=>{let h=0;for(const y of f)try{h+=i.prepare(`SELECT COUNT(*) AS n FROM ${y} WHERE project_id = ? AND ${u}`).get(g).n}catch{}return h},p=[];let c=0;for(const f of t){const g=n(r,f.id,"cross_project_visibility IN ('org','public')"),u=n(s,f.id,"is_shared = 1"),h=f.sync_policy==="private"&&g+u>0;h&&c++;const y=h?d.red(`LEAK: ${g} promoted + ${u} shared in a PRIVATE grove`):`${g} promoted / ${u} shared`;p.push([String(f.id),f.name,f.sync_policy,y])}console.log(P(["#","Grove","sync_policy","rows eligible to leave"],p)),console.log(`
189
190
  private = never replicates . cloud = your own cloud backup . team = federates to a team Wyrm`),c>0&&console.log(d.red(`
190
- ! ${c} private grove(s) hold rows marked to leave. Re-private those rows or change the grove lane.`)),e.close();return}if(a==="policy"||a==="set"){const t=l[1],i=l[2];(!t||!["private","cloud","team"].includes(i))&&(m("Usage: wyrm grove policy <project|id> <private|cloud|team>"),e.close(),process.exit(1));let n=T(e,t);!n&&/^\d+$/.test(t)&&(n=s.prepare("SELECT id, name FROM projects WHERE id = ?").get(Number(t))),n||(m(`Grove not found: ${t}`),e.close(),process.exit(1)),s.prepare("UPDATE projects SET sync_policy = ? WHERE id = ?").run(i,n.id),b(`Grove "${n.name}" set to '${i}'.`),i!=="private"&&console.log(` Rows still only leave when also marked ${i==="team"?"is_shared (team)":"org/public (cloud)"}. The grove is the outer gate.`),e.close();return}m(`Unknown grove subcommand: ${a}`),console.log(`Usage:
191
+ ! ${c} private grove(s) hold rows marked to leave. Re-private those rows or change the grove lane.`)),e.close();return}if(a==="policy"||a==="set"){const t=l[1],r=l[2];(!t||!["private","cloud","team"].includes(r))&&(m("Usage: wyrm grove policy <project|id> <private|cloud|team>"),e.close(),process.exit(1));let s=D(e,t);!s&&/^\d+$/.test(t)&&(s=i.prepare("SELECT id, name FROM projects WHERE id = ?").get(Number(t))),s||(m(`Grove not found: ${t}`),e.close(),process.exit(1)),i.prepare("UPDATE projects SET sync_policy = ? WHERE id = ?").run(r,s.id),b(`Grove "${s.name}" set to '${r}'.`),r!=="private"&&console.log(` Rows still only leave when also marked ${r==="team"?"is_shared (team)":"org/public (cloud)"}. The grove is the outer gate.`),e.close();return}m(`Unknown grove subcommand: ${a}`),console.log(`Usage:
191
192
  wyrm grove status
192
- wyrm grove policy <project|id> <private|cloud|team>`),e.close(),process.exit(1)}async function Tt(l){const a=l[0],{flags:e}=C(l.slice(1)),{ulid:s}=await import("./ulid.js"),{createRun:o,setRunStatus:t,registerAgent:i,getRun:n,getAgents:r}=await import("./handlers/run.js"),{sanitizeActorId:p}=await import("./handlers/boundary.js"),c=u=>typeof u=="string"?p(u):null,f=S(),g=f.getDatabase();try{switch(a){case"start":{const u=(typeof e.orchestrator=="string"?e.orchestrator:"cli").slice(0,200),h=c(e.agent)??p(u)??"cli",y=c(e.parent);if(y&&!n(g,y)){m(`Parent run not found: ${y}`),process.exitCode=1;return}const w=s();o(g,w,y,u),i(g,w,h,"orchestrator"),process.stdout.write(w+`
193
+ wyrm grove policy <project|id> <private|cloud|team>`),e.close(),process.exit(1)}async function Pt(l){const a=l[0],{flags:e}=C(l.slice(1)),{ulid:i}=await import("./ulid.js"),{createRun:o,setRunStatus:t,registerAgent:r,getRun:s,getAgents:n}=await import("./handlers/run.js"),{sanitizeActorId:p}=await import("./handlers/boundary.js"),c=u=>typeof u=="string"?p(u):null,f=_(),g=f.getDatabase();try{switch(a){case"start":{const u=(typeof e.orchestrator=="string"?e.orchestrator:"cli").slice(0,200),h=c(e.agent)??p(u)??"cli",y=c(e.parent);if(y&&!s(g,y)){m(`Parent run not found: ${y}`),process.exitCode=1;return}const w=i();o(g,w,y,u),r(g,w,h,"orchestrator"),process.stdout.write(w+`
193
194
  `),process.stderr.write(`Run ${w} started by ${u}.
194
- `);break}case"join":{const u=c(e.run),h=c(e.agent);if(!u||!h){m("Usage: wyrm run join --run <RUNID> --agent <ID> [--role <ROLE>]"),process.exitCode=1;return}if(!n(g,u)){m(`Run not found: ${u}`),process.exitCode=1;return}const y=typeof e.role=="string"?e.role.slice(0,200):null;i(g,u,h,y),b(`${h} joined run ${u}${y?` as ${y}`:""}.`);break}case"end":{const u=c(e.run);if(!u){m("Usage: wyrm run end --run <RUNID> [--status completed|failed|abandoned]"),process.exitCode=1;return}if(!n(g,u)){m(`Run not found: ${u}`),process.exitCode=1;return}const h=typeof e.status=="string"?e.status:"completed",w=["completed","failed","abandoned"].includes(h)?h:"completed";t(g,u,w),b(`Run ${u} ended (${w}).`);break}case"status":{const u=c(e.run);if(!u){m("Usage: wyrm run status --run <RUNID>"),process.exitCode=1;return}const h=n(g,u);if(!h){m(`Run not found: ${u}`),process.exitCode=1;return}const y=r(g,u);j(`Run ${h.run_id} [${h.status}]`),h.orchestrator&&console.log(d.bold("Orchestrator: ")+h.orchestrator),h.parent_run_id&&console.log(d.bold("Parent: ")+h.parent_run_id),console.log(d.bold("Created: ")+h.created_at),console.log(d.bold("Updated: ")+h.updated_at),y.length>0?console.log(D(["Agent","Role","Joined"],y.map(w=>[w.agent_id,w.role??"",w.joined_at]))):console.log(d.dim(" No agents registered."));break}default:m("Usage: wyrm run <start|join|end|status> [options]"),console.log(" wyrm run start [--orchestrator NAME] [--agent ID] [--parent RUNID]"),console.log(" wyrm run join --run RUNID --agent ID [--role ROLE]"),console.log(" wyrm run end --run RUNID [--status completed|failed|abandoned]"),console.log(" wyrm run status --run RUNID"),process.exitCode=1;return}}finally{f.close()}}if(M==="--version"||M==="-v"||M==="version"){const l=A();console.log(`${l.name??"wyrm-mcp"} v${l.version??"unknown"}`)}else!M||M==="--help"||M==="-h"||M==="help"?be():(async()=>{try{switch(M){case"search":await Ve(v);break;case"ls":await Ke(v);break;case"show":await Ge(v);break;case"capture":await Je(v);break;case"rehydrate":await Qe(v);break;case"session":await Ze(v);break;case"presence":await Xe(v);break;case"digest":await ze(v);break;case"render":await et(v);break;case"reverse-bridge":await tt(v);break;case"import":await ot(v);break;case"stats":await nt(v);break;case"review":await rt(v);break;case"sync":await it(v);break;case"cloud":{const{cmdCloud:l}=await import("./cloud/cli.js");await l(v);break}case"grove":await xt(v);break;case"run":await Tt(v);break;case"skill":await Et(v);break;case"prune":await ct(v);break;case"license":await lt();break;case"login":await dt();break;case"activate":await pt(v);break;case"maintenance":await ut(v);break;case"index":await mt(v);break;case"update":await gt(v);break;case"prompt":await yt(v);break;case"hours":await ht(v);break;case"invoice":await wt(v);break;case"agent":await bt(v);break;case"feedback":await vt(v);break;case"setup":await $t(v);break;case"intro":{const{renderIntro:l}=await import("./visibility.js");console.log(l(A().version??"unknown"));break}case"events":await kt(v);break;case"watch":await _t(v);break;case"embed":await jt(v);break;case"harvest":await St(v);break;case"vault":await Ct(v);break;case"connector":case"connectors":await at(v);break;case"statusline":{const{installClaudeStatusline:l,removeClaudeStatusline:a}=await import("./autoconfig.js"),e=v.includes("--remove")?a():l();e?b(e.message):m("Claude Code not detected (~/.claude missing).");break}case"guard":{const{installWyrmGuardHooks:l,removeWyrmGuardHooks:a,wyrmGuardHookStatus:e}=await import("./autoconfig.js");if(v.includes("--status")){const o=e();if(console.log(` settings: ${o.settingsPath}`),o.installed){b(`wyrm-guard hooks installed (${o.commands.length} entr${o.commands.length===1?"y":"ies"})`);for(const t of o.commands)console.log(d.dim(` ${t}`))}else console.log(d.yellow(" wyrm-guard hooks not installed \u2014 run: wyrm guard"));break}const s=v.includes("--remove")||v.includes("--uninstall")?a():l();s?s.action==="failed"?(m(s.message),process.exitCode=1):b(s.message):m("Claude Code not detected (~/.claude missing).");break}case"ui":case"dashboard":v.includes("--ui")||v.push("--ui");case"serve":{const l=v.includes("--ui");if(l){const{enableDevMode:o}=await import("./http-auth.js");o()}const{server:a}=await import("./http-fast.js"),e=parseInt(process.env.WYRM_PORT??process.env.PORT??"3333",10),s=process.env.WYRM_BIND_HOST||"127.0.0.1";a.listen(e,s,()=>{if(b(`Wyrm HTTP server running on ${s}:${e}`),process.env.WYRM_UI_READONLY==="1"&&console.log("\u{1F512} READ-ONLY mode: writes + off-box egress are blocked; safe to expose."),l){const o=`http://localhost:${e}/ui`;console.log(`\u{1F5A5}\uFE0F Dashboard: ${o}`),import("child_process").then(({spawn:t})=>{const i=process.platform;try{const n=i==="darwin"?t("open",[o],{stdio:"ignore",detached:!0}):i==="win32"?t("cmd",["/c","start","",o],{stdio:"ignore",detached:!0}):t("xdg-open",[o],{stdio:"ignore",detached:!0});n.on("error",()=>{}),n.unref()}catch{}}).catch(()=>{})}});break}default:m(`Unknown command: ${M}`),be(),process.exit(1)}}catch(l){m(String(l)),process.exit(1)}})();
195
+ `);break}case"join":{const u=c(e.run),h=c(e.agent);if(!u||!h){m("Usage: wyrm run join --run <RUNID> --agent <ID> [--role <ROLE>]"),process.exitCode=1;return}if(!s(g,u)){m(`Run not found: ${u}`),process.exitCode=1;return}const y=typeof e.role=="string"?e.role.slice(0,200):null;r(g,u,h,y),b(`${h} joined run ${u}${y?` as ${y}`:""}.`);break}case"end":{const u=c(e.run);if(!u){m("Usage: wyrm run end --run <RUNID> [--status completed|failed|abandoned]"),process.exitCode=1;return}if(!s(g,u)){m(`Run not found: ${u}`),process.exitCode=1;return}const h=typeof e.status=="string"?e.status:"completed",w=["completed","failed","abandoned"].includes(h)?h:"completed";t(g,u,w),b(`Run ${u} ended (${w}).`);break}case"status":{const u=c(e.run);if(!u){m("Usage: wyrm run status --run <RUNID>"),process.exitCode=1;return}const h=s(g,u);if(!h){m(`Run not found: ${u}`),process.exitCode=1;return}const y=n(g,u);S(`Run ${h.run_id} [${h.status}]`),h.orchestrator&&console.log(d.bold("Orchestrator: ")+h.orchestrator),h.parent_run_id&&console.log(d.bold("Parent: ")+h.parent_run_id),console.log(d.bold("Created: ")+h.created_at),console.log(d.bold("Updated: ")+h.updated_at),y.length>0?console.log(P(["Agent","Role","Joined"],y.map(w=>[w.agent_id,w.role??"",w.joined_at]))):console.log(d.dim(" No agents registered."));break}default:m("Usage: wyrm run <start|join|end|status> [options]"),console.log(" wyrm run start [--orchestrator NAME] [--agent ID] [--parent RUNID]"),console.log(" wyrm run join --run RUNID --agent ID [--role ROLE]"),console.log(" wyrm run end --run RUNID [--status completed|failed|abandoned]"),console.log(" wyrm run status --run RUNID"),process.exitCode=1;return}}finally{f.close()}}if(A==="--version"||A==="-v"||A==="version"){const l=M();console.log(`${l.name??"wyrm-mcp"} v${l.version??"unknown"}`)}else!A||A==="--help"||A==="-h"||A==="help"?be():(async()=>{try{switch(A){case"search":await Ve(v);break;case"ls":await Ke(v);break;case"show":await Ge(v);break;case"capture":await Je(v);break;case"rehydrate":await Qe(v);break;case"session":await Xe(v);break;case"presence":await et(v);break;case"digest":await ze(v);break;case"bridge":await Ze(v);break;case"render":await tt(v);break;case"reverse-bridge":await ot(v);break;case"import":await st(v);break;case"stats":await rt(v);break;case"review":await it(v);break;case"sync":await ct(v);break;case"cloud":{const{cmdCloud:l}=await import("./cloud/cli.js");await l(v);break}case"grove":await Dt(v);break;case"run":await Pt(v);break;case"skill":await xt(v);break;case"prune":await at(v);break;case"license":await dt();break;case"login":await pt();break;case"activate":await ut(v);break;case"maintenance":await mt(v);break;case"index":await ft(v);break;case"update":await yt(v);break;case"prompt":await ht(v);break;case"hours":await wt(v);break;case"invoice":await bt(v);break;case"agent":await $t(v);break;case"feedback":await kt(v);break;case"setup":await vt(v);break;case"intro":{const{renderIntro:l}=await import("./visibility.js");console.log(l(M().version??"unknown"));break}case"events":await jt(v);break;case"watch":await St(v);break;case"embed":await _t(v);break;case"harvest":await Ct(v);break;case"vault":await Rt(v);break;case"connector":case"connectors":await lt(v);break;case"statusline":{const{installClaudeStatusline:l,removeClaudeStatusline:a}=await import("./autoconfig.js"),e=v.includes("--remove")?a():l();e?b(e.message):m("Claude Code not detected (~/.claude missing).");break}case"guard":{const{installWyrmGuardHooks:l,removeWyrmGuardHooks:a,wyrmGuardHookStatus:e}=await import("./autoconfig.js");if(v.includes("--status")){const o=e();if(console.log(` settings: ${o.settingsPath}`),o.installed){b(`wyrm-guard hooks installed (${o.commands.length} entr${o.commands.length===1?"y":"ies"})`);for(const t of o.commands)console.log(d.dim(` ${t}`))}else console.log(d.yellow(" wyrm-guard hooks not installed \u2014 run: wyrm guard"));break}const i=v.includes("--remove")||v.includes("--uninstall")?a():l();i?i.action==="failed"?(m(i.message),process.exitCode=1):b(i.message):m("Claude Code not detected (~/.claude missing).");break}case"ui":case"dashboard":v.includes("--ui")||v.push("--ui");case"serve":{const l=v.includes("--ui");if(l){const{enableDevMode:o}=await import("./http-auth.js");o()}const{server:a}=await import("./http-fast.js"),e=parseInt(process.env.WYRM_PORT??process.env.PORT??"3333",10),i=process.env.WYRM_BIND_HOST||"127.0.0.1";a.listen(e,i,()=>{if(b(`Wyrm HTTP server running on ${i}:${e}`),process.env.WYRM_UI_READONLY==="1"&&console.log("\u{1F512} READ-ONLY mode: writes + off-box egress are blocked; safe to expose."),l){const o=`http://localhost:${e}/ui`;console.log(`\u{1F5A5}\uFE0F Dashboard: ${o}`),import("child_process").then(({spawn:t})=>{const r=process.platform;try{const s=r==="darwin"?t("open",[o],{stdio:"ignore",detached:!0}):r==="win32"?t("cmd",["/c","start","",o],{stdio:"ignore",detached:!0}):t("xdg-open",[o],{stdio:"ignore",detached:!0});s.on("error",()=>{}),s.unref()}catch{}}).catch(()=>{})}});break}default:m(`Unknown command: ${A}`),be(),process.exit(1)}}catch(l){m(String(l)),process.exit(1)}})();
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "contract": "wyrm-cloud-memory-v1",
3
- "version": "7.9.0",
3
+ "version": "7.10.0",
4
4
  "generatedBy": "scripts/gen-tool-manifest.mjs (from the dist/ registry modules — never hand-edited)",
5
5
  "tiers": {
6
6
  "core": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wyrm-mcp",
3
- "version": "7.9.0",
3
+ "version": "7.10.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",