wyrm-mcp 8.4.0 → 8.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ import{readFileSync as d}from"node:fs";import{homedir as p}from"node:os";import{join as R}from"node:path";const n={product:"Wyrm",fleetLabel:"Fleet",fleetTitle:"Fleet Channel",fleetGrove:"fleet-warroom",fleetBlurb:"The multi-agent coordination channel. Findings, claims, fix-receipts and verdicts flow as structured work between agents."};function L(){try{const t=process.env.WYRM_BRAND_FILE||R(p(),".wyrm","brand.json");return JSON.parse(d(t,"utf8"))}catch{return{}}}function F(t=!1){const e=L(),l=i=>{const r=process.env[i];return r&&r.trim()?r.trim():void 0},o=(i,r,_)=>l(i)??(r&&r.trim()?r.trim():void 0)??_,a=!!(e.fleetLabel||e.fleetTitle||e.fleet==="on"||e.profile),c=(l("WYRM_UI_FLEET")||e.fleet||(a?"on":"auto")).toLowerCase(),u=c==="on"?!0:c==="off"?!1:t,f=l("WYRM_UI_BRAND_MARK_URL")??e.markUrl,s=l("WYRM_UI_BRAND_MARK")??e.mark,m=f?`<img src="${E(f)}" alt="" class="logo-mark" />`:s?B(s):"";return{product:o("WYRM_UI_BRAND_NAME",e.product,n.product),markHtml:m,showFleet:u,fleetLabel:o("WYRM_UI_FLEET_LABEL",e.fleetLabel,n.fleetLabel),fleetTitle:o("WYRM_UI_FLEET_TITLE",e.fleetTitle,n.fleetTitle),fleetGrove:o("WYRM_UI_FLEET_GROVE",e.fleetGrove,n.fleetGrove),fleetBlurb:l("WYRM_UI_FLEET_BLURB")??(e.fleetBlurb&&e.fleetBlurb.trim()?e.fleetBlurb.trim():void 0)??n.fleetBlurb}}function E(t){return String(t).replace(/["'<>]/g,"").slice(0,300)}function B(t){return String(t).replace(/[<>&"']/g,"").slice(0,24)}export{F as resolveBranding};
package/dist/cloud/cli.js CHANGED
@@ -1,4 +1,4 @@
1
- import{randomBytes as I}from"node:crypto";import{writeFileSync as N}from"node:fs";import{hostname as P}from"node:os";import{resolve as R,dirname as L,join as A}from"node:path";import{fileURLToPath as O}from"node:url";import{createInterface as W}from"node:readline";import{CloudClient as d,CloudError as b,DEFAULT_BASE as p,SESSION_FILE as f,loadSession as i,saveSession as _,clearSession as E}from"./client.js";import{getMasterKey as M,keyExists as h,keyFilePath as g}from"./crypto.js";import{computeMachineFp as x,classifySession as F}from"./machine-id.js";import{runSync as B,CopiedSessionError as U}from"./sync-engine.js";import{SovereignSyncManager as j}from"./sync-daemon.js";import{initializeLicense as G,hasFeature as H,getTier as K}from"../license.js";function w(){G(),H("cloud_backup")||(console.error("\u{F115D} Wyrm Cloud sync requires a Pro license or higher."),console.error(` Current tier: ${K()}`),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 v(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 $(e){return e?new Date(e).toISOString().replace("T"," ").slice(0,16)+" UTC":"\u2014"}async function fe(e){const t=e[0],n=e.slice(1);try{switch(t){case void 0:case"help":case"--help":case"-h":return Y();case"login":return await q(n);case"logout":return await z();case"status":return await J();case"doctor":return Q();case"devices":return await V(n);case"sync":return await X(n);case"daemon":return await ee(n);case"export":return await oe(n);case"delete":return await se(n);case"recovery":return await ne(n);default:console.error(`Unknown subcommand: wyrm cloud ${t}`),console.error("Try `wyrm cloud help`"),process.exit(1)}}catch(o){if(o instanceof U&&process.exit(1),o instanceof b)o.status===401?console.error("\u2718 Not authenticated. Run `wyrm cloud login`."):o.status===403?console.error(`\u2718 Forbidden: ${o.message}`):o.status===0?(console.error(`\u2718 ${o.message}`),console.error(" Check your internet connection. If wyrm.ghosts.lk is unreachable, see status at https://www.cloudflarestatus.com")):o.status===501?console.error(`\u2718 Service not configured: ${o.message}`):console.error(`\u2718 ${o.message} (HTTP ${o.status})`);else if(o instanceof Error){const r=o.message;r.includes("Not logged in")?console.error("\u2718 Not logged in. Run `wyrm cloud login`."):r.includes("Wyrm Cloud key")?console.error(`\u2718 Encryption-key error: ${r}`):console.error(`\u2718 ${r}`)}else console.error("\u2718 Unexpected error:",o);process.exit(1)}}function Y(){console.log(`\u{F115D} wyrm cloud \u2014 sync your Wyrm memory across devices
1
+ import{randomBytes as A}from"node:crypto";import{writeFileSync as P}from"node:fs";import{hostname as O}from"node:os";import{resolve as L,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 d,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 S,keyFilePath as a,decrypt as j}from"./crypto.js";import{computeMachineFp as I,classifySession as G}from"./machine-id.js";import{runSync as H,CopiedSessionError as Y}from"./sync-engine.js";import{SovereignSyncManager as K,restartIntervalMinutes as q}from"./sync-daemon.js";import{initializeLicense as z,hasFeature as J,getTier as V}from"../license.js";function b(){z(),J("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 E(e){return e?new Date(e).toISOString().replace("T"," ").slice(0,16)+" UTC":"\u2014"}async function ke(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 ne(n);case"daemon":return await re(n);case"export":return await ce(n);case"delete":return await le(n);case"recovery":return await ie(n);default:console.error(`Unknown subcommand: wyrm cloud ${r}`),console.error("Try `wyrm cloud help`"),process.exit(1)}}catch(s){if(s instanceof Y&&process.exit(1),s instanceof w)s.status===401?console.error("\u2718 Not authenticated. Run `wyrm cloud login`."):s.status===403?console.error(`\u2718 Forbidden: ${s.message}`):s.status===0?(console.error(`\u2718 ${s.message}`),console.error(" Check your internet connection. If wyrm.ghosts.lk is unreachable, see status at https://www.cloudflarestatus.com")):s.status===501?console.error(`\u2718 Service not configured: ${s.message}`):console.error(`\u2718 ${s.message} (HTTP ${s.status})`);else if(s instanceof Error){const t=s.message;t.includes("Not logged in")?console.error("\u2718 Not logged in. Run `wyrm cloud login`."):t.includes("Wyrm Cloud key")?console.error(`\u2718 Encryption-key error: ${t}`):console.error(`\u2718 ${t}`)}else console.error("\u2718 Unexpected error:",s);process.exit(1)}}function Q(){console.log(`\u{F115D} wyrm cloud \u2014 sync your Wyrm memory across devices
2
2
 
3
3
  USAGE:
4
4
  wyrm cloud <subcommand> [args]
@@ -36,6 +36,6 @@ CONSTITUTION:
36
36
 
37
37
  DOMAIN:
38
38
  https://wyrm.ghosts.lk
39
- `)}async function q(e){i()&&(console.error("Already logged in. Run `wyrm cloud logout` first to switch accounts."),process.exit(1));const t=new d(p),n=I(32).toString("hex");let o;try{o=await t.cliInit(n)}catch(l){console.error(`\u2718 Could not start login: ${l instanceof Error?l.message:l}`),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(` ${o.browser_url}`),console.log(""),console.log(" 2. Type this code when prompted:"),console.log(` ${o.code}`),console.log(""),console.log(" 3. Sign in with Google or GitHub."),console.log(""),console.log(` Waiting (expires in ${Math.round(o.expires_in_sec/60)} min)\u2026`);const s=Date.now()+o.expires_in_sec*1e3;let c,a;for(;Date.now()<s;){await new Promise(l=>{setTimeout(l,3e3)});try{const l=await t.cliPoll(n);if(l.ready&&l.session){c=l.session,a=l.account_id;break}}catch(l){console.error(`
40
- \u2718 Login failed: ${l instanceof Error?l.message:l}`),process.exit(1)}}(!c||!a)&&(console.error("\n\u2718 Login timed out. Run `wyrm cloud login` again."),process.exit(1)),_({base:p,session:c,account_id:a,created_at:Date.now()});const u=new d(p,c);let y="";try{y=(await u.me()).account.email}catch{}const m=`${P()} (${process.platform})`;let k;try{k=(await u.registerDevice(m)).device.id;const S=i();S&&_({...S,email:y,device_id:k,device_name:m,machine_fp:x()})}catch(l){console.error(`
41
- \u26A0 Logged in but could not register device: ${l instanceof Error?l.message:l}`),console.error(" Re-run `wyrm cloud login` to retry.");return}console.log(""),console.log(`\u2713 Signed in as ${y}`),console.log(`\u2713 Device registered: ${m}`),console.log(`\u2713 Session saved to ${f}`);const D=!h();M(),D?(console.log(`\u2713 Encryption key generated at ${g()} (0600)`),console.log(""),console.log(" \u26A0 Back up the key file. Losing it means losing access to your synced data."),console.log(" The Wyrm Cloud server CANNOT recover it \u2014 that's the operator-owns-data"),console.log(" guarantee. Run `wyrm cloud recovery show` now to get a 24-word phrase,"),console.log(" or copy the key file to a password manager / encrypted backup.")):console.log(`\u2713 Encryption key reused from ${g()}`),console.log(""),console.log("Next: `wyrm cloud sync` to push your memory.")}async function z(){const e=i();if(!e){console.log("Not logged in.");return}try{await new d(e.base,e.session).logout(),console.log("\u2713 Session revoked server-side.")}catch(t){console.error(`\u26A0 Could not revoke server-side: ${t instanceof Error?t.message:t}`),console.error(" Local session cleared anyway.")}E(),console.log(`\u2713 Local session removed (${f}).`),console.log(" Note: ~/.wyrm/cloud.key kept. Delete manually if you want a clean slate.")}async function J(){const e=i();if(!e){console.log("Not logged in. Run `wyrm cloud login`.");return}const t=new d(e.base,e.session);try{const n=await t.me(),o=await t.syncStatus();console.log(""),console.log(`\u{F115D} Wyrm Cloud \u2014 ${n.account.email}`),console.log(""),console.log(` Tier: ${o.tier}`),console.log(` Storage: ${v(o.storage.used_bytes)} / ${v(o.storage.limit_bytes)} (${o.storage.percent}%)`),console.log(` Deltas: ${o.deltas}`),console.log(` Devices: ${o.devices}`),console.log(` Providers:${n.identities.map(r=>" "+r.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)`),T(e),console.log(""),h()?o.deltas>0&&(console.log(` Key: ${g()} (0600)`),console.log(` \u26A0 Back this file up. Losing it = losing access to your ${o.deltas} deltas.`),console.log("")):(console.log(" \u26A0 ENCRYPTION KEY MISSING"),console.log(` Expected at: ${g()}`),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 b&&n.status===401&&(console.error("Session expired or invalid. Run `wyrm cloud login` again."),process.exit(1)),n}}function T(e){const t=F(e.machine_fp);t.state==="match"?console.log(" Identity: \u2713 machine fingerprint matches (not a copied session)"):t.state==="adopt"?console.log(" Identity: \u2014 no fingerprint yet (pre-7.0.3 session; adopted on next sync)"):(console.log(" Identity: \u26A0 FINGERPRINT MISMATCH \u2014 this session looks COPIED"),console.log(` stored ${t.stored.slice(0,12)}\u2026 vs this machine ${t.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 Q(){const e=i();if(console.log(""),console.log("\u{F115D} Wyrm Cloud \u2014 device identity doctor"),console.log(""),!e){console.log(" Not logged in. Run `wyrm cloud login`."),console.log("");return}console.log(` Account: ${e.email??e.account_id.slice(0,8)+"\u2026"}`),console.log(` Device ID: ${e.device_id?e.device_id.slice(0,8)+"\u2026":"(none \u2014 re-run `wyrm cloud login`)"}`),console.log(` Server: ${e.base}`),console.log(` Stored fp: ${e.machine_fp?e.machine_fp.slice(0,12)+"\u2026":"(none \u2014 pre-7.0.3 session)"}`),console.log(` This box fp: ${x().slice(0,12)}\u2026`),T(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 V(e){const t=e[0],n=d.fromSession();if(t==="revoke"){const c=e[1];c||(console.error("Usage: wyrm cloud devices revoke <device-id>"),process.exit(1)),await n.revokeDevice(c),console.log(`\u2713 Device ${c.slice(0,8)}\u2026 revoked.`);return}const{devices:o}=await n.listDevices(),s=i()?.device_id;console.log(""),console.log(`\u{F115D} Devices (${o.length})`),console.log("");for(const c of o){const a=c.id===s?"\u2605":" ",u=c.revoked?"(revoked)":"";console.log(` ${a} ${c.id.slice(0,8)}\u2026 ${c.name.padEnd(30)} last_seen=${$(c.last_seen_at)} ${u}`)}console.log(""),console.log(" \u2605 = this machine"),console.log(" Revoke: wyrm cloud devices revoke <device-id>"),console.log("")}async function X(e){w();const t=e.includes("--dry-run"),n=e.includes("--all"),o=e.includes("--force");h()||(console.error("\u26A0 Encryption key not found at",g()),console.error(" A new key will be generated, but it will NOT decrypt deltas"),console.error(" pushed by other devices using a different key. If you have a backup,"),console.error(" restore it to ~/.wyrm/cloud.key (0600) before running sync."),console.error(" Continuing in 5 seconds \u2014 press Ctrl+C to abort."),await new Promise(s=>{setTimeout(s,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 r=await B({dryRun:t,all:n,force:o});if(console.log(""),console.log("\u{F115D} Sync complete"),console.log(""),console.log(` Pushed: ${r.pushed} row(s)${t?" (dry run)":""}`),console.log(` Pulled: ${r.pulled} row(s)`),console.log(` Deleted: ${r.deleted_local} row(s) (peer tombstones)`),r.errors.length>0){console.log(""),console.log(" Errors:");for(const s of r.errors)console.log(` - ${s}`)}console.log("")}function Z(){const e=A(L(O(import.meta.url)),"sync-daemon-entrypoint.js");return new j(e)}async function ee(e){w();const t=e[0]??"status",n=e.find(s=>s.startsWith("--interval=")),o=n?Number(n.split("=")[1]):void 0,r=Z();switch(t){case"start":{i()||(console.error("\u2718 Not logged in. Run `wyrm cloud login` first."),process.exit(1));const s=r.start({interval_minutes:o});s.ok||(console.error(`\u{F115D} sovereign-sync daemon: ${s.reason??"failed to start"}${s.pid?` (pid ${s.pid})`:""}`),process.exit(s.pid?0:1)),console.log(`\u{F115D} \u2713 sovereign-sync daemon started (pid ${s.pid}). Logs: ~/.wyrm/wyrm-sovereign-sync.log`);return}case"stop":{const s=r.stop();console.log(s.ok?"\u{F115D} \u2713 sovereign-sync daemon stopped":`\u{F115D} sovereign-sync daemon: ${s.reason??"failed to stop"}`);return}case"restart":{r.stop(),await new Promise(c=>{setTimeout(c,500)});const s=r.start({interval_minutes:o});console.log(s.ok?`\u{F115D} \u2713 sovereign-sync daemon restarted (pid ${s.pid})`:`\u{F115D} restart failed: ${s.reason??"unknown"}`);return}case"status":{const s=r.status();console.log("\u{F115D} Sovereign-sync daemon"),console.log(` Running: ${s.running?`\u2713 yes (pid ${s.pid})`:"\u2717 no"}`),s.state?.started_ts&&console.log(` Started: ${$(s.state.started_ts)}`),s.state?.last_tick_ts&&(console.log(` Last tick: ${$(s.state.last_tick_ts)} (exit ${s.state.last_tick_exit??"\u2014"})`),s.state.last_tick_summary&&console.log(` Last result: ${s.state.last_tick_summary}`));return}default:console.error(`Unknown daemon action: ${t}. Use: start, stop, restart, status`),process.exit(1)}}async function oe(e){w();const t=d.fromSession(),n=e.find(c=>!c.startsWith("-")),o=R(n??`wyrm-cloud-export-${new Date().toISOString().slice(0,10)}.json`);console.log(" Fetching account export\u2026");const r=await t.accountExport(),s=JSON.stringify(r,null,2);N(o,s,{mode:384}),console.log(""),console.log(`\u2713 Exported to ${o}`),console.log(""),console.log(` Account: ${r.account.email} (${r.account.id.slice(0,8)}\u2026)`),console.log(` Devices: ${r.devices.length}`),console.log(` Identities: ${r.identities.length}`),console.log(` Orgs: ${r.orgs.length}`),console.log(` Deltas: ${r.deltas.length}`),console.log(` Size: ${v(s.length)}`),console.log(""),console.log(" Deltas are still AES-256-GCM ciphertext. To decrypt them you"),console.log(` need the key at ${g()}. Back that up alongside this file.`),console.log("")}async function se(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=i();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 C(' Type "DELETE my account" to confirm: ')).trim()!=="DELETE my account"&&(console.error("\u2718 Confirmation phrase did not match. Aborting."),process.exit(1)),await new d(n.base,n.session).accountDelete(),E(),console.log(""),console.log("\u2713 Account deleted on server."),console.log(`\u2713 Local session removed (${f}).`),console.log(` Kept: ${g()} (in case you need to decrypt an old export).`),console.log("")}function C(e){return new Promise(t=>{const n=W({input:process.stdin,output:process.stdout});n.question(e,o=>{n.close(),t(o)})})}async function ne(e){const t=e[0];if(t==="show"){const{keyToMnemonic:n}=await import("./recovery.js"),r=n().split(" ");console.log(""),console.log("\u{1F511} Wyrm Cloud recovery phrase (BIP39, 24 words)"),console.log("");for(let s=0;s<4;s++){const c=r.slice(s*6,s*6+6).map((a,u)=>`${String(s*6+u+1).padStart(2)}. ${a.padEnd(10)}`).join(" ");console.log(` ${c}`)}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(t==="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 o=await C(" > "),{mnemonicToKey:r}=await import("./recovery.js"),{path:s,overwrote:c}=r(o,{overwrite:n});console.log(""),console.log(`\u2713 Master key restored to ${s} (0600)`),c&&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{fe as cmdCloud};
39
+ `)}async function X(e){const r=g();if(r)try{await new d(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 d(v),s=A(32).toString("hex");let t;try{t=await n.cliInit(s)}catch(c){console.error(`\u2718 Could not start login: ${c instanceof Error?c.message:c}`),process.exit(1)}console.log(""),console.log("\u{F115D} Sign in to Wyrm Cloud"),console.log(""),console.log(" 1. Open this URL on any device:"),console.log(` ${t.browser_url}`),console.log(""),console.log(" 2. Type this code when prompted:"),console.log(` ${t.code}`),console.log(""),console.log(" 3. Sign in with Google or GitHub."),console.log(""),console.log(` Waiting (expires in ${Math.round(t.expires_in_sec/60)} min)\u2026`);const l=Date.now()+t.expires_in_sec*1e3;let i,u;for(;Date.now()<l;){await new Promise(c=>{setTimeout(c,3e3)});try{const c=await n.cliPoll(s);if(c.ready&&c.session){i=c.session,u=c.account_id;break}}catch(c){console.error(`
40
+ \u2718 Login failed: ${c instanceof Error?c.message:c}`),process.exit(1)}}(!i||!u)&&(console.error("\n\u2718 Login timed out. Run `wyrm cloud login` again."),process.exit(1)),C({base:v,session:i,account_id:u,created_at:Date.now()});const y=new d(v,i);let p="";try{p=(await y.me()).account.email}catch{}const f=`${O()} (${process.platform})`;let m;try{m=(await y.registerDevice(f)).device.id;const h=g();h&&C({...h,email:p,device_id:m,device_name:f,machine_fp:I()})}catch(c){console.error(`
41
+ \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 ${p}`),console.log(`\u2713 Device registered: ${f}`),console.log(`\u2713 Session saved to ${$}`);const R=!S();if(U(),R?(console.log(`\u2713 Encryption key generated at ${a()} (0600)`),console.log(""),console.log(" \u26A0 Back up the key file. Losing it means losing access to your synced data."),console.log(" The Wyrm Cloud server CANNOT recover it \u2014 that's the operator-owns-data"),console.log(" guarantee. Run `wyrm cloud recovery show` now to get a 24-word phrase,"),console.log(" or copy the key file to a password manager / encrypted backup.")):console.log(`\u2713 Encryption key reused from ${a()}`),m)try{const c=await y.syncStatus();if(c.deltas>0){const T=(await y.syncPull(m,0,1)).deltas[0];if(T){let x=!0;try{j(T.payload_b64)}catch{x=!1}x||(console.log(""),console.log(" \u26A0 THIS DEVICE CANNOT READ THE ACCOUNT'S DATA YET"),console.log(` Login succeeded, but the key at ${a()} cannot decrypt`),console.log(` the ${c.deltas} delta(s) already on this account. They were encrypted`),console.log(" with a different master key (e.g. this is an org account whose other"),console.log(" devices hold the shared key). Until the keys match, sync will move"),console.log(" 0 rows in BOTH directions \u2014 a connected pipe that carries nothing."),console.log(""),console.log(" Fix: adopt the account key on this device \u2014"),console.log(" wyrm cloud recovery restore (needs the account's 24-word phrase)"),console.log(` Heads-up: restore OVERWRITES ${a()}. 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 d(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 d(e.base,e.session);try{const n=await r.me(),s=await r.syncStatus();console.log(""),console.log(`\u{F115D} Wyrm Cloud \u2014 ${n.account.email}`),console.log(""),console.log(` Tier: ${s.tier}`),console.log(` Storage: ${_(s.storage.used_bytes)} / ${_(s.storage.limit_bytes)} (${s.storage.percent}%)`),console.log(` Deltas: ${s.deltas}`),console.log(` Devices: ${s.devices}`),console.log(` Providers:${n.identities.map(t=>" "+t.provider).join(",")}`),console.log(""),console.log(` This machine: ${e.device_name??"(not registered)"}`),console.log(` Device ID: ${e.device_id?e.device_id.slice(0,8)+"\u2026":"(none)"}`),console.log(` Session age: ${Math.round((Date.now()-e.created_at)/(1440*60*1e3))} day(s)`),D(e),console.log(""),S()?s.deltas>0&&(console.log(` Key: ${a()} (0600)`),console.log(` \u26A0 Back this file up. Losing it = losing access to your ${s.deltas} deltas.`),console.log("")):(console.log(" \u26A0 ENCRYPTION KEY MISSING"),console.log(` Expected at: ${a()}`),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 D(e){const r=G(e.machine_fp);r.state==="match"?console.log(" Identity: \u2713 machine fingerprint matches (not a copied session)"):r.state==="adopt"?console.log(" Identity: \u2014 no fingerprint yet (pre-7.0.3 session; adopted on next sync)"):(console.log(" Identity: \u26A0 FINGERPRINT MISMATCH \u2014 this session looks COPIED"),console.log(` stored ${r.stored.slice(0,12)}\u2026 vs this machine ${r.current.slice(0,12)}\u2026`),console.log(" device_id collision \u2192 cross-device pull returns 0 silently."),console.log(" Fix: rm ~/.wyrm/cloud.json ~/.wyrm/cloud-cursor.json (KEEP cloud.key),"),console.log(" then `wyrm cloud login`. (Or `wyrm cloud sync --force` if intended.)"))}function oe(){const e=g();if(console.log(""),console.log("\u{F115D} Wyrm Cloud \u2014 device identity doctor"),console.log(""),!e){console.log(" Not logged in. Run `wyrm cloud login`."),console.log("");return}console.log(` Account: ${e.email??e.account_id.slice(0,8)+"\u2026"}`),console.log(` Device ID: ${e.device_id?e.device_id.slice(0,8)+"\u2026":"(none \u2014 re-run `wyrm cloud login`)"}`),console.log(` Server: ${e.base}`),console.log(` Stored fp: ${e.machine_fp?e.machine_fp.slice(0,12)+"\u2026":"(none \u2014 pre-7.0.3 session)"}`),console.log(` This box fp: ${I().slice(0,12)}\u2026`),D(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=d.fromSession();if(r==="revoke"){const l=e[1];l||(console.error("Usage: wyrm cloud devices revoke <device-id>"),process.exit(1)),await n.revokeDevice(l),console.log(`\u2713 Device ${l.slice(0,8)}\u2026 revoked.`);return}const{devices:s}=await n.listDevices(),o=g()?.device_id;console.log(""),console.log(`\u{F115D} Devices (${s.length})`),console.log("");for(const l of s){const i=l.id===o?"\u2605":" ",u=l.revoked?"(revoked)":"";console.log(` ${i} ${l.id.slice(0,8)}\u2026 ${l.name.padEnd(30)} last_seen=${E(l.last_seen_at)} ${u}`)}console.log(""),console.log(" \u2605 = this machine"),console.log(" Revoke: wyrm cloud devices revoke <device-id>"),console.log("")}async function ne(e){b();const r=e.includes("--dry-run"),n=e.includes("--all"),s=e.includes("--force");S()||(console.error("\u26A0 Encryption key not found at",a()),console.error(" A new key will be generated, but it will NOT decrypt deltas"),console.error(" pushed by other devices using a different key. If you have a backup,"),console.error(" restore it to ~/.wyrm/cloud.key (0600) before running sync."),console.error(" Continuing in 5 seconds \u2014 press Ctrl+C to abort."),await new Promise(o=>{setTimeout(o,5e3)})),n&&(console.log("\u{1F310} --all mode: syncing every semantic table (ignoring visibility flags)."),console.log(" Skipped: per-device logs, FTS shadows, sync internals."),console.log(""));const t=await H({dryRun:r,all:n,force:s});if(console.log(""),console.log("\u{F115D} Sync complete"),console.log(""),console.log(` Pushed: ${t.pushed} row(s)${r?" (dry run)":""}`),console.log(` Pulled: ${t.pulled} row(s)`),console.log(` Deleted: ${t.deleted_local} row(s) (peer tombstones)`),t.errors.length>0){console.log(""),console.log(" Errors:");for(const o of t.errors)console.log(` - ${o}`)}console.log("")}function te(){const e=M(W(F(import.meta.url)),"sync-daemon-entrypoint.js");return new K(e)}async function re(e){b();const r=e[0]??"status",n=e.find(o=>o.startsWith("--interval=")),s=n?Number(n.split("=")[1]):void 0,t=te();switch(r){case"start":{g()||(console.error("\u2718 Not logged in. Run `wyrm cloud login` first."),process.exit(1));const o=t.start({interval_minutes:s});o.ok||(console.error(`\u{F115D} sovereign-sync daemon: ${o.reason??"failed to start"}${o.pid?` (pid ${o.pid})`:""}`),process.exit(o.pid?0:1)),console.log(`\u{F115D} \u2713 sovereign-sync daemon started (pid ${o.pid}). Logs: ~/.wyrm/wyrm-sovereign-sync.log`);return}case"stop":{const o=t.stop();console.log(o.ok?"\u{F115D} \u2713 sovereign-sync daemon stopped":`\u{F115D} sovereign-sync daemon: ${o.reason??"failed to stop"}`);return}case"restart":{const o=q(s,t.status().state?.interval_ms);t.stop(),await new Promise(i=>{setTimeout(i,500)});const l=t.start({interval_minutes:o});console.log(l.ok?`\u{F115D} \u2713 sovereign-sync daemon restarted (pid ${l.pid}, every ${o} min)`:`\u{F115D} restart failed: ${l.reason??"unknown"}`);return}case"status":{const o=t.status();console.log("\u{F115D} Sovereign-sync daemon"),console.log(` Running: ${o.running?`\u2713 yes (pid ${o.pid})`:"\u2717 no"}`),o.state?.interval_ms&&console.log(` Interval: every ${o.state.interval_ms/6e4} min`),o.state?.started_ts&&console.log(` Started: ${E(o.state.started_ts)}`),o.state?.last_tick_ts&&(console.log(` Last tick: ${E(o.state.last_tick_ts)} (exit ${o.state.last_tick_exit??"\u2014"})`),o.state.last_tick_summary&&console.log(` Last result: ${o.state.last_tick_summary}`));return}default:console.error(`Unknown daemon action: ${r}. Use: start, stop, restart, status`),process.exit(1)}}async function ce(e){b();const r=d.fromSession(),n=e.find(l=>!l.startsWith("-")),s=L(n??`wyrm-cloud-export-${new Date().toISOString().slice(0,10)}.json`);console.log(" Fetching account export\u2026");const t=await r.accountExport(),o=JSON.stringify(t,null,2);P(s,o,{mode:384}),console.log(""),console.log(`\u2713 Exported to ${s}`),console.log(""),console.log(` Account: ${t.account.email} (${t.account.id.slice(0,8)}\u2026)`),console.log(` Devices: ${t.devices.length}`),console.log(` Identities: ${t.identities.length}`),console.log(` Orgs: ${t.orgs.length}`),console.log(` Deltas: ${t.deltas.length}`),console.log(` Size: ${_(o.length)}`),console.log(""),console.log(" Deltas are still AES-256-GCM ciphertext. To decrypt them you"),console.log(` need the key at ${a()}. Back that up alongside this file.`),console.log("")}async function le(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 d(n.base,n.session).accountDelete(),k(),console.log(""),console.log("\u2713 Account deleted on server."),console.log(`\u2713 Local session removed (${$}).`),console.log(` Kept: ${a()} (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,s=>{n.close(),r(s)})})}async function ie(e){const r=e[0];if(r==="show"){const{keyToMnemonic:n}=await import("./recovery.js"),t=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=t.slice(o*6,o*6+6).map((i,u)=>`${String(o*6+u+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 s=await N(" > "),{mnemonicToKey:t}=await import("./recovery.js"),{path:o,overwrote:l}=t(s,{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{ke as cmdCloud};
@@ -1 +1 @@
1
- import{spawn as g}from"child_process";import{existsSync as o,readFileSync as d,writeFileSync as p,mkdirSync as E,openSync as y,closeSync as S,unlinkSync as m,statSync as _,renameSync as v}from"fs";import{homedir as k}from"os";import{join as a}from"path";const n=a(k(),".wyrm"),c=a(n,"wyrm-sovereign-sync.pid"),t=a(n,"wyrm-sovereign-sync.log"),u=a(n,"wyrm-sovereign-sync.state"),I=1e6,L=0,P=600*1e3;class x{daemonEntrypoint;constructor(e){this.daemonEntrypoint=e,h()}start(e={}){if(this.isAlive())return{ok:!1,pid:this.readPid(),reason:"already running"};N();const i=(e.interval_minutes??P/6e4)*6e4,f=y(t,"a"),l=y(t,"a"),s=g(process.execPath,[this.daemonEntrypoint,String(i)],{detached:!0,stdio:["ignore",f,l],env:{...process.env}});return s.unref(),S(f),S(l),typeof s.pid!="number"?{ok:!1,reason:"spawn returned no pid"}:(p(c,String(s.pid)),w({started_ts:Date.now()}),{ok:!0,pid:s.pid})}stop(){if(!this.isAlive())return this.cleanPid(),{ok:!1,reason:"not running"};const e=this.readPid();if(!e)return{ok:!1,reason:"pid file unreadable"};try{return process.kill(e,"SIGTERM"),{ok:!0}}catch(i){return{ok:!1,reason:i?.message??String(i)}}}status(){const e=this.isAlive();return{running:e,pid:e?this.readPid():void 0,state:o(u)?A():void 0}}isAlive(){if(!o(c))return!1;const e=this.readPid();if(!e)return!1;try{return process.kill(e,L),!0}catch{return!1}}readPid(){try{const e=Number(d(c,"utf8").trim());return Number.isFinite(e)&&e>0?e:void 0}catch{return}}cleanPid(){try{m(c)}catch{}}}function w(r){h();let e={};try{e=JSON.parse(d(u,"utf8"))}catch{}p(u,JSON.stringify({...e,...r},null,2))}function h(){o(n)||E(n,{recursive:!0})}function A(){try{return JSON.parse(d(u,"utf8"))}catch{return}}function N(){if(o(t))try{if(_(t).size>I){const r=`${t}.1`;try{m(r)}catch{}v(t,r)}}catch{}}export{P as DEFAULT_SOVEREIGN_INTERVAL_MS,x as SovereignSyncManager,w as writeSovereignState};
1
+ import{spawn as g}from"child_process";import{existsSync as o,readFileSync as d,writeFileSync as y,mkdirSync as E,openSync as S,closeSync as p,unlinkSync as m,statSync as v,renameSync as I}from"fs";import{homedir as k}from"os";import{join as a}from"path";const i=a(k(),".wyrm"),c=a(i,"wyrm-sovereign-sync.pid"),r=a(i,"wyrm-sovereign-sync.log"),u=a(i,"wyrm-sovereign-sync.state"),L=1e6,P=0,h=600*1e3;class D{daemonEntrypoint;constructor(t){this.daemonEntrypoint=t,_()}start(t={}){if(this.isAlive())return{ok:!1,pid:this.readPid(),reason:"already running"};N();const n=(t.interval_minutes??h/6e4)*6e4,f=S(r,"a"),l=S(r,"a"),s=g(process.execPath,[this.daemonEntrypoint,String(n)],{detached:!0,stdio:["ignore",f,l],env:{...process.env}});return s.unref(),p(f),p(l),typeof s.pid!="number"?{ok:!1,reason:"spawn returned no pid"}:(y(c,String(s.pid)),w({started_ts:Date.now(),interval_ms:n}),{ok:!0,pid:s.pid})}stop(){if(!this.isAlive())return this.cleanPid(),{ok:!1,reason:"not running"};const t=this.readPid();if(!t)return{ok:!1,reason:"pid file unreadable"};try{return process.kill(t,"SIGTERM"),{ok:!0}}catch(n){return{ok:!1,reason:n?.message??String(n)}}}status(){const t=this.isAlive();return{running:t,pid:t?this.readPid():void 0,state:o(u)?A():void 0}}isAlive(){if(!o(c))return!1;const t=this.readPid();if(!t)return!1;try{return process.kill(t,P),!0}catch{return!1}}readPid(){try{const t=Number(d(c,"utf8").trim());return Number.isFinite(t)&&t>0?t:void 0}catch{return}}cleanPid(){try{m(c)}catch{}}}function G(e,t){return e&&e>0?e:t&&t>0?t/6e4:h/6e4}function w(e){_();let t={};try{t=JSON.parse(d(u,"utf8"))}catch{}y(u,JSON.stringify({...t,...e},null,2))}function _(){o(i)||E(i,{recursive:!0})}function A(){try{return JSON.parse(d(u,"utf8"))}catch{return}}function N(){if(o(r))try{if(v(r).size>L){const e=`${r}.1`;try{m(e)}catch{}I(r,e)}}catch{}}export{h as DEFAULT_SOVEREIGN_INTERVAL_MS,D as SovereignSyncManager,G as restartIntervalMinutes,w as writeSovereignState};
package/dist/http-auth.js CHANGED
@@ -1,3 +1,3 @@
1
1
  import{existsSync as w,readFileSync as I,writeFileSync as u,mkdirSync as R}from"fs";import{join as y}from"path";import{homedir as x}from"os";import{randomBytes as O,createHash as T}from"crypto";import{WyrmLogger as k}from"./logger.js";const g=y(x(),".wyrm"),a=y(g,"http-config.json"),L={enabled:!0,requests:100,windowMs:6e4},S=["http://localhost:3333","http://127.0.0.1:3333"],d=new Map;let c=null;const i=new k;function m(e){return T("sha256").update(e).digest("hex")}function P(e,t){if(e.length!==t.length)return!1;let r=0;for(let n=0;n<e.length;n++)r|=e.charCodeAt(n)^t.charCodeAt(n);return r===0}function M(){if(w(g)||R(g,{recursive:!0,mode:448}),w(a))try{const r=JSON.parse(I(a,"utf-8"));return{apiKeyHash:r.apiKeyHash,allowedOrigins:r.allowedOrigins||S,rateLimit:{...L,...r.rateLimit},requireAuth:r.requireAuth??!0,devMode:process.env.WYRM_DEV==="true"}}catch(r){i.error("Failed to load HTTP config, regenerating",{error:r.message})}const e=O(32).toString("hex");i.info("Generated new API key for HTTP server"),console.log(`
2
2
  `+"\u2550".repeat(60)),console.log("\u{1F510} WYRM API KEY (save this securely, shown once):"),console.log(""),console.log(` ${e}`),console.log(""),console.log(" Use with: Authorization: Bearer <key>"),console.log("\u2550".repeat(60)+`
3
- `);const t={apiKeyHash:m(e),allowedOrigins:S,rateLimit:L,requireAuth:!0,devMode:process.env.WYRM_DEV==="true"};return u(a,JSON.stringify(t,null,2),{mode:384}),t}function s(){return c||(c=M()),c}function N(e){const t=s();if(t.devMode){const h=e.socket.remoteAddress;if(h==="127.0.0.1"||h==="::1"||h==="::ffff:127.0.0.1")return!0}const r=e.socket.remoteAddress??"",n=r==="127.0.0.1"||r==="::1"||r==="::ffff:127.0.0.1",o=(e.headers.host??"").split(":")[0].toLowerCase();if(n&&(o==="localhost"||o==="127.0.0.1"||o==="::1"||o==="[::1]")&&e.headers["x-wyrm-origin"]==="ui")return!0;if(!t.requireAuth)return i.warn("Authentication disabled - API is open"),!0;const l=e.headers.authorization;if(!l||!l.startsWith("Bearer "))return!1;const f=l.slice(7);if(!f||f.length<32)return!1;const C=m(f);return P(C,t.apiKeyHash)}function H(e){const t=s();if(!t.rateLimit.enabled)return{allowed:!0,remaining:1/0,resetAt:0};const r=e.socket.remoteAddress||"unknown",n=Date.now();if(d.size>1e4)for(const[A,l]of d)l.resetAt<n&&d.delete(A);let o=d.get(r);return(!o||o.resetAt<n)&&(o={count:0,resetAt:n+t.rateLimit.windowMs},d.set(r,o)),o.count++,{allowed:o.count<=t.rateLimit.requests,remaining:Math.max(0,t.rateLimit.requests-o.count),resetAt:o.resetAt}}function v(e){const t=s(),r=e.headers.origin;return r&&t.allowedOrigins.includes(r)?r:t.allowedOrigins[0]}function p(e){return{"Access-Control-Allow-Origin":v(e),"Access-Control-Allow-Methods":"GET, POST, OPTIONS","Access-Control-Allow-Headers":"Content-Type, Authorization","X-Content-Type-Options":"nosniff","X-Frame-Options":"DENY","Cache-Control":"no-store","Content-Security-Policy":"default-src 'none'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; connect-src 'self'; img-src 'self' data:; frame-ancestors 'none'","Referrer-Policy":"no-referrer","Permissions-Policy":"geolocation=(), microphone=(), camera=()","X-Robots-Tag":"noindex, nofollow"}}const D=["/health","/auth/status","/ui"];function J(e,t){if(e.method==="OPTIONS"){const o=p(e);return t.writeHead(204,o),t.end(),{error:!0}}const r=H(e);if(!r.allowed){const o=p(e);return t.writeHead(429,{...o,"X-RateLimit-Remaining":"0","X-RateLimit-Reset":String(r.resetAt),"Retry-After":String(Math.ceil((r.resetAt-Date.now())/1e3))}),t.end(JSON.stringify({error:"Rate limit exceeded"})),i.warn("Rate limit exceeded",{ip:e.socket.remoteAddress,path:e.url}),{error:!0}}const n=new URL(e.url||"/","http://localhost").pathname;if(D.includes(n))return{error:!1};if(!N(e)){const o=p(e);return t.writeHead(401,{...o,"WWW-Authenticate":'Bearer realm="Wyrm API"'}),t.end(JSON.stringify({error:"Unauthorized"})),i.warn("Authentication failed",{ip:e.socket.remoteAddress,path:e.url}),{error:!0}}return{error:!1}}function _(){const e=O(32).toString("hex"),t=s();return t.apiKeyHash=m(e),u(a,JSON.stringify(t,null,2),{mode:384}),c=t,i.info("API key regenerated"),e}function U(e){const t=s();t.allowedOrigins=e,u(a,JSON.stringify(t,null,2),{mode:384}),c=t,i.info("CORS origins updated",{origins:e})}function z(e,t){const r=s();r.rateLimit={enabled:!0,requests:e,windowMs:t},u(a,JSON.stringify(r,null,2),{mode:384}),c=r,i.info("Rate limit updated",{requests:e,windowMs:t})}function B(e){const t=s();t.requireAuth=e,u(a,JSON.stringify(t,null,2),{mode:384}),c=t,e||i.warn("Authentication disabled - API is now open")}function G(){const e=s();e.devMode=!0}function X(){const e=s();return{requireAuth:e.requireAuth,devMode:e.devMode,rateLimit:e.rateLimit,allowedOrigins:e.allowedOrigins}}export{J as authMiddleware,N as authenticate,H as checkRateLimit,G as enableDevMode,X as getAuthStatus,v as getCorsOrigin,p as getSecurityHeaders,_ as regenerateApiKey,U as setAllowedOrigins,z as setRateLimit,B as setRequireAuth};
3
+ `);const t={apiKeyHash:m(e),allowedOrigins:S,rateLimit:L,requireAuth:!0,devMode:process.env.WYRM_DEV==="true"};return u(a,JSON.stringify(t,null,2),{mode:384}),t}function s(){return c||(c=M()),c}function N(e){const t=s();if(t.devMode){const h=e.socket.remoteAddress;if(h==="127.0.0.1"||h==="::1"||h==="::ffff:127.0.0.1")return!0}const r=e.socket.remoteAddress??"",n=r==="127.0.0.1"||r==="::1"||r==="::ffff:127.0.0.1",o=(e.headers.host??"").split(":")[0].toLowerCase();if(n&&(o==="localhost"||o==="127.0.0.1"||o==="::1"||o==="[::1]")&&e.headers["x-wyrm-origin"]==="ui")return!0;if(!t.requireAuth)return i.warn("Authentication disabled - API is open"),!0;const l=e.headers.authorization;if(!l||!l.startsWith("Bearer "))return!1;const f=l.slice(7);if(!f||f.length<32)return!1;const C=m(f);return P(C,t.apiKeyHash)}function H(e){const t=s();if(!t.rateLimit.enabled)return{allowed:!0,remaining:1/0,resetAt:0};const r=e.socket.remoteAddress||"unknown",n=Date.now();if(d.size>1e4)for(const[A,l]of d)l.resetAt<n&&d.delete(A);let o=d.get(r);return(!o||o.resetAt<n)&&(o={count:0,resetAt:n+t.rateLimit.windowMs},d.set(r,o)),o.count++,{allowed:o.count<=t.rateLimit.requests,remaining:Math.max(0,t.rateLimit.requests-o.count),resetAt:o.resetAt}}function v(e){const t=s(),r=e.headers.origin;return r&&t.allowedOrigins.includes(r)?r:t.allowedOrigins[0]}function p(e){return{"Access-Control-Allow-Origin":v(e),"Access-Control-Allow-Methods":"GET, POST, OPTIONS","Access-Control-Allow-Headers":"Content-Type, Authorization","X-Content-Type-Options":"nosniff","X-Frame-Options":"DENY","Cache-Control":"no-store","Content-Security-Policy":"default-src 'none'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src https://fonts.gstatic.com; connect-src 'self'; img-src 'self' data:; frame-ancestors 'none'","Referrer-Policy":"no-referrer","Permissions-Policy":"geolocation=(), microphone=(), camera=()","X-Robots-Tag":"noindex, nofollow"}}const D=["/health","/auth/status","/ui"];function J(e,t){if(e.method==="OPTIONS"){const o=p(e);return t.writeHead(204,o),t.end(),{error:!0}}const r=H(e);if(!r.allowed){const o=p(e);return t.writeHead(429,{...o,"X-RateLimit-Remaining":"0","X-RateLimit-Reset":String(r.resetAt),"Retry-After":String(Math.ceil((r.resetAt-Date.now())/1e3))}),t.end(JSON.stringify({error:"Rate limit exceeded"})),i.warn("Rate limit exceeded",{ip:e.socket.remoteAddress,path:e.url}),{error:!0}}const n=new URL(e.url||"/","http://localhost").pathname;if(D.includes(n))return{error:!1};if(!N(e)){const o=p(e);return t.writeHead(401,{...o,"WWW-Authenticate":'Bearer realm="Wyrm API"'}),t.end(JSON.stringify({error:"Unauthorized"})),i.warn("Authentication failed",{ip:e.socket.remoteAddress,path:e.url}),{error:!0}}return{error:!1}}function _(){const e=O(32).toString("hex"),t=s();return t.apiKeyHash=m(e),u(a,JSON.stringify(t,null,2),{mode:384}),c=t,i.info("API key regenerated"),e}function U(e){const t=s();t.allowedOrigins=e,u(a,JSON.stringify(t,null,2),{mode:384}),c=t,i.info("CORS origins updated",{origins:e})}function z(e,t){const r=s();r.rateLimit={enabled:!0,requests:e,windowMs:t},u(a,JSON.stringify(r,null,2),{mode:384}),c=r,i.info("Rate limit updated",{requests:e,windowMs:t})}function B(e){const t=s();t.requireAuth=e,u(a,JSON.stringify(t,null,2),{mode:384}),c=t,e||i.warn("Authentication disabled - API is now open")}function G(){const e=s();e.devMode=!0}function X(){const e=s();return{requireAuth:e.requireAuth,devMode:e.devMode,rateLimit:e.rateLimit,allowedOrigins:e.allowedOrigins}}export{J as authMiddleware,N as authenticate,H as checkRateLimit,G as enableDevMode,X as getAuthStatus,v as getCorsOrigin,p as getSecurityHeaders,_ as regenerateApiKey,U as setAllowedOrigins,z as setRateLimit,B as setRequireAuth};
package/dist/http-fast.js CHANGED
@@ -1,31 +1,31 @@
1
1
  #!/usr/bin/env node
2
- import V from"http";import{WyrmDB as H}from"./database.js";import{cache as h,estimateTokens as z,truncateToTokens as Z,timed as G}from"./performance.js";import{authMiddleware as ee,getAuthStatus as N,getSecurityHeaders as L}from"./http-auth.js";import{WyrmLogger as te}from"./logger.js";import{sanitizeFtsQuery as W,validateProjectPath as re}from"./security.js";import{readFileSync as R,existsSync as S,readdirSync as ne,realpathSync as x}from"fs";import{homedir as se}from"os";import{dirname as U,join as _}from"path";import{fileURLToPath as oe}from"url";const q=_(U(oe(import.meta.url)),"..","ui","dragon-mark.svg");let v=null;import{getUIDashboardHTML as ae}from"./ui-dashboard.js";import{verifyLicense as B}from"./license.js";import{rerankConfigured as ce,rerankEgressHost as ie}from"./rerank.js";import{MemoryArtifacts as le}from"./memory-artifacts.js";import{createVectorStore as ue}from"./vectors.js";import{GroundTruths as de,computeStaleness as me}from"./intelligence.js";import{Causality as pe}from"./causality.js";import{wireTruthAutoCascade as fe}from"./truth-cascade.js";import{FailurePatterns as Ee}from"./failure-patterns.js";import{handleDaemonWrite as he}from"./daemon-write-endpoint.js";import{retryableWriteCause as Y,busyErrorBody as $}from"./sqlite-busy.js";import{sseFrame as ye,resolveStartCursor as J,SSE_KEEPALIVE as _e}from"./events-sse.js";import{readonlyBlocks as Se}from"./readonly-gate.js";import{resolveActorEnvelope as Te,runWithActor as ge}from"./handlers/boundary.js";const j=parseInt(process.env.WYRM_PORT||"3333")||3333,T=process.env.WYRM_UI_READONLY==="1",Oe=512*1024,M=500,Re=parseInt(process.env.WYRM_MAX_SSE_STREAMS||"64",10)||64,ve=parseInt(process.env.WYRM_MAX_SSE_PER_IP||"8",10)||8;let P=0;const g=new Map;let O=null,b=null,C=null,k=null;function o(){return O||(O=new H(process.env.WYRM_DB_PATH)),O}function I(){if(!b){b=new le(o().getDatabase());try{const e=process.env.WYRM_VECTOR_PROVIDER||"auto";e!=="none"&&b.setVectorStore(ue({provider:e},o().getDatabase()))}catch{}}return b}function be(){return C||(C=new de(o().getDatabase()),fe(C,new pe(o().getDatabase()))),C}function Ce(){return k||(k=new Ee(o().getDatabase())),k}function Le(e){const t=O;O=e,b=null,C=null,k=null;try{t?.close()}catch{}}function Q(){const e=[],t=se(),r=new Set;let n=[];try{n=ne(t)}catch{return e}n.sort((s,a)=>a.localeCompare(s));for(const s of n){if(s!==".wyrm"&&!s.startsWith(".wyrm-"))continue;let a;try{a=x(_(t,s))}catch{continue}if(r.has(a))continue;const i=_(a,"wyrm.db");if(!S(i))continue;r.add(a);let c="Local",l="free";try{const p=_(a,"license.json");if(S(p)){const d=JSON.parse(R(p,"utf-8")),m=B(d);c=d.license&&d.license.issued_to||c,l=m.valid?m.tier:"free"}else{const d=_(a,"cloud.json");S(d)&&(c=JSON.parse(R(d,"utf-8")).email||c)}}catch{}const u=s.replace(/^\.wyrm-?/,"")||"default";e.push({name:u,dbPath:i,account:c,tier:l})}return e}const D=new te({level:"info"});function y(e,t,r){const n=Number.parseInt(String(e??""),10);return!Number.isFinite(n)||n<1?t:Math.min(n,r)}function A(e){const t=e==null?"":String(e);if(t)return o().getProject(t)||o().getProjectByName(t)}function E(e,t,r,n=200){const s=JSON.stringify(r),a=L(t);e.writeHead(n,{"Content-Type":"application/json","Content-Length":Buffer.byteLength(s),"X-Tokens":String(z(r)),...a}),e.end(s)}function je(e,t,r){const n=L(t);e.writeHead(200,{...n,"Content-Type":"text/html; charset=utf-8","Content-Length":Buffer.byteLength(r),"Cache-Control":"no-store"}),e.end(r)}function Me(e){return new Promise((t,r)=>{if(e.method==="GET"){t({});return}let n="",s=0;e.on("data",a=>{if(s+=a.length,s>Oe){e.destroy(),r(new Error("Request body too large"));return}n+=a}),e.on("end",()=>{try{t(n?JSON.parse(n):{})}catch{r(new Error("Invalid JSON"))}}),e.on("error",r)})}const X={"GET /c":e=>{const t=`ctx:${e.p||"global"}`,r=h.get(t);if(r)return r;const n=e.p;let s;if(n){const a=o().getProject(n);if(!a)return{e:"not found"};const i=o().getPendingQuests(a.id),c=o().getRecentSessions(a.id,3);s={n:a.name,s:a.stack,q:i.length,qt:i.slice(0,5).map(l=>l.title),r:c[0]?.summary||c[0]?.notes?.slice(0,200)||null}}else{const a=o().getAllProjects(20),i=o().getAllPendingQuests();s={p:a.map(c=>({n:c.name,s:c.stack,q:o().getPendingQuests(c.id).length})),tq:i.length}}return h.set(t,s),s},"GET /p":()=>{const e=h.get("projects");if(e)return e;const r=o().getAllProjects(50).map(n=>({i:n.id,n:n.name,p:n.path,s:n.stack}));return h.set("projects",r),r},"POST /scan":e=>{const t=e.d||e.directory||process.env.HOME+"/Git Projects",r=re(t);return r?(h.invalidate(),{found:o().scanForProjects(r,!0).length}):{e:"path not allowed"}},"GET /q":e=>{const t=e.p;if(t){const r=o().getProject(t);return r?o().getPendingQuests(r.id).map(n=>({i:n.id,t:n.title,p:n.priority[0]})):{e:"not found"}}return o().getAllPendingQuests().slice(0,20).map(r=>({i:r.id,t:r.title,p:r.priority[0]}))},"POST /q":e=>{const t=e.p,r=o().getProject(t);return r?(h.invalidate("ctx"),{i:o().addQuest(r.id,e.t,e.d,e.pr||"medium").id}):{e:"not found"}},"POST /qc":e=>{const t=typeof e.i=="number"?e.i:parseInt(String(e.i),10);return!t||t<1?{e:"id required"}:(h.invalidate("ctx"),o().updateQuest(t,"completed"),{ok:1})},"POST /s":e=>{const t=e.p;let r=o().getProject(t);if(r||(o().scanForProjects(t,!1),r=o().getProject(t)),!r)return{e:"not found"};let n=o().getTodaySession(r.id);return n||(n=o().createSession(r.id,{objectives:e.o||"",notes:""})),h.invalidate("ctx"),{i:n.id,d:n.date}},"POST /su":e=>{const t=e.i;return h.invalidate("ctx"),o().updateSession(t,{notes:e.n,summary:e.s,completed:e.c}),{ok:1}},"POST /x":e=>{const t=e.p,r=o().getProject(t);return r?(o().setContext(r.id,e.k,e.v),h.invalidate("ctx"),{ok:1}):{e:"not found"}},"GET /x":e=>{const t=e.p,r=o().getProject(t);return r?o().getAllContext(r.id):{e:"not found"}},"POST /g":e=>{const t=e.k,r=e.v;return!t||typeof t!="string"?{e:"k (key) required"}:r==null?{e:"v (value) required"}:(o().setGlobalContext(t,String(r)),h.invalidate(),{ok:1})},"GET /g":()=>o().getAllGlobalContext(),"GET /s":e=>{const t=e.q;if(!t)return{e:"query required"};const r=W(t);return{q:o().searchQuests(r).slice(0,5).map(s=>({i:s.id,t:s.title})),s:o().searchSessions(r).slice(0,3).map(s=>({i:s.id,d:s.date})),p:o().searchProjects(r).slice(0,3).map(s=>({n:s.name,p:s.path}))}},"POST /d":e=>{const t=e.p,r=o().getProject(t);return r?{i:o().insertData(r.id,e.c,e.k,e.v,e.m).id}:{e:"not found"}},"GET /d":e=>{const t=e.p,r=o().getProject(t);if(!r)return{e:"not found"};const n=y(e.l,20,100);return o().queryData(r.id,e.c,n).map(s=>({k:s.key,v:Z(s.value,100)}))},"GET /d/search":e=>{const t=e.p,r=o().getProject(t);if(!r)return{e:"not found"};const n=e.q;if(!n)return{e:"q (query) required"};const s=e.c,a=y(e.l,10,100);let i=o().searchData(n,r.id);return s&&(i=i.filter(c=>c.category===s)),i.slice(0,a).map(c=>({k:c.key,v:c.value,c:c.category,m:c.metadata}))},"GET /d/agg":e=>{const t=e.p,r=o().getProject(t);if(!r)return{e:"not found"};const n=e.c;if(!n)return{e:"c (category) required"};const s=e.stack_key,a=e.scanner,i=y(e.l,1e3,1e4),c={};for(const l of o().queryData(r.id,n,i)){let u;try{u=JSON.parse(l.value)}catch{continue}if(s&&u.stack_key!==s||a&&u.scanner!==a)continue;const p=String(u.scanner??""),d=String(u.payload_hash??"");if(!p||!d)continue;const m=`${p}:${d}`,f=c[m]??{s:p,ph:d,sk:String(u.stack_key??""),h:0,t:0};f.t+=1,u.hit&&(f.h+=1),c[m]=f}return Object.values(c).map(l=>({...l,r:l.t>0?l.h/l.t:0})).sort((l,u)=>u.r-l.r||u.h-l.h)},"POST /batch":e=>{const t=e.ops;if(!t||!Array.isArray(t))return{e:"ops required"};if(t.length>M)return{e:`max ${M} ops per batch`};const r=performance.now();return{r:t.map(s=>{const a=`${s.m} ${s.r}`,i=X[a];if(!i)return{e:"unknown"};try{return{d:i(s.a||{})}}catch{return{e:"op failed"}}}),ms:Math.round(performance.now()-r)}},"POST /write":e=>he({db:o(),artifacts:I(),truths:be(),failures:Ce()},e),"GET /syms":e=>{const t=String(e.q??"").slice(0,200);if(!t)return[];const r=y(e.limit,20,200),n=o().getDatabase();try{return n.prepare(`
2
+ import z from"http";import{WyrmDB as W}from"./database.js";import{cache as h,estimateTokens as Z,truncateToTokens as ee,timed as G}from"./performance.js";import{authMiddleware as te,getAuthStatus as k,getSecurityHeaders as b}from"./http-auth.js";import{WyrmLogger as re}from"./logger.js";import{sanitizeFtsQuery as U,validateProjectPath as ne}from"./security.js";import{readFileSync as R,existsSync as y,readdirSync as se,realpathSync as x}from"fs";import{homedir as oe}from"os";import{dirname as q,join as S}from"path";import{fileURLToPath as ae}from"url";const B=S(q(ae(import.meta.url)),"..","ui","dragon-mark.svg");let v=null;import{getUIDashboardHTML as ie}from"./ui-dashboard.js";import{resolveBranding as I}from"./branding.js";import{verifyLicense as $}from"./license.js";import{rerankConfigured as ce,rerankEgressHost as le}from"./rerank.js";import{MemoryArtifacts as de}from"./memory-artifacts.js";import{createVectorStore as ue}from"./vectors.js";import{GroundTruths as pe,computeStaleness as me}from"./intelligence.js";import{Causality as fe}from"./causality.js";import{wireTruthAutoCascade as Ee}from"./truth-cascade.js";import{FailurePatterns as he}from"./failure-patterns.js";import{handleDaemonWrite as _e}from"./daemon-write-endpoint.js";import{retryableWriteCause as Y,busyErrorBody as J}from"./sqlite-busy.js";import{sseFrame as Se,resolveStartCursor as K,SSE_KEEPALIVE as ye}from"./events-sse.js";import{readonlyBlocks as ge}from"./readonly-gate.js";import{resolveActorEnvelope as Te,runWithActor as Oe}from"./handlers/boundary.js";const j=parseInt(process.env.WYRM_PORT||"3333")||3333,g=process.env.WYRM_UI_READONLY==="1",Re=512*1024,M=500,ve=parseInt(process.env.WYRM_MAX_SSE_STREAMS||"64",10)||64,Ce=parseInt(process.env.WYRM_MAX_SSE_PER_IP||"8",10)||8;let w=0;const T=new Map;let O=null,C=null,L=null,N=null;function a(){return O||(O=new W(process.env.WYRM_DB_PATH)),O}function A(){if(!C){C=new de(a().getDatabase());try{const e=process.env.WYRM_VECTOR_PROVIDER||"auto";e!=="none"&&C.setVectorStore(ue({provider:e},a().getDatabase()))}catch{}}return C}function Le(){return L||(L=new pe(a().getDatabase()),Ee(L,new fe(a().getDatabase()))),L}function be(){return N||(N=new he(a().getDatabase())),N}function je(e){const t=O;O=e,C=null,L=null,N=null;try{t?.close()}catch{}}function Q(){const e=[],t=oe(),r=new Set;let n=[];try{n=se(t)}catch{return e}n.sort((s,o)=>o.localeCompare(s));for(const s of n){if(s!==".wyrm"&&!s.startsWith(".wyrm-"))continue;let o;try{o=x(S(t,s))}catch{continue}if(r.has(o))continue;const d=S(o,"wyrm.db");if(!y(d))continue;r.add(o);let i="Local",l="free";try{const c=S(o,"license.json");if(y(c)){const u=JSON.parse(R(c,"utf-8")),m=$(u);i=u.license&&u.license.issued_to||i,l=m.valid?m.tier:"free"}else{const u=S(o,"cloud.json");y(u)&&(i=JSON.parse(R(u,"utf-8")).email||i)}}catch{}const p=s.replace(/^\.wyrm-?/,"")||"default";e.push({name:p,dbPath:d,account:i,tier:l})}return e}const D=new re({level:"info"});function _(e,t,r){const n=Number.parseInt(String(e??""),10);return!Number.isFinite(n)||n<1?t:Math.min(n,r)}function P(e){const t=e==null?"":String(e);if(t)return a().getProject(t)||a().getProjectByName(t)}function E(e,t,r,n=200){const s=JSON.stringify(r),o=b(t);e.writeHead(n,{"Content-Type":"application/json","Content-Length":Buffer.byteLength(s),"X-Tokens":String(Z(r)),...o}),e.end(s)}function Me(e,t,r){const n=b(t);e.writeHead(200,{...n,"Content-Type":"text/html; charset=utf-8","Content-Length":Buffer.byteLength(r),"Cache-Control":"no-store"}),e.end(r)}function Ne(e){return new Promise((t,r)=>{if(e.method==="GET"){t({});return}let n="",s=0;e.on("data",o=>{if(s+=o.length,s>Re){e.destroy(),r(new Error("Request body too large"));return}n+=o}),e.on("end",()=>{try{t(n?JSON.parse(n):{})}catch{r(new Error("Invalid JSON"))}}),e.on("error",r)})}const X={"GET /c":e=>{const t=`ctx:${e.p||"global"}`,r=h.get(t);if(r)return r;const n=e.p;let s;if(n){const o=a().getProject(n);if(!o)return{e:"not found"};const d=a().getPendingQuests(o.id),i=a().getRecentSessions(o.id,3);s={n:o.name,s:o.stack,q:d.length,qt:d.slice(0,5).map(l=>l.title),r:i[0]?.summary||i[0]?.notes?.slice(0,200)||null}}else{const o=a().getAllProjects(20),d=a().getAllPendingQuests();s={p:o.map(i=>({n:i.name,s:i.stack,q:a().getPendingQuests(i.id).length})),tq:d.length}}return h.set(t,s),s},"GET /p":()=>{const e=h.get("projects");if(e)return e;const r=a().getAllProjects(50).map(n=>({i:n.id,n:n.name,p:n.path,s:n.stack}));return h.set("projects",r),r},"POST /scan":e=>{const t=e.d||e.directory||process.env.HOME+"/Git Projects",r=ne(t);return r?(h.invalidate(),{found:a().scanForProjects(r,!0).length}):{e:"path not allowed"}},"GET /q":e=>{const t=e.p;if(t){const r=a().getProject(t);return r?a().getPendingQuests(r.id).map(n=>({i:n.id,t:n.title,p:n.priority[0]})):{e:"not found"}}return a().getAllPendingQuests().slice(0,20).map(r=>({i:r.id,t:r.title,p:r.priority[0]}))},"POST /q":e=>{const t=e.p,r=a().getProject(t);return r?(h.invalidate("ctx"),{i:a().addQuest(r.id,e.t,e.d,e.pr||"medium").id}):{e:"not found"}},"POST /qc":e=>{const t=typeof e.i=="number"?e.i:parseInt(String(e.i),10);return!t||t<1?{e:"id required"}:(h.invalidate("ctx"),a().updateQuest(t,"completed"),{ok:1})},"POST /s":e=>{const t=e.p;let r=a().getProject(t);if(r||(a().scanForProjects(t,!1),r=a().getProject(t)),!r)return{e:"not found"};let n=a().getTodaySession(r.id);return n||(n=a().createSession(r.id,{objectives:e.o||"",notes:""})),h.invalidate("ctx"),{i:n.id,d:n.date}},"POST /su":e=>{const t=e.i;return h.invalidate("ctx"),a().updateSession(t,{notes:e.n,summary:e.s,completed:e.c}),{ok:1}},"POST /x":e=>{const t=e.p,r=a().getProject(t);return r?(a().setContext(r.id,e.k,e.v),h.invalidate("ctx"),{ok:1}):{e:"not found"}},"GET /x":e=>{const t=e.p,r=a().getProject(t);return r?a().getAllContext(r.id):{e:"not found"}},"POST /g":e=>{const t=e.k,r=e.v;return!t||typeof t!="string"?{e:"k (key) required"}:r==null?{e:"v (value) required"}:(a().setGlobalContext(t,String(r)),h.invalidate(),{ok:1})},"GET /g":()=>a().getAllGlobalContext(),"GET /s":e=>{const t=e.q;if(!t)return{e:"query required"};const r=U(t);return{q:a().searchQuests(r).slice(0,5).map(s=>({i:s.id,t:s.title})),s:a().searchSessions(r).slice(0,3).map(s=>({i:s.id,d:s.date})),p:a().searchProjects(r).slice(0,3).map(s=>({n:s.name,p:s.path}))}},"POST /d":e=>{const t=e.p,r=a().getProject(t);return r?{i:a().insertData(r.id,e.c,e.k,e.v,e.m).id}:{e:"not found"}},"GET /d":e=>{const t=e.p,r=a().getProject(t);if(!r)return{e:"not found"};const n=_(e.l,20,100);return a().queryData(r.id,e.c,n).map(s=>({k:s.key,v:ee(s.value,100)}))},"GET /d/search":e=>{const t=e.p,r=a().getProject(t);if(!r)return{e:"not found"};const n=e.q;if(!n)return{e:"q (query) required"};const s=e.c,o=_(e.l,10,100);let d=a().searchData(n,r.id);return s&&(d=d.filter(i=>i.category===s)),d.slice(0,o).map(i=>({k:i.key,v:i.value,c:i.category,m:i.metadata}))},"GET /d/agg":e=>{const t=e.p,r=a().getProject(t);if(!r)return{e:"not found"};const n=e.c;if(!n)return{e:"c (category) required"};const s=e.stack_key,o=e.scanner,d=_(e.l,1e3,1e4),i={};for(const l of a().queryData(r.id,n,d)){let p;try{p=JSON.parse(l.value)}catch{continue}if(s&&p.stack_key!==s||o&&p.scanner!==o)continue;const c=String(p.scanner??""),u=String(p.payload_hash??"");if(!c||!u)continue;const m=`${c}:${u}`,f=i[m]??{s:c,ph:u,sk:String(p.stack_key??""),h:0,t:0};f.t+=1,p.hit&&(f.h+=1),i[m]=f}return Object.values(i).map(l=>({...l,r:l.t>0?l.h/l.t:0})).sort((l,p)=>p.r-l.r||p.h-l.h)},"POST /batch":e=>{const t=e.ops;if(!t||!Array.isArray(t))return{e:"ops required"};if(t.length>M)return{e:`max ${M} ops per batch`};const r=performance.now();return{r:t.map(s=>{const o=`${s.m} ${s.r}`,d=X[o];if(!d)return{e:"unknown"};try{return{d:d(s.a||{})}}catch{return{e:"op failed"}}}),ms:Math.round(performance.now()-r)}},"POST /write":e=>_e({db:a(),artifacts:A(),truths:Le(),failures:be()},e),"GET /syms":e=>{const t=String(e.q??"").slice(0,200);if(!t)return[];const r=_(e.limit,20,200),n=a().getDatabase();try{return n.prepare(`
3
3
  SELECT symbol, kind, language, file_path, line, signature, project_id
4
4
  FROM symbol_index
5
5
  WHERE symbol LIKE ?
6
6
  ORDER BY project_id, file_path, line
7
7
  LIMIT ?
8
- `).all(`%${t}%`,r)}catch{return[]}},"GET /failures":e=>{const t=String(e.target??"").slice(0,200),r=y(e.limit,10,100),n=o().getDatabase();try{return n.prepare(`
8
+ `).all(`%${t}%`,r)}catch{return[]}},"GET /failures":e=>{const t=String(e.target??"").slice(0,200),r=_(e.limit,10,100),n=a().getDatabase();try{return n.prepare(`
9
9
  SELECT id, scope, target, description, why_failed, occurrences,
10
10
  severity, last_seen
11
11
  FROM failure_patterns
12
12
  WHERE resolved = 0 AND (target = ? OR target LIKE ?)
13
13
  ORDER BY occurrences DESC, last_seen DESC
14
14
  LIMIT ?
15
- `).all(t,`%${t}%`,r)}catch{return[]}},"GET /truths":e=>{const t=String(e.q??"").slice(0,200),r=y(e.limit,5,50);if(!t)return[];const n=o().getDatabase();try{return n.prepare(`
15
+ `).all(t,`%${t}%`,r)}catch{return[]}},"GET /truths":e=>{const t=String(e.q??"").slice(0,200),r=_(e.limit,5,50);if(!t)return[];const n=a().getDatabase();try{return n.prepare(`
16
16
  SELECT category, key, value, rationale, confidence, ttl_days
17
17
  FROM ground_truths
18
18
  WHERE is_current = 1
19
19
  AND (key LIKE ? OR value LIKE ? OR rationale LIKE ?)
20
20
  ORDER BY confidence DESC
21
21
  LIMIT ?
22
- `).all(`%${t}%`,`%${t}%`,`%${t}%`,r)}catch{return[]}},"GET /routing":e=>{const t=String(e.task_type??e.t??"").slice(0,200).trim();if(!t)return{e:"task_type required"};const r=y(e.limit,10,100);let n;const s=e.p;if(s){const c=o().getProject(s);if(!c)return{e:"not found"};n=c.id}const a=e.since_days!=null?Number(e.since_days):void 0;return o().recallRouting(t,{projectId:n,limit:r,sinceDays:Number.isFinite(a)?a:void 0}).map(c=>({m:c.model_id,tools:c.tool_names,runs:c.runs,ok:c.successes,fail:c.failures,rate:Math.round(c.success_rate*1e3)/1e3,ms:c.avg_latency_ms,last:c.last_ts}))},"POST /routing":e=>{const t=String(e.task_type??"").slice(0,200).trim(),r=String(e.model_id??"").slice(0,200).trim();if(!t)return{e:"task_type required"};if(!r)return{e:"model_id required"};if(e.success===void 0||e.success===null)return{e:"success required"};let n;const s=e.p;if(s){const l=o().getProject(s);l&&(n=l.id)}const a=Array.isArray(e.tool_names)?e.tool_names.map(l=>String(l)):e.tool_names!=null?String(e.tool_names):null,i=e.latency_ms!=null?Number(e.latency_ms):null,c=e.success===!0||e.success===1||e.success==="1"||e.success==="true";try{return{i:o().recordRouting({projectId:n,taskType:t,modelId:r,toolNames:a,success:c,latencyMs:Number.isFinite(i)?i:null})}}catch(l){return{e:l.message}}},"GET /audit":e=>{const t=y(e.limit,50,500),r=e.kind,n=o().getDatabase();try{return r?n.prepare("SELECT * FROM audit_log WHERE event_kind = ? ORDER BY id DESC LIMIT ?").all(r,t):n.prepare("SELECT * FROM audit_log ORDER BY id DESC LIMIT ?").all(t)}catch{return[]}},"GET /sync/push":e=>{const t=e.since,r=y(e.limit,500,5e3),n=o().getDatabase(),s=[],a=t??"1970-01-01",i=[["session","sessions","created_at"],["quest","quests","created_at"],["truth","ground_truths","updated_at"],["artifact","memory_artifacts","last_accessed_at"],["edge","decision_edges","created_at"]],c=(()=>{try{return n.prepare("PRAGMA table_info(projects)").all().some(u=>u.name==="sync_policy")?"AND (project_id IS NULL OR project_id IN (SELECT id FROM projects WHERE sync_policy = 'team'))":""}catch{return""}})();for(const[l,u,p]of i){if(s.length>=r)break;try{const d=n.prepare(`SELECT * FROM ${u}
23
- WHERE is_shared = 1 ${c} AND ${p} > ?
24
- ORDER BY ${p} ASC
25
- LIMIT ?`).all(a,Math.max(1,r-s.length));for(const m of d)s.push({kind:l,id:m.id,project_id:m.project_id??null,updated_at:m[p]??null,payload:m})}catch{}}return{rows:s,count:s.length,cursor:new Date().toISOString()}},"GET /events":e=>{if(!o().liveMemoryEnabled())return{e:"live memory disabled",disabled:1};const t=A(e.project??e.p);if(!t)return{e:"not found"};const r=J(void 0,e.since),n=y(e.limit??e.l,100,1e3),a=e.shared==="1"||e.shared==="true"?o().eventsForPush(t.id,r,n):o().eventsSince(t.id,r,n),i=a.length?a[a.length-1].cursor:r;return{project:t.name,cursor:i,count:a.length,events:a}},"POST /events":e=>{if(!o().liveMemoryEnabled())return{e:"live memory disabled",disabled:1};const t=A(e.project??e.p);if(!t)return{e:"not found"};const r=Array.isArray(e.events)?e.events:e.event?[e.event]:[];if(r.length===0)return{e:"events[] required"};if(r.length>M)return{e:`max ${M} events per request`};let n=0,s=0,a=0;for(const i of r){const c=o().ingestRemoteEvent(t.id,i);c==="inserted"?n++:c==="echo"?a++:s++}return n>0&&h.invalidate("ctx"),{project:t.name,inserted:n,duplicate:s,echo:a}},"GET /stats":()=>{const{result:e,ms:t}=G(()=>o().getStats());return{...e,ms:t,cache:h.stats()}},"GET /health":async e=>{const t={ok:1,ts:Date.now()};if(!/^(1|true|yes)$/i.test(String(e.check_dependencies??"")))return t;const r={...t};try{const s=o().getDatabase().prepare("SELECT MAX(version) v FROM schema_versions").get().v;r.db={ok:1,migration:s}}catch{r.ok=0,r.db={ok:0}}try{const n=I().getVectorStore();if(n){const s=n.getProvider(),a=n.indexCoverage();r.vectors={provider:s.name,model:s.model,ready:await s.isReady(),indexed:a.activeCount,stale:a.stale?1:0,...a.stale?{indexed_under:Object.keys(a.otherModels),hint:"run wyrm_reindex"}:{},egress:s.remoteHost??"none"}}else r.vectors={provider:"none",ready:!1}}catch{r.vectors={ok:0}}return r.rerank={configured:ce()?1:0,provider:(process.env.WYRM_RERANK_PROVIDER||"llamacpp").trim()||"llamacpp",egress:ie()??"none"},r},"GET /auth/status":()=>{const e=N();return{auth:e.requireAuth?1:0,origins:e.allowedOrigins.length}},"GET /ui/stats":()=>{const e=o().getDatabase(),t=e.prepare("SELECT COUNT(*) as c FROM projects").get().c,r=e.prepare("SELECT COUNT(*) as c FROM sessions").get().c,n=e.prepare("SELECT COUNT(*) as c FROM quests WHERE status IN ('pending','in_progress')").get().c,s=e.prepare("SELECT COUNT(*) as c FROM data_lake").get().c,a=e.prepare("SELECT COUNT(*) as c FROM memory_artifacts WHERE needs_review = 0").get().c,i=e.prepare("SELECT COUNT(*) as c FROM memory_artifacts WHERE needs_review = 1").get().c,c=e.prepare("SELECT COUNT(*) as c FROM ground_truths WHERE is_current = 1").get().c,l=e.prepare(`
22
+ `).all(`%${t}%`,`%${t}%`,`%${t}%`,r)}catch{return[]}},"GET /routing":e=>{const t=String(e.task_type??e.t??"").slice(0,200).trim();if(!t)return{e:"task_type required"};const r=_(e.limit,10,100);let n;const s=e.p;if(s){const i=a().getProject(s);if(!i)return{e:"not found"};n=i.id}const o=e.since_days!=null?Number(e.since_days):void 0;return a().recallRouting(t,{projectId:n,limit:r,sinceDays:Number.isFinite(o)?o:void 0}).map(i=>({m:i.model_id,tools:i.tool_names,runs:i.runs,ok:i.successes,fail:i.failures,rate:Math.round(i.success_rate*1e3)/1e3,ms:i.avg_latency_ms,last:i.last_ts}))},"POST /routing":e=>{const t=String(e.task_type??"").slice(0,200).trim(),r=String(e.model_id??"").slice(0,200).trim();if(!t)return{e:"task_type required"};if(!r)return{e:"model_id required"};if(e.success===void 0||e.success===null)return{e:"success required"};let n;const s=e.p;if(s){const l=a().getProject(s);l&&(n=l.id)}const o=Array.isArray(e.tool_names)?e.tool_names.map(l=>String(l)):e.tool_names!=null?String(e.tool_names):null,d=e.latency_ms!=null?Number(e.latency_ms):null,i=e.success===!0||e.success===1||e.success==="1"||e.success==="true";try{return{i:a().recordRouting({projectId:n,taskType:t,modelId:r,toolNames:o,success:i,latencyMs:Number.isFinite(d)?d:null})}}catch(l){return{e:l.message}}},"GET /audit":e=>{const t=_(e.limit,50,500),r=e.kind,n=a().getDatabase();try{return r?n.prepare("SELECT * FROM audit_log WHERE event_kind = ? ORDER BY id DESC LIMIT ?").all(r,t):n.prepare("SELECT * FROM audit_log ORDER BY id DESC LIMIT ?").all(t)}catch{return[]}},"GET /sync/push":e=>{const t=e.since,r=_(e.limit,500,5e3),n=a().getDatabase(),s=[],o=t??"1970-01-01",d=[["session","sessions","created_at"],["quest","quests","created_at"],["truth","ground_truths","updated_at"],["artifact","memory_artifacts","last_accessed_at"],["edge","decision_edges","created_at"]],i=(()=>{try{return n.prepare("PRAGMA table_info(projects)").all().some(p=>p.name==="sync_policy")?"AND (project_id IS NULL OR project_id IN (SELECT id FROM projects WHERE sync_policy = 'team'))":""}catch{return""}})();for(const[l,p,c]of d){if(s.length>=r)break;try{const u=n.prepare(`SELECT * FROM ${p}
23
+ WHERE is_shared = 1 ${i} AND ${c} > ?
24
+ ORDER BY ${c} ASC
25
+ LIMIT ?`).all(o,Math.max(1,r-s.length));for(const m of u)s.push({kind:l,id:m.id,project_id:m.project_id??null,updated_at:m[c]??null,payload:m})}catch{}}return{rows:s,count:s.length,cursor:new Date().toISOString()}},"GET /events":e=>{if(!a().liveMemoryEnabled())return{e:"live memory disabled",disabled:1};const t=P(e.project??e.p);if(!t)return{e:"not found"};const r=K(void 0,e.since),n=_(e.limit??e.l,100,1e3),o=e.shared==="1"||e.shared==="true"?a().eventsForPush(t.id,r,n):a().eventsSince(t.id,r,n),d=o.length?o[o.length-1].cursor:r;return{project:t.name,cursor:d,count:o.length,events:o}},"POST /events":e=>{if(!a().liveMemoryEnabled())return{e:"live memory disabled",disabled:1};const t=P(e.project??e.p);if(!t)return{e:"not found"};const r=Array.isArray(e.events)?e.events:e.event?[e.event]:[];if(r.length===0)return{e:"events[] required"};if(r.length>M)return{e:`max ${M} events per request`};let n=0,s=0,o=0;for(const d of r){const i=a().ingestRemoteEvent(t.id,d);i==="inserted"?n++:i==="echo"?o++:s++}return n>0&&h.invalidate("ctx"),{project:t.name,inserted:n,duplicate:s,echo:o}},"GET /stats":()=>{const{result:e,ms:t}=G(()=>a().getStats());return{...e,ms:t,cache:h.stats()}},"GET /health":async e=>{const t={ok:1,ts:Date.now()};if(!/^(1|true|yes)$/i.test(String(e.check_dependencies??"")))return t;const r={...t};try{const s=a().getDatabase().prepare("SELECT MAX(version) v FROM schema_versions").get().v;r.db={ok:1,migration:s}}catch{r.ok=0,r.db={ok:0}}try{const n=A().getVectorStore();if(n){const s=n.getProvider(),o=n.indexCoverage();r.vectors={provider:s.name,model:s.model,ready:await s.isReady(),indexed:o.activeCount,stale:o.stale?1:0,...o.stale?{indexed_under:Object.keys(o.otherModels),hint:"run wyrm_reindex"}:{},egress:s.remoteHost??"none"}}else r.vectors={provider:"none",ready:!1}}catch{r.vectors={ok:0}}return r.rerank={configured:ce()?1:0,provider:(process.env.WYRM_RERANK_PROVIDER||"llamacpp").trim()||"llamacpp",egress:le()??"none"},r},"GET /auth/status":()=>{const e=k();return{auth:e.requireAuth?1:0,origins:e.allowedOrigins.length}},"GET /ui/stats":()=>{const e=a().getDatabase(),t=e.prepare("SELECT COUNT(*) as c FROM projects").get().c,r=e.prepare("SELECT COUNT(*) as c FROM sessions").get().c,n=e.prepare("SELECT COUNT(*) as c FROM quests WHERE status IN ('pending','in_progress')").get().c,s=e.prepare("SELECT COUNT(*) as c FROM data_lake").get().c,o=e.prepare("SELECT COUNT(*) as c FROM memory_artifacts WHERE needs_review = 0").get().c,d=e.prepare("SELECT COUNT(*) as c FROM memory_artifacts WHERE needs_review = 1").get().c,i=e.prepare("SELECT COUNT(*) as c FROM ground_truths WHERE is_current = 1").get().c,l=e.prepare(`
26
26
  SELECT date, summary, objectives FROM sessions
27
27
  ORDER BY created_at DESC LIMIT 8
28
- `).all();return{projects:t,sessions:r,active_quests:n,data_points:s,artifacts:a,review_queue:i,truths:c,recent_sessions:l}},"GET /ui/memories":e=>{const t=o().getDatabase(),r=Math.max(1,parseInt(String(e.page||"1"),10)),n=Math.min(100,Math.max(1,parseInt(String(e.limit||"20"),10))),s=e.kind?String(e.kind):"",a=e.search?String(e.search):"",i=(r-1)*n;let c,l,u,p;if(a){const f=W(a);s?(c=`
28
+ `).all();return{projects:t,sessions:r,active_quests:n,data_points:s,artifacts:o,review_queue:d,truths:i,recent_sessions:l}},"GET /ui/memories":e=>{const t=a().getDatabase(),r=Math.max(1,parseInt(String(e.page||"1"),10)),n=Math.min(100,Math.max(1,parseInt(String(e.limit||"20"),10))),s=e.kind?String(e.kind):"",o=e.search?String(e.search):"",d=(r-1)*n;let i,l,p,c;if(o){const f=U(o);s?(i=`
29
29
  SELECT ma.*, p.name AS project_name
30
30
  FROM memory_artifacts ma
31
31
  JOIN memory_artifacts_fts fts ON fts.rowid = ma.id
@@ -36,7 +36,7 @@ import V from"http";import{WyrmDB as H}from"./database.js";import{cache as h,est
36
36
  SELECT COUNT(*) as c FROM memory_artifacts ma
37
37
  JOIN memory_artifacts_fts fts ON fts.rowid = ma.id
38
38
  WHERE memory_artifacts_fts MATCH ? AND ma.kind = ?
39
- AND ma.supersedes_id IS NULL`,u=[f,s,n,i],p=[f,s]):(c=`
39
+ AND ma.supersedes_id IS NULL`,p=[f,s,n,d],c=[f,s]):(i=`
40
40
  SELECT ma.*, p.name AS project_name
41
41
  FROM memory_artifacts ma
42
42
  JOIN memory_artifacts_fts fts ON fts.rowid = ma.id
@@ -45,37 +45,37 @@ import V from"http";import{WyrmDB as H}from"./database.js";import{cache as h,est
45
45
  ORDER BY ma.confidence DESC LIMIT ? OFFSET ?`,l=`
46
46
  SELECT COUNT(*) as c FROM memory_artifacts ma
47
47
  JOIN memory_artifacts_fts fts ON fts.rowid = ma.id
48
- WHERE memory_artifacts_fts MATCH ? AND ma.supersedes_id IS NULL`,u=[f,n,i],p=[f])}else s?(c=`
48
+ WHERE memory_artifacts_fts MATCH ? AND ma.supersedes_id IS NULL`,p=[f,n,d],c=[f])}else s?(i=`
49
49
  SELECT ma.*, p.name AS project_name
50
50
  FROM memory_artifacts ma
51
51
  LEFT JOIN projects p ON ma.project_id = p.id
52
52
  WHERE ma.kind = ? AND ma.supersedes_id IS NULL
53
- ORDER BY ma.confidence DESC LIMIT ? OFFSET ?`,l="SELECT COUNT(*) as c FROM memory_artifacts WHERE kind = ? AND supersedes_id IS NULL",u=[s,n,i],p=[s]):(c=`
53
+ ORDER BY ma.confidence DESC LIMIT ? OFFSET ?`,l="SELECT COUNT(*) as c FROM memory_artifacts WHERE kind = ? AND supersedes_id IS NULL",p=[s,n,d],c=[s]):(i=`
54
54
  SELECT ma.*, p.name AS project_name
55
55
  FROM memory_artifacts ma
56
56
  LEFT JOIN projects p ON ma.project_id = p.id
57
57
  WHERE ma.supersedes_id IS NULL
58
- ORDER BY ma.confidence DESC LIMIT ? OFFSET ?`,l="SELECT COUNT(*) as c FROM memory_artifacts WHERE supersedes_id IS NULL",u=[n,i],p=[]);const d=t.prepare(c).all(...u),m=t.prepare(l).get(...p).c;return{items:d,total:m,page:r,limit:n}},"GET /ui/quests":()=>{const t=o().getDatabase().prepare(`
58
+ ORDER BY ma.confidence DESC LIMIT ? OFFSET ?`,l="SELECT COUNT(*) as c FROM memory_artifacts WHERE supersedes_id IS NULL",p=[n,d],c=[]);const u=t.prepare(i).all(...p),m=t.prepare(l).get(...c).c;return{items:u,total:m,page:r,limit:n}},"GET /ui/quests":()=>{const t=a().getDatabase().prepare(`
59
59
  SELECT q.id, q.title, q.status, q.priority, p.name AS project_name
60
60
  FROM quests q
61
61
  LEFT JOIN projects p ON q.project_id = p.id
62
62
  ORDER BY q.created_at DESC
63
- `).all(),r={pending:[],in_progress:[],completed:[],abandoned:[]};for(const n of t)r[n.status]&&r[n.status].push(n);return r},"GET /ui/truths":e=>{const t=o().getDatabase(),r=Math.max(1,parseInt(String(e.page||"1"),10)),n=Math.min(100,Math.max(1,parseInt(String(e.limit||"20"),10))),s=(r-1)*n,a=t.prepare(`
63
+ `).all(),r={pending:[],in_progress:[],completed:[],abandoned:[]};for(const n of t)r[n.status]&&r[n.status].push(n);return r},"GET /ui/warroom":()=>{const e=a().getDatabase(),t=(c,...u)=>{try{const m=e.prepare(c).get(...u);return m?Object.values(m)[0]??0:0}catch{return 0}},r=(c,...u)=>{try{return e.prepare(c).all(...u)}catch{return[]}},n=I(!1).fleetGrove,o=(()=>{try{return e.prepare("SELECT id FROM projects WHERE name = ? LIMIT 1").get(n)}catch{return}})()?.id??-1,d=r("SELECT agent_id, agent_kind, last_heartbeat, ttl_seconds, role FROM agent_presence ORDER BY last_heartbeat DESC LIMIT 24").map(c=>({agent:c.agent_id,kind:c.agent_kind,role:c.role,last:c.last_heartbeat,alive:(()=>{try{const u=(Date.now()-new Date(c.last_heartbeat.replace(" ","T")+"Z").getTime())/1e3;return u>=0&&u<(c.ttl_seconds||300)}catch{return!1}})()})),i={findings:t("SELECT COUNT(*) n FROM quests WHERE project_id = ? AND status = ?",o,"pending")+t("SELECT COUNT(*) n FROM failure_patterns WHERE project_id = ? AND resolved = 0",o),claimed:t("SELECT COUNT(*) n FROM quests WHERE project_id = ? AND agent_id IS NOT NULL AND status IN ('pending','in_progress')",o),fixed:t("SELECT COUNT(*) n FROM quests WHERE project_id = ? AND status = 'completed'",o)+t("SELECT COUNT(*) n FROM memory_artifacts WHERE project_id = ? AND needs_review = 0 AND tags LIKE '%receipt%'",o),verified:t("SELECT COUNT(*) n FROM memory_artifacts WHERE project_id = ? AND needs_review = 0 AND tags LIKE '%verdict%'",o)},l=[];for(const c of r("SELECT id, title, status, agent_id, COALESCE(updated_at, created_at) AS ts, COALESCE(description, '') AS description FROM quests WHERE project_id = ?",o))l.push({type:"request",id:c.id,title:c.title,status:c.status,agent:c.agent_id,ts:c.ts,detail:String(c.description).slice(0,240)});for(const c of r("SELECT id, COALESCE(problem,'') AS problem, COALESCE(validated_fix,'') AS validated_fix, COALESCE(tags,'') AS tags, created_at FROM memory_artifacts WHERE project_id = ? AND needs_review = 0 AND tags LIKE '%warroom%'",o)){const u=String(c.tags).includes("verdict")?"verdict":String(c.tags).includes("receipt")?"receipt":"note";l.push({type:u,id:c.id,title:String(c.problem).slice(0,120),status:"",agent:null,ts:c.created_at,detail:String(c.validated_fix).slice(0,240)})}for(const c of r("SELECT id, scope, target, COALESCE(description,'') AS description, last_seen, resolved FROM failure_patterns WHERE project_id = ?",o))l.push({type:"finding",id:c.id,title:`[${c.scope}] ${c.target}`,status:c.resolved?"resolved":"open",agent:null,ts:c.last_seen,detail:String(c.description).slice(0,240)});l.sort((c,u)=>String(u.ts).localeCompare(String(c.ts)));const p={failure_blocks:t("SELECT COUNT(*) n FROM failure_blocks"),tokens_saved:t("SELECT COALESCE(SUM(estimated_tokens),0) n FROM token_savings_log"),rows_shared:t("SELECT COUNT(*) n FROM memory_artifacts WHERE project_id = ? AND is_shared = 1",o)+t("SELECT COUNT(*) n FROM quests WHERE project_id = ? AND is_shared = 1",o)};return{grove:n,found:o!==-1,presence:d,loop:i,metrics:p,feed:l.slice(0,40)}},"GET /ui/truths":e=>{const t=a().getDatabase(),r=Math.max(1,parseInt(String(e.page||"1"),10)),n=Math.min(100,Math.max(1,parseInt(String(e.limit||"20"),10))),s=(r-1)*n,o=t.prepare(`
64
64
  SELECT gt.*, p.name AS project_name
65
65
  FROM ground_truths gt
66
66
  LEFT JOIN projects p ON gt.project_id = p.id
67
67
  WHERE gt.is_current = 1
68
68
  ORDER BY gt.updated_at DESC LIMIT ? OFFSET ?
69
- `).all(n,s),i=t.prepare("SELECT COUNT(*) as c FROM ground_truths WHERE is_current = 1").get().c;return{items:a.map(l=>({...l,staleness:me(l)})),total:i,page:r,limit:n}},"GET /ui/review":()=>({items:o().getDatabase().prepare(`
69
+ `).all(n,s),d=t.prepare("SELECT COUNT(*) as c FROM ground_truths WHERE is_current = 1").get().c;return{items:o.map(l=>({...l,staleness:me(l)})),total:d,page:r,limit:n}},"GET /ui/review":()=>({items:a().getDatabase().prepare(`
70
70
  SELECT ma.*, p.name AS project_name
71
71
  FROM memory_artifacts ma
72
72
  LEFT JOIN projects p ON ma.project_id = p.id
73
73
  WHERE ma.needs_review = 1
74
74
  ORDER BY ma.created_at DESC
75
- `).all()}),"GET /ui/account":()=>{const e=U(o().getDatabasePath());try{const t=_(e,"license.json");if(S(t)){const r=JSON.parse(R(t,"utf-8")),n=B(r);return{account:r.license&&r.license.issued_to||"unknown",tier:n.valid?n.tier:"free",valid:!!n.valid,expires:n.expiresAt||null,readonly:T}}}catch{}try{const t=_(e,"cloud.json");if(S(t))return{account:JSON.parse(R(t,"utf-8")).email||"Local",tier:"free",valid:!1,expires:null,readonly:T}}catch{}return{account:"Local",tier:"free",valid:!1,expires:null,readonly:T}},"GET /ui/impact":()=>{const e=o().getDatabase(),t=r=>{try{return e.prepare(r).get()?.n||0}catch{return 0}};return{failures_blocked:t("SELECT COALESCE(SUM(occurrences),0) AS n FROM failure_patterns"),tokens_saved:t("SELECT COALESCE(SUM(estimated_tokens),0) AS n FROM token_savings_log"),memories:t("SELECT COUNT(*) AS n FROM memory_artifacts WHERE supersedes_id IS NULL"),memories_week:t("SELECT COUNT(*) AS n FROM memory_artifacts WHERE created_at >= datetime('now','-7 days')"),truths:t("SELECT COUNT(*) AS n FROM ground_truths WHERE is_current = 1"),sessions:t("SELECT COUNT(*) AS n FROM sessions"),quests_completed:t("SELECT COUNT(*) AS n FROM quests WHERE status = 'completed'"),skills:t("SELECT COUNT(*) AS n FROM skills"),projects:t("SELECT COUNT(*) AS n FROM projects")}},"GET /ui/memory":e=>{const t=o().getDatabase(),r=parseInt(String(e&&e.id||"0"),10);if(!r)return{item:null};try{return{item:t.prepare(`SELECT ma.*, p.name AS project_name
75
+ `).all()}),"GET /ui/account":()=>{const e=q(a().getDatabasePath());try{const t=S(e,"license.json");if(y(t)){const r=JSON.parse(R(t,"utf-8")),n=$(r);return{account:r.license&&r.license.issued_to||"unknown",tier:n.valid?n.tier:"free",valid:!!n.valid,expires:n.expiresAt||null,readonly:g}}}catch{}try{const t=S(e,"cloud.json");if(y(t))return{account:JSON.parse(R(t,"utf-8")).email||"Local",tier:"free",valid:!1,expires:null,readonly:g}}catch{}return{account:"Local",tier:"free",valid:!1,expires:null,readonly:g}},"GET /ui/impact":()=>{const e=a().getDatabase(),t=r=>{try{return e.prepare(r).get()?.n||0}catch{return 0}};return{failures_blocked:t("SELECT COALESCE(SUM(occurrences),0) AS n FROM failure_patterns"),tokens_saved:t("SELECT COALESCE(SUM(estimated_tokens),0) AS n FROM token_savings_log"),memories:t("SELECT COUNT(*) AS n FROM memory_artifacts WHERE supersedes_id IS NULL"),memories_week:t("SELECT COUNT(*) AS n FROM memory_artifacts WHERE created_at >= datetime('now','-7 days')"),truths:t("SELECT COUNT(*) AS n FROM ground_truths WHERE is_current = 1"),sessions:t("SELECT COUNT(*) AS n FROM sessions"),quests_completed:t("SELECT COUNT(*) AS n FROM quests WHERE status = 'completed'"),skills:t("SELECT COUNT(*) AS n FROM skills"),projects:t("SELECT COUNT(*) AS n FROM projects")}},"GET /ui/memory":e=>{const t=a().getDatabase(),r=parseInt(String(e&&e.id||"0"),10);if(!r)return{item:null};try{return{item:t.prepare(`SELECT ma.*, p.name AS project_name
76
76
  FROM memory_artifacts ma LEFT JOIN projects p ON ma.project_id = p.id
77
- WHERE ma.id = ?`).get(r)||null}}catch{return{item:null}}},"GET /ui/recall":async e=>{const t=e.search?String(e.search):e.q?String(e.q):"";if(!t)return{items:[],total:0};const r=y(e.limit,6,25),n=e.kind?String(e.kind):void 0,a=(await I().recallHybridGlobal(t,{limit:r,kind:n})).map(i=>({...i.artifact,relevance:Math.round(i.relevance_score*1e3)/1e3,match_type:i.match_type}));return{items:a,total:a.length}},"GET /ui/context":async e=>{const t=e.search?String(e.search):e.q?String(e.q):"";if(!t)return{context:"",items:[],total:0,tokens:0};const r=y(e.limit,10,25),n=y(e.budget,900,2400),s=await I().recallHybridGlobal(t,{limit:r}),a=[],i=[];let c=0;for(const l of s){const u=l.artifact,p=String(u.problem??u.title??u.summary??u.key??u.kind??"").trim(),d=String(u.validated_fix??u.content??u.body??u.value??u.problem??"").replace(/\s+/g," ").trim(),m=`- ${p}${d?": "+d.slice(0,320):""}`,f=Math.ceil(m.length/4);c+f<=n?(a.push(m),c+=f):a.push(`- ${p.slice(0,64)} \u2026 (recall for detail)`),i.push({...u,relevance:Math.round(l.relevance_score*1e3)/1e3,match_type:l.match_type})}return{context:a.join(`
78
- `),tokens:c,items:i,total:i.length}},"GET /ui/skills":()=>{const e=o().getDatabase();try{const t=e.prepare(`SELECT name, description, category, tier, usage_count, is_active
79
- FROM skills ORDER BY category COLLATE NOCASE, name COLLATE NOCASE`).all();return{items:t,total:t.length}}catch{return{items:[],total:0}}},"GET /ui/homes":()=>{const e=Q();let t=o().getDatabasePath();try{t=x(t)}catch{}return{homes:e.map(r=>({...r,active:r.dbPath===t}))}},"POST /ui/switch":e=>{const t=String(e&&e.path||""),r=Q().find(n=>n.dbPath===t);if(!r)return{ok:!1,error:"unknown home"};try{return Le(new H(r.dbPath)),{ok:!0,account:r.account,tier:r.tier}}catch(n){return{ok:!1,error:n instanceof Error?n.message:String(n)}}}};function ke(e,t,r){if(!o().liveMemoryEnabled()){E(t,e,{e:"live memory disabled",disabled:1},503);return}const n=A(r.project??r.p);if(!n){E(t,e,{e:"not found"},404);return}const s=e.socket.remoteAddress||"unknown";if(P>=Re){E(t,e,{e:"too many concurrent streams",retry_after:5},503);return}if((g.get(s)??0)>=ve){E(t,e,{e:"too many concurrent streams from this client",retry_after:5},503);return}P++,g.set(s,(g.get(s)??0)+1);let a=J(e.headers["last-event-id"],r.since);const c=!(s==="127.0.0.1"||s==="::1"||s==="::ffff:127.0.0.1")||r.shared==="1"||r.shared==="true";t.writeHead(200,{...L(e),"Content-Type":"text/event-stream; charset=utf-8","Cache-Control":"no-cache, no-transform",Connection:"keep-alive","X-Accel-Buffering":"no"}),t.write(`retry: 3000
77
+ WHERE ma.id = ?`).get(r)||null}}catch{return{item:null}}},"GET /ui/recall":async e=>{const t=e.search?String(e.search):e.q?String(e.q):"";if(!t)return{items:[],total:0};const r=_(e.limit,6,25),n=e.kind?String(e.kind):void 0,o=(await A().recallHybridGlobal(t,{limit:r,kind:n})).map(d=>({...d.artifact,relevance:Math.round(d.relevance_score*1e3)/1e3,match_type:d.match_type}));return{items:o,total:o.length}},"GET /ui/context":async e=>{const t=e.search?String(e.search):e.q?String(e.q):"";if(!t)return{context:"",items:[],total:0,tokens:0};const r=_(e.limit,10,25),n=_(e.budget,900,2400),s=await A().recallHybridGlobal(t,{limit:r}),o=[],d=[];let i=0;for(const l of s){const p=l.artifact,c=String(p.problem??p.title??p.summary??p.key??p.kind??"").trim(),u=String(p.validated_fix??p.content??p.body??p.value??p.problem??"").replace(/\s+/g," ").trim(),m=`- ${c}${u?": "+u.slice(0,320):""}`,f=Math.ceil(m.length/4);i+f<=n?(o.push(m),i+=f):o.push(`- ${c.slice(0,64)} \u2026 (recall for detail)`),d.push({...p,relevance:Math.round(l.relevance_score*1e3)/1e3,match_type:l.match_type})}return{context:o.join(`
78
+ `),tokens:i,items:d,total:d.length}},"GET /ui/skills":()=>{const e=a().getDatabase();try{const t=e.prepare(`SELECT name, description, category, tier, usage_count, is_active
79
+ FROM skills ORDER BY category COLLATE NOCASE, name COLLATE NOCASE`).all();return{items:t,total:t.length}}catch{return{items:[],total:0}}},"GET /ui/homes":()=>{const e=Q();let t=a().getDatabasePath();try{t=x(t)}catch{}return{homes:e.map(r=>({...r,active:r.dbPath===t}))}},"POST /ui/switch":e=>{const t=String(e&&e.path||""),r=Q().find(n=>n.dbPath===t);if(!r)return{ok:!1,error:"unknown home"};try{return je(new W(r.dbPath)),{ok:!0,account:r.account,tier:r.tier}}catch(n){return{ok:!1,error:n instanceof Error?n.message:String(n)}}}};function Ae(e,t,r){if(!a().liveMemoryEnabled()){E(t,e,{e:"live memory disabled",disabled:1},503);return}const n=P(r.project??r.p);if(!n){E(t,e,{e:"not found"},404);return}const s=e.socket.remoteAddress||"unknown";if(w>=ve){E(t,e,{e:"too many concurrent streams",retry_after:5},503);return}if((T.get(s)??0)>=Ce){E(t,e,{e:"too many concurrent streams from this client",retry_after:5},503);return}w++,T.set(s,(T.get(s)??0)+1);let o=K(e.headers["last-event-id"],r.since);const i=!(s==="127.0.0.1"||s==="::1"||s==="::ffff:127.0.0.1")||r.shared==="1"||r.shared==="true";t.writeHead(200,{...b(e),"Content-Type":"text/event-stream; charset=utf-8","Cache-Control":"no-cache, no-transform",Connection:"keep-alive","X-Accel-Buffering":"no"}),t.write(`retry: 3000
80
80
 
81
- `);let l,u,p=!1;const d=()=>{if(p)return;p=!0,l&&clearInterval(l),u&&clearInterval(u),P--;const f=(g.get(s)??1)-1;f<=0?g.delete(s):g.set(s,f)},m=()=>{if(t.writableEnded){d();return}try{const f=c?o().eventsForPush(n.id,a,200):o().eventsSince(n.id,a,200);for(const F of f)a=F.cursor,t.write(ye(F))}catch{}};m(),l=setInterval(m,1e3),u=setInterval(()=>{try{t.write(_e)}catch{}},25e3),e.on("close",d),t.on("close",d),t.on("error",d)}const w=V.createServer(async(e,t)=>{if(!ee(e,t).error)try{const n=new URL(e.url||"/",`http://localhost:${j}`);if(T&&Se(e.method||"GET",n.pathname)){E(t,e,{e:"read-only mode",readonly:1},403);return}let s;try{s=await Me(e)}catch(d){if(d.message==="Invalid JSON"){E(t,e,{e:"Invalid JSON body"},400);return}throw d}if(n.searchParams.forEach((d,m)=>s[m]=d),e.method==="GET"&&n.pathname==="/dragon-mark.svg"){try{if(!v&&S(q)&&(v=R(q)),v){t.writeHead(200,{...L(e),"Content-Type":"image/svg+xml","Cache-Control":"public, max-age=86400","Content-Length":v.length}),t.end(v);return}}catch{}E(t,e,{e:"not found"},404);return}if(e.method==="GET"&&n.pathname==="/ui"){je(t,e,ae());return}if(e.method==="GET"&&n.pathname==="/events/stream"){ke(e,t,s);return}const a=n.pathname.match(/^\/ui\/review\/(\d+)\/(approve|reject)$/);if(e.method==="POST"&&a){const d=parseInt(a[1],10),m=a[2],f=o().getDatabase();m==="approve"?f.prepare("UPDATE memory_artifacts SET needs_review = 0 WHERE id = ?").run(d):f.prepare("DELETE FROM memory_artifacts WHERE id = ?").run(d),E(t,e,{ok:!0});return}const i=`${e.method} ${n.pathname}`,c=X[i];if(!c){if(n.pathname==="/"){E(t,e,{wyrm:"3.0",auth:N().requireAuth?"required":"disabled",tip:"GET /c for quick context"});return}E(t,e,{e:"not found"},404);return}const l=Te({header:e.headers["wyrm-actor"]}),{result:u,ms:p}=G(()=>ge(l,()=>c(s)));t.setHeader("X-Time-Ms",String(p)),u instanceof Promise?u.then(d=>E(t,e,d)).catch(d=>{const m=Y(d);if(m!==null){const f=$("http",m);t.setHeader("Retry-After",String(Math.ceil(f.error.retry_after_ms/1e3))),E(t,e,f,503);return}D.error("Fast API request failed",{path:e.url,error:d.message}),E(t,e,{e:"Internal server error"},500)}):E(t,e,u)}catch(n){const s=Y(n);if(s!==null){const a=$("http",s);t.setHeader("Retry-After",String(Math.ceil(a.error.retry_after_ms/1e3))),E(t,e,a,503);return}D.error("Fast API request failed",{path:e.url,error:n.message}),E(t,e,{e:"Internal server error"},500)}}),K=()=>{w.close(()=>{try{O?.close()}catch{}process.exit(0)})};process.on("SIGINT",K),process.on("SIGTERM",K);import{fileURLToPath as Ie}from"url";const De=Ie(import.meta.url);if(process.argv[1]===De){const e=process.env.WYRM_BIND_HOST||"127.0.0.1";w.listen(j,e,()=>{D.info("Wyrm Fast API started",{port:j,host:e}),console.log(`\u{F115D} Wyrm Fast API on ${e}:${j}`),console.log(` Auth: ${N().requireAuth?"required":"disabled"}`),T&&console.log(" Read-only: writes + off-box egress blocked (safe to expose)"),e!=="127.0.0.1"&&e!=="::1"&&e!=="localhost"&&(D.warn("Wyrm Fast API bound to a NON-loopback host \u2014 reachable beyond localhost; ensure auth is required",{host:e}),console.log(` \u26A0 bound to ${e}: reachable beyond localhost \u2014 make sure auth is required`),T||console.log(" \u26A0 not in read-only mode; set WYRM_UI_READONLY=1 for a public bind"))})}export{w as server};
81
+ `);let l,p,c=!1;const u=()=>{if(c)return;c=!0,l&&clearInterval(l),p&&clearInterval(p),w--;const f=(T.get(s)??1)-1;f<=0?T.delete(s):T.set(s,f)},m=()=>{if(t.writableEnded){u();return}try{const f=i?a().eventsForPush(n.id,o,200):a().eventsSince(n.id,o,200);for(const H of f)o=H.cursor,t.write(Se(H))}catch{}};m(),l=setInterval(m,1e3),p=setInterval(()=>{try{t.write(ye)}catch{}},25e3),e.on("close",u),t.on("close",u),t.on("error",u)}const F=z.createServer(async(e,t)=>{if(!te(e,t).error)try{const n=new URL(e.url||"/",`http://localhost:${j}`);if(g&&ge(e.method||"GET",n.pathname)){E(t,e,{e:"read-only mode",readonly:1},403);return}let s;try{s=await Ne(e)}catch(u){if(u.message==="Invalid JSON"){E(t,e,{e:"Invalid JSON body"},400);return}throw u}if(n.searchParams.forEach((u,m)=>s[m]=u),e.method==="GET"&&n.pathname==="/dragon-mark.svg"){try{if(!v&&y(B)&&(v=R(B)),v){t.writeHead(200,{...b(e),"Content-Type":"image/svg+xml","Cache-Control":"public, max-age=86400","Content-Length":v.length}),t.end(v);return}}catch{}E(t,e,{e:"not found"},404);return}if(e.method==="GET"&&n.pathname==="/ui"){{const u=I(!1);let m=!1;try{m=!!a().getDatabase().prepare("SELECT 1 FROM projects WHERE name = ? LIMIT 1").get(u.fleetGrove)}catch{}Me(t,e,ie(I(m)))}return}if(e.method==="GET"&&n.pathname==="/events/stream"){Ae(e,t,s);return}const o=n.pathname.match(/^\/ui\/review\/(\d+)\/(approve|reject)$/);if(e.method==="POST"&&o){const u=parseInt(o[1],10),m=o[2],f=a().getDatabase();m==="approve"?f.prepare("UPDATE memory_artifacts SET needs_review = 0 WHERE id = ?").run(u):f.prepare("DELETE FROM memory_artifacts WHERE id = ?").run(u),E(t,e,{ok:!0});return}const d=`${e.method} ${n.pathname}`,i=X[d];if(!i){if(n.pathname==="/"){E(t,e,{wyrm:"3.0",auth:k().requireAuth?"required":"disabled",tip:"GET /c for quick context"});return}E(t,e,{e:"not found"},404);return}const l=Te({header:e.headers["wyrm-actor"]}),{result:p,ms:c}=G(()=>Oe(l,()=>i(s)));t.setHeader("X-Time-Ms",String(c)),p instanceof Promise?p.then(u=>E(t,e,u)).catch(u=>{const m=Y(u);if(m!==null){const f=J("http",m);t.setHeader("Retry-After",String(Math.ceil(f.error.retry_after_ms/1e3))),E(t,e,f,503);return}D.error("Fast API request failed",{path:e.url,error:u.message}),E(t,e,{e:"Internal server error"},500)}):E(t,e,p)}catch(n){const s=Y(n);if(s!==null){const o=J("http",s);t.setHeader("Retry-After",String(Math.ceil(o.error.retry_after_ms/1e3))),E(t,e,o,503);return}D.error("Fast API request failed",{path:e.url,error:n.message}),E(t,e,{e:"Internal server error"},500)}}),V=()=>{F.close(()=>{try{O?.close()}catch{}process.exit(0)})};process.on("SIGINT",V),process.on("SIGTERM",V);import{fileURLToPath as De}from"url";const ke=De(import.meta.url);if(process.argv[1]===ke){const e=process.env.WYRM_BIND_HOST||"127.0.0.1";F.listen(j,e,()=>{D.info("Wyrm Fast API started",{port:j,host:e}),console.log(`\u{F115D} Wyrm Fast API on ${e}:${j}`),console.log(` Auth: ${k().requireAuth?"required":"disabled"}`),g&&console.log(" Read-only: writes + off-box egress blocked (safe to expose)"),e!=="127.0.0.1"&&e!=="::1"&&e!=="localhost"&&(D.warn("Wyrm Fast API bound to a NON-loopback host \u2014 reachable beyond localhost; ensure auth is required",{host:e}),console.log(` \u26A0 bound to ${e}: reachable beyond localhost \u2014 make sure auth is required`),g||console.log(" \u26A0 not in read-only mode; set WYRM_UI_READONLY=1 for a public bind"))})}export{F as server};
package/dist/setup.js CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import{detectClients as g,autoConfigureAll as u,removeFromAll as p,configureSpecific as $,reconfAll as h,getStatusSummary as y,findWyrmServerPath as b,getDefaultDbPath as v,loadWyrmMeta as k,exportServerConfig as P}from"./autoconfig.js";import{c as o,icons as r,BANNER as w,MINI_BANNER as a,printError as f,printSection as i}from"./cli.js";import{runWizard as C}from"./setup-wizard.js";function I(n){const e=n.slice(2);if(e.length===0)return{command:"auto"};const c={command:"auto"};for(let l=0;l<e.length;l++)switch(e[l]){case"--check":case"-c":case"check":case"status":c.command="check";break;case"--remove":case"-r":case"remove":case"uninstall":c.command="remove";break;case"--only":case"-o":case"only":c.command="only",e[l+1]&&(c.clientIds=e[l+1].split(",").map(t=>t.trim()),l++);break;case"--reconf":case"--reconfigure":case"reconf":c.command="reconf";break;case"--list":case"-l":case"list":c.command="list";break;case"--export":case"export":{c.command="export";const t=e[l+1];(t==="stdio"||t==="vscode"||t==="mcp-json")&&(c.exportFormat=t,l++);break}case"--server":case"-s":e[l+1]&&(c.serverPath=e[l+1],l++);break;case"--db":case"-d":e[l+1]&&(c.dbPath=e[l+1],l++);break;case"--wizard":case"wizard":c.command="wizard";break;case"--no-wizard":case"--plain":c.noWizard=!0;break;case"--help":case"-h":case"help":c.command="help";break}return c}function d(n){console.log("");const e=n.filter(s=>s.action==="configured"),c=n.filter(s=>s.action==="updated"),l=n.filter(s=>s.action==="skipped"),t=n.filter(s=>s.action==="failed");for(const s of e)console.log(` ${r.success} ${s.client.icon} ${o.green(s.client.name)} \u2014 ${o.success("configured")}`),s.backup&&console.log(` ${o.dim(`backup \u2192 ${s.backup}`)}`);for(const s of c)console.log(` ${r.sync} ${s.client.icon} ${o.blue(s.client.name)} \u2014 ${o.cyan("updated")}`),s.backup&&console.log(` ${o.dim(`backup \u2192 ${s.backup}`)}`);for(const s of l)console.log(` ${o.dim(` \u25CB ${s.client.icon} ${s.client.name} \u2014 ${s.message}`)}`);for(const s of t)console.log(` ${r.error} ${s.client.icon} ${o.red(s.client.name)} \u2014 ${o.error(s.message)}`);console.log("");const m=e.length+c.length;m>0?(console.log(` ${o.success(`${r.dragon} Wyrm connected to ${m} AI client(s)`)}`),console.log(` ${o.dim("Switch AIs anytime \u2014 run wyrm-setup again to reconnect")}`)):t.length>0?console.log(` ${o.error("Some configurations failed. Check errors above.")}`):console.log(` ${o.dim("No AI clients detected. Install one and try again.")}`),console.log("")}function A(){console.log(a),console.log(""),console.log(`${o.bold("Usage:")} wyrm-setup ${o.dim("[command] [options]")}`),console.log(""),console.log(`${o.bold("Commands:")}`),console.log(` ${o.cyan("(no args)")} Auto-detect and configure all AI clients`),console.log(` ${o.cyan("check")} Show current configuration status`),console.log(` ${o.cyan("list")} List all supported AI clients`),console.log(` ${o.cyan("remove")} Remove Wyrm from all AI clients`),console.log(` ${o.cyan("reconf")} Re-configure previously configured clients`),console.log(` ${o.cyan("only X,Y")} Configure specific clients only`),console.log(` ${o.cyan("export [fmt]")} Print a portable config (fmt: mcp-json|vscode|stdio) for any MCP host`),console.log(` ${o.cyan("help")} Show this help message`),console.log(""),console.log(`${o.bold("Options:")}`),console.log(` ${o.cyan("--server P")} Override Wyrm MCP server path`),console.log(` ${o.cyan("--db P")} Override Wyrm database path`),console.log(""),console.log(`${o.bold("Client IDs:")}`);const n=g();for(const e of n)console.log(` ${e.icon} ${o.cyan(e.id.padEnd(18))} ${e.name}`);console.log(""),console.log(`${o.bold("Examples:")}`),console.log(` wyrm-setup ${o.dim("# Auto-configure everything")}`),console.log(` wyrm-setup check ${o.dim("# See what's configured")}`),console.log(` wyrm-setup only vscode-copilot,cursor ${o.dim("# Only VS Code + Cursor")}`),console.log(` wyrm-setup --server /path/to/wyrm/dist/index.js ${o.dim("# Custom server path")}`),console.log(` wyrm-setup remove ${o.dim("# Remove from all clients")}`),console.log(` wyrm-setup export stdio > wyrm.json ${o.dim("# Portable config for any MCP host")}`),console.log("")}function S(){console.log(a),console.log(""),i("Supported AI Clients"),console.log("");const n=g();for(const e of n){const c=e.detected?e.configured?o.success("\u25CF connected"):o.yellow("\u25D0 available"):o.dim("\u25CB not found"),l=e.version?` ${o.dim(`v${e.version}`)}`:"";console.log(` ${e.icon} ${o.bold(e.name.padEnd(20))} ${o.cyan(e.id.padEnd(18))} ${c}${l}`),console.log(` ${o.dim(`Config: ${e.configPath}`)}`)}console.log("")}async function x(){const n=I(process.argv);if(n.command==="wizard"||n.command==="auto"&&!n.noWizard){if(await C({serverPath:n.serverPath,dbPath:n.dbPath}))return;n.command==="wizard"&&(n.command="auto")}switch(n.command){case"help":A();break;case"check":{console.log(a),console.log(""),console.log(y());break}case"list":S();break;case"auto":{console.log(w),i("Auto-Configure"),console.log("");const e=n.serverPath||b(),c=n.dbPath||v();console.log(` ${r.sword} Server: ${o.cyan(e)}`),console.log(` ${r.treasure} DB: ${o.cyan(c)}`),console.log(""),console.log(` ${o.dim("Scanning for AI clients...")}`);const l=u({serverPath:e,dbPath:c});d(l);break}case"only":{(!n.clientIds||n.clientIds.length===0)&&(f("No client IDs specified. Use: wyrm-setup only vscode-copilot,cursor"),process.exit(1)),console.log(a),i(`Configure: ${n.clientIds.join(", ")}`);const e=$(n.clientIds,{serverPath:n.serverPath,dbPath:n.dbPath});d(e);break}case"remove":{console.log(a),i("Remove Wyrm from AI Clients"),console.log(""),console.log(` ${o.warning("Removing Wyrm configuration from all AI clients...")}`);const e=p(),c=e.filter(t=>t.action==="configured"),l=e.filter(t=>t.action==="skipped");for(const t of c)console.log(` ${r.check} ${t.client.icon} ${t.client.name} \u2014 removed`),t.backup&&console.log(` ${o.dim(`backup \u2192 ${t.backup}`)}`);for(const t of l)console.log(` ${o.dim(` \u25CB ${t.client.icon} ${t.client.name} \u2014 ${t.message}`)}`);console.log(""),c.length>0&&console.log(` ${o.success(`Removed from ${c.length} client(s). Run wyrm-setup to reconnect.`)}`),console.log("");break}case"reconf":{console.log(a),i("Re-Configure");const e=k();console.log(e?` ${o.dim(`Restoring config for: ${e.configuredClients.join(", ")}`)}`:` ${o.dim("No previous config found \u2014 running full auto-configure")}`);const c=h();d(c);break}case"export":{const e=n.exportFormat??"mcp-json",c=P(e,{serverPath:n.serverPath,dbPath:n.dbPath});process.stderr.write(`${o.dim(`# Wyrm MCP config (${e}). Paste into any MCP-compatible host.`)}
3
- `),process.stdout.write(JSON.stringify(c,null,2)+`
4
- `);break}}}x().catch(n=>{f(`Setup failed: ${n}`),process.exit(1)});
2
+ import{detectClients as m,autoConfigureAll as u,removeFromAll as p,configureSpecific as $,reconfAll as y,getStatusSummary as h,findWyrmServerPath as b,getDefaultDbPath as v,loadWyrmMeta as k,exportServerConfig as P}from"./autoconfig.js";import{c as o,icons as r,BANNER as C,MINI_BANNER as i,printError as f,printSection as a}from"./cli.js";function w(c){const e=c.slice(2);if(e.length===0)return{command:"auto"};const n={command:"auto"};for(let l=0;l<e.length;l++)switch(e[l]){case"--check":case"-c":case"check":case"status":n.command="check";break;case"--remove":case"-r":case"remove":case"uninstall":n.command="remove";break;case"--only":case"-o":case"only":n.command="only",e[l+1]&&(n.clientIds=e[l+1].split(",").map(t=>t.trim()),l++);break;case"--reconf":case"--reconfigure":case"reconf":n.command="reconf";break;case"--list":case"-l":case"list":n.command="list";break;case"--export":case"export":{n.command="export";const t=e[l+1];(t==="stdio"||t==="vscode"||t==="mcp-json")&&(n.exportFormat=t,l++);break}case"--server":case"-s":e[l+1]&&(n.serverPath=e[l+1],l++);break;case"--db":case"-d":e[l+1]&&(n.dbPath=e[l+1],l++);break;case"--help":case"-h":case"help":n.command="help";break}return n}function g(c){console.log("");const e=c.filter(s=>s.action==="configured"),n=c.filter(s=>s.action==="updated"),l=c.filter(s=>s.action==="skipped"),t=c.filter(s=>s.action==="failed");for(const s of e)console.log(` ${r.success} ${s.client.icon} ${o.green(s.client.name)} \u2014 ${o.success("configured")}`),s.backup&&console.log(` ${o.dim(`backup \u2192 ${s.backup}`)}`);for(const s of n)console.log(` ${r.sync} ${s.client.icon} ${o.blue(s.client.name)} \u2014 ${o.cyan("updated")}`),s.backup&&console.log(` ${o.dim(`backup \u2192 ${s.backup}`)}`);for(const s of l)console.log(` ${o.dim(` \u25CB ${s.client.icon} ${s.client.name} \u2014 ${s.message}`)}`);for(const s of t)console.log(` ${r.error} ${s.client.icon} ${o.red(s.client.name)} \u2014 ${o.error(s.message)}`);console.log("");const d=e.length+n.length;d>0?(console.log(` ${o.success(`${r.dragon} Wyrm connected to ${d} AI client(s)`)}`),console.log(` ${o.dim("Switch AIs anytime \u2014 run wyrm-setup again to reconnect")}`)):t.length>0?console.log(` ${o.error("Some configurations failed. Check errors above.")}`):console.log(` ${o.dim("No AI clients detected. Install one and try again.")}`),console.log("")}function I(){console.log(i),console.log(""),console.log(`${o.bold("Usage:")} wyrm-setup ${o.dim("[command] [options]")}`),console.log(""),console.log(`${o.bold("Commands:")}`),console.log(` ${o.cyan("(no args)")} Auto-detect and configure all AI clients`),console.log(` ${o.cyan("check")} Show current configuration status`),console.log(` ${o.cyan("list")} List all supported AI clients`),console.log(` ${o.cyan("remove")} Remove Wyrm from all AI clients`),console.log(` ${o.cyan("reconf")} Re-configure previously configured clients`),console.log(` ${o.cyan("only X,Y")} Configure specific clients only`),console.log(` ${o.cyan("export [fmt]")} Print a portable config (fmt: mcp-json|vscode|stdio) for any MCP host`),console.log(` ${o.cyan("help")} Show this help message`),console.log(""),console.log(`${o.bold("Options:")}`),console.log(` ${o.cyan("--server P")} Override Wyrm MCP server path`),console.log(` ${o.cyan("--db P")} Override Wyrm database path`),console.log(""),console.log(`${o.bold("Client IDs:")}`);const c=m();for(const e of c)console.log(` ${e.icon} ${o.cyan(e.id.padEnd(18))} ${e.name}`);console.log(""),console.log(`${o.bold("Examples:")}`),console.log(` wyrm-setup ${o.dim("# Auto-configure everything")}`),console.log(` wyrm-setup check ${o.dim("# See what's configured")}`),console.log(` wyrm-setup only vscode-copilot,cursor ${o.dim("# Only VS Code + Cursor")}`),console.log(` wyrm-setup --server /path/to/wyrm/dist/index.js ${o.dim("# Custom server path")}`),console.log(` wyrm-setup remove ${o.dim("# Remove from all clients")}`),console.log(` wyrm-setup export stdio > wyrm.json ${o.dim("# Portable config for any MCP host")}`),console.log("")}function A(){console.log(i),console.log(""),a("Supported AI Clients"),console.log("");const c=m();for(const e of c){const n=e.detected?e.configured?o.success("\u25CF connected"):o.yellow("\u25D0 available"):o.dim("\u25CB not found"),l=e.version?` ${o.dim(`v${e.version}`)}`:"";console.log(` ${e.icon} ${o.bold(e.name.padEnd(20))} ${o.cyan(e.id.padEnd(18))} ${n}${l}`),console.log(` ${o.dim(`Config: ${e.configPath}`)}`)}console.log("")}async function S(){const c=w(process.argv);switch(c.command){case"help":I();break;case"check":{console.log(i),console.log(""),console.log(h());break}case"list":A();break;case"auto":{console.log(C),a("Auto-Configure"),console.log("");const e=c.serverPath||b(),n=c.dbPath||v();console.log(` ${r.sword} Server: ${o.cyan(e)}`),console.log(` ${r.treasure} DB: ${o.cyan(n)}`),console.log(""),console.log(` ${o.dim("Scanning for AI clients...")}`);const l=u({serverPath:e,dbPath:n});g(l);break}case"only":{(!c.clientIds||c.clientIds.length===0)&&(f("No client IDs specified. Use: wyrm-setup only vscode-copilot,cursor"),process.exit(1)),console.log(i),a(`Configure: ${c.clientIds.join(", ")}`);const e=$(c.clientIds,{serverPath:c.serverPath,dbPath:c.dbPath});g(e);break}case"remove":{console.log(i),a("Remove Wyrm from AI Clients"),console.log(""),console.log(` ${o.warning("Removing Wyrm configuration from all AI clients...")}`);const e=p(),n=e.filter(t=>t.action==="configured"),l=e.filter(t=>t.action==="skipped");for(const t of n)console.log(` ${r.check} ${t.client.icon} ${t.client.name} \u2014 removed`),t.backup&&console.log(` ${o.dim(`backup \u2192 ${t.backup}`)}`);for(const t of l)console.log(` ${o.dim(` \u25CB ${t.client.icon} ${t.client.name} \u2014 ${t.message}`)}`);console.log(""),n.length>0&&console.log(` ${o.success(`Removed from ${n.length} client(s). Run wyrm-setup to reconnect.`)}`),console.log("");break}case"reconf":{console.log(i),a("Re-Configure");const e=k();console.log(e?` ${o.dim(`Restoring config for: ${e.configuredClients.join(", ")}`)}`:` ${o.dim("No previous config found \u2014 running full auto-configure")}`);const n=y();g(n);break}case"export":{const e=c.exportFormat??"mcp-json",n=P(e,{serverPath:c.serverPath,dbPath:c.dbPath});process.stderr.write(`${o.dim(`# Wyrm MCP config (${e}). Paste into any MCP-compatible host.`)}
3
+ `),process.stdout.write(JSON.stringify(n,null,2)+`
4
+ `);break}}}S().catch(c=>{f(`Setup failed: ${c}`),process.exit(1)});
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "contract": "wyrm-cloud-memory-v2",
3
- "version": "8.4.0",
3
+ "version": "8.5.2",
4
4
  "derivedFrom": "standard tier MINUS device-local/egress/subprocess (src/cloud-profile.ts)",
5
5
  "count": 19,
6
6
  "tools": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "contract": "wyrm-cloud-memory-v1",
3
- "version": "8.4.0",
3
+ "version": "8.5.2",
4
4
  "tools": [
5
5
  {
6
6
  "name": "wyrm_recall",