@agentmemory/agentmemory 0.8.0 → 0.8.5

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.mjs CHANGED
@@ -1,13 +1,84 @@
1
1
  #!/usr/bin/env node
2
- import { execFileSync, execSync, spawn } from "node:child_process";
2
+ import { execFileSync, spawn } from "node:child_process";
3
3
  import { existsSync } from "node:fs";
4
- import { dirname, join } from "node:path";
4
+ import { delimiter, dirname, join } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
+ import { platform } from "node:os";
6
7
  import * as p from "@clack/prompts";
8
+ import { createHash } from "node:crypto";
7
9
 
10
+ //#region src/state/schema.ts
11
+ const KV = {
12
+ sessions: "mem:sessions",
13
+ observations: (sessionId) => `mem:obs:${sessionId}`,
14
+ memories: "mem:memories",
15
+ summaries: "mem:summaries",
16
+ config: "mem:config",
17
+ metrics: "mem:metrics",
18
+ health: "mem:health",
19
+ embeddings: (obsId) => `mem:emb:${obsId}`,
20
+ bm25Index: "mem:index:bm25",
21
+ relations: "mem:relations",
22
+ profiles: "mem:profiles",
23
+ claudeBridge: "mem:claude-bridge",
24
+ graphNodes: "mem:graph:nodes",
25
+ graphEdges: "mem:graph:edges",
26
+ semantic: "mem:semantic",
27
+ procedural: "mem:procedural",
28
+ teamShared: (teamId) => `mem:team:${teamId}:shared`,
29
+ teamUsers: (teamId, userId) => `mem:team:${teamId}:users:${userId}`,
30
+ teamProfile: (teamId) => `mem:team:${teamId}:profile`,
31
+ audit: "mem:audit",
32
+ actions: "mem:actions",
33
+ actionEdges: "mem:action-edges",
34
+ leases: "mem:leases",
35
+ routines: "mem:routines",
36
+ routineRuns: "mem:routine-runs",
37
+ signals: "mem:signals",
38
+ checkpoints: "mem:checkpoints",
39
+ mesh: "mem:mesh",
40
+ sketches: "mem:sketches",
41
+ facets: "mem:facets",
42
+ sentinels: "mem:sentinels",
43
+ crystals: "mem:crystals",
44
+ lessons: "mem:lessons",
45
+ insights: "mem:insights",
46
+ graphEdgeHistory: "mem:graph:edge-history",
47
+ enrichedChunks: (sessionId) => `mem:enriched:${sessionId}`,
48
+ latentEmbeddings: (obsId) => `mem:latent:${obsId}`,
49
+ retentionScores: "mem:retention",
50
+ accessLog: "mem:access"
51
+ };
52
+ const STREAM = {
53
+ name: "mem-live",
54
+ group: (sessionId) => sessionId,
55
+ viewerGroup: "viewer"
56
+ };
57
+ function generateId(prefix) {
58
+ return `${prefix}_${Date.now().toString(36)}_${crypto.randomUUID().replace(/-/g, "").slice(0, 12)}`;
59
+ }
60
+ function fingerprintId(prefix, content) {
61
+ return `${prefix}_${createHash("sha256").update(content).digest("hex").slice(0, 16)}`;
62
+ }
63
+ function jaccardSimilarity(a, b) {
64
+ const setA = new Set(a.split(/\s+/).filter((t) => t.length > 2));
65
+ const setB = new Set(b.split(/\s+/).filter((t) => t.length > 2));
66
+ if (setA.size === 0 && setB.size === 0) return 1;
67
+ if (setA.size === 0 || setB.size === 0) return 0;
68
+ let intersection = 0;
69
+ for (const word of setA) if (setB.has(word)) intersection++;
70
+ return intersection / (setA.size + setB.size - intersection);
71
+ }
72
+
73
+ //#endregion
8
74
  //#region src/cli.ts
9
75
  const __dirname = dirname(fileURLToPath(import.meta.url));
