@hasna/mementos 0.14.68 → 0.14.69

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 (37) hide show
  1. package/dist/cli/__fixtures__/clean-fallback-stub-server.d.ts +2 -0
  2. package/dist/cli/__fixtures__/clean-fallback-stub-server.d.ts.map +1 -0
  3. package/dist/cli/commands/io-clean.d.ts.map +1 -1
  4. package/dist/cli/commands/memory-cmd-crud.d.ts.map +1 -1
  5. package/dist/cli/commands/storage.d.ts.map +1 -1
  6. package/dist/cli/global-options.d.ts +27 -0
  7. package/dist/cli/global-options.d.ts.map +1 -0
  8. package/dist/cli/index.js +502 -241
  9. package/dist/cli/register-all.d.ts +17 -0
  10. package/dist/cli/register-all.d.ts.map +1 -0
  11. package/dist/cli/startup-side-effects.d.ts +9 -0
  12. package/dist/cli/startup-side-effects.d.ts.map +1 -0
  13. package/dist/db/__fixtures__/fail-closed-stub-server.d.ts +2 -0
  14. package/dist/db/__fixtures__/fail-closed-stub-server.d.ts.map +1 -0
  15. package/dist/db/api-mode.d.ts +70 -2
  16. package/dist/db/api-mode.d.ts.map +1 -1
  17. package/dist/db/locks.d.ts.map +1 -1
  18. package/dist/db/memories.d.ts +24 -6
  19. package/dist/db/memories.d.ts.map +1 -1
  20. package/dist/db/session-jobs.d.ts.map +1 -1
  21. package/dist/db/store-backend.d.ts +33 -0
  22. package/dist/db/store-backend.d.ts.map +1 -0
  23. package/dist/index.js +80 -20
  24. package/dist/lib/enum-validation.d.ts +20 -0
  25. package/dist/lib/enum-validation.d.ts.map +1 -0
  26. package/dist/mcp/index.js +147 -30
  27. package/dist/server/helpers.d.ts +11 -0
  28. package/dist/server/helpers.d.ts.map +1 -1
  29. package/dist/server/index.d.ts.map +1 -1
  30. package/dist/server/index.js +185 -30
  31. package/dist/test-support/preload-local-store.d.ts +2 -0
  32. package/dist/test-support/preload-local-store.d.ts.map +1 -0
  33. package/dist/test-support/store-isolation.d.ts +83 -0
  34. package/dist/test-support/store-isolation.d.ts.map +1 -0
  35. package/dist/types/index.d.ts +8 -4
  36. package/dist/types/index.d.ts.map +1 -1
  37. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -2101,9 +2101,9 @@ var require_commander = __commonJS((exports) => {
2101
2101
 
2102
2102
  // src/storage.ts
2103
2103
  import { Database } from "bun:sqlite";
2104
- import { existsSync as existsSync2, mkdirSync, readFileSync, writeFileSync } from "fs";
2105
- import { homedir as homedir2 } from "os";
2106
- import { join as join2 } from "path";
2104
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
2105
+ import { homedir } from "os";
2106
+ import { join } from "path";
2107
2107
  import { fileURLToPath } from "url";
2108
2108
  import { Worker } from "worker_threads";
2109
2109
  import pg from "pg";
@@ -2371,7 +2371,7 @@ function normalizeStorageMode(value) {
2371
2371
  return null;
2372
2372
  }
2373
2373
  function readConfigFile() {
2374
- if (!existsSync2(STORAGE_CONFIG_PATH)) {
2374
+ if (!existsSync(STORAGE_CONFIG_PATH)) {
2375
2375
  return {};
2376
2376
  }
2377
2377
  try {
@@ -2831,12 +2831,12 @@ var init_storage = __esm(() => {
2831
2831
  const ext = import.meta.url.endsWith(".ts") ? ".ts" : ".js";
2832
2832
  const here = fileURLToPath(new URL(".", import.meta.url));
2833
2833
  const candidates = [
2834
- join2(here, `pg-sync-worker${ext}`),
2835
- join2(here, "..", `pg-sync-worker${ext}`),
2836
- join2(here, "..", "..", `pg-sync-worker${ext}`)
2834
+ join(here, `pg-sync-worker${ext}`),
2835
+ join(here, "..", `pg-sync-worker${ext}`),
2836
+ join(here, "..", "..", `pg-sync-worker${ext}`)
2837
2837
  ];
2838
2838
  for (const candidate of candidates) {
2839
- if (existsSync2(candidate))
2839
+ if (existsSync(candidate))
2840
2840
  return candidate;
2841
2841
  }
2842
2842
  return candidates[0];
@@ -2911,7 +2911,7 @@ var init_storage = __esm(() => {
2911
2911
  databaseUrl: "MEMENTOS_DATABASE_URL",
2912
2912
  mode: "MEMENTOS_STORAGE_MODE"
2913
2913
  };
2914
- LOCAL_DATA_DIR = join2(homedir2(), ".hasna", "mementos");
2914
+ LOCAL_DATA_DIR = join(homedir(), ".hasna", "mementos");
2915
2915
  DEFAULT_STORAGE_CONFIG = {
2916
2916
  rds: {
2917
2917
  host: "",
@@ -2927,8 +2927,8 @@ var init_storage = __esm(() => {
2927
2927
  schedule_minutes: 0
2928
2928
  }
2929
2929
  };
2930
- STORAGE_CONFIG_DIR = join2(LOCAL_DATA_DIR, "storage");
2931
- STORAGE_CONFIG_PATH = join2(STORAGE_CONFIG_DIR, "config.json");
2930
+ STORAGE_CONFIG_DIR = join(LOCAL_DATA_DIR, "storage");
2931
+ STORAGE_CONFIG_PATH = join(STORAGE_CONFIG_DIR, "config.json");
2932
2932
  DATABASE_ENV_NAMES = [
2933
2933
  { name: MEMENTOS_STORAGE_ENV.databaseUrl, deprecated: false },
2934
2934
  { name: MEMENTOS_STORAGE_FALLBACK_ENV.databaseUrl, deprecated: false }
@@ -2958,10 +2958,10 @@ var init_storage = __esm(() => {
2958
2958
 
2959
2959
  // src/db/api-mode.ts
2960
2960
  import { tmpdir } from "os";
2961
- import { join as join3 } from "path";
2961
+ import { join as join2 } from "path";
2962
2962
  import { writeFileSync as writeFileSync2, unlinkSync } from "fs";
2963
- import { randomUUID as randomUUID3 } from "crypto";
2964
- function firstEnv(...keys) {
2963
+ import { randomUUID } from "crypto";
2964
+ function firstEnv(keys) {
2965
2965
  for (const k of keys) {
2966
2966
  const v = process.env[k]?.trim();
2967
2967
  if (v)
@@ -2969,8 +2969,45 @@ function firstEnv(...keys) {
2969
2969
  }
2970
2970
  return;
2971
2971
  }
2972
+ function firstEnvKey(keys) {
2973
+ for (const k of keys) {
2974
+ if (process.env[k]?.trim())
2975
+ return k;
2976
+ }
2977
+ return null;
2978
+ }
2972
2979
  function hasDatabaseUrl() {
2973
- return Boolean(firstEnv("HASNA_MEMENTOS_DATABASE_URL", "MEMENTOS_DATABASE_URL"));
2980
+ return Boolean(firstEnv(DATABASE_URL_ENV_KEYS));
2981
+ }
2982
+ function getApiModeEnvSources() {
2983
+ return {
2984
+ urlKey: firstEnvKey(API_URL_ENV_KEYS),
2985
+ keyKey: firstEnvKey(API_KEY_ENV_KEYS),
2986
+ databaseUrlKey: firstEnvKey(DATABASE_URL_ENV_KEYS)
2987
+ };
2988
+ }
2989
+ function isLoopbackHost(rawHost) {
2990
+ const host = rawHost.replace(/^\[/, "").replace(/\]$/, "").toLowerCase();
2991
+ return host === "localhost" || host === "::1" || /^127\./.test(host);
2992
+ }
2993
+ function assertRequestAllowedUnderTest(baseUrl) {
2994
+ if (process.env["NODE_ENV"] !== "test")
2995
+ return;
2996
+ if (process.env[ALLOW_REMOTE_API_IN_TESTS_ENV]?.trim())
2997
+ return;
2998
+ let host;
2999
+ try {
3000
+ host = new URL(baseUrl).hostname;
3001
+ } catch {
3002
+ host = "";
3003
+ }
3004
+ if (host && isLoopbackHost(host))
3005
+ return;
3006
+ throw new Error("api-mode: REFUSING to make a cloud request from a test process \u2014 this would write to or read " + "from the SHARED PRODUCTION memory store, where test fixtures are indistinguishable from real " + `memories.
3007
+ ` + ` host : ${host || "(unparseable base URL)"}
3008
+ ` + ` how this happens: a selector set at module scope (after the bun test preload ran), or \`bun test\` ` + `invoked from a directory with no bunfig.toml so the preload never loaded.
3009
+ ` + " fix : build the child/process env via src/test-support/store-isolation.ts, or point the " + `suite at a loopback stub.
3010
+ ` + ` override : set ${ALLOW_REMOTE_API_IN_TESTS_ENV}=1 only for a test that must reach a remote endpoint.`);
2974
3011
  }
2975
3012
  function normalizeBase(raw) {
2976
3013
  let base = raw.trim().replace(/\/+$/, "");
@@ -2978,9 +3015,24 @@ function normalizeBase(raw) {
2978
3015
  return base;
2979
3016
  return `${base}/v1`;
2980
3017
  }
3018
+ function assertUnambiguousStoreEnv() {
3019
+ if (firstEnvKey(DB_PATH_ENV_KEYS))
3020
+ return;
3021
+ if (hasDatabaseUrl())
3022
+ return;
3023
+ const urlKey = firstEnvKey(API_URL_ENV_KEYS);
3024
+ const keyKey = firstEnvKey(API_KEY_ENV_KEYS);
3025
+ if (urlKey && !keyKey) {
3026
+ throw new MementosStoreConfigError(`${urlKey} points at the cloud memory store but ${API_KEY_ENV_KEYS[0]} is not set. ` + `Refusing to serve the on-box SQLite store in its place, because it holds a different ` + `dataset. Set ${API_KEY_ENV_KEYS[0]} to reach the cloud store. If you meant to use the ` + `on-box SQLite store, unset ${urlKey} or set ${DB_PATH_ENV_KEYS[0]} explicitly.`);
3027
+ }
3028
+ if (keyKey && !urlKey) {
3029
+ throw new MementosStoreConfigError(`${keyKey} is set but ${API_URL_ENV_KEYS[0]} is not, so the cloud memory store cannot be ` + `reached. Refusing to serve the on-box SQLite store in its place, because it holds a ` + `different dataset. Set ${API_URL_ENV_KEYS[0]} to reach the cloud store. If you meant to ` + `use the on-box SQLite store, unset ${keyKey} or set ${DB_PATH_ENV_KEYS[0]} explicitly.`);
3030
+ }
3031
+ }
2981
3032
  function getApiConfig() {
2982
- const rawBase = firstEnv("HASNA_MEMENTOS_API_URL", "MEMENTOS_API_URL");
2983
- const apiKey = firstEnv("HASNA_MEMENTOS_API_KEY", "MEMENTOS_API_KEY");
3033
+ assertUnambiguousStoreEnv();
3034
+ const rawBase = firstEnv(API_URL_ENV_KEYS);
3035
+ const apiKey = firstEnv(API_KEY_ENV_KEYS);
2984
3036
  if (!rawBase || !apiKey)
2985
3037
  return null;
2986
3038
  return { baseUrl: normalizeBase(rawBase), apiKey };
@@ -2994,6 +3046,7 @@ function apiRequestRaw(method, path, body) {
2994
3046
  const cfg = getApiConfig();
2995
3047
  if (!cfg)
2996
3048
  throw new Error("api-mode: not configured (HASNA_MEMENTOS_API_URL / HASNA_MEMENTOS_API_KEY)");
3049
+ assertRequestAllowedUnderTest(cfg.baseUrl);
2997
3050
  const url = `${cfg.baseUrl}${path.startsWith("/") ? path : `/${path}`}`;
2998
3051
  const hasBody = body !== undefined && body !== null;
2999
3052
  const timeout = process.env["HASNA_MEMENTOS_API_TIMEOUT"] || DEFAULT_TIMEOUT_S;
@@ -3019,7 +3072,7 @@ x-api-key: ${cfg.apiKey}
3019
3072
  ];
3020
3073
  let bodyFile;
3021
3074
  if (hasBody) {
3022
- bodyFile = join3(tmpdir(), `mem-req-${process.pid}-${randomUUID3()}.json`);
3075
+ bodyFile = join2(tmpdir(), `mem-req-${process.pid}-${randomUUID()}.json`);
3023
3076
  writeFileSync2(bodyFile, JSON.stringify(body), { mode: 384 });
3024
3077
  args.push("--data-binary", `@${bodyFile}`);
3025
3078
  }
@@ -3060,13 +3113,13 @@ x-api-key: ${cfg.apiKey}
3060
3113
  }
3061
3114
  return { status, body: respBody };
3062
3115
  }
3063
- function apiJson(method, path, body) {
3116
+ function apiJson(method, path, body, options) {
3064
3117
  const raw = apiRequestRaw(method, path, body);
3065
3118
  if (raw.status >= 200 && raw.status < 300) {
3066
3119
  const data = raw.body.trim() ? JSON.parse(raw.body) : undefined;
3067
3120
  return { status: raw.status, data };
3068
3121
  }
3069
- if (raw.status === 404) {
3122
+ if (raw.status === 404 && options?.allow404) {
3070
3123
  return { status: 404, data: undefined };
3071
3124
  }
3072
3125
  let msg = `mementos cloud ${method} ${path} \u2192 ${raw.status}`;
@@ -3098,8 +3151,19 @@ function toQuery(params) {
3098
3151
  const s = sp.toString();
3099
3152
  return s ? `?${s}` : "";
3100
3153
  }
3101
- var ApiRequestError, DEFAULT_TIMEOUT_S = "45";
3154
+ var API_URL_ENV_KEYS, API_KEY_ENV_KEYS, DATABASE_URL_ENV_KEYS, ALLOW_REMOTE_API_IN_TESTS_ENV = "MEMENTOS_ALLOW_REMOTE_API_IN_TESTS", DB_PATH_ENV_KEYS, MementosStoreConfigError, ApiRequestError, DEFAULT_TIMEOUT_S = "45";
3102
3155
  var init_api_mode = __esm(() => {
3156
+ API_URL_ENV_KEYS = ["HASNA_MEMENTOS_API_URL", "MEMENTOS_API_URL"];
3157
+ API_KEY_ENV_KEYS = ["HASNA_MEMENTOS_API_KEY", "MEMENTOS_API_KEY"];
3158
+ DATABASE_URL_ENV_KEYS = ["HASNA_MEMENTOS_DATABASE_URL", "MEMENTOS_DATABASE_URL"];
3159
+ DB_PATH_ENV_KEYS = ["HASNA_MEMENTOS_DB_PATH", "MEMENTOS_DB_PATH"];
3160
+ MementosStoreConfigError = class MementosStoreConfigError extends Error {
3161
+ code = "MEMENTOS_STORE_CONFIG";
3162
+ constructor(message) {
3163
+ super(message);
3164
+ this.name = "MementosStoreConfigError";
3165
+ }
3166
+ };
3103
3167
  ApiRequestError = class ApiRequestError extends Error {
3104
3168
  status;
3105
3169
  body;
@@ -4022,13 +4086,13 @@ __export(exports_database, {
4022
4086
  shortUuid: () => shortUuid,
4023
4087
  resolvePartialId: () => resolvePartialId,
4024
4088
  resetDatabase: () => resetDatabase,
4025
- now: () => now2,
4089
+ now: () => now,
4026
4090
  getDbPath: () => getDbPath,
4027
4091
  getDatabase: () => getDatabase,
4028
4092
  closeDatabase: () => closeDatabase
4029
4093
  });
4030
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, cpSync } from "fs";
4031
- import { dirname, join as join4, resolve } from "path";
4094
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, cpSync } from "fs";
4095
+ import { dirname, join as join3, resolve } from "path";
4032
4096
  function isInMemoryDb(path) {
4033
4097
  return path === ":memory:" || path.startsWith("file::memory:");
4034
4098
  }
@@ -4037,8 +4101,8 @@ function findNearestMementosDb(startDir) {
4037
4101
  const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
4038
4102
  const legacyHomeDb = resolve(home, ".mementos", "mementos.db");
4039
4103
  while (true) {
4040
- const candidate = join4(dir, ".mementos", "mementos.db");
4041
- if (existsSync3(candidate) && resolve(candidate) !== legacyHomeDb)
4104
+ const candidate = join3(dir, ".mementos", "mementos.db");
4105
+ if (existsSync2(candidate) && resolve(candidate) !== legacyHomeDb)
4042
4106
  return candidate;
4043
4107
  const parent = dirname(dir);
4044
4108
  if (parent === dir)
@@ -4050,7 +4114,7 @@ function findNearestMementosDb(startDir) {
4050
4114
  function findGitRoot(startDir) {
4051
4115
  let dir = resolve(startDir);
4052
4116
  while (true) {
4053
- if (existsSync3(join4(dir, ".git")))
4117
+ if (existsSync2(join3(dir, ".git")))
4054
4118
  return dir;
4055
4119
  const parent = dirname(dir);
4056
4120
  if (parent === dir)
@@ -4061,10 +4125,10 @@ function findGitRoot(startDir) {
4061
4125
  }
4062
4126
  function migrateGlobalDir() {
4063
4127
  const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
4064
- const newDir = join4(home, ".hasna", "mementos");
4065
- const oldDir = join4(home, ".mementos");
4066
- if (!existsSync3(newDir) && existsSync3(oldDir)) {
4067
- mkdirSync2(join4(home, ".hasna"), { recursive: true });
4128
+ const newDir = join3(home, ".hasna", "mementos");
4129
+ const oldDir = join3(home, ".mementos");
4130
+ if (!existsSync2(newDir) && existsSync2(oldDir)) {
4131
+ mkdirSync2(join3(home, ".hasna"), { recursive: true });
4068
4132
  cpSync(oldDir, newDir, { recursive: true });
4069
4133
  }
4070
4134
  }
@@ -4080,18 +4144,18 @@ function getDbPath() {
4080
4144
  if (process.env["MEMENTOS_DB_SCOPE"] === "project") {
4081
4145
  const gitRoot = findGitRoot(cwd);
4082
4146
  if (gitRoot) {
4083
- return join4(gitRoot, ".mementos", "mementos.db");
4147
+ return join3(gitRoot, ".mementos", "mementos.db");
4084
4148
  }
4085
4149
  }
4086
4150
  migrateGlobalDir();
4087
4151
  const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
4088
- return join4(home, ".hasna", "mementos", "mementos.db");
4152
+ return join3(home, ".hasna", "mementos", "mementos.db");
4089
4153
  }
4090
4154
  function ensureDir(filePath) {
4091
4155
  if (isInMemoryDb(filePath))
4092
4156
  return;
4093
4157
  const dir = dirname(resolve(filePath));
4094
- if (!existsSync3(dir)) {
4158
+ if (!existsSync2(dir)) {
4095
4159
  mkdirSync2(dir, { recursive: true });
4096
4160
  }
4097
4161
  }
@@ -4190,7 +4254,7 @@ function resetDatabase() {
4190
4254
  _db = null;
4191
4255
  _pg = null;
4192
4256
  }
4193
- function now2() {
4257
+ function now() {
4194
4258
  return new Date().toISOString();
4195
4259
  }
4196
4260
  function uuid() {
@@ -4236,8 +4300,19 @@ var init_database = __esm(() => {
4236
4300
  });
4237
4301
 
4238
4302
  // src/types/index.ts
4239
- var AgentConflictError, EntityNotFoundError, MemoryNotFoundError, VersionConflictError, MemoryConflictError;
4303
+ var MEMORY_SCOPES, MEMORY_CATEGORIES, MEMORY_SOURCES, MEMORY_STATUSES, AgentConflictError, EntityNotFoundError, MemoryNotFoundError, VersionConflictError, MemoryConflictError;
4240
4304
  var init_types = __esm(() => {
4305
+ MEMORY_SCOPES = ["global", "shared", "private", "working"];
4306
+ MEMORY_CATEGORIES = [
4307
+ "preference",
4308
+ "fact",
4309
+ "knowledge",
4310
+ "history",
4311
+ "procedural",
4312
+ "resource"
4313
+ ];
4314
+ MEMORY_SOURCES = ["user", "agent", "system", "auto", "imported"];
4315
+ MEMORY_STATUSES = ["active", "archived", "expired"];
4241
4316
  AgentConflictError = class AgentConflictError extends Error {
4242
4317
  conflict = true;
4243
4318
  existing_id;
@@ -4394,6 +4469,41 @@ var init_redact = __esm(() => {
4394
4469
  ];
4395
4470
  });
4396
4471
 
4472
+ // src/lib/enum-validation.ts
4473
+ function formatEnumViolation(v) {
4474
+ return `Invalid ${v.field}: "${v.value}". Allowed values: ${v.allowed.join(", ")}.`;
4475
+ }
4476
+ function validateEnumField(field, value) {
4477
+ const allowed = ENUM_FIELDS[field];
4478
+ if (!allowed)
4479
+ return null;
4480
+ if (value === undefined || value === null || value === "")
4481
+ return null;
4482
+ if (typeof value === "string" && allowed.includes(value))
4483
+ return null;
4484
+ return { field, value: String(value), allowed };
4485
+ }
4486
+ function validateMemoryEnums(input) {
4487
+ for (const field of Object.keys(ENUM_FIELDS)) {
4488
+ if (!(field in input))
4489
+ continue;
4490
+ const violation = validateEnumField(field, input[field]);
4491
+ if (violation)
4492
+ return violation;
4493
+ }
4494
+ return null;
4495
+ }
4496
+ var ENUM_FIELDS;
4497
+ var init_enum_validation = __esm(() => {
4498
+ init_types();
4499
+ ENUM_FIELDS = {
4500
+ category: MEMORY_CATEGORIES,
4501
+ scope: MEMORY_SCOPES,
4502
+ source: MEMORY_SOURCES,
4503
+ status: MEMORY_STATUSES
4504
+ };
4505
+ });
4506
+
4397
4507
  // src/lib/hooks.ts
4398
4508
  var exports_hooks = {};
4399
4509
  __export(exports_hooks, {
@@ -4565,7 +4675,7 @@ function linkEntityToMemory(entityId, memoryId, role = "context", db) {
4565
4675
  return data;
4566
4676
  }
4567
4677
  const d = db || getDatabase();
4568
- const timestamp = now2();
4678
+ const timestamp = now();
4569
4679
  d.run(`INSERT OR IGNORE INTO entity_memories (entity_id, memory_id, role, created_at)
4570
4680
  VALUES (?, ?, ?, ?)`, [entityId, memoryId, role, timestamp]);
4571
4681
  const row = d.query("SELECT * FROM entity_memories WHERE entity_id = ? AND memory_id = ?").get(entityId, memoryId);
@@ -4691,11 +4801,14 @@ function parseMemoryRow(row) {
4691
4801
  }
4692
4802
  function createMemory(input, dedupeMode = "merge", db) {
4693
4803
  if (!db && isApiMode()) {
4694
- const { data } = apiJson("POST", "/memories", { ...input, dedupe: dedupeMode });
4804
+ const { status, data } = apiJson("POST", "/memories", { ...input, dedupe: dedupeMode });
4805
+ if (!data || !data.id) {
4806
+ throw new ApiRequestError(`mementos cloud POST /memories \u2192 ${status} but no memory was returned; the write did not persist (key: ${input.key})`, status, "");
4807
+ }
4695
4808
  return data;
4696
4809
  }
4697
4810
  const d = db || getDatabase();
4698
- const timestamp = now2();
4811
+ const timestamp = now();
4699
4812
  if (input.project_id) {
4700
4813
  const resolved = resolvePartialId(d, "projects", input.project_id);
4701
4814
  if (resolved) {
@@ -4830,19 +4943,28 @@ function bulkUpsertMemories(memories, db) {
4830
4943
  const d = db || getDatabase();
4831
4944
  let inserted = 0;
4832
4945
  let skipped = 0;
4946
+ let rejected = 0;
4833
4947
  const errors = [];
4834
- const insert = d.prepare(`INSERT OR IGNORE INTO memories (id, key, value, category, scope, summary, tags, importance, source, status, pinned, agent_id, project_id, session_id, machine_id, namespace, created_by_agent, when_to_use, sequence_group, sequence_order, metadata, access_count, version, expires_at, valid_from, valid_until, ingested_at, created_at, updated_at)
4835
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
4948
+ const insert = d.prepare(`INSERT INTO memories (id, key, value, category, scope, summary, tags, importance, source, status, pinned, agent_id, project_id, session_id, machine_id, namespace, created_by_agent, when_to_use, sequence_group, sequence_order, metadata, access_count, version, expires_at, valid_from, valid_until, ingested_at, created_at, updated_at)
4949
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
4950
+ ON CONFLICT DO NOTHING`);
4836
4951
  const insertTag = d.prepare("INSERT OR IGNORE INTO memory_tags (memory_id, tag) VALUES (?, ?)");
4837
4952
  for (const mem of memories) {
4838
4953
  const key = mem["key"];
4839
4954
  const id = mem["id"] || uuid();
4840
4955
  try {
4841
4956
  if (!key) {
4842
- errors.push(`skipped row without key (id=${id})`);
4957
+ rejected++;
4958
+ errors.push(`rejected row without key (id=${id})`);
4959
+ continue;
4960
+ }
4961
+ const violation = validateMemoryEnums(mem);
4962
+ if (violation) {
4963
+ rejected++;
4964
+ errors.push(`Rejected "${key}": ${formatEnumViolation(violation)}`);
4843
4965
  continue;
4844
4966
  }
4845
- const timestamp = now2();
4967
+ const timestamp = now();
4846
4968
  let tags = [];
4847
4969
  const rawTags = mem["tags"];
4848
4970
  if (Array.isArray(rawTags)) {
@@ -4888,13 +5010,14 @@ function bulkUpsertMemories(memories, db) {
4888
5010
  skipped++;
4889
5011
  }
4890
5012
  } catch (e) {
5013
+ rejected++;
4891
5014
  errors.push(`Failed "${String(key)}": ${e instanceof Error ? e.message : String(e)}`);
4892
5015
  }
4893
5016
  }
4894
- return { inserted, skipped, errors, total: memories.length };
5017
+ return { inserted, skipped, rejected, errors, total: memories.length };
4895
5018
  }
4896
5019
  function ensureMemoryReferences(d, input) {
4897
- const t = now2();
5020
+ const t = now();
4898
5021
  const tryRun = (sql, params) => {
4899
5022
  try {
4900
5023
  d.run(sql, params);
@@ -4919,7 +5042,7 @@ function listMemoriesByKey(key, db) {
4919
5042
  }
4920
5043
  function getMemory(id, db) {
4921
5044
  if (!db && isApiMode()) {
4922
- const { status, data } = apiJson("GET", `/memories/${encodeURIComponent(id)}`);
5045
+ const { status, data } = apiJson("GET", `/memories/${encodeURIComponent(id)}`, undefined, { allow404: true });
4923
5046
  return status === 404 ? null : data ?? null;
4924
5047
  }
4925
5048
  const d = db || getDatabase();
@@ -5252,7 +5375,7 @@ function getMemoryEmbeddings(ids, db) {
5252
5375
  }
5253
5376
  function updateMemory(id, input, db) {
5254
5377
  if (!db && isApiMode()) {
5255
- const { status, data } = apiJson("PATCH", `/memories/${encodeURIComponent(id)}`, input);
5378
+ const { status, data } = apiJson("PATCH", `/memories/${encodeURIComponent(id)}`, input, { allow404: true });
5256
5379
  if (status === 404)
5257
5380
  throw new MemoryNotFoundError(id);
5258
5381
  return data;
@@ -5283,7 +5406,7 @@ function updateMemory(id, input, db) {
5283
5406
  ]);
5284
5407
  } catch {}
5285
5408
  const sets = ["version = version + 1", "updated_at = ?"];
5286
- const params = [now2()];
5409
+ const params = [now()];
5287
5410
  if (input.value !== undefined) {
5288
5411
  sets.push("value = ?");
5289
5412
  params.push(redactSecrets(input.value));
@@ -5360,7 +5483,7 @@ function updateMemory(id, input, db) {
5360
5483
  }
5361
5484
  function deleteMemory(id, db) {
5362
5485
  if (!db && isApiMode()) {
5363
- const { status } = apiJson("DELETE", `/memories/${encodeURIComponent(id)}`);
5486
+ const { status } = apiJson("DELETE", `/memories/${encodeURIComponent(id)}`, undefined, { allow404: true });
5364
5487
  return status !== 404;
5365
5488
  }
5366
5489
  const d = db || getDatabase();
@@ -5393,14 +5516,14 @@ function touchMemory(id, db) {
5393
5516
  if (!db && isApiMode())
5394
5517
  return;
5395
5518
  const d = db || getDatabase();
5396
- d.run("UPDATE memories SET access_count = access_count + 1, accessed_at = ? WHERE id = ?", [now2(), id]);
5519
+ d.run("UPDATE memories SET access_count = access_count + 1, accessed_at = ? WHERE id = ?", [now(), id]);
5397
5520
  }
5398
5521
  function incrementRecallCount(id, db) {
5399
5522
  if (!db && isApiMode())
5400
5523
  return;
5401
5524
  const d = db || getDatabase();
5402
5525
  try {
5403
- d.run("UPDATE memories SET recall_count = recall_count + 1, access_count = access_count + 1, accessed_at = ? WHERE id = ?", [now2(), id]);
5526
+ d.run("UPDATE memories SET recall_count = recall_count + 1, access_count = access_count + 1, accessed_at = ? WHERE id = ?", [now(), id]);
5404
5527
  const row = d.query("SELECT recall_count, importance FROM memories WHERE id = ?").get(id);
5405
5528
  if (!row)
5406
5529
  return;
@@ -5417,7 +5540,7 @@ function cleanExpiredMemories(db) {
5417
5540
  return data?.cleaned ?? 0;
5418
5541
  }
5419
5542
  const d = db || getDatabase();
5420
- const timestamp = now2();
5543
+ const timestamp = now();
5421
5544
  const countRow = d.query("SELECT COUNT(*) as c FROM memories WHERE expires_at IS NOT NULL AND expires_at < ?").get(timestamp);
5422
5545
  const count = countRow.c;
5423
5546
  if (count > 0) {
@@ -5512,6 +5635,7 @@ var init_memories = __esm(() => {
5512
5635
  init_types();
5513
5636
  init_database();
5514
5637
  init_redact();
5638
+ init_enum_validation();
5515
5639
  init_hooks();
5516
5640
  init_poisoning();
5517
5641
  init_entity_memories();
@@ -5547,7 +5671,7 @@ function registerProject(name, path, description, memoryPrefix, db) {
5547
5671
  return data;
5548
5672
  }
5549
5673
  const d = db || getDatabase();
5550
- const timestamp = now2();
5674
+ const timestamp = now();
5551
5675
  const existing = d.query("SELECT * FROM projects WHERE path = ?").get(path);
5552
5676
  if (existing) {
5553
5677
  const existingId = existing["id"];
@@ -5563,7 +5687,7 @@ function registerProject(name, path, description, memoryPrefix, db) {
5563
5687
  }
5564
5688
  function getProject(idOrPath, db) {
5565
5689
  if (!db && isApiMode()) {
5566
- const { status, data } = apiJson("GET", `/projects/${encodeURIComponent(idOrPath)}`);
5690
+ const { status, data } = apiJson("GET", `/projects/${encodeURIComponent(idOrPath)}`, undefined, { allow404: true });
5567
5691
  if (status === 404 || !data)
5568
5692
  return null;
5569
5693
  return data;
@@ -5613,7 +5737,7 @@ function createEntity(input, db) {
5613
5737
  return data;
5614
5738
  }
5615
5739
  const d = db || getDatabase();
5616
- const timestamp = now2();
5740
+ const timestamp = now();
5617
5741
  const metadataJson = JSON.stringify(input.metadata || {});
5618
5742
  const existing = d.query(`SELECT * FROM entities
5619
5743
  WHERE name = ? AND type = ? AND COALESCE(project_id, '') = ?`).get(input.name, input.type, input.project_id || "");
@@ -5656,7 +5780,7 @@ function createEntity(input, db) {
5656
5780
  }
5657
5781
  function getEntity(id, db) {
5658
5782
  if (!db && isApiMode()) {
5659
- const { status, data } = apiJson("GET", `/entities/${encodeURIComponent(id)}`);
5783
+ const { status, data } = apiJson("GET", `/entities/${encodeURIComponent(id)}`, undefined, { allow404: true });
5660
5784
  if (status === 404 || !data)
5661
5785
  throw new EntityNotFoundError(id);
5662
5786
  return data;
@@ -5739,7 +5863,7 @@ function listEntities(filter = {}, db) {
5739
5863
  }
5740
5864
  function deleteEntity(id, db) {
5741
5865
  if (!db && isApiMode()) {
5742
- const { status } = apiJson("DELETE", `/entities/${encodeURIComponent(id)}`);
5866
+ const { status } = apiJson("DELETE", `/entities/${encodeURIComponent(id)}`, undefined, { allow404: true });
5743
5867
  if (status === 404)
5744
5868
  throw new EntityNotFoundError(id);
5745
5869
  return;
@@ -5789,7 +5913,7 @@ function mergeEntities(sourceId, targetId, db) {
5789
5913
  d.run(`UPDATE entity_memories SET entity_id = ? WHERE entity_id = ?`, [tgt, src]);
5790
5914
  d.run("DELETE FROM entity_memories WHERE entity_id = ?", [src]);
5791
5915
  d.run("DELETE FROM entities WHERE id = ?", [src]);
5792
- d.run("UPDATE entities SET updated_at = ? WHERE id = ?", [now2(), tgt]);
5916
+ d.run("UPDATE entities SET updated_at = ? WHERE id = ?", [now(), tgt]);
5793
5917
  return getEntity(tgt, d);
5794
5918
  }
5795
5919
  var init_entities = __esm(() => {
@@ -6454,7 +6578,7 @@ function registerAgent(name, sessionId, description, role, projectId, db) {
6454
6578
  return data;
6455
6579
  }
6456
6580
  const d = db || getDatabase();
6457
- const timestamp = now2();
6581
+ const timestamp = now();
6458
6582
  const normalizedName = name.trim().toLowerCase();
6459
6583
  if (projectId) {
6460
6584
  const resolvedProjectId = resolvePartialId(d, "projects", projectId);
@@ -6503,7 +6627,7 @@ function registerAgent(name, sessionId, description, role, projectId, db) {
6503
6627
  }
6504
6628
  function getAgent(idOrName, db) {
6505
6629
  if (!db && isApiMode()) {
6506
- const { status, data } = apiJson("GET", `/agents/${encodeURIComponent(idOrName)}`);
6630
+ const { status, data } = apiJson("GET", `/agents/${encodeURIComponent(idOrName)}`, undefined, { allow404: true });
6507
6631
  if (status === 404 || !data)
6508
6632
  return null;
6509
6633
  return data;
@@ -6541,7 +6665,7 @@ function touchAgent(idOrName, db) {
6541
6665
  const agent = getAgent(idOrName, d);
6542
6666
  if (!agent)
6543
6667
  return;
6544
- d.run("UPDATE agents SET last_seen_at = ? WHERE id = ?", [now2(), agent.id]);
6668
+ d.run("UPDATE agents SET last_seen_at = ? WHERE id = ?", [now(), agent.id]);
6545
6669
  }
6546
6670
  function listAgentsByProject(projectId, db) {
6547
6671
  if (!db && isApiMode()) {
@@ -6556,7 +6680,7 @@ function listAgentsByProject(projectId, db) {
6556
6680
  }
6557
6681
  function updateAgent(id, updates, db) {
6558
6682
  if (!db && isApiMode()) {
6559
- const { status, data } = apiJson("PATCH", `/agents/${encodeURIComponent(id)}`, updates);
6683
+ const { status, data } = apiJson("PATCH", `/agents/${encodeURIComponent(id)}`, updates, { allow404: true });
6560
6684
  if (status === 404 || !data)
6561
6685
  return null;
6562
6686
  return data;
@@ -6565,7 +6689,7 @@ function updateAgent(id, updates, db) {
6565
6689
  const agent = getAgent(id, d);
6566
6690
  if (!agent)
6567
6691
  return null;
6568
- const timestamp = now2();
6692
+ const timestamp = now();
6569
6693
  if (updates.name) {
6570
6694
  const normalizedNewName = updates.name.trim().toLowerCase();
6571
6695
  if (normalizedNewName !== agent.name) {
@@ -7402,7 +7526,7 @@ function createRelation(input, db) {
7402
7526
  }
7403
7527
  const d = db || getDatabase();
7404
7528
  const id = shortUuid();
7405
- const timestamp = now2();
7529
+ const timestamp = now();
7406
7530
  const weight = input.weight ?? 1;
7407
7531
  const metadata = JSON.stringify(input.metadata ?? {});
7408
7532
  d.run(`INSERT INTO relations (id, source_entity_id, target_entity_id, relation_type, weight, metadata, created_at)
@@ -7455,7 +7579,7 @@ function listRelations(filter, db) {
7455
7579
  }
7456
7580
  function deleteRelation(id, db) {
7457
7581
  if (!db && isApiMode()) {
7458
- const { status } = apiJson("DELETE", `/relations/${encodeURIComponent(id)}`);
7582
+ const { status } = apiJson("DELETE", `/relations/${encodeURIComponent(id)}`, undefined, { allow404: true });
7459
7583
  if (status === 404)
7460
7584
  throw new Error(`Relation not found: ${id}`);
7461
7585
  return;
@@ -8367,7 +8491,7 @@ function createWebhookHook(input, db) {
8367
8491
  }
8368
8492
  const d = db || getDatabase();
8369
8493
  const id = shortUuid();
8370
- const timestamp = now2();
8494
+ const timestamp = now();
8371
8495
  d.run(`INSERT INTO webhook_hooks
8372
8496
  (id, type, handler_url, priority, blocking, agent_id, project_id, description, enabled, created_at, invocation_count, failure_count)
8373
8497
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, 1, ?, 0, 0)`, [
@@ -8385,7 +8509,7 @@ function createWebhookHook(input, db) {
8385
8509
  }
8386
8510
  function getWebhookHook(id, db) {
8387
8511
  if (!db && isApiMode()) {
8388
- const { status, data } = apiJson("GET", `/webhooks/${encodeURIComponent(id)}`);
8512
+ const { status, data } = apiJson("GET", `/webhooks/${encodeURIComponent(id)}`, undefined, { allow404: true });
8389
8513
  if (status === 404 || !data)
8390
8514
  return null;
8391
8515
  return data;
@@ -8421,7 +8545,7 @@ function updateWebhookHook(id, updates, db) {
8421
8545
  enabled: updates.enabled,
8422
8546
  priority: updates.priority,
8423
8547
  description: updates.description
8424
- });
8548
+ }, { allow404: true });
8425
8549
  if (status === 404 || !data)
8426
8550
  return null;
8427
8551
  return data;
@@ -8452,7 +8576,7 @@ function updateWebhookHook(id, updates, db) {
8452
8576
  }
8453
8577
  function deleteWebhookHook(id, db) {
8454
8578
  if (!db && isApiMode()) {
8455
- const { status } = apiJson("DELETE", `/webhooks/${encodeURIComponent(id)}`);
8579
+ const { status } = apiJson("DELETE", `/webhooks/${encodeURIComponent(id)}`, undefined, { allow404: true });
8456
8580
  return status === 204 || status === 200;
8457
8581
  }
8458
8582
  const d = db || getDatabase();
@@ -8550,7 +8674,7 @@ function parseEventRow(row) {
8550
8674
  function createSynthesisRun(input, db) {
8551
8675
  const d = db || getDatabase();
8552
8676
  const id = shortUuid();
8553
- const timestamp = now2();
8677
+ const timestamp = now();
8554
8678
  d.run(`INSERT INTO synthesis_runs (id, triggered_by, project_id, agent_id, corpus_size, proposals_generated, proposals_accepted, proposals_rejected, status, started_at)
8555
8679
  VALUES (?, ?, ?, ?, ?, 0, 0, 0, 'pending', ?)`, [
8556
8680
  id,
@@ -8651,7 +8775,7 @@ function updateSynthesisRun(id, updates, db) {
8651
8775
  function createProposal(input, db) {
8652
8776
  const d = db || getDatabase();
8653
8777
  const id = shortUuid();
8654
- const timestamp = now2();
8778
+ const timestamp = now();
8655
8779
  d.run(`INSERT INTO synthesis_proposals (id, run_id, proposal_type, memory_ids, target_memory_id, proposed_changes, reasoning, confidence, status, created_at)
8656
8780
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'pending', ?)`, [
8657
8781
  id,
@@ -8710,7 +8834,7 @@ function updateProposal(id, updates, db) {
8710
8834
  function createMetric(input, db) {
8711
8835
  const d = db || getDatabase();
8712
8836
  const id = shortUuid();
8713
- const timestamp = now2();
8837
+ const timestamp = now();
8714
8838
  d.run(`INSERT INTO synthesis_metrics (id, run_id, metric_type, value, baseline, created_at)
8715
8839
  VALUES (?, ?, ?, ?, ?, ?)`, [id, input.run_id, input.metric_type, input.value, input.baseline ?? null, timestamp]);
8716
8840
  return { id, run_id: input.run_id, metric_type: input.metric_type, value: input.value, baseline: input.baseline ?? null, created_at: timestamp };
@@ -8724,7 +8848,7 @@ function recordSynthesisEvent(input, db) {
8724
8848
  try {
8725
8849
  const d = db || getDatabase();
8726
8850
  const id = shortUuid();
8727
- const timestamp = now2();
8851
+ const timestamp = now();
8728
8852
  d.run(`INSERT INTO synthesis_events (id, event_type, memory_id, agent_id, project_id, session_id, query, importance_at_time, metadata, created_at)
8729
8853
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
8730
8854
  id,
@@ -8978,7 +9102,7 @@ __export(exports_contradiction, {
8978
9102
  });
8979
9103
  function invalidateFact(oldMemoryId, newMemoryId, db) {
8980
9104
  const d = db || getDatabase();
8981
- const timestamp = now2();
9105
+ const timestamp = now();
8982
9106
  d.run("UPDATE memories SET valid_until = ?, updated_at = ? WHERE id = ?", [timestamp, timestamp, oldMemoryId]);
8983
9107
  if (newMemoryId) {
8984
9108
  const row = d.query("SELECT metadata FROM memories WHERE id = ?").get(newMemoryId);
@@ -9456,7 +9580,7 @@ async function buildCorpus(options) {
9456
9580
  duplicateCandidates,
9457
9581
  lowImportanceHighRecall,
9458
9582
  highImportanceLowRecall,
9459
- generatedAt: now2()
9583
+ generatedAt: now()
9460
9584
  };
9461
9585
  }
9462
9586
  var init_corpus_builder = __esm(() => {
@@ -9789,7 +9913,7 @@ async function executeProposals(runId, proposals, db) {
9789
9913
  const rollback = executeProposal(proposal, d);
9790
9914
  updateProposal(proposal.id, {
9791
9915
  status: "accepted",
9792
- executed_at: now2(),
9916
+ executed_at: now(),
9793
9917
  rollback_data: rollback
9794
9918
  }, d);
9795
9919
  rollbackData[proposal.id] = rollback;
@@ -9838,7 +9962,7 @@ function executeArchive(proposal, d) {
9838
9962
  if (!mem)
9839
9963
  continue;
9840
9964
  rollback[memId] = mem.status;
9841
- d.run("UPDATE memories SET status = 'archived', updated_at = ? WHERE id = ?", [now2(), memId]);
9965
+ d.run("UPDATE memories SET status = 'archived', updated_at = ? WHERE id = ?", [now(), memId]);
9842
9966
  }
9843
9967
  return { old_status: rollback };
9844
9968
  }
@@ -9853,7 +9977,7 @@ function executePromote(proposal, d) {
9853
9977
  if (!mem)
9854
9978
  continue;
9855
9979
  rollback[memId] = mem.importance;
9856
- d.run("UPDATE memories SET importance = ?, updated_at = ? WHERE id = ?", [Math.max(1, Math.min(10, Math.round(newImportance))), now2(), memId]);
9980
+ d.run("UPDATE memories SET importance = ?, updated_at = ? WHERE id = ?", [Math.max(1, Math.min(10, Math.round(newImportance))), now(), memId]);
9857
9981
  }
9858
9982
  return { old_importance: rollback };
9859
9983
  }
@@ -9870,7 +9994,7 @@ function executeUpdateValue(proposal, d) {
9870
9994
  if (!mem)
9871
9995
  throw new Error(`Memory ${memId} not found`);
9872
9996
  rollback[memId] = { value: mem.value, version: mem.version };
9873
- d.run("UPDATE memories SET value = ?, version = version + 1, updated_at = ? WHERE id = ?", [newValue, now2(), memId]);
9997
+ d.run("UPDATE memories SET value = ?, version = version + 1, updated_at = ? WHERE id = ?", [newValue, now(), memId]);
9874
9998
  return { old_state: rollback };
9875
9999
  }
9876
10000
  function executeAddTag(proposal, d) {
@@ -9885,7 +10009,7 @@ function executeAddTag(proposal, d) {
9885
10009
  continue;
9886
10010
  rollback[memId] = [...mem.tags];
9887
10011
  const newTags = Array.from(new Set([...mem.tags, ...tagsToAdd]));
9888
- d.run("UPDATE memories SET tags = ?, updated_at = ? WHERE id = ?", [JSON.stringify(newTags), now2(), memId]);
10012
+ d.run("UPDATE memories SET tags = ?, updated_at = ? WHERE id = ?", [JSON.stringify(newTags), now(), memId]);
9889
10013
  const insertTag = d.prepare("INSERT OR IGNORE INTO memory_tags (memory_id, tag) VALUES (?, ?)");
9890
10014
  for (const tag of tagsToAdd) {
9891
10015
  insertTag.run(memId, tag);
@@ -9918,11 +10042,11 @@ function executeMerge(proposal, d) {
9918
10042
  const mergedValue = proposal.proposed_changes["merged_value"] ?? [target.value, ...sourceValues].join(`
9919
10043
  ---
9920
10044
  `);
9921
- d.run("UPDATE memories SET value = ?, version = version + 1, updated_at = ? WHERE id = ?", [mergedValue, now2(), targetId]);
10045
+ d.run("UPDATE memories SET value = ?, version = version + 1, updated_at = ? WHERE id = ?", [mergedValue, now(), targetId]);
9922
10046
  for (const memId of proposal.memory_ids) {
9923
10047
  if (memId === targetId)
9924
10048
  continue;
9925
- d.run("UPDATE memories SET status = 'archived', updated_at = ? WHERE id = ?", [now2(), memId]);
10049
+ d.run("UPDATE memories SET status = 'archived', updated_at = ? WHERE id = ?", [now(), memId]);
9926
10050
  }
9927
10051
  return rollback;
9928
10052
  }
@@ -9938,7 +10062,7 @@ function executeRemoveDuplicate(proposal, d) {
9938
10062
  if (mem.id === keepId)
9939
10063
  continue;
9940
10064
  rollback[mem.id] = mem.status;
9941
- d.run("UPDATE memories SET status = 'archived', updated_at = ? WHERE id = ?", [now2(), mem.id]);
10065
+ d.run("UPDATE memories SET status = 'archived', updated_at = ? WHERE id = ?", [now(), mem.id]);
9942
10066
  }
9943
10067
  return { old_status: rollback, kept_id: keepId };
9944
10068
  }
@@ -9970,7 +10094,7 @@ function rollbackProposal(proposal, d) {
9970
10094
  if (!oldStatus)
9971
10095
  break;
9972
10096
  for (const [memId, status] of Object.entries(oldStatus)) {
9973
- d.run("UPDATE memories SET status = ?, updated_at = ? WHERE id = ?", [status, now2(), memId]);
10097
+ d.run("UPDATE memories SET status = ?, updated_at = ? WHERE id = ?", [status, now(), memId]);
9974
10098
  }
9975
10099
  break;
9976
10100
  }
@@ -9979,7 +10103,7 @@ function rollbackProposal(proposal, d) {
9979
10103
  if (!oldImportance)
9980
10104
  break;
9981
10105
  for (const [memId, importance] of Object.entries(oldImportance)) {
9982
- d.run("UPDATE memories SET importance = ?, updated_at = ? WHERE id = ?", [importance, now2(), memId]);
10106
+ d.run("UPDATE memories SET importance = ?, updated_at = ? WHERE id = ?", [importance, now(), memId]);
9983
10107
  }
9984
10108
  break;
9985
10109
  }
@@ -9988,7 +10112,7 @@ function rollbackProposal(proposal, d) {
9988
10112
  if (!oldState)
9989
10113
  break;
9990
10114
  for (const [memId, state] of Object.entries(oldState)) {
9991
- d.run("UPDATE memories SET value = ?, version = ?, updated_at = ? WHERE id = ?", [state.value, state.version, now2(), memId]);
10115
+ d.run("UPDATE memories SET value = ?, version = ?, updated_at = ? WHERE id = ?", [state.value, state.version, now(), memId]);
9992
10116
  }
9993
10117
  break;
9994
10118
  }
@@ -9997,7 +10121,7 @@ function rollbackProposal(proposal, d) {
9997
10121
  if (!oldTags)
9998
10122
  break;
9999
10123
  for (const [memId, tags] of Object.entries(oldTags)) {
10000
- d.run("UPDATE memories SET tags = ?, updated_at = ? WHERE id = ?", [JSON.stringify(tags), now2(), memId]);
10124
+ d.run("UPDATE memories SET tags = ?, updated_at = ? WHERE id = ?", [JSON.stringify(tags), now(), memId]);
10001
10125
  d.run("DELETE FROM memory_tags WHERE memory_id = ?", [memId]);
10002
10126
  const insertTag = d.prepare("INSERT OR IGNORE INTO memory_tags (memory_id, tag) VALUES (?, ?)");
10003
10127
  for (const tag of tags) {
@@ -10012,11 +10136,11 @@ function rollbackProposal(proposal, d) {
10012
10136
  const archivedMemories = rb["archived_memories"];
10013
10137
  const targetId = proposal.target_memory_id;
10014
10138
  if (targetId && targetOldValue !== undefined && targetOldVersion !== undefined) {
10015
- d.run("UPDATE memories SET value = ?, version = ?, updated_at = ? WHERE id = ?", [targetOldValue, targetOldVersion, now2(), targetId]);
10139
+ d.run("UPDATE memories SET value = ?, version = ?, updated_at = ? WHERE id = ?", [targetOldValue, targetOldVersion, now(), targetId]);
10016
10140
  }
10017
10141
  if (archivedMemories) {
10018
10142
  for (const [memId, status] of Object.entries(archivedMemories)) {
10019
- d.run("UPDATE memories SET status = ?, updated_at = ? WHERE id = ?", [status, now2(), memId]);
10143
+ d.run("UPDATE memories SET status = ?, updated_at = ? WHERE id = ?", [status, now(), memId]);
10020
10144
  }
10021
10145
  }
10022
10146
  break;
@@ -10150,12 +10274,12 @@ async function runSynthesis(options = {}) {
10150
10274
  proposals_accepted: execResult.executed,
10151
10275
  proposals_rejected: validation.rejectedProposals.length + execResult.failed,
10152
10276
  status: "completed",
10153
- completed_at: now2()
10277
+ completed_at: now()
10154
10278
  }, d);
10155
10279
  } else {
10156
10280
  updateSynthesisRun(run.id, {
10157
10281
  status: "completed",
10158
- completed_at: now2()
10282
+ completed_at: now()
10159
10283
  }, d);
10160
10284
  }
10161
10285
  let effectivenessReport = null;
@@ -10177,7 +10301,7 @@ async function runSynthesis(options = {}) {
10177
10301
  updateSynthesisRun(run.id, {
10178
10302
  status: "failed",
10179
10303
  error: err instanceof Error ? err.message : String(err),
10180
- completed_at: now2()
10304
+ completed_at: now()
10181
10305
  }, d);
10182
10306
  const failedRun = listSynthesisRuns({ project_id: projectId, limit: 1 }, d)[0] ?? run;
10183
10307
  return {
@@ -10193,7 +10317,7 @@ async function rollbackSynthesis(runId, db) {
10193
10317
  const d = db || getDatabase();
10194
10318
  const result = await rollbackRun(runId, d);
10195
10319
  if (result.errors.length === 0) {
10196
- updateSynthesisRun(runId, { status: "rolled_back", completed_at: now2() }, d);
10320
+ updateSynthesisRun(runId, { status: "rolled_back", completed_at: now() }, d);
10197
10321
  }
10198
10322
  return result;
10199
10323
  }
@@ -10259,7 +10383,7 @@ function parseJobRow(row) {
10259
10383
  function createSessionJob(input, db) {
10260
10384
  const d = db || getDatabase();
10261
10385
  const id = uuid();
10262
- const timestamp = now2();
10386
+ const timestamp = now();
10263
10387
  const source = input.source ?? "manual";
10264
10388
  const metadata = JSON.stringify(input.metadata ?? {});
10265
10389
  d.run(`INSERT INTO session_memory_jobs
@@ -10278,7 +10402,7 @@ function createSessionJob(input, db) {
10278
10402
  }
10279
10403
  function getSessionJob(id, db) {
10280
10404
  if (!db && isApiMode()) {
10281
- const { status, data } = apiJson("GET", `/sessions/jobs/${encodeURIComponent(id)}`);
10405
+ const { status, data } = apiJson("GET", `/sessions/jobs/${encodeURIComponent(id)}`, undefined, { allow404: true });
10282
10406
  if (status === 404 || !data)
10283
10407
  return null;
10284
10408
  return data;
@@ -10408,7 +10532,7 @@ function saveToolEvent(input, db) {
10408
10532
  }
10409
10533
  const d = db || getDatabase();
10410
10534
  const id = uuid();
10411
- const timestamp = now2();
10535
+ const timestamp = now();
10412
10536
  const metadataJson = JSON.stringify(input.metadata || {});
10413
10537
  d.run(`INSERT INTO tool_events (id, tool_name, action, success, error_type, error_message, tokens_used, latency_ms, context, lesson, when_to_use, agent_id, project_id, session_id, metadata, created_at)
10414
10538
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
@@ -58701,15 +58825,108 @@ var {
58701
58825
  Help
58702
58826
  } = import__.default;
58703
58827
 
58828
+ // src/cli/index.tsx
58829
+ init_database();
58830
+ import { readFileSync as readFileSync8 } from "fs";
58831
+ import { dirname as dirname7, join as join12 } from "path";
58832
+ import { fileURLToPath as fileURLToPath4 } from "url";
58833
+
58834
+ // src/db/machines.ts
58835
+ init_database();
58836
+ import { hostname, platform } from "os";
58837
+ function parseMachine(row) {
58838
+ if (!row)
58839
+ return null;
58840
+ return {
58841
+ ...row,
58842
+ is_primary: Boolean(row.is_primary)
58843
+ };
58844
+ }
58845
+ function normalizeHostname(host) {
58846
+ return host.replace(/\.(local|lan|home|internal)$/i, "");
58847
+ }
58848
+ function registerMachine(name, db = getDatabase()) {
58849
+ const rawHost = hostname();
58850
+ const host = normalizeHostname(rawHost);
58851
+ const plat = platform();
58852
+ const machineName = name?.trim() || host;
58853
+ const existing = parseMachine(db.query("SELECT * FROM machines WHERE hostname = ?").get(host));
58854
+ if (existing) {
58855
+ db.run("UPDATE machines SET last_seen_at = ? WHERE id = ?", [now(), existing.id]);
58856
+ return parseMachine(db.query("SELECT * FROM machines WHERE id = ?").get(existing.id));
58857
+ }
58858
+ let finalName = machineName;
58859
+ let suffix = 2;
58860
+ while (db.query("SELECT id FROM machines WHERE name = ?").get(finalName)) {
58861
+ finalName = `${machineName}-${suffix++}`;
58862
+ }
58863
+ const id = uuid();
58864
+ db.run("INSERT INTO machines (id, name, hostname, platform) VALUES (?, ?, ?, ?)", [id, finalName, host, plat]);
58865
+ return parseMachine(db.query("SELECT * FROM machines WHERE id = ?").get(id));
58866
+ }
58867
+ function getPrimaryMachine(db = getDatabase()) {
58868
+ return parseMachine(db.query("SELECT * FROM machines WHERE is_primary = 1 LIMIT 1").get());
58869
+ }
58870
+ function getPrimaryMachineCandidate(db = getDatabase()) {
58871
+ if (getPrimaryMachine(db))
58872
+ return null;
58873
+ return parseMachine(db.query("SELECT * FROM machines ORDER BY created_at ASC, id ASC LIMIT 1").get());
58874
+ }
58875
+ function getPrimaryMachineStartupWarning(db = getDatabase()) {
58876
+ if (getPrimaryMachine(db))
58877
+ return null;
58878
+ const candidate = getPrimaryMachineCandidate(db);
58879
+ if (!candidate) {
58880
+ return "No primary machine configured. Fallback sync target is unset because no machines are registered yet.";
58881
+ }
58882
+ return `No primary machine configured. Fallback sync target is unset. Candidate: ${candidate.name} (${candidate.id.slice(0, 8)} / ${candidate.hostname}). Confirm it with set_primary_machine.`;
58883
+ }
58884
+ function touchMachine(id, db = getDatabase()) {
58885
+ db.run("UPDATE machines SET last_seen_at = ? WHERE id = ?", [now(), id]);
58886
+ }
58887
+ function getCurrentMachineId(db = getDatabase()) {
58888
+ const host = normalizeHostname(hostname());
58889
+ const m = db.query("SELECT id FROM machines WHERE hostname = ?").get(host);
58890
+ if (m) {
58891
+ touchMachine(m.id, db);
58892
+ return m.id;
58893
+ }
58894
+ return registerMachine(undefined, db).id;
58895
+ }
58896
+
58897
+ // src/cli/startup-side-effects.ts
58898
+ var NO_STARTUP_DB_ACCESS = new WeakSet;
58899
+ function withoutStartupDbAccess(command) {
58900
+ NO_STARTUP_DB_ACCESS.add(command);
58901
+ return command;
58902
+ }
58903
+ function skipsStartupDbAccess(command) {
58904
+ return command !== undefined && NO_STARTUP_DB_ACCESS.has(command);
58905
+ }
58906
+
58907
+ // src/cli/global-options.ts
58908
+ var GLOBAL_OPTIONS = [
58909
+ ["-p, --project <path>", "Project path for scoping"],
58910
+ ["-j, --json", "Output as JSON"],
58911
+ ["-f, --format <fmt>", "Output format: compact, json, csv, yaml"],
58912
+ ["-a, --agent <name>", "Agent name or ID"],
58913
+ ["-s, --session <id>", "Session ID"]
58914
+ ];
58915
+ function applyGlobalOptions(program2) {
58916
+ for (const [flags, description] of GLOBAL_OPTIONS)
58917
+ program2.option(flags, description);
58918
+ return program2;
58919
+ }
58920
+
58704
58921
  // node_modules/@hasna/events/dist/commander.js
58705
58922
  import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
58706
- import { existsSync } from "fs";
58707
- import { homedir } from "os";
58708
- import { join } from "path";
58923
+ import { existsSync as existsSync3 } from "fs";
58924
+ import { homedir as homedir2 } from "os";
58925
+ import { join as join4 } from "path";
58709
58926
  import { createHmac, timingSafeEqual } from "crypto";
58710
- import { randomUUID } from "crypto";
58711
- import { spawn } from "child_process";
58712
58927
  import { randomUUID as randomUUID2 } from "crypto";
58928
+ import { spawn } from "child_process";
58929
+ import { randomUUID as randomUUID22 } from "crypto";
58713
58930
  function getPathValue(input, path) {
58714
58931
  return path.split(".").reduce((value, part) => {
58715
58932
  if (value && typeof value === "object" && part in value) {
@@ -58754,7 +58971,7 @@ function channelMatchesEvent(channel, event) {
58754
58971
  var HASNA_EVENTS_DIR_ENV = "HASNA_EVENTS_DIR";
58755
58972
  var HASNA_EVENTS_HOME_ENV = "HASNA_EVENTS_HOME";
58756
58973
  function getEventsDataDir(override) {
58757
- return override || process.env[HASNA_EVENTS_DIR_ENV] || process.env[HASNA_EVENTS_HOME_ENV] || join(homedir(), ".hasna", "events");
58974
+ return override || process.env[HASNA_EVENTS_DIR_ENV] || process.env[HASNA_EVENTS_HOME_ENV] || join4(homedir2(), ".hasna", "events");
58758
58975
  }
58759
58976
 
58760
58977
  class JsonEventsStore {
@@ -58764,9 +58981,9 @@ class JsonEventsStore {
58764
58981
  deliveriesPath;
58765
58982
  constructor(dataDir = getEventsDataDir()) {
58766
58983
  this.dataDir = dataDir;
58767
- this.channelsPath = join(dataDir, "channels.json");
58768
- this.eventsPath = join(dataDir, "events.json");
58769
- this.deliveriesPath = join(dataDir, "deliveries.json");
58984
+ this.channelsPath = join4(dataDir, "channels.json");
58985
+ this.eventsPath = join4(dataDir, "events.json");
58986
+ this.deliveriesPath = join4(dataDir, "deliveries.json");
58770
58987
  }
58771
58988
  async init() {
58772
58989
  await mkdir(this.dataDir, { recursive: true, mode: 448 });
@@ -58838,7 +59055,7 @@ class JsonEventsStore {
58838
59055
  };
58839
59056
  }
58840
59057
  async ensureArrayFile(path) {
58841
- if (!existsSync(path)) {
59058
+ if (!existsSync3(path)) {
58842
59059
  await writeFile(path, `[]
58843
59060
  `, { encoding: "utf-8", mode: 384 });
58844
59061
  }
@@ -58876,7 +59093,7 @@ function signPayload(secret, timestamp, body) {
58876
59093
  const digest = createHmac("sha256", secret).update(buildSignatureBase(timestamp, body)).digest("hex");
58877
59094
  return `sha256=${digest}`;
58878
59095
  }
58879
- function now() {
59096
+ function now2() {
58880
59097
  return new Date().toISOString();
58881
59098
  }
58882
59099
  function truncate(value, max = 4096) {
@@ -58903,7 +59120,7 @@ function buildWebhookRequest(event, channel) {
58903
59120
  async function dispatchWebhook(event, channel, options = {}) {
58904
59121
  if (!channel.webhook)
58905
59122
  throw new Error(`Channel ${channel.id} has no webhook config`);
58906
- const startedAt = now();
59123
+ const startedAt = now2();
58907
59124
  const { body, headers } = buildWebhookRequest(event, channel);
58908
59125
  const controller = new AbortController;
58909
59126
  const timeout = setTimeout(() => controller.abort(), channel.webhook.timeoutMs ?? 15000);
@@ -58919,7 +59136,7 @@ async function dispatchWebhook(event, channel, options = {}) {
58919
59136
  attempt: 1,
58920
59137
  status: response.ok ? "success" : "failed",
58921
59138
  startedAt,
58922
- completedAt: now(),
59139
+ completedAt: now2(),
58923
59140
  responseStatus: response.status,
58924
59141
  responseBody,
58925
59142
  error: response.ok ? undefined : `Webhook returned HTTP ${response.status}`
@@ -58929,7 +59146,7 @@ async function dispatchWebhook(event, channel, options = {}) {
58929
59146
  attempt: 1,
58930
59147
  status: "failed",
58931
59148
  startedAt,
58932
- completedAt: now(),
59149
+ completedAt: now2(),
58933
59150
  error: error instanceof Error ? error.message : String(error)
58934
59151
  };
58935
59152
  } finally {
@@ -58939,7 +59156,7 @@ async function dispatchWebhook(event, channel, options = {}) {
58939
59156
  async function dispatchCommand(event, channel) {
58940
59157
  if (!channel.command)
58941
59158
  throw new Error(`Channel ${channel.id} has no command config`);
58942
- const startedAt = now();
59159
+ const startedAt = now2();
58943
59160
  const eventJson = JSON.stringify(event);
58944
59161
  const env = {
58945
59162
  ...process.env,
@@ -58955,7 +59172,7 @@ async function dispatchCommand(event, channel) {
58955
59172
  HASNA_EVENT_SCHEMA_VERSION: event.schemaVersion,
58956
59173
  HASNA_EVENT_JSON: eventJson
58957
59174
  };
58958
- return new Promise((resolve) => {
59175
+ return new Promise((resolve2) => {
58959
59176
  const child = spawn(channel.command.command, channel.command.args ?? [], {
58960
59177
  cwd: channel.command.cwd,
58961
59178
  env,
@@ -58973,11 +59190,11 @@ async function dispatchCommand(event, channel) {
58973
59190
  });
58974
59191
  child.on("error", (error) => {
58975
59192
  clearTimeout(timeout);
58976
- resolve({
59193
+ resolve2({
58977
59194
  attempt: 1,
58978
59195
  status: "failed",
58979
59196
  startedAt,
58980
- completedAt: now(),
59197
+ completedAt: now2(),
58981
59198
  stdout: truncate(stdout),
58982
59199
  stderr: truncate(stderr),
58983
59200
  error: error.message
@@ -58986,11 +59203,11 @@ async function dispatchCommand(event, channel) {
58986
59203
  child.on("close", (code, signal) => {
58987
59204
  clearTimeout(timeout);
58988
59205
  const success = code === 0;
58989
- resolve({
59206
+ resolve2({
58990
59207
  attempt: 1,
58991
59208
  status: success ? "success" : "failed",
58992
59209
  startedAt,
58993
- completedAt: now(),
59210
+ completedAt: now2(),
58994
59211
  stdout: truncate(stdout),
58995
59212
  stderr: truncate(stderr),
58996
59213
  error: success ? undefined : `Command exited with ${signal ? `signal ${signal}` : `code ${code}`}`
@@ -59006,27 +59223,27 @@ async function dispatchChannel(event, channel, options = {}) {
59006
59223
  return {
59007
59224
  attempt: 1,
59008
59225
  status: "skipped",
59009
- startedAt: now(),
59010
- completedAt: now(),
59226
+ startedAt: now2(),
59227
+ completedAt: now2(),
59011
59228
  error: `Unsupported transport: ${channel.transport}`
59012
59229
  };
59013
59230
  }
59014
59231
  function createDeliveryResult(event, channel, attempts) {
59015
59232
  const status = attempts.some((attempt) => attempt.status === "success") ? "success" : attempts.every((attempt) => attempt.status === "skipped") ? "skipped" : "failed";
59016
59233
  return {
59017
- id: randomUUID(),
59234
+ id: randomUUID2(),
59018
59235
  eventId: event.id,
59019
59236
  channelId: channel.id,
59020
59237
  transport: channel.transport,
59021
59238
  status,
59022
59239
  attempts,
59023
- createdAt: attempts[0]?.startedAt ?? now(),
59024
- completedAt: attempts.at(-1)?.completedAt ?? now()
59240
+ createdAt: attempts[0]?.startedAt ?? now2(),
59241
+ completedAt: attempts.at(-1)?.completedAt ?? now2()
59025
59242
  };
59026
59243
  }
59027
59244
  function createEvent(input) {
59028
59245
  return {
59029
- id: input.id ?? randomUUID2(),
59246
+ id: input.id ?? randomUUID22(),
59030
59247
  source: input.source,
59031
59248
  type: input.type,
59032
59249
  time: normalizeTime(input.time),
@@ -59385,75 +59602,6 @@ function collectValues(value, previous) {
59385
59602
  return previous;
59386
59603
  }
59387
59604
 
59388
- // src/cli/index.tsx
59389
- init_database();
59390
- import { readFileSync as readFileSync8 } from "fs";
59391
- import { dirname as dirname7, join as join12 } from "path";
59392
- import { fileURLToPath as fileURLToPath4 } from "url";
59393
-
59394
- // src/db/machines.ts
59395
- init_database();
59396
- import { hostname, platform } from "os";
59397
- function parseMachine(row) {
59398
- if (!row)
59399
- return null;
59400
- return {
59401
- ...row,
59402
- is_primary: Boolean(row.is_primary)
59403
- };
59404
- }
59405
- function normalizeHostname(host) {
59406
- return host.replace(/\.(local|lan|home|internal)$/i, "");
59407
- }
59408
- function registerMachine(name, db = getDatabase()) {
59409
- const rawHost = hostname();
59410
- const host = normalizeHostname(rawHost);
59411
- const plat = platform();
59412
- const machineName = name?.trim() || host;
59413
- const existing = parseMachine(db.query("SELECT * FROM machines WHERE hostname = ?").get(host));
59414
- if (existing) {
59415
- db.run("UPDATE machines SET last_seen_at = ? WHERE id = ?", [now2(), existing.id]);
59416
- return parseMachine(db.query("SELECT * FROM machines WHERE id = ?").get(existing.id));
59417
- }
59418
- let finalName = machineName;
59419
- let suffix = 2;
59420
- while (db.query("SELECT id FROM machines WHERE name = ?").get(finalName)) {
59421
- finalName = `${machineName}-${suffix++}`;
59422
- }
59423
- const id = uuid();
59424
- db.run("INSERT INTO machines (id, name, hostname, platform) VALUES (?, ?, ?, ?)", [id, finalName, host, plat]);
59425
- return parseMachine(db.query("SELECT * FROM machines WHERE id = ?").get(id));
59426
- }
59427
- function getPrimaryMachine(db = getDatabase()) {
59428
- return parseMachine(db.query("SELECT * FROM machines WHERE is_primary = 1 LIMIT 1").get());
59429
- }
59430
- function getPrimaryMachineCandidate(db = getDatabase()) {
59431
- if (getPrimaryMachine(db))
59432
- return null;
59433
- return parseMachine(db.query("SELECT * FROM machines ORDER BY created_at ASC, id ASC LIMIT 1").get());
59434
- }
59435
- function getPrimaryMachineStartupWarning(db = getDatabase()) {
59436
- if (getPrimaryMachine(db))
59437
- return null;
59438
- const candidate = getPrimaryMachineCandidate(db);
59439
- if (!candidate) {
59440
- return "No primary machine configured. Fallback sync target is unset because no machines are registered yet.";
59441
- }
59442
- return `No primary machine configured. Fallback sync target is unset. Candidate: ${candidate.name} (${candidate.id.slice(0, 8)} / ${candidate.hostname}). Confirm it with set_primary_machine.`;
59443
- }
59444
- function touchMachine(id, db = getDatabase()) {
59445
- db.run("UPDATE machines SET last_seen_at = ? WHERE id = ?", [now2(), id]);
59446
- }
59447
- function getCurrentMachineId(db = getDatabase()) {
59448
- const host = normalizeHostname(hostname());
59449
- const m = db.query("SELECT id FROM machines WHERE hostname = ?").get(host);
59450
- if (m) {
59451
- touchMachine(m.id, db);
59452
- return m.id;
59453
- }
59454
- return registerMachine(undefined, db).id;
59455
- }
59456
-
59457
59605
  // src/cli/commands/memory.ts
59458
59606
  init_helpers();
59459
59607
 
@@ -59510,10 +59658,12 @@ var FORMAT_UNITS = [
59510
59658
  ];
59511
59659
 
59512
59660
  // src/cli/commands/memory-cmd-crud.ts
59661
+ init_enum_validation();
59662
+ init_types();
59513
59663
  init_helpers();
59514
59664
  function registerCrudCommands(program2) {
59515
59665
  const handleError = makeHandleError(program2);
59516
- program2.command("save <key> <value>").description("Save a memory (create or upsert)").option("-c, --category <cat>", "Category: preference, fact, knowledge, history").option("-s, --scope <scope>", "Scope: global, shared, private").option("--importance <n>", "Importance 1-10", parseInt).option("--tags <tags>", "Comma-separated tags").option("--summary <text>", "Brief summary").option("--ttl <duration>", "Time-to-live: 30s, 5m, 2h, 1d, 1w, or milliseconds").option("--source <src>", "Source: user, agent, system, auto, imported").option("--template <name>", "Apply a template: correction, preference, decision, learning").action((key, value, opts) => {
59666
+ program2.command("save <key> <value>").description("Save a memory (create or upsert)").option("-c, --category <cat>", `Category: ${MEMORY_CATEGORIES.join(", ")}`).option("--scope <scope>", `Scope: ${MEMORY_SCOPES.join(", ")}`).option("--importance <n>", "Importance 1-10", parseInt).option("--tags <tags>", "Comma-separated tags").option("--summary <text>", "Brief summary").option("--ttl <duration>", "Time-to-live: 30s, 5m, 2h, 1d, 1w, or milliseconds").option("--source <src>", "Source: user, agent, system, auto, imported").option("--template <name>", "Apply a template: correction, preference, decision, learning").option("--dedupe <mode>", "Conflict handling: merge (default, upsert the matching row), create (fork a new row under the same key), error").action((key, value, opts) => {
59517
59667
  try {
59518
59668
  const globalOpts = program2.opts();
59519
59669
  const templates = {
@@ -59551,6 +59701,20 @@ function registerCrudCommands(program2) {
59551
59701
  }
59552
59702
  templateDefaults = tpl;
59553
59703
  }
59704
+ for (const [flag, value2] of [
59705
+ ["category", opts.category],
59706
+ ["scope", opts.scope],
59707
+ ["source", opts.source]
59708
+ ]) {
59709
+ const violation = validateEnumField(flag, value2);
59710
+ if (!violation)
59711
+ continue;
59712
+ let msg = formatEnumViolation(violation);
59713
+ if (flag === "category" && templates[violation.value]) {
59714
+ msg += ` Did you mean --template ${violation.value}?`;
59715
+ }
59716
+ throw new Error(msg);
59717
+ }
59554
59718
  const explicitTags = opts.tags ? opts.tags.split(",").map((t) => t.trim()) : undefined;
59555
59719
  const mergedTags = explicitTags ? explicitTags : templateDefaults?.tags && templateDefaults.tags.length > 0 ? templateDefaults.tags : undefined;
59556
59720
  let resolvedAgentId;
@@ -59576,17 +59740,40 @@ function registerCrudCommands(program2) {
59576
59740
  if (project)
59577
59741
  input.project_id = project.id;
59578
59742
  }
59579
- const memory = createMemory(input);
59743
+ const bucket = (m) => [m.scope ?? "private", m.agent_id ?? "", m.project_id ?? "", m.session_id ?? ""].join("\x1F");
59744
+ const targetBucket = bucket({
59745
+ scope: input.scope ?? "private",
59746
+ agent_id: input.agent_id,
59747
+ project_id: input.project_id,
59748
+ session_id: input.session_id
59749
+ });
59750
+ const dedupe = opts.dedupe;
59751
+ const forkRequested = dedupe === "create" || dedupe === "version-fork";
59752
+ let willUpdateExisting = false;
59753
+ if (!forkRequested) {
59754
+ const sameKey = getMemoriesByKey(key);
59755
+ const match = sameKey.find((m) => bucket(m) === targetBucket);
59756
+ willUpdateExisting = Boolean(match);
59757
+ if (!match && sameKey.length > 0) {
59758
+ const rows = sameKey.map((m) => ` ${m.id.slice(0, 8)} scope=${m.scope} project=${m.project_id ?? "none"} session=${m.session_id ?? "none"} agent=${m.agent_id ?? "none"}`).join(`
59759
+ `);
59760
+ throw new Error(`Refusing to fork key "${key}": ${sameKey.length} active memor${sameKey.length === 1 ? "y" : "ies"} ` + `already ${sameKey.length === 1 ? "uses" : "use"} it, ` + `but none matches the scope/project/session this save targets ` + `(scope=${input.scope ?? "private"}, project=${input.project_id ?? "none"}, session=${input.session_id ?? "none"}).
59761
+ ` + `${rows}
59762
+ ` + `Saving would create a second active row under the same key. Either target the existing row ` + `(match its scope/project/session flags, or use \`mementos update <id>\`), or pass \`--dedupe create\` ` + `to fork deliberately.`);
59763
+ }
59764
+ }
59765
+ const memory = forkRequested ? createMemory(input, dedupe) : createMemory(input);
59766
+ const outcome = willUpdateExisting ? "Updated" : "Created";
59580
59767
  if (globalOpts.json) {
59581
- outputJson(memory);
59768
+ outputJson({ ...memory, outcome: outcome.toLowerCase() });
59582
59769
  } else {
59583
- console.log(chalk2.green(`Saved: ${memory.key} (${memory.id.slice(0, 8)})`));
59770
+ console.log(chalk2.green(`${outcome}: ${memory.key} (${memory.id.slice(0, 8)})`));
59584
59771
  }
59585
59772
  } catch (e) {
59586
59773
  handleError(e);
59587
59774
  }
59588
59775
  });
59589
- program2.command("update <id>").description("Update a memory by ID").option("--value <text>", "New value").option("--importance <n>", "New importance 1-10", parseInt).option("--tags <tags>", "New comma-separated tags").option("--summary <text>", "New summary").option("--pin", "Pin the memory").option("--unpin", "Unpin the memory").option("-c, --category <cat>", "New category").option("-s, --scope <scope>", "New scope").option("--status <status>", "New status: active, archived, expired").action((id, opts) => {
59776
+ program2.command("update <id>").description("Update a memory by ID").option("--value <text>", "New value").option("--importance <n>", "New importance 1-10", parseInt).option("--tags <tags>", "New comma-separated tags").option("--summary <text>", "New summary").option("--pin", "Pin the memory").option("--unpin", "Unpin the memory").option("-c, --category <cat>", "New category").option("--scope <scope>", "New scope").option("--status <status>", "New status: active, archived, expired").action((id, opts) => {
59590
59777
  try {
59591
59778
  const globalOpts = program2.opts();
59592
59779
  const resolvedId = resolveMemoryId(id);
@@ -59620,17 +59807,31 @@ function registerCrudCommands(program2) {
59620
59807
  updateInput.scope = opts.scope;
59621
59808
  if (opts.status !== undefined)
59622
59809
  updateInput.status = opts.status;
59810
+ for (const [flag, value] of [
59811
+ ["category", opts.category],
59812
+ ["scope", opts.scope],
59813
+ ["status", opts.status]
59814
+ ]) {
59815
+ const violation = validateEnumField(flag, value);
59816
+ if (violation)
59817
+ throw new Error(formatEnumViolation(violation));
59818
+ }
59819
+ const changedFields = Object.keys(updateInput).filter((k) => k !== "version");
59820
+ if (changedFields.length === 0) {
59821
+ throw new Error(`Nothing to update: no fields were given for ${existing.key} (${existing.id.slice(0, 8)}). ` + `Pass at least one of --value, --scope, --category, --status, --importance, --tags, --summary, --pin/--unpin. ` + `Note that -s is the global --session, not --scope.`);
59822
+ }
59623
59823
  const updated = updateMemory(resolvedId, updateInput);
59624
59824
  if (globalOpts.json) {
59625
- outputJson(updated);
59825
+ outputJson({ ...updated, updated_fields: changedFields });
59626
59826
  } else {
59627
- console.log(chalk2.green(`Updated: ${updated.key} (${updated.id.slice(0, 8)})`));
59827
+ const n = changedFields.length;
59828
+ console.log(chalk2.green(`Updated ${n} field${n === 1 ? "" : "s"}: ${updated.key} (${updated.id.slice(0, 8)})`) + chalk2.dim(` [${changedFields.join(", ")}]`));
59628
59829
  }
59629
59830
  } catch (e) {
59630
59831
  handleError(e);
59631
59832
  }
59632
59833
  });
59633
- program2.command("forget <keyOrId>").description("Delete a memory by key or ID").option("-s, --scope <scope>", "Filter by scope (global, shared, private)").option("-a, --agent <agent>", "Filter by agent ID").option("-p, --project <project>", "Filter by project ID").option("--all", "Delete ALL matching memories (no disambiguation needed)").action((keyOrId, opts) => {
59834
+ program2.command("forget <keyOrId>").description("Delete a memory by key or ID").option("--scope <scope>", "Filter by scope (global, shared, private)").option("--agent <agent>", "Filter by agent ID").option("--project <project>", "Filter by project ID").option("--all", "Delete ALL matching memories (no disambiguation needed)").action((keyOrId, opts) => {
59634
59835
  try {
59635
59836
  const globalOpts = program2.opts();
59636
59837
  const idMatch = isApiMode() ? null : resolvePartialId(getDatabase(), "memories", keyOrId);
@@ -59730,7 +59931,7 @@ function registerViewCommands(program2) {
59730
59931
  handleError(e);
59731
59932
  }
59732
59933
  });
59733
- program2.command("pin <keyOrId>").description("Pin a memory by key or partial ID").option("-s, --scope <scope>", "Scope filter for key lookup").option("--agent <name>", "Agent filter for key lookup").option("--project <path>", "Project filter for key lookup").action((keyOrId, opts) => {
59934
+ program2.command("pin <keyOrId>").description("Pin a memory by key or partial ID").option("--scope <scope>", "Scope filter for key lookup").option("--agent <name>", "Agent filter for key lookup").option("--project <path>", "Project filter for key lookup").action((keyOrId, opts) => {
59734
59935
  try {
59735
59936
  const globalOpts = program2.opts();
59736
59937
  const memory = resolveKeyOrId(keyOrId, opts, globalOpts);
@@ -59755,7 +59956,7 @@ function registerViewCommands(program2) {
59755
59956
  handleError(e);
59756
59957
  }
59757
59958
  });
59758
- program2.command("unpin <keyOrId>").description("Unpin a memory by key or partial ID").option("-s, --scope <scope>", "Scope filter for key lookup").option("--agent <name>", "Agent filter for key lookup").option("--project <path>", "Project filter for key lookup").action((keyOrId, opts) => {
59959
+ program2.command("unpin <keyOrId>").description("Unpin a memory by key or partial ID").option("--scope <scope>", "Scope filter for key lookup").option("--agent <name>", "Agent filter for key lookup").option("--project <path>", "Project filter for key lookup").action((keyOrId, opts) => {
59759
59960
  try {
59760
59961
  const globalOpts = program2.opts();
59761
59962
  const memory = resolveKeyOrId(keyOrId, opts, globalOpts);
@@ -59780,7 +59981,7 @@ function registerViewCommands(program2) {
59780
59981
  handleError(e);
59781
59982
  }
59782
59983
  });
59783
- program2.command("archive <keyOrId>").description("Archive a memory by key or ID (hides from lists, keeps history)").option("-s, --scope <scope>", "Scope filter for key lookup").action((keyOrId, opts) => {
59984
+ program2.command("archive <keyOrId>").description("Archive a memory by key or ID (hides from lists, keeps history)").option("--scope <scope>", "Scope filter for key lookup").action((keyOrId, opts) => {
59784
59985
  try {
59785
59986
  const globalOpts = program2.opts();
59786
59987
  const memory = resolveKeyOrId(keyOrId, opts, globalOpts);
@@ -59799,7 +60000,7 @@ function registerViewCommands(program2) {
59799
60000
  process.exit(1);
59800
60001
  }
59801
60002
  });
59802
- program2.command("versions <keyOrId>").description("Show version history for a memory").option("-s, --scope <scope>", "Scope filter for key lookup").action((keyOrId, opts) => {
60003
+ program2.command("versions <keyOrId>").description("Show version history for a memory").option("--scope <scope>", "Scope filter for key lookup").action((keyOrId, opts) => {
59803
60004
  try {
59804
60005
  const globalOpts = program2.opts();
59805
60006
  const memory = resolveKeyOrId(keyOrId, opts, globalOpts);
@@ -59841,7 +60042,7 @@ import chalk4 from "chalk";
59841
60042
  import { resolve as resolve4 } from "path";
59842
60043
  function registerTailCommand(program2) {
59843
60044
  const handleError = makeHandleError(program2);
59844
- program2.command("tail").description("Watch for new/updated memories in real-time (like tail -f)").option("-s, --scope <scope>", "Scope filter: global, shared, private").option("-c, --category <cat>", "Category filter: preference, fact, knowledge, history").option("--agent <name>", "Agent filter").option("--project <path>", "Project filter").option("--interval <ms>", "Poll interval in milliseconds (default: 2000)", parseInt).option("--notify", "Send macOS notifications for each change").action((opts) => {
60045
+ program2.command("tail").description("Watch for new/updated memories in real-time (like tail -f)").option("--scope <scope>", "Scope filter: global, shared, private").option("-c, --category <cat>", "Category filter: preference, fact, knowledge, history").option("--agent <name>", "Agent filter").option("--project <path>", "Project filter").option("--interval <ms>", "Poll interval in milliseconds (default: 2000)", parseInt).option("--notify", "Send macOS notifications for each change").action((opts) => {
59845
60046
  try {
59846
60047
  const globalOpts = program2.opts();
59847
60048
  const jsonMode = !!globalOpts.json;
@@ -59955,7 +60156,7 @@ import chalk6 from "chalk";
59955
60156
  import { resolve as resolve5 } from "path";
59956
60157
  function registerSearchCommand(program2) {
59957
60158
  const handleError = makeHandleError(program2);
59958
- program2.command("search <query>").description("Full-text search across memories").option("-s, --scope <scope>", "Scope filter").option("-c, --category <cat>", "Category filter").option("--tags <tags>", "Comma-separated tags filter").option("--project <path>", "Project filter (path or name)").option("--agent <name>", "Agent filter").option("--session <id>", "Session ID filter").option("--limit <n>", "Max results", parseInt).option("--offset <n>", "Offset for pagination", parseInt).option("--cursor <n>", "Cursor offset for the next page", parseInt).option("--format <fmt>", "Output format: compact (default), json, csv, yaml").option("--verbose", "Show match highlights and wider snippets").option("--history", "Show recent search queries instead of searching").option("--popular", "Show most popular search queries").action((query, opts) => {
60159
+ program2.command("search <query>").description("Full-text search across memories").option("--scope <scope>", "Scope filter").option("-c, --category <cat>", "Category filter").option("--tags <tags>", "Comma-separated tags filter").option("--project <path>", "Project filter (path or name)").option("--agent <name>", "Agent filter").option("--session <id>", "Session ID filter").option("--limit <n>", "Max results", parseInt).option("--offset <n>", "Offset for pagination", parseInt).option("--cursor <n>", "Cursor offset for the next page", parseInt).option("--format <fmt>", "Output format: compact (default), json, csv, yaml").option("--verbose", "Show match highlights and wider snippets").option("--history", "Show recent search queries instead of searching").option("--popular", "Show most popular search queries").action((query, opts) => {
59959
60160
  try {
59960
60161
  const fmt = getOutputFormat(program2, opts.format);
59961
60162
  const isStructured = fmt === "json" || fmt === "csv" || fmt === "yaml";
@@ -60216,7 +60417,7 @@ init_memories();
60216
60417
  init_helpers();
60217
60418
  import chalk10 from "chalk";
60218
60419
  function registerRemoveCommand(program2) {
60219
- program2.command("remove <nameOrId>").description("Remove/delete a memory by name or ID (alias for memory forget)").option("--agent <id>", "Agent ID").option("-s, --scope <scope>", "Filter by scope (when looking up by key)").action((nameOrId, opts) => {
60420
+ program2.command("remove <nameOrId>").description("Remove/delete a memory by name or ID (alias for memory forget)").option("--agent <id>", "Agent ID").option("--scope <scope>", "Filter by scope (when looking up by key)").action((nameOrId, opts) => {
60220
60421
  const globalOpts = program2.opts();
60221
60422
  const agentId = opts.agent || globalOpts.agent;
60222
60423
  let id = isApiMode() ? null : resolvePartialId(getDatabase(), "memories", nameOrId);
@@ -60273,7 +60474,7 @@ import chalk11 from "chalk";
60273
60474
  import { resolve as resolve6 } from "path";
60274
60475
  function registerRecallCommand(program2) {
60275
60476
  const handleError = makeHandleError(program2);
60276
- program2.command("recall <key>").description("Recall a memory by key").option("-s, --scope <scope>", "Scope filter").option("--agent <name>", "Agent filter").option("--project <path>", "Project filter").action((key, opts) => {
60477
+ program2.command("recall <key>").description("Recall a memory by key").option("--scope <scope>", "Scope filter").option("--agent <name>", "Agent filter").option("--project <path>", "Project filter").action((key, opts) => {
60277
60478
  try {
60278
60479
  const globalOpts = program2.opts();
60279
60480
  const agentId = opts.agent || globalOpts.agent;
@@ -60331,7 +60532,7 @@ import chalk12 from "chalk";
60331
60532
  import { resolve as resolve7 } from "path";
60332
60533
  function registerListCommand(program2) {
60333
60534
  const handleError = makeHandleError(program2);
60334
- program2.command("list").description("List memories with optional filters").option("-s, --scope <scope>", "Scope filter").option("-c, --category <cat>", "Category filter").option("--tags <tags>", "Comma-separated tags filter").option("--importance-min <n>", "Minimum importance", parseInt).option("--pinned", "Show only pinned").option("--agent <name>", "Agent filter").option("--project <path>", "Project filter").option("--session <id>", "Session ID filter").option("--limit <n>", "Max results", parseInt).option("--offset <n>", "Offset for pagination", parseInt).option("--cursor <n>", "Cursor offset for the next page", parseInt).option("--status <status>", "Status filter: active, archived, expired").option("--format <fmt>", "Output format: compact (default), json, csv, yaml").option("--verbose", "Show wider memory snippets in human output").action((opts) => {
60535
+ program2.command("list").description("List memories with optional filters").option("--scope <scope>", "Scope filter").option("-c, --category <cat>", "Category filter").option("--tags <tags>", "Comma-separated tags filter").option("--importance-min <n>", "Minimum importance", parseInt).option("--pinned", "Show only pinned").option("--agent <name>", "Agent filter").option("--project <path>", "Project filter").option("--session <id>", "Session ID filter").option("--limit <n>", "Max results", parseInt).option("--offset <n>", "Offset for pagination", parseInt).option("--cursor <n>", "Cursor offset for the next page", parseInt).option("--status <status>", "Status filter: active, archived, expired").option("--format <fmt>", "Output format: compact (default), json, csv, yaml").option("--verbose", "Show wider memory snippets in human output").action((opts) => {
60335
60536
  try {
60336
60537
  const globalOpts = program2.opts();
60337
60538
  const fmt = getOutputFormat(program2, opts.format);
@@ -60980,7 +61181,7 @@ init_helpers();
60980
61181
  import { resolve as resolve11 } from "path";
60981
61182
  function registerExportCommand(program2) {
60982
61183
  const handleError = makeHandleError(program2);
60983
- 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) => {
61184
+ program2.command("export").description("Export memories as JSON").option("--scope <scope>", "Scope filter").option("-c, --category <cat>", "Category filter").option("--agent <name>", "Agent filter").option("--project <path>", "Project filter").action((opts) => {
60984
61185
  try {
60985
61186
  const globalOpts = program2.opts();
60986
61187
  const agentId = opts.agent || globalOpts.agent;
@@ -61296,7 +61497,7 @@ function enforceQuotas(config, db) {
61296
61497
  }
61297
61498
  function archiveStale(staleDays, db) {
61298
61499
  const d = db || getDatabase();
61299
- const timestamp = now2();
61500
+ const timestamp = now();
61300
61501
  const cutoff = new Date(Date.now() - staleDays * 24 * 60 * 60 * 1000).toISOString();
61301
61502
  const archiveWhere = `status = 'active' AND pinned = 0 AND COALESCE(accessed_at, created_at) < ?`;
61302
61503
  const count = d.query(`SELECT COUNT(*) as c FROM memories WHERE ${archiveWhere}`).get(cutoff).c;
@@ -61307,7 +61508,7 @@ function archiveStale(staleDays, db) {
61307
61508
  }
61308
61509
  function archiveUnused(days, db) {
61309
61510
  const d = db || getDatabase();
61310
- const timestamp = now2();
61511
+ const timestamp = now();
61311
61512
  const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString();
61312
61513
  const unusedWhere = `status = 'active' AND pinned = 0 AND access_count = 0 AND created_at < ?`;
61313
61514
  const count = d.query(`SELECT COUNT(*) as c FROM memories WHERE ${unusedWhere}`).get(cutoff).c;
@@ -61318,7 +61519,7 @@ function archiveUnused(days, db) {
61318
61519
  }
61319
61520
  function deprioritizeStale(days, db) {
61320
61521
  const d = db || getDatabase();
61321
- const timestamp = now2();
61522
+ const timestamp = now();
61322
61523
  const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString();
61323
61524
  const deprioWhere = `status = 'active' AND pinned = 0 AND importance > 1 AND COALESCE(accessed_at, updated_at) < ?`;
61324
61525
  const count = d.query(`SELECT COUNT(*) as c FROM memories WHERE ${deprioWhere}`).get(cutoff).c;
@@ -61346,7 +61547,9 @@ init_api_mode();
61346
61547
  init_helpers();
61347
61548
  function runCleanupViaApi() {
61348
61549
  const empty = { expired: 0, evicted: 0, archived: 0, unused_archived: 0, deprioritized: 0 };
61349
- const { status, data } = apiJson("POST", "/maintenance/cleanup");
61550
+ const { status, data } = apiJson("POST", "/maintenance/cleanup", undefined, {
61551
+ allow404: true
61552
+ });
61350
61553
  if (status !== 404 && data)
61351
61554
  return { ...empty, ...data };
61352
61555
  const legacy = apiJson("POST", "/memories/clean");
@@ -61637,7 +61840,7 @@ function getFocus(agentId) {
61637
61840
  init_helpers();
61638
61841
  function registerAgentCommands(program2) {
61639
61842
  const handleError = makeHandleError(program2);
61640
- program2.command("register-agent <name>").alias("init-agent").description("Register an agent (returns ID)").option("-d, --description <text>", "Agent description").option("-r, --role <role>", "Agent role").option("-p, --project <id>", "Lock agent to a project (sets active_project_id)").action((name, opts) => {
61843
+ program2.command("register-agent <name>").alias("init-agent").description("Register an agent (returns ID)").option("-d, --description <text>", "Agent description").option("-r, --role <role>", "Agent role").option("--project <id>", "Lock agent to a project (sets active_project_id)").action((name, opts) => {
61641
61844
  try {
61642
61845
  const globalOpts = program2.opts();
61643
61846
  const agent = registerAgent(name, undefined, opts.description, opts.role, opts.project);
@@ -64060,7 +64263,7 @@ import chalk39 from "chalk";
64060
64263
  import { resolve as resolve19 } from "path";
64061
64264
  function registerWatchCommand(program2) {
64062
64265
  const handleError = makeHandleError(program2);
64063
- program2.command("watch").description("Watch for new and changed memories in real-time").option("-s, --scope <scope>", "Scope filter: global, shared, private").option("-c, --category <cat>", "Category filter: preference, fact, knowledge, history").option("--agent <name>", "Agent filter").option("--project <path>", "Project filter").option("--interval <ms>", "Poll interval in milliseconds", parseInt).action((opts) => {
64266
+ program2.command("watch").description("Watch for new and changed memories in real-time").option("--scope <scope>", "Scope filter: global, shared, private").option("-c, --category <cat>", "Category filter: preference, fact, knowledge, history").option("--agent <name>", "Agent filter").option("--project <path>", "Project filter").option("--interval <ms>", "Poll interval in milliseconds", parseInt).action((opts) => {
64064
64267
  try {
64065
64268
  const globalOpts = program2.opts();
64066
64269
  const agentId = opts.agent || globalOpts.agent;
@@ -64656,6 +64859,35 @@ function getStorageSyncStatus(options = {}) {
64656
64859
  }
64657
64860
  }
64658
64861
 
64862
+ // src/db/store-backend.ts
64863
+ init_database();
64864
+ init_api_mode();
64865
+ init_storage();
64866
+ function resolveStoreBackend() {
64867
+ const apiMode = isApiMode();
64868
+ const apiConfig = getApiConfig();
64869
+ const storageMode = getStorageMode();
64870
+ const sources = getApiModeEnvSources();
64871
+ const backend = apiMode ? "cloud-api" : storageMode === "cloud" ? "cloud-postgres" : "local-sqlite";
64872
+ let selectedBy = "default";
64873
+ if (apiMode) {
64874
+ selectedBy = `${sources.urlKey} + ${sources.keyKey} (presence)`;
64875
+ } else if (backend === "cloud-postgres") {
64876
+ const modeKey = [MEMENTOS_STORAGE_ENV.mode, MEMENTOS_STORAGE_FALLBACK_ENV.mode].find((key) => process.env[key]?.trim());
64877
+ selectedBy = modeKey ?? sources.databaseUrlKey ?? "storage config file";
64878
+ }
64879
+ return {
64880
+ schema: "mementos.store_backend.v1",
64881
+ backend,
64882
+ api_mode: apiMode,
64883
+ storage_mode: storageMode,
64884
+ db_path: getDbPath(),
64885
+ api_endpoint: apiConfig?.baseUrl ?? null,
64886
+ api_key_present: Boolean(apiConfig?.apiKey),
64887
+ selected_by: selectedBy
64888
+ };
64889
+ }
64890
+
64659
64891
  // src/cli/commands/storage.ts
64660
64892
  function parseTables(raw) {
64661
64893
  if (!raw) {
@@ -64680,6 +64912,26 @@ function printSyncResult(result) {
64680
64912
  }
64681
64913
  }
64682
64914
  function installStorageSubcommands(storage, program2) {
64915
+ withoutStartupDbAccess(storage.command("mode").description("Show which store this process will actually read and write (no DB or network access)").option("--json", "Output JSON").action((opts) => {
64916
+ const useJson = Boolean(opts.json || program2.opts().json);
64917
+ const report = resolveStoreBackend();
64918
+ if (useJson) {
64919
+ outputJson2(true, report);
64920
+ return;
64921
+ }
64922
+ const label = report.backend === "local-sqlite" ? chalk40.green(report.backend) : chalk40.yellow(report.backend);
64923
+ console.log(`Backend: ${label}`);
64924
+ console.log(`Selected by: ${report.selected_by}`);
64925
+ console.log(`API mode: ${report.api_mode ? "yes" : "no"}`);
64926
+ console.log(`Storage mode: ${report.storage_mode}`);
64927
+ if (report.backend === "local-sqlite") {
64928
+ console.log(`Database: ${report.db_path}`);
64929
+ } else {
64930
+ console.log(`API endpoint: ${report.api_endpoint ?? "(none)"}`);
64931
+ console.log(`API key: ${report.api_key_present ? "configured" : "not configured"}`);
64932
+ console.log(`Local SQLite (not authoritative): ${report.db_path}`);
64933
+ }
64934
+ }));
64683
64935
  storage.command("status").description("Show local database and remote storage sync status").option("--json", "Output JSON").action((opts) => {
64684
64936
  const useJson = Boolean(opts.json || program2.opts().json);
64685
64937
  const status = getStorageSyncStatus();
@@ -65140,7 +65392,7 @@ function parseMemoryLink(row) {
65140
65392
  function createMemoryLink(input, db) {
65141
65393
  const d = db || getDatabase();
65142
65394
  const id = shortUuid();
65143
- const timestamp = now2();
65395
+ const timestamp = now();
65144
65396
  d.run(`INSERT OR IGNORE INTO memory_links (id, source_memory_id, target_memory_id, relation_type, run_id, metadata, created_at)
65145
65397
  VALUES (?, ?, ?, ?, ?, ?, ?)`, [
65146
65398
  id,
@@ -65226,7 +65478,7 @@ function createRun(options, db) {
65226
65478
  options.projectId ?? null,
65227
65479
  options.agentId ?? null,
65228
65480
  options.dryRun ?? true ? 1 : 0,
65229
- now2()
65481
+ now()
65230
65482
  ]);
65231
65483
  return getRun(id, db);
65232
65484
  }
@@ -65271,7 +65523,7 @@ function persistAction(action, db) {
65271
65523
  action.reason,
65272
65524
  JSON.stringify(action.plannedChanges),
65273
65525
  action.applied ? 1 : 0,
65274
- now2()
65526
+ now()
65275
65527
  ]);
65276
65528
  }
65277
65529
  function markActionApplied(action, db) {
@@ -65646,11 +65898,11 @@ async function runConsolidation(options = {}) {
65646
65898
  actions = applied;
65647
65899
  }
65648
65900
  const summary = buildSummary2(actions);
65649
- run = updateRun(run.id, { status: "completed", summary, completed_at: now2() }, db);
65901
+ run = updateRun(run.id, { status: "completed", summary, completed_at: now() }, db);
65650
65902
  return { run, actions, dryRun, summary };
65651
65903
  } catch (error) {
65652
65904
  const message = error instanceof Error ? error.message : String(error);
65653
- run = updateRun(run.id, { status: "failed", error: message, completed_at: now2() }, db);
65905
+ run = updateRun(run.id, { status: "failed", error: message, completed_at: now() }, db);
65654
65906
  return {
65655
65907
  run,
65656
65908
  actions: [],
@@ -65723,7 +65975,7 @@ function createRun2(options, memoryIds, db) {
65723
65975
  options.provider ?? null,
65724
65976
  options.model ?? null,
65725
65977
  JSON.stringify(memoryIds),
65726
- now2()
65978
+ now()
65727
65979
  ]);
65728
65980
  return getRun2(id, db);
65729
65981
  }
@@ -65766,7 +66018,7 @@ function insertLessonRow(runId, lesson, db) {
65766
66018
  lesson.lesson,
65767
66019
  JSON.stringify(lesson.evidence),
65768
66020
  lesson.importance,
65769
- now2()
66021
+ now()
65770
66022
  ]);
65771
66023
  }
65772
66024
  function listToolEventsForTrajectory(options, db) {
@@ -66059,7 +66311,7 @@ async function reflectOnTrajectory(options) {
66059
66311
  }
66060
66312
  });
66061
66313
  }
66062
- run = updateRun2(run.id, { status: "completed", summary: criticResult.summary, completed_at: now2() }, db);
66314
+ run = updateRun2(run.id, { status: "completed", summary: criticResult.summary, completed_at: now() }, db);
66063
66315
  return {
66064
66316
  run,
66065
66317
  dryRun,
@@ -66073,7 +66325,7 @@ async function reflectOnTrajectory(options) {
66073
66325
  };
66074
66326
  } catch (error40) {
66075
66327
  const message = error40 instanceof Error ? error40.message : String(error40);
66076
- run = updateRun2(run.id, { status: "failed", error: message, completed_at: now2() }, db);
66328
+ run = updateRun2(run.id, { status: "failed", error: message, completed_at: now() }, db);
66077
66329
  return {
66078
66330
  run,
66079
66331
  dryRun,
@@ -66489,6 +66741,26 @@ function makeBrainsCommand() {
66489
66741
  return brains;
66490
66742
  }
66491
66743
 
66744
+ // src/cli/register-all.ts
66745
+ function registerAllCommands(program2) {
66746
+ registerInitCommand(program2);
66747
+ registerMemoryCommands(program2);
66748
+ registerInfoCommands(program2);
66749
+ registerIoCommands(program2);
66750
+ registerAgentCommands(program2);
66751
+ registerProjectCommands(program2);
66752
+ registerProjectPanelCommand(program2);
66753
+ registerEntityCommands(program2);
66754
+ registerRelationCommands(program2);
66755
+ registerGraphCommands(program2);
66756
+ registerSystemCommands(program2);
66757
+ registerStorageCommands(program2);
66758
+ registerConsolidationCommands(program2);
66759
+ program2.addCommand(makeBrainsCommand());
66760
+ registerEventsCommands(program2, { source: "mementos" });
66761
+ return program2;
66762
+ }
66763
+
66492
66764
  // src/cli/index.tsx
66493
66765
  function getPackageVersion2() {
66494
66766
  try {
@@ -66500,9 +66772,12 @@ function getPackageVersion2() {
66500
66772
  }
66501
66773
  }
66502
66774
  var program2 = new Command;
66503
- program2.name("mementos").description("Universal memory system for AI agents").version(getPackageVersion2()).option("-p, --project <path>", "Project path for scoping").option("-j, --json", "Output as JSON").option("-f, --format <fmt>", "Output format: compact, json, csv, yaml").option("-a, --agent <name>", "Agent name or ID").option("-s, --session <id>", "Session ID");
66775
+ program2.name("mementos").description("Universal memory system for AI agents").version(getPackageVersion2());
66776
+ applyGlobalOptions(program2);
66504
66777
  var startupWarningShown = false;
66505
- program2.hook("preAction", () => {
66778
+ program2.hook("preAction", (_thisCommand, actionCommand) => {
66779
+ if (skipsStartupDbAccess(actionCommand))
66780
+ return;
66506
66781
  if (startupWarningShown)
66507
66782
  return;
66508
66783
  startupWarningShown = true;
@@ -66513,19 +66788,5 @@ program2.hook("preAction", () => {
66513
66788
  }
66514
66789
  } catch {}
66515
66790
  });
66516
- registerInitCommand(program2);
66517
- registerMemoryCommands(program2);
66518
- registerInfoCommands(program2);
66519
- registerIoCommands(program2);
66520
- registerAgentCommands(program2);
66521
- registerProjectCommands(program2);
66522
- registerProjectPanelCommand(program2);
66523
- registerEntityCommands(program2);
66524
- registerRelationCommands(program2);
66525
- registerGraphCommands(program2);
66526
- registerSystemCommands(program2);
66527
- registerStorageCommands(program2);
66528
- registerConsolidationCommands(program2);
66529
- program2.addCommand(makeBrainsCommand());
66530
- registerEventsCommands(program2, { source: "mementos" });
66791
+ registerAllCommands(program2);
66531
66792
  program2.parse(process.argv);