wyrm-mcp 8.5.4 → 8.5.5

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,6 +1,6 @@
1
1
  {
2
2
  "contract": "wyrm-cloud-memory-v2",
3
- "version": "8.5.4",
3
+ "version": "8.5.5",
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.5.4",
3
+ "version": "8.5.5",
4
4
  "tools": [
5
5
  {
6
6
  "name": "wyrm_recall",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "contract": "wyrm-cloud-memory-v1",
3
- "version": "8.5.4",
3
+ "version": "8.5.5",
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.5.4",
3
+ "version": "8.5.5",
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",
@@ -16,7 +16,32 @@
16
16
  */
17
17
  import { readFileSync, existsSync } from 'node:fs';
18
18
  import { spawnSync } from 'node:child_process';
19
- import { basename } from 'node:path';
19
+ import { basename, join } from 'node:path';
20
+ import { homedir } from 'node:os';
21
+
22
+ // Portable, self-healing `wyrm` resolution: bare command first (honors PATH),
23
+ // then npm/user CLI-install locations that exist here — so a minimal PATH
24
+ // (containers / CI / cron) doesn't silently drop the capture. NOT /usr/bin: a
25
+ // distro or global scoped-package can put an unrelated `wyrm` there, and
26
+ // resolving to the wrong binary is worse than not resolving at all.
27
+ function wyrmCandidates() {
28
+ const home = homedir();
29
+ return [
30
+ 'wyrm',
31
+ join(home, '.npm-global', 'bin', 'wyrm'),
32
+ join(home, '.local', 'bin', 'wyrm'),
33
+ join(home, 'node_modules', '.bin', 'wyrm'),
34
+ '/usr/local/bin/wyrm',
35
+ '/opt/homebrew/bin/wyrm',
36
+ ].filter((c) => c === 'wyrm' || existsSync(c));
37
+ }
38
+ function runWyrm(args, opts) {
39
+ for (const bin of wyrmCandidates()) {
40
+ const r = spawnSync(bin, args, opts);
41
+ if (!(r.error && r.error.code === 'ENOENT')) return r; // spawned (even if non-zero exit)
42
+ }
43
+ return { status: 127, stdout: '', error: new Error('wyrm not found') };
44
+ }
20
45
 
21
46
  function extractText(content) {
22
47
  if (content == null) return '';
@@ -49,7 +74,7 @@ const tpath = payload.transcript_path;
49
74
  // harness dies without a SessionEnd, pid-liveness marks the row DEAD anyway.
50
75
  try {
51
76
  if (event === 'SessionEnd' && payload.session_id) {
52
- spawnSync('wyrm', ['presence', 'release', '--agent', `claude-${String(payload.session_id).slice(0, 8)}`],
77
+ runWyrm(['presence', 'release', '--agent', `claude-${String(payload.session_id).slice(0, 8)}`],
53
78
  { timeout: 10000, stdio: 'ignore' });
54
79
  }
55
80
  } catch { /* never break the session */ }
@@ -105,7 +130,7 @@ if (process.env.WYRM_CAPTURE_DRYRUN === '1') {
105
130
  }
106
131
 
107
132
  try {
108
- spawnSync('wyrm', ['capture', content, '--project', project, '--mode', 'memory'], {
133
+ runWyrm(['capture', content, '--project', project, '--mode', 'memory'], {
109
134
  timeout: 25000,
110
135
  stdio: 'ignore',
111
136
  });
@@ -121,7 +146,7 @@ try {
121
146
  if (payload.session_id) sessionArgs.push('--run', String(payload.session_id).slice(0, 64));
122
147
  if (objective) sessionArgs.push('--objectives', objective);
123
148
  sessionArgs.push('--completed', content);
124
- spawnSync('wyrm', sessionArgs, { timeout: 25000, stdio: 'ignore' });
149
+ runWyrm(sessionArgs, { timeout: 25000, stdio: 'ignore' });
125
150
  } catch { /* older wyrm without `session log`, or write failed — never break the session */ }
126
151
 
127
152
  process.exit(0);
@@ -17,11 +17,36 @@
17
17
  *
18
18
  * @copyright 2026 Ghost Protocol (Pvt) Ltd.
19
19
  */
20
- import { readFileSync } from 'node:fs';
20
+ import { readFileSync, existsSync } from 'node:fs';
21
21
  import { spawnSync } from 'node:child_process';
22
+ import { homedir } from 'node:os';
23
+ import { join } from 'node:path';
22
24
 
23
25
  if (process.env.WYRM_AUTO_PRUNE !== '1') process.exit(0); // opt-in only
24
26
 
27
+ // Portable, self-healing `wyrm` resolution: bare command first (honors PATH),
28
+ // then npm/user CLI-install locations that exist here — so a minimal PATH
29
+ // (containers / CI / cron) doesn't silently skip the prune. NOT /usr/bin: a
30
+ // distro or global scoped-package can put an unrelated `wyrm` there.
31
+ function wyrmCandidates() {
32
+ const home = homedir();
33
+ return [
34
+ 'wyrm',
35
+ join(home, '.npm-global', 'bin', 'wyrm'),
36
+ join(home, '.local', 'bin', 'wyrm'),
37
+ join(home, 'node_modules', '.bin', 'wyrm'),
38
+ '/usr/local/bin/wyrm',
39
+ '/opt/homebrew/bin/wyrm',
40
+ ].filter((c) => c === 'wyrm' || existsSync(c));
41
+ }
42
+ function runWyrm(args, opts) {
43
+ for (const bin of wyrmCandidates()) {
44
+ const r = spawnSync(bin, args, opts);
45
+ if (!(r.error && r.error.code === 'ENOENT')) return r; // spawned (even if non-zero exit)
46
+ }
47
+ return { status: 127, stdout: '', error: new Error('wyrm not found') };
48
+ }
49
+
25
50
  let payload = {};
26
51
  try { payload = JSON.parse(readFileSync(0, 'utf8') || '{}'); } catch { /* no/invalid stdin */ }
27
52
 
@@ -34,7 +59,7 @@ try {
34
59
  // Scope by EXACT project path (--path), not a directory-name match, so we
35
60
  // never touch another project's memories. If this directory maps to no
36
61
  // project, prune exits without deleting anything (fine).
37
- spawnSync('wyrm', [
62
+ runWyrm([
38
63
  'prune', '--no-dry-run', '--yes',
39
64
  '--path', cwd,
40
65
  '--min-confidence', minConf,
@@ -16,8 +16,37 @@
16
16
  *
17
17
  * @copyright 2026 Ghost Protocol (Pvt) Ltd.
18
18
  */
19
- import { readFileSync } from 'node:fs';
19
+ import { readFileSync, existsSync } from 'node:fs';
20
20
  import { spawnSync } from 'node:child_process';
21
+ import { homedir } from 'node:os';
22
+ import { join } from 'node:path';
23
+
24
+ // ── Portable, self-healing `wyrm` resolution ────────────────────────────────
25
+ // Claude Code can spawn hooks with a minimal PATH (containers, CI, cron, non-
26
+ // login shells), which would silently disable memory. Try the bare command
27
+ // first (zero overhead where PATH is fine); only on ENOENT fall back to well-
28
+ // known npm/user CLI-install locations that actually exist on THIS machine.
29
+ // Deliberately NOT /usr/bin: a distro or global scoped-package can put an
30
+ // unrelated `wyrm` there (e.g. the http-server entrypoint), and resolving to
31
+ // the wrong binary is worse than not resolving at all.
32
+ function wyrmCandidates() {
33
+ const home = homedir();
34
+ return [
35
+ 'wyrm',
36
+ join(home, '.npm-global', 'bin', 'wyrm'),
37
+ join(home, '.local', 'bin', 'wyrm'),
38
+ join(home, 'node_modules', '.bin', 'wyrm'),
39
+ '/usr/local/bin/wyrm',
40
+ '/opt/homebrew/bin/wyrm',
41
+ ].filter((c) => c === 'wyrm' || existsSync(c));
42
+ }
43
+ function runWyrm(args, opts) {
44
+ for (const bin of wyrmCandidates()) {
45
+ const r = spawnSync(bin, args, opts);
46
+ if (!(r.error && r.error.code === 'ENOENT')) return r; // spawned (even if non-zero exit)
47
+ }
48
+ return { status: 127, stdout: '', error: new Error('wyrm not found') };
49
+ }
21
50
 
22
51
  let payload = {};
23
52
  try { payload = JSON.parse(readFileSync(0, 'utf8') || '{}'); } catch { /* no/invalid stdin */ }
@@ -36,7 +65,7 @@ const cwd = (typeof payload.cwd === 'string' && payload.cwd ? payload.cwd : proc
36
65
  try {
37
66
  if (payload.session_id) {
38
67
  const agentId = `claude-${String(payload.session_id).slice(0, 8)}`;
39
- spawnSync('wyrm', [
68
+ runWyrm([
40
69
  'presence', 'announce',
41
70
  '--agent', agentId,
42
71
  '--kind', 'claude',
@@ -50,7 +79,7 @@ let brief = '';
50
79
  try {
51
80
  // --quiet keeps the "nothing to restore" note off stderr; --path scopes to
52
81
  // this directory's project; --max-chars bounds the injection.
53
- const res = spawnSync('wyrm', ['rehydrate', '--path', cwd, '--quiet', '--max-chars', '6000'], {
82
+ const res = runWyrm(['rehydrate', '--path', cwd, '--quiet', '--max-chars', '6000'], {
54
83
  timeout: 15000,
55
84
  encoding: 'utf8',
56
85
  });