feat: hardcode Umami analytics defaults for FOSS usage tracking

Bake in cloud.umami.is host and basic-memory-foss site ID so all
open-source installs send anonymous CLI events by default. Users
can still opt out with BASIC_MEMORY_NO_PROMOS=1 or override the
endpoint via env vars.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
This commit is contained in:
phernandez
2026-02-24 19:50:28 -06:00
parent 0130573342
commit 79db876bd6
2 changed files with 24 additions and 20 deletions
+10 -7
View File
@@ -6,10 +6,10 @@ no cookies. Respects the same opt-out mechanisms as promo messaging.
Events are fire-and-forget — analytics never blocks or breaks the CLI.
Setup:
Set these environment variables (or leave unset to disable):
BASIC_MEMORY_UMAMI_HOST — Umami instance URL (e.g. https://analytics.basicmemory.com)
BASIC_MEMORY_UMAMI_SITE_ID — Website ID from Umami dashboard
Defaults point to the Basic Memory Umami Cloud instance. Override via:
BASIC_MEMORY_UMAMI_HOST — Custom Umami instance URL
BASIC_MEMORY_UMAMI_SITE_ID — Custom Website ID
Opt out entirely with BASIC_MEMORY_NO_PROMOS=1.
"""
import json
@@ -22,16 +22,19 @@ import basic_memory
# ---------------------------------------------------------------------------
# Configuration — read from environment so nothing is hard-coded in source
# Configuration — defaults baked in, overridable via environment
# ---------------------------------------------------------------------------
_DEFAULT_UMAMI_HOST = "https://cloud.umami.is"
_DEFAULT_UMAMI_SITE_ID = "f6479898-ebaf-4e60-bce2-6dc60a3f6c5c"
def _umami_host() -> Optional[str]:
return os.getenv("BASIC_MEMORY_UMAMI_HOST", "").strip() or None
return os.getenv("BASIC_MEMORY_UMAMI_HOST", "").strip() or _DEFAULT_UMAMI_HOST
def _umami_site_id() -> Optional[str]:
return os.getenv("BASIC_MEMORY_UMAMI_SITE_ID", "").strip() or None
return os.getenv("BASIC_MEMORY_UMAMI_SITE_ID", "").strip() or _DEFAULT_UMAMI_SITE_ID
def _analytics_disabled() -> bool:
+14 -13
View File
@@ -9,6 +9,8 @@ from basic_memory.cli.analytics import (
track,
_analytics_disabled,
_is_configured,
_umami_host,
_umami_site_id,
EVENT_PROMO_SHOWN,
EVENT_CLOUD_LOGIN_STARTED,
EVENT_CLOUD_LOGIN_SUCCESS,
@@ -37,20 +39,17 @@ class TestIsConfigured:
monkeypatch.setenv("BASIC_MEMORY_UMAMI_SITE_ID", "abc-123")
assert _is_configured() is True
def test_not_configured_when_host_missing(self, monkeypatch):
def test_configured_by_default(self, monkeypatch):
"""Defaults are baked in — always configured unless explicitly emptied."""
monkeypatch.delenv("BASIC_MEMORY_UMAMI_HOST", raising=False)
monkeypatch.setenv("BASIC_MEMORY_UMAMI_SITE_ID", "abc-123")
assert _is_configured() is False
def test_not_configured_when_site_id_missing(self, monkeypatch):
monkeypatch.setenv("BASIC_MEMORY_UMAMI_HOST", "https://analytics.example.com")
monkeypatch.delenv("BASIC_MEMORY_UMAMI_SITE_ID", raising=False)
assert _is_configured() is False
assert _is_configured() is True
def test_not_configured_when_empty_strings(self, monkeypatch):
monkeypatch.setenv("BASIC_MEMORY_UMAMI_HOST", "")
monkeypatch.setenv("BASIC_MEMORY_UMAMI_SITE_ID", "")
assert _is_configured() is False
def test_env_override_takes_precedence(self, monkeypatch):
monkeypatch.setenv("BASIC_MEMORY_UMAMI_HOST", "https://custom.example.com")
monkeypatch.setenv("BASIC_MEMORY_UMAMI_SITE_ID", "custom-id")
assert _umami_host() == "https://custom.example.com"
assert _umami_site_id() == "custom-id"
class TestTrack:
@@ -60,13 +59,15 @@ class TestTrack:
track("test-event")
mock_thread.assert_not_called()
def test_no_op_when_not_configured(self, monkeypatch):
def test_sends_when_using_defaults(self, monkeypatch):
"""With baked-in defaults, track() fires even without env vars."""
monkeypatch.delenv("BASIC_MEMORY_NO_PROMOS", raising=False)
monkeypatch.delenv("BASIC_MEMORY_UMAMI_HOST", raising=False)
monkeypatch.delenv("BASIC_MEMORY_UMAMI_SITE_ID", raising=False)
with patch("basic_memory.cli.analytics.threading.Thread") as mock_thread:
mock_thread.return_value = MagicMock()
track("test-event")
mock_thread.assert_not_called()
mock_thread.assert_called_once()
def test_sends_event_when_configured(self, monkeypatch):
monkeypatch.delenv("BASIC_MEMORY_NO_PROMOS", raising=False)