10
76
  const args = process.argv.slice(2);
77
+ const IS_WINDOWS = platform() === "win32";
78
+ const IS_VERBOSE = args.includes("--verbose") || args.includes("-v");
79
+ function vlog(msg) {
80
+ if (IS_VERBOSE) p.log.info(`[verbose] ${msg}`);
81
+ }
11
82
  if (args.includes("--help") || args.includes("-h")) {
12
83
  console.log(`
13
84
  agentmemory — persistent memory for AI coding agents
@@ -17,17 +88,22 @@ Usage: agentmemory [command] [options]
17
88
  Commands:
18
89
  (default) Start agentmemory worker
19
90
  status Show connection status, memory count, and health
91
+ demo Seed sample sessions and show recall in action
92
+ mcp Start standalone MCP server (no engine required)
20
93
 
21
94
  Options:
22
95
  --help, -h Show this help
96
+ --verbose, -v Show engine stderr and diagnostic info on startup
23
97
  --tools all|core Tool visibility (default: core = 7 tools)
24
98
  --no-engine Skip auto-starting iii-engine
25
99
  --port <N> Override REST port (default: 3111)
26
100
 
27
101
  Quick start:
28
- npx @agentmemory/agentmemory # start everything
102
+ npx @agentmemory/agentmemory # start with local iii-engine or Docker
29
103
  npx @agentmemory/agentmemory status # check health
30
- npx agentmemory-mcp # standalone MCP (no engine)
104
+ npx @agentmemory/agentmemory demo # try it in 30 seconds (needs server running)
105
+ npx @agentmemory/agentmemory mcp # standalone MCP server (no engine)
106
+ npx agentmemory-mcp # same as above (shim package)
31
107
  `);
32
108
  process.exit(0);
33
109
  }
@@ -57,94 +133,106 @@ function findIiiConfig() {
57
133
  return "";
58
134
  }
59
135
  function whichBinary(name) {
60
- const cmd = process.platform === "win32" ? "where" : "which";
136
+ const cmd = IS_WINDOWS ? "where" : "which";
61
137
  try {
62
- return execFileSync(cmd, [name], { encoding: "utf-8" }).trim().split("\n")[0];
138
+ return execFileSync(cmd, [name], { encoding: "utf-8" }).split(/\r?\n/).map((line) => line.trim()).find((line) => line.length > 0) ?? null;
63
139
  } catch {
64
140
  return null;
65
141
  }
66
142
  }
