wyrm-mcp 9.2.1 → 9.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,90 +1,92 @@
1
- import{TOOL_ANNOTATIONS as u}from"../tool-annotations.js";import{CloudSyncDaemon as h}from"../cloud-sync.js";import{getCrypto as g,initializeCrypto as f}from"../crypto.js";import{activateLicense as k,getLicenseInfo as w,getTier as v,hasFeature as d}from"../license.js";import{loadSession as x}from"../cloud/client.js";import{execFile as b}from"node:child_process";import{promisify as _}from"node:util";import{fileURLToPath as $}from"node:url";import{dirname as S,join as E}from"node:path";const T=_(b);async function P(r){const c=x();if(!c||!c.device_id)return{ok:!1,text:`\u{F115D} Sovereign cloud sync: this device is not logged in.
1
+ import{TOOL_ANNOTATIONS as d}from"../tool-annotations.js";import{CloudSyncDaemon as h}from"../cloud-sync.js";import{getCrypto as f,initializeCrypto as g}from"../crypto.js";import{activateLicense as k,getLicenseInfo as w,getTier as v,hasFeature as y}from"../license.js";import{loadSession as x}from"../cloud/client.js";import{execFile as b}from"node:child_process";import{promisify as _}from"node:util";import{fileURLToPath as $}from"node:url";import{dirname as S,join as E}from"node:path";const P=_(b);async function T(a){const l=x();if(!l||!l.device_id)return{ok:!1,text:`\u{F115D} Sovereign cloud sync: this device is not logged in.
2
2
  The login is an interactive device flow, so run it in a terminal:
3
3
  wyrm cloud login
4
- Then retry this action. (Status/sync here drive an already-registered device.)`};const a=E(S($(import.meta.url)),"..","wyrm-cli.js");try{const{stdout:o,stderr:i}=await T(process.execPath,[a,"cloud",...r],{timeout:18e4,maxBuffer:4194304,env:{...process.env}});return{ok:!0,text:(o||"").trim()||(i||"").trim()||"(no output)"}}catch(o){return{ok:!1,text:`\u{F115D} Sovereign cloud sync failed.
5
- ${[o?.stdout,o?.stderr].filter(Boolean).join(`
6
- `).trim()||(o?.message??"unknown error")}`}}}const q=[{name:"wyrm_license",description:"View current license status, tier, and available features",inputSchema:{type:"object",properties:{}},annotations:u.wyrm_license,aliases:[],handler:async(r,c)=>{const{cachedResponse:a,sync:o}=c,i=r||{},n=w(),e=v();let t=`\u{F115D} **Wyrm License Status**
7
-
8
- `;if(t+=`- **Tier:** ${e.charAt(0).toUpperCase()+e.slice(1)}
9
- `,n.valid&&n.key){t+=`- **Key:** ${n.key}
10
- `,t+=`- **Issued To:** ${n.issuedTo||"Unknown"}
11
- `,t+=`- **Expires:** ${n.expiresAt?new Date(n.expiresAt).toLocaleDateString():"Never"}
12
- `,t+=`- **Status:** \u2713 Valid
13
-
14
- `,t+=`### Features
15
- `;for(const l of n.features)t+=`- \u2713 ${l}
16
- `}else t+=`- **Status:** Free tier (no license key)
17
-
18
- `,t+=`### Free Features
19
- `,t+=`- \u2713 Local SQLite storage
20
- `,t+=`- \u2713 32 MCP tools
21
- `,t+=`- \u2713 FTS5 search
22
- `,t+=`- \u2713 File-based sync
23
-
24
- `,t+=`### Upgrade to Pro
25
- `,t+=`- Analytics dashboard & cost tracking
26
- `,t+=`- Cloud backup to R2
27
- `,t+=`- Encryption at rest
28
- `,t+=`- Priority support
29
-
30
- `,t+=`### Already have a license?
31
- `,t+="Activate it \u2014 paste the license JSON into the `wyrm_activate` tool, or save it to `~/.wyrm/license.json` and restart Wyrm.\n",t+="Manage or download your license at https://account.ghosts.lk \xB7 buy one at https://ghosts.lk/wyrm.";return a(t)}},{name:"wyrm_activate",description:"Activate a license key to unlock Pro/Team/Enterprise features",inputSchema:{type:"object",properties:{license:{type:"string",description:"License JSON string (signed license from Ghost Protocol)"}},required:["license"]},annotations:u.wyrm_activate,aliases:[],handler:async(r,c)=>{const a=r?.license;if(!a)throw new Error("License JSON string required");try{const o=k(a);return o.valid?{content:[{type:"text",text:`\u{F115D} \u2713 License activated!
32
-
33
- - **Tier:** ${o.tier}
34
- - **Features:** ${o.features.join(", ")}
35
-
36
- Restart Wyrm to apply all features.`}]}:{content:[{type:"text",text:`\u{F115D} \u2717 License activation failed: ${o.error||"unknown error"}`}],isError:!0}}catch{return{content:[{type:"text",text:"\u{F115D} \u2717 Invalid license format. Please verify your license key."}],isError:!0}}}},{name:"wyrm_analytics_dashboard",description:"View usage analytics dashboard \u2014 tool calls, tokens, costs, cache efficiency (Pro+)",inputSchema:{type:"object",properties:{days:{type:"number",description:"Number of days to analyze (default: 30)"}}},annotations:u.wyrm_analytics_dashboard,aliases:[],handler:async(r,c)=>{const{analytics:a}=c,o=r||{};if(!d("analytics"))return{content:[{type:"text",text:`\u{F115D} Analytics requires a Pro license or higher.
37
- Visit https://ghosts.lk/wyrm to upgrade.`}]};const i=r?.days||30,n=a.dashboard(i);let e=`\u{F115D} **Usage Analytics** (last ${i} days)
38
-
39
- `;if(e+=`### Overview
40
- `,e+=`- **Total Tool Calls:** ${n.summary.total_calls.toLocaleString()}
41
- `,e+=`- **Unique Tools Used:** ${n.summary.unique_tools}
42
- `,e+=`- **Total Tokens (In):** ${n.summary.total_tokens_in.toLocaleString()}
43
- `,e+=`- **Total Tokens (Out):** ${n.summary.total_tokens_out.toLocaleString()}
44
- `,e+=`- **Cache Hit Rate:** ${(n.summary.cache_hit_rate*100).toFixed(1)}%
45
- `,e+=`- **Estimated Cost:** $${n.summary.estimated_cost_usd.toFixed(4)}
46
-
47
- `,n.top_tools.length>0){e+=`### Top Tools
48
- `;for(const t of n.top_tools.slice(0,10))e+=`- **${t.tool}** \u2014 ${t.calls} calls, ${t.tokens.toLocaleString()} tokens
49
- `;e+=`
50
- `}if(n.daily.length>0){e+=`### Recent Activity
51
- `;for(const t of n.daily.slice(-7))e+=`- ${t.date}: ${t.calls} calls, ${t.tokens.toLocaleString()} tokens
52
- `}return{content:[{type:"text",text:e}]}}},{name:"wyrm_cost_report",description:"View estimated API cost report for current billing period (Pro+)",inputSchema:{type:"object",properties:{period:{type:"string",description:"Period in YYYY-MM format (default: current month)"}}},annotations:u.wyrm_cost_report,aliases:[],handler:async(r,c)=>{const{analytics:a}=c,o=r||{};if(!d("analytics"))return{content:[{type:"text",text:`\u{F115D} Cost reports require a Pro license or higher.
53
- Visit https://ghosts.lk/wyrm to upgrade.`}]};const i=r?.period,n=a.costReport(i);let e=`\u{F115D} **Cost Report** \u2014 ${n.period}
54
-
55
- `;if(e+=`- **Total Cost:** $${n.total_cost_usd.toFixed(4)}
56
- `,e+=`- **Projected Monthly:** $${n.projected_monthly_usd.toFixed(2)}
57
-
58
- `,n.tools.length>0){e+=`### By Tool
59
- `;for(const t of n.tools.slice(0,10))e+=`- **${t.tool}** \u2014 ${t.calls} calls, $${t.cost_usd.toFixed(4)}
60
- `}return{content:[{type:"text",text:e}]}}},{name:"wyrm_cloud_backup",description:"Legacy whole-DB snapshot backup to encrypted cloud storage (Pro+): backup, restore, list, prune. This is a full-database snapshot (last-write-wins on restore), NOT the sovereign per-row sync \u2014 for multi-device memory use wyrm_cloud_sync action=sovereign. Keep this for simple point-in-time backups.",inputSchema:{type:"object",properties:{action:{type:"string",description:"Action: backup, restore, list, prune",enum:["backup","restore","list","prune"]},backupKey:{type:"string",description:"Backup key for restore (from list)"},keepCount:{type:"number",description:"Number of backups to keep when pruning (default: 10)"}},required:["action"]},annotations:u.wyrm_cloud_backup,aliases:[],handler:async(r,c)=>{const{cloudBackup:a,db:o}=c,i=r||{};if(!d("cloud_backup"))return{content:[{type:"text",text:`\u{F115D} Cloud backup requires a Pro license or higher.
61
- Visit https://ghosts.lk/wyrm to upgrade.`}]};const n=r?.action;switch(n){case"backup":{const e=await a.backup(o.getDatabasePath()),t=e.metadata;return{content:[{type:"text",text:`\u{F115D} \u2713 Backup complete!
62
-
63
- - **Key:** ${e.key}
64
- - **Size:** ${(t.db_size/1024).toFixed(1)} KB \u2192 ${(t.compressed_size/1024).toFixed(1)} KB compressed
65
- - **Encrypted:** ${t.encrypted?"Yes":"No"}
66
- - **Checksum:** ${t.checksum.slice(0,16)}...`}]}}case"restore":{const e=r?.backupKey;if(!e)throw new Error("backupKey required for restore");const t=await a.restore(e,o.getDatabasePath());return{content:[{type:"text",text:`\u{F115D} \u2713 Restore complete!
67
-
68
- - **Restored:** ${t.restored?"Yes":"No"}
69
- - **Size:** ${(t.size/1024).toFixed(1)} KB
70
- - **Previous DB backed up to:** ${o.getDatabasePath()}.bak
71
-
72
- \u26A0\uFE0F Restart Wyrm to use the restored database.`}]}}case"list":{const e=await a.listBackups();if(e.length===0)return{content:[{type:"text",text:"\u{F115D} No backups found."}]};let t=`\u{F115D} **Cloud Backups** (${e.length} found)
73
-
74
- `;for(const l of e)t+=`- **${l.timestamp}** \u2014 ${(l.db_size/1024).toFixed(1)} KB, encrypted: ${l.encrypted}, machine: ${l.machine_id.slice(0,8)}...
75
- `;return{content:[{type:"text",text:t}]}}case"prune":{const e=r?.keepCount||10;return{content:[{type:"text",text:`\u{F115D} \u2713 Pruned ${(await a.pruneBackups(e)).deleted} old backup(s). Kept ${e} most recent.`}]}}default:return{content:[{type:"text",text:`Unknown backup action: ${n}. Use: backup, restore, list, prune`}],isError:!0}}}},{name:"wyrm_cloud_sync",description:"Cloud sync. The SOVEREIGN path is the promoted one: zero-knowledge, per-row AES-256-GCM E2E-encrypted delta sync with tombstone propagation \u2014 the cloud never sees plaintext. Use action 'sovereign' to sync (push+pull), 'sovereign-preview' for a dry run, 'sovereign-status' to show device + sync state. For hands-off periodic sync, 'sovereign-daemon-start' / 'sovereign-daemon-stop' / 'sovereign-daemon-status' run a background daemon. (First-time login is an interactive device flow: run `wyrm cloud login` in a terminal once.) The start/stop/restart/status/force-sync actions drive the LEGACY whole-DB snapshot daemon (last-write-wins by mtime, weaker) \u2014 retained only for simple full-DB backup; it is NOT the sovereign path. Prefer sovereign for multi-device memory.",inputSchema:{type:"object",properties:{action:{type:"string",description:"Sovereign (preferred): sovereign, sovereign-preview, sovereign-status, sovereign-daemon-start, sovereign-daemon-stop, sovereign-daemon-status. Legacy snapshot daemon: start, stop, restart, status, force-sync.",enum:["sovereign","sovereign-preview","sovereign-status","sovereign-daemon-start","sovereign-daemon-stop","sovereign-daemon-status","start","stop","restart","status","force-sync"]},all:{type:"boolean",description:"Sovereign only: sync every semantic table, ignoring visibility flags (default: false)"},interval_minutes:{type:"number",description:"Legacy snapshot only: sync interval (default: 10)"},keep_count:{type:"number",description:"Legacy snapshot only: snapshots to retain after pruning (default: 20)"}},required:["action"]},annotations:u.wyrm_cloud_sync,aliases:[],handler:async(r,c)=>{const{cloudBackup:a,cloudSyncManager:o,db:i,sync:n}=c,e=r||{},t=r?.action,l={sovereign:r?.all?["sync","--all"]:["sync"],"sovereign-preview":["sync","--dry-run"],"sovereign-status":["status"],"sovereign-daemon-start":["daemon","start"],"sovereign-daemon-stop":["daemon","stop"],"sovereign-daemon-status":["daemon","status"]};if(t in l){const s=await P(l[t]);return{content:[{type:"text",text:s.text}],isError:!s.ok}}if(!d("cloud_backup"))return{content:[{type:"text",text:`\u{F115D} The legacy snapshot daemon requires a Pro license or higher.
76
- Visit https://ghosts.lk/wyrm to upgrade. (The sovereign path \u2014 action=sovereign \u2014 is the preferred sync.)`}]};const y=r?.interval_minutes,m=r?.keep_count;switch(t){case"start":{const s=o.start({interval_minutes:y,keep_count:m});return s.ok?{content:[{type:"text",text:`\u{F115D} \u2713 cloud-sync daemon started (pid ${s.pid}). Logs: ~/.wyrm/wyrm-cloud-sync.log`}]}:{content:[{type:"text",text:`\u{F115D} cloud-sync: ${s.reason??"failed to start"}${s.pid?` (pid ${s.pid})`:""}`}],isError:!s.pid}}case"stop":{const s=o.stop();return{content:[{type:"text",text:s.ok?"\u{F115D} \u2713 cloud-sync daemon stopped":`\u{F115D} cloud-sync: ${s.reason??"failed to stop"}`}],isError:!s.ok}}case"restart":{o.stop(),await new Promise(p=>{setTimeout(p,500)});const s=o.start({interval_minutes:y,keep_count:m});return{content:[{type:"text",text:s.ok?`\u{F115D} \u2713 cloud-sync daemon restarted (pid ${s.pid})`:`\u{F115D} cloud-sync restart failed: ${s.reason??"unknown"}`}],isError:!s.ok}}case"status":{const s=o.status(),p=["\u{F115D} **Legacy Snapshot Daemon Status** (not the sovereign path \u2014 see action=sovereign-status)"];return p.push(`- Running: ${s.running?"\u2713 yes":"\u2717 no"}`),s.pid&&p.push(`- PID: ${s.pid}`),s.state&&(s.state.last_uploaded_ts&&p.push(`- Last upload: ${new Date(s.state.last_uploaded_ts).toISOString()}${s.state.last_uploaded_key?` (${s.state.last_uploaded_key})`:""}`),s.state.last_restored_key&&p.push(`- Last restore: ${s.state.last_restored_key}`),s.state.last_check_ts&&p.push(`- Last check: ${new Date(s.state.last_check_ts).toISOString()}`),p.push(`- Machine: ${s.state.machine}`)),{content:[{type:"text",text:p.join(`
77
- `)}]}}case"force-sync":{const p=await new h(a,i.getDatabasePath()).tick();return{content:[{type:"text",text:`\u{F115D} force-sync: ${JSON.stringify(p,null,2)}`}]}}default:return{content:[{type:"text",text:`Unknown cloud-sync action: ${t}. Use: start, stop, restart, status, force-sync`}],isError:!0}}}},{name:"wyrm_encrypt_setup",description:"Set up or check encryption status for sensitive data (Pro+)",inputSchema:{type:"object",properties:{action:{type:"string",description:"Action: status, enable, test",enum:["status","enable","test"]},password:{type:"string",description:"Encryption password (min 8 chars, required for enable)"}},required:["action"]},annotations:u.wyrm_encrypt_setup,aliases:[],handler:async(r,c)=>{const a=r||{};if(!d("encryption"))return{content:[{type:"text",text:`\u{F115D} Encryption setup requires a Pro license or higher.
78
- Visit https://ghosts.lk/wyrm to upgrade.`}]};const o=r?.action,i=g();switch(o){case"status":{const n=i.isEnabled();let e=`\u{F115D} **Encryption Status**
79
-
80
- `;return e+=`- **Enabled:** ${n?"\u2713 Yes":"\u2717 No"}
81
- `,e+=`- **Algorithm:** AES-256-GCM
82
- `,n?e+=`- **Status:** Active \u2014 new data is encrypted at rest
83
- `:e+='\nTo enable, run: `wyrm_encrypt_setup` with action "enable" and a strong password.\n',{content:[{type:"text",text:e}]}}case"enable":{const n=r?.password;return!n||n.length<8?{content:[{type:"text",text:"\u{F115D} Password must be at least 8 characters."}],isError:!0}:(f(n),{content:[{type:"text",text:`\u{F115D} \u2713 Encryption enabled!
4
+ Then retry this action. (Status/sync here drive an already-registered device.)`};const i=E(S($(import.meta.url)),"..","wyrm-cli.js");try{const{stdout:r,stderr:c}=await P(process.execPath,[i,"cloud",...a],{timeout:18e4,maxBuffer:4194304,env:{...process.env}});return{ok:!0,text:(r||"").trim()||(c||"").trim()||"(no output)"}}catch(r){return{ok:!1,text:`\u{F115D} Sovereign cloud sync failed.
5
+ ${[r?.stdout,r?.stderr].filter(Boolean).join(`
6
+ `).trim()||(r?.message??"unknown error")}`}}}const q=[{name:"wyrm_license",description:"View current license status, tier, and available features",inputSchema:{type:"object",properties:{}},annotations:d.wyrm_license,aliases:[],handler:async(a,l)=>{const{cachedResponse:i,sync:r,toolCounts:c}=l,n=a||{},t=w(),e=v();let o=`\u{F115D} **Wyrm License Status**
7
+
8
+ `;if(o+=`- **Tier:** ${e.charAt(0).toUpperCase()+e.slice(1)}
9
+ `,t.valid&&t.key){o+=`- **Key:** ${t.key}
10
+ `,o+=`- **Issued To:** ${t.issuedTo||"Unknown"}
11
+ `,o+=`- **Expires:** ${t.expiresAt?new Date(t.expiresAt).toLocaleDateString():"Never"}
12
+ `,o+=`- **Status:** \u2713 Valid
13
+
14
+ `,o+=`### Features
15
+ `;for(const p of t.features)o+=`- \u2713 ${p}
16
+ `}else{o+=`- **Status:** Free tier (no license key)
17
+
18
+ `,o+=`### Free Features
19
+ `,o+=`- \u2713 Local SQLite storage
20
+ `;const p=c?.();o+=p?p.active===p.full?`- \u2713 ${p.full} MCP tools
21
+ `:`- \u2713 ${p.active} MCP tools (${p.full} in the full profile)
22
+ `:`- \u2713 MCP tools
23
+ `,o+=`- \u2713 FTS5 search
24
+ `,o+=`- \u2713 File-based sync
25
+
26
+ `,o+=`### Upgrade to Pro
27
+ `,o+=`- Analytics dashboard & cost tracking
28
+ `,o+=`- Cloud backup to R2
29
+ `,o+=`- Encryption at rest
30
+ `,o+=`- Priority support
31
+
32
+ `,o+=`### Already have a license?
33
+ `,o+="Activate it: paste the license JSON into the `wyrm_activate` tool, or save it to `~/.wyrm/license.json` and restart Wyrm.\n",o+="Manage or download your license at https://account.ghosts.lk \xB7 buy one at https://ghosts.lk/wyrm."}return i(o)}},{name:"wyrm_activate",description:"Activate a license key to unlock Pro/Team/Enterprise features",inputSchema:{type:"object",properties:{license:{type:"string",description:"License JSON string (signed license from Ghost Protocol)"}},required:["license"]},annotations:d.wyrm_activate,aliases:[],handler:async(a,l)=>{const i=a?.license;if(!i)throw new Error("License JSON string required");try{const r=k(i);return r.valid?{content:[{type:"text",text:`\u{F115D} \u2713 License activated!
34
+
35
+ - **Tier:** ${r.tier}
36
+ - **Features:** ${r.features.join(", ")}
37
+
38
+ Restart Wyrm to apply all features.`}]}:{content:[{type:"text",text:`\u{F115D} \u2717 License activation failed: ${r.error||"unknown error"}`}],isError:!0}}catch{return{content:[{type:"text",text:"\u{F115D} \u2717 Invalid license format. Please verify your license key."}],isError:!0}}}},{name:"wyrm_analytics_dashboard",description:"View usage analytics dashboard \u2014 tool calls, tokens, costs, cache efficiency (Pro+)",inputSchema:{type:"object",properties:{days:{type:"number",description:"Number of days to analyze (default: 30)"}}},annotations:d.wyrm_analytics_dashboard,aliases:[],handler:async(a,l)=>{const{analytics:i}=l,r=a||{};if(!y("analytics"))return{content:[{type:"text",text:`\u{F115D} Analytics requires a Pro license or higher.
39
+ Visit https://ghosts.lk/wyrm to upgrade.`}]};const c=a?.days||30,n=i.dashboard(c);let t=`\u{F115D} **Usage Analytics** (last ${c} days)
40
+
41
+ `;if(t+=`### Overview
42
+ `,t+=`- **Total Tool Calls:** ${n.summary.total_calls.toLocaleString()}
43
+ `,t+=`- **Unique Tools Used:** ${n.summary.unique_tools}
44
+ `,t+=`- **Total Tokens (In):** ${n.summary.total_tokens_in.toLocaleString()}
45
+ `,t+=`- **Total Tokens (Out):** ${n.summary.total_tokens_out.toLocaleString()}
46
+ `,t+=`- **Cache Hit Rate:** ${(n.summary.cache_hit_rate*100).toFixed(1)}%
47
+ `,t+=`- **Estimated Cost:** $${n.summary.estimated_cost_usd.toFixed(4)}
48
+
49
+ `,n.top_tools.length>0){t+=`### Top Tools
50
+ `;for(const e of n.top_tools.slice(0,10))t+=`- **${e.tool}** \u2014 ${e.calls} calls, ${e.tokens.toLocaleString()} tokens
51
+ `;t+=`
52
+ `}if(n.daily.length>0){t+=`### Recent Activity
53
+ `;for(const e of n.daily.slice(-7))t+=`- ${e.date}: ${e.calls} calls, ${e.tokens.toLocaleString()} tokens
54
+ `}return{content:[{type:"text",text:t}]}}},{name:"wyrm_cost_report",description:"View estimated API cost report for current billing period (Pro+)",inputSchema:{type:"object",properties:{period:{type:"string",description:"Period in YYYY-MM format (default: current month)"}}},annotations:d.wyrm_cost_report,aliases:[],handler:async(a,l)=>{const{analytics:i}=l,r=a||{};if(!y("analytics"))return{content:[{type:"text",text:`\u{F115D} Cost reports require a Pro license or higher.
55
+ Visit https://ghosts.lk/wyrm to upgrade.`}]};const c=a?.period,n=i.costReport(c);let t=`\u{F115D} **Cost Report** \u2014 ${n.period}
56
+
57
+ `;if(t+=`- **Total Cost:** $${n.total_cost_usd.toFixed(4)}
58
+ `,t+=`- **Projected Monthly:** $${n.projected_monthly_usd.toFixed(2)}
59
+
60
+ `,n.tools.length>0){t+=`### By Tool
61
+ `;for(const e of n.tools.slice(0,10))t+=`- **${e.tool}** \u2014 ${e.calls} calls, $${e.cost_usd.toFixed(4)}
62
+ `}return{content:[{type:"text",text:t}]}}},{name:"wyrm_cloud_backup",description:"Legacy whole-DB snapshot backup to encrypted cloud storage (Pro+): backup, restore, list, prune. This is a full-database snapshot (last-write-wins on restore), NOT the sovereign per-row sync \u2014 for multi-device memory use wyrm_cloud_sync action=sovereign. Keep this for simple point-in-time backups.",inputSchema:{type:"object",properties:{action:{type:"string",description:"Action: backup, restore, list, prune",enum:["backup","restore","list","prune"]},backupKey:{type:"string",description:"Backup key for restore (from list)"},keepCount:{type:"number",description:"Number of backups to keep when pruning (default: 10)"}},required:["action"]},annotations:d.wyrm_cloud_backup,aliases:[],handler:async(a,l)=>{const{cloudBackup:i,db:r}=l,c=a||{};if(!y("cloud_backup"))return{content:[{type:"text",text:`\u{F115D} Cloud backup requires a Pro license or higher.
63
+ Visit https://ghosts.lk/wyrm to upgrade.`}]};const n=a?.action;switch(n){case"backup":{const t=await i.backup(r.getDatabasePath()),e=t.metadata;return{content:[{type:"text",text:`\u{F115D} \u2713 Backup complete!
64
+
65
+ - **Key:** ${t.key}
66
+ - **Size:** ${(e.db_size/1024).toFixed(1)} KB \u2192 ${(e.compressed_size/1024).toFixed(1)} KB compressed
67
+ - **Encrypted:** ${e.encrypted?"Yes":"No"}
68
+ - **Checksum:** ${e.checksum.slice(0,16)}...`}]}}case"restore":{const t=a?.backupKey;if(!t)throw new Error("backupKey required for restore");const e=await i.restore(t,r.getDatabasePath());return{content:[{type:"text",text:`\u{F115D} \u2713 Restore complete!
69
+
70
+ - **Restored:** ${e.restored?"Yes":"No"}
71
+ - **Size:** ${(e.size/1024).toFixed(1)} KB
72
+ - **Previous DB backed up to:** ${r.getDatabasePath()}.bak
73
+
74
+ \u26A0\uFE0F Restart Wyrm to use the restored database.`}]}}case"list":{const t=await i.listBackups();if(t.length===0)return{content:[{type:"text",text:"\u{F115D} No backups found."}]};let e=`\u{F115D} **Cloud Backups** (${t.length} found)
75
+
76
+ `;for(const o of t)e+=`- **${o.timestamp}** \u2014 ${(o.db_size/1024).toFixed(1)} KB, encrypted: ${o.encrypted}, machine: ${o.machine_id.slice(0,8)}...
77
+ `;return{content:[{type:"text",text:e}]}}case"prune":{const t=a?.keepCount||10;return{content:[{type:"text",text:`\u{F115D} \u2713 Pruned ${(await i.pruneBackups(t)).deleted} old backup(s). Kept ${t} most recent.`}]}}default:return{content:[{type:"text",text:`Unknown backup action: ${n}. Use: backup, restore, list, prune`}],isError:!0}}}},{name:"wyrm_cloud_sync",description:"Cloud sync. The SOVEREIGN path is the promoted one: zero-knowledge, per-row AES-256-GCM E2E-encrypted delta sync with tombstone propagation \u2014 the cloud never sees plaintext. Use action 'sovereign' to sync (push+pull), 'sovereign-preview' for a dry run, 'sovereign-status' to show device + sync state. For hands-off periodic sync, 'sovereign-daemon-start' / 'sovereign-daemon-stop' / 'sovereign-daemon-status' run a background daemon. (First-time login is an interactive device flow: run `wyrm cloud login` in a terminal once.) The start/stop/restart/status/force-sync actions drive the LEGACY whole-DB snapshot daemon (last-write-wins by mtime, weaker) \u2014 retained only for simple full-DB backup; it is NOT the sovereign path. Prefer sovereign for multi-device memory.",inputSchema:{type:"object",properties:{action:{type:"string",description:"Sovereign (preferred): sovereign, sovereign-preview, sovereign-status, sovereign-daemon-start, sovereign-daemon-stop, sovereign-daemon-status. Legacy snapshot daemon: start, stop, restart, status, force-sync.",enum:["sovereign","sovereign-preview","sovereign-status","sovereign-daemon-start","sovereign-daemon-stop","sovereign-daemon-status","start","stop","restart","status","force-sync"]},all:{type:"boolean",description:"Sovereign only: sync every semantic table, ignoring visibility flags (default: false)"},interval_minutes:{type:"number",description:"Legacy snapshot only: sync interval (default: 10)"},keep_count:{type:"number",description:"Legacy snapshot only: snapshots to retain after pruning (default: 20)"}},required:["action"]},annotations:d.wyrm_cloud_sync,aliases:[],handler:async(a,l)=>{const{cloudBackup:i,cloudSyncManager:r,db:c,sync:n}=l,t=a||{},e=a?.action,o={sovereign:a?.all?["sync","--all"]:["sync"],"sovereign-preview":["sync","--dry-run"],"sovereign-status":["status"],"sovereign-daemon-start":["daemon","start"],"sovereign-daemon-stop":["daemon","stop"],"sovereign-daemon-status":["daemon","status"]};if(e in o){const s=await T(o[e]);return{content:[{type:"text",text:s.text}],isError:!s.ok}}if(!y("cloud_backup"))return{content:[{type:"text",text:`\u{F115D} The legacy snapshot daemon requires a Pro license or higher.
78
+ Visit https://ghosts.lk/wyrm to upgrade. (The sovereign path \u2014 action=sovereign \u2014 is the preferred sync.)`}]};const p=a?.interval_minutes,m=a?.keep_count;switch(e){case"start":{const s=r.start({interval_minutes:p,keep_count:m});return s.ok?{content:[{type:"text",text:`\u{F115D} \u2713 cloud-sync daemon started (pid ${s.pid}). Logs: ~/.wyrm/wyrm-cloud-sync.log`}]}:{content:[{type:"text",text:`\u{F115D} cloud-sync: ${s.reason??"failed to start"}${s.pid?` (pid ${s.pid})`:""}`}],isError:!s.pid}}case"stop":{const s=r.stop();return{content:[{type:"text",text:s.ok?"\u{F115D} \u2713 cloud-sync daemon stopped":`\u{F115D} cloud-sync: ${s.reason??"failed to stop"}`}],isError:!s.ok}}case"restart":{r.stop(),await new Promise(u=>{setTimeout(u,500)});const s=r.start({interval_minutes:p,keep_count:m});return{content:[{type:"text",text:s.ok?`\u{F115D} \u2713 cloud-sync daemon restarted (pid ${s.pid})`:`\u{F115D} cloud-sync restart failed: ${s.reason??"unknown"}`}],isError:!s.ok}}case"status":{const s=r.status(),u=["\u{F115D} **Legacy Snapshot Daemon Status** (not the sovereign path \u2014 see action=sovereign-status)"];return u.push(`- Running: ${s.running?"\u2713 yes":"\u2717 no"}`),s.pid&&u.push(`- PID: ${s.pid}`),s.state&&(s.state.last_uploaded_ts&&u.push(`- Last upload: ${new Date(s.state.last_uploaded_ts).toISOString()}${s.state.last_uploaded_key?` (${s.state.last_uploaded_key})`:""}`),s.state.last_restored_key&&u.push(`- Last restore: ${s.state.last_restored_key}`),s.state.last_check_ts&&u.push(`- Last check: ${new Date(s.state.last_check_ts).toISOString()}`),u.push(`- Machine: ${s.state.machine}`)),{content:[{type:"text",text:u.join(`
79
+ `)}]}}case"force-sync":{const u=await new h(i,c.getDatabasePath()).tick();return{content:[{type:"text",text:`\u{F115D} force-sync: ${JSON.stringify(u,null,2)}`}]}}default:return{content:[{type:"text",text:`Unknown cloud-sync action: ${e}. Use: start, stop, restart, status, force-sync`}],isError:!0}}}},{name:"wyrm_encrypt_setup",description:"Set up or check encryption status for sensitive data (Pro+)",inputSchema:{type:"object",properties:{action:{type:"string",description:"Action: status, enable, test",enum:["status","enable","test"]},password:{type:"string",description:"Encryption password (min 8 chars, required for enable)"}},required:["action"]},annotations:d.wyrm_encrypt_setup,aliases:[],handler:async(a,l)=>{const i=a||{};if(!y("encryption"))return{content:[{type:"text",text:`\u{F115D} Encryption setup requires a Pro license or higher.
80
+ Visit https://ghosts.lk/wyrm to upgrade.`}]};const r=a?.action,c=f();switch(r){case"status":{const n=c.isEnabled();let t=`\u{F115D} **Encryption Status**
81
+
82
+ `;return t+=`- **Enabled:** ${n?"\u2713 Yes":"\u2717 No"}
83
+ `,t+=`- **Algorithm:** AES-256-GCM
84
+ `,n?t+=`- **Status:** Active \u2014 new data is encrypted at rest
85
+ `:t+='\nTo enable, run: `wyrm_encrypt_setup` with action "enable" and a strong password.\n',{content:[{type:"text",text:t}]}}case"enable":{const n=a?.password;return!n||n.length<8?{content:[{type:"text",text:"\u{F115D} Password must be at least 8 characters."}],isError:!0}:(g(n),{content:[{type:"text",text:`\u{F115D} \u2713 Encryption enabled!
84
86
 
85
87
  - **Algorithm:** AES-256-GCM
86
88
  - **Key derived from:** your password (PBKDF2)
87
89
 
88
90
  \u26A0\uFE0F **Store your password safely.** Lost passwords cannot be recovered.
89
- New data lake entries will be encrypted at rest.`}]})}case"test":{if(!i.isEnabled())return{content:[{type:"text",text:'\u{F115D} Encryption not enabled. Run with action "enable" first.'}]};const n="Wyrm encryption test "+Date.now(),e=i.encrypt(n),t=i.decrypt(e);return{content:[{type:"text",text:`\u{F115D} Encryption test: ${t===n?"\u2713 PASSED":"\u2717 FAILED"}
90
- - Encrypted ${n.length} chars \u2192 decrypted back to ${t.length} chars`}]}}default:return{content:[{type:"text",text:`Unknown encrypt action: ${o}. Use: status, enable, test`}],isError:!0}}}}];export{q as cloudToolSpecs};
91
+ New data lake entries will be encrypted at rest.`}]})}case"test":{if(!c.isEnabled())return{content:[{type:"text",text:'\u{F115D} Encryption not enabled. Run with action "enable" first.'}]};const n="Wyrm encryption test "+Date.now(),t=c.encrypt(n),e=c.decrypt(t);return{content:[{type:"text",text:`\u{F115D} Encryption test: ${e===n?"\u2713 PASSED":"\u2717 FAILED"}
92
+ - Encrypted ${n.length} chars \u2192 decrypted back to ${e.length} chars`}]}}default:return{content:[{type:"text",text:`Unknown encrypt action: ${r}. Use: status, enable, test`}],isError:!0}}}}];export{q as cloudToolSpecs};
@@ -1,4 +1,4 @@
1
- import{TOOL_ANNOTATIONS as l}from"../tool-annotations.js";import{injectSystemPrompt as g}from"../autoconfig.js";import{buildPeerBuddyReply as f}from"../buddy.js";import{getCapabilities as w,renderCapabilityBriefing as j}from"../capabilities.js";import{getUpdateStatus as b}from"../version-check.js";import{isCompanionBuddyCall as _}from"./alias-adapters.js";import{asEnum as u}from"../validate.js";import{spawn as k}from"child_process";const E=[{name:"wyrm_inject_prompt",description:"Opt-in: inject a Wyrm session-prime instruction block into .github/copilot-instructions.md and/or .cursor/rules for a project. Uses managed markers so re-runs are safe.",inputSchema:{type:"object",properties:{project_path:{type:"string",description:"Absolute path to the project root"},clients:{type:"array",items:{type:"string"},description:"Clients to inject into: 'copilot', 'cursor' (default: both detected)"}},required:["project_path"]},annotations:l.wyrm_inject_prompt,aliases:[],handler:async(o,c)=>{const{project_path:s,clients:a}=o,t=g(s,a??[]);let e=`\u{F115D} **System Prompt Injection**
1
+ import{TOOL_ANNOTATIONS as l}from"../tool-annotations.js";import{injectSystemPrompt as g}from"../autoconfig.js";import{buildPeerBuddyReply as f}from"../buddy.js";import{getCapabilities as w,renderCapabilityBriefing as j}from"../capabilities.js";import{getUpdateStatus as b}from"../version-check.js";import{isCompanionBuddyCall as _}from"./alias-adapters.js";import{asEnum as m}from"../validate.js";import{spawn as k}from"child_process";const E=[{name:"wyrm_inject_prompt",description:"Opt-in: inject a Wyrm session-prime instruction block into .github/copilot-instructions.md and/or .cursor/rules for a project. Uses managed markers so re-runs are safe.",inputSchema:{type:"object",properties:{project_path:{type:"string",description:"Absolute path to the project root"},clients:{type:"array",items:{type:"string"},description:"Clients to inject into: 'copilot', 'cursor' (default: both detected)"}},required:["project_path"]},annotations:l.wyrm_inject_prompt,aliases:[],handler:async(i,c)=>{const{project_path:s,clients:a}=i,t=g(s,a??[]);let e=`\u{F115D} **System Prompt Injection**
2
2
 
3
3
  `;return t.injected.length>0&&(e+=`\u2705 Injected into:
4
4
  ${t.injected.map(r=>`- ${r}`).join(`
@@ -10,19 +10,19 @@ ${t.injected.map(r=>`- ${r}`).join(`
10
10
  ${t.errors.map(r=>`- ${r}`).join(`
11
11
  `)}
12
12
 
13
- `),t.injected.length>0&&(e+="AI models in this project will now call `wyrm_session_prime` at the start of each conversation."),{content:[{type:"text",text:e}]}}},{name:"buddy",description:"Use when another buddy-compatible MCP server (or the operator) asks Wyrm for a brief, data-grounded status reply - the Buddy Protocol v1.0 well-known entry point. Companion params switch to the full companion reply; bare calls stay protocol-pure. Cycle-protected.",inputSchema:{type:"object",properties:{from_buddy:{type:"string",description:"Caller identity"},project_hint:{type:"string",description:"Scope the reply"},mood_hint:{type:"string",enum:["normal","celebratory","stuck","quiet"]},size:{type:"string",enum:["full","compact","mini"]},format:{type:"string",enum:["markdown","json"],description:"Peer mode only."},project_path:{type:"string",description:"Companion mode: project root (default cwd)"},persona:{type:"string",enum:["wyrm","drogo","berlin","sumair","custom"],description:"Companion voice."},persona_name:{type:"string",description:"For persona=custom"},mood:{type:"string",description:"Companion override (mood_hint values)"},federate:{type:"boolean",description:"Fold in other buddy MCPs"}}},annotations:l.buddy,aliases:[],handler:async(o,c)=>{const{WYRM_PACKAGE_VERSION:s,db:a,runCompanionBuddy:t}=c,e=o||{};if(u("size",e.size,["full","compact","mini"]),u("mood_hint",e.mood_hint,["normal","celebratory","stuck","quiet"]),u("format",e.format,["markdown","json"]),u("persona",e.persona,["wyrm","drogo","berlin","sumair","custom"]),_(o??{}))return await t(o??{});const r=o,i=f(a.getDatabase(),r,s);return r.format==="json"?{content:[{type:"text",text:JSON.stringify(i.json,null,2)}]}:{content:[{type:"text",text:i.markdown}]}}},{name:"wyrm_buddy",description:"Friendly, data-grounded coding companion. Returns a brief greeting + project state + suggested next step sourced entirely from Wyrm's real data. Every claim cites a real DB row ID \u2014 no hallucinated encouragement. Personas: wyrm (default, dragon-themed), drogo (gruff), berlin (precise), sumair (warm), custom. Moods: normal / celebratory / stuck / quiet \u2014 auto-detected. Sizes: full (10-line PhantomDragon-style mascot), compact (5-line workspace-friendly), mini (single-line inline prefix). Federates with other registered MCPs that expose `buddy`, `*_buddy`, or `buddy_*` tools \u2014 their replies fold into a 'From other buddies' section.",inputSchema:{type:"object",properties:{project_path:{type:"string",description:"Project root. Default: current cwd if registered."},mood:{type:"string",enum:["normal","celebratory","stuck","quiet"],description:"Override the auto-detected mood."},persona:{type:"string",enum:["wyrm","drogo","berlin","sumair","custom"],description:"Voice. Default: WYRM_BUDDY_DEFAULT_PERSONA env or 'wyrm'."},persona_name:{type:"string",description:"Required when persona === 'custom'."},size:{type:"string",enum:["full","compact","mini"],description:"Mascot size. 'full' = 10-line head+shoulder banner (default), 'compact' = 5-line workspace-friendly, 'mini' = single-line inline prefix on the greeting."},federate:{type:"boolean",description:"Query other registered MCPs with *_buddy tools and fold their replies in. Default: true if any are registered."}}},annotations:l.wyrm_buddy,aliases:[],handler:async(o,c)=>{const{runCompanionBuddy:s}=c;return await s(o??{})}},{name:"wyrm_token_set",description:"Set a design token for a project. Tokens are colour / type / spacing / motion / shadow / radius / breakpoint / custom \u2014 the design system primitives the project is built on. Stored first-class so AI helpers working on creative tasks don't have to re-derive the system from scattered CSS each time. Upserts on (project_id, category, key).",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Project root path."},category:{type:"string",enum:["color","type","spacing","motion","shadow","radius","breakpoint","custom"],description:"Token category."},key:{type:"string",description:"Short slug, e.g. 'primary' / 'h1' / 'page-padding-md'."},value:{type:"string",description:"Actual value: '#00B89F' / 'Inter 900 2.5rem' / '1.25rem' / etc."},notes:{type:"string",description:"Optional why / when-to-use guidance."},source:{type:"string",enum:["user","derived","imported","observed"],description:"How the token was determined. Default: 'user'."}},required:["projectPath","category","key","value"]},annotations:l.wyrm_token_set,aliases:[],handler:async(o,c)=>{const{db:s,designSystem:a}=c,{projectPath:t,category:e,key:r,value:i,notes:p,source:m}=o,n=s.getProject(t);if(!n)return{content:[{type:"text",text:`Project not found: ${t}`}],isError:!0};const d=a().setToken({projectId:n.id,category:e,key:r,value:i,notes:p,source:m});return{content:[{type:"text",text:`\u{1F3A8} token set: \`${d.category}/${d.key}\` = \`${d.value}\``+(d.notes?` \u2014 ${d.notes}`:"")}]}}},{name:"wyrm_token_get",description:"Retrieve all design tokens for a project, optionally filtered by category. Returns the design system primitives the project is built on.",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Project root path."},category:{type:"string",enum:["color","type","spacing","motion","shadow","radius","breakpoint","custom"],description:"Filter by category. Omit for all tokens."}},required:["projectPath"]},annotations:l.wyrm_token_get,aliases:[],handler:async(o,c)=>{const{db:s,designSystem:a}=c,{projectPath:t,category:e}=o,r=s.getProject(t);if(!r)return{content:[{type:"text",text:`Project not found: ${t}`}],isError:!0};const i=a().formatForContext(r.id);if(!i)return{content:[{type:"text",text:`_(no design tokens for ${r.name} yet \u2014 use \`wyrm_token_set\` to start the design system)_`}]};if(e){const p=a().listTokens(r.id,e);if(p.length===0)return{content:[{type:"text",text:`_(no ${e} tokens for ${r.name})_`}]};const m=[`### \u{1F3A8} ${e} tokens for ${r.name}`,""];for(const n of p)m.push(`- \`${n.key}\` = \`${n.value}\`${n.notes?` \u2014 ${n.notes}`:""}`);return{content:[{type:"text",text:m.join(`
14
- `)}]}}return{content:[{type:"text",text:i}]}}},{name:"wyrm_token_delete",description:"Remove a design token from a project.",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Project root path."},category:{type:"string",enum:["color","type","spacing","motion","shadow","radius","breakpoint","custom"]},key:{type:"string",description:"Token key to remove."}},required:["projectPath","category","key"]},annotations:l.wyrm_token_delete,aliases:[],handler:async(o,c)=>{const{db:s,designSystem:a}=c,{projectPath:t,category:e,key:r}=o,i=s.getProject(t);return i?{content:[{type:"text",text:a().deleteToken(i.id,e,r)?`\u{1F5D1} removed \`${e}/${r}\``:`not found: \`${e}/${r}\``}]}:{content:[{type:"text",text:`Project not found: ${t}`}],isError:!0}}},{name:"wyrm_reference_add",description:"Clip a design reference (URL, image path, palette, or inline snippet) for inspiration. Stored per-project (or globally if no project path). Tag aggressively \u2014 tags drive future search and recall when building a new project in the same vein.",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Project root path. Omit to attach as a global reference."},kind:{type:"string",enum:["url","image","palette","snippet"],description:"Reference type."},location:{type:"string",description:"URL, file path, or inline payload (e.g. palette like '#00B89F,#ff5a55,#0a0c0e' or a code snippet)."},title:{type:"string",description:"Short title \u2014 what is this reference?"},notes:{type:"string",description:"Why you saved it / what to remember about it."},tags:{type:"string",description:"Comma-separated tags \u2014 'hero,scroll-tied,framer-motion'."}},required:["kind","location"]},annotations:l.wyrm_reference_add,aliases:[],handler:async(o,c)=>{const{db:s,designSystem:a}=c,{projectPath:t,kind:e,location:r,title:i,notes:p,tags:m}=o;let n=null;if(t){const y=s.getProject(t);if(!y)return{content:[{type:"text",text:`Project not found: ${t}`}],isError:!0};n=y.id}const d=a().addReference({projectId:n,kind:e,location:r,title:i,notes:p,tags:m}),h=n?` (project ${n})`:" (global)";return{content:[{type:"text",text:`\u{1F4CC} reference #${d.id} saved${h}: \`${d.kind}\` ${d.title??d.location.slice(0,60)}${d.tags?` \xB7 tags: ${d.tags}`:""}`}]}}},{name:"wyrm_reference_list",description:"Browse saved design references. Filter by project or tag. Default: 50 most recent across all projects.",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Limit to a specific project."},tag:{type:"string",description:"Filter to references containing this tag."},limit:{type:"number",description:"Max results (default 50, max 200)."}}},annotations:l.wyrm_reference_list,aliases:[],handler:async(o,c)=>{const{db:s,designSystem:a}=c,{projectPath:t,tag:e,limit:r}=o;let i;if(t){const n=s.getProject(t);if(!n)return{content:[{type:"text",text:`Project not found: ${t}`}],isError:!0};i=n.id}const p=a().listReferences({projectId:i,tag:e,limit:r});if(p.length===0)return{content:[{type:"text",text:"_(no references match)_"}]};const m=[`\u{1F4CC} **${p.length} reference${p.length===1?"":"s"}**`];for(const n of p){const d=n.project_id?`p${n.project_id}`:"global";m.push(`- [${n.id}/${d}] \`${n.kind}\` ${n.title??n.location.slice(0,60)}${n.tags?` \xB7 ${n.tags}`:""}`)}return{content:[{type:"text",text:m.join(`
15
- `)}]}}},{name:"wyrm_reference_search",description:"Full-text search across design references' title / notes / tags. Returns matching references sorted by recency.",inputSchema:{type:"object",properties:{query:{type:"string",description:"FTS5 query \u2014 keywords, phrases in quotes."},projectPath:{type:"string",description:"Limit to a specific project."},limit:{type:"number",description:"Max results (default 20, max 100)."}},required:["query"]},annotations:l.wyrm_reference_search,aliases:[],handler:async(o,c)=>{const{db:s,designSystem:a}=c,{query:t,projectPath:e,limit:r}=o;let i;if(e){const n=s.getProject(e);if(!n)return{content:[{type:"text",text:`Project not found: ${e}`}],isError:!0};i=n.id}const p=a().searchReferences(t,{projectId:i,limit:r});if(p.length===0)return{content:[{type:"text",text:`_(no references match "${t}")_`}]};const m=[`\u{1F50D} **${p.length} reference${p.length===1?"":"s"}** matching "${t}"`];for(const n of p){const d=n.project_id?`p${n.project_id}`:"global";m.push(`- [${n.id}/${d}] \`${n.kind}\` ${n.title??n.location.slice(0,60)}${n.tags?` \xB7 ${n.tags}`:""}`)}return{content:[{type:"text",text:m.join(`
16
- `)}]}}},{name:"wyrm_capabilities",description:"Use to learn what your memory system can actually do - feature inventory, why each matters, runtime state, and which tool to reach for. Call at session start instead of re-discovering the surface.",inputSchema:{type:"object",properties:{format:{type:"string",enum:["json","markdown"]}}},annotations:l.wyrm_capabilities,aliases:[],handler:async(o,c)=>{const{WYRM_TOOL_COUNT:s,agentDaemon:a,db:t}=c,e=u("format",o.format,["json","markdown"])??"markdown",r=w(t.getDatabase(),s,{hasEncryption:()=>!!process.env.WYRM_ENCRYPTION_KEY,isFederationEnabled:()=>!0,isAgentRunning:()=>{try{return a.status().running}catch{return null}}});return e==="json"?{content:[{type:"text",text:JSON.stringify(r,null,2)}]}:{content:[{type:"text",text:j(r)}]}}},{name:"wyrm_intro",description:"Return a plain-English explanation of what Wyrm is and what it does. Aimed at HUMANS who don't yet understand Wyrm's value \u2014 co-founders, operators, designers, anyone who didn't read the docs. Call this when a human user asks 'what does Wyrm even do?' or seems unaware Wyrm is involved in their AI assistant's behavior. NOT for AI-agent self-orientation (use wyrm_capabilities for that).",inputSchema:{type:"object",properties:{}},annotations:l.wyrm_intro,aliases:[],handler:async(o,c)=>{const{WYRM_PACKAGE_VERSION:s}=c,{renderIntro:a}=await import("../visibility.js");return{content:[{type:"text",text:a(s)}]}}},{name:"wyrm_migrate_prompt",description:"Rewrite the Wyrm-managed block in this project's AI-client instruction files (.cursor/rules, .github/copilot-instructions.md, CLAUDE.md, etc.) to the current canonical version. Operator-authored content outside the <!-- wyrm:start --> / <!-- wyrm:end --> markers is preserved verbatim. Dry-run by default; pass apply:true to write changes.",inputSchema:{type:"object",properties:{project_path:{type:"string",description:"Project root containing the client config files."},apply:{type:"boolean",description:"Default false (dry-run). Set true to actually write changes."}},required:["project_path"]},annotations:l.wyrm_migrate_prompt,aliases:[],handler:async(o,c)=>{const{migrateProject:s,renderMigrationReport:a}=await import("../migrate-prompt.js"),{WYRM_INJECT_BLOCK:t}=await import("../autoconfig.js"),{project_path:e,apply:r=!1}=o;if(!e)return{content:[{type:"text",text:"project_path required"}],isError:!0};const i=s({projectPath:e,newBlock:t,apply:r});return{content:[{type:"text",text:a(i,r)}]}}},{name:"wyrm_digest",description:"Render a plain-English summary of what Wyrm has done for the operator over a period. Shows counts (sessions tracked, quests completed, repeated failures blocked, ground truths set, references clipped, hours logged) and highlights so a non-technical operator can see Wyrm's actual contribution. Use this when the user asks 'what did Wyrm do?', 'is Wyrm doing anything?', or to demonstrate value to skeptics.",inputSchema:{type:"object",properties:{period:{type:"string",enum:["today","week","month","quarter","year","all"],description:"Reporting window (default: week)."}}},annotations:l.wyrm_digest,aliases:[],handler:async(o,c)=>{const{db:s}=c,{gatherDigest:a,renderDigest:t}=await import("../visibility.js"),{period:e="week"}=o,r=a(s.getDatabase(),e);return{content:[{type:"text",text:t(r)}]}}},{name:"wyrm_check_update",description:"Check whether a newer wyrm-mcp is available on npm. Cached 24h by default; pass force:true to bypass cache.",inputSchema:{type:"object",properties:{force:{type:"boolean",description:"Bypass the 24-hour cache and re-query the registry."}}},annotations:l.wyrm_check_update,aliases:[],handler:async(o,c)=>{const{WYRM_PACKAGE_VERSION:s,db:a}=c,{force:t=!1}=o,e=await b(a.getDatabase(),s,{force:t}),r=e.latest?e.lookupFailed?`${e.latest} (last known \u2014 registry unreachable now)`:e.latest:e.lookupFailed?"unknown \u2014 npm registry unreachable":"unknown (version check disabled)",i=e.updateAvailable?"yes":e.lookupFailed&&!e.latest?"unknown (lookup failed)":"no";let p=`\u{F115D} **Update check**
13
+ `),t.injected.length>0&&(e+="AI models in this project will now call `wyrm_session_prime` at the start of each conversation."),{content:[{type:"text",text:e}]}}},{name:"buddy",description:"Use when another buddy-compatible MCP server (or the operator) asks Wyrm for a brief, data-grounded status reply - the Buddy Protocol v1.0 well-known entry point. Companion params switch to the full companion reply; bare calls stay protocol-pure. Cycle-protected.",inputSchema:{type:"object",properties:{from_buddy:{type:"string",description:"Caller identity"},project_hint:{type:"string",description:"Scope the reply"},mood_hint:{type:"string",enum:["normal","celebratory","stuck","quiet"]},size:{type:"string",enum:["full","compact","mini"]},format:{type:"string",enum:["markdown","json"],description:"Peer mode only."},project_path:{type:"string",description:"Companion mode: project root (default cwd)"},persona:{type:"string",enum:["wyrm","drogo","berlin","sumair","custom"],description:"Companion voice."},persona_name:{type:"string",description:"For persona=custom"},mood:{type:"string",description:"Companion override (mood_hint values)"},federate:{type:"boolean",description:"Fold in other buddy MCPs"}}},annotations:l.buddy,aliases:[],handler:async(i,c)=>{const{WYRM_PACKAGE_VERSION:s,db:a,runCompanionBuddy:t}=c,e=i||{};if(m("size",e.size,["full","compact","mini"]),m("mood_hint",e.mood_hint,["normal","celebratory","stuck","quiet"]),m("format",e.format,["markdown","json"]),m("persona",e.persona,["wyrm","drogo","berlin","sumair","custom"]),_(i??{}))return await t(i??{});const r=i,o=f(a.getDatabase(),r,s);return r.format==="json"?{content:[{type:"text",text:JSON.stringify(o.json,null,2)}]}:{content:[{type:"text",text:o.markdown}]}}},{name:"wyrm_buddy",description:"Friendly, data-grounded coding companion. Returns a brief greeting + project state + suggested next step sourced entirely from Wyrm's real data. Every claim cites a real DB row ID \u2014 no hallucinated encouragement. Personas: wyrm (default, dragon-themed), drogo (gruff), berlin (precise), sumair (warm), custom. Moods: normal / celebratory / stuck / quiet \u2014 auto-detected. Sizes: full (10-line PhantomDragon-style mascot), compact (5-line workspace-friendly), mini (single-line inline prefix). Federates with other registered MCPs that expose `buddy`, `*_buddy`, or `buddy_*` tools \u2014 their replies fold into a 'From other buddies' section.",inputSchema:{type:"object",properties:{project_path:{type:"string",description:"Project root. Default: current cwd if registered."},mood:{type:"string",enum:["normal","celebratory","stuck","quiet"],description:"Override the auto-detected mood."},persona:{type:"string",enum:["wyrm","drogo","berlin","sumair","custom"],description:"Voice. Default: WYRM_BUDDY_DEFAULT_PERSONA env or 'wyrm'."},persona_name:{type:"string",description:"Required when persona === 'custom'."},size:{type:"string",enum:["full","compact","mini"],description:"Mascot size. 'full' = 10-line head+shoulder banner (default), 'compact' = 5-line workspace-friendly, 'mini' = single-line inline prefix on the greeting."},federate:{type:"boolean",description:"Query other registered MCPs with *_buddy tools and fold their replies in. Default: true if any are registered."}}},annotations:l.wyrm_buddy,aliases:[],handler:async(i,c)=>{const{runCompanionBuddy:s}=c;return await s(i??{})}},{name:"wyrm_token_set",description:"Set a design token for a project. Tokens are colour / type / spacing / motion / shadow / radius / breakpoint / custom \u2014 the design system primitives the project is built on. Stored first-class so AI helpers working on creative tasks don't have to re-derive the system from scattered CSS each time. Upserts on (project_id, category, key).",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Project root path."},category:{type:"string",enum:["color","type","spacing","motion","shadow","radius","breakpoint","custom"],description:"Token category."},key:{type:"string",description:"Short slug, e.g. 'primary' / 'h1' / 'page-padding-md'."},value:{type:"string",description:"Actual value: '#00B89F' / 'Inter 900 2.5rem' / '1.25rem' / etc."},notes:{type:"string",description:"Optional why / when-to-use guidance."},source:{type:"string",enum:["user","derived","imported","observed"],description:"How the token was determined. Default: 'user'."}},required:["projectPath","category","key","value"]},annotations:l.wyrm_token_set,aliases:[],handler:async(i,c)=>{const{db:s,designSystem:a}=c,{projectPath:t,category:e,key:r,value:o,notes:p,source:u}=i,n=s.getProject(t);if(!n)return{content:[{type:"text",text:`Project not found: ${t}`}],isError:!0};const d=a().setToken({projectId:n.id,category:e,key:r,value:o,notes:p,source:u});return{content:[{type:"text",text:`\u{1F3A8} token set: \`${d.category}/${d.key}\` = \`${d.value}\``+(d.notes?` \u2014 ${d.notes}`:"")}]}}},{name:"wyrm_token_get",description:"Retrieve all design tokens for a project, optionally filtered by category. Returns the design system primitives the project is built on.",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Project root path."},category:{type:"string",enum:["color","type","spacing","motion","shadow","radius","breakpoint","custom"],description:"Filter by category. Omit for all tokens."}},required:["projectPath"]},annotations:l.wyrm_token_get,aliases:[],handler:async(i,c)=>{const{db:s,designSystem:a}=c,{projectPath:t,category:e}=i,r=s.getProject(t);if(!r)return{content:[{type:"text",text:`Project not found: ${t}`}],isError:!0};const o=a().formatForContext(r.id);if(!o)return{content:[{type:"text",text:`_(no design tokens for ${r.name} yet \u2014 use \`wyrm_token_set\` to start the design system)_`}]};if(e){const p=a().listTokens(r.id,e);if(p.length===0)return{content:[{type:"text",text:`_(no ${e} tokens for ${r.name})_`}]};const u=[`### \u{1F3A8} ${e} tokens for ${r.name}`,""];for(const n of p)u.push(`- \`${n.key}\` = \`${n.value}\`${n.notes?` \u2014 ${n.notes}`:""}`);return{content:[{type:"text",text:u.join(`
14
+ `)}]}}return{content:[{type:"text",text:o}]}}},{name:"wyrm_token_delete",description:"Remove a design token from a project.",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Project root path."},category:{type:"string",enum:["color","type","spacing","motion","shadow","radius","breakpoint","custom"]},key:{type:"string",description:"Token key to remove."}},required:["projectPath","category","key"]},annotations:l.wyrm_token_delete,aliases:[],handler:async(i,c)=>{const{db:s,designSystem:a}=c,{projectPath:t,category:e,key:r}=i,o=s.getProject(t);return o?{content:[{type:"text",text:a().deleteToken(o.id,e,r)?`\u{1F5D1} removed \`${e}/${r}\``:`not found: \`${e}/${r}\``}]}:{content:[{type:"text",text:`Project not found: ${t}`}],isError:!0}}},{name:"wyrm_reference_add",description:"Clip a design reference (URL, image path, palette, or inline snippet) for inspiration. Stored per-project (or globally if no project path). Tag aggressively \u2014 tags drive future search and recall when building a new project in the same vein.",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Project root path. Omit to attach as a global reference."},kind:{type:"string",enum:["url","image","palette","snippet"],description:"Reference type."},location:{type:"string",description:"URL, file path, or inline payload (e.g. palette like '#00B89F,#ff5a55,#0a0c0e' or a code snippet)."},title:{type:"string",description:"Short title \u2014 what is this reference?"},notes:{type:"string",description:"Why you saved it / what to remember about it."},tags:{type:"string",description:"Comma-separated tags \u2014 'hero,scroll-tied,framer-motion'."}},required:["kind","location"]},annotations:l.wyrm_reference_add,aliases:[],handler:async(i,c)=>{const{db:s,designSystem:a}=c,{projectPath:t,kind:e,location:r,title:o,notes:p,tags:u}=i;let n=null;if(t){const y=s.getProject(t);if(!y)return{content:[{type:"text",text:`Project not found: ${t}`}],isError:!0};n=y.id}const d=a().addReference({projectId:n,kind:e,location:r,title:o,notes:p,tags:u}),h=n?` (project ${n})`:" (global)";return{content:[{type:"text",text:`\u{1F4CC} reference #${d.id} saved${h}: \`${d.kind}\` ${d.title??d.location.slice(0,60)}${d.tags?` \xB7 tags: ${d.tags}`:""}`}]}}},{name:"wyrm_reference_list",description:"Browse saved design references. Filter by project or tag. Default: 50 most recent across all projects.",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Limit to a specific project."},tag:{type:"string",description:"Filter to references containing this tag."},limit:{type:"number",description:"Max results (default 50, max 200)."}}},annotations:l.wyrm_reference_list,aliases:[],handler:async(i,c)=>{const{db:s,designSystem:a}=c,{projectPath:t,tag:e,limit:r}=i;let o;if(t){const n=s.getProject(t);if(!n)return{content:[{type:"text",text:`Project not found: ${t}`}],isError:!0};o=n.id}const p=a().listReferences({projectId:o,tag:e,limit:r});if(p.length===0)return{content:[{type:"text",text:"_(no references match)_"}]};const u=[`\u{1F4CC} **${p.length} reference${p.length===1?"":"s"}**`];for(const n of p){const d=n.project_id?`p${n.project_id}`:"global";u.push(`- [${n.id}/${d}] \`${n.kind}\` ${n.title??n.location.slice(0,60)}${n.tags?` \xB7 ${n.tags}`:""}`)}return{content:[{type:"text",text:u.join(`
15
+ `)}]}}},{name:"wyrm_reference_search",description:"Full-text search across design references' title / notes / tags. Returns matching references sorted by recency.",inputSchema:{type:"object",properties:{query:{type:"string",description:"FTS5 query \u2014 keywords, phrases in quotes."},projectPath:{type:"string",description:"Limit to a specific project."},limit:{type:"number",description:"Max results (default 20, max 100)."}},required:["query"]},annotations:l.wyrm_reference_search,aliases:[],handler:async(i,c)=>{const{db:s,designSystem:a}=c,{query:t,projectPath:e,limit:r}=i;let o;if(e){const n=s.getProject(e);if(!n)return{content:[{type:"text",text:`Project not found: ${e}`}],isError:!0};o=n.id}const p=a().searchReferences(t,{projectId:o,limit:r});if(p.length===0)return{content:[{type:"text",text:`_(no references match "${t}")_`}]};const u=[`\u{1F50D} **${p.length} reference${p.length===1?"":"s"}** matching "${t}"`];for(const n of p){const d=n.project_id?`p${n.project_id}`:"global";u.push(`- [${n.id}/${d}] \`${n.kind}\` ${n.title??n.location.slice(0,60)}${n.tags?` \xB7 ${n.tags}`:""}`)}return{content:[{type:"text",text:u.join(`
16
+ `)}]}}},{name:"wyrm_capabilities",description:"Use to learn what your memory system can actually do - feature inventory, why each matters, runtime state, and which tool to reach for. Call at session start instead of re-discovering the surface.",inputSchema:{type:"object",properties:{format:{type:"string",enum:["json","markdown"]}}},annotations:l.wyrm_capabilities,aliases:[],handler:async(i,c)=>{const{WYRM_TOOL_COUNT:s,agentDaemon:a,db:t,toolCounts:e}=c,r=m("format",i.format,["json","markdown"])??"markdown",o=e?.(),p=w(t.getDatabase(),o?.active??s,{hasEncryption:()=>!!process.env.WYRM_ENCRYPTION_KEY,isFederationEnabled:()=>!0,isAgentRunning:()=>{try{return a.status().running}catch{return null}}},o?.full);return r==="json"?{content:[{type:"text",text:JSON.stringify(p,null,2)}]}:{content:[{type:"text",text:j(p)}]}}},{name:"wyrm_intro",description:"Return a plain-English explanation of what Wyrm is and what it does. Aimed at HUMANS who don't yet understand Wyrm's value \u2014 co-founders, operators, designers, anyone who didn't read the docs. Call this when a human user asks 'what does Wyrm even do?' or seems unaware Wyrm is involved in their AI assistant's behavior. NOT for AI-agent self-orientation (use wyrm_capabilities for that).",inputSchema:{type:"object",properties:{}},annotations:l.wyrm_intro,aliases:[],handler:async(i,c)=>{const{WYRM_PACKAGE_VERSION:s}=c,{renderIntro:a}=await import("../visibility.js");return{content:[{type:"text",text:a(s)}]}}},{name:"wyrm_migrate_prompt",description:"Rewrite the Wyrm-managed block in this project's AI-client instruction files (.cursor/rules, .github/copilot-instructions.md, CLAUDE.md, etc.) to the current canonical version. Operator-authored content outside the <!-- wyrm:start --> / <!-- wyrm:end --> markers is preserved verbatim. Dry-run by default; pass apply:true to write changes.",inputSchema:{type:"object",properties:{project_path:{type:"string",description:"Project root containing the client config files."},apply:{type:"boolean",description:"Default false (dry-run). Set true to actually write changes."}},required:["project_path"]},annotations:l.wyrm_migrate_prompt,aliases:[],handler:async(i,c)=>{const{migrateProject:s,renderMigrationReport:a}=await import("../migrate-prompt.js"),{WYRM_INJECT_BLOCK:t}=await import("../autoconfig.js"),{project_path:e,apply:r=!1}=i;if(!e)return{content:[{type:"text",text:"project_path required"}],isError:!0};const o=s({projectPath:e,newBlock:t,apply:r});return{content:[{type:"text",text:a(o,r)}]}}},{name:"wyrm_digest",description:"Render a plain-English summary of what Wyrm has done for the operator over a period. Shows counts (sessions tracked, quests completed, repeated failures blocked, ground truths set, references clipped, hours logged) and highlights so a non-technical operator can see Wyrm's actual contribution. Use this when the user asks 'what did Wyrm do?', 'is Wyrm doing anything?', or to demonstrate value to skeptics.",inputSchema:{type:"object",properties:{period:{type:"string",enum:["today","week","month","quarter","year","all"],description:"Reporting window (default: week)."}}},annotations:l.wyrm_digest,aliases:[],handler:async(i,c)=>{const{db:s}=c,{gatherDigest:a,renderDigest:t}=await import("../visibility.js"),{period:e="week"}=i,r=a(s.getDatabase(),e);return{content:[{type:"text",text:t(r)}]}}},{name:"wyrm_check_update",description:"Check whether a newer wyrm-mcp is available on npm. Cached 24h by default; pass force:true to bypass cache.",inputSchema:{type:"object",properties:{force:{type:"boolean",description:"Bypass the 24-hour cache and re-query the registry."}}},annotations:l.wyrm_check_update,aliases:[],handler:async(i,c)=>{const{WYRM_PACKAGE_VERSION:s,db:a}=c,{force:t=!1}=i,e=await b(a.getDatabase(),s,{force:t}),r=e.latest?e.lookupFailed?`${e.latest} (last known \u2014 registry unreachable now)`:e.latest:e.lookupFailed?"unknown \u2014 npm registry unreachable":"unknown (version check disabled)",o=e.updateAvailable?"yes":e.lookupFailed&&!e.latest?"unknown (lookup failed)":"no";let p=`\u{F115D} **Update check**
17
17
 
18
18
  - Current: ${e.current}
19
19
  - Latest: ${r}
20
- - Update available: ${i}
21
- - Checked: ${e.checkedAt} (${e.source})`;return e.updateAvailable&&(p+="\n\nRun `wyrm_self_update` with `confirm: true` to upgrade, or `npm install -g wyrm-mcp@latest` from your shell."),{content:[{type:"text",text:p}]}}},{name:"wyrm_self_update",description:"Run `npm install -g wyrm-mcp@latest` to upgrade. Returns the install transcript. Requires the operator to have write access to the global npm prefix.",inputSchema:{type:"object",properties:{confirm:{type:"boolean",description:"Must be true to actually run the upgrade (safety guard)."}},required:["confirm"]},annotations:l.wyrm_self_update,aliases:[],handler:async(o,c)=>{const{confirm:s=!1}=o;return s?{content:[{type:"text",text:`\u{F115D} **Self-update transcript**
20
+ - Update available: ${o}
21
+ - Checked: ${e.checkedAt} (${e.source})`;return e.updateAvailable&&(p+="\n\nRun `wyrm_self_update` with `confirm: true` to upgrade, or `npm install -g wyrm-mcp@latest` from your shell."),{content:[{type:"text",text:p}]}}},{name:"wyrm_self_update",description:"Run `npm install -g wyrm-mcp@latest` to upgrade. Returns the install transcript. Requires the operator to have write access to the global npm prefix.",inputSchema:{type:"object",properties:{confirm:{type:"boolean",description:"Must be true to actually run the upgrade (safety guard)."}},required:["confirm"]},annotations:l.wyrm_self_update,aliases:[],handler:async(i,c)=>{const{confirm:s=!1}=i;return s?{content:[{type:"text",text:`\u{F115D} **Self-update transcript**
22
22
 
23
23
  \`\`\`
24
- ${await new Promise(t=>{const e=k("npm",["install","-g","wyrm-mcp@latest","--allow-scripts=wyrm-mcp,better-sqlite3"],{stdio:["ignore","pipe","pipe"]}),r=[];e.stdout.on("data",i=>r.push(i.toString())),e.stderr.on("data",i=>r.push(i.toString())),e.on("close",i=>{r.push(`
25
- [exit ${i??"unknown"}]`),t(r.join(""))}),e.on("error",i=>t(`spawn failed: ${i.message}`))})}
24
+ ${await new Promise(t=>{const e=k("npm",["install","-g","wyrm-mcp@latest","--allow-scripts=wyrm-mcp,better-sqlite3"],{stdio:["ignore","pipe","pipe"]}),r=[];e.stdout.on("data",o=>r.push(o.toString())),e.stderr.on("data",o=>r.push(o.toString())),e.on("close",o=>{r.push(`
25
+ [exit ${o??"unknown"}]`),t(r.join(""))}),e.on("error",o=>t(`spawn failed: ${o.message}`))})}
26
26
  \`\`\`
27
27
 
28
28
  Restart your MCP client to pick up the new binary.`}]}:{content:[{type:"text",text:"\u{F115D} **Self-update declined**: call again with `confirm: true` to run `npm install -g wyrm-mcp@latest`. Note: requires write access to the global npm prefix."}]}}}];export{E as companionToolSpecs};
@@ -1,5 +1,5 @@
1
- import{TOOL_ANNOTATIONS as f}from"../tool-annotations.js";import{installClaudeCodeHooks as x,installClaudeStatusline as w}from"../autoconfig.js";import{replicateOnce as _}from"../event-replication.js";import{embedAll as P,removeAll as E,statusAll as S}from"../priority-embed.js";import{ReplicationDaemon as k,addPeer as M,listPeers as b,removePeer as $}from"../replication-daemon.js";import{ValidationError as j,asEnum as R}from"../validate.js";const C=[{name:"wyrm_events_subscribe",description:"Use to START consuming a project's Live Memory event stream: returns the current head cursor plus a recent chronological window of events in one read-only call. Nothing persists server-side \u2014 poll wyrm_events_since with the returned cursor for new events. Returns disabled:true if WYRM_LIVE_MEMORY is off. Keywords: events, live memory, event stream, cursor, tail events, watch project, replication feed.",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Project path"},window:{type:"number",description:"How many recent events to return (1-200, default 20)"}},required:["projectPath"]},annotations:f.wyrm_events_subscribe,aliases:[],handler:async(a,m)=>{const{db:r}=m,o=a||{},c=(s=>{const n=s.filter(e=>!o[e]&&o[e]!==0&&o[e]!==!1);return n.length>0?{content:[{type:"text",text:`Missing required arguments: ${n.join(", ")}`}],isError:!0}:null})(["projectPath"]);if(c)return c;const{projectPath:d,window:l}=a;if(!r.liveMemoryEnabled())return{content:[{type:"text",text:JSON.stringify({disabled:!0,hint:"Set WYRM_LIVE_MEMORY=1 to enable the event log."})}]};const i=r.getProject(d);if(!i)return{content:[{type:"text",text:"Project not found"}]};const p=r.subscribeEvents(i.id,l);return{content:[{type:"text",text:JSON.stringify({project:i.name,cursor:p.cursor,count:p.recent.length,events:p.recent})}]}}},{name:"wyrm_events_since",description:"Live Memory: pull events newer than a cursor for a project, in chronological order. Idempotent \u2014 safe to re-poll. Returns the events and the new head cursor to pass next time.",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Project path"},cursor:{type:"number",description:"Return events with cursor greater than this (default 0 = from start)"},limit:{type:"number",description:"Max events to return (1-1000, default 100)"}},required:["projectPath"]},annotations:f.wyrm_events_since,aliases:[],handler:async(a,m)=>{const{db:r}=m,o=a||{},c=(e=>{const g=e.filter(t=>!o[t]&&o[t]!==0&&o[t]!==!1);return g.length>0?{content:[{type:"text",text:`Missing required arguments: ${g.join(", ")}`}],isError:!0}:null})(["projectPath"]);if(c)return c;const{projectPath:d,cursor:l,limit:i}=a;if(!r.liveMemoryEnabled())return{content:[{type:"text",text:JSON.stringify({disabled:!0,hint:"Set WYRM_LIVE_MEMORY=1 to enable the event log."})}]};const p=r.getProject(d);if(!p)return{content:[{type:"text",text:"Project not found"}]};const s=r.eventsSince(p.id,l||0,i||100),n=s.length?s[s.length-1].cursor:l||0;return{content:[{type:"text",text:JSON.stringify({project:p.name,cursor:n,count:s.length,events:s})}]}}},{name:"wyrm_events_publish",description:"Live Memory: manually publish an event onto a project's stream (e.g. a tool_call or presence marker). Failure-isolated \u2014 never throws. Payload is stored only when isShared is true (reference-only otherwise).",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Project path"},kind:{type:"string",enum:["capture","session_update","quest","truth","presence","tool_call"],description:"Event kind"},refTable:{type:"string",description:"Canonical table this event refers to (optional)"},refId:{type:"string",description:"Canonical row id this event refers to (optional)"},actor:{type:"string",description:"Who/what produced the event (optional)"},isShared:{type:"boolean",description:"If true, the payload is denormalized into the event (default false = reference-only)"},payload:{type:"object",description:"Denormalized snapshot, stored only when isShared is true"}},required:["projectPath","kind"]},annotations:f.wyrm_events_publish,aliases:[],handler:async(a,m)=>{const{db:r,presence:o}=m,y=a||{},d=(u=>{const h=u.filter(v=>!y[v]&&y[v]!==0&&y[v]!==!1);return h.length>0?{content:[{type:"text",text:`Missing required arguments: ${h.join(", ")}`}],isError:!0}:null})(["projectPath","kind"]);if(d)return d;const{projectPath:l,kind:i,refTable:p,refId:s,actor:n,isShared:e,payload:g}=a;if(!r.liveMemoryEnabled())return{content:[{type:"text",text:JSON.stringify({disabled:!0,hint:"Set WYRM_LIVE_MEMORY=1 to enable the event log."})}]};const t=r.getProject(l);return t?(r.publishEvent({projectId:t.id,kind:i,refTable:p,refId:s,actor:n,isShared:e,payload:g}),{content:[{type:"text",text:`\u{F115D} Event published to ${t.name} stream (${i})`}]}):{content:[{type:"text",text:"Project not found"}]}}},{name:"wyrm_events_replicate",description:"Live Memory (Phase 3): one-shot cross-device replication with a peer Wyrm node \u2014 pulls the peer's shared events and pushes ours over the peer's HTTP surface. Privacy-gated (only is_shared events leave the box; reference-only rows carry no payload), with automatic dedup + echo-suppression. Watermarks persist, so repeat calls are incremental. Returns pulled/pushed/duplicate counts.",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Local project path"},peerUrl:{type:"string",description:"Peer Wyrm HTTP (wyrm serve) base URL, e.g. http://other-host:3333"},token:{type:"string",description:"Bearer token for the peer (optional if the peer runs open / dev mode)"},remoteProject:{type:"string",description:"Project path or name on the peer (defaults to this project's name)"}},required:["projectPath","peerUrl"]},annotations:f.wyrm_events_replicate,aliases:[],handler:async(a,m)=>{const{db:r}=m,o=a||{},c=(t=>{const u=t.filter(h=>!o[h]&&o[h]!==0&&o[h]!==!1);return u.length>0?{content:[{type:"text",text:`Missing required arguments: ${u.join(", ")}`}],isError:!0}:null})(["projectPath","peerUrl"]);if(c)return c;if(!r.liveMemoryEnabled())return{content:[{type:"text",text:JSON.stringify({disabled:!0,hint:"Set WYRM_LIVE_MEMORY=1 to enable replication."})}]};const{projectPath:d,peerUrl:l,token:i,remoteProject:p}=a,s=r.getProject(d);if(!s)return{content:[{type:"text",text:"Project not found"}]};const n={baseUrl:String(l).replace(/\/+$/,""),token:i,localProjectId:s.id,remoteProject:p||s.name},e=await _(r,n,fetch);return{content:[{type:"text",text:`\u{F115D} Replicated ${s.name} \u2194 ${n.baseUrl}: pulled ${e.pulled}, pushed ${e.pushed}, dup ${e.duplicates}, echo ${e.echoes}`+(e.errors.length?`
2
- \u26A0\uFE0F ${e.errors.join("; ")}`:"")}]}}},{name:"wyrm_replication",description:"Use to sync memory between machines and watch the live event mesh - action=start|stop|status|tick (daemon + peers; tokens via tokenEnv), conflicts (did the last sync leave conflicts to resolve?), resolve, publish, since, subscribe (watch a project's live activity feed for changes from other agents), replicate (one-shot: sync my memory over to another machine or peer). Back up the memory database off this machine, encrypted: wyrm cloud backup (CLI).",inputSchema:{type:"object",properties:{action:{type:"string",enum:["start","stop","status","tick","peer_add","peer_remove","peer_list","conflicts","resolve","publish","since","subscribe","replicate"]},interval_minutes:{type:"number",description:"start (default 1)"},baseUrl:{type:"string",description:"peer_add/peer_remove"},projectPath:{type:"string"},remoteProject:{type:"string",description:"peer_add/replicate"},tokenEnv:{type:"string",description:"peer_add: env var NAME with the peer token"},limit:{type:"number",description:"conflicts/since"},conflict_id:{type:"number",description:"resolve"},mode:{type:"string",description:"resolve: kept_local|kept_remote|merged"},kind:{type:"string",description:"publish"},payload:{type:"object",description:"publish"},cursor:{type:"number",description:"since"},peerUrl:{type:"string",description:"replicate"},token:{type:"string",description:"replicate: bearer"}},required:["action"]},annotations:f.wyrm_replication,aliases:[],handler:async(a,m)=>{const{db:r,replicationManager:o}=m,y=["start","stop","status","tick","peer_add","peer_remove","peer_list","conflicts","resolve","publish","since","subscribe","replicate"],c=R("action",a.action,y);if(c===void 0)throw new j("action",`is required (one of: ${y.join(", ")})`);const{interval_minutes:d,baseUrl:l,projectPath:i,remoteProject:p,tokenEnv:s}=a;switch(c){case"start":{if(!r.liveMemoryEnabled())return{content:[{type:"text",text:"Live Memory is off (WYRM_LIVE_MEMORY=0) \u2014 daemon not started."}]};const n=o.start({interval_minutes:d});return{content:[{type:"text",text:n.ok?`\u{F115D} Replication daemon started (pid ${n.pid})`:`Not started: ${n.reason}`}]}}case"stop":{const n=o.stop();return{content:[{type:"text",text:n.ok?"\u{F115D} Replication daemon stopping":`Not stopped: ${n.reason}`}]}}case"status":{const n=o.status(),e=b(r);return{content:[{type:"text",text:JSON.stringify({running:n.running,pid:n.pid,peers:e})}]}}case"tick":{const e=await new k(r).tick(fetch);return{content:[{type:"text",text:JSON.stringify({peers:e.length,results:e})}]}}case"peer_add":{if(!l||!i)return{content:[{type:"text",text:"peer_add requires baseUrl and projectPath"}]};if(!r.getProject(i))return{content:[{type:"text",text:"Project not found"}]};try{return{content:[{type:"text",text:`\u{F115D} Peer added. Registry now has ${M(r,{baseUrl:l,projectPath:i,remoteProject:p,tokenEnv:s}).length} peer(s).`}]}}catch(e){return{content:[{type:"text",text:`Peer rejected: ${e instanceof Error?e.message:String(e)}`}]}}}case"peer_remove":return!l||!i?{content:[{type:"text",text:"peer_remove requires baseUrl and projectPath"}]}:{content:[{type:"text",text:`\u{F115D} Peer removed. Registry now has ${$(r,l,i).length} peer(s).`}]};case"peer_list":return{content:[{type:"text",text:JSON.stringify({peers:b(r)})}]};default:throw new j("action",`'${c}' is routed by the wyrm_replication tool; call wyrm_replication with action=${c}`)}}},{name:"wyrm_embed",description:"Make Wyrm FIRST-PRIORITY, always-loaded memory across Claude Code and other CLIs \u2014 so it isn't ignored and is consulted proactively, not just when called. Prepends a managed 'Wyrm is your memory \u2014 consult it FIRST' directive to the TOP of each client's always-loaded instruction file (~/.claude/CLAUDE.md globally; project CLAUDE.md/AGENTS.md, and with allClients also .cursorrules/.windsurfrules/.github/copilot-instructions.md). install also installs the proactive SessionStart/predictive hooks. Idempotent + fully reversible. action=install|status|remove.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["install","status","remove"],description:"Operation to perform"},projectPath:{type:"string",description:"Also embed into this project's instruction files (CLAUDE.md + AGENTS.md)"},allClients:{type:"boolean",description:"With projectPath: also write Cursor/.cursorrules, Windsurf/.windsurfrules, and GitHub Copilot instructions (default false)"}},required:["action"]},annotations:f.wyrm_embed,aliases:[],handler:async(a,m)=>{const{memory:r}=m,o=a||{},c=(t=>{const u=t.filter(h=>!o[h]&&o[h]!==0&&o[h]!==!1);return u.length>0?{content:[{type:"text",text:`Missing required arguments: ${u.join(", ")}`}],isError:!0}:null})(["action"]);if(c)return c;const{action:d,projectPath:l,allClients:i}=a,p={projectDir:l,allClients:i},s=t=>t.map(u=>` ${String(u.result??u.status).padEnd(9)} [${u.scope}] ${u.file}`).join(`
1
+ import{TOOL_ANNOTATIONS as f}from"../tool-annotations.js";import{installClaudeCodeHooks as x,installClaudeStatusline as w}from"../autoconfig.js";import{replicateOnce as _}from"../event-replication.js";import{embedAll as P,removeAll as E,statusAll as S}from"../priority-embed.js";import{ReplicationDaemon as k,addPeer as M,listPeers as b,removePeer as $}from"../replication-daemon.js";import{ValidationError as j,asEnum as R}from"../validate.js";const N=[{name:"wyrm_events_subscribe",description:"Use to START consuming a project's Live Memory event stream: returns the current head cursor plus a recent chronological window of events in one read-only call. Nothing persists server-side \u2014 poll wyrm_events_since with the returned cursor for new events. Returns disabled:true if WYRM_LIVE_MEMORY is off. Keywords: events, live memory, event stream, cursor, tail events, watch project, replication feed.",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Project path"},window:{type:"number",description:"How many recent events to return (1-200, default 20)"}},required:["projectPath"]},annotations:f.wyrm_events_subscribe,aliases:[],handler:async(a,m)=>{const{db:r}=m,o=a||{},c=(s=>{const n=s.filter(e=>!o[e]&&o[e]!==0&&o[e]!==!1);return n.length>0?{content:[{type:"text",text:`Missing required arguments: ${n.join(", ")}`}],isError:!0}:null})(["projectPath"]);if(c)return c;const{projectPath:d,window:l}=a;if(!r.liveMemoryEnabled())return{content:[{type:"text",text:JSON.stringify({disabled:!0,hint:"Set WYRM_LIVE_MEMORY=1 to enable the event log."})}]};const i=r.getProject(d);if(!i)return{content:[{type:"text",text:"Project not found"}]};const p=r.subscribeEvents(i.id,l);return{content:[{type:"text",text:JSON.stringify({project:i.name,cursor:p.cursor,count:p.recent.length,events:p.recent})}]}}},{name:"wyrm_events_since",description:"Live Memory: pull events newer than a cursor for a project, in chronological order. Idempotent \u2014 safe to re-poll. Returns the events and the new head cursor to pass next time.",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Project path"},cursor:{type:"number",description:"Return events with cursor greater than this (default 0 = from start)"},limit:{type:"number",description:"Max events to return (1-1000, default 100)"}},required:["projectPath"]},annotations:f.wyrm_events_since,aliases:[],handler:async(a,m)=>{const{db:r}=m,o=a||{},c=(e=>{const g=e.filter(t=>!o[t]&&o[t]!==0&&o[t]!==!1);return g.length>0?{content:[{type:"text",text:`Missing required arguments: ${g.join(", ")}`}],isError:!0}:null})(["projectPath"]);if(c)return c;const{projectPath:d,cursor:l,limit:i}=a;if(!r.liveMemoryEnabled())return{content:[{type:"text",text:JSON.stringify({disabled:!0,hint:"Set WYRM_LIVE_MEMORY=1 to enable the event log."})}]};const p=r.getProject(d);if(!p)return{content:[{type:"text",text:"Project not found"}]};const s=r.eventsSince(p.id,l||0,i||100),n=s.length?s[s.length-1].cursor:l||0;return{content:[{type:"text",text:JSON.stringify({project:p.name,cursor:n,count:s.length,events:s})}]}}},{name:"wyrm_events_publish",description:"Live Memory: manually publish an event onto a project's stream (e.g. a tool_call or presence marker). Failure-isolated \u2014 never throws. Payload is stored only when isShared is true (reference-only otherwise).",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Project path"},kind:{type:"string",enum:["capture","session_update","quest","truth","presence","tool_call"],description:"Event kind"},refTable:{type:"string",description:"Canonical table this event refers to (optional)"},refId:{type:"string",description:"Canonical row id this event refers to (optional)"},actor:{type:"string",description:"Who/what produced the event (optional)"},isShared:{type:"boolean",description:"If true, the payload is denormalized into the event (default false = reference-only)"},payload:{type:"object",description:"Denormalized snapshot, stored only when isShared is true"}},required:["projectPath","kind"]},annotations:f.wyrm_events_publish,aliases:[],handler:async(a,m)=>{const{db:r,presence:o}=m,h=a||{},d=(u=>{const y=u.filter(v=>!h[v]&&h[v]!==0&&h[v]!==!1);return y.length>0?{content:[{type:"text",text:`Missing required arguments: ${y.join(", ")}`}],isError:!0}:null})(["projectPath","kind"]);if(d)return d;const{projectPath:l,kind:i,refTable:p,refId:s,actor:n,isShared:e,payload:g}=a;if(!r.liveMemoryEnabled())return{content:[{type:"text",text:JSON.stringify({disabled:!0,hint:"Set WYRM_LIVE_MEMORY=1 to enable the event log."})}]};const t=r.getProject(l);return t?(r.publishEvent({projectId:t.id,kind:i,refTable:p,refId:s,actor:n,isShared:e,payload:g}),{content:[{type:"text",text:`\u{F115D} Event published to ${t.name} stream (${i})`}]}):{content:[{type:"text",text:"Project not found"}]}}},{name:"wyrm_events_replicate",description:"Live Memory (Phase 3): one-shot cross-device replication with a peer Wyrm node \u2014 pulls the peer's shared events and pushes ours over the peer's HTTP surface. Privacy-gated (only is_shared events leave the box; reference-only rows carry no payload), with automatic dedup + echo-suppression. Watermarks persist, so repeat calls are incremental. Returns pulled/pushed/duplicate counts.",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Local project path"},peerUrl:{type:"string",description:"Peer Wyrm HTTP (wyrm serve) base URL, e.g. http://other-host:3333"},token:{type:"string",description:"Bearer token for the peer (optional if the peer runs open / dev mode)"},remoteProject:{type:"string",description:"Project path or name on the peer (defaults to this project's name)"}},required:["projectPath","peerUrl"]},annotations:f.wyrm_events_replicate,aliases:[],handler:async(a,m)=>{const{db:r}=m,o=a||{},c=(t=>{const u=t.filter(y=>!o[y]&&o[y]!==0&&o[y]!==!1);return u.length>0?{content:[{type:"text",text:`Missing required arguments: ${u.join(", ")}`}],isError:!0}:null})(["projectPath","peerUrl"]);if(c)return c;if(!r.liveMemoryEnabled())return{content:[{type:"text",text:JSON.stringify({disabled:!0,hint:"Set WYRM_LIVE_MEMORY=1 to enable replication."})}]};const{projectPath:d,peerUrl:l,token:i,remoteProject:p}=a,s=r.getProject(d);if(!s)return{content:[{type:"text",text:"Project not found"}]};const n={baseUrl:String(l).replace(/\/+$/,""),token:i,localProjectId:s.id,remoteProject:p||s.name},e=await _(r,n,fetch);return{content:[{type:"text",text:`\u{F115D} Replicated ${s.name} \u2194 ${n.baseUrl}: pulled ${e.pulled}, pushed ${e.pushed}, dup ${e.duplicates}, echo ${e.echoes}`+(e.errors.length?`
2
+ \u26A0\uFE0F ${e.errors.join("; ")}`:"")}]}}},{name:"wyrm_replication",description:"Use to sync memory between machines and watch the live event mesh - action=start|stop|status|tick (daemon + peers; tokens via tokenEnv), conflicts (did the last sync leave conflicts to resolve?), resolve, publish, since, subscribe (watch a project's live activity feed for changes from other agents), replicate (one-shot: sync my memory over to another machine or peer), unshare (kind + id: make a record private again so it does not leave this machine). Back up the memory database off this machine, encrypted: wyrm cloud backup (CLI).",inputSchema:{type:"object",properties:{action:{type:"string",enum:["start","stop","status","tick","peer_add","peer_remove","peer_list","conflicts","resolve","publish","since","subscribe","replicate","unshare"]},interval_minutes:{type:"number",description:"start"},baseUrl:{type:"string",description:"peer_add/remove"},projectPath:{type:"string"},remoteProject:{type:"string",description:"peer_add/replicate"},tokenEnv:{type:"string",description:"peer_add: env var name"},limit:{type:"number",description:"conflicts/since"},conflict_id:{type:"number",description:"resolve"},mode:{type:"string",description:"resolve: kept_local|kept_remote|merged"},kind:{type:"string",enum:["capture","session_update","quest","truth","presence","tool_call","session","artifact","edge"]},id:{type:"number"},payload:{type:"object",description:"publish"},cursor:{type:"number",description:"since"},peerUrl:{type:"string",description:"replicate"},token:{type:"string",description:"replicate"}},required:["action"]},annotations:f.wyrm_replication,aliases:[],handler:async(a,m)=>{const{db:r,replicationManager:o}=m,h=["start","stop","status","tick","peer_add","peer_remove","peer_list","conflicts","resolve","publish","since","subscribe","replicate","unshare"],c=R("action",a.action,h);if(c===void 0)throw new j("action",`is required (one of: ${h.join(", ")})`);const{interval_minutes:d,baseUrl:l,projectPath:i,remoteProject:p,tokenEnv:s}=a;switch(c){case"start":{if(!r.liveMemoryEnabled())return{content:[{type:"text",text:"Live Memory is off (WYRM_LIVE_MEMORY=0) \u2014 daemon not started."}]};const n=o.start({interval_minutes:d});return{content:[{type:"text",text:n.ok?`\u{F115D} Replication daemon started (pid ${n.pid})`:`Not started: ${n.reason}`}]}}case"stop":{const n=o.stop();return{content:[{type:"text",text:n.ok?"\u{F115D} Replication daemon stopping":`Not stopped: ${n.reason}`}]}}case"status":{const n=o.status(),e=b(r);return{content:[{type:"text",text:JSON.stringify({running:n.running,pid:n.pid,peers:e})}]}}case"tick":{const e=await new k(r).tick(fetch);return{content:[{type:"text",text:JSON.stringify({peers:e.length,results:e})}]}}case"peer_add":{if(!l||!i)return{content:[{type:"text",text:"peer_add requires baseUrl and projectPath"}]};if(!r.getProject(i))return{content:[{type:"text",text:"Project not found"}]};try{return{content:[{type:"text",text:`\u{F115D} Peer added. Registry now has ${M(r,{baseUrl:l,projectPath:i,remoteProject:p,tokenEnv:s}).length} peer(s).`}]}}catch(e){return{content:[{type:"text",text:`Peer rejected: ${e instanceof Error?e.message:String(e)}`}]}}}case"peer_remove":return!l||!i?{content:[{type:"text",text:"peer_remove requires baseUrl and projectPath"}]}:{content:[{type:"text",text:`\u{F115D} Peer removed. Registry now has ${$(r,l,i).length} peer(s).`}]};case"peer_list":return{content:[{type:"text",text:JSON.stringify({peers:b(r)})}]};default:throw new j("action",`'${c}' is routed by the wyrm_replication tool; call wyrm_replication with action=${c}`)}}},{name:"wyrm_embed",description:"Make Wyrm FIRST-PRIORITY, always-loaded memory across Claude Code and other CLIs \u2014 so it isn't ignored and is consulted proactively, not just when called. Prepends a managed 'Wyrm is your memory \u2014 consult it FIRST' directive to the TOP of each client's always-loaded instruction file (~/.claude/CLAUDE.md globally; project CLAUDE.md/AGENTS.md, and with allClients also .cursorrules/.windsurfrules/.github/copilot-instructions.md). install also installs the proactive SessionStart/predictive hooks. Idempotent + fully reversible. action=install|status|remove.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["install","status","remove"],description:"Operation to perform"},projectPath:{type:"string",description:"Also embed into this project's instruction files (CLAUDE.md + AGENTS.md)"},allClients:{type:"boolean",description:"With projectPath: also write Cursor/.cursorrules, Windsurf/.windsurfrules, and GitHub Copilot instructions (default false)"}},required:["action"]},annotations:f.wyrm_embed,aliases:[],handler:async(a,m)=>{const{memory:r}=m,o=a||{},c=(t=>{const u=t.filter(y=>!o[y]&&o[y]!==0&&o[y]!==!1);return u.length>0?{content:[{type:"text",text:`Missing required arguments: ${u.join(", ")}`}],isError:!0}:null})(["action"]);if(c)return c;const{action:d,projectPath:l,allClients:i}=a,p={projectDir:l,allClients:i},s=t=>t.map(u=>` ${String(u.result??u.status).padEnd(9)} [${u.scope}] ${u.file}`).join(`
3
3
  `);if(d==="status")return{content:[{type:"text",text:`\u{F115D} Wyrm priority embedding status:
4
4
  ${s(S(p))}`}]};if(d==="remove")return{content:[{type:"text",text:`\u{F115D} Removed Wyrm priority embedding:
5
5
  ${s(E(p))}`}]};const n=P(p);let e="";try{const t=x();e=t?`
@@ -13,4 +13,4 @@ ${s(E(p))}`}]};const n=P(p);let e="";try{const t=x();e=t?`
13
13
  (Statusline skipped: ${t instanceof Error?t.message:String(t)})`}return{content:[{type:"text",text:`\u{F115D} Wyrm is now FIRST-PRIORITY memory:
14
14
  ${s(n)}${e}${g}
15
15
 
16
- Wyrm will now be read at the top of every session, primed proactively, and shown in the TUI at all times \u2014 not waiting to be called.`}]}}}];export{C as eventsToolSpecs};
16
+ Wyrm will now be read at the top of every session, primed proactively, and shown in the TUI at all times \u2014 not waiting to be called.`}]}}}];export{N as eventsToolSpecs};