ziiagentmemory 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/.env.example +175 -0
  2. package/AGENTS.md +124 -0
  3. package/LICENSE +190 -0
  4. package/README.md +1546 -0
  5. package/dist/cli.d.mts +5 -0
  6. package/dist/cli.d.mts.map +1 -0
  7. package/dist/cli.mjs +2859 -0
  8. package/dist/cli.mjs.map +1 -0
  9. package/dist/config-ChC9EtgV.mjs +259 -0
  10. package/dist/config-ChC9EtgV.mjs.map +1 -0
  11. package/dist/connect-JnsJXc68.mjs +1075 -0
  12. package/dist/connect-JnsJXc68.mjs.map +1 -0
  13. package/dist/hooks/notification.d.mts +1 -0
  14. package/dist/hooks/notification.mjs +74 -0
  15. package/dist/hooks/notification.mjs.map +1 -0
  16. package/dist/hooks/post-commit.d.mts +1 -0
  17. package/dist/hooks/post-commit.mjs +101 -0
  18. package/dist/hooks/post-commit.mjs.map +1 -0
  19. package/dist/hooks/post-tool-failure.d.mts +1 -0
  20. package/dist/hooks/post-tool-failure.mjs +75 -0
  21. package/dist/hooks/post-tool-failure.mjs.map +1 -0
  22. package/dist/hooks/post-tool-use.d.mts +1 -0
  23. package/dist/hooks/post-tool-use.mjs +120 -0
  24. package/dist/hooks/post-tool-use.mjs.map +1 -0
  25. package/dist/hooks/pre-compact.d.mts +1 -0
  26. package/dist/hooks/pre-compact.mjs +78 -0
  27. package/dist/hooks/pre-compact.mjs.map +1 -0
  28. package/dist/hooks/pre-tool-use.d.mts +1 -0
  29. package/dist/hooks/pre-tool-use.mjs +84 -0
  30. package/dist/hooks/pre-tool-use.mjs.map +1 -0
  31. package/dist/hooks/prompt-submit.d.mts +1 -0
  32. package/dist/hooks/prompt-submit.mjs +67 -0
  33. package/dist/hooks/prompt-submit.mjs.map +1 -0
  34. package/dist/hooks/session-end.d.mts +1 -0
  35. package/dist/hooks/session-end.mjs +60 -0
  36. package/dist/hooks/session-end.mjs.map +1 -0
  37. package/dist/hooks/session-start.d.mts +1 -0
  38. package/dist/hooks/session-start.mjs +85 -0
  39. package/dist/hooks/session-start.mjs.map +1 -0
  40. package/dist/hooks/stop.d.mts +1 -0
  41. package/dist/hooks/stop.mjs +44 -0
  42. package/dist/hooks/stop.mjs.map +1 -0
  43. package/dist/hooks/subagent-start.d.mts +1 -0
  44. package/dist/hooks/subagent-start.mjs +73 -0
  45. package/dist/hooks/subagent-start.mjs.map +1 -0
  46. package/dist/hooks/subagent-stop.d.mts +1 -0
  47. package/dist/hooks/subagent-stop.mjs +74 -0
  48. package/dist/hooks/subagent-stop.mjs.map +1 -0
  49. package/dist/hooks/task-completed.d.mts +1 -0
  50. package/dist/hooks/task-completed.mjs +73 -0
  51. package/dist/hooks/task-completed.mjs.map +1 -0
  52. package/dist/image-refs-BKLmp_sD.mjs +34 -0
  53. package/dist/image-refs-BKLmp_sD.mjs.map +1 -0
  54. package/dist/image-refs-C2YU7BIm.mjs +34 -0
  55. package/dist/image-refs-C2YU7BIm.mjs.map +1 -0
  56. package/dist/index.d.mts +2 -0
  57. package/dist/index.mjs +22324 -0
  58. package/dist/index.mjs.map +1 -0
  59. package/dist/keyed-mutex-DTHtcZrt.mjs +88 -0
  60. package/dist/keyed-mutex-DTHtcZrt.mjs.map +1 -0
  61. package/dist/logger-BEB7pCI9.mjs +43 -0
  62. package/dist/logger-BEB7pCI9.mjs.map +1 -0
  63. package/dist/schema-CNiCWzV9.mjs +78 -0
  64. package/dist/schema-CNiCWzV9.mjs.map +1 -0
  65. package/dist/src-Cr3pH_uH.mjs +20745 -0
  66. package/dist/src-Cr3pH_uH.mjs.map +1 -0
  67. package/dist/standalone-C6KzNkt5.mjs +700 -0
  68. package/dist/standalone-C6KzNkt5.mjs.map +1 -0
  69. package/dist/standalone.d.mts +25 -0
  70. package/dist/standalone.d.mts.map +1 -0
  71. package/dist/standalone.mjs +1867 -0
  72. package/dist/standalone.mjs.map +1 -0
  73. package/dist/tools-registry-DCt2KAWA.mjs +1145 -0
  74. package/dist/tools-registry-DCt2KAWA.mjs.map +1 -0
  75. package/docker-compose.yml +47 -0
  76. package/iii-config.docker.yaml +53 -0
  77. package/iii-config.yaml +61 -0
  78. package/package.json +99 -0
  79. package/plugin/.claude-plugin/plugin.json +13 -0
  80. package/plugin/.codex-plugin/plugin.json +15 -0
  81. package/plugin/.mcp.copilot.json +15 -0
  82. package/plugin/.mcp.json +13 -0
  83. package/plugin/hooks/hooks.codex.json +67 -0
  84. package/plugin/hooks/hooks.copilot.json +72 -0
  85. package/plugin/hooks/hooks.json +125 -0
  86. package/plugin/opencode/README.md +229 -0
  87. package/plugin/opencode/agentmemory-capture.ts +687 -0
  88. package/plugin/opencode/commands/recall.md +19 -0
  89. package/plugin/opencode/commands/remember.md +19 -0
  90. package/plugin/opencode/plugin.json +12 -0
  91. package/plugin/plugin.json +15 -0
  92. package/plugin/scripts/diagnostics.mjs +551 -0
  93. package/plugin/scripts/notification.d.mts +1 -0
  94. package/plugin/scripts/notification.mjs +74 -0
  95. package/plugin/scripts/notification.mjs.map +1 -0
  96. package/plugin/scripts/post-commit.d.mts +1 -0
  97. package/plugin/scripts/post-commit.mjs +101 -0
  98. package/plugin/scripts/post-commit.mjs.map +1 -0
  99. package/plugin/scripts/post-tool-failure.d.mts +1 -0
  100. package/plugin/scripts/post-tool-failure.mjs +75 -0
  101. package/plugin/scripts/post-tool-failure.mjs.map +1 -0
  102. package/plugin/scripts/post-tool-use.d.mts +1 -0
  103. package/plugin/scripts/post-tool-use.mjs +120 -0
  104. package/plugin/scripts/post-tool-use.mjs.map +1 -0
  105. package/plugin/scripts/pre-compact.d.mts +1 -0
  106. package/plugin/scripts/pre-compact.mjs +78 -0
  107. package/plugin/scripts/pre-compact.mjs.map +1 -0
  108. package/plugin/scripts/pre-tool-use.d.mts +1 -0
  109. package/plugin/scripts/pre-tool-use.mjs +84 -0
  110. package/plugin/scripts/pre-tool-use.mjs.map +1 -0
  111. package/plugin/scripts/prompt-submit.d.mts +1 -0
  112. package/plugin/scripts/prompt-submit.mjs +67 -0
  113. package/plugin/scripts/prompt-submit.mjs.map +1 -0
  114. package/plugin/scripts/session-end.d.mts +1 -0
  115. package/plugin/scripts/session-end.mjs +60 -0
  116. package/plugin/scripts/session-end.mjs.map +1 -0
  117. package/plugin/scripts/session-start.d.mts +1 -0
  118. package/plugin/scripts/session-start.mjs +85 -0
  119. package/plugin/scripts/session-start.mjs.map +1 -0
  120. package/plugin/scripts/stop.d.mts +1 -0
  121. package/plugin/scripts/stop.mjs +44 -0
  122. package/plugin/scripts/stop.mjs.map +1 -0
  123. package/plugin/scripts/subagent-start.d.mts +1 -0
  124. package/plugin/scripts/subagent-start.mjs +73 -0
  125. package/plugin/scripts/subagent-start.mjs.map +1 -0
  126. package/plugin/scripts/subagent-stop.d.mts +1 -0
  127. package/plugin/scripts/subagent-stop.mjs +74 -0
  128. package/plugin/scripts/subagent-stop.mjs.map +1 -0
  129. package/plugin/scripts/task-completed.d.mts +1 -0
  130. package/plugin/scripts/task-completed.mjs +73 -0
  131. package/plugin/scripts/task-completed.mjs.map +1 -0
  132. package/plugin/skills/_shared/TROUBLESHOOTING.md +38 -0
  133. package/plugin/skills/agentmemory-agents/REFERENCE.md +28 -0
  134. package/plugin/skills/agentmemory-agents/SKILL.md +34 -0
  135. package/plugin/skills/agentmemory-architecture/SKILL.md +33 -0
  136. package/plugin/skills/agentmemory-config/REFERENCE.md +42 -0
  137. package/plugin/skills/agentmemory-config/SKILL.md +37 -0
  138. package/plugin/skills/agentmemory-hooks/REFERENCE.md +20 -0
  139. package/plugin/skills/agentmemory-hooks/SKILL.md +39 -0
  140. package/plugin/skills/agentmemory-mcp-tools/REFERENCE.md +65 -0
  141. package/plugin/skills/agentmemory-mcp-tools/SKILL.md +39 -0
  142. package/plugin/skills/agentmemory-rest-api/REFERENCE.md +129 -0
  143. package/plugin/skills/agentmemory-rest-api/SKILL.md +43 -0
  144. package/plugin/skills/commit-context/EXAMPLES.md +71 -0
  145. package/plugin/skills/commit-context/SKILL.md +64 -0
  146. package/plugin/skills/commit-history/EXAMPLES.md +71 -0
  147. package/plugin/skills/commit-history/SKILL.md +62 -0
  148. package/plugin/skills/forget/EXAMPLES.md +75 -0
  149. package/plugin/skills/forget/SKILL.md +66 -0
  150. package/plugin/skills/handoff/EXAMPLES.md +56 -0
  151. package/plugin/skills/handoff/SKILL.md +68 -0
  152. package/plugin/skills/recall/EXAMPLES.md +74 -0
  153. package/plugin/skills/recall/SKILL.md +60 -0
  154. package/plugin/skills/recap/EXAMPLES.md +60 -0
  155. package/plugin/skills/recap/SKILL.md +63 -0
  156. package/plugin/skills/remember/EXAMPLES.md +67 -0
  157. package/plugin/skills/remember/SKILL.md +61 -0
  158. package/plugin/skills/session-history/EXAMPLES.md +59 -0
  159. package/plugin/skills/session-history/SKILL.md +61 -0
  160. package/plugin/skills/write-agentmemory-skill/SKILL.md +40 -0