67
- async function installIii() {
68
- if (process.platform === "win32") {
69
- p.log.warn("Automatic iii-engine install is not supported on Windows.");
70
- p.log.info("Install manually: https://iii.dev/docs");
71
- return false;
72
- }
73
- if (!whichBinary("curl")) {
74
- p.log.warn("curl not found — cannot auto-install iii-engine.");
75
- return false;
143
+ function fallbackIiiPaths() {
144
+ if (IS_WINDOWS) {
145
+ const userProfile = process.env["USERPROFILE"];
146
+ if (!userProfile) return [];
147
+ return [join(userProfile, ".local", "bin", "iii.exe"), join(userProfile, "bin", "iii.exe")];
76
148
  }
77
- const shouldInstall = await p.confirm({
78
- message: "iii-engine is not installed. Install it now?",
79
- initialValue: true
149
+ const home = process.env["HOME"];
150
+ if (!home) return ["/usr/local/bin/iii"];
151
+ return [join(home, ".local", "bin", "iii"), "/usr/local/bin/iii"];
152
+ }
153
+ let startupFailure = null;
154
+ function spawnEngineBackground(bin, spawnArgs, label) {
155
+ vlog(`spawn: ${bin} ${spawnArgs.join(" ")}`);
156
+ const child = spawn(bin, spawnArgs, {
157
+ detached: true,
158
+ stdio: [
159
+ "ignore",
160
+ "ignore",
161
+ "pipe"
162
+ ],
163
+ windowsHide: true
80
164
  });
81
- if (p.isCancel(shouldInstall) || !shouldInstall) return false;
82
- const s = p.spinner();
83
- s.start("Installing iii-engine...");
84
- try {
85
- execSync("curl -fsSL https://install.iii.dev/iii/main/install.sh | sh", {
86
- stdio: [
87
- "pipe",
88
- "pipe",
89
- "pipe"
90
- ],
91
- timeout: 12e4
92
- });
93
- if (whichBinary("iii")) {
94
- s.stop("iii-engine installed successfully");
95
- return true;
96
- }
97
- s.stop("Installation completed but iii not found in PATH");
98
- p.log.warn("You may need to restart your shell or add iii to your PATH.");
99
- const iiiPaths = [join(process.env["HOME"] || "", ".local", "bin", "iii"), "/usr/local/bin/iii"];
100
- for (const iiiPath of iiiPaths) if (existsSync(iiiPath)) {
101
- p.log.info(`Found iii at: ${iiiPath}`);
102
- process.env["PATH"] = `${dirname(iiiPath)}:${process.env["PATH"]}`;
103
- return true;
165
+ const stderrChunks = [];
166
+ let stderrBytes = 0;
167
+ const MAX_STDERR_CAPTURE = 16 * 1024;
168
+ child.stderr?.on("data", (chunk) => {
169
+ if (stderrBytes >= MAX_STDERR_CAPTURE) return;
170
+ const slice = chunk.subarray(0, MAX_STDERR_CAPTURE - stderrBytes);
171
+ stderrChunks.push(slice);
172
+ stderrBytes += slice.length;
173
+ });
174
+ child.on("exit", (code, signal) => {
175
+ if (code !== null && code !== 0 || code === null && signal !== null) {
176
+ const stderr = Buffer.concat(stderrChunks).toString("utf-8");
177
+ startupFailure = {
178
+ kind: label.includes("Docker") ? "docker-crashed" : "engine-crashed",
179
+ stderr: stderr.trim() || (signal ? `process killed by signal ${signal}` : `process exited with code ${code}`),
180
+ binary: bin
181
+ };
182
+ vlog(`engine exited early: code=${code} signal=${signal}`);
183
+ if (IS_VERBOSE && stderr.trim()) p.log.error(`engine stderr:\n${stderr}`);
104
184
  }
105
- return false;
106
- } catch (err) {
107
- s.stop("Failed to install iii-engine");
108
- p.log.error(err instanceof Error ? err.message : String(err));
109
- return false;
110
- }
185
+ });
186
+ child.unref();
187
+ return child;
111
188
  }
112
189
  async function startEngine() {
113
190
  const configPath = findIiiConfig();
114
191
  let iiiBin = whichBinary("iii");
115
- if (!iiiBin) {
116
- if (await installIii()) iiiBin = whichBinary("iii");
117
- }
192
+ vlog(`iii binary: ${iiiBin ?? "(not on PATH)"}, config: ${configPath || "(not found)"}`);
118
193
  if (iiiBin && configPath) {
119
194
  const s = p.spinner();
120
195
  s.start(`Starting iii-engine: ${iiiBin}`);
121
- spawn(iiiBin, ["--config", configPath], {
122
- detached: true,
123
- stdio: "ignore"
124
- }).unref();
196
+ spawnEngineBackground(iiiBin, ["--config", configPath], "iii-engine");
125
197
  s.stop("iii-engine process started");
126
198
  return true;
127
199
  }
128
200
  const dockerBin = whichBinary("docker");
129
- const dockerCompose = join(__dirname, "..", "docker-compose.yml");
130
- const dcExists = existsSync(dockerCompose) || existsSync(join(process.cwd(), "docker-compose.yml"));
131
- if (dockerBin && dcExists) {
132
- const composeFile = existsSync(dockerCompose) ? dockerCompose : join(process.cwd(), "docker-compose.yml");
201
+ vlog(`docker binary: ${dockerBin ?? "(not on PATH)"}`);
202
+ const composeFile = [
203
+ join(__dirname, "..", "docker-compose.yml"),
204
+ join(__dirname, "docker-compose.yml"),
205
+ join(process.cwd(), "docker-compose.yml")
206
+ ].find((c) => existsSync(c));
207
+ vlog(`docker-compose.yml: ${composeFile ?? "(not found)"}`);
208
+ if (dockerBin && composeFile) {
133
209
  const s = p.spinner();
134
210
  s.start("Starting iii-engine via Docker...");
135
- spawn(dockerBin, [
211
+ spawnEngineBackground(dockerBin, [
136
212
  "compose",
137
213
  "-f",
138
214
  composeFile,
139
215
  "up",
140
216
  "-d"
141
- ], {
142
- detached: true,
143
- stdio: "ignore"
144
- }).unref();
217
+ ], "iii-engine via Docker");
145
218
  s.stop("Docker compose started");
146
219
  return true;
147
220
  }
221
+ for (const iiiPath of fallbackIiiPaths()) if (existsSync(iiiPath)) {
222
+ p.log.info(`Found iii at: ${iiiPath}`);
223
+ process.env["PATH"] = `${dirname(iiiPath)}${delimiter}${process.env["PATH"] ?? ""}`;
224
+ iiiBin = iiiPath;
225
+ break;
226
+ }
227
+ if (iiiBin && configPath) {
228
+ const s = p.spinner();
229
+ s.start(`Starting iii-engine: ${iiiBin}`);
230
+ spawnEngineBackground(iiiBin, ["--config", configPath], "iii-engine");
231
+ s.stop("iii-engine process started");
232
+ return true;
233
+ }
234
+ if (!iiiBin && (!dockerBin || !composeFile)) startupFailure = { kind: "no-engine" };
235
+ else if (!composeFile && dockerBin) startupFailure = { kind: "no-docker-compose" };
148
236
  return false;
149
237
  }
150
238
  async function waitForEngine(timeoutMs) {
@@ -155,32 +243,60 @@ async function waitForEngine(timeoutMs) {
155
243
  }
156
244
  return false;
157
245
  }
246
+ function installInstructions() {
247
+ if (IS_WINDOWS) return [
248
+ "agentmemory requires the `iii-engine` runtime. Pick one:",
249
+ "",
250
+ " A) Download the prebuilt Windows binary:",
251
+ " 1. Open https://github.com/iii-hq/iii/releases/latest",
252
+ " 2. Download iii-x86_64-pc-windows-msvc.zip",
253
+ " (or iii-aarch64-pc-windows-msvc.zip on ARM)",
254
+ " 3. Extract iii.exe and either add its folder to PATH",
255
+ " or move it to %USERPROFILE%\\.local\\bin\\iii.exe",
256
+ " 4. Re-run: npx @agentmemory/agentmemory",
257
+ "",
258
+ " B) Docker Desktop:",
259
+ " 1. Install Docker Desktop for Windows",
260
+ " 2. Start Docker Desktop (engine must be running)",
261
+ " 3. Re-run: npx @agentmemory/agentmemory",
262
+ "",
263
+ "Or skip the engine entirely for standalone MCP:",
264
+ " npx @agentmemory/agentmemory mcp"
265
+ ];
266
+ return [
267
+ "agentmemory requires the `iii-engine` runtime. Pick one:",
268
+ "",
269
+ " A) curl -fsSL https://install.iii.dev/iii/main/install.sh | sh",
270
+ " (installs the prebuilt iii binary into ~/.local/bin/iii)",
271
+ "",
272
+ " B) Docker: install Docker Desktop or docker-ce, then re-run",
273
+ "",
274
+ "Or skip the engine entirely for standalone MCP:",
275
+ " npx @agentmemory/agentmemory mcp",
276
+ "",
277
+ "Docs: https://iii.dev/docs"
278
+ ];
279
+ }
280
+ function portInUseDiagnostic(port) {
281
+ return IS_WINDOWS ? ` netstat -ano | findstr :${port}` : ` lsof -i :${port} # or: ss -tlnp | grep :${port}`;
282
+ }
158
283
  async function main() {
159
284
  p.intro("agentmemory");
160
285
  if (skipEngine) {
161
286
  p.log.info("Skipping engine check (--no-engine)");
162
- await import("./src-qOdKVNQz.mjs");
287
+ await import("./src-BuDB8dPq.mjs");
163
288
  return;
164
289
  }
165
290
  if (await isEngineRunning()) {
166
291
  p.log.success("iii-engine is running");
167
- await import("./src-qOdKVNQz.mjs");
292
+ await import("./src-BuDB8dPq.mjs");
168
293
  return;
169
294
  }
170
295
  if (!await startEngine()) {
171
296
  p.log.error("Could not start iii-engine.");
172
- p.note([
173
- "Install iii-engine (pick one):",
174
- " curl -fsSL https://install.iii.dev/iii/main/install.sh | sh",
175
- " cargo install iii-engine",
176
- "",
177
- "Or use Docker:",
178
- " docker pull iiidev/iii:latest",
179
- "",
180
- "Docs: https://iii.dev/docs",
181
- "",
182
- "Or skip with: agentmemory --no-engine"
183
- ].join("\n"), "Setup required");
297
+ const lines = installInstructions();
298
+ if (startupFailure?.kind === "no-docker-compose") lines.unshift("Docker is installed but docker-compose.yml is missing from this", "install. Re-install with: npm install -g @agentmemory/agentmemory", "");
299
+ p.note(lines.join("\n"), "Setup required");
184
300
  process.exit(1);
185
301
  }
186
302
  const s = p.spinner();
@@ -188,11 +304,36 @@ async function main() {
188
304
  if (!await waitForEngine(15e3)) {
189
305
  const port = getRestPort();
190
306
  s.stop("iii-engine did not become ready within 15s");
191
- p.log.error(`Check that ports ${port}, ${port + 1}, 49134 are available.`);
307
+ if (startupFailure?.kind === "engine-crashed" || startupFailure?.kind === "docker-crashed") {
308
+ p.log.error("The iii-engine process crashed on startup.");
309
+ if (startupFailure.binary) p.log.info(`Binary: ${startupFailure.binary}`);
310
+ if (startupFailure.stderr) p.note(startupFailure.stderr, "engine stderr");
311
+ else p.log.info("No stderr was captured. Re-run with --verbose for more detail.");
312
+ p.note([
313
+ "Common causes:",
314
+ " - iii-engine version mismatch — reinstall the latest binary",
315
+ " (sh script on macOS/Linux, GitHub release zip on Windows)",
316
+ " - Docker Desktop not running (if you're using the Docker path)",
317
+ " - Port already in use (see below)",
318
+ "",
319
+ "See https://iii.dev/docs for current install instructions."
320
+ ].join("\n"), "Troubleshooting");
321
+ } else {
322
+ p.log.error("The engine process started but the REST API never responded.");
323
+ p.note([
324
+ `Check whether port ${port} is already bound by another process:`,
325
+ portInUseDiagnostic(port),
326
+ "",
327
+ "If it is, free the port or override: agentmemory --port <N>",
328
+ "",
329
+ "If it isn't, a firewall may be blocking 127.0.0.1:" + port + ".",
330
+ "Re-run with --verbose to see engine stderr."
331
+ ].join("\n"), "Troubleshooting");
332
+ }
192
333
  process.exit(1);
193
334
  }
194
335
  s.stop("iii-engine is ready");
195
- await import("./src-qOdKVNQz.mjs");
336
+ await import("./src-BuDB8dPq.mjs");
196
337
  }
197
338
  async function runStatus() {
198
339
  const port = getRestPort();
@@ -204,46 +345,229 @@ async function runStatus() {
204
345
  process.exit(1);
205
346
  }
206
347
  try {
207
- const [healthRes, sessionsRes, graphRes] = await Promise.all([
348
+ const [healthRes, sessionsRes, graphRes, memoriesRes] = await Promise.all([
208
349
  fetch(`${base}/agentmemory/health`, { signal: AbortSignal.timeout(5e3) }).then((r) => r.json()).catch(() => null),
209
350
  fetch(`${base}/agentmemory/sessions`, { signal: AbortSignal.timeout(5e3) }).then((r) => r.json()).catch(() => null),
210
- fetch(`${base}/agentmemory/graph/stats`, { signal: AbortSignal.timeout(5e3) }).then((r) => r.json()).catch(() => null)
351
+ fetch(`${base}/agentmemory/graph/stats`, { signal: AbortSignal.timeout(5e3) }).then((r) => r.json()).catch(() => null),
352
+ fetch(`${base}/agentmemory/export`, { signal: AbortSignal.timeout(5e3) }).then((r) => r.json()).catch(() => null)
211
353
  ]);
212
354
  const h = healthRes?.health;
213
355
  const status = healthRes?.status || "unknown";
214
356
  const version = healthRes?.version || "?";
215
357
  const sessions = Array.isArray(sessionsRes?.sessions) ? sessionsRes.sessions.length : 0;
216
- h?.workers?.[0]?.function_count;
217
358
  const nodes = graphRes?.nodes || 0;
218
359
  const edges = graphRes?.edges || 0;
219
360
  const cb = healthRes?.circuitBreaker?.state || "closed";
220
361
  const heapMB = h?.memory ? Math.round(h.memory.heapUsed / 1048576) : 0;
221
362
  const uptime = h?.uptimeSeconds ? Math.round(h.uptimeSeconds) : 0;
363
+ const obsCount = memoriesRes?.observations?.length || 0;
364
+ const memCount = memoriesRes?.memories?.length || 0;
365
+ const estFullTokens = obsCount * 80;
366
+ const estInjectedTokens = Math.min(obsCount, 50) * 38;
367
+ const tokensSaved = estFullTokens - estInjectedTokens;
368
+ const pctSaved = estFullTokens > 0 ? Math.round(tokensSaved / estFullTokens * 100) : 0;
222
369
  p.log.success(`Connected — v${version} on port ${port}`);
223
370
  const lines = [
224
- `Health: ${status === "healthy" ? "healthy" : status}`,
225
- `Sessions: ${sessions}`,
226
- `Graph: ${nodes} nodes, ${edges} edges`,
227
- `Circuit: ${cb}`,
228
- `Heap: ${heapMB} MB`,
229
- `Uptime: ${uptime}s`,
230
- `Viewer: http://localhost:${port + 2}`
371
+ `Health: ${status === "healthy" ? "healthy" : status}`,
372
+ `Sessions: ${sessions}`,
373
+ `Observations: ${obsCount}`,
374
+ `Memories: ${memCount}`,
375
+ `Graph: ${nodes} nodes, ${edges} edges`,
376
+ `Circuit: ${cb}`,
377
+ `Heap: ${heapMB} MB`,
378
+ `Uptime: ${uptime}s`,
379
+ `Viewer: http://localhost:${port + 2}`
231
380
  ];
381
+ if (obsCount > 0) {
382
+ lines.push("");
383
+ lines.push(`Token savings: ~${tokensSaved.toLocaleString()} tokens saved (${pctSaved}% reduction)`);
384
+ lines.push(` Full context: ~${estFullTokens.toLocaleString()} tokens`);
385
+ lines.push(` Injected: ~${estInjectedTokens.toLocaleString()} tokens`);
386
+ }
232
387
  p.note(lines.join("\n"), "agentmemory");
233
388
  } catch (err) {
234
389
  p.log.error(err instanceof Error ? err.message : String(err));
235
390
  process.exit(1);
236
391
  }
237
392
  }
238
- if (args[0] === "status") runStatus().catch((err) => {
239
- p.log.error(err instanceof Error ? err.message : String(err));
240
- process.exit(1);
241
- });
242
- else main().catch((err) => {
393
+ function buildDemoSessions() {
394
+ return [
395
+ {
396
+ id: generateId("demo"),
397
+ title: "Session 1: JWT auth setup",
398
+ observations: [
399
+ {
400
+ toolName: "Write",
401
+ toolInput: { file_path: "src/middleware/auth.ts" },
402
+ toolOutput: "Created JWT middleware using jose library. Tokens expire after 30 days. Chose jose over jsonwebtoken for Edge compatibility."
403
+ },
404
+ {
405
+ toolName: "Write",
406
+ toolInput: { file_path: "test/auth.test.ts" },
407
+ toolOutput: "Added token validation tests covering expired, malformed, and valid cases."
408
+ },
409
+ {
410
+ toolName: "Bash",
411
+ toolInput: { command: "npm test" },
412
+ toolOutput: "All 12 auth tests passing."
413
+ }
414
+ ]
415
+ },
416
+ {
417
+ id: generateId("demo"),
418
+ title: "Session 2: Database migration debugging",
419
+ observations: [{
420
+ toolName: "Read",
421
+ toolInput: { file_path: "prisma/schema.prisma" },
422
+ toolOutput: "Found N+1 query issue in user relations. Need to add include on posts query."
423
+ }, {
424
+ toolName: "Edit",
425
+ toolInput: { file_path: "src/api/users.ts" },
426
+ toolOutput: "Fixed N+1 by adding Prisma include. Query time dropped from 450ms to 28ms."
427
+ }]
428
+ },
429
+ {
430
+ id: generateId("demo"),
431
+ title: "Session 3: Rate limiting",
432
+ observations: [{
433
+ toolName: "Write",
434
+ toolInput: { file_path: "src/middleware/ratelimit.ts" },
435
+ toolOutput: "Added rate limiting middleware with 100 req/min default. Uses in-memory store for dev, Redis for prod."
436
+ }]
437
+ }
438
+ ];
439
+ }
440
+ async function postJson(url, body, timeoutMs = 5e3) {
441
+ try {
442
+ const res = await fetch(url, {
443
+ method: "POST",
444
+ headers: { "Content-Type": "application/json" },
445
+ body: JSON.stringify(body),
446
+ signal: AbortSignal.timeout(timeoutMs)
447
+ });
448
+ if (!res.ok) return null;
449
+ return await res.json().catch(() => null);
450
+ } catch {
451
+ return null;
452
+ }
453
+ }
454
+ async function postJsonStrict(url, body, timeoutMs = 5e3) {
455
+ const res = await fetch(url, {
456
+ method: "POST",
457
+ headers: { "Content-Type": "application/json" },
458
+ body: JSON.stringify(body),
459
+ signal: AbortSignal.timeout(timeoutMs)
460
+ });
461
+ if (!res.ok) {
462
+ const errBody = await res.text().catch(() => "");
463
+ const suffix = errBody ? ` — ${errBody.slice(0, 200)}` : "";
464
+ throw new Error(`POST ${url} failed: ${res.status} ${res.statusText}${suffix}`);
465
+ }
466
+ return await res.json().catch(() => null);
467
+ }
468
+ async function seedDemoSession(base, project, session) {
469
+ await postJsonStrict(`${base}/agentmemory/session/start`, {
470
+ sessionId: session.id,
471
+ project,
472
+ cwd: project
473
+ });
474
+ let stored = 0;
475
+ for (const obs of session.observations) {
476
+ const url = `${base}/agentmemory/observe`;
477
+ const payload = {
478
+ hookType: "post_tool_use",
479
+ sessionId: session.id,
480
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
481
+ data: {
482
+ tool_name: obs.toolName,
483
+ tool_input: obs.toolInput,
484
+ tool_output: obs.toolOutput
485
+ }
486
+ };
487
+ try {
488
+ const res = await fetch(url, {
489
+ method: "POST",
490
+ headers: { "Content-Type": "application/json" },
491
+ body: JSON.stringify(payload),
492
+ signal: AbortSignal.timeout(5e3)
493
+ });
494
+ if (res.ok) stored++;
495
+ else {
496
+ const body = await res.text().catch(() => "");
497
+ p.log.warn(`observe failed for ${obs.toolName}: ${res.status} ${res.statusText}${body ? ` — ${body.slice(0, 160)}` : ""}`);
498
+ }
499
+ } catch (err) {
500
+ p.log.warn(`observe request failed for ${obs.toolName}: ${err instanceof Error ? err.message : String(err)}`);
501
+ }
502
+ }
503
+ await postJsonStrict(`${base}/agentmemory/session/end`, { sessionId: session.id });
504
+ return stored;
505
+ }
506
+ async function runDemoSearch(base, query) {
507
+ const items = (await postJson(`${base}/agentmemory/smart-search`, {
508
+ query,
509
+ limit: 5
510
+ }, 1e4))?.results ?? [];
511
+ return {
512
+ query,
513
+ hits: items.length,
514
+ topTitle: items[0]?.title ?? "(no results)"
515
+ };
516
+ }
517
+ async function runDemo() {
518
+ const port = getRestPort();
519
+ const base = `http://localhost:${port}`;
520
+ p.intro("agentmemory demo");
521
+ if (!await isEngineRunning()) {
522
+ p.log.error(`Not running — no response on port ${port}`);
523
+ p.log.info("Start the server first: npx @agentmemory/agentmemory");
524
+ process.exit(1);
525
+ }
526
+ const demoProject = "/tmp/agentmemory-demo";
527
+ const sessions = buildDemoSessions();
528
+ const sSeed = p.spinner();
529
+ sSeed.start("Seeding 3 demo sessions with realistic observations...");
530
+ let totalObs = 0;
531
+ for (const session of sessions) totalObs += await seedDemoSession(base, demoProject, session);
532
+ sSeed.stop(`Seeded ${totalObs} observations across ${sessions.length} sessions`);
533
+ const queries = [
534
+ "jwt auth middleware",
535
+ "database performance optimization",
536
+ "rate limiting"
537
+ ];
538
+ const sQuery = p.spinner();
539
+ sQuery.start(`Running ${queries.length} smart-search queries...`);
540
+ const results = [];
541
+ for (const query of queries) results.push(await runDemoSearch(base, query));
542
+ sQuery.stop("Search complete");
543
+ const lines = [
544
+ `Project: ${demoProject}`,
545
+ `Sessions: ${sessions.length} seeded (${totalObs} observations)`,
546
+ "",
547
+ "Search results:",
548
+ ...results.flatMap((r) => [` "${r.query}"`, ` → ${r.hits} hit(s), top: ${r.topTitle.slice(0, 60)}`]),
549
+ "",
550
+ `Notice: searching "database performance optimization"`,
551
+ `found the N+1 query fix — keyword matching can't do that.`,
552
+ "",
553
+ `Viewer: http://localhost:${port + 2}`,
554
+ `Clean up with: curl -X DELETE "${base}/agentmemory/sessions?project=${demoProject}"`
555
+ ];
556
+ p.note(lines.join("\n"), "demo complete");
557
+ p.log.success("agentmemory is working. Point your agent at it and get back to coding.");
558
+ }
559
+ async function runMcp() {
560
+ await import("./standalone-CxAvUMQk.mjs");
561
+ }
562
+ ({
563
+ status: runStatus,
564
+ demo: runDemo,
565
+ mcp: runMcp
566
+ }[args[0] ?? ""] ?? main)().catch((err) => {
243
567
  p.log.error(err instanceof Error ? err.message : String(err));
244
568
  process.exit(1);
245
569
  });
246
570
 
247
571
  //#endregion
248
- export { };
572
+ export { jaccardSimilarity as a, generateId as i, STREAM as n, fingerprintId as r, KV as t };
249
573
  //# sourceMappingURL=cli.mjs.map