Compare commits

..

14 Commits

Author SHA1 Message Date
Kartik b7ad69afda docs(skills): refresh mem0 skills against SDK 2.2.1 / TS 3.3.1 (#7548) 2026-10-07 18:36:15 +05:30
rupak d62ce07fac fix(vector-store): stop writing service account private key to DEBUG logs in GoogleMatchingEngine (#7506)
Co-authored-by: rupak-eng <rupak-eng@users.noreply.github.com>
2026-10-07 16:20:38 +05:30
Shashidhar Kundur 0516f19f72 fix(ts-oss): stop add() from mutating the caller's filters object (#6797) 2026-10-07 15:53:25 +05:30
Harsh Vardhan Gupta c93420c49a fix(security): resolve 13 Vanta HIGH Dependabot vulnerabilities (#7528)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 20:52:59 +05:30
Harsh Vardhan Gupta abb81c88e1 fix(security): resolve 7 Vanta MEDIUM Dependabot vulnerabilities (undici, ip-address, adm-zip) (#7510)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 21:46:18 +05:30
Kartik 94c3fe9f23 chore(release): bump Python SDK to 2.2.1 and TypeScript SDK to 3.3.1 (#7456) 2026-09-25 23:04:12 +05:30
Kartik 947ac79818 fix(ts-sdk): stop forcing pg on installs (optional ranged pg peer, optional natural) (#7450) 2026-09-25 20:46:24 +05:30
Yash Singh 8127e8bd47 fix(vector_stores/turbopuffer): make search score respect distance_metric (#6559) 2026-09-25 17:08:20 +05:30
Abhay Singh 5fd01d284a fix(ts-oss/turbopuffer): bound euclidean_squared distance to a similarity score (#6580) 2026-09-25 17:07:11 +05:30
Ratish jain 70c676c8c4 fix: wire dev tooling (ruff/isort/pre-commit) into hatch dev environments (#6684) 2026-09-25 17:04:47 +05:30
Yash Singh a2d8a8a849 fix(vector_stores/s3_vectors): make search score metric-aware (#6547) 2026-09-25 16:21:16 +05:30
Ayaan Gazali fb6d5e1917 fix(llms/aws_bedrock): iterate Converse content blocks for Anthropic text (#6369) 2026-09-25 16:20:09 +05:30
Yash Singh ccd216cff0 fix(vector_stores/turbopuffer): apply all filter operators instead of dropping them (#6564) 2026-09-25 15:53:35 +05:30
Abhay Singh 545306db5a fix(ts-oss/turbopuffer): apply all filter operators, not just gte/lte (#6578) 2026-09-25 15:30:31 +05:30
130 changed files with 2681 additions and 2135 deletions
+1 -1
View File
@@ -12,7 +12,7 @@
"name": "mem0",
"source": "./integrations/claude-code-plugin",
"description": "Cross-session memory and token savings for coding agents.",
"version": "0.3.4"
"version": "0.3.3"
}
]
}
+1 -1
View File
@@ -12,7 +12,7 @@
"name": "mem0",
"source": "./integrations/cursor-plugin",
"description": "Cross-session memory and token savings for coding agents.",
"version": "0.3.4"
"version": "0.3.3"
}
]
}
+1 -1
View File
@@ -5,7 +5,7 @@
{
"id": "mem0",
"displayName": "Mem0",
"version": "0.3.4",
"version": "0.3.3",
"description": "Cross-session memory and token savings for coding agents.",
"homepage": "https://mem0.ai",
"keywords": ["memory", "personalization", "mcp", "semantic-search"],
+22 -77
View File
@@ -7,6 +7,18 @@ mode: "wide"
<Tabs>
<Tab title="Python">
<Update label="2026-09-25" description="v2.2.1">
**Bug Fixes:**
- **Core:** `add()` (`Memory` and `AsyncMemory`) no longer reports records the vector store rejected as successful `ADD` events. Only records that were actually inserted are written to history, entity-linked, and returned. If none of the extracted memories could be inserted, `add()` now raises `VectorStoreError` instead of returning memories that were never stored ([#7066](https://github.com/mem0ai/mem0/pull/7066))
- **Core:** Restore the context-manager protocol on `Memory` (`with Memory() as m:`) and `AsyncMemory` (`async with AsyncMemory() as m:`), which closes the instance on exit ([#7354](https://github.com/mem0ai/mem0/pull/7354))
- **LLMs:** The AWS Bedrock Anthropic path now returns the first Converse content block that carries text instead of always reading `content[0]`. Claude reasoning models can emit a `reasoningContent` block before the text block, which previously made the call fail with a `KeyError` ([#6369](https://github.com/mem0ai/mem0/pull/6369))
- **Vector Stores:** Turbopuffer filters now apply every operator (`eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `in`, `nin`). Only `gte` and `lte` were read before, so any other operator was silently dropped and the query returned unfiltered results. An unsupported operator now raises `ValueError` ([#6564](https://github.com/mem0ai/mem0/pull/6564))
- **Vector Stores:** Turbopuffer `search()` scores now respect `distance_metric`. With `euclidean_squared`, the unbounded squared distance maps to `1 / (1 + distance)` instead of `1 - distance`, which went negative and inverted ranking for any distance above 1 ([#6559](https://github.com/mem0ai/mem0/pull/6559))
- **Vector Stores:** S3 Vectors `search()` scores are now metric-aware. With `euclidean`, the distance maps to `1 / (1 + distance)` instead of `max(0, 1 - distance)`, which collapsed most scores to 0 ([#6547](https://github.com/mem0ai/mem0/pull/6547))
</Update>
<Update label="2026-09-23" description="v2.2.0">
**New Features:**
@@ -1245,6 +1257,16 @@ See the [OSS v2 to v3 migration guide](https://docs.mem0.ai/migration/oss-v2-to-
<Tab title="TypeScript">
<Update label="2026-09-25" description="v3.3.1">
**Bug Fixes:**
- **Config (OSS):** `ConfigManager` no longer injects OpenAI's default `baseURL` and `model` into non-OpenAI LLM providers. The defaults now apply only to `openai` and `openai_structured`, so a DeepSeek, xAI, or other provider config without an explicit `baseURL` or `model` falls back to that provider's own defaults and env vars (`DEEPSEEK_API_BASE`, `XAI_API_BASE`, ...) instead of pointing at OpenAI with an OpenAI model name ([#7350](https://github.com/mem0ai/mem0/pull/7350))
- **Vector Stores:** Turbopuffer filters now apply every operator (`eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `in`, `nin`). Only the range operators were read before, so `eq`, `ne`, `in`, and `nin` were silently dropped. A `"*"` value now matches anything instead of nothing, an array value is treated as `in`, and an unsupported operator throws ([#6578](https://github.com/mem0ai/mem0/pull/6578))
- **Vector Stores:** Turbopuffer `search()` with `euclidean_squared` now maps the unbounded squared distance to `1 / (1 + distance)` instead of `1 - distance`, which went negative and inverted ranking for any distance above 1 ([#6580](https://github.com/mem0ai/mem0/pull/6580))
- **Packaging:** `pg`, `@types/pg`, and `natural` are now optional peer dependencies, and `pg` / `@types/pg` accept caret ranges instead of the exact `8.11.3` / `8.11.0` pins, so installing `mem0ai` no longer pulls in `pg` or conflicts with an app's own `pg` version. The PGVector store now imports `pg` only when it is used, so install it alongside `mem0ai` (`npm install pg`) if you use that store. `@types/jest` moved from peer dependencies to dev dependencies ([#7450](https://github.com/mem0ai/mem0/pull/7450))
</Update>
<Update label="2026-09-23" description="v3.3.0">
**New Features:**
@@ -2100,16 +2122,6 @@ A full-featured command-line interface for Mem0, available in both Python and No
<Tabs>
<Tab title="Mem0 Plugin">
<Update label="2026-09-25" description="Portable agent plugin v0.3.4">
**Fixes:**
- **Auth:** Falls back to the API key `mem0 init` saved in `~/.mem0/config.json` when no key is set in the plugin settings or `MEM0_API_KEY`, so search and the status check no longer report a missing key after `mem0 init` ([#7449](https://github.com/mem0ai/mem0/pull/7449))
- **Auth:** A plugin setting that reaches the plugin as an unexpanded `${api_key}` or `${user_id}` placeholder is treated as unset. It is no longer sent as the API key, cached to disk, or used as the user ID ([#7449](https://github.com/mem0ai/mem0/pull/7449))
- **State:** The `search_memories` MCP server now keeps its state in `~/.mem0/coding-agent-plugin`, the same directory the status, pause, resume and forget skills use, instead of `~/.mem0/mem0-plugin` ([#7449](https://github.com/mem0ai/mem0/pull/7449))
- **Packaging:** `PLUGIN_VERSION` bumped to `0.3.4`, so the `mem0-plugin/<version>` wire header and `plugin_version` telemetry field identify builds with this fix ([#7449](https://github.com/mem0ai/mem0/pull/7449))
</Update>
<Update label="2026-09-08" description="Shared agent plugin runtime">
**Changed:**
@@ -2421,15 +2433,6 @@ Initial release of the Mem0 plugin for Claude Code and Cursor, followed by Codex
<Tab title="Claude Code">
<Update label="2026-09-25" description="Claude Code plugin v0.3.4">
**Fixes:**
- **Auth:** Falls back to the API key `mem0 init` saved in `~/.mem0/config.json` when no key is set in the plugin settings or `MEM0_API_KEY`, so search and the status check no longer report a missing key after `mem0 init` ([#7449](https://github.com/mem0ai/mem0/pull/7449))
- **Auth:** A plugin setting that reaches the plugin as an unexpanded `${api_key}` or `${user_id}` placeholder is treated as unset. It is no longer sent as the API key, cached to disk, or used as the user ID ([#7449](https://github.com/mem0ai/mem0/pull/7449))
- **Packaging:** `PLUGIN_VERSION` bumped to `0.3.4`, so the `mem0-plugin/<version>` wire header and `plugin_version` telemetry field identify builds with this fix ([#7449](https://github.com/mem0ai/mem0/pull/7449))
</Update>
<Update label="2026-09-23" description="Claude Code plugin v0.3.3">
**Improvements:**
@@ -2471,16 +2474,6 @@ Initial release of the Mem0 plugin for Claude Code and Cursor, followed by Codex
<Tab title="Cursor">
<Update label="2026-09-25" description="Cursor plugin v0.3.4">
**Fixes:**
- **Auth:** An API key set in the Cursor plugin settings now reaches the `search_memories` MCP server. The server looked for the cached key in `~/.mem0/mem0-plugin` while the hooks cached it in `~/.mem0/cursor-plugin`, so search asked for auth whenever Cursor did not pass the setting to the MCP server directly ([#7449](https://github.com/mem0ai/mem0/pull/7449))
- **Auth:** Falls back to the API key `mem0 init` saved in `~/.mem0/config.json` when no key is set in the plugin settings or `MEM0_API_KEY`, so search and the status check no longer report a missing key after `mem0 init` ([#7449](https://github.com/mem0ai/mem0/pull/7449))
- **Auth:** A plugin setting that reaches the plugin as an unexpanded `${api_key}` or `${user_id}` placeholder is treated as unset. It is no longer sent as the API key, cached to disk, or used as the user ID ([#7449](https://github.com/mem0ai/mem0/pull/7449))
- **Packaging:** `PLUGIN_VERSION` bumped to `0.3.4`, so the `mem0-plugin/<version>` wire header and `plugin_version` telemetry field identify builds with this fix ([#7449](https://github.com/mem0ai/mem0/pull/7449))
</Update>
<Update label="2026-09-23" description="Cursor plugin v0.3.3">
**Improvements:**
@@ -2521,14 +2514,6 @@ Initial release of the Mem0 plugin for Claude Code and Cursor, followed by Codex
<Tab title="Codex">
<Update label="2026-09-25" description="Codex plugin v0.3.4">
**Fixes:**
- **Auth:** Falls back to the API key `mem0 init` saved in `~/.mem0/config.json` when `MEM0_API_KEY` is not set, so search and the status check no longer report a missing key after `mem0 init` ([#7449](https://github.com/mem0ai/mem0/pull/7449))
- **Packaging:** `PLUGIN_VERSION` bumped to `0.3.4`, so the `mem0-plugin/<version>` wire header and `plugin_version` telemetry field identify builds with this fix ([#7449](https://github.com/mem0ai/mem0/pull/7449))
</Update>
<Update label="2026-09-23" description="Codex plugin v0.3.3">
**Improvements:**
@@ -2568,13 +2553,6 @@ Initial release of the Mem0 plugin for Claude Code and Cursor, followed by Codex
<Tab title="OpenCode">
<Update label="2026-09-25" description="OpenCode plugin v0.4.2">
**Fixes:**
- **Auth:** Falls back to the API key `mem0 init` saved in `~/.mem0/config.json` when neither `MEM0_API_KEY` nor a shell profile sets one, so search and the status check no longer report a missing key after `mem0 init` ([#7449](https://github.com/mem0ai/mem0/pull/7449))
</Update>
<Update label="2026-09-23" description="OpenCode plugin v0.4.1">
**Improvements:**
@@ -2690,15 +2668,6 @@ Initial release of the Mem0 plugin for Claude Code and Cursor, followed by Codex
<Tab title="Antigravity">
<Update label="2026-09-25" description="Antigravity plugin v0.3.4">
**Fixes:**
- **Auth:** Falls back to the API key `mem0 init` saved in `~/.mem0/config.json` when `MEM0_API_KEY` is not set, so search and the status check no longer report a missing key after `mem0 init` ([#7449](https://github.com/mem0ai/mem0/pull/7449))
- **Data directory:** The `search_memories` MCP server uses `~/.mem0/antigravity-plugin`, the data directory the hooks use, instead of `~/.mem0/mem0-plugin` ([#7449](https://github.com/mem0ai/mem0/pull/7449))
- **Packaging:** `PLUGIN_VERSION` bumped to `0.3.4`, so the `mem0-plugin/<version>` wire header and `plugin_version` telemetry field identify builds with this fix ([#7449](https://github.com/mem0ai/mem0/pull/7449))
</Update>
<Update label="2026-09-23" description="Antigravity plugin v0.3.3">
**Improvements:**
@@ -2812,15 +2781,6 @@ Existing memories written by the previous versions are not rewritten. If your me
<Tab title="Kimi">
<Update label="2026-09-25" description="Kimi Code plugin v0.3.4">
**Fixes:**
- **Auth:** Falls back to the API key `mem0 init` saved in `~/.mem0/config.json` when `MEM0_API_KEY` is not set, so search and the status check no longer report a missing key after `mem0 init` ([#7449](https://github.com/mem0ai/mem0/pull/7449))
- **Data directory:** The `search_memories` MCP server uses `~/.mem0/kimi-plugin`, the data directory the hooks use, instead of `~/.mem0/mem0-plugin` ([#7449](https://github.com/mem0ai/mem0/pull/7449))
- **Packaging:** `PLUGIN_VERSION` bumped to `0.3.4`, so the `mem0-plugin/<version>` wire header and `plugin_version` telemetry field identify builds with this fix ([#7449](https://github.com/mem0ai/mem0/pull/7449))
</Update>
<Update label="2026-09-23" description="Kimi Code plugin v0.3.3">
**Improvements:**
@@ -3179,13 +3139,6 @@ Existing memories written by the previous versions are not rewritten. If your me
<Tab title="Pi Agent">
<Update label="2026-09-25" description="Pi Agent plugin v0.3.3">
**Fixes:**
- **Auth:** Falls back to the API key `mem0 init` saved in `~/.mem0/config.json` when neither `MEM0_API_KEY` nor `~/.pi/agent/mem0-config.json` sets one, so search and the status check no longer report a missing key after `mem0 init` ([#7449](https://github.com/mem0ai/mem0/pull/7449))
</Update>
<Update label="2026-09-23" description="Pi Agent plugin v0.3.2">
**Improvements:**
@@ -3315,14 +3268,6 @@ Existing memories written by the previous versions are not rewritten. If your me
<Tab title="DeepSeek Harness">
<Update label="2026-09-25" description="deepseek-plugin v0.3.3">
**Fixes:**
- **Auth:** Falls back to the API key `mem0 init` saved in `~/.mem0/config.json` when neither `config.apiKey` nor `MEM0_API_KEY` is set, so search and the status check no longer report a missing key after `mem0 init` ([#7449](https://github.com/mem0ai/mem0/pull/7449))
- **Config:** An empty `apiKey` now counts as unset and falls through to `MEM0_API_KEY` and the `mem0 init` key, instead of failing validation ([#7449](https://github.com/mem0ai/mem0/pull/7449))
</Update>
<Update label="2026-09-23" description="deepseek-plugin v0.3.2">
**Improvements:**
@@ -5,6 +5,12 @@ description: "Use pgvector as a vector store in Mem0 for PostgreSQL-based vector
[pgvector](https://github.com/pgvector/pgvector) is an open-source vector similarity search extension for Postgres. After connecting to Postgres, run `CREATE EXTENSION IF NOT EXISTS vector;` to create the vector extension.
The TypeScript SDK loads the `pg` driver only when you use this store, so install it alongside `mem0ai`:
```bash
npm install pg
```
### Usage
<CodeGroup>
-4
View File
@@ -28,10 +28,6 @@ echo 'export MEM0_API_KEY="m0-your-api-key"' >> ~/.bashrc && source ~/.bashrc
```
</CodeGroup>
<Tip>
Already set up the [Mem0 CLI](/platform/cli) with `mem0 init`? The Mem0 plugin also reads the key it saved in `~/.mem0/config.json`, so you can skip this step. `MEM0_API_KEY` takes precedence when set.
</Tip>
## Installation
**Option A: degit** (recommended):
+1 -1
View File
@@ -166,7 +166,7 @@ claude plugin update mem0@mem0-plugins --scope user
| Problem | Fix |
| --- | --- |
| Missing key | Reinstall with `--config api_key="$MEM0_API_KEY"` while the var is set, or run `mem0 init` with the [Mem0 CLI](/platform/cli). The plugin falls back to the key it saves. |
| Missing key | Reinstall with `--config api_key="$MEM0_API_KEY"` while the var is set. |
| `401 Unauthorized` | API key is invalid or expired. Run `/mem0:status` to confirm. |
| No memory after ending a session | Extraction runs in the background. Wait a moment, then search again. |
| Sidekick won't start | Must be in a Git repo. Check that your Claude Code version supports plugin agents and worktrees. |
-4
View File
@@ -35,10 +35,6 @@ source ~/.bashrc
```
</CodeGroup>
<Tip>
Already set up the [Mem0 CLI](/platform/cli) with `mem0 init`? The plugin (Option A) also reads the key it saved in `~/.mem0/config.json`, so you can skip this step. `MEM0_API_KEY` takes precedence when set.
</Tip>
## Installation
### Option A: Plugin Marketplace (Recommended)
-5
View File
@@ -35,10 +35,6 @@ source ~/.bashrc
```
</CodeGroup>
<Tip>
Already set up the [Mem0 CLI](/platform/cli) with `mem0 init`? The full plugin (Option A) also reads the key it saved in `~/.mem0/config.json`, so you can skip this step. That includes Cursor opened from the Dock, which does not load your shell profile. A key from the plugin configuration or the environment takes precedence.
</Tip>
<Warning>
Already have `mem0` configured as an MCP server in Cursor? Remove the existing entry from your Cursor MCP settings before installing to avoid duplicate tools.
</Warning>
@@ -168,7 +164,6 @@ Captured prompts and responses retain their full redacted text without a per-mes
## Troubleshooting
- **"Connection failed"**: Verify `MEM0_API_KEY` is set: `echo $MEM0_API_KEY`
- **Still asked for an API key**: Run `mem0 init` with the [Mem0 CLI](/platform/cli). The full plugin falls back to the key it saves.
- **Duplicate tools**: Do not combine the full plugin with an MCP-only option. Remove the standalone `mem0` MCP entry before installing the plugin.
- **No tools appearing**: Go to Cursor Settings > MCP and verify the `mem0` server shows as connected
+1 -5
View File
@@ -56,10 +56,6 @@ source ~/.bashrc
```
</CodeGroup>
<Tip>
Already set up the [Mem0 CLI](/platform/cli) with `mem0 init`? The plugin also reads the key it saved in `~/.mem0/config.json`, so you can skip this step. `config.apiKey` and `MEM0_API_KEY` take precedence.
</Tip>
## Try it locally
1. Build and pack the plugin:
@@ -107,7 +103,7 @@ For a Mem0 Platform on-prem or dedicated deployment, point `config.host` at that
| Field | Required | Default | Notes |
|---|---|---|---|
| `apiKey` | no | `$MEM0_API_KEY` | Mem0 platform API key. Falls back to the key `mem0 init` saved. |
| `apiKey` | no | `$MEM0_API_KEY` | Mem0 platform API key |
| `userId` | yes | | Default entity that owns the memories |
| `allowUserOverride` | no | `false` | Permit model-selected access to a different user only in a trusted multi-user deployment |
| `host` | no | `api.mem0.ai` | Platform base URL (on-prem / dedicated) |
+1 -1
View File
@@ -99,7 +99,7 @@ Captured prompts and responses retain their full redacted text without a per-mes
| Problem | Fix |
| --- | --- |
| Missing API key | Start Kimi from a shell where `MEM0_API_KEY` is exported, or run `mem0 init` with the [Mem0 CLI](/platform/cli). The plugin falls back to the key it saves. |
| Missing API key | Start Kimi from a shell where `MEM0_API_KEY` is exported. |
| Plugin changes do not appear | Run `/plugins reload`, then `/reload` or `/new`. |
| MCP server is disabled | Run `/plugins mcp enable mem0 mem0`, then `/reload`. |
| No memory in a later session | Wait a moment for the background flush, then ask Kimi to search memory explicitly. |
-4
View File
@@ -26,10 +26,6 @@ echo 'export MEM0_API_KEY="m0-your-api-key"' >> ~/.bashrc && source ~/.bashrc
```
</CodeGroup>
<Tip>
Already set up the [Mem0 CLI](/platform/cli) with `mem0 init`? The OpenCode plugin also reads the key it saved in `~/.mem0/config.json`, so you can skip this step. `MEM0_API_KEY` and your shell profile take precedence.
</Tip>
## Installation
### Option A: Plugin Install (Recommended)
+2 -6
View File
@@ -40,10 +40,6 @@ source ~/.bashrc
```
</CodeGroup>
<Tip>
Already set up the [Mem0 CLI](/platform/cli) with `mem0 init`? The extension also reads the key it saved in `~/.mem0/config.json`, so you can skip this step. `MEM0_API_KEY` and `apiKey` in `mem0-config.json` take precedence.
</Tip>
## Installation
```bash
@@ -72,7 +68,7 @@ For advanced settings, create `~/.pi/agent/mem0-config.json`:
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `apiKey` | `string` | `$MEM0_API_KEY` | Mem0 API key. Environment variable takes precedence. Falls back to the key `mem0 init` saved. |
| `apiKey` | `string` | `$MEM0_API_KEY` | Mem0 API key. Environment variable takes precedence. |
| `userId` | `string` | `$MEM0_USER_ID` or `"default"` | User identity for memory scoping |
| `autoCapture` | `boolean` | `true` | Store facts from conversations automatically |
| `defaultScope` | `string` | `"project"` | Default memory scope: `project`, `session`, or `global` |
@@ -151,7 +147,7 @@ You: What do you know about my preferences?
## Troubleshooting
- **"No API key found"**: Verify `MEM0_API_KEY` is set: `echo $MEM0_API_KEY`. If empty, add it to your shell profile (see Prerequisites) or run `mem0 init`
- **"No API key found"**: Verify `MEM0_API_KEY` is set: `echo $MEM0_API_KEY`. If empty, add it to your shell profile (see Prerequisites)
- **Extension not loading**: Check Pi startup output for errors. For a source checkout, run `pnpm build`, then `pi -e ./dist/entry.js` from the plugin directory
- **Memories not capturing**: Verify `autoCapture` is `true` (default). Check `/mem0-status` for connection health
- **Wrong project detected**: The plugin uses the git repository root as `app_id`. If not in a git repo, it falls back to the working directory name. Run `/mem0-status` to see the detected project
+4 -4
View File
@@ -136,8 +136,8 @@ curl -X POST 'https://api.mem0.ai/v3/memories/?page=1&page_size=50' \
| Parameter | V1/V2 | V3 | Notes |
|---|---|---|---|
| `top_k` | Supported | Supported (1-1000, default 10) | No change |
| `threshold` | Default: none | Default: `0.1` | Pass `0.0` to disable |
| `rerank` | Default: `true` | Default: `false` | Pass `true` to enable (adds latency) |
| `threshold` | Default: `0.3` (v2) | Default: `0.1` | Pass `0.3` to keep the v2 cutoff, or `0.0` to disable |
| `rerank` | Default: `false` | Default: `false` | No change. Pass `true` to enable (adds latency) |
| Entity IDs in `search` / `get_all` | Top-level | Inside `filters` dict | Top-level raises 400 |
### Response Format
@@ -265,10 +265,10 @@ If your application previously read graph relations from the API response (`rela
<Steps>
<Step title="Review your search thresholds">
The default `threshold` is now `0.1` (previously no threshold). If your application was relying on unfiltered results, explicitly pass `threshold=0.0` in your search calls to preserve the old behavior. In most cases, the new default is better: it filters out low-relevance noise.
The default `threshold` is now `0.1` (it was `0.3` on v2), so lower-scoring matches that v2 hid can now appear. Scores are computed differently in v3, so retune against representative queries. To keep a stricter cutoff, pass an explicit `threshold` in your search calls, or pass `threshold=0.0` to disable filtering.
</Step>
<Step title="Review reranking usage">
Reranking is now `false` by default. If your application depended on reranked results, add `rerank=True` to your search calls. Note that reranking adds latency (~200-400ms) but can improve ordering quality for complex queries.
Reranking is `false` by default, the same as on v2. If your application depends on reranked results, pass `rerank=True` to your search calls. Note that reranking adds latency (~200-400ms) but can improve ordering quality for complex queries.
</Step>
<Step title="Update score handling (optional)">
The top-level `score` field continues to work as before. It is now a combined multi-signal score (semantic + keyword + entity) rather than pure cosine similarity, so the absolute numbers will differ. Relative ranking remains comparable: if you have threshold-based filtering in your app, retune on a representative query set.
@@ -103,7 +103,6 @@ def _render_harness_id(host: str, *, portable: bool = False) -> str:
"\n"
f'HARNESS_ID = "{host}"\n'
f'SOURCE_TAG = "{tag}"\n'
f'DATA_DIR_NAME = "{host}-plugin"\n'
"\n"
"# Platform-side vocabulary (mem0_event.source + X-Application). The whole\n"
"# plugin family is one source; which editor it runs in is the application.\n"
@@ -28,23 +28,12 @@ from typing import Any, Iterable
import telemetry
# Read from the generated per-host module so a new entrypoint is correct without
# remembering to configure anything.
try: # pragma: no cover - absent only in the un-built shared source tree
from _harness_id import DATA_DIR_NAME as _DATA_DIR_NAME
from _harness_id import PLATFORM_APPLICATION as _PLATFORM_APPLICATION
from _harness_id import PLATFORM_SOURCE as _PLATFORM_SOURCE
except ImportError:
_DATA_DIR_NAME = "mem0-plugin"
_PLATFORM_SOURCE = "MEM0_PLUGIN"
_PLATFORM_APPLICATION = ""
DEFAULT_API_URL = "https://api.mem0.ai"
PLUGIN_VERSION = "0.3.4"
PLUGIN_VERSION = "0.3.3"
_harness_name: str = "generic"
_harness_env_prefix: str = "MEM0_PLUGIN"
_harness_data_dir_name: str = _DATA_DIR_NAME
_harness_data_dir_name: str = "mem0-plugin"
_harness_source_tag: str = "mem0_plugin"
@@ -389,46 +378,30 @@ def resolve_repo(cwd: str | None) -> RepoContext:
return _resolve_repo_cached(os.path.abspath(cwd or os.getcwd()))
_PLUGIN_API_KEY_ENV = (
"PLUGIN_OPTION_API_KEY",
"CLAUDE_PLUGIN_OPTION_API_KEY",
"CLAUDE_PLUGIN_OPTION_MEM0_API_KEY",
)
def _configured(value: object) -> str:
"""The stripped value, or empty when the host left its ${placeholder} unexpanded."""
text = value.strip() if isinstance(value, str) else ""
return "" if text.startswith("${") and text.endswith("}") else text
def _first_env(*names: str) -> str:
return next((value for name in names if (value := _configured(os.environ.get(name)))), "")
def _mem0_cli_api_key() -> str:
"""The key `mem0 init` saved to the Mem0 CLI config."""
try:
config = json.loads((Path.home() / ".mem0" / "config.json").read_text(encoding="utf-8"))
return _configured(config["platform"]["api_key"])
except (OSError, ValueError, LookupError, TypeError):
return ""
def api_key() -> str:
configured = _first_env("MEM0_API_KEY", *_PLUGIN_API_KEY_ENV)
configured = (
os.environ.get("MEM0_API_KEY")
or os.environ.get("PLUGIN_OPTION_API_KEY")
or os.environ.get("CLAUDE_PLUGIN_OPTION_API_KEY")
or os.environ.get("CLAUDE_PLUGIN_OPTION_MEM0_API_KEY")
or ""
).strip()
if configured:
return configured
try:
cached = _configured((data_dir() / "api-key").read_text(encoding="utf-8"))
return (data_dir() / "api-key").read_text(encoding="utf-8").strip()
except OSError:
cached = ""
return cached or _mem0_cli_api_key()
return ""
def cache_plugin_api_key() -> bool:
"""Bridge host's hook-only sensitive config into plugin-owned storage."""
configured = _first_env(*_PLUGIN_API_KEY_ENV)
configured = (
os.environ.get("PLUGIN_OPTION_API_KEY")
or os.environ.get("CLAUDE_PLUGIN_OPTION_API_KEY")
or os.environ.get("CLAUDE_PLUGIN_OPTION_MEM0_API_KEY")
or ""
).strip()
if not configured:
return False
@@ -456,7 +429,14 @@ def cache_plugin_api_key() -> bool:
def clear_stale_api_key_cache() -> bool:
"""Drop the cached key file once every configured key source is gone."""
if _first_env("MEM0_API_KEY", *_PLUGIN_API_KEY_ENV):
configured = (
os.environ.get("MEM0_API_KEY")
or os.environ.get("PLUGIN_OPTION_API_KEY")
or os.environ.get("CLAUDE_PLUGIN_OPTION_API_KEY")
or os.environ.get("CLAUDE_PLUGIN_OPTION_MEM0_API_KEY")
or ""
).strip()
if configured:
return False
path = data_dir() / "api-key"
if not path.exists():
@@ -479,7 +459,12 @@ def detached_process_kwargs(platform: str | None = None) -> dict:
def _plugin_option(name: str, fallback: str = "") -> str:
return _first_env(f"PLUGIN_OPTION_{name.upper()}", f"CLAUDE_PLUGIN_OPTION_{name.upper()}", fallback)
return (
os.environ.get(f"PLUGIN_OPTION_{name.upper()}")
or os.environ.get(f"CLAUDE_PLUGIN_OPTION_{name.upper()}")
or os.environ.get(fallback)
or ""
).strip()
def user_id() -> str:
@@ -1813,6 +1798,16 @@ def extraction_message_batches(
return batches
# Platform surface attribution. Read from the generated per-host module so a new
# entrypoint is correct without remembering to configure anything.
try: # pragma: no cover - absent only in the un-built shared source tree
from _harness_id import PLATFORM_APPLICATION as _PLATFORM_APPLICATION
from _harness_id import PLATFORM_SOURCE as _PLATFORM_SOURCE
except ImportError:
_PLATFORM_SOURCE = "MEM0_PLUGIN"
_PLATFORM_APPLICATION = ""
def platform_headers(key: str) -> dict[str, str]:
"""Auth plus the three surface-identity headers.
@@ -19,7 +19,5 @@ API key is configured, the event/flush/retrieval counts (`flushes` is the
number of completed flushes, not a pending count), and the doctor check
results. If doctor reports an authentication failure (401 / invalid key), say
clearly that the Mem0 API key is invalid or expired and that memories are NOT
being created. Never report an auth failure as "no memories found". When the
key is missing or invalid, suggest updating the plugin's API key setting,
exporting `MEM0_API_KEY`, or running `mem0 init` (the plugin reads the key the
Mem0 CLI saves in `~/.mem0/config.json`).
being created. Never report an auth failure as "no memories found". Suggest
reinstalling with `--config api_key=...` in that case.
@@ -92,7 +92,6 @@ def test_the_portable_bundle_declares_no_host_application(tmp_path: Path) -> Non
assert identity["PLATFORM_APPLICATION"] == ""
# The PostHog-side label is still useful for grouping and stays populated.
assert identity["HARNESS_ID"] == "coding-agent"
assert identity["DATA_DIR_NAME"] == "coding-agent-plugin"
assert identity["PLATFORM_SOURCE"] == "MEM0_PLUGIN"
@@ -101,7 +100,6 @@ def test_a_native_bundle_names_the_host_it_was_built_for(host: str, tmp_path: Pa
identity = _harness_identity(build(host, "native", tmp_path / host))
assert identity["PLATFORM_APPLICATION"] == host
assert identity["DATA_DIR_NAME"] == f"{host}-plugin"
@pytest.mark.parametrize("host", ["claude-code", "cursor", "codex", "kimi", "antigravity"])
@@ -1,175 +0,0 @@
"""Launch each host's real hooks and MCP server the way the host does, and check the search is authenticated."""
from __future__ import annotations
import json
import os
import subprocess
import sys
import threading
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
from string import Template
import pytest
INTEGRATIONS = Path(__file__).resolve().parents[2]
KEY = "m0-plugin-setting-key"
CODEX_INHERITED_ENV = ("HOME", "PATH", "LANG", "TMPDIR")
class _Mem0Api(BaseHTTPRequestHandler):
authorizations: list[str]
def do_POST(self) -> None:
self.rfile.read(int(self.headers.get("Content-Length") or 0))
self.authorizations.append(self.headers.get("Authorization", ""))
body = b'{"results": []}'
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def log_message(self, *args: object) -> None:
pass
@pytest.fixture
def mem0_api():
handler = type("Handler", (_Mem0Api,), {"authorizations": []})
server = ThreadingHTTPServer(("127.0.0.1", 0), handler)
threading.Thread(target=server.serve_forever, daemon=True).start()
yield f"http://127.0.0.1:{server.server_port}", handler.authorizations
server.shutdown()
def _json(path: Path) -> dict:
return json.loads(path.read_text(encoding="utf-8"))
def _expand(value: str, variables: dict[str, str]) -> str:
return Template(value).safe_substitute(variables)
def _run_hook(command: str, cwd: Path, env: dict[str, str], payload: dict) -> None:
result = subprocess.run(
["sh", "-c", command], cwd=cwd, env=env, input=json.dumps(payload), text=True, capture_output=True
)
assert result.returncode == 0, result.stderr
def _search(argv: list[str], cwd: Path, env: dict[str, str]) -> dict:
requests = [
{"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {"protocolVersion": "2024-11-05"}},
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {"name": "search_memories", "arguments": {"query": "earlier fixes"}},
},
]
result = subprocess.run(
argv,
cwd=cwd,
env=env,
input="\n".join(json.dumps(request) for request in requests) + "\n",
text=True,
capture_output=True,
timeout=30,
)
assert result.returncode == 0, result.stderr
return json.loads(result.stdout.splitlines()[-1])
def _claude_code(host_env: dict[str, str], tmp_path: Path, repo: Path, setting: bool) -> tuple:
root = INTEGRATIONS / "claude-code-plugin"
plugin_env = {"CLAUDE_PLUGIN_ROOT": str(root), "CLAUDE_PLUGIN_DATA": str(tmp_path / "claude-data")}
hook_env = {**host_env, **plugin_env, **({"CLAUDE_PLUGIN_OPTION_API_KEY": KEY} if setting else {})}
command = _json(root / "hooks" / "hooks.json")["hooks"]["SessionStart"][0]["hooks"][0]["command"]
_run_hook(command, repo, hook_env, {"session_id": "s1", "cwd": str(repo)})
server = _json(root / ".mcp.json")["mcpServers"]["mem0"]
env = {**host_env, **{name: _expand(value, plugin_env) for name, value in server["env"].items()}}
return [sys.executable, *(_expand(argument, plugin_env) for argument in server["args"])], repo, env
def _cursor(host_env: dict[str, str], tmp_path: Path, repo: Path, setting: bool) -> tuple:
root = INTEGRATIONS / "cursor-plugin"
command = _json(root / "hooks" / "hooks.json")["hooks"]["sessionStart"][0]["command"]
if setting:
command = command.replace("${api_key}", KEY)
hook_env = {**host_env, "CURSOR_PLUGIN_ROOT": str(root)}
_run_hook(command, repo, hook_env, {"conversation_id": "c1", "workspace_roots": [str(repo)]})
server = _json(root / "mcp.json")["mcpServers"]["mem0"]
args = [_expand(argument, {"CURSOR_PLUGIN_ROOT": str(root)}) for argument in server["args"]]
return [sys.executable, *args], repo, {**host_env, **server["env"]}
def _codex(host_env: dict[str, str], tmp_path: Path, repo: Path, setting: bool) -> tuple:
root = INTEGRATIONS / "codex-plugin"
codex_env = {**host_env, **({"MEM0_API_KEY": KEY} if setting else {})}
data = str(tmp_path / "codex-data")
hook_env = {**codex_env, "PLUGIN_ROOT": str(root), "PLUGIN_DATA": data, "CLAUDE_PLUGIN_DATA": data}
command = _json(root / "hooks" / "hooks.json")["hooks"]["SessionStart"][0]["hooks"][0]["command"]
_run_hook(command, repo, hook_env, {"session_id": "s1", "cwd": str(repo)})
server = _json(root / ".mcp.json")["mcpServers"]["mem0"]
forwarded = [*CODEX_INHERITED_ENV, *server["env_vars"]]
env = {name: codex_env[name] for name in forwarded if name in codex_env}
return [sys.executable, *server["args"]], root / server["cwd"], env
HOSTS = {"claude-code": _claude_code, "cursor": _cursor, "codex": _codex}
def _mem0_init(home: Path, key: str) -> None:
(home / ".mem0").mkdir(parents=True, exist_ok=True)
(home / ".mem0" / "config.json").write_text(json.dumps({"platform": {"api_key": key}}), encoding="utf-8")
def _host_env(home: Path, api_url: str) -> dict[str, str]:
return {
"HOME": str(home),
"PATH": os.environ["PATH"],
"MEM0_API_URL": api_url,
"MEM0_TELEMETRY": "false",
"MEM0_CODE_USER_ID": "test-user",
}
@pytest.mark.parametrize("key_source", ["plugin setting", "mem0 init"])
@pytest.mark.parametrize("host", sorted(HOSTS))
def test_mcp_server_searches_with_the_key_the_user_configured(host, key_source, tmp_path, mem0_api):
"""The key reaches the MCP server however the host delivers it: plugin setting, env, or `mem0 init`."""
api_url, authorizations = mem0_api
home = tmp_path / "home"
repo = tmp_path / "repo"
repo.mkdir()
if key_source == "mem0 init":
_mem0_init(home, KEY)
argv, cwd, env = HOSTS[host](_host_env(home, api_url), tmp_path, repo, key_source == "plugin setting")
response = _search(argv, cwd, env)
assert not response["result"].get("isError"), response
assert authorizations == [f"Token {KEY}"]
@pytest.mark.parametrize("host", sorted(HOSTS))
def test_a_removed_plugin_setting_gives_way_to_a_later_mem0_init(host, tmp_path, mem0_api):
"""The key saved from a plugin setting is dropped once the setting is gone, so a newer `mem0 init` key wins."""
api_url, authorizations = mem0_api
home = tmp_path / "home"
repo = tmp_path / "repo"
repo.mkdir()
host_env = _host_env(home, api_url)
HOSTS[host](host_env, tmp_path, repo, True)
_mem0_init(home, "m0-mem0-init-key")
argv, cwd, env = HOSTS[host](host_env, tmp_path, repo, False)
response = _search(argv, cwd, env)
assert not response["result"].get("isError"), response
assert authorizations == ["Token m0-mem0-init-key"]
@@ -83,26 +83,6 @@ def test_identity_resolves_without_init(harness, spec):
assert out == f"{harness} {source_tag}"
@pytest.mark.parametrize("harness,spec", sorted(HOSTS.items()))
def test_data_dir_resolves_without_init(harness, spec):
"""mcp_server never configures the harness, so its default must match configure_harness(harness) in hooks and skills."""
directory, _ = spec
core = _core_dir(directory)
if not core.exists():
pytest.skip(f"{directory} is not built in this tree")
with tempfile.TemporaryDirectory() as tmp:
out = _run(
core,
Path(tmp),
"import os; os.environ.pop('MEM0_CODE_DATA_DIR')\n"
"import mcp_server, memory_core; print(memory_core.data_dir())\n"
f"memory_core.configure_harness({harness!r}); print(memory_core.data_dir())",
)
expected = str(Path(tmp) / ".mem0" / f"{harness}-plugin")
assert out.splitlines() == [expected, expected]
def test_mcp_server_records_the_real_harness():
"""mcp_server imports telemetry and never initialises it (server.py has no init).
@@ -1,11 +0,0 @@
import { readFileSync } from "node:fs";
import { join } from "node:path";
export function mem0CliApiKey(homeDir: string): string {
try {
const key = JSON.parse(readFileSync(join(homeDir, ".mem0", "config.json"), "utf8"))?.platform?.api_key;
return typeof key === "string" ? key.trim() : "";
} catch {
return "";
}
}
@@ -1,26 +0,0 @@
import assert from "node:assert/strict";
import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import test from "node:test";
import { mem0CliApiKey } from "../src/credentials.ts";
function homeWithCliConfig(config: string): string {
const home = mkdtempSync(join(tmpdir(), "mem0-cli-home-"));
mkdirSync(join(home, ".mem0"));
writeFileSync(join(home, ".mem0", "config.json"), config);
return home;
}
test("reads the key mem0 init saved", () => {
const home = homeWithCliConfig(JSON.stringify({ platform: { api_key: " m0-cli-key\n" } }));
assert.equal(mem0CliApiKey(home), "m0-cli-key");
});
test("missing, malformed, or non-string config reads as no key", () => {
assert.equal(mem0CliApiKey(mkdtempSync(join(tmpdir(), "mem0-cli-home-"))), "");
assert.equal(mem0CliApiKey(homeWithCliConfig("{not json")), "");
assert.equal(mem0CliApiKey(homeWithCliConfig("null")), "");
assert.equal(mem0CliApiKey(homeWithCliConfig(JSON.stringify({ platform: { api_key: 42 } }))), "");
});
@@ -2,7 +2,6 @@
HARNESS_ID = "antigravity"
SOURCE_TAG = "ANTIGRAVITY_PLUGIN"
DATA_DIR_NAME = "antigravity-plugin"
# Platform-side vocabulary (mem0_event.source + X-Application). The whole
# plugin family is one source; which editor it runs in is the application.
@@ -28,23 +28,12 @@ from typing import Any, Iterable
import telemetry
# Read from the generated per-host module so a new entrypoint is correct without
# remembering to configure anything.
try: # pragma: no cover - absent only in the un-built shared source tree
from _harness_id import DATA_DIR_NAME as _DATA_DIR_NAME
from _harness_id import PLATFORM_APPLICATION as _PLATFORM_APPLICATION
from _harness_id import PLATFORM_SOURCE as _PLATFORM_SOURCE
except ImportError:
_DATA_DIR_NAME = "mem0-plugin"
_PLATFORM_SOURCE = "MEM0_PLUGIN"
_PLATFORM_APPLICATION = ""
DEFAULT_API_URL = "https://api.mem0.ai"
PLUGIN_VERSION = "0.3.4"
PLUGIN_VERSION = "0.3.3"
_harness_name: str = "generic"
_harness_env_prefix: str = "MEM0_PLUGIN"
_harness_data_dir_name: str = _DATA_DIR_NAME
_harness_data_dir_name: str = "mem0-plugin"
_harness_source_tag: str = "mem0_plugin"
@@ -389,46 +378,30 @@ def resolve_repo(cwd: str | None) -> RepoContext:
return _resolve_repo_cached(os.path.abspath(cwd or os.getcwd()))
_PLUGIN_API_KEY_ENV = (
"PLUGIN_OPTION_API_KEY",
"CLAUDE_PLUGIN_OPTION_API_KEY",
"CLAUDE_PLUGIN_OPTION_MEM0_API_KEY",
)
def _configured(value: object) -> str:
"""The stripped value, or empty when the host left its ${placeholder} unexpanded."""
text = value.strip() if isinstance(value, str) else ""
return "" if text.startswith("${") and text.endswith("}") else text
def _first_env(*names: str) -> str:
return next((value for name in names if (value := _configured(os.environ.get(name)))), "")
def _mem0_cli_api_key() -> str:
"""The key `mem0 init` saved to the Mem0 CLI config."""
try:
config = json.loads((Path.home() / ".mem0" / "config.json").read_text(encoding="utf-8"))
return _configured(config["platform"]["api_key"])
except (OSError, ValueError, LookupError, TypeError):
return ""
def api_key() -> str:
configured = _first_env("MEM0_API_KEY", *_PLUGIN_API_KEY_ENV)
configured = (
os.environ.get("MEM0_API_KEY")
or os.environ.get("PLUGIN_OPTION_API_KEY")
or os.environ.get("CLAUDE_PLUGIN_OPTION_API_KEY")
or os.environ.get("CLAUDE_PLUGIN_OPTION_MEM0_API_KEY")
or ""
).strip()
if configured:
return configured
try:
cached = _configured((data_dir() / "api-key").read_text(encoding="utf-8"))
return (data_dir() / "api-key").read_text(encoding="utf-8").strip()
except OSError:
cached = ""
return cached or _mem0_cli_api_key()
return ""
def cache_plugin_api_key() -> bool:
"""Bridge host's hook-only sensitive config into plugin-owned storage."""
configured = _first_env(*_PLUGIN_API_KEY_ENV)
configured = (
os.environ.get("PLUGIN_OPTION_API_KEY")
or os.environ.get("CLAUDE_PLUGIN_OPTION_API_KEY")
or os.environ.get("CLAUDE_PLUGIN_OPTION_MEM0_API_KEY")
or ""
).strip()
if not configured:
return False
@@ -456,7 +429,14 @@ def cache_plugin_api_key() -> bool:
def clear_stale_api_key_cache() -> bool:
"""Drop the cached key file once every configured key source is gone."""
if _first_env("MEM0_API_KEY", *_PLUGIN_API_KEY_ENV):
configured = (
os.environ.get("MEM0_API_KEY")
or os.environ.get("PLUGIN_OPTION_API_KEY")
or os.environ.get("CLAUDE_PLUGIN_OPTION_API_KEY")
or os.environ.get("CLAUDE_PLUGIN_OPTION_MEM0_API_KEY")
or ""
).strip()
if configured:
return False
path = data_dir() / "api-key"
if not path.exists():
@@ -479,7 +459,12 @@ def detached_process_kwargs(platform: str | None = None) -> dict:
def _plugin_option(name: str, fallback: str = "") -> str:
return _first_env(f"PLUGIN_OPTION_{name.upper()}", f"CLAUDE_PLUGIN_OPTION_{name.upper()}", fallback)
return (
os.environ.get(f"PLUGIN_OPTION_{name.upper()}")
or os.environ.get(f"CLAUDE_PLUGIN_OPTION_{name.upper()}")
or os.environ.get(fallback)
or ""
).strip()
def user_id() -> str:
@@ -1813,6 +1798,16 @@ def extraction_message_batches(
return batches
# Platform surface attribution. Read from the generated per-host module so a new
# entrypoint is correct without remembering to configure anything.
try: # pragma: no cover - absent only in the un-built shared source tree
from _harness_id import PLATFORM_APPLICATION as _PLATFORM_APPLICATION
from _harness_id import PLATFORM_SOURCE as _PLATFORM_SOURCE
except ImportError:
_PLATFORM_SOURCE = "MEM0_PLUGIN"
_PLATFORM_APPLICATION = ""
def platform_headers(key: str) -> dict[str, str]:
"""Auth plus the three surface-identity headers.
@@ -1,6 +1,6 @@
{
"id": "mem0",
"version": "0.3.4",
"version": "0.3.3",
"homepage": "https://docs.mem0.ai/integrations/antigravity",
"native": {
"pluginRoot": "${ANTIGRAVITY_PLUGIN_ROOT}",
@@ -19,7 +19,5 @@ API key is configured, the event/flush/retrieval counts (`flushes` is the
number of completed flushes, not a pending count), and the doctor check
results. If doctor reports an authentication failure (401 / invalid key), say
clearly that the Mem0 API key is invalid or expired and that memories are NOT
being created. Never report an auth failure as "no memories found". When the
key is missing or invalid, suggest updating the plugin's API key setting,
exporting `MEM0_API_KEY`, or running `mem0 init` (the plugin reads the key the
Mem0 CLI saves in `~/.mem0/config.json`).
being created. Never report an auth failure as "no memories found". Suggest
reinstalling with `--config api_key=...` in that case.
@@ -1,6 +1,6 @@
{
"name": "mem0",
"version": "0.3.4",
"version": "0.3.3",
"description": "Cross-session memory and token savings for coding agents.",
"author": {
"name": "Mem0"
@@ -2,7 +2,6 @@
HARNESS_ID = "claude-code"
SOURCE_TAG = "CLAUDE_CODE_PLUGIN"
DATA_DIR_NAME = "claude-code-plugin"
# Platform-side vocabulary (mem0_event.source + X-Application). The whole
# plugin family is one source; which editor it runs in is the application.
@@ -28,23 +28,12 @@ from typing import Any, Iterable
import telemetry
# Read from the generated per-host module so a new entrypoint is correct without
# remembering to configure anything.
try: # pragma: no cover - absent only in the un-built shared source tree
from _harness_id import DATA_DIR_NAME as _DATA_DIR_NAME
from _harness_id import PLATFORM_APPLICATION as _PLATFORM_APPLICATION
from _harness_id import PLATFORM_SOURCE as _PLATFORM_SOURCE
except ImportError:
_DATA_DIR_NAME = "mem0-plugin"
_PLATFORM_SOURCE = "MEM0_PLUGIN"
_PLATFORM_APPLICATION = ""
DEFAULT_API_URL = "https://api.mem0.ai"
PLUGIN_VERSION = "0.3.4"
PLUGIN_VERSION = "0.3.3"
_harness_name: str = "generic"
_harness_env_prefix: str = "MEM0_PLUGIN"
_harness_data_dir_name: str = _DATA_DIR_NAME
_harness_data_dir_name: str = "mem0-plugin"
_harness_source_tag: str = "mem0_plugin"
@@ -389,46 +378,30 @@ def resolve_repo(cwd: str | None) -> RepoContext:
return _resolve_repo_cached(os.path.abspath(cwd or os.getcwd()))
_PLUGIN_API_KEY_ENV = (
"PLUGIN_OPTION_API_KEY",
"CLAUDE_PLUGIN_OPTION_API_KEY",
"CLAUDE_PLUGIN_OPTION_MEM0_API_KEY",
)
def _configured(value: object) -> str:
"""The stripped value, or empty when the host left its ${placeholder} unexpanded."""
text = value.strip() if isinstance(value, str) else ""
return "" if text.startswith("${") and text.endswith("}") else text
def _first_env(*names: str) -> str:
return next((value for name in names if (value := _configured(os.environ.get(name)))), "")
def _mem0_cli_api_key() -> str:
"""The key `mem0 init` saved to the Mem0 CLI config."""
try:
config = json.loads((Path.home() / ".mem0" / "config.json").read_text(encoding="utf-8"))
return _configured(config["platform"]["api_key"])
except (OSError, ValueError, LookupError, TypeError):
return ""
def api_key() -> str:
configured = _first_env("MEM0_API_KEY", *_PLUGIN_API_KEY_ENV)
configured = (
os.environ.get("MEM0_API_KEY")
or os.environ.get("PLUGIN_OPTION_API_KEY")
or os.environ.get("CLAUDE_PLUGIN_OPTION_API_KEY")
or os.environ.get("CLAUDE_PLUGIN_OPTION_MEM0_API_KEY")
or ""
).strip()
if configured:
return configured
try:
cached = _configured((data_dir() / "api-key").read_text(encoding="utf-8"))
return (data_dir() / "api-key").read_text(encoding="utf-8").strip()
except OSError:
cached = ""
return cached or _mem0_cli_api_key()
return ""
def cache_plugin_api_key() -> bool:
"""Bridge host's hook-only sensitive config into plugin-owned storage."""
configured = _first_env(*_PLUGIN_API_KEY_ENV)
configured = (
os.environ.get("PLUGIN_OPTION_API_KEY")
or os.environ.get("CLAUDE_PLUGIN_OPTION_API_KEY")
or os.environ.get("CLAUDE_PLUGIN_OPTION_MEM0_API_KEY")
or ""
).strip()
if not configured:
return False
@@ -456,7 +429,14 @@ def cache_plugin_api_key() -> bool:
def clear_stale_api_key_cache() -> bool:
"""Drop the cached key file once every configured key source is gone."""
if _first_env("MEM0_API_KEY", *_PLUGIN_API_KEY_ENV):
configured = (
os.environ.get("MEM0_API_KEY")
or os.environ.get("PLUGIN_OPTION_API_KEY")
or os.environ.get("CLAUDE_PLUGIN_OPTION_API_KEY")
or os.environ.get("CLAUDE_PLUGIN_OPTION_MEM0_API_KEY")
or ""
).strip()
if configured:
return False
path = data_dir() / "api-key"
if not path.exists():
@@ -479,7 +459,12 @@ def detached_process_kwargs(platform: str | None = None) -> dict:
def _plugin_option(name: str, fallback: str = "") -> str:
return _first_env(f"PLUGIN_OPTION_{name.upper()}", f"CLAUDE_PLUGIN_OPTION_{name.upper()}", fallback)
return (
os.environ.get(f"PLUGIN_OPTION_{name.upper()}")
or os.environ.get(f"CLAUDE_PLUGIN_OPTION_{name.upper()}")
or os.environ.get(fallback)
or ""
).strip()
def user_id() -> str:
@@ -1813,6 +1798,16 @@ def extraction_message_batches(
return batches
# Platform surface attribution. Read from the generated per-host module so a new
# entrypoint is correct without remembering to configure anything.
try: # pragma: no cover - absent only in the un-built shared source tree
from _harness_id import PLATFORM_APPLICATION as _PLATFORM_APPLICATION
from _harness_id import PLATFORM_SOURCE as _PLATFORM_SOURCE
except ImportError:
_PLATFORM_SOURCE = "MEM0_PLUGIN"
_PLATFORM_APPLICATION = ""
def platform_headers(key: str) -> dict[str, str]:
"""Auth plus the three surface-identity headers.
@@ -1,6 +1,6 @@
{
"id": "mem0",
"version": "0.3.4",
"version": "0.3.3",
"homepage": "https://docs.mem0.ai/integrations/claude-code",
"native": {
"pluginRoot": "${CLAUDE_PLUGIN_ROOT}",
@@ -19,7 +19,5 @@ API key is configured, the event/flush/retrieval counts (`flushes` is the
number of completed flushes, not a pending count), and the doctor check
results. If doctor reports an authentication failure (401 / invalid key), say
clearly that the Mem0 API key is invalid or expired and that memories are NOT
being created. Never report an auth failure as "no memories found". When the
key is missing or invalid, suggest updating the plugin's API key setting,
exporting `MEM0_API_KEY`, or running `mem0 init` (the plugin reads the key the
Mem0 CLI saves in `~/.mem0/config.json`).
being created. Never report an auth failure as "no memories found". Suggest
reinstalling with `--config api_key=...` in that case.
@@ -31,13 +31,9 @@ def isolated_env(tmp_path, monkeypatch):
monkeypatch.setenv("MEM0_CODE_DATA_DIR", str(tmp_path / "data"))
monkeypatch.setenv("MEM0_CODE_USER_ID", "test-user")
monkeypatch.delenv("MEM0_API_KEY", raising=False)
monkeypatch.delenv("PLUGIN_OPTION_API_KEY", raising=False)
monkeypatch.delenv("PLUGIN_OPTION_USER_ID", raising=False)
monkeypatch.delenv("CLAUDE_PLUGIN_OPTION_API_KEY", raising=False)
monkeypatch.delenv("CLAUDE_PLUGIN_OPTION_MEM0_API_KEY", raising=False)
monkeypatch.delenv("CLAUDE_PLUGIN_DATA", raising=False)
monkeypatch.setenv("HOME", str(tmp_path / "home"))
monkeypatch.setenv("USERPROFILE", str(tmp_path / "home"))
# The 0.2.x plugin exports these into every hooked shell; without this the
# suite fails for anyone running it inside a session with that plugin active.
monkeypatch.delenv("MEM0_PROJECT_ID", raising=False)
@@ -3754,71 +3750,6 @@ def test_stale_cached_api_key_is_cleared_when_config_is_removed(
assert memory_core.clear_stale_api_key_cache() is False
def _mem0_cli_init(home: Path, config: object) -> None:
(home / ".mem0").mkdir(parents=True, exist_ok=True)
(home / ".mem0" / "config.json").write_text(json.dumps(config), encoding="utf-8")
def test_api_key_falls_back_to_the_mem0_cli_config(isolated_env):
_mem0_cli_init(isolated_env / "home", {"platform": {"api_key": " m0-cli-key\n"}})
assert memory_core.api_key() == "m0-cli-key"
@pytest.mark.parametrize(
"config",
[{"platform": {}}, {"platform": "m0-oops"}, {"platform": {"api_key": 42}}, ["m0-list"]],
)
def test_malformed_mem0_cli_config_reads_as_no_key(isolated_env, config):
_mem0_cli_init(isolated_env / "home", config)
assert memory_core.api_key() == ""
def test_unreadable_mem0_cli_config_reads_as_no_key(isolated_env):
(isolated_env / "home" / ".mem0").mkdir(parents=True)
(isolated_env / "home" / ".mem0" / "config.json").write_text("{not json", encoding="utf-8")
assert memory_core.api_key() == ""
def test_plugin_configured_key_wins_over_the_mem0_cli_config(isolated_env, monkeypatch):
_mem0_cli_init(isolated_env / "home", {"platform": {"api_key": "m0-cli-key"}})
monkeypatch.setenv("PLUGIN_OPTION_API_KEY", "m0-plugin-key")
assert memory_core.cache_plugin_api_key() is True
assert memory_core.api_key() == "m0-plugin-key"
monkeypatch.delenv("PLUGIN_OPTION_API_KEY")
assert memory_core.api_key() == "m0-plugin-key"
def test_unexpanded_host_placeholder_is_never_used_as_the_api_key(isolated_env, monkeypatch):
monkeypatch.setenv("PLUGIN_OPTION_API_KEY", "${api_key}")
assert memory_core.cache_plugin_api_key() is False
assert not (isolated_env / "data" / "api-key").exists()
assert memory_core.api_key() == ""
_mem0_cli_init(isolated_env / "home", {"platform": {"api_key": "m0-cli-key"}})
assert memory_core.api_key() == "m0-cli-key"
def test_placeholder_cached_by_an_older_plugin_is_ignored(isolated_env):
(isolated_env / "data").mkdir()
(isolated_env / "data" / "api-key").write_text("${api_key}", encoding="utf-8")
_mem0_cli_init(isolated_env / "home", {"platform": {"api_key": "m0-cli-key"}})
assert memory_core.api_key() == "m0-cli-key"
def test_unexpanded_placeholder_plugin_options_fall_back(isolated_env, monkeypatch):
monkeypatch.setenv("PLUGIN_OPTION_USER_ID", "${user_id}")
monkeypatch.setenv("PLUGIN_OPTION_TOP_K", "${top_k}")
assert memory_core.user_id() == "test-user"
assert memory_core._plugin_option("top_k") == ""
def _big_batch_messages() -> list[dict[str, str]]:
return [
{"role": "user", "content": "A" * 20000},
@@ -24,8 +24,6 @@ def isolated_env(tmp_path, monkeypatch):
monkeypatch.delenv("CLAUDE_PLUGIN_OPTION_API_KEY", raising=False)
monkeypatch.delenv("CLAUDE_PLUGIN_OPTION_MEM0_API_KEY", raising=False)
monkeypatch.delenv("MEM0_API_URL", raising=False)
monkeypatch.setenv("HOME", str(tmp_path / "home"))
monkeypatch.setenv("USERPROFILE", str(tmp_path / "home"))
return tmp_path
@@ -1,6 +1,6 @@
{
"name": "mem0",
"version": "0.3.4",
"version": "0.3.3",
"description": "Cross-session memory and token savings for coding agents.",
"author": { "name": "Mem0", "email": "support@mem0.ai" },
"homepage": "https://docs.mem0.ai/integrations/codex",
@@ -2,7 +2,6 @@
HARNESS_ID = "codex"
SOURCE_TAG = "CODEX_PLUGIN"
DATA_DIR_NAME = "codex-plugin"
# Platform-side vocabulary (mem0_event.source + X-Application). The whole
# plugin family is one source; which editor it runs in is the application.
+41 -46
View File
@@ -28,23 +28,12 @@ from typing import Any, Iterable
import telemetry
# Read from the generated per-host module so a new entrypoint is correct without
# remembering to configure anything.
try: # pragma: no cover - absent only in the un-built shared source tree
from _harness_id import DATA_DIR_NAME as _DATA_DIR_NAME
from _harness_id import PLATFORM_APPLICATION as _PLATFORM_APPLICATION
from _harness_id import PLATFORM_SOURCE as _PLATFORM_SOURCE
except ImportError:
_DATA_DIR_NAME = "mem0-plugin"
_PLATFORM_SOURCE = "MEM0_PLUGIN"
_PLATFORM_APPLICATION = ""
DEFAULT_API_URL = "https://api.mem0.ai"
PLUGIN_VERSION = "0.3.4"
PLUGIN_VERSION = "0.3.3"
_harness_name: str = "generic"
_harness_env_prefix: str = "MEM0_PLUGIN"
_harness_data_dir_name: str = _DATA_DIR_NAME
_harness_data_dir_name: str = "mem0-plugin"
_harness_source_tag: str = "mem0_plugin"
@@ -389,46 +378,30 @@ def resolve_repo(cwd: str | None) -> RepoContext:
return _resolve_repo_cached(os.path.abspath(cwd or os.getcwd()))
_PLUGIN_API_KEY_ENV = (
"PLUGIN_OPTION_API_KEY",
"CLAUDE_PLUGIN_OPTION_API_KEY",
"CLAUDE_PLUGIN_OPTION_MEM0_API_KEY",
)
def _configured(value: object) -> str:
"""The stripped value, or empty when the host left its ${placeholder} unexpanded."""
text = value.strip() if isinstance(value, str) else ""
return "" if text.startswith("${") and text.endswith("}") else text
def _first_env(*names: str) -> str:
return next((value for name in names if (value := _configured(os.environ.get(name)))), "")
def _mem0_cli_api_key() -> str:
"""The key `mem0 init` saved to the Mem0 CLI config."""
try:
config = json.loads((Path.home() / ".mem0" / "config.json").read_text(encoding="utf-8"))
return _configured(config["platform"]["api_key"])
except (OSError, ValueError, LookupError, TypeError):
return ""
def api_key() -> str:
configured = _first_env("MEM0_API_KEY", *_PLUGIN_API_KEY_ENV)
configured = (
os.environ.get("MEM0_API_KEY")
or os.environ.get("PLUGIN_OPTION_API_KEY")
or os.environ.get("CLAUDE_PLUGIN_OPTION_API_KEY")
or os.environ.get("CLAUDE_PLUGIN_OPTION_MEM0_API_KEY")
or ""
).strip()
if configured:
return configured
try:
cached = _configured((data_dir() / "api-key").read_text(encoding="utf-8"))
return (data_dir() / "api-key").read_text(encoding="utf-8").strip()
except OSError:
cached = ""
return cached or _mem0_cli_api_key()
return ""
def cache_plugin_api_key() -> bool:
"""Bridge host's hook-only sensitive config into plugin-owned storage."""
configured = _first_env(*_PLUGIN_API_KEY_ENV)
configured = (
os.environ.get("PLUGIN_OPTION_API_KEY")
or os.environ.get("CLAUDE_PLUGIN_OPTION_API_KEY")
or os.environ.get("CLAUDE_PLUGIN_OPTION_MEM0_API_KEY")
or ""
).strip()
if not configured:
return False
@@ -456,7 +429,14 @@ def cache_plugin_api_key() -> bool:
def clear_stale_api_key_cache() -> bool:
"""Drop the cached key file once every configured key source is gone."""
if _first_env("MEM0_API_KEY", *_PLUGIN_API_KEY_ENV):
configured = (
os.environ.get("MEM0_API_KEY")
or os.environ.get("PLUGIN_OPTION_API_KEY")
or os.environ.get("CLAUDE_PLUGIN_OPTION_API_KEY")
or os.environ.get("CLAUDE_PLUGIN_OPTION_MEM0_API_KEY")
or ""
).strip()
if configured:
return False
path = data_dir() / "api-key"
if not path.exists():
@@ -479,7 +459,12 @@ def detached_process_kwargs(platform: str | None = None) -> dict:
def _plugin_option(name: str, fallback: str = "") -> str:
return _first_env(f"PLUGIN_OPTION_{name.upper()}", f"CLAUDE_PLUGIN_OPTION_{name.upper()}", fallback)
return (
os.environ.get(f"PLUGIN_OPTION_{name.upper()}")
or os.environ.get(f"CLAUDE_PLUGIN_OPTION_{name.upper()}")
or os.environ.get(fallback)
or ""
).strip()
def user_id() -> str:
@@ -1813,6 +1798,16 @@ def extraction_message_batches(
return batches
# Platform surface attribution. Read from the generated per-host module so a new
# entrypoint is correct without remembering to configure anything.
try: # pragma: no cover - absent only in the un-built shared source tree
from _harness_id import PLATFORM_APPLICATION as _PLATFORM_APPLICATION
from _harness_id import PLATFORM_SOURCE as _PLATFORM_SOURCE
except ImportError:
_PLATFORM_SOURCE = "MEM0_PLUGIN"
_PLATFORM_APPLICATION = ""
def platform_headers(key: str) -> dict[str, str]:
"""Auth plus the three surface-identity headers.
+1 -1
View File
@@ -1,6 +1,6 @@
{
"id": "mem0",
"version": "0.3.4",
"version": "0.3.3",
"homepage": "https://docs.mem0.ai/integrations/codex",
"native": {
"pluginRoot": "${PLUGIN_ROOT}",
@@ -19,7 +19,5 @@ API key is configured, the event/flush/retrieval counts (`flushes` is the
number of completed flushes, not a pending count), and the doctor check
results. If doctor reports an authentication failure (401 / invalid key), say
clearly that the Mem0 API key is invalid or expired and that memories are NOT
being created. Never report an auth failure as "no memories found". When the
key is missing or invalid, suggest updating the plugin's API key setting,
exporting `MEM0_API_KEY`, or running `mem0 init` (the plugin reads the key the
Mem0 CLI saves in `~/.mem0/config.json`).
being created. Never report an auth failure as "no memories found". Suggest
reinstalling with `--config api_key=...` in that case.
@@ -1,6 +1,6 @@
{
"name": "mem0",
"version": "0.3.4",
"version": "0.3.3",
"description": "Cross-session memory and token savings for coding agents.",
"author": { "name": "Mem0", "email": "support@mem0.ai" },
"homepage": "https://docs.mem0.ai/integrations/cursor",
@@ -2,7 +2,6 @@
HARNESS_ID = "cursor"
SOURCE_TAG = "CURSOR_PLUGIN"
DATA_DIR_NAME = "cursor-plugin"
# Platform-side vocabulary (mem0_event.source + X-Application). The whole
# plugin family is one source; which editor it runs in is the application.
+41 -46
View File
@@ -28,23 +28,12 @@ from typing import Any, Iterable
import telemetry
# Read from the generated per-host module so a new entrypoint is correct without
# remembering to configure anything.
try: # pragma: no cover - absent only in the un-built shared source tree
from _harness_id import DATA_DIR_NAME as _DATA_DIR_NAME
from _harness_id import PLATFORM_APPLICATION as _PLATFORM_APPLICATION
from _harness_id import PLATFORM_SOURCE as _PLATFORM_SOURCE
except ImportError:
_DATA_DIR_NAME = "mem0-plugin"
_PLATFORM_SOURCE = "MEM0_PLUGIN"
_PLATFORM_APPLICATION = ""
DEFAULT_API_URL = "https://api.mem0.ai"
PLUGIN_VERSION = "0.3.4"
PLUGIN_VERSION = "0.3.3"
_harness_name: str = "generic"
_harness_env_prefix: str = "MEM0_PLUGIN"
_harness_data_dir_name: str = _DATA_DIR_NAME
_harness_data_dir_name: str = "mem0-plugin"
_harness_source_tag: str = "mem0_plugin"
@@ -389,46 +378,30 @@ def resolve_repo(cwd: str | None) -> RepoContext:
return _resolve_repo_cached(os.path.abspath(cwd or os.getcwd()))
_PLUGIN_API_KEY_ENV = (
"PLUGIN_OPTION_API_KEY",
"CLAUDE_PLUGIN_OPTION_API_KEY",
"CLAUDE_PLUGIN_OPTION_MEM0_API_KEY",
)
def _configured(value: object) -> str:
"""The stripped value, or empty when the host left its ${placeholder} unexpanded."""
text = value.strip() if isinstance(value, str) else ""
return "" if text.startswith("${") and text.endswith("}") else text
def _first_env(*names: str) -> str:
return next((value for name in names if (value := _configured(os.environ.get(name)))), "")
def _mem0_cli_api_key() -> str:
"""The key `mem0 init` saved to the Mem0 CLI config."""
try:
config = json.loads((Path.home() / ".mem0" / "config.json").read_text(encoding="utf-8"))
return _configured(config["platform"]["api_key"])
except (OSError, ValueError, LookupError, TypeError):
return ""
def api_key() -> str:
configured = _first_env("MEM0_API_KEY", *_PLUGIN_API_KEY_ENV)
configured = (
os.environ.get("MEM0_API_KEY")
or os.environ.get("PLUGIN_OPTION_API_KEY")
or os.environ.get("CLAUDE_PLUGIN_OPTION_API_KEY")
or os.environ.get("CLAUDE_PLUGIN_OPTION_MEM0_API_KEY")
or ""
).strip()
if configured:
return configured
try:
cached = _configured((data_dir() / "api-key").read_text(encoding="utf-8"))
return (data_dir() / "api-key").read_text(encoding="utf-8").strip()
except OSError:
cached = ""
return cached or _mem0_cli_api_key()
return ""
def cache_plugin_api_key() -> bool:
"""Bridge host's hook-only sensitive config into plugin-owned storage."""
configured = _first_env(*_PLUGIN_API_KEY_ENV)
configured = (
os.environ.get("PLUGIN_OPTION_API_KEY")
or os.environ.get("CLAUDE_PLUGIN_OPTION_API_KEY")
or os.environ.get("CLAUDE_PLUGIN_OPTION_MEM0_API_KEY")
or ""
).strip()
if not configured:
return False
@@ -456,7 +429,14 @@ def cache_plugin_api_key() -> bool:
def clear_stale_api_key_cache() -> bool:
"""Drop the cached key file once every configured key source is gone."""
if _first_env("MEM0_API_KEY", *_PLUGIN_API_KEY_ENV):
configured = (
os.environ.get("MEM0_API_KEY")
or os.environ.get("PLUGIN_OPTION_API_KEY")
or os.environ.get("CLAUDE_PLUGIN_OPTION_API_KEY")
or os.environ.get("CLAUDE_PLUGIN_OPTION_MEM0_API_KEY")
or ""
).strip()
if configured:
return False
path = data_dir() / "api-key"
if not path.exists():
@@ -479,7 +459,12 @@ def detached_process_kwargs(platform: str | None = None) -> dict:
def _plugin_option(name: str, fallback: str = "") -> str:
return _first_env(f"PLUGIN_OPTION_{name.upper()}", f"CLAUDE_PLUGIN_OPTION_{name.upper()}", fallback)
return (
os.environ.get(f"PLUGIN_OPTION_{name.upper()}")
or os.environ.get(f"CLAUDE_PLUGIN_OPTION_{name.upper()}")
or os.environ.get(fallback)
or ""
).strip()
def user_id() -> str:
@@ -1813,6 +1798,16 @@ def extraction_message_batches(
return batches
# Platform surface attribution. Read from the generated per-host module so a new
# entrypoint is correct without remembering to configure anything.
try: # pragma: no cover - absent only in the un-built shared source tree
from _harness_id import PLATFORM_APPLICATION as _PLATFORM_APPLICATION
from _harness_id import PLATFORM_SOURCE as _PLATFORM_SOURCE
except ImportError:
_PLATFORM_SOURCE = "MEM0_PLUGIN"
_PLATFORM_APPLICATION = ""
def platform_headers(key: str) -> dict[str, str]:
"""Auth plus the three surface-identity headers.
+1 -1
View File
@@ -1,6 +1,6 @@
{
"id": "mem0",
"version": "0.3.4",
"version": "0.3.3",
"homepage": "https://docs.mem0.ai/integrations/cursor",
"native": {
"pluginRoot": "${CURSOR_PLUGIN_ROOT}",
@@ -19,7 +19,5 @@ API key is configured, the event/flush/retrieval counts (`flushes` is the
number of completed flushes, not a pending count), and the doctor check
results. If doctor reports an authentication failure (401 / invalid key), say
clearly that the Mem0 API key is invalid or expired and that memories are NOT
being created. Never report an auth failure as "no memories found". When the
key is missing or invalid, suggest updating the plugin's API key setting,
exporting `MEM0_API_KEY`, or running `mem0 init` (the plugin reads the key the
Mem0 CLI saves in `~/.mem0/config.json`).
being created. Never report an auth failure as "no memories found". Suggest
reinstalling with `--config api_key=...` in that case.
+6 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@mem0/deepseek-plugin",
"version": "0.3.3",
"version": "0.3.2",
"description": "Mem0 long-term memory as a native DeepSeek Harness (Cordis) plugin.",
"type": "module",
"license": "Apache-2.0",
@@ -60,5 +60,10 @@
"tsup": "^8.5.0",
"typescript": "^5.6.0",
"vitest": "^4.1.7"
},
"pnpm": {
"overrides": {
"axios@<1.20.0": ">=1.20.0 <2.0.0"
}
}
}
+7 -4
View File
@@ -4,6 +4,9 @@ settings:
autoInstallPeers: false
excludeLinksFromLockfile: false
overrides:
axios@<1.20.0: '>=1.20.0 <2.0.0'
importers:
.:
@@ -575,8 +578,8 @@ packages:
asynckit@0.4.0:
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
axios@1.19.0:
resolution: {integrity: sha512-ht/iuYZXEjFxLH/Hkezgd7m6JKlHHXEUSneaDz8uZe1Gj5QZtCnpyDsckvAiEnT89OEbCLmnte4R4sn7P0EKFw==}
axios@1.20.0:
resolution: {integrity: sha512-r8aOh8j9cGKpgQAqpzrUHnSIc6a59Y3Xf/cv8sy1DrHCkZHzQGEuoq1tARk6qSyDdtQGSDgpb9kFlruzPvrgwg==}
bundle-require@5.1.0:
resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==}
@@ -1610,7 +1613,7 @@ snapshots:
asynckit@0.4.0: {}
axios@1.19.0:
axios@1.20.0:
dependencies:
follow-redirects: 1.16.0
form-data: 4.0.6
@@ -1856,7 +1859,7 @@ snapshots:
mem0ai@3.1.6:
dependencies:
axios: 1.19.0
axios: 1.20.0
openai: 4.104.0(zod@3.25.76)
uuid: 11.1.1
zod: 3.25.76
+1 -3
View File
@@ -10,7 +10,6 @@
* tools registered via `ctx.tools.register(...)` are auto-unregistered when the
* plugin unmounts (Cordis revertible effects).
*/
import { homedir } from "node:os";
import type { Context } from "@deepseek-ai/cordis";
import type {} from "@deepseek-ai/dsh-agent";
import type { PromptAssembly } from "@deepseek-ai/dsh-system-prompt";
@@ -21,7 +20,6 @@ import { formatMemoryList, formatAddResult } from "./formatting.ts";
import { truncateOutput } from "./output.ts";
import { resolveSearchFilters, resolveAddParams } from "./scoping.ts";
import { captureEvent, errorKind } from "./telemetry.ts";
import { mem0CliApiKey } from "../../agent-plugin-core/typescript/src/credentials.ts";
import { createMemoryLifecycle } from "../../agent-plugin-core/typescript/src/lifecycle.ts";
import {
USER_RECALL_HEADING,
@@ -96,7 +94,7 @@ const scopeParams = {
} as const;
export function apply(ctx: Context, config: Config): void {
const apiKey = config.apiKey || process.env.MEM0_API_KEY || mem0CliApiKey(homedir());
const apiKey = config.apiKey ?? process.env.MEM0_API_KEY;
if (!apiKey) {
throw new Error("deepseek-plugin: set config.apiKey or the MEM0_API_KEY env var");
}
@@ -1,19 +1,12 @@
import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
// Offline mock of the Mem0 SDK so these tests never touch the network.
const mockSearch = vi.fn();
const mockAdd = vi.fn();
const mockClientOptions = vi.fn();
vi.mock("mem0ai", () => ({
MemoryClient: class {
search = mockSearch;
add = mockAdd;
constructor(options: unknown) {
mockClientOptions(options);
}
},
}));
@@ -56,24 +49,18 @@ function applyAndCollectListeners(config: Config): Map<string, HarnessListener>
let savedKey: string | undefined;
let savedTelemetry: string | undefined;
let savedHome: string | undefined;
beforeEach(() => {
savedKey = process.env.MEM0_API_KEY;
savedTelemetry = process.env.MEM0_TELEMETRY;
savedHome = process.env.HOME;
process.env.MEM0_TELEMETRY = "false";
process.env.HOME = mkdtempSync(join(tmpdir(), "deepseek-home-"));
mockSearch.mockReset();
mockAdd.mockReset();
mockClientOptions.mockReset();
});
afterEach(() => {
if (savedKey === undefined) delete process.env.MEM0_API_KEY;
else process.env.MEM0_API_KEY = savedKey;
if (savedHome === undefined) delete process.env.HOME;
else process.env.HOME = savedHome;
if (savedTelemetry === undefined) delete process.env.MEM0_TELEMETRY;
else process.env.MEM0_TELEMETRY = savedTelemetry;
});
@@ -84,17 +71,6 @@ describe("apply() config validation", () => {
expect(() => applyAndCollect({ userId: "u" } as Config)).toThrow(/apiKey|MEM0_API_KEY/);
});
it("falls back to the key mem0 init saved", () => {
delete process.env.MEM0_API_KEY;
const home = process.env.HOME as string;
mkdirSync(join(home, ".mem0"));
writeFileSync(join(home, ".mem0", "config.json"), JSON.stringify({ platform: { api_key: "m0-cli-key" } }));
applyAndCollect({ userId: "u" } as Config);
expect(mockClientOptions).toHaveBeenCalledWith({ apiKey: "m0-cli-key" });
});
it("throws when userId is missing", () => {
expect(() => applyAndCollect({ apiKey: "k", userId: "" } as Config)).toThrow(/userId/);
});
@@ -2,7 +2,6 @@
HARNESS_ID = "kimi"
SOURCE_TAG = "KIMI_PLUGIN"
DATA_DIR_NAME = "kimi-plugin"
# Platform-side vocabulary (mem0_event.source + X-Application). The whole
# plugin family is one source; which editor it runs in is the application.
+41 -46
View File
@@ -28,23 +28,12 @@ from typing import Any, Iterable
import telemetry
# Read from the generated per-host module so a new entrypoint is correct without
# remembering to configure anything.
try: # pragma: no cover - absent only in the un-built shared source tree
from _harness_id import DATA_DIR_NAME as _DATA_DIR_NAME
from _harness_id import PLATFORM_APPLICATION as _PLATFORM_APPLICATION
from _harness_id import PLATFORM_SOURCE as _PLATFORM_SOURCE
except ImportError:
_DATA_DIR_NAME = "mem0-plugin"
_PLATFORM_SOURCE = "MEM0_PLUGIN"
_PLATFORM_APPLICATION = ""
DEFAULT_API_URL = "https://api.mem0.ai"
PLUGIN_VERSION = "0.3.4"
PLUGIN_VERSION = "0.3.3"
_harness_name: str = "generic"
_harness_env_prefix: str = "MEM0_PLUGIN"
_harness_data_dir_name: str = _DATA_DIR_NAME
_harness_data_dir_name: str = "mem0-plugin"
_harness_source_tag: str = "mem0_plugin"
@@ -389,46 +378,30 @@ def resolve_repo(cwd: str | None) -> RepoContext:
return _resolve_repo_cached(os.path.abspath(cwd or os.getcwd()))
_PLUGIN_API_KEY_ENV = (
"PLUGIN_OPTION_API_KEY",
"CLAUDE_PLUGIN_OPTION_API_KEY",
"CLAUDE_PLUGIN_OPTION_MEM0_API_KEY",
)
def _configured(value: object) -> str:
"""The stripped value, or empty when the host left its ${placeholder} unexpanded."""
text = value.strip() if isinstance(value, str) else ""
return "" if text.startswith("${") and text.endswith("}") else text
def _first_env(*names: str) -> str:
return next((value for name in names if (value := _configured(os.environ.get(name)))), "")
def _mem0_cli_api_key() -> str:
"""The key `mem0 init` saved to the Mem0 CLI config."""
try:
config = json.loads((Path.home() / ".mem0" / "config.json").read_text(encoding="utf-8"))
return _configured(config["platform"]["api_key"])
except (OSError, ValueError, LookupError, TypeError):
return ""
def api_key() -> str:
configured = _first_env("MEM0_API_KEY", *_PLUGIN_API_KEY_ENV)
configured = (
os.environ.get("MEM0_API_KEY")
or os.environ.get("PLUGIN_OPTION_API_KEY")
or os.environ.get("CLAUDE_PLUGIN_OPTION_API_KEY")
or os.environ.get("CLAUDE_PLUGIN_OPTION_MEM0_API_KEY")
or ""
).strip()
if configured:
return configured
try:
cached = _configured((data_dir() / "api-key").read_text(encoding="utf-8"))
return (data_dir() / "api-key").read_text(encoding="utf-8").strip()
except OSError:
cached = ""
return cached or _mem0_cli_api_key()
return ""
def cache_plugin_api_key() -> bool:
"""Bridge host's hook-only sensitive config into plugin-owned storage."""
configured = _first_env(*_PLUGIN_API_KEY_ENV)
configured = (
os.environ.get("PLUGIN_OPTION_API_KEY")
or os.environ.get("CLAUDE_PLUGIN_OPTION_API_KEY")
or os.environ.get("CLAUDE_PLUGIN_OPTION_MEM0_API_KEY")
or ""
).strip()
if not configured:
return False
@@ -456,7 +429,14 @@ def cache_plugin_api_key() -> bool:
def clear_stale_api_key_cache() -> bool:
"""Drop the cached key file once every configured key source is gone."""
if _first_env("MEM0_API_KEY", *_PLUGIN_API_KEY_ENV):
configured = (
os.environ.get("MEM0_API_KEY")
or os.environ.get("PLUGIN_OPTION_API_KEY")
or os.environ.get("CLAUDE_PLUGIN_OPTION_API_KEY")
or os.environ.get("CLAUDE_PLUGIN_OPTION_MEM0_API_KEY")
or ""
).strip()
if configured:
return False
path = data_dir() / "api-key"
if not path.exists():
@@ -479,7 +459,12 @@ def detached_process_kwargs(platform: str | None = None) -> dict:
def _plugin_option(name: str, fallback: str = "") -> str:
return _first_env(f"PLUGIN_OPTION_{name.upper()}", f"CLAUDE_PLUGIN_OPTION_{name.upper()}", fallback)
return (
os.environ.get(f"PLUGIN_OPTION_{name.upper()}")
or os.environ.get(f"CLAUDE_PLUGIN_OPTION_{name.upper()}")
or os.environ.get(fallback)
or ""
).strip()
def user_id() -> str:
@@ -1813,6 +1798,16 @@ def extraction_message_batches(
return batches
# Platform surface attribution. Read from the generated per-host module so a new
# entrypoint is correct without remembering to configure anything.
try: # pragma: no cover - absent only in the un-built shared source tree
from _harness_id import PLATFORM_APPLICATION as _PLATFORM_APPLICATION
from _harness_id import PLATFORM_SOURCE as _PLATFORM_SOURCE
except ImportError:
_PLATFORM_SOURCE = "MEM0_PLUGIN"
_PLATFORM_APPLICATION = ""
def platform_headers(key: str) -> dict[str, str]:
"""Auth plus the three surface-identity headers.
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "mem0",
"version": "0.3.4",
"version": "0.3.3",
"description": "Cross-session memory and token savings for coding agents.",
"keywords": ["memory", "coding-agents", "continual-learning", "token-efficiency"],
"author": { "name": "Mem0", "email": "support@mem0.ai" },
+1 -1
View File
@@ -1,6 +1,6 @@
{
"id": "mem0",
"version": "0.3.4",
"version": "0.3.3",
"homepage": "https://docs.mem0.ai/integrations/kimi",
"native": {
"pluginRoot": "${KIMI_PLUGIN_ROOT}",
@@ -19,7 +19,5 @@ API key is configured, the event/flush/retrieval counts (`flushes` is the
number of completed flushes, not a pending count), and the doctor check
results. If doctor reports an authentication failure (401 / invalid key), say
clearly that the Mem0 API key is invalid or expired and that memories are NOT
being created. Never report an auth failure as "no memories found". When the
key is missing or invalid, suggest updating the plugin's API key setting,
exporting `MEM0_API_KEY`, or running `mem0 init` (the plugin reads the key the
Mem0 CLI saves in `~/.mem0/config.json`).
being created. Never report an auth failure as "no memories found". Suggest
reinstalling with `--config api_key=...` in that case.
@@ -2,7 +2,6 @@
HARNESS_ID = "coding-agent"
SOURCE_TAG = "CODING_AGENT_PLUGIN"
DATA_DIR_NAME = "coding-agent-plugin"
# Platform-side vocabulary (mem0_event.source + X-Application). The whole
# plugin family is one source; which editor it runs in is the application.
@@ -28,23 +28,12 @@ from typing import Any, Iterable
import telemetry
# Read from the generated per-host module so a new entrypoint is correct without
# remembering to configure anything.
try: # pragma: no cover - absent only in the un-built shared source tree
from _harness_id import DATA_DIR_NAME as _DATA_DIR_NAME
from _harness_id import PLATFORM_APPLICATION as _PLATFORM_APPLICATION
from _harness_id import PLATFORM_SOURCE as _PLATFORM_SOURCE
except ImportError:
_DATA_DIR_NAME = "mem0-plugin"
_PLATFORM_SOURCE = "MEM0_PLUGIN"
_PLATFORM_APPLICATION = ""
DEFAULT_API_URL = "https://api.mem0.ai"
PLUGIN_VERSION = "0.3.4"
PLUGIN_VERSION = "0.3.3"
_harness_name: str = "generic"
_harness_env_prefix: str = "MEM0_PLUGIN"
_harness_data_dir_name: str = _DATA_DIR_NAME
_harness_data_dir_name: str = "mem0-plugin"
_harness_source_tag: str = "mem0_plugin"
@@ -389,46 +378,30 @@ def resolve_repo(cwd: str | None) -> RepoContext:
return _resolve_repo_cached(os.path.abspath(cwd or os.getcwd()))
_PLUGIN_API_KEY_ENV = (
"PLUGIN_OPTION_API_KEY",
"CLAUDE_PLUGIN_OPTION_API_KEY",
"CLAUDE_PLUGIN_OPTION_MEM0_API_KEY",
)
def _configured(value: object) -> str:
"""The stripped value, or empty when the host left its ${placeholder} unexpanded."""
text = value.strip() if isinstance(value, str) else ""
return "" if text.startswith("${") and text.endswith("}") else text
def _first_env(*names: str) -> str:
return next((value for name in names if (value := _configured(os.environ.get(name)))), "")
def _mem0_cli_api_key() -> str:
"""The key `mem0 init` saved to the Mem0 CLI config."""
try:
config = json.loads((Path.home() / ".mem0" / "config.json").read_text(encoding="utf-8"))
return _configured(config["platform"]["api_key"])
except (OSError, ValueError, LookupError, TypeError):
return ""
def api_key() -> str:
configured = _first_env("MEM0_API_KEY", *_PLUGIN_API_KEY_ENV)
configured = (
os.environ.get("MEM0_API_KEY")
or os.environ.get("PLUGIN_OPTION_API_KEY")
or os.environ.get("CLAUDE_PLUGIN_OPTION_API_KEY")
or os.environ.get("CLAUDE_PLUGIN_OPTION_MEM0_API_KEY")
or ""
).strip()
if configured:
return configured
try:
cached = _configured((data_dir() / "api-key").read_text(encoding="utf-8"))
return (data_dir() / "api-key").read_text(encoding="utf-8").strip()
except OSError:
cached = ""
return cached or _mem0_cli_api_key()
return ""
def cache_plugin_api_key() -> bool:
"""Bridge host's hook-only sensitive config into plugin-owned storage."""
configured = _first_env(*_PLUGIN_API_KEY_ENV)
configured = (
os.environ.get("PLUGIN_OPTION_API_KEY")
or os.environ.get("CLAUDE_PLUGIN_OPTION_API_KEY")
or os.environ.get("CLAUDE_PLUGIN_OPTION_MEM0_API_KEY")
or ""
).strip()
if not configured:
return False
@@ -456,7 +429,14 @@ def cache_plugin_api_key() -> bool:
def clear_stale_api_key_cache() -> bool:
"""Drop the cached key file once every configured key source is gone."""
if _first_env("MEM0_API_KEY", *_PLUGIN_API_KEY_ENV):
configured = (
os.environ.get("MEM0_API_KEY")
or os.environ.get("PLUGIN_OPTION_API_KEY")
or os.environ.get("CLAUDE_PLUGIN_OPTION_API_KEY")
or os.environ.get("CLAUDE_PLUGIN_OPTION_MEM0_API_KEY")
or ""
).strip()
if configured:
return False
path = data_dir() / "api-key"
if not path.exists():
@@ -479,7 +459,12 @@ def detached_process_kwargs(platform: str | None = None) -> dict:
def _plugin_option(name: str, fallback: str = "") -> str:
return _first_env(f"PLUGIN_OPTION_{name.upper()}", f"CLAUDE_PLUGIN_OPTION_{name.upper()}", fallback)
return (
os.environ.get(f"PLUGIN_OPTION_{name.upper()}")
or os.environ.get(f"CLAUDE_PLUGIN_OPTION_{name.upper()}")
or os.environ.get(fallback)
or ""
).strip()
def user_id() -> str:
@@ -1813,6 +1798,16 @@ def extraction_message_batches(
return batches
# Platform surface attribution. Read from the generated per-host module so a new
# entrypoint is correct without remembering to configure anything.
try: # pragma: no cover - absent only in the un-built shared source tree
from _harness_id import PLATFORM_APPLICATION as _PLATFORM_APPLICATION
from _harness_id import PLATFORM_SOURCE as _PLATFORM_SOURCE
except ImportError:
_PLATFORM_SOURCE = "MEM0_PLUGIN"
_PLATFORM_APPLICATION = ""
def platform_headers(key: str) -> dict[str, str]:
"""Auth plus the three surface-identity headers.
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
"name": "mem0",
"version": "0.3.4",
"version": "0.3.3",
"description": "Cross-session memory and token savings for coding agents.",
"author": {
"name": "Mem0",
@@ -18,7 +18,5 @@ API key is configured, the event/flush/retrieval counts (`flushes` is the
number of completed flushes, not a pending count), and the doctor check
results. If doctor reports an authentication failure (401 / invalid key), say
clearly that the Mem0 API key is invalid or expired and that memories are NOT
being created. Never report an auth failure as "no memories found". When the
key is missing or invalid, suggest updating the plugin's API key setting,
exporting `MEM0_API_KEY`, or running `mem0 init` (the plugin reads the key the
Mem0 CLI saves in `~/.mem0/config.json`).
being created. Never report an auth failure as "no memories found". Suggest
reinstalling with `--config api_key=...` in that case.
+2 -2
View File
@@ -74,8 +74,8 @@
"brace-expansion@<1.1.16": ">=1.1.16 <2.0.0",
"brace-expansion@>=2.0.0 <2.1.2": ">=2.1.2 <3.0.0",
"brace-expansion@>=5.0.0 <5.0.8": ">=5.0.8",
"js-yaml@<3.15.1": ">=3.15.1 <4.0.0",
"js-yaml@>=4.0.0 <4.3.1": ">=4.3.1 <5.0.0"
"js-yaml@<3.15.2": ">=3.15.2 <4.0.0",
"js-yaml@>=4.0.0 <4.3.2": ">=4.3.2 <5.0.0"
}
}
}
+11 -11
View File
@@ -11,8 +11,8 @@ overrides:
brace-expansion@<1.1.16: '>=1.1.16 <2.0.0'
brace-expansion@>=2.0.0 <2.1.2: '>=2.1.2 <3.0.0'
brace-expansion@>=5.0.0 <5.0.8: '>=5.0.8'
js-yaml@<3.15.1: '>=3.15.1 <4.0.0'
js-yaml@>=4.0.0 <4.3.1: '>=4.3.1 <5.0.0'
js-yaml@<3.15.2: '>=3.15.2 <4.0.0'
js-yaml@>=4.0.0 <4.3.2: '>=4.3.2 <5.0.0'
importers:
@@ -1494,12 +1494,12 @@ packages:
js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
js-yaml@3.15.1:
resolution: {integrity: sha512-S99WuO3HlhO3XN41EtYUNl9zzXjoJx7QvmipxsJVxtCBT0YHEFy+iOJhjSvrmV12nYhWpZaM8lPHkJm0yUMbag==}
js-yaml@3.15.2:
resolution: {integrity: sha512-6EuL879VkRA+1Cz578mKMiKvjPNEuk6+r1JaFzoSWejZmtf7xWbIyw1e3KkxlkzTIt9Taw6JBhEppG7utc1P+w==}
hasBin: true
js-yaml@4.3.1:
resolution: {integrity: sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==}
js-yaml@4.3.2:
resolution: {integrity: sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==}
hasBin: true
jsesc@3.1.0:
@@ -2506,7 +2506,7 @@ snapshots:
globals: 13.24.0
ignore: 5.3.2
import-fresh: 3.3.1
js-yaml: 4.3.1
js-yaml: 4.3.2
minimatch: 3.1.5
strip-json-comments: 3.1.1
transitivePeerDependencies:
@@ -2546,7 +2546,7 @@ snapshots:
camelcase: 5.3.1
find-up: 4.1.0
get-package-type: 0.1.0
js-yaml: 3.15.1
js-yaml: 3.15.2
resolve-from: 5.0.0
'@istanbuljs/schema@0.1.6': {}
@@ -3353,7 +3353,7 @@ snapshots:
imurmurhash: 0.1.4
is-glob: 4.0.3
is-path-inside: 3.0.3
js-yaml: 4.3.1
js-yaml: 4.3.2
json-stable-stringify-without-jsonify: 1.0.1
levn: 0.4.1
lodash.merge: 4.6.2
@@ -4173,12 +4173,12 @@ snapshots:
js-tokens@4.0.0: {}
js-yaml@3.15.1:
js-yaml@3.15.2:
dependencies:
argparse: 1.0.10
esprima: 4.0.1
js-yaml@4.3.1:
js-yaml@4.3.2:
dependencies:
argparse: 2.0.1
+2 -2
View File
@@ -72,8 +72,8 @@
"@qdrant/js-client-rest": "^1.18.0",
"uuid@<11.1.1": ">=11.1.1",
"esbuild": ">=0.28.1",
"undici@<7.29.0": ">=7.29.0 <8.0.0",
"axios@<1.18.0": ">=1.18.0 <2.0.0",
"undici@<7.29.1": ">=7.29.1 <8.0.0",
"axios@<1.20.0": ">=1.20.0 <2.0.0",
"postcss@<8.5.18": ">=8.5.18 <9.0.0",
"mongoose@>=9.0.0 <9.7.2": ">=9.7.2 <10.0.0"
}
+10 -10
View File
@@ -13,8 +13,8 @@ overrides:
'@qdrant/js-client-rest': ^1.18.0
uuid@<11.1.1: '>=11.1.1'
esbuild: '>=0.28.1'
undici@<7.29.0: '>=7.29.0 <8.0.0'
axios@<1.18.0: '>=1.18.0 <2.0.0'
undici@<7.29.1: '>=7.29.1 <8.0.0'
axios@<1.20.0: '>=1.20.0 <2.0.0'
postcss@<8.5.18: '>=8.5.18 <9.0.0'
mongoose@>=9.0.0 <9.7.2: '>=9.7.2 <10.0.0'
@@ -857,8 +857,8 @@ packages:
asynckit@0.4.0:
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
axios@1.18.1:
resolution: {integrity: sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==}
axios@1.20.0:
resolution: {integrity: sha512-r8aOh8j9cGKpgQAqpzrUHnSIc6a59Y3Xf/cv8sy1DrHCkZHzQGEuoq1tARk6qSyDdtQGSDgpb9kFlruzPvrgwg==}
base-64@0.1.0:
resolution: {integrity: sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA==}
@@ -2036,8 +2036,8 @@ packages:
undici-types@6.21.0:
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
undici@7.29.0:
resolution: {integrity: sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==}
undici@7.30.0:
resolution: {integrity: sha512-dkrQXeHSaoamnItlYbmzG0wFYrM0ZwDxCIg0A7aKjTyyhh9svRzCNFEzV+Vm05/yehjCzjDZ31KXfGEjYSztDQ==}
engines: {node: '>=20.18.1'}
util-deprecate@1.0.2:
@@ -2547,7 +2547,7 @@ snapshots:
dependencies:
'@qdrant/openapi-typescript-fetch': 1.2.6
typescript: 5.9.3
undici: 7.29.0
undici: 7.30.0
'@qdrant/openapi-typescript-fetch@1.2.6': {}
@@ -2906,7 +2906,7 @@ snapshots:
asynckit@0.4.0: {}
axios@1.18.1:
axios@1.20.0:
dependencies:
follow-redirects: 1.16.0
form-data: 4.0.6
@@ -3529,7 +3529,7 @@ snapshots:
'@supabase/supabase-js': 2.108.1
'@types/jest': 29.5.14
'@types/pg': 8.11.0
axios: 1.18.1
axios: 1.20.0
better-sqlite3: 12.10.0
cloudflare: 4.5.0
compromise: 14.15.1
@@ -4121,7 +4121,7 @@ snapshots:
undici-types@6.21.0: {}
undici@7.29.0: {}
undici@7.30.0: {}
util-deprecate@1.0.2: {}
+2 -2
View File
@@ -21,7 +21,7 @@ overrides:
"@qdrant/js-client-rest": "^1.18.0"
"uuid@<11.1.1": ">=11.1.1"
"esbuild": ">=0.28.1"
"undici@<7.29.0": ">=7.29.0 <8.0.0"
"axios@<1.18.0": ">=1.18.0 <2.0.0"
"undici@<7.29.1": ">=7.29.1 <8.0.0"
"axios@<1.20.0": ">=1.20.0 <2.0.0"
"postcss@<8.5.18": ">=8.5.18 <9.0.0"
"mongoose@>=9.0.0 <9.7.2": ">=9.7.2 <10.0.0"
@@ -102,16 +102,6 @@ describe("resolveApiKey", () => {
expect(resolveApiKey({}, dir)).toBe("m0-later");
});
test("falls back to the key mem0 init saved", () => {
const dir = home();
mkdirSync(join(dir, ".mem0"));
writeFileSync(join(dir, ".mem0", "config.json"), JSON.stringify({platform: {api_key: "m0-cli-key"}}));
expect(resolveApiKey({}, dir)).toBe("m0-cli-key");
writeFileSync(join(dir, ".zshrc"), "export MEM0_API_KEY=m0-from-profile\n");
expect(resolveApiKey({}, dir)).toBe("m0-from-profile");
});
test("ignores unsupported files and invalid assignments", () => {
const dir = home();
writeFileSync(join(dir, ".env"), "MEM0_API_KEY=unsupported\n");
+1 -2
View File
@@ -1,7 +1,6 @@
import {readFileSync} from "fs";
import {homedir} from "os";
import {join} from "path";
import {mem0CliApiKey} from "../agent-plugin-core/typescript/src/credentials.ts";
const PROFILE_FILES = [".zshrc", ".bashrc", ".zprofile", ".bash_profile", ".profile"];
@@ -27,5 +26,5 @@ export function resolveApiKey(env: NodeJS.ProcessEnv = process.env, homeDir = ho
}
}
return mem0CliApiKey(homeDir);
return "";
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@mem0/opencode-plugin",
"version": "0.4.2",
"version": "0.4.1",
"type": "module",
"description": "Mem0 persistent memory plugin for OpenCode — add, search, and manage memories across sessions",
"main": "dist/index.js",
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@mem0/pi-agent-plugin",
"version": "0.3.3",
"version": "0.3.2",
"type": "module",
"description": "Mem0 memory extension for Pi Agent persistent, scoped, semantic memory across sessions and projects",
"license": "Apache-2.0",
@@ -72,9 +72,9 @@
"form-data@<4.0.6": ">=4.0.6",
"uuid@<11.1.1": ">=11.1.1",
"esbuild": ">=0.28.1",
"undici@<7.29.0": ">=7.29.0 <8.0.0",
"undici@>=8.0.0 <8.9.0": ">=8.9.0 <9.0.0",
"axios@<1.18.0": ">=1.18.0 <2.0.0",
"undici@<7.29.1": ">=7.29.1 <8.0.0",
"undici@>=8.0.0 <8.10.2": ">=8.10.2 <9.0.0",
"axios@<1.20.0": ">=1.20.0 <2.0.0",
"brace-expansion@>=3.0.0 <5.0.8": ">=5.0.8 <6.0.0",
"postcss@<8.5.18": ">=8.5.18 <9.0.0",
"mongoose@>=9.0.0 <9.7.2": ">=9.7.2 <10.0.0",
+15 -15
View File
@@ -8,9 +8,9 @@ overrides:
form-data@<4.0.6: '>=4.0.6'
uuid@<11.1.1: '>=11.1.1'
esbuild: '>=0.28.1'
undici@<7.29.0: '>=7.29.0 <8.0.0'
undici@>=8.0.0 <8.9.0: '>=8.9.0 <9.0.0'
axios@<1.18.0: '>=1.18.0 <2.0.0'
undici@<7.29.1: '>=7.29.1 <8.0.0'
undici@>=8.0.0 <8.10.2: '>=8.10.2 <9.0.0'
axios@<1.20.0: '>=1.20.0 <2.0.0'
brace-expansion@>=3.0.0 <5.0.8: '>=5.0.8 <6.0.0'
postcss@<8.5.18: '>=8.5.18 <9.0.0'
mongoose@>=9.0.0 <9.7.2: '>=9.7.2 <10.0.0'
@@ -1066,8 +1066,8 @@ packages:
asynckit@0.4.0:
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
axios@1.18.1:
resolution: {integrity: sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==}
axios@1.20.0:
resolution: {integrity: sha512-r8aOh8j9cGKpgQAqpzrUHnSIc6a59Y3Xf/cv8sy1DrHCkZHzQGEuoq1tARk6qSyDdtQGSDgpb9kFlruzPvrgwg==}
balanced-match@4.0.4:
resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==}
@@ -2354,12 +2354,12 @@ packages:
undici-types@7.24.6:
resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==}
undici@7.29.0:
resolution: {integrity: sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==}
undici@7.30.0:
resolution: {integrity: sha512-dkrQXeHSaoamnItlYbmzG0wFYrM0ZwDxCIg0A7aKjTyyhh9svRzCNFEzV+Vm05/yehjCzjDZ31KXfGEjYSztDQ==}
engines: {node: '>=20.18.1'}
undici@8.10.0:
resolution: {integrity: sha512-HvltHd7avK13QIw/oLe4qoOLyoVSoafqJ2jYOrtMRBkbYT31eiBQ8O0ehRKZiEZCMEyLFQNIADpgCWC5fALvYQ==}
undici@8.11.2:
resolution: {integrity: sha512-u4UB2/IrKdU6lFxumHmmo1a3fCQO5tzQllRorfoRS63txhrB7xTpSn1PftwC4qEHkOaqP95fCWW4lJzwErwzhQ==}
engines: {node: '>=22.19.0'}
util-deprecate@1.0.2:
@@ -2942,7 +2942,7 @@ snapshots:
minimatch: 10.2.5
proper-lockfile: 4.1.2
typebox: 1.1.38
undici: 8.10.0
undici: 8.11.2
yaml: 2.9.0
optionalDependencies:
'@mariozechner/clipboard': 0.3.9
@@ -3203,7 +3203,7 @@ snapshots:
dependencies:
'@qdrant/openapi-typescript-fetch': 1.2.6
typescript: 6.0.3
undici: 7.29.0
undici: 7.30.0
'@qdrant/openapi-typescript-fetch@1.2.6': {}
@@ -3598,7 +3598,7 @@ snapshots:
asynckit@0.4.0: {}
axios@1.18.1:
axios@1.20.0:
dependencies:
follow-redirects: 1.16.0
form-data: 4.0.6
@@ -4253,7 +4253,7 @@ snapshots:
'@supabase/supabase-js': 2.108.1
'@types/jest': 29.5.14
'@types/pg': 8.11.0
axios: 1.18.1
axios: 1.20.0
better-sqlite3: 12.10.0
cloudflare: 4.5.0
compromise: 14.15.1
@@ -4895,9 +4895,9 @@ snapshots:
undici-types@7.24.6: {}
undici@7.29.0: {}
undici@7.30.0: {}
undici@8.10.0: {}
undici@8.11.2: {}
util-deprecate@1.0.2: {}
@@ -5,9 +5,9 @@ overrides:
"form-data@<4.0.6": ">=4.0.6"
"uuid@<11.1.1": ">=11.1.1"
"esbuild": ">=0.28.1"
"undici@<7.29.0": ">=7.29.0 <8.0.0"
"undici@>=8.0.0 <8.9.0": ">=8.9.0 <9.0.0"
"axios@<1.18.0": ">=1.18.0 <2.0.0"
"undici@<7.29.1": ">=7.29.1 <8.0.0"
"undici@>=8.0.0 <8.10.2": ">=8.10.2 <9.0.0"
"axios@<1.20.0": ">=1.20.0 <2.0.0"
"brace-expansion@>=3.0.0 <5.0.8": ">=5.0.8 <6.0.0"
"postcss@<8.5.18": ">=8.5.18 <9.0.0"
"mongoose@>=9.0.0 <9.7.2": ">=9.7.2 <10.0.0"
@@ -1,7 +1,6 @@
import * as fs from "node:fs";
import * as os from "node:os";
import * as path from "node:path";
import { mem0CliApiKey } from "../../../agent-plugin-core/typescript/src/credentials.ts";
import type { Mem0Config } from "../types.ts";
const AGENT_ROOT = path.join(os.homedir(), ".pi", "agent");
@@ -37,9 +36,6 @@ export function loadConfig(): Mem0Config {
if (process.env.MEM0_API_KEY) {
config.apiKey = process.env.MEM0_API_KEY;
}
if (!config.apiKey) {
config.apiKey = mem0CliApiKey(os.homedir());
}
if (process.env.MEM0_USER_ID) {
config.userId = process.env.MEM0_USER_ID;
}
@@ -1,6 +1,5 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import * as fs from "node:fs";
import * as path from "node:path";
import { loadConfig } from "../src/config/index.ts";
vi.mock("node:fs");
@@ -34,17 +33,6 @@ describe("loadConfig", () => {
expect(config.apiKey).toBe("");
});
it("falls back to the key mem0 init saved", () => {
delete process.env.MEM0_API_KEY;
vi.mocked(fs.readFileSync).mockImplementation((file) => {
if (String(file).endsWith(path.join(".mem0", "config.json"))) {
return JSON.stringify({ platform: { api_key: "m0-cli-key" } });
}
throw new Error("ENOENT");
});
expect(loadConfig().apiKey).toBe("m0-cli-key");
});
it("reads config file and merges with defaults", () => {
delete process.env.MEM0_API_KEY;
delete process.env.MEM0_USER_ID;
+2 -2
View File
@@ -88,8 +88,8 @@
"esbuild": ">=0.28.1",
"brace-expansion@<1.1.16": ">=1.1.16 <2.0.0",
"brace-expansion@>=3.0.0 <5.0.8": ">=5.0.8 <6.0.0",
"js-yaml@<3.15.1": ">=3.15.1 <4.0.0",
"js-yaml@>=4.0.0 <4.3.1": ">=4.3.1 <5.0.0",
"js-yaml@<3.15.2": ">=3.15.2 <4.0.0",
"js-yaml@>=4.0.0 <4.3.2": ">=4.3.2 <5.0.0",
"browserslist@<4.28.7": ">=4.28.7 <5.0.0"
}
}
+6 -6
View File
@@ -14,8 +14,8 @@ overrides:
esbuild: '>=0.28.1'
brace-expansion@<1.1.16: '>=1.1.16 <2.0.0'
brace-expansion@>=3.0.0 <5.0.8: '>=5.0.8 <6.0.0'
js-yaml@<3.15.1: '>=3.15.1 <4.0.0'
js-yaml@>=4.0.0 <4.3.1: '>=4.3.1 <5.0.0'
js-yaml@<3.15.2: '>=3.15.2 <4.0.0'
js-yaml@>=4.0.0 <4.3.2: '>=4.3.2 <5.0.0'
browserslist@<4.28.7: '>=4.28.7 <5.0.0'
importers:
@@ -1356,8 +1356,8 @@ packages:
js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
js-yaml@3.15.1:
resolution: {integrity: sha512-S99WuO3HlhO3XN41EtYUNl9zzXjoJx7QvmipxsJVxtCBT0YHEFy+iOJhjSvrmV12nYhWpZaM8lPHkJm0yUMbag==}
js-yaml@3.15.2:
resolution: {integrity: sha512-6EuL879VkRA+1Cz578mKMiKvjPNEuk6+r1JaFzoSWejZmtf7xWbIyw1e3KkxlkzTIt9Taw6JBhEppG7utc1P+w==}
hasBin: true
jsesc@3.1.0:
@@ -2231,7 +2231,7 @@ snapshots:
camelcase: 5.3.1
find-up: 4.1.0
get-package-type: 0.1.0
js-yaml: 3.15.1
js-yaml: 3.15.2
resolve-from: 5.0.0
'@istanbuljs/schema@0.1.6': {}
@@ -3360,7 +3360,7 @@ snapshots:
js-tokens@4.0.0: {}
js-yaml@3.15.1:
js-yaml@3.15.2:
dependencies:
argparse: 1.0.10
esprima: 4.0.1
@@ -15,6 +15,6 @@ overrides:
"esbuild": ">=0.28.1"
"brace-expansion@<1.1.16": ">=1.1.16 <2.0.0"
"brace-expansion@>=3.0.0 <5.0.8": ">=5.0.8 <6.0.0"
"js-yaml@<3.15.1": ">=3.15.1 <4.0.0"
"js-yaml@>=4.0.0 <4.3.1": ">=4.3.1 <5.0.0"
"js-yaml@<3.15.2": ">=3.15.2 <4.0.0"
"js-yaml@>=4.0.0 <4.3.2": ">=4.3.2 <5.0.0"
"browserslist@<4.28.7": ">=4.28.7 <5.0.0"
+1 -1
View File
@@ -72,7 +72,7 @@
"undici@<7.29.0": ">=7.29.0 <8.0.0",
"undici@>=8.0.0 <8.9.0": ">=8.9.0 <9.0.0",
"brace-expansion@<1.1.16": ">=1.1.16 <2.0.0",
"js-yaml@<3.15.1": ">=3.15.1 <4.0.0"
"js-yaml@<3.15.2": ">=3.15.2 <4.0.0"
}
}
}
+5 -5
View File
@@ -11,7 +11,7 @@ overrides:
undici@<7.29.0: '>=7.29.0 <8.0.0'
undici@>=8.0.0 <8.9.0: '>=8.9.0 <9.0.0'
brace-expansion@<1.1.16: '>=1.1.16 <2.0.0'
js-yaml@<3.15.1: '>=3.15.1 <4.0.0'
js-yaml@<3.15.2: '>=3.15.2 <4.0.0'
importers:
@@ -951,8 +951,8 @@ packages:
js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
js-yaml@3.15.1:
resolution: {integrity: sha512-S99WuO3HlhO3XN41EtYUNl9zzXjoJx7QvmipxsJVxtCBT0YHEFy+iOJhjSvrmV12nYhWpZaM8lPHkJm0yUMbag==}
js-yaml@3.15.2:
resolution: {integrity: sha512-6EuL879VkRA+1Cz578mKMiKvjPNEuk6+r1JaFzoSWejZmtf7xWbIyw1e3KkxlkzTIt9Taw6JBhEppG7utc1P+w==}
hasBin: true
jsesc@3.1.0:
@@ -1654,7 +1654,7 @@ snapshots:
camelcase: 5.3.1
find-up: 4.1.0
get-package-type: 0.1.0
js-yaml: 3.15.1
js-yaml: 3.15.2
resolve-from: 5.0.0
'@istanbuljs/schema@0.1.6': {}
@@ -2714,7 +2714,7 @@ snapshots:
js-tokens@4.0.0: {}
js-yaml@3.15.1:
js-yaml@3.15.2:
dependencies:
argparse: 1.0.10
esprima: 4.0.1
+1 -1
View File
@@ -8,4 +8,4 @@ overrides:
"undici@<7.29.0": ">=7.29.0 <8.0.0"
"undici@>=8.0.0 <8.9.0": ">=8.9.0 <9.0.0"
"brace-expansion@<1.1.16": ">=1.1.16 <2.0.0"
"js-yaml@<3.15.1": ">=3.15.1 <4.0.0"
"js-yaml@<3.15.2": ">=3.15.2 <4.0.0"
+1 -1
View File
@@ -13,7 +13,7 @@
},
"category": "Productivity",
"description": "Cross-session memory and token savings for coding agents.",
"version": "0.3.4"
"version": "0.3.3"
}
]
}
+26 -15
View File
@@ -1,6 +1,6 @@
{
"name": "mem0ai",
"version": "3.3.0",
"version": "3.3.1",
"description": "The Memory Layer For Your AI Apps",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
@@ -100,10 +100,11 @@
"tsup": "^8.3.0",
"typescript": "5.5.4",
"iovalkey": "^0.3.3",
"@mochow/mochow-sdk-node": "^2.1.5"
"@mochow/mochow-sdk-node": "^2.1.5",
"@types/jest": "^29.5.14"
},
"dependencies": {
"axios": "^1.18.0",
"axios": "^1.20.0",
"openai": "^4.93.0",
"uuid": "^11.1.1",
"zod": "^3.24.1"
@@ -127,8 +128,7 @@
"@qdrant/js-client-rest": "^1.18.0",
"@supabase/supabase-js": "^2.49.1",
"@turbopuffer/turbopuffer": "^2.0.0",
"@types/jest": "29.5.14",
"@types/pg": "8.11.0",
"@types/pg": "^8.11.0",
"@upstash/vector": "^1.2.3",
"better-sqlite3": "^12.6.2",
"cassandra-driver": "4.8.0",
@@ -141,7 +141,7 @@
"weaviate-client": "^3.0.0",
"ollama": "^0.5.14",
"oracledb": "^6.5.0 || ^7.0.0",
"pg": "8.11.3",
"pg": "^8.11.3",
"redis": "^4.6.13",
"@elastic/elasticsearch": "^9.0.0",
"iovalkey": "^0.3.3",
@@ -257,6 +257,15 @@
},
"oracledb": {
"optional": true
},
"pg": {
"optional": true
},
"@types/pg": {
"optional": true
},
"natural": {
"optional": true
}
},
"engines": {
@@ -276,10 +285,10 @@
"picomatch@<2.3.2": "^2.3.2",
"picomatch@>=4.0.0 <4.0.4": "^4.0.4",
"jws@4.0.0": "4.0.1",
"js-yaml@<3.15.1": ">=3.15.1 <4.0.0",
"js-yaml@>=4.0.0 <4.3.1": ">=4.3.1 <5.0.0",
"sharp@<0.35.0": ">=0.35.0 <0.36.0",
"adm-zip@<0.6.0": ">=0.6.0 <0.7.0",
"js-yaml@<3.15.2": ">=3.15.2 <4.0.0",
"js-yaml@>=4.0.0 <4.3.2": ">=4.3.2 <5.0.0",
"sharp@<0.35.4": ">=0.35.4 <0.36.0",
"adm-zip@<0.6.1": ">=0.6.1 <0.7.0",
"langsmith@<0.6.0": "^0.6.0",
"minimatch@<3.1.3": "^3.1.3",
"minimatch@>=5.0.0 <5.1.8": "^5.1.8",
@@ -294,21 +303,23 @@
"glob@>=10.2.0 <10.5.0": "^10.5.0",
"@modelcontextprotocol/sdk": "^1.25.4",
"esbuild": ">=0.28.1",
"undici@<7.29.0": ">=7.29.0 <8.0.0",
"undici@<7.29.1": ">=7.29.1 <8.0.0",
"@aws-sdk/client-bedrock-runtime": "3.967.0",
"@aws-sdk/client-neptune-graph": "3.966.0",
"axios@<1.18.0": ">=1.18.0 <2.0.0",
"axios@<1.20.0": ">=1.20.0 <2.0.0",
"brace-expansion@<1.1.16": ">=1.1.16 <2.0.0",
"brace-expansion@>=2.0.0 <2.1.4": ">=2.1.4 <3.0.0",
"brace-expansion@>=2.0.0 <2.1.6": ">=2.1.6 <3.0.0",
"brace-expansion@>=3.0.0 <5.0.8": ">=5.0.8 <6.0.0",
"fast-xml-parser@>=5.0.0 <5.10.1": ">=5.10.1 <6.0.0",
"tar@<7.5.21": ">=7.5.21 <8.0.0",
"thrift@<0.23.0": "^0.23.0",
"mongoose@>=9.0.0 <9.7.2": ">=9.7.2 <10.0.0",
"protobufjs@<7.6.5": ">=7.6.5 <8.0.0",
"ip-address@<10.3.1": ">=10.3.1 <11.0.0",
"ip-address@<10.5.1": ">=10.5.1 <11.0.0",
"browserslist@<4.28.7": ">=4.28.7 <5.0.0",
"mysql2@<3.23.1": ">=3.23.1 <4.0.0"
"mysql2@<3.23.1": ">=3.23.1 <4.0.0",
"basic-ftp@<6.2.1": ">=6.2.1 <7.0.0",
"@grpc/grpc-js@>=1.14.0 <1.14.5": ">=1.14.5 <2.0.0"
}
}
}
+179 -213
View File
@@ -9,10 +9,10 @@ overrides:
picomatch@<2.3.2: ^2.3.2
picomatch@>=4.0.0 <4.0.4: ^4.0.4
jws@4.0.0: 4.0.1
js-yaml@<3.15.1: '>=3.15.1 <4.0.0'
js-yaml@>=4.0.0 <4.3.1: '>=4.3.1 <5.0.0'
sharp@<0.35.0: '>=0.35.0 <0.36.0'
adm-zip@<0.6.0: '>=0.6.0 <0.7.0'
js-yaml@<3.15.2: '>=3.15.2 <4.0.0'
js-yaml@>=4.0.0 <4.3.2: '>=4.3.2 <5.0.0'
sharp@<0.35.4: '>=0.35.4 <0.36.0'
adm-zip@<0.6.1: '>=0.6.1 <0.7.0'
langsmith@<0.6.0: ^0.6.0
minimatch@<3.1.3: ^3.1.3
minimatch@>=5.0.0 <5.1.8: ^5.1.8
@@ -27,21 +27,23 @@ overrides:
glob@>=10.2.0 <10.5.0: ^10.5.0
'@modelcontextprotocol/sdk': ^1.25.4
esbuild: '>=0.28.1'
undici@<7.29.0: '>=7.29.0 <8.0.0'
undici@<7.29.1: '>=7.29.1 <8.0.0'
'@aws-sdk/client-bedrock-runtime': 3.967.0
'@aws-sdk/client-neptune-graph': 3.966.0
axios@<1.18.0: '>=1.18.0 <2.0.0'
axios@<1.20.0: '>=1.20.0 <2.0.0'
brace-expansion@<1.1.16: '>=1.1.16 <2.0.0'
brace-expansion@>=2.0.0 <2.1.4: '>=2.1.4 <3.0.0'
brace-expansion@>=2.0.0 <2.1.6: '>=2.1.6 <3.0.0'
brace-expansion@>=3.0.0 <5.0.8: '>=5.0.8 <6.0.0'
fast-xml-parser@>=5.0.0 <5.10.1: '>=5.10.1 <6.0.0'
tar@<7.5.21: '>=7.5.21 <8.0.0'
thrift@<0.23.0: ^0.23.0
mongoose@>=9.0.0 <9.7.2: '>=9.7.2 <10.0.0'
protobufjs@<7.6.5: '>=7.6.5 <8.0.0'
ip-address@<10.3.1: '>=10.3.1 <11.0.0'
ip-address@<10.5.1: '>=10.5.1 <11.0.0'
browserslist@<4.28.7: '>=4.28.7 <5.0.0'
mysql2@<3.23.1: '>=3.23.1 <4.0.0'
basic-ftp@<6.2.1: '>=6.2.1 <7.0.0'
'@grpc/grpc-js@>=1.14.0 <1.14.5': '>=1.14.5 <2.0.0'
patchedDependencies:
tar@7.5.22:
@@ -109,11 +111,8 @@ importers:
'@turbopuffer/turbopuffer':
specifier: ^2.0.0
version: 2.5.0
'@types/jest':
specifier: 29.5.14
version: 29.5.14
'@types/pg':
specifier: 8.11.0
specifier: ^8.11.0
version: 8.11.0
'@upstash/vector':
specifier: ^1.2.3
@@ -122,8 +121,8 @@ importers:
specifier: ^2.4.0 || ^3.0.0
version: 3.0.3
axios:
specifier: ^1.18.0
version: 1.18.1
specifier: ^1.20.0
version: 1.20.0
better-sqlite3:
specifier: ^12.6.2
version: 12.10.0
@@ -167,8 +166,8 @@ importers:
specifier: ^6.5.0 || ^7.0.0
version: 7.0.1
pg:
specifier: 8.11.3
version: 8.11.3
specifier: ^8.11.3
version: 8.21.0
redis:
specifier: ^4.6.13
version: 4.7.1
@@ -191,6 +190,9 @@ importers:
'@types/better-sqlite3':
specifier: ^7.6.13
version: 7.6.13
'@types/jest':
specifier: ^29.5.14
version: 29.5.14
'@types/node':
specifier: ^22.7.6
version: 22.19.21
@@ -861,8 +863,8 @@ packages:
resolution: {integrity: sha512-L38Ax21uF2OPUmCRWycZ/dZdMYf7gMrtClcxvVrqJVFmn8ET2M++GYmFGJpLqOHS1beATxOXLWe7y2ijSQz/ng==}
engines: {node: '>=20'}
'@emnapi/runtime@1.11.2':
resolution: {integrity: sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==}
'@emnapi/runtime@1.11.3':
resolution: {integrity: sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==}
'@esbuild/aix-ppc64@0.28.1':
resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==}
@@ -1038,8 +1040,8 @@ packages:
peerDependencies:
graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0
'@grpc/grpc-js@1.14.4':
resolution: {integrity: sha512-k9Dj3DV/itK9D06Y8f190Qgop7/Ui+D0njFV3LHMPwPT75DpXLQohE9Wmz0QElrJnzsjB7KPWiKJbOl7IPDArQ==}
'@grpc/grpc-js@1.14.5':
resolution: {integrity: sha512-7VZM+SVdEcUUqSQeNI3zM8Qs/BhQKZndPo2h5VkYkAM8Iz0wJIa8mKV5ekQGqG8UUsnkQ0NMxIxwkIHYvj0qOw==}
engines: {node: '>=12.10.0'}
'@grpc/proto-loader@0.8.1':
@@ -1075,144 +1077,144 @@ packages:
resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==}
engines: {node: '>=18'}
'@img/sharp-darwin-arm64@0.35.3':
resolution: {integrity: sha512-RMnFX7YQsMoh7lWfcM4NEHHymBX/rLuKNPVM84XE9ONPcaSCDgE7CHIHpSgPcO2xcRthgBy1HfNO319mwhIAkg==}
'@img/sharp-darwin-arm64@0.35.5':
resolution: {integrity: sha512-QRUlFQ0WxvdWyqqG/WtI3iupfD5rBzmCHXSdPsY91sAtVtTo7Q4cb6zOccZ3gqEqkr0f1As1ehLqmEpDsRf+lg==}
engines: {node: '>=20.9.0'}
cpu: [arm64]
os: [darwin]
'@img/sharp-darwin-x64@0.35.3':
resolution: {integrity: sha512-Xo+5uFBtLN0BKqieTxiFzFPQAUlBbbH5iBKyRX/z1JrbnYsHTfKJnUfL8+p2TPXr1pXqao4eeL4Rl144uDpK9w==}
'@img/sharp-darwin-x64@0.35.5':
resolution: {integrity: sha512-+BR255RhDlpygUpOc/Jdt1nT6DQ3XG/ERo5wbcdOf5Q320dKtPCKPLR1LJs9VGXRaMa8l1uUa0tkCNOXiAxZUw==}
engines: {node: '>=20.9.0'}
cpu: [x64]
os: [darwin]
'@img/sharp-freebsd-wasm32@0.35.3':
resolution: {integrity: sha512-lUxcqWIj2wMQ9BrwNjngcr1gWUr5xgaGThBRqPPalIC2n67Cqj1uPh8NnA/ZhAg8hUbKl+kVHKwgUIwe6ZYPrg==}
'@img/sharp-freebsd-wasm32@0.35.5':
resolution: {integrity: sha512-Y/z91nEZ4uIBX5X3nfTovjU9lHNKFYbL2lpHCLVNmXQK03VIZvXBBt0KxbPGp2SdGSF+2mQU4e+hQaWOt86iAw==}
engines: {node: '>=20.9.0'}
os: [freebsd]
'@img/sharp-libvips-darwin-arm64@1.3.2':
resolution: {integrity: sha512-9J6ypZFpQBj4YnePGoq/S38w6nz+vqg5WZLrLGY4YuSemdMq47GMLBPO42MzwdGwpg/agZ7xzZcFHa48xlywfg==}
'@img/sharp-libvips-darwin-arm64@1.3.4':
resolution: {integrity: sha512-5R89nBYiRdUlSWJxPhO+GVtaXzXSxKnRu/xqMn3KTA3L9EB9Oy/P+Nn2f2vlhPuUdy/Zusb2DarbyTpGCfEDuw==}
cpu: [arm64]
os: [darwin]
'@img/sharp-libvips-darwin-x64@1.3.2':
resolution: {integrity: sha512-m2pW1n6cns9VaubNwsZ+c3CRYjxNQWgJ5gPlnL1nbBcpkBvFm6SCFN5o0psFHI8w9n11NKhFkeEDns98tiqbEw==}
'@img/sharp-libvips-darwin-x64@1.3.4':
resolution: {integrity: sha512-iR2OKH80yi0U+dUplyh3/xdpFvps6YkCwsXenIJxqxR1v9o+xtKTGbS9H7cps+2Vxjc8B1j96p75NmTGjIhtpQ==}
cpu: [x64]
os: [darwin]
'@img/sharp-libvips-linux-arm64@1.3.2':
resolution: {integrity: sha512-dqVSFynCox4C/J8kT16V7SIFAns0IjgLwkvYT7p8LQVmJ5OS5b6tI9IGflxTeuBS//zXeFIUbwt5dwxyZ17cnA==}
'@img/sharp-libvips-linux-arm64@1.3.4':
resolution: {integrity: sha512-Y3dgX/6lE2QhQb+Gxy0WZxfg9MEm/JBjamZpS2IklP7xIQoKN4hzAm7KcMVGtaVDt3neE9OKBC7vAfonA/Lr1A==}
cpu: [arm64]
os: [linux]
'@img/sharp-libvips-linux-arm@1.3.2':
resolution: {integrity: sha512-1eMLzy92I4J6rmi4mAT8yC3HxOtniyGELlzGbNMLLeqe052ahFQ0h6LFq+lh5DsDIdYViIDst08abvSbcEdLXQ==}
'@img/sharp-libvips-linux-arm@1.3.4':
resolution: {integrity: sha512-LmRtTsOHuvM2+wlO2Db37dx5MiZhB0FvSunciw48YjdOkZz9KAiRbm8ujeMOA1INqmei5NapFxYEK1D1ZSidmw==}
cpu: [arm]
os: [linux]
'@img/sharp-libvips-linux-ppc64@1.3.2':
resolution: {integrity: sha512-3z0NHDxD6n5I9gc05U1eW1AyRm+Gznzq3naMrthPNqE6oYykcogW0l/jfpJdjYnuNl8R7yI9pNbE1XiUeyq0Aw==}
'@img/sharp-libvips-linux-ppc64@1.3.4':
resolution: {integrity: sha512-Le6boB8Tai0Nis+gIxIpKx68UDVVIqdR8Tin5Yf1z2LJJQLDJvCDRqRu+jC2qCoD+eIomonmOwB4smBRxfVpYQ==}
cpu: [ppc64]
os: [linux]
'@img/sharp-libvips-linux-riscv64@1.3.2':
resolution: {integrity: sha512-bsb4rI+NldGOsXuej2r8OdSS8+zXDVaCWxyWrcv6kneTOlgAHtZABRzBBCwdsPiD90J4myNJuHpg6kA20ImW/w==}
'@img/sharp-libvips-linux-riscv64@1.3.4':
resolution: {integrity: sha512-aHkkIEHPRdQEegJN20MLmGtxYD9R2wQr3Cwpddnu5+YKMt6Uzax7S9h5gpZTo8wyrGuZSlfQ63OevL5mTyOC7Q==}
cpu: [riscv64]
os: [linux]
'@img/sharp-libvips-linux-s390x@1.3.2':
resolution: {integrity: sha512-/ABshyj8gCpyIrNXnHn4LorDJ0HHm1VhXPBlxZ8zAtfVPAaSafXPGn+sUSIRiwaSBy0mmFjSjiXI5mkcwdChKQ==}
'@img/sharp-libvips-linux-s390x@1.3.4':
resolution: {integrity: sha512-ra/mB6MikESDUO7Yg+Mi95bFBb9GsObURuhnOv3OqknjGe9sZrG8tCe9q0xSIGrtLgvgw0gKnFWcK4blSgQOuQ==}
cpu: [s390x]
os: [linux]
'@img/sharp-libvips-linux-x64@1.3.2':
resolution: {integrity: sha512-ITPEtgffGJ0S6G9dRyw/366tJQqFRcHWPHhC+Stpg3Z8AEMrDrTr2lhdz4f/Y/HMbRh//7Z5mBzEpVdi62Oc3w==}
'@img/sharp-libvips-linux-x64@1.3.4':
resolution: {integrity: sha512-GJ//SSXbnwSDes02umB3nDJLFcQzw8a18V8fyhqr6tV515tOEMdImjjxj1AoafMRz56F3PHgftnj1QEKSU1zkw==}
cpu: [x64]
os: [linux]
'@img/sharp-libvips-linuxmusl-arm64@1.3.2':
resolution: {integrity: sha512-zE9EdiUzUmg5mDT5a1rk5fYJ6GWPloTwWBYDS14naqHsL+EaMpDj1AWnpLgh3u0YCORv2Tt50wrcrpYqkP97Kw==}
'@img/sharp-libvips-linuxmusl-arm64@1.3.4':
resolution: {integrity: sha512-hvulFwtjUcagsis6BBxHwGFwWoNZjgYmULGVrZcyfNbjA8hKILbRxGg15/7w5HDyXHXUos/j6baAWqnCyQ2DWA==}
cpu: [arm64]
os: [linux]
'@img/sharp-libvips-linuxmusl-x64@1.3.2':
resolution: {integrity: sha512-m0lrLiUt+lBYnCFr8qV/65yMR4E/c7/wf78I5eKTdkEakFAlZ9QlzEM3QIhhAwVeUhLAHLcCq7a7Vszq/oFNZQ==}
'@img/sharp-libvips-linuxmusl-x64@1.3.4':
resolution: {integrity: sha512-6zXKeE/p39I1AmA3cJG35eyBGNqNddLnUXjhwBnsGjFPWqf5VKkDBEqaEkPDoTEtkxwi2vv8Tcr2mDyP4So7Fg==}
cpu: [x64]
os: [linux]
'@img/sharp-linux-arm64@0.35.3':
resolution: {integrity: sha512-QgKDspHPnrU+GQ55XPhGwyhC8acLVOOSyAvo1oVfFmrIXLkDNmGWzAfDZ4xK8oSA1qBQrALcHX0G5UZni/SuFQ==}
'@img/sharp-linux-arm64@0.35.5':
resolution: {integrity: sha512-LYVx5JTsOM2CBzmxreh+nl64/3H6Xb09iSLknqH47z2T2DFFxDeFLP5y4dJwe6H7uGQlHPyEEtIqyo3DYsRwdQ==}
engines: {node: '>=20.9.0'}
cpu: [arm64]
os: [linux]
'@img/sharp-linux-arm@0.35.3':
resolution: {integrity: sha512-affVWCTLooy8TSxbDx2qkzuDeaWLNVBA+P//FNBirHsXpP2fuBhk5AuboYUnrDnzoXes8GFjpTx0SBFOCRg+FA==}
'@img/sharp-linux-arm@0.35.5':
resolution: {integrity: sha512-LEaXK2WdXVK5ykcw0buWyPMsmLLL2vpHLD6yrNSW+JGEL3BZPA4tpKN6iaMc4AxTTAoaX/sU1rOL51lcIz48ZQ==}
engines: {node: '>=20.9.0'}
cpu: [arm]
os: [linux]
'@img/sharp-linux-ppc64@0.35.3':
resolution: {integrity: sha512-sMd8rDxmpLOwv/7N44klFjOD5DUO7FLdjiXDI0hoxYaf7Ar262dQIEkosE98bps+5HPLtp/EvNqeqQtOycP/IA==}
'@img/sharp-linux-ppc64@0.35.5':
resolution: {integrity: sha512-QVxAAq8evVRI9ia2vqgwrmWucn5Dfv+JdWzj75pD8omHLPSP7f8p20O8jxzjCcuCEQEOtYOZUmX1hkiZ0kdevA==}
engines: {node: '>=20.9.0'}
cpu: [ppc64]
os: [linux]
'@img/sharp-linux-riscv64@0.35.3':
resolution: {integrity: sha512-0Eob78yjlYPfL5vMNWAW55l3R9Y6BQS/gOfe0ZcP9mEz9ohhKSt4im1hayiknXgf8AWrFqMvJcKIdmLmEe7yeQ==}
'@img/sharp-linux-riscv64@0.35.5':
resolution: {integrity: sha512-LtdreXguaavKODPIfzJ4kffx7UNt1omwtK0rch4EBbbSTXPnxWmYSayXdLJw0fJzQ97kHt1gL/yh4tvU+nCyRQ==}
engines: {node: '>=20.9.0'}
cpu: [riscv64]
os: [linux]
'@img/sharp-linux-s390x@0.35.3':
resolution: {integrity: sha512-KgAxQ0DxpNOq1rG2t5cgTgShJFGSuU7XO45cqC+1NVOuZnP6tlgZRuSYOfNupGkHID0o3cJOsw4DVeJpMovcGw==}
'@img/sharp-linux-s390x@0.35.5':
resolution: {integrity: sha512-UZasTOFiYzotTsGOCu42BfUzP6Tu6Do/947iRm1RsLKvlllxwGcn4RN27LibGWceix4Y+Pmw3jsnTcCQIgWjqA==}
engines: {node: '>=20.9.0'}
cpu: [s390x]
os: [linux]
'@img/sharp-linux-x64@0.35.3':
resolution: {integrity: sha512-8pqvxubL2PGdhlPy6GLqzDYMUjyRmKAwKHYKixpdJYBUK7PJ0C029XdsnpFIdgRZG68fZiGdHVWcKPvtiPB4cA==}
'@img/sharp-linux-x64@0.35.5':
resolution: {integrity: sha512-SxFtLTeJInhAA9Q836kux2vZNeOBQEx658qvbboZScr0wIARym3IcGmW7KpVD5sbVg0Ojy+udFQdayYIZyoNog==}
engines: {node: '>=20.9.0'}
cpu: [x64]
os: [linux]
'@img/sharp-linuxmusl-arm64@0.35.3':
resolution: {integrity: sha512-Vz0iQjzzcSX3HCbfwFfCSG/9SCIqyO0mH2sXyiHaAYfBk0cRsCWXRyQYX0ovCK/PAQBbTzQ0dsPQHh5MAFL59w==}
'@img/sharp-linuxmusl-arm64@0.35.5':
resolution: {integrity: sha512-9HbMclmI1zlNkFRs3z9/eBtDjfD0sGlrX1z6b1qwmiFY5ElDLh4BC0LPBdVp7z1DXFiKlIcznf+ZlsuZzLxQqg==}
engines: {node: '>=20.9.0'}
cpu: [arm64]
os: [linux]
'@img/sharp-linuxmusl-x64@0.35.3':
resolution: {integrity: sha512-6O1NPKcDVj9QEdg7Hx549EX8U0rp6yXQERqru6yRN7fGBn32UvIRJUlWnk+8xDCiG76hXVBbX82NZ/ZKr0euIg==}
'@img/sharp-linuxmusl-x64@0.35.5':
resolution: {integrity: sha512-4KOphqB035HrVdqLZfCgMzzERrQkkzOwRhl4OAkRO1YCldbaFjySXMaK534Mo0V+LndnlJk+sbUyLeU0ULyD1A==}
engines: {node: '>=20.9.0'}
cpu: [x64]
os: [linux]
'@img/sharp-wasm32@0.35.3':
resolution: {integrity: sha512-cZ0XkcYGpHZkqW6iCkqTcmUC0CD9DhD5d/qeZlZkfRBn6GnHniZXLUo5+9xw8Iv76YE6LQFN9YNBlKREcCG76w==}
'@img/sharp-wasm32@0.35.5':
resolution: {integrity: sha512-Ptsga1su4tQx+LLF1ECS9U6nz5kmrXKo6XVbtR48Ke3ZRxxgaWBu7IDtEe1quo8hiupwm6WFqxVlXaSf7IINGQ==}
engines: {node: '>=20.9.0'}
'@img/sharp-webcontainers-wasm32@0.35.3':
resolution: {integrity: sha512-2rnq7bX3NzeR2T4YWgz8qiG4h3TSdMe+vN1iQXpJleSJ3SM5zQ8Fy2SyyXAWlbxpEZ2Y+Z4u1BePgJEYbSy80Q==}
'@img/sharp-webcontainers-wasm32@0.35.5':
resolution: {integrity: sha512-hfhF/FmoQyTUkA0bIKFOtw536BQSeBMe6BF6QyWlrPxT754+TFLaZ7sKKTfvvM0yJgKgaYTwnFCIZ/GuDw5SUA==}
engines: {node: '>=20.9.0'}
cpu: [wasm32]
'@img/sharp-win32-arm64@0.35.3':
resolution: {integrity: sha512-4bPwFdMbeC4JQ8L8LOyWp6nsHcboP5fxkp6iPOXz2Vg49R42TuMs2whkJ5OAP4/Ul035qOzy0AecOF9VOscn4w==}
'@img/sharp-win32-arm64@0.35.5':
resolution: {integrity: sha512-X4t7g+7ZA5DKblCBEXGjUqqemj4vczING/5viFwAL8h4N3qYeyjwdCvRLHi4EdOUI+2Z7UFlp1VM+p/AuEtm6Q==}
engines: {node: '>=20.9.0'}
cpu: [arm64]
os: [win32]
'@img/sharp-win32-ia32@0.35.3':
resolution: {integrity: sha512-r53mXsBN6lFUDiST764SvgwUdHAqM4rPAiDzAmf4fLoB6X/rkfyTrLCg6+g17wJJiCmB3JYgHuUldCWUIRFSXw==}
'@img/sharp-win32-ia32@0.35.5':
resolution: {integrity: sha512-5Zm82LoBc43nhwNybZlG7Y1KO//Zhsn306fQl29ZOuStHLGTo3BWL83q3cznX0poxSAMuYL1On/BHBxkBeKr6A==}
engines: {node: ^20.9.0}
cpu: [ia32]
os: [win32]
'@img/sharp-win32-x64@0.35.3':
resolution: {integrity: sha512-D4y1vNeZrIIJCN+uHaWVtH86B+aCrdMYYjicy9pXHvbGZeGYLLSd3wdVuC37FxVXlU1ARsk84eKWfWMXGYEqvA==}
'@img/sharp-win32-x64@0.35.5':
resolution: {integrity: sha512-x76eH0vEiHlcMQu8Y8IenntaACtddpT6W0wmXtWrnKcnKI7ME5DdgqhAD6SEWOEl1v2zDvkZDhFA9KnURwpfqg==}
engines: {node: '>=20.9.0'}
cpu: [x64]
os: [win32]
@@ -2009,8 +2011,8 @@ packages:
engines: {node: '>=0.4.0'}
hasBin: true
adm-zip@0.6.0:
resolution: {integrity: sha512-XleryMhbuksdKtofnWZ9Sk+4CUTbms4Mb/EU32SZwToAyZ5RgVos/ki8n+yr0LWHOGKuakbXTuuYNHLQjhddgg==}
adm-zip@0.6.1:
resolution: {integrity: sha512-Xwrja8nx9e5o2N1my4DsKCeKpdrnACyr1wtbPxBDgGzKzKyE9kRtBFA8mWldI+RVlD7CBZNWY/wQ2+ydwOR6kQ==}
engines: {node: '>=14.0'}
afinn-165-financialmarketnews@3.0.0:
@@ -2107,8 +2109,8 @@ packages:
aws4@1.13.2:
resolution: {integrity: sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==}
axios@1.18.1:
resolution: {integrity: sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==}
axios@1.20.0:
resolution: {integrity: sha512-r8aOh8j9cGKpgQAqpzrUHnSIc6a59Y3Xf/cv8sy1DrHCkZHzQGEuoq1tARk6qSyDdtQGSDgpb9kFlruzPvrgwg==}
babel-jest@29.7.0:
resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==}
@@ -2153,8 +2155,8 @@ packages:
engines: {node: '>=6.0.0'}
hasBin: true
basic-ftp@5.3.1:
resolution: {integrity: sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw==}
basic-ftp@6.2.1:
resolution: {integrity: sha512-bK67isD+lKq46AU8vNtjvMaT2ZqAOAmNCbxUHlFBRD4k15NWxyEjmaKtZPlgce58So4BNTjITGQOVTjL9y0ECA==}
engines: {node: '>=10.0.0'}
better-sqlite3@12.10.0:
@@ -2184,8 +2186,8 @@ packages:
brace-expansion@1.1.16:
resolution: {integrity: sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==}
brace-expansion@2.1.4:
resolution: {integrity: sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==}
brace-expansion@2.1.7:
resolution: {integrity: sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==}
brace-expansion@5.0.8:
resolution: {integrity: sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==}
@@ -2228,10 +2230,6 @@ packages:
buffer-from@1.1.2:
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
buffer-writer@2.0.0:
resolution: {integrity: sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==}
engines: {node: '>=4'}
buffer@5.7.1:
resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}
@@ -3038,8 +3036,8 @@ packages:
resolution: {integrity: sha512-4rTJX6Q5wTYEvxboXi8DsEiUo+OvqJGtLYOSGm37KpdRXsG5XJjbVtYKGJpPSWP+QT7rWscA4vsrdmzbEbenpw==}
engines: {node: '>=18.12.0'}
ip-address@10.4.0:
resolution: {integrity: sha512-oSK96Grm3aP6OrS263xVxbNDGVL7rzBtYdpGqlDG8iQdoenDoTs/nkki+DflYbAEE8Xl6o5YxhxlrKvI3nqKXQ==}
ip-address@10.7.2:
resolution: {integrity: sha512-7H/2gFSIitxc0hG3nOI1glS8QLo/EHBFFLk8vEUjXY/xu0AdL8jZ9U1IzO2PUm0d2D/ofQcAifb0g6OBkt8U7w==}
engines: {node: '>= 12'}
is-arrayish@0.2.1:
@@ -3286,8 +3284,8 @@ packages:
js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
js-yaml@3.15.1:
resolution: {integrity: sha512-S99WuO3HlhO3XN41EtYUNl9zzXjoJx7QvmipxsJVxtCBT0YHEFy+iOJhjSvrmV12nYhWpZaM8lPHkJm0yUMbag==}
js-yaml@3.15.2:
resolution: {integrity: sha512-6EuL879VkRA+1Cz578mKMiKvjPNEuk6+r1JaFzoSWejZmtf7xWbIyw1e3KkxlkzTIt9Taw6JBhEppG7utc1P+w==}
hasBin: true
jsesc@3.1.0:
@@ -3846,9 +3844,6 @@ packages:
package-json-from-dist@1.0.1:
resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
packet-reader@1.0.0:
resolution: {integrity: sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==}
pad-left@2.1.0:
resolution: {integrity: sha512-HJxs9K9AztdIQIAIa/OIazRAUW/L6B9hbQDxO4X07roW3eo9XqZc2ur9bn1StH9CnbbI9EgvejHQX7CBpCF1QA==}
engines: {node: '>=0.10.0'}
@@ -3920,15 +3915,6 @@ packages:
resolution: {integrity: sha512-o2XFanIMy/3+mThw69O8d4n1E5zsLhdO+OPqswezu7Z5ekP4hYDqlDjlmOpYMbzY2Br0ufCwJLdDIXeNVwcWFg==}
engines: {node: '>=10'}
pg@8.11.3:
resolution: {integrity: sha512-+9iuvG8QfaaUrrph+kpF24cXkH1YOOUeArRNYIxq1viYHZagBxrTno7cecY1Fa44tJeZvaoG+Djpkc3JwehN5g==}
engines: {node: '>= 8.0.0'}
peerDependencies:
pg-native: '>=3.0.1'
peerDependenciesMeta:
pg-native:
optional: true
pg@8.21.0:
resolution: {integrity: sha512-AUP1EYJuHraQGsVoCQVIcM7TEJVGtDzxWtGFZd8rds9d+CCXlU5Js1rYgfLNvxy9iJrpHjGrRjoi/3BT9fRyiA==}
engines: {node: '>= 16.0.0'}
@@ -4225,8 +4211,8 @@ packages:
resolution: {integrity: sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==}
engines: {node: '>=10'}
sharp@0.35.3:
resolution: {integrity: sha512-ej0zVHuZGHCiABXcNxeYhpRnPNPAcvbG8RMdBAhDAxLKkCRVSpK3Iyu7qbqw3JMzoj0REeM6f3tJLtVwl0023Q==}
sharp@0.35.5:
resolution: {integrity: sha512-Ywn4OnzGukp7CDMrp08RQ50YKmuwG47brZgIVPTvBaaAfQlRlygrRqSrxdCiL9M+LlzLBiJ68IR1QqvzHyjC7g==}
engines: {node: '>=20.9.0'}
peerDependencies:
'@types/node': '*'
@@ -4625,8 +4611,8 @@ packages:
undici-types@7.24.6:
resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==}
undici@7.29.0:
resolution: {integrity: sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==}
undici@7.30.0:
resolution: {integrity: sha512-dkrQXeHSaoamnItlYbmzG0wFYrM0ZwDxCIg0A7aKjTyyhh9svRzCNFEzV+Vm05/yehjCzjDZ31KXfGEjYSztDQ==}
engines: {node: '>=20.18.1'}
update-browserslist-db@1.3.2:
@@ -6154,11 +6140,11 @@ snapshots:
ms: 2.1.3
secure-json-parse: 4.1.0
tslib: 2.8.1
undici: 7.29.0
undici: 7.30.0
transitivePeerDependencies:
- supports-color
'@emnapi/runtime@1.11.2':
'@emnapi/runtime@1.11.3':
dependencies:
tslib: 2.8.1
optional: true
@@ -6262,7 +6248,7 @@ snapshots:
dependencies:
graphql: 16.14.2
'@grpc/grpc-js@1.14.4':
'@grpc/grpc-js@1.14.5':
dependencies:
'@grpc/proto-loader': 0.8.1
'@js-sdsl/ordered-map': 4.4.2
@@ -6295,7 +6281,7 @@ snapshots:
'@huggingface/tokenizers': 0.1.3
onnxruntime-node: 1.24.3
onnxruntime-web: 1.26.0-dev.20260416-b7804b056c
sharp: 0.35.3(@types/node@22.19.21)
sharp: 0.35.5(@types/node@22.19.21)
transitivePeerDependencies:
- '@types/node'
@@ -6306,108 +6292,108 @@ snapshots:
'@img/colour@1.1.0': {}
'@img/sharp-darwin-arm64@0.35.3':
'@img/sharp-darwin-arm64@0.35.5':
optionalDependencies:
'@img/sharp-libvips-darwin-arm64': 1.3.2
'@img/sharp-libvips-darwin-arm64': 1.3.4
optional: true
'@img/sharp-darwin-x64@0.35.3':
'@img/sharp-darwin-x64@0.35.5':
optionalDependencies:
'@img/sharp-libvips-darwin-x64': 1.3.2
'@img/sharp-libvips-darwin-x64': 1.3.4
optional: true
'@img/sharp-freebsd-wasm32@0.35.3':
'@img/sharp-freebsd-wasm32@0.35.5':
dependencies:
'@img/sharp-wasm32': 0.35.3
'@img/sharp-wasm32': 0.35.5
optional: true
'@img/sharp-libvips-darwin-arm64@1.3.2':
'@img/sharp-libvips-darwin-arm64@1.3.4':
optional: true
'@img/sharp-libvips-darwin-x64@1.3.2':
'@img/sharp-libvips-darwin-x64@1.3.4':
optional: true
'@img/sharp-libvips-linux-arm64@1.3.2':
'@img/sharp-libvips-linux-arm64@1.3.4':
optional: true
'@img/sharp-libvips-linux-arm@1.3.2':
'@img/sharp-libvips-linux-arm@1.3.4':
optional: true
'@img/sharp-libvips-linux-ppc64@1.3.2':
'@img/sharp-libvips-linux-ppc64@1.3.4':
optional: true
'@img/sharp-libvips-linux-riscv64@1.3.2':
'@img/sharp-libvips-linux-riscv64@1.3.4':
optional: true
'@img/sharp-libvips-linux-s390x@1.3.2':
'@img/sharp-libvips-linux-s390x@1.3.4':
optional: true
'@img/sharp-libvips-linux-x64@1.3.2':
'@img/sharp-libvips-linux-x64@1.3.4':
optional: true
'@img/sharp-libvips-linuxmusl-arm64@1.3.2':
'@img/sharp-libvips-linuxmusl-arm64@1.3.4':
optional: true
'@img/sharp-libvips-linuxmusl-x64@1.3.2':
'@img/sharp-libvips-linuxmusl-x64@1.3.4':
optional: true
'@img/sharp-linux-arm64@0.35.3':
'@img/sharp-linux-arm64@0.35.5':
optionalDependencies:
'@img/sharp-libvips-linux-arm64': 1.3.2
'@img/sharp-libvips-linux-arm64': 1.3.4
optional: true
'@img/sharp-linux-arm@0.35.3':
'@img/sharp-linux-arm@0.35.5':
optionalDependencies:
'@img/sharp-libvips-linux-arm': 1.3.2
'@img/sharp-libvips-linux-arm': 1.3.4
optional: true
'@img/sharp-linux-ppc64@0.35.3':
'@img/sharp-linux-ppc64@0.35.5':
optionalDependencies:
'@img/sharp-libvips-linux-ppc64': 1.3.2
'@img/sharp-libvips-linux-ppc64': 1.3.4
optional: true
'@img/sharp-linux-riscv64@0.35.3':
'@img/sharp-linux-riscv64@0.35.5':
optionalDependencies:
'@img/sharp-libvips-linux-riscv64': 1.3.2
'@img/sharp-libvips-linux-riscv64': 1.3.4
optional: true
'@img/sharp-linux-s390x@0.35.3':
'@img/sharp-linux-s390x@0.35.5':
optionalDependencies:
'@img/sharp-libvips-linux-s390x': 1.3.2
'@img/sharp-libvips-linux-s390x': 1.3.4
optional: true
'@img/sharp-linux-x64@0.35.3':
'@img/sharp-linux-x64@0.35.5':
optionalDependencies:
'@img/sharp-libvips-linux-x64': 1.3.2
'@img/sharp-libvips-linux-x64': 1.3.4
optional: true
'@img/sharp-linuxmusl-arm64@0.35.3':
'@img/sharp-linuxmusl-arm64@0.35.5':
optionalDependencies:
'@img/sharp-libvips-linuxmusl-arm64': 1.3.2
'@img/sharp-libvips-linuxmusl-arm64': 1.3.4
optional: true
'@img/sharp-linuxmusl-x64@0.35.3':
'@img/sharp-linuxmusl-x64@0.35.5':
optionalDependencies:
'@img/sharp-libvips-linuxmusl-x64': 1.3.2
'@img/sharp-libvips-linuxmusl-x64': 1.3.4
optional: true
'@img/sharp-wasm32@0.35.3':
'@img/sharp-wasm32@0.35.5':
dependencies:
'@emnapi/runtime': 1.11.2
'@emnapi/runtime': 1.11.3
optional: true
'@img/sharp-webcontainers-wasm32@0.35.3':
'@img/sharp-webcontainers-wasm32@0.35.5':
dependencies:
'@img/sharp-wasm32': 0.35.3
'@img/sharp-wasm32': 0.35.5
optional: true
'@img/sharp-win32-arm64@0.35.3':
'@img/sharp-win32-arm64@0.35.5':
optional: true
'@img/sharp-win32-ia32@0.35.3':
'@img/sharp-win32-ia32@0.35.5':
optional: true
'@img/sharp-win32-x64@0.35.3':
'@img/sharp-win32-x64@0.35.5':
optional: true
'@iovalkey/commands@0.1.0': {}
@@ -6430,7 +6416,7 @@ snapshots:
camelcase: 5.3.1
find-up: 4.1.0
get-package-type: 0.1.0
js-yaml: 3.15.1
js-yaml: 3.15.2
resolve-from: 5.0.0
'@istanbuljs/schema@0.1.6': {}
@@ -6722,7 +6708,7 @@ snapshots:
dependencies:
'@qdrant/openapi-typescript-fetch': 1.2.6
typescript: 5.5.4
undici: 7.29.0
undici: 7.30.0
'@qdrant/openapi-typescript-fetch@1.2.6': {}
@@ -7194,7 +7180,7 @@ snapshots:
'@turbopuffer/turbopuffer@2.5.0':
dependencies:
pako: 2.2.0
undici: 7.29.0
undici: 7.30.0
'@types/babel__core@7.20.5':
dependencies:
@@ -7336,7 +7322,7 @@ snapshots:
'@zilliz/milvus2-sdk-node@3.0.3':
dependencies:
'@grpc/grpc-js': 1.14.4
'@grpc/grpc-js': 1.14.5
'@grpc/proto-loader': 0.8.1
'@opentelemetry/api': 1.9.1
'@petamoriken/float16': 3.9.3
@@ -7362,7 +7348,7 @@ snapshots:
acorn@8.16.0: {}
adm-zip@0.6.0: {}
adm-zip@0.6.1: {}
afinn-165-financialmarketnews@3.0.0: {}
@@ -7448,7 +7434,7 @@ snapshots:
aws4@1.13.2: {}
axios@1.18.1:
axios@1.20.0:
dependencies:
follow-redirects: 1.16.0
form-data: 4.0.6
@@ -7523,7 +7509,7 @@ snapshots:
baseline-browser-mapping@2.11.21: {}
basic-ftp@5.3.1: {}
basic-ftp@6.2.1: {}
better-sqlite3@12.10.0:
dependencies:
@@ -7553,7 +7539,7 @@ snapshots:
balanced-match: 1.0.2
concat-map: 0.0.1
brace-expansion@2.1.4:
brace-expansion@2.1.7:
dependencies:
balanced-match: 1.0.2
@@ -7593,8 +7579,6 @@ snapshots:
buffer-from@1.1.2: {}
buffer-writer@2.0.0: {}
buffer@5.7.1:
dependencies:
base64-js: 1.5.1
@@ -7632,7 +7616,7 @@ snapshots:
cassandra-driver@4.8.0:
dependencies:
'@types/node': 18.19.130
adm-zip: 0.6.0
adm-zip: 0.6.1
long: 5.2.5
chalk-template@0.4.0:
@@ -8225,7 +8209,7 @@ snapshots:
get-uri@6.0.5:
dependencies:
basic-ftp: 5.3.1
basic-ftp: 6.2.1
data-uri-to-buffer: 6.0.2
debug: 4.4.3(supports-color@5.5.0)
transitivePeerDependencies:
@@ -8294,7 +8278,7 @@ snapshots:
google-gax@5.0.7:
dependencies:
'@grpc/grpc-js': 1.14.4
'@grpc/grpc-js': 1.14.5
'@grpc/proto-loader': 0.8.1
duplexify: 4.1.3
google-auth-library: 10.5.0
@@ -8453,7 +8437,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
ip-address@10.4.0: {}
ip-address@10.7.2: {}
is-arrayish@0.2.1: {}
@@ -8525,7 +8509,7 @@ snapshots:
'@babel/parser': 7.29.7
'@istanbuljs/schema': 0.1.6
istanbul-lib-coverage: 3.2.2
semver: 7.8.4
semver: 7.8.5
transitivePeerDependencies:
- supports-color
@@ -8873,7 +8857,7 @@ snapshots:
js-tokens@4.0.0: {}
js-yaml@3.15.1:
js-yaml@3.15.2:
dependencies:
argparse: 1.0.10
esprima: 4.0.1
@@ -9023,7 +9007,7 @@ snapshots:
make-dir@4.0.0:
dependencies:
semver: 7.8.4
semver: 7.8.5
make-error@1.3.6: {}
@@ -9081,7 +9065,7 @@ snapshots:
minimatch@9.0.9:
dependencies:
brace-expansion: 2.1.4
brace-expansion: 2.1.7
minimist@1.2.8: {}
@@ -9218,7 +9202,7 @@ snapshots:
nice-grpc@2.1.16:
dependencies:
'@grpc/grpc-js': 1.14.4
'@grpc/grpc-js': 1.14.5
abort-controller-x: 0.5.0
nice-grpc-common: 2.0.3
@@ -9309,7 +9293,7 @@ snapshots:
onnxruntime-node@1.24.3:
dependencies:
adm-zip: 0.6.0
adm-zip: 0.6.1
global-agent: 3.0.0
onnxruntime-common: 1.24.3
@@ -9409,8 +9393,6 @@ snapshots:
package-json-from-dist@1.0.1: {}
packet-reader@1.0.0: {}
pad-left@2.1.0:
dependencies:
repeat-string: 1.6.1
@@ -9458,10 +9440,6 @@ snapshots:
pg-numeric@1.0.2: {}
pg-pool@3.14.0(pg@8.11.3):
dependencies:
pg: 8.11.3
pg-pool@3.14.0(pg@8.21.0):
dependencies:
pg: 8.21.0
@@ -9486,18 +9464,6 @@ snapshots:
postgres-interval: 3.0.0
postgres-range: 1.1.4
pg@8.11.3:
dependencies:
buffer-writer: 2.0.0
packet-reader: 1.0.0
pg-connection-string: 2.13.0
pg-pool: 3.14.0(pg@8.11.3)
pg-protocol: 1.14.0
pg-types: 2.2.0
pgpass: 1.0.5
optionalDependencies:
pg-cloudflare: 1.4.0
pg@8.21.0:
dependencies:
pg-connection-string: 2.13.0
@@ -9806,37 +9772,37 @@ snapshots:
dependencies:
type-fest: 0.13.1
sharp@0.35.3(@types/node@22.19.21):
sharp@0.35.5(@types/node@22.19.21):
dependencies:
'@img/colour': 1.1.0
detect-libc: 2.1.2
semver: 7.8.5
optionalDependencies:
'@img/sharp-darwin-arm64': 0.35.3
'@img/sharp-darwin-x64': 0.35.3
'@img/sharp-freebsd-wasm32': 0.35.3
'@img/sharp-libvips-darwin-arm64': 1.3.2
'@img/sharp-libvips-darwin-x64': 1.3.2
'@img/sharp-libvips-linux-arm': 1.3.2
'@img/sharp-libvips-linux-arm64': 1.3.2
'@img/sharp-libvips-linux-ppc64': 1.3.2
'@img/sharp-libvips-linux-riscv64': 1.3.2
'@img/sharp-libvips-linux-s390x': 1.3.2
'@img/sharp-libvips-linux-x64': 1.3.2
'@img/sharp-libvips-linuxmusl-arm64': 1.3.2
'@img/sharp-libvips-linuxmusl-x64': 1.3.2
'@img/sharp-linux-arm': 0.35.3
'@img/sharp-linux-arm64': 0.35.3
'@img/sharp-linux-ppc64': 0.35.3
'@img/sharp-linux-riscv64': 0.35.3
'@img/sharp-linux-s390x': 0.35.3
'@img/sharp-linux-x64': 0.35.3
'@img/sharp-linuxmusl-arm64': 0.35.3
'@img/sharp-linuxmusl-x64': 0.35.3
'@img/sharp-webcontainers-wasm32': 0.35.3
'@img/sharp-win32-arm64': 0.35.3
'@img/sharp-win32-ia32': 0.35.3
'@img/sharp-win32-x64': 0.35.3
'@img/sharp-darwin-arm64': 0.35.5
'@img/sharp-darwin-x64': 0.35.5
'@img/sharp-freebsd-wasm32': 0.35.5
'@img/sharp-libvips-darwin-arm64': 1.3.4
'@img/sharp-libvips-darwin-x64': 1.3.4
'@img/sharp-libvips-linux-arm': 1.3.4
'@img/sharp-libvips-linux-arm64': 1.3.4
'@img/sharp-libvips-linux-ppc64': 1.3.4
'@img/sharp-libvips-linux-riscv64': 1.3.4
'@img/sharp-libvips-linux-s390x': 1.3.4
'@img/sharp-libvips-linux-x64': 1.3.4
'@img/sharp-libvips-linuxmusl-arm64': 1.3.4
'@img/sharp-libvips-linuxmusl-x64': 1.3.4
'@img/sharp-linux-arm': 0.35.5
'@img/sharp-linux-arm64': 0.35.5
'@img/sharp-linux-ppc64': 0.35.5
'@img/sharp-linux-riscv64': 0.35.5
'@img/sharp-linux-s390x': 0.35.5
'@img/sharp-linux-x64': 0.35.5
'@img/sharp-linuxmusl-arm64': 0.35.5
'@img/sharp-linuxmusl-x64': 0.35.5
'@img/sharp-webcontainers-wasm32': 0.35.5
'@img/sharp-win32-arm64': 0.35.5
'@img/sharp-win32-ia32': 0.35.5
'@img/sharp-win32-x64': 0.35.5
'@types/node': 22.19.21
shebang-command@2.0.0:
@@ -9881,7 +9847,7 @@ snapshots:
socks@2.8.9:
dependencies:
ip-address: 10.4.0
ip-address: 10.7.2
smart-buffer: 4.2.0
source-map-support@0.5.13:
@@ -10218,7 +10184,7 @@ snapshots:
undici-types@7.24.6: {}
undici@7.29.0: {}
undici@7.30.0: {}
update-browserslist-db@1.3.2(browserslist@4.28.9):
dependencies:
+10 -8
View File
@@ -10,10 +10,10 @@ overrides:
"picomatch@<2.3.2": "^2.3.2"
"picomatch@>=4.0.0 <4.0.4": "^4.0.4"
"jws@4.0.0": "4.0.1"
"js-yaml@<3.15.1": ">=3.15.1 <4.0.0"
"js-yaml@>=4.0.0 <4.3.1": ">=4.3.1 <5.0.0"
"sharp@<0.35.0": ">=0.35.0 <0.36.0"
"adm-zip@<0.6.0": ">=0.6.0 <0.7.0"
"js-yaml@<3.15.2": ">=3.15.2 <4.0.0"
"js-yaml@>=4.0.0 <4.3.2": ">=4.3.2 <5.0.0"
"sharp@<0.35.4": ">=0.35.4 <0.36.0"
"adm-zip@<0.6.1": ">=0.6.1 <0.7.0"
"langsmith@<0.6.0": "^0.6.0"
"minimatch@<3.1.3": "^3.1.3"
"minimatch@>=5.0.0 <5.1.8": "^5.1.8"
@@ -27,19 +27,21 @@ overrides:
"glob@>=10.2.0 <10.5.0": "^10.5.0"
"@modelcontextprotocol/sdk": "^1.25.4"
"esbuild": ">=0.28.1"
"undici@<7.29.0": ">=7.29.0 <8.0.0"
"axios@<1.18.0": ">=1.18.0 <2.0.0"
"undici@<7.29.1": ">=7.29.1 <8.0.0"
"axios@<1.20.0": ">=1.20.0 <2.0.0"
"brace-expansion@<1.1.16": ">=1.1.16 <2.0.0"
"brace-expansion@>=2.0.0 <2.1.4": ">=2.1.4 <3.0.0"
"brace-expansion@>=2.0.0 <2.1.6": ">=2.1.6 <3.0.0"
"brace-expansion@>=3.0.0 <5.0.8": ">=5.0.8 <6.0.0"
"fast-xml-parser@>=5.0.0 <5.10.1": ">=5.10.1 <6.0.0"
"tar@<7.5.21": ">=7.5.21 <8.0.0"
"thrift@<0.23.0": "^0.23.0"
"mongoose@>=9.0.0 <9.7.2": ">=9.7.2 <10.0.0"
"protobufjs@<7.6.5": ">=7.6.5 <8.0.0"
"ip-address@<10.3.1": ">=10.3.1 <11.0.0"
"ip-address@<10.5.1": ">=10.5.1 <11.0.0"
"browserslist@<4.28.7": ">=4.28.7 <5.0.0"
"mysql2@<3.23.1": ">=3.23.1 <4.0.0"
"basic-ftp@<6.2.1": ">=6.2.1 <7.0.0"
"@grpc/grpc-js@>=1.14.0 <1.14.5": ">=1.14.5 <2.0.0"
patchedDependencies:
"tar@7.5.22": "patches/tar@7.5.22.patch"
+3 -1
View File
@@ -768,7 +768,9 @@ export class Memory {
has_filters: !!config.filters,
infer: config.infer,
});
const { filters = {}, infer = true } = config;
// Copy: scope keys are written into this object below, and the caller's next add() must not inherit them (#6796).
const filters: SearchFilters = { ...(config.filters ?? {}) };
const { infer = true } = config;
const metadata = stripIdentityKeys(config.metadata);
// Validate and trim entity IDs
+29 -11
View File
@@ -1,8 +1,11 @@
import type { Client as ClientType, ClientConfig } from "pg";
import pkg from "pg";
const { Client, escapeIdentifier } = pkg;
import { VectorStore } from "./base";
import { SearchFilters, VectorStoreConfig, VectorStoreResult } from "../types";
import { loadPeer } from "../utils/load_peer";
function escapeIdentifier(name: string): string {
return `"${name.replace(/"/g, '""')}"`;
}
const SAFE_IDENTIFIER_RE = /^[a-zA-Z_][a-zA-Z0-9_]{0,127}$/;
@@ -212,7 +215,7 @@ function buildClientConfig(
}
export class PGVector implements VectorStore {
private client: ClientType;
private client!: ClientType;
private collectionName: string;
private useDiskann: boolean;
private useHnsw: boolean;
@@ -234,13 +237,6 @@ export class PGVector implements VectorStore {
? ""
: validateIdentifier(config.dbname || "vector_store", "dbname");
this.config = config;
this.client = new Client(
buildClientConfig(
config,
this.useDirectConnection ? undefined : "postgres",
),
);
this.initialize().catch(console.error);
}
@@ -257,6 +253,18 @@ export class PGVector implements VectorStore {
private async _doInitialize(): Promise<void> {
try {
const pg = await loadPeer(
"pg",
"PGVector vector store",
() => import("pg"),
);
const { Client } = pg.default ?? pg;
this.client = new Client(
buildClientConfig(
this.config,
this.useDirectConnection ? undefined : "postgres",
),
);
await this.client.connect();
if (!this.useDirectConnection) {
@@ -345,6 +353,7 @@ export class PGVector implements VectorStore {
ids: string[],
payloads: Record<string, any>[],
): Promise<void> {
await this.initialize();
const values = vectors.map((vector, i) => ({
id: ids[i],
vector: `[${vector.join(",")}]`,
@@ -368,6 +377,7 @@ export class PGVector implements VectorStore {
topK: number = 5,
filters?: SearchFilters,
): Promise<VectorStoreResult[] | null> {
await this.initialize();
try {
const {
conditions,
@@ -406,6 +416,7 @@ export class PGVector implements VectorStore {
topK: number = 5,
filters?: SearchFilters,
): Promise<VectorStoreResult[]> {
await this.initialize();
const queryVector = `[${query.join(",")}]`;
const {
conditions,
@@ -435,6 +446,7 @@ export class PGVector implements VectorStore {
}
async get(vectorId: string): Promise<VectorStoreResult | null> {
await this.initialize();
const result = await this.client.query(
`SELECT id, payload FROM ${this.col()} WHERE id = $1`,
[vectorId],
@@ -453,6 +465,7 @@ export class PGVector implements VectorStore {
vector: number[],
payload: Record<string, any>,
): Promise<void> {
await this.initialize();
const vectorStr = `[${vector.join(",")}]`;
await this.client.query(
`
@@ -465,12 +478,14 @@ export class PGVector implements VectorStore {
}
async delete(vectorId: string): Promise<void> {
await this.initialize();
await this.client.query(`DELETE FROM ${this.col()} WHERE id = $1`, [
vectorId,
]);
}
async deleteCol(): Promise<void> {
await this.initialize();
await this.client.query(`DROP TABLE IF EXISTS ${this.col()}`);
}
@@ -487,6 +502,7 @@ export class PGVector implements VectorStore {
filters?: SearchFilters,
topK: number = 100,
): Promise<[VectorStoreResult[], number]> {
await this.initialize();
const {
conditions,
values: filterValues,
@@ -525,10 +541,11 @@ export class PGVector implements VectorStore {
}
async close(): Promise<void> {
await this.client.end();
await this.client?.end();
}
async getUserId(): Promise<string> {
await this.initialize();
const result = await this.client.query(
"SELECT user_id FROM memory_migrations LIMIT 1",
);
@@ -549,6 +566,7 @@ export class PGVector implements VectorStore {
}
async setUserId(userId: string): Promise<void> {
await this.initialize();
await this.client.query("DELETE FROM memory_migrations");
await this.client.query(
"INSERT INTO memory_migrations (user_id) VALUES ($1)",
@@ -247,23 +247,54 @@ export class TurbopufferDB implements VectorStore {
}
}
// Maps mem0's universal filter operators to Turbopuffer's filter tokens.
private static readonly OPERATOR_MAP: Record<string, string> = {
eq: "Eq",
ne: "NotEq",
gt: "Gt",
gte: "Gte",
lt: "Lt",
lte: "Lte",
in: "In",
nin: "NotIn",
};
private convertFilters(filters?: SearchFilters): any {
if (!filters || Object.keys(filters).length === 0) return null;
const conditions: any[] = [];
for (const [key, value] of Object.entries(filters)) {
if (
typeof value === "object" &&
value !== null &&
!Array.isArray(value)
) {
if ("gte" in value) conditions.push([key, "Gte", value.gte]);
if ("lte" in value) conditions.push([key, "Lte", value.lte]);
if ("gt" in value) conditions.push([key, "Gt", value.gt]);
if ("lt" in value) conditions.push([key, "Lt", value.lt]);
} else {
conditions.push([key, "Eq", value]);
// "*" is a match-any wildcard: it must not constrain the query. The old
// code turned it into `[key, "Eq", "*"]`, matching nothing.
if (value === "*") {
continue;
}
// Array shorthand: { key: [a, b] } means "in".
if (Array.isArray(value)) {
conditions.push([key, "In", value]);
continue;
}
if (typeof value === "object" && value !== null) {
// Operator dict: every operator present must hold. Previously only
// `gte` and `lte` were read, so `gt`/`lt`/`ne`/`eq`/`in`/`nin` were
// silently dropped and the filter returned unfiltered results.
for (const [op, operand] of Object.entries(value)) {
const token = TurbopufferDB.OPERATOR_MAP[op];
if (!token) {
throw new Error(
`Unsupported Turbopuffer filter operator '${op}' for field '${key}'. ` +
`Supported operators: ${Object.keys(TurbopufferDB.OPERATOR_MAP).join(", ")}.`,
);
}
conditions.push([key, token, operand]);
}
continue;
}
// Scalar shorthand: equality.
conditions.push([key, "Eq", value]);
}
if (conditions.length === 0) return null;
@@ -274,7 +305,18 @@ export class TurbopufferDB implements VectorStore {
private parseRows(rows: any[]): VectorStoreResult[] {
return rows.map((row) => {
const { id, $dist, vector, ...rest } = row;
const score = $dist != null ? 1 - $dist : undefined;
let score: number | undefined;
if ($dist == null) {
score = undefined;
} else if (this.distanceMetric === "euclidean_squared") {
// euclidean_squared $dist is an unbounded squared distance, so 1 - $dist
// goes negative for any $dist > 1 and inverts ranking. Convert it to a
// bounded higher-is-better score, mirroring the milvus/baidu stores.
score = 1 / (1 + $dist);
} else {
// Cosine distance is in [0, 2]; 1 - $dist stays a meaningful similarity.
score = 1 - $dist;
}
return { id: String(id), payload: rest, score };
});
}
@@ -0,0 +1,115 @@
/// <reference types="jest" />
/**
* add() writes the resolved scope (user_id / agent_id / run_id) into its
* filters object. If that object is the caller's, the scope survives the call
* and leaks into the next add() that reuses it, which also defeats the
* "one of userId, agentId or runId is required" guard.
*
* search() and getAll() already build a fresh object from config.filters.
*/
jest.mock("../src/utils/telemetry", () => ({
captureClientEvent: jest.fn().mockResolvedValue(undefined),
isTelemetryEnabled: jest.fn(() => false),
}));
import { Memory } from "../src/memory";
const DIM = 1536;
let seq = 0;
function createMemory(): Memory {
const m = new Memory({
disableHistory: true,
vectorStore: {
provider: "memory",
config: {
collectionName: `test-add-filters-${seq++}`,
dimension: DIM,
dbPath: ":memory:",
},
},
historyDbPath: ":memory:",
embedder: { provider: "openai", config: { apiKey: "test-key" } },
llm: { provider: "openai", config: { apiKey: "test-key" } },
} as any);
// infer:false is enough to exercise the scope handling, so the LLM is never
// called; the embedder just has to return a well-formed vector.
(m as any).embedder = {
embed: async () => new Array(DIM).fill(0.1),
embedBatch: async (texts: string[]) =>
texts.map(() => new Array(DIM).fill(0.1)),
};
return m;
}
describe("add() does not mutate the caller's filters object", () => {
it("leaves the caller's object untouched", async () => {
const memory = createMemory();
const filters: Record<string, any> = {};
await memory.add([{ role: "user", content: "alice note" }], {
userId: "alice",
infer: false,
filters,
});
expect(filters).toEqual({});
});
it("still throws when a reused object is the only source of scope", async () => {
const memory = createMemory();
const filters: Record<string, any> = {};
await memory.add([{ role: "user", content: "alice note" }], {
userId: "alice",
infer: false,
filters,
});
// No userId/agentId/runId on this call, so the required-scope guard must
// fire rather than reading a value left behind by the previous call.
await expect(
memory.add([{ role: "user", content: "bob note" }], {
infer: false,
filters,
}),
).rejects.toThrow(
"One of the filters: userId, agentId or runId is required!",
);
});
it("does not store one user's memory under another user's scope", async () => {
const memory = createMemory();
const filters: Record<string, any> = {};
await memory.add([{ role: "user", content: "alice note" }], {
userId: "alice",
infer: false,
filters,
});
// A second caller reuses the object and supplies no scope of its own. This
// must not be silently attributed to alice.
await memory
.add([{ role: "user", content: "bob note" }], {
infer: false,
filters,
})
.catch(() => undefined);
const alice = await memory.getAll({ filters: { user_id: "alice" } });
expect(alice.results.map((r) => r.memory)).toEqual(["alice note"]);
});
it("keeps scope keys the caller passed in", async () => {
const memory = createMemory();
await memory.add([{ role: "user", content: "scoped note" }], {
infer: false,
filters: { user_id: "carol" },
});
const carol = await memory.getAll({ filters: { user_id: "carol" } });
expect(carol.results.map((r) => r.memory)).toEqual(["scoped note"]);
});
});
@@ -0,0 +1,39 @@
jest.mock("pg", () => {
throw new Error("Cannot find module 'pg'");
});
jest.mock("natural", () => {
throw new Error("Cannot find module 'natural'");
});
describe("mem0ai/oss without pg or natural installed", () => {
beforeEach(() => {
jest.spyOn(console, "error").mockImplementation(() => {});
});
afterEach(() => {
jest.restoreAllMocks();
});
test("mem0ai/oss loads", async () => {
await expect(import("../src")).resolves.toHaveProperty("Memory");
});
test("PGVector explains how to install pg", async () => {
const { PGVector } = await import("../src/vector_stores/pgvector");
const store = new PGVector({
connectionString: "postgresql://localhost:5432/db",
embeddingModelDims: 3,
} as any);
await expect(store.initialize()).rejects.toThrow(
"The 'pg' package is required to use the PGVector vector store. Install it with: npm install pg",
);
});
test("BM25 lemmatization falls back to the built-in stemmer", async () => {
const { lemmatizeForBm25 } = await import("../src/utils/lemmatization");
expect(lemmatizeForBm25("The dogs were running")).toBe("dog runn running");
});
});
@@ -0,0 +1,76 @@
/// <reference types="jest" />
/**
* Turbopuffer vector store — score conversion unit tests.
*
* Drives parseRows() through the public search() API with a mocked
* @turbopuffer/turbopuffer peer, asserting the score returned per metric.
*/
const mockQuery = jest.fn();
jest.mock("@turbopuffer/turbopuffer", () => ({
__esModule: true,
default: class {
namespace() {
return { query: mockQuery };
}
},
}));
import { TurbopufferDB } from "../src/vector_stores/turbopuffer";
function makeStore(distanceMetric?: string) {
return new TurbopufferDB({
apiKey: "test-key",
collectionName: "mem0",
...(distanceMetric ? { distanceMetric } : {}),
} as any);
}
async function scoreFor(
distanceMetric: string | undefined,
row: Record<string, any>,
): Promise<number | undefined> {
mockQuery.mockResolvedValueOnce({ rows: [row] });
const results = await makeStore(distanceMetric).search([0.1, 0.2, 0.3], 5);
return results[0].score;
}
describe("TurbopufferDB score conversion", () => {
it("keeps cosine distance as 1 - dist (default metric)", async () => {
// cosine_distance is the default; a distance of 0.25 -> similarity 0.75.
expect(await scoreFor(undefined, { id: "a", $dist: 0.25 })).toBeCloseTo(
0.75,
10,
);
});
it("bounds an unbounded euclidean_squared distance to a 0..1 similarity", async () => {
// $dist = 4.0 is a squared distance. 1 - 4.0 = -3.0 would invert ranking;
// 1 / (1 + 4.0) = 0.2 keeps it higher-is-better and in range.
const score = await scoreFor("euclidean_squared", { id: "a", $dist: 4.0 });
expect(score).toBeCloseTo(0.2, 10);
expect(score!).toBeGreaterThanOrEqual(0);
expect(score!).toBeLessThanOrEqual(1);
});
it("ranks a nearer euclidean_squared hit above a farther one", async () => {
mockQuery.mockResolvedValueOnce({
rows: [
{ id: "near", $dist: 1.0 },
{ id: "far", $dist: 9.0 },
],
});
const results = await makeStore("euclidean_squared").search(
[0.1, 0.2, 0.3],
5,
);
const near = results.find((r) => r.id === "near")!;
const far = results.find((r) => r.id === "far")!;
expect(near.score!).toBeGreaterThan(far.score!);
});
it("preserves an undefined score when the row has no distance", async () => {
expect(await scoreFor("euclidean_squared", { id: "a" })).toBeUndefined();
});
});
@@ -0,0 +1,97 @@
/// <reference types="jest" />
/**
* Turbopuffer vector store — filter translation unit tests.
*
* Drives the private convertFilters() through the public search() API with a
* mocked @turbopuffer/turbopuffer peer, and asserts the filter tuple handed to
* ns.query().
*/
const mockQuery = jest.fn().mockResolvedValue({ rows: [] });
// createClient() does `new sdk.default({...})`, whose namespace() returns the
// object search() calls query() on.
jest.mock("@turbopuffer/turbopuffer", () => ({
__esModule: true,
default: class {
namespace() {
return { query: mockQuery };
}
},
}));
import { TurbopufferDB } from "../src/vector_stores/turbopuffer";
function makeStore() {
return new TurbopufferDB({
apiKey: "test-key",
collectionName: "mem0",
} as any);
}
async function filterFor(filters: any): Promise<any> {
mockQuery.mockClear();
await makeStore().search([0.1, 0.2, 0.3], 5, filters);
return mockQuery.mock.calls[0][0].filters;
}
describe("TurbopufferDB convertFilters", () => {
it("maps every operator, not just gte/lte", async () => {
expect(await filterFor({ age: { gt: 18 } })).toEqual(["age", "Gt", 18]);
expect(await filterFor({ age: { lt: 65 } })).toEqual(["age", "Lt", 65]);
expect(await filterFor({ age: { ne: 40 } })).toEqual(["age", "NotEq", 40]);
expect(await filterFor({ role: { eq: "admin" } })).toEqual([
"role",
"Eq",
"admin",
]);
expect(await filterFor({ tier: { in: ["a", "b"] } })).toEqual([
"tier",
"In",
["a", "b"],
]);
expect(await filterFor({ tier: { nin: ["x"] } })).toEqual([
"tier",
"NotIn",
["x"],
]);
});
it("applies every operator in a compound range (AND), not just the first", async () => {
const filter = await filterFor({ age: { gt: 18, lt: 65 } });
expect(filter[0]).toBe("And");
expect(filter[1]).toEqual(
expect.arrayContaining([
["age", "Gt", 18],
["age", "Lt", 65],
]),
);
});
it("treats a bare array value as an 'in' filter", async () => {
expect(await filterFor({ tier: ["gold", "silver"] })).toEqual([
"tier",
"In",
["gold", "silver"],
]);
});
it("keeps scalar equality working", async () => {
expect(await filterFor({ user_id: "u1" })).toEqual(["user_id", "Eq", "u1"]);
});
it("skips a '*' wildcard value instead of matching it literally", async () => {
// Only the real agent_id clause survives; user_id: "*" contributes nothing.
expect(await filterFor({ user_id: "*", agent_id: "a1" })).toEqual([
"agent_id",
"Eq",
"a1",
]);
});
it("throws on an unsupported operator rather than silently dropping it", async () => {
await expect(
makeStore().search([0.1, 0.2, 0.3], 5, { name: { startsWith: "a" } }),
).rejects.toThrow(/Unsupported Turbopuffer filter operator 'startsWith'/);
});
});
+12 -3
View File
@@ -579,11 +579,20 @@ class AWSBedrockLLM(LLMBase):
# Use converse API for Anthropic models
response = self.client.converse(**converse_params)
# Parse Converse API response
# Parse Converse API response. Claude reasoning models can emit a
# `reasoningContent` block before the `text` block, so iterate to
# find the first block that carries text instead of indexing
# content[0] (same approach as the MiniMax branch below).
if hasattr(response, 'output') and hasattr(response.output, 'message'):
return response.output.message.content[0].text
for block in response.output.message.content:
if hasattr(block, 'text'):
return block.text
return ""
elif 'output' in response and 'message' in response['output']:
return response['output']['message']['content'][0]['text']
for block in response['output']['message']['content']:
if 'text' in block:
return block['text']
return ""
else:
return str(response)
+11 -1
View File
@@ -69,6 +69,16 @@ class S3Vectors(VectorStoreBase):
else:
raise
def _distance_to_score(self, raw_distance: Optional[float]) -> Optional[float]:
if raw_distance is None:
return None
# Euclidean distance is unbounded, so 1 - distance would collapse most
# scores to 0. Use a bounded monotonic map instead. Cosine distance is
# in [0, 2], where 1 - distance is already a valid similarity.
if self.distance_metric == "euclidean":
return 1.0 / (1.0 + raw_distance)
return max(0.0, 1.0 - raw_distance)
def _parse_output(self, vectors: List[Dict]) -> List[OutputData]:
results = []
for v in vectors:
@@ -81,7 +91,7 @@ class S3Vectors(VectorStoreBase):
logger.warning(f"Failed to parse metadata for key {v.get('key')}")
payload = {}
raw_distance = v.get("distance")
score = max(0.0, 1.0 - raw_distance) if raw_distance is not None else None
score = self._distance_to_score(raw_distance)
results.append(OutputData(id=v.get("key"), score=score, payload=payload))
return results
+28 -5
View File
@@ -122,7 +122,14 @@ class TurbopufferDB(VectorStoreBase):
dist = row_dict.pop("$dist", None)
row_dict.pop("vector", None)
score = 1 - dist if dist is not None else None
if dist is None:
score = None
elif self.distance_metric == "euclidean_squared":
# $dist is unbounded squared-L2 (lower = closer); map to a bounded
# higher-is-better score, mirroring milvus/baidu. Cosine returns 1 - dist.
score = 1.0 / (1.0 + dist)
else:
score = 1 - dist
results.append(OutputData(
id=row_id,
@@ -131,6 +138,18 @@ class TurbopufferDB(VectorStoreBase):
))
return results
# Maps mem0 filter operators to their Turbopuffer equivalents.
OPERATOR_MAP = {
"eq": "Eq",
"ne": "NotEq",
"gt": "Gt",
"gte": "Gte",
"lt": "Lt",
"lte": "Lte",
"in": "In",
"nin": "NotIn",
}
def _convert_filters(self, filters: Optional[Dict]):
"""
Convert mem0 filters to Turbopuffer filter format.
@@ -143,10 +162,14 @@ class TurbopufferDB(VectorStoreBase):
conditions = []
for key, value in filters.items():
if isinstance(value, dict):
if "gte" in value:
conditions.append((key, "Gte", value["gte"]))
if "lte" in value:
conditions.append((key, "Lte", value["lte"]))
for op, operand in value.items():
tpuf_op = self.OPERATOR_MAP.get(op)
if tpuf_op is None:
raise ValueError(
f"Unsupported filter operator '{op}' for field '{key}'. "
f"Supported operators: {sorted(self.OPERATOR_MAP)}"
)
conditions.append((key, tpuf_op, operand))
else:
conditions.append((key, "Eq", value))
@@ -34,7 +34,7 @@ class OutputData(BaseModel):
class GoogleMatchingEngine(VectorStoreBase):
def __init__(self, **kwargs):
"""Initialize Google Matching Engine client."""
logger.debug("Initializing Google Matching Engine with kwargs: %s", kwargs)
logger.debug("Initializing Google Matching Engine with config keys: %s", sorted(kwargs))
# If collection_name is passed, use it as deployment_index_id if deployment_index_id is not provided
if "collection_name" in kwargs and "deployment_index_id" not in kwargs:
@@ -46,7 +46,7 @@ class GoogleMatchingEngine(VectorStoreBase):
try:
config = GoogleMatchingEngineConfig(**kwargs)
logger.debug("Config created: %s", config.model_dump())
logger.debug("Config created with fields: %s", sorted(config.model_dump()))
logger.debug("Config collection_name: %s", getattr(config, "collection_name", None))
except Exception as e:
logger.error("Failed to validate config: %s", str(e))
+10 -2
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "mem0ai"
version = "2.2.0"
version = "2.2.1"
description = "Long-term memory for AI Agents"
authors = [
{ name = "Mem0", email = "support@mem0.ai" }
@@ -83,7 +83,7 @@ test = [
dev = [
"ruff==0.16.0",
"isort>=5.13.2",
"pytest>=8.2.2",
"pre-commit>=3.5.0",
]
[tool.pytest.ini_options]
@@ -111,6 +111,7 @@ features = [
"vector-stores",
"llms",
"extras",
"dev",
]
[tool.hatch.envs.dev_py_3_11]
@@ -120,6 +121,7 @@ features = [
"vector-stores",
"llms",
"extras",
"dev",
]
[tool.hatch.envs.dev_py_3_12]
@@ -129,6 +131,12 @@ features = [
"vector-stores",
"llms",
"extras",
"dev",
]
[tool.hatch.envs.default]
features = [
"dev",
]
[tool.hatch.envs.default.scripts]
+1 -1
View File
@@ -37,7 +37,7 @@
"@radix-ui/react-tooltip": "^1.0.7",
"@reduxjs/toolkit": "^2.3.0",
"@tanstack/react-table": "^8.17.3",
"axios": "^1.18.0",
"axios": "^1.20.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.0",
"cmdk": "^1.0.0",
+133 -133
View File
@@ -8,14 +8,14 @@ overrides:
form-data@<4.0.6: '>=4.0.6'
prismjs@<1.30.0: '>=1.30.0'
uuid@<11.1.1: '>=11.1.1'
axios@<1.18.0: '>=1.18.0 <2.0.0'
axios@<1.20.0: '>=1.20.0 <2.0.0'
brace-expansion@<1.1.16: '>=1.1.16 <2.0.0'
brace-expansion@>=3.0.0 <5.0.8: '>=5.0.8 <6.0.0'
js-yaml@<3.15.1: '>=3.15.1 <4.0.0'
js-yaml@>=4.0.0 <4.3.1: '>=4.3.1 <5.0.0'
js-yaml@<3.15.2: '>=3.15.2 <4.0.0'
js-yaml@>=4.0.0 <4.3.2: '>=4.3.2 <5.0.0'
postcss@<8.5.18: '>=8.5.18 <9.0.0'
nanoid@<3.3.18: '>=3.3.18 <4.0.0'
sharp@<0.35.0: '>=0.35.0 <0.36.0'
sharp@<0.35.4: '>=0.35.4 <0.36.0'
browserslist@<4.28.7: '>=4.28.7 <5.0.0'
importers:
@@ -98,8 +98,8 @@ importers:
specifier: ^8.17.3
version: 8.21.3(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
axios:
specifier: ^1.18.0
version: 1.18.1
specifier: ^1.20.0
version: 1.20.0
class-variance-authority:
specifier: ^0.7.1
version: 0.7.1
@@ -299,160 +299,160 @@ packages:
resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==}
engines: {node: '>=18'}
'@img/sharp-darwin-arm64@0.35.3':
resolution: {integrity: sha512-RMnFX7YQsMoh7lWfcM4NEHHymBX/rLuKNPVM84XE9ONPcaSCDgE7CHIHpSgPcO2xcRthgBy1HfNO319mwhIAkg==}
'@img/sharp-darwin-arm64@0.35.5':
resolution: {integrity: sha512-QRUlFQ0WxvdWyqqG/WtI3iupfD5rBzmCHXSdPsY91sAtVtTo7Q4cb6zOccZ3gqEqkr0f1As1ehLqmEpDsRf+lg==}
engines: {node: '>=20.9.0'}
cpu: [arm64]
os: [darwin]
'@img/sharp-darwin-x64@0.35.3':
resolution: {integrity: sha512-Xo+5uFBtLN0BKqieTxiFzFPQAUlBbbH5iBKyRX/z1JrbnYsHTfKJnUfL8+p2TPXr1pXqao4eeL4Rl144uDpK9w==}
'@img/sharp-darwin-x64@0.35.5':
resolution: {integrity: sha512-+BR255RhDlpygUpOc/Jdt1nT6DQ3XG/ERo5wbcdOf5Q320dKtPCKPLR1LJs9VGXRaMa8l1uUa0tkCNOXiAxZUw==}
engines: {node: '>=20.9.0'}
cpu: [x64]
os: [darwin]
'@img/sharp-freebsd-wasm32@0.35.3':
resolution: {integrity: sha512-lUxcqWIj2wMQ9BrwNjngcr1gWUr5xgaGThBRqPPalIC2n67Cqj1uPh8NnA/ZhAg8hUbKl+kVHKwgUIwe6ZYPrg==}
'@img/sharp-freebsd-wasm32@0.35.5':
resolution: {integrity: sha512-Y/z91nEZ4uIBX5X3nfTovjU9lHNKFYbL2lpHCLVNmXQK03VIZvXBBt0KxbPGp2SdGSF+2mQU4e+hQaWOt86iAw==}
engines: {node: '>=20.9.0'}
os: [freebsd]
'@img/sharp-libvips-darwin-arm64@1.3.2':
resolution: {integrity: sha512-9J6ypZFpQBj4YnePGoq/S38w6nz+vqg5WZLrLGY4YuSemdMq47GMLBPO42MzwdGwpg/agZ7xzZcFHa48xlywfg==}
'@img/sharp-libvips-darwin-arm64@1.3.4':
resolution: {integrity: sha512-5R89nBYiRdUlSWJxPhO+GVtaXzXSxKnRu/xqMn3KTA3L9EB9Oy/P+Nn2f2vlhPuUdy/Zusb2DarbyTpGCfEDuw==}
cpu: [arm64]
os: [darwin]
'@img/sharp-libvips-darwin-x64@1.3.2':
resolution: {integrity: sha512-m2pW1n6cns9VaubNwsZ+c3CRYjxNQWgJ5gPlnL1nbBcpkBvFm6SCFN5o0psFHI8w9n11NKhFkeEDns98tiqbEw==}
'@img/sharp-libvips-darwin-x64@1.3.4':
resolution: {integrity: sha512-iR2OKH80yi0U+dUplyh3/xdpFvps6YkCwsXenIJxqxR1v9o+xtKTGbS9H7cps+2Vxjc8B1j96p75NmTGjIhtpQ==}
cpu: [x64]
os: [darwin]
'@img/sharp-libvips-linux-arm64@1.3.2':
resolution: {integrity: sha512-dqVSFynCox4C/J8kT16V7SIFAns0IjgLwkvYT7p8LQVmJ5OS5b6tI9IGflxTeuBS//zXeFIUbwt5dwxyZ17cnA==}
'@img/sharp-libvips-linux-arm64@1.3.4':
resolution: {integrity: sha512-Y3dgX/6lE2QhQb+Gxy0WZxfg9MEm/JBjamZpS2IklP7xIQoKN4hzAm7KcMVGtaVDt3neE9OKBC7vAfonA/Lr1A==}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-arm@1.3.2':
resolution: {integrity: sha512-1eMLzy92I4J6rmi4mAT8yC3HxOtniyGELlzGbNMLLeqe052ahFQ0h6LFq+lh5DsDIdYViIDst08abvSbcEdLXQ==}
'@img/sharp-libvips-linux-arm@1.3.4':
resolution: {integrity: sha512-LmRtTsOHuvM2+wlO2Db37dx5MiZhB0FvSunciw48YjdOkZz9KAiRbm8ujeMOA1INqmei5NapFxYEK1D1ZSidmw==}
cpu: [arm]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-ppc64@1.3.2':
resolution: {integrity: sha512-3z0NHDxD6n5I9gc05U1eW1AyRm+Gznzq3naMrthPNqE6oYykcogW0l/jfpJdjYnuNl8R7yI9pNbE1XiUeyq0Aw==}
'@img/sharp-libvips-linux-ppc64@1.3.4':
resolution: {integrity: sha512-Le6boB8Tai0Nis+gIxIpKx68UDVVIqdR8Tin5Yf1z2LJJQLDJvCDRqRu+jC2qCoD+eIomonmOwB4smBRxfVpYQ==}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-riscv64@1.3.2':
resolution: {integrity: sha512-bsb4rI+NldGOsXuej2r8OdSS8+zXDVaCWxyWrcv6kneTOlgAHtZABRzBBCwdsPiD90J4myNJuHpg6kA20ImW/w==}
'@img/sharp-libvips-linux-riscv64@1.3.4':
resolution: {integrity: sha512-aHkkIEHPRdQEegJN20MLmGtxYD9R2wQr3Cwpddnu5+YKMt6Uzax7S9h5gpZTo8wyrGuZSlfQ63OevL5mTyOC7Q==}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-s390x@1.3.2':
resolution: {integrity: sha512-/ABshyj8gCpyIrNXnHn4LorDJ0HHm1VhXPBlxZ8zAtfVPAaSafXPGn+sUSIRiwaSBy0mmFjSjiXI5mkcwdChKQ==}
'@img/sharp-libvips-linux-s390x@1.3.4':
resolution: {integrity: sha512-ra/mB6MikESDUO7Yg+Mi95bFBb9GsObURuhnOv3OqknjGe9sZrG8tCe9q0xSIGrtLgvgw0gKnFWcK4blSgQOuQ==}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-x64@1.3.2':
resolution: {integrity: sha512-ITPEtgffGJ0S6G9dRyw/366tJQqFRcHWPHhC+Stpg3Z8AEMrDrTr2lhdz4f/Y/HMbRh//7Z5mBzEpVdi62Oc3w==}
'@img/sharp-libvips-linux-x64@1.3.4':
resolution: {integrity: sha512-GJ//SSXbnwSDes02umB3nDJLFcQzw8a18V8fyhqr6tV515tOEMdImjjxj1AoafMRz56F3PHgftnj1QEKSU1zkw==}
cpu: [x64]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linuxmusl-arm64@1.3.2':
resolution: {integrity: sha512-zE9EdiUzUmg5mDT5a1rk5fYJ6GWPloTwWBYDS14naqHsL+EaMpDj1AWnpLgh3u0YCORv2Tt50wrcrpYqkP97Kw==}
'@img/sharp-libvips-linuxmusl-arm64@1.3.4':
resolution: {integrity: sha512-hvulFwtjUcagsis6BBxHwGFwWoNZjgYmULGVrZcyfNbjA8hKILbRxGg15/7w5HDyXHXUos/j6baAWqnCyQ2DWA==}
cpu: [arm64]
os: [linux]
libc: [musl]
'@img/sharp-libvips-linuxmusl-x64@1.3.2':
resolution: {integrity: sha512-m0lrLiUt+lBYnCFr8qV/65yMR4E/c7/wf78I5eKTdkEakFAlZ9QlzEM3QIhhAwVeUhLAHLcCq7a7Vszq/oFNZQ==}
'@img/sharp-libvips-linuxmusl-x64@1.3.4':
resolution: {integrity: sha512-6zXKeE/p39I1AmA3cJG35eyBGNqNddLnUXjhwBnsGjFPWqf5VKkDBEqaEkPDoTEtkxwi2vv8Tcr2mDyP4So7Fg==}
cpu: [x64]
os: [linux]
libc: [musl]
'@img/sharp-linux-arm64@0.35.3':
resolution: {integrity: sha512-QgKDspHPnrU+GQ55XPhGwyhC8acLVOOSyAvo1oVfFmrIXLkDNmGWzAfDZ4xK8oSA1qBQrALcHX0G5UZni/SuFQ==}
'@img/sharp-linux-arm64@0.35.5':
resolution: {integrity: sha512-LYVx5JTsOM2CBzmxreh+nl64/3H6Xb09iSLknqH47z2T2DFFxDeFLP5y4dJwe6H7uGQlHPyEEtIqyo3DYsRwdQ==}
engines: {node: '>=20.9.0'}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@img/sharp-linux-arm@0.35.3':
resolution: {integrity: sha512-affVWCTLooy8TSxbDx2qkzuDeaWLNVBA+P//FNBirHsXpP2fuBhk5AuboYUnrDnzoXes8GFjpTx0SBFOCRg+FA==}
'@img/sharp-linux-arm@0.35.5':
resolution: {integrity: sha512-LEaXK2WdXVK5ykcw0buWyPMsmLLL2vpHLD6yrNSW+JGEL3BZPA4tpKN6iaMc4AxTTAoaX/sU1rOL51lcIz48ZQ==}
engines: {node: '>=20.9.0'}
cpu: [arm]
os: [linux]
libc: [glibc]
'@img/sharp-linux-ppc64@0.35.3':
resolution: {integrity: sha512-sMd8rDxmpLOwv/7N44klFjOD5DUO7FLdjiXDI0hoxYaf7Ar262dQIEkosE98bps+5HPLtp/EvNqeqQtOycP/IA==}
'@img/sharp-linux-ppc64@0.35.5':
resolution: {integrity: sha512-QVxAAq8evVRI9ia2vqgwrmWucn5Dfv+JdWzj75pD8omHLPSP7f8p20O8jxzjCcuCEQEOtYOZUmX1hkiZ0kdevA==}
engines: {node: '>=20.9.0'}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@img/sharp-linux-riscv64@0.35.3':
resolution: {integrity: sha512-0Eob78yjlYPfL5vMNWAW55l3R9Y6BQS/gOfe0ZcP9mEz9ohhKSt4im1hayiknXgf8AWrFqMvJcKIdmLmEe7yeQ==}
'@img/sharp-linux-riscv64@0.35.5':
resolution: {integrity: sha512-LtdreXguaavKODPIfzJ4kffx7UNt1omwtK0rch4EBbbSTXPnxWmYSayXdLJw0fJzQ97kHt1gL/yh4tvU+nCyRQ==}
engines: {node: '>=20.9.0'}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@img/sharp-linux-s390x@0.35.3':
resolution: {integrity: sha512-KgAxQ0DxpNOq1rG2t5cgTgShJFGSuU7XO45cqC+1NVOuZnP6tlgZRuSYOfNupGkHID0o3cJOsw4DVeJpMovcGw==}
'@img/sharp-linux-s390x@0.35.5':
resolution: {integrity: sha512-UZasTOFiYzotTsGOCu42BfUzP6Tu6Do/947iRm1RsLKvlllxwGcn4RN27LibGWceix4Y+Pmw3jsnTcCQIgWjqA==}
engines: {node: '>=20.9.0'}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@img/sharp-linux-x64@0.35.3':
resolution: {integrity: sha512-8pqvxubL2PGdhlPy6GLqzDYMUjyRmKAwKHYKixpdJYBUK7PJ0C029XdsnpFIdgRZG68fZiGdHVWcKPvtiPB4cA==}
'@img/sharp-linux-x64@0.35.5':
resolution: {integrity: sha512-SxFtLTeJInhAA9Q836kux2vZNeOBQEx658qvbboZScr0wIARym3IcGmW7KpVD5sbVg0Ojy+udFQdayYIZyoNog==}
engines: {node: '>=20.9.0'}
cpu: [x64]
os: [linux]
libc: [glibc]
'@img/sharp-linuxmusl-arm64@0.35.3':
resolution: {integrity: sha512-Vz0iQjzzcSX3HCbfwFfCSG/9SCIqyO0mH2sXyiHaAYfBk0cRsCWXRyQYX0ovCK/PAQBbTzQ0dsPQHh5MAFL59w==}
'@img/sharp-linuxmusl-arm64@0.35.5':
resolution: {integrity: sha512-9HbMclmI1zlNkFRs3z9/eBtDjfD0sGlrX1z6b1qwmiFY5ElDLh4BC0LPBdVp7z1DXFiKlIcznf+ZlsuZzLxQqg==}
engines: {node: '>=20.9.0'}
cpu: [arm64]
os: [linux]
libc: [musl]
'@img/sharp-linuxmusl-x64@0.35.3':
resolution: {integrity: sha512-6O1NPKcDVj9QEdg7Hx549EX8U0rp6yXQERqru6yRN7fGBn32UvIRJUlWnk+8xDCiG76hXVBbX82NZ/ZKr0euIg==}
'@img/sharp-linuxmusl-x64@0.35.5':
resolution: {integrity: sha512-4KOphqB035HrVdqLZfCgMzzERrQkkzOwRhl4OAkRO1YCldbaFjySXMaK534Mo0V+LndnlJk+sbUyLeU0ULyD1A==}
engines: {node: '>=20.9.0'}
cpu: [x64]
os: [linux]
libc: [musl]
'@img/sharp-wasm32@0.35.3':
resolution: {integrity: sha512-cZ0XkcYGpHZkqW6iCkqTcmUC0CD9DhD5d/qeZlZkfRBn6GnHniZXLUo5+9xw8Iv76YE6LQFN9YNBlKREcCG76w==}
'@img/sharp-wasm32@0.35.5':
resolution: {integrity: sha512-Ptsga1su4tQx+LLF1ECS9U6nz5kmrXKo6XVbtR48Ke3ZRxxgaWBu7IDtEe1quo8hiupwm6WFqxVlXaSf7IINGQ==}
engines: {node: '>=20.9.0'}
'@img/sharp-webcontainers-wasm32@0.35.3':
resolution: {integrity: sha512-2rnq7bX3NzeR2T4YWgz8qiG4h3TSdMe+vN1iQXpJleSJ3SM5zQ8Fy2SyyXAWlbxpEZ2Y+Z4u1BePgJEYbSy80Q==}
'@img/sharp-webcontainers-wasm32@0.35.5':
resolution: {integrity: sha512-hfhF/FmoQyTUkA0bIKFOtw536BQSeBMe6BF6QyWlrPxT754+TFLaZ7sKKTfvvM0yJgKgaYTwnFCIZ/GuDw5SUA==}
engines: {node: '>=20.9.0'}
cpu: [wasm32]
'@img/sharp-win32-arm64@0.35.3':
resolution: {integrity: sha512-4bPwFdMbeC4JQ8L8LOyWp6nsHcboP5fxkp6iPOXz2Vg49R42TuMs2whkJ5OAP4/Ul035qOzy0AecOF9VOscn4w==}
'@img/sharp-win32-arm64@0.35.5':
resolution: {integrity: sha512-X4t7g+7ZA5DKblCBEXGjUqqemj4vczING/5viFwAL8h4N3qYeyjwdCvRLHi4EdOUI+2Z7UFlp1VM+p/AuEtm6Q==}
engines: {node: '>=20.9.0'}
cpu: [arm64]
os: [win32]
'@img/sharp-win32-ia32@0.35.3':
resolution: {integrity: sha512-r53mXsBN6lFUDiST764SvgwUdHAqM4rPAiDzAmf4fLoB6X/rkfyTrLCg6+g17wJJiCmB3JYgHuUldCWUIRFSXw==}
'@img/sharp-win32-ia32@0.35.5':
resolution: {integrity: sha512-5Zm82LoBc43nhwNybZlG7Y1KO//Zhsn306fQl29ZOuStHLGTo3BWL83q3cznX0poxSAMuYL1On/BHBxkBeKr6A==}
engines: {node: ^20.9.0}
cpu: [ia32]
os: [win32]
'@img/sharp-win32-x64@0.35.3':
resolution: {integrity: sha512-D4y1vNeZrIIJCN+uHaWVtH86B+aCrdMYYjicy9pXHvbGZeGYLLSd3wdVuC37FxVXlU1ARsk84eKWfWMXGYEqvA==}
'@img/sharp-win32-x64@0.35.5':
resolution: {integrity: sha512-x76eH0vEiHlcMQu8Y8IenntaACtddpT6W0wmXtWrnKcnKI7ME5DdgqhAD6SEWOEl1v2zDvkZDhFA9KnURwpfqg==}
engines: {node: '>=20.9.0'}
cpu: [x64]
os: [win32]
@@ -1494,8 +1494,8 @@ packages:
resolution: {integrity: sha512-s7iGf5GaVMxEG0ENN9x+xTr7GFZCb1ZP/1uATUpCEK2X78nDB3RwbtFCo9pGAf9ru+VwoQ464DkaLEeRM08wJA==}
engines: {node: '>=4'}
axios@1.18.1:
resolution: {integrity: sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==}
axios@1.20.0:
resolution: {integrity: sha512-r8aOh8j9cGKpgQAqpzrUHnSIc6a59Y3Xf/cv8sy1DrHCkZHzQGEuoq1tARk6qSyDdtQGSDgpb9kFlruzPvrgwg==}
axobject-query@4.1.0:
resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==}
@@ -2292,8 +2292,8 @@ packages:
js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
js-yaml@4.3.1:
resolution: {integrity: sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==}
js-yaml@4.3.2:
resolution: {integrity: sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==}
hasBin: true
json-buffer@3.0.1:
@@ -2832,8 +2832,8 @@ packages:
resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==}
engines: {node: '>= 0.4'}
sharp@0.35.3:
resolution: {integrity: sha512-ej0zVHuZGHCiABXcNxeYhpRnPNPAcvbG8RMdBAhDAxLKkCRVSpK3Iyu7qbqw3JMzoj0REeM6f3tJLtVwl0023Q==}
sharp@0.35.5:
resolution: {integrity: sha512-Ywn4OnzGukp7CDMrp08RQ50YKmuwG47brZgIVPTvBaaAfQlRlygrRqSrxdCiL9M+LlzLBiJ68IR1QqvzHyjC7g==}
engines: {node: '>=20.9.0'}
peerDependencies:
'@types/node': '*'
@@ -3174,7 +3174,7 @@ snapshots:
globals: 13.24.0
ignore: 5.3.2
import-fresh: 3.3.1
js-yaml: 4.3.1
js-yaml: 4.3.2
minimatch: 3.1.5
strip-json-comments: 3.1.1
transitivePeerDependencies:
@@ -3218,108 +3218,108 @@ snapshots:
'@img/colour@1.1.0':
optional: true
'@img/sharp-darwin-arm64@0.35.3':
'@img/sharp-darwin-arm64@0.35.5':
optionalDependencies:
'@img/sharp-libvips-darwin-arm64': 1.3.2
'@img/sharp-libvips-darwin-arm64': 1.3.4
optional: true
'@img/sharp-darwin-x64@0.35.3':
'@img/sharp-darwin-x64@0.35.5':
optionalDependencies:
'@img/sharp-libvips-darwin-x64': 1.3.2
'@img/sharp-libvips-darwin-x64': 1.3.4
optional: true
'@img/sharp-freebsd-wasm32@0.35.3':
'@img/sharp-freebsd-wasm32@0.35.5':
dependencies:
'@img/sharp-wasm32': 0.35.3
'@img/sharp-wasm32': 0.35.5
optional: true
'@img/sharp-libvips-darwin-arm64@1.3.2':
'@img/sharp-libvips-darwin-arm64@1.3.4':
optional: true
'@img/sharp-libvips-darwin-x64@1.3.2':
'@img/sharp-libvips-darwin-x64@1.3.4':
optional: true
'@img/sharp-libvips-linux-arm64@1.3.2':
'@img/sharp-libvips-linux-arm64@1.3.4':
optional: true
'@img/sharp-libvips-linux-arm@1.3.2':
'@img/sharp-libvips-linux-arm@1.3.4':
optional: true
'@img/sharp-libvips-linux-ppc64@1.3.2':
'@img/sharp-libvips-linux-ppc64@1.3.4':
optional: true
'@img/sharp-libvips-linux-riscv64@1.3.2':
'@img/sharp-libvips-linux-riscv64@1.3.4':
optional: true
'@img/sharp-libvips-linux-s390x@1.3.2':
'@img/sharp-libvips-linux-s390x@1.3.4':
optional: true
'@img/sharp-libvips-linux-x64@1.3.2':
'@img/sharp-libvips-linux-x64@1.3.4':
optional: true
'@img/sharp-libvips-linuxmusl-arm64@1.3.2':
'@img/sharp-libvips-linuxmusl-arm64@1.3.4':
optional: true
'@img/sharp-libvips-linuxmusl-x64@1.3.2':
'@img/sharp-libvips-linuxmusl-x64@1.3.4':
optional: true
'@img/sharp-linux-arm64@0.35.3':
'@img/sharp-linux-arm64@0.35.5':
optionalDependencies:
'@img/sharp-libvips-linux-arm64': 1.3.2
'@img/sharp-libvips-linux-arm64': 1.3.4
optional: true
'@img/sharp-linux-arm@0.35.3':
'@img/sharp-linux-arm@0.35.5':
optionalDependencies:
'@img/sharp-libvips-linux-arm': 1.3.2
'@img/sharp-libvips-linux-arm': 1.3.4
optional: true
'@img/sharp-linux-ppc64@0.35.3':
'@img/sharp-linux-ppc64@0.35.5':
optionalDependencies:
'@img/sharp-libvips-linux-ppc64': 1.3.2
'@img/sharp-libvips-linux-ppc64': 1.3.4
optional: true
'@img/sharp-linux-riscv64@0.35.3':
'@img/sharp-linux-riscv64@0.35.5':
optionalDependencies:
'@img/sharp-libvips-linux-riscv64': 1.3.2
'@img/sharp-libvips-linux-riscv64': 1.3.4
optional: true
'@img/sharp-linux-s390x@0.35.3':
'@img/sharp-linux-s390x@0.35.5':
optionalDependencies:
'@img/sharp-libvips-linux-s390x': 1.3.2
'@img/sharp-libvips-linux-s390x': 1.3.4
optional: true
'@img/sharp-linux-x64@0.35.3':
'@img/sharp-linux-x64@0.35.5':
optionalDependencies:
'@img/sharp-libvips-linux-x64': 1.3.2
'@img/sharp-libvips-linux-x64': 1.3.4
optional: true
'@img/sharp-linuxmusl-arm64@0.35.3':
'@img/sharp-linuxmusl-arm64@0.35.5':
optionalDependencies:
'@img/sharp-libvips-linuxmusl-arm64': 1.3.2
'@img/sharp-libvips-linuxmusl-arm64': 1.3.4
optional: true
'@img/sharp-linuxmusl-x64@0.35.3':
'@img/sharp-linuxmusl-x64@0.35.5':
optionalDependencies:
'@img/sharp-libvips-linuxmusl-x64': 1.3.2
'@img/sharp-libvips-linuxmusl-x64': 1.3.4
optional: true
'@img/sharp-wasm32@0.35.3':
'@img/sharp-wasm32@0.35.5':
dependencies:
'@emnapi/runtime': 1.11.3
optional: true
'@img/sharp-webcontainers-wasm32@0.35.3':
'@img/sharp-webcontainers-wasm32@0.35.5':
dependencies:
'@img/sharp-wasm32': 0.35.3
'@img/sharp-wasm32': 0.35.5
optional: true
'@img/sharp-win32-arm64@0.35.3':
'@img/sharp-win32-arm64@0.35.5':
optional: true
'@img/sharp-win32-ia32@0.35.3':
'@img/sharp-win32-ia32@0.35.5':
optional: true
'@img/sharp-win32-x64@0.35.3':
'@img/sharp-win32-x64@0.35.5':
optional: true
'@jridgewell/gen-mapping@0.3.13':
@@ -4367,7 +4367,7 @@ snapshots:
axe-core@4.12.1: {}
axios@1.18.1:
axios@1.20.0:
dependencies:
follow-redirects: 1.16.0
form-data: 4.0.6
@@ -4897,7 +4897,7 @@ snapshots:
imurmurhash: 0.1.4
is-glob: 4.0.3
is-path-inside: 3.0.3
js-yaml: 4.3.1
js-yaml: 4.3.2
json-stable-stringify-without-jsonify: 1.0.1
levn: 0.4.1
lodash.merge: 4.6.2
@@ -5310,7 +5310,7 @@ snapshots:
js-tokens@4.0.0: {}
js-yaml@4.3.1:
js-yaml@4.3.2:
dependencies:
argparse: 2.0.1
@@ -5439,7 +5439,7 @@ snapshots:
'@next/swc-linux-x64-musl': 15.5.24
'@next/swc-win32-arm64-msvc': 15.5.24
'@next/swc-win32-x64-msvc': 15.5.24
sharp: 0.35.3(@types/node@22.5.4)
sharp: 0.35.5(@types/node@22.5.4)
transitivePeerDependencies:
- '@babel/core'
- '@types/node'
@@ -5852,37 +5852,37 @@ snapshots:
es-errors: 1.3.0
es-object-atoms: 1.1.2
sharp@0.35.3(@types/node@22.5.4):
sharp@0.35.5(@types/node@22.5.4):
dependencies:
'@img/colour': 1.1.0
detect-libc: 2.1.2
semver: 7.8.5
optionalDependencies:
'@img/sharp-darwin-arm64': 0.35.3
'@img/sharp-darwin-x64': 0.35.3
'@img/sharp-freebsd-wasm32': 0.35.3
'@img/sharp-libvips-darwin-arm64': 1.3.2
'@img/sharp-libvips-darwin-x64': 1.3.2
'@img/sharp-libvips-linux-arm': 1.3.2
'@img/sharp-libvips-linux-arm64': 1.3.2
'@img/sharp-libvips-linux-ppc64': 1.3.2
'@img/sharp-libvips-linux-riscv64': 1.3.2
'@img/sharp-libvips-linux-s390x': 1.3.2
'@img/sharp-libvips-linux-x64': 1.3.2
'@img/sharp-libvips-linuxmusl-arm64': 1.3.2
'@img/sharp-libvips-linuxmusl-x64': 1.3.2
'@img/sharp-linux-arm': 0.35.3
'@img/sharp-linux-arm64': 0.35.3
'@img/sharp-linux-ppc64': 0.35.3
'@img/sharp-linux-riscv64': 0.35.3
'@img/sharp-linux-s390x': 0.35.3
'@img/sharp-linux-x64': 0.35.3
'@img/sharp-linuxmusl-arm64': 0.35.3
'@img/sharp-linuxmusl-x64': 0.35.3
'@img/sharp-webcontainers-wasm32': 0.35.3
'@img/sharp-win32-arm64': 0.35.3
'@img/sharp-win32-ia32': 0.35.3
'@img/sharp-win32-x64': 0.35.3
'@img/sharp-darwin-arm64': 0.35.5
'@img/sharp-darwin-x64': 0.35.5
'@img/sharp-freebsd-wasm32': 0.35.5
'@img/sharp-libvips-darwin-arm64': 1.3.4
'@img/sharp-libvips-darwin-x64': 1.3.4
'@img/sharp-libvips-linux-arm': 1.3.4
'@img/sharp-libvips-linux-arm64': 1.3.4
'@img/sharp-libvips-linux-ppc64': 1.3.4
'@img/sharp-libvips-linux-riscv64': 1.3.4
'@img/sharp-libvips-linux-s390x': 1.3.4
'@img/sharp-libvips-linux-x64': 1.3.4
'@img/sharp-libvips-linuxmusl-arm64': 1.3.4
'@img/sharp-libvips-linuxmusl-x64': 1.3.4
'@img/sharp-linux-arm': 0.35.5
'@img/sharp-linux-arm64': 0.35.5
'@img/sharp-linux-ppc64': 0.35.5
'@img/sharp-linux-riscv64': 0.35.5
'@img/sharp-linux-s390x': 0.35.5
'@img/sharp-linux-x64': 0.35.5
'@img/sharp-linuxmusl-arm64': 0.35.5
'@img/sharp-linuxmusl-x64': 0.35.5
'@img/sharp-webcontainers-wasm32': 0.35.5
'@img/sharp-win32-arm64': 0.35.5
'@img/sharp-win32-ia32': 0.35.5
'@img/sharp-win32-x64': 0.35.5
'@types/node': 22.5.4
optional: true
+4 -4
View File
@@ -5,12 +5,12 @@ overrides:
"form-data@<4.0.6": ">=4.0.6"
"prismjs@<1.30.0": ">=1.30.0"
"uuid@<11.1.1": ">=11.1.1"
"axios@<1.18.0": ">=1.18.0 <2.0.0"
"axios@<1.20.0": ">=1.20.0 <2.0.0"
"brace-expansion@<1.1.16": ">=1.1.16 <2.0.0"
"brace-expansion@>=3.0.0 <5.0.8": ">=5.0.8 <6.0.0"
"js-yaml@<3.15.1": ">=3.15.1 <4.0.0"
"js-yaml@>=4.0.0 <4.3.1": ">=4.3.1 <5.0.0"
"js-yaml@<3.15.2": ">=3.15.2 <4.0.0"
"js-yaml@>=4.0.0 <4.3.2": ">=4.3.2 <5.0.0"
"postcss@<8.5.18": ">=8.5.18 <9.0.0"
"nanoid@<3.3.18": ">=3.3.18 <4.0.0"
"sharp@<0.35.0": ">=0.35.0 <0.36.0"
"sharp@<0.35.4": ">=0.35.4 <0.36.0"
"browserslist@<4.28.7": ">=4.28.7 <5.0.0"
+13 -9
View File
@@ -50,16 +50,20 @@ Everything else, meaning full step mechanics, document templates, and verbatim s
Current sizes, longest first:
```
mem0/references/use-cases.md 720 reference, on demand
mem0-cli/references/command-reference.md 694 reference, on demand
mem0/client/python.md 487 reference, on demand
mem0-integrate/references/pipeline.md 375 reference, on demand
mem0-test-integration/SKILL.md 368 entry point, under budget
mem0-cli/references/command-reference.md 757 reference, on demand
mem0/references/use-cases.md 721 reference, on demand
mem0/client/python.md 522 reference, on demand
mem0/client/node.md 507 reference, on demand
mem0-cli/references/workflows.md 446 reference, on demand
mem0/references/features.md 406 reference, on demand
mem0/references/integration-patterns.md 403 reference, on demand
mem0-test-integration/SKILL.md 386 entry point, under budget
mem0-integrate/references/pipeline.md 380 reference, on demand
mem0-integrate/SKILL.md 220 entry point
mem0/SKILL.md 193 entry point
mem0-vercel-ai-sdk/SKILL.md 192 entry point
mem0-cli/SKILL.md 169 entry point
mem0-oss-to-platform/SKILL.md 120 entry point
mem0-vercel-ai-sdk/SKILL.md 210 entry point
mem0/SKILL.md 194 entry point
mem0-cli/SKILL.md 170 entry point
mem0-oss-to-platform/SKILL.md 132 entry point
```
`mem0-integrate` is the one skill that needed splitting: it was 620 lines, now
+1 -1
View File
@@ -6,7 +6,7 @@ Manage memories from the terminal using the [Mem0 CLI](https://docs.mem0.ai/plat
When installed, Claude can:
- **Run mem0 commands** correctly in your terminal (add, search, list, get, update, delete, import, config, init, status, entity, event)
- **Run mem0 commands** correctly in your terminal (add, search, list, get, update, delete, import, config, init, identify, whoami, status, entity, event, agent-rush, version, help)
- **Construct complex invocations** with the right flags, scoping, filters, and output formats
- **Pipe and script** mem0 commands in shell workflows, CI/CD pipelines, and agent loops
- **Debug issues** like missing API keys, entity scoping conflicts, and async processing delays
+16 -15
View File
@@ -12,9 +12,10 @@ description: >
license: Apache-2.0
metadata:
author: mem0ai
version: "1.1.0"
version: "1.2.0"
category: ai-memory
tags: "cli, terminal, memory, ai, command-line"
mem0_tested_versions: "mem0-cli (PyPI) >=0.2.13,<0.3.0; @mem0/cli (npm) >=0.2.14,<0.3.0; mem0ai (PyPI) >=2.0.0,<3.0.0; mem0ai (npm) >=3.0.0,<4.0.0"
compatibility: Node.js 18+ (npm install -g @mem0/cli) or Python 3.10+ (pip install mem0-cli), MEM0_API_KEY env var
---
@@ -34,7 +35,7 @@ npm install -g @mem0/cli
pip install mem0-cli
```
Both packages install a `mem0` binary with identical commands, options, and output formats.
Both packages install a `mem0` binary with the same commands, options, and output formats (see [Node and Python Differences](#node-and-python-differences) for the exceptions).
## Setup
@@ -105,7 +106,7 @@ mem0 delete --all --user-id alice --force
## Agent / JSON Mode
Use `--json` or `--agent` to get structured output suitable for LLM consumption. Every command wraps its response in a standard envelope:
Use `--json` or `--agent` to get structured output suitable for LLM consumption. Place the flag before the subcommand (`mem0 --json search "q"`), which works in both CLIs. Data commands (add, search, list, get, update, delete, import, config, entity, event, status) wrap their response in a standard envelope:
```json
{
@@ -114,7 +115,6 @@ Use `--json` or `--agent` to get structured output suitable for LLM consumption.
"duration_ms": 245,
"scope": { "user_id": "alice" },
"count": 3,
"error": null,
"data": [
{ "id": "mem-abc", "memory": "User prefers dark mode", "score": 0.92 }
]
@@ -126,30 +126,31 @@ On error:
{
"status": "error",
"command": "search",
"error": "Authentication failed. Your API key may be invalid or expired.",
"error": "Invalid or expired API key.",
"data": null
}
```
The `--agent` flag is an alias for `--json`. Both write spinners and progress to stderr so stdout is always clean, parseable JSON.
The `--agent` flag is an alias for `--json` (except on `mem0 init`, where `--agent` is the Agent Mode bootstrap flag). Both write spinners and progress to stderr so stdout is clean, parseable JSON (the one exception is Node `import`, see below). Optional keys: `duration_ms`, `scope`, `count` appear only where relevant, and `mem0_notice` appears when the platform flags an unclaimed Agent Mode account.
## Node and Python Parity
## Node and Python Differences
Both the Node.js (`@mem0/cli`) and Python (`mem0-cli`) CLIs are implemented from the same specification (`cli-spec.json`). They share:
Both the Node.js (`@mem0/cli`) and Python (`mem0-cli`) CLIs share the command set, flags, entity ID resolution, filter building, and the JSON envelope. Choose whichever runtime you already have installed. Known differences:
- Identical command names, arguments, and flags
- Identical output formats (text, json, table, quiet)
- Identical entity ID resolution, graph tri-state, filter building
- Identical error messages and exit codes
Choose whichever runtime you already have installed. The behavior is the same.
- **`--json` / `--agent` placement:** Python accepts the flag anywhere on the command line. Node reads it only as a global option, so put it before the subcommand (`mem0 --json list`). `mem0 init --json` and `mem0 help --json` work after the subcommand in both. For the Agent Mode bootstrap, Node starts it only from `init --agent` or `init --json` (a root-level `mem0 --json init` or `mem0 --agent init` does not start it, and without an agent runtime env var it fails with the non-TTY error), while Python also accepts those root-level forms. `mem0 init --agent --json` works in both.
- **`--limit`:** `mem0 search --limit` is a Python-only alias for `--top-k`.
- **Agent-mode `delete --all` data:** Python returns `{"deleted": true}` (plus scope for `--project`); Node returns the raw API result.
- **`import` JSON output:** Python prints the envelope with `scope`. Node omits `scope` and writes the `Importing memories... n/n` progress line to stdout before the JSON, so only Python's output pipes cleanly to `jq`.
- **Message text:** The empty-search error (`Search query cannot be empty.` in Python, `No query provided...` in Node) and the delete dry-run footers differ slightly.
## Common Edge Cases
- **Async processing delay:** After `mem0 add`, memories process asynchronously. Wait 2-3 seconds before searching for newly added content. Use `mem0 event list` to check processing status.
- **`--all` vs `--entity` delete modes:** `mem0 delete --all -u alice` deletes all memories for user alice. `mem0 delete --entity -u alice` deletes the entity itself AND all its memories (cascade). These are mutually exclusive modes.
- **`--dry-run` in `--json`/`--agent` mode:** `mem0 --json delete --all --dry-run` still requires `--force`, then prints nothing and deletes nothing (exit 0). Node also prints nothing for single and `--entity` dry runs. Use text mode to see the preview.
- **`--dry-run` does not protect `--all --project`:** `mem0 delete --all --project --dry-run` ignores the flag and deletes every memory in the project. Never use `--dry-run` to preview a project-wide delete.
- **Entity ID resolution:** If you pass any explicit scope flag (e.g. `--user-id`), the CLI uses ONLY the explicit IDs and ignores config defaults. If no scope flags are given, all configured defaults apply.
- **Stdin detection:** When no text argument is provided and input is piped (not a TTY), the CLI reads from stdin. Works with `add`, `search`, and `update`.
- **Stdin detection:** When no text argument is provided and stdin is a pipe or a redirected file (a plain non-TTY is not enough), the CLI reads from stdin. Works with `add`, `search`, and `update`. In `--json`/`--agent` mode `add` never reads stdin (Python also skips it for `search` and `update`), so pass the text as an argument there.
## References
+135 -72
View File
@@ -1,20 +1,25 @@
# Mem0 CLI Command Reference
Complete reference for every command, argument, flag, and output mode in the mem0 CLI. Both the Node.js (`@mem0/cli`) and Python (`mem0-cli`) implementations are identical in behavior.
Complete reference for every command, argument, flag, and output mode in the mem0 CLI. Both the Node.js (`@mem0/cli`) and Python (`mem0-cli`) implementations share the same commands and flags. Where they differ, the difference is called out inline.
---
## Global Options
These options are available on every command:
Only these two options are global:
| Flag | Type | Description |
|------|------|-------------|
| `--json` / `--agent` | boolean | Agent mode: wrap output in a structured JSON envelope on stdout. Spinners and progress go to stderr (except Node `import`, see its section). Put it before the subcommand (`mem0 --json list`); Python also accepts it anywhere. On `mem0 init`, `--agent` is the Agent Mode bootstrap flag instead (use `--json` there, after the subcommand). |
| `--version` | boolean | Print version and exit. |
These options are declared per command (not global) on `add`, `search`, `get`, `list`, `update`, `delete`, `import`, `status`, `entity list`, `entity delete`, `event list` and `event status`. `config show` takes only `-o`, and `init` takes only `--api-key`.
| Flag | Type | Description |
|------|------|-------------|
| `--json` / `--agent` | boolean | Agent mode: wrap all output in a structured JSON envelope on stdout. Spinners and progress go to stderr. |
| `-o, --output <format>` | string | Output format. Supported values vary per command (see matrix below). |
| `--api-key <key>` | string | Override the API key for this invocation. Takes precedence over env var and config file. |
| `--base-url <url>` | string | Override the API base URL (default: `https://api.mem0.ai`). |
| `--version` | boolean | Print version and exit. |
---
@@ -41,11 +46,12 @@ Interactive setup wizard. Configures API key and default user ID.
**Behavior:**
- If `~/.mem0/config.json` already exists with an API key, warns and asks for confirmation (or errors in non-TTY unless `--force` is set).
- **Email login flow** (`--email`): sends a 6-digit code to the email via `POST /api/v1/auth/email_code/`. If `--code` is also given, verifies immediately. On success, saves API key, org_id, and project_id. Cannot be combined with `--api-key`.
- **API key flow**: if both `--api-key` and `--user-id` are given, runs fully non-interactively. Otherwise prompts for missing values.
- **Agent Mode flow** (`--agent`): POSTs to `/api/v1/auth/agent_mode/`, mints a shadow API key in <5s with no email required. Pass `--agent-caller <your-name>` to attribute the signup to your AI agent identity. If omitted, run `mem0 identify <your-name>` afterward.
- In non-TTY without sufficient flags, prints a usage hint and exits with error.
- If `~/.mem0/config.json` already exists with an API key, warns and asks for confirmation. In non-TTY it errors with "Existing config would be overwritten." unless `--force` is set. The Agent Mode path (below) runs before this check.
- **Email login flow** (`--email`): sends a 6-digit code to the email via `POST /api/v1/auth/email_code/`. If `--code` is also given, skips sending and verifies immediately via `/api/v1/auth/email_code/verify/`. In non-TTY without `--code`, the code is sent and the command then errors; re-run with `--code`. On success, saves the API key, `user_email`, and `created_via: "email"`, and sets the default user ID to `--user-id`, else `$USER`/`$USERNAME`, else `mem0-cli`. Cannot be combined with `--api-key`. `--code` without `--email` is an error.
- **Claim flow** (`--email` while the existing config is an unclaimed Agent Mode key): runs the same code flow but claims the existing key to that email. The API key value does not change and memories are kept.
- **API key flow**: if both `--api-key` and `--user-id` are given, runs fully non-interactively (and validates the key against the API). In non-TTY, `--api-key` alone is enough (the user ID defaults to `$USER`/`$USERNAME`/`mem0-cli`). In a TTY with no flags, prompts for the auth method (email or API key), then for the missing values.
- **Agent Mode flow** (`init --agent`, `init --json`, or an agent runtime env var such as `CLAUDECODE` or `CURSOR_AGENT`, with no `--api-key`/`--email`; Python also enters it on a global `mem0 --json init` or `mem0 --agent init`, Node does not): first reuses a valid `MEM0_API_KEY` or a valid key already in config (no new key is minted). Otherwise POSTs to `/api/v1/auth/agent_mode/` and mints a shadow API key in <5s with no email required; the generated `user_<slug>` becomes `defaults.user_id`. Limited to 5 signups per day per network. Pass `--agent-caller <your-name>` to attribute the signup to your AI agent identity. If omitted, run `mem0 identify <your-name>` afterward.
- In non-TTY without `--api-key`, `--email`, or an agent signal, prints "Non-interactive terminal detected and --api-key is required." and exits with error.
**Examples:**
```bash
@@ -61,16 +67,16 @@ mem0 init --agent --agent-caller claude-code # AI agent self-identifies during
### `mem0 identify`
Tag your active Agent Mode key with the AI agent that's using it. Run this once after `mem0 init --agent` if you didn't pass `--agent-caller`. Idempotent — re-running just overwrites the value.
Tag your active Agent Mode key with the AI agent that's using it. Run this once after `mem0 init --agent` if you didn't pass `--agent-caller`. Idempotent: re-running just overwrites the value.
**Usage:** `mem0 identify <name>`
**Argument:** `<name>` — the AI agent identity (e.g. `claude-code`, `cursor`, `codex`, `cline`, `aider`, or a custom string).
**Argument:** `<name>`: the AI agent identity (e.g. `claude-code`, `cursor`, `codex`, `cline`, `aider`, or a custom string).
**Behavior:**
- PATCHes `/api/v1/auth/agent_mode/caller/` with `Authorization: Token <current-api-key>` and body `{agent_caller}`.
- Only works on unclaimed agent-mode keys (`platform.agent_mode=true` in config).
- Only works on unclaimed agent-mode keys (`platform.agent_mode=true` in config). Errors with "No API key configured." if no key is set, or "This command only works on unclaimed agent-mode keys." otherwise.
- Backend sanitizes the value: lowercases, drops anything outside `[a-z0-9._/-]`, truncates to 32 chars.
**Examples:**
@@ -82,6 +88,45 @@ mem0 identify my-custom-bot
---
### `mem0 whoami`
Print your AGENTRUSH identifier (`platform.default_user_id` from config). Errors with "No default_user_id found. Run `mem0 init --agent` first." if none is stored.
**Usage:** `mem0 whoami`
---
### `mem0 agent-rush add|search`
Commands for the AGENTRUSH event game. Memories are public to other players, so never include real names, emails, secrets, or PII.
**Usage:** `mem0 agent-rush add <content>` and `mem0 agent-rush search <query>`
- `add`: content must be 50-1000 characters with no URLs. The server requires 3 searches before adding and caps each key at 3 lifetime searches and 3 lifetime adds.
- Requires an API key from config or `MEM0_API_KEY` (otherwise errors with "Not initialized. Run `mem0 init --agent` first."). Node joins unquoted words into one string; Python takes a single quoted argument.
**Examples:**
```bash
mem0 agent-rush search "constraint satisfaction"
mem0 agent-rush add "I enjoy solving constraint-satisfaction problems and writing small solvers."
```
---
### `mem0 version`
Print the CLI version. `mem0 --version` does the same.
---
### `mem0 help`
**Usage:** `mem0 help [--json]`
Prints the command overview. `--json` (or global `--json`/`--agent`) prints a machine-readable command spec. In both CLIs this spec is hand-maintained and can lag behind the real option list, so trust `mem0 <command> --help` and this reference over it.
---
### `mem0 add`
Add a memory from text, messages, file, or stdin.
@@ -106,10 +151,17 @@ Add a memory from text, messages, file, or stdin.
| `-f, --file <path>` | path | - | Read messages from a JSON file. |
| `-m, --metadata <json>` | string | - | Custom metadata as JSON object (e.g. `'{"source":"cli"}'`). |
| `--no-infer` | boolean | false | Skip inference; store the text verbatim. |
| `--categories <cats>` | string | - | Categories as JSON array or comma-separated string. |
| `--expires <date>` | string | - | Expiration date (YYYY-MM-DD). Must be in the future. |
| `--immutable` | boolean | false | Accepted but has no effect on v3: the memory can still be updated and no marker is stored. |
| `--custom-instructions <text>` | string | - | Custom instructions for fact extraction. |
| `--agent-custom-instructions <text>` | string | - | Extraction instructions for agent-scoped memories, overriding the project setting. |
| `--custom-categories <json>` | string | - | Custom categories as a JSON array of `{name: description}` objects. |
| `--structured-data-schema <json>` | string | - | Schema for structured data extraction, as JSON. |
| `--timestamp <unix>` | integer | - | Unix timestamp for the memory. |
| `--categories <value>` | string | - | Rejected with an error. Use `--custom-categories` instead. |
| `-o, --output <fmt>` | string | `text` | Output format: `text`, `json`, `quiet`. |
**Input priority:** `--file` > `--messages` > text argument > stdin (if piped and no text).
**Input priority:** `--file` > `--messages` > text argument > stdin (if piped or redirected, no text, and not in `--json`/`--agent` mode).
Text content is wrapped as `[{"role": "user", "content": "<text>"}]` before sending to the API. Messages from `--messages` or `--file` are sent as-is.
@@ -130,9 +182,8 @@ mem0 add "allergic to nuts" -u alice -m '{"source":"onboarding"}'
mem0 add --messages '[{"role":"user","content":"I like Python"}]' -u alice
mem0 add --file conversation.json -u alice -o json
echo "I prefer dark mode" | mem0 add -u alice
mem0 add "temporary note" -u alice --expires 2025-12-31
mem0 add "important fact" -u alice --immutable
mem0 add "uses vim" -u alice --categories "tools,preferences"
mem0 add "temporary note" -u alice --expires 2027-12-31
mem0 add "uses vim" -u alice --custom-categories '[{"tools":"Editors and developer tooling"}]'
```
---
@@ -147,7 +198,7 @@ Search memories by semantic query.
| Name | Type | Required | Description |
|------|------|----------|-------------|
| `query` | string | Yes | The search query. Falls back to stdin if piped. |
| `query` | string | Yes | The search query. Falls back to stdin if piped or redirected (Python skips this in `--json`/`--agent` mode; Node does not). |
**Options:**
@@ -157,11 +208,15 @@ Search memories by semantic query.
| `--agent-id <id>` | string | - | Filter by agent. |
| `--app-id <id>` | string | - | Filter by app. |
| `--run-id <id>` | string | - | Filter by run. |
| `-k, --top-k, --limit <n>` | integer | 10 | Maximum number of results to return. |
| `--threshold <score>` | float | 0.1 | Minimum similarity score (0.0 to 1.0). |
| `-k, --top-k <n>` | integer | 10 | Maximum number of results to return (must be >= 1). Python also accepts `--limit` as an alias. |
| `--threshold <score>` | float | 0.3 | Minimum similarity score (0.0 to 1.0), applied before hybrid score blending, so a returned item's displayed `score` can be lower than this value. |
| `--rerank` | boolean | false | Enable reranking for improved relevance (Platform only). |
| `--filter <json>` | string | - | Advanced filter expression as JSON (AND/OR operators). |
| `--fields <list>` | string | - | Comma-separated list of fields to return. |
| `--keyword` | boolean | false | Sent to the API as `keyword_search` but not applied by v3 search, which always blends keyword matching into hybrid scoring. |
| `--filter <json>` | string | - | Advanced filter expression as JSON. If it contains `AND` or `OR` it is sent as-is and entity IDs (including config defaults) are not merged in. |
| `--fields <list>` | string | - | Comma-separated list of fields to return. Sent to the API but not applied by v3 search. |
| `--show-expired` | boolean | false | Include expired memories. |
| `--reference-date <date>` | string | - | Reference date for relative queries (YYYY-MM-DD or unix timestamp). |
| `--latest-only` | boolean | false | Only return the latest version of each memory. |
| `-o, --output <fmt>` | string | `text` | Output format: `text`, `json`, `table`. |
**Examples:**
@@ -171,6 +226,8 @@ mem0 search "tools" -u alice -o json -k 5
mem0 search "dietary restrictions" -u alice --threshold 0.5
mem0 search "project setup" -u alice --rerank
mem0 search "preferences" -u alice --filter '{"categories":{"contains":"food"}}'
mem0 search "invoices" -u alice --filter '{"AND":[{"user_id":"alice"},{"categories":{"in":["work"]}}]}'
mem0 search "plans" -u alice --latest-only
echo "preferences" | mem0 search -u alice
```
@@ -221,6 +278,8 @@ List memories with optional filters and pagination.
| `--category <name>` | string | - | Filter by category. |
| `--after <date>` | string | - | Created after (YYYY-MM-DD). |
| `--before <date>` | string | - | Created before (YYYY-MM-DD). |
| `--show-expired` | boolean | false | Include expired memories. |
| `--latest-only` | boolean | false | Only return the latest version of each memory. |
| `-o, --output <fmt>` | string | `table` | Output format: `text`, `json`, `table`. |
**Examples:**
@@ -244,13 +303,15 @@ Update a memory's text or metadata.
| Name | Type | Required | Description |
|------|------|----------|-------------|
| `memory_id` | string | Yes | The UUID of the memory to update. |
| `text` | string | No | New memory text. Falls back to stdin if piped and no `--metadata`. |
| `text` | string | No | New memory text. Falls back to stdin if piped or redirected (Python skips this in `--json`/`--agent` mode; Node does not). |
**Options:**
| Flag | Type | Default | Description |
|------|------|---------|-------------|
| `-m, --metadata <json>` | string | - | Update metadata as JSON object. |
| `--expires <date>` | string | - | Expiration date (YYYY-MM-DD). Must be in the future. |
| `--timestamp <unix>` | integer | - | Unix timestamp for the memory. |
| `-o, --output <fmt>` | string | `text` | Output format: `text`, `json`, `quiet`. |
**Examples:**
@@ -282,8 +343,9 @@ Delete a memory, all memories matching a scope, or an entity. This command has t
| `--all` | boolean | false | Delete all memories matching scope filters. |
| `--entity` | boolean | false | Delete the entity itself and all its memories (cascade). |
| `--project` | boolean | false | With `--all`: delete ALL memories project-wide (sends wildcard IDs). |
| `--dry-run` | boolean | false | Show what would be deleted without actually deleting. |
| `--force` | boolean | false | Skip confirmation prompt. |
| `--dry-run` | boolean | false | Show what would be deleted without actually deleting. Ignored by `--all --project`, which deletes (see Dry-run behavior). |
| `--force` | boolean | false | Skip confirmation prompt (`--all` and `--entity` only). Required for those modes in `--json`/`--agent` mode. |
| `--delete-linked` | boolean | false | Single-memory mode: also delete memories linked to this memory. |
| `-u, --user-id <id>` | string | - | Scope to user. |
| `--agent-id <id>` | string | - | Scope to agent. |
| `--app-id <id>` | string | - | Scope to app. |
@@ -296,14 +358,17 @@ Delete a memory, all memories matching a scope, or an entity. This command has t
2. **Bulk delete:** `mem0 delete --all [scope flags]` -- deletes all memories matching the scope. Add `--project` to wipe all memories project-wide (sends wildcard `*` entity IDs).
3. **Entity cascade:** `mem0 delete --entity [scope flags]` -- deletes the entity itself AND all its memories.
You cannot combine `<memory_id>` with `--all` or `--entity`, and you cannot combine `--all` with `--entity`. If none of these are provided, the command prints a usage hint and exits with an error.
You cannot combine `<memory_id>` with `--all` or `--entity`, and you cannot combine `--all` with `--entity`. If none of these are provided, the command prints an error and exits 1.
**Entity IDs:** `--all` resolves IDs like `search` (explicit flags only, else config defaults). Single delete and `--entity` use only explicit flags, and `--entity` requires at least one.
**Dry-run behavior:**
- Single: fetches the memory, displays it, prints "No changes made."
- `--all`: lists matching memories with count, prints "No changes made."
- `--entity`: shows the affected scope without deleting.
- Single: fetches the memory, displays it, and prints "No changes made." (Python: "No changes made (dry run)."). Node also prints "Would delete memory <id8>: <text>".
- `--all`: lists matching memories, prints "Would delete N memories." and the "No changes made" line. In `--json`/`--agent` mode `--all` still requires `--force` even with `--dry-run`, and the command then exits 0 with no output and deletes nothing. Use text mode to see the preview.
- `--entity`: prints "Would delete entity <scope> and all its memories." and the "No changes made" line.
- **Warning:** `--all --project` does not honor `--dry-run`. It skips the preview and deletes every memory in the project (after the confirmation, or immediately with `--force`). Never pass `--dry-run` to `--all --project` expecting a preview. This is a known CLI bug in both CLIs, not intended behavior, so do not rely on it. To preview, run `mem0 delete --all --dry-run` per scope (for example `-u alice`) instead.
**Confirmation:** Without `--force`, all destructive modes prompt `[y/N]`. With `--all --project`, the prompt explicitly warns about project-wide deletion.
**Confirmation:** Without `--force`, `--all` and `--entity` prompt `[y/N]`. Single-memory delete never prompts and ignores `--force`. With `--all --project`, the prompt explicitly warns about project-wide deletion and the scope flags are ignored.
**`--all --project` behavior:** Sends `DELETE /v1/memories/` with `user_id=*&agent_id=*&app_id=*&run_id=*`. The API returns an async response. The CLI prints "Deletion started. Memories will be removed in the background."
@@ -339,7 +404,7 @@ Import memories from a JSON file.
| `--agent-id <id>` | string | - | Override agent ID for all imported items. |
| `-o, --output <fmt>` | string | `text` | Output format: `text`, `json`. |
**File format:** A JSON array (or single object) where each item has a `memory`, `text`, or `content` field for the text, plus optional `user_id`, `agent_id`, and `metadata` fields. CLI-provided `--user-id` and `--agent-id` override per-item values.
**File format:** A JSON array (or single object) where each item has a `memory`, `text`, or `content` field for the text, plus optional `user_id`, `agent_id`, and `metadata` fields. `--user-id` and `--agent-id` override per-item values, and so do the config defaults when neither flag is given. Items with no text count as failed.
**Import format example:**
```json
@@ -350,7 +415,7 @@ Import memories from a JSON file.
]
```
**Behavior:** Iterates through items, calling the add API for each. Displays progress and reports `added` and `failed` counts on completion.
**Behavior:** Iterates through items, calling the add API for each. Displays progress and reports `added` and `failed` counts on completion (text mode writes the summary to stderr in Python and to stdout in Node). In JSON mode the Python CLI sends progress to stderr and includes `scope` in the envelope; the Node CLI writes the progress line to stdout before the JSON (so `| jq` fails) and omits `scope`. Only `-u`, `--agent-id`, `-o`, `--api-key` and `--base-url` are accepted.
**Examples:**
```bash
@@ -372,6 +437,8 @@ Display current configuration with secrets redacted.
|------|------|---------|-------------|
| `-o, --output <fmt>` | string | `text` | Output format: `text`, `json`. |
**Behavior:** `-o json` (or agent mode) returns the standard envelope with `data` shaped as `{"defaults": {"user_id", "agent_id", "app_id", "run_id"}, "platform": {"api_key", "base_url"}}`. The API key is redacted and unset defaults are `null`.
**Examples:**
```bash
mem0 config show
@@ -392,9 +459,9 @@ Get a single configuration value.
|------|------|----------|-------------|
| `key` | string | Yes | Dotted config key (e.g. `platform.api_key`, `defaults.user_id`). |
**Valid keys:** `platform.api_key`, `platform.base_url`, `defaults.user_id`, `defaults.agent_id`, `defaults.app_id`, `defaults.run_id`.
**Valid keys:** `platform.api_key`, `platform.base_url`, `platform.user_email`, `defaults.user_id`, `defaults.agent_id`, `defaults.app_id`, `defaults.run_id`, plus the short forms `api_key`, `base_url`, `user_email`, `user_id`, `agent_id`, `app_id`, `run_id`. Python also resolves any other field path in the config file (e.g. `platform.agent_mode`); Node does not.
API key values are always redacted in output.
An unknown key prints "Unknown config key: <key>" and still exits 0. API key values are always redacted in output. `config get` and `config set` emit a `{key, value}` envelope only in `--json`/`--agent` mode.
**Examples:**
```bash
@@ -427,19 +494,6 @@ mem0 config set platform.base_url https://api.mem0.ai
---
### `mem0 config clear`
Clear the configuration file. Removes `~/.mem0/config.json`.
**Usage:** `mem0 config clear`
**Examples:**
```bash
mem0 config clear
```
---
### `mem0 entity list`
List all entities of a given type.
@@ -507,9 +561,9 @@ List recent background processing events.
| Flag | Type | Default | Description |
|------|------|---------|-------------|
| `-o, --output <fmt>` | string | `table` | Output format: `text` (table), `json`. |
| `-o, --output <fmt>` | string | `table` | Output format: `table`, `json`. |
**Behavior:** Fetches all events for the project. Displays a table with columns: Event ID (first 8 chars), Type, Status (color-coded), Latency, Created. Status values: `PENDING`, `SUCCEEDED`, `FAILED`, `PROCESSING`.
**Behavior:** Fetches all events for the project. Displays a table with columns: Event ID (first 8 chars), Type, Status (color-coded), Latency, Created. Status values: `PENDING`, `RUNNING`, `SUCCEEDED`, `FAILED`.
**Examples:**
```bash
@@ -585,16 +639,15 @@ mem0 status -o json
## Agent Mode Envelope Format
When `--json` or `--agent` is passed, every command wraps its output in a consistent JSON envelope on stdout:
When `--json` or `--agent` is passed, data commands (add, search, list, get, update, delete, import, config, entity, event, status) wrap their output in a consistent JSON envelope on stdout:
```json
{
"status": "success",
"command": "<command_name>",
"duration_ms": 245,
"scope": { "user_id": "alice", "agent_id": null },
"scope": { "user_id": "alice" },
"count": 10,
"error": null,
"data": { ... }
}
```
@@ -603,38 +656,47 @@ When `--json` or `--agent` is passed, every command wraps its output in a consis
- `status`: `"success"` or `"error"`.
- `command`: The command name (e.g. `"search"`, `"add"`, `"list"`).
- `duration_ms`: Elapsed time in milliseconds (optional).
- `scope`: Active entity scope, omitted if empty (optional).
- `scope`: Active entity scope with empty values dropped, omitted if empty (optional).
- `count`: Number of results, where applicable (optional).
- `error`: Error message string, or `null` on success.
- `data`: Command-specific response data, or `null` on error.
- `data`: Command-specific response data (`null` on error).
- `error`: Present only on error envelopes (see below); success envelopes have no `error` key.
- `mem0_notice`: Present when the platform flags an unclaimed Agent Mode account (optional).
**Sanitized data fields per command in agent mode:**
| Command | `data` shape |
|---------|-------------|
| `add` | `[{id, memory, event}]` or `[{status, event_id}]` for PENDING |
| `search` | `[{id, memory, score, created_at, categories}]` |
| `list` | `[{id, memory, created_at, categories}]` |
| `get` | `{id, memory, created_at, updated_at, categories, metadata}` |
| `update` | `{id, memory}` |
| `delete` | Raw API response |
| `entity list` | `[{name, type, count}]` |
| `add` | `[{id, event}]` for synchronous results (`--no-infer`) or `[{status, event_id}]` for PENDING (default) |
| `search` | `[{id, memory, score, created_at, categories, expiration_date}]` |
| `list` | `[{id, memory, created_at, categories, expiration_date}]` |
| `get` | `{id, memory, created_at, updated_at, categories, metadata, expiration_date}` |
| `update` | `{id, memory, expiration_date}` |
| `delete` (single) | `{id, deleted}` |
| `delete --all` | `{deleted}`, with `scope` in the envelope (Node: raw API result) |
| `delete --all --project` | `{deleted, scope: "project"}` (Node: raw API result) |
| `delete --entity` / `entity delete` | `{deleted}` |
| `entity list` | `[{name, type}]` |
| `event list` | `[{id, event_type, status, latency, created_at}]` |
| `event status` | `{id, event_type, status, latency, created_at, updated_at, results}` |
| `status` | `{connected, backend, base_url}` |
| `config show` | Config object (keys redacted) |
| `import` | `{added, failed, duration_s}` |
| `config show` | `{defaults: {user_id, agent_id, app_id, run_id}, platform: {api_key, base_url}}` (key redacted) |
| `config get` / `config set` | `{key, value}` (agent mode only) |
| `import` | `{added, failed}` |
**`-o json` without agent mode:** `list`, `status`, `import` and `config show` print the same envelope. `add`, `search`, `get`, `update`, `delete` and `entity delete` print the raw API JSON instead. `entity list`, `event list` and `event status` print the envelope in Node and raw JSON in Python.
**Error envelope:**
```json
{
"status": "error",
"command": "search",
"error": "Authentication failed. Your API key may be invalid or expired.",
"error": "Invalid or expired API key.",
"data": null
}
```
The `error` text varies by CLI and failure point (for example a 401 after the upfront key check passes returns "Authentication failed. Your API key may be invalid or expired."). Branch on `status`, not on the message.
---
## Entity ID Resolution
@@ -652,19 +714,20 @@ else:
use all configured defaults
```
This applies to commands with `resolveIds: true`: `add`, `search`, `list`, `delete`, `import`.
This applies to `add`, `search`, `list`, `delete --all`, and `import` (user and agent IDs only). Single `delete` and `entity delete` use only explicitly passed flags.
---
## Filter Building
For `search` and `list`, entity IDs and additional filters are composed into the API filter structure:
For `search` and `list`, entity IDs and additional filters are composed into the API filter structure. `--filter` exists only on `search`; `list` builds its extra filters from `--category`, `--after` and `--before`.
1. If the user provides a pre-built filter via `--filter` containing `AND` or `OR` keys, it is passed through to the API as-is.
1. If the user provides a pre-built filter via `--filter` containing `AND` or `OR` keys, it is passed through to the API as-is (entity IDs, including config defaults, are not merged in).
2. Otherwise, the CLI builds an array of AND conditions:
- Each entity ID becomes a condition: `{"user_id": "alice"}`, etc.
- Category filters: `{"categories": {"contains": "<category>"}}`.
- Date filters: `{"created_at": {"gte": "YYYY-MM-DD"}}` and/or `{"created_at": {"lte": "YYYY-MM-DD"}}`.
- A `--filter` without `AND`/`OR` contributes each of its top-level keys as a condition.
- Category filters (`list`): `{"categories": {"contains": "<category>"}}`.
- Date filters (`list`): one condition `{"created_at": {"gte": "YYYY-MM-DD", "lte": "YYYY-MM-DD"}}`, with only the bounds you passed.
3. If exactly 1 condition: sent as a single object (no wrapping).
4. If 2+ conditions: wrapped as `{"AND": [condition1, condition2, ...]}`.
5. If 0 conditions: no filter sent.
@@ -687,8 +750,8 @@ For `search` and `list`, entity IDs and additional filters are composed into the
| `config set` | msg | - | - | - | msg |
| `entity list` | - | Y | Y | - | `table` |
| `entity delete` | Y | Y | - | Y | `text` |
| `event list` | Y (table) | Y | - | - | `table` |
| `event list` | - | Y | Y | - | `table` |
| `event status` | Y | Y | - | - | `text` |
| `status` | Y | Y | - | - | `text` |
All commands additionally support agent mode (`--json`/`--agent`) which overrides the output format with the JSON envelope.
Agent mode (`--json`/`--agent`) overrides the output format with the JSON envelope for all data commands above (it applies to `config get` and `config set` too). See the envelope section for how `-o json` differs from agent mode.
+54 -26
View File
@@ -13,6 +13,8 @@ Everything about configuring the mem0 CLI: config file format, environment varia
The restricted permissions ensure API keys are not world-readable.
Saving an API key also updates an existing `env.MEM0_API_KEY` entry in `~/.claude/settings.json` and existing `export MEM0_API_KEY=...` lines in `~/.zshrc`, `~/.bashrc`, and `~/.bash_profile`. It never creates new entries.
---
## Config File Schema
@@ -28,7 +30,19 @@ The restricted permissions ensure API keys are not world-readable.
},
"platform": {
"api_key": "",
"base_url": "https://api.mem0.ai"
"base_url": "https://api.mem0.ai",
"user_email": "",
"agent_mode": false,
"created_via": "",
"agent_caller": "",
"claimed_at": "",
"default_user_id": ""
},
"telemetry": {
"anonymous_id": ""
},
"agent_rush": {
"acknowledged_at": ""
}
}
```
@@ -44,12 +58,20 @@ The restricted permissions ensure API keys are not world-readable.
| `defaults.run_id` | string | `""` | Default run ID for scoping commands. |
| `platform.api_key` | string | `""` | API key for the Mem0 Platform. |
| `platform.base_url` | string | `"https://api.mem0.ai"` | Base URL for API requests. |
| `platform.user_email` | string | `""` | Email used for login or claim. Also cached from the ping response during `init`. |
| `platform.agent_mode` | boolean | `false` | `true` while the key is an unclaimed Agent Mode key. |
| `platform.created_via` | string | `""` | How the key was obtained: `agent_mode`, `email`, `api_key`, or `existing_key`. |
| `platform.agent_caller` | string | `""` | Agent name set by `--agent-caller` or `mem0 identify`. |
| `platform.claimed_at` | string | `""` | ISO timestamp once an Agent Mode account is claimed. |
| `platform.default_user_id` | string | `""` | `user_<slug>` returned by the Agent Mode bootstrap. Printed by `mem0 whoami`. |
| `telemetry.anonymous_id` | string | `""` | Persistent anonymous ID used for telemetry. |
| `agent_rush.acknowledged_at` | string | `""` | ISO timestamp when the `agent-rush` public-memory warning was acknowledged. |
---
## `mem0 init` Wizard
The `init` command provides two authentication flows:
The `init` command provides three authentication flows: API key, email login, and Agent Mode (see [Agent Mode Init](#agent-mode-init)).
### API Key Flow (default)
@@ -65,17 +87,17 @@ mem0 init --api-key m0-xxx --user-id alice
1. Displays the mem0 banner.
2. Checks for existing config. If found with an API key, asks for confirmation to overwrite.
3. Prompts for API key (input masked with `*` characters; supports backspace and Ctrl+U to clear).
4. Prompts for default user ID (default value: `mem0-cli`).
5. Validates the connection by calling the status endpoint.
6. Saves config to `~/.mem0/config.json` with `0600` permissions.
7. Prints success message.
3. Asks how to authenticate (`1` email login, recommended; `2` enter an API key) unless `--api-key` was passed.
4. For option 2, prompts for the API key (input masked with `*` characters; supports backspace and Ctrl+U to clear).
5. Prompts for default user ID (default value: `$USER`, then `$USERNAME`, then `mem0-cli`) unless `--user-id` was passed.
6. Validates the connection by calling the status endpoint. A failed check prints an error but the config is still saved.
7. Saves config to `~/.mem0/config.json` with `0600` permissions.
8. Prints success message.
**Non-interactive mode:** When both `--api-key` and `--user-id` are provided, skips all prompts and saves directly. When running in a non-TTY without both flags, prints an error:
**Non-interactive mode:** When both `--api-key` and `--user-id` are provided, skips all prompts and saves directly. In a non-TTY, `--api-key` alone is enough: the user ID falls back to `$USER`, then `$USERNAME`, then `mem0-cli`. A non-TTY without `--api-key` (and without `--email` or an Agent Mode signal) prints an error:
```
Non-interactive terminal detected and missing required flags.
Usage: mem0 init --api-key <key> --user-id <id>
Non-interactive terminal detected and --api-key is required.
```
### Email Login Flow
@@ -92,14 +114,23 @@ mem0 init --email alice@company.com --code 482901
1. Sends a 6-digit verification code to the email via `POST /api/v1/auth/email_code/`.
2. If `--code` is provided, verifies immediately. Otherwise prompts for the code.
3. On success: receives API key, org_id, and project_id from the server.
4. Saves to config. Creates a new account if the email is not registered.
3. On success: receives the API key from the server and saves it with `platform.user_email` and `platform.created_via: "email"`. The default user ID is `--user-id`, else `$USER`, else `mem0-cli`.
Cannot be combined with `--api-key`.
Without `--code` in a non-TTY, `init` sends the code and exits with an error telling you to rerun with `--code`. `--code` requires `--email`, and `--email` cannot be combined with `--api-key`.
**Claiming an Agent Mode account:** If the existing config is an unclaimed Agent Mode key (`platform.agent_mode: true`), `mem0 init --email <email> [--code <code>]` claims that key for the email instead of minting a new one. The API key stays the same.
### Agent Mode Init
```bash
mem0 init --agent --agent-caller <name> --json
```
With `init --agent`, `init --json`, or an agent environment variable (such as `CLAUDECODE`, `CURSOR_AGENT`, `CODEX_CLI`, `CLINE`, `AIDER_SESSION`, `GOOSE_AGENT`, `WINDSURF_AGENT`) and no `--api-key` or `--email`, `init` first reuses a valid `MEM0_API_KEY` or a valid key already in the config. Only when there is no valid key does it mint a new Agent Mode key via `POST /api/v1/auth/agent_mode/`, which is limited to 5 signups per day per network. See [command-reference.md](command-reference.md) for the `init` flags and `identify`.
### Force Overwrite
If `~/.mem0/config.json` already exists with an API key, `mem0 init` warns and asks for confirmation. Use `--force` to skip:
If `~/.mem0/config.json` already exists with an API key, `mem0 init` warns and asks for confirmation (in a non-TTY it exits 1 with "Existing config would be overwritten." instead). Use `--force` to skip:
```bash
mem0 init --api-key m0-new-key --user-id alice --force
@@ -111,7 +142,7 @@ mem0 init --api-key m0-new-key --user-id alice --force
### `mem0 config show`
Displays the current configuration as a formatted table (text mode) or JSON envelope (json mode). API keys are always redacted.
Displays `defaults.*`, `platform.api_key` (redacted), and `platform.base_url` as a formatted table (text mode) or JSON envelope (json mode). Other `platform.*` fields are not shown.
```bash
mem0 config show
@@ -120,7 +151,7 @@ mem0 config show -o json
### `mem0 config get <key>`
Reads a single configuration value. The key uses dotted notation.
Reads a single configuration value. The key uses dotted notation or a short alias (`api_key`, `base_url`, `user_email`, `user_id`, `agent_id`, `app_id`, `run_id`).
```bash
mem0 config get platform.api_key # prints: m0-x...xxxx (redacted)
@@ -130,12 +161,13 @@ mem0 config get defaults.user_id # prints: alice
**Valid keys:**
- `platform.api_key`
- `platform.base_url`
- `platform.user_email`
- `defaults.user_id`
- `defaults.agent_id`
- `defaults.app_id`
- `defaults.run_id`
Unknown keys print an error message.
Python also resolves any other field path in the config (for example `platform.created_via`); Node accepts only the keys above and their short aliases. Unknown keys print "Unknown config key: <key>" and still exit 0, so check the output rather than the exit code. In `--json`/`--agent` mode `config get` and `config set` return `{"key": ..., "value": ...}` in the envelope.
### `mem0 config set <key> <value>`
@@ -148,17 +180,9 @@ mem0 config set platform.base_url https://api.mem0.ai
**Type coercion:**
- Boolean fields accept `true`, `1`, `yes` (case-insensitive) as true. Anything else is false.
- Integer fields are parsed with `parseInt`.
- Integer fields are parsed as integers (Node `parseInt`, Python `int`; Python rejects a non-numeric value as an unknown key).
- String fields are stored as-is.
### `mem0 config clear`
Removes the config file (`~/.mem0/config.json`).
```bash
mem0 config clear
```
---
## Environment Variables
@@ -173,6 +197,7 @@ Environment variables override config file values but are overridden by CLI flag
| `MEM0_AGENT_ID` | `defaults.agent_id` | string | `""` |
| `MEM0_APP_ID` | `defaults.app_id` | string | `""` |
| `MEM0_RUN_ID` | `defaults.run_id` | string | `""` |
| `MEM0_TELEMETRY` | n/a | string | enabled. Set to `false` to disable anonymous telemetry. |
---
@@ -218,7 +243,10 @@ The `config get` and `config set` commands use dotted key paths. Here is the ful
|------------|---------|-------|
| `platform.api_key` | platform | api_key |
| `platform.base_url` | platform | base_url |
| `platform.user_email` | platform | user_email |
| `defaults.user_id` | defaults | user_id |
| `defaults.agent_id` | defaults | agent_id |
| `defaults.app_id` | defaults | app_id |
| `defaults.run_id` | defaults | run_id |
Short aliases (`api_key`, `base_url`, `user_email`, `user_id`, `agent_id`, `app_id`, `run_id`) map to the same fields. Python additionally resolves any other dotted field path.
+44 -37
View File
@@ -6,11 +6,11 @@ Practical recipes for using the mem0 CLI in scripts, pipelines, and agent loops.
## Piping Content via Stdin
The CLI reads from stdin when no text argument is provided and input is piped (not a TTY). This works with `add`, `search`, and `update`.
The CLI reads from stdin when no text argument is provided and stdin is a pipe or a redirected file. This works with `add`, `search`, and `update`. It never reads stdin in `--json`/`--agent` mode for `add`, and Python also skips it there for `search` and `update`, so pass the text as an argument in agent mode.
**Stdin detection method:**
- Python: `not sys.stdin.isatty()`
- Node: `!process.stdin.isTTY`
- Python: `os.fstat(sys.stdin.fileno())` is a FIFO or a regular file
- Node: `fs.fstatSync(0)` is a FIFO or a regular file
### Add from pipe
@@ -69,7 +69,7 @@ The file should be a JSON array where each item has a `memory`, `text`, or `cont
]
```
CLI-provided `--user-id` overrides per-item `user_id` values.
Items may also carry `agent_id`. CLI-provided `--user-id` and `--agent-id` (or configured defaults) override per-item values. A single JSON object is treated as a one-item array, and items without `memory`, `text`, or `content` are counted as failed.
### Import with JSON output
@@ -82,21 +82,24 @@ Output:
{
"status": "success",
"command": "import",
"data": { "added": 42, "failed": 0, "duration_s": 3.14 },
"duration_ms": 3140
"duration_ms": 3140,
"scope": { "user_id": "alice" },
"data": { "added": 42, "failed": 0 }
}
```
This is the Python CLI output. The Node CLI omits `scope` and writes the `Importing memories... n/n` progress line to stdout before the JSON, so its output cannot be piped to `jq`.
---
## Agent Mode for LLM Consumption
Use `--json` or `--agent` to get structured JSON output suitable for LLM tool calling or agent frameworks. Spinners and progress always go to stderr, keeping stdout clean.
Use `--json` or `--agent` to get structured JSON output suitable for LLM tool calling or agent frameworks. Spinners and progress always go to stderr, keeping stdout clean. Put the flag before the subcommand so it works in both the Python and Node CLIs.
### Search with agent mode
```bash
mem0 search "preferences" --user-id alice --agent
mem0 --agent search "preferences" --user-id alice
```
Output (stdout):
@@ -107,7 +110,6 @@ Output (stdout):
"duration_ms": 187,
"scope": { "user_id": "alice" },
"count": 2,
"error": null,
"data": [
{ "id": "mem-abc", "memory": "User prefers dark mode", "score": 0.95, "created_at": "2025-01-15T10:00:00Z", "categories": ["preferences"] },
{ "id": "mem-def", "memory": "User likes monospace fonts", "score": 0.82, "created_at": "2025-01-15T10:01:00Z", "categories": ["preferences"] }
@@ -118,7 +120,7 @@ Output (stdout):
### Add with agent mode
```bash
mem0 add "Uses Python 3.12" --user-id alice --json
mem0 --json add "Uses Python 3.12" --user-id alice
```
### Error handling in agent mode
@@ -126,7 +128,7 @@ mem0 add "Uses Python 3.12" --user-id alice --json
Errors also return valid JSON with `"status": "error"`:
```bash
mem0 search "test" --user-id alice --api-key invalid --agent
mem0 --agent search "test" --user-id alice --api-key invalid
```
Output:
@@ -134,7 +136,7 @@ Output:
{
"status": "error",
"command": "search",
"error": "Authentication failed. Your API key may be invalid or expired.",
"error": "Invalid or expired API key.",
"data": null
}
```
@@ -143,30 +145,30 @@ Output:
## JSON Output + jq
Use `--output json` (or `-o json`) for raw JSON output, then pipe to `jq` for processing.
Use `--output json` (or `-o json`) for JSON output, then pipe to `jq` for processing. `search`, `get`, `add`, `update`, and `delete` print the raw API response. `list`, `status`, and `import` print the standard envelope instead, so read the results from `.data`. Piping `import` to `jq` works only with the Python CLI (Node writes its progress line to stdout ahead of the JSON).
### Extract just memory text
```bash
mem0 list --user-id alice --output json | jq '.[] | .memory'
mem0 list --user-id alice --output json | jq '.data[] | .memory'
```
### Get memory IDs
```bash
mem0 list --user-id alice -o json | jq '.[].id'
mem0 list --user-id alice -o json | jq '.data[].id'
```
### Count memories
```bash
mem0 list --user-id alice -o json | jq 'length'
mem0 list --user-id alice -o json | jq '.count'
```
### Filter by category in jq
```bash
mem0 list --user-id alice -o json | jq '[.[] | select(.categories[]? == "preferences")]'
mem0 list --user-id alice -o json | jq '[.data[] | select(.categories[]? == "preferences")]'
```
### Extract search scores
@@ -183,7 +185,7 @@ mem0 search "tools" --user-id alice -o json | jq '.[] | {memory, score}'
```bash
# Get IDs, then delete each one
mem0 list --user-id alice -o json | jq -r '.[].id' | while read id; do
mem0 list --user-id alice -o json | jq -r '.data[].id' | while read id; do
mem0 delete "$id" --force
done
```
@@ -199,7 +201,7 @@ done < memories.txt
### Copy memories between users
```bash
mem0 list --user-id alice -o json | jq -r '.[].memory' | while IFS= read -r mem; do
mem0 list --user-id alice -o json | jq -r '.data[].memory' | while IFS= read -r mem; do
mem0 add "$mem" --user-id bob
done
```
@@ -216,7 +218,7 @@ mem0 list --user-id alice -o json > alice_memories.json
page=1
while true; do
result=$(mem0 list --user-id alice -o json --page "$page" --page-size 100)
count=$(echo "$result" | jq 'length')
count=$(echo "$result" | jq '.count')
if [ "$count" -eq 0 ]; then
break
fi
@@ -271,7 +273,7 @@ mem0 add "CI run started" --user-id ci-bot
```bash
test_summary=$(cat test-results.txt | head -20)
mem0 add "$test_summary" --agent-id ci-bot --metadata '{"type":"test-results"}' --categories "ci,testing"
mem0 add "$test_summary" --agent-id ci-bot --metadata '{"type":"test-results"}'
```
---
@@ -282,11 +284,11 @@ The CLI reads from stdin only when ALL of these conditions are met:
1. No text argument was provided on the command line.
2. For `add`: no `--messages` and no `--file` flag.
3. For `update`: no `--metadata` flag.
4. stdin is piped (not a TTY).
3. stdin is a pipe or a redirected file (a plain non-TTY such as `/dev/null` does not count).
4. The CLI is not in `--json`/`--agent` mode (`add` in both CLIs, `search` and `update` in Python).
**This means:**
- `mem0 add --user-id alice` in an interactive terminal will NOT hang waiting for input. It will print a usage error.
- `mem0 add --user-id alice` in an interactive terminal will NOT hang waiting for input. It exits 1 with "No content provided".
- `echo "text" | mem0 add --user-id alice` will read "text" from stdin.
- `mem0 add "explicit text" --user-id alice` will use the explicit text, even if stdin is piped.
@@ -303,8 +305,7 @@ The CLI reads from stdin only when ALL of these conditions are met:
```bash
set -e # Exit on error
# This will exit the script if the API key is invalid
mem0 status > /dev/null 2>&1
mem0 status -o json | jq -e '.data.connected' > /dev/null
# Add with error check
if mem0 add "test memory" --user-id alice 2>/dev/null; then
@@ -317,10 +318,16 @@ fi
### Capture memory ID from add
Default adds are asynchronous and return an `event_id`. Use `--no-infer` for a synchronous add whose `.data[0].id` is the memory id.
```bash
# Use agent mode to get structured output
result=$(mem0 add "new fact" --user-id alice --agent 2>/dev/null)
memory_id=$(echo "$result" | jq -r '.data[0].id // empty')
event_id=$(mem0 --agent add "new fact" --user-id alice 2>/dev/null | jq -r '.data[0].event_id // empty')
for _ in $(seq 30); do
status=$(mem0 --agent event status "$event_id" | jq -r '.data.status')
[ "$status" = "SUCCEEDED" ] || [ "$status" = "FAILED" ] && break
sleep 2
done
memory_id=$(mem0 --agent event status "$event_id" | jq -r '.data.results[0].id // empty')
if [ -n "$memory_id" ]; then
echo "Created memory: $memory_id"
fi
@@ -330,7 +337,7 @@ fi
```bash
# Only add if search returns no results
count=$(mem0 search "dark mode" --user-id alice --agent 2>/dev/null | jq '.count // 0')
count=$(mem0 --agent search "dark mode" --user-id alice 2>/dev/null | jq '.count // 0')
if [ "$count" -eq 0 ]; then
mem0 add "User prefers dark mode" --user-id alice
fi
@@ -358,7 +365,7 @@ mem0 list
### Timeout handling
The CLI uses a 30-second timeout for all API requests. For long-running scripts, handle timeouts:
The CLI uses a 30-second timeout for normal API requests (the key-validation ping uses 5 seconds and `init` uses 5, 10 and 30 seconds). For long-running scripts, handle timeouts:
```bash
if ! mem0 search "query" --user-id alice -o json 2>/dev/null; then
@@ -382,13 +389,13 @@ Or use the event system to poll for completion:
```bash
# Add and capture event ID from agent output
result=$(mem0 add "new preference" --user-id alice --agent 2>/dev/null)
result=$(mem0 --agent add "new preference" --user-id alice 2>/dev/null)
event_id=$(echo "$result" | jq -r '.data[0].event_id // empty')
if [ -n "$event_id" ]; then
# Poll until processing completes
while true; do
status=$(mem0 event status "$event_id" --agent 2>/dev/null | jq -r '.data.status')
status=$(mem0 --agent event status "$event_id" 2>/dev/null | jq -r '.data.status')
if [ "$status" = "SUCCEEDED" ] || [ "$status" = "FAILED" ]; then
break
fi
@@ -413,16 +420,16 @@ shift 2
case "$ACTION" in
recall)
mem0 search "$*" --user-id "$USER_ID" --agent 2>/dev/null
mem0 --agent search "$*" --user-id "$USER_ID" 2>/dev/null
;;
remember)
mem0 add "$*" --user-id "$USER_ID" --agent 2>/dev/null
mem0 --agent add "$*" --user-id "$USER_ID" 2>/dev/null
;;
forget)
mem0 delete --all --user-id "$USER_ID" --force --agent 2>/dev/null
mem0 --agent delete --all --user-id "$USER_ID" --force 2>/dev/null
;;
history)
mem0 list --user-id "$USER_ID" --agent 2>/dev/null
mem0 --agent list --user-id "$USER_ID" 2>/dev/null
;;
*)
echo '{"status":"error","error":"Unknown action: '"$ACTION"'"}' >&2

Some files were not shown because too many files have changed in this diff Show More