feat(cli): add a version subcommand and document the --filter JSON shape (#6907)
This commit is contained in:
+10
-1
@@ -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",
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
+17
-3
@@ -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 <json>", "Advanced filter expression (JSON).")
|
||||
.option(
|
||||
"--filter <json>",
|
||||
'Advanced filter as JSON: {"AND": [...]} or {"OR": [...]}, e.g. {"AND": [{"categories": {"in": ["work"]}}]}.',
|
||||
)
|
||||
.option("--fields <list>", "Specific fields to return (comma-separated).")
|
||||
.option("--show-expired", "Include expired memories.", false)
|
||||
.option(
|
||||
@@ -404,7 +411,7 @@ program
|
||||
.option("--base-url <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 <filePath>")
|
||||
.description("Import memories from a JSON file.")
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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).",
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user