wyrm-mcp 8.2.0 → 8.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/capabilities.js +3 -3
- package/dist/cloud/sync-daemon.js +1 -1
- package/dist/handlers/companion.js +1 -1
- package/dist/handlers/recall.js +4 -4
- package/dist/handlers/resources.js +1 -1
- package/dist/handlers/session.js +17 -14
- package/dist/providers/embedding-provider.js +1 -1
- package/dist/receipt.js +1 -1
- package/dist/setup-wizard.js +5 -0
- package/dist/setup.js +3 -3
- package/dist/tool-manifest-v2.json +1 -1
- package/dist/tool-manifest.json +1 -1
- package/dist/vector-init.js +1 -1
- package/dist/wyrm-cli.js +120 -116
- package/dist/wyrm-manifest.json +1 -1
- package/package.json +1 -1
- package/scripts/hooks/wyrm-session-capture.mjs +44 -4
- package/scripts/hooks/wyrm-session-rehydrate.mjs +54 -1
package/dist/capabilities.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{homedir as
|
|
1
|
+
import{homedir as l}from"os";import{join as a,dirname as c}from"path";import{readFileSync as m,existsSync as y}from"fs";import{resolveEmbeddingState as h}from"./providers/embedding-provider.js";import{fileURLToPath as d}from"url";const u=d(import.meta.url),n=c(u),w=`Wyrm is not "AI memory" in the file-write sense. It's a layered intelligence
|
|
2
2
|
substrate that solves the four specific problems AI assistants share:
|
|
3
3
|
|
|
4
4
|
1. Amnesia. Sessions inherit prior state via lossless rehydration.
|
|
@@ -11,5 +11,5 @@ substrate that solves the four specific problems AI assistants share:
|
|
|
11
11
|
Every Wyrm session is local-first SQLite with FTS5 + hybrid semantic search.
|
|
12
12
|
Encryption is operator-controlled. Federation is opt-in per row. The audit
|
|
13
13
|
log is hash-chained and Ed25519-signable for SOC2/HIPAA scenarios. None of
|
|
14
|
-
this requires you to leave your laptop.`,
|
|
15
|
-
`)}export{
|
|
14
|
+
this requires you to leave your laptop.`,p=[{name:"Counter-pattern memory",what:"Records failed approaches with (scope, target, description) and blocks the same suggestion next time.",why:"Other memory tools learn only from success. Wyrm refuses to repeat mistakes \u2014 saves the most expensive class of tokens (the regenerated wrong answer).",tools:["wyrm_failure_record","wyrm_failure_check","wyrm_failure_list","wyrm_failure_resolve"]},{name:"Ground truths (versioned, cascade-aware)",what:"Validated project facts (architecture, constraints, decisions) \u2014 each truth carries a version, last-verified timestamp, and outbound decision links.",why:"Truths are injected first into every wyrm_context_build call so the AI never works from contradicted assumptions. When a truth changes, dependent decisions auto-invalidate.",tools:["wyrm_truth_set","wyrm_truth_get","wyrm_decided_because","wyrm_decision_upstream","wyrm_decision_downstream","wyrm_decision_invalidate"]},{name:"Reasoning scaffolds",what:'Saved step-by-step checklists for problem types ("debug a slow query", "ship a migration") that surface automatically when a task description matches.',why:"Replaces ad-hoc rediscovery of process knowledge with structured prompts. Cheaper than re-explaining the playbook every session.",tools:["wyrm_scaffold_save","wyrm_scaffold_get"]},{name:"Lossless session rehydration",what:"wyrm_session_rehydrate produces a complete briefing markdown a fresh AI ingests to inherit prior state \u2014 decisions, open quests, failures, recent commits, files touched.",why:"Cross-session continuity without re-exploration. The single biggest token-saver in the toolkit.",tools:["wyrm_session_rehydrate","wyrm_session_prime","wyrm_session_start","wyrm_session_update"]},{name:"Distillation queue",what:"Candidate memory artifacts extracted from sessions land in a review queue; the operator approves or rejects each.",why:"Bayesian confidence updates after each review \u2014 Wyrm gets sharper with use without auto-trusting noise.",tools:["wyrm_distill","wyrm_review","wyrm_remember","wyrm_recall","wyrm_feedback"]},{name:"Knowledge graph",what:"Named entities + typed directional relationships, neighborhood traversal, shortest-path queries.",why:'When the AI asks "how does X relate to Y?", Wyrm answers in O(log n) instead of grepping the corpus.',tools:["wyrm_entity_add","wyrm_entity_link","wyrm_entity_search","wyrm_entity_graph","wyrm_entity_path","wyrm_entity_merge"]},{name:"Cross-repo symbol index",what:"Workspace-wide function/class/type index across TS/JS/Python/Rust/Go/PHP/Ruby with caller resolution.",why:`Symbol lookups don't require re-grepping every project. "Who calls handleAuth?" answers immediately.`,tools:["wyrm_symbol_index","wyrm_symbol_search","wyrm_symbol_callers","wyrm_symbol_stats"]},{name:"OODA agent loop (autonomous goals)",what:"wyrm_goal_set persists an objective with success criteria. The wyrm-loop daemon runs Observe\u2192Orient\u2192Decide\u2192Act every interval, dispatching whitelisted tools. Replayable, capped, auditable.",why:"Lets you hand the AI a multi-step objective and walk away. No data-exfil tools accessible from inside the loop.",tools:["wyrm_goal_set","wyrm_goal_list","wyrm_goal_pause","wyrm_goal_resume","wyrm_goal_complete","wyrm_goal_abandon","wyrm_goal_iterations","wyrm_act","wyrm_agent_init","wyrm_agent_status","wyrm_agent_stop","wyrm_agent_restart"]},{name:"Outbound MCP client",what:"Wyrm calls other MCP servers (GitHub, Slack, Linear, \u2026) over stdio. The agent loop dispatches them like internal tools.",why:"Composability: Wyrm becomes the orchestration layer over your entire MCP stack instead of a sibling.",tools:["wyrm_mcp_register","wyrm_mcp_list","wyrm_mcp_disable","wyrm_mcp_tools","wyrm_call_external"]},{name:"Hybrid semantic search",what:"wyrm_search fuses FTS5 keyword candidates with vector candidates (Ollama nomic-embed-text by default; OpenAI optional) via Reciprocal Rank Fusion.",why:`Catches both "exact term" and "semantically similar" hits in one call. Silently falls back to FTS5-only if Ollama isn't running.`,tools:["wyrm_search","wyrm_vector_setup"]},{name:"Multi-agent presence + work-stealing",what:"Claude, Copilot, Cursor, and Codex announce themselves via TTL heartbeats. Quests can be claimed exclusively.",why:"No two agents step on the same quest. The work-stealing semantics scale to teams.",tools:["wyrm_presence_announce","wyrm_presence_list","wyrm_presence_release","wyrm_quest_claim","wyrm_quest_release"]},{name:"Federated team sync",what:"Per-row is_shared flag, /sync/push endpoint, conflict-as-quest semantics. PII never leaves the box unless explicitly shared.",why:"Team memory without a SaaS. No silent overwrites \u2014 every conflict becomes a quest the operator resolves.",tools:["wyrm_share","wyrm_unshare","wyrm_sync","wyrm_sync_export","wyrm_sync_import","wyrm_sync_conflicts","wyrm_sync_resolve"]},{name:"Compliance audit trail",what:"Hash-chained audit log with optional Ed25519 signatures. wyrm_audit_export produces tamper-evident bundles.",why:"SOC2/HIPAA reviewers get a real artifact. The hash chain detects any tampering after the fact.",tools:["wyrm_audit_log","wyrm_audit_export","wyrm_audit_verify"]},{name:"Hour ledger + invoice generation",what:"Derives billable hours from session content and emits markdown invoices.",why:"For solo operators and small teams: Wyrm is also the time tracker. No second tool to keep in sync.",tools:["wyrm_hours_report","wyrm_invoice_generate","wyrm_cost_report"]},{name:"Self-improvement analytics",what:"Per-tool error rate, p50/p95 latency, cost-per-tool. Wyrm watches Wyrm.",why:"Lets you spot which tools are hot, which are slow, which fail \u2014 input for both ops tuning and product decisions.",tools:["wyrm_analytics_dashboard","wyrm_tool_analytics","wyrm_usage","wyrm_stats"]}];function _(){try{const t=[a(n,"..","package.json"),a(n,"..","..","package.json")];for(const e of t)if(y(e))return JSON.parse(m(e,"utf-8")).version??"unknown"}catch{}return"unknown"}function x(t,e,s){const o=process.env.WYRM_DB_PATH??a(l(),".wyrm","wyrm.db"),i=s.isAgentRunning(),r=h();return{product:"Wyrm",version:_(),pitch:w,install:{dbPath:o,nodeVersion:process.version,platform:`${process.platform}-${process.arch}`},runtime:{vectorProvider:s.detectVectorProvider?s.detectVectorProvider():r.resolved,vectorReason:r.reason,vectorFix:r.fix,encryption:s.hasEncryption(),federation:s.isFederationEnabled(),agentLoop:i===null?"unknown":i?"running":"stopped"},features:p,toolCount:e}}function S(t){const e=[];e.push(`# ${t.product} ${t.version} \u2014 Capability Briefing`),e.push(""),e.push(t.pitch),e.push("");const s=t.runtime.vectorProvider==="none"?"vectors OFF (FTS-only recall)":`${t.runtime.vectorProvider} embeddings`;e.push(`**Runtime:** ${t.toolCount} MCP tools \xB7 ${s} \xB7 ${t.runtime.encryption?"encryption ON":"encryption off"} \xB7 agent ${t.runtime.agentLoop}`),t.runtime.vectorReason&&(e.push(""),t.runtime.vectorProvider==="none"?e.push(`> WARNING: semantic recall degraded (${t.runtime.vectorReason}) - recall is FTS5 keyword-only. Fix: ${t.runtime.vectorFix}`):e.push(`> NOTE: embedding provider is advisory (${t.runtime.vectorReason}) - semantic recall is running. Fix: ${t.runtime.vectorFix}`)),e.push(`**Install:** ${t.install.platform} \xB7 Node ${t.install.nodeVersion} \xB7 DB ${t.install.dbPath}`),e.push(""),e.push('## What Wyrm gives you beyond "AI memory"'),e.push("");for(const o of t.features)e.push(`### ${o.name}`),e.push(`- **What:** ${o.what}`),e.push(`- **Why:** ${o.why}`),e.push(`- **Tools:** \`${o.tools.join("`, `")}\``),e.push("");return e.join(`
|
|
15
|
+
`)}export{x as getCapabilities,S as renderCapabilityBriefing};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{spawn as
|
|
1
|
+
import{spawn as g}from"child_process";import{existsSync as o,readFileSync as d,writeFileSync as p,mkdirSync as E,openSync as y,closeSync as S,unlinkSync as m,statSync as _,renameSync as v}from"fs";import{homedir as k}from"os";import{join as a}from"path";const n=a(k(),".wyrm"),c=a(n,"wyrm-sovereign-sync.pid"),t=a(n,"wyrm-sovereign-sync.log"),u=a(n,"wyrm-sovereign-sync.state"),I=1e6,L=0,P=600*1e3;class x{daemonEntrypoint;constructor(e){this.daemonEntrypoint=e,h()}start(e={}){if(this.isAlive())return{ok:!1,pid:this.readPid(),reason:"already running"};N();const i=(e.interval_minutes??P/6e4)*6e4,f=y(t,"a"),l=y(t,"a"),s=g(process.execPath,[this.daemonEntrypoint,String(i)],{detached:!0,stdio:["ignore",f,l],env:{...process.env}});return s.unref(),S(f),S(l),typeof s.pid!="number"?{ok:!1,reason:"spawn returned no pid"}:(p(c,String(s.pid)),w({started_ts:Date.now()}),{ok:!0,pid:s.pid})}stop(){if(!this.isAlive())return this.cleanPid(),{ok:!1,reason:"not running"};const e=this.readPid();if(!e)return{ok:!1,reason:"pid file unreadable"};try{return process.kill(e,"SIGTERM"),{ok:!0}}catch(i){return{ok:!1,reason:i?.message??String(i)}}}status(){const e=this.isAlive();return{running:e,pid:e?this.readPid():void 0,state:o(u)?A():void 0}}isAlive(){if(!o(c))return!1;const e=this.readPid();if(!e)return!1;try{return process.kill(e,L),!0}catch{return!1}}readPid(){try{const e=Number(d(c,"utf8").trim());return Number.isFinite(e)&&e>0?e:void 0}catch{return}}cleanPid(){try{m(c)}catch{}}}function w(r){h();let e={};try{e=JSON.parse(d(u,"utf8"))}catch{}p(u,JSON.stringify({...e,...r},null,2))}function h(){o(n)||E(n,{recursive:!0})}function A(){try{return JSON.parse(d(u,"utf8"))}catch{return}}function N(){if(o(t))try{if(_(t).size>I){const r=`${t}.1`;try{m(r)}catch{}v(t,r)}}catch{}}export{P as DEFAULT_SOVEREIGN_INTERVAL_MS,x as SovereignSyncManager,w as writeSovereignState};
|
|
@@ -13,7 +13,7 @@ ${t.errors.map(r=>`- ${r}`).join(`
|
|
|
13
13
|
`),t.injected.length>0&&(e+="AI models in this project will now call `wyrm_session_prime` at the start of each conversation."),{content:[{type:"text",text:e}]}}},{name:"buddy",description:"Use when another buddy-compatible MCP server (or the operator) asks Wyrm for a brief, data-grounded status reply - the Buddy Protocol v1.0 well-known entry point. Companion params switch to the full companion reply; bare calls stay protocol-pure. Cycle-protected.",inputSchema:{type:"object",properties:{from_buddy:{type:"string",description:"Caller identity"},project_hint:{type:"string",description:"Scope the reply"},mood_hint:{type:"string",enum:["normal","celebratory","stuck","quiet"]},size:{type:"string",enum:["full","compact","mini"]},format:{type:"string",enum:["markdown","json"],description:"Peer mode only."},project_path:{type:"string",description:"Companion mode: project root (default cwd)"},persona:{type:"string",enum:["wyrm","drogo","berlin","sumair","custom"],description:"Companion voice."},persona_name:{type:"string",description:"For persona=custom"},mood:{type:"string",description:"Companion override (mood_hint values)"},federate:{type:"boolean",description:"Fold in other buddy MCPs"}}},annotations:l.buddy,aliases:[],handler:async(o,c)=>{const{WYRM_PACKAGE_VERSION:i,db:a,runCompanionBuddy:t}=c,e=o||{};if(b(o??{}))return await t(o??{});const r=o,s=g(a.getDatabase(),r,i);return r.format==="json"?{content:[{type:"text",text:JSON.stringify(s.json,null,2)}]}:{content:[{type:"text",text:s.markdown}]}}},{name:"wyrm_buddy",description:"Friendly, data-grounded coding companion. Returns a brief greeting + project state + suggested next step sourced entirely from Wyrm's real data. Every claim cites a real DB row ID \u2014 no hallucinated encouragement. Personas: wyrm (default, dragon-themed), drogo (gruff), berlin (precise), sumair (warm), custom. Moods: normal / celebratory / stuck / quiet \u2014 auto-detected. Sizes: full (10-line PhantomDragon-style mascot), compact (5-line workspace-friendly), mini (single-line inline prefix). Federates with other registered MCPs that expose `buddy`, `*_buddy`, or `buddy_*` tools \u2014 their replies fold into a 'From other buddies' section.",inputSchema:{type:"object",properties:{project_path:{type:"string",description:"Project root. Default: current cwd if registered."},mood:{type:"string",enum:["normal","celebratory","stuck","quiet"],description:"Override the auto-detected mood."},persona:{type:"string",enum:["wyrm","drogo","berlin","sumair","custom"],description:"Voice. Default: WYRM_BUDDY_DEFAULT_PERSONA env or 'wyrm'."},persona_name:{type:"string",description:"Required when persona === 'custom'."},size:{type:"string",enum:["full","compact","mini"],description:"Mascot size. 'full' = 10-line head+shoulder banner (default), 'compact' = 5-line workspace-friendly, 'mini' = single-line inline prefix on the greeting."},federate:{type:"boolean",description:"Query other registered MCPs with *_buddy tools and fold their replies in. Default: true if any are registered."}}},annotations:l.wyrm_buddy,aliases:[],handler:async(o,c)=>{const{runCompanionBuddy:i}=c;return await i(o??{})}},{name:"wyrm_token_set",description:"Set a design token for a project. Tokens are colour / type / spacing / motion / shadow / radius / breakpoint / custom \u2014 the design system primitives the project is built on. Stored first-class so AI helpers working on creative tasks don't have to re-derive the system from scattered CSS each time. Upserts on (project_id, category, key).",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Project root path."},category:{type:"string",enum:["color","type","spacing","motion","shadow","radius","breakpoint","custom"],description:"Token category."},key:{type:"string",description:"Short slug, e.g. 'primary' / 'h1' / 'page-padding-md'."},value:{type:"string",description:"Actual value: '#00B89F' / 'Inter 900 2.5rem' / '1.25rem' / etc."},notes:{type:"string",description:"Optional why / when-to-use guidance."},source:{type:"string",enum:["user","derived","imported","observed"],description:"How the token was determined. Default: 'user'."}},required:["projectPath","category","key","value"]},annotations:l.wyrm_token_set,aliases:[],handler:async(o,c)=>{const{db:i,designSystem:a}=c,{projectPath:t,category:e,key:r,value:s,notes:p,source:u}=o,n=i.getProject(t);if(!n)return{content:[{type:"text",text:`Project not found: ${t}`}],isError:!0};const d=a().setToken({projectId:n.id,category:e,key:r,value:s,notes:p,source:u});return{content:[{type:"text",text:`\u{1F3A8} token set: \`${d.category}/${d.key}\` = \`${d.value}\``+(d.notes?` \u2014 ${d.notes}`:"")}]}}},{name:"wyrm_token_get",description:"Retrieve all design tokens for a project, optionally filtered by category. Returns the design system primitives the project is built on.",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Project root path."},category:{type:"string",enum:["color","type","spacing","motion","shadow","radius","breakpoint","custom"],description:"Filter by category. Omit for all tokens."}},required:["projectPath"]},annotations:l.wyrm_token_get,aliases:[],handler:async(o,c)=>{const{db:i,designSystem:a}=c,{projectPath:t,category:e}=o,r=i.getProject(t);if(!r)return{content:[{type:"text",text:`Project not found: ${t}`}],isError:!0};const s=a().formatForContext(r.id);if(!s)return{content:[{type:"text",text:`_(no design tokens for ${r.name} yet \u2014 use \`wyrm_token_set\` to start the design system)_`}]};if(e){const p=a().listTokens(r.id,e);if(p.length===0)return{content:[{type:"text",text:`_(no ${e} tokens for ${r.name})_`}]};const u=[`### \u{1F3A8} ${e} tokens for ${r.name}`,""];for(const n of p)u.push(`- \`${n.key}\` = \`${n.value}\`${n.notes?` \u2014 ${n.notes}`:""}`);return{content:[{type:"text",text:u.join(`
|
|
14
14
|
`)}]}}return{content:[{type:"text",text:s}]}}},{name:"wyrm_token_delete",description:"Remove a design token from a project.",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Project root path."},category:{type:"string",enum:["color","type","spacing","motion","shadow","radius","breakpoint","custom"]},key:{type:"string",description:"Token key to remove."}},required:["projectPath","category","key"]},annotations:l.wyrm_token_delete,aliases:[],handler:async(o,c)=>{const{db:i,designSystem:a}=c,{projectPath:t,category:e,key:r}=o,s=i.getProject(t);return s?{content:[{type:"text",text:a().deleteToken(s.id,e,r)?`\u{1F5D1} removed \`${e}/${r}\``:`not found: \`${e}/${r}\``}]}:{content:[{type:"text",text:`Project not found: ${t}`}],isError:!0}}},{name:"wyrm_reference_add",description:"Clip a design reference (URL, image path, palette, or inline snippet) for inspiration. Stored per-project (or globally if no project path). Tag aggressively \u2014 tags drive future search and recall when building a new project in the same vein.",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Project root path. Omit to attach as a global reference."},kind:{type:"string",enum:["url","image","palette","snippet"],description:"Reference type."},location:{type:"string",description:"URL, file path, or inline payload (e.g. palette like '#00B89F,#ff5a55,#0a0c0e' or a code snippet)."},title:{type:"string",description:"Short title \u2014 what is this reference?"},notes:{type:"string",description:"Why you saved it / what to remember about it."},tags:{type:"string",description:"Comma-separated tags \u2014 'hero,scroll-tied,framer-motion'."}},required:["kind","location"]},annotations:l.wyrm_reference_add,aliases:[],handler:async(o,c)=>{const{db:i,designSystem:a}=c,{projectPath:t,kind:e,location:r,title:s,notes:p,tags:u}=o;let n=null;if(t){const m=i.getProject(t);if(!m)return{content:[{type:"text",text:`Project not found: ${t}`}],isError:!0};n=m.id}const d=a().addReference({projectId:n,kind:e,location:r,title:s,notes:p,tags:u}),y=n?` (project ${n})`:" (global)";return{content:[{type:"text",text:`\u{1F4CC} reference #${d.id} saved${y}: \`${d.kind}\` ${d.title??d.location.slice(0,60)}${d.tags?` \xB7 tags: ${d.tags}`:""}`}]}}},{name:"wyrm_reference_list",description:"Browse saved design references. Filter by project or tag. Default: 50 most recent across all projects.",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Limit to a specific project."},tag:{type:"string",description:"Filter to references containing this tag."},limit:{type:"number",description:"Max results (default 50, max 200)."}}},annotations:l.wyrm_reference_list,aliases:[],handler:async(o,c)=>{const{db:i,designSystem:a}=c,{projectPath:t,tag:e,limit:r}=o;let s;if(t){const n=i.getProject(t);if(!n)return{content:[{type:"text",text:`Project not found: ${t}`}],isError:!0};s=n.id}const p=a().listReferences({projectId:s,tag:e,limit:r});if(p.length===0)return{content:[{type:"text",text:"_(no references match)_"}]};const u=[`\u{1F4CC} **${p.length} reference${p.length===1?"":"s"}**`];for(const n of p){const d=n.project_id?`p${n.project_id}`:"global";u.push(`- [${n.id}/${d}] \`${n.kind}\` ${n.title??n.location.slice(0,60)}${n.tags?` \xB7 ${n.tags}`:""}`)}return{content:[{type:"text",text:u.join(`
|
|
15
15
|
`)}]}}},{name:"wyrm_reference_search",description:"Full-text search across design references' title / notes / tags. Returns matching references sorted by recency.",inputSchema:{type:"object",properties:{query:{type:"string",description:"FTS5 query \u2014 keywords, phrases in quotes."},projectPath:{type:"string",description:"Limit to a specific project."},limit:{type:"number",description:"Max results (default 20, max 100)."}},required:["query"]},annotations:l.wyrm_reference_search,aliases:[],handler:async(o,c)=>{const{db:i,designSystem:a}=c,{query:t,projectPath:e,limit:r}=o;let s;if(e){const n=i.getProject(e);if(!n)return{content:[{type:"text",text:`Project not found: ${e}`}],isError:!0};s=n.id}const p=a().searchReferences(t,{projectId:s,limit:r});if(p.length===0)return{content:[{type:"text",text:`_(no references match "${t}")_`}]};const u=[`\u{1F50D} **${p.length} reference${p.length===1?"":"s"}** matching "${t}"`];for(const n of p){const d=n.project_id?`p${n.project_id}`:"global";u.push(`- [${n.id}/${d}] \`${n.kind}\` ${n.title??n.location.slice(0,60)}${n.tags?` \xB7 ${n.tags}`:""}`)}return{content:[{type:"text",text:u.join(`
|
|
16
|
-
`)}]}}},{name:"wyrm_capabilities",description:"Use to learn what your memory system can actually do - feature inventory, why each matters, runtime state, and which tool to reach for. Call at session start instead of re-discovering the surface.",inputSchema:{type:"object",properties:{format:{type:"string",enum:["json","markdown"]}}},annotations:l.wyrm_capabilities,aliases:[],handler:async(o,c)=>{const{WYRM_TOOL_COUNT:i,agentDaemon:a,db:t}=c,{format:e="markdown"}=o,r=f(t.getDatabase(),i,{
|
|
16
|
+
`)}]}}},{name:"wyrm_capabilities",description:"Use to learn what your memory system can actually do - feature inventory, why each matters, runtime state, and which tool to reach for. Call at session start instead of re-discovering the surface.",inputSchema:{type:"object",properties:{format:{type:"string",enum:["json","markdown"]}}},annotations:l.wyrm_capabilities,aliases:[],handler:async(o,c)=>{const{WYRM_TOOL_COUNT:i,agentDaemon:a,db:t}=c,{format:e="markdown"}=o,r=f(t.getDatabase(),i,{hasEncryption:()=>!!process.env.WYRM_ENCRYPTION_KEY,isFederationEnabled:()=>!0,isAgentRunning:()=>{try{return a.status().running}catch{return null}}});return e==="json"?{content:[{type:"text",text:JSON.stringify(r,null,2)}]}:{content:[{type:"text",text:w(r)}]}}},{name:"wyrm_intro",description:"Return a plain-English explanation of what Wyrm is and what it does. Aimed at HUMANS who don't yet understand Wyrm's value \u2014 co-founders, operators, designers, anyone who didn't read the docs. Call this when a human user asks 'what does Wyrm even do?' or seems unaware Wyrm is involved in their AI assistant's behavior. NOT for AI-agent self-orientation (use wyrm_capabilities for that).",inputSchema:{type:"object",properties:{}},annotations:l.wyrm_intro,aliases:[],handler:async(o,c)=>{const{WYRM_PACKAGE_VERSION:i}=c,{renderIntro:a}=await import("../visibility.js");return{content:[{type:"text",text:a(i)}]}}},{name:"wyrm_migrate_prompt",description:"Rewrite the Wyrm-managed block in this project's AI-client instruction files (.cursor/rules, .github/copilot-instructions.md, CLAUDE.md, etc.) to the current canonical version. Operator-authored content outside the <!-- wyrm:start --> / <!-- wyrm:end --> markers is preserved verbatim. Dry-run by default; pass apply:true to write changes.",inputSchema:{type:"object",properties:{project_path:{type:"string",description:"Project root containing the client config files."},apply:{type:"boolean",description:"Default false (dry-run). Set true to actually write changes."}},required:["project_path"]},annotations:l.wyrm_migrate_prompt,aliases:[],handler:async(o,c)=>{const{migrateProject:i,renderMigrationReport:a}=await import("../migrate-prompt.js"),{WYRM_INJECT_BLOCK:t}=await import("../autoconfig.js"),{project_path:e,apply:r=!1}=o;if(!e)return{content:[{type:"text",text:"project_path required"}],isError:!0};const s=i({projectPath:e,newBlock:t,apply:r});return{content:[{type:"text",text:a(s,r)}]}}},{name:"wyrm_digest",description:"Render a plain-English summary of what Wyrm has done for the operator over a period. Shows counts (sessions tracked, quests completed, repeated failures blocked, ground truths set, references clipped, hours logged) and highlights so a non-technical operator can see Wyrm's actual contribution. Use this when the user asks 'what did Wyrm do?', 'is Wyrm doing anything?', or to demonstrate value to skeptics.",inputSchema:{type:"object",properties:{period:{type:"string",enum:["today","week","month","quarter","year","all"],description:"Reporting window (default: week)."}}},annotations:l.wyrm_digest,aliases:[],handler:async(o,c)=>{const{db:i}=c,{gatherDigest:a,renderDigest:t}=await import("../visibility.js"),{period:e="week"}=o,r=a(i.getDatabase(),e);return{content:[{type:"text",text:t(r)}]}}},{name:"wyrm_check_update",description:"Check whether a newer wyrm-mcp is available on npm. Cached 24h by default; pass force:true to bypass cache.",inputSchema:{type:"object",properties:{force:{type:"boolean",description:"Bypass the 24-hour cache and re-query the registry."}}},annotations:l.wyrm_check_update,aliases:[],handler:async(o,c)=>{const{WYRM_PACKAGE_VERSION:i,db:a}=c,{force:t=!1}=o,e=await j(a.getDatabase(),i,{force:t});let r=`\u{F115D} **Update check**
|
|
17
17
|
|
|
18
18
|
- Current: ${e.current}
|
|
19
19
|
- Latest: ${e.latest??"unknown (offline?)"}
|
package/dist/handlers/recall.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import{TOOL_ANNOTATIONS as L}from"../tool-annotations.js";import{renderResult as
|
|
1
|
+
import{TOOL_ANNOTATIONS as L}from"../tool-annotations.js";import{renderResult as T,withGlyph as v}from"../render.js";import{memoryUri as A,resourceLink as N}from"./resources.js";import{buildDeterminismReceipt as H,attestation as U}from"../receipt.js";import{rerankConfigured as W,rerankEgressHost as F}from"../rerank.js";const V=[{name:"wyrm_recall",description:"Use before starting any non-trivial task - what do we know about this? Pull up relevant lessons learned, proven patterns, anti-patterns, and reasoning traces from past work. Hybrid lexical+semantic retrieval, fully local, no LLM. Mark what you recalled as useful (or not) with wyrm_feedback.",inputSchema:{type:"object",properties:{projectPath:{type:"string"},query:{type:"string",description:"The task or problem"},kind:{type:"string",enum:["reasoning_trace","lesson","pattern","anti_pattern","heuristic"]},limit:{type:"number",description:"Default 10"},minConfidence:{type:"number",description:"0-1 (default 0)"},detail:{type:"string",enum:["full","link"],description:"Default full"}},required:["projectPath","query"]},outputSchema:{type:"object",properties:{query:{type:"string"},count:{type:"integer"},results:{type:"array",items:{type:"object",properties:{id:{type:"integer"},kind:{type:"string"},relevance:{type:"number"},problem:{type:"string"},validated_fix:{type:["string","null"]},confidence:{type:"number"},reuse_count:{type:"integer"}},required:["id","kind","relevance","problem","confidence","reuse_count"]}}},required:["query","count","results"]},annotations:L.wyrm_recall,aliases:[],handler:async($,{store:E,raw:S,memory:h,vectors:w,clientSupportsResources:P})=>{const{projectPath:g,query:u,kind:R,limit:j,minConfidence:q,detail:b}=$,p=E.getProject(g);if(!p)return{content:[{type:"text",text:`Project not found: ${g}`}],isError:!0};const f=!!w(),O=f&&W()&&/^(1|true|yes|on)$/i.test(process.env.WYRM_RERANK??"");let r;const x={kind:R,limit:j??10,minConfidence:q,rerank:O,onStats:t=>{r=t}},a=f?await h.recallHybrid(p.id,u,x):h.recall(p.id,u,x),l=r!==void 0,m=r?.rerankMs!=null,s=w(),y=typeof s?.getProvider=="function"?s.getProvider():void 0;let c;if(typeof s?.indexCoverage=="function")try{c=s.indexCoverage()}catch{}let d=null;if(!l){const t=y?.name;if(!f||t==="none"){const{resolveEmbeddingState:e}=await import("../providers/embedding-provider.js");d=e().reason||"vector_store_unavailable"}else if(c?.stale)d="stale_index";else{let e=-1;if(typeof s?.getStats=="function")try{e=s.getStats().total}catch{}d=e===0?"empty_index":"embed_error"}}const C=H({vectorsAvailable:l,rerankActive:m,embedModel:l?y?.model??(process.env.WYRM_EMBED_MODEL||"nomic-embed-text"):null,rerankModel:m&&process.env.WYRM_RERANK_MODEL||null,embedEgressHost:l?y?.remoteHost??null:null,rerankEgressHost:m?F():null,stageMs:r?{fts5:r.ftsMs,vector:r.vectorMs,fusion:r.fusionMs,...r.rerankMs!=null?{rerank:r.rerankMs}:{}}:void 0,indexStale:c?.stale?{activeModel:c.activeModel,indexedUnder:Object.keys(c.otherModels)}:void 0,ftsOnlyReason:d});if(a.length>0){const t=a.map(o=>o.artifact.id),e=t.map(()=>"?").join(",");S().prepare(`UPDATE memory_artifacts SET last_accessed_at = datetime('now'), access_count = access_count + 1 WHERE id IN (${e})`).run(...t)}const M=t=>Math.round(t*1e4)/1e4,_={query:u,count:a.length,results:a.map(t=>{const e=t.artifact,o={id:e.id,kind:e.kind,relevance:M(t.relevance_score),problem:e.problem,outcome:e.outcome,confidence:e.confidence==null?e.confidence:M(e.confidence),reuse_count:e.reuse_count,reuse_success_count:e.reuse_success_count};return e.constraints!=null&&(o.constraints=e.constraints),e.validated_fix!=null&&(o.validated_fix=e.validated_fix),e.why_it_worked!=null&&(o.why_it_worked=e.why_it_worked),o}),receipt:C},k=T(_,(t,e)=>{const o=`
|
|
2
2
|
|
|
3
|
-
_${
|
|
3
|
+
_${U(t.receipt)}_`;if(t.count===0)return v(e.brand,`No relevant memory found for: "${t.query}"`)+"\n\nUse `wyrm_remember` to store knowledge as you work."+o;let i=v(e.brand,`**Memory Recall** -- "${t.query}" (${t.count} found)`)+`
|
|
4
4
|
|
|
5
5
|
`;for(const n of t.results){i+=`### ${n.kind} (ID: ${n.id}) -- relevance ${(n.relevance*100).toFixed(0)}%
|
|
6
6
|
`,i+=`**Problem:** ${n.problem}
|
|
7
7
|
`,n.constraints&&(i+=`**Constraints:** ${n.constraints}
|
|
8
8
|
`),n.validated_fix&&(i+=`**Solution:** ${n.validated_fix}
|
|
9
9
|
`),n.why_it_worked&&(i+=`**Why:** ${n.why_it_worked}
|
|
10
|
-
`),n.outcome==="negative"&&(i+=
|
|
10
|
+
`),n.outcome==="negative"&&(i+=v(e.warn,"_This approach failed -- see solution for what to do instead_")+`
|
|
11
11
|
`);const D=n.confidence==null?"unknown":`${(n.confidence*100).toFixed(0)}%`;i+=`_Confidence: ${D} | Used ${n.reuse_count}x | Success rate: ${n.reuse_count>0?(n.reuse_success_count/n.reuse_count*100).toFixed(0)+"%":"not yet used"}_
|
|
12
12
|
|
|
13
|
-
`}return i+="_Use `wyrm_feedback` to record whether these were helpful._",i+o},{exemptStructured:!0});if((
|
|
13
|
+
`}return i+="_Use `wyrm_feedback` to record whether these were helpful._",i+o},{exemptStructured:!0});if((b==="link"||b!=="full"&&!!P?.())&&_.count>0){const t=_.results.map(e=>N(A(e.id),`memory #${e.id} (${e.kind})`,{description:e.problem.slice(0,120),mimeType:"application/json"}));return{...k,content:[...k.content,...t]}}return k}}];export{V as recallToolSpecs};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{getCapabilities as f,renderCapabilityBriefing as y}from"../capabilities.js";import{decodeCursor as g}from"../keyset.js";const o="wyrm://",c="application/json",l="text/markdown",T=["truths","failures","quests","memory","stats"];function
|
|
1
|
+
import{getCapabilities as f,renderCapabilityBriefing as y}from"../capabilities.js";import{decodeCursor as g}from"../keyset.js";const o="wyrm://",c="application/json",l="text/markdown",T=["truths","failures","quests","memory","stats"];function R(r){return`${o}memory/${r}`}function $(r,t,e){const i=`${o}project/${r}/${t}`;return e?`${i}?cursor=${e}`:i}const h=`${o}capabilities`,j=`${o}stats`;function C(r,t,e){return{type:"resource_link",uri:r,name:t,...e?.description?{description:e.description}:{},...e?.mimeType?{mimeType:e.mimeType}:{}}}function d(r){if(!/^\d{1,15}$/.test(r))return null;const t=Number(r);return Number.isSafeInteger(t)&&t>=0?t:null}function E(r){if(typeof r!="string"||!r.startsWith(o))return null;let t=r.slice(o.length),e;const i=t.indexOf("?");if(i>=0){const s=t.slice(i+1);t=t.slice(0,i);const a=/^cursor=([A-Za-z0-9_-]{1,512})$/.exec(s);if(!a)return null;e=a[1]}if(/[\x00-\x1f\x7f]/.test(t)||t.includes(".."))return null;const n=t.split("/");if(n.some(s=>s.length===0))return null;if(n.length===1)return e!==void 0?null:n[0]==="capabilities"?{kind:"capabilities"}:n[0]==="stats"?{kind:"stats"}:null;if(n.length===2&&n[0]==="memory"){if(e!==void 0)return null;const s=d(n[1]);return s===null?null:{kind:"memory",id:s}}if(n.length===3&&n[0]==="project"){const s=d(n[1]);if(s===null)return null;const a=n[2];return T.includes(a)?e!==void 0&&a!=="memory"?null:{kind:"project",id:s,leaf:a,...e!==void 0?{cursor:e}:{}}:null}return null}function N(){return[{uri:h,name:"Wyrm capabilities",description:"Feature inventory + runtime state \u2014 what this memory system can do and which tool to reach for. The resource form of wyrm_capabilities.",mimeType:l},{uri:j,name:"Wyrm global stats",description:"Global database stats (projects, sessions, quests, data points, size). The resource form of wyrm_stats.",mimeType:c}]}function I(){return[{uriTemplate:`${o}memory/{id}`,name:"Memory artifact",description:"A single memory artifact (lesson/pattern/anti-pattern/heuristic/reasoning trace) by id.",mimeType:c},{uriTemplate:`${o}project/{id}/truths`,name:"Project ground truths",description:"A project's current ground truths (the authoritative facts).",mimeType:c},{uriTemplate:`${o}project/{id}/failures`,name:"Project failures",description:"A project's unresolved failure patterns (the negative-learning set).",mimeType:c},{uriTemplate:`${o}project/{id}/quests`,name:"Project quests",description:"A project's open quests (pending + in-progress).",mimeType:c},{uriTemplate:`${o}project/{id}/memory`,name:"Project memory",description:"A project's validated memory artifacts.",mimeType:c},{uriTemplate:`${o}project/{id}/stats`,name:"Project memory stats",description:"A project's memory artifact stats (totals by kind, avg confidence).",mimeType:c}]}const m=200;class p extends Error{uri;constructor(t,e){super(e),this.uri=t,this.name="ResourceNotFoundError"}}function u(r,t){return{uri:r,mimeType:c,text:JSON.stringify(t,null,2)}}function x(r,t){const e=E(r);if(!e)throw new p(r,`Unknown or malformed resource URI: ${r}`);switch(e.kind){case"capabilities":{const i=f(t.db.getDatabase(),t.WYRM_TOOL_COUNT,{hasEncryption:()=>!!process.env.WYRM_ENCRYPTION_KEY,isFederationEnabled:()=>!0,isAgentRunning:()=>{try{return t.agentDaemon.status().running}catch{return null}}});return{uri:r,mimeType:l,text:y(i)}}case"stats":return u(r,t.db.getStats());case"memory":{const i=t.memory.get(e.id);if(!i)throw new p(r,`Memory artifact not found: ${e.id}`);return u(r,i)}case"project":{if(!t.db.getProjectById(e.id))throw new p(r,`Project not found: ${e.id}`);switch(e.leaf){case"truths":return u(r,{project_id:e.id,truths:t.groundTruths.getCurrent(e.id)});case"failures":return u(r,{project_id:e.id,failures:t.failures.list(e.id,m)});case"quests":{const n=t.db.getDatabase().prepare("SELECT * FROM quests WHERE project_id = ? AND status IN ('pending','in_progress') ORDER BY CASE priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 ELSE 3 END, id LIMIT ?").all(e.id,m);return u(r,{project_id:e.id,quests:n})}case"memory":{const n=g(e.cursor),s=t.memory.listPage(e.id,{pageSize:m,after:n}),a=u(r,{project_id:e.id,artifacts:s.items,...s.nextCursor?{next_cursor:s.nextCursor}:{}});return s.nextCursor&&(a.nextCursor=s.nextCursor),a}case"stats":return u(r,{project_id:e.id,stats:t.memory.getStats(e.id)})}}}}export{h as CAPABILITIES_URI,p as ResourceNotFoundError,j as STATS_URI,o as WYRM_URI_SCHEME,I as listWyrmResourceTemplates,N as listWyrmResources,R as memoryUri,E as parseWyrmUri,$ as projectUri,x as readWyrmResource,C as resourceLink};
|
package/dist/handlers/session.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import{basename as X}from"path";import{createHash as Z}from"crypto";import{blockText as Q}from"./types.js";import{TOOL_ANNOTATIONS as T}from"../tool-annotations.js";import{renderResult as
|
|
1
|
+
import{basename as X}from"path";import{createHash as Z}from"crypto";import{blockText as Q}from"./types.js";import{TOOL_ANNOTATIONS as T}from"../tool-annotations.js";import{renderResult as v,withGlyph as N,cacheKeyFor as O}from"../render.js";import{computeStaleness as ee}from"../intelligence.js";import{ValidationError as te,asInt as se,asString as ne}from"../validate.js";import{sanitizeActorId as ie}from"./boundary.js";import{makeEstimator as oe}from"../token-budget.js";function G(e){return!e||!e.thread.trim()?null:`### \u{1F9F5} Where We Left Off _(the living thread \u2014 what we were working on and thinking about)_
|
|
2
2
|
`+`_updated ${e.updated_at}${e.updated_by?" \xB7 "+e.updated_by:""} \xB7 ${e.turn_count} turn(s)_`+`
|
|
3
3
|
|
|
4
4
|
`+e.thread.slice(-1800)}const re={tech_stack:"\u{1F6E0}\uFE0F Tech Stack",architecture:"\u{1F3D7}\uFE0F Architecture",constraint:"\u{1F6A7} Constraints",decision:"\u{1F4CB} Decisions",contact:"\u{1F464} Contacts",other:"\u{1F4CC} Other Facts"};function J(e,t){let s=`### \u{1F4DA} Project Ground Truths _(treat as authoritative \u2014 do not infer or contradict)_
|
|
5
|
-
`;const n=new Map;for(const i of e)n.has(i.category)||n.set(i.category,[]),n.get(i.category).push(i);for(const[i,
|
|
6
|
-
`;for(const
|
|
5
|
+
`;const n=new Map;for(const i of e)n.has(i.category)||n.set(i.category,[]),n.get(i.category).push(i);for(const[i,r]of n){s+=`**${re[i]??i}:**
|
|
6
|
+
`;for(const a of r){let p="";if(t){const l=ee(a);l!==null&&l>.7&&(p="[\u26A0\uFE0F STALE] ")}s+=`- ${p}**${a.key}:** ${a.value}${a.rationale?` _(${a.rationale})_`:""}
|
|
7
7
|
`}}return s}const V=`
|
|
8
8
|
SELECT * FROM quests
|
|
9
9
|
WHERE project_id = ? AND status = 'pending'
|
|
@@ -20,7 +20,7 @@ import{basename as X}from"path";import{createHash as Z}from"crypto";import{block
|
|
|
20
20
|
`).get()}function P(e){const t=e.getAllProjects(20);return t.length===0?{content:[{type:"text",text:"No projects found. Run `wyrm_scan_projects` first."}],isError:!0}:{content:[{type:"text",text:`Project not found. Available projects:
|
|
21
21
|
${t.map(n=>`- ${n.name} (ID: ${n.id})`).join(`
|
|
22
22
|
`)}`}],isError:!0}}const K=(e,t)=>{const s=e.session_id!==null?`
|
|
23
|
-
Session logged: #${e.session_id}`:"";return
|
|
23
|
+
Session logged: #${e.session_id}`:"";return N(t.brand,`Wyrm primed for **${e.project_name}** | ${e.truth_count} truth${e.truth_count!==1?"s":""} - ${e.quest_count} quest${e.quest_count!==1?"s":""} - ${e.memory_count} memor${e.memory_count!==1?"ies":"y"}${s}`)},L=(e,t)=>{const s=K(e,t);return e.sections.length>0?e.sections.map(n=>n.text).join(`
|
|
24
24
|
|
|
25
25
|
---
|
|
26
26
|
|
|
@@ -28,14 +28,17 @@ Session logged: #${e.session_id}`:"";return O(t.brand,`Wyrm primed for **${e.pro
|
|
|
28
28
|
|
|
29
29
|
`+s:s+`
|
|
30
30
|
|
|
31
|
-
_No memory stored yet -- start working and Wyrm will learn._`},A={summary:K};async function
|
|
32
|
-
${n}`:n})):
|
|
31
|
+
_No memory stored yet -- start working and Wyrm will learn._`},A={summary:K};async function F(e,t,s){try{const{logSavings:n}=await import("../statusline.js"),i=Q(s.content[0]).length,r=Math.round(i/4),a=e.prepare("SELECT id FROM sessions WHERE project_id = ? ORDER BY created_at DESC LIMIT 1").get(t);n(e,"wyrm_session_prime","recovered_context",r,a?.id)}catch{}}const ce={continuity:{path:"continuity",tool:"wyrm_session_prime"},truths:{path:"truths",tool:"wyrm_truth_get"},scaffold:{path:"scaffolds",tool:"wyrm_context_build"},memory:{path:"memory",tool:"wyrm_recall"},quests:{path:"quests",tool:"wyrm_all_quests"}},ae=48,de=1200;async function pe(e,{store:t,raw:s,memory:n,truths:i,scaffolds:r,cache:a}){const{project_id:p,project_name:l,task:d,log_session:y}=e,c=ie(e.run_id);if(c===null)throw new te("run_id",'must be <=64 printable-ASCII chars (no ";")');const u=ne("role",e.role,{maxLen:200})??"",j=se("token_budget",e.token_budget,{min:1})??de,_=s(),w=()=>_.prepare("SELECT body_json FROM run_briefs WHERE run_id = ? AND role = ?").get(c,u),h=p||l?q(t,_,p,l):void 0;if((p||l)&&!h)return P(t);let S=w();if(S&&h){const o=JSON.parse(S.body_json);if(o.project_id!==h.id)return{content:[{type:"text",text:`Fleet brief for (run ${c}, role '${u}') is pinned to project '${o.project_name}' (ID: ${o.project_id}) \u2014 refusing to serve it for requested project '${h.name}' (ID: ${h.id}). Use a distinct role for a second project, or prime without run_id.`}],isError:!0}}if(!S){const o=h??q(t,_,p,l);if(!o)return P(t);const m=[],$=G(t.getContinuity(o.id));$&&m.push({kind:"continuity",text:$});const k=i.getCurrent(o.id);k.length>0&&m.push({kind:"truths",text:J(k,!1).slice(0,1200)});const x=d??(u||void 0);if(x){const b=r.findBest(x,o.id,.3);b&&m.push({kind:"scaffold",text:r.formatForContext(b,1500)})}const M=n.buildContextBrief(o.id,x??"general",{maxItems:10,minConfidence:.3});M.text&&m.push({kind:"memory",text:M.text.slice(0,2e3)});const R=_.prepare(V).all(o.id);R.length>0&&m.push({kind:"quests",text:Y(R).slice(0,800)});const D=oe(),I=[];let C=ae;for(const b of m){const W=D.count(b.text)+2;if(C+W<=j)I.push(b),C+=W;else{const H=ce[b.kind]??{path:b.kind,tool:"wyrm_search"},U=`[beyond token_budget] wyrm://project/${o.id}/${H.path} -- fetch via ${H.tool}`;I.push({kind:b.kind,text:U}),C+=D.count(U)+2}}let B=null;y&&(B=t.createSession(o.id,{objectives:d??"Session started via wyrm_session_prime",summary:""}).id);const z={project_id:o.id,project_name:o.name,truth_count:i.getStats(o.id).current,quest_count:R.length,memory_count:n.getStats(o.id).total,session_id:B,sections:I,run_id:c,role:u,token_budget:j};_.prepare("INSERT OR IGNORE INTO run_briefs (run_id, role, body_json, token_budget) VALUES (?, ?, ?, ?)").run(c,u,JSON.stringify(z),j),S=w()}const g=JSON.parse(S.body_json),E=v(g,L,A);if(e.for_spawn===!0){const o=v(g,L,{...A,exemptStructured:!0}),m=Q(o.content[0]),$=Z("sha256").update(m,"utf8").digest("hex"),k={project_id:g.project_id,project_name:g.project_name,session_id:g.session_id??null,sections:g.sections,for_spawn:!0,run_id:c,role:u,brief:m,brief_hash:$,brief_chars:m.length,cache_key:{table:"run_briefs",run_id:c,role:u}};await F(_,g.project_id,o);const x={content:[{type:"text",text:m}],structuredContent:k};return a.set(O("wyrm_session_prime",JSON.stringify(e)),x,3e4),x}return await F(_,g.project_id,E),a.set(O("wyrm_session_prime",JSON.stringify(e)),E,3e4),E}const Se=[{name:"wyrm_session_start",description:"Start or continue a session for a project",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Project path"},objectives:{type:"string",description:"Session objectives"}},required:["projectPath"]},outputSchema:{type:"object",properties:{session_id:{type:"integer"},project:{type:"string"},date:{type:"string"},objectives:{type:["string","null"]}},required:["session_id","project","date","objectives"]},annotations:T.wyrm_session_start,aliases:[],handler:(e,{store:t})=>{const{projectPath:s,objectives:n}=e;let i=t.getProject(s);i||(i=t.registerProject(X(s),s));let r=t.getTodaySession(i.id);r?n&&(r=t.updateSession(r.id,{objectives:r.objectives?`${r.objectives}
|
|
32
|
+
${n}`:n})):r=t.createSession(i.id,{objectives:n||""}),t.archiveOldSessions(i.id,10);const a={session_id:r.id,project:i.name,date:r.date,objectives:r.objectives||null};return v(a,(p,l)=>N(l.brand,`Session ${p.session_id} for ${p.project}`)+`
|
|
33
33
|
**Date:** ${p.date}
|
|
34
|
-
**Objectives:** ${p.objectives??"None set"}`)}},{name:"wyrm_session_update",description:"Update the current session with completed work, issues, or notes",inputSchema:{type:"object",properties:{projectPath:{type:"string"},completed:{type:"string",description:"What was completed"},issues:{type:"string"},commits:{type:"string",description:"Git commits made"},notes:{type:"string"}},required:["projectPath"]},outputSchema:{type:"object",properties:{session_id:{type:"integer"},project:{type:"string"},updated:{type:"array",items:{type:"string",enum:["completed","issues","commits","notes"]}}},required:["session_id","project","updated"]},annotations:T.wyrm_session_update,aliases:[],handler:(e,{store:t,indexing:s})=>{const{projectPath:n,completed:i,issues:
|
|
35
|
-
${i}`:i),
|
|
36
|
-
${
|
|
37
|
-
${
|
|
38
|
-
${p}`:p),
|
|
39
|
-
${
|
|
40
|
-
|
|
41
|
-
|
|
34
|
+
**Objectives:** ${p.objectives??"None set"}`)}},{name:"wyrm_session_update",description:"Update the current session with completed work, issues, or notes",inputSchema:{type:"object",properties:{projectPath:{type:"string"},completed:{type:"string",description:"What was completed"},issues:{type:"string"},commits:{type:"string",description:"Git commits made"},notes:{type:"string"}},required:["projectPath"]},outputSchema:{type:"object",properties:{session_id:{type:"integer"},project:{type:"string"},updated:{type:"array",items:{type:"string",enum:["completed","issues","commits","notes"]}}},required:["session_id","project","updated"]},annotations:T.wyrm_session_update,aliases:[],handler:(e,{store:t,indexing:s})=>{const{projectPath:n,completed:i,issues:r,commits:a,notes:p}=e,l=t.getProject(n);if(!l)return{content:[{type:"text",text:"Project not found"}],isError:!0};let d=t.getTodaySession(l.id);d||(d=t.createSession(l.id,{}));const y={};i&&(y.completed=d.completed?`${d.completed}
|
|
35
|
+
${i}`:i),r&&(y.issues=d.issues?`${d.issues}
|
|
36
|
+
${r}`:r),a&&(y.commits=d.commits?`${d.commits}
|
|
37
|
+
${a}`:a),p&&(y.notes=d.notes?`${d.notes}
|
|
38
|
+
${p}`:p),d=t.updateSession(d.id,y);const c=[i,r,p].filter(Boolean).join(" \xB7 ");c&&t.updateContinuity(l.id,c,"session_update"),s()?.enqueue("session",d.id,l.id);const u={session_id:d.id,project:l.name,updated:Object.keys(y)};return v(u,(j,_)=>N(_.brand,`Session updated for ${j.project}`))}},{name:"wyrm_session_rehydrate",description:"Lossless session rehydration. Given a past session ID, returns a complete briefing markdown a fresh AI agent can ingest to inherit prior state: objectives, completed work, notes, summary, current ground truths, open quests, validated patterns (artifacts), unresolved failure patterns. Cross-session continuity no other AI memory tool offers.",inputSchema:{type:"object",properties:{session_id:{type:"number",description:"ID of the session to rehydrate"},include_artifacts:{type:"boolean",description:"Include memory artifacts (default true)"},include_failures:{type:"boolean",description:"Include unresolved failure patterns (default true)"},max_truth_chars:{type:"number",description:"Cap on chars used for ground truths (default 2000)"}},required:["session_id"]},outputSchema:{type:"object",properties:{session_id:{type:"integer"},project_name:{type:"string"},project_path:{type:"string"},briefing_markdown:{type:"string"},context_chars:{type:"integer"},attached:{type:"object",properties:{quests:{type:"integer"},truths:{type:"integer"},artifacts:{type:"integer"},failures:{type:"integer"}},required:["quests","truths","artifacts","failures"]}},required:["session_id","project_name","project_path","briefing_markdown","context_chars","attached"]},annotations:T.wyrm_session_rehydrate,aliases:[],handler:(e,{rehydration:t})=>{const{session_id:s,include_artifacts:n,include_failures:i,max_truth_chars:r}=e,a=t.rehydrate(s,{include_artifacts:n,include_failures:i,max_truth_chars:r});return a?v({...a},p=>p.briefing_markdown,{exemptStructured:!0}):{content:[{type:"text",text:`Session #${s} not found.`}],isError:!0}}},{name:"wyrm_session_prime",description:"Use first at session start - load everything you need to know before starting work, and catch me up on where we left off last time: ground truths, memory brief, reasoning scaffold, and active quests in one call. First call every agent makes in a run.",inputSchema:{type:"object",properties:{project_id:{type:"number"},project_name:{type:"string"},task:{type:"string",description:"Drives scaffold + memory selection"},log_session:{type:"boolean",description:"Also log a session row"},run_id:{type:"string",description:"Fleet: brief cached per (run_id, role)"},role:{type:"string"},token_budget:{type:"number"},for_spawn:{type:"boolean"}}},outputSchema:{type:"object",properties:{project_id:{type:"integer"},project_name:{type:"string"},session_id:{type:["integer","null"]},sections:{type:"array",items:{type:"object",properties:{kind:{type:"string"},text:{type:"string"}},required:["kind","text"]}}},required:["project_id","project_name","session_id","sections"]},annotations:T.wyrm_session_prime,aliases:[],handler:async(e,{store:t,raw:s,memory:n,truths:i,scaffolds:r,cache:a})=>{const{project_id:p,project_name:l,task:d,log_session:y}=e;if(e.run_id!==void 0&&e.run_id!==null)return pe(e,{store:t,raw:s,memory:n,truths:i,scaffolds:r,cache:a});const c=q(t,s(),p,l);if(!c)return P(t);const u=[],j=G(t.getContinuity(c.id));j&&u.push({kind:"continuity",text:j});const _=i.getCurrent(c.id);if(_.length>0&&u.push({kind:"truths",text:J(_,!0).slice(0,1200)}),d){const f=r.findBest(d,c.id,.3);f&&u.push({kind:"scaffold",text:r.formatForContext(f,1500)})}const w=n.buildContextBrief(c.id,d??"general",{maxItems:10,minConfidence:.3});w.text&&u.push({kind:"memory",text:w.text.slice(0,2e3)});const h=s().prepare(V).all(c.id);h.length>0&&u.push({kind:"quests",text:Y(h).slice(0,800)});try{const f=s().prepare("SELECT COUNT(*) AS n FROM memory_artifacts WHERE project_id = ? AND needs_review = 1").get(c.id).n;f>0&&u.push({kind:"review_queue",text:`### \u{1F4E5} Review Queue
|
|
39
|
+
${f} captured memor${f===1?"y awaits":"ies await"} review (wyrm_review) \u2014 queued memory is invisible to recall until approved.`})}catch{}try{const{resolveEmbeddingState:f}=await import("../providers/embedding-provider.js"),o=f();let m=-1;try{m=s().prepare("SELECT COUNT(*) AS n FROM vectors").get().n}catch{m=0}o.resolved==="none"?u.push({kind:"vector_health",text:`### [!] Semantic Recall is OFF
|
|
40
|
+
No embedding provider is active (${o.reason||"no provider"}) - recall is FTS5 keyword-only (~59.9% vs 72.2% hybrid recall@10). Fix: ${o.fix}`}):o.resolved==="local"?u.push({kind:"vector_health",text:`### [!] Semantic Recall is NON-SEMANTIC
|
|
41
|
+
Provider 'local' (hash-384) is a test-grade fallback, not a real embedder - recall quality is at the FTS floor. Fix: ${o.fix}`}):m===0&&u.push({kind:"vector_health",text:`### [!] Vector Index is EMPTY
|
|
42
|
+
Provider '${o.resolved}' (${o.model}) is configured but nothing is indexed - recall is running FTS5-only until the index is built. Fix: wyrm index rebuild`})}catch{}try{const{vaultAdvisory:f}=await import("../vault.js"),o=f();o.length>0&&u.push({kind:"vault",text:`### \u{1F510} Credential Vault
|
|
43
|
+
`+o.map(m=>`- ${m}`).join(`
|
|
44
|
+
`)})}catch{}let S=null;y&&(S=t.createSession(c.id,{objectives:d??"Session started via wyrm_session_prime",summary:""}).id);const g={project_id:c.id,project_name:c.name,truth_count:i.getStats(c.id).current,quest_count:h.length,memory_count:n.getStats(c.id).total,session_id:S,sections:u},E=v(g,L,A);return await F(s(),c.id,E),a.set(O("wyrm_session_prime",JSON.stringify(e)),E,3e4),E}}];export{Se as sessionToolSpecs};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{isLoopbackHost as
|
|
1
|
+
import{isLoopbackHost as b}from"../receipt.js";function m(o){try{const e=new URL(o).hostname.toLowerCase();return b(e)?null:e}catch{return null}}class v{name="local-hash";model="hash-384";dimensions=384;async embed(e){const t=new Float32Array(this.dimensions),s=e.toLowerCase().split(/\s+/);for(let r=0;r<s.length;r++){const i=s[r];for(let a=0;a<i.length;a++){const l=i.charCodeAt(a)*(r+1)*(a+1)%this.dimensions;t[l]+=1/s.length}}const n=Math.sqrt(t.reduce((r,i)=>r+i*i,0));if(n>0)for(let r=0;r<t.length;r++)t[r]/=n;return t}async isReady(){return!0}}class y{name="openai";model;dimensions;remoteHost="api.openai.com";apiKey;timeoutMs;constructor(e,t){this.apiKey=e,this.model=t||"text-embedding-3-small",this.dimensions=this.model.includes("large")?3072:1536;const s=Number(process.env.WYRM_OPENAI_TIMEOUT);this.timeoutMs=Number.isFinite(s)&&s>0?s:8e3}async embed(e){const t=await fetch("https://api.openai.com/v1/embeddings",{method:"POST",headers:{Authorization:`Bearer ${this.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify({input:e,model:this.model}),signal:AbortSignal.timeout(this.timeoutMs)});if(!t.ok){const n=await t.text();throw new Error(`OpenAI embedding API error ${t.status}: ${n}`)}const s=await t.json();return new Float32Array(s.data[0].embedding)}async isReady(){return!!this.apiKey}}class I{name="ollama";model;dimensions;remoteHost;baseUrl;timeoutMs;constructor(e,t){this.baseUrl=e||"http://localhost:11434",this.model=t||"nomic-embed-text",this.dimensions=this.model.includes("mxbai")?1024:768,this.remoteHost=m(this.baseUrl);const s=Number(process.env.WYRM_OLLAMA_TIMEOUT);this.timeoutMs=Number.isFinite(s)&&s>0?s:8e3}async embed(e){const t=await fetch(`${this.baseUrl}/api/embeddings`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({model:this.model,prompt:e}),signal:AbortSignal.timeout(this.timeoutMs)});if(!t.ok){const n=await t.text();throw new Error(`Ollama embedding API error ${t.status}: ${n}`)}const s=await t.json();return new Float32Array(s.embedding)}async isReady(){try{return(await fetch(`${this.baseUrl}/api/tags`,{signal:AbortSignal.timeout(2e3)})).ok}catch{return!1}}}class p{name="nim";model;remoteHost;dims;baseUrl;apiKey;timeoutMs;static KNOWN_DIMS={"nvidia/llama-nemotron-embed-1b-v2":2048,"nvidia/llama-3.2-nv-embedqa-1b-v2":2048,"nvidia/nv-embedqa-e5-v5":1024,"nvidia/nv-embedqa-mistral-7b-v2":4096,"nvidia/nv-embed-v1":4096};get dimensions(){return this.dims}constructor(e,t,s){this.apiKey=e,this.model=t||process.env.WYRM_NIM_EMBED_MODEL||"nvidia/llama-nemotron-embed-1b-v2",this.baseUrl=(s||process.env.WYRM_NIM_BASE_URL||"https://integrate.api.nvidia.com").replace(/\/+$/,"");const n=Number(process.env.WYRM_NIM_EMBED_DIM);this.dims=Number.isFinite(n)&&n>0?n:p.KNOWN_DIMS[this.model]??2048;const r=Number(process.env.WYRM_NIM_TIMEOUT);this.timeoutMs=Number.isFinite(r)&&r>0?r:8e3,this.remoteHost=m(this.baseUrl)}async embed(e,t="passage"){let s;for(let i=0;;i++){let a=null;try{const l={"Content-Type":"application/json"};if(this.apiKey&&(l.Authorization=`Bearer ${this.apiKey}`),s=await fetch(`${this.baseUrl}/v1/embeddings`,{method:"POST",headers:l,body:JSON.stringify({input:[e],model:this.model,input_type:t,encoding_format:"float",truncate:"END"}),signal:AbortSignal.timeout(this.timeoutMs)}),s.ok||s.status!==429&&s.status<500)break;const c=Number(s.headers?.get?.("retry-after"));a=Number.isFinite(c)&&c>0?Math.min(c*1e3,15e3):500*2**i}catch(l){if(i>=2)throw l;a=500*2**i}if(i>=2)break;await new Promise(l=>setTimeout(l,a??500))}if(!s.ok){const i=await s.text();throw new Error(`NIM embedding API error ${s.status}: ${i.slice(0,300)}`)}const r=(await s.json()).data?.[0]?.embedding;if(!Array.isArray(r)||r.length===0)throw new Error("NIM embedding API returned no embedding");return r.length!==this.dims&&(this.dims=r.length),new Float32Array(r)}async isReady(){return this.remoteHost?!!this.apiKey:!0}}class u{name="none";model="none";dimensions=0;async embed(e){return new Float32Array(0)}async isReady(){return!0}}const d="set WYRM_VECTOR_PROVIDER=nim and NVIDIA_API_KEY (or WYRM_NIM_API_KEY) in the server env, then: wyrm index rebuild";function h(o){const e=String(o??"").trim().toLowerCase();return e===""?"auto":e}function M(o){try{return new URL(o),!1}catch{return!0}}function E(o){const e=h(o??process.env.WYRM_VECTOR_PROVIDER);switch(e){case"nim":{const t=process.env.NVIDIA_API_KEY||process.env.WYRM_NIM_API_KEY||"",s=process.env.WYRM_NIM_BASE_URL||"https://integrate.api.nvidia.com";if(M(s))return{requested:e,resolved:"none",model:null,reason:"nim_bad_base_url",fix:"WYRM_NIM_BASE_URL is not a valid URL (needs https://\u2026): "+s,egressHost:null};const n=m(s);if(!t&&n)return{requested:e,resolved:"none",model:null,reason:"nim_missing_key",fix:d,egressHost:null};const r=process.env.WYRM_NIM_EMBED_MODEL||"nvidia/llama-nemotron-embed-1b-v2";return{requested:e,resolved:"nim",model:r,reason:"",fix:"",egressHost:n}}case"openai":return process.env.OPENAI_API_KEY?{requested:e,resolved:"openai",model:"text-embedding-3-small",reason:"",fix:"",egressHost:"api.openai.com"}:{requested:e,resolved:"none",model:null,reason:"openai_missing_key",fix:"set OPENAI_API_KEY, or use the supported provider: "+d,egressHost:null};case"ollama":{const t=process.env.OLLAMA_URL||"http://localhost:11434";return{requested:e,resolved:"ollama",model:process.env.WYRM_EMBED_MODEL||"nomic-embed-text",reason:"ollama_deprecated",fix:"Ollama support is deprecated and leaves in the next major \u2014 "+d,egressHost:m(t),advisory:!0}}case"none":return{requested:e,resolved:"none",model:null,reason:"disabled",fix:"vectors were explicitly disabled; to enable: "+d,egressHost:null};case"local":return{requested:e,resolved:"local",model:"hash-384",reason:"local_hash_fallback",fix:"local-hash is non-semantic (test only) \u2014 "+d,egressHost:null,advisory:!0};default:return{requested:e,resolved:"none",model:null,reason:"no_provider_configured",fix:d,egressHost:null}}}let _=!1;function g(o){switch(h(o?.provider??process.env.WYRM_VECTOR_PROVIDER)){case"openai":{const t=o?.apiKey||process.env.OPENAI_API_KEY;if(!t)throw new Error("OpenAI provider requires OPENAI_API_KEY");return new y(t,o?.model)}case"ollama":return _||(_=!0,console.error("Wyrm: WYRM_VECTOR_PROVIDER=ollama is deprecated \u2014 the supported embedding provider is NVIDIA NIM ("+d+")")),new I(o?.ollamaUrl||process.env.OLLAMA_URL,o?.model);case"nim":{const t=process.env.NVIDIA_API_KEY||process.env.WYRM_NIM_API_KEY||o?.apiKey||"",s=o?.nimBaseUrl||process.env.WYRM_NIM_BASE_URL||"https://integrate.api.nvidia.com";if(!t&&m(s))throw new Error("NIM provider requires NVIDIA_API_KEY (or WYRM_NIM_API_KEY) for a remote endpoint");return new p(t,o?.model,o?.nimBaseUrl)}case"none":return new u;case"auto":return new u;case"local":return new v;default:return new u}}export{v as LocalHashProvider,d as NIM_FIX_LINE,p as NimProvider,u as NoneProvider,I as OllamaProvider,y as OpenAIProvider,g as createProvider,m as hostIfRemote,h as normalizeProviderKey,E as resolveEmbeddingState};
|
package/dist/receipt.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
function
|
|
1
|
+
function s(e){const n=["fts5"];e.vectorsAvailable&&n.push("vector"),e.rerankActive&&n.push("rerank");const t=[];e.vectorsAvailable&&e.embedEgressHost&&t.push(`embed:${e.embedEgressHost}`),e.rerankActive&&e.rerankEgressHost&&t.push(`rerank:${e.rerankEgressHost}`);const r=t.length===0?"none":t.join("; ");return{deterministic:!0,generativeLlmCalls:0,dataEgress:r,local:r==="none",stages:n,embedModel:e.vectorsAvailable?e.embedModel??null:null,rerankModel:e.rerankActive?e.rerankModel??null:null,...e.stageMs?{stageMs:e.stageMs}:{},...e.indexStale?{indexStale:e.indexStale}:{},...!e.vectorsAvailable&&e.ftsOnlyReason?{ftsOnlyReason:e.ftsOnlyReason}:{}}}function l(e){const n=[e.embedModel,e.rerankModel].filter(Boolean).join(", "),t=e.ftsOnlyReason?` Vector stage skipped: ${e.ftsOnlyReason}.`:"",r=`${e.stages.join(" -> ")}${n?` (${n})`:""}`;return e.local?`No generative LLM touched this query; no data left this machine. Retrieved by ${r}, all local.${t}`:`No generative LLM touched this query. Non-generative scoring by an explicit opt-in remote provider (${e.dataEgress}). Retrieved by ${r}.`}function a(e){const n=e.toLowerCase();return n==="localhost"||n==="::1"||n==="[::1]"?!0:/^127(\.\d{1,3}){3}$/.test(n)}function o(e){if(!e)return null;try{const n=new URL(e).hostname.toLowerCase();return a(n)?null:n}catch{return null}}export{l as attestation,s as buildDeterminismReceipt,o as egressHostOf,a as isLoopbackHost};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import{c as r,icons as E}from"./cli.js";import{detectClients as I,autoConfigureAll as q,findWyrmServerPath as T,getDefaultDbPath as R}from"./autoconfig.js";const H=[[1,8],[2,16],[4,32],[64,128]],v=[[20,24,30],[70,90,110],[150,170,190],[235,245,255]];function O(o,e){o=o<0?0:o>1?1:o;const i=e.length-1,t=o*i,s=Math.min(t|0,i-1),n=e[s],c=e[s+1],a=t-s;return[n[0]+(c[0]-n[0])*a|0,n[1]+(c[1]-n[1])*a|0,n[2]+(c[2]-n[2])*a|0]}class k{cols;rows;W;H;bits;col;lum;constructor(e,i){this.cols=e,this.rows=i,this.W=e*2,this.H=i*4,this.bits=new Uint8Array(e*i),this.col=new Array(e*i).fill(null),this.lum=new Float32Array(e*i).fill(-1)}clear(){this.bits.fill(0),this.col.fill(null),this.lum.fill(-1)}set(e,i,t,s){if(e|=0,i|=0,e<0||e>=this.W||i<0||i>=this.H)return;const n=(i>>2)*this.cols+(e>>1);this.bits[n]|=H[i&3][e&1],s>this.lum[n]&&(this.lum[n]=s,this.col[n]=t)}ripples(e){this.clear();const i=[[this.W*(.5+.34*Math.sin(e*.7)),this.H*(.5+.34*Math.cos(e*.9))],[this.W*(.5+.4*Math.cos(e*.5)),this.H*(.5+.4*Math.sin(e*1.1))]];for(let t=0;t<this.H;t++)for(let s=0;s<this.W;s++){let n=0;for(const c of i)n+=Math.sin(Math.hypot(s-c[0],t-c[1])*.3-e*3);n/=i.length,n>.5&&this.set(s,t,O((n-.5)/.5,v),n)}}render(e){const i=[];for(let t=0;t<this.rows;t++){let s="";for(let n=0;n<this.cols;n++){const c=t*this.cols+n,a=this.bits[c];if(!a){s+=" ";continue}const f=String.fromCodePoint(10240+a),l=this.col[c]??v[2];s+=e?`\x1B[38;2;${l[0]};${l[1]};${l[2]}m${f}\x1B[0m`:r.dim(f)}i.push(s)}return i}}const M="W Y R M";function W(o,e){const i=Math.min(o.cols-4,72),t=new k(i,5);t.ripples(o.frame*.09);const s=t.render(o.truecolor),n=" ".repeat(Math.max(0,o.cols-i>>1)),c=e==="success"?r.green:r.cyan;return s.map((f,l)=>n+(l===2?C(f,c(r.bold(M)),i,n):f))}function C(o,e,i,t){const s=M.length,n=Math.max(0,i-s>>1);return" ".repeat(n)+e}function d(o,e){const i=z(o).length;return" ".repeat(Math.max(0,e-i>>1))+o}function z(o){return o.replace(/\x1b\[[0-9;]*m/g,"")}const p=o=>r.dim(o);function L(o,e){const i=[""],t=(s="")=>i.push(s);if(o.step==="welcome")W(e,"silver").forEach(s=>t(s)),t(),t(d(r.bold("Wyrm \u2014 memory for your AI"),e.cols)),t(d(r.dim("It remembers your projects so your assistant stops starting from zero."),e.cols)),t(),t(d(p("press Enter to begin \xB7 q to quit"),e.cols));else if(o.step==="about")t(),t(" "+r.bold("What this does")),t(),t(" "+r.dim("\u2022")+" Wyrm is a private memory that lives on "+r.cyan("this computer")+"."),t(" "+r.dim("\u2022")+" Your AI tools read it, so they recall past work, decisions,"),t(" and what already did not work \u2014 across every new chat."),t(" "+r.dim("\u2022")+" Nothing is sent anywhere unless you turn on cloud sync yourself."),t(),t(" Next, Wyrm will look for AI apps you already have and connect them."),t(),t(" "+p("Enter continue \xB7 b back \xB7 q quit"));else if(o.step==="confirm"){t(),t(" "+r.bold("Found these on your computer")),t();const s=o.clients.filter(n=>n.detected);if(s.length===0)t(" "+r.yellow("\u25D0")+" No AI apps detected yet. You can install one and run this again.");else for(const n of s){const c=n.configured?r.success("\u25CF already connected"):r.cyan("\u25D0 will connect");t(` ${n.icon} ${r.bold(D(n.name,18))} ${c}`)}t(),t(" "+r.dim("Wyrm server: ")+r.cyan(P(o.serverPath))),t(" "+r.dim("Memory file: ")+r.cyan(P(o.dbPath))),t(),t(" "+(s.length?p("Enter connect them \xB7 b back \xB7 q quit"):p("Enter finish \xB7 q quit")))}else if(o.step==="working"){t(),t(" "+r.bold("Connecting\u2026")),t();const s="\xB7\u2022\u25CF\u2022"[e.frame%4];t(" "+r.cyan(s)+" "+r.dim("writing each app's config (a backup is saved first)"))}else if(o.step==="done"){W(e,"success").forEach(n=>t(n)),t();const s=(o.results??[]).filter(n=>n.action==="configured"||n.action==="updated");t(d(r.success(`${E.dragon} Wyrm connected to ${s.length} app${s.length===1?"":"s"}`),e.cols));for(const n of s)t(d(r.dim(`${n.client.icon} ${n.client.name}`),e.cols));t(),t(d(r.dim("Open your AI app and it will remember. That's it."),e.cols)),t(),t(d(p("press Enter or q to close"),e.cols))}for(;i.length<e.rows-1;)t();return t(r.dim(" \u2500".padEnd(Math.min(e.cols,40),"\u2500"))),i.join(`
|
|
2
|
+
`)}function D(o,e){return o.length>=e?o.slice(0,e):o+" ".repeat(e-o.length)}function P(o){const e=process.env.HOME??"";return e&&o.startsWith(e)?"~"+o.slice(e.length):o}const F="\x1B[?1049h\x1B[?25l",N="\x1B[?25h\x1B[?1049l";async function _(o={}){const e=process.stdout,i=process.stdin;if(!e.isTTY||!i.isTTY)return!1;const t=/^(truecolor|24bit)$/i.test(process.env.COLORTERM??""),s={step:"welcome",clients:I(),results:null,serverPath:o.serverPath||T(),dbPath:o.dbPath||R()};let n=0,c=!0,a=!0;const f=()=>({cols:e.columns||80,rows:e.rows||24}),l=()=>{const{cols:y,rows:m}=f(),u=L(s,{cols:y,rows:m,frame:n,truecolor:t});e.write("\x1B[?2026h\x1B[H"+u.split(`
|
|
3
|
+
`).map(g=>g+"\x1B[K").join(`
|
|
4
|
+
`)+"\x1B[J\x1B[?2026l")},w=()=>{if(c){c=!1;try{i.setRawMode?.(!1)}catch{}i.pause(),e.write(N)}};process.once("exit",w),e.write(F);try{i.setRawMode?.(!0)}catch{}i.resume(),i.setEncoding("utf8");const A=setInterval(()=>{a&&(n++,l())},90),x=()=>l();e.on("resize",x);const $=()=>{clearInterval(A),e.off("resize",x),w()};return await new Promise(y=>{const m=()=>{$(),y(!0)},u=()=>{a=s.step==="welcome"||s.step==="done"},g=async h=>{if(h===""||h==="q"||h==="\x1B"){m();return}const b=h==="\r"||h===`
|
|
5
|
+
`||h===" ";s.step==="welcome"&&b?(s.step="about",u(),l()):s.step==="about"?h==="b"?(s.step="welcome",u(),l()):b&&(s.step="confirm",u(),l()):s.step==="confirm"?h==="b"?(s.step="about",u(),l()):b&&(s.step="working",u(),l(),s.results=q({serverPath:s.serverPath,dbPath:s.dbPath}),s.step="done",u(),n=0,l()):s.step==="done"&&b&&m()};i.on("data",h=>Promise.resolve(g(h)).catch(()=>m())),process.once("SIGINT",m),l()})}export{L as renderFrame,_ as runWizard};
|
package/dist/setup.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{detectClients as
|
|
3
|
-
`),process.stdout.write(JSON.stringify(
|
|
4
|
-
`);break}}}
|
|
2
|
+
import{detectClients as g,autoConfigureAll as u,removeFromAll as p,configureSpecific as $,reconfAll as h,getStatusSummary as y,findWyrmServerPath as b,getDefaultDbPath as v,loadWyrmMeta as k,exportServerConfig as P}from"./autoconfig.js";import{c as o,icons as r,BANNER as w,MINI_BANNER as a,printError as f,printSection as i}from"./cli.js";import{runWizard as C}from"./setup-wizard.js";function I(n){const e=n.slice(2);if(e.length===0)return{command:"auto"};const c={command:"auto"};for(let l=0;l<e.length;l++)switch(e[l]){case"--check":case"-c":case"check":case"status":c.command="check";break;case"--remove":case"-r":case"remove":case"uninstall":c.command="remove";break;case"--only":case"-o":case"only":c.command="only",e[l+1]&&(c.clientIds=e[l+1].split(",").map(t=>t.trim()),l++);break;case"--reconf":case"--reconfigure":case"reconf":c.command="reconf";break;case"--list":case"-l":case"list":c.command="list";break;case"--export":case"export":{c.command="export";const t=e[l+1];(t==="stdio"||t==="vscode"||t==="mcp-json")&&(c.exportFormat=t,l++);break}case"--server":case"-s":e[l+1]&&(c.serverPath=e[l+1],l++);break;case"--db":case"-d":e[l+1]&&(c.dbPath=e[l+1],l++);break;case"--wizard":case"wizard":c.command="wizard";break;case"--no-wizard":case"--plain":c.noWizard=!0;break;case"--help":case"-h":case"help":c.command="help";break}return c}function d(n){console.log("");const e=n.filter(s=>s.action==="configured"),c=n.filter(s=>s.action==="updated"),l=n.filter(s=>s.action==="skipped"),t=n.filter(s=>s.action==="failed");for(const s of e)console.log(` ${r.success} ${s.client.icon} ${o.green(s.client.name)} \u2014 ${o.success("configured")}`),s.backup&&console.log(` ${o.dim(`backup \u2192 ${s.backup}`)}`);for(const s of c)console.log(` ${r.sync} ${s.client.icon} ${o.blue(s.client.name)} \u2014 ${o.cyan("updated")}`),s.backup&&console.log(` ${o.dim(`backup \u2192 ${s.backup}`)}`);for(const s of l)console.log(` ${o.dim(` \u25CB ${s.client.icon} ${s.client.name} \u2014 ${s.message}`)}`);for(const s of t)console.log(` ${r.error} ${s.client.icon} ${o.red(s.client.name)} \u2014 ${o.error(s.message)}`);console.log("");const m=e.length+c.length;m>0?(console.log(` ${o.success(`${r.dragon} Wyrm connected to ${m} AI client(s)`)}`),console.log(` ${o.dim("Switch AIs anytime \u2014 run wyrm-setup again to reconnect")}`)):t.length>0?console.log(` ${o.error("Some configurations failed. Check errors above.")}`):console.log(` ${o.dim("No AI clients detected. Install one and try again.")}`),console.log("")}function A(){console.log(a),console.log(""),console.log(`${o.bold("Usage:")} wyrm-setup ${o.dim("[command] [options]")}`),console.log(""),console.log(`${o.bold("Commands:")}`),console.log(` ${o.cyan("(no args)")} Auto-detect and configure all AI clients`),console.log(` ${o.cyan("check")} Show current configuration status`),console.log(` ${o.cyan("list")} List all supported AI clients`),console.log(` ${o.cyan("remove")} Remove Wyrm from all AI clients`),console.log(` ${o.cyan("reconf")} Re-configure previously configured clients`),console.log(` ${o.cyan("only X,Y")} Configure specific clients only`),console.log(` ${o.cyan("export [fmt]")} Print a portable config (fmt: mcp-json|vscode|stdio) for any MCP host`),console.log(` ${o.cyan("help")} Show this help message`),console.log(""),console.log(`${o.bold("Options:")}`),console.log(` ${o.cyan("--server P")} Override Wyrm MCP server path`),console.log(` ${o.cyan("--db P")} Override Wyrm database path`),console.log(""),console.log(`${o.bold("Client IDs:")}`);const n=g();for(const e of n)console.log(` ${e.icon} ${o.cyan(e.id.padEnd(18))} ${e.name}`);console.log(""),console.log(`${o.bold("Examples:")}`),console.log(` wyrm-setup ${o.dim("# Auto-configure everything")}`),console.log(` wyrm-setup check ${o.dim("# See what's configured")}`),console.log(` wyrm-setup only vscode-copilot,cursor ${o.dim("# Only VS Code + Cursor")}`),console.log(` wyrm-setup --server /path/to/wyrm/dist/index.js ${o.dim("# Custom server path")}`),console.log(` wyrm-setup remove ${o.dim("# Remove from all clients")}`),console.log(` wyrm-setup export stdio > wyrm.json ${o.dim("# Portable config for any MCP host")}`),console.log("")}function S(){console.log(a),console.log(""),i("Supported AI Clients"),console.log("");const n=g();for(const e of n){const c=e.detected?e.configured?o.success("\u25CF connected"):o.yellow("\u25D0 available"):o.dim("\u25CB not found"),l=e.version?` ${o.dim(`v${e.version}`)}`:"";console.log(` ${e.icon} ${o.bold(e.name.padEnd(20))} ${o.cyan(e.id.padEnd(18))} ${c}${l}`),console.log(` ${o.dim(`Config: ${e.configPath}`)}`)}console.log("")}async function x(){const n=I(process.argv);if(n.command==="wizard"||n.command==="auto"&&!n.noWizard){if(await C({serverPath:n.serverPath,dbPath:n.dbPath}))return;n.command==="wizard"&&(n.command="auto")}switch(n.command){case"help":A();break;case"check":{console.log(a),console.log(""),console.log(y());break}case"list":S();break;case"auto":{console.log(w),i("Auto-Configure"),console.log("");const e=n.serverPath||b(),c=n.dbPath||v();console.log(` ${r.sword} Server: ${o.cyan(e)}`),console.log(` ${r.treasure} DB: ${o.cyan(c)}`),console.log(""),console.log(` ${o.dim("Scanning for AI clients...")}`);const l=u({serverPath:e,dbPath:c});d(l);break}case"only":{(!n.clientIds||n.clientIds.length===0)&&(f("No client IDs specified. Use: wyrm-setup only vscode-copilot,cursor"),process.exit(1)),console.log(a),i(`Configure: ${n.clientIds.join(", ")}`);const e=$(n.clientIds,{serverPath:n.serverPath,dbPath:n.dbPath});d(e);break}case"remove":{console.log(a),i("Remove Wyrm from AI Clients"),console.log(""),console.log(` ${o.warning("Removing Wyrm configuration from all AI clients...")}`);const e=p(),c=e.filter(t=>t.action==="configured"),l=e.filter(t=>t.action==="skipped");for(const t of c)console.log(` ${r.check} ${t.client.icon} ${t.client.name} \u2014 removed`),t.backup&&console.log(` ${o.dim(`backup \u2192 ${t.backup}`)}`);for(const t of l)console.log(` ${o.dim(` \u25CB ${t.client.icon} ${t.client.name} \u2014 ${t.message}`)}`);console.log(""),c.length>0&&console.log(` ${o.success(`Removed from ${c.length} client(s). Run wyrm-setup to reconnect.`)}`),console.log("");break}case"reconf":{console.log(a),i("Re-Configure");const e=k();console.log(e?` ${o.dim(`Restoring config for: ${e.configuredClients.join(", ")}`)}`:` ${o.dim("No previous config found \u2014 running full auto-configure")}`);const c=h();d(c);break}case"export":{const e=n.exportFormat??"mcp-json",c=P(e,{serverPath:n.serverPath,dbPath:n.dbPath});process.stderr.write(`${o.dim(`# Wyrm MCP config (${e}). Paste into any MCP-compatible host.`)}
|
|
3
|
+
`),process.stdout.write(JSON.stringify(c,null,2)+`
|
|
4
|
+
`);break}}}x().catch(n=>{f(`Setup failed: ${n}`),process.exit(1)});
|
package/dist/tool-manifest.json
CHANGED
package/dist/vector-init.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createVectorStore as p}from"./vectors.js";import{IndexingPipeline as
|
|
1
|
+
import{createVectorStore as p}from"./vectors.js";import{resolveEmbeddingState as m}from"./providers/embedding-provider.js";import{IndexingPipeline as v}from"./indexer.js";function R(t,s,i){let r=null,o=null;try{const n=process.env.WYRM_VECTOR_PROVIDER||"auto";if(m(n).resolved!=="none"){r=p({provider:n},t.getDatabase()),s.setVectorStore(r);const c=(u,a)=>{try{switch(u){case"session":{const e=t.getSession(a);return e&&[e.objectives,e.completed,e.notes,e.summary].filter(Boolean).join(" ")||null}case"quest":{const e=t.getDatabase().prepare("SELECT * FROM quests WHERE id = ?").get(a);return e&&[e.title,e.description].filter(Boolean).join(" ")||null}case"note":case"context":{const e=t.getDatabase().prepare("SELECT value FROM data_lake WHERE id = ?").all(a);if(!e.length)return null;const l=i.maybeDecrypt(e[0].value);return l.startsWith("ENCRYPTED:")?null:l}default:return null}}catch{return null}};o=new v(t.getDatabase(),r,c,{batchSize:20,maxRetries:3,intervalMs:1e4}),o.start()}}catch(n){console.error("Wyrm: Failed to initialize vector store:",n)}return{vectorStore:r,indexingPipeline:o}}export{R as initVectorSearch};
|