wyrm-mcp 9.2.0 → 9.2.2

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.
Files changed (66) hide show
  1. package/README.md +2 -0
  2. package/dist/auto-runs.js +1 -0
  3. package/dist/autoconfig.js +3 -3
  4. package/dist/capabilities.js +3 -3
  5. package/dist/causality.js +13 -8
  6. package/dist/cli.js +25 -22
  7. package/dist/cloud/cli.js +3 -3
  8. package/dist/cloud/sync-daemon-entrypoint.js +2 -2
  9. package/dist/cloud/sync-daemon.js +1 -1
  10. package/dist/color.js +1 -0
  11. package/dist/database.js +3 -3
  12. package/dist/deprecations.js +2 -2
  13. package/dist/events.js +6 -6
  14. package/dist/federation.js +10 -10
  15. package/dist/handlers/arg-keys.js +1 -0
  16. package/dist/handlers/capture.js +58 -58
  17. package/dist/handlers/causality.js +8 -8
  18. package/dist/handlers/cloud.js +85 -83
  19. package/dist/handlers/companion.js +10 -10
  20. package/dist/handlers/entity.js +1 -1
  21. package/dist/handlers/events.js +12 -12
  22. package/dist/handlers/failure.js +12 -12
  23. package/dist/handlers/goals.js +4 -4
  24. package/dist/handlers/intelligence.js +63 -63
  25. package/dist/handlers/mcpclient.js +1 -1
  26. package/dist/handlers/presence.js +3 -3
  27. package/dist/handlers/project-arg.js +1 -0
  28. package/dist/handlers/project.js +1 -1
  29. package/dist/handlers/quest.js +5 -3
  30. package/dist/handlers/recall.js +18 -18
  31. package/dist/handlers/registry.js +1 -1
  32. package/dist/handlers/resources.js +1 -1
  33. package/dist/handlers/review.js +3 -3
  34. package/dist/handlers/run.js +3 -3
  35. package/dist/handlers/search.js +3 -3
  36. package/dist/handlers/session.js +24 -24
  37. package/dist/handlers/share.js +5 -5
  38. package/dist/handlers/shims.js +1 -1
  39. package/dist/handlers/thread.js +2 -2
  40. package/dist/http-auth.js +1 -1
  41. package/dist/http-fast.js +15 -15
  42. package/dist/index.js +2 -2
  43. package/dist/logger.js +2 -2
  44. package/dist/receipt.js +1 -1
  45. package/dist/rehydration.js +24 -9
  46. package/dist/render.js +3 -3
  47. package/dist/setup.js +2 -3
  48. package/dist/sqlite-busy.js +1 -1
  49. package/dist/stale-servers.js +2 -0
  50. package/dist/stdio-lifecycle.js +1 -0
  51. package/dist/storage/index.js +3 -3
  52. package/dist/tool-manifest-v2.json +1 -1
  53. package/dist/tool-manifest.json +1 -1
  54. package/dist/ui-dashboard.js +9 -8
  55. package/dist/validate.js +1 -1
  56. package/dist/vault-exec.js +4 -0
  57. package/dist/vault.js +1 -1
  58. package/dist/wyrm-cli.js +105 -142
  59. package/dist/wyrm-manifest.json +3 -3
  60. package/dist/wyrm-ui.js +14 -7
  61. package/dist/wyrm.js +2 -0
  62. package/package.json +6 -4
  63. package/scripts/hooks/wyrm-citation-audit.mjs +155 -46
  64. package/scripts/hooks/wyrm-prompt-context.mjs +300 -0
  65. package/scripts/hooks/wyrm-run-auto.mjs +148 -37
  66. package/scripts/hooks/wyrm-session-rehydrate.mjs +142 -24
package/README.md CHANGED
@@ -35,6 +35,8 @@ npm install -g wyrm-mcp # install
35
35
  wyrm-setup # wire it into your AI clients, then restart them
