@agentmemory/agentmemory 0.7.8 → 0.8.0

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 CHANGED
@@ -3,8 +3,8 @@
3
3
  </p>
4
4
 
5
5
  <p align="center">
6
- <strong>Persistent memory for AI coding agents.</strong><br/>
7
- Powered by <a href="https://iii.dev">iii-engine</a>.
6
+ <strong>Your coding agent remembers everything. No more re-explaining.</strong><br/>
7
+ Persistent memory for Claude Code, Cursor, Gemini CLI, OpenCode, and any MCP client.
8
8
  </p>
9
9
 
10
10
  <p align="center">
@@ -26,9 +26,11 @@
26
26
 
27
27
  ---
28
28
 
29
- Every AI coding agent has the same blind spot. Session ends, memory vanishes. You re-explain architecture. You re-discover bugs. You re-teach preferences. Built-in memory files like CLAUDE.md and .cursorrules are 200-line sticky notes that overflow and go stale. agentmemory replaces that with a searchable, versioned, cross-agent database 43 MCP tools, triple-stream retrieval (BM25 + vector + knowledge graph), 4-tier memory consolidation, provenance-tracked citations, and cascading staleness so retired facts never pollute your context again. One instance serves Claude Code, Cursor, Codex, Windsurf, and any MCP client simultaneously. 646 tests. Zero external DB dependencies.
29
+ You explain the same architecture every session. You re-discover the same bugs. You re-teach the same preferences. Built-in memory (CLAUDE.md, .cursorrules) caps out at 200 lines and goes stale. agentmemory fixes this it silently captures what your agent does, compresses it into searchable memory, and injects the right context when the next session starts. One command. Works across agents.
30
30
 
31
- The result is measurable. On LongMemEval-S (ICLR 2025, 500 questions), agentmemory achieves **95.2% Recall@5** and **98.6% Recall@10** with hybrid BM25+vector search — within 1.4pp of raw vector baselines while using 92% fewer tokens than dumping everything into context. When an agent searches "database performance optimization," it finds the N+1 fix you made three weeks ago something keyword grep literally cannot do. Memories version automatically, supersede each other, propagate staleness to related graph nodes, and sync across agent instances via P2P mesh. Your agents stop repeating mistakes. Your context stays clean. Your sessions start fast.
31
+ **What changes:** Session 1 you set up JWT auth. Session 2 you ask for rate limiting the agent already knows your auth uses jose middleware in `src/middleware/auth.ts`, your tests cover token validation, and you chose jose over jsonwebtoken for Edge compatibility. No re-explaining. No copy-pasting. The agent just *knows*.
32
+
33
+ **95.2% retrieval accuracy** on [LongMemEval](https://arxiv.org/abs/2410.10813) (ICLR 2025). 43 MCP tools. 12 hooks. Real-time viewer. Works with Claude Code, Cursor, Gemini CLI, OpenCode, and any MCP client. 646 tests. Zero external DB dependencies.
32
34
 
33
35
  ```bash
34
36
  npx @agentmemory/agentmemory # installs iii-engine if missing, starts everything
@@ -38,7 +40,7 @@ npx @agentmemory/agentmemory # installs iii-engine if missing, starts everythi
38
40
 
39
41
  ## Why agentmemory
40
42
 
41
- AI coding agents forget everything between sessions. You explain the same architecture, re-discover the same patterns, and re-learn the same preferences every time. agentmemory fixes that.
43
+ Every coding agent forgets everything when the session ends. You waste the first 5 minutes of every session re-explaining your stack, your conventions, your recent decisions. agentmemory runs in the background and eliminates that entirely.
42
44
 
43
45
  ```
44
46
  Session 1: "Add auth to the API"
@@ -47,15 +49,14 @@ Session 1: "Add auth to the API"
47
49
  Session ends -> observations compressed into structured memory
48
50
 
49
51
  Session 2: "Now add rate limiting"
50
- agentmemory injects context from Session 1:
52
+ Agent already knows:
51
53
  - Auth uses JWT middleware in src/middleware/auth.ts
52
54
  - Tests in test/auth.test.ts cover token validation
53
- - Decision: chose jose over jsonwebtoken for Edge compatibility
54
- Agent starts with full project awareness
55
+ - You chose jose over jsonwebtoken for Edge compatibility
56
+ - The rate limit discussion from last week's debugging session
57
+ Zero re-explaining. Starts working immediately.
55
58
  ```
56
59
 
57
- No manual notes. No copy-pasting. The agent just *knows*.
58
-
59
60
  ### What it gives you
60
61
 
61
62
  | Capability | What it does |
@@ -240,6 +241,12 @@ Add to `.opencode/config.json`:
240
241
  }
241
242
  ```
242
243
 
244
+ **Any agent via SkillKit (32+ agents supported):**
245
+
246
+ ```bash
247
+ npx skillkit install agentmemory
248
+ ```
249
+
243
250
  **REST API (any agent, any language):**
244
251
 
245
252
  ```bash
@@ -297,6 +304,54 @@ If you prefer not to use the plugin, add hooks directly to `~/.claude/settings.j
297
304
  }