package/dist/cli.mjs ADDED
@@ -0,0 +1,2859 @@
1
+ #!/usr/bin/env node
2
+ import { a as runAdapter, i as resolveAdapter, r as knownAgents, t as ADAPTERS } from "./connect-JnsJXc68.mjs";
3
+ import { i as generateId } from "./schema-CNiCWzV9.mjs";
4
+ import { r as setBootVerbose } from "./logger-BEB7pCI9.mjs";
5
+ import { i as VERSION, n as getAllTools, t as ESSENTIAL_TOOLS } from "./tools-registry-DCt2KAWA.mjs";
6
+ import { execFileSync, spawn, spawnSync } from "node:child_process";
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, join } from "node:path";
9
+ import { fileURLToPath } from "node:url";
10
+ import { homedir, platform } from "node:os";
11
+ import * as p from "@clack/prompts";
12
+ import pc from "picocolors";
13
+ import { copyFile, mkdir } from "node:fs/promises";
14
+ //#region src/cli/doctor-diagnostics.ts
15
+ /** Common placeholder values shipped in .env.example. */
16
+ const PLACEHOLDER_VALUES = new Set([
17
+ "",
18
+ "your-key-here",
19
+ "sk-ant-...",
20
+ "sk-...",
21
+ "changeme",
22
+ "todo",
23
+ "xxx"
24
+ ]);
25
+ const PROVIDER_KEY_NAMES = [
26
+ "ANTHROPIC_API_KEY",
27
+ "OPENAI_API_KEY",
28
+ "GEMINI_API_KEY",
29
+ "GOOGLE_API_KEY",
30
+ "OPENROUTER_API_KEY",
31
+ "MINIMAX_API_KEY"
32
+ ];
33
+ function parseEnvFile(content) {
34
+ const out = {};
35
+ for (const rawLine of content.split(/\r?\n/)) {
36
+ const line = rawLine.trim();
37
+ if (!line || line.startsWith("#")) continue;
38
+ const eq = line.indexOf("=");
39
+ if (eq < 0) continue;
40
+ const key = line.slice(0, eq).trim();
41
+ let value = line.slice(eq + 1).trim();
42
+ if (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1);
43
+ out[key] = value;
44
+ }
45
+ return out;
46
+ }
47
+ /** Returns the list of provider keys that look real (non-placeholder). */
48
+ function realProviderKeys(env) {
49
+ return PROVIDER_KEY_NAMES.filter((k) => {
50
+ const v = (env[k] ?? "").trim();
51
+ if (!v) return false;
52
+ if (PLACEHOLDER_VALUES.has(v.toLowerCase())) return false;
53
+ if (/^x+$/i.test(v.replace(/[-_]/g, ""))) return false;
54
+ return true;
55
+ });
56
+ }
57
+ /** Returns the list of provider key NAMES that exist but are placeholders. */
58
+ function placeholderProviderKeys(env) {
59
+ return PROVIDER_KEY_NAMES.filter((k) => {
60
+ const v = (env[k] ?? "").trim();
61
+ if (!v) return false;
62
+ if (PLACEHOLDER_VALUES.has(v.toLowerCase())) return true;
63
+ if (/^x+$/i.test(v.replace(/[-_]/g, ""))) return true;
64
+ return false;
65
+ });
66
+ }
67
+ function buildDiagnostics(effects) {
68
+ return [
69
+ {
70
+ id: "env-missing",
71
+ message: "~/.agentmemory/.env is missing.",
72
+ fixPreview: "Copy .env.example into ~/.agentmemory/.env (your keys file).",
73
+ moreInfo: "agentmemory reads provider API keys (Anthropic, OpenAI, Gemini, …) from ~/.agentmemory/.env. Without this file the daemon falls back to BM25-only search and no LLM-backed enrichment runs.",
74
+ check: async () => ({
75
+ ok: effects.envFileExists(),
76
+ detail: effects.envFileExists() ? void 0 : "no env file"
77
+ }),
78
+ fix: () => effects.runInit()
79
+ },
80
+ {
81
+ id: "no-llm-provider-key",
82
+ message: "No LLM provider API key found in ~/.agentmemory/.env.",
83
+ fixPreview: "Open ~/.agentmemory/.env in $EDITOR and paste your key, then re-check.",
84
+ moreInfo: "Set at least one of: ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY, OPENROUTER_API_KEY, MINIMAX_API_KEY. The daemon picks the first that resolves to a real (non-placeholder) value at startup.",
85
+ check: async () => {
86
+ if (!effects.envFileExists()) return {
87
+ ok: false,
88
+ detail: "env file missing (run env-missing fix first)"
89
+ };
90
+ const real = realProviderKeys(effects.readEnvFile());
91
+ return {
92
+ ok: real.length > 0,
93
+ detail: real.length > 0 ? `found: ${real.join(", ")}` : "no provider key set"
94
+ };
95
+ },
96
+ fix: (ctx) => effects.openEditor(ctx.envPath)
97
+ },
98
+ {
99
+ id: "engine-version-mismatch",
100
+ message: "iii binary on PATH doesn't match the version agentmemory pins to.",
101
+ fixPreview: "Re-run the iii installer for the pinned version and restart the engine.",
102
+ moreInfo: "agentmemory pins the iii engine to a specific release because newer engines use a different worker model. Running a mismatched binary surfaces as EPIPE reconnect loops and empty search results.",
103
+ check: async (ctx) => {
104
+ const bin = effects.findIiiBinary();
105
+ if (!bin) return {
106
+ ok: false,
107
+ detail: "iii not on PATH"
108
+ };
109
+ const v = effects.iiiBinaryVersion(bin);
110
+ if (!v) return {
111
+ ok: false,
112
+ detail: "iii on PATH but --version failed"
113
+ };
114
+ return {
115
+ ok: v === ctx.pinnedVersion,
116
+ detail: `${v} (pinned ${ctx.pinnedVersion})`
117
+ };
118
+ },
119
+ fix: async () => {
120
+ const r = await effects.runIiiInstaller();
121
+ if (!r.ok) return r;
122
+ await effects.runStop();
123
+ return effects.runStart();
124
+ }
125
+ },
126
+ {
127
+ id: "viewer-unreachable",
128
+ message: "Viewer port not reachable.",
129
+ fixPreview: "Stop the engine, restart it, and retry the viewer probe.",
130
+ moreInfo: "The viewer is served on REST port + 2 (default 3113). If it never came up the most common cause is port collision; a sibling PR ships auto-bump for this case. If that lands first this check just verifies; otherwise restart the engine to retry binding.",
131
+ check: async () => ({
132
+ ok: await effects.viewerReachable(),
133
+ detail: void 0
134
+ }),
135
+ fix: async () => {
136
+ const stopped = await effects.runStop();
137
+ if (!stopped.ok) return stopped;
138
+ return effects.runStart();
139
+ }
140
+ },
141
+ {
142
+ id: "stale-pidfile",
143
+ message: "Stale pidfile: pid recorded but the process is gone.",
144
+ fixPreview: "Clear ~/.agentmemory/iii.pid + engine-state.json, then restart.",
145
+ moreInfo: "When the engine crashes hard (kill -9, OOM, host reboot) the pidfile sticks around. agentmemory refuses to start a second engine on top of a stale pid, so this state must be cleared explicitly.",
146
+ check: async () => {
147
+ if (!effects.pidfileExists()) return {
148
+ ok: true,
149
+ detail: "no pidfile"
150
+ };
151
+ const alive = effects.pidfilePidIsAlive();
152
+ if (alive === null) return {
153
+ ok: true,
154
+ detail: "pidfile unreadable"
155
+ };
156
+ return {
157
+ ok: alive,
158
+ detail: alive ? "pid is alive" : "pid is gone"
159
+ };
160
+ },
161
+ fix: async () => {
162
+ effects.clearEnginePidAndState();
163
+ return effects.runStart();
164
+ }
165
+ },
166
+ {
167
+ id: "env-placeholder-keys",
168
+ message: "~/.agentmemory/.env contains placeholder/empty API keys.",
169
+ fixPreview: "Open ~/.agentmemory/.env in $EDITOR to paste real values.",
170
+ moreInfo: "Lines like ANTHROPIC_API_KEY=sk-ant-... or =your-key-here are treated as absent. The daemon will fall back to BM25-only search. Replace placeholders with real keys or comment the line out.",
171
+ check: async () => {
172
+ if (!effects.envFileExists()) return {
173
+ ok: true,
174
+ detail: "env file missing (handled by env-missing)"
175
+ };
176
+ const placeholders = placeholderProviderKeys(effects.readEnvFile());
177
+ return {
178
+ ok: placeholders.length === 0,
179
+ detail: placeholders.length === 0 ? void 0 : `placeholder: ${placeholders.join(", ")}`
180
+ };
181
+ },
182
+ fix: (ctx) => effects.openEditor(ctx.envPath)
183
+ },
184
+ {
185
+ id: "iii-on-path-not-local-bin",
186
+ message: "iii is on PATH but not at agentmemory's private install path.",
187
+ fixPreview: "Install the pinned version to ~/.agentmemory/bin — won't touch your PATH.",
188
+ moreInfo: "agentmemory installs its pinned engine to ~/.agentmemory/bin/iii so a user-managed iii on PATH (homebrew, cargo, manual install) stays untouched. When agentmemory needs the pin and PATH doesn't have it, it falls back to the private install. If neither exists, run the installer.",
189
+ manualOnly: true,
190
+ check: async () => {
191
+ const bin = effects.findIiiBinary();
192
+ if (!bin) return {
193
+ ok: true,
194
+ detail: "iii not on PATH (handled elsewhere)"
195
+ };
196
+ const localBin = effects.localBinIiiPath();
197
+ return {
198
+ ok: bin === localBin,
199
+ detail: bin === localBin ? void 0 : `iii at: ${bin}`
200
+ };
201
+ },
202
+ fix: async () => effects.runIiiInstaller().then((r) => ({
203
+ ok: r.ok,
204
+ message: r.message ?? "Installer wrote to ~/.agentmemory/bin/iii. Your PATH wasn't modified."
205
+ }))
206
+ }
207
+ ];
208
+ }
209
+ /**
210
+ * Dry-run output: each failing check's fix preview, prefixed by the diagnostic
211
+ * message. Pure function so we can snapshot-test the format.
212
+ */
213
+ function dryRunPlan(ctx, results) {
214
+ const lines = [];
215
+ let n = 0;
216
+ for (const { diagnostic, status } of results) {
217
+ if (status.ok) continue;
218
+ n++;
219
+ lines.push(`${n}. [${diagnostic.id}] ${diagnostic.message}`);
220
+ lines.push(` would fix: ${diagnostic.fixPreview}`);
221
+ if (status.detail) lines.push(` detail: ${status.detail}`);
222
+ }
223
+ if (lines.length === 0) lines.push(`All checks passing for ${ctx.baseUrl} — no fixes to run.`);
224
+ return lines;
225
+ }
226
+ //#endregion
227
+ //#region src/cli/remove-plan.ts
228
+ function pidfilePath(home) {
229
+ return join(home, ".agentmemory", "iii.pid");
230
+ }
231
+ function enginePath(home) {
232
+ return join(home, ".agentmemory", "engine-state.json");
233
+ }
234
+ function envPath(home) {
235
+ return join(home, ".agentmemory", ".env");
236
+ }
237
+ function preferencesPath(home) {
238
+ return join(home, ".agentmemory", "preferences.json");
239
+ }
240
+ function backupsDir(home) {
241
+ return join(home, ".agentmemory", "backups");
242
+ }
243
+ function dataDir(home) {
244
+ return join(home, ".agentmemory", "data");
245
+ }
246
+ function iiiBinFile() {
247
+ return process.platform === "win32" ? "iii.exe" : "iii";
248
+ }
249
+ function legacyLocalBinIii(home) {
250
+ return join(home, ".local", "bin", iiiBinFile());
251
+ }
252
+ function privateIiiBin(home) {
253
+ return join(home, ".agentmemory", "bin", iiiBinFile());
254
+ }
255
+ function safeSize(path) {
256
+ try {
257
+ return statSync(path).size;
258
+ } catch {
259
+ return -1;
260
+ }
261
+ }
262
+ function pathExists(path) {
263
+ try {
264
+ return existsSync(path);
265
+ } catch {
266
+ return false;
267
+ }
268
+ }
269
+ /**
270
+ * Build the destruction plan for `agentmemory remove`.
271
+ *
272
+ * Plan items are returned regardless of whether `applicable` is true — the
273
+ * caller can decide whether to skip-and-log or hide entirely. This keeps
274
+ * the structure stable for tests.
275
+ */
276
+ function buildRemovePlan(ctx, options) {
277
+ const { home, pinnedVersion, localBinIiiVersion, connectManifest } = ctx;
278
+ const plan = [];
279
+ plan.push({
280
+ id: "stop-engine",
281
+ description: "Stop running iii-engine (if any) cleanly",
282
+ path: null,
283
+ alwaysAsk: false,
284
+ applicable: pathExists(pidfilePath(home)) || pathExists(enginePath(home)),
285
+ sizeBytes: -1
286
+ });
287
+ plan.push({
288
+ id: "pidfile",
289
+ description: "Delete pidfile",
290
+ path: pidfilePath(home),
291
+ alwaysAsk: false,
292
+ applicable: pathExists(pidfilePath(home)),
293
+ sizeBytes: safeSize(pidfilePath(home))
294
+ });
295
+ plan.push({
296
+ id: "engine-state",
297
+ description: "Delete engine-state.json",
298
+ path: enginePath(home),
299
+ alwaysAsk: false,
300
+ applicable: pathExists(enginePath(home)),
301
+ sizeBytes: safeSize(enginePath(home))
302
+ });
303
+ plan.push({
304
+ id: "env",
305
+ description: "Delete .env (your API keys) — will ask separately",
306
+ path: envPath(home),
307
+ alwaysAsk: true,
308
+ applicable: !options.keepData && pathExists(envPath(home)),
309
+ sizeBytes: safeSize(envPath(home))
310
+ });
311
+ plan.push({
312
+ id: "preferences",
313
+ description: "Delete preferences.json",
314
+ path: preferencesPath(home),
315
+ alwaysAsk: false,
316
+ applicable: !options.keepData && pathExists(preferencesPath(home)),
317
+ sizeBytes: safeSize(preferencesPath(home))
318
+ });
319
+ plan.push({
320
+ id: "backups",
321
+ description: "Delete backups/ directory (connect manifest + backups)",
322
+ path: backupsDir(home),
323
+ alwaysAsk: false,
324
+ applicable: !options.keepData && pathExists(backupsDir(home)),
325
+ sizeBytes: -1
326
+ });
327
+ if (connectManifest?.installed?.length) for (const entry of connectManifest.installed) plan.push({
328
+ id: `connect:${entry.target}`,
329
+ description: `Remove agent connection (${entry.agent ?? "unknown"})`,
330
+ path: entry.target,
331
+ alwaysAsk: false,
332
+ applicable: pathExists(entry.target),
333
+ sizeBytes: safeSize(entry.target)
334
+ });
335
+ const privIii = privateIiiBin(home);
336
+ if (pathExists(privIii)) plan.push({
337
+ id: "private-bin-iii",
338
+ description: `Delete ~/.agentmemory/bin/iii (agentmemory's private install)`,
339
+ path: privIii,
340
+ alwaysAsk: false,
341
+ applicable: true,
342
+ sizeBytes: safeSize(privIii)
343
+ });
344
+ const legacyIii = legacyLocalBinIii(home);
345
+ if (pathExists(legacyIii)) {
346
+ const matches = localBinIiiVersion === pinnedVersion;
347
+ plan.push({
348
+ id: "legacy-local-bin-iii",
349
+ description: matches ? `Delete ~/.local/bin/iii (legacy install location, matches pinned v${pinnedVersion})` : `Delete ~/.local/bin/iii (legacy install location, version ${localBinIiiVersion ?? "unknown"} != pinned v${pinnedVersion}) — will ask`,
350
+ path: legacyIii,
351
+ alwaysAsk: !matches,
352
+ applicable: true,
353
+ sizeBytes: safeSize(legacyIii)
354
+ });
355
+ }
356
+ plan.push({
357
+ id: "data-dir",
358
+ description: "Delete memory data directory (~/.agentmemory/data/) — will ask separately",
359
+ path: dataDir(home),
360
+ alwaysAsk: true,
361
+ applicable: !options.keepData && pathExists(dataDir(home)),
362
+ sizeBytes: -1
363
+ });
364
+ return plan;
365
+ }
366
+ /** Format a plan for the user — one line per item. */
367
+ function formatPlan(plan) {
368
+ return plan.filter((p) => p.applicable).map((p, i) => {
369
+ const tag = p.alwaysAsk ? " [asks]" : "";
370
+ const sz = p.sizeBytes > 0 ? ` (${humanBytes(p.sizeBytes)})` : "";
371
+ return ` ${i + 1}. ${p.description}${tag}${sz}${p.path ? `\n ${p.path}` : ""}`;
372
+ }).join("\n");
373
+ }
374
+ function humanBytes(n) {
375
+ if (n < 1024) return `${n} B`;
376
+ if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;
377
+ return `${(n / (1024 * 1024)).toFixed(1)} MB`;
378
+ }
379
+ //#endregion
380
+ //#region src/cli/splash.ts
381
+ const IS_COLOR_TTY = !!process.stdout.isTTY && !process.env["NO_COLOR"];
382
+ function accent(s) {
383
+ return IS_COLOR_TTY ? `\x1b[38;5;208m${s}\x1b[0m` : s;
384
+ }
385
+ function dim(s) {
386
+ return IS_COLOR_TTY ? `\x1b[2m${s}\x1b[22m` : s;
387
+ }
388
+ function bold(s) {
389
+ return IS_COLOR_TTY ? `\x1b[1m${s}\x1b[22m` : s;
390
+ }
391
+ function getTerminalWidth() {
392
+ const w = process.stdout.columns;
393
+ return typeof w === "number" && w > 0 ? w : 80;
394
+ }
395
+ const TAGLINE = "Persistent memory for AI coding agents";
396
+ function fullBanner(version) {
397
+ const lines = ["", ...[
398
+ " _ ",
399
+ " __ _ __ _ ___ _ __ | |_ _ __ ___ ___ _ __ ___ ___ _ __ _ _ ",
400
+ " / _` |/ _` |/ _ \\ '_ \\| __| '_ ` _ \\ / _ \\ '_ ` _ \\ / _ \\| '__| | | |",
401
+ "| (_| | (_| | __/ | | | |_| | | | | | __/ | | | | | (_) | | | |_| |",
402
+ " \\__,_|\\__, |\\___|_| |_|\\__|_| |_| |_|\\___|_| |_| |_|\\___/|_| \\__, |",
403
+ " |___/ |___/ "
404
+ ].map((line) => " " + accent(line))];
405
+ lines.push("");
406
+ lines.push(" " + bold(TAGLINE) + " " + dim(`v${version}`));
407
+ lines.push("");
408
+ return lines.join("\n");
409
+ }
410
+ function compactBanner(version) {
411
+ return [
412
+ "",
413
+ " " + bold(accent("agentmemory")),
414
+ " " + dim(`v${version} · ${TAGLINE}`),
415
+ ""
416
+ ].join("\n");
417
+ }
418
+ function minimalBanner(version) {
419
+ return `${accent("agentmemory")} ${dim(`v${version}`)}`;
420
+ }
421
+ function renderSplash(version) {
422
+ const width = getTerminalWidth();
423
+ let out;
424
+ if (width >= 120) out = fullBanner(version);
425
+ else if (width >= 80) out = compactBanner(version);
426
+ else out = minimalBanner(version);
427
+ process.stdout.write(out + "\n");
428
+ }
429
+ //#endregion
430
+ //#region src/cli/preferences.ts
431
+ const DEFAULTS = {
432
+ schemaVersion: 1,
433
+ lastAgent: null,
434
+ lastAgents: [],
435
+ lastProvider: null,
436
+ skipSplash: false,
437
+ skipNpxHint: false,
438
+ skipGlobalInstall: false,
439
+ skipConsoleInstall: false,
440
+ firstRunAt: null,
441
+ injectContextChosen: false
442
+ };
443
+ function prefsDir() {
444
+ return join(homedir(), ".agentmemory");
445
+ }
446
+ function prefsPath() {
447
+ return join(prefsDir(), "preferences.json");
448
+ }
449
+ function readPrefs() {
450
+ try {
451
+ if (!existsSync(prefsPath())) return { ...DEFAULTS };
452
+ const raw = readFileSync(prefsPath(), "utf-8");
453
+ const parsed = JSON.parse(raw);
454
+ return {
455
+ ...DEFAULTS,
456
+ ...parsed,
457
+ schemaVersion: 1
458
+ };
459
+ } catch {
460
+ return { ...DEFAULTS };
461
+ }
462
+ }
463
+ function writePrefs(p) {
464
+ try {
465
+ mkdirSync(prefsDir(), { recursive: true });
466
+ const next = {
467
+ ...readPrefs(),
468
+ ...p,
469
+ schemaVersion: 1
470
+ };
471
+ const target = prefsPath();
472
+ const tmp = target + ".tmp";
473
+ const fd = openSync(tmp, "w", 384);
474
+ try {
475
+ writeSync(fd, JSON.stringify(next, null, 2) + "\n");
476
+ try {
477
+ fsyncSync(fd);
478
+ } catch {}
479
+ } finally {
480
+ closeSync(fd);
481
+ }
482
+ renameSync(tmp, target);
483
+ } catch {}
484
+ }
485
+ function resetPrefs() {
486
+ try {
487
+ unlinkSync(prefsPath());
488
+ } catch {}
489
+ }
490
+ function isFirstRun() {
491
+ if (!existsSync(prefsPath())) return true;
492
+ return readPrefs().firstRunAt === null;
493
+ }
494
+ //#endregion
495
+ //#region src/cli/onboarding.ts
496
+ const __dirname$1 = dirname(fileURLToPath(import.meta.url));
497
+ const AGENT_GLYPH = {
498
+ "claude-code": "⟁",
499
+ "copilot-cli": "◈",
500
+ codex: "◎",
501
+ cursor: "◫",
502
+ "gemini-cli": "✦",
503
+ opencode: "⬡"
504
+ };
505
+ const PROVIDERS = [
506
+ {
507
+ value: "anthropic",
508
+ label: "Anthropic — claude",
509
+ envKey: "ANTHROPIC_API_KEY"
510
+ },
511
+ {
512
+ value: "openai",
513
+ label: "OpenAI — gpt",
514
+ envKey: "OPENAI_API_KEY"
515
+ },
516
+ {
517
+ value: "gemini",
518
+ label: "Google — gemini",
519
+ envKey: "GEMINI_API_KEY"
520
+ },
521
+ {
522
+ value: "openrouter",
523
+ label: "OpenRouter — multi-model",
524
+ envKey: "OPENROUTER_API_KEY"
525
+ },
526
+ {
527
+ value: "minimax",
528
+ label: "MiniMax — minimax-m1",
529
+ envKey: "MINIMAX_API_KEY"
530
+ },
531
+ {
532
+ value: "skip",
533
+ label: "Skip — BM25-only mode (no LLM key)",
534
+ envKey: null
535
+ }
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
+ };
544
+ function buildAgentOptions() {
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")];
551
+ }
552
+ function getInitialAgentValues(env = process.env) {
553
+ if (env["COPILOT_CLI"] === "1" || env["COPILOT_AGENT_SESSION_ID"]) return ["copilot-cli"];
554
+ return ["claude-code"];
555
+ }
556
+ function findEnvExample$1() {
557
+ const candidates = [
558
+ join(__dirname$1, "..", "..", ".env.example"),
559
+ join(__dirname$1, "..", ".env.example"),
560
+ join(__dirname$1, ".env.example"),
561
+ join(process.cwd(), ".env.example")
562
+ ];
563
+ for (const c of candidates) if (existsSync(c)) return c;
564
+ return null;
565
+ }
566
+ async function seedEnvFile(provider) {
567
+ const target = join(homedir(), ".agentmemory", ".env");
568
+ await mkdir(dirname(target), { recursive: true });
569
+ const template = findEnvExample$1();
570
+ if (template && !existsSync(target)) try {
571
+ await copyFile(template, target, constants.COPYFILE_EXCL);
572
+ } catch (err) {
573
+ if (err?.code !== "EEXIST") return null;
574
+ }
575
+ else if (!template && !existsSync(target)) {
576
+ const lines = [
577
+ "# agentmemory environment — uncomment what you need",
578
+ "# AGENTMEMORY_URL=http://localhost:3111",
579
+ ""
580
+ ];
581
+ const envKey = PROVIDERS.find((x) => x.value === provider)?.envKey;
582
+ if (envKey) lines.push(`# ${envKey}=`);
583
+ writeFileSync(target, lines.join("\n"), { mode: 384 });
584
+ }
585
+ return target;
586
+ }
587
+ function shouldSkipInteractiveOnboarding() {
588
+ const ci = process.env["CI"];
589
+ return process.stdin.isTTY !== true || process.stdout.isTTY !== true || ci !== void 0 && ci !== "" && ci !== "0" && ci.toLowerCase() !== "false";
590
+ }
591
+ function writeDefaultOnboardingPrefs() {
592
+ writePrefs({
593
+ lastAgent: null,
594
+ lastAgents: [],
595
+ lastProvider: null,
596
+ skipSplash: true,
597
+ firstRunAt: (/* @__PURE__ */ new Date()).toISOString()
598
+ });
599
+ return {
600
+ agents: [],
601
+ provider: null
602
+ };
603
+ }
604
+ async function runOnboarding() {
605
+ if (shouldSkipInteractiveOnboarding()) return writeDefaultOnboardingPrefs();
606
+ p.note([
607
+ "Welcome to agentmemory.",
608
+ "",
609
+ "Persistent memory for your AI coding agents. We'll pick which",
610
+ "agents to wire up and which provider (if any) handles compression",
611
+ "and consolidation. Either step can be changed later in ~/.agentmemory/.env."
612
+ ].join("\n"), "first-run setup");
613
+ const agentsPicked = await p.multiselect({
614
+ message: "Which agents will use agentmemory? (space to toggle, enter to confirm)",
615
+ options: buildAgentOptions(),
616
+ required: false,
617
+ initialValues: getInitialAgentValues()
618
+ });
619
+ if (p.isCancel(agentsPicked)) {
620
+ p.cancel("Setup cancelled. Re-run any time with: agentmemory --reset");
621
+ process.exit(0);
622
+ }
623
+ if ((agentsPicked ?? []).length > 0) p.note([
624
+ "━ how this works ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
625
+ "All selected agents share the same memory at :3111.",
626
+ "A memory saved by Claude Code is visible to Copilot + Codex + Cursor instantly.",
627
+ "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
628
+ ].join("\n"));
629
+ const providerPicked = await p.select({
630
+ message: "Which LLM provider should agentmemory use for compress/consolidate?",
631
+ options: PROVIDERS.map(({ value, label }) => ({
632
+ value,
633
+ label
634
+ })),
635
+ initialValue: "anthropic"
636
+ });
637
+ if (p.isCancel(providerPicked)) {
638
+ p.cancel("Setup cancelled. Re-run any time with: agentmemory --reset");
639
+ process.exit(0);
640
+ }
641
+ const provider = providerPicked === "skip" ? null : providerPicked;
642
+ const agents = agentsPicked ?? [];
643
+ if (provider) {
644
+ const hint = PROVIDER_COST_HINTS[provider];
645
+ if (hint) p.log.info(hint);
646
+ }
647
+ const envPath = await seedEnvFile(provider);
648
+ await maybePromptContextInjection(envPath);
649
+ writePrefs({
650
+ lastAgent: agents[0] ?? null,
651
+ lastAgents: agents,
652
+ lastProvider: provider,
653
+ skipSplash: true,
654
+ firstRunAt: (/* @__PURE__ */ new Date()).toISOString()
655
+ });
656
+ const lines = [`✓ Saved preferences to ${join(homedir(), ".agentmemory", "preferences.json")}`];
657
+ if (envPath) lines.push(`✓ Wrote ${envPath} (edit to add your API key)`);
658
+ else lines.push(`! Could not write ~/.agentmemory/.env — run \`agentmemory init\` after this completes.`);
659
+ if (provider) {
660
+ const envKey = PROVIDERS.find((x) => x.value === provider)?.envKey;
661
+ if (envKey) lines.push(` Uncomment ${envKey}= in that file to enable ${provider}.`);
662
+ } else lines.push(" No provider chosen — agentmemory will run in BM25-only mode.");
663
+ p.note(lines.join("\n"), "ready");
664
+ if (agents.length > 0) await wireSelectedAgents(agents);
665
+ return {
666
+ agents,
667
+ provider
668
+ };
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
+ }
697
+ async function wireSelectedAgents(agents) {
698
+ p.note("Wire selected agents now?", "next step");
699
+ const confirmed = await p.confirm({
700
+ message: "Run `agentmemory connect <agent>` for each selected agent now? [Y/n]",
701
+ initialValue: true
702
+ });
703
+ if (p.isCancel(confirmed) || confirmed === false) {
704
+ const cmds = agents.map((a) => ` agentmemory connect ${a}`);
705
+ p.note(["Wire later with:", ...cmds].join("\n"), "later");
706
+ return;
707
+ }
708
+ const wired = [];
709
+ const manual = [];
710
+ const failed = [];
711
+ for (const name of agents) {
712
+ const adapter = resolveAdapter(name);
713
+ if (!adapter) {
714
+ failed.push({
715
+ name,
716
+ reason: "no adapter available"
717
+ });
718
+ p.log.warn(`Wiring ${name}… no adapter available (skipped).`);
719
+ continue;
720
+ }
721
+ p.log.step(`Wiring ${name}...`);
722
+ let result;
723
+ try {
724
+ result = await runAdapter(adapter, {
725
+ dryRun: false,
726
+ force: false
727
+ });
728
+ } catch (err) {
729
+ const reason = err instanceof Error ? err.message : String(err);
730
+ failed.push({
731
+ name,
732
+ reason
733
+ });
734
+ p.log.error(`${name}: ${reason}`);
735
+ continue;
736
+ }
737
+ switch (result.kind) {
738
+ case "installed":
739
+ case "already-wired":
740
+ wired.push(name);
741
+ break;
742
+ case "stub":
743
+ manual.push({
744
+ name,
745
+ docs: adapter.docs
746
+ });
747
+ break;
748
+ case "skipped":
749
+ failed.push({
750
+ name,
751
+ reason: result.reason
752
+ });
753
+ break;
754
+ }
755
+ }
756
+ const summary = [];
757
+ if (wired.length > 0) summary.push(`Wired: ${wired.join(", ")}.`);
758
+ if (manual.length > 0 || failed.length > 0) {
759
+ const parts = [];
760
+ for (const m of manual) parts.push(`${m.name} (manual install required${m.docs ? ` — see ${m.docs}` : ""})`);
761
+ for (const f of failed) parts.push(`${f.name} (${f.reason})`);
762
+ summary.push(`Skipped/failed: ${parts.join(", ")}.`);
763
+ }
764
+ if (summary.length === 0) summary.push("No agents were wired.");
765
+ p.note(summary.join("\n"), "wire summary");
766
+ }
767
+ //#endregion
768
+ //#region src/cli.ts
769
+ const c = {
770
+ url: pc.cyan,
771
+ ok: pc.green,
772
+ warn: pc.yellow,
773
+ err: pc.red,
774
+ cmd: (s) => pc.bold(pc.cyan(s)),
775
+ label: pc.bold,
776
+ dim: pc.dim,
777
+ accent: (s) => pc.bold(pc.yellow(s))
778
+ };
779
+ const ALL_TOOLS_COUNT = getAllTools().length;
780
+ const CORE_TOOLS_COUNT = getAllTools().filter((t) => ESSENTIAL_TOOLS.has(t.name)).length;
781
+ const __dirname = dirname(fileURLToPath(import.meta.url));
782
+ const args = process.argv.slice(2);
783
+ const IS_WINDOWS = platform() === "win32";
784
+ const IS_VERBOSE = args.includes("--verbose") || args.includes("-v") || process.env["AGENTMEMORY_VERBOSE"] === "1" || process.env["AGENTMEMORY_VERBOSE"] === "true";
785
+ setBootVerbose(IS_VERBOSE);
786
+ const IS_RESET = args.includes("--reset");
787
+ if (args.includes("--version") || args.includes("-V")) {
788
+ process.stdout.write(`${VERSION}\n`);
789
+ process.exit(0);
790
+ }
791
+ const IIPINNED_VERSION = process.env["AGENTMEMORY_III_VERSION"] || "0.11.2";
792
+ function iiiReleaseAsset() {
793
+ const p = platform();
794
+ const a = process.arch;
795
+ if (p === "darwin" && a === "arm64") return "iii-aarch64-apple-darwin.tar.gz";
796
+ if (p === "darwin" && a === "x64") return "iii-x86_64-apple-darwin.tar.gz";
797
+ if (p === "linux" && a === "x64") return "iii-x86_64-unknown-linux-gnu.tar.gz";
798
+ if (p === "linux" && a === "arm64") return "iii-aarch64-unknown-linux-gnu.tar.gz";
799
+ if (p === "linux" && a === "arm") return "iii-armv7-unknown-linux-gnueabihf.tar.gz";
800
+ if (p === "win32" && a === "x64") return "iii-x86_64-pc-windows-msvc.zip";
801
+ if (p === "win32" && a === "arm64") return "iii-aarch64-pc-windows-msvc.zip";
802
+ return null;
803
+ }
804
+ function iiiReleaseUrl() {
805
+ const asset = iiiReleaseAsset();
806
+ if (!asset) return null;
807
+ return `https://github.com/iii-hq/iii/releases/download/iii/v${IIPINNED_VERSION}/${asset}`;
808
+ }
809
+ function vlog(msg) {
810
+ if (IS_VERBOSE) p.log.info(`[verbose] ${msg}`);
811
+ }
812
+ function wrapList(items, indent, width = 78) {
813
+ const lines = [];
814
+ let line = "";
815
+ for (const item of items) {
816
+ const joined = line ? `${line}, ${item}` : item;
817
+ if (line && indent + joined.length > width) {
818
+ lines.push(`${line},`);
819
+ line = item;
820
+ } else line = joined;
821
+ }
822
+ lines.push(line);
823
+ return lines.join(`\n${" ".repeat(indent)}`);
824
+ }
825
+ if (args.includes("--help") || args.includes("-h")) {
826
+ console.log(`
827
+ agentmemory — persistent memory for AI coding agents
828
+
829
+ Usage: agentmemory [command] [options]
830
+
831
+ Commands:
832
+ (default) Start agentmemory worker
833
+ init Copy bundled .env.example to ~/.agentmemory/.env if absent
834
+ connect [agent] Wire agentmemory into an installed agent
835
+ (${wrapList(knownAgents(), 21)}).
836
+ No arg = interactive picker. --all wires every detected agent.
837
+ --dry-run shows what would change. --force re-installs.
838
+ status Show connection status, memory count, flags, and health
839
+ doctor Interactive diagnostic + fixer. [F]ix · [S]kip · [?]more · [Q]uit
840
+ --all: apply every fix without prompting (CI)
841
+ --dry-run: show what each fix would do, don't execute
842
+ remove Cleanly uninstall agentmemory (pidfile, state, .env, binaries).
843
+ --force: skip confirmations · --keep-data: keep memory data
844
+ demo [--serve] Seed sample sessions and show recall in action.
845
+ --serve boots the server, runs the demo, and stops it
846
+ in one command (no second terminal).
847
+ upgrade Upgrade local deps + iii runtime (best effort)
848
+ stop [--force] Stop the running iii-engine started by this CLI.
849
+ --force bypasses the Docker-heuristic guard and signals
850
+ whatever pidfile+lsof report on the REST port (use when
851
+ the engine was started natively but state file is missing).
852
+ mcp Start standalone MCP shim — opt-in surface for MCP-only clients
853
+ (Cursor, Gemini CLI, etc). REST always available at :3111.
854
+ import-jsonl [p] Import Claude Code JSONL transcripts (default: ~/.claude/projects)
855
+ --max-files <N> | --max-files=<N>: override scan cap (default 200, max 1000;
856
+ out-of-range is rejected; for trees >1000 files, batch by subdirectory)
857
+
858
+ Options:
859
+ --help, -h Show this help
860
+ --verbose, -v Show engine stderr, boot log, and diagnostic info
861
+ --reset Wipe ~/.agentmemory/preferences.json and re-run onboarding
862
+ --tools all|core Tool visibility (default: all = ${ALL_TOOLS_COUNT} tools; core = ${CORE_TOOLS_COUNT} essentials)
863
+ --no-engine Skip auto-starting iii-engine
864
+ --port <N> Override REST port (default: 3111). Streams (N+1), viewer
865
+ (N+2), and iii engine (N+46023) auto-derive from N so a
866
+ single flag relocates the whole quartet.
867
+ --instance <N> Shortcut for --port (3111 + N*100) to run multiple
868
+ daemons side-by-side without env gymnastics.
869
+ --instance 1 -> 3211/3212/3213/49234, etc. (max N=50)
870
+
871
+ Environment:
872
+ AGENTMEMORY_URL Full REST base URL (e.g. http://localhost:3111).
873
+ Honored by status, doctor, and MCP shim commands.
874
+ AGENTMEMORY_USE_DOCKER=1 Prefer the bundled docker-compose path over the
875
+ native iii-engine binary on first run.
876
+ AGENTMEMORY_III_VERSION Override pinned iii-engine version (default ${IIPINNED_VERSION}).
877
+ AGENTMEMORY_FOLLOWUP_WINDOW_SECONDS
878
+ Window (seconds) for the smart-search follow-up diagnostic
879
+ (default 30). Long values overcount, short values undercount.
880
+
881
+ Quick start:
882
+ npx @agentmemory/agentmemory # start with local iii-engine or Docker
883
+ npx @agentmemory/agentmemory demo # see semantic recall in 30 seconds
884
+ npx @agentmemory/agentmemory doctor # diagnose config + feature flags
885
+ npx @agentmemory/agentmemory status # health + memory count + flags
886
+ npx @agentmemory/agentmemory upgrade # upgrade agentmemory + iii runtime
887
+ npx @agentmemory/agentmemory mcp # standalone MCP server (no engine)
888
+ npx @agentmemory/mcp # same as above (shim package)
889
+ `);
890
+ process.exit(0);
891
+ }
892
+ const toolsIdx = args.indexOf("--tools");
893
+ if (toolsIdx !== -1 && args[toolsIdx + 1]) {
894
+ const toolsMode = args[toolsIdx + 1];
895
+ if (toolsMode !== "all" && toolsMode !== "core") p.log.warn(`Unknown --tools value "${toolsMode}" (valid: all, core); falling back to all.`);
896
+ process.env["AGENTMEMORY_TOOLS"] = toolsMode;
897
+ }
898
+ const portIdx = args.indexOf("--port");
899
+ if (portIdx !== -1 && args[portIdx + 1]) process.env["III_REST_PORT"] = args[portIdx + 1];
900
+ const instanceIdx = args.indexOf("--instance");
901
+ if (instanceIdx !== -1 && args[instanceIdx + 1]) {
902
+ const n = parseInt(args[instanceIdx + 1] || "", 10);
903
+ if (Number.isFinite(n) && n >= 0 && n <= 50) {
904
+ const base = 3111 + n * 100;
905
+ if (!process.env["III_REST_PORT"]) process.env["III_REST_PORT"] = String(base);
906
+ }
907
+ }
908
+ const skipEngine = args.includes("--no-engine");
909
+ function getRestPort() {
910
+ const url = process.env["AGENTMEMORY_URL"];
911
+ if (url) try {
912
+ const parsed = new URL(url).port;
913
+ if (parsed) return parseInt(parsed, 10);
914
+ } catch {}
915
+ return parseInt(process.env["III_REST_PORT"] || "3111", 10) || 3111;
916
+ }
917
+ function getBaseUrl() {
918
+ const url = process.env["AGENTMEMORY_URL"];
919
+ if (url) return url.replace(/\/+$/, "");
920
+ return `http://localhost:${getRestPort()}`;
921
+ }
922
+ let discoveredViewerPort = null;
923
+ async function discoverViewerPort() {
924
+ if (discoveredViewerPort !== null) return;
925
+ try {
926
+ const res = await fetch(`${getBaseUrl()}/agentmemory/livez`, { signal: AbortSignal.timeout(1e3) });
927
+ if (res.ok) {
928
+ const data = await res.json();
929
+ if (typeof data.viewerPort === "number") discoveredViewerPort = data.viewerPort;
930
+ }
931
+ } catch {}
932
+ }
933
+ function getViewerUrl() {
934
+ const envUrl = process.env["AGENTMEMORY_VIEWER_URL"];
935
+ if (envUrl) return envUrl.replace(/\/+$/, "");
936
+ if (discoveredViewerPort !== null) try {
937
+ const u = new URL(getBaseUrl());
938
+ return `${u.protocol}//${u.hostname}:${discoveredViewerPort}`;
939
+ } catch {
940
+ return `http://localhost:${discoveredViewerPort}`;
941
+ }
942
+ try {
943
+ const u = new URL(getBaseUrl());
944
+ const vPort = parseInt(process.env["III_VIEWER_PORT"] || "", 10) || (parseInt(u.port || "3111", 10) || 3111) + 2;
945
+ return `${u.protocol}//${u.hostname}:${vPort}`;
946
+ } catch {
947
+ return `http://localhost:${parseInt(process.env["III_VIEWER_PORT"] || "", 10) || getRestPort() + 2}`;
948
+ }
949
+ }
950
+ function getStreamPort() {
951
+ return parseInt(process.env["III_STREAM_PORT"] || "", 10) || parseInt(process.env["III_STREAMS_PORT"] || "", 10) || getRestPort() + 1;
952
+ }
953
+ function getEnginePort() {
954
+ const explicit = parseInt(process.env["III_ENGINE_PORT"] || "", 10);
955
+ if (explicit) return explicit;
956
+ const url = process.env["III_ENGINE_URL"];
957
+ if (url) try {
958
+ const parsed = new URL(url).port;
959
+ if (parsed) return parseInt(parsed, 10);
960
+ } catch {}
961
+ return getRestPort() + 46023;
962
+ }
963
+ async function isEngineRunning() {
964
+ try {
965
+ await fetch(`${getBaseUrl()}/`, { signal: AbortSignal.timeout(2e3) });
966
+ return true;
967
+ } catch {
968
+ return false;
969
+ }
970
+ }
971
+ async function isAgentmemoryReady() {
972
+ try {
973
+ const res = await fetch(`${getBaseUrl()}/agentmemory/livez`, { signal: AbortSignal.timeout(2e3) });
974
+ if (!res.ok) return false;
975
+ try {
976
+ const data = await res.json();
977
+ if (typeof data.viewerPort === "number") {
978
+ discoveredViewerPort = data.viewerPort;
979
+ return true;
980
+ }
981
+ if (data.viewerSkipped) return true;
982
+ return false;
983
+ } catch {
984
+ return false;
985
+ }
986
+ } catch {
987
+ return false;
988
+ }
989
+ }
990
+ function findIiiConfig() {
991
+ const envPath = process.env["AGENTMEMORY_III_CONFIG"];
992
+ const candidates = [
993
+ ...envPath ? [envPath] : [],
994
+ join(process.cwd(), "iii-config.yaml"),
995
+ join(homedir(), ".agentmemory", "iii-config.yaml"),
996
+ join(__dirname, "iii-config.yaml"),
997
+ join(__dirname, "..", "iii-config.yaml")
998
+ ];
999
+ for (const c of candidates) if (existsSync(c)) return c;
1000
+ return "";
1001
+ }
1002
+ function whichBinary(name) {
1003
+ const cmd = IS_WINDOWS ? "where" : "which";
1004
+ try {
1005
+ return execFileSync(cmd, [name], {
1006
+ encoding: "utf-8",
1007
+ stdio: [
1008
+ "ignore",
1009
+ "pipe",
1010
+ "pipe"
1011
+ ]
1012
+ }).split(/\r?\n/).map((line) => line.trim()).find((line) => line.length > 0) ?? null;
1013
+ } catch {
1014
+ return null;
1015
+ }
1016
+ }
1017
+ function agentmemoryBinDir() {
1018
+ if (IS_WINDOWS) {
1019
+ const userProfile = process.env["USERPROFILE"];
1020
+ if (!userProfile) return join(homedir(), ".agentmemory", "bin");
1021
+ return join(userProfile, ".agentmemory", "bin");
1022
+ }
1023
+ return join(homedir(), ".agentmemory", "bin");
1024
+ }
1025
+ function privateIiiPath() {
1026
+ return join(agentmemoryBinDir(), IS_WINDOWS ? "iii.exe" : "iii");
1027
+ }
1028
+ function fallbackIiiPaths() {
1029
+ if (IS_WINDOWS) {
1030
+ const userProfile = process.env["USERPROFILE"];
1031
+ const paths = [privateIiiPath()];
1032
+ if (userProfile) paths.push(join(userProfile, ".local", "bin", "iii.exe"), join(userProfile, "bin", "iii.exe"));
1033
+ return paths;
1034
+ }
1035
+ const home = process.env["HOME"];
1036
+ const paths = [privateIiiPath()];
1037
+ if (home) paths.push(join(home, ".local", "bin", "iii"));
1038
+ paths.push("/usr/local/bin/iii");
1039
+ return paths;
1040
+ }
1041
+ function iiiBinVersion(binPath) {
1042
+ try {
1043
+ const match = execFileSync(binPath, ["--version"], {
1044
+ encoding: "utf-8",
1045
+ stdio: [
1046
+ "ignore",
1047
+ "pipe",
1048
+ "ignore"
1049
+ ],
1050
+ timeout: 3e3
1051
+ }).match(/(\d+\.\d+\.\d+(?:[-+][\w.]+)?)/);
1052
+ return match ? match[1] : null;
1053
+ } catch {
1054
+ return null;
1055
+ }
1056
+ }
1057
+ function resolveCompatibleIii(iiiBinPath) {
1058
+ if (!iiiBinPath) return null;
1059
+ const detected = iiiBinVersion(iiiBinPath);
1060
+ if (detected && detected === IIPINNED_VERSION) return iiiBinPath;
1061
+ const privatePath = privateIiiPath();
1062
+ if (iiiBinPath !== privatePath && existsSync(privatePath)) {
1063
+ if (iiiBinVersion(privatePath) === IIPINNED_VERSION) {
1064
+ vlog(`iii at ${iiiBinPath} ${detected ? `v${detected} mismatches pin` : "probe failed"} v${IIPINNED_VERSION}; using private install at ${privatePath}.`);
1065
+ return privatePath;
1066
+ }
1067
+ }
1068
+ return null;
1069
+ }
1070
+ function enginePidfilePath() {
1071
+ return join(homedir(), ".agentmemory", "iii.pid");
1072
+ }
1073
+ function engineStatePath() {
1074
+ return join(homedir(), ".agentmemory", "engine-state.json");
1075
+ }
1076
+ function writeEnginePidfile(pid) {
1077
+ try {
1078
+ const pidPath = enginePidfilePath();
1079
+ mkdirSync(dirname(pidPath), { recursive: true });
1080
+ writeFileSync(pidPath, `${pid}\n`, { encoding: "utf-8" });
1081
+ } catch (err) {
1082
+ vlog(`writeEnginePidfile: ${err instanceof Error ? err.message : String(err)}`);
1083
+ }
1084
+ }
1085
+ function readEnginePidfile() {
1086
+ try {
1087
+ const pidStr = readFileSync(enginePidfilePath(), "utf-8").trim();
1088
+ const pid = parseInt(pidStr, 10);
1089
+ return Number.isFinite(pid) && pid > 0 ? pid : null;
1090
+ } catch {
1091
+ return null;
1092
+ }
1093
+ }
1094
+ function clearEnginePidfile() {
1095
+ try {
1096
+ unlinkSync(enginePidfilePath());
1097
+ } catch {}
1098
+ }
1099
+ function workerPidfilePath() {
1100
+ return join(homedir(), ".agentmemory", "worker.pid");
1101
+ }
1102
+ function readWorkerPidfile() {
1103
+ try {
1104
+ const pidStr = readFileSync(workerPidfilePath(), "utf-8").trim();
1105
+ const pid = parseInt(pidStr, 10);
1106
+ return Number.isFinite(pid) && pid > 0 ? pid : null;
1107
+ } catch {
1108
+ return null;
1109
+ }
1110
+ }
1111
+ function clearWorkerPidfile() {
1112
+ try {
1113
+ unlinkSync(workerPidfilePath());
1114
+ } catch {}
1115
+ }
1116
+ function writeEngineState(state) {
1117
+ try {
1118
+ const statePath = engineStatePath();
1119
+ mkdirSync(dirname(statePath), { recursive: true });
1120
+ writeFileSync(statePath, `${JSON.stringify(state)}\n`, { encoding: "utf-8" });
1121
+ } catch (err) {
1122
+ vlog(`writeEngineState: ${err instanceof Error ? err.message : String(err)}`);
1123
+ }
1124
+ }
1125
+ function readEngineState() {
1126
+ try {
1127
+ const raw = readFileSync(engineStatePath(), "utf-8");
1128
+ const parsed = JSON.parse(raw);
1129
+ if (parsed && (parsed.kind === "native" || parsed.kind === "docker")) return parsed;
1130
+ return null;
1131
+ } catch {
1132
+ return null;
1133
+ }
1134
+ }
1135
+ function clearEngineState() {
1136
+ try {
1137
+ unlinkSync(engineStatePath());
1138
+ } catch {}
1139
+ }
1140
+ function discoverComposeFile() {
1141
+ return [
1142
+ join(__dirname, "..", "docker-compose.yml"),
1143
+ join(__dirname, "docker-compose.yml"),
1144
+ join(process.cwd(), "docker-compose.yml")
1145
+ ].find((c) => existsSync(c)) ?? null;
1146
+ }
1147
+ function isInvokedViaNpx() {
1148
+ if (process.env["npm_lifecycle_event"] === "npx") return true;
1149
+ if ((process.argv[1] ?? "").includes("_npx")) return true;
1150
+ const ua = process.env["npm_config_user_agent"] ?? "";
1151
+ if (ua.startsWith("npm/") || ua.includes(" npm/")) return true;
1152
+ return false;
1153
+ }
1154
+ async function maybeOfferGlobalInstall() {
1155
+ if (!isInvokedViaNpx()) return;
1156
+ if (!process.stdin.isTTY) return;
1157
+ if (process.env["CI"]) return;
1158
+ const prefs = readPrefs();
1159
+ if (prefs.skipGlobalInstall || prefs.skipNpxHint) return;
1160
+ const answer = await p.confirm({
1161
+ message: "Install agentmemory globally so the bare `agentmemory` command works in any shell? [Y/n]",
1162
+ initialValue: true
1163
+ });
1164
+ if (p.isCancel(answer)) return;
1165
+ if (answer === false) {
1166
+ writePrefs({ skipGlobalInstall: true });
1167
+ p.log.info("Skipped. Re-run via `npx @agentmemory/agentmemory` or install later with: npm install -g @agentmemory/agentmemory");
1168
+ return;
1169
+ }
1170
+ const npmBin = whichBinary("npm");
1171
+ if (!npmBin) {
1172
+ p.log.warn("npm not found on PATH. Install manually: npm install -g @agentmemory/agentmemory");
1173
+ return;
1174
+ }
1175
+ if (runCommand(npmBin, [
1176
+ "install",
1177
+ "-g",
1178
+ `@agentmemory/agentmemory@0.9.27`
1179
+ ], { label: `Installing @agentmemory/agentmemory@0.9.27 globally` })) {
1180
+ p.log.success("Installed globally. `agentmemory stop` etc. will now work in new shells.");
1181
+ writePrefs({ skipGlobalInstall: true });
1182
+ } else p.log.warn("Global install failed. Try manually: npm install -g @agentmemory/agentmemory");
1183
+ }
1184
+ function detectIiiConsole() {
1185
+ const onPath = whichBinary("iii-console");
1186
+ if (onPath) return {
1187
+ kind: "installed",
1188
+ binPath: onPath
1189
+ };
1190
+ const fallback = IS_WINDOWS ? join(process.env["USERPROFILE"] ?? "", ".local", "bin", "iii-console.exe") : join(homedir(), ".local", "bin", "iii-console");
1191
+ if (fallback && existsSync(fallback)) return {
1192
+ kind: "installed",
1193
+ binPath: fallback
1194
+ };
1195
+ return { kind: "missing" };
1196
+ }
1197
+ const III_CONSOLE_INSTALL_CMD = `curl -fsSL https://install.iii.dev/iii/main/install.sh | VERSION=${IIPINNED_VERSION} sh`;
1198
+ function iiiConsoleInstallHint() {
1199
+ if (!IS_WINDOWS) return III_CONSOLE_INSTALL_CMD;
1200
+ return `# PowerShell:\n $env:VERSION = "${IIPINNED_VERSION}"\n iwr -useb https://install.iii.dev/iii/main/install.sh -OutFile install.sh\n bash install.sh # WSL or Git Bash required\n# Or grab the pinned release directly:\n https://github.com/iii-hq/iii/releases/tag/iii%2Fv${IIPINNED_VERSION}`;
1201
+ }
1202
+ async function ensureIiiConsole() {
1203
+ const state = detectIiiConsole();
1204
+ if (state.kind === "installed") return state;
1205
+ if (!process.stdin.isTTY || process.env["CI"]) return state;
1206
+ if (readPrefs().skipConsoleInstall) return state;
1207
+ const answer = await p.confirm({
1208
+ message: "iii console gives engine-level visibility (workers, functions, queues, traces). Install now?",
1209
+ initialValue: true
1210
+ });
1211
+ if (p.isCancel(answer)) return state;
1212
+ if (answer === false) {
1213
+ writePrefs({ skipConsoleInstall: true });
1214
+ return state;
1215
+ }
1216
+ const shBin = whichBinary("sh");
1217
+ const curlBin = whichBinary("curl");
1218
+ if (!shBin || !curlBin) {
1219
+ p.log.warn(`curl or sh not found. Install manually:\n ${iiiConsoleInstallHint()}`);
1220
+ return state;
1221
+ }
1222
+ if (!runCommand(shBin, ["-c", III_CONSOLE_INSTALL_CMD], { label: "Installing iii console" })) {
1223
+ p.log.warn(`iii console install failed. Re-run manually:\n ${iiiConsoleInstallHint()}`);
1224
+ return state;
1225
+ }
1226
+ return detectIiiConsole();
1227
+ }
1228
+ function adoptRunningEngine() {
1229
+ try {
1230
+ const existingState = readEngineState();
1231
+ const existingPid = readEnginePidfile();
1232
+ if (existingState && existingPid) return;
1233
+ const enginePid = findEnginePidsByPort(getRestPort())[0];
1234
+ if (enginePid && !existingPid) writeEnginePidfile(enginePid);
1235
+ if (!existingState) writeEngineState({
1236
+ kind: "native",
1237
+ configPath: findIiiConfig() || "",
1238
+ attached: true
1239
+ });
1240
+ if (enginePid && !existingPid) p.log.info(c.ok(`Attached to existing iii-engine (pid ${enginePid})`));
1241
+ } catch (err) {
1242
+ vlog(`adoptRunningEngine: ${err instanceof Error ? err.message : String(err)}`);
1243
+ }
1244
+ }
1245
+ async function runIiiInstaller() {
1246
+ const releaseUrl = iiiReleaseUrl();
1247
+ const asset = iiiReleaseAsset();
1248
+ const isZipAsset = asset?.endsWith(".zip") === true;
1249
+ if (!releaseUrl) {
1250
+ p.log.warn(`iii-engine binary not available for ${platform()}/${process.arch}. Use Docker (\`docker pull iiidev/iii:${IIPINNED_VERSION}\`) or download manually from https://github.com/iii-hq/iii/releases/tag/iii%2Fv${IIPINNED_VERSION}.`);
1251
+ return {
1252
+ ok: false,
1253
+ binPath: null
1254
+ };
1255
+ }
1256
+ if (IS_WINDOWS || isZipAsset) {
1257
+ p.log.info(`Auto-install unavailable on ${platform()} — ${asset} isn't tar-compatible. Install manually:\n 1. Download ${releaseUrl}\n 2. Extract iii.exe and place it on PATH (e.g. %USERPROFILE%\\.local\\bin)\nOr use Docker: docker pull iiidev/iii:${IIPINNED_VERSION}`);
1258
+ return {
1259
+ ok: false,
1260
+ binPath: null
1261
+ };
1262
+ }
1263
+ const shBin = whichBinary("sh");
1264
+ const curlBin = whichBinary("curl");
1265
+ if (!shBin || !curlBin) {
1266
+ p.log.warn("curl or sh not found. Cannot auto-install iii-engine.");
1267
+ return {
1268
+ ok: false,
1269
+ binPath: null
1270
+ };
1271
+ }
1272
+ const binDir = agentmemoryBinDir();
1273
+ const binPath = privateIiiPath();
1274
+ if (!runCommand(shBin, ["-c", [
1275
+ `mkdir -p "${binDir}"`,
1276
+ `curl -fsSL "${releaseUrl}" | tar -xz -C "${binDir}"`,
1277
+ `chmod +x "${binPath}"`
1278
+ ].join(" && ")], {
1279
+ label: `Installing iii-engine v${IIPINNED_VERSION} (pinned)`,
1280
+ optional: true
1281
+ })) {
1282
+ p.log.warn(`iii-engine installer failed. Fallbacks: Docker (\`docker pull iiidev/iii:${IIPINNED_VERSION}\`) or download manually from https://github.com/iii-hq/iii/releases/tag/iii%2Fv${IIPINNED_VERSION}.`);
1283
+ return {
1284
+ ok: false,
1285
+ binPath: null
1286
+ };
1287
+ }
1288
+ return {
1289
+ ok: true,
1290
+ binPath
1291
+ };
1292
+ }
1293
+ let startupFailure = null;
1294
+ function spawnEngineBackground(bin, spawnArgs, label) {
1295
+ vlog(`spawn: ${bin} ${spawnArgs.join(" ")}`);
1296
+ const child = spawn(bin, spawnArgs, {
1297
+ detached: true,
1298
+ stdio: [
1299
+ "ignore",
1300
+ "ignore",
1301
+ "pipe"
1302
+ ],
1303
+ windowsHide: true
1304
+ });
1305
+ const isDocker = label.includes("Docker");
1306
+ if (!isDocker && typeof child.pid === "number") writeEnginePidfile(child.pid);
1307
+ const stderrChunks = [];
1308
+ let stderrBytes = 0;
1309
+ const MAX_STDERR_CAPTURE = 16 * 1024;
1310
+ child.stderr?.on("data", (chunk) => {
1311
+ if (stderrBytes >= MAX_STDERR_CAPTURE) return;
1312
+ const slice = chunk.subarray(0, MAX_STDERR_CAPTURE - stderrBytes);
1313
+ stderrChunks.push(slice);
1314
+ stderrBytes += slice.length;
1315
+ });
1316
+ child.on("exit", (code, signal) => {
1317
+ if (code !== null && code !== 0 || code === null && signal !== null) {
1318
+ const stderr = Buffer.concat(stderrChunks).toString("utf-8");
1319
+ startupFailure = {
1320
+ kind: isDocker ? "docker-crashed" : "engine-crashed",
1321
+ stderr: stderr.trim() || (signal ? `process killed by signal ${signal}` : `process exited with code ${code}`),
1322
+ binary: bin
1323
+ };
1324
+ vlog(`engine exited early: code=${code} signal=${signal}`);
1325
+ if (IS_VERBOSE && stderr.trim()) p.log.error(`engine stderr:\n${stderr}`);
1326
+ if (!isDocker) clearEnginePidfile();
1327
+ clearEngineState();
1328
+ }
1329
+ });
1330
+ child.unref();
1331
+ return child;
1332
+ }
1333
+ function startIiiBin(iiiBin, configPath) {
1334
+ const s = p.spinner();
1335
+ s.start(`Starting iii-engine: ${iiiBin}`);
1336
+ writeEngineState({
1337
+ kind: "native",
1338
+ configPath,
1339
+ binPath: iiiBin
1340
+ });
1341
+ spawnEngineBackground(iiiBin, ["--config", configPath], "iii-engine");
1342
+ s.stop(c.ok("iii-engine process started"));
1343
+ return true;
1344
+ }
1345
+ function pickCompatibleIii(candidates) {
1346
+ for (const c of candidates) {
1347
+ if (!c) continue;
1348
+ const resolved = resolveCompatibleIii(c);
1349
+ if (resolved) return resolved;
1350
+ }
1351
+ return null;
1352
+ }
1353
+ async function startEngine() {
1354
+ const configPath = findIiiConfig();
1355
+ const pathIii = whichBinary("iii");
1356
+ vlog(`iii binary: ${pathIii ?? "(not on PATH)"}, config: ${configPath || "(not found)"}`);
1357
+ const fallbacks = fallbackIiiPaths().filter((p) => existsSync(p));
1358
+ for (const f of fallbacks) vlog(`fallback iii at ${f} reports version: ${iiiBinVersion(f) ?? "unknown"}`);
1359
+ let iiiBin = pickCompatibleIii([pathIii, ...fallbacks]);
1360
+ if (iiiBin && configPath) {
1361
+ if (iiiBin !== pathIii) {
1362
+ p.log.info(`Using iii at: ${c.dim(iiiBin)} (v${c.accent(IIPINNED_VERSION)})`);
1363
+ process.env["PATH"] = `${dirname(iiiBin)}${delimiter}${process.env["PATH"] ?? ""}`;
1364
+ }
1365
+ return startIiiBin(iiiBin, configPath);
1366
+ }
1367
+ if (pathIii && !iiiBin) vlog(`iii on PATH is v${iiiBinVersion(pathIii) ?? "unknown"}, pin is v${IIPINNED_VERSION}. Will install pinned engine to ${privateIiiPath()}.`);
1368
+ if (!configPath) {
1369
+ startupFailure = { kind: "no-engine" };
1370
+ return false;
1371
+ }
1372
+ const dockerBin = whichBinary("docker");
1373
+ vlog(`docker binary: ${dockerBin ?? "(not on PATH)"}`);
1374
+ const composeFile = [
1375
+ join(__dirname, "..", "docker-compose.yml"),
1376
+ join(__dirname, "docker-compose.yml"),
1377
+ join(process.cwd(), "docker-compose.yml")
1378
+ ].find((c) => existsSync(c));
1379
+ vlog(`docker-compose.yml: ${composeFile ?? "(not found)"}`);
1380
+ const dockerOptIn = process.env["AGENTMEMORY_USE_DOCKER"] === "1" || process.env["AGENTMEMORY_USE_DOCKER"] === "true";
1381
+ const interactive = !!process.stdin.isTTY && !process.env["CI"];
1382
+ let choice;
1383
+ const pathIiiMismatch = pathIii !== null && resolveCompatibleIii(pathIii) === null;
1384
+ if (dockerOptIn && dockerBin && composeFile) choice = "docker";
1385
+ else if (pathIiiMismatch) {
1386
+ choice = "install";
1387
+ const detected = iiiBinVersion(pathIii);
1388
+ p.log.info(`iii on PATH is v${detected ?? "unknown"} but agentmemory pins v${IIPINNED_VERSION}. Installing pinned engine to ~/.agentmemory/bin (leaves your existing iii untouched).`);
1389
+ } else if (!interactive) {
1390
+ choice = "install";
1391
+ p.log.info("Non-interactive environment detected — auto-installing iii-engine.");
1392
+ } else {
1393
+ p.log.warn(`iii-engine binary not found locally.`);
1394
+ const options = [{
1395
+ value: "install",
1396
+ label: `Install iii v${IIPINNED_VERSION} to ~/.agentmemory/bin (~6MB, ~5s)`,
1397
+ hint: "recommended"
1398
+ }];
1399
+ if (dockerBin && composeFile) options.push({
1400
+ value: "docker",
1401
+ label: "Use Docker compose",
1402
+ hint: "advanced"
1403
+ });
1404
+ options.push({
1405
+ value: "manual",
1406
+ label: "Show manual install steps and exit"
1407
+ });
1408
+ const picked = await p.select({
1409
+ message: "How would you like to start iii-engine?",
1410
+ options,
1411
+ initialValue: "install"
1412
+ });
1413
+ if (p.isCancel(picked)) {
1414
+ startupFailure = { kind: "no-engine" };
1415
+ return false;
1416
+ }
1417
+ choice = picked;
1418
+ }
1419
+ if (choice === "manual") {
1420
+ startupFailure = { kind: "no-engine" };
1421
+ return false;
1422
+ }
1423
+ if (choice === "install") {
1424
+ const result = await runIiiInstaller();
1425
+ if (result.ok && result.binPath) {
1426
+ process.env["PATH"] = `${dirname(result.binPath)}${delimiter}${process.env["PATH"] ?? ""}`;
1427
+ iiiBin = result.binPath;
1428
+ return startIiiBin(iiiBin, configPath);
1429
+ }
1430
+ if (dockerBin && composeFile && interactive) {
1431
+ const fallback = await p.confirm({
1432
+ message: "Auto-install failed. Try Docker compose instead?",
1433
+ initialValue: true
1434
+ });
1435
+ if (p.isCancel(fallback) || fallback !== true) {
1436
+ startupFailure = { kind: "no-engine" };
1437
+ return false;
1438
+ }
1439
+ choice = "docker";
1440
+ } else {
1441
+ startupFailure = { kind: "no-engine" };
1442
+ return false;
1443
+ }
1444
+ }
1445
+ if (choice === "docker" && dockerBin && composeFile) {
1446
+ const s = p.spinner();
1447
+ s.start("Starting iii-engine via Docker...");
1448
+ writeEngineState({
1449
+ kind: "docker",
1450
+ composeFile
1451
+ });
1452
+ spawnEngineBackground(dockerBin, [
1453
+ "compose",
1454
+ "-f",
1455
+ composeFile,
1456
+ "up",
1457
+ "-d"
1458
+ ], "iii-engine via Docker");
1459
+ s.stop("Docker compose started");
1460
+ return true;
1461
+ }
1462
+ if (!composeFile && dockerBin) startupFailure = { kind: "no-docker-compose" };
1463
+ else startupFailure = { kind: "no-engine" };
1464
+ return false;
1465
+ }
1466
+ async function waitForEngine(timeoutMs) {
1467
+ const start = Date.now();
1468
+ while (Date.now() - start < timeoutMs) {
1469
+ if (await isEngineRunning()) return true;
1470
+ await new Promise((r) => setTimeout(r, 500));
1471
+ }
1472
+ return false;
1473
+ }
1474
+ function installInstructions() {
1475
+ const releaseUrl = iiiReleaseUrl();
1476
+ if (IS_WINDOWS) return [
1477
+ `agentmemory needs iii-engine v${IIPINNED_VERSION}. Pick one:`,
1478
+ "",
1479
+ " A) Download the prebuilt Windows binary:",
1480
+ ` 1. Open https://github.com/iii-hq/iii/releases/tag/iii%2Fv${IIPINNED_VERSION}`,
1481
+ ` 2. Download iii-x86_64-pc-windows-msvc.zip (or iii-aarch64-pc-windows-msvc.zip on ARM)`,
1482
+ " 3. Extract iii.exe to %USERPROFILE%\\.local\\bin\\iii.exe (or add to PATH)",
1483
+ " 4. Re-run: npx @agentmemory/agentmemory",
1484
+ "",
1485
+ ` B) Docker: docker pull iiidev/iii:${IIPINNED_VERSION}`,
1486
+ " Re-run with AGENTMEMORY_USE_DOCKER=1 npx @agentmemory/agentmemory",
1487
+ "",
1488
+ "Or skip the engine entirely (standalone MCP): npx @agentmemory/agentmemory mcp",
1489
+ "",
1490
+ "Docs: https://iii.dev/docs"
1491
+ ];
1492
+ const linuxInstall = releaseUrl ? ` A) mkdir -p ~/.agentmemory/bin && curl -fsSL "${releaseUrl}" | tar -xz -C ~/.agentmemory/bin && chmod +x ~/.agentmemory/bin/iii` : ` A) Manual download: https://github.com/iii-hq/iii/releases/tag/iii%2Fv${IIPINNED_VERSION}`;
1493
+ return [
1494
+ `agentmemory needs iii-engine v${IIPINNED_VERSION}. Pick one:`,
1495
+ "",
1496
+ linuxInstall,
1497
+ " Then re-run: npx @agentmemory/agentmemory",
1498
+ "",
1499
+ ` B) Docker: docker pull iiidev/iii:${IIPINNED_VERSION}`,
1500
+ " Re-run with AGENTMEMORY_USE_DOCKER=1 npx @agentmemory/agentmemory",
1501
+ "",
1502
+ "Or skip the engine entirely (standalone MCP): npx @agentmemory/agentmemory mcp",
1503
+ "",
1504
+ "Docs: https://iii.dev/docs"
1505
+ ];
1506
+ }
1507
+ function portInUseDiagnostic(port) {
1508
+ return IS_WINDOWS ? ` netstat -ano | findstr :${port}` : ` lsof -i :${port} # or: ss -tlnp | grep :${port}`;
1509
+ }
1510
+ async function waitForAgentmemoryReady(timeoutMs) {
1511
+ const start = Date.now();
1512
+ while (Date.now() - start < timeoutMs) {
1513
+ if (await isAgentmemoryReady()) return true;
1514
+ await new Promise((r) => setTimeout(r, 250));
1515
+ }
1516
+ return false;
1517
+ }
1518
+ function getEngineHost() {
1519
+ for (const envKey of ["III_ENGINE_URL", "AGENTMEMORY_URL"]) {
1520
+ const raw = process.env[envKey];
1521
+ if (!raw) continue;
1522
+ try {
1523
+ const parsed = new URL(raw);
1524
+ if (parsed.hostname) return parsed.hostname;
1525
+ } catch {}
1526
+ }
1527
+ return "localhost";
1528
+ }
1529
+ function printReadyHint(consoleState) {
1530
+ const restUrl = getBaseUrl();
1531
+ const viewerUrl = getViewerUrl();
1532
+ const engineHost = getEngineHost();
1533
+ const streamUrl = `ws://${engineHost}:${getStreamPort()}`;
1534
+ const engineUrl = `ws://${engineHost}:${getEnginePort()}`;
1535
+ 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()})`)}`;
1536
+ const lines = [
1537
+ `${c.label("REST API")} ${c.url(restUrl)}`,
1538
+ `${c.label("Viewer")} ${c.url(viewerUrl)}`,
1539
+ `${c.label("Streams")} ${c.url(streamUrl)}`,
1540
+ `${c.label("Engine")} ${c.url(engineUrl)}`,
1541
+ consoleLine
1542
+ ];
1543
+ p.note(lines.join("\n"), `agentmemory v${c.accent(VERSION)}`);
1544
+ const demoCommand = isInvokedViaNpx() ? "npx @agentmemory/agentmemory demo" : "agentmemory demo";
1545
+ process.stdout.write(`\n${c.dim("Try:")} ${c.cmd(demoCommand)}\n`);
1546
+ }
1547
+ async function main() {
1548
+ if (IS_RESET) resetPrefs();
1549
+ const firstRun = isFirstRun();
1550
+ const prefs = readPrefs();
1551
+ if (firstRun || IS_RESET || IS_VERBOSE || !prefs.skipSplash) renderSplash(VERSION);
1552
+ if (firstRun || IS_RESET) await runOnboarding();
1553
+ if (skipEngine) {
1554
+ if (IS_VERBOSE) p.log.info("Skipping engine check (--no-engine)");
1555
+ await import("./src-Cr3pH_uH.mjs");
1556
+ if (await waitForAgentmemoryReady(15e3)) {
1557
+ const consoleState = await ensureIiiConsole();
1558
+ await maybeOfferGlobalInstall();
1559
+ printReadyHint(consoleState);
1560
+ }
1561
+ return;
1562
+ }
1563
+ if (await isEngineRunning()) {
1564
+ if (IS_VERBOSE) p.log.success("iii-engine is running");
1565
+ const persisted = readEngineState();
1566
+ const attachedBin = (persisted?.kind === "native" && persisted.binPath && existsSync(persisted.binPath) ? persisted.binPath : null) ?? whichBinary("iii") ?? fallbackIiiPaths().find((p) => existsSync(p)) ?? null;
1567
+ const detected = attachedBin ? iiiBinVersion(attachedBin) : null;
1568
+ if (detected === IIPINNED_VERSION) {
1569
+ adoptRunningEngine();
1570
+ await import("./src-Cr3pH_uH.mjs");
1571
+ if (await waitForAgentmemoryReady(15e3)) {
1572
+ const consoleState = await ensureIiiConsole();
1573
+ await maybeOfferGlobalInstall();
1574
+ printReadyHint(consoleState);
1575
+ }
1576
+ return;
1577
+ }
1578
+ const detectedLabel = detected ? `v${detected}` : "an unverified version";
1579
+ const base = isInvokedViaNpx() ? "npx @agentmemory/agentmemory" : "agentmemory";
1580
+ p.log.error(`Another iii-engine (${detectedLabel}) is running on port ${getEnginePort()}, and agentmemory needs its own pinned v${IIPINNED_VERSION}.`);
1581
+ p.note([
1582
+ `agentmemory only supports iii-engine v${IIPINNED_VERSION}. It will not adopt or change the running engine (${detectedLabel}).`,
1583
+ "",
1584
+ c.label("Switch to the pinned engine in two steps:"),
1585
+ "",
1586
+ ` 1. Stop the running engine:`,
1587
+ ` ${c.cmd(`${base} stop --force`)}`,
1588
+ ` ${c.dim(`(or stop your own iii however you started it — agentmemory leaves your global iii untouched)`)}`,
1589
+ "",
1590
+ ` 2. Start agentmemory. It downloads and runs the pinned`,
1591
+ ` v${IIPINNED_VERSION} into ~/.agentmemory/bin automatically:`,
1592
+ ` ${c.cmd(base)}`,
1593
+ "",
1594
+ c.dim(`Step 2 needs no manual install. To install iii v${IIPINNED_VERSION} yourself (replaces your global iii), curl:`),
1595
+ ` ${c.cmd(III_CONSOLE_INSTALL_CMD)}`,
1596
+ ` ${c.dim("or download the release:")} ${c.url(`https://github.com/iii-hq/iii/releases/tag/iii%2Fv${IIPINNED_VERSION}`)}`
1597
+ ].join("\n"), "engine conflict");
1598
+ process.exit(1);
1599
+ }
1600
+ if (!await startEngine()) {
1601
+ p.log.error("Could not start iii-engine.");
1602
+ const lines = installInstructions();
1603
+ 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", "");
1604
+ p.note(lines.join("\n"), "Setup required");
1605
+ process.exit(1);
1606
+ }
1607
+ const s = p.spinner();
1608
+ s.start("Waiting for iii-engine to be ready...");
1609
+ if (!await waitForEngine(15e3)) {
1610
+ const port = getRestPort();
1611
+ s.stop("iii-engine did not become ready within 15s");
1612
+ if (startupFailure?.kind === "engine-crashed" || startupFailure?.kind === "docker-crashed") {
1613
+ p.log.error("The iii-engine process crashed on startup.");
1614
+ if (startupFailure.binary) p.log.info(`Binary: ${startupFailure.binary}`);
1615
+ if (startupFailure.stderr) p.note(startupFailure.stderr, "engine stderr");
1616
+ else p.log.info("No stderr was captured. Re-run with --verbose for more detail.");
1617
+ p.note([
1618
+ "Common causes:",
1619
+ ` - iii-engine version mismatch — reinstall the pinned v${IIPINNED_VERSION} binary`,
1620
+ " (sh script on macOS/Linux, GitHub release zip on Windows)",
1621
+ " - Docker Desktop not running (if you're using the Docker path)",
1622
+ " - Port already in use (see below)",
1623
+ "",
1624
+ "See https://iii.dev/docs for current install instructions."
1625
+ ].join("\n"), "Troubleshooting");
1626
+ } else {
1627
+ p.log.error("The engine process started but the REST API never responded.");
1628
+ p.note([
1629
+ `Check whether port ${port} is already bound by another process:`,
1630
+ portInUseDiagnostic(port),
1631
+ "",
1632
+ "If it is, free the port or override: agentmemory --port <N>",
1633
+ "",
1634
+ "If it isn't, a firewall may be blocking 127.0.0.1:" + port + ".",
1635
+ "Re-run with --verbose to see engine stderr."
1636
+ ].join("\n"), "Troubleshooting");
1637
+ }
1638
+ process.exit(1);
1639
+ }
1640
+ s.stop(c.ok("iii-engine is ready"));
1641
+ await import("./src-Cr3pH_uH.mjs");
1642
+ if (await waitForAgentmemoryReady(15e3)) {
1643
+ const consoleState = await ensureIiiConsole();
1644
+ await maybeOfferGlobalInstall();
1645
+ printReadyHint(consoleState);
1646
+ }
1647
+ writePrefs({ skipSplash: true });
1648
+ }
1649
+ async function apiFetch(base, path, timeoutMs = 5e3) {
1650
+ try {
1651
+ const headers = {};
1652
+ const secret = process.env["AGENTMEMORY_SECRET"];
1653
+ if (secret) headers["Authorization"] = `Bearer ${secret}`;
1654
+ return await (await fetch(`${base}/agentmemory/${path}`, {
1655
+ signal: AbortSignal.timeout(timeoutMs),
1656
+ headers
1657
+ })).json();
1658
+ } catch {
1659
+ return null;
1660
+ }
1661
+ }
1662
+ async function runStatus() {
1663
+ getRestPort();
1664
+ const base = getBaseUrl();
1665
+ p.intro("agentmemory status");
1666
+ if (!await isEngineRunning()) {
1667
+ p.log.error(`Not running — no response at ${base}`);
1668
+ p.log.info("Start with: npx @agentmemory/agentmemory");
1669
+ process.exit(1);
1670
+ }
1671
+ try {
1672
+ const [healthRes, sessionsRes, graphRes, memoriesRes, flagsRes, followupRes] = await Promise.all([
1673
+ apiFetch(base, "health"),
1674
+ apiFetch(base, "sessions"),
1675
+ apiFetch(base, "graph/stats"),
1676
+ apiFetch(base, "memories?count=true"),
1677
+ apiFetch(base, "config/flags"),
1678
+ apiFetch(base, "diagnostics/followup")
1679
+ ]);
1680
+ if (typeof healthRes?.viewerPort === "number") discoveredViewerPort = healthRes.viewerPort;
1681
+ const h = healthRes?.health;
1682
+ const status = healthRes?.status || "unknown";
1683
+ const version = healthRes?.version || "?";
1684
+ const sessionList = Array.isArray(sessionsRes?.sessions) ? sessionsRes.sessions : [];
1685
+ const sessions = sessionList.length;
1686
+ const nodes = Number(graphRes?.totalNodes ?? graphRes?.nodes ?? graphRes?.nodeCount ?? 0);
1687
+ const edges = Number(graphRes?.totalEdges ?? graphRes?.edges ?? graphRes?.edgeCount ?? 0);
1688
+ const cb = healthRes?.circuitBreaker?.state || "closed";
1689
+ const heapMB = h?.memory ? Math.round(h.memory.heapUsed / 1048576) : 0;
1690
+ const uptime = h?.uptimeSeconds ? Math.round(h.uptimeSeconds) : 0;
1691
+ const obsCount = sessionList.reduce((sum, s) => sum + (Number(s?.observationCount) || 0), 0);
1692
+ const memCount = Number(memoriesRes?.latestCount ?? memoriesRes?.total ?? 0) || 0;
1693
+ const estFullTokens = obsCount * 80;
1694
+ const estInjectedTokens = Math.min(obsCount, 50) * 38;
1695
+ const tokensSaved = estFullTokens - estInjectedTokens;
1696
+ const pctSaved = estFullTokens > 0 ? Math.round(tokensSaved / estFullTokens * 100) : 0;
1697
+ p.log.success(`Connected — v${version} at ${base}`);
1698
+ const lines = [
1699
+ `Health: ${status === "healthy" ? pc.green("✓ healthy") : pc.yellow(status)}`,
1700
+ `Sessions: ${sessions}`,
1701
+ `Observations: ${obsCount}`,
1702
+ `Memories: ${memCount}`,
1703
+ `Graph: ${nodes} nodes, ${edges} edges`,
1704
+ `Circuit: ${cb}`,
1705
+ `Heap: ${heapMB} MB`,
1706
+ `Uptime: ${uptime}s`,
1707
+ `Viewer: ${c.url(getViewerUrl())}`
1708
+ ];
1709
+ if (obsCount > 0) {
1710
+ lines.push("");
1711
+ lines.push(`Token savings: ~${tokensSaved.toLocaleString()} tokens saved (${pctSaved}% reduction)`);
1712
+ lines.push(` Full context: ~${estFullTokens.toLocaleString()} tokens`);
1713
+ lines.push(` Injected: ~${estInjectedTokens.toLocaleString()} tokens`);
1714
+ }
1715
+ if (flagsRes) {
1716
+ const provider = flagsRes.provider === "llm" ? pc.green("✓ llm") : pc.yellow("✗ noop (no key)");
1717
+ const embed = flagsRes.embeddingProvider === "embeddings" ? pc.green("✓ embeddings") : pc.dim("bm25-only");
1718
+ const flagRows = (flagsRes.flags || []).map((f) => ` ${f.enabled ? pc.green("✓") : pc.dim("✗")} ${pc.bold(f.key.padEnd(32))} ${f.label}`);
1719
+ lines.push("");
1720
+ lines.push(`Provider: ${provider}`);
1721
+ lines.push(`Embeddings: ${embed}`);
1722
+ lines.push(`Flags:`);
1723
+ flagRows.forEach((r) => lines.push(r));
1724
+ }
1725
+ if (followupRes && Number.isFinite(followupRes.agentInitiatedSearches)) {
1726
+ const total = Number(followupRes.agentInitiatedSearches) || 0;
1727
+ const hits = Number(followupRes.followupWithinWindow) || 0;
1728
+ const pct = total > 0 ? Math.round(hits / total * 100) : 0;
1729
+ lines.push("");
1730
+ lines.push(`Followup rate: ${hits}/${total} (${pct}%) within ${followupRes.windowSeconds}s — directional, may overcount on refinement`);
1731
+ }
1732
+ p.note(lines.join("\n"), "agentmemory");
1733
+ } catch (err) {
1734
+ p.log.error(err instanceof Error ? err.message : String(err));
1735
+ process.exit(1);
1736
+ }
1737
+ }
1738
+ function formatChecks(checks) {
1739
+ return checks.map((c) => `${c.ok ? pc.green("✓") : pc.red("✗")} ${c.name}${c.hint ? `\n ${c.hint}` : ""}`).join("\n");
1740
+ }
1741
+ function findLatestDebugLog(debugDir) {
1742
+ const latestLink = join(debugDir, "latest");
1743
+ try {
1744
+ if (existsSync(latestLink)) {
1745
+ const target = readlinkSync(latestLink);
1746
+ const resolved = target.startsWith("/") ? target : join(debugDir, target);
1747
+ if (existsSync(resolved)) return resolved;
1748
+ }
1749
+ } catch {}
1750
+ try {
1751
+ const newest = readdirSync(debugDir).filter((f) => f.endsWith(".txt")).map((f) => ({
1752
+ f,
1753
+ m: statSync(join(debugDir, f)).mtimeMs
1754
+ })).sort((a, b) => b.m - a.m)[0];
1755
+ if (newest) return join(debugDir, newest.f);
1756
+ } catch {}
1757
+ }
1758
+ function checkClaudeCodeHooks() {
1759
+ const debugDir = join(homedir(), ".claude", "debug");
1760
+ if (!existsSync(debugDir)) return { state: "no-cc-dir" };
1761
+ const logPath = findLatestDebugLog(debugDir);
1762
+ if (!logPath) return { state: "no-debug-log" };
1763
+ let content;
1764
+ try {
1765
+ content = readFileSync(logPath, "utf8");
1766
+ } catch {
1767
+ return { state: "no-debug-log" };
1768
+ }
1769
+ const match = content.match(/Loaded hooks from standard location for plugin agentmemory:\s*(\S+)/);
1770
+ if (match) return {
1771
+ state: "loaded",
1772
+ manifestPath: match[1]
1773
+ };
1774
+ if (content.includes("Loading hooks from plugin: agentmemory")) return { state: "loaded" };
1775
+ return { state: "not-loaded" };
1776
+ }
1777
+ function buildDoctorContext() {
1778
+ return {
1779
+ baseUrl: getBaseUrl(),
1780
+ viewerUrl: getViewerUrl(),
1781
+ envPath: join(homedir(), ".agentmemory", ".env"),
1782
+ pidfilePath: enginePidfilePath(),
1783
+ enginePath: engineStatePath(),
1784
+ pinnedVersion: IIPINNED_VERSION
1785
+ };
1786
+ }
1787
+ function buildDoctorEffects() {
1788
+ return {
1789
+ envFileExists: () => existsSync(join(homedir(), ".agentmemory", ".env")),
1790
+ readEnvFile: () => {
1791
+ try {
1792
+ return parseEnvFile(readFileSync(join(homedir(), ".agentmemory", ".env"), "utf-8"));
1793
+ } catch {
1794
+ return {};
1795
+ }
1796
+ },
1797
+ pidfileExists: () => existsSync(enginePidfilePath()),
1798
+ pidfilePidIsAlive: () => {
1799
+ const pid = readEnginePidfile();
1800
+ if (pid === null) return null;
1801
+ return pidAlive(pid);
1802
+ },
1803
+ findIiiBinary: () => whichBinary("iii"),
1804
+ localBinIiiPath: () => privateIiiPath(),
1805
+ iiiBinaryVersion: (binPath) => iiiBinVersion(binPath),
1806
+ viewerReachable: async (timeoutMs = 2e3) => {
1807
+ try {
1808
+ await discoverViewerPort();
1809
+ return (await fetch(getViewerUrl(), { signal: AbortSignal.timeout(timeoutMs) })).ok;
1810
+ } catch {
1811
+ return false;
1812
+ }
1813
+ },
1814
+ runInit: async () => {
1815
+ try {
1816
+ await runInit();
1817
+ return {
1818
+ ok: true,
1819
+ message: "Wrote ~/.agentmemory/.env"
1820
+ };
1821
+ } catch (err) {
1822
+ return {
1823
+ ok: false,
1824
+ message: err instanceof Error ? err.message : String(err)
1825
+ };
1826
+ }
1827
+ },
1828
+ openEditor: async (path) => {
1829
+ const editor = process.env["EDITOR"] || process.env["VISUAL"] || "nano";
1830
+ p.log.info(`Opening ${path} in ${editor}…`);
1831
+ try {
1832
+ const result = spawnSync(editor, [path], { stdio: "inherit" });
1833
+ if (result.error) return {
1834
+ ok: false,
1835
+ message: `Failed to launch ${editor}: ${result.error.message}`
1836
+ };
1837
+ if ((result.status ?? 0) !== 0) return {
1838
+ ok: false,
1839
+ message: `${editor} exited with code ${result.status}`
1840
+ };
1841
+ return {
1842
+ ok: true,
1843
+ message: `Saved ${path}`
1844
+ };
1845
+ } catch (err) {
1846
+ return {
1847
+ ok: false,
1848
+ message: err instanceof Error ? err.message : String(err)
1849
+ };
1850
+ }
1851
+ },
1852
+ runIiiInstaller: async () => {
1853
+ const r = await runIiiInstaller();
1854
+ return {
1855
+ ok: r.ok,
1856
+ message: r.ok ? `Installed iii v${IIPINNED_VERSION} to ${r.binPath}` : "iii installer failed (see warnings above)"
1857
+ };
1858
+ },
1859
+ runStop: async () => {
1860
+ try {
1861
+ const portPids = findEnginePidsByPort(getRestPort());
1862
+ const pidfilePid = readEnginePidfile();
1863
+ if (portPids.length === 0 && pidfilePid === null) {
1864
+ clearEnginePidfile();
1865
+ clearEngineState();
1866
+ return {
1867
+ ok: true,
1868
+ message: "Nothing to stop."
1869
+ };
1870
+ }
1871
+ const candidates = /* @__PURE__ */ new Set();
1872
+ if (pidfilePid) candidates.add(pidfilePid);
1873
+ for (const pid of portPids) candidates.add(pid);
1874
+ let allStopped = true;
1875
+ for (const pid of candidates) if (!await signalAndWait(pid, "SIGTERM", 3e3)) allStopped = false;
1876
+ clearEnginePidfile();
1877
+ clearEngineState();
1878
+ return {
1879
+ ok: allStopped,
1880
+ message: allStopped ? "Engine stopped." : "Some engine pids survived."
1881
+ };
1882
+ } catch (err) {
1883
+ return {
1884
+ ok: false,
1885
+ message: err instanceof Error ? err.message : String(err)
1886
+ };
1887
+ }
1888
+ },
1889
+ runStart: async () => {
1890
+ try {
1891
+ if (!await startEngine()) return {
1892
+ ok: false,
1893
+ message: "startEngine() returned false"
1894
+ };
1895
+ const ready = await waitForEngine(15e3);
1896
+ return {
1897
+ ok: ready,
1898
+ message: ready ? "Engine ready" : "Engine did not become ready within 15s"
1899
+ };
1900
+ } catch (err) {
1901
+ return {
1902
+ ok: false,
1903
+ message: err instanceof Error ? err.message : String(err)
1904
+ };
1905
+ }
1906
+ },
1907
+ clearEnginePidAndState: () => {
1908
+ clearEnginePidfile();
1909
+ clearEngineState();
1910
+ }
1911
+ };
1912
+ }
1913
+ async function passiveServerChecks() {
1914
+ const base = getBaseUrl();
1915
+ const checks = [];
1916
+ const serverUp = await isEngineRunning();
1917
+ checks.push({
1918
+ name: "Server reachable",
1919
+ ok: serverUp,
1920
+ hint: serverUp ? void 0 : `Start with: npx @agentmemory/agentmemory (tried ${base})`
1921
+ });
1922
+ if (!serverUp) return checks;
1923
+ const [health, flags, graph] = await Promise.all([
1924
+ apiFetch(base, "health", 3e3),
1925
+ apiFetch(base, "config/flags", 3e3),
1926
+ apiFetch(base, "graph/stats", 3e3)
1927
+ ]);
1928
+ const hasLlm = flags?.provider === "llm";
1929
+ const hasEmbed = flags?.embeddingProvider === "embeddings";
1930
+ const graphHas = Number(graph?.totalNodes ?? graph?.nodes ?? graph?.nodeCount ?? 0) > 0;
1931
+ checks.push({
1932
+ name: "Health status",
1933
+ ok: health?.status === "healthy",
1934
+ hint: health?.status === "healthy" ? void 0 : `Status: ${health?.status || "unknown"}`
1935
+ }, {
1936
+ name: "LLM provider",
1937
+ ok: hasLlm,
1938
+ hint: hasLlm ? void 0 : "set ANTHROPIC_API_KEY (or GEMINI/OPENROUTER/MINIMAX) in ~/.agentmemory/.env"
1939
+ }, {
1940
+ name: "Embedding provider",
1941
+ ok: hasEmbed,
1942
+ hint: hasEmbed ? void 0 : "Running BM25-only. Add OPENAI_API_KEY / VOYAGE_API_KEY / COHERE_API_KEY / OLLAMA_HOST"
1943
+ });
1944
+ for (const f of flags?.flags || []) checks.push({
1945
+ name: f.label,
1946
+ ok: f.enabled,
1947
+ hint: f.enabled ? void 0 : f.enableHow
1948
+ });
1949
+ const cc = checkClaudeCodeHooks();
1950
+ const ccCheck = (() => {
1951
+ switch (cc.state) {
1952
+ case "loaded": return {
1953
+ ok: true,
1954
+ hint: cc.manifestPath ? `manifest: ${cc.manifestPath}` : void 0
1955
+ };
1956
+ case "not-loaded": return {
1957
+ ok: false,
1958
+ hint: "Plugin enabled but hooks not loaded by Claude Code. Try: /plugin uninstall agentmemory@agentmemory && /plugin install agentmemory@agentmemory, then restart the session."
1959
+ };
1960
+ case "no-debug-log": return {
1961
+ ok: false,
1962
+ hint: "Cannot verify — no Claude Code debug log found. Run once with `claude --debug -p \"x\"`, then re-run doctor."
1963
+ };
1964
+ case "no-cc-dir": return;
1965
+ }
1966
+ })();
1967
+ if (ccCheck) checks.push({
1968
+ name: "Claude Code plugin hooks registered",
1969
+ ...ccCheck
1970
+ });
1971
+ checks.push({
1972
+ name: "Knowledge graph populated",
1973
+ ok: graphHas,
1974
+ hint: graphHas ? void 0 : "Graph is empty. Run a session with GRAPH_EXTRACTION_ENABLED=true."
1975
+ });
1976
+ return checks;
1977
+ }
1978
+ async function askFixAction(d) {
1979
+ const choice = await p.select({
1980
+ message: `[${d.id}] ${d.message}`,
1981
+ options: [
1982
+ {
1983
+ value: "fix",
1984
+ label: "F Fix",
1985
+ hint: d.fixPreview
1986
+ },
1987
+ {
1988
+ value: "skip",
1989
+ label: "S Skip"
1990
+ },
1991
+ {
1992
+ value: "more",
1993
+ label: "? More info"
1994
+ },
1995
+ {
1996
+ value: "quit",
1997
+ label: "Q Quit doctor"
1998
+ }
1999
+ ],
2000
+ initialValue: "fix"
2001
+ });
2002
+ if (p.isCancel(choice)) return "quit";
2003
+ return choice;
2004
+ }
2005
+ async function applyFixWithReport(d, ctx, dryRun) {
2006
+ if (dryRun) {
2007
+ p.log.info(`[dry-run] would: ${d.fixPreview}`);
2008
+ return {
2009
+ ok: true,
2010
+ message: "(dry-run)"
2011
+ };
2012
+ }
2013
+ const result = await d.fix(ctx);
2014
+ if (result.ok) p.log.success(result.message ?? `${d.id} fixed.`);
2015
+ else p.log.error(result.message ?? `${d.id} fix failed.`);
2016
+ return result;
2017
+ }
2018
+ async function runDoctor() {
2019
+ p.intro("agentmemory doctor");
2020
+ const applyAll = args.includes("--all");
2021
+ const dryRun = args.includes("--dry-run");
2022
+ if (applyAll && dryRun) {
2023
+ p.log.error("Cannot combine --all and --dry-run.");
2024
+ process.exit(2);
2025
+ }
2026
+ const passive = await passiveServerChecks();
2027
+ const passivePassed = passive.filter((c) => c.ok).length;
2028
+ p.note(formatChecks(passive), `server: ${passivePassed}/${passive.length} passing`);
2029
+ const ctx = buildDoctorContext();
2030
+ const diagnostics = buildDiagnostics(buildDoctorEffects());
2031
+ if (dryRun) {
2032
+ const results = [];
2033
+ for (const d of diagnostics) results.push({
2034
+ diagnostic: d,
2035
+ status: await d.check(ctx)
2036
+ });
2037
+ const lines = dryRunPlan(ctx, results);
2038
+ p.note(lines.join("\n"), "dry-run plan");
2039
+ p.outro("Dry-run complete. Re-run without --dry-run to apply.");
2040
+ return;
2041
+ }
2042
+ let failed = 0;
2043
+ let fixed = 0;
2044
+ let skipped = 0;
2045
+ let quit = false;
2046
+ for (const d of diagnostics) {
2047
+ if (quit) {
2048
+ skipped++;
2049
+ continue;
2050
+ }
2051
+ const status = await d.check(ctx);
2052
+ if (status.ok) {
2053
+ p.log.success(`${d.id} ✓${status.detail ? ` (${status.detail})` : ""}`);
2054
+ continue;
2055
+ }
2056
+ failed++;
2057
+ p.log.warn(`${d.id} ✗ ${status.detail ?? ""}`.trim());
2058
+ p.log.info(`why: ${d.fixPreview}`);
2059
+ if (d.manualOnly) p.log.info(`(manual fix only — see "${d.id}" docs)`);
2060
+ if (applyAll) {
2061
+ if ((await applyFixWithReport(d, ctx, false)).ok) fixed++;
2062
+ if (!(await d.check(ctx)).ok) p.log.warn(`${d.id} still failing after fix.`);
2063
+ continue;
2064
+ }
2065
+ while (true) {
2066
+ const action = await askFixAction(d);
2067
+ if (action === "fix") {
2068
+ if ((await applyFixWithReport(d, ctx, false)).ok) {
2069
+ const after = await d.check(ctx);
2070
+ if (after.ok) fixed++;
2071
+ else p.log.warn(`${d.id} still failing after fix: ${after.detail ?? ""}`);
2072
+ }
2073
+ break;
2074
+ }
2075
+ if (action === "skip") {
2076
+ skipped++;
2077
+ break;
2078
+ }
2079
+ if (action === "more") {
2080
+ p.note(d.moreInfo, `[${d.id}] more info`);
2081
+ continue;
2082
+ }
2083
+ if (action === "quit") {
2084
+ quit = true;
2085
+ break;
2086
+ }
2087
+ }
2088
+ }
2089
+ const summary = `${diagnostics.length} checks · ${failed} failing · ${fixed} fixed · ${skipped} skipped`;
2090
+ if (quit) {
2091
+ p.outro(`Quit early. ${summary}`);
2092
+ process.exit(1);
2093
+ }
2094
+ if (failed === 0) {
2095
+ p.outro("All diagnostics passing. agentmemory is healthy.");
2096
+ return;
2097
+ }
2098
+ if (failed - fixed === 0) {
2099
+ p.outro(`All fixes applied. ${summary}`);
2100
+ return;
2101
+ }
2102
+ p.outro(summary);
2103
+ process.exit(1);
2104
+ }
2105
+ function buildDemoSessions() {
2106
+ return [
2107
+ {
2108
+ id: generateId("demo"),
2109
+ title: "Session 1: JWT auth setup",
2110
+ observations: [
2111
+ {
2112
+ toolName: "Write",
2113
+ toolInput: { file_path: "src/middleware/auth.ts" },
2114
+ toolOutput: "Created JWT middleware using jose library. Tokens expire after 30 days. Chose jose over jsonwebtoken for Edge compatibility."
2115
+ },
2116
+ {
2117
+ toolName: "Write",
2118
+ toolInput: { file_path: "test/auth.test.ts" },
2119
+ toolOutput: "Added token validation tests covering expired, malformed, and valid cases."
2120
+ },
2121
+ {
2122
+ toolName: "Bash",
2123
+ toolInput: { command: "npm test" },
2124
+ toolOutput: "All 12 auth tests passing."
2125
+ }
2126
+ ]
2127
+ },
2128
+ {
2129
+ id: generateId("demo"),
2130
+ title: "Session 2: Database migration debugging",
2131
+ observations: [{
2132
+ toolName: "Read",
2133
+ toolInput: { file_path: "prisma/schema.prisma" },
2134
+ toolOutput: "Found N+1 query issue in user relations. Need to add include on posts query."
2135
+ }, {
2136
+ toolName: "Edit",
2137
+ toolInput: { file_path: "src/api/users.ts" },
2138
+ toolOutput: "Fixed N+1 by adding Prisma include. Query time dropped from 450ms to 28ms."
2139
+ }]
2140
+ },
2141
+ {
2142
+ id: generateId("demo"),
2143
+ title: "Session 3: Rate limiting",
2144
+ observations: [{
2145
+ toolName: "Write",
2146
+ toolInput: { file_path: "src/middleware/ratelimit.ts" },
2147
+ toolOutput: "Added rate limiting middleware with 100 req/min default. Uses in-memory store for dev, Redis for prod."
2148
+ }]
2149
+ }
2150
+ ];
2151
+ }
2152
+ async function postJson(url, body, timeoutMs = 5e3) {
2153
+ try {
2154
+ const res = await fetch(url, {
2155
+ method: "POST",
2156
+ headers: { "Content-Type": "application/json" },
2157
+ body: JSON.stringify(body),
2158
+ signal: AbortSignal.timeout(timeoutMs)
2159
+ });
2160
+ if (!res.ok) return null;
2161
+ return await res.json().catch(() => null);
2162
+ } catch {
2163
+ return null;
2164
+ }
2165
+ }
2166
+ async function postJsonStrict(url, body, timeoutMs = 5e3) {
2167
+ const res = await fetch(url, {
2168
+ method: "POST",
2169
+ headers: { "Content-Type": "application/json" },
2170
+ body: JSON.stringify(body),
2171
+ signal: AbortSignal.timeout(timeoutMs)
2172
+ });
2173
+ if (!res.ok) {
2174
+ const errBody = await res.text().catch(() => "");
2175
+ const suffix = errBody ? ` — ${errBody.slice(0, 200)}` : "";
2176
+ throw new Error(`POST ${url} failed: ${res.status} ${res.statusText}${suffix}`);
2177
+ }
2178
+ return await res.json().catch(() => null);
2179
+ }
2180
+ async function seedDemoSession(base, project, session) {
2181
+ await postJsonStrict(`${base}/agentmemory/session/start`, {
2182
+ sessionId: session.id,
2183
+ project,
2184
+ cwd: project
2185
+ });
2186
+ let stored = 0;
2187
+ for (const obs of session.observations) {
2188
+ const url = `${base}/agentmemory/observe`;
2189
+ const payload = {
2190
+ hookType: "post_tool_use",
2191
+ sessionId: session.id,
2192
+ project,
2193
+ cwd: project,
2194
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2195
+ data: {
2196
+ tool_name: obs.toolName,
2197
+ tool_input: obs.toolInput,
2198
+ tool_output: obs.toolOutput
2199
+ }
2200
+ };
2201
+ try {
2202
+ const res = await fetch(url, {
2203
+ method: "POST",
2204
+ headers: { "Content-Type": "application/json" },
2205
+ body: JSON.stringify(payload),
2206
+ signal: AbortSignal.timeout(5e3)
2207
+ });
2208
+ if (res.ok) stored++;
2209
+ else {
2210
+ const body = await res.text().catch(() => "");
2211
+ p.log.warn(`observe failed for ${obs.toolName}: ${res.status} ${res.statusText}${body ? ` — ${body.slice(0, 160)}` : ""}`);
2212
+ }
2213
+ } catch (err) {
2214
+ p.log.warn(`observe request failed for ${obs.toolName}: ${err instanceof Error ? err.message : String(err)}`);
2215
+ }
2216
+ }
2217
+ await postJsonStrict(`${base}/agentmemory/session/end`, { sessionId: session.id });
2218
+ return stored;
2219
+ }
2220
+ async function runDemoSearch(base, query) {
2221
+ const items = (await postJson(`${base}/agentmemory/smart-search`, {
2222
+ query,
2223
+ limit: 5
2224
+ }, 1e4))?.results ?? [];
2225
+ return {
2226
+ query,
2227
+ hits: items.length,
2228
+ topTitle: items[0]?.title ?? "(no results)"
2229
+ };
2230
+ }
2231
+ function findEnvExample() {
2232
+ const candidates = [
2233
+ join(__dirname, "..", ".env.example"),
2234
+ join(__dirname, ".env.example"),
2235
+ join(process.cwd(), ".env.example")
2236
+ ];
2237
+ for (const c of candidates) if (existsSync(c)) return c;
2238
+ return null;
2239
+ }
2240
+ async function runInit() {
2241
+ p.intro("agentmemory init");
2242
+ const target = join(homedir(), ".agentmemory", ".env");
2243
+ const template = findEnvExample();
2244
+ if (!template) {
2245
+ p.log.error("Could not locate .env.example in the package. Re-install with: npm i -g @agentmemory/agentmemory");
2246
+ process.exit(1);
2247
+ }
2248
+ const dir = dirname(target);
2249
+ const { mkdir, copyFile } = await import("node:fs/promises");
2250
+ const { constants: fsConstants } = await import("node:fs");
2251
+ try {
2252
+ await mkdir(dir, { recursive: true });
2253
+ await copyFile(template, target, fsConstants.COPYFILE_EXCL);
2254
+ } catch (err) {
2255
+ if (err?.code === "EEXIST") {
2256
+ p.log.warn(`${target} already exists — leaving it untouched.`);
2257
+ p.log.info(`Compare against the latest template: diff ${target} ${template}`);
2258
+ p.outro("Nothing changed.");
2259
+ return;
2260
+ }
2261
+ p.log.error(`Failed to copy template: ${err instanceof Error ? err.message : String(err)}`);
2262
+ process.exit(1);
2263
+ }
2264
+ p.log.success(`Wrote ${target}`);
2265
+ p.note([
2266
+ "All keys are commented out by default. Uncomment the ones you want.",
2267
+ "",
2268
+ "Common next steps:",
2269
+ " 1. Pick an LLM provider key (ANTHROPIC_API_KEY / OPENAI_API_KEY / GEMINI_API_KEY / etc.)",
2270
+ " 2. Run `npx @agentmemory/agentmemory doctor` to verify the daemon sees them",
2271
+ " 3. Run `npx @agentmemory/agentmemory` to start the worker"
2272
+ ].join("\n"), "Next steps");
2273
+ p.outro(`Edit ${target} and you're set.`);
2274
+ }
2275
+ async function startServerForDemo() {
2276
+ if (await isAgentmemoryReady()) return async () => {};
2277
+ const startedEngine = !await isEngineRunning();
2278
+ if (startedEngine) {
2279
+ if (!await startEngine()) {
2280
+ p.log.error("Could not start iii-engine for the demo.");
2281
+ p.note(installInstructions().join("\n"), "Setup required");
2282
+ process.exit(1);
2283
+ }
2284
+ if (!await waitForEngine(15e3)) {
2285
+ p.log.error("iii-engine did not become ready within 15s.");
2286
+ process.exit(1);
2287
+ }
2288
+ }
2289
+ await import("./src-Cr3pH_uH.mjs");
2290
+ if (!await waitForAgentmemoryReady(15e3)) {
2291
+ p.log.error("agentmemory worker did not become ready within 15s.");
2292
+ process.exit(1);
2293
+ }
2294
+ return async () => {
2295
+ if (!startedEngine) return;
2296
+ const port = getRestPort();
2297
+ const state = readEngineState();
2298
+ if (state?.kind === "docker") {
2299
+ await stopDockerEngine(state.composeFile, port).catch(() => {});
2300
+ return;
2301
+ }
2302
+ const pids = new Set(findEnginePidsByPort(port));
2303
+ const pidfilePid = readEnginePidfile();
2304
+ if (pidfilePid) pids.add(pidfilePid);
2305
+ for (const pid of pids) await signalAndWait(pid, "SIGTERM", 3e3).catch(() => {});
2306
+ clearEnginePidfile();
2307
+ clearEngineState();
2308
+ clearWorkerPidfile();
2309
+ };
2310
+ }
2311
+ async function runDemo() {
2312
+ const port = getRestPort();
2313
+ const base = `http://localhost:${port}`;
2314
+ p.intro("agentmemory demo");
2315
+ const serve = args.includes("--serve");
2316
+ let teardown = async () => {};
2317
+ if (serve) teardown = await startServerForDemo();
2318
+ else if (!await isAgentmemoryReady()) {
2319
+ 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/*.`);
2320
+ p.log.info("Start it with: npx @agentmemory/agentmemory");
2321
+ p.log.info("Or run a one-command demo with: npx @agentmemory/agentmemory demo --serve");
2322
+ process.exit(1);
2323
+ }
2324
+ try {
2325
+ await runDemoBody(base);
2326
+ } finally {
2327
+ await teardown();
2328
+ }
2329
+ if (serve) process.exit(0);
2330
+ }
2331
+ async function runDemoBody(base) {
2332
+ const demoProject = "/tmp/agentmemory-demo";
2333
+ const sessions = buildDemoSessions();
2334
+ const sSeed = p.spinner();
2335
+ sSeed.start("Seeding 3 demo sessions with realistic observations...");
2336
+ let totalObs = 0;
2337
+ for (const session of sessions) totalObs += await seedDemoSession(base, demoProject, session);
2338
+ sSeed.stop(`Seeded ${totalObs} observations across ${sessions.length} sessions`);
2339
+ const queries = [
2340
+ "jwt auth middleware",
2341
+ "database performance optimization",
2342
+ "rate limiting"
2343
+ ];
2344
+ const sQuery = p.spinner();
2345
+ sQuery.start(`Running ${queries.length} smart-search queries...`);
2346
+ const results = [];
2347
+ for (const query of queries) results.push(await runDemoSearch(base, query));
2348
+ sQuery.stop("Search complete");
2349
+ const lines = [
2350
+ `Project: ${demoProject}`,
2351
+ `Sessions: ${sessions.length} seeded (${totalObs} observations)`,
2352
+ "",
2353
+ c.label("Search results:"),
2354
+ ...results.flatMap((r) => [` ${c.label(`"${r.query}"`)}`, ` ${c.dim("→")} ${c.ok(`${r.hits} hit(s)`)}, top: ${r.topTitle.slice(0, 60)}`]),
2355
+ "",
2356
+ c.accent(`Notice: searching "database performance optimization"`),
2357
+ c.accent(`found the N+1 query fix — keyword matching can't do that.`),
2358
+ "",
2359
+ `Viewer: ${c.url(getViewerUrl())}`,
2360
+ `Clean up with: ${c.dim(`curl -X DELETE "${base}/agentmemory/sessions?project=${demoProject}"`)}`
2361
+ ];
2362
+ p.note(lines.join("\n"), "demo complete");
2363
+ p.log.success("agentmemory is working. Point your agent at it and get back to coding.");
2364
+ }
2365
+ function runCommand(command, commandArgs, options = { label: "command" }) {
2366
+ const spinner = p.spinner();
2367
+ spinner.start(options.label);
2368
+ const result = spawnSync(command, commandArgs, {
2369
+ cwd: options.cwd || process.cwd(),
2370
+ stdio: "pipe",
2371
+ encoding: "utf-8"
2372
+ });
2373
+ if (result.status === 0) {
2374
+ spinner.stop(`${options.label} ${pc.green("✓")}`);
2375
+ return true;
2376
+ }
2377
+ const stderr = (result.stderr || "").toString().trim();
2378
+ const stdout = (result.stdout || "").toString().trim();
2379
+ const msg = stderr || stdout || "unknown error";
2380
+ if (options.optional) {
2381
+ spinner.stop(`${options.label} (skipped)`);
2382
+ p.log.warn(msg.slice(0, 300));
2383
+ return false;
2384
+ }
2385
+ spinner.stop(`${options.label} ${pc.red("✗")}`);
2386
+ p.log.error(msg.slice(0, 300));
2387
+ return false;
2388
+ }
2389
+ async function runUpgrade() {
2390
+ p.intro("agentmemory upgrade");
2391
+ const cwd = process.cwd();
2392
+ const hasPackageJson = existsSync(join(cwd, "package.json"));
2393
+ const hasPnpmLock = existsSync(join(cwd, "pnpm-lock.yaml"));
2394
+ const pnpmBin = whichBinary("pnpm");
2395
+ const npmBin = whichBinary("npm");
2396
+ const dockerBin = whichBinary("docker");
2397
+ p.log.info(`Working directory: ${cwd}`);
2398
+ const requireSuccess = (ok, label) => {
2399
+ if (!ok) {
2400
+ p.log.error(`Upgrade aborted: ${label} failed.`);
2401
+ process.exit(1);
2402
+ }
2403
+ };
2404
+ if (hasPackageJson) if (!!pnpmBin && hasPnpmLock && pnpmBin) {
2405
+ requireSuccess(runCommand(pnpmBin, ["install"], { label: "Refreshing dependencies (pnpm install)" }), "pnpm install");
2406
+ runCommand(pnpmBin, ["up", "iii-sdk@0.11.2"], {
2407
+ label: "Pinning iii-sdk@0.11.2",
2408
+ optional: true
2409
+ });
2410
+ } else if (npmBin) {
2411
+ requireSuccess(runCommand(npmBin, ["install"], { label: "Refreshing dependencies (npm install)" }), "npm install");
2412
+ runCommand(npmBin, ["install", "iii-sdk@0.11.2"], {
2413
+ label: "Pinning iii-sdk@0.11.2",
2414
+ optional: true
2415
+ });
2416
+ } else p.log.warn("No package manager found (pnpm/npm). Skipping JS dependency upgrade.");
2417
+ else p.log.warn("No package.json in current directory. Skipping JS dependency upgrade.");
2418
+ const upgradeEngine = await p.confirm({
2419
+ message: "Re-run the iii-engine install script (curl | sh)?",
2420
+ initialValue: true
2421
+ });
2422
+ if (p.isCancel(upgradeEngine)) {
2423
+ p.cancel("Cancelled.");
2424
+ return process.exit(0);
2425
+ }
2426
+ if (upgradeEngine === true) await runIiiInstaller();
2427
+ else p.log.info("Skipped iii-engine installer.");
2428
+ if (dockerBin) runCommand(dockerBin, ["pull", `iiidev/iii:${IIPINNED_VERSION}`], {
2429
+ label: `Pulling iii Docker image v${IIPINNED_VERSION} (pinned)`,
2430
+ optional: true
2431
+ });
2432
+ else p.log.info("Docker not found. Skipping Docker image refresh.");
2433
+ p.note([
2434
+ "Upgrade flow completed.",
2435
+ "",
2436
+ "Recommended next steps:",
2437
+ " 1) agentmemory status",
2438
+ " 2) npm/pnpm test",
2439
+ " 3) restart agentmemory process"
2440
+ ].join("\n"), "agentmemory upgrade");
2441
+ }
2442
+ function pidAlive(pid) {
2443
+ try {
2444
+ process.kill(pid, 0);
2445
+ return true;
2446
+ } catch (err) {
2447
+ return err?.code === "EPERM";
2448
+ }
2449
+ }
2450
+ async function signalAndWait(pid, initialSignal, timeoutMs) {
2451
+ try {
2452
+ process.kill(pid, initialSignal);
2453
+ } catch (err) {
2454
+ const code = err?.code;
2455
+ if (code === "ESRCH") return true;
2456
+ if (code === "EPERM") {
2457
+ p.log.warn(`No permission to signal pid ${pid}. Try: kill ${pid}`);
2458
+ return false;
2459
+ }
2460
+ vlog(`${initialSignal} ${pid}: ${err instanceof Error ? err.message : String(err)}`);
2461
+ return false;
2462
+ }
2463
+ const deadline = Date.now() + timeoutMs;
2464
+ while (Date.now() < deadline) {
2465
+ if (!pidAlive(pid)) return true;
2466
+ await new Promise((r) => setTimeout(r, 200));
2467
+ }
2468
+ if (!pidAlive(pid)) return true;
2469
+ try {
2470
+ process.kill(pid, "SIGKILL");
2471
+ } catch (err) {
2472
+ if (err?.code === "ESRCH") return true;
2473
+ vlog(`SIGKILL ${pid}: ${err instanceof Error ? err.message : String(err)}`);
2474
+ return false;
2475
+ }
2476
+ await new Promise((r) => setTimeout(r, 200));
2477
+ return !pidAlive(pid);
2478
+ }
2479
+ function findEnginePidsByPort(port) {
2480
+ if (IS_WINDOWS) return [];
2481
+ const lsof = whichBinary("lsof");
2482
+ if (!lsof) return [];
2483
+ const selfPid = process.pid;
2484
+ try {
2485
+ return execFileSync(lsof, [
2486
+ "-i",
2487
+ `:${port}`,
2488
+ "-sTCP:LISTEN",
2489
+ "-t"
2490
+ ], {
2491
+ encoding: "utf-8",
2492
+ stdio: [
2493
+ "ignore",
2494
+ "pipe",
2495
+ "ignore"
2496
+ ]
2497
+ }).split(/\s+/).map((s) => parseInt(s, 10)).filter((n) => Number.isFinite(n) && n > 0 && n !== selfPid);
2498
+ } catch (err) {
2499
+ vlog(`lsof :${port}: ${err instanceof Error ? err.message : String(err)}`);
2500
+ return [];
2501
+ }
2502
+ }
2503
+ async function stopDockerEngine(composeFile, port) {
2504
+ const dockerBin = whichBinary("docker");
2505
+ if (!dockerBin) {
2506
+ p.log.error(`Engine was started via Docker compose, but \`docker\` is no longer on PATH. Stop it manually:\n docker compose -f ${composeFile} down`);
2507
+ process.exit(1);
2508
+ }
2509
+ if (!existsSync(composeFile)) {
2510
+ 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)`);
2511
+ process.exit(1);
2512
+ }
2513
+ const ok = runCommand(dockerBin, [
2514
+ "compose",
2515
+ "-f",
2516
+ composeFile,
2517
+ "down"
2518
+ ], { label: `docker compose -f ${composeFile} down` });
2519
+ clearEnginePidfile();
2520
+ clearEngineState();
2521
+ clearWorkerPidfile();
2522
+ if (!ok) {
2523
+ p.log.error(`docker compose down failed. The engine may still be running on :${port}. Inspect with:\n docker compose -f ${composeFile} ps`);
2524
+ process.exit(1);
2525
+ }
2526
+ p.outro("Stopped. Memories persisted to disk; restart anytime with: npx @agentmemory/agentmemory");
2527
+ }
2528
+ async function runStop() {
2529
+ p.intro("agentmemory stop");
2530
+ const port = getRestPort();
2531
+ const state = readEngineState();
2532
+ const running = await isEngineRunning();
2533
+ const force = args.includes("--force");
2534
+ if (state?.kind === "docker") {
2535
+ if (!running) {
2536
+ p.log.info(`No engine responding on port ${port}.`);
2537
+ clearEnginePidfile();
2538
+ clearEngineState();
2539
+ clearWorkerPidfile();
2540
+ p.outro("Nothing to stop.");
2541
+ return;
2542
+ }
2543
+ await stopDockerEngine(state.composeFile, port);
2544
+ return;
2545
+ }
2546
+ const portPids = findEnginePidsByPort(port);
2547
+ const pidfilePid = readEnginePidfile();
2548
+ const workerPid = readWorkerPidfile();
2549
+ if (!running) {
2550
+ if (portPids.length === 0 && pidfilePid === null && workerPid === null) {
2551
+ clearEnginePidfile();
2552
+ clearEngineState();
2553
+ clearWorkerPidfile();
2554
+ p.outro("Nothing to stop.");
2555
+ return;
2556
+ }
2557
+ if (workerPid !== null && portPids.length === 0 && pidfilePid === null) {
2558
+ const s = p.spinner();
2559
+ s.start(`Stopping orphaned agentmemory worker (pid ${workerPid})...`);
2560
+ const ok = await signalAndWait(workerPid, "SIGTERM", 3e3);
2561
+ s.stop(ok ? `Stopped worker pid ${workerPid}` : `Failed to stop worker pid ${workerPid}`);
2562
+ clearEnginePidfile();
2563
+ clearEngineState();
2564
+ clearWorkerPidfile();
2565
+ if (!ok) {
2566
+ p.log.error(`Worker pid ${workerPid} survived SIGKILL. Investigate with \`ps\`.`);
2567
+ process.exit(1);
2568
+ }
2569
+ p.outro("Stopped orphaned worker. Memories persisted to disk.");
2570
+ return;
2571
+ }
2572
+ const survivors = new Set(portPids);
2573
+ if (pidfilePid) survivors.add(pidfilePid);
2574
+ if (workerPid) survivors.add(workerPid);
2575
+ p.log.warn(`Engine not responding on :${port}, but ${survivors.size} process(es) still hold the port or pidfile: ${[...survivors].join(", ")}`);
2576
+ p.log.info(`Preserving ~/.agentmemory/iii.pid + worker.pid. Investigate before manual cleanup:\n ps -p ${[...survivors].join(",")} -o pid,ppid,comm,etime\n ${IS_WINDOWS ? "netstat -ano | findstr :" + port : "lsof -i :" + port}`);
2577
+ process.exit(1);
2578
+ }
2579
+ if (!state) {
2580
+ const compose = discoverComposeFile();
2581
+ if (compose && pidfilePid === null) if (force) p.log.warn(`--force: bypassing Docker-heuristic guard. Falling back to native pidfile + lsof on :${port}.`);
2582
+ else {
2583
+ p.log.error(`Engine is running on :${port} but no pidfile or state file is present. It may have been started via Docker compose by a different shell. Refusing to signal host PIDs.\n\nStop it with:\n docker compose -f ${compose} down\n\nOr re-run with --force to signal whatever lsof finds on :${port}, or AGENTMEMORY_USE_DOCKER=1 to record state next time.`);
2584
+ process.exit(1);
2585
+ }
2586
+ }
2587
+ const candidates = /* @__PURE__ */ new Set();
2588
+ if (pidfilePid) candidates.add(pidfilePid);
2589
+ for (const pid of portPids) candidates.add(pid);
2590
+ const workerCandidates = /* @__PURE__ */ new Set();
2591
+ if (workerPid) workerCandidates.add(workerPid);
2592
+ if (candidates.size === 0 && workerCandidates.size === 0) {
2593
+ p.log.error(`Could not locate engine process. Try:\n ${IS_WINDOWS ? "netstat -ano | findstr :" + port : "lsof -i :" + port + " -t | xargs kill -9"}`);
2594
+ process.exit(1);
2595
+ }
2596
+ let allStopped = true;
2597
+ for (const pid of workerCandidates) {
2598
+ const s = p.spinner();
2599
+ s.start(`Stopping agentmemory worker (pid ${pid})... [flushing state]`);
2600
+ const ok = await signalAndWait(pid, "SIGTERM", 5e3);
2601
+ s.stop(ok ? `Stopped worker pid ${pid}` : `Failed to stop worker pid ${pid}`);
2602
+ if (!ok) allStopped = false;
2603
+ }
2604
+ for (const pid of candidates) {
2605
+ if (workerCandidates.has(pid)) continue;
2606
+ const s = p.spinner();
2607
+ s.start(`Stopping iii-engine (pid ${pid})...`);
2608
+ const ok = await signalAndWait(pid, "SIGTERM", 3e3);
2609
+ s.stop(ok ? `Stopped pid ${pid}` : `Failed to stop pid ${pid}`);
2610
+ if (!ok) allStopped = false;
2611
+ }
2612
+ clearEnginePidfile();
2613
+ clearEngineState();
2614
+ clearWorkerPidfile();
2615
+ if (!allStopped) {
2616
+ p.log.error("One or more processes survived SIGKILL. Investigate with `ps`.");
2617
+ process.exit(1);
2618
+ }
2619
+ p.outro("Stopped. Memories persisted to disk; restart anytime with: npx @agentmemory/agentmemory");
2620
+ }
2621
+ async function runMcp() {
2622
+ await import("./standalone-C6KzNkt5.mjs");
2623
+ }
2624
+ async function runConnectCmd() {
2625
+ const { runConnect } = await import("./connect-JnsJXc68.mjs").then((n) => n.n);
2626
+ await runConnect(args.slice(1));
2627
+ }
2628
+ async function runImportJsonl() {
2629
+ const VALUE_FLAGS = new Set(["--port", "--tools"]);
2630
+ let maxFiles;
2631
+ const tail = args.slice(1);
2632
+ const positional = [];
2633
+ for (let i = 0; i < tail.length; i++) {
2634
+ const a = tail[i];
2635
+ if (a === "--max-files") {
2636
+ const raw = tail[i + 1];
2637
+ const parsed = raw !== void 0 ? parseInt(raw, 10) : NaN;
2638
+ if (Number.isInteger(parsed) && parsed > 0) maxFiles = parsed;
2639
+ else if (raw !== void 0) p.log.warn(`Ignoring --max-files ${raw}: expected a positive integer.`);
2640
+ i++;
2641
+ continue;
2642
+ }
2643
+ if (a.startsWith("--max-files=")) {
2644
+ const raw = a.slice(12);
2645
+ const parsed = parseInt(raw, 10);
2646
+ if (Number.isInteger(parsed) && parsed > 0) maxFiles = parsed;
2647
+ else p.log.warn(`Ignoring --max-files=${raw}: expected a positive integer.`);
2648
+ continue;
2649
+ }
2650
+ if (VALUE_FLAGS.has(a)) {
2651
+ i++;
2652
+ continue;
2653
+ }
2654
+ if (a.startsWith("-")) continue;
2655
+ positional.push(a);
2656
+ }
2657
+ const pathArg = positional[0];
2658
+ const port = getRestPort();
2659
+ const base = `http://localhost:${port}`;
2660
+ let probeOk = false;
2661
+ let probeDetail = "";
2662
+ try {
2663
+ const probe = await fetch(`${base}/agentmemory/livez`, { signal: AbortSignal.timeout(2e3) });
2664
+ probeOk = probe.ok;
2665
+ if (!probeOk) {
2666
+ const probeBody = await probe.text().catch(() => "");
2667
+ probeDetail = `reachable but unhealthy (HTTP ${probe.status}${probeBody ? `: ${probeBody.slice(0, 200)}` : ""})`;
2668
+ }
2669
+ } catch (err) {
2670
+ probeOk = false;
2671
+ probeDetail = `unreachable (${err instanceof Error ? err.message : String(err)})`;
2672
+ }
2673
+ if (!probeOk) {
2674
+ p.log.error(`agentmemory livez probe failed on port ${port}: ${probeDetail}. Start it with \`npx @agentmemory/agentmemory\` in another terminal, then re-run this command.`);
2675
+ process.exit(1);
2676
+ }
2677
+ const body = {};
2678
+ if (pathArg) body["path"] = pathArg;
2679
+ if (maxFiles !== void 0) body["maxFiles"] = maxFiles;
2680
+ const headers = { "content-type": "application/json" };
2681
+ const secret = process.env["AGENTMEMORY_SECRET"];
2682
+ if (secret) headers["authorization"] = `Bearer ${secret}`;
2683
+ p.log.info(`Importing JSONL from ${pathArg || "~/.claude/projects"}…`);
2684
+ const spinner = p.spinner();
2685
+ spinner.start("scanning files");
2686
+ try {
2687
+ const res = await fetch(`${base}/agentmemory/replay/import-jsonl`, {
2688
+ method: "POST",
2689
+ headers,
2690
+ body: JSON.stringify(body),
2691
+ signal: AbortSignal.timeout(12e4)
2692
+ });
2693
+ const text = await res.text();
2694
+ let json = {};
2695
+ if (text.length > 0) try {
2696
+ json = JSON.parse(text);
2697
+ } catch {
2698
+ spinner.stop("failed");
2699
+ p.log.error(`server returned non-JSON response (HTTP ${res.status}): ${text.slice(0, 200)}`);
2700
+ process.exit(1);
2701
+ }
2702
+ if (!res.ok || json.success !== true) {
2703
+ spinner.stop("failed");
2704
+ const detail = json.error || (text.length === 0 ? "empty response body" : json.success === void 0 ? `HTTP ${res.status} (response missing success field)` : `HTTP ${res.status}`);
2705
+ if (res.status === 401) p.log.error(`${detail}. Set AGENTMEMORY_SECRET to match the server's secret and re-run.`);
2706
+ else if (res.status === 404) p.log.error(`${detail}. The running agentmemory server does not expose /agentmemory/replay/import-jsonl — upgrade to v0.8.13 or later.`);
2707
+ else p.log.error(detail);
2708
+ process.exit(1);
2709
+ }
2710
+ spinner.stop(`imported ${json.imported ?? 0} file(s), ${json.observations ?? 0} observation(s) across ${json.sessionIds?.length || 0} session(s)`);
2711
+ if (json.truncated) {
2712
+ const cap = json.maxFiles ?? 200;
2713
+ const upper = json.maxFilesUpperBound ?? 1e3;
2714
+ const discovered = json.discovered ?? 0;
2715
+ 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.`;
2716
+ 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).`);
2717
+ else {
2718
+ const suggested = Math.min(Math.max((discovered || cap) + 100, cap * 2), upper);
2719
+ p.log.warn(`${baseMsg} Re-run with --max-files=${suggested} (max ${upper}) or batch by subdirectory.`);
2720
+ }
2721
+ }
2722
+ if (json.sessionIds && json.sessionIds.length > 0) p.log.info(`View at ${getViewerUrl()} → Replay tab`);
2723
+ } catch (err) {
2724
+ spinner.stop("failed");
2725
+ if (err instanceof Error && err.name === "TimeoutError") p.log.error("import timed out after 2 minutes");
2726
+ else p.log.error(err instanceof Error ? err.message : String(err));
2727
+ process.exit(1);
2728
+ }
2729
+ }
2730
+ function loadConnectManifest(home) {
2731
+ const path = join(home, ".agentmemory", "backups", "connect-manifest.json");
2732
+ try {
2733
+ const raw = readFileSync(path, "utf-8");
2734
+ const parsed = JSON.parse(raw);
2735
+ if (Array.isArray(parsed?.installed)) return { installed: parsed.installed };
2736
+ return null;
2737
+ } catch {
2738
+ return null;
2739
+ }
2740
+ }
2741
+ function probeLocalBinIiiVersion(home) {
2742
+ const path = legacyLocalBinIii(home);
2743
+ if (!existsSync(path)) return null;
2744
+ return iiiBinVersion(path);
2745
+ }
2746
+ function safeDelete(path) {
2747
+ try {
2748
+ if (!existsSync(path)) return {
2749
+ ok: true,
2750
+ message: `not present (${path})`
2751
+ };
2752
+ if (statSync(path).isDirectory()) rmSync(path, {
2753
+ recursive: true,
2754
+ force: true
2755
+ });
2756
+ else unlinkSync(path);
2757
+ return {
2758
+ ok: true,
2759
+ message: `deleted ${path}`
2760
+ };
2761
+ } catch (err) {
2762
+ return {
2763
+ ok: false,
2764
+ message: `failed ${path}: ${err instanceof Error ? err.message : String(err)}`
2765
+ };
2766
+ }
2767
+ }
2768
+ async function runRemove() {
2769
+ p.intro("agentmemory remove");
2770
+ const force = args.includes("--force");
2771
+ const keepData = args.includes("--keep-data");
2772
+ const home = homedir();
2773
+ const connectManifest = loadConnectManifest(home);
2774
+ const plan = buildRemovePlan({
2775
+ home,
2776
+ pinnedVersion: IIPINNED_VERSION,
2777
+ localBinIiiVersion: probeLocalBinIiiVersion(home),
2778
+ connectManifest
2779
+ }, {
2780
+ force,
2781
+ keepData
2782
+ });
2783
+ if (plan.filter((it) => it.applicable).length === 0) {
2784
+ p.outro("Nothing to remove. agentmemory is already gone.");
2785
+ return;
2786
+ }
2787
+ p.note(formatPlan(plan), "destruction plan");
2788
+ if (!force) {
2789
+ const proceed = await p.confirm({
2790
+ message: "Proceed with these deletions?",
2791
+ initialValue: false
2792
+ });
2793
+ if (p.isCancel(proceed) || proceed !== true) {
2794
+ p.cancel("Cancelled. Nothing was deleted.");
2795
+ return;
2796
+ }
2797
+ const sure = await p.confirm({
2798
+ message: "This is irreversible. Continue?",
2799
+ initialValue: false
2800
+ });
2801
+ if (p.isCancel(sure) || sure !== true) {
2802
+ p.cancel("Cancelled. Nothing was deleted.");
2803
+ return;
2804
+ }
2805
+ }
2806
+ for (const item of plan) {
2807
+ if (!item.applicable) continue;
2808
+ if (item.alwaysAsk) {
2809
+ const ok = await p.confirm({
2810
+ message: `${item.description} — really delete${item.path ? ` ${item.path}` : ""}?`,
2811
+ initialValue: false
2812
+ });
2813
+ if (p.isCancel(ok) || ok !== true) {
2814
+ p.log.info(`skipped: ${item.id}`);
2815
+ continue;
2816
+ }
2817
+ }
2818
+ if (item.id === "stop-engine") {
2819
+ try {
2820
+ const portPids = findEnginePidsByPort(getRestPort());
2821
+ const pidfilePid = readEnginePidfile();
2822
+ const cands = /* @__PURE__ */ new Set();
2823
+ if (pidfilePid) cands.add(pidfilePid);
2824
+ for (const pid of portPids) cands.add(pid);
2825
+ for (const pid of cands) await signalAndWait(pid, "SIGTERM", 3e3);
2826
+ clearEnginePidfile();
2827
+ clearEngineState();
2828
+ p.log.success(cands.size > 0 ? `stopped engine (${cands.size} pid${cands.size === 1 ? "" : "s"})` : "no engine running");
2829
+ } catch (err) {
2830
+ p.log.warn(`engine stop best-effort: ${err instanceof Error ? err.message : String(err)}`);
2831
+ }
2832
+ continue;
2833
+ }
2834
+ if (!item.path) continue;
2835
+ const r = safeDelete(item.path);
2836
+ if (r.ok) p.log.success(r.message);
2837
+ else p.log.error(r.message);
2838
+ }
2839
+ p.outro("Done. agentmemory cleanly removed. The npm package itself: npm uninstall -g @agentmemory/agentmemory");
2840
+ }
2841
+ ({
2842
+ init: runInit,
2843
+ connect: runConnectCmd,
2844
+ status: runStatus,
2845
+ doctor: runDoctor,
2846
+ demo: runDemo,
2847
+ upgrade: runUpgrade,
2848
+ stop: runStop,
2849
+ remove: runRemove,
2850
+ mcp: runMcp,
2851
+ "import-jsonl": runImportJsonl
2852
+ }[args[0] ?? ""] ?? main)().catch((err) => {
2853
+ p.log.error(err instanceof Error ? err.message : String(err));
2854
+ process.exit(1);
2855
+ });
2856
+ //#endregion
2857
+ export { discoverViewerPort };
2858
+
2859
+ //# sourceMappingURL=cli.mjs.map