fix(cli): fix Umami analytics event delivery

Three issues prevented CLI analytics from reaching the Umami dashboard:

1. Wrong API endpoint — cloud.umami.is rejects /api/send, the JS tracker
   uses api-gateway.umami.dev
2. Missing "type": "event" top-level field required by Umami v2 API
3. Non-browser User-Agent ("basic-memory-cli/...") triggers Umami's bot
   detection, which silently drops events with {"beep":"boop"} 🤖
4. Daemon thread was killed before HTTP request completed on fast commands

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
This commit is contained in:
phernandez
2026-03-02 19:24:10 -06:00
parent 7f2d4d2a6f
commit 7d2012a82c
+11 -4
View File
@@ -25,7 +25,7 @@ import basic_memory
# Configuration — defaults baked in, overridable via environment
# ---------------------------------------------------------------------------
_DEFAULT_UMAMI_HOST = "https://cloud.umami.is"
_DEFAULT_UMAMI_HOST = "https://api-gateway.umami.dev"
_DEFAULT_UMAMI_SITE_ID = "f6479898-ebaf-4e60-bce2-6dc60a3f6c5c"
@@ -76,7 +76,9 @@ def track(event_name: str, data: Optional[dict] = None) -> None:
host = _umami_host()
site_id = _umami_site_id()
# Umami v2 /api/send requires "type" at top level alongside "payload"
payload = {
"type": "event",
"payload": {
"hostname": "cli.basicmemory.com",
"language": "en",
@@ -87,7 +89,7 @@ def track(event_name: str, data: Optional[dict] = None) -> None:
"version": basic_memory.__version__,
**(data or {}),
},
}
},
}
def _send():
@@ -97,11 +99,16 @@ def track(event_name: str, data: Optional[dict] = None) -> None:
data=json.dumps(payload).encode("utf-8"),
headers={
"Content-Type": "application/json",
"User-Agent": f"basic-memory-cli/{basic_memory.__version__}",
# Umami's bot detection rejects non-browser User-Agents
"User-Agent": "Mozilla/5.0 (compatible; BasicMemoryCLI/"
f"{basic_memory.__version__})",
},
)
urllib.request.urlopen(req, timeout=3)
except Exception:
pass # Never break the CLI for analytics
threading.Thread(target=_send, daemon=True).start()
# Non-daemon so the process waits for the request to complete.
# The 3s urllib timeout caps the worst-case exit delay.
t = threading.Thread(target=_send)
t.start()