36
36
  ```
37
37
 
38
+ > **If `npm install -g` fails with EACCES**, npm's global prefix is a directory you cannot write (often `/usr`). Install under a prefix you own instead, and put its `bin` on your PATH: `npm install -g --prefix ~/.npm-global wyrm-mcp`, then `export PATH="$HOME/.npm-global/bin:$PATH"`. `wyrm update` later updates that same install.
39
+
38
40
  > **On npm v12+**, npm denies dependency install scripts by default, which skips `better-sqlite3`'s native build — the install *succeeds* but `wyrm` then fails with *"Could not locate the bindings file"*. Install with the build allow-listed instead: `npm install -g wyrm-mcp --allow-scripts=wyrm-mcp,better-sqlite3` (`wyrm update` already does this for you). See [TROUBLESHOOTING.md](https://github.com/Ghosts-Protocol-Pvt-Ltd/wyrm-mcp/blob/HEAD/TROUBLESHOOTING.md).
39
41
 
40
42
  Then, from inside your client, ask it to call `wyrm_capabilities` to confirm the connection. The everyday loop is four steps the agent runs on its own once the habit sets in:
@@ -0,0 +1 @@
1
+ import{randomBytes as y}from"node:crypto";import{closeSync as p,existsSync as S,mkdirSync as h,openSync as A,readFileSync as f,renameSync as x,statSync as O,unlinkSync as m,writeFileSync as w,writeSync as k}from"node:fs";import{homedir as g}from"node:os";import{dirname as N,join as M}from"node:path";const $=7,_=6e3,E=5e3;function C(){return M(g(),".wyrm","auto-runs.json")}const R="0123456789ABCDEFGHJKMNPQRSTVWXYZ";function T(e){if(typeof e!="string"||!/^[0-9A-HJKMNP-TV-Z]{26}$/.test(e))return null;let r=0;for(let t=0;t<10;t++)r=r*32+R.indexOf(e[t]);return r}function d(e,r){const t={},c=[];for(const[o,u]of Object.entries(e)){const n=u;if(!n||typeof n!="object"||typeof n.run_id!="string"||n.run_id===""){c.push({session:o,runId:null,reason:"invalid"});continue}const i=n.run_id,a=r.runStatus?.(i);if(a!==void 0&&a!=="running"){c.push({session:o,runId:i,reason:"ended"});continue}const s=typeof n.t=="number"&&Number.isFinite(n.t)?n.t:T(i);if(s!==null&&r.now-s>r.maxAgeMs){c.push({session:o,runId:i,reason:"idle"});continue}t[o]=s===null?{...n,t:r.now}:n}return{next:t,removed:c}}function b(e){Atomics.wait(new Int32Array(new SharedArrayBuffer(4)),0,0,e)}function j(e,r){const t=`${e}.lock`,c=`${process.pid}:${y(8).toString("hex")}`,o=Date.now()+_;for(h(N(e),{recursive:!0});;){try{const n=A(t,"wx",384);try{k(n,c)}finally{p(n)}break}catch(n){if(n.code!=="EEXIST")return!1}let u=!1;try{u=Date.now()-O(t).mtimeMs>E}catch(n){if(n.code==="ENOENT")continue}if(u)try{m(t);continue}catch{}if(Date.now()>=o)return!1;b(10+Math.floor(Math.random()*30))}try{return r(),!0}finally{try{f(t,"utf8")===c&&m(t)}catch{}}}function l(e){let r;try{r=f(e,"utf8")}catch(t){if(t.code==="ENOENT")return{outcome:"missing"};throw t}try{const t=JSON.parse(r);return!t||typeof t!="object"||Array.isArray(t)?{outcome:"corrupt"}:{store:t}}catch{return{outcome:"corrupt"}}}function I(e,r){if(!S(e))return{outcome:"missing"};if(r.dryRun){const o=l(e);return"outcome"in o?o:{outcome:"ok",total:Object.keys(o.store).length,removed:d(o.store,r).removed}}let t={outcome:"locked"};return j(e,()=>{const o=l(e);if("outcome"in o){t=o;return}const{next:u,removed:n}=d(o.store,r);if(JSON.stringify(u)!==JSON.stringify(o.store)){const i=`${e}.tmp.${process.pid}`;w(i,JSON.stringify(u),{mode:384}),x(i,e)}t={outcome:"ok",total:Object.keys(o.store).length,removed:n}})?t:{outcome:"locked"}}export{$ as AUTO_RUNS_MAX_AGE_DAYS,E as LOCK_STALE_MS,_ as LOCK_WAIT_MS,C as autoRunsPath,d as planAutoRunsPrune,I as pruneAutoRunsFile,T as ulidTimeMs,j as withAutoRunsLock};
@@ -1,10 +1,10 @@
1
- import{existsSync as f,readFileSync as y,writeFileSync as p,mkdirSync as P,copyFileSync as X,chmodSync as C}from"fs";import{homedir as h,platform as b}from"os";import{join as a,dirname as v,resolve as ee}from"path";import{fileURLToPath as _}from"url";import{spawnSync as w}from"child_process";function te(){const e=h(),t=b(),o=t==="darwin"?a(e,"Library","Application Support","Code","User"):t==="win32"?a(e,"AppData","Roaming","Code","User"):a(e,".config","Code","User"),n=t==="darwin"?a(e,"Library","Application Support","Code - Insiders","User"):t==="win32"?a(e,"AppData","Roaming","Code - Insiders","User"):a(e,".config","Code - Insiders","User"),s=t==="darwin"?a(e,"Library","Application Support","Claude"):t==="win32"?a(e,"AppData","Roaming","Claude"):a(e,".config","claude"),r=t==="darwin"?a(e,".cursor"):t==="win32"?a(e,".cursor"):a(e,".cursor"),i=t==="darwin"?a(e,".codeium","windsurf"):t==="win32"?a(e,".codeium","windsurf"):a(e,".codeium","windsurf"),c=t==="darwin"?a(e,".config","zed"):t==="win32"?a(e,"AppData","Roaming","Zed"):a(e,".config","zed"),u=a(e,".continue");return[{id:"vscode-copilot",name:"VS Code (Copilot)",icon:"\u{1F4BB}",configPath:a(o,"settings.json"),configFormat:"vscode",mcpKey:"mcp.servers",detected:!1,configured:!1},{id:"vscode-insiders",name:"VS Code Insiders",icon:"\u{1F7E2}",configPath:a(n,"settings.json"),configFormat:"vscode",mcpKey:"mcp.servers",detected:!1,configured:!1},{id:"claude-desktop",name:"Claude Desktop",icon:"\u{1F916}",configPath:a(s,"claude_desktop_config.json"),configFormat:"mcp-json",mcpKey:"mcpServers",detected:!1,configured:!1},{id:"claude-code",name:"Claude Code",icon:"\u2733\uFE0F",configPath:a(e,".claude.json"),configFormat:"mcp-json",mcpKey:"mcpServers",detected:!1,configured:!1},{id:"cursor",name:"Cursor",icon:"\u{1F4D0}",configPath:a(r,"mcp.json"),configFormat:"mcp-json",mcpKey:"mcpServers",detected:!1,configured:!1},{id:"windsurf",name:"Windsurf",icon:"\u{1F3C4}",configPath:a(i,"mcp_config.json"),configFormat:"mcp-json",mcpKey:"mcpServers",detected:!1,configured:!1},{id:"zed",name:"Zed",icon:"\u26A1",configPath:a(c,"settings.json"),configFormat:"zed",mcpKey:"context_servers",detected:!1,configured:!1},{id:"continue",name:"Continue",icon:"\u{1F504}",configPath:a(u,"config.json"),configFormat:"mcp-json",mcpKey:"mcpServers",detected:!1,configured:!1}]}function oe(e){try{const t=b()==="win32"?"where":"which",o=w(t,[e],{encoding:"utf-8",timeout:5e3});return o.status===0&&!!o.stdout&&o.stdout.trim().length>0}catch{return!1}}function A(){const e=te();for(const t of e){if(t.id==="claude-code")t.detected=f(t.configPath)||oe("claude");else{const o=v(t.configPath);t.detected=f(o)}if(t.detected&&f(t.configPath))try{const o=y(t.configPath,"utf-8"),n=$(o);t.configured=re(n,t)}catch{t.configured=!1}t.detected&&(t.version=ne(t))}return e}function ne(e){try{switch(e.id){case"vscode-copilot":case"vscode-insiders":{const t=e.id==="vscode-insiders"?"code-insiders":"code",o=w(t,["--version"],{encoding:"utf-8",timeout:5e3});if(o.stdout)return o.stdout.split(`
1
+ import{existsSync as f,readFileSync as y,writeFileSync as p,mkdirSync as P,copyFileSync as X,chmodSync as C}from"fs";import{homedir as h,platform as b}from"os";import{join as a,dirname as v,resolve as ee}from"path";import{fileURLToPath as _}from"url";import{spawnSync as w}from"child_process";function te(){const e=h(),t=b(),o=t==="darwin"?a(e,"Library","Application Support","Code","User"):t==="win32"?a(e,"AppData","Roaming","Code","User"):a(e,".config","Code","User"),n=t==="darwin"?a(e,"Library","Application Support","Code - Insiders","User"):t==="win32"?a(e,"AppData","Roaming","Code - Insiders","User"):a(e,".config","Code - Insiders","User"),s=t==="darwin"?a(e,"Library","Application Support","Claude"):t==="win32"?a(e,"AppData","Roaming","Claude"):a(e,".config","claude"),r=t==="darwin"?a(e,".cursor"):t==="win32"?a(e,".cursor"):a(e,".cursor"),i=t==="darwin"?a(e,".codeium","windsurf"):t==="win32"?a(e,".codeium","windsurf"):a(e,".codeium","windsurf"),c=t==="darwin"?a(e,".config","zed"):t==="win32"?a(e,"AppData","Roaming","Zed"):a(e,".config","zed"),u=a(e,".continue");return[{id:"vscode-copilot",name:"VS Code (Copilot)",icon:"\u{1F4BB}",configPath:a(o,"settings.json"),configFormat:"vscode",mcpKey:"mcp.servers",detected:!1,configured:!1},{id:"vscode-insiders",name:"VS Code Insiders",icon:"\u{1F7E2}",configPath:a(n,"settings.json"),configFormat:"vscode",mcpKey:"mcp.servers",detected:!1,configured:!1},{id:"claude-desktop",name:"Claude Desktop",icon:"\u{1F916}",configPath:a(s,"claude_desktop_config.json"),configFormat:"mcp-json",mcpKey:"mcpServers",detected:!1,configured:!1},{id:"claude-code",name:"Claude Code",icon:"\u2733\uFE0F",configPath:a(e,".claude.json"),configFormat:"mcp-json",mcpKey:"mcpServers",detected:!1,configured:!1},{id:"cursor",name:"Cursor",icon:"\u{1F4D0}",configPath:a(r,"mcp.json"),configFormat:"mcp-json",mcpKey:"mcpServers",detected:!1,configured:!1},{id:"windsurf",name:"Windsurf",icon:"\u{1F3C4}",configPath:a(i,"mcp_config.json"),configFormat:"mcp-json",mcpKey:"mcpServers",detected:!1,configured:!1},{id:"zed",name:"Zed",icon:"\u26A1",configPath:a(c,"settings.json"),configFormat:"zed",mcpKey:"context_servers",detected:!1,configured:!1},{id:"continue",name:"Continue",icon:"\u{1F504}",configPath:a(u,"config.json"),configFormat:"mcp-json",mcpKey:"mcpServers",detected:!1,configured:!1}]}function oe(e){try{const t=b()==="win32"?"where":"which",o=w(t,[e],{encoding:"utf-8",timeout:5e3});return o.status===0&&!!o.stdout&&o.stdout.trim().length>0}catch{return!1}}function A(){const e=te();for(const t of e){if(t.id==="claude-code")t.detected=f(a(v(t.configPath),".claude"))||oe("claude");else{const o=v(t.configPath);t.detected=f(o)}if(t.detected&&f(t.configPath))try{const o=y(t.configPath,"utf-8"),n=$(o);t.configured=re(n,t)}catch{t.configured=!1}t.detected&&(t.version=ne(t))}return e}function ne(e){try{switch(e.id){case"vscode-copilot":case"vscode-insiders":{const t=e.id==="vscode-insiders"?"code-insiders":"code",o=w(t,["--version"],{encoding:"utf-8",timeout:5e3});if(o.stdout)return o.stdout.split(`
2
2
  `)[0];break}case"cursor":{const t=w("cursor",["--version"],{encoding:"utf-8",timeout:5e3});if(t.stdout)return t.stdout.split(`
3
3
  `)[0];break}case"claude-code":{const t=w("claude",["--version"],{encoding:"utf-8",timeout:5e3});if(t.stdout)return t.stdout.trim().split(`
4
4
  `)[0];break}}}catch{}}function re(e,t){switch(t.configFormat){case"vscode":return e.mcp?.servers?.wyrm!==void 0;case"mcp-json":return e[t.mcpKey]?.wyrm!==void 0;case"zed":return e[t.mcpKey]?.wyrm!==void 0;default:return!1}}function M(){try{const n=w("which",["wyrm-mcp"],{encoding:"utf-8",timeout:5e3});if(n.stdout?.trim())return n.stdout.trim()}catch{}try{const s=w("npm",["root","-g"],{encoding:"utf-8",timeout:5e3}).stdout?.trim();if(s){const r=a(s,"wyrm-mcp","dist","index.js");if(f(r))return r}}catch{}const e=ee(__dirname,"index.js");if(f(e))return e;const t=a(h(),".wyrm","node_modules","wyrm-mcp","dist","index.js");if(f(t))return t;const o=[a(h(),"Git Projects","Wyrm","packages","mcp-server","dist","index.js"),a(h(),"projects","Wyrm","packages","mcp-server","dist","index.js"),a(h(),"dev","Wyrm","packages","mcp-server","dist","index.js")];for(const n of o)if(f(n))return n;return"wyrm-mcp"}function D(){return a(h(),".wyrm","wyrm.db")}function je(e="mcp-json",t){const o=t?.serverPath??M(),n=t?.dbPath??D(),s=o==="wyrm-mcp",r={command:s?"npx":"node",args:s?["wyrm-mcp"]:[o],env:{WYRM_DB_PATH:n}};switch(e){case"stdio":return r;case"vscode":return{mcp:{servers:{wyrm:r}}};default:return{mcpServers:{wyrm:r}}}}function se(e,t){const o=t.serverPath,n=o==="wyrm-mcp",s=o.endsWith(".js");switch(e.configFormat){case"vscode":return{command:n?"npx":"node",args:n?["wyrm-mcp"]:[o],env:{WYRM_DB_PATH:t.dbPath}};case"mcp-json":return{command:n?"npx":"node",args:n?["wyrm-mcp"]:[o],env:{WYRM_DB_PATH:t.dbPath}};case"zed":return{command:n?"npx":"node",args:n?["wyrm-mcp"]:[o],env:{WYRM_DB_PATH:t.dbPath}};default:return{}}}function ie(e,t){if(!e||typeof e!="object"||Array.isArray(e))return{entry:t,preservedLauncher:!1};const o=e,n=o.env&&typeof o.env=="object"&&!Array.isArray(o.env)?o.env:{},s=t.env&&typeof t.env=="object"&&!Array.isArray(t.env)?t.env:{};if(o.command===t.command&&JSON.stringify(o.args??null)===JSON.stringify(t.args??null))return{entry:{...t,env:{...s,...n}},preservedLauncher:!1};const i={...n};for(const[c,u]of Object.entries(s))c in i||(i[c]=u);return{entry:{...o,env:i},preservedLauncher:!0}}function H(e,t){if(!e.detected)return{client:e,action:"skipped",message:`${e.name} not detected`};try{const o=v(e.configPath);f(o)||P(o,{recursive:!0});let n={},s=!1;if(f(e.configPath))try{const d=y(e.configPath,"utf-8");n=$(d),s=!0}catch{n={}}let r;if(s){r=`${e.configPath}.wyrm-backup`,p(r,y(e.configPath),{mode:384});try{C(r,384)}catch{}}const i=se(e,t);let c=!1;const u=(d,l)=>{const g=ie(d.wyrm,l);return d.wyrm=g.entry,g.preservedLauncher};switch(e.configFormat){case"vscode":{n.mcp||(n.mcp={});const d=n.mcp;d.servers||(d.servers={});const l=d.servers;c=u(l,i)||c;break}case"mcp-json":{n[e.mcpKey]||(n[e.mcpKey]={});const d=n[e.mcpKey];c=u(d,i)||c;break}case"zed":{n[e.mcpKey]||(n[e.mcpKey]={});const d=n[e.mcpKey];c=u(d,i)||c;break}}p(e.configPath,JSON.stringify(n,null,2)+`
5
5
  `,"utf-8");const m=e.configured?"updated":"configured";return e.configured=!0,{client:e,action:m,message:`${e.name} ${m==="configured"?"configured":"updated"} successfully`+(c?" (kept your custom launch command; only missing env was added)":""),backup:r}}catch(o){return{client:e,action:"failed",message:`Failed to configure ${e.name}: ${o}`}}}function Ce(e,t){try{if(!f(e.configPath))return{client:e,action:"skipped",message:"No config file"};const o=y(e.configPath,"utf-8");let n;try{n=$(o)}catch{return{client:e,action:"failed",message:"Config file is not valid JSON; refusing to touch it"}}const r=(e.configFormat==="vscode"?n.mcp?.servers:n[e.mcpKey])?.wyrm;if(!r)return{client:e,action:"skipped",message:"Wyrm not configured in this client"};const i=`${e.configPath}.wyrm-backup`;p(i,o,{encoding:"utf-8",mode:384});try{C(i,384)}catch{}return r.env={...r.env,...t},p(e.configPath,JSON.stringify(n,null,2)+`
6
6
  `,"utf-8"),{client:e,action:"updated",message:`env: ${Object.keys(t).join(", ")}`,backup:i}}catch(o){return{client:e,action:"failed",message:String(o)}}}function ce(e){if(!e.detected||!e.configured)return{client:e,action:"skipped",message:`${e.name} not configured`};try{if(!f(e.configPath))return{client:e,action:"skipped",message:`${e.name} config not found`};const t=y(e.configPath,"utf-8"),o=$(t),n=`${e.configPath}.wyrm-backup`;p(n,t,{mode:384});try{C(n,384)}catch{}switch(e.configFormat){case"vscode":{const r=o.mcp?.servers;r&&delete r.wyrm;break}case"mcp-json":case"zed":{const s=o[e.mcpKey];s&&delete s.wyrm;break}}return p(e.configPath,JSON.stringify(o,null,2)+`
7
- `,"utf-8"),e.configured=!1,{client:e,action:"configured",message:`Removed Wyrm from ${e.name}`,backup:n}}catch(t){return{client:e,action:"failed",message:`Failed to remove from ${e.name}: ${t}`}}}function O(){return{id:"claude-code-hooks",name:"Claude Code (auto-memory)",icon:"\u{1FA9D}",configPath:a(h(),".claude","settings.json"),configFormat:"mcp-json",mcpKey:"",detected:!0,configured:!1}}const J=[{script:"wyrm-session-capture.mjs",events:["SessionEnd","PreCompact"]},{script:"wyrm-session-rehydrate.mjs",events:["SessionStart"]},{script:"wyrm-session-prune.mjs",events:["SessionEnd"]},{script:"wyrm-tool-call-trace.mjs",events:["PostToolUse"]},{script:"wyrm-run-auto.mjs",events:["SessionStart",{event:"PreToolUse",matcher:"Task"},"SessionEnd"]},{script:"wyrm-citation-audit.mjs",events:["Stop"]}];function ae(){const e=a(h(),".claude");if(!f(e))return null;const t=O();try{const o=a(e,"hooks");f(o)||P(o,{recursive:!0});const n=v(_(import.meta.url)),s=a(n,"..","scripts","hooks"),r=t.configPath;let i={};if(f(r))try{i=JSON.parse(y(r,"utf-8"))}catch{i={}}const c=i.hooks??{};let u=!1;const m=[];for(const l of J){const g=a(s,l.script);if(!f(g)){m.push(l.script);continue}const N=a(o,l.script);X(g,N);try{C(N,493)}catch{}const T=`node ${N}`;for(const S of l.events){const W=typeof S=="string"?S:S.event,B=typeof S=="string"?void 0:S.matcher,x=Array.isArray(c[W])?c[W]:[];if(!x.some(j=>Array.isArray(j.hooks)&&j.hooks.some(Z=>Z.command===T))){const j=B?{matcher:B,hooks:[{type:"command",command:T,timeout:30}]}:{hooks:[{type:"command",command:T,timeout:30}]};x.push(j),u=!0}c[W]=x}}i.hooks=c,p(r,JSON.stringify(i,null,2)+`
7
+ `,"utf-8"),e.configured=!1,{client:e,action:"configured",message:`Removed Wyrm from ${e.name}`,backup:n}}catch(t){return{client:e,action:"failed",message:`Failed to remove from ${e.name}: ${t}`}}}function O(){return{id:"claude-code-hooks",name:"Claude Code (auto-memory)",icon:"\u{1FA9D}",configPath:a(h(),".claude","settings.json"),configFormat:"mcp-json",mcpKey:"",detected:!0,configured:!1}}const J=[{script:"wyrm-session-capture.mjs",events:["SessionEnd","PreCompact"]},{script:"wyrm-session-rehydrate.mjs",events:["SessionStart"]},{script:"wyrm-session-prune.mjs",events:["SessionEnd"]},{script:"wyrm-tool-call-trace.mjs",events:["PostToolUse"]},{script:"wyrm-run-auto.mjs",events:["SessionStart",{event:"PreToolUse",matcher:"Task"},"SessionEnd"]},{script:"wyrm-citation-audit.mjs",events:["Stop"]},{script:"wyrm-prompt-context.mjs",events:["UserPromptSubmit"]}];function ae(){const e=a(h(),".claude");if(!f(e))return null;const t=O();try{const o=a(e,"hooks");f(o)||P(o,{recursive:!0});const n=v(_(import.meta.url)),s=a(n,"..","scripts","hooks"),r=t.configPath;let i={};if(f(r))try{i=JSON.parse(y(r,"utf-8"))}catch{i={}}const c=i.hooks??{};let u=!1;const m=[];for(const l of J){const g=a(s,l.script);if(!f(g)){m.push(l.script);continue}const N=a(o,l.script);X(g,N);try{C(N,493)}catch{}const T=`node ${N}`;for(const S of l.events){const W=typeof S=="string"?S:S.event,B=typeof S=="string"?void 0:S.matcher,x=Array.isArray(c[W])?c[W]:[];if(!x.some(j=>Array.isArray(j.hooks)&&j.hooks.some(Z=>Z.command===T))){const j=B?{matcher:B,hooks:[{type:"command",command:T,timeout:30}]}:{hooks:[{type:"command",command:T,timeout:30}]};x.push(j),u=!0}c[W]=x}}i.hooks=c,p(r,JSON.stringify(i,null,2)+`
8
8
  `,"utf-8"),t.configured=!0;const d=m.length?` (missing from package: ${m.join(", ")})`:"";return{client:t,action:u?"configured":"skipped",message:(u?"auto-memory hooks installed":"already installed")+d}}catch(o){return{client:t,action:"failed",message:o instanceof Error?o.message:String(o)}}}function _e(){const e=a(h(),".claude");if(!f(e))return null;const t=O();try{const o=v(_(import.meta.url)),n=`node ${a(o,"wyrm-statusline.js")}`,s=t.configPath;let r={};if(f(s))try{r=JSON.parse(y(s,"utf-8"))}catch{r={}}const i=r.statusLine;if(i?.command&&!/wyrm-statusline/.test(i.command))return{client:t,action:"skipped",message:`a non-Wyrm statusLine is already set (${i.command}); not overwriting`};const c=i?.command===n;return r.statusLine={type:"command",command:n,padding:0},p(s,JSON.stringify(r,null,2)+`
9
9
  `,"utf-8"),{client:t,action:c?"skipped":"configured",message:c?"buddy statusline already installed":"Wyrm buddy statusline installed \u2014 persistent in the Claude Code TUI"}}catch(o){return{client:t,action:"failed",message:o instanceof Error?o.message:String(o)}}}function Oe(){const e=a(h(),".claude","settings.json");if(!f(e))return null;const t=O();try{const o=JSON.parse(y(e,"utf-8")),n=o.statusLine;return n?.command&&/wyrm-statusline/.test(n.command)?(delete o.statusLine,p(e,JSON.stringify(o,null,2)+`
10
10
  `,"utf-8"),{client:t,action:"configured",message:"Wyrm statusline removed"}):{client:t,action:"skipped",message:"no Wyrm statusline set"}}catch(o){return{client:t,action:"failed",message:o instanceof Error?o.message:String(o)}}}function ue(){const e=a(h(),".claude","settings.json");if(!f(e))return null;const t=O();try{const o=JSON.parse(y(e,"utf-8")),n=a(h(),".claude","hooks"),s=new Set(J.map(c=>`node ${a(n,c.script)}`)),r=new Set(J.flatMap(c=>c.events.map(u=>typeof u=="string"?u:u.event))),i=o.hooks;if(i){for(const c of r)Array.isArray(i[c])&&(i[c]=i[c].map(u=>({...u,hooks:Array.isArray(u.hooks)?u.hooks.filter(m=>!s.has(m.command??"")):u.hooks})).filter(u=>!Array.isArray(u.hooks)||u.hooks.length>0),i[c].length===0&&delete i[c]);Object.keys(i).length===0&&delete o.hooks}return p(e,JSON.stringify(o,null,2)+`
@@ -14,7 +14,7 @@ import{existsSync as f,readFileSync as y,writeFileSync as p,mkdirSync as P,copyF
14
14
  `,"utf-8"),{client:o,action:"configured",message:"failure-firewall hooks removed"}):{client:o,action:"skipped",message:"no wyrm-guard hooks installed"}}catch(n){return{client:o,action:"failed",message:n instanceof Error?n.message:String(n)}}}function Ee(e={}){const t=k(e),o=[];try{const s=JSON.parse(y(t,"utf-8")).hooks??{};for(const r of Object.values(s))if(Array.isArray(r))for(const i of r)for(const c of i?.hooks??[]){const u=c?.command;u&&I.test(u)&&o.push(u)}}catch{}return{settingsPath:t,installed:o.length>0,commands:o}}const L=/wyrm-memo/,pe=[{event:"PreToolUse",matcher:"Read"},{event:"PreToolUse",matcher:"Bash"},{event:"PostToolUse",matcher:"Read"},{event:"PostToolUse",matcher:"Bash"}];function Y(e){return{id:"claude-code-memo",name:"Claude Code (token firewall)",icon:"",configPath:e,configFormat:"mcp-json",mcpKey:"",detected:!0,configured:!1}}function he(){const e=v(_(import.meta.url)),t=a(e,"wyrm-memo.js");if(f(t))return`node ${E(t)}`;try{const o=b()==="win32"?"where":"which",s=w(o,["wyrm-memo"],{encoding:"utf-8",timeout:5e3}).stdout?.trim().split(`
15
15
  `)[0]?.trim();if(s)return E(s)}catch{}return null}function ge(e={}){const t=k(e),o=!!(e.settingsPath??process.env.CLAUDE_SETTINGS),n=Y(t);if(!o&&!f(a(h(),".claude")))return{client:n,action:"skipped",message:"token firewall: ~/.claude not found on this machine (needs the Claude Code CLI). Not armed. Install Claude Code then re-run `wyrm memo`"};try{const s=e.memoCommand??he();if(!s)return{client:n,action:"skipped",message:"wyrm-memo bin not found (npm i -g wyrm-mcp, or build dist/). Token-firewall hooks not installed"};P(v(t),{recursive:!0});let r={};if(f(t))try{const d=JSON.parse(y(t,"utf-8"));if(typeof d!="object"||d===null||Array.isArray(d))throw new Error("not an object");r=d}catch{return{client:n,action:"failed",message:`${t} is not valid JSON \u2014 refusing to overwrite it (fix or remove the file, then re-run)`}}const i=r.hooks;if(i!==void 0&&(typeof i!="object"||i===null||Array.isArray(i)))return{client:n,action:"failed",message:`${t} "hooks" has an unexpected shape (not a plain object) \u2014 refusing to modify`};for(const d of["PreToolUse","PostToolUse"]){const l=i?.[d];if(l!==void 0&&!Array.isArray(l))return{client:n,action:"failed",message:`${t} hooks.${d} has an unexpected shape (not an array) \u2014 refusing to modify`}}const c=JSON.stringify(r);U(r,L);const u=r.hooks??{};for(const{event:d,matcher:l}of pe){const g=Array.isArray(u[d])?u[d]:[];g.push({matcher:l,hooks:[{type:"command",command:s,timeout:G}]}),u[d]=g}r.hooks=u;const m=JSON.stringify(r)!==c;return m&&p(t,JSON.stringify(r,null,2)+`
16
16
  `,"utf-8"),n.configured=!0,{client:n,action:m?"configured":"skipped",message:m?"token-firewall hooks installed (PreToolUse + PostToolUse: Read, Bash). Mode: observe (set WYRM_MEMO_MODE=warn to hand maps back)":"token-firewall hooks already installed"}}catch(s){return{client:n,action:"failed",message:s instanceof Error?s.message:String(s)}}}function $e(e={}){const t=k(e);if(!f(t))return null;const o=Y(t);try{let n;try{const r=JSON.parse(y(t,"utf-8"));if(typeof r!="object"||r===null||Array.isArray(r))throw new Error("not an object");n=r}catch{return{client:o,action:"failed",message:`${t} is not valid JSON \u2014 not touching it`}}return U(n,L)?(p(t,JSON.stringify(n,null,2)+`
17
- `,"utf-8"),{client:o,action:"configured",message:"token-firewall hooks removed"}):{client:o,action:"skipped",message:"no wyrm-memo hooks installed"}}catch(n){return{client:o,action:"failed",message:n instanceof Error?n.message:String(n)}}}function Ne(e={}){const t=k(e),o=[];try{const s=JSON.parse(y(t,"utf-8")).hooks??{};for(const r of Object.values(s))if(Array.isArray(r))for(const i of r)for(const c of i?.hooks??[]){const u=c?.command;u&&L.test(u)&&o.push(u)}}catch{}return{settingsPath:t,installed:o.length>0,commands:o}}function ye(e){const t={serverPath:e?.serverPath||M(),dbPath:e?.dbPath||D(),httpPort:e?.httpPort},o=a(h(),".wyrm");f(o)||P(o,{recursive:!0}),q(t);const n=A(),s=[];for(const u of n)s.push(H(u,t));const r=ae();r&&s.push(r);const i=me();i&&s.push(i);const c=ge();return c&&s.push(c),s}function Te(){const e=A(),t=[];for(const s of e)t.push(ce(s));const o=ue();o&&t.push(o);const n=le();return n&&t.push(n),t}function ve(e,t){const o={serverPath:t?.serverPath||M(),dbPath:t?.dbPath||D(),httpPort:t?.httpPort};q(o);const n=A(),s=[];for(const r of n)e.includes(r.id)&&s.push(H(r,o));return s}function q(e){const t=a(h(),".wyrm","wyrm-config.json"),o=A().filter(s=>s.configured).map(s=>s.id),n={version:"3.0.0",serverPath:e.serverPath,dbPath:e.dbPath,httpPort:e.httpPort,configuredClients:o,lastSetup:new Date().toISOString(),autoUpdate:!0};p(t,JSON.stringify(n,null,2)+`
17
+ `,"utf-8"),{client:o,action:"configured",message:"token-firewall hooks removed"}):{client:o,action:"skipped",message:"no wyrm-memo hooks installed"}}catch(n){return{client:o,action:"failed",message:n instanceof Error?n.message:String(n)}}}function Ne(e={}){const t=k(e),o=[];try{const s=JSON.parse(y(t,"utf-8")).hooks??{};for(const r of Object.values(s))if(Array.isArray(r))for(const i of r)for(const c of i?.hooks??[]){const u=c?.command;u&&L.test(u)&&o.push(u)}}catch{}return{settingsPath:t,installed:o.length>0,commands:o}}function ye(e){const t={serverPath:e?.serverPath||M(),dbPath:e?.dbPath||D(),httpPort:e?.httpPort},o=a(h(),".wyrm");f(o)||P(o,{recursive:!0});const n=A(),s=[];for(const u of n)s.push(H(u,t));q(t);const r=ae();r&&s.push(r);const i=me();i&&s.push(i);const c=ge();return c&&s.push(c),s}function Te(){const e=A(),t=[];for(const s of e)t.push(ce(s));const o=ue();o&&t.push(o);const n=le();return n&&t.push(n),t}function ve(e,t){const o={serverPath:t?.serverPath||M(),dbPath:t?.dbPath||D(),httpPort:t?.httpPort},n=A(),s=[];for(const r of n)e.includes(r.id)&&s.push(H(r,o));return q(o),s}function q(e){const t=a(h(),".wyrm","wyrm-config.json"),o=A().filter(s=>s.configured).map(s=>s.id),n={version:"3.0.0",serverPath:e.serverPath,dbPath:e.dbPath,httpPort:e.httpPort,configuredClients:o,lastSetup:new Date().toISOString(),autoUpdate:!0};p(t,JSON.stringify(n,null,2)+`
18
18
  `,"utf-8")}function V(){const e=a(h(),".wyrm","wyrm-config.json");if(!f(e))return null;try{return JSON.parse(y(e,"utf-8"))}catch{return null}}function We(){const e=V();return e?ve(e.configuredClients,{serverPath:e.serverPath,dbPath:e.dbPath,httpPort:e.httpPort}):ye()}const Q="<!-- wyrm:start -->",K="<!-- wyrm:end -->",F=`${Q}
19
19
  ## Wyrm Memory + Agent (auto-managed \u2014 do not edit this section)
20
20
 
@@ -1,4 +1,4 @@
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
1
+ import{homedir as c}from"os";import{join as n,dirname as m}from"path";import{readFileSync as h,existsSync as d}from"fs";import{resolveEmbeddingState as u}from"./providers/embedding-provider.js";import{fileURLToPath as y}from"url";const p=y(import.meta.url),l=m(p),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.`,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};
14
+ this requires you to leave your laptop.`,f=[{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"],cli:["wyrm failure list","wyrm failure resolve <id>"]},{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_trace action=upstream|downstream|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_capture mode=checklist","wyrm_context_build"]},{name:"Lossless session rehydration",what:"wyrm_session action=rehydrate produces a complete briefing markdown a fresh AI ingests to inherit prior state: 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 action=rehydrate","wyrm_session_prime","wyrm_session action=start|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_capture mode=session","wyrm_review","wyrm_capture mode=direct","wyrm_recall","wyrm_capture mode=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 action=add|link|search|graph|path|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_search type=symbols","wyrm_capture mode=artifacts includeCode=true"]},{name:"OODA agent loop (autonomous goals)",what:"wyrm_goal action=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 action=set|list|pause|resume|complete|abandon|iterations","wyrm_act"],cli:["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 action=register|list|disable|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"],cli:["wyrm index 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 action=announce|list|release","wyrm_quest action=claim|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_replication action=unshare|conflicts|resolve","wyrm_project action=sync"],cli:["wyrm sync export --out <path>","wyrm sync import --from <path>"]},{name:"Compliance audit trail",what:"Hash-chained audit log with optional Ed25519 signatures. wyrm_audit action=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 action=log|export|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:[],cli:["wyrm hours report","wyrm invoice generate"]},{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_stats"]}];function g(){try{const e=[n(l,"..","package.json"),n(l,"..","..","package.json")];for(const t of e)if(d(t))return JSON.parse(h(t,"utf-8")).version??"unknown"}catch{}return"unknown"}function C(e,t,o,a){const s=process.env.WYRM_DB_PATH??n(c(),".wyrm","wyrm.db"),r=o.isAgentRunning(),i=u();return{product:"Wyrm",version:g(),pitch:w,install:{dbPath:s,nodeVersion:process.version,platform:`${process.platform}-${process.arch}`},runtime:{vectorProvider:o.detectVectorProvider?o.detectVectorProvider():i.resolved,vectorReason:i.reason,vectorFix:i.fix,encryption:o.hasEncryption(),federation:o.isFederationEnabled(),agentLoop:r===null?"unknown":r?"running":"stopped"},features:f,toolCount:t,...a!==void 0?{fullToolCount:a}:{}}}function T(e){const t=[];t.push(`# ${e.product} ${e.version} \u2014 Capability Briefing`),t.push(""),t.push(e.pitch),t.push("");const o=e.runtime.vectorProvider==="none"?"vectors OFF (FTS-only recall)":`${e.runtime.vectorProvider} embeddings`,a=e.fullToolCount!==void 0&&e.fullToolCount!==e.toolCount?`${e.toolCount} MCP tools (${e.fullToolCount} in the full profile)`:`${e.toolCount} MCP tools`;t.push(`**Runtime:** ${a} \xB7 ${o} \xB7 ${e.runtime.encryption?"encryption ON":"encryption off"} \xB7 agent ${e.runtime.agentLoop}`),e.runtime.vectorReason&&(t.push(""),e.runtime.vectorProvider==="none"?t.push(`> WARNING: semantic recall degraded (${e.runtime.vectorReason}) - recall is FTS5 keyword-only. Fix: ${e.runtime.vectorFix}`):t.push(`> NOTE: embedding provider is advisory (${e.runtime.vectorReason}) - semantic recall is running. Fix: ${e.runtime.vectorFix}`)),t.push(`**Install:** ${e.install.platform} \xB7 Node ${e.install.nodeVersion} \xB7 DB ${e.install.dbPath}`),t.push(""),t.push('## What Wyrm gives you beyond "AI memory"'),t.push("");for(const s of e.features)t.push(`### ${s.name}`),t.push(`- **What:** ${s.what}`),t.push(`- **Why:** ${s.why}`),s.tools.length>0&&t.push(`- **Tools:** \`${s.tools.join("`, `")}\``),s.cli&&s.cli.length>0&&t.push(`- **CLI:** \`${s.cli.join("`, `")}\``),t.push("");return t.join(`
15
+ `)}export{C as getCapabilities,T as renderCapabilityBriefing};
package/dist/causality.js CHANGED
@@ -1,29 +1,34 @@
1
- import{emitEvent as f}from"./events.js";import{getActor as E}from"./handlers/boundary.js";function g(c){if(c.length===0)return"";const e=c.map(r=>`${r.to_kind} #${r.to_id} (${r.reason})`).join(", "),t=c.length===1;return`[STALE FOUNDATION] this decision rests on ${t?"a foundation that has":`${c.length} foundations that have`} since been ${t?c[0].reason:"invalidated"} -- ${e}. Re-evaluate before relying on it (wyrm_decision_invalidate to cascade, or re-decide).`}class p{db;constructor(e){this.db=e}link(e){const t=E(),r=this.db.prepare(`
1
+ import{emitEvent as f}from"./events.js";import{getActor as E}from"./handlers/boundary.js";function g(c){if(c.length===0)return"";const e=c.map(t=>`${t.to_kind} #${t.to_id} (${t.reason})`).join(", "),i=c.length===1;return`[STALE FOUNDATION] this decision rests on ${i?"a foundation that has":`${c.length} foundations that have`} since been ${i?c[0].reason:"invalidated"} -- ${e}. Re-evaluate before relying on it (wyrm_decision_invalidate to cascade, or re-decide).`}class k{db;constructor(e){this.db=e}link(e){return this.linkOnce(e).edge}linkOnce(e){const i=e.relation??"because_of",t=this.db.prepare(`
2
+ SELECT * FROM decision_edges
3
+ WHERE project_id = ? AND from_kind = ? AND from_id = ? AND to_kind = ? AND to_id = ?
4
+ AND relation = ? AND invalidated_at IS NULL
5
+ ORDER BY id LIMIT 1
6
+ `).get(e.project_id,e.from_kind,e.from_id,e.to_kind,e.to_id,i);return t?{edge:t,created:!1}:{edge:this.insertEdge(e),created:!0}}insertEdge(e){const i=E(),t=this.db.prepare(`
2
7
  INSERT INTO decision_edges
3
8
  (project_id, from_kind, from_id, to_kind, to_id, relation, rationale, confidence, agent_id, run_id)
4
9
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
5
- `).run(e.project_id,e.from_kind,e.from_id,e.to_kind,e.to_id,e.relation??"because_of",e.rationale??null,e.confidence??1,t.agent_id,t.run_id),n=this.get(r.lastInsertRowid);return f(this.db,{projectId:e.project_id,kind:"decision",refTable:"decision_edges",refId:n.id,isShared:!!n.is_shared}),n}get(e){return this.db.prepare("SELECT * FROM decision_edges WHERE id = ?").get(e)??null}downstreamOf(e,t){return this.db.prepare(`
10
+ `).run(e.project_id,e.from_kind,e.from_id,e.to_kind,e.to_id,e.relation??"because_of",e.rationale??null,e.confidence??1,i.agent_id,i.run_id),o=this.get(t.lastInsertRowid);return f(this.db,{projectId:e.project_id,kind:"decision",refTable:"decision_edges",refId:o.id,isShared:!!o.is_shared}),o}get(e){return this.db.prepare("SELECT * FROM decision_edges WHERE id = ?").get(e)??null}downstreamOf(e,i){return this.db.prepare(`
6
11
  SELECT * FROM decision_edges
7
12
  WHERE to_kind = ? AND to_id = ? AND invalidated_at IS NULL
8
13
  ORDER BY created_at DESC
9
- `).all(e,t)}upstreamOf(e,t){return this.db.prepare(`
14
+ `).all(e,i)}upstreamOf(e,i){return this.db.prepare(`
10
15
  SELECT * FROM decision_edges
11
16
  WHERE from_kind = ? AND from_id = ? AND invalidated_at IS NULL
12
17
  ORDER BY created_at DESC
13
- `).all(e,t)}staleFoundations(e,t){const r=this.db.prepare(`
18
+ `).all(e,i)}staleFoundations(e,i){const t=this.db.prepare(`
14
19
  SELECT to_kind, to_id FROM decision_edges
15
20
  WHERE from_kind = ? AND from_id = ? AND relation = 'because_of' AND invalidated_at IS NULL
16
- `).all(e,t),n=this.db.prepare("SELECT is_current FROM ground_truths WHERE id = ?"),s=this.db.prepare(`
21
+ `).all(e,i),o=this.db.prepare("SELECT is_current FROM ground_truths WHERE id = ?"),s=this.db.prepare(`
17
22
  SELECT 1 FROM decision_edges
18
23
  WHERE to_kind = ? AND to_id = ? AND relation = 'refutes' AND invalidated_at IS NULL LIMIT 1
19
- `),i=[];for(const o of r){let d=null;if(o.to_kind==="truth"){const a=n.get(o.to_id);a&&a.is_current===0&&(d="superseded")}!d&&s.get(o.to_kind,o.to_id)&&(d="refuted"),d&&i.push({to_kind:o.to_kind,to_id:o.to_id,reason:d})}return i}invalidateDownstream(e,t,r){const n=[],s=new Set,i=[{kind:e,id:t}],o=this.db.prepare(`
24
+ `),d=[];for(const r of t){let n=null;if(r.to_kind==="truth"){const a=o.get(r.to_id);a&&a.is_current===0&&(n="superseded")}!n&&s.get(r.to_kind,r.to_id)&&(n="refuted"),n&&d.push({to_kind:r.to_kind,to_id:r.to_id,reason:n})}return d}invalidateDownstream(e,i,t){const o=[],s=new Set,d=[{kind:e,id:i}],r=this.db.prepare(`
20
25
  UPDATE decision_edges
21
26
  SET invalidated_at = datetime('now'),
22
27
  rationale = COALESCE(?, rationale)
23
28
  WHERE id = ?
24
- `);for(;i.length>0;){const d=i.shift(),a=`${d.kind}:${d.id}`;if(s.has(a))continue;s.add(a);const l=this.downstreamOf(d.kind,d.id);for(const _ of l)o.run(r??null,_.id),n.push({kind:_.from_kind,id:_.from_id,edge_id:_.id,rationale:_.rationale}),i.push({kind:_.from_kind,id:_.from_id})}return{invalidated:n.length,affected:n}}pathTo(e,t,r=6){const n=new Set,s=[{node:e,path:[]}];for(;s.length>0;){const{node:i,path:o}=s.pop(),d=`${i.kind}:${i.id}`;if(!(n.has(d)||o.length>r)){if(n.add(d),i.kind===t.kind&&i.id===t.id)return o;for(const a of this.upstreamOf(i.kind,i.id))s.push({node:{kind:a.to_kind,id:a.to_id},path:[...o,a]})}}return[]}invalidatedInProject(e,t=50){return this.db.prepare(`
29
+ `);for(;d.length>0;){const n=d.shift(),a=`${n.kind}:${n.id}`;if(s.has(a))continue;s.add(a);const l=this.downstreamOf(n.kind,n.id);for(const _ of l)r.run(t??null,_.id),o.push({kind:_.from_kind,id:_.from_id,edge_id:_.id,rationale:_.rationale}),d.push({kind:_.from_kind,id:_.from_id})}return{invalidated:o.length,affected:o}}pathTo(e,i,t=6){const o=new Set,s=[{node:e,path:[]}];for(;s.length>0;){const{node:d,path:r}=s.pop(),n=`${d.kind}:${d.id}`;if(!(o.has(n)||r.length>t)){if(o.add(n),d.kind===i.kind&&d.id===i.id)return r;for(const a of this.upstreamOf(d.kind,d.id))s.push({node:{kind:a.to_kind,id:a.to_id},path:[...r,a]})}}return[]}invalidatedInProject(e,i=50){return this.db.prepare(`
25
30
  SELECT * FROM decision_edges
26
31
  WHERE project_id = ? AND invalidated_at IS NOT NULL
27
32
  ORDER BY invalidated_at DESC
28
33
  LIMIT ?
29
- `).all(e,t)}}export{p as Causality,g as staleFoundationWarning};
34
+ `).all(e,i)}}export{k as Causality,g as staleFoundationWarning};
package/dist/cli.js CHANGED
@@ -1,22 +1,25 @@
1
- import{readFileSync as b}from"fs";import{dirname as f,join as x}from"path";import{fileURLToPath as w}from"url";function g(){try{const e=f(w(import.meta.url));return JSON.parse(b(x(e,"..","package.json"),"utf-8")).version??"dev"}catch{return"dev"}}const t={reset:"\x1B[0m",bold:"\x1B[1m",dim:"\x1B[2m",italic:"\x1B[3m",underline:"\x1B[4m",black:"\x1B[30m",red:"\x1B[31m",green:"\x1B[32m",yellow:"\x1B[33m",blue:"\x1B[34m",magenta:"\x1B[35m",cyan:"\x1B[36m",white:"\x1B[37m",brightRed:"\x1B[91m",brightGreen:"\x1B[92m",brightYellow:"\x1B[93m",brightBlue:"\x1B[94m",brightMagenta:"\x1B[95m",brightCyan:"\x1B[96m",bgRed:"\x1B[41m",bgGreen:"\x1B[42m",bgYellow:"\x1B[43m",bgBlue:"\x1B[44m",bgMagenta:"\x1B[45m",bgCyan:"\x1B[46m"},l={bold:e=>`${t.bold}${e}${t.reset}`,dim:e=>`${t.dim}${e}${t.reset}`,red:e=>`${t.red}${e}${t.reset}`,green:e=>`${t.green}${e}${t.reset}`,yellow:e=>`${t.yellow}${e}${t.reset}`,blue:e=>`${t.blue}${e}${t.reset}`,magenta:e=>`${t.magenta}${e}${t.reset}`,cyan:e=>`${t.cyan}${e}${t.reset}`,primary:e=>`${t.brightMagenta}${e}${t.reset}`,success:e=>`${t.brightGreen}${e}${t.reset}`,warning:e=>`${t.brightYellow}${e}${t.reset}`,error:e=>`${t.brightRed}${e}${t.reset}`},m={dragon:"\u{F115D}",fire:"\u{1F525}",scroll:"\u{1F4DC}",sword:"\u2694\uFE0F",shield:"\u{1F6E1}\uFE0F",treasure:"\u{1F48E}",quest:"\u{1F3AF}",check:"\u2713",cross:"\u2717",arrow:"\u2192",bullet:"\u2022",star:"\u2605",warning:"\u26A0\uFE0F",error:"\u274C",success:"\u2705",info:"\u2139\uFE0F",clock:"\u{1F550}",folder:"\u{1F4C1}",file:"\u{1F4C4}",lock:"\u{1F512}",unlock:"\u{1F513}",sync:"\u{1F504}",search:"\u{1F50D}",sparkle:"\u2728"},R={critical:"\u{1F534}",high:"\u{1F7E0}",medium:"\u{1F7E1}",low:"\u{1F7E2}"};function v(){return`
2
- ${t.brightMagenta}\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557${t.reset}
3
- ${t.brightMagenta}\u2588\u2588\u2551 \u2588\u2588\u2551\u255A\u2588\u2588\u2557 \u2588\u2588\u2554\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2551${t.reset}
4
- ${t.brightMagenta}\u2588\u2588\u2551 \u2588\u2557 \u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2554\u2588\u2588\u2588\u2588\u2554\u2588\u2588\u2551${t.reset}
5
- ${t.brightMagenta}\u2588\u2588\u2551\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u255A\u2588\u2588\u2554\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551\u255A\u2588\u2588\u2554\u255D\u2588\u2588\u2551${t.reset}
6
- ${t.brightMagenta}\u255A\u2588\u2588\u2588\u2554\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2550\u255D \u2588\u2588\u2551${t.reset}
7
- ${t.brightMagenta} \u255A\u2550\u2550\u255D\u255A\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D${t.reset}
8
- ${t.dim} Persistent AI Memory System v${g()}${t.reset}
9
- ${t.dim} ghosts.lk${t.reset}
10
- `}function k(){return`${t.brightMagenta}\u{F115D} Wyrm v${g()}${t.reset}`}class E{frames=["\u280B","\u2819","\u2839","\u2838","\u283C","\u2834","\u2826","\u2827","\u2807","\u280F"];current=0;interval=null;text;constructor(r="Loading..."){this.text=r}start(){this.interval=setInterval(()=>{process.stdout.write(`\r${t.cyan}${this.frames[this.current]}${t.reset} ${this.text}`),this.current=(this.current+1)%this.frames.length},80)}update(r){this.text=r}succeed(r){this.stop(),console.log(`\r${l.success("\u2713")} ${r||this.text}`)}fail(r){this.stop(),console.log(`\r${l.error("\u2717")} ${r||this.text}`)}warn(r){this.stop(),console.log(`\r${l.warning("!")} ${r||this.text}`)}stop(){this.interval&&(clearInterval(this.interval),this.interval=null,process.stdout.write("\r\x1B[K"))}}class j{total;current=0;width;label;constructor(r,n="Progress",i=30){this.total=r,this.width=i,this.label=n}update(r,n){this.current=r,n&&(this.label=n),this.render()}increment(r){this.update(this.current+1,r)}render(){const r=Math.min(100,Math.floor(this.current/this.total*100)),n=Math.floor(this.current/this.total*this.width),i=this.width-n,o=`${t.brightMagenta}${"\u2588".repeat(n)}${t.dim}${"\u2591".repeat(i)}${t.reset}`,s=`${r}%`.padStart(4);process.stdout.write(`\r${this.label} ${o} ${s} (${this.current}/${this.total})`)}complete(){this.update(this.total),console.log()}}function I(e,r,n={}){const{padding:i=2,border:o=!0}=n,s=e.map((a,$)=>{const h=Math.max(...r.map(u=>(u[$]||"").length));return Math.max(a.length,h)}),c=(a,$)=>a.padEnd($),p=s.map(a=>"\u2500".repeat(a+i)).join(o?"\u253C":" ");let d="";o&&(d+=`${t.dim}\u250C${s.map(a=>"\u2500".repeat(a+i)).join("\u252C")}\u2510${t.reset}
11
- `),d+=(o?`${t.dim}\u2502${t.reset}`:"")+e.map((a,$)=>` ${l.bold(c(a,s[$]))} `).join(o?`${t.dim}\u2502${t.reset}`:"")+(o?`${t.dim}\u2502${t.reset}`:"")+`
12
- `,o&&(d+=`${t.dim}\u251C${p}\u2524${t.reset}
13
- `);for(const a of r)d+=(o?`${t.dim}\u2502${t.reset}`:"")+a.map(($,h)=>` ${c($||"",s[h])} `).join(o?`${t.dim}\u2502${t.reset}`:"")+(o?`${t.dim}\u2502${t.reset}`:"")+`
14
- `;return o&&(d+=`${t.dim}\u2514${s.map(a=>"\u2500".repeat(a+i)).join("\u2534")}\u2518${t.reset}`),d}function C(e,r,n=50){const i=r.split(`
15
- `),o=Math.max(e.length,...i.map(c=>c.length),n);let s=`${t.dim}\u256D${"\u2500".repeat(o+2)}\u256E${t.reset}
16
- `;s+=`${t.dim}\u2502${t.reset} ${l.bold(e.padEnd(o))} ${t.dim}\u2502${t.reset}
17
- `,s+=`${t.dim}\u251C${"\u2500".repeat(o+2)}\u2524${t.reset}
18
- `;for(const c of i)s+=`${t.dim}\u2502${t.reset} ${c.padEnd(o)} ${t.dim}\u2502${t.reset}
19
- `;return s+=`${t.dim}\u2570${"\u2500".repeat(o+2)}\u256F${t.reset}`,s}function N(e){if(e===0)return"0 B";const r=1024,n=["B","KB","MB","GB","TB"],i=Math.floor(Math.log(e)/Math.log(r));return`${(e/Math.pow(r,i)).toFixed(2)} ${n[i]}`}function L(e){return e<1e3?`${e}ms`:e<6e4?`${(e/1e3).toFixed(1)}s`:e<36e5?`${Math.floor(e/6e4)}m ${Math.floor(e%6e4/1e3)}s`:`${Math.floor(e/36e5)}h ${Math.floor(e%36e5/6e4)}m`}function P(e){console.log(`
20
- ${l.bold(l.primary(`\u25B6 ${e}`))}`),console.log(`${t.dim}${"\u2500".repeat(e.length+4)}${t.reset}`)}function G(e){console.log(`${m.success} ${l.success(e)}`)}function A(e){console.log(`${m.error} ${l.error(e)}`)}function F(e){console.log(`${m.warning} ${l.warning(e)}`)}function T(e){console.log(`${m.info} ${e}`)}class M extends Error{constructor(){super("readSecret canceled"),this.name="ReadSecretCanceled"}}async function Y(e,r={}){const n=r.input??process.stdin,i=r.output??process.stdout,o=typeof n.setRawMode=="function";i.write(e),o&&n.setRawMode(!0),n.resume();let s="";return new Promise((c,p)=>{const d=h=>{for(const u of h){if(u===13||u===10){$(),i.write(`
21
- `),c(s);return}if(u===3){$(),p(new M);return}if(u===4){$(),i.write(`
22
- `),c(s);return}if(u===127||u===8){s.length>0&&(s=s.slice(0,-1),i.write("\b \b"));continue}s+=String.fromCharCode(u),i.write("*")}},a=()=>{$(),c(s)};function $(){o&&n.setRawMode(!1),n.pause(),n.removeListener("data",d),n.removeListener("end",a)}n.on("data",d),n.on("end",a)})}export{v as BANNER,k as MINI_BANNER,j as ProgressBar,M as ReadSecretCanceled,E as Spinner,l as c,t as colors,C as formatBox,N as formatBytes,L as formatDuration,I as formatTable,m as icons,A as printError,T as printInfo,P as printSection,G as printSuccess,F as printWarning,R as priorityIcons,Y as readSecret};
1
+ import{readFileSync as x}from"fs";import{dirname as b,join as w}from"path";import{fileURLToPath as M}from"url";import{shouldUseColor as g}from"./color.js";function f(){try{const t=b(M(import.meta.url));return JSON.parse(x(w(t,"..","package.json"),"utf-8")).version??"dev"}catch{return"dev"}}const y=g(process.env,process.stdout.isTTY),S=t=>y?t:"",R={reset:"\x1B[0m",bold:"\x1B[1m",dim:"\x1B[2m",italic:"\x1B[3m",underline:"\x1B[4m",black:"\x1B[30m",red:"\x1B[31m",green:"\x1B[32m",yellow:"\x1B[33m",blue:"\x1B[34m",magenta:"\x1B[35m",cyan:"\x1B[36m",white:"\x1B[37m",brightRed:"\x1B[91m",brightGreen:"\x1B[92m",brightYellow:"\x1B[93m",brightBlue:"\x1B[94m",brightMagenta:"\x1B[95m",brightCyan:"\x1B[96m",bgRed:"\x1B[41m",bgGreen:"\x1B[42m",bgYellow:"\x1B[43m",bgBlue:"\x1B[44m",bgMagenta:"\x1B[45m",bgCyan:"\x1B[46m"},e=Object.fromEntries(Object.entries(R).map(([t,n])=>[t,S(n)])),$={bold:t=>`${e.bold}${t}${e.reset}`,dim:t=>`${e.dim}${t}${e.reset}`,red:t=>`${e.red}${t}${e.reset}`,green:t=>`${e.green}${t}${e.reset}`,yellow:t=>`${e.yellow}${t}${e.reset}`,blue:t=>`${e.blue}${t}${e.reset}`,magenta:t=>`${e.magenta}${t}${e.reset}`,cyan:t=>`${e.cyan}${t}${e.reset}`,primary:t=>`${e.brightMagenta}${t}${e.reset}`,success:t=>`${e.brightGreen}${t}${e.reset}`,warning:t=>`${e.brightYellow}${t}${e.reset}`,error:t=>`${e.brightRed}${t}${e.reset}`},u={dragon:"\u{F115D}",fire:"\u{1F525}",scroll:"\u{1F4DC}",sword:"\u2694\uFE0F",shield:"\u{1F6E1}\uFE0F",treasure:"\u{1F48E}",quest:"\u{1F3AF}",check:"\u2713",cross:"\u2717",arrow:"\u2192",bullet:"\u2022",star:"\u2605",warning:"\u26A0\uFE0F",error:"\u274C",success:"\u2705",info:"\u2139\uFE0F",clock:"\u{1F550}",folder:"\u{1F4C1}",file:"\u{1F4C4}",lock:"\u{1F512}",unlock:"\u{1F513}",sync:"\u{1F504}",search:"\u{1F50D}",sparkle:"\u2728"},L={critical:"\u{1F534}",high:"\u{1F7E0}",medium:"\u{1F7E1}",low:"\u{1F7E2}"};function I(){return`
2
+ ${e.brightMagenta}\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557${e.reset}
3
+ ${e.brightMagenta}\u2588\u2588\u2551 \u2588\u2588\u2551\u255A\u2588\u2588\u2557 \u2588\u2588\u2554\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2551${e.reset}
4
+ ${e.brightMagenta}\u2588\u2588\u2551 \u2588\u2557 \u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2554\u2588\u2588\u2588\u2588\u2554\u2588\u2588\u2551${e.reset}
5
+ ${e.brightMagenta}\u2588\u2588\u2551\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u255A\u2588\u2588\u2554\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551\u255A\u2588\u2588\u2554\u255D\u2588\u2588\u2551${e.reset}
6
+ ${e.brightMagenta}\u255A\u2588\u2588\u2588\u2554\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2550\u255D \u2588\u2588\u2551${e.reset}
7
+ ${e.brightMagenta} \u255A\u2550\u2550\u255D\u255A\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D${e.reset}
8
+ ${e.dim} Persistent AI Memory System v${f()}${e.reset}
9
+ ${e.dim} ghosts.lk${e.reset}
10
+ `}function N(){return`${e.brightMagenta}\u{F115D} Wyrm v${f()}${e.reset}`}class T{frames=["\u280B","\u2819","\u2839","\u2838","\u283C","\u2834","\u2826","\u2827","\u2807","\u280F"];current=0;interval=null;text;constructor(n="Loading..."){this.text=n}start(){this.interval=setInterval(()=>{process.stdout.write(`\r${e.cyan}${this.frames[this.current]}${e.reset} ${this.text}`),this.current=(this.current+1)%this.frames.length},80)}update(n){this.text=n}succeed(n){this.stop(),console.log(`\r${$.success("\u2713")} ${n||this.text}`)}fail(n){this.stop(),console.log(`\r${$.error("\u2717")} ${n||this.text}`)}warn(n){this.stop(),console.log(`\r${$.warning("!")} ${n||this.text}`)}stop(){this.interval&&(clearInterval(this.interval),this.interval=null,process.stdout.write("\r\x1B[K"))}}class A{total;current=0;width;label;constructor(n,o="Progress",r=30){this.total=n,this.width=r,this.label=o}update(n,o){this.current=n,o&&(this.label=o),this.render()}increment(n){this.update(this.current+1,n)}render(){const n=Math.min(100,Math.floor(this.current/this.total*100)),o=Math.floor(this.current/this.total*this.width),r=this.width-o,s=`${e.brightMagenta}${"\u2588".repeat(o)}${e.dim}${"\u2591".repeat(r)}${e.reset}`,i=`${n}%`.padStart(4);process.stdout.write(`\r${this.label} ${s} ${i} (${this.current}/${this.total})`)}complete(){this.update(this.total),console.log()}}function _(t,n,o={}){const{padding:r=2,border:s=!0}=o,i=t.map((c,l)=>{const p=Math.max(...n.map(m=>(m[l]||"").length));return Math.max(c.length,p)}),a=(c,l)=>c.padEnd(l),h=i.map(c=>"\u2500".repeat(c+r)).join(s?"\u253C":" ");let d="";s&&(d+=`${e.dim}\u250C${i.map(c=>"\u2500".repeat(c+r)).join("\u252C")}\u2510${e.reset}
11
+ `),d+=(s?`${e.dim}\u2502${e.reset}`:"")+t.map((c,l)=>` ${$.bold(a(c,i[l]))} `).join(s?`${e.dim}\u2502${e.reset}`:"")+(s?`${e.dim}\u2502${e.reset}`:"")+`
12
+ `,s&&(d+=`${e.dim}\u251C${h}\u2524${e.reset}
13
+ `);for(const c of n)d+=(s?`${e.dim}\u2502${e.reset}`:"")+c.map((l,p)=>` ${a(l||"",i[p])} `).join(s?`${e.dim}\u2502${e.reset}`:"")+(s?`${e.dim}\u2502${e.reset}`:"")+`
14
+ `;return s&&(d+=`${e.dim}\u2514${i.map(c=>"\u2500".repeat(c+r)).join("\u2534")}\u2518${e.reset}`),d}function G(t,n,o=50){const r=n.split(`
15
+ `),s=Math.max(t.length,...r.map(a=>a.length),o);let i=`${e.dim}\u256D${"\u2500".repeat(s+2)}\u256E${e.reset}
16
+ `;i+=`${e.dim}\u2502${e.reset} ${$.bold(t.padEnd(s))} ${e.dim}\u2502${e.reset}
17
+ `,i+=`${e.dim}\u251C${"\u2500".repeat(s+2)}\u2524${e.reset}
18
+ `;for(const a of r)i+=`${e.dim}\u2502${e.reset} ${a.padEnd(s)} ${e.dim}\u2502${e.reset}
19
+ `;return i+=`${e.dim}\u2570${"\u2500".repeat(s+2)}\u256F${e.reset}`,i}function v(t){if(t===0)return"0 B";const n=1024,o=["B","KB","MB","GB","TB"],r=Math.floor(Math.log(t)/Math.log(n));return`${(t/Math.pow(n,r)).toFixed(2)} ${o[r]}`}function O(t){return t<1e3?`${t}ms`:t<6e4?`${(t/1e3).toFixed(1)}s`:t<36e5?`${Math.floor(t/6e4)}m ${Math.floor(t%6e4/1e3)}s`:`${Math.floor(t/36e5)}h ${Math.floor(t%36e5/6e4)}m`}function Y(t){console.log(`
20
+ ${$.bold($.primary(`\u25B6 ${t}`))}`),console.log(`${e.dim}${"\u2500".repeat(t.length+4)}${e.reset}`)}function D(t){console.log(`${u.success} ${$.success(t)}`)}function F(t){console.error(`${u.error} ${$.error(t)}`)}function W(t){console.log(`${u.warning} ${$.warning(t)}`)}function K(t){console.log(`${u.info} ${t}`)}function B(t,n){const o=Array.from({length:t.length+1},(r,s)=>[s,...Array(n.length).fill(0)]);for(let r=1;r<=n.length;r++)o[0][r]=r;for(let r=1;r<=t.length;r++)for(let s=1;s<=n.length;s++){const i=t[r-1]===n[s-1]?0:1;o[r][s]=Math.min(o[r-1][s]+1,o[r][s-1]+1,o[r-1][s-1]+i),r>1&&s>1&&t[r-1]===n[s-2]&&t[r-2]===n[s-1]&&(o[r][s]=Math.min(o[r][s],o[r-2][s-2]+1))}return o[t.length][n.length]}function U(t,n,o=3){const r=t.toLowerCase(),s=r.length<=4?1:2;return[...new Set(n)].map(i=>({cand:i,dist:B(r,i.toLowerCase())})).filter(({cand:i,dist:a})=>a<=s||r.length>=3&&i.toLowerCase().startsWith(r)).sort((i,a)=>i.dist-a.dist||i.cand.localeCompare(a.cand)).slice(0,o).map(({cand:i})=>i)}function q(t,n=process.platform){const o=n==="win32"?/^(.*?)[\\/]node_modules[\\/]wyrm-mcp[\\/]/i.exec(t):/^(.*)\/lib\/node_modules\/wyrm-mcp\//.exec(t);return!o||!o[1]||/[\\/](_npx|node_modules)([\\/]|$)/.test(o[1])?null:o[1]}function J(t){if(t.isTTY){let r=-1;return{onProgress:(s,i,a)=>{const h=Math.floor(i/a*100);h!==r&&(r=h,t.write(`\r ${s}: ${h}% `))},finish:()=>{t.write(`
21
+ `)}}}const n=new Set,o=new Set;return{onProgress:(r,s,i)=>{n.has(r)||(n.add(r),t.write(` ${r}: downloading (${v(i)})
22
+ `)),s>=i&&!o.has(r)&&(o.add(r),t.write(` ${r}: done
23
+ `))},finish:()=>{}}}class C extends Error{constructor(){super("readSecret canceled"),this.name="ReadSecretCanceled"}}async function V(t,n={}){const o=n.input??process.stdin,r=n.output??process.stdout,s=typeof o.setRawMode=="function";r.write(t),s&&o.setRawMode(!0),o.resume();let i="";return new Promise((a,h)=>{const d=p=>{for(const m of p){if(m===13||m===10){l(),r.write(`
24
+ `),a(i);return}if(m===3){l(),h(new C);return}if(m===4){l(),r.write(`
25
+ `),a(i);return}if(m===127||m===8){i.length>0&&(i=i.slice(0,-1),r.write("\b \b"));continue}i+=String.fromCharCode(m),r.write("*")}},c=()=>{l(),a(i)};function l(){s&&o.setRawMode(!1),o.pause(),o.removeListener("data",d),o.removeListener("end",c)}o.on("data",d),o.on("end",c)})}export{I as BANNER,N as MINI_BANNER,A as ProgressBar,C as ReadSecretCanceled,T as Spinner,$ as c,e as colors,J as createDownloadProgress,B as editDistance,G as formatBox,v as formatBytes,O as formatDuration,_ as formatTable,u as icons,q as npmGlobalPrefixFromPath,F as printError,K as printInfo,Y as printSection,D as printSuccess,W as printWarning,L as priorityIcons,V as readSecret,g as shouldUseColor,U as suggestClosest};
package/dist/cloud/cli.js CHANGED
@@ -1,4 +1,4 @@
1
- import{randomBytes as A}from"node:crypto";import{writeFileSync as L}from"node:fs";import{hostname as O}from"node:os";import{resolve as P,dirname as W,join as M}from"node:path";import{fileURLToPath as F}from"node:url";import{createInterface as B}from"node:readline";import{CloudClient as u,CloudError as w,DEFAULT_BASE as v,SESSION_FILE as $,loadSession as g,saveSession as C,clearSession as k}from"./client.js";import{getMasterKey as U,keyExists as b,keyFilePath as d,decrypt as j}from"./crypto.js";import{computeMachineFp as D,classifySession as G}from"./machine-id.js";import{runSync as H,CopiedSessionError as Y}from"./sync-engine.js";import{SovereignSyncManager as q,restartIntervalMinutes as K}from"./sync-daemon.js";import{initializeLicense as J,hasFeature as z,getTier as V}from"../license.js";function S(){J(),z("cloud_backup")||(console.error("\u{F115D} Wyrm Cloud sync requires a Pro license or higher."),console.error(` Current tier: ${V()}`),console.error(" Upgrade: https://ghosts.lk/wyrm"),console.error(" Already paid? Activate your license (wyrm_license tool, or place"),console.error(" the signed JSON at ~/.wyrm/license.json) and retry."),process.exit(1))}function _(e){return e<1024?`${e} B`:e<1024*1024?`${(e/1024).toFixed(1)} KiB`:e<1024*1024*1024?`${(e/(1024*1024)).toFixed(1)} MiB`:`${(e/(1024*1024*1024)).toFixed(2)} GiB`}function x(e){return e?new Date(e).toISOString().replace("T"," ").slice(0,16)+" UTC":"\u2014"}async function be(e){const r=e[0],s=e.slice(1);try{switch(r){case void 0:case"help":case"--help":case"-h":return Q();case"login":return await X(s);case"logout":return await Z();case"status":return await ee();case"doctor":return oe();case"devices":return await se(s);case"sync":return await te(s);case"daemon":return await ce(s);case"export":return await le(s);case"delete":return await ie(s);case"recovery":return await ae(s);default:console.error(`Unknown subcommand: wyrm cloud ${r}`),console.error("Try `wyrm cloud help`"),process.exit(1)}}catch(n){if(n instanceof Y&&process.exit(1),n instanceof w)n.status===401?console.error("\u2718 Not authenticated. Run `wyrm cloud login`."):n.status===403?console.error(`\u2718 Forbidden: ${n.message}`):n.status===0?(console.error(`\u2718 ${n.message}`),console.error(" Check your internet connection. If wyrm.ghosts.lk is unreachable, see status at https://www.cloudflarestatus.com")):n.status===501?console.error(`\u2718 Service not configured: ${n.message}`):console.error(`\u2718 ${n.message} (HTTP ${n.status})`);else if(n instanceof Error){const t=n.message;t.includes("Not logged in")?console.error("\u2718 Not logged in. Run `wyrm cloud login`."):t.includes("Wyrm Cloud key")?console.error(`\u2718 Encryption-key error: ${t}`):console.error(`\u2718 ${t}`)}else console.error("\u2718 Unexpected error:",n);process.exit(1)}}function Q(){console.log(`\u{F115D} wyrm cloud \u2014 sync your Wyrm memory across devices
1
+ import{randomBytes as A}from"node:crypto";import{writeFileSync as L}from"node:fs";import{hostname as O}from"node:os";import{resolve as P,dirname as W,join as M}from"node:path";import{fileURLToPath as F}from"node:url";import{createInterface as B}from"node:readline";import{CloudClient as u,CloudError as w,DEFAULT_BASE as v,SESSION_FILE as $,loadSession as g,saveSession as C,clearSession as k}from"./client.js";import{getMasterKey as U,keyExists as b,keyFilePath as d,decrypt as j}from"./crypto.js";import{computeMachineFp as D,classifySession as G}from"./machine-id.js";import{runSync as H,CopiedSessionError as Y}from"./sync-engine.js";import{SovereignSyncManager as q,restartIntervalMinutes as K}from"./sync-daemon.js";import{initializeLicense as J,hasFeature as z,getTier as V}from"../license.js";function S(){J(),z("cloud_backup")||(console.error("\u{F115D} Wyrm Cloud sync requires a Pro license or higher."),console.error(` Current tier: ${V()}`),console.error(" Upgrade: https://ghosts.lk/wyrm"),console.error(" Already paid? Activate your license (wyrm_license tool, or place"),console.error(" the signed JSON at ~/.wyrm/license.json) and retry."),process.exit(1))}function _(e){return e<1024?`${e} B`:e<1024*1024?`${(e/1024).toFixed(1)} KiB`:e<1024*1024*1024?`${(e/(1024*1024)).toFixed(1)} MiB`:`${(e/(1024*1024*1024)).toFixed(2)} GiB`}function x(e){return e?new Date(e).toISOString().replace("T"," ").slice(0,16)+" UTC":"\u2014"}async function be(e){const r=e[0],n=e.slice(1);try{switch(r){case void 0:case"help":case"--help":case"-h":return Q();case"login":return await X(n);case"logout":return await Z();case"status":return await ee();case"doctor":return oe();case"devices":return await se(n);case"sync":return await te(n);case"daemon":return await ce(n);case"export":return await le(n);case"delete":return await ie(n);case"recovery":return await ae(n);default:console.error(`Unknown subcommand: wyrm cloud ${r}`),console.error("Try `wyrm cloud help`"),process.exit(1)}}catch(t){if(t instanceof Y&&process.exit(1),t instanceof w)t.status===401?console.error("\u2718 Not authenticated. Run `wyrm cloud login`."):t.status===403?console.error(`\u2718 Forbidden: ${t.message}`):t.status===0?(console.error(`\u2718 ${t.message}`),console.error(" Check your internet connection. If wyrm.ghosts.lk is unreachable, see status at https://www.cloudflarestatus.com")):t.status===501?console.error(`\u2718 Service not configured: ${t.message}`):console.error(`\u2718 ${t.message} (HTTP ${t.status})`);else if(t instanceof Error){const s=t.message;s.includes("Not logged in")?console.error("\u2718 Not logged in. Run `wyrm cloud login`."):s.includes("Wyrm Cloud key")?console.error(`\u2718 Encryption-key error: ${s}`):console.error(`\u2718 ${s}`)}else console.error("\u2718 Unexpected error:",t);process.exit(1)}}function Q(){console.log(`\u{F115D} wyrm cloud \u2014 sync your Wyrm memory across devices
2
2
 
3
3
  USAGE:
4
4
  wyrm cloud <subcommand> [args]
@@ -37,6 +37,6 @@ CONSTITUTION:
37
37
 
38
38
  DOMAIN:
39
39
  https://wyrm.ghosts.lk
40
- `)}async function X(e){const r=g();if(r)try{await new u(r.base,r.session).me(),console.error("Already logged in. Run `wyrm cloud logout` first to switch accounts."),process.exit(1)}catch(c){c instanceof w&&c.status===401||(console.error("Already logged in. Run `wyrm cloud logout` first to switch accounts."),process.exit(1)),console.log("Previous session expired; starting a fresh login."),k()}const s=new u(v),n=A(32).toString("hex");let t;try{t=await s.cliInit(n)}catch(c){console.error(`\u2718 Could not start login: ${c instanceof Error?c.message:c}`),process.exit(1)}console.log(""),console.log("\u{F115D} Sign in to Wyrm Cloud"),console.log(""),console.log(" 1. Open this URL on any device:"),console.log(` ${t.browser_url}`),console.log(""),console.log(" 2. Type this code when prompted:"),console.log(` ${t.code}`),console.log(""),console.log(" 3. Sign in with Google or GitHub."),console.log(""),console.log(` Waiting (expires in ${Math.round(t.expires_in_sec/60)} min)\u2026`);const l=Date.now()+t.expires_in_sec*1e3;let i,a;for(;Date.now()<l;){await new Promise(c=>{setTimeout(c,3e3)});try{const c=await s.cliPoll(n);if(c.ready&&c.session){i=c.session,a=c.account_id;break}}catch(c){console.error(`
40
+ `)}async function X(e){const r=g();if(r)try{await new u(r.base,r.session).me(),console.error("Already logged in. Run `wyrm cloud logout` first to switch accounts."),process.exit(1)}catch(c){c instanceof w&&c.status===401||(console.error("Already logged in. Run `wyrm cloud logout` first to switch accounts."),process.exit(1)),console.log("Previous session expired; starting a fresh login."),k()}const n=new u(v),t=A(32).toString("hex");let s;try{s=await n.cliInit(t)}catch(c){console.error(`\u2718 Could not start login: ${c instanceof Error?c.message:c}`),process.exit(1)}console.log(""),console.log("\u{F115D} Sign in to Wyrm Cloud"),console.log(""),console.log(" 1. Open this URL on any device:"),console.log(` ${s.browser_url}`),console.log(""),console.log(" 2. Type this code when prompted:"),console.log(` ${s.code}`),console.log(""),console.log(" 3. Sign in with Google or GitHub."),console.log(""),console.log(` Waiting (expires in ${Math.round(s.expires_in_sec/60)} min)\u2026`);const l=Date.now()+s.expires_in_sec*1e3;let i,a;for(;Date.now()<l;){await new Promise(c=>{setTimeout(c,3e3)});try{const c=await n.cliPoll(t);if(c.ready&&c.session){i=c.session,a=c.account_id;break}}catch(c){console.error(`
41
41
  \u2718 Login failed: ${c instanceof Error?c.message:c}`),process.exit(1)}}(!i||!a)&&(console.error("\n\u2718 Login timed out. Run `wyrm cloud login` again."),process.exit(1)),C({base:v,session:i,account_id:a,created_at:Date.now()});const y=new u(v,i);let m="";try{m=(await y.me()).account.email}catch{}const f=`${O()} (${process.platform})`;let p;try{p=(await y.registerDevice(f)).device.id;const h=g();h&&C({...h,email:m,device_id:p,device_name:f,machine_fp:D()})}catch(c){console.error(`
42
- \u26A0 Logged in but could not register device: ${c instanceof Error?c.message:c}`),console.error(" Re-run `wyrm cloud login` to retry.");return}console.log(""),console.log(`\u2713 Signed in as ${m}`),console.log(`\u2713 Device registered: ${f}`),console.log(`\u2713 Session saved to ${$}`);const R=!b();if(U(),R?(console.log(`\u2713 Encryption key generated at ${d()} (0600)`),console.log(""),console.log(" \u26A0 Back up the key file. Losing it means losing access to your synced data."),console.log(" The Wyrm Cloud server CANNOT recover it \u2014 that's the operator-owns-data"),console.log(" guarantee. Run `wyrm cloud recovery show` now to get a 24-word phrase,"),console.log(" or copy the key file to a password manager / encrypted backup.")):console.log(`\u2713 Encryption key reused from ${d()}`),p)try{const c=await y.syncStatus();if(c.deltas>0){const E=(await y.syncPull(p,0,1)).deltas[0];if(E){let T=!0;try{j(E.payload_b64)}catch{T=!1}T||(console.log(""),console.log(" \u26A0 THIS DEVICE CANNOT READ THE ACCOUNT'S DATA YET"),console.log(` Login succeeded, but the key at ${d()} cannot decrypt`),console.log(` the ${c.deltas} delta(s) already on this account. They were encrypted`),console.log(" with a different master key (e.g. this is an org account whose other"),console.log(" devices hold the shared key). Until the keys match, sync will move"),console.log(" 0 rows in BOTH directions \u2014 a connected pipe that carries nothing."),console.log(""),console.log(" Fix: adopt the account key on this device \u2014"),console.log(" wyrm cloud recovery restore (needs the account's 24-word phrase)"),console.log(` Heads-up: restore OVERWRITES ${d()}. If this device also has`),console.log(" its OWN separate cloud data, back that key up first, or run the org"),console.log(" sync under a separate WYRM home so both keys coexist."))}}}catch{}console.log(""),console.log("Next: `wyrm cloud sync` to push your memory.")}async function Z(){const e=g();if(!e){console.log("Not logged in.");return}try{await new u(e.base,e.session).logout(),console.log("\u2713 Session revoked server-side.")}catch(r){console.error(`\u26A0 Could not revoke server-side: ${r instanceof Error?r.message:r}`),console.error(" Local session cleared anyway.")}k(),console.log(`\u2713 Local session removed (${$}).`),console.log(" Note: ~/.wyrm/cloud.key kept. Delete manually if you want a clean slate.")}async function ee(){const e=g();if(!e){console.log("Not logged in. Run `wyrm cloud login`.");return}const r=new u(e.base,e.session);try{const s=await r.me(),n=await r.syncStatus();console.log(""),console.log(`\u{F115D} Wyrm Cloud \u2014 ${s.account.email}`),console.log(""),console.log(` Tier: ${n.tier}`),console.log(` Storage: ${_(n.storage.used_bytes)} / ${_(n.storage.limit_bytes)} (${n.storage.percent}%)`),console.log(` Deltas: ${n.deltas}`),console.log(` Devices: ${n.devices}`),console.log(` Providers:${s.identities.map(t=>" "+t.provider).join(",")}`),console.log(""),console.log(` This machine: ${e.device_name??"(not registered)"}`),console.log(` Device ID: ${e.device_id?e.device_id.slice(0,8)+"\u2026":"(none)"}`),console.log(` Session age: ${Math.round((Date.now()-e.created_at)/(1440*60*1e3))} day(s)`),I(e),console.log(""),b()?n.deltas>0&&(console.log(` Key: ${d()} (0600)`),console.log(` \u26A0 Back this file up. Losing it = losing access to your ${n.deltas} deltas.`),console.log("")):(console.log(" \u26A0 ENCRYPTION KEY MISSING"),console.log(` Expected at: ${d()}`),console.log(" Without this file, deltas already pushed cannot be decrypted."),console.log(" If you have a backup, restore it before running sync."),console.log(""))}catch(s){throw s instanceof w&&s.status===401&&(console.error("Session expired or invalid. Run `wyrm cloud login` again."),process.exit(1)),s}}function I(e){const r=G(e.machine_fp);r.state==="match"?console.log(" Identity: \u2713 machine fingerprint matches (not a copied session)"):r.state==="adopt"?console.log(" Identity: \u2014 no fingerprint yet (pre-7.0.3 session; adopted on next sync)"):(console.log(" Identity: \u26A0 FINGERPRINT MISMATCH \u2014 this session looks COPIED"),console.log(` stored ${r.stored.slice(0,12)}\u2026 vs this machine ${r.current.slice(0,12)}\u2026`),console.log(" device_id collision \u2192 cross-device pull returns 0 silently."),console.log(" Fix: rm ~/.wyrm/cloud.json ~/.wyrm/cloud-cursor.json (KEEP cloud.key),"),console.log(" then `wyrm cloud login`. (Or `wyrm cloud sync --force` if intended.)"))}function oe(){const e=g();if(console.log(""),console.log("\u{F115D} Wyrm Cloud \u2014 device identity doctor"),console.log(""),!e){console.log(" Not logged in. Run `wyrm cloud login`."),console.log("");return}console.log(` Account: ${e.email??e.account_id.slice(0,8)+"\u2026"}`),console.log(` Device ID: ${e.device_id?e.device_id.slice(0,8)+"\u2026":"(none \u2014 re-run `wyrm cloud login`)"}`),console.log(` Server: ${e.base}`),console.log(` Stored fp: ${e.machine_fp?e.machine_fp.slice(0,12)+"\u2026":"(none \u2014 pre-7.0.3 session)"}`),console.log(` This box fp: ${D().slice(0,12)}\u2026`),I(e),console.log(""),console.log(" Only ~/.wyrm/cloud.key is meant to be shared across your machines."),console.log(" cloud.json + cloud-cursor.json + machine-id are per-device."),console.log("")}async function se(e){const r=e[0],s=u.fromSession();if(r==="revoke"){const i=e[1];i||(console.error("Usage: wyrm cloud devices revoke <device-id>"),process.exit(1));const a=await ne(s,i);await s.revokeDevice(a),console.log(`\u2713 Device ${a.slice(0,8)}\u2026 revoked.`);return}const n=e.includes("--full"),{devices:t}=await s.listDevices(),l=g()?.device_id;console.log(""),console.log(`\u{F115D} Devices (${t.length})`),console.log("");for(const i of t){const a=i.id===l?"\u2605":" ",y=i.revoked?"(revoked)":"",m=n?i.id:`${i.id.slice(0,8)}\u2026`;console.log(` ${a} ${m} ${i.name.padEnd(30)} last_seen=${x(i.last_seen_at)} ${y}`)}console.log(""),console.log(" \u2605 = this machine"),console.log(" Revoke: wyrm cloud devices revoke <device-id> (an id prefix as shown above is fine)"),n||console.log(" Full ids: wyrm cloud devices --full"),console.log("")}async function ne(e,r){const s=r.replace(/[….]+$/,"").trim().toLowerCase(),{devices:n}=await e.listDevices(),t=n.find(l=>l.id.toLowerCase()===s);if(t)return t.id;const o=n.filter(l=>l.id.toLowerCase().startsWith(s));if(o.length===1)return o[0].id;o.length===0&&(console.error(`\u2718 No device matches "${r}". List them with: wyrm cloud devices`),process.exit(1)),console.error(`\u2718 "${r}" is ambiguous \u2014 it matches ${o.length} devices:`);for(const l of o)console.error(` ${l.id} ${l.name}`);console.error(" Re-run with more characters, or the full id."),process.exit(1)}async function te(e){S();const r=e.includes("--dry-run"),s=e.includes("--all"),n=e.includes("--force");b()||(console.error("\u26A0 Encryption key not found at",d()),console.error(" A new key will be generated, but it will NOT decrypt deltas"),console.error(" pushed by other devices using a different key. If you have a backup,"),console.error(" restore it to ~/.wyrm/cloud.key (0600) before running sync."),console.error(" Continuing in 5 seconds \u2014 press Ctrl+C to abort."),await new Promise(o=>{setTimeout(o,5e3)})),s&&(console.log("\u{1F310} --all mode: syncing every semantic table (ignoring visibility flags)."),console.log(" Skipped: per-device logs, FTS shadows, sync internals."),console.log(""));const t=await H({dryRun:r,all:s,force:n});if(console.log(""),console.log("\u{F115D} Sync complete"),console.log(""),console.log(` Pushed: ${t.pushed} row(s)${r?" (dry run)":""}`),console.log(` Pulled: ${t.pulled} row(s)`),console.log(` Deleted: ${t.deleted_local} row(s) (peer tombstones)`),t.quarantined&&t.quarantined>0&&console.log(` Quarantined: ${t.quarantined} undecryptable delta(s) set aside (see errors)`),t.errors.length>0){console.log(""),console.log(" Errors:");for(const o of t.errors)console.log(` - ${o}`)}console.log("")}function re(){const e=M(W(F(import.meta.url)),"sync-daemon-entrypoint.js");return new q(e)}async function ce(e){S();const r=e[0]??"status",s=e.find(o=>o.startsWith("--interval="));let n;if(s){const o=Number(s.split("=")[1]);(!Number.isFinite(o)||o<=0)&&(console.error("\u2716 --interval= expects a positive number of minutes, got "+JSON.stringify(s)),process.exit(1)),n=o}const t=re();switch(r){case"start":{g()||(console.error("\u2718 Not logged in. Run `wyrm cloud login` first."),process.exit(1));const o=t.start({interval_minutes:n});o.ok||(console.error(`\u{F115D} sovereign-sync daemon: ${o.reason??"failed to start"}${o.pid?` (pid ${o.pid})`:""}`),process.exit(o.pid?0:1)),console.log(`\u{F115D} \u2713 sovereign-sync daemon started (pid ${o.pid}). Logs: ~/.wyrm/wyrm-sovereign-sync.log`);return}case"stop":{const o=t.stop();console.log(o.ok?"\u{F115D} \u2713 sovereign-sync daemon stopped":`\u{F115D} sovereign-sync daemon: ${o.reason??"failed to stop"}`);return}case"restart":{const o=K(n,t.status().state?.interval_ms);t.stop(),await new Promise(i=>{setTimeout(i,500)});const l=t.start({interval_minutes:o});console.log(l.ok?`\u{F115D} \u2713 sovereign-sync daemon restarted (pid ${l.pid}, every ${o} min)`:`\u{F115D} restart failed: ${l.reason??"unknown"}`);return}case"status":{const o=t.status();console.log("\u{F115D} Sovereign-sync daemon"),console.log(` Running: ${o.running?`\u2713 yes (pid ${o.pid})`:"\u2717 no"}`),o.state?.interval_ms&&console.log(` Interval: every ${o.state.interval_ms/6e4} min`),o.state?.started_ts&&console.log(` Started: ${x(o.state.started_ts)}`),o.state?.last_tick_ts&&(console.log(` Last tick: ${x(o.state.last_tick_ts)} (exit ${o.state.last_tick_exit??"\u2014"})`),o.state.last_tick_summary&&console.log(` Last result: ${o.state.last_tick_summary}`));return}default:console.error(`Unknown daemon action: ${r}. Use: start, stop, restart, status`),process.exit(1)}}async function le(e){S();const r=u.fromSession(),s=e.find(l=>!l.startsWith("-")),n=P(s??`wyrm-cloud-export-${new Date().toISOString().slice(0,10)}.json`);console.log(" Fetching account export\u2026");const t=await r.accountExport(),o=JSON.stringify(t,null,2);L(n,o,{mode:384}),console.log(""),console.log(`\u2713 Exported to ${n}`),console.log(""),console.log(` Account: ${t.account.email} (${t.account.id.slice(0,8)}\u2026)`),console.log(` Devices: ${t.devices.length}`),console.log(` Identities: ${t.identities.length}`),console.log(` Orgs: ${t.orgs.length}`),console.log(` Deltas: ${t.deltas.length}`),console.log(` Size: ${_(o.length)}`),console.log(""),console.log(" Deltas are still AES-256-GCM ciphertext. To decrypt them you"),console.log(` need the key at ${d()}. Back that up alongside this file.`),console.log("")}async function ie(e){e.includes("--confirm")||(console.error("\u2718 This permanently deletes your Wyrm Cloud account."),console.error(" Re-run with --confirm to proceed:"),console.error(" wyrm cloud delete --confirm"),process.exit(1));const s=g();s||(console.error("\u2718 Not logged in. Nothing to delete."),process.exit(1)),console.log(""),console.log("\u26A0 You are about to PERMANENTLY DELETE your Wyrm Cloud account."),console.log(""),console.log(` Account: ${s.email??s.account_id}`),console.log(` Server: ${s.base}`),console.log(""),console.log(" This wipes all encrypted blobs, devices, identities, and org"),console.log(" memberships you own. The local ~/.wyrm/cloud.key is kept so"),console.log(" any backups remain decryptable. This action CANNOT be undone."),console.log(""),(await N(' Type "DELETE my account" to confirm: ')).trim()!=="DELETE my account"&&(console.error("\u2718 Confirmation phrase did not match. Aborting."),process.exit(1)),await new u(s.base,s.session).accountDelete(),k(),console.log(""),console.log("\u2713 Account deleted on server."),console.log(`\u2713 Local session removed (${$}).`),console.log(` Kept: ${d()} (in case you need to decrypt an old export).`),console.log("")}function N(e){return new Promise(r=>{const s=B({input:process.stdin,output:process.stdout});s.question(e,n=>{s.close(),r(n)})})}async function ae(e){const r=e[0];if(r==="show"){const{keyToMnemonic:s}=await import("./recovery.js"),t=s().split(" ");console.log(""),console.log("\u{1F511} Wyrm Cloud recovery phrase (BIP39, 24 words)"),console.log("");for(let o=0;o<4;o++){const l=t.slice(o*6,o*6+6).map((i,a)=>`${String(o*6+a+1).padStart(2)}. ${i.padEnd(10)}`).join(" ");console.log(` ${l}`)}console.log(""),console.log(" Write these down on PAPER and keep them somewhere safe."),console.log(" Anyone with this phrase can decrypt your synced Wyrm memory."),console.log(" Wyrm Cloud has NO COPY of this. Lose it = lose your data."),console.log("");return}if(r==="restore"){const s=e.includes("--overwrite");console.log(""),console.log("\u{1F511} Restore master key from BIP39 recovery phrase"),console.log(""),console.log(" Enter your 24 words separated by spaces (case-insensitive)."),console.log(" Press Enter when done."),console.log("");const n=await N(" > "),{mnemonicToKey:t}=await import("./recovery.js"),{path:o,overwrote:l}=t(n,{overwrite:s});console.log(""),console.log(`\u2713 Master key restored to ${o} (0600)`),l&&console.log(" Replaced existing key (you passed --overwrite)."),console.log(" Run `wyrm cloud sync` to pull deltas with the restored key."),console.log("");return}console.error("Usage:"),console.error(" wyrm cloud recovery show"),console.error(" wyrm cloud recovery restore [--overwrite]"),process.exit(1)}export{be as cmdCloud};
42
+ \u26A0 Logged in but could not register device: ${c instanceof Error?c.message:c}`),console.error(" Re-run `wyrm cloud login` to retry.");return}console.log(""),console.log(`\u2713 Signed in as ${m}`),console.log(`\u2713 Device registered: ${f}`),console.log(`\u2713 Session saved to ${$}`);const R=!b();if(U(),R?(console.log(`\u2713 Encryption key generated at ${d()} (0600)`),console.log(""),console.log(" \u26A0 Back up the key file. Losing it means losing access to your synced data."),console.log(" The Wyrm Cloud server CANNOT recover it \u2014 that's the operator-owns-data"),console.log(" guarantee. Run `wyrm cloud recovery show` now to get a 24-word phrase,"),console.log(" or copy the key file to a password manager / encrypted backup.")):console.log(`\u2713 Encryption key reused from ${d()}`),p)try{const c=await y.syncStatus();if(c.deltas>0){const E=(await y.syncPull(p,0,1)).deltas[0];if(E){let T=!0;try{j(E.payload_b64)}catch{T=!1}T||(console.log(""),console.log(" \u26A0 THIS DEVICE CANNOT READ THE ACCOUNT'S DATA YET"),console.log(` Login succeeded, but the key at ${d()} cannot decrypt`),console.log(` the ${c.deltas} delta(s) already on this account. They were encrypted`),console.log(" with a different master key (e.g. this is an org account whose other"),console.log(" devices hold the shared key). Until the keys match, sync will move"),console.log(" 0 rows in BOTH directions \u2014 a connected pipe that carries nothing."),console.log(""),console.log(" Fix: adopt the account key on this device \u2014"),console.log(" wyrm cloud recovery restore (needs the account's 24-word phrase)"),console.log(` Heads-up: restore OVERWRITES ${d()}. If this device also has`),console.log(" its OWN separate cloud data, back that key up first, or run the org"),console.log(" sync under a separate WYRM home so both keys coexist."))}}}catch{}console.log(""),console.log("Next: `wyrm cloud sync` to push your memory.")}async function Z(){const e=g();if(!e){console.log("Not logged in.");return}try{await new u(e.base,e.session).logout(),console.log("\u2713 Session revoked server-side.")}catch(r){console.error(`\u26A0 Could not revoke server-side: ${r instanceof Error?r.message:r}`),console.error(" Local session cleared anyway.")}k(),console.log(`\u2713 Local session removed (${$}).`),console.log(" Note: ~/.wyrm/cloud.key kept. Delete manually if you want a clean slate.")}async function ee(){const e=g();if(!e){console.log("Not logged in. Run `wyrm cloud login`.");return}const r=new u(e.base,e.session);try{const n=await r.me(),t=await r.syncStatus();console.log(""),console.log(`\u{F115D} Wyrm Cloud \u2014 ${n.account.email}`),console.log(""),console.log(` Tier: ${t.tier}`),console.log(` Storage: ${_(t.storage.used_bytes)} / ${_(t.storage.limit_bytes)} (${t.storage.percent}%)`),console.log(` Deltas: ${t.deltas}`),console.log(` Devices: ${t.devices}`),console.log(` Providers:${n.identities.map(s=>" "+s.provider).join(",")}`),console.log(""),console.log(` This machine: ${e.device_name??"(not registered)"}`),console.log(` Device ID: ${e.device_id?e.device_id.slice(0,8)+"\u2026":"(none)"}`),console.log(` Session age: ${Math.round((Date.now()-e.created_at)/(1440*60*1e3))} day(s)`),I(e),console.log(""),b()?t.deltas>0&&(console.log(` Key: ${d()} (0600)`),console.log(` \u26A0 Back this file up. Losing it = losing access to your ${t.deltas} deltas.`),console.log("")):(console.log(" \u26A0 ENCRYPTION KEY MISSING"),console.log(` Expected at: ${d()}`),console.log(" Without this file, deltas already pushed cannot be decrypted."),console.log(" If you have a backup, restore it before running sync."),console.log(""))}catch(n){throw n instanceof w&&n.status===401&&(console.error("Session expired or invalid. Run `wyrm cloud login` again."),process.exit(1)),n}}function I(e){const r=G(e.machine_fp);r.state==="match"?console.log(" Identity: \u2713 machine fingerprint matches (not a copied session)"):r.state==="adopt"?console.log(" Identity: \u2014 no fingerprint yet (pre-7.0.3 session; adopted on next sync)"):(console.log(" Identity: \u26A0 FINGERPRINT MISMATCH \u2014 this session looks COPIED"),console.log(` stored ${r.stored.slice(0,12)}\u2026 vs this machine ${r.current.slice(0,12)}\u2026`),console.log(" device_id collision \u2192 cross-device pull returns 0 silently."),console.log(" Fix: rm ~/.wyrm/cloud.json ~/.wyrm/cloud-cursor.json (KEEP cloud.key),"),console.log(" then `wyrm cloud login`. (Or `wyrm cloud sync --force` if intended.)"))}function oe(){const e=g();if(console.log(""),console.log("\u{F115D} Wyrm Cloud \u2014 device identity doctor"),console.log(""),!e){console.log(" Not logged in. Run `wyrm cloud login`."),console.log("");return}console.log(` Account: ${e.email??e.account_id.slice(0,8)+"\u2026"}`),console.log(` Device ID: ${e.device_id?e.device_id.slice(0,8)+"\u2026":"(none \u2014 re-run `wyrm cloud login`)"}`),console.log(` Server: ${e.base}`),console.log(` Stored fp: ${e.machine_fp?e.machine_fp.slice(0,12)+"\u2026":"(none \u2014 pre-7.0.3 session)"}`),console.log(` This box fp: ${D().slice(0,12)}\u2026`),I(e),console.log(""),console.log(" Only ~/.wyrm/cloud.key is meant to be shared across your machines."),console.log(" cloud.json + cloud-cursor.json + machine-id are per-device."),console.log("")}async function se(e){const r=e[0],n=u.fromSession();if(r==="revoke"){const i=e[1];i||(console.error("Usage: wyrm cloud devices revoke <device-id>"),process.exit(1));const a=await ne(n,i);await n.revokeDevice(a),console.log(`\u2713 Device ${a.slice(0,8)}\u2026 revoked.`);return}const t=e.includes("--full"),{devices:s}=await n.listDevices(),l=g()?.device_id;console.log(""),console.log(`\u{F115D} Devices (${s.length})`),console.log("");for(const i of s){const a=i.id===l?"\u2605":" ",y=i.revoked?"(revoked)":"",m=t?i.id:`${i.id.slice(0,8)}\u2026`;console.log(` ${a} ${m} ${i.name.padEnd(30)} last_seen=${x(i.last_seen_at)} ${y}`)}console.log(""),console.log(" \u2605 = this machine"),console.log(" Revoke: wyrm cloud devices revoke <device-id> (an id prefix as shown above is fine)"),t||console.log(" Full ids: wyrm cloud devices --full"),console.log("")}async function ne(e,r){const n=r.replace(/[….]+$/,"").trim().toLowerCase(),{devices:t}=await e.listDevices(),s=t.find(l=>l.id.toLowerCase()===n);if(s)return s.id;const o=t.filter(l=>l.id.toLowerCase().startsWith(n));if(o.length===1)return o[0].id;o.length===0&&(console.error(`\u2718 No device matches "${r}". List them with: wyrm cloud devices`),process.exit(1)),console.error(`\u2718 "${r}" is ambiguous \u2014 it matches ${o.length} devices:`);for(const l of o)console.error(` ${l.id} ${l.name}`);console.error(" Re-run with more characters, or the full id."),process.exit(1)}async function te(e){S();const r=e.includes("--dry-run"),n=e.includes("--all"),t=e.includes("--force");b()||(console.error("\u26A0 Encryption key not found at",d()),console.error(" A new key will be generated, but it will NOT decrypt deltas"),console.error(" pushed by other devices using a different key. If you have a backup,"),console.error(" restore it to ~/.wyrm/cloud.key (0600) before running sync."),console.error(" Continuing in 5 seconds \u2014 press Ctrl+C to abort."),await new Promise(o=>{setTimeout(o,5e3)})),n&&(console.log("\u{1F310} --all mode: syncing every semantic table (ignoring visibility flags)."),console.log(" Skipped: per-device logs, FTS shadows, sync internals."),console.log(""));const s=await H({dryRun:r,all:n,force:t});if(console.log(""),console.log(s.errors.length>0?"\u{F115D} Sync finished with errors":"\u{F115D} Sync complete"),console.log(""),console.log(` Pushed: ${s.pushed} row(s)${r?" (dry run)":""}`),console.log(` Pulled: ${s.pulled} row(s)`),console.log(` Deleted: ${s.deleted_local} row(s) (peer tombstones)`),s.quarantined&&s.quarantined>0&&console.log(` Quarantined: ${s.quarantined} undecryptable delta(s) set aside (see errors)`),s.errors.length>0){console.log(""),console.log(" Errors:");for(const o of s.errors)console.log(` - ${o}`);process.exitCode=1}console.log("")}function re(){const e=M(W(F(import.meta.url)),"sync-daemon-entrypoint.js");return new q(e)}async function ce(e){S();const r=e[0]??"status",n=e.find(o=>o.startsWith("--interval="));let t;if(n){const o=Number(n.split("=")[1]);(!Number.isFinite(o)||o<=0)&&(console.error("\u2716 --interval= expects a positive number of minutes, got "+JSON.stringify(n)),process.exit(1)),t=o}const s=re();switch(r){case"start":{g()||(console.error("\u2718 Not logged in. Run `wyrm cloud login` first."),process.exit(1));const o=s.start({interval_minutes:t});o.ok||(console.error(`\u{F115D} sovereign-sync daemon: ${o.reason??"failed to start"}${o.pid?` (pid ${o.pid})`:""}`),process.exit(o.pid?0:1)),console.log(`\u{F115D} \u2713 sovereign-sync daemon started (pid ${o.pid}). Logs: ~/.wyrm/wyrm-sovereign-sync.log`);return}case"stop":{const o=s.stop();console.log(o.ok?"\u{F115D} \u2713 sovereign-sync daemon stopped":`\u{F115D} sovereign-sync daemon: ${o.reason??"failed to stop"}`);return}case"restart":{const o=K(t,s.status().state?.interval_ms);s.stop(),await new Promise(i=>{setTimeout(i,500)});const l=s.start({interval_minutes:o});console.log(l.ok?`\u{F115D} \u2713 sovereign-sync daemon restarted (pid ${l.pid}, every ${o} min)`:`\u{F115D} restart failed: ${l.reason??"unknown"}`);return}case"status":{const o=s.status();console.log("\u{F115D} Sovereign-sync daemon"),console.log(` Running: ${o.running?`\u2713 yes (pid ${o.pid})`:"\u2717 no"}`),o.state?.interval_ms&&console.log(` Interval: every ${o.state.interval_ms/6e4} min`),o.state?.started_ts&&console.log(` Started: ${x(o.state.started_ts)}`),o.state?.last_tick_ts&&(console.log(` Last tick: ${x(o.state.last_tick_ts)} (exit ${o.state.last_tick_exit??"\u2014"})`),o.state.last_tick_summary&&console.log(` Last result: ${o.state.last_tick_summary}`));return}default:console.error(`Unknown daemon action: ${r}. Use: start, stop, restart, status`),process.exit(1)}}async function le(e){S();const r=u.fromSession(),n=e.find(l=>!l.startsWith("-")),t=P(n??`wyrm-cloud-export-${new Date().toISOString().slice(0,10)}.json`);console.log(" Fetching account export\u2026");const s=await r.accountExport(),o=JSON.stringify(s,null,2);L(t,o,{mode:384}),console.log(""),console.log(`\u2713 Exported to ${t}`),console.log(""),console.log(` Account: ${s.account.email} (${s.account.id.slice(0,8)}\u2026)`),console.log(` Devices: ${s.devices.length}`),console.log(` Identities: ${s.identities.length}`),console.log(` Orgs: ${s.orgs.length}`),console.log(` Deltas: ${s.deltas.length}`),console.log(` Size: ${_(o.length)}`),console.log(""),console.log(" Deltas are still AES-256-GCM ciphertext. To decrypt them you"),console.log(` need the key at ${d()}. Back that up alongside this file.`),console.log("")}async function ie(e){e.includes("--confirm")||(console.error("\u2718 This permanently deletes your Wyrm Cloud account."),console.error(" Re-run with --confirm to proceed:"),console.error(" wyrm cloud delete --confirm"),process.exit(1));const n=g();n||(console.error("\u2718 Not logged in. Nothing to delete."),process.exit(1)),console.log(""),console.log("\u26A0 You are about to PERMANENTLY DELETE your Wyrm Cloud account."),console.log(""),console.log(` Account: ${n.email??n.account_id}`),console.log(` Server: ${n.base}`),console.log(""),console.log(" This wipes all encrypted blobs, devices, identities, and org"),console.log(" memberships you own. The local ~/.wyrm/cloud.key is kept so"),console.log(" any backups remain decryptable. This action CANNOT be undone."),console.log(""),(await N(' Type "DELETE my account" to confirm: ')).trim()!=="DELETE my account"&&(console.error("\u2718 Confirmation phrase did not match. Aborting."),process.exit(1)),await new u(n.base,n.session).accountDelete(),k(),console.log(""),console.log("\u2713 Account deleted on server."),console.log(`\u2713 Local session removed (${$}).`),console.log(` Kept: ${d()} (in case you need to decrypt an old export).`),console.log("")}function N(e){return new Promise(r=>{const n=B({input:process.stdin,output:process.stdout});n.question(e,t=>{n.close(),r(t)})})}async function ae(e){const r=e[0];if(r==="show"){const{keyToMnemonic:n}=await import("./recovery.js"),s=n().split(" ");console.log(""),console.log("\u{1F511} Wyrm Cloud recovery phrase (BIP39, 24 words)"),console.log("");for(let o=0;o<4;o++){const l=s.slice(o*6,o*6+6).map((i,a)=>`${String(o*6+a+1).padStart(2)}. ${i.padEnd(10)}`).join(" ");console.log(` ${l}`)}console.log(""),console.log(" Write these down on PAPER and keep them somewhere safe."),console.log(" Anyone with this phrase can decrypt your synced Wyrm memory."),console.log(" Wyrm Cloud has NO COPY of this. Lose it = lose your data."),console.log("");return}if(r==="restore"){const n=e.includes("--overwrite");console.log(""),console.log("\u{1F511} Restore master key from BIP39 recovery phrase"),console.log(""),console.log(" Enter your 24 words separated by spaces (case-insensitive)."),console.log(" Press Enter when done."),console.log("");const t=await N(" > "),{mnemonicToKey:s}=await import("./recovery.js"),{path:o,overwrote:l}=s(t,{overwrite:n});console.log(""),console.log(`\u2713 Master key restored to ${o} (0600)`),l&&console.log(" Replaced existing key (you passed --overwrite)."),console.log(" Run `wyrm cloud sync` to pull deltas with the restored key."),console.log("");return}console.error("Usage:"),console.error(" wyrm cloud recovery show"),console.error(" wyrm cloud recovery restore [--overwrite]"),process.exit(1)}export{be as cmdCloud};
@@ -1,2 +1,2 @@
1
- import{spawn as a}from"child_process";import{fileURLToPath as l}from"url";import{dirname as m,join as p}from"path";import{loadSession as d}from"./client.js";import{writeSovereignState as n,DEFAULT_SOVEREIGN_INTERVAL_MS as g}from"./sync-daemon.js";const u=p(m(l(import.meta.url)),"..","wyrm-cli.js");function i(e){process.stdout.write(`[${new Date().toISOString()}] ${e}
2
- `)}function c(){return new Promise(e=>{const s=a(process.execPath,[u,"cloud","sync"],{stdio:["ignore","pipe","pipe"],env:{...process.env}});let o="";s.stdout.on("data",t=>{o+=t.toString()}),s.stderr.on("data",t=>{o+=t.toString()}),s.on("close",t=>{const r=o.replace(/\s+/g," ").trim().slice(0,300);i(`tick exit=${t}: ${r}`),n({last_tick_ts:Date.now(),last_tick_exit:t,last_tick_summary:r}),e()}),s.on("error",t=>{i(`tick spawn error: ${t.message}`),n({last_tick_ts:Date.now(),last_tick_exit:null,last_tick_summary:`spawn error: ${t.message}`}),e()})})}async function _(){const e=Number(process.argv[2])||g,s=d();(!s||!s.device_id)&&(console.error("[sovereign-sync] device not logged in (run `wyrm cloud login`) \u2014 refusing to start"),process.exit(2)),i(`sovereign-sync started \xB7 interval=${e}ms \xB7 device=${s.device_id.slice(0,8)}\u2026`),await c();const o=setInterval(()=>{c()},e),t=r=>{clearInterval(o),i(`${r} \u2014 exiting`),process.exit(0)};process.on("SIGTERM",()=>t("SIGTERM")),process.on("SIGINT",()=>t("SIGINT"))}_().catch(e=>{console.error("[sovereign-sync] fatal:",e),process.exit(1)});
1
+ import{spawn as a}from"child_process";import{fileURLToPath as l}from"url";import{dirname as m,join as p}from"path";import{loadSession as d}from"./client.js";import{writeSovereignState as n,DEFAULT_SOVEREIGN_INTERVAL_MS as _,SOVEREIGN_PID_FILE as g,releasePidFileOnExit as u}from"./sync-daemon.js";const v=p(m(l(import.meta.url)),"..","wyrm-cli.js");function i(e){process.stdout.write(`[${new Date().toISOString()}] ${e}
2
+ `)}function c(){return new Promise(e=>{const s=a(process.execPath,[v,"cloud","sync"],{stdio:["ignore","pipe","pipe"],env:{...process.env}});let o="";s.stdout.on("data",t=>{o+=t.toString()}),s.stderr.on("data",t=>{o+=t.toString()}),s.on("close",t=>{const r=o.replace(/\s+/g," ").trim().slice(0,300);i(`tick exit=${t}: ${r}`),n({last_tick_ts:Date.now(),last_tick_exit:t,last_tick_summary:r}),e()}),s.on("error",t=>{i(`tick spawn error: ${t.message}`),n({last_tick_ts:Date.now(),last_tick_exit:null,last_tick_summary:`spawn error: ${t.message}`}),e()})})}async function I(){u(g);let e;const s=r=>{e&&clearInterval(e),i(`${r} \u2014 exiting`),process.exit(0)};process.on("SIGTERM",()=>s("SIGTERM")),process.on("SIGINT",()=>s("SIGINT"));const o=Number(process.argv[2])||_,t=d();(!t||!t.device_id)&&(console.error("[sovereign-sync] device not logged in (run `wyrm cloud login`) \u2014 refusing to start"),process.exit(2)),i(`sovereign-sync started \xB7 interval=${o}ms \xB7 device=${t.device_id.slice(0,8)}\u2026`),await c(),e=setInterval(()=>{c()},o)}I().catch(e=>{console.error("[sovereign-sync] fatal:",e),process.exit(1)});
@@ -1 +1 @@
1
- import{spawn as g}from"child_process";import{existsSync as o,readFileSync as d,writeFileSync as y,mkdirSync as E,openSync as S,closeSync as p,unlinkSync as m,statSync as v,renameSync as I}from"fs";import{homedir as k}from"os";import{join as a}from"path";const i=a(k(),".wyrm"),c=a(i,"wyrm-sovereign-sync.pid"),r=a(i,"wyrm-sovereign-sync.log"),u=a(i,"wyrm-sovereign-sync.state"),L=1e6,P=0,h=600*1e3;class D{daemonEntrypoint;constructor(t){this.daemonEntrypoint=t,_()}start(t={}){if(this.isAlive())return{ok:!1,pid:this.readPid(),reason:"already running"};N();const n=(t.interval_minutes??h/6e4)*6e4,f=S(r,"a"),l=S(r,"a"),s=g(process.execPath,[this.daemonEntrypoint,String(n)],{detached:!0,stdio:["ignore",f,l],env:{...process.env}});return s.unref(),p(f),p(l),typeof s.pid!="number"?{ok:!1,reason:"spawn returned no pid"}:(y(c,String(s.pid)),w({started_ts:Date.now(),interval_ms:n}),{ok:!0,pid:s.pid})}stop(){if(!this.isAlive())return this.cleanPid(),{ok:!1,reason:"not running"};const t=this.readPid();if(!t)return{ok:!1,reason:"pid file unreadable"};try{return process.kill(t,"SIGTERM"),{ok:!0}}catch(n){return{ok:!1,reason:n?.message??String(n)}}}status(){const t=this.isAlive();return{running:t,pid:t?this.readPid():void 0,state:o(u)?A():void 0}}isAlive(){if(!o(c))return!1;const t=this.readPid();if(!t)return!1;try{return process.kill(t,P),!0}catch{return!1}}readPid(){try{const t=Number(d(c,"utf8").trim());return Number.isFinite(t)&&t>0?t:void 0}catch{return}}cleanPid(){try{m(c)}catch{}}}function G(e,t){return e&&e>0?e:t&&t>0?t/6e4:h/6e4}function w(e){_();let t={};try{t=JSON.parse(d(u,"utf8"))}catch{}y(u,JSON.stringify({...t,...e},null,2))}function _(){o(i)||E(i,{recursive:!0})}function A(){try{return JSON.parse(d(u,"utf8"))}catch{return}}function N(){if(o(r))try{if(v(r).size>L){const e=`${r}.1`;try{m(e)}catch{}I(r,e)}}catch{}}export{h as DEFAULT_SOVEREIGN_INTERVAL_MS,D as SovereignSyncManager,G as restartIntervalMinutes,w as writeSovereignState};
1
+ import{spawn as g}from"child_process";import{existsSync as d,readFileSync as o,writeFileSync as y,mkdirSync as v,openSync as S,closeSync as m,unlinkSync as f,statSync as P,renameSync as I}from"fs";import{homedir as k}from"os";import{basename as x,join as c}from"path";const i=c(k(),".wyrm"),a=c(i,"wyrm-sovereign-sync.pid"),n=c(i,"wyrm-sovereign-sync.log"),u=c(i,"wyrm-sovereign-sync.state"),L=1e6,A=0,h=600*1e3,V=a;function E(t){try{const e=Number(o(t,"utf8").trim());return Number.isInteger(e)&&e>0?e:void 0}catch{return}}function F(t,e){try{process.kill(t,A)}catch{return!1}let r;try{r=o(`/proc/${t}/cmdline`,"utf8")}catch{return!0}return r.includes(e)}function N(t,e){if(E(t)!==e)return!1;try{return f(t),!0}catch{return!1}}function J(t,e=process.pid){process.on("exit",()=>{N(t,e)})}class B{daemonEntrypoint;constructor(e){this.daemonEntrypoint=e,_()}start(e={}){if(this.isAlive())return{ok:!1,pid:this.readPid(),reason:"already running"};this.cleanPid(),R();const r=(e.interval_minutes??h/6e4)*6e4,l=S(n,"a"),p=S(n,"a"),s=g(process.execPath,[this.daemonEntrypoint,String(r)],{detached:!0,stdio:["ignore",l,p],env:{...process.env}});return s.unref(),m(l),m(p),typeof s.pid!="number"?{ok:!1,reason:"spawn returned no pid"}:(y(a,String(s.pid)),O({started_ts:Date.now(),interval_ms:r}),{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(r){return{ok:!1,reason:r?.message??String(r)}}}status(){const e=this.isAlive();return e||this.cleanPid(),{running:e,pid:e?this.readPid():void 0,state:d(u)?w():void 0}}isAlive(){const e=this.readPid();return e!==void 0&&F(e,x(this.daemonEntrypoint))}readPid(){return E(a)}cleanPid(){try{f(a)}catch{}}}function M(t,e){return t&&t>0?t:e&&e>0?e/6e4:h/6e4}function O(t){_();let e={};try{e=JSON.parse(o(u,"utf8"))}catch{}y(u,JSON.stringify({...e,...t},null,2))}function _(){d(i)||v(i,{recursive:!0})}function w(){try{return JSON.parse(o(u,"utf8"))}catch{return}}function R(){if(d(n))try{if(P(n).size>L){const t=`${n}.1`;try{f(t)}catch{}I(n,t)}}catch{}}export{h as DEFAULT_SOVEREIGN_INTERVAL_MS,V as SOVEREIGN_PID_FILE,B as SovereignSyncManager,F as daemonProcessAlive,E as readPidFile,N as releasePidFile,J as releasePidFileOnExit,M as restartIntervalMinutes,O as writeSovereignState};
package/dist/color.js ADDED
@@ -0,0 +1 @@
1
+ function O(e,o){const r=e.FORCE_COLOR;return r!==void 0?!(r==="0"||r.toLowerCase()==="false"):e.NO_COLOR!==void 0&&e.NO_COLOR!==""?!1:o===!0}export{O as shouldUseColor};
package/dist/database.js CHANGED
@@ -1,7 +1,7 @@
1
- import{openDatabase as L}from"./storage/index.js";import{existsSync as u,mkdirSync as O,readdirSync as I,statSync as A,writeFileSync as w,chmodSync as M}from"fs";import{homedir as y}from"os";import{join as d,basename as j,resolve as v,normalize as H}from"path";import{spawnSync as T}from"child_process";import{getResilienceManager as F}from"./resilience.js";import{WyrmLogger as W}from"./logger.js";import{runMigrations as U,schemaSkew as x,getSchemaVersion as P}from"./migrations.js";import{validateProjectPath as N,buildFtsMatchQuery as h,redactCredentials as C}from"./security.js";import{emitEvent as m,eventsSince as B,subscribeEvents as q,isLiveMemoryEnabled as $,ingestRemoteEvent as Y,eventsForPush as G,pruneEvents as V,getMeta as J,setMeta as z}from"./events.js";import{getActor as k}from"./handlers/boundary.js";import{readSkillContent as D,slugify as Q,skillContentSha as K}from"./skill-author.js";import{resolveProjectForPath as Z}from"./project-resolve.js";function me(){return process.env.WYRM_DB_PATH??process.env.WYRM_DB??d(y(),".wyrm","wyrm.db")}function X(f,t){const e=(s,r)=>{try{u(s)&&M(s,r)}catch{}};t&&e(t,448),e(f,384),e(`${f}-wal`,384),e(`${f}-shm`,384)}const ee={truth:{select:`SELECT t.cross_project_visibility AS vis, t.project_id AS pid, p.name AS pname, p.sync_policy AS policy
1
+ import{openDatabase as L}from"./storage/index.js";import{existsSync as u,mkdirSync as O,readdirSync as I,statSync as A,writeFileSync as w,chmodSync as M}from"fs";import{homedir as y}from"os";import{join as d,basename as j,resolve as v,normalize as H}from"path";import{spawnSync as T}from"child_process";import{getResilienceManager as F}from"./resilience.js";import{getStoreLogger as W}from"./logger.js";import{runMigrations as U,schemaSkew as x,getSchemaVersion as P}from"./migrations.js";import{validateProjectPath as N,buildFtsMatchQuery as h,redactCredentials as C}from"./security.js";import{emitEvent as m,eventsSince as B,subscribeEvents as q,isLiveMemoryEnabled as $,ingestRemoteEvent as Y,eventsForPush as G,pruneEvents as V,getMeta as J,setMeta as z}from"./events.js";import{getActor as k}from"./handlers/boundary.js";import{readSkillContent as D,slugify as Q,skillContentSha as K}from"./skill-author.js";import{resolveProjectForPath as Z}from"./project-resolve.js";function me(){return process.env.WYRM_DB_PATH??process.env.WYRM_DB??d(y(),".wyrm","wyrm.db")}function X(f,t){const e=(s,r)=>{try{u(s)&&M(s,r)}catch{}};t&&e(t,448),e(f,384),e(`${f}-wal`,384),e(`${f}-shm`,384)}const ee={truth:{select:`SELECT t.cross_project_visibility AS vis, t.project_id AS pid, p.name AS pname, p.sync_policy AS policy
2
2
  FROM ground_truths t LEFT JOIN projects p ON p.id = t.project_id WHERE t.id = ?`,update:"UPDATE ground_truths SET cross_project_visibility = ?, updated_at = datetime('now') WHERE id = ?"},memory:{select:`SELECT t.cross_project_visibility AS vis, t.project_id AS pid, p.name AS pname, p.sync_policy AS policy
3
3
  FROM memory_artifacts t LEFT JOIN projects p ON p.id = t.project_id WHERE t.id = ?`,update:"UPDATE memory_artifacts SET cross_project_visibility = ?, updated_at = datetime('now') WHERE id = ?"},quest:{select:`SELECT t.cross_project_visibility AS vis, t.project_id AS pid, p.name AS pname, p.sync_policy AS policy
4
- FROM quests t LEFT JOIN projects p ON p.id = t.project_id WHERE t.id = ?`,update:"UPDATE quests SET cross_project_visibility = ?, updated_at = datetime('now') WHERE id = ?"}},te=new Set(["within","org","public"]);class _e{db;BATCH_SIZE=1e3;resilience;logger;dbPath;constructor(t){const e=d(y(),".wyrm");u(e)||O(e,{recursive:!0,mode:448}),this.dbPath=t||d(e,"wyrm.db"),this.logger=new W,this.resilience=F(),this.db=this.initializeDatabase(this.dbPath),X(this.dbPath,e),this.db.pragma("journal_mode = WAL"),this.db.pragma("synchronous = NORMAL"),this.db.pragma("cache_size = -64000"),this.db.pragma("temp_store = MEMORY"),this.db.pragma("busy_timeout = 5000"),this.db.pragma("mmap_size = 268435456"),this.db.pragma("page_size = 4096"),this.db.pragma("foreign_keys = ON"),this.db.pragma("secure_delete = ON");const s=U(this.db),r=x(this.db);r.storeIsNewer&&this.logger.warn(`This store is at schema v${r.store}, newer than this Wyrm knows (v${r.binary}). An older binary is writing to a newer store under older rules. Upgrade Wyrm on this machine.`),s.length>0&&this.logger.info(`Applied ${s.length} migration(s), now at v${P(this.db)}`),this.recoverIncompleteOperations()}getDatabase(){return this.db}getDatabasePath(){return this.dbPath}initializeDatabase(t){const e=this.resilience.withRetrySync(()=>L(t),"database_init",{maxAttempts:3,baseDelayMs:500});if(!e.success)throw this.logger.error("Failed to initialize database",{path:t,error:e.error?.message}),e.error||new Error("Database initialization failed");return e.data}recoverIncompleteOperations(){const t=this.resilience.getIncompleteOperations();for(const e of t)this.logger.warn("Found incomplete operation from previous session",{operation:e.operation,stage:e.stage,id:e.id}),e.operation==="batch_insert"&&this.logger.info("Batch insert was incomplete - data may need re-import"),this.resilience.completeCheckpoint(e.id)}addWatchDir(t,e=!0){return N(t),this.db.prepare(`
4
+ FROM quests t LEFT JOIN projects p ON p.id = t.project_id WHERE t.id = ?`,update:"UPDATE quests SET cross_project_visibility = ?, updated_at = datetime('now') WHERE id = ?"}},te=new Set(["within","org","public"]);class _e{db;BATCH_SIZE=1e3;resilience;logger;dbPath;constructor(t){const e=d(y(),".wyrm");u(e)||O(e,{recursive:!0,mode:448}),this.dbPath=t||d(e,"wyrm.db"),this.logger=W(this.dbPath),this.resilience=F(),this.db=this.initializeDatabase(this.dbPath),X(this.dbPath,e),this.db.pragma("journal_mode = WAL"),this.db.pragma("synchronous = NORMAL"),this.db.pragma("cache_size = -64000"),this.db.pragma("temp_store = MEMORY"),this.db.pragma("busy_timeout = 5000"),this.db.pragma("mmap_size = 268435456"),this.db.pragma("page_size = 4096"),this.db.pragma("foreign_keys = ON"),this.db.pragma("secure_delete = ON");const s=U(this.db),r=x(this.db);r.storeIsNewer&&this.logger.warn(`This store is at schema v${r.store}, newer than this Wyrm knows (v${r.binary}). An older binary is writing to a newer store under older rules. Upgrade Wyrm on this machine.`),s.length>0&&this.logger.info(`Applied ${s.length} migration(s), now at v${P(this.db)}`),this.recoverIncompleteOperations()}getDatabase(){return this.db}getDatabasePath(){return this.dbPath}initializeDatabase(t){const e=this.resilience.withRetrySync(()=>L(t),"database_init",{maxAttempts:3,baseDelayMs:500});if(!e.success)throw this.logger.error("Failed to initialize database",{path:t,error:e.error?.message}),e.error||new Error("Database initialization failed");return e.data}recoverIncompleteOperations(){const t=this.resilience.getIncompleteOperations();for(const e of t)this.logger.warn("Found incomplete operation from previous session",{operation:e.operation,stage:e.stage,id:e.id}),e.operation==="batch_insert"&&this.logger.info("Batch insert was incomplete - data may need re-import"),this.resilience.completeCheckpoint(e.id)}addWatchDir(t,e=!0){return N(t),this.db.prepare(`
5
5
  INSERT INTO watch_dirs (path, recursive)
6
6
  VALUES (?, ?)
7
7
  ON CONFLICT(path) DO UPDATE SET recursive = excluded.recursive
@@ -30,7 +30,7 @@ import{openDatabase as L}from"./storage/index.js";import{existsSync as u,mkdirSy
30
30
  INSERT INTO sessions (project_id, date, objectives, completed, issues, commits, files_changed, notes, tokens_estimate, agent_id, run_id)
31
31
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
32
32
  RETURNING *
33
- `).get(t,e.date||new Date().toISOString().split("T")[0],e.objectives||"",e.completed||"",e.issues||"",e.commits||"",e.files_changed||"",e.notes||"",s,r.agent_id,r.run_id),"createSession");if(!i.success)throw i.error||new Error("Failed to create session");return m(this.db,{projectId:t,kind:"session_update",refTable:"sessions",refId:i.data.id}),i.data}updateSession(t,e){const s=[],r=[],i=new Set(["objectives","completed","issues","commits","files_changed","notes","summary","is_archived"]);for(const[a,o]of Object.entries(e))i.has(a)&&(s.push(`${a} = ?`),r.push(o));if(s.length===0)return this.getSession(t);if(e.objectives||e.completed||e.issues||e.notes){const a=this.getSession(t);if(a){const o=this.estimateTokens((e.objectives||a.objectives)+(e.completed||a.completed)+(e.issues||a.issues)+(e.notes||a.notes));s.push("tokens_estimate = ?"),r.push(o)}}r.push(t);const n=this.resilience.withRetrySync(()=>this.db.prepare(`
33
+ `).get(t,e.date||new Date().toISOString().split("T")[0],e.objectives||"",e.completed||"",e.issues||"",e.commits||"",e.files_changed||"",e.notes||"",s,r.agent_id,r.run_id),"createSession");if(!i.success)throw i.error||new Error("Failed to create session");return m(this.db,{projectId:t,kind:"session_update",refTable:"sessions",refId:i.data.id}),i.data}updateSession(t,e){const s=[],r=[],i=new Set(["objectives","completed","issues","commits","files_changed","notes","summary","is_archived"]);for(const[a,o]of Object.entries(e))i.has(a)&&o!==void 0&&(s.push(`${a} = ?`),r.push(o));if(s.length===0)return this.getSession(t);if(e.objectives||e.completed||e.issues||e.notes){const a=this.getSession(t);if(a){const o=this.estimateTokens((e.objectives||a.objectives)+(e.completed||a.completed)+(e.issues||a.issues)+(e.notes||a.notes));s.push("tokens_estimate = ?"),r.push(o)}}r.push(t);const n=this.resilience.withRetrySync(()=>this.db.prepare(`
34
34
  UPDATE sessions SET ${s.join(", ")} WHERE id = ? RETURNING *
35
35
  `).get(...r),"updateSession");if(!n.success)throw n.error||new Error("Failed to update session");return m(this.db,{projectId:n.data.project_id,kind:"session_update",refTable:"sessions",refId:t}),n.data}getSession(t){return this.db.prepare("SELECT * FROM sessions WHERE id = ?").get(t)}updateContinuity(t,e,s){const r=e.trim();if(!r)return;const i=12e3,n=this.db.prepare("SELECT thread, turn_count FROM project_continuity WHERE project_id = ?").get(t),a=new Date().toISOString(),o=`[${a}${s?" \xB7 "+s:""}] ${r}`;let c=n?.thread?n.thread+`
36
36
 
@@ -1,2 +1,2 @@
1
- import{renderErrorResponse as c}from"./render.js";const i={wyrm_setup:{cli:"wyrm setup"},wyrm_embed:{cli:"wyrm embed"},wyrm_inject_prompt:{cli:"wyrm prompt inject"},wyrm_migrate_prompt:{cli:"wyrm prompt migrate"},wyrm_check_update:{cli:"wyrm update --check"},wyrm_self_update:{cli:"wyrm update"},wyrm_vector_setup:{cli:"wyrm index setup"},wyrm_reindex:{cli:"wyrm index rebuild"},wyrm_encrypt_setup:{cli:"wyrm setup --encrypt"},wyrm_license:{cli:"wyrm license"},wyrm_activate:{cli:"wyrm activate <key>"},wyrm_hours_report:{cli:"wyrm hours report"},wyrm_invoice_generate:{cli:"wyrm invoice generate"},wyrm_agent_init:{cli:"wyrm agent init"},wyrm_agent_status:{cli:"wyrm agent status"},wyrm_agent_stop:{cli:"wyrm agent stop"},wyrm_agent_restart:{cli:"wyrm agent restart"},wyrm_intro:{cli:"wyrm intro"},wyrm_sync_export:{cli:"wyrm sync export --out <path>",functionalThrough:"7.0.x"},wyrm_sync_import:{cli:"wyrm sync import --from <path>"},wyrm_cloud_backup:{cli:"wyrm cloud backup",functionalThrough:"7.0.x"},wyrm_cloud_sync:{cli:"wyrm cloud sync"}};function o(r){const e=i[r];if(!e)return"";const t=e.functionalThrough?` \u2014 remains functional through ${e.functionalThrough}`:"";return` [Deprecated in v7 \u2192 use the wyrm CLI: ${e.cli}${t}]`}function y(r){return r.map(e=>{const t=o(e.name);return t?{...e,description:`${e.description??""}${t}`}:e})}const n=new Set;function m(){n.clear()}function a(r){try{const e=i[r];if(!e||n.has(r))return;n.add(r);const t=e.functionalThrough?` This name stays functional through ${e.functionalThrough} (published sunset) so scheduled jobs never silently stop.`:"";process.stderr.write(`[wyrm] DEPRECATION: '${r}' moves off MCP to the wyrm CLI in 7.0 \u2014 use: ${e.cli}. Behavior is unchanged in 6.18.${t}
2
- `)}catch{}}const s="WYRM_CLI_EXILE";function p(r){const e=i[r];return e!==void 0&&e.functionalThrough===void 0}function l(r){const e=i[r];if(!e)throw new Error(`cliExileRedirectBody: '${r}' is not a CLI-exile tool`);return{error:{code:s,message:`${r} moved off MCP in 7.0 \u2014 run: ${e.cli}`,retryable:!1},cli:e.cli,tool:r,expected:`This operator/egress tool is CLI-exiled (spec FR-4): its MCP alias never executes in 7.0. Run \`${e.cli}\` from a shell instead \u2014 do not retry this MCP call (the redirect is deterministic). The wyrm CLI ships the full replacement; \`wyrm --help\` lists it.`}}function w(r){return c(l(r))}export{i as CLI_EXILE_TOOLS,s as WYRM_CLI_EXILE_CODE,m as _resetDeprecationWarningsForTests,y as applyDeprecationNotices,l as cliExileRedirectBody,w as cliExileRedirectResponse,o as deprecationSuffix,p as isHardCliExile,a as noteDeprecatedToolInvocation};
1
+ import{renderErrorResponse as c}from"./render.js";const i={wyrm_setup:{cli:"wyrm setup"},wyrm_embed:{cli:"wyrm embed"},wyrm_inject_prompt:{cli:"wyrm prompt inject"},wyrm_migrate_prompt:{cli:"wyrm prompt migrate"},wyrm_check_update:{cli:"wyrm update --check"},wyrm_self_update:{cli:"wyrm update"},wyrm_vector_setup:{cli:"wyrm index setup"},wyrm_reindex:{cli:"wyrm index rebuild"},wyrm_encrypt_setup:{cli:"wyrm setup --encrypt"},wyrm_license:{cli:"wyrm license"},wyrm_activate:{cli:"wyrm activate <key>"},wyrm_hours_report:{cli:"wyrm hours report"},wyrm_invoice_generate:{cli:"wyrm invoice generate"},wyrm_agent_init:{cli:"wyrm agent init"},wyrm_agent_status:{cli:"wyrm agent status"},wyrm_agent_stop:{cli:"wyrm agent stop"},wyrm_agent_restart:{cli:"wyrm agent restart"},wyrm_intro:{cli:"wyrm intro"},wyrm_sync_export:{cli:"wyrm sync export --out <path>",functionalThrough:"7.0.x"},wyrm_sync_import:{cli:"wyrm sync import --from <path>"},wyrm_cloud_backup:{cli:"wyrm cloud backup",functionalThrough:"7.0.x"},wyrm_cloud_sync:{cli:"wyrm cloud sync"}};function o(r){const e=i[r];if(!e)return"";const t=e.functionalThrough?`; remains functional through ${e.functionalThrough}`:"";return` [Deprecated in v7 \u2192 use the wyrm CLI: ${e.cli}${t}]`}function y(r){return r.map(e=>{const t=o(e.name);return t?{...e,description:`${e.description??""}${t}`}:e})}const n=new Set;function a(){n.clear()}function m(r){try{const e=i[r];if(!e||n.has(r))return;n.add(r);const t=e.functionalThrough?` This name stays functional through ${e.functionalThrough} (published sunset) so scheduled jobs never silently stop.`:"";process.stderr.write(`[wyrm] DEPRECATION: '${r}' moves off MCP to the wyrm CLI in 7.0; use: ${e.cli}. Behavior is unchanged in 6.18.${t}
2
+ `)}catch{}}const s="WYRM_CLI_EXILE";function p(r){const e=i[r];return e!==void 0&&e.functionalThrough===void 0}function l(r){const e=i[r];if(!e)throw new Error(`cliExileRedirectBody: '${r}' is not a CLI-exile tool`);return{error:{code:s,message:`${r} moved off MCP in 7.0; run: ${e.cli}`,retryable:!1},cli:e.cli,tool:r,expected:`This operator/egress tool is CLI-exiled (spec FR-4): its MCP alias never executes in 7.0. Run \`${e.cli}\` from a shell instead, and do not retry this MCP call (the redirect is deterministic). The wyrm CLI ships the full replacement; \`wyrm --help\` lists it.`}}function w(r){return c(l(r))}export{i as CLI_EXILE_TOOLS,s as WYRM_CLI_EXILE_CODE,a as _resetDeprecationWarningsForTests,y as applyDeprecationNotices,l as cliExileRedirectBody,w as cliExileRedirectResponse,o as deprecationSuffix,p as isHardCliExile,m as noteDeprecatedToolInvocation};
package/dist/events.js CHANGED
@@ -1,18 +1,18 @@
1
- import{randomUUID as g}from"node:crypto";import{hostname as m}from"node:os";import{logger as l}from"./logger.js";import{getActor as R}from"./handlers/boundary.js";const I=new Set(["capture","session_update","quest","truth","presence","tool_call","failure","decision"]),T=64*1024,y=80,d=256,_=864e5,p=r=>typeof r=="number"&&Number.isInteger(r)&&r>=0&&r<=Number.MAX_SAFE_INTEGER,s=(r,t)=>typeof r=="string"?r.slice(0,t):null;function O(){const r=(process.env.WYRM_LIVE_MEMORY||"").toLowerCase();return r!=="0"&&r!=="false"&&r!=="off"}const f=new WeakMap;function S(r){const t=f.get(r);if(t)return t;const e=()=>r.prepare("SELECT value FROM wyrm_meta WHERE key = 'device_id'").get()?.value;let n=e();if(!n){const o=`${m().slice(0,24)}-${g().slice(0,8)}`;r.prepare("INSERT OR IGNORE INTO wyrm_meta (key, value) VALUES ('device_id', ?)").run(o),n=e()||o}return f.set(r,n),n}function a(r,t,e,n){const o=typeof r=="number"&&Number.isFinite(r)?Math.floor(r):n;return Math.max(t,Math.min(e,o))}function C(r,t){if(O())try{const e=S(r),n=t.isShared?1:0;let o=null;if(n&&t.payload!==void 0)try{o=JSON.stringify(t.payload)}catch{o=null}const i=R(),c=t.agentId!==void 0?t.agentId:i.agent_id,u=t.runId!==void 0?t.runId:i.run_id;r.prepare(`
1
+ import{randomUUID as m}from"node:crypto";import{hostname as R}from"node:os";import{logger as p}from"./logger.js";import{getActor as T}from"./handlers/boundary.js";const I=new Set(["capture","session_update","quest","truth","presence","tool_call","failure","decision"]),y=64*1024,O=80,u=256,f=864e5,_=e=>typeof e=="number"&&Number.isInteger(e)&&e>=0&&e<=Number.MAX_SAFE_INTEGER,s=(e,t)=>typeof e=="string"?e.slice(0,t):null;function M(){const e=(process.env.WYRM_LIVE_MEMORY||"").toLowerCase();return e!=="0"&&e!=="false"&&e!=="off"}const S=new WeakMap;function g(e){const t=S.get(e);if(t)return t;const r=()=>e.prepare("SELECT value FROM wyrm_meta WHERE key = 'device_id'").get()?.value;let n=r();if(!n){const o=`${R().slice(0,24)}-${m().slice(0,8)}`;e.prepare("INSERT OR IGNORE INTO wyrm_meta (key, value) VALUES ('device_id', ?)").run(o),n=r()||o}return S.set(e,n),n}function c(e,t,r,n){const o=typeof e=="number"&&Number.isFinite(e)?Math.floor(e):n;return Math.max(t,Math.min(r,o))}function C(e,t){if(M())try{const r=g(e),n=t.isShared?1:0;let o=null;if(n&&t.payload!==void 0)try{o=JSON.stringify(t.payload)}catch{o=null}const i=T(),a=t.agentId!==void 0?t.agentId:i.agent_id,E=t.runId!==void 0?t.runId:i.run_id;e.prepare(`
2
2
  INSERT INTO events
3
3
  (origin_device, origin_seq, project_id, kind, ref_table, ref_id, actor, is_shared, payload_json, created_at, agent_id, run_id)
4
4
  VALUES (?, (SELECT COALESCE(MAX(origin_seq), 0) + 1 FROM events WHERE origin_device = ?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
5
- `).run(e,e,String(t.projectId),t.kind,t.refTable??null,t.refId!=null?String(t.refId):null,t.actor??null,n,o,Date.now(),c,u)}catch(e){l.warn("live-memory: emitEvent dropped",{err:e instanceof Error?e.message:String(e)})}}function D(r,t,e=0,n=100){const o=a(n,1,1e3,100),i=a(e,0,Number.MAX_SAFE_INTEGER,0);return r.prepare("SELECT * FROM events WHERE project_id = ? AND cursor > ? ORDER BY cursor ASC LIMIT ?").all(String(t),i,o)}function F(r,t,e=20){const n=a(e,1,200,20),o=r.prepare("SELECT * FROM events WHERE project_id = ? ORDER BY cursor DESC LIMIT ?").all(String(t),n);return o.reverse(),{cursor:r.prepare("SELECT COALESCE(MAX(cursor), 0) AS c FROM events WHERE project_id = ?").get(String(t)).c,recent:o}}function j(r,t,e){try{if(!e||typeof e.origin_device!="string"||!p(e.origin_seq))return"duplicate";const n=s(e.origin_device,y);if(!n)return"duplicate";if(n===S(r))return"echo";if(typeof e.kind!="string"||!I.has(e.kind))return"duplicate";const o=e.is_shared?1:0;let i=o&&typeof e.payload_json=="string"?e.payload_json:null;i&&i.length>T&&(i=null);const c=Date.now(),u=p(e.created_at)&&e.created_at<=c+_?e.created_at:c,E=p(e.cloud_cursor)?e.cloud_cursor:null;return r.prepare(`
5
+ `).run(r,r,String(t.projectId),t.kind,t.refTable??null,t.refId!=null?String(t.refId):null,t.actor??null,n,o,Date.now(),a,E)}catch(r){p.warn("live-memory: emitEvent dropped",{err:r instanceof Error?r.message:String(r)})}}function D(e,t,r=0,n=100){const o=c(n,1,1e3,100),i=c(r,0,Number.MAX_SAFE_INTEGER,0);return e.prepare("SELECT * FROM events WHERE project_id = ? AND cursor > ? ORDER BY cursor ASC LIMIT ?").all(String(t),i,o)}function F(e,t,r=20){const n=c(r,1,200,20),o=e.prepare("SELECT * FROM events WHERE project_id = ? ORDER BY cursor DESC LIMIT ?").all(String(t),n);return o.reverse(),{cursor:e.prepare("SELECT COALESCE(MAX(cursor), 0) AS c FROM events WHERE project_id = ?").get(String(t)).c,recent:o}}function v(e,t,r){try{if(!r||typeof r.origin_device!="string"||!_(r.origin_seq))return"duplicate";const n=s(r.origin_device,O);if(!n)return"duplicate";if(n===g(e))return"echo";if(typeof r.kind!="string"||!I.has(r.kind))return"duplicate";const o=r.is_shared?1:0;let i=o&&typeof r.payload_json=="string"?r.payload_json:null;i&&i.length>y&&(i=null);const a=Date.now(),E=_(r.created_at)&&r.created_at<=a+f?r.created_at:a,d=_(r.cloud_cursor)?r.cloud_cursor:null;return e.prepare(`
6
6
  INSERT OR IGNORE INTO events
7
7
  (origin_device, origin_seq, cloud_cursor, project_id, kind, ref_table, ref_id, actor, is_shared, payload_json, created_at, agent_id, run_id)
8
8
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
9
- `).run(n,e.origin_seq,E,String(t),e.kind,s(e.ref_table,d),s(e.ref_id,d),s(e.actor,d),o,i,u,s(e.agent_id,d),s(e.run_id,d)).changes>0?"inserted":"duplicate"}catch(n){return l.warn("live-memory: ingestRemoteEvent dropped",{err:n instanceof Error?n.message:String(n)}),"duplicate"}}function v(r,t,e=0,n=200){const o=a(n,1,1e3,200),i=a(e,0,Number.MAX_SAFE_INTEGER,0);try{const c=r.prepare("SELECT sync_policy AS p FROM projects WHERE CAST(id AS TEXT) = ? OR path = ? LIMIT 1").get(String(t),String(t));if(c&&c.p&&c.p!=="team")return[]}catch{}return r.prepare(`
9
+ `).run(n,r.origin_seq,d,String(t),r.kind,s(r.ref_table,u),s(r.ref_id,u),s(r.actor,u),o,i,E,s(r.agent_id,u),s(r.run_id,u)).changes>0?"inserted":"duplicate"}catch(n){return p.warn("live-memory: ingestRemoteEvent dropped",{err:n instanceof Error?n.message:String(n)}),"duplicate"}}function j(e,t,r=0,n=200){const o=c(n,1,1e3,200),i=c(r,0,Number.MAX_SAFE_INTEGER,0);try{const a=e.prepare("SELECT sync_policy AS p FROM projects WHERE CAST(id AS TEXT) = ? OR path = ? LIMIT 1").get(String(t),String(t));if(a&&a.p&&a.p!=="team")return[]}catch{}return e.prepare(`
10
10
  SELECT * FROM events
11
11
  WHERE project_id = ? AND is_shared = 1 AND cursor > ?
12
12
  ORDER BY cursor ASC LIMIT ?
13
- `).all(String(t),i,o)}function w(r,t={}){const e=a(t.olderThanDays,1,3650,90),n=a(t.maxPerProject,1,1e6,5e3);try{let o=0;const i=Date.now()-e*_;o+=r.prepare("DELETE FROM events WHERE created_at < ?").run(i).changes;const c=r.prepare("SELECT project_id FROM events GROUP BY project_id HAVING COUNT(*) > ?").all(n),u=r.prepare(`
13
+ `).all(String(t),i,o)}function w(e,t={}){const r=c(t.olderThanDays,1,3650,90),n=c(t.maxPerProject,1,1e6,5e3);try{if(!e.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'events'").get())return{deleted:0};let i=0;const a=Date.now()-r*f;i+=e.prepare("DELETE FROM events WHERE created_at < ?").run(a).changes;const E=e.prepare("SELECT project_id FROM events GROUP BY project_id HAVING COUNT(*) > ?").all(n),d=e.prepare(`
14
14
  DELETE FROM events WHERE project_id = ? AND cursor <= (
15
15
  SELECT cursor FROM events WHERE project_id = ? ORDER BY cursor DESC LIMIT 1 OFFSET ?
16
16
  )
17
- `);for(const{project_id:E}of c)o+=u.run(E,E,n).changes;return{deleted:o}}catch(o){return l.warn("live-memory: pruneEvents failed",{err:o instanceof Error?o.message:String(o)}),{deleted:0}}}function x(r,t){return r.prepare("SELECT value FROM wyrm_meta WHERE key = ?").get(t)?.value}function W(r,t,e){r.prepare(`INSERT INTO wyrm_meta (key, value) VALUES (?, ?)
18
- ON CONFLICT(key) DO UPDATE SET value = excluded.value`).run(t,e)}export{C as emitEvent,v as eventsForPush,D as eventsSince,S as getDeviceId,x as getMeta,j as ingestRemoteEvent,O as isLiveMemoryEnabled,w as pruneEvents,W as setMeta,F as subscribeEvents};
17
+ `);for(const{project_id:l}of E)i+=d.run(l,l,n).changes;return{deleted:i}}catch(o){return p.warn("live-memory: pruneEvents failed",{db:e.name,err:o instanceof Error?o.message:String(o)}),{deleted:0}}}function W(e,t){return e.prepare("SELECT value FROM wyrm_meta WHERE key = ?").get(t)?.value}function x(e,t,r){e.prepare(`INSERT INTO wyrm_meta (key, value) VALUES (?, ?)
18
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value`).run(t,r)}export{C as emitEvent,j as eventsForPush,D as eventsSince,g as getDeviceId,W as getMeta,v as ingestRemoteEvent,M as isLiveMemoryEnabled,w as pruneEvents,x as setMeta,F as subscribeEvents};