claude-memory-layer 1.0.7 → 1.0.8
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/.claude/settings.local.json +4 -1
- package/.history/package_20260201192048.json +47 -0
- package/dist/cli/index.js +569 -39
- package/dist/cli/index.js.map +4 -4
- package/dist/core/index.js +192 -5
- package/dist/core/index.js.map +4 -4
- package/dist/hooks/session-end.js +262 -18
- package/dist/hooks/session-end.js.map +4 -4
- package/dist/hooks/session-start.js +262 -18
- package/dist/hooks/session-start.js.map +4 -4
- package/dist/hooks/stop.js +262 -18
- package/dist/hooks/stop.js.map +4 -4
- package/dist/hooks/user-prompt-submit.js +262 -18
- package/dist/hooks/user-prompt-submit.js.map +4 -4
- package/dist/server/api/index.js +404 -39
- package/dist/server/api/index.js.map +4 -4
- package/dist/server/index.js +413 -46
- package/dist/server/index.js.map +4 -4
- package/dist/services/memory-service.js +269 -18
- package/dist/services/memory-service.js.map +4 -4
- package/dist/ui/index.html +495 -15
- package/package.json +2 -1
- package/scripts/build.ts +3 -2
- package/src/cli/index.ts +226 -0
- package/src/core/db-wrapper.ts +8 -1
- package/src/core/event-store.ts +52 -3
- package/src/core/graduation-worker.ts +171 -0
- package/src/core/graduation.ts +15 -2
- package/src/core/index.ts +1 -0
- package/src/core/retriever.ts +18 -0
- package/src/server/api/citations.ts +7 -3
- package/src/server/api/events.ts +7 -3
- package/src/server/api/search.ts +7 -3
- package/src/server/api/sessions.ts +7 -3
- package/src/server/api/stats.ts +129 -12
- package/src/server/index.ts +18 -9
- package/src/services/memory-service.ts +107 -19
- package/src/ui/index.html +495 -15
package/dist/cli/index.js
CHANGED
|
@@ -9,6 +9,9 @@ const __dirname = dirname(__filename);
|
|
|
9
9
|
// src/cli/index.ts
|
|
10
10
|
import { Command } from "commander";
|
|
11
11
|
import { exec } from "child_process";
|
|
12
|
+
import * as fs4 from "fs";
|
|
13
|
+
import * as path4 from "path";
|
|
14
|
+
import * as os3 from "os";
|
|
12
15
|
|
|
13
16
|
// src/services/memory-service.ts
|
|
14
17
|
import * as path from "path";
|
|
@@ -71,8 +74,11 @@ function toDate(value) {
|
|
|
71
74
|
return new Date(value);
|
|
72
75
|
return new Date(String(value));
|
|
73
76
|
}
|
|
74
|
-
function createDatabase(
|
|
75
|
-
|
|
77
|
+
function createDatabase(path5, options) {
|
|
78
|
+
if (options?.readOnly) {
|
|
79
|
+
return new duckdb.Database(path5, { access_mode: "READ_ONLY" });
|
|
80
|
+
}
|
|
81
|
+
return new duckdb.Database(path5);
|
|
76
82
|
}
|
|
77
83
|
function dbRun(db, sql, params = []) {
|
|
78
84
|
return new Promise((resolve2, reject) => {
|
|
@@ -125,18 +131,24 @@ function dbClose(db) {
|
|
|
125
131
|
|
|
126
132
|
// src/core/event-store.ts
|
|
127
133
|
var EventStore = class {
|
|
128
|
-
constructor(dbPath) {
|
|
134
|
+
constructor(dbPath, options) {
|
|
129
135
|
this.dbPath = dbPath;
|
|
130
|
-
this.
|
|
136
|
+
this.readOnly = options?.readOnly ?? false;
|
|
137
|
+
this.db = createDatabase(dbPath, { readOnly: this.readOnly });
|
|
131
138
|
}
|
|
132
139
|
db;
|
|
133
140
|
initialized = false;
|
|
141
|
+
readOnly;
|
|
134
142
|
/**
|
|
135
143
|
* Initialize database schema
|
|
136
144
|
*/
|
|
137
145
|
async initialize() {
|
|
138
146
|
if (this.initialized)
|
|
139
147
|
return;
|
|
148
|
+
if (this.readOnly) {
|
|
149
|
+
this.initialized = true;
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
140
152
|
await dbRun(this.db, `
|
|
141
153
|
CREATE TABLE IF NOT EXISTS events (
|
|
142
154
|
id VARCHAR PRIMARY KEY,
|
|
@@ -613,6 +625,36 @@ var EventStore = class {
|
|
|
613
625
|
);
|
|
614
626
|
return rows;
|
|
615
627
|
}
|
|
628
|
+
/**
|
|
629
|
+
* Get events by memory level
|
|
630
|
+
*/
|
|
631
|
+
async getEventsByLevel(level, options) {
|
|
632
|
+
await this.initialize();
|
|
633
|
+
const limit = options?.limit || 50;
|
|
634
|
+
const offset = options?.offset || 0;
|
|
635
|
+
const rows = await dbAll(
|
|
636
|
+
this.db,
|
|
637
|
+
`SELECT e.* FROM events e
|
|
638
|
+
INNER JOIN memory_levels ml ON e.id = ml.event_id
|
|
639
|
+
WHERE ml.level = ?
|
|
640
|
+
ORDER BY e.timestamp DESC
|
|
641
|
+
LIMIT ? OFFSET ?`,
|
|
642
|
+
[level, limit, offset]
|
|
643
|
+
);
|
|
644
|
+
return rows.map((row) => this.rowToEvent(row));
|
|
645
|
+
}
|
|
646
|
+
/**
|
|
647
|
+
* Get memory level for a specific event
|
|
648
|
+
*/
|
|
649
|
+
async getEventLevel(eventId) {
|
|
650
|
+
await this.initialize();
|
|
651
|
+
const rows = await dbAll(
|
|
652
|
+
this.db,
|
|
653
|
+
`SELECT level FROM memory_levels WHERE event_id = ?`,
|
|
654
|
+
[eventId]
|
|
655
|
+
);
|
|
656
|
+
return rows.length > 0 ? rows[0].level : null;
|
|
657
|
+
}
|
|
616
658
|
// ============================================================
|
|
617
659
|
// Endless Mode Helper Methods
|
|
618
660
|
// ============================================================
|
|
@@ -1195,6 +1237,7 @@ var Retriever = class {
|
|
|
1195
1237
|
matcher;
|
|
1196
1238
|
sharedStore;
|
|
1197
1239
|
sharedVectorStore;
|
|
1240
|
+
graduation;
|
|
1198
1241
|
constructor(eventStore, vectorStore, embedder, matcher, sharedOptions) {
|
|
1199
1242
|
this.eventStore = eventStore;
|
|
1200
1243
|
this.vectorStore = vectorStore;
|
|
@@ -1203,6 +1246,12 @@ var Retriever = class {
|
|
|
1203
1246
|
this.sharedStore = sharedOptions?.sharedStore;
|
|
1204
1247
|
this.sharedVectorStore = sharedOptions?.sharedVectorStore;
|
|
1205
1248
|
}
|
|
1249
|
+
/**
|
|
1250
|
+
* Set graduation pipeline for access tracking
|
|
1251
|
+
*/
|
|
1252
|
+
setGraduationPipeline(graduation) {
|
|
1253
|
+
this.graduation = graduation;
|
|
1254
|
+
}
|
|
1206
1255
|
/**
|
|
1207
1256
|
* Set shared stores after construction
|
|
1208
1257
|
*/
|
|
@@ -1325,6 +1374,13 @@ var Retriever = class {
|
|
|
1325
1374
|
const event = await this.eventStore.getEvent(result.eventId);
|
|
1326
1375
|
if (!event)
|
|
1327
1376
|
continue;
|
|
1377
|
+
if (this.graduation) {
|
|
1378
|
+
this.graduation.recordAccess(
|
|
1379
|
+
event.id,
|
|
1380
|
+
options.sessionId || "unknown",
|
|
1381
|
+
result.score
|
|
1382
|
+
);
|
|
1383
|
+
}
|
|
1328
1384
|
let sessionContext;
|
|
1329
1385
|
if (options.includeSessionContext) {
|
|
1330
1386
|
sessionContext = await this.getSessionContext(event.sessionId, event.id);
|
|
@@ -1446,15 +1502,26 @@ var GraduationPipeline = class {
|
|
|
1446
1502
|
L3toL4: { ...DEFAULT_CRITERIA.L3toL4, ...criteria.L3toL4 }
|
|
1447
1503
|
};
|
|
1448
1504
|
}
|
|
1505
|
+
// Track which sessions have accessed each event
|
|
1506
|
+
sessionAccesses = /* @__PURE__ */ new Map();
|
|
1449
1507
|
/**
|
|
1450
1508
|
* Record an access to an event (used for graduation scoring)
|
|
1451
1509
|
*/
|
|
1452
1510
|
recordAccess(eventId, fromSessionId, confidence = 1) {
|
|
1453
1511
|
const existing = this.metrics.get(eventId);
|
|
1512
|
+
if (!this.sessionAccesses.has(eventId)) {
|
|
1513
|
+
this.sessionAccesses.set(eventId, /* @__PURE__ */ new Set());
|
|
1514
|
+
}
|
|
1515
|
+
const sessions = this.sessionAccesses.get(eventId);
|
|
1516
|
+
const isNewSession = !sessions.has(fromSessionId);
|
|
1517
|
+
sessions.add(fromSessionId);
|
|
1454
1518
|
if (existing) {
|
|
1455
1519
|
existing.accessCount++;
|
|
1456
1520
|
existing.lastAccessed = /* @__PURE__ */ new Date();
|
|
1457
1521
|
existing.confidence = Math.max(existing.confidence, confidence);
|
|
1522
|
+
if (isNewSession && sessions.size > 1) {
|
|
1523
|
+
existing.crossSessionRefs = sessions.size - 1;
|
|
1524
|
+
}
|
|
1458
1525
|
} else {
|
|
1459
1526
|
this.metrics.set(eventId, {
|
|
1460
1527
|
eventId,
|
|
@@ -3342,6 +3409,127 @@ function createContinuityManager(eventStore, config) {
|
|
|
3342
3409
|
return new ContinuityManager(eventStore, config);
|
|
3343
3410
|
}
|
|
3344
3411
|
|
|
3412
|
+
// src/core/graduation-worker.ts
|
|
3413
|
+
var DEFAULT_CONFIG4 = {
|
|
3414
|
+
evaluationIntervalMs: 3e5,
|
|
3415
|
+
// 5 minutes
|
|
3416
|
+
batchSize: 50,
|
|
3417
|
+
cooldownMs: 36e5
|
|
3418
|
+
// 1 hour cooldown between evaluations
|
|
3419
|
+
};
|
|
3420
|
+
var GraduationWorker = class {
|
|
3421
|
+
constructor(eventStore, graduation, config = DEFAULT_CONFIG4) {
|
|
3422
|
+
this.eventStore = eventStore;
|
|
3423
|
+
this.graduation = graduation;
|
|
3424
|
+
this.config = config;
|
|
3425
|
+
}
|
|
3426
|
+
running = false;
|
|
3427
|
+
timeout = null;
|
|
3428
|
+
lastEvaluated = /* @__PURE__ */ new Map();
|
|
3429
|
+
/**
|
|
3430
|
+
* Start the graduation worker
|
|
3431
|
+
*/
|
|
3432
|
+
start() {
|
|
3433
|
+
if (this.running)
|
|
3434
|
+
return;
|
|
3435
|
+
this.running = true;
|
|
3436
|
+
this.scheduleNext();
|
|
3437
|
+
}
|
|
3438
|
+
/**
|
|
3439
|
+
* Stop the graduation worker
|
|
3440
|
+
*/
|
|
3441
|
+
stop() {
|
|
3442
|
+
this.running = false;
|
|
3443
|
+
if (this.timeout) {
|
|
3444
|
+
clearTimeout(this.timeout);
|
|
3445
|
+
this.timeout = null;
|
|
3446
|
+
}
|
|
3447
|
+
}
|
|
3448
|
+
/**
|
|
3449
|
+
* Check if currently running
|
|
3450
|
+
*/
|
|
3451
|
+
isRunning() {
|
|
3452
|
+
return this.running;
|
|
3453
|
+
}
|
|
3454
|
+
/**
|
|
3455
|
+
* Force a graduation evaluation run
|
|
3456
|
+
*/
|
|
3457
|
+
async forceRun() {
|
|
3458
|
+
return await this.runGraduation();
|
|
3459
|
+
}
|
|
3460
|
+
/**
|
|
3461
|
+
* Schedule the next graduation check
|
|
3462
|
+
*/
|
|
3463
|
+
scheduleNext() {
|
|
3464
|
+
if (!this.running)
|
|
3465
|
+
return;
|
|
3466
|
+
this.timeout = setTimeout(
|
|
3467
|
+
() => this.run(),
|
|
3468
|
+
this.config.evaluationIntervalMs
|
|
3469
|
+
);
|
|
3470
|
+
}
|
|
3471
|
+
/**
|
|
3472
|
+
* Run graduation evaluation
|
|
3473
|
+
*/
|
|
3474
|
+
async run() {
|
|
3475
|
+
if (!this.running)
|
|
3476
|
+
return;
|
|
3477
|
+
try {
|
|
3478
|
+
await this.runGraduation();
|
|
3479
|
+
} catch (error) {
|
|
3480
|
+
console.error("Graduation error:", error);
|
|
3481
|
+
}
|
|
3482
|
+
this.scheduleNext();
|
|
3483
|
+
}
|
|
3484
|
+
/**
|
|
3485
|
+
* Perform graduation evaluation across all levels
|
|
3486
|
+
*/
|
|
3487
|
+
async runGraduation() {
|
|
3488
|
+
const result = {
|
|
3489
|
+
evaluated: 0,
|
|
3490
|
+
graduated: 0,
|
|
3491
|
+
byLevel: {}
|
|
3492
|
+
};
|
|
3493
|
+
const levels = ["L0", "L1", "L2", "L3"];
|
|
3494
|
+
const now = Date.now();
|
|
3495
|
+
for (const level of levels) {
|
|
3496
|
+
const events = await this.eventStore.getEventsByLevel(level, {
|
|
3497
|
+
limit: this.config.batchSize
|
|
3498
|
+
});
|
|
3499
|
+
let levelGraduated = 0;
|
|
3500
|
+
for (const event of events) {
|
|
3501
|
+
const lastEval = this.lastEvaluated.get(event.id);
|
|
3502
|
+
if (lastEval && now - lastEval < this.config.cooldownMs) {
|
|
3503
|
+
continue;
|
|
3504
|
+
}
|
|
3505
|
+
result.evaluated++;
|
|
3506
|
+
this.lastEvaluated.set(event.id, now);
|
|
3507
|
+
const gradResult = await this.graduation.evaluateGraduation(event.id, level);
|
|
3508
|
+
if (gradResult.success) {
|
|
3509
|
+
result.graduated++;
|
|
3510
|
+
levelGraduated++;
|
|
3511
|
+
}
|
|
3512
|
+
}
|
|
3513
|
+
if (levelGraduated > 0) {
|
|
3514
|
+
result.byLevel[level] = levelGraduated;
|
|
3515
|
+
}
|
|
3516
|
+
}
|
|
3517
|
+
if (this.lastEvaluated.size > 1e3) {
|
|
3518
|
+
const entries = Array.from(this.lastEvaluated.entries());
|
|
3519
|
+
entries.sort((a, b) => b[1] - a[1]);
|
|
3520
|
+
this.lastEvaluated = new Map(entries.slice(0, 1e3));
|
|
3521
|
+
}
|
|
3522
|
+
return result;
|
|
3523
|
+
}
|
|
3524
|
+
};
|
|
3525
|
+
function createGraduationWorker(eventStore, graduation, config) {
|
|
3526
|
+
return new GraduationWorker(
|
|
3527
|
+
eventStore,
|
|
3528
|
+
graduation,
|
|
3529
|
+
{ ...DEFAULT_CONFIG4, ...config }
|
|
3530
|
+
);
|
|
3531
|
+
}
|
|
3532
|
+
|
|
3345
3533
|
// src/services/memory-service.ts
|
|
3346
3534
|
function normalizePath(projectPath) {
|
|
3347
3535
|
const expanded = projectPath.startsWith("~") ? path.join(os.homedir(), projectPath.slice(1)) : projectPath;
|
|
@@ -3369,6 +3557,7 @@ var MemoryService = class {
|
|
|
3369
3557
|
retriever;
|
|
3370
3558
|
graduation;
|
|
3371
3559
|
vectorWorker = null;
|
|
3560
|
+
graduationWorker = null;
|
|
3372
3561
|
initialized = false;
|
|
3373
3562
|
// Endless Mode components
|
|
3374
3563
|
workingSetStore = null;
|
|
@@ -3383,14 +3572,16 @@ var MemoryService = class {
|
|
|
3383
3572
|
sharedPromoter = null;
|
|
3384
3573
|
sharedStoreConfig = null;
|
|
3385
3574
|
projectHash = null;
|
|
3575
|
+
readOnly;
|
|
3386
3576
|
constructor(config) {
|
|
3387
3577
|
const storagePath = this.expandPath(config.storagePath);
|
|
3388
|
-
|
|
3578
|
+
this.readOnly = config.readOnly ?? false;
|
|
3579
|
+
if (!this.readOnly && !fs.existsSync(storagePath)) {
|
|
3389
3580
|
fs.mkdirSync(storagePath, { recursive: true });
|
|
3390
3581
|
}
|
|
3391
3582
|
this.projectHash = config.projectHash || null;
|
|
3392
3583
|
this.sharedStoreConfig = config.sharedStoreConfig ?? { enabled: true };
|
|
3393
|
-
this.eventStore = new EventStore(path.join(storagePath, "events.duckdb"));
|
|
3584
|
+
this.eventStore = new EventStore(path.join(storagePath, "events.duckdb"), { readOnly: this.readOnly });
|
|
3394
3585
|
this.vectorStore = new VectorStore(path.join(storagePath, "vectors"));
|
|
3395
3586
|
this.embedder = config.embeddingModel ? new Embedder(config.embeddingModel) : getDefaultEmbedder();
|
|
3396
3587
|
this.matcher = getDefaultMatcher();
|
|
@@ -3411,19 +3602,27 @@ var MemoryService = class {
|
|
|
3411
3602
|
await this.eventStore.initialize();
|
|
3412
3603
|
await this.vectorStore.initialize();
|
|
3413
3604
|
await this.embedder.initialize();
|
|
3414
|
-
this.
|
|
3415
|
-
this.
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
this.
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3605
|
+
if (!this.readOnly) {
|
|
3606
|
+
this.vectorWorker = createVectorWorker(
|
|
3607
|
+
this.eventStore,
|
|
3608
|
+
this.vectorStore,
|
|
3609
|
+
this.embedder
|
|
3610
|
+
);
|
|
3611
|
+
this.vectorWorker.start();
|
|
3612
|
+
this.retriever.setGraduationPipeline(this.graduation);
|
|
3613
|
+
this.graduationWorker = createGraduationWorker(
|
|
3614
|
+
this.eventStore,
|
|
3615
|
+
this.graduation
|
|
3616
|
+
);
|
|
3617
|
+
this.graduationWorker.start();
|
|
3618
|
+
const savedMode = await this.eventStore.getEndlessConfig("mode");
|
|
3619
|
+
if (savedMode === "endless") {
|
|
3620
|
+
this.endlessMode = "endless";
|
|
3621
|
+
await this.initializeEndlessMode();
|
|
3622
|
+
}
|
|
3623
|
+
if (this.sharedStoreConfig?.enabled !== false) {
|
|
3624
|
+
await this.initializeSharedStore();
|
|
3625
|
+
}
|
|
3427
3626
|
}
|
|
3428
3627
|
this.initialized = true;
|
|
3429
3628
|
}
|
|
@@ -3604,6 +3803,20 @@ var MemoryService = class {
|
|
|
3604
3803
|
}
|
|
3605
3804
|
return 0;
|
|
3606
3805
|
}
|
|
3806
|
+
/**
|
|
3807
|
+
* Get events by memory level
|
|
3808
|
+
*/
|
|
3809
|
+
async getEventsByLevel(level, options) {
|
|
3810
|
+
await this.initialize();
|
|
3811
|
+
return this.eventStore.getEventsByLevel(level, options);
|
|
3812
|
+
}
|
|
3813
|
+
/**
|
|
3814
|
+
* Get memory level for a specific event
|
|
3815
|
+
*/
|
|
3816
|
+
async getEventLevel(eventId) {
|
|
3817
|
+
await this.initialize();
|
|
3818
|
+
return this.eventStore.getEventLevel(eventId);
|
|
3819
|
+
}
|
|
3607
3820
|
/**
|
|
3608
3821
|
* Format retrieval results as context for Claude
|
|
3609
3822
|
*/
|
|
@@ -3786,6 +3999,22 @@ var MemoryService = class {
|
|
|
3786
3999
|
return [];
|
|
3787
4000
|
return this.consolidatedStore.getAll({ limit });
|
|
3788
4001
|
}
|
|
4002
|
+
/**
|
|
4003
|
+
* Get most accessed consolidated memories
|
|
4004
|
+
*/
|
|
4005
|
+
async getMostAccessedMemories(limit = 10) {
|
|
4006
|
+
if (!this.consolidatedStore)
|
|
4007
|
+
return [];
|
|
4008
|
+
return this.consolidatedStore.getMostAccessed(limit);
|
|
4009
|
+
}
|
|
4010
|
+
/**
|
|
4011
|
+
* Mark a consolidated memory as accessed
|
|
4012
|
+
*/
|
|
4013
|
+
async markMemoryAccessed(memoryId) {
|
|
4014
|
+
if (!this.consolidatedStore)
|
|
4015
|
+
return;
|
|
4016
|
+
await this.consolidatedStore.markAccessed(memoryId);
|
|
4017
|
+
}
|
|
3789
4018
|
/**
|
|
3790
4019
|
* Calculate continuity score for current context
|
|
3791
4020
|
*/
|
|
@@ -3873,10 +4102,28 @@ var MemoryService = class {
|
|
|
3873
4102
|
}
|
|
3874
4103
|
return parts.join("\n");
|
|
3875
4104
|
}
|
|
4105
|
+
/**
|
|
4106
|
+
* Force a graduation evaluation run
|
|
4107
|
+
*/
|
|
4108
|
+
async forceGraduation() {
|
|
4109
|
+
if (!this.graduationWorker) {
|
|
4110
|
+
return { evaluated: 0, graduated: 0, byLevel: {} };
|
|
4111
|
+
}
|
|
4112
|
+
return this.graduationWorker.forceRun();
|
|
4113
|
+
}
|
|
4114
|
+
/**
|
|
4115
|
+
* Record access to a memory event (for graduation scoring)
|
|
4116
|
+
*/
|
|
4117
|
+
recordMemoryAccess(eventId, sessionId, confidence = 1) {
|
|
4118
|
+
this.graduation.recordAccess(eventId, sessionId, confidence);
|
|
4119
|
+
}
|
|
3876
4120
|
/**
|
|
3877
4121
|
* Shutdown service
|
|
3878
4122
|
*/
|
|
3879
4123
|
async shutdown() {
|
|
4124
|
+
if (this.graduationWorker) {
|
|
4125
|
+
this.graduationWorker.stop();
|
|
4126
|
+
}
|
|
3880
4127
|
if (this.consolidationWorker) {
|
|
3881
4128
|
this.consolidationWorker.stop();
|
|
3882
4129
|
}
|
|
@@ -3908,6 +4155,12 @@ function getDefaultMemoryService() {
|
|
|
3908
4155
|
}
|
|
3909
4156
|
return serviceCache.get(GLOBAL_KEY);
|
|
3910
4157
|
}
|
|
4158
|
+
function getReadOnlyMemoryService() {
|
|
4159
|
+
return new MemoryService({
|
|
4160
|
+
storagePath: "~/.claude-code/memory",
|
|
4161
|
+
readOnly: true
|
|
4162
|
+
});
|
|
4163
|
+
}
|
|
3911
4164
|
function getMemoryServiceForProject(projectPath, sharedStoreConfig) {
|
|
3912
4165
|
const hash = hashProjectPath(projectPath);
|
|
3913
4166
|
if (!serviceCache.has(hash)) {
|
|
@@ -4163,7 +4416,8 @@ function createSessionHistoryImporter(memoryService) {
|
|
|
4163
4416
|
import { Hono as Hono7 } from "hono";
|
|
4164
4417
|
import { cors } from "hono/cors";
|
|
4165
4418
|
import { logger } from "hono/logger";
|
|
4166
|
-
import {
|
|
4419
|
+
import { serve } from "@hono/node-server";
|
|
4420
|
+
import { serveStatic } from "@hono/node-server/serve-static";
|
|
4167
4421
|
import * as path3 from "path";
|
|
4168
4422
|
import * as fs3 from "fs";
|
|
4169
4423
|
|
|
@@ -4176,8 +4430,8 @@ var sessionsRouter = new Hono();
|
|
|
4176
4430
|
sessionsRouter.get("/", async (c) => {
|
|
4177
4431
|
const page = parseInt(c.req.query("page") || "1", 10);
|
|
4178
4432
|
const pageSize = parseInt(c.req.query("pageSize") || "20", 10);
|
|
4433
|
+
const memoryService = getReadOnlyMemoryService();
|
|
4179
4434
|
try {
|
|
4180
|
-
const memoryService = getDefaultMemoryService();
|
|
4181
4435
|
await memoryService.initialize();
|
|
4182
4436
|
const recentEvents = await memoryService.getRecentEvents(1e3);
|
|
4183
4437
|
const sessionMap = /* @__PURE__ */ new Map();
|
|
@@ -4214,12 +4468,14 @@ sessionsRouter.get("/", async (c) => {
|
|
|
4214
4468
|
});
|
|
4215
4469
|
} catch (error) {
|
|
4216
4470
|
return c.json({ error: error.message }, 500);
|
|
4471
|
+
} finally {
|
|
4472
|
+
await memoryService.shutdown();
|
|
4217
4473
|
}
|
|
4218
4474
|
});
|
|
4219
4475
|
sessionsRouter.get("/:id", async (c) => {
|
|
4220
4476
|
const { id } = c.req.param();
|
|
4477
|
+
const memoryService = getReadOnlyMemoryService();
|
|
4221
4478
|
try {
|
|
4222
|
-
const memoryService = getDefaultMemoryService();
|
|
4223
4479
|
await memoryService.initialize();
|
|
4224
4480
|
const events = await memoryService.getSessionHistory(id);
|
|
4225
4481
|
if (events.length === 0) {
|
|
@@ -4248,6 +4504,8 @@ sessionsRouter.get("/:id", async (c) => {
|
|
|
4248
4504
|
});
|
|
4249
4505
|
} catch (error) {
|
|
4250
4506
|
return c.json({ error: error.message }, 500);
|
|
4507
|
+
} finally {
|
|
4508
|
+
await memoryService.shutdown();
|
|
4251
4509
|
}
|
|
4252
4510
|
});
|
|
4253
4511
|
|
|
@@ -4259,8 +4517,8 @@ eventsRouter.get("/", async (c) => {
|
|
|
4259
4517
|
const eventType = c.req.query("type");
|
|
4260
4518
|
const limit = parseInt(c.req.query("limit") || "100", 10);
|
|
4261
4519
|
const offset = parseInt(c.req.query("offset") || "0", 10);
|
|
4520
|
+
const memoryService = getReadOnlyMemoryService();
|
|
4262
4521
|
try {
|
|
4263
|
-
const memoryService = getDefaultMemoryService();
|
|
4264
4522
|
await memoryService.initialize();
|
|
4265
4523
|
let events = await memoryService.getRecentEvents(limit + offset + 1e3);
|
|
4266
4524
|
if (sessionId) {
|
|
@@ -4287,12 +4545,14 @@ eventsRouter.get("/", async (c) => {
|
|
|
4287
4545
|
});
|
|
4288
4546
|
} catch (error) {
|
|
4289
4547
|
return c.json({ error: error.message }, 500);
|
|
4548
|
+
} finally {
|
|
4549
|
+
await memoryService.shutdown();
|
|
4290
4550
|
}
|
|
4291
4551
|
});
|
|
4292
4552
|
eventsRouter.get("/:id", async (c) => {
|
|
4293
4553
|
const { id } = c.req.param();
|
|
4554
|
+
const memoryService = getReadOnlyMemoryService();
|
|
4294
4555
|
try {
|
|
4295
|
-
const memoryService = getDefaultMemoryService();
|
|
4296
4556
|
await memoryService.initialize();
|
|
4297
4557
|
const recentEvents = await memoryService.getRecentEvents(1e4);
|
|
4298
4558
|
const event = recentEvents.find((e) => e.id === id);
|
|
@@ -4322,6 +4582,8 @@ eventsRouter.get("/:id", async (c) => {
|
|
|
4322
4582
|
});
|
|
4323
4583
|
} catch (error) {
|
|
4324
4584
|
return c.json({ error: error.message }, 500);
|
|
4585
|
+
} finally {
|
|
4586
|
+
await memoryService.shutdown();
|
|
4325
4587
|
}
|
|
4326
4588
|
});
|
|
4327
4589
|
|
|
@@ -4329,12 +4591,12 @@ eventsRouter.get("/:id", async (c) => {
|
|
|
4329
4591
|
import { Hono as Hono3 } from "hono";
|
|
4330
4592
|
var searchRouter = new Hono3();
|
|
4331
4593
|
searchRouter.post("/", async (c) => {
|
|
4594
|
+
const memoryService = getReadOnlyMemoryService();
|
|
4332
4595
|
try {
|
|
4333
4596
|
const body = await c.req.json();
|
|
4334
4597
|
if (!body.query) {
|
|
4335
4598
|
return c.json({ error: "Query is required" }, 400);
|
|
4336
4599
|
}
|
|
4337
|
-
const memoryService = getDefaultMemoryService();
|
|
4338
4600
|
await memoryService.initialize();
|
|
4339
4601
|
const startTime = Date.now();
|
|
4340
4602
|
const result = await memoryService.retrieveMemories(body.query, {
|
|
@@ -4363,6 +4625,8 @@ searchRouter.post("/", async (c) => {
|
|
|
4363
4625
|
});
|
|
4364
4626
|
} catch (error) {
|
|
4365
4627
|
return c.json({ error: error.message }, 500);
|
|
4628
|
+
} finally {
|
|
4629
|
+
await memoryService.shutdown();
|
|
4366
4630
|
}
|
|
4367
4631
|
});
|
|
4368
4632
|
searchRouter.get("/", async (c) => {
|
|
@@ -4371,8 +4635,8 @@ searchRouter.get("/", async (c) => {
|
|
|
4371
4635
|
return c.json({ error: 'Query parameter "q" is required' }, 400);
|
|
4372
4636
|
}
|
|
4373
4637
|
const topK = parseInt(c.req.query("topK") || "5", 10);
|
|
4638
|
+
const memoryService = getReadOnlyMemoryService();
|
|
4374
4639
|
try {
|
|
4375
|
-
const memoryService = getDefaultMemoryService();
|
|
4376
4640
|
await memoryService.initialize();
|
|
4377
4641
|
const result = await memoryService.retrieveMemories(query, { topK });
|
|
4378
4642
|
return c.json({
|
|
@@ -4390,6 +4654,8 @@ searchRouter.get("/", async (c) => {
|
|
|
4390
4654
|
});
|
|
4391
4655
|
} catch (error) {
|
|
4392
4656
|
return c.json({ error: error.message }, 500);
|
|
4657
|
+
} finally {
|
|
4658
|
+
await memoryService.shutdown();
|
|
4393
4659
|
}
|
|
4394
4660
|
});
|
|
4395
4661
|
|
|
@@ -4397,8 +4663,8 @@ searchRouter.get("/", async (c) => {
|
|
|
4397
4663
|
import { Hono as Hono4 } from "hono";
|
|
4398
4664
|
var statsRouter = new Hono4();
|
|
4399
4665
|
statsRouter.get("/shared", async (c) => {
|
|
4666
|
+
const memoryService = getReadOnlyMemoryService();
|
|
4400
4667
|
try {
|
|
4401
|
-
const memoryService = getDefaultMemoryService();
|
|
4402
4668
|
await memoryService.initialize();
|
|
4403
4669
|
const sharedStats = await memoryService.getSharedStoreStats();
|
|
4404
4670
|
return c.json({
|
|
@@ -4416,12 +4682,14 @@ statsRouter.get("/shared", async (c) => {
|
|
|
4416
4682
|
totalUsageCount: 0,
|
|
4417
4683
|
lastUpdated: null
|
|
4418
4684
|
});
|
|
4685
|
+
} finally {
|
|
4686
|
+
await memoryService.shutdown();
|
|
4419
4687
|
}
|
|
4420
4688
|
});
|
|
4421
4689
|
statsRouter.get("/endless", async (c) => {
|
|
4690
|
+
const projectPath = c.req.query("project") || process.cwd();
|
|
4691
|
+
const memoryService = getMemoryServiceForProject(projectPath);
|
|
4422
4692
|
try {
|
|
4423
|
-
const projectPath = c.req.query("project") || process.cwd();
|
|
4424
|
-
const memoryService = getMemoryServiceForProject(projectPath);
|
|
4425
4693
|
await memoryService.initialize();
|
|
4426
4694
|
const status = await memoryService.getEndlessModeStatus();
|
|
4427
4695
|
return c.json({
|
|
@@ -4439,11 +4707,48 @@ statsRouter.get("/endless", async (c) => {
|
|
|
4439
4707
|
consolidatedCount: 0,
|
|
4440
4708
|
lastConsolidation: null
|
|
4441
4709
|
});
|
|
4710
|
+
} finally {
|
|
4711
|
+
await memoryService.shutdown();
|
|
4712
|
+
}
|
|
4713
|
+
});
|
|
4714
|
+
statsRouter.get("/levels/:level", async (c) => {
|
|
4715
|
+
const { level } = c.req.param();
|
|
4716
|
+
const limit = parseInt(c.req.query("limit") || "20", 10);
|
|
4717
|
+
const offset = parseInt(c.req.query("offset") || "0", 10);
|
|
4718
|
+
const validLevels = ["L0", "L1", "L2", "L3", "L4"];
|
|
4719
|
+
if (!validLevels.includes(level)) {
|
|
4720
|
+
return c.json({ error: `Invalid level. Must be one of: ${validLevels.join(", ")}` }, 400);
|
|
4721
|
+
}
|
|
4722
|
+
const memoryService = getReadOnlyMemoryService();
|
|
4723
|
+
try {
|
|
4724
|
+
await memoryService.initialize();
|
|
4725
|
+
const events = await memoryService.getEventsByLevel(level, { limit, offset });
|
|
4726
|
+
const stats = await memoryService.getStats();
|
|
4727
|
+
const levelStat = stats.levelStats.find((s) => s.level === level);
|
|
4728
|
+
return c.json({
|
|
4729
|
+
level,
|
|
4730
|
+
events: events.map((e) => ({
|
|
4731
|
+
id: e.id,
|
|
4732
|
+
eventType: e.eventType,
|
|
4733
|
+
sessionId: e.sessionId,
|
|
4734
|
+
timestamp: e.timestamp.toISOString(),
|
|
4735
|
+
content: e.content.slice(0, 500) + (e.content.length > 500 ? "..." : ""),
|
|
4736
|
+
metadata: e.metadata
|
|
4737
|
+
})),
|
|
4738
|
+
total: levelStat?.count || 0,
|
|
4739
|
+
limit,
|
|
4740
|
+
offset,
|
|
4741
|
+
hasMore: events.length === limit
|
|
4742
|
+
});
|
|
4743
|
+
} catch (error) {
|
|
4744
|
+
return c.json({ error: error.message }, 500);
|
|
4745
|
+
} finally {
|
|
4746
|
+
await memoryService.shutdown();
|
|
4442
4747
|
}
|
|
4443
4748
|
});
|
|
4444
4749
|
statsRouter.get("/", async (c) => {
|
|
4750
|
+
const memoryService = getReadOnlyMemoryService();
|
|
4445
4751
|
try {
|
|
4446
|
-
const memoryService = getDefaultMemoryService();
|
|
4447
4752
|
await memoryService.initialize();
|
|
4448
4753
|
const stats = await memoryService.getStats();
|
|
4449
4754
|
const recentEvents = await memoryService.getRecentEvents(1e4);
|
|
@@ -4480,12 +4785,43 @@ statsRouter.get("/", async (c) => {
|
|
|
4480
4785
|
});
|
|
4481
4786
|
} catch (error) {
|
|
4482
4787
|
return c.json({ error: error.message }, 500);
|
|
4788
|
+
} finally {
|
|
4789
|
+
await memoryService.shutdown();
|
|
4790
|
+
}
|
|
4791
|
+
});
|
|
4792
|
+
statsRouter.get("/most-accessed", async (c) => {
|
|
4793
|
+
const limit = parseInt(c.req.query("limit") || "10", 10);
|
|
4794
|
+
const projectPath = c.req.query("project") || process.cwd();
|
|
4795
|
+
const memoryService = getMemoryServiceForProject(projectPath);
|
|
4796
|
+
try {
|
|
4797
|
+
await memoryService.initialize();
|
|
4798
|
+
const memories = await memoryService.getMostAccessedMemories(limit);
|
|
4799
|
+
return c.json({
|
|
4800
|
+
memories: memories.map((m) => ({
|
|
4801
|
+
memoryId: m.memoryId,
|
|
4802
|
+
summary: m.summary,
|
|
4803
|
+
topics: m.topics,
|
|
4804
|
+
accessCount: m.accessCount,
|
|
4805
|
+
lastAccessed: m.accessedAt?.toISOString() || null,
|
|
4806
|
+
confidence: m.confidence,
|
|
4807
|
+
createdAt: m.createdAt.toISOString()
|
|
4808
|
+
})),
|
|
4809
|
+
total: memories.length
|
|
4810
|
+
});
|
|
4811
|
+
} catch (error) {
|
|
4812
|
+
return c.json({
|
|
4813
|
+
memories: [],
|
|
4814
|
+
total: 0,
|
|
4815
|
+
error: error.message
|
|
4816
|
+
});
|
|
4817
|
+
} finally {
|
|
4818
|
+
await memoryService.shutdown();
|
|
4483
4819
|
}
|
|
4484
4820
|
});
|
|
4485
4821
|
statsRouter.get("/timeline", async (c) => {
|
|
4486
4822
|
const days = parseInt(c.req.query("days") || "7", 10);
|
|
4823
|
+
const memoryService = getReadOnlyMemoryService();
|
|
4487
4824
|
try {
|
|
4488
|
-
const memoryService = getDefaultMemoryService();
|
|
4489
4825
|
await memoryService.initialize();
|
|
4490
4826
|
const recentEvents = await memoryService.getRecentEvents(1e4);
|
|
4491
4827
|
const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1e3);
|
|
@@ -4510,8 +4846,46 @@ statsRouter.get("/timeline", async (c) => {
|
|
|
4510
4846
|
});
|
|
4511
4847
|
} catch (error) {
|
|
4512
4848
|
return c.json({ error: error.message }, 500);
|
|
4849
|
+
} finally {
|
|
4850
|
+
await memoryService.shutdown();
|
|
4851
|
+
}
|
|
4852
|
+
});
|
|
4853
|
+
statsRouter.post("/graduation/run", async (c) => {
|
|
4854
|
+
const memoryService = getReadOnlyMemoryService();
|
|
4855
|
+
try {
|
|
4856
|
+
await memoryService.initialize();
|
|
4857
|
+
const result = await memoryService.forceGraduation();
|
|
4858
|
+
return c.json({
|
|
4859
|
+
success: true,
|
|
4860
|
+
evaluated: result.evaluated,
|
|
4861
|
+
graduated: result.graduated,
|
|
4862
|
+
byLevel: result.byLevel
|
|
4863
|
+
});
|
|
4864
|
+
} catch (error) {
|
|
4865
|
+
return c.json({
|
|
4866
|
+
success: false,
|
|
4867
|
+
error: error.message
|
|
4868
|
+
}, 500);
|
|
4869
|
+
} finally {
|
|
4870
|
+
await memoryService.shutdown();
|
|
4513
4871
|
}
|
|
4514
4872
|
});
|
|
4873
|
+
statsRouter.get("/graduation", async (c) => {
|
|
4874
|
+
return c.json({
|
|
4875
|
+
criteria: {
|
|
4876
|
+
L0toL1: { minAccessCount: 1, minConfidence: 0.5, minCrossSessionRefs: 0, maxAgeDays: 30 },
|
|
4877
|
+
L1toL2: { minAccessCount: 3, minConfidence: 0.7, minCrossSessionRefs: 1, maxAgeDays: 60 },
|
|
4878
|
+
L2toL3: { minAccessCount: 5, minConfidence: 0.85, minCrossSessionRefs: 2, maxAgeDays: 90 },
|
|
4879
|
+
L3toL4: { minAccessCount: 10, minConfidence: 0.92, minCrossSessionRefs: 3, maxAgeDays: 180 }
|
|
4880
|
+
},
|
|
4881
|
+
description: {
|
|
4882
|
+
accessCount: "Number of times the memory was retrieved/referenced",
|
|
4883
|
+
confidence: "Match confidence score when retrieved (0.0-1.0)",
|
|
4884
|
+
crossSessionRefs: "Number of different sessions that referenced this memory",
|
|
4885
|
+
maxAgeDays: "Maximum days since last access (prevents stale promotion)"
|
|
4886
|
+
}
|
|
4887
|
+
});
|
|
4888
|
+
});
|
|
4515
4889
|
|
|
4516
4890
|
// src/server/api/citations.ts
|
|
4517
4891
|
import { Hono as Hono5 } from "hono";
|
|
@@ -4538,8 +4912,8 @@ var citationsRouter = new Hono5();
|
|
|
4538
4912
|
citationsRouter.get("/:id", async (c) => {
|
|
4539
4913
|
const { id } = c.req.param();
|
|
4540
4914
|
const citationId = parseCitationId(id) || id;
|
|
4915
|
+
const memoryService = getReadOnlyMemoryService();
|
|
4541
4916
|
try {
|
|
4542
|
-
const memoryService = getDefaultMemoryService();
|
|
4543
4917
|
await memoryService.initialize();
|
|
4544
4918
|
const recentEvents = await memoryService.getRecentEvents(1e4);
|
|
4545
4919
|
const event = recentEvents.find((e) => {
|
|
@@ -4565,13 +4939,15 @@ citationsRouter.get("/:id", async (c) => {
|
|
|
4565
4939
|
});
|
|
4566
4940
|
} catch (error) {
|
|
4567
4941
|
return c.json({ error: error.message }, 500);
|
|
4942
|
+
} finally {
|
|
4943
|
+
await memoryService.shutdown();
|
|
4568
4944
|
}
|
|
4569
4945
|
});
|
|
4570
4946
|
citationsRouter.get("/:id/related", async (c) => {
|
|
4571
4947
|
const { id } = c.req.param();
|
|
4572
4948
|
const citationId = parseCitationId(id) || id;
|
|
4949
|
+
const memoryService = getReadOnlyMemoryService();
|
|
4573
4950
|
try {
|
|
4574
|
-
const memoryService = getDefaultMemoryService();
|
|
4575
4951
|
await memoryService.initialize();
|
|
4576
4952
|
const recentEvents = await memoryService.getRecentEvents(1e4);
|
|
4577
4953
|
const event = recentEvents.find((e) => {
|
|
@@ -4601,6 +4977,8 @@ citationsRouter.get("/:id/related", async (c) => {
|
|
|
4601
4977
|
});
|
|
4602
4978
|
} catch (error) {
|
|
4603
4979
|
return c.json({ error: error.message }, 500);
|
|
4980
|
+
} finally {
|
|
4981
|
+
await memoryService.shutdown();
|
|
4604
4982
|
}
|
|
4605
4983
|
});
|
|
4606
4984
|
|
|
@@ -4613,7 +4991,7 @@ app.use("/*", cors());
|
|
|
4613
4991
|
app.use("/*", logger());
|
|
4614
4992
|
app.route("/api", apiRouter);
|
|
4615
4993
|
app.get("/health", (c) => c.json({ status: "ok", timestamp: (/* @__PURE__ */ new Date()).toISOString() }));
|
|
4616
|
-
var uiPath = path3.join(
|
|
4994
|
+
var uiPath = path3.join(__dirname, "../../dist/ui");
|
|
4617
4995
|
if (fs3.existsSync(uiPath)) {
|
|
4618
4996
|
app.use("/*", serveStatic({ root: uiPath }));
|
|
4619
4997
|
}
|
|
@@ -4629,17 +5007,17 @@ function startServer(port = 37777) {
|
|
|
4629
5007
|
if (serverInstance) {
|
|
4630
5008
|
return serverInstance;
|
|
4631
5009
|
}
|
|
4632
|
-
serverInstance =
|
|
4633
|
-
|
|
5010
|
+
serverInstance = serve({
|
|
5011
|
+
fetch: app.fetch,
|
|
4634
5012
|
port,
|
|
4635
|
-
|
|
5013
|
+
hostname: "127.0.0.1"
|
|
4636
5014
|
});
|
|
4637
5015
|
console.log(`\u{1F9E0} Code Memory viewer started at http://localhost:${port}`);
|
|
4638
5016
|
return serverInstance;
|
|
4639
5017
|
}
|
|
4640
5018
|
function stopServer() {
|
|
4641
5019
|
if (serverInstance) {
|
|
4642
|
-
serverInstance.
|
|
5020
|
+
serverInstance.close();
|
|
4643
5021
|
serverInstance = null;
|
|
4644
5022
|
}
|
|
4645
5023
|
}
|
|
@@ -4651,14 +5029,166 @@ async function isServerRunning(port = 37777) {
|
|
|
4651
5029
|
return false;
|
|
4652
5030
|
}
|
|
4653
5031
|
}
|
|
4654
|
-
|
|
5032
|
+
var isMainModule = process.argv[1]?.includes("server/index") || process.argv[1]?.endsWith("server.js");
|
|
5033
|
+
if (isMainModule) {
|
|
4655
5034
|
const port = parseInt(process.env.PORT || "37777", 10);
|
|
4656
5035
|
startServer(port);
|
|
4657
5036
|
}
|
|
4658
5037
|
|
|
4659
5038
|
// src/cli/index.ts
|
|
5039
|
+
var CLAUDE_SETTINGS_PATH = path4.join(os3.homedir(), ".claude", "settings.json");
|
|
5040
|
+
function getPluginPath() {
|
|
5041
|
+
const possiblePaths = [
|
|
5042
|
+
path4.join(__dirname, ".."),
|
|
5043
|
+
// When running from dist/cli
|
|
5044
|
+
path4.join(__dirname, "../..", "dist"),
|
|
5045
|
+
// When running from src
|
|
5046
|
+
path4.join(process.cwd(), "dist")
|
|
5047
|
+
// Current working directory
|
|
5048
|
+
];
|
|
5049
|
+
for (const p of possiblePaths) {
|
|
5050
|
+
const hooksPath = path4.join(p, "hooks", "user-prompt-submit.js");
|
|
5051
|
+
if (fs4.existsSync(hooksPath)) {
|
|
5052
|
+
return p;
|
|
5053
|
+
}
|
|
5054
|
+
}
|
|
5055
|
+
return path4.join(os3.homedir(), ".npm-global", "lib", "node_modules", "claude-memory-layer", "dist");
|
|
5056
|
+
}
|
|
5057
|
+
function loadClaudeSettings() {
|
|
5058
|
+
try {
|
|
5059
|
+
if (fs4.existsSync(CLAUDE_SETTINGS_PATH)) {
|
|
5060
|
+
const content = fs4.readFileSync(CLAUDE_SETTINGS_PATH, "utf-8");
|
|
5061
|
+
return JSON.parse(content);
|
|
5062
|
+
}
|
|
5063
|
+
} catch (error) {
|
|
5064
|
+
console.error("Warning: Could not read existing settings:", error);
|
|
5065
|
+
}
|
|
5066
|
+
return {};
|
|
5067
|
+
}
|
|
5068
|
+
function saveClaudeSettings(settings) {
|
|
5069
|
+
const dir = path4.dirname(CLAUDE_SETTINGS_PATH);
|
|
5070
|
+
if (!fs4.existsSync(dir)) {
|
|
5071
|
+
fs4.mkdirSync(dir, { recursive: true });
|
|
5072
|
+
}
|
|
5073
|
+
const tempPath = CLAUDE_SETTINGS_PATH + ".tmp";
|
|
5074
|
+
fs4.writeFileSync(tempPath, JSON.stringify(settings, null, 2));
|
|
5075
|
+
fs4.renameSync(tempPath, CLAUDE_SETTINGS_PATH);
|
|
5076
|
+
}
|
|
5077
|
+
function getHooksConfig(pluginPath) {
|
|
5078
|
+
return {
|
|
5079
|
+
UserPromptSubmit: [
|
|
5080
|
+
{
|
|
5081
|
+
matcher: "",
|
|
5082
|
+
hooks: [
|
|
5083
|
+
{
|
|
5084
|
+
type: "command",
|
|
5085
|
+
command: `node ${path4.join(pluginPath, "hooks", "user-prompt-submit.js")}`
|
|
5086
|
+
}
|
|
5087
|
+
]
|
|
5088
|
+
}
|
|
5089
|
+
],
|
|
5090
|
+
PostToolUse: [
|
|
5091
|
+
{
|
|
5092
|
+
matcher: "",
|
|
5093
|
+
hooks: [
|
|
5094
|
+
{
|
|
5095
|
+
type: "command",
|
|
5096
|
+
command: `node ${path4.join(pluginPath, "hooks", "post-tool-use.js")}`
|
|
5097
|
+
}
|
|
5098
|
+
]
|
|
5099
|
+
}
|
|
5100
|
+
]
|
|
5101
|
+
};
|
|
5102
|
+
}
|
|
4660
5103
|
var program = new Command();
|
|
4661
5104
|
program.name("claude-memory-layer").description("Claude Code Memory Plugin CLI").version("1.0.0");
|
|
5105
|
+
program.command("install").description("Install hooks into Claude Code settings").option("--path <path>", "Custom plugin path (defaults to auto-detect)").action(async (options) => {
|
|
5106
|
+
try {
|
|
5107
|
+
const pluginPath = options.path || getPluginPath();
|
|
5108
|
+
const userPromptHook = path4.join(pluginPath, "hooks", "user-prompt-submit.js");
|
|
5109
|
+
if (!fs4.existsSync(userPromptHook)) {
|
|
5110
|
+
console.error(`
|
|
5111
|
+
\u274C Hook files not found at: ${pluginPath}`);
|
|
5112
|
+
console.error(' Make sure you have built the plugin with "npm run build"');
|
|
5113
|
+
process.exit(1);
|
|
5114
|
+
}
|
|
5115
|
+
const settings = loadClaudeSettings();
|
|
5116
|
+
const newHooks = getHooksConfig(pluginPath);
|
|
5117
|
+
settings.hooks = {
|
|
5118
|
+
...settings.hooks,
|
|
5119
|
+
...newHooks
|
|
5120
|
+
};
|
|
5121
|
+
saveClaudeSettings(settings);
|
|
5122
|
+
console.log("\n\u2705 Claude Memory Layer installed!\n");
|
|
5123
|
+
console.log("Hooks registered:");
|
|
5124
|
+
console.log(" - UserPromptSubmit: Memory retrieval on user input");
|
|
5125
|
+
console.log(" - PostToolUse: Store tool observations\n");
|
|
5126
|
+
console.log("Plugin path:", pluginPath);
|
|
5127
|
+
console.log("\n\u26A0\uFE0F Restart Claude Code for changes to take effect.\n");
|
|
5128
|
+
console.log("Commands:");
|
|
5129
|
+
console.log(" claude-memory-layer dashboard - Open web dashboard");
|
|
5130
|
+
console.log(" claude-memory-layer search - Search memories");
|
|
5131
|
+
console.log(" claude-memory-layer stats - View statistics");
|
|
5132
|
+
console.log(" claude-memory-layer uninstall - Remove hooks\n");
|
|
5133
|
+
} catch (error) {
|
|
5134
|
+
console.error("Install failed:", error);
|
|
5135
|
+
process.exit(1);
|
|
5136
|
+
}
|
|
5137
|
+
});
|
|
5138
|
+
program.command("uninstall").description("Remove hooks from Claude Code settings").action(async () => {
|
|
5139
|
+
try {
|
|
5140
|
+
const settings = loadClaudeSettings();
|
|
5141
|
+
if (!settings.hooks) {
|
|
5142
|
+
console.log("\n\u{1F4CB} No hooks installed.\n");
|
|
5143
|
+
return;
|
|
5144
|
+
}
|
|
5145
|
+
delete settings.hooks.UserPromptSubmit;
|
|
5146
|
+
delete settings.hooks.PostToolUse;
|
|
5147
|
+
if (Object.keys(settings.hooks).length === 0) {
|
|
5148
|
+
delete settings.hooks;
|
|
5149
|
+
}
|
|
5150
|
+
saveClaudeSettings(settings);
|
|
5151
|
+
console.log("\n\u2705 Claude Memory Layer uninstalled!\n");
|
|
5152
|
+
console.log("Hooks removed from Claude Code settings.");
|
|
5153
|
+
console.log("Your memory data is preserved and can be accessed with:");
|
|
5154
|
+
console.log(" claude-memory-layer dashboard\n");
|
|
5155
|
+
console.log("\u26A0\uFE0F Restart Claude Code for changes to take effect.\n");
|
|
5156
|
+
} catch (error) {
|
|
5157
|
+
console.error("Uninstall failed:", error);
|
|
5158
|
+
process.exit(1);
|
|
5159
|
+
}
|
|
5160
|
+
});
|
|
5161
|
+
program.command("status").description("Check plugin installation status").action(async () => {
|
|
5162
|
+
try {
|
|
5163
|
+
const settings = loadClaudeSettings();
|
|
5164
|
+
const pluginPath = getPluginPath();
|
|
5165
|
+
console.log("\n\u{1F9E0} Claude Memory Layer Status\n");
|
|
5166
|
+
const hasUserPromptHook = settings.hooks?.UserPromptSubmit?.some(
|
|
5167
|
+
(h) => h.hooks?.some((hook) => hook.command?.includes("user-prompt-submit"))
|
|
5168
|
+
);
|
|
5169
|
+
const hasPostToolHook = settings.hooks?.PostToolUse?.some(
|
|
5170
|
+
(h) => h.hooks?.some((hook) => hook.command?.includes("post-tool-use"))
|
|
5171
|
+
);
|
|
5172
|
+
console.log("Hooks:");
|
|
5173
|
+
console.log(` UserPromptSubmit: ${hasUserPromptHook ? "\u2705 Installed" : "\u274C Not installed"}`);
|
|
5174
|
+
console.log(` PostToolUse: ${hasPostToolHook ? "\u2705 Installed" : "\u274C Not installed"}`);
|
|
5175
|
+
const hooksExist = fs4.existsSync(path4.join(pluginPath, "hooks", "user-prompt-submit.js"));
|
|
5176
|
+
console.log(`
|
|
5177
|
+
Plugin files: ${hooksExist ? "\u2705 Found" : "\u274C Not found"}`);
|
|
5178
|
+
console.log(` Path: ${pluginPath}`);
|
|
5179
|
+
const dashboardRunning = await isServerRunning(37777);
|
|
5180
|
+
console.log(`
|
|
5181
|
+
Dashboard: ${dashboardRunning ? "\u2705 Running at http://localhost:37777" : "\u23F9\uFE0F Not running"}`);
|
|
5182
|
+
if (!hasUserPromptHook || !hasPostToolHook) {
|
|
5183
|
+
console.log('\n\u{1F4A1} Run "claude-memory-layer install" to set up hooks.\n');
|
|
5184
|
+
} else {
|
|
5185
|
+
console.log("\n\u2705 Plugin is fully installed and configured.\n");
|
|
5186
|
+
}
|
|
5187
|
+
} catch (error) {
|
|
5188
|
+
console.error("Status check failed:", error);
|
|
5189
|
+
process.exit(1);
|
|
5190
|
+
}
|
|
5191
|
+
});
|
|
4662
5192
|
program.command("search <query>").description("Search memories using semantic search").option("-k, --top-k <number>", "Number of results", "5").option("-s, --min-score <number>", "Minimum similarity score", "0.7").option("--session <id>", "Filter by session ID").option("-p, --project <path>", "Project path (defaults to cwd)").action(async (query, options) => {
|
|
4663
5193
|
const projectPath = options.project || process.cwd();
|
|
4664
5194
|
const service = getMemoryServiceForProject(projectPath);
|