@hasna/mementos 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -4,6 +4,7 @@ var __create = Object.create;
4
4
  var __getProtoOf = Object.getPrototypeOf;
5
5
  var __defProp = Object.defineProperty;
6
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
7
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
9
  var __toESM = (mod, isNodeMode, target) => {
9
10
  target = mod != null ? __create(__getProtoOf(mod)) : {};
@@ -16,7 +17,31 @@ var __toESM = (mod, isNodeMode, target) => {
16
17
  });
17
18
  return to;
18
19
  };
20
+ var __moduleCache = /* @__PURE__ */ new WeakMap;
21
+ var __toCommonJS = (from) => {
22
+ var entry = __moduleCache.get(from), desc;
23
+ if (entry)
24
+ return entry;
25
+ entry = __defProp({}, "__esModule", { value: true });
26
+ if (from && typeof from === "object" || typeof from === "function")
27
+ __getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
28
+ get: () => from[key],
29
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
30
+ }));
31
+ __moduleCache.set(from, entry);
32
+ return entry;
33
+ };
19
34
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
35
+ var __export = (target, all) => {
36
+ for (var name in all)
37
+ __defProp(target, name, {
38
+ get: all[name],
39
+ enumerable: true,
40
+ configurable: true,
41
+ set: (newValue) => all[name] = () => newValue
42
+ });
43
+ };
44
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
20
45
  var __require = import.meta.require;
21
46
 
22
47
  // node_modules/commander/lib/error.js
@@ -2053,28 +2078,6 @@ var require_commander = __commonJS((exports) => {
2053
2078
  exports.InvalidOptionArgumentError = InvalidArgumentError;
2054
2079
  });
2055
2080
 
2056
- // node_modules/commander/esm.mjs
2057
- var import__ = __toESM(require_commander(), 1);
2058
- var {
2059
- program,
2060
- createCommand,
2061
- createArgument,
2062
- createOption,
2063
- CommanderError,
2064
- InvalidArgumentError,
2065
- InvalidOptionArgumentError,
2066
- Command,
2067
- Argument,
2068
- Option,
2069
- Help
2070
- } = import__.default;
2071
-
2072
- // src/cli/index.tsx
2073
- import chalk from "chalk";
2074
- import { readFileSync as readFileSync2 } from "fs";
2075
- import { dirname as dirname3, join as join3, resolve as resolve3 } from "path";
2076
- import { fileURLToPath } from "url";
2077
-
2078
2081
  // src/db/database.ts
2079
2082
  import { Database } from "bun:sqlite";
2080
2083
  import { existsSync, mkdirSync } from "fs";
@@ -2132,8 +2135,59 @@ function ensureDir(filePath) {
2132
2135
  mkdirSync(dir, { recursive: true });
2133
2136
  }
2134
2137
  }
