Skip to content

Commit aa26af7

Browse files
jgravelleclaude
andcommitted
Add opt-in community token savings meter
When JCODEMUNCH_SHARE_SAVINGS=1 is set, each tool call fires a fire-and-forget POST of {delta, anon_id} to j.gravelle.us. The anon UUID is generated once and persisted in _savings.json. Network failures are silent and never affect tool performance. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent c9f4e87 commit aa26af7

File tree

3 files changed

+67
-8
lines changed

3 files changed

+67
-8
lines changed

README.md

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -290,12 +290,19 @@ See SECURITY.md for details.
290290

291291
## Environment Variables
292292

293-
| Variable | Purpose | Required |
294-
| ------------------- | ------------------------- | -------- |
295-
| `GITHUB_TOKEN` | GitHub API auth | No |
296-
| `ANTHROPIC_API_KEY` | Symbol summaries via Claude Haiku (takes priority) | No |
297-
| `GOOGLE_API_KEY` | Symbol summaries via Gemini Flash | No |
298-
| `CODE_INDEX_PATH` | Custom cache path | No |
293+
| Variable | Purpose | Required |
294+
| --------------------------- | ------------------------- | -------- |
295+
| `GITHUB_TOKEN` | GitHub API auth | No |
296+
| `ANTHROPIC_API_KEY` | Symbol summaries via Claude Haiku (takes priority) | No |
297+
| `GOOGLE_API_KEY` | Symbol summaries via Gemini Flash | No |
298+
| `CODE_INDEX_PATH` | Custom cache path | No |
299+
| `JCODEMUNCH_SHARE_SAVINGS` | Set to `1` to opt in to the community token savings meter | No |
300+
301+
### Community Savings Meter
302+
303+
Set `JCODEMUNCH_SHARE_SAVINGS=1` to contribute your token savings to a live global counter at [j.gravelle.us](https://j.gravelle.us).
304+
305+
Only two values are ever sent per tool call: the tokens saved (a number) and a random anonymous install ID. No code, paths, repo names, or anything identifying is transmitted. The anon ID is generated once and stored in `~/.code-index/_savings.json`.
299306

300307
---
301308

USER_GUIDE.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,27 @@ IDs are returned by `get_file_outline`, `search_symbols`, and `search_text`. Pas
215215

216216
---
217217

218+
## Community Savings Meter
219+
220+
Set `JCODEMUNCH_SHARE_SAVINGS=1` in your MCP server env to opt in to the live global counter at [j.gravelle.us](https://j.gravelle.us).
221+
222+
```json
223+
{
224+
"mcpServers": {
225+
"jcodemunch": {
226+
"command": "jcodemunch-mcp",
227+
"env": {
228+
"JCODEMUNCH_SHARE_SAVINGS": "1"
229+
}
230+
}
231+
}
232+
}
233+
```
234+
235+
Only two values are sent per tool call: the tokens saved (a number) and a random anonymous install ID. No code, paths, repo names, or anything identifying is ever transmitted. Network failures are silent and never affect tool performance.
236+
237+
---
238+
218239
## Troubleshooting
219240

220241
**"Repository not found"**

src/jcodemunch_mcp/storage/token_tracker.py

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,22 @@
55
66
Stored in ~/.code-index/_savings.json — a single small JSON file.
77
No API calls, no file reads — only os.stat for file sizes.
8+
9+
Opt-in community meter: set JCODEMUNCH_SHARE_SAVINGS=1 to contribute
10+
your delta (tokens saved per call) anonymously to the global counter at
11+
https://j.gravelle.us. Only {"delta": N, "anon_id": "<uuid>"} is sent —
12+
never code, paths, repo names, or anything identifying.
813
"""
914

1015
import json
1116
import os
17+
import uuid
1218
from pathlib import Path
1319
from typing import Optional
1420

15-
1621
_SAVINGS_FILE = "_savings.json"
1722
_BYTES_PER_TOKEN = 4 # ~4 bytes per token (rough but consistent)
23+
_TELEMETRY_URL = "https://j.gravelle.us/api/savings/post.php"
1824

1925
# Input token pricing ($ per token). Update as models reprice.
2026
PRICING = {
@@ -29,6 +35,26 @@ def _savings_path(base_path: Optional[str] = None) -> Path:
2935
return root / _SAVINGS_FILE
3036

3137

38+
def _get_or_create_anon_id(data: dict) -> str:
39+
"""Return the persistent anonymous install ID, creating it if absent."""
40+
if "anon_id" not in data:
41+
data["anon_id"] = str(uuid.uuid4())
42+
return data["anon_id"]
43+
44+
45+
def _share_savings(delta: int, anon_id: str) -> None:
46+
"""Fire-and-forget POST to the community meter. Never raises."""
47+
try:
48+
import httpx
49+
httpx.post(
50+
_TELEMETRY_URL,
51+
json={"delta": delta, "anon_id": anon_id},
52+
timeout=3.0,
53+
)
54+
except Exception:
55+
pass
56+
57+
3258
def record_savings(tokens_saved: int, base_path: Optional[str] = None) -> int:
3359
"""Add tokens_saved to the running total. Returns new cumulative total."""
3460
path = _savings_path(base_path)
@@ -37,9 +63,14 @@ def record_savings(tokens_saved: int, base_path: Optional[str] = None) -> int:
3763
except Exception:
3864
data = {}
3965

40-
total = data.get("total_tokens_saved", 0) + max(0, tokens_saved)
66+
delta = max(0, tokens_saved)
67+
total = data.get("total_tokens_saved", 0) + delta
4168
data["total_tokens_saved"] = total
4269

70+
if delta > 0 and os.environ.get("JCODEMUNCH_SHARE_SAVINGS") == "1":
71+
anon_id = _get_or_create_anon_id(data)
72+
_share_savings(delta, anon_id)
73+
4374
try:
4475
path.write_text(json.dumps(data))
4576
except Exception:

0 commit comments

Comments
 (0)