@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/README.md +44 -0
- package/dist/cli.mjs +1956 -40
- package/dist/cli.mjs.map +1 -1
- package/dist/{image-refs-HVu22rfu.mjs → image-refs-R3tin9MR.mjs} +2 -2
- package/dist/{image-refs-HVu22rfu.mjs.map → image-refs-R3tin9MR.mjs.map} +1 -1
- package/dist/{image-store-BfN1vDbj.mjs → image-store-DyrKZKqZ.mjs} +1 -1
- package/dist/index.mjs +64 -35
- package/dist/index.mjs.map +1 -1
- package/dist/{src-BBI-ah3h.mjs → src-3Oy_OOlF.mjs} +63 -72
- package/dist/src-3Oy_OOlF.mjs.map +1 -0
- package/dist/{standalone-Cf5sp0XM.mjs → standalone-BQOaGF4z.mjs} +3 -3
- package/dist/{standalone-Cf5sp0XM.mjs.map → standalone-BQOaGF4z.mjs.map} +1 -1
- package/dist/standalone.mjs +1 -1
- package/dist/standalone.mjs.map +1 -1
- package/dist/{tools-registry-BDimtXJb.mjs → tools-registry-BF0pgZmI.mjs} +2 -6
- package/dist/tools-registry-BF0pgZmI.mjs.map +1 -0
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/dist/src-BBI-ah3h.mjs.map +0 -1
- package/dist/tools-registry-BDimtXJb.mjs.map +0 -1
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
|
|
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
|
|
119
|
-
|
|
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
|
|
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
|
-
|
|
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-
|
|
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
|
-
|
|
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-
|
|
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
|
-
|
|
721
|
-
|
|
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 : "
|
|
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
|
|
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.
|
|
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
|
|
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
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
p.
|
|
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
|
-
|
|
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-
|
|
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 {
|
|
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
|