@hasna/mementos 0.14.58 → 0.14.59

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
@@ -2703,6 +2703,10 @@ var init_storage = __esm(() => {
2703
2703
  });
2704
2704
 
2705
2705
  // src/db/api-mode.ts
2706
+ import { tmpdir } from "os";
2707
+ import { join as join3 } from "path";
2708
+ import { writeFileSync as writeFileSync2, unlinkSync } from "fs";
2709
+ import { randomUUID as randomUUID3 } from "crypto";
2706
2710
  function firstEnv(...keys) {
2707
2711
  for (const k of keys) {
2708
2712
  const v = process.env[k]?.trim();
@@ -2738,33 +2742,60 @@ function apiRequestRaw(method, path, body) {
2738
2742
  throw new Error("api-mode: not configured (HASNA_MEMENTOS_API_URL / HASNA_MEMENTOS_API_KEY)");
2739
2743
  const url = `${cfg.baseUrl}${path.startsWith("/") ? path : `/${path}`}`;
2740
2744
  const hasBody = body !== undefined && body !== null;
2741
- const payload = hasBody ? JSON.stringify(body) : "";
2742
- const script = [
2743
- "curl -sS --fail-with-body",
2744
- `-m "$MEM_API_TIMEOUT"`,
2745
- `-X "$MEM_API_METHOD"`,
2746
- `-H "Authorization: Bearer $MEM_API_KEY"`,
2747
- `-H "x-api-key: $MEM_API_KEY"`,
2748
- `-H "Content-Type: application/json"`,
2749
- `-H "Accept: application/json"`,
2750
- hasBody ? "--data-binary @-" : "",
2751
- `-w '\\n%{http_code}'`,
2752
- `"$MEM_API_URL"`
2753
- ].filter(Boolean).join(" ");
2754
- const proc = Bun.spawnSync(["bash", "-c", script], {
2755
- stdin: hasBody ? Buffer.from(payload) : undefined,
2756
- env: {
2757
- ...process.env,
2758
- MEM_API_KEY: cfg.apiKey,
2759
- MEM_API_URL: url,
2760
- MEM_API_METHOD: method,
2761
- MEM_API_TIMEOUT: process.env["HASNA_MEMENTOS_API_TIMEOUT"] || DEFAULT_TIMEOUT_S
2762
- },
2763
- stdout: "pipe",
2764
- stderr: "pipe"
2765
- });
2766
- const out = proc.stdout ? new TextDecoder().decode(proc.stdout) : "";
2767
- const err = proc.stderr ? new TextDecoder().decode(proc.stderr) : "";
2745
+ const timeout = process.env["HASNA_MEMENTOS_API_TIMEOUT"] || DEFAULT_TIMEOUT_S;
2746
+ const headerLines = `Authorization: Bearer ${cfg.apiKey}
2747
+ x-api-key: ${cfg.apiKey}
2748
+ `;
2749
+ const args = [
2750
+ "curl",
2751
+ "-sS",
2752
+ "--fail-with-body",
2753
+ "-m",
2754
+ timeout,
2755
+ "-X",
2756
+ method,
2757
+ "-H",
2758
+ "@-",
2759
+ "-H",
2760
+ "Content-Type: application/json",
2761
+ "-H",
2762
+ "Accept: application/json",
2763
+ "-w",
2764
+ "\\n%{http_code}"
2765
+ ];
2766
+ let bodyFile;
2767
+ if (hasBody) {
2768
+ bodyFile = join3(tmpdir(), `mem-req-${process.pid}-${randomUUID3()}.json`);
2769
+ writeFileSync2(bodyFile, JSON.stringify(body), { mode: 384 });
2770
+ args.push("--data-binary", `@${bodyFile}`);
2771
+ }
2772
+ args.push(url);
2773
+ const childEnv = {};
2774
+ for (const [k, v] of Object.entries(process.env)) {
2775
+ if (v === undefined)
2776
+ continue;
2777
+ if (k === "HASNA_MEMENTOS_API_KEY" || k === "MEMENTOS_API_KEY")
2778
+ continue;
2779
+ childEnv[k] = v;
2780
+ }
2781
+ let out = "";
2782
+ let err = "";
2783
+ try {
2784
+ const proc = Bun.spawnSync(args, {
2785
+ stdin: Buffer.from(headerLines),
2786
+ stdout: "pipe",
2787
+ stderr: "pipe",
2788
+ env: childEnv
2789
+ });
2790
+ out = proc.stdout ? new TextDecoder().decode(proc.stdout) : "";
2791
+ err = proc.stderr ? new TextDecoder().decode(proc.stderr) : "";
2792
+ } finally {
2793
+ if (bodyFile) {
2794
+ try {
2795
+ unlinkSync(bodyFile);
2796
+ } catch {}
2797
+ }
2798
+ }
2768
2799
  const nl = out.lastIndexOf(`
2769
2800
  `);
2770
2801
  const codeStr = nl >= 0 ? out.slice(nl + 1).trim() : "";
@@ -3743,7 +3774,7 @@ __export(exports_database, {
3743
3774
  closeDatabase: () => closeDatabase
3744
3775
  });
3745
3776
  import { existsSync as existsSync3, mkdirSync as mkdirSync2, cpSync } from "fs";
3746
- import { dirname, join as join3, resolve } from "path";
3777
+ import { dirname, join as join4, resolve } from "path";
3747
3778
  function isInMemoryDb(path) {
3748
3779
  return path === ":memory:" || path.startsWith("file::memory:");
3749
3780
  }
@@ -3752,7 +3783,7 @@ function findNearestMementosDb(startDir) {
3752
3783
  const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
3753
3784
  const legacyHomeDb = resolve(home, ".mementos", "mementos.db");
3754
3785
  while (true) {
3755
- const candidate = join3(dir, ".mementos", "mementos.db");
3786
+ const candidate = join4(dir, ".mementos", "mementos.db");
3756
3787
  if (existsSync3(candidate) && resolve(candidate) !== legacyHomeDb)
3757
3788
  return candidate;
3758
3789
  const parent = dirname(dir);
@@ -3765,7 +3796,7 @@ function findNearestMementosDb(startDir) {
3765
3796
  function findGitRoot(startDir) {
3766
3797
  let dir = resolve(startDir);
3767
3798
  while (true) {
3768
- if (existsSync3(join3(dir, ".git")))
3799
+ if (existsSync3(join4(dir, ".git")))
3769
3800
  return dir;
3770
3801
  const parent = dirname(dir);
3771
3802
  if (parent === dir)
@@ -3776,10 +3807,10 @@ function findGitRoot(startDir) {
3776
3807
  }
3777
3808
  function migrateGlobalDir() {
3778
3809
  const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
3779
- const newDir = join3(home, ".hasna", "mementos");
3780
- const oldDir = join3(home, ".mementos");
3810
+ const newDir = join4(home, ".hasna", "mementos");
3811
+ const oldDir = join4(home, ".mementos");
3781
3812
  if (!existsSync3(newDir) && existsSync3(oldDir)) {
3782
- mkdirSync2(join3(home, ".hasna"), { recursive: true });
3813
+ mkdirSync2(join4(home, ".hasna"), { recursive: true });
3783
3814
  cpSync(oldDir, newDir, { recursive: true });
3784
3815
  }
3785
3816
  }
@@ -3795,12 +3826,12 @@ function getDbPath() {
3795
3826
  if (process.env["MEMENTOS_DB_SCOPE"] === "project") {
3796
3827
  const gitRoot = findGitRoot(cwd);
3797
3828
  if (gitRoot) {
3798
- return join3(gitRoot, ".mementos", "mementos.db");
3829
+ return join4(gitRoot, ".mementos", "mementos.db");
3799
3830
  }
3800
3831
  }
3801
3832
  migrateGlobalDir();
3802
3833
  const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
3803
- return join3(home, ".hasna", "mementos", "mementos.db");
3834
+ return join4(home, ".hasna", "mementos", "mementos.db");
3804
3835
  }
3805
3836
  function ensureDir(filePath) {
3806
3837
  if (isInMemoryDb(filePath))
@@ -4341,12 +4372,14 @@ __export(exports_memories, {
4341
4372
  parseMemoryRow: () => parseMemoryRow,
4342
4373
  listMemoryHistory: () => listMemoryHistory,
4343
4374
  listMemories: () => listMemories,
4375
+ listLowTrustMemories: () => listLowTrustMemories,
4344
4376
  indexMemoryEmbedding: () => indexMemoryEmbedding,
4345
4377
  incrementRecallCount: () => incrementRecallCount,
4346
4378
  getMemoryVersions: () => getMemoryVersions,
4347
4379
  getMemoryEmbeddings: () => getMemoryEmbeddings,
4348
4380
  getMemoryChain: () => getMemoryChain,
4349
4381
  getMemoryByKey: () => getMemoryByKey,
4382
+ getMemoryBriefing: () => getMemoryBriefing,
4350
4383
  getMemory: () => getMemory,
4351
4384
  getMemoriesByKey: () => getMemoriesByKey,
4352
4385
  deleteMemory: () => deleteMemory,
@@ -4356,6 +4389,14 @@ __export(exports_memories, {
4356
4389
  bulkDeleteMemories: () => bulkDeleteMemories
4357
4390
  });
4358
4391
  function runEntityExtraction(_memory, _projectId, _d) {}
4392
+ function applyContentType(d, id, memory, contentType) {
4393
+ if (!contentType || contentType === "text")
4394
+ return;
4395
+ try {
4396
+ d.run("UPDATE memories SET content_type = ? WHERE id = ?", [contentType, id]);
4397
+ memory.content_type = contentType;
4398
+ } catch {}
4399
+ }
4359
4400
  function parseMemoryRow(row) {
4360
4401
  return {
4361
4402
  id: row["id"],
@@ -4460,6 +4501,7 @@ function createMemory(input, dedupeMode = "merge", db) {
4460
4501
  insertTag2.run(existing.id, tag);
4461
4502
  }
4462
4503
  const merged = getMemory(existing.id, d);
4504
+ applyContentType(d, existing.id, merged, input.content_type);
4463
4505
  try {
4464
4506
  const existingMemories = listMemoriesByKey(input.key, d);
4465
4507
  const trustScore = computeTrustScore(safeValue, input.key, existingMemories, input.importance);
@@ -4509,6 +4551,7 @@ function createMemory(input, dedupeMode = "merge", db) {
4509
4551
  insertTag.run(id, tag);
4510
4552
  }
4511
4553
  const memory = getMemory(id, d);
4554
+ applyContentType(d, id, memory, input.content_type);
4512
4555
  try {
4513
4556
  const existingMemories = listMemoriesByKey(input.key, d);
4514
4557
  const trustScore = computeTrustScore(safeValue, input.key, existingMemories, input.importance);
@@ -4843,6 +4886,66 @@ function listMemories(filter, db) {
4843
4886
  const rows = d.query(sql).all(...params);
4844
4887
  return rows.map(parseMemoryRow);
4845
4888
  }
4889
+ function getMemoryBriefing(opts, db) {
4890
+ const limit = opts.limit ?? 20;
4891
+ if (!db && isApiMode()) {
4892
+ const q = toQuery({
4893
+ since: opts.since,
4894
+ scope: opts.scope,
4895
+ project_id: opts.project_id,
4896
+ machine_agnostic: opts.visible_machine_id === null || opts.visible_machine_id === undefined ? true : undefined,
4897
+ visible_machine_id: typeof opts.visible_machine_id === "string" ? opts.visible_machine_id : undefined,
4898
+ limit
4899
+ });
4900
+ const { data } = apiJson("GET", `/memories/briefing${q}`);
4901
+ return data ?? { new: [], updated: [], expired: [] };
4902
+ }
4903
+ const d = db || getDatabase();
4904
+ const visibleMachineId = opts.visible_machine_id;
4905
+ const scopeClause = opts.scope ? "AND scope = ?" : "";
4906
+ const projectClause = opts.project_id ? "AND project_id = ?" : "";
4907
+ const machineClause = typeof visibleMachineId === "string" ? "AND (machine_id IS NULL OR machine_id = ?)" : "AND machine_id IS NULL";
4908
+ const extraParams = [
4909
+ ...opts.scope ? [opts.scope] : [],
4910
+ ...opts.project_id ? [opts.project_id] : [],
4911
+ ...typeof visibleMachineId === "string" ? [visibleMachineId] : []
4912
+ ];
4913
+ const newRows = d.prepare(`SELECT * FROM memories
4914
+ WHERE status = 'active' AND created_at > ? ${scopeClause} ${projectClause} ${machineClause}
4915
+ ORDER BY importance DESC, created_at DESC LIMIT ?`).all(opts.since, ...extraParams, limit);
4916
+ const updatedRows = d.prepare(`SELECT * FROM memories
4917
+ WHERE status = 'active' AND updated_at > ? AND created_at <= ? ${scopeClause} ${projectClause} ${machineClause}
4918
+ ORDER BY importance DESC, updated_at DESC LIMIT ?`).all(opts.since, opts.since, ...extraParams, limit);
4919
+ const expiredRows = d.prepare(`SELECT * FROM memories
4920
+ WHERE status != 'active' AND updated_at > ? ${scopeClause} ${projectClause} ${machineClause}
4921
+ ORDER BY updated_at DESC LIMIT ?`).all(opts.since, ...extraParams, Math.min(limit, 10));
4922
+ return {
4923
+ new: newRows.map(parseMemoryRow),
4924
+ updated: updatedRows.map(parseMemoryRow),
4925
+ expired: expiredRows.map(parseMemoryRow)
4926
+ };
4927
+ }
4928
+ function listLowTrustMemories(opts = {}, db) {
4929
+ const threshold = opts.threshold ?? 0.8;
4930
+ const limit = opts.limit ?? 20;
4931
+ const offset = opts.offset ?? 0;
4932
+ if (!db && isApiMode()) {
4933
+ const q = toQuery({ threshold, project_id: opts.project_id, limit, offset });
4934
+ const { data } = apiJson("GET", `/memories/audit${q}`);
4935
+ return data?.memories ?? [];
4936
+ }
4937
+ const d = db || getDatabase();
4938
+ const conditions = ["trust_score < ?", "status = 'active'"];
4939
+ const params = [threshold];
4940
+ if (opts.project_id) {
4941
+ const resolved = resolvePartialId(d, "projects", opts.project_id);
4942
+ conditions.push("project_id = ?");
4943
+ params.push(resolved ?? opts.project_id);
4944
+ }
4945
+ params.push(limit, offset);
4946
+ const rows = d.prepare(`SELECT * FROM memories WHERE ${conditions.join(" AND ")} ORDER BY trust_score ASC LIMIT ? OFFSET ?`).all(...params);
4947
+ return rows.map(parseMemoryRow);
4948
+ }
4846
4949
  function listMemoryHistory(opts = {}, db) {
4847
4950
  const limit = opts.limit ?? 20;
4848
4951
  const offset = opts.offset ?? 0;
@@ -5054,6 +5157,10 @@ function incrementRecallCount(id, db) {
5054
5157
  } catch {}
5055
5158
  }
5056
5159
  function cleanExpiredMemories(db) {
5160
+ if (!db && isApiMode()) {
5161
+ const { data } = apiJson("POST", "/memories/clean");
5162
+ return data?.cleaned ?? 0;
5163
+ }
5057
5164
  const d = db || getDatabase();
5058
5165
  const timestamp = now2();
5059
5166
  const countRow = d.query("SELECT COUNT(*) as c FROM memories WHERE expires_at IS NOT NULL AND expires_at < ?").get(timestamp);
@@ -5106,6 +5213,12 @@ async function semanticSearch(queryText, options = {}, db) {
5106
5213
  }
5107
5214
  const d = db || getDatabase();
5108
5215
  const { threshold = 0.5, limit = 10, scope, agent_id, project_id } = options;
5216
+ if (options.index_missing) {
5217
+ const unindexed = d.prepare(`SELECT id, value, summary, when_to_use FROM memories
5218
+ WHERE status = 'active' AND id NOT IN (SELECT memory_id FROM memory_embeddings)
5219
+ LIMIT 100`).all();
5220
+ await Promise.all(unindexed.map((m) => indexMemoryEmbedding(m.id, m.when_to_use || [m.value, m.summary].filter(Boolean).join(" "), d)));
5221
+ }
5109
5222
  const { embedding: queryEmbedding } = await generateEmbedding(queryText);
5110
5223
  const conditions = ["m.status = 'active'", "e.embedding IS NOT NULL"];
5111
5224
  const params = [];
@@ -5452,11 +5565,11 @@ __export(exports_helpers, {
5452
5565
  });
5453
5566
  import chalk from "chalk";
5454
5567
  import { readFileSync as readFileSync2 } from "fs";
5455
- import { dirname as dirname2, join as join4, resolve as resolve2 } from "path";
5568
+ import { dirname as dirname2, join as join5, resolve as resolve2 } from "path";
5456
5569
  import { fileURLToPath as fileURLToPath2 } from "url";
5457
5570
  function getPackageVersion() {
5458
5571
  try {
5459
- const pkgPath = join4(dirname2(fileURLToPath2(import.meta.url)), "..", "..", "package.json");
5572
+ const pkgPath = join5(dirname2(fileURLToPath2(import.meta.url)), "..", "..", "package.json");
5460
5573
  const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
5461
5574
  return pkg.version || "0.0.0";
5462
5575
  } catch {
@@ -5965,7 +6078,7 @@ function validateConfigKeyValue(key, value, DEFAULT_CONFIG) {
5965
6078
  }
5966
6079
  function getConfigPath() {
5967
6080
  const { homedir: homedir3 } = __require("os");
5968
- return join4(homedir3(), ".hasna", "mementos", "config.json");
6081
+ return join5(homedir3(), ".hasna", "mementos", "config.json");
5969
6082
  }
5970
6083
  function readFileConfig() {
5971
6084
  const { existsSync: existsSync4 } = __require("fs");
@@ -5979,12 +6092,12 @@ function readFileConfig() {
5979
6092
  }
5980
6093
  }
5981
6094
  function writeFileConfig(data) {
5982
- const { existsSync: existsSync4, writeFileSync: writeFileSync2, mkdirSync: mkdirSync3 } = __require("fs");
6095
+ const { existsSync: existsSync4, writeFileSync: writeFileSync3, mkdirSync: mkdirSync3 } = __require("fs");
5983
6096
  const configPath = getConfigPath();
5984
6097
  const dir = dirname2(configPath);
5985
6098
  if (!existsSync4(dir))
5986
6099
  mkdirSync3(dir, { recursive: true });
5987
- writeFileSync2(configPath, JSON.stringify(data, null, 2) + `
6100
+ writeFileSync3(configPath, JSON.stringify(data, null, 2) + `
5988
6101
  `, "utf-8");
5989
6102
  }
5990
6103
  var scopeColor, categoryColor, entityTypeColor, DEFAULT_COMPACT_LIMIT = 20, DEFAULT_SEARCH_LIMIT = 10, DEFAULT_SNIPPET_LENGTH = 72, VALID_SCOPES, VALID_CATEGORIES;
@@ -10553,7 +10666,7 @@ __export(exports_session_registry, {
10553
10666
  cleanStaleSessions: () => cleanStaleSessions
10554
10667
  });
10555
10668
  import { existsSync as existsSync8, mkdirSync as mkdirSync5 } from "fs";
10556
- import { dirname as dirname5, join as join7 } from "path";
10669
+ import { dirname as dirname5, join as join8 } from "path";
10557
10670
  function getDb() {
10558
10671
  if (_db2)
10559
10672
  return _db2;
@@ -10733,7 +10846,7 @@ function closeRegistry() {
10733
10846
  var DB_PATH, _db2 = null;
10734
10847
  var init_session_registry = __esm(() => {
10735
10848
  init_storage();
10736
- DB_PATH = join7(process.env["HOME"] || process.env["USERPROFILE"] || "~", ".open-sessions-registry.db");
10849
+ DB_PATH = join8(process.env["HOME"] || process.env["USERPROFILE"] || "~", ".open-sessions-registry.db");
10737
10850
  });
10738
10851
 
10739
10852
  // src/db/pg-migrations.ts
@@ -58795,7 +58908,7 @@ function collectValues(value, previous) {
58795
58908
  // src/cli/index.tsx
58796
58909
  init_database();
58797
58910
  import { readFileSync as readFileSync8 } from "fs";
58798
- import { dirname as dirname7, join as join11 } from "path";
58911
+ import { dirname as dirname7, join as join12 } from "path";
58799
58912
  import { fileURLToPath as fileURLToPath4 } from "url";
58800
58913
 
58801
58914
  // src/db/machines.ts
@@ -59884,6 +59997,56 @@ function normalizeStats(data) {
59884
59997
  expired_count: data?.expired_count ?? 0
59885
59998
  };
59886
59999
  }
60000
+ function getMemoryReport(filter = {}, db) {
60001
+ const days = Math.min(filter.days || 7, 365);
60002
+ if (!db && isApiMode()) {
60003
+ const q = toQuery({ days, project_id: filter.project_id, agent_id: filter.agent_id });
60004
+ const { data } = apiJson("GET", `/report${q}`);
60005
+ return {
60006
+ total: data?.total ?? 0,
60007
+ pinned: data?.pinned ?? 0,
60008
+ days: data?.days ?? days,
60009
+ recent: data?.recent ?? { total: 0, activity: [] },
60010
+ by_scope: data?.by_scope ?? {},
60011
+ by_category: data?.by_category ?? {},
60012
+ top_memories: data?.top_memories ?? [],
60013
+ top_agents: data?.top_agents ?? []
60014
+ };
60015
+ }
60016
+ const d = db || getDatabase();
60017
+ const cutoffDate = new Date(Date.now() - days * 86400000).toISOString().slice(0, 10);
60018
+ const scopedCond = [
60019
+ filter.project_id ? "AND project_id = ?" : "",
60020
+ filter.agent_id ? "AND agent_id = ?" : ""
60021
+ ].filter(Boolean).join(" ");
60022
+ const scopedParams = [
60023
+ ...filter.project_id ? [filter.project_id] : [],
60024
+ ...filter.agent_id ? [filter.agent_id] : []
60025
+ ];
60026
+ const recentParams = [cutoffDate, ...scopedParams];
60027
+ const total = d.query(`SELECT COUNT(*) as c FROM memories WHERE status = 'active' ${scopedCond}`).get(...scopedParams).c;
60028
+ const pinned = d.query(`SELECT COUNT(*) as c FROM memories WHERE status = 'active' AND pinned = 1 ${scopedCond}`).get(...scopedParams).c;
60029
+ const actRows = d.query(`
60030
+ SELECT date(created_at) AS date, COUNT(*) AS memories_created
60031
+ FROM memories WHERE status = 'active' AND date(created_at) >= ? ${scopedCond}
60032
+ GROUP BY date(created_at) ORDER BY date(created_at) ASC
60033
+ `).all(...recentParams);
60034
+ const recentTotal = actRows.reduce((s, r) => s + r.memories_created, 0);
60035
+ const byScopeRows = d.query(`SELECT scope, COUNT(*) as c FROM memories WHERE status = 'active' ${scopedCond} GROUP BY scope`).all(...scopedParams);
60036
+ const byCatRows = d.query(`SELECT category, COUNT(*) as c FROM memories WHERE status = 'active' ${scopedCond} GROUP BY category`).all(...scopedParams);
60037
+ const topMems = d.query(`SELECT id, key, value, importance, scope, category FROM memories WHERE status = 'active' ${scopedCond} ORDER BY importance DESC, access_count DESC LIMIT 5`).all(...scopedParams);
60038
+ const topAgents = d.query(`SELECT agent_id, COUNT(*) as c FROM memories WHERE status = 'active' AND agent_id IS NOT NULL ${scopedCond} GROUP BY agent_id ORDER BY c DESC LIMIT 5`).all(...scopedParams);
60039
+ return {
60040
+ total,
60041
+ pinned,
60042
+ days,
60043
+ recent: { total: recentTotal, activity: actRows },
60044
+ by_scope: Object.fromEntries(byScopeRows.map((r) => [r.scope, r.c])),
60045
+ by_category: Object.fromEntries(byCatRows.map((r) => [r.category, r.c])),
60046
+ top_memories: topMems,
60047
+ top_agents: topAgents
60048
+ };
60049
+ }
59887
60050
  function getStaleMemories(filter = {}, db) {
59888
60051
  const days = Math.min(filter.days || 30, 365);
59889
60052
  const limit = filter.limit ?? 20;
@@ -59974,10 +60137,9 @@ function registerStatsCommand(program2) {
59974
60137
  }
59975
60138
 
59976
60139
  // src/cli/commands/info-report.ts
59977
- init_database();
59978
- init_projects();
59979
60140
  import chalk14 from "chalk";
59980
60141
  import { resolve as resolve8 } from "path";
60142
+ init_projects();
59981
60143
  function registerReportCommand(program2) {
59982
60144
  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) => {
59983
60145
  try {
@@ -59992,24 +60154,15 @@ function registerReportCommand(program2) {
59992
60154
  if (project)
59993
60155
  projectId = project.id;
59994
60156
  }
59995
- const db = getDatabase();
59996
- const conditions = projectId ? "AND project_id = ?" : "";
59997
- const params = projectId ? [projectId] : [];
59998
- const total = db.query(`SELECT COUNT(*) as c FROM memories WHERE status = 'active' ${conditions}`).get(...params).c;
59999
- const pinned = db.query(`SELECT COUNT(*) as c FROM memories WHERE status = 'active' AND pinned = 1 ${conditions}`).get(...params).c;
60000
- const activityRows = db.query(`
60001
- SELECT date(created_at) AS d, COUNT(*) AS cnt
60002
- FROM memories WHERE status = 'active' AND date(created_at) >= date('now', '-${days} days') ${conditions}
60003
- GROUP BY d ORDER BY d ASC
60004
- `).all(...params);
60005
- const recentTotal = activityRows.reduce((s, r) => s + r.cnt, 0);
60157
+ const report = getMemoryReport({ days, project_id: projectId });
60158
+ const { total, pinned } = report;
60159
+ const activityRows = report.recent.activity;
60160
+ const recentTotal = report.recent.total;
60006
60161
  const avgPerDay = activityRows.length > 0 ? (recentTotal / activityRows.length).toFixed(1) : "0";
60007
- const byScopeRows = db.query(`SELECT scope, COUNT(*) as c FROM memories WHERE status = 'active' ${conditions} GROUP BY scope`).all(...params);
60008
- const byScope = Object.fromEntries(byScopeRows.map((r) => [r.scope, r.c]));
60009
- const byCatRows = db.query(`SELECT category, COUNT(*) as c FROM memories WHERE status = 'active' ${conditions} GROUP BY category`).all(...params);
60010
- const byCat = Object.fromEntries(byCatRows.map((r) => [r.category, r.c]));
60011
- 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);
60012
- 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);
60162
+ const byScope = report.by_scope;
60163
+ const byCat = report.by_category;
60164
+ const topMems = report.top_memories;
60165
+ const topAgents = report.top_agents;
60013
60166
  if (isJson) {
60014
60167
  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));
60015
60168
  return;
@@ -60035,8 +60188,8 @@ function registerReportCommand(program2) {
60035
60188
  }
60036
60189
  const sparkline = activityRows.map((r) => {
60037
60190
  const bars = "\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588";
60038
- const maxC = Math.max(...activityRows.map((x) => x.cnt), 1);
60039
- return bars[Math.round(r.cnt / maxC * 7)] || "\u2581";
60191
+ const maxC = Math.max(...activityRows.map((x) => x.memories_created), 1);
60192
+ return bars[Math.round(r.memories_created / maxC * 7)] || "\u2581";
60040
60193
  }).join("");
60041
60194
  console.log(chalk14.bold(`
60042
60195
  mementos report \u2014 last ${days} days
@@ -60418,9 +60571,9 @@ function registerImportCommand(program2) {
60418
60571
  import chalk19 from "chalk";
60419
60572
 
60420
60573
  // src/lib/config.ts
60421
- import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync4, readdirSync, writeFileSync as writeFileSync2, unlinkSync, cpSync as cpSync2 } from "fs";
60574
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync4, readdirSync, writeFileSync as writeFileSync3, unlinkSync as unlinkSync2, cpSync as cpSync2 } from "fs";
60422
60575
  import { homedir as homedir3 } from "os";
60423
- import { basename, dirname as dirname3, join as join5, resolve as resolve13 } from "path";
60576
+ import { basename, dirname as dirname3, join as join6, resolve as resolve13 } from "path";
60424
60577
  function isInMemoryDb2(path) {
60425
60578
  return path === ":memory:" || path.startsWith("file::memory:");
60426
60579
  }
@@ -60483,7 +60636,7 @@ function isValidCategory(value) {
60483
60636
  return VALID_CATEGORIES2.includes(value);
60484
60637
  }
60485
60638
  function loadConfig() {
60486
- const configPath = join5(homeDir(), ".hasna", "mementos", "config.json");
60639
+ const configPath = join6(homeDir(), ".hasna", "mementos", "config.json");
60487
60640
  let fileConfig = {};
60488
60641
  if (existsSync4(configPath)) {
60489
60642
  try {
@@ -60513,7 +60666,7 @@ function findFileWalkingUp(filename) {
60513
60666
  let dir = process.cwd();
60514
60667
  const legacyHomeMementosDb = resolve13(homeDir(), ".mementos", "mementos.db");
60515
60668
  while (true) {
60516
- const candidate = join5(dir, filename);
60669
+ const candidate = join6(dir, filename);
60517
60670
  if (existsSync4(candidate) && resolve13(candidate) !== legacyHomeMementosDb) {
60518
60671
  return candidate;
60519
60672
  }
@@ -60527,7 +60680,7 @@ function findFileWalkingUp(filename) {
60527
60680
  function findGitRoot2() {
60528
60681
  let dir = process.cwd();
60529
60682
  while (true) {
60530
- if (existsSync4(join5(dir, ".git"))) {
60683
+ if (existsSync4(join6(dir, ".git"))) {
60531
60684
  return dir;
60532
60685
  }
60533
60686
  const parent = dirname3(dir);
@@ -60538,10 +60691,10 @@ function findGitRoot2() {
60538
60691
  }
60539
60692
  }
60540
60693
  function profilesDir() {
60541
- return join5(homeDir(), ".hasna", "mementos", "profiles");
60694
+ return join6(homeDir(), ".hasna", "mementos", "profiles");
60542
60695
  }
60543
60696
  function globalConfigPath() {
60544
- return join5(homeDir(), ".hasna", "mementos", "config.json");
60697
+ return join6(homeDir(), ".hasna", "mementos", "config.json");
60545
60698
  }
60546
60699
  function readGlobalConfig() {
60547
60700
  const p = globalConfigPath();
@@ -60556,7 +60709,7 @@ function readGlobalConfig() {
60556
60709
  function writeGlobalConfig(data) {
60557
60710
  const p = globalConfigPath();
60558
60711
  ensureDir2(dirname3(p));
60559
- writeFileSync2(p, JSON.stringify(data, null, 2), "utf-8");
60712
+ writeFileSync3(p, JSON.stringify(data, null, 2), "utf-8");
60560
60713
  }
60561
60714
  function getActiveProfile() {
60562
60715
  const envProfile = process.env["MEMENTOS_PROFILE"];
@@ -60581,20 +60734,20 @@ function listProfiles() {
60581
60734
  return readdirSync(dir).filter((f) => f.endsWith(".db")).map((f) => basename(f, ".db")).sort();
60582
60735
  }
60583
60736
  function deleteProfile(name) {
60584
- const dbPath = join5(profilesDir(), `${name}.db`);
60737
+ const dbPath = join6(profilesDir(), `${name}.db`);
60585
60738
  if (!existsSync4(dbPath))
60586
60739
  return false;
60587
- unlinkSync(dbPath);
60740
+ unlinkSync2(dbPath);
60588
60741
  if (getActiveProfile() === name)
60589
60742
  setActiveProfile(null);
60590
60743
  return true;
60591
60744
  }
60592
60745
  function getDbPath2() {
60593
60746
  const _home = homeDir();
60594
- const _newDir = join5(_home, ".hasna", "mementos");
60595
- const _oldDir = join5(_home, ".mementos");
60747
+ const _newDir = join6(_home, ".hasna", "mementos");
60748
+ const _oldDir = join6(_home, ".mementos");
60596
60749
  if (!existsSync4(_newDir) && existsSync4(_oldDir)) {
60597
- mkdirSync3(join5(_home, ".hasna"), { recursive: true });
60750
+ mkdirSync3(join6(_home, ".hasna"), { recursive: true });
60598
60751
  cpSync2(_oldDir, _newDir, { recursive: true });
60599
60752
  }
60600
60753
  const envDbPath = process.env["HASNA_MEMENTOS_DB_PATH"] ?? process.env["MEMENTOS_DB_PATH"];
@@ -60608,7 +60761,7 @@ function getDbPath2() {
60608
60761
  }
60609
60762
  const profile = getActiveProfile();
60610
60763
  if (profile) {
60611
- const profilePath = join5(profilesDir(), `${profile}.db`);
60764
+ const profilePath = join6(profilesDir(), `${profile}.db`);
60612
60765
  ensureDir2(dirname3(profilePath));
60613
60766
  return profilePath;
60614
60767
  }
@@ -60616,16 +60769,16 @@ function getDbPath2() {
60616
60769
  if (dbScope === "project") {
60617
60770
  const gitRoot = findGitRoot2();
60618
60771
  if (gitRoot) {
60619
- const dbPath = join5(gitRoot, ".mementos", "mementos.db");
60772
+ const dbPath = join6(gitRoot, ".mementos", "mementos.db");
60620
60773
  ensureDir2(dirname3(dbPath));
60621
60774
  return dbPath;
60622
60775
  }
60623
60776
  }
60624
- const found = findFileWalkingUp(join5(".mementos", "mementos.db"));
60777
+ const found = findFileWalkingUp(join6(".mementos", "mementos.db"));
60625
60778
  if (found) {
60626
60779
  return found;
60627
60780
  }
60628
- const fallback = join5(homeDir(), ".hasna", "mementos", "mementos.db");
60781
+ const fallback = join6(homeDir(), ".hasna", "mementos", "mementos.db");
60629
60782
  ensureDir2(dirname3(fallback));
60630
60783
  return fallback;
60631
60784
  }
@@ -62004,7 +62157,7 @@ init_memories();
62004
62157
  init_agents();
62005
62158
  init_projects();
62006
62159
  import chalk27 from "chalk";
62007
- import { join as join6 } from "path";
62160
+ import { join as join7 } from "path";
62008
62161
  import { homedir as homedir4 } from "os";
62009
62162
  import {
62010
62163
  readFileSync as readFileSync5,
@@ -62190,7 +62343,7 @@ function registerDoctorCommand(program2) {
62190
62343
  checks.push({ name: "MCP server", status: "warn", detail: "could not check (is claude CLI installed?)" });
62191
62344
  }
62192
62345
  try {
62193
- const settingsFilePath = join6(homedir4(), ".claude", "settings.json");
62346
+ const settingsFilePath = join7(homedir4(), ".claude", "settings.json");
62194
62347
  if (existsSync7(settingsFilePath)) {
62195
62348
  const settings = JSON.parse(readFileSync5(settingsFilePath, "utf-8"));
62196
62349
  const hooksObj = settings["hooks"] || {};
@@ -62208,7 +62361,7 @@ function registerDoctorCommand(program2) {
62208
62361
  checks.push({ name: "Stop hook", status: "warn", detail: "could not check stop hook" });
62209
62362
  }
62210
62363
  if (process.platform === "darwin") {
62211
- const plistFilePath = join6(homedir4(), "Library", "LaunchAgents", "com.hasna.mementos.plist");
62364
+ const plistFilePath = join7(homedir4(), "Library", "LaunchAgents", "com.hasna.mementos.plist");
62212
62365
  checks.push({
62213
62366
  name: "Auto-start",
62214
62367
  status: existsSync7(plistFilePath) ? "ok" : "warn",
@@ -62335,9 +62488,9 @@ function registerConfigCommand(program2) {
62335
62488
  }
62336
62489
  } else {
62337
62490
  const configPath = getConfigPath();
62338
- const { unlinkSync: unlinkSync2, existsSync: _existsSync } = __require("fs");
62491
+ const { unlinkSync: unlinkSync3, existsSync: _existsSync } = __require("fs");
62339
62492
  if (_existsSync(configPath)) {
62340
- unlinkSync2(configPath);
62493
+ unlinkSync3(configPath);
62341
62494
  }
62342
62495
  if (useJson) {
62343
62496
  outputJson({ reset: true, all: true });
@@ -64027,12 +64180,12 @@ function registerStorageCommands(program2) {
64027
64180
  import chalk41 from "chalk";
64028
64181
  import {
64029
64182
  readFileSync as readFileSync6,
64030
- writeFileSync as writeFileSync3,
64183
+ writeFileSync as writeFileSync4,
64031
64184
  existsSync as existsSync9,
64032
64185
  copyFileSync as copyFileSync3,
64033
64186
  mkdirSync as mkdirSync6
64034
64187
  } from "fs";
64035
- import { dirname as dirname6, join as join8 } from "path";
64188
+ import { dirname as dirname6, join as join9 } from "path";
64036
64189
  import { homedir as homedir5 } from "os";
64037
64190
  import { fileURLToPath as fileURLToPath3 } from "url";
64038
64191
  function registerInitCommand(program2) {
@@ -64093,9 +64246,9 @@ function registerInitCommand(program2) {
64093
64246
  } else {
64094
64247
  console.log(chalk41.green(" \u2713 MCP server registered with Claude Code"));
64095
64248
  }
64096
- const hooksDir = join8(home, ".claude", "hooks");
64097
- const hookDest = join8(hooksDir, "mementos-stop-hook.ts");
64098
- const settingsPath = join8(home, ".claude", "settings.json");
64249
+ const hooksDir = join9(home, ".claude", "hooks");
64250
+ const hookDest = join9(hooksDir, "mementos-stop-hook.ts");
64251
+ const settingsPath = join9(home, ".claude", "settings.json");
64099
64252
  const hookCommand = `bun ${hookDest}`;
64100
64253
  let hookAlreadyInstalled = false;
64101
64254
  let hookError = null;
@@ -64120,9 +64273,9 @@ function registerInitCommand(program2) {
64120
64273
  if (!existsSync9(hookDest)) {
64121
64274
  const packageDir = dirname6(dirname6(fileURLToPath3(import.meta.url)));
64122
64275
  const candidatePaths = [
64123
- join8(packageDir, "scripts", "hooks", "claude-stop-hook.ts"),
64124
- join8(packageDir, "..", "scripts", "hooks", "claude-stop-hook.ts"),
64125
- join8(home, ".bun", "install", "global", "node_modules", "@hasna", "mementos", "scripts", "hooks", "claude-stop-hook.ts")
64276
+ join9(packageDir, "scripts", "hooks", "claude-stop-hook.ts"),
64277
+ join9(packageDir, "..", "scripts", "hooks", "claude-stop-hook.ts"),
64278
+ join9(home, ".bun", "install", "global", "node_modules", "@hasna", "mementos", "scripts", "hooks", "claude-stop-hook.ts")
64126
64279
  ];
64127
64280
  let hookSourceFound = false;
64128
64281
  for (const src of candidatePaths) {
@@ -64166,7 +64319,7 @@ async function main() {
64166
64319
 
64167
64320
  main().catch(() => {});
64168
64321
  `;
64169
- writeFileSync3(hookDest, inlineHook, "utf-8");
64322
+ writeFileSync4(hookDest, inlineHook, "utf-8");
64170
64323
  }
64171
64324
  }
64172
64325
  const newStopEntry = {
@@ -64175,7 +64328,7 @@ main().catch(() => {});
64175
64328
  };
64176
64329
  hooksObj["Stop"] = [...stopHooks, newStopEntry];
64177
64330
  settings["hooks"] = hooksObj;
64178
- writeFileSync3(settingsPath, JSON.stringify(settings, null, 2), "utf-8");
64331
+ writeFileSync4(settingsPath, JSON.stringify(settings, null, 2), "utf-8");
64179
64332
  }
64180
64333
  } catch (e) {
64181
64334
  hookError = e instanceof Error ? e.message : String(e);
@@ -64192,7 +64345,7 @@ main().catch(() => {});
64192
64345
  if (!isMac) {
64193
64346
  console.log(chalk41.dim(` \xB7 Auto-start skipped (not macOS \u2014 platform: ${platform2})`));
64194
64347
  } else {
64195
- const plistPath = join8(home, "Library", "LaunchAgents", "com.hasna.mementos.plist");
64348
+ const plistPath = join9(home, "Library", "LaunchAgents", "com.hasna.mementos.plist");
64196
64349
  const plistContent = `<?xml version="1.0" encoding="UTF-8"?>
64197
64350
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
64198
64351
  <plist version="1.0">
@@ -64220,11 +64373,11 @@ main().catch(() => {});
64220
64373
  if (existsSync9(plistPath)) {
64221
64374
  autoStartAlreadyInstalled = true;
64222
64375
  } else {
64223
- const launchAgentsDir = join8(home, "Library", "LaunchAgents");
64376
+ const launchAgentsDir = join9(home, "Library", "LaunchAgents");
64224
64377
  if (!existsSync9(launchAgentsDir)) {
64225
64378
  mkdirSync6(launchAgentsDir, { recursive: true });
64226
64379
  }
64227
- writeFileSync3(plistPath, plistContent, "utf-8");
64380
+ writeFileSync4(plistPath, plistContent, "utf-8");
64228
64381
  }
64229
64382
  } catch (e) {
64230
64383
  autoStartError = e instanceof Error ? e.message : String(e);
@@ -64237,7 +64390,7 @@ main().catch(() => {});
64237
64390
  console.log(chalk41.green(" \u2713 Auto-start configured (starts on login)"));
64238
64391
  }
64239
64392
  if (!autoStartAlreadyInstalled && !autoStartError) {
64240
- const plistPath2 = join8(home, "Library", "LaunchAgents", "com.hasna.mementos.plist");
64393
+ const plistPath2 = join9(home, "Library", "LaunchAgents", "com.hasna.mementos.plist");
64241
64394
  const loadResult = await run(["launchctl", "load", plistPath2]);
64242
64395
  if (!loadResult.ok) {
64243
64396
  console.log(chalk41.dim(` \xB7 launchctl load: ${loadResult.output || "already loaded"}`));
@@ -65323,11 +65476,11 @@ function lessonTagForCli(kind) {
65323
65476
  import {
65324
65477
  existsSync as existsSync11,
65325
65478
  mkdirSync as mkdirSync8,
65326
- writeFileSync as writeFileSync5,
65479
+ writeFileSync as writeFileSync6,
65327
65480
  readdirSync as readdirSync4
65328
65481
  } from "fs";
65329
65482
  import { homedir as homedir7 } from "os";
65330
- import { join as join10 } from "path";
65483
+ import { join as join11 } from "path";
65331
65484
  import chalk43 from "chalk";
65332
65485
 
65333
65486
  // src/lib/gatherer.ts
@@ -65404,12 +65557,12 @@ var gatherTrainingData = async (options = {}) => {
65404
65557
  };
65405
65558
 
65406
65559
  // src/lib/model-config.ts
65407
- import { existsSync as existsSync10, mkdirSync as mkdirSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
65560
+ import { existsSync as existsSync10, mkdirSync as mkdirSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
65408
65561
  import { homedir as homedir6 } from "os";
65409
- import { join as join9 } from "path";
65562
+ import { join as join10 } from "path";
65410
65563
  var DEFAULT_MODEL = "gpt-4o-mini";
65411
- var CONFIG_DIR = join9(homedir6(), ".hasna", "mementos");
65412
- var CONFIG_PATH = join9(CONFIG_DIR, "config.json");
65564
+ var CONFIG_DIR = join10(homedir6(), ".hasna", "mementos");
65565
+ var CONFIG_PATH = join10(CONFIG_DIR, "config.json");
65413
65566
  function readConfig() {
65414
65567
  if (!existsSync10(CONFIG_PATH))
65415
65568
  return {};
@@ -65424,7 +65577,7 @@ function writeConfig(config2) {
65424
65577
  if (!existsSync10(CONFIG_DIR)) {
65425
65578
  mkdirSync7(CONFIG_DIR, { recursive: true });
65426
65579
  }
65427
- writeFileSync4(CONFIG_PATH, JSON.stringify(config2, null, 2) + `
65580
+ writeFileSync5(CONFIG_PATH, JSON.stringify(config2, null, 2) + `
65428
65581
  `, "utf-8");
65429
65582
  }
65430
65583
  function getActiveModel() {
@@ -65469,15 +65622,15 @@ function makeBrainsCommand() {
65469
65622
  limit: opts.limit,
65470
65623
  since
65471
65624
  });
65472
- const outputDir = opts.output ?? join10(homedir7(), ".hasna", "mementos", "training");
65625
+ const outputDir = opts.output ?? join11(homedir7(), ".hasna", "mementos", "training");
65473
65626
  if (!existsSync11(outputDir)) {
65474
65627
  mkdirSync8(outputDir, { recursive: true });
65475
65628
  }
65476
65629
  const timestamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
65477
- const outputPath = join10(outputDir, `mementos-training-${timestamp}.jsonl`);
65630
+ const outputPath = join11(outputDir, `mementos-training-${timestamp}.jsonl`);
65478
65631
  const jsonl = result.examples.map((ex) => JSON.stringify(ex)).join(`
65479
65632
  `);
65480
- writeFileSync5(outputPath, jsonl + `
65633
+ writeFileSync6(outputPath, jsonl + `
65481
65634
  `, "utf-8");
65482
65635
  if (opts.json) {
65483
65636
  console.log(JSON.stringify({
@@ -65498,7 +65651,7 @@ function makeBrainsCommand() {
65498
65651
  try {
65499
65652
  let datasetPath = opts.dataset;
65500
65653
  if (!datasetPath) {
65501
- const trainingDir = join10(homedir7(), ".hasna", "mementos", "training");
65654
+ const trainingDir = join11(homedir7(), ".hasna", "mementos", "training");
65502
65655
  if (!existsSync11(trainingDir)) {
65503
65656
  printError("No training data found. Run `mementos brains gather` first.");
65504
65657
  process.exit(1);
@@ -65509,7 +65662,7 @@ function makeBrainsCommand() {
65509
65662
  printError("No JSONL training files found. Run `mementos brains gather` first.");
65510
65663
  process.exit(1);
65511
65664
  }
65512
- datasetPath = join10(trainingDir, latestFile);
65665
+ datasetPath = join11(trainingDir, latestFile);
65513
65666
  }
65514
65667
  if (!datasetPath || !existsSync11(datasetPath)) {
65515
65668
  printError(`Dataset file not found: ${datasetPath ?? "(unresolved)"}`);
@@ -65615,7 +65768,7 @@ function makeBrainsCommand() {
65615
65768
  // src/cli/index.tsx
65616
65769
  function getPackageVersion2() {
65617
65770
  try {
65618
- const pkgPath = join11(dirname7(fileURLToPath4(import.meta.url)), "..", "..", "package.json");
65771
+ const pkgPath = join12(dirname7(fileURLToPath4(import.meta.url)), "..", "..", "package.json");
65619
65772
  const pkg = JSON.parse(readFileSync8(pkgPath, "utf-8"));
65620
65773
  return pkg.version || "0.0.0";
65621
65774
  } catch {