memorix 0.5.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -2631,10 +2631,10 @@ var init_engine2 = __esm({
2631
2631
  for (const [target, adapter] of this.adapters) {
2632
2632
  const configPath = adapter.getConfigPath(this.projectRoot);
2633
2633
  const globalPath = adapter.getConfigPath();
2634
- for (const path6 of [configPath, globalPath]) {
2635
- if (existsSync4(path6)) {
2634
+ for (const path8 of [configPath, globalPath]) {
2635
+ if (existsSync4(path8)) {
2636
2636
  try {
2637
- const content = readFileSync2(path6, "utf-8");
2637
+ const content = readFileSync2(path8, "utf-8");
2638
2638
  const servers = adapter.parse(content);
2639
2639
  if (servers.length > 0) {
2640
2640
  mcpConfigs[target] = servers;
@@ -3376,9 +3376,202 @@ var init_retention = __esm({
3376
3376
  }
3377
3377
  });
3378
3378
 
3379
- // src/server.ts
3379
+ // src/dashboard/server.ts
3380
3380
  var server_exports = {};
3381
3381
  __export(server_exports, {
3382
+ startDashboard: () => startDashboard
3383
+ });
3384
+ import { createServer } from "http";
3385
+ import { promises as fs4 } from "fs";
3386
+ import path6 from "path";
3387
+ import { exec } from "child_process";
3388
+ function sendJson(res, data, status = 200) {
3389
+ res.writeHead(status, {
3390
+ "Content-Type": "application/json; charset=utf-8",
3391
+ "Access-Control-Allow-Origin": "*"
3392
+ });
3393
+ res.end(JSON.stringify(data));
3394
+ }
3395
+ function sendError(res, message, status = 500) {
3396
+ sendJson(res, { error: message }, status);
3397
+ }
3398
+ function filterByProject(items, projectId) {
3399
+ return items.filter((item) => item.projectId === projectId);
3400
+ }
3401
+ async function handleApi(req, res, dataDir, projectId, projectName) {
3402
+ const url = new URL(req.url || "/", `http://${req.headers.host}`);
3403
+ const apiPath = url.pathname.replace("/api", "");
3404
+ try {
3405
+ switch (apiPath) {
3406
+ case "/project": {
3407
+ sendJson(res, { id: projectId, name: projectName });
3408
+ break;
3409
+ }
3410
+ case "/graph": {
3411
+ const graph = await loadGraphJsonl(dataDir);
3412
+ sendJson(res, graph);
3413
+ break;
3414
+ }
3415
+ case "/observations": {
3416
+ const allObs = await loadObservationsJson(dataDir);
3417
+ const observations2 = filterByProject(allObs, projectId);
3418
+ sendJson(res, observations2);
3419
+ break;
3420
+ }
3421
+ case "/stats": {
3422
+ const graph = await loadGraphJsonl(dataDir);
3423
+ const allObs = await loadObservationsJson(dataDir);
3424
+ const observations2 = filterByProject(allObs, projectId);
3425
+ const nextId2 = await loadIdCounter(dataDir);
3426
+ const typeCounts = {};
3427
+ for (const obs of observations2) {
3428
+ const t = obs.type || "unknown";
3429
+ typeCounts[t] = (typeCounts[t] || 0) + 1;
3430
+ }
3431
+ const sorted = [...observations2].sort((a, b) => (b.id || 0) - (a.id || 0)).slice(0, 10);
3432
+ sendJson(res, {
3433
+ entities: graph.entities.length,
3434
+ relations: graph.relations.length,
3435
+ observations: observations2.length,
3436
+ nextId: nextId2,
3437
+ typeCounts,
3438
+ recentObservations: sorted
3439
+ });
3440
+ break;
3441
+ }
3442
+ case "/retention": {
3443
+ const allObs = await loadObservationsJson(dataDir);
3444
+ const observations2 = filterByProject(allObs, projectId);
3445
+ const now = Date.now();
3446
+ const scored = observations2.map((obs) => {
3447
+ const age = now - new Date(obs.createdAt || now).getTime();
3448
+ const ageHours = age / (1e3 * 60 * 60);
3449
+ const importance = obs.importance ?? 5;
3450
+ const accessCount = obs.accessCount ?? 0;
3451
+ const lambda = 0.01;
3452
+ const decayScore = importance * Math.exp(-lambda * ageHours);
3453
+ const accessBonus = Math.min(accessCount * 0.5, 3);
3454
+ const score = Math.min(decayScore + accessBonus, 10);
3455
+ const isImmune2 = importance >= 8 || obs.type === "gotcha" || obs.type === "decision";
3456
+ return {
3457
+ id: obs.id,
3458
+ title: obs.title,
3459
+ type: obs.type,
3460
+ entityName: obs.entityName,
3461
+ score: Math.round(score * 100) / 100,
3462
+ isImmune: isImmune2,
3463
+ ageHours: Math.round(ageHours * 10) / 10,
3464
+ accessCount
3465
+ };
3466
+ });
3467
+ scored.sort((a, b) => b.score - a.score);
3468
+ const activeCount = scored.filter((s) => s.score >= 3).length;
3469
+ const staleCount = scored.filter((s) => s.score < 3 && s.score >= 1).length;
3470
+ const archiveCount = scored.filter((s) => s.score < 1).length;
3471
+ const immuneCount = scored.filter((s) => s.isImmune).length;
3472
+ sendJson(res, {
3473
+ summary: { active: activeCount, stale: staleCount, archive: archiveCount, immune: immuneCount },
3474
+ items: scored
3475
+ });
3476
+ break;
3477
+ }
3478
+ default:
3479
+ sendError(res, "Not found", 404);
3480
+ }
3481
+ } catch (err) {
3482
+ const message = err instanceof Error ? err.message : "Unknown error";
3483
+ sendError(res, message);
3484
+ }
3485
+ }
3486
+ async function serveStatic(req, res, staticDir) {
3487
+ let urlPath = new URL(req.url || "/", `http://${req.headers.host}`).pathname;
3488
+ if (urlPath === "/" || !urlPath.includes(".")) {
3489
+ urlPath = "/index.html";
3490
+ }
3491
+ const filePath = path6.join(staticDir, urlPath);
3492
+ if (!filePath.startsWith(staticDir)) {
3493
+ sendError(res, "Forbidden", 403);
3494
+ return;
3495
+ }
3496
+ try {
3497
+ const data = await fs4.readFile(filePath);
3498
+ const ext = path6.extname(filePath);
3499
+ res.writeHead(200, {
3500
+ "Content-Type": MIME_TYPES[ext] || "application/octet-stream",
3501
+ "Cache-Control": "no-cache"
3502
+ });
3503
+ res.end(data);
3504
+ } catch {
3505
+ try {
3506
+ const indexData = await fs4.readFile(path6.join(staticDir, "index.html"));
3507
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
3508
+ res.end(indexData);
3509
+ } catch {
3510
+ sendError(res, "Not found", 404);
3511
+ }
3512
+ }
3513
+ }
3514
+ function openBrowser(url) {
3515
+ const cmd = process.platform === "win32" ? `start "" "${url}"` : process.platform === "darwin" ? `open "${url}"` : `xdg-open "${url}"`;
3516
+ exec(cmd, () => {
3517
+ });
3518
+ }
3519
+ async function startDashboard(dataDir, port, staticDir, projectId, projectName, autoOpen = true) {
3520
+ const resolvedStaticDir = staticDir;
3521
+ const server = createServer(async (req, res) => {
3522
+ const url = req.url || "/";
3523
+ if (url.startsWith("/api/")) {
3524
+ await handleApi(req, res, dataDir, projectId, projectName);
3525
+ } else {
3526
+ await serveStatic(req, res, resolvedStaticDir);
3527
+ }
3528
+ });
3529
+ return new Promise((resolve2, reject) => {
3530
+ server.on("error", (err) => {
3531
+ if (err.code === "EADDRINUSE") {
3532
+ console.error(`Port ${port} is already in use. Try: memorix dashboard --port ${port + 1}`);
3533
+ reject(err);
3534
+ } else {
3535
+ reject(err);
3536
+ }
3537
+ });
3538
+ server.listen(port, () => {
3539
+ const url = `http://localhost:${port}`;
3540
+ console.error(`
3541
+ Memorix Dashboard`);
3542
+ console.error(` \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`);
3543
+ console.error(` Project: ${projectName} (${projectId})`);
3544
+ console.error(` Local: ${url}`);
3545
+ console.error(` Data dir: ${dataDir}`);
3546
+ console.error(`
3547
+ Press Ctrl+C to stop
3548
+ `);
3549
+ if (autoOpen) openBrowser(url);
3550
+ resolve2();
3551
+ });
3552
+ });
3553
+ }
3554
+ var MIME_TYPES;
3555
+ var init_server = __esm({
3556
+ "src/dashboard/server.ts"() {
3557
+ "use strict";
3558
+ init_esm_shims();
3559
+ init_persistence();
3560
+ MIME_TYPES = {
3561
+ ".html": "text/html; charset=utf-8",
3562
+ ".css": "text/css; charset=utf-8",
3563
+ ".js": "application/javascript; charset=utf-8",
3564
+ ".json": "application/json; charset=utf-8",
3565
+ ".svg": "image/svg+xml",
3566
+ ".png": "image/png",
3567
+ ".ico": "image/x-icon"
3568
+ };
3569
+ }
3570
+ });
3571
+
3572
+ // src/server.ts
3573
+ var server_exports2 = {};
3574
+ __export(server_exports2, {
3382
3575
  createMemorixServer: () => createMemorixServer
3383
3576
  });
3384
3577
  import { watch } from "fs";
@@ -3996,10 +4189,89 @@ Entity: ${entityName} | Type: ${type} | Project: ${project.id}${enrichment}`
3996
4189
  };
3997
4190
  }
3998
4191
  );
4192
+ let dashboardRunning = false;
4193
+ server.registerTool(
4194
+ "memorix_dashboard",
4195
+ {
4196
+ title: "Launch Dashboard",
4197
+ description: "Launch the Memorix Web Dashboard in the browser. Shows knowledge graph, observations, retention scores, and project stats in a visual interface.",
4198
+ inputSchema: {
4199
+ port: z.number().optional().describe("Port to run the dashboard on (default: 3210)")
4200
+ }
4201
+ },
4202
+ async ({ port: dashboardPort }) => {
4203
+ const portNum = dashboardPort || 3210;
4204
+ const url = `http://localhost:${portNum}`;
4205
+ if (dashboardRunning) {
4206
+ const { exec: exec2 } = await import("child_process");
4207
+ const cmd = process.platform === "win32" ? `start "" "${url}"` : process.platform === "darwin" ? `open "${url}"` : `xdg-open "${url}"`;
4208
+ exec2(cmd, () => {
4209
+ });
4210
+ return {
4211
+ content: [{ type: "text", text: `Dashboard is already running at ${url}. Opened in browser.` }]
4212
+ };
4213
+ }
4214
+ try {
4215
+ const pathMod = await import("path");
4216
+ const fsMod = await import("fs");
4217
+ const { fileURLToPath: fileURLToPath3 } = await import("url");
4218
+ const { startDashboard: startDashboard2 } = await Promise.resolve().then(() => (init_server(), server_exports));
4219
+ const candidates = [
4220
+ pathMod.default.join(__dirname, "..", "dashboard", "static"),
4221
+ pathMod.default.join(__dirname, "dashboard", "static"),
4222
+ pathMod.default.join(pathMod.default.dirname(fileURLToPath3(import.meta.url)), "..", "dashboard", "static"),
4223
+ pathMod.default.join(pathMod.default.dirname(fileURLToPath3(import.meta.url)), "dashboard", "static")
4224
+ ];
4225
+ for (const [i, c] of candidates.entries()) {
4226
+ const hasIndex = fsMod.existsSync(pathMod.default.join(c, "index.html"));
4227
+ console.error(`[memorix] candidate[${i}]: ${c} (has index.html: ${hasIndex})`);
4228
+ }
4229
+ let staticDir = candidates[0];
4230
+ for (const c of candidates) {
4231
+ if (fsMod.existsSync(pathMod.default.join(c, "index.html"))) {
4232
+ staticDir = c;
4233
+ break;
4234
+ }
4235
+ }
4236
+ console.error(`[memorix] Dashboard staticDir: ${staticDir}`);
4237
+ startDashboard2(projectDir2, portNum, staticDir, project.id, project.name, false).then(() => {
4238
+ dashboardRunning = true;
4239
+ }).catch((err) => {
4240
+ console.error("[memorix] Dashboard error:", err);
4241
+ dashboardRunning = false;
4242
+ });
4243
+ await new Promise((resolve2) => setTimeout(resolve2, 800));
4244
+ dashboardRunning = true;
4245
+ const { exec: execCmd } = await import("child_process");
4246
+ const openCmd = process.platform === "win32" ? `start "" "${url}"` : process.platform === "darwin" ? `open "${url}"` : `xdg-open "${url}"`;
4247
+ execCmd(openCmd, () => {
4248
+ });
4249
+ return {
4250
+ content: [{
4251
+ type: "text",
4252
+ text: [
4253
+ `Memorix Dashboard started!`,
4254
+ ``,
4255
+ `URL: ${url}`,
4256
+ `Project: ${project.name} (${project.id})`,
4257
+ `Static: ${staticDir}`,
4258
+ ``,
4259
+ `The dashboard has been opened in your default browser.`,
4260
+ `It shows your knowledge graph, observations, retention scores, and project stats.`
4261
+ ].join("\n")
4262
+ }]
4263
+ };
4264
+ } catch (err) {
4265
+ return {
4266
+ content: [{ type: "text", text: `Failed to start dashboard: ${err instanceof Error ? err.message : String(err)}` }]
4267
+ };
4268
+ }
4269
+ }
4270
+ );
3999
4271
  return { server, graphManager, projectId: project.id };
4000
4272
  }
4001
4273
  var OBSERVATION_TYPES;
4002
- var init_server = __esm({
4274
+ var init_server2 = __esm({
4003
4275
  "src/server.ts"() {
4004
4276
  "use strict";
4005
4277
  init_esm_shims();
@@ -4052,7 +4324,7 @@ var init_serve = __esm({
4052
4324
  },
4053
4325
  run: async ({ args }) => {
4054
4326
  const { StdioServerTransport } = await import("@modelcontextprotocol/sdk/server/stdio.js");
4055
- const { createMemorixServer: createMemorixServer2 } = await Promise.resolve().then(() => (init_server(), server_exports));
4327
+ const { createMemorixServer: createMemorixServer2 } = await Promise.resolve().then(() => (init_server2(), server_exports2));
4056
4328
  const projectRoot = args.cwd || process.cwd();
4057
4329
  const { server, projectId } = await createMemorixServer2(projectRoot);
4058
4330
  const transport = new StdioServerTransport();
@@ -4164,8 +4436,8 @@ var init_sync = __esm({
4164
4436
  run: async ({ args }) => {
4165
4437
  const { detectProject: detectProject2 } = await Promise.resolve().then(() => (init_detector(), detector_exports));
4166
4438
  const { RulesSyncer: RulesSyncer2 } = await Promise.resolve().then(() => (init_syncer(), syncer_exports));
4167
- const { promises: fs4 } = await import("fs");
4168
- const path6 = await import("path");
4439
+ const { promises: fs5 } = await import("fs");
4440
+ const path8 = await import("path");
4169
4441
  p2.intro("memorix sync");
4170
4442
  const project = detectProject2();
4171
4443
  p2.log.info(`Project: ${project.name} (${project.id})`);
@@ -4233,9 +4505,9 @@ var init_sync = __esm({
4233
4505
  process.exit(0);
4234
4506
  }
4235
4507
  for (const file of files) {
4236
- const fullPath = path6.join(project.rootPath, file.filePath);
4237
- await fs4.mkdir(path6.dirname(fullPath), { recursive: true });
4238
- await fs4.writeFile(fullPath, file.content, "utf-8");
4508
+ const fullPath = path8.join(project.rootPath, file.filePath);
4509
+ await fs5.mkdir(path8.dirname(fullPath), { recursive: true });
4510
+ await fs5.writeFile(fullPath, file.content, "utf-8");
4239
4511
  p2.log.success(`Written: ${file.filePath}`);
4240
4512
  }
4241
4513
  p2.outro(`Synced ${files.length} rule(s) to ${target} format`);
@@ -4960,10 +5232,52 @@ var init_hooks = __esm({
4960
5232
  }
4961
5233
  });
4962
5234
 
5235
+ // src/cli/commands/dashboard.ts
5236
+ var dashboard_exports = {};
5237
+ __export(dashboard_exports, {
5238
+ default: () => dashboard_default
5239
+ });
5240
+ import { defineCommand as defineCommand9 } from "citty";
5241
+ import { fileURLToPath as fileURLToPath2 } from "url";
5242
+ import path7 from "path";
5243
+ var dashboard_default;
5244
+ var init_dashboard = __esm({
5245
+ "src/cli/commands/dashboard.ts"() {
5246
+ "use strict";
5247
+ init_esm_shims();
5248
+ dashboard_default = defineCommand9({
5249
+ meta: {
5250
+ name: "dashboard",
5251
+ description: "Launch the Memorix Web Dashboard"
5252
+ },
5253
+ args: {
5254
+ port: {
5255
+ type: "string",
5256
+ description: "Port to run the dashboard on (default: 3210)",
5257
+ default: "3210"
5258
+ }
5259
+ },
5260
+ run: async ({ args }) => {
5261
+ const { detectProject: detectProject2 } = await Promise.resolve().then(() => (init_detector(), detector_exports));
5262
+ const { getProjectDataDir: getProjectDataDir2 } = await Promise.resolve().then(() => (init_persistence(), persistence_exports));
5263
+ const { startDashboard: startDashboard2 } = await Promise.resolve().then(() => (init_server(), server_exports));
5264
+ const project = detectProject2();
5265
+ const dataDir = await getProjectDataDir2(project.id);
5266
+ const port = parseInt(args.port, 10) || 3210;
5267
+ const cliDir = path7.dirname(fileURLToPath2(import.meta.url));
5268
+ const staticDir = path7.join(cliDir, "..", "dashboard", "static");
5269
+ await startDashboard2(dataDir, port, staticDir, project.id, project.name);
5270
+ await new Promise(() => {
5271
+ });
5272
+ }
5273
+ });
5274
+ }
5275
+ });
5276
+
4963
5277
  // src/cli/index.ts
4964
5278
  init_esm_shims();
4965
- import { defineCommand as defineCommand9, runMain } from "citty";
4966
- var main = defineCommand9({
5279
+ import { defineCommand as defineCommand10, runMain } from "citty";
5280
+ var main = defineCommand10({
4967
5281
  meta: {
4968
5282
  name: "memorix",
4969
5283
  version: "0.3.9",
@@ -4974,7 +5288,8 @@ var main = defineCommand9({
4974
5288
  status: () => Promise.resolve().then(() => (init_status(), status_exports)).then((m) => m.default),
4975
5289
  sync: () => Promise.resolve().then(() => (init_sync(), sync_exports)).then((m) => m.default),
4976
5290
  hook: () => Promise.resolve().then(() => (init_hook(), hook_exports)).then((m) => m.default),
4977
- hooks: () => Promise.resolve().then(() => (init_hooks(), hooks_exports)).then((m) => m.default)
5291
+ hooks: () => Promise.resolve().then(() => (init_hooks(), hooks_exports)).then((m) => m.default),
5292
+ dashboard: () => Promise.resolve().then(() => (init_dashboard(), dashboard_exports)).then((m) => m.default)
4978
5293
  },
4979
5294
  run() {
4980
5295
  }