claude-memory-layer 1.0.6 → 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/.claude-plugin/plugin.json +3 -3
- package/.history/package_20260201142928.json +46 -0
- package/.history/package_20260201192048.json +47 -0
- package/README.md +26 -26
- package/dist/.claude-plugin/plugin.json +3 -3
- package/dist/cli/index.js +1109 -25
- 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 +4728 -0
- package/dist/server/api/index.js.map +7 -0
- package/dist/server/index.js +4790 -0
- package/dist/server/index.js.map +7 -0
- package/dist/services/memory-service.js +269 -18
- package/dist/services/memory-service.js.map +4 -4
- package/dist/ui/index.html +1225 -0
- package/package.json +4 -2
- package/scripts/build.ts +33 -3
- package/src/cli/index.ts +311 -6
- 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/core/types.ts +1 -1
- package/src/mcp/index.ts +2 -2
- package/src/mcp/tools.ts +1 -1
- 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 +175 -5
- package/src/server/index.ts +18 -9
- package/src/services/memory-service.ts +107 -19
- package/src/ui/index.html +1225 -0
package/dist/cli/index.js
CHANGED
|
@@ -8,6 +8,10 @@ const __dirname = dirname(__filename);
|
|
|
8
8
|
|
|
9
9
|
// src/cli/index.ts
|
|
10
10
|
import { Command } from "commander";
|
|
11
|
+
import { exec } from "child_process";
|
|
12
|
+
import * as fs4 from "fs";
|
|
13
|
+
import * as path4 from "path";
|
|
14
|
+
import * as os3 from "os";
|
|
11
15
|
|
|
12
16
|
// src/services/memory-service.ts
|
|
13
17
|
import * as path from "path";
|
|
@@ -70,8 +74,11 @@ function toDate(value) {
|
|
|
70
74
|
return new Date(value);
|
|
71
75
|
return new Date(String(value));
|
|
72
76
|
}
|
|
73
|
-
function createDatabase(
|
|
74
|
-
|
|
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);
|
|
75
82
|
}
|
|
76
83
|
function dbRun(db, sql, params = []) {
|
|
77
84
|
return new Promise((resolve2, reject) => {
|
|
@@ -124,18 +131,24 @@ function dbClose(db) {
|
|
|
124
131
|
|
|
125
132
|
// src/core/event-store.ts
|
|
126
133
|
var EventStore = class {
|
|
127
|
-
constructor(dbPath) {
|
|
134
|
+
constructor(dbPath, options) {
|
|
128
135
|
this.dbPath = dbPath;
|
|
129
|
-
this.
|
|
136
|
+
this.readOnly = options?.readOnly ?? false;
|
|
137
|
+
this.db = createDatabase(dbPath, { readOnly: this.readOnly });
|
|
130
138
|
}
|
|
131
139
|
db;
|
|
132
140
|
initialized = false;
|
|
141
|
+
readOnly;
|
|
133
142
|
/**
|
|
134
143
|
* Initialize database schema
|
|
135
144
|
*/
|
|
136
145
|
async initialize() {
|
|
137
146
|
if (this.initialized)
|
|
138
147
|
return;
|
|
148
|
+
if (this.readOnly) {
|
|
149
|
+
this.initialized = true;
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
139
152
|
await dbRun(this.db, `
|
|
140
153
|
CREATE TABLE IF NOT EXISTS events (
|
|
141
154
|
id VARCHAR PRIMARY KEY,
|
|
@@ -612,6 +625,36 @@ var EventStore = class {
|
|
|
612
625
|
);
|
|
613
626
|
return rows;
|
|
614
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
|
+
}
|
|
615
658
|
// ============================================================
|
|
616
659
|
// Endless Mode Helper Methods
|
|
617
660
|
// ============================================================
|
|
@@ -1194,6 +1237,7 @@ var Retriever = class {
|
|
|
1194
1237
|
matcher;
|
|
1195
1238
|
sharedStore;
|
|
1196
1239
|
sharedVectorStore;
|
|
1240
|
+
graduation;
|
|
1197
1241
|
constructor(eventStore, vectorStore, embedder, matcher, sharedOptions) {
|
|
1198
1242
|
this.eventStore = eventStore;
|
|
1199
1243
|
this.vectorStore = vectorStore;
|
|
@@ -1202,6 +1246,12 @@ var Retriever = class {
|
|
|
1202
1246
|
this.sharedStore = sharedOptions?.sharedStore;
|
|
1203
1247
|
this.sharedVectorStore = sharedOptions?.sharedVectorStore;
|
|
1204
1248
|
}
|
|
1249
|
+
/**
|
|
1250
|
+
* Set graduation pipeline for access tracking
|
|
1251
|
+
*/
|
|
1252
|
+
setGraduationPipeline(graduation) {
|
|
1253
|
+
this.graduation = graduation;
|
|
1254
|
+
}
|
|
1205
1255
|
/**
|
|
1206
1256
|
* Set shared stores after construction
|
|
1207
1257
|
*/
|
|
@@ -1324,6 +1374,13 @@ var Retriever = class {
|
|
|
1324
1374
|
const event = await this.eventStore.getEvent(result.eventId);
|
|
1325
1375
|
if (!event)
|
|
1326
1376
|
continue;
|
|
1377
|
+
if (this.graduation) {
|
|
1378
|
+
this.graduation.recordAccess(
|
|
1379
|
+
event.id,
|
|
1380
|
+
options.sessionId || "unknown",
|
|
1381
|
+
result.score
|
|
1382
|
+
);
|
|
1383
|
+
}
|
|
1327
1384
|
let sessionContext;
|
|
1328
1385
|
if (options.includeSessionContext) {
|
|
1329
1386
|
sessionContext = await this.getSessionContext(event.sessionId, event.id);
|
|
@@ -1445,15 +1502,26 @@ var GraduationPipeline = class {
|
|
|
1445
1502
|
L3toL4: { ...DEFAULT_CRITERIA.L3toL4, ...criteria.L3toL4 }
|
|
1446
1503
|
};
|
|
1447
1504
|
}
|
|
1505
|
+
// Track which sessions have accessed each event
|
|
1506
|
+
sessionAccesses = /* @__PURE__ */ new Map();
|
|
1448
1507
|
/**
|
|
1449
1508
|
* Record an access to an event (used for graduation scoring)
|
|
1450
1509
|
*/
|
|
1451
1510
|
recordAccess(eventId, fromSessionId, confidence = 1) {
|
|
1452
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);
|
|
1453
1518
|
if (existing) {
|
|
1454
1519
|
existing.accessCount++;
|
|
1455
1520
|
existing.lastAccessed = /* @__PURE__ */ new Date();
|
|
1456
1521
|
existing.confidence = Math.max(existing.confidence, confidence);
|
|
1522
|
+
if (isNewSession && sessions.size > 1) {
|
|
1523
|
+
existing.crossSessionRefs = sessions.size - 1;
|
|
1524
|
+
}
|
|
1457
1525
|
} else {
|
|
1458
1526
|
this.metrics.set(eventId, {
|
|
1459
1527
|
eventId,
|
|
@@ -3341,6 +3409,127 @@ function createContinuityManager(eventStore, config) {
|
|
|
3341
3409
|
return new ContinuityManager(eventStore, config);
|
|
3342
3410
|
}
|
|
3343
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
|
+
|
|
3344
3533
|
// src/services/memory-service.ts
|
|
3345
3534
|
function normalizePath(projectPath) {
|
|
3346
3535
|
const expanded = projectPath.startsWith("~") ? path.join(os.homedir(), projectPath.slice(1)) : projectPath;
|
|
@@ -3368,6 +3557,7 @@ var MemoryService = class {
|
|
|
3368
3557
|
retriever;
|
|
3369
3558
|
graduation;
|
|
3370
3559
|
vectorWorker = null;
|
|
3560
|
+
graduationWorker = null;
|
|
3371
3561
|
initialized = false;
|
|
3372
3562
|
// Endless Mode components
|
|
3373
3563
|
workingSetStore = null;
|
|
@@ -3382,14 +3572,16 @@ var MemoryService = class {
|
|
|
3382
3572
|
sharedPromoter = null;
|
|
3383
3573
|
sharedStoreConfig = null;
|
|
3384
3574
|
projectHash = null;
|
|
3575
|
+
readOnly;
|
|
3385
3576
|
constructor(config) {
|
|
3386
3577
|
const storagePath = this.expandPath(config.storagePath);
|
|
3387
|
-
|
|
3578
|
+
this.readOnly = config.readOnly ?? false;
|
|
3579
|
+
if (!this.readOnly && !fs.existsSync(storagePath)) {
|
|
3388
3580
|
fs.mkdirSync(storagePath, { recursive: true });
|
|
3389
3581
|
}
|
|
3390
3582
|
this.projectHash = config.projectHash || null;
|
|
3391
3583
|
this.sharedStoreConfig = config.sharedStoreConfig ?? { enabled: true };
|
|
3392
|
-
this.eventStore = new EventStore(path.join(storagePath, "events.duckdb"));
|
|
3584
|
+
this.eventStore = new EventStore(path.join(storagePath, "events.duckdb"), { readOnly: this.readOnly });
|
|
3393
3585
|
this.vectorStore = new VectorStore(path.join(storagePath, "vectors"));
|
|
3394
3586
|
this.embedder = config.embeddingModel ? new Embedder(config.embeddingModel) : getDefaultEmbedder();
|
|
3395
3587
|
this.matcher = getDefaultMatcher();
|
|
@@ -3410,19 +3602,27 @@ var MemoryService = class {
|
|
|
3410
3602
|
await this.eventStore.initialize();
|
|
3411
3603
|
await this.vectorStore.initialize();
|
|
3412
3604
|
await this.embedder.initialize();
|
|
3413
|
-
this.
|
|
3414
|
-
this.
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
this.
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
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
|
+
}
|
|
3426
3626
|
}
|
|
3427
3627
|
this.initialized = true;
|
|
3428
3628
|
}
|
|
@@ -3603,6 +3803,20 @@ var MemoryService = class {
|
|
|
3603
3803
|
}
|
|
3604
3804
|
return 0;
|
|
3605
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
|
+
}
|
|
3606
3820
|
/**
|
|
3607
3821
|
* Format retrieval results as context for Claude
|
|
3608
3822
|
*/
|
|
@@ -3785,6 +3999,22 @@ var MemoryService = class {
|
|
|
3785
3999
|
return [];
|
|
3786
4000
|
return this.consolidatedStore.getAll({ limit });
|
|
3787
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
|
+
}
|
|
3788
4018
|
/**
|
|
3789
4019
|
* Calculate continuity score for current context
|
|
3790
4020
|
*/
|
|
@@ -3872,10 +4102,28 @@ var MemoryService = class {
|
|
|
3872
4102
|
}
|
|
3873
4103
|
return parts.join("\n");
|
|
3874
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
|
+
}
|
|
3875
4120
|
/**
|
|
3876
4121
|
* Shutdown service
|
|
3877
4122
|
*/
|
|
3878
4123
|
async shutdown() {
|
|
4124
|
+
if (this.graduationWorker) {
|
|
4125
|
+
this.graduationWorker.stop();
|
|
4126
|
+
}
|
|
3879
4127
|
if (this.consolidationWorker) {
|
|
3880
4128
|
this.consolidationWorker.stop();
|
|
3881
4129
|
}
|
|
@@ -3907,6 +4155,12 @@ function getDefaultMemoryService() {
|
|
|
3907
4155
|
}
|
|
3908
4156
|
return serviceCache.get(GLOBAL_KEY);
|
|
3909
4157
|
}
|
|
4158
|
+
function getReadOnlyMemoryService() {
|
|
4159
|
+
return new MemoryService({
|
|
4160
|
+
storagePath: "~/.claude-code/memory",
|
|
4161
|
+
readOnly: true
|
|
4162
|
+
});
|
|
4163
|
+
}
|
|
3910
4164
|
function getMemoryServiceForProject(projectPath, sharedStoreConfig) {
|
|
3911
4165
|
const hash = hashProjectPath(projectPath);
|
|
3912
4166
|
if (!serviceCache.has(hash)) {
|
|
@@ -4158,9 +4412,783 @@ function createSessionHistoryImporter(memoryService) {
|
|
|
4158
4412
|
return new SessionHistoryImporter(memoryService);
|
|
4159
4413
|
}
|
|
4160
4414
|
|
|
4415
|
+
// src/server/index.ts
|
|
4416
|
+
import { Hono as Hono7 } from "hono";
|
|
4417
|
+
import { cors } from "hono/cors";
|
|
4418
|
+
import { logger } from "hono/logger";
|
|
4419
|
+
import { serve } from "@hono/node-server";
|
|
4420
|
+
import { serveStatic } from "@hono/node-server/serve-static";
|
|
4421
|
+
import * as path3 from "path";
|
|
4422
|
+
import * as fs3 from "fs";
|
|
4423
|
+
|
|
4424
|
+
// src/server/api/index.ts
|
|
4425
|
+
import { Hono as Hono6 } from "hono";
|
|
4426
|
+
|
|
4427
|
+
// src/server/api/sessions.ts
|
|
4428
|
+
import { Hono } from "hono";
|
|
4429
|
+
var sessionsRouter = new Hono();
|
|
4430
|
+
sessionsRouter.get("/", async (c) => {
|
|
4431
|
+
const page = parseInt(c.req.query("page") || "1", 10);
|
|
4432
|
+
const pageSize = parseInt(c.req.query("pageSize") || "20", 10);
|
|
4433
|
+
const memoryService = getReadOnlyMemoryService();
|
|
4434
|
+
try {
|
|
4435
|
+
await memoryService.initialize();
|
|
4436
|
+
const recentEvents = await memoryService.getRecentEvents(1e3);
|
|
4437
|
+
const sessionMap = /* @__PURE__ */ new Map();
|
|
4438
|
+
for (const event of recentEvents) {
|
|
4439
|
+
const existing = sessionMap.get(event.sessionId);
|
|
4440
|
+
if (!existing) {
|
|
4441
|
+
sessionMap.set(event.sessionId, {
|
|
4442
|
+
id: event.sessionId,
|
|
4443
|
+
startedAt: event.timestamp,
|
|
4444
|
+
eventCount: 1,
|
|
4445
|
+
lastEventAt: event.timestamp
|
|
4446
|
+
});
|
|
4447
|
+
} else {
|
|
4448
|
+
existing.eventCount++;
|
|
4449
|
+
if (event.timestamp < existing.startedAt) {
|
|
4450
|
+
existing.startedAt = event.timestamp;
|
|
4451
|
+
}
|
|
4452
|
+
if (event.timestamp > existing.lastEventAt) {
|
|
4453
|
+
existing.lastEventAt = event.timestamp;
|
|
4454
|
+
}
|
|
4455
|
+
}
|
|
4456
|
+
}
|
|
4457
|
+
const sessions = Array.from(sessionMap.values()).sort((a, b) => b.lastEventAt.getTime() - a.lastEventAt.getTime());
|
|
4458
|
+
const total = sessions.length;
|
|
4459
|
+
const start = (page - 1) * pageSize;
|
|
4460
|
+
const end = start + pageSize;
|
|
4461
|
+
const paginatedSessions = sessions.slice(start, end);
|
|
4462
|
+
return c.json({
|
|
4463
|
+
sessions: paginatedSessions,
|
|
4464
|
+
total,
|
|
4465
|
+
page,
|
|
4466
|
+
pageSize,
|
|
4467
|
+
hasMore: end < total
|
|
4468
|
+
});
|
|
4469
|
+
} catch (error) {
|
|
4470
|
+
return c.json({ error: error.message }, 500);
|
|
4471
|
+
} finally {
|
|
4472
|
+
await memoryService.shutdown();
|
|
4473
|
+
}
|
|
4474
|
+
});
|
|
4475
|
+
sessionsRouter.get("/:id", async (c) => {
|
|
4476
|
+
const { id } = c.req.param();
|
|
4477
|
+
const memoryService = getReadOnlyMemoryService();
|
|
4478
|
+
try {
|
|
4479
|
+
await memoryService.initialize();
|
|
4480
|
+
const events = await memoryService.getSessionHistory(id);
|
|
4481
|
+
if (events.length === 0) {
|
|
4482
|
+
return c.json({ error: "Session not found" }, 404);
|
|
4483
|
+
}
|
|
4484
|
+
const session = {
|
|
4485
|
+
id,
|
|
4486
|
+
startedAt: events[0].timestamp,
|
|
4487
|
+
endedAt: events[events.length - 1].timestamp,
|
|
4488
|
+
eventCount: events.length
|
|
4489
|
+
};
|
|
4490
|
+
const eventsByType = {
|
|
4491
|
+
user_prompt: events.filter((e) => e.eventType === "user_prompt").length,
|
|
4492
|
+
agent_response: events.filter((e) => e.eventType === "agent_response").length,
|
|
4493
|
+
tool_observation: events.filter((e) => e.eventType === "tool_observation").length
|
|
4494
|
+
};
|
|
4495
|
+
return c.json({
|
|
4496
|
+
session,
|
|
4497
|
+
events: events.slice(0, 100).map((e) => ({
|
|
4498
|
+
id: e.id,
|
|
4499
|
+
eventType: e.eventType,
|
|
4500
|
+
timestamp: e.timestamp,
|
|
4501
|
+
preview: e.content.slice(0, 200) + (e.content.length > 200 ? "..." : "")
|
|
4502
|
+
})),
|
|
4503
|
+
stats: eventsByType
|
|
4504
|
+
});
|
|
4505
|
+
} catch (error) {
|
|
4506
|
+
return c.json({ error: error.message }, 500);
|
|
4507
|
+
} finally {
|
|
4508
|
+
await memoryService.shutdown();
|
|
4509
|
+
}
|
|
4510
|
+
});
|
|
4511
|
+
|
|
4512
|
+
// src/server/api/events.ts
|
|
4513
|
+
import { Hono as Hono2 } from "hono";
|
|
4514
|
+
var eventsRouter = new Hono2();
|
|
4515
|
+
eventsRouter.get("/", async (c) => {
|
|
4516
|
+
const sessionId = c.req.query("sessionId");
|
|
4517
|
+
const eventType = c.req.query("type");
|
|
4518
|
+
const limit = parseInt(c.req.query("limit") || "100", 10);
|
|
4519
|
+
const offset = parseInt(c.req.query("offset") || "0", 10);
|
|
4520
|
+
const memoryService = getReadOnlyMemoryService();
|
|
4521
|
+
try {
|
|
4522
|
+
await memoryService.initialize();
|
|
4523
|
+
let events = await memoryService.getRecentEvents(limit + offset + 1e3);
|
|
4524
|
+
if (sessionId) {
|
|
4525
|
+
events = events.filter((e) => e.sessionId === sessionId);
|
|
4526
|
+
}
|
|
4527
|
+
if (eventType) {
|
|
4528
|
+
events = events.filter((e) => e.eventType === eventType);
|
|
4529
|
+
}
|
|
4530
|
+
const total = events.length;
|
|
4531
|
+
events = events.slice(offset, offset + limit);
|
|
4532
|
+
return c.json({
|
|
4533
|
+
events: events.map((e) => ({
|
|
4534
|
+
id: e.id,
|
|
4535
|
+
eventType: e.eventType,
|
|
4536
|
+
timestamp: e.timestamp,
|
|
4537
|
+
sessionId: e.sessionId,
|
|
4538
|
+
preview: e.content.slice(0, 200) + (e.content.length > 200 ? "..." : ""),
|
|
4539
|
+
contentLength: e.content.length
|
|
4540
|
+
})),
|
|
4541
|
+
total,
|
|
4542
|
+
limit,
|
|
4543
|
+
offset,
|
|
4544
|
+
hasMore: offset + limit < total
|
|
4545
|
+
});
|
|
4546
|
+
} catch (error) {
|
|
4547
|
+
return c.json({ error: error.message }, 500);
|
|
4548
|
+
} finally {
|
|
4549
|
+
await memoryService.shutdown();
|
|
4550
|
+
}
|
|
4551
|
+
});
|
|
4552
|
+
eventsRouter.get("/:id", async (c) => {
|
|
4553
|
+
const { id } = c.req.param();
|
|
4554
|
+
const memoryService = getReadOnlyMemoryService();
|
|
4555
|
+
try {
|
|
4556
|
+
await memoryService.initialize();
|
|
4557
|
+
const recentEvents = await memoryService.getRecentEvents(1e4);
|
|
4558
|
+
const event = recentEvents.find((e) => e.id === id);
|
|
4559
|
+
if (!event) {
|
|
4560
|
+
return c.json({ error: "Event not found" }, 404);
|
|
4561
|
+
}
|
|
4562
|
+
const sessionEvents = recentEvents.filter((e) => e.sessionId === event.sessionId).sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
|
|
4563
|
+
const eventIndex = sessionEvents.findIndex((e) => e.id === id);
|
|
4564
|
+
const start = Math.max(0, eventIndex - 2);
|
|
4565
|
+
const end = Math.min(sessionEvents.length, eventIndex + 3);
|
|
4566
|
+
const context = sessionEvents.slice(start, end).filter((e) => e.id !== id);
|
|
4567
|
+
return c.json({
|
|
4568
|
+
event: {
|
|
4569
|
+
id: event.id,
|
|
4570
|
+
eventType: event.eventType,
|
|
4571
|
+
timestamp: event.timestamp,
|
|
4572
|
+
sessionId: event.sessionId,
|
|
4573
|
+
content: event.content,
|
|
4574
|
+
metadata: event.metadata
|
|
4575
|
+
},
|
|
4576
|
+
context: context.map((e) => ({
|
|
4577
|
+
id: e.id,
|
|
4578
|
+
eventType: e.eventType,
|
|
4579
|
+
timestamp: e.timestamp,
|
|
4580
|
+
preview: e.content.slice(0, 100) + (e.content.length > 100 ? "..." : "")
|
|
4581
|
+
}))
|
|
4582
|
+
});
|
|
4583
|
+
} catch (error) {
|
|
4584
|
+
return c.json({ error: error.message }, 500);
|
|
4585
|
+
} finally {
|
|
4586
|
+
await memoryService.shutdown();
|
|
4587
|
+
}
|
|
4588
|
+
});
|
|
4589
|
+
|
|
4590
|
+
// src/server/api/search.ts
|
|
4591
|
+
import { Hono as Hono3 } from "hono";
|
|
4592
|
+
var searchRouter = new Hono3();
|
|
4593
|
+
searchRouter.post("/", async (c) => {
|
|
4594
|
+
const memoryService = getReadOnlyMemoryService();
|
|
4595
|
+
try {
|
|
4596
|
+
const body = await c.req.json();
|
|
4597
|
+
if (!body.query) {
|
|
4598
|
+
return c.json({ error: "Query is required" }, 400);
|
|
4599
|
+
}
|
|
4600
|
+
await memoryService.initialize();
|
|
4601
|
+
const startTime = Date.now();
|
|
4602
|
+
const result = await memoryService.retrieveMemories(body.query, {
|
|
4603
|
+
topK: body.options?.topK ?? 10,
|
|
4604
|
+
minScore: body.options?.minScore ?? 0.7,
|
|
4605
|
+
sessionId: body.options?.sessionId
|
|
4606
|
+
});
|
|
4607
|
+
const searchTime = Date.now() - startTime;
|
|
4608
|
+
return c.json({
|
|
4609
|
+
results: result.memories.map((m) => ({
|
|
4610
|
+
id: m.event.id,
|
|
4611
|
+
eventType: m.event.eventType,
|
|
4612
|
+
timestamp: m.event.timestamp,
|
|
4613
|
+
sessionId: m.event.sessionId,
|
|
4614
|
+
score: m.score,
|
|
4615
|
+
content: m.event.content,
|
|
4616
|
+
preview: m.event.content.slice(0, 200) + (m.event.content.length > 200 ? "..." : ""),
|
|
4617
|
+
context: m.sessionContext
|
|
4618
|
+
})),
|
|
4619
|
+
meta: {
|
|
4620
|
+
totalMatches: result.memories.length,
|
|
4621
|
+
searchTime,
|
|
4622
|
+
confidence: result.matchResult.confidence,
|
|
4623
|
+
totalTokens: result.totalTokens
|
|
4624
|
+
}
|
|
4625
|
+
});
|
|
4626
|
+
} catch (error) {
|
|
4627
|
+
return c.json({ error: error.message }, 500);
|
|
4628
|
+
} finally {
|
|
4629
|
+
await memoryService.shutdown();
|
|
4630
|
+
}
|
|
4631
|
+
});
|
|
4632
|
+
searchRouter.get("/", async (c) => {
|
|
4633
|
+
const query = c.req.query("q");
|
|
4634
|
+
if (!query) {
|
|
4635
|
+
return c.json({ error: 'Query parameter "q" is required' }, 400);
|
|
4636
|
+
}
|
|
4637
|
+
const topK = parseInt(c.req.query("topK") || "5", 10);
|
|
4638
|
+
const memoryService = getReadOnlyMemoryService();
|
|
4639
|
+
try {
|
|
4640
|
+
await memoryService.initialize();
|
|
4641
|
+
const result = await memoryService.retrieveMemories(query, { topK });
|
|
4642
|
+
return c.json({
|
|
4643
|
+
results: result.memories.map((m) => ({
|
|
4644
|
+
id: m.event.id,
|
|
4645
|
+
eventType: m.event.eventType,
|
|
4646
|
+
timestamp: m.event.timestamp,
|
|
4647
|
+
score: m.score,
|
|
4648
|
+
preview: m.event.content.slice(0, 200) + (m.event.content.length > 200 ? "..." : "")
|
|
4649
|
+
})),
|
|
4650
|
+
meta: {
|
|
4651
|
+
totalMatches: result.memories.length,
|
|
4652
|
+
confidence: result.matchResult.confidence
|
|
4653
|
+
}
|
|
4654
|
+
});
|
|
4655
|
+
} catch (error) {
|
|
4656
|
+
return c.json({ error: error.message }, 500);
|
|
4657
|
+
} finally {
|
|
4658
|
+
await memoryService.shutdown();
|
|
4659
|
+
}
|
|
4660
|
+
});
|
|
4661
|
+
|
|
4662
|
+
// src/server/api/stats.ts
|
|
4663
|
+
import { Hono as Hono4 } from "hono";
|
|
4664
|
+
var statsRouter = new Hono4();
|
|
4665
|
+
statsRouter.get("/shared", async (c) => {
|
|
4666
|
+
const memoryService = getReadOnlyMemoryService();
|
|
4667
|
+
try {
|
|
4668
|
+
await memoryService.initialize();
|
|
4669
|
+
const sharedStats = await memoryService.getSharedStoreStats();
|
|
4670
|
+
return c.json({
|
|
4671
|
+
troubleshooting: sharedStats?.troubleshooting || 0,
|
|
4672
|
+
bestPractices: sharedStats?.bestPractices || 0,
|
|
4673
|
+
commonErrors: sharedStats?.commonErrors || 0,
|
|
4674
|
+
totalUsageCount: sharedStats?.totalUsageCount || 0,
|
|
4675
|
+
lastUpdated: sharedStats?.lastUpdated || null
|
|
4676
|
+
});
|
|
4677
|
+
} catch (error) {
|
|
4678
|
+
return c.json({
|
|
4679
|
+
troubleshooting: 0,
|
|
4680
|
+
bestPractices: 0,
|
|
4681
|
+
commonErrors: 0,
|
|
4682
|
+
totalUsageCount: 0,
|
|
4683
|
+
lastUpdated: null
|
|
4684
|
+
});
|
|
4685
|
+
} finally {
|
|
4686
|
+
await memoryService.shutdown();
|
|
4687
|
+
}
|
|
4688
|
+
});
|
|
4689
|
+
statsRouter.get("/endless", async (c) => {
|
|
4690
|
+
const projectPath = c.req.query("project") || process.cwd();
|
|
4691
|
+
const memoryService = getMemoryServiceForProject(projectPath);
|
|
4692
|
+
try {
|
|
4693
|
+
await memoryService.initialize();
|
|
4694
|
+
const status = await memoryService.getEndlessModeStatus();
|
|
4695
|
+
return c.json({
|
|
4696
|
+
mode: status.mode,
|
|
4697
|
+
continuityScore: status.continuityScore,
|
|
4698
|
+
workingSetSize: status.workingSetSize,
|
|
4699
|
+
consolidatedCount: status.consolidatedCount,
|
|
4700
|
+
lastConsolidation: status.lastConsolidation?.toISOString() || null
|
|
4701
|
+
});
|
|
4702
|
+
} catch (error) {
|
|
4703
|
+
return c.json({
|
|
4704
|
+
mode: "session",
|
|
4705
|
+
continuityScore: 0,
|
|
4706
|
+
workingSetSize: 0,
|
|
4707
|
+
consolidatedCount: 0,
|
|
4708
|
+
lastConsolidation: null
|
|
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();
|
|
4747
|
+
}
|
|
4748
|
+
});
|
|
4749
|
+
statsRouter.get("/", async (c) => {
|
|
4750
|
+
const memoryService = getReadOnlyMemoryService();
|
|
4751
|
+
try {
|
|
4752
|
+
await memoryService.initialize();
|
|
4753
|
+
const stats = await memoryService.getStats();
|
|
4754
|
+
const recentEvents = await memoryService.getRecentEvents(1e4);
|
|
4755
|
+
const eventsByType = recentEvents.reduce((acc, e) => {
|
|
4756
|
+
acc[e.eventType] = (acc[e.eventType] || 0) + 1;
|
|
4757
|
+
return acc;
|
|
4758
|
+
}, {});
|
|
4759
|
+
const uniqueSessions = new Set(recentEvents.map((e) => e.sessionId));
|
|
4760
|
+
const now = /* @__PURE__ */ new Date();
|
|
4761
|
+
const sevenDaysAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1e3);
|
|
4762
|
+
const eventsByDay = recentEvents.filter((e) => e.timestamp >= sevenDaysAgo).reduce((acc, e) => {
|
|
4763
|
+
const day = e.timestamp.toISOString().split("T")[0];
|
|
4764
|
+
acc[day] = (acc[day] || 0) + 1;
|
|
4765
|
+
return acc;
|
|
4766
|
+
}, {});
|
|
4767
|
+
return c.json({
|
|
4768
|
+
storage: {
|
|
4769
|
+
eventCount: stats.totalEvents,
|
|
4770
|
+
vectorCount: stats.vectorCount
|
|
4771
|
+
},
|
|
4772
|
+
sessions: {
|
|
4773
|
+
total: uniqueSessions.size
|
|
4774
|
+
},
|
|
4775
|
+
eventsByType,
|
|
4776
|
+
activity: {
|
|
4777
|
+
daily: eventsByDay,
|
|
4778
|
+
total7Days: recentEvents.filter((e) => e.timestamp >= sevenDaysAgo).length
|
|
4779
|
+
},
|
|
4780
|
+
memory: {
|
|
4781
|
+
heapUsed: Math.round(process.memoryUsage().heapUsed / 1024 / 1024),
|
|
4782
|
+
heapTotal: Math.round(process.memoryUsage().heapTotal / 1024 / 1024)
|
|
4783
|
+
},
|
|
4784
|
+
levelStats: stats.levelStats
|
|
4785
|
+
});
|
|
4786
|
+
} catch (error) {
|
|
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();
|
|
4819
|
+
}
|
|
4820
|
+
});
|
|
4821
|
+
statsRouter.get("/timeline", async (c) => {
|
|
4822
|
+
const days = parseInt(c.req.query("days") || "7", 10);
|
|
4823
|
+
const memoryService = getReadOnlyMemoryService();
|
|
4824
|
+
try {
|
|
4825
|
+
await memoryService.initialize();
|
|
4826
|
+
const recentEvents = await memoryService.getRecentEvents(1e4);
|
|
4827
|
+
const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1e3);
|
|
4828
|
+
const filteredEvents = recentEvents.filter((e) => e.timestamp >= cutoff);
|
|
4829
|
+
const daily = filteredEvents.reduce((acc, e) => {
|
|
4830
|
+
const day = e.timestamp.toISOString().split("T")[0];
|
|
4831
|
+
if (!acc[day]) {
|
|
4832
|
+
acc[day] = { date: day, total: 0, prompts: 0, responses: 0, tools: 0 };
|
|
4833
|
+
}
|
|
4834
|
+
acc[day].total++;
|
|
4835
|
+
if (e.eventType === "user_prompt")
|
|
4836
|
+
acc[day].prompts++;
|
|
4837
|
+
if (e.eventType === "agent_response")
|
|
4838
|
+
acc[day].responses++;
|
|
4839
|
+
if (e.eventType === "tool_observation")
|
|
4840
|
+
acc[day].tools++;
|
|
4841
|
+
return acc;
|
|
4842
|
+
}, {});
|
|
4843
|
+
return c.json({
|
|
4844
|
+
days,
|
|
4845
|
+
daily: Object.values(daily).sort((a, b) => a.date.localeCompare(b.date))
|
|
4846
|
+
});
|
|
4847
|
+
} catch (error) {
|
|
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();
|
|
4871
|
+
}
|
|
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
|
+
});
|
|
4889
|
+
|
|
4890
|
+
// src/server/api/citations.ts
|
|
4891
|
+
import { Hono as Hono5 } from "hono";
|
|
4892
|
+
|
|
4893
|
+
// src/core/citation-generator.ts
|
|
4894
|
+
import { createHash as createHash3 } from "crypto";
|
|
4895
|
+
var ID_LENGTH = 6;
|
|
4896
|
+
var CHARSET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
4897
|
+
function generateCitationId(eventId) {
|
|
4898
|
+
const hash = createHash3("sha256").update(eventId).digest();
|
|
4899
|
+
let id = "";
|
|
4900
|
+
for (let i = 0; i < ID_LENGTH; i++) {
|
|
4901
|
+
id += CHARSET[hash[i] % CHARSET.length];
|
|
4902
|
+
}
|
|
4903
|
+
return id;
|
|
4904
|
+
}
|
|
4905
|
+
function parseCitationId(formatted) {
|
|
4906
|
+
const match = formatted.match(/\[?mem:([A-Za-z0-9]{6})\]?/);
|
|
4907
|
+
return match ? match[1] : null;
|
|
4908
|
+
}
|
|
4909
|
+
|
|
4910
|
+
// src/server/api/citations.ts
|
|
4911
|
+
var citationsRouter = new Hono5();
|
|
4912
|
+
citationsRouter.get("/:id", async (c) => {
|
|
4913
|
+
const { id } = c.req.param();
|
|
4914
|
+
const citationId = parseCitationId(id) || id;
|
|
4915
|
+
const memoryService = getReadOnlyMemoryService();
|
|
4916
|
+
try {
|
|
4917
|
+
await memoryService.initialize();
|
|
4918
|
+
const recentEvents = await memoryService.getRecentEvents(1e4);
|
|
4919
|
+
const event = recentEvents.find((e) => {
|
|
4920
|
+
const eventCitationId = generateCitationId(e.id);
|
|
4921
|
+
return eventCitationId === citationId;
|
|
4922
|
+
});
|
|
4923
|
+
if (!event) {
|
|
4924
|
+
return c.json({ error: "Citation not found" }, 404);
|
|
4925
|
+
}
|
|
4926
|
+
return c.json({
|
|
4927
|
+
citation: {
|
|
4928
|
+
id: citationId,
|
|
4929
|
+
eventId: event.id
|
|
4930
|
+
},
|
|
4931
|
+
event: {
|
|
4932
|
+
id: event.id,
|
|
4933
|
+
eventType: event.eventType,
|
|
4934
|
+
timestamp: event.timestamp,
|
|
4935
|
+
sessionId: event.sessionId,
|
|
4936
|
+
content: event.content,
|
|
4937
|
+
metadata: event.metadata
|
|
4938
|
+
}
|
|
4939
|
+
});
|
|
4940
|
+
} catch (error) {
|
|
4941
|
+
return c.json({ error: error.message }, 500);
|
|
4942
|
+
} finally {
|
|
4943
|
+
await memoryService.shutdown();
|
|
4944
|
+
}
|
|
4945
|
+
});
|
|
4946
|
+
citationsRouter.get("/:id/related", async (c) => {
|
|
4947
|
+
const { id } = c.req.param();
|
|
4948
|
+
const citationId = parseCitationId(id) || id;
|
|
4949
|
+
const memoryService = getReadOnlyMemoryService();
|
|
4950
|
+
try {
|
|
4951
|
+
await memoryService.initialize();
|
|
4952
|
+
const recentEvents = await memoryService.getRecentEvents(1e4);
|
|
4953
|
+
const event = recentEvents.find((e) => {
|
|
4954
|
+
const eventCitationId = generateCitationId(e.id);
|
|
4955
|
+
return eventCitationId === citationId;
|
|
4956
|
+
});
|
|
4957
|
+
if (!event) {
|
|
4958
|
+
return c.json({ error: "Citation not found" }, 404);
|
|
4959
|
+
}
|
|
4960
|
+
const sessionEvents = recentEvents.filter((e) => e.sessionId === event.sessionId).sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
|
|
4961
|
+
const eventIndex = sessionEvents.findIndex((e) => e.id === event.id);
|
|
4962
|
+
const prev = eventIndex > 0 ? sessionEvents[eventIndex - 1] : null;
|
|
4963
|
+
const next = eventIndex < sessionEvents.length - 1 ? sessionEvents[eventIndex + 1] : null;
|
|
4964
|
+
return c.json({
|
|
4965
|
+
previous: prev ? {
|
|
4966
|
+
citationId: generateCitationId(prev.id),
|
|
4967
|
+
eventType: prev.eventType,
|
|
4968
|
+
timestamp: prev.timestamp,
|
|
4969
|
+
preview: prev.content.slice(0, 100) + (prev.content.length > 100 ? "..." : "")
|
|
4970
|
+
} : null,
|
|
4971
|
+
next: next ? {
|
|
4972
|
+
citationId: generateCitationId(next.id),
|
|
4973
|
+
eventType: next.eventType,
|
|
4974
|
+
timestamp: next.timestamp,
|
|
4975
|
+
preview: next.content.slice(0, 100) + (next.content.length > 100 ? "..." : "")
|
|
4976
|
+
} : null
|
|
4977
|
+
});
|
|
4978
|
+
} catch (error) {
|
|
4979
|
+
return c.json({ error: error.message }, 500);
|
|
4980
|
+
} finally {
|
|
4981
|
+
await memoryService.shutdown();
|
|
4982
|
+
}
|
|
4983
|
+
});
|
|
4984
|
+
|
|
4985
|
+
// src/server/api/index.ts
|
|
4986
|
+
var apiRouter = new Hono6().route("/sessions", sessionsRouter).route("/events", eventsRouter).route("/search", searchRouter).route("/stats", statsRouter).route("/citations", citationsRouter);
|
|
4987
|
+
|
|
4988
|
+
// src/server/index.ts
|
|
4989
|
+
var app = new Hono7();
|
|
4990
|
+
app.use("/*", cors());
|
|
4991
|
+
app.use("/*", logger());
|
|
4992
|
+
app.route("/api", apiRouter);
|
|
4993
|
+
app.get("/health", (c) => c.json({ status: "ok", timestamp: (/* @__PURE__ */ new Date()).toISOString() }));
|
|
4994
|
+
var uiPath = path3.join(__dirname, "../../dist/ui");
|
|
4995
|
+
if (fs3.existsSync(uiPath)) {
|
|
4996
|
+
app.use("/*", serveStatic({ root: uiPath }));
|
|
4997
|
+
}
|
|
4998
|
+
app.get("*", (c) => {
|
|
4999
|
+
const indexPath = path3.join(uiPath, "index.html");
|
|
5000
|
+
if (fs3.existsSync(indexPath)) {
|
|
5001
|
+
return c.html(fs3.readFileSync(indexPath, "utf-8"));
|
|
5002
|
+
}
|
|
5003
|
+
return c.text('UI not built. Run "npm run build:ui" first.', 404);
|
|
5004
|
+
});
|
|
5005
|
+
var serverInstance = null;
|
|
5006
|
+
function startServer(port = 37777) {
|
|
5007
|
+
if (serverInstance) {
|
|
5008
|
+
return serverInstance;
|
|
5009
|
+
}
|
|
5010
|
+
serverInstance = serve({
|
|
5011
|
+
fetch: app.fetch,
|
|
5012
|
+
port,
|
|
5013
|
+
hostname: "127.0.0.1"
|
|
5014
|
+
});
|
|
5015
|
+
console.log(`\u{1F9E0} Code Memory viewer started at http://localhost:${port}`);
|
|
5016
|
+
return serverInstance;
|
|
5017
|
+
}
|
|
5018
|
+
function stopServer() {
|
|
5019
|
+
if (serverInstance) {
|
|
5020
|
+
serverInstance.close();
|
|
5021
|
+
serverInstance = null;
|
|
5022
|
+
}
|
|
5023
|
+
}
|
|
5024
|
+
async function isServerRunning(port = 37777) {
|
|
5025
|
+
try {
|
|
5026
|
+
const response = await fetch(`http://127.0.0.1:${port}/health`);
|
|
5027
|
+
return response.ok;
|
|
5028
|
+
} catch {
|
|
5029
|
+
return false;
|
|
5030
|
+
}
|
|
5031
|
+
}
|
|
5032
|
+
var isMainModule = process.argv[1]?.includes("server/index") || process.argv[1]?.endsWith("server.js");
|
|
5033
|
+
if (isMainModule) {
|
|
5034
|
+
const port = parseInt(process.env.PORT || "37777", 10);
|
|
5035
|
+
startServer(port);
|
|
5036
|
+
}
|
|
5037
|
+
|
|
4161
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
|
+
}
|
|
4162
5103
|
var program = new Command();
|
|
4163
|
-
program.name("
|
|
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
|
+
});
|
|
4164
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) => {
|
|
4165
5193
|
const projectPath = options.project || process.cwd();
|
|
4166
5194
|
const service = getMemoryServiceForProject(projectPath);
|
|
@@ -4384,7 +5412,7 @@ program.command("list").description("List available Claude Code sessions").optio
|
|
|
4384
5412
|
if (sessions.length > 20) {
|
|
4385
5413
|
console.log(`... and ${sessions.length - 20} more sessions`);
|
|
4386
5414
|
}
|
|
4387
|
-
console.log('\nUse "
|
|
5415
|
+
console.log('\nUse "claude-memory-layer import --session <path>" to import a specific session');
|
|
4388
5416
|
} catch (error) {
|
|
4389
5417
|
console.error("List failed:", error);
|
|
4390
5418
|
process.exit(1);
|
|
@@ -4404,7 +5432,7 @@ endlessCmd.command("enable").description("Enable Endless Mode").option("-p, --pr
|
|
|
4404
5432
|
console.log(" - Working Set: Recent context kept active");
|
|
4405
5433
|
console.log(" - Consolidation: Automatic memory integration");
|
|
4406
5434
|
console.log(" - Continuity: Seamless context transitions\n");
|
|
4407
|
-
console.log('Use "
|
|
5435
|
+
console.log('Use "claude-memory-layer endless status" to view current state');
|
|
4408
5436
|
await service.shutdown();
|
|
4409
5437
|
} catch (error) {
|
|
4410
5438
|
console.error("Enable failed:", error);
|
|
@@ -4452,7 +5480,7 @@ ${modeIcon} ${modeName}
|
|
|
4452
5480
|
}
|
|
4453
5481
|
} else {
|
|
4454
5482
|
console.log("Endless Mode is disabled.");
|
|
4455
|
-
console.log('Use "
|
|
5483
|
+
console.log('Use "claude-memory-layer endless enable" to activate.');
|
|
4456
5484
|
}
|
|
4457
5485
|
await service.shutdown();
|
|
4458
5486
|
} catch (error) {
|
|
@@ -4467,7 +5495,7 @@ endlessCmd.command("consolidate").description("Manually trigger memory consolida
|
|
|
4467
5495
|
await service.initialize();
|
|
4468
5496
|
if (!service.isEndlessModeActive()) {
|
|
4469
5497
|
console.log("\n\u26A0\uFE0F Endless Mode is not active");
|
|
4470
|
-
console.log('Use "
|
|
5498
|
+
console.log('Use "claude-memory-layer endless enable" first');
|
|
4471
5499
|
process.exit(1);
|
|
4472
5500
|
}
|
|
4473
5501
|
console.log("\n\u23F3 Running memory consolidation...");
|
|
@@ -4492,7 +5520,7 @@ endlessCmd.command("working-set").alias("ws").description("View current working
|
|
|
4492
5520
|
await service.initialize();
|
|
4493
5521
|
if (!service.isEndlessModeActive()) {
|
|
4494
5522
|
console.log("\n\u26A0\uFE0F Endless Mode is not active");
|
|
4495
|
-
console.log('Use "
|
|
5523
|
+
console.log('Use "claude-memory-layer endless enable" first');
|
|
4496
5524
|
process.exit(1);
|
|
4497
5525
|
}
|
|
4498
5526
|
const workingSet = await service.getWorkingSet();
|
|
@@ -4568,5 +5596,61 @@ endlessCmd.command("memories").description("View consolidated memories").option(
|
|
|
4568
5596
|
process.exit(1);
|
|
4569
5597
|
}
|
|
4570
5598
|
});
|
|
5599
|
+
program.command("dashboard").description("Open memory dashboard in browser").option("-p, --port <port>", "Server port", "37777").option("--no-open", "Do not auto-open browser").action(async (options) => {
|
|
5600
|
+
const port = parseInt(options.port, 10);
|
|
5601
|
+
try {
|
|
5602
|
+
const running = await isServerRunning(port);
|
|
5603
|
+
if (running) {
|
|
5604
|
+
console.log(`
|
|
5605
|
+
\u{1F9E0} Dashboard already running at http://localhost:${port}
|
|
5606
|
+
`);
|
|
5607
|
+
if (options.open) {
|
|
5608
|
+
openBrowser(`http://localhost:${port}`);
|
|
5609
|
+
}
|
|
5610
|
+
return;
|
|
5611
|
+
}
|
|
5612
|
+
console.log("\n\u{1F9E0} Starting Code Memory Dashboard...\n");
|
|
5613
|
+
startServer(port);
|
|
5614
|
+
if (options.open) {
|
|
5615
|
+
setTimeout(() => {
|
|
5616
|
+
openBrowser(`http://localhost:${port}`);
|
|
5617
|
+
}, 500);
|
|
5618
|
+
}
|
|
5619
|
+
console.log(`
|
|
5620
|
+
\u{1F4CA} Dashboard: http://localhost:${port}`);
|
|
5621
|
+
console.log("Press Ctrl+C to stop the server\n");
|
|
5622
|
+
const shutdown = () => {
|
|
5623
|
+
console.log("\n\n\u{1F44B} Shutting down dashboard...");
|
|
5624
|
+
stopServer();
|
|
5625
|
+
process.exit(0);
|
|
5626
|
+
};
|
|
5627
|
+
process.on("SIGINT", shutdown);
|
|
5628
|
+
process.on("SIGTERM", shutdown);
|
|
5629
|
+
await new Promise(() => {
|
|
5630
|
+
});
|
|
5631
|
+
} catch (error) {
|
|
5632
|
+
console.error("Dashboard failed:", error);
|
|
5633
|
+
process.exit(1);
|
|
5634
|
+
}
|
|
5635
|
+
});
|
|
5636
|
+
function openBrowser(url) {
|
|
5637
|
+
const platform = process.platform;
|
|
5638
|
+
let command;
|
|
5639
|
+
if (platform === "darwin") {
|
|
5640
|
+
command = `open "${url}"`;
|
|
5641
|
+
} else if (platform === "win32") {
|
|
5642
|
+
command = `start "" "${url}"`;
|
|
5643
|
+
} else {
|
|
5644
|
+
command = `xdg-open "${url}"`;
|
|
5645
|
+
}
|
|
5646
|
+
exec(command, (error) => {
|
|
5647
|
+
if (error) {
|
|
5648
|
+
console.log(`
|
|
5649
|
+
\u26A0\uFE0F Could not open browser automatically.`);
|
|
5650
|
+
console.log(` Please open ${url} manually.
|
|
5651
|
+
`);
|
|
5652
|
+
}
|
|
5653
|
+
});
|
|
5654
|
+
}
|
|
4571
5655
|
program.parse();
|
|
4572
5656
|
//# sourceMappingURL=index.js.map
|