ci(cli): add CI pipelines for Node and Python CLI SDKs (#4640)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Saket Aryan
2026-03-31 22:30:31 +05:30
committed by GitHub
parent 9bebcf45f7
commit 32c1ccba5b
30 changed files with 3042 additions and 2457 deletions
+36 -18
View File
@@ -253,8 +253,12 @@ def search(
help="Specific fields to return (comma-separated).",
rich_help_panel="Search",
),
graph: bool = typer.Option(False, "--graph", help="Enable graph in search.", rich_help_panel="Search"),
no_graph: bool = typer.Option(False, "--no-graph", help="Disable graph in search.", rich_help_panel="Search"),
graph: bool = typer.Option(
False, "--graph", help="Enable graph in search.", rich_help_panel="Search"
),
no_graph: bool = typer.Option(
False, "--no-graph", help="Disable graph in search.", rich_help_panel="Search"
),
output: str = typer.Option(
"text", "--output", "-o", help="Output: text, json, table.", rich_help_panel="Output"
),
@@ -372,8 +376,12 @@ def list_cmd(
before: str | None = typer.Option(
None, "--before", help="Created before (YYYY-MM-DD).", rich_help_panel="Filters"
),
graph: bool = typer.Option(False, "--graph", help="Enable graph in listing.", rich_help_panel="Filters"),
no_graph: bool = typer.Option(False, "--no-graph", help="Disable graph in listing.", rich_help_panel="Filters"),
graph: bool = typer.Option(
False, "--graph", help="Enable graph in listing.", rich_help_panel="Filters"
),
no_graph: bool = typer.Option(
False, "--no-graph", help="Disable graph in listing.", rich_help_panel="Filters"
),
output: str = typer.Option(
"table", "--output", "-o", help="Output: text, json, table.", rich_help_panel="Output"
),
@@ -463,11 +471,19 @@ def update(
@app.command(rich_help_panel="Memory")
def delete(
memory_id: str | None = typer.Argument(None, help="Memory ID to delete (omit when using --all or --entity)."),
memory_id: str | None = typer.Argument(
None, help="Memory ID to delete (omit when using --all or --entity)."
),
all_: bool = typer.Option(False, "--all", help="Delete all memories matching scope filters."),
entity: bool = typer.Option(False, "--entity", help="Delete the entity itself and all its memories (cascade)."),
project: bool = typer.Option(False, "--project", help="With --all: delete ALL memories project-wide."),
dry_run: bool = typer.Option(False, "--dry-run", help="Show what would be deleted without deleting."),
entity: bool = typer.Option(
False, "--entity", help="Delete the entity itself and all its memories (cascade)."
),
project: bool = typer.Option(
False, "--project", help="With --all: delete ALL memories project-wide."
),
dry_run: bool = typer.Option(
False, "--dry-run", help="Show what would be deleted without deleting."
),
force: bool = typer.Option(False, "--force", help="Skip confirmation."),
user_id: str | None = typer.Option(
None, "--user-id", "-u", help="Scope to user.", rich_help_panel="Scope"
@@ -641,14 +657,12 @@ def entity_delete(
agent_id: str | None = typer.Option(
None, "--agent-id", help="Agent ID.", rich_help_panel="Scope"
),
app_id: str | None = typer.Option(
None, "--app-id", help="App ID.", rich_help_panel="Scope"
),
run_id: str | None = typer.Option(
None, "--run-id", help="Run ID.", rich_help_panel="Scope"
),
app_id: str | None = typer.Option(None, "--app-id", help="App ID.", rich_help_panel="Scope"),
run_id: str | None = typer.Option(None, "--run-id", help="Run ID.", rich_help_panel="Scope"),
force: bool = typer.Option(False, "--force", help="Skip confirmation."),
dry_run: bool = typer.Option(False, "--dry-run", help="Show what would be deleted without deleting."),
dry_run: bool = typer.Option(
False, "--dry-run", help="Show what would be deleted without deleting."
),
output: str = typer.Option(
"text", "--output", "-o", help="Output: text, json, quiet.", rich_help_panel="Output"
),
@@ -694,9 +708,13 @@ app.add_typer(entity_app, name="entity", rich_help_panel="Management")
@app.command(rich_help_panel="Management")
def init(
api_key: str | None = typer.Option(None, "--api-key", help="API key (skip prompt)."),
user_id: str | None = typer.Option(None, "--user-id", "-u", help="Default user ID (skip prompt)."),
user_id: str | None = typer.Option(
None, "--user-id", "-u", help="Default user ID (skip prompt)."
),
email: str | None = typer.Option(None, "--email", help="Login via email verification code."),
code: str | None = typer.Option(None, "--code", help="Verification code (use with --email for non-interactive login)."),
code: str | None = typer.Option(
None, "--code", help="Verification code (use with --email for non-interactive login)."
),
) -> None:
"""Interactive setup wizard for mem0 CLI.
@@ -747,7 +765,6 @@ def status(
)
@app.command("import", rich_help_panel="Management")
def import_cmd(
file_path: str = typer.Argument(..., help="JSON file to import."),
@@ -1018,6 +1035,7 @@ def help(
def version() -> None:
"""Show version and exit."""
from mem0_cli.commands.utils import cmd_version
cmd_version()
-1
View File
@@ -105,7 +105,6 @@ class Backend(ABC):
def entities(self, entity_type: str) -> list[dict]: ...
def get_backend(config: Mem0Config) -> Backend:
"""Return the Platform backend."""
from mem0_cli.backend.platform import PlatformBackend
@@ -312,7 +312,6 @@ class PlatformBackend(Backend):
return items
class AuthError(Exception):
pass
+6 -2
View File
@@ -28,7 +28,9 @@ def cmd_entities_list(backend: Backend, entity_type: str, *, output: str) -> Non
"""List entities of a given type."""
valid_types = {"users", "agents", "apps", "runs"}
if entity_type not in valid_types:
print_error(err_console, f"Invalid entity type: {entity_type}. Use: {', '.join(valid_types)}")
print_error(
err_console, f"Invalid entity type: {entity_type}. Use: {', '.join(valid_types)}"
)
raise typer.Exit(1)
_start = _time.perf_counter()
@@ -76,7 +78,9 @@ def cmd_entities_delete(
) -> None:
"""Delete an entity and all its memories (cascade delete)."""
if not any([user_id, agent_id, app_id, run_id]):
print_error(err_console, "Provide at least one of --user-id, --agent-id, --app-id, --run-id.")
print_error(
err_console, "Provide at least one of --user-id, --agent-id, --app-id, --run-id."
)
raise typer.Exit(1)
if dry_run:
+3 -1
View File
@@ -265,7 +265,9 @@ def run_init(
api_key_val = result.get("api_key")
if not api_key_val:
print_error(err_console, "Auth succeeded but no API key was returned. Contact support.")
print_error(
err_console, "Auth succeeded but no API key was returned. Contact support."
)
raise typer.Exit(1)
config.platform.api_key = api_key_val
config.platform.base_url = base_url
+7 -2
View File
@@ -98,10 +98,14 @@ def cmd_add(
# Validate --expires
if expires:
import re
if not re.match(r'^\d{4}-\d{2}-\d{2}$', expires):
print_error(err_console, "Invalid date format for --expires. Use YYYY-MM-DD (e.g. 2025-12-31).")
if not re.match(r"^\d{4}-\d{2}-\d{2}$", expires):
print_error(
err_console, "Invalid date format for --expires. Use YYYY-MM-DD (e.g. 2025-12-31)."
)
raise typer.Exit(1)
from datetime import date
if date.fromisoformat(expires) <= date.today():
print_error(err_console, "--expires date must be in the future.")
raise typer.Exit(1)
@@ -290,6 +294,7 @@ def cmd_list(
if output == "json":
from mem0_cli.output import format_json_envelope
format_json_envelope(
console,
command="list",
+3 -1
View File
@@ -110,7 +110,9 @@ def cmd_import(
added = 0
failed = 0
_start = _time.perf_counter()
for item in track(data, description=f"[{DIM_COLOR}]Importing memories...[/]", console=err_console):
for item in track(
data, description=f"[{DIM_COLOR}]Importing memories...[/]", console=err_console
):
content = item.get("memory", item.get("text", item.get("content", "")))
if not content:
failed += 1
+8 -2
View File
@@ -49,7 +49,9 @@ def format_memories_text(console: Console, memories: list[dict], title: str = "m
console.print()
def format_memories_table(console: Console, memories: list[dict], *, show_score: bool = False) -> None:
def format_memories_table(
console: Console, memories: list[dict], *, show_score: bool = False
) -> None:
"""Render memories in a rich table."""
table = Table(
border_style=BRAND_COLOR,
@@ -71,7 +73,11 @@ def format_memories_table(console: Console, memories: list[dict], *, show_score:
memory_text = memory_text[:57] + "..."
categories = mem.get("categories", [])
if isinstance(categories, list) and categories:
cat = categories[0] if len(categories) == 1 else f"{categories[0]} (+{len(categories) - 1})"
cat = (
categories[0]
if len(categories) == 1
else f"{categories[0]} (+{len(categories) - 1})"
)
else:
cat = "—"
created = _format_date(mem.get("created_at")) or "—"