wyrm-mcp 7.10.0 → 7.11.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/handlers/intelligence.js +68 -68
- package/dist/maintenance.js +2 -2
- package/dist/memory-artifacts.js +23 -23
- package/dist/metabolize.js +26 -0
- package/dist/tool-manifest-v2.json +1 -1
- package/dist/tool-manifest.json +1 -1
- package/dist/wyrm-cli.js +56 -55
- package/dist/wyrm-manifest.json +1 -1
- package/package.json +1 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import{TOOL_ANNOTATIONS as $}from"../tool-annotations.js";import{currentReadCacheKey as
|
|
1
|
+
import{TOOL_ANNOTATIONS as $}from"../tool-annotations.js";import{currentReadCacheKey as I}from"./boundary.js";import{autoConfigureAll as U,findWyrmServerPath as W,getDefaultDbPath as B,getStatusSummary as q,removeFromAll as V}from"../autoconfig.js";import{daemonOr as H}from"../daemon-writer.js";import{renderFailureStats as K}from"../failure-patterns.js";import{computeStaleness as z}from"../intelligence.js";import{logger as Y}from"../logger.js";import{runMaintenance as G}from"../maintenance.js";import{cache as w}from"../performance.js";import{createProvider as Q}from"../providers/embedding-provider.js";import{reindexProjects as Z}from"../reindex.js";import{requireString as J}from"../validate.js";import{createVectorStore as X}from"../vectors.js";import{recordWrite as E,buildWriteDigest as ee}from"../receipts.js";const ye=[{name:"wyrm_feedback",description:"Record whether a recalled knowledge artifact was useful. Adjusts confidence over time \u2014 successful reuse boosts it, failure lowers it. Call after applying a recalled pattern or lesson.",inputSchema:{type:"object",properties:{artifactId:{type:"number",description:"ID of the memory artifact (from wyrm_recall results)"},success:{type:"boolean",description:"Was this artifact helpful for the task?"}},required:["artifactId","success"]},annotations:$.wyrm_feedback,aliases:[],handler:async(p,f)=>{const{memory:c}=f,{artifactId:i,success:l}=p,a=c.get(i);if(!a)return{content:[{type:"text",text:`Artifact #${i} not found.`}],isError:!0};c.recordFeedback(i,l);const t=c.get(i);return{content:[{type:"text",text:`\u{F115D} Feedback recorded ${l?"\u2705":"\u274C"}
|
|
2
2
|
|
|
3
|
-
- Artifact #${i}: "${
|
|
3
|
+
- Artifact #${i}: "${a.problem.slice(0,60)}"
|
|
4
4
|
- New confidence: ${(t.confidence*100).toFixed(0)}%
|
|
5
|
-
- Total uses: ${t.reuse_count} (\u2705 ${t.reuse_success_count} / \u274C ${t.reuse_failure_count})`}]}}},{name:"wyrm_context_build",description:"Use to build a compact context brief for the current task under a token budget - relevant patterns, lessons, and truths in one formatted block; max_tokens elides low-score items to recallable stubs.",inputSchema:{type:"object",properties:{projectPath:{type:"string"},task:{type:"string",description:"The current task"},maxItems:{type:"number",description:"Default 10, max 20"},kinds:{type:"array",items:{type:"string"},description:"Artifact kinds (see wyrm_recall)"},minConfidence:{type:"number",description:"Default 0.3"},max_tokens:{type:"number",description:"Token budget; low-score items elide to stubs"},session_id:{type:"number",description:"Already-seen dedup"},strict_budget:{type:"boolean",description:"Also elide truths over budget"}},required:["projectPath","task"]},annotations:$.wyrm_context_build,aliases:[],handler:async(
|
|
5
|
+
- Total uses: ${t.reuse_count} (\u2705 ${t.reuse_success_count} / \u274C ${t.reuse_failure_count})`}]}}},{name:"wyrm_context_build",description:"Use to build a compact context brief for the current task under a token budget - relevant patterns, lessons, and truths in one formatted block; max_tokens elides low-score items to recallable stubs.",inputSchema:{type:"object",properties:{projectPath:{type:"string"},task:{type:"string",description:"The current task"},maxItems:{type:"number",description:"Default 10, max 20"},kinds:{type:"array",items:{type:"string"},description:"Artifact kinds (see wyrm_recall)"},minConfidence:{type:"number",description:"Default 0.3"},max_tokens:{type:"number",description:"Token budget; low-score items elide to stubs"},session_id:{type:"number",description:"Already-seen dedup"},strict_budget:{type:"boolean",description:"Also elide truths over budget"}},required:["projectPath","task"]},annotations:$.wyrm_context_build,aliases:[],handler:async(p,f)=>{const{cachedResponse:c,db:i,groundTruths:l,memory:a,runBudgetedContextBuild:t,scaffoldLib:o}=f,r=p||{},s=I(),{projectPath:e,task:n,maxItems:m,kinds:u,minConfidence:d,max_tokens:g,session_id:b,strict_budget:S}=p,y=i.getProject(e);if(!y)return{content:[{type:"text",text:`Project not found: ${e}`}],isError:!0};if(!n||typeof n!="string"||!n.trim())return{content:[{type:"text",text:"wyrm_context_build: 'task' is required \u2014 pass a short description of the current task (not 'query')."}],isError:!0};if(g!=null&&!process.env.WYRM_DISABLE_TOKEN_BUDGET)return t({project:y,task:n,maxTokens:g,sessionId:b,strictBudget:S===!0,kinds:u,minConfidence:d});const h=[],v=l.formatForContext(y.id);v&&h.push(v.slice(0,1200));const _=o.findBest(n,y.id);if(_){const k=o.formatForContext(_);h.push(k.slice(0,1500))}const x=a.buildContextBrief(y.id,n,{maxItems:Math.min(m??10,20),kinds:u,minConfidence:d});if(x.sections.length>0){const k=x.text.slice(0,2e3);h.push(k)}if(h.length===0)return{content:[{type:"text",text:`\u{F115D} **Context Brief**
|
|
6
6
|
|
|
7
7
|
No relevant memory found for this task yet.
|
|
8
8
|
|
|
@@ -12,9 +12,9 @@ As you work, use \`wyrm_remember\` to store:
|
|
|
12
12
|
- \u{1F4A1} Heuristics and shortcuts
|
|
13
13
|
- \u{1F9E0} Reasoning traces from complex problems
|
|
14
14
|
|
|
15
|
-
Future context briefs will become richer over time.`}]};const
|
|
15
|
+
Future context briefs will become richer over time.`}]};const T=a.getStats(y.id),P=l.getStats(y.id),A=`\u{F115D} **Context Brief** \u2014 "${n}"
|
|
16
16
|
|
|
17
|
-
`,C=[];
|
|
17
|
+
`,C=[];v&&C.push(v.slice(0,1200)),_&&C.push(o.formatForContext(_).slice(0,1500));const O=C.length>0?`<!-- cache-stable -->
|
|
18
18
|
${C.join(`
|
|
19
19
|
|
|
20
20
|
---
|
|
@@ -24,107 +24,107 @@ ${C.join(`
|
|
|
24
24
|
|
|
25
25
|
---
|
|
26
26
|
|
|
27
|
-
`:"",
|
|
27
|
+
`:"",L=x.sections.length>0?x.text.slice(0,2e3):"";let R=`${A}${O}${L}`;R+=`
|
|
28
28
|
|
|
29
|
-
_Brief: ${P.current} ground truth${P.current!==1?"s":""}, scaffold: ${
|
|
30
|
-
- **Auto-cascade:** ${
|
|
29
|
+
_Brief: ${P.current} ground truth${P.current!==1?"s":""}, scaffold: ${_?_.scaffold.problem_type:"none"}, ${T.total} memory artifact${T.total!==1?"s":""}.`,x.sourceIds.length>0?R+=` Memory IDs: [${x.sourceIds.join(", ")}] \u2014 use \`wyrm_feedback\` to rate._`:R+="_";try{const{logSavings:k}=await import("../statusline.js"),F=i.getDatabase(),N=F.prepare("SELECT id FROM sessions WHERE project_id = ? ORDER BY created_at DESC LIMIT 1").get(y.id),M=Math.round(O.length/4);M>0&&k(F,"wyrm_context_build","cached_preamble",Math.round(M*.9),N?.id)}catch{}const D=c(R);return s&&w.set(s,D,1e4),D}},{name:"wyrm_truth_set",description:"Use to lock in a confirmed fact or constraint as project ground truth - architecture, conventions, key decisions. Injected first into every brief; re-setting the same category+key supersedes (history kept).",inputSchema:{type:"object",properties:{projectPath:{type:"string"},category:{type:"string",description:"e.g. architecture, conventions"},key:{type:"string",description:"snake_case key"},value:{type:"string",description:"The statement"},rationale:{type:"string",description:"Why this is true"},source:{type:"string",description:"'user'|'derived'|'observed'|'confirmed'"},confidence:{type:"number",description:"0-1 (default 1.0)"},ttl_days:{type:"number",description:"Stale after N days"}},required:["projectPath","category","key","value"]},annotations:$.wyrm_truth_set,aliases:[],handler:async(p,f)=>{const{db:c,groundTruths:i,presence:l}=f,{projectPath:a,category:t,key:o,value:r,rationale:s,source:e,confidence:n,ttl_days:m}=p,u=J("category",t,{maxLen:200}),d=c.getProject(a);if(!d)return{content:[{type:"text",text:`Project not found: ${a}`}],isError:!0};const{isConfigShapedTruth:g}=await import("../metabolize.js");if(process.env.WYRM_NO_TRUTH_LINT!=="1"&&g(o,r)){const h=(await import("../memory-artifacts.js")).MemoryArtifacts,_=new h(c.getDatabase()).add(d.id,{kind:"pattern",problem:`TRUTH-SHAPE LINT: "${o}" looks like a config assignment, not a ground truth: ${r.slice(0,120)}`,validatedFix:"If this really is a durable truth, re-set it as a full statement with a subject (or bypass once with WYRM_NO_TRUTH_LINT=1). If it is scan/tool config, it belongs in the data lake, not ground truths.",tags:["metabolize","truth-lint"],confidence:.5,needsReview:1}),x=E(c.getDatabase(),{tool:"wyrm_truth_set",outcome:"queued",refTable:"mem",refId:_.id,reason:"truth-shape lint \u2014 config-assignment shape queued for review instead of stored as a bare truth",source:e,projectId:d.id});return{content:[{type:"text",text:`\u{F115D} **Truth-shape lint** \u2014 "${o}" looks like a config assignment (\`ident = value\`), not a ground truth. Queued for review as mem:${_.id} instead of storing (receipt: ${x.outcome}). Re-phrase as a full statement, or set WYRM_NO_TRUTH_LINT=1 to bypass once.`}]}}const b={category:u,key:o,value:r,rationale:s,source:e,confidence:n??1,ttl_days:m};i.lastCascade=null;const S=await H("truth_set",d.id,b,()=>i.set(d.id,b));w.invalidate("wyrm_truth_get"),w.invalidate("wyrm_context_build");const y=i.lastCascade,j=y&&y.invalidated>0?`
|
|
30
|
+
- **Auto-cascade:** ${y.invalidated} downstream decision edge(s) invalidated (rested on the superseded value \u2014 review with \`wyrm_decision_downstream\`)`:"";return E(c.getDatabase(),{tool:"wyrm_truth_set",outcome:"stored",refTable:"truth",refId:S.id,reason:y&&y.invalidated>0?`supersede cascaded \u2014 ${y.invalidated} downstream decision edge(s) invalidated`:void 0,source:e,projectId:d.id}),{content:[{type:"text",text:`\u{F115D} **Ground Truth Set** \u2705
|
|
31
31
|
|
|
32
32
|
- **Category:** ${t}
|
|
33
|
-
- **Key:** ${
|
|
34
|
-
- **Value:** ${
|
|
35
|
-
- **Rationale:** ${
|
|
33
|
+
- **Key:** ${o}
|
|
34
|
+
- **Value:** ${r}${s?`
|
|
35
|
+
- **Rationale:** ${s}`:""}${m?`
|
|
36
36
|
- **TTL:** ${m} days`:""}
|
|
37
|
-
- **ID:** ${
|
|
37
|
+
- **ID:** ${S.id}${j}
|
|
38
38
|
|
|
39
|
-
This truth will be injected at the top of every \`wyrm_context_build\` response for this project.`}]}}},{name:"wyrm_truth_get",description:"Use to read the established facts and ground rules for a codebase - validated truths the AI should treat as baseline, optionally filtered by category.",inputSchema:{type:"object",properties:{projectPath:{type:"string"},category:{type:"string",description:"Category filter"}},required:["projectPath"]},annotations:$.wyrm_truth_get,aliases:[],handler:async(
|
|
39
|
+
This truth will be injected at the top of every \`wyrm_context_build\` response for this project.`}]}}},{name:"wyrm_truth_get",description:"Use to read the established facts and ground rules for a codebase - validated truths the AI should treat as baseline, optionally filtered by category.",inputSchema:{type:"object",properties:{projectPath:{type:"string"},category:{type:"string",description:"Category filter"}},required:["projectPath"]},annotations:$.wyrm_truth_get,aliases:[],handler:async(p,f)=>{const{cachedResponse:c,db:i,groundTruths:l}=f,a=I(),{projectPath:t,category:o}=p,r=i.getProject(t);if(!r)return{content:[{type:"text",text:`Project not found: ${t}`}],isError:!0};const s=l.getCurrent(r.id,o);if(s.length===0)return{content:[{type:"text",text:`\u{F115D} **Ground Truths**
|
|
40
40
|
|
|
41
|
-
${
|
|
41
|
+
${o?`No ground truths in category "${o}" for this project.`:"No ground truths stored yet. Use `wyrm_truth_set` to add validated facts."}`}]};const e=new Map;for(const u of s)e.has(u.category)||e.set(u.category,[]),e.get(u.category).push(u);let n=`\u{F115D} **Ground Truths** \u2014 ${s.length} active
|
|
42
42
|
|
|
43
|
-
`;for(const[
|
|
44
|
-
`;for(const
|
|
45
|
-
_${
|
|
46
|
-
_staleness: ${(b*100).toFixed(0)}% (TTL: ${
|
|
43
|
+
`;for(const[u,d]of e){n+=`### ${u}
|
|
44
|
+
`;for(const g of d){const b=z(g),y=b!==null&&b>.7?"[\u26A0\uFE0F STALE] ":"";n+=`- ${y}**${g.key}:** ${g.value}`,g.confidence<1&&(n+=` _(confidence: ${(g.confidence*100).toFixed(0)}%)_`),g.rationale&&(n+=`
|
|
45
|
+
_${g.rationale}_`),b!==null&&(n+=`
|
|
46
|
+
_staleness: ${(b*100).toFixed(0)}% (TTL: ${g.ttl_days}d)_`),n+=`
|
|
47
47
|
`}n+=`
|
|
48
|
-
`}const m=
|
|
48
|
+
`}const m=c(n);return a&&w.set(a,m,15e3),m}},{name:"wyrm_scaffold_get",description:"Find the best reasoning scaffold for a task description. Returns the most relevant structured checklist, or null if no match meets the confidence threshold.",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Project path (searches project + global)"},task:{type:"string",description:"Task description to match against scaffolds"},minConfidence:{type:"number",description:"Minimum match confidence 0-1 (default: 0.3)"}},required:["task"]},annotations:$.wyrm_scaffold_get,aliases:[],handler:async(p,f)=>{const{cachedResponse:c,db:i,scaffoldLib:l}=f,a=I(),{projectPath:t,task:o,minConfidence:r}=p,s=t?i.getProject(t)?.id??void 0:void 0,e=l.findBest(o,s,r);if(!e)return{content:[{type:"text",text:`\u{F115D} **Reasoning Scaffold**
|
|
49
49
|
|
|
50
|
-
No scaffold matched for: "${
|
|
50
|
+
No scaffold matched for: "${o}"
|
|
51
51
|
|
|
52
|
-
Use \`wyrm_scaffold_save\` to add scaffolds for this problem type.`}]};const n=
|
|
52
|
+
Use \`wyrm_scaffold_save\` to add scaffolds for this problem type.`}]};const n=l.formatForContext(e),m=c(`\u{F115D} **Reasoning Scaffold Match** (${(e.matchScore*100).toFixed(0)}% confidence)
|
|
53
53
|
|
|
54
|
-
${n}`);return
|
|
54
|
+
${n}`);return a&&w.set(a,m,15e3),m}},{name:"wyrm_sync",description:"Sync database with .wyrm folders in all projects",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Sync specific project, or all if not specified"},direction:{type:"string",enum:["import","export","both"],description:"Sync direction"}}},annotations:$.wyrm_sync,aliases:[],handler:async(p,f)=>{const{db:c,sync:i}=f,{projectPath:l,direction:a}=p,t=a||"both";let o=0;if(l)c.getProject(l)&&((t==="import"||t==="both")&&i.importFromFolder(l),(t==="export"||t==="both")&&i.exportToFolder(l),o=1);else{const r=c.getAllProjects(1e3);for(const s of r)try{(t==="import"||t==="both")&&i.importFromFolder(s.path),(t==="export"||t==="both")&&i.exportToFolder(s.path),o++}catch{}}return{content:[{type:"text",text:`\u{F115D} Synced ${o} project(s)`}]}}},{name:"wyrm_stats",description:"Use for a health snapshot of the memory database - row counts, vector index coverage, usage.",inputSchema:{type:"object",properties:{view:{type:"string",enum:["failures"],description:"'failures' = prevented-repeat analytics"},projectPath:{type:"string",description:"failures view scope"}}},annotations:$.wyrm_stats,aliases:[],handler:async(p,f)=>{const{WRITE_TOOLS:c,analytics:i,cachedResponse:l,db:a,failures:t,getUsageStats:o,orchestrator:r,vectorStore:s}=f,e=p||{},n=I(),{view:m,projectPath:u}=p;if(m==="failures"){const h=u?a.getProject(u):null,v=t.failureStats({projectId:h?.id??null});return{content:[{type:"text",text:K(v)}],structuredContent:v}}const d=a.getStats(),g=w.stats(),b=o();let S="";if(s){const h=s.getStats(),v=h.byType||{},_=(v.artifact||0)+(v.quest||0)+(v.session||0),x=d.artifacts+d.quests+d.sessions,T=x>0?Math.min(100,_/x*100).toFixed(1):"0";S=`
|
|
55
55
|
**Vectors:**
|
|
56
56
|
- **Provider:** ${h.provider} (${h.model})
|
|
57
57
|
- **Embeddings:** ${h.total} vectors
|
|
58
|
-
- **Coverage:** ${
|
|
58
|
+
- **Coverage:** ${T}% of embeddable content
|
|
59
59
|
- **By Type:** ${Object.entries(h.byType).map(([P,A])=>`${P}: ${A}`).join(", ")}
|
|
60
|
-
`}let
|
|
61
|
-
**Writes (24h):** ${Object.entries(h.byOutcome).map(([
|
|
60
|
+
`}let y="";try{const h=ee(a.getDatabase(),1);y=`
|
|
61
|
+
**Writes (24h):** ${Object.entries(h.byOutcome).map(([_,x])=>`${_}: ${x}`).join(", ")||"none"}
|
|
62
62
|
**Review queue:** ${h.reviewQueueDepth} awaiting wyrm_review${h.reviewQueueDepth>0?" (invisible to recall until approved)":""}
|
|
63
|
-
`}catch{}const
|
|
63
|
+
`}catch{}const j=l(`\u{F115D} **Wyrm Statistics**
|
|
64
64
|
|
|
65
|
-
- **Projects:** ${
|
|
66
|
-
- **Sessions:** ${
|
|
67
|
-
- **Quests:** ${
|
|
68
|
-
- **Data Points:** ${
|
|
69
|
-
- **Active Tokens:** ~${
|
|
70
|
-
- **Database Size:** ${
|
|
71
|
-
`+
|
|
72
|
-
**Cache:** ${
|
|
73
|
-
**Usage:** ${b.totalCalls} calls | ~${b.tokensSaved.toLocaleString()} tokens saved by cache`);return n&&
|
|
65
|
+
- **Projects:** ${d.projects}
|
|
66
|
+
- **Sessions:** ${d.sessions}
|
|
67
|
+
- **Quests:** ${d.quests}
|
|
68
|
+
- **Data Points:** ${d.dataPoints}
|
|
69
|
+
- **Active Tokens:** ~${d.totalTokens.toLocaleString()}
|
|
70
|
+
- **Database Size:** ${d.dbSize}
|
|
71
|
+
`+S+y+`
|
|
72
|
+
**Cache:** ${g.size} entries | Hit rate: ${b.cacheHitRate}
|
|
73
|
+
**Usage:** ${b.totalCalls} calls | ~${b.tokensSaved.toLocaleString()} tokens saved by cache`);return n&&w.set(n,j,15e3),j}},{name:"wyrm_vector_setup",description:"Configure embedding provider and download models. Supports local, OpenAI, and Ollama.",inputSchema:{type:"object",properties:{provider:{type:"string",enum:["auto","local","openai","ollama","none"],description:"Provider: auto-detect, local hash, OpenAI API, Ollama, or none"},model:{type:"string",description:"Model name (e.g., 'text-embedding-3-small' for OpenAI, 'nomic-embed-text' for Ollama)"},apiKey:{type:"string",description:"OpenAI API key (if using openai provider)"},ollamaUrl:{type:"string",description:"Ollama URL (default: http://localhost:11434)"}}},annotations:$.wyrm_vector_setup,aliases:[],handler:async(p,f)=>{const{db:c,indexingPipeline:i,memory:l,setVectorStore:a,vectorStore:t}=f,{provider:o,model:r,apiKey:s,ollamaUrl:e}=p,n={provider:o||"auto",model:r,apiKey:s,ollamaUrl:e};try{const m=Q(n),u=await m.isReady();if(!u&&n.provider!=="none")return{content:[{type:"text",text:`\u274C **Provider Not Ready**
|
|
74
74
|
|
|
75
|
-
Couldn't connect to ${m.name}. Check your configuration and try again.`}],isError:!0};const
|
|
75
|
+
Couldn't connect to ${m.name}. Check your configuration and try again.`}],isError:!0};const d=X(n,c.getDatabase());a(d),l.setVectorStore(d),i&&i.updateStore(d);const g=d.getStats();return{content:[{type:"text",text:`\u2705 **Vector Setup Complete**
|
|
76
76
|
|
|
77
77
|
- **Provider:** ${m.name}
|
|
78
78
|
- **Model:** ${m.model}
|
|
79
79
|
- **Dimensions:** ${m.dimensions}
|
|
80
|
-
- **Status:** ${
|
|
80
|
+
- **Status:** ${u?"Ready":"Ready (will auto-connect)"}
|
|
81
81
|
|
|
82
82
|
_Reindex your projects with wyrm_reindex to generate embeddings._`}]}}catch(m){return{content:[{type:"text",text:`\u274C **Vector Setup Failed**
|
|
83
83
|
|
|
84
|
-
${String(m)}`}],isError:!0}}}},{name:"wyrm_reindex",description:"Rebuild embeddings for a project or all projects. Regenerates vectors for sessions, quests, data, and memory artifacts (the latter power hybrid recall).",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Project to reindex, or leave empty for all projects"},dryRun:{type:"boolean",description:"Show what would be indexed without making changes"}}},annotations:$.wyrm_reindex,aliases:[],handler:async(
|
|
84
|
+
${String(m)}`}],isError:!0}}}},{name:"wyrm_reindex",description:"Rebuild embeddings for a project or all projects. Regenerates vectors for sessions, quests, data, and memory artifacts (the latter power hybrid recall).",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Project to reindex, or leave empty for all projects"},dryRun:{type:"boolean",description:"Show what would be indexed without making changes"}}},annotations:$.wyrm_reindex,aliases:[],handler:async(p,f)=>{const{db:c,failures:i,vectorStore:l}=f,{projectPath:a,dryRun:t}=p;if(!l)return{content:[{type:"text",text:`\u274C **Vector Indexing Disabled**
|
|
85
85
|
|
|
86
|
-
Run wyrm_vector_setup first to enable vector search.`}],isError:!0};let
|
|
86
|
+
Run wyrm_vector_setup first to enable vector search.`}],isError:!0};let o=[];if(a){const e=c.getProject(a);if(!e)return{content:[{type:"text",text:`\u274C **Project Not Found:** ${a}`}],isError:!0};o=[e.id]}else o=c.getAllProjects(1e3).map(n=>n.id);const{indexed:r,skipped:s}=await Z(c.getDatabase(),l,o,{dryRun:t,onError:(e,n)=>Y.error(e,n)});return{content:[{type:"text",text:`\u{F115D} **Reindexing ${t?"(Dry Run)":"Complete"}**
|
|
87
87
|
|
|
88
|
-
- **Projects:** ${
|
|
89
|
-
- **Indexed:** ${
|
|
90
|
-
- **Skipped:** ${
|
|
88
|
+
- **Projects:** ${o.length}
|
|
89
|
+
- **Indexed:** ${r}
|
|
90
|
+
- **Skipped:** ${s}
|
|
91
91
|
|
|
92
|
-
`+(t?"_Run without dryRun to actually index._":"_Vectors are now up to date._")}]}}},{name:"wyrm_maintenance",description:"Use for periodic upkeep of the memory store - archive old sessions, vacuum, prune stale events, sweep run quarantine, rebuild vector embeddings (reindex). Admin-gated, destructive; fleets run it between waves.",inputSchema:{type:"object",properties:{vacuum:{type:"boolean",description:"Reclaim space"},archiveDays:{type:"number",description:"Archive sessions > N days"}}},annotations:$.wyrm_maintenance,aliases:[],handler:async(
|
|
92
|
+
`+(t?"_Run without dryRun to actually index._":"_Vectors are now up to date._")}]}}},{name:"wyrm_maintenance",description:"Use for periodic upkeep of the memory store - archive old sessions, vacuum, prune stale events, sweep run quarantine, rebuild vector embeddings (reindex). Admin-gated, destructive; fleets run it between waves.",inputSchema:{type:"object",properties:{vacuum:{type:"boolean",description:"Reclaim space"},archiveDays:{type:"number",description:"Archive sessions > N days"}}},annotations:$.wyrm_maintenance,aliases:[],handler:async(p,f)=>{const{db:c,failures:i,presence:l,sessionSeen:a}=f,t=p||{},{vacuum:o,archiveDays:r}=p,s=G({db:c,sessionSeen:a(),failures:i,presence:l},{vacuum:o,archiveDays:r});return{content:[{type:"text",text:`\u{F115D} **Maintenance Complete**
|
|
93
93
|
|
|
94
|
-
`+
|
|
94
|
+
`+s.lines.map(n=>`- ${n}
|
|
95
95
|
`).join("")+`
|
|
96
|
-
**New Database Size:** ${
|
|
96
|
+
**New Database Size:** ${s.dbSize}`}]}}},{name:"wyrm_setup",description:"Auto-detect installed AI clients (VS Code, Claude Desktop, Cursor, Windsurf, Zed) and configure Wyrm's MCP server in all of them. Run this to connect Wyrm to a new AI or after switching providers.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["configure","check","remove"],description:"Action: configure (default), check status, or remove from all"},serverPath:{type:"string",description:"Override Wyrm server path (auto-detected if empty)"},dbPath:{type:"string",description:"Override database path (default: ~/.wyrm/wyrm.db)"}}},annotations:$.wyrm_setup,aliases:[],handler:async(p,f)=>{const{action:c,serverPath:i,dbPath:l}=p,a=c||"configure";if(a==="check")return{content:[{type:"text",text:q()}]};if(a==="remove"){const e=V();return{content:[{type:"text",text:`\u{F115D} **Wyrm Removed**
|
|
97
97
|
|
|
98
|
-
Removed from ${e.filter(
|
|
99
|
-
`+e.map(
|
|
98
|
+
Removed from ${e.filter(u=>u.action==="configured").length} AI client(s):
|
|
99
|
+
`+e.map(u=>`- ${u.client.icon} ${u.client.name}: ${u.message}`).join(`
|
|
100
100
|
`)+`
|
|
101
101
|
|
|
102
|
-
Run wyrm_setup again to reconnect.`}]}}const t=
|
|
102
|
+
Run wyrm_setup again to reconnect.`}]}}const t=U({serverPath:i||void 0,dbPath:l||void 0}),o=t.filter(e=>e.action==="configured"||e.action==="updated"),r=t.filter(e=>e.action==="failed");let s=`\u{F115D} **Wyrm Auto-Configure Complete**
|
|
103
103
|
|
|
104
|
-
`;
|
|
105
|
-
`;for(const e of t){const n=e.action==="configured"?"\u2705":e.action==="updated"?"\u{1F504}":e.action==="skipped"?"\u25CB":"\u274C";
|
|
106
|
-
`}return
|
|
107
|
-
\u26A0\uFE0F ${
|
|
104
|
+
`;s+=`Connected to ${o.length} AI client(s):
|
|
105
|
+
`;for(const e of t){const n=e.action==="configured"?"\u2705":e.action==="updated"?"\u{1F504}":e.action==="skipped"?"\u25CB":"\u274C";s+=`- ${n} ${e.client.icon} ${e.client.name}: ${e.message}
|
|
106
|
+
`}return r.length>0&&(s+=`
|
|
107
|
+
\u26A0\uFE0F ${r.length} client(s) failed. Check errors above.`),s+=`
|
|
108
108
|
|
|
109
|
-
Server: ${
|
|
110
|
-
DB: ${
|
|
109
|
+
Server: ${W()}
|
|
110
|
+
DB: ${B()}`,s+=`
|
|
111
111
|
|
|
112
|
-
_Switch AIs anytime \u2014 run wyrm_setup again to reconnect._`,{content:[{type:"text",text:
|
|
112
|
+
_Switch AIs anytime \u2014 run wyrm_setup again to reconnect._`,{content:[{type:"text",text:s}]}}},{name:"wyrm_usage",description:"View token usage stats, cache hit rates, and estimated cost savings. Helps monitor and optimize AI credit consumption.",inputSchema:{type:"object",properties:{last:{type:"number",description:"Show stats for last N calls (default: all)"},reset:{type:"boolean",description:"Reset usage counters"}}},annotations:$.wyrm_usage,aliases:[],handler:async(p,f)=>{const{cachedResponse:c,getUsageStats:i,responseFingerprints:l,usageLog:a}=f,{last:t,reset:o}=p;if(o)return a.length=0,l.clear(),w.invalidate(),{content:[{type:"text",text:"\u{F115D} Usage counters reset, caches cleared."}]};const r=i(t),s=w.stats();let e=`\u{F115D} **Wyrm Usage Report**
|
|
113
113
|
|
|
114
114
|
`;if(e+=`## Overview${t?` (last ${t} calls)`:""}
|
|
115
|
-
`,e+=`- **Total Calls:** ${
|
|
116
|
-
`,e+=`- **Cache Hits:** ${
|
|
117
|
-
`,e+=`- **Tokens In:** ~${
|
|
118
|
-
`,e+=`- **Tokens Out:** ~${
|
|
119
|
-
`,e+=`- **Tokens Saved (cache):** ~${
|
|
120
|
-
`,e+=`- **Avg Response:** ${
|
|
121
|
-
`,e+=`- **Active Cache Entries:** ${
|
|
122
|
-
|
|
123
|
-
`,
|
|
124
|
-
`;for(const
|
|
115
|
+
`,e+=`- **Total Calls:** ${r.totalCalls}
|
|
116
|
+
`,e+=`- **Cache Hits:** ${r.cachedCalls} (${r.cacheHitRate})
|
|
117
|
+
`,e+=`- **Tokens In:** ~${r.totalTokensIn.toLocaleString()}
|
|
118
|
+
`,e+=`- **Tokens Out:** ~${r.totalTokensOut.toLocaleString()}
|
|
119
|
+
`,e+=`- **Tokens Saved (cache):** ~${r.tokensSaved.toLocaleString()}
|
|
120
|
+
`,e+=`- **Avg Response:** ${r.avgResponseMs}ms
|
|
121
|
+
`,e+=`- **Active Cache Entries:** ${s.size}
|
|
122
|
+
|
|
123
|
+
`,r.topTools.length>0){e+=`## Top Tools by Token Usage
|
|
124
|
+
`;for(const d of r.topTools)e+=`- **${d.tool}:** ${d.calls} calls, ~${d.tokens.toLocaleString()} tokens
|
|
125
125
|
`;e+=`
|
|
126
|
-
`}const n=
|
|
126
|
+
`}const n=r.totalTokensIn/1e6*15,m=r.totalTokensOut/1e6*75,u=r.tokensSaved/1e6*75;return e+=`## Estimated Cost (Claude Opus rates)
|
|
127
127
|
`,e+=`- **Input:** $${n.toFixed(4)}
|
|
128
128
|
`,e+=`- **Output:** $${m.toFixed(4)}
|
|
129
|
-
`,e+=`- **Saved by cache:** $${
|
|
130
|
-
`,e+=`- **Net cost:** $${(n+m-
|
|
129
|
+
`,e+=`- **Saved by cache:** $${u.toFixed(4)}
|
|
130
|
+
`,e+=`- **Net cost:** $${(n+m-u).toFixed(4)}`,c(e,!0)}}];export{ye as intelligenceToolSpecs};
|
package/dist/maintenance.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{pruneWriteLedger as _}from"./receipts.js";function
|
|
1
|
+
import{pruneWriteLedger as _}from"./receipts.js";import{metabolizeEnabled as E,runMetabolize as h}from"./metabolize.js";function R(c,o={}){const{db:n,sessionSeen:d,failures:a,presence:u}=c,{vacuum:p,archiveDays:i}=o,r=[];if(i){const e=n.getAllProjects(1e3);let s=0;for(const t of e)s+=n.archiveOldSessions(t.id,i);r.push(`Archived ${s} old sessions`)}p&&(n.vacuum(),r.push("Vacuumed database"));try{const e=parseInt(process.env.WYRM_SEEN_TTL_DAYS??"7",10),s=d.prune(Number.isFinite(e)&&e>0?e:7);s>0&&r.push(`Pruned ${s} session_seen_artifacts rows older than ${e}d`)}catch{}try{const e=parseInt(process.env.WYRM_EVENT_RETAIN_DAYS??"90",10),s=parseInt(process.env.WYRM_EVENT_MAX_PER_PROJECT??"5000",10),{deleted:t}=n.pruneEvents({olderThanDays:Number.isFinite(e)?e:90,maxPerProject:Number.isFinite(s)?s:5e3});t>0&&r.push(`Pruned ${t} Live Memory events (retention)`)}catch{}try{if(E()){const e=h(n.getDatabase());e.exactMerged+e.nearCandidatesQueued+e.decayed>0&&r.push(`Metabolize: ${e.exactMerged} exact dupes superseded, ${e.nearCandidatesQueued} near-dup candidates queued, ${e.decayed} stale auto-captures decayed`)}}catch{}try{const e=_(n.getDatabase());e>0&&r.push(`Pruned ${e} write_ledger receipts (retention)`)}catch{}try{const e=parseInt(process.env.WYRM_EVENT_RETAIN_DAYS??"90",10),s=Number.isFinite(e)&&e>0?e:90,t=a.pruneBlocks(s);t>0&&r.push(`Pruned ${t} failure_blocks rows older than ${s}d (retention)`)}catch{}try{const e=u.reap();(e.presence>0||e.claims>0)&&r.push(`Reaped ${e.presence} stale presence row(s) and ${e.claims} expired quest claim(s)`)}catch{}try{const e=parseInt(process.env.WYRM_EVENT_RETAIN_DAYS??"90",10),s=Number.isFinite(e)&&e>0?e:90,t=n.getDatabase().prepare("DELETE FROM run_briefs WHERE created_at < datetime('now', ?)").run(`-${s} days`).changes;t>0&&r.push(`Pruned ${t} run_briefs rows older than ${s}d (retention)`)}catch{}try{const e=parseInt(process.env.WYRM_RUN_BRIEFS_MAX??"512",10),s=Number.isFinite(e)&&e>0?e:512,t=n.getDatabase().prepare(`
|
|
2
2
|
DELETE FROM run_briefs WHERE rowid IN (
|
|
3
3
|
SELECT rowid FROM run_briefs ORDER BY created_at DESC, rowid DESC LIMIT -1 OFFSET ?
|
|
4
|
-
)`).run(s).changes;
|
|
4
|
+
)`).run(s).changes;t>0&&r.push(`Pruned ${t} run_briefs rows over the ${s}-row cap (WYRM_RUN_BRIEFS_MAX)`)}catch{}try{const e=parseInt(process.env.WYRM_RUN_INACTIVE_HOURS??"24",10),s=a.sweepRunQuarantine({runInactiveHours:Number.isFinite(e)&&e>0?e:24});(s.promoted>0||s.expired>0||s.runs_abandoned>0)&&r.push(`Run-quarantine sweep: promoted ${s.promoted} failure(s), expired ${s.expired} abandoned-run failure(s), TTL-abandoned ${s.runs_abandoned} stalled run(s)`),s.degraded&&r.push(`Run-quarantine sweep: ${s.degraded}`)}catch{}return n.checkpoint(),r.push("Checkpointed WAL"),{lines:r,dbSize:n.getStats().dbSize}}export{R as runMaintenance};
|
package/dist/memory-artifacts.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import{emitEvent as V}from"./events.js";import{sanitizeFtsQuery as $,buildFtsMatchQuery as x}from"./security.js";import{getActor as z}from"./handlers/boundary.js";import{buildPage as G,DEFAULT_PAGE_SIZE as X}from"./keyset.js";import{rerankConfigured as J,rerankCandidates as Q}from"./rerank.js";import{autoSummaryPenalty as U,recallRecencyWeight as B,recallRecencyHalfLifeDays as P,recencyMultiplier as K}from"./context-ranking.js";const Y=40,Z=10,q=.3;class ce{db;vectorStore;setVectorStore(t){this.vectorStore=t}getVectorStore(){return this.vectorStore}constructor(t){this.db=t}indexArtifact(t){if(!this.vectorStore)return;const e=`${t.problem}${t.validated_fix?" "+t.validated_fix:""}`.slice(0,2e3);this.vectorStore.addVector(e,"artifact",t.id,t.project_id).catch(()=>{})}add(t,e){if(typeof e.problem!="string"||!e.problem.trim())throw new Error("memory.add: 'problem' is required (a non-empty string).");const n=e.tags?.length?e.tags.join(","):null,i=z(),
|
|
1
|
+
import{emitEvent as V}from"./events.js";import{sanitizeFtsQuery as $,buildFtsMatchQuery as x}from"./security.js";import{getActor as z}from"./handlers/boundary.js";import{buildPage as G,DEFAULT_PAGE_SIZE as X}from"./keyset.js";import{rerankConfigured as J,rerankCandidates as Q}from"./rerank.js";import{autoSummaryPenalty as U,recallRecencyWeight as B,recallRecencyHalfLifeDays as P,recencyMultiplier as K}from"./context-ranking.js";const Y=40,Z=10,q=.3;class ce{db;vectorStore;setVectorStore(t){this.vectorStore=t}getVectorStore(){return this.vectorStore}constructor(t){this.db=t}indexArtifact(t){if(!this.vectorStore)return;const e=`${t.problem}${t.validated_fix?" "+t.validated_fix:""}`.slice(0,2e3);this.vectorStore.addVector(e,"artifact",t.id,t.project_id).catch(()=>{})}add(t,e){if(typeof e.problem!="string"||!e.problem.trim())throw new Error("memory.add: 'problem' is required (a non-empty string).");const n=e.tags?.length?e.tags.join(","):null,i=z(),r=this.db.prepare(`
|
|
2
2
|
INSERT INTO memory_artifacts
|
|
3
3
|
(project_id, kind, problem, constraints, validated_fix, why_it_worked,
|
|
4
4
|
outcome, source_session_id, tags, confidence, needs_review, created_by, agent_id, run_id)
|
|
5
5
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
6
|
-
`).run(t,e.kind,e.problem.trim(),e.constraints?.trim()??null,e.validatedFix?.trim()??null,e.whyItWorked?.trim()??null,e.outcome??"neutral",e.sourceSessionId??null,n,e.confidence??1,e.needsReview??0,e.createdBy??"local",i.agent_id,i.run_id),s=this.get(
|
|
6
|
+
`).run(t,e.kind,e.problem.trim(),e.constraints?.trim()??null,e.validatedFix?.trim()??null,e.whyItWorked?.trim()??null,e.outcome??"neutral",e.sourceSessionId??null,n,e.confidence??1,e.needsReview??0,e.createdBy??"local",i.agent_id,i.run_id),s=this.get(r.lastInsertRowid);return e.needsReview||(V(this.db,{projectId:t,kind:"capture",refTable:"memory_artifacts",refId:s.id,isShared:!!s.is_shared}),this.indexArtifact(s)),s}get(t){return this.db.prepare("SELECT * FROM memory_artifacts WHERE id = ?").get(t)??null}update(t,e){return this.db.prepare(`
|
|
7
7
|
UPDATE memory_artifacts SET
|
|
8
8
|
confidence = COALESCE(?, confidence),
|
|
9
9
|
validated_fix = COALESCE(?, validated_fix),
|
|
@@ -22,7 +22,7 @@ import{emitEvent as V}from"./events.js";import{sanitizeFtsQuery as $,buildFtsMat
|
|
|
22
22
|
END,
|
|
23
23
|
updated_at = datetime('now')
|
|
24
24
|
WHERE id = ?
|
|
25
|
-
`).run(e?1:0,t)}recall(t,e,n={}){const i=n.limit??10,
|
|
25
|
+
`).run(e?1:0,t)}recall(t,e,n={}){const i=n.limit??10,r=n.minConfidence??0,s=this.searchByFts(t,e,Y,n.kind,r),o=this.searchByTags(t,e,Y,n.kind,r),a=new Set,u=[];s.forEach((l,c)=>{a.add(l.id),u.push({artifact:l,inFts:!0,inTag:!1,ftsRank:c})});for(const l of o)if(!a.has(l.id))a.add(l.id),u.push({artifact:l,inFts:!1,inTag:!0});else{const c=u.find(D=>D.artifact.id===l.id);c&&(c.inTag=!0)}return u.filter(({artifact:l})=>l.confidence>=r).map(({artifact:l,inFts:c,inTag:D,ftsRank:y})=>{let S;if(c){const w=1-(y??0)/(s.length+1);S=(D?.7:.55)+.45*w}else S=.4;S*=l.confidence,l.outcome==="positive"&&(S*=1.1);const h=(Date.now()-new Date(l.last_validated_at).getTime())/(1e3*60*60*24),R=Math.max(.5,1-h/180);return S*=R,{artifact:l,relevance_score:Math.min(1,S),match_type:c&&D?"both":c?"fts":"tag"}}).sort((l,c)=>c.relevance_score-l.relevance_score).slice(0,i)}async recallHybrid(t,e,n={}){const i=n.limit??10,r=n.minConfidence??0,s=n.rerank===!0&&J(),o=Math.min(Math.max(i*2,20),50),a=s?o:i;if(!this.vectorStore)return this.recall(t,e,n);const u=Math.max(50,a*5),E=Date.now(),l=this.searchByFts(t,e,u,n.kind,r),c=Date.now()-E,D=Date.now();let y=[];try{y=(await this.vectorStore.search(e,u,t,["artifact"])).map(p=>({id:p.content_id,s:p.similarity}))}catch{}const S=Date.now()-D;if(y.length===0)return this.recall(t,e,n);const h=Date.now(),R=60,w=Number(process.env.WYRM_RERANK_ALPHA),L=Number.isFinite(w)?Math.max(0,Math.min(1,w)):.7,m=new Map;if(process.env.WYRM_RERANK_FUSION==="rrf")l.forEach((f,p)=>m.set(f.id,(m.get(f.id)??0)+1/(R+p+1))),y.forEach((f,p)=>m.set(f.id,(m.get(f.id)??0)+1/(R+p+1)));else{const f=l.length+1;l.forEach((v,j)=>m.set(v.id,(m.get(v.id)??0)+(1-L)*(1-j/f)));const p=y.map(v=>v.s),A=Math.min(...p),F=Math.max(...p),W=F-A;y.forEach(v=>m.set(v.id,(m.get(v.id)??0)+L*(W>1e-9?(v.s-A)/W:1)))}const C=new Set(l.map(f=>f.id)),I=new Set(y.map(f=>f.id)),k=new Map(l.map(f=>[f.id,f])),d=[...m.entries()].sort((f,p)=>p[1]-f[1]),_=B(),g=P(),b=new Date,T=Math.min(d.length,Math.max(a*4,50)),N=[];for(const[f,p]of d.slice(0,T)){const A=k.get(f)??this.get(f)??void 0;A&&(A.confidence<r||A.needs_review===1||A.supersedes_id!=null||n.kind&&A.kind!==n.kind||N.push({id:f,s:p*K(A.updated_at??A.created_at,_,g,b),art:A}))}N.sort((f,p)=>p.s-f.s);const O=N[0]?.s??1,M=[];for(const{id:f,s:p,art:A}of N){if(M.length>=a)break;const F=C.has(f)&&I.has(f)?"hybrid":I.has(f)?"vector":"fts";M.push({artifact:A,relevance_score:Math.min(1,p/O),match_type:F})}const H=Date.now()-h;if(s&&M.length>0){const f=Date.now(),p=await this.rerankResults(e,M);return n.onStats?.({ftsMs:c,vectorMs:S,fusionMs:H,rerankMs:Date.now()-f,candidates:M.length}),p.slice(0,i)}return n.onStats?.({ftsMs:c,vectorMs:S,fusionMs:H,candidates:M.length}),M}async rerankResults(t,e){const n=e.map(a=>({id:a.artifact.id,text:`${a.artifact.problem}${a.artifact.validated_fix?" "+a.artifact.validated_fix:""}`.slice(0,2e3)})),i=await Q(t,n);if(!i)return e;const r=new Map(e.map(a=>[a.artifact.id,a])),s=[],o=i.length;return i.forEach((a,u)=>{const E=r.get(a.id);E&&s.push({...E,relevance_score:o>0?(o-u)/o:E.relevance_score})}),s}async recallHybridGlobal(t,e={}){const n=e.limit??10,i=e.minConfidence??0,r=Math.max(50,n*5),s=this.searchByFtsGlobal(t,r,e.kind,i),o=()=>s.slice(0,n).map((d,_)=>({artifact:d,relevance_score:1-_/(s.length+1),match_type:"fts"}));if(!this.vectorStore)return o();let a=[];try{a=(await this.vectorStore.search(t,r,void 0,["artifact"])).map(_=>({id:_.content_id,s:_.similarity}))}catch{}if(a.length===0)return o();const u=60,E=Number(process.env.WYRM_RERANK_ALPHA),l=Number.isFinite(E)?Math.max(0,Math.min(1,E)):.7,c=new Map;if(process.env.WYRM_RERANK_FUSION==="rrf")s.forEach((d,_)=>c.set(d.id,(c.get(d.id)??0)+1/(u+_+1))),a.forEach((d,_)=>c.set(d.id,(c.get(d.id)??0)+1/(u+_+1)));else{const d=s.length+1;s.forEach((N,O)=>c.set(N.id,(c.get(N.id)??0)+(1-l)*(1-O/d)));const _=a.map(N=>N.s),g=Math.min(..._),b=Math.max(..._),T=b-g;a.forEach(N=>c.set(N.id,(c.get(N.id)??0)+l*(T>1e-9?(N.s-g)/T:1)))}const D=new Set(s.map(d=>d.id)),y=new Set(a.map(d=>d.id)),S=new Map(s.map(d=>[d.id,d])),h=[...c.entries()].sort((d,_)=>_[1]-d[1]),R=B(),w=P(),L=new Date,m=Math.min(h.length,Math.max(n*4,50)),C=[];for(const[d,_]of h.slice(0,m)){const g=S.get(d)??this.get(d)??void 0;g&&(g.confidence<i||g.needs_review===1||g.supersedes_id!=null||e.kind&&g.kind!==e.kind||C.push({id:d,s:_*K(g.updated_at??g.created_at,R,w,L),art:g}))}C.sort((d,_)=>_.s-d.s);const I=C[0]?.s??1,k=[];for(const{id:d,s:_,art:g}of C){if(k.length>=n)break;const b=D.has(d)&&y.has(d)?"hybrid":y.has(d)?"vector":"fts";k.push({artifact:g,relevance_score:Math.min(1,_/I),match_type:b})}return k}searchByFtsGlobal(t,e,n,i=0){if(!this.db)return[];let r="";try{const a=$(t);r=a?x(a):""}catch{return[]}if(!r)return[];const s=n?"AND a.kind = ?":"",o=[r,i];n&&o.push(n),o.push(e);try{return this.db.prepare(`
|
|
26
26
|
SELECT a.* FROM memory_artifacts a
|
|
27
27
|
JOIN memory_artifacts_fts ON a.id = memory_artifacts_fts.rowid
|
|
28
28
|
WHERE memory_artifacts_fts MATCH ?
|
|
@@ -32,7 +32,7 @@ import{emitEvent as V}from"./events.js";import{sanitizeFtsQuery as $,buildFtsMat
|
|
|
32
32
|
${s}
|
|
33
33
|
ORDER BY rank, a.confidence DESC
|
|
34
34
|
LIMIT ?
|
|
35
|
-
`).all(...o)}catch{return[]}}searchByFts(t,e,n,i,
|
|
35
|
+
`).all(...o)}catch{return[]}}searchByFts(t,e,n,i,r=0){let s="";try{const u=$(e);s=u?x(u):""}catch{return this.listRecent(t,n,i)}if(!s)return this.listRecent(t,n,i);const o=i?"AND a.kind = ?":"",a=[s,t,r];i&&a.push(i),a.push(n);try{return this.db.prepare(`
|
|
36
36
|
SELECT a.* FROM memory_artifacts a
|
|
37
37
|
JOIN memory_artifacts_fts ON a.id = memory_artifacts_fts.rowid
|
|
38
38
|
WHERE memory_artifacts_fts MATCH ?
|
|
@@ -43,50 +43,50 @@ import{emitEvent as V}from"./events.js";import{sanitizeFtsQuery as $,buildFtsMat
|
|
|
43
43
|
${o}
|
|
44
44
|
ORDER BY rank, a.confidence DESC
|
|
45
45
|
LIMIT ?
|
|
46
|
-
`).all(...
|
|
46
|
+
`).all(...a)}catch{return this.listRecent(t,n,i)}}searchByTags(t,e,n,i,r=0){const s=e.toLowerCase().split(/\s+/).filter(c=>c.length>2);if(!s.length)return[];const o=c=>c.replace(/[%_\\]/g,"\\$&"),a=s.map(()=>"LOWER(tags) LIKE ? ESCAPE '\\'").join(" OR "),u=s.map(c=>`%${o(c)}%`),E=i?"AND kind = ?":"",l=[t,r,...u];return i&&l.push(i),l.push(n),this.db.prepare(`
|
|
47
47
|
SELECT * FROM memory_artifacts
|
|
48
48
|
WHERE project_id = ? AND confidence >= ? AND supersedes_id IS NULL
|
|
49
49
|
AND needs_review = 0
|
|
50
|
-
AND (${
|
|
51
|
-
${
|
|
50
|
+
AND (${a})
|
|
51
|
+
${E}
|
|
52
52
|
ORDER BY confidence DESC, reuse_success_count DESC
|
|
53
53
|
LIMIT ?
|
|
54
|
-
`).all(...
|
|
54
|
+
`).all(...l)}listRecent(t,e=20,n){const i=n?"AND kind = ?":"",r=[t];return n&&r.push(n),r.push(e),this.db.prepare(`
|
|
55
55
|
SELECT * FROM memory_artifacts
|
|
56
56
|
WHERE project_id = ? AND supersedes_id IS NULL AND needs_review = 0
|
|
57
57
|
${i}
|
|
58
58
|
ORDER BY confidence DESC, created_at DESC
|
|
59
59
|
LIMIT ?
|
|
60
|
-
`).all(...
|
|
61
|
-
_Constraints:_ ${
|
|
62
|
-
_Solution:_ ${
|
|
63
|
-
_Why it worked:_ ${
|
|
64
|
-
_Note: This approach failed \u2014 avoid it_`),
|
|
60
|
+
`).all(...r)}buildContextBrief(t,e,n={}){const i=n.kinds??["pattern","heuristic","reasoning_trace","lesson","anti_pattern"],r=n.maxItems??Z,s=n.minConfidence??q,o=this.recall(t,e,{limit:r*2,minConfidence:s});o.sort((h,R)=>(U(h.artifact.problem)>0?1:0)-(U(R.artifact.problem)>0?1:0));const a=o.filter(h=>i.includes(h.artifact.kind)).slice(0,r),u=new Map;for(const h of a){const R=u.get(h.artifact.kind)??[];R.push(h),u.set(h.artifact.kind,R)}const E={pattern:"\u2705 Proven Patterns",heuristic:"\u{1F4A1} Heuristics",reasoning_trace:"\u{1F9E0} Past Reasoning",lesson:"\u{1F4DA} Lessons Learned",anti_pattern:"\u26A0\uFE0F Anti-Patterns to Avoid"},l=["pattern","heuristic","reasoning_trace","lesson","anti_pattern"],c=[],D=[];let y=0;for(const h of l){if(!i.includes(h))continue;const R=u.get(h)??[];if(!R.length)continue;const w=[];for(const L of R){if(y>=r)break;const m=L.artifact;let C=`**Problem:** ${m.problem}`;m.constraints&&(C+=`
|
|
61
|
+
_Constraints:_ ${m.constraints}`),m.validated_fix&&(C+=`
|
|
62
|
+
_Solution:_ ${m.validated_fix}`),m.why_it_worked&&(C+=`
|
|
63
|
+
_Why it worked:_ ${m.why_it_worked}`),m.outcome==="negative"&&(C+=`
|
|
64
|
+
_Note: This approach failed \u2014 avoid it_`),w.push(C),D.push(m.id),y++}w.length&&c.push({heading:E[h],items:w,source:h})}let S="";if(c.length>0){S+=`---
|
|
65
65
|
## \u{F115D} Memory Brief
|
|
66
66
|
_Relevant past knowledge from Wyrm:_
|
|
67
67
|
|
|
68
|
-
`;for(const
|
|
69
|
-
`;for(const R of
|
|
70
|
-
`;
|
|
71
|
-
`}
|
|
72
|
-
`}return{sections:c,text:
|
|
68
|
+
`;for(const h of c){S+=`### ${h.heading}
|
|
69
|
+
`;for(const R of h.items)S+=`- ${R}
|
|
70
|
+
`;S+=`
|
|
71
|
+
`}S+=`---
|
|
72
|
+
`}return{sections:c,text:S,sourceIds:D}}getStats(t){const e=this.db.prepare("SELECT COUNT(*) as n FROM memory_artifacts WHERE project_id = ? AND supersedes_id IS NULL").get(t).n,n=this.db.prepare("SELECT COUNT(*) as n FROM memory_artifacts WHERE project_id = ? AND supersedes_id IS NOT NULL").get(t).n,i=this.db.prepare("SELECT AVG(confidence) as v FROM memory_artifacts WHERE project_id = ? AND supersedes_id IS NULL").get(t).v??0,r=this.db.prepare("SELECT kind, COUNT(*) as cnt FROM memory_artifacts WHERE project_id = ? AND supersedes_id IS NULL GROUP BY kind").all(t),s={};for(const o of r)s[o.kind]=o.cnt;return{total:e,byKind:s,avgConfidence:Math.round(i*100)/100,supersededCount:n}}listAll(t,e={}){const n=e.kind?"AND kind = ?":"",i=e.includeSuperseded?"":"AND supersedes_id IS NULL",r=[t];return e.kind&&r.push(e.kind),r.push(e.limit??50),this.db.prepare(`
|
|
73
73
|
SELECT * FROM memory_artifacts
|
|
74
74
|
WHERE project_id = ? ${n} ${i}
|
|
75
75
|
ORDER BY confidence DESC, created_at DESC
|
|
76
76
|
LIMIT ?
|
|
77
|
-
`).all(...
|
|
77
|
+
`).all(...r)}listPage(t,e={pageSize:X}){const n=e.kind?"AND kind = ?":"",i=e.includeSuperseded?"":"AND supersedes_id IS NULL",r="AND needs_review = 0",s=[t];e.kind&&s.push(e.kind);let o="";if(e.after){const E=Number(e.after.sortKey);Number.isFinite(E)&&(o="AND (confidence < ? OR (confidence = ? AND id < ?))",s.push(E,E,e.after.id))}const a=e.pageSize+1;s.push(a);const u=this.db.prepare(`
|
|
78
78
|
SELECT * FROM memory_artifacts
|
|
79
|
-
WHERE project_id = ? ${n} ${i} ${
|
|
79
|
+
WHERE project_id = ? ${n} ${i} ${r} ${o}
|
|
80
80
|
ORDER BY confidence DESC, id DESC
|
|
81
81
|
LIMIT ?
|
|
82
|
-
`).all(...s);return G(
|
|
82
|
+
`).all(...s);return G(u,e.pageSize,E=>({sortKey:String(E.confidence),id:E.id}))}pruneStale(t={}){const e=t.minConfidence??.3,n=t.olderThanDays??90,i=t.dryRun??!0,r=t.projectId!=null,s=[e,`-${n} days`];r&&s.push(t.projectId);const o=this.db.prepare(`
|
|
83
83
|
SELECT id, kind, problem, confidence, last_accessed_at
|
|
84
84
|
FROM memory_artifacts
|
|
85
85
|
WHERE confidence < ?
|
|
86
86
|
AND (last_accessed_at IS NULL OR last_accessed_at < datetime('now', ?))
|
|
87
87
|
AND needs_review = 0
|
|
88
88
|
AND supersedes_id IS NULL
|
|
89
|
-
${
|
|
89
|
+
${r?"AND project_id = ?":""}
|
|
90
90
|
ORDER BY confidence ASC
|
|
91
91
|
LIMIT 500
|
|
92
|
-
`).all(...s);return i||o.length===0?{candidates:o,deleted:0,dryRun:i}:{candidates:o,deleted:this.deleteArtifacts(o.map(
|
|
92
|
+
`).all(...s);return i||o.length===0?{candidates:o,deleted:0,dryRun:i}:{candidates:o,deleted:this.deleteArtifacts(o.map(a=>a.id)),dryRun:i}}deleteArtifacts(t){if(t.length===0)return 0;const e=t.map(()=>"?").join(",");return this.db.prepare(`DELETE FROM memory_artifacts WHERE id IN (${e}) AND needs_review = 0`).run(...t).changes}}export{ce as MemoryArtifacts};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import{createHash as g}from"crypto";import{recordWrite as E}from"./receipts.js";const h=t=>g("sha1").update(t).digest("hex");function p(t,e){return e!=null?t.prepare(`
|
|
2
|
+
SELECT id, project_id, kind, problem, validated_fix, confidence, reuse_count,
|
|
3
|
+
created_by, created_at, updated_at, last_accessed_at
|
|
4
|
+
FROM memory_artifacts
|
|
5
|
+
WHERE needs_review = 0 AND supersedes_id IS NULL AND project_id = ?
|
|
6
|
+
ORDER BY id
|
|
7
|
+
`).all(e):t.prepare(`
|
|
8
|
+
SELECT id, project_id, kind, problem, validated_fix, confidence, reuse_count,
|
|
9
|
+
created_by, created_at, updated_at, last_accessed_at
|
|
10
|
+
FROM memory_artifacts
|
|
11
|
+
WHERE needs_review = 0 AND supersedes_id IS NULL
|
|
12
|
+
ORDER BY id
|
|
13
|
+
`).all()}function x(t,e={}){const r=p(t,e.projectId),n=new Map;for(const s of r){const u=`${s.project_id}:${h(s.problem+`
|
|
14
|
+
`+(s.validated_fix??""))}`,a=n.get(u);a?a.push(s):n.set(u,[s])}let i=0,o=0;const d=t.prepare("UPDATE memory_artifacts SET supersedes_id = ?, updated_at = datetime('now') WHERE id = ?");for(const s of n.values()){if(s.length<2)continue;o++;const u=s[0];for(const a of s.slice(1))e.dryRun||(d.run(u.id,a.id),E(t,{tool:"wyrm_metabolize",outcome:"merged",refTable:"mem",refId:a.id,reason:`exact duplicate of mem:${u.id} \u2014 superseded (not deleted; sync-safe)`,source:"metabolize",projectId:a.project_id})),i++}return{merged:i,groups:o}}const R=new Set(["the","a","an","and","or","to","of","in","on","for","with","is","was","are","this","that","it","as","at","by"]);function w(t,e=3){const r=t.toLowerCase().split(/[^a-z0-9]+/).filter(i=>i.length>1&&!R.has(i)),n=new Set;for(let i=0;i+e<=r.length;i++)n.add(r.slice(i,i+e).join(" "));return n}function $(t,e){if(t.size===0||e.size===0)return 0;let r=0;const[n,i]=t.size<=e.size?[t,e]:[e,t];for(const o of n)i.has(o)&&r++;return r/(t.size+e.size-r)}function L(t,e={}){const r=e.threshold??.82,n=e.maxPairs??20,o=p(t,e.projectId).map(a=>({r:a,s:w(`${a.problem} ${a.validated_fix??""}`.slice(0,1500))})).filter(a=>a.s.size>=8);let d=0,s=0;const u=t.prepare("SELECT 1 FROM memory_artifacts WHERE tags LIKE ? ESCAPE '\\' LIMIT 1");for(let a=0;a<o.length&&d<n;a++)for(let m=a+1;m<o.length&&d<n;m++){if(o[a].r.project_id!==o[m].r.project_id)continue;s++;const f=$(o[a].s,o[m].s);if(f<r)continue;const c=o[a].r,l=o[m].r,_="nd:"+h(`${Math.min(c.id,l.id)}:${Math.max(c.id,l.id)}`).slice(0,16);u.get("%"+_.replace(/[%_\\]/g,y=>"\\"+y)+"%")||(e.dryRun||(t.prepare(`
|
|
15
|
+
INSERT INTO memory_artifacts (project_id, kind, problem, validated_fix, confidence, needs_review, tags, created_by)
|
|
16
|
+
VALUES (?, 'pattern', ?, ?, ?, 1, ?, 'metabolize')
|
|
17
|
+
`).run(c.project_id,`MERGE CANDIDATE (similarity ${(f*100).toFixed(0)}%): mem:${c.id} and mem:${l.id} look like the same memory. Approve to keep both; to merge, supersede one via review.`,`mem:${c.id}: ${c.problem.slice(0,160)}
|
|
18
|
+
---
|
|
19
|
+
mem:${l.id}: ${l.problem.slice(0,160)}`,f,`metabolize,near-dup,${_}`),E(t,{tool:"wyrm_metabolize",outcome:"queued",reason:`near-duplicate candidate mem:${c.id} ~ mem:${l.id} (${(f*100).toFixed(0)}%) \u2014 review to merge`,source:"metabolize",projectId:c.project_id})),d++)}return{queued:d,comparedPairs:s}}function z(t,e={}){const r=e.olderThanDays??60,n=e.factor??.9,i=e.floor??.3,o=t.prepare(`
|
|
20
|
+
SELECT id FROM memory_artifacts
|
|
21
|
+
WHERE needs_review = 0 AND supersedes_id IS NULL
|
|
22
|
+
AND reuse_count = 0
|
|
23
|
+
AND confidence > ?
|
|
24
|
+
AND updated_at < datetime('now', ?)
|
|
25
|
+
AND (problem LIKE '[auto:%' OR created_by LIKE 'bridge:%' OR created_by = 'auto-extract')
|
|
26
|
+
`).all(i,`-${r} days`);if(!e.dryRun&&o.length>0){const d=t.prepare("UPDATE memory_artifacts SET confidence = MAX(?, confidence * ?), updated_at = datetime('now') WHERE id = ?");for(const{id:s}of o)d.run(i,n,s)}return{decayed:o.length}}function S(t,e={}){const r=p(t,e.projectId).length,n=x(t,e),i=e.near===!1?{queued:0}:L(t,e),o=z(t,e);return{scanned:r,exactMerged:n.merged,nearCandidatesQueued:i.queued,decayed:o.decayed,dryRun:!!e.dryRun}}function j(t=process.env){const e=(t.WYRM_METABOLIZE??"").toLowerCase();return e==="1"||e==="true"||e==="yes"}function D(t,e){const r=e.trim();if(/^[a-z][a-z0-9_.-]*\s*=\s*(true|false|\d+(\.\d+)?|"[^"]{0,40}"|'[^']{0,40}'|[a-z0-9_.-]{1,40})$/i.test(r))return!0;const n=t.trim().toLowerCase();return!!(r.toLowerCase().startsWith(n)&&/=\s*\S+$/.test(r)&&r.length<80&&!/[.!?]\s/.test(r))}export{z as decayStaleAutoCaptures,D as isConfigShapedTruth,$ as jaccard,x as mergeExactDuplicates,j as metabolizeEnabled,L as queueNearDuplicates,S as runMetabolize,w as shingles};
|
package/dist/tool-manifest.json
CHANGED
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 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
|
|
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 r=0;for(;r<l.length;){const o=l[r];if(o.startsWith("--")){const t=o.slice(2),n=l[r+1];n&&!n.startsWith("--")?(e[t]=n,r+=2):(e[t]=!0,r++)}else a.push(o),r++}return{positional:a,flags:e}}function x(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),r=a[0];r||(m("Usage: wyrm search <query> [--project <name>] [--type all|memories|truths|quests|data]"),process.exit(1));const o=_(),t=o.getDatabase(),n=e.type??"all",s=e.project,p=(s?x(o,s):null)?.id;S(`Search: "${r}"`);const c=[];if(n==="all"||n==="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(
|
|
7
|
+
`).all(r);for(const u of g)c.push([`mem:${u.id}`,"memory",u.kind,u.problem.slice(0,80)])}catch{}if(n==="all"||n==="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(
|
|
12
|
+
`).all(r);for(const u of g)c.push([`session:${u.id}`,"session","",u.objectives.slice(0,80)])}catch{}if(n==="all"||n==="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(
|
|
17
|
+
`).all(r);for(const u of g)c.push([`quest:${u.id}`,"quest",u.priority,u.title.slice(0,80)])}catch{}if(n==="all"||n==="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(
|
|
23
|
-
${c.length} result${c.length!==1?"s":""}`))}async function Ke(l){const{flags:a}=C(l),e=a.type??"all",
|
|
22
|
+
`).all(r);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 "${r}"`));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",r=T(a.limit,20),o=a.project,t=_(),n=t.getDatabase(),i=(o?x(t,o):null)?.id,p=i?`WHERE project_id = ${i}`:"",c=i?`AND project_id = ${i}`:"";if(S("Wyrm Memory"),e==="all"||e==="memories"){S("Memories");const f=n.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(
|
|
27
|
+
`).all(r);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=n.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(
|
|
31
|
+
`).all(r);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=n.prepare(`
|
|
32
32
|
SELECT id, priority, title, status FROM quests
|
|
33
33
|
${p}
|
|
34
34
|
ORDER BY created_at DESC LIMIT ?
|
|
35
|
-
`).all(
|
|
36
|
-
`+
|
|
37
|
-
`+
|
|
38
|
-
`+
|
|
39
|
-
`+
|
|
40
|
-
`+
|
|
41
|
-
`+
|
|
42
|
-
`+String(
|
|
43
|
-
`+
|
|
44
|
-
`+
|
|
45
|
-
`+
|
|
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(([
|
|
47
|
-
`);return}const h=
|
|
48
|
-
`);return}c=h[0].id}const f=p.rehydrate(c);if(!f){
|
|
35
|
+
`).all(r);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[r,o]=e.split(":"),t=parseInt(o??"",10);(!r||isNaN(t))&&(m(`Invalid typed ID: ${e}. Format: type:number (e.g. mem:41)`),process.exit(1));const n=_(),s=n.getDatabase();switch(S(`${e}`),r){case"mem":{const i=s.prepare("SELECT * FROM memory_artifacts WHERE id = ?").get(t);if(!i){m(`Memory artifact ${t} not found`);break}console.log(d.bold("Kind: ")+i.kind),console.log(d.bold("Confidence:")+` ${Math.round(i.confidence*100)}%`),console.log(d.bold("Problem: ")+`
|
|
36
|
+
`+i.problem),i.validated_fix&&console.log(d.bold("Solution: ")+`
|
|
37
|
+
`+i.validated_fix),i.why_it_worked&&console.log(d.bold("Why: ")+`
|
|
38
|
+
`+i.why_it_worked),i.tags&&console.log(d.bold("Tags: ")+i.tags),console.log(d.bold("Created: ")+i.created_at);break}case"quest":{const i=s.prepare("SELECT * FROM quests WHERE id = ?").get(t);if(!i){m(`Quest ${t} not found`);break}console.log(d.bold("Title: ")+i.title),console.log(d.bold("Priority: ")+i.priority),console.log(d.bold("Status: ")+i.status),i.description&&console.log(d.bold("Desc: ")+`
|
|
39
|
+
`+i.description),i.tags&&console.log(d.bold("Tags: ")+i.tags),console.log(d.bold("Created: ")+i.created_at);break}case"truth":{const i=s.prepare("SELECT * FROM ground_truths WHERE id = ?").get(t);if(!i){m(`Ground truth ${t} not found`);break}console.log(d.bold("Category: ")+i.category),console.log(d.bold("Key: ")+i.key),console.log(d.bold("Value: ")+`
|
|
40
|
+
`+i.value),i.rationale&&console.log(d.bold("Rationale:")+`
|
|
41
|
+
`+i.rationale),console.log(d.bold("Active: ")+(i.is_current?"Yes":"No (superseded)")),console.log(d.bold("Created: ")+i.created_at);break}case"data":{const i=s.prepare("SELECT * FROM data_lake WHERE id = ?").get(t);if(!i){m(`Data point ${t} not found`);break}console.log(d.bold("Category: ")+i.category),console.log(d.bold("Key: ")+i.key),console.log(d.bold("Value: ")+`
|
|
42
|
+
`+String(i.value).slice(0,500)),console.log(d.bold("Created: ")+i.created_at);break}case"session":{const i=s.prepare("SELECT * FROM sessions WHERE id = ?").get(t);if(!i){m(`Session ${t} not found`);break}console.log(d.bold("Date: ")+i.date),console.log(d.bold("Objectives: ")+`
|
|
43
|
+
`+i.objectives),i.completed&&console.log(d.bold("Completed: ")+`
|
|
44
|
+
`+i.completed),i.notes&&console.log(d.bold("Notes: ")+`
|
|
45
|
+
`+i.notes);break}default:m(`Unknown type prefix: ${r}. Use mem|quest|truth|data|session`)}n.close()}async function Je(l){const{positional:a,flags:e}=C(l),r=a[0];r||(m('Usage: wyrm capture "<content>" [--project <name>] [--mode auto|quest|truth|memory]'),process.exit(1));const o=e.project,t=e.mode,n=_(),s=n.getDatabase();let i=null;if(o){const k=x(n,o);k||(m(`Project not found: ${o}`),n.close(),process.exit(1)),i=k.id}let p=We(r);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")&&i===null&&(m('A --project is required to capture. Use: wyrm capture "<text>" --project <name>'),n.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=n.addQuest(i,r.slice(0,200),"","medium").id,y="quest";else if(c==="truth")t!=="truth"&&g<100?(h=$.add(i,{kind:"pattern",problem:r,confidence:g/100,needsReview:1}).id,y="mem",w=!0):(h=R.set(i,{category:"decision",key:r.slice(0,60),value:r}).id,y="truth");else{const k=g>=75;h=$.add(i,{kind:f,problem:r,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:i??void 0});n.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:r}=await import("./receipts.js"),o=_();try{const t=r(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(([n,s])=>`${n} ${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(n=>[n.tool,n.outcome,String(n.n)]))),t.reasons.length>0){console.log(d.bold("Non-stored reasons:"));for(const n of t.reasons)console.log(` ${n.outcome} \xD7${n.n} \u2014 ${n.reason.slice(0,110)}`)}t.bySource.length>0&&console.log(d.bold("By source: ")+t.bySource.map(n=>`${n.source} ${n.n}`).join(" \xB7 "))}finally{o.close()}}async function Qe(l){const{flags:a}=C(l),e=T(a.session,0),r=a.path,o=a.project,t=T(a["max-chars"],6e3),n=a.quiet===!0,s=console.log;console.log=()=>{};const i=_();try{const p=new Me(i.getDatabase());let c=e;if(c<=0){let u=r?i.getProject(r):void 0;if(!u&&o&&(u=x(i,o)??void 0),u||(u=i.getProject(process.cwd())),!u){n||process.stderr.write(`wyrm rehydrate: no Wyrm project for this directory, nothing to restore.
|
|
47
|
+
`);return}const h=i.getRecentSessions(u.id,1);if(h.length===0){n||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){n||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{
|
|
52
|
+
`)}finally{i.close(),console.log=s}}async function Ze(l){const{flags:a}=C(l),{runMetabolize:e}=await import("./metabolize.js"),r=_(),o=console.log;try{let t;if(typeof a.project=="string"){const s=x(r,a.project);if(!s){m(`Project not found: ${a.project}`),process.exitCode=1;return}t=s.id}const n=e(r.getDatabase(),{projectId:t,dryRun:a["dry-run"]===!0,near:a["no-near"]===!0?!1:void 0});console.log(`metabolize${n.dryRun?" (dry-run)":""} \u2014 scanned ${n.scanned} \xB7 exact-merged ${n.exactMerged} (superseded, not deleted) \xB7 near-dup candidates queued ${n.nearCandidatesQueued} \xB7 decayed ${n.decayed}`)}finally{r.close(),console.log=o}}async function Xe(l){const{positional:a,flags:e}=C(l),r=a[0],o=await import("./bridge.js");if(r==="init"){const{existsSync:t,writeFileSync:n}=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 i=(await import("node:os")).homedir(),p={sources:[{type:"remember",name:"remember",dir:`${i}/.remember`,projectPath:i,windowDays:7}]};n(s,JSON.stringify(p,null,2)),b(`Wrote ${s} (1 source: remember). Add render/reverse sources per project as needed.`);return}if(r==="status"){const t=o.loadConfig();if(!t){m(`No bridge config at ${o.configPath()} \u2014 run \`wyrm bridge init\`.`),process.exitCode=1;return}const n=o.loadState();S("Bridge sources");for(const s of t.sources){const i=Object.keys(n).filter(c=>c.startsWith(s.name+":")),p=i.reduce((c,f)=>c+n[f].length,0);console.log(` ${s.name} [${s.type}] \u2192 ${"projectPath"in s?s.projectPath:""}${s.type==="remember"?` (${i.length} files tracked, ${p} sections bridged)`:""}`)}return}if(r==="run"){const t=o.loadConfig();if(!t){m(`No bridge config at ${o.configPath()} \u2014 run \`wyrm bridge init\`.`),process.exitCode=1;return}const n=_(),s=console.log;try{const i={wyrm_version:M().version??"unknown",compiled_at:new Date().toISOString()},p=await o.runBridge(n.getDatabase(),t,i,{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{n.close(),console.log=s}}m("Usage: wyrm bridge <init|status|run> [--source <name>] [--dry-run] [--force]"),process.exitCode=1}async function et(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"),n=o("project"),s=o("run")?.slice(0,64),i=console.log;console.log=()=>{};const p=_();try{let c=t?p.getProject(t):void 0;if(!c&&n&&(c=x(p,n)??void 0),c||(c=p.getProject(process.cwd())),!c){process.stderr.write(`wyrm session log: no Wyrm project for this directory, nothing recorded.
|
|
53
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=
|
|
55
|
-
`);return}if(
|
|
54
|
+
`)}finally{p.close(),console.log=i}}async function tt(l){const{positional:a,flags:e}=C(l),r=a[0],o=p=>typeof e[p]=="string"?e[p]:void 0,{AgentPresence:t,processStartTime:n,presenceLiveness:s}=await import("./presence.js"),i=_();try{const p=new t(i.getDatabase());if(r==="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?i.getProject(f):void 0;!g&&o("project")&&(g=x(i,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=n(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(r==="list"||r==="ls"||r===void 0){const c=i.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
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(
|
|
57
|
+
`)}return}if(r==="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{
|
|
60
|
-
`);return}const w=o??u.path;p.length===0&&
|
|
61
|
-
`).filter(y=>y.trim()),g=new G(
|
|
59
|
+
`);return}m("Usage: wyrm presence <announce|list|release> [options]"),process.exitCode=1}finally{i.close()}}async function ot(l){const{flags:a}=C(l),e=a.path,r=a.project,o=a.out,t=a.brief===!0,n=a.force===!0,s=a.quiet===!0,i=(typeof a.client=="string"?a.client:"").split(",").map(u=>u.trim().toLowerCase()).filter(Boolean);let p=i.filter(u=>ce.includes(u));const c=i.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&&r&&(u=x(g,r)??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&&i.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:n});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 st(l){const{flags:a}=C(l),e=a.path,r=a.project,o=a.root,t=a["dry-run"]===!0||a.dry===!0,n=(typeof a.client=="string"?a.client:"").split(",").map(f=>f.trim().toLowerCase()).filter(Boolean),s=["claude","cursor","copilot","agents"],i=n.filter(f=>s.includes(f)),p=await import("./reverse-bridge.js"),c=_();try{let f=e?c.getProject(e):void 0;if(!f&&r&&(f=x(c,r)??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 i){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 nt(l){const a=C(l);if(typeof a.flags.from=="string"){await rt(a);return}const{positional:e,flags:r}=C(l.slice(1)),o=l[0],t=r.project;if(o==="git"){const n=T(r.last,20),s=_(),i=s.getDatabase();let p=null;if(t){const y=x(s,t);y||(m(`Project not found: ${t}`),s.close(),process.exit(1)),p=y.id}else{const y=i.prepare("SELECT p.* FROM projects p JOIN sessions s ON s.project_id = p.id ORDER BY s.created_at DESC LIMIT 1").get();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",`-${n}`],{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(i);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 n=e[0],s=r.format??"plain";n||(m("Usage: wyrm import rules <path> [--project <name>] [--format cursorrules|copilot|plain]"),process.exit(1)),H(n)||(m(`File not found: ${n}`),process.exit(1));const i=B(n,"utf-8"),p=_(),c=p.getDatabase();let f=null;if(t){const k=x(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=n.split("/").pop()??"rules",u=["imported",s,g],h=i.split(/\n(?=#)/),y=i.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 rt(l){const a=l.flags.from,e=l.positional[0],r=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(i){m(`Could not parse JSON from ${e}: ${i.message}`),process.exit(1)}let n;try{n=Ue(a,t)}catch(i){m(i.message),process.exit(1)}const s=_();try{let i=r?x(s,r):null;if(i||(i=s.getProject(process.cwd())??null),!i){m(r?`Project not found: ${r}`:"wyrm import: no Wyrm project for this directory (pass --project <name>)."),process.exitCode=1;return}if(n.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 n)c.add(i.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(i.name)} review queue. Run ${d.cyan("wyrm review")} to vet (tagged ${d.dim(`imported_from:${n[0]?.source??a}`)}).`)}finally{s.close()}}async function it(l){const{flags:a}=C(l),e=a.project,r=_(),o=r.getDatabase();if(S("Wyrm Statistics"),e){const t=x(r,e);t||(m(`Project not found: ${e}`),r.close(),process.exit(1));const n=r.getProjectStats(t.id),s=[["Sessions",String(n.sessions)],["Quests (pending)",String(n.quests.pending)],["Quests (completed)",String(n.quests.completed)],["Data Points",String(n.dataPoints)]];console.log(P(["Metric","Value"],s))}else{const t=r.getStats(),n=[["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,i=o.prepare("SELECT COUNT(*) as n FROM ground_truths WHERE is_current = 1").get().n;n.push(["Memories",String(s)],["Ground Truths",String(i)]),console.log(P(["Metric","Value"],n))}r.close()}async function ct(l){const{flags:a}=C(l),e=a.project,r=_(),o=r.getDatabase();let t=null;if(e){const c=x(r,e);c||(m(`Project not found: ${e}`),r.close(),process.exit(1)),t=c.id}const n=t?`AND project_id = ${t}`:"",s=o.prepare(`
|
|
62
62
|
SELECT id, kind, problem FROM memory_artifacts
|
|
63
|
-
WHERE needs_review = 1 ${
|
|
63
|
+
WHERE needs_review = 1 ${n}
|
|
64
64
|
ORDER BY created_at ASC
|
|
65
|
-
`).all();if(s.length===0){console.log(d.dim(` No artifacts pending review${e?` for ${e}`:""}.`)),
|
|
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}`))}
|
|
67
|
-
Review complete.`)}async function
|
|
68
|
-
Total: ${g.length} candidate(s)`),!
|
|
69
|
-
This is a dry run. Use --no-dry-run to delete (confirm each ID).`)),
|
|
70
|
-
Delete these ${g.length} artifact(s)? Type CONFIRM to proceed: `,$)});if(y.close(),w.trim()!=="CONFIRM"){console.log(d.dim("Aborted.")),
|
|
65
|
+
`).all();if(s.length===0){console.log(d.dim(` No artifacts pending review${e?` for ${e}`:""}.`)),r.close();return}S(`Review Queue (${s.length} items)`);const i=Q({input:process.stdin,output:process.stdout}),p=c=>new Promise(f=>{i.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}`))}i.close(),r.close(),console.log(`
|
|
67
|
+
Review complete.`)}async function at(l){const{positional:a,flags:e}=C(l),r=a[0];(!r||!["export","import","preview"].includes(r))&&(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:n,createDecipheriv:s}=await import("crypto"),{readFileSync:i,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 D=Q({input:process.stdin,output:process.stdout});$=await new Promise(I=>{process.stdout.write("Passphrase: "),process.stdin.isTTY&&process.stdin.setRawMode?.(!0),D.question("",U=>{process.stdin.isTTY&&process.stdin.setRawMode?.(!1),console.log(""),D.close(),I(U)})})}$||(m("Passphrase is required. Set WYRM_SYNC_PASSPHRASE or enter interactively."),process.exit(1));const R=y(h(),".wyrm"),j=_();if(r==="export"){const D=e.out;D||(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=i(I),F=o(32),J=o(16),Re=t($,F,6e5,32,"sha256"),se=n("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(D,de);try{u(D,384)}catch{}try{f(I)}catch{}const Pe=(de.length/(1024*1024)).toFixed(2);b(`Exported to ${D} (${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=i(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(r==="preview"){const D=y(R,"wyrm_cli_preview_temp.db");g(D)&&f(D),p(D,ee);try{const I=new w(D,{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(D)&&f(D)}catch{}j.close();return}const V=j.getDatabasePath(),ae=Q({input:process.stdin,output:process.stdout}),_e=await new Promise(D=>{ae.question("This will REPLACE your current database. Type CONFIRM to proceed: ",D)});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 D of["-wal","-shm"])try{g(V+D)&&f(V+D)}catch{}try{f(oe)}catch{}b(`Restored from ${E}. Backup at ${te}`)}async function lt(l){const{flags:a}=C(l),e=a.project,r=a.path,o=Z(a["min-confidence"],.3),t=T(a["older-than"],90),n=a["no-dry-run"]===!0,s=a.yes===!0,i=_(),p=i.getDatabase();let c=null;if(r||e){const y=r?i.getProject(r):x(i,e);y||(m(`Project not found: ${r??e}`),i.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${n?" (LIVE DELETE)":" (dry-run)"}`),g.length===0){console.log(d.dim(" No artifacts match prune criteria.")),i.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)`),!n){console.log(d.dim(`
|
|
69
|
+
This is a dry run. Use --no-dry-run to delete (confirm each ID).`)),i.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.")),i.close();return}}const h=f.deleteArtifacts(g.map(y=>y.id));b(`Deleted ${h} artifact(s).`),i.close()}async function dt(l){const{positional:a,flags:e}=C(l),r=a[0]||"list",o=_();try{if(r==="list"||r==="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 n=t.map(s=>[s.name,s.source,s.enabled?"on":"off",s.workspace,yt(s.baseUrl),s.allowlist.length?`${s.allowlist.length} chats`:"all"]);console.log(P(["Name","Source","Enabled","Workspace","Bridge","Allowlist"],n));return}if(r==="add"){const t=O(e.name),n=O(e.url),s=O(e.workspace);if(!t||!n||!s){m("usage: wyrm connector add --name <n> --url <bridge-url> --workspace <w> [--allowlist a,b] [--token T] [--disabled]"),process.exitCode=1;return}const i={name:t,source:"bridge",enabled:!e.disabled,baseUrl:n,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,i),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(r==="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 i=await Ye(o,s);b(`${i.source}: fetched ${i.fetched}, ingested ${i.ingested}, skipped ${i.skipped} -> ${i.workspace}`);return}const n=await Fe(o);if(n.length===0){console.log(`${N.dim} no enabled connectors.${N.reset}`);return}for(const s of n)"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: ${r} (use list | add | sync)`),process.exitCode=1}finally{o.close()}}function we(){return B(0,"utf-8").trim()}async function pt(){const{initializeLicense:l,getLicenseInfo:a,getTier:e}=await import("./license.js");l();const r=a();S("Wyrm License");const o=[["Tier",e()],["Status",r.valid?"valid":"free tier (no license key)"]];r.valid&&r.key&&(o.push(["Key",r.key]),o.push(["Issued to",r.issuedTo??"unknown"]),o.push(["Expires",r.expiresAt?new Date(r.expiresAt).toLocaleDateString():"never"])),o.push(["Features",r.features.join(", ")||"(free)"]),console.log(P(["Field","Value"],o)),r.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
|
|
73
|
-
${d.cyan("Sign in to activate Wyrm (free):")}`),console.log(` 1. Open ${d.cyan(
|
|
74
|
-
Waiting for approval\u2026 (Ctrl-C to cancel)`));const o=(e.interval??3)*1e3,t=Date.now()+(e.expires_in??600)*1e3;let
|
|
75
|
-
Database size: ${p.dbSize}`))}finally{
|
|
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 ut(){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 r=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(r)}`),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 n="";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){n=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(!n){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 ${n}`,"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:i}=await import("./license.js"),p=i(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 mt(l){const{positional:a}=C(l),e=a[0];let r;e&&H(e)?r=B(e,"utf-8"):e?r=e:process.stdin.isTTY?(m("Usage: wyrm activate <license.json path | license JSON> (or pipe the JSON on stdin)"),process.exit(1)):r=we();const{activateLicense:o}=await import("./license.js");try{const t=o(r);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 ft(l){const{flags:a}=C(l),{runMaintenance:e}=await import("./maintenance.js"),{FailurePatterns:r}=await import("./failure-patterns.js"),{SessionSeen:o}=await import("./session-seen.js"),{AgentPresence:t}=await import("./presence.js"),n=_();try{const s=n.getDatabase(),i=T(a["archive-days"],0),p=e({db:n,sessionSeen:new o(s),failures:new r(s),presence:new t(s)},{vacuum:a.vacuum===!0,archiveDays:i>0?i:void 0});S("Maintenance complete");for(const c of p.lines)console.log(` - ${c}`);console.log(d.dim(`
|
|
75
|
+
Database size: ${p.dbSize}`))}finally{n.close()}}async function gt(l){const{positional:a,flags:e}=C(l),r=a[0]??"status",{createVectorStore:o}=await import("./vectors.js"),n={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(r==="setup"){const{createProvider:i}=await import("./providers/embedding-provider.js"),p=i(n);if(!await p.isReady()&&n.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":n.provider} in the server env, then: wyrm index rebuild`));return}const s=_();try{const i=s.getDatabase(),p=o(n,i);if(r==="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(r==="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)??x(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(i,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 yt(l){try{return new URL(l).host}catch{return l}}async function ht(l){const{flags:a}=C(l),{getUpdateStatus:e}=await import("./version-check.js"),r=M().version??"0.0.0",o=_();let t;try{t=await e(o.getDatabase(),r,{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
|
|
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
|
|
78
|
+
`));const n=z("npm",["install","-g","wyrm-mcp@latest"],{stdio:"inherit",shell:!1});n.status===0?b("Updated. Restart your MCP clients to pick up the new binary."):(m(`npm install exited with ${n.status??"unknown"}`),process.exitCode=n.status??1)}async function wt(l){const{positional:a,flags:e}=C(l),r=a[0],o=e.project??process.cwd();if(r==="inject"){const{injectSystemPrompt:t}=await import("./autoconfig.js"),n=typeof e.clients=="string"?e.clients.split(",").map(i=>i.trim()).filter(Boolean):[],s=t(o,n);S("System prompt injection");for(const i of s.injected)console.log(` + ${i}`);for(const i of s.skipped)console.log(d.dim(` o skipped (unknown client): ${i}`));for(const i of s.errors)m(i);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(r==="migrate"){const{migrateProject:t,renderMigrationReport:n}=await import("./migrate-prompt.js"),{WYRM_INJECT_BLOCK:s}=await import("./autoconfig.js"),i=e.apply===!0,p=t({projectPath:o,newBlock:s,apply:i});console.log(n(p,i));return}m("Usage: wyrm prompt inject [--project <path>] [--clients copilot,cursor] | wyrm prompt migrate [--project <path>] [--apply]"),process.exitCode=1}async function bt(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:n}=await import("./hours.js"),s=_();try{const i=e.project,p=i?x(s,i):null;if(i&&!p){m(`Project not found: ${i}`),process.exitCode=1;return}const c=new n(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 $t(l){const{positional:a,flags:e}=C(l),r=a[0]??"generate",o=e.client,t=Z(e.rate,NaN),n=e.from,s=e.to;(r!=="generate"||!o||!Number.isFinite(t)||!n||!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:i}=await import("./hours.js"),p=_();try{const c=e.project,f=c?x(p,c):null;if(c&&!f){m(`Project not found: ${c}`),process.exitCode=1;return}const g=new i(p.getDatabase()).invoice({client_name:o,hourly_rate_usd:t,range_start:n,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
|
|
82
|
-
=== Recent log ===`),console.log(
|
|
81
|
+
`)}finally{p.close()}}async function vt(l){const{positional:a,flags:e}=C(l),r=a[0]??"status",{AgentDaemon:o}=await import("./agent-daemon.js"),t=_();try{const n=new o(t.getDatabase());switch(r){case"init":case"start":{const s=Math.max(10,Math.min(T(e.interval,600),86400)),i=n.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(!i.ok){m(`Agent init failed: ${i.error}`),process.exitCode=1;return}const p=i.status;b(`Agent ${p.pid!=null&&p.pid!==i.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=n.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(n.recentLog(40)));return}case"stop":{const s=await n.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 n.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 kt(l){if(l.includes("--encrypt")){const{initializeLicense:r,hasFeature:o}=await import("./license.js");if(r(),!o("encryption")){m("Encryption setup requires a Pro license or higher. See: wyrm license"),process.exitCode=1;return}const{getCrypto:t,initializeCrypto:n}=await import("./crypto.js"),s=l.includes("--enable"),i=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}n(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(i){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 jt(l){const a="https://github.com/Ghosts-Protocol-Pvt-Ltd/wyrm-mcp",r=`- 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(),
|
|
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(),n={bug:`**What happened**
|
|
85
85
|
|
|
86
86
|
|
|
87
87
|
**What you expected**
|
|
@@ -92,22 +92,22 @@ Delete these ${g.length} artifact(s)? Type CONFIRM to proceed: `,$)});if(y.close
|
|
|
92
92
|
|
|
93
93
|
---
|
|
94
94
|
_environment_
|
|
95
|
-
${
|
|
95
|
+
${r}`,idea:`**The idea**
|
|
96
96
|
|
|
97
97
|
|
|
98
98
|
**Why it would help**
|
|
99
99
|
|
|
100
100
|
|
|
101
101
|
---
|
|
102
|
-
${
|
|
102
|
+
${r}`,question:`**Your question**
|
|
103
103
|
|
|
104
104
|
|
|
105
105
|
---
|
|
106
|
-
${
|
|
106
|
+
${r}`,feedback:`**Your feedback** (what's working, what's rough)
|
|
107
107
|
|
|
108
108
|
|
|
109
109
|
---
|
|
110
|
-
${
|
|
110
|
+
${r}`},s={bug:"bug",idea:"enhancement",question:"question",feedback:"feedback"};let i;o==="question"?i=`${a}/discussions/new?category=q-a`+(t?`&title=${encodeURIComponent(t)}`:"")+`&body=${encodeURIComponent(n.question)}`:i=`${a}/issues/new?labels=${encodeURIComponent(s[o])}&title=${encodeURIComponent(`[${o}] ${t}`.trim())}&body=${encodeURIComponent(n[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(i)),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",[i],{stdio:"ignore",detached:!0}):c==="win32"?p("cmd",["/c","start","",i],{stdio:"ignore",detached:!0}):p("xdg-open",[i],{stdio:"ignore",detached:!0});f.on("error",()=>{}),f.unref()}catch{}}function be(){console.log(`
|
|
111
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
|
|
|
@@ -123,6 +123,7 @@ ${d.bold("Commands:")}
|
|
|
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
125
|
${d.cyan("bridge")} <init|status|run> The substrate flow: remember/render/reverse lanes from one receipted config
|
|
126
|
+
${d.cyan("metabolize")} [--dry-run] Memory metabolism: supersede exact dupes, queue near-dup candidates, decay stale auto-captures
|
|
126
127
|
${d.cyan("render")} [--client <list>] [--brief] Compile DB \u2192 MEMORY.md + topic files (zero-MCP-token memory)
|
|
127
128
|
${d.cyan("import git")} Import git log history to review queue
|
|
128
129
|
${d.cyan("import rules")} <path> Import .cursorrules / copilot-instructions file
|
|
@@ -181,15 +182,15 @@ ${d.bold("Examples:")}
|
|
|
181
182
|
wyrm sync export --out ~/wyrm-backup.wyrm
|
|
182
183
|
wyrm sync preview --from ~/wyrm-backup.wyrm
|
|
183
184
|
wyrm prune --project MyApp --min-confidence 0.2 --older-than 30
|
|
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
|
|
185
|
-
`).find(g=>g.trim())??"").trim();else{const c=
|
|
185
|
+
`)}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 St(l){const{positional:a,flags:e}=C(l),r=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(),n=o.getProject(t)??o.getProjectByName(t);if(!n){m(`Project not found: ${t}`),process.exitCode=1;return}if(r==="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:n.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 ${n.name}`);return}if(r==="since"){const s=T(e.cursor,0),i=T(e.limit,50),p=o.eventsSince(n.id,s,i);for(const c of p)console.log($e(c));S(`${p.length} event(s) for '${n.name}' since cursor ${s}`);return}m("usage: wyrm events <publish|since> ..."),process.exitCode=1}finally{o.close()}}async function _t(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 r=(typeof a.project=="string"?a.project:"")||process.cwd(),o=e.getProject(r)??x(e,r);if(!o){m(`Project not found: ${r}`),e.close(),process.exitCode=1;return}const t=Math.max(250,T(a.interval,1e3));let n=T(a.since,e.subscribeEvents(o.id,1).cursor);S(`Watching '${o.name}' (cursor ${n}, every ${t}ms) \u2014 Ctrl-C to stop`);const s=()=>{try{for(const c of e.eventsSince(o.id,n,200))n=c.cursor,console.log($e(c))}catch{}};s();const i=setInterval(s,t),p=()=>{clearInterval(i);try{e.close()}catch{}process.exit(0)};process.on("SIGINT",p),process.on("SIGTERM",p)}async function Ct(l){const{flags:a}=C(l),{embedAll:e,removeAll:r,statusAll:o}=await import("./priority-embed.js"),t={projectDir:typeof a.project=="string"?a.project:void 0,allClients:a.all===!0},n=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(n);return}if(a.remove){S("Wyrm priority embedding \u2014 removed"),r(t).forEach(n);return}S("Wyrm is now FIRST-PRIORITY memory"),e(t).forEach(n);try{const{installClaudeCodeHooks:s,installClaudeStatusline:i}=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=i();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 Rt(l){const{flags:a}=C(l),{harvestProjects:e}=await import("./harvest.js"),{MemoryArtifacts:r}=await import("./memory-artifacts.js"),{escapeLikePattern:o}=await import("./auto-capture.js"),t=_();try{const n=t.getDatabase(),s=new r(n),i={existsBySig:(w,$)=>!!n.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(i,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(n);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 Et(l){const a=await import("./vault.js"),[e,...r]=l;try{switch(e){case"set":{const o=r[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 n=(await import("node:fs")).readFileSync(0,"utf8").replace(/\r?\n$/,"");if(!n){m("empty secret on stdin"),process.exitCode=1;return}a.vaultSet(o,n),b(`Stored "${o}" (AES-256-GCM). Use it without exposing it: wyrm vault exec ${o} -- <command>`);break}case"get":{const o=r[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=r[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=r[0],t=r.indexOf("--");if(!o||t===-1||t+1>=r.length){m("usage: wyrm vault exec <name> [--as ENVVAR] -- <command...>"),process.exitCode=1;return}const n=r.slice(1,t),s=n.indexOf("--as"),i=s>=0?n[s+1]:o.toUpperCase().replace(/[^A-Z0-9]+/g,"_"),p=r.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,[i]: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 i=o.readFileSync(s,"utf8").match(/\/\/registry\.npmjs\.org\/:_authToken=(.+)/);if(!i){m("no npm authToken found in ~/.npmrc"),process.exitCode=1;return}a.vaultSet("npm-token",i[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 n=a.vaultSecure({backend:t});b(`Vault secured: ${n.from} \u2192 ${n.to}${n.rotated?" (key rotated)":""}.`),n.keyfileShredded&&console.log(" plaintext vault.key: shredded \u2714"),n.backup&&console.log(` ciphertext backup: ${n.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=r.indexOf("--backend"),t=o>=0?r[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 n=!r.includes("--no-rotate"),s=a.vaultSecure({backend:t,rotate:n});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 xt(){const l=ne(re(import.meta.url)),e=[q(l,"..","skills"),q(l,"..","..","skills")].find(o=>H(o));if(!e)return[];const r=[];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 n=o.name,s="";try{const i=B(t,"utf8"),p=i.match(/^name:\s*(.+)$/m);if(p&&(n=p[1].trim()),/^description:\s*[|>]/m.test(i))s=((i.split(/^description:.*$/m)[1]??"").split(`
|
|
186
|
+
`).find(g=>g.trim())??"").trim();else{const c=i.match(/^description:\s*(.+)$/m);c&&(s=c[1].trim())}}catch{}r.push({name:n,description:s})}return r.sort((o,t)=>o.name.localeCompare(t.name))}async function Dt(l){const a=l[0];if(!a||a==="help"||a==="--help"){console.log(`Usage:
|
|
186
187
|
wyrm skill list show the bundled guides + your registered skills
|
|
187
188
|
wyrm skill backfill-content read every skill's SKILL.md into the registry (idempotent)
|
|
188
189
|
wyrm skill export <targetDir> [--all] materialize SKILL.md files from stored content
|
|
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
|
|
190
|
+
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 r=xt();if(S("Bundled skill guides (shipped in the box)"),r.length===0)console.log(d.yellow(" none found next to this install"));else{for(const t of r)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 n of t.slice(0,40))console.log(` ${d.cyan(n.name)}${n.tier?d.dim(" ["+n.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 r=e.backfillSkillContent();b(`${r.filled} filled . ${r.unchanged} unchanged . ${r.missing} missing-file (of ${r.total} registered)`),r.missing>0&&console.log(d.yellow(` ${r.missing} skill(s) had no readable SKILL.md \u2014 re-run after restoring their files.`));return}if(a==="export"){const r=l[1];r||(m("Usage: wyrm skill export <targetDir> [--all]"),e.close(),process.exit(1));const o=l.includes("--all");S(`Export skills \u2192 ${r}`);const t=e.exportSkillContent(r,{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 r=l.includes("--private")?"within":l.includes("--public")?"public":"org",o=l.includes("--include-inactive"),t=l.indexOf("--tier"),n=t>=0?l[t+1]:void 0;if(t>=0&&(!n||n.startsWith("--"))&&(m("Usage: wyrm skill share --tier <god|mega|atomic> [--public|--private] [--include-inactive]"),e.close(),process.exit(1)),l.includes("--all")||!!n){const c=e.setAllSkillsVisibility(r,{tier:n,includeInactive:o}),f=n?`tier '${n}'`:o?"all skills":"all active skills";b(`${c} skill(s) (${f}) visibility \u2192 '${r}'.`),console.log(r==="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 i=l[1];i||(m("Usage: wyrm skill share <name|--all|--tier <tier>> [--public|--private] [--include-inactive]"),e.close(),process.exit(1)),e.setSkillVisibility(i,r)||(m(`Skill not found: ${i}`),e.close(),process.exit(1)),b(`Skill "${i}" visibility \u2192 '${r}'.`),console.log(r==="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 Pt(l){const a=l[0]??"status",e=_(),r=e.getDatabase();if(r.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=r.prepare("SELECT id, name, sync_policy FROM projects ORDER BY id").all(),n=["ground_truths","memory_artifacts","quests","design_tokens","design_references"],s=["ground_truths","memory_artifacts","quests","sessions","decision_edges"],i=(f,g,u)=>{let h=0;for(const y of f)try{h+=r.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=i(n,f.id,"cross_project_visibility IN ('org','public')"),u=i(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(`
|
|
190
191
|
private = never replicates . cloud = your own cloud backup . team = federates to a team Wyrm`),c>0&&console.log(d.red(`
|
|
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],
|
|
192
|
+
! ${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],n=l[2];(!t||!["private","cloud","team"].includes(n))&&(m("Usage: wyrm grove policy <project|id> <private|cloud|team>"),e.close(),process.exit(1));let s=x(e,t);!s&&/^\d+$/.test(t)&&(s=r.prepare("SELECT id, name FROM projects WHERE id = ?").get(Number(t))),s||(m(`Grove not found: ${t}`),e.close(),process.exit(1)),r.prepare("UPDATE projects SET sync_policy = ? WHERE id = ?").run(n,s.id),b(`Grove "${s.name}" set to '${n}'.`),n!=="private"&&console.log(` Rows still only leave when also marked ${n==="team"?"is_shared (team)":"org/public (cloud)"}. The grove is the outer gate.`),e.close();return}m(`Unknown grove subcommand: ${a}`),console.log(`Usage:
|
|
192
193
|
wyrm grove status
|
|
193
|
-
wyrm grove policy <project|id> <private|cloud|team>`),e.close(),process.exit(1)}async function
|
|
194
|
+
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:r}=await import("./ulid.js"),{createRun:o,setRunStatus:t,registerAgent:n,getRun:s,getAgents:i}=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=r();o(g,w,y,u),n(g,w,h,"orchestrator"),process.stdout.write(w+`
|
|
194
195
|
`),process.stderr.write(`Run ${w} started by ${u}.
|
|
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;
|
|
196
|
+
`);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;n(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=i(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 et(v);break;case"presence":await tt(v);break;case"digest":await ze(v);break;case"bridge":await Xe(v);break;case"metabolize":await Ze(v);break;case"render":await ot(v);break;case"reverse-bridge":await st(v);break;case"import":await nt(v);break;case"stats":await it(v);break;case"review":await ct(v);break;case"sync":await at(v);break;case"cloud":{const{cmdCloud:l}=await import("./cloud/cli.js");await l(v);break}case"grove":await Pt(v);break;case"run":await Tt(v);break;case"skill":await Dt(v);break;case"prune":await lt(v);break;case"license":await pt();break;case"login":await ut();break;case"activate":await mt(v);break;case"maintenance":await ft(v);break;case"index":await gt(v);break;case"update":await ht(v);break;case"prompt":await wt(v);break;case"hours":await bt(v);break;case"invoice":await $t(v);break;case"agent":await vt(v);break;case"feedback":await jt(v);break;case"setup":await kt(v);break;case"intro":{const{renderIntro:l}=await import("./visibility.js");console.log(l(M().version??"unknown"));break}case"events":await St(v);break;case"watch":await _t(v);break;case"embed":await Ct(v);break;case"harvest":await Rt(v);break;case"vault":await Et(v);break;case"connector":case"connectors":await dt(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 r=v.includes("--remove")||v.includes("--uninstall")?a():l();r?r.action==="failed"?(m(r.message),process.exitCode=1):b(r.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),r=process.env.WYRM_BIND_HOST||"127.0.0.1";a.listen(e,r,()=>{if(b(`Wyrm HTTP server running on ${r}:${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 n=process.platform;try{const s=n==="darwin"?t("open",[o],{stdio:"ignore",detached:!0}):n==="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)}})();
|
package/dist/wyrm-manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wyrm-mcp",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.11.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",
|