mem0-cli 0.2.2__tar.gz → 0.2.3__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.3}/PKG-INFO +1 -1
- {mem0_cli-0.2.2 → mem0_cli-0.2.3}/pyproject.toml +1 -1
- {mem0_cli-0.2.2 → mem0_cli-0.2.3}/src/mem0_cli/__init__.py +1 -1
- {mem0_cli-0.2.2 → mem0_cli-0.2.3}/src/mem0_cli/backend/platform.py +10 -4
- {mem0_cli-0.2.2 → mem0_cli-0.2.3}/src/mem0_cli/config.py +12 -0
- {mem0_cli-0.2.2 → mem0_cli-0.2.3}/src/mem0_cli/telemetry.py +45 -4
- {mem0_cli-0.2.2 → mem0_cli-0.2.3}/src/mem0_cli/telemetry_sender.py +22 -0
- {mem0_cli-0.2.2 → mem0_cli-0.2.3}/.gitignore +0 -0
- {mem0_cli-0.2.2 → mem0_cli-0.2.3}/README.md +0 -0
- {mem0_cli-0.2.2 → mem0_cli-0.2.3}/src/mem0_cli/__main__.py +0 -0
- {mem0_cli-0.2.2 → mem0_cli-0.2.3}/src/mem0_cli/app.py +0 -0
- {mem0_cli-0.2.2 → mem0_cli-0.2.3}/src/mem0_cli/backend/__init__.py +0 -0
- {mem0_cli-0.2.2 → mem0_cli-0.2.3}/src/mem0_cli/backend/base.py +0 -0
- {mem0_cli-0.2.2 → mem0_cli-0.2.3}/src/mem0_cli/branding.py +0 -0
- {mem0_cli-0.2.2 → mem0_cli-0.2.3}/src/mem0_cli/commands/__init__.py +0 -0
- {mem0_cli-0.2.2 → mem0_cli-0.2.3}/src/mem0_cli/commands/config_cmd.py +0 -0
- {mem0_cli-0.2.2 → mem0_cli-0.2.3}/src/mem0_cli/commands/entities.py +0 -0
- {mem0_cli-0.2.2 → mem0_cli-0.2.3}/src/mem0_cli/commands/events_cmd.py +0 -0
- {mem0_cli-0.2.2 → mem0_cli-0.2.3}/src/mem0_cli/commands/init_cmd.py +0 -0
- {mem0_cli-0.2.2 → mem0_cli-0.2.3}/src/mem0_cli/commands/memory.py +0 -0
- {mem0_cli-0.2.2 → mem0_cli-0.2.3}/src/mem0_cli/commands/utils.py +0 -0
- {mem0_cli-0.2.2 → mem0_cli-0.2.3}/src/mem0_cli/output.py +0 -0
- {mem0_cli-0.2.2 → mem0_cli-0.2.3}/src/mem0_cli/state.py +0 -0
|
@@ -93,6 +93,7 @@ class PlatformBackend(Backend):
|
|
|
93
93
|
payload["categories"] = categories
|
|
94
94
|
if enable_graph:
|
|
95
95
|
payload["enable_graph"] = True
|
|
96
|
+
payload["source"] = "CLI"
|
|
96
97
|
|
|
97
98
|
return self._request("POST", "/v1/memories/", json=payload)
|
|
98
99
|
|
|
@@ -172,6 +173,7 @@ class PlatformBackend(Backend):
|
|
|
172
173
|
payload["fields"] = fields
|
|
173
174
|
if enable_graph:
|
|
174
175
|
payload["enable_graph"] = True
|
|
176
|
+
payload["source"] = "CLI"
|
|
175
177
|
|
|
176
178
|
result = self._request("POST", "/v2/memories/search/", json=payload)
|
|
177
179
|
return (
|
|
@@ -181,7 +183,7 @@ class PlatformBackend(Backend):
|
|
|
181
183
|
)
|
|
182
184
|
|
|
183
185
|
def get(self, memory_id: str) -> dict:
|
|
184
|
-
return self._request("GET", f"/v1/memories/{memory_id}/")
|
|
186
|
+
return self._request("GET", f"/v1/memories/{memory_id}/", params={"source": "CLI"})
|
|
185
187
|
|
|
186
188
|
def list_memories(
|
|
187
189
|
self,
|
|
@@ -220,6 +222,7 @@ class PlatformBackend(Backend):
|
|
|
220
222
|
payload["filters"] = api_filters
|
|
221
223
|
if enable_graph:
|
|
222
224
|
payload["enable_graph"] = True
|
|
225
|
+
payload["source"] = "CLI"
|
|
223
226
|
|
|
224
227
|
result = self._request("POST", "/v2/memories/", json=payload, params=params)
|
|
225
228
|
return (
|
|
@@ -236,6 +239,7 @@ class PlatformBackend(Backend):
|
|
|
236
239
|
payload["text"] = content
|
|
237
240
|
if metadata:
|
|
238
241
|
payload["metadata"] = metadata
|
|
242
|
+
payload["source"] = "CLI"
|
|
239
243
|
return self._request("PUT", f"/v1/memories/{memory_id}/", json=payload)
|
|
240
244
|
|
|
241
245
|
def delete(
|
|
@@ -249,7 +253,7 @@ class PlatformBackend(Backend):
|
|
|
249
253
|
run_id: str | None = None,
|
|
250
254
|
) -> dict:
|
|
251
255
|
if all:
|
|
252
|
-
params: dict[str, str] = {}
|
|
256
|
+
params: dict[str, str] = {"source": "CLI"}
|
|
253
257
|
if user_id:
|
|
254
258
|
params["user_id"] = user_id
|
|
255
259
|
if agent_id:
|
|
@@ -260,7 +264,7 @@ class PlatformBackend(Backend):
|
|
|
260
264
|
params["run_id"] = run_id
|
|
261
265
|
return self._request("DELETE", "/v1/memories/", params=params)
|
|
262
266
|
elif memory_id:
|
|
263
|
-
return self._request("DELETE", f"/v1/memories/{memory_id}/")
|
|
267
|
+
return self._request("DELETE", f"/v1/memories/{memory_id}/", params={"source": "CLI"})
|
|
264
268
|
else:
|
|
265
269
|
raise ValueError("Either memory_id or --all is required")
|
|
266
270
|
|
|
@@ -285,7 +289,9 @@ class PlatformBackend(Backend):
|
|
|
285
289
|
# Delete each provided entity via the v2 path-based endpoint
|
|
286
290
|
result: dict = {}
|
|
287
291
|
for entity_type, entity_id in entities.items():
|
|
288
|
-
result = self._request(
|
|
292
|
+
result = self._request(
|
|
293
|
+
"DELETE", f"/v2/entities/{entity_type}/{entity_id}/", params={"source": "CLI"}
|
|
294
|
+
)
|
|
289
295
|
return result
|
|
290
296
|
|
|
291
297
|
def ping(self, timeout: float | None = None) -> dict:
|
|
@@ -39,11 +39,17 @@ class DefaultsConfig:
|
|
|
39
39
|
enable_graph: bool = False
|
|
40
40
|
|
|
41
41
|
|
|
42
|
+
@dataclass
|
|
43
|
+
class TelemetryConfig:
|
|
44
|
+
anonymous_id: str = ""
|
|
45
|
+
|
|
46
|
+
|
|
42
47
|
@dataclass
|
|
43
48
|
class Mem0Config:
|
|
44
49
|
version: int = CONFIG_VERSION
|
|
45
50
|
defaults: DefaultsConfig = field(default_factory=DefaultsConfig)
|
|
46
51
|
platform: PlatformConfig = field(default_factory=PlatformConfig)
|
|
52
|
+
telemetry: TelemetryConfig = field(default_factory=TelemetryConfig)
|
|
47
53
|
|
|
48
54
|
|
|
49
55
|
SHORT_KEY_ALIASES: dict[str, str] = {
|
|
@@ -87,6 +93,9 @@ def load_config() -> Mem0Config:
|
|
|
87
93
|
config.defaults.run_id = defaults.get("run_id", "")
|
|
88
94
|
config.defaults.enable_graph = defaults.get("enable_graph", False)
|
|
89
95
|
|
|
96
|
+
telemetry = data.get("telemetry", {})
|
|
97
|
+
config.telemetry.anonymous_id = telemetry.get("anonymous_id", "")
|
|
98
|
+
|
|
90
99
|
# Environment variable overrides
|
|
91
100
|
env_key = os.environ.get("MEM0_API_KEY")
|
|
92
101
|
if env_key:
|
|
@@ -137,6 +146,9 @@ def save_config(config: Mem0Config) -> None:
|
|
|
137
146
|
"base_url": config.platform.base_url,
|
|
138
147
|
"user_email": config.platform.user_email,
|
|
139
148
|
},
|
|
149
|
+
"telemetry": {
|
|
150
|
+
"anonymous_id": config.telemetry.anonymous_id,
|
|
151
|
+
},
|
|
140
152
|
}
|
|
141
153
|
|
|
142
154
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|