wyrm-mcp 5.2.2 → 5.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Version check — quietly polls the npm registry once per day to see if a
3
+ * newer wyrm-mcp is available, caches the result in the Wyrm database, and
4
+ * exposes it to both the MCP startup banner and the AI (via the
5
+ * `wyrm_check_update` tool).
6
+ *
7
+ * Design notes:
8
+ * - Cache TTL: 24 h. We don't want to ping registry every MCP server start
9
+ * (which can be per-session-restart for some clients).
10
+ * - Cache lives in a single-row `update_check` table so it survives restarts.
11
+ * - Network errors are non-fatal — we just log debug and keep going.
12
+ * - `fetch` is the Node 22+ global; no extra deps.
13
+ *
14
+ * @copyright 2026 Ghost Protocol (Pvt) Ltd. All Rights Reserved.
15
+ * @license Proprietary
16
+ */
17
+ import type Database from 'better-sqlite3';
18
+ export interface UpdateStatus {
19
+ current: string;
20
+ latest: string | null;
21
+ updateAvailable: boolean;
22
+ checkedAt: string;
23
+ source: 'cache' | 'live' | 'offline';
24
+ }
25
+ /**
26
+ * Returns the current update status. By default uses the cache when fresh.
27
+ * Pass `force: true` to bypass the cache (used by `wyrm_check_update`).
28
+ */
29
+ export declare function getUpdateStatus(db: Database.Database, current: string, opts?: {
30
+ force?: boolean;
31
+ }): Promise<UpdateStatus>;
32
+ /**
33
+ * Startup banner — log a single line to stderr if a newer version is out.
34
+ * Stderr (not stdout) so it never pollutes the MCP stdio protocol.
35
+ */
36
+ export declare function emitStartupVersionBanner(db: Database.Database, current: string): Promise<void>;
37
+ //# sourceMappingURL=version-check.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version-check.d.ts","sourceRoot":"","sources":["../src/version-check.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAO3C,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,eAAe,EAAE,OAAO,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAAC;CACtC;AAwED;;;GAGG;AACH,wBAAsB,eAAe,CACnC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,OAAO,EAAE,MAAM,EACf,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAO,GAC7B,OAAO,CAAC,YAAY,CAAC,CAgCvB;AAED;;;GAGG;AACH,wBAAsB,wBAAwB,CAC5C,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC,CAWf"}
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Version check — quietly polls the npm registry once per day to see if a
3
+ * newer wyrm-mcp is available, caches the result in the Wyrm database, and
4
+ * exposes it to both the MCP startup banner and the AI (via the
5
+ * `wyrm_check_update` tool).
6
+ *
7
+ * Design notes:
8
+ * - Cache TTL: 24 h. We don't want to ping registry every MCP server start
9
+ * (which can be per-session-restart for some clients).
10
+ * - Cache lives in a single-row `update_check` table so it survives restarts.
11
+ * - Network errors are non-fatal — we just log debug and keep going.
12
+ * - `fetch` is the Node 22+ global; no extra deps.
13
+ *
14
+ * @copyright 2026 Ghost Protocol (Pvt) Ltd. All Rights Reserved.
15
+ * @license Proprietary
16
+ */
17
+ import { logger } from './logger.js';
18
+ const REGISTRY_URL = 'https://registry.npmjs.org/wyrm-mcp/latest';
19
+ const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
20
+ const FETCH_TIMEOUT_MS = 10_000; // generous for mobile / slow networks; startup is async anyway
21
+ function ensureSchema(db) {
22
+ db.exec(`
23
+ CREATE TABLE IF NOT EXISTS update_check (
24
+ id INTEGER PRIMARY KEY CHECK (id = 1),
25
+ checked_at TEXT NOT NULL,
26
+ latest_version TEXT,
27
+ check_succeeded INTEGER NOT NULL DEFAULT 0
28
+ );
29
+ `);
30
+ }
31
+ function readCache(db) {
32
+ ensureSchema(db);
33
+ const row = db
34
+ .prepare('SELECT checked_at, latest_version, check_succeeded FROM update_check WHERE id = 1')
35
+ .get();
36
+ if (!row)
37
+ return null;
38
+ return {
39
+ checkedAt: new Date(row.checked_at).getTime(),
40
+ latest: row.latest_version,
41
+ ok: row.check_succeeded === 1,
42
+ };
43
+ }
44
+ function writeCache(db, latest, ok) {
45
+ ensureSchema(db);
46
+ db.prepare(`
47
+ INSERT INTO update_check (id, checked_at, latest_version, check_succeeded)
48
+ VALUES (1, ?, ?, ?)
49
+ ON CONFLICT(id) DO UPDATE SET
50
+ checked_at = excluded.checked_at,
51
+ latest_version = excluded.latest_version,
52
+ check_succeeded = excluded.check_succeeded
53
+ `).run(new Date().toISOString(), latest, ok ? 1 : 0);
54
+ }
55
+ /**
56
+ * Semver comparison — only handles the standard X.Y.Z (no prerelease tags).
57
+ * Returns: -1 if a<b, 0 if a==b, +1 if a>b. Anything unparseable returns 0.
58
+ */
59
+ function semverCompare(a, b) {
60
+ const parse = (s) => s.split('.').map((p) => parseInt(p, 10));
61
+ const [a1, a2, a3] = parse(a);
62
+ const [b1, b2, b3] = parse(b);
63
+ if ([a1, a2, a3, b1, b2, b3].some((n) => Number.isNaN(n)))
64
+ return 0;
65
+ if (a1 !== b1)
66
+ return a1 < b1 ? -1 : 1;
67
+ if (a2 !== b2)
68
+ return a2 < b2 ? -1 : 1;
69
+ if (a3 !== b3)
70
+ return a3 < b3 ? -1 : 1;
71
+ return 0;
72
+ }
73
+ async function fetchLatestFromRegistry() {
74
+ if (typeof fetch !== 'function')
75
+ return null; // very old Node
76
+ const controller = new AbortController();
77
+ const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
78
+ try {
79
+ const res = await fetch(REGISTRY_URL, {
80
+ headers: { Accept: 'application/json' },
81
+ signal: controller.signal,
82
+ });
83
+ if (!res.ok)
84
+ return null;
85
+ const data = (await res.json());
86
+ return typeof data.version === 'string' ? data.version : null;
87
+ }
88
+ catch {
89
+ return null;
90
+ }
91
+ finally {
92
+ clearTimeout(timer);
93
+ }
94
+ }
95
+ /**
96
+ * Returns the current update status. By default uses the cache when fresh.
97
+ * Pass `force: true` to bypass the cache (used by `wyrm_check_update`).
98
+ */
99
+ export async function getUpdateStatus(db, current, opts = {}) {
100
+ ensureSchema(db);
101
+ const force = opts.force === true;
102
+ // Use cache if fresh and not forced.
103
+ if (!force) {
104
+ const cached = readCache(db);
105
+ if (cached && Date.now() - cached.checkedAt < CACHE_TTL_MS) {
106
+ const latest = cached.latest;
107
+ return {
108
+ current,
109
+ latest,
110
+ updateAvailable: latest != null && semverCompare(current, latest) < 0,
111
+ checkedAt: new Date(cached.checkedAt).toISOString(),
112
+ source: 'cache',
113
+ };
114
+ }
115
+ }
116
+ const latest = await fetchLatestFromRegistry();
117
+ const ok = latest !== null;
118
+ writeCache(db, latest, ok);
119
+ if (!ok) {
120
+ logger.debug('version-check: registry fetch failed (offline or rate-limited)');
121
+ }
122
+ return {
123
+ current,
124
+ latest,
125
+ updateAvailable: latest != null && semverCompare(current, latest) < 0,
126
+ checkedAt: new Date().toISOString(),
127
+ source: ok ? 'live' : 'offline',
128
+ };
129
+ }
130
+ /**
131
+ * Startup banner — log a single line to stderr if a newer version is out.
132
+ * Stderr (not stdout) so it never pollutes the MCP stdio protocol.
133
+ */
134
+ export async function emitStartupVersionBanner(db, current) {
135
+ try {
136
+ const status = await getUpdateStatus(db, current);
137
+ if (status.updateAvailable) {
138
+ process.stderr.write(`🐉 wyrm-mcp ${status.latest} is available (you have ${current}). Run "wyrm_self_update" from your AI client, or "npm install -g wyrm-mcp@latest" manually.\n`);
139
+ }
140
+ }
141
+ catch {
142
+ // Never crash startup on a version check.
143
+ }
144
+ }
145
+ //# sourceMappingURL=version-check.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version-check.js","sourceRoot":"","sources":["../src/version-check.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wyrm-mcp",
3
- "version": "5.2.2",
3
+ "version": "5.3.0",
4
4
  "description": "🐉 Wyrm - Persistent AI Memory System with encryption, full-text search, and infinite storage",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -14,11 +14,13 @@
