wyrm-mcp 9.2.3 → 9.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/BENCHMARKS.md +8 -2
  2. package/COMPARISON.md +5 -0
  3. package/README.md +5 -3
  4. package/dist/autoconfig.js +33 -32
  5. package/dist/backup-cli.js +5 -5
  6. package/dist/backup.js +1 -1
  7. package/dist/bridge.js +1 -1
  8. package/dist/buddy.js +10 -10
  9. package/dist/cli.js +13 -13
  10. package/dist/cloud/cli.js +4 -4
  11. package/dist/cloud/client.js +1 -1
  12. package/dist/cloud/machine-id.js +2 -2
  13. package/dist/database.js +24 -14
  14. package/dist/deprecations.js +1 -1
  15. package/dist/erasure-cli.js +3 -3
  16. package/dist/handlers/cloud.js +11 -11
  17. package/dist/handlers/companion.js +10 -10
  18. package/dist/handlers/events.js +11 -9
  19. package/dist/http-fast.js +5 -5
  20. package/dist/index.js +2 -2
  21. package/dist/is-main.js +1 -0
  22. package/dist/license-refresh.js +1 -1
  23. package/dist/license.js +2 -2
  24. package/dist/live-embedding-state.js +2 -2
  25. package/dist/project-resolve.js +1 -1
  26. package/dist/revocations.js +1 -1
  27. package/dist/setup-wizard.js +4 -4
  28. package/dist/setup.js +2 -2
  29. package/dist/statusline-endpoint.js +1 -0
  30. package/dist/statusline.js +10 -10
  31. package/dist/storage/index.js +3 -3
  32. package/dist/tool-manifest-v2.json +1 -1
  33. package/dist/tool-manifest.json +1 -1
  34. package/dist/wyrm-cli.js +91 -88
  35. package/dist/wyrm-guard.js +9 -9
  36. package/dist/wyrm-manifest.json +2 -2
  37. package/dist/wyrm-memo.js +4 -4
  38. package/dist/wyrm-ui.js +7 -7
  39. package/package.json +8 -3
  40. package/scripts/hooks/wyrm-prompt-context.mjs +63 -5
  41. package/scripts/hooks/wyrm-run-auto.mjs +36 -4
  42. package/scripts/hooks/wyrm-session-capture.mjs +35 -4
  43. package/scripts/hooks/wyrm-session-prune.mjs +35 -4
  44. package/scripts/hooks/wyrm-session-rehydrate.mjs +68 -12
  45. package/scripts/hooks/wyrm-tool-call-trace.mjs +56 -6
  46. package/scripts/smoke-platform.mjs +162 -0
  47. package/skills/wyrm-memory-and-recall/SKILL.md +8 -5
  48. package/dist/cors.js +0 -1
