chore: merge origin/main into mintlify/af82bda4 and resolve conflicts
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
# Python CLI (`cli/python/`)
|
||||
|
||||
The `mem0-cli` package on PyPI. Typer-based, entry point `mem0`.
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
pip install -e ".[dev]" # dev install: ruff + pytest
|
||||
ruff check . # lint
|
||||
ruff format . # format
|
||||
pytest # test
|
||||
hatch build # build
|
||||
```
|
||||
|
||||
## Conventions
|
||||
|
||||
> **Line length is 100 here, not 120.** The root Python SDK uses 120. Running the root
|
||||
> `make format` over this directory reformats every file and fails CI. Use the local
|
||||
> `ruff` invocations above.
|
||||
|
||||
- **Python 3.10+.** Not 3.9, unlike the root SDK.
|
||||
- **Ruff** with an extended rule set: `E`, `F`, `I`, `W`, `UP`, `B`, `SIM`, `RUF`.
|
||||
Ignores `E501` (formatter handles it), `B008` (required by Typer's argument defaults),
|
||||
and `SIM108`.
|
||||
- **Ruff format:** double quotes, space indent, `docstring-code-format = true`.
|
||||
- **isort** first-party is `mem0_cli` only.
|
||||
- **pytest** for tests.
|
||||
- Target version pinned to `py310`.
|
||||
|
||||
## Layout
|
||||
|
||||
```
|
||||
cli/python/
|
||||
├── src/mem0_cli/ package source (src layout)
|
||||
└── tests/
|
||||
```
|
||||
|
||||
Entry point: `mem0 = "mem0_cli.app:main"`.
|
||||
|
||||
## Dependencies
|
||||
|
||||
Typer + Rich + httpx. `mem0ai` is **optional**, exposed through the `[oss]` extra for OSS mode. Do not promote it to a required dependency.
|
||||
|
||||
## CI and release
|
||||
|
||||
- CI: `cli-python-ci.yml`, ruff + pytest + `hatch build` on Python 3.10, 3.11, 3.12.
|
||||
- Release: tag prefix `cli-v*` dispatches `cli-python-cd.yml`, publishing to PyPI over OIDC.
|
||||
@@ -0,0 +1 @@
|
||||
AGENTS.md
|
||||
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "mem0-cli"
|
||||
version = "0.2.10"
|
||||
version = "0.2.12"
|
||||
description = "The official CLI for mem0 — the memory layer for AI agents"
|
||||
readme = "README.md"
|
||||
license = "Apache-2.0"
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
"""mem0 CLI — the command-line interface for the mem0 memory layer."""
|
||||
|
||||
__version__ = "0.2.10"
|
||||
__version__ = "0.2.12"
|
||||
|
||||
@@ -278,6 +278,11 @@ def add(
|
||||
custom_instructions: str | None = typer.Option(
|
||||
None, "--custom-instructions", help="Custom instructions for fact extraction."
|
||||
),
|
||||
agent_custom_instructions: str | None = typer.Option(
|
||||
None,
|
||||
"--agent-custom-instructions",
|
||||
help="Extraction instructions for agent-scoped memories, overriding the project setting.",
|
||||
),
|
||||
custom_categories: str | None = typer.Option(
|
||||
None,
|
||||
"--custom-categories",
|
||||
@@ -327,6 +332,7 @@ def add(
|
||||
expires=expires,
|
||||
categories=categories,
|
||||
custom_instructions=custom_instructions,
|
||||
agent_custom_instructions=agent_custom_instructions,
|
||||
custom_categories=custom_categories,
|
||||
structured_data_schema=structured_data_schema,
|
||||
timestamp=timestamp,
|
||||
@@ -365,7 +371,11 @@ def search(
|
||||
False, "--keyword", help="Use keyword search.", rich_help_panel="Search"
|
||||
),
|
||||
filter_json: str | None = typer.Option(
|
||||
None, "--filter", help="Advanced filter expression (JSON).", rich_help_panel="Search"
|
||||
None,
|
||||
"--filter",
|
||||
help='Advanced filter as JSON: {"AND": [...]} or {"OR": [...]}, '
|
||||
'e.g. {"AND": [{"categories": {"in": ["work"]}}]}.',
|
||||
rich_help_panel="Search",
|
||||
),
|
||||
fields: str | None = typer.Option(
|
||||
None,
|
||||
@@ -408,6 +418,7 @@ def search(
|
||||
mem0 search "preferences" --user-id alice
|
||||
mem0 search "tools" -u alice -o json -k 5
|
||||
echo "preferences" | mem0 search -u alice
|
||||
mem0 search "invoices" -u alice --filter '{"AND": [{"categories": {"in": ["work"]}}]}'
|
||||
"""
|
||||
from mem0_cli.commands.memory import cmd_search
|
||||
|
||||
@@ -1078,6 +1089,18 @@ def status(
|
||||
)
|
||||
|
||||
|
||||
@app.command(rich_help_panel="Management")
|
||||
def version() -> None:
|
||||
"""Show version and exit.
|
||||
|
||||
Example:
|
||||
mem0 version
|
||||
"""
|
||||
from mem0_cli.commands.utils import cmd_version
|
||||
|
||||
cmd_version()
|
||||
|
||||
|
||||
@app.command("import", rich_help_panel="Management")
|
||||
def import_cmd(
|
||||
file_path: str = typer.Argument(..., help="JSON file to import."),
|
||||
@@ -1139,6 +1162,7 @@ def _build_help_json() -> dict:
|
||||
"--expires": "Expiration date (YYYY-MM-DD).",
|
||||
"--categories": "Not supported on add, use --custom-categories instead.",
|
||||
"--custom-instructions": "Custom instructions for fact extraction.",
|
||||
"--agent-custom-instructions": "Extraction instructions for agent-scoped memories, overriding the project setting.",
|
||||
"--custom-categories": "Custom categories as a JSON array of {name: description} objects.",
|
||||
"--structured-data-schema": "Schema for structured data extraction, as JSON.",
|
||||
"--timestamp": "Unix timestamp for the memory.",
|
||||
@@ -1158,7 +1182,10 @@ def _build_help_json() -> dict:
|
||||
"--threshold": "Minimum similarity score (default: 0.3).",
|
||||
"--rerank": "Enable reranking (Platform only).",
|
||||
"--keyword": "Use keyword search instead of semantic.",
|
||||
"--filter": "Advanced filter expression (JSON).",
|
||||
"--filter": (
|
||||
'Advanced filter as JSON: {"AND": [...]} or {"OR": [...]}, '
|
||||
'e.g. {"AND": [{"categories": {"in": ["work"]}}]}.'
|
||||
),
|
||||
"--fields": "Specific fields to return (comma-separated).",
|
||||
"--show-expired": "Include expired memories.",
|
||||
"--reference-date": "Reference date for relative queries (YYYY-MM-DD or unix timestamp).",
|
||||
|
||||
@@ -26,6 +26,7 @@ class Backend(ABC):
|
||||
infer: bool = True,
|
||||
expires: str | None = None,
|
||||
custom_instructions: str | None = None,
|
||||
agent_custom_instructions: str | None = None,
|
||||
custom_categories: list[dict] | None = None,
|
||||
structured_data_schema: dict | None = None,
|
||||
timestamp: int | None = None,
|
||||
|
||||
@@ -88,6 +88,7 @@ class PlatformBackend(Backend):
|
||||
infer: bool = True,
|
||||
expires: str | None = None,
|
||||
custom_instructions: str | None = None,
|
||||
agent_custom_instructions: str | None = None,
|
||||
custom_categories: list[dict] | None = None,
|
||||
structured_data_schema: dict | None = None,
|
||||
timestamp: int | None = None,
|
||||
@@ -117,6 +118,8 @@ class PlatformBackend(Backend):
|
||||
payload["expiration_date"] = expires
|
||||
if custom_instructions:
|
||||
payload["custom_instructions"] = custom_instructions
|
||||
if agent_custom_instructions:
|
||||
payload["agent_custom_instructions"] = agent_custom_instructions
|
||||
if custom_categories:
|
||||
payload["custom_categories"] = custom_categories
|
||||
if structured_data_schema:
|
||||
|
||||
@@ -77,6 +77,7 @@ def cmd_add(
|
||||
expires: str | None,
|
||||
categories: str | None,
|
||||
custom_instructions: str | None = None,
|
||||
agent_custom_instructions: str | None = None,
|
||||
custom_categories: str | None = None,
|
||||
structured_data_schema: str | None = None,
|
||||
timestamp: int | None = None,
|
||||
@@ -166,6 +167,7 @@ def cmd_add(
|
||||
infer=not no_infer,
|
||||
expires=expires,
|
||||
custom_instructions=custom_instructions,
|
||||
agent_custom_instructions=agent_custom_instructions,
|
||||
custom_categories=custom_cats,
|
||||
structured_data_schema=schema,
|
||||
timestamp=timestamp,
|
||||
|
||||
@@ -91,7 +91,12 @@ class TestCLIIntegration:
|
||||
flag = _run(["--version"])
|
||||
assert flag.returncode == 0
|
||||
assert __version__ in flag.stdout
|
||||
assert _run(["version"]).returncode != 0
|
||||
|
||||
def test_version_subcommand_matches_flag_byte_for_byte(self):
|
||||
flag = _run(["--version"])
|
||||
cmd = _run(["version"])
|
||||
assert cmd.returncode == 0
|
||||
assert cmd.stdout == flag.stdout
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"args",
|
||||
@@ -127,6 +132,12 @@ class TestCLIIntegration:
|
||||
assert result.returncode == 0
|
||||
assert "top-k" in result.stdout
|
||||
|
||||
def test_search_help_documents_filter_json_shape(self):
|
||||
result = _run(["search", "--help"])
|
||||
assert result.returncode == 0
|
||||
assert "AND" in result.stdout
|
||||
assert "categories" in result.stdout
|
||||
|
||||
def test_list_help(self):
|
||||
result = _run(["list", "--help"])
|
||||
assert result.returncode == 0
|
||||
|
||||
@@ -38,6 +38,7 @@ ADD_MAPPING: dict[str, list[str]] = {
|
||||
"metadata": ["metadata"],
|
||||
"expiration_date": ["expires"],
|
||||
"custom_instructions": ["custom_instructions"],
|
||||
"agent_custom_instructions": ["agent_custom_instructions"],
|
||||
"custom_categories": ["custom_categories"],
|
||||
"infer": ["no_infer"],
|
||||
"immutable": ["immutable"],
|
||||
|
||||
@@ -22,12 +22,14 @@ class TestAddOptions:
|
||||
metadata={"source": "test"},
|
||||
expires="2099-01-01",
|
||||
custom_instructions="Extract only preferences.",
|
||||
agent_custom_instructions="Extract only tool outcomes.",
|
||||
custom_categories=[{"prefs": "user preferences"}],
|
||||
structured_data_schema={"type": "object"},
|
||||
timestamp=1700000000,
|
||||
)
|
||||
payload = mock_request.call_args.kwargs["json"]
|
||||
assert payload["custom_instructions"] == "Extract only preferences."
|
||||
assert payload["agent_custom_instructions"] == "Extract only tool outcomes."
|
||||
assert payload["custom_categories"] == [{"prefs": "user preferences"}]
|
||||
assert payload["structured_data_schema"] == {"type": "object"}
|
||||
assert payload["timestamp"] == 1700000000
|
||||
@@ -40,6 +42,7 @@ class TestAddOptions:
|
||||
backend.add(content="hello", user_id="alice")
|
||||
payload = mock_request.call_args.kwargs["json"]
|
||||
assert "custom_instructions" not in payload
|
||||
assert "agent_custom_instructions" not in payload
|
||||
assert "custom_categories" not in payload
|
||||
assert "structured_data_schema" not in payload
|
||||
assert "timestamp" not in payload
|
||||
|
||||
Reference in New Issue
Block a user