14
14
  "files": [
15
15
  "dist",
16
16
  "scripts/preinstall.cjs",
17
+ "scripts/postinstall.cjs",
17
18
  "README.md",
18
19
  "LICENSE"
19
20
  ],
20
21
  "scripts": {
21
22
  "preinstall": "node scripts/preinstall.cjs",
23
+ "postinstall": "node scripts/postinstall.cjs",
22
24
  "build": "tsc && chmod +x dist/*.js",
23
25
  "start": "node dist/index.js",
24
26
  "http": "node dist/http-server.js",
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Postinstall pitch.
4
+ *
5
+ * Prints what Wyrm uniquely offers — once per install lifetime, then a short
6
+ * "upgraded to X" line on each subsequent install. A marker file under
7
+ * `~/.wyrm/.first-install-shown` tracks whether the long pitch has been
8
+ * displayed.
9
+ *
10
+ * Set WYRM_SKIP_POSTINSTALL=1 to silence entirely.
11
+ */
12
+
13
+ 'use strict';
14
+
15
+ const fs = require('fs');
16
+ const path = require('path');
17
+ const os = require('os');
18
+
19
+ if (process.env.WYRM_SKIP_POSTINSTALL) process.exit(0);
20
+
21
+ const home = os.homedir();
22
+ const wyrmDir = path.join(home, '.wyrm');
23
+ const marker = path.join(wyrmDir, '.first-install-shown');
24
+
25
+ let version = 'unknown';
26
+ try {
27
+ version = JSON.parse(
28
+ fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8'),
29
+ ).version;
30
+ } catch { /* fall through */ }
31
+
32
+ function shortBanner() {
33
+ process.stdout.write(`\n🐉 wyrm-mcp upgraded to ${version}. Run \`wyrm_check_update\` from your AI any time.\n\n`);
34
+ }
35
+
36
+ function firstInstallPitch() {
37
+ const lines = [
38
+ '',
39
+ '🐉 ─────────────────────────────────────────────────────────────────',
40
+ ` Wyrm ${version} · Persistent AI Memory System · Ghost Protocol`,
41
+ '─────────────────────────────────────────────────────────────────────',
42
+ '',
43
+ 'You just installed something that is NOT just AI memory.',
44
+ '',
45
+ 'What Wyrm gives you that other memory tools don\'t:',
46
+ '',
47
+ ' ⛔ Counter-pattern memory — failed approaches are recorded and',
48
+ ' BLOCKED next time. Other tools learn only from success.',
49
+ '',
50
+ ' 🧱 Ground truths — validated project facts, versioned, with',
51
+ ' cascade-invalidation when an upstream truth changes.',
52
+ '',
53
+ ' 🧭 Reasoning scaffolds — saved checklists that surface',
54
+ ' automatically when a task description matches.',
55
+ '',
56
+ ' 🧬 Lossless session rehydration — wyrm_session_rehydrate produces',
57
+ ' a complete briefing a fresh AI ingests to inherit prior state.',
58
+ '',
59
+ ' 🕸️ Knowledge graph — entity / relationship graph with',
60
+ ' neighborhood traversal and shortest-path queries.',
61
+ '',
62
+ ' 🎯 OODA agent loop — set persistent goals, the wyrm-loop daemon',
63
+ ' runs Observe→Orient→Decide→Act until success criteria are met.',
64
+ '',
65
+ ' 🛰️ Outbound MCP client — Wyrm calls OTHER MCP servers (GitHub,',
66
+ ' Slack, Linear) as tools. Composes your whole stack.',
67
+ '',
68
+ ' 🔬 Hybrid semantic search — FTS5 + vector candidates via',
69
+ ' Reciprocal Rank Fusion. Auto-detects Ollama; OpenAI optional.',
70
+ '',
71
+ ' 👥 Multi-agent presence + work-stealing across Claude/Copilot/Cursor.',
72
+ '',
73
+ ' 🌐 Federated team sync — per-row is_shared flag, conflict-as-quest',
74
+ ' semantics. PII never leaves the box unless explicitly shared.',
75
+ '',
76
+ ' 🛡️ Hash-chained audit log (Ed25519 signable) for SOC2/HIPAA.',
77
+ '',
78
+ ' 💰 Hour ledger + invoice generation from session content.',
79
+ '',
80
+ 'Next steps:',
81
+ '',
82
+ ' 1. Connect Wyrm to your AI client: wyrm-setup',
83
+ ' 2. (Optional) Enable semantic search: ollama pull nomic-embed-text',
84
+ ' 3. From your AI, call wyrm_capabilities to see all 114 tools.',
85
+ '',
86
+ 'Docs: https://github.com/ghosts-lk/Wyrm',
87
+ 'Troubleshoot: https://github.com/ghosts-lk/Wyrm/blob/main/TROUBLESHOOTING.md',
88
+ '',
89
+ 'Silence this banner on future installs: WYRM_SKIP_POSTINSTALL=1',
90
+ '─────────────────────────────────────────────────────────────────────',
91
+ '',
92
+ ];
93
+ process.stdout.write(lines.join('\n'));
94
+ }
95
+
96
+ try {
97
+ if (!fs.existsSync(wyrmDir)) fs.mkdirSync(wyrmDir, { recursive: true });
98
+
99
+ if (fs.existsSync(marker)) {
100
+ shortBanner();
101
+ } else {
102
+ firstInstallPitch();
103
+ fs.writeFileSync(marker, `${new Date().toISOString()}\nversion: ${version}\n`);
104
+ }
105
+ } catch {
106
+ // Never fail an install on a banner.
107
+ }