298
305
  ```
299
306
 
307
+ ## First Steps After Install
308
+
309
+ Once hooks are installed, memory builds silently. No action needed — just use your agent normally.
310
+
311
+ ### Session 1: Your agent works as usual
312
+
313
+ ```text
314
+ You: "Add JWT auth to the Express API"
315
+ Agent: reads files, writes code, runs tests, fixes errors
316
+ ```
317
+
318
+ agentmemory captures every tool use via PostToolUse hooks. At session end, 47 raw observations compress into structured memory:
319
+
320
+ ```json
321
+ {
322
+ "type": "file_edit",
323
+ "title": "Implement JWT middleware with jose",
324
+ "facts": ["Using jose library for Edge compatibility", "JWT tokens expire after 30 days", "Middleware in src/middleware/auth.ts"],
325
+ "concepts": ["jwt", "authentication", "jose", "middleware"],
326
+ "files": ["src/middleware/auth.ts", "src/app/api/auth/route.ts"],
327
+ "importance": 9
328
+ }
329
+ ```
330
+
331
+ ### Session 2: The payoff
332
+
333
+ You start a new session. Before the agent responds, the SessionStart hook fires and injects context (~1,900 tokens):
334
+
335
+ ```text
336
+ Agent already knows:
337
+ - Auth uses jose JWT middleware in src/middleware/auth.ts
338
+ - Tests in test/auth.test.ts cover token validation
339
+ - You chose jose over jsonwebtoken for Edge compatibility
340
+ - Rate limiting discussion from last week's debugging session
341
+ ```
342
+
343
+ No re-explaining. The agent starts working immediately.
344
+
345
+ ### How to verify it's working
346
+
347
+ ```bash
348
+ npx @agentmemory/agentmemory status # quick terminal check
349
+ curl http://localhost:3111/agentmemory/health
350
+ open http://localhost:3113 # real-time viewer
351
+ ```
352
+
353
+ After 1 session: check the Timeline tab in the viewer. After 2+ sessions: check Dashboard for memory count > 0 and the Token Savings card.
354
+
300
355
  ## How It Works
301
356
 
302
357
  ### Observation Pipeline
@@ -612,7 +667,8 @@ agentmemory needs an LLM for compressing observations and generating summaries.
612
667
  | Provider | Config | Notes |
613
668
  |----------|--------|-------|
614
669
  | **Claude subscription** (default) | No config needed | Uses `@anthropic-ai/claude-agent-sdk`. Zero cost beyond your Max/Pro plan |
615
- | **Anthropic API** | `ANTHROPIC_API_KEY` | Direct API access, per-token billing |
670
+ | **Anthropic API** | `ANTHROPIC_API_KEY` | Direct API access, per-token billing. Supports `ANTHROPIC_BASE_URL` for custom endpoints |
671
+ | **MiniMax** | `MINIMAX_API_KEY` | Anthropic-compatible API. Default model: `MiniMax-M2.7` |
616
672
  | **Gemini** | `GEMINI_API_KEY` | Also enables Gemini embeddings (free tier) |
617
673
  | **OpenRouter** | `OPENROUTER_API_KEY` | Access any model through one API |
618
674
 
@@ -625,6 +681,9 @@ Create `~/.agentmemory/.env`:
625
681
  ```env
626
682
  # LLM provider (pick one, or leave empty for Claude subscription)
627
683
  ANTHROPIC_API_KEY=sk-ant-...
684
+ # ANTHROPIC_BASE_URL=https://custom-endpoint.example.com
685
+ # MINIMAX_API_KEY=...
686
+ # MINIMAX_MODEL=MiniMax-M2.7
628
687
  # GEMINI_API_KEY=...
629
688
  # OPENROUTER_API_KEY=...
630
689
 
@@ -639,7 +698,7 @@ ANTHROPIC_API_KEY=sk-ant-...
639
698
  # VECTOR_WEIGHT=0.6
640
699
 
641
700
  # Provider fallback chain (comma-separated, tried in order)
642
- # FALLBACK_PROVIDERS=anthropic,gemini,openrouter
701
+ # FALLBACK_PROVIDERS=anthropic,minimax,gemini,openrouter
643
702
 
644
703
  # Bearer token for API auth
645
704
  # AGENTMEMORY_SECRET=your-secret-here
@@ -702,7 +761,7 @@ ANTHROPIC_API_KEY=sk-ant-...
702
761
  | `POST` | `/agentmemory/session/end` | Mark session complete |
703
762
  | `POST` | `/agentmemory/observe` | Capture observation |
704
763
  | `POST` | `/agentmemory/context` | Generate context |
705
- | `POST` | `/agentmemory/search` | Search observations (BM25) |
764
+ | `POST` | `/agentmemory/search` | Search observations (BM25). Optional `project`/`cwd` filters |
706
765
  | `POST` | `/agentmemory/smart-search` | Hybrid search with progressive disclosure |
707
766
  | `POST` | `/agentmemory/summarize` | Generate session summary |
708
767
  | `POST` | `/agentmemory/remember` | Save to long-term memory |
package/dist/cli.mjs CHANGED
@@ -12,7 +12,11 @@ if (args.includes("--help") || args.includes("-h")) {
12
12
  console.log(`
13
13
  agentmemory — persistent memory for AI coding agents
14
14
 
15
- Usage: agentmemory [options]
15
+ Usage: agentmemory [command] [options]
16
+
17
+ Commands:
18
+ (default) Start agentmemory worker
19
+ status Show connection status, memory count, and health
16
20
 
17
21
  Options:
18
22
  --help, -h Show this help
@@ -20,15 +24,10 @@ Options:
20
24
  --no-engine Skip auto-starting iii-engine
21
25
  --port <N> Override REST port (default: 3111)
22
26
 
23
- Environment:
24
- AGENTMEMORY_TOOLS=all Expose all 41 MCP tools
25
- AGENTMEMORY_SECRET=xxx Auth secret for REST/MCP
26
- CONSOLIDATION_ENABLED=true Enable auto-consolidation (off by default)
27
- OBSIDIAN_AUTO_EXPORT=true Auto-export on consolidation
28
-
29
27
  Quick start:
30
- npx @agentmemory/agentmemory # installs iii if missing, starts everything
31
- npx agentmemory-mcp # standalone MCP server (no engine needed)
28
+ npx @agentmemory/agentmemory # start everything
29
+ npx @agentmemory/agentmemory status # check health
30
+ npx agentmemory-mcp # standalone MCP (no engine)
32
31
  `);
33
32
  process.exit(0);
34
33
  }
