mem0-cli 0.2.2__tar.gz → 0.2.4__tar.gz
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-0.2.2 → mem0_cli-0.2.4}/PKG-INFO +1 -1
- {mem0_cli-0.2.2 → mem0_cli-0.2.4}/pyproject.toml +1 -1
- {mem0_cli-0.2.2 → mem0_cli-0.2.4}/src/mem0_cli/__init__.py +1 -1
- {mem0_cli-0.2.2 → mem0_cli-0.2.4}/src/mem0_cli/app.py +0 -38
- {mem0_cli-0.2.2 → mem0_cli-0.2.4}/src/mem0_cli/backend/base.py +0 -3
- {mem0_cli-0.2.2 → mem0_cli-0.2.4}/src/mem0_cli/backend/platform.py +15 -18
- {mem0_cli-0.2.2 → mem0_cli-0.2.4}/src/mem0_cli/commands/config_cmd.py +0 -5
- {mem0_cli-0.2.2 → mem0_cli-0.2.4}/src/mem0_cli/commands/memory.py +0 -6
- {mem0_cli-0.2.2 → mem0_cli-0.2.4}/src/mem0_cli/config.py +11 -8
- {mem0_cli-0.2.2 → mem0_cli-0.2.4}/src/mem0_cli/telemetry.py +45 -4
- {mem0_cli-0.2.2 → mem0_cli-0.2.4}/src/mem0_cli/telemetry_sender.py +22 -0
- {mem0_cli-0.2.2 → mem0_cli-0.2.4}/.gitignore +0 -0
- {mem0_cli-0.2.2 → mem0_cli-0.2.4}/README.md +0 -0
- {mem0_cli-0.2.2 → mem0_cli-0.2.4}/src/mem0_cli/__main__.py +0 -0
- {mem0_cli-0.2.2 → mem0_cli-0.2.4}/src/mem0_cli/backend/__init__.py +0 -0
- {mem0_cli-0.2.2 → mem0_cli-0.2.4}/src/mem0_cli/branding.py +0 -0
- {mem0_cli-0.2.2 → mem0_cli-0.2.4}/src/mem0_cli/commands/__init__.py +0 -0
- {mem0_cli-0.2.2 → mem0_cli-0.2.4}/src/mem0_cli/commands/entities.py +0 -0
- {mem0_cli-0.2.2 → mem0_cli-0.2.4}/src/mem0_cli/commands/events_cmd.py +0 -0
- {mem0_cli-0.2.2 → mem0_cli-0.2.4}/src/mem0_cli/commands/init_cmd.py +0 -0
- {mem0_cli-0.2.2 → mem0_cli-0.2.4}/src/mem0_cli/commands/utils.py +0 -0
- {mem0_cli-0.2.2 → mem0_cli-0.2.4}/src/mem0_cli/output.py +0 -0
- {mem0_cli-0.2.2 → mem0_cli-0.2.4}/src/mem0_cli/state.py +0 -0
|
@@ -267,8 +267,6 @@ def add(
|
|
|
267
267
|
categories: str | None = typer.Option(
|
|
268
268
|
None, "--categories", help="Categories (JSON array or comma-separated)."
|
|
269
269
|
),
|
|
270
|
-
graph: bool = typer.Option(False, "--graph", help="Enable graph memory extraction."),
|
|
271
|
-
no_graph: bool = typer.Option(False, "--no-graph", help="Disable graph memory extraction."),
|
|
272
270
|
output: str = typer.Option(
|
|
273
271
|
"text", "--output", "-o", help="Output format: text, json, quiet.", rich_help_panel="Output"
|
|
274
272
|
),
|
|
@@ -295,13 +293,6 @@ def add(
|
|
|
295
293
|
backend, config = _get_backend_and_config(api_key, base_url)
|
|
296
294
|
ids = _resolve_ids(config, user_id=user_id, agent_id=agent_id, app_id=app_id, run_id=run_id)
|
|
297
295
|
|
|
298
|
-
if no_graph:
|
|
299
|
-
graph_enabled = False
|
|
300
|
-
elif graph:
|
|
301
|
-
graph_enabled = True
|
|
302
|
-
else:
|
|
303
|
-
graph_enabled = config.defaults.enable_graph
|
|
304
|
-
|
|
305
296
|
cmd_add(
|
|
306
297
|
backend,
|
|
307
298
|
text,
|
|
@@ -313,7 +304,6 @@ def add(
|
|
|
313
304
|
no_infer=no_infer,
|
|
314
305
|
expires=expires,
|
|
315
306
|
categories=categories,
|
|
316
|
-
enable_graph=graph_enabled,
|
|
317
307
|
output=output,
|
|
318
308
|
)
|
|
319
309
|
|
|
@@ -357,12 +347,6 @@ def search(
|
|
|
357
347
|
help="Specific fields to return (comma-separated).",
|
|
358
348
|
rich_help_panel="Search",
|
|
359
349
|
),
|
|
360
|
-
graph: bool = typer.Option(
|
|
361
|
-
False, "--graph", help="Enable graph in search.", rich_help_panel="Search"
|
|
362
|
-
),
|
|
363
|
-
no_graph: bool = typer.Option(
|
|
364
|
-
False, "--no-graph", help="Disable graph in search.", rich_help_panel="Search"
|
|
365
|
-
),
|
|
366
350
|
output: str = typer.Option(
|
|
367
351
|
"text", "--output", "-o", help="Output: text, json, table.", rich_help_panel="Output"
|
|
368
352
|
),
|
|
@@ -396,13 +380,6 @@ def search(
|
|
|
396
380
|
backend, config = _get_backend_and_config(api_key, base_url)
|
|
397
381
|
ids = _resolve_ids(config, user_id=user_id, agent_id=agent_id, app_id=app_id, run_id=run_id)
|
|
398
382
|
|
|
399
|
-
if no_graph:
|
|
400
|
-
graph_enabled = False
|
|
401
|
-
elif graph:
|
|
402
|
-
graph_enabled = True
|
|
403
|
-
else:
|
|
404
|
-
graph_enabled = config.defaults.enable_graph
|
|
405
|
-
|
|
406
383
|
cmd_search(
|
|
407
384
|
backend,
|
|
408
385
|
query,
|
|
@@ -413,7 +390,6 @@ def search(
|
|
|
413
390
|
keyword=keyword,
|
|
414
391
|
filter_json=filter_json,
|
|
415
392
|
fields=fields,
|
|
416
|
-
enable_graph=graph_enabled,
|
|
417
393
|
output=output,
|
|
418
394
|
)
|
|
419
395
|
|
|
@@ -480,12 +456,6 @@ def list_cmd(
|
|
|
480
456
|
before: str | None = typer.Option(
|
|
481
457
|
None, "--before", help="Created before (YYYY-MM-DD).", rich_help_panel="Filters"
|
|
482
458
|
),
|
|
483
|
-
graph: bool = typer.Option(
|
|
484
|
-
False, "--graph", help="Enable graph in listing.", rich_help_panel="Filters"
|
|
485
|
-
),
|
|
486
|
-
no_graph: bool = typer.Option(
|
|
487
|
-
False, "--no-graph", help="Disable graph in listing.", rich_help_panel="Filters"
|
|
488
|
-
),
|
|
489
459
|
output: str = typer.Option(
|
|
490
460
|
"table", "--output", "-o", help="Output: text, json, table.", rich_help_panel="Output"
|
|
491
461
|
),
|
|
@@ -511,13 +481,6 @@ def list_cmd(
|
|
|
511
481
|
backend, config = _get_backend_and_config(api_key, base_url)
|
|
512
482
|
ids = _resolve_ids(config, user_id=user_id, agent_id=agent_id, app_id=app_id, run_id=run_id)
|
|
513
483
|
|
|
514
|
-
if no_graph:
|
|
515
|
-
graph_enabled = False
|
|
516
|
-
elif graph:
|
|
517
|
-
graph_enabled = True
|
|
518
|
-
else:
|
|
519
|
-
graph_enabled = config.defaults.enable_graph
|
|
520
|
-
|
|
521
484
|
cmd_list(
|
|
522
485
|
backend,
|
|
523
486
|
**ids,
|
|
@@ -526,7 +489,6 @@ def list_cmd(
|
|
|
526
489
|
category=category,
|
|
527
490
|
after=after,
|
|
528
491
|
before=before,
|
|
529
|
-
enable_graph=graph_enabled,
|
|
530
492
|
output=output,
|
|
531
493
|
)
|
|
532
494
|
|
|
@@ -26,7 +26,6 @@ class Backend(ABC):
|
|
|
26
26
|
infer: bool = True,
|
|
27
27
|
expires: str | None = None,
|
|
28
28
|
categories: list[str] | None = None,
|
|
29
|
-
enable_graph: bool = False,
|
|
30
29
|
) -> dict: ...
|
|
31
30
|
|
|
32
31
|
@abstractmethod
|
|
@@ -44,7 +43,6 @@ class Backend(ABC):
|
|
|
44
43
|
keyword: bool = False,
|
|
45
44
|
filters: dict | None = None,
|
|
46
45
|
fields: list[str] | None = None,
|
|
47
|
-
enable_graph: bool = False,
|
|
48
46
|
) -> list[dict]: ...
|
|
49
47
|
|
|
50
48
|
@abstractmethod
|
|
@@ -63,7 +61,6 @@ class Backend(ABC):
|
|
|
63
61
|
category: str | None = None,
|
|
64
62
|
after: str | None = None,
|
|
65
63
|
before: str | None = None,
|
|
66
|
-
enable_graph: bool = False,
|
|
67
64
|
) -> list[dict]: ...
|
|
68
65
|
|
|
69
66
|
@abstractmethod
|
|
@@ -64,7 +64,6 @@ class PlatformBackend(Backend):
|
|
|
64
64
|
infer: bool = True,
|
|
65
65
|
expires: str | None = None,
|
|
66
66
|
categories: list[str] | None = None,
|
|
67
|
-
enable_graph: bool = False,
|
|
68
67
|
) -> dict:
|
|
69
68
|
payload: dict[str, Any] = {}
|
|
70
69
|
|
|
@@ -91,10 +90,9 @@ class PlatformBackend(Backend):
|
|
|
91
90
|
payload["expiration_date"] = expires
|
|
92
91
|
if categories:
|
|
93
92
|
payload["categories"] = categories
|
|
94
|
-
|
|
95
|
-
payload["enable_graph"] = True
|
|
93
|
+
payload["source"] = "CLI"
|
|
96
94
|
|
|
97
|
-
return self._request("POST", "/
|
|
95
|
+
return self._request("POST", "/v3/memories/add/", json=payload)
|
|
98
96
|
|
|
99
97
|
def _build_filters(
|
|
100
98
|
self,
|
|
@@ -105,7 +103,7 @@ class PlatformBackend(Backend):
|
|
|
105
103
|
run_id: str | None = None,
|
|
106
104
|
extra_filters: dict | None = None,
|
|
107
105
|
) -> dict | None:
|
|
108
|
-
"""Build a filters dict for
|
|
106
|
+
"""Build a filters dict for v3 API endpoints.
|
|
109
107
|
|
|
110
108
|
Entity IDs are ANDed (all provided IDs must match).
|
|
111
109
|
Extra filters (date ranges, categories) are also ANDed.
|
|
@@ -151,7 +149,6 @@ class PlatformBackend(Backend):
|
|
|
151
149
|
keyword: bool = False,
|
|
152
150
|
filters: dict | None = None,
|
|
153
151
|
fields: list[str] | None = None,
|
|
154
|
-
enable_graph: bool = False,
|
|
155
152
|
) -> list[dict]:
|
|
156
153
|
payload: dict[str, Any] = {"query": query, "top_k": top_k, "threshold": threshold}
|
|
157
154
|
|
|
@@ -170,10 +167,9 @@ class PlatformBackend(Backend):
|
|
|
170
167
|
payload["keyword_search"] = True
|
|
171
168
|
if fields:
|
|
172
169
|
payload["fields"] = fields
|
|
173
|
-
|
|
174
|
-
payload["enable_graph"] = True
|
|
170
|
+
payload["source"] = "CLI"
|
|
175
171
|
|
|
176
|
-
result = self._request("POST", "/
|
|
172
|
+
result = self._request("POST", "/v3/memories/search/", json=payload)
|
|
177
173
|
return (
|
|
178
174
|
result
|
|
179
175
|
if isinstance(result, list)
|
|
@@ -181,7 +177,7 @@ class PlatformBackend(Backend):
|
|
|
181
177
|
)
|
|
182
178
|
|
|
183
179
|
def get(self, memory_id: str) -> dict:
|
|
184
|
-
return self._request("GET", f"/v1/memories/{memory_id}/")
|
|
180
|
+
return self._request("GET", f"/v1/memories/{memory_id}/", params={"source": "CLI"})
|
|
185
181
|
|
|
186
182
|
def list_memories(
|
|
187
183
|
self,
|
|
@@ -195,12 +191,11 @@ class PlatformBackend(Backend):
|
|
|
195
191
|
category: str | None = None,
|
|
196
192
|
after: str | None = None,
|
|
197
193
|
before: str | None = None,
|
|
198
|
-
enable_graph: bool = False,
|
|
199
194
|
) -> list[dict]:
|
|
200
195
|
payload: dict[str, Any] = {}
|
|
201
196
|
params = {"page": str(page), "page_size": str(page_size)}
|
|
202
197
|
|
|
203
|
-
# Build filters
|
|
198
|
+
# Build filters — entity IDs and date filters go inside "filters"
|
|
204
199
|
extra: dict[str, Any] = {}
|
|
205
200
|
if category:
|
|
206
201
|
extra["categories"] = {"contains": category}
|
|
@@ -218,10 +213,9 @@ class PlatformBackend(Backend):
|
|
|
218
213
|
)
|
|
219
214
|
if api_filters:
|
|
220
215
|
payload["filters"] = api_filters
|
|
221
|
-
|
|
222
|
-
payload["enable_graph"] = True
|
|
216
|
+
payload["source"] = "CLI"
|
|
223
217
|
|
|
224
|
-
result = self._request("POST", "/
|
|
218
|
+
result = self._request("POST", "/v3/memories/", json=payload, params=params)
|
|
225
219
|
return (
|
|
226
220
|
result
|
|
227
221
|
if isinstance(result, list)
|
|
@@ -236,6 +230,7 @@ class PlatformBackend(Backend):
|
|
|
236
230
|
payload["text"] = content
|
|
237
231
|
if metadata:
|
|
238
232
|
payload["metadata"] = metadata
|
|
233
|
+
payload["source"] = "CLI"
|
|
239
234
|
return self._request("PUT", f"/v1/memories/{memory_id}/", json=payload)
|
|
240
235
|
|
|
241
236
|
def delete(
|
|
@@ -249,7 +244,7 @@ class PlatformBackend(Backend):
|
|
|
249
244
|
run_id: str | None = None,
|
|
250
245
|
) -> dict:
|
|
251
246
|
if all:
|
|
252
|
-
params: dict[str, str] = {}
|
|
247
|
+
params: dict[str, str] = {"source": "CLI"}
|
|
253
248
|
if user_id:
|
|
254
249
|
params["user_id"] = user_id
|
|
255
250
|
if agent_id:
|
|
@@ -260,7 +255,7 @@ class PlatformBackend(Backend):
|
|
|
260
255
|
params["run_id"] = run_id
|
|
261
256
|
return self._request("DELETE", "/v1/memories/", params=params)
|
|
262
257
|
elif memory_id:
|
|
263
|
-
return self._request("DELETE", f"/v1/memories/{memory_id}/")
|
|
258
|
+
return self._request("DELETE", f"/v1/memories/{memory_id}/", params={"source": "CLI"})
|
|
264
259
|
else:
|
|
265
260
|
raise ValueError("Either memory_id or --all is required")
|
|
266
261
|
|
|
@@ -285,7 +280,9 @@ class PlatformBackend(Backend):
|
|
|
285
280
|
# Delete each provided entity via the v2 path-based endpoint
|
|
286
281
|
result: dict = {}
|
|
287
282
|
for entity_type, entity_id in entities.items():
|
|
288
|
-
result = self._request(
|
|
283
|
+
result = self._request(
|
|
284
|
+
"DELETE", f"/v2/entities/{entity_type}/{entity_id}/", params={"source": "CLI"}
|
|
285
|
+
)
|
|
289
286
|
return result
|
|
290
287
|
|
|
291
288
|
def ping(self, timeout: float | None = None) -> dict:
|
|
@@ -39,7 +39,6 @@ def cmd_config_show(*, output: str = "text") -> None:
|
|
|
39
39
|
"agent_id": config.defaults.agent_id or None,
|
|
40
40
|
"app_id": config.defaults.app_id or None,
|
|
41
41
|
"run_id": config.defaults.run_id or None,
|
|
42
|
-
"enable_graph": config.defaults.enable_graph,
|
|
43
42
|
},
|
|
44
43
|
"platform": {
|
|
45
44
|
"api_key": redact_key(config.platform.api_key),
|
|
@@ -73,10 +72,6 @@ def cmd_config_show(*, output: str = "text") -> None:
|
|
|
73
72
|
"defaults.run_id",
|
|
74
73
|
config.defaults.run_id or f"[{DIM_COLOR}](not set)[/]",
|
|
75
74
|
)
|
|
76
|
-
table.add_row(
|
|
77
|
-
"defaults.enable_graph",
|
|
78
|
-
str(config.defaults.enable_graph).lower(),
|
|
79
|
-
)
|
|
80
75
|
table.add_row("", "")
|
|
81
76
|
|
|
82
77
|
# Platform
|
|
@@ -62,7 +62,6 @@ def cmd_add(
|
|
|
62
62
|
no_infer: bool,
|
|
63
63
|
expires: str | None,
|
|
64
64
|
categories: str | None,
|
|
65
|
-
enable_graph: bool = False,
|
|
66
65
|
output: str = "text",
|
|
67
66
|
) -> None:
|
|
68
67
|
"""Add a memory."""
|
|
@@ -145,7 +144,6 @@ def cmd_add(
|
|
|
145
144
|
infer=not no_infer,
|
|
146
145
|
expires=expires,
|
|
147
146
|
categories=cats,
|
|
148
|
-
enable_graph=enable_graph,
|
|
149
147
|
)
|
|
150
148
|
except Exception as e:
|
|
151
149
|
ts.error_msg = str(e)
|
|
@@ -226,7 +224,6 @@ def cmd_search(
|
|
|
226
224
|
keyword: bool,
|
|
227
225
|
filter_json: str | None,
|
|
228
226
|
fields: str | None,
|
|
229
|
-
enable_graph: bool = False,
|
|
230
227
|
output: str = "text",
|
|
231
228
|
) -> None:
|
|
232
229
|
"""Search memories."""
|
|
@@ -269,7 +266,6 @@ def cmd_search(
|
|
|
269
266
|
keyword=keyword,
|
|
270
267
|
filters=filters,
|
|
271
268
|
fields=field_list,
|
|
272
|
-
enable_graph=enable_graph,
|
|
273
269
|
)
|
|
274
270
|
except Exception as e:
|
|
275
271
|
print_error(err_console, str(e))
|
|
@@ -356,7 +352,6 @@ def cmd_list(
|
|
|
356
352
|
category: str | None,
|
|
357
353
|
after: str | None,
|
|
358
354
|
before: str | None,
|
|
359
|
-
enable_graph: bool = False,
|
|
360
355
|
output: str = "table",
|
|
361
356
|
) -> None:
|
|
362
357
|
"""List memories."""
|
|
@@ -385,7 +380,6 @@ def cmd_list(
|
|
|
385
380
|
category=category,
|
|
386
381
|
after=after,
|
|
387
382
|
before=before,
|
|
388
|
-
enable_graph=enable_graph,
|
|
389
383
|
)
|
|
390
384
|
except Exception as e:
|
|
391
385
|
print_error(err_console, str(e))
|
|
@@ -36,7 +36,11 @@ class DefaultsConfig:
|
|
|
36
36
|
agent_id: str = ""
|
|
37
37
|
app_id: str = ""
|
|
38
38
|
run_id: str = ""
|
|
39
|
-
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class TelemetryConfig:
|
|
43
|
+
anonymous_id: str = ""
|
|
40
44
|
|
|
41
45
|
|
|
42
46
|
@dataclass
|
|
@@ -44,6 +48,7 @@ class Mem0Config:
|
|
|
44
48
|
version: int = CONFIG_VERSION
|
|
45
49
|
defaults: DefaultsConfig = field(default_factory=DefaultsConfig)
|
|
46
50
|
platform: PlatformConfig = field(default_factory=PlatformConfig)
|
|
51
|
+
telemetry: TelemetryConfig = field(default_factory=TelemetryConfig)
|
|
47
52
|
|
|
48
53
|
|
|
49
54
|
SHORT_KEY_ALIASES: dict[str, str] = {
|
|
@@ -54,7 +59,6 @@ SHORT_KEY_ALIASES: dict[str, str] = {
|
|
|
54
59
|
"agent_id": "defaults.agent_id",
|
|
55
60
|
"app_id": "defaults.app_id",
|
|
56
61
|
"run_id": "defaults.run_id",
|
|
57
|
-
"enable_graph": "defaults.enable_graph",
|
|
58
62
|
}
|
|
59
63
|
|
|
60
64
|
|
|
@@ -85,7 +89,8 @@ def load_config() -> Mem0Config:
|
|
|
85
89
|
config.defaults.agent_id = defaults.get("agent_id", "")
|
|
86
90
|
config.defaults.app_id = defaults.get("app_id", "")
|
|
87
91
|
config.defaults.run_id = defaults.get("run_id", "")
|
|
88
|
-
|
|
92
|
+
telemetry = data.get("telemetry", {})
|
|
93
|
+
config.telemetry.anonymous_id = telemetry.get("anonymous_id", "")
|
|
89
94
|
|
|
90
95
|
# Environment variable overrides
|
|
91
96
|
env_key = os.environ.get("MEM0_API_KEY")
|
|
@@ -112,10 +117,6 @@ def load_config() -> Mem0Config:
|
|
|
112
117
|
if env_run_id:
|
|
113
118
|
config.defaults.run_id = env_run_id
|
|
114
119
|
|
|
115
|
-
env_graph = os.environ.get("MEM0_ENABLE_GRAPH")
|
|
116
|
-
if env_graph:
|
|
117
|
-
config.defaults.enable_graph = env_graph.lower() in ("true", "1", "yes")
|
|
118
|
-
|
|
119
120
|
return config
|
|
120
121
|
|
|
121
122
|
|
|
@@ -130,13 +131,15 @@ def save_config(config: Mem0Config) -> None:
|
|
|
130
131
|
"agent_id": config.defaults.agent_id,
|
|
131
132
|
"app_id": config.defaults.app_id,
|
|
132
133
|
"run_id": config.defaults.run_id,
|
|
133
|
-
"enable_graph": config.defaults.enable_graph,
|
|
134
134
|
},
|
|
135
135
|
"platform": {
|
|
136
136
|
"api_key": config.platform.api_key,
|
|
137
137
|
"base_url": config.platform.base_url,
|
|
138
138
|
"user_email": config.platform.user_email,
|
|
139
139
|
},
|
|
140
|
+
"telemetry": {
|
|
141
|
+
"anonymous_id": config.telemetry.anonymous_id,
|
|
142
|
+
},
|
|
140
143
|
}
|
|
141
144
|
|
|
142
145
|
with open(CONFIG_FILE, "w") as f:
|
|
@@ -9,12 +9,14 @@ Disable with: MEM0_TELEMETRY=false
|
|
|
9
9
|
|
|
10
10
|
from __future__ import annotations
|
|
11
11
|
|
|
12
|
+
import contextlib
|
|
12
13
|
import hashlib
|
|
13
14
|
import json
|
|
14
15
|
import os
|
|
15
16
|
import platform
|
|
16
17
|
import subprocess
|
|
17
18
|
import sys
|
|
19
|
+
import uuid
|
|
18
20
|
from typing import Any
|
|
19
21
|
|
|
20
22
|
POSTHOG_API_KEY = "phc_hgJkUVJFYtmaJqrvf6CYN67TIQ8yhXAkWzUn9AMU4yX"
|
|
@@ -26,11 +28,31 @@ def _is_telemetry_enabled() -> bool:
|
|
|
26
28
|
return val not in ("false", "0", "no")
|
|
27
29
|
|
|
28
30
|
|
|
31
|
+
def _get_or_create_anonymous_id() -> str:
|
|
32
|
+
"""Return a persistent per-machine anonymous ID, generating one if needed.
|
|
33
|
+
|
|
34
|
+
Stored in ~/.mem0/config.json under `telemetry.anonymous_id` so that
|
|
35
|
+
repeat runs on the same machine share one PostHog identity instead of
|
|
36
|
+
collapsing into a single shared fallback string.
|
|
37
|
+
"""
|
|
38
|
+
from mem0_cli.config import load_config, save_config
|
|
39
|
+
|
|
40
|
+
config = load_config()
|
|
41
|
+
if config.telemetry.anonymous_id:
|
|
42
|
+
return config.telemetry.anonymous_id
|
|
43
|
+
|
|
44
|
+
new_id = f"cli-anon-{uuid.uuid4().hex}"
|
|
45
|
+
config.telemetry.anonymous_id = new_id
|
|
46
|
+
with contextlib.suppress(Exception):
|
|
47
|
+
save_config(config)
|
|
48
|
+
return new_id
|
|
49
|
+
|
|
50
|
+
|
|
29
51
|
def _get_distinct_id() -> str:
|
|
30
52
|
"""Return a stable anonymous identifier for the current user.
|
|
31
53
|
|
|
32
|
-
Priority: cached user_email (from /v1/ping/) > MD5(api_key) >
|
|
33
|
-
|
|
54
|
+
Priority: cached user_email (from /v1/ping/) > MD5(api_key) >
|
|
55
|
+
persistent per-machine anonymous ID.
|
|
34
56
|
"""
|
|
35
57
|
try:
|
|
36
58
|
from mem0_cli.config import load_config
|
|
@@ -42,7 +64,10 @@ def _get_distinct_id() -> str:
|
|
|
42
64
|
return hashlib.md5(config.platform.api_key.encode()).hexdigest()
|
|
43
65
|
except Exception:
|
|
44
66
|
pass
|
|
45
|
-
|
|
67
|
+
try:
|
|
68
|
+
return _get_or_create_anonymous_id()
|
|
69
|
+
except Exception:
|
|
70
|
+
return f"cli-anon-{uuid.uuid4().hex}"
|
|
46
71
|
|
|
47
72
|
|
|
48
73
|
def capture_event(
|
|
@@ -61,12 +86,27 @@ def capture_event(
|
|
|
61
86
|
|
|
62
87
|
try:
|
|
63
88
|
from mem0_cli import __version__
|
|
64
|
-
from mem0_cli.config import CONFIG_FILE, load_config
|
|
89
|
+
from mem0_cli.config import CONFIG_FILE, load_config, save_config
|
|
65
90
|
from mem0_cli.state import is_agent_mode
|
|
66
91
|
|
|
67
92
|
config = load_config()
|
|
68
93
|
distinct_id = pre_resolved_email or _get_distinct_id()
|
|
69
94
|
|
|
95
|
+
# Detect anonymous → identified transition. If a stored anonymous_id
|
|
96
|
+
# exists and we just resolved to a real identity, fire a one-shot
|
|
97
|
+
# $identify event so PostHog stitches the pre-signup history onto
|
|
98
|
+
# the authenticated profile. Clear the stored id so we don't re-alias.
|
|
99
|
+
anon_id_to_alias: str | None = None
|
|
100
|
+
if (
|
|
101
|
+
distinct_id
|
|
102
|
+
and not distinct_id.startswith("cli-anon-")
|
|
103
|
+
and config.telemetry.anonymous_id
|
|
104
|
+
):
|
|
105
|
+
anon_id_to_alias = config.telemetry.anonymous_id
|
|
106
|
+
config.telemetry.anonymous_id = ""
|
|
107
|
+
with contextlib.suppress(Exception):
|
|
108
|
+
save_config(config)
|
|
109
|
+
|
|
70
110
|
payload = {
|
|
71
111
|
"api_key": POSTHOG_API_KEY,
|
|
72
112
|
"distinct_id": distinct_id,
|
|
@@ -92,6 +132,7 @@ def capture_event(
|
|
|
92
132
|
"mem0_api_key": config.platform.api_key or "",
|
|
93
133
|
"mem0_base_url": config.platform.base_url or "https://api.mem0.ai",
|
|
94
134
|
"config_path": str(CONFIG_FILE),
|
|
135
|
+
"anon_distinct_id_to_alias": anon_id_to_alias,
|
|
95
136
|
}
|
|
96
137
|
|
|
97
138
|
subprocess.Popen(
|
|
@@ -27,9 +27,31 @@ def main() -> None:
|
|
|
27
27
|
if ctx.get("needs_email") and ctx.get("mem0_api_key"):
|
|
28
28
|
_resolve_and_cache_email(ctx, payload)
|
|
29
29
|
|
|
30
|
+
# Fire $identify *after* email resolution so PostHog links the stored
|
|
31
|
+
# anonymous id directly to the final identity (email, not the api-key
|
|
32
|
+
# hash). The regular event is sent next so it lands under the merged
|
|
33
|
+
# profile.
|
|
34
|
+
anon_id = ctx.get("anon_distinct_id_to_alias")
|
|
35
|
+
if anon_id:
|
|
36
|
+
_send_identify_event(ctx, payload, anon_id)
|
|
37
|
+
|
|
30
38
|
_send_posthog_event(ctx["posthog_host"], payload)
|
|
31
39
|
|
|
32
40
|
|
|
41
|
+
def _send_identify_event(ctx: dict, payload: dict, anon_id: str) -> None:
|
|
42
|
+
"""Send a PostHog $identify event aliasing anon_id → payload['distinct_id']."""
|
|
43
|
+
identify_payload = {
|
|
44
|
+
"api_key": payload["api_key"],
|
|
45
|
+
"event": "$identify",
|
|
46
|
+
"distinct_id": payload["distinct_id"],
|
|
47
|
+
"properties": {
|
|
48
|
+
"$anon_distinct_id": anon_id,
|
|
49
|
+
"$lib": payload.get("properties", {}).get("$lib", "posthog-python"),
|
|
50
|
+
},
|
|
51
|
+
}
|
|
52
|
+
_send_posthog_event(ctx["posthog_host"], identify_payload)
|
|
53
|
+
|
|
54
|
+
|
|
33
55
|
def _resolve_and_cache_email(ctx: dict, payload: dict) -> None:
|
|
34
56
|
"""Call /v1/ping/ to get the user's email, update the payload, and cache it."""
|
|
35
57
|
try:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|