package/dist/cli.js CHANGED
@@ -1,25 +1,25 @@
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`
1
+ import{existsSync as b,readFileSync as w}from"fs";import{dirname as M,join as y,win32 as g}from"path";import{fileURLToPath as S}from"url";import{shouldUseColor as f}from"./color.js";function x(){try{const t=M(S(import.meta.url));return JSON.parse(w(y(t,"..","package.json"),"utf-8")).version??"dev"}catch{return"dev"}}const v=f(process.env,process.stdout.isTTY),C=t=>v?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,C(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}`},h={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 N(){return`
2
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
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
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
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
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
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}
8
+ ${e.dim} Persistent AI Memory System v${x()}${e.reset}
9
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(`
10
+ `}function T(){return`${e.brightMagenta}\u{F115D} Wyrm v${x()}${e.reset}`}class _{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 G{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 O(t,n,o={}){const{padding:r=2,border:s=!0}=o,i=t.map((c,l)=>{const u=Math.max(...n.map(d=>(d[l]||"").length));return Math.max(c.length,u)}),a=(c,l)=>c.padEnd(l),p=i.map(c=>"\u2500".repeat(c+r)).join(s?"\u253C":" ");let m="";s&&(m+=`${e.dim}\u250C${i.map(c=>"\u2500".repeat(c+r)).join("\u252C")}\u2510${e.reset}
11
+ `),m+=(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&&(m+=`${e.dim}\u251C${p}\u2524${e.reset}
13
+ `);for(const c of n)m+=(s?`${e.dim}\u2502${e.reset}`:"")+c.map((l,u)=>` ${a(l||"",i[u])} `).join(s?`${e.dim}\u2502${e.reset}`:"")+(s?`${e.dim}\u2502${e.reset}`:"")+`
14
+ `;return s&&(m+=`${e.dim}\u2514${i.map(c=>"\u2500".repeat(c+r)).join("\u2534")}\u2518${e.reset}`),m}function Y(t,n,o=50){const r=n.split(`
15
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
16
  `;i+=`${e.dim}\u2502${e.reset} ${$.bold(t.padEnd(s))} ${e.dim}\u2502${e.reset}
17
17
  `,i+=`${e.dim}\u251C${"\u2500".repeat(s+2)}\u2524${e.reset}
18
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)})
19
+ `;return i+=`${e.dim}\u2570${"\u2500".repeat(s+2)}\u256F${e.reset}`,i}function j(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 D(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 F(t){console.log(`
20
+ ${$.bold($.primary(`\u25B6 ${t}`))}`),console.log(`${e.dim}${"\u2500".repeat(t.length+4)}${e.reset}`)}function W(t){console.log(`${h.success} ${$.success(t)}`)}function K(t){console.error(`${h.error} ${$.error(t)}`)}function U(t){console.log(`${h.warning} ${$.warning(t)}`)}function q(t){console.log(`${h.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 J(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 V(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 z(t,n,o=b){if(t!=="win32")return{command:"npm",prefixArgs:[]};const r=g.join(g.dirname(n),"node_modules","npm","bin","npm-cli.js");return o(r)?{command:n,prefixArgs:[r]}:{command:"npm",prefixArgs:[]}}function H(t){if(t.isTTY){let r=-1;return{onProgress:(s,i,a)=>{const p=Math.floor(i/a*100);p!==r&&(r=p,t.write(`\r ${s}: ${p}% `))},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 (${j(i)})
22
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};
23
+ `))},finish:()=>{}}}class k extends Error{constructor(){super("readSecret canceled"),this.name="ReadSecretCanceled"}}async function Q(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,p)=>{const m=u=>{for(const d of u){if(d===13||d===10){l(),r.write(`
24
+ `),a(i);return}if(d===3){l(),p(new k);return}if(d===4){l(),r.write(`
25
+ `),a(i);return}if(d===127||d===8){i.length>0&&(i=i.slice(0,-1),r.write("\b \b"));continue}i+=String.fromCharCode(d),r.write("*")}},c=()=>{l(),a(i)};function l(){s&&o.setRawMode(!1),o.pause(),o.removeListener("data",m),o.removeListener("end",c)}o.on("data",m),o.on("end",c)})}export{N as BANNER,T as MINI_BANNER,G as ProgressBar,k as ReadSecretCanceled,_ as Spinner,$ as c,e as colors,H as createDownloadProgress,B as editDistance,Y as formatBox,j as formatBytes,D as formatDuration,O as formatTable,h as icons,V as npmGlobalPrefixFromPath,z as npmInvocation,K as printError,q as printInfo,F as printSection,W as printSuccess,U as printWarning,L as priorityIcons,Q as readSecret,f as shouldUseColor,J 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 P}from"node:os";import{resolve as O,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":"never"}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: sync your Wyrm memory across devices
1
+ import{randomBytes as A}from"node:crypto";import{writeFileSync as L}from"node:fs";import{hostname as P}from"node:os";import{resolve as O,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,cloudMachineId as H}from"./machine-id.js";import{runSync as Y,CopiedSessionError as q}from"./sync-engine.js";import{SovereignSyncManager as K,restartIntervalMinutes as J}from"./sync-daemon.js";import{initializeLicense as z,hasFeature as V,getTier as Q}from"../license.js";function S(){z(),V("cloud_backup")||(console.error("\u{F115D} Wyrm Cloud sync requires a Pro license or higher."),console.error(` Current tier: ${Q()}`),console.error(" Get Pro: write to support@ghosts.lk"),console.error(" Details: 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":"never"}async function Se(e){const r=e[0],n=e.slice(1);try{switch(r){case void 0:case"help":case"--help":case"-h":return X();case"login":return await Z(n);case"logout":return await ee();case"status":return await oe();case"doctor":return se();case"devices":return await ne(n);case"sync":return await re(n);case"daemon":return await le(n);case"export":return await ie(n);case"delete":return await ae(n);case"recovery":return await de(n);default:console.error(`Unknown subcommand: wyrm cloud ${r}`),console.error("Try `wyrm cloud help`"),process.exit(1)}}catch(t){if(t instanceof q&&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 X(){console.log(`\u{F115D} wyrm cloud: 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 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
- \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=`${P()} (${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; 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: a connected pipe that carries nothing."),console.log(""),console.log(" Fix: adopt the account key on this device by running"),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: ${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: no fingerprint yet (pre-7.0.3 session; adopted on next sync)"):(console.log(" Identity: \u26A0 FINGERPRINT MISMATCH: 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: 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; 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, 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; 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. 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??"n/a"})`),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=O(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};
40
+ `)}async function Z(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
+ \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=`${P()} (${process.platform})`;let p;try{p=(await y.registerDevice(f,void 0,H())).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; 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: a connected pipe that carries nothing."),console.log(""),console.log(" Fix: adopt the account key on this device by running"),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 ee(){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 oe(){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: ${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: no fingerprint yet (pre-7.0.3 session; adopted on next sync)"):(console.log(" Identity: \u26A0 FINGERPRINT MISMATCH: 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 se(){const e=g();if(console.log(""),console.log("\u{F115D} Wyrm Cloud: 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; 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, 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 ne(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 te(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 te(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; 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 re(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. 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 Y({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 ce(){const e=M(W(F(import.meta.url)),"sync-daemon-entrypoint.js");return new K(e)}async function le(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=ce();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=J(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??"n/a"})`),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 ie(e){S();const r=u.fromSession(),n=e.find(l=>!l.startsWith("-")),t=O(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 ae(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 de(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{Se as cmdCloud};
@@ -1 +1 @@
1
- import{readFileSync as w,writeFileSync as g,existsSync as S,mkdirSync as b,chmodSync as E,unlinkSync as O}from"node:fs";import{join as y}from"node:path";import{homedir as T}from"node:os";const k=process.env.WYRM_CLOUD_URL??"https://wyrm.ghosts.lk";function d(){return process.env.WYRM_CLOUD_DIR??y(T(),".wyrm")}const x=d(),N=y(x,"cloud.json");function m(){return y(d(),"cloud.json")}function q(){const s=d();S(s)||b(s,{recursive:!0,mode:448})}function D(){const s=m();if(!S(s))return null;try{return JSON.parse(w(s,"utf-8"))}catch{return null}}function L(s){q();const t=m();g(t,JSON.stringify(s,null,2),{mode:384});try{E(t,384)}catch{}}function R(){try{O(m())}catch{}}class u extends Error{status;body;constructor(t,e,r){super(r),this.status=t,this.body=e,this.name="CloudError"}}class v{base;bearer;constructor(t,e){this.base=t,this.bearer=e}static fromSession(){const t=D();if(!t)throw new u(401,null,"Not logged in. Run `wyrm cloud login` first.");return new v(t.base,t.session)}async req(t,e={}){const r={"content-type":"application/json","user-agent":"wyrm-cli/cloud",...e.headers??{}};this.bearer&&(r.authorization=`Bearer ${this.bearer}`);const n=4;let l=null;for(let i=0;i<=n;i++){const p=new AbortController,f=setTimeout(()=>p.abort(),6e4);let o;try{o=await fetch(`${this.base}${t}`,{...e,headers:r,signal:p.signal})}catch(c){if(clearTimeout(f),l=c.name==="AbortError"?new u(0,null,`Timed out reaching ${this.base}${t} after 60s`):new u(0,null,`Network error: ${c instanceof Error?c.message:c}`),i<n){await this.backoff(i,null);continue}throw l}if(clearTimeout(f),(o.status===429||o.status>=500)&&i<n){await this.backoff(i,o.headers.get("retry-after"));continue}const h=await o.text();let a=null;try{a=h?JSON.parse(h):null}catch{a=h}if(!o.ok){const c=a?.message??a?.error??`HTTP ${o.status}`;throw new u(o.status,a,c)}return a}throw l??new u(0,null,`request to ${t} failed after ${n} retries`)}async backoff(t,e){let r;e&&/^\d+$/.test(e)?r=Math.min(3e4,parseInt(e,10)*1e3):(r=Math.min(3e4,250*Math.pow(2,t)),r+=Math.floor(Math.random()*(r*.25))),await new Promise(n=>{setTimeout(n,r)})}async cliInit(t){return this.req("/api/v1/auth/cli-init",{method:"POST",body:JSON.stringify({poll_token:t})})}async cliPoll(t){return this.req("/api/v1/auth/cli-poll",{method:"POST",body:JSON.stringify({poll_token:t})})}async me(){return this.req("/api/v1/auth/me")}async logout(){return this.req("/api/v1/auth/logout",{method:"POST"})}async registerDevice(t,e){return this.req("/api/v1/devices",{method:"POST",body:JSON.stringify({name:t,pubkey:e})})}async listDevices(){return this.req("/api/v1/devices")}async revokeDevice(t){return this.req(`/api/v1/devices/${encodeURIComponent(t)}`,{method:"DELETE"})}async syncStatus(){return this.req("/api/v1/sync/status")}async syncPush(t,e){return this.req("/api/v1/sync/push",{method:"POST",body:JSON.stringify({device_id:t,deltas:e})})}async accountExport(){return this.req("/api/v1/account/export")}async accountDelete(){return this.req("/api/v1/account",{method:"DELETE",body:JSON.stringify({confirm:"DELETE my account"})})}async syncPull(t,e,r=100){const n=new URLSearchParams({device_id:t,since:String(e),limit:String(r)});return this.req(`/api/v1/sync/pull?${n.toString()}`)}}export{x as CLOUD_DIR,v as CloudClient,u as CloudError,k as DEFAULT_BASE,N as SESSION_FILE,R as clearSession,D as loadSession,d as resolveCloudDir,L as saveSession,m as sessionFilePath};
1
+ import{readFileSync as w,writeFileSync as b,existsSync as S,mkdirSync as g,chmodSync as E,unlinkSync as _}from"node:fs";import{join as y}from"node:path";import{homedir as O}from"node:os";const k=process.env.WYRM_CLOUD_URL??"https://wyrm.ghosts.lk";function d(){return process.env.WYRM_CLOUD_DIR??y(O(),".wyrm")}const T=d(),R=y(T,"cloud.json");function m(){return y(d(),"cloud.json")}function x(){const n=d();S(n)||g(n,{recursive:!0,mode:448})}function D(){const n=m();if(!S(n))return null;try{return JSON.parse(w(n,"utf-8"))}catch{return null}}function L(n){x();const t=m();b(t,JSON.stringify(n,null,2),{mode:384});try{E(t,384)}catch{}}function C(){try{_(m())}catch{}}class u extends Error{status;body;constructor(t,e,r){super(r),this.status=t,this.body=e,this.name="CloudError"}}const q=/^[A-Za-z0-9_-]{16,128}$/;class v{base;bearer;constructor(t,e){this.base=t,this.bearer=e}static fromSession(){const t=D();if(!t)throw new u(401,null,"Not logged in. Run `wyrm cloud login` first.");return new v(t.base,t.session)}async req(t,e={}){const r={"content-type":"application/json","user-agent":"wyrm-cli/cloud",...e.headers??{}};this.bearer&&(r.authorization=`Bearer ${this.bearer}`);const s=4;let l=null;for(let i=0;i<=s;i++){const f=new AbortController,p=setTimeout(()=>f.abort(),6e4);let o;try{o=await fetch(`${this.base}${t}`,{...e,headers:r,signal:f.signal})}catch(c){if(clearTimeout(p),l=c.name==="AbortError"?new u(0,null,`Timed out reaching ${this.base}${t} after 60s`):new u(0,null,`Network error: ${c instanceof Error?c.message:c}`),i<s){await this.backoff(i,null);continue}throw l}if(clearTimeout(p),(o.status===429||o.status>=500)&&i<s){await this.backoff(i,o.headers.get("retry-after"));continue}const h=await o.text();let a=null;try{a=h?JSON.parse(h):null}catch{a=h}if(!o.ok){const c=a?.message??a?.error??`HTTP ${o.status}`;throw new u(o.status,a,c)}return a}throw l??new u(0,null,`request to ${t} failed after ${s} retries`)}async backoff(t,e){let r;e&&/^\d+$/.test(e)?r=Math.min(3e4,parseInt(e,10)*1e3):(r=Math.min(3e4,250*Math.pow(2,t)),r+=Math.floor(Math.random()*(r*.25))),await new Promise(s=>{setTimeout(s,r)})}async cliInit(t){return this.req("/api/v1/auth/cli-init",{method:"POST",body:JSON.stringify({poll_token:t})})}async cliPoll(t){return this.req("/api/v1/auth/cli-poll",{method:"POST",body:JSON.stringify({poll_token:t})})}async me(){return this.req("/api/v1/auth/me")}async logout(){return this.req("/api/v1/auth/logout",{method:"POST"})}async registerDevice(t,e,r){const s={name:t,pubkey:e};return r!==void 0&&q.test(r)&&(s.machine_id=r),this.req("/api/v1/devices",{method:"POST",body:JSON.stringify(s)})}async listDevices(){return this.req("/api/v1/devices")}async revokeDevice(t){return this.req(`/api/v1/devices/${encodeURIComponent(t)}`,{method:"DELETE"})}async syncStatus(){return this.req("/api/v1/sync/status")}async syncPush(t,e){return this.req("/api/v1/sync/push",{method:"POST",body:JSON.stringify({device_id:t,deltas:e})})}async accountExport(){return this.req("/api/v1/account/export")}async accountDelete(){return this.req("/api/v1/account",{method:"DELETE",body:JSON.stringify({confirm:"DELETE my account"})})}async syncPull(t,e,r=100){const s=new URLSearchParams({device_id:t,since:String(e),limit:String(r)});return this.req(`/api/v1/sync/pull?${s.toString()}`)}}export{T as CLOUD_DIR,v as CloudClient,u as CloudError,k as DEFAULT_BASE,R as SESSION_FILE,C as clearSession,D as loadSession,d as resolveCloudDir,L as saveSession,m as sessionFilePath};
@@ -1,2 +1,2 @@
1
- import{createHash as i,randomBytes as c}from"node:crypto";import{readFileSync as s,writeFileSync as a,existsSync as n,mkdirSync as m,chmodSync as f}from"node:fs";import{join as u}from"node:path";import{hostname as h}from"node:os";import{resolveCloudDir as o}from"./client.js";function d(){return u(o(),"machine-id")}const p=/^[0-9a-f]{32}$/;function l(){const t=o();n(t)||m(t,{recursive:!0,mode:448})}function y(){const t=d();if(n(t))try{const e=s(t,"utf-8").trim();if(p.test(e))return e}catch{}l();const r=c(16).toString("hex");a(t,r,{mode:384});try{f(t,384)}catch{}return r}function S(){const t=h(),r=y();return i("sha256").update(`${t}
2
- ${r}`).digest("hex").slice(0,32)}function v(t){if(!t)return{state:"adopt"};const r=S();return t===r?{state:"match"}:{state:"mismatch",stored:t,current:r}}export{v as classifySession,S as computeMachineFp,y as getInstallId,d as machineIdFilePath};
1
+ import{createHash as n,randomBytes as s}from"node:crypto";import{readFileSync as u,writeFileSync as a,existsSync as i,mkdirSync as f,chmodSync as m}from"node:fs";import{join as d}from"node:path";import{hostname as h}from"node:os";import{resolveCloudDir as o}from"./client.js";function p(){return d(o(),"machine-id")}const l=/^[0-9a-f]{32}$/;function y(){const t=o();i(t)||f(t,{recursive:!0,mode:448})}function x(){const t=p();if(i(t))try{const r=u(t,"utf-8").trim();if(l.test(r))return r}catch{}y();const e=s(16).toString("hex");a(t,e,{mode:384});try{m(t,384)}catch{}return e}function c(){const t=h(),e=x();return n("sha256").update(`${t}
2
+ ${e}`).digest("hex").slice(0,32)}const I="wyrm-cloud-device:";function v(t=c){let e;try{e=t()}catch{return}if(!(typeof e!="string"||e.trim()===""))return n("sha256").update(`${I}${e}`).digest("hex")}function w(t){if(!t)return{state:"adopt"};const e=c();return t===e?{state:"match"}:{state:"mismatch",stored:t,current:e}}export{w as classifySession,v as cloudMachineId,c as computeMachineFp,x as getInstallId,p as machineIdFilePath};
package/dist/database.js CHANGED
@@ -1,14 +1,14 @@
1
- import{openDatabase as L}from"./storage/index.js";import{existsSync as u,mkdirSync as O,readdirSync as A,statSync as w,writeFileSync as j,chmodSync as M}from"fs";import{homedir as y}from"os";import{join as d,basename as v,resolve as H,normalize as F}from"path";import{spawnSync as T}from"child_process";import{getResilienceManager as W}from"./resilience.js";import{getStoreLogger as U}from"./logger.js";import{runMigrations as x,schemaSkew as P,getSchemaVersion as q}from"./migrations.js";import{recordGovernanceEvent as b}from"./governance.js";import{enforceReadOnlyOpen as B,newerStoreMessage as $,newerStoreWritesAllowed as Y,NEWER_STORE_OVERRIDE_ENV as G}from"./store-readonly.js";import{describeStoreError as De}from"./store-readonly.js";import{validateProjectPath as C,buildFtsMatchQuery as m,redactCredentials as k}from"./security.js";import{emitEvent as E,eventsSince as V,subscribeEvents as J,isLiveMemoryEnabled as Q,ingestRemoteEvent as z,eventsForPush as K,pruneEvents as Z,getMeta as X,setMeta as ee}from"./events.js";import{getActor as D}from"./handlers/boundary.js";import{eraseLakeRows as te}from"./erasure.js";import{readSkillContent as I,slugify as se,skillContentSha as re}from"./skill-author.js";import{resolveProjectForPath as ie}from"./project-resolve.js";function Oe(){return process.env.WYRM_DB_PATH??process.env.WYRM_DB??d(y(),".wyrm","wyrm.db")}function ne(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 oe={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 A}from"./storage/index.js";import{existsSync as u,mkdirSync as N,readdirSync as w,statSync as j,writeFileSync as M,chmodSync as H}from"fs";import{homedir as y}from"os";import{join as l,basename as v,resolve as F,normalize as W}from"path";import{spawnSync as T}from"child_process";import{getResilienceManager as U}from"./resilience.js";import{getStoreLogger as x}from"./logger.js";import{runMigrations as P,schemaSkew as q,getSchemaVersion as B}from"./migrations.js";import{recordGovernanceEvent as b}from"./governance.js";import{enforceReadOnlyOpen as $,newerStoreMessage as Y,newerStoreWritesAllowed as G,NEWER_STORE_OVERRIDE_ENV as V}from"./store-readonly.js";import{describeStoreError as Ie}from"./store-readonly.js";import{validateProjectPath as C,buildFtsMatchQuery as m,redactCredentials as k}from"./security.js";import{emitEvent as E,eventsSince as J,subscribeEvents as Q,isLiveMemoryEnabled as z,ingestRemoteEvent as K,eventsForPush as Z,pruneEvents as X,getMeta as ee,setMeta as te}from"./events.js";import{getActor as D}from"./handlers/boundary.js";import{eraseLakeRows as se}from"./erasure.js";import{readSkillContent as I,slugify as re,skillContentSha as ie}from"./skill-author.js";import{resolveProjectForPath as ne,projectIdByPathKey as L}from"./project-resolve.js";function ye(){return process.env.WYRM_DB_PATH??process.env.WYRM_DB??l(y(),".wyrm","wyrm.db")}function oe(f,t){const e=(s,r)=>{try{u(s)&&H(s,r)}catch{}};t&&e(t,448),e(f,384),e(`${f}-wal`,384),e(`${f}-shm`,384)}const ae={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 = ?"}},ae=new Set(["within","org","public"]);class ye{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=U(this.dbPath),this.resilience=W(),this.db=this.initializeDatabase(this.dbPath),ne(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=x(this.db),r=P(this.db);r.storeIsNewer&&(Y()?this.logger.warn(`This store is at schema v${r.store}, newer than this Wyrm knows (v${r.binary}). ${G}=1 is set, so this older binary is writing to it under older rules. Upgrade wyrm-mcp on this machine.`):(B(this.db,r),this.logger.warn($(r.store,r.binary)))),s.length>0&&this.logger.info(`Applied ${s.length} migration(s), now at v${q(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 C(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 = ?"}},ce=new Set(["within","org","public"]);class Ce{db;BATCH_SIZE=1e3;resilience;logger;dbPath;constructor(t){const e=l(y(),".wyrm");u(e)||N(e,{recursive:!0,mode:448}),this.dbPath=t||l(e,"wyrm.db"),this.logger=x(this.dbPath),this.resilience=U(),this.db=this.initializeDatabase(this.dbPath),oe(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=P(this.db),r=q(this.db);r.storeIsNewer&&(G()?this.logger.warn(`This store is at schema v${r.store}, newer than this Wyrm knows (v${r.binary}). ${V}=1 is set, so this older binary is writing to it under older rules. Upgrade wyrm-mcp on this machine.`):($(this.db,r),this.logger.warn(Y(r.store,r.binary)))),s.length>0&&this.logger.info(`Applied ${s.length} migration(s), now at v${B(this.db)}`),this.recoverIncompleteOperations()}getDatabase(){return this.db}getDatabasePath(){return this.dbPath}initializeDatabase(t){const e=this.resilience.withRetrySync(()=>A(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 C(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
8
8
  RETURNING *
9
- `).get(t,e?1:0)}getWatchDirs(){return this.db.prepare("SELECT * FROM watch_dirs").all()}removeWatchDir(t){this.db.prepare("DELETE FROM watch_dirs WHERE path = ?").run(t)}scanForProjects(t,e=!0){C(t);const s=[],r=(i,n=0)=>{if(!(n>3&&e))try{const a=A(i,{withFileTypes:!0});for(const o of a){if(!o.isDirectory()||o.name.startsWith(".")&&o.name!==".git")continue;const c=d(i,o.name),l=d(c,".git");if(u(l)){const p=this.registerProjectFromPath(c);p&&s.push(p)}else e&&n<3&&r(c,n+1)}}catch{}};return r(t),this.db.prepare(`
9
+ `).get(t,e?1:0)}getWatchDirs(){return this.db.prepare("SELECT * FROM watch_dirs").all()}removeWatchDir(t){this.db.prepare("DELETE FROM watch_dirs WHERE path = ?").run(t)}scanForProjects(t,e=!0){C(t);const s=[],r=(i,n=0)=>{if(!(n>3&&e))try{const a=w(i,{withFileTypes:!0});for(const o of a){if(!o.isDirectory()||o.name.startsWith(".")&&o.name!==".git")continue;const c=l(i,o.name),d=l(c,".git");if(u(d)){const p=this.registerProjectFromPath(c);p&&s.push(p)}else e&&n<3&&r(c,n+1)}}catch{}};return r(t),this.db.prepare(`
10
10
  UPDATE watch_dirs SET last_scan = datetime('now') WHERE path = ?
11
- `).run(t),s}scanAllWatchDirs(){const t=this.getWatchDirs(),e=[];for(const s of t){const r=this.scanForProjects(s.path,!!s.recursive);e.push(...r)}return e}registerProjectFromPath(t){try{const e=F(H(t));if(!u(e)||!w(e).isDirectory())return null;const s=v(e);let r,i,n,a;try{const o=T("git",["config","--get","remote.origin.url"],{cwd:e,encoding:"utf-8",timeout:5e3,shell:!1});o.status===0&&(r=o.stdout.trim());const c=T("git",["rev-parse","--abbrev-ref","HEAD"],{cwd:e,encoding:"utf-8",timeout:5e3,shell:!1});c.status===0&&(i=c.stdout.trim());const l=T("git",["log","-1","--format=%h %s"],{cwd:e,encoding:"utf-8",timeout:5e3,shell:!1});l.status===0&&(n=l.stdout.trim())}catch{}return u(d(e,"package.json"))?(a="Node.js",u(d(e,"next.config.js"))||u(d(e,"next.config.ts"))||u(d(e,"next.config.mjs"))?a="Next.js":u(d(e,"vite.config.ts"))&&(a="Vite")):u(d(e,"requirements.txt"))||u(d(e,"pyproject.toml"))?a="Python":u(d(e,"composer.json"))?a="PHP":u(d(e,"Cargo.toml"))?a="Rust":u(d(e,"go.mod"))&&(a="Go"),this.registerProject(s,e,r,a,n,i)}catch{return null}}registerProject(t,e,s,r,i,n){return this.db.prepare(`
11
+ `).run(t),s}scanAllWatchDirs(){const t=this.getWatchDirs(),e=[];for(const s of t){const r=this.scanForProjects(s.path,!!s.recursive);e.push(...r)}return e}registerProjectFromPath(t){try{const e=W(F(t));if(!u(e)||!j(e).isDirectory())return null;const s=v(e);let r,i,n,a;try{const o=T("git",["config","--get","remote.origin.url"],{cwd:e,encoding:"utf-8",timeout:5e3,shell:!1});o.status===0&&(r=o.stdout.trim());const c=T("git",["rev-parse","--abbrev-ref","HEAD"],{cwd:e,encoding:"utf-8",timeout:5e3,shell:!1});c.status===0&&(i=c.stdout.trim());const d=T("git",["log","-1","--format=%h %s"],{cwd:e,encoding:"utf-8",timeout:5e3,shell:!1});d.status===0&&(n=d.stdout.trim())}catch{}return u(l(e,"package.json"))?(a="Node.js",u(l(e,"next.config.js"))||u(l(e,"next.config.ts"))||u(l(e,"next.config.mjs"))?a="Next.js":u(l(e,"vite.config.ts"))&&(a="Vite")):u(l(e,"requirements.txt"))||u(l(e,"pyproject.toml"))?a="Python":u(l(e,"composer.json"))?a="PHP":u(l(e,"Cargo.toml"))?a="Rust":u(l(e,"go.mod"))&&(a="Go"),this.registerProject(s,e,r,a,n,i)}catch{return null}}registerProject(t,e,s,r,i,n,a=process.platform){const o=()=>this.db.prepare(`
12
12
  INSERT INTO projects (name, path, repo, stack, last_commit, branch)
13
13
  VALUES (?, ?, ?, ?, ?, ?)
14
14
  ON CONFLICT(path) DO UPDATE SET
@@ -19,7 +19,17 @@ import{openDatabase as L}from"./storage/index.js";import{existsSync as u,mkdirSy
19
19
  branch = COALESCE(excluded.branch, projects.branch),
20
20
  updated_at = datetime('now')
21
21
  RETURNING *
22
- `).get(t,e,s||null,r||null,i||null,n||null)}getProject(t){return this.db.prepare("SELECT * FROM projects WHERE path = ?").get(t)}getProjectForPath(t){const{id:e,root:s}=ie(this.db,t);return e!==null&&s!==null?{id:e,path:s}:void 0}getProjectById(t){return this.db.prepare("SELECT * FROM projects WHERE id = ?").get(t)}getProjectByName(t){return this.db.prepare("SELECT * FROM projects WHERE name = ?").get(t)}getAllProjects(t=100,e=0){return this.db.prepare(`
22
+ `).get(t,e,s||null,r||null,i||null,n||null);return a!=="win32"?o():this.db.transaction(()=>{const d=this.db.prepare("SELECT 1 FROM projects WHERE path = ?").get(e)?void 0:L(this.db,e,a);return d===void 0?o():this.db.prepare(`
23
+ UPDATE projects SET
24
+ name = ?,
25
+ repo = COALESCE(?, repo),
26
+ stack = COALESCE(?, stack),
27
+ last_commit = COALESCE(?, last_commit),
28
+ branch = COALESCE(?, branch),
29
+ updated_at = datetime('now')
30
+ WHERE id = ?
31
+ RETURNING *
32
+ `).get(t,s||null,r||null,i||null,n||null,d)}).immediate()}getProject(t,e=process.platform){const s=this.db.prepare("SELECT * FROM projects WHERE path = ?").get(t);if(s||e!=="win32")return s;const r=L(this.db,t,e);return r===void 0?void 0:this.getProjectById(r)}getProjectForPath(t,e=process.platform){const{id:s,root:r}=ne(this.db,t,e);return s!==null&&r!==null?{id:s,path:r}:void 0}getProjectById(t){return this.db.prepare("SELECT * FROM projects WHERE id = ?").get(t)}getProjectByName(t){return this.db.prepare("SELECT * FROM projects WHERE name = ?").get(t)}getAllProjects(t=100,e=0){return this.db.prepare(`
23
33
  SELECT * FROM projects ORDER BY updated_at DESC LIMIT ? OFFSET ?
24
34
  `).all(t,e)}searchProjects(t){const e=`%${t}%`;return this.db.prepare(`
25
35
  SELECT * FROM projects
@@ -36,13 +46,13 @@ import{openDatabase as L}from"./storage/index.js";import{existsSync as u,mkdirSy
36
46
 
37
47
  `+o:o;c.length>i&&(c=`[\u2026earlier continuity trimmed; gist retained below\u2026]
38
48
 
39
- `+c.slice(c.length-i));const l=(n?.turn_count??0)+1;this.resilience.withRetrySync(()=>(this.db.prepare(`
49
+ `+c.slice(c.length-i));const d=(n?.turn_count??0)+1;this.resilience.withRetrySync(()=>(this.db.prepare(`
40
50
  INSERT INTO project_continuity (project_id, thread, updated_at, updated_by, turn_count)
41
51
  VALUES (?, ?, ?, ?, ?)
42
52
  ON CONFLICT(project_id) DO UPDATE SET
43
53
  thread = excluded.thread, updated_at = excluded.updated_at,
44
54
  updated_by = excluded.updated_by, turn_count = excluded.turn_count
45
- `).run(t,c,a,s??null,l),!0),"updateContinuity")}getContinuity(t){return this.db.prepare("SELECT thread, updated_at, updated_by, turn_count FROM project_continuity WHERE project_id = ?").get(t)??null}recordRouting(t){const e=t.projectId==null?-1:t.projectId,s=(t.taskType??"").trim(),r=(t.modelId??"").trim();if(!s)throw new Error("recordRouting: taskType is required");if(!r)throw new Error("recordRouting: modelId is required");let i=null;if(Array.isArray(t.toolNames)){const o=[...new Set(t.toolNames.map(c=>String(c).trim()).filter(Boolean))];i=o.length?o.join(","):null}else if(typeof t.toolNames=="string"){const o=[...new Set(t.toolNames.split(",").map(c=>c.trim()).filter(Boolean))];i=o.length?o.join(","):null}const n=t.latencyMs==null||!Number.isFinite(t.latencyMs)?null:Math.max(0,Math.round(t.latencyMs));let a=-1;return this.resilience.withRetrySync(()=>{const o=this.db.prepare(`
55
+ `).run(t,c,a,s??null,d),!0),"updateContinuity")}getContinuity(t){return this.db.prepare("SELECT thread, updated_at, updated_by, turn_count FROM project_continuity WHERE project_id = ?").get(t)??null}recordRouting(t){const e=t.projectId==null?-1:t.projectId,s=(t.taskType??"").trim(),r=(t.modelId??"").trim();if(!s)throw new Error("recordRouting: taskType is required");if(!r)throw new Error("recordRouting: modelId is required");let i=null;if(Array.isArray(t.toolNames)){const o=[...new Set(t.toolNames.map(c=>String(c).trim()).filter(Boolean))];i=o.length?o.join(","):null}else if(typeof t.toolNames=="string"){const o=[...new Set(t.toolNames.split(",").map(c=>c.trim()).filter(Boolean))];i=o.length?o.join(","):null}const n=t.latencyMs==null||!Number.isFinite(t.latencyMs)?null:Math.max(0,Math.round(t.latencyMs));let a=-1;return this.resilience.withRetrySync(()=>{const o=this.db.prepare(`
46
56
  INSERT INTO routing_memory (project_id, task_type, model_id, tool_names, success, latency_ms)
47
57
  VALUES (?, ?, ?, ?, ?, ?)
48
58
  `).run(e,s,r,i,t.success?1:0,n);return a=Number(o.lastInsertRowid),!0},"recordRouting"),a}recallRouting(t,e={}){const s=(t??"").trim();if(!s)return[];const r=Math.min(100,Math.max(1,e.limit??10)),i=["task_type = ?"],n=[s];e.projectId!=null&&(i.push("(project_id = ? OR project_id = -1)"),n.push(e.projectId)),e.sinceDays!=null&&Number.isFinite(e.sinceDays)&&e.sinceDays>0&&(i.push("ts >= datetime('now', ?)"),n.push(`-${Math.round(e.sinceDays)} days`)),n.push(r);try{return this.db.prepare(`
@@ -160,7 +170,7 @@ import{openDatabase as L}from"./storage/index.js";import{existsSync as u,mkdirSy
160
170
  SELECT value FROM context WHERE project_id = ? AND key = ?
161
171
  `).get(t,e)?.value}getAllContext(t){const e=this.db.prepare(`
162
172
  SELECT key, value FROM context WHERE project_id = ?
163
- `).all(t),s={};for(const r of e)s[r.key]=r.value;return s}liveMemoryEnabled(){return Q()}eventsSince(t,e=0,s=100){return V(this.db,t,e,s)}subscribeEvents(t,e=20){return J(this.db,t,e)}publishEvent(t){E(this.db,t)}pruneEvents(t={}){return Z(this.db,t)}ingestRemoteEvent(t,e){return z(this.db,t,e)}eventsForPush(t,e=0,s=200){return K(this.db,t,e,s)}getMeta(t){return X(this.db,t)}setMeta(t,e){ee(this.db,t,e)}setGlobalContext(t,e){this.db.prepare(`
173
+ `).all(t),s={};for(const r of e)s[r.key]=r.value;return s}liveMemoryEnabled(){return z()}eventsSince(t,e=0,s=100){return J(this.db,t,e,s)}subscribeEvents(t,e=20){return Q(this.db,t,e)}publishEvent(t){E(this.db,t)}pruneEvents(t={}){return X(this.db,t)}ingestRemoteEvent(t,e){return K(this.db,t,e)}eventsForPush(t,e=0,s=200){return Z(this.db,t,e,s)}getMeta(t){return ee(this.db,t)}setMeta(t,e){te(this.db,t,e)}setGlobalContext(t,e){this.db.prepare(`
164
174
  INSERT INTO global_context (key, value)
165
175
  VALUES (?, ?)
166
176
  ON CONFLICT(key) DO UPDATE SET
@@ -168,7 +178,7 @@ import{openDatabase as L}from"./storage/index.js";import{existsSync as u,mkdirSy
168
178
  updated_at = datetime('now')
169
179
  `).run(t,e)}getGlobalContext(t){return this.db.prepare(`
170
180
  SELECT value FROM global_context WHERE key = ?
171
- `).get(t)?.value}getAllGlobalContext(){const t=this.db.prepare("SELECT key, value FROM global_context").all(),e={};for(const s of t)e[s.key]=s.value;return e}registerSkill(t,e,s,r,i,n,a,o){const c=o?.tier??"atomic",l=JSON.stringify(Array.isArray(o?.governs)?o.governs:[]),p=JSON.stringify(Array.isArray(o?.composes)?o.composes:[]),_=I({skillPath:s,name:t}),g=_?.content??null,S=_?.sha256??null,R=_?new Date().toISOString():null,h=this.resilience.withRetrySync(()=>this.db.prepare(`
181
+ `).get(t)?.value}getAllGlobalContext(){const t=this.db.prepare("SELECT key, value FROM global_context").all(),e={};for(const s of t)e[s.key]=s.value;return e}registerSkill(t,e,s,r,i,n,a,o){const c=o?.tier??"atomic",d=JSON.stringify(Array.isArray(o?.governs)?o.governs:[]),p=JSON.stringify(Array.isArray(o?.composes)?o.composes:[]),_=I({skillPath:s,name:t}),g=_?.content??null,S=_?.sha256??null,R=_?new Date().toISOString():null,h=this.resilience.withRetrySync(()=>this.db.prepare(`
172
182
  INSERT INTO skills (name, description, skill_path, category, author, version, tags, tier, governs, composes, content, content_sha256, content_updated_at, is_active, usage_count)
173
183
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, 0)
174
184
  ON CONFLICT(name) DO UPDATE SET
@@ -190,13 +200,13 @@ import{openDatabase as L}from"./storage/index.js";import{existsSync as u,mkdirSy
190
200
  updated_at = datetime('now'),
191
201
  is_active = 1
192
202
  RETURNING *
193
- `).get(t,e,s,r||null,i||null,n||null,a||null,c,l,p,g,S,R),"registerSkill");if(!h.success)throw h.error||new Error("Failed to register skill");return h.data}getSkill(t){const e=this.db.prepare("SELECT * FROM skills WHERE name = ?").get(t);return e&&this.db.prepare("UPDATE skills SET last_used = datetime('now'), usage_count = usage_count + 1 WHERE id = ?").run(e.id),e}backfillSkillContent(t={}){const e=t.read??I,s=this.db.prepare("SELECT id, name, skill_path, content_sha256 FROM skills").all();let r=0,i=0,n=0;const a=this.db.prepare("UPDATE skills SET content = ?, content_sha256 = ?, content_updated_at = ?, updated_at = datetime('now') WHERE id = ?");return this.db.transaction(()=>{for(const c of s){const l=e({skillPath:c.skill_path,name:c.name,skillsDir:t.skillsDir});if(!l){n++;continue}if(c.content_sha256&&c.content_sha256===l.sha256){i++;continue}a.run(l.content,l.sha256,new Date().toISOString(),c.id),r++}})(),{total:s.length,filled:r,unchanged:i,missing:n}}exportSkillContent(t,e={}){const s=e.includeInactive?"":" WHERE is_active = 1",r=this.db.prepare(`SELECT name, content FROM skills${s} ORDER BY name`).all();let i=0,n=0,a=0;const o=new Set;for(const c of r){if(c.content==null){n++;continue}let l=se(c.name);if(!l){n++;continue}o.has(l)&&(l=`${l}-${re(c.name).slice(0,8)}`,a++),o.add(l);const p=d(t,l);O(p,{recursive:!0}),j(d(p,"SKILL.md"),c.content,"utf-8"),i++}return{total:r.length,written:i,skipped_no_content:n,collisions:a}}setSkillVisibility(t,e){return this.db.transaction(()=>{const s=this.db.prepare("UPDATE skills SET cross_project_visibility = ?, updated_at = datetime('now') WHERE name = ? RETURNING *").get(e,t);return s&&b(this.db,"visibility.set",{kind:"skill",row_id:s.id,to:e}),s})()}setAllSkillsVisibility(t,e={}){const s=[],r=[t];e.includeInactive||s.push("is_active = 1"),e.tier&&(s.push("tier = ?"),r.push(e.tier));const i=s.length?" WHERE "+s.join(" AND "):"";return this.db.transaction(()=>{const n=this.db.prepare(`UPDATE skills SET cross_project_visibility = ?, updated_at = datetime('now')${i}`).run(...r);return n.changes>0&&b(this.db,"visibility.set",{kind:"skill",count:n.changes,to:t}),n.changes})()}setRowVisibility(t,e,s){const r=oe[t];if(!r)throw new Error(`Unknown visibility kind: ${String(t)} (use truth|memory|quest)`);if(!ae.has(s))throw new Error(`Unknown visibility: ${String(s)} (use within|org|public)`);const i=this.db.prepare(r.select).get(e),n={kind:t,id:e,visibility:s};if(!i)return{...n,outcome:"not_found",projectId:null,projectName:null,syncPolicy:null,egressEligible:!1};const a={projectId:i.pid,projectName:i.pname,syncPolicy:i.policy,previous:i.vis},o=c=>c!=="within"&&(i.pid==null||i.policy==="cloud"||i.policy==="team");return s!=="within"&&i.policy==="private"?{...n,...a,outcome:"refused_private_grove",egressEligible:!1}:i.vis===s?{...n,...a,outcome:"unchanged",egressEligible:o(s)}:(this.db.transaction(()=>{this.db.prepare(r.update).run(s,e),b(this.db,"visibility.set",{kind:t,row_id:e,from:i.vis,to:s},{projectId:i.pid})})(),{...n,...a,outcome:"updated",egressEligible:o(s)})}listSkills(t,e,s,r){let i="SELECT * FROM skills WHERE 1=1";const n=[];if(t!==void 0&&(i+=" AND is_active = ?",n.push(t?1:0)),e&&(i+=" AND category = ?",n.push(e)),r&&(i+=" AND tier = ?",n.push(r)),s){const a=m(s);a&&(i+=" AND id IN (SELECT rowid FROM skills_fts WHERE skills_fts MATCH ?)",n.push(a))}return i+=" ORDER BY updated_at DESC",this.db.prepare(i).all(...n)}searchSkills(t,e=20,s){const r=m(t);if(!r)return[];const i=s?" AND s.tier = ?":"",n=this.db.prepare(`
203
+ `).get(t,e,s,r||null,i||null,n||null,a||null,c,d,p,g,S,R),"registerSkill");if(!h.success)throw h.error||new Error("Failed to register skill");return h.data}getSkill(t){const e=this.db.prepare("SELECT * FROM skills WHERE name = ?").get(t);return e&&this.db.prepare("UPDATE skills SET last_used = datetime('now'), usage_count = usage_count + 1 WHERE id = ?").run(e.id),e}backfillSkillContent(t={}){const e=t.read??I,s=this.db.prepare("SELECT id, name, skill_path, content_sha256 FROM skills").all();let r=0,i=0,n=0;const a=this.db.prepare("UPDATE skills SET content = ?, content_sha256 = ?, content_updated_at = ?, updated_at = datetime('now') WHERE id = ?");return this.db.transaction(()=>{for(const c of s){const d=e({skillPath:c.skill_path,name:c.name,skillsDir:t.skillsDir});if(!d){n++;continue}if(c.content_sha256&&c.content_sha256===d.sha256){i++;continue}a.run(d.content,d.sha256,new Date().toISOString(),c.id),r++}})(),{total:s.length,filled:r,unchanged:i,missing:n}}exportSkillContent(t,e={}){const s=e.includeInactive?"":" WHERE is_active = 1",r=this.db.prepare(`SELECT name, content FROM skills${s} ORDER BY name`).all();let i=0,n=0,a=0;const o=new Set;for(const c of r){if(c.content==null){n++;continue}let d=re(c.name);if(!d){n++;continue}o.has(d)&&(d=`${d}-${ie(c.name).slice(0,8)}`,a++),o.add(d);const p=l(t,d);N(p,{recursive:!0}),M(l(p,"SKILL.md"),c.content,"utf-8"),i++}return{total:r.length,written:i,skipped_no_content:n,collisions:a}}setSkillVisibility(t,e){return this.db.transaction(()=>{const s=this.db.prepare("UPDATE skills SET cross_project_visibility = ?, updated_at = datetime('now') WHERE name = ? RETURNING *").get(e,t);return s&&b(this.db,"visibility.set",{kind:"skill",row_id:s.id,to:e}),s})()}setAllSkillsVisibility(t,e={}){const s=[],r=[t];e.includeInactive||s.push("is_active = 1"),e.tier&&(s.push("tier = ?"),r.push(e.tier));const i=s.length?" WHERE "+s.join(" AND "):"";return this.db.transaction(()=>{const n=this.db.prepare(`UPDATE skills SET cross_project_visibility = ?, updated_at = datetime('now')${i}`).run(...r);return n.changes>0&&b(this.db,"visibility.set",{kind:"skill",count:n.changes,to:t}),n.changes})()}setRowVisibility(t,e,s){const r=ae[t];if(!r)throw new Error(`Unknown visibility kind: ${String(t)} (use truth|memory|quest)`);if(!ce.has(s))throw new Error(`Unknown visibility: ${String(s)} (use within|org|public)`);const i=this.db.prepare(r.select).get(e),n={kind:t,id:e,visibility:s};if(!i)return{...n,outcome:"not_found",projectId:null,projectName:null,syncPolicy:null,egressEligible:!1};const a={projectId:i.pid,projectName:i.pname,syncPolicy:i.policy,previous:i.vis},o=c=>c!=="within"&&(i.pid==null||i.policy==="cloud"||i.policy==="team");return s!=="within"&&i.policy==="private"?{...n,...a,outcome:"refused_private_grove",egressEligible:!1}:i.vis===s?{...n,...a,outcome:"unchanged",egressEligible:o(s)}:(this.db.transaction(()=>{this.db.prepare(r.update).run(s,e),b(this.db,"visibility.set",{kind:t,row_id:e,from:i.vis,to:s},{projectId:i.pid})})(),{...n,...a,outcome:"updated",egressEligible:o(s)})}listSkills(t,e,s,r){let i="SELECT * FROM skills WHERE 1=1";const n=[];if(t!==void 0&&(i+=" AND is_active = ?",n.push(t?1:0)),e&&(i+=" AND category = ?",n.push(e)),r&&(i+=" AND tier = ?",n.push(r)),s){const a=m(s);a&&(i+=" AND id IN (SELECT rowid FROM skills_fts WHERE skills_fts MATCH ?)",n.push(a))}return i+=" ORDER BY updated_at DESC",this.db.prepare(i).all(...n)}searchSkills(t,e=20,s){const r=m(t);if(!r)return[];const i=s?" AND s.tier = ?":"",n=this.db.prepare(`
194
204
  SELECT s.* FROM skills s
195
205
  JOIN skills_fts ON s.id = skills_fts.rowid
196
206
  WHERE skills_fts MATCH ?${i}
197
207
  ORDER BY rank
198
208
  LIMIT ?
199
- `);return s?n.all(r,s,e):n.all(r,e)}getSkillGraph(t){const e=this.db.prepare("SELECT * FROM skills").all(),s=new Map;for(const o of e)s.set(o.name,o);const r=o=>{if(!o)return[];try{const c=JSON.parse(o);return Array.isArray(c)?c.filter(l=>typeof l=="string"):[]}catch{return[]}},i=(o,c)=>{const l=s.get(o);if(!l)return null;const p=r(l.governs),_=r(l.composes),g={name:l.name,tier:l.tier??"atomic",description:l.description,governs:p,composes:_,children:[]},S=[];if(!c.has(o)){const R=new Set(c).add(o);for(const h of p){const N=i(h,R);N?g.children.push(N):S.push(h)}}return S.length&&(g.missing=S),g};if(t){const o=i(t,new Set);return o?[o]:[]}const n=e.filter(o=>(o.tier??"atomic")==="god").map(o=>o.name).sort(),a=[];for(const o of n){const c=i(o,new Set);c&&a.push(c)}return a}updateSkill(t,e){const s=[],r=[];return e.description!==void 0&&(s.push("description = ?"),r.push(e.description)),e.skill_path!==void 0&&(s.push("skill_path = ?"),r.push(e.skill_path)),e.category!==void 0&&(s.push("category = ?"),r.push(e.category)),e.is_active!==void 0&&(s.push("is_active = ?"),r.push(e.is_active?1:0)),e.tags!==void 0&&(s.push("tags = ?"),r.push(e.tags)),e.version!==void 0&&(s.push("version = ?"),r.push(e.version)),s.length===0?this.getSkill(t):(s.push("updated_at = datetime('now')"),r.push(t),this.db.prepare(`
209
+ `);return s?n.all(r,s,e):n.all(r,e)}getSkillGraph(t){const e=this.db.prepare("SELECT * FROM skills").all(),s=new Map;for(const o of e)s.set(o.name,o);const r=o=>{if(!o)return[];try{const c=JSON.parse(o);return Array.isArray(c)?c.filter(d=>typeof d=="string"):[]}catch{return[]}},i=(o,c)=>{const d=s.get(o);if(!d)return null;const p=r(d.governs),_=r(d.composes),g={name:d.name,tier:d.tier??"atomic",description:d.description,governs:p,composes:_,children:[]},S=[];if(!c.has(o)){const R=new Set(c).add(o);for(const h of p){const O=i(h,R);O?g.children.push(O):S.push(h)}}return S.length&&(g.missing=S),g};if(t){const o=i(t,new Set);return o?[o]:[]}const n=e.filter(o=>(o.tier??"atomic")==="god").map(o=>o.name).sort(),a=[];for(const o of n){const c=i(o,new Set);c&&a.push(c)}return a}updateSkill(t,e){const s=[],r=[];return e.description!==void 0&&(s.push("description = ?"),r.push(e.description)),e.skill_path!==void 0&&(s.push("skill_path = ?"),r.push(e.skill_path)),e.category!==void 0&&(s.push("category = ?"),r.push(e.category)),e.is_active!==void 0&&(s.push("is_active = ?"),r.push(e.is_active?1:0)),e.tags!==void 0&&(s.push("tags = ?"),r.push(e.tags)),e.version!==void 0&&(s.push("version = ?"),r.push(e.version)),s.length===0?this.getSkill(t):(s.push("updated_at = datetime('now')"),r.push(t),this.db.prepare(`
200
210
  UPDATE skills SET ${s.join(", ")} WHERE name = ? RETURNING *
201
211
  `).get(...r))}deleteSkill(t){return this.db.prepare("DELETE FROM skills WHERE name = ?").run(t).changes>0}deactivateSkill(t){return this.updateSkill(t,{is_active:!1})}activateSkill(t){return this.updateSkill(t,{is_active:!0})}getSkillStats(){const t=this.db.prepare("SELECT COUNT(*) as count FROM skills").get().count,e=this.db.prepare("SELECT COUNT(*) as count FROM skills WHERE is_active = 1").get().count,s=this.db.prepare(`
202
212
  SELECT category, COUNT(*) as count FROM skills WHERE category IS NOT NULL GROUP BY category
@@ -221,7 +231,7 @@ import{openDatabase as L}from"./storage/index.js";import{existsSync as u,mkdirSy
221
231
  `).get(t,e,s,r,i?JSON.stringify(i):null),"insertData");if(!n.success)throw n.error||new Error("Insert data failed");return n.data}batchWrites(t,e){return this.db.transaction(r=>r.map(i=>e(i)))(t)}insertDataBatch(t){const e=this.resilience.generateOperationId("batch_insert"),s=this.BATCH_SIZE;let r=0;this.resilience.createCheckpoint(e,"batch_insert","started",{totalItems:t.length,batchSize:s});const i=this.db.prepare(`
222
232
  INSERT INTO data_lake (project_id, category, key, value, metadata)
223
233
  VALUES (?, ?, ?, ?, ?)
224
- `);try{for(let n=0;n<t.length;n+=s){const a=t.slice(n,n+s),o=Math.floor(n/s)+1;this.resilience.updateCheckpoint(e,`batch_${o}`,{processed:n,currentBatch:o});const c=this.resilience.withRetrySync(()=>this.batchWrites(a,l=>(i.run(l.projectId,l.category,l.key,l.value,l.metadata?JSON.stringify(l.metadata):null),1)).length,`batch_insert_${o}`,{maxAttempts:3});if(!c.success){this.logger.error("Batch insert failed",{batch:o,processed:r,error:c.error?.message}),this.resilience.updateCheckpoint(e,"partial_failure",{inserted:r,failedAt:n});const l=c.error??new Error(`Batch insert failed at chunk ${o}`);throw l.inserted=r,l}r+=c.data}return this.resilience.completeCheckpoint(e),r}catch(n){throw n!==null&&typeof n=="object"&&"inserted"in n||(this.logger.error("Batch insert exception",{inserted:r,error:n.message}),this.resilience.updateCheckpoint(e,"exception",{inserted:r,error:n.message}),n!==null&&typeof n=="object"&&(n.inserted=r)),n}}queryData(t,e,s=100,r=0){return e?this.db.prepare(`
234
+ `);try{for(let n=0;n<t.length;n+=s){const a=t.slice(n,n+s),o=Math.floor(n/s)+1;this.resilience.updateCheckpoint(e,`batch_${o}`,{processed:n,currentBatch:o});const c=this.resilience.withRetrySync(()=>this.batchWrites(a,d=>(i.run(d.projectId,d.category,d.key,d.value,d.metadata?JSON.stringify(d.metadata):null),1)).length,`batch_insert_${o}`,{maxAttempts:3});if(!c.success){this.logger.error("Batch insert failed",{batch:o,processed:r,error:c.error?.message}),this.resilience.updateCheckpoint(e,"partial_failure",{inserted:r,failedAt:n});const d=c.error??new Error(`Batch insert failed at chunk ${o}`);throw d.inserted=r,d}r+=c.data}return this.resilience.completeCheckpoint(e),r}catch(n){throw n!==null&&typeof n=="object"&&"inserted"in n||(this.logger.error("Batch insert exception",{inserted:r,error:n.message}),this.resilience.updateCheckpoint(e,"exception",{inserted:r,error:n.message}),n!==null&&typeof n=="object"&&(n.inserted=r)),n}}queryData(t,e,s=100,r=0){return e?this.db.prepare(`
225
235
  SELECT * FROM data_lake
226
236
  WHERE project_id = ? AND category = ?
227
237
  ORDER BY created_at DESC
@@ -263,8 +273,8 @@ import{openDatabase as L}from"./storage/index.js";import{existsSync as u,mkdirSy
263
273
  WHERE project_id = ?
264
274
  GROUP BY category
265
275
  ORDER BY count DESC
266
- `).all(t)}forgetData(t){const e=[],s=[];if(t.match){e.push("(key LIKE ? OR value LIKE ?)");const p=`%${t.match}%`;s.push(p,p)}if(t.dataset&&(e.push("category = ?"),s.push(t.dataset)),typeof t.olderThanDays=="number"&&(e.push("created_at < datetime('now', ?)"),s.push(`-${Math.max(0,Math.floor(t.olderThanDays))} days`)),t.projectId!=null&&(e.push("project_id = ?"),s.push(t.projectId)),e.length===0)return{matched:0,deleted:0,samples:[],counts:{},erasureId:null};const r=e.join(" AND "),i=this.db.prepare(`SELECT id, category, key FROM data_lake WHERE ${r}`).all(...s),n=i.length,a=this.db.prepare(`SELECT category, key, created_at FROM data_lake WHERE ${r} ORDER BY created_at LIMIT 5`).all(...s),o=t.dryRun!==!1,c=te(this.db,i,{subject:t.match,dryRun:o,reason:t.reason??(t.match?"subject":t.dataset?"source":"age"),predicate:{match:!!t.match,dataset:t.dataset??null,older_than_days:typeof t.olderThanDays=="number"?t.olderThanDays:null,project:t.projectId!=null},actor:t.actor??null}),l=o?0:c.counts.data_lake??0;return{matched:n,deleted:l,samples:a,counts:c.counts,erasureId:c.erasureId}}deleteDataCategory(t,e){return this.db.prepare(`
276
+ `).all(t)}forgetData(t){const e=[],s=[];if(t.match){e.push("(key LIKE ? OR value LIKE ?)");const p=`%${t.match}%`;s.push(p,p)}if(t.dataset&&(e.push("category = ?"),s.push(t.dataset)),typeof t.olderThanDays=="number"&&(e.push("created_at < datetime('now', ?)"),s.push(`-${Math.max(0,Math.floor(t.olderThanDays))} days`)),t.projectId!=null&&(e.push("project_id = ?"),s.push(t.projectId)),e.length===0)return{matched:0,deleted:0,samples:[],counts:{},erasureId:null};const r=e.join(" AND "),i=this.db.prepare(`SELECT id, category, key FROM data_lake WHERE ${r}`).all(...s),n=i.length,a=this.db.prepare(`SELECT category, key, created_at FROM data_lake WHERE ${r} ORDER BY created_at LIMIT 5`).all(...s),o=t.dryRun!==!1,c=se(this.db,i,{subject:t.match,dryRun:o,reason:t.reason??(t.match?"subject":t.dataset?"source":"age"),predicate:{match:!!t.match,dataset:t.dataset??null,older_than_days:typeof t.olderThanDays=="number"?t.olderThanDays:null,project:t.projectId!=null},actor:t.actor??null}),d=o?0:c.counts.data_lake??0;return{matched:n,deleted:d,samples:a,counts:c.counts,erasureId:c.erasureId}}deleteDataCategory(t,e){return this.db.prepare(`
267
277
  DELETE FROM data_lake WHERE project_id = ? AND category = ?
268
278
  `).run(t,e).changes}*streamSessions(t){const e=this.db.prepare(`
269
279
  SELECT * FROM sessions WHERE project_id = ? ORDER BY date DESC
270
- `);for(const s of e.iterate(t))yield s}*streamData(t,e){const s=e?this.db.prepare("SELECT * FROM data_lake WHERE project_id = ? AND category = ?"):this.db.prepare("SELECT * FROM data_lake WHERE project_id = ?"),r=e?[t,e]:[t];for(const i of s.iterate(...r))yield i}getStats(){const t=this.db.prepare("SELECT COUNT(*) as count FROM projects").get(),e=this.db.prepare("SELECT COUNT(*) as count FROM sessions").get(),s=this.db.prepare("SELECT COUNT(*) as count FROM quests").get(),r=this.db.prepare("SELECT COUNT(*) as count FROM memory_artifacts").get(),i=this.db.prepare("SELECT COUNT(*) as count FROM data_lake").get(),n=this.db.prepare("SELECT COALESCE(SUM(tokens_estimate), 0) as total FROM sessions WHERE is_archived = 0").get(),a=this.db.pragma("page_count",{simple:!0}),o=this.db.pragma("page_size",{simple:!0}),c=a*o/(1024*1024);return{projects:t.count,sessions:e.count,quests:s.count,artifacts:r.count,dataPoints:i.count,totalTokens:n.total,dbSize:`${c.toFixed(2)} MB`}}getProjectStats(t){const e=this.db.prepare("SELECT COUNT(*) as count FROM sessions WHERE project_id = ?").get(t),s=this.db.prepare("SELECT COUNT(*) as count FROM quests WHERE project_id = ? AND status IN ('pending', 'in_progress')").get(t),r=this.db.prepare("SELECT COUNT(*) as count FROM quests WHERE project_id = ? AND status = 'completed'").get(t),i=this.db.prepare("SELECT COUNT(*) as count FROM data_lake WHERE project_id = ?").get(t),n=this.db.prepare("SELECT COALESCE(SUM(tokens_estimate), 0) as total FROM sessions WHERE project_id = ? AND is_archived = 0").get(t);return{sessions:e.count,quests:{pending:s.count,completed:r.count},dataPoints:i.count,tokens:n.total}}estimateTokens(t){return Math.ceil(t.length/4)}vacuum(){this.db.exec("VACUUM")}checkpoint(){this.db.pragma("wal_checkpoint(TRUNCATE)")}getResilienceStatus(){const t=this.resilience.getCircuitStatus(),e=this.resilience.getIncompleteOperations();return{circuitState:t.state,failures:t.failures,incompleteOps:e.length}}resetCircuitBreaker(){this.resilience.resetCircuit()}close(){try{this.checkpoint(),this.logger.info("Database checkpoint completed")}catch(t){this.logger.error("Checkpoint failed during close",{error:t.message})}try{this.db.close(),this.logger.info("Database closed successfully")}catch(t){this.logger.error("Database close failed",{error:t.message})}}checkIntegrity(){const t=[];try{const e=this.db.pragma("integrity_check",{simple:!0});e!=="ok"&&t.push(`Integrity check failed: ${e}`)}catch(e){t.push(`Integrity check error: ${e.message}`)}try{const e=this.db.pragma("foreign_key_check");e.length>0&&t.push(`Foreign key violations: ${e.length}`)}catch(e){t.push(`FK check error: ${e.message}`)}return{ok:t.length===0,issues:t}}}export{ye as WyrmDB,De as describeStoreError,Oe as resolveDbPath,ne as tightenStorePermissions};
280
+ `);for(const s of e.iterate(t))yield s}*streamData(t,e){const s=e?this.db.prepare("SELECT * FROM data_lake WHERE project_id = ? AND category = ?"):this.db.prepare("SELECT * FROM data_lake WHERE project_id = ?"),r=e?[t,e]:[t];for(const i of s.iterate(...r))yield i}getStats(){const t=this.db.prepare("SELECT COUNT(*) as count FROM projects").get(),e=this.db.prepare("SELECT COUNT(*) as count FROM sessions").get(),s=this.db.prepare("SELECT COUNT(*) as count FROM quests").get(),r=this.db.prepare("SELECT COUNT(*) as count FROM memory_artifacts").get(),i=this.db.prepare("SELECT COUNT(*) as count FROM data_lake").get(),n=this.db.prepare("SELECT COALESCE(SUM(tokens_estimate), 0) as total FROM sessions WHERE is_archived = 0").get(),a=this.db.pragma("page_count",{simple:!0}),o=this.db.pragma("page_size",{simple:!0}),c=a*o/(1024*1024);return{projects:t.count,sessions:e.count,quests:s.count,artifacts:r.count,dataPoints:i.count,totalTokens:n.total,dbSize:`${c.toFixed(2)} MB`}}getProjectStats(t){const e=this.db.prepare("SELECT COUNT(*) as count FROM sessions WHERE project_id = ?").get(t),s=this.db.prepare("SELECT COUNT(*) as count FROM quests WHERE project_id = ? AND status IN ('pending', 'in_progress')").get(t),r=this.db.prepare("SELECT COUNT(*) as count FROM quests WHERE project_id = ? AND status = 'completed'").get(t),i=this.db.prepare("SELECT COUNT(*) as count FROM data_lake WHERE project_id = ?").get(t),n=this.db.prepare("SELECT COALESCE(SUM(tokens_estimate), 0) as total FROM sessions WHERE project_id = ? AND is_archived = 0").get(t);return{sessions:e.count,quests:{pending:s.count,completed:r.count},dataPoints:i.count,tokens:n.total}}estimateTokens(t){return Math.ceil(t.length/4)}vacuum(){this.db.exec("VACUUM")}checkpoint(){this.db.pragma("wal_checkpoint(TRUNCATE)")}getResilienceStatus(){const t=this.resilience.getCircuitStatus(),e=this.resilience.getIncompleteOperations();return{circuitState:t.state,failures:t.failures,incompleteOps:e.length}}resetCircuitBreaker(){this.resilience.resetCircuit()}close(){try{this.checkpoint(),this.logger.info("Database checkpoint completed")}catch(t){this.logger.error("Checkpoint failed during close",{error:t.message})}try{this.db.close(),this.logger.info("Database closed successfully")}catch(t){this.logger.error("Database close failed",{error:t.message})}}checkIntegrity(){const t=[];try{const e=this.db.pragma("integrity_check",{simple:!0});e!=="ok"&&t.push(`Integrity check failed: ${e}`)}catch(e){t.push(`Integrity check error: ${e.message}`)}try{const e=this.db.pragma("foreign_key_check");e.length>0&&t.push(`Foreign key violations: ${e.length}`)}catch(e){t.push(`FK check error: ${e.message}`)}return{ok:t.length===0,issues:t}}}export{Ce as WyrmDB,Ie as describeStoreError,ye as resolveDbPath,oe as tightenStorePermissions};
@@ -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?`; 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}
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 <license.json>"},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
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};
@@ -1,3 +1,3 @@
1
- import{c,formatTable as l,printSection as d,printSuccess as u,printWarning as g}from"./cli.js";import{enforceIngestRetention as m,ingestedCategories as h,RETENTION_GRACE_DAYS as y}from"./retention.js";import{redactBackfill as f}from"./redact-backfill.js";import{scanBackupHygiene as w,listBackups as b,defaultBackupDir as k}from"./backup.js";import{fmtBytes as R}from"./backup-cli.js";function $(a,o=" "){for(const[n,t]of Object.entries(a).sort((e,s)=>s[1]-e[1]))console.log(`${o}${String(t).padStart(6)} ${n}`)}function A(a,o){const n=typeof o.days=="string"?Number(o.days):void 0,t=o.yes===!0&&o["dry-run"]!==!0,e=m(a,{days:n,dryRun:!t,skipGrace:t,actor:"cli"});if(e.status==="disabled"){console.log("Ingest retention is off (WYRM_INGEST_RETAIN_DAYS=0), so nothing ages out.");return}d(t?`Ingest retention: erased (limit ${e.days}d)`:`Ingest retention (dry run, limit ${e.days}d)`),console.log(l(["Category",t?"Erased":"Rows past the limit"],e.categories.map(r=>[r.category,String(t?r.deleted:r.matched)])));const s={};for(const r of e.categories)for(const[i,p]of Object.entries(r.counts))s[i]=(s[i]??0)+p;if(Object.keys(s).length>0&&(console.log(t?" Removed, per table:":" Would remove, per table:"),$(s," ")),e.graceEndsAt){const r=Date.parse(e.graceEndsAt)<=Date.now();console.log(` Automatic enforcement by maintenance ${r?"has been active since":"starts"} ${e.graceEndsAt.slice(0,10)}.`)}else console.log(` Automatic enforcement has not started: the first maintenance run records a preview and waits ${y} days.`);t?(u(`Erased ${e.deleted} ingested row(s) and ${e.derived} derived row(s); the erasure is recorded in erasure_log.`),console.log(" Snapshots still hold the erased rows until they rotate out: wyrm backup prune, wyrm backup legacy.")):(console.log(c.dim(`
2
- Dry run: nothing was deleted. --yes erases these now, without waiting for the grace period.`)),console.log(c.dim(" Change the limit with WYRM_INGEST_RETAIN_DAYS=<days> (0 turns retention off).")))}function _(a,o){const n=o.apply===!0&&o["dry-run"]!==!0,t=a.getDatabase(),e=f(t,{apply:n,ingestedCategories:h(a)});d(n?"Redaction back-fill":"Redaction back-fill (dry run)"),console.log(l(["Table","Scanned","Rows with credentials","Fields"],e.tables.map(r=>[r.table,String(r.scanned),String(r.rows),String(r.fields)])));const s=e.tables.reduce((r,i)=>r+i.rows,0);if(!n){console.log(c.dim(`
3
- Dry run: nothing changed. ${s>0?"Re-run with --apply (add --vacuum to rewrite the file).":"Nothing to redact."}`));return}u(`Redacted ${s} row(s). Matched text is never printed.`),e.vectorsDropped>0&&console.log(` Dropped ${e.vectorsDropped} vector(s) built from the old text: wyrm index rebuild re-embeds them.`),o.vacuum===!0?(a.vacuum(),a.checkpoint(),console.log(" Vacuumed: the file is rewritten, so pages that held the old text are gone.")):s>0&&console.log(c.dim(" Freed pages may still hold the old text: wyrm maintenance redact --apply --vacuum rewrites the file.")),s>0&&g("Existing snapshots still hold the unredacted rows. Take a fresh one (wyrm backup create --encrypt), then remove the old ones (wyrm backup prune, wyrm backup legacy).")}function T(a){const o=w({dbPath:a}),n=[];if(o.exposed.length>0&&n.push({ok:!1,label:"Backup permissions",detail:`${o.exposed.length} backup file(s) or folder(s) in the Wyrm home are readable by other users`,fix:"wyrm backup legacy --dry-run, then wyrm backup legacy --apply (files 0600, folders 0700)"}),o.legacy.length>0){const t=o.legacy.reduce((e,s)=>e+s.bytes,0);n.push({ok:!1,label:"Stray backups",detail:`${o.legacy.length} database copy(ies) outside rotation (${R(t)}); nothing ages them out, and they keep rows an erasure removed`,fix:"wyrm backup legacy --dry-run, then --apply --adopt to move them into rotation"})}if(n.length===0){const t=b(k(a));n.push(t.length>0?{ok:!0,label:"Backups",detail:`${t.length} rotated snapshot(s), newest ${Math.floor((Date.now()-t[0].at.getTime())/864e5)}d old, no stray or exposed copies`}:{ok:!0,label:"Backups",detail:"no snapshots yet (optional): wyrm backup create --encrypt"})}return n}export{A as cmdForgetRetention,_ as cmdMaintenanceRedact,T as doctorBackupRows,$ as printErasureCounts};
1
+ import{c,formatTable as l,printSection as d,printSuccess as p,printWarning as g}from"./cli.js";import{enforceIngestRetention as m,ingestedCategories as h,RETENTION_GRACE_DAYS as y}from"./retention.js";import{redactBackfill as f}from"./redact-backfill.js";import{scanBackupHygiene as w,listBackups as b,defaultBackupDir as k}from"./backup.js";import{fmtBytes as v}from"./backup-cli.js";function R(a,o=" "){for(const[r,t]of Object.entries(a).sort((e,s)=>s[1]-e[1]))console.log(`${o}${String(t).padStart(6)} ${r}`)}function A(a,o){const r=typeof o.days=="string"?Number(o.days):void 0,t=o.yes===!0&&o["dry-run"]!==!0,e=m(a,{days:r,dryRun:!t,skipGrace:t,actor:"cli"});if(e.status==="disabled"){console.log("Ingest retention is off (WYRM_INGEST_RETAIN_DAYS=0), so nothing ages out.");return}d(t?`Ingest retention: erased (limit ${e.days}d)`:`Ingest retention (dry run, limit ${e.days}d)`),console.log(l(["Category",t?"Erased":"Rows past the limit"],e.categories.map(n=>[n.category,String(t?n.deleted:n.matched)])));const s={};for(const n of e.categories)for(const[i,u]of Object.entries(n.counts))s[i]=(s[i]??0)+u;if(Object.keys(s).length>0&&(console.log(t?" Removed, per table:":" Would remove, per table:"),R(s," ")),e.graceEndsAt){const n=Date.parse(e.graceEndsAt)<=Date.now();console.log(` Automatic enforcement by maintenance ${n?"has been active since":"starts"} ${e.graceEndsAt.slice(0,10)}.`)}else console.log(` Automatic enforcement has not started: the first maintenance run records a preview and waits ${y} days.`);t?(p(`Erased ${e.deleted} ingested row(s) and ${e.derived} derived row(s); the erasure is recorded in erasure_log.`),console.log(" Snapshots still hold the erased rows until they rotate out: wyrm backup prune, wyrm backup legacy.")):(console.log(c.dim(`
2
+ Dry run: nothing was deleted. --yes erases these now, without waiting for the grace period.`)),console.log(c.dim(" Change the limit with WYRM_INGEST_RETAIN_DAYS=<days> (0 turns retention off).")))}function _(a,o){const r=o.apply===!0&&o["dry-run"]!==!0,t=a.getDatabase(),e=f(t,{apply:r,ingestedCategories:h(a)});d(r?"Redaction back-fill":"Redaction back-fill (dry run)"),console.log(l(["Table","Scanned","Rows with credentials","Fields"],e.tables.map(n=>[n.table,String(n.scanned),String(n.rows),String(n.fields)])));const s=e.tables.reduce((n,i)=>n+i.rows,0);if(!r){console.log(c.dim(`
3
+ Dry run: nothing changed. ${s>0?"Re-run with --apply (add --vacuum to rewrite the file).":"Nothing to redact."}`));return}p(`Redacted ${s} row(s). Matched text is never printed.`),e.vectorsDropped>0&&console.log(` Dropped ${e.vectorsDropped} vector(s) built from the old text: wyrm index rebuild re-embeds them.`),o.vacuum===!0?(a.vacuum(),a.checkpoint(),console.log(" Vacuumed: the file is rewritten, so pages that held the old text are gone.")):s>0&&console.log(c.dim(" Freed pages may still hold the old text: wyrm maintenance redact --apply --vacuum rewrites the file.")),s>0&&g("Existing snapshots still hold the unredacted rows. Take a fresh one (wyrm backup create --encrypt), then remove the old ones (wyrm backup prune, wyrm backup legacy).")}function B(a){const o=w({dbPath:a}),r=[];if(process.platform==="win32"&&r.push({ok:!0,label:"Backup permissions",detail:"not checked on Windows (access there is governed by ACLs, not mode bits)"}),o.exposed.length>0&&r.push({ok:!1,label:"Backup permissions",detail:`${o.exposed.length} backup file(s) or folder(s) in the Wyrm home are readable by other users`,fix:"wyrm backup legacy --dry-run, then wyrm backup legacy --apply (files 0600, folders 0700)"}),o.legacy.length>0){const t=o.legacy.reduce((e,s)=>e+s.bytes,0);r.push({ok:!1,label:"Stray backups",detail:`${o.legacy.length} database copy(ies) outside rotation (${v(t)}); nothing ages them out, and they keep rows an erasure removed`,fix:"wyrm backup legacy --dry-run, then --apply --adopt to move them into rotation"})}if(r.length===0){const t=b(k(a));r.push(t.length>0?{ok:!0,label:"Backups",detail:`${t.length} rotated snapshot(s), newest ${Math.floor((Date.now()-t[0].at.getTime())/864e5)}d old, no stray or exposed copies`}:{ok:!0,label:"Backups",detail:"no snapshots yet (optional): wyrm backup create --encrypt"})}return r}export{A as cmdForgetRetention,_ as cmdMaintenanceRedact,B as doctorBackupRows,R as printErasureCounts};
@@ -1,9 +1,9 @@
1
- import{TOOL_ANNOTATIONS as d}from"../tool-annotations.js";import{CloudSyncDaemon as h}from"../cloud-sync.js";import{getCrypto as f,initializeCrypto as g}from"../crypto.js";import{activateLicense as k,getLicenseInfo as w,getTier as v,hasFeature as y}from"../license.js";import{loadSession as x}from"../cloud/client.js";import{execFile as b}from"node:child_process";import{promisify as _}from"node:util";import{fileURLToPath as $}from"node:url";import{dirname as S,join as E}from"node:path";const P=_(b);async function T(a){const l=x();if(!l||!l.device_id)return{ok:!1,text:`\u{F115D} Sovereign cloud sync: this device is not logged in.
1
+ import{TOOL_ANNOTATIONS as d}from"../tool-annotations.js";import{CloudSyncDaemon as h}from"../cloud-sync.js";import{getCrypto as g,initializeCrypto as f}from"../crypto.js";import{activateLicense as k,getLicenseInfo as w,getTier as b,hasFeature as y}from"../license.js";import{loadSession as v}from"../cloud/client.js";import{execFile as x}from"node:child_process";import{promisify as _}from"node:util";import{fileURLToPath as $}from"node:url";import{dirname as S,join as P}from"node:path";const E=_(x);async function T(a){const l=v();if(!l||!l.device_id)return{ok:!1,text:`\u{F115D} Sovereign cloud sync: this device is not logged in.
2
2
  The login is an interactive device flow, so run it in a terminal:
3
3
  wyrm cloud login
4
- Then retry this action. (Status/sync here drive an already-registered device.)`};const i=E(S($(import.meta.url)),"..","wyrm-cli.js");try{const{stdout:r,stderr:c}=await P(process.execPath,[i,"cloud",...a],{timeout:18e4,maxBuffer:4194304,env:{...process.env}});return{ok:!0,text:(r||"").trim()||(c||"").trim()||"(no output)"}}catch(r){return{ok:!1,text:`\u{F115D} Sovereign cloud sync failed.
4
+ Then retry this action. (Status/sync here drive an already-registered device.)`};const i=P(S($(import.meta.url)),"..","wyrm-cli.js");try{const{stdout:r,stderr:c}=await E(process.execPath,[i,"cloud",...a],{timeout:18e4,maxBuffer:4194304,env:{...process.env}});return{ok:!0,text:(r||"").trim()||(c||"").trim()||"(no output)"}}catch(r){return{ok:!1,text:`\u{F115D} Sovereign cloud sync failed.
5
5
  ${[r?.stdout,r?.stderr].filter(Boolean).join(`
6
- `).trim()||(r?.message??"unknown error")}`}}}const q=[{name:"wyrm_license",description:"View current license status, tier, and available features",inputSchema:{type:"object",properties:{}},annotations:d.wyrm_license,aliases:[],handler:async(a,l)=>{const{cachedResponse:i,sync:r,toolCounts:c}=l,n=a||{},t=w(),e=v();let o=`\u{F115D} **Wyrm License Status**
6
+ `).trim()||(r?.message??"unknown error")}`}}}const q=[{name:"wyrm_license",description:"View current license status, tier, and available features",inputSchema:{type:"object",properties:{}},annotations:d.wyrm_license,aliases:[],handler:async(a,l)=>{const{cachedResponse:i,sync:r,toolCounts:c}=l,n=a||{},t=w(),e=b();let o=`\u{F115D} **Wyrm License Status**
7
7
 
8
8
  `;if(o+=`- **Tier:** ${e.charAt(0).toUpperCase()+e.slice(1)}
9
9
  `,t.valid&&t.key){o+=`- **Key:** ${t.key}
@@ -23,20 +23,20 @@ ${[r?.stdout,r?.stderr].filter(Boolean).join(`
23
23
  `,o+=`- \u2713 FTS5 search
24
24
  `,o+=`- \u2713 File-based sync
25
25
 
26
- `,o+=`### Upgrade to Pro
26
+ `,o+=`### Included with Pro
27
27
  `,o+=`- Analytics dashboard & cost tracking
28
28
  `,o+=`- Cloud backup to R2
29
29
  `,o+=`- Encryption at rest
30
30
  `,o+=`- Priority support
31
31
 
32
32
  `,o+=`### Already have a license?
33
- `,o+="Activate it: paste the license JSON into the `wyrm_activate` tool, or save it to `~/.wyrm/license.json` and restart Wyrm.\n",o+="Manage or download your license at https://account.ghosts.lk \xB7 buy one at https://ghosts.lk/wyrm."}return i(o)}},{name:"wyrm_activate",description:"Activate a license key to unlock Pro/Team/Enterprise features",inputSchema:{type:"object",properties:{license:{type:"string",description:"License JSON string (signed license from Ghost Protocol)"}},required:["license"]},annotations:d.wyrm_activate,aliases:[],handler:async(a,l)=>{const i=a?.license;if(!i)throw new Error("License JSON string required");try{const r=k(i);return r.valid?{content:[{type:"text",text:`\u{F115D} \u2713 License activated!
33
+ `,o+="Activate it: paste the license JSON into the `wyrm_activate` tool, or save it to `~/.wyrm/license.json` and restart Wyrm.\n",o+="Manage or download your license at https://account.ghosts.lk. To get one, write to support@ghosts.lk (https://ghosts.lk/wyrm)."}return i(o)}},{name:"wyrm_activate",description:"Activate a license key to unlock Pro/Team/Enterprise features",inputSchema:{type:"object",properties:{license:{type:"string",description:"License JSON string (signed license from Ghost Protocol)"}},required:["license"]},annotations:d.wyrm_activate,aliases:[],handler:async(a,l)=>{const i=a?.license;if(!i)throw new Error("License JSON string required");try{const r=k(i);return r.valid?{content:[{type:"text",text:`\u{F115D} \u2713 License activated!
34
34
 
35
35
  - **Tier:** ${r.tier}
36
36
  - **Features:** ${r.features.join(", ")}
37
37
 
38
38
  Restart Wyrm to apply all features.`}]}:{content:[{type:"text",text:`\u{F115D} \u2717 License activation failed: ${r.error||"unknown error"}`}],isError:!0}}catch{return{content:[{type:"text",text:"\u{F115D} \u2717 Invalid license format. Please verify your license key."}],isError:!0}}}},{name:"wyrm_analytics_dashboard",description:"View usage analytics dashboard: tool calls, tokens, costs, cache efficiency (Pro+)",inputSchema:{type:"object",properties:{days:{type:"number",description:"Number of days to analyze (default: 30)"}}},annotations:d.wyrm_analytics_dashboard,aliases:[],handler:async(a,l)=>{const{analytics:i}=l,r=a||{};if(!y("analytics"))return{content:[{type:"text",text:`\u{F115D} Analytics requires a Pro license or higher.
39
- Visit https://ghosts.lk/wyrm to upgrade.`}]};const c=a?.days||30,n=i.dashboard(c);let t=`\u{F115D} **Usage Analytics** (last ${c} days)
39
+ Pro is arranged by writing to support@ghosts.lk. Details: https://ghosts.lk/wyrm`}]};const c=a?.days||30,n=i.dashboard(c);let t=`\u{F115D} **Usage Analytics** (last ${c} days)
40
40
 
41
41
  `;if(t+=`### Overview
42
42
  `,t+=`- **Total Tool Calls:** ${n.summary.total_calls.toLocaleString()}
@@ -52,7 +52,7 @@ Visit https://ghosts.lk/wyrm to upgrade.`}]};const c=a?.days||30,n=i.dashboard(c
52
52
  `}if(n.daily.length>0){t+=`### Recent Activity
53
53
  `;for(const e of n.daily.slice(-7))t+=`- ${e.date}: ${e.calls} calls, ${e.tokens.toLocaleString()} tokens
54
54
  `}return{content:[{type:"text",text:t}]}}},{name:"wyrm_cost_report",description:"View estimated API cost report for current billing period (Pro+)",inputSchema:{type:"object",properties:{period:{type:"string",description:"Period in YYYY-MM format (default: current month)"}}},annotations:d.wyrm_cost_report,aliases:[],handler:async(a,l)=>{const{analytics:i}=l,r=a||{};if(!y("analytics"))return{content:[{type:"text",text:`\u{F115D} Cost reports require a Pro license or higher.
55
- Visit https://ghosts.lk/wyrm to upgrade.`}]};const c=a?.period,n=i.costReport(c);let t=`\u{F115D} **Cost Report**: ${n.period}
55
+ Pro is arranged by writing to support@ghosts.lk. Details: https://ghosts.lk/wyrm`}]};const c=a?.period,n=i.costReport(c);let t=`\u{F115D} **Cost Report**: ${n.period}
56
56
 
57
57
  `;if(t+=`- **Total Cost:** $${n.total_cost_usd.toFixed(4)}
58
58
  `,t+=`- **Projected Monthly:** $${n.projected_monthly_usd.toFixed(2)}
@@ -60,7 +60,7 @@ Visit https://ghosts.lk/wyrm to upgrade.`}]};const c=a?.period,n=i.costReport(c)
60
60
  `,n.tools.length>0){t+=`### By Tool
61
61
  `;for(const e of n.tools.slice(0,10))t+=`- **${e.tool}**: ${e.calls} calls, $${e.cost_usd.toFixed(4)}
62
62
  `}return{content:[{type:"text",text:t}]}}},{name:"wyrm_cloud_backup",description:"Legacy whole-DB snapshot backup to encrypted cloud storage (Pro+): backup, restore, list, prune. This is a full-database snapshot (last-write-wins on restore), NOT the sovereign per-row sync; for multi-device memory use wyrm_cloud_sync action=sovereign. Keep this for simple point-in-time backups.",inputSchema:{type:"object",properties:{action:{type:"string",description:"Action: backup, restore, list, prune",enum:["backup","restore","list","prune"]},backupKey:{type:"string",description:"Backup key for restore (from list)"},keepCount:{type:"number",description:"Number of backups to keep when pruning (default: 10)"}},required:["action"]},annotations:d.wyrm_cloud_backup,aliases:[],handler:async(a,l)=>{const{cloudBackup:i,db:r}=l,c=a||{};if(!y("cloud_backup"))return{content:[{type:"text",text:`\u{F115D} Cloud backup requires a Pro license or higher.
63
- Visit https://ghosts.lk/wyrm to upgrade.`}]};const n=a?.action;switch(n){case"backup":{const t=await i.backup(r.getDatabasePath()),e=t.metadata;return{content:[{type:"text",text:`\u{F115D} \u2713 Backup complete!
63
+ Pro is arranged by writing to support@ghosts.lk. Details: https://ghosts.lk/wyrm`}]};const n=a?.action;switch(n){case"backup":{const t=await i.backup(r.getDatabasePath()),e=t.metadata;return{content:[{type:"text",text:`\u{F115D} \u2713 Backup complete!
64
64
 
65
65
  - **Key:** ${t.key}
66
66
  - **Size:** ${(e.db_size/1024).toFixed(1)} KB \u2192 ${(e.compressed_size/1024).toFixed(1)} KB compressed
@@ -75,14 +75,14 @@ Visit https://ghosts.lk/wyrm to upgrade.`}]};const n=a?.action;switch(n){case"ba
75
75
 
76
76
  `;for(const o of t)e+=`- **${o.timestamp}**: ${(o.db_size/1024).toFixed(1)} KB, encrypted: ${o.encrypted}, machine: ${o.machine_id.slice(0,8)}...
77
77
  `;return{content:[{type:"text",text:e}]}}case"prune":{const t=a?.keepCount||10;return{content:[{type:"text",text:`\u{F115D} \u2713 Pruned ${(await i.pruneBackups(t)).deleted} old backup(s). Kept ${t} most recent.`}]}}default:return{content:[{type:"text",text:`Unknown backup action: ${n}. Use: backup, restore, list, prune`}],isError:!0}}}},{name:"wyrm_cloud_sync",description:"Cloud sync. The SOVEREIGN path is the promoted one: zero-knowledge, per-row AES-256-GCM E2E-encrypted delta sync with tombstone propagation; the cloud never sees plaintext. Use action 'sovereign' to sync (push+pull), 'sovereign-preview' for a dry run, 'sovereign-status' to show device + sync state. For hands-off periodic sync, 'sovereign-daemon-start' / 'sovereign-daemon-stop' / 'sovereign-daemon-status' run a background daemon. (First-time login is an interactive device flow: run `wyrm cloud login` in a terminal once.) The start/stop/restart/status/force-sync actions drive the LEGACY whole-DB snapshot daemon (last-write-wins by mtime, weaker), retained only for simple full-DB backup; it is NOT the sovereign path. Prefer sovereign for multi-device memory.",inputSchema:{type:"object",properties:{action:{type:"string",description:"Sovereign (preferred): sovereign, sovereign-preview, sovereign-status, sovereign-daemon-start, sovereign-daemon-stop, sovereign-daemon-status. Legacy snapshot daemon: start, stop, restart, status, force-sync.",enum:["sovereign","sovereign-preview","sovereign-status","sovereign-daemon-start","sovereign-daemon-stop","sovereign-daemon-status","start","stop","restart","status","force-sync"]},all:{type:"boolean",description:"Sovereign only: sync every semantic table, ignoring visibility flags (default: false)"},interval_minutes:{type:"number",description:"Legacy snapshot only: sync interval (default: 10)"},keep_count:{type:"number",description:"Legacy snapshot only: snapshots to retain after pruning (default: 20)"}},required:["action"]},annotations:d.wyrm_cloud_sync,aliases:[],handler:async(a,l)=>{const{cloudBackup:i,cloudSyncManager:r,db:c,sync:n}=l,t=a||{},e=a?.action,o={sovereign:a?.all?["sync","--all"]:["sync"],"sovereign-preview":["sync","--dry-run"],"sovereign-status":["status"],"sovereign-daemon-start":["daemon","start"],"sovereign-daemon-stop":["daemon","stop"],"sovereign-daemon-status":["daemon","status"]};if(e in o){const s=await T(o[e]);return{content:[{type:"text",text:s.text}],isError:!s.ok}}if(!y("cloud_backup"))return{content:[{type:"text",text:`\u{F115D} The legacy snapshot daemon requires a Pro license or higher.
78
- Visit https://ghosts.lk/wyrm to upgrade. (The sovereign path, action=sovereign, is the preferred sync.)`}]};const p=a?.interval_minutes,m=a?.keep_count;switch(e){case"start":{const s=r.start({interval_minutes:p,keep_count:m});return s.ok?{content:[{type:"text",text:`\u{F115D} \u2713 cloud-sync daemon started (pid ${s.pid}). Logs: ~/.wyrm/wyrm-cloud-sync.log`}]}:{content:[{type:"text",text:`\u{F115D} cloud-sync: ${s.reason??"failed to start"}${s.pid?` (pid ${s.pid})`:""}`}],isError:!s.pid}}case"stop":{const s=r.stop();return{content:[{type:"text",text:s.ok?"\u{F115D} \u2713 cloud-sync daemon stopped":`\u{F115D} cloud-sync: ${s.reason??"failed to stop"}`}],isError:!s.ok}}case"restart":{r.stop(),await new Promise(u=>{setTimeout(u,500)});const s=r.start({interval_minutes:p,keep_count:m});return{content:[{type:"text",text:s.ok?`\u{F115D} \u2713 cloud-sync daemon restarted (pid ${s.pid})`:`\u{F115D} cloud-sync restart failed: ${s.reason??"unknown"}`}],isError:!s.ok}}case"status":{const s=r.status(),u=["\u{F115D} **Legacy Snapshot Daemon Status** (not the sovereign path; see action=sovereign-status)"];return u.push(`- Running: ${s.running?"\u2713 yes":"\u2717 no"}`),s.pid&&u.push(`- PID: ${s.pid}`),s.state&&(s.state.last_uploaded_ts&&u.push(`- Last upload: ${new Date(s.state.last_uploaded_ts).toISOString()}${s.state.last_uploaded_key?` (${s.state.last_uploaded_key})`:""}`),s.state.last_restored_key&&u.push(`- Last restore: ${s.state.last_restored_key}`),s.state.last_check_ts&&u.push(`- Last check: ${new Date(s.state.last_check_ts).toISOString()}`),u.push(`- Machine: ${s.state.machine}`)),{content:[{type:"text",text:u.join(`
78
+ Pro is arranged by writing to support@ghosts.lk. Details: https://ghosts.lk/wyrm (The sovereign path, action=sovereign, is the preferred sync.)`}]};const p=a?.interval_minutes,m=a?.keep_count;switch(e){case"start":{const s=r.start({interval_minutes:p,keep_count:m});return s.ok?{content:[{type:"text",text:`\u{F115D} \u2713 cloud-sync daemon started (pid ${s.pid}). Logs: ~/.wyrm/wyrm-cloud-sync.log`}]}:{content:[{type:"text",text:`\u{F115D} cloud-sync: ${s.reason??"failed to start"}${s.pid?` (pid ${s.pid})`:""}`}],isError:!s.pid}}case"stop":{const s=r.stop();return{content:[{type:"text",text:s.ok?"\u{F115D} \u2713 cloud-sync daemon stopped":`\u{F115D} cloud-sync: ${s.reason??"failed to stop"}`}],isError:!s.ok}}case"restart":{r.stop(),await new Promise(u=>{setTimeout(u,500)});const s=r.start({interval_minutes:p,keep_count:m});return{content:[{type:"text",text:s.ok?`\u{F115D} \u2713 cloud-sync daemon restarted (pid ${s.pid})`:`\u{F115D} cloud-sync restart failed: ${s.reason??"unknown"}`}],isError:!s.ok}}case"status":{const s=r.status(),u=["\u{F115D} **Legacy Snapshot Daemon Status** (not the sovereign path; see action=sovereign-status)"];return u.push(`- Running: ${s.running?"\u2713 yes":"\u2717 no"}`),s.pid&&u.push(`- PID: ${s.pid}`),s.state&&(s.state.last_uploaded_ts&&u.push(`- Last upload: ${new Date(s.state.last_uploaded_ts).toISOString()}${s.state.last_uploaded_key?` (${s.state.last_uploaded_key})`:""}`),s.state.last_restored_key&&u.push(`- Last restore: ${s.state.last_restored_key}`),s.state.last_check_ts&&u.push(`- Last check: ${new Date(s.state.last_check_ts).toISOString()}`),u.push(`- Machine: ${s.state.machine}`)),{content:[{type:"text",text:u.join(`
79
79
  `)}]}}case"force-sync":{const u=await new h(i,c.getDatabasePath()).tick();return{content:[{type:"text",text:`\u{F115D} force-sync: ${JSON.stringify(u,null,2)}`}]}}default:return{content:[{type:"text",text:`Unknown cloud-sync action: ${e}. Use: start, stop, restart, status, force-sync`}],isError:!0}}}},{name:"wyrm_encrypt_setup",description:"Set up or check encryption status for sensitive data (Pro+)",inputSchema:{type:"object",properties:{action:{type:"string",description:"Action: status, enable, test",enum:["status","enable","test"]},password:{type:"string",description:"Encryption password (min 8 chars, required for enable)"}},required:["action"]},annotations:d.wyrm_encrypt_setup,aliases:[],handler:async(a,l)=>{const i=a||{};if(!y("encryption"))return{content:[{type:"text",text:`\u{F115D} Encryption setup requires a Pro license or higher.
80
- Visit https://ghosts.lk/wyrm to upgrade.`}]};const r=a?.action,c=f();switch(r){case"status":{const n=c.isEnabled();let t=`\u{F115D} **Encryption Status**
80
+ Pro is arranged by writing to support@ghosts.lk. Details: https://ghosts.lk/wyrm`}]};const r=a?.action,c=g();switch(r){case"status":{const n=c.isEnabled();let t=`\u{F115D} **Encryption Status**
81
81
 
82
82
  `;return t+=`- **Enabled:** ${n?"\u2713 Yes":"\u2717 No"}
83
83
  `,t+=`- **Algorithm:** AES-256-GCM
84
84
  `,n?t+=`- **Status:** Active (new data is encrypted at rest)
85
- `:t+='\nTo enable, run: `wyrm_encrypt_setup` with action "enable" and a strong password.\n',{content:[{type:"text",text:t}]}}case"enable":{const n=a?.password;return!n||n.length<8?{content:[{type:"text",text:"\u{F115D} Password must be at least 8 characters."}],isError:!0}:(g(n),{content:[{type:"text",text:`\u{F115D} \u2713 Encryption enabled!
85
+ `:t+='\nTo enable, run: `wyrm_encrypt_setup` with action "enable" and a strong password.\n',{content:[{type:"text",text:t}]}}case"enable":{const n=a?.password;return!n||n.length<8?{content:[{type:"text",text:"\u{F115D} Password must be at least 8 characters."}],isError:!0}:(f(n),{content:[{type:"text",text:`\u{F115D} \u2713 Encryption enabled!
86
86
 
87
87
  - **Algorithm:** AES-256-GCM
88
88
  - **Key derived from:** your password (PBKDF2)