@agentmemory/agentmemory 0.9.14 → 0.9.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.mjs +1217 -37
- package/dist/cli.mjs.map +1 -1
- package/dist/connect-hRTF7E2c.mjs +525 -0
- package/dist/connect-hRTF7E2c.mjs.map +1 -0
- 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 +62 -35
- package/dist/index.mjs.map +1 -1
- package/dist/{src-BBI-ah3h.mjs → src-BGcqJR1a.mjs} +61 -72
- package/dist/src-BGcqJR1a.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,11 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { execFileSync, spawn, spawnSync } from "node:child_process";
|
|
3
|
-
import { existsSync, mkdirSync, readFileSync, readdirSync, readlinkSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { closeSync, constants, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, readdirSync, readlinkSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync, writeSync } from "node:fs";
|
|
4
4
|
import { delimiter, dirname, join } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { homedir, platform } from "node:os";
|
|
7
7
|
import * as p from "@clack/prompts";
|
|
8
8
|
import { createHash } from "node:crypto";
|
|
9
|
+
import { copyFile, mkdir } from "node:fs/promises";
|
|
9
10
|
|
|
10
11
|
//#region src/state/schema.ts
|
|
11
12
|
const KV = {
|
|
@@ -75,12 +76,741 @@ function jaccardSimilarity(a, b) {
|
|
|
75
76
|
return intersection / (setA.size + setB.size - intersection);
|
|
76
77
|
}
|
|
77
78
|
|
|
79
|
+
//#endregion
|
|
80
|
+
//#region src/cli/doctor-diagnostics.ts
|
|
81
|
+
/** Common placeholder values shipped in .env.example. */
|
|
82
|
+
const PLACEHOLDER_VALUES = new Set([
|
|
83
|
+
"",
|
|
84
|
+
"your-key-here",
|
|
85
|
+
"sk-ant-...",
|
|
86
|
+
"sk-...",
|
|
87
|
+
"changeme",
|
|
88
|
+
"todo",
|
|
89
|
+
"xxx"
|
|
90
|
+
]);
|
|
91
|
+
const PROVIDER_KEY_NAMES = [
|
|
92
|
+
"ANTHROPIC_API_KEY",
|
|
93
|
+
"OPENAI_API_KEY",
|
|
94
|
+
"GEMINI_API_KEY",
|
|
95
|
+
"GOOGLE_API_KEY",
|
|
96
|
+
"OPENROUTER_API_KEY",
|
|
97
|
+
"MINIMAX_API_KEY"
|
|
98
|
+
];
|
|
99
|
+
function parseEnvFile(content) {
|
|
100
|
+
const out = {};
|
|
101
|
+
for (const rawLine of content.split(/\r?\n/)) {
|
|
102
|
+
const line = rawLine.trim();
|
|
103
|
+
if (!line || line.startsWith("#")) continue;
|
|
104
|
+
const eq = line.indexOf("=");
|
|
105
|
+
if (eq < 0) continue;
|
|
106
|
+
const key = line.slice(0, eq).trim();
|
|
107
|
+
let value = line.slice(eq + 1).trim();
|
|
108
|
+
if (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1);
|
|
109
|
+
out[key] = value;
|
|
110
|
+
}
|
|
111
|
+
return out;
|
|
112
|
+
}
|
|
113
|
+
/** Returns the list of provider keys that look real (non-placeholder). */
|
|
114
|
+
function realProviderKeys(env) {
|
|
115
|
+
return PROVIDER_KEY_NAMES.filter((k) => {
|
|
116
|
+
const v = (env[k] ?? "").trim();
|
|
117
|
+
if (!v) return false;
|
|
118
|
+
if (PLACEHOLDER_VALUES.has(v.toLowerCase())) return false;
|
|
119
|
+
if (/^x+$/i.test(v.replace(/[-_]/g, ""))) return false;
|
|
120
|
+
return true;
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
/** Returns the list of provider key NAMES that exist but are placeholders. */
|
|
124
|
+
function placeholderProviderKeys(env) {
|
|
125
|
+
return PROVIDER_KEY_NAMES.filter((k) => {
|
|
126
|
+
const v = (env[k] ?? "").trim();
|
|
127
|
+
if (!v) return false;
|
|
128
|
+
if (PLACEHOLDER_VALUES.has(v.toLowerCase())) return true;
|
|
129
|
+
if (/^x+$/i.test(v.replace(/[-_]/g, ""))) return true;
|
|
130
|
+
return false;
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
function buildDiagnostics(effects) {
|
|
134
|
+
return [
|
|
135
|
+
{
|
|
136
|
+
id: "env-missing",
|
|
137
|
+
message: "~/.agentmemory/.env is missing.",
|
|
138
|
+
fixPreview: "Copy .env.example into ~/.agentmemory/.env (your keys file).",
|
|
139
|
+
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.",
|
|
140
|
+
check: async () => ({
|
|
141
|
+
ok: effects.envFileExists(),
|
|
142
|
+
detail: effects.envFileExists() ? void 0 : "no env file"
|
|
143
|
+
}),
|
|
144
|
+
fix: () => effects.runInit()
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
id: "no-llm-provider-key",
|
|
148
|
+
message: "No LLM provider API key found in ~/.agentmemory/.env.",
|
|
149
|
+
fixPreview: "Open ~/.agentmemory/.env in $EDITOR and paste your key, then re-check.",
|
|
150
|
+
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.",
|
|
151
|
+
check: async () => {
|
|
152
|
+
if (!effects.envFileExists()) return {
|
|
153
|
+
ok: false,
|
|
154
|
+
detail: "env file missing (run env-missing fix first)"
|
|
155
|
+
};
|
|
156
|
+
const real = realProviderKeys(effects.readEnvFile());
|
|
157
|
+
return {
|
|
158
|
+
ok: real.length > 0,
|
|
159
|
+
detail: real.length > 0 ? `found: ${real.join(", ")}` : "no provider key set"
|
|
160
|
+
};
|
|
161
|
+
},
|
|
162
|
+
fix: (ctx) => effects.openEditor(ctx.envPath)
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
id: "engine-version-mismatch",
|
|
166
|
+
message: "iii binary on PATH doesn't match the version agentmemory pins to.",
|
|
167
|
+
fixPreview: "Re-run the iii installer for the pinned version and restart the engine.",
|
|
168
|
+
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.",
|
|
169
|
+
check: async (ctx) => {
|
|
170
|
+
const bin = effects.findIiiBinary();
|
|
171
|
+
if (!bin) return {
|
|
172
|
+
ok: false,
|
|
173
|
+
detail: "iii not on PATH"
|
|
174
|
+
};
|
|
175
|
+
const v = effects.iiiBinaryVersion(bin);
|
|
176
|
+
if (!v) return {
|
|
177
|
+
ok: false,
|
|
178
|
+
detail: "iii on PATH but --version failed"
|
|
179
|
+
};
|
|
180
|
+
return {
|
|
181
|
+
ok: v === ctx.pinnedVersion,
|
|
182
|
+
detail: `${v} (pinned ${ctx.pinnedVersion})`
|
|
183
|
+
};
|
|
184
|
+
},
|
|
185
|
+
fix: async () => {
|
|
186
|
+
const r = await effects.runIiiInstaller();
|
|
187
|
+
if (!r.ok) return r;
|
|
188
|
+
await effects.runStop();
|
|
189
|
+
return effects.runStart();
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
id: "viewer-unreachable",
|
|
194
|
+
message: "Viewer port not reachable.",
|
|
195
|
+
fixPreview: "Stop the engine, restart it, and retry the viewer probe.",
|
|
196
|
+
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.",
|
|
197
|
+
check: async () => ({
|
|
198
|
+
ok: await effects.viewerReachable(),
|
|
199
|
+
detail: void 0
|
|
200
|
+
}),
|
|
201
|
+
fix: async () => {
|
|
202
|
+
const stopped = await effects.runStop();
|
|
203
|
+
if (!stopped.ok) return stopped;
|
|
204
|
+
return effects.runStart();
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
id: "stale-pidfile",
|
|
209
|
+
message: "Stale pidfile: pid recorded but the process is gone.",
|
|
210
|
+
fixPreview: "Clear ~/.agentmemory/iii.pid + engine-state.json, then restart.",
|
|
211
|
+
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.",
|
|
212
|
+
check: async () => {
|
|
213
|
+
if (!effects.pidfileExists()) return {
|
|
214
|
+
ok: true,
|
|
215
|
+
detail: "no pidfile"
|
|
216
|
+
};
|
|
217
|
+
const alive = effects.pidfilePidIsAlive();
|
|
218
|
+
if (alive === null) return {
|
|
219
|
+
ok: true,
|
|
220
|
+
detail: "pidfile unreadable"
|
|
221
|
+
};
|
|
222
|
+
return {
|
|
223
|
+
ok: alive,
|
|
224
|
+
detail: alive ? "pid is alive" : "pid is gone"
|
|
225
|
+
};
|
|
226
|
+
},
|
|
227
|
+
fix: async () => {
|
|
228
|
+
effects.clearEnginePidAndState();
|
|
229
|
+
return effects.runStart();
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
id: "env-placeholder-keys",
|
|
234
|
+
message: "~/.agentmemory/.env contains placeholder/empty API keys.",
|
|
235
|
+
fixPreview: "Open ~/.agentmemory/.env in $EDITOR to paste real values.",
|
|
236
|
+
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.",
|
|
237
|
+
check: async () => {
|
|
238
|
+
if (!effects.envFileExists()) return {
|
|
239
|
+
ok: true,
|
|
240
|
+
detail: "env file missing (handled by env-missing)"
|
|
241
|
+
};
|
|
242
|
+
const placeholders = placeholderProviderKeys(effects.readEnvFile());
|
|
243
|
+
return {
|
|
244
|
+
ok: placeholders.length === 0,
|
|
245
|
+
detail: placeholders.length === 0 ? void 0 : `placeholder: ${placeholders.join(", ")}`
|
|
246
|
+
};
|
|
247
|
+
},
|
|
248
|
+
fix: (ctx) => effects.openEditor(ctx.envPath)
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
id: "iii-on-path-not-local-bin",
|
|
252
|
+
message: "iii is on PATH but not in ~/.local/bin/iii (where we install).",
|
|
253
|
+
fixPreview: "Suggest re-installing the pinned version via the installer — won't touch your PATH.",
|
|
254
|
+
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.",
|
|
255
|
+
manualOnly: true,
|
|
256
|
+
check: async () => {
|
|
257
|
+
const bin = effects.findIiiBinary();
|
|
258
|
+
if (!bin) return {
|
|
259
|
+
ok: true,
|
|
260
|
+
detail: "iii not on PATH (handled elsewhere)"
|
|
261
|
+
};
|
|
262
|
+
const localBin = effects.localBinIiiPath();
|
|
263
|
+
return {
|
|
264
|
+
ok: bin === localBin,
|
|
265
|
+
detail: bin === localBin ? void 0 : `iii at: ${bin}`
|
|
266
|
+
};
|
|
267
|
+
},
|
|
268
|
+
fix: async () => effects.runIiiInstaller().then((r) => ({
|
|
269
|
+
ok: r.ok,
|
|
270
|
+
message: r.message ?? "Installer wrote to ~/.local/bin/iii. Your PATH wasn't modified — adjust it yourself if needed."
|
|
271
|
+
}))
|
|
272
|
+
}
|
|
273
|
+
];
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Dry-run output: each failing check's fix preview, prefixed by the diagnostic
|
|
277
|
+
* message. Pure function so we can snapshot-test the format.
|
|
278
|
+
*/
|
|
279
|
+
function dryRunPlan(ctx, results) {
|
|
280
|
+
const lines = [];
|
|
281
|
+
let n = 0;
|
|
282
|
+
for (const { diagnostic, status } of results) {
|
|
283
|
+
if (status.ok) continue;
|
|
284
|
+
n++;
|
|
285
|
+
lines.push(`${n}. [${diagnostic.id}] ${diagnostic.message}`);
|
|
286
|
+
lines.push(` would fix: ${diagnostic.fixPreview}`);
|
|
287
|
+
if (status.detail) lines.push(` detail: ${status.detail}`);
|
|
288
|
+
}
|
|
289
|
+
if (lines.length === 0) lines.push(`All checks passing for ${ctx.baseUrl} — no fixes to run.`);
|
|
290
|
+
return lines;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
//#endregion
|
|
294
|
+
//#region src/cli/remove-plan.ts
|
|
295
|
+
function pidfilePath(home) {
|
|
296
|
+
return join(home, ".agentmemory", "iii.pid");
|
|
297
|
+
}
|
|
298
|
+
function enginePath(home) {
|
|
299
|
+
return join(home, ".agentmemory", "engine-state.json");
|
|
300
|
+
}
|
|
301
|
+
function envPath(home) {
|
|
302
|
+
return join(home, ".agentmemory", ".env");
|
|
303
|
+
}
|
|
304
|
+
function preferencesPath(home) {
|
|
305
|
+
return join(home, ".agentmemory", "preferences.json");
|
|
306
|
+
}
|
|
307
|
+
function backupsDir(home) {
|
|
308
|
+
return join(home, ".agentmemory", "backups");
|
|
309
|
+
}
|
|
310
|
+
function dataDir(home) {
|
|
311
|
+
return join(home, ".agentmemory", "data");
|
|
312
|
+
}
|
|
313
|
+
function localBinIii(home) {
|
|
314
|
+
return join(home, ".local", "bin", "iii");
|
|
315
|
+
}
|
|
316
|
+
function safeSize(path) {
|
|
317
|
+
try {
|
|
318
|
+
return statSync(path).size;
|
|
319
|
+
} catch {
|
|
320
|
+
return -1;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
function pathExists(path) {
|
|
324
|
+
try {
|
|
325
|
+
return existsSync(path);
|
|
326
|
+
} catch {
|
|
327
|
+
return false;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Build the destruction plan for `agentmemory remove`.
|
|
332
|
+
*
|
|
333
|
+
* Plan items are returned regardless of whether `applicable` is true — the
|
|
334
|
+
* caller can decide whether to skip-and-log or hide entirely. This keeps
|
|
335
|
+
* the structure stable for tests.
|
|
336
|
+
*/
|
|
337
|
+
function buildRemovePlan(ctx, options) {
|
|
338
|
+
const { home, pinnedVersion, localBinIiiVersion, connectManifest } = ctx;
|
|
339
|
+
const plan = [];
|
|
340
|
+
plan.push({
|
|
341
|
+
id: "stop-engine",
|
|
342
|
+
description: "Stop running iii-engine (if any) cleanly",
|
|
343
|
+
path: null,
|
|
344
|
+
alwaysAsk: false,
|
|
345
|
+
applicable: pathExists(pidfilePath(home)) || pathExists(enginePath(home)),
|
|
346
|
+
sizeBytes: -1
|
|
347
|
+
});
|
|
348
|
+
plan.push({
|
|
349
|
+
id: "pidfile",
|
|
350
|
+
description: "Delete pidfile",
|
|
351
|
+
path: pidfilePath(home),
|
|
352
|
+
alwaysAsk: false,
|
|
353
|
+
applicable: pathExists(pidfilePath(home)),
|
|
354
|
+
sizeBytes: safeSize(pidfilePath(home))
|
|
355
|
+
});
|
|
356
|
+
plan.push({
|
|
357
|
+
id: "engine-state",
|
|
358
|
+
description: "Delete engine-state.json",
|
|
359
|
+
path: enginePath(home),
|
|
360
|
+
alwaysAsk: false,
|
|
361
|
+
applicable: pathExists(enginePath(home)),
|
|
362
|
+
sizeBytes: safeSize(enginePath(home))
|
|
363
|
+
});
|
|
364
|
+
plan.push({
|
|
365
|
+
id: "env",
|
|
366
|
+
description: "Delete .env (your API keys) — will ask separately",
|
|
367
|
+
path: envPath(home),
|
|
368
|
+
alwaysAsk: true,
|
|
369
|
+
applicable: !options.keepData && pathExists(envPath(home)),
|
|
370
|
+
sizeBytes: safeSize(envPath(home))
|
|
371
|
+
});
|
|
372
|
+
plan.push({
|
|
373
|
+
id: "preferences",
|
|
374
|
+
description: "Delete preferences.json",
|
|
375
|
+
path: preferencesPath(home),
|
|
376
|
+
alwaysAsk: false,
|
|
377
|
+
applicable: !options.keepData && pathExists(preferencesPath(home)),
|
|
378
|
+
sizeBytes: safeSize(preferencesPath(home))
|
|
379
|
+
});
|
|
380
|
+
plan.push({
|
|
381
|
+
id: "backups",
|
|
382
|
+
description: "Delete backups/ directory (connect manifest + backups)",
|
|
383
|
+
path: backupsDir(home),
|
|
384
|
+
alwaysAsk: false,
|
|
385
|
+
applicable: !options.keepData && pathExists(backupsDir(home)),
|
|
386
|
+
sizeBytes: -1
|
|
387
|
+
});
|
|
388
|
+
if (connectManifest?.installed?.length) for (const entry of connectManifest.installed) plan.push({
|
|
389
|
+
id: `connect:${entry.target}`,
|
|
390
|
+
description: `Remove agent connection (${entry.agent ?? "unknown"})`,
|
|
391
|
+
path: entry.target,
|
|
392
|
+
alwaysAsk: false,
|
|
393
|
+
applicable: pathExists(entry.target),
|
|
394
|
+
sizeBytes: safeSize(entry.target)
|
|
395
|
+
});
|
|
396
|
+
const localIii = localBinIii(home);
|
|
397
|
+
if (pathExists(localIii)) {
|
|
398
|
+
const matches = localBinIiiVersion === pinnedVersion;
|
|
399
|
+
plan.push({
|
|
400
|
+
id: "local-bin-iii",
|
|
401
|
+
description: matches ? `Delete ~/.local/bin/iii (matches pinned v${pinnedVersion})` : `Delete ~/.local/bin/iii (version ${localBinIiiVersion ?? "unknown"} != pinned v${pinnedVersion}) — will ask`,
|
|
402
|
+
path: localIii,
|
|
403
|
+
alwaysAsk: !matches,
|
|
404
|
+
applicable: true,
|
|
405
|
+
sizeBytes: safeSize(localIii)
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
plan.push({
|
|
409
|
+
id: "data-dir",
|
|
410
|
+
description: "Delete memory data directory (~/.agentmemory/data/) — will ask separately",
|
|
411
|
+
path: dataDir(home),
|
|
412
|
+
alwaysAsk: true,
|
|
413
|
+
applicable: !options.keepData && pathExists(dataDir(home)),
|
|
414
|
+
sizeBytes: -1
|
|
415
|
+
});
|
|
416
|
+
return plan;
|
|
417
|
+
}
|
|
418
|
+
/** Format a plan for the user — one line per item. */
|
|
419
|
+
function formatPlan(plan) {
|
|
420
|
+
return plan.filter((p) => p.applicable).map((p, i) => {
|
|
421
|
+
const tag = p.alwaysAsk ? " [asks]" : "";
|
|
422
|
+
const sz = p.sizeBytes > 0 ? ` (${humanBytes(p.sizeBytes)})` : "";
|
|
423
|
+
return ` ${i + 1}. ${p.description}${tag}${sz}${p.path ? `\n ${p.path}` : ""}`;
|
|
424
|
+
}).join("\n");
|
|
425
|
+
}
|
|
426
|
+
function humanBytes(n) {
|
|
427
|
+
if (n < 1024) return `${n} B`;
|
|
428
|
+
if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;
|
|
429
|
+
return `${(n / (1024 * 1024)).toFixed(1)} MB`;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
//#endregion
|
|
433
|
+
//#region src/cli/splash.ts
|
|
434
|
+
const IS_COLOR_TTY = !!process.stdout.isTTY && !process.env["NO_COLOR"];
|
|
435
|
+
function accent(s) {
|
|
436
|
+
return IS_COLOR_TTY ? `\x1b[38;5;208m${s}\x1b[0m` : s;
|
|
437
|
+
}
|
|
438
|
+
function dim(s) {
|
|
439
|
+
return IS_COLOR_TTY ? `\x1b[2m${s}\x1b[22m` : s;
|
|
440
|
+
}
|
|
441
|
+
function bold(s) {
|
|
442
|
+
return IS_COLOR_TTY ? `\x1b[1m${s}\x1b[22m` : s;
|
|
443
|
+
}
|
|
444
|
+
function getTerminalWidth() {
|
|
445
|
+
const w = process.stdout.columns;
|
|
446
|
+
return typeof w === "number" && w > 0 ? w : 80;
|
|
447
|
+
}
|
|
448
|
+
const TAGLINE = "Persistent memory for AI coding agents";
|
|
449
|
+
function fullBanner(version) {
|
|
450
|
+
const lines = ["", ...[
|
|
451
|
+
" _ ",
|
|
452
|
+
" __ _ __ _ ___ _ _ | |_ _ __ ___ _ __ ___ ___ _ __ _ _ ",
|
|
453
|
+
" / _` |/ _` |/ _ \\ '_\\| __| ' \\/ -_) ' \\ _ \\ / _ \\| '__| | | | ",
|
|
454
|
+
"| (_| | (_| | __/ | || |_| | | \\___| | | | | | (_) | | | |_| | ",
|
|
455
|
+
" \\__,_|\\__, |\\___|_| \\__|_| |_| |_| |_| |_|\\___/|_| \\__, | ",
|
|
456
|
+
" |___/ |___/ "
|
|
457
|
+
].map((line) => " " + accent(line))];
|
|
458
|
+
lines.push("");
|
|
459
|
+
lines.push(" " + bold(TAGLINE) + " " + dim(`v${version}`));
|
|
460
|
+
lines.push("");
|
|
461
|
+
return lines.join("\n");
|
|
462
|
+
}
|
|
463
|
+
function compactBanner(version) {
|
|
464
|
+
return [
|
|
465
|
+
"",
|
|
466
|
+
" " + bold(accent("agentmemory")),
|
|
467
|
+
" " + dim(`v${version} · ${TAGLINE}`),
|
|
468
|
+
""
|
|
469
|
+
].join("\n");
|
|
470
|
+
}
|
|
471
|
+
function minimalBanner(version) {
|
|
472
|
+
return `${accent("agentmemory")} ${dim(`v${version}`)}`;
|
|
473
|
+
}
|
|
474
|
+
function renderSplash(version) {
|
|
475
|
+
const width = getTerminalWidth();
|
|
476
|
+
let out;
|
|
477
|
+
if (width >= 120) out = fullBanner(version);
|
|
478
|
+
else if (width >= 80) out = compactBanner(version);
|
|
479
|
+
else out = minimalBanner(version);
|
|
480
|
+
process.stdout.write(out + "\n");
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
//#endregion
|
|
484
|
+
//#region src/cli/preferences.ts
|
|
485
|
+
const DEFAULTS = {
|
|
486
|
+
schemaVersion: 1,
|
|
487
|
+
lastAgent: null,
|
|
488
|
+
lastAgents: [],
|
|
489
|
+
lastProvider: null,
|
|
490
|
+
skipSplash: false,
|
|
491
|
+
skipNpxHint: false,
|
|
492
|
+
firstRunAt: null
|
|
493
|
+
};
|
|
494
|
+
function prefsDir() {
|
|
495
|
+
return join(homedir(), ".agentmemory");
|
|
496
|
+
}
|
|
497
|
+
function prefsPath() {
|
|
498
|
+
return join(prefsDir(), "preferences.json");
|
|
499
|
+
}
|
|
500
|
+
function readPrefs() {
|
|
501
|
+
try {
|
|
502
|
+
if (!existsSync(prefsPath())) return { ...DEFAULTS };
|
|
503
|
+
const raw = readFileSync(prefsPath(), "utf-8");
|
|
504
|
+
const parsed = JSON.parse(raw);
|
|
505
|
+
return {
|
|
506
|
+
...DEFAULTS,
|
|
507
|
+
...parsed,
|
|
508
|
+
schemaVersion: 1
|
|
509
|
+
};
|
|
510
|
+
} catch {
|
|
511
|
+
return { ...DEFAULTS };
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
function writePrefs(p) {
|
|
515
|
+
try {
|
|
516
|
+
mkdirSync(prefsDir(), { recursive: true });
|
|
517
|
+
const next = {
|
|
518
|
+
...readPrefs(),
|
|
519
|
+
...p,
|
|
520
|
+
schemaVersion: 1
|
|
521
|
+
};
|
|
522
|
+
const target = prefsPath();
|
|
523
|
+
const tmp = target + ".tmp";
|
|
524
|
+
const fd = openSync(tmp, "w", 384);
|
|
525
|
+
try {
|
|
526
|
+
writeSync(fd, JSON.stringify(next, null, 2) + "\n");
|
|
527
|
+
try {
|
|
528
|
+
fsyncSync(fd);
|
|
529
|
+
} catch {}
|
|
530
|
+
} finally {
|
|
531
|
+
closeSync(fd);
|
|
532
|
+
}
|
|
533
|
+
renameSync(tmp, target);
|
|
534
|
+
} catch {}
|
|
535
|
+
}
|
|
536
|
+
function resetPrefs() {
|
|
537
|
+
try {
|
|
538
|
+
unlinkSync(prefsPath());
|
|
539
|
+
} catch {}
|
|
540
|
+
}
|
|
541
|
+
function isFirstRun() {
|
|
542
|
+
if (!existsSync(prefsPath())) return true;
|
|
543
|
+
return readPrefs().firstRunAt === null;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
//#endregion
|
|
547
|
+
//#region src/cli/onboarding.ts
|
|
548
|
+
const __dirname$1 = dirname(fileURLToPath(import.meta.url));
|
|
549
|
+
const NATIVE_AGENTS = [
|
|
550
|
+
{
|
|
551
|
+
value: "claude-code",
|
|
552
|
+
label: "Claude Code",
|
|
553
|
+
glyph: "⟁"
|
|
554
|
+
},
|
|
555
|
+
{
|
|
556
|
+
value: "codex",
|
|
557
|
+
label: "Codex",
|
|
558
|
+
glyph: "◎"
|
|
559
|
+
},
|
|
560
|
+
{
|
|
561
|
+
value: "openhuman",
|
|
562
|
+
label: "OpenHuman",
|
|
563
|
+
glyph: "◇"
|
|
564
|
+
},
|
|
565
|
+
{
|
|
566
|
+
value: "openclaw",
|
|
567
|
+
label: "OpenClaw",
|
|
568
|
+
glyph: "◇"
|
|
569
|
+
},
|
|
570
|
+
{
|
|
571
|
+
value: "hermes",
|
|
572
|
+
label: "Hermes",
|
|
573
|
+
glyph: "◇"
|
|
574
|
+
},
|
|
575
|
+
{
|
|
576
|
+
value: "pi",
|
|
577
|
+
label: "Pi",
|
|
578
|
+
glyph: "◇"
|
|
579
|
+
},
|
|
580
|
+
{
|
|
581
|
+
value: "cursor",
|
|
582
|
+
label: "Cursor",
|
|
583
|
+
glyph: "◫"
|
|
584
|
+
},
|
|
585
|
+
{
|
|
586
|
+
value: "gemini-cli",
|
|
587
|
+
label: "Gemini CLI",
|
|
588
|
+
glyph: "✦"
|
|
589
|
+
}
|
|
590
|
+
];
|
|
591
|
+
const MCP_AGENTS = [
|
|
592
|
+
{
|
|
593
|
+
value: "opencode",
|
|
594
|
+
label: "OpenCode",
|
|
595
|
+
glyph: "⬡"
|
|
596
|
+
},
|
|
597
|
+
{
|
|
598
|
+
value: "cline",
|
|
599
|
+
label: "Cline",
|
|
600
|
+
glyph: "◇"
|
|
601
|
+
},
|
|
602
|
+
{
|
|
603
|
+
value: "goose",
|
|
604
|
+
label: "Goose",
|
|
605
|
+
glyph: "◇"
|
|
606
|
+
},
|
|
607
|
+
{
|
|
608
|
+
value: "kilo",
|
|
609
|
+
label: "Kilo",
|
|
610
|
+
glyph: "◇"
|
|
611
|
+
},
|
|
612
|
+
{
|
|
613
|
+
value: "aider",
|
|
614
|
+
label: "Aider",
|
|
615
|
+
glyph: "◇"
|
|
616
|
+
},
|
|
617
|
+
{
|
|
618
|
+
value: "claude-desktop",
|
|
619
|
+
label: "Claude Desktop",
|
|
620
|
+
glyph: "⟁"
|
|
621
|
+
},
|
|
622
|
+
{
|
|
623
|
+
value: "windsurf",
|
|
624
|
+
label: "Windsurf",
|
|
625
|
+
glyph: "◇"
|
|
626
|
+
},
|
|
627
|
+
{
|
|
628
|
+
value: "roo",
|
|
629
|
+
label: "Roo",
|
|
630
|
+
glyph: "◇"
|
|
631
|
+
}
|
|
632
|
+
];
|
|
633
|
+
const PROVIDERS = [
|
|
634
|
+
{
|
|
635
|
+
value: "anthropic",
|
|
636
|
+
label: "Anthropic — claude",
|
|
637
|
+
envKey: "ANTHROPIC_API_KEY"
|
|
638
|
+
},
|
|
639
|
+
{
|
|
640
|
+
value: "openai",
|
|
641
|
+
label: "OpenAI — gpt",
|
|
642
|
+
envKey: "OPENAI_API_KEY"
|
|
643
|
+
},
|
|
644
|
+
{
|
|
645
|
+
value: "gemini",
|
|
646
|
+
label: "Google — gemini",
|
|
647
|
+
envKey: "GEMINI_API_KEY"
|
|
648
|
+
},
|
|
649
|
+
{
|
|
650
|
+
value: "openrouter",
|
|
651
|
+
label: "OpenRouter — multi-model",
|
|
652
|
+
envKey: "OPENROUTER_API_KEY"
|
|
653
|
+
},
|
|
654
|
+
{
|
|
655
|
+
value: "minimax",
|
|
656
|
+
label: "MiniMax — minimax-m1",
|
|
657
|
+
envKey: "MINIMAX_API_KEY"
|
|
658
|
+
},
|
|
659
|
+
{
|
|
660
|
+
value: "skip",
|
|
661
|
+
label: "Skip — BM25-only mode (no LLM key)",
|
|
662
|
+
envKey: null
|
|
663
|
+
}
|
|
664
|
+
];
|
|
665
|
+
function buildAgentOptions() {
|
|
666
|
+
return [...NATIVE_AGENTS.map((a) => ({
|
|
667
|
+
value: a.value,
|
|
668
|
+
label: `${a.glyph} ${a.label}`,
|
|
669
|
+
hint: "native plugin"
|
|
670
|
+
})), ...MCP_AGENTS.map((a) => ({
|
|
671
|
+
value: a.value,
|
|
672
|
+
label: `${a.glyph} ${a.label}`,
|
|
673
|
+
hint: "MCP server"
|
|
674
|
+
}))];
|
|
675
|
+
}
|
|
676
|
+
function findEnvExample$1() {
|
|
677
|
+
const candidates = [
|
|
678
|
+
join(__dirname$1, "..", "..", ".env.example"),
|
|
679
|
+
join(__dirname$1, "..", ".env.example"),
|
|
680
|
+
join(__dirname$1, ".env.example"),
|
|
681
|
+
join(process.cwd(), ".env.example")
|
|
682
|
+
];
|
|
683
|
+
for (const c of candidates) if (existsSync(c)) return c;
|
|
684
|
+
return null;
|
|
685
|
+
}
|
|
686
|
+
async function seedEnvFile(provider) {
|
|
687
|
+
const target = join(homedir(), ".agentmemory", ".env");
|
|
688
|
+
await mkdir(dirname(target), { recursive: true });
|
|
689
|
+
const template = findEnvExample$1();
|
|
690
|
+
if (template && !existsSync(target)) try {
|
|
691
|
+
await copyFile(template, target, constants.COPYFILE_EXCL);
|
|
692
|
+
} catch (err) {
|
|
693
|
+
if (err?.code !== "EEXIST") return null;
|
|
694
|
+
}
|
|
695
|
+
else if (!template && !existsSync(target)) {
|
|
696
|
+
const lines = [
|
|
697
|
+
"# agentmemory environment — uncomment what you need",
|
|
698
|
+
"# AGENTMEMORY_URL=http://localhost:3111",
|
|
699
|
+
""
|
|
700
|
+
];
|
|
701
|
+
const envKey = PROVIDERS.find((x) => x.value === provider)?.envKey;
|
|
702
|
+
if (envKey) lines.push(`# ${envKey}=`);
|
|
703
|
+
writeFileSync(target, lines.join("\n"), { mode: 384 });
|
|
704
|
+
}
|
|
705
|
+
return target;
|
|
706
|
+
}
|
|
707
|
+
async function runOnboarding() {
|
|
708
|
+
p.note([
|
|
709
|
+
"Welcome to agentmemory.",
|
|
710
|
+
"",
|
|
711
|
+
"Persistent memory for your AI coding agents. We'll pick which",
|
|
712
|
+
"agents to wire up and which provider (if any) handles compression",
|
|
713
|
+
"and consolidation. Either step can be changed later in ~/.agentmemory/.env."
|
|
714
|
+
].join("\n"), "first-run setup");
|
|
715
|
+
const agentsPicked = await p.multiselect({
|
|
716
|
+
message: "Which agents will use agentmemory? (space to toggle, enter to confirm)",
|
|
717
|
+
options: buildAgentOptions(),
|
|
718
|
+
required: false,
|
|
719
|
+
initialValues: ["claude-code"]
|
|
720
|
+
});
|
|
721
|
+
if (p.isCancel(agentsPicked)) {
|
|
722
|
+
p.cancel("Setup cancelled. Re-run any time with: agentmemory --reset");
|
|
723
|
+
process.exit(0);
|
|
724
|
+
}
|
|
725
|
+
const providerPicked = await p.select({
|
|
726
|
+
message: "Which LLM provider should agentmemory use for compress/consolidate?",
|
|
727
|
+
options: PROVIDERS.map(({ value, label }) => ({
|
|
728
|
+
value,
|
|
729
|
+
label
|
|
730
|
+
})),
|
|
731
|
+
initialValue: "anthropic"
|
|
732
|
+
});
|
|
733
|
+
if (p.isCancel(providerPicked)) {
|
|
734
|
+
p.cancel("Setup cancelled. Re-run any time with: agentmemory --reset");
|
|
735
|
+
process.exit(0);
|
|
736
|
+
}
|
|
737
|
+
const provider = providerPicked === "skip" ? null : providerPicked;
|
|
738
|
+
const agents = agentsPicked ?? [];
|
|
739
|
+
const envPath = await seedEnvFile(provider);
|
|
740
|
+
writePrefs({
|
|
741
|
+
lastAgent: agents[0] ?? null,
|
|
742
|
+
lastAgents: agents,
|
|
743
|
+
lastProvider: provider,
|
|
744
|
+
skipSplash: true,
|
|
745
|
+
firstRunAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
746
|
+
});
|
|
747
|
+
const lines = [`✓ Saved preferences to ${join(homedir(), ".agentmemory", "preferences.json")}`];
|
|
748
|
+
if (envPath) lines.push(`✓ Wrote ${envPath} (edit to add your API key)`);
|
|
749
|
+
else lines.push(`! Could not write ~/.agentmemory/.env — run \`agentmemory init\` after this completes.`);
|
|
750
|
+
if (provider) {
|
|
751
|
+
const envKey = PROVIDERS.find((x) => x.value === provider)?.envKey;
|
|
752
|
+
if (envKey) lines.push(` Uncomment ${envKey}= in that file to enable ${provider}.`);
|
|
753
|
+
} else lines.push(" No provider chosen — agentmemory will run in BM25-only mode.");
|
|
754
|
+
p.note(lines.join("\n"), "ready");
|
|
755
|
+
return {
|
|
756
|
+
agents,
|
|
757
|
+
provider
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
//#endregion
|
|
762
|
+
//#region src/logger.ts
|
|
763
|
+
function fmt(level, msg, fields) {
|
|
764
|
+
if (!fields || Object.keys(fields).length === 0) return `[agentmemory] ${level} ${msg}`;
|
|
765
|
+
try {
|
|
766
|
+
return `[agentmemory] ${level} ${msg} ${JSON.stringify(fields)}`;
|
|
767
|
+
} catch {
|
|
768
|
+
return `[agentmemory] ${level} ${msg}`;
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
function emit(level, msg, fields) {
|
|
772
|
+
try {
|
|
773
|
+
process.stderr.write(fmt(level, msg, fields) + "\n");
|
|
774
|
+
} catch {}
|
|
775
|
+
}
|
|
776
|
+
const logger = {
|
|
777
|
+
info(msg, fields) {
|
|
778
|
+
emit("info", msg, fields);
|
|
779
|
+
},
|
|
780
|
+
warn(msg, fields) {
|
|
781
|
+
emit("warn", msg, fields);
|
|
782
|
+
},
|
|
783
|
+
error(msg, fields) {
|
|
784
|
+
emit("error", msg, fields);
|
|
785
|
+
}
|
|
786
|
+
};
|
|
787
|
+
let bootVerbose = process.env["AGENTMEMORY_VERBOSE"] === "1" || process.env["AGENTMEMORY_VERBOSE"] === "true";
|
|
788
|
+
const bootBuffer = [];
|
|
789
|
+
function setBootVerbose(enabled) {
|
|
790
|
+
bootVerbose = enabled;
|
|
791
|
+
}
|
|
792
|
+
function bootLog(msg) {
|
|
793
|
+
if (bootVerbose) {
|
|
794
|
+
try {
|
|
795
|
+
process.stderr.write(`[agentmemory] ${msg}\n`);
|
|
796
|
+
} catch {}
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
if (bootBuffer.length < 500) bootBuffer.push(msg);
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
//#endregion
|
|
803
|
+
//#region src/version.ts
|
|
804
|
+
const VERSION = "0.9.15";
|
|
805
|
+
|
|
78
806
|
//#endregion
|
|
79
807
|
//#region src/cli.ts
|
|
80
808
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
81
809
|
const args = process.argv.slice(2);
|
|
82
810
|
const IS_WINDOWS = platform() === "win32";
|
|
83
|
-
const IS_VERBOSE = args.includes("--verbose") || args.includes("-v");
|
|
811
|
+
const IS_VERBOSE = args.includes("--verbose") || args.includes("-v") || process.env["AGENTMEMORY_VERBOSE"] === "1" || process.env["AGENTMEMORY_VERBOSE"] === "true";
|
|
812
|
+
setBootVerbose(IS_VERBOSE);
|
|
813
|
+
const IS_RESET = args.includes("--reset");
|
|
84
814
|
const IIPINNED_VERSION = process.env["AGENTMEMORY_III_VERSION"] || "0.11.2";
|
|
85
815
|
function iiiReleaseAsset() {
|
|
86
816
|
const p = platform();
|
|
@@ -111,11 +841,22 @@ Usage: agentmemory [command] [options]
|
|
|
111
841
|
Commands:
|
|
112
842
|
(default) Start agentmemory worker
|
|
113
843
|
init Copy bundled .env.example to ~/.agentmemory/.env if absent
|
|
844
|
+
connect [agent] Wire agentmemory into an installed agent (claude-code, codex,
|
|
845
|
+
cursor, gemini-cli, openclaw, hermes, pi, openhuman).
|
|
846
|
+
No arg = interactive picker. --all wires every detected agent.
|
|
847
|
+
--dry-run shows what would change. --force re-installs.
|
|
114
848
|
status Show connection status, memory count, flags, and health
|
|
115
|
-
doctor
|
|
849
|
+
doctor Interactive diagnostic + fixer. [F]ix · [S]kip · [?]more · [Q]uit
|
|
850
|
+
--all: apply every fix without prompting (CI)
|
|
851
|
+
--dry-run: show what each fix would do, don't execute
|
|
852
|
+
remove Cleanly uninstall agentmemory (pidfile, state, .env, binaries).
|
|
853
|
+
--force: skip confirmations · --keep-data: keep memory data
|
|
116
854
|
demo Seed sample sessions and show recall in action
|
|
117
855
|
upgrade Upgrade local deps + iii runtime (best effort)
|
|
118
|
-
stop
|
|
856
|
+
stop [--force] Stop the running iii-engine started by this CLI.
|
|
857
|
+
--force bypasses the Docker-heuristic guard and signals
|
|
858
|
+
whatever pidfile+lsof report on the REST port (use when
|
|
859
|
+
the engine was started natively but state file is missing).
|
|
119
860
|
mcp Start standalone MCP server (no engine required)
|
|
120
861
|
import-jsonl [p] Import Claude Code JSONL transcripts (default: ~/.claude/projects)
|
|
121
862
|
--max-files <N> | --max-files=<N>: override scan cap (default 200, max 1000;
|
|
@@ -123,7 +864,8 @@ Commands:
|
|
|
123
864
|
|
|
124
865
|
Options:
|
|
125
866
|
--help, -h Show this help
|
|
126
|
-
--verbose, -v Show engine stderr and diagnostic info
|
|
867
|
+
--verbose, -v Show engine stderr, boot log, and diagnostic info
|
|
868
|
+
--reset Wipe ~/.agentmemory/preferences.json and re-run onboarding
|
|
127
869
|
--tools all|core Tool visibility (default: core = 7 tools)
|
|
128
870
|
--no-engine Skip auto-starting iii-engine
|
|
129
871
|
--port <N> Override REST port (default: 3111)
|
|
@@ -240,6 +982,16 @@ function iiiBinVersion(binPath) {
|
|
|
240
982
|
return null;
|
|
241
983
|
}
|
|
242
984
|
}
|
|
985
|
+
let warnedVersionMismatch = false;
|
|
986
|
+
function warnIfEngineVersionMismatch(iiiBinPath) {
|
|
987
|
+
if (!iiiBinPath || warnedVersionMismatch) return;
|
|
988
|
+
const detected = iiiBinVersion(iiiBinPath);
|
|
989
|
+
if (!detected || detected === IIPINNED_VERSION) return;
|
|
990
|
+
warnedVersionMismatch = true;
|
|
991
|
+
const asset = iiiReleaseAsset();
|
|
992
|
+
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}`;
|
|
993
|
+
p.log.warn(`iii-engine on PATH is v${detected} but agentmemory v0.9.14+ pins v${IIPINNED_VERSION}. Set AGENTMEMORY_III_VERSION=${detected} to silence, or downgrade with: \`${downloadHint}\``);
|
|
994
|
+
}
|
|
243
995
|
function enginePidfilePath() {
|
|
244
996
|
return join(homedir(), ".agentmemory", "iii.pid");
|
|
245
997
|
}
|
|
@@ -300,6 +1052,45 @@ function discoverComposeFile() {
|
|
|
300
1052
|
join(process.cwd(), "docker-compose.yml")
|
|
301
1053
|
].find((c) => existsSync(c)) ?? null;
|
|
302
1054
|
}
|
|
1055
|
+
function isInvokedViaNpx() {
|
|
1056
|
+
if (process.env["npm_lifecycle_event"] === "npx") return true;
|
|
1057
|
+
if ((process.argv[1] ?? "").includes("_npx")) return true;
|
|
1058
|
+
const ua = process.env["npm_config_user_agent"] ?? "";
|
|
1059
|
+
if (ua.startsWith("npm/") || ua.includes(" npm/")) return true;
|
|
1060
|
+
return false;
|
|
1061
|
+
}
|
|
1062
|
+
function shouldSkipNpxHint() {
|
|
1063
|
+
try {
|
|
1064
|
+
const prefsPath = join(homedir(), ".agentmemory", "preferences.json");
|
|
1065
|
+
if (!existsSync(prefsPath)) return false;
|
|
1066
|
+
const raw = readFileSync(prefsPath, "utf-8");
|
|
1067
|
+
return JSON.parse(raw)?.skipNpxHint === true;
|
|
1068
|
+
} catch {
|
|
1069
|
+
return false;
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
function maybeEmitNpxHint() {
|
|
1073
|
+
if (!isInvokedViaNpx()) return;
|
|
1074
|
+
if (shouldSkipNpxHint()) return;
|
|
1075
|
+
p.log.info("Tip: install globally for the bare `agentmemory` command:\n npm install -g @agentmemory/agentmemory");
|
|
1076
|
+
}
|
|
1077
|
+
function adoptRunningEngine() {
|
|
1078
|
+
try {
|
|
1079
|
+
const existingState = readEngineState();
|
|
1080
|
+
const existingPid = readEnginePidfile();
|
|
1081
|
+
if (existingState && existingPid) return;
|
|
1082
|
+
const enginePid = findEnginePidsByPort(getRestPort())[0];
|
|
1083
|
+
if (enginePid && !existingPid) writeEnginePidfile(enginePid);
|
|
1084
|
+
if (!existingState) writeEngineState({
|
|
1085
|
+
kind: "native",
|
|
1086
|
+
configPath: findIiiConfig() || "",
|
|
1087
|
+
attached: true
|
|
1088
|
+
});
|
|
1089
|
+
if (enginePid && !existingPid) p.log.info(`Attached to existing iii-engine (pid ${enginePid})`);
|
|
1090
|
+
} catch (err) {
|
|
1091
|
+
vlog(`adoptRunningEngine: ${err instanceof Error ? err.message : String(err)}`);
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
303
1094
|
async function runIiiInstaller() {
|
|
304
1095
|
const releaseUrl = iiiReleaseUrl();
|
|
305
1096
|
const asset = iiiReleaseAsset();
|
|
@@ -389,6 +1180,7 @@ function spawnEngineBackground(bin, spawnArgs, label) {
|
|
|
389
1180
|
return child;
|
|
390
1181
|
}
|
|
391
1182
|
function startIiiBin(iiiBin, configPath) {
|
|
1183
|
+
warnIfEngineVersionMismatch(iiiBin);
|
|
392
1184
|
const s = p.spinner();
|
|
393
1185
|
s.start(`Starting iii-engine: ${iiiBin}`);
|
|
394
1186
|
writeEngineState({
|
|
@@ -550,16 +1342,37 @@ function installInstructions() {
|
|
|
550
1342
|
function portInUseDiagnostic(port) {
|
|
551
1343
|
return IS_WINDOWS ? ` netstat -ano | findstr :${port}` : ` lsof -i :${port} # or: ss -tlnp | grep :${port}`;
|
|
552
1344
|
}
|
|
1345
|
+
async function waitForAgentmemoryReady(timeoutMs) {
|
|
1346
|
+
const start = Date.now();
|
|
1347
|
+
while (Date.now() - start < timeoutMs) {
|
|
1348
|
+
if (await isAgentmemoryReady()) return true;
|
|
1349
|
+
await new Promise((r) => setTimeout(r, 250));
|
|
1350
|
+
}
|
|
1351
|
+
return false;
|
|
1352
|
+
}
|
|
1353
|
+
function printReadyHint() {
|
|
1354
|
+
const hint = `Memory ready on :${getRestPort()} · viewer on ${getViewerUrl()} · try: agentmemory demo`;
|
|
1355
|
+
process.stdout.write("\n" + hint + "\n");
|
|
1356
|
+
}
|
|
553
1357
|
async function main() {
|
|
554
|
-
|
|
1358
|
+
if (IS_RESET) resetPrefs();
|
|
1359
|
+
const firstRun = isFirstRun();
|
|
1360
|
+
const prefs = readPrefs();
|
|
1361
|
+
if (firstRun || IS_RESET || IS_VERBOSE || !prefs.skipSplash) renderSplash(VERSION);
|
|
1362
|
+
if (firstRun || IS_RESET) await runOnboarding();
|
|
555
1363
|
if (skipEngine) {
|
|
556
|
-
p.log.info("Skipping engine check (--no-engine)");
|
|
557
|
-
await import("./src-
|
|
1364
|
+
if (IS_VERBOSE) p.log.info("Skipping engine check (--no-engine)");
|
|
1365
|
+
await import("./src-BGcqJR1a.mjs");
|
|
1366
|
+
if (await waitForAgentmemoryReady(15e3)) printReadyHint();
|
|
558
1367
|
return;
|
|
559
1368
|
}
|
|
560
1369
|
if (await isEngineRunning()) {
|
|
561
|
-
p.log.success("iii-engine is running");
|
|
562
|
-
|
|
1370
|
+
if (IS_VERBOSE) p.log.success("iii-engine is running");
|
|
1371
|
+
warnIfEngineVersionMismatch(whichBinary("iii") ?? fallbackIiiPaths().find((p) => existsSync(p)) ?? null);
|
|
1372
|
+
adoptRunningEngine();
|
|
1373
|
+
maybeEmitNpxHint();
|
|
1374
|
+
await import("./src-BGcqJR1a.mjs");
|
|
1375
|
+
if (await waitForAgentmemoryReady(15e3)) printReadyHint();
|
|
563
1376
|
return;
|
|
564
1377
|
}
|
|
565
1378
|
if (!await startEngine()) {
|
|
@@ -603,7 +1416,10 @@ async function main() {
|
|
|
603
1416
|
process.exit(1);
|
|
604
1417
|
}
|
|
605
1418
|
s.stop("iii-engine is ready");
|
|
606
|
-
|
|
1419
|
+
maybeEmitNpxHint();
|
|
1420
|
+
await import("./src-BGcqJR1a.mjs");
|
|
1421
|
+
if (await waitForAgentmemoryReady(15e3)) printReadyHint();
|
|
1422
|
+
writePrefs({ skipSplash: true });
|
|
607
1423
|
}
|
|
608
1424
|
async function apiFetch(base, path, timeoutMs = 5e3) {
|
|
609
1425
|
try {
|
|
@@ -717,10 +1533,143 @@ function checkClaudeCodeHooks() {
|
|
|
717
1533
|
if (content.includes("Loading hooks from plugin: agentmemory")) return { state: "loaded" };
|
|
718
1534
|
return { state: "not-loaded" };
|
|
719
1535
|
}
|
|
720
|
-
|
|
721
|
-
|
|
1536
|
+
function buildDoctorContext() {
|
|
1537
|
+
return {
|
|
1538
|
+
baseUrl: getBaseUrl(),
|
|
1539
|
+
viewerUrl: getViewerUrl(),
|
|
1540
|
+
envPath: join(homedir(), ".agentmemory", ".env"),
|
|
1541
|
+
pidfilePath: enginePidfilePath(),
|
|
1542
|
+
enginePath: engineStatePath(),
|
|
1543
|
+
pinnedVersion: IIPINNED_VERSION
|
|
1544
|
+
};
|
|
1545
|
+
}
|
|
1546
|
+
function buildDoctorEffects() {
|
|
1547
|
+
return {
|
|
1548
|
+
envFileExists: () => existsSync(join(homedir(), ".agentmemory", ".env")),
|
|
1549
|
+
readEnvFile: () => {
|
|
1550
|
+
try {
|
|
1551
|
+
return parseEnvFile(readFileSync(join(homedir(), ".agentmemory", ".env"), "utf-8"));
|
|
1552
|
+
} catch {
|
|
1553
|
+
return {};
|
|
1554
|
+
}
|
|
1555
|
+
},
|
|
1556
|
+
pidfileExists: () => existsSync(enginePidfilePath()),
|
|
1557
|
+
pidfilePidIsAlive: () => {
|
|
1558
|
+
const pid = readEnginePidfile();
|
|
1559
|
+
if (pid === null) return null;
|
|
1560
|
+
return pidAlive(pid);
|
|
1561
|
+
},
|
|
1562
|
+
findIiiBinary: () => whichBinary("iii"),
|
|
1563
|
+
localBinIiiPath: () => join(homedir(), ".local", "bin", IS_WINDOWS ? "iii.exe" : "iii"),
|
|
1564
|
+
iiiBinaryVersion: (binPath) => iiiBinVersion(binPath),
|
|
1565
|
+
viewerReachable: async (timeoutMs = 2e3) => {
|
|
1566
|
+
try {
|
|
1567
|
+
return (await fetch(getViewerUrl(), { signal: AbortSignal.timeout(timeoutMs) })).ok;
|
|
1568
|
+
} catch {
|
|
1569
|
+
return false;
|
|
1570
|
+
}
|
|
1571
|
+
},
|
|
1572
|
+
runInit: async () => {
|
|
1573
|
+
try {
|
|
1574
|
+
await runInit();
|
|
1575
|
+
return {
|
|
1576
|
+
ok: true,
|
|
1577
|
+
message: "Wrote ~/.agentmemory/.env"
|
|
1578
|
+
};
|
|
1579
|
+
} catch (err) {
|
|
1580
|
+
return {
|
|
1581
|
+
ok: false,
|
|
1582
|
+
message: err instanceof Error ? err.message : String(err)
|
|
1583
|
+
};
|
|
1584
|
+
}
|
|
1585
|
+
},
|
|
1586
|
+
openEditor: async (path) => {
|
|
1587
|
+
const editor = process.env["EDITOR"] || process.env["VISUAL"] || "nano";
|
|
1588
|
+
p.log.info(`Opening ${path} in ${editor}…`);
|
|
1589
|
+
try {
|
|
1590
|
+
const result = spawnSync(editor, [path], { stdio: "inherit" });
|
|
1591
|
+
if (result.error) return {
|
|
1592
|
+
ok: false,
|
|
1593
|
+
message: `Failed to launch ${editor}: ${result.error.message}`
|
|
1594
|
+
};
|
|
1595
|
+
if ((result.status ?? 0) !== 0) return {
|
|
1596
|
+
ok: false,
|
|
1597
|
+
message: `${editor} exited with code ${result.status}`
|
|
1598
|
+
};
|
|
1599
|
+
return {
|
|
1600
|
+
ok: true,
|
|
1601
|
+
message: `Saved ${path}`
|
|
1602
|
+
};
|
|
1603
|
+
} catch (err) {
|
|
1604
|
+
return {
|
|
1605
|
+
ok: false,
|
|
1606
|
+
message: err instanceof Error ? err.message : String(err)
|
|
1607
|
+
};
|
|
1608
|
+
}
|
|
1609
|
+
},
|
|
1610
|
+
runIiiInstaller: async () => {
|
|
1611
|
+
const r = await runIiiInstaller();
|
|
1612
|
+
return {
|
|
1613
|
+
ok: r.ok,
|
|
1614
|
+
message: r.ok ? `Installed iii v${IIPINNED_VERSION} to ${r.binPath}` : "iii installer failed (see warnings above)"
|
|
1615
|
+
};
|
|
1616
|
+
},
|
|
1617
|
+
runStop: async () => {
|
|
1618
|
+
try {
|
|
1619
|
+
const portPids = findEnginePidsByPort(getRestPort());
|
|
1620
|
+
const pidfilePid = readEnginePidfile();
|
|
1621
|
+
if (portPids.length === 0 && pidfilePid === null) {
|
|
1622
|
+
clearEnginePidfile();
|
|
1623
|
+
clearEngineState();
|
|
1624
|
+
return {
|
|
1625
|
+
ok: true,
|
|
1626
|
+
message: "Nothing to stop."
|
|
1627
|
+
};
|
|
1628
|
+
}
|
|
1629
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
1630
|
+
if (pidfilePid) candidates.add(pidfilePid);
|
|
1631
|
+
for (const pid of portPids) candidates.add(pid);
|
|
1632
|
+
let allStopped = true;
|
|
1633
|
+
for (const pid of candidates) if (!await signalAndWait(pid, "SIGTERM", 3e3)) allStopped = false;
|
|
1634
|
+
clearEnginePidfile();
|
|
1635
|
+
clearEngineState();
|
|
1636
|
+
return {
|
|
1637
|
+
ok: allStopped,
|
|
1638
|
+
message: allStopped ? "Engine stopped." : "Some engine pids survived."
|
|
1639
|
+
};
|
|
1640
|
+
} catch (err) {
|
|
1641
|
+
return {
|
|
1642
|
+
ok: false,
|
|
1643
|
+
message: err instanceof Error ? err.message : String(err)
|
|
1644
|
+
};
|
|
1645
|
+
}
|
|
1646
|
+
},
|
|
1647
|
+
runStart: async () => {
|
|
1648
|
+
try {
|
|
1649
|
+
if (!await startEngine()) return {
|
|
1650
|
+
ok: false,
|
|
1651
|
+
message: "startEngine() returned false"
|
|
1652
|
+
};
|
|
1653
|
+
const ready = await waitForEngine(15e3);
|
|
1654
|
+
return {
|
|
1655
|
+
ok: ready,
|
|
1656
|
+
message: ready ? "Engine ready" : "Engine did not become ready within 15s"
|
|
1657
|
+
};
|
|
1658
|
+
} catch (err) {
|
|
1659
|
+
return {
|
|
1660
|
+
ok: false,
|
|
1661
|
+
message: err instanceof Error ? err.message : String(err)
|
|
1662
|
+
};
|
|
1663
|
+
}
|
|
1664
|
+
},
|
|
1665
|
+
clearEnginePidAndState: () => {
|
|
1666
|
+
clearEnginePidfile();
|
|
1667
|
+
clearEngineState();
|
|
1668
|
+
}
|
|
1669
|
+
};
|
|
1670
|
+
}
|
|
1671
|
+
async function passiveServerChecks() {
|
|
722
1672
|
const base = getBaseUrl();
|
|
723
|
-
const viewerUrl = getViewerUrl();
|
|
724
1673
|
const checks = [];
|
|
725
1674
|
const serverUp = await isEngineRunning();
|
|
726
1675
|
checks.push({
|
|
@@ -728,16 +1677,12 @@ async function runDoctor() {
|
|
|
728
1677
|
ok: serverUp,
|
|
729
1678
|
hint: serverUp ? void 0 : `Start with: npx @agentmemory/agentmemory (tried ${base})`
|
|
730
1679
|
});
|
|
731
|
-
if (!serverUp)
|
|
732
|
-
p.note(formatChecks(checks), "server unreachable");
|
|
733
|
-
process.exit(1);
|
|
734
|
-
}
|
|
1680
|
+
if (!serverUp) return checks;
|
|
735
1681
|
const [health, flags, graph] = await Promise.all([
|
|
736
1682
|
apiFetch(base, "health", 3e3),
|
|
737
1683
|
apiFetch(base, "config/flags", 3e3),
|
|
738
1684
|
apiFetch(base, "graph/stats", 3e3)
|
|
739
1685
|
]);
|
|
740
|
-
const viewerUp = await fetch(viewerUrl, { signal: AbortSignal.timeout(2e3) }).then((r) => r.ok).catch(() => false);
|
|
741
1686
|
const hasLlm = flags?.provider === "llm";
|
|
742
1687
|
const hasEmbed = flags?.embeddingProvider === "embeddings";
|
|
743
1688
|
const graphHas = Number(graph?.totalNodes ?? graph?.nodes ?? graph?.nodeCount ?? 0) > 0;
|
|
@@ -745,18 +1690,14 @@ async function runDoctor() {
|
|
|
745
1690
|
name: "Health status",
|
|
746
1691
|
ok: health?.status === "healthy",
|
|
747
1692
|
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
1693
|
}, {
|
|
753
1694
|
name: "LLM provider",
|
|
754
1695
|
ok: hasLlm,
|
|
755
|
-
hint: hasLlm ? void 0 : "
|
|
1696
|
+
hint: hasLlm ? void 0 : "set ANTHROPIC_API_KEY (or GEMINI/OPENROUTER/MINIMAX) in ~/.agentmemory/.env"
|
|
756
1697
|
}, {
|
|
757
1698
|
name: "Embedding provider",
|
|
758
1699
|
ok: hasEmbed,
|
|
759
|
-
hint: hasEmbed ? void 0 : "Running BM25-only. Add OPENAI_API_KEY / VOYAGE_API_KEY / COHERE_API_KEY / OLLAMA_HOST
|
|
1700
|
+
hint: hasEmbed ? void 0 : "Running BM25-only. Add OPENAI_API_KEY / VOYAGE_API_KEY / COHERE_API_KEY / OLLAMA_HOST"
|
|
760
1701
|
});
|
|
761
1702
|
for (const f of flags?.flags || []) checks.push({
|
|
762
1703
|
name: f.label,
|
|
@@ -772,7 +1713,7 @@ async function runDoctor() {
|
|
|
772
1713
|
};
|
|
773
1714
|
case "not-loaded": return {
|
|
774
1715
|
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.
|
|
1716
|
+
hint: "Plugin enabled but hooks not loaded by Claude Code. Try: /plugin uninstall agentmemory@agentmemory && /plugin install agentmemory@agentmemory, then restart the session."
|
|
776
1717
|
};
|
|
777
1718
|
case "no-debug-log": return {
|
|
778
1719
|
ok: false,
|
|
@@ -788,16 +1729,136 @@ async function runDoctor() {
|
|
|
788
1729
|
checks.push({
|
|
789
1730
|
name: "Knowledge graph populated",
|
|
790
1731
|
ok: graphHas,
|
|
791
|
-
hint: graphHas ? void 0 : "Graph is empty. Run a session with GRAPH_EXTRACTION_ENABLED=true
|
|
1732
|
+
hint: graphHas ? void 0 : "Graph is empty. Run a session with GRAPH_EXTRACTION_ENABLED=true."
|
|
1733
|
+
});
|
|
1734
|
+
return checks;
|
|
1735
|
+
}
|
|
1736
|
+
async function askFixAction(d) {
|
|
1737
|
+
const choice = await p.select({
|
|
1738
|
+
message: `[${d.id}] ${d.message}`,
|
|
1739
|
+
options: [
|
|
1740
|
+
{
|
|
1741
|
+
value: "fix",
|
|
1742
|
+
label: "F Fix",
|
|
1743
|
+
hint: d.fixPreview
|
|
1744
|
+
},
|
|
1745
|
+
{
|
|
1746
|
+
value: "skip",
|
|
1747
|
+
label: "S Skip"
|
|
1748
|
+
},
|
|
1749
|
+
{
|
|
1750
|
+
value: "more",
|
|
1751
|
+
label: "? More info"
|
|
1752
|
+
},
|
|
1753
|
+
{
|
|
1754
|
+
value: "quit",
|
|
1755
|
+
label: "Q Quit doctor"
|
|
1756
|
+
}
|
|
1757
|
+
],
|
|
1758
|
+
initialValue: "fix"
|
|
792
1759
|
});
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
p.
|
|
1760
|
+
if (p.isCancel(choice)) return "quit";
|
|
1761
|
+
return choice;
|
|
1762
|
+
}
|
|
1763
|
+
async function applyFixWithReport(d, ctx, dryRun) {
|
|
1764
|
+
if (dryRun) {
|
|
1765
|
+
p.log.info(`[dry-run] would: ${d.fixPreview}`);
|
|
1766
|
+
return {
|
|
1767
|
+
ok: true,
|
|
1768
|
+
message: "(dry-run)"
|
|
1769
|
+
};
|
|
1770
|
+
}
|
|
1771
|
+
const result = await d.fix(ctx);
|
|
1772
|
+
if (result.ok) p.log.success(result.message ?? `${d.id} fixed.`);
|
|
1773
|
+
else p.log.error(result.message ?? `${d.id} fix failed.`);
|
|
1774
|
+
return result;
|
|
1775
|
+
}
|
|
1776
|
+
async function runDoctor() {
|
|
1777
|
+
p.intro("agentmemory doctor");
|
|
1778
|
+
const applyAll = args.includes("--all");
|
|
1779
|
+
const dryRun = args.includes("--dry-run");
|
|
1780
|
+
if (applyAll && dryRun) {
|
|
1781
|
+
p.log.error("Cannot combine --all and --dry-run.");
|
|
1782
|
+
process.exit(2);
|
|
1783
|
+
}
|
|
1784
|
+
const passive = await passiveServerChecks();
|
|
1785
|
+
const passivePassed = passive.filter((c) => c.ok).length;
|
|
1786
|
+
p.note(formatChecks(passive), `server: ${passivePassed}/${passive.length} passing`);
|
|
1787
|
+
const ctx = buildDoctorContext();
|
|
1788
|
+
const diagnostics = buildDiagnostics(buildDoctorEffects());
|
|
1789
|
+
if (dryRun) {
|
|
1790
|
+
const results = [];
|
|
1791
|
+
for (const d of diagnostics) results.push({
|
|
1792
|
+
diagnostic: d,
|
|
1793
|
+
status: await d.check(ctx)
|
|
1794
|
+
});
|
|
1795
|
+
const lines = dryRunPlan(ctx, results);
|
|
1796
|
+
p.note(lines.join("\n"), "dry-run plan");
|
|
1797
|
+
p.outro("Dry-run complete. Re-run without --dry-run to apply.");
|
|
1798
|
+
return;
|
|
1799
|
+
}
|
|
1800
|
+
let failed = 0;
|
|
1801
|
+
let fixed = 0;
|
|
1802
|
+
let skipped = 0;
|
|
1803
|
+
let quit = false;
|
|
1804
|
+
for (const d of diagnostics) {
|
|
1805
|
+
if (quit) {
|
|
1806
|
+
skipped++;
|
|
1807
|
+
continue;
|
|
1808
|
+
}
|
|
1809
|
+
const status = await d.check(ctx);
|
|
1810
|
+
if (status.ok) {
|
|
1811
|
+
p.log.success(`${d.id} ✓${status.detail ? ` (${status.detail})` : ""}`);
|
|
1812
|
+
continue;
|
|
1813
|
+
}
|
|
1814
|
+
failed++;
|
|
1815
|
+
p.log.warn(`${d.id} ✗ ${status.detail ?? ""}`.trim());
|
|
1816
|
+
p.log.info(`why: ${d.fixPreview}`);
|
|
1817
|
+
if (d.manualOnly) p.log.info(`(manual fix only — see "${d.id}" docs)`);
|
|
1818
|
+
if (applyAll) {
|
|
1819
|
+
if ((await applyFixWithReport(d, ctx, false)).ok) fixed++;
|
|
1820
|
+
if (!(await d.check(ctx)).ok) p.log.warn(`${d.id} still failing after fix.`);
|
|
1821
|
+
continue;
|
|
1822
|
+
}
|
|
1823
|
+
while (true) {
|
|
1824
|
+
const action = await askFixAction(d);
|
|
1825
|
+
if (action === "fix") {
|
|
1826
|
+
if ((await applyFixWithReport(d, ctx, false)).ok) {
|
|
1827
|
+
const after = await d.check(ctx);
|
|
1828
|
+
if (after.ok) fixed++;
|
|
1829
|
+
else p.log.warn(`${d.id} still failing after fix: ${after.detail ?? ""}`);
|
|
1830
|
+
}
|
|
1831
|
+
break;
|
|
1832
|
+
}
|
|
1833
|
+
if (action === "skip") {
|
|
1834
|
+
skipped++;
|
|
1835
|
+
break;
|
|
1836
|
+
}
|
|
1837
|
+
if (action === "more") {
|
|
1838
|
+
p.note(d.moreInfo, `[${d.id}] more info`);
|
|
1839
|
+
continue;
|
|
1840
|
+
}
|
|
1841
|
+
if (action === "quit") {
|
|
1842
|
+
quit = true;
|
|
1843
|
+
break;
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1847
|
+
const summary = `${diagnostics.length} checks · ${failed} failing · ${fixed} fixed · ${skipped} skipped`;
|
|
1848
|
+
if (quit) {
|
|
1849
|
+
p.outro(`Quit early. ${summary}`);
|
|
799
1850
|
process.exit(1);
|
|
800
1851
|
}
|
|
1852
|
+
if (failed === 0) {
|
|
1853
|
+
p.outro("All diagnostics passing. agentmemory is healthy.");
|
|
1854
|
+
return;
|
|
1855
|
+
}
|
|
1856
|
+
if (failed - fixed === 0) {
|
|
1857
|
+
p.outro(`All fixes applied. ${summary}`);
|
|
1858
|
+
return;
|
|
1859
|
+
}
|
|
1860
|
+
p.outro(summary);
|
|
1861
|
+
process.exit(1);
|
|
801
1862
|
}
|
|
802
1863
|
function buildDemoSessions() {
|
|
803
1864
|
return [
|
|
@@ -1178,6 +2239,7 @@ async function runStop() {
|
|
|
1178
2239
|
const port = getRestPort();
|
|
1179
2240
|
const state = readEngineState();
|
|
1180
2241
|
const running = await isEngineRunning();
|
|
2242
|
+
const force = args.includes("--force");
|
|
1181
2243
|
if (state?.kind === "docker") {
|
|
1182
2244
|
if (!running) {
|
|
1183
2245
|
p.log.info(`No engine responding on port ${port}.`);
|
|
@@ -1206,8 +2268,9 @@ async function runStop() {
|
|
|
1206
2268
|
}
|
|
1207
2269
|
if (!state) {
|
|
1208
2270
|
const compose = discoverComposeFile();
|
|
1209
|
-
if (compose && pidfilePid === null) {
|
|
1210
|
-
|
|
2271
|
+
if (compose && pidfilePid === null) if (force) p.log.warn(`--force: bypassing Docker-heuristic guard. Falling back to native pidfile + lsof on :${port}.`);
|
|
2272
|
+
else {
|
|
2273
|
+
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
2274
|
process.exit(1);
|
|
1212
2275
|
}
|
|
1213
2276
|
}
|
|
@@ -1235,7 +2298,11 @@ async function runStop() {
|
|
|
1235
2298
|
p.outro("Stopped. Memories persisted to disk; restart anytime with: npx @agentmemory/agentmemory");
|
|
1236
2299
|
}
|
|
1237
2300
|
async function runMcp() {
|
|
1238
|
-
await import("./standalone-
|
|
2301
|
+
await import("./standalone-BQOaGF4z.mjs");
|
|
2302
|
+
}
|
|
2303
|
+
async function runConnectCmd() {
|
|
2304
|
+
const { runConnect } = await import("./connect-hRTF7E2c.mjs");
|
|
2305
|
+
await runConnect(args.slice(1));
|
|
1239
2306
|
}
|
|
1240
2307
|
async function runImportJsonl() {
|
|
1241
2308
|
const VALUE_FLAGS = new Set(["--port", "--tools"]);
|
|
@@ -1339,13 +2406,126 @@ async function runImportJsonl() {
|
|
|
1339
2406
|
process.exit(1);
|
|
1340
2407
|
}
|
|
1341
2408
|
}
|
|
2409
|
+
function loadConnectManifest(home) {
|
|
2410
|
+
const path = join(home, ".agentmemory", "backups", "connect-manifest.json");
|
|
2411
|
+
try {
|
|
2412
|
+
const raw = readFileSync(path, "utf-8");
|
|
2413
|
+
const parsed = JSON.parse(raw);
|
|
2414
|
+
if (Array.isArray(parsed?.installed)) return { installed: parsed.installed };
|
|
2415
|
+
return null;
|
|
2416
|
+
} catch {
|
|
2417
|
+
return null;
|
|
2418
|
+
}
|
|
2419
|
+
}
|
|
2420
|
+
function probeLocalBinIiiVersion(home) {
|
|
2421
|
+
const path = localBinIii(home);
|
|
2422
|
+
if (!existsSync(path)) return null;
|
|
2423
|
+
return iiiBinVersion(path);
|
|
2424
|
+
}
|
|
2425
|
+
function safeDelete(path) {
|
|
2426
|
+
try {
|
|
2427
|
+
if (!existsSync(path)) return {
|
|
2428
|
+
ok: true,
|
|
2429
|
+
message: `not present (${path})`
|
|
2430
|
+
};
|
|
2431
|
+
if (statSync(path).isDirectory()) rmSync(path, {
|
|
2432
|
+
recursive: true,
|
|
2433
|
+
force: true
|
|
2434
|
+
});
|
|
2435
|
+
else unlinkSync(path);
|
|
2436
|
+
return {
|
|
2437
|
+
ok: true,
|
|
2438
|
+
message: `deleted ${path}`
|
|
2439
|
+
};
|
|
2440
|
+
} catch (err) {
|
|
2441
|
+
return {
|
|
2442
|
+
ok: false,
|
|
2443
|
+
message: `failed ${path}: ${err instanceof Error ? err.message : String(err)}`
|
|
2444
|
+
};
|
|
2445
|
+
}
|
|
2446
|
+
}
|
|
2447
|
+
async function runRemove() {
|
|
2448
|
+
p.intro("agentmemory remove");
|
|
2449
|
+
const force = args.includes("--force");
|
|
2450
|
+
const keepData = args.includes("--keep-data");
|
|
2451
|
+
const home = homedir();
|
|
2452
|
+
const connectManifest = loadConnectManifest(home);
|
|
2453
|
+
const plan = buildRemovePlan({
|
|
2454
|
+
home,
|
|
2455
|
+
pinnedVersion: IIPINNED_VERSION,
|
|
2456
|
+
localBinIiiVersion: probeLocalBinIiiVersion(home),
|
|
2457
|
+
connectManifest
|
|
2458
|
+
}, {
|
|
2459
|
+
force,
|
|
2460
|
+
keepData
|
|
2461
|
+
});
|
|
2462
|
+
if (plan.filter((it) => it.applicable).length === 0) {
|
|
2463
|
+
p.outro("Nothing to remove. agentmemory is already gone.");
|
|
2464
|
+
return;
|
|
2465
|
+
}
|
|
2466
|
+
p.note(formatPlan(plan), "destruction plan");
|
|
2467
|
+
if (!force) {
|
|
2468
|
+
const proceed = await p.confirm({
|
|
2469
|
+
message: "Proceed with these deletions?",
|
|
2470
|
+
initialValue: false
|
|
2471
|
+
});
|
|
2472
|
+
if (p.isCancel(proceed) || proceed !== true) {
|
|
2473
|
+
p.cancel("Cancelled. Nothing was deleted.");
|
|
2474
|
+
return;
|
|
2475
|
+
}
|
|
2476
|
+
const sure = await p.confirm({
|
|
2477
|
+
message: "This is irreversible. Continue?",
|
|
2478
|
+
initialValue: false
|
|
2479
|
+
});
|
|
2480
|
+
if (p.isCancel(sure) || sure !== true) {
|
|
2481
|
+
p.cancel("Cancelled. Nothing was deleted.");
|
|
2482
|
+
return;
|
|
2483
|
+
}
|
|
2484
|
+
}
|
|
2485
|
+
for (const item of plan) {
|
|
2486
|
+
if (!item.applicable) continue;
|
|
2487
|
+
if (item.alwaysAsk) {
|
|
2488
|
+
const ok = await p.confirm({
|
|
2489
|
+
message: `${item.description} — really delete${item.path ? ` ${item.path}` : ""}?`,
|
|
2490
|
+
initialValue: false
|
|
2491
|
+
});
|
|
2492
|
+
if (p.isCancel(ok) || ok !== true) {
|
|
2493
|
+
p.log.info(`skipped: ${item.id}`);
|
|
2494
|
+
continue;
|
|
2495
|
+
}
|
|
2496
|
+
}
|
|
2497
|
+
if (item.id === "stop-engine") {
|
|
2498
|
+
try {
|
|
2499
|
+
const portPids = findEnginePidsByPort(getRestPort());
|
|
2500
|
+
const pidfilePid = readEnginePidfile();
|
|
2501
|
+
const cands = /* @__PURE__ */ new Set();
|
|
2502
|
+
if (pidfilePid) cands.add(pidfilePid);
|
|
2503
|
+
for (const pid of portPids) cands.add(pid);
|
|
2504
|
+
for (const pid of cands) await signalAndWait(pid, "SIGTERM", 3e3);
|
|
2505
|
+
clearEnginePidfile();
|
|
2506
|
+
clearEngineState();
|
|
2507
|
+
p.log.success(cands.size > 0 ? `stopped engine (${cands.size} pid${cands.size === 1 ? "" : "s"})` : "no engine running");
|
|
2508
|
+
} catch (err) {
|
|
2509
|
+
p.log.warn(`engine stop best-effort: ${err instanceof Error ? err.message : String(err)}`);
|
|
2510
|
+
}
|
|
2511
|
+
continue;
|
|
2512
|
+
}
|
|
2513
|
+
if (!item.path) continue;
|
|
2514
|
+
const r = safeDelete(item.path);
|
|
2515
|
+
if (r.ok) p.log.success(r.message);
|
|
2516
|
+
else p.log.error(r.message);
|
|
2517
|
+
}
|
|
2518
|
+
p.outro("Done. agentmemory cleanly removed. The npm package itself: npm uninstall -g @agentmemory/agentmemory");
|
|
2519
|
+
}
|
|
1342
2520
|
({
|
|
1343
2521
|
init: runInit,
|
|
2522
|
+
connect: runConnectCmd,
|
|
1344
2523
|
status: runStatus,
|
|
1345
2524
|
doctor: runDoctor,
|
|
1346
2525
|
demo: runDemo,
|
|
1347
2526
|
upgrade: runUpgrade,
|
|
1348
2527
|
stop: runStop,
|
|
2528
|
+
remove: runRemove,
|
|
1349
2529
|
mcp: runMcp,
|
|
1350
2530
|
"import-jsonl": runImportJsonl
|
|
1351
2531
|
}[args[0] ?? ""] ?? main)().catch((err) => {
|
|
@@ -1354,5 +2534,5 @@ async function runImportJsonl() {
|
|
|
1354
2534
|
});
|
|
1355
2535
|
|
|
1356
2536
|
//#endregion
|
|
1357
|
-
export {
|
|
2537
|
+
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
2538
|
//# sourceMappingURL=cli.mjs.map
|