feat(cli): add event commands, --json/--agent flag, agent output sanitization, and edge-case hardening for CLI SDKs (#4649)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+137
-15
@@ -3,6 +3,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json as _json
|
||||
import os
|
||||
import stat as _stat_mod
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
@@ -43,7 +45,14 @@ entity_app = typer.Typer(
|
||||
no_args_is_help=True,
|
||||
rich_markup_mode="rich",
|
||||
)
|
||||
# entity_app registered after Memory commands to control panel ordering
|
||||
|
||||
event_app = typer.Typer(
|
||||
name="event",
|
||||
help="Inspect background processing events.",
|
||||
no_args_is_help=True,
|
||||
rich_markup_mode="rich",
|
||||
)
|
||||
# entity_app and event_app registered after Memory commands to control panel ordering
|
||||
|
||||
|
||||
# ── Helpers ───────────────────────────────────────────────────────────────
|
||||
@@ -114,9 +123,22 @@ def _resolve_ids(
|
||||
}
|
||||
|
||||
|
||||
def _stdin_is_piped() -> bool:
|
||||
"""Return True only when stdin is an actual pipe or file redirect — not a bare open fd."""
|
||||
from mem0_cli.state import is_agent_mode
|
||||
|
||||
if is_agent_mode():
|
||||
return False
|
||||
try:
|
||||
mode = os.fstat(sys.stdin.fileno()).st_mode
|
||||
return _stat_mod.S_ISFIFO(mode) or _stat_mod.S_ISREG(mode)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def _read_stdin() -> str | None:
|
||||
"""Read from stdin if it is piped (not a TTY)."""
|
||||
if not sys.stdin.isatty():
|
||||
"""Read from stdin if it is an actual pipe or file redirect (not a TTY, not agent mode)."""
|
||||
if _stdin_is_piped():
|
||||
return sys.stdin.read().strip() or None
|
||||
return None
|
||||
|
||||
@@ -128,7 +150,18 @@ def _read_stdin() -> str | None:
|
||||
def main_callback(
|
||||
ctx: typer.Context,
|
||||
version: bool = typer.Option(False, "--version", help="Show version and exit."),
|
||||
json_agent: bool = typer.Option(
|
||||
False,
|
||||
"--json",
|
||||
"--agent",
|
||||
help="Output as JSON for agent/programmatic use.",
|
||||
is_eager=False,
|
||||
),
|
||||
) -> None:
|
||||
if json_agent:
|
||||
from mem0_cli.state import set_agent_mode
|
||||
|
||||
set_agent_mode(True)
|
||||
if version:
|
||||
from mem0_cli.commands.utils import cmd_version
|
||||
|
||||
@@ -273,7 +306,7 @@ def search(
|
||||
None, "--base-url", help="Override API base URL.", rich_help_panel="Connection"
|
||||
),
|
||||
) -> None:
|
||||
"""Search memories by semantic query.
|
||||
"""Query your memory store — semantic, keyword, or hybrid retrieval.
|
||||
|
||||
Examples:
|
||||
mem0 search "preferences" --user-id alice
|
||||
@@ -702,6 +735,70 @@ def entity_delete(
|
||||
app.add_typer(entity_app, name="entity", rich_help_panel="Management")
|
||||
|
||||
|
||||
# ── Event subcommands ─────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@event_app.command("list")
|
||||
def event_list(
|
||||
output: str = typer.Option(
|
||||
"table", "--output", "-o", help="Output: table, json.", rich_help_panel="Output"
|
||||
),
|
||||
api_key: str | None = typer.Option(
|
||||
None,
|
||||
"--api-key",
|
||||
help="Override API key.",
|
||||
envvar="MEM0_API_KEY",
|
||||
rich_help_panel="Connection",
|
||||
),
|
||||
base_url: str | None = typer.Option(
|
||||
None, "--base-url", help="Override API base URL.", rich_help_panel="Connection"
|
||||
),
|
||||
) -> None:
|
||||
"""List recent background processing events.
|
||||
|
||||
Examples:
|
||||
mem0 event list
|
||||
mem0 event list -o json
|
||||
"""
|
||||
from mem0_cli.commands.events_cmd import cmd_event_list
|
||||
|
||||
backend = _get_backend(api_key, base_url)
|
||||
cmd_event_list(backend, output=output)
|
||||
|
||||
|
||||
@event_app.command("status")
|
||||
def event_status(
|
||||
event_id: str = typer.Argument(..., help="Event ID to inspect."),
|
||||
output: str = typer.Option(
|
||||
"text", "--output", "-o", help="Output: text, json.", rich_help_panel="Output"
|
||||
),
|
||||
api_key: str | None = typer.Option(
|
||||
None,
|
||||
"--api-key",
|
||||
help="Override API key.",
|
||||
envvar="MEM0_API_KEY",
|
||||
rich_help_panel="Connection",
|
||||
),
|
||||
base_url: str | None = typer.Option(
|
||||
None, "--base-url", help="Override API base URL.", rich_help_panel="Connection"
|
||||
),
|
||||
) -> None:
|
||||
"""Check the status of a specific background event.
|
||||
|
||||
Examples:
|
||||
mem0 event status <event-id>
|
||||
mem0 event status <event-id> -o json
|
||||
"""
|
||||
from mem0_cli.commands.events_cmd import cmd_event_status
|
||||
|
||||
backend = _get_backend(api_key, base_url)
|
||||
cmd_event_status(backend, event_id, output=output)
|
||||
|
||||
|
||||
# ── Event subgroup ──
|
||||
app.add_typer(event_app, name="event", rich_help_panel="Management")
|
||||
|
||||
|
||||
# ── Management commands ───────────────────────────────────────────────────
|
||||
|
||||
|
||||
@@ -715,6 +812,9 @@ def init(
|
||||
code: str | None = typer.Option(
|
||||
None, "--code", help="Verification code (use with --email for non-interactive login)."
|
||||
),
|
||||
force: bool = typer.Option(
|
||||
False, "--force", help="Overwrite existing config without confirmation."
|
||||
),
|
||||
) -> None:
|
||||
"""Interactive setup wizard for mem0 CLI.
|
||||
|
||||
@@ -726,7 +826,7 @@ def init(
|
||||
"""
|
||||
from mem0_cli.commands.init_cmd import run_init
|
||||
|
||||
run_init(api_key=api_key, user_id=user_id, email=email, code=code)
|
||||
run_init(api_key=api_key, user_id=user_id, email=email, code=code, force=force)
|
||||
|
||||
|
||||
# (entity_app registered at module level, below sub-group definitions)
|
||||
@@ -831,7 +931,7 @@ def _build_help_json() -> dict:
|
||||
},
|
||||
},
|
||||
"search": {
|
||||
"description": "Search memories by semantic query.",
|
||||
"description": "Query your memory store — semantic, keyword, or hybrid retrieval.",
|
||||
"usage": "mem0 search <query> [OPTIONS]",
|
||||
"arguments": {"query": {"description": "Search query.", "required": False}},
|
||||
"options": {
|
||||
@@ -935,6 +1035,24 @@ def _build_help_json() -> dict:
|
||||
"value": {"description": "Value to set.", "required": True},
|
||||
},
|
||||
},
|
||||
"event": {
|
||||
"description": "Inspect background processing events.",
|
||||
"subcommands": {
|
||||
"list": {
|
||||
"description": "List recent background processing events.",
|
||||
"usage": "mem0 event list [OPTIONS]",
|
||||
"options": {"--output, -o": "Output format: table, json."},
|
||||
},
|
||||
"status": {
|
||||
"description": "Check the status of a specific background event.",
|
||||
"usage": "mem0 event status <event_id> [OPTIONS]",
|
||||
"arguments": {
|
||||
"event_id": {"description": "Event ID to inspect.", "required": True}
|
||||
},
|
||||
"options": {"--output, -o": "Output format: text, json."},
|
||||
},
|
||||
},
|
||||
},
|
||||
"entity": {
|
||||
"description": "Manage entities.",
|
||||
"subcommands": {
|
||||
@@ -986,6 +1104,7 @@ def _build_help_json() -> dict:
|
||||
"global_options": {
|
||||
"--api-key": "Override API key (env: MEM0_API_KEY).",
|
||||
"--base-url": "Override API base URL.",
|
||||
"--json / --agent": "Output as JSON for agent/programmatic use.",
|
||||
"--help": "Show help for a command.",
|
||||
"--version": "Show version and exit.",
|
||||
},
|
||||
@@ -1015,7 +1134,7 @@ def help(
|
||||
console.print("Usage: mem0 <command> [OPTIONS]\n")
|
||||
console.print("[bold]Commands:[/]")
|
||||
console.print(" add Add a memory from text, messages, file, or stdin")
|
||||
console.print(" search Search memories by semantic query")
|
||||
console.print(" search Query your memory store (semantic, keyword, hybrid)")
|
||||
console.print(" get Get a specific memory by ID")
|
||||
console.print(" list List memories with optional filters")
|
||||
console.print(" update Update a memory's text or metadata")
|
||||
@@ -1023,6 +1142,7 @@ def help(
|
||||
console.print(" import Import memories from a JSON file")
|
||||
console.print(" config Manage configuration (show, get, set)")
|
||||
console.print(" entity Manage entities (list, delete)")
|
||||
console.print(" event Inspect background events (list, status)")
|
||||
console.print(" init Interactive setup wizard")
|
||||
console.print(" status Check connectivity and authentication")
|
||||
console.print()
|
||||
@@ -1031,14 +1151,6 @@ def help(
|
||||
console.print()
|
||||
|
||||
|
||||
@app.command(rich_help_panel="Utility")
|
||||
def version() -> None:
|
||||
"""Show version and exit."""
|
||||
from mem0_cli.commands.utils import cmd_version
|
||||
|
||||
cmd_version()
|
||||
|
||||
|
||||
# Register config subgroup here so it appears after help in Management panel
|
||||
app.add_typer(config_app, name="config", rich_help_panel="Management")
|
||||
|
||||
@@ -1047,4 +1159,14 @@ app.add_typer(config_app, name="config", rich_help_panel="Management")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
import sys
|
||||
|
||||
# Allow --json/--agent anywhere in the command line (not just before subcommand).
|
||||
_json_flags = {"--json", "--agent"}
|
||||
if any(a in _json_flags for a in sys.argv[1:]):
|
||||
from mem0_cli.state import set_agent_mode
|
||||
|
||||
set_agent_mode(True)
|
||||
sys.argv = [sys.argv[0]] + [a for a in sys.argv[1:] if a not in _json_flags]
|
||||
|
||||
app()
|
||||
|
||||
Reference in New Issue
Block a user