@agentmemory/agentmemory 0.9.14 → 0.9.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs CHANGED
@@ -1,12 +1,31 @@
1
1
  #!/usr/bin/env node
2
+ import { createRequire } from "node:module";
2
3
  import { execFileSync, spawn, spawnSync } from "node:child_process";
3
- import { existsSync, mkdirSync, readFileSync, readdirSync, readlinkSync, statSync, unlinkSync, writeFileSync } from "node:fs";
4
+ import { closeSync, constants, copyFileSync, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, readdirSync, readlinkSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync, writeSync } from "node:fs";
4
5
  import { delimiter, dirname, join } from "node:path";
5
6
  import { fileURLToPath } from "node:url";
6
7
  import { homedir, platform } from "node:os";
7
8
  import * as p from "@clack/prompts";
8
9
  import { createHash } from "node:crypto";
10
+ import { copyFile, mkdir } from "node:fs/promises";
9
11
 
12
+ //#region \0rolldown/runtime.js
13
+ var __defProp = Object.defineProperty;
14
+ var __exportAll = (all, no_symbols) => {
15
+ let target = {};
16
+ for (var name in all) {
17
+ __defProp(target, name, {
18
+ get: all[name],
19
+ enumerable: true
20
+ });
21
+ }
22
+ if (!no_symbols) {
23
+ __defProp(target, Symbol.toStringTag, { value: "Module" });
24
+ }
25
+ return target;
26
+ };
27
+
28
+ //#endregion
10
29
  //#region src/state/schema.ts
