feat(mem0-plugin): add PostHog telemetry across all hooks
Wire fire-and-forget PostHog telemetry into every lifecycle hook (session_start, stop, pre_compact, task_completed, user_prompt, post_tool_use) matching the same project key, endpoint, and payload structure used by the Python SDK, CLI, and OpenClaw. - 10% sampling on ALL events to keep PostHog costs predictable - Anonymous distinct_id: MD5(api_key) or SHA-256(username) - Opt-out via MEM0_TELEMETRY=false - Platform detection: Claude Code / Cursor / Codex - Never sends user content, API keys, or raw identifiers - 16 new tests (110 total passing)
This commit is contained in:
@@ -19,9 +19,17 @@ case "$TOOL_NAME" in
|
|||||||
mcp__mem0__add_memory)
|
mcp__mem0__add_memory)
|
||||||
CATEGORY=$(echo "$INPUT" | jq -r '.tool_input.metadata.type // .tool_input.metadata.category // ""' 2>/dev/null || echo "")
|
CATEGORY=$(echo "$INPUT" | jq -r '.tool_input.metadata.type // .tool_input.metadata.category // ""' 2>/dev/null || echo "")
|
||||||
python3 "$SCRIPT_DIR/session_stats.py" add "$CATEGORY" 2>/dev/null || true
|
python3 "$SCRIPT_DIR/session_stats.py" add "$CATEGORY" 2>/dev/null || true
|
||||||
|
python3 "$SCRIPT_DIR/telemetry.py" tool_use --tool=add_memory 2>/dev/null &
|
||||||
;;
|
;;
|
||||||
mcp__mem0__search_memories|mcp__mem0__get_memories)
|
mcp__mem0__search_memories|mcp__mem0__get_memories)
|
||||||
python3 "$SCRIPT_DIR/session_stats.py" search 2>/dev/null || true
|
python3 "$SCRIPT_DIR/session_stats.py" search 2>/dev/null || true
|
||||||
|
python3 "$SCRIPT_DIR/telemetry.py" tool_use --tool=search_memories 2>/dev/null &
|
||||||
|
;;
|
||||||
|
mcp__mem0__delete_memory)
|
||||||
|
python3 "$SCRIPT_DIR/telemetry.py" tool_use --tool=delete_memory 2>/dev/null &
|
||||||
|
;;
|
||||||
|
mcp__mem0__update_memory)
|
||||||
|
python3 "$SCRIPT_DIR/telemetry.py" tool_use --tool=update_memory 2>/dev/null &
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ INPUT=$(cat)
|
|||||||
# Fire REST API transcript backup in background (safety net if agent
|
# Fire REST API transcript backup in background (safety net if agent
|
||||||
# can't complete the add_memory call before compaction finishes)
|
# can't complete the add_memory call before compaction finishes)
|
||||||
echo "$INPUT" | python3 "$SCRIPT_DIR/on_pre_compact.py" --source=pre-compaction 2>/dev/null &
|
echo "$INPUT" | python3 "$SCRIPT_DIR/on_pre_compact.py" --source=pre-compaction 2>/dev/null &
|
||||||
|
python3 "$SCRIPT_DIR/telemetry.py" pre_compact 2>/dev/null &
|
||||||
|
|
||||||
cat <<'EOF'
|
cat <<'EOF'
|
||||||
## CRITICAL: Pre-Compaction Session Summary
|
## CRITICAL: Pre-Compaction Session Summary
|
||||||
|
|||||||
@@ -162,4 +162,7 @@ is being captured to mem0 in the background as `metadata.type=compact_summary`.
|
|||||||
EOF
|
EOF
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Telemetry (background, fire-and-forget)
|
||||||
|
python3 "$SCRIPT_DIR/telemetry.py" session_start --source="$SOURCE" --memory_count="${MEM0_COUNT:-0}" 2>/dev/null &
|
||||||
|
|
||||||
exit 0
|
exit 0
|
||||||
|
|||||||
@@ -26,6 +26,10 @@ if [ "$STOP_HOOK_ACTIVE" = "true" ]; then
|
|||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Telemetry: fire before report() deletes stats file
|
||||||
|
_TELEM_CAT=$(python3 "$SCRIPT_DIR/session_stats.py" peek 2>/dev/null | python3 -c "import json,sys; d=json.load(sys.stdin); print(len(d.get('categories',[])))" 2>/dev/null || echo "0")
|
||||||
|
python3 "$SCRIPT_DIR/telemetry.py" stop --categories_count="$_TELEM_CAT" 2>/dev/null &
|
||||||
|
|
||||||
# Print session-end report
|
# Print session-end report
|
||||||
REPORT=$(python3 "$SCRIPT_DIR/session_stats.py" report 2>/dev/null || echo "")
|
REPORT=$(python3 "$SCRIPT_DIR/session_stats.py" report 2>/dev/null || echo "")
|
||||||
if [ -n "$REPORT" ]; then
|
if [ -n "$REPORT" ]; then
|
||||||
|
|||||||
@@ -33,6 +33,10 @@ if [ "$STOP_HOOK_ACTIVE" = "true" ]; then
|
|||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Telemetry: fire before report() deletes stats file
|
||||||
|
_TELEM_CAT=$(python3 "$SCRIPT_DIR/session_stats.py" peek 2>/dev/null | python3 -c "import json,sys; d=json.load(sys.stdin); print(len(d.get('categories',[])))" 2>/dev/null || echo "0")
|
||||||
|
python3 "$SCRIPT_DIR/telemetry.py" stop --categories_count="$_TELEM_CAT" 2>/dev/null &
|
||||||
|
|
||||||
# Session-end report (best-effort, must not break JSON output)
|
# Session-end report (best-effort, must not break JSON output)
|
||||||
REPORT=$(python3 "$SCRIPT_DIR/session_stats.py" report 2>/dev/null || echo "")
|
REPORT=$(python3 "$SCRIPT_DIR/session_stats.py" report 2>/dev/null || echo "")
|
||||||
REPORT_BLOCK=""
|
REPORT_BLOCK=""
|
||||||
|
|||||||
@@ -27,6 +27,10 @@ if [ "$LOOP_COUNT" -gt 1 ]; then
|
|||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Telemetry: fire before report() deletes stats file
|
||||||
|
_TELEM_CAT=$(python3 "$SCRIPT_DIR/session_stats.py" peek 2>/dev/null | python3 -c "import json,sys; d=json.load(sys.stdin); print(len(d.get('categories',[])))" 2>/dev/null || echo "0")
|
||||||
|
python3 "$SCRIPT_DIR/telemetry.py" stop --categories_count="$_TELEM_CAT" 2>/dev/null &
|
||||||
|
|
||||||
# Session-end report (best-effort)
|
# Session-end report (best-effort)
|
||||||
REPORT=$(python3 "$SCRIPT_DIR/session_stats.py" report 2>/dev/null || echo "")
|
REPORT=$(python3 "$SCRIPT_DIR/session_stats.py" report 2>/dev/null || echo "")
|
||||||
REPORT_BLOCK=""
|
REPORT_BLOCK=""
|
||||||
|
|||||||
@@ -37,4 +37,7 @@ Only store genuinely useful learnings — skip if the task was trivial.
|
|||||||
Include \`app_id\` = \`"$_PROJECT"\` as a top-level parameter in every \`add_memory\` call (not in metadata).
|
Include \`app_id\` = \`"$_PROJECT"\` as a top-level parameter in every \`add_memory\` call (not in metadata).
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
|
# Telemetry (background, fire-and-forget)
|
||||||
|
python3 "$SCRIPT_DIR/telemetry.py" task_completed 2>/dev/null &
|
||||||
|
|
||||||
exit 0
|
exit 0
|
||||||
|
|||||||
@@ -38,6 +38,12 @@ fi
|
|||||||
# Detect file paths in the prompt (no API needed)
|
# Detect file paths in the prompt (no API needed)
|
||||||
FILE_PATHS=$(echo "$PROMPT" | grep -oE '([a-zA-Z0-9_./-]+\.(py|ts|tsx|js|jsx|rs|go|rb|java|sh|yaml|yml|json|toml|md|sql|css|html))\b' 2>/dev/null | head -5 || echo "")
|
FILE_PATHS=$(echo "$PROMPT" | grep -oE '([a-zA-Z0-9_./-]+\.(py|ts|tsx|js|jsx|rs|go|rb|java|sh|yaml|yml|json|toml|md|sql|css|html))\b' 2>/dev/null | head -5 || echo "")
|
||||||
|
|
||||||
|
# Telemetry (background, fire-and-forget)
|
||||||
|
_TELEM_ARGS=""
|
||||||
|
[ -n "$HAS_ERROR" ] && _TELEM_ARGS="$_TELEM_ARGS --error_detected"
|
||||||
|
[ -n "$FILE_PATHS" ] && _TELEM_ARGS="$_TELEM_ARGS --file_paths_detected"
|
||||||
|
python3 "$SCRIPT_DIR/telemetry.py" user_prompt $_TELEM_ARGS 2>/dev/null &
|
||||||
|
|
||||||
# No API key — emit detections only, skip search rubric
|
# No API key — emit detections only, skip search rubric
|
||||||
if [ -z "${MEM0_API_KEY:-}" ]; then
|
if [ -z "${MEM0_API_KEY:-}" ]; then
|
||||||
if [ -n "$HAS_ERROR" ]; then
|
if [ -n "$HAS_ERROR" ]; then
|
||||||
|
|||||||
@@ -0,0 +1,149 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Lightweight fire-and-forget telemetry for the mem0 plugin.
|
||||||
|
|
||||||
|
Sends anonymous usage events to PostHog using the same project key and
|
||||||
|
endpoint as the mem0 Python SDK and CLI. No posthog library dependency —
|
||||||
|
uses stdlib urllib directly (same pattern as cli/python telemetry_sender.py).
|
||||||
|
|
||||||
|
CLI usage (called from hooks as a background subprocess):
|
||||||
|
python3 telemetry.py <event_type> [--memory_count=N] [--categories_count=N]
|
||||||
|
[--error_detected] [--file_paths_detected]
|
||||||
|
[--source=<src>] [--tool=<name>]
|
||||||
|
|
||||||
|
Opt-out: set MEM0_TELEMETRY=false (or 0/no/off) to disable all telemetry.
|
||||||
|
|
||||||
|
Never sends: user content, memory content, API keys, raw user/project IDs.
|
||||||
|
Only sends: event type, platform, plugin version, anonymized hashes, counts.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import platform
|
||||||
|
import random
|
||||||
|
import sys
|
||||||
|
import urllib.error
|
||||||
|
import urllib.request
|
||||||
|
|
||||||
|
PLUGIN_VERSION = "0.2.1"
|
||||||
|
|
||||||
|
POSTHOG_API_KEY = "phc_hgJkUVJFYtmaJqrvf6CYN67TIQ8yhXAkWzUn9AMU4yX"
|
||||||
|
POSTHOG_HOST = "https://us.i.posthog.com/i/v0/e/"
|
||||||
|
REQUEST_TIMEOUT = 2
|
||||||
|
|
||||||
|
# All events sampled at 10% to keep PostHog costs predictable.
|
||||||
|
SAMPLE_RATE = 0.1
|
||||||
|
|
||||||
|
|
||||||
|
def _sha256(value: str) -> str:
|
||||||
|
return hashlib.sha256(value.encode("utf-8")).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
def _distinct_id() -> str:
|
||||||
|
"""Stable anonymous ID: MD5 of API key if available, else SHA-256 of username."""
|
||||||
|
api_key = os.environ.get("MEM0_API_KEY") or os.environ.get("CLAUDE_PLUGIN_OPTION_MEM0_API_KEY") or ""
|
||||||
|
if api_key:
|
||||||
|
return hashlib.md5(api_key.encode()).hexdigest()
|
||||||
|
user_id = os.environ.get("MEM0_RESOLVED_USER_ID") or os.environ.get("USER") or "unknown"
|
||||||
|
return _sha256(user_id)
|
||||||
|
|
||||||
|
|
||||||
|
def detect_platform() -> str:
|
||||||
|
if os.environ.get("CLAUDECODE") or os.environ.get("CLAUDE_PLUGIN_ROOT"):
|
||||||
|
return "claude-code"
|
||||||
|
if os.environ.get("CURSOR_PLUGIN_ROOT"):
|
||||||
|
return "cursor"
|
||||||
|
if os.environ.get("CODEX_PLUGIN_ROOT"):
|
||||||
|
return "codex"
|
||||||
|
return "unknown"
|
||||||
|
|
||||||
|
|
||||||
|
def is_enabled() -> bool:
|
||||||
|
return os.environ.get("MEM0_TELEMETRY", "true").lower() not in ("false", "0", "no", "off")
|
||||||
|
|
||||||
|
|
||||||
|
def _should_sample() -> bool:
|
||||||
|
return random.random() < SAMPLE_RATE
|
||||||
|
|
||||||
|
|
||||||
|
def build_posthog_payload(event_name: str, properties: dict | None = None) -> dict:
|
||||||
|
project_id = os.environ.get("MEM0_PROJECT_ID") or "unknown"
|
||||||
|
return {
|
||||||
|
"api_key": POSTHOG_API_KEY,
|
||||||
|
"distinct_id": _distinct_id(),
|
||||||
|
"event": event_name,
|
||||||
|
"properties": {
|
||||||
|
"source": "plugin",
|
||||||
|
"platform": detect_platform(),
|
||||||
|
"plugin_version": PLUGIN_VERSION,
|
||||||
|
"project_hash": _sha256(project_id),
|
||||||
|
"os": sys.platform,
|
||||||
|
"os_version": platform.version(),
|
||||||
|
"sample_rate": SAMPLE_RATE,
|
||||||
|
"$process_person_profile": False,
|
||||||
|
"$lib": "posthog-python",
|
||||||
|
**(properties or {}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def send(payload: dict) -> None:
|
||||||
|
data = json.dumps(payload).encode("utf-8")
|
||||||
|
req = urllib.request.Request(
|
||||||
|
POSTHOG_HOST,
|
||||||
|
data=data,
|
||||||
|
headers={"Content-Type": "application/json"},
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
with urllib.request.urlopen(req, timeout=REQUEST_TIMEOUT):
|
||||||
|
pass
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def emit(event_type: str, properties: dict | None = None) -> None:
|
||||||
|
if not is_enabled():
|
||||||
|
return
|
||||||
|
event_name = f"plugin.{event_type}"
|
||||||
|
if not _should_sample():
|
||||||
|
return
|
||||||
|
send(build_posthog_payload(event_name, properties))
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
if not is_enabled():
|
||||||
|
return 0
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
event_type = sys.argv[1]
|
||||||
|
properties: dict = {}
|
||||||
|
|
||||||
|
for arg in sys.argv[2:]:
|
||||||
|
if arg.startswith("--memory_count="):
|
||||||
|
try:
|
||||||
|
properties["memory_count"] = int(arg.split("=", 1)[1])
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
elif arg.startswith("--categories_count="):
|
||||||
|
try:
|
||||||
|
properties["categories_count"] = int(arg.split("=", 1)[1])
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
elif arg == "--error_detected":
|
||||||
|
properties["error_detected"] = True
|
||||||
|
elif arg == "--file_paths_detected":
|
||||||
|
properties["file_paths_detected"] = True
|
||||||
|
elif arg.startswith("--source="):
|
||||||
|
properties["source_detail"] = arg.split("=", 1)[1]
|
||||||
|
elif arg.startswith("--tool="):
|
||||||
|
properties["tool"] = arg.split("=", 1)[1]
|
||||||
|
|
||||||
|
emit(event_type, properties)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
"""Tests for telemetry.py — fire-and-forget PostHog plugin telemetry."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import urllib.error
|
||||||
|
|
||||||
|
SCRIPTS_DIR = os.path.join(os.path.dirname(__file__), "..", "scripts")
|
||||||
|
sys.path.insert(0, os.path.abspath(SCRIPTS_DIR))
|
||||||
|
|
||||||
|
|
||||||
|
def test_import_succeeds():
|
||||||
|
import telemetry
|
||||||
|
|
||||||
|
assert hasattr(telemetry, "emit")
|
||||||
|
assert hasattr(telemetry, "main")
|
||||||
|
|
||||||
|
|
||||||
|
def test_opt_out_skips_send(monkeypatch):
|
||||||
|
import telemetry
|
||||||
|
|
||||||
|
monkeypatch.setenv("MEM0_TELEMETRY", "false")
|
||||||
|
sent = []
|
||||||
|
monkeypatch.setattr(telemetry, "send", lambda p: sent.append(p))
|
||||||
|
telemetry.emit("session_start")
|
||||||
|
assert sent == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_opt_out_variants(monkeypatch):
|
||||||
|
import telemetry
|
||||||
|
|
||||||
|
for val in ("0", "no", "off", "FALSE", "No"):
|
||||||
|
monkeypatch.setenv("MEM0_TELEMETRY", val)
|
||||||
|
assert not telemetry.is_enabled()
|
||||||
|
|
||||||
|
|
||||||
|
def test_enabled_by_default(monkeypatch):
|
||||||
|
import telemetry
|
||||||
|
|
||||||
|
monkeypatch.delenv("MEM0_TELEMETRY", raising=False)
|
||||||
|
assert telemetry.is_enabled()
|
||||||
|
|
||||||
|
|
||||||
|
def test_posthog_payload_structure(monkeypatch):
|
||||||
|
import telemetry
|
||||||
|
|
||||||
|
monkeypatch.setenv("MEM0_RESOLVED_USER_ID", "testuser")
|
||||||
|
monkeypatch.setenv("MEM0_PROJECT_ID", "test-project")
|
||||||
|
monkeypatch.delenv("MEM0_API_KEY", raising=False)
|
||||||
|
monkeypatch.delenv("CLAUDE_PLUGIN_OPTION_MEM0_API_KEY", raising=False)
|
||||||
|
|
||||||
|
payload = telemetry.build_posthog_payload("plugin.session_start", {"memory_count": 5})
|
||||||
|
|
||||||
|
assert payload["api_key"] == telemetry.POSTHOG_API_KEY
|
||||||
|
assert payload["event"] == "plugin.session_start"
|
||||||
|
assert "distinct_id" in payload
|
||||||
|
assert payload["properties"]["source"] == "plugin"
|
||||||
|
assert payload["properties"]["plugin_version"] == "0.2.1"
|
||||||
|
assert payload["properties"]["memory_count"] == 5
|
||||||
|
assert payload["properties"]["$process_person_profile"] is False
|
||||||
|
|
||||||
|
raw = json.dumps(payload)
|
||||||
|
assert "testuser" not in raw
|
||||||
|
assert "test-project" not in raw
|
||||||
|
|
||||||
|
|
||||||
|
def test_distinct_id_from_api_key(monkeypatch):
|
||||||
|
import hashlib
|
||||||
|
|
||||||
|
import telemetry
|
||||||
|
|
||||||
|
monkeypatch.setenv("MEM0_API_KEY", "m0-testkey123")
|
||||||
|
expected = hashlib.md5(b"m0-testkey123").hexdigest()
|
||||||
|
assert telemetry._distinct_id() == expected
|
||||||
|
|
||||||
|
|
||||||
|
def test_distinct_id_fallback_no_key(monkeypatch):
|
||||||
|
import telemetry
|
||||||
|
|
||||||
|
monkeypatch.delenv("MEM0_API_KEY", raising=False)
|
||||||
|
monkeypatch.delenv("CLAUDE_PLUGIN_OPTION_MEM0_API_KEY", raising=False)
|
||||||
|
monkeypatch.setenv("MEM0_RESOLVED_USER_ID", "kartik")
|
||||||
|
assert telemetry._distinct_id() == telemetry._sha256("kartik")
|
||||||
|
|
||||||
|
|
||||||
|
def test_hash_deterministic():
|
||||||
|
import telemetry
|
||||||
|
|
||||||
|
h1 = telemetry._sha256("same-value")
|
||||||
|
h2 = telemetry._sha256("same-value")
|
||||||
|
assert h1 == h2
|
||||||
|
assert h1 != telemetry._sha256("different-value")
|
||||||
|
|
||||||
|
|
||||||
|
def test_platform_claude_code(monkeypatch):
|
||||||
|
import telemetry
|
||||||
|
|
||||||
|
monkeypatch.setenv("CLAUDECODE", "1")
|
||||||
|
monkeypatch.delenv("CURSOR_PLUGIN_ROOT", raising=False)
|
||||||
|
monkeypatch.delenv("CODEX_PLUGIN_ROOT", raising=False)
|
||||||
|
assert telemetry.detect_platform() == "claude-code"
|
||||||
|
|
||||||
|
|
||||||
|
def test_platform_cursor(monkeypatch):
|
||||||
|
import telemetry
|
||||||
|
|
||||||
|
monkeypatch.delenv("CLAUDECODE", raising=False)
|
||||||
|
monkeypatch.delenv("CLAUDE_PLUGIN_ROOT", raising=False)
|
||||||
|
monkeypatch.setenv("CURSOR_PLUGIN_ROOT", "/path")
|
||||||
|
monkeypatch.delenv("CODEX_PLUGIN_ROOT", raising=False)
|
||||||
|
assert telemetry.detect_platform() == "cursor"
|
||||||
|
|
||||||
|
|
||||||
|
def test_platform_codex(monkeypatch):
|
||||||
|
import telemetry
|
||||||
|
|
||||||
|
monkeypatch.delenv("CLAUDECODE", raising=False)
|
||||||
|
monkeypatch.delenv("CLAUDE_PLUGIN_ROOT", raising=False)
|
||||||
|
monkeypatch.delenv("CURSOR_PLUGIN_ROOT", raising=False)
|
||||||
|
monkeypatch.setenv("CODEX_PLUGIN_ROOT", "/path")
|
||||||
|
assert telemetry.detect_platform() == "codex"
|
||||||
|
|
||||||
|
|
||||||
|
def test_sampling_drops_at_high_random(monkeypatch):
|
||||||
|
import telemetry
|
||||||
|
|
||||||
|
monkeypatch.setattr(telemetry.random, "random", lambda: 0.5)
|
||||||
|
assert telemetry._should_sample() is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_sampling_sends_at_low_random(monkeypatch):
|
||||||
|
import telemetry
|
||||||
|
|
||||||
|
monkeypatch.setattr(telemetry.random, "random", lambda: 0.05)
|
||||||
|
assert telemetry._should_sample() is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_send_fails_silently(monkeypatch):
|
||||||
|
import telemetry
|
||||||
|
|
||||||
|
def raise_error(req, timeout):
|
||||||
|
raise urllib.error.URLError("connection refused")
|
||||||
|
|
||||||
|
monkeypatch.setattr(telemetry.urllib.request, "urlopen", raise_error)
|
||||||
|
telemetry.send({"event": "test"})
|
||||||
|
|
||||||
|
|
||||||
|
def test_cli_exits_zero_when_disabled(monkeypatch):
|
||||||
|
import telemetry
|
||||||
|
|
||||||
|
monkeypatch.setenv("MEM0_TELEMETRY", "false")
|
||||||
|
monkeypatch.setattr(sys, "argv", ["telemetry.py", "session_start"])
|
||||||
|
assert telemetry.main() == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_cli_no_args_exits_nonzero(monkeypatch):
|
||||||
|
import telemetry
|
||||||
|
|
||||||
|
monkeypatch.delenv("MEM0_TELEMETRY", raising=False)
|
||||||
|
monkeypatch.setattr(sys, "argv", ["telemetry.py"])
|
||||||
|
assert telemetry.main() == 1
|
||||||
Reference in New Issue
Block a user