From 02ff6c5595fa4c6dbb4db60ea04e6e2f7acd8be2 Mon Sep 17 00:00:00 2001 From: Kartik Date: Fri, 14 Aug 2026 16:55:12 +0530 Subject: [PATCH] feat(cli): add a version subcommand and document the --filter JSON shape (#6907) --- cli/node/src/help.ts | 11 ++++++++++- cli/node/src/index.ts | 20 +++++++++++++++++--- cli/node/tests/cli-integration.test.ts | 17 +++++++++++++++-- cli/python/src/mem0_cli/app.py | 24 ++++++++++++++++++++++-- cli/python/tests/test_cli_integration.py | 13 ++++++++++++- docs/platform/cli.mdx | 5 +++-- 6 files changed, 79 insertions(+), 11 deletions(-) diff --git a/cli/node/src/help.ts b/cli/node/src/help.ts index 4b014a50d..d89ca59f2 100644 --- a/cli/node/src/help.ts +++ b/cli/node/src/help.ts @@ -36,7 +36,16 @@ const COMMAND_GROUPS: { panel: string; commands: string[] }[] = [ }, { panel: "Management", - commands: ["init", "status", "import", "help", "entity", "event", "config"], + commands: [ + "init", + "status", + "version", + "import", + "help", + "entity", + "event", + "config", + ], }, ]; diff --git a/cli/node/src/index.ts b/cli/node/src/index.ts index bb6462a37..5639cd6b4 100644 --- a/cli/node/src/index.ts +++ b/cli/node/src/index.ts @@ -95,6 +95,10 @@ async function getBackendOnly( return (await getBackendAndConfig(apiKey, baseUrl)).backend; } +function printVersion(): void { + console.log(` ${colors.brand("◆ Mem0")} CLI v${CLI_VERSION}`); +} + function checkAgentMode(): boolean { const rootOpts = program.opts(); const isAgent = !!(rootOpts.json || rootOpts.agent); @@ -154,7 +158,7 @@ program .enablePositionalOptions() .option("--version", "Show version and exit.") .on("option:version", () => { - console.log(` ${colors.brand("◆ Mem0")} CLI v${CLI_VERSION}`); + printVersion(); process.exit(0); }) .option("--json", "Output as JSON for agent/programmatic use.") @@ -387,7 +391,10 @@ program ) .option("--rerank", "Enable reranking (Platform only).", false) .option("--keyword", "Use keyword search.", false) - .option("--filter ", "Advanced filter expression (JSON).") + .option( + "--filter ", + 'Advanced filter as JSON: {"AND": [...]} or {"OR": [...]}, e.g. {"AND": [{"categories": {"in": ["work"]}}]}.', + ) .option("--fields ", "Specific fields to return (comma-separated).") .option("--show-expired", "Include expired memories.", false) .option( @@ -404,7 +411,7 @@ program .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', + '\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\n $ mem0 search "invoices" -u alice --filter \'{"AND": [{"categories": {"in": ["work"]}}]}\'', ) .action(async (query, opts) => { let resolvedQuery = query; @@ -823,6 +830,13 @@ program }); }); +program + .command("version") + .description("Show version and exit.") + .action(() => { + printVersion(); + }); + program .command("import ") .description("Import memories from a JSON file.") diff --git a/cli/node/tests/cli-integration.test.ts b/cli/node/tests/cli-integration.test.ts index 285a8abc8..96d5178a4 100644 --- a/cli/node/tests/cli-integration.test.ts +++ b/cli/node/tests/cli-integration.test.ts @@ -44,11 +44,17 @@ describe("CLI Integration — help and version", () => { expect(result.stdout).toContain("search"); }); - it("prints the version with --version, and has no version subcommand", () => { + it("prints the version with --version", () => { const flag = run(["--version"]); expect(flag.exitCode).toBe(0); expect(flag.stdout).toContain("Mem0"); - expect(run(["version"]).exitCode).not.toBe(0); + }); + + it("version subcommand output matches --version output byte-for-byte", () => { + const flag = run(["--version"]); + const cmd = run(["version"]); + expect(cmd.exitCode).toBe(0); + expect(cmd.stdout).toBe(flag.stdout); }); it.each([["help", "--json"], ["--json", "help"], ["--agent", "help"]])( @@ -129,6 +135,13 @@ describe("CLI Integration — help and version", () => { expect(result.stdout).toContain("--rerank"); }); + it("search help documents the --filter JSON shape with an example", () => { + const result = run(["search", "--help"]); + expect(result.exitCode).toBe(0); + expect(result.stdout).toContain("AND"); + expect(result.stdout).toContain("categories"); + }); + it("list help has --category flag", () => { const result = run(["list", "--help"]); expect(result.exitCode).toBe(0); diff --git a/cli/python/src/mem0_cli/app.py b/cli/python/src/mem0_cli/app.py index 7bfa76dd9..fae19acc8 100644 --- a/cli/python/src/mem0_cli/app.py +++ b/cli/python/src/mem0_cli/app.py @@ -371,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, @@ -414,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 @@ -1084,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."), @@ -1165,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).", diff --git a/cli/python/tests/test_cli_integration.py b/cli/python/tests/test_cli_integration.py index fb2b9a029..e4396306b 100644 --- a/cli/python/tests/test_cli_integration.py +++ b/cli/python/tests/test_cli_integration.py @@ -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 diff --git a/docs/platform/cli.mdx b/docs/platform/cli.mdx index f2547997d..766737c18 100644 --- a/docs/platform/cli.mdx +++ b/docs/platform/cli.mdx @@ -143,6 +143,7 @@ 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 +mem0 search "invoices" --user-id alice --filter '{"AND": [{"categories": {"in": ["work"]}}]}' ``` | Flag | Description | @@ -155,7 +156,7 @@ mem0 search "preferred tools" --user-id alice --output json --top-k 5 | `--threshold` | Minimum similarity score (default: 0.3) | | `--rerank` | Enable reranking | | `--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` | Return only the named fields | | `--show-expired` | Include expired memories | | `--reference-date` | Reference date for relative queries (`YYYY-MM-DD` or Unix timestamp) | @@ -471,7 +472,7 @@ These two flags belong to `mem0` itself, so they go **before** the command name: |------|-------------| | `--json` | Enable agent mode: structured JSON envelope output, no colors or spinners | | `--agent` | Alias for `--json` | -| `--version` | Print the CLI version and exit | +| `--version` | Print the CLI version and exit. `mem0 version` does the same thing as a regular subcommand | On `init` only, `--agent` means something different. `mem0 init --agent` creates an Agent Mode account (see [Sign up as an agent](/platform/agent-signup)); it does not switch the output to JSON. To get JSON from `init`, put the flag first: `mem0 --json init`.