wyrm-mcp 8.3.1 → 8.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ import{resolveEmbeddingState as S}from"./providers/embedding-provider.js";function u(t,s){try{const e=t.prepare(s).get();if(!e)return null;const o=Object.values(e)[0];return typeof o=="number"?o:null}catch{return null}}function w(t){const s=u(t,"SELECT COUNT(*) AS n FROM memory_artifacts"),e=u(t,"SELECT COUNT(*) AS n FROM vectors")??0,o=u(t,"SELECT COUNT(*) AS n FROM memory_artifacts_fts"),n=u(t,"SELECT COUNT(*) AS n FROM memory_artifacts WHERE needs_review = 1"),c=u(t,"SELECT COUNT(*) AS n FROM failure_patterns WHERE resolved = 0"),l=S(),i=u(t,"SELECT MAX(version) AS v FROM schema_versions"),r=s&&s>0&&e!=null?Math.min(100,Math.round(e/s*100)):e===0?0:null,a=s!=null&&o!=null?s-o:null;return{memories:s,truths:u(t,"SELECT COUNT(*) AS n FROM ground_truths"),open_quests:u(t,"SELECT COUNT(*) AS n FROM quests WHERE status = 'pending'"),vectors:e,vector_coverage_pct:r,fts_drift:a,review_queue:n,unresolved_failures:c,provider:l.resolved,provider_degraded:l.resolved==="none"?1:0,schema_version:i,ok_vectors:l.resolved!=="none"&&(e??0)>0?1:0,ok_fts:a===0?1:0,ok_review:(n??0)===0?1:0}}function k(t){const s=r=>u(t,`SELECT COALESCE(SUM(estimated_tokens), 0) AS n FROM token_savings_log WHERE category = '${r}'`),e=r=>u(t,`SELECT COUNT(*) AS n FROM usage_events WHERE tool_name = '${r}'`),o=s("recovered_context"),n=s("blocked_retry"),c=s("cached_preamble"),l=s("truth_citation"),i=[o,n,c,l].every(r=>r!=null)?(o??0)+(n??0)+(c??0)+(l??0):null;return{failure_blocks:u(t,"SELECT COUNT(*) AS n FROM failure_blocks"),failure_check_calls:e("wyrm_failure_check"),rehydrate_calls:(e("wyrm_session_rehydrate")??0)+(e("wyrm_session_prime")??0),recall_calls:e("wyrm_recall"),capture_calls:e("wyrm_capture"),tokens_recovered:o,tokens_blocked_retry:n,tokens_saved_total:i}}function O(t,s,e,o){try{const n=t.prepare("INSERT INTO metrics_snapshots (wyrm_version, kind, metrics_json) VALUES (?, ?, ?)").run(o,s,JSON.stringify(e));return Number(n.lastInsertRowid)}catch{return null}}function T(t,s,e=30){try{return t.prepare("SELECT id, captured_at, wyrm_version, kind, metrics_json FROM metrics_snapshots WHERE kind = ? ORDER BY captured_at DESC, id DESC LIMIT ?").all(s,e).map(n=>({id:n.id,captured_at:n.captured_at,wyrm_version:n.wyrm_version,kind:n.kind,metrics:g(n.metrics_json)}))}catch{return[]}}function g(t){try{return JSON.parse(t)}catch{return{}}}function R(t,s){const e=[];if(t.length===0)return e;const o=t[0].metrics,n=t[1]?.metrics,c=(l,i)=>{const r=l?.[i];return typeof r=="number"?r:null};if(s==="health"){const l=t.findIndex(E=>c(E.metrics,"provider_degraded")!==1),i=l===-1?t.length:l;i>=2&&e.push(`vectors OFF for ${i} consecutive snapshots \u2014 recall has been FTS-only that long (wyrm doctor for the fix)`);const r=c(o,"review_queue"),a=c(n,"review_queue");r!=null&&r>0&&a!=null&&r>a&&e.push(`review queue GROWING: ${a} -> ${r} (queued memory is invisible to recall until \`wyrm review\`)`);const _=c(o,"unresolved_failures"),v=c(n,"unresolved_failures");_!=null&&v!=null&&_-v>=5&&e.push(`unresolved failures +${_-v} (${v} -> ${_}) \u2014 the firewall is accumulating debt (\`wyrm failure list\`)`);const f=c(o,"fts_drift");f!=null&&f!==0&&e.push(`FTS index drift: ${f} memories not mirrored in FTS \u2014 keyword recall is missing rows`);const d=c(o,"memories"),m=c(n,"memories"),h=c(o,"vector_coverage_pct"),p=c(n,"vector_coverage_pct");d!=null&&m!=null&&d>m&&h!=null&&p!=null&&h<p-5&&e.push(`corpus outgrowing the index: memories ${m} -> ${d} but coverage ${p}% -> ${h}% (\`wyrm index rebuild\`)`)}if(s==="effectiveness"){const l=c(o,"failure_blocks"),i=c(n,"failure_blocks");l!=null&&i!=null&&l>i&&e.push(`repeats prevented: +${l-i} failure blocks since last snapshot (negative learning paying off)`);const r=c(o,"tokens_saved_total"),a=c(n,"tokens_saved_total");r!=null&&a!=null&&r>a&&e.push(`+${(r-a).toLocaleString()} tokens saved since last snapshot (recovered context + blocked retries)`),r!=null&&a!=null&&r===a&&t.length>=3&&e.push("token savings FLAT across the last snapshots \u2014 memory is not being credited with any help (are the hooks + failure_check firing?)")}if(s==="retrieval"){const l=c(o,"recall_at_10"),i=c(n,"recall_at_10");if(l!=null&&i!=null){const r=Math.round((l-i)*1e3)/10;r<=-2?e.push(`recall@10 REGRESSED ${r}pts (${(i*100).toFixed(1)}% -> ${(l*100).toFixed(1)}%) between ${t[1].wyrm_version??"?"} and ${t[0].wyrm_version??"?"} \u2014 investigate the ranking change`):r>=2&&e.push(`recall@10 improved +${r}pts (${(i*100).toFixed(1)}% -> ${(l*100).toFixed(1)}%)`)}}return e}export{k as captureEffectiveness,w as captureHealth,T as getSnapshots,O as recordSnapshot,R as trendFlags};
@@ -210,7 +210,7 @@ const n=[{version:1,description:"Baseline schema \u2014 projects, sessions, ques
210
210
  timestamp TEXT DEFAULT (datetime('now'))
211
211
  );
212
212
  CREATE INDEX IF NOT EXISTS idx_usage_timestamp ON usage_events(timestamp);
213
- `)}},{version:3,description:"Add visibility and created_by columns for future multi-user support",up:e=>{const E=["sessions","quests","context","data_lake"];for(const t of E){const i=e.prepare(`PRAGMA table_info(${t})`).all().map(T=>T.name);i.includes("visibility")||e.exec(`ALTER TABLE ${t} ADD COLUMN visibility TEXT DEFAULT 'private'`),i.includes("created_by")||e.exec(`ALTER TABLE ${t} ADD COLUMN created_by TEXT DEFAULT 'local'`)}}},{version:4,description:"Knowledge graph \u2014 entities, aliases, relationships with FTS5",up:e=>{e.exec(`
213
+ `)}},{version:3,description:"Add visibility and created_by columns for future multi-user support",up:e=>{const s=["sessions","quests","context","data_lake"];for(const t of s){const i=e.prepare(`PRAGMA table_info(${t})`).all().map(T=>T.name);i.includes("visibility")||e.exec(`ALTER TABLE ${t} ADD COLUMN visibility TEXT DEFAULT 'private'`),i.includes("created_by")||e.exec(`ALTER TABLE ${t} ADD COLUMN created_by TEXT DEFAULT 'local'`)}}},{version:4,description:"Knowledge graph \u2014 entities, aliases, relationships with FTS5",up:e=>{e.exec(`
214
214
  -- Entities: named things that can be linked together
215
215
  CREATE TABLE IF NOT EXISTS entities (
216
216
  id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -844,7 +844,7 @@ const n=[{version:1,description:"Baseline schema \u2014 projects, sessions, ques
844
844
  ON token_savings_log(session_id, timestamp DESC);
845
845
  CREATE INDEX IF NOT EXISTS idx_savings_category
846
846
  ON token_savings_log(category, timestamp DESC);
847
- `);const E=["ground_truths","memory_artifacts","quests","decision_edges","design_references","design_tokens"];for(const t of E)try{if(e.prepare(`PRAGMA table_info(${t})`).all().some(i=>i.name==="cross_project_visibility"))continue;e.exec(`
847
+ `);const s=["ground_truths","memory_artifacts","quests","decision_edges","design_references","design_tokens"];for(const t of s)try{if(e.prepare(`PRAGMA table_info(${t})`).all().some(i=>i.name==="cross_project_visibility"))continue;e.exec(`
848
848
  ALTER TABLE ${t} ADD COLUMN cross_project_visibility TEXT NOT NULL DEFAULT 'within'
849
849
  CHECK (cross_project_visibility IN ('within', 'org', 'public'));
850
850
  `)}catch{}}},{version:15,description:"Live Memory v6.4 \u2014 append-only events log + wyrm_meta (device identity)",up:e=>{e.exec(`
@@ -908,7 +908,7 @@ const n=[{version:1,description:"Baseline schema \u2014 projects, sessions, ques
908
908
  `)}},{version:17,description:'Backfill cross_project_visibility on decision_edges (migration 14 named a non-existent "decisions" table)',up:e=>{try{e.prepare("PRAGMA table_info(decision_edges)").all().some(t=>t.name==="cross_project_visibility")||e.exec(`
909
909
  ALTER TABLE decision_edges ADD COLUMN cross_project_visibility TEXT NOT NULL DEFAULT 'within'
910
910
  CHECK (cross_project_visibility IN ('within', 'org', 'public'));
911
- `)}catch{}}},{version:18,description:"GOD-SKILL SPEC v2 \u2014 skill tier/governs/composes + spec-kit registry (specs table)",up:e=>{const E=e.prepare("PRAGMA table_info(skills)").all(),t=s=>E.some(i=>i.name===s);t("tier")||e.exec(`
911
+ `)}catch{}}},{version:18,description:"GOD-SKILL SPEC v2 \u2014 skill tier/governs/composes + spec-kit registry (specs table)",up:e=>{const s=e.prepare("PRAGMA table_info(skills)").all(),t=E=>s.some(i=>i.name===E);t("tier")||e.exec(`
912
912
  ALTER TABLE skills ADD COLUMN tier TEXT NOT NULL DEFAULT 'atomic'
913
913
  CHECK (tier IN ('atomic', 'mega', 'god'));
914
914
  `),t("governs")||e.exec("ALTER TABLE skills ADD COLUMN governs TEXT NOT NULL DEFAULT '[]';"),t("composes")||e.exec("ALTER TABLE skills ADD COLUMN composes TEXT NOT NULL DEFAULT '[]';"),e.exec("CREATE INDEX IF NOT EXISTS idx_skills_tier ON skills(tier);"),e.exec(`
@@ -929,7 +929,7 @@ const n=[{version:1,description:"Baseline schema \u2014 projects, sessions, ques
929
929
  `)}},{version:19,description:"Grove sync policy: projects.sync_policy gate (private by default) for cloud + federation isolation",up:e=>{e.prepare("PRAGMA table_info(projects)").all().some(o=>o.name==="sync_policy")||e.exec(`
930
930
  ALTER TABLE projects ADD COLUMN sync_policy TEXT NOT NULL DEFAULT 'private'
931
931
  CHECK (sync_policy IN ('private', 'cloud', 'team'));
932
- `);const t=(o,a)=>{try{return e.prepare(`PRAGMA table_info(${o})`).all().some(r=>r.name===a)}catch{return!1}},s=o=>{try{return e.prepare(o).all().map(a=>a.pid)}catch{return[]}},i=new Set;for(const o of["ground_truths","memory_artifacts","quests","design_tokens","design_references"])if(!(!t(o,"cross_project_visibility")||!t(o,"project_id")))for(const a of s(`SELECT DISTINCT project_id AS pid FROM ${o}
932
+ `);const t=(o,a)=>{try{return e.prepare(`PRAGMA table_info(${o})`).all().some(r=>r.name===a)}catch{return!1}},E=o=>{try{return e.prepare(o).all().map(a=>a.pid)}catch{return[]}},i=new Set;for(const o of["ground_truths","memory_artifacts","quests","design_tokens","design_references"])if(!(!t(o,"cross_project_visibility")||!t(o,"project_id")))for(const a of E(`SELECT DISTINCT project_id AS pid FROM ${o}
933
933
  WHERE cross_project_visibility IN ('org', 'public') AND project_id IS NOT NULL`))i.add(a);const T=e.prepare("UPDATE projects SET sync_policy = 'cloud' WHERE id = ? AND sync_policy = 'private'");for(const o of i)T.run(o)}},{version:20,description:"v7 F2 (T008) \u2014 run-native provenance: runs + run_agents tables, agent_id/run_id attribution on 9 tables, failure_patterns.quarantine_scope",up:e=>{e.exec(`
934
934
  CREATE TABLE IF NOT EXISTS runs (
935
935
  run_id TEXT PRIMARY KEY,
@@ -955,11 +955,11 @@ const n=[{version:1,description:"Baseline schema \u2014 projects, sessions, ques
955
955
  FOREIGN KEY (run_id) REFERENCES runs(run_id) ON DELETE CASCADE
956
956
  );
957
957
  CREATE INDEX IF NOT EXISTS idx_run_agents_agent ON run_agents(agent_id);
958
- `);const E=(s,i)=>{try{return e.prepare(`PRAGMA table_info(${s})`).all().some(T=>T.name===i)}catch{return!1}},t=["memory_artifacts","failure_patterns","decision_edges","ground_truths","quests","sessions","quest_claims","events","audit_log"];for(const s of t)E(s,"agent_id")||e.exec(`ALTER TABLE ${s} ADD COLUMN agent_id TEXT;`),E(s,"run_id")||e.exec(`ALTER TABLE ${s} ADD COLUMN run_id TEXT;`);e.exec(`
958
+ `);const s=(E,i)=>{try{return e.prepare(`PRAGMA table_info(${E})`).all().some(T=>T.name===i)}catch{return!1}},t=["memory_artifacts","failure_patterns","decision_edges","ground_truths","quests","sessions","quest_claims","events","audit_log"];for(const E of t)s(E,"agent_id")||e.exec(`ALTER TABLE ${E} ADD COLUMN agent_id TEXT;`),s(E,"run_id")||e.exec(`ALTER TABLE ${E} ADD COLUMN run_id TEXT;`);e.exec(`
959
959
  CREATE INDEX IF NOT EXISTS idx_failure_run ON failure_patterns(run_id) WHERE run_id IS NOT NULL;
960
960
  CREATE INDEX IF NOT EXISTS idx_events_run ON events(run_id) WHERE run_id IS NOT NULL;
961
961
  CREATE INDEX IF NOT EXISTS idx_artifacts_run ON memory_artifacts(run_id) WHERE run_id IS NOT NULL;
962
- `),E("failure_patterns","quarantine_scope")||(e.exec(`
962
+ `),s("failure_patterns","quarantine_scope")||(e.exec(`
963
963
  ALTER TABLE failure_patterns ADD COLUMN quarantine_scope TEXT NOT NULL DEFAULT 'project'
964
964
  CHECK (quarantine_scope IN ('run', 'project', 'global'));
965
965
  `),e.exec("UPDATE failure_patterns SET quarantine_scope = 'global' WHERE project_id IS NULL;")),e.exec("CREATE INDEX IF NOT EXISTS idx_failure_quarantine ON failure_patterns(quarantine_scope, resolved);")}},{version:21,description:"v7 F2 (T015) \u2014 failure_confirmations: distinct-agent confirmation ledger behind run-quarantine auto-promotion",up:e=>{e.exec(`
@@ -990,10 +990,10 @@ const n=[{version:1,description:"Baseline schema \u2014 projects, sessions, ques
990
990
  created_at TEXT DEFAULT (datetime('now')),
991
991
  PRIMARY KEY (run_id, role)
992
992
  );
993
- `)}},{version:24,description:"v7 F3 (T029) \u2014 claims/presence hardening: role on quest_claims, run_id/role on agent_presence, run-scoped partial indexes",up:e=>{const E=(t,s)=>{try{return e.prepare(`PRAGMA table_info(${t})`).all().some(i=>i.name===s)}catch{return!1}};E("quest_claims","role")||e.exec("ALTER TABLE quest_claims ADD COLUMN role TEXT;"),E("agent_presence","run_id")||e.exec("ALTER TABLE agent_presence ADD COLUMN run_id TEXT;"),E("agent_presence","role")||e.exec("ALTER TABLE agent_presence ADD COLUMN role TEXT;"),e.exec(`
993
+ `)}},{version:24,description:"v7 F3 (T029) \u2014 claims/presence hardening: role on quest_claims, run_id/role on agent_presence, run-scoped partial indexes",up:e=>{const s=(t,E)=>{try{return e.prepare(`PRAGMA table_info(${t})`).all().some(i=>i.name===E)}catch{return!1}};s("quest_claims","role")||e.exec("ALTER TABLE quest_claims ADD COLUMN role TEXT;"),s("agent_presence","run_id")||e.exec("ALTER TABLE agent_presence ADD COLUMN run_id TEXT;"),s("agent_presence","role")||e.exec("ALTER TABLE agent_presence ADD COLUMN role TEXT;"),e.exec(`
994
994
  CREATE INDEX IF NOT EXISTS idx_quest_claims_run ON quest_claims(run_id) WHERE run_id IS NOT NULL;
995
995
  CREATE INDEX IF NOT EXISTS idx_presence_run ON agent_presence(run_id) WHERE run_id IS NOT NULL;
996
- `)}},{version:25,description:"Skills portability \u2014 store SKILL.md content (+ sha/ts) in the registry and make skills cloud-sync-eligible behind the per-row visibility gate",up:e=>{const E=(t,s)=>{try{return e.prepare(`PRAGMA table_info(${t})`).all().some(i=>i.name===s)}catch{return!1}};E("skills","content")||e.exec("ALTER TABLE skills ADD COLUMN content TEXT;"),E("skills","content_sha256")||e.exec("ALTER TABLE skills ADD COLUMN content_sha256 TEXT;"),E("skills","content_updated_at")||e.exec("ALTER TABLE skills ADD COLUMN content_updated_at TEXT;"),E("skills","cross_project_visibility")||e.exec("ALTER TABLE skills ADD COLUMN cross_project_visibility TEXT NOT NULL DEFAULT 'within';"),E("skills","is_shared")||e.exec("ALTER TABLE skills ADD COLUMN is_shared INTEGER NOT NULL DEFAULT 0;"),e.exec("CREATE INDEX IF NOT EXISTS idx_skills_visibility ON skills(cross_project_visibility) WHERE cross_project_visibility != 'within';")}},{version:26,description:"Reverse-bridge rejection tombstones \u2014 remember a once-rejected outside-prose sig so the next sweep never re-queues content the operator already deleted",up:e=>{e.exec(`
996
+ `)}},{version:25,description:"Skills portability \u2014 store SKILL.md content (+ sha/ts) in the registry and make skills cloud-sync-eligible behind the per-row visibility gate",up:e=>{const s=(t,E)=>{try{return e.prepare(`PRAGMA table_info(${t})`).all().some(i=>i.name===E)}catch{return!1}};s("skills","content")||e.exec("ALTER TABLE skills ADD COLUMN content TEXT;"),s("skills","content_sha256")||e.exec("ALTER TABLE skills ADD COLUMN content_sha256 TEXT;"),s("skills","content_updated_at")||e.exec("ALTER TABLE skills ADD COLUMN content_updated_at TEXT;"),s("skills","cross_project_visibility")||e.exec("ALTER TABLE skills ADD COLUMN cross_project_visibility TEXT NOT NULL DEFAULT 'within';"),s("skills","is_shared")||e.exec("ALTER TABLE skills ADD COLUMN is_shared INTEGER NOT NULL DEFAULT 0;"),e.exec("CREATE INDEX IF NOT EXISTS idx_skills_visibility ON skills(cross_project_visibility) WHERE cross_project_visibility != 'within';")}},{version:26,description:"Reverse-bridge rejection tombstones \u2014 remember a once-rejected outside-prose sig so the next sweep never re-queues content the operator already deleted",up:e=>{e.exec(`
997
997
  CREATE TABLE IF NOT EXISTS reverse_bridge_tombstones (
998
998
  project_id INTEGER NOT NULL,
999
999
  sig TEXT NOT NULL,
@@ -1036,7 +1036,7 @@ const n=[{version:1,description:"Baseline schema \u2014 projects, sessions, ques
1036
1036
  );
1037
1037
  CREATE UNIQUE INDEX IF NOT EXISTS ux_failure_anchors_failure_rel
1038
1038
  ON failure_anchors(failure_id, rel_path);
1039
- `)}},{version:31,description:"FABLE skill tier \u2014 widen skills.tier to (atomic, mega, god, fable): model-vintage master-craft skills authored by Fable-class models during real builds, every law validated by shipping the artifact in the authoring session (fable-skill-spec)",up:e=>{const E=e.prepare("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'skills'").get();!E?.sql||E.sql.includes("'fable'")||e.exec(`
1039
+ `)}},{version:31,description:"FABLE skill tier \u2014 widen skills.tier to (atomic, mega, god, fable): model-vintage master-craft skills authored by Fable-class models during real builds, every law validated by shipping the artifact in the authoring session (fable-skill-spec)",up:e=>{const s=e.prepare("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'skills'").get();!s?.sql||s.sql.includes("'fable'")||e.exec(`
1040
1040
  CREATE TABLE skills_v31 (
1041
1041
  id INTEGER PRIMARY KEY AUTOINCREMENT,
1042
1042
  name TEXT UNIQUE NOT NULL,
@@ -1130,23 +1130,31 @@ const n=[{version:1,description:"Baseline schema \u2014 projects, sessions, ques
1130
1130
  UNIQUE(kind, row_id)
1131
1131
  );
1132
1132
  CREATE INDEX IF NOT EXISTS idx_sync_tombstones_pending ON sync_tombstones(pushed_at_ms);
1133
- `);const E=[["ground_truths","truth"],["memory_artifacts","memory_artifact"],["quests","quest"],["design_tokens","design_token"],["design_references","design_reference"],["skills","skill"]];for(const[t,s]of E)e.exec(`
1133
+ `);const s=[["ground_truths","truth"],["memory_artifacts","memory_artifact"],["quests","quest"],["design_tokens","design_token"],["design_references","design_reference"],["skills","skill"]];for(const[t,E]of s)e.exec(`
1134
1134
  CREATE TRIGGER IF NOT EXISTS trg_tombstone_${t} AFTER DELETE ON ${t}
1135
1135
  BEGIN
1136
1136
  INSERT OR IGNORE INTO sync_tombstones (kind, row_id, deleted_at_ms)
1137
- VALUES ('${s}', CAST(OLD.id AS TEXT), CAST(strftime('%s','now') AS INTEGER) * 1000);
1137
+ VALUES ('${E}', CAST(OLD.id AS TEXT), CAST(strftime('%s','now') AS INTEGER) * 1000);
1138
1138
  END;
1139
- `)}},{version:35,description:"Grove-gate the tombstone triggers (quest #113): a tombstone must record ONLY when the deleted row would have SYNCED, matching the upsert gate exactly (cross_project_visibility IN org/public AND grove sync_policy IN cloud/team). Migration 34 recorded a tombstone for EVERY delete, so a PRIVATE grove's (or unshared skill's) deletion leaked content-free deletion metadata to the cloud as noise. Recreates all 6 triggers gated.",up:e=>{const E=[["ground_truths","truth"],["memory_artifacts","memory_artifact"],["quests","quest"],["design_tokens","design_token"],["design_references","design_reference"],["skills","skill"]];for(const[t,s]of E){e.exec(`DROP TRIGGER IF EXISTS trg_tombstone_${t};`);const i=t==="skills"?"OLD.cross_project_visibility IN ('org','public')":"OLD.cross_project_visibility IN ('org','public') AND (OLD.project_id IS NULL OR (SELECT sync_policy FROM projects WHERE id = OLD.project_id) IN ('cloud','team'))";e.exec(`
1139
+ `)}},{version:35,description:"Grove-gate the tombstone triggers (quest #113): a tombstone must record ONLY when the deleted row would have SYNCED, matching the upsert gate exactly (cross_project_visibility IN org/public AND grove sync_policy IN cloud/team). Migration 34 recorded a tombstone for EVERY delete, so a PRIVATE grove's (or unshared skill's) deletion leaked content-free deletion metadata to the cloud as noise. Recreates all 6 triggers gated.",up:e=>{const s=[["ground_truths","truth"],["memory_artifacts","memory_artifact"],["quests","quest"],["design_tokens","design_token"],["design_references","design_reference"],["skills","skill"]];for(const[t,E]of s){e.exec(`DROP TRIGGER IF EXISTS trg_tombstone_${t};`);const i=t==="skills"?"OLD.cross_project_visibility IN ('org','public')":"OLD.cross_project_visibility IN ('org','public') AND (OLD.project_id IS NULL OR (SELECT sync_policy FROM projects WHERE id = OLD.project_id) IN ('cloud','team'))";e.exec(`
1140
1140
  CREATE TRIGGER trg_tombstone_${t} AFTER DELETE ON ${t}
1141
1141
  WHEN ${i}
1142
1142
  BEGIN
1143
1143
  INSERT OR IGNORE INTO sync_tombstones (kind, row_id, deleted_at_ms)
1144
- VALUES ('${s}', CAST(OLD.id AS TEXT), CAST(strftime('%s','now') AS INTEGER) * 1000);
1144
+ VALUES ('${E}', CAST(OLD.id AS TEXT), CAST(strftime('%s','now') AS INTEGER) * 1000);
1145
1145
  END;
1146
- `)}}},{version:36,description:`Add updated_at to quests + design_references so cloud sync can watermark-filter them. Root cause of "sync never settles" (2026-07-17): collectChangedRows skips its incremental time filter for any synced table WITHOUT an updated_at column, so it re-collected ALL org/public quests (101) + design_references every sync, stamping each with Date.now() and re-pushing forever (~1s/row server-side). The 4 other synced tables already have updated_at. This migration adds the column + backfills from the row's real last-change time (normalized via datetime() to the clean "YYYY-MM-DD HH:MM:SS" format strftime expects). No triggers: both tables are external-content FTS5, where a self-UPDATE inside a trigger corrupts the FTS index when an indexed column changes ("database disk image is malformed"). updated_at is stamped in app code on every insert/update instead (database.ts addQuest/updateQuest/dynamic-update, design.ts) \u2014 the same convention the other synced tables use.`,up:e=>{const E=(t,s)=>e.prepare(`PRAGMA table_info(${t})`).all().some(i=>i.name===s);E("quests","updated_at")||(e.exec("ALTER TABLE quests ADD COLUMN updated_at TEXT"),e.exec("UPDATE quests SET updated_at = datetime(COALESCE(completed_at, created_at, 'now'))")),E("design_references","updated_at")||(e.exec("ALTER TABLE design_references ADD COLUMN updated_at TEXT"),e.exec("UPDATE design_references SET updated_at = datetime('now')"))}}];function d(e){e.exec(`
1146
+ `)}}},{version:36,description:`Add updated_at to quests + design_references so cloud sync can watermark-filter them. Root cause of "sync never settles" (2026-07-17): collectChangedRows skips its incremental time filter for any synced table WITHOUT an updated_at column, so it re-collected ALL org/public quests (101) + design_references every sync, stamping each with Date.now() and re-pushing forever (~1s/row server-side). The 4 other synced tables already have updated_at. This migration adds the column + backfills from the row's real last-change time (normalized via datetime() to the clean "YYYY-MM-DD HH:MM:SS" format strftime expects). No triggers: both tables are external-content FTS5, where a self-UPDATE inside a trigger corrupts the FTS index when an indexed column changes ("database disk image is malformed"). updated_at is stamped in app code on every insert/update instead (database.ts addQuest/updateQuest/dynamic-update, design.ts) \u2014 the same convention the other synced tables use.`,up:e=>{const s=(t,E)=>e.prepare(`PRAGMA table_info(${t})`).all().some(i=>i.name===E);s("quests","updated_at")||(e.exec("ALTER TABLE quests ADD COLUMN updated_at TEXT"),e.exec("UPDATE quests SET updated_at = datetime(COALESCE(completed_at, created_at, 'now'))")),s("design_references","updated_at")||(e.exec("ALTER TABLE design_references ADD COLUMN updated_at TEXT"),e.exec("UPDATE design_references SET updated_at = datetime('now')"))}},{version:37,description:"Add metrics_snapshots \u2014 the longitudinal flight recorder for Wyrm effectiveness + quality over time. wyrm doctor / stats / benches all measure a MOMENT; nothing timestamped a trend, so a slow degradation (recall drifting as the corpus outgrows the index, vectors quietly off for days, a crucible-caught defect class recurring) was invisible until someone re-ran a tool by hand. One row per (kind, capture): kind=health (corpus/coverage/queue/failures/doctor checks), kind=effectiveness (recall hit-rate, failure-block rate, rehydrate non-empty rate, token savings), kind=retrieval (recall@k from the eval bench). metrics_json keeps the schema flexible so new metrics need no migration. Written nightly (health+effectiveness) + on-demand/per-release (retrieval); read by `wyrm metrics trend`.",up:e=>{e.exec(`
1147
+ CREATE TABLE IF NOT EXISTS metrics_snapshots (
1148
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
1149
+ captured_at TEXT NOT NULL DEFAULT (datetime('now')),
1150
+ wyrm_version TEXT,
1151
+ kind TEXT NOT NULL CHECK (kind IN ('health', 'effectiveness', 'retrieval')),
1152
+ metrics_json TEXT NOT NULL
1153
+ );
1154
+ `),e.exec("CREATE INDEX IF NOT EXISTS idx_metrics_kind_time ON metrics_snapshots(kind, captured_at DESC);")}}];function d(e){e.exec(`
1147
1155
  CREATE TABLE IF NOT EXISTS schema_versions (
1148
1156
  version INTEGER PRIMARY KEY,
1149
1157
  description TEXT NOT NULL,
1150
1158
  applied_at TEXT DEFAULT (datetime('now'))
1151
1159
  );
1152
- `);const E=e.prepare("SELECT COALESCE(MAX(version), 0) as v FROM schema_versions").get().v,t=n.filter(T=>T.version>E).slice().sort((T,o)=>T.version-o.version);if(t.length===0)return[];const s=[],i=e.prepare("INSERT INTO schema_versions (version, description) VALUES (?, ?)");for(const T of t)e.transaction(()=>{T.up(e),i.run(T.version,T.description)})(),s.push(T);return s}function N(e){try{return e.prepare("SELECT COALESCE(MAX(version), 0) as v FROM schema_versions").get().v}catch{return 0}}export{N as getSchemaVersion,n as migrations,d as runMigrations};
1160
+ `);const s=e.prepare("SELECT COALESCE(MAX(version), 0) as v FROM schema_versions").get().v,t=n.filter(T=>T.version>s).slice().sort((T,o)=>T.version-o.version);if(t.length===0)return[];const E=[],i=e.prepare("INSERT INTO schema_versions (version, description) VALUES (?, ?)");for(const T of t)e.transaction(()=>{T.up(e),i.run(T.version,T.description)})(),E.push(T);return E}function N(e){try{return e.prepare("SELECT COALESCE(MAX(version), 0) as v FROM schema_versions").get().v}catch{return 0}}export{N as getSchemaVersion,n as migrations,d as runMigrations};
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "contract": "wyrm-cloud-memory-v2",
3
- "version": "8.3.1",
3
+ "version": "8.4.0",
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.3.1",
3
+ "version": "8.4.0",
4
4
  "tools": [
5
5
  {
6
6
  "name": "wyrm_recall",
package/dist/wyrm-cli.js CHANGED
@@ -1,88 +1,88 @@
1
1
  #!/usr/bin/env node
2
- import{join as F,dirname as re,resolve as Ie,basename as Pe}from"path";import{homedir as Ne}from"os";import{existsSync as q,readFileSync as V,readdirSync as Me}from"fs";import{fileURLToPath as ne}from"url";import{spawnSync as z}from"child_process";import{createInterface as Q}from"readline";import{WyrmDB as Oe}from"./database.js";import{MemoryArtifacts as G}from"./memory-artifacts.js";import{GroundTruths as pe}from"./intelligence.js";import{Causality as ue}from"./causality.js";import{wireTruthAutoCascade as me}from"./truth-cascade.js";import{Rehydration as Ae}from"./rehydration.js";import{makeRenderDeps as fe,buildRenderPlan as ie,renderToDisk as Le,renderForClient as ge,detectClients as We,ALL_RENDER_CLIENTS as ae,ALL_CLIENT_MARKERS as Ue}from"./render-target.js";import{classifyCapture as Fe}from"./capture.js";import{importFrom as qe,IMPORT_SOURCES as ye}from"./importers.js";import{c as d,colors as N,formatTable as D,printSection as k,printSuccess as b,printError as m,icons as Y}from"./cli.js";import{loadConnectorConfigs as he,upsertConnectorConfig as Ye,runConnector as He,runAllConnectors as Ve}from"./connectors/index.js";import{readActor as Be}from"./attribution.js";function M(){try{const l=re(ne(import.meta.url));return JSON.parse(V(F(l,"..","package.json"),"utf-8"))}catch{return{}}}function I(l,c){const t=typeof l=="string"?parseInt(l,10):NaN;return Number.isFinite(t)?t:c}function X(l,c){const t=typeof l=="string"?parseFloat(l):NaN;return Number.isFinite(t)?t:c}function Ke(){return process.env.WYRM_DB_PATH??F(Ne(),".wyrm","wyrm.db")}function j(){return new Oe(Ke())}function C(l){const c=[],t={};let i=0;for(;i<l.length;){const r=l[i];if(r.startsWith("--")){const e=r.slice(2),s=l[i+1];s&&!s.startsWith("--")?(t[e]=s,i+=2):(t[e]=!0,i++)}else c.push(r),i++}return{positional:c,flags:t}}function x(l,c){return c?l.getDatabase().prepare("SELECT * FROM projects WHERE LOWER(name) LIKE LOWER(?) LIMIT 1").get(`%${c}%`)??null:null}async function Ge(l){const{positional:c,flags:t}=C(l),i=c[0];i||(m("Usage: wyrm search <query> [--project <name>] [--type all|memories|truths|quests|data]"),process.exit(1));const r=j(),e=r.getDatabase(),s=t.type??"all",o=t.project,p=(o?x(r,o):null)?.id;k(`Search: "${i}"`);const a=[];if(s==="all"||s==="memories")try{const u=p?`AND m.project_id = ${p}`:"",g=e.prepare(`
2
+ import{join as O,dirname as z,resolve as Ne,basename as Me}from"path";import{homedir as Oe}from"os";import{existsSync as L,readFileSync as Y,readdirSync as Ae,rmSync as Le}from"fs";import{fileURLToPath as Q}from"url";import{spawnSync as X}from"child_process";import{createInterface as Z}from"readline";import{WyrmDB as We}from"./database.js";import{MemoryArtifacts as G}from"./memory-artifacts.js";import{GroundTruths as ue}from"./intelligence.js";import{Causality as me}from"./causality.js";import{wireTruthAutoCascade as fe}from"./truth-cascade.js";import{Rehydration as Ue}from"./rehydration.js";import{makeRenderDeps as ge,buildRenderPlan as ie,renderToDisk as qe,renderForClient as ye,detectClients as Fe,ALL_RENDER_CLIENTS as ae,ALL_CLIENT_MARKERS as Ye}from"./render-target.js";import{classifyCapture as He}from"./capture.js";import{importFrom as Ve,IMPORT_SOURCES as he}from"./importers.js";import{c as p,colors as A,formatTable as D,printSection as k,printSuccess as b,printError as m,icons as H}from"./cli.js";import{loadConnectorConfigs as we,upsertConnectorConfig as Be,runConnector as Ke,runAllConnectors as Ge}from"./connectors/index.js";import{readActor as Je}from"./attribution.js";import{captureHealth as ze,captureEffectiveness as Qe,recordSnapshot as ce,getSnapshots as be,trendFlags as Xe}from"./metrics.js";function M(){try{const l=z(Q(import.meta.url));return JSON.parse(Y(O(l,"..","package.json"),"utf-8"))}catch{return{}}}function I(l,c){const e=typeof l=="string"?parseInt(l,10):NaN;return Number.isFinite(e)?e:c}function ee(l,c){const e=typeof l=="string"?parseFloat(l):NaN;return Number.isFinite(e)?e:c}function Ze(){return process.env.WYRM_DB_PATH??O(Oe(),".wyrm","wyrm.db")}function j(){return new We(Ze())}function E(l){const c=[],e={};let i=0;for(;i<l.length;){const n=l[i];if(n.startsWith("--")){const t=n.slice(2),s=l[i+1];s&&!s.startsWith("--")?(e[t]=s,i+=2):(e[t]=!0,i++)}else c.push(n),i++}return{positional:c,flags:e}}function x(l,c){return c?l.getDatabase().prepare("SELECT * FROM projects WHERE LOWER(name) LIKE LOWER(?) LIMIT 1").get(`%${c}%`)??null:null}async function et(l){const{positional:c,flags:e}=E(l),i=c[0];i||(m("Usage: wyrm search <query> [--project <name>] [--type all|memories|truths|quests|data]"),process.exit(1));const n=j(),t=n.getDatabase(),s=e.type??"all",o=e.project,d=(o?x(n,o):null)?.id;k(`Search: "${i}"`);const a=[];if(s==="all"||s==="memories")try{const u=d?`AND m.project_id = ${d}`:"",g=t.prepare(`
3
3
  SELECT m.id, m.kind, m.problem, m.project_id FROM memory_artifacts m
4
4
  JOIN memory_artifacts_fts fts ON m.id = fts.rowid
5
5
  WHERE memory_artifacts_fts MATCH ? ${u}
6
6
  LIMIT 20
7
- `).all(i);for(const f of g)a.push([`mem:${f.id}`,"memory",f.kind,f.problem.slice(0,80)])}catch{}if(s==="all"||s==="sessions")try{const u=p?`AND s.project_id = ${p}`:"",g=e.prepare(`
7
+ `).all(i);for(const f of g)a.push([`mem:${f.id}`,"memory",f.kind,f.problem.slice(0,80)])}catch{}if(s==="all"||s==="sessions")try{const u=d?`AND s.project_id = ${d}`:"",g=t.prepare(`
8
8
  SELECT s.id, s.objectives, s.project_id FROM sessions s
9
9
  JOIN sessions_fts fts ON s.id = fts.rowid
10
10
  WHERE sessions_fts MATCH ? ${u}
11
11
  LIMIT 10
12
- `).all(i);for(const f of g)a.push([`session:${f.id}`,"session","",f.objectives.slice(0,80)])}catch{}if(s==="all"||s==="quests")try{const u=p?`AND q.project_id = ${p}`:"",g=e.prepare(`
12
+ `).all(i);for(const f of g)a.push([`session:${f.id}`,"session","",f.objectives.slice(0,80)])}catch{}if(s==="all"||s==="quests")try{const u=d?`AND q.project_id = ${d}`:"",g=t.prepare(`
13
13
  SELECT q.id, q.title, q.priority, q.project_id FROM quests q
14
14
  JOIN quests_fts fts ON q.id = fts.rowid
15
15
  WHERE quests_fts MATCH ? ${u}
16
16
  LIMIT 10
17
- `).all(i);for(const f of g)a.push([`quest:${f.id}`,"quest",f.priority,f.title.slice(0,80)])}catch{}if(s==="all"||s==="data")try{const u=p?`AND d.project_id = ${p}`:"",g=e.prepare(`
17
+ `).all(i);for(const f of g)a.push([`quest:${f.id}`,"quest",f.priority,f.title.slice(0,80)])}catch{}if(s==="all"||s==="data")try{const u=d?`AND d.project_id = ${d}`:"",g=t.prepare(`
18
18
  SELECT d.id, d.category, d.key, d.value FROM data_lake d
19
19
  JOIN data_lake_fts fts ON d.id = fts.rowid
20
20
  WHERE data_lake_fts MATCH ? ${u}
21
21
  LIMIT 10
22
- `).all(i);for(const f of g)a.push([`data:${f.id}`,"data",f.category,f.value.slice(0,80)])}catch{}if(r.close(),a.length===0){console.log(d.dim(` No results found for "${i}"`));return}console.log(D(["ID","Type","Subtype","Preview"],a)),console.log(d.dim(`
23
- ${a.length} result${a.length!==1?"s":""}`))}async function Je(l){const{flags:c}=C(l),t=c.type??"all",i=I(c.limit,20),r=c.project,e=j(),s=e.getDatabase(),n=(r?x(e,r):null)?.id,p=n?`WHERE project_id = ${n}`:"",a=n?`AND project_id = ${n}`:"";if(k("Wyrm Memory"),t==="all"||t==="memories"){k("Memories");const u=s.prepare(`
22
+ `).all(i);for(const f of g)a.push([`data:${f.id}`,"data",f.category,f.value.slice(0,80)])}catch{}if(n.close(),a.length===0){console.log(p.dim(` No results found for "${i}"`));return}console.log(D(["ID","Type","Subtype","Preview"],a)),console.log(p.dim(`
23
+ ${a.length} result${a.length!==1?"s":""}`))}async function tt(l){const{flags:c}=E(l),e=c.type??"all",i=I(c.limit,20),n=c.project,t=j(),s=t.getDatabase(),r=(n?x(t,n):null)?.id,d=r?`WHERE project_id = ${r}`:"",a=r?`AND project_id = ${r}`:"";if(k("Wyrm Memory"),e==="all"||e==="memories"){k("Memories");const u=s.prepare(`
24
24
  SELECT id, kind, confidence, problem, tags FROM memory_artifacts
25
- ${p}
25
+ ${d}
26
26
  ORDER BY created_at DESC LIMIT ?
27
- `).all(i);if(u.length>0){const g=u.map(f=>[`mem:${f.id}`,f.kind,`${Math.round(f.confidence*100)}%`,f.problem.slice(0,60),f.tags?.slice(0,30)??""]);console.log(D(["ID","Kind","Conf","Preview","Tags"],g))}else console.log(d.dim(" No memories yet."))}if(t==="all"||t==="truths"){k("Ground Truths");const u=s.prepare(`
27
+ `).all(i);if(u.length>0){const g=u.map(f=>[`mem:${f.id}`,f.kind,`${Math.round(f.confidence*100)}%`,f.problem.slice(0,60),f.tags?.slice(0,30)??""]);console.log(D(["ID","Kind","Conf","Preview","Tags"],g))}else console.log(p.dim(" No memories yet."))}if(e==="all"||e==="truths"){k("Ground Truths");const u=s.prepare(`
28
28
  SELECT id, category, key, value FROM ground_truths
29
29
  WHERE is_current = 1 ${a}
30
30
  ORDER BY created_at DESC LIMIT ?
31
- `).all(i);if(u.length>0){const g=u.map(f=>[`truth:${f.id}`,f.category,f.key.slice(0,30),f.value.slice(0,60)]);console.log(D(["ID","Category","Key","Value"],g))}else console.log(d.dim(" No ground truths yet."))}if(t==="all"||t==="quests"){k("Quests");const u=s.prepare(`
31
+ `).all(i);if(u.length>0){const g=u.map(f=>[`truth:${f.id}`,f.category,f.key.slice(0,30),f.value.slice(0,60)]);console.log(D(["ID","Category","Key","Value"],g))}else console.log(p.dim(" No ground truths yet."))}if(e==="all"||e==="quests"){k("Quests");const u=s.prepare(`
32
32
  SELECT id, priority, title, status FROM quests
33
- ${p}
33
+ ${d}
34
34
  ORDER BY created_at DESC LIMIT ?
35
- `).all(i);if(u.length>0){const g=u.map(f=>[`quest:${f.id}`,f.priority,f.title.slice(0,60),f.status]);console.log(D(["ID","Priority","Title","Status"],g))}else console.log(d.dim(" No quests yet."))}e.close()}async function ze(l){const{positional:c}=C(l),t=c[0];t||(m("Usage: wyrm show <typed-id> (e.g. mem:41, quest:12, truth:7, data:5, session:3)"),process.exit(1));const[i,r]=t.split(":"),e=parseInt(r??"",10);(!i||isNaN(e))&&(m(`Invalid typed ID: ${t}. Format: type:number (e.g. mem:41)`),process.exit(1));const s=j(),o=s.getDatabase();switch(k(`${t}`),i){case"mem":{const n=o.prepare("SELECT * FROM memory_artifacts WHERE id = ?").get(e);if(!n){m(`Memory artifact ${e} not found`);break}console.log(d.bold("Kind: ")+n.kind),console.log(d.bold("Confidence:")+` ${Math.round(n.confidence*100)}%`),console.log(d.bold("Problem: ")+`
36
- `+n.problem),n.validated_fix&&console.log(d.bold("Solution: ")+`
37
- `+n.validated_fix),n.why_it_worked&&console.log(d.bold("Why: ")+`
38
- `+n.why_it_worked),n.tags&&console.log(d.bold("Tags: ")+n.tags),console.log(d.bold("Created: ")+n.created_at);break}case"quest":{const n=o.prepare("SELECT * FROM quests WHERE id = ?").get(e);if(!n){m(`Quest ${e} not found`);break}console.log(d.bold("Title: ")+n.title),console.log(d.bold("Priority: ")+n.priority),console.log(d.bold("Status: ")+n.status),n.description&&console.log(d.bold("Desc: ")+`
39
- `+n.description),n.tags&&console.log(d.bold("Tags: ")+n.tags),console.log(d.bold("Created: ")+n.created_at);break}case"truth":{const n=o.prepare("SELECT * FROM ground_truths WHERE id = ?").get(e);if(!n){m(`Ground truth ${e} not found`);break}console.log(d.bold("Category: ")+n.category),console.log(d.bold("Key: ")+n.key),console.log(d.bold("Value: ")+`
40
- `+n.value),n.rationale&&console.log(d.bold("Rationale:")+`
41
- `+n.rationale),console.log(d.bold("Active: ")+(n.is_current?"Yes":"No (superseded)")),console.log(d.bold("Created: ")+n.created_at);break}case"data":{const n=o.prepare("SELECT * FROM data_lake WHERE id = ?").get(e);if(!n){m(`Data point ${e} not found`);break}console.log(d.bold("Category: ")+n.category),console.log(d.bold("Key: ")+n.key),console.log(d.bold("Value: ")+`
42
- `+String(n.value).slice(0,500)),console.log(d.bold("Created: ")+n.created_at);break}case"session":{const n=o.prepare("SELECT * FROM sessions WHERE id = ?").get(e);if(!n){m(`Session ${e} not found`);break}console.log(d.bold("Date: ")+n.date),console.log(d.bold("Objectives: ")+`
43
- `+n.objectives),n.completed&&console.log(d.bold("Completed: ")+`
44
- `+n.completed),n.notes&&console.log(d.bold("Notes: ")+`
45
- `+n.notes);break}default:m(`Unknown type prefix: ${i}. Use mem|quest|truth|data|session`)}s.close()}async function Qe(l){const{positional:c,flags:t}=C(l),i=c[0];i||(m('Usage: wyrm capture "<content>" [--project <name>] [--mode auto|quest|truth|memory]'),process.exit(1));const r=t.project,e=t.mode,s=j(),o=s.getDatabase();let n=null;if(r){const S=x(s,r);S||(m(`Project not found: ${r}`),s.close(),process.exit(1)),n=S.id}let p=Fe(i);e&&e!=="auto"&&(p={type:e,subtype:{quest:"quest",truth:"decision",memory:"pattern"}[e]??e,confidence:100,reasoning:`Mode override: ${e}`});const{type:a,subtype:u,confidence:g,reasoning:f}=p;(a==="quest"||a==="truth"||a==="memory")&&n===null&&(m('A --project is required to capture. Use: wyrm capture "<text>" --project <name>'),s.close(),process.exit(1));let y=0,h="",w=!1;const $=new G(o),E=new pe(o);if(me(E,new ue(o)),a==="quest")y=s.addQuest(n,i.slice(0,200),"","medium").id,h="quest";else if(a==="truth")e!=="truth"&&g<100?(y=$.add(n,{kind:"pattern",problem:i,confidence:g/100,needsReview:1}).id,h="mem",w=!0):(y=E.set(n,{category:"decision",key:i.slice(0,60),value:i}).id,h="truth");else{const S=g>=75;y=$.add(n,{kind:u,problem:i,confidence:g/100,needsReview:S?0:1}).id,h="mem",S||(w=!0)}const{recordWrite:_}=await import("./receipts.js"),R=_(o,{tool:"wyrm_capture",outcome:w?"queued":"stored",refTable:h,refId:y,reason:w?`needs_review (confidence ${g}%) \u2014 review to activate`:void 0,source:"cli",projectId:n??void 0});s.close(),b(`Captured as ${a}: ${u}`),console.log(`${d.dim("Confidence:")} ${g}% | ${d.dim(f)}`),console.log(`${d.dim("ID:")} ${h}:${y}`),w&&console.log(`${Y.warning} Stored for review \u2014 run ${d.cyan("wyrm review")} to activate`),console.log(`receipt: ${R.outcome} ${R.ref??""}${R.reason?` \u2014 ${R.reason}`:""}`)}async function Xe(l){const{flags:c}=C(l);if(c.writes!==!0){m("Usage: wyrm digest --writes [--since <days>] [--json]"),process.exitCode=1;return}const t=Number(c.since)>=1?Number(c.since):1,{buildWriteDigest:i}=await import("./receipts.js"),r=j();try{const e=i(r.getDatabase(),t);if(c.json===!0){process.stdout.write(JSON.stringify(e)+`
46
- `);return}if(k(`Write digest \u2014 last ${e.since}`),console.log(`${d.bold("Total writes:")} ${e.total} ${d.bold("Outcomes:")} ${Object.entries(e.byOutcome).map(([s,o])=>`${s} ${o}`).join(" \xB7 ")||"none"}`),console.log(`${d.bold("Review queue:")} ${e.reviewQueueDepth} awaiting review${e.reviewQueueDepth>0?d.dim(" (invisible to recall until approved)"):""}`),e.byTool.length>0&&console.log(D(["Tool","Outcome","N"],e.byTool.map(s=>[s.tool,s.outcome,String(s.n)]))),e.reasons.length>0){console.log(d.bold("Non-stored reasons:"));for(const s of e.reasons)console.log(` ${s.outcome} \xD7${s.n} \u2014 ${s.reason.slice(0,110)}`)}e.bySource.length>0&&console.log(d.bold("By source: ")+e.bySource.map(s=>`${s.source} ${s.n}`).join(" \xB7 "))}finally{r.close()}}async function Ze(l){const{flags:c}=C(l),t=I(c.session,0),i=c.path,r=c.project,e=I(c["max-chars"],6e3),s=c.quiet===!0,o=console.log;console.log=()=>{};const n=j();try{const p=new Ae(n.getDatabase());let a=t;if(a<=0){let f=i?n.getProject(i):void 0;if(!f&&r&&(f=x(n,r)??void 0),f||(f=n.getProject(process.cwd())),!f){s||process.stderr.write(`wyrm rehydrate: no Wyrm project for this directory, nothing to restore.
47
- `);return}const y=n.getRecentSessions(f.id,1);if(y.length===0){s||process.stderr.write(`wyrm rehydrate: project "${f.name}" has no prior sessions yet.
48
- `);return}a=y[0].id}const u=p.rehydrate(a);if(!u){s||process.stderr.write(`wyrm rehydrate: session ${a} not found.
49
- `);return}let g=u.briefing_markdown;e>0&&g.length>e&&(g=g.slice(0,e)+`
50
-
51
- _... brief truncated at ${e} chars, run \`wyrm show session:${u.session_id}\` for the full record._`),process.stdout.write(g+`
52
- `)}finally{n.close(),console.log=o}}async function et(l){const{flags:c}=C(l),{runMetabolize:t}=await import("./metabolize.js"),i=j(),r=console.log;try{let e;if(typeof c.project=="string"){const o=x(i,c.project);if(!o){m(`Project not found: ${c.project}`),process.exitCode=1;return}e=o.id}const s=t(i.getDatabase(),{projectId:e,dryRun:c["dry-run"]===!0,near:c["no-near"]===!0?!1:void 0});console.log(`metabolize${s.dryRun?" (dry-run)":""} \u2014 scanned ${s.scanned} \xB7 exact-merged ${s.exactMerged} (superseded, not deleted) \xB7 near-dup candidates queued ${s.nearCandidatesQueued} \xB7 decayed ${s.decayed}`)}finally{i.close(),console.log=r}}async function tt(l){const{positional:c,flags:t}=C(l),i=c[0]??"stats",{backfillEntities:r}=await import("./entity-populate.js"),e=j(),s=console.log;try{const o=e.getDatabase();let n;if(typeof t.project=="string"){const p=x(e,t.project);if(!p){m(`Project not found: ${t.project}`),process.exitCode=1;return}n=p.id}if(i==="stats"){const p=a=>{if(n!=null){const g=a==="entities"?"SELECT COUNT(*) AS n FROM entities WHERE project_id = ?":"SELECT COUNT(*) AS n FROM relationships WHERE project_id = ?";return o.prepare(g).get(n).n}const u=a==="entities"?"SELECT COUNT(*) AS n FROM entities":"SELECT COUNT(*) AS n FROM relationships";return o.prepare(u).get().n};console.log(`entities: ${p("entities")} \xB7 relationships: ${p("relationships")}${n!=null?` (project ${n})`:" (all projects)"}`);return}if(i==="backfill"){const p=n!=null?[{id:n}]:o.prepare("SELECT id FROM projects").all();let a=0,u=0,g=0;for(const f of p){const y=r(o,f.id,{limit:I(t.limit,5e3)});a+=y.created,u+=y.linked,g+=y.artifactsScanned}console.log(`entities backfill \u2014 scanned ${g} artifact(s) across ${p.length} project(s): +${a} entities, +${u} co-occurrence edges`);return}m("Usage: wyrm entities <backfill|stats> [--project <name>] [--limit N]"),process.exitCode=1}finally{e.close(),console.log=s}}async function ot(l){const{positional:c,flags:t}=C(l),i=c[0],r=await import("./bridge.js");if(i==="init"){const{existsSync:e,writeFileSync:s}=await import("node:fs"),o=r.configPath();if(e(o)&&t.force!==!0){m(`${o} already exists (use --force to overwrite).`),process.exitCode=1;return}const n=(await import("node:os")).homedir(),p={sources:[{type:"remember",name:"remember",dir:`${n}/.remember`,projectPath:n,windowDays:7}]};s(o,JSON.stringify(p,null,2)),b(`Wrote ${o} (1 source: remember). Add render/reverse sources per project as needed.`);return}if(i==="status"){const e=r.loadConfig();if(!e){m(`No bridge config at ${r.configPath()} \u2014 run \`wyrm bridge init\`.`),process.exitCode=1;return}const s=r.loadState();k("Bridge sources");for(const o of e.sources){const n=Object.keys(s).filter(a=>a.startsWith(o.name+":")),p=n.reduce((a,u)=>a+s[u].length,0);console.log(` ${o.name} [${o.type}] \u2192 ${"projectPath"in o?o.projectPath:""}${o.type==="remember"?` (${n.length} files tracked, ${p} sections bridged)`:""}`)}return}if(i==="run"){const e=r.loadConfig();if(!e){m(`No bridge config at ${r.configPath()} \u2014 run \`wyrm bridge init\`.`),process.exitCode=1;return}const s=j(),o=console.log;try{const n={wyrm_version:M().version??"unknown",compiled_at:new Date().toISOString()},p=await r.runBridge(s.getDatabase(),e,n,{only:typeof t.source=="string"?t.source:void 0,dryRun:t["dry-run"]===!0});for(const a of p)console.log(`bridge:${a.source} [${a.type}]${t["dry-run"]===!0?" (dry-run)":""} \u2014 stored ${a.stored} \xB7 queued ${a.queued} \xB7 dropped ${a.dropped} \xB7 rendered ${a.rendered}${a.note?` \xB7 ${a.note}`:""}`);if(t.watch===!0){const a=Number(t.interval)>=2?Number(t.interval):15;console.log(`bridge watch \u2014 re-rendering on write events (poll ${a}s, Ctrl-C to stop)`),await r.watchBridge(s.getDatabase(),e,()=>({wyrm_version:M().version??"unknown",compiled_at:new Date().toISOString()}),{intervalSec:a,onCycle:u=>{for(const g of u)console.log(`bridge:${g.source} re-rendered (${g.rendered} file(s), ${g.queued} edit(s) harvested)`)}})}return}finally{s.close(),console.log=o}}m("Usage: wyrm bridge <init|status|run> [--source <name>] [--dry-run] [--force]"),process.exitCode=1}async function st(l){const{positional:c,flags:t}=C(l);if(c[0]!=="log"){m("Usage: wyrm session log [--path <dir>] [--run <id>] [--objectives <t>] [--completed <t>] [--issues <t>] [--commits <t>] [--notes <t>]"),process.exitCode=1;return}const r=a=>typeof t[a]=="string"?t[a]:void 0,e=r("path"),s=r("project"),o=r("run")?.slice(0,64),n=console.log;console.log=()=>{};const p=j();try{let a=e?p.getProject(e):void 0;if(!a&&s&&(a=x(p,s)??void 0),a||(a=p.getProject(process.cwd())),!a){process.stderr.write(`wyrm session log: no Wyrm project for this directory, nothing recorded.
53
- `);return}const u={};for(const y of["objectives","completed","issues","commits","notes"]){const h=r(y);h&&(u[y]=h.slice(0,8e3))}const g=p.getDatabase();let f;o&&(f=g.prepare("SELECT id FROM sessions WHERE project_id = ? AND run_id = ? ORDER BY id DESC LIMIT 1").get(a.id,o)?.id),f?p.updateSession(f,u):(f=p.createSession(a.id,u).id,o&&g.prepare("UPDATE sessions SET run_id = ? WHERE id = ?").run(o,f)),process.stdout.write(`session:${f}
54
- `)}finally{p.close(),console.log=n}}async function rt(l){const{positional:c,flags:t}=C(l),i=c[0],r=p=>typeof t[p]=="string"?t[p]:void 0,{AgentPresence:e,processStartTime:s,presenceLiveness:o}=await import("./presence.js"),n=j();try{const p=new e(n.getDatabase());if(i==="announce"){const a=r("agent");if(!a){m("Usage: wyrm presence announce --agent <id> [--kind <kind>] [--project <name>|--path <dir>] [--quest <n>] [--pid <n>|--auto-pid] [--ttl <sec>] [--role <r>]"),process.exitCode=1;return}const u=r("path");let g=u?n.getProject(u):void 0;!g&&r("project")&&(g=x(n,r("project"))??void 0);const f=t["auto-pid"]===!0?process.ppid:Number(r("pid")),y={};let h="";if(Number.isInteger(f)&&f>0){const E=s(f);E?(y.pid=f,y.pid_start=E,h=` \xB7 pid ${f} (start ${E})`):h=` \xB7 pid ${f} unreadable \u2014 TTL fallback`}const w=Number(r("ttl")),$=p.announce({agent_id:a,agent_kind:r("kind")??"cli",project_id:g?.id??null,current_quest_id:r("quest")?Number(r("quest")):null,ttl_seconds:Number.isFinite(w)&&w>0?w:y.pid?86400:300,metadata:Object.keys(y).length?y:void 0,role:r("role")??null});process.stdout.write(`presence:${$.id} ${a}${h}
55
- `);return}if(i==="list"||i==="ls"||i===void 0){const a=n.getDatabase().prepare("SELECT * FROM agent_presence ORDER BY last_heartbeat DESC").all();if(a.length===0){process.stdout.write(`No agents on the board.
35
+ `).all(i);if(u.length>0){const g=u.map(f=>[`quest:${f.id}`,f.priority,f.title.slice(0,60),f.status]);console.log(D(["ID","Priority","Title","Status"],g))}else console.log(p.dim(" No quests yet."))}t.close()}async function ot(l){const{positional:c}=E(l),e=c[0];e||(m("Usage: wyrm show <typed-id> (e.g. mem:41, quest:12, truth:7, data:5, session:3)"),process.exit(1));const[i,n]=e.split(":"),t=parseInt(n??"",10);(!i||isNaN(t))&&(m(`Invalid typed ID: ${e}. Format: type:number (e.g. mem:41)`),process.exit(1));const s=j(),o=s.getDatabase();switch(k(`${e}`),i){case"mem":{const r=o.prepare("SELECT * FROM memory_artifacts WHERE id = ?").get(t);if(!r){m(`Memory artifact ${t} not found`);break}console.log(p.bold("Kind: ")+r.kind),console.log(p.bold("Confidence:")+` ${Math.round(r.confidence*100)}%`),console.log(p.bold("Problem: ")+`
36
+ `+r.problem),r.validated_fix&&console.log(p.bold("Solution: ")+`
37
+ `+r.validated_fix),r.why_it_worked&&console.log(p.bold("Why: ")+`
38
+ `+r.why_it_worked),r.tags&&console.log(p.bold("Tags: ")+r.tags),console.log(p.bold("Created: ")+r.created_at);break}case"quest":{const r=o.prepare("SELECT * FROM quests WHERE id = ?").get(t);if(!r){m(`Quest ${t} not found`);break}console.log(p.bold("Title: ")+r.title),console.log(p.bold("Priority: ")+r.priority),console.log(p.bold("Status: ")+r.status),r.description&&console.log(p.bold("Desc: ")+`
39
+ `+r.description),r.tags&&console.log(p.bold("Tags: ")+r.tags),console.log(p.bold("Created: ")+r.created_at);break}case"truth":{const r=o.prepare("SELECT * FROM ground_truths WHERE id = ?").get(t);if(!r){m(`Ground truth ${t} not found`);break}console.log(p.bold("Category: ")+r.category),console.log(p.bold("Key: ")+r.key),console.log(p.bold("Value: ")+`
40
+ `+r.value),r.rationale&&console.log(p.bold("Rationale:")+`
41
+ `+r.rationale),console.log(p.bold("Active: ")+(r.is_current?"Yes":"No (superseded)")),console.log(p.bold("Created: ")+r.created_at);break}case"data":{const r=o.prepare("SELECT * FROM data_lake WHERE id = ?").get(t);if(!r){m(`Data point ${t} not found`);break}console.log(p.bold("Category: ")+r.category),console.log(p.bold("Key: ")+r.key),console.log(p.bold("Value: ")+`
42
+ `+String(r.value).slice(0,500)),console.log(p.bold("Created: ")+r.created_at);break}case"session":{const r=o.prepare("SELECT * FROM sessions WHERE id = ?").get(t);if(!r){m(`Session ${t} not found`);break}console.log(p.bold("Date: ")+r.date),console.log(p.bold("Objectives: ")+`
43
+ `+r.objectives),r.completed&&console.log(p.bold("Completed: ")+`
44
+ `+r.completed),r.notes&&console.log(p.bold("Notes: ")+`
45
+ `+r.notes);break}default:m(`Unknown type prefix: ${i}. Use mem|quest|truth|data|session`)}s.close()}async function st(l){const{positional:c,flags:e}=E(l),i=c[0];i||(m('Usage: wyrm capture "<content>" [--project <name>] [--mode auto|quest|truth|memory]'),process.exit(1));const n=e.project,t=e.mode,s=j(),o=s.getDatabase();let r=null;if(n){const _=x(s,n);_||(m(`Project not found: ${n}`),s.close(),process.exit(1)),r=_.id}let d=He(i);t&&t!=="auto"&&(d={type:t,subtype:{quest:"quest",truth:"decision",memory:"pattern"}[t]??t,confidence:100,reasoning:`Mode override: ${t}`});const{type:a,subtype:u,confidence:g,reasoning:f}=d;(a==="quest"||a==="truth"||a==="memory")&&r===null&&(m('A --project is required to capture. Use: wyrm capture "<text>" --project <name>'),s.close(),process.exit(1));let y=0,h="",w=!1;const $=new G(o),C=new ue(o);if(fe(C,new me(o)),a==="quest")y=s.addQuest(r,i.slice(0,200),"","medium").id,h="quest";else if(a==="truth")t!=="truth"&&g<100?(y=$.add(r,{kind:"pattern",problem:i,confidence:g/100,needsReview:1}).id,h="mem",w=!0):(y=C.set(r,{category:"decision",key:i.slice(0,60),value:i}).id,h="truth");else{const _=g>=75;y=$.add(r,{kind:u,problem:i,confidence:g/100,needsReview:_?0:1}).id,h="mem",_||(w=!0)}const{recordWrite:S}=await import("./receipts.js"),R=S(o,{tool:"wyrm_capture",outcome:w?"queued":"stored",refTable:h,refId:y,reason:w?`needs_review (confidence ${g}%) \u2014 review to activate`:void 0,source:"cli",projectId:r??void 0});s.close(),b(`Captured as ${a}: ${u}`),console.log(`${p.dim("Confidence:")} ${g}% | ${p.dim(f)}`),console.log(`${p.dim("ID:")} ${h}:${y}`),w&&console.log(`${H.warning} Stored for review \u2014 run ${p.cyan("wyrm review")} to activate`),console.log(`receipt: ${R.outcome} ${R.ref??""}${R.reason?` \u2014 ${R.reason}`:""}`)}async function rt(l){const{flags:c}=E(l);if(c.writes!==!0){m("Usage: wyrm digest --writes [--since <days>] [--json]"),process.exitCode=1;return}const e=Number(c.since)>=1?Number(c.since):1,{buildWriteDigest:i}=await import("./receipts.js"),n=j();try{const t=i(n.getDatabase(),e);if(c.json===!0){process.stdout.write(JSON.stringify(t)+`
46
+ `);return}if(k(`Write digest \u2014 last ${t.since}`),console.log(`${p.bold("Total writes:")} ${t.total} ${p.bold("Outcomes:")} ${Object.entries(t.byOutcome).map(([s,o])=>`${s} ${o}`).join(" \xB7 ")||"none"}`),console.log(`${p.bold("Review queue:")} ${t.reviewQueueDepth} awaiting review${t.reviewQueueDepth>0?p.dim(" (invisible to recall until approved)"):""}`),t.byTool.length>0&&console.log(D(["Tool","Outcome","N"],t.byTool.map(s=>[s.tool,s.outcome,String(s.n)]))),t.reasons.length>0){console.log(p.bold("Non-stored reasons:"));for(const s of t.reasons)console.log(` ${s.outcome} \xD7${s.n} \u2014 ${s.reason.slice(0,110)}`)}t.bySource.length>0&&console.log(p.bold("By source: ")+t.bySource.map(s=>`${s.source} ${s.n}`).join(" \xB7 "))}finally{n.close()}}async function nt(l){const{flags:c}=E(l),e=I(c.session,0),i=c.path,n=c.project,t=I(c["max-chars"],6e3),s=c.quiet===!0,o=console.log;console.log=()=>{};const r=j();try{const d=new Ue(r.getDatabase());let a=e;if(a<=0){let f=i?r.getProject(i):void 0;if(!f&&n&&(f=x(r,n)??void 0),f||(f=r.getProject(process.cwd())),!f){s||process.stderr.write(`wyrm rehydrate: no Wyrm project for this directory, nothing to restore.
47
+ `);return}const y=r.getRecentSessions(f.id,1);if(y.length===0){s||process.stderr.write(`wyrm rehydrate: project "${f.name}" has no prior sessions yet.
48
+ `);return}a=y[0].id}const u=d.rehydrate(a);if(!u){s||process.stderr.write(`wyrm rehydrate: session ${a} not found.
49
+ `);return}let g=u.briefing_markdown;t>0&&g.length>t&&(g=g.slice(0,t)+`
50
+
51
+ _... brief truncated at ${t} chars, run \`wyrm show session:${u.session_id}\` for the full record._`),process.stdout.write(g+`
52
+ `)}finally{r.close(),console.log=o}}async function it(l){const{flags:c}=E(l),{runMetabolize:e}=await import("./metabolize.js"),i=j(),n=console.log;try{let t;if(typeof c.project=="string"){const o=x(i,c.project);if(!o){m(`Project not found: ${c.project}`),process.exitCode=1;return}t=o.id}const s=e(i.getDatabase(),{projectId:t,dryRun:c["dry-run"]===!0,near:c["no-near"]===!0?!1:void 0});console.log(`metabolize${s.dryRun?" (dry-run)":""} \u2014 scanned ${s.scanned} \xB7 exact-merged ${s.exactMerged} (superseded, not deleted) \xB7 near-dup candidates queued ${s.nearCandidatesQueued} \xB7 decayed ${s.decayed}`)}finally{i.close(),console.log=n}}async function at(l){const{positional:c,flags:e}=E(l),i=c[0]??"stats",{backfillEntities:n}=await import("./entity-populate.js"),t=j(),s=console.log;try{const o=t.getDatabase();let r;if(typeof e.project=="string"){const d=x(t,e.project);if(!d){m(`Project not found: ${e.project}`),process.exitCode=1;return}r=d.id}if(i==="stats"){const d=a=>{if(r!=null){const g=a==="entities"?"SELECT COUNT(*) AS n FROM entities WHERE project_id = ?":"SELECT COUNT(*) AS n FROM relationships WHERE project_id = ?";return o.prepare(g).get(r).n}const u=a==="entities"?"SELECT COUNT(*) AS n FROM entities":"SELECT COUNT(*) AS n FROM relationships";return o.prepare(u).get().n};console.log(`entities: ${d("entities")} \xB7 relationships: ${d("relationships")}${r!=null?` (project ${r})`:" (all projects)"}`);return}if(i==="backfill"){const d=r!=null?[{id:r}]:o.prepare("SELECT id FROM projects").all();let a=0,u=0,g=0;for(const f of d){const y=n(o,f.id,{limit:I(e.limit,5e3)});a+=y.created,u+=y.linked,g+=y.artifactsScanned}console.log(`entities backfill \u2014 scanned ${g} artifact(s) across ${d.length} project(s): +${a} entities, +${u} co-occurrence edges`);return}m("Usage: wyrm entities <backfill|stats> [--project <name>] [--limit N]"),process.exitCode=1}finally{t.close(),console.log=s}}async function ct(l){const{positional:c,flags:e}=E(l),i=c[0],n=await import("./bridge.js");if(i==="init"){const{existsSync:t,writeFileSync:s}=await import("node:fs"),o=n.configPath();if(t(o)&&e.force!==!0){m(`${o} already exists (use --force to overwrite).`),process.exitCode=1;return}const r=(await import("node:os")).homedir(),d={sources:[{type:"remember",name:"remember",dir:`${r}/.remember`,projectPath:r,windowDays:7}]};s(o,JSON.stringify(d,null,2)),b(`Wrote ${o} (1 source: remember). Add render/reverse sources per project as needed.`);return}if(i==="status"){const t=n.loadConfig();if(!t){m(`No bridge config at ${n.configPath()} \u2014 run \`wyrm bridge init\`.`),process.exitCode=1;return}const s=n.loadState();k("Bridge sources");for(const o of t.sources){const r=Object.keys(s).filter(a=>a.startsWith(o.name+":")),d=r.reduce((a,u)=>a+s[u].length,0);console.log(` ${o.name} [${o.type}] \u2192 ${"projectPath"in o?o.projectPath:""}${o.type==="remember"?` (${r.length} files tracked, ${d} sections bridged)`:""}`)}return}if(i==="run"){const t=n.loadConfig();if(!t){m(`No bridge config at ${n.configPath()} \u2014 run \`wyrm bridge init\`.`),process.exitCode=1;return}const s=j(),o=console.log;try{const r={wyrm_version:M().version??"unknown",compiled_at:new Date().toISOString()},d=await n.runBridge(s.getDatabase(),t,r,{only:typeof e.source=="string"?e.source:void 0,dryRun:e["dry-run"]===!0});for(const a of d)console.log(`bridge:${a.source} [${a.type}]${e["dry-run"]===!0?" (dry-run)":""} \u2014 stored ${a.stored} \xB7 queued ${a.queued} \xB7 dropped ${a.dropped} \xB7 rendered ${a.rendered}${a.note?` \xB7 ${a.note}`:""}`);if(e.watch===!0){const a=Number(e.interval)>=2?Number(e.interval):15;console.log(`bridge watch \u2014 re-rendering on write events (poll ${a}s, Ctrl-C to stop)`),await n.watchBridge(s.getDatabase(),t,()=>({wyrm_version:M().version??"unknown",compiled_at:new Date().toISOString()}),{intervalSec:a,onCycle:u=>{for(const g of u)console.log(`bridge:${g.source} re-rendered (${g.rendered} file(s), ${g.queued} edit(s) harvested)`)}})}return}finally{s.close(),console.log=o}}m("Usage: wyrm bridge <init|status|run> [--source <name>] [--dry-run] [--force]"),process.exitCode=1}async function lt(l){const{positional:c,flags:e}=E(l);if(c[0]!=="log"){m("Usage: wyrm session log [--path <dir>] [--run <id>] [--objectives <t>] [--completed <t>] [--issues <t>] [--commits <t>] [--notes <t>]"),process.exitCode=1;return}const n=a=>typeof e[a]=="string"?e[a]:void 0,t=n("path"),s=n("project"),o=n("run")?.slice(0,64),r=console.log;console.log=()=>{};const d=j();try{let a=t?d.getProject(t):void 0;if(!a&&s&&(a=x(d,s)??void 0),a||(a=d.getProject(process.cwd())),!a){process.stderr.write(`wyrm session log: no Wyrm project for this directory, nothing recorded.
53
+ `);return}const u={};for(const y of["objectives","completed","issues","commits","notes"]){const h=n(y);h&&(u[y]=h.slice(0,8e3))}const g=d.getDatabase();let f;o&&(f=g.prepare("SELECT id FROM sessions WHERE project_id = ? AND run_id = ? ORDER BY id DESC LIMIT 1").get(a.id,o)?.id),f?d.updateSession(f,u):(f=d.createSession(a.id,u).id,o&&g.prepare("UPDATE sessions SET run_id = ? WHERE id = ?").run(o,f)),process.stdout.write(`session:${f}
54
+ `)}finally{d.close(),console.log=r}}async function dt(l){const{positional:c,flags:e}=E(l),i=c[0],n=d=>typeof e[d]=="string"?e[d]:void 0,{AgentPresence:t,processStartTime:s,presenceLiveness:o}=await import("./presence.js"),r=j();try{const d=new t(r.getDatabase());if(i==="announce"){const a=n("agent");if(!a){m("Usage: wyrm presence announce --agent <id> [--kind <kind>] [--project <name>|--path <dir>] [--quest <n>] [--pid <n>|--auto-pid] [--ttl <sec>] [--role <r>]"),process.exitCode=1;return}const u=n("path");let g=u?r.getProject(u):void 0;!g&&n("project")&&(g=x(r,n("project"))??void 0);const f=e["auto-pid"]===!0?process.ppid:Number(n("pid")),y={};let h="";if(Number.isInteger(f)&&f>0){const C=s(f);C?(y.pid=f,y.pid_start=C,h=` \xB7 pid ${f} (start ${C})`):h=` \xB7 pid ${f} unreadable \u2014 TTL fallback`}const w=Number(n("ttl")),$=d.announce({agent_id:a,agent_kind:n("kind")??"cli",project_id:g?.id??null,current_quest_id:n("quest")?Number(n("quest")):null,ttl_seconds:Number.isFinite(w)&&w>0?w:y.pid?86400:300,metadata:Object.keys(y).length?y:void 0,role:n("role")??null});process.stdout.write(`presence:${$.id} ${a}${h}
55
+ `);return}if(i==="list"||i==="ls"||i===void 0){const a=r.getDatabase().prepare("SELECT * FROM agent_presence ORDER BY last_heartbeat DESC").all();if(a.length===0){process.stdout.write(`No agents on the board.
56
56
  `);return}const u={"alive-pid":"ALIVE (pid)","dead-pid":"DEAD (pid gone)","alive-ttl":"alive (ttl)",stale:"stale"};for(const g of a){const f=o(g);process.stdout.write(`${g.agent_id} [${g.agent_kind}] ${u[f]} hb ${g.last_heartbeat}${g.role?` role ${g.role}`:""}${g.current_quest_id?` quest #${g.current_quest_id}`:""}
57
- `)}return}if(i==="release"){const a=r("agent");if(!a){m("Usage: wyrm presence release --agent <id>"),process.exitCode=1;return}const u=p.release(a);process.stdout.write(u?`released ${a}
57
+ `)}return}if(i==="release"){const a=n("agent");if(!a){m("Usage: wyrm presence release --agent <id>"),process.exitCode=1;return}const u=d.release(a);process.stdout.write(u?`released ${a}
58
58
  `:`no presence row for ${a}
59
- `);return}m("Usage: wyrm presence <announce|list|release> [options]"),process.exitCode=1}finally{n.close()}}async function nt(l){const{flags:c}=C(l),t=c.path,i=c.project,r=c.out,e=c.brief===!0,s=c.force===!0,o=c.quiet===!0,n=(typeof c.client=="string"?c.client:"").split(",").map(f=>f.trim().toLowerCase()).filter(Boolean);let p=n.filter(f=>ae.includes(f));const a=n.filter(f=>!ae.includes(f));a.length>0&&(m(`Unknown --client value(s): ${a.join(", ")} (valid: ${ae.join("|")})`),process.exit(1));const u=console.log;e&&(console.log=()=>{});const g=j();try{let f=t?g.getProject(t):void 0;!f&&i&&(f=x(g,i)??void 0),f||(f=g.getProject(process.cwd())),f||(console.log=u,m("wyrm render: no Wyrm project for this directory (use --path or --project)."),process.exit(1));const y=fe(g.getDatabase()),h={wyrm_version:M().version??"unknown",compiled_at:new Date().toISOString()};if(e){const _=ie(y,f,h);console.log=u,process.stdout.write(_.sessionBrief+`
60
- `);return}const w=r??f.path;p.length===0&&n.length===0&&(p=We(Ue.filter(_=>q(F(w,_)))));{const _=await import("./reverse-bridge.js"),R=ie(y,f,h),S={"MEMORY.md":R.memoryMd};for(const L of p){const W=ge(L,R.model,h);S[W.relPath]=W.block}try{const L=_.makeBridgeDeps(g.getDatabase()),W=await _.sweepProject(L,{id:f.id,path:f.path},S,{rootDir:w});W.added>0&&!o&&console.log(` ${Y.warning} harvested ${W.added} human edit(s) \u2192 review queue before overwrite`)}catch{}}const{plan:$,writes:E}=Le(y,f,h,{rootDir:w,clients:p,force:s});if(!o){b(`Rendered ${f.name} memory (${$.model.truths.length} truths, ${$.model.failures.length} failures, ${$.model.quests.length} quests, ${$.model.artifacts.length} patterns) to ${w}`);for(const _ of E){const R=_.action==="created"?Y.success:_.action==="updated"?Y.info:Y.warning;console.log(` ${R} ${_.action.padEnd(7)} ${_.path}${_.reason?` (${_.reason})`:""}`)}}}finally{g.close(),console.log=u}}async function it(l){const{flags:c}=C(l),t=c.path,i=c.project,r=c.root,e=c["dry-run"]===!0||c.dry===!0,s=(typeof c.client=="string"?c.client:"").split(",").map(u=>u.trim().toLowerCase()).filter(Boolean),o=["claude","cursor","copilot","agents"],n=s.filter(u=>o.includes(u)),p=await import("./reverse-bridge.js"),a=j();try{let u=t?a.getProject(t):void 0;if(!u&&i&&(u=x(a,i)??void 0),u||(u=a.getProject(process.cwd())),!u){m("wyrm reverse-bridge: no Wyrm project for this directory (use --path or --project)."),process.exitCode=1;return}const g=fe(a.getDatabase()),f={wyrm_version:M().version??"unknown",compiled_at:new Date().toISOString()},y=ie(g,u,f),h={"MEMORY.md":y.memoryMd};for(const E of n){const _=ge(E,y.model,f);h[_.relPath]=_.block}const w=p.makeBridgeDeps(a.getDatabase()),$=await p.sweepProject(w,{id:u.id,path:u.path},h,{dryRun:e,rootDir:r});k(`Reverse bridge ${e?"(dry run) ":""}\u2014 ${$.added} candidate(s) queued, ${$.skipped} already present (${$.filesWithEdits}/${$.filesScanned} file(s) with edits)`);for(const E of $.sample)console.log(` ${Y.bullet} ${E}`);!e&&$.added>0&&b("Review with: wyrm review")}finally{a.close()}}async function at(l){const c=C(l);if(typeof c.flags.from=="string"){await ct(c);return}const{positional:t,flags:i}=C(l.slice(1)),r=l[0],e=i.project;if(r==="git"){const s=I(i.last,20),o=j(),n=o.getDatabase();let p=null;if(e){const h=x(o,e);h||(m(`Project not found: ${e}`),o.close(),process.exit(1)),p=h.id}else{const h=n.prepare("SELECT p.* FROM projects p JOIN sessions s ON s.project_id = p.id ORDER BY s.created_at DESC LIMIT 1").get();h&&(p=h.id)}p||(m("No project found. Use --project <name>"),o.close(),process.exit(1));const a=z("git",["log","--pretty=format:%H%x1f%s%x1f%an%x1f%ai",`-${s}`],{cwd:process.cwd(),encoding:"utf-8",timeout:1e4,shell:!1});(a.error||a.status!==0)&&(m("git log failed. Make sure you are in a git repository."),o.close(),process.exit(1));const u=a.stdout.split(`
61
- `).filter(h=>h.trim()),g=new G(n);let f=0,y=0;for(const h of u){const[,w,$,E]=h.split(""),_=w??"";if(/^Merge /i.test(_)||/^(chore|bump|release|version)/i.test(_)){y++;continue}let R="pattern";/^fix(\(.+\))?:/i.test(_)?R="lesson":/^refactor(\(.+\))?:/i.test(_)&&(R="heuristic");const S=_.split(":")[0]??"commit";g.add(p,{kind:R,problem:_,whyItWorked:`Committed by ${$??"unknown"} on ${E??"unknown"}`,tags:["git","commit",S.toLowerCase()],confidence:.6,needsReview:1}),f++}o.close(),b(`Imported ${f} commits (${y} skipped). Run ${d.cyan("wyrm review")} to activate.`)}else if(r==="rules"){const s=t[0],o=i.format??"plain";s||(m("Usage: wyrm import rules <path> [--project <name>] [--format cursorrules|copilot|plain]"),process.exit(1)),q(s)||(m(`File not found: ${s}`),process.exit(1));const n=V(s,"utf-8"),p=j(),a=p.getDatabase();let u=null;if(e){const S=x(p,e);S||(m(`Project not found: ${e}`),p.close(),process.exit(1)),u=S.id}else{const S=a.prepare("SELECT p.* FROM projects p JOIN sessions s ON s.project_id = p.id ORDER BY s.created_at DESC LIMIT 1").get();S&&(u=S.id)}u||(m("No project found. Use --project <name>"),p.close(),process.exit(1));const g=s.split("/").pop()??"rules",f=["imported",o,g],y=n.split(/\n(?=#)/),h=n.split(/\n\n+/),w=(y.length>=h.length?y:h).map(S=>S.trim()).filter(S=>S.length>=15),$=new G(a),E=new pe(a);me(E,new ue(a));let _=0,R=0;for(const S of w)/\b(always|never|must|use|don't|avoid|prefer)\b/i.test(S)?(E.set(u,{category:"constraint",key:S.slice(0,50).replace(/\n/g," "),value:S,source:g}),_++):($.add(u,{kind:"heuristic",problem:S,tags:f,confidence:.7,needsReview:1}),R++);p.close(),b(`Imported ${_} ground truths + ${R} artifacts (pending review).`)}else m("Usage: wyrm import git [--project <name>] [--last N]"),m(" wyrm import rules <path> [--project <name>] [--format cursorrules|copilot|plain]"),m(` wyrm import --from ${ye.join("|")} <file.json> [--project <name>]`),process.exit(1)}async function ct(l){const c=l.flags.from,t=l.positional[0],i=l.flags.project,r=`Usage: wyrm import --from ${ye.join("|")} <file.json> [--project <name>]`;t||(m(r),process.exit(1)),q(t)||(m(`File not found: ${t}`),process.exit(1));let e;try{e=JSON.parse(V(t,"utf-8"))}catch(n){m(`Could not parse JSON from ${t}: ${n.message}`),process.exit(1)}let s;try{s=qe(c,e)}catch(n){m(n.message),process.exit(1)}const o=j();try{let n=i?x(o,i):null;if(n||(n=o.getProject(process.cwd())??null),!n){m(i?`Project not found: ${i}`:"wyrm import: no Wyrm project for this directory (pass --project <name>)."),process.exitCode=1;return}if(s.length===0){m(`No importable memories found in ${t} for source "${c}".`);return}const p=o.getDatabase(),a=new G(p);try{const{createVectorStore:g}=await import("./vectors.js"),f=process.env.WYRM_VECTOR_PROVIDER??"auto";a.setVectorStore(g({provider:f},p))}catch{}let u=0;for(const g of s)a.add(n.id,{kind:"lesson",problem:g.text,tags:g.tags,constraints:g.metadata?JSON.stringify(g.metadata):void 0,whyItWorked:`Imported from ${g.source}`,outcome:"neutral",confidence:.6,needsReview:1}),u++;b(`Imported ${u} ${u===1?"memory":"memories"} from ${c} into ${d.cyan(n.name)} review queue. Run ${d.cyan("wyrm review")} to vet (tagged ${d.dim(`imported_from:${s[0]?.source??c}`)}).`)}finally{o.close()}}async function lt(l){const{flags:c}=C(l),t=c.project,i=j(),r=i.getDatabase();if(k("Wyrm Statistics"),t){const e=x(i,t);e||(m(`Project not found: ${t}`),i.close(),process.exit(1));const s=i.getProjectStats(e.id),o=[["Sessions",String(s.sessions)],["Quests (pending)",String(s.quests.pending)],["Quests (completed)",String(s.quests.completed)],["Data Points",String(s.dataPoints)]];console.log(D(["Metric","Value"],o))}else{const e=i.getStats(),s=[["Projects",String(e.projects)],["Sessions",String(e.sessions)],["Quests",String(e.quests)],["Data Points",String(e.dataPoints)],["DB Size",e.dbSize]],o=r.prepare("SELECT COUNT(*) as n FROM memory_artifacts").get().n,n=r.prepare("SELECT COUNT(*) as n FROM ground_truths WHERE is_current = 1").get().n;s.push(["Memories",String(o)],["Ground Truths",String(n)]),console.log(D(["Metric","Value"],s))}i.close()}async function dt(l){const{flags:c}=C(l),t=c.project,i=j(),r=i.getDatabase();let e=null;if(t){const a=x(i,t);a||(m(`Project not found: ${t}`),i.close(),process.exit(1)),e=a.id}const s=e?`AND project_id = ${e}`:"",o=r.prepare(`
59
+ `);return}m("Usage: wyrm presence <announce|list|release> [options]"),process.exitCode=1}finally{r.close()}}async function pt(l){const{flags:c}=E(l),e=c.path,i=c.project,n=c.out,t=c.brief===!0,s=c.force===!0,o=c.quiet===!0,r=(typeof c.client=="string"?c.client:"").split(",").map(f=>f.trim().toLowerCase()).filter(Boolean);let d=r.filter(f=>ae.includes(f));const a=r.filter(f=>!ae.includes(f));a.length>0&&(m(`Unknown --client value(s): ${a.join(", ")} (valid: ${ae.join("|")})`),process.exit(1));const u=console.log;t&&(console.log=()=>{});const g=j();try{let f=e?g.getProject(e):void 0;!f&&i&&(f=x(g,i)??void 0),f||(f=g.getProject(process.cwd())),f||(console.log=u,m("wyrm render: no Wyrm project for this directory (use --path or --project)."),process.exit(1));const y=ge(g.getDatabase()),h={wyrm_version:M().version??"unknown",compiled_at:new Date().toISOString()};if(t){const S=ie(y,f,h);console.log=u,process.stdout.write(S.sessionBrief+`
60
+ `);return}const w=n??f.path;d.length===0&&r.length===0&&(d=Fe(Ye.filter(S=>L(O(w,S)))));{const S=await import("./reverse-bridge.js"),R=ie(y,f,h),_={"MEMORY.md":R.memoryMd};for(const U of d){const q=ye(U,R.model,h);_[q.relPath]=q.block}try{const U=S.makeBridgeDeps(g.getDatabase()),q=await S.sweepProject(U,{id:f.id,path:f.path},_,{rootDir:w});q.added>0&&!o&&console.log(` ${H.warning} harvested ${q.added} human edit(s) \u2192 review queue before overwrite`)}catch{}}const{plan:$,writes:C}=qe(y,f,h,{rootDir:w,clients:d,force:s});if(!o){b(`Rendered ${f.name} memory (${$.model.truths.length} truths, ${$.model.failures.length} failures, ${$.model.quests.length} quests, ${$.model.artifacts.length} patterns) to ${w}`);for(const S of C){const R=S.action==="created"?H.success:S.action==="updated"?H.info:H.warning;console.log(` ${R} ${S.action.padEnd(7)} ${S.path}${S.reason?` (${S.reason})`:""}`)}}}finally{g.close(),console.log=u}}async function ut(l){const{flags:c}=E(l),e=c.path,i=c.project,n=c.root,t=c["dry-run"]===!0||c.dry===!0,s=(typeof c.client=="string"?c.client:"").split(",").map(u=>u.trim().toLowerCase()).filter(Boolean),o=["claude","cursor","copilot","agents"],r=s.filter(u=>o.includes(u)),d=await import("./reverse-bridge.js"),a=j();try{let u=e?a.getProject(e):void 0;if(!u&&i&&(u=x(a,i)??void 0),u||(u=a.getProject(process.cwd())),!u){m("wyrm reverse-bridge: no Wyrm project for this directory (use --path or --project)."),process.exitCode=1;return}const g=ge(a.getDatabase()),f={wyrm_version:M().version??"unknown",compiled_at:new Date().toISOString()},y=ie(g,u,f),h={"MEMORY.md":y.memoryMd};for(const C of r){const S=ye(C,y.model,f);h[S.relPath]=S.block}const w=d.makeBridgeDeps(a.getDatabase()),$=await d.sweepProject(w,{id:u.id,path:u.path},h,{dryRun:t,rootDir:n});k(`Reverse bridge ${t?"(dry run) ":""}\u2014 ${$.added} candidate(s) queued, ${$.skipped} already present (${$.filesWithEdits}/${$.filesScanned} file(s) with edits)`);for(const C of $.sample)console.log(` ${H.bullet} ${C}`);!t&&$.added>0&&b("Review with: wyrm review")}finally{a.close()}}async function mt(l){const c=E(l);if(typeof c.flags.from=="string"){await ft(c);return}const{positional:e,flags:i}=E(l.slice(1)),n=l[0],t=i.project;if(n==="git"){const s=I(i.last,20),o=j(),r=o.getDatabase();let d=null;if(t){const h=x(o,t);h||(m(`Project not found: ${t}`),o.close(),process.exit(1)),d=h.id}else{const h=r.prepare("SELECT p.* FROM projects p JOIN sessions s ON s.project_id = p.id ORDER BY s.created_at DESC LIMIT 1").get();h&&(d=h.id)}d||(m("No project found. Use --project <name>"),o.close(),process.exit(1));const a=X("git",["log","--pretty=format:%H%x1f%s%x1f%an%x1f%ai",`-${s}`],{cwd:process.cwd(),encoding:"utf-8",timeout:1e4,shell:!1});(a.error||a.status!==0)&&(m("git log failed. Make sure you are in a git repository."),o.close(),process.exit(1));const u=a.stdout.split(`
61
+ `).filter(h=>h.trim()),g=new G(r);let f=0,y=0;for(const h of u){const[,w,$,C]=h.split(""),S=w??"";if(/^Merge /i.test(S)||/^(chore|bump|release|version)/i.test(S)){y++;continue}let R="pattern";/^fix(\(.+\))?:/i.test(S)?R="lesson":/^refactor(\(.+\))?:/i.test(S)&&(R="heuristic");const _=S.split(":")[0]??"commit";g.add(d,{kind:R,problem:S,whyItWorked:`Committed by ${$??"unknown"} on ${C??"unknown"}`,tags:["git","commit",_.toLowerCase()],confidence:.6,needsReview:1}),f++}o.close(),b(`Imported ${f} commits (${y} skipped). Run ${p.cyan("wyrm review")} to activate.`)}else if(n==="rules"){const s=e[0],o=i.format??"plain";s||(m("Usage: wyrm import rules <path> [--project <name>] [--format cursorrules|copilot|plain]"),process.exit(1)),L(s)||(m(`File not found: ${s}`),process.exit(1));const r=Y(s,"utf-8"),d=j(),a=d.getDatabase();let u=null;if(t){const _=x(d,t);_||(m(`Project not found: ${t}`),d.close(),process.exit(1)),u=_.id}else{const _=a.prepare("SELECT p.* FROM projects p JOIN sessions s ON s.project_id = p.id ORDER BY s.created_at DESC LIMIT 1").get();_&&(u=_.id)}u||(m("No project found. Use --project <name>"),d.close(),process.exit(1));const g=s.split("/").pop()??"rules",f=["imported",o,g],y=r.split(/\n(?=#)/),h=r.split(/\n\n+/),w=(y.length>=h.length?y:h).map(_=>_.trim()).filter(_=>_.length>=15),$=new G(a),C=new ue(a);fe(C,new me(a));let S=0,R=0;for(const _ of w)/\b(always|never|must|use|don't|avoid|prefer)\b/i.test(_)?(C.set(u,{category:"constraint",key:_.slice(0,50).replace(/\n/g," "),value:_,source:g}),S++):($.add(u,{kind:"heuristic",problem:_,tags:f,confidence:.7,needsReview:1}),R++);d.close(),b(`Imported ${S} ground truths + ${R} artifacts (pending review).`)}else m("Usage: wyrm import git [--project <name>] [--last N]"),m(" wyrm import rules <path> [--project <name>] [--format cursorrules|copilot|plain]"),m(` wyrm import --from ${he.join("|")} <file.json> [--project <name>]`),process.exit(1)}async function ft(l){const c=l.flags.from,e=l.positional[0],i=l.flags.project,n=`Usage: wyrm import --from ${he.join("|")} <file.json> [--project <name>]`;e||(m(n),process.exit(1)),L(e)||(m(`File not found: ${e}`),process.exit(1));let t;try{t=JSON.parse(Y(e,"utf-8"))}catch(r){m(`Could not parse JSON from ${e}: ${r.message}`),process.exit(1)}let s;try{s=Ve(c,t)}catch(r){m(r.message),process.exit(1)}const o=j();try{let r=i?x(o,i):null;if(r||(r=o.getProject(process.cwd())??null),!r){m(i?`Project not found: ${i}`:"wyrm import: no Wyrm project for this directory (pass --project <name>)."),process.exitCode=1;return}if(s.length===0){m(`No importable memories found in ${e} for source "${c}".`);return}const d=o.getDatabase(),a=new G(d);try{const{createVectorStore:g}=await import("./vectors.js"),f=process.env.WYRM_VECTOR_PROVIDER??"auto";a.setVectorStore(g({provider:f},d))}catch{}let u=0;for(const g of s)a.add(r.id,{kind:"lesson",problem:g.text,tags:g.tags,constraints:g.metadata?JSON.stringify(g.metadata):void 0,whyItWorked:`Imported from ${g.source}`,outcome:"neutral",confidence:.6,needsReview:1}),u++;b(`Imported ${u} ${u===1?"memory":"memories"} from ${c} into ${p.cyan(r.name)} review queue. Run ${p.cyan("wyrm review")} to vet (tagged ${p.dim(`imported_from:${s[0]?.source??c}`)}).`)}finally{o.close()}}async function gt(l){const{flags:c}=E(l),e=c.project,i=j(),n=i.getDatabase();if(k("Wyrm Statistics"),e){const t=x(i,e);t||(m(`Project not found: ${e}`),i.close(),process.exit(1));const s=i.getProjectStats(t.id),o=[["Sessions",String(s.sessions)],["Quests (pending)",String(s.quests.pending)],["Quests (completed)",String(s.quests.completed)],["Data Points",String(s.dataPoints)]];console.log(D(["Metric","Value"],o))}else{const t=i.getStats(),s=[["Projects",String(t.projects)],["Sessions",String(t.sessions)],["Quests",String(t.quests)],["Data Points",String(t.dataPoints)],["DB Size",t.dbSize]],o=n.prepare("SELECT COUNT(*) as n FROM memory_artifacts").get().n,r=n.prepare("SELECT COUNT(*) as n FROM ground_truths WHERE is_current = 1").get().n;s.push(["Memories",String(o)],["Ground Truths",String(r)]),console.log(D(["Metric","Value"],s))}i.close()}async function yt(l){const{flags:c}=E(l),e=c.project,i=j(),n=i.getDatabase();let t=null;if(e){const a=x(i,e);a||(m(`Project not found: ${e}`),i.close(),process.exit(1)),t=a.id}const s=t?`AND project_id = ${t}`:"",o=n.prepare(`
62
62
  SELECT id, kind, problem FROM memory_artifacts
63
63
  WHERE needs_review = 1 ${s}
64
64
  ORDER BY created_at ASC
65
- `).all();if(o.length===0){console.log(d.dim(` No artifacts pending review${t?` for ${t}`:""}.`)),i.close();return}k(`Review Queue (${o.length} items)`);const n=Q({input:process.stdin,output:process.stdout}),p=a=>new Promise(u=>{n.question(a,u)});for(const a of o){console.log(`
66
- ${d.bold(`[${a.kind}] #${a.id}`)}`),console.log(d.dim("\u2500".repeat(60))),console.log(a.problem.slice(0,300)),console.log(d.dim("\u2500".repeat(60)));const g=(await p(`${d.cyan("[a]")}pprove / ${d.red("[r]")}eject / ${d.yellow("[s]")}kip? `)).trim().toLowerCase();g==="a"?(r.prepare("UPDATE memory_artifacts SET needs_review = 0, updated_at = datetime('now') WHERE id = ?").run(a.id),b(`Approved #${a.id}`)):g==="r"?(r.prepare("DELETE FROM memory_artifacts WHERE id = ?").run(a.id),console.log(`${Y.cross} Rejected #${a.id}`)):console.log(d.dim(` Skipped #${a.id}`))}n.close(),i.close(),console.log(`
67
- Review complete.`)}async function pt(l){const{positional:c,flags:t}=C(l),i=c[0];(!i||!["export","import","preview"].includes(i))&&(m("Usage: wyrm sync export --out <path> | wyrm sync import --from <path> | wyrm sync preview --from <path>"),process.exit(1));const{randomBytes:r,pbkdf2Sync:e,createCipheriv:s,createDecipheriv:o}=await import("crypto"),{readFileSync:n,writeFileSync:p,copyFileSync:a,unlinkSync:u,existsSync:g,chmodSync:f}=await import("fs"),{homedir:y}=await import("os"),{join:h}=await import("path"),w=(await import("better-sqlite3")).default;let $=process.env.WYRM_SYNC_PASSPHRASE??"";if(!$){const T=Q({input:process.stdin,output:process.stdout});$=await new Promise(P=>{process.stdout.write("Passphrase: "),process.stdin.isTTY&&process.stdin.setRawMode?.(!0),T.question("",U=>{process.stdin.isTTY&&process.stdin.setRawMode?.(!1),console.log(""),T.close(),P(U)})})}$||(m("Passphrase is required. Set WYRM_SYNC_PASSPHRASE or enter interactively."),process.exit(1));const E=h(y(),".wyrm"),_=j();if(i==="export"){const T=t.out;T||(m("--out <path> is required"),process.exit(1));const P=h(E,"wyrm_cli_export_temp.db");try{const U=_.getDatabase();g(P)&&u(P),U.prepare("VACUUM INTO ?").run(P);const K=n(P),H=r(32),J=r(16),Ee=e($,H,6e5,32,"sha256"),se=s("aes-256-gcm",Ee,J),Re=Buffer.concat([se.update(K),se.final()]),xe=se.getAuthTag(),Te=Buffer.from("WYRM"),le=Buffer.alloc(1);le.writeUInt8(1,0);const de=Buffer.concat([Te,le,H,J,xe,Re]);p(T,de);try{f(T,384)}catch{}try{u(P)}catch{}const De=(de.length/(1024*1024)).toFixed(2);b(`Exported to ${T} (${De} MB)`)}catch(U){try{g(P)&&u(P)}catch{}m(`Export failed: ${U}`)}_.close();return}const R=t.from;R||(m("--from <path> is required"),process.exit(1));const S=n(R);S.subarray(0,4).toString("ascii")!=="WYRM"&&(m("Invalid Wyrm snapshot file."),process.exit(1));const L=S.readUInt8(4);L!==1&&(m(`Unsupported snapshot version: ${L}`),process.exit(1));const W=S.subarray(5,37),ve=S.subarray(37,53),ke=S.subarray(53,69),Se=S.subarray(69),_e=e($,W,6e5,32,"sha256"),Z=o("aes-256-gcm",_e,ve);Z.setAuthTag(ke);let ee;try{ee=Buffer.concat([Z.update(Se),Z.final()])}catch{m("Decryption failed \u2014 wrong passphrase or corrupted file."),process.exit(1)}if(i==="preview"){const T=h(E,"wyrm_cli_preview_temp.db");g(T)&&u(T),p(T,ee);try{const P=new w(T,{readonly:!0}),U=["projects","sessions","ground_truths","memory_artifacts","quests"];k("Snapshot Preview");const K=[];for(const H of U)try{const J=P.prepare(`SELECT COUNT(*) as n FROM ${H}`).get();K.push([H,String(J.n)])}catch{K.push([H,"?"])}console.log(D(["Table","Count"],K)),P.close()}catch(P){m(`Preview failed: ${P}`)}try{g(T)&&u(T)}catch{}_.close();return}const B=_.getDatabasePath(),ce=Q({input:process.stdin,output:process.stdout}),je=await new Promise(T=>{ce.question("This will REPLACE your current database. Type CONFIRM to proceed: ",T)});if(ce.close(),je.trim()!=="CONFIRM"){console.log(d.dim("Aborted.")),_.close();return}const Ce=new Date().toISOString().replace(/[:.]/g,"-"),te=`${B}.backup.${Ce}`;a(B,te),b(`Backed up to ${te}`);const oe=h(E,"wyrm_cli_restore_temp.db");p(oe,ee),_.getDatabase().close(),a(oe,B);for(const T of["-wal","-shm"])try{g(B+T)&&u(B+T)}catch{}try{u(oe)}catch{}b(`Restored from ${R}. Backup at ${te}`)}async function ut(l){const{flags:c}=C(l),t=c.project,i=c.path,r=X(c["min-confidence"],.3),e=I(c["older-than"],90),s=c["no-dry-run"]===!0,o=c.yes===!0,n=j(),p=n.getDatabase();let a=null;if(i||t){const h=i?n.getProject(i):x(n,t);h||(m(`Project not found: ${i??t}`),n.close(),process.exit(1)),a=h.id}const u=new G(p),{candidates:g}=u.pruneStale({projectId:a,minConfidence:r,olderThanDays:e,dryRun:!0});if(k(`Prune Candidates${s?" (LIVE DELETE)":" (dry-run)"}`),g.length===0){console.log(d.dim(" No artifacts match prune criteria.")),n.close();return}const f=g.map(h=>[String(h.id),h.kind,h.problem.slice(0,60),(h.confidence*100).toFixed(0)+"%",h.last_accessed_at??"never"]);if(console.log(D(["ID","Kind","Problem","Conf","Last Accessed"],f)),console.log(`
68
- Total: ${g.length} candidate(s)`),!s){console.log(d.dim(`
69
- This is a dry run. Use --no-dry-run to delete (confirm each ID).`)),n.close();return}if(!o){const h=Q({input:process.stdin,output:process.stdout}),w=await new Promise($=>{h.question(`
70
- Delete these ${g.length} artifact(s)? Type CONFIRM to proceed: `,$)});if(h.close(),w.trim()!=="CONFIRM"){console.log(d.dim("Aborted.")),n.close();return}}const y=u.deleteArtifacts(g.map(h=>h.id));b(`Deleted ${y} artifact(s).`),n.close()}async function mt(l){const{positional:c,flags:t}=C(l),i=c[0]||"list",r=j();try{if(i==="list"||i==="ls"){const e=he(r);if(e.length===0){k("Connectors"),console.log(`${N.dim} none configured. Add one with: wyrm connector add --name <n> --url <bridge-url> --workspace <w>${N.reset}`);return}k("Connectors");const s=e.map(o=>[o.name,o.source,o.enabled?"on":"off",o.workspace,kt(o.baseUrl),o.allowlist.length?`${o.allowlist.length} chats`:"all"]);console.log(D(["Name","Source","Enabled","Workspace","Bridge","Allowlist"],s));return}if(i==="add"){const e=O(t.name),s=O(t.url),o=O(t.workspace);if(!e||!s||!o){m("usage: wyrm connector add --name <n> --url <bridge-url> --workspace <w> [--allowlist a,b] [--token T] [--disabled]"),process.exitCode=1;return}const n={name:e,source:"bridge",enabled:!t.disabled,baseUrl:s,workspace:o,allowlist:O(t.allowlist)?O(t.allowlist).split(",").map(p=>p.trim()).filter(Boolean):[],...O(t.token)?{authToken:O(t.token)}:{}};Ye(r,n),b(`Connector "${e}" saved (source=bridge, workspace=${o}). Token, if any, stored without being printed.`),console.log(`${N.dim} Tip: prefer the env var WYRM_CONNECTOR_TOKEN_${e.toUpperCase().replace(/[^A-Z0-9]/g,"_")} over --token.${N.reset}`);return}if(i==="sync"){const e=c[1]||O(t.name);if(e){const o=he(r).find(p=>p.name===e);if(!o){m(`Connector not found: ${e}`),process.exitCode=1;return}const n=await He(r,o);b(`${n.source}: fetched ${n.fetched}, ingested ${n.ingested}, skipped ${n.skipped} -> ${n.workspace}`);return}const s=await Ve(r);if(s.length===0){console.log(`${N.dim} no enabled connectors.${N.reset}`);return}for(const o of s)"error"in o?m(`${o.name}: ${o.error}`):b(`${o.source}: fetched ${o.fetched}, ingested ${o.ingested}, skipped ${o.skipped} -> ${o.workspace}`);return}m(`Unknown connector subcommand: ${i} (use list | add | sync)`),process.exitCode=1}finally{r.close()}}function we(){return V(0,"utf-8").trim()}async function ft(){const{initializeLicense:l,getLicenseInfo:c,getTier:t}=await import("./license.js");l();const i=c();k("Wyrm License");const r=[["Tier",t()],["Status",i.valid?"valid":"free tier (no license key)"]];i.valid&&i.key&&(r.push(["Key",i.key]),r.push(["Issued to",i.issuedTo??"unknown"]),r.push(["Expires",i.expiresAt?new Date(i.expiresAt).toLocaleDateString():"never"])),r.push(["Features",i.features.join(", ")||"(free)"]),console.log(D(["Field","Value"],r)),i.valid||console.log(d.dim(`
71
- Activate with: wyrm login (free) \xB7 or: wyrm activate <license.json | key>`)),console.log(d.dim(`
72
- \xA9 2026 Ghost Protocol (Pvt) Ltd \xB7 Proprietary \xB7 https://wyrm.ghosts.lk`)),console.log(d.dim(" Licensed under the Wyrm Terms of Service. No open-source license is granted."))}async function gt(){const l=(process.env.WYRM_ACCOUNT_URL??"https://account.ghosts.lk").replace(/\/$/,""),c=(()=>{try{return M().version??"unknown"}catch{return"unknown"}})();let t;try{const a=await fetch(`${l}/api/v1/cli/auth/start`,{method:"POST",headers:{"x-wyrm-version":c}});if(!a.ok)throw new Error(`HTTP ${a.status}`);t=await a.json()}catch(a){m(`Couldn't reach ${l} (${a instanceof Error?a.message:"network error"}).`),process.exitCode=1;return}const i=t.verification_uri_complete||t.verification_uri||`${l}/cli`;console.log(`
73
- ${d.cyan("Sign in to activate Wyrm (free):")}`),console.log(` 1. Open ${d.cyan(i)}`),console.log(` 2. Approve the code ${d.cyan(t.user_code)}`),console.log(d.dim(`
74
- Waiting for approval\u2026 (Ctrl-C to cancel)`));const r=(t.interval??3)*1e3,e=Date.now()+(t.expires_in??600)*1e3;let s="";for(;Date.now()<e;){await new Promise(a=>{setTimeout(a,r)});try{const u=await(await fetch(`${l}/api/v1/cli/auth/poll`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({device_code:t.device_code})})).json();if(u.status==="approved"&&u.token){s=u.token;break}if(u.status==="denied"||u.error==="expired"){m("Login was denied or the code expired. Run `wyrm login` again."),process.exitCode=1;return}}catch{}}if(!s){m("Login timed out. Run `wyrm login` again."),process.exitCode=1;return}let o;try{const a=await fetch(`${l}/api/v1/license/free`,{method:"POST",headers:{authorization:`Bearer ${s}`,"x-wyrm-version":c}});if(!a.ok){const u=await a.json().catch(()=>({}));m(`Activation failed (${a.status}): ${u.hint||u.error||"unknown error"}`),process.exitCode=1;return}o=JSON.stringify(await a.json())}catch(a){m(`Activation request failed (${a instanceof Error?a.message:"network error"}).`),process.exitCode=1;return}const{activateLicense:n}=await import("./license.js"),p=n(o);p.valid?(b(`Signed in & activated \u2014 ${p.tier} tier (expires ${p.expiresAt??"never"}).`),console.log(d.dim(" Restart the Wyrm MCP server / daemon to apply."))):(m(`Activation failed: ${p.error??"unknown error"}`),process.exitCode=1)}async function yt(l){const{positional:c}=C(l),t=c[0];let i;t&&q(t)?i=V(t,"utf-8"):t?i=t:process.stdin.isTTY?(m("Usage: wyrm activate <license.json path | license JSON> (or pipe the JSON on stdin)"),process.exit(1)):i=we();const{activateLicense:r}=await import("./license.js");try{const e=r(i);e.valid?(b(`License activated \u2014 ${e.tier} tier (${e.features.join(", ")})`),console.log(d.dim(" Restart Wyrm (MCP server / daemon) to apply all features."))):(m(`License activation failed: ${e.error??"unknown error"}`),process.exitCode=1)}catch{m("Invalid license format. Please verify your license key."),process.exitCode=1}}async function ht(l){const{flags:c}=C(l),{runMaintenance:t}=await import("./maintenance.js"),{FailurePatterns:i}=await import("./failure-patterns.js"),{SessionSeen:r}=await import("./session-seen.js"),{AgentPresence:e}=await import("./presence.js"),s=j();try{const o=s.getDatabase(),n=I(c["archive-days"],0),p=t({db:s,sessionSeen:new r(o),failures:new i(o),presence:new e(o)},{vacuum:c.vacuum===!0,archiveDays:n>0?n:void 0});k("Maintenance complete");for(const a of p.lines)console.log(` - ${a}`);console.log(d.dim(`
75
- Database size: ${p.dbSize}`))}finally{s.close()}}async function wt(l){const{resolveEmbeddingState:c}=await import("./providers/embedding-provider.js"),t=j();let i=0;const r=(s,o)=>{console.log(` ${d.green("\u2714")} ${s} \u2014 ${d.dim(o)}`)},e=(s,o,n)=>{i++,console.log(` ${d.red("\u2716")} ${s} \u2014 ${o}`),console.log(` ${d.yellow("fix:")} ${n}`)};try{const s=t.getDatabase();k("wyrm doctor");const o=c();o.reason===""&&o.resolved!=="local"?r("Embedding provider",`${o.resolved} (${o.model})${o.egressHost?` \xB7 egress: ${o.egressHost}`:" \xB7 local"}`):o.resolved==="local"?e("Embedding provider","local-hash (hash-384) is NOT semantic \u2014 test-only vectors",o.fix||"use NVIDIA NIM: set WYRM_VECTOR_PROVIDER=nim + NVIDIA_API_KEY"):o.reason==="ollama_deprecated"?e("Embedding provider","ollama (deprecated \u2014 leaves in the next major)",o.fix):e("Embedding provider",`none (${o.reason})`,o.fix);const n=s.prepare("SELECT COUNT(*) AS n FROM memory_artifacts").get().n;let p=0;try{p=s.prepare("SELECT COUNT(*) AS n FROM vectors").get().n}catch{}o.resolved==="none"||o.resolved==="local"?e("Vector index",`${p} vector(s) / ${n} memories \u2014 recall is FTS5 keyword-only (~59.9% vs 72.2% hybrid recall@10)`,o.fix||"enable a provider, then: wyrm index rebuild"):p===0&&n>0?e("Vector index",`provider '${o.resolved}' is configured but 0 of ${n} memories are indexed`,"wyrm index rebuild"):r("Vector index",`${p} vector(s) over ${n} memories`);try{const g=s.prepare("SELECT COUNT(*) AS n FROM memory_artifacts_fts").get().n;g===n?r("FTS index",`${g}/${n} rows in sync`):e("FTS index",`${g} FTS rows vs ${n} memories \u2014 keyword recall is missing rows`,"INSERT INTO memory_artifacts_fts(memory_artifacts_fts) VALUES('rebuild') via wyrm maintenance")}catch{e("FTS index","memory_artifacts_fts unreadable","wyrm maintenance")}const a=s.prepare("SELECT COUNT(*) AS n FROM failure_patterns WHERE resolved = 0").get().n;r("Failure firewall",`${a} unresolved pattern(s) \xB7 matching = exact signature + FTS5 fuzzy (no vector tier in this build)`);const u=s.prepare("SELECT COUNT(*) AS n FROM memory_artifacts WHERE needs_review = 1").get().n;u>0?e("Review queue",`${u} memor${u===1?"y":"ies"} invisible to recall until approved`,"wyrm review"):r("Review queue","empty");try{const g=s.prepare("SELECT MAX(version) AS v FROM schema_versions").get().v;r("Schema",`migration v${g??0}`)}catch{e("Schema","schema_versions unreadable","wyrm maintenance")}console.log(""),i===0?b("All checks passed \u2014 nothing is silently off."):(m(`${i} check(s) DEGRADED \u2014 the fixes above are exact.`),process.exitCode=1)}finally{t.close()}}async function bt(l){const{positional:c,flags:t}=C(l),i=c[0]??"list",r=j();try{const e=r.getDatabase();if(i==="list"){const s=e.prepare("SELECT id, scope, target, occurrences, last_seen FROM failure_patterns WHERE resolved = 0 ORDER BY last_seen DESC LIMIT 25").all();if(k("Unresolved failures"),!s.length){console.log(d.dim(" none \u2014 the firewall has nothing active."));return}console.log(D(["ID","Scope","Target","Seen","Last seen"],s.map(o=>[String(o.id),o.scope,o.target.slice(0,48),String(o.occurrences),o.last_seen]))),console.log(d.dim(`
76
- resolve one: wyrm failure resolve <id>`));return}if(i==="resolve"){const s=Number(c[1]);if(!Number.isInteger(s)||s<=0){m('Usage: wyrm failure resolve <id> [--note "root cause fixed by ..."]'),process.exitCode=1;return}const o=O(t.note);(e.prepare("UPDATE failure_patterns SET resolved = 1, resolution_note = ?, last_seen = datetime('now') WHERE id = ? AND resolved = 0").run(o||"resolved via wyrm failure resolve",s).changes??0)>0?b(`Failure #${s} resolved \u2014 it will no longer flag.`):(m(`No unresolved failure #${s}.`),process.exitCode=1);return}m("Usage: wyrm failure <list|resolve <id> [--note N]>"),process.exitCode=1}finally{r.close()}}async function $t(l){const{positional:c,flags:t}=C(l),i=c[0]??"list",r=j();try{if(i==="add"){const e=c[1]??process.cwd(),s=Ie(e);if(!q(s)){m(`Path does not exist: ${s}`),process.exitCode=1;return}const o=O(t.name)||Pe(s),n=r.registerProject(o,s);b(`Project #${n.id} '${n.name}' registered at ${n.path}`);return}if(i==="list"){const e=r.getAllProjects(50);k("Projects"),console.log(D(["ID","Name","Path"],e.map(s=>[String(s.id),s.name,s.path])));return}m("Usage: wyrm project <add [path] [--name N] | list>"),process.exitCode=1}finally{r.close()}}async function vt(l){const{positional:c,flags:t}=C(l),i="Usage: wyrm index <setup|rebuild|status> [--provider auto|local|nim|openai|none] [--model M] [--project P] [--dry-run]";if(t.help===!0||typeof t.help=="string"){console.log(i);return}const r=c[0]??"status",{createVectorStore:e}=await import("./vectors.js"),o={provider:t.provider??process.env.WYRM_VECTOR_PROVIDER??"auto",model:t.model,apiKey:t["api-key"]??process.env.OPENAI_API_KEY,ollamaUrl:t["ollama-url"]};if(r==="setup"){const{createProvider:p}=await import("./providers/embedding-provider.js"),a=p(o);if(!await a.isReady()&&o.provider!=="none"){m(`Provider not ready: ${a.name}. Check the configuration and try again.`),process.exitCode=1;return}b(`Vector provider verified: ${a.name} (model ${a.model}, ${a.dimensions}d)`),console.log(d.dim(" The MCP server reads WYRM_VECTOR_PROVIDER (and provider-specific env) at boot \u2014")),console.log(d.dim(` set WYRM_VECTOR_PROVIDER=${a.name==="none"?"none":o.provider} in the server env, then: wyrm index rebuild`));return}const n=j();try{const p=n.getDatabase(),a=e(o,p);if(r==="status"){const u=a.getStats();k("Vector index");const g=[["Provider",u.provider],["Model",u.model],["Vectors",String(u.total)],...Object.entries(u.byType).map(([f,y])=>[` ${f}`,String(y)])];console.log(D(["Field","Value"],g));return}if(r==="rebuild"){const{reindexProjects:u}=await import("./reindex.js"),g=t["dry-run"]===!0,f=t.project;let y;if(f){const $=n.getProject(f)??x(n,f);if(!$){m(`Project not found: ${f}`),process.exitCode=1;return}y=[$.id]}else y=n.getAllProjects(1e3).map($=>$.id);const{indexed:h,skipped:w}=await u(p,a,y,{dryRun:g,onError:($,E)=>m(`${$}: ${JSON.stringify(E)}`)});b(`Reindex ${g?"(dry run) ":""}\u2014 ${y.length} project(s), ${h} indexed, ${w} skipped`);return}m(i),process.exitCode=1}finally{n.close()}}function O(l){return typeof l=="string"?l:""}function kt(l){try{return new URL(l).host}catch{return l}}async function St(l){const{flags:c}=C(l),{getUpdateStatus:t}=await import("./version-check.js"),i=M().version??"0.0.0",r=j();let e;try{e=await t(r.getDatabase(),i,{force:c.force===!0||c.check===!0})}finally{r.close()}if(k("Wyrm update"),console.log(D(["Field","Value"],[["Current",e.current],["Latest",e.latest??"unknown (offline?)"],["Update available",e.updateAvailable?"yes":"no"],["Checked",`${e.checkedAt} (${e.source})`]])),c.check===!0)return;if(!e.updateAvailable&&c.force!==!0){console.log(d.dim(`
77
- Already up to date. (Use --force to reinstall anyway.)`));return}console.log(d.dim(`
65
+ `).all();if(o.length===0){console.log(p.dim(` No artifacts pending review${e?` for ${e}`:""}.`)),i.close();return}k(`Review Queue (${o.length} items)`);const r=Z({input:process.stdin,output:process.stdout}),d=a=>new Promise(u=>{r.question(a,u)});for(const a of o){console.log(`
66
+ ${p.bold(`[${a.kind}] #${a.id}`)}`),console.log(p.dim("\u2500".repeat(60))),console.log(a.problem.slice(0,300)),console.log(p.dim("\u2500".repeat(60)));const g=(await d(`${p.cyan("[a]")}pprove / ${p.red("[r]")}eject / ${p.yellow("[s]")}kip? `)).trim().toLowerCase();g==="a"?(n.prepare("UPDATE memory_artifacts SET needs_review = 0, updated_at = datetime('now') WHERE id = ?").run(a.id),b(`Approved #${a.id}`)):g==="r"?(n.prepare("DELETE FROM memory_artifacts WHERE id = ?").run(a.id),console.log(`${H.cross} Rejected #${a.id}`)):console.log(p.dim(` Skipped #${a.id}`))}r.close(),i.close(),console.log(`
67
+ Review complete.`)}async function ht(l){const{positional:c,flags:e}=E(l),i=c[0];(!i||!["export","import","preview"].includes(i))&&(m("Usage: wyrm sync export --out <path> | wyrm sync import --from <path> | wyrm sync preview --from <path>"),process.exit(1));const{randomBytes:n,pbkdf2Sync:t,createCipheriv:s,createDecipheriv:o}=await import("crypto"),{readFileSync:r,writeFileSync:d,copyFileSync:a,unlinkSync:u,existsSync:g,chmodSync:f}=await import("fs"),{homedir:y}=await import("os"),{join:h}=await import("path"),w=(await import("better-sqlite3")).default;let $=process.env.WYRM_SYNC_PASSPHRASE??"";if(!$){const T=Z({input:process.stdin,output:process.stdout});$=await new Promise(P=>{process.stdout.write("Passphrase: "),process.stdin.isTTY&&process.stdin.setRawMode?.(!0),T.question("",F=>{process.stdin.isTTY&&process.stdin.setRawMode?.(!1),console.log(""),T.close(),P(F)})})}$||(m("Passphrase is required. Set WYRM_SYNC_PASSPHRASE or enter interactively."),process.exit(1));const C=h(y(),".wyrm"),S=j();if(i==="export"){const T=e.out;T||(m("--out <path> is required"),process.exit(1));const P=h(C,"wyrm_cli_export_temp.db");try{const F=S.getDatabase();g(P)&&u(P),F.prepare("VACUUM INTO ?").run(P);const K=r(P),V=n(32),J=n(16),xe=t($,V,6e5,32,"sha256"),ne=s("aes-256-gcm",xe,J),Te=Buffer.concat([ne.update(K),ne.final()]),De=ne.getAuthTag(),Ie=Buffer.from("WYRM"),de=Buffer.alloc(1);de.writeUInt8(1,0);const pe=Buffer.concat([Ie,de,V,J,De,Te]);d(T,pe);try{f(T,384)}catch{}try{u(P)}catch{}const Pe=(pe.length/(1024*1024)).toFixed(2);b(`Exported to ${T} (${Pe} MB)`)}catch(F){try{g(P)&&u(P)}catch{}m(`Export failed: ${F}`)}S.close();return}const R=e.from;R||(m("--from <path> is required"),process.exit(1));const _=r(R);_.subarray(0,4).toString("ascii")!=="WYRM"&&(m("Invalid Wyrm snapshot file."),process.exit(1));const U=_.readUInt8(4);U!==1&&(m(`Unsupported snapshot version: ${U}`),process.exit(1));const q=_.subarray(5,37),_e=_.subarray(37,53),Se=_.subarray(53,69),je=_.subarray(69),Ee=t($,q,6e5,32,"sha256"),te=o("aes-256-gcm",Ee,_e);te.setAuthTag(Se);let oe;try{oe=Buffer.concat([te.update(je),te.final()])}catch{m("Decryption failed \u2014 wrong passphrase or corrupted file."),process.exit(1)}if(i==="preview"){const T=h(C,"wyrm_cli_preview_temp.db");g(T)&&u(T),d(T,oe);try{const P=new w(T,{readonly:!0}),F=["projects","sessions","ground_truths","memory_artifacts","quests"];k("Snapshot Preview");const K=[];for(const V of F)try{const J=P.prepare(`SELECT COUNT(*) as n FROM ${V}`).get();K.push([V,String(J.n)])}catch{K.push([V,"?"])}console.log(D(["Table","Count"],K)),P.close()}catch(P){m(`Preview failed: ${P}`)}try{g(T)&&u(T)}catch{}S.close();return}const B=S.getDatabasePath(),le=Z({input:process.stdin,output:process.stdout}),Ce=await new Promise(T=>{le.question("This will REPLACE your current database. Type CONFIRM to proceed: ",T)});if(le.close(),Ce.trim()!=="CONFIRM"){console.log(p.dim("Aborted.")),S.close();return}const Re=new Date().toISOString().replace(/[:.]/g,"-"),se=`${B}.backup.${Re}`;a(B,se),b(`Backed up to ${se}`);const re=h(C,"wyrm_cli_restore_temp.db");d(re,oe),S.getDatabase().close(),a(re,B);for(const T of["-wal","-shm"])try{g(B+T)&&u(B+T)}catch{}try{u(re)}catch{}b(`Restored from ${R}. Backup at ${se}`)}async function wt(l){const{flags:c}=E(l),e=c.project,i=c.path,n=ee(c["min-confidence"],.3),t=I(c["older-than"],90),s=c["no-dry-run"]===!0,o=c.yes===!0,r=j(),d=r.getDatabase();let a=null;if(i||e){const h=i?r.getProject(i):x(r,e);h||(m(`Project not found: ${i??e}`),r.close(),process.exit(1)),a=h.id}const u=new G(d),{candidates:g}=u.pruneStale({projectId:a,minConfidence:n,olderThanDays:t,dryRun:!0});if(k(`Prune Candidates${s?" (LIVE DELETE)":" (dry-run)"}`),g.length===0){console.log(p.dim(" No artifacts match prune criteria.")),r.close();return}const f=g.map(h=>[String(h.id),h.kind,h.problem.slice(0,60),(h.confidence*100).toFixed(0)+"%",h.last_accessed_at??"never"]);if(console.log(D(["ID","Kind","Problem","Conf","Last Accessed"],f)),console.log(`
68
+ Total: ${g.length} candidate(s)`),!s){console.log(p.dim(`
69
+ This is a dry run. Use --no-dry-run to delete (confirm each ID).`)),r.close();return}if(!o){const h=Z({input:process.stdin,output:process.stdout}),w=await new Promise($=>{h.question(`
70
+ Delete these ${g.length} artifact(s)? Type CONFIRM to proceed: `,$)});if(h.close(),w.trim()!=="CONFIRM"){console.log(p.dim("Aborted.")),r.close();return}}const y=u.deleteArtifacts(g.map(h=>h.id));b(`Deleted ${y} artifact(s).`),r.close()}async function bt(l){const{positional:c,flags:e}=E(l),i=c[0]||"list",n=j();try{if(i==="list"||i==="ls"){const t=we(n);if(t.length===0){k("Connectors"),console.log(`${A.dim} none configured. Add one with: wyrm connector add --name <n> --url <bridge-url> --workspace <w>${A.reset}`);return}k("Connectors");const s=t.map(o=>[o.name,o.source,o.enabled?"on":"off",o.workspace,It(o.baseUrl),o.allowlist.length?`${o.allowlist.length} chats`:"all"]);console.log(D(["Name","Source","Enabled","Workspace","Bridge","Allowlist"],s));return}if(i==="add"){const t=N(e.name),s=N(e.url),o=N(e.workspace);if(!t||!s||!o){m("usage: wyrm connector add --name <n> --url <bridge-url> --workspace <w> [--allowlist a,b] [--token T] [--disabled]"),process.exitCode=1;return}const r={name:t,source:"bridge",enabled:!e.disabled,baseUrl:s,workspace:o,allowlist:N(e.allowlist)?N(e.allowlist).split(",").map(d=>d.trim()).filter(Boolean):[],...N(e.token)?{authToken:N(e.token)}:{}};Be(n,r),b(`Connector "${t}" saved (source=bridge, workspace=${o}). Token, if any, stored without being printed.`),console.log(`${A.dim} Tip: prefer the env var WYRM_CONNECTOR_TOKEN_${t.toUpperCase().replace(/[^A-Z0-9]/g,"_")} over --token.${A.reset}`);return}if(i==="sync"){const t=c[1]||N(e.name);if(t){const o=we(n).find(d=>d.name===t);if(!o){m(`Connector not found: ${t}`),process.exitCode=1;return}const r=await Ke(n,o);b(`${r.source}: fetched ${r.fetched}, ingested ${r.ingested}, skipped ${r.skipped} -> ${r.workspace}`);return}const s=await Ge(n);if(s.length===0){console.log(`${A.dim} no enabled connectors.${A.reset}`);return}for(const o of s)"error"in o?m(`${o.name}: ${o.error}`):b(`${o.source}: fetched ${o.fetched}, ingested ${o.ingested}, skipped ${o.skipped} -> ${o.workspace}`);return}m(`Unknown connector subcommand: ${i} (use list | add | sync)`),process.exitCode=1}finally{n.close()}}function $e(){return Y(0,"utf-8").trim()}async function $t(){const{initializeLicense:l,getLicenseInfo:c,getTier:e}=await import("./license.js");l();const i=c();k("Wyrm License");const n=[["Tier",e()],["Status",i.valid?"valid":"free tier (no license key)"]];i.valid&&i.key&&(n.push(["Key",i.key]),n.push(["Issued to",i.issuedTo??"unknown"]),n.push(["Expires",i.expiresAt?new Date(i.expiresAt).toLocaleDateString():"never"])),n.push(["Features",i.features.join(", ")||"(free)"]),console.log(D(["Field","Value"],n)),i.valid||console.log(p.dim(`
71
+ Activate with: wyrm login (free) \xB7 or: wyrm activate <license.json | key>`)),console.log(p.dim(`
72
+ \xA9 2026 Ghost Protocol (Pvt) Ltd \xB7 Proprietary \xB7 https://wyrm.ghosts.lk`)),console.log(p.dim(" Licensed under the Wyrm Terms of Service. No open-source license is granted."))}async function vt(){const l=(process.env.WYRM_ACCOUNT_URL??"https://account.ghosts.lk").replace(/\/$/,""),c=(()=>{try{return M().version??"unknown"}catch{return"unknown"}})();let e;try{const a=await fetch(`${l}/api/v1/cli/auth/start`,{method:"POST",headers:{"x-wyrm-version":c}});if(!a.ok)throw new Error(`HTTP ${a.status}`);e=await a.json()}catch(a){m(`Couldn't reach ${l} (${a instanceof Error?a.message:"network error"}).`),process.exitCode=1;return}const i=e.verification_uri_complete||e.verification_uri||`${l}/cli`;console.log(`
73
+ ${p.cyan("Sign in to activate Wyrm (free):")}`),console.log(` 1. Open ${p.cyan(i)}`),console.log(` 2. Approve the code ${p.cyan(e.user_code)}`),console.log(p.dim(`
74
+ Waiting for approval\u2026 (Ctrl-C to cancel)`));const n=(e.interval??3)*1e3,t=Date.now()+(e.expires_in??600)*1e3;let s="";for(;Date.now()<t;){await new Promise(a=>{setTimeout(a,n)});try{const u=await(await fetch(`${l}/api/v1/cli/auth/poll`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({device_code:e.device_code})})).json();if(u.status==="approved"&&u.token){s=u.token;break}if(u.status==="denied"||u.error==="expired"){m("Login was denied or the code expired. Run `wyrm login` again."),process.exitCode=1;return}}catch{}}if(!s){m("Login timed out. Run `wyrm login` again."),process.exitCode=1;return}let o;try{const a=await fetch(`${l}/api/v1/license/free`,{method:"POST",headers:{authorization:`Bearer ${s}`,"x-wyrm-version":c}});if(!a.ok){const u=await a.json().catch(()=>({}));m(`Activation failed (${a.status}): ${u.hint||u.error||"unknown error"}`),process.exitCode=1;return}o=JSON.stringify(await a.json())}catch(a){m(`Activation request failed (${a instanceof Error?a.message:"network error"}).`),process.exitCode=1;return}const{activateLicense:r}=await import("./license.js"),d=r(o);d.valid?(b(`Signed in & activated \u2014 ${d.tier} tier (expires ${d.expiresAt??"never"}).`),console.log(p.dim(" Restart the Wyrm MCP server / daemon to apply."))):(m(`Activation failed: ${d.error??"unknown error"}`),process.exitCode=1)}async function kt(l){const{positional:c}=E(l),e=c[0];let i;e&&L(e)?i=Y(e,"utf-8"):e?i=e:process.stdin.isTTY?(m("Usage: wyrm activate <license.json path | license JSON> (or pipe the JSON on stdin)"),process.exit(1)):i=$e();const{activateLicense:n}=await import("./license.js");try{const t=n(i);t.valid?(b(`License activated \u2014 ${t.tier} tier (${t.features.join(", ")})`),console.log(p.dim(" Restart Wyrm (MCP server / daemon) to apply all features."))):(m(`License activation failed: ${t.error??"unknown error"}`),process.exitCode=1)}catch{m("Invalid license format. Please verify your license key."),process.exitCode=1}}async function _t(l){const{flags:c}=E(l),{runMaintenance:e}=await import("./maintenance.js"),{FailurePatterns:i}=await import("./failure-patterns.js"),{SessionSeen:n}=await import("./session-seen.js"),{AgentPresence:t}=await import("./presence.js"),s=j();try{const o=s.getDatabase(),r=I(c["archive-days"],0),d=e({db:s,sessionSeen:new n(o),failures:new i(o),presence:new t(o)},{vacuum:c.vacuum===!0,archiveDays:r>0?r:void 0});k("Maintenance complete");for(const a of d.lines)console.log(` - ${a}`);console.log(p.dim(`
75
+ Database size: ${d.dbSize}`))}finally{s.close()}}async function St(l){const{positional:c,flags:e}=E(l),i=c[0]??"trend",n=M().version??null,t=j();try{const s=t.getDatabase();if(i==="snapshot"){const o=N(e.kind)||"all",r=[];if((o==="all"||o==="health")&&ce(s,"health",ze(s),n)!=null&&r.push("health"),(o==="all"||o==="effectiveness")&&ce(s,"effectiveness",Qe(s),n)!=null&&r.push("effectiveness"),o==="retrieval"||e.full===!0){const d=await Ct();d&&ce(s,"retrieval",d,n)!=null?r.push("retrieval"):d||console.log(p.dim(" retrieval: bench unavailable (run from the repo with bench/ present) \u2014 skipped"))}r.length?b(`metrics snapshot recorded: ${r.join(", ")} @ ${n??"unknown"}`):m("metrics snapshot: nothing recorded");return}if(i==="trend"){const o=N(e.kind),r=Number(N(e.limit))||14,d=o?[o]:["health","effectiveness","retrieval"];let a=!1;for(const u of d){const g=be(s,u,r);if(!g.length)continue;a=!0,k(`${u} \u2014 ${g.length} snapshot(s)`);const f=jt[u],y=g.slice().reverse().map(h=>[h.captured_at.slice(0,16),h.wyrm_version??"-",...f.map(w=>Et(h.metrics[w]))]);console.log(D(["when","ver",...f],y));for(const h of Xe(g,u))console.log(" "+p.yellow("!")+" "+h)}a||(console.log(p.dim(" No metrics yet. Record the first snapshot: wyrm metrics snapshot")),console.log(p.dim(" (health + effectiveness are cheap; add --full for retrieval recall@k.)")));return}if(i==="show"){const o=N(e.kind)||"health",r=be(s,o,1);if(!r.length){console.log(p.dim(` No ${o} snapshot yet.`));return}k(`latest ${o} @ ${r[0].captured_at} (${r[0].wyrm_version??"-"})`),console.log(JSON.stringify(r[0].metrics,null,2));return}m("Usage: wyrm metrics <snapshot [--kind all|health|effectiveness|retrieval] [--full] | trend [--kind K] [--limit N] | show [--kind K]>"),process.exitCode=1}finally{t.close()}}const jt={health:["memories","vectors","vector_coverage_pct","review_queue","unresolved_failures","provider"],effectiveness:["failure_blocks","rehydrate_calls","recall_calls","tokens_saved_total"],retrieval:["recall_at_1","recall_at_10","mrr","mode","queries"]};function Et(l){return l==null?"-":typeof l=="number"?Number.isInteger(l)?String(l):l.toFixed(3):String(l)}async function Ct(){try{const l=z(Q(import.meta.url)),c=O(l,"..","bench"),e=O(c,"lib","retrieval-eval.mjs"),i=O(c,"eval-corpus.json");if(!L(e)||!L(i))return null;const n=process.env.WYRM_VECTOR_PROVIDER||"none";process.env.WYRM_LIVE_MEMORY="0";const{evaluate:t}=await import(e),{WyrmDB:s}=await import("./database.js"),{MemoryArtifacts:o}=await import("./memory-artifacts.js"),r=JSON.parse(Y(i,"utf8")),d=`/tmp/wyrm-metrics-eval-${process.pid}.db`,a=new s(d);let u;try{u=t(r,a,o,{k:10})}finally{a.close();for(const f of["","-wal","-shm"])try{Le(d+f,{force:!0})}catch{}}const g=u.quality.overall;return{mode:n==="none"?"fts-floor":n,k:10,recall_at_1:g.recall_at_1??null,recall_at_10:g.recall_at_k??null,mrr:g.mrr??null,queries:g.n??u.performance?.queries??null,p50_ms:u.performance?.p50_ms??null,p95_ms:u.performance?.p95_ms??null}}catch{return null}}async function Rt(l){const{resolveEmbeddingState:c}=await import("./providers/embedding-provider.js"),e=j();let i=0;const n=(s,o)=>{console.log(` ${p.green("\u2714")} ${s} \u2014 ${p.dim(o)}`)},t=(s,o,r)=>{i++,console.log(` ${p.red("\u2716")} ${s} \u2014 ${o}`),console.log(` ${p.yellow("fix:")} ${r}`)};try{const s=e.getDatabase();k("wyrm doctor");const o=c();o.reason===""&&o.resolved!=="local"?n("Embedding provider",`${o.resolved} (${o.model})${o.egressHost?` \xB7 egress: ${o.egressHost}`:" \xB7 local"}`):o.resolved==="local"?t("Embedding provider","local-hash (hash-384) is NOT semantic \u2014 test-only vectors",o.fix||"use NVIDIA NIM: set WYRM_VECTOR_PROVIDER=nim + NVIDIA_API_KEY"):o.reason==="ollama_deprecated"?t("Embedding provider","ollama (deprecated \u2014 leaves in the next major)",o.fix):t("Embedding provider",`none (${o.reason})`,o.fix);const r=s.prepare("SELECT COUNT(*) AS n FROM memory_artifacts").get().n;let d=0;try{d=s.prepare("SELECT COUNT(*) AS n FROM vectors").get().n}catch{}o.resolved==="none"||o.resolved==="local"?t("Vector index",`${d} vector(s) / ${r} memories \u2014 recall is FTS5 keyword-only (~59.9% vs 72.2% hybrid recall@10)`,o.fix||"enable a provider, then: wyrm index rebuild"):d===0&&r>0?t("Vector index",`provider '${o.resolved}' is configured but 0 of ${r} memories are indexed`,"wyrm index rebuild"):n("Vector index",`${d} vector(s) over ${r} memories`);try{const g=s.prepare("SELECT COUNT(*) AS n FROM memory_artifacts_fts").get().n;g===r?n("FTS index",`${g}/${r} rows in sync`):t("FTS index",`${g} FTS rows vs ${r} memories \u2014 keyword recall is missing rows`,"INSERT INTO memory_artifacts_fts(memory_artifacts_fts) VALUES('rebuild') via wyrm maintenance")}catch{t("FTS index","memory_artifacts_fts unreadable","wyrm maintenance")}const a=s.prepare("SELECT COUNT(*) AS n FROM failure_patterns WHERE resolved = 0").get().n;n("Failure firewall",`${a} unresolved pattern(s) \xB7 matching = exact signature + FTS5 fuzzy (no vector tier in this build)`);const u=s.prepare("SELECT COUNT(*) AS n FROM memory_artifacts WHERE needs_review = 1").get().n;u>0?t("Review queue",`${u} memor${u===1?"y":"ies"} invisible to recall until approved`,"wyrm review"):n("Review queue","empty");try{const g=s.prepare("SELECT MAX(version) AS v FROM schema_versions").get().v;n("Schema",`migration v${g??0}`)}catch{t("Schema","schema_versions unreadable","wyrm maintenance")}console.log(""),i===0?b("All checks passed \u2014 nothing is silently off."):(m(`${i} check(s) DEGRADED \u2014 the fixes above are exact.`),process.exitCode=1)}finally{e.close()}}async function xt(l){const{positional:c,flags:e}=E(l),i=c[0]??"list",n=j();try{const t=n.getDatabase();if(i==="list"){const s=t.prepare("SELECT id, scope, target, occurrences, last_seen FROM failure_patterns WHERE resolved = 0 ORDER BY last_seen DESC LIMIT 25").all();if(k("Unresolved failures"),!s.length){console.log(p.dim(" none \u2014 the firewall has nothing active."));return}console.log(D(["ID","Scope","Target","Seen","Last seen"],s.map(o=>[String(o.id),o.scope,o.target.slice(0,48),String(o.occurrences),o.last_seen]))),console.log(p.dim(`
76
+ resolve one: wyrm failure resolve <id>`));return}if(i==="resolve"){const s=Number(c[1]);if(!Number.isInteger(s)||s<=0){m('Usage: wyrm failure resolve <id> [--note "root cause fixed by ..."]'),process.exitCode=1;return}const o=N(e.note);(t.prepare("UPDATE failure_patterns SET resolved = 1, resolution_note = ?, last_seen = datetime('now') WHERE id = ? AND resolved = 0").run(o||"resolved via wyrm failure resolve",s).changes??0)>0?b(`Failure #${s} resolved \u2014 it will no longer flag.`):(m(`No unresolved failure #${s}.`),process.exitCode=1);return}m("Usage: wyrm failure <list|resolve <id> [--note N]>"),process.exitCode=1}finally{n.close()}}async function Tt(l){const{positional:c,flags:e}=E(l),i=c[0]??"list",n=j();try{if(i==="add"){const t=c[1]??process.cwd(),s=Ne(t);if(!L(s)){m(`Path does not exist: ${s}`),process.exitCode=1;return}const o=N(e.name)||Me(s),r=n.registerProject(o,s);b(`Project #${r.id} '${r.name}' registered at ${r.path}`);return}if(i==="list"){const t=n.getAllProjects(50);k("Projects"),console.log(D(["ID","Name","Path"],t.map(s=>[String(s.id),s.name,s.path])));return}m("Usage: wyrm project <add [path] [--name N] | list>"),process.exitCode=1}finally{n.close()}}async function Dt(l){const{positional:c,flags:e}=E(l),i="Usage: wyrm index <setup|rebuild|status> [--provider auto|local|nim|openai|none] [--model M] [--project P] [--dry-run]";if(e.help===!0||typeof e.help=="string"){console.log(i);return}const n=c[0]??"status",{createVectorStore:t}=await import("./vectors.js"),o={provider:e.provider??process.env.WYRM_VECTOR_PROVIDER??"auto",model:e.model,apiKey:e["api-key"]??process.env.OPENAI_API_KEY,ollamaUrl:e["ollama-url"]};if(n==="setup"){const{createProvider:d}=await import("./providers/embedding-provider.js"),a=d(o);if(!await a.isReady()&&o.provider!=="none"){m(`Provider not ready: ${a.name}. Check the configuration and try again.`),process.exitCode=1;return}b(`Vector provider verified: ${a.name} (model ${a.model}, ${a.dimensions}d)`),console.log(p.dim(" The MCP server reads WYRM_VECTOR_PROVIDER (and provider-specific env) at boot \u2014")),console.log(p.dim(` set WYRM_VECTOR_PROVIDER=${a.name==="none"?"none":o.provider} in the server env, then: wyrm index rebuild`));return}const r=j();try{const d=r.getDatabase(),a=t(o,d);if(n==="status"){const u=a.getStats();k("Vector index");const g=[["Provider",u.provider],["Model",u.model],["Vectors",String(u.total)],...Object.entries(u.byType).map(([f,y])=>[` ${f}`,String(y)])];console.log(D(["Field","Value"],g));return}if(n==="rebuild"){const{reindexProjects:u}=await import("./reindex.js"),g=e["dry-run"]===!0,f=e.project;let y;if(f){const $=r.getProject(f)??x(r,f);if(!$){m(`Project not found: ${f}`),process.exitCode=1;return}y=[$.id]}else y=r.getAllProjects(1e3).map($=>$.id);const{indexed:h,skipped:w}=await u(d,a,y,{dryRun:g,onError:($,C)=>m(`${$}: ${JSON.stringify(C)}`)});b(`Reindex ${g?"(dry run) ":""}\u2014 ${y.length} project(s), ${h} indexed, ${w} skipped`);return}m(i),process.exitCode=1}finally{r.close()}}function N(l){return typeof l=="string"?l:""}function It(l){try{return new URL(l).host}catch{return l}}async function Pt(l){const{flags:c}=E(l),{getUpdateStatus:e}=await import("./version-check.js"),i=M().version??"0.0.0",n=j();let t;try{t=await e(n.getDatabase(),i,{force:c.force===!0||c.check===!0})}finally{n.close()}if(k("Wyrm update"),console.log(D(["Field","Value"],[["Current",t.current],["Latest",t.latest??"unknown (offline?)"],["Update available",t.updateAvailable?"yes":"no"],["Checked",`${t.checkedAt} (${t.source})`]])),c.check===!0)return;if(!t.updateAvailable&&c.force!==!0){console.log(p.dim(`
77
+ Already up to date. (Use --force to reinstall anyway.)`));return}console.log(p.dim(`
78
78
  Running: npm install -g wyrm-mcp@latest
79
- `));const s=z("npm",["install","-g","wyrm-mcp@latest"],{stdio:"inherit",shell:!1});s.status===0?b("Updated. Restart your MCP clients to pick up the new binary."):(m(`npm install exited with ${s.status??"unknown"}`),process.exitCode=s.status??1)}async function _t(l){const{positional:c,flags:t}=C(l),i=c[0],r=t.project??process.cwd();if(i==="inject"){const{injectSystemPrompt:e}=await import("./autoconfig.js"),s=typeof t.clients=="string"?t.clients.split(",").map(n=>n.trim()).filter(Boolean):[],o=e(r,s);k("System prompt injection");for(const n of o.injected)console.log(` + ${n}`);for(const n of o.skipped)console.log(d.dim(` o skipped (unknown client): ${n}`));for(const n of o.errors)m(n);o.injected.length>0&&b("AI clients in this project will now call wyrm_session_prime at conversation start."),o.errors.length>0&&(process.exitCode=1);return}if(i==="migrate"){const{migrateProject:e,renderMigrationReport:s}=await import("./migrate-prompt.js"),{WYRM_INJECT_BLOCK:o}=await import("./autoconfig.js"),n=t.apply===!0,p=e({projectPath:r,newBlock:o,apply:n});console.log(s(p,n));return}m("Usage: wyrm prompt inject [--project <path>] [--clients copilot,cursor] | wyrm prompt migrate [--project <path>] [--apply]"),process.exitCode=1}async function jt(l){const{positional:c,flags:t}=C(l);(c[0]??"report")!=="report"&&(m("Usage: wyrm hours report --from YYYY-MM-DD --to YYYY-MM-DD [--project <name>] [--session-hours H] [--json]"),process.exit(1));const r=t.from,e=t.to;(!r||!e)&&(m("--from and --to are required (YYYY-MM-DD)"),process.exit(1));const{HourLedger:s}=await import("./hours.js"),o=j();try{const n=t.project,p=n?x(o,n):null;if(n&&!p){m(`Project not found: ${n}`),process.exitCode=1;return}const a=new s(o.getDatabase()).report({range_start:r,range_end:e,project_id:p?.id,default_session_hours:X(t["session-hours"],1)});if(t.json===!0){console.log(JSON.stringify(a,null,2));return}if(k(`Hours ${a.range.start} \u2192 ${a.range.end}`),a.by_project.length===0){console.log(d.dim(" No sessions in range."));return}console.log(D(["Project","Sessions","Hours"],a.by_project.map(u=>[u.project_name,String(u.session_count),u.hours.toFixed(2)]))),console.log(`
80
- Total: ${a.total_hours.toFixed(2)}h across ${a.entries.length} session(s)`+(a.estimated_sessions>0?d.dim(` (${a.estimated_sessions} estimated)`):""))}finally{o.close()}}async function Ct(l){const{positional:c,flags:t}=C(l),i=c[0]??"generate",r=t.client,e=X(t.rate,NaN),s=t.from,o=t.to;(i!=="generate"||!r||!Number.isFinite(e)||!s||!o)&&(m('Usage: wyrm invoice generate --client <name> --rate <usd/hour> --from YYYY-MM-DD --to YYYY-MM-DD [--project <name>] [--number INV-X] [--currency USD] [--notes "\u2026"] [--out <path>]'),process.exit(1));const{HourLedger:n}=await import("./hours.js"),p=j();try{const a=t.project,u=a?x(p,a):null;if(a&&!u){m(`Project not found: ${a}`),process.exitCode=1;return}const g=new n(p.getDatabase()).invoice({client_name:r,hourly_rate_usd:e,range_start:s,range_end:o,project_id:u?.id,invoice_number:t.number,currency:t.currency,notes:t.notes,business_name:t["business-name"],business_address:t["business-address"],business_contact:t["business-contact"],client_address:t["client-address"],default_session_hours:X(t["session-hours"],1)}),f=t.out;if(f){const{writeFileSync:y}=await import("node:fs");y(f,g+`
79
+ `));const s=X("npm",["install","-g","wyrm-mcp@latest"],{stdio:"inherit",shell:!1});s.status===0?b("Updated. Restart your MCP clients to pick up the new binary."):(m(`npm install exited with ${s.status??"unknown"}`),process.exitCode=s.status??1)}async function Nt(l){const{positional:c,flags:e}=E(l),i=c[0],n=e.project??process.cwd();if(i==="inject"){const{injectSystemPrompt:t}=await import("./autoconfig.js"),s=typeof e.clients=="string"?e.clients.split(",").map(r=>r.trim()).filter(Boolean):[],o=t(n,s);k("System prompt injection");for(const r of o.injected)console.log(` + ${r}`);for(const r of o.skipped)console.log(p.dim(` o skipped (unknown client): ${r}`));for(const r of o.errors)m(r);o.injected.length>0&&b("AI clients in this project will now call wyrm_session_prime at conversation start."),o.errors.length>0&&(process.exitCode=1);return}if(i==="migrate"){const{migrateProject:t,renderMigrationReport:s}=await import("./migrate-prompt.js"),{WYRM_INJECT_BLOCK:o}=await import("./autoconfig.js"),r=e.apply===!0,d=t({projectPath:n,newBlock:o,apply:r});console.log(s(d,r));return}m("Usage: wyrm prompt inject [--project <path>] [--clients copilot,cursor] | wyrm prompt migrate [--project <path>] [--apply]"),process.exitCode=1}async function Mt(l){const{positional:c,flags:e}=E(l);(c[0]??"report")!=="report"&&(m("Usage: wyrm hours report --from YYYY-MM-DD --to YYYY-MM-DD [--project <name>] [--session-hours H] [--json]"),process.exit(1));const n=e.from,t=e.to;(!n||!t)&&(m("--from and --to are required (YYYY-MM-DD)"),process.exit(1));const{HourLedger:s}=await import("./hours.js"),o=j();try{const r=e.project,d=r?x(o,r):null;if(r&&!d){m(`Project not found: ${r}`),process.exitCode=1;return}const a=new s(o.getDatabase()).report({range_start:n,range_end:t,project_id:d?.id,default_session_hours:ee(e["session-hours"],1)});if(e.json===!0){console.log(JSON.stringify(a,null,2));return}if(k(`Hours ${a.range.start} \u2192 ${a.range.end}`),a.by_project.length===0){console.log(p.dim(" No sessions in range."));return}console.log(D(["Project","Sessions","Hours"],a.by_project.map(u=>[u.project_name,String(u.session_count),u.hours.toFixed(2)]))),console.log(`
80
+ Total: ${a.total_hours.toFixed(2)}h across ${a.entries.length} session(s)`+(a.estimated_sessions>0?p.dim(` (${a.estimated_sessions} estimated)`):""))}finally{o.close()}}async function Ot(l){const{positional:c,flags:e}=E(l),i=c[0]??"generate",n=e.client,t=ee(e.rate,NaN),s=e.from,o=e.to;(i!=="generate"||!n||!Number.isFinite(t)||!s||!o)&&(m('Usage: wyrm invoice generate --client <name> --rate <usd/hour> --from YYYY-MM-DD --to YYYY-MM-DD [--project <name>] [--number INV-X] [--currency USD] [--notes "\u2026"] [--out <path>]'),process.exit(1));const{HourLedger:r}=await import("./hours.js"),d=j();try{const a=e.project,u=a?x(d,a):null;if(a&&!u){m(`Project not found: ${a}`),process.exitCode=1;return}const g=new r(d.getDatabase()).invoice({client_name:n,hourly_rate_usd:t,range_start:s,range_end:o,project_id:u?.id,invoice_number:e.number,currency:e.currency,notes:e.notes,business_name:e["business-name"],business_address:e["business-address"],business_contact:e["business-contact"],client_address:e["client-address"],default_session_hours:ee(e["session-hours"],1)}),f=e.out;if(f){const{writeFileSync:y}=await import("node:fs");y(f,g+`
81
81
  `,"utf-8"),b(`Invoice written to ${f}`)}else process.stdout.write(g+`
82
- `)}finally{p.close()}}async function Et(l){const{positional:c,flags:t}=C(l),i=c[0]??"status",{AgentDaemon:r}=await import("./agent-daemon.js"),e=j();try{const s=new r(e.getDatabase());switch(i){case"init":case"start":{const o=Math.max(10,Math.min(I(t.interval,600),86400)),n=s.start({interval_seconds:o,max_steps:t["max-steps"]!==void 0&&I(t["max-steps"],0)||void 0,project_path:t.project,verbose:t.verbose===!0});if(!n.ok){m(`Agent init failed: ${n.error}`),process.exitCode=1;return}const p=n.status;b(`Agent ${p.pid!=null&&p.pid!==n.pid?"already running":"started"} \u2014 pid ${p.pid}`),console.log(` Interval: ${o}s \xB7 Active goals: ${p.active_goals} \xB7 Total iterations: ${p.total_iterations}`),console.log(d.dim(` Log: ${p.log_file}`));return}case"status":{const o=s.status();k("Wyrm agent"),console.log(o.running?` RUNNING \u2014 pid ${o.pid}${o.started_at?` (since ${o.started_at})`:""}`:" NOT RUNNING. Start it with: wyrm agent init"),console.log(` Active goals: ${o.active_goals} \xB7 Total iterations: ${o.total_iterations}`),o.last_action&&console.log(` Last action (${o.last_action.ran_at}): ${o.last_action.summary} [${o.last_action.result_status??"?"}]`),t.log===!0&&(console.log(`
83
- === Recent log ===`),console.log(s.recentLog(40)));return}case"stop":{const o=await s.stop({grace_ms:t.grace!==void 0?I(t.grace,3e3):void 0});if(!o.ok){m(`Stop failed: ${o.error}`),process.exitCode=1;return}b(o.was_running?`Agent stopped (was pid ${o.pid}). Goals remain in DB \u2014 wyrm agent init resumes.`:"Agent was not running. (No-op.)");return}case"restart":{const o=await s.restart({interval_seconds:t.interval!==void 0?I(t.interval,600):void 0,max_steps:t["max-steps"]!==void 0&&I(t["max-steps"],0)||void 0,project_path:t.project,verbose:t.verbose===!0});if(!o.ok){m(`Restart failed: ${o.error}`),process.exitCode=1;return}b(`Agent restarted \u2014 pid ${o.status.pid}. Active goals: ${o.status.active_goals}.`);return}default:m("Usage: wyrm agent <init|status|stop|restart> [--interval N] [--max-steps N] [--project <path>] [--verbose] [--log]"),process.exitCode=1}}finally{e.close()}}async function Rt(l){if(l.includes("--encrypt")){const{initializeLicense:i,hasFeature:r}=await import("./license.js");if(i(),!r("encryption")){m("Encryption setup requires a Pro license or higher. See: wyrm license"),process.exitCode=1;return}const{getCrypto:e,initializeCrypto:s}=await import("./crypto.js"),o=l.includes("--enable"),n=l.includes("--test");if(o){const a=process.env.WYRM_ENCRYPTION_KEY??(process.stdin.isTTY?"":we());if(!a||a.length<8){m('Password must be at least 8 characters. Set WYRM_ENCRYPTION_KEY or pipe it: printf %s "$PW" | wyrm setup --encrypt --enable'),process.exitCode=1;return}s(a),b("Encryption enabled (AES-256-GCM, key derived via PBKDF2). Store your password safely \u2014 it cannot be recovered.");return}const p=e();if(n){if(!p.isEnabled()){m("Encryption not enabled. Run: wyrm setup --encrypt --enable"),process.exitCode=1;return}const a="Wyrm encryption test "+Date.now();p.decrypt(p.encrypt(a))===a?b("Encryption test PASSED (encrypt \u2192 decrypt roundtrip)."):(m("Encryption test FAILED."),process.exitCode=1);return}k("Encryption status"),console.log(` Enabled: ${p.isEnabled()?"yes \u2014 new data is encrypted at rest":"no"}`),console.log(" Algorithm: AES-256-GCM"),p.isEnabled()||console.log(d.dim(" Enable with: wyrm setup --encrypt --enable (password via WYRM_ENCRYPTION_KEY or stdin)"));return}const c=re(ne(import.meta.url)),t=z(process.execPath,[F(c,"setup.js"),...l],{stdio:"inherit",shell:!1});process.exitCode=t.status??0}async function xt(l){const c="https://github.com/Ghosts-Protocol-Pvt-Ltd/wyrm-mcp",i=`- wyrm-mcp: ${M().version??"unknown"}
82
+ `)}finally{d.close()}}async function At(l){const{positional:c,flags:e}=E(l),i=c[0]??"status",{AgentDaemon:n}=await import("./agent-daemon.js"),t=j();try{const s=new n(t.getDatabase());switch(i){case"init":case"start":{const o=Math.max(10,Math.min(I(e.interval,600),86400)),r=s.start({interval_seconds:o,max_steps:e["max-steps"]!==void 0&&I(e["max-steps"],0)||void 0,project_path:e.project,verbose:e.verbose===!0});if(!r.ok){m(`Agent init failed: ${r.error}`),process.exitCode=1;return}const d=r.status;b(`Agent ${d.pid!=null&&d.pid!==r.pid?"already running":"started"} \u2014 pid ${d.pid}`),console.log(` Interval: ${o}s \xB7 Active goals: ${d.active_goals} \xB7 Total iterations: ${d.total_iterations}`),console.log(p.dim(` Log: ${d.log_file}`));return}case"status":{const o=s.status();k("Wyrm agent"),console.log(o.running?` RUNNING \u2014 pid ${o.pid}${o.started_at?` (since ${o.started_at})`:""}`:" NOT RUNNING. Start it with: wyrm agent init"),console.log(` Active goals: ${o.active_goals} \xB7 Total iterations: ${o.total_iterations}`),o.last_action&&console.log(` Last action (${o.last_action.ran_at}): ${o.last_action.summary} [${o.last_action.result_status??"?"}]`),e.log===!0&&(console.log(`
83
+ === Recent log ===`),console.log(s.recentLog(40)));return}case"stop":{const o=await s.stop({grace_ms:e.grace!==void 0?I(e.grace,3e3):void 0});if(!o.ok){m(`Stop failed: ${o.error}`),process.exitCode=1;return}b(o.was_running?`Agent stopped (was pid ${o.pid}). Goals remain in DB \u2014 wyrm agent init resumes.`:"Agent was not running. (No-op.)");return}case"restart":{const o=await s.restart({interval_seconds:e.interval!==void 0?I(e.interval,600):void 0,max_steps:e["max-steps"]!==void 0&&I(e["max-steps"],0)||void 0,project_path:e.project,verbose:e.verbose===!0});if(!o.ok){m(`Restart failed: ${o.error}`),process.exitCode=1;return}b(`Agent restarted \u2014 pid ${o.status.pid}. Active goals: ${o.status.active_goals}.`);return}default:m("Usage: wyrm agent <init|status|stop|restart> [--interval N] [--max-steps N] [--project <path>] [--verbose] [--log]"),process.exitCode=1}}finally{t.close()}}async function Lt(l){if(l.includes("--encrypt")){const{initializeLicense:i,hasFeature:n}=await import("./license.js");if(i(),!n("encryption")){m("Encryption setup requires a Pro license or higher. See: wyrm license"),process.exitCode=1;return}const{getCrypto:t,initializeCrypto:s}=await import("./crypto.js"),o=l.includes("--enable"),r=l.includes("--test");if(o){const a=process.env.WYRM_ENCRYPTION_KEY??(process.stdin.isTTY?"":$e());if(!a||a.length<8){m('Password must be at least 8 characters. Set WYRM_ENCRYPTION_KEY or pipe it: printf %s "$PW" | wyrm setup --encrypt --enable'),process.exitCode=1;return}s(a),b("Encryption enabled (AES-256-GCM, key derived via PBKDF2). Store your password safely \u2014 it cannot be recovered.");return}const d=t();if(r){if(!d.isEnabled()){m("Encryption not enabled. Run: wyrm setup --encrypt --enable"),process.exitCode=1;return}const a="Wyrm encryption test "+Date.now();d.decrypt(d.encrypt(a))===a?b("Encryption test PASSED (encrypt \u2192 decrypt roundtrip)."):(m("Encryption test FAILED."),process.exitCode=1);return}k("Encryption status"),console.log(` Enabled: ${d.isEnabled()?"yes \u2014 new data is encrypted at rest":"no"}`),console.log(" Algorithm: AES-256-GCM"),d.isEnabled()||console.log(p.dim(" Enable with: wyrm setup --encrypt --enable (password via WYRM_ENCRYPTION_KEY or stdin)"));return}const c=z(Q(import.meta.url)),e=X(process.execPath,[O(c,"setup.js"),...l],{stdio:"inherit",shell:!1});process.exitCode=e.status??0}async function Wt(l){const c="https://github.com/Ghosts-Protocol-Pvt-Ltd/wyrm-mcp",i=`- wyrm-mcp: ${M().version??"unknown"}
84
84
  - node: ${process.version}
85
- - platform: ${process.platform} ${process.arch}`,r=l.includes("--bug")?"bug":l.includes("--idea")||l.includes("--feature")?"idea":l.includes("--question")||l.includes("--ask")?"question":"feedback",e=l.filter(p=>!p.startsWith("--")).join(" ").trim(),s={bug:`**What happened**
85
+ - platform: ${process.platform} ${process.arch}`,n=l.includes("--bug")?"bug":l.includes("--idea")||l.includes("--feature")?"idea":l.includes("--question")||l.includes("--ask")?"question":"feedback",t=l.filter(d=>!d.startsWith("--")).join(" ").trim(),s={bug:`**What happened**
86
86
 
87
87
 
88
88
  **What you expected**
@@ -108,57 +108,58 @@ ${i}`,feedback:`**Your feedback** (what's working, what's rough)
108
108
 
109
109
 
110
110
  ---
111
- ${i}`},o={bug:"bug",idea:"enhancement",question:"question",feedback:"feedback"};let n;r==="question"?n=`${c}/discussions/new?category=q-a`+(e?`&title=${encodeURIComponent(e)}`:"")+`&body=${encodeURIComponent(s.question)}`:n=`${c}/issues/new?labels=${encodeURIComponent(o[r])}&title=${encodeURIComponent(`[${r}] ${e}`.trim())}&body=${encodeURIComponent(s[r])}`,console.log(""),console.log(" "+d.bold("Thanks for helping make Wyrm better.")),console.log(" Opening a prefilled report in your browser. If it does not open, use this link:"),console.log(""),console.log(" "+d.cyan(n)),console.log(""),console.log(d.dim(" Prefer email? ryan@ghosts.lk \xB7 Bug: wyrm feedback --bug Idea: --idea Question: --question")),console.log("");try{const{spawn:p}=await import("child_process"),a=process.platform,u=a==="darwin"?p("open",[n],{stdio:"ignore",detached:!0}):a==="win32"?p("cmd",["/c","start","",n],{stdio:"ignore",detached:!0}):p("xdg-open",[n],{stdio:"ignore",detached:!0});u.on("error",()=>{}),u.unref()}catch{}}function be(){console.log(`
112
- ${N.brightMagenta}\u{F115D} Wyrm CLI v${M().version??"unknown"}${N.reset}
113
- ${N.dim}Persistent AI Memory System${N.reset}
114
-
115
- ${d.bold("Usage:")} wyrm <command> [options]
116
-
117
- ${d.bold("Commands:")}
118
- ${d.cyan("search")} <query> Search across memories, truths, quests, and data
119
- ${d.cyan("ls")} List stored items
120
- ${d.cyan("show")} <typed-id> Show full detail (mem:N, quest:N, truth:N, data:N, session:N)
121
- ${d.cyan("capture")} "<content>" Classify and capture a thought, task, or lesson
122
- ${d.cyan("rehydrate")} [--project <name>] Print the latest session's continuity brief (for SessionStart)
123
- ${d.cyan("session log")} [--run <id>] [--\u2026] Create/update this project's session record (rehydrate's write side)
124
- ${d.cyan("presence")} <announce|list|release> Agent presence board from the shell; --pid = process-identity liveness
125
- ${d.cyan("digest")} --writes [--since <days>] What Wyrm learned, queued, and refused \u2014 the offline write-receipt report
126
- ${d.cyan("bridge")} <init|status|run> The substrate flow: remember/render/reverse lanes from one receipted config
127
- ${d.cyan("metabolize")} [--dry-run] Memory metabolism: supersede exact dupes, queue near-dup candidates, decay stale auto-captures
128
- ${d.cyan("render")} [--client <list>] [--brief] Compile DB \u2192 MEMORY.md + topic files (zero-MCP-token memory)
129
- ${d.cyan("import git")} Import git log history to review queue
130
- ${d.cyan("import rules")} <path> Import .cursorrules / copilot-instructions file
131
- ${d.cyan("stats")} Show database statistics
132
- ${d.cyan("review")} Interactively review pending artifacts
133
- ${d.cyan("sync export")} --out <path> Export encrypted DB snapshot
134
- ${d.cyan("sync import")} --from <path> Restore from encrypted snapshot (backs up first)
135
- ${d.cyan("sync preview")} --from <path> Preview snapshot stats without restoring
136
- ${d.cyan("prune")} [--project <name>] Prune stale low-confidence artifacts (dry-run by default)
137
- ${d.cyan("cloud")} <login|sync|export|\u2026> Cloud backup & multi-device sync (Pro license)
138
- ${d.cyan("grove")} <status|policy> Per-project sync lanes (private|cloud|team) + leak audit
139
- ${d.cyan("run")} <start|join|end|status> Fleet-run lifecycle (records the migration-20 runs/run_agents tables)
140
- ${d.cyan("events")} [since|publish] [--project] Live-Memory event stream: read (since) or publish (was undocumented \u2014 tallow, 2026-07-14)
141
- ${d.cyan("skill")} <list|backfill-content|export|share> Discover bundled guides; store/sync SKILL.md content
142
- ${d.cyan("serve")} [--ui] Start the Wyrm HTTP server (optionally open the dashboard)
143
- ${d.cyan("setup")} [--check|--encrypt|\u2026] Auto-configure AI clients (wyrm-setup); --encrypt for at-rest crypto
144
- ${d.cyan("guard")} [--remove|--status] Failure-firewall PreToolUse hooks for Claude Code (wyrm-guard)
145
- ${d.cyan("intro")} What Wyrm is and how to use it
146
- ${d.cyan("license")} Show license status & features
147
- ${d.cyan("login")} Sign in (free account) and activate \u2014 required on official builds
148
- ${d.cyan("activate")} <key|path> Activate a license (JSON arg, file path, or stdin)
149
- ${d.cyan("maintenance")} [--vacuum] Archive/prune/sweep/checkpoint the database
150
- ${d.cyan("index")} <setup|rebuild|status> Vector search: verify provider, reindex, stats
151
- ${d.cyan("doctor")} Health check that refuses to flatter \u2014 reports anything silently degraded
152
- ${d.cyan("failure")} <list|resolve> Failure firewall: list unresolved patterns, resolve a fixed one
153
- ${d.cyan("project")} <add|list> Register a project without needing a session start
154
- ${d.cyan("update")} [--check] Check for / install the latest wyrm-mcp
155
- ${d.cyan("prompt")} <inject|migrate> Inject/refresh the session-prime block in client files
156
- ${d.cyan("hours")} report --from --to Hours report derived from session history
157
- ${d.cyan("invoice")} generate --client \u2026 Markdown invoice from the hours ledger
158
- ${d.cyan("agent")} <init|status|stop|restart> Background agent daemon (goal loop)
159
- ${d.cyan("feedback")} [--bug|--idea|--question] Send feedback or report a bug (opens a prefilled GitHub report)
160
-
161
- ${d.bold("Options:")}
111
+ ${i}`},o={bug:"bug",idea:"enhancement",question:"question",feedback:"feedback"};let r;n==="question"?r=`${c}/discussions/new?category=q-a`+(t?`&title=${encodeURIComponent(t)}`:"")+`&body=${encodeURIComponent(s.question)}`:r=`${c}/issues/new?labels=${encodeURIComponent(o[n])}&title=${encodeURIComponent(`[${n}] ${t}`.trim())}&body=${encodeURIComponent(s[n])}`,console.log(""),console.log(" "+p.bold("Thanks for helping make Wyrm better.")),console.log(" Opening a prefilled report in your browser. If it does not open, use this link:"),console.log(""),console.log(" "+p.cyan(r)),console.log(""),console.log(p.dim(" Prefer email? ryan@ghosts.lk \xB7 Bug: wyrm feedback --bug Idea: --idea Question: --question")),console.log("");try{const{spawn:d}=await import("child_process"),a=process.platform,u=a==="darwin"?d("open",[r],{stdio:"ignore",detached:!0}):a==="win32"?d("cmd",["/c","start","",r],{stdio:"ignore",detached:!0}):d("xdg-open",[r],{stdio:"ignore",detached:!0});u.on("error",()=>{}),u.unref()}catch{}}function ve(){console.log(`
112
+ ${A.brightMagenta}\u{F115D} Wyrm CLI v${M().version??"unknown"}${A.reset}
113
+ ${A.dim}Persistent AI Memory System${A.reset}
114
+
115
+ ${p.bold("Usage:")} wyrm <command> [options]
116
+
117
+ ${p.bold("Commands:")}
118
+ ${p.cyan("search")} <query> Search across memories, truths, quests, and data
119
+ ${p.cyan("ls")} List stored items
120
+ ${p.cyan("show")} <typed-id> Show full detail (mem:N, quest:N, truth:N, data:N, session:N)
121
+ ${p.cyan("capture")} "<content>" Classify and capture a thought, task, or lesson
122
+ ${p.cyan("rehydrate")} [--project <name>] Print the latest session's continuity brief (for SessionStart)
123
+ ${p.cyan("session log")} [--run <id>] [--\u2026] Create/update this project's session record (rehydrate's write side)
124
+ ${p.cyan("presence")} <announce|list|release> Agent presence board from the shell; --pid = process-identity liveness
125
+ ${p.cyan("digest")} --writes [--since <days>] What Wyrm learned, queued, and refused \u2014 the offline write-receipt report
126
+ ${p.cyan("bridge")} <init|status|run> The substrate flow: remember/render/reverse lanes from one receipted config
127
+ ${p.cyan("metabolize")} [--dry-run] Memory metabolism: supersede exact dupes, queue near-dup candidates, decay stale auto-captures
128
+ ${p.cyan("render")} [--client <list>] [--brief] Compile DB \u2192 MEMORY.md + topic files (zero-MCP-token memory)
129
+ ${p.cyan("import git")} Import git log history to review queue
130
+ ${p.cyan("import rules")} <path> Import .cursorrules / copilot-instructions file
131
+ ${p.cyan("stats")} Show database statistics
132
+ ${p.cyan("review")} Interactively review pending artifacts
133
+ ${p.cyan("sync export")} --out <path> Export encrypted DB snapshot
134
+ ${p.cyan("sync import")} --from <path> Restore from encrypted snapshot (backs up first)
135
+ ${p.cyan("sync preview")} --from <path> Preview snapshot stats without restoring
136
+ ${p.cyan("prune")} [--project <name>] Prune stale low-confidence artifacts (dry-run by default)
137
+ ${p.cyan("cloud")} <login|sync|export|\u2026> Cloud backup & multi-device sync (Pro license)
138
+ ${p.cyan("grove")} <status|policy> Per-project sync lanes (private|cloud|team) + leak audit
139
+ ${p.cyan("run")} <start|join|end|status> Fleet-run lifecycle (records the migration-20 runs/run_agents tables)
140
+ ${p.cyan("events")} [since|publish] [--project] Live-Memory event stream: read (since) or publish (was undocumented \u2014 tallow, 2026-07-14)
141
+ ${p.cyan("skill")} <list|backfill-content|export|share> Discover bundled guides; store/sync SKILL.md content
142
+ ${p.cyan("serve")} [--ui] Start the Wyrm HTTP server (optionally open the dashboard)
143
+ ${p.cyan("setup")} [--check|--encrypt|\u2026] Auto-configure AI clients (wyrm-setup); --encrypt for at-rest crypto
144
+ ${p.cyan("guard")} [--remove|--status] Failure-firewall PreToolUse hooks for Claude Code (wyrm-guard)
145
+ ${p.cyan("intro")} What Wyrm is and how to use it
146
+ ${p.cyan("license")} Show license status & features
147
+ ${p.cyan("login")} Sign in (free account) and activate \u2014 required on official builds
148
+ ${p.cyan("activate")} <key|path> Activate a license (JSON arg, file path, or stdin)
149
+ ${p.cyan("maintenance")} [--vacuum] Archive/prune/sweep/checkpoint the database
150
+ ${p.cyan("index")} <setup|rebuild|status> Vector search: verify provider, reindex, stats
151
+ ${p.cyan("doctor")} Health check that refuses to flatter \u2014 reports anything silently degraded
152
+ ${p.cyan("metrics")} <snapshot|trend|show> Effectiveness + quality OVER TIME \u2014 the flight recorder (trend flags drift)
153
+ ${p.cyan("failure")} <list|resolve> Failure firewall: list unresolved patterns, resolve a fixed one
154
+ ${p.cyan("project")} <add|list> Register a project without needing a session start
155
+ ${p.cyan("update")} [--check] Check for / install the latest wyrm-mcp
156
+ ${p.cyan("prompt")} <inject|migrate> Inject/refresh the session-prime block in client files
157
+ ${p.cyan("hours")} report --from --to Hours report derived from session history
158
+ ${p.cyan("invoice")} generate --client \u2026 Markdown invoice from the hours ledger
159
+ ${p.cyan("agent")} <init|status|stop|restart> Background agent daemon (goal loop)
160
+ ${p.cyan("feedback")} [--bug|--idea|--question] Send feedback or report a bug (opens a prefilled GitHub report)
161
+
162
+ ${p.bold("Options:")}
162
163
  --project <name> Filter or associate with a specific project
163
164
  --type <type> Filter type (memories|truths|quests|data|all)
164
165
  --limit <N> Limit number of results (default: 20)
@@ -175,26 +176,26 @@ ${d.bold("Options:")}
175
176
  --help, -h Show this help
176
177
  --version, -v Print the installed version
177
178
 
178
- ${d.bold("Environment:")}
179
+ ${p.bold("Environment:")}
179
180
  WYRM_DB_PATH Override database path (default: ~/.wyrm/wyrm.db)
180
181
  WYRM_SYNC_PASSPHRASE Passphrase for encrypted sync (or prompted interactively)
181
182
 
182
- ${d.bold("Examples:")}
183
+ ${p.bold("Examples:")}
183
184
  wyrm search "authentication bug"
184
185
  wyrm ls --type memories --project MyApp
185
186
  wyrm capture "TODO: refactor the auth module" --project MyApp
186
187
  wyrm sync export --out ~/wyrm-backup.wyrm
187
188
  wyrm sync preview --from ~/wyrm-backup.wyrm
188
189
  wyrm prune --project MyApp --min-confidence 0.2 --older-than 30
189
- `)}const[,,A,...v]=process.argv;function $e(l){const c=l.ref_table?`${l.ref_table}${l.ref_id?"#"+l.ref_id:""}`:"",t=new Date(l.created_at).toLocaleTimeString();return`#${l.cursor} ${l.kind.padEnd(15)} ${c.padEnd(16)} ${Be(l.actor)} ${t}`}async function Tt(l){const{positional:c,flags:t}=C(l),i=c[0]||"since",r=j();try{if(!r.liveMemoryEnabled()){m("Live Memory is disabled (set WYRM_LIVE_MEMORY=1)."),process.exitCode=1;return}const e=(typeof t.project=="string"?t.project:"")||process.cwd(),s=r.getProject(e)??r.getProjectByName(e);if(!s){m(`Project not found: ${e}`),process.exitCode=1;return}if(i==="publish"){const o=c[1]||(typeof t.kind=="string"?t.kind:"");if(!o){m("usage: wyrm events publish <kind> --project <p> [--actor A] [--ref-table T --ref-id ID]"),process.exitCode=1;return}r.publishEvent({projectId:s.id,kind:o,refTable:typeof t["ref-table"]=="string"?t["ref-table"]:void 0,refId:typeof t["ref-id"]=="string"?t["ref-id"]:void 0,actor:typeof t.actor=="string"?t.actor:void 0}),b(`Event published (${o}) to ${s.name}`);return}if(i==="since"){const o=I(t.cursor,0),n=I(t.limit,50),p=r.eventsSince(s.id,o,n);for(const a of p)console.log($e(a));k(`${p.length} event(s) for '${s.name}' since cursor ${o}`);return}m("usage: wyrm events <publish|since> ..."),process.exitCode=1}finally{r.close()}}async function Dt(l){const{flags:c}=C(l),t=j();if(!t.liveMemoryEnabled()){m("Live Memory is disabled (set WYRM_LIVE_MEMORY=1)."),t.close(),process.exitCode=1;return}const i=(typeof c.project=="string"?c.project:"")||process.cwd(),r=t.getProject(i)??x(t,i);if(!r){m(`Project not found: ${i}`),t.close(),process.exitCode=1;return}const e=Math.max(250,I(c.interval,1e3));let s=I(c.since,t.subscribeEvents(r.id,1).cursor);k(`Watching '${r.name}' (cursor ${s}, every ${e}ms) \u2014 Ctrl-C to stop`);const o=()=>{try{for(const a of t.eventsSince(r.id,s,200))s=a.cursor,console.log($e(a))}catch{}};o();const n=setInterval(o,e),p=()=>{clearInterval(n);try{t.close()}catch{}process.exit(0)};process.on("SIGINT",p),process.on("SIGTERM",p)}async function It(l){const{flags:c}=C(l),{embedAll:t,removeAll:i,statusAll:r}=await import("./priority-embed.js"),e={projectDir:typeof c.project=="string"?c.project:void 0,allClients:c.all===!0},s=o=>console.log(` ${String(o.result??o.status).padEnd(9)} [${o.scope}] ${o.file}`);if(c.status){k("Wyrm priority embedding \u2014 status"),r(e).forEach(s);return}if(c.remove){k("Wyrm priority embedding \u2014 removed"),i(e).forEach(s);return}k("Wyrm is now FIRST-PRIORITY memory"),t(e).forEach(s);try{const{installClaudeCodeHooks:o,installClaudeStatusline:n}=await import("./autoconfig.js");o()?b("Proactive hooks installed (SessionStart rehydrate + capture + tool-trace)."):console.log(" (Claude Code not detected \u2014 skipped hook install.)");const a=n();a&&b(`Buddy statusline: ${a.message}`)}catch{}b("Wyrm will now be read first, primed proactively, and shown in the TUI at all times.")}async function Pt(l){const{flags:c}=C(l),{harvestProjects:t}=await import("./harvest.js"),{MemoryArtifacts:i}=await import("./memory-artifacts.js"),{escapeLikePattern:r}=await import("./auto-capture.js"),e=j();try{const s=e.getDatabase(),o=new i(s),n={existsBySig:(w,$)=>!!s.prepare("SELECT 1 FROM memory_artifacts WHERE project_id = ? AND tags LIKE ? ESCAPE '\\' LIMIT 1").get(w,"%"+r($)+"%"),addCandidate:(w,$)=>o.add(w,{kind:$.kind,problem:$.text,tags:[...$.tags,$.sig],confidence:$.confidence,needsReview:1,createdBy:"harvest"})},p=typeof c.project=="string"?c.project:void 0;let a;if(p){const w=e.getProject(p)??e.getProjectByName(p);if(!w){m(`Project not found: ${p}`),process.exitCode=1;return}a=[{id:w.id,name:w.name,path:w.path}]}else a=e.getAllProjects(500).map(w=>({id:w.id,name:w.name,path:w.path}));const u=c["dry-run"]===!0||c.dry===!0,g=c.code===!0||c["include-code"]===!0,{reports:f,totalAdded:y,totalSkipped:h}=t(n,a,{dryRun:u,gitLimit:I(c.limit,30),includeCode:g});k(`Harvest ${u?"(dry run) ":""}\u2014 ${y} candidate(s), ${h} already present (${a.length} project(s))`);for(const w of f.filter($=>$.added>0).sort(($,E)=>E.added-$.added).slice(0,25))console.log(` +${String(w.added).padStart(3)} (skip ${w.skipped}) ${w.project}`);if(g&&!u){const{SymbolGraph:w}=await import("./symbols.js"),$=new w(s);let E=0,_=0;for(const R of a)try{const S=$.indexProject(R.id,R.path);E+=S.symbols,_+=S.files}catch{}console.log(` \u{1F4D0} Indexed ${E} code symbols (${_} files) \u2192 searchable via 'wyrm search'`)}!u&&y>0&&b("Review with: wyrm review")}finally{e.close()}}async function Nt(l){const c=await import("./vault.js"),[t,...i]=l;try{switch(t){case"set":{const r=i[0];if(!r){m("usage: wyrm vault set <name> (the secret is read from STDIN, never argv)"),process.exitCode=1;return}if(process.stdin.isTTY){m(`pipe the secret in, e.g.: printf %s "$TOKEN" | wyrm vault set ${r}`),process.exitCode=1;return}const s=(await import("node:fs")).readFileSync(0,"utf8").replace(/\r?\n$/,"");if(!s){m("empty secret on stdin"),process.exitCode=1;return}c.vaultSet(r,s),b(`Stored "${r}" (AES-256-GCM). Use it without exposing it: wyrm vault exec ${r} -- <command>`);break}case"get":{const r=i[0];if(!r){m("usage: wyrm vault get <name>"),process.exitCode=1;return}const e=c.vaultGet(r);if(e===void 0){m(`no secret named "${r}"`),process.exitCode=1;return}process.stdout.write(e);break}case"list":case"ls":{const r=c.vaultList();if(!r.length){console.log("(vault is empty)");break}k(`Vault \u2014 ${r.length} secret(s)`);for(const e of r)console.log(" \u2022 "+e);break}case"rm":case"remove":case"delete":{const r=i[0];if(!r){m("usage: wyrm vault rm <name>"),process.exitCode=1;return}b(c.vaultRemove(r)?`Removed "${r}"`:`(no secret named "${r}")`);break}case"exec":{const r=i[0],e=i.indexOf("--");if(!r||e===-1||e+1>=i.length){m("usage: wyrm vault exec <name> [--as ENVVAR] -- <command...>"),process.exitCode=1;return}const s=i.slice(1,e),o=s.indexOf("--as"),n=o>=0?s[o+1]:r.toUpperCase().replace(/[^A-Z0-9]+/g,"_"),p=i.slice(e+1),a=c.vaultGet(r);if(a===void 0){m(`no secret named "${r}"`),process.exitCode=1;return}const u=z(p[0],p.slice(1),{stdio:"inherit",env:{...process.env,[n]:a}});process.exitCode=u.status??1;break}case"import-npm":{const r=await import("node:fs"),e=await import("node:os"),o=(await import("node:path")).join(e.homedir(),".npmrc");if(!r.existsSync(o)){m("~/.npmrc not found"),process.exitCode=1;return}const n=r.readFileSync(o,"utf8").match(/\/\/registry\.npmjs\.org\/:_authToken=(.+)/);if(!n){m("no npm authToken found in ~/.npmrc"),process.exitCode=1;return}c.vaultSet("npm-token",n[1].trim()),b('Imported npm token \u2192 vault as "npm-token". You can now scrub the plaintext from ~/.npmrc and use: wyrm vault exec npm-token --as NODE_AUTH_TOKEN -- npm publish');break}case"setup":{const r=c.vaultPaths();if(r.secure)b(`Vault is already secure (backend: ${r.backend}, ${r.count} secret(s)).`);else{const e=r.keychainAvailable?"keychain":"passphrase";if(e==="passphrase"&&!process.env.WYRM_VAULT_PASSPHRASE){m("No OS keychain on this host. Set WYRM_VAULT_PASSPHRASE, then re-run: wyrm vault setup"),process.exitCode=1;return}const s=c.vaultSecure({backend:e});b(`Vault secured: ${s.from} \u2192 ${s.to}${s.rotated?" (key rotated)":""}.`),s.keyfileShredded&&console.log(" plaintext vault.key: shredded \u2714"),s.backup&&console.log(` ciphertext backup: ${s.backup} (delete once confirmed)`)}k("Store & use credentials safely"),console.log(' store: printf %s "$TOKEN" | wyrm vault set <name> # reads STDIN \u2014 never argv/shell history'),console.log(" use: wyrm vault exec <name> --as ENV_VAR -- <cmd> # injected as env var, never printed"),console.log(" list: wyrm vault list inspect: wyrm vault info");break}case"secure":{const r=i.indexOf("--backend"),e=r>=0?i[r+1]:"keychain";if(e!=="keychain"&&e!=="passphrase"){m("usage: wyrm vault secure [--backend keychain|passphrase] [--no-rotate]"),process.exitCode=1;return}if(e==="passphrase"&&!process.env.WYRM_VAULT_PASSPHRASE){m("set WYRM_VAULT_PASSPHRASE before: wyrm vault secure --backend passphrase"),process.exitCode=1;return}const s=!i.includes("--no-rotate"),o=c.vaultSecure({backend:e,rotate:s});b(`Vault secured: ${o.from} \u2192 ${o.to}${o.rotated?" (key rotated)":""}.`),console.log(` secrets re-encrypted: ${o.secrets}`),o.keyfileShredded&&console.log(" plaintext vault.key: shredded \u2714"),o.backup&&console.log(` ciphertext backup: ${o.backup} (delete once you've confirmed)`),console.log(e==="keychain"?" master key now lives in the OS keychain \u2014 not on disk.":" master key now derived from WYRM_VAULT_PASSPHRASE \u2014 keep that set for future use.");break}case"info":{const r=c.vaultPaths();k("Vault"),console.log(` backend: ${r.backend}`),console.log(` secrets: ${r.count}`),console.log(` store: ${r.vault} (0600)`),console.log(` key: ${r.backend==="keyfile"?r.key+" (0600)":r.backend==="keychain"?"(OS keychain \u2014 no key on disk)":"(derived from WYRM_VAULT_PASSPHRASE \u2014 no key on disk)"}`),console.log(` secure: ${r.secure?"yes \u2014 key is not a plaintext file beside the ciphertext":"NO \u2014 key sits beside ciphertext"}`),r.secure||console.log(r.keychainAvailable?" \u26A0 run `wyrm vault secure` to move the key into the OS keychain.":" \u26A0 no OS keychain found \u2014 set WYRM_VAULT_PASSPHRASE and run `wyrm vault secure --backend passphrase`.");break}default:m("usage: wyrm vault <setup|set|get|list|rm|exec|import-npm|secure|info>"),process.exitCode=1}}catch(r){m(`vault: ${r.message}`),process.exitCode=1}}function Mt(){const l=re(ne(import.meta.url)),t=[F(l,"..","skills"),F(l,"..","..","skills")].find(r=>q(r));if(!t)return[];const i=[];for(const r of Me(t,{withFileTypes:!0})){if(!r.isDirectory())continue;const e=F(t,r.name,"SKILL.md");if(!q(e))continue;let s=r.name,o="";try{const n=V(e,"utf8"),p=n.match(/^name:\s*(.+)$/m);if(p&&(s=p[1].trim()),/^description:\s*[|>]/m.test(n))o=((n.split(/^description:.*$/m)[1]??"").split(`
190
- `).find(g=>g.trim())??"").trim();else{const a=n.match(/^description:\s*(.+)$/m);a&&(o=a[1].trim())}}catch{}i.push({name:s,description:o})}return i.sort((r,e)=>r.name.localeCompare(e.name))}async function Ot(l){const c=l[0];if(!c||c==="help"||c==="--help"){console.log(`Usage:
190
+ `)}const[,,W,...v]=process.argv;function ke(l){const c=l.ref_table?`${l.ref_table}${l.ref_id?"#"+l.ref_id:""}`:"",e=new Date(l.created_at).toLocaleTimeString();return`#${l.cursor} ${l.kind.padEnd(15)} ${c.padEnd(16)} ${Je(l.actor)} ${e}`}async function Ut(l){const{positional:c,flags:e}=E(l),i=c[0]||"since",n=j();try{if(!n.liveMemoryEnabled()){m("Live Memory is disabled (set WYRM_LIVE_MEMORY=1)."),process.exitCode=1;return}const t=(typeof e.project=="string"?e.project:"")||process.cwd(),s=n.getProject(t)??n.getProjectByName(t);if(!s){m(`Project not found: ${t}`),process.exitCode=1;return}if(i==="publish"){const o=c[1]||(typeof e.kind=="string"?e.kind:"");if(!o){m("usage: wyrm events publish <kind> --project <p> [--actor A] [--ref-table T --ref-id ID]"),process.exitCode=1;return}n.publishEvent({projectId:s.id,kind:o,refTable:typeof e["ref-table"]=="string"?e["ref-table"]:void 0,refId:typeof e["ref-id"]=="string"?e["ref-id"]:void 0,actor:typeof e.actor=="string"?e.actor:void 0}),b(`Event published (${o}) to ${s.name}`);return}if(i==="since"){const o=I(e.cursor,0),r=I(e.limit,50),d=n.eventsSince(s.id,o,r);for(const a of d)console.log(ke(a));k(`${d.length} event(s) for '${s.name}' since cursor ${o}`);return}m("usage: wyrm events <publish|since> ..."),process.exitCode=1}finally{n.close()}}async function qt(l){const{flags:c}=E(l),e=j();if(!e.liveMemoryEnabled()){m("Live Memory is disabled (set WYRM_LIVE_MEMORY=1)."),e.close(),process.exitCode=1;return}const i=(typeof c.project=="string"?c.project:"")||process.cwd(),n=e.getProject(i)??x(e,i);if(!n){m(`Project not found: ${i}`),e.close(),process.exitCode=1;return}const t=Math.max(250,I(c.interval,1e3));let s=I(c.since,e.subscribeEvents(n.id,1).cursor);k(`Watching '${n.name}' (cursor ${s}, every ${t}ms) \u2014 Ctrl-C to stop`);const o=()=>{try{for(const a of e.eventsSince(n.id,s,200))s=a.cursor,console.log(ke(a))}catch{}};o();const r=setInterval(o,t),d=()=>{clearInterval(r);try{e.close()}catch{}process.exit(0)};process.on("SIGINT",d),process.on("SIGTERM",d)}async function Ft(l){const{flags:c}=E(l),{embedAll:e,removeAll:i,statusAll:n}=await import("./priority-embed.js"),t={projectDir:typeof c.project=="string"?c.project:void 0,allClients:c.all===!0},s=o=>console.log(` ${String(o.result??o.status).padEnd(9)} [${o.scope}] ${o.file}`);if(c.status){k("Wyrm priority embedding \u2014 status"),n(t).forEach(s);return}if(c.remove){k("Wyrm priority embedding \u2014 removed"),i(t).forEach(s);return}k("Wyrm is now FIRST-PRIORITY memory"),e(t).forEach(s);try{const{installClaudeCodeHooks:o,installClaudeStatusline:r}=await import("./autoconfig.js");o()?b("Proactive hooks installed (SessionStart rehydrate + capture + tool-trace)."):console.log(" (Claude Code not detected \u2014 skipped hook install.)");const a=r();a&&b(`Buddy statusline: ${a.message}`)}catch{}b("Wyrm will now be read first, primed proactively, and shown in the TUI at all times.")}async function Yt(l){const{flags:c}=E(l),{harvestProjects:e}=await import("./harvest.js"),{MemoryArtifacts:i}=await import("./memory-artifacts.js"),{escapeLikePattern:n}=await import("./auto-capture.js"),t=j();try{const s=t.getDatabase(),o=new i(s),r={existsBySig:(w,$)=>!!s.prepare("SELECT 1 FROM memory_artifacts WHERE project_id = ? AND tags LIKE ? ESCAPE '\\' LIMIT 1").get(w,"%"+n($)+"%"),addCandidate:(w,$)=>o.add(w,{kind:$.kind,problem:$.text,tags:[...$.tags,$.sig],confidence:$.confidence,needsReview:1,createdBy:"harvest"})},d=typeof c.project=="string"?c.project:void 0;let a;if(d){const w=t.getProject(d)??t.getProjectByName(d);if(!w){m(`Project not found: ${d}`),process.exitCode=1;return}a=[{id:w.id,name:w.name,path:w.path}]}else a=t.getAllProjects(500).map(w=>({id:w.id,name:w.name,path:w.path}));const u=c["dry-run"]===!0||c.dry===!0,g=c.code===!0||c["include-code"]===!0,{reports:f,totalAdded:y,totalSkipped:h}=e(r,a,{dryRun:u,gitLimit:I(c.limit,30),includeCode:g});k(`Harvest ${u?"(dry run) ":""}\u2014 ${y} candidate(s), ${h} already present (${a.length} project(s))`);for(const w of f.filter($=>$.added>0).sort(($,C)=>C.added-$.added).slice(0,25))console.log(` +${String(w.added).padStart(3)} (skip ${w.skipped}) ${w.project}`);if(g&&!u){const{SymbolGraph:w}=await import("./symbols.js"),$=new w(s);let C=0,S=0;for(const R of a)try{const _=$.indexProject(R.id,R.path);C+=_.symbols,S+=_.files}catch{}console.log(` \u{1F4D0} Indexed ${C} code symbols (${S} files) \u2192 searchable via 'wyrm search'`)}!u&&y>0&&b("Review with: wyrm review")}finally{t.close()}}async function Ht(l){const c=await import("./vault.js"),[e,...i]=l;try{switch(e){case"set":{const n=i[0];if(!n){m("usage: wyrm vault set <name> (the secret is read from STDIN, never argv)"),process.exitCode=1;return}if(process.stdin.isTTY){m(`pipe the secret in, e.g.: printf %s "$TOKEN" | wyrm vault set ${n}`),process.exitCode=1;return}const s=(await import("node:fs")).readFileSync(0,"utf8").replace(/\r?\n$/,"");if(!s){m("empty secret on stdin"),process.exitCode=1;return}c.vaultSet(n,s),b(`Stored "${n}" (AES-256-GCM). Use it without exposing it: wyrm vault exec ${n} -- <command>`);break}case"get":{const n=i[0];if(!n){m("usage: wyrm vault get <name>"),process.exitCode=1;return}const t=c.vaultGet(n);if(t===void 0){m(`no secret named "${n}"`),process.exitCode=1;return}process.stdout.write(t);break}case"list":case"ls":{const n=c.vaultList();if(!n.length){console.log("(vault is empty)");break}k(`Vault \u2014 ${n.length} secret(s)`);for(const t of n)console.log(" \u2022 "+t);break}case"rm":case"remove":case"delete":{const n=i[0];if(!n){m("usage: wyrm vault rm <name>"),process.exitCode=1;return}b(c.vaultRemove(n)?`Removed "${n}"`:`(no secret named "${n}")`);break}case"exec":{const n=i[0],t=i.indexOf("--");if(!n||t===-1||t+1>=i.length){m("usage: wyrm vault exec <name> [--as ENVVAR] -- <command...>"),process.exitCode=1;return}const s=i.slice(1,t),o=s.indexOf("--as"),r=o>=0?s[o+1]:n.toUpperCase().replace(/[^A-Z0-9]+/g,"_"),d=i.slice(t+1),a=c.vaultGet(n);if(a===void 0){m(`no secret named "${n}"`),process.exitCode=1;return}const u=X(d[0],d.slice(1),{stdio:"inherit",env:{...process.env,[r]:a}});process.exitCode=u.status??1;break}case"import-npm":{const n=await import("node:fs"),t=await import("node:os"),o=(await import("node:path")).join(t.homedir(),".npmrc");if(!n.existsSync(o)){m("~/.npmrc not found"),process.exitCode=1;return}const r=n.readFileSync(o,"utf8").match(/\/\/registry\.npmjs\.org\/:_authToken=(.+)/);if(!r){m("no npm authToken found in ~/.npmrc"),process.exitCode=1;return}c.vaultSet("npm-token",r[1].trim()),b('Imported npm token \u2192 vault as "npm-token". You can now scrub the plaintext from ~/.npmrc and use: wyrm vault exec npm-token --as NODE_AUTH_TOKEN -- npm publish');break}case"setup":{const n=c.vaultPaths();if(n.secure)b(`Vault is already secure (backend: ${n.backend}, ${n.count} secret(s)).`);else{const t=n.keychainAvailable?"keychain":"passphrase";if(t==="passphrase"&&!process.env.WYRM_VAULT_PASSPHRASE){m("No OS keychain on this host. Set WYRM_VAULT_PASSPHRASE, then re-run: wyrm vault setup"),process.exitCode=1;return}const s=c.vaultSecure({backend:t});b(`Vault secured: ${s.from} \u2192 ${s.to}${s.rotated?" (key rotated)":""}.`),s.keyfileShredded&&console.log(" plaintext vault.key: shredded \u2714"),s.backup&&console.log(` ciphertext backup: ${s.backup} (delete once confirmed)`)}k("Store & use credentials safely"),console.log(' store: printf %s "$TOKEN" | wyrm vault set <name> # reads STDIN \u2014 never argv/shell history'),console.log(" use: wyrm vault exec <name> --as ENV_VAR -- <cmd> # injected as env var, never printed"),console.log(" list: wyrm vault list inspect: wyrm vault info");break}case"secure":{const n=i.indexOf("--backend"),t=n>=0?i[n+1]:"keychain";if(t!=="keychain"&&t!=="passphrase"){m("usage: wyrm vault secure [--backend keychain|passphrase] [--no-rotate]"),process.exitCode=1;return}if(t==="passphrase"&&!process.env.WYRM_VAULT_PASSPHRASE){m("set WYRM_VAULT_PASSPHRASE before: wyrm vault secure --backend passphrase"),process.exitCode=1;return}const s=!i.includes("--no-rotate"),o=c.vaultSecure({backend:t,rotate:s});b(`Vault secured: ${o.from} \u2192 ${o.to}${o.rotated?" (key rotated)":""}.`),console.log(` secrets re-encrypted: ${o.secrets}`),o.keyfileShredded&&console.log(" plaintext vault.key: shredded \u2714"),o.backup&&console.log(` ciphertext backup: ${o.backup} (delete once you've confirmed)`),console.log(t==="keychain"?" master key now lives in the OS keychain \u2014 not on disk.":" master key now derived from WYRM_VAULT_PASSPHRASE \u2014 keep that set for future use.");break}case"info":{const n=c.vaultPaths();k("Vault"),console.log(` backend: ${n.backend}`),console.log(` secrets: ${n.count}`),console.log(` store: ${n.vault} (0600)`),console.log(` key: ${n.backend==="keyfile"?n.key+" (0600)":n.backend==="keychain"?"(OS keychain \u2014 no key on disk)":"(derived from WYRM_VAULT_PASSPHRASE \u2014 no key on disk)"}`),console.log(` secure: ${n.secure?"yes \u2014 key is not a plaintext file beside the ciphertext":"NO \u2014 key sits beside ciphertext"}`),n.secure||console.log(n.keychainAvailable?" \u26A0 run `wyrm vault secure` to move the key into the OS keychain.":" \u26A0 no OS keychain found \u2014 set WYRM_VAULT_PASSPHRASE and run `wyrm vault secure --backend passphrase`.");break}default:m("usage: wyrm vault <setup|set|get|list|rm|exec|import-npm|secure|info>"),process.exitCode=1}}catch(n){m(`vault: ${n.message}`),process.exitCode=1}}function Vt(){const l=z(Q(import.meta.url)),e=[O(l,"..","skills"),O(l,"..","..","skills")].find(n=>L(n));if(!e)return[];const i=[];for(const n of Ae(e,{withFileTypes:!0})){if(!n.isDirectory())continue;const t=O(e,n.name,"SKILL.md");if(!L(t))continue;let s=n.name,o="";try{const r=Y(t,"utf8"),d=r.match(/^name:\s*(.+)$/m);if(d&&(s=d[1].trim()),/^description:\s*[|>]/m.test(r))o=((r.split(/^description:.*$/m)[1]??"").split(`
191
+ `).find(g=>g.trim())??"").trim();else{const a=r.match(/^description:\s*(.+)$/m);a&&(o=a[1].trim())}}catch{}i.push({name:s,description:o})}return i.sort((n,t)=>n.name.localeCompare(t.name))}async function Bt(l){const c=l[0];if(!c||c==="help"||c==="--help"){console.log(`Usage:
191
192
  wyrm skill list show the bundled guides + your registered skills
192
193
  wyrm skill backfill-content read every skill's SKILL.md into the registry (idempotent)
193
194
  wyrm skill export <targetDir> [--all] materialize SKILL.md files from stored content
194
- wyrm skill share <name|--all|--tier T> [--public|--private] [--include-inactive] set cloud-sync visibility (single or bulk)`);return}if(c==="list"||c==="ls"){const i=Mt();if(k("Bundled skill guides (shipped in the box)"),i.length===0)console.log(d.yellow(" none found next to this install"));else{for(const e of i)console.log(` ${d.cyan(e.name)}`),e.description&&console.log(` ${d.dim(e.description)}`);console.log(d.dim("\n Read one: open its SKILL.md, or `wyrm skill export <dir>` to materialize all."))}const r=j();try{const e=r.listSkills(!0);if(k(`Registered skills in your memory (${e.length})`),e.length===0)console.log(d.dim(" none yet \u2014 register with wyrm_skill_register or author with wyrm_skill_create"));else{for(const s of e.slice(0,40))console.log(` ${d.cyan(s.name)}${s.tier?d.dim(" ["+s.tier+"]"):""}`);e.length>40&&console.log(d.dim(` \u2026 and ${e.length-40} more`))}}finally{r.close()}return}const t=j();try{if(c==="backfill-content"||c==="backfill"){k("Backfill SKILL.md content into the registry");const i=t.backfillSkillContent();b(`${i.filled} filled . ${i.unchanged} unchanged . ${i.missing} missing-file (of ${i.total} registered)`),i.missing>0&&console.log(d.yellow(` ${i.missing} skill(s) had no readable SKILL.md \u2014 re-run after restoring their files.`));return}if(c==="export"){const i=l[1];i||(m("Usage: wyrm skill export <targetDir> [--all]"),t.close(),process.exit(1));const r=l.includes("--all");k(`Export skills \u2192 ${i}`);const e=t.exportSkillContent(i,{includeInactive:r});b(`${e.written} SKILL.md written . ${e.skipped_no_content} skipped (no stored content) (of ${e.total} ${r?"total":"active"})`),e.skipped_no_content>0&&console.log(d.yellow(" Run `wyrm skill backfill-content` on the source machine to populate content first.")),e.collisions>0&&console.log(d.yellow(` ${e.collisions} slug collision(s) disambiguated with a name-hash suffix (distinct skills, same slug) \u2014 no content lost.`));return}if(c==="share"){const i=l.includes("--private")?"within":l.includes("--public")?"public":"org",r=l.includes("--include-inactive"),e=l.indexOf("--tier"),s=e>=0?l[e+1]:void 0;if(e>=0&&(!s||s.startsWith("--"))&&(m("Usage: wyrm skill share --tier <god|mega|atomic> [--public|--private] [--include-inactive]"),t.close(),process.exit(1)),l.includes("--all")||!!s){const a=t.setAllSkillsVisibility(i,{tier:s,includeInactive:r}),u=s?`tier '${s}'`:r?"all skills":"all active skills";b(`${a} skill(s) (${u}) visibility \u2192 '${i}'.`),console.log(i==="within"?" These skills will NOT egress on cloud sync (private).":` ${a} skills are now cloud-sync-eligible (they leave on the next \`wyrm cloud sync\`).`);return}const n=l[1];n||(m("Usage: wyrm skill share <name|--all|--tier <tier>> [--public|--private] [--include-inactive]"),t.close(),process.exit(1)),t.setSkillVisibility(n,i)||(m(`Skill not found: ${n}`),t.close(),process.exit(1)),b(`Skill "${n}" visibility \u2192 '${i}'.`),console.log(i==="within"?" This skill will NOT egress on cloud sync (private).":" This skill is now cloud-sync-eligible (it leaves on the next `wyrm cloud sync`).");return}m(`Unknown skill subcommand: ${c}`),process.exit(1)}finally{t.close()}}async function At(l){const c=l[0]??"status",t=j(),i=t.getDatabase();if(i.prepare("PRAGMA table_info(projects)").all().some(e=>e.name==="sync_policy")||(m("Grove sync policy is not on this database yet (upgrade Wyrm so migrations apply)."),t.close(),process.exit(1)),c==="status"||c==="ls"||c==="list"){k("Grove sync policy + leak audit");const e=i.prepare("SELECT id, name, sync_policy FROM projects ORDER BY id").all(),s=["ground_truths","memory_artifacts","quests","design_tokens","design_references"],o=["ground_truths","memory_artifacts","quests","sessions","decision_edges"],n=(u,g,f)=>{let y=0;for(const h of u)try{y+=i.prepare(`SELECT COUNT(*) AS n FROM ${h} WHERE project_id = ? AND ${f}`).get(g).n}catch{}return y},p=[];let a=0;for(const u of e){const g=n(s,u.id,"cross_project_visibility IN ('org','public')"),f=n(o,u.id,"is_shared = 1"),y=u.sync_policy==="private"&&g+f>0;y&&a++;const h=y?d.red(`LEAK: ${g} promoted + ${f} shared in a PRIVATE grove`):`${g} promoted / ${f} shared`;p.push([String(u.id),u.name,u.sync_policy,h])}console.log(D(["#","Grove","sync_policy","rows eligible to leave"],p)),console.log(`
195
- private = never replicates . cloud = your own cloud backup . team = federates to a team Wyrm`),a>0&&console.log(d.red(`
196
- ! ${a} private grove(s) hold rows marked to leave. Re-private those rows or change the grove lane.`)),t.close();return}if(c==="policy"||c==="set"){const e=l[1],s=l[2];(!e||!["private","cloud","team"].includes(s))&&(m("Usage: wyrm grove policy <project|id> <private|cloud|team>"),t.close(),process.exit(1));let o=x(t,e);!o&&/^\d+$/.test(e)&&(o=i.prepare("SELECT id, name FROM projects WHERE id = ?").get(Number(e))),o||(m(`Grove not found: ${e}`),t.close(),process.exit(1)),i.prepare("UPDATE projects SET sync_policy = ? WHERE id = ?").run(s,o.id),b(`Grove "${o.name}" set to '${s}'.`),s!=="private"&&console.log(` Rows still only leave when also marked ${s==="team"?"is_shared (team)":"org/public (cloud)"}. The grove is the outer gate.`),t.close();return}m(`Unknown grove subcommand: ${c}`),console.log(`Usage:
195
+ wyrm skill share <name|--all|--tier T> [--public|--private] [--include-inactive] set cloud-sync visibility (single or bulk)`);return}if(c==="list"||c==="ls"){const i=Vt();if(k("Bundled skill guides (shipped in the box)"),i.length===0)console.log(p.yellow(" none found next to this install"));else{for(const t of i)console.log(` ${p.cyan(t.name)}`),t.description&&console.log(` ${p.dim(t.description)}`);console.log(p.dim("\n Read one: open its SKILL.md, or `wyrm skill export <dir>` to materialize all."))}const n=j();try{const t=n.listSkills(!0);if(k(`Registered skills in your memory (${t.length})`),t.length===0)console.log(p.dim(" none yet \u2014 register with wyrm_skill_register or author with wyrm_skill_create"));else{for(const s of t.slice(0,40))console.log(` ${p.cyan(s.name)}${s.tier?p.dim(" ["+s.tier+"]"):""}`);t.length>40&&console.log(p.dim(` \u2026 and ${t.length-40} more`))}}finally{n.close()}return}const e=j();try{if(c==="backfill-content"||c==="backfill"){k("Backfill SKILL.md content into the registry");const i=e.backfillSkillContent();b(`${i.filled} filled . ${i.unchanged} unchanged . ${i.missing} missing-file (of ${i.total} registered)`),i.missing>0&&console.log(p.yellow(` ${i.missing} skill(s) had no readable SKILL.md \u2014 re-run after restoring their files.`));return}if(c==="export"){const i=l[1];i||(m("Usage: wyrm skill export <targetDir> [--all]"),e.close(),process.exit(1));const n=l.includes("--all");k(`Export skills \u2192 ${i}`);const t=e.exportSkillContent(i,{includeInactive:n});b(`${t.written} SKILL.md written . ${t.skipped_no_content} skipped (no stored content) (of ${t.total} ${n?"total":"active"})`),t.skipped_no_content>0&&console.log(p.yellow(" Run `wyrm skill backfill-content` on the source machine to populate content first.")),t.collisions>0&&console.log(p.yellow(` ${t.collisions} slug collision(s) disambiguated with a name-hash suffix (distinct skills, same slug) \u2014 no content lost.`));return}if(c==="share"){const i=l.includes("--private")?"within":l.includes("--public")?"public":"org",n=l.includes("--include-inactive"),t=l.indexOf("--tier"),s=t>=0?l[t+1]:void 0;if(t>=0&&(!s||s.startsWith("--"))&&(m("Usage: wyrm skill share --tier <god|mega|atomic> [--public|--private] [--include-inactive]"),e.close(),process.exit(1)),l.includes("--all")||!!s){const a=e.setAllSkillsVisibility(i,{tier:s,includeInactive:n}),u=s?`tier '${s}'`:n?"all skills":"all active skills";b(`${a} skill(s) (${u}) visibility \u2192 '${i}'.`),console.log(i==="within"?" These skills will NOT egress on cloud sync (private).":` ${a} skills are now cloud-sync-eligible (they leave on the next \`wyrm cloud sync\`).`);return}const r=l[1];r||(m("Usage: wyrm skill share <name|--all|--tier <tier>> [--public|--private] [--include-inactive]"),e.close(),process.exit(1)),e.setSkillVisibility(r,i)||(m(`Skill not found: ${r}`),e.close(),process.exit(1)),b(`Skill "${r}" visibility \u2192 '${i}'.`),console.log(i==="within"?" This skill will NOT egress on cloud sync (private).":" This skill is now cloud-sync-eligible (it leaves on the next `wyrm cloud sync`).");return}m(`Unknown skill subcommand: ${c}`),process.exit(1)}finally{e.close()}}async function Kt(l){const c=l[0]??"status",e=j(),i=e.getDatabase();if(i.prepare("PRAGMA table_info(projects)").all().some(t=>t.name==="sync_policy")||(m("Grove sync policy is not on this database yet (upgrade Wyrm so migrations apply)."),e.close(),process.exit(1)),c==="status"||c==="ls"||c==="list"){k("Grove sync policy + leak audit");const t=i.prepare("SELECT id, name, sync_policy FROM projects ORDER BY id").all(),s=["ground_truths","memory_artifacts","quests","design_tokens","design_references"],o=["ground_truths","memory_artifacts","quests","sessions","decision_edges"],r=(u,g,f)=>{let y=0;for(const h of u)try{y+=i.prepare(`SELECT COUNT(*) AS n FROM ${h} WHERE project_id = ? AND ${f}`).get(g).n}catch{}return y},d=[];let a=0;for(const u of t){const g=r(s,u.id,"cross_project_visibility IN ('org','public')"),f=r(o,u.id,"is_shared = 1"),y=u.sync_policy==="private"&&g+f>0;y&&a++;const h=y?p.red(`LEAK: ${g} promoted + ${f} shared in a PRIVATE grove`):`${g} promoted / ${f} shared`;d.push([String(u.id),u.name,u.sync_policy,h])}console.log(D(["#","Grove","sync_policy","rows eligible to leave"],d)),console.log(`
196
+ private = never replicates . cloud = your own cloud backup . team = federates to a team Wyrm`),a>0&&console.log(p.red(`
197
+ ! ${a} private grove(s) hold rows marked to leave. Re-private those rows or change the grove lane.`)),e.close();return}if(c==="policy"||c==="set"){const t=l[1],s=l[2];(!t||!["private","cloud","team"].includes(s))&&(m("Usage: wyrm grove policy <project|id> <private|cloud|team>"),e.close(),process.exit(1));let o=x(e,t);!o&&/^\d+$/.test(t)&&(o=i.prepare("SELECT id, name FROM projects WHERE id = ?").get(Number(t))),o||(m(`Grove not found: ${t}`),e.close(),process.exit(1)),i.prepare("UPDATE projects SET sync_policy = ? WHERE id = ?").run(s,o.id),b(`Grove "${o.name}" set to '${s}'.`),s!=="private"&&console.log(` Rows still only leave when also marked ${s==="team"?"is_shared (team)":"org/public (cloud)"}. The grove is the outer gate.`),e.close();return}m(`Unknown grove subcommand: ${c}`),console.log(`Usage:
197
198
  wyrm grove status
198
- wyrm grove policy <project|id> <private|cloud|team>`),t.close(),process.exit(1)}async function Lt(l){const c=l[0],{flags:t}=C(l.slice(1)),{ulid:i}=await import("./ulid.js"),{createRun:r,setRunStatus:e,registerAgent:s,getRun:o,getAgents:n}=await import("./handlers/run.js"),{sanitizeActorId:p}=await import("./handlers/boundary.js"),a=f=>typeof f=="string"?p(f):null,u=j(),g=u.getDatabase();try{switch(c){case"start":{const f=(typeof t.orchestrator=="string"?t.orchestrator:"cli").slice(0,200),y=a(t.agent)??p(f)??"cli",h=a(t.parent);if(h&&!o(g,h)){m(`Parent run not found: ${h}`),process.exitCode=1;return}const w=i();r(g,w,h,f),s(g,w,y,"orchestrator"),process.stdout.write(w+`
199
+ wyrm grove policy <project|id> <private|cloud|team>`),e.close(),process.exit(1)}async function Gt(l){const c=l[0],{flags:e}=E(l.slice(1)),{ulid:i}=await import("./ulid.js"),{createRun:n,setRunStatus:t,registerAgent:s,getRun:o,getAgents:r}=await import("./handlers/run.js"),{sanitizeActorId:d}=await import("./handlers/boundary.js"),a=f=>typeof f=="string"?d(f):null,u=j(),g=u.getDatabase();try{switch(c){case"start":{const f=(typeof e.orchestrator=="string"?e.orchestrator:"cli").slice(0,200),y=a(e.agent)??d(f)??"cli",h=a(e.parent);if(h&&!o(g,h)){m(`Parent run not found: ${h}`),process.exitCode=1;return}const w=i();n(g,w,h,f),s(g,w,y,"orchestrator"),process.stdout.write(w+`
199
200
  `),process.stderr.write(`Run ${w} started by ${f}.
200
- `);break}case"join":{const f=a(t.run),y=a(t.agent);if(!f||!y){m("Usage: wyrm run join --run <RUNID> --agent <ID> [--role <ROLE>]"),process.exitCode=1;return}if(!o(g,f)){m(`Run not found: ${f}`),process.exitCode=1;return}const h=typeof t.role=="string"?t.role.slice(0,200):null;s(g,f,y,h),b(`${y} joined run ${f}${h?` as ${h}`:""}.`);break}case"end":{const f=a(t.run);if(!f){m("Usage: wyrm run end --run <RUNID> [--status completed|failed|abandoned]"),process.exitCode=1;return}if(!o(g,f)){m(`Run not found: ${f}`),process.exitCode=1;return}const y=typeof t.status=="string"?t.status:"completed",w=["completed","failed","abandoned"].includes(y)?y:"completed";e(g,f,w),b(`Run ${f} ended (${w}).`);break}case"status":{const f=a(t.run);if(!f){m("Usage: wyrm run status --run <RUNID>"),process.exitCode=1;return}const y=o(g,f);if(!y){m(`Run not found: ${f}`),process.exitCode=1;return}const h=n(g,f);k(`Run ${y.run_id} [${y.status}]`),y.orchestrator&&console.log(d.bold("Orchestrator: ")+y.orchestrator),y.parent_run_id&&console.log(d.bold("Parent: ")+y.parent_run_id),console.log(d.bold("Created: ")+y.created_at),console.log(d.bold("Updated: ")+y.updated_at),h.length>0?console.log(D(["Agent","Role","Joined"],h.map(w=>[w.agent_id,w.role??"",w.joined_at]))):console.log(d.dim(" No agents registered."));break}default:m("Usage: wyrm run <start|join|end|status> [options]"),console.log(" wyrm run start [--orchestrator NAME] [--agent ID] [--parent RUNID]"),console.log(" wyrm run join --run RUNID --agent ID [--role ROLE]"),console.log(" wyrm run end --run RUNID [--status completed|failed|abandoned]"),console.log(" wyrm run status --run RUNID"),process.exitCode=1;return}}finally{u.close()}}if(A==="--version"||A==="-v"||A==="version"){const l=M();console.log(`${l.name??"wyrm-mcp"} v${l.version??"unknown"}`)}else!A||A==="--help"||A==="-h"||A==="help"?be():(async()=>{try{switch(A){case"search":await Ge(v);break;case"ls":await Je(v);break;case"show":await ze(v);break;case"capture":await Qe(v);break;case"rehydrate":await Ze(v);break;case"session":await st(v);break;case"presence":await rt(v);break;case"digest":await Xe(v);break;case"bridge":await ot(v);break;case"metabolize":await et(v);break;case"entities":await tt(v);break;case"render":await nt(v);break;case"reverse-bridge":await it(v);break;case"import":await at(v);break;case"stats":await lt(v);break;case"review":await dt(v);break;case"sync":await pt(v);break;case"cloud":{const{cmdCloud:l}=await import("./cloud/cli.js");await l(v);break}case"grove":await At(v);break;case"run":await Lt(v);break;case"skill":await Ot(v);break;case"prune":await ut(v);break;case"license":await ft();break;case"login":await gt();break;case"activate":await yt(v);break;case"maintenance":await ht(v);break;case"doctor":await wt(v);break;case"failure":await bt(v);break;case"project":await $t(v);break;case"index":await vt(v);break;case"update":await St(v);break;case"prompt":await _t(v);break;case"hours":await jt(v);break;case"invoice":await Ct(v);break;case"agent":await Et(v);break;case"feedback":await xt(v);break;case"setup":await Rt(v);break;case"intro":{const{renderIntro:l}=await import("./visibility.js");console.log(l(M().version??"unknown"));break}case"events":await Tt(v);break;case"watch":await Dt(v);break;case"embed":await It(v);break;case"harvest":await Pt(v);break;case"vault":await Nt(v);break;case"connector":case"connectors":await mt(v);break;case"statusline":{const{installClaudeStatusline:l,removeClaudeStatusline:c}=await import("./autoconfig.js"),t=v.includes("--remove")?c():l();t?b(t.message):m("Claude Code not detected (~/.claude missing).");break}case"guard":{const{installWyrmGuardHooks:l,removeWyrmGuardHooks:c,wyrmGuardHookStatus:t}=await import("./autoconfig.js");if(v.includes("--status")){const r=t();if(console.log(` settings: ${r.settingsPath}`),r.installed){b(`wyrm-guard hooks installed (${r.commands.length} entr${r.commands.length===1?"y":"ies"})`);for(const e of r.commands)console.log(d.dim(` ${e}`))}else console.log(d.yellow(" wyrm-guard hooks not installed \u2014 run: wyrm guard"));break}const i=v.includes("--remove")||v.includes("--uninstall")?c():l();i?i.action==="failed"?(m(i.message),process.exitCode=1):b(i.message):m("Claude Code not detected (~/.claude missing).");break}case"ui":case"dashboard":v.includes("--ui")||v.push("--ui");case"serve":{const l=v.includes("--ui");if(l){const{enableDevMode:r}=await import("./http-auth.js");r()}const{server:c}=await import("./http-fast.js"),t=parseInt(process.env.WYRM_PORT??process.env.PORT??"3333",10),i=process.env.WYRM_BIND_HOST||"127.0.0.1";c.listen(t,i,()=>{if(b(`Wyrm HTTP server running on ${i}:${t}`),process.env.WYRM_UI_READONLY==="1"&&console.log("\u{1F512} READ-ONLY mode: writes + off-box egress are blocked; safe to expose."),l){const r=`http://localhost:${t}/ui`;console.log(`\u{1F5A5}\uFE0F Dashboard: ${r}`),import("child_process").then(({spawn:e})=>{const s=process.platform;try{const o=s==="darwin"?e("open",[r],{stdio:"ignore",detached:!0}):s==="win32"?e("cmd",["/c","start","",r],{stdio:"ignore",detached:!0}):e("xdg-open",[r],{stdio:"ignore",detached:!0});o.on("error",()=>{}),o.unref()}catch{}}).catch(()=>{})}});break}default:m(`Unknown command: ${A}`),be(),process.exit(1)}}catch(l){m(String(l)),process.exit(1)}})();
201
+ `);break}case"join":{const f=a(e.run),y=a(e.agent);if(!f||!y){m("Usage: wyrm run join --run <RUNID> --agent <ID> [--role <ROLE>]"),process.exitCode=1;return}if(!o(g,f)){m(`Run not found: ${f}`),process.exitCode=1;return}const h=typeof e.role=="string"?e.role.slice(0,200):null;s(g,f,y,h),b(`${y} joined run ${f}${h?` as ${h}`:""}.`);break}case"end":{const f=a(e.run);if(!f){m("Usage: wyrm run end --run <RUNID> [--status completed|failed|abandoned]"),process.exitCode=1;return}if(!o(g,f)){m(`Run not found: ${f}`),process.exitCode=1;return}const y=typeof e.status=="string"?e.status:"completed",w=["completed","failed","abandoned"].includes(y)?y:"completed";t(g,f,w),b(`Run ${f} ended (${w}).`);break}case"status":{const f=a(e.run);if(!f){m("Usage: wyrm run status --run <RUNID>"),process.exitCode=1;return}const y=o(g,f);if(!y){m(`Run not found: ${f}`),process.exitCode=1;return}const h=r(g,f);k(`Run ${y.run_id} [${y.status}]`),y.orchestrator&&console.log(p.bold("Orchestrator: ")+y.orchestrator),y.parent_run_id&&console.log(p.bold("Parent: ")+y.parent_run_id),console.log(p.bold("Created: ")+y.created_at),console.log(p.bold("Updated: ")+y.updated_at),h.length>0?console.log(D(["Agent","Role","Joined"],h.map(w=>[w.agent_id,w.role??"",w.joined_at]))):console.log(p.dim(" No agents registered."));break}default:m("Usage: wyrm run <start|join|end|status> [options]"),console.log(" wyrm run start [--orchestrator NAME] [--agent ID] [--parent RUNID]"),console.log(" wyrm run join --run RUNID --agent ID [--role ROLE]"),console.log(" wyrm run end --run RUNID [--status completed|failed|abandoned]"),console.log(" wyrm run status --run RUNID"),process.exitCode=1;return}}finally{u.close()}}if(W==="--version"||W==="-v"||W==="version"){const l=M();console.log(`${l.name??"wyrm-mcp"} v${l.version??"unknown"}`)}else!W||W==="--help"||W==="-h"||W==="help"?ve():(async()=>{try{switch(W){case"search":await et(v);break;case"ls":await tt(v);break;case"show":await ot(v);break;case"capture":await st(v);break;case"rehydrate":await nt(v);break;case"session":await lt(v);break;case"presence":await dt(v);break;case"digest":await rt(v);break;case"bridge":await ct(v);break;case"metabolize":await it(v);break;case"entities":await at(v);break;case"render":await pt(v);break;case"reverse-bridge":await ut(v);break;case"import":await mt(v);break;case"stats":await gt(v);break;case"review":await yt(v);break;case"sync":await ht(v);break;case"cloud":{const{cmdCloud:l}=await import("./cloud/cli.js");await l(v);break}case"grove":await Kt(v);break;case"run":await Gt(v);break;case"skill":await Bt(v);break;case"prune":await wt(v);break;case"license":await $t();break;case"login":await vt();break;case"activate":await kt(v);break;case"maintenance":await _t(v);break;case"doctor":await Rt(v);break;case"metrics":await St(v);break;case"failure":await xt(v);break;case"project":await Tt(v);break;case"index":await Dt(v);break;case"update":await Pt(v);break;case"prompt":await Nt(v);break;case"hours":await Mt(v);break;case"invoice":await Ot(v);break;case"agent":await At(v);break;case"feedback":await Wt(v);break;case"setup":await Lt(v);break;case"intro":{const{renderIntro:l}=await import("./visibility.js");console.log(l(M().version??"unknown"));break}case"events":await Ut(v);break;case"watch":await qt(v);break;case"embed":await Ft(v);break;case"harvest":await Yt(v);break;case"vault":await Ht(v);break;case"connector":case"connectors":await bt(v);break;case"statusline":{const{installClaudeStatusline:l,removeClaudeStatusline:c}=await import("./autoconfig.js"),e=v.includes("--remove")?c():l();e?b(e.message):m("Claude Code not detected (~/.claude missing).");break}case"guard":{const{installWyrmGuardHooks:l,removeWyrmGuardHooks:c,wyrmGuardHookStatus:e}=await import("./autoconfig.js");if(v.includes("--status")){const n=e();if(console.log(` settings: ${n.settingsPath}`),n.installed){b(`wyrm-guard hooks installed (${n.commands.length} entr${n.commands.length===1?"y":"ies"})`);for(const t of n.commands)console.log(p.dim(` ${t}`))}else console.log(p.yellow(" wyrm-guard hooks not installed \u2014 run: wyrm guard"));break}const i=v.includes("--remove")||v.includes("--uninstall")?c():l();i?i.action==="failed"?(m(i.message),process.exitCode=1):b(i.message):m("Claude Code not detected (~/.claude missing).");break}case"ui":case"dashboard":v.includes("--ui")||v.push("--ui");case"serve":{const l=v.includes("--ui");if(l){const{enableDevMode:n}=await import("./http-auth.js");n()}const{server:c}=await import("./http-fast.js"),e=parseInt(process.env.WYRM_PORT??process.env.PORT??"3333",10),i=process.env.WYRM_BIND_HOST||"127.0.0.1";c.listen(e,i,()=>{if(b(`Wyrm HTTP server running on ${i}:${e}`),process.env.WYRM_UI_READONLY==="1"&&console.log("\u{1F512} READ-ONLY mode: writes + off-box egress are blocked; safe to expose."),l){const n=`http://localhost:${e}/ui`;console.log(`\u{1F5A5}\uFE0F Dashboard: ${n}`),import("child_process").then(({spawn:t})=>{const s=process.platform;try{const o=s==="darwin"?t("open",[n],{stdio:"ignore",detached:!0}):s==="win32"?t("cmd",["/c","start","",n],{stdio:"ignore",detached:!0}):t("xdg-open",[n],{stdio:"ignore",detached:!0});o.on("error",()=>{}),o.unref()}catch{}}).catch(()=>{})}});break}default:m(`Unknown command: ${W}`),ve(),process.exit(1)}}catch(l){m(String(l)),process.exit(1)}})();
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "contract": "wyrm-cloud-memory-v1",
3
- "version": "8.3.1",
3
+ "version": "8.4.0",
4
4
  "generatedBy": "scripts/gen-tool-manifest.mjs (from the dist/ registry modules — never hand-edited)",
5
5
  "tiers": {
6
6
  "core": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wyrm-mcp",
3
- "version": "8.3.1",
3
+ "version": "8.4.0",
4
4
  "mcpName": "lk.ghosts/wyrm",
5
5
  "description": "Local-first persistent memory for AI agents over MCP. Ground truths, negative learning (recorded failures block repeats), decision causality, hybrid recall, live memory streams, run-attributed fleet memory — a structured SQLite memory on your machine, no cloud or LLM required. Claude / Copilot / Cursor / Windsurf / Codex.",
6
6
  "type": "module",