Skip to content

Commit 79db876

Browse files
phernandezclaude
andcommitted
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>
1 parent 0130573 commit 79db876

2 files changed

Lines changed: 24 additions & 20 deletions

File tree

src/basic_memory/cli/analytics.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66
77
Events are fire-and-forget — analytics never blocks or breaks the CLI.
88
9-
Setup:
10-
Set these environment variables (or leave unset to disable):
11-
BASIC_MEMORY_UMAMI_HOST — Umami instance URL (e.g. https://analytics.basicmemory.com)
12-
BASIC_MEMORY_UMAMI_SITE_ID — Website ID from Umami dashboard
9+
Defaults point to the Basic Memory Umami Cloud instance. Override via:
10+
BASIC_MEMORY_UMAMI_HOST — Custom Umami instance URL
11+
BASIC_MEMORY_UMAMI_SITE_ID — Custom Website ID
12+
Opt out entirely with BASIC_MEMORY_NO_PROMOS=1.
1313
"""
1414

1515
import json
@@ -22,16 +22,19 @@
2222

2323

2424
# ---------------------------------------------------------------------------
25-
# Configuration — read from environment so nothing is hard-coded in source
25+
# Configuration — defaults baked in, overridable via environment
2626
# ---------------------------------------------------------------------------
2727

28+
_DEFAULT_UMAMI_HOST = "https://cloud.umami.is"
29+
_DEFAULT_UMAMI_SITE_ID = "f6479898-ebaf-4e60-bce2-6dc60a3f6c5c"
30+
2831

2932
def _umami_host() -> Optional[str]:
30-
return os.getenv("BASIC_MEMORY_UMAMI_HOST", "").strip() or None
33+
return os.getenv("BASIC_MEMORY_UMAMI_HOST", "").strip() or _DEFAULT_UMAMI_HOST
3134

3235

3336
def _umami_site_id() -> Optional[str]:
34-
return os.getenv("BASIC_MEMORY_UMAMI_SITE_ID", "").strip() or None
37+
return os.getenv("BASIC_MEMORY_UMAMI_SITE_ID", "").strip() or _DEFAULT_UMAMI_SITE_ID
3538

3639

3740
def _analytics_disabled() -> bool:

tests/cli/test_analytics.py

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
track,
1010
_analytics_disabled,
1111
_is_configured,
12+
_umami_host,
13+
_umami_site_id,
1214
EVENT_PROMO_SHOWN,
1315
EVENT_CLOUD_LOGIN_STARTED,
1416
EVENT_CLOUD_LOGIN_SUCCESS,
@@ -37,20 +39,17 @@ def test_configured_when_both_set(self, monkeypatch):
3739
monkeypatch.setenv("BASIC_MEMORY_UMAMI_SITE_ID", "abc-123")
3840
assert _is_configured() is True
3941

40-
def test_not_configured_when_host_missing(self, monkeypatch):
42+
def test_configured_by_default(self, monkeypatch):
43+
"""Defaults are baked in — always configured unless explicitly emptied."""
4144
monkeypatch.delenv("BASIC_MEMORY_UMAMI_HOST", raising=False)
42-
monkeypatch.setenv("BASIC_MEMORY_UMAMI_SITE_ID", "abc-123")
43-
assert _is_configured() is False
44-
45-
def test_not_configured_when_site_id_missing(self, monkeypatch):
46-
monkeypatch.setenv("BASIC_MEMORY_UMAMI_HOST", "https://analytics.example.com")
4745
monkeypatch.delenv("BASIC_MEMORY_UMAMI_SITE_ID", raising=False)
48-
assert _is_configured() is False
46+
assert _is_configured() is True
4947

50-
def test_not_configured_when_empty_strings(self, monkeypatch):
51-
monkeypatch.setenv("BASIC_MEMORY_UMAMI_HOST", "")
52-
monkeypatch.setenv("BASIC_MEMORY_UMAMI_SITE_ID", "")
53-
assert _is_configured() is False
48+
def test_env_override_takes_precedence(self, monkeypatch):
49+
monkeypatch.setenv("BASIC_MEMORY_UMAMI_HOST", "https://custom.example.com")
50+
monkeypatch.setenv("BASIC_MEMORY_UMAMI_SITE_ID", "custom-id")
51+
assert _umami_host() == "https://custom.example.com"
52+
assert _umami_site_id() == "custom-id"
5453

5554

5655
class TestTrack:
@@ -60,13 +59,15 @@ def test_no_op_when_disabled(self, monkeypatch):
6059
track("test-event")
6160
mock_thread.assert_not_called()
6261

63-
def test_no_op_when_not_configured(self, monkeypatch):
62+
def test_sends_when_using_defaults(self, monkeypatch):
63+
"""With baked-in defaults, track() fires even without env vars."""
6464
monkeypatch.delenv("BASIC_MEMORY_NO_PROMOS", raising=False)
6565
monkeypatch.delenv("BASIC_MEMORY_UMAMI_HOST", raising=False)
6666
monkeypatch.delenv("BASIC_MEMORY_UMAMI_SITE_ID", raising=False)
6767
with patch("basic_memory.cli.analytics.threading.Thread") as mock_thread:
68+
mock_thread.return_value = MagicMock()
6869
track("test-event")
69-
mock_thread.assert_not_called()
70+
mock_thread.assert_called_once()
7071

7172
def test_sends_event_when_configured(self, monkeypatch):
7273
monkeypatch.delenv("BASIC_MEMORY_NO_PROMOS", raising=False)

0 commit comments

Comments
 (0)