11
30
  const KV = {
12
31
  sessions: "mem:sessions",
@@ -75,12 +94,1355 @@ function jaccardSimilarity(a, b) {
75
94
  return intersection / (setA.size + setB.size - intersection);
76
95
  }
77
96
 
97
+ //#endregion
98
+ //#region src/cli/doctor-diagnostics.ts
99
+ /** Common placeholder values shipped in .env.example. */
100
+ const PLACEHOLDER_VALUES = new Set([
101
+ "",
102
+ "your-key-here",
103
+ "sk-ant-...",
104
+ "sk-...",
105
+ "changeme",
106
+ "todo",
107
+ "xxx"
108
+ ]);
109
+ const PROVIDER_KEY_NAMES = [
110
+ "ANTHROPIC_API_KEY",
111
+ "OPENAI_API_KEY",
112
+ "GEMINI_API_KEY",
113
+ "GOOGLE_API_KEY",
114
+ "OPENROUTER_API_KEY",
115
+ "MINIMAX_API_KEY"
116
+ ];
117
+ function parseEnvFile(content) {
118
+ const out = {};
119
+ for (const rawLine of content.split(/\r?\n/)) {
120
+ const line = rawLine.trim();
121
+ if (!line || line.startsWith("#")) continue;
122
+ const eq = line.indexOf("=");
123
+ if (eq < 0) continue;
124
+ const key = line.slice(0, eq).trim();
125
+ let value = line.slice(eq + 1).trim();
126
+ if (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1);
127
+ out[key] = value;
128
+ }
129
+ return out;
130
+ }
131
+ /** Returns the list of provider keys that look real (non-placeholder). */
132
+ function realProviderKeys(env) {
133
+ return PROVIDER_KEY_NAMES.filter((k) => {
134
+ const v = (env[k] ?? "").trim();
135
+ if (!v) return false;
136
+ if (PLACEHOLDER_VALUES.has(v.toLowerCase())) return false;
137
+ if (/^x+$/i.test(v.replace(/[-_]/g, ""))) return false;
138
+ return true;
139
+ });
140
+ }
141
+ /** Returns the list of provider key NAMES that exist but are placeholders. */
142
+ function placeholderProviderKeys(env) {
143
+ return PROVIDER_KEY_NAMES.filter((k) => {
144
+ const v = (env[k] ?? "").trim();
145
+ if (!v) return false;
146
+ if (PLACEHOLDER_VALUES.has(v.toLowerCase())) return true;
147
+ if (/^x+$/i.test(v.replace(/[-_]/g, ""))) return true;
148
+ return false;
149
+ });
150
+ }
151
+ function buildDiagnostics(effects) {
152
+ return [
153
+ {
154
+ id: "env-missing",
155
+ message: "~/.agentmemory/.env is missing.",
156
+ fixPreview: "Copy .env.example into ~/.agentmemory/.env (your keys file).",
157
+ 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.",
158
+ check: async () => ({
159
+ ok: effects.envFileExists(),
160
+ detail: effects.envFileExists() ? void 0 : "no env file"
161
+ }),
162
+ fix: () => effects.runInit()
163
+ },
164
+ {
165
+ id: "no-llm-provider-key",
166
+ message: "No LLM provider API key found in ~/.agentmemory/.env.",
167
+ fixPreview: "Open ~/.agentmemory/.env in $EDITOR and paste your key, then re-check.",
168
+ 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.",
169
+ check: async () => {
170
+ if (!effects.envFileExists()) return {
171
+ ok: false,
172
+ detail: "env file missing (run env-missing fix first)"
173
+ };
174
+ const real = realProviderKeys(effects.readEnvFile());
175
+ return {
176
+ ok: real.length > 0,
177
+ detail: real.length > 0 ? `found: ${real.join(", ")}` : "no provider key set"
178
+ };
179
+ },
180
+ fix: (ctx) => effects.openEditor(ctx.envPath)
181
+ },
182
+ {
183
+ id: "engine-version-mismatch",
184
+ message: "iii binary on PATH doesn't match the version agentmemory pins to.",
185
+ fixPreview: "Re-run the iii installer for the pinned version and restart the engine.",
186
+ 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.",
187
+ check: async (ctx) => {
188
+ const bin = effects.findIiiBinary();
189
+ if (!bin) return {
190
+ ok: false,
191
+ detail: "iii not on PATH"
192
+ };
193
+ const v = effects.iiiBinaryVersion(bin);
194
+ if (!v) return {
195
+ ok: false,
196
+ detail: "iii on PATH but --version failed"
197
+ };
198
+ return {
199
+ ok: v === ctx.pinnedVersion,
200
+ detail: `${v} (pinned ${ctx.pinnedVersion})`
201
+ };
202
+ },
203
+ fix: async () => {
204
+ const r = await effects.runIiiInstaller();
205
+ if (!r.ok) return r;
206
+ await effects.runStop();
207
+ return effects.runStart();
208
+ }
209
+ },
210
+ {
211
+ id: "viewer-unreachable",
212
+ message: "Viewer port not reachable.",
213
+ fixPreview: "Stop the engine, restart it, and retry the viewer probe.",
214
+ 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.",
215
+ check: async () => ({
216
+ ok: await effects.viewerReachable(),
217
+ detail: void 0
218
+ }),
219
+ fix: async () => {
220
+ const stopped = await effects.runStop();
221
+ if (!stopped.ok) return stopped;
222
+ return effects.runStart();
223
+ }
224
+ },
225
+ {
226
+ id: "stale-pidfile",
227
+ message: "Stale pidfile: pid recorded but the process is gone.",
228
+ fixPreview: "Clear ~/.agentmemory/iii.pid + engine-state.json, then restart.",
229
+ 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.",
230
+ check: async () => {
231
+ if (!effects.pidfileExists()) return {
232
+ ok: true,
233
+ detail: "no pidfile"
234
+ };
235
+ const alive = effects.pidfilePidIsAlive();
236
+ if (alive === null) return {
237
+ ok: true,
238
+ detail: "pidfile unreadable"
239
+ };
240
+ return {
241
+ ok: alive,
242
+ detail: alive ? "pid is alive" : "pid is gone"
243
+ };
244
+ },
245
+ fix: async () => {
246
+ effects.clearEnginePidAndState();
247
+ return effects.runStart();
248
+ }
249
+ },
250
+ {
251
+ id: "env-placeholder-keys",
252
+ message: "~/.agentmemory/.env contains placeholder/empty API keys.",
253
+ fixPreview: "Open ~/.agentmemory/.env in $EDITOR to paste real values.",
254
+ 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.",
255
+ check: async () => {
256
+ if (!effects.envFileExists()) return {
257
+ ok: true,
258
+ detail: "env file missing (handled by env-missing)"
259
+ };
260
+ const placeholders = placeholderProviderKeys(effects.readEnvFile());
261
+ return {
262
+ ok: placeholders.length === 0,
263
+ detail: placeholders.length === 0 ? void 0 : `placeholder: ${placeholders.join(", ")}`
264
+ };
265
+ },
266
+ fix: (ctx) => effects.openEditor(ctx.envPath)
267
+ },
268
+ {
269
+ id: "iii-on-path-not-local-bin",
270
+ message: "iii is on PATH but not in ~/.local/bin/iii (where we install).",
271
+ fixPreview: "Suggest re-installing the pinned version via the installer — won't touch your PATH.",
272
+ moreInfo: "agentmemory's installer writes to ~/.local/bin/iii. When a user-managed iii lives somewhere else (homebrew, cargo, $XDG_BIN) we don't auto-overwrite it. If you want our pinned build, run the installer; otherwise this is informational.",
273
+ manualOnly: true,
274
+ check: async () => {
275
+ const bin = effects.findIiiBinary();
276
+ if (!bin) return {
277
+ ok: true,
278
+ detail: "iii not on PATH (handled elsewhere)"
279
+ };
280
+ const localBin = effects.localBinIiiPath();
281
+ return {
282
+ ok: bin === localBin,
283
+ detail: bin === localBin ? void 0 : `iii at: ${bin}`
284
+ };
285
+ },
286
+ fix: async () => effects.runIiiInstaller().then((r) => ({
287
+ ok: r.ok,
288
+ message: r.message ?? "Installer wrote to ~/.local/bin/iii. Your PATH wasn't modified — adjust it yourself if needed."
289
+ }))
290
+ }
291
+ ];
292
+ }
293
+ /**
294
+ * Dry-run output: each failing check's fix preview, prefixed by the diagnostic
295
+ * message. Pure function so we can snapshot-test the format.
296
+ */
297
+ function dryRunPlan(ctx, results) {
298
+ const lines = [];
299
+ let n = 0;
300
+ for (const { diagnostic, status } of results) {
301
+ if (status.ok) continue;
302
+ n++;
303
+ lines.push(`${n}. [${diagnostic.id}] ${diagnostic.message}`);
304
+ lines.push(` would fix: ${diagnostic.fixPreview}`);
305
+ if (status.detail) lines.push(` detail: ${status.detail}`);
306
+ }
307
+ if (lines.length === 0) lines.push(`All checks passing for ${ctx.baseUrl} — no fixes to run.`);
308
+ return lines;
309
+ }
310
+
311
+ //#endregion
312
+ //#region src/cli/remove-plan.ts
313
+ function pidfilePath(home) {
314
+ return join(home, ".agentmemory", "iii.pid");
315
+ }
316
+ function enginePath(home) {
317
+ return join(home, ".agentmemory", "engine-state.json");
318
+ }
319
+ function envPath(home) {
320
+ return join(home, ".agentmemory", ".env");
321
+ }
322
+ function preferencesPath(home) {
323
+ return join(home, ".agentmemory", "preferences.json");
324
+ }
325
+ function backupsDir$1(home) {
326
+ return join(home, ".agentmemory", "backups");
327
+ }
328
+ function dataDir(home) {
329
+ return join(home, ".agentmemory", "data");
330
+ }
331
+ function localBinIii(home) {
332
+ return join(home, ".local", "bin", "iii");
333
+ }
334
+ function safeSize(path) {
335
+ try {
336
+ return statSync(path).size;
337
+ } catch {
338
+ return -1;
339
+ }
340
+ }
341
+ function pathExists(path) {
342
+ try {
343
+ return existsSync(path);
344
+ } catch {
345
+ return false;
346
+ }
347
+ }
348
+ /**
349
+ * Build the destruction plan for `agentmemory remove`.
350
+ *
351
+ * Plan items are returned regardless of whether `applicable` is true — the
352
+ * caller can decide whether to skip-and-log or hide entirely. This keeps
353
+ * the structure stable for tests.
354
+ */
355
+ function buildRemovePlan(ctx, options) {
356
+ const { home, pinnedVersion, localBinIiiVersion, connectManifest } = ctx;
357
+ const plan = [];
358
+ plan.push({
359
+ id: "stop-engine",
360
+ description: "Stop running iii-engine (if any) cleanly",
361
+ path: null,
362
+ alwaysAsk: false,
363
+ applicable: pathExists(pidfilePath(home)) || pathExists(enginePath(home)),
364
+ sizeBytes: -1
365
+ });
366
+ plan.push({
367
+ id: "pidfile",
368
+ description: "Delete pidfile",
369
+ path: pidfilePath(home),
370
+ alwaysAsk: false,
371
+ applicable: pathExists(pidfilePath(home)),
372
+ sizeBytes: safeSize(pidfilePath(home))
373
+ });
374
+ plan.push({
375
+ id: "engine-state",
376
+ description: "Delete engine-state.json",
377
+ path: enginePath(home),
378
+ alwaysAsk: false,
379
+ applicable: pathExists(enginePath(home)),
380
+ sizeBytes: safeSize(enginePath(home))
381
+ });
382
+ plan.push({
383
+ id: "env",
384
+ description: "Delete .env (your API keys) — will ask separately",
385
+ path: envPath(home),
386
+ alwaysAsk: true,
387
+ applicable: !options.keepData && pathExists(envPath(home)),
388
+ sizeBytes: safeSize(envPath(home))
389
+ });
390
+ plan.push({
391
+ id: "preferences",
392
+ description: "Delete preferences.json",
393
+ path: preferencesPath(home),
394
+ alwaysAsk: false,
395
+ applicable: !options.keepData && pathExists(preferencesPath(home)),
396
+ sizeBytes: safeSize(preferencesPath(home))
397
+ });
398
+ plan.push({
399
+ id: "backups",
400
+ description: "Delete backups/ directory (connect manifest + backups)",
401
+ path: backupsDir$1(home),
402
+ alwaysAsk: false,
403
+ applicable: !options.keepData && pathExists(backupsDir$1(home)),
404
+ sizeBytes: -1
405
+ });
406
+ if (connectManifest?.installed?.length) for (const entry of connectManifest.installed) plan.push({
407
+ id: `connect:${entry.target}`,
408
+ description: `Remove agent connection (${entry.agent ?? "unknown"})`,
409
+ path: entry.target,
410
+ alwaysAsk: false,
411
+ applicable: pathExists(entry.target),
412
+ sizeBytes: safeSize(entry.target)
413
+ });
414
+ const localIii = localBinIii(home);
415
+ if (pathExists(localIii)) {
416
+ const matches = localBinIiiVersion === pinnedVersion;
417
+ plan.push({
418
+ id: "local-bin-iii",
419
+ description: matches ? `Delete ~/.local/bin/iii (matches pinned v${pinnedVersion})` : `Delete ~/.local/bin/iii (version ${localBinIiiVersion ?? "unknown"} != pinned v${pinnedVersion}) — will ask`,
420
+ path: localIii,
421
+ alwaysAsk: !matches,
422
+ applicable: true,
423
+ sizeBytes: safeSize(localIii)
424
+ });
425
+ }
426
+ plan.push({
427
+ id: "data-dir",
428
+ description: "Delete memory data directory (~/.agentmemory/data/) — will ask separately",
429
+ path: dataDir(home),
430
+ alwaysAsk: true,
431
+ applicable: !options.keepData && pathExists(dataDir(home)),
432
+ sizeBytes: -1
433
+ });
434
+ return plan;
435
+ }
436
+ /** Format a plan for the user — one line per item. */
437
+ function formatPlan(plan) {
438
+ return plan.filter((p) => p.applicable).map((p, i) => {
439
+ const tag = p.alwaysAsk ? " [asks]" : "";
440
+ const sz = p.sizeBytes > 0 ? ` (${humanBytes(p.sizeBytes)})` : "";
441
+ return ` ${i + 1}. ${p.description}${tag}${sz}${p.path ? `\n ${p.path}` : ""}`;
442
+ }).join("\n");
443
+ }
444
+ function humanBytes(n) {
445
+ if (n < 1024) return `${n} B`;
446
+ if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;
447
+ return `${(n / (1024 * 1024)).toFixed(1)} MB`;
448
+ }
449
+
450
+ //#endregion
451
+ //#region src/cli/splash.ts
452
+ const IS_COLOR_TTY = !!process.stdout.isTTY && !process.env["NO_COLOR"];
453
+ function accent(s) {
454
+ return IS_COLOR_TTY ? `\x1b[38;5;208m${s}\x1b[0m` : s;
455
+ }
456
+ function dim(s) {
457
+ return IS_COLOR_TTY ? `\x1b[2m${s}\x1b[22m` : s;
458
+ }
459
+ function bold(s) {
460
+ return IS_COLOR_TTY ? `\x1b[1m${s}\x1b[22m` : s;
461
+ }
462
+ function getTerminalWidth() {
463
+ const w = process.stdout.columns;
464
+ return typeof w === "number" && w > 0 ? w : 80;
465
+ }
466
+ const TAGLINE = "Persistent memory for AI coding agents";
467
+ function fullBanner(version) {
468
+ const lines = ["", ...[
469
+ " _ ",
470
+ " __ _ __ _ ___ _ __ | |_ _ __ ___ ___ _ __ ___ ___ _ __ _ _ ",
471
+ " / _` |/ _` |/ _ \\ '_ \\| __| '_ ` _ \\ / _ \\ '_ ` _ \\ / _ \\| '__| | | |",
472
+ "| (_| | (_| | __/ | | | |_| | | | | | __/ | | | | | (_) | | | |_| |",
473
+ " \\__,_|\\__, |\\___|_| |_|\\__|_| |_| |_|\\___|_| |_| |_|\\___/|_| \\__, |",
474
+ " |___/ |___/ "
475
+ ].map((line) => " " + accent(line))];
476
+ lines.push("");
477
+ lines.push(" " + bold(TAGLINE) + " " + dim(`v${version}`));
478
+ lines.push("");
479
+ return lines.join("\n");
480
+ }
481
+ function compactBanner(version) {
482
+ return [
483
+ "",
484
+ " " + bold(accent("agentmemory")),
485
+ " " + dim(`v${version} · ${TAGLINE}`),
486
+ ""
487
+ ].join("\n");
488
+ }
489
+ function minimalBanner(version) {
490
+ return `${accent("agentmemory")} ${dim(`v${version}`)}`;
491
+ }
492
+ function renderSplash(version) {
493
+ const width = getTerminalWidth();
494
+ let out;
495
+ if (width >= 120) out = fullBanner(version);
496
+ else if (width >= 80) out = compactBanner(version);
497
+ else out = minimalBanner(version);
498
+ process.stdout.write(out + "\n");
499
+ }
500
+
501
+ //#endregion
502
+ //#region src/cli/preferences.ts
503
+ const DEFAULTS = {
504
+ schemaVersion: 1,
505
+ lastAgent: null,
506
+ lastAgents: [],
507
+ lastProvider: null,
508
+ skipSplash: false,
509
+ skipNpxHint: false,
510
+ skipGlobalInstall: false,
511
+ skipConsoleInstall: false,
512
+ firstRunAt: null
513
+ };
514
+ function prefsDir() {
515
+ return join(homedir(), ".agentmemory");
516
+ }
517
+ function prefsPath() {
518
+ return join(prefsDir(), "preferences.json");
519
+ }
520
+ function readPrefs() {
521
+ try {
522
+ if (!existsSync(prefsPath())) return { ...DEFAULTS };
523
+ const raw = readFileSync(prefsPath(), "utf-8");
524
+ const parsed = JSON.parse(raw);
525
+ return {
526
+ ...DEFAULTS,
527
+ ...parsed,
528
+ schemaVersion: 1
529
+ };
530
+ } catch {
531
+ return { ...DEFAULTS };
532
+ }
533
+ }
534
+ function writePrefs(p) {
535
+ try {
536
+ mkdirSync(prefsDir(), { recursive: true });
537
+ const next = {
538
+ ...readPrefs(),
539
+ ...p,
540
+ schemaVersion: 1
541
+ };
542
+ const target = prefsPath();
543
+ const tmp = target + ".tmp";
544
+ const fd = openSync(tmp, "w", 384);
545
+ try {
546
+ writeSync(fd, JSON.stringify(next, null, 2) + "\n");
547
+ try {
548
+ fsyncSync(fd);
549
+ } catch {}
550
+ } finally {
551
+ closeSync(fd);
552
+ }
553
+ renameSync(tmp, target);
554
+ } catch {}
555
+ }
556
+ function resetPrefs() {
557
+ try {
558
+ unlinkSync(prefsPath());
559
+ } catch {}
560
+ }
561
+ function isFirstRun() {
562
+ if (!existsSync(prefsPath())) return true;
563
+ return readPrefs().firstRunAt === null;
564
+ }
565
+
566
+ //#endregion
567
+ //#region src/cli/connect/util.ts
568
+ const AGENTMEMORY_MCP_BLOCK = {
569
+ command: "npx",
570
+ args: ["-y", "@agentmemory/mcp"],
571
+ env: { AGENTMEMORY_URL: "http://localhost:3111" }
572
+ };
573
+ function backupsDir() {
574
+ return join(homedir(), ".agentmemory", "backups");
575
+ }
576
+ function ensureBackupsDir() {
577
+ const dir = backupsDir();
578
+ mkdirSync(dir, { recursive: true });
579
+ return dir;
580
+ }
581
+ function timestampSlug() {
582
+ return (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
583
+ }
584
+ function backupFile(sourcePath, agent, ext = "json") {
585
+ ensureBackupsDir();
586
+ const stamp = timestampSlug();
587
+ const target = join(backupsDir(), `${agent}-${stamp}.${ext}`);
588
+ copyFileSync(sourcePath, target);
589
+ return target;
590
+ }
591
+ function readJsonSafe(path) {
592
+ if (!existsSync(path)) return null;
593
+ try {
594
+ return JSON.parse(readFileSync(path, "utf-8"));
595
+ } catch {
596
+ return null;
597
+ }
598
+ }
599
+ function writeJsonAtomic(path, value) {
600
+ mkdirSync(dirname(path), { recursive: true });
601
+ const tmp = `${path}.tmp-${process.pid}-${Date.now()}`;
602
+ writeFileSync(tmp, `${JSON.stringify(value, null, 2)}\n`, "utf-8");
603
+ renameSync(tmp, path);
604
+ }
605
+ function logInstalled(label, target) {
606
+ p.log.success(`${label} → wired into ${target}`);
607
+ }
608
+ function logAlreadyWired(label, target) {
609
+ p.log.info(`${label} already wired in ${target} (use --force to re-install)`);
610
+ }
611
+ function logBackup(target) {
612
+ p.log.info(`Backup: ${target}`);
613
+ }
614
+
615
+ //#endregion
616
+ //#region src/cli/connect/claude-code.ts
617
+ const CLAUDE_DIR = join(homedir(), ".claude");
618
+ const CLAUDE_JSON = join(homedir(), ".claude.json");
619
+ function entryMatches$1(entry) {
620
+ if (!entry || typeof entry !== "object") return false;
621
+ const e = entry;
622
+ if (e["command"] !== "npx") return false;
623
+ return (Array.isArray(e["args"]) ? e["args"] : []).includes("@agentmemory/mcp");
624
+ }
625
+ const adapter$7 = {
626
+ name: "claude-code",
627
+ displayName: "Claude Code",
628
+ docs: "https://github.com/rohitg00/agentmemory#claude-code-one-block-paste-it",
629
+ protocolNote: "→ Using MCP. Hooks are also available — see docs/claude-code.md.",
630
+ detect() {
631
+ return existsSync(CLAUDE_DIR);
632
+ },
633
+ async install(opts) {
634
+ const existing = readJsonSafe(CLAUDE_JSON);
635
+ const next = existing ? { ...existing } : {};
636
+ const servers = { ...next.mcpServers ?? {} };
637
+ const alreadyHas = entryMatches$1(servers["agentmemory"]);
638
+ if (alreadyHas && !opts.force) {
639
+ logAlreadyWired("Claude Code", CLAUDE_JSON);
640
+ return {
641
+ kind: "already-wired",
642
+ mutatedPath: CLAUDE_JSON
643
+ };
644
+ }
645
+ if (opts.dryRun) {
646
+ p.log.info(`[dry-run] Would ${alreadyHas ? "overwrite" : "add"} mcpServers.agentmemory in ${CLAUDE_JSON}`);
647
+ return {
648
+ kind: "installed",
649
+ mutatedPath: CLAUDE_JSON
650
+ };
651
+ }
652
+ let backupPath;
653
+ if (existsSync(CLAUDE_JSON)) {
654
+ backupPath = backupFile(CLAUDE_JSON, "claude-code");
655
+ logBackup(backupPath);
656
+ } else {
657
+ mkdirSync(CLAUDE_DIR, { recursive: true });
658
+ writeFileSync(CLAUDE_JSON, "{}\n", "utf-8");
659
+ }
660
+ servers["agentmemory"] = AGENTMEMORY_MCP_BLOCK;
661
+ next.mcpServers = servers;
662
+ writeJsonAtomic(CLAUDE_JSON, next);
663
+ if (!entryMatches$1(readJsonSafe(CLAUDE_JSON)?.mcpServers?.["agentmemory"])) {
664
+ p.log.error(`Verification failed: ${CLAUDE_JSON} did not contain mcpServers.agentmemory after write.`);
665
+ return {
666
+ kind: "skipped",
667
+ reason: "verification-failed"
668
+ };
669
+ }
670
+ logInstalled("Claude Code", CLAUDE_JSON);
671
+ p.log.info("Restart Claude Code (or run `/mcp` inside a session) to pick up the new server.");
672
+ return {
673
+ kind: "installed",
674
+ mutatedPath: CLAUDE_JSON,
675
+ backupPath
676
+ };
677
+ }
678
+ };
679
+
680
+ //#endregion
681
+ //#region src/cli/connect/codex.ts
682
+ const CODEX_DIR = join(homedir(), ".codex");
683
+ const CODEX_TOML = join(CODEX_DIR, "config.toml");
684
+ const TOML_BLOCK = `[mcp_servers.agentmemory]
685
+ command = "npx"
686
+ args = ["-y", "@agentmemory/mcp"]
687
+
688
+ [mcp_servers.agentmemory.env]
689
+ AGENTMEMORY_URL = "http://localhost:3111"
690
+ `;
691
+ const SECTION_HEADER = "[mcp_servers.agentmemory]";
692
+ function isWiredText(toml) {
693
+ return toml.includes(SECTION_HEADER);
694
+ }
695
+ function stripExistingBlock(toml) {
696
+ const lines = toml.split(/\r?\n/);
697
+ const out = [];
698
+ let skipping = false;
699
+ for (const line of lines) {
700
+ const trimmed = line.trim();
701
+ if (trimmed === SECTION_HEADER || trimmed === "[mcp_servers.agentmemory.env]") {
702
+ skipping = true;
703
+ continue;
704
+ }
705
+ if (skipping && trimmed.startsWith("[") && trimmed !== "[mcp_servers.agentmemory.env]") skipping = false;
706
+ if (!skipping) out.push(line);
707
+ }
708
+ return out.join("\n").replace(/\n{3,}$/, "\n\n").trimEnd() + "\n";
709
+ }
710
+ const adapter$6 = {
711
+ name: "codex",
712
+ displayName: "Codex CLI",
713
+ docs: "https://github.com/rohitg00/agentmemory#codex-cli-codex-plugin-platform",
714
+ protocolNote: "→ Using MCP. Hooks are also available — see docs/codex.md.",
715
+ detect() {
716
+ return existsSync(CODEX_DIR);
717
+ },
718
+ async install(opts) {
719
+ const exists = existsSync(CODEX_TOML);
720
+ const current = exists ? readFileSync(CODEX_TOML, "utf-8") : "";
721
+ const wired = isWiredText(current);
722
+ if (wired && !opts.force) {
723
+ logAlreadyWired("Codex CLI", CODEX_TOML);
724
+ return {
725
+ kind: "already-wired",
726
+ mutatedPath: CODEX_TOML
727
+ };
728
+ }
729
+ if (opts.dryRun) {
730
+ p.log.info(`[dry-run] Would ${wired ? "rewrite" : "append"} [mcp_servers.agentmemory] in ${CODEX_TOML}`);
731
+ return {
732
+ kind: "installed",
733
+ mutatedPath: CODEX_TOML
734
+ };
735
+ }
736
+ let backupPath;
737
+ if (exists) {
738
+ backupPath = backupFile(CODEX_TOML, "codex", "toml");
739
+ logBackup(backupPath);
740
+ } else mkdirSync(dirname(CODEX_TOML), { recursive: true });
741
+ const cleaned = wired ? stripExistingBlock(current) : current;
742
+ writeFileSync(CODEX_TOML, `${cleaned}${cleaned.length === 0 || cleaned.endsWith("\n") ? "" : "\n"}${cleaned.length > 0 ? "\n" : ""}${TOML_BLOCK}`, "utf-8");
743
+ if (!isWiredText(readFileSync(CODEX_TOML, "utf-8"))) {
744
+ p.log.error(`Verification failed: ${CODEX_TOML} did not contain ${SECTION_HEADER} after write.`);
745
+ return {
746
+ kind: "skipped",
747
+ reason: "verification-failed"
748
+ };
749
+ }
750
+ logInstalled("Codex CLI", CODEX_TOML);
751
+ p.log.info("Codex picks up MCP servers on next launch. For the deeper plugin install, run: codex plugin marketplace add rohitg00/agentmemory && codex plugin install agentmemory");
752
+ return {
753
+ kind: "installed",
754
+ mutatedPath: CODEX_TOML,
755
+ ...backupPath !== void 0 && { backupPath }
756
+ };
757
+ }
758
+ };
759
+
760
+ //#endregion
761
+ //#region src/cli/connect/json-mcp-adapter.ts
762
+ function entryMatches(entry) {
763
+ if (!entry || typeof entry !== "object") return false;
764
+ const e = entry;
765
+ if (e["command"] !== "npx") return false;
766
+ return (Array.isArray(e["args"]) ? e["args"] : []).includes("@agentmemory/mcp");
767
+ }
768
+ function createJsonMcpAdapter(config) {
769
+ return {
770
+ name: config.name,
771
+ displayName: config.displayName,
772
+ ...config.docs !== void 0 && { docs: config.docs },
773
+ ...config.protocolNote !== void 0 && { protocolNote: config.protocolNote },
774
+ detect() {
775
+ return existsSync(config.detectDir);
776
+ },
777
+ async install(opts) {
778
+ const existing = readJsonSafe(config.configPath);
779
+ const next = existing ? { ...existing } : {};
780
+ const servers = { ...next.mcpServers ?? {} };
781
+ const alreadyHas = entryMatches(servers["agentmemory"]);
782
+ if (alreadyHas && !opts.force) {
783
+ logAlreadyWired(config.displayName, config.configPath);
784
+ return {
785
+ kind: "already-wired",
786
+ mutatedPath: config.configPath
787
+ };
788
+ }
789
+ if (opts.dryRun) {
790
+ p.log.info(`[dry-run] Would ${alreadyHas ? "overwrite" : "add"} mcpServers.agentmemory in ${config.configPath}`);
791
+ return {
792
+ kind: "installed",
793
+ mutatedPath: config.configPath
794
+ };
795
+ }
796
+ let backupPath;
797
+ if (existsSync(config.configPath)) {
798
+ backupPath = backupFile(config.configPath, config.name);
799
+ logBackup(backupPath);
800
+ } else mkdirSync(dirname(config.configPath), { recursive: true });
801
+ servers["agentmemory"] = AGENTMEMORY_MCP_BLOCK;
802
+ next.mcpServers = servers;
803
+ writeJsonAtomic(config.configPath, next);
804
+ if (!entryMatches(readJsonSafe(config.configPath)?.mcpServers?.["agentmemory"])) {
805
+ p.log.error(`Verification failed: ${config.configPath} did not contain mcpServers.agentmemory after write.`);
806
+ return {
807
+ kind: "skipped",
808
+ reason: "verification-failed"
809
+ };
810
+ }
811
+ logInstalled(config.displayName, config.configPath);
812
+ return {
813
+ kind: "installed",
814
+ mutatedPath: config.configPath,
815
+ ...backupPath !== void 0 && { backupPath }
816
+ };
817
+ }
818
+ };
819
+ }
820
+
821
+ //#endregion
822
+ //#region src/cli/connect/cursor.ts
823
+ const adapter$5 = createJsonMcpAdapter({
824
+ name: "cursor",
825
+ displayName: "Cursor",
826
+ detectDir: join(homedir(), ".cursor"),
827
+ configPath: join(homedir(), ".cursor", "mcp.json"),
828
+ docs: "https://github.com/rohitg00/agentmemory#other-agents",
829
+ protocolNote: "→ Using MCP (the only protocol Cursor speaks). Memory bridge runs at :3111 underneath."
830
+ });
831
+
832
+ //#endregion
833
+ //#region src/cli/connect/gemini-cli.ts
834
+ const adapter$4 = createJsonMcpAdapter({
835
+ name: "gemini-cli",
836
+ displayName: "Gemini CLI",
837
+ detectDir: join(homedir(), ".gemini"),
838
+ configPath: join(homedir(), ".gemini", "settings.json"),
839
+ docs: "https://github.com/rohitg00/agentmemory#other-agents",
840
+ protocolNote: "→ Using MCP (the only protocol Gemini CLI speaks). Memory bridge runs at :3111 underneath."
841
+ });
842
+
843
+ //#endregion
844
+ //#region src/cli/connect/hermes.ts
845
+ const HERMES_DIR = join(homedir(), ".hermes");
846
+ const HERMES_CONFIG = join(HERMES_DIR, "config.yaml");
847
+ const DOCS$2 = "https://github.com/rohitg00/agentmemory/tree/main/integrations/hermes";
848
+ const adapter$3 = {
849
+ name: "hermes",
850
+ displayName: "Hermes Agent",
851
+ docs: DOCS$2,
852
+ protocolNote: "→ Using MCP. Hooks are also available — see docs/hermes.md.",
853
+ detect() {
854
+ return existsSync(HERMES_DIR);
855
+ },
856
+ async install(_opts) {
857
+ p.log.warn("Hermes uses YAML config. Automated merge isn't implemented yet — manual install required.");
858
+ p.note([
859
+ `Add to ${HERMES_CONFIG}:`,
860
+ "",
861
+ " mcp_servers:",
862
+ " agentmemory:",
863
+ " command: npx",
864
+ " args: [\"-y\", \"@agentmemory/mcp\"]",
865
+ "",
866
+ " memory:",
867
+ " provider: agentmemory",
868
+ "",
869
+ `Full guide: ${DOCS$2}`
870
+ ].join("\n"), "Hermes manual install");
871
+ return {
872
+ kind: "stub",
873
+ reason: "yaml-merge-not-implemented"
874
+ };
875
+ }
876
+ };
877
+
878
+ //#endregion
879
+ //#region src/cli/connect/openclaw.ts
880
+ const adapter$2 = createJsonMcpAdapter({
881
+ name: "openclaw",
882
+ displayName: "OpenClaw",
883
+ detectDir: join(homedir(), ".openclaw"),
884
+ configPath: join(homedir(), ".openclaw", "openclaw.json"),
885
+ docs: "https://github.com/rohitg00/agentmemory/tree/main/integrations/openclaw",
886
+ protocolNote: "→ Using MCP. Hooks are also available — see docs/openclaw.md."
887
+ });
888
+
889
+ //#endregion
890
+ //#region src/cli/connect/openhuman.ts
891
+ const OPENHUMAN_DIR = join(homedir(), ".openhuman");
892
+ const DOCS$1 = "https://github.com/tinyhumansai/openhuman";
893
+ const adapter$1 = {
894
+ name: "openhuman",
895
+ displayName: "OpenHuman",
896
+ docs: DOCS$1,
897
+ protocolNote: "→ Using native hooks (REST API at :3111). MCP not required.",
898
+ detect() {
899
+ return existsSync(OPENHUMAN_DIR);
900
+ },
901
+ async install(_opts) {
902
+ p.log.warn("OpenHuman integration is not yet automated. No `integrations/openhuman/` folder exists in the agentmemory repo today.");
903
+ p.note([
904
+ "OpenHuman is a Memory-trait host. The expected wiring is the REST",
905
+ "proxy at http://localhost:3111 plus an OpenHuman-side Memory trait",
906
+ "impl. Once integrations/openhuman/ lands in agentmemory we'll wire",
907
+ "this up automatically.",
908
+ "",
909
+ `Tracking: ${DOCS$1}`
910
+ ].join("\n"), "OpenHuman manual install");
911
+ return {
912
+ kind: "stub",
913
+ reason: "no-integration-folder-yet"
914
+ };
915
+ }
916
+ };
917
+
918
+ //#endregion
919
+ //#region src/cli/connect/pi.ts
920
+ const PI_DIR = join(homedir(), ".pi");
921
+ const PI_EXT_DIR = join(PI_DIR, "agent", "extensions", "agentmemory");
922
+ const DOCS = "https://github.com/rohitg00/agentmemory/tree/main/integrations/pi";
923
+ const adapter = {
924
+ name: "pi",
925
+ displayName: "pi",
926
+ docs: DOCS,
927
+ protocolNote: "→ Using native hooks (REST API at :3111). MCP not required.",
928
+ detect() {
929
+ return existsSync(PI_DIR);
930
+ },
931
+ async install(_opts) {
932
+ p.log.warn("pi uses a TypeScript extension file. Automated copy + register isn't implemented yet — manual install required.");
933
+ p.note([
934
+ "Run these from the agentmemory repo root:",
935
+ "",
936
+ ` mkdir -p ${PI_EXT_DIR}`,
937
+ ` cp integrations/pi/index.ts ${PI_EXT_DIR}/index.ts`,
938
+ ` cp integrations/pi/security.ts ${PI_EXT_DIR}/security.ts`,
939
+ "",
940
+ "Then add to ~/.pi/agent/settings.json:",
941
+ " { \"extensions\": [\"~/.pi/agent/extensions/agentmemory\"] }",
942
+ "",
943
+ `Full guide: ${DOCS}`
944
+ ].join("\n"), "pi manual install");
945
+ return {
946
+ kind: "stub",
947
+ reason: "ts-extension-copy-not-implemented"
948
+ };
949
+ }
950
+ };
951
+
952
+ //#endregion
953
+ //#region src/cli/connect/index.ts
954
+ var connect_exports = /* @__PURE__ */ __exportAll({
955
+ ADAPTERS: () => ADAPTERS,
956
+ knownAgents: () => knownAgents,
957
+ resolveAdapter: () => resolveAdapter,
958
+ runAdapter: () => runAdapter,
959
+ runConnect: () => runConnect
960
+ });
961
+ const ADAPTERS = [
962
+ adapter$7,
963
+ adapter$6,
964
+ adapter$5,
965
+ adapter$4,
966
+ adapter$2,
967
+ adapter$3,
968
+ adapter,
969
+ adapter$1
970
+ ];
971
+ function resolveAdapter(name) {
972
+ const lower = name.toLowerCase();
973
+ return ADAPTERS.find((a) => a.name === lower) ?? null;
974
+ }
975
+ function knownAgents() {
976
+ return ADAPTERS.map((a) => a.name);
977
+ }
978
+ function parseFlags(args) {
979
+ const positional = [];
980
+ let dryRun = false;
981
+ let force = false;
982
+ let all = false;
983
+ for (const a of args) if (a === "--dry-run") dryRun = true;
984
+ else if (a === "--force") force = true;
985
+ else if (a === "--all") all = true;
986
+ else if (!a.startsWith("-")) positional.push(a);
987
+ return {
988
+ dryRun,
989
+ force,
990
+ all,
991
+ positional
992
+ };
993
+ }
994
+ async function runAdapter(adapter, opts) {
995
+ if (!adapter.detect()) {
996
+ p.log.warn(`${adapter.displayName}: not detected on this machine (skipping).${adapter.docs ? ` Docs: ${adapter.docs}` : ""}`);
997
+ return {
998
+ kind: "skipped",
999
+ reason: "not-detected"
1000
+ };
1001
+ }
1002
+ p.log.step(`Wiring ${adapter.displayName}…`);
1003
+ if (adapter.protocolNote) p.log.message(adapter.protocolNote);
1004
+ try {
1005
+ return await adapter.install(opts);
1006
+ } catch (err) {
1007
+ p.log.error(`${adapter.displayName}: ${err instanceof Error ? err.message : String(err)}`);
1008
+ return {
1009
+ kind: "skipped",
1010
+ reason: "exception"
1011
+ };
1012
+ }
1013
+ }
1014
+ async function runConnect(args) {
1015
+ if (platform() === "win32") {
1016
+ p.intro("agentmemory connect");
1017
+ p.log.warn("Windows: automated `connect` is not supported yet. See https://github.com/rohitg00/agentmemory#other-agents for manual install steps.");
1018
+ p.outro("Windows: manual install required — see docs");
1019
+ return;
1020
+ }
1021
+ const { dryRun, force, all, positional } = parseFlags(args);
1022
+ const opts = {
1023
+ dryRun,
1024
+ force
1025
+ };
1026
+ p.intro("agentmemory connect");
1027
+ if (positional.length === 0 && !all) {
1028
+ const detected = ADAPTERS.filter((a) => a.detect());
1029
+ if (detected.length === 0) {
1030
+ p.log.error("No supported agents detected on this machine.");
1031
+ p.outro(`Supported: ${knownAgents().join(", ")}`);
1032
+ process.exit(1);
1033
+ }
1034
+ const picked = await p.multiselect({
1035
+ message: "Wire agentmemory into which agents?",
1036
+ options: detected.map((a) => ({
1037
+ value: a.name,
1038
+ label: a.displayName
1039
+ })),
1040
+ required: true
1041
+ });
1042
+ if (p.isCancel(picked)) {
1043
+ p.cancel("Cancelled.");
1044
+ return;
1045
+ }
1046
+ const results = [];
1047
+ for (const name of picked) {
1048
+ const adapter = resolveAdapter(name);
1049
+ if (!adapter) continue;
1050
+ results.push({
1051
+ name,
1052
+ result: await runAdapter(adapter, opts)
1053
+ });
1054
+ }
1055
+ summarize(results);
1056
+ return;
1057
+ }
1058
+ if (all) {
1059
+ const detected = ADAPTERS.filter((a) => a.detect());
1060
+ if (detected.length === 0) {
1061
+ p.log.error("No supported agents detected on this machine.");
1062
+ process.exit(1);
1063
+ }
1064
+ const results = [];
1065
+ for (const adapter of detected) results.push({
1066
+ name: adapter.name,
1067
+ result: await runAdapter(adapter, opts)
1068
+ });
1069
+ summarize(results);
1070
+ return;
1071
+ }
1072
+ const agentName = positional[0];
1073
+ const adapter = resolveAdapter(agentName);
1074
+ if (!adapter) {
1075
+ p.log.error(`Unknown agent: ${agentName}`);
1076
+ p.outro(`Supported: ${knownAgents().join(", ")}`);
1077
+ process.exit(1);
1078
+ }
1079
+ const result = await runAdapter(adapter, opts);
1080
+ summarize([{
1081
+ name: agentName,
1082
+ result
1083
+ }]);
1084
+ if (result.kind === "skipped" && result.reason !== "not-detected") process.exit(1);
1085
+ }
1086
+ function summarize(results) {
1087
+ const lines = results.map(({ name, result }) => {
1088
+ switch (result.kind) {
1089
+ case "installed": return ` ✓ ${name}${result.mutatedPath ? ` → ${result.mutatedPath}` : ""}`;
1090
+ case "already-wired": return ` ✓ ${name} (already wired)`;
1091
+ case "stub": return ` ⚠ ${name} (manual install required: ${result.reason})`;
1092
+ case "skipped": return ` ✗ ${name} (skipped: ${result.reason})`;
1093
+ }
1094
+ });
1095
+ p.note(lines.join("\n"), "summary");
1096
+ const stubs = results.filter((r) => r.result.kind === "stub");
1097
+ if (stubs.length > 0) p.log.info(`${stubs.length} agent(s) require manual install — see docs links above.`);
1098
+ p.outro("Restart any wired agent (or open a new session) to pick up agentmemory.");
1099
+ }
1100
+
1101
+ //#endregion
1102
+ //#region src/cli/onboarding.ts
1103
+ const __dirname$1 = dirname(fileURLToPath(import.meta.url));
1104
+ const NATIVE_AGENTS = [
1105
+ {
1106
+ value: "claude-code",
1107
+ label: "Claude Code",
1108
+ glyph: "⟁"
1109
+ },
1110
+ {
1111
+ value: "codex",
1112
+ label: "Codex",
1113
+ glyph: "◎"
1114
+ },
1115
+ {
1116
+ value: "openhuman",
1117
+ label: "OpenHuman",
1118
+ glyph: "◇"
1119
+ },
1120
+ {
1121
+ value: "openclaw",
1122
+ label: "OpenClaw",
1123
+ glyph: "◇"
1124
+ },
1125
+ {
1126
+ value: "hermes",
1127
+ label: "Hermes",
1128
+ glyph: "◇"
1129
+ },
1130
+ {
1131
+ value: "pi",
1132
+ label: "Pi",
1133
+ glyph: "◇"
1134
+ },
1135
+ {
1136
+ value: "cursor",
1137
+ label: "Cursor",
1138
+ glyph: "◫"
1139
+ },
1140
+ {
1141
+ value: "gemini-cli",
1142
+ label: "Gemini CLI",
1143
+ glyph: "✦"
1144
+ }
1145
+ ];
1146
+ const MCP_AGENTS = [
1147
+ {
1148
+ value: "opencode",
1149
+ label: "OpenCode",
1150
+ glyph: "⬡"
1151
+ },
1152
+ {
1153
+ value: "cline",
1154
+ label: "Cline",
1155
+ glyph: "◇"
1156
+ },
1157
+ {
1158
+ value: "goose",
1159
+ label: "Goose",
1160
+ glyph: "◇"
1161
+ },
1162
+ {
1163
+ value: "kilo",
1164
+ label: "Kilo",
1165
+ glyph: "◇"
1166
+ },
1167
+ {
1168
+ value: "aider",
1169
+ label: "Aider",
1170
+ glyph: "◇"
1171
+ },
1172
+ {
1173
+ value: "claude-desktop",
1174
+ label: "Claude Desktop",
1175
+ glyph: "⟁"
1176
+ },
1177
+ {
1178
+ value: "windsurf",
1179
+ label: "Windsurf",
1180
+ glyph: "◇"
1181
+ },
1182
+ {
1183
+ value: "roo",
1184
+ label: "Roo",
1185
+ glyph: "◇"
1186
+ }
1187
+ ];
1188
+ const PROVIDERS = [
1189
+ {
1190
+ value: "anthropic",
1191
+ label: "Anthropic — claude",
1192
+ envKey: "ANTHROPIC_API_KEY"
1193
+ },
1194
+ {
1195
+ value: "openai",
1196
+ label: "OpenAI — gpt",
1197
+ envKey: "OPENAI_API_KEY"
1198
+ },
1199
+ {
1200
+ value: "gemini",
1201
+ label: "Google — gemini",
1202
+ envKey: "GEMINI_API_KEY"
1203
+ },
1204
+ {
1205
+ value: "openrouter",
1206
+ label: "OpenRouter — multi-model",
1207
+ envKey: "OPENROUTER_API_KEY"
1208
+ },
1209
+ {
1210
+ value: "minimax",
1211
+ label: "MiniMax — minimax-m1",
1212
+ envKey: "MINIMAX_API_KEY"
1213
+ },
1214
+ {
1215
+ value: "skip",
1216
+ label: "Skip — BM25-only mode (no LLM key)",
1217
+ envKey: null
1218
+ }
1219
+ ];
1220
+ function buildAgentOptions() {
1221
+ return [...NATIVE_AGENTS.map((a) => ({
1222
+ value: a.value,
1223
+ label: `${a.glyph} ${a.label}`,
1224
+ hint: "native plugin"
1225
+ })), ...MCP_AGENTS.map((a) => ({
1226
+ value: a.value,
1227
+ label: `${a.glyph} ${a.label}`,
1228
+ hint: "MCP server"
1229
+ }))];
1230
+ }
1231
+ function findEnvExample$1() {
1232
+ const candidates = [
1233
+ join(__dirname$1, "..", "..", ".env.example"),
1234
+ join(__dirname$1, "..", ".env.example"),
1235
+ join(__dirname$1, ".env.example"),
1236
+ join(process.cwd(), ".env.example")
1237
+ ];
1238
+ for (const c of candidates) if (existsSync(c)) return c;
1239
+ return null;
1240
+ }
1241
+ async function seedEnvFile(provider) {
1242
+ const target = join(homedir(), ".agentmemory", ".env");
1243
+ await mkdir(dirname(target), { recursive: true });
1244
+ const template = findEnvExample$1();
1245
+ if (template && !existsSync(target)) try {
1246
+ await copyFile(template, target, constants.COPYFILE_EXCL);
1247
+ } catch (err) {
1248
+ if (err?.code !== "EEXIST") return null;
1249
+ }
1250
+ else if (!template && !existsSync(target)) {
1251
+ const lines = [
1252
+ "# agentmemory environment — uncomment what you need",
1253
+ "# AGENTMEMORY_URL=http://localhost:3111",
1254
+ ""
1255
+ ];
1256
+ const envKey = PROVIDERS.find((x) => x.value === provider)?.envKey;
1257
+ if (envKey) lines.push(`# ${envKey}=`);
1258
+ writeFileSync(target, lines.join("\n"), { mode: 384 });
1259
+ }
1260
+ return target;
1261
+ }
1262
+ async function runOnboarding() {
1263
+ p.note([
1264
+ "Welcome to agentmemory.",
1265
+ "",
1266
+ "Persistent memory for your AI coding agents. We'll pick which",
1267
+ "agents to wire up and which provider (if any) handles compression",
1268
+ "and consolidation. Either step can be changed later in ~/.agentmemory/.env."
1269
+ ].join("\n"), "first-run setup");
1270
+ const agentsPicked = await p.multiselect({
1271
+ message: "Which agents will use agentmemory? (space to toggle, enter to confirm)",
1272
+ options: buildAgentOptions(),
1273
+ required: false,
1274
+ initialValues: ["claude-code"]
1275
+ });
1276
+ if (p.isCancel(agentsPicked)) {
1277
+ p.cancel("Setup cancelled. Re-run any time with: agentmemory --reset");
1278
+ process.exit(0);
1279
+ }
1280
+ if ((agentsPicked ?? []).length > 0) p.note([
1281
+ "━ how this works ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
1282
+ "All selected agents share the same memory at :3111.",
1283
+ "A memory saved by Claude Code is visible to Codex + Cursor instantly.",
1284
+ "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
1285
+ ].join("\n"));
1286
+ const providerPicked = await p.select({
1287
+ message: "Which LLM provider should agentmemory use for compress/consolidate?",
1288
+ options: PROVIDERS.map(({ value, label }) => ({
1289
+ value,
1290
+ label
1291
+ })),
1292
+ initialValue: "anthropic"
1293
+ });
1294
+ if (p.isCancel(providerPicked)) {
1295
+ p.cancel("Setup cancelled. Re-run any time with: agentmemory --reset");
1296
+ process.exit(0);
1297
+ }
1298
+ const provider = providerPicked === "skip" ? null : providerPicked;
1299
+ const agents = agentsPicked ?? [];
1300
+ const envPath = await seedEnvFile(provider);
1301
+ writePrefs({
1302
+ lastAgent: agents[0] ?? null,
1303
+ lastAgents: agents,
1304
+ lastProvider: provider,
1305
+ skipSplash: true,
1306
+ firstRunAt: (/* @__PURE__ */ new Date()).toISOString()
1307
+ });
1308
+ const lines = [`✓ Saved preferences to ${join(homedir(), ".agentmemory", "preferences.json")}`];
1309
+ if (envPath) lines.push(`✓ Wrote ${envPath} (edit to add your API key)`);
1310
+ else lines.push(`! Could not write ~/.agentmemory/.env — run \`agentmemory init\` after this completes.`);
1311
+ if (provider) {
1312
+ const envKey = PROVIDERS.find((x) => x.value === provider)?.envKey;
1313
+ if (envKey) lines.push(` Uncomment ${envKey}= in that file to enable ${provider}.`);
1314
+ } else lines.push(" No provider chosen — agentmemory will run in BM25-only mode.");
1315
+ p.note(lines.join("\n"), "ready");
1316
+ if (agents.length > 0) await wireSelectedAgents(agents);
1317
+ return {
1318
+ agents,
1319
+ provider
1320
+ };
1321
+ }
1322
+ async function wireSelectedAgents(agents) {
1323
+ p.note("Wire selected agents now?", "next step");
1324
+ const confirmed = await p.confirm({
1325
+ message: "Run `agentmemory connect <agent>` for each selected agent now? [Y/n]",
1326
+ initialValue: true
1327
+ });
1328
+ if (p.isCancel(confirmed) || confirmed === false) {
1329
+ const cmds = agents.map((a) => ` agentmemory connect ${a}`);
1330
+ p.note(["Wire later with:", ...cmds].join("\n"), "later");
1331
+ return;
1332
+ }
1333
+ const wired = [];
1334
+ const manual = [];
1335
+ const failed = [];
1336
+ for (const name of agents) {
1337
+ const adapter = resolveAdapter(name);
1338
+ if (!adapter) {
1339
+ failed.push({
1340
+ name,
1341
+ reason: "no adapter available"
1342
+ });
1343
+ p.log.warn(`Wiring ${name}… no adapter available (skipped).`);
1344
+ continue;
1345
+ }
1346
+ p.log.step(`Wiring ${name}...`);
1347
+ let result;
1348
+ try {
1349
+ result = await runAdapter(adapter, {
1350
+ dryRun: false,
1351
+ force: false
1352
+ });
1353
+ } catch (err) {
1354
+ const reason = err instanceof Error ? err.message : String(err);
1355
+ failed.push({
1356
+ name,
1357
+ reason
1358
+ });
1359
+ p.log.error(`${name}: ${reason}`);
1360
+ continue;
1361
+ }
1362
+ switch (result.kind) {
1363
+ case "installed":
1364
+ case "already-wired":
1365
+ wired.push(name);
1366
+ break;
1367
+ case "stub":
1368
+ manual.push({
1369
+ name,
1370
+ docs: adapter.docs
1371
+ });
1372
+ break;
1373
+ case "skipped":
1374
+ failed.push({
1375
+ name,
1376
+ reason: result.reason
1377
+ });
1378
+ break;
1379
+ }
1380
+ }
1381
+ const summary = [];
1382
+ if (wired.length > 0) summary.push(`Wired: ${wired.join(", ")}.`);
1383
+ if (manual.length > 0 || failed.length > 0) {
1384
+ const parts = [];
1385
+ for (const m of manual) parts.push(`${m.name} (manual install required${m.docs ? ` — see ${m.docs}` : ""})`);
1386
+ for (const f of failed) parts.push(`${f.name} (${f.reason})`);
1387
+ summary.push(`Skipped/failed: ${parts.join(", ")}.`);
1388
+ }
1389
+ if (summary.length === 0) summary.push("No agents were wired.");
1390
+ p.note(summary.join("\n"), "wire summary");
1391
+ }
1392
+
1393
+ //#endregion
1394
+ //#region src/logger.ts
1395
+ function fmt(level, msg, fields) {
1396
+ if (!fields || Object.keys(fields).length === 0) return `[agentmemory] ${level} ${msg}`;
1397
+ try {
1398
+ return `[agentmemory] ${level} ${msg} ${JSON.stringify(fields)}`;
1399
+ } catch {
1400
+ return `[agentmemory] ${level} ${msg}`;
1401
+ }
1402
+ }
1403
+ function emit(level, msg, fields) {
1404
+ try {
1405
+ process.stderr.write(fmt(level, msg, fields) + "\n");
1406
+ } catch {}
1407
+ }
1408
+ const logger = {
1409
+ info(msg, fields) {
1410
+ emit("info", msg, fields);
1411
+ },
1412
+ warn(msg, fields) {
1413
+ emit("warn", msg, fields);
1414
+ },
1415
+ error(msg, fields) {
1416
+ emit("error", msg, fields);
1417
+ }
1418
+ };
1419
+ let bootVerbose = process.env["AGENTMEMORY_VERBOSE"] === "1" || process.env["AGENTMEMORY_VERBOSE"] === "true";
1420
+ const bootBuffer = [];
1421
+ function setBootVerbose(enabled) {
1422
+ bootVerbose = enabled;
1423
+ }
1424
+ function bootLog(msg) {
1425
+ if (bootVerbose) {
1426
+ try {
1427
+ process.stderr.write(`[agentmemory] ${msg}\n`);
1428
+ } catch {}
1429
+ return;
1430
+ }
1431
+ if (bootBuffer.length < 500) bootBuffer.push(msg);
1432
+ }
1433
+
1434
+ //#endregion
1435
+ //#region src/version.ts
1436
+ const VERSION = "0.9.16";
1437
+
78
1438
  //#endregion
79
1439
  //#region src/cli.ts
80
1440
  const __dirname = dirname(fileURLToPath(import.meta.url));
81
1441
  const args = process.argv.slice(2);
82
1442
  const IS_WINDOWS = platform() === "win32";
83
- const IS_VERBOSE = args.includes("--verbose") || args.includes("-v");
1443
+ const IS_VERBOSE = args.includes("--verbose") || args.includes("-v") || process.env["AGENTMEMORY_VERBOSE"] === "1" || process.env["AGENTMEMORY_VERBOSE"] === "true";
1444
+ setBootVerbose(IS_VERBOSE);
1445
+ const IS_RESET = args.includes("--reset");
84
1446
  const IIPINNED_VERSION = process.env["AGENTMEMORY_III_VERSION"] || "0.11.2";
85
1447
  function iiiReleaseAsset() {
86
1448
  const p = platform();
@@ -111,19 +1473,32 @@ Usage: agentmemory [command] [options]
111
1473
  Commands:
112
1474
  (default) Start agentmemory worker
113
1475
  init Copy bundled .env.example to ~/.agentmemory/.env if absent
1476
+ connect [agent] Wire agentmemory into an installed agent (claude-code, codex,
1477
+ cursor, gemini-cli, openclaw, hermes, pi, openhuman).
1478
+ No arg = interactive picker. --all wires every detected agent.
1479
+ --dry-run shows what would change. --force re-installs.
114
1480
  status Show connection status, memory count, flags, and health
115
- doctor Run diagnostic checks (server, flags, graph, providers)
1481
+ doctor Interactive diagnostic + fixer. [F]ix · [S]kip · [?]more · [Q]uit
1482
+ --all: apply every fix without prompting (CI)
1483
+ --dry-run: show what each fix would do, don't execute
1484
+ remove Cleanly uninstall agentmemory (pidfile, state, .env, binaries).
1485
+ --force: skip confirmations · --keep-data: keep memory data
116
1486
  demo Seed sample sessions and show recall in action
117
1487
  upgrade Upgrade local deps + iii runtime (best effort)
118
- stop Stop the running iii-engine started by this CLI
119
- mcp Start standalone MCP server (no engine required)
1488
+ stop [--force] Stop the running iii-engine started by this CLI.
1489
+ --force bypasses the Docker-heuristic guard and signals
1490
+ whatever pidfile+lsof report on the REST port (use when
1491
+ the engine was started natively but state file is missing).
1492
+ mcp Start standalone MCP shim — opt-in surface for MCP-only clients
1493
+ (Cursor, Gemini CLI, etc). REST always available at :3111.
120
1494
  import-jsonl [p] Import Claude Code JSONL transcripts (default: ~/.claude/projects)
121
1495
  --max-files <N> | --max-files=<N>: override scan cap (default 200, max 1000;
122
1496
  out-of-range is rejected; for trees >1000 files, batch by subdirectory)
123
1497
 
124
1498
  Options:
125
1499
  --help, -h Show this help
126
- --verbose, -v Show engine stderr and diagnostic info on startup
1500
+ --verbose, -v Show engine stderr, boot log, and diagnostic info
1501
+ --reset Wipe ~/.agentmemory/preferences.json and re-run onboarding
127
1502
  --tools all|core Tool visibility (default: core = 7 tools)
128
1503
  --no-engine Skip auto-starting iii-engine
129
1504
  --port <N> Override REST port (default: 3111)
@@ -169,12 +1544,25 @@ function getViewerUrl() {
169
1544
  if (envUrl) return envUrl.replace(/\/+$/, "");
170
1545
  try {
171
1546
  const u = new URL(getBaseUrl());
172
- const vPort = (parseInt(u.port || "3111", 10) || 3111) + 2;
1547
+ const vPort = parseInt(process.env["III_VIEWER_PORT"] || "", 10) || (parseInt(u.port || "3111", 10) || 3111) + 2;
173
1548
  return `${u.protocol}//${u.hostname}:${vPort}`;
174
1549
  } catch {
175
- return `http://localhost:${getRestPort() + 2}`;
1550
+ return `http://localhost:${parseInt(process.env["III_VIEWER_PORT"] || "", 10) || getRestPort() + 2}`;
176
1551
  }
177
1552
  }
1553
+ function getStreamPort() {
1554
+ return parseInt(process.env["III_STREAM_PORT"] || "", 10) || parseInt(process.env["III_STREAMS_PORT"] || "", 10) || 3112;
1555
+ }
1556
+ function getEnginePort() {
1557
+ const explicit = parseInt(process.env["III_ENGINE_PORT"] || "", 10);
1558
+ if (explicit) return explicit;
1559
+ const url = process.env["III_ENGINE_URL"];
1560
+ if (url) try {
1561
+ const parsed = new URL(url).port;
1562
+ if (parsed) return parseInt(parsed, 10);
1563
+ } catch {}
1564
+ return 49134;
1565
+ }
178
1566
  async function isEngineRunning() {
179
1567
  try {
180
1568
  await fetch(`${getBaseUrl()}/`, { signal: AbortSignal.timeout(2e3) });
@@ -240,6 +1628,16 @@ function iiiBinVersion(binPath) {
240
1628
  return null;
241
1629
  }
242
1630
  }
1631
+ let warnedVersionMismatch = false;
1632
+ function warnIfEngineVersionMismatch(iiiBinPath) {
1633
+ if (!iiiBinPath || warnedVersionMismatch) return;
1634
+ const detected = iiiBinVersion(iiiBinPath);
1635
+ if (!detected || detected === IIPINNED_VERSION) return;
1636
+ warnedVersionMismatch = true;
1637
+ const asset = iiiReleaseAsset();
1638
+ const downloadHint = asset ? `curl -fsSL https://github.com/iii-hq/iii/releases/download/iii/v${IIPINNED_VERSION}/${asset} | tar -xz -C ~/.local/bin` : `download v${IIPINNED_VERSION} from https://github.com/iii-hq/iii/releases/tag/iii%2Fv${IIPINNED_VERSION}`;
1639
+ p.log.warn(`iii-engine on PATH is v${detected} but agentmemory v${VERSION} pins v${IIPINNED_VERSION}. Set AGENTMEMORY_III_VERSION=${detected} to silence, or downgrade with: \`${downloadHint}\``);
1640
+ }
243
1641
  function enginePidfilePath() {
244
1642
  return join(homedir(), ".agentmemory", "iii.pid");
245
1643
  }
@@ -300,6 +1698,100 @@ function discoverComposeFile() {
300
1698
  join(process.cwd(), "docker-compose.yml")
301
1699
  ].find((c) => existsSync(c)) ?? null;
302
1700
  }
1701
+ function isInvokedViaNpx() {
1702
+ if (process.env["npm_lifecycle_event"] === "npx") return true;
1703
+ if ((process.argv[1] ?? "").includes("_npx")) return true;
1704
+ const ua = process.env["npm_config_user_agent"] ?? "";
1705
+ if (ua.startsWith("npm/") || ua.includes(" npm/")) return true;
1706
+ return false;
1707
+ }
1708
+ async function maybeOfferGlobalInstall() {
1709
+ if (!isInvokedViaNpx()) return;
1710
+ if (!process.stdin.isTTY) return;
1711
+ if (process.env["CI"]) return;
1712
+ const prefs = readPrefs();
1713
+ if (prefs.skipGlobalInstall || prefs.skipNpxHint) return;
1714
+ const answer = await p.confirm({
1715
+ message: "Install agentmemory globally so the bare `agentmemory` command works in any shell? [Y/n]",
1716
+ initialValue: true
1717
+ });
1718
+ if (p.isCancel(answer)) return;
1719
+ if (answer === false) {
1720
+ writePrefs({ skipGlobalInstall: true });
1721
+ p.log.info("Skipped. Re-run via `npx @agentmemory/agentmemory` or install later with: npm install -g @agentmemory/agentmemory");
1722
+ return;
1723
+ }
1724
+ const npmBin = whichBinary("npm");
1725
+ if (!npmBin) {
1726
+ p.log.warn("npm not found on PATH. Install manually: npm install -g @agentmemory/agentmemory");
1727
+ return;
1728
+ }
1729
+ if (runCommand(npmBin, [
1730
+ "install",
1731
+ "-g",
1732
+ `@agentmemory/agentmemory@${VERSION}`
1733
+ ], { label: `Installing @agentmemory/agentmemory@${VERSION} globally` })) {
1734
+ p.log.success("Installed globally. `agentmemory stop` etc. will now work in new shells.");
1735
+ writePrefs({ skipGlobalInstall: true });
1736
+ } else p.log.warn("Global install failed. Try manually: npm install -g @agentmemory/agentmemory");
1737
+ }
1738
+ function detectIiiConsole() {
1739
+ const onPath = whichBinary("iii-console");
1740
+ if (onPath) return {
1741
+ kind: "installed",
1742
+ binPath: onPath
1743
+ };
1744
+ const fallback = IS_WINDOWS ? join(process.env["USERPROFILE"] ?? "", ".local", "bin", "iii-console.exe") : join(homedir(), ".local", "bin", "iii-console");
1745
+ if (fallback && existsSync(fallback)) return {
1746
+ kind: "installed",
1747
+ binPath: fallback
1748
+ };
1749
+ return { kind: "missing" };
1750
+ }
1751
+ const III_CONSOLE_INSTALL_CMD = "curl -fsSL https://install.iii.dev/console/main/install.sh | sh";
1752
+ async function ensureIiiConsole() {
1753
+ const state = detectIiiConsole();
1754
+ if (state.kind === "installed") return state;
1755
+ if (!process.stdin.isTTY || process.env["CI"]) return state;
1756
+ if (readPrefs().skipConsoleInstall) return state;
1757
+ const answer = await p.confirm({
1758
+ message: "iii console gives engine-level visibility (workers, functions, queues, traces). Install now?",
1759
+ initialValue: true
1760
+ });
1761
+ if (p.isCancel(answer)) return state;
1762
+ if (answer === false) {
1763
+ writePrefs({ skipConsoleInstall: true });
1764
+ return state;
1765
+ }
1766
+ const shBin = whichBinary("sh");
1767
+ const curlBin = whichBinary("curl");
1768
+ if (!shBin || !curlBin) {
1769
+ p.log.warn(`curl or sh not found. Install manually:\n ${III_CONSOLE_INSTALL_CMD}`);
1770
+ return state;
1771
+ }
1772
+ if (!runCommand(shBin, ["-c", III_CONSOLE_INSTALL_CMD], { label: "Installing iii console" })) {
1773
+ p.log.warn(`iii console install failed. Re-run manually:\n ${III_CONSOLE_INSTALL_CMD}`);
1774
+ return state;
1775
+ }
1776
+ return detectIiiConsole();
1777
+ }
1778
+ function adoptRunningEngine() {
1779
+ try {
1780
+ const existingState = readEngineState();
1781
+ const existingPid = readEnginePidfile();
1782
+ if (existingState && existingPid) return;
1783
+ const enginePid = findEnginePidsByPort(getRestPort())[0];
1784
+ if (enginePid && !existingPid) writeEnginePidfile(enginePid);
1785
+ if (!existingState) writeEngineState({
1786
+ kind: "native",
1787
+ configPath: findIiiConfig() || "",
1788
+ attached: true
1789
+ });
1790
+ if (enginePid && !existingPid) p.log.info(`Attached to existing iii-engine (pid ${enginePid})`);
1791
+ } catch (err) {
1792
+ vlog(`adoptRunningEngine: ${err instanceof Error ? err.message : String(err)}`);
1793
+ }
1794
+ }
303
1795
  async function runIiiInstaller() {
304
1796
  const releaseUrl = iiiReleaseUrl();
305
1797
  const asset = iiiReleaseAsset();
@@ -389,6 +1881,7 @@ function spawnEngineBackground(bin, spawnArgs, label) {
389
1881
  return child;
390
1882
  }
391
1883
  function startIiiBin(iiiBin, configPath) {
1884
+ warnIfEngineVersionMismatch(iiiBin);
392
1885
  const s = p.spinner();
393
1886
  s.start(`Starting iii-engine: ${iiiBin}`);
394
1887
  writeEngineState({
@@ -550,16 +2043,69 @@ function installInstructions() {
550
2043
  function portInUseDiagnostic(port) {
551
2044
  return IS_WINDOWS ? ` netstat -ano | findstr :${port}` : ` lsof -i :${port} # or: ss -tlnp | grep :${port}`;
552
2045
  }
2046
+ async function waitForAgentmemoryReady(timeoutMs) {
2047
+ const start = Date.now();
2048
+ while (Date.now() - start < timeoutMs) {
2049
+ if (await isAgentmemoryReady()) return true;
2050
+ await new Promise((r) => setTimeout(r, 250));
2051
+ }
2052
+ return false;
2053
+ }
2054
+ function getEngineHost() {
2055
+ for (const envKey of ["III_ENGINE_URL", "AGENTMEMORY_URL"]) {
2056
+ const raw = process.env[envKey];
2057
+ if (!raw) continue;
2058
+ try {
2059
+ const parsed = new URL(raw);
2060
+ if (parsed.hostname) return parsed.hostname;
2061
+ } catch {}
2062
+ }
2063
+ return "localhost";
2064
+ }
2065
+ function printReadyHint(consoleState) {
2066
+ const restUrl = getBaseUrl();
2067
+ const viewerUrl = getViewerUrl();
2068
+ const engineHost = getEngineHost();
2069
+ const streamUrl = `ws://${engineHost}:${getStreamPort()}`;
2070
+ const engineUrl = `ws://${engineHost}:${getEnginePort()}`;
2071
+ const consoleLine = consoleState.kind === "installed" ? `iii console ${consoleState.binPath} (run: ${consoleState.binPath} -p <port>)` : `iii console (install: ${III_CONSOLE_INSTALL_CMD})`;
2072
+ const lines = [
2073
+ `REST API ${restUrl}`,
2074
+ `Viewer ${viewerUrl}`,
2075
+ `Streams ${streamUrl}`,
2076
+ `Engine ${engineUrl}`,
2077
+ consoleLine
2078
+ ];
2079
+ p.note(lines.join("\n"), `agentmemory v${VERSION}`);
2080
+ const demoCommand = isInvokedViaNpx() ? "npx @agentmemory/agentmemory demo" : "agentmemory demo";
2081
+ process.stdout.write(`\nTry: ${demoCommand}\n`);
2082
+ }
553
2083
  async function main() {
554
- p.intro("agentmemory");
2084
+ if (IS_RESET) resetPrefs();
2085
+ const firstRun = isFirstRun();
2086
+ const prefs = readPrefs();
2087
+ if (firstRun || IS_RESET || IS_VERBOSE || !prefs.skipSplash) renderSplash(VERSION);
2088
+ if (firstRun || IS_RESET) await runOnboarding();
555
2089
  if (skipEngine) {
556
- p.log.info("Skipping engine check (--no-engine)");
557
- await import("./src-BBI-ah3h.mjs");
2090
+ if (IS_VERBOSE) p.log.info("Skipping engine check (--no-engine)");
2091
+ await import("./src-3Oy_OOlF.mjs");
2092
+ if (await waitForAgentmemoryReady(15e3)) {
2093
+ const consoleState = await ensureIiiConsole();
2094
+ await maybeOfferGlobalInstall();
2095
+ printReadyHint(consoleState);
2096
+ }
558
2097
  return;
559
2098
  }
560
2099
  if (await isEngineRunning()) {
561
- p.log.success("iii-engine is running");
562
- await import("./src-BBI-ah3h.mjs");
2100
+ if (IS_VERBOSE) p.log.success("iii-engine is running");
2101
+ warnIfEngineVersionMismatch(whichBinary("iii") ?? fallbackIiiPaths().find((p) => existsSync(p)) ?? null);
2102
+ adoptRunningEngine();
2103
+ await import("./src-3Oy_OOlF.mjs");
2104
+ if (await waitForAgentmemoryReady(15e3)) {
2105
+ const consoleState = await ensureIiiConsole();
2106
+ await maybeOfferGlobalInstall();
2107
+ printReadyHint(consoleState);
2108
+ }
563
2109
  return;
564
2110
  }
565
2111
  if (!await startEngine()) {
@@ -603,7 +2149,13 @@ async function main() {
603
2149
  process.exit(1);
604
2150
  }
605
2151
  s.stop("iii-engine is ready");
606
- await import("./src-BBI-ah3h.mjs");
2152
+ await import("./src-3Oy_OOlF.mjs");
2153
+ if (await waitForAgentmemoryReady(15e3)) {
2154
+ const consoleState = await ensureIiiConsole();
2155
+ await maybeOfferGlobalInstall();
2156
+ printReadyHint(consoleState);
2157
+ }
2158
+ writePrefs({ skipSplash: true });
607
2159
  }
608
2160
  async function apiFetch(base, path, timeoutMs = 5e3) {
609
2161
  try {
@@ -717,10 +2269,143 @@ function checkClaudeCodeHooks() {
717
2269
  if (content.includes("Loading hooks from plugin: agentmemory")) return { state: "loaded" };
718
2270
  return { state: "not-loaded" };
719
2271
  }
720
- async function runDoctor() {
721
- p.intro("agentmemory doctor");
2272
+ function buildDoctorContext() {
2273
+ return {
2274
+ baseUrl: getBaseUrl(),
2275
+ viewerUrl: getViewerUrl(),
2276
+ envPath: join(homedir(), ".agentmemory", ".env"),
2277
+ pidfilePath: enginePidfilePath(),
2278
+ enginePath: engineStatePath(),
2279
+ pinnedVersion: IIPINNED_VERSION
2280
+ };
2281
+ }
2282
+ function buildDoctorEffects() {
2283
+ return {
2284
+ envFileExists: () => existsSync(join(homedir(), ".agentmemory", ".env")),
2285
+ readEnvFile: () => {
2286
+ try {
2287
+ return parseEnvFile(readFileSync(join(homedir(), ".agentmemory", ".env"), "utf-8"));
2288
+ } catch {
2289
+ return {};
2290
+ }
2291
+ },
2292
+ pidfileExists: () => existsSync(enginePidfilePath()),
2293
+ pidfilePidIsAlive: () => {
2294
+ const pid = readEnginePidfile();
2295
+ if (pid === null) return null;
2296
+ return pidAlive(pid);
2297
+ },
2298
+ findIiiBinary: () => whichBinary("iii"),
2299
+ localBinIiiPath: () => join(homedir(), ".local", "bin", IS_WINDOWS ? "iii.exe" : "iii"),
2300
+ iiiBinaryVersion: (binPath) => iiiBinVersion(binPath),
2301
+ viewerReachable: async (timeoutMs = 2e3) => {
2302
+ try {
2303
+ return (await fetch(getViewerUrl(), { signal: AbortSignal.timeout(timeoutMs) })).ok;
2304
+ } catch {
2305
+ return false;
2306
+ }
2307
+ },
2308
+ runInit: async () => {
2309
+ try {
2310
+ await runInit();
2311
+ return {
2312
+ ok: true,
2313
+ message: "Wrote ~/.agentmemory/.env"
2314
+ };
2315
+ } catch (err) {
2316
+ return {
2317
+ ok: false,
2318
+ message: err instanceof Error ? err.message : String(err)
2319
+ };
2320
+ }
2321
+ },
2322
+ openEditor: async (path) => {
2323
+ const editor = process.env["EDITOR"] || process.env["VISUAL"] || "nano";
2324
+ p.log.info(`Opening ${path} in ${editor}…`);
2325
+ try {
2326
+ const result = spawnSync(editor, [path], { stdio: "inherit" });
2327
+ if (result.error) return {
2328
+ ok: false,
2329
+ message: `Failed to launch ${editor}: ${result.error.message}`
2330
+ };
2331
+ if ((result.status ?? 0) !== 0) return {
2332
+ ok: false,
2333
+ message: `${editor} exited with code ${result.status}`
2334
+ };
2335
+ return {
2336
+ ok: true,
2337
+ message: `Saved ${path}`
2338
+ };
2339
+ } catch (err) {
2340
+ return {
2341
+ ok: false,
2342
+ message: err instanceof Error ? err.message : String(err)
2343
+ };
2344
+ }
2345
+ },
2346
+ runIiiInstaller: async () => {
2347
+ const r = await runIiiInstaller();
2348
+ return {
2349
+ ok: r.ok,
2350
+ message: r.ok ? `Installed iii v${IIPINNED_VERSION} to ${r.binPath}` : "iii installer failed (see warnings above)"
2351
+ };
2352
+ },
2353
+ runStop: async () => {
2354
+ try {
2355
+ const portPids = findEnginePidsByPort(getRestPort());
2356
+ const pidfilePid = readEnginePidfile();
2357
+ if (portPids.length === 0 && pidfilePid === null) {
2358
+ clearEnginePidfile();
2359
+ clearEngineState();
2360
+ return {
2361
+ ok: true,
2362
+ message: "Nothing to stop."
2363
+ };
2364
+ }
2365
+ const candidates = /* @__PURE__ */ new Set();
2366
+ if (pidfilePid) candidates.add(pidfilePid);
2367
+ for (const pid of portPids) candidates.add(pid);
2368
+ let allStopped = true;
2369
+ for (const pid of candidates) if (!await signalAndWait(pid, "SIGTERM", 3e3)) allStopped = false;
2370
+ clearEnginePidfile();
2371
+ clearEngineState();
2372
+ return {
2373
+ ok: allStopped,
2374
+ message: allStopped ? "Engine stopped." : "Some engine pids survived."
2375
+ };
2376
+ } catch (err) {
2377
+ return {
2378
+ ok: false,
2379
+ message: err instanceof Error ? err.message : String(err)
2380
+ };
2381
+ }
2382
+ },
2383
+ runStart: async () => {
2384
+ try {
2385
+ if (!await startEngine()) return {
2386
+ ok: false,
2387
+ message: "startEngine() returned false"
2388
+ };
2389
+ const ready = await waitForEngine(15e3);
2390
+ return {
2391
+ ok: ready,
2392
+ message: ready ? "Engine ready" : "Engine did not become ready within 15s"
2393
+ };
2394
+ } catch (err) {
2395
+ return {
2396
+ ok: false,
2397
+ message: err instanceof Error ? err.message : String(err)
2398
+ };
2399
+ }
2400
+ },
2401
+ clearEnginePidAndState: () => {
2402
+ clearEnginePidfile();
2403
+ clearEngineState();
2404
+ }
2405
+ };
2406
+ }
2407
+ async function passiveServerChecks() {
722
2408
  const base = getBaseUrl();
723
- const viewerUrl = getViewerUrl();
724
2409
  const checks = [];
725
2410
  const serverUp = await isEngineRunning();
726
2411
  checks.push({
@@ -728,16 +2413,12 @@ async function runDoctor() {
728
2413
  ok: serverUp,
729
2414
  hint: serverUp ? void 0 : `Start with: npx @agentmemory/agentmemory (tried ${base})`
730
2415
  });
731
- if (!serverUp) {
732
- p.note(formatChecks(checks), "server unreachable");
733
- process.exit(1);
734
- }
2416
+ if (!serverUp) return checks;
735
2417
  const [health, flags, graph] = await Promise.all([
736
2418
  apiFetch(base, "health", 3e3),
737
2419
  apiFetch(base, "config/flags", 3e3),
738
2420
  apiFetch(base, "graph/stats", 3e3)
739
2421
  ]);
740
- const viewerUp = await fetch(viewerUrl, { signal: AbortSignal.timeout(2e3) }).then((r) => r.ok).catch(() => false);
741
2422
  const hasLlm = flags?.provider === "llm";
742
2423
  const hasEmbed = flags?.embeddingProvider === "embeddings";
743
2424
  const graphHas = Number(graph?.totalNodes ?? graph?.nodes ?? graph?.nodeCount ?? 0) > 0;
@@ -745,18 +2426,14 @@ async function runDoctor() {
745
2426
  name: "Health status",
746
2427
  ok: health?.status === "healthy",
747
2428
  hint: health?.status === "healthy" ? void 0 : `Status: ${health?.status || "unknown"}`
748
- }, {
749
- name: "Viewer reachable",
750
- ok: viewerUp,
751
- hint: viewerUp ? void 0 : `${viewerUrl} not responding`
752
2429
  }, {
753
2430
  name: "LLM provider",
754
2431
  ok: hasLlm,
755
- hint: hasLlm ? void 0 : "export ANTHROPIC_API_KEY=sk-ant-... (or GEMINI/OPENROUTER/MINIMAX) then restart"
2432
+ hint: hasLlm ? void 0 : "set ANTHROPIC_API_KEY (or GEMINI/OPENROUTER/MINIMAX) in ~/.agentmemory/.env"
756
2433
  }, {
757
2434
  name: "Embedding provider",
758
2435
  ok: hasEmbed,
759
- hint: hasEmbed ? void 0 : "Running BM25-only. Add OPENAI_API_KEY / VOYAGE_API_KEY / COHERE_API_KEY / OLLAMA_HOST for semantic recall"
2436
+ hint: hasEmbed ? void 0 : "Running BM25-only. Add OPENAI_API_KEY / VOYAGE_API_KEY / COHERE_API_KEY / OLLAMA_HOST"
760
2437
  });
761
2438
  for (const f of flags?.flags || []) checks.push({
762
2439
  name: f.label,
@@ -772,7 +2449,7 @@ async function runDoctor() {
772
2449
  };
773
2450
  case "not-loaded": return {
774
2451
  ok: false,
775
- hint: "Plugin enabled but hooks not loaded by Claude Code. Try: /plugin uninstall agentmemory@agentmemory && /plugin install agentmemory@agentmemory, then restart the session. CC must be >= 2.1.x for plugin-hook auto-load."
2452
+ hint: "Plugin enabled but hooks not loaded by Claude Code. Try: /plugin uninstall agentmemory@agentmemory && /plugin install agentmemory@agentmemory, then restart the session."
776
2453
  };
777
2454
  case "no-debug-log": return {
778
2455
  ok: false,
@@ -788,16 +2465,136 @@ async function runDoctor() {
788
2465
  checks.push({
789
2466
  name: "Knowledge graph populated",
790
2467
  ok: graphHas,
791
- hint: graphHas ? void 0 : "Graph is empty. Run a session with GRAPH_EXTRACTION_ENABLED=true, or POST /agentmemory/graph/extract"
2468
+ hint: graphHas ? void 0 : "Graph is empty. Run a session with GRAPH_EXTRACTION_ENABLED=true."
2469
+ });
2470
+ return checks;
2471
+ }
2472
+ async function askFixAction(d) {
2473
+ const choice = await p.select({
2474
+ message: `[${d.id}] ${d.message}`,
2475
+ options: [
2476
+ {
2477
+ value: "fix",
2478
+ label: "F Fix",
2479
+ hint: d.fixPreview
2480
+ },
2481
+ {
2482
+ value: "skip",
2483
+ label: "S Skip"
2484
+ },
2485
+ {
2486
+ value: "more",
2487
+ label: "? More info"
2488
+ },
2489
+ {
2490
+ value: "quit",
2491
+ label: "Q Quit doctor"
2492
+ }
2493
+ ],
2494
+ initialValue: "fix"
792
2495
  });
793
- const passed = checks.filter((c) => c.ok).length;
794
- const total = checks.length;
795
- p.note(formatChecks(checks), `${passed}/${total} checks passing`);
796
- if (passed === total) p.outro("✓ All checks passed. agentmemory is healthy.");
797
- else {
798
- p.outro(`${total - passed} issue(s) — follow hints above to fix.`);
2496
+ if (p.isCancel(choice)) return "quit";
2497
+ return choice;
2498
+ }
2499
+ async function applyFixWithReport(d, ctx, dryRun) {
2500
+ if (dryRun) {
2501
+ p.log.info(`[dry-run] would: ${d.fixPreview}`);
2502
+ return {
2503
+ ok: true,
2504
+ message: "(dry-run)"
2505
+ };
2506
+ }
2507
+ const result = await d.fix(ctx);
2508
+ if (result.ok) p.log.success(result.message ?? `${d.id} fixed.`);
2509
+ else p.log.error(result.message ?? `${d.id} fix failed.`);
2510
+ return result;
2511
+ }
2512
+ async function runDoctor() {
2513
+ p.intro("agentmemory doctor");
2514
+ const applyAll = args.includes("--all");
2515
+ const dryRun = args.includes("--dry-run");
2516
+ if (applyAll && dryRun) {
2517
+ p.log.error("Cannot combine --all and --dry-run.");
2518
+ process.exit(2);
2519
+ }
2520
+ const passive = await passiveServerChecks();
2521
+ const passivePassed = passive.filter((c) => c.ok).length;
2522
+ p.note(formatChecks(passive), `server: ${passivePassed}/${passive.length} passing`);
2523
+ const ctx = buildDoctorContext();
2524
+ const diagnostics = buildDiagnostics(buildDoctorEffects());
2525
+ if (dryRun) {
2526
+ const results = [];
2527
+ for (const d of diagnostics) results.push({
2528
+ diagnostic: d,
2529
+ status: await d.check(ctx)
2530
+ });
2531
+ const lines = dryRunPlan(ctx, results);
2532
+ p.note(lines.join("\n"), "dry-run plan");
2533
+ p.outro("Dry-run complete. Re-run without --dry-run to apply.");
2534
+ return;
2535
+ }
2536
+ let failed = 0;
2537
+ let fixed = 0;
2538
+ let skipped = 0;
2539
+ let quit = false;
2540
+ for (const d of diagnostics) {
2541
+ if (quit) {
2542
+ skipped++;
2543
+ continue;
2544
+ }
2545
+ const status = await d.check(ctx);
2546
+ if (status.ok) {
2547
+ p.log.success(`${d.id} ✓${status.detail ? ` (${status.detail})` : ""}`);
2548
+ continue;
2549
+ }
2550
+ failed++;
2551
+ p.log.warn(`${d.id} ✗ ${status.detail ?? ""}`.trim());
2552
+ p.log.info(`why: ${d.fixPreview}`);
2553
+ if (d.manualOnly) p.log.info(`(manual fix only — see "${d.id}" docs)`);
2554
+ if (applyAll) {
2555
+ if ((await applyFixWithReport(d, ctx, false)).ok) fixed++;
2556
+ if (!(await d.check(ctx)).ok) p.log.warn(`${d.id} still failing after fix.`);
2557
+ continue;
2558
+ }
2559
+ while (true) {
2560
+ const action = await askFixAction(d);
2561
+ if (action === "fix") {
2562
+ if ((await applyFixWithReport(d, ctx, false)).ok) {
2563
+ const after = await d.check(ctx);
2564
+ if (after.ok) fixed++;
2565
+ else p.log.warn(`${d.id} still failing after fix: ${after.detail ?? ""}`);
2566
+ }
2567
+ break;
2568
+ }
2569
+ if (action === "skip") {
2570
+ skipped++;
2571
+ break;
2572
+ }
2573
+ if (action === "more") {
2574
+ p.note(d.moreInfo, `[${d.id}] more info`);
2575
+ continue;
2576
+ }
2577
+ if (action === "quit") {
2578
+ quit = true;
2579
+ break;
2580
+ }
2581
+ }
2582
+ }
2583
+ const summary = `${diagnostics.length} checks · ${failed} failing · ${fixed} fixed · ${skipped} skipped`;
2584
+ if (quit) {
2585
+ p.outro(`Quit early. ${summary}`);
799
2586
  process.exit(1);
800
2587
  }
2588
+ if (failed === 0) {
2589
+ p.outro("All diagnostics passing. agentmemory is healthy.");
2590
+ return;
2591
+ }
2592
+ if (failed - fixed === 0) {
2593
+ p.outro(`All fixes applied. ${summary}`);
2594
+ return;
2595
+ }
2596
+ p.outro(summary);
2597
+ process.exit(1);
801
2598
  }
802
2599
  function buildDemoSessions() {
803
2600
  return [
@@ -1178,6 +2975,7 @@ async function runStop() {
1178
2975
  const port = getRestPort();
1179
2976
  const state = readEngineState();
1180
2977
  const running = await isEngineRunning();
2978
+ const force = args.includes("--force");
1181
2979
  if (state?.kind === "docker") {
1182
2980
  if (!running) {
1183
2981
  p.log.info(`No engine responding on port ${port}.`);
@@ -1206,8 +3004,9 @@ async function runStop() {
1206
3004
  }
1207
3005
  if (!state) {
1208
3006
  const compose = discoverComposeFile();
1209
- if (compose && pidfilePid === null) {
1210
- 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 AGENTMEMORY_USE_DOCKER=1 to record state next time.`);
3007
+ if (compose && pidfilePid === null) if (force) p.log.warn(`--force: bypassing Docker-heuristic guard. Falling back to native pidfile + lsof on :${port}.`);
3008
+ else {
3009
+ 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.`);
1211
3010
  process.exit(1);
1212
3011
  }
1213
3012
  }
@@ -1235,7 +3034,11 @@ async function runStop() {
1235
3034
  p.outro("Stopped. Memories persisted to disk; restart anytime with: npx @agentmemory/agentmemory");
1236
3035
  }
1237
3036
  async function runMcp() {
1238
- await import("./standalone-Cf5sp0XM.mjs");
3037
+ await import("./standalone-BQOaGF4z.mjs");
3038
+ }
3039
+ async function runConnectCmd() {
3040
+ const { runConnect } = await Promise.resolve().then(() => connect_exports);
3041
+ await runConnect(args.slice(1));
1239
3042
  }
1240
3043
  async function runImportJsonl() {
1241
3044
  const VALUE_FLAGS = new Set(["--port", "--tools"]);
@@ -1339,13 +3142,126 @@ async function runImportJsonl() {
1339
3142
  process.exit(1);
1340
3143
  }
1341
3144
  }
3145
+ function loadConnectManifest(home) {
3146
+ const path = join(home, ".agentmemory", "backups", "connect-manifest.json");
3147
+ try {
3148
+ const raw = readFileSync(path, "utf-8");
3149
+ const parsed = JSON.parse(raw);
3150
+ if (Array.isArray(parsed?.installed)) return { installed: parsed.installed };
3151
+ return null;
3152
+ } catch {
3153
+ return null;
3154
+ }
3155
+ }
3156
+ function probeLocalBinIiiVersion(home) {
3157
+ const path = localBinIii(home);
3158
+ if (!existsSync(path)) return null;
3159
+ return iiiBinVersion(path);
3160
+ }
3161
+ function safeDelete(path) {
3162
+ try {
3163
+ if (!existsSync(path)) return {
3164
+ ok: true,
3165
+ message: `not present (${path})`
3166
+ };
3167
+ if (statSync(path).isDirectory()) rmSync(path, {
3168
+ recursive: true,
3169
+ force: true
3170
+ });
3171
+ else unlinkSync(path);
3172
+ return {
3173
+ ok: true,
3174
+ message: `deleted ${path}`
3175
+ };
3176
+ } catch (err) {
3177
+ return {
3178
+ ok: false,
3179
+ message: `failed ${path}: ${err instanceof Error ? err.message : String(err)}`
3180
+ };
3181
+ }
3182
+ }
3183
+ async function runRemove() {
3184
+ p.intro("agentmemory remove");
3185
+ const force = args.includes("--force");
3186
+ const keepData = args.includes("--keep-data");
3187
+ const home = homedir();
3188
+ const connectManifest = loadConnectManifest(home);
3189
+ const plan = buildRemovePlan({
3190
+ home,
3191
+ pinnedVersion: IIPINNED_VERSION,
3192
+ localBinIiiVersion: probeLocalBinIiiVersion(home),
3193
+ connectManifest
3194
+ }, {
3195
+ force,
3196
+ keepData
3197
+ });
3198
+ if (plan.filter((it) => it.applicable).length === 0) {
3199
+ p.outro("Nothing to remove. agentmemory is already gone.");
3200
+ return;
3201
+ }
3202
+ p.note(formatPlan(plan), "destruction plan");
3203
+ if (!force) {
3204
+ const proceed = await p.confirm({
3205
+ message: "Proceed with these deletions?",
3206
+ initialValue: false
3207
+ });
3208
+ if (p.isCancel(proceed) || proceed !== true) {
3209
+ p.cancel("Cancelled. Nothing was deleted.");
3210
+ return;
3211
+ }
3212
+ const sure = await p.confirm({
3213
+ message: "This is irreversible. Continue?",
3214
+ initialValue: false
3215
+ });
3216
+ if (p.isCancel(sure) || sure !== true) {
3217
+ p.cancel("Cancelled. Nothing was deleted.");
3218
+ return;
3219
+ }
3220
+ }
3221
+ for (const item of plan) {
3222
+ if (!item.applicable) continue;
3223
+ if (item.alwaysAsk) {
3224
+ const ok = await p.confirm({
3225
+ message: `${item.description} — really delete${item.path ? ` ${item.path}` : ""}?`,
3226
+ initialValue: false
3227
+ });
3228
+ if (p.isCancel(ok) || ok !== true) {
3229
+ p.log.info(`skipped: ${item.id}`);
3230
+ continue;
3231
+ }
3232
+ }
3233
+ if (item.id === "stop-engine") {
3234
+ try {
3235
+ const portPids = findEnginePidsByPort(getRestPort());
3236
+ const pidfilePid = readEnginePidfile();
3237
+ const cands = /* @__PURE__ */ new Set();
3238
+ if (pidfilePid) cands.add(pidfilePid);
3239
+ for (const pid of portPids) cands.add(pid);
3240
+ for (const pid of cands) await signalAndWait(pid, "SIGTERM", 3e3);
3241
+ clearEnginePidfile();
3242
+ clearEngineState();
3243
+ p.log.success(cands.size > 0 ? `stopped engine (${cands.size} pid${cands.size === 1 ? "" : "s"})` : "no engine running");
3244
+ } catch (err) {
3245
+ p.log.warn(`engine stop best-effort: ${err instanceof Error ? err.message : String(err)}`);
3246
+ }
3247
+ continue;
3248
+ }
3249
+ if (!item.path) continue;
3250
+ const r = safeDelete(item.path);
3251
+ if (r.ok) p.log.success(r.message);
3252
+ else p.log.error(r.message);
3253
+ }
3254
+ p.outro("Done. agentmemory cleanly removed. The npm package itself: npm uninstall -g @agentmemory/agentmemory");
3255
+ }
1342
3256
  ({
1343
3257
  init: runInit,
3258
+ connect: runConnectCmd,
1344
3259
  status: runStatus,
1345
3260
  doctor: runDoctor,
1346
3261
  demo: runDemo,
1347
3262
  upgrade: runUpgrade,
1348
3263
  stop: runStop,
3264
+ remove: runRemove,
1349
3265
  mcp: runMcp,
1350
3266
  "import-jsonl": runImportJsonl
1351
3267
  }[args[0] ?? ""] ?? main)().catch((err) => {
@@ -1354,5 +3270,5 @@ async function runImportJsonl() {
1354
3270
  });
1355
3271
 
1356
3272
  //#endregion
1357
- export { jaccardSimilarity as a, generateId as i, STREAM as n, fingerprintId as r, KV as t };
3273
+ export { STREAM as a, jaccardSimilarity as c, KV as i, bootLog as n, fingerprintId as o, logger as r, generateId as s, VERSION as t };
1358
3274
  //# sourceMappingURL=cli.mjs.map