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/config.py ADDED
@@ -0,0 +1,176 @@
1
+ """Configuration management for mem0 CLI.
2
+
3
+ Config precedence (highest to lowest):
4
+ 1. CLI flags (--api-key, --base-url, etc.)
5
+ 2. Environment variables (MEM0_API_KEY, etc.)
6
+ 3. Config file (~/.mem0/config.json)
7
+ 4. Defaults
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import json
13
+ import os
14
+ import stat
15
+ from dataclasses import dataclass, field
16
+ from pathlib import Path
17
+ from typing import Any
18
+
19
+ CONFIG_DIR = Path.home() / ".mem0"
20
+ CONFIG_FILE = CONFIG_DIR / "config.json"
21
+
22
+ DEFAULT_BASE_URL = "https://api.mem0.ai"
23
+ CONFIG_VERSION = 1
24
+
25
+
26
+ @dataclass
27
+ class PlatformConfig:
28
+ api_key: str = ""
29
+ base_url: str = DEFAULT_BASE_URL
30
+
31
+
32
+ @dataclass
33
+ class DefaultsConfig:
34
+ user_id: str = ""
35
+ agent_id: str = ""
36
+ app_id: str = ""
37
+ run_id: str = ""
38
+ enable_graph: bool = False
39
+
40
+
41
+ @dataclass
42
+ class Mem0Config:
43
+ version: int = CONFIG_VERSION
44
+ defaults: DefaultsConfig = field(default_factory=DefaultsConfig)
45
+ platform: PlatformConfig = field(default_factory=PlatformConfig)
46
+
47
+
48
+ def ensure_config_dir() -> Path:
49
+ """Create ~/.mem0 directory with secure permissions if it doesn't exist."""
50
+ CONFIG_DIR.mkdir(parents=True, exist_ok=True)
51
+ os.chmod(CONFIG_DIR, stat.S_IRWXU) # 0700
52
+ return CONFIG_DIR
53
+
54
+
55
+ def load_config() -> Mem0Config:
56
+ """Load config from file, applying env var overrides."""
57
+ config = Mem0Config()
58
+
59
+ if CONFIG_FILE.exists():
60
+ with open(CONFIG_FILE) as f:
61
+ data = json.load(f)
62
+
63
+ config.version = data.get("version", CONFIG_VERSION)
64
+
65
+ plat = data.get("platform", {})
66
+ config.platform.api_key = plat.get("api_key", "")
67
+ config.platform.base_url = plat.get("base_url", DEFAULT_BASE_URL)
68
+
69
+ defaults = data.get("defaults", {})
70
+ config.defaults.user_id = defaults.get("user_id", "")
71
+ config.defaults.agent_id = defaults.get("agent_id", "")
72
+ config.defaults.app_id = defaults.get("app_id", "")
73
+ config.defaults.run_id = defaults.get("run_id", "")
74
+ config.defaults.enable_graph = defaults.get("enable_graph", False)
75
+
76
+ # Environment variable overrides
77
+ env_key = os.environ.get("MEM0_API_KEY")
78
+ if env_key:
79
+ config.platform.api_key = env_key
80
+
81
+ env_base = os.environ.get("MEM0_BASE_URL")
82
+ if env_base:
83
+ config.platform.base_url = env_base
84
+
85
+ env_user_id = os.environ.get("MEM0_USER_ID")
86
+ if env_user_id:
87
+ config.defaults.user_id = env_user_id
88
+
89
+ env_agent_id = os.environ.get("MEM0_AGENT_ID")
90
+ if env_agent_id:
91
+ config.defaults.agent_id = env_agent_id
92
+
93
+ env_app_id = os.environ.get("MEM0_APP_ID")
94
+ if env_app_id:
95
+ config.defaults.app_id = env_app_id
96
+
97
+ env_run_id = os.environ.get("MEM0_RUN_ID")
98
+ if env_run_id:
99
+ config.defaults.run_id = env_run_id
100
+
101
+ env_graph = os.environ.get("MEM0_ENABLE_GRAPH")
102
+ if env_graph:
103
+ config.defaults.enable_graph = env_graph.lower() in ("true", "1", "yes")
104
+
105
+ return config
106
+
107
+
108
+ def save_config(config: Mem0Config) -> None:
109
+ """Write config to disk with secure permissions."""
110
+ ensure_config_dir()
111
+
112
+ data: dict[str, Any] = {
113
+ "version": config.version,
114
+ "defaults": {
115
+ "user_id": config.defaults.user_id,
116
+ "agent_id": config.defaults.agent_id,
117
+ "app_id": config.defaults.app_id,
118
+ "run_id": config.defaults.run_id,
119
+ "enable_graph": config.defaults.enable_graph,
120
+ },
121
+ "platform": {
122
+ "api_key": config.platform.api_key,
123
+ "base_url": config.platform.base_url,
124
+ },
125
+ }
126
+
127
+ with open(CONFIG_FILE, "w") as f:
128
+ json.dump(data, f, indent=2)
129
+
130
+ os.chmod(CONFIG_FILE, stat.S_IRUSR | stat.S_IWUSR) # 0600
131
+
132
+
133
+ def redact_key(key: str) -> str:
134
+ """Redact an API key for display: m0-xxx...xxx"""
135
+ if not key:
136
+ return "(not set)"
137
+ if len(key) <= 8:
138
+ return key[:2] + "***"
139
+ return key[:4] + "..." + key[-4:]
140
+
141
+
142
+ def get_nested_value(config: Mem0Config, dotted_key: str) -> Any:
143
+ """Get a config value by dotted path, e.g. 'platform.api_key'."""
144
+ parts = dotted_key.split(".")
145
+ obj: Any = config
146
+ for part in parts:
147
+ if hasattr(obj, part):
148
+ obj = getattr(obj, part)
149
+ else:
150
+ return None
151
+ return obj
152
+
153
+
154
+ def set_nested_value(config: Mem0Config, dotted_key: str, value: str) -> bool:
155
+ """Set a config value by dotted path. Returns True on success."""
156
+ parts = dotted_key.split(".")
157
+ obj: Any = config
158
+ for part in parts[:-1]:
159
+ if hasattr(obj, part):
160
+ obj = getattr(obj, part)
161
+ else:
162
+ return False
163
+
164
+ final_key = parts[-1]
165
+ if not hasattr(obj, final_key):
166
+ return False
167
+
168
+ current = getattr(obj, final_key)
169
+ # Type coercion
170
+ if isinstance(current, bool):
171
+ value = value.lower() in ("true", "1", "yes") # type: ignore[assignment]
172
+ elif isinstance(current, int):
173
+ value = int(value) # type: ignore[assignment]
174
+
175
+ setattr(obj, final_key, value)
176
+ return True
mem0_cli/output.py ADDED
@@ -0,0 +1,242 @@
1
+ """Output formatting for mem0 CLI — text, JSON, table, quiet modes."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from datetime import datetime
7
+ from typing import Any
8
+
9
+ from rich.console import Console
10
+ from rich.panel import Panel
11
+ from rich.table import Table
12
+ from rich.text import Text
13
+
14
+ from mem0_cli.branding import ACCENT_COLOR, BRAND_COLOR, DIM_COLOR, SUCCESS_COLOR, _sym
15
+
16
+
17
+ def format_memories_text(console: Console, memories: list[dict], title: str = "memories") -> None:
18
+ """Render memories in human-friendly text mode."""
19
+ count = len(memories)
20
+ console.print(f"\n[{BRAND_COLOR}]Found {count} {title}:[/]\n")
21
+
22
+ for i, mem in enumerate(memories, 1):
23
+ memory_text = mem.get("memory", mem.get("text", ""))
24
+ mem_id = mem.get("id", "")[:8]
25
+ score = mem.get("score")
26
+ created = _format_date(mem.get("created_at"))
27
+ category = mem.get("categories", [None])
28
+ if isinstance(category, list):
29
+ category = category[0] if category else None
30
+
31
+ line = Text()
32
+ line.append(f" {i}. ", style="bold")
33
+ line.append(memory_text, style="white")
34
+ console.print(line)
35
+
36
+ details = []
37
+ if score is not None:
38
+ details.append(f"Score: {score:.2f}")
39
+ if mem_id:
40
+ details.append(f"ID: {mem_id}")
41
+ if created:
42
+ details.append(f"Created: {created}")
43
+ if category:
44
+ details.append(f"Category: {category}")
45
+
46
+ if details:
47
+ detail_str = " · ".join(details)
48
+ console.print(f" [{DIM_COLOR}]{detail_str}[/]")
49
+ console.print()
50
+
51
+
52
+ def format_memories_table(console: Console, memories: list[dict]) -> None:
53
+ """Render memories in a rich table."""
54
+ table = Table(
55
+ border_style=BRAND_COLOR,
56
+ header_style=f"bold {ACCENT_COLOR}",
57
+ row_styles=["", "dim"],
58
+ padding=(0, 1),
59
+ )
60
+ table.add_column("ID", style="dim", max_width=10)
61
+ table.add_column("Memory", max_width=50, no_wrap=False)
62
+ table.add_column("Category", max_width=14)
63
+ table.add_column("Created", max_width=12)
64
+
65
+ for mem in memories:
66
+ mem_id = mem.get("id", "")[:8]
67
+ memory_text = mem.get("memory", mem.get("text", ""))
68
+ if len(memory_text) > 60:
69
+ memory_text = memory_text[:57] + "..."
70
+ categories = mem.get("categories", [])
71
+ cat = categories[0] if isinstance(categories, list) and categories else "—"
72
+ created = _format_date(mem.get("created_at")) or "—"
73
+ table.add_row(mem_id, memory_text, cat, created)
74
+
75
+ console.print()
76
+ console.print(table)
77
+ console.print()
78
+
79
+
80
+ def format_json(console: Console, data: Any) -> None:
81
+ """Output data as pretty-printed JSON."""
82
+ console.print_json(json.dumps(data, default=str))
83
+
84
+
85
+ def format_single_memory(console: Console, mem: dict, output: str = "text") -> None:
86
+ """Format a single memory for display."""
87
+ if output == "json":
88
+ format_json(console, mem)
89
+ return
90
+
91
+ memory_text = mem.get("memory", mem.get("text", ""))
92
+ mem_id = mem.get("id", "")
93
+
94
+ lines = []
95
+ lines.append(f" [white bold]{memory_text}[/]")
96
+ lines.append("")
97
+
98
+ if mem_id:
99
+ lines.append(f" [{DIM_COLOR}]ID:[/] {mem_id}")
100
+ created = _format_date(mem.get("created_at"))
101
+ if created:
102
+ lines.append(f" [{DIM_COLOR}]Created:[/] {created}")
103
+ updated = _format_date(mem.get("updated_at"))
104
+ if updated:
105
+ lines.append(f" [{DIM_COLOR}]Updated:[/] {updated}")
106
+ meta = mem.get("metadata")
107
+ if meta:
108
+ lines.append(f" [{DIM_COLOR}]Metadata:[/] {json.dumps(meta)}")
109
+ categories = mem.get("categories")
110
+ if categories:
111
+ cat_str = ", ".join(categories) if isinstance(categories, list) else categories
112
+ lines.append(f" [{DIM_COLOR}]Categories:[/] {cat_str}")
113
+
114
+ content = "\n".join(lines)
115
+ panel = Panel(
116
+ content,
117
+ title=f"[{BRAND_COLOR}]Memory[/]",
118
+ title_align="left",
119
+ border_style=BRAND_COLOR,
120
+ padding=(1, 1),
121
+ )
122
+ console.print()
123
+ console.print(panel)
124
+ console.print()
125
+
126
+
127
+ def format_add_result(console: Console, result: dict | list, output: str = "text") -> None:
128
+ """Format the result of an add operation."""
129
+ if output == "json":
130
+ format_json(console, result)
131
+ return
132
+ if output == "quiet":
133
+ return
134
+
135
+ # result from API is typically {"results": [...]}
136
+ results = result if isinstance(result, list) else result.get("results", [result])
137
+ if not results:
138
+ console.print(f" [{DIM_COLOR}]No memories extracted.[/]")
139
+ return
140
+
141
+ console.print()
142
+ for r in results:
143
+ # Detect async PENDING response from Platform API
144
+ if r.get("status") == "PENDING":
145
+ event_id = r.get("event_id", "")[:8]
146
+ icon = f"[{ACCENT_COLOR}]{_sym('⧗', '...')}[/]"
147
+ parts = [f" {icon} [{DIM_COLOR}]{'Queued':<10}[/]"]
148
+ parts.append("[white]Processing in background[/]")
149
+ if event_id:
150
+ parts.append(f"[{DIM_COLOR}](event {event_id})[/]")
151
+ console.print(" ".join(parts))
152
+ continue
153
+
154
+ event = r.get("event", "ADD")
155
+ memory = r.get("memory") or r.get("text") or r.get("content") or r.get("data") or ""
156
+ mem_id = (r.get("id") or r.get("memory_id") or "")[:8]
157
+
158
+ if event == "ADD":
159
+ icon = f"[{SUCCESS_COLOR}]+[/]"
160
+ label = "Added"
161
+ elif event == "UPDATE":
162
+ icon = f"[{ACCENT_COLOR}]~[/]"
163
+ label = "Updated"
164
+ elif event == "DELETE":
165
+ icon = "[red]-[/]"
166
+ label = "Deleted"
167
+ elif event == "NOOP":
168
+ icon = f"[{DIM_COLOR}]·[/]"
169
+ label = "No change"
170
+ else:
171
+ icon = f"[{DIM_COLOR}]?[/]"
172
+ label = event
173
+
174
+ # Build the display line
175
+ parts = [f" {icon} [{DIM_COLOR}]{label:<10}[/]"]
176
+ if memory:
177
+ parts.append(f"[white]{memory}[/]")
178
+ if mem_id:
179
+ parts.append(f"[{DIM_COLOR}]({mem_id})[/]")
180
+ console.print(" ".join(parts))
181
+ console.print()
182
+
183
+
184
+ def format_json_envelope(
185
+ console: Console,
186
+ *,
187
+ command: str,
188
+ data: Any,
189
+ duration_ms: int | None = None,
190
+ scope: dict | None = None,
191
+ count: int | None = None,
192
+ status: str = "success",
193
+ error: str | None = None,
194
+ ) -> None:
195
+ """Output structured JSON envelope for AI agent consumption."""
196
+ envelope: dict[str, Any] = {
197
+ "status": status,
198
+ "command": command,
199
+ }
200
+ if duration_ms is not None:
201
+ envelope["duration_ms"] = duration_ms
202
+ if scope is not None:
203
+ envelope["scope"] = scope
204
+ if count is not None:
205
+ envelope["count"] = count
206
+ if error:
207
+ envelope["error"] = error
208
+ envelope["data"] = data
209
+ console.print_json(json.dumps(envelope, default=str))
210
+
211
+
212
+ def print_result_summary(
213
+ console: Console,
214
+ count: int,
215
+ *,
216
+ duration_secs: float | None = None,
217
+ page: int | None = None,
218
+ **scope_ids: str | None,
219
+ ) -> None:
220
+ """Print a summary footer after result lists."""
221
+ parts = [f"{count} result{'s' if count != 1 else ''}"]
222
+ if page is not None:
223
+ parts.append(f"page {page}")
224
+ scope_parts = [f"{k.replace('_', ' ')}={v}" for k, v in scope_ids.items() if v]
225
+ if scope_parts:
226
+ parts.append(", ".join(scope_parts))
227
+ if duration_secs is not None:
228
+ parts.append(f"{duration_secs:.2f}s")
229
+
230
+ summary = " · ".join(parts)
231
+ console.print(f" [{DIM_COLOR}]{summary}[/]")
232
+ console.print()
233
+
234
+
235
+ def _format_date(dt_str: str | None) -> str | None:
236
+ if not dt_str:
237
+ return None
238
+ try:
239
+ dt = datetime.fromisoformat(dt_str.replace("Z", "+00:00"))
240
+ return dt.strftime("%Y-%m-%d")
241
+ except (ValueError, AttributeError):
242
+ return str(dt_str)[:10] if dt_str else None
@@ -0,0 +1,57 @@
1
+ Metadata-Version: 2.4
2
+ Name: mem0-cli
3
+ Version: 0.1.0
4
+ Summary: The official CLI for mem0 — the memory layer for AI agents
5
+ Author-email: "mem0.ai" <founders@mem0.ai>
6
+ License-Expression: Apache-2.0
7
+ Keywords: agents,ai,cli,mem0,memory
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: Environment :: Console
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: Apache Software License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Topic :: Software Development :: Libraries
17
+ Requires-Python: >=3.10
18
+ Requires-Dist: httpx>=0.24.0
19
+ Requires-Dist: rich>=13.0.0
20
+ Requires-Dist: typer>=0.9.0
21
+ Provides-Extra: dev
22
+ Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
23
+ Requires-Dist: pytest>=7.0; extra == 'dev'
24
+ Requires-Dist: ruff>=0.1.0; extra == 'dev'
25
+ Provides-Extra: oss
26
+ Requires-Dist: mem0ai>=0.1.0; extra == 'oss'
27
+ Description-Content-Type: text/markdown
28
+
29
+ # mem0 CLI
30
+
31
+ The official command-line interface for [mem0](https://mem0.ai) — the memory layer for AI agents.
32
+
33
+ ## Installation
34
+
35
+ ```bash
36
+ pip install mem0-cli
37
+ ```
38
+
39
+ ## Quick Start
40
+
41
+ ```bash
42
+ # Set up your configuration
43
+ mem0 init
44
+
45
+ # Add a memory
46
+ mem0 add "I prefer dark mode and use vim keybindings" --user-id alice
47
+
48
+ # Search memories
49
+ mem0 search "What are Alice's preferences?" --user-id alice
50
+
51
+ # List all memories
52
+ mem0 list --user-id alice
53
+ ```
54
+
55
+ ## License
56
+
57
+ Apache-2.0
@@ -0,0 +1,19 @@
1
+ mem0_cli/__init__.py,sha256=1GKZL4QjClY1AD4w4HbQquF5nvNT1kn44dsuY0fBAL4,96
2
+ mem0_cli/__main__.py,sha256=bfYlOrVq4kv2QX1wjtYBCAjS0eSsLi0qmyT6TSQDv9A,86
3
+ mem0_cli/app.py,sha256=aCKP2A429wprh15xM2Xx3Xoxgviiu2434vHp8t8O_Ds,37882
4
+ mem0_cli/branding.py,sha256=JnDxyNFXAvz0awiHM2WB0vva-HZckGhdTqMdAioYhzM,4432
5
+ mem0_cli/config.py,sha256=nIZXMkKHS8fPL-p2Z32z3ofK0FZNwo5H1kwuU--BtTA,4962
6
+ mem0_cli/output.py,sha256=5RphhMqxdw2gF3qrTp0lJTJKoBZELkPaRw7FUedPm4I,7865
7
+ mem0_cli/backend/__init__.py,sha256=sGfi-FMQvISsSzP0fCQ1J7_01cTPorOQuXekyyHhX-s,140
8
+ mem0_cli/backend/base.py,sha256=UMgsy6gPNcfqw-gfqaJag8D1eppTCxUnHbX_ANHNXjo,2854
9
+ mem0_cli/backend/platform.py,sha256=DyE9nROJEas9S_6C33-4c0QO7nfUUuR1UtKB7tRBkao,10771
10
+ mem0_cli/commands/__init__.py,sha256=gQ6tnU0Rvm0-ESWFUBU-KDl5dpNOpUTG509hXOQQjwY,27
11
+ mem0_cli/commands/config_cmd.py,sha256=MCSsUnBOiof1jDP9NEnhAH_OMy1SjL_q9okrP54KkcE,3223
12
+ mem0_cli/commands/entities.py,sha256=yniG3zJOcw-4r3C3DV4VJ_R2ULj-yzenIMLWJZq0Yg4,4137
13
+ mem0_cli/commands/init_cmd.py,sha256=R8nyJDC--22imTS0Fio0tstjLQfdm8bxOiA8FjI4Y14,6132
14
+ mem0_cli/commands/memory.py,sha256=B6upEu3t0AMFeyr7JOtoIRbtlYAXasa2OEQZEukagVk,14069
15
+ mem0_cli/commands/utils.py,sha256=uTTQ0aICKg6veL-Ee2Bb8ADFDZccnFIohVLetc2THIQ,4056
16
+ mem0_cli-0.1.0.dist-info/METADATA,sha256=E884sL35pYWrT8EU9TVAJtEDlhIePVilMurd7zfZAec,1512
17
+ mem0_cli-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
18
+ mem0_cli-0.1.0.dist-info/entry_points.txt,sha256=KK5vcLwfiOuxMhPvSzhg3Cg67iCUz10YRxExoBrFwQo,43
19
+ mem0_cli-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ mem0 = mem0_cli.app:main