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.
Files changed (45) hide show
  1. package/.claude/settings.local.json +4 -1
  2. package/.claude-plugin/plugin.json +3 -3
  3. package/.history/package_20260201142928.json +46 -0
  4. package/.history/package_20260201192048.json +47 -0
  5. package/README.md +26 -26
  6. package/dist/.claude-plugin/plugin.json +3 -3
  7. package/dist/cli/index.js +1109 -25
  8. package/dist/cli/index.js.map +4 -4
  9. package/dist/core/index.js +192 -5
  10. package/dist/core/index.js.map +4 -4
  11. package/dist/hooks/session-end.js +262 -18
  12. package/dist/hooks/session-end.js.map +4 -4
  13. package/dist/hooks/session-start.js +262 -18
  14. package/dist/hooks/session-start.js.map +4 -4
  15. package/dist/hooks/stop.js +262 -18
  16. package/dist/hooks/stop.js.map +4 -4
  17. package/dist/hooks/user-prompt-submit.js +262 -18
  18. package/dist/hooks/user-prompt-submit.js.map +4 -4
  19. package/dist/server/api/index.js +4728 -0
  20. package/dist/server/api/index.js.map +7 -0
  21. package/dist/server/index.js +4790 -0
  22. package/dist/server/index.js.map +7 -0
  23. package/dist/services/memory-service.js +269 -18
  24. package/dist/services/memory-service.js.map +4 -4
  25. package/dist/ui/index.html +1225 -0
  26. package/package.json +4 -2
  27. package/scripts/build.ts +33 -3
  28. package/src/cli/index.ts +311 -6
  29. package/src/core/db-wrapper.ts +8 -1
  30. package/src/core/event-store.ts +52 -3
  31. package/src/core/graduation-worker.ts +171 -0
  32. package/src/core/graduation.ts +15 -2
  33. package/src/core/index.ts +1 -0
  34. package/src/core/retriever.ts +18 -0
  35. package/src/core/types.ts +1 -1
  36. package/src/mcp/index.ts +2 -2
  37. package/src/mcp/tools.ts +1 -1
  38. package/src/server/api/citations.ts +7 -3
  39. package/src/server/api/events.ts +7 -3
  40. package/src/server/api/search.ts +7 -3
  41. package/src/server/api/sessions.ts +7 -3
  42. package/src/server/api/stats.ts +175 -5
  43. package/src/server/index.ts +18 -9
  44. package/src/services/memory-service.ts +107 -19
  45. 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(path3) {
74
- return new duckdb.Database(path3);
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.db = createDatabase(dbPath);
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
- if (!fs.existsSync(storagePath)) {
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.vectorWorker = createVectorWorker(
3414
- this.eventStore,
3415
- this.vectorStore,
3416
- this.embedder
3417
- );
3418
- this.vectorWorker.start();
3419
- const savedMode = await this.eventStore.getEndlessConfig("mode");
3420
- if (savedMode === "endless") {
3421
- this.endlessMode = "endless";
3422
- await this.initializeEndlessMode();
3423
- }
3424
- if (this.sharedStoreConfig?.enabled !== false) {
3425
- await this.initializeSharedStore();
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("code-memory").description("Claude Code Memory Plugin CLI").version("1.0.0");
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 "code-memory import --session <path>" to import a specific session');
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 "code-memory endless status" to view current state');
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 "code-memory endless enable" to activate.');
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 "code-memory endless enable" first');
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 "code-memory endless enable" first');
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