wyrm-mcp 7.3.2 → 7.3.3
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.
- package/README.md +8 -14
- package/dist/activation.js +59 -1
- package/dist/agent-daemon.js +281 -4
- package/dist/agent-loop.js +332 -7
- package/dist/analytics.js +236 -13
- package/dist/attribution.js +49 -1
- package/dist/audit.js +457 -2
- package/dist/auto-capture.js +138 -3
- package/dist/auto-orchestrator.js +325 -1
- package/dist/autoconfig.d.ts +50 -0
- package/dist/autoconfig.d.ts.map +1 -1
- package/dist/autoconfig.js +1115 -39
- package/dist/autoconfig.js.map +1 -1
- package/dist/buddy-runner.js +109 -1
- package/dist/buddy.js +564 -14
- package/dist/build-flags.js +15 -1
- package/dist/capabilities.js +183 -3
- package/dist/capture.js +56 -1
- package/dist/causality.js +148 -8
- package/dist/cli.js +281 -20
- package/dist/cloud/cli.js +541 -5
- package/dist/cloud/client.js +221 -1
- package/dist/cloud/crypto.js +85 -1
- package/dist/cloud/machine-id.js +113 -2
- package/dist/cloud/recovery.js +60 -1
- package/dist/cloud/sync-engine.js +543 -7
- package/dist/cloud-backup.js +579 -5
- package/dist/cloud-profile.js +138 -1
- package/dist/cloud-sync-entrypoint.js +47 -1
- package/dist/cloud-sync.js +309 -2
- package/dist/connectors/bridge-source.d.ts +46 -0
- package/dist/connectors/bridge-source.d.ts.map +1 -0
- package/dist/connectors/bridge-source.js +77 -0
- package/dist/connectors/bridge-source.js.map +1 -0
- package/dist/connectors/index.d.ts +24 -0
- package/dist/connectors/index.d.ts.map +1 -0
- package/dist/connectors/index.js +69 -0
- package/dist/connectors/index.js.map +1 -0
- package/dist/connectors/ingest.d.ts +16 -0
- package/dist/connectors/ingest.d.ts.map +1 -0
- package/dist/connectors/ingest.js +116 -0
- package/dist/connectors/ingest.js.map +1 -0
- package/dist/connectors/types.d.ts +99 -0
- package/dist/connectors/types.d.ts.map +1 -0
- package/dist/connectors/types.js +17 -0
- package/dist/connectors/types.js.map +1 -0
- package/dist/constellation.js +168 -12
- package/dist/content-signature.js +45 -1
- package/dist/context-build-budgeted.js +144 -4
- package/dist/context-ranking.js +69 -1
- package/dist/crypto.js +179 -1
- package/dist/daemon-write-endpoint.js +290 -1
- package/dist/daemon-writer.js +406 -2
- package/dist/database.js +1278 -53
- package/dist/deprecations.js +162 -2
- package/dist/design.js +141 -13
- package/dist/event-replication.js +112 -1
- package/dist/events-sse.js +43 -7
- package/dist/events.js +238 -6
- package/dist/failure-patterns.d.ts +107 -0
- package/dist/failure-patterns.d.ts.map +1 -1
- package/dist/failure-patterns.js +924 -43
- package/dist/failure-patterns.js.map +1 -1
- package/dist/federation.js +236 -12
- package/dist/goals.js +101 -13
- package/dist/golden.js +355 -3
- package/dist/handlers/agent.js +165 -4
- package/dist/handlers/alias-adapters.js +129 -1
- package/dist/handlers/aliases.js +171 -1
- package/dist/handlers/audit.js +87 -1
- package/dist/handlers/boundary.js +221 -1
- package/dist/handlers/capture.js +1114 -73
- package/dist/handlers/causality.js +119 -9
- package/dist/handlers/cloud.js +382 -85
- package/dist/handlers/companion.js +459 -28
- package/dist/handlers/datalake.js +187 -7
- package/dist/handlers/dispatch-context.js +22 -0
- package/dist/handlers/entity.js +256 -25
- package/dist/handlers/events.js +335 -16
- package/dist/handlers/failure.d.ts.map +1 -1
- package/dist/handlers/failure.js +408 -13
- package/dist/handlers/failure.js.map +1 -1
- package/dist/handlers/goals.js +296 -4
- package/dist/handlers/intelligence.js +681 -126
- package/dist/handlers/invoicing.js +70 -1
- package/dist/handlers/mcpclient.js +137 -6
- package/dist/handlers/orchestration.js +125 -40
- package/dist/handlers/output-schemas.js +24 -1
- package/dist/handlers/presence.js +99 -3
- package/dist/handlers/project.js +182 -28
- package/dist/handlers/prompts.js +157 -6
- package/dist/handlers/quest.js +224 -4
- package/dist/handlers/recall.js +237 -13
- package/dist/handlers/registry.js +167 -1
- package/dist/handlers/resources.js +288 -1
- package/dist/handlers/review.js +74 -11
- package/dist/handlers/run.js +498 -16
- package/dist/handlers/search.js +338 -15
- package/dist/handlers/session.js +643 -31
- package/dist/handlers/share.js +184 -8
- package/dist/handlers/shims.js +464 -1
- package/dist/handlers/skill.js +449 -67
- package/dist/handlers/survivors.js +120 -1
- package/dist/handlers/symbols.js +109 -8
- package/dist/handlers/syncops.js +302 -4
- package/dist/handlers/types.js +27 -1
- package/dist/harvest.js +191 -5
- package/dist/hours.js +156 -7
- package/dist/http-auth.js +321 -3
- package/dist/http-fast.js +1302 -22
- package/dist/icons.js +47 -1
- package/dist/importers.js +268 -1
- package/dist/index.js +840 -2
- package/dist/indexer.js +145 -4
- package/dist/intelligence.js +261 -31
- package/dist/internal-dispatch.js +212 -3
- package/dist/keyset.js +110 -1
- package/dist/knowledge-graph.js +176 -12
- package/dist/license.js +441 -2
- package/dist/logger.js +199 -2
- package/dist/maintenance.js +148 -2
- package/dist/mcp-client.js +262 -6
- package/dist/memory-artifacts.js +596 -32
- package/dist/migrate-prompt.js +124 -2
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +799 -42
- package/dist/migrations.js.map +1 -1
- package/dist/performance.js +228 -1
- package/dist/presence.js +140 -11
- package/dist/priority-embed.js +164 -5
- package/dist/providers/embedding-provider.js +196 -1
- package/dist/readonly-gate.js +29 -1
- package/dist/receipt.js +43 -1
- package/dist/rehydration.js +157 -9
- package/dist/reindex.js +88 -1
- package/dist/render-target.js +544 -21
- package/dist/render.js +280 -4
- package/dist/repl-guard.js +173 -1
- package/dist/replication-daemon-entrypoint.js +31 -1
- package/dist/replication-daemon.js +262 -2
- package/dist/rerank.js +142 -1
- package/dist/resilience.js +591 -1
- package/dist/reverse-bridge.js +360 -5
- package/dist/security.js +244 -1
- package/dist/session-seen.js +51 -3
- package/dist/setup.js +260 -1
- package/dist/skill-author.js +168 -5
- package/dist/spec-kit.js +191 -1
- package/dist/sqlite-busy.js +154 -1
- package/dist/statusline.js +315 -11
- package/dist/sub-agent.js +262 -13
- package/dist/summarizer.js +139 -13
- package/dist/symbols.js +283 -7
- package/dist/sync.js +359 -5
- package/dist/tasks-dispatch.js +84 -1
- package/dist/tasks.js +282 -1
- package/dist/token-budget.js +143 -1
- package/dist/tool-analytics.js +129 -7
- package/dist/tool-annotations.js +365 -1
- package/dist/tool-manifest-v2.json +1 -1
- package/dist/tool-manifest.json +1 -1
- package/dist/tool-profiles.js +75 -1
- package/dist/trace-harvest.js +244 -6
- package/dist/types.js +30 -1
- package/dist/ui-dashboard.js +50 -41
- package/dist/ulid.js +81 -1
- package/dist/usage-tracker.js +66 -1
- package/dist/validate.js +129 -1
- package/dist/vault.js +534 -1
- package/dist/vector-init.js +67 -1
- package/dist/vectors.js +184 -3
- package/dist/version-check.js +136 -4
- package/dist/visibility.js +155 -19
- package/dist/wyrm-cli.js +2845 -101
- package/dist/wyrm-cli.js.map +1 -1
- package/dist/wyrm-guard.d.ts.map +1 -1
- package/dist/wyrm-guard.js +475 -14
- package/dist/wyrm-guard.js.map +1 -1
- package/dist/wyrm-loop.js +150 -3
- package/dist/wyrm-manifest.json +1 -1
- package/dist/wyrm-statusline-daemon.js +11 -1
- package/dist/wyrm-statusline.js +56 -4
- package/dist/wyrm-ui.js +77 -9
- package/package.json +1 -1
package/dist/vectors.js
CHANGED
|
@@ -1,4 +1,34 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Wyrm Vector Embeddings — Semantic Search Support
|
|
3
|
+
*
|
|
4
|
+
* @copyright 2026 Ghost Protocol (Pvt) Ltd.
|
|
5
|
+
* @license Proprietary — (c) 2026 Ghost Protocol (Pvt) Ltd. All rights reserved. See LICENSE.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Provider-agnostic embeddings (local, OpenAI, Ollama)
|
|
9
|
+
* - SQLite-backed vector storage (no external DB needed)
|
|
10
|
+
* - Cosine similarity search with content-hash deduplication
|
|
11
|
+
* - Graceful degradation when no provider is configured
|
|
12
|
+
*/
|
|
13
|
+
import { createProvider, NoneProvider, } from './providers/embedding-provider.js';
|
|
14
|
+
/**
|
|
15
|
+
* SQLite-backed vector store with pluggable embedding providers.
|
|
16
|
+
*/
|
|
17
|
+
export class VectorStore {
|
|
18
|
+
provider;
|
|
19
|
+
db;
|
|
20
|
+
constructor(provider, db) {
|
|
21
|
+
this.provider = provider;
|
|
22
|
+
this.db = db || null;
|
|
23
|
+
if (this.db) {
|
|
24
|
+
this.ensureTable();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/** Create the vectors table if it doesn't exist */
|
|
28
|
+
ensureTable() {
|
|
29
|
+
if (!this.db)
|
|
30
|
+
return;
|
|
31
|
+
this.db.exec(`
|
|
2
32
|
CREATE TABLE IF NOT EXISTS vectors (
|
|
3
33
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
4
34
|
project_id INTEGER,
|
|
@@ -14,11 +44,162 @@ import{createProvider as u,NoneProvider as l}from"./providers/embedding-provider
|
|
|
14
44
|
CREATE INDEX IF NOT EXISTS idx_vectors_project ON vectors(project_id);
|
|
15
45
|
CREATE INDEX IF NOT EXISTS idx_vectors_content ON vectors(content_type, content_id);
|
|
16
46
|
CREATE INDEX IF NOT EXISTS idx_vectors_hash ON vectors(content_hash);
|
|
17
|
-
`)
|
|
47
|
+
`);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Hash content for deduplication — same hash means same content, skip re-embedding.
|
|
51
|
+
*/
|
|
52
|
+
hashContent(content) {
|
|
53
|
+
let hash = 0;
|
|
54
|
+
for (let i = 0; i < content.length; i++) {
|
|
55
|
+
const char = content.charCodeAt(i);
|
|
56
|
+
hash = ((hash << 5) - hash) + char;
|
|
57
|
+
hash = hash & hash;
|
|
58
|
+
}
|
|
59
|
+
return Math.abs(hash).toString(16);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Add or update a vector entry.
|
|
63
|
+
* Returns the content hash, or null if the provider is disabled.
|
|
64
|
+
*/
|
|
65
|
+
async addVector(content, contentType, contentId, projectId) {
|
|
66
|
+
if (this.provider instanceof NoneProvider)
|
|
67
|
+
return null;
|
|
68
|
+
const contentHash = this.hashContent(content);
|
|
69
|
+
// Skip if content unchanged (same hash + same model already stored)
|
|
70
|
+
if (this.db) {
|
|
71
|
+
const existing = this.db.prepare('SELECT id FROM vectors WHERE content_type = ? AND content_id = ? AND model = ? AND content_hash = ?').get(contentType, contentId, this.provider.model, contentHash);
|
|
72
|
+
if (existing)
|
|
73
|
+
return contentHash;
|
|
74
|
+
}
|
|
75
|
+
let embedding;
|
|
76
|
+
try {
|
|
77
|
+
embedding = await this.provider.embed(content);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
if (embedding.length === 0)
|
|
83
|
+
return null;
|
|
84
|
+
if (this.db) {
|
|
85
|
+
// Upsert: replace if same content_type + content_id + model
|
|
86
|
+
this.db.prepare(`
|
|
18
87
|
INSERT INTO vectors (project_id, content_type, content_id, content_hash, embedding, model, dimensions)
|
|
19
88
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
20
89
|
ON CONFLICT(content_type, content_id, model) DO UPDATE SET
|
|
21
90
|
content_hash = excluded.content_hash,
|
|
22
91
|
embedding = excluded.embedding,
|
|
23
92
|
created_at = datetime('now')
|
|
24
|
-
`).run(
|
|
93
|
+
`).run(projectId, contentType, contentId, contentHash, Buffer.from(embedding.buffer), this.provider.model, this.provider.dimensions);
|
|
94
|
+
}
|
|
95
|
+
return contentHash;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Cosine similarity between two vectors.
|
|
99
|
+
*/
|
|
100
|
+
cosineSimilarity(a, b) {
|
|
101
|
+
if (a.length !== b.length)
|
|
102
|
+
return 0;
|
|
103
|
+
let dotProduct = 0;
|
|
104
|
+
let magnitudeA = 0;
|
|
105
|
+
let magnitudeB = 0;
|
|
106
|
+
for (let i = 0; i < a.length; i++) {
|
|
107
|
+
dotProduct += a[i] * b[i];
|
|
108
|
+
magnitudeA += a[i] * a[i];
|
|
109
|
+
magnitudeB += b[i] * b[i];
|
|
110
|
+
}
|
|
111
|
+
const magnitude = Math.sqrt(magnitudeA) * Math.sqrt(magnitudeB);
|
|
112
|
+
return magnitude > 0 ? dotProduct / magnitude : 0;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Search for similar content by embedding the query and comparing.
|
|
116
|
+
*/
|
|
117
|
+
async search(query, limit = 10, projectId, contentTypes) {
|
|
118
|
+
if (this.provider instanceof NoneProvider || !this.db)
|
|
119
|
+
return [];
|
|
120
|
+
let queryEmbedding;
|
|
121
|
+
try {
|
|
122
|
+
queryEmbedding = await this.provider.embed(query);
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return [];
|
|
126
|
+
}
|
|
127
|
+
if (queryEmbedding.length === 0)
|
|
128
|
+
return [];
|
|
129
|
+
// Load all matching vectors from SQLite
|
|
130
|
+
let sql = 'SELECT * FROM vectors WHERE model = ?';
|
|
131
|
+
const params = [this.provider.model];
|
|
132
|
+
if (projectId !== undefined) {
|
|
133
|
+
sql += ' AND project_id = ?';
|
|
134
|
+
params.push(projectId);
|
|
135
|
+
}
|
|
136
|
+
if (contentTypes && contentTypes.length > 0) {
|
|
137
|
+
sql += ` AND content_type IN (${contentTypes.map(() => '?').join(',')})`;
|
|
138
|
+
params.push(...contentTypes);
|
|
139
|
+
}
|
|
140
|
+
const rows = this.db.prepare(sql).all(...params);
|
|
141
|
+
const results = [];
|
|
142
|
+
for (const row of rows) {
|
|
143
|
+
const storedEmbedding = new Float32Array(row.embedding.buffer, row.embedding.byteOffset, row.dimensions);
|
|
144
|
+
const similarity = this.cosineSimilarity(queryEmbedding, storedEmbedding);
|
|
145
|
+
results.push({
|
|
146
|
+
content_type: row.content_type,
|
|
147
|
+
content_id: row.content_id,
|
|
148
|
+
project_id: row.project_id,
|
|
149
|
+
similarity,
|
|
150
|
+
content_hash: row.content_hash,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
results.sort((a, b) => b.similarity - a.similarity);
|
|
154
|
+
return results.slice(0, limit);
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Remove vector by content hash.
|
|
158
|
+
*/
|
|
159
|
+
removeVector(contentHash) {
|
|
160
|
+
if (!this.db)
|
|
161
|
+
return false;
|
|
162
|
+
const result = this.db.prepare('DELETE FROM vectors WHERE content_hash = ?').run(contentHash);
|
|
163
|
+
return result.changes > 0;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Clear all vectors for a project.
|
|
167
|
+
*/
|
|
168
|
+
clearProjectVectors(projectId) {
|
|
169
|
+
if (!this.db)
|
|
170
|
+
return 0;
|
|
171
|
+
const result = this.db.prepare('DELETE FROM vectors WHERE project_id = ?').run(projectId);
|
|
172
|
+
return result.changes;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Get stats about the vector store.
|
|
176
|
+
*/
|
|
177
|
+
getStats() {
|
|
178
|
+
const byType = {};
|
|
179
|
+
const byProject = {};
|
|
180
|
+
let total = 0;
|
|
181
|
+
if (this.db) {
|
|
182
|
+
const typeRows = this.db.prepare('SELECT content_type, COUNT(*) as cnt FROM vectors GROUP BY content_type').all();
|
|
183
|
+
for (const r of typeRows) {
|
|
184
|
+
byType[r.content_type] = r.cnt;
|
|
185
|
+
total += r.cnt;
|
|
186
|
+
}
|
|
187
|
+
const projRows = this.db.prepare('SELECT project_id, COUNT(*) as cnt FROM vectors GROUP BY project_id').all();
|
|
188
|
+
for (const r of projRows) {
|
|
189
|
+
byProject[r.project_id] = r.cnt;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return { total, byType, byProject, provider: this.provider.name, model: this.provider.model };
|
|
193
|
+
}
|
|
194
|
+
/** Get the active embedding provider */
|
|
195
|
+
getProvider() { return this.provider; }
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Create a vector store instance with config.
|
|
199
|
+
* Accepts an optional SQLite database for persistent storage.
|
|
200
|
+
*/
|
|
201
|
+
export function createVectorStore(config, db) {
|
|
202
|
+
const provider = createProvider(config);
|
|
203
|
+
return new VectorStore(provider, db);
|
|
204
|
+
}
|
|
205
|
+
//# sourceMappingURL=vectors.js.map
|
package/dist/version-check.js
CHANGED
|
@@ -1,16 +1,148 @@
|
|
|
1
|
-
|
|
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.
|
|
15
|
+
* @license Proprietary — (c) 2026 Ghost Protocol (Pvt) Ltd. All rights reserved. See LICENSE.
|
|
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(`
|
|
2
23
|
CREATE TABLE IF NOT EXISTS update_check (
|
|
3
24
|
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
4
25
|
checked_at TEXT NOT NULL,
|
|
5
26
|
latest_version TEXT,
|
|
6
27
|
check_succeeded INTEGER NOT NULL DEFAULT 0
|
|
7
28
|
);
|
|
8
|
-
`)
|
|
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(`
|
|
9
47
|
INSERT INTO update_check (id, checked_at, latest_version, check_succeeded)
|
|
10
48
|
VALUES (1, ?, ?, ?)
|
|
11
49
|
ON CONFLICT(id) DO UPDATE SET
|
|
12
50
|
checked_at = excluded.checked_at,
|
|
13
51
|
latest_version = excluded.latest_version,
|
|
14
52
|
check_succeeded = excluded.check_succeeded
|
|
15
|
-
`).run(new Date().toISOString(),
|
|
16
|
-
|
|
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
|
+
// T032 findings pass: T023 hard-exiled wyrm_self_update off MCP (its
|
|
139
|
+
// alias returns a WYRM_CLI_EXILE redirect, never updates) — the banner
|
|
140
|
+
// must name the real path, the `wyrm update` CLI replacement.
|
|
141
|
+
process.stderr.write(` wyrm-mcp ${status.latest} is available (you have ${current}). Run "wyrm update" from a shell, or "npm install -g wyrm-mcp@latest" manually.\n`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
// Never crash startup on a version check.
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=version-check.js.map
|
package/dist/visibility.js
CHANGED
|
@@ -1,29 +1,163 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Visibility tools (5.9.0).
|
|
3
|
+
*
|
|
4
|
+
* Wyrm's value tends to be invisible — users don't realise the AI is
|
|
5
|
+
* remembering things, blocking failed patterns, injecting truths, citing
|
|
6
|
+
* past sessions. This module surfaces what Wyrm is actually doing so the
|
|
7
|
+
* operator can see the work product, and AI helpers can attribute their
|
|
8
|
+
* answers to Wyrm sources rather than silently using them.
|
|
9
|
+
*
|
|
10
|
+
* Two tools:
|
|
11
|
+
* - wyrm_intro: plain-English explanation of what Wyrm is + can do.
|
|
12
|
+
* Aimed at humans new to Wyrm, not at AI agents.
|
|
13
|
+
* - wyrm_digest: "what Wyrm did for you this period" — counts and
|
|
14
|
+
* highlights from the operator's actual usage data.
|
|
15
|
+
*
|
|
16
|
+
* @copyright 2026 Ghost Protocol (Pvt) Ltd.
|
|
17
|
+
* @license Proprietary — (c) 2026 Ghost Protocol (Pvt) Ltd. All rights reserved. See LICENSE.
|
|
18
|
+
*/
|
|
19
|
+
import { ICON } from './icons.js';
|
|
20
|
+
export const DIGEST_PRESETS = {
|
|
21
|
+
today: { days: 1, label: 'today' },
|
|
22
|
+
week: { days: 7, label: 'this week' },
|
|
23
|
+
month: { days: 30, label: 'this month' },
|
|
24
|
+
quarter: { days: 90, label: 'this quarter' },
|
|
25
|
+
year: { days: 365, label: 'this year' },
|
|
26
|
+
all: { days: 36500, label: 'all time' },
|
|
27
|
+
};
|
|
28
|
+
export function gatherDigest(db, periodKey = 'week') {
|
|
29
|
+
// Use hasOwn() to defeat prototype-pollution probes (`__proto__`,
|
|
30
|
+
// `toString`, `constructor`, etc. would otherwise resolve to
|
|
31
|
+
// Object.prototype, bypass the `??` fallback, and produce `undefined`
|
|
32
|
+
// when we access `.days`.)
|
|
33
|
+
const period = Object.hasOwn(DIGEST_PRESETS, periodKey)
|
|
34
|
+
? DIGEST_PRESETS[periodKey]
|
|
35
|
+
: DIGEST_PRESETS.week;
|
|
36
|
+
const since = `datetime('now', '-${period.days} days')`;
|
|
37
|
+
const num = (sql) => {
|
|
38
|
+
try {
|
|
39
|
+
const row = db.prepare(sql).get();
|
|
40
|
+
return row?.n ?? 0;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return 0;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
return {
|
|
47
|
+
period,
|
|
48
|
+
toolCalls: num(`SELECT COUNT(*) AS n FROM tool_call_log WHERE called_at >= ${since}`),
|
|
49
|
+
topTools: (() => {
|
|
50
|
+
try {
|
|
51
|
+
const rows = db.prepare(`
|
|
2
52
|
SELECT tool_name AS name, COUNT(*) AS count
|
|
3
53
|
FROM tool_call_log
|
|
4
|
-
WHERE called_at >= ${
|
|
54
|
+
WHERE called_at >= ${since}
|
|
5
55
|
GROUP BY tool_name
|
|
6
56
|
ORDER BY count DESC
|
|
7
57
|
LIMIT 5
|
|
8
|
-
`).all()
|
|
58
|
+
`).all();
|
|
59
|
+
return rows;
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return [];
|
|
63
|
+
}
|
|
64
|
+
})(),
|
|
65
|
+
sessions: num(`SELECT COUNT(*) AS n FROM sessions WHERE date >= date('now', '-${period.days} days')`),
|
|
66
|
+
questsCompleted: num(`SELECT COUNT(*) AS n FROM quests WHERE status='completed' AND completed_at >= ${since}`),
|
|
67
|
+
questsAdded: num(`SELECT COUNT(*) AS n FROM quests WHERE created_at >= ${since}`),
|
|
68
|
+
failuresBlocked: num(`SELECT COALESCE(SUM(occurrences), 0) AS n FROM failure_patterns WHERE last_seen_at >= ${since}`),
|
|
69
|
+
truthsSet: num(`SELECT COUNT(*) AS n FROM ground_truths WHERE created_at >= ${since} OR last_verified_at >= ${since}`),
|
|
70
|
+
referencesAdded: num(`SELECT COUNT(*) AS n FROM design_references WHERE captured_at >= ${since}`),
|
|
71
|
+
memoryArtifactsAdded: num(`SELECT COUNT(*) AS n FROM memory_artifacts WHERE created_at >= ${since}`),
|
|
72
|
+
hoursLogged: (() => {
|
|
73
|
+
try {
|
|
74
|
+
const row = db.prepare(`SELECT COALESCE(SUM(hours), 0) AS n FROM hour_entries WHERE date >= date('now', '-${period.days} days')`).get();
|
|
75
|
+
return row.n;
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return 0;
|
|
79
|
+
}
|
|
80
|
+
})(),
|
|
81
|
+
projectsActive: (() => {
|
|
82
|
+
try {
|
|
83
|
+
return db.prepare(`
|
|
9
84
|
SELECT p.id, p.name
|
|
10
85
|
FROM projects p
|
|
11
86
|
JOIN sessions s ON s.project_id = p.id
|
|
12
|
-
WHERE s.date >= date('now', '-${
|
|
87
|
+
WHERE s.date >= date('now', '-${period.days} days')
|
|
13
88
|
GROUP BY p.id, p.name
|
|
14
89
|
ORDER BY MAX(s.date) DESC
|
|
15
90
|
LIMIT 10
|
|
16
|
-
`).all()
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
91
|
+
`).all();
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return [];
|
|
95
|
+
}
|
|
96
|
+
})(),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
export function renderDigest(data) {
|
|
100
|
+
const { period, toolCalls, topTools, sessions, questsCompleted, questsAdded, failuresBlocked, truthsSet, referencesAdded, memoryArtifactsAdded, hoursLogged, projectsActive } = data;
|
|
101
|
+
const lines = [];
|
|
102
|
+
lines.push(`# ${ICON.brand} What Wyrm did ${period.label}`);
|
|
103
|
+
lines.push('');
|
|
104
|
+
if (toolCalls === 0 && sessions === 0) {
|
|
105
|
+
lines.push('_Wyrm was idle — no recorded activity in this period._');
|
|
106
|
+
lines.push('');
|
|
107
|
+
lines.push('If your AI helper is connected to Wyrm but you see nothing here, it means it hasn\'t been calling Wyrm\'s tools. Try asking it: *"summarize my current project using wyrm_session_prime"* or *"what failures has Wyrm recorded on this project?"* — that\'ll get the loop going.');
|
|
108
|
+
return lines.join('\n');
|
|
109
|
+
}
|
|
110
|
+
lines.push('## The numbers');
|
|
111
|
+
lines.push('');
|
|
112
|
+
if (sessions > 0)
|
|
113
|
+
lines.push(`- ${ICON.session} **${sessions}** session${sessions === 1 ? '' : 's'} tracked`);
|
|
114
|
+
if (toolCalls > 0)
|
|
115
|
+
lines.push(`- ${ICON.toolCall} **${toolCalls}** Wyrm tool call${toolCalls === 1 ? '' : 's'} (your AI reached for Wyrm this many times)`);
|
|
116
|
+
if (questsCompleted > 0)
|
|
117
|
+
lines.push(`- ${ICON.questDone} **${questsCompleted}** quest${questsCompleted === 1 ? '' : 's'} completed`);
|
|
118
|
+
if (questsAdded > 0)
|
|
119
|
+
lines.push(`- ${ICON.questOpen} **${questsAdded}** quest${questsAdded === 1 ? '' : 's'} added`);
|
|
120
|
+
if (failuresBlocked > 0)
|
|
121
|
+
lines.push(`- ${ICON.blocked} **${failuresBlocked}** repeated-failure block${failuresBlocked === 1 ? '' : 's'} — Wyrm refused to suggest something that didn't work before`);
|
|
122
|
+
if (truthsSet > 0)
|
|
123
|
+
lines.push(`- ${ICON.truth} **${truthsSet}** ground truth${truthsSet === 1 ? '' : 's'} set or re-verified`);
|
|
124
|
+
if (memoryArtifactsAdded > 0)
|
|
125
|
+
lines.push(`- ${ICON.memory} **${memoryArtifactsAdded}** memory artifact${memoryArtifactsAdded === 1 ? '' : 's'} stored (patterns, lessons, anti-patterns)`);
|
|
126
|
+
if (referencesAdded > 0)
|
|
127
|
+
lines.push(`- ${ICON.reference} **${referencesAdded}** design reference${referencesAdded === 1 ? '' : 's'} clipped`);
|
|
128
|
+
if (hoursLogged > 0)
|
|
129
|
+
lines.push(`- ${ICON.bullet} **${hoursLogged.toFixed(1)}h** logged across sessions`);
|
|
130
|
+
if (topTools.length > 0) {
|
|
131
|
+
lines.push('');
|
|
132
|
+
lines.push('## Wyrm tools your AI reached for most');
|
|
133
|
+
lines.push('');
|
|
134
|
+
for (const t of topTools) {
|
|
135
|
+
lines.push(`- \`${t.name}\` — ${t.count}×`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (projectsActive.length > 0) {
|
|
139
|
+
lines.push('');
|
|
140
|
+
lines.push('## Projects Wyrm tracked');
|
|
141
|
+
lines.push('');
|
|
142
|
+
for (const p of projectsActive) {
|
|
143
|
+
lines.push(`- ${p.name}`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
lines.push('');
|
|
147
|
+
lines.push('---');
|
|
148
|
+
lines.push('_If this is the first time you\'re seeing what Wyrm does — that\'s the point. It works in the background. Every quest you completed, every failed approach your AI didn\'t repeat, every ground truth it cited — Wyrm made that happen. Without Wyrm your AI would have re-explored the same ground every session._');
|
|
149
|
+
return lines.join('\n');
|
|
150
|
+
}
|
|
151
|
+
export function renderIntro(version) {
|
|
152
|
+
return `# ${ICON.brand} What is Wyrm?
|
|
153
|
+
|
|
154
|
+
You're running **Wyrm ${version}** — a memory system that makes your AI assistant *much* better at working on your stuff.
|
|
21
155
|
|
|
22
156
|
## The simplest explanation
|
|
23
157
|
|
|
24
158
|
Without Wyrm: every time you start a new conversation with Claude / Copilot / Cursor / Windsurf, the AI starts from nothing. It doesn't remember what you worked on yesterday, what decisions you made, what didn't work last time.
|
|
25
159
|
|
|
26
|
-
With Wyrm: your AI **remembers**. Across every session, every tool, every project. You don't have to re-explain. The AI picks up where you left off
|
|
160
|
+
With Wyrm: your AI **remembers**. Across every session, every tool, every project. You don't have to re-explain. The AI picks up where you left off — *with all the context*.
|
|
27
161
|
|
|
28
162
|
## What Wyrm specifically does
|
|
29
163
|
|
|
@@ -31,25 +165,27 @@ It's not just "memory" in the file-write sense. Wyrm solves four specific proble
|
|
|
31
165
|
|
|
32
166
|
**1. Amnesia.** Wyrm assembles a complete briefing at every session start. Your AI inherits the prior state instead of starting from zero.
|
|
33
167
|
|
|
34
|
-
**2. Repeated mistakes.** When an approach fails, Wyrm records it. Next time, your AI is *blocked* from suggesting the same failed thing again. Other memory tools learn only from success
|
|
168
|
+
**2. Repeated mistakes.** When an approach fails, Wyrm records it. Next time, your AI is *blocked* from suggesting the same failed thing again. Other memory tools learn only from success — Wyrm learns from failure too.
|
|
35
169
|
|
|
36
170
|
**3. Lost rationale.** When you make a decision ("we use Postgres, not Mongo"), Wyrm stores it as a *ground truth* linked to the reasoning. When something downstream changes, Wyrm cascades the impact so you don't accidentally violate decisions you forgot you made.
|
|
37
171
|
|
|
38
|
-
**4. Tool-call sprawl.** The agent loop turns goals into autonomous tool sequences. Say *"keep my dependencies up to date weekly"*
|
|
172
|
+
**4. Tool-call sprawl.** The agent loop turns goals into autonomous tool sequences. Say *"keep my dependencies up to date weekly"* — Wyrm's daemon does it.
|
|
39
173
|
|
|
40
174
|
## What to try first
|
|
41
175
|
|
|
42
176
|
If you've never used Wyrm directly, ask your AI:
|
|
43
177
|
|
|
44
|
-
- *"What's the state of this project?"*
|
|
45
|
-
- *"What have we learned about [topic]?"*
|
|
46
|
-
- *"Save this as a ground truth: [fact]"*
|
|
47
|
-
- *"What did Wyrm do for me this week?"*
|
|
48
|
-
- *"Show me everything Wyrm can do"*
|
|
178
|
+
- *"What's the state of this project?"* — your AI will call \`wyrm_session_prime\` and brief you.
|
|
179
|
+
- *"What have we learned about [topic]?"* — your AI will search Wyrm's memory.
|
|
180
|
+
- *"Save this as a ground truth: [fact]"* — your AI will store it for future sessions.
|
|
181
|
+
- *"What did Wyrm do for me this week?"* — run \`wyrm_digest\` to see the work product.
|
|
182
|
+
- *"Show me everything Wyrm can do"* — call \`wyrm_capabilities\` to get the full tool catalogue.
|
|
49
183
|
|
|
50
184
|
## The 30-second pitch for skeptics
|
|
51
185
|
|
|
52
186
|
You know that feeling when your AI says *"Could you remind me what we decided about X?"* on session three of the same project? Wyrm exists so that never happens. It's the layer between you and AI amnesia.
|
|
53
187
|
|
|
54
|
-
If you've been using Claude Code / Cursor / Windsurf with Wyrm installed, **Wyrm has already been working**
|
|
55
|
-
|
|
188
|
+
If you've been using Claude Code / Cursor / Windsurf with Wyrm installed, **Wyrm has already been working** — silently, in the background, every session. \`wyrm_digest\` shows you the receipts.
|
|
189
|
+
`;
|
|
190
|
+
}
|
|
191
|
+
//# sourceMappingURL=visibility.js.map
|