@@ -160,12 +159,12 @@ async function main() {
160
159
  p.intro("agentmemory");
161
160
  if (skipEngine) {
162
161
  p.log.info("Skipping engine check (--no-engine)");
163
- await import("./src-Ct05bSXw.mjs");
162
+ await import("./src-qOdKVNQz.mjs");
164
163
  return;
165
164
  }
166
165
  if (await isEngineRunning()) {
167
166
  p.log.success("iii-engine is running");
168
- await import("./src-Ct05bSXw.mjs");
167
+ await import("./src-qOdKVNQz.mjs");
169
168
  return;
170
169
  }
171
170
  if (!await startEngine()) {
@@ -193,9 +192,54 @@ async function main() {
193
192
  process.exit(1);
194
193
  }
195
194
  s.stop("iii-engine is ready");
196
- await import("./src-Ct05bSXw.mjs");
195
+ await import("./src-qOdKVNQz.mjs");
197
196
  }
198
- main().catch((err) => {
197
+ async function runStatus() {
198
+ const port = getRestPort();
199
+ const base = `http://localhost:${port}`;
200
+ p.intro("agentmemory status");
201
+ if (!await isEngineRunning()) {
202
+ p.log.error(`Not running — no response on port ${port}`);
203
+ p.log.info("Start with: npx @agentmemory/agentmemory");
204
+ process.exit(1);
205
+ }
206
+ try {
207
+ const [healthRes, sessionsRes, graphRes] = await Promise.all([
208
+ fetch(`${base}/agentmemory/health`, { signal: AbortSignal.timeout(5e3) }).then((r) => r.json()).catch(() => null),
209
+ 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)
211
+ ]);
212
+ const h = healthRes?.health;
213
+ const status = healthRes?.status || "unknown";
214
+ const version = healthRes?.version || "?";
215
+ const sessions = Array.isArray(sessionsRes?.sessions) ? sessionsRes.sessions.length : 0;
216
+ h?.workers?.[0]?.function_count;
217
+ const nodes = graphRes?.nodes || 0;
218
+ const edges = graphRes?.edges || 0;
219
+ const cb = healthRes?.circuitBreaker?.state || "closed";
220
+ const heapMB = h?.memory ? Math.round(h.memory.heapUsed / 1048576) : 0;
221
+ const uptime = h?.uptimeSeconds ? Math.round(h.uptimeSeconds) : 0;
222
+ p.log.success(`Connected — v${version} on port ${port}`);
223
+ const lines = [
224
+ `Health: ${status === "healthy" ? "healthy" : status}`,
225
+ `Sessions: ${sessions}`,
226
+ `Graph: ${nodes} nodes, ${edges} edges`,
227
+ `Circuit: ${cb}`,
228
+ `Heap: ${heapMB} MB`,
229
+ `Uptime: ${uptime}s`,
230
+ `Viewer: http://localhost:${port + 2}`
231
+ ];
232
+ p.note(lines.join("\n"), "agentmemory");
233
+ } catch (err) {
234
+ p.log.error(err instanceof Error ? err.message : String(err));
235
+ process.exit(1);
236
+ }
237
+ }
238
+ if (args[0] === "status") runStatus().catch((err) => {
239
+ p.log.error(err instanceof Error ? err.message : String(err));
240
+ process.exit(1);
241
+ });
242
+ else main().catch((err) => {
199
243
  p.log.error(err instanceof Error ? err.message : String(err));
200
244
  process.exit(1);
201
245
  });
package/dist/cli.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.mjs","names":[],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { spawn, execFileSync, execSync } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport { join, dirname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport * as p from \"@clack/prompts\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst args = process.argv.slice(2);\n\nif (args.includes(\"--help\") || args.includes(\"-h\")) {\n console.log(`\nagentmemory — persistent memory for AI coding agents\n\nUsage: agentmemory [options]\n\nOptions:\n --help, -h Show this help\n --tools all|core Tool visibility (default: core = 7 tools)\n --no-engine Skip auto-starting iii-engine\n --port <N> Override REST port (default: 3111)\n\nEnvironment:\n AGENTMEMORY_TOOLS=all Expose all 41 MCP tools\n AGENTMEMORY_SECRET=xxx Auth secret for REST/MCP\n CONSOLIDATION_ENABLED=true Enable auto-consolidation (off by default)\n OBSIDIAN_AUTO_EXPORT=true Auto-export on consolidation\n\nQuick start:\n npx @agentmemory/agentmemory # installs iii if missing, starts everything\n npx agentmemory-mcp # standalone MCP server (no engine needed)\n`);\n process.exit(0);\n}\n\nconst toolsIdx = args.indexOf(\"--tools\");\nif (toolsIdx !== -1 && args[toolsIdx + 1]) {\n process.env[\"AGENTMEMORY_TOOLS\"] = args[toolsIdx + 1];\n}\n\nconst portIdx = args.indexOf(\"--port\");\nif (portIdx !== -1 && args[portIdx + 1]) {\n process.env[\"III_REST_PORT\"] = args[portIdx + 1];\n}\n\nconst skipEngine = args.includes(\"--no-engine\");\n\nfunction getRestPort(): number {\n return parseInt(process.env[\"III_REST_PORT\"] || \"3111\", 10) || 3111;\n}\n\nasync function isEngineRunning(): Promise<boolean> {\n try {\n await fetch(`http://localhost:${getRestPort()}/`, {\n signal: AbortSignal.timeout(2000),\n });\n return true;\n } catch {\n return false;\n }\n}\n\nfunction findIiiConfig(): string {\n const candidates = [\n join(__dirname, \"iii-config.yaml\"),\n join(__dirname, \"..\", \"iii-config.yaml\"),\n join(process.cwd(), \"iii-config.yaml\"),\n ];\n for (const c of candidates) {\n if (existsSync(c)) return c;\n }\n return \"\";\n}\n\nfunction whichBinary(name: string): string | null {\n const cmd = process.platform === \"win32\" ? \"where\" : \"which\";\n try {\n return execFileSync(cmd, [name], { encoding: \"utf-8\" }).trim().split(\"\\n\")[0];\n } catch {\n return null;\n }\n}\n\nasync function installIii(): Promise<boolean> {\n if (process.platform === \"win32\") {\n p.log.warn(\"Automatic iii-engine install is not supported on Windows.\");\n p.log.info(\"Install manually: https://iii.dev/docs\");\n return false;\n }\n\n const curlBin = whichBinary(\"curl\");\n if (!curlBin) {\n p.log.warn(\"curl not found — cannot auto-install iii-engine.\");\n return false;\n }\n\n const shouldInstall = await p.confirm({\n message: \"iii-engine is not installed. Install it now?\",\n initialValue: true,\n });\n\n if (p.isCancel(shouldInstall) || !shouldInstall) {\n return false;\n }\n\n const s = p.spinner();\n s.start(\"Installing iii-engine...\");\n\n try {\n execSync(\"curl -fsSL https://install.iii.dev/iii/main/install.sh | sh\", {\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n timeout: 120000,\n });\n\n const installed = whichBinary(\"iii\");\n if (installed) {\n s.stop(\"iii-engine installed successfully\");\n return true;\n }\n\n s.stop(\"Installation completed but iii not found in PATH\");\n p.log.warn(\"You may need to restart your shell or add iii to your PATH.\");\n\n const iiiPaths = [\n join(process.env[\"HOME\"] || \"\", \".local\", \"bin\", \"iii\"),\n \"/usr/local/bin/iii\",\n ];\n for (const iiiPath of iiiPaths) {\n if (existsSync(iiiPath)) {\n p.log.info(`Found iii at: ${iiiPath}`);\n process.env[\"PATH\"] = `${dirname(iiiPath)}:${process.env[\"PATH\"]}`;\n return true;\n }\n }\n\n return false;\n } catch (err) {\n s.stop(\"Failed to install iii-engine\");\n p.log.error(err instanceof Error ? err.message : String(err));\n return false;\n }\n}\n\nasync function startEngine(): Promise<boolean> {\n const configPath = findIiiConfig();\n let iiiBin = whichBinary(\"iii\");\n\n if (!iiiBin) {\n const installed = await installIii();\n if (installed) {\n iiiBin = whichBinary(\"iii\");\n }\n }\n\n if (iiiBin && configPath) {\n const s = p.spinner();\n s.start(`Starting iii-engine: ${iiiBin}`);\n const child = spawn(iiiBin, [\"--config\", configPath], {\n detached: true,\n stdio: \"ignore\",\n });\n child.unref();\n s.stop(\"iii-engine process started\");\n return true;\n }\n\n const dockerBin = whichBinary(\"docker\");\n const dockerCompose = join(__dirname, \"..\", \"docker-compose.yml\");\n const dcExists = existsSync(dockerCompose) || existsSync(join(process.cwd(), \"docker-compose.yml\"));\n\n if (dockerBin && dcExists) {\n const composeFile = existsSync(dockerCompose) ? dockerCompose : join(process.cwd(), \"docker-compose.yml\");\n const s = p.spinner();\n s.start(\"Starting iii-engine via Docker...\");\n const child = spawn(dockerBin, [\"compose\", \"-f\", composeFile, \"up\", \"-d\"], {\n detached: true,\n stdio: \"ignore\",\n });\n child.unref();\n s.stop(\"Docker compose started\");\n return true;\n }\n\n return false;\n}\n\nasync function waitForEngine(timeoutMs: number): Promise<boolean> {\n const start = Date.now();\n while (Date.now() - start < timeoutMs) {\n if (await isEngineRunning()) return true;\n await new Promise((r) => setTimeout(r, 500));\n }\n return false;\n}\n\nasync function main() {\n p.intro(\"agentmemory\");\n\n if (skipEngine) {\n p.log.info(\"Skipping engine check (--no-engine)\");\n await import(\"./index.js\");\n return;\n }\n\n if (await isEngineRunning()) {\n p.log.success(\"iii-engine is running\");\n await import(\"./index.js\");\n return;\n }\n\n const started = await startEngine();\n if (!started) {\n p.log.error(\"Could not start iii-engine.\");\n p.note(\n [\n \"Install iii-engine (pick one):\",\n \" curl -fsSL https://install.iii.dev/iii/main/install.sh | sh\",\n \" cargo install iii-engine\",\n \"\",\n \"Or use Docker:\",\n \" docker pull iiidev/iii:latest\",\n \"\",\n \"Docs: https://iii.dev/docs\",\n \"\",\n \"Or skip with: agentmemory --no-engine\",\n ].join(\"\\n\"),\n \"Setup required\",\n );\n process.exit(1);\n }\n\n const s = p.spinner();\n s.start(\"Waiting for iii-engine to be ready...\");\n\n const ready = await waitForEngine(15000);\n if (!ready) {\n const port = getRestPort();\n s.stop(\"iii-engine did not become ready within 15s\");\n p.log.error(`Check that ports ${port}, ${port + 1}, 49134 are available.`);\n process.exit(1);\n }\n\n s.stop(\"iii-engine is ready\");\n await import(\"./index.js\");\n}\n\nmain().catch((err) => {\n p.log.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"cli.mjs","names":[],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { spawn, execFileSync, execSync } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport { join, dirname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport * as p from \"@clack/prompts\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst args = process.argv.slice(2);\n\nif (args.includes(\"--help\") || args.includes(\"-h\")) {\n console.log(`\nagentmemory — persistent memory for AI coding agents\n\nUsage: agentmemory [command] [options]\n\nCommands:\n (default) Start agentmemory worker\n status Show connection status, memory count, and health\n\nOptions:\n --help, -h Show this help\n --tools all|core Tool visibility (default: core = 7 tools)\n --no-engine Skip auto-starting iii-engine\n --port <N> Override REST port (default: 3111)\n\nQuick start:\n npx @agentmemory/agentmemory # start everything\n npx @agentmemory/agentmemory status # check health\n npx agentmemory-mcp # standalone MCP (no engine)\n`);\n process.exit(0);\n}\n\nconst toolsIdx = args.indexOf(\"--tools\");\nif (toolsIdx !== -1 && args[toolsIdx + 1]) {\n process.env[\"AGENTMEMORY_TOOLS\"] = args[toolsIdx + 1];\n}\n\nconst portIdx = args.indexOf(\"--port\");\nif (portIdx !== -1 && args[portIdx + 1]) {\n process.env[\"III_REST_PORT\"] = args[portIdx + 1];\n}\n\nconst skipEngine = args.includes(\"--no-engine\");\n\nfunction getRestPort(): number {\n return parseInt(process.env[\"III_REST_PORT\"] || \"3111\", 10) || 3111;\n}\n\nasync function isEngineRunning(): Promise<boolean> {\n try {\n await fetch(`http://localhost:${getRestPort()}/`, {\n signal: AbortSignal.timeout(2000),\n });\n return true;\n } catch {\n return false;\n }\n}\n\nfunction findIiiConfig(): string {\n const candidates = [\n join(__dirname, \"iii-config.yaml\"),\n join(__dirname, \"..\", \"iii-config.yaml\"),\n join(process.cwd(), \"iii-config.yaml\"),\n ];\n for (const c of candidates) {\n if (existsSync(c)) return c;\n }\n return \"\";\n}\n\nfunction whichBinary(name: string): string | null {\n const cmd = process.platform === \"win32\" ? \"where\" : \"which\";\n try {\n return execFileSync(cmd, [name], { encoding: \"utf-8\" }).trim().split(\"\\n\")[0];\n } catch {\n return null;\n }\n}\n\nasync function installIii(): Promise<boolean> {\n if (process.platform === \"win32\") {\n p.log.warn(\"Automatic iii-engine install is not supported on Windows.\");\n p.log.info(\"Install manually: https://iii.dev/docs\");\n return false;\n }\n\n const curlBin = whichBinary(\"curl\");\n if (!curlBin) {\n p.log.warn(\"curl not found — cannot auto-install iii-engine.\");\n return false;\n }\n\n const shouldInstall = await p.confirm({\n message: \"iii-engine is not installed. Install it now?\",\n initialValue: true,\n });\n\n if (p.isCancel(shouldInstall) || !shouldInstall) {\n return false;\n }\n\n const s = p.spinner();\n s.start(\"Installing iii-engine...\");\n\n try {\n execSync(\"curl -fsSL https://install.iii.dev/iii/main/install.sh | sh\", {\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n timeout: 120000,\n });\n\n const installed = whichBinary(\"iii\");\n if (installed) {\n s.stop(\"iii-engine installed successfully\");\n return true;\n }\n\n s.stop(\"Installation completed but iii not found in PATH\");\n p.log.warn(\"You may need to restart your shell or add iii to your PATH.\");\n\n const iiiPaths = [\n join(process.env[\"HOME\"] || \"\", \".local\", \"bin\", \"iii\"),\n \"/usr/local/bin/iii\",\n ];\n for (const iiiPath of iiiPaths) {\n if (existsSync(iiiPath)) {\n p.log.info(`Found iii at: ${iiiPath}`);\n process.env[\"PATH\"] = `${dirname(iiiPath)}:${process.env[\"PATH\"]}`;\n return true;\n }\n }\n\n return false;\n } catch (err) {\n s.stop(\"Failed to install iii-engine\");\n p.log.error(err instanceof Error ? err.message : String(err));\n return false;\n }\n}\n\nasync function startEngine(): Promise<boolean> {\n const configPath = findIiiConfig();\n let iiiBin = whichBinary(\"iii\");\n\n if (!iiiBin) {\n const installed = await installIii();\n if (installed) {\n iiiBin = whichBinary(\"iii\");\n }\n }\n\n if (iiiBin && configPath) {\n const s = p.spinner();\n s.start(`Starting iii-engine: ${iiiBin}`);\n const child = spawn(iiiBin, [\"--config\", configPath], {\n detached: true,\n stdio: \"ignore\",\n });\n child.unref();\n s.stop(\"iii-engine process started\");\n return true;\n }\n\n const dockerBin = whichBinary(\"docker\");\n const dockerCompose = join(__dirname, \"..\", \"docker-compose.yml\");\n const dcExists = existsSync(dockerCompose) || existsSync(join(process.cwd(), \"docker-compose.yml\"));\n\n if (dockerBin && dcExists) {\n const composeFile = existsSync(dockerCompose) ? dockerCompose : join(process.cwd(), \"docker-compose.yml\");\n const s = p.spinner();\n s.start(\"Starting iii-engine via Docker...\");\n const child = spawn(dockerBin, [\"compose\", \"-f\", composeFile, \"up\", \"-d\"], {\n detached: true,\n stdio: \"ignore\",\n });\n child.unref();\n s.stop(\"Docker compose started\");\n return true;\n }\n\n return false;\n}\n\nasync function waitForEngine(timeoutMs: number): Promise<boolean> {\n const start = Date.now();\n while (Date.now() - start < timeoutMs) {\n if (await isEngineRunning()) return true;\n await new Promise((r) => setTimeout(r, 500));\n }\n return false;\n}\n\nasync function main() {\n p.intro(\"agentmemory\");\n\n if (skipEngine) {\n p.log.info(\"Skipping engine check (--no-engine)\");\n await import(\"./index.js\");\n return;\n }\n\n if (await isEngineRunning()) {\n p.log.success(\"iii-engine is running\");\n await import(\"./index.js\");\n return;\n }\n\n const started = await startEngine();\n if (!started) {\n p.log.error(\"Could not start iii-engine.\");\n p.note(\n [\n \"Install iii-engine (pick one):\",\n \" curl -fsSL https://install.iii.dev/iii/main/install.sh | sh\",\n \" cargo install iii-engine\",\n \"\",\n \"Or use Docker:\",\n \" docker pull iiidev/iii:latest\",\n \"\",\n \"Docs: https://iii.dev/docs\",\n \"\",\n \"Or skip with: agentmemory --no-engine\",\n ].join(\"\\n\"),\n \"Setup required\",\n );\n process.exit(1);\n }\n\n const s = p.spinner();\n s.start(\"Waiting for iii-engine to be ready...\");\n\n const ready = await waitForEngine(15000);\n if (!ready) {\n const port = getRestPort();\n s.stop(\"iii-engine did not become ready within 15s\");\n p.log.error(`Check that ports ${port}, ${port + 1}, 49134 are available.`);\n process.exit(1);\n }\n\n s.stop(\"iii-engine is ready\");\n await import(\"./index.js\");\n}\n\nasync function runStatus() {\n const port = getRestPort();\n const base = `http://localhost:${port}`;\n p.intro(\"agentmemory status\");\n\n const up = await isEngineRunning();\n if (!up) {\n p.log.error(`Not running — no response on port ${port}`);\n p.log.info(\"Start with: npx @agentmemory/agentmemory\");\n process.exit(1);\n }\n\n try {\n const [healthRes, sessionsRes, graphRes] = await Promise.all([\n fetch(`${base}/agentmemory/health`, { signal: AbortSignal.timeout(5000) }).then((r) => r.json()).catch(() => null),\n fetch(`${base}/agentmemory/sessions`, { signal: AbortSignal.timeout(5000) }).then((r) => r.json()).catch(() => null),\n fetch(`${base}/agentmemory/graph/stats`, { signal: AbortSignal.timeout(5000) }).then((r) => r.json()).catch(() => null),\n ]);\n\n const h = healthRes?.health;\n const status = healthRes?.status || \"unknown\";\n const version = healthRes?.version || \"?\";\n const sessions = Array.isArray(sessionsRes?.sessions) ? sessionsRes.sessions.length : 0;\n const memories = h?.workers?.[0]?.function_count || 0;\n const nodes = graphRes?.nodes || 0;\n const edges = graphRes?.edges || 0;\n const cb = healthRes?.circuitBreaker?.state || \"closed\";\n const heapMB = h?.memory ? Math.round(h.memory.heapUsed / 1048576) : 0;\n const uptime = h?.uptimeSeconds ? Math.round(h.uptimeSeconds) : 0;\n\n p.log.success(`Connected — v${version} on port ${port}`);\n\n const lines = [\n `Health: ${status === \"healthy\" ? \"healthy\" : status}`,\n `Sessions: ${sessions}`,\n `Graph: ${nodes} nodes, ${edges} edges`,\n `Circuit: ${cb}`,\n `Heap: ${heapMB} MB`,\n `Uptime: ${uptime}s`,\n `Viewer: http://localhost:${port + 2}`,\n ];\n p.note(lines.join(\"\\n\"), \"agentmemory\");\n } catch (err) {\n p.log.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n}\n\nif (args[0] === \"status\") {\n runStatus().catch((err) => {\n p.log.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n });\n} else {\n main().catch((err) => {\n p.log.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n });\n}\n"],"mappings":"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"}
package/dist/index.mjs CHANGED
@@ -40,10 +40,16 @@ function loadEnvFile() {
40
40
  }
