@cohaku/mcp 0.1.0 → 0.2.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.
- package/dist/index.js +240 -66
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { join } from "path";
|
|
4
|
+
import { join, basename } from "path";
|
|
5
5
|
import { homedir } from "os";
|
|
6
|
+
import { execSync } from "child_process";
|
|
6
7
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
7
8
|
|
|
8
9
|
// ../core/dist/storage/sqlite-driver.js
|
|
@@ -25,11 +26,12 @@ var DEFAULT_DECAY_RATE = 0.01;
|
|
|
25
26
|
var DEFAULT_EMBEDDING_DIMENSIONS = 384;
|
|
26
27
|
|
|
27
28
|
// ../core/dist/storage/schema.js
|
|
28
|
-
var SCHEMA_VERSION =
|
|
29
|
+
var SCHEMA_VERSION = 2;
|
|
29
30
|
var CREATE_TABLES = `
|
|
30
31
|
-- Memory (3-layer)
|
|
31
32
|
CREATE TABLE IF NOT EXISTS memories (
|
|
32
33
|
id TEXT PRIMARY KEY,
|
|
34
|
+
project_id TEXT,
|
|
33
35
|
content TEXT NOT NULL,
|
|
34
36
|
layer TEXT NOT NULL DEFAULT 'long_term',
|
|
35
37
|
type TEXT NOT NULL DEFAULT 'note',
|
|
@@ -73,6 +75,7 @@ var CREATE_TABLES = `
|
|
|
73
75
|
-- Entity nodes
|
|
74
76
|
CREATE TABLE IF NOT EXISTS nodes (
|
|
75
77
|
id TEXT PRIMARY KEY,
|
|
78
|
+
project_id TEXT,
|
|
76
79
|
name TEXT NOT NULL,
|
|
77
80
|
name_embedding BLOB,
|
|
78
81
|
summary TEXT,
|
|
@@ -84,6 +87,7 @@ var CREATE_TABLES = `
|
|
|
84
87
|
-- Edges (bi-temporal)
|
|
85
88
|
CREATE TABLE IF NOT EXISTS edges (
|
|
86
89
|
id TEXT PRIMARY KEY,
|
|
90
|
+
project_id TEXT,
|
|
87
91
|
source_id TEXT NOT NULL REFERENCES nodes(id),
|
|
88
92
|
target_id TEXT NOT NULL REFERENCES nodes(id),
|
|
89
93
|
relation TEXT NOT NULL,
|
|
@@ -98,6 +102,7 @@ var CREATE_TABLES = `
|
|
|
98
102
|
-- Episodes
|
|
99
103
|
CREATE TABLE IF NOT EXISTS episodes (
|
|
100
104
|
id TEXT PRIMARY KEY,
|
|
105
|
+
project_id TEXT,
|
|
101
106
|
content TEXT NOT NULL,
|
|
102
107
|
content_type TEXT,
|
|
103
108
|
source TEXT,
|
|
@@ -115,6 +120,7 @@ var CREATE_TABLES = `
|
|
|
115
120
|
-- Sessions
|
|
116
121
|
CREATE TABLE IF NOT EXISTS sessions (
|
|
117
122
|
id TEXT PRIMARY KEY,
|
|
123
|
+
project_id TEXT,
|
|
118
124
|
status TEXT NOT NULL DEFAULT 'active',
|
|
119
125
|
started_at TEXT NOT NULL,
|
|
120
126
|
ended_at TEXT,
|
|
@@ -132,10 +138,28 @@ var CREATE_TABLES = `
|
|
|
132
138
|
-- Indexes
|
|
133
139
|
CREATE INDEX IF NOT EXISTS idx_memories_layer ON memories(layer);
|
|
134
140
|
CREATE INDEX IF NOT EXISTS idx_memories_deleted ON memories(deleted_at);
|
|
141
|
+
CREATE INDEX IF NOT EXISTS idx_memories_project ON memories(project_id);
|
|
142
|
+
CREATE INDEX IF NOT EXISTS idx_nodes_project ON nodes(project_id);
|
|
135
143
|
CREATE INDEX IF NOT EXISTS idx_edges_source ON edges(source_id);
|
|
136
144
|
CREATE INDEX IF NOT EXISTS idx_edges_target ON edges(target_id);
|
|
137
145
|
CREATE INDEX IF NOT EXISTS idx_edges_expired ON edges(expired_at);
|
|
146
|
+
CREATE INDEX IF NOT EXISTS idx_edges_project ON edges(project_id);
|
|
147
|
+
CREATE INDEX IF NOT EXISTS idx_episodes_project ON episodes(project_id);
|
|
138
148
|
CREATE INDEX IF NOT EXISTS idx_sessions_status ON sessions(status);
|
|
149
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_project ON sessions(project_id);
|
|
150
|
+
`;
|
|
151
|
+
var MIGRATE_V1_TO_V2 = `
|
|
152
|
+
ALTER TABLE memories ADD COLUMN project_id TEXT;
|
|
153
|
+
ALTER TABLE nodes ADD COLUMN project_id TEXT;
|
|
154
|
+
ALTER TABLE edges ADD COLUMN project_id TEXT;
|
|
155
|
+
ALTER TABLE episodes ADD COLUMN project_id TEXT;
|
|
156
|
+
ALTER TABLE sessions ADD COLUMN project_id TEXT;
|
|
157
|
+
|
|
158
|
+
CREATE INDEX IF NOT EXISTS idx_memories_project ON memories(project_id);
|
|
159
|
+
CREATE INDEX IF NOT EXISTS idx_nodes_project ON nodes(project_id);
|
|
160
|
+
CREATE INDEX IF NOT EXISTS idx_edges_project ON edges(project_id);
|
|
161
|
+
CREATE INDEX IF NOT EXISTS idx_episodes_project ON episodes(project_id);
|
|
162
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_project ON sessions(project_id);
|
|
139
163
|
`;
|
|
140
164
|
function createVecTables(dimensions) {
|
|
141
165
|
return `
|
|
@@ -178,27 +202,51 @@ var SqliteDriver = class {
|
|
|
178
202
|
this.db.pragma("journal_mode = WAL");
|
|
179
203
|
this.db.pragma("foreign_keys = ON");
|
|
180
204
|
sqliteVec.load(this.db);
|
|
205
|
+
this.db.exec("CREATE TABLE IF NOT EXISTS config (key TEXT PRIMARY KEY, value TEXT NOT NULL)");
|
|
206
|
+
const existing = this.db.prepare("SELECT value FROM config WHERE key = ?").get("schema_version");
|
|
207
|
+
const currentVersion = existing ? Number(existing.value) : 0;
|
|
208
|
+
if (currentVersion >= 1 && currentVersion < 2) {
|
|
209
|
+
for (const stmt of MIGRATE_V1_TO_V2.split(";").filter((s) => s.trim())) {
|
|
210
|
+
this.db.exec(stmt);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
181
213
|
this.db.exec(CREATE_TABLES);
|
|
182
214
|
const vecSQL = createVecTables(this.dimensions);
|
|
183
215
|
for (const stmt of vecSQL.split(";").filter((s) => s.trim())) {
|
|
184
216
|
this.db.exec(stmt);
|
|
185
217
|
}
|
|
186
|
-
|
|
187
|
-
if (!existing) {
|
|
188
|
-
this.db.prepare("INSERT INTO config (key, value) VALUES (?, ?)").run("schema_version", String(SCHEMA_VERSION));
|
|
189
|
-
}
|
|
218
|
+
this.db.prepare("INSERT INTO config (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").run("schema_version", String(SCHEMA_VERSION));
|
|
190
219
|
}
|
|
191
220
|
close() {
|
|
192
221
|
this.db.close();
|
|
193
222
|
}
|
|
223
|
+
// ── Project Filter Helper ──
|
|
224
|
+
buildProjectFilter(projectIds, tableAlias) {
|
|
225
|
+
if (!projectIds)
|
|
226
|
+
return { clause: "", params: [] };
|
|
227
|
+
const hasNull = projectIds.includes(null);
|
|
228
|
+
const nonNull = projectIds.filter((id) => id !== null);
|
|
229
|
+
const conditions = [];
|
|
230
|
+
const params = [];
|
|
231
|
+
if (hasNull) {
|
|
232
|
+
conditions.push(`${tableAlias}.project_id IS NULL`);
|
|
233
|
+
}
|
|
234
|
+
if (nonNull.length > 0) {
|
|
235
|
+
conditions.push(`${tableAlias}.project_id IN (${nonNull.map(() => "?").join(", ")})`);
|
|
236
|
+
params.push(...nonNull);
|
|
237
|
+
}
|
|
238
|
+
if (conditions.length === 0)
|
|
239
|
+
return { clause: "", params: [] };
|
|
240
|
+
return { clause: `AND (${conditions.join(" OR ")})`, params };
|
|
241
|
+
}
|
|
194
242
|
// ── Memory ──
|
|
195
243
|
async addMemory(memory) {
|
|
196
244
|
const tagsStr = memory.tags ? JSON.stringify(memory.tags) : null;
|
|
197
245
|
this.db.transaction(() => {
|
|
198
246
|
this.db.prepare(`
|
|
199
|
-
INSERT INTO memories (id, content, layer, type, priority, importance, tags, expires_at, last_accessed_at, created_at, updated_at, deleted_at)
|
|
200
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
201
|
-
`).run(memory.id, memory.content, memory.layer, memory.type, memory.priority, memory.importance, tagsStr, memory.expiresAt ?? null, memory.lastAccessedAt, memory.createdAt, memory.updatedAt, memory.deletedAt ?? null);
|
|
247
|
+
INSERT INTO memories (id, project_id, content, layer, type, priority, importance, tags, expires_at, last_accessed_at, created_at, updated_at, deleted_at)
|
|
248
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
249
|
+
`).run(memory.id, memory.projectId ?? null, memory.content, memory.layer, memory.type, memory.priority, memory.importance, tagsStr, memory.expiresAt ?? null, memory.lastAccessedAt, memory.createdAt, memory.updatedAt, memory.deletedAt ?? null);
|
|
202
250
|
if (memory.embedding) {
|
|
203
251
|
this.db.prepare(`
|
|
204
252
|
INSERT INTO vec_memories (memory_id, embedding) VALUES (?, ?)
|
|
@@ -249,7 +297,8 @@ var SqliteDriver = class {
|
|
|
249
297
|
async deleteMemory(id) {
|
|
250
298
|
this.db.prepare("UPDATE memories SET deleted_at = ? WHERE id = ?").run((/* @__PURE__ */ new Date()).toISOString(), id);
|
|
251
299
|
}
|
|
252
|
-
async searchMemories(embedding, limit) {
|
|
300
|
+
async searchMemories(embedding, limit, projectIds) {
|
|
301
|
+
const { clause, params } = this.buildProjectFilter(projectIds, "m");
|
|
253
302
|
const rows = this.db.prepare(`
|
|
254
303
|
SELECT m.*, v.distance
|
|
255
304
|
FROM vec_memories v
|
|
@@ -257,55 +306,60 @@ var SqliteDriver = class {
|
|
|
257
306
|
WHERE v.embedding MATCH ?
|
|
258
307
|
AND m.deleted_at IS NULL
|
|
259
308
|
AND k = ?
|
|
260
|
-
|
|
309
|
+
${clause}
|
|
310
|
+
`).all(toVecBlob(embedding), limit, ...params);
|
|
261
311
|
return rows.map((row) => ({
|
|
262
312
|
memory: this.rowToMemory(row),
|
|
263
313
|
distance: row.distance
|
|
264
314
|
}));
|
|
265
315
|
}
|
|
266
|
-
async fullTextSearch(query, limit) {
|
|
316
|
+
async fullTextSearch(query, limit, projectIds) {
|
|
317
|
+
const { clause, params } = this.buildProjectFilter(projectIds, "m");
|
|
267
318
|
const rows = this.db.prepare(`
|
|
268
319
|
SELECT m.*, fts.rank
|
|
269
320
|
FROM memories_fts fts
|
|
270
321
|
INNER JOIN memories m ON m.rowid = fts.rowid
|
|
271
322
|
WHERE memories_fts MATCH ?
|
|
272
323
|
AND m.deleted_at IS NULL
|
|
324
|
+
${clause}
|
|
273
325
|
ORDER BY fts.rank
|
|
274
326
|
LIMIT ?
|
|
275
|
-
`).all(query, limit);
|
|
327
|
+
`).all(query, ...params, limit);
|
|
276
328
|
return rows.map((row) => ({
|
|
277
329
|
memory: this.rowToMemory(row),
|
|
278
330
|
rank: row.rank
|
|
279
331
|
}));
|
|
280
332
|
}
|
|
281
|
-
async findSimilarMemories(embedding, threshold, limit) {
|
|
282
|
-
const results = await this.searchMemories(embedding, limit * 3);
|
|
333
|
+
async findSimilarMemories(embedding, threshold, limit, projectIds) {
|
|
334
|
+
const results = await this.searchMemories(embedding, limit * 3, projectIds);
|
|
283
335
|
return results.filter((r) => r.distance <= threshold).slice(0, limit);
|
|
284
336
|
}
|
|
285
|
-
async listMemoriesByLayer(layer, limit) {
|
|
337
|
+
async listMemoriesByLayer(layer, limit, projectIds) {
|
|
286
338
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
339
|
+
const { clause, params } = this.buildProjectFilter(projectIds, "memories");
|
|
287
340
|
const rows = this.db.prepare(`
|
|
288
341
|
SELECT * FROM memories
|
|
289
342
|
WHERE layer = ?
|
|
290
343
|
AND deleted_at IS NULL
|
|
291
344
|
AND (expires_at IS NULL OR expires_at > ?)
|
|
345
|
+
${clause}
|
|
292
346
|
ORDER BY updated_at DESC
|
|
293
347
|
LIMIT ?
|
|
294
|
-
`).all(layer, now, limit);
|
|
348
|
+
`).all(layer, now, ...params, limit);
|
|
295
349
|
return rows.map((row) => this.rowToMemory(row));
|
|
296
350
|
}
|
|
297
351
|
// ── Nodes ──
|
|
298
352
|
async upsertNode(node) {
|
|
299
353
|
this.db.transaction(() => {
|
|
300
354
|
this.db.prepare(`
|
|
301
|
-
INSERT INTO nodes (id, name, summary, entity_type, created_at, updated_at)
|
|
302
|
-
VALUES (?, ?, ?, ?, ?, ?)
|
|
355
|
+
INSERT INTO nodes (id, project_id, name, summary, entity_type, created_at, updated_at)
|
|
356
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
303
357
|
ON CONFLICT(id) DO UPDATE SET
|
|
304
358
|
name = excluded.name,
|
|
305
359
|
summary = excluded.summary,
|
|
306
360
|
entity_type = excluded.entity_type,
|
|
307
361
|
updated_at = excluded.updated_at
|
|
308
|
-
`).run(node.id, node.name, node.summary ?? null, node.entityType ?? null, node.createdAt, node.updatedAt);
|
|
362
|
+
`).run(node.id, node.projectId ?? null, node.name, node.summary ?? null, node.entityType ?? null, node.createdAt, node.updatedAt);
|
|
309
363
|
if (node.nameEmbedding) {
|
|
310
364
|
this.db.prepare(`
|
|
311
365
|
DELETE FROM vec_nodes WHERE node_id = ?
|
|
@@ -320,29 +374,31 @@ var SqliteDriver = class {
|
|
|
320
374
|
const row = this.db.prepare("SELECT * FROM nodes WHERE id = ?").get(id);
|
|
321
375
|
return row ? this.rowToNode(row) : null;
|
|
322
376
|
}
|
|
323
|
-
async searchNodes(embedding, limit) {
|
|
377
|
+
async searchNodes(embedding, limit, projectIds) {
|
|
378
|
+
const { clause, params } = this.buildProjectFilter(projectIds, "n");
|
|
324
379
|
const rows = this.db.prepare(`
|
|
325
380
|
SELECT n.*
|
|
326
381
|
FROM vec_nodes v
|
|
327
382
|
INNER JOIN nodes n ON n.id = v.node_id
|
|
328
383
|
WHERE v.embedding MATCH ?
|
|
329
384
|
AND k = ?
|
|
330
|
-
|
|
385
|
+
${clause}
|
|
386
|
+
`).all(toVecBlob(embedding), limit, ...params);
|
|
331
387
|
return rows.map((row) => this.rowToNode(row));
|
|
332
388
|
}
|
|
333
389
|
// ── Edges ──
|
|
334
390
|
async upsertEdge(edge) {
|
|
335
391
|
this.db.transaction(() => {
|
|
336
392
|
this.db.prepare(`
|
|
337
|
-
INSERT INTO edges (id, source_id, target_id, relation, fact, created_at, expired_at, valid_at, invalid_at)
|
|
338
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
393
|
+
INSERT INTO edges (id, project_id, source_id, target_id, relation, fact, created_at, expired_at, valid_at, invalid_at)
|
|
394
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
339
395
|
ON CONFLICT(id) DO UPDATE SET
|
|
340
396
|
relation = excluded.relation,
|
|
341
397
|
fact = excluded.fact,
|
|
342
398
|
expired_at = excluded.expired_at,
|
|
343
399
|
valid_at = excluded.valid_at,
|
|
344
400
|
invalid_at = excluded.invalid_at
|
|
345
|
-
`).run(edge.id, edge.sourceId, edge.targetId, edge.relation, edge.fact ?? null, edge.createdAt, edge.expiredAt ?? null, edge.validAt ?? null, edge.invalidAt ?? null);
|
|
401
|
+
`).run(edge.id, edge.projectId ?? null, edge.sourceId, edge.targetId, edge.relation, edge.fact ?? null, edge.createdAt, edge.expiredAt ?? null, edge.validAt ?? null, edge.invalidAt ?? null);
|
|
346
402
|
if (edge.factEmbedding) {
|
|
347
403
|
this.db.prepare(`
|
|
348
404
|
DELETE FROM vec_edges WHERE edge_id = ?
|
|
@@ -360,7 +416,7 @@ var SqliteDriver = class {
|
|
|
360
416
|
async invalidateEdge(id, expiredAt, invalidAt) {
|
|
361
417
|
this.db.prepare("UPDATE edges SET expired_at = ?, invalid_at = ? WHERE id = ?").run(expiredAt, invalidAt, id);
|
|
362
418
|
}
|
|
363
|
-
async searchEdges(embedding, filters, limit) {
|
|
419
|
+
async searchEdges(embedding, filters, limit, projectIds) {
|
|
364
420
|
let whereClause = "1=1";
|
|
365
421
|
const params = [toVecBlob(embedding), limit];
|
|
366
422
|
if (filters.excludeExpired !== false) {
|
|
@@ -372,6 +428,9 @@ var SqliteDriver = class {
|
|
|
372
428
|
whereClause += " AND (e.invalid_at IS NULL OR e.invalid_at > ?)";
|
|
373
429
|
params.push(filters.asOf);
|
|
374
430
|
}
|
|
431
|
+
const { clause: projectClause, params: projectParams } = this.buildProjectFilter(projectIds, "e");
|
|
432
|
+
whereClause += ` ${projectClause}`;
|
|
433
|
+
params.push(...projectParams);
|
|
375
434
|
const rows = this.db.prepare(`
|
|
376
435
|
SELECT e.*
|
|
377
436
|
FROM vec_edges v
|
|
@@ -427,9 +486,9 @@ var SqliteDriver = class {
|
|
|
427
486
|
// ── Episodes ──
|
|
428
487
|
async addEpisode(episode) {
|
|
429
488
|
this.db.prepare(`
|
|
430
|
-
INSERT INTO episodes (id, content, content_type, source, created_at)
|
|
431
|
-
VALUES (?, ?, ?, ?, ?)
|
|
432
|
-
`).run(episode.id, episode.content, episode.contentType ?? null, episode.source ?? null, episode.createdAt);
|
|
489
|
+
INSERT INTO episodes (id, project_id, content, content_type, source, created_at)
|
|
490
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
491
|
+
`).run(episode.id, episode.projectId ?? null, episode.content, episode.contentType ?? null, episode.source ?? null, episode.createdAt);
|
|
433
492
|
}
|
|
434
493
|
async addEpisodeRef(ref) {
|
|
435
494
|
this.db.prepare(`
|
|
@@ -441,8 +500,9 @@ var SqliteDriver = class {
|
|
|
441
500
|
const row = this.db.prepare("SELECT * FROM episodes WHERE id = ?").get(id);
|
|
442
501
|
return row ? this.rowToEpisode(row) : null;
|
|
443
502
|
}
|
|
444
|
-
async listEpisodes(limit) {
|
|
445
|
-
const
|
|
503
|
+
async listEpisodes(limit, projectIds) {
|
|
504
|
+
const { clause, params } = this.buildProjectFilter(projectIds, "episodes");
|
|
505
|
+
const rows = this.db.prepare(`SELECT * FROM episodes WHERE 1=1 ${clause} ORDER BY created_at DESC LIMIT ?`).all(...params, limit);
|
|
446
506
|
return rows.map((row) => this.rowToEpisode(row));
|
|
447
507
|
}
|
|
448
508
|
async getEpisodeRefs(episodeId) {
|
|
@@ -456,9 +516,9 @@ var SqliteDriver = class {
|
|
|
456
516
|
// ── Sessions ──
|
|
457
517
|
async addSession(session) {
|
|
458
518
|
this.db.prepare(`
|
|
459
|
-
INSERT INTO sessions (id, status, started_at, ended_at, checkpoint_count, working_memory_snapshot, metadata)
|
|
460
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
461
|
-
`).run(session.id, session.status, session.startedAt, session.endedAt ?? null, session.checkpointCount, session.workingMemorySnapshot ?? null, session.metadata ?? null);
|
|
519
|
+
INSERT INTO sessions (id, project_id, status, started_at, ended_at, checkpoint_count, working_memory_snapshot, metadata)
|
|
520
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
521
|
+
`).run(session.id, session.projectId ?? null, session.status, session.startedAt, session.endedAt ?? null, session.checkpointCount, session.workingMemorySnapshot ?? null, session.metadata ?? null);
|
|
462
522
|
}
|
|
463
523
|
async updateSession(id, updates) {
|
|
464
524
|
const sets = [];
|
|
@@ -492,8 +552,9 @@ var SqliteDriver = class {
|
|
|
492
552
|
const row = this.db.prepare("SELECT * FROM sessions WHERE id = ?").get(id);
|
|
493
553
|
return row ? this.rowToSession(row) : null;
|
|
494
554
|
}
|
|
495
|
-
async listSessions(limit) {
|
|
496
|
-
const
|
|
555
|
+
async listSessions(limit, projectIds) {
|
|
556
|
+
const { clause, params } = this.buildProjectFilter(projectIds, "sessions");
|
|
557
|
+
const rows = this.db.prepare(`SELECT * FROM sessions WHERE 1=1 ${clause} ORDER BY started_at DESC LIMIT ?`).all(...params, limit);
|
|
497
558
|
return rows.map((row) => this.rowToSession(row));
|
|
498
559
|
}
|
|
499
560
|
// ── Config ──
|
|
@@ -508,6 +569,7 @@ var SqliteDriver = class {
|
|
|
508
569
|
rowToMemory(row) {
|
|
509
570
|
return {
|
|
510
571
|
id: row.id,
|
|
572
|
+
projectId: row.project_id ?? void 0,
|
|
511
573
|
content: row.content,
|
|
512
574
|
layer: row.layer,
|
|
513
575
|
type: row.type,
|
|
@@ -524,6 +586,7 @@ var SqliteDriver = class {
|
|
|
524
586
|
rowToNode(row) {
|
|
525
587
|
return {
|
|
526
588
|
id: row.id,
|
|
589
|
+
projectId: row.project_id ?? void 0,
|
|
527
590
|
name: row.name,
|
|
528
591
|
summary: row.summary ?? void 0,
|
|
529
592
|
entityType: row.entity_type ?? void 0,
|
|
@@ -534,6 +597,7 @@ var SqliteDriver = class {
|
|
|
534
597
|
rowToEdge(row) {
|
|
535
598
|
return {
|
|
536
599
|
id: row.id,
|
|
600
|
+
projectId: row.project_id ?? void 0,
|
|
537
601
|
sourceId: row.source_id,
|
|
538
602
|
targetId: row.target_id,
|
|
539
603
|
relation: row.relation,
|
|
@@ -547,6 +611,7 @@ var SqliteDriver = class {
|
|
|
547
611
|
rowToEpisode(row) {
|
|
548
612
|
return {
|
|
549
613
|
id: row.id,
|
|
614
|
+
projectId: row.project_id ?? void 0,
|
|
550
615
|
content: row.content,
|
|
551
616
|
contentType: row.content_type ?? void 0,
|
|
552
617
|
source: row.source ?? void 0,
|
|
@@ -556,6 +621,7 @@ var SqliteDriver = class {
|
|
|
556
621
|
rowToSession(row) {
|
|
557
622
|
return {
|
|
558
623
|
id: row.id,
|
|
624
|
+
projectId: row.project_id ?? void 0,
|
|
559
625
|
status: row.status,
|
|
560
626
|
startedAt: row.started_at,
|
|
561
627
|
endedAt: row.ended_at ?? void 0,
|
|
@@ -632,12 +698,12 @@ function distanceToSimilarity(distance) {
|
|
|
632
698
|
|
|
633
699
|
// ../core/dist/engine/context.js
|
|
634
700
|
async function getContext(storage, options = {}) {
|
|
635
|
-
const { maxMemories = 50 } = options;
|
|
636
|
-
const rules = await storage.listMemoriesByLayer("rule", maxMemories);
|
|
701
|
+
const { maxMemories = 50, projectIds } = options;
|
|
702
|
+
const rules = await storage.listMemoriesByLayer("rule", maxMemories, projectIds);
|
|
637
703
|
const remaining1 = maxMemories - rules.length;
|
|
638
|
-
const working = remaining1 > 0 ? await storage.listMemoriesByLayer("working", Math.ceil(remaining1 * 0.6)) : [];
|
|
704
|
+
const working = remaining1 > 0 ? await storage.listMemoriesByLayer("working", Math.ceil(remaining1 * 0.6), projectIds) : [];
|
|
639
705
|
const remaining2 = maxMemories - rules.length - working.length;
|
|
640
|
-
const longTerm = remaining2 > 0 ? await storage.listMemoriesByLayer("long_term", remaining2) : [];
|
|
706
|
+
const longTerm = remaining2 > 0 ? await storage.listMemoriesByLayer("long_term", remaining2, projectIds) : [];
|
|
641
707
|
const memories = [...rules, ...working, ...longTerm];
|
|
642
708
|
const totalCount = memories.length;
|
|
643
709
|
return {
|
|
@@ -653,12 +719,14 @@ var MemoryEngine = class {
|
|
|
653
719
|
embedding;
|
|
654
720
|
weights;
|
|
655
721
|
decayRate;
|
|
722
|
+
defaultProjectId;
|
|
656
723
|
constructor(config) {
|
|
657
724
|
mkdirSync(dirname(config.dbPath), { recursive: true });
|
|
658
725
|
this.storage = new SqliteDriver(config.dbPath);
|
|
659
726
|
this.embedding = new LocalEmbedding(config.embeddingCacheDir);
|
|
660
727
|
this.weights = { ...DEFAULT_WEIGHTS, ...config.weights };
|
|
661
728
|
this.decayRate = config.decayRate ?? DEFAULT_DECAY_RATE;
|
|
729
|
+
this.defaultProjectId = config.defaultProjectId;
|
|
662
730
|
}
|
|
663
731
|
async initialize() {
|
|
664
732
|
await this.storage.initialize();
|
|
@@ -666,12 +734,28 @@ var MemoryEngine = class {
|
|
|
666
734
|
close() {
|
|
667
735
|
this.storage.close();
|
|
668
736
|
}
|
|
737
|
+
// ── Scope Helpers ──
|
|
738
|
+
resolveProjectId(scope) {
|
|
739
|
+
if (scope === "global")
|
|
740
|
+
return void 0;
|
|
741
|
+
return this.defaultProjectId;
|
|
742
|
+
}
|
|
743
|
+
resolveProjectFilter(scope) {
|
|
744
|
+
if (scope === "all")
|
|
745
|
+
return void 0;
|
|
746
|
+
if (scope === "global")
|
|
747
|
+
return [null];
|
|
748
|
+
if (this.defaultProjectId)
|
|
749
|
+
return [this.defaultProjectId, null];
|
|
750
|
+
return void 0;
|
|
751
|
+
}
|
|
669
752
|
// ── Memory ──
|
|
670
753
|
async addMemory(input) {
|
|
671
754
|
const now = nowISO();
|
|
672
755
|
const layer = input.layer ?? "long_term";
|
|
673
756
|
const memory = {
|
|
674
757
|
id: generateId(),
|
|
758
|
+
projectId: this.resolveProjectId(input.scope),
|
|
675
759
|
content: input.content,
|
|
676
760
|
layer,
|
|
677
761
|
type: input.type ?? "note",
|
|
@@ -690,11 +774,12 @@ var MemoryEngine = class {
|
|
|
690
774
|
async searchMemories(query) {
|
|
691
775
|
const limit = query.limit ?? 10;
|
|
692
776
|
const weights = { ...this.weights, ...query.weights };
|
|
777
|
+
const projectIds = this.resolveProjectFilter(query.scope);
|
|
693
778
|
const queryEmbedding = query.embedding ?? await this.embedding.embed(query.text);
|
|
694
|
-
const semanticResults = await this.storage.searchMemories(queryEmbedding, limit * 2);
|
|
779
|
+
const semanticResults = await this.storage.searchMemories(queryEmbedding, limit * 2, projectIds);
|
|
695
780
|
let bm25Results = [];
|
|
696
781
|
try {
|
|
697
|
-
bm25Results = await this.storage.fullTextSearch(query.text, limit * 2);
|
|
782
|
+
bm25Results = await this.storage.fullTextSearch(query.text, limit * 2, projectIds);
|
|
698
783
|
} catch {
|
|
699
784
|
}
|
|
700
785
|
const candidateMap = /* @__PURE__ */ new Map();
|
|
@@ -752,7 +837,8 @@ var MemoryEngine = class {
|
|
|
752
837
|
if (!target)
|
|
753
838
|
throw new Error(`Memory not found: ${memoryId}`);
|
|
754
839
|
const embedding = target.embedding ?? await this.embedding.embed(target.content);
|
|
755
|
-
const
|
|
840
|
+
const projectIds = this.resolveProjectFilter("project");
|
|
841
|
+
const similar = await this.storage.findSimilarMemories(embedding, threshold, 20, projectIds);
|
|
756
842
|
return similar.filter((r) => r.memory.id !== memoryId);
|
|
757
843
|
}
|
|
758
844
|
async consolidateMemories(sourceIds, mergedContent, layer) {
|
|
@@ -771,6 +857,7 @@ var MemoryEngine = class {
|
|
|
771
857
|
const now = nowISO();
|
|
772
858
|
const node = {
|
|
773
859
|
id: generateId(),
|
|
860
|
+
projectId: this.resolveProjectId(input.scope),
|
|
774
861
|
name: input.name,
|
|
775
862
|
nameEmbedding: await this.embedding.embed(input.name),
|
|
776
863
|
summary: input.summary,
|
|
@@ -785,6 +872,7 @@ var MemoryEngine = class {
|
|
|
785
872
|
const now = nowISO();
|
|
786
873
|
const edge = {
|
|
787
874
|
id: generateId(),
|
|
875
|
+
projectId: this.resolveProjectId(input.scope),
|
|
788
876
|
sourceId: input.sourceId,
|
|
789
877
|
targetId: input.targetId,
|
|
790
878
|
relation: input.relation,
|
|
@@ -819,9 +907,10 @@ var MemoryEngine = class {
|
|
|
819
907
|
}
|
|
820
908
|
async searchGraph(query, limit = 10, options) {
|
|
821
909
|
const depth = options?.traverseDepth ?? 1;
|
|
910
|
+
const projectIds = this.resolveProjectFilter(options?.scope);
|
|
822
911
|
const queryEmbedding = await this.embedding.embed(query);
|
|
823
|
-
const nodes = await this.storage.searchNodes(queryEmbedding, Math.ceil(limit / 2));
|
|
824
|
-
const edges = await this.storage.searchEdges(queryEmbedding, { excludeExpired: true }, Math.ceil(limit / 2));
|
|
912
|
+
const nodes = await this.storage.searchNodes(queryEmbedding, Math.ceil(limit / 2), projectIds);
|
|
913
|
+
const edges = await this.storage.searchEdges(queryEmbedding, { excludeExpired: true }, Math.ceil(limit / 2), projectIds);
|
|
825
914
|
const results = [];
|
|
826
915
|
const seenNodeIds = /* @__PURE__ */ new Set();
|
|
827
916
|
for (const node of nodes) {
|
|
@@ -864,6 +953,7 @@ var MemoryEngine = class {
|
|
|
864
953
|
async addEpisode(input, refs) {
|
|
865
954
|
const episode = {
|
|
866
955
|
id: generateId(),
|
|
956
|
+
projectId: this.resolveProjectId(input.scope),
|
|
867
957
|
content: input.content,
|
|
868
958
|
contentType: input.contentType,
|
|
869
959
|
source: input.source,
|
|
@@ -880,13 +970,15 @@ var MemoryEngine = class {
|
|
|
880
970
|
async getEpisode(id) {
|
|
881
971
|
return this.storage.getEpisode(id);
|
|
882
972
|
}
|
|
883
|
-
async listEpisodes(limit = 20) {
|
|
884
|
-
|
|
973
|
+
async listEpisodes(limit = 20, scope) {
|
|
974
|
+
const projectIds = this.resolveProjectFilter(scope);
|
|
975
|
+
return this.storage.listEpisodes(limit, projectIds);
|
|
885
976
|
}
|
|
886
977
|
// ── Sessions ──
|
|
887
978
|
async sessionStart(metadata) {
|
|
888
979
|
const session = {
|
|
889
980
|
id: generateId(),
|
|
981
|
+
projectId: this.resolveProjectId("project"),
|
|
890
982
|
status: "active",
|
|
891
983
|
startedAt: nowISO(),
|
|
892
984
|
checkpointCount: 0,
|
|
@@ -909,12 +1001,14 @@ var MemoryEngine = class {
|
|
|
909
1001
|
checkpointCount: session.checkpointCount + 1
|
|
910
1002
|
});
|
|
911
1003
|
}
|
|
912
|
-
async sessionList(limit = 20) {
|
|
913
|
-
|
|
1004
|
+
async sessionList(limit = 20, scope) {
|
|
1005
|
+
const projectIds = this.resolveProjectFilter(scope);
|
|
1006
|
+
return this.storage.listSessions(limit, projectIds);
|
|
914
1007
|
}
|
|
915
1008
|
// ── Context ──
|
|
916
1009
|
async getContext(options) {
|
|
917
|
-
|
|
1010
|
+
const projectIds = this.resolveProjectFilter(options?.scope);
|
|
1011
|
+
return getContext(this.storage, { ...options, projectIds });
|
|
918
1012
|
}
|
|
919
1013
|
};
|
|
920
1014
|
|
|
@@ -967,6 +1061,8 @@ function textContent(text) {
|
|
|
967
1061
|
}
|
|
968
1062
|
|
|
969
1063
|
// src/tools/memory.ts
|
|
1064
|
+
var scopeSchema = z.enum(["project", "global"]).default("project").describe('Scope: "project" (default) stores in current project, "global" stores across all projects');
|
|
1065
|
+
var searchScopeSchema = z.enum(["project", "global", "all"]).default("project").describe('Scope: "project" (default) searches project + global, "global" searches global only, "all" searches everything');
|
|
970
1066
|
function registerMemoryTools(server2, engine2) {
|
|
971
1067
|
server2.tool(
|
|
972
1068
|
"add_memory",
|
|
@@ -976,7 +1072,8 @@ function registerMemoryTools(server2, engine2) {
|
|
|
976
1072
|
layer: z.enum(["rule", "working", "long_term"]).default("long_term").describe("Memory layer"),
|
|
977
1073
|
type: z.enum(["rule", "decision", "fact", "note", "skill"]).default("note").describe("Memory type"),
|
|
978
1074
|
tags: z.array(z.string()).optional().describe("Tags"),
|
|
979
|
-
expiresAt: z.string().optional().describe("Expiration date (ISO 8601)")
|
|
1075
|
+
expiresAt: z.string().optional().describe("Expiration date (ISO 8601)"),
|
|
1076
|
+
scope: scopeSchema
|
|
980
1077
|
},
|
|
981
1078
|
async (params) => {
|
|
982
1079
|
const memory = await engine2.addMemory(params);
|
|
@@ -991,13 +1088,15 @@ ID: ${memory.id}`);
|
|
|
991
1088
|
{
|
|
992
1089
|
query: z.string().describe("Search query"),
|
|
993
1090
|
limit: z.number().int().min(1).max(100).default(10).describe("Max results"),
|
|
994
|
-
layers: z.array(z.enum(["rule", "working", "long_term"])).optional().describe("Target layers")
|
|
1091
|
+
layers: z.array(z.enum(["rule", "working", "long_term"])).optional().describe("Target layers"),
|
|
1092
|
+
scope: searchScopeSchema
|
|
995
1093
|
},
|
|
996
1094
|
async (params) => {
|
|
997
1095
|
const results = await engine2.searchMemories({
|
|
998
1096
|
text: params.query,
|
|
999
1097
|
limit: params.limit,
|
|
1000
|
-
layers: params.layers
|
|
1098
|
+
layers: params.layers,
|
|
1099
|
+
scope: params.scope
|
|
1001
1100
|
});
|
|
1002
1101
|
return textContent(formatSearchResults(results));
|
|
1003
1102
|
}
|
|
@@ -1067,6 +1166,7 @@ ID: ${merged.id}`);
|
|
|
1067
1166
|
|
|
1068
1167
|
// src/tools/session.ts
|
|
1069
1168
|
import { z as z2 } from "zod";
|
|
1169
|
+
var searchScopeSchema2 = z2.enum(["project", "global", "all"]).default("project").describe('Scope: "project" (default) searches project + global, "global" searches global only, "all" searches everything');
|
|
1070
1170
|
function registerSessionTools(server2, engine2) {
|
|
1071
1171
|
server2.tool(
|
|
1072
1172
|
"session_start",
|
|
@@ -1106,10 +1206,11 @@ ${formatSession(session)}`);
|
|
|
1106
1206
|
"session_list",
|
|
1107
1207
|
"List past sessions",
|
|
1108
1208
|
{
|
|
1109
|
-
limit: z2.number().int().min(1).max(100).default(20).describe("Max results")
|
|
1209
|
+
limit: z2.number().int().min(1).max(100).default(20).describe("Max results"),
|
|
1210
|
+
scope: searchScopeSchema2
|
|
1110
1211
|
},
|
|
1111
1212
|
async (params) => {
|
|
1112
|
-
const sessions = await engine2.sessionList(params.limit);
|
|
1213
|
+
const sessions = await engine2.sessionList(params.limit, params.scope);
|
|
1113
1214
|
if (sessions.length === 0) return textContent("No sessions found");
|
|
1114
1215
|
return textContent(sessions.map((s) => formatSession(s)).join("\n"));
|
|
1115
1216
|
}
|
|
@@ -1118,15 +1219,17 @@ ${formatSession(session)}`);
|
|
|
1118
1219
|
|
|
1119
1220
|
// src/tools/context.ts
|
|
1120
1221
|
import { z as z3 } from "zod";
|
|
1222
|
+
var searchScopeSchema3 = z3.enum(["project", "global", "all"]).default("project").describe('Scope: "project" (default) searches project + global, "global" searches global only, "all" searches everything');
|
|
1121
1223
|
function registerContextTools(server2, engine2) {
|
|
1122
1224
|
server2.tool(
|
|
1123
1225
|
"get_context",
|
|
1124
1226
|
"Get current context (priority: rule > working > long_term)",
|
|
1125
1227
|
{
|
|
1126
|
-
maxMemories: z3.number().int().min(1).max(200).default(50).describe("Max number of memories")
|
|
1228
|
+
maxMemories: z3.number().int().min(1).max(200).default(50).describe("Max number of memories"),
|
|
1229
|
+
scope: searchScopeSchema3
|
|
1127
1230
|
},
|
|
1128
1231
|
async (params) => {
|
|
1129
|
-
const ctx = await engine2.getContext({ maxMemories: params.maxMemories });
|
|
1232
|
+
const ctx = await engine2.getContext({ maxMemories: params.maxMemories, scope: params.scope });
|
|
1130
1233
|
return textContent(formatContext(ctx));
|
|
1131
1234
|
}
|
|
1132
1235
|
);
|
|
@@ -1134,6 +1237,8 @@ function registerContextTools(server2, engine2) {
|
|
|
1134
1237
|
|
|
1135
1238
|
// src/tools/graph.ts
|
|
1136
1239
|
import { z as z4 } from "zod";
|
|
1240
|
+
var scopeSchema2 = z4.enum(["project", "global"]).default("project").describe('Scope: "project" (default) stores in current project, "global" stores across all projects');
|
|
1241
|
+
var searchScopeSchema4 = z4.enum(["project", "global", "all"]).default("project").describe('Scope: "project" (default) searches project + global, "global" searches global only, "all" searches everything');
|
|
1137
1242
|
function registerGraphTools(server2, engine2) {
|
|
1138
1243
|
server2.tool(
|
|
1139
1244
|
"add_entity",
|
|
@@ -1141,7 +1246,8 @@ function registerGraphTools(server2, engine2) {
|
|
|
1141
1246
|
{
|
|
1142
1247
|
name: z4.string().describe("Entity name"),
|
|
1143
1248
|
summary: z4.string().optional().describe("Summary"),
|
|
1144
|
-
entityType: z4.string().optional().describe("Type (person, project, technology, etc.)")
|
|
1249
|
+
entityType: z4.string().optional().describe("Type (person, project, technology, etc.)"),
|
|
1250
|
+
scope: scopeSchema2
|
|
1145
1251
|
},
|
|
1146
1252
|
async (params) => {
|
|
1147
1253
|
const node = await engine2.addEntity(params);
|
|
@@ -1158,7 +1264,8 @@ ID: ${node.id}`);
|
|
|
1158
1264
|
targetId: z4.string().describe("Target node ID"),
|
|
1159
1265
|
relation: z4.string().describe("Relation (works_at, uses, depends_on, etc.)"),
|
|
1160
1266
|
fact: z4.string().optional().describe("Fact description"),
|
|
1161
|
-
validAt: z4.string().optional().describe("Valid-from timestamp (ISO 8601)")
|
|
1267
|
+
validAt: z4.string().optional().describe("Valid-from timestamp (ISO 8601)"),
|
|
1268
|
+
scope: scopeSchema2
|
|
1162
1269
|
},
|
|
1163
1270
|
async (params) => {
|
|
1164
1271
|
const edge = await engine2.addEdge(params);
|
|
@@ -1201,11 +1308,13 @@ New ID: ${edge.id}`);
|
|
|
1201
1308
|
{
|
|
1202
1309
|
query: z4.string().describe("Search query"),
|
|
1203
1310
|
limit: z4.number().int().min(1).max(100).default(10).describe("Max results"),
|
|
1204
|
-
traverseDepth: z4.number().int().min(0).max(2).default(1).describe("Graph traversal depth (0=none, 1=1-hop, 2=2-hop)")
|
|
1311
|
+
traverseDepth: z4.number().int().min(0).max(2).default(1).describe("Graph traversal depth (0=none, 1=1-hop, 2=2-hop)"),
|
|
1312
|
+
scope: searchScopeSchema4
|
|
1205
1313
|
},
|
|
1206
1314
|
async (params) => {
|
|
1207
1315
|
const results = await engine2.searchGraph(params.query, params.limit, {
|
|
1208
|
-
traverseDepth: params.traverseDepth
|
|
1316
|
+
traverseDepth: params.traverseDepth,
|
|
1317
|
+
scope: params.scope
|
|
1209
1318
|
});
|
|
1210
1319
|
return textContent(formatSearchResults(results));
|
|
1211
1320
|
}
|
|
@@ -1214,6 +1323,8 @@ New ID: ${edge.id}`);
|
|
|
1214
1323
|
|
|
1215
1324
|
// src/tools/episode.ts
|
|
1216
1325
|
import { z as z5 } from "zod";
|
|
1326
|
+
var scopeSchema3 = z5.enum(["project", "global"]).default("project").describe('Scope: "project" (default) stores in current project, "global" stores across all projects');
|
|
1327
|
+
var searchScopeSchema5 = z5.enum(["project", "global", "all"]).default("project").describe('Scope: "project" (default) searches project + global, "global" searches global only, "all" searches everything');
|
|
1217
1328
|
function registerEpisodeTools(server2, engine2) {
|
|
1218
1329
|
server2.tool(
|
|
1219
1330
|
"add_episode",
|
|
@@ -1225,7 +1336,8 @@ function registerEpisodeTools(server2, engine2) {
|
|
|
1225
1336
|
refs: z5.array(z5.object({
|
|
1226
1337
|
refType: z5.enum(["node", "edge"]).describe("Reference type"),
|
|
1227
1338
|
refId: z5.string().describe("Referenced entity ID")
|
|
1228
|
-
})).optional().describe("References to related nodes/edges")
|
|
1339
|
+
})).optional().describe("References to related nodes/edges"),
|
|
1340
|
+
scope: scopeSchema3
|
|
1229
1341
|
},
|
|
1230
1342
|
async (params) => {
|
|
1231
1343
|
const refs = params.refs?.map((r) => ({
|
|
@@ -1235,7 +1347,7 @@ function registerEpisodeTools(server2, engine2) {
|
|
|
1235
1347
|
refId: r.refId
|
|
1236
1348
|
}));
|
|
1237
1349
|
const episode = await engine2.addEpisode(
|
|
1238
|
-
{ content: params.content, contentType: params.contentType, source: params.source },
|
|
1350
|
+
{ content: params.content, contentType: params.contentType, source: params.source, scope: params.scope },
|
|
1239
1351
|
refs
|
|
1240
1352
|
);
|
|
1241
1353
|
return textContent(`Episode added:
|
|
@@ -1259,10 +1371,11 @@ ID: ${episode.id}`);
|
|
|
1259
1371
|
"list_episodes",
|
|
1260
1372
|
"List episodes",
|
|
1261
1373
|
{
|
|
1262
|
-
limit: z5.number().int().min(1).max(100).default(20).describe("Max results")
|
|
1374
|
+
limit: z5.number().int().min(1).max(100).default(20).describe("Max results"),
|
|
1375
|
+
scope: searchScopeSchema5
|
|
1263
1376
|
},
|
|
1264
1377
|
async (params) => {
|
|
1265
|
-
const episodes = await engine2.listEpisodes(params.limit);
|
|
1378
|
+
const episodes = await engine2.listEpisodes(params.limit, params.scope);
|
|
1266
1379
|
if (episodes.length === 0) return textContent("No episodes found");
|
|
1267
1380
|
const lines = episodes.map((ep, i) => `${i + 1}. ${formatEpisode(ep)}
|
|
1268
1381
|
ID: ${ep.id}`);
|
|
@@ -1364,11 +1477,72 @@ function createServer(engine2) {
|
|
|
1364
1477
|
}
|
|
1365
1478
|
|
|
1366
1479
|
// src/index.ts
|
|
1480
|
+
var VERSION = "0.2.0";
|
|
1481
|
+
var c = {
|
|
1482
|
+
reset: "\x1B[0m",
|
|
1483
|
+
bold: "\x1B[1m",
|
|
1484
|
+
dim: "\x1B[2m",
|
|
1485
|
+
amber: "\x1B[38;5;214m",
|
|
1486
|
+
yellow: "\x1B[38;5;220m",
|
|
1487
|
+
gray: "\x1B[38;5;245m",
|
|
1488
|
+
green: "\x1B[38;5;114m",
|
|
1489
|
+
white: "\x1B[97m"
|
|
1490
|
+
};
|
|
1367
1491
|
var dbPath = process.env["COHAKU_DB_PATH"] ?? join(homedir(), ".config", "cohaku", "memory.db");
|
|
1368
|
-
|
|
1492
|
+
function detectProjectId() {
|
|
1493
|
+
const envProjectId = process.env["COHAKU_PROJECT_ID"];
|
|
1494
|
+
if (envProjectId) return envProjectId;
|
|
1495
|
+
try {
|
|
1496
|
+
const gitRoot = execSync("git rev-parse --show-toplevel", {
|
|
1497
|
+
encoding: "utf-8",
|
|
1498
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1499
|
+
}).trim();
|
|
1500
|
+
return gitRoot || void 0;
|
|
1501
|
+
} catch {
|
|
1502
|
+
return void 0;
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
async function checkForUpdate() {
|
|
1506
|
+
try {
|
|
1507
|
+
const res = await fetch("https://registry.npmjs.org/@cohaku/mcp/latest", {
|
|
1508
|
+
signal: AbortSignal.timeout(3e3)
|
|
1509
|
+
});
|
|
1510
|
+
if (!res.ok) return null;
|
|
1511
|
+
const data = await res.json();
|
|
1512
|
+
if (data.version && data.version !== VERSION) return data.version;
|
|
1513
|
+
return null;
|
|
1514
|
+
} catch {
|
|
1515
|
+
return null;
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
function printBanner(projectId, latestVersion2) {
|
|
1519
|
+
const projectName = projectId ? basename(projectId) : void 0;
|
|
1520
|
+
const scopeLabel = projectName ? `${c.green}${projectName}${c.reset}` : `${c.gray}global${c.reset}`;
|
|
1521
|
+
const lines = [
|
|
1522
|
+
"",
|
|
1523
|
+
` ${c.amber}${c.bold}\u25C6 Cohaku AI${c.reset} ${c.dim}v${VERSION}${c.reset}`
|
|
1524
|
+
];
|
|
1525
|
+
if (latestVersion2) {
|
|
1526
|
+
lines.push(` ${c.yellow}\u26A0 v${latestVersion2} available${c.reset} ${c.dim}\u2014 npx @cohaku/mcp@latest${c.reset}`);
|
|
1527
|
+
}
|
|
1528
|
+
lines.push(
|
|
1529
|
+
"",
|
|
1530
|
+
` ${c.gray}Scope${c.reset} ${scopeLabel}`,
|
|
1531
|
+
` ${c.gray}Database${c.reset} ${c.dim}${dbPath.replace(homedir(), "~")}${c.reset}`,
|
|
1532
|
+
` ${c.gray}Transport${c.reset} ${c.dim}stdio${c.reset}`,
|
|
1533
|
+
"",
|
|
1534
|
+
` ${c.green}\u25CF${c.reset} ${c.white}Ready${c.reset} ${c.dim}\u2014 waiting for MCP client${c.reset}`,
|
|
1535
|
+
""
|
|
1536
|
+
);
|
|
1537
|
+
process.stderr.write(lines.join("\n") + "\n");
|
|
1538
|
+
}
|
|
1539
|
+
var defaultProjectId = detectProjectId();
|
|
1540
|
+
var engine = new MemoryEngine({ dbPath, defaultProjectId });
|
|
1369
1541
|
await engine.initialize();
|
|
1370
1542
|
var server = createServer(engine);
|
|
1371
1543
|
var transport = new StdioServerTransport();
|
|
1544
|
+
var latestVersion = await checkForUpdate();
|
|
1545
|
+
printBanner(defaultProjectId, latestVersion);
|
|
1372
1546
|
await server.connect(transport);
|
|
1373
1547
|
process.on("SIGINT", () => {
|
|
1374
1548
|
engine.close();
|