@hasna/mementos 0.4.23 → 0.4.25

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
@@ -5110,6 +5110,90 @@ program2.command("stats").description("Show memory statistics").option("--format
5110
5110
  handleError(e);
5111
5111
  }
5112
5112
  });
5113
+ program2.command("report").description("Rich summary of memory activity and top memories").option("--days <n>", "Activity window in days (default: 7)", "7").option("--project <path>", "Filter by project path").option("--markdown", "Output as Markdown (for PRs, docs, etc.)").option("--json", "Output as JSON").action((opts) => {
5114
+ try {
5115
+ const globalOpts = program2.opts();
5116
+ const days = parseInt(opts.days, 10) || 7;
5117
+ const projectPath = opts.project || globalOpts.project;
5118
+ let projectId;
5119
+ if (projectPath) {
5120
+ const project = getProject(resolve3(projectPath));
5121
+ if (project)
5122
+ projectId = project.id;
5123
+ }
5124
+ const db = getDatabase();
5125
+ const conditions = projectId ? "AND project_id = ?" : "";
5126
+ const params = projectId ? [projectId] : [];
5127
+ const total = db.query(`SELECT COUNT(*) as c FROM memories WHERE status = 'active' ${conditions}`).get(...params).c;
5128
+ const pinned = db.query(`SELECT COUNT(*) as c FROM memories WHERE status = 'active' AND pinned = 1 ${conditions}`).get(...params).c;
5129
+ const activityRows = db.query(`
5130
+ SELECT date(created_at) AS d, COUNT(*) AS cnt
5131
+ FROM memories WHERE status = 'active' AND date(created_at) >= date('now', '-${days} days') ${conditions}
5132
+ GROUP BY d ORDER BY d ASC
5133
+ `).all(...params);
5134
+ const recentTotal = activityRows.reduce((s, r) => s + r.cnt, 0);
5135
+ const avgPerDay = activityRows.length > 0 ? (recentTotal / activityRows.length).toFixed(1) : "0";
5136
+ const byScopeRows = db.query(`SELECT scope, COUNT(*) as c FROM memories WHERE status = 'active' ${conditions} GROUP BY scope`).all(...params);
5137
+ const byScope = Object.fromEntries(byScopeRows.map((r) => [r.scope, r.c]));
5138
+ const byCatRows = db.query(`SELECT category, COUNT(*) as c FROM memories WHERE status = 'active' ${conditions} GROUP BY category`).all(...params);
5139
+ const byCat = Object.fromEntries(byCatRows.map((r) => [r.category, r.c]));
5140
+ const topMems = db.query(`SELECT key, value, importance, scope, category FROM memories WHERE status = 'active' ${conditions} ORDER BY importance DESC, access_count DESC LIMIT 5`).all(...params);
5141
+ const topAgents = db.query(`SELECT agent_id, COUNT(*) as c FROM memories WHERE status = 'active' AND agent_id IS NOT NULL ${conditions} GROUP BY agent_id ORDER BY c DESC LIMIT 5`).all(...params);
5142
+ if (opts.json) {
5143
+ console.log(JSON.stringify({ total, pinned, recent: { days, total: recentTotal, avg_per_day: parseFloat(avgPerDay) }, by_scope: byScope, by_category: byCat, top_memories: topMems, top_agents: topAgents }, null, 2));
5144
+ return;
5145
+ }
5146
+ if (opts.markdown) {
5147
+ const lines = [
5148
+ `## Mementos Report (last ${days} days)`,
5149
+ "",
5150
+ `- **Total memories:** ${total} (${pinned} pinned)`,
5151
+ `- **Recent activity:** ${recentTotal} new in ${days} days (~${avgPerDay}/day)`,
5152
+ `- **Scopes:** global=${byScope["global"] || 0} shared=${byScope["shared"] || 0} private=${byScope["private"] || 0}`,
5153
+ `- **Categories:** knowledge=${byCat["knowledge"] || 0} fact=${byCat["fact"] || 0} preference=${byCat["preference"] || 0} history=${byCat["history"] || 0}`,
5154
+ "",
5155
+ "### Top Memories",
5156
+ ...topMems.map((m) => `- **${m.key}** (${m.scope}/${m.category}, imp:${m.importance}): ${m.value.slice(0, 80)}${m.value.length > 80 ? "..." : ""}`)
5157
+ ];
5158
+ if (topAgents.length > 0) {
5159
+ lines.push("", "### Top Agents", ...topAgents.map((a) => `- ${a.agent_id}: ${a.c} memories`));
5160
+ }
5161
+ console.log(lines.join(`
5162
+ `));
5163
+ return;
5164
+ }
5165
+ const sparkline = activityRows.map((r) => {
5166
+ const bars = "\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588";
5167
+ const maxC = Math.max(...activityRows.map((x) => x.cnt), 1);
5168
+ return bars[Math.round(r.cnt / maxC * 7)] || "\u2581";
5169
+ }).join("");
5170
+ console.log(chalk.bold(`
5171
+ mementos report \u2014 last ${days} days
5172
+ `));
5173
+ console.log(` ${chalk.cyan("Total:")} ${total} memories (${chalk.yellow(String(pinned))} pinned)`);
5174
+ console.log(` ${chalk.cyan("Recent:")} ${recentTotal} new \xB7 ${chalk.dim(`~${avgPerDay}/day`)}`);
5175
+ console.log(` ${chalk.cyan("Activity:")} ${sparkline || chalk.dim("no activity")}`);
5176
+ console.log(` ${chalk.cyan("Scopes:")} global=${byScope["global"] || 0} shared=${byScope["shared"] || 0} private=${byScope["private"] || 0}`);
5177
+ console.log(` ${chalk.cyan("Categories:")} knowledge=${byCat["knowledge"] || 0} fact=${byCat["fact"] || 0} preference=${byCat["preference"] || 0} history=${byCat["history"] || 0}`);
5178
+ if (topMems.length > 0) {
5179
+ console.log(`
5180
+ ${chalk.bold("Top memories by importance:")}`);
5181
+ topMems.forEach((m) => {
5182
+ console.log(` ${chalk.green(`[${m.importance}]`)} ${chalk.bold(m.key)} ${chalk.dim(`(${m.scope}/${m.category})`)}`);
5183
+ console.log(` ${m.value.slice(0, 90)}${m.value.length > 90 ? "..." : ""}`);
5184
+ });
5185
+ }
5186
+ if (topAgents.length > 0) {
5187
+ console.log(`
5188
+ ${chalk.bold("Top agents:")}`);
5189
+ topAgents.forEach((a) => console.log(` ${a.agent_id}: ${a.c} memories`));
5190
+ }
5191
+ console.log("");
5192
+ } catch (e) {
5193
+ console.error(chalk.red(`report failed: ${e instanceof Error ? e.message : String(e)}`));
5194
+ process.exit(1);
5195
+ }
5196
+ });
5113
5197
  program2.command("export").description("Export memories as JSON").option("-s, --scope <scope>", "Scope filter").option("-c, --category <cat>", "Category filter").option("--agent <name>", "Agent filter").option("--project <path>", "Project filter").action((opts) => {
5114
5198
  try {
5115
5199
  const globalOpts = program2.opts();
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",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/mementos",
3
- "version": "0.4.23",
3
+ "version": "0.4.25",
4
4
  "description": "Universal memory system for AI agents - CLI + MCP server + library API",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",