From 21aae599beb6a9743b357709dd654fa5ceaee77e Mon Sep 17 00:00:00 2001 From: Kartik Date: Mon, 3 Aug 2026 21:29:47 +0530 Subject: [PATCH] feat(cli): remove mem0 version subcommand from md files and fix help --json in the Python CLI (#6773) --- cli/README.md | 3 +-- cli/node/README.md | 10 ++------ cli/node/development.md | 2 +- cli/node/src/commands/utils.ts | 7 +----- cli/node/src/index.ts | 4 +-- cli/node/tests/cli-integration.test.ts | 24 ++++++++++++------ cli/node/tests/option-parity.test.ts | 32 +++++++++++++++++++++++- cli/python/README.md | 10 ++------ cli/python/development.md | 2 +- cli/python/src/mem0_cli/app.py | 6 +++-- cli/python/tests/test_cli_integration.py | 26 +++++++++++++++++++ cli/python/tests/test_option_parity.py | 26 ++++++++++++++++++- 12 files changed, 113 insertions(+), 39 deletions(-) diff --git a/cli/README.md b/cli/README.md index d411daa65..2bce6aad6 100644 --- a/cli/README.md +++ b/cli/README.md @@ -60,9 +60,8 @@ mem0 delete | `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. +Run `mem0 --help` for detailed usage on any command, or `mem0 --version` to print the CLI version. ## Agent mode diff --git a/cli/node/README.md b/cli/node/README.md index 6110d1149..dd96b6515 100644 --- a/cli/node/README.md +++ b/cli/node/README.md @@ -232,14 +232,6 @@ Verify your API connection and display the current project. 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: @@ -298,6 +290,8 @@ These flags are available on all commands: | `--base-url` | Override the configured API base URL for this request | | `-o, --output` | Set the output format | +`mem0 --version` prints the CLI version. It is only valid before a subcommand, not after one. + ## Environment variables | Variable | Description | diff --git a/cli/node/development.md b/cli/node/development.md index 5b5d0b060..38317253f 100644 --- a/cli/node/development.md +++ b/cli/node/development.md @@ -51,7 +51,7 @@ pnpm link --global # Now use it like a normal CLI mem0 --help -mem0 version +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`. diff --git a/cli/node/src/commands/utils.ts b/cli/node/src/commands/utils.ts index 1e12d231a..354200d71 100644 --- a/cli/node/src/commands/utils.ts +++ b/cli/node/src/commands/utils.ts @@ -1,5 +1,5 @@ /** - * Utility commands: status, version, import. + * Utility commands: status, import. */ import fs from "node:fs"; @@ -8,7 +8,6 @@ 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; @@ -82,10 +81,6 @@ export async function cmdStatus( console.log(); } -export function cmdVersion(): void { - console.log(` ${brand("◆ Mem0")} CLI v${CLI_VERSION}`); -} - export async function cmdImport( backend: Backend, filePath: string, diff --git a/cli/node/src/index.ts b/cli/node/src/index.ts index dfe46910f..d102b5a33 100644 --- a/cli/node/src/index.ts +++ b/cli/node/src/index.ts @@ -858,8 +858,8 @@ program .addHelpText("after", "\nExamples:\n $ mem0 help\n $ mem0 help --json") .action((opts) => { // opts.json is set when `mem0 help --json` is used (subcommand flag). - // program.opts().json is set when the root --json global flag was used first. - if (opts.json || program.opts().json) { + // program.opts().json/.agent is set when a root global flag was used first. + if (opts.json || program.opts().json || program.opts().agent) { // Load spec from parent directory const __dirname = path.dirname(fileURLToPath(import.meta.url)); const specPath = path.join(__dirname, "..", "..", "cli-spec.json"); diff --git a/cli/node/tests/cli-integration.test.ts b/cli/node/tests/cli-integration.test.ts index 630cfec4b..285a8abc8 100644 --- a/cli/node/tests/cli-integration.test.ts +++ b/cli/node/tests/cli-integration.test.ts @@ -44,15 +44,25 @@ describe("CLI Integration — help and version", () => { expect(result.stdout).toContain("search"); }); - it("help --json produces valid JSON", () => { - const result = run(["help", "--json"]); - expect(result.exitCode).toBe(0); - const parsed = JSON.parse(result.stdout); - // spec may have cli.name or top-level name - const name = parsed.name ?? parsed.cli?.name; - expect(name).toBe("mem0"); + it("prints the version with --version, and has no version subcommand", () => { + const flag = run(["--version"]); + expect(flag.exitCode).toBe(0); + expect(flag.stdout).toContain("Mem0"); + expect(run(["version"]).exitCode).not.toBe(0); }); + it.each([["help", "--json"], ["--json", "help"], ["--agent", "help"]])( + "%s %s produces valid JSON", + (...args) => { + const result = run(args); + expect(result.exitCode).toBe(0); + const parsed = JSON.parse(result.stdout); + // spec may have cli.name or top-level name + const name = parsed.name ?? parsed.cli?.name; + expect(name).toBe("mem0"); + }, + ); + it("shows add help", () => { const result = run(["add", "--help"]); expect(result.exitCode).toBe(0); diff --git a/cli/node/tests/option-parity.test.ts b/cli/node/tests/option-parity.test.ts index 72f46a5f2..4a0ff9b90 100644 --- a/cli/node/tests/option-parity.test.ts +++ b/cli/node/tests/option-parity.test.ts @@ -16,18 +16,45 @@ const OPENAPI_PATH = path.join( "openapi.json", ); -const KNOWN_UNSURFACED: Record> = {}; +const KNOWN_UNSURFACED: Record> = { + "/v3/memories/add/": { + includes: "extraction hint, no CLI flag yet", + excludes: "extraction hint, no CLI flag yet", + enable_graph: "graph memory toggle, no CLI flag yet", + output_format: "response envelope is pinned by the CLI", + prompt_profile_id: "no CLI flag yet", + temporal_reasoning: "no CLI flag yet", + timezone: "no CLI flag yet", + observation_datetime: "no CLI flag yet, --timestamp backdates instead", + observation_date: "no CLI flag yet, --timestamp backdates instead", + }, + "/v3/memories/search/": { + categories: "expressible through --filter", + metadata: "expressible through --filter", + }, + "/v3/memories/": { + start_date: "covered by --after via filters.created_at.gte", + end_date: "covered by --before via filters.created_at.lte", + categories: "covered by --category via filters.categories", + fields: "no CLI flag yet", + keywords: "no CLI flag yet", + }, +}; const ADD_MAPPING: Record = { messages: ["--messages", "--file", "text"], user_id: ["--user-id"], agent_id: ["--agent-id"], + app_id: ["--app-id"], run_id: ["--run-id"], metadata: ["--metadata"], expiration_date: ["--expires"], custom_instructions: ["--custom-instructions"], custom_categories: ["--custom-categories"], infer: ["--no-infer"], + immutable: ["--immutable"], + structured_data_schema: ["--structured-data-schema"], + timestamp: ["--timestamp"], }; const SEARCH_MAPPING: Record = { @@ -38,6 +65,7 @@ const SEARCH_MAPPING: Record = { threshold: ["--threshold"], rerank: ["--rerank"], reference_date: ["--reference-date"], + fields: ["--fields"], }; const LIST_MAPPING: Record = { @@ -50,6 +78,8 @@ const LIST_MAPPING: Record = { "--before", ], show_expired: ["--show-expired"], + page: ["--page"], + page_size: ["--page-size"], }; function documentedFields(endpoint: string): string[] { diff --git a/cli/python/README.md b/cli/python/README.md index a6ac0beb1..8cb0aa29d 100644 --- a/cli/python/README.md +++ b/cli/python/README.md @@ -241,14 +241,6 @@ Verify your API connection and display the current project. 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: @@ -307,6 +299,8 @@ These flags are available on all commands: | `--base-url` | Override the configured API base URL for this request | | `-o, --output` | Set the output format | +`mem0 --version` prints the CLI version. It is only valid before a subcommand, not after one. + ## Environment variables | Variable | Description | diff --git a/cli/python/development.md b/cli/python/development.md index e05a2b062..bf77594a5 100644 --- a/cli/python/development.md +++ b/cli/python/development.md @@ -80,7 +80,7 @@ mem0 --help # Using Python directly (with venv activated) source .venv/bin/activate mem0 --help -mem0 version +mem0 --version # Or run without activating .venv/bin/mem0 --help diff --git a/cli/python/src/mem0_cli/app.py b/cli/python/src/mem0_cli/app.py index 9eb4f58ef..d3258bf6a 100644 --- a/cli/python/src/mem0_cli/app.py +++ b/cli/python/src/mem0_cli/app.py @@ -1350,8 +1350,10 @@ def help( mem0 help mem0 help --json """ - if json: - console.print(_json.dumps(_build_help_json(), indent=2)) + from mem0_cli.state import is_agent_mode + + if json or is_agent_mode(): + console.print_json(_json.dumps(_build_help_json())) else: console.print( f"[{BRAND_COLOR}]◆ mem0 CLI[/] v{__version__} — The Memory Layer for AI Agents\n" diff --git a/cli/python/tests/test_cli_integration.py b/cli/python/tests/test_cli_integration.py index 8abd145d0..fb2b9a029 100644 --- a/cli/python/tests/test_cli_integration.py +++ b/cli/python/tests/test_cli_integration.py @@ -7,6 +7,7 @@ boundaries). from __future__ import annotations +import json import os import re import subprocess @@ -84,6 +85,31 @@ class TestCLIIntegration: assert "add" in result.stdout assert "search" in result.stdout + def test_version_flag_only(self): + from mem0_cli import __version__ + + flag = _run(["--version"]) + assert flag.returncode == 0 + assert __version__ in flag.stdout + assert _run(["version"]).returncode != 0 + + @pytest.mark.parametrize( + "args", + [["help", "--json"], ["--json", "help"], ["help", "--agent"], ["--agent", "help"]], + ) + def test_help_json_produces_valid_json(self, args): + result = _run(args) + assert result.returncode == 0 + spec = json.loads(result.stdout) + assert spec["name"] == "mem0" + assert "add" in spec["commands"] + + def test_help_without_json_is_text(self): + result = _run(["help"]) + assert result.returncode == 0 + with pytest.raises(json.JSONDecodeError): + json.loads(result.stdout) + def test_add_help(self): result = _run(["add", "--help"]) assert result.returncode == 0 diff --git a/cli/python/tests/test_option_parity.py b/cli/python/tests/test_option_parity.py index 191cb32f4..e7bc575f3 100644 --- a/cli/python/tests/test_option_parity.py +++ b/cli/python/tests/test_option_parity.py @@ -10,18 +10,39 @@ from mem0_cli.app import app REPO_ROOT = Path(__file__).resolve().parents[3] OPENAPI_PATH = REPO_ROOT / "docs" / "openapi.json" -KNOWN_UNSURFACED: dict[tuple[str, str], str] = {} +KNOWN_UNSURFACED: dict[tuple[str, str], str] = { + ("/v3/memories/add/", "includes"): "extraction hint, no CLI flag yet", + ("/v3/memories/add/", "excludes"): "extraction hint, no CLI flag yet", + ("/v3/memories/add/", "enable_graph"): "graph memory toggle, no CLI flag yet", + ("/v3/memories/add/", "output_format"): "response envelope is pinned by the CLI", + ("/v3/memories/add/", "prompt_profile_id"): "no CLI flag yet", + ("/v3/memories/add/", "temporal_reasoning"): "no CLI flag yet", + ("/v3/memories/add/", "timezone"): "no CLI flag yet", + ("/v3/memories/add/", "observation_datetime"): "no CLI flag yet, --timestamp backdates instead", + ("/v3/memories/add/", "observation_date"): "no CLI flag yet, --timestamp backdates instead", + ("/v3/memories/search/", "categories"): "expressible through --filter", + ("/v3/memories/search/", "metadata"): "expressible through --filter", + ("/v3/memories/", "start_date"): "covered by --after via filters.created_at.gte", + ("/v3/memories/", "end_date"): "covered by --before via filters.created_at.lte", + ("/v3/memories/", "categories"): "covered by --category via filters.categories", + ("/v3/memories/", "fields"): "no CLI flag yet", + ("/v3/memories/", "keywords"): "no CLI flag yet", +} ADD_MAPPING: dict[str, list[str]] = { "messages": ["messages", "file", "text"], "user_id": ["user_id"], "agent_id": ["agent_id"], + "app_id": ["app_id"], "run_id": ["run_id"], "metadata": ["metadata"], "expiration_date": ["expires"], "custom_instructions": ["custom_instructions"], "custom_categories": ["custom_categories"], "infer": ["no_infer"], + "immutable": ["immutable"], + "structured_data_schema": ["structured_data_schema"], + "timestamp": ["timestamp"], } SEARCH_MAPPING: dict[str, list[str]] = { @@ -32,11 +53,14 @@ SEARCH_MAPPING: dict[str, list[str]] = { "threshold": ["threshold"], "rerank": ["rerank"], "reference_date": ["reference_date"], + "fields": ["fields"], } LIST_MAPPING: dict[str, list[str]] = { "filters": ["user_id", "agent_id", "run_id", "category", "after", "before"], "show_expired": ["show_expired"], + "page": ["page"], + "page_size": ["page_size"], }