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.
Files changed (23) hide show
  1. {mem0_cli-0.2.2 → mem0_cli-0.2.4}/PKG-INFO +1 -1
  2. {mem0_cli-0.2.2 → mem0_cli-0.2.4}/pyproject.toml +1 -1
  3. {mem0_cli-0.2.2 → mem0_cli-0.2.4}/src/mem0_cli/__init__.py +1 -1
  4. {mem0_cli-0.2.2 → mem0_cli-0.2.4}/src/mem0_cli/app.py +0 -38
  5. {mem0_cli-0.2.2 → mem0_cli-0.2.4}/src/mem0_cli/backend/base.py +0 -3
  6. {mem0_cli-0.2.2 → mem0_cli-0.2.4}/src/mem0_cli/backend/platform.py +15 -18
  7. {mem0_cli-0.2.2 → mem0_cli-0.2.4}/src/mem0_cli/commands/config_cmd.py +0 -5
  8. {mem0_cli-0.2.2 → mem0_cli-0.2.4}/src/mem0_cli/commands/memory.py +0 -6
  9. {mem0_cli-0.2.2 → mem0_cli-0.2.4}/src/mem0_cli/config.py +11 -8
  10. {mem0_cli-0.2.2 → mem0_cli-0.2.4}/src/mem0_cli/telemetry.py +45 -4
  11. {mem0_cli-0.2.2 → mem0_cli-0.2.4}/src/mem0_cli/telemetry_sender.py +22 -0
  12. {mem0_cli-0.2.2 → mem0_cli-0.2.4}/.gitignore +0 -0
  13. {mem0_cli-0.2.2 → mem0_cli-0.2.4}/README.md +0 -0
  14. {mem0_cli-0.2.2 → mem0_cli-0.2.4}/src/mem0_cli/__main__.py +0 -0
  15. {mem0_cli-0.2.2 → mem0_cli-0.2.4}/src/mem0_cli/backend/__init__.py +0 -0
  16. {mem0_cli-0.2.2 → mem0_cli-0.2.4}/src/mem0_cli/branding.py +0 -0
  17. {mem0_cli-0.2.2 → mem0_cli-0.2.4}/src/mem0_cli/commands/__init__.py +0 -0
  18. {mem0_cli-0.2.2 → mem0_cli-0.2.4}/src/mem0_cli/commands/entities.py +0 -0
  19. {mem0_cli-0.2.2 → mem0_cli-0.2.4}/src/mem0_cli/commands/events_cmd.py +0 -0
  20. {mem0_cli-0.2.2 → mem0_cli-0.2.4}/src/mem0_cli/commands/init_cmd.py +0 -0
  21. {mem0_cli-0.2.2 → mem0_cli-0.2.4}/src/mem0_cli/commands/utils.py +0 -0
  22. {mem0_cli-0.2.2 → mem0_cli-0.2.4}/src/mem0_cli/output.py +0 -0
  23. {mem0_cli-0.2.2 → mem0_cli-0.2.4}/src/mem0_cli/state.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mem0-cli
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: The official CLI for mem0 — the memory layer for AI agents
5
5
  Author-email: "mem0.ai" <founders@mem0.ai>
6
6
  License-Expression: Apache-2.0
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "mem0-cli"
7
- version = "0.2.2"
7
+ version = "0.2.4"
8
8
  description = "The official CLI for mem0 — the memory layer for AI agents"
9
9
  readme = "README.md"
10
10
  license = "Apache-2.0"
@@ -1,3 +1,3 @@
1
1
  """mem0 CLI — the command-line interface for the mem0 memory layer."""
2
2
 
3
- __version__ = "0.2.2"
3
+ __version__ = "0.2.4"
@@ -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
- if enable_graph:
95
- payload["enable_graph"] = True
93
+ payload["source"] = "CLI"
96
94
 
97
- return self._request("POST", "/v1/memories/", json=payload)
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 v2 API endpoints.
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
- if enable_graph:
174
- payload["enable_graph"] = True
170
+ payload["source"] = "CLI"
175
171
 
176
- result = self._request("POST", "/v2/memories/search/", json=payload)
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 for v2 API — entity IDs and date filters go inside "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
- if enable_graph:
222
- payload["enable_graph"] = True
216
+ payload["source"] = "CLI"
223
217
 
224
- result = self._request("POST", "/v2/memories/", json=payload, params=params)
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("DELETE", f"/v2/entities/{entity_type}/{entity_id}/")
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
- enable_graph: bool = False
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
- config.defaults.enable_graph = defaults.get("enable_graph", False)
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) > fallback.
33
- Matches the SDK pattern in mem0/client/main.py.
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
- return "anonymous-cli"
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