@agentmemory/agentmemory 0.9.12 → 0.9.14
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/.env.example +167 -0
- package/README.md +88 -22
- package/dist/.env.example +167 -0
- package/dist/cli.mjs +449 -84
- package/dist/cli.mjs.map +1 -1
- package/dist/image-refs-HVu22rfu.mjs +116 -0
- package/dist/image-refs-HVu22rfu.mjs.map +1 -0
- package/dist/image-store-BfN1vDbj.mjs +3 -0
- package/dist/index.mjs +140 -8
- package/dist/index.mjs.map +1 -1
- package/dist/{src-Cqsy23f_.mjs → src-BBI-ah3h.mjs} +150 -124
- package/dist/src-BBI-ah3h.mjs.map +1 -0
- package/dist/{standalone-DNt6O3zG.mjs → standalone-Cf5sp0XM.mjs} +2 -2
- package/dist/{standalone-DNt6O3zG.mjs.map → standalone-Cf5sp0XM.mjs.map} +1 -1
- package/dist/standalone.mjs +1 -1
- package/dist/standalone.mjs.map +1 -1
- package/dist/{tools-registry-NUso4hxA.mjs → tools-registry-BDimtXJb.mjs} +3 -3
- package/dist/{tools-registry-NUso4hxA.mjs.map → tools-registry-BDimtXJb.mjs.map} +1 -1
- package/package.json +13 -9
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/dist/image-refs-BkMGNmEs.mjs +0 -3
- package/dist/image-store-RY_BkYWK.mjs +0 -3
- package/dist/src-Cqsy23f_.mjs.map +0 -1
package/dist/cli.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { execFileSync, spawn, spawnSync } from "node:child_process";
|
|
3
|
-
import { existsSync, readFileSync, readdirSync, readlinkSync, statSync } from "node:fs";
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, readlinkSync, statSync, unlinkSync, writeFileSync } 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";
|
|
@@ -110,10 +110,12 @@ Usage: agentmemory [command] [options]
|
|
|
110
110
|
|
|
111
111
|
Commands:
|
|
112
112
|
(default) Start agentmemory worker
|
|
113
|
+
init Copy bundled .env.example to ~/.agentmemory/.env if absent
|
|
113
114
|
status Show connection status, memory count, flags, and health
|
|
114
115
|
doctor Run diagnostic checks (server, flags, graph, providers)
|
|
115
116
|
demo Seed sample sessions and show recall in action
|
|
116
117
|
upgrade Upgrade local deps + iii runtime (best effort)
|
|
118
|
+
stop Stop the running iii-engine started by this CLI
|
|
117
119
|
mcp Start standalone MCP server (no engine required)
|
|
118
120
|
import-jsonl [p] Import Claude Code JSONL transcripts (default: ~/.claude/projects)
|
|
119
121
|
--max-files <N> | --max-files=<N>: override scan cap (default 200, max 1000;
|
|
@@ -127,8 +129,11 @@ Options:
|
|
|
127
129
|
--port <N> Override REST port (default: 3111)
|
|
128
130
|
|
|
129
131
|
Environment:
|
|
130
|
-
AGENTMEMORY_URL
|
|
131
|
-
|
|
132
|
+
AGENTMEMORY_URL Full REST base URL (e.g. http://localhost:3111).
|
|
133
|
+
Honored by status, doctor, and MCP shim commands.
|
|
134
|
+
AGENTMEMORY_USE_DOCKER=1 Prefer the bundled docker-compose path over the
|
|
135
|
+
native iii-engine binary on first run.
|
|
136
|
+
AGENTMEMORY_III_VERSION Override pinned iii-engine version (default ${IIPINNED_VERSION}).
|
|
132
137
|
|
|
133
138
|
Quick start:
|
|
134
139
|
npx @agentmemory/agentmemory # start with local iii-engine or Docker
|
|
@@ -219,6 +224,130 @@ function fallbackIiiPaths() {
|
|
|
219
224
|
if (!home) return ["/usr/local/bin/iii"];
|
|
220
225
|
return [join(home, ".local", "bin", "iii"), "/usr/local/bin/iii"];
|
|
221
226
|
}
|
|
227
|
+
function iiiBinVersion(binPath) {
|
|
228
|
+
try {
|
|
229
|
+
const match = execFileSync(binPath, ["--version"], {
|
|
230
|
+
encoding: "utf-8",
|
|
231
|
+
stdio: [
|
|
232
|
+
"ignore",
|
|
233
|
+
"pipe",
|
|
234
|
+
"ignore"
|
|
235
|
+
],
|
|
236
|
+
timeout: 3e3
|
|
237
|
+
}).match(/(\d+\.\d+\.\d+(?:[-+][\w.]+)?)/);
|
|
238
|
+
return match ? match[1] : null;
|
|
239
|
+
} catch {
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
function enginePidfilePath() {
|
|
244
|
+
return join(homedir(), ".agentmemory", "iii.pid");
|
|
245
|
+
}
|
|
246
|
+
function engineStatePath() {
|
|
247
|
+
return join(homedir(), ".agentmemory", "engine-state.json");
|
|
248
|
+
}
|
|
249
|
+
function writeEnginePidfile(pid) {
|
|
250
|
+
try {
|
|
251
|
+
const pidPath = enginePidfilePath();
|
|
252
|
+
mkdirSync(dirname(pidPath), { recursive: true });
|
|
253
|
+
writeFileSync(pidPath, `${pid}\n`, { encoding: "utf-8" });
|
|
254
|
+
} catch (err) {
|
|
255
|
+
vlog(`writeEnginePidfile: ${err instanceof Error ? err.message : String(err)}`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
function readEnginePidfile() {
|
|
259
|
+
try {
|
|
260
|
+
const pidStr = readFileSync(enginePidfilePath(), "utf-8").trim();
|
|
261
|
+
const pid = parseInt(pidStr, 10);
|
|
262
|
+
return Number.isFinite(pid) && pid > 0 ? pid : null;
|
|
263
|
+
} catch {
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
function clearEnginePidfile() {
|
|
268
|
+
try {
|
|
269
|
+
unlinkSync(enginePidfilePath());
|
|
270
|
+
} catch {}
|
|
271
|
+
}
|
|
272
|
+
function writeEngineState(state) {
|
|
273
|
+
try {
|
|
274
|
+
const statePath = engineStatePath();
|
|
275
|
+
mkdirSync(dirname(statePath), { recursive: true });
|
|
276
|
+
writeFileSync(statePath, `${JSON.stringify(state)}\n`, { encoding: "utf-8" });
|
|
277
|
+
} catch (err) {
|
|
278
|
+
vlog(`writeEngineState: ${err instanceof Error ? err.message : String(err)}`);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
function readEngineState() {
|
|
282
|
+
try {
|
|
283
|
+
const raw = readFileSync(engineStatePath(), "utf-8");
|
|
284
|
+
const parsed = JSON.parse(raw);
|
|
285
|
+
if (parsed && (parsed.kind === "native" || parsed.kind === "docker")) return parsed;
|
|
286
|
+
return null;
|
|
287
|
+
} catch {
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
function clearEngineState() {
|
|
292
|
+
try {
|
|
293
|
+
unlinkSync(engineStatePath());
|
|
294
|
+
} catch {}
|
|
295
|
+
}
|
|
296
|
+
function discoverComposeFile() {
|
|
297
|
+
return [
|
|
298
|
+
join(__dirname, "..", "docker-compose.yml"),
|
|
299
|
+
join(__dirname, "docker-compose.yml"),
|
|
300
|
+
join(process.cwd(), "docker-compose.yml")
|
|
301
|
+
].find((c) => existsSync(c)) ?? null;
|
|
302
|
+
}
|
|
303
|
+
async function runIiiInstaller() {
|
|
304
|
+
const releaseUrl = iiiReleaseUrl();
|
|
305
|
+
const asset = iiiReleaseAsset();
|
|
306
|
+
const isZipAsset = asset?.endsWith(".zip") === true;
|
|
307
|
+
if (!releaseUrl) {
|
|
308
|
+
p.log.warn(`iii-engine binary not available for ${platform()}/${process.arch}. Use Docker (\`docker pull iiidev/iii:${IIPINNED_VERSION}\`) or download manually from https://github.com/iii-hq/iii/releases/tag/iii%2Fv${IIPINNED_VERSION}.`);
|
|
309
|
+
return {
|
|
310
|
+
ok: false,
|
|
311
|
+
binPath: null
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
if (IS_WINDOWS || isZipAsset) {
|
|
315
|
+
p.log.info(`Auto-install unavailable on ${platform()} — ${asset} isn't tar-compatible. Install manually:\n 1. Download ${releaseUrl}\n 2. Extract iii.exe and place it on PATH (e.g. %USERPROFILE%\\.local\\bin)\nOr use Docker: docker pull iiidev/iii:${IIPINNED_VERSION}`);
|
|
316
|
+
return {
|
|
317
|
+
ok: false,
|
|
318
|
+
binPath: null
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
const shBin = whichBinary("sh");
|
|
322
|
+
const curlBin = whichBinary("curl");
|
|
323
|
+
if (!shBin || !curlBin) {
|
|
324
|
+
p.log.warn("curl or sh not found. Cannot auto-install iii-engine.");
|
|
325
|
+
return {
|
|
326
|
+
ok: false,
|
|
327
|
+
binPath: null
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
const binDir = join(homedir(), ".local", "bin");
|
|
331
|
+
const binPath = join(binDir, "iii");
|
|
332
|
+
if (!runCommand(shBin, ["-c", [
|
|
333
|
+
`mkdir -p "${binDir}"`,
|
|
334
|
+
`curl -fsSL "${releaseUrl}" | tar -xz -C "${binDir}"`,
|
|
335
|
+
`chmod +x "${binPath}"`
|
|
336
|
+
].join(" && ")], {
|
|
337
|
+
label: `Installing iii-engine v${IIPINNED_VERSION} (pinned)`,
|
|
338
|
+
optional: true
|
|
339
|
+
})) {
|
|
340
|
+
p.log.warn(`iii-engine installer failed. Fallbacks: Docker (\`docker pull iiidev/iii:${IIPINNED_VERSION}\`) or download manually from https://github.com/iii-hq/iii/releases/tag/iii%2Fv${IIPINNED_VERSION}.`);
|
|
341
|
+
return {
|
|
342
|
+
ok: false,
|
|
343
|
+
binPath: null
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
return {
|
|
347
|
+
ok: true,
|
|
348
|
+
binPath
|
|
349
|
+
};
|
|
350
|
+
}
|
|
222
351
|
let startupFailure = null;
|
|
223
352
|
function spawnEngineBackground(bin, spawnArgs, label) {
|
|
224
353
|
vlog(`spawn: ${bin} ${spawnArgs.join(" ")}`);
|
|
@@ -231,6 +360,8 @@ function spawnEngineBackground(bin, spawnArgs, label) {
|
|
|
231
360
|
],
|
|
232
361
|
windowsHide: true
|
|
233
362
|
});
|
|
363
|
+
const isDocker = label.includes("Docker");
|
|
364
|
+
if (!isDocker && typeof child.pid === "number") writeEnginePidfile(child.pid);
|
|
234
365
|
const stderrChunks = [];
|
|
235
366
|
let stderrBytes = 0;
|
|
236
367
|
const MAX_STDERR_CAPTURE = 16 * 1024;
|
|
@@ -244,27 +375,47 @@ function spawnEngineBackground(bin, spawnArgs, label) {
|
|
|
244
375
|
if (code !== null && code !== 0 || code === null && signal !== null) {
|
|
245
376
|
const stderr = Buffer.concat(stderrChunks).toString("utf-8");
|
|
246
377
|
startupFailure = {
|
|
247
|
-
kind:
|
|
378
|
+
kind: isDocker ? "docker-crashed" : "engine-crashed",
|
|
248
379
|
stderr: stderr.trim() || (signal ? `process killed by signal ${signal}` : `process exited with code ${code}`),
|
|
249
380
|
binary: bin
|
|
250
381
|
};
|
|
251
382
|
vlog(`engine exited early: code=${code} signal=${signal}`);
|
|
252
383
|
if (IS_VERBOSE && stderr.trim()) p.log.error(`engine stderr:\n${stderr}`);
|
|
384
|
+
if (!isDocker) clearEnginePidfile();
|
|
385
|
+
clearEngineState();
|
|
253
386
|
}
|
|
254
387
|
});
|
|
255
388
|
child.unref();
|
|
256
389
|
return child;
|
|
257
390
|
}
|
|
391
|
+
function startIiiBin(iiiBin, configPath) {
|
|
392
|
+
const s = p.spinner();
|
|
393
|
+
s.start(`Starting iii-engine: ${iiiBin}`);
|
|
394
|
+
writeEngineState({
|
|
395
|
+
kind: "native",
|
|
396
|
+
configPath
|
|
397
|
+
});
|
|
398
|
+
spawnEngineBackground(iiiBin, ["--config", configPath], "iii-engine");
|
|
399
|
+
s.stop("iii-engine process started");
|
|
400
|
+
return true;
|
|
401
|
+
}
|
|
258
402
|
async function startEngine() {
|
|
259
403
|
const configPath = findIiiConfig();
|
|
260
404
|
let iiiBin = whichBinary("iii");
|
|
261
405
|
vlog(`iii binary: ${iiiBin ?? "(not on PATH)"}, config: ${configPath || "(not found)"}`);
|
|
262
|
-
if (iiiBin && configPath)
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
406
|
+
if (iiiBin && configPath) return startIiiBin(iiiBin, configPath);
|
|
407
|
+
for (const iiiPath of fallbackIiiPaths()) if (existsSync(iiiPath)) {
|
|
408
|
+
const v = iiiBinVersion(iiiPath);
|
|
409
|
+
vlog(`fallback iii at ${iiiPath} reports version: ${v ?? "unknown"}`);
|
|
410
|
+
p.log.info(`Found iii at: ${iiiPath}${v ? ` (v${v})` : ""}`);
|
|
411
|
+
process.env["PATH"] = `${dirname(iiiPath)}${delimiter}${process.env["PATH"] ?? ""}`;
|
|
412
|
+
iiiBin = iiiPath;
|
|
413
|
+
break;
|
|
414
|
+
}
|
|
415
|
+
if (iiiBin && configPath) return startIiiBin(iiiBin, configPath);
|
|
416
|
+
if (!configPath) {
|
|
417
|
+
startupFailure = { kind: "no-engine" };
|
|
418
|
+
return false;
|
|
268
419
|
}
|
|
269
420
|
const dockerBin = whichBinary("docker");
|
|
270
421
|
vlog(`docker binary: ${dockerBin ?? "(not on PATH)"}`);
|
|
@@ -274,9 +425,73 @@ async function startEngine() {
|
|
|
274
425
|
join(process.cwd(), "docker-compose.yml")
|
|
275
426
|
].find((c) => existsSync(c));
|
|
276
427
|
vlog(`docker-compose.yml: ${composeFile ?? "(not found)"}`);
|
|
277
|
-
|
|
428
|
+
const dockerOptIn = process.env["AGENTMEMORY_USE_DOCKER"] === "1" || process.env["AGENTMEMORY_USE_DOCKER"] === "true";
|
|
429
|
+
const interactive = !!process.stdin.isTTY && !process.env["CI"];
|
|
430
|
+
let choice;
|
|
431
|
+
if (dockerOptIn && dockerBin && composeFile) choice = "docker";
|
|
432
|
+
else if (!interactive) {
|
|
433
|
+
choice = "install";
|
|
434
|
+
p.log.info("Non-interactive environment detected — auto-installing iii-engine.");
|
|
435
|
+
} else {
|
|
436
|
+
p.log.warn(`iii-engine binary not found locally.`);
|
|
437
|
+
const options = [{
|
|
438
|
+
value: "install",
|
|
439
|
+
label: `Install iii v${IIPINNED_VERSION} to ~/.local/bin (~6MB, ~5s)`,
|
|
440
|
+
hint: "recommended"
|
|
441
|
+
}];
|
|
442
|
+
if (dockerBin && composeFile) options.push({
|
|
443
|
+
value: "docker",
|
|
444
|
+
label: "Use Docker compose",
|
|
445
|
+
hint: "advanced"
|
|
446
|
+
});
|
|
447
|
+
options.push({
|
|
448
|
+
value: "manual",
|
|
449
|
+
label: "Show manual install steps and exit"
|
|
450
|
+
});
|
|
451
|
+
const picked = await p.select({
|
|
452
|
+
message: "How would you like to start iii-engine?",
|
|
453
|
+
options,
|
|
454
|
+
initialValue: "install"
|
|
455
|
+
});
|
|
456
|
+
if (p.isCancel(picked)) {
|
|
457
|
+
startupFailure = { kind: "no-engine" };
|
|
458
|
+
return false;
|
|
459
|
+
}
|
|
460
|
+
choice = picked;
|
|
461
|
+
}
|
|
462
|
+
if (choice === "manual") {
|
|
463
|
+
startupFailure = { kind: "no-engine" };
|
|
464
|
+
return false;
|
|
465
|
+
}
|
|
466
|
+
if (choice === "install") {
|
|
467
|
+
const result = await runIiiInstaller();
|
|
468
|
+
if (result.ok && result.binPath) {
|
|
469
|
+
process.env["PATH"] = `${dirname(result.binPath)}${delimiter}${process.env["PATH"] ?? ""}`;
|
|
470
|
+
iiiBin = result.binPath;
|
|
471
|
+
return startIiiBin(iiiBin, configPath);
|
|
472
|
+
}
|
|
473
|
+
if (dockerBin && composeFile && interactive) {
|
|
474
|
+
const fallback = await p.confirm({
|
|
475
|
+
message: "Auto-install failed. Try Docker compose instead?",
|
|
476
|
+
initialValue: true
|
|
477
|
+
});
|
|
478
|
+
if (p.isCancel(fallback) || fallback !== true) {
|
|
479
|
+
startupFailure = { kind: "no-engine" };
|
|
480
|
+
return false;
|
|
481
|
+
}
|
|
482
|
+
choice = "docker";
|
|
483
|
+
} else {
|
|
484
|
+
startupFailure = { kind: "no-engine" };
|
|
485
|
+
return false;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
if (choice === "docker" && dockerBin && composeFile) {
|
|
278
489
|
const s = p.spinner();
|
|
279
490
|
s.start("Starting iii-engine via Docker...");
|
|
491
|
+
writeEngineState({
|
|
492
|
+
kind: "docker",
|
|
493
|
+
composeFile
|
|
494
|
+
});
|
|
280
495
|
spawnEngineBackground(dockerBin, [
|
|
281
496
|
"compose",
|
|
282
497
|
"-f",
|
|
@@ -287,21 +502,8 @@ async function startEngine() {
|
|
|
287
502
|
s.stop("Docker compose started");
|
|
288
503
|
return true;
|
|
289
504
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
process.env["PATH"] = `${dirname(iiiPath)}${delimiter}${process.env["PATH"] ?? ""}`;
|
|
293
|
-
iiiBin = iiiPath;
|
|
294
|
-
break;
|
|
295
|
-
}
|
|
296
|
-
if (iiiBin && configPath) {
|
|
297
|
-
const s = p.spinner();
|
|
298
|
-
s.start(`Starting iii-engine: ${iiiBin}`);
|
|
299
|
-
spawnEngineBackground(iiiBin, ["--config", configPath], "iii-engine");
|
|
300
|
-
s.stop("iii-engine process started");
|
|
301
|
-
return true;
|
|
302
|
-
}
|
|
303
|
-
if (!iiiBin && (!dockerBin || !composeFile)) startupFailure = { kind: "no-engine" };
|
|
304
|
-
else if (!composeFile && dockerBin) startupFailure = { kind: "no-docker-compose" };
|
|
505
|
+
if (!composeFile && dockerBin) startupFailure = { kind: "no-docker-compose" };
|
|
506
|
+
else startupFailure = { kind: "no-engine" };
|
|
305
507
|
return false;
|
|
306
508
|
}
|
|
307
509
|
async function waitForEngine(timeoutMs) {
|
|
@@ -315,43 +517,34 @@ async function waitForEngine(timeoutMs) {
|
|
|
315
517
|
function installInstructions() {
|
|
316
518
|
const releaseUrl = iiiReleaseUrl();
|
|
317
519
|
if (IS_WINDOWS) return [
|
|
318
|
-
`agentmemory
|
|
520
|
+
`agentmemory needs iii-engine v${IIPINNED_VERSION}. Pick one:`,
|
|
319
521
|
"",
|
|
320
522
|
" A) Download the prebuilt Windows binary:",
|
|
321
523
|
` 1. Open https://github.com/iii-hq/iii/releases/tag/iii%2Fv${IIPINNED_VERSION}`,
|
|
322
|
-
` 2. Download iii-x86_64-pc-windows-msvc.zip`,
|
|
323
|
-
"
|
|
324
|
-
" 3. Extract iii.exe and either add its folder to PATH",
|
|
325
|
-
" or move it to %USERPROFILE%\\.local\\bin\\iii.exe",
|
|
524
|
+
` 2. Download iii-x86_64-pc-windows-msvc.zip (or iii-aarch64-pc-windows-msvc.zip on ARM)`,
|
|
525
|
+
" 3. Extract iii.exe to %USERPROFILE%\\.local\\bin\\iii.exe (or add to PATH)",
|
|
326
526
|
" 4. Re-run: npx @agentmemory/agentmemory",
|
|
327
527
|
"",
|
|
328
|
-
|
|
329
|
-
"
|
|
330
|
-
|
|
331
|
-
"
|
|
332
|
-
" 4. Re-run: npx @agentmemory/agentmemory",
|
|
528
|
+
` B) Docker: docker pull iiidev/iii:${IIPINNED_VERSION}`,
|
|
529
|
+
" Re-run with AGENTMEMORY_USE_DOCKER=1 npx @agentmemory/agentmemory",
|
|
530
|
+
"",
|
|
531
|
+
"Or skip the engine entirely (standalone MCP): npx @agentmemory/agentmemory mcp",
|
|
333
532
|
"",
|
|
334
|
-
"
|
|
335
|
-
" npx @agentmemory/agentmemory mcp"
|
|
533
|
+
"Docs: https://iii.dev/docs"
|
|
336
534
|
];
|
|
337
|
-
const linuxInstall = releaseUrl ? ` A)
|
|
535
|
+
const linuxInstall = releaseUrl ? ` A) curl -fsSL "${releaseUrl}" | tar -xz -C ~/.local/bin && chmod +x ~/.local/bin/iii` : ` A) Manual download: https://github.com/iii-hq/iii/releases/tag/iii%2Fv${IIPINNED_VERSION}`;
|
|
338
536
|
return [
|
|
339
|
-
`agentmemory
|
|
537
|
+
`agentmemory needs iii-engine v${IIPINNED_VERSION}. Pick one:`,
|
|
340
538
|
"",
|
|
341
539
|
linuxInstall,
|
|
342
|
-
|
|
540
|
+
" Then re-run: npx @agentmemory/agentmemory",
|
|
343
541
|
"",
|
|
344
|
-
` B) Docker:
|
|
542
|
+
` B) Docker: docker pull iiidev/iii:${IIPINNED_VERSION}`,
|
|
543
|
+
" Re-run with AGENTMEMORY_USE_DOCKER=1 npx @agentmemory/agentmemory",
|
|
345
544
|
"",
|
|
346
|
-
"Or skip the engine entirely
|
|
347
|
-
" npx @agentmemory/agentmemory mcp",
|
|
545
|
+
"Or skip the engine entirely (standalone MCP): npx @agentmemory/agentmemory mcp",
|
|
348
546
|
"",
|
|
349
|
-
"Docs: https://iii.dev/docs"
|
|
350
|
-
`Why pinned: iii v0.11.6 introduces the new sandbox-everything model`,
|
|
351
|
-
`(\`iii worker add\` registration). agentmemory still uses the older`,
|
|
352
|
-
`iii-exec config-file worker model and needs a refactor before it`,
|
|
353
|
-
`runs cleanly under the new engine. Override with`,
|
|
354
|
-
`AGENTMEMORY_III_VERSION=<version> when you've migrated manually.`
|
|
547
|
+
"Docs: https://iii.dev/docs"
|
|
355
548
|
];
|
|
356
549
|
}
|
|
357
550
|
function portInUseDiagnostic(port) {
|
|
@@ -361,12 +554,12 @@ async function main() {
|
|
|
361
554
|
p.intro("agentmemory");
|
|
362
555
|
if (skipEngine) {
|
|
363
556
|
p.log.info("Skipping engine check (--no-engine)");
|
|
364
|
-
await import("./src-
|
|
557
|
+
await import("./src-BBI-ah3h.mjs");
|
|
365
558
|
return;
|
|
366
559
|
}
|
|
367
560
|
if (await isEngineRunning()) {
|
|
368
561
|
p.log.success("iii-engine is running");
|
|
369
|
-
await import("./src-
|
|
562
|
+
await import("./src-BBI-ah3h.mjs");
|
|
370
563
|
return;
|
|
371
564
|
}
|
|
372
565
|
if (!await startEngine()) {
|
|
@@ -410,7 +603,7 @@ async function main() {
|
|
|
410
603
|
process.exit(1);
|
|
411
604
|
}
|
|
412
605
|
s.stop("iii-engine is ready");
|
|
413
|
-
await import("./src-
|
|
606
|
+
await import("./src-BBI-ah3h.mjs");
|
|
414
607
|
}
|
|
415
608
|
async function apiFetch(base, path, timeoutMs = 5e3) {
|
|
416
609
|
try {
|
|
@@ -732,6 +925,50 @@ async function runDemoSearch(base, query) {
|
|
|
732
925
|
topTitle: items[0]?.title ?? "(no results)"
|
|
733
926
|
};
|
|
734
927
|
}
|
|
928
|
+
function findEnvExample() {
|
|
929
|
+
const candidates = [
|
|
930
|
+
join(__dirname, "..", ".env.example"),
|
|
931
|
+
join(__dirname, ".env.example"),
|
|
932
|
+
join(process.cwd(), ".env.example")
|
|
933
|
+
];
|
|
934
|
+
for (const c of candidates) if (existsSync(c)) return c;
|
|
935
|
+
return null;
|
|
936
|
+
}
|
|
937
|
+
async function runInit() {
|
|
938
|
+
p.intro("agentmemory init");
|
|
939
|
+
const target = join(homedir(), ".agentmemory", ".env");
|
|
940
|
+
const template = findEnvExample();
|
|
941
|
+
if (!template) {
|
|
942
|
+
p.log.error("Could not locate .env.example in the package. Re-install with: npm i -g @agentmemory/agentmemory");
|
|
943
|
+
process.exit(1);
|
|
944
|
+
}
|
|
945
|
+
const dir = dirname(target);
|
|
946
|
+
const { mkdir, copyFile } = await import("node:fs/promises");
|
|
947
|
+
const { constants: fsConstants } = await import("node:fs");
|
|
948
|
+
try {
|
|
949
|
+
await mkdir(dir, { recursive: true });
|
|
950
|
+
await copyFile(template, target, fsConstants.COPYFILE_EXCL);
|
|
951
|
+
} catch (err) {
|
|
952
|
+
if (err?.code === "EEXIST") {
|
|
953
|
+
p.log.warn(`${target} already exists — leaving it untouched.`);
|
|
954
|
+
p.log.info(`Compare against the latest template: diff ${target} ${template}`);
|
|
955
|
+
p.outro("Nothing changed.");
|
|
956
|
+
return;
|
|
957
|
+
}
|
|
958
|
+
p.log.error(`Failed to copy template: ${err instanceof Error ? err.message : String(err)}`);
|
|
959
|
+
process.exit(1);
|
|
960
|
+
}
|
|
961
|
+
p.log.success(`Wrote ${target}`);
|
|
962
|
+
p.note([
|
|
963
|
+
"All keys are commented out by default. Uncomment the ones you want.",
|
|
964
|
+
"",
|
|
965
|
+
"Common next steps:",
|
|
966
|
+
" 1. Pick an LLM provider key (ANTHROPIC_API_KEY / OPENAI_API_KEY / GEMINI_API_KEY / etc.)",
|
|
967
|
+
" 2. Run `npx @agentmemory/agentmemory doctor` to verify the daemon sees them",
|
|
968
|
+
" 3. Run `npx @agentmemory/agentmemory` to start the worker"
|
|
969
|
+
].join("\n"), "Next steps");
|
|
970
|
+
p.outro(`Edit ${target} and you're set.`);
|
|
971
|
+
}
|
|
735
972
|
async function runDemo() {
|
|
736
973
|
const port = getRestPort();
|
|
737
974
|
const base = `http://localhost:${port}`;
|
|
@@ -827,36 +1064,16 @@ async function runUpgrade() {
|
|
|
827
1064
|
});
|
|
828
1065
|
} else p.log.warn("No package manager found (pnpm/npm). Skipping JS dependency upgrade.");
|
|
829
1066
|
else p.log.warn("No package.json in current directory. Skipping JS dependency upgrade.");
|
|
830
|
-
const
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
}
|
|
841
|
-
if (upgradeEngine === true) {
|
|
842
|
-
const releaseUrl = iiiReleaseUrl();
|
|
843
|
-
const asset = iiiReleaseAsset();
|
|
844
|
-
const isZipAsset = asset?.endsWith(".zip") === true;
|
|
845
|
-
if (!releaseUrl) p.log.warn(`iii-engine binary not available for ${platform()}/${process.arch}. Use Docker (\`docker pull iiidev/iii:${IIPINNED_VERSION}\`) or download manually from https://github.com/iii-hq/iii/releases/tag/iii%2Fv${IIPINNED_VERSION}.`);
|
|
846
|
-
else if (IS_WINDOWS || isZipAsset) p.log.info(`Skipping auto-install on ${platform()} — the ${asset} asset isn't tar-compatible. Install manually:\n 1. Download ${releaseUrl}\n 2. Extract iii.exe and place it on PATH (e.g. %USERPROFILE%\\.local\\bin)\nOr use Docker: docker pull iiidev/iii:${IIPINNED_VERSION}`);
|
|
847
|
-
else {
|
|
848
|
-
const binDir = join(homedir(), ".local", "bin");
|
|
849
|
-
if (!runCommand(shBin, ["-c", [
|
|
850
|
-
`mkdir -p "${binDir}"`,
|
|
851
|
-
`curl -fsSL "${releaseUrl}" | tar -xz -C "${binDir}"`,
|
|
852
|
-
`chmod +x "${binDir}/iii"`
|
|
853
|
-
].join(" && ")], {
|
|
854
|
-
label: `Installing iii-engine v${IIPINNED_VERSION} (pinned)`,
|
|
855
|
-
optional: true
|
|
856
|
-
})) p.log.warn(`iii-engine installer failed. Fallbacks: Docker (\`docker pull iiidev/iii:${IIPINNED_VERSION}\`) or download manually from https://github.com/iii-hq/iii/releases/tag/iii%2Fv${IIPINNED_VERSION}.`);
|
|
857
|
-
}
|
|
858
|
-
} else p.log.info("Skipped iii-engine installer.");
|
|
859
|
-
} else p.log.warn("curl or sh not found. Skipping iii-engine installer.");
|
|
1067
|
+
const upgradeEngine = await p.confirm({
|
|
1068
|
+
message: "Re-run the iii-engine install script (curl | sh)?",
|
|
1069
|
+
initialValue: true
|
|
1070
|
+
});
|
|
1071
|
+
if (p.isCancel(upgradeEngine)) {
|
|
1072
|
+
p.cancel("Cancelled.");
|
|
1073
|
+
return process.exit(0);
|
|
1074
|
+
}
|
|
1075
|
+
if (upgradeEngine === true) await runIiiInstaller();
|
|
1076
|
+
else p.log.info("Skipped iii-engine installer.");
|
|
860
1077
|
if (dockerBin) runCommand(dockerBin, ["pull", `iiidev/iii:${IIPINNED_VERSION}`], {
|
|
861
1078
|
label: `Pulling iii Docker image v${IIPINNED_VERSION} (pinned)`,
|
|
862
1079
|
optional: true
|
|
@@ -871,8 +1088,154 @@ async function runUpgrade() {
|
|
|
871
1088
|
" 3) restart agentmemory process"
|
|
872
1089
|
].join("\n"), "agentmemory upgrade");
|
|
873
1090
|
}
|
|
1091
|
+
function pidAlive(pid) {
|
|
1092
|
+
try {
|
|
1093
|
+
process.kill(pid, 0);
|
|
1094
|
+
return true;
|
|
1095
|
+
} catch (err) {
|
|
1096
|
+
return err?.code === "EPERM";
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
async function signalAndWait(pid, initialSignal, timeoutMs) {
|
|
1100
|
+
try {
|
|
1101
|
+
process.kill(pid, initialSignal);
|
|
1102
|
+
} catch (err) {
|
|
1103
|
+
const code = err?.code;
|
|
1104
|
+
if (code === "ESRCH") return true;
|
|
1105
|
+
if (code === "EPERM") {
|
|
1106
|
+
p.log.warn(`No permission to signal pid ${pid}. Try: kill ${pid}`);
|
|
1107
|
+
return false;
|
|
1108
|
+
}
|
|
1109
|
+
vlog(`${initialSignal} ${pid}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1110
|
+
return false;
|
|
1111
|
+
}
|
|
1112
|
+
const deadline = Date.now() + timeoutMs;
|
|
1113
|
+
while (Date.now() < deadline) {
|
|
1114
|
+
if (!pidAlive(pid)) return true;
|
|
1115
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
1116
|
+
}
|
|
1117
|
+
if (!pidAlive(pid)) return true;
|
|
1118
|
+
try {
|
|
1119
|
+
process.kill(pid, "SIGKILL");
|
|
1120
|
+
} catch (err) {
|
|
1121
|
+
if (err?.code === "ESRCH") return true;
|
|
1122
|
+
vlog(`SIGKILL ${pid}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1123
|
+
return false;
|
|
1124
|
+
}
|
|
1125
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
1126
|
+
return !pidAlive(pid);
|
|
1127
|
+
}
|
|
1128
|
+
function findEnginePidsByPort(port) {
|
|
1129
|
+
if (IS_WINDOWS) return [];
|
|
1130
|
+
const lsof = whichBinary("lsof");
|
|
1131
|
+
if (!lsof) return [];
|
|
1132
|
+
const selfPid = process.pid;
|
|
1133
|
+
try {
|
|
1134
|
+
return execFileSync(lsof, [
|
|
1135
|
+
"-i",
|
|
1136
|
+
`:${port}`,
|
|
1137
|
+
"-sTCP:LISTEN",
|
|
1138
|
+
"-t"
|
|
1139
|
+
], {
|
|
1140
|
+
encoding: "utf-8",
|
|
1141
|
+
stdio: [
|
|
1142
|
+
"ignore",
|
|
1143
|
+
"pipe",
|
|
1144
|
+
"ignore"
|
|
1145
|
+
]
|
|
1146
|
+
}).split(/\s+/).map((s) => parseInt(s, 10)).filter((n) => Number.isFinite(n) && n > 0 && n !== selfPid);
|
|
1147
|
+
} catch (err) {
|
|
1148
|
+
vlog(`lsof :${port}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1149
|
+
return [];
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
async function stopDockerEngine(composeFile, port) {
|
|
1153
|
+
const dockerBin = whichBinary("docker");
|
|
1154
|
+
if (!dockerBin) {
|
|
1155
|
+
p.log.error(`Engine was started via Docker compose, but \`docker\` is no longer on PATH. Stop it manually:\n docker compose -f ${composeFile} down`);
|
|
1156
|
+
process.exit(1);
|
|
1157
|
+
}
|
|
1158
|
+
if (!existsSync(composeFile)) {
|
|
1159
|
+
p.log.error(`Engine state references ${composeFile}, but the file is gone. Stop it manually:\n docker compose down (from the dir holding the original docker-compose.yml)`);
|
|
1160
|
+
process.exit(1);
|
|
1161
|
+
}
|
|
1162
|
+
const ok = runCommand(dockerBin, [
|
|
1163
|
+
"compose",
|
|
1164
|
+
"-f",
|
|
1165
|
+
composeFile,
|
|
1166
|
+
"down"
|
|
1167
|
+
], { label: `docker compose -f ${composeFile} down` });
|
|
1168
|
+
clearEnginePidfile();
|
|
1169
|
+
clearEngineState();
|
|
1170
|
+
if (!ok) {
|
|
1171
|
+
p.log.error(`docker compose down failed. The engine may still be running on :${port}. Inspect with:\n docker compose -f ${composeFile} ps`);
|
|
1172
|
+
process.exit(1);
|
|
1173
|
+
}
|
|
1174
|
+
p.outro("Stopped. Memories persisted to disk; restart anytime with: npx @agentmemory/agentmemory");
|
|
1175
|
+
}
|
|
1176
|
+
async function runStop() {
|
|
1177
|
+
p.intro("agentmemory stop");
|
|
1178
|
+
const port = getRestPort();
|
|
1179
|
+
const state = readEngineState();
|
|
1180
|
+
const running = await isEngineRunning();
|
|
1181
|
+
if (state?.kind === "docker") {
|
|
1182
|
+
if (!running) {
|
|
1183
|
+
p.log.info(`No engine responding on port ${port}.`);
|
|
1184
|
+
clearEnginePidfile();
|
|
1185
|
+
clearEngineState();
|
|
1186
|
+
p.outro("Nothing to stop.");
|
|
1187
|
+
return;
|
|
1188
|
+
}
|
|
1189
|
+
await stopDockerEngine(state.composeFile, port);
|
|
1190
|
+
return;
|
|
1191
|
+
}
|
|
1192
|
+
const portPids = findEnginePidsByPort(port);
|
|
1193
|
+
const pidfilePid = readEnginePidfile();
|
|
1194
|
+
if (!running) {
|
|
1195
|
+
if (portPids.length === 0 && pidfilePid === null) {
|
|
1196
|
+
clearEnginePidfile();
|
|
1197
|
+
clearEngineState();
|
|
1198
|
+
p.outro("Nothing to stop.");
|
|
1199
|
+
return;
|
|
1200
|
+
}
|
|
1201
|
+
const survivors = new Set(portPids);
|
|
1202
|
+
if (pidfilePid) survivors.add(pidfilePid);
|
|
1203
|
+
p.log.warn(`Engine not responding on :${port}, but ${survivors.size} process(es) still hold the port or pidfile: ${[...survivors].join(", ")}`);
|
|
1204
|
+
p.log.info(`Preserving ~/.agentmemory/iii.pid. Investigate before manual cleanup:\n ps -p ${[...survivors].join(",")} -o pid,ppid,comm,etime\n ${IS_WINDOWS ? "netstat -ano | findstr :" + port : "lsof -i :" + port}`);
|
|
1205
|
+
process.exit(1);
|
|
1206
|
+
}
|
|
1207
|
+
if (!state) {
|
|
1208
|
+
const compose = discoverComposeFile();
|
|
1209
|
+
if (compose && pidfilePid === null) {
|
|
1210
|
+
p.log.error(`Engine is running on :${port} but no pidfile or state file is present. It may have been started via Docker compose by a different shell. Refusing to signal host PIDs.\n\nStop it with:\n docker compose -f ${compose} down\n\nOr re-run with AGENTMEMORY_USE_DOCKER=1 to record state next time.`);
|
|
1211
|
+
process.exit(1);
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
1215
|
+
if (pidfilePid) candidates.add(pidfilePid);
|
|
1216
|
+
for (const pid of portPids) candidates.add(pid);
|
|
1217
|
+
if (candidates.size === 0) {
|
|
1218
|
+
p.log.error(`Could not locate engine process. Try:\n ${IS_WINDOWS ? "netstat -ano | findstr :" + port : "lsof -i :" + port + " -t | xargs kill -9"}`);
|
|
1219
|
+
process.exit(1);
|
|
1220
|
+
}
|
|
1221
|
+
let allStopped = true;
|
|
1222
|
+
for (const pid of candidates) {
|
|
1223
|
+
const s = p.spinner();
|
|
1224
|
+
s.start(`Stopping iii-engine (pid ${pid})...`);
|
|
1225
|
+
const ok = await signalAndWait(pid, "SIGTERM", 3e3);
|
|
1226
|
+
s.stop(ok ? `Stopped pid ${pid}` : `Failed to stop pid ${pid}`);
|
|
1227
|
+
if (!ok) allStopped = false;
|
|
1228
|
+
}
|
|
1229
|
+
clearEnginePidfile();
|
|
1230
|
+
clearEngineState();
|
|
1231
|
+
if (!allStopped) {
|
|
1232
|
+
p.log.error("One or more engine processes survived SIGKILL. Investigate with `ps`.");
|
|
1233
|
+
process.exit(1);
|
|
1234
|
+
}
|
|
1235
|
+
p.outro("Stopped. Memories persisted to disk; restart anytime with: npx @agentmemory/agentmemory");
|
|
1236
|
+
}
|
|
874
1237
|
async function runMcp() {
|
|
875
|
-
await import("./standalone-
|
|
1238
|
+
await import("./standalone-Cf5sp0XM.mjs");
|
|
876
1239
|
}
|
|
877
1240
|
async function runImportJsonl() {
|
|
878
1241
|
const VALUE_FLAGS = new Set(["--port", "--tools"]);
|
|
@@ -977,10 +1340,12 @@ async function runImportJsonl() {
|
|
|
977
1340
|
}
|
|
978
1341
|
}
|
|
979
1342
|
({
|
|
1343
|
+
init: runInit,
|
|
980
1344
|
status: runStatus,
|
|
981
1345
|
doctor: runDoctor,
|
|
982
1346
|
demo: runDemo,
|
|
983
1347
|
upgrade: runUpgrade,
|
|
1348
|
+
stop: runStop,
|
|
984
1349
|
mcp: runMcp,
|
|
985
1350
|
"import-jsonl": runImportJsonl
|
|
986
1351
|
}[args[0] ?? ""] ?? main)().catch((err) => {
|