@agentmemory/agentmemory 0.9.27 → 0.9.29

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 (156) hide show
  1. package/.env.example +15 -6
  2. package/AGENTS.md +7 -7
  3. package/README.md +292 -164
  4. package/dist/.env.example +15 -6
  5. package/dist/cli.d.mts.map +1 -1
  6. package/dist/cli.mjs +427 -177
  7. package/dist/cli.mjs.map +1 -1
  8. package/dist/{connect-bmZ5eqYN.mjs → connect-Qaeq15TQ.mjs} +717 -65
  9. package/dist/connect-Qaeq15TQ.mjs.map +1 -0
  10. package/dist/docker-compose.yml +2 -2
  11. package/dist/hooks/antigravity-bridge.d.mts +8 -0
  12. package/dist/hooks/antigravity-bridge.d.mts.map +1 -0
  13. package/dist/hooks/antigravity-bridge.mjs +114 -0
  14. package/dist/hooks/antigravity-bridge.mjs.map +1 -0
  15. package/dist/hooks/notification.mjs +21 -5
  16. package/dist/hooks/notification.mjs.map +1 -1
  17. package/dist/hooks/post-commit.mjs +15 -2
  18. package/dist/hooks/post-commit.mjs.map +1 -1
  19. package/dist/hooks/post-tool-failure.mjs +16 -4
  20. package/dist/hooks/post-tool-failure.mjs.map +1 -1
  21. package/dist/hooks/post-tool-use.mjs +16 -4
  22. package/dist/hooks/post-tool-use.mjs.map +1 -1
  23. package/dist/hooks/pre-compact.mjs +14 -3
  24. package/dist/hooks/pre-compact.mjs.map +1 -1
  25. package/dist/hooks/pre-tool-use.mjs +3 -2
  26. package/dist/hooks/pre-tool-use.mjs.map +1 -1
  27. package/dist/hooks/prompt-submit.mjs +16 -4
  28. package/dist/hooks/prompt-submit.mjs.map +1 -1
  29. package/dist/hooks/session-end.mjs +84 -19
  30. package/dist/hooks/session-end.mjs.map +1 -1
  31. package/dist/hooks/session-start.mjs +24 -5
  32. package/dist/hooks/session-start.mjs.map +1 -1
  33. package/dist/hooks/stop.mjs +3 -8
  34. package/dist/hooks/stop.mjs.map +1 -1
  35. package/dist/hooks/subagent-start.mjs +16 -4
  36. package/dist/hooks/subagent-start.mjs.map +1 -1
  37. package/dist/hooks/subagent-stop.mjs +16 -4
  38. package/dist/hooks/subagent-stop.mjs.map +1 -1
  39. package/dist/hooks/task-completed.mjs +16 -4
  40. package/dist/hooks/task-completed.mjs.map +1 -1
  41. package/dist/{image-refs-B5Zf_Djo.mjs → image-refs-BKLmp_sD.mjs} +4 -22
  42. package/dist/image-refs-BKLmp_sD.mjs.map +1 -0
  43. package/dist/image-refs-CJgZmb9X.mjs +34 -0
  44. package/dist/image-refs-CJgZmb9X.mjs.map +1 -0
  45. package/dist/index.mjs +1681 -911
  46. package/dist/index.mjs.map +1 -1
  47. package/dist/{image-store-Gpo2mgM9.mjs → keyed-mutex-DTHtcZrt.mjs} +14 -11
  48. package/dist/keyed-mutex-DTHtcZrt.mjs.map +1 -0
  49. package/dist/{logger-yHTcEBAI.mjs → logger-BEB7pCI9.mjs} +1 -1
  50. package/dist/{logger-yHTcEBAI.mjs.map → logger-BEB7pCI9.mjs.map} +1 -1
  51. package/dist/schema-BFZhNTF7.mjs +197 -0
  52. package/dist/schema-BFZhNTF7.mjs.map +1 -0
  53. package/dist/{src-MOg2zKJs.mjs → src-G7yt8gGm.mjs} +1521 -854
  54. package/dist/src-G7yt8gGm.mjs.map +1 -0
  55. package/dist/{standalone-BORXC71-.mjs → standalone-BqNJDJ04.mjs} +30 -16
  56. package/dist/standalone-BqNJDJ04.mjs.map +1 -0
  57. package/dist/standalone.d.mts.map +1 -1
  58. package/dist/standalone.mjs +55 -15
  59. package/dist/standalone.mjs.map +1 -1
  60. package/dist/{tools-registry-D8XVVPbJ.mjs → tools-registry-BV_2bu7z.mjs} +55 -17
  61. package/dist/tools-registry-BV_2bu7z.mjs.map +1 -0
  62. package/dist/viewer/favicon.svg +35 -1
  63. package/dist/viewer/index.html +473 -83
  64. package/docker-compose.yml +2 -2
  65. package/integrations/pi/README.md +77 -0
  66. package/integrations/pi/index.ts +403 -0
  67. package/integrations/pi/package.json +32 -0
  68. package/integrations/pi/security.ts +35 -0
  69. package/package.json +6 -4
  70. package/plugin/.claude-plugin/plugin.json +2 -2
  71. package/plugin/.codex-plugin/plugin.json +2 -2
  72. package/plugin/.devin-plugin/plugin.json +32 -0
  73. package/plugin/cursor/hooks.json +41 -0
  74. package/plugin/cursor/mcp.json +12 -0
  75. package/plugin/hooks/hooks.antigravity.json +43 -0
  76. package/plugin/hooks/hooks.devin.json +67 -0
  77. package/plugin/hooks/hooks.droid.json +55 -0
  78. package/plugin/opencode/README.md +1 -1
  79. package/plugin/opencode/agentmemory-capture.ts +73 -13
  80. package/plugin/opencode/plugin.json +1 -1
  81. package/plugin/plugin.json +2 -2
  82. package/plugin/scripts/antigravity-bridge.d.mts +8 -0
  83. package/plugin/scripts/antigravity-bridge.d.mts.map +1 -0
  84. package/plugin/scripts/antigravity-bridge.mjs +114 -0
  85. package/plugin/scripts/antigravity-bridge.mjs.map +1 -0
  86. package/plugin/scripts/notification.mjs +21 -5
  87. package/plugin/scripts/notification.mjs.map +1 -1
  88. package/plugin/scripts/post-commit.mjs +15 -2
  89. package/plugin/scripts/post-commit.mjs.map +1 -1
  90. package/plugin/scripts/post-tool-failure.mjs +16 -4
  91. package/plugin/scripts/post-tool-failure.mjs.map +1 -1
  92. package/plugin/scripts/post-tool-use.mjs +16 -4
  93. package/plugin/scripts/post-tool-use.mjs.map +1 -1
  94. package/plugin/scripts/pre-compact.mjs +14 -3
  95. package/plugin/scripts/pre-compact.mjs.map +1 -1
  96. package/plugin/scripts/pre-tool-use.mjs +3 -2
  97. package/plugin/scripts/pre-tool-use.mjs.map +1 -1
  98. package/plugin/scripts/prompt-submit.mjs +16 -4
  99. package/plugin/scripts/prompt-submit.mjs.map +1 -1
  100. package/plugin/scripts/session-end.mjs +84 -19
  101. package/plugin/scripts/session-end.mjs.map +1 -1
  102. package/plugin/scripts/session-start.mjs +24 -5
  103. package/plugin/scripts/session-start.mjs.map +1 -1
  104. package/plugin/scripts/stop.mjs +3 -8
  105. package/plugin/scripts/stop.mjs.map +1 -1
  106. package/plugin/scripts/subagent-start.mjs +16 -4
  107. package/plugin/scripts/subagent-start.mjs.map +1 -1
  108. package/plugin/scripts/subagent-stop.mjs +16 -4
  109. package/plugin/scripts/subagent-stop.mjs.map +1 -1
  110. package/plugin/scripts/task-completed.mjs +16 -4
  111. package/plugin/scripts/task-completed.mjs.map +1 -1
  112. package/plugin/skills/_shared/TROUBLESHOOTING.md +38 -0
  113. package/plugin/skills/agentmemory-agents/REFERENCE.md +31 -0
  114. package/plugin/skills/agentmemory-agents/SKILL.md +34 -0
  115. package/plugin/skills/agentmemory-architecture/SKILL.md +33 -0
  116. package/plugin/skills/agentmemory-config/REFERENCE.md +45 -0
  117. package/plugin/skills/agentmemory-config/SKILL.md +37 -0
  118. package/plugin/skills/agentmemory-hooks/REFERENCE.md +20 -0
  119. package/plugin/skills/agentmemory-hooks/SKILL.md +39 -0
  120. package/plugin/skills/agentmemory-mcp-tools/REFERENCE.md +66 -0
  121. package/plugin/skills/agentmemory-mcp-tools/SKILL.md +39 -0
  122. package/plugin/skills/agentmemory-rest-api/REFERENCE.md +142 -0
  123. package/plugin/skills/agentmemory-rest-api/SKILL.md +43 -0
  124. package/plugin/skills/commit-context/EXAMPLES.md +71 -0
  125. package/plugin/skills/commit-context/SKILL.md +53 -8
  126. package/plugin/skills/commit-history/EXAMPLES.md +71 -0
  127. package/plugin/skills/commit-history/SKILL.md +51 -9
  128. package/plugin/skills/forget/EXAMPLES.md +75 -0
  129. package/plugin/skills/forget/SKILL.md +57 -14
  130. package/plugin/skills/handoff/EXAMPLES.md +56 -0
  131. package/plugin/skills/handoff/SKILL.md +56 -9
  132. package/plugin/skills/lesson/SKILL.md +64 -0
  133. package/plugin/skills/memory-discipline/SKILL.md +57 -0
  134. package/plugin/skills/recall/EXAMPLES.md +74 -0
  135. package/plugin/skills/recall/SKILL.md +53 -11
  136. package/plugin/skills/recap/EXAMPLES.md +60 -0
  137. package/plugin/skills/recap/SKILL.md +51 -13
  138. package/plugin/skills/remember/EXAMPLES.md +67 -0
  139. package/plugin/skills/remember/SKILL.md +58 -17
  140. package/plugin/skills/session-history/EXAMPLES.md +59 -0
  141. package/plugin/skills/session-history/SKILL.md +54 -11
  142. package/plugin/skills/write-agentmemory-skill/SKILL.md +40 -0
  143. package/dist/connect-bmZ5eqYN.mjs.map +0 -1
  144. package/dist/image-refs-B5Zf_Djo.mjs.map +0 -1
  145. package/dist/image-store-Gpo2mgM9.mjs.map +0 -1
  146. package/dist/rolldown-runtime-twds-ZHy.mjs +0 -14
  147. package/dist/schema-CNiCWzV9.mjs +0 -78
  148. package/dist/schema-CNiCWzV9.mjs.map +0 -1
  149. package/dist/src-MOg2zKJs.mjs.map +0 -1
  150. package/dist/standalone-BORXC71-.mjs.map +0 -1
  151. package/dist/tools-registry-D8XVVPbJ.mjs.map +0 -1
  152. package/dist/version-9DNrRyph.mjs +0 -6
  153. package/dist/version-9DNrRyph.mjs.map +0 -1
  154. package/plugin/scripts/diagnostics.d.mts +0 -17
  155. package/plugin/scripts/diagnostics.d.mts.map +0 -1
  156. package/plugin/scripts/diagnostics.mjs.map +0 -1
