claude-count-tokens 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025-2026 Contexere
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,209 @@
1
+ # claude-count-tokens
2
+
3
+ A GitHub-style contribution heatmap for your Claude Code token usage. Add it to your website — it stays up to date automatically.
4
+
5
+ ```
6
+ ┌─────────────────────────────────────────────────────────────┐
7
+ │ Your Claude Code Token Usage 2026 │
8
+ │ │
9
+ │ Mon ░░▒▒░░▓▓░░▒▒░░██▒▒░░▓▓░░▒▒░░██░░▒▒░░▓▓░░▒▒░░██░░ │
10
+ │ Wed ▒▒░░▓▓░░██░░▒▒░░▓▓░░██▒▒░░▓▓░░▒▒░░██░░▒▒░░▓▓░░██ │
11
+ │ Fri ░░██▒▒░░▓▓░░▒▒░░██▒▒░░▓▓░░▒▒░░██░░▓▓░░▒▒░░██░░▒▒ │
12
+ │ │
13
+ │ Less ░░▒▒▓▓██ More 1.2M tokens │
14
+ └─────────────────────────────────────────────────────────────┘
15
+ ```
16
+
17
+ ## Quick start
18
+
19
+ ```bash
20
+ npx claude-count-tokens login # 1. log in with GitHub
21
+ npx claude-count-tokens sync # 2. upload your token data
22
+ npx claude-count-tokens sync --install # 3. auto-sync every hour
23
+ ```
24
+
25
+ Then add two lines to your website:
26
+
27
+ ```html
28
+ <script src="https://unpkg.com/claude-count-tokens/widget/claude-token-heatmap.js"></script>
29
+ <claude-token-heatmap user="YOUR_GITHUB_USERNAME" palette="spring">
30
+ ```
31
+
32
+ That's it. Your heatmap stays up to date automatically.
33
+
34
+ ## How it works
35
+
36
+ ```
37
+ Your Mac Cloud Your Website
38
+ ┌────────────────┐ ┌──────────────┐ ┌──────────────────┐
39
+ │ │ sync │ │ fetch │ │
40
+ │ Claude Code │ ────────> │ Supabase │ <────────── │ <claude-token- │
41
+ │ local logs │ (hourly) │ Storage │ (on load) │ heatmap> │
42
+ │ │ │ │ │ │
43
+ │ ~/.claude/ │ │ yourname │ │ Renders the │
44
+ │ projects/ │ │ .json │ │ heatmap widget │
45
+ │ │ │ │ │ │
46
+ └────────────────┘ └──────────────┘ └──────────────────┘
47
+ │ │
48
+ │ launchd runs │
49
+ │ sync every hour │
50
+ └── automatically ─────────────────────────────────────────>│
51
+ keeps your widget up to date
52
+ ```
53
+
54
+ 1. **Login** — authenticates you via GitHub so we know your username
55
+ 2. **Sync** — reads your local Claude Code logs, aggregates token counts, uploads the result as a small JSON file
56
+ 3. **Auto-sync** — a background job on your Mac re-syncs every hour so your widget is always current
57
+ 4. **Widget** — a self-contained web component that fetches your JSON and renders the heatmap
58
+
59
+ No data leaves your machine except the aggregated token counts (no prompts, no code, no conversation content).
60
+
61
+ ## Step-by-step setup
62
+
63
+ ### 1. Log in
64
+
65
+ ```bash
66
+ npx claude-count-tokens login
67
+ ```
68
+
69
+ This opens your browser for GitHub login. Once authenticated, you'll see:
70
+
71
+ ```
72
+ Opening browser for GitHub login...
73
+ ✓ Logged in as vitoria
74
+
75
+ Your widget embed:
76
+ <claude-token-heatmap user="vitoria" palette="spring">
77
+ ```
78
+
79
+ ### 2. Sync your data
80
+
81
+ ```bash
82
+ npx claude-count-tokens sync
83
+ ```
84
+
85
+ ```
86
+ Parsing local Claude Code logs...
87
+ Found 1.2M tokens across 84 days
88
+ Uploading to cloud...
89
+
90
+ ✓ Synced to cloud. Widget is live.
91
+ ```
92
+
93
+ ### 3. Set up auto-sync (recommended)
94
+
95
+ ```bash
96
+ npx claude-count-tokens sync --install
97
+ ```
98
+
99
+ This installs a background job that syncs every hour. You never have to think about it again.
100
+
101
+ ```
102
+ ✓ Installed background sync (runs every hour)
103
+ Logs: ~/.claude-count-tokens/sync.log
104
+ To uninstall: npx claude-count-tokens sync --uninstall
105
+ ```
106
+
107
+ ### 4. Add the widget to your site
108
+
109
+ Add these two lines anywhere in your HTML:
110
+
111
+ ```html
112
+ <script src="https://unpkg.com/claude-count-tokens/widget/claude-token-heatmap.js"></script>
113
+ <claude-token-heatmap user="YOUR_GITHUB_USERNAME" palette="spring">
114
+ ```
115
+
116
+ Works with any site — plain HTML, Next.js, Astro, Hugo, Jekyll, WordPress, anything. No build step, no dependencies, no framework required.
117
+
118
+ ## Dark mode
119
+
120
+ The widget automatically follows your site's theme. No extra code needed.
121
+
122
+ It detects dark mode from:
123
+ - `prefers-color-scheme` (system preference)
124
+ - `class="dark"` on `<html>` or `<body>`
125
+ - `data-theme="dark"` on `<html>` or `<body>`
126
+
127
+ To force a specific theme:
128
+ ```html
129
+ <claude-token-heatmap user="vitoria" theme="dark"></claude-token-heatmap>
130
+ ```
131
+
132
+ ## Color palettes
133
+
134
+ The default palette is **spring**. To pick a different one, add a `palette` attribute:
135
+
136
+ ```html
137
+ <claude-token-heatmap user="vitoria" palette="mint"></claude-token-heatmap>
138
+ ```
139
+
140
+ Available palettes:
141
+
142
+ `fern` · `sage` · `moss` · `mint` · `spring` · `eucalyptus` · `pistachio` · `clover` · `jade` · `matcha` · `tea` · `basil`
143
+
144
+ Or define your own with CSS custom properties:
145
+
146
+ ```css
147
+ claude-token-heatmap {
148
+ --cth-cell-l1: #d4e4c8;
149
+ --cth-cell-l2: #b5cda3;
150
+ --cth-cell-l3: #94b47e;
151
+ --cth-cell-l4: #6e9a56;
152
+ }
153
+ ```
154
+
155
+ ## Live dashboard
156
+
157
+ Want to see your usage locally while you code?
158
+
159
+ ```bash
160
+ npx claude-count-tokens
161
+ ```
162
+
163
+ Opens a live dashboard at `http://localhost:7890` that updates in real-time as you use Claude Code.
164
+
165
+ ## CLI reference
166
+
167
+ ```
168
+ npx claude-count-tokens # live dashboard on port 7890
169
+ npx claude-count-tokens --port 3000 # custom port
170
+ npx claude-count-tokens --days 90 # last 90 days only
171
+
172
+ npx claude-count-tokens login # log in with GitHub
173
+ npx claude-count-tokens logout # log out, clear credentials
174
+
175
+ npx claude-count-tokens sync # upload token data to cloud
176
+ npx claude-count-tokens sync --install # auto-sync every hour (macOS)
177
+ npx claude-count-tokens sync --uninstall # remove auto-sync
178
+ npx claude-count-tokens sync --status # check if auto-sync is running
179
+
180
+ npx claude-count-tokens export # export to ./claude-token-data.json
181
+ npx claude-count-tokens export -o out.json # custom output path
182
+ ```
183
+
184
+ ## What gets counted
185
+
186
+ The widget reads local JSONL logs that Claude Code writes at `~/.claude/projects/`. For each AI response, it sums:
187
+
188
+ | Type | What it is |
189
+ |------|-----------|
190
+ | Input | tokens in your prompt |
191
+ | Output | tokens Claude generated |
192
+ | Cache write | tokens written to context cache |
193
+ | Cache read | tokens read from context cache |
194
+
195
+ This is different from your Claude account's usage page, which tracks billing across all Claude products. This widget only shows Claude Code CLI usage from your machine's local logs.
196
+
197
+ ## Privacy
198
+
199
+ Only aggregated token counts are synced to the cloud — **no prompts, no code, no conversation content**. The sync uploads a small JSON file with daily/hourly token totals. Everything else stays on your machine.
200
+
201
+ ## Requirements
202
+
203
+ - Node.js 18+
204
+ - Claude Code installed (creates `~/.claude/projects/`)
205
+ - macOS for auto-sync (Linux users can use a cron job instead)
206
+
207
+ ## License
208
+
209
+ MIT
package/index.html ADDED
@@ -0,0 +1,267 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Claude Token Usage</title>
7
+ <style>
8
+ @import url('https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&display=swap');
9
+
10
+ * { box-sizing: border-box; margin: 0; padding: 0; }
11
+
12
+ body {
13
+ font-family: "Space Mono", ui-monospace, monospace;
14
+ display: flex;
15
+ justify-content: center;
16
+ align-items: flex-start;
17
+ min-height: 100vh;
18
+ padding: 60px 24px;
19
+ transition: background 0.3s ease, color 0.3s ease;
20
+ background: #f5f4f0;
21
+ color: #1a1a2e;
22
+ }
23
+
24
+ @media (prefers-color-scheme: dark) {
25
+ body {
26
+ background: #0d0b14;
27
+ color: #e8e4f0;
28
+ }
29
+ }
30
+
31
+ .dashboard {
32
+ width: 100%;
33
+ max-width: 960px;
34
+ }
35
+
36
+ .dashboard-header {
37
+ display: flex;
38
+ align-items: center;
39
+ justify-content: space-between;
40
+ margin-bottom: 32px;
41
+ }
42
+
43
+ .dashboard-title {
44
+ font-size: 14px;
45
+ font-weight: 400;
46
+ letter-spacing: -0.01em;
47
+ opacity: 0.6;
48
+ }
49
+
50
+ .dashboard-live {
51
+ display: flex;
52
+ align-items: center;
53
+ gap: 6px;
54
+ font-size: 11px;
55
+ opacity: 0.5;
56
+ }
57
+
58
+ .live-dot {
59
+ width: 6px;
60
+ height: 6px;
61
+ border-radius: 50%;
62
+ background: #22c55e;
63
+ animation: pulse 2s ease-in-out infinite;
64
+ }
65
+
66
+ @keyframes pulse {
67
+ 0%, 100% { opacity: 1; }
68
+ 50% { opacity: 0.4; }
69
+ }
70
+
71
+ .theme-toggle {
72
+ background: none;
73
+ border: 1px solid rgba(128, 128, 128, 0.2);
74
+ border-radius: 8px;
75
+ padding: 7px 9px;
76
+ cursor: pointer;
77
+ line-height: 0;
78
+ transition: border-color 0.2s;
79
+ color: inherit;
80
+ display: flex;
81
+ align-items: center;
82
+ justify-content: center;
83
+ }
84
+
85
+ .theme-toggle:hover {
86
+ border-color: rgba(128, 128, 128, 0.4);
87
+ }
88
+
89
+ .theme-toggle svg {
90
+ width: 16px;
91
+ height: 16px;
92
+ opacity: 0.6;
93
+ }
94
+
95
+ .controls {
96
+ display: flex;
97
+ align-items: center;
98
+ gap: 12px;
99
+ }
100
+ </style>
101
+ </head>
102
+ <body>
103
+ <div class="dashboard">
104
+ <div class="dashboard-header">
105
+ <span class="dashboard-title">claude code tokens</span>
106
+ <div class="controls">
107
+ <div class="dashboard-live">
108
+ <div class="live-dot"></div>
109
+ live
110
+ </div>
111
+ <button class="theme-toggle" onclick="toggleTheme()" title="Toggle theme" id="theme-btn">
112
+ <!-- Moon icon (light mode default) -->
113
+ <svg id="icon-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
114
+ <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
115
+ </svg>
116
+ <!-- Sun icon (shown in dark mode) -->
117
+ <svg id="icon-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display:none">
118
+ <circle cx="12" cy="12" r="5"/>
119
+ <line x1="12" y1="1" x2="12" y2="3"/>
120
+ <line x1="12" y1="21" x2="12" y2="23"/>
121
+ <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/>
122
+ <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/>
123
+ <line x1="1" y1="12" x2="3" y2="12"/>
124
+ <line x1="21" y1="12" x2="23" y2="12"/>
125
+ <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/>
126
+ <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/>
127
+ </svg>
128
+ </button>
129
+ </div>
130
+ </div>
131
+
132
+ <claude-token-heatmap id="heatmap" src="/api/data"></claude-token-heatmap>
133
+
134
+ <div class="palette-picker" id="palette-picker">
135
+ <div class="palette-label">pick a palette</div>
136
+ <div class="palette-list" id="palette-list"></div>
137
+ </div>
138
+ </div>
139
+
140
+ <style>
141
+ .palette-picker {
142
+ margin-top: 32px;
143
+ padding-top: 24px;
144
+ border-top: 1px solid rgba(128,128,128,0.15);
145
+ }
146
+ .palette-label {
147
+ font-size: 11px;
148
+ opacity: 0.4;
149
+ margin-bottom: 12px;
150
+ }
151
+ .palette-list {
152
+ display: flex;
153
+ flex-direction: column;
154
+ gap: 8px;
155
+ }
156
+ .palette-row {
157
+ display: flex;
158
+ align-items: center;
159
+ gap: 6px;
160
+ cursor: pointer;
161
+ padding: 6px 8px;
162
+ border-radius: 8px;
163
+ transition: background 0.15s;
164
+ }
165
+ .palette-row:hover {
166
+ background: rgba(128,128,128,0.08);
167
+ }
168
+ .palette-row.active {
169
+ background: rgba(128,128,128,0.12);
170
+ }
171
+ .palette-swatch {
172
+ width: 18px;
173
+ height: 18px;
174
+ border-radius: 4px;
175
+ flex-shrink: 0;
176
+ }
177
+ .palette-name {
178
+ font-size: 11px;
179
+ opacity: 0.5;
180
+ margin-left: 8px;
181
+ white-space: nowrap;
182
+ }
183
+ </style>
184
+
185
+ <script src="/widget/claude-token-heatmap.js"></script>
186
+ <script>
187
+ // Palettes — leafy pastel greens
188
+ const palettes = [
189
+ { name: 'fern', colors: ['#dbe8d0', '#b6d4a0', '#8bba76', '#629e4e'] },
190
+ { name: 'sage', colors: ['#dce5d4', '#bccfab', '#96b882', '#6f9e5c'] },
191
+ { name: 'moss', colors: ['#d5e0cd', '#aec9a0', '#87b074', '#5f964c'] },
192
+ { name: 'mint', colors: ['#d2ead8', '#a8d4b4', '#7dbd90', '#54a46c'] },
193
+ { name: 'spring', colors: ['#dbebc7', '#b8d69a', '#90c06c', '#68a844'] },
194
+ { name: 'eucalyptus', colors: ['#d8e5d2', '#b4cdaa', '#8fb682', '#6b9d5c'] },
195
+ { name: 'pistachio', colors: ['#e0e8c8', '#c4d4a0', '#a4bd78', '#82a454'] },
196
+ { name: 'clover', colors: ['#d0e4d0', '#a6cca6', '#7cb47c', '#559c55'] },
197
+ { name: 'jade', colors: ['#cce5d8', '#99ccb4', '#66b290', '#3d996e'] },
198
+ { name: 'matcha', colors: ['#d4e4c8', '#b5cda3', '#94b47e', '#6e9a56'] },
199
+ { name: 'tea', colors: ['#dde4c4', '#c0cea0', '#9eb87a', '#7ca054'] },
200
+ { name: 'basil', colors: ['#d0dece', '#a4c0a0', '#78a474', '#50884c'] },
201
+ ];
202
+
203
+ const listEl = document.getElementById('palette-list');
204
+ palettes.forEach((p, i) => {
205
+ const row = document.createElement('div');
206
+ row.className = 'palette-row' + (p.name === 'matcha' ? ' active' : '');
207
+ row.innerHTML = p.colors.map(c => `<div class="palette-swatch" style="background:${c}"></div>`).join('') +
208
+ `<span class="palette-name">${p.name}</span>`;
209
+ row.addEventListener('click', () => {
210
+ document.querySelectorAll('.palette-row').forEach(r => r.classList.remove('active'));
211
+ row.classList.add('active');
212
+ const hm = document.getElementById('heatmap');
213
+ const root = hm.shadowRoot.querySelector(':host') || hm;
214
+ // Apply via CSS custom properties on the host element
215
+ hm.style.setProperty('--cth-cell-l1', p.colors[0]);
216
+ hm.style.setProperty('--cth-cell-l2', p.colors[1]);
217
+ hm.style.setProperty('--cth-cell-l3', p.colors[2]);
218
+ hm.style.setProperty('--cth-cell-l4', p.colors[3]);
219
+ hm.style.setProperty('--cth-bar-color', p.colors[1]);
220
+ hm.style.setProperty('--cth-bar-hover', p.colors[3]);
221
+ hm.style.setProperty('--cth-year-active-bg', p.colors[2]);
222
+ });
223
+ listEl.appendChild(row);
224
+ });
225
+
226
+ // SSE: listen for live updates
227
+ const events = new EventSource('/api/events');
228
+ events.onmessage = (e) => {
229
+ if (e.data === 'updated') {
230
+ fetch('/api/data')
231
+ .then(r => r.json())
232
+ .then(data => {
233
+ document.getElementById('heatmap').tokenData = data;
234
+ });
235
+ }
236
+ };
237
+
238
+ // Theme toggle
239
+ let forcedTheme = null;
240
+
241
+ function toggleTheme() {
242
+ const heatmap = document.getElementById('heatmap');
243
+ const moon = document.getElementById('icon-moon');
244
+ const sun = document.getElementById('icon-sun');
245
+
246
+ if (!forcedTheme) {
247
+ const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
248
+ forcedTheme = isDark ? 'light' : 'dark';
249
+ } else {
250
+ forcedTheme = forcedTheme === 'dark' ? 'light' : 'dark';
251
+ }
252
+
253
+ heatmap.setAttribute('theme', forcedTheme);
254
+ document.body.style.background = forcedTheme === 'dark' ? '#0d0b14' : '#f5f4f0';
255
+ document.body.style.color = forcedTheme === 'dark' ? '#e8e4f0' : '#1a1a2e';
256
+
257
+ if (forcedTheme === 'dark') {
258
+ moon.style.display = 'none';
259
+ sun.style.display = 'block';
260
+ } else {
261
+ moon.style.display = 'block';
262
+ sun.style.display = 'none';
263
+ }
264
+ }
265
+ </script>
266
+ </body>
267
+ </html>
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "claude-count-tokens",
3
+ "version": "1.1.0",
4
+ "description": "GitHub-style heatmap widget showing your Claude Code token usage. One command to run, one line to embed.",
5
+ "type": "module",
6
+ "bin": {
7
+ "claude-count-tokens": "./server/index.js"
8
+ },
9
+ "exports": {
10
+ "./widget": "./widget/claude-token-heatmap.js",
11
+ ".": "./server/index.js"
12
+ },
13
+ "files": [
14
+ "server/",
15
+ "widget/",
16
+ "supabase/",
17
+ "index.html"
18
+ ],
19
+ "keywords": [
20
+ "claude",
21
+ "claude-code",
22
+ "tokens",
23
+ "heatmap",
24
+ "web-component",
25
+ "usage",
26
+ "widget",
27
+ "analytics",
28
+ "dashboard",
29
+ "contribution-graph"
30
+ ],
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "git+https://github.com/contexere/claude-count-tokens.git"
34
+ },
35
+ "homepage": "https://github.com/contexere/claude-count-tokens#readme",
36
+ "author": "Contexere",
37
+ "engines": {
38
+ "node": ">=18"
39
+ },
40
+ "license": "MIT"
41
+ }