@hasna/mementos 0.4.24 → 0.4.26

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/mcp/index.js CHANGED
@@ -4728,6 +4728,33 @@ function listEntities(filter = {}, db) {
4728
4728
  const rows = d.query(sql).all(...params);
4729
4729
  return rows.map(parseEntityRow);
4730
4730
  }
4731
+ function updateEntity(id, input, db) {
4732
+ const d = db || getDatabase();
4733
+ const existing = d.query("SELECT id FROM entities WHERE id = ?").get(id);
4734
+ if (!existing)
4735
+ throw new EntityNotFoundError(id);
4736
+ const sets = ["updated_at = ?"];
4737
+ const params = [now()];
4738
+ if (input.name !== undefined) {
4739
+ sets.push("name = ?");
4740
+ params.push(input.name);
4741
+ }
4742
+ if (input.type !== undefined) {
4743
+ sets.push("type = ?");
4744
+ params.push(input.type);
4745
+ }
4746
+ if (input.description !== undefined) {
4747
+ sets.push("description = ?");
4748
+ params.push(input.description);
4749
+ }
4750
+ if (input.metadata !== undefined) {
4751
+ sets.push("metadata = ?");
4752
+ params.push(JSON.stringify(input.metadata));
4753
+ }
4754
+ params.push(id);
4755
+ d.run(`UPDATE entities SET ${sets.join(", ")} WHERE id = ?`, params);
4756
+ return getEntity(id, d);
4757
+ }
4731
4758
  function deleteEntity(id, db) {
4732
4759
  const d = db || getDatabase();
4733
4760
  const result = d.run("DELETE FROM entities WHERE id = ?", [id]);
@@ -4839,6 +4866,13 @@ function createRelation(input, db) {
4839
4866
  WHERE source_entity_id = ? AND target_entity_id = ? AND relation_type = ?`).get(input.source_entity_id, input.target_entity_id, input.relation_type);
4840
4867
  return parseRelationRow(row);
4841
4868
  }
4869
+ function getRelation(id, db) {
4870
+ const d = db || getDatabase();
4871
+ const row = d.query("SELECT * FROM relations WHERE id = ?").get(id);
4872
+ if (!row)
4873
+ throw new Error(`Relation not found: ${id}`);
4874
+ return parseRelationRow(row);
4875
+ }
4842
4876
  function listRelations(filter, db) {
4843
4877
  const d = db || getDatabase();
4844
4878
  const conditions = [];
@@ -6454,6 +6488,44 @@ ${lines.join(`
6454
6488
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
6455
6489
  }
6456
6490
  });
6491
+ server.tool("memory_report", "Get a rich summary report: totals, activity trend, top memories, scope/category breakdown.", {
6492
+ days: exports_external.coerce.number().optional(),
6493
+ project_id: exports_external.string().optional(),
6494
+ agent_id: exports_external.string().optional()
6495
+ }, async (args) => {
6496
+ try {
6497
+ const days = Math.min(args.days || 7, 365);
6498
+ const db = getDatabase();
6499
+ const cond = [args.project_id ? "AND project_id = ?" : "", args.agent_id ? "AND agent_id = ?" : ""].filter(Boolean).join(" ");
6500
+ const params = [...args.project_id ? [args.project_id] : [], ...args.agent_id ? [args.agent_id] : []];
6501
+ const total = db.query(`SELECT COUNT(*) as c FROM memories WHERE status = 'active' ${cond}`).get(...params).c;
6502
+ const pinned = db.query(`SELECT COUNT(*) as c FROM memories WHERE status = 'active' AND pinned = 1 ${cond}`).get(...params).c;
6503
+ const actRows = db.query(`SELECT date(created_at) AS d, COUNT(*) AS cnt FROM memories WHERE status = 'active' AND date(created_at) >= date('now', '-${days} days') ${cond} GROUP BY d ORDER BY d`).all(...params);
6504
+ const recentTotal = actRows.reduce((s, r) => s + r.cnt, 0);
6505
+ const sparkline = actRows.length > 0 ? actRows.map((r) => {
6506
+ const bars = "\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588";
6507
+ const max = Math.max(...actRows.map((x) => x.cnt), 1);
6508
+ return bars[Math.round(r.cnt / max * 7)] || "\u2581";
6509
+ }).join("") : "\u2014";
6510
+ const byScopeRows = db.query(`SELECT scope, COUNT(*) as c FROM memories WHERE status = 'active' ${cond} GROUP BY scope`).all(...params);
6511
+ const byCatRows = db.query(`SELECT category, COUNT(*) as c FROM memories WHERE status = 'active' ${cond} GROUP BY category`).all(...params);
6512
+ const topMems = db.query(`SELECT key, value, importance FROM memories WHERE status = 'active' ${cond} ORDER BY importance DESC, access_count DESC LIMIT 5`).all(...params);
6513
+ const lines = [
6514
+ `Memory Report (last ${days} days)`,
6515
+ `Total: ${total} (${pinned} pinned) | Recent: +${recentTotal} | Activity: ${sparkline}`,
6516
+ `Scopes: ${byScopeRows.map((r) => `${r.scope}=${r.c}`).join(" ")}`,
6517
+ `Categories: ${byCatRows.map((r) => `${r.category}=${r.c}`).join(" ")}`,
6518
+ topMems.length > 0 ? `
6519
+ Top memories:
6520
+ ${topMems.map((m) => ` [${m.importance}] ${m.key}: ${m.value.slice(0, 80)}${m.value.length > 80 ? "..." : ""}`).join(`
6521
+ `)}` : ""
6522
+ ].filter(Boolean);
6523
+ return { content: [{ type: "text", text: lines.join(`
6524
+ `) }] };
6525
+ } catch (e) {
6526
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
6527
+ }
6528
+ });
6457
6529
  server.tool("memory_export", "Export memories as JSON", {
6458
6530
  scope: exports_external.enum(["global", "shared", "private"]).optional(),
6459
6531
  category: exports_external.enum(["preference", "fact", "knowledge", "history"]).optional(),
@@ -6991,6 +7063,46 @@ server.tool("entity_link", "Link an entity to a memory with a role (subject, obj
6991
7063
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
6992
7064
  }
6993
7065
  });