package/dist/cli.mjs CHANGED
@@ -1,14 +1,15 @@
1
1
  #!/usr/bin/env node
2
- import { i as generateId } from "./schema-CNiCWzV9.mjs";
3
- import { n as resolveAdapter, r as runAdapter } from "./connect-bmZ5eqYN.mjs";
4
- import { r as setBootVerbose } from "./logger-yHTcEBAI.mjs";
5
- import { t as VERSION } from "./version-9DNrRyph.mjs";
2
+ import { a as runAdapter, i as resolveAdapter, r as knownAgents, t as ADAPTERS } from "./connect-Qaeq15TQ.mjs";
3
+ import { i as generateId } from "./schema-BFZhNTF7.mjs";
4
+ import { r as setBootVerbose } from "./logger-BEB7pCI9.mjs";
5
+ import { i as VERSION, n as getAllTools, p as hydrateProcessEnvFromFile, t as ESSENTIAL_TOOLS } from "./tools-registry-BV_2bu7z.mjs";
6
6
  import { execFileSync, spawn, spawnSync } from "node:child_process";
7
- import { closeSync, constants, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, readdirSync, readlinkSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync, writeSync } from "node:fs";
8
- import { delimiter, dirname, join } from "node:path";
7
+ import { appendFileSync, closeSync, constants, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, readdirSync, readlinkSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync, writeSync } from "node:fs";
8
+ import { delimiter, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
9
9
  import { fileURLToPath } from "node:url";
10
10
  import { homedir, platform } from "node:os";
11
11
  import * as p from "@clack/prompts";
12
+ import pc from "picocolors";
12
13
  import { copyFile, mkdir } from "node:fs/promises";
13
14
  //#region src/cli/doctor-diagnostics.ts
14
15
  /** Common placeholder values shipped in .env.example. */
@@ -436,7 +437,8 @@ const DEFAULTS = {
436
437
  skipNpxHint: false,
437
438
  skipGlobalInstall: false,
438
439
  skipConsoleInstall: false,
439
- firstRunAt: null
440
+ firstRunAt: null,
441
+ injectContextChosen: false
440
442
  };
441
443
  function prefsDir() {
442
444
  return join(homedir(), ".agentmemory");
@@ -492,95 +494,14 @@ function isFirstRun() {
492
494
  //#endregion
493
495
  //#region src/cli/onboarding.ts
494
496
  const __dirname$1 = dirname(fileURLToPath(import.meta.url));
495
- const NATIVE_AGENTS = [
496
- {
497
- value: "claude-code",
498
- label: "Claude Code",
499
- glyph: "⟁"
500
- },
501
- {
502
- value: "copilot-cli",
503
- label: "GitHub Copilot CLI",
504
- glyph: "◈"
505
- },
506
- {
507
- value: "codex",
508
- label: "Codex",
509
- glyph: "◎"
510
- },
511
- {
512
- value: "openhuman",
513
- label: "OpenHuman",
514
- glyph: "◇"
515
- },
516
- {
517
- value: "openclaw",
518
- label: "OpenClaw",
519
- glyph: "◇"
520
- },
521
- {
522
- value: "hermes",
523
- label: "Hermes",
524
- glyph: "◇"
525
- },
526
- {
527
- value: "pi",
528
- label: "Pi",
529
- glyph: "◇"
530
- },
531
- {
532
- value: "cursor",
533
- label: "Cursor",
534
- glyph: "◫"
535
- },
536
- {
537
- value: "gemini-cli",
538
- label: "Gemini CLI",
539
- glyph: "✦"
540
- }
541
- ];
542
- const MCP_AGENTS = [
543
- {
544
- value: "opencode",
545
- label: "OpenCode",
546
- glyph: "⬡"
547
- },
548
- {
549
- value: "cline",
550
- label: "Cline",
551
- glyph: "◇"
552
- },
553
- {
554
- value: "goose",
555
- label: "Goose",
556
- glyph: "◇"
557
- },
558
- {
559
- value: "kilo",
560
- label: "Kilo",
561
- glyph: "◇"
562
- },
563
- {
564
- value: "aider",
565
- label: "Aider",
566
- glyph: "◇"
567
- },
568
- {
569
- value: "claude-desktop",
570
- label: "Claude Desktop",
571
- glyph: "⟁"
572
- },
573
- {
574
- value: "windsurf",
575
- label: "Windsurf",
576
- glyph: "◇"
577
- },
578
- {
579
- value: "roo",
580
- label: "Roo",
581
- glyph: "◇"
582
- }
583
- ];
497
+ const AGENT_GLYPH = {
498
+ "claude-code": "⟁",
499
+ "copilot-cli": "◈",
500
+ codex: "◎",
501
+ cursor: "◫",
502
+ "gemini-cli": "✦",
503
+ opencode: "⬡"
504
+ };
584
505
  const PROVIDERS = [
585
506
  {
586
507
  value: "anthropic",
@@ -604,7 +525,7 @@ const PROVIDERS = [
604
525
  },
605
526
  {
606
527
  value: "minimax",
607
- label: "MiniMax — minimax-m1",
528
+ label: "MiniMax — MiniMax-M3",
608
529
  envKey: "MINIMAX_API_KEY"
609
530
  },
610
531
  {
@@ -613,16 +534,20 @@ const PROVIDERS = [
613
534
  envKey: null
614
535
  }
615
536
  ];
537
+ const PROVIDER_COST_HINTS = {
538
+ anthropic: "rough cost: a fast Haiku-class model keeps compress/consolidate at fractions of a cent per session.",
539
+ openai: "rough cost: a mini-class model keeps compress/consolidate at fractions of a cent per session.",
540
+ gemini: "rough cost: a Flash-class model keeps compress/consolidate at fractions of a cent per session.",
541
+ openrouter: "rough cost: pick a small model; spend tracks your chosen model's per-token price.",
542
+ minimax: "rough cost: scales with the MiniMax model price per token."
543
+ };
616
544
  function buildAgentOptions() {
617
- return [...NATIVE_AGENTS.map((a) => ({
618
- value: a.value,
619
- label: `${a.glyph} ${a.label}`,
620
- hint: "native plugin"
621
- })), ...MCP_AGENTS.map((a) => ({
622
- value: a.value,
623
- label: `${a.glyph} ${a.label}`,
624
- hint: "MCP server"
625
- }))];
545
+ const options = ADAPTERS.map((a) => ({
546
+ value: a.name,
547
+ label: `${AGENT_GLYPH[a.name] ?? "◇"} ${a.displayName}`,
548
+ hint: a.category === "native" ? "native plugin" : "MCP server"
549
+ }));
550
+ return [...options.filter((o) => o.hint === "native plugin"), ...options.filter((o) => o.hint === "MCP server")];
626
551
  }
627
552
  function getInitialAgentValues(env = process.env) {
628
553
  if (env["COPILOT_CLI"] === "1" || env["COPILOT_AGENT_SESSION_ID"]) return ["copilot-cli"];
@@ -715,7 +640,12 @@ async function runOnboarding() {
715
640
  }
716
641
  const provider = providerPicked === "skip" ? null : providerPicked;
717
642
  const agents = agentsPicked ?? [];
643
+ if (provider) {
644
+ const hint = PROVIDER_COST_HINTS[provider];
645
+ if (hint) p.log.info(hint);
646
+ }
718
647
  const envPath = await seedEnvFile(provider);
648
+ await maybePromptContextInjection(envPath);
719
649
  writePrefs({
720
650
  lastAgent: agents[0] ?? null,
721
651
  lastAgents: agents,
@@ -737,6 +667,33 @@ async function runOnboarding() {
737
667
  provider
738
668
  };
739
669
  }
670
+ function enableInjectContextInEnv(envPath) {
671
+ if (!envPath || !existsSync(envPath)) return false;
672
+ try {
673
+ const current = readFileSync(envPath, "utf-8");
674
+ if (/^\s*AGENTMEMORY_INJECT_CONTEXT\s*=\s*true\b/m.test(current)) return true;
675
+ appendFileSync(envPath, `${current.length > 0 && !current.endsWith("\n") ? "\n" : ""}AGENTMEMORY_INJECT_CONTEXT=true\n`, { mode: 384 });
676
+ return true;
677
+ } catch {
678
+ return false;
679
+ }
680
+ }
681
+ async function maybePromptContextInjection(envPath) {
682
+ if (readPrefs().injectContextChosen) return;
683
+ const enable = await p.confirm({
684
+ message: "Enable automatic context injection so the agent recalls past sessions without being asked? [y/N]",
685
+ initialValue: false
686
+ });
687
+ if (p.isCancel(enable)) {
688
+ p.cancel("Setup cancelled. Re-run any time with: agentmemory --reset");
689
+ process.exit(0);
690
+ }
691
+ p.log.info("Cost note: injection spends session tokens proportional to tool-call frequency. Default is off.");
692
+ writePrefs({ injectContextChosen: true });
693
+ if (enable === true) if (enableInjectContextInEnv(envPath)) p.log.success("Context injection enabled (AGENTMEMORY_INJECT_CONTEXT=true).");
694
+ else p.log.warn("Could not update ~/.agentmemory/.env. Set AGENTMEMORY_INJECT_CONTEXT=true there to enable it.");
695
+ else p.log.info("Context injection left off. Set AGENTMEMORY_INJECT_CONTEXT=true later to enable.");
696
+ }
740
697
  async function wireSelectedAgents(agents) {
741
698
  p.note("Wire selected agents now?", "next step");
742
699
  const confirmed = await p.confirm({
@@ -808,13 +765,107 @@ async function wireSelectedAgents(agents) {
808
765
  p.note(summary.join("\n"), "wire summary");
809
766
  }
810
767
  //#endregion
768
+ //#region src/cli-data-dir.ts
769
+ function argValue(args, name) {
770
+ const equals = `${name}=`;
771
+ const inline = args.find((arg) => arg.startsWith(equals));
772
+ if (inline) return inline.slice(equals.length);
773
+ const idx = args.indexOf(name);
774
+ if (idx !== -1) return args[idx + 1];
775
+ }
776
+ function expandHome(pathValue, home) {
777
+ if (pathValue === "~") return home;
778
+ if (pathValue.startsWith("~/") || pathValue.startsWith("~\\")) return join(home, pathValue.slice(2));
779
+ return pathValue;
780
+ }
781
+ function absoluteDataDir(pathValue, cwd, home) {
782
+ const expanded = expandHome(pathValue, home);
783
+ return isAbsolute(expanded) ? expanded : resolve(cwd, expanded);
784
+ }
785
+ function platformDefaultDataDir(env, home, nodePlatform, useXdg = true) {
786
+ if (nodePlatform === "darwin") return join(home, "Library", "Application Support", "agentmemory");
787
+ if (nodePlatform === "win32") {
788
+ const appData = env["APPDATA"];
789
+ return appData ? join(appData, "agentmemory") : join(home, ".agentmemory");
790
+ }
791
+ const xdgDataHome = env["XDG_DATA_HOME"];
792
+ if (useXdg && xdgDataHome && isAbsolute(xdgDataHome)) return join(xdgDataHome, "agentmemory");
793
+ return join(home, ".local", "share", "agentmemory");
794
+ }
795
+ function nearestGitParent(pathValue) {
796
+ let current = pathValue;
797
+ while (true) {
798
+ if (existsSync(join(current, ".git"))) return current;
799
+ const parent = dirname(current);
800
+ if (parent === current) return void 0;
801
+ current = parent;
802
+ }
803
+ }
804
+ function isWithin(parent, child) {
805
+ const pathFromParent = relative(parent, child);
806
+ return pathFromParent === "" || pathFromParent !== ".." && !pathFromParent.startsWith(`..${sep}`) && !isAbsolute(pathFromParent);
807
+ }
808
+ function resolveDataDir(options = {}) {
809
+ const args = options.args ?? process.argv.slice(2);
810
+ const env = options.env ?? process.env;
811
+ const cwd = options.cwd ?? process.cwd();
812
+ const home = options.home ?? homedir();
813
+ const nodePlatform = options.platform ?? platform();
814
+ const parsedInstance = parseInt(argValue(args, "--instance") ?? "0", 10);
815
+ const instance = parsedInstance > 0 && parsedInstance <= 50 ? parsedInstance : 0;
816
+ const forInstance = (dataDir) => instance ? join(dataDir, `instance-${instance}`) : dataDir;
817
+ const flagValue = argValue(args, "--data-dir");
818
+ if (flagValue) return {
819
+ dataDir: forInstance(absoluteDataDir(flagValue, cwd, home)),
820
+ source: "flag"
821
+ };
822
+ const envValue = env["AGENTMEMORY_DATA_DIR"];
823
+ if (envValue) return {
824
+ dataDir: forInstance(absoluteDataDir(envValue, cwd, home)),
825
+ source: "env"
826
+ };
827
+ const legacyDir = resolve(cwd, "data");
828
+ const hasLegacyStore = existsSync(join(legacyDir, "state_store.db")) || existsSync(join(legacyDir, "iii-config.yaml"));
829
+ if (instance === 0 && hasLegacyStore) return {
830
+ dataDir: legacyDir,
831
+ source: "default"
832
+ };
833
+ const defaultDir = platformDefaultDataDir(env, home, nodePlatform);
834
+ const gitParent = nearestGitParent(cwd);
835
+ if (gitParent && isWithin(gitParent, defaultDir)) {
836
+ const relocated = platformDefaultDataDir(env, home, nodePlatform, false);
837
+ if (relocated !== defaultDir) return {
838
+ dataDir: forInstance(relocated),
839
+ source: "default",
840
+ relocatedFrom: defaultDir
841
+ };
842
+ }
843
+ return {
844
+ dataDir: forInstance(defaultDir),
845
+ source: "default"
846
+ };
847
+ }
848
+ //#endregion
811
849
  //#region src/cli.ts
850
+ const c = {
851
+ url: pc.cyan,
852
+ ok: pc.green,
853
+ warn: pc.yellow,
854
+ err: pc.red,
855
+ cmd: (s) => pc.bold(pc.cyan(s)),
856
+ label: pc.bold,
857
+ dim: pc.dim,
858
+ accent: (s) => pc.bold(pc.yellow(s))
859
+ };
860
+ const ALL_TOOLS_COUNT = getAllTools().length;
861
+ const CORE_TOOLS_COUNT = getAllTools().filter((t) => ESSENTIAL_TOOLS.has(t.name)).length;
812
862
  const __dirname = dirname(fileURLToPath(import.meta.url));
813
863
  const args = process.argv.slice(2);
814
864
  const IS_WINDOWS = platform() === "win32";
815
865
  const IS_VERBOSE = args.includes("--verbose") || args.includes("-v") || process.env["AGENTMEMORY_VERBOSE"] === "1" || process.env["AGENTMEMORY_VERBOSE"] === "true";
816
866
  setBootVerbose(IS_VERBOSE);
817
867
  const IS_RESET = args.includes("--reset");
868
+ hydrateProcessEnvFromFile();
818
869
  if (args.includes("--version") || args.includes("-V")) {
819
870
  process.stdout.write(`${VERSION}\n`);
820
871
  process.exit(0);
@@ -840,6 +891,19 @@ function iiiReleaseUrl() {
840
891
  function vlog(msg) {
841
892
  if (IS_VERBOSE) p.log.info(`[verbose] ${msg}`);
842
893
  }
894
+ function wrapList(items, indent, width = 78) {
895
+ const lines = [];
896
+ let line = "";
897
+ for (const item of items) {
898
+ const joined = line ? `${line}, ${item}` : item;
899
+ if (line && indent + joined.length > width) {
900
+ lines.push(`${line},`);
901
+ line = item;
902
+ } else line = joined;
903
+ }
904
+ lines.push(line);
905
+ return lines.join(`\n${" ".repeat(indent)}`);
906
+ }
843
907
  if (args.includes("--help") || args.includes("-h")) {
844
908
  console.log(`
845
909
  agentmemory — persistent memory for AI coding agents
@@ -849,9 +913,8 @@ Usage: agentmemory [command] [options]
849
913
  Commands:
850
914
  (default) Start agentmemory worker
851
915
  init Copy bundled .env.example to ~/.agentmemory/.env if absent
852
- connect [agent] Wire agentmemory into an installed agent (claude-code,
853
- copilot-cli, codex, cursor, gemini-cli, openclaw,
854
- hermes, pi, openhuman).
916
+ connect [agent] Wire agentmemory into an installed agent
917
+ (${wrapList(knownAgents(), 21)}).
855
918
  No arg = interactive picker. --all wires every detected agent.
856
919
  --dry-run shows what would change. --force re-installs.
857
920
  status Show connection status, memory count, flags, and health
@@ -860,7 +923,9 @@ Commands:
860
923
  --dry-run: show what each fix would do, don't execute
861
924
  remove Cleanly uninstall agentmemory (pidfile, state, .env, binaries).
862
925
  --force: skip confirmations · --keep-data: keep memory data
863
- demo Seed sample sessions and show recall in action
926
+ demo [--serve] Seed sample sessions and show recall in action.
927
+ --serve boots the server, runs the demo, and stops it
928
+ in one command (no second terminal).
864
929
  upgrade Upgrade local deps + iii runtime (best effort)
865
930
  stop [--force] Stop the running iii-engine started by this CLI.
866
931
  --force bypasses the Docker-heuristic guard and signals
@@ -876,7 +941,7 @@ Options:
876
941
  --help, -h Show this help
877
942
  --verbose, -v Show engine stderr, boot log, and diagnostic info
878
943
  --reset Wipe ~/.agentmemory/preferences.json and re-run onboarding
879
- --tools all|core Tool visibility (default: all = 51 tools; core = 8 essentials)
944
+ --tools all|core Tool visibility (default: all = ${ALL_TOOLS_COUNT} tools; core = ${CORE_TOOLS_COUNT} essentials)
880
945
  --no-engine Skip auto-starting iii-engine
881
946
  --port <N> Override REST port (default: 3111). Streams (N+1), viewer
882
947
  (N+2), and iii engine (N+46023) auto-derive from N so a
@@ -884,10 +949,12 @@ Options:
884
949
  --instance <N> Shortcut for --port (3111 + N*100) to run multiple
885
950
  daemons side-by-side without env gymnastics.
886
951
  --instance 1 -> 3211/3212/3213/49234, etc. (max N=50)
952
+ --data-dir <path> Store iii-engine state outside the current repo
887
953
 
888
954
  Environment:
889
955
  AGENTMEMORY_URL Full REST base URL (e.g. http://localhost:3111).
890
956
  Honored by status, doctor, and MCP shim commands.
957
+ AGENTMEMORY_DATA_DIR State directory fallback when --data-dir is not set.
891
958
  AGENTMEMORY_USE_DOCKER=1 Prefer the bundled docker-compose path over the
892
959
  native iii-engine binary on first run.
893
960
  AGENTMEMORY_III_VERSION Override pinned iii-engine version (default ${IIPINNED_VERSION}).
@@ -907,7 +974,11 @@ Quick start:
907
974
  process.exit(0);
908
975
  }
909
976
  const toolsIdx = args.indexOf("--tools");
910
- if (toolsIdx !== -1 && args[toolsIdx + 1]) process.env["AGENTMEMORY_TOOLS"] = args[toolsIdx + 1];
977
+ if (toolsIdx !== -1 && args[toolsIdx + 1]) {
978
+ const toolsMode = args[toolsIdx + 1];
979
+ if (toolsMode !== "all" && toolsMode !== "core") p.log.warn(`Unknown --tools value "${toolsMode}" (valid: all, core); falling back to all.`);
980
+ process.env["AGENTMEMORY_TOOLS"] = toolsMode;
981
+ }
911
982
  const portIdx = args.indexOf("--port");
912
983
  if (portIdx !== -1 && args[portIdx + 1]) process.env["III_REST_PORT"] = args[portIdx + 1];
913
984
  const instanceIdx = args.indexOf("--instance");
@@ -918,6 +989,8 @@ if (instanceIdx !== -1 && args[instanceIdx + 1]) {
918
989
  if (!process.env["III_REST_PORT"]) process.env["III_REST_PORT"] = String(base);
919
990
  }
920
991
  }
992
+ const dataDirResolution = resolveDataDir({ args });
993
+ if (dataDirResolution.source !== "default") process.env["AGENTMEMORY_DATA_DIR"] = dataDirResolution.dataDir;
921
994
  const skipEngine = args.includes("--no-engine");
922
995
  function getRestPort() {
923
996
  const url = process.env["AGENTMEMORY_URL"];
@@ -1012,6 +1085,30 @@ function findIiiConfig() {
1012
1085
  for (const c of candidates) if (existsSync(c)) return c;
1013
1086
  return "";
1014
1087
  }
1088
+ function yamlSingleQuoted(value) {
1089
+ return `'${value.replace(/'/g, "''")}'`;
1090
+ }
1091
+ function renderIiiConfig(template, dataDir) {
1092
+ return template.replace("file_path: ./data/state_store.db", `file_path: ${yamlSingleQuoted(join(dataDir, "state_store.db"))}`).replace("file_path: ./data/stream_store", `file_path: ${yamlSingleQuoted(join(dataDir, "stream_store"))}`);
1093
+ }
1094
+ function writeRuntimeIiiConfig(configPath, dataDir) {
1095
+ mkdirSync(dataDir, { recursive: true });
1096
+ const runtimeConfigPath = join(dataDir, "iii-config.yaml");
1097
+ writeFileSync(runtimeConfigPath, renderIiiConfig(readFileSync(configPath, "utf8"), dataDir));
1098
+ return runtimeConfigPath;
1099
+ }
1100
+ function warnIfRelocatedDataDir() {
1101
+ if (!dataDirResolution.relocatedFrom) return;
1102
+ try {
1103
+ mkdirSync(dataDirResolution.dataDir, { recursive: true });
1104
+ const marker = join(dataDirResolution.dataDir, ".cwd-relocation-warning");
1105
+ if (existsSync(marker)) return;
1106
+ p.log.warn(`Default data dir ${dataDirResolution.relocatedFrom} is inside a git worktree; using ${dataDirResolution.dataDir} instead.`);
1107
+ writeFileSync(marker, (/* @__PURE__ */ new Date()).toISOString());
1108
+ } catch {
1109
+ p.log.warn(`Default data dir ${dataDirResolution.relocatedFrom} is inside a git worktree; using ${dataDirResolution.dataDir} instead.`);
1110
+ }
1111
+ }
1015
1112
  function whichBinary(name) {
1016
1113
  const cmd = IS_WINDOWS ? "where" : "which";
1017
1114
  try {
@@ -1188,8 +1285,8 @@ async function maybeOfferGlobalInstall() {
1188
1285
  if (runCommand(npmBin, [
1189
1286
  "install",
1190
1287
  "-g",
1191
- `@agentmemory/agentmemory@0.9.27`
1192
- ], { label: `Installing @agentmemory/agentmemory@0.9.27 globally` })) {
1288
+ `@agentmemory/agentmemory@0.9.29`
1289
+ ], { label: `Installing @agentmemory/agentmemory@0.9.29 globally` })) {
1193
1290
  p.log.success("Installed globally. `agentmemory stop` etc. will now work in new shells.");
1194
1291
  writePrefs({ skipGlobalInstall: true });
1195
1292
  } else p.log.warn("Global install failed. Try manually: npm install -g @agentmemory/agentmemory");
@@ -1244,13 +1341,20 @@ function adoptRunningEngine() {
1244
1341
  const existingPid = readEnginePidfile();
1245
1342
  if (existingState && existingPid) return;
1246
1343
  const enginePid = findEnginePidsByPort(getRestPort())[0];
1344
+ if (enginePid) {
1345
+ const comm = pidCommand(enginePid);
1346
+ if (isForeignPortHolder(comm)) {
1347
+ vlog(`adoptRunningEngine: refusing to adopt pid ${enginePid} (${comm}) — not the iii engine binary`);
1348
+ return;
1349
+ }
1350
+ }
1247
1351
  if (enginePid && !existingPid) writeEnginePidfile(enginePid);
1248
1352
  if (!existingState) writeEngineState({
1249
1353
  kind: "native",
1250
1354
  configPath: findIiiConfig() || "",
1251
1355
  attached: true
1252
1356
  });
1253
- if (enginePid && !existingPid) p.log.info(`Attached to existing iii-engine (pid ${enginePid})`);
1357
+ if (enginePid && !existingPid) p.log.info(c.ok(`Attached to existing iii-engine (pid ${enginePid})`));
1254
1358
  } catch (err) {
1255
1359
  vlog(`adoptRunningEngine: ${err instanceof Error ? err.message : String(err)}`);
1256
1360
  }
@@ -1352,7 +1456,7 @@ function startIiiBin(iiiBin, configPath) {
1352
1456
  binPath: iiiBin
1353
1457
  });
1354
1458
  spawnEngineBackground(iiiBin, ["--config", configPath], "iii-engine");
1355
- s.stop("iii-engine process started");
1459
+ s.stop(c.ok("iii-engine process started"));
1356
1460
  return true;
1357
1461
  }
1358
1462
  function pickCompatibleIii(candidates) {
@@ -1365,17 +1469,19 @@ function pickCompatibleIii(candidates) {
1365
1469
  }
1366
1470
  async function startEngine() {
1367
1471
  const configPath = findIiiConfig();
1472
+ warnIfRelocatedDataDir();
1473
+ const runtimeConfigPath = configPath ? writeRuntimeIiiConfig(configPath, dataDirResolution.dataDir) : "";
1368
1474
  const pathIii = whichBinary("iii");
1369
- vlog(`iii binary: ${pathIii ?? "(not on PATH)"}, config: ${configPath || "(not found)"}`);
1475
+ vlog(`iii binary: ${pathIii ?? "(not on PATH)"}, config: ${runtimeConfigPath || "(not found)"}`);
1370
1476
  const fallbacks = fallbackIiiPaths().filter((p) => existsSync(p));
1371
1477
  for (const f of fallbacks) vlog(`fallback iii at ${f} reports version: ${iiiBinVersion(f) ?? "unknown"}`);
1372
1478
  let iiiBin = pickCompatibleIii([pathIii, ...fallbacks]);
1373
- if (iiiBin && configPath) {
1479
+ if (iiiBin && runtimeConfigPath) {
1374
1480
  if (iiiBin !== pathIii) {
1375
- p.log.info(`Using iii at: ${iiiBin} (v${IIPINNED_VERSION})`);
1481
+ p.log.info(`Using iii at: ${c.dim(iiiBin)} (v${c.accent(IIPINNED_VERSION)})`);
1376
1482
  process.env["PATH"] = `${dirname(iiiBin)}${delimiter}${process.env["PATH"] ?? ""}`;
1377
1483
  }
1378
- return startIiiBin(iiiBin, configPath);
1484
+ return startIiiBin(iiiBin, runtimeConfigPath);
1379
1485
  }
1380
1486
  if (pathIii && !iiiBin) vlog(`iii on PATH is v${iiiBinVersion(pathIii) ?? "unknown"}, pin is v${IIPINNED_VERSION}. Will install pinned engine to ${privateIiiPath()}.`);
1381
1487
  if (!configPath) {
@@ -1384,6 +1490,7 @@ async function startEngine() {
1384
1490
  }
1385
1491
  const dockerBin = whichBinary("docker");
1386
1492
  vlog(`docker binary: ${dockerBin ?? "(not on PATH)"}`);
1493
+ mkdirSync(dataDirResolution.dataDir, { recursive: true });
1387
1494
  const composeFile = [
1388
1495
  join(__dirname, "..", "docker-compose.yml"),
1389
1496
  join(__dirname, "docker-compose.yml"),
@@ -1545,19 +1652,25 @@ function printReadyHint(consoleState) {
1545
1652
  const engineHost = getEngineHost();
1546
1653
  const streamUrl = `ws://${engineHost}:${getStreamPort()}`;
1547
1654
  const engineUrl = `ws://${engineHost}:${getEnginePort()}`;
1548
- const consoleLine = consoleState.kind === "installed" ? `iii console ${consoleState.binPath} (run: ${consoleState.binPath} -p <port>)` : `iii console (install: ${iiiConsoleInstallHint()})`;
1655
+ const consoleLine = consoleState.kind === "installed" ? `${c.label("iii console")} ${c.dim(consoleState.binPath)} ${c.dim(`(run: ${consoleState.binPath} -p <port>)`)}` : `${c.label("iii console")} ${c.dim(`(install: ${iiiConsoleInstallHint()})`)}`;
1549
1656
  const lines = [
1550
- `REST API ${restUrl}`,
1551
- `Viewer ${viewerUrl}`,
1552
- `Streams ${streamUrl}`,
1553
- `Engine ${engineUrl}`,
1657
+ `${c.label("REST API")} ${c.url(restUrl)}`,
1658
+ `${c.label("Viewer")} ${c.url(viewerUrl)}`,
1659
+ `${c.label("Streams")} ${c.url(streamUrl)}`,
1660
+ `${c.label("Engine")} ${c.url(engineUrl)}`,
1554
1661
  consoleLine
1555
1662
  ];
1556
- p.note(lines.join("\n"), `agentmemory v${VERSION}`);
1663
+ p.note(lines.join("\n"), `agentmemory v${c.accent(VERSION)}`);
1557
1664
  const demoCommand = isInvokedViaNpx() ? "npx @agentmemory/agentmemory demo" : "agentmemory demo";
1558
- process.stdout.write(`\nTry: ${demoCommand}\n`);
1665
+ process.stdout.write(`\n${c.dim("Try:")} ${c.cmd(demoCommand)}\n`);
1559
1666
  }
1560
1667
  async function main() {
1668
+ try {
1669
+ if ((await fetch(`${getBaseUrl()}/agentmemory/livez`, { signal: AbortSignal.timeout(1500) })).ok) {
1670
+ p.log.error(`agentmemory is already running on port ${getRestPort()}. Starting a second instance here would corrupt the running daemon's REST routing. Use the REST API (or the MCP tools) against the running instance, run a different --instance, or stop it first with \`agentmemory stop\`.`);
1671
+ process.exit(1);
1672
+ }
1673
+ } catch {}
1561
1674
  if (IS_RESET) resetPrefs();
1562
1675
  const firstRun = isFirstRun();
1563
1676
  const prefs = readPrefs();
@@ -1565,7 +1678,7 @@ async function main() {
1565
1678
  if (firstRun || IS_RESET) await runOnboarding();
1566
1679
  if (skipEngine) {
1567
1680
  if (IS_VERBOSE) p.log.info("Skipping engine check (--no-engine)");
1568
- await import("./src-MOg2zKJs.mjs");
1681
+ await import("./src-G7yt8gGm.mjs");
1569
1682
  if (await waitForAgentmemoryReady(15e3)) {
1570
1683
  const consoleState = await ensureIiiConsole();
1571
1684
  await maybeOfferGlobalInstall();
@@ -1577,21 +1690,38 @@ async function main() {
1577
1690
  if (IS_VERBOSE) p.log.success("iii-engine is running");
1578
1691
  const persisted = readEngineState();
1579
1692
  const attachedBin = (persisted?.kind === "native" && persisted.binPath && existsSync(persisted.binPath) ? persisted.binPath : null) ?? whichBinary("iii") ?? fallbackIiiPaths().find((p) => existsSync(p)) ?? null;
1580
- if (attachedBin) {
1581
- const detected = iiiBinVersion(attachedBin);
1582
- if (detected && detected !== IIPINNED_VERSION) {
1583
- p.log.error(`Attached iii-engine appears to be v${detected} (from ${attachedBin}) but agentmemory v${VERSION} hard-pins v${IIPINNED_VERSION}. Engine API drift causes runtime failures (e.g. state::list-not-found on v0.13.0+). Stop the running engine (\`agentmemory stop --force\`) and re-run \`agentmemory\` to install the pinned engine into ~/.agentmemory/bin without touching ${attachedBin}. Or set AGENTMEMORY_III_VERSION=${detected} to override at your own risk.`);
1584
- process.exit(1);
1693
+ const detected = attachedBin ? iiiBinVersion(attachedBin) : null;
1694
+ if (detected === IIPINNED_VERSION) {
1695
+ adoptRunningEngine();
1696
+ await import("./src-G7yt8gGm.mjs");
1697
+ if (await waitForAgentmemoryReady(15e3)) {
1698
+ const consoleState = await ensureIiiConsole();
1699
+ await maybeOfferGlobalInstall();
1700
+ printReadyHint(consoleState);
1585
1701
  }
1702
+ return;
1586
1703
  }
1587
- adoptRunningEngine();
1588
- await import("./src-MOg2zKJs.mjs");
1589
- if (await waitForAgentmemoryReady(15e3)) {
1590
- const consoleState = await ensureIiiConsole();
1591
- await maybeOfferGlobalInstall();
1592
- printReadyHint(consoleState);
1593
- }
1594
- return;
1704
+ const detectedLabel = detected ? `v${detected}` : "an unverified version";
1705
+ const base = isInvokedViaNpx() ? "npx @agentmemory/agentmemory" : "agentmemory";
1706
+ p.log.error(`Another iii-engine (${detectedLabel}) is running on port ${getEnginePort()}, and agentmemory needs its own pinned v${IIPINNED_VERSION}.`);
1707
+ p.note([
1708
+ `agentmemory only supports iii-engine v${IIPINNED_VERSION}. It will not adopt or change the running engine (${detectedLabel}).`,
1709
+ "",
1710
+ c.label("Switch to the pinned engine in two steps:"),
1711
+ "",
1712
+ ` 1. Stop the running engine:`,
1713
+ ` ${c.cmd(`${base} stop --force`)}`,
1714
+ ` ${c.dim(`(or stop your own iii however you started it — agentmemory leaves your global iii untouched)`)}`,
1715
+ "",
1716
+ ` 2. Start agentmemory. It downloads and runs the pinned`,
1717
+ ` v${IIPINNED_VERSION} into ~/.agentmemory/bin automatically:`,
1718
+ ` ${c.cmd(base)}`,
1719
+ "",
1720
+ c.dim(`Step 2 needs no manual install. To install iii v${IIPINNED_VERSION} yourself (replaces your global iii), curl:`),
1721
+ ` ${c.cmd(III_CONSOLE_INSTALL_CMD)}`,
1722
+ ` ${c.dim("or download the release:")} ${c.url(`https://github.com/iii-hq/iii/releases/tag/iii%2Fv${IIPINNED_VERSION}`)}`
1723
+ ].join("\n"), "engine conflict");
1724
+ process.exit(1);
1595
1725
  }
1596
1726
  if (!await startEngine()) {
1597
1727
  p.log.error("Could not start iii-engine.");
@@ -1633,8 +1763,8 @@ async function main() {
1633
1763
  }
1634
1764
  process.exit(1);
1635
1765
  }
1636
- s.stop("iii-engine is ready");
1637
- await import("./src-MOg2zKJs.mjs");
1766
+ s.stop(c.ok("iii-engine is ready"));
1767
+ await import("./src-G7yt8gGm.mjs");
1638
1768
  if (await waitForAgentmemoryReady(15e3)) {
1639
1769
  const consoleState = await ensureIiiConsole();
1640
1770
  await maybeOfferGlobalInstall();
@@ -1692,7 +1822,7 @@ async function runStatus() {
1692
1822
  const pctSaved = estFullTokens > 0 ? Math.round(tokensSaved / estFullTokens * 100) : 0;
1693
1823
  p.log.success(`Connected — v${version} at ${base}`);
1694
1824
  const lines = [
1695
- `Health: ${status === "healthy" ? "✓ healthy" : status}`,
1825
+ `Health: ${status === "healthy" ? pc.green("✓ healthy") : pc.yellow(status)}`,
1696
1826
  `Sessions: ${sessions}`,
1697
1827
  `Observations: ${obsCount}`,
1698
1828
  `Memories: ${memCount}`,
@@ -1700,7 +1830,7 @@ async function runStatus() {
1700
1830
  `Circuit: ${cb}`,
1701
1831
  `Heap: ${heapMB} MB`,
1702
1832
  `Uptime: ${uptime}s`,
1703
- `Viewer: ${getViewerUrl()}`
1833
+ `Viewer: ${c.url(getViewerUrl())}`
1704
1834
  ];
1705
1835
  if (obsCount > 0) {
1706
1836
  lines.push("");
@@ -1709,9 +1839,9 @@ async function runStatus() {
1709
1839
  lines.push(` Injected: ~${estInjectedTokens.toLocaleString()} tokens`);
1710
1840
  }
1711
1841
  if (flagsRes) {
1712
- const provider = flagsRes.provider === "llm" ? "✓ llm" : "✗ noop (no key)";
1713
- const embed = flagsRes.embeddingProvider === "embeddings" ? "✓ embeddings" : "bm25-only";
1714
- const flagRows = (flagsRes.flags || []).map((f) => ` ${f.enabled ? "✓" : "✗"} ${f.key.padEnd(32)} ${f.label}`);
1842
+ const provider = flagsRes.provider === "llm" ? pc.green("✓ llm") : pc.yellow("✗ noop (no key)");
1843
+ const embed = flagsRes.embeddingProvider === "embeddings" ? pc.green("✓ embeddings") : pc.dim("bm25-only");
1844
+ const flagRows = (flagsRes.flags || []).map((f) => ` ${f.enabled ? pc.green("✓") : pc.dim("✗")} ${pc.bold(f.key.padEnd(32))} ${f.label}`);
1715
1845
  lines.push("");
1716
1846
  lines.push(`Provider: ${provider}`);
1717
1847
  lines.push(`Embeddings: ${embed}`);
@@ -1732,7 +1862,7 @@ async function runStatus() {
1732
1862
  }
1733
1863
  }
1734
1864
  function formatChecks(checks) {
1735
- return checks.map((c) => `${c.ok ? "✓" : "✗"} ${c.name}${c.hint ? `\n ${c.hint}` : ""}`).join("\n");
1865
+ return checks.map((c) => `${c.ok ? pc.green("✓") : pc.red("✗")} ${c.name}${c.hint ? `\n ${c.hint}` : ""}`).join("\n");
1736
1866
  }
1737
1867
  function findLatestDebugLog(debugDir) {
1738
1868
  const latestLink = join(debugDir, "latest");
@@ -2268,15 +2398,63 @@ async function runInit() {
2268
2398
  ].join("\n"), "Next steps");
2269
2399
  p.outro(`Edit ${target} and you're set.`);
2270
2400
  }
2401
+ async function startServerForDemo() {
2402
+ if (await isAgentmemoryReady()) return async () => {};
2403
+ const startedEngine = !await isEngineRunning();
2404
+ if (startedEngine) {
2405
+ if (!await startEngine()) {
2406
+ p.log.error("Could not start iii-engine for the demo.");
2407
+ p.note(installInstructions().join("\n"), "Setup required");
2408
+ process.exit(1);
2409
+ }
2410
+ if (!await waitForEngine(15e3)) {
2411
+ p.log.error("iii-engine did not become ready within 15s.");
2412
+ process.exit(1);
2413
+ }
2414
+ }
2415
+ await import("./src-G7yt8gGm.mjs");
2416
+ if (!await waitForAgentmemoryReady(15e3)) {
2417
+ p.log.error("agentmemory worker did not become ready within 15s.");
2418
+ process.exit(1);
2419
+ }
2420
+ return async () => {
2421
+ if (!startedEngine) return;
2422
+ const port = getRestPort();
2423
+ const state = readEngineState();
2424
+ if (state?.kind === "docker") {
2425
+ await stopDockerEngine(state.composeFile, port).catch(() => {});
2426
+ return;
2427
+ }
2428
+ const pids = new Set(findEnginePidsByPort(port));
2429
+ const pidfilePid = readEnginePidfile();
2430
+ if (pidfilePid) pids.add(pidfilePid);
2431
+ for (const pid of pids) await signalAndWait(pid, "SIGTERM", 3e3).catch(() => {});
2432
+ clearEnginePidfile();
2433
+ clearEngineState();
2434
+ clearWorkerPidfile();
2435
+ };
2436
+ }
2271
2437
  async function runDemo() {
2272
2438
  const port = getRestPort();
2273
2439
  const base = `http://localhost:${port}`;
2274
2440
  p.intro("agentmemory demo");
2275
- if (!await isAgentmemoryReady()) {
2441
+ const serve = args.includes("--serve");
2442
+ let teardown = async () => {};
2443
+ if (serve) teardown = await startServerForDemo();
2444
+ else if (!await isAgentmemoryReady()) {
2276
2445
  p.log.error(`agentmemory worker not reachable on port ${port} (livez probe failed). Something may be on the port but it isn't serving /agentmemory/*.`);
2277
2446
  p.log.info("Start it with: npx @agentmemory/agentmemory");
2447
+ p.log.info("Or run a one-command demo with: npx @agentmemory/agentmemory demo --serve");
2278
2448
  process.exit(1);
2279
2449
  }
2450
+ try {
2451
+ await runDemoBody(base);
2452
+ } finally {
2453
+ await teardown();
2454
+ }
2455
+ if (serve) process.exit(0);
2456
+ }
2457
+ async function runDemoBody(base) {
2280
2458
  const demoProject = "/tmp/agentmemory-demo";
2281
2459
  const sessions = buildDemoSessions();
2282
2460
  const sSeed = p.spinner();
@@ -2294,18 +2472,22 @@ async function runDemo() {
2294
2472
  const results = [];
2295
2473
  for (const query of queries) results.push(await runDemoSearch(base, query));
2296
2474
  sQuery.stop("Search complete");
2475
+ const semanticHits = results.find((r) => r.query === "database performance optimization")?.hits ?? 0;
2297
2476
  const lines = [
2298
2477
  `Project: ${demoProject}`,
2299
2478
  `Sessions: ${sessions.length} seeded (${totalObs} observations)`,
2300
2479
  "",
2301
- "Search results:",
2302
- ...results.flatMap((r) => [` "${r.query}"`, ` → ${r.hits} hit(s), top: ${r.topTitle.slice(0, 60)}`]),
2480
+ c.label("Search results:"),
2481
+ ...results.flatMap((r) => [` ${c.label(`"${r.query}"`)}`, ` ${c.dim("→")} ${c.ok(`${r.hits} hit(s)`)}, top: ${r.topTitle.slice(0, 60)}`]),
2303
2482
  "",
2304
- `Notice: searching "database performance optimization"`,
2305
- `found the N+1 query fix — keyword matching can't do that.`,
2483
+ ...semanticHits > 0 ? [c.accent(`Notice: searching "database performance optimization"`), c.accent(`found the N+1 query fix — keyword matching can't do that.`)] : [
2484
+ c.dim(`Note: "database performance optimization" found nothing —`),
2485
+ c.dim(`semantic recall needs an embedding provider key (e.g.`),
2486
+ c.dim(`OPENAI_API_KEY or GEMINI_API_KEY in ~/.agentmemory/.env).`)
2487
+ ],
2306
2488
  "",
2307
- `Viewer: ${getViewerUrl()}`,
2308
- `Clean up with: curl -X DELETE "${base}/agentmemory/sessions?project=${demoProject}"`
2489
+ `Viewer: ${c.url(getViewerUrl())}`,
2490
+ `Clean up with: ${c.dim(`curl -X DELETE "${base}/agentmemory/sessions?project=${demoProject}"`)}`
2309
2491
  ];
2310
2492
  p.note(lines.join("\n"), "demo complete");
2311
2493
  p.log.success("agentmemory is working. Point your agent at it and get back to coding.");
@@ -2319,7 +2501,7 @@ function runCommand(command, commandArgs, options = { label: "command" }) {
2319
2501
  encoding: "utf-8"
2320
2502
  });
2321
2503
  if (result.status === 0) {
2322
- spinner.stop(`${options.label} ✓`);
2504
+ spinner.stop(`${options.label} ${pc.green("✓")}`);
2323
2505
  return true;
2324
2506
  }
2325
2507
  const stderr = (result.stderr || "").toString().trim();
@@ -2330,7 +2512,7 @@ function runCommand(command, commandArgs, options = { label: "command" }) {
2330
2512
  p.log.warn(msg.slice(0, 300));
2331
2513
  return false;
2332
2514
  }
2333
- spinner.stop(`${options.label} ✗`);
2515
+ spinner.stop(`${options.label} ${pc.red("✗")}`);
2334
2516
  p.log.error(msg.slice(0, 300));
2335
2517
  return false;
2336
2518
  }
@@ -2424,6 +2606,38 @@ async function signalAndWait(pid, initialSignal, timeoutMs) {
2424
2606
  await new Promise((r) => setTimeout(r, 200));
2425
2607
  return !pidAlive(pid);
2426
2608
  }
2609
+ async function stopWorkerPid(pid, graceMs) {
2610
+ const s = p.spinner();
2611
+ s.start(`Stopping agentmemory worker (pid ${pid})... [flushing state]`);
2612
+ const ok = await signalAndWait(pid, "SIGTERM", graceMs);
2613
+ s.stop(ok ? `Stopped worker pid ${pid}` : `Failed to stop worker pid ${pid}`);
2614
+ return ok;
2615
+ }
2616
+ function pidCommand(pid) {
2617
+ if (IS_WINDOWS) return "";
2618
+ try {
2619
+ return execFileSync("ps", [
2620
+ "-p",
2621
+ String(pid),
2622
+ "-o",
2623
+ "comm="
2624
+ ], {
2625
+ encoding: "utf-8",
2626
+ stdio: [
2627
+ "ignore",
2628
+ "pipe",
2629
+ "ignore"
2630
+ ]
2631
+ }).trim();
2632
+ } catch {
2633
+ return "";
2634
+ }
2635
+ }
2636
+ function isForeignPortHolder(comm) {
2637
+ if (!comm) return false;
2638
+ const base = comm.split("/").pop() || comm;
2639
+ return base !== "iii" && !base.startsWith("iii-");
2640
+ }
2427
2641
  function findEnginePidsByPort(port) {
2428
2642
  if (IS_WINDOWS) return [];
2429
2643
  const lsof = whichBinary("lsof");
@@ -2458,17 +2672,35 @@ async function stopDockerEngine(composeFile, port) {
2458
2672
  p.log.error(`Engine state references ${composeFile}, but the file is gone. Stop it manually:\n docker compose down (from the dir holding the original docker-compose.yml)`);
2459
2673
  process.exit(1);
2460
2674
  }
2675
+ const workerPid = readWorkerPidfile();
2676
+ let workerStopped = true;
2677
+ if (workerPid) workerStopped = await stopWorkerPid(workerPid, 5e3);
2678
+ let composeText = "";
2679
+ try {
2680
+ composeText = readFileSync(composeFile, "utf-8");
2681
+ } catch {
2682
+ composeText = "";
2683
+ }
2684
+ const ownServices = ["iii-engine", "iii-init"].filter((svc) => new RegExp(`^\\s+${svc}:`, "m").test(composeText));
2685
+ if (ownServices.length === 0) {
2686
+ p.log.error(`${composeFile} does not define the agentmemory services (iii-engine/iii-init). Refusing to run an unscoped \`docker compose down\` against it — that would tear down every service in the file.\n\nStop the engine service manually:\n docker compose -f ${composeFile} stop <service>`);
2687
+ process.exit(1);
2688
+ }
2461
2689
  const ok = runCommand(dockerBin, [
2462
2690
  "compose",
2463
2691
  "-f",
2464
2692
  composeFile,
2465
- "down"
2466
- ], { label: `docker compose -f ${composeFile} down` });
2467
- clearEnginePidfile();
2468
- clearEngineState();
2469
- clearWorkerPidfile();
2470
- if (!ok) {
2471
- p.log.error(`docker compose down failed. The engine may still be running on :${port}. Inspect with:\n docker compose -f ${composeFile} ps`);
2693
+ "rm",
2694
+ "-s",
2695
+ "-f",
2696
+ ...ownServices
2697
+ ], { label: `docker compose -f ${composeFile} rm -s -f ${ownServices.join(" ")}` });
2698
+ if (workerStopped) clearWorkerPidfile();
2699
+ if (ok) {
2700
+ clearEnginePidfile();
2701
+ clearEngineState();
2702
+ } else {
2703
+ p.log.error(`docker compose rm failed. The engine may still be running on :${port}. Inspect with:\n docker compose -f ${composeFile} ps`);
2472
2704
  process.exit(1);
2473
2705
  }
2474
2706
  p.outro("Stopped. Memories persisted to disk; restart anytime with: npx @agentmemory/agentmemory");
@@ -2542,15 +2774,18 @@ async function runStop() {
2542
2774
  process.exit(1);
2543
2775
  }
2544
2776
  let allStopped = true;
2545
- for (const pid of workerCandidates) {
2546
- const s = p.spinner();
2547
- s.start(`Stopping agentmemory worker (pid ${pid})... [flushing state]`);
2548
- const ok = await signalAndWait(pid, "SIGTERM", 5e3);
2549
- s.stop(ok ? `Stopped worker pid ${pid}` : `Failed to stop worker pid ${pid}`);
2550
- if (!ok) allStopped = false;
2551
- }
2777
+ for (const pid of workerCandidates) if (!await stopWorkerPid(pid, 5e3)) allStopped = false;
2778
+ const skippedForeign = [];
2552
2779
  for (const pid of candidates) {
2553
2780
  if (workerCandidates.has(pid)) continue;
2781
+ const comm = pidCommand(pid);
2782
+ if (!force && isForeignPortHolder(comm)) {
2783
+ skippedForeign.push({
2784
+ pid,
2785
+ comm
2786
+ });
2787
+ continue;
2788
+ }
2554
2789
  const s = p.spinner();
2555
2790
  s.start(`Stopping iii-engine (pid ${pid})...`);
2556
2791
  const ok = await signalAndWait(pid, "SIGTERM", 3e3);
@@ -2560,6 +2795,11 @@ async function runStop() {
2560
2795
  clearEnginePidfile();
2561
2796
  clearEngineState();
2562
2797
  clearWorkerPidfile();
2798
+ if (skippedForeign.length > 0) {
2799
+ const list = skippedForeign.map((sf) => ` pid ${sf.pid} ${sf.comm}`).join("\n");
2800
+ p.log.error(`Refused to signal process(es) holding :${port} that are not the iii engine:\n${list}\n\nIf the engine runs in Docker, stop it there:\n docker compose ps && docker compose rm -s -f <service>\n\nOr re-run with --force to signal them anyway.`);
2801
+ process.exit(1);
2802
+ }
2563
2803
  if (!allStopped) {
2564
2804
  p.log.error("One or more processes survived SIGKILL. Investigate with `ps`.");
2565
2805
  process.exit(1);
@@ -2567,14 +2807,18 @@ async function runStop() {
2567
2807
  p.outro("Stopped. Memories persisted to disk; restart anytime with: npx @agentmemory/agentmemory");
2568
2808
  }
2569
2809
  async function runMcp() {
2570
- await import("./standalone-BORXC71-.mjs");
2810
+ await import("./standalone-BqNJDJ04.mjs");
2571
2811
  }
2572
2812
  async function runConnectCmd() {
2573
- const { runConnect } = await import("./connect-bmZ5eqYN.mjs").then((n) => n.t);
2813
+ const { runConnect } = await import("./connect-Qaeq15TQ.mjs").then((n) => n.n);
2574
2814
  await runConnect(args.slice(1));
2575
2815
  }
2576
2816
  async function runImportJsonl() {
2577
- const VALUE_FLAGS = new Set(["--port", "--tools"]);
2817
+ const VALUE_FLAGS = new Set([
2818
+ "--port",
2819
+ "--tools",
2820
+ "--data-dir"
2821
+ ]);
2578
2822
  let maxFiles;
2579
2823
  const tail = args.slice(1);
2580
2824
  const positional = [];
@@ -2786,7 +3030,7 @@ async function runRemove() {
2786
3030
  }
2787
3031
  p.outro("Done. agentmemory cleanly removed. The npm package itself: npm uninstall -g @agentmemory/agentmemory");
2788
3032
  }
2789
- ({
3033
+ const commands = {
2790
3034
  init: runInit,
2791
3035
  connect: runConnectCmd,
2792
3036
  status: runStatus,
@@ -2797,7 +3041,13 @@ async function runRemove() {
2797
3041
  remove: runRemove,
2798
3042
  mcp: runMcp,
2799
3043
  "import-jsonl": runImportJsonl
2800
- }[args[0] ?? ""] ?? main)().catch((err) => {
3044
+ };
3045
+ const first = args[0] ?? "";
3046
+ async function unknownCommand() {
3047
+ p.log.error(`Unknown command: ${first}. Supported: ${Object.keys(commands).join(", ")}. Run \`agentmemory\` with no arguments to start the memory server, or \`agentmemory --help\` for usage.`);
3048
+ process.exit(1);
3049
+ }
3050
+ (commands[first] ?? (first && !first.startsWith("-") ? unknownCommand : main))().catch((err) => {
2801
3051
  p.log.error(err instanceof Error ? err.message : String(err));
2802
3052
  process.exit(1);
2803
3053
  });