mem0-cli 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mem0_cli/__init__.py +3 -0
- mem0_cli/__main__.py +5 -0
- mem0_cli/app.py +1021 -0
- mem0_cli/backend/__init__.py +5 -0
- mem0_cli/backend/base.py +113 -0
- mem0_cli/backend/platform.py +325 -0
- mem0_cli/branding.py +130 -0
- mem0_cli/commands/__init__.py +1 -0
- mem0_cli/commands/config_cmd.py +108 -0
- mem0_cli/commands/entities.py +133 -0
- mem0_cli/commands/init_cmd.py +195 -0
- mem0_cli/commands/memory.py +469 -0
- mem0_cli/commands/utils.py +142 -0
- mem0_cli/config.py +176 -0
- mem0_cli/output.py +242 -0
- mem0_cli-0.1.0.dist-info/METADATA +57 -0
- mem0_cli-0.1.0.dist-info/RECORD +19 -0
- mem0_cli-0.1.0.dist-info/WHEEL +4 -0
- mem0_cli-0.1.0.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""Entity management commands."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import time as _time
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
from rich.table import Table
|
|
10
|
+
|
|
11
|
+
from mem0_cli.backend.base import Backend
|
|
12
|
+
from mem0_cli.branding import (
|
|
13
|
+
ACCENT_COLOR,
|
|
14
|
+
BRAND_COLOR,
|
|
15
|
+
DIM_COLOR,
|
|
16
|
+
print_error,
|
|
17
|
+
print_info,
|
|
18
|
+
print_success,
|
|
19
|
+
timed_status,
|
|
20
|
+
)
|
|
21
|
+
from mem0_cli.output import format_json
|
|
22
|
+
|
|
23
|
+
console = Console()
|
|
24
|
+
err_console = Console(stderr=True)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def cmd_entities_list(backend: Backend, entity_type: str, *, output: str) -> None:
|
|
28
|
+
"""List entities of a given type."""
|
|
29
|
+
valid_types = {"users", "agents", "apps", "runs"}
|
|
30
|
+
if entity_type not in valid_types:
|
|
31
|
+
print_error(err_console, f"Invalid entity type: {entity_type}. Use: {', '.join(valid_types)}")
|
|
32
|
+
raise typer.Exit(1)
|
|
33
|
+
|
|
34
|
+
_start = _time.perf_counter()
|
|
35
|
+
with timed_status(err_console, f"Fetching {entity_type}...") as _ts:
|
|
36
|
+
try:
|
|
37
|
+
results = backend.entities(entity_type)
|
|
38
|
+
except Exception as e:
|
|
39
|
+
print_error(err_console, str(e), hint="This feature may require the mem0 Platform.")
|
|
40
|
+
raise typer.Exit(1) from None
|
|
41
|
+
_elapsed = _time.perf_counter() - _start
|
|
42
|
+
|
|
43
|
+
if output == "json":
|
|
44
|
+
format_json(console, results)
|
|
45
|
+
return
|
|
46
|
+
|
|
47
|
+
if not results:
|
|
48
|
+
print_info(console, f"No {entity_type} found.")
|
|
49
|
+
return
|
|
50
|
+
|
|
51
|
+
table = Table(border_style=BRAND_COLOR, header_style=f"bold {ACCENT_COLOR}", padding=(0, 1))
|
|
52
|
+
table.add_column("Name / ID", style="bold")
|
|
53
|
+
table.add_column("Created", max_width=12)
|
|
54
|
+
|
|
55
|
+
for entity in results:
|
|
56
|
+
name = entity.get("name", entity.get("id", "—"))
|
|
57
|
+
created = str(entity.get("created_at", "—"))[:10]
|
|
58
|
+
table.add_row(str(name), created)
|
|
59
|
+
|
|
60
|
+
console.print()
|
|
61
|
+
console.print(table)
|
|
62
|
+
console.print(f" [{DIM_COLOR}]{len(results)} {entity_type} ({_elapsed:.2f}s)[/]")
|
|
63
|
+
console.print()
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def cmd_entities_delete(
|
|
67
|
+
backend: Backend,
|
|
68
|
+
*,
|
|
69
|
+
user_id: str | None,
|
|
70
|
+
agent_id: str | None,
|
|
71
|
+
app_id: str | None,
|
|
72
|
+
run_id: str | None,
|
|
73
|
+
force: bool,
|
|
74
|
+
dry_run: bool = False,
|
|
75
|
+
output: str,
|
|
76
|
+
) -> None:
|
|
77
|
+
"""Delete an entity and all its memories (cascade delete)."""
|
|
78
|
+
if not any([user_id, agent_id, app_id, run_id]):
|
|
79
|
+
print_error(err_console, "Provide at least one of --user-id, --agent-id, --app-id, --run-id.")
|
|
80
|
+
raise typer.Exit(1)
|
|
81
|
+
|
|
82
|
+
if dry_run:
|
|
83
|
+
scope_parts = []
|
|
84
|
+
if user_id:
|
|
85
|
+
scope_parts.append(f"user={user_id}")
|
|
86
|
+
if agent_id:
|
|
87
|
+
scope_parts.append(f"agent={agent_id}")
|
|
88
|
+
if app_id:
|
|
89
|
+
scope_parts.append(f"app={app_id}")
|
|
90
|
+
if run_id:
|
|
91
|
+
scope_parts.append(f"run={run_id}")
|
|
92
|
+
scope = ", ".join(scope_parts)
|
|
93
|
+
print_info(console, f"Would delete entity {scope} and all its memories.")
|
|
94
|
+
print_info(console, "No changes made (dry run).")
|
|
95
|
+
return
|
|
96
|
+
|
|
97
|
+
if not force:
|
|
98
|
+
scope_parts = []
|
|
99
|
+
if user_id:
|
|
100
|
+
scope_parts.append(f"user={user_id}")
|
|
101
|
+
if agent_id:
|
|
102
|
+
scope_parts.append(f"agent={agent_id}")
|
|
103
|
+
if app_id:
|
|
104
|
+
scope_parts.append(f"app={app_id}")
|
|
105
|
+
if run_id:
|
|
106
|
+
scope_parts.append(f"run={run_id}")
|
|
107
|
+
scope = ", ".join(scope_parts)
|
|
108
|
+
|
|
109
|
+
confirm = typer.confirm(
|
|
110
|
+
f"\n \u26a0 Delete entity {scope} AND all its memories? This cannot be undone."
|
|
111
|
+
)
|
|
112
|
+
if not confirm:
|
|
113
|
+
print_info(console, "Cancelled.")
|
|
114
|
+
raise typer.Exit(0)
|
|
115
|
+
|
|
116
|
+
_start = _time.perf_counter()
|
|
117
|
+
with timed_status(err_console, "Deleting entity...") as _ts:
|
|
118
|
+
try:
|
|
119
|
+
result = backend.delete_entities(
|
|
120
|
+
user_id=user_id,
|
|
121
|
+
agent_id=agent_id,
|
|
122
|
+
app_id=app_id,
|
|
123
|
+
run_id=run_id,
|
|
124
|
+
)
|
|
125
|
+
except Exception as e:
|
|
126
|
+
print_error(err_console, str(e))
|
|
127
|
+
raise typer.Exit(1) from None
|
|
128
|
+
_elapsed = _time.perf_counter() - _start
|
|
129
|
+
|
|
130
|
+
if output == "json":
|
|
131
|
+
format_json(console, result)
|
|
132
|
+
elif output != "quiet":
|
|
133
|
+
print_success(console, f"Entity deleted with all memories ({_elapsed:.2f}s)")
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"""mem0 init — interactive setup wizard."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
from rich.prompt import Prompt
|
|
10
|
+
|
|
11
|
+
from mem0_cli.branding import (
|
|
12
|
+
BRAND_COLOR,
|
|
13
|
+
DIM_COLOR,
|
|
14
|
+
print_banner,
|
|
15
|
+
print_error,
|
|
16
|
+
print_info,
|
|
17
|
+
print_success,
|
|
18
|
+
)
|
|
19
|
+
from mem0_cli.config import Mem0Config, save_config
|
|
20
|
+
|
|
21
|
+
console = Console()
|
|
22
|
+
err_console = Console(stderr=True)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _prompt_secret(label: str) -> str:
|
|
26
|
+
"""Prompt for a secret value, echoing '*' for each character typed."""
|
|
27
|
+
sys.stdout.write(label)
|
|
28
|
+
sys.stdout.flush()
|
|
29
|
+
|
|
30
|
+
chars: list[str] = []
|
|
31
|
+
|
|
32
|
+
if sys.platform == "win32":
|
|
33
|
+
import msvcrt
|
|
34
|
+
|
|
35
|
+
while True:
|
|
36
|
+
ch = msvcrt.getwch()
|
|
37
|
+
if ch in ("\r", "\n"):
|
|
38
|
+
sys.stdout.write("\n")
|
|
39
|
+
sys.stdout.flush()
|
|
40
|
+
break
|
|
41
|
+
if ch == "\x03":
|
|
42
|
+
raise KeyboardInterrupt
|
|
43
|
+
if ch in ("\x08", "\x7f"): # backspace
|
|
44
|
+
if chars:
|
|
45
|
+
chars.pop()
|
|
46
|
+
sys.stdout.write("\b \b")
|
|
47
|
+
sys.stdout.flush()
|
|
48
|
+
else:
|
|
49
|
+
chars.append(ch)
|
|
50
|
+
sys.stdout.write("*")
|
|
51
|
+
sys.stdout.flush()
|
|
52
|
+
else:
|
|
53
|
+
import termios
|
|
54
|
+
import tty
|
|
55
|
+
|
|
56
|
+
fd = sys.stdin.fileno()
|
|
57
|
+
old_settings = termios.tcgetattr(fd)
|
|
58
|
+
try:
|
|
59
|
+
tty.setraw(fd)
|
|
60
|
+
while True:
|
|
61
|
+
ch = sys.stdin.read(1)
|
|
62
|
+
if ch in ("\r", "\n"):
|
|
63
|
+
sys.stdout.write("\r\n")
|
|
64
|
+
sys.stdout.flush()
|
|
65
|
+
break
|
|
66
|
+
if ch == "\x03":
|
|
67
|
+
raise KeyboardInterrupt
|
|
68
|
+
if ch in ("\x7f", "\x08"): # backspace/delete
|
|
69
|
+
if chars:
|
|
70
|
+
chars.pop()
|
|
71
|
+
sys.stdout.write("\b \b")
|
|
72
|
+
sys.stdout.flush()
|
|
73
|
+
elif ch == "\x15": # Ctrl+U — clear line
|
|
74
|
+
sys.stdout.write("\b \b" * len(chars))
|
|
75
|
+
sys.stdout.flush()
|
|
76
|
+
chars = []
|
|
77
|
+
elif ch >= " ": # ignore other control characters
|
|
78
|
+
chars.append(ch)
|
|
79
|
+
sys.stdout.write("*")
|
|
80
|
+
sys.stdout.flush()
|
|
81
|
+
finally:
|
|
82
|
+
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
|
|
83
|
+
|
|
84
|
+
return "".join(chars)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def run_init(*, api_key: str | None = None, user_id: str | None = None) -> None:
|
|
88
|
+
"""Interactive setup wizard for mem0 CLI.
|
|
89
|
+
|
|
90
|
+
When both *api_key* and *user_id* are supplied, all prompts are skipped
|
|
91
|
+
(non-interactive mode). When running in a non-TTY without the required
|
|
92
|
+
flags, an error message is printed.
|
|
93
|
+
"""
|
|
94
|
+
config = Mem0Config()
|
|
95
|
+
|
|
96
|
+
# Fully non-interactive when both flags provided
|
|
97
|
+
if api_key and user_id:
|
|
98
|
+
config.platform.api_key = api_key
|
|
99
|
+
config.defaults.user_id = user_id
|
|
100
|
+
_validate_platform(config)
|
|
101
|
+
save_config(config)
|
|
102
|
+
print_success(console, "Configuration saved to ~/.mem0/config.json")
|
|
103
|
+
return
|
|
104
|
+
|
|
105
|
+
# Non-TTY without full flags -> error
|
|
106
|
+
if not sys.stdin.isatty():
|
|
107
|
+
if not api_key or not user_id:
|
|
108
|
+
print_error(
|
|
109
|
+
err_console,
|
|
110
|
+
"Non-interactive terminal detected and required flags missing.",
|
|
111
|
+
hint="Run: mem0 init --api-key <key> --user-id <id>",
|
|
112
|
+
)
|
|
113
|
+
raise typer.Exit(1)
|
|
114
|
+
|
|
115
|
+
print_banner(console)
|
|
116
|
+
console.print()
|
|
117
|
+
print_info(console, "Welcome! Let's set up your mem0 CLI.\n")
|
|
118
|
+
|
|
119
|
+
# Use provided flags or prompt
|
|
120
|
+
if api_key:
|
|
121
|
+
config.platform.api_key = api_key
|
|
122
|
+
else:
|
|
123
|
+
_setup_platform(config)
|
|
124
|
+
|
|
125
|
+
if user_id:
|
|
126
|
+
config.defaults.user_id = user_id
|
|
127
|
+
else:
|
|
128
|
+
_setup_defaults(config)
|
|
129
|
+
|
|
130
|
+
_validate_platform(config)
|
|
131
|
+
|
|
132
|
+
save_config(config)
|
|
133
|
+
console.print()
|
|
134
|
+
print_success(console, "Configuration saved to ~/.mem0/config.json")
|
|
135
|
+
console.print()
|
|
136
|
+
console.print(f" [{DIM_COLOR}]Get started:[/]")
|
|
137
|
+
if config.defaults.user_id:
|
|
138
|
+
console.print(f' [{DIM_COLOR}] mem0 add "I prefer dark mode"[/]')
|
|
139
|
+
console.print(f' [{DIM_COLOR}] mem0 search "preferences"[/]')
|
|
140
|
+
else:
|
|
141
|
+
console.print(f' [{DIM_COLOR}] mem0 add "I prefer dark mode" --user-id alice[/]')
|
|
142
|
+
console.print(f' [{DIM_COLOR}] mem0 search "preferences" --user-id alice[/]')
|
|
143
|
+
console.print()
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _setup_platform(config: Mem0Config) -> None:
|
|
147
|
+
"""Platform setup flow."""
|
|
148
|
+
console.print()
|
|
149
|
+
console.print(f" [{DIM_COLOR}]Get your API key at https://app.mem0.ai/dashboard/api-keys[/]")
|
|
150
|
+
console.print()
|
|
151
|
+
|
|
152
|
+
console.print(f" [{BRAND_COLOR}]API Key[/]: ", end="")
|
|
153
|
+
api_key = _prompt_secret("")
|
|
154
|
+
if not api_key:
|
|
155
|
+
print_error(err_console, "API key is required.")
|
|
156
|
+
raise typer.Exit(1)
|
|
157
|
+
|
|
158
|
+
config.platform.api_key = api_key
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _setup_defaults(config: Mem0Config) -> None:
|
|
162
|
+
"""Collect default entity IDs."""
|
|
163
|
+
console.print()
|
|
164
|
+
print_info(console, "Set default entity IDs (press Enter to skip).\n")
|
|
165
|
+
|
|
166
|
+
user_id = Prompt.ask(
|
|
167
|
+
f" [{BRAND_COLOR}]Default User ID[/] [{DIM_COLOR}](recommended)[/]",
|
|
168
|
+
default="mem0-cli",
|
|
169
|
+
)
|
|
170
|
+
if user_id:
|
|
171
|
+
config.defaults.user_id = user_id
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _validate_platform(config: Mem0Config) -> None:
|
|
175
|
+
"""Validate platform connection after all inputs are collected."""
|
|
176
|
+
console.print()
|
|
177
|
+
print_info(console, "Validating connection...")
|
|
178
|
+
try:
|
|
179
|
+
from mem0_cli.backend.platform import PlatformBackend
|
|
180
|
+
|
|
181
|
+
backend = PlatformBackend(config.platform)
|
|
182
|
+
status = backend.status(
|
|
183
|
+
user_id=config.defaults.user_id or None,
|
|
184
|
+
agent_id=config.defaults.agent_id or None,
|
|
185
|
+
)
|
|
186
|
+
if status.get("connected"):
|
|
187
|
+
print_success(console, "Connected to mem0 Platform!")
|
|
188
|
+
else:
|
|
189
|
+
print_error(
|
|
190
|
+
err_console,
|
|
191
|
+
f"Could not connect: {status.get('error', 'Unknown error')}",
|
|
192
|
+
hint="Check your API key and try again.",
|
|
193
|
+
)
|
|
194
|
+
except Exception as e:
|
|
195
|
+
print_error(err_console, f"Connection test failed: {e}")
|