diff --git a/.agents/plugins/marketplace.json b/.agents/plugins/marketplace.json
new file mode 100644
index 000000000..6e0e240a1
--- /dev/null
+++ b/.agents/plugins/marketplace.json
@@ -0,0 +1,20 @@
+{
+ "name": "mem0-plugins",
+ "interface": {
+ "displayName": "Mem0 Plugins"
+ },
+ "plugins": [
+ {
+ "name": "mem0",
+ "source": {
+ "source": "local",
+ "path": "./mem0-plugin"
+ },
+ "policy": {
+ "installation": "AVAILABLE",
+ "authentication": "ON_INSTALL"
+ },
+ "category": "Productivity"
+ }
+ ]
+}
diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json
new file mode 100644
index 000000000..82ce93578
--- /dev/null
+++ b/.claude-plugin/marketplace.json
@@ -0,0 +1,18 @@
+{
+ "name": "mem0-plugins",
+ "owner": {
+ "name": "Mem0",
+ "email": "support@mem0.ai"
+ },
+ "metadata": {
+ "description": "Official Mem0 plugins for Claude"
+ },
+ "plugins": [
+ {
+ "name": "mem0",
+ "source": "./mem0-plugin",
+ "description": "Mem0 memory layer for AI applications. Add persistent memory, personalization, and semantic search to Claude workflows.",
+ "version": "0.1.0"
+ }
+ ]
+}
diff --git a/.cursor-plugin/marketplace.json b/.cursor-plugin/marketplace.json
new file mode 100644
index 000000000..c23fc2e22
--- /dev/null
+++ b/.cursor-plugin/marketplace.json
@@ -0,0 +1,18 @@
+{
+ "name": "mem0-plugins",
+ "owner": {
+ "name": "Mem0",
+ "email": "support@mem0.ai"
+ },
+ "metadata": {
+ "description": "Official Mem0 plugins for Cursor"
+ },
+ "plugins": [
+ {
+ "name": "mem0",
+ "source": "./mem0-plugin",
+ "description": "Mem0 memory layer for AI applications. Add persistent memory, personalization, and semantic search.",
+ "version": "0.1.0"
+ }
+ ]
+}
diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
index 749f4fa7d..a851488d9 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.yml
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -1,41 +1,55 @@
-name: ๐ Bug Report
-description: Create a report to help us reproduce and fix the bug
+name: Bug Report
+description: Report a bug in mem0
+labels: ["bug"]
body:
-- type: markdown
- attributes:
- value: >
- #### Before submitting a bug, please make sure the issue hasn't been already addressed by searching through [the existing and past issues](https://github.com/embedchain/embedchain/issues?q=is%3Aissue+sort%3Acreated-desc+).
-- type: textarea
- attributes:
- label: ๐ Describe the bug
- description: |
- Please provide a clear and concise description of what the bug is.
+ - type: dropdown
+ id: component
+ attributes:
+ label: Component
+ description: Which part of mem0 is affected?
+ options:
+ - Core / Python SDK
+ - TypeScript SDK
+ - Vector Store (Qdrant, PGVector, Redis, Chroma, etc.)
+ - Graph Memory (Neo4j, Memgraph, etc.)
+ - Ollama / Local Models
+ - OpenClaw
+ - REST API
+ - Other
+ validations:
+ required: true
- If relevant, add a minimal example so that we can reproduce the error by running the code. It is very important for the snippet to be as succinct (minimal) as possible, so please take time to trim down any irrelevant code to help us debug efficiently. We are going to copy-paste your code and we expect to get the same result as you did: avoid any external data, and include the relevant imports, etc. For example:
+ - type: textarea
+ id: description
+ attributes:
+ label: Description
+ value: |
+ ### Summary
- ```python
- # All necessary imports at the beginning
- import embedchain as ec
- # Your code goes here
+ A clear summary of the bug.
+ ### Steps to Reproduce
- ```
+ ```python
+ from mem0 import Memory
- Please also paste or describe the results you observe instead of the expected results. If you observe an error, please paste the error message including the **full** traceback of the exception. It may be relevant to wrap error messages in ```` ```triple quotes blocks``` ````.
- placeholder: |
- A clear and concise description of what the bug is.
+ m = Memory()
+ # Your code here...
+ ```
- ```python
- Sample code to reproduce the problem
- ```
+ ### Expected Behavior
- ```
- The error message you got, with the full traceback.
- ````
- validations:
- required: true
-- type: markdown
- attributes:
- value: >
- Thanks for contributing ๐!
+ What you expected to happen.
+
+ ### Actual Behavior
+
+ What actually happened. Paste the full error traceback if applicable.
+
+ ### Environment
+
+ - mem0 version:
+ - Python/Node version:
+ - OS:
+ validations:
+ required: true
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index 807a088a4..0e2f83054 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -1,8 +1,8 @@
blank_issues_enabled: true
contact_links:
- - name: 1-on-1 Session
- url: https://cal.com/taranjeetio/ec
- about: Speak directly with Taranjeet, the founder, to discuss issues, share feedback, or explore improvements for Embedchain
- - name: Discord
+ - name: Discord Community
url: https://discord.gg/6PzXDgEjG5
- about: General community discussions
+ about: Ask questions and discuss with the community
+ - name: Documentation
+ url: https://docs.mem0.ai
+ about: Read the official mem0 documentation
diff --git a/.github/ISSUE_TEMPLATE/documentation_issue.yml b/.github/ISSUE_TEMPLATE/documentation_issue.yml
index 781397fc8..0ac173a52 100644
--- a/.github/ISSUE_TEMPLATE/documentation_issue.yml
+++ b/.github/ISSUE_TEMPLATE/documentation_issue.yml
@@ -1,11 +1,23 @@
-name: Documentation
-description: Report an issue related to the Embedchain docs.
-title: "DOC: "
+name: Documentation Issue
+description: Report an issue or suggest an improvement to the mem0 docs
+labels: ["documentation"]
body:
-- type: textarea
- attributes:
- label: "Issue with current documentation:"
- description: >
- Please make sure to leave a reference to the document/code you're
- referring to.
+ - type: textarea
+ id: description
+ attributes:
+ label: Description
+ value: |
+ ### Page
+
+ Link to the docs page: https://docs.mem0.ai/...
+
+ ### What's Wrong or Missing
+
+ Describe what's incorrect, unclear, or missing.
+
+ ### Suggested Fix
+
+ How should the docs be improved?
+ validations:
+ required: true
diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml
index 30c3e118b..61c9927e0 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.yml
+++ b/.github/ISSUE_TEMPLATE/feature_request.yml
@@ -1,23 +1,41 @@
-name: ๐ Feature request
-description: Submit a proposal/request for a new Embedchain feature
+name: Feature Request
+description: Suggest a new feature or improvement for mem0
+labels: ["enhancement"]
body:
-- type: textarea
- id: feature-request
- attributes:
- label: ๐ The feature
- description: >
- A clear and concise description of the feature proposal
- validations:
- required: true
-- type: textarea
- attributes:
- label: Motivation, pitch
- description: >
- Please outline the motivation for the proposal. Is your feature request related to a specific problem? e.g., *"I'm working on X and would like Y to be possible"*. If this is related to another GitHub issue, please link here too.
- validations:
- required: true
-- type: markdown
- attributes:
- value: >
- Thanks for contributing ๐!
+ - type: dropdown
+ id: component
+ attributes:
+ label: Component
+ description: Which part of mem0 does this relate to?
+ options:
+ - Core / Python SDK
+ - TypeScript SDK
+ - Vector Store (Qdrant, PGVector, Redis, Chroma, etc.)
+ - Graph Memory (Neo4j, Memgraph, etc.)
+ - Ollama / Local Models
+ - OpenClaw
+ - REST API
+ - Benchmarks / Evals
+ - Other
+ validations:
+ required: true
+
+ - type: textarea
+ id: description
+ attributes:
+ label: Description
+ value: |
+ ### Use Case
+
+ What problem are you trying to solve?
+
+ ### Proposed Solution
+
+ How should this work? Include API examples or pseudocode if helpful.
+
+ ### Alternatives Considered
+
+ Any workarounds you've tried or other approaches considered.
+ validations:
+ required: true
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 53c64720e..14f7bdaac 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -1,41 +1,38 @@
+## Linked Issue
+
+Closes #
+
## Description
-Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change.
+
-Fixes # (issue)
+## Type of Change
-## Type of change
-
-Please delete options that are not relevant.
-
-- [ ] Bug fix (non-breaking change which fixes an issue)
-- [ ] New feature (non-breaking change which adds functionality)
-- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
-- [ ] Refactor (does not change functionality, e.g. code style improvements, linting)
+- [ ] Bug fix (non-breaking change that fixes an issue)
+- [ ] New feature (non-breaking change that adds functionality)
+- [ ] Breaking change (fix or feature that would cause existing functionality to change)
+- [ ] Refactor (no functional changes)
- [ ] Documentation update
-## How Has This Been Tested?
+## Breaking Changes
-Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration
+
-Please delete options that are not relevant.
+N/A
-- [ ] Unit Test
-- [ ] Test Script (please provide)
+## Test Coverage
-## Checklist:
+- [ ] I added/updated unit tests
+- [ ] I added/updated integration tests
+- [ ] I tested manually (describe below)
+- [ ] No tests needed (explain why)
-- [ ] My code follows the style guidelines of this project
-- [ ] I have performed a self-review of my own code
-- [ ] I have commented my code, particularly in hard-to-understand areas
-- [ ] I have made corresponding changes to the documentation
-- [ ] My changes generate no new warnings
-- [ ] I have added tests that prove my fix is effective or that my feature works
-- [ ] New and existing unit tests pass locally with my changes
-- [ ] Any dependent changes have been merged and published in downstream modules
-- [ ] I have checked my code and corrected any misspellings
+
-## Maintainer Checklist
+## Checklist
-- [ ] closes #xxxx (Replace xxxx with the GitHub issue number)
-- [ ] Made sure Checks passed
+- [ ] My code follows the project's style guidelines
+- [ ] I have performed a self-review of my code
+- [ ] I have added tests that prove my fix/feature works
+- [ ] New and existing tests pass locally
+- [ ] I have updated documentation if needed
diff --git a/.github/advanced-issue-labeler.yml b/.github/advanced-issue-labeler.yml
new file mode 100644
index 000000000..502a662a1
--- /dev/null
+++ b/.github/advanced-issue-labeler.yml
@@ -0,0 +1,18 @@
+# Maps dropdown selections to GitHub labels
+# Used by the advanced-issue-labeler GitHub Action
+
+component:
+ - label: "sdk-python"
+ matcher: "Core / Python SDK"
+ - label: "sdk-typescript"
+ matcher: "TypeScript SDK"
+ - label: "vector-store"
+ matcher: "Vector Store"
+ - label: "graph-memory"
+ matcher: "Graph Memory"
+ - label: "ollama"
+ matcher: "Ollama"
+ - label: "openclaw"
+ matcher: "OpenClaw"
+ - label: "rest-api"
+ matcher: "REST API"
diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml
index 0cb147a5b..2da4e97e1 100644
--- a/.github/workflows/cd.yml
+++ b/.github/workflows/cd.yml
@@ -7,6 +7,7 @@ on:
jobs:
build-n-publish:
name: Build and publish Python ๐ distributions ๐ฆ to PyPI and TestPyPI
+ if: startsWith(github.event.release.tag_name, 'v')
runs-on: ubuntu-latest
permissions:
id-token: write
@@ -38,7 +39,7 @@ jobs:
# packages_dir: dist/
- name: Publish distribution ๐ฆ to PyPI
- if: startsWith(github.ref, 'refs/tags')
+ if: startsWith(github.ref, 'refs/tags/v')
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages_dir: dist/
diff --git a/.github/workflows/cli-node-cd.yml b/.github/workflows/cli-node-cd.yml
new file mode 100644
index 000000000..c583abb52
--- /dev/null
+++ b/.github/workflows/cli-node-cd.yml
@@ -0,0 +1,46 @@
+name: Publish @mem0/cli ๐ฆ to npm
+
+on:
+ release:
+ types: [published]
+
+jobs:
+ build-n-publish:
+ name: Build and publish @mem0/cli ๐ฆ to npm
+ if: startsWith(github.event.release.tag_name, 'cli-node-v')
+ runs-on: ubuntu-latest
+ permissions:
+ id-token: write
+ defaults:
+ run:
+ working-directory: cli/node
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Install pnpm
+ uses: pnpm/action-setup@v4
+ with:
+ version: 10
+
+ - name: Set up Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: '22'
+ registry-url: 'https://registry.npmjs.org'
+ cache: 'pnpm'
+ cache-dependency-path: cli/node/pnpm-lock.yaml
+
+ - name: Install dependencies
+ run: pnpm install --frozen-lockfile
+
+ - name: Build
+ run: pnpm run build
+
+ - name: Publish to npm
+ run: |
+ if [ "${{ github.event.release.prerelease }}" = "true" ]; then
+ PREID=$(node -p "require('./package.json').version.split('-')[1].split('.')[0]")
+ npx npm@latest publish --provenance --access public --tag "$PREID"
+ else
+ npx npm@latest publish --provenance --access public
+ fi
diff --git a/.github/workflows/cli-node-ci.yml b/.github/workflows/cli-node-ci.yml
new file mode 100644
index 000000000..c4ebb4dc8
--- /dev/null
+++ b/.github/workflows/cli-node-ci.yml
@@ -0,0 +1,100 @@
+name: CLI Node CI
+
+on:
+ workflow_dispatch:
+ push:
+ branches: [main]
+ paths:
+ - 'cli/node/**'
+ - '.github/workflows/cli-node-ci.yml'
+ pull_request:
+ paths:
+ - 'cli/node/**'
+ - '.github/workflows/cli-node-ci.yml'
+
+jobs:
+ lint:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Install pnpm
+ uses: pnpm/action-setup@v4
+ with:
+ version: 10
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: 20
+ cache: 'pnpm'
+ cache-dependency-path: cli/node/pnpm-lock.yaml
+
+ - name: Install dependencies
+ working-directory: cli/node
+ run: pnpm install --frozen-lockfile
+
+ - name: Lint
+ working-directory: cli/node
+ run: pnpm run lint
+
+ - name: Type check
+ working-directory: cli/node
+ run: pnpm run typecheck
+
+ test:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ node-version: [20, 22]
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Install pnpm
+ uses: pnpm/action-setup@v4
+ with:
+ version: 10
+
+ - name: Setup Node.js ${{ matrix.node-version }}
+ uses: actions/setup-node@v4
+ with:
+ node-version: ${{ matrix.node-version }}
+ cache: 'pnpm'
+ cache-dependency-path: cli/node/pnpm-lock.yaml
+
+ - name: Install dependencies
+ working-directory: cli/node
+ run: pnpm install --frozen-lockfile
+
+ - name: Run tests
+ working-directory: cli/node
+ run: pnpm run test
+
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Install pnpm
+ uses: pnpm/action-setup@v4
+ with:
+ version: 10
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: 20
+ cache: 'pnpm'
+ cache-dependency-path: cli/node/pnpm-lock.yaml
+
+ - name: Install dependencies
+ working-directory: cli/node
+ run: pnpm install --frozen-lockfile
+
+ - name: Build
+ working-directory: cli/node
+ run: pnpm run build
+
+ - name: Verify dist output
+ run: |
+ test -f cli/node/dist/index.js || (echo "Build output missing: dist/index.js" && exit 1)
diff --git a/.github/workflows/cli-python-cd.yml b/.github/workflows/cli-python-cd.yml
new file mode 100644
index 000000000..316bc1df2
--- /dev/null
+++ b/.github/workflows/cli-python-cd.yml
@@ -0,0 +1,34 @@
+name: Publish mem0-cli ๐ distributions ๐ฆ to PyPI
+
+on:
+ release:
+ types: [published]
+
+jobs:
+ build-n-publish:
+ name: Build and publish mem0-cli ๐ฆ to PyPI
+ if: startsWith(github.event.release.tag_name, 'cli-v')
+ runs-on: ubuntu-latest
+ permissions:
+ id-token: write
+ defaults:
+ run:
+ working-directory: cli/python
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: '3.11'
+
+ - name: Install Hatch
+ run: pip install hatch
+
+ - name: Build a binary wheel and a source tarball
+ run: hatch build --clean
+
+ - name: Publish distribution ๐ฆ to PyPI
+ uses: pypa/gh-action-pypi-publish@release/v1
+ with:
+ packages-dir: cli/python/dist/
diff --git a/.github/workflows/cli-python-ci.yml b/.github/workflows/cli-python-ci.yml
new file mode 100644
index 000000000..1fbe6bf0e
--- /dev/null
+++ b/.github/workflows/cli-python-ci.yml
@@ -0,0 +1,79 @@
+name: CLI Python CI
+
+on:
+ workflow_dispatch:
+ push:
+ branches: [main]
+ paths:
+ - 'cli/python/**'
+ - '.github/workflows/cli-python-ci.yml'
+ pull_request:
+ paths:
+ - 'cli/python/**'
+ - '.github/workflows/cli-python-ci.yml'
+
+jobs:
+ lint:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: '3.12'
+
+ - name: Install dev dependencies
+ working-directory: cli/python
+ run: pip install -e ".[dev]"
+
+ - name: Lint with ruff
+ working-directory: cli/python
+ run: ruff check .
+
+ - name: Check formatting
+ working-directory: cli/python
+ run: ruff format --check .
+
+ test:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ python-version: ["3.10", "3.11", "3.12"]
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up Python ${{ matrix.python-version }}
+ uses: actions/setup-python@v5
+ with:
+ python-version: ${{ matrix.python-version }}
+
+ - name: Install dev dependencies
+ working-directory: cli/python
+ run: pip install -e ".[dev]"
+
+ - name: Run tests
+ working-directory: cli/python
+ run: pytest
+
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: '3.12'
+
+ - name: Install Hatch
+ run: pip install hatch
+
+ - name: Build
+ working-directory: cli/python
+ run: hatch build --clean
+
+ - name: Verify dist output
+ run: |
+ ls cli/python/dist/*.whl || (echo "Wheel file missing" && exit 1)
+ ls cli/python/dist/*.tar.gz || (echo "Source dist missing" && exit 1)
diff --git a/.github/workflows/issue-labeler.yml b/.github/workflows/issue-labeler.yml
new file mode 100644
index 000000000..0486a57b1
--- /dev/null
+++ b/.github/workflows/issue-labeler.yml
@@ -0,0 +1,39 @@
+name: Auto-label issues
+
+on:
+ issues:
+ types: [opened]
+
+permissions:
+ contents: read
+ issues: write
+
+jobs:
+ label:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: stefanbuck/github-issue-parser@v3
+ id: issue-parser
+ with:
+ template-path: .github/ISSUE_TEMPLATE/bug_report.yml
+
+ - uses: redhat-plumbers-in-action/advanced-issue-labeler@v3
+ with:
+ issue-form: ${{ steps.issue-parser.outputs.jsonString }}
+ section: component
+ token: ${{ secrets.GITHUB_TOKEN }}
+ config-path: .github/advanced-issue-labeler.yml
+
+ - uses: stefanbuck/github-issue-parser@v3
+ id: feature-parser
+ if: contains(github.event.issue.labels.*.name, 'enhancement')
+ with:
+ template-path: .github/ISSUE_TEMPLATE/feature_request.yml
+
+ - uses: redhat-plumbers-in-action/advanced-issue-labeler@v3
+ if: contains(github.event.issue.labels.*.name, 'enhancement')
+ with:
+ issue-form: ${{ steps.feature-parser.outputs.jsonString }}
+ section: component
+ token: ${{ secrets.GITHUB_TOKEN }}
+ config-path: .github/advanced-issue-labeler.yml
diff --git a/.github/workflows/openclaw-cd.yml b/.github/workflows/openclaw-cd.yml
new file mode 100644
index 000000000..8a0d0e7e0
--- /dev/null
+++ b/.github/workflows/openclaw-cd.yml
@@ -0,0 +1,46 @@
+name: Publish @mem0/openclaw-mem0 ๐ฆ to npm
+
+on:
+ release:
+ types: [published]
+
+jobs:
+ build-n-publish:
+ name: Build and publish @mem0/openclaw-mem0 ๐ฆ to npm
+ if: startsWith(github.event.release.tag_name, 'openclaw-v')
+ runs-on: ubuntu-latest
+ permissions:
+ id-token: write
+ defaults:
+ run:
+ working-directory: openclaw
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Install pnpm
+ uses: pnpm/action-setup@v4
+ with:
+ version: 9
+
+ - name: Set up Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: '22'
+ registry-url: 'https://registry.npmjs.org'
+ cache: 'pnpm'
+ cache-dependency-path: openclaw/pnpm-lock.yaml
+
+ - name: Install dependencies
+ run: pnpm install --frozen-lockfile
+
+ - name: Build
+ run: pnpm build
+
+ - name: Publish to npm
+ run: |
+ if [ "${{ github.event.release.prerelease }}" = "true" ]; then
+ PREID=$(node -p "require('./package.json').version.split('-')[1].split('.')[0]")
+ npx npm@latest publish --provenance --access public --tag "$PREID"
+ else
+ npx npm@latest publish --provenance --access public
+ fi
diff --git a/.github/workflows/openclaw-checks.yml b/.github/workflows/openclaw-checks.yml
new file mode 100644
index 000000000..114f54ed6
--- /dev/null
+++ b/.github/workflows/openclaw-checks.yml
@@ -0,0 +1,100 @@
+name: openclaw checks
+
+on:
+ workflow_dispatch:
+ push:
+ branches: [main]
+ paths:
+ - 'openclaw/**'
+ - '.github/workflows/openclaw-checks.yml'
+ pull_request:
+ paths:
+ - 'openclaw/**'
+ - '.github/workflows/openclaw-checks.yml'
+
+jobs:
+ lint:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Install pnpm
+ uses: pnpm/action-setup@v4
+ with:
+ version: 9
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: 20
+ cache: 'pnpm'
+ cache-dependency-path: openclaw/pnpm-lock.yaml
+
+ - name: Install dependencies
+ run: cd openclaw && pnpm install --frozen-lockfile
+
+ - name: Type check
+ run: cd openclaw && pnpm exec tsc --noEmit
+
+ test:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ node-version: [20, 22]
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Install pnpm
+ uses: pnpm/action-setup@v4
+ with:
+ version: 9
+
+ - name: Setup Node.js ${{ matrix.node-version }}
+ uses: actions/setup-node@v4
+ with:
+ node-version: ${{ matrix.node-version }}
+ cache: 'pnpm'
+ cache-dependency-path: openclaw/pnpm-lock.yaml
+
+ - name: Install dependencies
+ run: cd openclaw && pnpm install --frozen-lockfile
+
+ - name: Run tests with coverage
+ run: cd openclaw && pnpm exec vitest run --coverage
+
+ - name: Upload coverage to Codecov
+ if: matrix.node-version == 20
+ uses: codecov/codecov-action@v4
+ with:
+ flags: openclaw
+ directory: openclaw/coverage
+ env:
+ CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
+
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Install pnpm
+ uses: pnpm/action-setup@v4
+ with:
+ version: 9
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: 20
+ cache: 'pnpm'
+ cache-dependency-path: openclaw/pnpm-lock.yaml
+
+ - name: Install dependencies
+ run: cd openclaw && pnpm install --frozen-lockfile
+
+ - name: Build
+ run: cd openclaw && pnpm build
+
+ - name: Verify dist output exists
+ run: |
+ test -f openclaw/dist/index.js || (echo "Build output missing: dist/index.js" && exit 1)
+ test -f openclaw/dist/index.d.ts || (echo "Build output missing: dist/index.d.ts" && exit 1)
diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml
new file mode 100644
index 000000000..89fec8e27
--- /dev/null
+++ b/.github/workflows/stale.yml
@@ -0,0 +1,48 @@
+name: Close stale issues
+
+on:
+ schedule:
+ - cron: '0 0 * * *'
+ workflow_dispatch:
+
+permissions:
+ issues: write
+ pull-requests: write
+
+jobs:
+ stale:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/stale@v9
+ with:
+ # Issue settings
+ days-before-issue-stale: 90
+ days-before-issue-close: 14
+ stale-issue-label: 'stale'
+ stale-issue-message: >
+ This issue has been automatically marked as stale because it has not
+ had any activity in 90 days. It will be closed in 14 days if no
+ further activity occurs. If this is still relevant, please leave a
+ comment or remove the `stale` label.
+ close-issue-message: >
+ This issue has been closed due to inactivity. If this is still
+ relevant, feel free to reopen it or create a new issue.
+
+ # PR settings โ mark stale but never auto-close
+ days-before-pr-stale: 90
+ days-before-pr-close: -1
+ stale-pr-label: 'stale'
+ stale-pr-message: >
+ This pull request has been automatically marked as stale because it
+ has not had any activity in 90 days. Please update your branch and
+ address any review comments, or it may be closed in the future.
+
+ # Exempt these labels from stale processing
+ exempt-issue-labels: 'P0-critical,P1-high,good first issue,security'
+ exempt-pr-labels: 'P0-critical,P1-high'
+
+ # Remove stale label when there is new activity
+ remove-stale-when-updated: true
+
+ # Process up to 100 issues per run to stay within API limits
+ operations-per-run: 100
diff --git a/.github/workflows/ts-sdk-cd.yml b/.github/workflows/ts-sdk-cd.yml
new file mode 100644
index 000000000..8b7202ee9
--- /dev/null
+++ b/.github/workflows/ts-sdk-cd.yml
@@ -0,0 +1,46 @@
+name: Publish mem0ai ๐ฆ to npm
+
+on:
+ release:
+ types: [published]
+
+jobs:
+ build-n-publish:
+ name: Build and publish mem0ai ๐ฆ to npm
+ if: startsWith(github.event.release.tag_name, 'ts-v')
+ runs-on: ubuntu-latest
+ permissions:
+ id-token: write
+ defaults:
+ run:
+ working-directory: mem0-ts
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Install pnpm
+ uses: pnpm/action-setup@v4
+ with:
+ version: 10
+
+ - name: Set up Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: '22'
+ registry-url: 'https://registry.npmjs.org'
+ cache: 'pnpm'
+ cache-dependency-path: mem0-ts/pnpm-lock.yaml
+
+ - name: Install dependencies
+ run: pnpm install --frozen-lockfile
+
+ - name: Build
+ run: pnpm run build
+
+ - name: Publish to npm
+ run: |
+ if [ "${{ github.event.release.prerelease }}" = "true" ]; then
+ PREID=$(node -p "require('./package.json').version.split('-')[1].split('.')[0]")
+ npx npm@latest publish --provenance --access public --tag "$PREID"
+ else
+ npx npm@latest publish --provenance --access public
+ fi
diff --git a/.github/workflows/ts-sdk-ci.yml b/.github/workflows/ts-sdk-ci.yml
new file mode 100644
index 000000000..c1fb0d0c8
--- /dev/null
+++ b/.github/workflows/ts-sdk-ci.yml
@@ -0,0 +1,110 @@
+name: TypeScript SDK CI
+
+on:
+ push:
+ branches: [main]
+ paths:
+ - 'mem0-ts/**'
+ - '.github/workflows/ts-sdk-ci.yml'
+ pull_request:
+ paths:
+ - 'mem0-ts/**'
+
+jobs:
+ check_changes:
+ runs-on: ubuntu-latest
+ outputs:
+ ts_sdk_changed: ${{ steps.filter.outputs.ts_sdk }}
+ steps:
+ - uses: actions/checkout@v4
+ - uses: dorny/paths-filter@v2
+ id: filter
+ with:
+ filters: |
+ ts_sdk:
+ - 'mem0-ts/**'
+
+ build_ts_sdk:
+ needs: check_changes
+ if: needs.check_changes.outputs.ts_sdk_changed == 'true'
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ node-version: [20, 22]
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: pnpm/action-setup@v4
+ with:
+ version: 10
+
+ - uses: actions/setup-node@v4
+ with:
+ node-version: ${{ matrix.node-version }}
+ cache: 'pnpm'
+ cache-dependency-path: mem0-ts/pnpm-lock.yaml
+
+ - name: Install dependencies
+ working-directory: mem0-ts
+ run: pnpm install --frozen-lockfile
+
+ - name: Lint
+ working-directory: mem0-ts
+ run: npx prettier --check .
+
+ - name: Build
+ working-directory: mem0-ts
+ run: pnpm run build
+
+ - name: Run unit tests
+ working-directory: mem0-ts
+ run: pnpm run test:unit
+
+ - name: Verify package exports
+ working-directory: mem0-ts
+ run: |
+ node -e "const m = require('./dist/index.js'); console.log('Client exports:', Object.keys(m).length)"
+ node -e "const m = require('./dist/oss/index.js'); console.log('OSS exports:', Object.keys(m).length)"
+
+ - name: Upload coverage
+ if: matrix.node-version == 20
+ uses: actions/upload-artifact@v4
+ with:
+ name: coverage-report
+ path: mem0-ts/coverage/
+
+ integration_ts_sdk:
+ needs: build_ts_sdk
+ runs-on: ubuntu-latest
+ strategy:
+ max-parallel: 1
+ matrix:
+ node-version: [20, 22]
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: pnpm/action-setup@v4
+ with:
+ version: 10
+
+ - uses: actions/setup-node@v4
+ with:
+ node-version: ${{ matrix.node-version }}
+ cache: 'pnpm'
+ cache-dependency-path: mem0-ts/pnpm-lock.yaml
+
+ - name: Install dependencies
+ working-directory: mem0-ts
+ run: pnpm install --frozen-lockfile
+
+ - name: Build
+ working-directory: mem0-ts
+ run: pnpm run build
+
+ - name: Run integration tests (with cleanup)
+ working-directory: mem0-ts
+ env:
+ MEM0_API_KEY: ${{ secrets.MEM0_API_KEY }}
+ run: pnpm run test:integration
diff --git a/.github/workflows/vercel-ai-cd.yml b/.github/workflows/vercel-ai-cd.yml
new file mode 100644
index 000000000..172afc288
--- /dev/null
+++ b/.github/workflows/vercel-ai-cd.yml
@@ -0,0 +1,46 @@
+name: Publish @mem0/vercel-ai-provider ๐ฆ to npm
+
+on:
+ release:
+ types: [published]
+
+jobs:
+ build-n-publish:
+ name: Build and publish @mem0/vercel-ai-provider ๐ฆ to npm
+ if: startsWith(github.event.release.tag_name, 'vercel-ai-v')
+ runs-on: ubuntu-latest
+ permissions:
+ id-token: write
+ defaults:
+ run:
+ working-directory: vercel-ai-sdk
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Install pnpm
+ uses: pnpm/action-setup@v4
+ with:
+ version: 10
+
+ - name: Set up Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: '22'
+ registry-url: 'https://registry.npmjs.org'
+ cache: 'pnpm'
+ cache-dependency-path: vercel-ai-sdk/pnpm-lock.yaml
+
+ - name: Install dependencies
+ run: pnpm install --frozen-lockfile
+
+ - name: Build
+ run: pnpm run build
+
+ - name: Publish to npm
+ run: |
+ if [ "${{ github.event.release.prerelease }}" = "true" ]; then
+ PREID=$(node -p "require('./package.json').version.split('-')[1].split('.')[0]")
+ npx npm@latest publish --provenance --access public --tag "$PREID"
+ else
+ npx npm@latest publish --provenance --access public
+ fi
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 000000000..b31839c3a
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,580 @@
+# AGENTS.md
+
+This file provides context for AI coding assistants (Claude Code, Cursor, GitHub Copilot, Codex, etc.) working with the Mem0 repository.
+
+## Project Overview
+
+**Mem0** ("mem-zero") is an intelligent memory layer for AI agents and assistants. It provides persistent, personalized memory via both a hosted platform API and self-hosted open-source SDKs.
+
+- **Repository**: https://github.com/mem0ai/mem0
+- **Documentation**: https://docs.mem0.ai
+- **License**: Apache-2.0
+
+## Repository Structure
+
+This is a **polyglot monorepo** containing Python and TypeScript packages, CLIs, servers, plugins, documentation, and evaluation tooling.
+
+### Key Directories
+
+| Directory | Description |
+|-----------|-------------|
+| `mem0/` | Core Python SDK (`mem0ai` on PyPI) โ memory, LLMs, embeddings, vector stores, graphs, rerankers |
+| `mem0-ts/` | TypeScript SDK (`mem0ai` on npm) โ client + OSS memory |
+| `cli/python/` | Python CLI (`mem0-cli` on PyPI) โ Typer-based, entry point `mem0` |
+| `cli/node/` | Node CLI (`@mem0/cli` on npm) โ Commander-based, entry point `mem0` |
+| `vercel-ai-sdk/` | `@mem0/vercel-ai-provider` โ Vercel AI SDK memory provider |
+| `openclaw/` | `@mem0/openclaw-mem0` โ OpenClaw plugin for Claude Code / AI editors |
+| `server/` | FastAPI REST server for self-hosted Mem0 (Docker: FastAPI + PostgreSQL/pgvector + Neo4j) |
+| `openmemory/` | Self-hosted memory platform โ `api/` (FastAPI + Alembic + MCP server) and `ui/` (Next.js 15 + React 19) |
+| `mem0-plugin/` | AI editor plugins (Claude Code, Cursor, Codex) โ MCP server connection, lifecycle hooks, skills |
+| `skills/` | Claude Code skill definitions โ `mem0/`, `mem0-cli/`, `mem0-vercel-ai-sdk/` |
+| `docs/` | Documentation site (Mintlify) |
+| `tests/` | Python SDK tests (pytest) |
+| `evaluation/` | Benchmarking framework โ LOCOMO evals, experiment runner, score generation |
+| `examples/` | Sample projects โ demo apps, Chrome extension, multi-agent patterns |
+| `cookbooks/` | Jupyter notebooks โ customer support chatbot, AutoGen integration |
+| `embedchain/` | Legacy Embedchain RAG framework (maintained separately, Poetry-based) |
+| `pr-reviews/` | Pull request review materials |
+
+### Core Package Dependencies
+
+```
+mem0 (Python SDK) mem0-ts (TypeScript SDK)
+โโโ mem0/memory/ โโโ src/client/ (MemoryClient โ hosted)
+โโโ mem0/llms/ โโโ src/oss/ (Memory โ self-hosted)
+โโโ mem0/embeddings/ โโโ src/llms/
+โโโ mem0/vector_stores/ โโโ src/embeddings/
+โโโ mem0/graphs/ โโโ src/vector_stores/
+โโโ mem0/reranker/ โโโ src/graphs/
+
+cli/python/ โโโถ mem0ai (optional, for OSS mode)
+cli/node/ โโโถ mem0ai (npm, for API calls)
+vercel-ai-sdk/ โโโถ ai, @ai-sdk/* providers
+openclaw/ โโโถ mem0ai (npm)
+```
+
+## Development Setup
+
+### Requirements
+
+- **Python**: 3.9+ (3.10+ for CLI)
+- **Node.js**: v18+ (v20 or v22 recommended)
+- **pnpm**: v10+ (`npm install -g pnpm@10`) โ used for all TypeScript packages
+- **Hatch**: Python build/environment tool (`pip install hatch`)
+- **Docker**: Required for `server/` and `openmemory/` development
+
+### Initial Setup
+
+```bash
+# Python SDK
+hatch shell dev_py_3_11 # creates environment with all deps
+pre-commit install # install git hooks
+
+# TypeScript packages
+cd mem0-ts && pnpm install # TS SDK
+cd cli/node && pnpm install # Node CLI
+cd vercel-ai-sdk && pnpm install # Vercel AI provider
+cd openclaw && pnpm install # OpenClaw plugin
+```
+
+## Build, Lint, and Test Commands
+
+### Python SDK (`mem0/`)
+
+```bash
+# Environment setup (uses Hatch)
+hatch shell dev_py_3_11 # or dev_py_3_9, dev_py_3_10, dev_py_3_12
+
+# Linting and formatting
+make lint # ruff check
+make format # ruff format
+make sort # isort mem0/
+
+# Tests
+make test # pytest tests/
+make test-py-3.9 # test specific Python version (3.9โ3.12)
+
+# Build and publish
+make build # hatch build
+make publish # hatch publish
+```
+
+- **Python:** 3.9, 3.10, 3.11, 3.12
+- **Linter/formatter:** Ruff (line length **120**)
+- **Import sorting:** isort (`profile = "black"`)
+- **Test framework:** pytest (with pytest-mock, pytest-asyncio)
+- **Pre-commit hooks:** ruff + isort โ run `pre-commit install` before committing
+
+### TypeScript SDK (`mem0-ts/`)
+
+```bash
+cd mem0-ts
+pnpm install
+pnpm run build # tsup
+pnpm run test # jest (all tests)
+pnpm run test:unit # jest --coverage (unit tests only)
+pnpm run test:integration # jest (integration tests, needs MEM0_API_KEY)
+pnpm run test:ci # jest --coverage --ci (CI mode)
+pnpm run test:watch # jest watch mode
+```
+
+- **Node:** 20, 22 (CI-tested)
+- **Build:** tsup (CJS + ESM)
+- **Test:** jest
+- **Formatter:** prettier
+
+### Python CLI (`cli/python/`)
+
+```bash
+cd cli/python
+pip install -e ".[dev]" # dev install with ruff + pytest
+ruff check . # lint
+ruff format . # format
+pytest # test
+hatch build # build
+```
+
+- **Python:** 3.10+ (not 3.9)
+- **Linter/formatter:** Ruff (line length **100** โ different from root SDK)
+- **Ruff rules:** E, F, I, W, UP, B, SIM, RUF (ignores E501, B008 for Typer patterns, SIM108)
+- **Framework:** Typer + Rich + httpx
+- **Entry point:** `mem0 = "mem0_cli.app:main"`
+- **Source layout:** `src/mem0_cli/`
+- **Optional dependency:** `mem0ai` (for OSS mode, via `[oss]` extra)
+
+### Node CLI (`cli/node/`)
+
+```bash
+cd cli/node
+pnpm install
+pnpm run build # tsup
+pnpm run lint # biome check src/
+pnpm run lint:fix # biome check --write src/
+pnpm run typecheck # tsc --noEmit
+pnpm run test # vitest run
+pnpm run test:watch # vitest (watch mode)
+pnpm run dev # tsx src/index.ts (development)
+```
+
+- **Node:** 18+ required
+- **Build:** tsup (ESM)
+- **Linter:** Biome (not ESLint, not Ruff)
+- **Test:** vitest (not jest)
+- **Framework:** Commander + Chalk + ora + cli-table3
+
+### Vercel AI SDK Provider (`vercel-ai-sdk/`)
+
+```bash
+cd vercel-ai-sdk
+pnpm install
+pnpm run build # tsup
+pnpm run lint # eslint
+pnpm run type-check # tsc --noEmit
+pnpm run prettier-check # prettier --check
+pnpm run test # jest
+pnpm run test:edge # vitest (edge runtime)
+pnpm run test:node # vitest (node runtime)
+```
+
+- **Build:** tsup (CJS + ESM)
+- **Lint:** ESLint + Prettier
+- **Test:** jest + vitest (edge/node configs)
+
+### OpenClaw Plugin (`openclaw/`)
+
+```bash
+cd openclaw
+pnpm install
+pnpm run build # tsup
+pnpm run test # vitest run
+```
+
+- **Build:** tsup (ESM)
+- **Test:** vitest (with Codecov in CI)
+- **Plugin manifest:** `openclaw.plugin.json`
+
+### Server (`server/`)
+
+```bash
+# Docker production build
+cd server
+make build # docker build -t mem0-api-server .
+make run_local # docker run -p 8000:8000 with .env
+
+# Docker Compose development (FastAPI + PostgreSQL/pgvector + Neo4j)
+cd server
+docker-compose up # starts all 3 services
+# mem0 API: localhost:8888
+# PostgreSQL: localhost:8432
+# Neo4j HTTP: localhost:8474, Bolt: localhost:8687
+```
+
+- **Framework:** FastAPI with uvicorn (auto-reload in dev)
+- **Services:** PostgreSQL with pgvector, Neo4j 5.x with APOC plugin
+- **Hot reload:** Dev Dockerfile mounts `server/` and `mem0/` for live changes
+
+### OpenMemory (`openmemory/`)
+
+```bash
+# Full stack via Docker Compose
+cd openmemory
+docker-compose up
+# Qdrant: localhost:6333
+# API (MCP): localhost:8765
+# UI: localhost:3000
+
+# Individual development
+cd openmemory/api && uvicorn main:app --reload # FastAPI backend
+cd openmemory/ui && npm run dev # Next.js frontend
+
+# Tests
+cd openmemory/api && pytest tests/ # API tests (e.g., test_mcp_server.py)
+```
+
+- **API:** FastAPI + Alembic (DB migrations) + MCP server (Model Context Protocol)
+- **UI:** Next.js 15, React 19, Radix UI, Redux Toolkit, TailwindCSS, Recharts
+- **Vector store:** Qdrant
+
+### Documentation (`docs/`)
+
+```bash
+make docs # or: cd docs && mintlify dev
+```
+
+- **Framework:** Mintlify
+- **API spec:** `docs/openapi.json`
+- **Structure:** `api-reference/`, `open-source/`, `platform/`, `integrations/`, `cookbooks/`, `core-concepts/`
+
+### Evaluation (`evaluation/`)
+
+```bash
+cd evaluation
+make run-mem0-add # Run mem0 add experiments
+make run-mem0-search # Run mem0 search experiments
+make run-mem0-plus-add # With graph memory
+make run-mem0-plus-search # With graph memory
+make run-rag # RAG baseline
+make run-full-context # Full context baseline
+make run-langmem # LangMem comparison
+make run-openai # OpenAI comparison
+```
+
+## Core APIs
+
+### Python
+
+| Function / Class | Purpose | Import |
+|-----------------|---------|--------|
+| `Memory` | Self-hosted memory (sync) | `from mem0 import Memory` |
+| `AsyncMemory` | Self-hosted memory (async) | `from mem0 import AsyncMemory` |
+| `MemoryClient` | Hosted platform client (sync) | `from mem0 import MemoryClient` |
+| `AsyncMemoryClient` | Hosted platform client (async) | `from mem0 import AsyncMemoryClient` |
+
+**Key `Memory` / `MemoryClient` methods:**
+
+| Method | Purpose |
+|--------|---------|
+| `add(messages, *, user_id, agent_id, run_id, metadata)` | Store a new memory |
+| `search(query, *, user_id, agent_id, run_id, limit, filters)` | Search memories |
+| `get(memory_id)` | Retrieve a single memory by ID |
+| `get_all(*, user_id, agent_id, run_id, limit)` | List all memories |
+| `update(memory_id, data)` | Update a memory |
+| `delete(memory_id)` | Delete a memory |
+| `delete_all(*, user_id, agent_id, run_id)` | Delete all memories |
+| `history(memory_id)` | Get change history for a memory |
+
+### TypeScript
+
+| Export | Purpose | Import |
+|--------|---------|--------|
+| `MemoryClient` | Hosted platform client | `import { MemoryClient } from 'mem0ai'` |
+| `Memory` | Self-hosted OSS memory | `import { Memory } from 'mem0ai/oss'` |
+
+## Import Patterns
+
+### Python
+
+| What | Import |
+|------|--------|
+| Core memory classes | `from mem0 import Memory, AsyncMemory` |
+| Platform client | `from mem0 import MemoryClient, AsyncMemoryClient` |
+| Configuration | `from mem0.configs.base import MemoryConfig` |
+| LLM providers | `from mem0.llms. import ` |
+| Embedding providers | `from mem0.embeddings. import ` |
+| Vector store providers | `from mem0.vector_stores. import ` |
+
+### TypeScript
+
+| What | Import |
+|------|--------|
+| Hosted client | `import { MemoryClient } from 'mem0ai'` |
+| OSS memory | `import { Memory } from 'mem0ai/oss'` |
+| Specific providers (OSS) | `import { OpenAIEmbedding } from 'mem0ai/oss'` |
+
+## Coding Standards
+
+### File Naming Conventions
+
+- **Python source files:** `snake_case.py` (e.g., `azure_openai.py`, `cohere_reranker.py`)
+- **Python test files:** `test_.py` (e.g., `test_memory.py`, `test_main.py`)
+- **TypeScript source files:** `snake_case.ts` (e.g., `azure_ai_search.ts`)
+- **TypeScript test files:** `.test.ts` (e.g., `memory.test.ts`)
+- **Config/manifest files:** `kebab-case` (e.g., `openclaw.plugin.json`, `jest.config.js`)
+
+### Python Conventions
+
+- **Provider pattern:** All providers (LLMs, embeddings, vector stores, graphs, rerankers) inherit from a `base.py` abstract class in their directory. Config classes live in `configs.py`.
+- **Pydantic v2** for all data models and configuration.
+- **Ruff** is the single linting and formatting tool โ no black, no flake8.
+ - Root SDK: line length **120**
+ - Python CLI: line length **100** with extended rule set (UP, B, SIM, RUF)
+- **isort** with `profile = "black"` for import sorting.
+- Ruff excludes `embedchain/` and `openmemory/` from root config.
+
+### TypeScript Conventions
+
+- **Build:** tsup across all packages.
+- **Package manager:** pnpm everywhere (no npm, no yarn).
+- **TypeScript strict mode** across all packages.
+- **Linting varies by package:**
+
+| Package | Linter | Formatter | Test Framework |
+|---------|--------|-----------|---------------|
+| `mem0-ts/` | โ | Prettier | jest |
+| `cli/node/` | Biome | Biome | vitest |
+| `vercel-ai-sdk/` | ESLint | Prettier | jest + vitest |
+| `openclaw/` | โ | โ | vitest |
+
+### Type Checking
+
+Always run type checking after modifying TypeScript code:
+
+```bash
+cd && pnpm run typecheck # or: tsc --noEmit
+```
+
+## Architecture
+
+### Provider Pattern
+
+The SDK uses a consistent plugin architecture across 5 categories. Each category has a `base.py` abstract class and concrete provider implementations:
+
+| Category | Count | Examples |
+|----------|-------|---------|
+| **LLMs** | 24 | OpenAI, Anthropic, AWS Bedrock, Azure OpenAI, Gemini, Groq, Ollama, Together, DeepSeek, vLLM, LiteLLM, LM Studio, xAI |
+| **Vector Stores** | 30 | Qdrant, Pinecone, Chroma, Weaviate, Milvus, MongoDB, Redis, Elasticsearch, pgvector, Supabase, Faiss, S3 Vectors |
+| **Embeddings** | 15 | OpenAI, Azure OpenAI, Gemini, HuggingFace, FastEmbed, Together, AWS Bedrock, Ollama, Vertex AI |
+| **Graph Stores** | 4 | Neo4j, Memgraph, Kuzu, Apache AGE |
+| **Rerankers** | 5 | Cohere, HuggingFace, LLM-based, Sentence Transformer, Zero Entropy |
+
+### Two Usage Modes
+
+Self-hosted `Memory` / `AsyncMemory` classes and hosted-platform `MemoryClient` โ both in Python and TypeScript.
+
+### Graph Memory
+
+Optional layer on top of vector memory for relationship-aware retrieval. Configured via the `graph` section of `MemoryConfig`.
+
+### MCP Integration
+
+Model Context Protocol support in multiple places:
+
+- **Remote:** MCP server at `mcp.mem0.ai`
+- **Local:** MCP server in `openmemory/api/` (FastAPI-based)
+- **Plugin:** MCP tools in `mem0-plugin/` โ 9 tools: `add_memory`, `search_memories`, `get_memories`, `get_memory`, `update_memory`, `delete_memory`, `delete_all_memories`, `delete_entities`, `list_entities`
+
+### Plugin & Skills System
+
+- `mem0-plugin/` provides integrations for Claude Code, Cursor, and Codex via MCP server connections and lifecycle hooks for automatic memory capture.
+- `skills/` contains structured skill definitions for AI agents, covering SDK usage, CLI workflows, and Vercel AI SDK patterns.
+
+### Adding a New Provider
+
+To add a new LLM, embedding, vector store, or reranker provider:
+
+1. Create `mem0//.py`
+2. Inherit from the abstract base class in `mem0//base.py`
+3. Add configuration to `mem0//configs.py` (if the category uses one)
+4. Register the provider in `mem0//__init__.py`
+5. Add tests in `tests///`
+6. Add any new dependencies to the appropriate optional group in `pyproject.toml` (never to core `dependencies`)
+7. Follow the exact pattern of existing providers in the same category โ match method signatures, error handling, and config structure
+
+## CI/CD
+
+### CI Workflows (automated testing)
+
+| Workflow | File | Triggers | Tests |
+|----------|------|----------|-------|
+| Python SDK | `ci.yml` | Push to main, PRs on `mem0/`, `tests/`, `pyproject.toml` | Ruff lint + pytest on Python 3.10, 3.11, 3.12 |
+| TypeScript SDK | `ts-sdk-ci.yml` | Push to main, PRs on `mem0-ts/` | Prettier + build + jest on Node 20, 22 |
+| Python CLI | `cli-python-ci.yml` | Push to `cli/python/`, PRs, manual | Ruff lint + pytest + hatch build on Python 3.10, 3.11, 3.12 |
+| Node CLI | `cli-node-ci.yml` | Push to `cli/node/`, PRs, manual | Biome lint + tsc + vitest + tsup build on Node 20, 22 |
+| OpenClaw | `openclaw-checks.yml` | Push to `openclaw/`, PRs, manual | tsc + vitest (with Codecov) + tsup build on Node 20, 22 |
+| Embedchain | `ci.yml` (shared) | PRs on `embedchain/` | Ruff + pytest + coverage on Python 3.9โ3.12 |
+
+### CD Workflows (automated publishing)
+
+| Workflow | File | Tag Prefix | Target |
+|----------|------|------------|--------|
+| Python SDK | `cd.yml` | `v*` | PyPI (`mem0ai`) |
+| TypeScript SDK | `ts-sdk-cd.yml` | `ts-v*` | npm (`mem0ai`) |
+| Python CLI | `cli-python-cd.yml` | `cli-v*` | PyPI (`mem0-cli`) |
+| Node CLI | `cli-node-cd.yml` | `cli-node-v*` | npm (`@mem0/cli`) |
+| Vercel AI SDK | `vercel-ai-cd.yml` | `vercel-ai-v*` | npm (`@mem0/vercel-ai-provider`) |
+| OpenClaw | `openclaw-cd.yml` | `openclaw-v*` | npm (`@mem0/openclaw-mem0`) |
+
+- All publishing uses **OIDC trusted publishing** โ no tokens or secrets required.
+- First publish of a new npm package must be done manually; OIDC works for subsequent versions.
+
+### Utility Workflows
+
+| Workflow | File | Purpose |
+|----------|------|---------|
+| Issue Labeler | `issue-labeler.yml` | Automatic issue labeling |
+| Stale Bot | `stale.yml` | Marks stale issues and PRs |
+
+## Task Completion Guidelines
+
+These guidelines outline typical artifacts for different task types. Use judgment to adapt based on scope and context.
+
+### Bug Fixes
+
+1. **Unit tests**: Add tests that would fail without the fix (regression tests)
+2. **Implementation**: Fix the bug
+3. **Manual verification**: Run the relevant test suite to confirm the fix
+4. **Lint**: Run the appropriate linter for the package you modified
+
+### New Features
+
+1. **Implementation**: Build the feature following existing patterns
+2. **Unit tests**: Comprehensive test coverage for new functionality
+3. **Documentation**: Update relevant docs in `docs/` for public APIs
+4. **Examples**: Add usage examples if the feature introduces new user-facing behavior
+
+### New Provider (LLM / Embedding / Vector Store / Reranker)
+
+1. **Implementation**: Follow the "Adding a New Provider" steps above
+2. **Tests**: Add unit tests matching the pattern of existing providers
+3. **Configuration**: Add to the appropriate `configs.py` and `__init__.py`
+4. **Dependencies**: Add to the correct optional group in `pyproject.toml`
+5. **Documentation**: Add an integration guide in `docs/integrations/`
+
+### Refactoring / Internal Changes
+
+- Unit tests for any changed behavior
+- No documentation needed for internal-only changes
+- Ensure all existing tests still pass
+
+### When to Deviate
+
+These are guidelines, not rigid rules. Adjust based on:
+
+- **Scope**: Trivial fixes (typos, comments) may not need tests
+- **Visibility**: Internal changes may not need documentation
+- **Context**: Some changes span multiple categories โ use judgment
+
+When uncertain about expected artifacts, ask for clarification.
+
+## Contributing Guidelines
+
+### Workflow
+
+1. Fork and clone the repository.
+2. Create a feature branch from `main` (e.g., `feature/my-new-feature`).
+3. Make your changes โ add tests, docs, and examples as appropriate.
+4. Run linting and tests for every package you modified (see commands above).
+5. Run `pre-commit install` on first setup โ hooks run ruff + isort automatically.
+6. Commit with a clear message following [Conventional Commits](https://www.conventionalcommits.org/) (e.g., `feat:`, `fix:`, `docs:`, `refactor:`).
+7. Push and open a Pull Request against `main`.
+
+### Pull Request Requirements
+
+Every PR must follow the repo's PR template (`.github/PULL_REQUEST_TEMPLATE.md`):
+
+1. **Linked Issue** โ Reference the issue with `Closes #`. If no issue exists, create one first or explain why in the description.
+2. **Description** โ Explain what the PR does and why it's needed.
+3. **Type of Change** โ Check the appropriate box:
+ - Bug fix / New feature / Breaking change / Refactor / Documentation update
+4. **Breaking Changes** โ If applicable, describe what breaks and the migration path.
+5. **Test Coverage** โ Check what applies:
+ - Added/updated unit tests
+ - Added/updated integration tests
+ - Tested manually (describe how)
+ - No tests needed (explain why)
+6. **Checklist** โ All must be checked before merge:
+ - [ ] Code follows the project's style guidelines
+ - [ ] Self-review performed
+ - [ ] Tests added that prove the fix/feature works
+ - [ ] New and existing tests pass locally
+ - [ ] Documentation updated if needed
+
+### PR Description Template
+
+```markdown
+## Linked Issue
+
+Closes #
+
+## Description
+
+
+
+## Type of Change
+
+- [ ] Bug fix (non-breaking change that fixes an issue)
+- [ ] New feature (non-breaking change that adds functionality)
+- [ ] Breaking change (fix or feature that would cause existing functionality to change)
+- [ ] Refactor (no functional changes)
+- [ ] Documentation update
+
+## Breaking Changes
+
+N/A
+
+## Test Coverage
+
+- [ ] I added/updated unit tests
+- [ ] I added/updated integration tests
+- [ ] I tested manually (describe below)
+- [ ] No tests needed (explain why)
+
+## Checklist
+
+- [ ] My code follows the project's style guidelines
+- [ ] I have performed a self-review of my code
+- [ ] I have added tests that prove my fix/feature works
+- [ ] New and existing tests pass locally
+- [ ] I have updated documentation if needed
+```
+
+### General Rules
+
+- Follow existing code patterns โ don't introduce new frameworks or abstractions without discussion.
+- Version bumps go in `pyproject.toml` (Python) or `package.json` (TypeScript).
+- For `server/` and `openmemory/` work, use Docker Compose for local development.
+- Do NOT use `pip` or `conda` for dependency management โ use `hatch` (see `docs/contributing/development.mdx`).
+
+### Contributing Guides
+
+| Task | Guide |
+|------|-------|
+| Code contributions | `docs/contributing/development.mdx` |
+| Documentation contributions | `docs/contributing/documentation.mdx` |
+| PR template | `.github/PULL_REQUEST_TEMPLATE.md` |
+| Bug reports | `.github/ISSUE_TEMPLATE/bug_report.yml` |
+| Feature requests | `.github/ISSUE_TEMPLATE/feature_request.yml` |
+| Documentation issues | `.github/ISSUE_TEMPLATE/documentation_issue.yml` |
+
+## Do NOT
+
+- Modify CI/CD workflows without explicit approval.
+- Add new Python dependencies to the core `dependencies` list in `pyproject.toml` without discussion โ use optional dependency groups instead.
+- Commit `.env` files, API keys, or credentials.
+- Modify `embedchain/` unless specifically working on that package โ it has its own build system (Poetry).
+- Skip pre-commit hooks.
+- Use npm or yarn in TypeScript packages โ this repo uses pnpm exclusively.
+- Use `require()` for imports in TypeScript โ use ES module `import` syntax.
+- Mix up linter configs: root Python SDK uses line-length 120, Python CLI uses 100, Node CLI uses Biome (not ESLint/Ruff).
+- Modify `openmemory/` database migrations without understanding the Alembic migration chain.
+- Change public APIs without updating documentation in `docs/`.
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 120000
index 000000000..47dc3e3d8
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1 @@
+AGENTS.md
\ No newline at end of file
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index c27d3a023..80a9852f6 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -61,3 +61,31 @@ make test # After activating a shell with hatch shell test_XX
Make sure that all tests pass across all supported Python versions before submitting a pull request.
We look forward to your pull requests and can't wait to see your contributions!
+
+### ๐ Releasing
+
+All packages are published automatically via GitHub Actions when a GitHub Release is created with the correct tag prefix.
+
+#### Tag Prefixes
+
+| Package | Registry | Tag Prefix | Example |
+|---------|----------|------------|---------|
+| `mem0ai` (Python SDK) | PyPI | `v*` | `v0.1.31` |
+| `mem0-cli` (Python CLI) | PyPI | `cli-v*` | `cli-v0.2.1` |
+| `mem0ai` (TypeScript SDK) | npm | `ts-v*` | `ts-v2.4.6` |
+| `@mem0/cli` (Node CLI) | npm | `cli-node-v*` | `cli-node-v0.1.2` |
+| `@mem0/vercel-ai-provider` | npm | `vercel-ai-v*` | `vercel-ai-v2.0.6` |
+| `@mem0/openclaw-mem0` | npm | `openclaw-v*` | `openclaw-v1.0.1` |
+
+#### How to Release
+
+1. Bump the version in `pyproject.toml` (Python) or `package.json` (Node)
+2. Create a [GitHub Release](https://github.com/mem0ai/mem0/releases/new) with the matching tag prefix
+3. The correct workflow will trigger automatically โ verify in the [Actions tab](https://github.com/mem0ai/mem0/actions)
+
+#### Publishing Details
+
+- **PyPI packages** use OIDC trusted publishing via `pypa/gh-action-pypi-publish`
+- **npm packages** use OIDC trusted publishing via npm CLI (>= 11.5.1) โ no tokens or secrets required
+- All workflows require `permissions: id-token: write` for OIDC authentication
+- First publish of a new npm package must be done manually; OIDC works for subsequent versions
diff --git a/LLM.md b/LLM.md
index 69c08cccc..693f69ae2 100644
--- a/LLM.md
+++ b/LLM.md
@@ -266,14 +266,14 @@ config = MemoryConfig(
graph_store=GraphStoreConfig(provider="neo4j", config={...}), # optional
history_db_path="~/.mem0/history.db",
version="v1.1",
- custom_fact_extraction_prompt="Custom prompt...",
+ custom_instructions="Custom prompt...",
custom_update_memory_prompt="Custom prompt..."
)
```
### Supported Providers
-#### LLM Providers (19 supported)
+#### LLM Providers (20 supported)
- **openai** - OpenAI GPT models (default)
- **anthropic** - Claude models
- **gemini** - Google Gemini
@@ -284,6 +284,7 @@ config = MemoryConfig(
- **azure_openai** - Azure OpenAI
- **litellm** - LiteLLM proxy
- **deepseek** - DeepSeek models
+- **minimax** - MiniMax models
- **xai** - xAI models
- **sarvam** - Sarvam AI
- **lmstudio** - LM Studio local server
@@ -683,7 +684,7 @@ Conversation: {messages}
"""
config = MemoryConfig(
- custom_fact_extraction_prompt=custom_extraction_prompt
+ custom_instructions=custom_extraction_prompt
)
memory = Memory(config)
```
diff --git a/README.md b/README.md
index 0d70bf230..ff65b697b 100644
--- a/README.md
+++ b/README.md
@@ -15,8 +15,6 @@
Join Discord
ยท
Demo
- ยท
- OpenMemory
@@ -102,6 +100,20 @@ Install sdk via npm:
npm install mem0ai
```
+### CLI
+
+Manage memories from your terminal:
+
+```bash
+npm install -g @mem0/cli # or: pip install mem0-cli
+
+mem0 init
+mem0 add "Prefers dark mode and vim keybindings" --user-id alice
+mem0 search "What does Alice prefer?" --user-id alice
+```
+
+See the [CLI documentation](https://docs.mem0.ai/platform/cli) for the full command reference.
+
### Basic Usage
Mem0 requires an LLM to function, with `gpt-4.1-nano-2025-04-14` from OpenAI as the default. However, it supports a variety of LLMs; for details, refer to our [Supported LLMs documentation](https://docs.mem0.ai/components/llms/overview).
@@ -159,7 +171,7 @@ For detailed integration steps, see the [Quickstart](https://docs.mem0.ai/quicks
## ๐ Documentation & Support
- Full docs: https://docs.mem0.ai
-- Community: [Discord](https://mem0.dev/DiG) ยท [Twitter](https://x.com/mem0ai)
+- Community: [Discord](https://mem0.dev/DiG) ยท [X (formerly Twitter)](https://x.com/mem0ai)
- Contact: founders@mem0.ai
## Citation
@@ -177,4 +189,4 @@ We now have a paper you can cite:
## โ๏ธ License
-Apache 2.0 โ see the [LICENSE](https://github.com/mem0ai/mem0/blob/main/LICENSE) file for details.
\ No newline at end of file
+Apache 2.0 โ see the [LICENSE](https://github.com/mem0ai/mem0/blob/main/LICENSE) file for details.
diff --git a/cli/CLI_SPECIFICATION.md b/cli/CLI_SPECIFICATION.md
new file mode 100644
index 000000000..a243e833c
--- /dev/null
+++ b/cli/CLI_SPECIFICATION.md
@@ -0,0 +1,1613 @@
+# mem0 CLI SDK Specification
+
+Complete reference for the mem0 CLI. This document is the authoritative guide for any developer or AI agent working on this SDK.
+
+---
+
+## Table of Contents
+
+1. [Project Overview](#1-project-overview)
+2. [Architecture](#2-architecture)
+3. [Complete Command Reference](#3-complete-command-reference)
+4. [API Endpoints](#4-api-endpoints)
+5. [Configuration](#5-configuration)
+6. [Key Behavioral Patterns](#6-key-behavioral-patterns)
+7. [Output Modes](#7-output-modes)
+8. [Agent-Friendly Design Decisions](#8-agent-friendly-design-decisions)
+9. [Adding a New Command](#9-adding-a-new-command)
+10. [Adding a New Language Implementation](#10-adding-a-new-language-implementation)
+
+---
+
+## 1. Project Overview
+
+### What is mem0 CLI?
+
+mem0 CLI is the official command-line interface for [mem0](https://mem0.ai) -- the memory layer for AI agents. It lets developers and AI agents add, search, list, update, and delete memories via the mem0 Platform API from the terminal.
+
+### Who is it for?
+
+- Developers integrating mem0 into their workflows
+- AI agents that need persistent memory (the CLI is designed with `--json`/`--agent` global flags and `help --json` specifically for machine consumption)
+- DevOps/CI pipelines that need to manage memories programmatically
+
+### Project Structure
+
+The `cli/` directory provides the mem0 CLI in two languages with a shared specification for behavioral consistency.
+
+| Language | Directory | Package Name | Install Command |
+|------------|------------|---------------|----------------------------|
+| TypeScript | `node/` | `@mem0/cli` | `npm install -g @mem0/cli` |
+| Python | `python/` | `mem0-cli` | `pip install mem0-cli` |
+
+Both implementations produce a binary named `mem0` and provide **identical CLI behavior** -- same commands, same options, same output formats, same error messages.
+
+### Version
+
+Current version: `0.1.0` (defined in `cli-spec.json`, `node/package.json`, and `python/pyproject.toml`).
+
+### License
+
+Apache-2.0
+
+---
+
+## 2. Architecture
+
+### Directory Layout
+
+```
+.
+โโโ cli-spec.json # Shared CLI specification (source of truth)
+โโโ README.md # CLI README
+โโโ SDK_SPECIFICATION.md # This file
+โโโ python/
+โ โโโ pyproject.toml # Python package config (hatchling build)
+โ โโโ README.md
+โ โโโ src/mem0_cli/
+โ โโโ __init__.py # __version__
+โ โโโ app.py # Main Typer app, command registration, helpers
+โ โโโ config.py # Config loading/saving, env var overrides
+โ โโโ branding.py # Colors, icons, banner, timed_status, print helpers
+โ โโโ output.py # Output formatting (text, json, table, quiet)
+โ โโโ backend/
+โ โ โโโ __init__.py # Re-exports get_backend
+โ โ โโโ base.py # Abstract Backend ABC, get_backend factory
+โ โ โโโ platform.py # PlatformBackend (httpx), error classes
+โ โโโ commands/
+โ โโโ memory.py # cmd_add, cmd_search, cmd_get, cmd_list, cmd_update, cmd_delete, cmd_delete_all
+โ โโโ init_cmd.py # run_init (interactive wizard)
+โ โโโ config_cmd.py # cmd_config_show, cmd_config_get, cmd_config_set
+โ โโโ entities.py # cmd_entities_list, cmd_entities_delete
+โ โโโ events_cmd.py # cmd_event_list, cmd_event_status
+โ โโโ utils.py # cmd_status, cmd_version, cmd_import
+โโโ node/
+ โโโ package.json # Node package config (tsup build)
+ โโโ README.md
+ โโโ src/
+ โโโ index.ts # Main Commander.js app, command registration, helpers
+ โโโ config.ts # Config loading/saving, env var overrides
+ โโโ branding.ts # Colors, icons, banner, timedStatus, print helpers
+ โโโ output.ts # Output formatting (text, json, table, quiet)
+ โโโ state.ts # Agent mode flag (setAgentMode, isAgentMode)
+ โโโ help.ts # Rich-style help formatter (panels, command ordering)
+ โโโ backend/
+ โ โโโ index.ts # Re-exports
+ โ โโโ base.ts # Backend interface, error classes, getBackend factory
+ โ โโโ platform.ts # PlatformBackend (native fetch), _buildFilters
+ โโโ commands/
+ โโโ memory.ts # cmdAdd, cmdSearch, cmdGet, cmdList, cmdUpdate, cmdDelete, cmdDeleteAll
+ โโโ init.ts # runInit (interactive wizard)
+ โโโ config.ts # cmdConfigShow, cmdConfigGet, cmdConfigSet
+ โโโ entities.ts # cmdEntitiesList, cmdEntitiesDelete
+ โโโ events.ts # cmdEventList, cmdEventStatus
+ โโโ utils.ts # cmdStatus, cmdVersion, cmdImport
+```
+
+### How Both CLIs Mirror Each Other
+
+Every command, option, argument, and behavioral pattern is implemented identically in both CLIs. The shared `cli-spec.json` is the source of truth for:
+
+- All command names, descriptions, and usage strings
+- All arguments and options (names, types, defaults, help text, panel grouping)
+- API endpoint paths and methods
+- Branding constants (colors, icons, logo)
+- Config schema (sections, fields, env var mappings)
+- Error messages and templates
+- Option grouping (Scope, Search, Pagination, Filters, Output, Connection)
+
+### Tech Stacks
+
+| Concern | Python | Node |
+|------------------|-------------------------------------|-----------------------------------------|
+| CLI framework | Typer >= 0.9.0 | Commander.js ^12.0.0 |
+| Rich output | Rich >= 13.0.0 | chalk ^5.3.0 + cli-table3 ^0.6.4 |
+| Spinners | Rich Status | ora ^8.0.0 |
+| Boxed panels | Rich Panel | boxen ^7.1.0 |
+| HTTP client | httpx >= 0.24.0 | Native fetch (Node >= 18) |
+| Build system | Hatchling | tsup ^8.0.0 |
+| Test framework | pytest >= 7.0 | vitest ^1.5.0 |
+| Linter | ruff >= 0.1.0 | Biome ^1.7.0 |
+| Type checking | (ruff type checks) | TypeScript ^5.4.0 |
+| Min runtime | Python >= 3.10 | Node >= 18.0.0 |
+| Module format | Standard Python package | ESM (`"type": "module"`) |
+| Entrypoint | `mem0 = "mem0_cli.app:main"` | `"bin": { "mem0": "./dist/index.js" }` |
+
+---
+
+## 3. Complete Command Reference
+
+### 3.1 `init`
+
+Interactive setup wizard for mem0 CLI.
+
+| Property | Value |
+|------------------|-------|
+| Usage | `mem0 init [OPTIONS]` |
+| needsBackend | No |
+| needsConfig | No |
+| resolveIds | No |
+| resolveGraph | No |
+| confirmDangerous | No |
+
+**Options:**
+
+| Flag | Type | Required | Default | Help |
+|-----------------|--------|----------|---------|------|
+| `--api-key` | string | No | - | API key (skip prompt). |
+| `-u, --user-id` | string | No | - | Default user ID (skip prompt). |
+| `--email` | string | No | - | Login via email verification code. |
+| `--code` | string | No | - | Verification code (use with --email for non-interactive login). |
+| `--force` | bool | No | false | Overwrite existing config without confirmation. |
+
+**Behavior:**
+
+*Existing config protection:*
+- If `~/.mem0/config.json` exists with an API key, the CLI warns and asks for confirmation before overwriting.
+- In non-TTY mode, this is a hard error unless `--force` is passed.
+- `--force` skips the confirmation in both TTY and non-TTY modes.
+
+*Email login flow (when `--email` is provided):*
+- Sends a 6-digit verification code to the email via `POST /api/v1/auth/email_code/`.
+- If `--code` is also provided, verifies immediately (fully non-interactive).
+- If `--code` is not provided, prompts for the code interactively.
+- On success: receives API key, org_id, project_id. Saves to config. Creates account if email is new.
+- Cannot be combined with `--api-key`.
+
+*API key flow (existing behavior):*
+- If both `--api-key` and `--user-id` are provided, runs non-interactively (no prompts).
+- If running in a non-TTY without both flags, prints an error with usage hint and exits.
+- Interactive mode: prints banner, prompts for API key (masked with `*`), prompts for default user ID (default: `mem0-cli`), validates connection, saves config.
+- API key input uses raw terminal mode to echo `*` for each character typed. Supports backspace and Ctrl+U (clear line).
+
+**Examples:**
+```bash
+mem0 init
+mem0 init --api-key m0-xxx --user-id alice
+mem0 init --api-key m0-xxx --user-id alice --force
+mem0 init --email alice@company.com
+mem0 init --email alice@company.com --code 482901
+```
+
+---
+
+### 3.2 `add`
+
+Add a memory from text, messages, file, or stdin.
+
+| Property | Value |
+|------------------|-------|
+| Usage | `mem0 add [OPTIONS]` |
+| needsBackend | Yes |
+| needsConfig | Yes |
+| resolveIds | Yes |
+| resolveGraph | Yes |
+| confirmDangerous | No |
+| Output formats | text, json, quiet |
+| Default output | text |
+| API endpoint | `POST /v1/memories/` |
+
+**Arguments:**
+
+| Name | Type | Required | Help |
+|--------|--------|----------|------|
+| `text` | string | No | Text content to add as a memory. |
+
+**Options:**
+
+| Flag | Type | Default | Panel | Help |
+|------------------|---------|---------|------------|------|
+| `-u, --user-id` | string | - | Scope | Scope to user. |
+| `--agent-id` | string | - | Scope | Scope to agent. |
+| `--app-id` | string | - | Scope | Scope to app. |
+| `--run-id` | string | - | Scope | Scope to run. |
+| `--messages` | string | - | - | Conversation messages as JSON. |
+| `-f, --file` | path | - | - | Read messages from JSON file. |
+| `-m, --metadata` | string | - | - | Custom metadata as JSON. |
+| `--immutable` | boolean | false | - | Prevent future updates. |
+| `--no-infer` | boolean | false | - | Skip inference, store raw. |
+| `--expires` | string | - | - | Expiration date (YYYY-MM-DD). |
+| `--categories` | string | - | - | Categories (JSON array or comma-separated). |
+| `--graph` | boolean | false | Scope | Enable graph memory extraction. |
+| `--no-graph` | boolean | false | Scope | Disable graph memory extraction. |
+| `-o, --output` | string | "text" | Output | Output format: text, json, quiet. |
+| `--api-key` | string | - | Connection | Override API key. |
+| `--base-url` | string | - | Connection | Override API base URL. |
+
+**Input priority:** `--file` > `--messages` > text argument > stdin (if piped and no text).
+
+**Content wrapping:** Text content is wrapped as `[{"role": "user", "content": ""}]` before sending to the API. Messages from `--messages` or `--file` are sent as-is.
+
+**Examples:**
+```bash
+mem0 add "I prefer dark mode" --user-id alice
+echo "text" | mem0 add -u alice
+mem0 add --file msgs.json -u alice -o json
+```
+
+---
+
+### 3.3 `search`
+
+Search memories by semantic query.
+
+| Property | Value |
+|------------------|-------|
+| Usage | `mem0 search [OPTIONS]` |
+| needsBackend | Yes |
+| needsConfig | Yes |
+| resolveIds | Yes |
+| resolveGraph | Yes |
+| confirmDangerous | No |
+| Output formats | text, json, table |
+| Default output | text |
+| API endpoint | `POST /v2/memories/search/` |
+
+**Arguments:**
+
+| Name | Type | Required | Help |
+|---------|--------|----------|------|
+| `query` | string | Yes | Search query. |
+
+**Options:**
+
+| Flag | Type | Default | Panel | Help |
+|-------------------------|---------|---------|------------|------|
+| `-u, --user-id` | string | - | Scope | Filter by user. |
+| `--agent-id` | string | - | Scope | Filter by agent. |
+| `--app-id` | string | - | Scope | Filter by app. |
+| `--run-id` | string | - | Scope | Filter by run. |
+| `-k, --top-k, --limit` | integer | 10 | Search | Number of results. |
+| `--threshold` | float | 0.3 | Search | Minimum similarity score. |
+| `--rerank` | boolean | false | Search | Enable reranking (Platform only). |
+| `--keyword` | boolean | false | Search | Use keyword search. |
+| `--filter` | string | - | Search | Advanced filter expression (JSON). |
+| `--fields` | string | - | Search | Specific fields to return (comma-separated). |
+| `--graph` | boolean | false | Search | Enable graph in search. |
+| `--no-graph` | boolean | false | Search | Disable graph in search. |
+| `-o, --output` | string | "text" | Output | Output: text, json, table. |
+| `--api-key` | string | - | Connection | Override API key. |
+| `--base-url` | string | - | Connection | Override API base URL. |
+
+**Stdin fallback:** If no query argument is provided and stdin is piped, reads query from stdin.
+
+**Examples:**
+```bash
+mem0 search "preferences" --user-id alice
+mem0 search "tools" -u alice -o json -k 5
+echo "preferences" | mem0 search -u alice
+```
+
+---
+
+### 3.4 `get`
+
+Get a specific memory by ID.
+
+| Property | Value |
+|------------------|-------|
+| Usage | `mem0 get [OPTIONS]` |
+| needsBackend | Yes |
+| needsConfig | No |
+| resolveIds | No |
+| resolveGraph | No |
+| confirmDangerous | No |
+| Output formats | text, json |
+| Default output | text |
+| API endpoint | `GET /v1/memories/{memory_id}/` |
+
+**Arguments:**
+
+| Name | Type | Required | Help |
+|-------------|--------|----------|------|
+| `memory_id` | string | Yes | Memory ID to retrieve. |
+
+**Options:**
+
+| Flag | Type | Default | Panel | Help |
+|----------------|--------|---------|------------|------|
+| `-o, --output` | string | "text" | Output | Output: text, json. |
+| `--api-key` | string | - | Connection | Override API key. |
+| `--base-url` | string | - | Connection | Override API base URL. |
+
+**Examples:**
+```bash
+mem0 get abc-123-def-456
+mem0 get abc-123-def-456 -o json
+```
+
+---
+
+### 3.5 `list`
+
+List memories with optional filters.
+
+| Property | Value |
+|------------------|-------|
+| Usage | `mem0 list [OPTIONS]` |
+| needsBackend | Yes |
+| needsConfig | Yes |
+| resolveIds | Yes |
+| resolveGraph | Yes |
+| confirmDangerous | No |
+| Output formats | text, json, table |
+| Default output | table |
+| API endpoint | `POST /v2/memories/` |
+
+**Arguments:** None.
+
+**Options:**
+
+| Flag | Type | Default | Panel | Help |
+|------------------|---------|---------|------------|------|
+| `-u, --user-id` | string | - | Scope | Filter by user. |
+| `--agent-id` | string | - | Scope | Filter by agent. |
+| `--app-id` | string | - | Scope | Filter by app. |
+| `--run-id` | string | - | Scope | Filter by run. |
+| `--page` | integer | 1 | Pagination | Page number. |
+| `--page-size` | integer | 100 | Pagination | Results per page. |
+| `--category` | string | - | Filters | Filter by category. |
+| `--after` | string | - | Filters | Created after (YYYY-MM-DD). |
+| `--before` | string | - | Filters | Created before (YYYY-MM-DD). |
+| `--graph` | boolean | false | Filters | Enable graph in listing. |
+| `--no-graph` | boolean | false | Filters | Disable graph in listing. |
+| `-o, --output` | string | "table" | Output | Output: text, json, table. |
+| `--api-key` | string | - | Connection | Override API key. |
+| `--base-url` | string | - | Connection | Override API base URL. |
+
+**Examples:**
+```bash
+mem0 list -u alice
+mem0 list --category prefs --after 2024-01-01 -o json
+```
+
+---
+
+### 3.6 `update`
+
+Update a memory's text or metadata.
+
+| Property | Value |
+|------------------|-------|
+| Usage | `mem0 update [text] [OPTIONS]` |
+| needsBackend | Yes |
+| needsConfig | No |
+| resolveIds | No |
+| resolveGraph | No |
+| confirmDangerous | No |
+| Output formats | text, json, quiet |
+| Default output | text |
+| API endpoint | `PUT /v1/memories/{memory_id}/` |
+
+**Arguments:**
+
+| Name | Type | Required | Help |
+|-------------|--------|----------|------|
+| `memory_id` | string | Yes | Memory ID to update. |
+| `text` | string | No | New memory text. |
+
+**Options:**
+
+| Flag | Type | Default | Panel | Help |
+|------------------|--------|---------|------------|------|
+| `-m, --metadata` | string | - | - | Update metadata (JSON). |
+| `-o, --output` | string | "text" | Output | Output: text, json, quiet. |
+| `--api-key` | string | - | Connection | Override API key. |
+| `--base-url` | string | - | Connection | Override API base URL. |
+
+**Stdin fallback:** If no text argument is provided and no `--metadata` flag is set and stdin is piped, reads text from stdin.
+
+**Examples:**
+```bash
+mem0 update abc-123 "new text"
+mem0 update abc-123 --metadata '{"key":"val"}'
+echo "new text" | mem0 update abc-123
+```
+
+---
+
+### 3.7 `delete`
+
+Delete a memory, all memories matching a scope, or an entity. This is a consolidated command with three mutually exclusive modes.
+
+| Property | Value |
+|------------------|-------|
+| Usage | `mem0 delete [memory_id] [OPTIONS]` |
+| needsBackend | Yes |
+| needsConfig | Yes |
+| resolveIds | Yes |
+| resolveGraph | No |
+| confirmDangerous | Yes |
+| Output formats | text, json, quiet |
+| Default output | text |
+| API endpoint | `DELETE /v1/memories/{memory_id}/` (single), `DELETE /v1/memories/` (--all), `DELETE /v1/entities/` (--entity) |
+
+**Arguments:**
+
+| Name | Type | Required | Help |
+|-------------|--------|----------|------|
+| `memory_id` | string | No | Memory ID to delete (omit when using --all or --entity). |
+
+**Options:**
+
+| Flag | Type | Default | Panel | Help |
+|-----------------|---------|---------|------------|------|
+| `--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. |
+| `--dry-run` | boolean | false | - | Show what would be deleted without deleting. |
+| `--force` | boolean | false | - | Skip confirmation. |
+| `-u, --user-id` | string | - | Scope | Scope to user. |
+| `--agent-id` | string | - | Scope | Scope to agent. |
+| `--app-id` | string | - | Scope | Scope to app. |
+| `--run-id` | string | - | Scope | Scope to run. |
+| `-o, --output` | string | "text" | Output | Output: text, json, quiet. |
+| `--api-key` | string | - | Connection | Override API key. |
+| `--base-url` | string | - | Connection | Override API base URL. |
+
+**Three modes (mutually exclusive):**
+
+1. **Single memory:** `mem0 delete ` -- deletes one memory by ID. Cannot combine with `--all` or `--entity`.
+2. **Bulk delete:** `mem0 delete --all [scope]` -- deletes all memories matching scope filters. Use `--project` with `--all` to wipe all memories project-wide (sends wildcard `*` entity IDs). Cannot combine with `` or `--entity`.
+3. **Entity cascade:** `mem0 delete --entity [scope]` -- deletes the entity itself and all its memories. Cannot combine with `` or `--all`.
+
+If none of ``, `--all`, or `--entity` is provided, the command prints a usage hint and exits with an error.
+
+**Dry-run behavior:**
+- Single: fetches the memory via `GET`, displays it, then prints "No changes made."
+- `--all`: lists matching memories and displays the count, then prints "No changes made."
+- `--entity`: shows the scope that would be affected without deleting.
+
+**Confirmation:** Without `--force`, prompts the user with "[y/N]" confirmation. With `--all --project`, the prompt warns about entire project wipe.
+
+**`--all --project` wildcard behavior:** Sends `DELETE /v1/memories/` with query params `user_id=*&agent_id=*&app_id=*&run_id=*`. The API typically returns an async response with a `message` field (deletion happens in background). The CLI detects this and prints "Deletion started. Memories will be removed in the background."
+
+**Examples:**
+```bash
+mem0 delete abc-123-def-456 # single memory
+mem0 delete --all -u alice --force # all memories for user
+mem0 delete --all --project --force # project-wide wipe
+mem0 delete --entity -u alice --force # entity + all its memories
+mem0 delete abc-123 --dry-run # preview single delete
+```
+
+---
+
+### 3.8 `import`
+
+Import memories from a JSON file.
+
+| Property | Value |
+|------------------|-------|
+| Usage | `mem0 import [OPTIONS]` |
+| needsBackend | Yes |
+| needsConfig | Yes |
+| resolveIds | Yes |
+| resolveGraph | No |
+| confirmDangerous | No |
+| Output formats | text, json |
+| Default output | text |
+| API endpoint | `POST /v1/memories/` (per item) |
+
+**Arguments:**
+
+| Name | Type | Required | Help |
+|-------------|--------|----------|------|
+| `file_path` | string | Yes | JSON file to import. |
+
+**Options:**
+
+| Flag | Type | Default | Panel | Help |
+|-----------------|--------|---------|------------|------|
+| `-u, --user-id` | string | - | Scope | Override user ID. |
+| `--agent-id` | string | - | Scope | Override agent ID. |
+| `-o, --output` | string | "text" | Output | Output: text, json. |
+| `--api-key` | string | - | Connection | Override API key. |
+| `--base-url` | string | - | Connection | Override API base URL. |
+
+**File format:** JSON array (or single object) where each item has `memory`, `text`, or `content` field for the text, plus optional `user_id`, `agent_id`, and `metadata` fields.
+
+**Behavior:** Iterates through items, calling `backend.add()` for each. CLI-provided `--user-id` and `--agent-id` override per-item values. Displays progress indicator (every 10 items in Node, Rich progress bar in Python). Reports `added` and `failed` counts.
+
+**JSON output envelope:**
+```json
+{
+ "status": "success",
+ "command": "import",
+ "data": { "added": 42, "failed": 0, "duration_s": 3.14 },
+ "duration_ms": 3140
+}
+```
+
+**Examples:**
+```bash
+mem0 import data.json --user-id alice
+mem0 import data.json -u alice -o json
+```
+
+---
+
+### 3.9 `config show`
+
+Display current configuration (secrets redacted).
+
+| Property | Value |
+|------------------|-------|
+| Usage | `mem0 config show [OPTIONS]` |
+| needsBackend | No |
+| needsConfig | No |
+
+**Options:**
+
+| Flag | Type | Default | Help |
+|----------------|--------|---------|------|
+| `-o, --output` | string | "text" | Output: text, json. |
+
+**Behavior:** Loads config (file + env vars), displays as table (text) or JSON envelope. API keys are always redacted using `redact_key()`.
+
+**Examples:**
+```bash
+mem0 config show
+mem0 config show -o json
+```
+
+---
+
+### 3.10 `config get`
+
+Get a configuration value.
+
+| Property | Value |
+|------------------|-------|
+| Usage | `mem0 config get ` |
+| needsBackend | No |
+| needsConfig | No |
+
+**Arguments:**
+
+| Name | Type | Required | Help |
+|-------|--------|----------|------|
+| `key` | string | Yes | Config key (e.g. `platform.api_key`). |
+
+**Valid keys:** `platform.api_key`, `platform.base_url`, `defaults.user_id`, `defaults.agent_id`, `defaults.app_id`, `defaults.run_id`, `defaults.enable_graph`.
+
+**Behavior:** Prints the value to stdout. API keys are redacted. Unknown keys print an error.
+
+**Examples:**
+```bash
+mem0 config get platform.api_key
+mem0 config get defaults.user_id
+```
+
+---
+
+### 3.11 `config set`
+
+Set a configuration value.
+
+| Property | Value |
+|------------------|-------|
+| Usage | `mem0 config set ` |
+| needsBackend | No |
+| needsConfig | No |
+
+**Arguments:**
+
+| Name | Type | Required | Help |
+|---------|--------|----------|------|
+| `key` | string | Yes | Config key (e.g. `platform.api_key`). |
+| `value` | string | Yes | Value to set. |
+
+**Type coercion:** Boolean fields accept `true/1/yes` (case-insensitive) as true, anything else as false. Integer fields are parsed via `parseInt`. String fields are stored as-is.
+
+**Examples:**
+```bash
+mem0 config set defaults.user_id alice
+mem0 config set platform.base_url https://api.mem0.ai
+```
+
+---
+
+### 3.12 `entity list`
+
+List all entities of a given type.
+
+| Property | Value |
+|------------------|-------|
+| Usage | `mem0 entity list ` |
+| needsBackend | Yes |
+| needsConfig | No |
+| resolveIds | No |
+| resolveGraph | No |
+| confirmDangerous | No |
+| Output formats | table, json |
+| Default output | table |
+| API endpoint | `GET /v1/entities/` |
+
+**Arguments:**
+
+| Name | Type | Required | Choices | Help |
+|---------------|--------|----------|--------------------------------|------|
+| `entity_type` | string | Yes | `users`, `agents`, `apps`, `runs` | Entity type to list. |
+
+**Behavior:** Calls `GET /v1/entities/` which returns ALL entity types, then filters client-side using the type map: `users` -> `user`, `agents` -> `agent`, `apps` -> `app`, `runs` -> `run`. Displays a table with "Name / ID" and "Created" columns.
+
+**Options:**
+
+| Flag | Type | Default | Panel | Help |
+|----------------|--------|---------|------------|------|
+| `-o, --output` | string | "table" | Output | Output: table, json. |
+| `--api-key` | string | - | Connection | Override API key. |
+| `--base-url` | string | - | Connection | Override API base URL. |
+
+**Examples:**
+```bash
+mem0 entity list users
+mem0 entity list agents -o json
+```
+
+---
+
+### 3.13 `entity delete`
+
+Delete an entity and ALL its memories (cascade). Also accessible via `mem0 delete --entity`.
+
+| Property | Value |
+|------------------|-------|
+| Usage | `mem0 entity delete [OPTIONS]` |
+| needsBackend | Yes |
+| needsConfig | No |
+| resolveIds | No |
+| resolveGraph | No |
+| confirmDangerous | Yes |
+| Output formats | text, json, quiet |
+| Default output | text |
+| API endpoint | `DELETE /v1/entities/` |
+
+**Arguments:** None.
+
+**Options:**
+
+| Flag | Type | Default | Panel | Help |
+|-----------------|---------|---------|------------|------|
+| `-u, --user-id` | string | - | Scope | User ID. |
+| `--agent-id` | string | - | Scope | Agent ID. |
+| `--app-id` | string | - | Scope | App ID. |
+| `--run-id` | string | - | Scope | Run ID. |
+| `--dry-run` | boolean | false | - | Show what would be deleted without deleting. |
+| `--force` | boolean | false | - | Skip confirmation. |
+| `-o, --output` | string | "text" | Output | Output: text, json, quiet. |
+| `--api-key` | string | - | Connection | Override API key. |
+| `--base-url` | string | - | Connection | Override API base URL. |
+
+**Validation:** At least one entity ID is required. Errors if none provided.
+
+**Examples:**
+```bash
+mem0 entity delete --user-id alice --force
+mem0 entity delete --user-id alice --dry-run
+```
+
+---
+
+### 3.14 `event list`
+
+List recent background processing events.
+
+| Property | Value |
+|------------------|-------|
+| Usage | `mem0 event list [OPTIONS]` |
+| needsBackend | Yes |
+| needsConfig | Yes |
+| resolveIds | No |
+| resolveGraph | No |
+| confirmDangerous | No |
+| Output formats | text (table), json |
+| Default output | table |
+| API endpoint | `GET /v1/events/` |
+
+**Options:**
+
+| Flag | Type | Default | Panel | Help |
+|----------------|--------|---------|------------|------|
+| `-o, --output` | string | "table" | Output | Output: text, json. |
+| `--api-key` | string | - | Connection | Override API key. |
+| `--base-url` | string | - | Connection | Override API base URL. |
+
+**Behavior:** Fetches all background events for the project. Displays as a table with columns: Event ID (first 8 chars), Type, Status (color-coded), Latency, Created. Status values: `PENDING` (accent), `SUCCEEDED` (green), `FAILED` (red), `PROCESSING` (yellow).
+
+**JSON output envelope:**
+```json
+{
+ "status": "success",
+ "command": "event list",
+ "count": 3,
+ "duration_ms": 87,
+ "data": [
+ { "id": "evt-abc", "event_type": "ADD", "status": "SUCCEEDED", "latency": 412.0, "created_at": "2026-01-01T10:00:00Z" }
+ ]
+}
+```
+
+**Examples:**
+```bash
+mem0 event list
+mem0 event list --output json
+```
+
+---
+
+### 3.15 `event status`
+
+Get the status and results of a specific background event.
+
+| Property | Value |
+|------------------|-------|
+| Usage | `mem0 event status [OPTIONS]` |
+| needsBackend | Yes |
+| needsConfig | Yes |
+| resolveIds | No |
+| resolveGraph | No |
+| confirmDangerous | No |
+| Output formats | text, json |
+| Default output | text |
+| API endpoint | `GET /v1/events/{event_id}/` |
+
+**Arguments:**
+
+| Name | Type | Required | Help |
+|------------|--------|----------|------|
+| `event_id` | string | Yes | Event ID to inspect. |
+
+**Options:**
+
+| Flag | Type | Default | Panel | Help |
+|----------------|--------|---------|------------|------|
+| `-o, --output` | string | "text" | Output | Output: text, json. |
+| `--api-key` | string | - | Connection | Override API key. |
+| `--base-url` | string | - | Connection | Override API base URL. |
+
+**Behavior:** Fetches the event by ID and displays: Event ID, Type, Status (color-coded), Latency, Created, Updated, and a numbered list of result memories (event type, memory text, user_id, truncated memory ID). Displayed in a boxed panel (text) or JSON envelope.
+
+**JSON output envelope:**
+```json
+{
+ "status": "success",
+ "command": "event status",
+ "duration_ms": 65,
+ "data": {
+ "id": "evt-abc",
+ "event_type": "ADD",
+ "status": "SUCCEEDED",
+ "latency": 412.0,
+ "created_at": "2026-01-01T10:00:00Z",
+ "updated_at": "2026-01-01T10:00:01Z",
+ "results": [
+ { "id": "mem-xyz", "event": "ADD", "user_id": "alice", "memory": "User prefers dark mode" }
+ ]
+ }
+}
+```
+
+**Examples:**
+```bash
+mem0 event status evt-abc-123
+mem0 event status evt-abc-123 --output json
+```
+
+---
+
+### 3.16 `status`
+
+Check connectivity and authentication.
+
+| Property | Value |
+|------------------|-------|
+| Usage | `mem0 status [OPTIONS]` |
+| needsBackend | Yes |
+| needsConfig | Yes |
+| resolveIds | No |
+| resolveGraph | No |
+| confirmDangerous | No |
+
+**Options:**
+
+| Flag | Type | Default | Panel | Help |
+|----------------|--------|---------|------------|------|
+| `-o, --output` | string | "text" | Output | Output: text, json. |
+| `--api-key` | string | - | Connection | Override API key. |
+| `--base-url` | string | - | Connection | Override API base URL. |
+
+**Behavior:** Validates connectivity by calling `GET /v1/ping/`. Displays connection status in a boxed panel (text) or JSON envelope. The ping endpoint is lightweight and does not require any entity scope.
+
+**JSON output:**
+```json
+{
+ "status": "success",
+ "command": "status",
+ "duration_ms": 112,
+ "data": {
+ "connected": true,
+ "backend": "platform",
+ "base_url": "https://api.mem0.ai"
+ }
+}
+```
+
+**Examples:**
+```bash
+mem0 status
+mem0 status -o json
+```
+
+---
+
+### 3.17 `help`
+
+Show help. Use `--json` for machine-readable output (for LLM agents).
+
+| Property | Value |
+|------------------|-------|
+| Usage | `mem0 help [OPTIONS]` |
+| needsBackend | No |
+| needsConfig | No |
+
+**Options:**
+
+| Flag | Type | Default | Help |
+|----------|---------|---------|------|
+| `--json` | boolean | false | Output machine-readable JSON for LLM agents. |
+
+**Behavior:**
+- Without `--json`: prints a human-readable summary of all commands.
+- With `--json`: Node outputs the entire `cli-spec.json` file. Python outputs a hand-built JSON object describing all commands, arguments, options, and global options.
+
+**Examples:**
+```bash
+mem0 help
+mem0 help --json
+```
+
+---
+
+## 4. API Endpoints
+
+### Base URL
+
+Default: `https://api.mem0.ai` (configurable via `--base-url`, `MEM0_BASE_URL`, or `platform.base_url` in config).
+
+### Authentication
+
+All requests include the header:
+```
+Authorization: Token
+```
+
+The auth header name is `Authorization` and the scheme is `Token` (not Bearer).
+
+### Timeout
+
+30 seconds for all requests (Python: `httpx.Client(timeout=30.0)`, Node: `AbortSignal.timeout(30_000)`).
+
+### Endpoint Reference
+
+| Operation | Method | Path | Request Body | Query Params |
+|-----------------|----------|-------------------------------|------------------|-------------------------|
+| Add memory | `POST` | `/v1/memories/` | JSON payload | - |
+| Search | `POST` | `/v2/memories/search/` | JSON payload | - |
+| Get memory | `GET` | `/v1/memories/{memory_id}/` | - | - |
+| List memories | `POST` | `/v2/memories/` | JSON payload | `page`, `page_size` |
+| Update memory | `PUT` | `/v1/memories/{memory_id}/` | JSON payload | - |
+| Delete memory | `DELETE` | `/v1/memories/{memory_id}/` | - | - |
+| Delete all | `DELETE` | `/v1/memories/` | - | entity ID params |
+| List entities | `GET` | `/v1/entities/` | - | - |
+| Delete entities | `DELETE` | `/v1/entities/` | - | entity ID params |
+| List events | `GET` | `/v1/events/` | - | - |
+| Get event | `GET` | `/v1/events/{event_id}/` | - | - |
+| Ping (status) | `GET` | `/v1/ping/` | - | - |
+
+### How Filters Are Built (`_buildFilters` / `_build_filters`)
+
+Both CLIs use an identical filter-building algorithm:
+
+1. If the caller passed a pre-built filter structure containing `AND` or `OR` keys (e.g. from `--filter`), use it directly.
+2. Otherwise, build an array of AND conditions:
+ - Each entity ID becomes `{"user_id": "..."}`, `{"agent_id": "..."}`, etc.
+ - Extra filters (category, date ranges) are appended as additional conditions.
+3. If exactly 1 condition: return it directly (no wrapping).
+4. If 2+ conditions: return `{"AND": [condition1, condition2, ...]}`.
+5. If 0 conditions: return `undefined`/`None`.
+
+**Category filter format:** `{"categories": {"contains": ""}}`
+
+**Date filter format:** `{"created_at": {"gte": "YYYY-MM-DD"}}` and/or `{"created_at": {"lte": "YYYY-MM-DD"}}`. If both `after` and `before` are set, they merge into one `created_at` object: `{"created_at": {"gte": "...", "lte": "..."}}`.
+
+### How Pagination Works
+
+For the `list` command (and `search` internally):
+- `page` and `page_size` are sent as **query parameters** (not in the POST body).
+- Filters and `enable_graph` are sent in the **POST body**.
+- Default: `page=1`, `page_size=100`.
+
+### Response Normalization
+
+Both CLIs handle inconsistent API response formats:
+```
+# For search and list, the API may return:
+result = [...] # Direct array
+result = {"results": [...]} # Wrapped in results key
+result = {"memories": [...]} # Wrapped in memories key
+
+# Normalization logic (identical in both CLIs):
+if isinstance(result, list):
+ return result
+else:
+ return result.get("results", result.get("memories", []))
+```
+
+### Error Handling
+
+HTTP errors are mapped to typed exceptions:
+
+| HTTP Status | Error Class | Message Template |
+|-------------|----------------|-----------------|
+| 401 | `AuthError` | "Authentication failed. Your API key may be invalid or expired." |
+| 404 | `NotFoundError`| "Resource not found: {path}" |
+| 400 | `APIError` | "Bad request to {path}: {detail}" (detail extracted from response JSON `.detail` field) |
+| 204 | (success) | Returns `{}` (empty object) |
+| Other | Generic Error | "HTTP {status}: {statusText}" |
+
+---
+
+## 5. Configuration
+
+### Config File Location
+
+- Directory: `~/.mem0/` (created with permissions `0700`)
+- File: `~/.mem0/config.json` (written with permissions `0600`)
+
+### Config Precedence (highest to lowest)
+
+1. **CLI flags** (`--api-key`, `--base-url`, `--user-id`, etc.)
+2. **Environment variables** (`MEM0_API_KEY`, etc.)
+3. **Config file** (`~/.mem0/config.json`)
+4. **Defaults** (hardcoded)
+
+### Environment Variables
+
+| Variable | Config Path | Type | Default |
+|--------------------|--------------------------|---------|-----------------------|
+| `MEM0_API_KEY` | `platform.api_key` | string | `""` |
+| `MEM0_BASE_URL` | `platform.base_url` | string | `"https://api.mem0.ai"` |
+| `MEM0_USER_ID` | `defaults.user_id` | string | `""` |
+| `MEM0_AGENT_ID` | `defaults.agent_id` | string | `""` |
+| `MEM0_APP_ID` | `defaults.app_id` | string | `""` |
+| `MEM0_RUN_ID` | `defaults.run_id` | string | `""` |
+| `MEM0_ENABLE_GRAPH`| `defaults.enable_graph` | boolean | `false` |
+
+**Boolean parsing for `MEM0_ENABLE_GRAPH`:** Accepted truthy values are `"true"`, `"1"`, `"yes"` (case-insensitive). Everything else is `false`.
+
+### Config File JSON Schema
+
+```json
+{
+ "version": 1,
+ "defaults": {
+ "user_id": "",
+ "agent_id": "",
+ "app_id": "",
+ "run_id": "",
+ "enable_graph": false
+ },
+ "platform": {
+ "api_key": "",
+ "base_url": "https://api.mem0.ai"
+ }
+}
+```
+
+| Field | Type | Default | Description |
+|--------------------------|---------|--------------------------|-------------|
+| `version` | integer | `1` | Config schema version. |
+| `defaults.user_id` | string | `""` | Default user ID for scoping. |
+| `defaults.agent_id` | string | `""` | Default agent ID for scoping. |
+| `defaults.app_id` | string | `""` | Default app ID for scoping. |
+| `defaults.run_id` | string | `""` | Default run ID for scoping. |
+| `defaults.enable_graph` | boolean | `false` | Default graph memory extraction. |
+| `platform.api_key` | string | `""` | API key for mem0 Platform. |
+| `platform.base_url` | string | `"https://api.mem0.ai"` | Base URL for API requests. |
+
+### Config Key Map (for `config get`/`config set`)
+
+The dotted key paths map to internal config objects as follows:
+
+| Dotted Key | Section | Field |
+|-------------------------|------------|--------------|
+| `platform.api_key` | platform | apiKey / api_key |
+| `platform.base_url` | platform | baseUrl / base_url |
+| `defaults.user_id` | defaults | userId / user_id |
+| `defaults.agent_id` | defaults | agentId / agent_id |
+| `defaults.app_id` | defaults | appId / app_id |
+| `defaults.run_id` | defaults | runId / run_id |
+| `defaults.enable_graph` | defaults | enableGraph / enable_graph |
+
+### API Key Redaction
+
+The `redact_key`/`redactKey` function:
+- Empty string: returns `"(not set)"`
+- Length <= 8: returns first 2 chars + `"***"`
+- Length > 8: returns first 4 chars + `"..."` + last 4 chars
+
+---
+
+## 6. Key Behavioral Patterns
+
+These patterns are the **contract** both CLIs must follow. Any new implementation must replicate them exactly.
+
+### 6.1 Entity ID Resolution
+
+**Function:** `_resolve_ids` (Python) / `resolveIds` (Node)
+
+**Rule:** If **any** explicit entity ID is provided via CLI flags, only use the explicitly provided IDs. Do NOT mix in defaults for other entity types (which would over-filter). If **no** explicit IDs are provided, fall back to **all** configured defaults.
+
+```
+if any(user_id, agent_id, app_id, run_id):
+ # Only use what was explicitly passed; others become None
+ return {user_id or None, agent_id or None, app_id or None, run_id or None}
+else:
+ # Fall back to all configured defaults
+ return {config.user_id or None, config.agent_id or None, ...}
+```
+
+**Rationale:** If a user passes `--user-id alice` and the config also has `agent_id=bot1`, they probably want only Alice's memories, not the intersection of Alice AND bot1.
+
+### 6.2 Graph Tri-State Resolution
+
+**Rule:** `--no-graph` > `--graph` > config default.
+
+```
+if opts.no_graph: return false
+if opts.graph: return true
+return config.defaults.enable_graph
+```
+
+This is resolved in the main app file (not in the command handlers) before calling the command function.
+
+### 6.3 Category Parsing
+
+**Rule:** Try JSON parse first, fallback to comma-split.
+
+```
+if categories:
+ try:
+ cats = JSON.parse(categories) # e.g. '["a","b"]'
+ except:
+ cats = categories.split(",").map(s => s.trim()) # e.g. "a, b"
+```
+
+### 6.4 Stdin Detection
+
+**Rule:** Read from stdin if no text argument is provided AND stdin is piped (not a TTY).
+
+- `add`: If no `text`, no `--messages`, no `--file`, and stdin is piped -> read content from stdin.
+- `search`: If no `query` argument and stdin is piped -> read query from stdin.
+- `update`: If no `text` argument and no `--metadata` and stdin is piped -> read text from stdin.
+
+Detection method:
+- Python: `not sys.stdin.isatty()`
+- Node: `!process.stdin.isTTY`
+
+Reading method:
+- Python: `sys.stdin.read().strip()`
+- Node: `fs.readFileSync(0, "utf-8").trim()`
+
+### 6.5 Filter Building (`_buildFilters`)
+
+Detailed algorithm (see Section 4 for full description):
+
+1. If `extraFilters` has `AND` or `OR` key -> return it as-is (pre-built filter).
+2. Collect AND conditions from entity IDs.
+3. Append extra filters (category, date ranges).
+4. 0 conditions -> `undefined`/`None`.
+5. 1 condition -> return that single object.
+6. 2+ conditions -> `{"AND": [...]}`.
+
+### 6.6 API Response Normalization
+
+All `search` and `listMemories`/`list_memories` calls normalize the response:
+
+```
+if Array.isArray(result): return result
+return result.results ?? result.memories ?? []
+```
+
+This handles both direct array responses and wrapped `{results: [...]}` or `{memories: [...]}` formats.
+
+### 6.7 Config File Permissions
+
+- Config directory (`~/.mem0/`): created with mode `0o700` (owner read+write+execute only).
+- Config file (`~/.mem0/config.json`): written with mode `0o600` (owner read+write only).
+- Python uses `os.chmod()` with `stat.S_IRWXU` (dir) and `stat.S_IRUSR | stat.S_IWUSR` (file).
+- Node uses `fs.mkdirSync(..., { mode: 0o700 })` and `fs.chmodSync(file, 0o600)`.
+
+### 6.8 Timed Status Pattern
+
+Every API call is wrapped in a spinner + timing pattern:
+
+**Python:**
+```python
+with timed_status(err_console, "Adding memory...") as ts:
+ result = backend.add(...)
+```
+Uses Rich `Status` context manager on stderr. On success, prints `ts.success_msg` with elapsed time. On error, prints `ts.error_msg` with elapsed time.
+
+**Node:**
+```typescript
+result = await timedStatus("Adding memory...", async (ctx) => {
+ return backend.add(...);
+});
+```
+Uses `ora` spinner on stderr. On success, prints `ctx.successMsg` with elapsed time. On error, prints `ctx.errorMsg` with elapsed time.
+
+Both use `performance.now()` / `time.perf_counter()` for timing. Elapsed time is formatted as `{seconds:.2f}s`.
+
+**Key:** Spinners and timing messages always go to **stderr** so they never contaminate machine-readable stdout.
+
+### 6.9 Error Hierarchy
+
+```
+AuthError (HTTP 401) -> "Authentication failed. Your API key may be invalid or expired."
+NotFoundError (HTTP 404) -> "Resource not found: {path}"
+APIError (HTTP 400) -> "Bad request to {path}: {detail}"
+```
+
+For HTTP 400, the CLI attempts to extract a `detail` field from the JSON response body. If parsing fails, falls back to `resp.statusText`/`resp.text`.
+
+HTTP 204 is treated as success with empty body (`{}`).
+
+Any other non-OK response throws a generic error with `"HTTP {status}: {statusText}"`.
+
+### 6.10 `delete --all --project` Wildcard Behavior
+
+When `delete --all --project` is used, the CLI sends wildcard entity IDs (`user_id=*`, `agent_id=*`, `app_id=*`, `run_id=*`) to `DELETE /v1/memories/`. The API typically returns an **asynchronous response** with a `message` field (the deletion happens in the background). The CLI detects the `message` key in the response and prints "Deletion started. Memories will be removed in the background." instead of a success count.
+
+### 6.11 Non-Interactive Init
+
+When both `--api-key` and `--user-id` are provided to `mem0 init`:
+1. Sets config values directly (no prompts).
+2. Validates the platform connection.
+3. Saves config to disk.
+4. Prints success message.
+
+When running in a non-TTY (piped input) without both flags, prints an error with usage hint:
+```
+"Non-interactive terminal detected and missing required flags."
+"Usage: mem0 init --api-key --user-id "
+```
+
+### 6.12 Add Result Event Display
+
+The `format_add_result` function handles the API response from `POST /v1/memories/`:
+
+The response is either a direct array or `{results: [...]}`. Each result item has an `event` field:
+
+| Event | Icon | Label |
+|----------|------|------------|
+| `ADD` | `+` | Added |
+| `UPDATE` | `~` | Updated |
+| `DELETE` | `-` | Deleted |
+| `NOOP` | `.` | No change |
+| `PENDING`| hourglass | Queued (async) |
+
+For `PENDING` events, displays "Processing in background" with the event ID.
+
+---
+
+## 7. Output Modes
+
+### 7.1 Supported Modes Per Command
+
+All commands also support `agent` mode via the global `--json`/`--agent` flag, which wraps output in a structured JSON envelope with sanitized fields.
+
+| Command | text | json | table | quiet |
+|----------------|------|------|-------|-------|
+| add | Y | Y | - | Y |
+| search | Y | Y | Y | - |
+| get | Y | Y | - | - |
+| list | Y | Y | Y (default) | - |
+| update | Y | Y | - | Y |
+| delete | Y | Y | - | Y |
+| import | Y | Y | - | - |
+| config show | Y | Y | - | - |
+| config get | (raw value) | - | - | - |
+| config set | (success msg) | - | - | - |
+| entity list | - | Y | Y (default) | - |
+| entity delete | Y | Y | - | Y |
+| event list | Y (table) | Y | - | - |
+| event status | Y | Y | - | - |
+| status | Y | Y | - | - |
+| help | Y | Y (--json) | - | - |
+
+### 7.2 JSON Envelope Format
+
+There are two related envelope formats:
+
+**`formatJsonEnvelope`** โ used by `config show`, `status`, and `import` for `--output json`:
+
+```json
+{
+ "status": "success",
+ "command": "",
+ "duration_ms": 245,
+ "scope": {"user_id": "alice", "agent_id": null},
+ "count": 10,
+ "error": null,
+ "data": { ... }
+}
+```
+
+**`formatAgentEnvelope`** โ used by all commands in agent mode (`--json`/`--agent`). Same structure, but `data` is passed through `sanitizeAgentData(command, data)` to project only the most relevant fields:
+
+| Command | Fields in `data` |
+|---------------|-----------------|
+| 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}]` |
+| event list | `[{id, event_type, status, latency, created_at}]` |
+| event status | `{id, event_type, status, latency, created_at, updated_at, results: [{id, event, user_id, memory}]}` |
+| status/config/import | (pass-through) |
+
+Error envelopes (on non-zero exit):
+```json
+{
+ "status": "error",
+ "command": "",
+ "error": "Authentication failed. Your API key may be invalid or expired.",
+ "data": null
+}
+```
+
+Fields:
+- `status`: `"success"` or `"error"`.
+- `command`: The command name.
+- `duration_ms`: Optional, elapsed time in milliseconds.
+- `scope`: Optional, active entity scope (omitted if empty).
+- `count`: Optional, result count.
+- `error`: Only present when `status` is `"error"`.
+- `data`: The primary payload (sanitized in agent mode).
+
+### 7.3 Text Output
+
+- `formatMemoriesText`: Numbered list with memory text, score, ID (first 8 chars), created date, and category, separated by ` . ` in dim color.
+- `formatSingleMemory`: Boxed panel (boxen/Rich Panel) showing memory text, ID, created date, updated date, metadata, and categories.
+- `formatAddResult`: Event-based output with icons (+, ~, -, .) and labels.
+
+### 7.4 Table Output
+
+Uses `cli-table3` (Node) or `rich.table.Table` (Python) with columns:
+- ID (first 8 chars, dim)
+- Memory (truncated to 60 chars with "...")
+- Category (first category from array)
+- Created (YYYY-MM-DD)
+
+### 7.5 Quiet Mode
+
+Commands that support `--output quiet` (`add`, `update`, `delete`, `entity delete`) produce **no stdout output** in quiet mode. The operation still executes. Exit code indicates success/failure.
+
+### 7.6 Error Output
+
+- **Errors always go to stderr.** Both CLIs use a separate stderr console:
+ - Python: `Console(stderr=True)` for `print_error` calls.
+ - Node: `console.error()` in `printError`, spinner on `process.stderr` stream.
+- **Data always goes to stdout.** JSON output, table output, and text output all go to stdout.
+
+### 7.7 Unicode Symbol Degradation
+
+The `_sym`/`sym` function selects symbols based on terminal capability:
+
+| Condition | Fancy Symbol | Plain Fallback |
+|----------------------------------------|-------------|----------------|
+| `!stdout.isTTY` or `NO_COLOR` env set | - | Used |
+| Interactive TTY with color | Used | - |
+
+| Symbol | Fancy | Plain |
+|----------|-------|-----------|
+| Success | `checkmark` | `[ok]` |
+| Error | `X` | `[error]` |
+| Warning | `warning triangle` | `[warn]` |
+| Info | `diamond` | `*` |
+
+### 7.8 Result Summary Footer
+
+After list/search results, a summary line is printed in dim:
+```
+ 10 results . page 1 . user id=alice . 0.45s
+```
+
+Format: `{count} result(s) . page {n} . {scope} . {elapsed}s`
+
+### 7.9 Date Formatting
+
+All dates are normalized to `YYYY-MM-DD` format for display. The formatting handles ISO 8601 strings with `Z` timezone suffix by replacing it with `+00:00` before parsing.
+
+---
+
+## 8. Agent-Friendly Design Decisions
+
+### Why `--dry-run` exists on destructive commands
+
+The `delete` command (all modes) and `entity delete` support `--dry-run`. This lets AI agents preview the effect of a destructive operation before committing. For `delete `, it fetches the memory and displays it. For `delete --all`, it lists matching memories and shows the count. For `delete --entity` / `entity delete`, it shows the scope that would be affected.
+
+### Why `--force` exists
+
+Destructive commands (`delete --all`, `delete --entity`, `entity delete`) require interactive confirmation by default. The `--force` flag skips this confirmation, which is essential for:
+- AI agents (non-interactive)
+- CI/CD pipelines
+- Scripting
+
+### Why `--json`/`--agent` global flags exist
+
+The `--json` and `--agent` flags (aliases of each other) activate agent mode globally. When set:
+1. All output becomes a structured JSON envelope (`{status, command, duration_ms, scope, count, data}`).
+2. The `data` field is sanitized via `sanitizeAgentData` โ only the most relevant fields are included per command, reducing noise for agents parsing the output.
+3. All human-readable output (spinners, colors, banners, timing lines) is suppressed.
+4. Errors are emitted as JSON to stdout with a non-zero exit code, not to stderr as text.
+
+This is distinct from `--output json`, which returns the raw API response without sanitization.
+
+### Why `--output json` is on every command
+
+Every data-returning command supports `--output json` (or `--json` for `help`). This enables machine consumption by AI agents and scripts. JSON output goes to stdout while human-readable spinners/timing go to stderr, so piping `mem0 list -o json | jq .` works cleanly.
+
+### Why stdin is supported
+
+Commands `add`, `search`, and `update` can read from stdin when piped. This enables composability:
+```bash
+echo "I prefer dark mode" | mem0 add -u alice
+cat query.txt | mem0 search -u alice
+echo "updated text" | mem0 update abc-123
+```
+
+### Why `help --json` exists
+
+The `help --json` command outputs the complete CLI specification in machine-readable JSON. AI agents can call this once to discover all available commands, their arguments, options, and valid values -- enabling self-documenting tool use.
+
+### Why errors go to stderr
+
+All error messages, warnings, spinners, and timing information go to stderr. This means `--output json` produces **only** valid JSON on stdout, with no interleaved human-readable messages. An AI agent can safely parse stdout as JSON.
+
+---
+
+## 9. Adding a New Command
+
+Step-by-step guide for adding a new command to both CLIs.
+
+### Step 1: Add to `cli-spec.json`
+
+Add a new entry to the `commands` array with all required fields:
+
+```json
+{
+ "name": "my-command",
+ "description": "What this command does.",
+ "usage": "mem0 my-command [OPTIONS]",
+ "needsBackend": true,
+ "needsConfig": true,
+ "resolveIds": true,
+ "resolveGraph": false,
+ "confirmDangerous": false,
+ "outputFormats": ["text", "json"],
+ "defaultOutput": "text",
+ "arguments": [
+ {
+ "name": "arg",
+ "type": "string",
+ "required": true,
+ "help": "Argument description."
+ }
+ ],
+ "options": [
+ {
+ "name": "user_id",
+ "flags": ["--user-id", "-u"],
+ "type": "string",
+ "help": "Scope to user.",
+ "panel": "Scope"
+ },
+ {
+ "name": "output",
+ "flags": ["--output", "-o"],
+ "type": "string",
+ "default": "text",
+ "help": "Output format.",
+ "panel": "Output"
+ }
+ ],
+ "apiEndpoint": "myEndpoint"
+}
+```
+
+If the command calls a new API endpoint, also add it to `api.endpoints`.
+
+### Step 2: Add Backend Method (if new API endpoint)
+
+**Python** (`python/src/mem0_cli/backend/base.py` and `platform.py`):
+1. Add abstract method to `Backend` ABC in `base.py`.
+2. Implement in `PlatformBackend` in `platform.py`.
+
+**Node** (`node/src/backend/base.ts` and `platform.ts`):
+1. Add method signature to `Backend` interface in `base.ts`.
+2. Implement in `PlatformBackend` class in `platform.ts`.
+
+### Step 3: Add Command Handler
+
+**Python** (`python/src/mem0_cli/commands/`):
+Create a function `cmd_my_command(backend, ...)` in the appropriate commands file. Follow the patterns:
+- Use `timed_status(err_console, "...")` for API calls.
+- Use `print_error(err_console, ...)` for errors.
+- Use `format_json(console, ...)` for JSON output.
+- Raise `typer.Exit(1)` on errors.
+
+**Node** (`node/src/commands/`):
+Create an async function `cmdMyCommand(backend, ...)`. Follow the patterns:
+- Use `await timedStatus("...", async () => { ... })` for API calls.
+- Use `printError(...)` for errors.
+- Use `formatJson(...)` for JSON output.
+- Call `process.exit(1)` on errors.
+
+### Step 4: Register in App Entrypoint
+
+**Python** (`python/src/mem0_cli/app.py`):
+```python
+@app.command(name="my-command")
+def my_command(
+ arg: str = typer.Argument(..., help="..."),
+ output: str = typer.Option("text", "--output", "-o", help="...", rich_help_panel="Output"),
+ api_key: str | None = typer.Option(None, "--api-key", help="...", rich_help_panel="Connection"),
+ base_url: str | None = typer.Option(None, "--base-url", help="...", rich_help_panel="Connection"),
+) -> None:
+ """Command description."""
+ from mem0_cli.commands.my_module import cmd_my_command
+ backend, config = _get_backend_and_config(api_key, base_url)
+ ids = _resolve_ids(config, ...)
+ cmd_my_command(backend, arg, **ids, output=output)
+```
+
+**Node** (`node/src/index.ts`):
+```typescript
+program
+ .command("my-command ")
+ .description("Command description.")
+ .option("-o, --output ", "Output format.", "text")
+ .option("--api-key ", "Override API key.")
+ .option("--base-url ", "Override API base URL.")
+ .action(async (arg, opts) => {
+ const { cmdMyCommand } = await import("./commands/my-module.js");
+ const { backend, config } = getBackendAndConfig(opts.apiKey, opts.baseUrl);
+ const ids = resolveIds(config, opts);
+ await cmdMyCommand(backend, arg, { ...ids, output: opts.output });
+ });
+```
+
+### Step 5: Add Help Examples
+
+Both CLIs include examples in the help text:
+- Python: In the docstring of the Typer command function.
+- Node: Via `.addHelpText("after", "\nExamples:\n $ mem0 ...")`.
+
+### Step 6: Add to Help Display and Command Order
+
+**Node** (`node/src/help.ts`):
+1. Add `"my-command"` to `COMMAND_ORDER` array (determines display order in `--help`).
+2. Add option-to-panel mappings in `OPTION_PANELS["my-command"]`.
+
+**Python**: Options are assigned to panels via `rich_help_panel="..."` on each `typer.Option()`. The `help` command's `_build_help_json()` function needs a new entry.
+
+### Step 7: Add to `help` Command Output
+
+**Python** (`python/src/mem0_cli/app.py`):
+1. Add entry in `_build_help_json()` dict.
+2. Add line in the `help` command's human-readable output.
+
+**Node** (`node/src/index.ts`):
+Add line in the `help` command's human-readable output listing.
+
+### Step 8: Add Tests
+
+- Python: Add tests in `python/tests/`.
+- Node: Add tests in `node/src/__tests__/` or similar.
+
+### Step 9: Update This Specification
+
+Add the command to the Complete Command Reference (Section 3) with all arguments, options, behavior notes, and examples.
+
+---
+
+## 10. Adding a New Language Implementation
+
+To add a new language implementation (e.g., Go, Rust, Ruby), you need to replicate the exact behavioral contract defined in `cli-spec.json` and this document. Here is what is required:
+
+### 10.1 Core Modules to Implement
+
+| Module | Purpose |
+|---------------|---------|
+| **config** | Load `~/.mem0/config.json`, apply env var overrides, enforce precedence. Implement `load_config`, `save_config`, `ensure_config_dir`, `redact_key`, `get_nested_value`, `set_nested_value`. |
+| **backend/base** | Define the `Backend` interface/trait with all 8 methods: `add`, `search`, `get`, `list_memories`, `update`, `delete`, `delete_entities`, `status`, `entities`. Define error types: `AuthError`, `NotFoundError`, `APIError`. |
+| **backend/platform** | Implement `PlatformBackend` with HTTP client. Must implement `_build_filters` logic exactly. Must handle response normalization. Must set `Authorization: Token ` header. 30s timeout. |
+| **branding** | Implement print helpers (`print_success`, `print_error`, `print_warning`, `print_info`, `print_scope`), `print_banner`, `timed_status` pattern, `sym` function for Unicode degradation. Colors must match the hex values in `cli-spec.json`. |
+| **output** | Implement `format_memories_text`, `format_memories_table`, `format_single_memory`, `format_add_result`, `format_json`, `format_json_envelope`, `print_result_summary`. Date formatting to YYYY-MM-DD. ID truncation to 8 chars. Memory text truncation to 60 chars in tables. |
+| **commands/** | Implement all command handlers matching the exact behavior described in Section 3. |
+| **app/main** | CLI entrypoint with all commands registered. Implement `resolve_ids`, `resolve_graph`, stdin detection, and the `getBackendAndConfig` helper. |
+| **help** | Implement help formatter with grouped option panels (Scope, Search, Pagination, Filters, Output, Connection). Implement `help --json` output. |
+
+### 10.2 Behavioral Checklist
+
+Every new implementation MUST:
+
+- [ ] Read and respect `cli-spec.json` for all command names, descriptions, argument names, option flags, and defaults
+- [ ] Implement config precedence: CLI flags > env vars > config file > defaults
+- [ ] Implement entity ID resolution (explicit IDs only vs. all defaults)
+- [ ] Implement graph tri-state (`--no-graph` > `--graph` > config default)
+- [ ] Implement category parsing (JSON first, comma-split fallback)
+- [ ] Implement stdin detection and reading for `add`, `search`, `update`
+- [ ] Implement `_build_filters` with AND/OR structure
+- [ ] Implement response normalization (array vs `{results}` vs `{memories}`)
+- [ ] Set config directory permissions to 0700 and file to 0600
+- [ ] Implement timed status with spinner on stderr + elapsed time
+- [ ] Implement error hierarchy (AuthError 401, NotFoundError 404, APIError 400)
+- [ ] Implement `delete --all --project` with wildcard `*` entity IDs and async response handling
+- [ ] Implement non-interactive `init` when both `--api-key` and `--user-id` provided
+- [ ] Implement `--dry-run` on delete (all modes) and entity delete
+- [ ] Implement `--force` on delete --all, delete --entity, and entity delete
+- [ ] Send errors to stderr, data to stdout
+- [ ] Implement Unicode symbol degradation for non-TTY/NO_COLOR
+- [ ] Implement JSON envelope format for status, config show, import
+- [ ] Support `--output` on all data-returning commands
+- [ ] Implement `help --json` for machine-readable command discovery
+- [ ] Implement masked API key input during `init` (echo `*` per character)
+- [ ] Implement confirmation prompts for dangerous commands (unless `--force`)
+- [ ] Binary must be named `mem0`
+
+### 10.3 Package Metadata
+
+Follow the naming conventions:
+- Package description: "The official CLI for mem0 -- the memory layer for AI agents"
+- Author: `mem0.ai `
+- License: Apache-2.0
+- Keywords: `mem0`, `memory`, `ai`, `agents`, `cli`
+
+### 10.4 Testing
+
+Conformance tests should verify:
+- All commands from `cli-spec.json` are registered
+- All options from `cli-spec.json` are accepted
+- Config precedence is correct
+- Entity ID resolution matches the spec
+- Filter building produces correct structures
+- Output modes produce expected formats
+- Error codes are mapped correctly
+- Stdin reading works for supported commands
diff --git a/cli/README.md b/cli/README.md
new file mode 100644
index 000000000..d411daa65
--- /dev/null
+++ b/cli/README.md
@@ -0,0 +1,136 @@
+# mem0 CLI
+
+The official command-line interface for [mem0](https://mem0.ai) โ the memory layer for AI agents. Works with the Mem0 Platform API. Available in Python and Node.js.
+
+> **For AI agents:** pass `--agent` (or `--json`) on any command for structured JSON output purpose-built for tool loops โ sanitized fields, no colors or spinners, errors as JSON. See [Agent mode](#agent-mode) below.
+
+## Installation
+
+```bash
+npm install -g @mem0/cli
+```
+
+```bash
+pip install mem0-cli
+```
+
+Both packages install a `mem0` binary with identical behavior.
+
+## Quick start
+
+```bash
+# Interactive setup wizard
+mem0 init
+
+# Or login via email (get a new API key)
+mem0 init --email alice@company.com
+
+# Or authenticate with an existing API key
+mem0 init --api-key m0-xxx
+
+# Add a memory
+mem0 add "I prefer dark mode and use vim keybindings" --user-id alice
+
+# Search memories
+mem0 search "What are Alice's preferences?" --user-id alice
+
+# List all memories for a user
+mem0 list --user-id alice
+
+# Update a memory
+mem0 update "I switched to light mode"
+
+# Delete a memory
+mem0 delete
+```
+
+## Commands
+
+| Command | Description |
+|---------|-------------|
+| `mem0 init` | Setup wizard โ login via email or configure API key manually |
+| `mem0 add` | Add a memory from text, JSON messages, a file, or stdin |
+| `mem0 search` | Search memories using natural language |
+| `mem0 list` | List memories with optional filters and pagination |
+| `mem0 get` | Retrieve a specific memory by ID |
+| `mem0 update` | Update the text or metadata of a memory |
+| `mem0 delete` | Delete a memory, all memories for a scope, or an entity |
+| `mem0 import` | Bulk import memories from a JSON file |
+| `mem0 config` | View or modify CLI configuration |
+| `mem0 entity` | List or delete entities (users, agents, apps, runs) |
+| `mem0 event` | Inspect background processing events (bulk deletes, large add jobs) |
+| `mem0 status` | Verify API connection and display current project |
+| `mem0 version` | Print the CLI version |
+
+Run `mem0 --help` for detailed usage on any command.
+
+## Agent mode
+
+Pass `--agent` (or its alias `--json`) as a **global flag** on any command to get output designed for AI agent tool loops:
+
+```bash
+mem0 --agent search "user preferences" --user-id alice
+mem0 --agent add "User prefers dark mode" --user-id alice
+mem0 --agent list --user-id alice
+```
+
+Every command returns the same envelope shape:
+
+```json
+{
+ "status": "success",
+ "command": "search",
+ "duration_ms": 134,
+ "scope": { "user_id": "alice" },
+ "count": 2,
+ "data": [
+ { "id": "abc-123", "memory": "User prefers dark mode", "score": 0.97, "created_at": "2026-01-15", "categories": ["preferences"] }
+ ]
+}
+```
+
+What agent mode does differently from `--output json`:
+- **Sanitized `data`**: only the fields an agent needs (id, memory, score, etc.) โ no internal API noise
+- **No human output**: spinners, colors, and banners are suppressed entirely
+- **Errors as JSON**: errors go to stdout as `{"status": "error", "command": "...", "error": "..."}` with a non-zero exit code
+
+Use `mem0 help --json` to get the full command tree as JSON โ useful for agents that need to self-discover available commands.
+
+## Output formats
+
+Control how results are displayed with `--output`:
+
+| Format | Description |
+|--------|-------------|
+| `text` | Human-readable with colors and formatting (default) |
+| `json` | Structured JSON for piping to `jq` (raw API response) |
+| `table` | Tabular format (default for `list`) |
+| `quiet` | Minimal โ just IDs or status codes |
+| `agent` | Structured JSON envelope with sanitized fields (set by `--agent`/`--json`) |
+
+## Environment variables
+
+| Variable | Description |
+|----------|-------------|
+| `MEM0_API_KEY` | API key (overrides config file) |
+| `MEM0_BASE_URL` | API base URL |
+| `MEM0_USER_ID` | Default user ID |
+| `MEM0_AGENT_ID` | Default agent ID |
+| `MEM0_APP_ID` | Default app ID |
+| `MEM0_RUN_ID` | Default run ID |
+| `MEM0_ENABLE_GRAPH` | Enable graph memory (`true` / `false`) |
+
+## Implementations
+
+| Language | Directory | Package | Docs |
+|----------|-----------|---------|------|
+| TypeScript | [`node/`](./node/) | `@mem0/cli` | [README](./node/README.md) |
+| Python | [`python/`](./python/) | `mem0-cli` | [README](./python/README.md) |
+
+## Documentation
+
+Full documentation is available at [docs.mem0.ai/platform/cli](https://docs.mem0.ai/platform/cli).
+
+## License
+
+Apache-2.0
diff --git a/cli/cli-spec.json b/cli/cli-spec.json
new file mode 100644
index 000000000..4826bd1ad
--- /dev/null
+++ b/cli/cli-spec.json
@@ -0,0 +1,549 @@
+{
+ "specVersion": 1,
+ "cli": {
+ "name": "mem0",
+ "version": "0.1.0",
+ "description": "The Memory Layer for AI Agents",
+ "helpText": "\u25c6 mem0 CLI \u2014 The Memory Layer for AI Agents"
+ },
+ "branding": {
+ "logoMini": "\u25c6 mem0",
+ "tagline": "The Memory Layer for AI Agents",
+ "colors": {
+ "brand": "#8b5cf6",
+ "accent": "#a78bfa",
+ "success": "#22c55e",
+ "error": "#ef4444",
+ "warning": "#f59e0b",
+ "dim": "#6b7280"
+ },
+ "icons": {
+ "success": "\u2713",
+ "error": "\u2717",
+ "warning": "\u26a0",
+ "info": "\u25c6",
+ "pending": "\u29d7",
+ "add": "+",
+ "update": "~",
+ "delete": "-",
+ "noop": "\u00b7",
+ "connected": "\u25cf",
+ "disconnected": "\u25cf"
+ },
+ "logo": "โโโ\u2557 โโโ\u2557โโโโโโโ\u2557โโโ\u2557 โโโ\u2557 โโโโโโ\u2557 โโโโโโ\u2557โโ\u2557 โโ\u2557\nโโโโ\u2557 โโโโ\u2551โโ\u2554\u2550\u2550\u2550\u2550\u255dโโโโ\u2557 โโโโ\u2551โโ\u2554\u2550โโโโ\u2557 โโ\u2554\u2550\u2550\u2550\u2550\u255dโโ\u2551 โโ\u2551\nโโ\u2554โโโโ\u2554โโ\u2551โโโโโ\u2557 โโ\u2554โโโโ\u2554โโ\u2551โโ\u2551โโ\u2554โโ\u2551 โโ\u2551 โโ\u2551 โโ\u2551\nโโ\u2551\u255aโโ\u2554\u255dโโ\u2551โโ\u2554\u2550\u2550\u255d โโ\u2551\u255aโโ\u2554\u255dโโ\u2551โโโโ\u2554\u255dโโ\u2551 โโ\u2551 โโ\u2551 โโ\u2551\nโโ\u2551 \u255a\u2550\u255d โโ\u2551โโโโโโโ\u2557โโ\u2551 \u255a\u2550\u255d โโ\u2551\u255aโโโโโโ\u2554\u255d \u255aโโโโโโ\u2557โโโโโโโ\u2557โโ\u2551\n\u255a\u2550\u255d \u255a\u2550\u255d\u255a\u2550\u2550\u2550\u2550\u2550\u2550\u255d\u255a\u2550\u255d \u255a\u2550\u255d \u255a\u2550\u2550\u2550\u2550\u2550\u255d \u255a\u2550\u2550\u2550\u2550\u2550\u255d\u255a\u2550\u2550\u2550\u2550\u2550\u2550\u255d\u255a\u2550\u255d"
+ },
+ "config": {
+ "configDir": "~/.mem0",
+ "configFile": "config.json",
+ "version": 1,
+ "defaultBaseUrl": "https://api.mem0.ai",
+ "sections": {
+ "platform": {
+ "fields": {
+ "api_key": {
+ "type": "string",
+ "default": "",
+ "envVar": "MEM0_API_KEY",
+ "redact": true
+ },
+ "base_url": {
+ "type": "string",
+ "default": "https://api.mem0.ai",
+ "envVar": "MEM0_BASE_URL"
+ }
+ }
+ },
+ "defaults": {
+ "fields": {
+ "user_id": {
+ "type": "string",
+ "default": "",
+ "envVar": "MEM0_USER_ID"
+ },
+ "agent_id": {
+ "type": "string",
+ "default": "",
+ "envVar": "MEM0_AGENT_ID"
+ },
+ "app_id": {
+ "type": "string",
+ "default": "",
+ "envVar": "MEM0_APP_ID"
+ },
+ "run_id": {
+ "type": "string",
+ "default": "",
+ "envVar": "MEM0_RUN_ID"
+ },
+ "enable_graph": {
+ "type": "boolean",
+ "default": false,
+ "envVar": "MEM0_ENABLE_GRAPH"
+ }
+ }
+ }
+ }
+ },
+ "api": {
+ "endpoints": {
+ "add": { "method": "POST", "path": "/v1/memories/" },
+ "search": { "method": "POST", "path": "/v2/memories/search/" },
+ "get": { "method": "GET", "path": "/v1/memories/{memory_id}/" },
+ "list": { "method": "POST", "path": "/v2/memories/" },
+ "update": { "method": "PUT", "path": "/v1/memories/{memory_id}/" },
+ "delete": { "method": "DELETE", "path": "/v1/memories/{memory_id}/" },
+ "deleteAll": { "method": "DELETE", "path": "/v1/memories/" },
+ "entities": { "method": "GET", "path": "/v1/entities/" },
+ "deleteEntities": { "method": "DELETE", "path": "/v1/entities/" }
+ },
+ "authHeader": "Authorization",
+ "authScheme": "Token",
+ "timeout": 30,
+ "entityTypeMap": {
+ "users": "user",
+ "agents": "agent",
+ "apps": "app",
+ "runs": "run"
+ }
+ },
+ "errors": {
+ "AuthError": {
+ "httpStatus": 401,
+ "message": "Authentication failed. Your API key may be invalid or expired."
+ },
+ "NotFoundError": {
+ "httpStatus": 404,
+ "messageTemplate": "Resource not found: {path}"
+ },
+ "APIError": {
+ "httpStatus": 400,
+ "messageTemplate": "Bad request to {path}: {detail}"
+ },
+ "noApiKey": {
+ "message": "No API key configured.",
+ "hint": "Run 'mem0 init' or set MEM0_API_KEY environment variable."
+ }
+ },
+ "optionGroups": {
+ "scope": {
+ "label": "Scope",
+ "options": ["user_id", "agent_id", "app_id", "run_id"]
+ },
+ "search": {
+ "label": "Search",
+ "options": ["top_k", "threshold", "rerank", "keyword", "filter_json", "fields", "graph", "no_graph"]
+ },
+ "pagination": {
+ "label": "Pagination",
+ "options": ["page", "page_size"]
+ },
+ "filters": {
+ "label": "Filters",
+ "options": ["category", "after", "before", "graph", "no_graph"]
+ },
+ "output": {
+ "label": "Output",
+ "options": ["output"]
+ },
+ "connection": {
+ "label": "Connection",
+ "options": ["api_key", "base_url"]
+ }
+ },
+ "globalOptions": [
+ {
+ "name": "api_key",
+ "flags": ["--api-key"],
+ "type": "string",
+ "required": false,
+ "envVar": "MEM0_API_KEY",
+ "help": "Override API key.",
+ "panel": "Connection"
+ },
+ {
+ "name": "base_url",
+ "flags": ["--base-url"],
+ "type": "string",
+ "required": false,
+ "help": "Override API base URL.",
+ "panel": "Connection"
+ },
+ {
+ "name": "version",
+ "flags": ["--version"],
+ "type": "boolean",
+ "required": false,
+ "help": "Show version and exit."
+ }
+ ],
+ "commands": [
+ {
+ "name": "add",
+ "description": "Add a memory from text, messages, file, or stdin.",
+ "usage": "mem0 add [OPTIONS]",
+ "needsBackend": true,
+ "needsConfig": true,
+ "resolveIds": true,
+ "resolveGraph": true,
+ "confirmDangerous": false,
+ "outputFormats": ["text", "json", "quiet"],
+ "defaultOutput": "text",
+ "arguments": [
+ {
+ "name": "text",
+ "type": "string",
+ "required": false,
+ "help": "Text content to add as a memory."
+ }
+ ],
+ "options": [
+ { "name": "user_id", "flags": ["--user-id", "-u"], "type": "string", "help": "Scope to user.", "panel": "Scope" },
+ { "name": "agent_id", "flags": ["--agent-id"], "type": "string", "help": "Scope to agent.", "panel": "Scope" },
+ { "name": "app_id", "flags": ["--app-id"], "type": "string", "help": "Scope to app.", "panel": "Scope" },
+ { "name": "run_id", "flags": ["--run-id"], "type": "string", "help": "Scope to run.", "panel": "Scope" },
+ { "name": "messages", "flags": ["--messages"], "type": "string", "help": "Conversation messages as JSON." },
+ { "name": "file", "flags": ["--file", "-f"], "type": "path", "help": "Read messages from JSON file." },
+ { "name": "metadata", "flags": ["--metadata", "-m"], "type": "string", "help": "Custom metadata as JSON." },
+ { "name": "immutable", "flags": ["--immutable"], "type": "boolean", "default": false, "help": "Prevent future updates." },
+ { "name": "no_infer", "flags": ["--no-infer"], "type": "boolean", "default": false, "help": "Skip inference, store raw." },
+ { "name": "expires", "flags": ["--expires"], "type": "string", "help": "Expiration date (YYYY-MM-DD)." },
+ { "name": "categories", "flags": ["--categories"], "type": "string", "help": "Categories (JSON array or comma-separated)." },
+ { "name": "graph", "flags": ["--graph"], "type": "boolean", "default": false, "help": "Enable graph memory extraction.", "panel": "Scope" },
+ { "name": "no_graph", "flags": ["--no-graph"], "type": "boolean", "default": false, "help": "Disable graph memory extraction.", "panel": "Scope" },
+ { "name": "output", "flags": ["--output", "-o"], "type": "string", "default": "text", "help": "Output format: text, json, quiet.", "panel": "Output" }
+ ],
+ "apiEndpoint": "add"
+ },
+ {
+ "name": "search",
+ "description": "Search memories by semantic query.",
+ "usage": "mem0 search [OPTIONS]",
+ "needsBackend": true,
+ "needsConfig": true,
+ "resolveIds": true,
+ "resolveGraph": true,
+ "confirmDangerous": false,
+ "outputFormats": ["text", "json", "table"],
+ "defaultOutput": "text",
+ "arguments": [
+ {
+ "name": "query",
+ "type": "string",
+ "required": true,
+ "help": "Search query."
+ }
+ ],
+ "options": [
+ { "name": "user_id", "flags": ["--user-id", "-u"], "type": "string", "help": "Filter by user.", "panel": "Scope" },
+ { "name": "agent_id", "flags": ["--agent-id"], "type": "string", "help": "Filter by agent.", "panel": "Scope" },
+ { "name": "app_id", "flags": ["--app-id"], "type": "string", "help": "Filter by app.", "panel": "Scope" },
+ { "name": "run_id", "flags": ["--run-id"], "type": "string", "help": "Filter by run.", "panel": "Scope" },
+ { "name": "top_k", "flags": ["--top-k", "-k", "--limit"], "type": "integer", "default": 10, "help": "Number of results.", "panel": "Search" },
+ { "name": "threshold", "flags": ["--threshold"], "type": "float", "default": 0.3, "help": "Minimum similarity score.", "panel": "Search" },
+ { "name": "rerank", "flags": ["--rerank"], "type": "boolean", "default": false, "help": "Enable reranking (Platform only).", "panel": "Search" },
+ { "name": "keyword", "flags": ["--keyword"], "type": "boolean", "default": false, "help": "Use keyword search.", "panel": "Search" },
+ { "name": "filter_json", "flags": ["--filter"], "type": "string", "help": "Advanced filter expression (JSON).", "panel": "Search" },
+ { "name": "fields", "flags": ["--fields"], "type": "string", "help": "Specific fields to return (comma-separated).", "panel": "Search" },
+ { "name": "graph", "flags": ["--graph"], "type": "boolean", "default": false, "help": "Enable graph in search.", "panel": "Search" },
+ { "name": "no_graph", "flags": ["--no-graph"], "type": "boolean", "default": false, "help": "Disable graph in search.", "panel": "Search" },
+ { "name": "output", "flags": ["--output", "-o"], "type": "string", "default": "text", "help": "Output: text, json, table.", "panel": "Output" }
+ ],
+ "apiEndpoint": "search"
+ },
+ {
+ "name": "get",
+ "description": "Get a specific memory by ID.",
+ "usage": "mem0 get [OPTIONS]",
+ "needsBackend": true,
+ "needsConfig": false,
+ "resolveIds": false,
+ "resolveGraph": false,
+ "confirmDangerous": false,
+ "outputFormats": ["text", "json"],
+ "defaultOutput": "text",
+ "arguments": [
+ {
+ "name": "memory_id",
+ "type": "string",
+ "required": true,
+ "help": "Memory ID to retrieve."
+ }
+ ],
+ "options": [
+ { "name": "output", "flags": ["--output", "-o"], "type": "string", "default": "text", "help": "Output: text, json.", "panel": "Output" }
+ ],
+ "apiEndpoint": "get"
+ },
+ {
+ "name": "list",
+ "description": "List memories with optional filters.",
+ "usage": "mem0 list [OPTIONS]",
+ "needsBackend": true,
+ "needsConfig": true,
+ "resolveIds": true,
+ "resolveGraph": true,
+ "confirmDangerous": false,
+ "outputFormats": ["text", "json", "table"],
+ "defaultOutput": "table",
+ "arguments": [],
+ "options": [
+ { "name": "user_id", "flags": ["--user-id", "-u"], "type": "string", "help": "Filter by user.", "panel": "Scope" },
+ { "name": "agent_id", "flags": ["--agent-id"], "type": "string", "help": "Filter by agent.", "panel": "Scope" },
+ { "name": "app_id", "flags": ["--app-id"], "type": "string", "help": "Filter by app.", "panel": "Scope" },
+ { "name": "run_id", "flags": ["--run-id"], "type": "string", "help": "Filter by run.", "panel": "Scope" },
+ { "name": "page", "flags": ["--page"], "type": "integer", "default": 1, "help": "Page number.", "panel": "Pagination" },
+ { "name": "page_size", "flags": ["--page-size"], "type": "integer", "default": 100, "help": "Results per page.", "panel": "Pagination" },
+ { "name": "category", "flags": ["--category"], "type": "string", "help": "Filter by category.", "panel": "Filters" },
+ { "name": "after", "flags": ["--after"], "type": "string", "help": "Created after (YYYY-MM-DD).", "panel": "Filters" },
+ { "name": "before", "flags": ["--before"], "type": "string", "help": "Created before (YYYY-MM-DD).", "panel": "Filters" },
+ { "name": "graph", "flags": ["--graph"], "type": "boolean", "default": false, "help": "Enable graph in listing.", "panel": "Filters" },
+ { "name": "no_graph", "flags": ["--no-graph"], "type": "boolean", "default": false, "help": "Disable graph in listing.", "panel": "Filters" },
+ { "name": "output", "flags": ["--output", "-o"], "type": "string", "default": "table", "help": "Output: text, json, table.", "panel": "Output" }
+ ],
+ "apiEndpoint": "list"
+ },
+ {
+ "name": "update",
+ "description": "Update a memory's text or metadata.",
+ "usage": "mem0 update [text] [OPTIONS]",
+ "needsBackend": true,
+ "needsConfig": false,
+ "resolveIds": false,
+ "resolveGraph": false,
+ "confirmDangerous": false,
+ "outputFormats": ["text", "json", "quiet"],
+ "defaultOutput": "text",
+ "arguments": [
+ {
+ "name": "memory_id",
+ "type": "string",
+ "required": true,
+ "help": "Memory ID to update."
+ },
+ {
+ "name": "text",
+ "type": "string",
+ "required": false,
+ "help": "New memory text."
+ }
+ ],
+ "options": [
+ { "name": "metadata", "flags": ["--metadata", "-m"], "type": "string", "help": "Update metadata (JSON)." },
+ { "name": "output", "flags": ["--output", "-o"], "type": "string", "default": "text", "help": "Output: text, json, quiet.", "panel": "Output" }
+ ],
+ "apiEndpoint": "update"
+ },
+ {
+ "name": "delete",
+ "description": "Delete a memory, all memories matching a scope, or an entity.",
+ "usage": "mem0 delete [memory_id] [OPTIONS]",
+ "needsBackend": true,
+ "needsConfig": true,
+ "resolveIds": true,
+ "resolveGraph": false,
+ "confirmDangerous": true,
+ "outputFormats": ["text", "json", "quiet"],
+ "defaultOutput": "text",
+ "arguments": [
+ {
+ "name": "memory_id",
+ "type": "string",
+ "required": false,
+ "help": "Memory ID to delete (omit when using --all or --entity)."
+ }
+ ],
+ "options": [
+ { "name": "all", "flags": ["--all"], "type": "boolean", "default": false, "help": "Delete all memories matching scope filters." },
+ { "name": "entity", "flags": ["--entity"], "type": "boolean", "default": false, "help": "Delete the entity itself and all its memories (cascade)." },
+ { "name": "project", "flags": ["--project"], "type": "boolean", "default": false, "help": "With --all: delete ALL memories project-wide." },
+ { "name": "dry_run", "flags": ["--dry-run"], "type": "boolean", "default": false, "help": "Show what would be deleted without deleting." },
+ { "name": "force", "flags": ["--force"], "type": "boolean", "default": false, "help": "Skip confirmation." },
+ { "name": "user_id", "flags": ["--user-id", "-u"], "type": "string", "help": "Scope to user.", "panel": "Scope" },
+ { "name": "agent_id", "flags": ["--agent-id"], "type": "string", "help": "Scope to agent.", "panel": "Scope" },
+ { "name": "app_id", "flags": ["--app-id"], "type": "string", "help": "Scope to app.", "panel": "Scope" },
+ { "name": "run_id", "flags": ["--run-id"], "type": "string", "help": "Scope to run.", "panel": "Scope" },
+ { "name": "output", "flags": ["--output", "-o"], "type": "string", "default": "text", "help": "Output: text, json, quiet.", "panel": "Output" }
+ ],
+ "apiEndpoint": "delete",
+ "notes": "Mutually exclusive modes: (1) mem0 delete -- single memory, (2) mem0 delete --all [scope] -- bulk delete, (3) mem0 delete --entity [scope] -- entity cascade delete. Cannot combine with --all or --entity, and cannot combine --all with --entity."
+ },
+ {
+ "name": "import",
+ "description": "Import memories from a JSON file.",
+ "usage": "mem0 import [OPTIONS]",
+ "needsBackend": true,
+ "needsConfig": true,
+ "resolveIds": true,
+ "resolveGraph": false,
+ "confirmDangerous": false,
+ "outputFormats": ["text"],
+ "defaultOutput": "text",
+ "arguments": [
+ {
+ "name": "file_path",
+ "type": "string",
+ "required": true,
+ "help": "JSON file to import."
+ }
+ ],
+ "options": [
+ { "name": "user_id", "flags": ["--user-id", "-u"], "type": "string", "help": "Override user ID.", "panel": "Scope" },
+ { "name": "agent_id", "flags": ["--agent-id"], "type": "string", "help": "Override agent ID.", "panel": "Scope" }
+ ],
+ "apiEndpoint": "add"
+ },
+ {
+ "name": "config",
+ "description": "Manage mem0 configuration.",
+ "isGroup": true,
+ "subcommands": [
+ {
+ "name": "show",
+ "description": "Display current configuration (secrets redacted).",
+ "usage": "mem0 config show",
+ "needsBackend": false,
+ "needsConfig": false,
+ "arguments": [],
+ "options": []
+ },
+ {
+ "name": "get",
+ "description": "Get a configuration value.",
+ "usage": "mem0 config get ",
+ "needsBackend": false,
+ "needsConfig": false,
+ "arguments": [
+ {
+ "name": "key",
+ "type": "string",
+ "required": true,
+ "help": "Config key (e.g. platform.api_key)."
+ }
+ ],
+ "options": []
+ },
+ {
+ "name": "set",
+ "description": "Set a configuration value.",
+ "usage": "mem0 config set ",
+ "needsBackend": false,
+ "needsConfig": false,
+ "arguments": [
+ {
+ "name": "key",
+ "type": "string",
+ "required": true,
+ "help": "Config key (e.g. platform.api_key)."
+ },
+ {
+ "name": "value",
+ "type": "string",
+ "required": true,
+ "help": "Value to set."
+ }
+ ],
+ "options": []
+ }
+ ]
+ },
+ {
+ "name": "entity",
+ "description": "Manage entities.",
+ "isGroup": true,
+ "subcommands": [
+ {
+ "name": "list",
+ "description": "List all entities of a given type.",
+ "usage": "mem0 entity list ",
+ "needsBackend": true,
+ "needsConfig": false,
+ "resolveIds": false,
+ "resolveGraph": false,
+ "confirmDangerous": false,
+ "outputFormats": ["table", "json"],
+ "defaultOutput": "table",
+ "arguments": [
+ {
+ "name": "entity_type",
+ "type": "string",
+ "required": true,
+ "help": "Entity type: users, agents, apps, runs.",
+ "choices": ["users", "agents", "apps", "runs"]
+ }
+ ],
+ "options": [
+ { "name": "output", "flags": ["--output", "-o"], "type": "string", "default": "table", "help": "Output: table, json.", "panel": "Output" }
+ ],
+ "apiEndpoint": "entities"
+ },
+ {
+ "name": "delete",
+ "description": "Delete an entity and ALL its memories (cascade).",
+ "usage": "mem0 entity delete [OPTIONS]",
+ "needsBackend": true,
+ "needsConfig": false,
+ "resolveIds": false,
+ "resolveGraph": false,
+ "confirmDangerous": true,
+ "outputFormats": ["text", "json", "quiet"],
+ "defaultOutput": "text",
+ "arguments": [],
+ "options": [
+ { "name": "user_id", "flags": ["--user-id", "-u"], "type": "string", "help": "User ID.", "panel": "Scope" },
+ { "name": "agent_id", "flags": ["--agent-id"], "type": "string", "help": "Agent ID.", "panel": "Scope" },
+ { "name": "app_id", "flags": ["--app-id"], "type": "string", "help": "App ID.", "panel": "Scope" },
+ { "name": "run_id", "flags": ["--run-id"], "type": "string", "help": "Run ID.", "panel": "Scope" },
+ { "name": "dry_run", "flags": ["--dry-run"], "type": "boolean", "default": false, "help": "Show what would be deleted without deleting." },
+ { "name": "force", "flags": ["--force"], "type": "boolean", "default": false, "help": "Skip confirmation." },
+ { "name": "output", "flags": ["--output", "-o"], "type": "string", "default": "text", "help": "Output: text, json, quiet.", "panel": "Output" }
+ ],
+ "apiEndpoint": "deleteEntities"
+ }
+ ]
+ },
+ {
+ "name": "init",
+ "description": "Setup wizard for mem0 CLI. Supports email login (--email) or manual API key (--api-key).",
+ "usage": "mem0 init [OPTIONS]",
+ "needsBackend": false,
+ "needsConfig": false,
+ "resolveIds": false,
+ "resolveGraph": false,
+ "confirmDangerous": false,
+ "arguments": [],
+ "options": [
+ { "name": "api-key", "flags": ["--api-key"], "type": "string", "default": null, "help": "API key (skip prompt)." },
+ { "name": "user-id", "flags": ["-u", "--user-id"], "type": "string", "default": null, "help": "Default user ID (skip prompt)." },
+ { "name": "email", "flags": ["--email"], "type": "string", "default": null, "help": "Login via email verification code." },
+ { "name": "code", "flags": ["--code"], "type": "string", "default": null, "help": "Verification code (use with --email for non-interactive login)." },
+ { "name": "force", "flags": ["--force"], "type": "boolean", "default": false, "help": "Overwrite existing config without confirmation." }
+ ]
+ },
+ {
+ "name": "status",
+ "description": "Check connectivity and authentication.",
+ "usage": "mem0 status [OPTIONS]",
+ "needsBackend": true,
+ "needsConfig": true,
+ "resolveIds": false,
+ "resolveGraph": false,
+ "confirmDangerous": false,
+ "arguments": [],
+ "options": []
+ },
+ {
+ "name": "help",
+ "description": "Show help. Use --json for machine-readable output (for LLM agents).",
+ "usage": "mem0 help [OPTIONS]",
+ "needsBackend": false,
+ "needsConfig": false,
+ "resolveIds": false,
+ "resolveGraph": false,
+ "confirmDangerous": false,
+ "arguments": [],
+ "options": [
+ { "name": "json", "flags": ["--json"], "type": "boolean", "default": false, "help": "Output machine-readable JSON for LLM agents." }
+ ]
+ }
+ ]
+}
diff --git a/cli/node/README.md b/cli/node/README.md
new file mode 100644
index 000000000..6110d1149
--- /dev/null
+++ b/cli/node/README.md
@@ -0,0 +1,337 @@
+# mem0 CLI (Node.js)
+
+The official command-line interface for [mem0](https://mem0.ai) โ the memory layer for AI agents. TypeScript implementation.
+
+> **Built for AI agents.** Pass `--agent` (or `--json`) as a global flag on any command to get structured JSON output optimized for programmatic consumption โ sanitized fields, no colors or spinners, and errors as JSON too.
+
+## Prerequisites
+
+- Node.js **18+**
+- pnpm (`npm install -g pnpm`) โ for development only
+
+## Installation
+
+```bash
+npm install -g @mem0/cli
+```
+
+## Quick start
+
+```bash
+# Interactive setup wizard
+mem0 init
+
+# Or login via email
+mem0 init --email alice@company.com
+
+# Or authenticate with an existing API key
+mem0 init --api-key m0-xxx
+
+# Add a memory
+mem0 add "I prefer dark mode and use vim keybindings" --user-id alice
+
+# Search memories
+mem0 search "What are Alice's preferences?" --user-id alice
+
+# List all memories for a user
+mem0 list --user-id alice
+
+# Get a specific memory
+mem0 get
+
+# Update a memory
+mem0 update "I switched to light mode"
+
+# Delete a memory
+mem0 delete
+```
+
+## Commands
+
+### `mem0 init`
+
+Interactive setup wizard. Prompts for your API key and default user ID.
+
+```bash
+mem0 init
+mem0 init --api-key m0-xxx --user-id alice
+mem0 init --email alice@company.com
+```
+
+If an existing configuration is detected, the CLI asks for confirmation before overwriting. Use `--force` to skip the prompt (useful in CI/CD).
+
+```bash
+mem0 init --api-key m0-xxx --user-id alice --force
+```
+
+| Flag | Description |
+|------|-------------|
+| `--api-key` | API key (skip prompt) |
+| `-u, --user-id` | Default user ID (skip prompt) |
+| `--email` | Login via email verification code |
+| `--code` | Verification code (use with `--email` for non-interactive login) |
+| `--force` | Overwrite existing config without confirmation |
+
+### `mem0 add`
+
+Add a memory from text, a JSON messages array, a file, or stdin.
+
+```bash
+mem0 add "I prefer dark mode" --user-id alice
+mem0 add --file conversation.json --user-id alice
+echo "Loves hiking on weekends" | mem0 add --user-id alice
+```
+
+| Flag | Description |
+|------|-------------|
+| `-u, --user-id` | Scope to a user |
+| `--agent-id` | Scope to an agent |
+| `--messages` | Conversation messages as JSON |
+| `-f, --file` | Read messages from a JSON file |
+| `-m, --metadata` | Custom metadata as JSON |
+| `--categories` | Categories (JSON array or comma-separated) |
+| `--graph / --no-graph` | Enable or disable graph memory extraction |
+| `-o, --output` | Output format: `text`, `json`, `quiet` |
+
+### `mem0 search`
+
+Search memories using natural language.
+
+```bash
+mem0 search "dietary restrictions" --user-id alice
+mem0 search "preferred tools" --user-id alice --output json --top-k 5
+```
+
+| Flag | Description |
+|------|-------------|
+| `-u, --user-id` | Filter by user |
+| `-k, --top-k` | Number of results (default: 10) |
+| `--threshold` | Minimum similarity score (default: 0.3) |
+| `--rerank` | Enable reranking |
+| `--keyword` | Use keyword search instead of semantic |
+| `--filter` | Advanced filter expression (JSON) |
+| `--graph / --no-graph` | Enable or disable graph in search |
+| `-o, --output` | Output format: `text`, `json`, `table` |
+
+### `mem0 list`
+
+List memories with optional filters and pagination.
+
+```bash
+mem0 list --user-id alice
+mem0 list --user-id alice --category preferences --output json
+mem0 list --user-id alice --after 2024-01-01 --page-size 50
+```
+
+| Flag | Description |
+|------|-------------|
+| `-u, --user-id` | Filter by user |
+| `--page` | Page number (default: 1) |
+| `--page-size` | Results per page (default: 100) |
+| `--category` | Filter by category |
+| `--after` | Created after date (YYYY-MM-DD) |
+| `--before` | Created before date (YYYY-MM-DD) |
+| `-o, --output` | Output format: `text`, `json`, `table` |
+
+### `mem0 get`
+
+Retrieve a specific memory by ID.
+
+```bash
+mem0 get 7b3c1a2e-4d5f-6789-abcd-ef0123456789
+mem0 get 7b3c1a2e-4d5f-6789-abcd-ef0123456789 --output json
+```
+
+### `mem0 update`
+
+Update the text or metadata of an existing memory.
+
+```bash
+mem0 update "Updated preference text"
+mem0 update --metadata '{"priority": "high"}'
+echo "new text" | mem0 update
+```
+
+### `mem0 delete`
+
+Delete a single memory, all memories for a scope, or an entire entity.
+
+```bash
+# Delete a single memory
+mem0 delete
+
+# Delete all memories for a user
+mem0 delete --all --user-id alice --force
+
+# Delete all memories project-wide
+mem0 delete --all --project --force
+
+# Preview what would be deleted
+mem0 delete --all --user-id alice --dry-run
+```
+
+| Flag | Description |
+|------|-------------|
+| `--all` | Delete all memories matching scope filters |
+| `--entity` | Delete the entity and all its memories |
+| `--project` | With `--all`: delete all memories project-wide |
+| `--dry-run` | Preview without deleting |
+| `--force` | Skip confirmation prompt |
+
+### `mem0 import`
+
+Bulk import memories from a JSON file.
+
+```bash
+mem0 import data.json --user-id alice
+```
+
+The file should be a JSON array where each item has a `memory` (or `text` or `content`) field and optional `user_id`, `agent_id`, and `metadata` fields.
+
+### `mem0 config`
+
+View or modify the local CLI configuration.
+
+```bash
+mem0 config show # Display current config (secrets redacted)
+mem0 config get api_key # Get a specific value
+mem0 config set user_id bob # Set a value
+```
+
+### `mem0 entity`
+
+List or delete entities (users, agents, apps, runs).
+
+```bash
+mem0 entity list users
+mem0 entity list agents --output json
+mem0 entity delete --user-id alice --force
+```
+
+### `mem0 event`
+
+Inspect background processing events created by async operations (e.g. bulk deletes, large add jobs).
+
+```bash
+# List recent events
+mem0 event list
+
+# Check the status of a specific event
+mem0 event status
+```
+
+| Flag | Description |
+|------|-------------|
+| `-o, --output` | Output format: `text`, `json` |
+
+### `mem0 status`
+
+Verify your API connection and display the current project.
+
+```bash
+mem0 status
+```
+
+### `mem0 version`
+
+Print the CLI version.
+
+```bash
+mem0 version
+```
+
+## Agent mode
+
+Pass `--agent` (or its alias `--json`) as a **global flag** on any command to get output designed for AI agent tool loops:
+
+```bash
+mem0 --agent search "user preferences" --user-id alice
+mem0 --agent add "User prefers dark mode" --user-id alice
+mem0 --agent list --user-id alice
+mem0 --agent delete --all --user-id alice --force
+```
+
+Every command returns the same envelope shape:
+
+```json
+{
+ "status": "success",
+ "command": "search",
+ "duration_ms": 134,
+ "scope": { "user_id": "alice" },
+ "count": 2,
+ "data": [
+ { "id": "abc-123", "memory": "User prefers dark mode", "score": 0.97, "created_at": "2026-01-15", "categories": ["preferences"] }
+ ]
+}
+```
+
+What agent mode does differently from `--output json`:
+
+- **Sanitized `data`**: only the fields an agent needs (id, memory, score, etc.) โ no internal API noise
+- **No human output**: spinners, colors, and banners are suppressed entirely
+- **Errors as JSON**: errors go to stdout as `{"status": "error", "command": "...", "error": "..."}` with a non-zero exit code
+
+Use `mem0 help --json` to get the full command tree as JSON โ useful for agents that need to self-discover available commands.
+
+## Output formats
+
+Control how results are displayed with `--output`:
+
+| Format | Description |
+|--------|-------------|
+| `text` | Human-readable with colors and formatting (default) |
+| `json` | Structured JSON for piping to `jq` (raw API response) |
+| `table` | Tabular format (default for `list`) |
+| `quiet` | Minimal โ just IDs or status codes |
+| `agent` | Structured JSON envelope with sanitized fields (set by `--agent`/`--json`) |
+
+## Global flags
+
+These flags are available on all commands:
+
+| Flag | Description |
+|------|-------------|
+| `--json` | Enable agent mode: structured JSON envelope output, no colors or spinners |
+| `--agent` | Alias for `--json` |
+| `--api-key` | Override the configured API key for this request |
+| `--base-url` | Override the configured API base URL for this request |
+| `-o, --output` | Set the output format |
+
+## Environment variables
+
+| Variable | Description |
+|----------|-------------|
+| `MEM0_API_KEY` | API key (overrides config file) |
+| `MEM0_BASE_URL` | API base URL |
+| `MEM0_USER_ID` | Default user ID |
+| `MEM0_AGENT_ID` | Default agent ID |
+| `MEM0_APP_ID` | Default app ID |
+| `MEM0_RUN_ID` | Default run ID |
+| `MEM0_ENABLE_GRAPH` | Enable graph memory (`true` / `false`) |
+
+Environment variables take precedence over values in the config file, which take precedence over defaults.
+
+## Development
+
+```bash
+cd cli/node
+pnpm install
+
+# Development mode (runs TypeScript directly, no build needed)
+pnpm dev --help
+pnpm dev add "test memory" --user-id alice
+pnpm dev search "test" --user-id alice
+
+# Or build first, then run the compiled JS
+pnpm build
+node dist/index.js --help
+```
+
+## Documentation
+
+Full documentation is available at [docs.mem0.ai/platform/cli](https://docs.mem0.ai/platform/cli).
+
+## License
+
+Apache-2.0
diff --git a/cli/node/development.md b/cli/node/development.md
new file mode 100644
index 000000000..5b5d0b060
--- /dev/null
+++ b/cli/node/development.md
@@ -0,0 +1,91 @@
+# Development
+
+## Prerequisites
+
+- Node.js **18+**
+- pnpm (`npm install -g pnpm`)
+
+## Setup
+
+From the `node/` directory:
+
+```bash
+pnpm install
+```
+
+## Running the CLI
+
+There are two ways to run the CLI during development:
+
+### Option 1: Development mode (no build needed)
+
+Uses `tsx` to run TypeScript directly. Pass CLI arguments after `pnpm dev`:
+
+```bash
+pnpm dev --help
+pnpm dev version
+pnpm dev add "test memory" --user-id alice
+pnpm dev search "test" --user-id alice
+pnpm dev config show
+```
+
+> **Note:** Do NOT use `pnpm dev -- --help`. With pnpm, arguments pass through directly โ adding `--` inserts a literal `--` that breaks the CLI parser.
+
+### Option 2: Build and run compiled JS
+
+```bash
+# Build first
+pnpm build
+
+# Run the compiled CLI
+node dist/index.js --help
+node dist/index.js version
+node dist/index.js add "test memory" --user-id alice
+```
+
+### Option 3: Link globally (makes `mem0` available system-wide)
+
+```bash
+pnpm build
+pnpm link --global
+
+# Now use it like a normal CLI
+mem0 --help
+mem0 version
+```
+
+> **Warning:** If you also have the Python CLI installed, both register the `mem0` command. The last one linked/installed wins. Unlink with `pnpm unlink --global`.
+
+## Build
+
+```bash
+pnpm build
+```
+
+The compiled output is in `dist/`.
+
+## Run tests
+
+```bash
+# Run all tests
+pnpm test
+
+# Watch mode
+pnpm test:watch
+```
+
+## Lint
+
+```bash
+# Check
+pnpm lint
+
+# Auto-fix
+pnpm lint:fix
+```
+
+## Type checking
+
+```bash
+pnpm typecheck
+```
diff --git a/cli/node/package.json b/cli/node/package.json
new file mode 100644
index 000000000..15a9643d6
--- /dev/null
+++ b/cli/node/package.json
@@ -0,0 +1,47 @@
+{
+ "name": "@mem0/cli",
+ "version": "0.2.3",
+ "description": "The official CLI for mem0 โ the memory layer for AI agents",
+ "type": "module",
+ "bin": {
+ "mem0": "./dist/index.js"
+ },
+ "scripts": {
+ "build": "tsup",
+ "dev": "tsx src/index.ts",
+ "test": "vitest run",
+ "test:watch": "vitest",
+ "lint": "biome check src/",
+ "lint:fix": "biome check --write src/",
+ "typecheck": "tsc --noEmit"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "license": "Apache-2.0",
+ "author": "mem0.ai ",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/mem0ai/mem0",
+ "directory": "cli/node"
+ },
+ "keywords": ["mem0", "memory", "ai", "agents", "cli"],
+ "publishConfig": {
+ "access": "public"
+ },
+ "dependencies": {
+ "commander": "^12.0.0",
+ "chalk": "^5.3.0",
+ "cli-table3": "^0.6.4",
+ "ora": "^8.0.0",
+ "boxen": "^7.1.0"
+ },
+ "devDependencies": {
+ "typescript": "^5.4.0",
+ "tsup": "^8.0.0",
+ "tsx": "^4.7.0",
+ "vitest": "^1.5.0",
+ "@biomejs/biome": "^1.7.0",
+ "@types/node": "^20.0.0"
+ }
+}
diff --git a/cli/node/pnpm-lock.yaml b/cli/node/pnpm-lock.yaml
new file mode 100644
index 000000000..f0371cd2c
--- /dev/null
+++ b/cli/node/pnpm-lock.yaml
@@ -0,0 +1,2066 @@
+lockfileVersion: '9.0'
+
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
+importers:
+
+ .:
+ dependencies:
+ boxen:
+ specifier: ^7.1.0
+ version: 7.1.1
+ chalk:
+ specifier: ^5.3.0
+ version: 5.6.2
+ cli-table3:
+ specifier: ^0.6.4
+ version: 0.6.5
+ commander:
+ specifier: ^12.0.0
+ version: 12.1.0
+ ora:
+ specifier: ^8.0.0
+ version: 8.2.0
+ devDependencies:
+ '@biomejs/biome':
+ specifier: ^1.7.0
+ version: 1.9.4
+ '@types/node':
+ specifier: ^20.0.0
+ version: 20.19.37
+ tsup:
+ specifier: ^8.0.0
+ version: 8.5.1(postcss@8.5.8)(tsx@4.21.0)(typescript@5.9.3)
+ tsx:
+ specifier: ^4.7.0
+ version: 4.21.0
+ typescript:
+ specifier: ^5.4.0
+ version: 5.9.3
+ vitest:
+ specifier: ^1.5.0
+ version: 1.6.1(@types/node@20.19.37)
+
+packages:
+
+ '@biomejs/biome@1.9.4':
+ resolution: {integrity: sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==}
+ engines: {node: '>=14.21.3'}
+ hasBin: true
+
+ '@biomejs/cli-darwin-arm64@1.9.4':
+ resolution: {integrity: sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==}
+ engines: {node: '>=14.21.3'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@biomejs/cli-darwin-x64@1.9.4':
+ resolution: {integrity: sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==}
+ engines: {node: '>=14.21.3'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@biomejs/cli-linux-arm64-musl@1.9.4':
+ resolution: {integrity: sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==}
+ engines: {node: '>=14.21.3'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@biomejs/cli-linux-arm64@1.9.4':
+ resolution: {integrity: sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==}
+ engines: {node: '>=14.21.3'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@biomejs/cli-linux-x64-musl@1.9.4':
+ resolution: {integrity: sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==}
+ engines: {node: '>=14.21.3'}
+ cpu: [x64]
+ os: [linux]
+
+ '@biomejs/cli-linux-x64@1.9.4':
+ resolution: {integrity: sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==}
+ engines: {node: '>=14.21.3'}
+ cpu: [x64]
+ os: [linux]
+
+ '@biomejs/cli-win32-arm64@1.9.4':
+ resolution: {integrity: sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==}
+ engines: {node: '>=14.21.3'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@biomejs/cli-win32-x64@1.9.4':
+ resolution: {integrity: sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==}
+ engines: {node: '>=14.21.3'}
+ cpu: [x64]
+ os: [win32]
+
+ '@colors/colors@1.5.0':
+ resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==}
+ engines: {node: '>=0.1.90'}
+
+ '@esbuild/aix-ppc64@0.21.5':
+ resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==}
+ engines: {node: '>=12'}
+ cpu: [ppc64]
+ os: [aix]
+
+ '@esbuild/aix-ppc64@0.27.4':
+ resolution: {integrity: sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [aix]
+
+ '@esbuild/android-arm64@0.21.5':
+ resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [android]
+
+ '@esbuild/android-arm64@0.27.4':
+ resolution: {integrity: sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [android]
+
+ '@esbuild/android-arm@0.21.5':
+ resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==}
+ engines: {node: '>=12'}
+ cpu: [arm]
+ os: [android]
+
+ '@esbuild/android-arm@0.27.4':
+ resolution: {integrity: sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [android]
+
+ '@esbuild/android-x64@0.21.5':
+ resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [android]
+
+ '@esbuild/android-x64@0.27.4':
+ resolution: {integrity: sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [android]
+
+ '@esbuild/darwin-arm64@0.21.5':
+ resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@esbuild/darwin-arm64@0.27.4':
+ resolution: {integrity: sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@esbuild/darwin-x64@0.21.5':
+ resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@esbuild/darwin-x64@0.27.4':
+ resolution: {integrity: sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@esbuild/freebsd-arm64@0.21.5':
+ resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@esbuild/freebsd-arm64@0.27.4':
+ resolution: {integrity: sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@esbuild/freebsd-x64@0.21.5':
+ resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@esbuild/freebsd-x64@0.27.4':
+ resolution: {integrity: sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@esbuild/linux-arm64@0.21.5':
+ resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@esbuild/linux-arm64@0.27.4':
+ resolution: {integrity: sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@esbuild/linux-arm@0.21.5':
+ resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==}
+ engines: {node: '>=12'}
+ cpu: [arm]
+ os: [linux]
+
+ '@esbuild/linux-arm@0.27.4':
+ resolution: {integrity: sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [linux]
+
+ '@esbuild/linux-ia32@0.21.5':
+ resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==}
+ engines: {node: '>=12'}
+ cpu: [ia32]
+ os: [linux]
+
+ '@esbuild/linux-ia32@0.27.4':
+ resolution: {integrity: sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [linux]
+
+ '@esbuild/linux-loong64@0.21.5':
+ resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==}
+ engines: {node: '>=12'}
+ cpu: [loong64]
+ os: [linux]
+
+ '@esbuild/linux-loong64@0.27.4':
+ resolution: {integrity: sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==}
+ engines: {node: '>=18'}
+ cpu: [loong64]
+ os: [linux]
+
+ '@esbuild/linux-mips64el@0.21.5':
+ resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==}
+ engines: {node: '>=12'}
+ cpu: [mips64el]
+ os: [linux]
+
+ '@esbuild/linux-mips64el@0.27.4':
+ resolution: {integrity: sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==}
+ engines: {node: '>=18'}
+ cpu: [mips64el]
+ os: [linux]
+
+ '@esbuild/linux-ppc64@0.21.5':
+ resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==}
+ engines: {node: '>=12'}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@esbuild/linux-ppc64@0.27.4':
+ resolution: {integrity: sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@esbuild/linux-riscv64@0.21.5':
+ resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==}
+ engines: {node: '>=12'}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@esbuild/linux-riscv64@0.27.4':
+ resolution: {integrity: sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==}
+ engines: {node: '>=18'}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@esbuild/linux-s390x@0.21.5':
+ resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==}
+ engines: {node: '>=12'}
+ cpu: [s390x]
+ os: [linux]
+
+ '@esbuild/linux-s390x@0.27.4':
+ resolution: {integrity: sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==}
+ engines: {node: '>=18'}
+ cpu: [s390x]
+ os: [linux]
+
+ '@esbuild/linux-x64@0.21.5':
+ resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [linux]
+
+ '@esbuild/linux-x64@0.27.4':
+ resolution: {integrity: sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [linux]
+
+ '@esbuild/netbsd-arm64@0.27.4':
+ resolution: {integrity: sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [netbsd]
+
+ '@esbuild/netbsd-x64@0.21.5':
+ resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [netbsd]
+
+ '@esbuild/netbsd-x64@0.27.4':
+ resolution: {integrity: sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [netbsd]
+
+ '@esbuild/openbsd-arm64@0.27.4':
+ resolution: {integrity: sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [openbsd]
+
+ '@esbuild/openbsd-x64@0.21.5':
+ resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [openbsd]
+
+ '@esbuild/openbsd-x64@0.27.4':
+ resolution: {integrity: sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [openbsd]
+
+ '@esbuild/openharmony-arm64@0.27.4':
+ resolution: {integrity: sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [openharmony]
+
+ '@esbuild/sunos-x64@0.21.5':
+ resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [sunos]
+
+ '@esbuild/sunos-x64@0.27.4':
+ resolution: {integrity: sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [sunos]
+
+ '@esbuild/win32-arm64@0.21.5':
+ resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@esbuild/win32-arm64@0.27.4':
+ resolution: {integrity: sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@esbuild/win32-ia32@0.21.5':
+ resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==}
+ engines: {node: '>=12'}
+ cpu: [ia32]
+ os: [win32]
+
+ '@esbuild/win32-ia32@0.27.4':
+ resolution: {integrity: sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [win32]
+
+ '@esbuild/win32-x64@0.21.5':
+ resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [win32]
+
+ '@esbuild/win32-x64@0.27.4':
+ resolution: {integrity: sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [win32]
+
+ '@jest/schemas@29.6.3':
+ resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ '@jridgewell/gen-mapping@0.3.13':
+ resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
+
+ '@jridgewell/resolve-uri@3.1.2':
+ resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
+ engines: {node: '>=6.0.0'}
+
+ '@jridgewell/sourcemap-codec@1.5.5':
+ resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
+
+ '@jridgewell/trace-mapping@0.3.31':
+ resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
+
+ '@rollup/rollup-android-arm-eabi@4.60.0':
+ resolution: {integrity: sha512-WOhNW9K8bR3kf4zLxbfg6Pxu2ybOUbB2AjMDHSQx86LIF4rH4Ft7vmMwNt0loO0eonglSNy4cpD3MKXXKQu0/A==}
+ cpu: [arm]
+ os: [android]
+
+ '@rollup/rollup-android-arm64@4.60.0':
+ resolution: {integrity: sha512-u6JHLll5QKRvjciE78bQXDmqRqNs5M/3GVqZeMwvmjaNODJih/WIrJlFVEihvV0MiYFmd+ZyPr9wxOVbPAG2Iw==}
+ cpu: [arm64]
+ os: [android]
+
+ '@rollup/rollup-darwin-arm64@4.60.0':
+ resolution: {integrity: sha512-qEF7CsKKzSRc20Ciu2Zw1wRrBz4g56F7r/vRwY430UPp/nt1x21Q/fpJ9N5l47WWvJlkNCPJz3QRVw008fi7yA==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@rollup/rollup-darwin-x64@4.60.0':
+ resolution: {integrity: sha512-WADYozJ4QCnXCH4wPB+3FuGmDPoFseVCUrANmA5LWwGmC6FL14BWC7pcq+FstOZv3baGX65tZ378uT6WG8ynTw==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@rollup/rollup-freebsd-arm64@4.60.0':
+ resolution: {integrity: sha512-6b8wGHJlDrGeSE3aH5mGNHBjA0TTkxdoNHik5EkvPHCt351XnigA4pS7Wsj/Eo9Y8RBU6f35cjN9SYmCFBtzxw==}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@rollup/rollup-freebsd-x64@4.60.0':
+ resolution: {integrity: sha512-h25Ga0t4jaylMB8M/JKAyrvvfxGRjnPQIR8lnCayyzEjEOx2EJIlIiMbhpWxDRKGKF8jbNH01NnN663dH638mA==}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@rollup/rollup-linux-arm-gnueabihf@4.60.0':
+ resolution: {integrity: sha512-RzeBwv0B3qtVBWtcuABtSuCzToo2IEAIQrcyB/b2zMvBWVbjo8bZDjACUpnaafaxhTw2W+imQbP2BD1usasK4g==}
+ cpu: [arm]
+ os: [linux]
+
+ '@rollup/rollup-linux-arm-musleabihf@4.60.0':
+ resolution: {integrity: sha512-Sf7zusNI2CIU1HLzuu9Tc5YGAHEZs5Lu7N1ssJG4Tkw6e0MEsN7NdjUDDfGNHy2IU+ENyWT+L2obgWiguWibWQ==}
+ cpu: [arm]
+ os: [linux]
+
+ '@rollup/rollup-linux-arm64-gnu@4.60.0':
+ resolution: {integrity: sha512-DX2x7CMcrJzsE91q7/O02IJQ5/aLkVtYFryqCjduJhUfGKG6yJV8hxaw8pZa93lLEpPTP/ohdN4wFz7yp/ry9A==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@rollup/rollup-linux-arm64-musl@4.60.0':
+ resolution: {integrity: sha512-09EL+yFVbJZlhcQfShpswwRZ0Rg+z/CsSELFCnPt3iK+iqwGsI4zht3secj5vLEs957QvFFXnzAT0FFPIxSrkQ==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@rollup/rollup-linux-loong64-gnu@4.60.0':
+ resolution: {integrity: sha512-i9IcCMPr3EXm8EQg5jnja0Zyc1iFxJjZWlb4wr7U2Wx/GrddOuEafxRdMPRYVaXjgbhvqalp6np07hN1w9kAKw==}
+ cpu: [loong64]
+ os: [linux]
+
+ '@rollup/rollup-linux-loong64-musl@4.60.0':
+ resolution: {integrity: sha512-DGzdJK9kyJ+B78MCkWeGnpXJ91tK/iKA6HwHxF4TAlPIY7GXEvMe8hBFRgdrR9Ly4qebR/7gfUs9y2IoaVEyog==}
+ cpu: [loong64]
+ os: [linux]
+
+ '@rollup/rollup-linux-ppc64-gnu@4.60.0':
+ resolution: {integrity: sha512-RwpnLsqC8qbS8z1H1AxBA1H6qknR4YpPR9w2XX0vo2Sz10miu57PkNcnHVaZkbqyw/kUWfKMI73jhmfi9BRMUQ==}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@rollup/rollup-linux-ppc64-musl@4.60.0':
+ resolution: {integrity: sha512-Z8pPf54Ly3aqtdWC3G4rFigZgNvd+qJlOE52fmko3KST9SoGfAdSRCwyoyG05q1HrrAblLbk1/PSIV+80/pxLg==}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@rollup/rollup-linux-riscv64-gnu@4.60.0':
+ resolution: {integrity: sha512-3a3qQustp3COCGvnP4SvrMHnPQ9d1vzCakQVRTliaz8cIp/wULGjiGpbcqrkv0WrHTEp8bQD/B3HBjzujVWLOA==}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@rollup/rollup-linux-riscv64-musl@4.60.0':
+ resolution: {integrity: sha512-pjZDsVH/1VsghMJ2/kAaxt6dL0psT6ZexQVrijczOf+PeP2BUqTHYejk3l6TlPRydggINOeNRhvpLa0AYpCWSQ==}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@rollup/rollup-linux-s390x-gnu@4.60.0':
+ resolution: {integrity: sha512-3ObQs0BhvPgiUVZrN7gqCSvmFuMWvWvsjG5ayJ3Lraqv+2KhOsp+pUbigqbeWqueGIsnn+09HBw27rJ+gYK4VQ==}
+ cpu: [s390x]
+ os: [linux]
+
+ '@rollup/rollup-linux-x64-gnu@4.60.0':
+ resolution: {integrity: sha512-EtylprDtQPdS5rXvAayrNDYoJhIz1/vzN2fEubo3yLE7tfAw+948dO0g4M0vkTVFhKojnF+n6C8bDNe+gDRdTg==}
+ cpu: [x64]
+ os: [linux]
+
+ '@rollup/rollup-linux-x64-musl@4.60.0':
+ resolution: {integrity: sha512-k09oiRCi/bHU9UVFqD17r3eJR9bn03TyKraCrlz5ULFJGdJGi7VOmm9jl44vOJvRJ6P7WuBi/s2A97LxxHGIdw==}
+ cpu: [x64]
+ os: [linux]
+
+ '@rollup/rollup-openbsd-x64@4.60.0':
+ resolution: {integrity: sha512-1o/0/pIhozoSaDJoDcec+IVLbnRtQmHwPV730+AOD29lHEEo4F5BEUB24H0OBdhbBBDwIOSuf7vgg0Ywxdfiiw==}
+ cpu: [x64]
+ os: [openbsd]
+
+ '@rollup/rollup-openharmony-arm64@4.60.0':
+ resolution: {integrity: sha512-pESDkos/PDzYwtyzB5p/UoNU/8fJo68vcXM9ZW2V0kjYayj1KaaUfi1NmTUTUpMn4UhU4gTuK8gIaFO4UGuMbA==}
+ cpu: [arm64]
+ os: [openharmony]
+
+ '@rollup/rollup-win32-arm64-msvc@4.60.0':
+ resolution: {integrity: sha512-hj1wFStD7B1YBeYmvY+lWXZ7ey73YGPcViMShYikqKT1GtstIKQAtfUI6yrzPjAy/O7pO0VLXGmUVWXQMaYgTQ==}
+ cpu: [arm64]
+ os: [win32]
+
+ '@rollup/rollup-win32-ia32-msvc@4.60.0':
+ resolution: {integrity: sha512-SyaIPFoxmUPlNDq5EHkTbiKzmSEmq/gOYFI/3HHJ8iS/v1mbugVa7dXUzcJGQfoytp9DJFLhHH4U3/eTy2Bq4w==}
+ cpu: [ia32]
+ os: [win32]
+
+ '@rollup/rollup-win32-x64-gnu@4.60.0':
+ resolution: {integrity: sha512-RdcryEfzZr+lAr5kRm2ucN9aVlCCa2QNq4hXelZxb8GG0NJSazq44Z3PCCc8wISRuCVnGs0lQJVX5Vp6fKA+IA==}
+ cpu: [x64]
+ os: [win32]
+
+ '@rollup/rollup-win32-x64-msvc@4.60.0':
+ resolution: {integrity: sha512-PrsWNQ8BuE00O3Xsx3ALh2Df8fAj9+cvvX9AIA6o4KpATR98c9mud4XtDWVvsEuyia5U4tVSTKygawyJkjm60w==}
+ cpu: [x64]
+ os: [win32]
+
+ '@sinclair/typebox@0.27.10':
+ resolution: {integrity: sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==}
+
+ '@types/estree@1.0.8':
+ resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
+
+ '@types/node@20.19.37':
+ resolution: {integrity: sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==}
+
+ '@vitest/expect@1.6.1':
+ resolution: {integrity: sha512-jXL+9+ZNIJKruofqXuuTClf44eSpcHlgj3CiuNihUF3Ioujtmc0zIa3UJOW5RjDK1YLBJZnWBlPuqhYycLioog==}
+
+ '@vitest/runner@1.6.1':
+ resolution: {integrity: sha512-3nSnYXkVkf3mXFfE7vVyPmi3Sazhb/2cfZGGs0JRzFsPFvAMBEcrweV1V1GsrstdXeKCTXlJbvnQwGWgEIHmOA==}
+
+ '@vitest/snapshot@1.6.1':
+ resolution: {integrity: sha512-WvidQuWAzU2p95u8GAKlRMqMyN1yOJkGHnx3M1PL9Raf7AQ1kwLKg04ADlCa3+OXUZE7BceOhVZiuWAbzCKcUQ==}
+
+ '@vitest/spy@1.6.1':
+ resolution: {integrity: sha512-MGcMmpGkZebsMZhbQKkAf9CX5zGvjkBTqf8Zx3ApYWXr3wG+QvEu2eXWfnIIWYSJExIp4V9FCKDEeygzkYrXMw==}
+
+ '@vitest/utils@1.6.1':
+ resolution: {integrity: sha512-jOrrUvXM4Av9ZWiG1EajNto0u96kWAhJ1LmPmJhXXQx/32MecEKd10pOLYgS2BQx1TgkGhloPU1ArDW2vvaY6g==}
+
+ acorn-walk@8.3.5:
+ resolution: {integrity: sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==}
+ engines: {node: '>=0.4.0'}
+
+ acorn@8.16.0:
+ resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==}
+ engines: {node: '>=0.4.0'}
+ hasBin: true
+
+ ansi-align@3.0.1:
+ resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==}
+
+ ansi-regex@5.0.1:
+ resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
+ engines: {node: '>=8'}
+
+ ansi-regex@6.2.2:
+ resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==}
+ engines: {node: '>=12'}
+
+ ansi-styles@5.2.0:
+ resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==}
+ engines: {node: '>=10'}
+
+ ansi-styles@6.2.3:
+ resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==}
+ engines: {node: '>=12'}
+
+ any-promise@1.3.0:
+ resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
+
+ assertion-error@1.1.0:
+ resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==}
+
+ boxen@7.1.1:
+ resolution: {integrity: sha512-2hCgjEmP8YLWQ130n2FerGv7rYpfBmnmp9Uy2Le1vge6X3gZIfSmEzP5QTDElFxcvVcXlEn8Aq6MU/PZygIOog==}
+ engines: {node: '>=14.16'}
+
+ bundle-require@5.1.0:
+ resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==}
+ engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+ peerDependencies:
+ esbuild: '>=0.18'
+
+ cac@6.7.14:
+ resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
+ engines: {node: '>=8'}
+
+ camelcase@7.0.1:
+ resolution: {integrity: sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==}
+ engines: {node: '>=14.16'}
+
+ chai@4.5.0:
+ resolution: {integrity: sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==}
+ engines: {node: '>=4'}
+
+ chalk@5.6.2:
+ resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==}
+ engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
+
+ check-error@1.0.3:
+ resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==}
+
+ chokidar@4.0.3:
+ resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
+ engines: {node: '>= 14.16.0'}
+
+ cli-boxes@3.0.0:
+ resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==}
+ engines: {node: '>=10'}
+
+ cli-cursor@5.0.0:
+ resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==}
+ engines: {node: '>=18'}
+
+ cli-spinners@2.9.2:
+ resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==}
+ engines: {node: '>=6'}
+
+ cli-table3@0.6.5:
+ resolution: {integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==}
+ engines: {node: 10.* || >= 12.*}
+
+ commander@12.1.0:
+ resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==}
+ engines: {node: '>=18'}
+
+ commander@4.1.1:
+ resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
+ engines: {node: '>= 6'}
+
+ confbox@0.1.8:
+ resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==}
+
+ consola@3.4.2:
+ resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==}
+ engines: {node: ^14.18.0 || >=16.10.0}
+
+ cross-spawn@7.0.6:
+ resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
+ engines: {node: '>= 8'}
+
+ debug@4.4.3:
+ resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
+ engines: {node: '>=6.0'}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+
+ deep-eql@4.1.4:
+ resolution: {integrity: sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==}
+ engines: {node: '>=6'}
+
+ diff-sequences@29.6.3:
+ resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ eastasianwidth@0.2.0:
+ resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
+
+ emoji-regex@10.6.0:
+ resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==}
+
+ emoji-regex@8.0.0:
+ resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
+
+ emoji-regex@9.2.2:
+ resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
+
+ esbuild@0.21.5:
+ resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==}
+ engines: {node: '>=12'}
+ hasBin: true
+
+ esbuild@0.27.4:
+ resolution: {integrity: sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==}
+ engines: {node: '>=18'}
+ hasBin: true
+
+ estree-walker@3.0.3:
+ resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
+
+ execa@8.0.1:
+ resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==}
+ engines: {node: '>=16.17'}
+
+ fdir@6.5.0:
+ resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
+ engines: {node: '>=12.0.0'}
+ peerDependencies:
+ picomatch: ^3 || ^4
+ peerDependenciesMeta:
+ picomatch:
+ optional: true
+
+ fix-dts-default-cjs-exports@1.0.1:
+ resolution: {integrity: sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==}
+
+ fsevents@2.3.3:
+ resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+ os: [darwin]
+
+ get-east-asian-width@1.5.0:
+ resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==}
+ engines: {node: '>=18'}
+
+ get-func-name@2.0.2:
+ resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==}
+
+ get-stream@8.0.1:
+ resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==}
+ engines: {node: '>=16'}
+
+ get-tsconfig@4.13.7:
+ resolution: {integrity: sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==}
+
+ human-signals@5.0.0:
+ resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==}
+ engines: {node: '>=16.17.0'}
+
+ is-fullwidth-code-point@3.0.0:
+ resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
+ engines: {node: '>=8'}
+
+ is-interactive@2.0.0:
+ resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==}
+ engines: {node: '>=12'}
+
+ is-stream@3.0.0:
+ resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==}
+ engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+
+ is-unicode-supported@1.3.0:
+ resolution: {integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==}
+ engines: {node: '>=12'}
+
+ is-unicode-supported@2.1.0:
+ resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==}
+ engines: {node: '>=18'}
+
+ isexe@2.0.0:
+ resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
+
+ joycon@3.1.1:
+ resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==}
+ engines: {node: '>=10'}
+
+ js-tokens@9.0.1:
+ resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==}
+
+ lilconfig@3.1.3:
+ resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==}
+ engines: {node: '>=14'}
+
+ lines-and-columns@1.2.4:
+ resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
+
+ load-tsconfig@0.2.5:
+ resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==}
+ engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+
+ local-pkg@0.5.1:
+ resolution: {integrity: sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==}
+ engines: {node: '>=14'}
+
+ log-symbols@6.0.0:
+ resolution: {integrity: sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==}
+ engines: {node: '>=18'}
+
+ loupe@2.3.7:
+ resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==}
+
+ magic-string@0.30.21:
+ resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
+
+ merge-stream@2.0.0:
+ resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
+
+ mimic-fn@4.0.0:
+ resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==}
+ engines: {node: '>=12'}
+
+ mimic-function@5.0.1:
+ resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==}
+ engines: {node: '>=18'}
+
+ mlly@1.8.2:
+ resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==}
+
+ ms@2.1.3:
+ resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
+
+ mz@2.7.0:
+ resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
+
+ nanoid@3.3.11:
+ resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
+ engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+ hasBin: true
+
+ npm-run-path@5.3.0:
+ resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==}
+ engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+
+ object-assign@4.1.1:
+ resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
+ engines: {node: '>=0.10.0'}
+
+ onetime@6.0.0:
+ resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==}
+ engines: {node: '>=12'}
+
+ onetime@7.0.0:
+ resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==}
+ engines: {node: '>=18'}
+
+ ora@8.2.0:
+ resolution: {integrity: sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==}
+ engines: {node: '>=18'}
+
+ p-limit@5.0.0:
+ resolution: {integrity: sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==}
+ engines: {node: '>=18'}
+
+ path-key@3.1.1:
+ resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
+ engines: {node: '>=8'}
+
+ path-key@4.0.0:
+ resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==}
+ engines: {node: '>=12'}
+
+ pathe@1.1.2:
+ resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==}
+
+ pathe@2.0.3:
+ resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
+
+ pathval@1.1.1:
+ resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==}
+
+ picocolors@1.1.1:
+ resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
+
+ picomatch@4.0.4:
+ resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==}
+ engines: {node: '>=12'}
+
+ pirates@4.0.7:
+ resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==}
+ engines: {node: '>= 6'}
+
+ pkg-types@1.3.1:
+ resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==}
+
+ postcss-load-config@6.0.1:
+ resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==}
+ engines: {node: '>= 18'}
+ peerDependencies:
+ jiti: '>=1.21.0'
+ postcss: '>=8.0.9'
+ tsx: ^4.8.1
+ yaml: ^2.4.2
+ peerDependenciesMeta:
+ jiti:
+ optional: true
+ postcss:
+ optional: true
+ tsx:
+ optional: true
+ yaml:
+ optional: true
+
+ postcss@8.5.8:
+ resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==}
+ engines: {node: ^10 || ^12 || >=14}
+
+ pretty-format@29.7.0:
+ resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ react-is@18.3.1:
+ resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==}
+
+ readdirp@4.1.2:
+ resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
+ engines: {node: '>= 14.18.0'}
+
+ resolve-from@5.0.0:
+ resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==}
+ engines: {node: '>=8'}
+
+ resolve-pkg-maps@1.0.0:
+ resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
+
+ restore-cursor@5.1.0:
+ resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==}
+ engines: {node: '>=18'}
+
+ rollup@4.60.0:
+ resolution: {integrity: sha512-yqjxruMGBQJ2gG4HtjZtAfXArHomazDHoFwFFmZZl0r7Pdo7qCIXKqKHZc8yeoMgzJJ+pO6pEEHa+V7uzWlrAQ==}
+ engines: {node: '>=18.0.0', npm: '>=8.0.0'}
+ hasBin: true
+
+ shebang-command@2.0.0:
+ resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
+ engines: {node: '>=8'}
+
+ shebang-regex@3.0.0:
+ resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
+ engines: {node: '>=8'}
+
+ siginfo@2.0.0:
+ resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
+
+ signal-exit@4.1.0:
+ resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
+ engines: {node: '>=14'}
+
+ source-map-js@1.2.1:
+ resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
+ engines: {node: '>=0.10.0'}
+
+ source-map@0.7.6:
+ resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==}
+ engines: {node: '>= 12'}
+
+ stackback@0.0.2:
+ resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
+
+ std-env@3.10.0:
+ resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==}
+
+ stdin-discarder@0.2.2:
+ resolution: {integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==}
+ engines: {node: '>=18'}
+
+ string-width@4.2.3:
+ resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
+ engines: {node: '>=8'}
+
+ string-width@5.1.2:
+ resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
+ engines: {node: '>=12'}
+
+ string-width@7.2.0:
+ resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==}
+ engines: {node: '>=18'}
+
+ strip-ansi@6.0.1:
+ resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
+ engines: {node: '>=8'}
+
+ strip-ansi@7.2.0:
+ resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==}
+ engines: {node: '>=12'}
+
+ strip-final-newline@3.0.0:
+ resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==}
+ engines: {node: '>=12'}
+
+ strip-literal@2.1.1:
+ resolution: {integrity: sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==}
+
+ sucrase@3.35.1:
+ resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==}
+ engines: {node: '>=16 || 14 >=14.17'}
+ hasBin: true
+
+ thenify-all@1.6.0:
+ resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==}
+ engines: {node: '>=0.8'}
+
+ thenify@3.3.1:
+ resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
+
+ tinybench@2.9.0:
+ resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
+
+ tinyexec@0.3.2:
+ resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==}
+
+ tinyglobby@0.2.15:
+ resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
+ engines: {node: '>=12.0.0'}
+
+ tinypool@0.8.4:
+ resolution: {integrity: sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==}
+ engines: {node: '>=14.0.0'}
+
+ tinyspy@2.2.1:
+ resolution: {integrity: sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==}
+ engines: {node: '>=14.0.0'}
+
+ tree-kill@1.2.2:
+ resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==}
+ hasBin: true
+
+ ts-interface-checker@0.1.13:
+ resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
+
+ tsup@8.5.1:
+ resolution: {integrity: sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing==}
+ engines: {node: '>=18'}
+ hasBin: true
+ peerDependencies:
+ '@microsoft/api-extractor': ^7.36.0
+ '@swc/core': ^1
+ postcss: ^8.4.12
+ typescript: '>=4.5.0'
+ peerDependenciesMeta:
+ '@microsoft/api-extractor':
+ optional: true
+ '@swc/core':
+ optional: true
+ postcss:
+ optional: true
+ typescript:
+ optional: true
+
+ tsx@4.21.0:
+ resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==}
+ engines: {node: '>=18.0.0'}
+ hasBin: true
+
+ type-detect@4.1.0:
+ resolution: {integrity: sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==}
+ engines: {node: '>=4'}
+
+ type-fest@2.19.0:
+ resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==}
+ engines: {node: '>=12.20'}
+
+ typescript@5.9.3:
+ resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
+ engines: {node: '>=14.17'}
+ hasBin: true
+
+ ufo@1.6.3:
+ resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==}
+
+ undici-types@6.21.0:
+ resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
+
+ vite-node@1.6.1:
+ resolution: {integrity: sha512-YAXkfvGtuTzwWbDSACdJSg4A4DZiAqckWe90Zapc/sEX3XvHcw1NdurM/6od8J207tSDqNbSsgdCacBgvJKFuA==}
+ engines: {node: ^18.0.0 || >=20.0.0}
+ hasBin: true
+
+ vite@5.4.21:
+ resolution: {integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==}
+ engines: {node: ^18.0.0 || >=20.0.0}
+ hasBin: true
+ peerDependencies:
+ '@types/node': ^18.0.0 || >=20.0.0
+ less: '*'
+ lightningcss: ^1.21.0
+ sass: '*'
+ sass-embedded: '*'
+ stylus: '*'
+ sugarss: '*'
+ terser: ^5.4.0
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+ less:
+ optional: true
+ lightningcss:
+ optional: true
+ sass:
+ optional: true
+ sass-embedded:
+ optional: true
+ stylus:
+ optional: true
+ sugarss:
+ optional: true
+ terser:
+ optional: true
+
+ vitest@1.6.1:
+ resolution: {integrity: sha512-Ljb1cnSJSivGN0LqXd/zmDbWEM0RNNg2t1QW/XUhYl/qPqyu7CsqeWtqQXHVaJsecLPuDoak2oJcZN2QoRIOag==}
+ engines: {node: ^18.0.0 || >=20.0.0}
+ hasBin: true
+ peerDependencies:
+ '@edge-runtime/vm': '*'
+ '@types/node': ^18.0.0 || >=20.0.0
+ '@vitest/browser': 1.6.1
+ '@vitest/ui': 1.6.1
+ happy-dom: '*'
+ jsdom: '*'
+ peerDependenciesMeta:
+ '@edge-runtime/vm':
+ optional: true
+ '@types/node':
+ optional: true
+ '@vitest/browser':
+ optional: true
+ '@vitest/ui':
+ optional: true
+ happy-dom:
+ optional: true
+ jsdom:
+ optional: true
+
+ which@2.0.2:
+ resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
+ engines: {node: '>= 8'}
+ hasBin: true
+
+ why-is-node-running@2.3.0:
+ resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==}
+ engines: {node: '>=8'}
+ hasBin: true
+
+ widest-line@4.0.1:
+ resolution: {integrity: sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==}
+ engines: {node: '>=12'}
+
+ wrap-ansi@8.1.0:
+ resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
+ engines: {node: '>=12'}
+
+ yocto-queue@1.2.2:
+ resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==}
+ engines: {node: '>=12.20'}
+
+snapshots:
+
+ '@biomejs/biome@1.9.4':
+ optionalDependencies:
+ '@biomejs/cli-darwin-arm64': 1.9.4
+ '@biomejs/cli-darwin-x64': 1.9.4
+ '@biomejs/cli-linux-arm64': 1.9.4
+ '@biomejs/cli-linux-arm64-musl': 1.9.4
+ '@biomejs/cli-linux-x64': 1.9.4
+ '@biomejs/cli-linux-x64-musl': 1.9.4
+ '@biomejs/cli-win32-arm64': 1.9.4
+ '@biomejs/cli-win32-x64': 1.9.4
+
+ '@biomejs/cli-darwin-arm64@1.9.4':
+ optional: true
+
+ '@biomejs/cli-darwin-x64@1.9.4':
+ optional: true
+
+ '@biomejs/cli-linux-arm64-musl@1.9.4':
+ optional: true
+
+ '@biomejs/cli-linux-arm64@1.9.4':
+ optional: true
+
+ '@biomejs/cli-linux-x64-musl@1.9.4':
+ optional: true
+
+ '@biomejs/cli-linux-x64@1.9.4':
+ optional: true
+
+ '@biomejs/cli-win32-arm64@1.9.4':
+ optional: true
+
+ '@biomejs/cli-win32-x64@1.9.4':
+ optional: true
+
+ '@colors/colors@1.5.0':
+ optional: true
+
+ '@esbuild/aix-ppc64@0.21.5':
+ optional: true
+
+ '@esbuild/aix-ppc64@0.27.4':
+ optional: true
+
+ '@esbuild/android-arm64@0.21.5':
+ optional: true
+
+ '@esbuild/android-arm64@0.27.4':
+ optional: true
+
+ '@esbuild/android-arm@0.21.5':
+ optional: true
+
+ '@esbuild/android-arm@0.27.4':
+ optional: true
+
+ '@esbuild/android-x64@0.21.5':
+ optional: true
+
+ '@esbuild/android-x64@0.27.4':
+ optional: true
+
+ '@esbuild/darwin-arm64@0.21.5':
+ optional: true
+
+ '@esbuild/darwin-arm64@0.27.4':
+ optional: true
+
+ '@esbuild/darwin-x64@0.21.5':
+ optional: true
+
+ '@esbuild/darwin-x64@0.27.4':
+ optional: true
+
+ '@esbuild/freebsd-arm64@0.21.5':
+ optional: true
+
+ '@esbuild/freebsd-arm64@0.27.4':
+ optional: true
+
+ '@esbuild/freebsd-x64@0.21.5':
+ optional: true
+
+ '@esbuild/freebsd-x64@0.27.4':
+ optional: true
+
+ '@esbuild/linux-arm64@0.21.5':
+ optional: true
+
+ '@esbuild/linux-arm64@0.27.4':
+ optional: true
+
+ '@esbuild/linux-arm@0.21.5':
+ optional: true
+
+ '@esbuild/linux-arm@0.27.4':
+ optional: true
+
+ '@esbuild/linux-ia32@0.21.5':
+ optional: true
+
+ '@esbuild/linux-ia32@0.27.4':
+ optional: true
+
+ '@esbuild/linux-loong64@0.21.5':
+ optional: true
+
+ '@esbuild/linux-loong64@0.27.4':
+ optional: true
+
+ '@esbuild/linux-mips64el@0.21.5':
+ optional: true
+
+ '@esbuild/linux-mips64el@0.27.4':
+ optional: true
+
+ '@esbuild/linux-ppc64@0.21.5':
+ optional: true
+
+ '@esbuild/linux-ppc64@0.27.4':
+ optional: true
+
+ '@esbuild/linux-riscv64@0.21.5':
+ optional: true
+
+ '@esbuild/linux-riscv64@0.27.4':
+ optional: true
+
+ '@esbuild/linux-s390x@0.21.5':
+ optional: true
+
+ '@esbuild/linux-s390x@0.27.4':
+ optional: true
+
+ '@esbuild/linux-x64@0.21.5':
+ optional: true
+
+ '@esbuild/linux-x64@0.27.4':
+ optional: true
+
+ '@esbuild/netbsd-arm64@0.27.4':
+ optional: true
+
+ '@esbuild/netbsd-x64@0.21.5':
+ optional: true
+
+ '@esbuild/netbsd-x64@0.27.4':
+ optional: true
+
+ '@esbuild/openbsd-arm64@0.27.4':
+ optional: true
+
+ '@esbuild/openbsd-x64@0.21.5':
+ optional: true
+
+ '@esbuild/openbsd-x64@0.27.4':
+ optional: true
+
+ '@esbuild/openharmony-arm64@0.27.4':
+ optional: true
+
+ '@esbuild/sunos-x64@0.21.5':
+ optional: true
+
+ '@esbuild/sunos-x64@0.27.4':
+ optional: true
+
+ '@esbuild/win32-arm64@0.21.5':
+ optional: true
+
+ '@esbuild/win32-arm64@0.27.4':
+ optional: true
+
+ '@esbuild/win32-ia32@0.21.5':
+ optional: true
+
+ '@esbuild/win32-ia32@0.27.4':
+ optional: true
+
+ '@esbuild/win32-x64@0.21.5':
+ optional: true
+
+ '@esbuild/win32-x64@0.27.4':
+ optional: true
+
+ '@jest/schemas@29.6.3':
+ dependencies:
+ '@sinclair/typebox': 0.27.10
+
+ '@jridgewell/gen-mapping@0.3.13':
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.5.5
+ '@jridgewell/trace-mapping': 0.3.31
+
+ '@jridgewell/resolve-uri@3.1.2': {}
+
+ '@jridgewell/sourcemap-codec@1.5.5': {}
+
+ '@jridgewell/trace-mapping@0.3.31':
+ dependencies:
+ '@jridgewell/resolve-uri': 3.1.2
+ '@jridgewell/sourcemap-codec': 1.5.5
+
+ '@rollup/rollup-android-arm-eabi@4.60.0':
+ optional: true
+
+ '@rollup/rollup-android-arm64@4.60.0':
+ optional: true
+
+ '@rollup/rollup-darwin-arm64@4.60.0':
+ optional: true
+
+ '@rollup/rollup-darwin-x64@4.60.0':
+ optional: true
+
+ '@rollup/rollup-freebsd-arm64@4.60.0':
+ optional: true
+
+ '@rollup/rollup-freebsd-x64@4.60.0':
+ optional: true
+
+ '@rollup/rollup-linux-arm-gnueabihf@4.60.0':
+ optional: true
+
+ '@rollup/rollup-linux-arm-musleabihf@4.60.0':
+ optional: true
+
+ '@rollup/rollup-linux-arm64-gnu@4.60.0':
+ optional: true
+
+ '@rollup/rollup-linux-arm64-musl@4.60.0':
+ optional: true
+
+ '@rollup/rollup-linux-loong64-gnu@4.60.0':
+ optional: true
+
+ '@rollup/rollup-linux-loong64-musl@4.60.0':
+ optional: true
+
+ '@rollup/rollup-linux-ppc64-gnu@4.60.0':
+ optional: true
+
+ '@rollup/rollup-linux-ppc64-musl@4.60.0':
+ optional: true
+
+ '@rollup/rollup-linux-riscv64-gnu@4.60.0':
+ optional: true
+
+ '@rollup/rollup-linux-riscv64-musl@4.60.0':
+ optional: true
+
+ '@rollup/rollup-linux-s390x-gnu@4.60.0':
+ optional: true
+
+ '@rollup/rollup-linux-x64-gnu@4.60.0':
+ optional: true
+
+ '@rollup/rollup-linux-x64-musl@4.60.0':
+ optional: true
+
+ '@rollup/rollup-openbsd-x64@4.60.0':
+ optional: true
+
+ '@rollup/rollup-openharmony-arm64@4.60.0':
+ optional: true
+
+ '@rollup/rollup-win32-arm64-msvc@4.60.0':
+ optional: true
+
+ '@rollup/rollup-win32-ia32-msvc@4.60.0':
+ optional: true
+
+ '@rollup/rollup-win32-x64-gnu@4.60.0':
+ optional: true
+
+ '@rollup/rollup-win32-x64-msvc@4.60.0':
+ optional: true
+
+ '@sinclair/typebox@0.27.10': {}
+
+ '@types/estree@1.0.8': {}
+
+ '@types/node@20.19.37':
+ dependencies:
+ undici-types: 6.21.0
+
+ '@vitest/expect@1.6.1':
+ dependencies:
+ '@vitest/spy': 1.6.1
+ '@vitest/utils': 1.6.1
+ chai: 4.5.0
+
+ '@vitest/runner@1.6.1':
+ dependencies:
+ '@vitest/utils': 1.6.1
+ p-limit: 5.0.0
+ pathe: 1.1.2
+
+ '@vitest/snapshot@1.6.1':
+ dependencies:
+ magic-string: 0.30.21
+ pathe: 1.1.2
+ pretty-format: 29.7.0
+
+ '@vitest/spy@1.6.1':
+ dependencies:
+ tinyspy: 2.2.1
+
+ '@vitest/utils@1.6.1':
+ dependencies:
+ diff-sequences: 29.6.3
+ estree-walker: 3.0.3
+ loupe: 2.3.7
+ pretty-format: 29.7.0
+
+ acorn-walk@8.3.5:
+ dependencies:
+ acorn: 8.16.0
+
+ acorn@8.16.0: {}
+
+ ansi-align@3.0.1:
+ dependencies:
+ string-width: 4.2.3
+
+ ansi-regex@5.0.1: {}
+
+ ansi-regex@6.2.2: {}
+
+ ansi-styles@5.2.0: {}
+
+ ansi-styles@6.2.3: {}
+
+ any-promise@1.3.0: {}
+
+ assertion-error@1.1.0: {}
+
+ boxen@7.1.1:
+ dependencies:
+ ansi-align: 3.0.1
+ camelcase: 7.0.1
+ chalk: 5.6.2
+ cli-boxes: 3.0.0
+ string-width: 5.1.2
+ type-fest: 2.19.0
+ widest-line: 4.0.1
+ wrap-ansi: 8.1.0
+
+ bundle-require@5.1.0(esbuild@0.27.4):
+ dependencies:
+ esbuild: 0.27.4
+ load-tsconfig: 0.2.5
+
+ cac@6.7.14: {}
+
+ camelcase@7.0.1: {}
+
+ chai@4.5.0:
+ dependencies:
+ assertion-error: 1.1.0
+ check-error: 1.0.3
+ deep-eql: 4.1.4
+ get-func-name: 2.0.2
+ loupe: 2.3.7
+ pathval: 1.1.1
+ type-detect: 4.1.0
+
+ chalk@5.6.2: {}
+
+ check-error@1.0.3:
+ dependencies:
+ get-func-name: 2.0.2
+
+ chokidar@4.0.3:
+ dependencies:
+ readdirp: 4.1.2
+
+ cli-boxes@3.0.0: {}
+
+ cli-cursor@5.0.0:
+ dependencies:
+ restore-cursor: 5.1.0
+
+ cli-spinners@2.9.2: {}
+
+ cli-table3@0.6.5:
+ dependencies:
+ string-width: 4.2.3
+ optionalDependencies:
+ '@colors/colors': 1.5.0
+
+ commander@12.1.0: {}
+
+ commander@4.1.1: {}
+
+ confbox@0.1.8: {}
+
+ consola@3.4.2: {}
+
+ cross-spawn@7.0.6:
+ dependencies:
+ path-key: 3.1.1
+ shebang-command: 2.0.0
+ which: 2.0.2
+
+ debug@4.4.3:
+ dependencies:
+ ms: 2.1.3
+
+ deep-eql@4.1.4:
+ dependencies:
+ type-detect: 4.1.0
+
+ diff-sequences@29.6.3: {}
+
+ eastasianwidth@0.2.0: {}
+
+ emoji-regex@10.6.0: {}
+
+ emoji-regex@8.0.0: {}
+
+ emoji-regex@9.2.2: {}
+
+ esbuild@0.21.5:
+ optionalDependencies:
+ '@esbuild/aix-ppc64': 0.21.5
+ '@esbuild/android-arm': 0.21.5
+ '@esbuild/android-arm64': 0.21.5
+ '@esbuild/android-x64': 0.21.5
+ '@esbuild/darwin-arm64': 0.21.5
+ '@esbuild/darwin-x64': 0.21.5
+ '@esbuild/freebsd-arm64': 0.21.5
+ '@esbuild/freebsd-x64': 0.21.5
+ '@esbuild/linux-arm': 0.21.5
+ '@esbuild/linux-arm64': 0.21.5
+ '@esbuild/linux-ia32': 0.21.5
+ '@esbuild/linux-loong64': 0.21.5
+ '@esbuild/linux-mips64el': 0.21.5
+ '@esbuild/linux-ppc64': 0.21.5
+ '@esbuild/linux-riscv64': 0.21.5
+ '@esbuild/linux-s390x': 0.21.5
+ '@esbuild/linux-x64': 0.21.5
+ '@esbuild/netbsd-x64': 0.21.5
+ '@esbuild/openbsd-x64': 0.21.5
+ '@esbuild/sunos-x64': 0.21.5
+ '@esbuild/win32-arm64': 0.21.5
+ '@esbuild/win32-ia32': 0.21.5
+ '@esbuild/win32-x64': 0.21.5
+
+ esbuild@0.27.4:
+ optionalDependencies:
+ '@esbuild/aix-ppc64': 0.27.4
+ '@esbuild/android-arm': 0.27.4
+ '@esbuild/android-arm64': 0.27.4
+ '@esbuild/android-x64': 0.27.4
+ '@esbuild/darwin-arm64': 0.27.4
+ '@esbuild/darwin-x64': 0.27.4
+ '@esbuild/freebsd-arm64': 0.27.4
+ '@esbuild/freebsd-x64': 0.27.4
+ '@esbuild/linux-arm': 0.27.4
+ '@esbuild/linux-arm64': 0.27.4
+ '@esbuild/linux-ia32': 0.27.4
+ '@esbuild/linux-loong64': 0.27.4
+ '@esbuild/linux-mips64el': 0.27.4
+ '@esbuild/linux-ppc64': 0.27.4
+ '@esbuild/linux-riscv64': 0.27.4
+ '@esbuild/linux-s390x': 0.27.4
+ '@esbuild/linux-x64': 0.27.4
+ '@esbuild/netbsd-arm64': 0.27.4
+ '@esbuild/netbsd-x64': 0.27.4
+ '@esbuild/openbsd-arm64': 0.27.4
+ '@esbuild/openbsd-x64': 0.27.4
+ '@esbuild/openharmony-arm64': 0.27.4
+ '@esbuild/sunos-x64': 0.27.4
+ '@esbuild/win32-arm64': 0.27.4
+ '@esbuild/win32-ia32': 0.27.4
+ '@esbuild/win32-x64': 0.27.4
+
+ estree-walker@3.0.3:
+ dependencies:
+ '@types/estree': 1.0.8
+
+ execa@8.0.1:
+ dependencies:
+ cross-spawn: 7.0.6
+ get-stream: 8.0.1
+ human-signals: 5.0.0
+ is-stream: 3.0.0
+ merge-stream: 2.0.0
+ npm-run-path: 5.3.0
+ onetime: 6.0.0
+ signal-exit: 4.1.0
+ strip-final-newline: 3.0.0
+
+ fdir@6.5.0(picomatch@4.0.4):
+ optionalDependencies:
+ picomatch: 4.0.4
+
+ fix-dts-default-cjs-exports@1.0.1:
+ dependencies:
+ magic-string: 0.30.21
+ mlly: 1.8.2
+ rollup: 4.60.0
+
+ fsevents@2.3.3:
+ optional: true
+
+ get-east-asian-width@1.5.0: {}
+
+ get-func-name@2.0.2: {}
+
+ get-stream@8.0.1: {}
+
+ get-tsconfig@4.13.7:
+ dependencies:
+ resolve-pkg-maps: 1.0.0
+
+ human-signals@5.0.0: {}
+
+ is-fullwidth-code-point@3.0.0: {}
+
+ is-interactive@2.0.0: {}
+
+ is-stream@3.0.0: {}
+
+ is-unicode-supported@1.3.0: {}
+
+ is-unicode-supported@2.1.0: {}
+
+ isexe@2.0.0: {}
+
+ joycon@3.1.1: {}
+
+ js-tokens@9.0.1: {}
+
+ lilconfig@3.1.3: {}
+
+ lines-and-columns@1.2.4: {}
+
+ load-tsconfig@0.2.5: {}
+
+ local-pkg@0.5.1:
+ dependencies:
+ mlly: 1.8.2
+ pkg-types: 1.3.1
+
+ log-symbols@6.0.0:
+ dependencies:
+ chalk: 5.6.2
+ is-unicode-supported: 1.3.0
+
+ loupe@2.3.7:
+ dependencies:
+ get-func-name: 2.0.2
+
+ magic-string@0.30.21:
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.5.5
+
+ merge-stream@2.0.0: {}
+
+ mimic-fn@4.0.0: {}
+
+ mimic-function@5.0.1: {}
+
+ mlly@1.8.2:
+ dependencies:
+ acorn: 8.16.0
+ pathe: 2.0.3
+ pkg-types: 1.3.1
+ ufo: 1.6.3
+
+ ms@2.1.3: {}
+
+ mz@2.7.0:
+ dependencies:
+ any-promise: 1.3.0
+ object-assign: 4.1.1
+ thenify-all: 1.6.0
+
+ nanoid@3.3.11: {}
+
+ npm-run-path@5.3.0:
+ dependencies:
+ path-key: 4.0.0
+
+ object-assign@4.1.1: {}
+
+ onetime@6.0.0:
+ dependencies:
+ mimic-fn: 4.0.0
+
+ onetime@7.0.0:
+ dependencies:
+ mimic-function: 5.0.1
+
+ ora@8.2.0:
+ dependencies:
+ chalk: 5.6.2
+ cli-cursor: 5.0.0
+ cli-spinners: 2.9.2
+ is-interactive: 2.0.0
+ is-unicode-supported: 2.1.0
+ log-symbols: 6.0.0
+ stdin-discarder: 0.2.2
+ string-width: 7.2.0
+ strip-ansi: 7.2.0
+
+ p-limit@5.0.0:
+ dependencies:
+ yocto-queue: 1.2.2
+
+ path-key@3.1.1: {}
+
+ path-key@4.0.0: {}
+
+ pathe@1.1.2: {}
+
+ pathe@2.0.3: {}
+
+ pathval@1.1.1: {}
+
+ picocolors@1.1.1: {}
+
+ picomatch@4.0.4: {}
+
+ pirates@4.0.7: {}
+
+ pkg-types@1.3.1:
+ dependencies:
+ confbox: 0.1.8
+ mlly: 1.8.2
+ pathe: 2.0.3
+
+ postcss-load-config@6.0.1(postcss@8.5.8)(tsx@4.21.0):
+ dependencies:
+ lilconfig: 3.1.3
+ optionalDependencies:
+ postcss: 8.5.8
+ tsx: 4.21.0
+
+ postcss@8.5.8:
+ dependencies:
+ nanoid: 3.3.11
+ picocolors: 1.1.1
+ source-map-js: 1.2.1
+
+ pretty-format@29.7.0:
+ dependencies:
+ '@jest/schemas': 29.6.3
+ ansi-styles: 5.2.0
+ react-is: 18.3.1
+
+ react-is@18.3.1: {}
+
+ readdirp@4.1.2: {}
+
+ resolve-from@5.0.0: {}
+
+ resolve-pkg-maps@1.0.0: {}
+
+ restore-cursor@5.1.0:
+ dependencies:
+ onetime: 7.0.0
+ signal-exit: 4.1.0
+
+ rollup@4.60.0:
+ dependencies:
+ '@types/estree': 1.0.8
+ optionalDependencies:
+ '@rollup/rollup-android-arm-eabi': 4.60.0
+ '@rollup/rollup-android-arm64': 4.60.0
+ '@rollup/rollup-darwin-arm64': 4.60.0
+ '@rollup/rollup-darwin-x64': 4.60.0
+ '@rollup/rollup-freebsd-arm64': 4.60.0
+ '@rollup/rollup-freebsd-x64': 4.60.0
+ '@rollup/rollup-linux-arm-gnueabihf': 4.60.0
+ '@rollup/rollup-linux-arm-musleabihf': 4.60.0
+ '@rollup/rollup-linux-arm64-gnu': 4.60.0
+ '@rollup/rollup-linux-arm64-musl': 4.60.0
+ '@rollup/rollup-linux-loong64-gnu': 4.60.0
+ '@rollup/rollup-linux-loong64-musl': 4.60.0
+ '@rollup/rollup-linux-ppc64-gnu': 4.60.0
+ '@rollup/rollup-linux-ppc64-musl': 4.60.0
+ '@rollup/rollup-linux-riscv64-gnu': 4.60.0
+ '@rollup/rollup-linux-riscv64-musl': 4.60.0
+ '@rollup/rollup-linux-s390x-gnu': 4.60.0
+ '@rollup/rollup-linux-x64-gnu': 4.60.0
+ '@rollup/rollup-linux-x64-musl': 4.60.0
+ '@rollup/rollup-openbsd-x64': 4.60.0
+ '@rollup/rollup-openharmony-arm64': 4.60.0
+ '@rollup/rollup-win32-arm64-msvc': 4.60.0
+ '@rollup/rollup-win32-ia32-msvc': 4.60.0
+ '@rollup/rollup-win32-x64-gnu': 4.60.0
+ '@rollup/rollup-win32-x64-msvc': 4.60.0
+ fsevents: 2.3.3
+
+ shebang-command@2.0.0:
+ dependencies:
+ shebang-regex: 3.0.0
+
+ shebang-regex@3.0.0: {}
+
+ siginfo@2.0.0: {}
+
+ signal-exit@4.1.0: {}
+
+ source-map-js@1.2.1: {}
+
+ source-map@0.7.6: {}
+
+ stackback@0.0.2: {}
+
+ std-env@3.10.0: {}
+
+ stdin-discarder@0.2.2: {}
+
+ string-width@4.2.3:
+ dependencies:
+ emoji-regex: 8.0.0
+ is-fullwidth-code-point: 3.0.0
+ strip-ansi: 6.0.1
+
+ string-width@5.1.2:
+ dependencies:
+ eastasianwidth: 0.2.0
+ emoji-regex: 9.2.2
+ strip-ansi: 7.2.0
+
+ string-width@7.2.0:
+ dependencies:
+ emoji-regex: 10.6.0
+ get-east-asian-width: 1.5.0
+ strip-ansi: 7.2.0
+
+ strip-ansi@6.0.1:
+ dependencies:
+ ansi-regex: 5.0.1
+
+ strip-ansi@7.2.0:
+ dependencies:
+ ansi-regex: 6.2.2
+
+ strip-final-newline@3.0.0: {}
+
+ strip-literal@2.1.1:
+ dependencies:
+ js-tokens: 9.0.1
+
+ sucrase@3.35.1:
+ dependencies:
+ '@jridgewell/gen-mapping': 0.3.13
+ commander: 4.1.1
+ lines-and-columns: 1.2.4
+ mz: 2.7.0
+ pirates: 4.0.7
+ tinyglobby: 0.2.15
+ ts-interface-checker: 0.1.13
+
+ thenify-all@1.6.0:
+ dependencies:
+ thenify: 3.3.1
+
+ thenify@3.3.1:
+ dependencies:
+ any-promise: 1.3.0
+
+ tinybench@2.9.0: {}
+
+ tinyexec@0.3.2: {}
+
+ tinyglobby@0.2.15:
+ dependencies:
+ fdir: 6.5.0(picomatch@4.0.4)
+ picomatch: 4.0.4
+
+ tinypool@0.8.4: {}
+
+ tinyspy@2.2.1: {}
+
+ tree-kill@1.2.2: {}
+
+ ts-interface-checker@0.1.13: {}
+
+ tsup@8.5.1(postcss@8.5.8)(tsx@4.21.0)(typescript@5.9.3):
+ dependencies:
+ bundle-require: 5.1.0(esbuild@0.27.4)
+ cac: 6.7.14
+ chokidar: 4.0.3
+ consola: 3.4.2
+ debug: 4.4.3
+ esbuild: 0.27.4
+ fix-dts-default-cjs-exports: 1.0.1
+ joycon: 3.1.1
+ picocolors: 1.1.1
+ postcss-load-config: 6.0.1(postcss@8.5.8)(tsx@4.21.0)
+ resolve-from: 5.0.0
+ rollup: 4.60.0
+ source-map: 0.7.6
+ sucrase: 3.35.1
+ tinyexec: 0.3.2
+ tinyglobby: 0.2.15
+ tree-kill: 1.2.2
+ optionalDependencies:
+ postcss: 8.5.8
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - jiti
+ - supports-color
+ - tsx
+ - yaml
+
+ tsx@4.21.0:
+ dependencies:
+ esbuild: 0.27.4
+ get-tsconfig: 4.13.7
+ optionalDependencies:
+ fsevents: 2.3.3
+
+ type-detect@4.1.0: {}
+
+ type-fest@2.19.0: {}
+
+ typescript@5.9.3: {}
+
+ ufo@1.6.3: {}
+
+ undici-types@6.21.0: {}
+
+ vite-node@1.6.1(@types/node@20.19.37):
+ dependencies:
+ cac: 6.7.14
+ debug: 4.4.3
+ pathe: 1.1.2
+ picocolors: 1.1.1
+ vite: 5.4.21(@types/node@20.19.37)
+ transitivePeerDependencies:
+ - '@types/node'
+ - less
+ - lightningcss
+ - sass
+ - sass-embedded
+ - stylus
+ - sugarss
+ - supports-color
+ - terser
+
+ vite@5.4.21(@types/node@20.19.37):
+ dependencies:
+ esbuild: 0.21.5
+ postcss: 8.5.8
+ rollup: 4.60.0
+ optionalDependencies:
+ '@types/node': 20.19.37
+ fsevents: 2.3.3
+
+ vitest@1.6.1(@types/node@20.19.37):
+ dependencies:
+ '@vitest/expect': 1.6.1
+ '@vitest/runner': 1.6.1
+ '@vitest/snapshot': 1.6.1
+ '@vitest/spy': 1.6.1
+ '@vitest/utils': 1.6.1
+ acorn-walk: 8.3.5
+ chai: 4.5.0
+ debug: 4.4.3
+ execa: 8.0.1
+ local-pkg: 0.5.1
+ magic-string: 0.30.21
+ pathe: 1.1.2
+ picocolors: 1.1.1
+ std-env: 3.10.0
+ strip-literal: 2.1.1
+ tinybench: 2.9.0
+ tinypool: 0.8.4
+ vite: 5.4.21(@types/node@20.19.37)
+ vite-node: 1.6.1(@types/node@20.19.37)
+ why-is-node-running: 2.3.0
+ optionalDependencies:
+ '@types/node': 20.19.37
+ transitivePeerDependencies:
+ - less
+ - lightningcss
+ - sass
+ - sass-embedded
+ - stylus
+ - sugarss
+ - supports-color
+ - terser
+
+ which@2.0.2:
+ dependencies:
+ isexe: 2.0.0
+
+ why-is-node-running@2.3.0:
+ dependencies:
+ siginfo: 2.0.0
+ stackback: 0.0.2
+
+ widest-line@4.0.1:
+ dependencies:
+ string-width: 5.1.2
+
+ wrap-ansi@8.1.0:
+ dependencies:
+ ansi-styles: 6.2.3
+ string-width: 5.1.2
+ strip-ansi: 7.2.0
+
+ yocto-queue@1.2.2: {}
diff --git a/cli/node/src/backend/base.ts b/cli/node/src/backend/base.ts
new file mode 100644
index 000000000..c0c9ae4f9
--- /dev/null
+++ b/cli/node/src/backend/base.ts
@@ -0,0 +1,130 @@
+/**
+ * Abstract backend interface and factory.
+ */
+
+import type { Mem0Config } from "../config.js";
+import { PlatformBackend } from "./platform.js";
+
+export interface AddOptions {
+ userId?: string;
+ agentId?: string;
+ appId?: string;
+ runId?: string;
+ metadata?: Record;
+ immutable?: boolean;
+ infer?: boolean;
+ expires?: string;
+ categories?: string[];
+ enableGraph?: boolean;
+}
+
+export interface SearchOptions {
+ userId?: string;
+ agentId?: string;
+ appId?: string;
+ runId?: string;
+ topK?: number;
+ threshold?: number;
+ rerank?: boolean;
+ keyword?: boolean;
+ filters?: Record;
+ fields?: string[];
+ enableGraph?: boolean;
+}
+
+export interface ListOptions {
+ userId?: string;
+ agentId?: string;
+ appId?: string;
+ runId?: string;
+ page?: number;
+ pageSize?: number;
+ category?: string;
+ after?: string;
+ before?: string;
+ enableGraph?: boolean;
+}
+
+export interface DeleteOptions {
+ all?: boolean;
+ userId?: string;
+ agentId?: string;
+ appId?: string;
+ runId?: string;
+}
+
+export interface EntityIds {
+ userId?: string;
+ agentId?: string;
+ appId?: string;
+ runId?: string;
+}
+
+export interface Backend {
+ add(
+ content?: string,
+ messages?: Record[],
+ opts?: AddOptions,
+ ): Promise>;
+
+ search(
+ query: string,
+ opts?: SearchOptions,
+ ): Promise[]>;
+
+ get(memoryId: string): Promise>;
+
+ listMemories(opts?: ListOptions): Promise[]>;
+
+ update(
+ memoryId: string,
+ content?: string,
+ metadata?: Record,
+ ): Promise>;
+
+ delete(
+ memoryId?: string,
+ opts?: DeleteOptions,
+ ): Promise>;
+
+ deleteEntities(opts: EntityIds): Promise>;
+
+ ping(): Promise>;
+
+ status(opts?: { userId?: string; agentId?: string }): Promise<
+ Record
+ >;
+
+ entities(entityType: string): Promise[]>;
+
+ listEvents(): Promise[]>;
+
+ getEvent(eventId: string): Promise>;
+}
+
+export class AuthError extends Error {
+ constructor(
+ message = "Authentication failed. Your API key may be invalid or expired.",
+ ) {
+ super(message);
+ this.name = "AuthError";
+ }
+}
+
+export class NotFoundError extends Error {
+ constructor(path: string) {
+ super(`Resource not found: ${path}`);
+ this.name = "NotFoundError";
+ }
+}
+
+export class APIError extends Error {
+ constructor(path: string, detail: string) {
+ super(`Bad request to ${path}: ${detail}`);
+ this.name = "APIError";
+ }
+}
+
+export function getBackend(config: Mem0Config): Backend {
+ return new PlatformBackend(config.platform);
+}
diff --git a/cli/node/src/backend/index.ts b/cli/node/src/backend/index.ts
new file mode 100644
index 000000000..d22e20b95
--- /dev/null
+++ b/cli/node/src/backend/index.ts
@@ -0,0 +1,14 @@
+/**
+ * Backend factory re-export.
+ */
+
+export { getBackend } from "./base.js";
+export type {
+ Backend,
+ AddOptions,
+ SearchOptions,
+ ListOptions,
+ DeleteOptions,
+ EntityIds,
+} from "./base.js";
+export { AuthError, NotFoundError, APIError } from "./base.js";
diff --git a/cli/node/src/backend/platform.ts b/cli/node/src/backend/platform.ts
new file mode 100644
index 000000000..7ac121948
--- /dev/null
+++ b/cli/node/src/backend/platform.ts
@@ -0,0 +1,363 @@
+/**
+ * Platform (SaaS) backend โ communicates with api.mem0.ai.
+ */
+
+import type { PlatformConfig } from "../config.js";
+import { isAgentMode } from "../state.js";
+import { CLI_VERSION } from "../version.js";
+import {
+ APIError,
+ type AddOptions,
+ AuthError,
+ type Backend,
+ type DeleteOptions,
+ type EntityIds,
+ type ListOptions,
+ NotFoundError,
+ type SearchOptions,
+} from "./base.js";
+
+export class PlatformBackend implements Backend {
+ private baseUrl: string;
+ private headers: Record;
+
+ constructor(config: PlatformConfig) {
+ this.baseUrl = config.baseUrl.replace(/\/+$/, "");
+ this.headers = {
+ Authorization: `Token ${config.apiKey}`,
+ "Content-Type": "application/json",
+ "X-Mem0-Source": "cli",
+ "X-Mem0-Client-Language": "node",
+ "X-Mem0-Client-Version": CLI_VERSION,
+ };
+ }
+
+ private async _request(
+ method: string,
+ path: string,
+ opts?: { json?: unknown; params?: Record },
+ ): Promise {
+ let url = `${this.baseUrl}${path}`;
+ if (opts?.params) {
+ const qs = new URLSearchParams(opts.params).toString();
+ url += `?${qs}`;
+ }
+
+ const headers = {
+ ...this.headers,
+ "X-Mem0-Caller-Type": isAgentMode() ? "agent" : "user",
+ };
+
+ const fetchOpts: RequestInit = {
+ method,
+ headers,
+ signal: AbortSignal.timeout(30_000),
+ };
+ if (opts?.json) {
+ fetchOpts.body = JSON.stringify(opts.json);
+ }
+
+ const resp = await fetch(url, fetchOpts);
+
+ if (resp.status === 401) {
+ throw new AuthError();
+ }
+ if (resp.status === 404) {
+ throw new NotFoundError(path);
+ }
+ if (resp.status === 400) {
+ let detail: string;
+ try {
+ const body = (await resp.json()) as Record;
+ detail =
+ ((body.detail ?? body.message ?? JSON.stringify(body)) as string) ??
+ resp.statusText;
+ } catch {
+ detail = resp.statusText;
+ }
+ throw new APIError(path, detail);
+ }
+ if (!resp.ok) {
+ let detail: string = resp.statusText;
+ try {
+ const body = (await resp.json()) as Record;
+ detail = (body.detail ?? body.message ?? resp.statusText) as string;
+ } catch {
+ /* ignore */
+ }
+ throw new Error(`HTTP ${resp.status}: ${detail}`);
+ }
+ if (resp.status === 204) {
+ return {};
+ }
+ return resp.json();
+ }
+
+ async add(
+ content?: string,
+ messages?: Record[],
+ opts: AddOptions = {},
+ ): Promise> {
+ const payload: Record = {};
+
+ if (messages) {
+ payload.messages = messages;
+ } else if (content) {
+ payload.messages = [{ role: "user", content }];
+ }
+
+ if (opts.userId) payload.user_id = opts.userId;
+ if (opts.agentId) payload.agent_id = opts.agentId;
+ if (opts.appId) payload.app_id = opts.appId;
+ if (opts.runId) payload.run_id = opts.runId;
+ if (opts.metadata) payload.metadata = opts.metadata;
+ if (opts.immutable) payload.immutable = true;
+ if (opts.infer === false) payload.infer = false;
+ if (opts.expires) payload.expiration_date = opts.expires;
+ if (opts.categories) payload.categories = opts.categories;
+ if (opts.enableGraph) payload.enable_graph = true;
+ payload.source = "CLI";
+
+ return (await this._request("POST", "/v1/memories/", {
+ json: payload,
+ })) as Record;
+ }
+
+ private _buildFilters(opts: {
+ userId?: string;
+ agentId?: string;
+ appId?: string;
+ runId?: string;
+ extraFilters?: Record;
+ }): Record | undefined {
+ // If caller passed a pre-built filter structure, use it directly
+ if (
+ opts.extraFilters &&
+ ("AND" in opts.extraFilters || "OR" in opts.extraFilters)
+ ) {
+ return opts.extraFilters;
+ }
+
+ const andConditions: Record[] = [];
+ if (opts.userId) andConditions.push({ user_id: opts.userId });
+ if (opts.agentId) andConditions.push({ agent_id: opts.agentId });
+ if (opts.appId) andConditions.push({ app_id: opts.appId });
+ if (opts.runId) andConditions.push({ run_id: opts.runId });
+
+ if (opts.extraFilters) {
+ for (const [k, v] of Object.entries(opts.extraFilters)) {
+ andConditions.push({ [k]: v });
+ }
+ }
+
+ if (andConditions.length === 1) return andConditions[0];
+ if (andConditions.length > 1) return { AND: andConditions };
+ return undefined;
+ }
+
+ async search(
+ query: string,
+ opts: SearchOptions = {},
+ ): Promise[]> {
+ const payload: Record = {
+ query,
+ top_k: opts.topK ?? 10,
+ threshold: opts.threshold ?? 0.3,
+ };
+
+ const apiFilters = this._buildFilters({
+ userId: opts.userId,
+ agentId: opts.agentId,
+ appId: opts.appId,
+ runId: opts.runId,
+ extraFilters: opts.filters,
+ });
+ if (apiFilters) payload.filters = apiFilters;
+ if (opts.rerank) payload.rerank = true;
+ if (opts.keyword) payload.keyword_search = true;
+ if (opts.fields) payload.fields = opts.fields;
+ if (opts.enableGraph) payload.enable_graph = true;
+ payload.source = "CLI";
+
+ const result = (await this._request("POST", "/v2/memories/search/", {
+ json: payload,
+ })) as unknown;
+ if (Array.isArray(result)) return result;
+ const obj = result as Record;
+ return (obj.results ?? obj.memories ?? []) as Record[];
+ }
+
+ async get(memoryId: string): Promise> {
+ return (await this._request("GET", `/v1/memories/${memoryId}/`, {
+ params: { source: "CLI" },
+ })) as Record;
+ }
+
+ async listMemories(
+ opts: ListOptions = {},
+ ): Promise[]> {
+ const payload: Record = {};
+ const params: Record = {
+ page: String(opts.page ?? 1),
+ page_size: String(opts.pageSize ?? 100),
+ };
+
+ const extra: Record = {};
+ if (opts.category) {
+ extra.categories = { contains: opts.category };
+ }
+ if (opts.after) {
+ extra.created_at = {
+ ...(extra.created_at as Record | undefined),
+ gte: opts.after,
+ };
+ }
+ if (opts.before) {
+ extra.created_at = {
+ ...(extra.created_at as Record | undefined),
+ lte: opts.before,
+ };
+ }
+
+ const apiFilters = this._buildFilters({
+ userId: opts.userId,
+ agentId: opts.agentId,
+ appId: opts.appId,
+ runId: opts.runId,
+ extraFilters: Object.keys(extra).length > 0 ? extra : undefined,
+ });
+ if (apiFilters) payload.filters = apiFilters;
+ if (opts.enableGraph) payload.enable_graph = true;
+ payload.source = "CLI";
+
+ const result = (await this._request("POST", "/v2/memories/", {
+ json: payload,
+ params,
+ })) as unknown;
+ if (Array.isArray(result)) return result;
+ const obj = result as Record;
+ return (obj.results ?? obj.memories ?? []) as Record[];
+ }
+
+ async update(
+ memoryId: string,
+ content?: string,
+ metadata?: Record,
+ ): Promise> {
+ const payload: Record = {};
+ if (content) payload.text = content;
+ if (metadata) payload.metadata = metadata;
+ payload.source = "CLI";
+ return (await this._request("PUT", `/v1/memories/${memoryId}/`, {
+ json: payload,
+ })) as Record;
+ }
+
+ async delete(
+ memoryId?: string,
+ opts: DeleteOptions = {},
+ ): Promise> {
+ if (opts.all) {
+ const params: Record = { source: "CLI" };
+ if (opts.userId) params.user_id = opts.userId;
+ if (opts.agentId) params.agent_id = opts.agentId;
+ if (opts.appId) params.app_id = opts.appId;
+ if (opts.runId) params.run_id = opts.runId;
+ return (await this._request("DELETE", "/v1/memories/", {
+ params,
+ })) as Record;
+ }
+ if (memoryId) {
+ return (await this._request("DELETE", `/v1/memories/${memoryId}/`, {
+ params: { source: "CLI" },
+ })) as Record;
+ }
+ throw new Error("Either memoryId or --all is required");
+ }
+
+ async deleteEntities(opts: EntityIds): Promise> {
+ // v2 endpoint: DELETE /v2/entities/{entity_type}/{entity_id}/
+ const typeMap: [string, string | undefined][] = [
+ ["user", opts.userId],
+ ["agent", opts.agentId],
+ ["app", opts.appId],
+ ["run", opts.runId],
+ ];
+ const entities = typeMap.filter(([, v]) => v) as [string, string][];
+ if (entities.length === 0) {
+ throw new Error("At least one entity ID is required for deleteEntities.");
+ }
+ // Delete each provided entity via the v2 path-based endpoint
+ let result: Record = {};
+ for (const [entityType, entityId] of entities) {
+ result = (await this._request(
+ "DELETE",
+ `/v2/entities/${entityType}/${entityId}/`,
+ { params: { source: "CLI" } },
+ )) as Record;
+ }
+ return result;
+ }
+
+ async ping(): Promise> {
+ return (await this._request("GET", "/v1/ping/")) as Record;
+ }
+
+ async status(
+ opts: { userId?: string; agentId?: string } = {},
+ ): Promise> {
+ try {
+ await this.ping();
+ return { connected: true, backend: "platform", base_url: this.baseUrl };
+ } catch (e) {
+ return {
+ connected: false,
+ backend: "platform",
+ error: e instanceof Error ? e.message : String(e),
+ };
+ }
+ }
+
+ async entities(entityType: string): Promise[]> {
+ const result = (await this._request("GET", "/v1/entities/")) as unknown;
+ let items: Record[];
+ if (Array.isArray(result)) {
+ items = result;
+ } else {
+ items = ((result as Record).results ?? []) as Record<
+ string,
+ unknown
+ >[];
+ }
+
+ const typeMap: Record = {
+ users: "user",
+ agents: "agent",
+ apps: "app",
+ runs: "run",
+ };
+ const targetType = typeMap[entityType];
+ if (targetType) {
+ items = items.filter(
+ (e) => (e.type as string | undefined)?.toLowerCase() === targetType,
+ );
+ }
+ return items;
+ }
+
+ async listEvents(): Promise[]> {
+ const result = (await this._request("GET", "/v1/events/")) as unknown;
+ if (Array.isArray(result)) return result;
+ return ((result as Record).results ?? []) as Record<
+ string,
+ unknown
+ >[];
+ }
+
+ async getEvent(eventId: string): Promise> {
+ return (await this._request("GET", `/v1/event/${eventId}/`)) as Record<
+ string,
+ unknown
+ >;
+ }
+}
diff --git a/cli/node/src/branding.ts b/cli/node/src/branding.ts
new file mode 100644
index 000000000..10c6aa094
--- /dev/null
+++ b/cli/node/src/branding.ts
@@ -0,0 +1,172 @@
+/**
+ * Branding and ASCII art for mem0 CLI.
+ */
+
+import chalk from "chalk";
+import ora, { type Ora } from "ora";
+import { getCurrentCommand, isAgentMode } from "./state.js";
+import { CLI_VERSION } from "./version.js";
+
+export const LOGO = `
+โโโโ โโโโโโโโโโโโโโโโ โโโโ โโโโโโโ โโโโโโโโโโ โโโ
+โโโโโ โโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโ โโโโโโโโโโโ โโโ
+โโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโ โโโ โโโ โโโ
+โโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโ โโโ โโโ โโโ
+โโโ โโโ โโโโโโโโโโโโโโ โโโ โโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ
+โโโ โโโโโโโโโโโโโโ โโโ โโโโโโโ โโโโโโโโโโโโโโโโโโ
+`;
+
+export const LOGO_MINI = "โ mem0";
+export const TAGLINE = "The Memory Layer for AI Agents";
+
+export const BRAND_COLOR = "#8b5cf6";
+export const ACCENT_COLOR = "#a78bfa";
+export const SUCCESS_COLOR = "#22c55e";
+export const ERROR_COLOR = "#ef4444";
+export const WARNING_COLOR = "#f59e0b";
+export const DIM_COLOR = "#6b7280";
+
+const brand = chalk.hex(BRAND_COLOR);
+const accent = chalk.hex(ACCENT_COLOR);
+const success = chalk.hex(SUCCESS_COLOR);
+const error = chalk.hex(ERROR_COLOR);
+const warning = chalk.hex(WARNING_COLOR);
+const dim = chalk.hex(DIM_COLOR);
+
+/**
+ * Choose a symbol based on TTY/NO_COLOR. Fancy for interactive terminals,
+ * plain-text for piped/non-TTY or NO_COLOR environments.
+ */
+export function sym(fancy: string, plain: string): string {
+ if (!process.stdout.isTTY || process.env.NO_COLOR) return plain;
+ return fancy;
+}
+
+export function printBanner(): void {
+ if (isAgentMode()) return;
+ const pad = 3; // horizontal padding each side (matches Rich's padding=(0, 2))
+ const logoLines = LOGO.trimEnd().split("\n");
+ const tagline = ` ${TAGLINE}`;
+ const subtitle = `Node.js SDK ยท v${CLI_VERSION}`;
+ const contentLines = ["", ...logoLines, "", tagline, ""];
+
+ // Compute inner width from longest content line + padding both sides
+ const maxContent = Math.max(...contentLines.map((l) => l.length));
+ const innerWidth = maxContent + pad * 2;
+ const totalWidth = innerWidth + 2; // + 2 for โ borders
+
+ const topBorder = brand(`โญ${"โ".repeat(totalWidth - 2)}โฎ`);
+ const subtitleFill = totalWidth - 2 - subtitle.length - 3; // 3 = "โ " before subtitle + "โ" after
+ const bottomBorder = brand(
+ `โฐ${"โ".repeat(subtitleFill)} ${dim(subtitle)} ${"โ"}โฏ`,
+ );
+
+ const body = contentLines.map((line) => {
+ const rightPad = innerWidth - pad - line.length;
+ return `${brand("โ")}${" ".repeat(pad)}${brand.bold(line)}${" ".repeat(Math.max(rightPad, 0))}${brand("โ")}`;
+ });
+ // Re-color tagline line with accent instead of brand.bold
+ const taglineIdx = body.length - 2; // second-to-last (before trailing empty line)
+ const taglineRightPad = innerWidth - pad - tagline.length;
+ body[taglineIdx] =
+ `${brand("โ")}${" ".repeat(pad)}${accent(tagline)}${" ".repeat(Math.max(taglineRightPad, 0))}${brand("โ")}`;
+
+ console.log(topBorder);
+ for (const line of body) console.log(line);
+ console.log(bottomBorder);
+}
+
+export function printSuccess(message: string): void {
+ if (isAgentMode()) return;
+ console.log(`${success(sym("โ", "[ok]"))} ${message}`);
+}
+
+export function printError(message: string, hint?: string): void {
+ if (isAgentMode()) {
+ const envelope = {
+ status: "error",
+ command: getCurrentCommand(),
+ error: message,
+ data: null,
+ };
+ console.log(JSON.stringify(envelope));
+ return;
+ }
+ console.error(`${error(`${sym("โ", "[error]")} Error:`)} ${message}`);
+ const resolvedHint =
+ hint ??
+ (message.includes("Authentication failed")
+ ? `Run ${brand("mem0 init")} to reconfigure your API key ยท https://app.mem0.ai/dashboard/api-keys`
+ : undefined);
+ if (resolvedHint) {
+ console.error(` ${dim(resolvedHint)}`);
+ }
+}
+
+export function printWarning(message: string): void {
+ console.error(`${warning(sym("โ ", "[warn]"))} ${message}`);
+}
+
+export function printInfo(message: string): void {
+ if (isAgentMode()) return;
+ console.error(`${brand(sym("โ", "*"))} ${message}`);
+}
+
+export function printScope(ids: Record): void {
+ if (isAgentMode()) return;
+ const parts: string[] = [];
+ for (const [key, val] of Object.entries(ids)) {
+ if (val) {
+ parts.push(`${key}=${val}`);
+ }
+ }
+ if (parts.length > 0) {
+ console.error(` ${dim(`Scope: ${parts.join(", ")}`)}`);
+ }
+}
+
+export interface TimedStatusContext {
+ successMsg: string;
+ errorMsg: string;
+}
+
+/**
+ * Run an async function with a spinner, timing the operation.
+ * Equivalent to Python's timed_status context manager.
+ */
+export async function timedStatus(
+ message: string,
+ fn: (ctx: TimedStatusContext) => Promise,
+): Promise {
+ if (isAgentMode()) {
+ const ctx: TimedStatusContext = { successMsg: "", errorMsg: "" };
+ return fn(ctx);
+ }
+ const ctx: TimedStatusContext = { successMsg: "", errorMsg: "" };
+ const spinner = ora({
+ text: dim(message),
+ color: "yellow",
+ stream: process.stderr,
+ }).start();
+ const start = performance.now();
+
+ try {
+ const result = await fn(ctx);
+ const elapsed = ((performance.now() - start) / 1000).toFixed(2);
+ spinner.stop();
+ if (ctx.successMsg) {
+ console.error(`${success("โ")} ${ctx.successMsg} (${elapsed}s)`);
+ }
+ return result;
+ } catch (err) {
+ const elapsed = ((performance.now() - start) / 1000).toFixed(2);
+ spinner.stop();
+ if (ctx.errorMsg) {
+ printError(`${ctx.errorMsg} (${elapsed}s)`);
+ }
+ throw err;
+ }
+}
+
+/** Format helpers using brand colors for external use. */
+export const colors = { brand, accent, success, error, warning, dim };
diff --git a/cli/node/src/commands/config.ts b/cli/node/src/commands/config.ts
new file mode 100644
index 000000000..9e11f8608
--- /dev/null
+++ b/cli/node/src/commands/config.ts
@@ -0,0 +1,111 @@
+/**
+ * Config management commands: show, set, get.
+ */
+
+import Table from "cli-table3";
+import { colors, printError, printSuccess } from "../branding.js";
+import {
+ getNestedValue,
+ loadConfig,
+ redactKey,
+ saveConfig,
+ setNestedValue,
+} from "../config.js";
+import { formatAgentEnvelope, formatJsonEnvelope } from "../output.js";
+import { isAgentMode, setCurrentCommand } from "../state.js";
+
+const { brand, accent, dim } = colors;
+
+export function cmdConfigShow(opts: { output?: string } = {}): void {
+ setCurrentCommand("config show");
+ const config = loadConfig();
+
+ if (opts.output === "agent" || opts.output === "json") {
+ formatAgentEnvelope({
+ command: "config show",
+ data: {
+ defaults: {
+ user_id: config.defaults.userId || null,
+ agent_id: config.defaults.agentId || null,
+ app_id: config.defaults.appId || null,
+ run_id: config.defaults.runId || null,
+ enable_graph: config.defaults.enableGraph,
+ },
+ platform: {
+ api_key: redactKey(config.platform.apiKey),
+ base_url: config.platform.baseUrl,
+ },
+ },
+ });
+ return;
+ }
+
+ console.log();
+ console.log(` ${brand("โ mem0 Configuration")}\n`);
+
+ const table = new Table({
+ head: [accent("Key"), accent("Value")],
+ style: { head: [], border: [] },
+ });
+
+ // Defaults
+ table.push(["defaults.user_id", config.defaults.userId || dim("(not set)")]);
+ table.push([
+ "defaults.agent_id",
+ config.defaults.agentId || dim("(not set)"),
+ ]);
+ table.push(["defaults.app_id", config.defaults.appId || dim("(not set)")]);
+ table.push(["defaults.run_id", config.defaults.runId || dim("(not set)")]);
+ table.push(["defaults.enable_graph", String(config.defaults.enableGraph)]);
+ table.push(["", ""]);
+
+ // Platform
+ table.push(["platform.api_key", redactKey(config.platform.apiKey)]);
+ table.push(["platform.base_url", config.platform.baseUrl]);
+
+ console.log(table.toString());
+ console.log();
+}
+
+export function cmdConfigGet(key: string): void {
+ setCurrentCommand("config get");
+ const config = loadConfig();
+ const value = getNestedValue(config, key);
+
+ if (value === undefined) {
+ printError(`Unknown config key: ${key}`);
+ } else {
+ // Redact secrets
+ const displayValue =
+ key.includes("api_key") || key.split(".").pop() === "key"
+ ? redactKey(String(value))
+ : String(value);
+ if (isAgentMode()) {
+ formatAgentEnvelope({
+ command: "config get",
+ data: { key, value: displayValue },
+ });
+ } else {
+ console.log(displayValue);
+ }
+ }
+}
+
+export function cmdConfigSet(key: string, value: string): void {
+ setCurrentCommand("config set");
+ const config = loadConfig();
+ if (setNestedValue(config, key, value)) {
+ saveConfig(config);
+ const display = key.includes("key") ? redactKey(value) : value;
+ if (isAgentMode()) {
+ formatAgentEnvelope({
+ command: "config set",
+ data: { key, value: display },
+ });
+ } else {
+ printSuccess(`${key} = ${display}`);
+ }
+ } else {
+ printError(`Unknown config key: ${key}`);
+ }
+}
diff --git a/cli/node/src/commands/entities.ts b/cli/node/src/commands/entities.ts
new file mode 100644
index 000000000..8b168539e
--- /dev/null
+++ b/cli/node/src/commands/entities.ts
@@ -0,0 +1,168 @@
+/**
+ * Entity management commands.
+ */
+
+import readline from "node:readline";
+import Table from "cli-table3";
+import type { Backend } from "../backend/base.js";
+import {
+ colors,
+ printError,
+ printInfo,
+ printSuccess,
+ timedStatus,
+} from "../branding.js";
+import { formatAgentEnvelope, formatJson } from "../output.js";
+import { setCurrentCommand } from "../state.js";
+
+const { brand, accent, dim } = colors;
+
+const VALID_TYPES = new Set(["users", "agents", "apps", "runs"]);
+
+export async function cmdEntitiesList(
+ backend: Backend,
+ entityType: string,
+ opts: { output: string },
+): Promise {
+ setCurrentCommand("entity list");
+ if (!VALID_TYPES.has(entityType)) {
+ printError(
+ `Invalid entity type: ${entityType}. Use: ${[...VALID_TYPES].join(", ")}`,
+ );
+ process.exit(1);
+ }
+
+ const start = performance.now();
+ let results: Record[];
+ try {
+ results = await timedStatus(`Fetching ${entityType}...`, async () => {
+ return backend.entities(entityType);
+ });
+ } catch (e) {
+ printError(
+ e instanceof Error ? e.message : String(e),
+ "This feature may require the mem0 Platform.",
+ );
+ process.exit(1);
+ }
+ const elapsed = (performance.now() - start) / 1000;
+
+ if (opts.output === "agent" || opts.output === "json") {
+ formatAgentEnvelope({
+ command: "entity list",
+ data: results,
+ count: results.length,
+ durationMs: Math.round(elapsed * 1000),
+ });
+ return;
+ }
+
+ if (!results.length) {
+ printInfo(`No ${entityType} found.`);
+ return;
+ }
+
+ const table = new Table({
+ head: [accent("Name / ID"), accent("Created")],
+ style: { head: [], border: [] },
+ });
+
+ for (const entity of results) {
+ const name = String(entity.name ?? entity.id ?? "โ");
+ const created = String(entity.created_at ?? "โ").slice(0, 10);
+ table.push([name, created]);
+ }
+
+ console.log();
+ console.log(table.toString());
+ console.log(
+ ` ${dim(`${results.length} ${entityType} (${elapsed.toFixed(2)}s)`)}`,
+ );
+ console.log();
+}
+
+export async function cmdEntitiesDelete(
+ backend: Backend,
+ opts: {
+ userId?: string;
+ agentId?: string;
+ appId?: string;
+ runId?: string;
+ dryRun?: boolean;
+ force: boolean;
+ output: string;
+ },
+): Promise {
+ setCurrentCommand("entity delete");
+ const { isAgentMode } = await import("../state.js");
+ if (isAgentMode() && !opts.force) {
+ printError("Destructive operation requires --force in agent mode.");
+ process.exit(1);
+ }
+ if (!opts.userId && !opts.agentId && !opts.appId && !opts.runId) {
+ printError(
+ "Provide at least one of --user-id, --agent-id, --app-id, --run-id.",
+ );
+ process.exit(1);
+ }
+
+ const scopeParts: string[] = [];
+ if (opts.userId) scopeParts.push(`user=${opts.userId}`);
+ if (opts.agentId) scopeParts.push(`agent=${opts.agentId}`);
+ if (opts.appId) scopeParts.push(`app=${opts.appId}`);
+ if (opts.runId) scopeParts.push(`run=${opts.runId}`);
+ const scope = scopeParts.join(", ");
+
+ if (opts.dryRun) {
+ printInfo(`Would delete entity ${scope} and all its memories.`);
+ printInfo("No changes made.");
+ return;
+ }
+
+ if (!opts.force) {
+ const rl = readline.createInterface({
+ input: process.stdin,
+ output: process.stdout,
+ });
+ const answer = await new Promise((resolve) => {
+ rl.question(
+ `\n \u26a0 Delete entity ${scope} AND all its memories? This cannot be undone. [y/N] `,
+ resolve,
+ );
+ });
+ rl.close();
+ if (answer.toLowerCase() !== "y") {
+ printInfo("Cancelled.");
+ process.exit(0);
+ }
+ }
+
+ const start = performance.now();
+ let result: Record;
+ try {
+ result = await timedStatus("Deleting entity...", async () => {
+ return backend.deleteEntities({
+ userId: opts.userId,
+ agentId: opts.agentId,
+ appId: opts.appId,
+ runId: opts.runId,
+ });
+ });
+ } catch (e) {
+ printError(e instanceof Error ? e.message : String(e));
+ process.exit(1);
+ }
+ const elapsed = (performance.now() - start) / 1000;
+
+ if (opts.output === "agent") {
+ formatAgentEnvelope({
+ command: "entity delete",
+ data: { deleted: true },
+ durationMs: Math.round(elapsed * 1000),
+ });
+ } else if (opts.output === "json") {
+ formatJson(result);
+ } else if (opts.output !== "quiet") {
+ printSuccess(`Entity deleted with all memories (${elapsed.toFixed(2)}s)`);
+ }
+}
diff --git a/cli/node/src/commands/events.ts b/cli/node/src/commands/events.ts
new file mode 100644
index 000000000..f3463c98a
--- /dev/null
+++ b/cli/node/src/commands/events.ts
@@ -0,0 +1,169 @@
+/**
+ * Event commands: list and status.
+ */
+
+import boxen from "boxen";
+import Table from "cli-table3";
+import type { Backend } from "../backend/base.js";
+import { colors, printError, printInfo, timedStatus } from "../branding.js";
+import { formatAgentEnvelope, formatJson } from "../output.js";
+import { setCurrentCommand } from "../state.js";
+
+const { brand, accent, success, error: errorColor, warning, dim } = colors;
+
+function statusStyled(status: string): string {
+ switch (status.toUpperCase()) {
+ case "SUCCEEDED":
+ return success("SUCCEEDED");
+ case "PENDING":
+ return accent("PENDING");
+ case "FAILED":
+ return errorColor("FAILED");
+ case "PROCESSING":
+ return warning("PROCESSING");
+ default:
+ return status;
+ }
+}
+
+export async function cmdEventList(
+ backend: Backend,
+ opts: { output: string },
+): Promise {
+ setCurrentCommand("event list");
+ const start = performance.now();
+ let results: Record[];
+ try {
+ results = await timedStatus("Fetching events...", async () => {
+ return backend.listEvents();
+ });
+ } catch (e) {
+ printError(e instanceof Error ? e.message : String(e));
+ process.exit(1);
+ }
+ const elapsed = (performance.now() - start) / 1000;
+
+ if (opts.output === "agent" || opts.output === "json") {
+ formatAgentEnvelope({
+ command: "event list",
+ data: results,
+ count: results.length,
+ durationMs: Math.round(elapsed * 1000),
+ });
+ return;
+ }
+
+ if (results.length === 0) {
+ console.log();
+ printInfo("No events found.");
+ console.log();
+ return;
+ }
+
+ const table = new Table({
+ head: [
+ accent("Event ID"),
+ accent("Type"),
+ accent("Status"),
+ accent("Latency"),
+ accent("Created"),
+ ],
+ colWidths: [12, 14, 14, 10, 22],
+ wordWrap: true,
+ style: { head: [], border: [] },
+ });
+
+ for (const ev of results) {
+ const evId = String(ev.id ?? "").slice(0, 8);
+ const evType = String(ev.event_type ?? "โ");
+ const status = String(ev.status ?? "โ");
+ const latency = ev.latency as number | undefined;
+ const latencyStr = latency !== undefined ? `${Math.round(latency)}ms` : "โ";
+ const created = String(ev.created_at ?? "โ")
+ .slice(0, 19)
+ .replace("T", " ");
+ table.push([dim(evId), evType, statusStyled(status), latencyStr, created]);
+ }
+
+ console.log();
+ console.log(table.toString());
+ console.log(
+ ` ${dim(`${results.length} event${results.length !== 1 ? "s" : ""}`)}`,
+ );
+ console.log();
+}
+
+export async function cmdEventStatus(
+ backend: Backend,
+ eventId: string,
+ opts: { output: string },
+): Promise {
+ setCurrentCommand("event status");
+ const start = performance.now();
+ let ev: Record;
+ try {
+ ev = await timedStatus("Fetching event...", async () => {
+ return backend.getEvent(eventId);
+ });
+ } catch (e) {
+ printError(e instanceof Error ? e.message : String(e));
+ process.exit(1);
+ }
+ const elapsed = (performance.now() - start) / 1000;
+
+ if (opts.output === "agent" || opts.output === "json") {
+ formatAgentEnvelope({
+ command: "event status",
+ data: ev,
+ durationMs: Math.round(elapsed * 1000),
+ });
+ return;
+ }
+
+ const status = String(ev.status ?? "โ");
+ const evType = String(ev.event_type ?? "โ");
+ const latency = ev.latency as number | undefined;
+ const latencyStr = latency !== undefined ? `${Math.round(latency)}ms` : "โ";
+ const created = String(ev.created_at ?? "โ")
+ .slice(0, 19)
+ .replace("T", " ");
+ const updated = String(ev.updated_at ?? "โ")
+ .slice(0, 19)
+ .replace("T", " ");
+ const results = ev.results as Record[] | undefined;
+
+ const lines: string[] = [];
+ lines.push(` ${dim("Event ID:")} ${eventId}`);
+ lines.push(` ${dim("Type:")} ${evType}`);
+ lines.push(` ${dim("Status:")} ${statusStyled(status)}`);
+ lines.push(` ${dim("Latency:")} ${latencyStr}`);
+ lines.push(` ${dim("Created:")} ${created}`);
+ lines.push(` ${dim("Updated:")} ${updated}`);
+
+ if (results && results.length > 0) {
+ lines.push("");
+ lines.push(` ${dim(`Results (${results.length}):`)}`);
+ for (const r of results) {
+ const memId = String(r.id ?? "").slice(0, 8);
+ const data = r.data as Record | undefined;
+ const memory = data?.memory ? String(data.memory) : "";
+ const evName = String(r.event ?? "");
+ const user = String(r.user_id ?? "");
+ let detail = `${evName} ${memory}`;
+ if (user) detail += ` ${dim(`(user_id=${user})`)}`;
+ lines.push(` ${success("ยท")} ${detail} ${dim(`(${memId})`)}`);
+ }
+ }
+
+ const content = lines.join("\n");
+ console.log();
+ console.log(
+ boxen(content, {
+ title: brand("Event Status"),
+ titleAlignment: "left",
+ borderColor: "magenta",
+ padding: 1,
+ }),
+ );
+ console.log();
+}
diff --git a/cli/node/src/commands/init.ts b/cli/node/src/commands/init.ts
new file mode 100644
index 000000000..9f65cc564
--- /dev/null
+++ b/cli/node/src/commands/init.ts
@@ -0,0 +1,446 @@
+/**
+ * mem0 init โ interactive setup wizard.
+ */
+
+import fs from "node:fs";
+import readline from "node:readline";
+import { PlatformBackend } from "../backend/platform.js";
+import {
+ colors,
+ printBanner,
+ printError,
+ printInfo,
+ printSuccess,
+} from "../branding.js";
+import {
+ CONFIG_FILE,
+ DEFAULT_BASE_URL,
+ type Mem0Config,
+ createDefaultConfig,
+ loadConfig,
+ redactKey,
+ saveConfig,
+} from "../config.js";
+
+const { brand, dim } = colors;
+
+const EMAIL_RE = /^[^@\s]+@[^@\s]+\.[^@\s]+$/;
+
+function validateEmail(email: string): void {
+ if (!EMAIL_RE.test(email)) {
+ printError(`Invalid email address: ${JSON.stringify(email)}`);
+ process.exit(1);
+ }
+}
+
+async function emailLogin(
+ email: string,
+ code: string | undefined,
+ baseUrl: string,
+): Promise> {
+ const url = baseUrl.replace(/\/+$/, "");
+ let codeValue = code;
+
+ const sourceHeaders = {
+ "Content-Type": "application/json",
+ "X-Mem0-Source": "cli",
+ "X-Mem0-Client-Language": "node",
+ };
+
+ if (!codeValue) {
+ const resp = await fetch(`${url}/api/v1/auth/email_code/`, {
+ method: "POST",
+ headers: sourceHeaders,
+ body: JSON.stringify({ email }),
+ signal: AbortSignal.timeout(30_000),
+ });
+ if (resp.status === 429) {
+ printError("Too many attempts. Try again in a few minutes.");
+ process.exit(1);
+ }
+ if (!resp.ok) {
+ let detail: string;
+ try {
+ const body = (await resp.json()) as Record;
+ detail = (body.error ?? body.detail ?? resp.statusText) as string;
+ } catch {
+ detail = resp.statusText;
+ }
+ printError(`Failed to send code: ${detail}`);
+ process.exit(1);
+ }
+
+ printSuccess("Verification code sent! Check your email.");
+
+ if (!process.stdin.isTTY) {
+ printError(
+ "No --code provided and terminal is non-interactive.",
+ "Run: mem0 init --email --code ",
+ );
+ process.exit(1);
+ }
+
+ console.log();
+ const entered = await promptLine(` ${brand("Verification Code")}`);
+ if (!entered) {
+ printError("Code is required.");
+ process.exit(1);
+ }
+ codeValue = entered;
+ }
+
+ const verifyResp = await fetch(`${url}/api/v1/auth/email_code/verify/`, {
+ method: "POST",
+ headers: sourceHeaders,
+ body: JSON.stringify({ email, code: codeValue.trim() }),
+ signal: AbortSignal.timeout(30_000),
+ });
+ if (verifyResp.status === 429) {
+ printError("Too many attempts. Try again in a few minutes.");
+ process.exit(1);
+ }
+ if (!verifyResp.ok) {
+ let detail: string;
+ try {
+ const body = (await verifyResp.json()) as Record;
+ detail = (body.error ?? body.detail ?? verifyResp.statusText) as string;
+ } catch {
+ detail = verifyResp.statusText;
+ }
+ printError(`Verification failed: ${detail}`);
+ process.exit(1);
+ }
+
+ return verifyResp.json() as Promise>;
+}
+
+function promptSecret(label: string): Promise {
+ return new Promise((resolve, reject) => {
+ process.stdout.write(label);
+
+ if (process.stdin.isTTY) {
+ process.stdin.setRawMode(true);
+ }
+ process.stdin.resume();
+ process.stdin.setEncoding("utf-8");
+
+ const chars: string[] = [];
+
+ const onData = (key: string) => {
+ for (const ch of key) {
+ if (ch === "\r" || ch === "\n") {
+ cleanup();
+ process.stdout.write("\n");
+ resolve(chars.join(""));
+ return;
+ }
+ if (ch === "\x03") {
+ cleanup();
+ reject(new Error("Interrupted"));
+ return;
+ }
+ if (ch === "\x7f" || ch === "\x08") {
+ // backspace
+ if (chars.length > 0) {
+ chars.pop();
+ process.stdout.write("\b \b");
+ }
+ } else if (ch === "\x15") {
+ // Ctrl+U โ clear line
+ process.stdout.write("\b \b".repeat(chars.length));
+ chars.length = 0;
+ } else if (ch >= " ") {
+ chars.push(ch);
+ process.stdout.write("*");
+ }
+ }
+ };
+
+ const cleanup = () => {
+ process.stdin.removeListener("data", onData);
+ if (process.stdin.isTTY) {
+ process.stdin.setRawMode(false);
+ }
+ process.stdin.pause();
+ };
+
+ process.stdin.on("data", onData);
+ });
+}
+
+function promptLine(label: string, defaultValue?: string): Promise {
+ const rl = readline.createInterface({
+ input: process.stdin,
+ output: process.stdout,
+ });
+ const prompt = defaultValue ? `${label} [${defaultValue}]: ` : `${label}: `;
+ return new Promise((resolve) => {
+ rl.question(prompt, (answer) => {
+ rl.close();
+ resolve(answer.trim() || defaultValue || "");
+ });
+ });
+}
+
+async function setupPlatform(config: Mem0Config): Promise {
+ console.log();
+ console.log(
+ ` ${dim("Get your API key at https://app.mem0.ai/dashboard/api-keys")}`,
+ );
+ console.log();
+
+ process.stdout.write(` ${brand("API Key")}: `);
+ const apiKey = await promptSecret("");
+ if (!apiKey) {
+ printError("API key is required.");
+ process.exit(1);
+ }
+ config.platform.apiKey = apiKey;
+}
+
+async function setupDefaults(config: Mem0Config): Promise {
+ console.log();
+ printInfo("Set default entity IDs (press Enter to skip).\n");
+
+ const _systemUser = process.env.USER || process.env.USERNAME || "mem0-cli";
+ const userId = await promptLine(
+ ` ${brand("Default User ID")} ${dim("(recommended)")}`,
+ _systemUser,
+ );
+ if (userId) config.defaults.userId = userId;
+}
+
+async function validatePlatform(config: Mem0Config): Promise {
+ console.log();
+ printInfo("Validating connection...");
+ try {
+ const backend = new PlatformBackend(config.platform);
+ const status = await backend.status({
+ userId: config.defaults.userId || undefined,
+ agentId: config.defaults.agentId || undefined,
+ });
+ if (status.connected) {
+ printSuccess("Connected to mem0 Platform!");
+ // Cache user_email from ping response for telemetry distinct_id
+ try {
+ const pingData = (await backend.ping()) as Record;
+ const userEmail = pingData?.user_email as string | undefined;
+ if (userEmail) {
+ config.platform.userEmail = userEmail;
+ }
+ } catch {
+ /* ignore โ telemetry ID will fall back to API key hash */
+ }
+ } else {
+ printError(
+ `Could not connect: ${status.error ?? "Unknown error"}`,
+ "Visit https://app.mem0.ai/dashboard/api-keys to get a new key, or run mem0 init again.",
+ );
+ }
+ } catch (e) {
+ printError(`Connection test failed: ${e instanceof Error ? e.message : e}`);
+ }
+}
+
+export async function runInit(
+ opts: {
+ apiKey?: string;
+ userId?: string;
+ email?: string;
+ code?: string;
+ force?: boolean;
+ } = {},
+): Promise {
+ const config = createDefaultConfig();
+ const savedConfig = loadConfig();
+ const baseUrl =
+ process.env.MEM0_BASE_URL ||
+ savedConfig.platform.baseUrl ||
+ DEFAULT_BASE_URL;
+
+ // Guards
+ if (opts.code && !opts.email) {
+ printError("--code requires --email.");
+ process.exit(1);
+ }
+ if (opts.email && opts.apiKey) {
+ printError("Cannot use both --api-key and --email.");
+ process.exit(1);
+ }
+
+ // Warn if an existing config with an API key would be overwritten
+ if (
+ !opts.force &&
+ fs.existsSync(CONFIG_FILE) &&
+ savedConfig.platform.apiKey
+ ) {
+ console.log(
+ `\n ${brand("Existing configuration found")} ${dim(`(API key: ${redactKey(savedConfig.platform.apiKey)})`)}`,
+ );
+ if (process.stdin.isTTY) {
+ const rl = readline.createInterface({
+ input: process.stdin,
+ output: process.stdout,
+ });
+ const answer = await new Promise((resolve) => {
+ rl.question(
+ " Overwrite existing config? This cannot be undone. [y/N] ",
+ resolve,
+ );
+ });
+ rl.close();
+ if (answer.toLowerCase() !== "y") {
+ printInfo("Cancelled. Use --force to skip this check.");
+ process.exit(0);
+ }
+ } else {
+ printError(
+ "Existing config would be overwritten.",
+ "Use --force to overwrite.",
+ );
+ process.exit(1);
+ }
+ }
+
+ // โโ Email login flow โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ if (opts.email) {
+ const email = opts.email.trim().toLowerCase();
+ validateEmail(email);
+
+ printBanner();
+ console.log();
+ printInfo(`Logging in as ${email}...\n`);
+
+ const result = await emailLogin(email, opts.code, baseUrl);
+
+ const apiKeyVal = result.api_key as string | undefined;
+ if (!apiKeyVal) {
+ printError(
+ "Auth succeeded but no API key was returned. Contact support.",
+ );
+ process.exit(1);
+ }
+
+ config.platform.apiKey = apiKeyVal;
+ config.platform.baseUrl = baseUrl;
+ config.platform.userEmail = email;
+ config.defaults.userId =
+ opts.userId || process.env.USER || process.env.USERNAME || "mem0-cli";
+
+ saveConfig(config);
+ console.log();
+ printSuccess("Authenticated! Configuration saved to ~/.mem0/config.json");
+ console.log();
+ console.log(` ${dim("Get started:")}`);
+ console.log(` ${dim(' mem0 add "I prefer dark mode"')}`);
+ console.log(` ${dim(' mem0 search "preferences"')}`);
+ console.log();
+ return;
+ }
+
+ // โโ API key flow โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+ // Non-TTY: resolve defaults so partial flags work in pipelines / CI
+ if (!process.stdin.isTTY) {
+ if (!opts.apiKey) {
+ printError(
+ "Non-interactive terminal detected and --api-key is required.",
+ "Usage: mem0 init --api-key [--user-id ]",
+ );
+ process.exit(1);
+ }
+ opts.userId =
+ opts.userId || process.env.USER || process.env.USERNAME || "mem0-cli";
+ }
+
+ // Non-interactive: both flags provided
+ if (opts.apiKey && opts.userId) {
+ config.platform.apiKey = opts.apiKey;
+ config.defaults.userId = opts.userId;
+ await validatePlatform(config);
+ saveConfig(config);
+ printSuccess("Configuration saved to ~/.mem0/config.json");
+ return;
+ }
+
+ printBanner();
+ console.log();
+ printInfo("Welcome! Let's set up your mem0 CLI.\n");
+
+ // Use provided API key or prompt
+ if (opts.apiKey) {
+ config.platform.apiKey = opts.apiKey;
+ } else {
+ console.log(` ${brand("How would you like to authenticate?")}`);
+ console.log(` ${dim("1.")} Login with email ${dim("(recommended)")}`);
+ console.log(` ${dim("2.")} Enter API key manually`);
+ console.log();
+
+ const choice = await promptLine(` ${brand("Choose")} [1/2]`, "1");
+
+ if (choice === "1") {
+ console.log();
+ const emailAddr = await promptLine(` ${brand("Email")}`);
+ if (!emailAddr) {
+ printError("Email is required.");
+ process.exit(1);
+ }
+
+ const email = emailAddr.trim().toLowerCase();
+ validateEmail(email);
+ printInfo(`Logging in as ${email}...\n`);
+
+ const result = await emailLogin(email, undefined, baseUrl);
+
+ const apiKeyVal = result.api_key as string | undefined;
+ if (!apiKeyVal) {
+ printError(
+ "Auth succeeded but no API key was returned. Contact support.",
+ );
+ process.exit(1);
+ }
+
+ config.platform.apiKey = apiKeyVal;
+ config.platform.baseUrl = baseUrl;
+ config.platform.userEmail = email;
+ config.defaults.userId =
+ opts.userId || process.env.USER || process.env.USERNAME || "mem0-cli";
+
+ saveConfig(config);
+ console.log();
+ printSuccess("Authenticated! Configuration saved to ~/.mem0/config.json");
+ console.log();
+ console.log(` ${dim("Get started:")}`);
+ console.log(` ${dim(' mem0 add "I prefer dark mode"')}`);
+ console.log(` ${dim(' mem0 search "preferences"')}`);
+ console.log();
+ return;
+ }
+
+ // choice === "2": fall through to API key prompt
+ await setupPlatform(config);
+ }
+
+ // Use provided user ID or prompt
+ if (opts.userId) {
+ config.defaults.userId = opts.userId;
+ } else {
+ await setupDefaults(config);
+ }
+
+ await validatePlatform(config);
+
+ saveConfig(config);
+ console.log();
+ printSuccess("Configuration saved to ~/.mem0/config.json");
+ console.log();
+ console.log(` ${dim("Get started:")}`);
+ if (config.defaults.userId) {
+ console.log(` ${dim(' mem0 add "I prefer dark mode"')}`);
+ console.log(` ${dim(' mem0 search "preferences"')}`);
+ } else {
+ console.log(` ${dim(' mem0 add "I prefer dark mode" --user-id alice')}`);
+ console.log(` ${dim(' mem0 search "preferences" --user-id alice')}`);
+ }
+ console.log();
+}
diff --git a/cli/node/src/commands/memory.ts b/cli/node/src/commands/memory.ts
new file mode 100644
index 000000000..8ce6169b4
--- /dev/null
+++ b/cli/node/src/commands/memory.ts
@@ -0,0 +1,709 @@
+/**
+ * Memory CRUD commands: add, search, get, list, update, delete.
+ */
+
+import fs from "node:fs";
+import type { Backend } from "../backend/base.js";
+import {
+ printError,
+ printInfo,
+ printScope,
+ printSuccess,
+ timedStatus,
+} from "../branding.js";
+import {
+ formatAddResult,
+ formatAgentEnvelope,
+ formatJson,
+ formatJsonEnvelope,
+ formatMemoriesTable,
+ formatMemoriesText,
+ formatSingleMemory,
+ printResultSummary,
+} from "../output.js";
+import { isAgentMode, setCurrentCommand } from "../state.js";
+
+/** True only when stdin is an actual pipe or file redirect โ never in agent mode. */
+function _stdinIsPiped(): boolean {
+ if (isAgentMode()) return false;
+ try {
+ const stat = fs.fstatSync(0);
+ return stat.isFIFO() || stat.isFile();
+ } catch {
+ return false;
+ }
+}
+
+export async function cmdAdd(
+ backend: Backend,
+ text: string | undefined,
+ opts: {
+ userId?: string;
+ agentId?: string;
+ appId?: string;
+ runId?: string;
+ messages?: string;
+ file?: string;
+ metadata?: string;
+ immutable: boolean;
+ noInfer: boolean;
+ expires?: string;
+ categories?: string;
+ enableGraph: boolean;
+ output: string;
+ },
+): Promise {
+ setCurrentCommand("add");
+ let msgs: Record[] | undefined;
+ let content = text;
+
+ // Read from file
+ if (opts.file) {
+ try {
+ const raw = fs.readFileSync(opts.file, "utf-8");
+ msgs = JSON.parse(raw);
+ } catch (e) {
+ printError(`Failed to read file: ${e instanceof Error ? e.message : e}`);
+ process.exit(1);
+ }
+ }
+ // Parse messages JSON
+ else if (opts.messages) {
+ try {
+ msgs = JSON.parse(opts.messages);
+ } catch (e) {
+ printError(
+ `Invalid JSON in --messages: ${e instanceof Error ? e.message : e}`,
+ );
+ process.exit(1);
+ }
+ }
+ // Read from stdin only if stdin is an actual pipe or file redirect
+ else if (!content && _stdinIsPiped()) {
+ content = fs.readFileSync(0, "utf-8").trim();
+ }
+
+ if (content !== undefined && content.trim() === "") {
+ printError("Content cannot be empty.");
+ process.exit(1);
+ }
+ if (!content && !msgs) {
+ printError(
+ "No content provided. Pass text, --messages, --file, or pipe via stdin.",
+ );
+ process.exit(1);
+ }
+
+ // Validate --expires
+ if (opts.expires) {
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(opts.expires)) {
+ printError(
+ "Invalid date format for --expires. Use YYYY-MM-DD (e.g. 2025-12-31).",
+ );
+ process.exit(1);
+ }
+ if (new Date(opts.expires) <= new Date()) {
+ printError("--expires date must be in the future.");
+ process.exit(1);
+ }
+ }
+
+ let meta: Record | undefined;
+ if (opts.metadata) {
+ try {
+ meta = JSON.parse(opts.metadata);
+ } catch {
+ printError("Invalid JSON in --metadata.");
+ process.exit(1);
+ }
+ }
+
+ let cats: string[] | undefined;
+ if (opts.categories) {
+ try {
+ cats = JSON.parse(opts.categories);
+ } catch {
+ cats = opts.categories.split(",").map((c) => c.trim());
+ }
+ }
+
+ let result: Record;
+ try {
+ result = await timedStatus("Adding memory...", async () => {
+ return backend.add(content ?? undefined, msgs, {
+ userId: opts.userId,
+ agentId: opts.agentId,
+ appId: opts.appId,
+ runId: opts.runId,
+ metadata: meta,
+ immutable: opts.immutable,
+ infer: !opts.noInfer,
+ expires: opts.expires,
+ categories: cats,
+ enableGraph: opts.enableGraph,
+ });
+ });
+ } catch (e) {
+ printError(e instanceof Error ? e.message : String(e));
+ process.exit(1);
+ }
+
+ if (opts.output === "quiet") return;
+
+ // Deduplicate PENDING entries sharing the same event_id across all output modes
+ const rawResults: Record[] = Array.isArray(result)
+ ? result
+ : ((result.results as Record[]) ?? [result]);
+ const seenEvents = new Set();
+ const deduped: Record[] = [];
+ for (const r of rawResults) {
+ if (r.status === "PENDING") {
+ const eid = (r.event_id as string) ?? "";
+ if (eid && seenEvents.has(eid)) continue;
+ if (eid) seenEvents.add(eid);
+ }
+ deduped.push(r);
+ }
+ // Write back so downstream formatters see deduplicated data
+ const dedupedResult: Record = Array.isArray(result)
+ ? (deduped as unknown as Record)
+ : { ...result, results: deduped };
+
+ if (opts.output === "agent") {
+ const scope: Record = {
+ user_id: opts.userId,
+ agent_id: opts.agentId,
+ app_id: opts.appId,
+ run_id: opts.runId,
+ };
+ formatAgentEnvelope({
+ command: "add",
+ data: deduped,
+ scope,
+ count: deduped.length,
+ });
+ return;
+ }
+
+ if (opts.output === "json") {
+ formatAddResult(dedupedResult, opts.output);
+ return;
+ }
+
+ console.log();
+ printScope({
+ user_id: opts.userId,
+ agent_id: opts.agentId,
+ app_id: opts.appId,
+ run_id: opts.runId,
+ });
+ const count = deduped.length;
+ const allPending = count > 0 && deduped.every((r) => r.status === "PENDING");
+ if (allPending) {
+ printSuccess(
+ `Memory queued โ ${count} event${count !== 1 ? "s" : ""} pending`,
+ );
+ } else {
+ printSuccess(
+ `Memory processed โ ${count} memor${count === 1 ? "y" : "ies"} extracted`,
+ );
+ }
+ formatAddResult(dedupedResult, opts.output);
+}
+
+export async function cmdSearch(
+ backend: Backend,
+ query: string | undefined,
+ opts: {
+ userId?: string;
+ agentId?: string;
+ appId?: string;
+ runId?: string;
+ topK: number;
+ threshold: number;
+ rerank: boolean;
+ keyword: boolean;
+ filterJson?: string;
+ fields?: string;
+ enableGraph: boolean;
+ output: string;
+ },
+): Promise {
+ setCurrentCommand("search");
+ if (!query) {
+ printError("No query provided. Pass a query argument or pipe via stdin.");
+ process.exit(1);
+ }
+
+ let filters: Record | undefined;
+ if (opts.filterJson) {
+ try {
+ filters = JSON.parse(opts.filterJson);
+ } catch {
+ printError("Invalid JSON in --filter.");
+ process.exit(1);
+ }
+ }
+
+ const fieldList = opts.fields
+ ? opts.fields.split(",").map((f) => f.trim())
+ : undefined;
+
+ if (opts.topK < 1) {
+ printError("--top-k must be >= 1.");
+ process.exit(1);
+ }
+ if (opts.threshold < 0 || opts.threshold > 1) {
+ printError("--threshold must be between 0.0 and 1.0.");
+ process.exit(1);
+ }
+
+ const start = performance.now();
+ let results: Record[];
+ try {
+ results = await timedStatus("Searching memories...", async () => {
+ // biome-ignore lint/style/noNonNullAssertion: guarded by process.exit above
+ return backend.search(query!, {
+ userId: opts.userId,
+ agentId: opts.agentId,
+ appId: opts.appId,
+ runId: opts.runId,
+ topK: opts.topK,
+ threshold: opts.threshold,
+ rerank: opts.rerank,
+ keyword: opts.keyword,
+ filters,
+ fields: fieldList,
+ enableGraph: opts.enableGraph,
+ });
+ });
+ } catch (e) {
+ printError(e instanceof Error ? e.message : String(e));
+ process.exit(1);
+ }
+ const elapsed = (performance.now() - start) / 1000;
+
+ if (opts.output === "quiet") return;
+
+ if (opts.output === "agent") {
+ const scope: Record = {
+ user_id: opts.userId,
+ agent_id: opts.agentId,
+ app_id: opts.appId,
+ run_id: opts.runId,
+ };
+ formatAgentEnvelope({
+ command: "search",
+ data: results,
+ scope,
+ count: results.length,
+ durationMs: Math.round(elapsed * 1000),
+ });
+ return;
+ }
+
+ if (opts.output === "json") {
+ formatJson(results);
+ } else if (opts.output === "table") {
+ if (results.length > 0) {
+ formatMemoriesTable(results, { showScore: true });
+ printResultSummary({
+ count: results.length,
+ durationSecs: elapsed,
+ scopeIds: { user_id: opts.userId, agent_id: opts.agentId },
+ });
+ } else {
+ console.log();
+ printInfo("No memories found matching your query.");
+ console.log();
+ }
+ } else {
+ if (results.length > 0) {
+ formatMemoriesText(results);
+ printResultSummary({
+ count: results.length,
+ durationSecs: elapsed,
+ scopeIds: { user_id: opts.userId, agent_id: opts.agentId },
+ });
+ } else {
+ console.log();
+ printInfo("No memories found matching your query.");
+ console.log();
+ }
+ }
+}
+
+export async function cmdGet(
+ backend: Backend,
+ memoryId: string,
+ opts: { output: string },
+): Promise {
+ setCurrentCommand("get");
+ let result: Record;
+ try {
+ result = await timedStatus("Fetching memory...", async () => {
+ return backend.get(memoryId);
+ });
+ } catch (e) {
+ printError(e instanceof Error ? e.message : String(e));
+ process.exit(1);
+ }
+
+ if (opts.output === "agent") {
+ formatAgentEnvelope({ command: "get", data: result });
+ } else {
+ formatSingleMemory(result, opts.output);
+ }
+}
+
+export async function cmdList(
+ backend: Backend,
+ opts: {
+ userId?: string;
+ agentId?: string;
+ appId?: string;
+ runId?: string;
+ page: number;
+ pageSize: number;
+ category?: string;
+ after?: string;
+ before?: string;
+ enableGraph: boolean;
+ output: string;
+ },
+): Promise {
+ setCurrentCommand("list");
+ if (opts.pageSize < 1) {
+ printError("--page-size must be >= 1.");
+ process.exit(1);
+ }
+ if (opts.page < 1) {
+ printError("--page must be >= 1.");
+ process.exit(1);
+ }
+
+ const start = performance.now();
+ let results: Record[];
+ try {
+ results = await timedStatus("Listing memories...", async () => {
+ return backend.listMemories({
+ userId: opts.userId,
+ agentId: opts.agentId,
+ appId: opts.appId,
+ runId: opts.runId,
+ page: opts.page,
+ pageSize: opts.pageSize,
+ category: opts.category,
+ after: opts.after,
+ before: opts.before,
+ enableGraph: opts.enableGraph,
+ });
+ });
+ } catch (e) {
+ printError(e instanceof Error ? e.message : String(e));
+ process.exit(1);
+ }
+ const elapsed = (performance.now() - start) / 1000;
+
+ if (opts.output === "quiet") return;
+
+ if (opts.output === "agent" || opts.output === "json") {
+ const scope: Record = {
+ user_id: opts.userId,
+ agent_id: opts.agentId,
+ app_id: opts.appId,
+ run_id: opts.runId,
+ };
+ formatAgentEnvelope({
+ command: "list",
+ data: results,
+ scope,
+ count: results.length,
+ durationMs: Math.round(elapsed * 1000),
+ });
+ } else if (opts.output === "table") {
+ if (results.length > 0) {
+ formatMemoriesTable(results);
+ printResultSummary({
+ count: results.length,
+ durationSecs: elapsed,
+ page: opts.page,
+ scopeIds: { user_id: opts.userId, agent_id: opts.agentId },
+ });
+ } else {
+ console.log();
+ printInfo("No memories found.");
+ console.log();
+ }
+ } else {
+ if (results.length > 0) {
+ formatMemoriesText(results, "memories");
+ printResultSummary({
+ count: results.length,
+ durationSecs: elapsed,
+ page: opts.page,
+ scopeIds: { user_id: opts.userId, agent_id: opts.agentId },
+ });
+ } else {
+ console.log();
+ printInfo("No memories found.");
+ console.log();
+ }
+ }
+}
+
+export async function cmdUpdate(
+ backend: Backend,
+ memoryId: string,
+ text: string | undefined,
+ opts: { metadata?: string; output: string },
+): Promise {
+ setCurrentCommand("update");
+ let meta: Record | undefined;
+ if (opts.metadata) {
+ try {
+ meta = JSON.parse(opts.metadata);
+ } catch {
+ printError("Invalid JSON in --metadata.");
+ process.exit(1);
+ }
+ }
+
+ const start = performance.now();
+ let result: Record;
+ try {
+ result = await timedStatus("Updating memory...", async () => {
+ return backend.update(memoryId, text, meta);
+ });
+ } catch (e) {
+ printError(e instanceof Error ? e.message : String(e));
+ process.exit(1);
+ }
+ const elapsed = (performance.now() - start) / 1000;
+
+ if (opts.output === "agent") {
+ formatAgentEnvelope({
+ command: "update",
+ data: result,
+ durationMs: Math.round(elapsed * 1000),
+ });
+ } else if (opts.output === "json") {
+ formatJson(result);
+ } else if (opts.output !== "quiet") {
+ printSuccess(
+ `Memory ${memoryId.slice(0, 8)} updated (${elapsed.toFixed(2)}s)`,
+ );
+ }
+}
+
+export async function cmdDelete(
+ backend: Backend,
+ memoryId: string,
+ opts: { output: string; dryRun?: boolean; force?: boolean },
+): Promise {
+ setCurrentCommand("delete");
+ if (opts.dryRun) {
+ let mem: Record;
+ try {
+ mem = await backend.get(memoryId);
+ } catch (e) {
+ printError(e instanceof Error ? e.message : String(e));
+ process.exit(1);
+ }
+ const text = (mem.memory ?? mem.text ?? "") as string;
+ printInfo(`Would delete memory ${memoryId.slice(0, 8)}: ${text}`);
+ printInfo("No changes made.");
+ return;
+ }
+
+ const start = performance.now();
+ let result: Record;
+ try {
+ result = await timedStatus("Deleting...", async () => {
+ return backend.delete(memoryId);
+ });
+ } catch (e) {
+ printError(e instanceof Error ? e.message : String(e));
+ process.exit(1);
+ }
+ const elapsed = (performance.now() - start) / 1000;
+
+ if (opts.output === "agent") {
+ formatAgentEnvelope({
+ command: "delete",
+ data: { id: memoryId, deleted: true },
+ durationMs: Math.round(elapsed * 1000),
+ });
+ } else if (opts.output === "json") {
+ formatJson(result);
+ } else if (opts.output !== "quiet") {
+ printSuccess(
+ `Memory ${memoryId.slice(0, 8)} deleted (${elapsed.toFixed(2)}s)`,
+ );
+ }
+}
+
+export async function cmdDeleteAll(
+ backend: Backend,
+ opts: {
+ force: boolean;
+ dryRun?: boolean;
+ all?: boolean;
+ userId?: string;
+ agentId?: string;
+ appId?: string;
+ runId?: string;
+ output: string;
+ },
+): Promise {
+ setCurrentCommand("delete-all");
+ const { isAgentMode } = await import("../state.js");
+ if (isAgentMode() && !opts.force) {
+ printError("Destructive operation requires --force in agent mode.");
+ process.exit(1);
+ }
+ if (opts.all) {
+ // Project-wide wipe using wildcard entity IDs
+ // Note: --dry-run is ignored here because the API has no count-before-delete endpoint.
+
+ if (!opts.force) {
+ const readline = await import("node:readline");
+ const rl = readline.createInterface({
+ input: process.stdin,
+ output: process.stdout,
+ });
+ const answer = await new Promise((resolve) => {
+ rl.question(
+ "\n \u26a0 Delete ALL memories across the ENTIRE project? This cannot be undone. [y/N] ",
+ resolve,
+ );
+ });
+ rl.close();
+ if (answer.toLowerCase() !== "y") {
+ printInfo("Cancelled.");
+ process.exit(0);
+ }
+ }
+
+ const start = performance.now();
+ let result: Record;
+ try {
+ result = await timedStatus(
+ "Deleting all memories project-wide...",
+ async () => {
+ return backend.delete(undefined, {
+ all: true,
+ userId: "*",
+ agentId: "*",
+ appId: "*",
+ runId: "*",
+ });
+ },
+ );
+ } catch (e) {
+ printError(e instanceof Error ? e.message : String(e));
+ process.exit(1);
+ }
+ const elapsed = (performance.now() - start) / 1000;
+
+ if (opts.output === "agent") {
+ formatAgentEnvelope({
+ command: "delete-all",
+ data: result,
+ durationMs: Math.round(elapsed * 1000),
+ });
+ } else if (opts.output === "json") {
+ formatJson(result);
+ } else if (opts.output !== "quiet") {
+ if (result.message) {
+ printInfo(
+ "Deletion started. Memories will be removed in the background.",
+ );
+ } else {
+ printSuccess(`All project memories deleted (${elapsed.toFixed(2)}s)`);
+ }
+ }
+ return;
+ }
+
+ if (opts.dryRun) {
+ let memories: Record[];
+ try {
+ memories = await backend.listMemories({
+ userId: opts.userId,
+ agentId: opts.agentId,
+ appId: opts.appId,
+ runId: opts.runId,
+ });
+ } catch (e) {
+ printError(e instanceof Error ? e.message : String(e));
+ process.exit(1);
+ }
+ printInfo(`Would delete ${memories.length} memories.`);
+ printInfo("No changes made.");
+ return;
+ }
+
+ if (!opts.force) {
+ const scopeParts: string[] = [];
+ if (opts.userId) scopeParts.push(`user=${opts.userId}`);
+ if (opts.agentId) scopeParts.push(`agent=${opts.agentId}`);
+ if (opts.appId) scopeParts.push(`app=${opts.appId}`);
+ if (opts.runId) scopeParts.push(`run=${opts.runId}`);
+ const scope =
+ scopeParts.length > 0 ? scopeParts.join(", ") : "ALL entities";
+
+ const readline = await import("node:readline");
+ const rl = readline.createInterface({
+ input: process.stdin,
+ output: process.stdout,
+ });
+ const answer = await new Promise((resolve) => {
+ rl.question(
+ `\n \u26a0 Delete ALL memories for ${scope}? This cannot be undone. [y/N] `,
+ resolve,
+ );
+ });
+ rl.close();
+ if (answer.toLowerCase() !== "y") {
+ printInfo("Cancelled.");
+ process.exit(0);
+ }
+ }
+
+ const start = performance.now();
+ let result: Record;
+ try {
+ result = await timedStatus("Deleting all memories...", async () => {
+ return backend.delete(undefined, {
+ all: true,
+ userId: opts.userId,
+ agentId: opts.agentId,
+ appId: opts.appId,
+ runId: opts.runId,
+ });
+ });
+ } catch (e) {
+ printError(e instanceof Error ? e.message : String(e));
+ process.exit(1);
+ }
+ const elapsed = (performance.now() - start) / 1000;
+
+ if (opts.output === "agent") {
+ formatAgentEnvelope({
+ command: "delete-all",
+ data: result,
+ durationMs: Math.round(elapsed * 1000),
+ });
+ } else if (opts.output === "json") {
+ formatJson(result);
+ } else if (opts.output !== "quiet") {
+ if (result.message) {
+ printInfo(
+ "Deletion started. Memories will be removed in the background.",
+ );
+ } else {
+ printSuccess(`All matching memories deleted (${elapsed.toFixed(2)}s)`);
+ }
+ }
+}
diff --git a/cli/node/src/commands/utils.ts b/cli/node/src/commands/utils.ts
new file mode 100644
index 000000000..ae7b441d4
--- /dev/null
+++ b/cli/node/src/commands/utils.ts
@@ -0,0 +1,155 @@
+/**
+ * Utility commands: status, version, import.
+ */
+
+import fs from "node:fs";
+import boxen from "boxen";
+import type { Backend } from "../backend/base.js";
+import { colors, printError, printSuccess, timedStatus } from "../branding.js";
+import { formatAgentEnvelope, formatJsonEnvelope } from "../output.js";
+import { setCurrentCommand } from "../state.js";
+import { CLI_VERSION } from "../version.js";
+
+const { brand, dim, success, error: errorColor } = colors;
+
+export async function cmdStatus(
+ backend: Backend,
+ opts: { userId?: string; agentId?: string; output?: string } = {},
+): Promise {
+ setCurrentCommand("status");
+ const start = performance.now();
+ let result: Record;
+ try {
+ result = await timedStatus("Checking connection...", async () => {
+ return backend.status({ userId: opts.userId, agentId: opts.agentId });
+ });
+ } catch (e) {
+ result = {
+ connected: false,
+ error: e instanceof Error ? e.message : String(e),
+ };
+ }
+ const elapsed = (performance.now() - start) / 1000;
+
+ if (opts.output === "agent" || opts.output === "json") {
+ formatAgentEnvelope({
+ command: "status",
+ data: {
+ connected: result.connected,
+ backend: result.backend ?? null,
+ base_url: result.base_url ?? null,
+ },
+ durationMs: Math.round(elapsed * 1000),
+ });
+ return;
+ }
+
+ const lines: string[] = [];
+ if (result.connected) {
+ lines.push(` ${success("\u25cf")} Connected`);
+ } else {
+ lines.push(` ${errorColor("\u25cf")} Disconnected`);
+ }
+
+ lines.push(` ${dim("Backend:")} ${result.backend ?? "?"}`);
+ if (result.base_url) {
+ lines.push(` ${dim("API URL:")} ${result.base_url}`);
+ }
+ if (result.error) {
+ lines.push(` ${errorColor("Error:")} ${result.error}`);
+ if (String(result.error).includes("Authentication failed")) {
+ lines.push("");
+ lines.push(
+ ` ${dim("Run")} ${brand("mem0 init")} ${dim("to reconfigure your API key")}`,
+ );
+ lines.push(
+ ` ${dim("Get a key at")} ${brand("https://app.mem0.ai/dashboard/api-keys")}`,
+ );
+ }
+ }
+ lines.push(` ${dim("Latency:")} ${elapsed.toFixed(2)}s`);
+
+ const content = lines.join("\n");
+ console.log();
+ console.log(
+ boxen(content, {
+ title: brand("Connection Status"),
+ titleAlignment: "left",
+ borderColor: "magenta",
+ padding: 1,
+ }),
+ );
+ console.log();
+}
+
+export function cmdVersion(): void {
+ console.log(` ${brand("โ Mem0")} CLI v${CLI_VERSION}`);
+}
+
+export async function cmdImport(
+ backend: Backend,
+ filePath: string,
+ opts: { userId?: string; agentId?: string; output?: string },
+): Promise {
+ setCurrentCommand("import");
+ let data: Record[];
+ try {
+ const raw = fs.readFileSync(filePath, "utf-8");
+ const parsed = JSON.parse(raw);
+ data = Array.isArray(parsed) ? parsed : [parsed];
+ } catch (e) {
+ printError(`Failed to read file: ${e instanceof Error ? e.message : e}`);
+ process.exit(1);
+ }
+
+ let added = 0;
+ let failed = 0;
+ const start = performance.now();
+
+ for (let i = 0; i < data.length; i++) {
+ const item = data[i];
+ const content = (item.memory ?? item.text ?? item.content ?? "") as string;
+ if (!content) {
+ failed++;
+ continue;
+ }
+
+ try {
+ await backend.add(content, undefined, {
+ userId: opts.userId ?? (item.user_id as string | undefined),
+ agentId: opts.agentId ?? (item.agent_id as string | undefined),
+ metadata: item.metadata as Record | undefined,
+ });
+ added++;
+ } catch {
+ failed++;
+ }
+
+ // Simple progress indicator
+ if ((i + 1) % 10 === 0 || i === data.length - 1) {
+ process.stdout.write(
+ `\r ${dim(`Importing memories... ${i + 1}/${data.length}`)}`,
+ );
+ }
+ }
+
+ const elapsed = (performance.now() - start) / 1000;
+ console.log(); // Clear progress line
+
+ if (opts.output === "agent" || opts.output === "json") {
+ formatAgentEnvelope({
+ command: "import",
+ data: {
+ added,
+ failed,
+ },
+ durationMs: Math.round(elapsed * 1000),
+ });
+ return;
+ }
+
+ printSuccess(`Imported ${added} memories (${elapsed.toFixed(2)}s)`);
+ if (failed > 0) {
+ printError(`${failed} memories failed to import.`);
+ }
+}
diff --git a/cli/node/src/config.ts b/cli/node/src/config.ts
new file mode 100644
index 000000000..8cbd8d0d4
--- /dev/null
+++ b/cli/node/src/config.ts
@@ -0,0 +1,195 @@
+/**
+ * Configuration management for mem0 CLI.
+ *
+ * Config precedence (highest to lowest):
+ * 1. CLI flags (--api-key, --base-url, etc.)
+ * 2. Environment variables (MEM0_API_KEY, etc.)
+ * 3. Config file (~/.mem0/config.json)
+ * 4. Defaults
+ */
+
+import fs from "node:fs";
+import os from "node:os";
+import path from "node:path";
+
+export const CONFIG_DIR = path.join(os.homedir(), ".mem0");
+export const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
+export const DEFAULT_BASE_URL = "https://api.mem0.ai";
+export const CONFIG_VERSION = 1;
+
+export interface PlatformConfig {
+ apiKey: string;
+ baseUrl: string;
+ userEmail: string;
+}
+
+export interface DefaultsConfig {
+ userId: string;
+ agentId: string;
+ appId: string;
+ runId: string;
+ enableGraph: boolean;
+}
+
+export interface TelemetryConfig {
+ anonymousId: string;
+}
+
+export interface Mem0Config {
+ version: number;
+ defaults: DefaultsConfig;
+ platform: PlatformConfig;
+ telemetry: TelemetryConfig;
+}
+
+export function createDefaultConfig(): Mem0Config {
+ return {
+ version: CONFIG_VERSION,
+ defaults: {
+ userId: "",
+ agentId: "",
+ appId: "",
+ runId: "",
+ enableGraph: false,
+ },
+ platform: {
+ apiKey: "",
+ baseUrl: DEFAULT_BASE_URL,
+ userEmail: "",
+ },
+ telemetry: {
+ anonymousId: "",
+ },
+ };
+}
+
+export function ensureConfigDir(): string {
+ fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
+ return CONFIG_DIR;
+}
+
+export function loadConfig(): Mem0Config {
+ const config = createDefaultConfig();
+
+ if (fs.existsSync(CONFIG_FILE)) {
+ const raw = fs.readFileSync(CONFIG_FILE, "utf-8");
+ const data = JSON.parse(raw);
+
+ config.version = data.version ?? CONFIG_VERSION;
+
+ const plat = data.platform ?? {};
+ config.platform.apiKey = plat.api_key ?? "";
+ config.platform.baseUrl = plat.base_url ?? DEFAULT_BASE_URL;
+ config.platform.userEmail = plat.user_email ?? "";
+
+ const defaults = data.defaults ?? {};
+ config.defaults.userId = defaults.user_id ?? "";
+ config.defaults.agentId = defaults.agent_id ?? "";
+ config.defaults.appId = defaults.app_id ?? "";
+ config.defaults.runId = defaults.run_id ?? "";
+ config.defaults.enableGraph = defaults.enable_graph ?? false;
+
+ const telemetry = data.telemetry ?? {};
+ config.telemetry.anonymousId = telemetry.anonymous_id ?? "";
+ }
+
+ // Environment variable overrides
+ if (process.env.MEM0_API_KEY)
+ config.platform.apiKey = process.env.MEM0_API_KEY;
+ if (process.env.MEM0_BASE_URL)
+ config.platform.baseUrl = process.env.MEM0_BASE_URL;
+ if (process.env.MEM0_USER_ID)
+ config.defaults.userId = process.env.MEM0_USER_ID;
+ if (process.env.MEM0_AGENT_ID)
+ config.defaults.agentId = process.env.MEM0_AGENT_ID;
+ if (process.env.MEM0_APP_ID) config.defaults.appId = process.env.MEM0_APP_ID;
+ if (process.env.MEM0_RUN_ID) config.defaults.runId = process.env.MEM0_RUN_ID;
+ if (process.env.MEM0_ENABLE_GRAPH) {
+ config.defaults.enableGraph = ["true", "1", "yes"].includes(
+ process.env.MEM0_ENABLE_GRAPH.toLowerCase(),
+ );
+ }
+
+ return config;
+}
+
+export function saveConfig(config: Mem0Config): void {
+ ensureConfigDir();
+
+ const data = {
+ version: config.version,
+ defaults: {
+ user_id: config.defaults.userId,
+ agent_id: config.defaults.agentId,
+ app_id: config.defaults.appId,
+ run_id: config.defaults.runId,
+ enable_graph: config.defaults.enableGraph,
+ },
+ platform: {
+ api_key: config.platform.apiKey,
+ base_url: config.platform.baseUrl,
+ user_email: config.platform.userEmail,
+ },
+ telemetry: {
+ anonymous_id: config.telemetry.anonymousId,
+ },
+ };
+
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(data, null, 2));
+ fs.chmodSync(CONFIG_FILE, 0o600);
+}
+
+export function redactKey(key: string): string {
+ if (!key) return "(not set)";
+ if (key.length <= 8) return `${key.slice(0, 2)}***`;
+ return `${key.slice(0, 4)}...${key.slice(-4)}`;
+}
+
+/** Key map from dotted config path to the config object fields. */
+const KEY_MAP: Record = {
+ "platform.api_key": ["platform", "apiKey"],
+ "platform.base_url": ["platform", "baseUrl"],
+ "platform.user_email": ["platform", "userEmail"],
+ "defaults.user_id": ["defaults", "userId"],
+ "defaults.agent_id": ["defaults", "agentId"],
+ "defaults.app_id": ["defaults", "appId"],
+ "defaults.run_id": ["defaults", "runId"],
+ "defaults.enable_graph": ["defaults", "enableGraph"],
+ // Short-form aliases
+ api_key: ["platform", "apiKey"],
+ base_url: ["platform", "baseUrl"],
+ user_email: ["platform", "userEmail"],
+ user_id: ["defaults", "userId"],
+ agent_id: ["defaults", "agentId"],
+ app_id: ["defaults", "appId"],
+ run_id: ["defaults", "runId"],
+ enable_graph: ["defaults", "enableGraph"],
+};
+
+export function getNestedValue(config: Mem0Config, dottedKey: string): unknown {
+ const mapping = KEY_MAP[dottedKey];
+ if (!mapping) return undefined;
+ const [section, field] = mapping;
+ return (config[section] as unknown as Record)[field];
+}
+
+export function setNestedValue(
+ config: Mem0Config,
+ dottedKey: string,
+ value: string,
+): boolean {
+ const mapping = KEY_MAP[dottedKey];
+ if (!mapping) return false;
+ const [section, field] = mapping;
+ const obj = config[section] as unknown as Record;
+
+ const current = obj[field];
+ if (typeof current === "boolean") {
+ obj[field] = ["true", "1", "yes"].includes(value.toLowerCase());
+ } else if (typeof current === "number") {
+ obj[field] = Number.parseInt(value, 10);
+ } else {
+ obj[field] = value;
+ }
+ return true;
+}
diff --git a/cli/node/src/globals.d.ts b/cli/node/src/globals.d.ts
new file mode 100644
index 000000000..32a5c75cd
--- /dev/null
+++ b/cli/node/src/globals.d.ts
@@ -0,0 +1,2 @@
+/** Injected by tsup at build time from package.json version field. Undefined in dev/test. */
+declare const __CLI_VERSION__: string | undefined;
diff --git a/cli/node/src/help.ts b/cli/node/src/help.ts
new file mode 100644
index 000000000..4b014a50d
--- /dev/null
+++ b/cli/node/src/help.ts
@@ -0,0 +1,378 @@
+/**
+ * Rich-style help formatter for Commander.js that matches the Python CLI's
+ * Typer + Rich output (rounded box panels, brand purple, grouped options).
+ */
+
+import chalk from "chalk";
+import type { Argument, Command, Help, Option } from "commander";
+// Colors imported from chalk directly to match Typer/Rich defaults
+
+// โโ Colors (matching Typer/Rich defaults) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+const cyanBold = chalk.cyan.bold; // option flags, command names
+const greenBold = chalk.green.bold; // switch flags (boolean --force etc)
+const yellowBold = chalk.yellow.bold; // metavar
+const yellow = chalk.yellow; // "Usage:" label
+const bold = chalk.bold; // command name in usage
+const dim = chalk.dim; // defaults, descriptions
+const dimBorder = chalk.dim; // panel borders
+
+// โโ Strip ANSI โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+// biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI escape sequence is intentional
+const ANSI_RE = /\x1b\[[0-9;]*m/g;
+
+function stripAnsi(str: string): number {
+ return str.replace(ANSI_RE, "").length;
+}
+
+// โโ Command display order (matches Python CLI) โโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+/** Commands grouped into panels, matching Python CLI's rich_help_panel. */
+const COMMAND_GROUPS: { panel: string; commands: string[] }[] = [
+ {
+ panel: "Memory",
+ commands: ["add", "search", "get", "list", "update", "delete"],
+ },
+ {
+ panel: "Management",
+ commands: ["init", "status", "import", "help", "entity", "event", "config"],
+ },
+];
+
+/** Flat order derived from COMMAND_GROUPS. */
+const COMMAND_ORDER: string[] = COMMAND_GROUPS.flatMap((g) => g.commands);
+
+// โโ Option-to-panel mapping (derived from Python's rich_help_panel) โโโโโ
+
+const OPTION_PANELS: Record> = {
+ add: {
+ "--user-id": "Scope",
+ "--agent-id": "Scope",
+ "--app-id": "Scope",
+ "--run-id": "Scope",
+ "--output": "Output",
+ "--api-key": "Connection",
+ "--base-url": "Connection",
+ },
+ search: {
+ "--user-id": "Scope",
+ "--agent-id": "Scope",
+ "--app-id": "Scope",
+ "--run-id": "Scope",
+ "--top-k": "Search",
+ "--threshold": "Search",
+ "--rerank": "Search",
+ "--keyword": "Search",
+ "--filter": "Search",
+ "--fields": "Search",
+ "--graph": "Search",
+ "--no-graph": "Search",
+ "--output": "Output",
+ "--api-key": "Connection",
+ "--base-url": "Connection",
+ },
+ get: {
+ "--output": "Output",
+ "--api-key": "Connection",
+ "--base-url": "Connection",
+ },
+ list: {
+ "--user-id": "Scope",
+ "--agent-id": "Scope",
+ "--app-id": "Scope",
+ "--run-id": "Scope",
+ "--page": "Pagination",
+ "--page-size": "Pagination",
+ "--category": "Filters",
+ "--after": "Filters",
+ "--before": "Filters",
+ "--graph": "Filters",
+ "--no-graph": "Filters",
+ "--output": "Output",
+ "--api-key": "Connection",
+ "--base-url": "Connection",
+ },
+ update: {
+ "--output": "Output",
+ "--api-key": "Connection",
+ "--base-url": "Connection",
+ },
+ delete: {
+ "--user-id": "Scope",
+ "--agent-id": "Scope",
+ "--app-id": "Scope",
+ "--run-id": "Scope",
+ "--output": "Output",
+ "--api-key": "Connection",
+ "--base-url": "Connection",
+ },
+ status: {
+ "--output": "Output",
+ "--api-key": "Connection",
+ "--base-url": "Connection",
+ },
+ import: {
+ "--user-id": "Scope",
+ "--agent-id": "Scope",
+ "--output": "Output",
+ "--api-key": "Connection",
+ "--base-url": "Connection",
+ },
+};
+
+const PANEL_ORDER: string[] = [
+ "Scope",
+ "Search",
+ "Pagination",
+ "Filters",
+ "Output",
+ "Connection",
+];
+
+// โโ Panel rendering โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+/**
+ * Render a Rich-style ROUNDED box panel.
+ *
+ * ```
+ * โญโ Title โโโโโโโโโโโโโโโโโโโโโโโโโฎ
+ * โ row content padded โ
+ * โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ
+ * ```
+ */
+function renderPanel(title: string, rows: string[], width: number): string {
+ if (rows.length === 0) return "";
+
+ // Inner width is total width minus the two border chars
+ const inner = width - 2;
+
+ // Top border: โญโ Title โ...โโฎ
+ const titleStr = ` ${title} `;
+ const fillLen = Math.max(0, inner - 1 - titleStr.length);
+ const topLine =
+ dimBorder("โญโ") +
+ dimBorder(titleStr) +
+ dimBorder("โ".repeat(fillLen)) +
+ dimBorder("โฎ");
+
+ // Bottom border: โฐโ...โโฏ
+ const bottomLine =
+ dimBorder("โฐ") + dimBorder("โ".repeat(inner)) + dimBorder("โฏ");
+
+ // Content rows
+ const contentLines = rows.map((row) => {
+ const visLen = stripAnsi(row);
+ const pad = Math.max(0, inner - 1 - visLen);
+ return `${dimBorder("โ")} ${row}${" ".repeat(pad)}${dimBorder("โ")}`;
+ });
+
+ return [topLine, ...contentLines, bottomLine].join("\n");
+}
+
+// โโ Format an option term (short + long) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+function formatOptionTerm(opt: Option): string {
+ const parts: string[] = [];
+ if (opt.short) parts.push(opt.short);
+ if (opt.long) parts.push(opt.long);
+ let term = parts.join(", ");
+
+ // Append value placeholder for non-boolean options
+ if (opt.flags) {
+ const match = opt.flags.match(/<[^>]+>|\[[^\]]+\]/);
+ if (match) {
+ term += ` ${match[0]}`;
+ }
+ }
+ return term;
+}
+
+// โโ Get the long flag name for panel lookup โโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+function getLongFlag(opt: Option): string {
+ if (opt.long) return opt.long;
+ return opt.short || "";
+}
+
+// โโ Format a default value โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+function formatDefault(opt: Option): string {
+ if (opt.defaultValue !== undefined && opt.defaultValue !== false) {
+ return dim(` [default: ${opt.defaultValue}]`);
+ }
+ return "";
+}
+
+// โโ The main help formatter โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+export function richFormatHelp(cmd: Command, helper: Help): string {
+ const width = process.stdout.columns || 80;
+ const lines: string[] = [];
+
+ const isRoot = !cmd.parent;
+
+ // โโ Usage line โโ
+ const usage = helper.commandUsage(cmd);
+ lines.push("");
+ if (isRoot) {
+ // Root: "Usage: mem0 [options]" โ yellow, [options] bold
+ lines.push(
+ ` ${yellow("Usage:")} ${bold(cmd.name())} ${yellow("")} ${bold("[options]")}`,
+ );
+ } else {
+ // Subcommands: split into command path (bold) and args (yellow)
+ const usageParts = usage.split(" ");
+ const cmdPath: string[] = [];
+ const argParts: string[] = [];
+ let pastCmd = false;
+ for (const part of usageParts) {
+ if (!pastCmd && !part.startsWith("[") && !part.startsWith("<")) {
+ cmdPath.push(part);
+ } else {
+ pastCmd = true;
+ argParts.push(part);
+ }
+ }
+ lines.push(
+ ` ${yellow("Usage:")} ${bold(cmdPath.join(" "))} ${yellow(argParts.join(" "))}`,
+ );
+ }
+ lines.push("");
+
+ // โโ Description โโ
+ const desc = helper.commandDescription(cmd);
+ if (desc) {
+ // Split multi-line descriptions (e.g., title + tagline)
+ const descLines = desc.split("\n");
+ for (let i = 0; i < descLines.length; i++) {
+ const dLine = descLines[i];
+ // First line is the title, subsequent non-empty lines are tagline (dimmed)
+ if (i === 0 || dLine.trim() === "") {
+ lines.push(` ${dLine}`);
+ } else {
+ lines.push(` ${dim(dLine)}`);
+ }
+ }
+ lines.push("");
+ }
+
+ // โโ Arguments panel (subcommands only) โโ
+ if (!isRoot) {
+ const visibleArgs = helper.visibleArguments(cmd);
+ if (visibleArgs.length > 0) {
+ const maxLen = Math.max(
+ ...visibleArgs.map((a: Argument) => a.name().length),
+ );
+ const argRows = visibleArgs.map((a: Argument) => {
+ const name = cyanBold(a.name().padEnd(maxLen));
+ const description = helper.argumentDescription(a);
+ return ` ${name} ${description}`;
+ });
+ const panel = renderPanel("Arguments", argRows, width);
+ if (panel) lines.push(panel);
+ }
+ }
+
+ // โโ Collect options (grouped into panels for subcommands) โโ
+ const visibleOpts = helper.visibleOptions(cmd);
+ const cmdName = cmd.name();
+ const panelMap =
+ !isRoot && OPTION_PANELS[cmdName] ? OPTION_PANELS[cmdName] : {};
+
+ const grouped: Record = { Options: [] };
+ for (const panelName of PANEL_ORDER) {
+ grouped[panelName] = [];
+ }
+
+ for (const opt of visibleOpts) {
+ const flag = getLongFlag(opt);
+ const panel = panelMap[flag];
+ if (panel && PANEL_ORDER.includes(panel)) {
+ grouped[panel].push(opt);
+ } else {
+ grouped.Options.push(opt);
+ }
+ }
+
+ // โโ Collect commands โโ
+ const visibleCmds = helper.visibleCommands(cmd);
+
+ if (isRoot) {
+ // ROOT: Options first, then command groups (matches Python/Typer ordering)
+ if (grouped.Options.length > 0) {
+ const optRows = formatOptionRows(grouped.Options);
+ const panel = renderPanel("Options", optRows, width);
+ if (panel) lines.push(panel);
+ }
+ if (visibleCmds.length > 0) {
+ const cmdMap = new Map(visibleCmds.map((c) => [c.name(), c]));
+ for (const group of COMMAND_GROUPS) {
+ const groupCmds = group.commands
+ .map((name) => cmdMap.get(name))
+ .filter((c): c is Command => c !== undefined);
+ if (groupCmds.length === 0) continue;
+ const maxLen = Math.max(...groupCmds.map((c) => c.name().length));
+ const cmdRows = groupCmds.map((c) => {
+ const name = cyanBold(c.name().padEnd(maxLen));
+ const description = helper.subcommandDescription(c);
+ return ` ${name} ${description}`;
+ });
+ const panel = renderPanel(group.panel, cmdRows, width);
+ if (panel) lines.push(panel);
+ }
+ }
+ } else {
+ // SUBCOMMANDS: Options/panels first, then sub-subcommands
+ const panelSequence = ["Options", ...PANEL_ORDER];
+ for (const panelName of panelSequence) {
+ const opts = grouped[panelName];
+ if (opts && opts.length > 0) {
+ const optRows = formatOptionRows(opts);
+ const panel = renderPanel(panelName, optRows, width);
+ if (panel) lines.push(panel);
+ }
+ }
+ // Sub-subcommands (e.g., config show/get/set, entity list/delete)
+ if (visibleCmds.length > 0) {
+ const maxLen = Math.max(...visibleCmds.map((c) => c.name().length));
+ const cmdRows = visibleCmds.map((c) => {
+ const name = cyanBold(c.name().padEnd(maxLen));
+ const description = helper.subcommandDescription(c);
+ return ` ${name} ${description}`;
+ });
+ const panel = renderPanel("Commands", cmdRows, width);
+ if (panel) lines.push(panel);
+ }
+ }
+
+ lines.push("");
+ return lines.join("\n");
+}
+
+// โโ Format option rows with aligned columns โโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+function formatOptionRows(opts: Option[]): string[] {
+ const terms = opts.map((o) => formatOptionTerm(o));
+ const maxTermLen = Math.max(...terms.map((t) => t.length));
+
+ return opts.map((opt, i) => {
+ const term = cyanBold(terms[i].padEnd(maxTermLen));
+ const desc = opt.description || "";
+ const def = formatDefault(opt);
+ return ` ${term} ${desc}${def}`;
+ });
+}
+
+// โโ Sort commands by COMMAND_ORDER โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+function sortCommands(cmds: Command[]): Command[] {
+ return [...cmds].sort((a, b) => {
+ const ai = COMMAND_ORDER.indexOf(a.name());
+ const bi = COMMAND_ORDER.indexOf(b.name());
+ // Unknown commands go to end, preserving original order
+ const aIdx = ai === -1 ? COMMAND_ORDER.length : ai;
+ const bIdx = bi === -1 ? COMMAND_ORDER.length : bi;
+ return aIdx - bIdx;
+ });
+}
diff --git a/cli/node/src/index.ts b/cli/node/src/index.ts
new file mode 100644
index 000000000..41de42cfd
--- /dev/null
+++ b/cli/node/src/index.ts
@@ -0,0 +1,795 @@
+#!/usr/bin/env node
+
+/**
+ * Main CLI application โ the entrypoint for `mem0`.
+ */
+
+import fs from "node:fs";
+import path from "node:path";
+import { fileURLToPath } from "node:url";
+import { Command } from "commander";
+import { AuthError, type Backend, getBackend } from "./backend/index.js";
+import { colors, printError, printWarning } from "./branding.js";
+import type { Mem0Config } from "./config.js";
+import { loadConfig, saveConfig } from "./config.js";
+import { richFormatHelp } from "./help.js";
+import { setAgentMode } from "./state.js";
+import { captureEvent } from "./telemetry.js";
+import { CLI_VERSION } from "./version.js";
+
+const program = new Command();
+
+// โโ Validated user identity (set by getBackendAndConfig) โโโโโโโโโโโโโโโโโ
+
+let _validatedUserEmail: string | undefined;
+
+// โโ Helpers โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+async function getBackendAndConfig(
+ apiKey?: string,
+ baseUrl?: string,
+): Promise<{ backend: Backend; config: Mem0Config }> {
+ const config = loadConfig();
+
+ if (apiKey) config.platform.apiKey = apiKey;
+ if (baseUrl) config.platform.baseUrl = baseUrl;
+
+ if (!config.platform.apiKey) {
+ printError(
+ "No API key configured.",
+ "Run 'mem0 init' or set MEM0_API_KEY environment variable.",
+ );
+ process.exit(1);
+ }
+
+ const backend = getBackend(config);
+
+ // Validate the API key upfront with a fast timeout
+ try {
+ const pingData = (await Promise.race([
+ backend.ping(),
+ new Promise((_, reject) =>
+ setTimeout(() => reject(new Error("timeout")), 5000),
+ ),
+ ])) as Record;
+
+ const email = pingData?.user_email as string | undefined;
+ if (email) {
+ _validatedUserEmail = email;
+ if (config.platform.userEmail !== email) {
+ config.platform.userEmail = email;
+ try {
+ saveConfig(config);
+ } catch {
+ /* ignore */
+ }
+ }
+ }
+ } catch (e) {
+ if (e instanceof AuthError) {
+ printError(
+ "Invalid or expired API key.",
+ "Run 'mem0 init' or set MEM0_API_KEY environment variable.",
+ );
+ process.exit(1);
+ }
+ // Network error / timeout โ warn but proceed
+ printWarning(
+ "Could not validate API key (network issue). Proceeding anyway.",
+ );
+ }
+
+ return { backend, config };
+}
+
+async function getBackendOnly(
+ apiKey?: string,
+ baseUrl?: string,
+): Promise {
+ return (await getBackendAndConfig(apiKey, baseUrl)).backend;
+}
+
+function checkAgentMode(): boolean {
+ const rootOpts = program.opts();
+ const isAgent = !!(rootOpts.json || rootOpts.agent);
+ if (isAgent) setAgentMode(true);
+ return isAgent;
+}
+
+/**
+ * Resolve entity IDs: CLI flag > config default > undefined.
+ *
+ * If any explicit ID is provided, only use explicit IDs (don't mix
+ * in defaults for other entity types which would over-filter).
+ * If no explicit IDs, fall back to all configured defaults.
+ */
+function resolveIds(
+ config: Mem0Config,
+ opts: {
+ userId?: string;
+ agentId?: string;
+ appId?: string;
+ runId?: string;
+ },
+): { userId?: string; agentId?: string; appId?: string; runId?: string } {
+ const hasExplicit = !!(
+ opts.userId ||
+ opts.agentId ||
+ opts.appId ||
+ opts.runId
+ );
+ if (hasExplicit) {
+ return {
+ userId: opts.userId || undefined,
+ agentId: opts.agentId || undefined,
+ appId: opts.appId || undefined,
+ runId: opts.runId || undefined,
+ };
+ }
+ return {
+ userId: config.defaults.userId || undefined,
+ agentId: config.defaults.agentId || undefined,
+ appId: config.defaults.appId || undefined,
+ runId: config.defaults.runId || undefined,
+ };
+}
+
+/**
+ * Resolve graph tri-state: --no-graph > --graph > config default.
+ */
+function resolveGraph(
+ config: Mem0Config,
+ opts: { graph?: boolean; noGraph?: boolean },
+): boolean {
+ if (opts.noGraph) return false;
+ if (opts.graph) return true;
+ return config.defaults.enableGraph;
+}
+
+// โโ Main program โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+program
+ .name("mem0")
+ .description(
+ `โ Mem0 CLI v${CLI_VERSION} ยท Node.js SDK\n\nThe Memory Layer for AI Agents`,
+ )
+ .option("--version", "Show version and exit.")
+ .on("option:version", () => {
+ console.log(` ${colors.brand("โ Mem0")} CLI v${CLI_VERSION}`);
+ process.exit(0);
+ })
+ .option("--json", "Output as JSON for agent/programmatic use.")
+ .option(
+ "--agent",
+ "Output as JSON for agent/programmatic use. (alias: --json)",
+ )
+ .usage(" [options]")
+ .helpOption("--help", "Show this message and exit.")
+ .addHelpCommand(false)
+ .configureHelp({ formatHelp: richFormatHelp });
+
+// โโ Telemetry hook โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+program.hook("preAction", (_thisCommand, actionCommand) => {
+ try {
+ const commandName = actionCommand.name();
+ const parentName = actionCommand.parent?.name();
+ const fullCommand =
+ parentName && parentName !== "mem0"
+ ? `${parentName}.${commandName}`
+ : commandName;
+ const isAgent = !!(program.opts().json || program.opts().agent);
+ captureEvent(
+ `cli.${fullCommand}`,
+ {
+ command: fullCommand,
+ is_agent: isAgent,
+ },
+ _validatedUserEmail,
+ );
+ } catch {
+ /* silently swallow */
+ }
+});
+
+// โโ Init โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+program
+ .command("init")
+ .description("Interactive setup wizard for mem0 CLI.")
+ .option("--api-key ", "API key (skip prompt).")
+ .option("-u, --user-id ", "Default user ID (skip prompt).")
+ .option("--email ", "Login via email verification code.")
+ .option(
+ "--code ",
+ "Verification code (use with --email for non-interactive login).",
+ )
+ .option("--force", "Overwrite existing config without confirmation.", false)
+ .addHelpText(
+ "after",
+ "\nExamples:\n $ mem0 init\n $ mem0 init --api-key m0-xxx --user-id alice\n $ mem0 init --email you@example.com\n $ mem0 init --email you@example.com --code 123456",
+ )
+ .action(async (opts) => {
+ const { runInit } = await import("./commands/init.js");
+ await runInit({
+ apiKey: opts.apiKey,
+ userId: opts.userId,
+ email: opts.email,
+ code: opts.code,
+ force: opts.force,
+ });
+ });
+
+// โโ Memory: add โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+program
+ .command("add [text]")
+ .description("Add a memory from text, messages, file, or stdin.")
+ .option("-u, --user-id ", "Scope to user.")
+ .option("--agent-id ", "Scope to agent.")
+ .option("--app-id ", "Scope to app.")
+ .option("--run-id ", "Scope to run.")
+ .option("--messages ", "Conversation messages as JSON.")
+ .option("-f, --file ", "Read messages from JSON file.")
+ .option("-m, --metadata ", "Custom metadata as JSON.")
+ .option("--immutable", "Prevent future updates.", false)
+ .option("--no-infer", "Skip inference, store raw.")
+ .option("--expires ", "Expiration date (YYYY-MM-DD).")
+ .option("--categories ", "Categories (JSON array or comma-separated).")
+ .option("--graph", "Enable graph memory extraction.", false)
+ .option("--no-graph", "Disable graph memory extraction.")
+ .option("-o, --output ", "Output format: text, json, quiet.", "text")
+ .option("--api-key ", "Override API key.")
+ .option("--base-url ", "Override API base URL.")
+ .addHelpText(
+ "after",
+ '\nExamples:\n $ mem0 add "I prefer dark mode" --user-id alice\n $ echo "text" | mem0 add -u alice\n $ mem0 add --file msgs.json -u alice -o json',
+ )
+ .action(async (text, opts) => {
+ const { cmdAdd } = await import("./commands/memory.js");
+ const isAgent = checkAgentMode();
+ const { backend, config } = await getBackendAndConfig(
+ opts.apiKey,
+ opts.baseUrl,
+ );
+ const ids = resolveIds(config, opts);
+ const enableGraph = resolveGraph(config, opts);
+ const output = isAgent ? "agent" : opts.output;
+ await cmdAdd(backend, text, { ...ids, ...opts, enableGraph, output });
+ });
+
+// โโ Memory: search โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+program
+ .command("search [query]")
+ .description(
+ "Query your memory store โ semantic, keyword, or hybrid retrieval.",
+ )
+ .option("-u, --user-id ", "Filter by user.")
+ .option("--agent-id ", "Filter by agent.")
+ .option("--app-id ", "Filter by app.")
+ .option("--run-id ", "Filter by run.")
+ .option(
+ "-k, --top-k ",
+ "Number of results.",
+ (v) => Number.parseInt(v),
+ 10,
+ )
+ .option(
+ "--threshold ",
+ "Minimum similarity score.",
+ (v) => Number.parseFloat(v),
+ 0.3,
+ )
+ .option("--rerank", "Enable reranking (Platform only).", false)
+ .option("--keyword", "Use keyword search.", false)
+ .option("--filter ", "Advanced filter expression (JSON).")
+ .option("--fields ", "Specific fields to return (comma-separated).")
+ .option("--graph", "Enable graph in search.", false)
+ .option("--no-graph", "Disable graph in search.")
+ .option("-o, --output ", "Output: text, json, table.", "text")
+ .option("--api-key ", "Override API key.")
+ .option("--base-url ", "Override API base URL.")
+ .addHelpText(
+ "after",
+ '\nExamples:\n $ mem0 search "preferences" --user-id alice\n $ mem0 search "tools" -u alice -o json -k 5\n $ echo "preferences" | mem0 search -u alice',
+ )
+ .action(async (query, opts) => {
+ let resolvedQuery = query;
+ if (!resolvedQuery && !process.stdin.isTTY) {
+ resolvedQuery = fs.readFileSync(0, "utf-8").trim();
+ }
+ if (!resolvedQuery) {
+ printError("No query provided. Pass a query argument or pipe via stdin.");
+ process.exit(1);
+ }
+ const { cmdSearch } = await import("./commands/memory.js");
+ const isAgent = checkAgentMode();
+ const { backend, config } = await getBackendAndConfig(
+ opts.apiKey,
+ opts.baseUrl,
+ );
+ const ids = resolveIds(config, opts);
+ const enableGraph = resolveGraph(config, opts);
+ const output = isAgent ? "agent" : opts.output;
+ await cmdSearch(backend, resolvedQuery, {
+ ...ids,
+ topK: opts.topK,
+ threshold: opts.threshold,
+ rerank: opts.rerank,
+ keyword: opts.keyword,
+ filterJson: opts.filter,
+ fields: opts.fields,
+ enableGraph,
+ output,
+ });
+ });
+
+// โโ Memory: get โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+program
+ .command("get ")
+ .description("Get a specific memory by ID.")
+ .option("-o, --output ", "Output: text, json.", "text")
+ .option("--api-key ", "Override API key.")
+ .option("--base-url ", "Override API base URL.")
+ .addHelpText(
+ "after",
+ "\nExamples:\n $ mem0 get abc-123-def-456\n $ mem0 get abc-123-def-456 -o json",
+ )
+ .action(async (memoryId, opts) => {
+ const { cmdGet } = await import("./commands/memory.js");
+ const isAgent = checkAgentMode();
+ const backend = await getBackendOnly(opts.apiKey, opts.baseUrl);
+ const output = isAgent ? "agent" : opts.output;
+ await cmdGet(backend, memoryId, { output });
+ });
+
+// โโ Memory: list โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+program
+ .command("list")
+ .description("List memories with optional filters.")
+ .option("-u, --user-id ", "Filter by user.")
+ .option("--agent-id ", "Filter by agent.")
+ .option("--app-id ", "Filter by app.")
+ .option("--run-id ", "Filter by run.")
+ .option("--page ", "Page number.", (v) => Number.parseInt(v), 1)
+ .option(
+ "--page-size ",
+ "Results per page.",
+ (v) => Number.parseInt(v),
+ 100,
+ )
+ .option("--category ", "Filter by category.")
+ .option("--after ", "Created after (YYYY-MM-DD).")
+ .option("--before ", "Created before (YYYY-MM-DD).")
+ .option("--graph", "Enable graph in listing.", false)
+ .option("--no-graph", "Disable graph in listing.")
+ .option("-o, --output ", "Output: text, json, table.", "table")
+ .option("--api-key ", "Override API key.")
+ .option("--base-url ", "Override API base URL.")
+ .addHelpText(
+ "after",
+ "\nExamples:\n $ mem0 list -u alice\n $ mem0 list --category prefs --after 2024-01-01 -o json",
+ )
+ .action(async (opts) => {
+ const { cmdList } = await import("./commands/memory.js");
+ const isAgent = checkAgentMode();
+ const { backend, config } = await getBackendAndConfig(
+ opts.apiKey,
+ opts.baseUrl,
+ );
+ const ids = resolveIds(config, opts);
+ const enableGraph = resolveGraph(config, opts);
+ const output = isAgent ? "agent" : opts.output;
+ await cmdList(backend, {
+ ...ids,
+ page: opts.page,
+ pageSize: opts.pageSize,
+ category: opts.category,
+ after: opts.after,
+ before: opts.before,
+ enableGraph,
+ output,
+ });
+ });
+
+// โโ Memory: update โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+program
+ .command("update [text]")
+ .description("Update a memory's text or metadata.")
+ .option("-m, --metadata ", "Update metadata (JSON).")
+ .option("-o, --output ", "Output: text, json, quiet.", "text")
+ .option("--api-key ", "Override API key.")
+ .option("--base-url ", "Override API base URL.")
+ .addHelpText(
+ "after",
+ `\nExamples:\n $ mem0 update abc-123 "new text"\n $ mem0 update abc-123 --metadata '{"key":"val"}'\n $ echo "new text" | mem0 update abc-123`,
+ )
+ .action(async (memoryId, text, opts) => {
+ let resolvedText = text;
+ if (!resolvedText && !opts.metadata && !process.stdin.isTTY) {
+ resolvedText = fs.readFileSync(0, "utf-8").trim();
+ }
+ const { cmdUpdate } = await import("./commands/memory.js");
+ const isAgent = checkAgentMode();
+ const backend = await getBackendOnly(opts.apiKey, opts.baseUrl);
+ const output = isAgent ? "agent" : opts.output;
+ await cmdUpdate(backend, memoryId, resolvedText, {
+ metadata: opts.metadata,
+ output,
+ });
+ });
+
+// โโ Memory: delete (consolidated) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+program
+ .command("delete [memoryId]")
+ .description("Delete a memory, all memories matching a scope, or an entity.")
+ .option("--all", "Delete all memories matching scope filters.", false)
+ .option(
+ "--entity",
+ "Delete the entity itself and all its memories (cascade).",
+ false,
+ )
+ .option("--project", "With --all: delete ALL memories project-wide.", false)
+ .option("--dry-run", "Show what would be deleted without deleting.", false)
+ .option("--force", "Skip confirmation.", false)
+ .option("-u, --user-id ", "Scope to user.")
+ .option("--agent-id ", "Scope to agent.")
+ .option("--app-id ", "Scope to app.")
+ .option("--run-id ", "Scope to run.")
+ .option("-o, --output ", "Output: text, json, quiet.", "text")
+ .option("--api-key ", "Override API key.")
+ .option("--base-url ", "Override API base URL.")
+ .addHelpText(
+ "after",
+ [
+ "\nExamples:",
+ " $ mem0 delete abc-123-def-456 # single memory",
+ " $ mem0 delete --all -u alice --force # all memories for user",
+ " $ mem0 delete --all --project --force # project-wide wipe",
+ " $ mem0 delete --entity -u alice --force # entity + all its memories",
+ ].join("\n"),
+ )
+ .action(async (memoryId, opts) => {
+ const isAgent = checkAgentMode();
+ const output = isAgent ? "agent" : opts.output;
+ // โโ Mutual-exclusion checks โโ
+ if (memoryId && opts.all) {
+ printError("Cannot combine with --all. Use one or the other.");
+ process.exit(1);
+ }
+ if (memoryId && opts.entity) {
+ printError(
+ "Cannot combine