41
41
  function detectProvider(env) {
42
42
  const maxTokens = parseInt(env["MAX_TOKENS"] || "4096", 10);
43
+ if (env["MINIMAX_API_KEY"]) return {
44
+ provider: "minimax",
45
+ model: env["MINIMAX_MODEL"] || "MiniMax-M2.7",
46
+ maxTokens
47
+ };
43
48
  if (env["ANTHROPIC_API_KEY"]) return {
44
49
  provider: "anthropic",
45
50
  model: env["ANTHROPIC_MODEL"] || "claude-sonnet-4-20250514",
46
- maxTokens
51
+ maxTokens,
52
+ baseURL: env["ANTHROPIC_BASE_URL"]
47
53
  };
48
54
  if (env["GEMINI_API_KEY"]) return {
49
55
  provider: "gemini",
@@ -157,7 +163,8 @@ const VALID_PROVIDERS = new Set([
157
163
  "anthropic",
158
164
  "gemini",
159
165
  "openrouter",
160
- "agent-sdk"
166
+ "agent-sdk",
167
+ "minimax"
161
168
  ]);
162
169
  function loadFallbackConfig() {
163
170
  return { providers: (getMergedEnv()["FALLBACK_PROVIDERS"] || "").split(",").map((p) => p.trim()).filter((p) => Boolean(p) && VALID_PROVIDERS.has(p)) };
@@ -196,8 +203,11 @@ var AnthropicProvider = class {
196
203
  client;
197
204
  model;
198
205
  maxTokens;
199
- constructor(apiKey, model, maxTokens) {
200
- this.client = new Anthropic({ apiKey });
206
+ constructor(apiKey, model, maxTokens, baseURL) {
207
+ this.client = new Anthropic({
208
+ apiKey,
209
+ ...baseURL ? { baseURL } : {}
210
+ });
201
211
  this.model = model;
202
212
  this.maxTokens = maxTokens;
203
213
  }
@@ -220,6 +230,67 @@ var AnthropicProvider = class {
220
230
  }
221
231
  };
222
232
 
233
+ //#endregion
234
+ //#region src/providers/minimax.ts
235
+ /**
236
+ * MiniMax provider using raw fetch to call MiniMax's Anthropic-compatible API.
237
+ *
238
+ * The Anthropic SDK automatically injects `x-stainless-*` headers that MiniMax
239
+ * rejects with 403. This provider bypasses the SDK and calls the API directly.
240
+ *
241
+ * Required env vars:
242
+ * MINIMAX_API_KEY — your MiniMax API key
243
+ * MINIMAX_MODEL — model name (default: MiniMax-M2.7)
244
+ * MAX_TOKENS — max output tokens (default: 800; MiniMax-M2.7 needs ≤800)
245
+ *
246
+ * Optional:
247
+ * MINIMAX_BASE_URL — base URL without path (default: https://api.minimaxi.com/anthropic)
248
+ */
249
+ var MinimaxProvider = class {
250
+ name = "minimax";
251
+ apiKey;
252
+ model;
253
+ maxTokens;
254
+ baseUrl;
255
+ constructor(apiKey, model, maxTokens) {
256
+ this.apiKey = apiKey;
257
+ this.model = model;
258
+ this.maxTokens = maxTokens;
259
+ this.baseUrl = process.env["MINIMAX_BASE_URL"] || "https://api.minimaxi.com/anthropic";
260
+ }
261
+ async compress(systemPrompt, userPrompt) {
262
+ return this.call(systemPrompt, userPrompt);
263
+ }
264
+ async summarize(systemPrompt, userPrompt) {
265
+ return this.call(systemPrompt, userPrompt);
266
+ }
267
+ async call(systemPrompt, userPrompt) {
268
+ const url = `${this.baseUrl}/v1/messages`;
269
+ const response = await fetch(url, {
270
+ method: "POST",
271
+ headers: {
272
+ "Content-Type": "application/json",
273
+ "x-api-key": this.apiKey,
274
+ "anthropic-version": "2023-06-01"
275
+ },
276
+ body: JSON.stringify({
277
+ model: this.model,
278
+ max_tokens: this.maxTokens,
279
+ system: systemPrompt,
280
+ messages: [{
281
+ role: "user",
282
+ content: userPrompt
283
+ }]
284
+ })
285
+ });
286
+ if (!response.ok) {
287
+ const text = await response.text();
288
+ throw new Error(`MiniMax API error ${response.status}: ${text}`);
289
+ }
290
+ return ((await response.json()).content?.find((b) => b.type === "text"))?.text ?? "";
291
+ }
292
+ };
293
+
223
294
  //#endregion
224
295
  //#region src/providers/openrouter.ts
225
296
  var OpenRouterProvider = class {
@@ -647,7 +718,8 @@ function createFallbackProvider(config, fallbackConfig) {
647
718
  }
648
719
  function createBaseProvider(config) {
649
720
  switch (config.provider) {
650
- case "anthropic": return new AnthropicProvider(requireEnvVar("ANTHROPIC_API_KEY"), config.model, config.maxTokens);
721
+ case "minimax": return new MinimaxProvider(requireEnvVar("MINIMAX_API_KEY"), config.model, config.maxTokens);
722
+ case "anthropic": return new AnthropicProvider(requireEnvVar("ANTHROPIC_API_KEY"), config.model, config.maxTokens, config.baseURL);
651
723
  case "gemini": return new OpenRouterProvider(requireEnvVar("GEMINI_API_KEY"), config.model, config.maxTokens, "https://generativelanguage.googleapis.com/v1beta/openai/chat/completions");
652
724
  case "openrouter": return new OpenRouterProvider(requireEnvVar("OPENROUTER_API_KEY"), config.model, config.maxTokens, "https://openrouter.ai/api/v1/chat/completions");
653
725
  default: return new AgentSDKProvider();
@@ -2123,23 +2195,56 @@ function registerSearchFunction(sdk, kv) {
2123
2195
  }, async (data) => {
2124
2196
  const ctx = getContext();
2125
2197
  const idx = getSearchIndex();
2198
+ if (typeof data?.query !== "string" || !data.query.trim()) throw new Error("mem::search: query must be a non-empty string");
2199
+ const query = data.query.trim();
2200
+ const MAX_LIMIT = 100;
2201
+ let effectiveLimit = 20;
2202
+ if (data.limit !== void 0) {
2203
+ if (!Number.isInteger(data.limit) || data.limit < 1) throw new Error("mem::search: limit must be a positive integer");
2204
+ effectiveLimit = Math.min(data.limit, MAX_LIMIT);
2205
+ }
2206
+ const projectFilter = typeof data.project === "string" && data.project.length > 0 ? data.project : void 0;
2207
+ const cwdFilter = typeof data.cwd === "string" && data.cwd.length > 0 ? data.cwd : void 0;
2126
2208
  if (idx.size === 0) {
2127
2209
  const count = await rebuildIndex(kv);
2128
2210
  ctx.logger.info("Search index rebuilt", { entries: count });
2129
2211
  }
2130
- const results = idx.search(data.query, data.limit || 20);
2131
- const enriched = [];
2212
+ const filtering = !!(projectFilter || cwdFilter);
2213
+ const fetchLimit = filtering ? Math.max(effectiveLimit * 10, 100) : effectiveLimit;
2214
+ const results = idx.search(query, fetchLimit);
2215
+ const sessionCache = /* @__PURE__ */ new Map();
2216
+ const loadSession = async (sessionId) => {
2217
+ if (sessionCache.has(sessionId)) return sessionCache.get(sessionId);
2218
+ const s = await kv.get(KV.sessions, sessionId);
2219
+ sessionCache.set(sessionId, s ?? null);
2220
+ return s ?? null;
2221
+ };
2222
+ const candidates = [];
2132
2223
  for (const r of results) {
2133
- const obs = await kv.get(KV.observations(r.sessionId), r.obsId);
2224
+ if (candidates.length >= effectiveLimit) break;
2225
+ if (filtering) {
2226
+ const s = await loadSession(r.sessionId);
2227
+ if (!s) continue;
2228
+ if (projectFilter && s.project !== projectFilter) continue;
2229
+ if (cwdFilter && s.cwd !== cwdFilter) continue;
2230
+ }
2231
+ candidates.push(r);
2232
+ }
2233
+ const obsResults = await Promise.all(candidates.map((r) => kv.get(KV.observations(r.sessionId), r.obsId)));
2234
+ const enriched = [];
2235
+ for (let i = 0; i < candidates.length; i++) {
2236
+ const obs = obsResults[i];
2134
2237
  if (obs) enriched.push({
2135
2238
  observation: obs,
2136
- score: r.score,
2137
- sessionId: r.sessionId
2239
+ score: candidates[i].score,
2240
+ sessionId: candidates[i].sessionId
2138
2241
  });
2139
2242
  }
2140
2243
  ctx.logger.info("Search completed", {
2141
- query: data.query,
2142
- results: enriched.length
2244
+ query,
2245
+ results: enriched.length,
2246
+ hasProjectFilter: !!projectFilter,
2247
+ hasCwdFilter: !!cwdFilter
2143
2248
  });
2144
2249
  return { results: enriched };
2145
2250
  });
@@ -3805,7 +3910,7 @@ function registerAutoForgetFunction(sdk, kv) {
3805
3910
 
3806
3911
  //#endregion
3807
3912
  //#region src/version.ts
3808
- const VERSION = "0.7.8";
3913
+ const VERSION = "0.8.0";
3809
3914
 
3810
3915
  //#endregion
3811
3916
  //#region src/functions/export-import.ts
@@ -3910,7 +4015,8 @@ function registerExportImportFunction(sdk, kv) {
3910
4015
  "0.7.5",
3911
4016
  "0.7.6",
3912
4017
  "0.7.7",
3913
- "0.7.8"
4018
+ "0.7.9",
4019
+ "0.8.0"
3914
4020
  ]).has(importData.version)) return {
3915
4021
  success: false,
3916
4022
  error: `Unsupported export version: ${importData.version}`
@@ -10033,9 +10139,32 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
10033
10139
  sdk.registerFunction({ id: "api::search" }, async (req) => {
10034
10140
  const authErr = checkAuth$1(req, secret);
10035
10141
  if (authErr) return authErr;
10142
+ const body = req.body ?? {};
10143
+ if (typeof body.query !== "string" || !body.query.trim()) return {
10144
+ status_code: 400,
10145
+ body: { error: "query is required and must be a non-empty string" }
10146
+ };
10147
+ if (body.limit !== void 0 && (!Number.isInteger(body.limit) || body.limit < 1)) return {
10148
+ status_code: 400,
10149
+ body: { error: "limit must be a positive integer" }
10150
+ };
10151
+ if (body.project !== void 0 && typeof body.project !== "string") return {
10152
+ status_code: 400,
10153
+ body: { error: "project must be a string" }
10154
+ };
10155
+ if (body.cwd !== void 0 && typeof body.cwd !== "string") return {
10156
+ status_code: 400,
10157
+ body: { error: "cwd must be a string" }
10158
+ };
10159
+ const payload = {
10160
+ query: body.query.trim(),
10161
+ limit: body.limit,
10162
+ project: body.project,
10163
+ cwd: body.cwd
10164
+ };
10036
10165
  return {
10037
10166
  status_code: 200,
10038
- body: await sdk.trigger("mem::search", req.body)
10167
+ body: await sdk.trigger("mem::search", payload)
10039
10168
  };
10040
10169
  });
10041
10170
  sdk.registerTrigger({