7066
+ server.tool("entity_update", "Update an entity's name, description, or metadata.", {
7067
+ entity_name_or_id: exports_external.string(),
7068
+ name: exports_external.string().optional(),
7069
+ description: exports_external.string().nullable().optional(),
7070
+ metadata: exports_external.record(exports_external.unknown()).optional()
7071
+ }, async (args) => {
7072
+ try {
7073
+ const entity = resolveEntityParam(args.entity_name_or_id);
7074
+ const { entity_name_or_id: _id, ...updates } = args;
7075
+ const updated = updateEntity(entity.id, updates);
7076
+ return { content: [{ type: "text", text: `Updated entity: ${updated.name} (${updated.id.slice(0, 8)})` }] };
7077
+ } catch (e) {
7078
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
7079
+ }
7080
+ });
7081
+ server.tool("entity_unlink", "Unlink a memory from an entity.", {
7082
+ entity_name_or_id: exports_external.string(),
7083
+ memory_id: exports_external.string()
7084
+ }, async (args) => {
7085
+ try {
7086
+ const entity = resolveEntityParam(args.entity_name_or_id);
7087
+ const memoryId = resolveId(args.memory_id);
7088
+ unlinkEntityFromMemory(entity.id, memoryId);
7089
+ return { content: [{ type: "text", text: `Unlinked: ${entity.name} \u219B memory ${memoryId.slice(0, 8)}` }] };
7090
+ } catch (e) {
7091
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
7092
+ }
7093
+ });
7094
+ server.tool("relation_get", "Get a relation by ID.", {
7095
+ id: exports_external.string()
7096
+ }, async (args) => {
7097
+ try {
7098
+ const relation = getRelation(args.id);
7099
+ if (!relation)
7100
+ return { content: [{ type: "text", text: `Relation not found: ${args.id}` }] };
7101
+ return { content: [{ type: "text", text: `Relation ${relation.id.slice(0, 8)}: ${relation.source_entity_id.slice(0, 8)} \u2014[${relation.relation_type}]\u2192 ${relation.target_entity_id.slice(0, 8)} (weight: ${relation.weight})` }] };
7102
+ } catch (e) {
7103
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
7104
+ }
7105
+ });
6994
7106
  server.tool("relation_create", "Create a relation between two entities (uses, knows, depends_on, created_by, related_to, contradicts, part_of, implements).", {
6995
7107
  source_entity: exports_external.string(),
6996
7108
  target_entity: exports_external.string(),
@@ -7427,6 +7539,44 @@ var FULL_SCHEMAS = {
7427
7539
  params: {},
7428
7540
  example: "{}"
7429
7541
  },
7542
+ memory_report: {
7543
+ description: "Rich summary: total/pinned counts, sparkline activity, scope/category breakdown, top 5 memories by importance.",
7544
+ category: "memory",
7545
+ params: {
7546
+ days: { type: "number", description: "Activity window in days (default 7)" },
7547
+ project_id: { type: "string", description: "Filter by project" },
7548
+ agent_id: { type: "string", description: "Filter by agent" }
7549
+ },
7550
+ example: '{"days":7,"project_id":"proj-uuid"}'
7551
+ },
7552
+ entity_update: {
7553
+ description: "Update an entity's name, description, or metadata.",
7554
+ category: "graph",
7555
+ params: {
7556
+ entity_name_or_id: { type: "string", description: "Entity name or ID", required: true },
7557
+ name: { type: "string", description: "New name" },
7558
+ description: { type: "string", description: "New description (null to clear)" },
7559
+ metadata: { type: "object", description: "New metadata" }
7560
+ },
7561
+ example: '{"entity_name_or_id":"TypeScript","description":"Typed superset of JavaScript by Microsoft"}'
7562
+ },
7563
+ entity_unlink: {
7564
+ description: "Remove the link between an entity and a memory.",
7565
+ category: "graph",
7566
+ params: {
7567
+ entity_name_or_id: { type: "string", description: "Entity name or ID", required: true },
7568
+ memory_id: { type: "string", description: "Memory ID (partial OK)", required: true }
7569
+ },
7570
+ example: '{"entity_name_or_id":"TypeScript","memory_id":"abc12345"}'
7571
+ },
7572
+ relation_get: {
7573
+ description: "Get a specific relation by ID.",
7574
+ category: "graph",
7575
+ params: {
7576
+ id: { type: "string", description: "Relation ID", required: true }
7577
+ },
7578
+ example: '{"id":"rel-uuid"}'
7579
+ },
7430
7580
  entity_create: {
7431
7581
  description: "Create a knowledge graph entity.",
7432
7582
  category: "graph",
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":";AACA;;;GAGG;AAkoCH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAsH9C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":";AACA;;;GAGG;AA2qCH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAsH9C"}
@@ -2293,6 +2293,40 @@ addRoute("GET", "/api/activity", (_req, url) => {
2293
2293
  `).all(...params);
2294
2294
  return json({ activity: rows, days, total: rows.reduce((s, r) => s + r.memories_created, 0) });
2295
2295
  });
2296
+ addRoute("GET", "/api/report", (_req, url) => {
2297
+ const q = getSearchParams(url);
2298
+ const days = Math.min(parseInt(q["days"] || "7", 10), 365);
2299
+ const projectId = q["project_id"];
2300
+ const agentId = q["agent_id"];
2301
+ const db = getDatabase();
2302
+ const cond = [
2303
+ projectId ? "AND project_id = ?" : "",
2304
+ agentId ? "AND agent_id = ?" : ""
2305
+ ].filter(Boolean).join(" ");
2306
+ const params = [...projectId ? [projectId] : [], ...agentId ? [agentId] : []];
2307
+ const total = db.query(`SELECT COUNT(*) as c FROM memories WHERE status = 'active' ${cond}`).get(...params).c;
2308
+ const pinned = db.query(`SELECT COUNT(*) as c FROM memories WHERE status = 'active' AND pinned = 1 ${cond}`).get(...params).c;
2309
+ const actRows = db.query(`
2310
+ SELECT date(created_at) AS date, COUNT(*) AS memories_created
2311
+ FROM memories WHERE status = 'active' AND date(created_at) >= date('now', '-${days} days') ${cond}
2312
+ GROUP BY date(created_at) ORDER BY date(created_at) ASC
2313
+ `).all(...params);
2314
+ const recentTotal = actRows.reduce((s, r) => s + r.memories_created, 0);
2315
+ const byScopeRows = db.query(`SELECT scope, COUNT(*) as c FROM memories WHERE status = 'active' ${cond} GROUP BY scope`).all(...params);
2316
+ const byCatRows = db.query(`SELECT category, COUNT(*) as c FROM memories WHERE status = 'active' ${cond} GROUP BY category`).all(...params);
2317
+ const topMems = db.query(`SELECT id, key, value, importance, scope, category FROM memories WHERE status = 'active' ${cond} ORDER BY importance DESC, access_count DESC LIMIT 5`).all(...params);
2318
+ const topAgents = db.query(`SELECT agent_id, COUNT(*) as c FROM memories WHERE status = 'active' AND agent_id IS NOT NULL ${cond} GROUP BY agent_id ORDER BY c DESC LIMIT 5`).all(...params);
2319
+ return json({
2320
+ total,
2321
+ pinned,
2322
+ days,
2323
+ recent: { total: recentTotal, activity: actRows },
2324
+ by_scope: Object.fromEntries(byScopeRows.map((r) => [r.scope, r.c])),
2325
+ by_category: Object.fromEntries(byCatRows.map((r) => [r.category, r.c])),
2326
+ top_memories: topMems,
2327
+ top_agents: topAgents
2328
+ });
2329
+ });
2296
2330
  addRoute("POST", "/api/memories/search", async (req) => {
2297
2331
  const body = await readJson(req);
2298
2332
  if (!body || typeof body["query"] !== "string") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/mementos",
3
- "version": "0.4.24",
3
+ "version": "0.4.26",
4
4
  "description": "Universal memory system for AI agents - CLI + MCP server + library API",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",