@agentmemory/agentmemory 0.9.2 → 0.9.4

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/README.md CHANGED
@@ -8,7 +8,7 @@
8
8
  </p>
9
9
 
10
10
  <p align="center">
11
- <a href="https://gist.github.com/rohitg00/2067ab416f7bbe447c1977edaaa681e2"><img src="https://img.shields.io/badge/Viral%20GitHub%20Gist-825%20stars%20%2F%20110%20forks-FF6B35?style=for-the-badge&logo=github&logoColor=white&labelColor=1a1a1a" alt="Design doc: 825 stars / 110 forks on the gist" /></a>
11
+ <a href="https://gist.github.com/rohitg00/2067ab416f7bbe447c1977edaaa681e2"><img src="https://img.shields.io/badge/Viral%20GitHub%20Gist-1050%20stars%20%2F%20150%20forks-FF6B35?style=for-the-badge&logo=github&logoColor=white&labelColor=1a1a1a" alt="Design doc: 1050 stars / 150 forks on the gist" /></a>
12
12
  </p>
13
13
 
14
14
  <p align="center">
@@ -370,7 +370,7 @@ mcp_servers:
370
370
  command: npx
371
371
  args: ["-y", "@agentmemory/mcp"]
372
372
 
373
- Verify with `curl http://localhost:3111/agentmemory/health`. Open http://localhost:3113 for the real-time viewer. For deeper 6-hook memory provider integration (pre-LLM context injection, turn capture, MEMORY.md mirroring, system prompt block), copy integrations/hermes from the agentmemory repo to ~/.hermes/plugins/memory/agentmemory.
373
+ Verify with `curl http://localhost:3111/agentmemory/health`. Open http://localhost:3113 for the real-time viewer. For deeper 6-hook memory provider integration (pre-LLM context injection, turn capture, MEMORY.md mirroring, system prompt block), copy integrations/hermes from the agentmemory repo to ~/.hermes/plugins/agentmemory.
374
374
  ```
375
375
 
376
376
  Full guide: [`integrations/hermes/`](integrations/hermes/)
@@ -506,7 +506,12 @@ PostToolUse hook fires
506
506
  -> Store raw observation
507
507
  -> LLM compress -> structured facts + concepts + narrative
508
508
  -> Vector embedding (6 providers + local)
509
- -> Index in BM25 + vector + knowledge graph
509
+ -> Index in BM25 + vector
510
+
511
+ Stop / SessionEnd hook fires
512
+ -> Summarize session
513
+ -> Knowledge graph extraction (if GRAPH_EXTRACTION_ENABLED=true)
514
+ -> Slot reflection (if SLOT_REFLECT_ENABLED=true)
510
515
 
511
516
  SessionStart hook fires
512
517
  -> Load project profile (top concepts, files, patterns)
package/dist/cli.mjs CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import { execFileSync, spawn, spawnSync } from "node:child_process";
3
- import { existsSync } from "node:fs";
3
+ import { existsSync, readFileSync, readdirSync, readlinkSync, statSync } from "node:fs";
4
4
  import { delimiter, dirname, join } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
- import { platform } from "node:os";
6
+ import { homedir, platform } from "node:os";
7
7
  import * as p from "@clack/prompts";
8
8
  import { createHash } from "node:crypto";
9
9
 
@@ -92,11 +92,14 @@ Usage: agentmemory [command] [options]
92
92
 
93
93
  Commands:
94
94
  (default) Start agentmemory worker
95
- status Show connection status, memory count, and health
95
+ status Show connection status, memory count, flags, and health
96
+ doctor Run diagnostic checks (server, flags, graph, providers)
96
97
  demo Seed sample sessions and show recall in action
97
98
  upgrade Upgrade local deps + iii runtime (best effort)
98
99
  mcp Start standalone MCP server (no engine required)
99
100
  import-jsonl [p] Import Claude Code JSONL transcripts (default: ~/.claude/projects)
101
+ --max-files <N> | --max-files=<N>: override scan cap (default 200, max 1000;
102
+ out-of-range is rejected; for trees >1000 files, batch by subdirectory)
100
103
 
101
104
  Options:
102
105
  --help, -h Show this help
@@ -105,10 +108,15 @@ Options:
105
108
  --no-engine Skip auto-starting iii-engine
106
109
  --port <N> Override REST port (default: 3111)
107
110
 
111
+ Environment:
112
+ AGENTMEMORY_URL Full REST base URL (e.g. http://localhost:3111).
113
+ Honored by status, doctor, and MCP shim commands.
114
+
108
115
  Quick start:
109
116
  npx @agentmemory/agentmemory # start with local iii-engine or Docker
110
- npx @agentmemory/agentmemory status # check health
111
- npx @agentmemory/agentmemory demo # try it in 30 seconds (needs server running)
117
+ npx @agentmemory/agentmemory demo # see semantic recall in 30 seconds
118
+ npx @agentmemory/agentmemory doctor # diagnose config + feature flags
119
+ npx @agentmemory/agentmemory status # health + memory count + flags
112
120
  npx @agentmemory/agentmemory upgrade # upgrade agentmemory + iii runtime
113
121
  npx @agentmemory/agentmemory mcp # standalone MCP server (no engine)
114
122
  npx @agentmemory/mcp # same as above (shim package)
@@ -121,11 +129,32 @@ const portIdx = args.indexOf("--port");
121
129
  if (portIdx !== -1 && args[portIdx + 1]) process.env["III_REST_PORT"] = args[portIdx + 1];
122
130
  const skipEngine = args.includes("--no-engine");
123
131
  function getRestPort() {
132
+ const url = process.env["AGENTMEMORY_URL"];
133
+ if (url) try {
134
+ const parsed = new URL(url).port;
135
+ if (parsed) return parseInt(parsed, 10);
136
+ } catch {}
124
137
  return parseInt(process.env["III_REST_PORT"] || "3111", 10) || 3111;
125
138
  }
139
+ function getBaseUrl() {
140
+ const url = process.env["AGENTMEMORY_URL"];
141
+ if (url) return url.replace(/\/+$/, "");
142
+ return `http://localhost:${getRestPort()}`;
143
+ }
144
+ function getViewerUrl() {
145
+ const envUrl = process.env["AGENTMEMORY_VIEWER_URL"];
146
+ if (envUrl) return envUrl.replace(/\/+$/, "");
147
+ try {
148
+ const u = new URL(getBaseUrl());
149
+ const vPort = (parseInt(u.port || "3111", 10) || 3111) + 2;
150
+ return `${u.protocol}//${u.hostname}:${vPort}`;
151
+ } catch {
152
+ return `http://localhost:${getRestPort() + 2}`;
153
+ }
154
+ }
126
155
  async function isEngineRunning() {
127
156
  try {
128
- await fetch(`http://localhost:${getRestPort()}/`, { signal: AbortSignal.timeout(2e3) });
157
+ await fetch(`${getBaseUrl()}/`, { signal: AbortSignal.timeout(2e3) });
129
158
  return true;
130
159
  } catch {
131
160
  return false;
@@ -133,7 +162,7 @@ async function isEngineRunning() {
133
162
  }
134
163
  async function isAgentmemoryReady() {
135
164
  try {
136
- return (await fetch(`http://localhost:${getRestPort()}/agentmemory/livez`, { signal: AbortSignal.timeout(2e3) })).ok;
165
+ return (await fetch(`${getBaseUrl()}/agentmemory/livez`, { signal: AbortSignal.timeout(2e3) })).ok;
137
166
  } catch {
138
167
  return false;
139
168
  }
@@ -299,12 +328,12 @@ async function main() {
299
328
  p.intro("agentmemory");
300
329
  if (skipEngine) {
301
330
  p.log.info("Skipping engine check (--no-engine)");
302
- await import("./src-tmuZyobT.mjs");
331
+ await import("./src-uDy2jLO-.mjs");
303
332
  return;
304
333
  }
305
334
  if (await isEngineRunning()) {
306
335
  p.log.success("iii-engine is running");
307
- await import("./src-tmuZyobT.mjs");
336
+ await import("./src-uDy2jLO-.mjs");
308
337
  return;
309
338
  }
310
339
  if (!await startEngine()) {
@@ -348,30 +377,38 @@ async function main() {
348
377
  process.exit(1);
349
378
  }
350
379
  s.stop("iii-engine is ready");
351
- await import("./src-tmuZyobT.mjs");
380
+ await import("./src-uDy2jLO-.mjs");
381
+ }
382
+ async function apiFetch(base, path, timeoutMs = 5e3) {
383
+ try {
384
+ return await (await fetch(`${base}/agentmemory/${path}`, { signal: AbortSignal.timeout(timeoutMs) })).json();
385
+ } catch {
386
+ return null;
387
+ }
352
388
  }
353
389
  async function runStatus() {
354
- const port = getRestPort();
355
- const base = `http://localhost:${port}`;
390
+ getRestPort();
391
+ const base = getBaseUrl();
356
392
  p.intro("agentmemory status");
357
393
  if (!await isEngineRunning()) {
358
- p.log.error(`Not running — no response on port ${port}`);
394
+ p.log.error(`Not running — no response at ${base}`);
359
395
  p.log.info("Start with: npx @agentmemory/agentmemory");
360
396
  process.exit(1);
361
397
  }
362
398
  try {
363
- const [healthRes, sessionsRes, graphRes, memoriesRes] = await Promise.all([
364
- fetch(`${base}/agentmemory/health`, { signal: AbortSignal.timeout(5e3) }).then((r) => r.json()).catch(() => null),
365
- fetch(`${base}/agentmemory/sessions`, { signal: AbortSignal.timeout(5e3) }).then((r) => r.json()).catch(() => null),
366
- fetch(`${base}/agentmemory/graph/stats`, { signal: AbortSignal.timeout(5e3) }).then((r) => r.json()).catch(() => null),
367
- fetch(`${base}/agentmemory/export`, { signal: AbortSignal.timeout(5e3) }).then((r) => r.json()).catch(() => null)
399
+ const [healthRes, sessionsRes, graphRes, memoriesRes, flagsRes] = await Promise.all([
400
+ apiFetch(base, "health"),
401
+ apiFetch(base, "sessions"),
402
+ apiFetch(base, "graph/stats"),
403
+ apiFetch(base, "export"),
404
+ apiFetch(base, "config/flags")
368
405
  ]);
369
406
  const h = healthRes?.health;
370
407
  const status = healthRes?.status || "unknown";
371
408
  const version = healthRes?.version || "?";
372
409
  const sessions = Array.isArray(sessionsRes?.sessions) ? sessionsRes.sessions.length : 0;
373
- const nodes = graphRes?.nodes || 0;
374
- const edges = graphRes?.edges || 0;
410
+ const nodes = Number(graphRes?.totalNodes ?? graphRes?.nodes ?? graphRes?.nodeCount ?? 0);
411
+ const edges = Number(graphRes?.totalEdges ?? graphRes?.edges ?? graphRes?.edgeCount ?? 0);
375
412
  const cb = healthRes?.circuitBreaker?.state || "closed";
376
413
  const heapMB = h?.memory ? Math.round(h.memory.heapUsed / 1048576) : 0;
377
414
  const uptime = h?.uptimeSeconds ? Math.round(h.uptimeSeconds) : 0;
@@ -381,7 +418,7 @@ async function runStatus() {
381
418
  const estInjectedTokens = Math.min(obsCount, 50) * 38;
382
419
  const tokensSaved = estFullTokens - estInjectedTokens;
383
420
  const pctSaved = estFullTokens > 0 ? Math.round(tokensSaved / estFullTokens * 100) : 0;
384
- p.log.success(`Connected — v${version} on port ${port}`);
421
+ p.log.success(`Connected — v${version} at ${base}`);
385
422
  const lines = [
386
423
  `Health: ${status === "healthy" ? "✓ healthy" : status}`,
387
424
  `Sessions: ${sessions}`,
@@ -391,7 +428,7 @@ async function runStatus() {
391
428
  `Circuit: ${cb}`,
392
429
  `Heap: ${heapMB} MB`,
393
430
  `Uptime: ${uptime}s`,
394
- `Viewer: http://localhost:${port + 2}`
431
+ `Viewer: ${getViewerUrl()}`
395
432
  ];
396
433
  if (obsCount > 0) {
397
434
  lines.push("");
@@ -399,12 +436,143 @@ async function runStatus() {
399
436
  lines.push(` Full context: ~${estFullTokens.toLocaleString()} tokens`);
400
437
  lines.push(` Injected: ~${estInjectedTokens.toLocaleString()} tokens`);
401
438
  }
439
+ if (flagsRes) {
440
+ const provider = flagsRes.provider === "llm" ? "✓ llm" : "✗ noop (no key)";
441
+ const embed = flagsRes.embeddingProvider === "embeddings" ? "✓ embeddings" : "bm25-only";
442
+ const flagRows = (flagsRes.flags || []).map((f) => ` ${f.enabled ? "✓" : "✗"} ${f.key.padEnd(32)} ${f.label}`);
443
+ lines.push("");
444
+ lines.push(`Provider: ${provider}`);
445
+ lines.push(`Embeddings: ${embed}`);
446
+ lines.push(`Flags:`);
447
+ flagRows.forEach((r) => lines.push(r));
448
+ }
402
449
  p.note(lines.join("\n"), "agentmemory");
403
450
  } catch (err) {
404
451
  p.log.error(err instanceof Error ? err.message : String(err));
405
452
  process.exit(1);
406
453
  }
407
454
  }
455
+ function formatChecks(checks) {
456
+ return checks.map((c) => `${c.ok ? "✓" : "✗"} ${c.name}${c.hint ? `\n ${c.hint}` : ""}`).join("\n");
457
+ }
458
+ function findLatestDebugLog(debugDir) {
459
+ const latestLink = join(debugDir, "latest");
460
+ try {
461
+ if (existsSync(latestLink)) {
462
+ const target = readlinkSync(latestLink);
463
+ const resolved = target.startsWith("/") ? target : join(debugDir, target);
464
+ if (existsSync(resolved)) return resolved;
465
+ }
466
+ } catch {}
467
+ try {
468
+ const newest = readdirSync(debugDir).filter((f) => f.endsWith(".txt")).map((f) => ({
469
+ f,
470
+ m: statSync(join(debugDir, f)).mtimeMs
471
+ })).sort((a, b) => b.m - a.m)[0];
472
+ if (newest) return join(debugDir, newest.f);
473
+ } catch {}
474
+ }
475
+ function checkClaudeCodeHooks() {
476
+ const debugDir = join(homedir(), ".claude", "debug");
477
+ if (!existsSync(debugDir)) return { state: "no-cc-dir" };
478
+ const logPath = findLatestDebugLog(debugDir);
479
+ if (!logPath) return { state: "no-debug-log" };
480
+ let content;
481
+ try {
482
+ content = readFileSync(logPath, "utf8");
483
+ } catch {
484
+ return { state: "no-debug-log" };
485
+ }
486
+ const match = content.match(/Loaded hooks from standard location for plugin agentmemory:\s*(\S+)/);
487
+ if (match) return {
488
+ state: "loaded",
489
+ manifestPath: match[1]
490
+ };
491
+ if (content.includes("Loading hooks from plugin: agentmemory")) return { state: "loaded" };
492
+ return { state: "not-loaded" };
493
+ }
494
+ async function runDoctor() {
495
+ p.intro("agentmemory doctor");
496
+ const base = getBaseUrl();
497
+ const viewerUrl = getViewerUrl();
498
+ const checks = [];
499
+ const serverUp = await isEngineRunning();
500
+ checks.push({
501
+ name: "Server reachable",
502
+ ok: serverUp,
503
+ hint: serverUp ? void 0 : `Start with: npx @agentmemory/agentmemory (tried ${base})`
504
+ });
505
+ if (!serverUp) {
506
+ p.note(formatChecks(checks), "server unreachable");
507
+ process.exit(1);
508
+ }
509
+ const [health, flags, graph] = await Promise.all([
510
+ apiFetch(base, "health", 3e3),
511
+ apiFetch(base, "config/flags", 3e3),
512
+ apiFetch(base, "graph/stats", 3e3)
513
+ ]);
514
+ const viewerUp = await fetch(viewerUrl, { signal: AbortSignal.timeout(2e3) }).then((r) => r.ok).catch(() => false);
515
+ const hasLlm = flags?.provider === "llm";
516
+ const hasEmbed = flags?.embeddingProvider === "embeddings";
517
+ const graphHas = Number(graph?.totalNodes ?? graph?.nodes ?? graph?.nodeCount ?? 0) > 0;
518
+ checks.push({
519
+ name: "Health status",
520
+ ok: health?.status === "healthy",
521
+ hint: health?.status === "healthy" ? void 0 : `Status: ${health?.status || "unknown"}`
522
+ }, {
523
+ name: "Viewer reachable",
524
+ ok: viewerUp,
525
+ hint: viewerUp ? void 0 : `${viewerUrl} not responding`
526
+ }, {
527
+ name: "LLM provider",
528
+ ok: hasLlm,
529
+ hint: hasLlm ? void 0 : "export ANTHROPIC_API_KEY=sk-ant-... (or GEMINI/OPENROUTER/MINIMAX) then restart"
530
+ }, {
531
+ name: "Embedding provider",
532
+ ok: hasEmbed,
533
+ hint: hasEmbed ? void 0 : "Running BM25-only. Add OPENAI_API_KEY / VOYAGE_API_KEY / COHERE_API_KEY / OLLAMA_HOST for semantic recall"
534
+ });
535
+ for (const f of flags?.flags || []) checks.push({
536
+ name: f.label,
537
+ ok: f.enabled,
538
+ hint: f.enabled ? void 0 : f.enableHow
539
+ });
540
+ const cc = checkClaudeCodeHooks();
541
+ const ccCheck = (() => {
542
+ switch (cc.state) {
543
+ case "loaded": return {
544
+ ok: true,
545
+ hint: cc.manifestPath ? `manifest: ${cc.manifestPath}` : void 0
546
+ };
547
+ case "not-loaded": return {
548
+ ok: false,
549
+ hint: "Plugin enabled but hooks not loaded by Claude Code. Try: /plugin uninstall agentmemory@agentmemory && /plugin install agentmemory@agentmemory, then restart the session. CC must be >= 2.1.x for plugin-hook auto-load."
550
+ };
551
+ case "no-debug-log": return {
552
+ ok: false,
553
+ hint: "Cannot verify — no Claude Code debug log found. Run once with `claude --debug -p \"x\"`, then re-run doctor."
554
+ };
555
+ case "no-cc-dir": return;
556
+ }
557
+ })();
558
+ if (ccCheck) checks.push({
559
+ name: "Claude Code plugin hooks registered",
560
+ ...ccCheck
561
+ });
562
+ checks.push({
563
+ name: "Knowledge graph populated",
564
+ ok: graphHas,
565
+ hint: graphHas ? void 0 : "Graph is empty. Run a session with GRAPH_EXTRACTION_ENABLED=true, or POST /agentmemory/graph/extract"
566
+ });
567
+ const passed = checks.filter((c) => c.ok).length;
568
+ const total = checks.length;
569
+ p.note(formatChecks(checks), `${passed}/${total} checks passing`);
570
+ if (passed === total) p.outro("✓ All checks passed. agentmemory is healthy.");
571
+ else {
572
+ p.outro(`${total - passed} issue(s) — follow hints above to fix.`);
573
+ process.exit(1);
574
+ }
575
+ }
408
576
  function buildDemoSessions() {
409
577
  return [
410
578
  {
@@ -565,7 +733,7 @@ async function runDemo() {
565
733
  `Notice: searching "database performance optimization"`,
566
734
  `found the N+1 query fix — keyword matching can't do that.`,
567
735
  "",
568
- `Viewer: http://localhost:${port + 2}`,
736
+ `Viewer: ${getViewerUrl()}`,
569
737
  `Clean up with: curl -X DELETE "${base}/agentmemory/sessions?project=${demoProject}"`
570
738
  ];
571
739
  p.note(lines.join("\n"), "demo complete");
@@ -657,10 +825,38 @@ async function runUpgrade() {
657
825
  ].join("\n"), "agentmemory upgrade");
658
826
  }
659
827
  async function runMcp() {
660
- await import("./standalone-BiwX0rdC.mjs");
828
+ await import("./standalone-CqqEcfNR.mjs");
661
829
  }
662
830
  async function runImportJsonl() {
663
- const pathArg = args.slice(1).filter((a) => !a.startsWith("-"))[0];
831
+ const VALUE_FLAGS = new Set(["--port", "--tools"]);
832
+ let maxFiles;
833
+ const tail = args.slice(1);
834
+ const positional = [];
835
+ for (let i = 0; i < tail.length; i++) {
836
+ const a = tail[i];
837
+ if (a === "--max-files") {
838
+ const raw = tail[i + 1];
839
+ const parsed = raw !== void 0 ? parseInt(raw, 10) : NaN;
840
+ if (Number.isInteger(parsed) && parsed > 0) maxFiles = parsed;
841
+ else if (raw !== void 0) p.log.warn(`Ignoring --max-files ${raw}: expected a positive integer.`);
842
+ i++;
843
+ continue;
844
+ }
845
+ if (a.startsWith("--max-files=")) {
846
+ const raw = a.slice(12);
847
+ const parsed = parseInt(raw, 10);
848
+ if (Number.isInteger(parsed) && parsed > 0) maxFiles = parsed;
849
+ else p.log.warn(`Ignoring --max-files=${raw}: expected a positive integer.`);
850
+ continue;
851
+ }
852
+ if (VALUE_FLAGS.has(a)) {
853
+ i++;
854
+ continue;
855
+ }
856
+ if (a.startsWith("-")) continue;
857
+ positional.push(a);
858
+ }
859
+ const pathArg = positional[0];
664
860
  const port = getRestPort();
665
861
  const base = `http://localhost:${port}`;
666
862
  let probeOk = false;
@@ -682,6 +878,7 @@ async function runImportJsonl() {
682
878
  }
683
879
  const body = {};
684
880
  if (pathArg) body["path"] = pathArg;
881
+ if (maxFiles !== void 0) body["maxFiles"] = maxFiles;
685
882
  const headers = { "content-type": "application/json" };
686
883
  const secret = process.env["AGENTMEMORY_SECRET"];
687
884
  if (secret) headers["authorization"] = `Bearer ${secret}`;
@@ -713,7 +910,18 @@ async function runImportJsonl() {
713
910
  process.exit(1);
714
911
  }
715
912
  spinner.stop(`imported ${json.imported ?? 0} file(s), ${json.observations ?? 0} observation(s) across ${json.sessionIds?.length || 0} session(s)`);
716
- if (json.sessionIds && json.sessionIds.length > 0) p.log.info(`View at http://localhost:${port + 2} → Replay tab`);
913
+ if (json.truncated) {
914
+ const cap = json.maxFiles ?? 200;
915
+ const upper = json.maxFilesUpperBound ?? 1e3;
916
+ const discovered = json.discovered ?? 0;
917
+ const baseMsg = `Hit the ${cap}-file scan cap; ${discovered - (json.imported ?? 0)} of ${json.traversalCapped ? `${discovered}+ (traversal halted at safety cap)` : String(discovered)} discovered file(s) were skipped.`;
918
+ if (discovered > upper || json.traversalCapped) p.log.warn(`${baseMsg} Tree exceeds the server's --max-files limit of ${upper}; batch by subdirectory (run import-jsonl once per project under ~/.claude/projects).`);
919
+ else {
920
+ const suggested = Math.min(Math.max((discovered || cap) + 100, cap * 2), upper);
921
+ p.log.warn(`${baseMsg} Re-run with --max-files=${suggested} (max ${upper}) or batch by subdirectory.`);
922
+ }
923
+ }
924
+ if (json.sessionIds && json.sessionIds.length > 0) p.log.info(`View at ${getViewerUrl()} → Replay tab`);
717
925
  } catch (err) {
718
926
  spinner.stop("failed");
719
927
  if (err instanceof Error && err.name === "TimeoutError") p.log.error("import timed out after 2 minutes");
@@ -723,6 +931,7 @@ async function runImportJsonl() {
723
931
  }
724
932
  ({
725
933
  status: runStatus,
934
+ doctor: runDoctor,
726
935
  demo: runDemo,
727
936
  upgrade: runUpgrade,
728
937
  mcp: runMcp,