2135
- var MIGRATIONS = [
2136
- `
2138
+ function getDatabase(dbPath) {
2139
+ if (_db)
2140
+ return _db;
2141
+ const path = dbPath || getDbPath();
2142
+ ensureDir(path);
2143
+ _db = new Database(path, { create: true });
2144
+ _db.run("PRAGMA journal_mode = WAL");
2145
+ _db.run("PRAGMA busy_timeout = 5000");
2146
+ _db.run("PRAGMA foreign_keys = ON");
2147
+ runMigrations(_db);
2148
+ return _db;
2149
+ }
2150
+ function runMigrations(db) {
2151
+ try {
2152
+ const result = db.query("SELECT MAX(id) as max_id FROM _migrations").get();
2153
+ const currentLevel = result?.max_id ?? 0;
2154
+ for (let i = currentLevel;i < MIGRATIONS.length; i++) {
2155
+ try {
2156
+ db.exec(MIGRATIONS[i]);
2157
+ } catch {}
2158
+ }
2159
+ } catch {
2160
+ for (const migration of MIGRATIONS) {
2161
+ try {
2162
+ db.exec(migration);
2163
+ } catch {}
2164
+ }
2165
+ }
2166
+ }
2167
+ function now() {
2168
+ return new Date().toISOString();
2169
+ }
2170
+ function uuid() {
2171
+ return crypto.randomUUID();
2172
+ }
2173
+ function shortUuid() {
2174
+ return crypto.randomUUID().slice(0, 8);
2175
+ }
2176
+ function resolvePartialId(db, table, partialId) {
2177
+ if (partialId.length >= 36) {
2178
+ const row = db.query(`SELECT id FROM ${table} WHERE id = ?`).get(partialId);
2179
+ return row?.id ?? null;
2180
+ }
2181
+ const rows = db.query(`SELECT id FROM ${table} WHERE id LIKE ?`).all(`${partialId}%`);
2182
+ if (rows.length === 1) {
2183
+ return rows[0].id;
2184
+ }
2185
+ return null;
2186
+ }
2187
+ var MIGRATIONS, _db = null;
2188
+ var init_database = __esm(() => {
2189
+ MIGRATIONS = [
2190
+ `
2137
2191
  CREATE TABLE IF NOT EXISTS projects (
2138
2192
  id TEXT PRIMARY KEY,
2139
2193
  name TEXT NOT NULL,
@@ -2220,75 +2274,29 @@ var MIGRATIONS = [
2220
2274
 
2221
2275
  INSERT OR IGNORE INTO _migrations (id) VALUES (1);
2222
2276
  `
2223
- ];
2224
- var _db = null;
2225
- function getDatabase(dbPath) {
2226
- if (_db)
2227
- return _db;
2228
- const path = dbPath || getDbPath();
2229
- ensureDir(path);
2230
- _db = new Database(path, { create: true });
2231
- _db.run("PRAGMA journal_mode = WAL");
2232
- _db.run("PRAGMA busy_timeout = 5000");
2233
- _db.run("PRAGMA foreign_keys = ON");
2234
- runMigrations(_db);
2235
- return _db;
2236
- }
2237
- function runMigrations(db) {
2238
- try {
2239
- const result = db.query("SELECT MAX(id) as max_id FROM _migrations").get();
2240
- const currentLevel = result?.max_id ?? 0;
2241
- for (let i = currentLevel;i < MIGRATIONS.length; i++) {
2242
- try {
2243
- db.exec(MIGRATIONS[i]);
2244
- } catch {}
2245
- }
2246
- } catch {
2247
- for (const migration of MIGRATIONS) {
2248
- try {
2249
- db.exec(migration);
2250
- } catch {}
2251
- }
2252
- }
2253
- }
2254
- function now() {
2255
- return new Date().toISOString();
2256
- }
2257
- function uuid() {
2258
- return crypto.randomUUID();
2259
- }
2260
- function shortUuid() {
2261
- return crypto.randomUUID().slice(0, 8);
2262
- }
2263
- function resolvePartialId(db, table, partialId) {
2264
- if (partialId.length >= 36) {
2265
- const row = db.query(`SELECT id FROM ${table} WHERE id = ?`).get(partialId);
2266
- return row?.id ?? null;
2267
- }
2268
- const rows = db.query(`SELECT id FROM ${table} WHERE id LIKE ?`).all(`${partialId}%`);
2269
- if (rows.length === 1) {
2270
- return rows[0].id;
2271
- }
2272
- return null;
2273
- }
2277
+ ];
2278
+ });
2274
2279
 
2275
2280
  // src/types/index.ts
2276
- class MemoryNotFoundError extends Error {
2277
- constructor(id) {
2278
- super(`Memory not found: ${id}`);
2279
- this.name = "MemoryNotFoundError";
2280
- }
2281
- }
2282
- class VersionConflictError extends Error {
2283
- expected;
2284
- actual;
2285
- constructor(id, expected, actual) {
2286
- super(`Version conflict for memory ${id}: expected ${expected}, got ${actual}`);
2287
- this.name = "VersionConflictError";
2288
- this.expected = expected;
2289
- this.actual = actual;
2290
- }
2291
- }
2281
+ var MemoryNotFoundError, VersionConflictError;
2282
+ var init_types = __esm(() => {
2283
+ MemoryNotFoundError = class MemoryNotFoundError extends Error {
2284
+ constructor(id) {
2285
+ super(`Memory not found: ${id}`);
2286
+ this.name = "MemoryNotFoundError";
2287
+ }
2288
+ };
2289
+ VersionConflictError = class VersionConflictError extends Error {
2290
+ expected;
2291
+ actual;
2292
+ constructor(id, expected, actual) {
2293
+ super(`Version conflict for memory ${id}: expected ${expected}, got ${actual}`);
2294
+ this.name = "VersionConflictError";
2295
+ this.expected = expected;
2296
+ this.actual = actual;
2297
+ }
2298
+ };
2299
+ });
2292
2300
 
2293
2301
  // src/db/memories.ts
2294
2302
  function parseMemoryRow(row) {
@@ -2591,8 +2599,150 @@ function cleanExpiredMemories(db) {
2591
2599
  const result = d.run("DELETE FROM memories WHERE expires_at IS NOT NULL AND expires_at < ?", [timestamp]);
2592
2600
  return result.changes;
2593
2601
  }
2602
+ var init_memories = __esm(() => {
2603
+ init_types();
2604
+ init_database();
2605
+ });
2606
+
2607
+ // src/lib/poll.ts
2608
+ var exports_poll = {};
2609
+ __export(exports_poll, {
2610
+ startPolling: () => startPolling
2611
+ });
2612
+ function startPolling(opts) {
2613
+ const interval = opts.interval_ms ?? 500;
2614
+ const db = opts.db ?? getDatabase();
2615
+ let stopped = false;
2616
+ let inFlight = false;
2617
+ let lastSeen = null;
2618
+ const seedLastSeen = () => {
2619
+ try {
2620
+ const latest = listMemories({
2621
+ scope: opts.scope,
2622
+ category: opts.category,
2623
+ agent_id: opts.agent_id,
2624
+ project_id: opts.project_id,
2625
+ limit: 1
2626
+ }, db);
2627
+ if (latest.length > 0 && latest[0]) {
2628
+ lastSeen = latest[0].updated_at;
2629
+ }
2630
+ } catch (err) {
2631
+ opts.on_error?.(err instanceof Error ? err : new Error(String(err)));
2632
+ }
2633
+ };
2634
+ const poll = () => {
2635
+ if (stopped || inFlight)
2636
+ return;
2637
+ inFlight = true;
2638
+ try {
2639
+ const conditions = ["status = 'active'"];
2640
+ const params = [];
2641
+ if (lastSeen) {
2642
+ conditions.push("(updated_at > ? OR created_at > ?)");
2643
+ params.push(lastSeen, lastSeen);
2644
+ }
2645
+ if (opts.scope) {
2646
+ conditions.push("scope = ?");
2647
+ params.push(opts.scope);
2648
+ }
2649
+ if (opts.category) {
2650
+ conditions.push("category = ?");
2651
+ params.push(opts.category);
2652
+ }
2653
+ if (opts.agent_id) {
2654
+ conditions.push("agent_id = ?");
2655
+ params.push(opts.agent_id);
2656
+ }
2657
+ if (opts.project_id) {
2658
+ conditions.push("project_id = ?");
2659
+ params.push(opts.project_id);
2660
+ }
2661
+ const sql = `SELECT * FROM memories WHERE ${conditions.join(" AND ")} ORDER BY updated_at ASC`;
2662
+ const rows = db.query(sql).all(...params);
2663
+ if (rows.length > 0) {
2664
+ const memories = rows.map(parseMemoryRow3);
2665
+ const lastRow = memories[memories.length - 1];
2666
+ if (lastRow) {
2667
+ lastSeen = lastRow.updated_at;
2668
+ }
2669
+ try {
2670
+ opts.on_memories(memories);
2671
+ } catch (err) {
2672
+ opts.on_error?.(err instanceof Error ? err : new Error(String(err)));
2673
+ }
2674
+ }
2675
+ } catch (err) {
2676
+ opts.on_error?.(err instanceof Error ? err : new Error(String(err)));
2677
+ } finally {
2678
+ inFlight = false;
2679
+ }
2680
+ };
2681
+ seedLastSeen();
2682
+ const timer = setInterval(poll, interval);
2683
+ return {
2684
+ stop: () => {
2685
+ stopped = true;
2686
+ clearInterval(timer);
2687
+ }
2688
+ };
2689
+ }
2690
+ function parseMemoryRow3(row) {
2691
+ return {
2692
+ id: row["id"],
2693
+ key: row["key"],
2694
+ value: row["value"],
2695
+ category: row["category"],
2696
+ scope: row["scope"],
2697
+ summary: row["summary"] || null,
2698
+ tags: JSON.parse(row["tags"] || "[]"),
2699
+ importance: row["importance"],
2700
+ source: row["source"],
2701
+ status: row["status"],
2702
+ pinned: !!row["pinned"],
2703
+ agent_id: row["agent_id"] || null,
2704
+ project_id: row["project_id"] || null,
2705
+ session_id: row["session_id"] || null,
2706
+ metadata: JSON.parse(row["metadata"] || "{}"),
2707
+ access_count: row["access_count"],
2708
+ version: row["version"],
2709
+ expires_at: row["expires_at"] || null,
2710
+ created_at: row["created_at"],
2711
+ updated_at: row["updated_at"],
2712
+ accessed_at: row["accessed_at"] || null
2713
+ };
2714
+ }
2715
+ var init_poll = __esm(() => {
2716
+ init_memories();
2717
+ init_database();
2718
+ });
2719
+
2720
+ // node_modules/commander/esm.mjs
2721
+ var import__ = __toESM(require_commander(), 1);
2722
+ var {
2723
+ program,
2724
+ createCommand,
2725
+ createArgument,
2726
+ createOption,
2727
+ CommanderError,
2728
+ InvalidArgumentError,
2729
+ InvalidOptionArgumentError,
2730
+ Command,
2731
+ Argument,
2732
+ Option,
2733
+ Help
2734
+ } = import__.default;
2735
+
2736
+ // src/cli/index.tsx
2737
+ init_database();
2738
+ init_memories();
2739
+ import chalk from "chalk";
2740
+ import { readFileSync as readFileSync2, existsSync as existsSync3, accessSync, constants as fsConstants } from "fs";
2741
+ import { dirname as dirname3, join as join3, resolve as resolve3 } from "path";
2742
+ import { fileURLToPath } from "url";
2594
2743
 
2595
2744
  // src/db/agents.ts
2745
+ init_database();
2596
2746
  function parseAgentRow(row) {
2597
2747
  return {
2598
2748
  id: row["id"],
@@ -2650,8 +2800,34 @@ function listAgents(db) {
2650
2800
  const rows = d.query("SELECT * FROM agents ORDER BY last_seen_at DESC").all();
2651
2801
  return rows.map(parseAgentRow);
2652
2802
  }
2803
+ function updateAgent(id, updates, db) {
2804
+ const d = db || getDatabase();
2805
+ const agent = getAgent(id, d);
2806
+ if (!agent)
2807
+ return null;
2808
+ const timestamp = now();
2809
+ if (updates.name && updates.name !== agent.name) {
2810
+ const existing = d.query("SELECT id FROM agents WHERE name = ? AND id != ?").get(updates.name, agent.id);
2811
+ if (existing) {
2812
+ throw new Error(`Agent name already taken: ${updates.name}`);
2813
+ }
2814
+ d.run("UPDATE agents SET name = ? WHERE id = ?", [updates.name, agent.id]);
2815
+ }
2816
+ if (updates.description !== undefined) {
2817
+ d.run("UPDATE agents SET description = ? WHERE id = ?", [updates.description, agent.id]);
2818
+ }
2819
+ if (updates.role !== undefined) {
2820
+ d.run("UPDATE agents SET role = ? WHERE id = ?", [updates.role, agent.id]);
2821
+ }
2822
+ if (updates.metadata !== undefined) {
2823
+ d.run("UPDATE agents SET metadata = ? WHERE id = ?", [JSON.stringify(updates.metadata), agent.id]);
2824
+ }
2825
+ d.run("UPDATE agents SET last_seen_at = ? WHERE id = ?", [timestamp, agent.id]);
2826
+ return getAgent(agent.id, d);
2827
+ }
2653
2828
 
2654
2829
  // src/db/projects.ts
2830
+ init_database();
2655
2831
  function parseProjectRow(row) {
2656
2832
  return {
2657
2833
  id: row["id"],
@@ -2696,6 +2872,7 @@ function listProjects(db) {
2696
2872
  }
2697
2873
 
2698
2874
  // src/lib/search.ts
2875
+ init_database();
2699
2876
  function parseMemoryRow2(row) {
2700
2877
  return {
2701
2878
  id: row["id"],
@@ -2929,6 +3106,8 @@ function loadConfig() {
2929
3106
  }
2930
3107
 
2931
3108
  // src/lib/retention.ts
3109
+ init_database();
3110
+ init_memories();
2932
3111
  function enforceQuotas(config, db) {
2933
3112
  const d = db || getDatabase();
2934
3113
  let totalEvicted = 0;
@@ -2971,6 +3150,94 @@ function runCleanup(config, db) {
2971
3150
  return { expired, evicted, archived };
2972
3151
  }
2973
3152
 
3153
+ // src/lib/injector.ts
3154
+ init_memories();
3155
+ class MemoryInjector {
3156
+ config;
3157
+ injectedIds = new Set;
3158
+ constructor(config) {
3159
+ this.config = config || loadConfig();
3160
+ }
3161
+ getInjectionContext(options = {}) {
3162
+ const maxTokens = options.max_tokens || this.config.injection.max_tokens;
3163
+ const minImportance = options.min_importance || this.config.injection.min_importance;
3164
+ const categories = options.categories || this.config.injection.categories;
3165
+ const db = options.db;
3166
+ const allMemories = [];
3167
+ const globalMems = listMemories({
3168
+ scope: "global",
3169
+ category: categories,
3170
+ min_importance: minImportance,
3171
+ status: "active",
3172
+ limit: 100
3173
+ }, db);
3174
+ allMemories.push(...globalMems);
3175
+ if (options.project_id) {
3176
+ const sharedMems = listMemories({
3177
+ scope: "shared",
3178
+ category: categories,
3179
+ min_importance: minImportance,
3180
+ status: "active",
3181
+ project_id: options.project_id,
3182
+ limit: 100
3183
+ }, db);
3184
+ allMemories.push(...sharedMems);
3185
+ }
3186
+ if (options.agent_id) {
3187
+ const privateMems = listMemories({
3188
+ scope: "private",
3189
+ category: categories,
3190
+ min_importance: minImportance,
3191
+ status: "active",
3192
+ agent_id: options.agent_id,
3193
+ limit: 100
3194
+ }, db);
3195
+ allMemories.push(...privateMems);
3196
+ }
3197
+ const seen = new Set;
3198
+ const unique = allMemories.filter((m) => {
3199
+ if (seen.has(m.id))
3200
+ return false;
3201
+ seen.add(m.id);
3202
+ return true;
3203
+ });
3204
+ unique.sort((a, b) => {
3205
+ if (a.pinned !== b.pinned)
3206
+ return a.pinned ? -1 : 1;
3207
+ if (b.importance !== a.importance)
3208
+ return b.importance - a.importance;
3209
+ return new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime();
3210
+ });
3211
+ const charBudget = maxTokens * 4;
3212
+ const lines = [];
3213
+ let totalChars = 0;
3214
+ for (const m of unique) {
3215
+ if (this.injectedIds.has(m.id))
3216
+ continue;
3217
+ const line = `- [${m.scope}/${m.category}] ${m.key}: ${m.value}`;
3218
+ if (totalChars + line.length > charBudget)
3219
+ break;
3220
+ lines.push(line);
3221
+ totalChars += line.length;
3222
+ this.injectedIds.add(m.id);
3223
+ touchMemory(m.id, db);
3224
+ }
3225
+ if (lines.length === 0) {
3226
+ return "";
3227
+ }
3228
+ return `<agent-memories>
3229
+ ${lines.join(`
3230
+ `)}
3231
+ </agent-memories>`;
3232
+ }
3233
+ resetDedup() {
3234
+ this.injectedIds.clear();
3235
+ }
3236
+ getInjectedCount() {
3237
+ return this.injectedIds.size;
3238
+ }
3239
+ }
3240
+
2974
3241
  // src/cli/index.tsx
2975
3242
  function getPackageVersion() {
2976
3243
  try {
@@ -3075,16 +3342,53 @@ function resolveMemoryId(partialId) {
3075
3342
  return id;
3076
3343
  }
3077
3344
  program2.name("mementos").description("Universal memory system for AI agents").version(getPackageVersion()).option("--project <path>", "Project path for scoping").option("--json", "Output as JSON").option("--agent <name>", "Agent name or ID").option("--session <id>", "Session ID");
3078
- 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 <ms>", "Time-to-live in milliseconds", parseInt).option("--source <src>", "Source: user, agent, system, auto, imported").action((key, value, opts) => {
3345
+ 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 <ms>", "Time-to-live in milliseconds", parseInt).option("--source <src>", "Source: user, agent, system, auto, imported").option("--template <name>", "Apply a template: correction, preference, decision, learning").action((key, value, opts) => {
3079
3346
  try {
3080
3347
  const globalOpts = program2.opts();
3348
+ const templates = {
3349
+ correction: {
3350
+ scope: "shared",
3351
+ category: "knowledge",
3352
+ importance: 9,
3353
+ tags: ["correction"]
3354
+ },
3355
+ preference: {
3356
+ scope: "global",
3357
+ category: "preference",
3358
+ importance: 8,
3359
+ tags: []
3360
+ },
3361
+ decision: {
3362
+ scope: "shared",
3363
+ category: "fact",
3364
+ importance: 8,
3365
+ tags: ["decision"]
3366
+ },
3367
+ learning: {
3368
+ scope: "shared",
3369
+ category: "knowledge",
3370
+ importance: 7,
3371
+ tags: ["learning"]
3372
+ }
3373
+ };
3374
+ let templateDefaults;
3375
+ if (opts.template) {
3376
+ const tpl = templates[opts.template];
3377
+ if (!tpl) {
3378
+ console.error(chalk.red(`Unknown template: ${opts.template}. Valid templates: ${Object.keys(templates).join(", ")}`));
3379
+ process.exit(1);
3380
+ }
3381
+ templateDefaults = tpl;
3382
+ }
3383
+ const explicitTags = opts.tags ? opts.tags.split(",").map((t) => t.trim()) : undefined;
3384
+ const mergedTags = explicitTags ? explicitTags : templateDefaults?.tags && templateDefaults.tags.length > 0 ? templateDefaults.tags : undefined;
3081
3385
  const input = {
3082
3386
  key,
3083
3387
  value,
3084
- category: opts.category,
3085
- scope: opts.scope,
3086
- importance: opts.importance,
3087
- tags: opts.tags ? opts.tags.split(",").map((t) => t.trim()) : undefined,
3388
+ category: opts.category ?? templateDefaults?.category,
3389
+ scope: opts.scope ?? templateDefaults?.scope,
3390
+ importance: opts.importance ?? templateDefaults?.importance,
3391
+ tags: mergedTags,
3088
3392
  summary: opts.summary,
3089
3393
  ttl_ms: opts.ttl,
3090
3394
  source: opts.source,
@@ -3119,20 +3423,38 @@ program2.command("recall <key>").description("Recall a memory by key").option("-
3119
3423
  projectId = project.id;
3120
3424
  }
3121
3425
  const memory = getMemoryByKey(key, opts.scope, agentId, projectId);
3122
- if (!memory) {
3426
+ if (memory) {
3427
+ touchMemory(memory.id);
3123
3428
  if (globalOpts.json) {
3124
- outputJson({ error: `No memory found for key: ${key}` });
3429
+ outputJson(memory);
3125
3430
  } else {
3126
- console.error(chalk.yellow(`No memory found for key: ${key}`));
3431
+ console.log(formatMemoryDetail(memory));
3127
3432
  }
3128
- process.exit(1);
3433
+ return;
3434
+ }
3435
+ const results = searchMemories(key, {
3436
+ scope: opts.scope,
3437
+ agent_id: agentId,
3438
+ project_id: projectId,
3439
+ limit: 1
3440
+ });
3441
+ if (results.length > 0) {
3442
+ const best = results[0];
3443
+ touchMemory(best.memory.id);
3444
+ if (globalOpts.json) {
3445
+ outputJson({ fuzzy_match: true, score: best.score, match_type: best.match_type, memory: best.memory });
3446
+ } else {
3447
+ console.log(chalk.yellow(`No exact match, showing best result (score: ${best.score.toFixed(2)}, match: ${best.match_type}):`));
3448
+ console.log(formatMemoryDetail(best.memory));
3449
+ }
3450
+ return;
3129
3451
  }
3130
- touchMemory(memory.id);
3131
3452
  if (globalOpts.json) {
3132
- outputJson(memory);
3453
+ outputJson({ error: `No memory found for key: ${key}` });
3133
3454
  } else {
3134
- console.log(formatMemoryDetail(memory));
3455
+ console.error(chalk.yellow(`No memory found for key: ${key}`));
3135
3456
  }
3457
+ process.exit(1);
3136
3458
  } catch (e) {
3137
3459
  handleError(e);
3138
3460
  }
@@ -3363,10 +3685,18 @@ program2.command("export").description("Export memories as JSON").option("-s, --
3363
3685
  handleError(e);
3364
3686
  }
3365
3687
  });
3366
- program2.command("import <file>").description("Import memories from a JSON file").option("--overwrite", "Overwrite existing memories (default: merge)").action((file, opts) => {
3688
+ program2.command("import [file]").description("Import memories from a JSON file or stdin (use '-' or pipe data)").option("--overwrite", "Overwrite existing memories (default: merge)").action(async (file, opts) => {
3367
3689
  try {
3368
3690
  const globalOpts = program2.opts();
3369
- const raw = readFileSync2(resolve3(file), "utf-8");
3691
+ let raw;
3692
+ if (file === "-" || !file && !process.stdin.isTTY) {
3693
+ raw = await Bun.stdin.text();
3694
+ } else if (file) {
3695
+ raw = readFileSync2(resolve3(file), "utf-8");
3696
+ } else {
3697
+ console.error(chalk.red("No input: provide a file path, use '-' for stdin, or pipe data."));
3698
+ process.exit(1);
3699
+ }
3370
3700
  const memories = JSON.parse(raw);
3371
3701
  if (!Array.isArray(memories)) {
3372
3702
  throw new Error("JSON file must contain an array of memories");
@@ -3440,6 +3770,47 @@ program2.command("agents").description("List all registered agents").action(() =
3440
3770
  handleError(e);
3441
3771
  }
3442
3772
  });
3773
+ program2.command("agent-update <id>").description("Update an agent's name, description, or role").option("--name <name>", "New agent name").option("-d, --description <text>", "New description").option("-r, --role <role>", "New role").action((id, opts) => {
3774
+ try {
3775
+ const globalOpts = program2.opts();
3776
+ const updates = {};
3777
+ if (opts.name !== undefined)
3778
+ updates.name = opts.name;
3779
+ if (opts.description !== undefined)
3780
+ updates.description = opts.description;
3781
+ if (opts.role !== undefined)
3782
+ updates.role = opts.role;
3783
+ if (Object.keys(updates).length === 0) {
3784
+ if (globalOpts.json) {
3785
+ outputJson({ error: "No updates provided. Use --name, --description, or --role." });
3786
+ } else {
3787
+ console.error(chalk.red("No updates provided. Use --name, --description, or --role."));
3788
+ }
3789
+ process.exit(1);
3790
+ }
3791
+ const agent = updateAgent(id, updates);
3792
+ if (!agent) {
3793
+ if (globalOpts.json) {
3794
+ outputJson({ error: `Agent not found: ${id}` });
3795
+ } else {
3796
+ console.error(chalk.red(`Agent not found: ${id}`));
3797
+ }
3798
+ process.exit(1);
3799
+ }
3800
+ if (globalOpts.json) {
3801
+ outputJson(agent);
3802
+ } else {
3803
+ console.log(chalk.green("Agent updated:"));
3804
+ console.log(` ${chalk.bold("ID:")} ${agent.id}`);
3805
+ console.log(` ${chalk.bold("Name:")} ${agent.name}`);
3806
+ console.log(` ${chalk.bold("Description:")} ${agent.description || "-"}`);
3807
+ console.log(` ${chalk.bold("Role:")} ${agent.role || "agent"}`);
3808
+ console.log(` ${chalk.bold("Last seen:")} ${agent.last_seen_at}`);
3809
+ }
3810
+ } catch (e) {
3811
+ handleError(e);
3812
+ }
3813
+ });
3443
3814
  program2.command("projects").description("Manage projects").option("--add", "Add a new project").option("--name <name>", "Project name").option("--path <path>", "Project path").option("--description <text>", "Project description").action((opts) => {
3444
3815
  try {
3445
3816
  const globalOpts = program2.opts();
@@ -3640,6 +4011,156 @@ program2.command("bulk <action> <ids...>").description("Batch operations: forget
3640
4011
  handleError(e);
3641
4012
  }
3642
4013
  });
4014
+ program2.command("doctor").description("Run health checks on the mementos database").action(() => {
4015
+ const globalOpts = program2.opts();
4016
+ const checks = [];
4017
+ const dbPath = getDbPath();
4018
+ if (existsSync3(dbPath)) {
4019
+ try {
4020
+ accessSync(dbPath, fsConstants.R_OK | fsConstants.W_OK);
4021
+ checks.push({ name: "Database file", status: "ok", detail: dbPath });
4022
+ } catch {
4023
+ checks.push({ name: "Database file", status: "fail", detail: `Not readable/writable: ${dbPath}` });
4024
+ }
4025
+ } else {
4026
+ checks.push({ name: "Database file", status: "fail", detail: `Not found: ${dbPath}` });
4027
+ }
4028
+ try {
4029
+ const db = getDatabase();
4030
+ const migRow = db.query("SELECT MAX(id) as max_id FROM _migrations").get();
4031
+ const schemaVersion = migRow?.max_id ?? 0;
4032
+ checks.push({ name: "Schema version", status: schemaVersion > 0 ? "ok" : "warn", detail: `v${schemaVersion}` });
4033
+ const memCount = db.query("SELECT COUNT(*) as c FROM memories").get().c;
4034
+ const agentCount = db.query("SELECT COUNT(*) as c FROM agents").get().c;
4035
+ const projectCount = db.query("SELECT COUNT(*) as c FROM projects").get().c;
4036
+ checks.push({ name: "Memories", status: "ok", detail: String(memCount) });
4037
+ checks.push({ name: "Agents", status: "ok", detail: String(agentCount) });
4038
+ checks.push({ name: "Projects", status: "ok", detail: String(projectCount) });
4039
+ const orphanedTags = db.query("SELECT COUNT(*) as c FROM memory_tags WHERE memory_id NOT IN (SELECT id FROM memories)").get().c;
4040
+ checks.push({
4041
+ name: "Orphaned tags",
4042
+ status: orphanedTags > 0 ? "warn" : "ok",
4043
+ detail: orphanedTags > 0 ? `${orphanedTags} orphaned tag(s)` : "None"
4044
+ });
4045
+ const expiredCount = db.query("SELECT COUNT(*) as c FROM memories WHERE status != 'expired' AND expires_at IS NOT NULL AND expires_at < datetime('now')").get().c;
4046
+ checks.push({
4047
+ name: "Expired memories",
4048
+ status: expiredCount > 0 ? "warn" : "ok",
4049
+ detail: expiredCount > 0 ? `${expiredCount} expired but not cleaned up (run 'mementos clean')` : "None pending"
4050
+ });
4051
+ } catch (e) {
4052
+ checks.push({ name: "Database connection", status: "fail", detail: e instanceof Error ? e.message : String(e) });
4053
+ }
4054
+ if (globalOpts.json) {
4055
+ outputJson({ checks, healthy: checks.every((c) => c.status === "ok") });
4056
+ } else {
4057
+ console.log(chalk.bold(`
4058
+ Mementos Health Report
4059
+ `));
4060
+ for (const check of checks) {
4061
+ const icon = check.status === "ok" ? chalk.green("\u2713") : check.status === "warn" ? chalk.yellow("!") : chalk.red("\u2717");
4062
+ console.log(` ${icon} ${chalk.bold(check.name)}: ${check.detail}`);
4063
+ }
4064
+ const healthy = checks.every((c) => c.status === "ok");
4065
+ const warnings = checks.filter((c) => c.status === "warn").length;
4066
+ const failures = checks.filter((c) => c.status === "fail").length;
4067
+ console.log("");
4068
+ if (healthy) {
4069
+ console.log(chalk.green(" All checks passed."));
4070
+ } else {
4071
+ if (failures > 0)
4072
+ console.log(chalk.red(` ${failures} check(s) failed.`));
4073
+ if (warnings > 0)
4074
+ console.log(chalk.yellow(` ${warnings} warning(s).`));
4075
+ }
4076
+ console.log("");
4077
+ }
4078
+ });
4079
+ program2.command("show <id>").description("Show full detail of a memory by ID (supports partial IDs)").action((id) => {
4080
+ try {
4081
+ const globalOpts = program2.opts();
4082
+ const resolvedId = resolveMemoryId(id);
4083
+ const memory = getMemory(resolvedId);
4084
+ if (!memory) {
4085
+ if (globalOpts.json) {
4086
+ outputJson({ error: `Memory not found: ${id}` });
4087
+ } else {
4088
+ console.error(chalk.red(`Memory not found: ${id}`));
4089
+ }
4090
+ process.exit(1);
4091
+ }
4092
+ touchMemory(memory.id);
4093
+ if (globalOpts.json) {
4094
+ outputJson(memory);
4095
+ } else {
4096
+ console.log(formatMemoryDetail(memory));
4097
+ }
4098
+ } catch (e) {
4099
+ handleError(e);
4100
+ }
4101
+ });
4102
+ program2.command("history").description("List memories sorted by most recently accessed").option("--limit <n>", "Max results (default: 20)", parseInt).action((opts) => {
4103
+ try {
4104
+ const globalOpts = program2.opts();
4105
+ const limit = opts.limit || 20;
4106
+ const db = getDatabase();
4107
+ const rows = db.query("SELECT * FROM memories WHERE status = 'active' AND accessed_at IS NOT NULL ORDER BY accessed_at DESC LIMIT ?").all(limit);
4108
+ const memories = rows.map(parseMemoryRow);
4109
+ if (globalOpts.json) {
4110
+ outputJson(memories);
4111
+ return;
4112
+ }
4113
+ if (memories.length === 0) {
4114
+ console.log(chalk.yellow("No recently accessed memories."));
4115
+ return;
4116
+ }
4117
+ console.log(chalk.bold(`${memories.length} recently accessed memor${memories.length === 1 ? "y" : "ies"}:`));
4118
+ for (const m of memories) {
4119
+ const id = chalk.dim(m.id.slice(0, 8));
4120
+ const scope = colorScope(m.scope);
4121
+ const cat = colorCategory(m.category);
4122
+ const value = m.value.length > 60 ? m.value.slice(0, 60) + "..." : m.value;
4123
+ const accessed = m.accessed_at ? chalk.dim(m.accessed_at) : chalk.dim("never");
4124
+ console.log(`${id} [${scope}/${cat}] ${chalk.bold(m.key)} = ${value} ${accessed}`);
4125
+ }
4126
+ } catch (e) {
4127
+ handleError(e);
4128
+ }
4129
+ });
4130
+ program2.command("context").description("Output formatted injection context (for piping into agent prompts)").option("--agent <name>", "Agent ID for private memory scope").option("--project <path>", "Project path for shared memory scope").option("--max-tokens <n>", "Token budget for context", parseInt).option("--categories <cats>", "Comma-separated categories: preference, fact, knowledge, history").action((opts) => {
4131
+ try {
4132
+ const globalOpts = program2.opts();
4133
+ const agentId = opts.agent || globalOpts.agent;
4134
+ const projectPath = opts.project || globalOpts.project;
4135
+ let projectId;
4136
+ if (projectPath) {
4137
+ const project = getProject(resolve3(projectPath));
4138
+ if (project)
4139
+ projectId = project.id;
4140
+ }
4141
+ const categories = opts.categories ? opts.categories.split(",").map((c) => c.trim()) : undefined;
4142
+ const injector = new MemoryInjector;
4143
+ const context = injector.getInjectionContext({
4144
+ agent_id: agentId,
4145
+ project_id: projectId,
4146
+ max_tokens: opts.maxTokens,
4147
+ categories
4148
+ });
4149
+ if (globalOpts.json) {
4150
+ outputJson({
4151
+ context,
4152
+ injected_count: injector.getInjectedCount()
4153
+ });
4154
+ } else if (context) {
4155
+ console.log(context);
4156
+ } else {
4157
+ console.error(chalk.yellow("No memories matched the injection criteria."));
4158
+ process.exit(1);
4159
+ }
4160
+ } catch (e) {
4161
+ handleError(e);
4162
+ }
4163
+ });
3643
4164
  program2.command("mcp").description("Install mementos MCP server into Claude Code, Codex, or Gemini").option("--claude", "Install into Claude Code (~/.claude/.mcp.json)").option("--codex", "Install into Codex (~/.codex/config.toml)").option("--gemini", "Install into Gemini (~/.gemini/settings.json)").option("--all", "Install into all supported agents").option("--uninstall", "Remove mementos MCP from config").action((opts) => {
3644
4165
  const { readFileSync: readFileSync3, writeFileSync, existsSync: fileExists } = __require("fs");
3645
4166
  const { join: pathJoin } = __require("path");
@@ -3717,4 +4238,105 @@ args = []
3717
4238
  }
3718
4239
  }
3719
4240
  });
4241
+ 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) => {
4242
+ try {
4243
+ const globalOpts = program2.opts();
4244
+ const agentId = opts.agent || globalOpts.agent;
4245
+ const projectPath = opts.project || globalOpts.project;
4246
+ let projectId;
4247
+ if (projectPath) {
4248
+ const project = getProject(resolve3(projectPath));
4249
+ if (project)
4250
+ projectId = project.id;
4251
+ }
4252
+ const intervalMs = opts.interval || 500;
4253
+ console.log(chalk.bold.cyan("Watching memories...") + chalk.dim(" (Ctrl+C to stop)"));
4254
+ const filters = [];
4255
+ if (opts.scope)
4256
+ filters.push(`scope=${colorScope(opts.scope)}`);
4257
+ if (opts.category)
4258
+ filters.push(`category=${colorCategory(opts.category)}`);
4259
+ if (agentId)
4260
+ filters.push(`agent=${chalk.dim(agentId)}`);
4261
+ if (projectId)
4262
+ filters.push(`project=${chalk.dim(projectId)}`);
4263
+ if (filters.length > 0) {
4264
+ console.log(chalk.dim("Filters: ") + filters.join(chalk.dim(" | ")));
4265
+ }
4266
+ console.log(chalk.dim(`Poll interval: ${intervalMs}ms`));
4267
+ console.log();
4268
+ const filter = {
4269
+ scope: opts.scope,
4270
+ category: opts.category,
4271
+ agent_id: agentId,
4272
+ project_id: projectId,
4273
+ limit: 20
4274
+ };
4275
+ const recent = listMemories(filter);
4276
+ if (recent.length > 0) {
4277
+ console.log(chalk.bold.dim(`Recent (${recent.length}):`));
4278
+ for (const m of recent.reverse()) {
4279
+ console.log(formatWatchLine(m));
4280
+ }
4281
+ } else {
4282
+ console.log(chalk.dim("No recent memories."));
4283
+ }
4284
+ console.log(chalk.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 Live \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
4285
+ console.log();
4286
+ const { startPolling: startPolling2 } = (init_poll(), __toCommonJS(exports_poll));
4287
+ const handle = startPolling2({
4288
+ interval_ms: intervalMs,
4289
+ scope: opts.scope,
4290
+ category: opts.category,
4291
+ agent_id: agentId,
4292
+ project_id: projectId,
4293
+ on_memories: (memories) => {
4294
+ for (const m of memories) {
4295
+ console.log(formatWatchLine(m));
4296
+ sendNotification(m);
4297
+ }
4298
+ },
4299
+ on_error: (err) => {
4300
+ console.error(chalk.red(`Poll error: ${err.message}`));
4301
+ }
4302
+ });
4303
+ const cleanup = () => {
4304
+ handle.stop();
4305
+ console.log();
4306
+ console.log(chalk.dim("Stopped watching."));
4307
+ process.exit(0);
4308
+ };
4309
+ process.on("SIGINT", cleanup);
4310
+ process.on("SIGTERM", cleanup);
4311
+ } catch (e) {
4312
+ handleError(e);
4313
+ }
4314
+ });
4315
+ function formatWatchLine(m) {
4316
+ const scope = colorScope(m.scope);
4317
+ const cat = colorCategory(m.category);
4318
+ const imp = colorImportance(m.importance);
4319
+ const value = m.value.length > 100 ? m.value.slice(0, 100) + "..." : m.value;
4320
+ const main = ` [${scope}/${cat}] ${chalk.bold(m.key)} = ${value} ${chalk.dim(`(importance: ${imp})`)}`;
4321
+ const parts = [];
4322
+ if (m.tags.length > 0)
4323
+ parts.push(`Tags: ${m.tags.join(", ")}`);
4324
+ if (m.agent_id)
4325
+ parts.push(`Agent: ${m.agent_id}`);
4326
+ parts.push(`Updated: ${m.updated_at}`);
4327
+ const detail = chalk.dim(` ${parts.join(" | ")}`);
4328
+ return `${main}
4329
+ ${detail}`;
4330
+ }
4331
+ function sendNotification(m) {
4332
+ if (process.platform !== "darwin")
4333
+ return;
4334
+ try {
4335
+ const title = "Mementos";
4336
+ const msg = `[${m.scope}/${m.category}] ${m.key} = ${m.value.slice(0, 60)}`;
4337
+ const escaped = msg.replace(/"/g, "\\\"");
4338
+ const { execSync } = __require("child_process");
4339
+ execSync(`osascript -e 'display notification "${escaped}" with title "${title}"'`, { stdio: "ignore", timeout: 2000 });
4340
+ } catch {}
4341
+ }
3720
4342
  program2.parse(process.argv);