@agentmemory/agentmemory 0.9.13 → 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/README.md +23 -21
- package/dist/cli.mjs +403 -84
- package/dist/cli.mjs.map +1 -1
- package/dist/index.mjs +3 -2
- package/dist/index.mjs.map +1 -1
- package/dist/{src-Ca9oX6Hq.mjs → src-BBI-ah3h.mjs} +4 -3
- package/dist/src-BBI-ah3h.mjs.map +1 -0
- package/dist/{standalone-BpbiNqr9.mjs → standalone-Cf5sp0XM.mjs} +2 -2
- package/dist/{standalone-BpbiNqr9.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-D5l632PP.mjs → tools-registry-BDimtXJb.mjs} +2 -2
- package/dist/{tools-registry-D5l632PP.mjs.map → tools-registry-BDimtXJb.mjs.map} +1 -1
- package/package.json +6 -6
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/dist/src-Ca9oX6Hq.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";
|
|
@@ -115,6 +115,7 @@ Commands:
|
|
|
115
115
|
doctor Run diagnostic checks (server, flags, graph, providers)
|
|
116
116
|
demo Seed sample sessions and show recall in action
|
|
117
117
|
upgrade Upgrade local deps + iii runtime (best effort)
|
|
118
|
+
stop Stop the running iii-engine started by this CLI
|
|
118
119
|
mcp Start standalone MCP server (no engine required)
|
|
119
120
|
import-jsonl [p] Import Claude Code JSONL transcripts (default: ~/.claude/projects)
|
|
120
121
|
--max-files <N> | --max-files=<N>: override scan cap (default 200, max 1000;
|
|
@@ -128,8 +129,11 @@ Options:
|
|
|
128
129
|
--port <N> Override REST port (default: 3111)
|
|
129
130
|
|
|
130
131
|
Environment:
|
|
131
|
-
AGENTMEMORY_URL
|
|
132
|
-
|
|
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}).
|
|
133
137
|
|
|
134
138
|
Quick start:
|
|
135
139
|
npx @agentmemory/agentmemory # start with local iii-engine or Docker
|
|
@@ -220,6 +224,130 @@ function fallbackIiiPaths() {
|
|
|
220
224
|
if (!home) return ["/usr/local/bin/iii"];
|
|
221
225
|
return [join(home, ".local", "bin", "iii"), "/usr/local/bin/iii"];
|
|
222
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
|
+
}
|
|
223
351
|
let startupFailure = null;
|
|
224
352
|
function spawnEngineBackground(bin, spawnArgs, label) {
|
|
225
353
|
vlog(`spawn: ${bin} ${spawnArgs.join(" ")}`);
|
|
@@ -232,6 +360,8 @@ function spawnEngineBackground(bin, spawnArgs, label) {
|
|
|
232
360
|
],
|
|
233
361
|
windowsHide: true
|
|
234
362
|
});
|
|
363
|
+
const isDocker = label.includes("Docker");
|
|
364
|
+
if (!isDocker && typeof child.pid === "number") writeEnginePidfile(child.pid);
|
|
235
365
|
const stderrChunks = [];
|
|
236
366
|
let stderrBytes = 0;
|
|
237
367
|
const MAX_STDERR_CAPTURE = 16 * 1024;
|
|
@@ -245,27 +375,47 @@ function spawnEngineBackground(bin, spawnArgs, label) {
|
|
|
245
375
|
if (code !== null && code !== 0 || code === null && signal !== null) {
|
|
246
376
|
const stderr = Buffer.concat(stderrChunks).toString("utf-8");
|
|
247
377
|
startupFailure = {
|
|
248
|
-
kind:
|
|
378
|
+
kind: isDocker ? "docker-crashed" : "engine-crashed",
|
|
249
379
|
stderr: stderr.trim() || (signal ? `process killed by signal ${signal}` : `process exited with code ${code}`),
|
|
250
380
|
binary: bin
|
|
251
381
|
};
|
|
252
382
|
vlog(`engine exited early: code=${code} signal=${signal}`);
|
|
253
383
|
if (IS_VERBOSE && stderr.trim()) p.log.error(`engine stderr:\n${stderr}`);
|
|
384
|
+
if (!isDocker) clearEnginePidfile();
|
|
385
|
+
clearEngineState();
|
|
254
386
|
}
|
|
255
387
|
});
|
|
256
388
|
child.unref();
|
|
257
389
|
return child;
|
|
258
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
|
+
}
|
|
259
402
|
async function startEngine() {
|
|
260
403
|
const configPath = findIiiConfig();
|
|
261
404
|
let iiiBin = whichBinary("iii");
|
|
262
405
|
vlog(`iii binary: ${iiiBin ?? "(not on PATH)"}, config: ${configPath || "(not found)"}`);
|
|
263
|
-
if (iiiBin && configPath)
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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;
|
|
269
419
|
}
|
|
270
420
|
const dockerBin = whichBinary("docker");
|
|
271
421
|
vlog(`docker binary: ${dockerBin ?? "(not on PATH)"}`);
|
|
@@ -275,9 +425,73 @@ async function startEngine() {
|
|
|
275
425
|
join(process.cwd(), "docker-compose.yml")
|
|
276
426
|
].find((c) => existsSync(c));
|
|
277
427
|
vlog(`docker-compose.yml: ${composeFile ?? "(not found)"}`);
|
|
278
|
-
|
|
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) {
|
|
279
489
|
const s = p.spinner();
|
|
280
490
|
s.start("Starting iii-engine via Docker...");
|
|
491
|
+
writeEngineState({
|
|
492
|
+
kind: "docker",
|
|
493
|
+
composeFile
|
|
494
|
+
});
|
|
281
495
|
spawnEngineBackground(dockerBin, [
|
|
282
496
|
"compose",
|
|
283
497
|
"-f",
|
|
@@ -288,21 +502,8 @@ async function startEngine() {
|
|
|
288
502
|
s.stop("Docker compose started");
|
|
289
503
|
return true;
|
|
290
504
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
process.env["PATH"] = `${dirname(iiiPath)}${delimiter}${process.env["PATH"] ?? ""}`;
|
|
294
|
-
iiiBin = iiiPath;
|
|
295
|
-
break;
|
|
296
|
-
}
|
|
297
|
-
if (iiiBin && configPath) {
|
|
298
|
-
const s = p.spinner();
|
|
299
|
-
s.start(`Starting iii-engine: ${iiiBin}`);
|
|
300
|
-
spawnEngineBackground(iiiBin, ["--config", configPath], "iii-engine");
|
|
301
|
-
s.stop("iii-engine process started");
|
|
302
|
-
return true;
|
|
303
|
-
}
|
|
304
|
-
if (!iiiBin && (!dockerBin || !composeFile)) startupFailure = { kind: "no-engine" };
|
|
305
|
-
else if (!composeFile && dockerBin) startupFailure = { kind: "no-docker-compose" };
|
|
505
|
+
if (!composeFile && dockerBin) startupFailure = { kind: "no-docker-compose" };
|
|
506
|
+
else startupFailure = { kind: "no-engine" };
|
|
306
507
|
return false;
|
|
307
508
|
}
|
|
308
509
|
async function waitForEngine(timeoutMs) {
|
|
@@ -316,43 +517,34 @@ async function waitForEngine(timeoutMs) {
|
|
|
316
517
|
function installInstructions() {
|
|
317
518
|
const releaseUrl = iiiReleaseUrl();
|
|
318
519
|
if (IS_WINDOWS) return [
|
|
319
|
-
`agentmemory
|
|
520
|
+
`agentmemory needs iii-engine v${IIPINNED_VERSION}. Pick one:`,
|
|
320
521
|
"",
|
|
321
522
|
" A) Download the prebuilt Windows binary:",
|
|
322
523
|
` 1. Open https://github.com/iii-hq/iii/releases/tag/iii%2Fv${IIPINNED_VERSION}`,
|
|
323
|
-
` 2. Download iii-x86_64-pc-windows-msvc.zip`,
|
|
324
|
-
"
|
|
325
|
-
" 3. Extract iii.exe and either add its folder to PATH",
|
|
326
|
-
" 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)",
|
|
327
526
|
" 4. Re-run: npx @agentmemory/agentmemory",
|
|
328
527
|
"",
|
|
329
|
-
|
|
330
|
-
"
|
|
331
|
-
` 2. docker pull iiidev/iii:${IIPINNED_VERSION}`,
|
|
332
|
-
" 3. Start Docker Desktop (engine must be running)",
|
|
333
|
-
" 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",
|
|
334
530
|
"",
|
|
335
|
-
"Or skip the engine entirely
|
|
336
|
-
"
|
|
531
|
+
"Or skip the engine entirely (standalone MCP): npx @agentmemory/agentmemory mcp",
|
|
532
|
+
"",
|
|
533
|
+
"Docs: https://iii.dev/docs"
|
|
337
534
|
];
|
|
338
|
-
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}`;
|
|
339
536
|
return [
|
|
340
|
-
`agentmemory
|
|
537
|
+
`agentmemory needs iii-engine v${IIPINNED_VERSION}. Pick one:`,
|
|
341
538
|
"",
|
|
342
539
|
linuxInstall,
|
|
343
|
-
|
|
540
|
+
" Then re-run: npx @agentmemory/agentmemory",
|
|
344
541
|
"",
|
|
345
|
-
` B) Docker:
|
|
542
|
+
` B) Docker: docker pull iiidev/iii:${IIPINNED_VERSION}`,
|
|
543
|
+
" Re-run with AGENTMEMORY_USE_DOCKER=1 npx @agentmemory/agentmemory",
|
|
346
544
|
"",
|
|
347
|
-
"Or skip the engine entirely
|
|
348
|
-
" npx @agentmemory/agentmemory mcp",
|
|
545
|
+
"Or skip the engine entirely (standalone MCP): npx @agentmemory/agentmemory mcp",
|
|
349
546
|
"",
|
|
350
|
-
"Docs: https://iii.dev/docs"
|
|
351
|
-
`Why pinned: iii v0.11.6 introduces the new sandbox-everything model`,
|
|
352
|
-
`(\`iii worker add\` registration). agentmemory still uses the older`,
|
|
353
|
-
`iii-exec config-file worker model and needs a refactor before it`,
|
|
354
|
-
`runs cleanly under the new engine. Override with`,
|
|
355
|
-
`AGENTMEMORY_III_VERSION=<version> when you've migrated manually.`
|
|
547
|
+
"Docs: https://iii.dev/docs"
|
|
356
548
|
];
|
|
357
549
|
}
|
|
358
550
|
function portInUseDiagnostic(port) {
|
|
@@ -362,12 +554,12 @@ async function main() {
|
|
|
362
554
|
p.intro("agentmemory");
|
|
363
555
|
if (skipEngine) {
|
|
364
556
|
p.log.info("Skipping engine check (--no-engine)");
|
|
365
|
-
await import("./src-
|
|
557
|
+
await import("./src-BBI-ah3h.mjs");
|
|
366
558
|
return;
|
|
367
559
|
}
|
|
368
560
|
if (await isEngineRunning()) {
|
|
369
561
|
p.log.success("iii-engine is running");
|
|
370
|
-
await import("./src-
|
|
562
|
+
await import("./src-BBI-ah3h.mjs");
|
|
371
563
|
return;
|
|
372
564
|
}
|
|
373
565
|
if (!await startEngine()) {
|
|
@@ -411,7 +603,7 @@ async function main() {
|
|
|
411
603
|
process.exit(1);
|
|
412
604
|
}
|
|
413
605
|
s.stop("iii-engine is ready");
|
|
414
|
-
await import("./src-
|
|
606
|
+
await import("./src-BBI-ah3h.mjs");
|
|
415
607
|
}
|
|
416
608
|
async function apiFetch(base, path, timeoutMs = 5e3) {
|
|
417
609
|
try {
|
|
@@ -872,36 +1064,16 @@ async function runUpgrade() {
|
|
|
872
1064
|
});
|
|
873
1065
|
} else p.log.warn("No package manager found (pnpm/npm). Skipping JS dependency upgrade.");
|
|
874
1066
|
else p.log.warn("No package.json in current directory. Skipping JS dependency upgrade.");
|
|
875
|
-
const
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
}
|
|
886
|
-
if (upgradeEngine === true) {
|
|
887
|
-
const releaseUrl = iiiReleaseUrl();
|
|
888
|
-
const asset = iiiReleaseAsset();
|
|
889
|
-
const isZipAsset = asset?.endsWith(".zip") === true;
|
|
890
|
-
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}.`);
|
|
891
|
-
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}`);
|
|
892
|
-
else {
|
|
893
|
-
const binDir = join(homedir(), ".local", "bin");
|
|
894
|
-
if (!runCommand(shBin, ["-c", [
|
|
895
|
-
`mkdir -p "${binDir}"`,
|
|
896
|
-
`curl -fsSL "${releaseUrl}" | tar -xz -C "${binDir}"`,
|
|
897
|
-
`chmod +x "${binDir}/iii"`
|
|
898
|
-
].join(" && ")], {
|
|
899
|
-
label: `Installing iii-engine v${IIPINNED_VERSION} (pinned)`,
|
|
900
|
-
optional: true
|
|
901
|
-
})) 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}.`);
|
|
902
|
-
}
|
|
903
|
-
} else p.log.info("Skipped iii-engine installer.");
|
|
904
|
-
} 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.");
|
|
905
1077
|
if (dockerBin) runCommand(dockerBin, ["pull", `iiidev/iii:${IIPINNED_VERSION}`], {
|
|
906
1078
|
label: `Pulling iii Docker image v${IIPINNED_VERSION} (pinned)`,
|
|
907
1079
|
optional: true
|
|
@@ -916,8 +1088,154 @@ async function runUpgrade() {
|
|
|
916
1088
|
" 3) restart agentmemory process"
|
|
917
1089
|
].join("\n"), "agentmemory upgrade");
|
|
918
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
|
+
}
|
|
919
1237
|
async function runMcp() {
|
|
920
|
-
await import("./standalone-
|
|
1238
|
+
await import("./standalone-Cf5sp0XM.mjs");
|
|
921
1239
|
}
|
|
922
1240
|
async function runImportJsonl() {
|
|
923
1241
|
const VALUE_FLAGS = new Set(["--port", "--tools"]);
|
|
@@ -1027,6 +1345,7 @@ async function runImportJsonl() {
|
|
|
1027
1345
|
doctor: runDoctor,
|
|
1028
1346
|
demo: runDemo,
|
|
1029
1347
|
upgrade: runUpgrade,
|
|
1348
|
+
stop: runStop,
|
|
1030
1349
|
mcp: runMcp,
|
|
1031
1350
|
"import-jsonl": runImportJsonl
|
|
1032
1351
|
}[args[0] ?? ""] ?? main)().catch((err) => {
|