@agentmemory/agentmemory 0.8.1 → 0.8.5
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 +578 -668
- package/dist/cli.mjs +419 -95
- package/dist/cli.mjs.map +1 -1
- package/dist/docker-compose.yml +5 -5
- package/dist/iii-config.docker.yaml +51 -0
- package/dist/iii-config.yaml +2 -2
- package/dist/index.mjs +407 -186
- package/dist/index.mjs.map +1 -1
- package/dist/{src-Df36IFVL.mjs → src-BuDB8dPq.mjs} +409 -1330
- package/dist/src-BuDB8dPq.mjs.map +1 -0
- package/dist/standalone-CxAvUMQk.mjs +267 -0
- package/dist/standalone-CxAvUMQk.mjs.map +1 -0
- package/dist/standalone.d.mts +22 -1
- package/dist/standalone.d.mts.map +1 -0
- package/dist/standalone.mjs +94 -61
- package/dist/standalone.mjs.map +1 -1
- package/dist/tools-registry-B9EXJvIf.mjs +1089 -0
- package/dist/tools-registry-B9EXJvIf.mjs.map +1 -0
- package/dist/viewer/index.html +101 -19
- package/docker-compose.yml +5 -5
- package/iii-config.yaml +2 -2
- package/package.json +11 -4
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/dist/src-Df36IFVL.mjs.map +0 -1
package/dist/cli.mjs
CHANGED
|
@@ -1,13 +1,84 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { execFileSync,
|
|
2
|
+
import { execFileSync, spawn } from "node:child_process";
|
|
3
3
|
import { existsSync } from "node:fs";
|
|
4
|
-
import { dirname, join } from "node:path";
|
|
4
|
+
import { delimiter, dirname, join } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { platform } from "node:os";
|
|
6
7
|
import * as p from "@clack/prompts";
|
|
8
|
+
import { createHash } from "node:crypto";
|
|
7
9
|
|
|
10
|
+
//#region src/state/schema.ts
|
|
11
|
+
const KV = {
|
|
12
|
+
sessions: "mem:sessions",
|
|
13
|
+
observations: (sessionId) => `mem:obs:${sessionId}`,
|
|
14
|
+
memories: "mem:memories",
|
|
15
|
+
summaries: "mem:summaries",
|
|
16
|
+
config: "mem:config",
|
|
17
|
+
metrics: "mem:metrics",
|
|
18
|
+
health: "mem:health",
|
|
19
|
+
embeddings: (obsId) => `mem:emb:${obsId}`,
|
|
20
|
+
bm25Index: "mem:index:bm25",
|
|
21
|
+
relations: "mem:relations",
|
|
22
|
+
profiles: "mem:profiles",
|
|
23
|
+
claudeBridge: "mem:claude-bridge",
|
|
24
|
+
graphNodes: "mem:graph:nodes",
|
|
25
|
+
graphEdges: "mem:graph:edges",
|
|
26
|
+
semantic: "mem:semantic",
|
|
27
|
+
procedural: "mem:procedural",
|
|
28
|
+
teamShared: (teamId) => `mem:team:${teamId}:shared`,
|
|
29
|
+
teamUsers: (teamId, userId) => `mem:team:${teamId}:users:${userId}`,
|
|
30
|
+
teamProfile: (teamId) => `mem:team:${teamId}:profile`,
|
|
31
|
+
audit: "mem:audit",
|
|
32
|
+
actions: "mem:actions",
|
|
33
|
+
actionEdges: "mem:action-edges",
|
|
34
|
+
leases: "mem:leases",
|
|
35
|
+
routines: "mem:routines",
|
|
36
|
+
routineRuns: "mem:routine-runs",
|
|
37
|
+
signals: "mem:signals",
|
|
38
|
+
checkpoints: "mem:checkpoints",
|
|
39
|
+
mesh: "mem:mesh",
|
|
40
|
+
sketches: "mem:sketches",
|
|
41
|
+
facets: "mem:facets",
|
|
42
|
+
sentinels: "mem:sentinels",
|
|
43
|
+
crystals: "mem:crystals",
|
|
44
|
+
lessons: "mem:lessons",
|
|
45
|
+
insights: "mem:insights",
|
|
46
|
+
graphEdgeHistory: "mem:graph:edge-history",
|
|
47
|
+
enrichedChunks: (sessionId) => `mem:enriched:${sessionId}`,
|
|
48
|
+
latentEmbeddings: (obsId) => `mem:latent:${obsId}`,
|
|
49
|
+
retentionScores: "mem:retention",
|
|
50
|
+
accessLog: "mem:access"
|
|
51
|
+
};
|
|
52
|
+
const STREAM = {
|
|
53
|
+
name: "mem-live",
|
|
54
|
+
group: (sessionId) => sessionId,
|
|
55
|
+
viewerGroup: "viewer"
|
|
56
|
+
};
|
|
57
|
+
function generateId(prefix) {
|
|
58
|
+
return `${prefix}_${Date.now().toString(36)}_${crypto.randomUUID().replace(/-/g, "").slice(0, 12)}`;
|
|
59
|
+
}
|
|
60
|
+
function fingerprintId(prefix, content) {
|
|
61
|
+
return `${prefix}_${createHash("sha256").update(content).digest("hex").slice(0, 16)}`;
|
|
62
|
+
}
|
|
63
|
+
function jaccardSimilarity(a, b) {
|
|
64
|
+
const setA = new Set(a.split(/\s+/).filter((t) => t.length > 2));
|
|
65
|
+
const setB = new Set(b.split(/\s+/).filter((t) => t.length > 2));
|
|
66
|
+
if (setA.size === 0 && setB.size === 0) return 1;
|
|
67
|
+
if (setA.size === 0 || setB.size === 0) return 0;
|
|
68
|
+
let intersection = 0;
|
|
69
|
+
for (const word of setA) if (setB.has(word)) intersection++;
|
|
70
|
+
return intersection / (setA.size + setB.size - intersection);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
//#endregion
|
|
8
74
|
//#region src/cli.ts
|
|
9
75
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
76
|
const args = process.argv.slice(2);
|
|
77
|
+
const IS_WINDOWS = platform() === "win32";
|
|
78
|
+
const IS_VERBOSE = args.includes("--verbose") || args.includes("-v");
|
|
79
|
+
function vlog(msg) {
|
|
80
|
+
if (IS_VERBOSE) p.log.info(`[verbose] ${msg}`);
|
|
81
|
+
}
|
|
11
82
|
if (args.includes("--help") || args.includes("-h")) {
|
|
12
83
|
console.log(`
|
|
13
84
|
agentmemory — persistent memory for AI coding agents
|
|
@@ -17,17 +88,22 @@ Usage: agentmemory [command] [options]
|
|
|
17
88
|
Commands:
|
|
18
89
|
(default) Start agentmemory worker
|
|
19
90
|
status Show connection status, memory count, and health
|
|
91
|
+
demo Seed sample sessions and show recall in action
|
|
92
|
+
mcp Start standalone MCP server (no engine required)
|
|
20
93
|
|
|
21
94
|
Options:
|
|
22
95
|
--help, -h Show this help
|
|
96
|
+
--verbose, -v Show engine stderr and diagnostic info on startup
|
|
23
97
|
--tools all|core Tool visibility (default: core = 7 tools)
|
|
24
98
|
--no-engine Skip auto-starting iii-engine
|
|
25
99
|
--port <N> Override REST port (default: 3111)
|
|
26
100
|
|
|
27
101
|
Quick start:
|
|
28
|
-
npx @agentmemory/agentmemory # start
|
|
102
|
+
npx @agentmemory/agentmemory # start with local iii-engine or Docker
|
|
29
103
|
npx @agentmemory/agentmemory status # check health
|
|
30
|
-
npx agentmemory
|
|
104
|
+
npx @agentmemory/agentmemory demo # try it in 30 seconds (needs server running)
|
|
105
|
+
npx @agentmemory/agentmemory mcp # standalone MCP server (no engine)
|
|
106
|
+
npx agentmemory-mcp # same as above (shim package)
|
|
31
107
|
`);
|
|
32
108
|
process.exit(0);
|
|
33
109
|
}
|
|
@@ -57,94 +133,106 @@ function findIiiConfig() {
|
|
|
57
133
|
return "";
|
|
58
134
|
}
|
|
59
135
|
function whichBinary(name) {
|
|
60
|
-
const cmd =
|
|
136
|
+
const cmd = IS_WINDOWS ? "where" : "which";
|
|
61
137
|
try {
|
|
62
|
-
return execFileSync(cmd, [name], { encoding: "utf-8" }).trim().
|
|
138
|
+
return execFileSync(cmd, [name], { encoding: "utf-8" }).split(/\r?\n/).map((line) => line.trim()).find((line) => line.length > 0) ?? null;
|
|
63
139
|
} catch {
|
|
64
140
|
return null;
|
|
65
141
|
}
|
|
66
142
|
}
|
|
67
|
-
|
|
68
|
-
if (
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
return
|
|
72
|
-
}
|
|
73
|
-
if (!whichBinary("curl")) {
|
|
74
|
-
p.log.warn("curl not found — cannot auto-install iii-engine.");
|
|
75
|
-
return false;
|
|
143
|
+
function fallbackIiiPaths() {
|
|
144
|
+
if (IS_WINDOWS) {
|
|
145
|
+
const userProfile = process.env["USERPROFILE"];
|
|
146
|
+
if (!userProfile) return [];
|
|
147
|
+
return [join(userProfile, ".local", "bin", "iii.exe"), join(userProfile, "bin", "iii.exe")];
|
|
76
148
|
}
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
149
|
+
const home = process.env["HOME"];
|
|
150
|
+
if (!home) return ["/usr/local/bin/iii"];
|
|
151
|
+
return [join(home, ".local", "bin", "iii"), "/usr/local/bin/iii"];
|
|
152
|
+
}
|
|
153
|
+
let startupFailure = null;
|
|
154
|
+
function spawnEngineBackground(bin, spawnArgs, label) {
|
|
155
|
+
vlog(`spawn: ${bin} ${spawnArgs.join(" ")}`);
|
|
156
|
+
const child = spawn(bin, spawnArgs, {
|
|
157
|
+
detached: true,
|
|
158
|
+
stdio: [
|
|
159
|
+
"ignore",
|
|
160
|
+
"ignore",
|
|
161
|
+
"pipe"
|
|
162
|
+
],
|
|
163
|
+
windowsHide: true
|
|
80
164
|
});
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
for (const iiiPath of iiiPaths) if (existsSync(iiiPath)) {
|
|
101
|
-
p.log.info(`Found iii at: ${iiiPath}`);
|
|
102
|
-
process.env["PATH"] = `${dirname(iiiPath)}:${process.env["PATH"]}`;
|
|
103
|
-
return true;
|
|
165
|
+
const stderrChunks = [];
|
|
166
|
+
let stderrBytes = 0;
|
|
167
|
+
const MAX_STDERR_CAPTURE = 16 * 1024;
|
|
168
|
+
child.stderr?.on("data", (chunk) => {
|
|
169
|
+
if (stderrBytes >= MAX_STDERR_CAPTURE) return;
|
|
170
|
+
const slice = chunk.subarray(0, MAX_STDERR_CAPTURE - stderrBytes);
|
|
171
|
+
stderrChunks.push(slice);
|
|
172
|
+
stderrBytes += slice.length;
|
|
173
|
+
});
|
|
174
|
+
child.on("exit", (code, signal) => {
|
|
175
|
+
if (code !== null && code !== 0 || code === null && signal !== null) {
|
|
176
|
+
const stderr = Buffer.concat(stderrChunks).toString("utf-8");
|
|
177
|
+
startupFailure = {
|
|
178
|
+
kind: label.includes("Docker") ? "docker-crashed" : "engine-crashed",
|
|
179
|
+
stderr: stderr.trim() || (signal ? `process killed by signal ${signal}` : `process exited with code ${code}`),
|
|
180
|
+
binary: bin
|
|
181
|
+
};
|
|
182
|
+
vlog(`engine exited early: code=${code} signal=${signal}`);
|
|
183
|
+
if (IS_VERBOSE && stderr.trim()) p.log.error(`engine stderr:\n${stderr}`);
|
|
104
184
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
p.log.error(err instanceof Error ? err.message : String(err));
|
|
109
|
-
return false;
|
|
110
|
-
}
|
|
185
|
+
});
|
|
186
|
+
child.unref();
|
|
187
|
+
return child;
|
|
111
188
|
}
|
|
112
189
|
async function startEngine() {
|
|
113
190
|
const configPath = findIiiConfig();
|
|
114
191
|
let iiiBin = whichBinary("iii");
|
|
115
|
-
|
|
116
|
-
if (await installIii()) iiiBin = whichBinary("iii");
|
|
117
|
-
}
|
|
192
|
+
vlog(`iii binary: ${iiiBin ?? "(not on PATH)"}, config: ${configPath || "(not found)"}`);
|
|
118
193
|
if (iiiBin && configPath) {
|
|
119
194
|
const s = p.spinner();
|
|
120
195
|
s.start(`Starting iii-engine: ${iiiBin}`);
|
|
121
|
-
|
|
122
|
-
detached: true,
|
|
123
|
-
stdio: "ignore"
|
|
124
|
-
}).unref();
|
|
196
|
+
spawnEngineBackground(iiiBin, ["--config", configPath], "iii-engine");
|
|
125
197
|
s.stop("iii-engine process started");
|
|
126
198
|
return true;
|
|
127
199
|
}
|
|
128
200
|
const dockerBin = whichBinary("docker");
|
|
129
|
-
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
201
|
+
vlog(`docker binary: ${dockerBin ?? "(not on PATH)"}`);
|
|
202
|
+
const composeFile = [
|
|
203
|
+
join(__dirname, "..", "docker-compose.yml"),
|
|
204
|
+
join(__dirname, "docker-compose.yml"),
|
|
205
|
+
join(process.cwd(), "docker-compose.yml")
|
|
206
|
+
].find((c) => existsSync(c));
|
|
207
|
+
vlog(`docker-compose.yml: ${composeFile ?? "(not found)"}`);
|
|
208
|
+
if (dockerBin && composeFile) {
|
|
133
209
|
const s = p.spinner();
|
|
134
210
|
s.start("Starting iii-engine via Docker...");
|
|
135
|
-
|
|
211
|
+
spawnEngineBackground(dockerBin, [
|
|
136
212
|
"compose",
|
|
137
213
|
"-f",
|
|
138
214
|
composeFile,
|
|
139
215
|
"up",
|
|
140
216
|
"-d"
|
|
141
|
-
],
|
|
142
|
-
detached: true,
|
|
143
|
-
stdio: "ignore"
|
|
144
|
-
}).unref();
|
|
217
|
+
], "iii-engine via Docker");
|
|
145
218
|
s.stop("Docker compose started");
|
|
146
219
|
return true;
|
|
147
220
|
}
|
|
221
|
+
for (const iiiPath of fallbackIiiPaths()) if (existsSync(iiiPath)) {
|
|
222
|
+
p.log.info(`Found iii at: ${iiiPath}`);
|
|
223
|
+
process.env["PATH"] = `${dirname(iiiPath)}${delimiter}${process.env["PATH"] ?? ""}`;
|
|
224
|
+
iiiBin = iiiPath;
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
if (iiiBin && configPath) {
|
|
228
|
+
const s = p.spinner();
|
|
229
|
+
s.start(`Starting iii-engine: ${iiiBin}`);
|
|
230
|
+
spawnEngineBackground(iiiBin, ["--config", configPath], "iii-engine");
|
|
231
|
+
s.stop("iii-engine process started");
|
|
232
|
+
return true;
|
|
233
|
+
}
|
|
234
|
+
if (!iiiBin && (!dockerBin || !composeFile)) startupFailure = { kind: "no-engine" };
|
|
235
|
+
else if (!composeFile && dockerBin) startupFailure = { kind: "no-docker-compose" };
|
|
148
236
|
return false;
|
|
149
237
|
}
|
|
150
238
|
async function waitForEngine(timeoutMs) {
|
|
@@ -155,32 +243,60 @@ async function waitForEngine(timeoutMs) {
|
|
|
155
243
|
}
|
|
156
244
|
return false;
|
|
157
245
|
}
|
|
246
|
+
function installInstructions() {
|
|
247
|
+
if (IS_WINDOWS) return [
|
|
248
|
+
"agentmemory requires the `iii-engine` runtime. Pick one:",
|
|
249
|
+
"",
|
|
250
|
+
" A) Download the prebuilt Windows binary:",
|
|
251
|
+
" 1. Open https://github.com/iii-hq/iii/releases/latest",
|
|
252
|
+
" 2. Download iii-x86_64-pc-windows-msvc.zip",
|
|
253
|
+
" (or iii-aarch64-pc-windows-msvc.zip on ARM)",
|
|
254
|
+
" 3. Extract iii.exe and either add its folder to PATH",
|
|
255
|
+
" or move it to %USERPROFILE%\\.local\\bin\\iii.exe",
|
|
256
|
+
" 4. Re-run: npx @agentmemory/agentmemory",
|
|
257
|
+
"",
|
|
258
|
+
" B) Docker Desktop:",
|
|
259
|
+
" 1. Install Docker Desktop for Windows",
|
|
260
|
+
" 2. Start Docker Desktop (engine must be running)",
|
|
261
|
+
" 3. Re-run: npx @agentmemory/agentmemory",
|
|
262
|
+
"",
|
|
263
|
+
"Or skip the engine entirely for standalone MCP:",
|
|
264
|
+
" npx @agentmemory/agentmemory mcp"
|
|
265
|
+
];
|
|
266
|
+
return [
|
|
267
|
+
"agentmemory requires the `iii-engine` runtime. Pick one:",
|
|
268
|
+
"",
|
|
269
|
+
" A) curl -fsSL https://install.iii.dev/iii/main/install.sh | sh",
|
|
270
|
+
" (installs the prebuilt iii binary into ~/.local/bin/iii)",
|
|
271
|
+
"",
|
|
272
|
+
" B) Docker: install Docker Desktop or docker-ce, then re-run",
|
|
273
|
+
"",
|
|
274
|
+
"Or skip the engine entirely for standalone MCP:",
|
|
275
|
+
" npx @agentmemory/agentmemory mcp",
|
|
276
|
+
"",
|
|
277
|
+
"Docs: https://iii.dev/docs"
|
|
278
|
+
];
|
|
279
|
+
}
|
|
280
|
+
function portInUseDiagnostic(port) {
|
|
281
|
+
return IS_WINDOWS ? ` netstat -ano | findstr :${port}` : ` lsof -i :${port} # or: ss -tlnp | grep :${port}`;
|
|
282
|
+
}
|
|
158
283
|
async function main() {
|
|
159
284
|
p.intro("agentmemory");
|
|
160
285
|
if (skipEngine) {
|
|
161
286
|
p.log.info("Skipping engine check (--no-engine)");
|
|
162
|
-
await import("./src-
|
|
287
|
+
await import("./src-BuDB8dPq.mjs");
|
|
163
288
|
return;
|
|
164
289
|
}
|
|
165
290
|
if (await isEngineRunning()) {
|
|
166
291
|
p.log.success("iii-engine is running");
|
|
167
|
-
await import("./src-
|
|
292
|
+
await import("./src-BuDB8dPq.mjs");
|
|
168
293
|
return;
|
|
169
294
|
}
|
|
170
295
|
if (!await startEngine()) {
|
|
171
296
|
p.log.error("Could not start iii-engine.");
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
" cargo install iii-engine",
|
|
176
|
-
"",
|
|
177
|
-
"Or use Docker:",
|
|
178
|
-
" docker pull iiidev/iii:latest",
|
|
179
|
-
"",
|
|
180
|
-
"Docs: https://iii.dev/docs",
|
|
181
|
-
"",
|
|
182
|
-
"Or skip with: agentmemory --no-engine"
|
|
183
|
-
].join("\n"), "Setup required");
|
|
297
|
+
const lines = installInstructions();
|
|
298
|
+
if (startupFailure?.kind === "no-docker-compose") lines.unshift("Docker is installed but docker-compose.yml is missing from this", "install. Re-install with: npm install -g @agentmemory/agentmemory", "");
|
|
299
|
+
p.note(lines.join("\n"), "Setup required");
|
|
184
300
|
process.exit(1);
|
|
185
301
|
}
|
|
186
302
|
const s = p.spinner();
|
|
@@ -188,11 +304,36 @@ async function main() {
|
|
|
188
304
|
if (!await waitForEngine(15e3)) {
|
|
189
305
|
const port = getRestPort();
|
|
190
306
|
s.stop("iii-engine did not become ready within 15s");
|
|
191
|
-
|
|
307
|
+
if (startupFailure?.kind === "engine-crashed" || startupFailure?.kind === "docker-crashed") {
|
|
308
|
+
p.log.error("The iii-engine process crashed on startup.");
|
|
309
|
+
if (startupFailure.binary) p.log.info(`Binary: ${startupFailure.binary}`);
|
|
310
|
+
if (startupFailure.stderr) p.note(startupFailure.stderr, "engine stderr");
|
|
311
|
+
else p.log.info("No stderr was captured. Re-run with --verbose for more detail.");
|
|
312
|
+
p.note([
|
|
313
|
+
"Common causes:",
|
|
314
|
+
" - iii-engine version mismatch — reinstall the latest binary",
|
|
315
|
+
" (sh script on macOS/Linux, GitHub release zip on Windows)",
|
|
316
|
+
" - Docker Desktop not running (if you're using the Docker path)",
|
|
317
|
+
" - Port already in use (see below)",
|
|
318
|
+
"",
|
|
319
|
+
"See https://iii.dev/docs for current install instructions."
|
|
320
|
+
].join("\n"), "Troubleshooting");
|
|
321
|
+
} else {
|
|
322
|
+
p.log.error("The engine process started but the REST API never responded.");
|
|
323
|
+
p.note([
|
|
324
|
+
`Check whether port ${port} is already bound by another process:`,
|
|
325
|
+
portInUseDiagnostic(port),
|
|
326
|
+
"",
|
|
327
|
+
"If it is, free the port or override: agentmemory --port <N>",
|
|
328
|
+
"",
|
|
329
|
+
"If it isn't, a firewall may be blocking 127.0.0.1:" + port + ".",
|
|
330
|
+
"Re-run with --verbose to see engine stderr."
|
|
331
|
+
].join("\n"), "Troubleshooting");
|
|
332
|
+
}
|
|
192
333
|
process.exit(1);
|
|
193
334
|
}
|
|
194
335
|
s.stop("iii-engine is ready");
|
|
195
|
-
await import("./src-
|
|
336
|
+
await import("./src-BuDB8dPq.mjs");
|
|
196
337
|
}
|
|
197
338
|
async function runStatus() {
|
|
198
339
|
const port = getRestPort();
|
|
@@ -204,46 +345,229 @@ async function runStatus() {
|
|
|
204
345
|
process.exit(1);
|
|
205
346
|
}
|
|
206
347
|
try {
|
|
207
|
-
const [healthRes, sessionsRes, graphRes] = await Promise.all([
|
|
348
|
+
const [healthRes, sessionsRes, graphRes, memoriesRes] = await Promise.all([
|
|
208
349
|
fetch(`${base}/agentmemory/health`, { signal: AbortSignal.timeout(5e3) }).then((r) => r.json()).catch(() => null),
|
|
209
350
|
fetch(`${base}/agentmemory/sessions`, { signal: AbortSignal.timeout(5e3) }).then((r) => r.json()).catch(() => null),
|
|
210
|
-
fetch(`${base}/agentmemory/graph/stats`, { signal: AbortSignal.timeout(5e3) }).then((r) => r.json()).catch(() => null)
|
|
351
|
+
fetch(`${base}/agentmemory/graph/stats`, { signal: AbortSignal.timeout(5e3) }).then((r) => r.json()).catch(() => null),
|
|
352
|
+
fetch(`${base}/agentmemory/export`, { signal: AbortSignal.timeout(5e3) }).then((r) => r.json()).catch(() => null)
|
|
211
353
|
]);
|
|
212
354
|
const h = healthRes?.health;
|
|
213
355
|
const status = healthRes?.status || "unknown";
|
|
214
356
|
const version = healthRes?.version || "?";
|
|
215
357
|
const sessions = Array.isArray(sessionsRes?.sessions) ? sessionsRes.sessions.length : 0;
|
|
216
|
-
h?.workers?.[0]?.function_count;
|
|
217
358
|
const nodes = graphRes?.nodes || 0;
|
|
218
359
|
const edges = graphRes?.edges || 0;
|
|
219
360
|
const cb = healthRes?.circuitBreaker?.state || "closed";
|
|
220
361
|
const heapMB = h?.memory ? Math.round(h.memory.heapUsed / 1048576) : 0;
|
|
221
362
|
const uptime = h?.uptimeSeconds ? Math.round(h.uptimeSeconds) : 0;
|
|
363
|
+
const obsCount = memoriesRes?.observations?.length || 0;
|
|
364
|
+
const memCount = memoriesRes?.memories?.length || 0;
|
|
365
|
+
const estFullTokens = obsCount * 80;
|
|
366
|
+
const estInjectedTokens = Math.min(obsCount, 50) * 38;
|
|
367
|
+
const tokensSaved = estFullTokens - estInjectedTokens;
|
|
368
|
+
const pctSaved = estFullTokens > 0 ? Math.round(tokensSaved / estFullTokens * 100) : 0;
|
|
222
369
|
p.log.success(`Connected — v${version} on port ${port}`);
|
|
223
370
|
const lines = [
|
|
224
|
-
`Health:
|
|
225
|
-
`Sessions:
|
|
226
|
-
`
|
|
227
|
-
`
|
|
228
|
-
`
|
|
229
|
-
`
|
|
230
|
-
`
|
|
371
|
+
`Health: ${status === "healthy" ? "✓ healthy" : status}`,
|
|
372
|
+
`Sessions: ${sessions}`,
|
|
373
|
+
`Observations: ${obsCount}`,
|
|
374
|
+
`Memories: ${memCount}`,
|
|
375
|
+
`Graph: ${nodes} nodes, ${edges} edges`,
|
|
376
|
+
`Circuit: ${cb}`,
|
|
377
|
+
`Heap: ${heapMB} MB`,
|
|
378
|
+
`Uptime: ${uptime}s`,
|
|
379
|
+
`Viewer: http://localhost:${port + 2}`
|
|
231
380
|
];
|
|
381
|
+
if (obsCount > 0) {
|
|
382
|
+
lines.push("");
|
|
383
|
+
lines.push(`Token savings: ~${tokensSaved.toLocaleString()} tokens saved (${pctSaved}% reduction)`);
|
|
384
|
+
lines.push(` Full context: ~${estFullTokens.toLocaleString()} tokens`);
|
|
385
|
+
lines.push(` Injected: ~${estInjectedTokens.toLocaleString()} tokens`);
|
|
386
|
+
}
|
|
232
387
|
p.note(lines.join("\n"), "agentmemory");
|
|
233
388
|
} catch (err) {
|
|
234
389
|
p.log.error(err instanceof Error ? err.message : String(err));
|
|
235
390
|
process.exit(1);
|
|
236
391
|
}
|
|
237
392
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
393
|
+
function buildDemoSessions() {
|
|
394
|
+
return [
|
|
395
|
+
{
|
|
396
|
+
id: generateId("demo"),
|
|
397
|
+
title: "Session 1: JWT auth setup",
|
|
398
|
+
observations: [
|
|
399
|
+
{
|
|
400
|
+
toolName: "Write",
|
|
401
|
+
toolInput: { file_path: "src/middleware/auth.ts" },
|
|
402
|
+
toolOutput: "Created JWT middleware using jose library. Tokens expire after 30 days. Chose jose over jsonwebtoken for Edge compatibility."
|
|
403
|
+
},
|
|
404
|
+
{
|
|
405
|
+
toolName: "Write",
|
|
406
|
+
toolInput: { file_path: "test/auth.test.ts" },
|
|
407
|
+
toolOutput: "Added token validation tests covering expired, malformed, and valid cases."
|
|
408
|
+
},
|
|
409
|
+
{
|
|
410
|
+
toolName: "Bash",
|
|
411
|
+
toolInput: { command: "npm test" },
|
|
412
|
+
toolOutput: "All 12 auth tests passing."
|
|
413
|
+
}
|
|
414
|
+
]
|
|
415
|
+
},
|
|
416
|
+
{
|
|
417
|
+
id: generateId("demo"),
|
|
418
|
+
title: "Session 2: Database migration debugging",
|
|
419
|
+
observations: [{
|
|
420
|
+
toolName: "Read",
|
|
421
|
+
toolInput: { file_path: "prisma/schema.prisma" },
|
|
422
|
+
toolOutput: "Found N+1 query issue in user relations. Need to add include on posts query."
|
|
423
|
+
}, {
|
|
424
|
+
toolName: "Edit",
|
|
425
|
+
toolInput: { file_path: "src/api/users.ts" },
|
|
426
|
+
toolOutput: "Fixed N+1 by adding Prisma include. Query time dropped from 450ms to 28ms."
|
|
427
|
+
}]
|
|
428
|
+
},
|
|
429
|
+
{
|
|
430
|
+
id: generateId("demo"),
|
|
431
|
+
title: "Session 3: Rate limiting",
|
|
432
|
+
observations: [{
|
|
433
|
+
toolName: "Write",
|
|
434
|
+
toolInput: { file_path: "src/middleware/ratelimit.ts" },
|
|
435
|
+
toolOutput: "Added rate limiting middleware with 100 req/min default. Uses in-memory store for dev, Redis for prod."
|
|
436
|
+
}]
|
|
437
|
+
}
|
|
438
|
+
];
|
|
439
|
+
}
|
|
440
|
+
async function postJson(url, body, timeoutMs = 5e3) {
|
|
441
|
+
try {
|
|
442
|
+
const res = await fetch(url, {
|
|
443
|
+
method: "POST",
|
|
444
|
+
headers: { "Content-Type": "application/json" },
|
|
445
|
+
body: JSON.stringify(body),
|
|
446
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
447
|
+
});
|
|
448
|
+
if (!res.ok) return null;
|
|
449
|
+
return await res.json().catch(() => null);
|
|
450
|
+
} catch {
|
|
451
|
+
return null;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
async function postJsonStrict(url, body, timeoutMs = 5e3) {
|
|
455
|
+
const res = await fetch(url, {
|
|
456
|
+
method: "POST",
|
|
457
|
+
headers: { "Content-Type": "application/json" },
|
|
458
|
+
body: JSON.stringify(body),
|
|
459
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
460
|
+
});
|
|
461
|
+
if (!res.ok) {
|
|
462
|
+
const errBody = await res.text().catch(() => "");
|
|
463
|
+
const suffix = errBody ? ` — ${errBody.slice(0, 200)}` : "";
|
|
464
|
+
throw new Error(`POST ${url} failed: ${res.status} ${res.statusText}${suffix}`);
|
|
465
|
+
}
|
|
466
|
+
return await res.json().catch(() => null);
|
|
467
|
+
}
|
|
468
|
+
async function seedDemoSession(base, project, session) {
|
|
469
|
+
await postJsonStrict(`${base}/agentmemory/session/start`, {
|
|
470
|
+
sessionId: session.id,
|
|
471
|
+
project,
|
|
472
|
+
cwd: project
|
|
473
|
+
});
|
|
474
|
+
let stored = 0;
|
|
475
|
+
for (const obs of session.observations) {
|
|
476
|
+
const url = `${base}/agentmemory/observe`;
|
|
477
|
+
const payload = {
|
|
478
|
+
hookType: "post_tool_use",
|
|
479
|
+
sessionId: session.id,
|
|
480
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
481
|
+
data: {
|
|
482
|
+
tool_name: obs.toolName,
|
|
483
|
+
tool_input: obs.toolInput,
|
|
484
|
+
tool_output: obs.toolOutput
|
|
485
|
+
}
|
|
486
|
+
};
|
|
487
|
+
try {
|
|
488
|
+
const res = await fetch(url, {
|
|
489
|
+
method: "POST",
|
|
490
|
+
headers: { "Content-Type": "application/json" },
|
|
491
|
+
body: JSON.stringify(payload),
|
|
492
|
+
signal: AbortSignal.timeout(5e3)
|
|
493
|
+
});
|
|
494
|
+
if (res.ok) stored++;
|
|
495
|
+
else {
|
|
496
|
+
const body = await res.text().catch(() => "");
|
|
497
|
+
p.log.warn(`observe failed for ${obs.toolName}: ${res.status} ${res.statusText}${body ? ` — ${body.slice(0, 160)}` : ""}`);
|
|
498
|
+
}
|
|
499
|
+
} catch (err) {
|
|
500
|
+
p.log.warn(`observe request failed for ${obs.toolName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
await postJsonStrict(`${base}/agentmemory/session/end`, { sessionId: session.id });
|
|
504
|
+
return stored;
|
|
505
|
+
}
|
|
506
|
+
async function runDemoSearch(base, query) {
|
|
507
|
+
const items = (await postJson(`${base}/agentmemory/smart-search`, {
|
|
508
|
+
query,
|
|
509
|
+
limit: 5
|
|
510
|
+
}, 1e4))?.results ?? [];
|
|
511
|
+
return {
|
|
512
|
+
query,
|
|
513
|
+
hits: items.length,
|
|
514
|
+
topTitle: items[0]?.title ?? "(no results)"
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
async function runDemo() {
|
|
518
|
+
const port = getRestPort();
|
|
519
|
+
const base = `http://localhost:${port}`;
|
|
520
|
+
p.intro("agentmemory demo");
|
|
521
|
+
if (!await isEngineRunning()) {
|
|
522
|
+
p.log.error(`Not running — no response on port ${port}`);
|
|
523
|
+
p.log.info("Start the server first: npx @agentmemory/agentmemory");
|
|
524
|
+
process.exit(1);
|
|
525
|
+
}
|
|
526
|
+
const demoProject = "/tmp/agentmemory-demo";
|
|
527
|
+
const sessions = buildDemoSessions();
|
|
528
|
+
const sSeed = p.spinner();
|
|
529
|
+
sSeed.start("Seeding 3 demo sessions with realistic observations...");
|
|
530
|
+
let totalObs = 0;
|
|
531
|
+
for (const session of sessions) totalObs += await seedDemoSession(base, demoProject, session);
|
|
532
|
+
sSeed.stop(`Seeded ${totalObs} observations across ${sessions.length} sessions`);
|
|
533
|
+
const queries = [
|
|
534
|
+
"jwt auth middleware",
|
|
535
|
+
"database performance optimization",
|
|
536
|
+
"rate limiting"
|
|
537
|
+
];
|
|
538
|
+
const sQuery = p.spinner();
|
|
539
|
+
sQuery.start(`Running ${queries.length} smart-search queries...`);
|
|
540
|
+
const results = [];
|
|
541
|
+
for (const query of queries) results.push(await runDemoSearch(base, query));
|
|
542
|
+
sQuery.stop("Search complete");
|
|
543
|
+
const lines = [
|
|
544
|
+
`Project: ${demoProject}`,
|
|
545
|
+
`Sessions: ${sessions.length} seeded (${totalObs} observations)`,
|
|
546
|
+
"",
|
|
547
|
+
"Search results:",
|
|
548
|
+
...results.flatMap((r) => [` "${r.query}"`, ` → ${r.hits} hit(s), top: ${r.topTitle.slice(0, 60)}`]),
|
|
549
|
+
"",
|
|
550
|
+
`Notice: searching "database performance optimization"`,
|
|
551
|
+
`found the N+1 query fix — keyword matching can't do that.`,
|
|
552
|
+
"",
|
|
553
|
+
`Viewer: http://localhost:${port + 2}`,
|
|
554
|
+
`Clean up with: curl -X DELETE "${base}/agentmemory/sessions?project=${demoProject}"`
|
|
555
|
+
];
|
|
556
|
+
p.note(lines.join("\n"), "demo complete");
|
|
557
|
+
p.log.success("agentmemory is working. Point your agent at it and get back to coding.");
|
|
558
|
+
}
|
|
559
|
+
async function runMcp() {
|
|
560
|
+
await import("./standalone-CxAvUMQk.mjs");
|
|
561
|
+
}
|
|
562
|
+
({
|
|
563
|
+
status: runStatus,
|
|
564
|
+
demo: runDemo,
|
|
565
|
+
mcp: runMcp
|
|
566
|
+
}[args[0] ?? ""] ?? main)().catch((err) => {
|
|
243
567
|
p.log.error(err instanceof Error ? err.message : String(err));
|
|
244
568
|
process.exit(1);
|
|
245
569
|
});
|
|
246
570
|
|
|
247
571
|
//#endregion
|
|
248
|
-
export {
|
|
572
|
+
export { jaccardSimilarity as a, generateId as i, STREAM as n, fingerprintId as r, KV as t };
|
|
249
573
|
//# sourceMappingURL=cli.mjs.map
|