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.
@@ -0,0 +1,469 @@
1
+ """Memory CRUD commands: add, search, get, list, update, delete."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import sys
7
+ import time as _time
8
+ from pathlib import Path
9
+
10
+ import typer
11
+ from rich.console import Console
12
+
13
+ from mem0_cli.backend.base import Backend
14
+ from mem0_cli.branding import (
15
+ print_error,
16
+ print_info,
17
+ print_scope,
18
+ print_success,
19
+ timed_status,
20
+ )
21
+ from mem0_cli.output import (
22
+ format_add_result,
23
+ format_json,
24
+ format_memories_table,
25
+ format_memories_text,
26
+ format_single_memory,
27
+ print_result_summary,
28
+ )
29
+
30
+ console = Console()
31
+ err_console = Console(stderr=True)
32
+
33
+
34
+ def cmd_add(
35
+ backend: Backend,
36
+ text: str | None,
37
+ *,
38
+ user_id: str | None,
39
+ agent_id: str | None,
40
+ app_id: str | None,
41
+ run_id: str | None,
42
+ messages: str | None,
43
+ file: Path | None,
44
+ metadata: str | None,
45
+ immutable: bool,
46
+ no_infer: bool,
47
+ expires: str | None,
48
+ categories: str | None,
49
+ enable_graph: bool = False,
50
+ output: str = "text",
51
+ ) -> None:
52
+ """Add a memory."""
53
+ msgs = None
54
+ content = text
55
+
56
+ # Read from file
57
+ if file:
58
+ try:
59
+ raw = Path(file).read_text()
60
+ msgs = json.loads(raw)
61
+ except (FileNotFoundError, json.JSONDecodeError) as e:
62
+ print_error(err_console, f"Failed to read file: {e}")
63
+ raise typer.Exit(1) from None
64
+
65
+ # Parse messages JSON
66
+ elif messages:
67
+ try:
68
+ msgs = json.loads(messages)
69
+ except json.JSONDecodeError as e:
70
+ print_error(err_console, f"Invalid JSON in --messages: {e}")
71
+ raise typer.Exit(1) from None
72
+
73
+ # Read from stdin if no text and stdin is piped
74
+ elif not content and not sys.stdin.isatty():
75
+ content = sys.stdin.read().strip()
76
+
77
+ if not content and not msgs:
78
+ print_error(
79
+ err_console, "No content provided. Pass text, --messages, --file, or pipe via stdin."
80
+ )
81
+ raise typer.Exit(1)
82
+
83
+ meta = None
84
+ if metadata:
85
+ try:
86
+ meta = json.loads(metadata)
87
+ except json.JSONDecodeError:
88
+ print_error(err_console, "Invalid JSON in --metadata.")
89
+ raise typer.Exit(1) from None
90
+
91
+ cats = None
92
+ if categories:
93
+ try:
94
+ cats = json.loads(categories)
95
+ except json.JSONDecodeError:
96
+ cats = [c.strip() for c in categories.split(",")]
97
+
98
+ with timed_status(err_console, "Adding memory...") as ts:
99
+ try:
100
+ result = backend.add(
101
+ content=content,
102
+ messages=msgs,
103
+ user_id=user_id,
104
+ agent_id=agent_id,
105
+ app_id=app_id,
106
+ run_id=run_id,
107
+ metadata=meta,
108
+ immutable=immutable,
109
+ infer=not no_infer,
110
+ expires=expires,
111
+ categories=cats,
112
+ enable_graph=enable_graph,
113
+ )
114
+ except Exception as e:
115
+ ts.error_msg = str(e)
116
+ print_error(err_console, str(e))
117
+ raise typer.Exit(1) from None
118
+
119
+ if output == "quiet":
120
+ return
121
+
122
+ if output == "json":
123
+ format_add_result(console, result, output)
124
+ return
125
+
126
+ console.print()
127
+ print_scope(console, user_id=user_id, agent_id=agent_id, app_id=app_id, run_id=run_id)
128
+ # Count results
129
+ results = result if isinstance(result, list) else result.get("results", [result])
130
+ count = len(results) if results else 0
131
+ print_success(
132
+ console, f"Memory processed — {count} memor{'y' if count == 1 else 'ies'} extracted"
133
+ )
134
+ format_add_result(console, result, output)
135
+
136
+
137
+ def cmd_search(
138
+ backend: Backend,
139
+ query: str,
140
+ *,
141
+ user_id: str | None,
142
+ agent_id: str | None,
143
+ app_id: str | None,
144
+ run_id: str | None,
145
+ top_k: int,
146
+ threshold: float,
147
+ rerank: bool,
148
+ keyword: bool,
149
+ filter_json: str | None,
150
+ fields: str | None,
151
+ enable_graph: bool = False,
152
+ output: str = "text",
153
+ ) -> None:
154
+ """Search memories."""
155
+ filters = None
156
+ if filter_json:
157
+ try:
158
+ filters = json.loads(filter_json)
159
+ except json.JSONDecodeError:
160
+ print_error(err_console, "Invalid JSON in --filter.")
161
+ raise typer.Exit(1) from None
162
+
163
+ field_list = None
164
+ if fields:
165
+ field_list = [f.strip() for f in fields.split(",")]
166
+
167
+ _start = _time.perf_counter()
168
+ with timed_status(err_console, "Searching memories...") as _ts:
169
+ try:
170
+ results = backend.search(
171
+ query,
172
+ user_id=user_id,
173
+ agent_id=agent_id,
174
+ app_id=app_id,
175
+ run_id=run_id,
176
+ top_k=top_k,
177
+ threshold=threshold,
178
+ rerank=rerank,
179
+ keyword=keyword,
180
+ filters=filters,
181
+ fields=field_list,
182
+ enable_graph=enable_graph,
183
+ )
184
+ except Exception as e:
185
+ print_error(err_console, str(e))
186
+ raise typer.Exit(1) from None
187
+ _elapsed = _time.perf_counter() - _start
188
+
189
+ if output == "json":
190
+ format_json(console, results)
191
+ elif output == "table":
192
+ if results:
193
+ format_memories_table(console, results)
194
+ print_result_summary(
195
+ console, len(results), duration_secs=_elapsed, user_id=user_id, agent_id=agent_id
196
+ )
197
+ else:
198
+ console.print()
199
+ print_info(console, "No memories found matching your query.")
200
+ console.print()
201
+ else:
202
+ if results:
203
+ format_memories_text(console, results)
204
+ print_result_summary(
205
+ console, len(results), duration_secs=_elapsed, user_id=user_id, agent_id=agent_id
206
+ )
207
+ else:
208
+ console.print()
209
+ print_info(console, "No memories found matching your query.")
210
+ console.print()
211
+
212
+
213
+ def cmd_get(backend: Backend, memory_id: str, *, output: str) -> None:
214
+ """Get a specific memory by ID."""
215
+ with timed_status(err_console, "Fetching memory...") as _ts:
216
+ try:
217
+ result = backend.get(memory_id)
218
+ except Exception as e:
219
+ print_error(err_console, str(e))
220
+ raise typer.Exit(1) from None
221
+
222
+ format_single_memory(console, result, output)
223
+
224
+
225
+ def cmd_list(
226
+ backend: Backend,
227
+ *,
228
+ user_id: str | None,
229
+ agent_id: str | None,
230
+ app_id: str | None,
231
+ run_id: str | None,
232
+ page: int,
233
+ page_size: int,
234
+ category: str | None,
235
+ after: str | None,
236
+ before: str | None,
237
+ enable_graph: bool = False,
238
+ output: str = "table",
239
+ ) -> None:
240
+ """List memories."""
241
+ _start = _time.perf_counter()
242
+ with timed_status(err_console, "Listing memories...") as _ts:
243
+ try:
244
+ results = backend.list_memories(
245
+ user_id=user_id,
246
+ agent_id=agent_id,
247
+ app_id=app_id,
248
+ run_id=run_id,
249
+ page=page,
250
+ page_size=page_size,
251
+ category=category,
252
+ after=after,
253
+ before=before,
254
+ enable_graph=enable_graph,
255
+ )
256
+ except Exception as e:
257
+ print_error(err_console, str(e))
258
+ raise typer.Exit(1) from None
259
+ _elapsed = _time.perf_counter() - _start
260
+
261
+ if output == "json":
262
+ format_json(console, results)
263
+ elif output == "table":
264
+ if results:
265
+ format_memories_table(console, results)
266
+ print_result_summary(
267
+ console,
268
+ len(results),
269
+ duration_secs=_elapsed,
270
+ page=page,
271
+ user_id=user_id,
272
+ agent_id=agent_id,
273
+ )
274
+ else:
275
+ console.print()
276
+ print_info(console, "No memories found.")
277
+ console.print()
278
+ else:
279
+ if results:
280
+ format_memories_text(console, results, title="memories")
281
+ print_result_summary(
282
+ console,
283
+ len(results),
284
+ duration_secs=_elapsed,
285
+ page=page,
286
+ user_id=user_id,
287
+ agent_id=agent_id,
288
+ )
289
+ else:
290
+ console.print()
291
+ print_info(console, "No memories found.")
292
+ console.print()
293
+
294
+
295
+ def cmd_update(
296
+ backend: Backend,
297
+ memory_id: str,
298
+ text: str | None,
299
+ *,
300
+ metadata: str | None,
301
+ output: str,
302
+ ) -> None:
303
+ """Update a memory."""
304
+ meta = None
305
+ if metadata:
306
+ try:
307
+ meta = json.loads(metadata)
308
+ except json.JSONDecodeError:
309
+ print_error(err_console, "Invalid JSON in --metadata.")
310
+ raise typer.Exit(1) from None
311
+
312
+ _start = _time.perf_counter()
313
+ with timed_status(err_console, "Updating memory...") as _ts:
314
+ try:
315
+ result = backend.update(memory_id, content=text, metadata=meta)
316
+ except Exception as e:
317
+ print_error(err_console, str(e))
318
+ raise typer.Exit(1) from None
319
+ _elapsed = _time.perf_counter() - _start
320
+
321
+ if output == "json":
322
+ format_json(console, result)
323
+ elif output != "quiet":
324
+ print_success(console, f"Memory {memory_id[:8]} updated ({_elapsed:.2f}s)")
325
+
326
+
327
+ def cmd_delete(
328
+ backend: Backend,
329
+ memory_id: str,
330
+ *,
331
+ dry_run: bool = False,
332
+ force: bool = False,
333
+ output: str,
334
+ ) -> None:
335
+ """Delete a single memory by ID."""
336
+ if dry_run:
337
+ # Fetch and display what would be deleted
338
+ try:
339
+ mem = backend.get(memory_id)
340
+ except Exception as e:
341
+ print_error(err_console, str(e))
342
+ raise typer.Exit(1) from None
343
+ format_single_memory(console, mem, output)
344
+ print_info(console, "No changes made (dry run).")
345
+ return
346
+
347
+ _start = _time.perf_counter()
348
+ with timed_status(err_console, "Deleting...") as _ts:
349
+ try:
350
+ result = backend.delete(memory_id=memory_id)
351
+ except Exception as e:
352
+ print_error(err_console, str(e))
353
+ raise typer.Exit(1) from None
354
+ _elapsed = _time.perf_counter() - _start
355
+
356
+ if output == "json":
357
+ format_json(console, result)
358
+ elif output != "quiet":
359
+ print_success(console, f"Memory {memory_id[:8]} deleted ({_elapsed:.2f}s)")
360
+
361
+
362
+ def cmd_delete_all(
363
+ backend: Backend,
364
+ *,
365
+ force: bool,
366
+ dry_run: bool = False,
367
+ all_: bool = False,
368
+ user_id: str | None,
369
+ agent_id: str | None,
370
+ app_id: str | None,
371
+ run_id: str | None,
372
+ output: str,
373
+ ) -> None:
374
+ """Delete all memories matching a scope."""
375
+ if all_:
376
+ # Project-wide wipe using wildcard entity IDs
377
+ if dry_run:
378
+ print_info(console, "Would delete ALL memories project-wide.")
379
+ print_info(console, "No changes made (dry run).")
380
+ return
381
+
382
+ if not force:
383
+ confirm = typer.confirm(
384
+ "\n ⚠ Delete ALL memories across the ENTIRE project? This cannot be undone."
385
+ )
386
+ if not confirm:
387
+ print_info(console, "Cancelled.")
388
+ raise typer.Exit(0)
389
+
390
+ _start = _time.perf_counter()
391
+ with timed_status(err_console, "Deleting all memories project-wide...") as _ts:
392
+ try:
393
+ result = backend.delete(
394
+ all=True,
395
+ user_id="*",
396
+ agent_id="*",
397
+ app_id="*",
398
+ run_id="*",
399
+ )
400
+ except Exception as e:
401
+ print_error(err_console, str(e))
402
+ raise typer.Exit(1) from None
403
+ _elapsed = _time.perf_counter() - _start
404
+
405
+ if output == "json":
406
+ format_json(console, result)
407
+ elif output != "quiet":
408
+ if isinstance(result, dict) and "message" in result:
409
+ print_info(console, "Deletion started. Memories will be removed in the background.")
410
+ else:
411
+ print_success(console, f"All project memories deleted ({_elapsed:.2f}s)")
412
+ return
413
+
414
+ if dry_run:
415
+ # List matching memories and show count
416
+ try:
417
+ results = backend.list_memories(
418
+ user_id=user_id,
419
+ agent_id=agent_id,
420
+ app_id=app_id,
421
+ run_id=run_id,
422
+ )
423
+ except Exception as e:
424
+ print_error(err_console, str(e))
425
+ raise typer.Exit(1) from None
426
+ count = len(results)
427
+ print_info(console, f"Would delete {count} memor{'y' if count == 1 else 'ies'}.")
428
+ print_info(console, "No changes made (dry run).")
429
+ return
430
+
431
+ if not force:
432
+ scope_parts = []
433
+ if user_id:
434
+ scope_parts.append(f"user={user_id}")
435
+ if agent_id:
436
+ scope_parts.append(f"agent={agent_id}")
437
+ if app_id:
438
+ scope_parts.append(f"app={app_id}")
439
+ if run_id:
440
+ scope_parts.append(f"run={run_id}")
441
+ scope = ", ".join(scope_parts) if scope_parts else "ALL entities"
442
+
443
+ confirm = typer.confirm(f"\n ⚠ Delete ALL memories for {scope}? This cannot be undone.")
444
+ if not confirm:
445
+ print_info(console, "Cancelled.")
446
+ raise typer.Exit(0)
447
+
448
+ _start = _time.perf_counter()
449
+ with timed_status(err_console, "Deleting all memories...") as _ts:
450
+ try:
451
+ result = backend.delete(
452
+ all=True,
453
+ user_id=user_id,
454
+ agent_id=agent_id,
455
+ app_id=app_id,
456
+ run_id=run_id,
457
+ )
458
+ except Exception as e:
459
+ print_error(err_console, str(e))
460
+ raise typer.Exit(1) from None
461
+ _elapsed = _time.perf_counter() - _start
462
+
463
+ if output == "json":
464
+ format_json(console, result)
465
+ elif output != "quiet":
466
+ if isinstance(result, dict) and "message" in result:
467
+ print_info(console, "Deletion started. Memories will be removed in the background.")
468
+ else:
469
+ print_success(console, f"All matching memories deleted ({_elapsed:.2f}s)")
@@ -0,0 +1,142 @@
1
+ """Utility commands: status, version, import."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import time as _time
7
+ from pathlib import Path
8
+
9
+ import typer
10
+ from rich.console import Console
11
+ from rich.panel import Panel
12
+ from rich.progress import track
13
+
14
+ from mem0_cli import __version__
15
+ from mem0_cli.backend.base import Backend
16
+ from mem0_cli.branding import (
17
+ BRAND_COLOR,
18
+ DIM_COLOR,
19
+ ERROR_COLOR,
20
+ SUCCESS_COLOR,
21
+ print_error,
22
+ print_success,
23
+ timed_status,
24
+ )
25
+ from mem0_cli.config import load_config
26
+
27
+ console = Console()
28
+ err_console = Console(stderr=True)
29
+
30
+
31
+ def cmd_status(
32
+ backend: Backend,
33
+ *,
34
+ user_id: str | None = None,
35
+ agent_id: str | None = None,
36
+ output: str = "text",
37
+ ) -> None:
38
+ """Check connectivity and auth."""
39
+ from mem0_cli.output import format_json_envelope
40
+
41
+ _start = _time.perf_counter()
42
+ with timed_status(err_console, "Checking connection...") as _ts:
43
+ result = backend.status(user_id=user_id, agent_id=agent_id)
44
+ _elapsed = _time.perf_counter() - _start
45
+
46
+ if output == "json":
47
+ format_json_envelope(
48
+ console,
49
+ command="status",
50
+ data={
51
+ "connected": result.get("connected", False),
52
+ "backend": result.get("backend", "?"),
53
+ "base_url": result.get("base_url", ""),
54
+ "latency_ms": int(_elapsed * 1000),
55
+ },
56
+ duration_ms=int(_elapsed * 1000),
57
+ )
58
+ return
59
+
60
+ lines = []
61
+ if result.get("connected"):
62
+ lines.append(f" [{SUCCESS_COLOR}]●[/] Connected")
63
+ else:
64
+ lines.append(f" [{ERROR_COLOR}]●[/] Disconnected")
65
+
66
+ lines.append(f" [{DIM_COLOR}]Backend:[/] {result.get('backend', '?')}")
67
+ if result.get("base_url"):
68
+ lines.append(f" [{DIM_COLOR}]API URL:[/] {result['base_url']}")
69
+ if result.get("error"):
70
+ lines.append(f" [{ERROR_COLOR}]Error:[/] {result['error']}")
71
+ lines.append(f" [{DIM_COLOR}]Latency:[/] {_elapsed:.2f}s")
72
+
73
+ content = "\n".join(lines)
74
+ panel = Panel(
75
+ content,
76
+ title=f"[{BRAND_COLOR}]Connection Status[/]",
77
+ title_align="left",
78
+ border_style=BRAND_COLOR,
79
+ padding=(1, 1),
80
+ )
81
+ console.print()
82
+ console.print(panel)
83
+ console.print()
84
+
85
+
86
+ def cmd_version() -> None:
87
+ """Show version."""
88
+ console.print(f" [{BRAND_COLOR}]◆ Mem0[/] CLI v{__version__}")
89
+
90
+
91
+ def cmd_import(
92
+ backend: Backend,
93
+ file_path: str,
94
+ *,
95
+ user_id: str | None,
96
+ agent_id: str | None,
97
+ output: str = "text",
98
+ ) -> None:
99
+ """Import memories from a JSON file."""
100
+ from mem0_cli.output import format_json_envelope
101
+
102
+ try:
103
+ data = json.loads(Path(file_path).read_text())
104
+ except (FileNotFoundError, json.JSONDecodeError) as e:
105
+ print_error(err_console, f"Failed to read file: {e}")
106
+ raise typer.Exit(1) from None
107
+
108
+ if not isinstance(data, list):
109
+ data = [data]
110
+
111
+ added = 0
112
+ failed = 0
113
+ _start = _time.perf_counter()
114
+ for item in track(data, description=f"[{DIM_COLOR}]Importing memories...[/]", console=err_console):
115
+ content = item.get("memory", item.get("text", item.get("content", "")))
116
+ if not content:
117
+ failed += 1
118
+ continue
119
+ try:
120
+ backend.add(
121
+ content=content,
122
+ user_id=user_id or item.get("user_id"),
123
+ agent_id=agent_id or item.get("agent_id"),
124
+ metadata=item.get("metadata"),
125
+ )
126
+ added += 1
127
+ except Exception:
128
+ failed += 1
129
+ _elapsed = _time.perf_counter() - _start
130
+
131
+ if output == "json":
132
+ format_json_envelope(
133
+ console,
134
+ command="import",
135
+ data={"added": added, "failed": failed, "duration_s": round(_elapsed, 2)},
136
+ duration_ms=int(_elapsed * 1000),
137
+ )
138
+ return
139
+
140
+ print_success(err_console, f"Imported {added} memories ({_elapsed:.2f}s)")
141
+ if failed:
142
+ print_error(err_console, f"{failed} memories failed to import.")