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 +21 -0
- package/README.md +209 -0
- package/index.html +267 -0
- package/package.json +41 -0
- package/server/auth.js +173 -0
- package/server/config.js +40 -0
- package/server/constants.js +5 -0
- package/server/daemon.js +103 -0
- package/server/discovery.js +62 -0
- package/server/index.js +174 -0
- package/server/parser.js +192 -0
- package/server/sync.js +80 -0
- package/server/watcher.js +37 -0
- package/supabase/migration.sql +73 -0
- package/widget/claude-token-heatmap.js +1161 -0
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
|
+
}
|