claude-usage-dashboard 1.0.4 → 1.0.6

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/README.md CHANGED
@@ -1,52 +1,62 @@
1
- # Claude Usage Dashboard
2
-
3
- A self-hosted dashboard that visualizes your [Claude Code](https://claude.ai/code) usage by parsing local JSONL session logs from `~/.claude/projects/`.
4
-
5
- ![Dashboard Screenshot](docs/screenshots/dashboard.png)
6
-
7
- ## Features
8
-
9
- - **Token tracking** — Total tokens with breakdown by input, output, cache read, and cache write
10
- - **Cost estimation** — API cost equivalent at standard pricing, compared against your subscription plan (Pro / Max 5x / Max 20x)
11
- - **Token consumption trend** — Stacked bar chart with hourly, daily, weekly, or monthly granularity
12
- - **Model distribution** — Donut chart showing usage across Claude models
13
- - **Cache efficiency** — Visual breakdown of cache read, cache creation, and uncached requests
14
- - **Project distribution** — Horizontal bar chart comparing token usage across projects
15
- - **Session details** — Sortable, paginated table of every session with cost and duration
16
-
17
- ## Quick Start
18
-
19
- Run directly without installing:
20
-
21
- ```bash
22
- npx claude-usage-dashboard
23
- ```
24
-
25
- Open http://localhost:3000 in your browser.
26
-
27
- ### From Source
28
-
29
- ```bash
30
- git clone https://github.com/ludengz/claudeUsageDashboard.git
31
- cd claudeUsageDashboard
32
- npm install
33
- npm start
34
- ```
35
-
36
- The dashboard reads logs from `~/.claude/projects/` — if you use Claude Code, these already exist on your machine. Logs are parsed once at startup; restart the server to pick up new session data.
37
-
38
- ## Tech Stack
39
-
40
- - **Backend:** Node.js, Express 5
41
- - **Frontend:** Vanilla JS (ES modules), D3.js v7
42
- - **Tests:** Mocha + Chai
43
-
44
- ## Running Tests
45
-
46
- ```bash
47
- npm test
48
- ```
49
-
50
- ## License
51
-
52
- ISC
1
+ # Claude Usage Dashboard
2
+
3
+ [![npm version](https://img.shields.io/npm/v/claude-usage-dashboard)](https://www.npmjs.com/package/claude-usage-dashboard)
4
+
5
+ A self-hosted dashboard that visualizes your [Claude Code](https://claude.ai/code) usage by parsing local JSONL session logs from `~/.claude/projects/`.
6
+
7
+ ![Dashboard Screenshot](docs/screenshots/dashboard.png)
8
+
9
+ ## Features
10
+
11
+ - **Token tracking** — Total tokens with breakdown by input, output, cache read, and cache write
12
+ - **Cost estimation** — API cost equivalent at standard pricing, compared against your subscription plan (Pro / Max 5x / Max 20x)
13
+ - **Token consumption trend** — Stacked bar chart with hourly, daily, weekly, or monthly granularity
14
+ - **Model distribution** — Donut chart showing usage across Claude models
15
+ - **Cache efficiency** — Visual breakdown of cache read, cache creation, and uncached requests
16
+ - **Project distribution** — Horizontal bar chart comparing token usage across projects
17
+ - **Session details** — Sortable, paginated table of every session with cost and duration
18
+
19
+ ## Quick Start
20
+
21
+ Run directly without installing:
22
+
23
+ ```bash
24
+ npx claude-usage-dashboard
25
+ ```
26
+
27
+ Open http://localhost:3000 in your browser.
28
+
29
+ ### From Source
30
+
31
+ ```bash
32
+ git clone https://github.com/ludengz/claudeUsageDashboard.git
33
+ cd claudeUsageDashboard
34
+ npm install
35
+ npm start
36
+ ```
37
+
38
+ ### Custom Port
39
+
40
+ ```bash
41
+ PORT=8080 npx claude-usage-dashboard
42
+ ```
43
+
44
+ ## How It Works
45
+
46
+ The dashboard reads Claude Code session logs from `~/.claude/projects/` — if you use Claude Code, these already exist on your machine. Logs are parsed once at startup; restart the server to pick up new session data.
47
+
48
+ ## Tech Stack
49
+
50
+ - **Backend:** Node.js, Express 5
51
+ - **Frontend:** Vanilla JS (ES modules), D3.js v7
52
+ - **Tests:** Mocha + Chai
53
+
54
+ ## Running Tests
55
+
56
+ ```bash
57
+ npm test
58
+ ```
59
+
60
+ ## License
61
+
62
+ ISC
package/bin/cli.js CHANGED
@@ -1,20 +1,20 @@
1
- #!/usr/bin/env node
2
- import { spawn } from 'child_process';
3
- import { fileURLToPath } from 'url';
4
- import path from 'path';
5
-
6
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
- const server = path.join(__dirname, '..', 'server', 'index.js');
8
-
9
- const child = spawn(process.execPath, [server], {
10
- stdio: 'inherit',
11
- // Attach child to the terminal's foreground process group
12
- detached: false,
13
- });
14
-
15
- child.on('exit', (code) => process.exit(code ?? 0));
16
-
17
- // Forward signals to child
18
- for (const sig of ['SIGINT', 'SIGTERM']) {
19
- process.on(sig, () => child.kill(sig));
20
- }
1
+ #!/usr/bin/env node
2
+ import { spawn } from 'child_process';
3
+ import { fileURLToPath } from 'url';
4
+ import path from 'path';
5
+
6
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
+ const server = path.join(__dirname, '..', 'server', 'index.js');
8
+
9
+ const child = spawn(process.execPath, [server], {
10
+ stdio: 'inherit',
11
+ // Attach child to the terminal's foreground process group
12
+ detached: false,
13
+ });
14
+
15
+ child.on('exit', (code) => process.exit(code ?? 0));
16
+
17
+ // Forward signals to child
18
+ for (const sig of ['SIGINT', 'SIGTERM']) {
19
+ process.on(sig, () => child.kill(sig));
20
+ }
package/package.json CHANGED
@@ -1,40 +1,40 @@
1
- {
2
- "name": "claude-usage-dashboard",
3
- "version": "1.0.4",
4
- "description": "Dashboard that visualizes Claude Code usage from local session logs",
5
- "main": "server/index.js",
6
- "bin": {
7
- "claude-usage-dashboard": "bin/cli.js"
8
- },
9
- "files": [
10
- "bin/",
11
- "server/",
12
- "public/"
13
- ],
14
- "scripts": {
15
- "start": "node server/index.js",
16
- "test": "mocha test/**/*.test.js --timeout 5000"
17
- },
18
- "keywords": [
19
- "claude",
20
- "usage",
21
- "dashboard",
22
- "token",
23
- "cost"
24
- ],
25
- "author": "",
26
- "license": "ISC",
27
- "repository": {
28
- "type": "git",
29
- "url": "https://github.com/ludengz/claudeUsageDashboard.git"
30
- },
31
- "type": "module",
32
- "dependencies": {
33
- "d3": "^7.9.0",
34
- "express": "^5.2.1"
35
- },
36
- "devDependencies": {
37
- "chai": "^6.2.2",
38
- "mocha": "^11.7.5"
39
- }
40
- }
1
+ {
2
+ "name": "claude-usage-dashboard",
3
+ "version": "1.0.6",
4
+ "description": "Dashboard that visualizes Claude Code usage from local session logs",
5
+ "main": "server/index.js",
6
+ "bin": {
7
+ "claude-usage-dashboard": "bin/cli.js"
8
+ },
9
+ "files": [
10
+ "bin/",
11
+ "server/",
12
+ "public/"
13
+ ],
14
+ "scripts": {
15
+ "start": "node server/index.js",
16
+ "test": "mocha test/**/*.test.js --timeout 5000"
17
+ },
18
+ "keywords": [
19
+ "claude",
20
+ "usage",
21
+ "dashboard",
22
+ "token",
23
+ "cost"
24
+ ],
25
+ "author": "",
26
+ "license": "ISC",
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "https://github.com/ludengz/claudeUsageDashboard.git"
30
+ },
31
+ "type": "module",
32
+ "dependencies": {
33
+ "d3": "^7.9.0",
34
+ "express": "^5.2.1"
35
+ },
36
+ "devDependencies": {
37
+ "chai": "^6.2.2",
38
+ "mocha": "^11.7.5"
39
+ }
40
+ }
@@ -1,221 +1,221 @@
1
- * { margin: 0; padding: 0; box-sizing: border-box; }
2
-
3
- :root {
4
- --bg-primary: #0f172a;
5
- --bg-card: #1e293b;
6
- --bg-input: #334155;
7
- --border: #475569;
8
- --text-primary: #f8fafc;
9
- --text-secondary: #94a3b8;
10
- --text-muted: #64748b;
11
- --blue: #3b82f6;
12
- --blue-light: #60a5fa;
13
- --purple: #8b5cf6;
14
- --orange: #f97316;
15
- --amber: #f59e0b;
16
- --green: #4ade80;
17
- --red: #ef4444;
18
- }
19
-
20
- body {
21
- background: var(--bg-primary);
22
- color: var(--text-primary);
23
- font-family: system-ui, -apple-system, sans-serif;
24
- padding: 0 24px 40px;
25
- }
26
-
27
- .top-bar {
28
- display: flex;
29
- justify-content: space-between;
30
- align-items: center;
31
- padding: 16px 0;
32
- border-bottom: 1px solid var(--bg-card);
33
- margin-bottom: 20px;
34
- }
35
- .logo { font-size: 18px; font-weight: 700; }
36
- .controls { display: flex; gap: 12px; align-items: center; }
37
-
38
- .summary-cards {
39
- display: grid;
40
- grid-template-columns: repeat(4, 1fr);
41
- gap: 12px;
42
- margin-bottom: 20px;
43
- }
44
- .card {
45
- background: var(--bg-card);
46
- border-radius: 8px;
47
- padding: 16px;
48
- }
49
- .card-label {
50
- font-size: 11px;
51
- color: var(--text-muted);
52
- text-transform: uppercase;
53
- letter-spacing: 1px;
54
- }
55
- .card-value {
56
- font-size: 24px;
57
- font-weight: 700;
58
- margin-top: 4px;
59
- }
60
- .card-sub {
61
- font-size: 11px;
62
- color: var(--text-secondary);
63
- margin-top: 2px;
64
- }
65
- #val-api-cost { color: var(--amber); }
66
- #val-savings { color: var(--green); }
67
- #val-cache-rate { color: var(--blue-light); }
68
-
69
- .chart-section {
70
- background: var(--bg-card);
71
- border-radius: 8px;
72
- padding: 20px;
73
- margin-bottom: 12px;
74
- }
75
- .chart-section h2 {
76
- font-size: 14px;
77
- font-weight: 600;
78
- margin-bottom: 16px;
79
- }
80
- .chart-header {
81
- display: flex;
82
- justify-content: space-between;
83
- align-items: center;
84
- margin-bottom: 16px;
85
- }
86
- .chart-header h2 { margin-bottom: 0; }
87
- .chart-container { min-height: 200px; }
88
-
89
- .chart-row-3 {
90
- display: grid;
91
- grid-template-columns: repeat(3, 1fr);
92
- gap: 12px;
93
- margin-bottom: 12px;
94
- }
95
-
96
- .granularity-toggle { display: flex; gap: 4px; }
97
- .granularity-toggle button {
98
- padding: 4px 12px;
99
- background: var(--bg-input);
100
- border: none;
101
- border-radius: 4px;
102
- color: var(--text-secondary);
103
- font-size: 12px;
104
- cursor: pointer;
105
- }
106
- .granularity-toggle button.active {
107
- background: var(--blue);
108
- color: white;
109
- }
110
-
111
- .table-controls { display: flex; gap: 8px; }
112
- .filter-input, .sort-select {
113
- padding: 6px 10px;
114
- background: var(--bg-input);
115
- border: 1px solid var(--border);
116
- border-radius: 6px;
117
- color: var(--text-primary);
118
- font-size: 12px;
119
- }
120
- .filter-input { width: 180px; }
121
-
122
- .table-container { overflow-x: auto; }
123
- .table-container table {
124
- width: 100%;
125
- border-collapse: collapse;
126
- font-size: 12px;
127
- }
128
- .table-container th {
129
- text-align: left;
130
- padding: 10px 8px;
131
- border-bottom: 2px solid var(--bg-input);
132
- color: var(--text-muted);
133
- text-transform: uppercase;
134
- font-size: 10px;
135
- letter-spacing: 1px;
136
- cursor: pointer;
137
- }
138
- .table-container th.align-right,
139
- .table-container td.align-right { text-align: right; }
140
- .table-container td {
141
- padding: 10px 8px;
142
- border-bottom: 1px solid var(--bg-primary);
143
- color: var(--text-secondary);
144
- }
145
- .table-container tfoot td {
146
- border-top: 2px solid var(--bg-input);
147
- font-weight: 600;
148
- color: var(--text-primary);
149
- }
150
-
151
- .tag {
152
- display: inline-block;
153
- padding: 2px 8px;
154
- border-radius: 4px;
155
- font-size: 11px;
156
- }
157
- .tag-project { background: #1e3a5f; color: var(--blue-light); }
158
- .tag-model-sonnet { background: #1e3a5f; color: var(--blue-light); }
159
- .tag-model-opus { background: #3b1764; color: #c084fc; }
160
- .tag-model-haiku { background: #1a2e1a; color: var(--green); }
161
-
162
- .pagination {
163
- display: flex;
164
- justify-content: center;
165
- gap: 4px;
166
- margin-top: 12px;
167
- }
168
- .pagination button {
169
- padding: 4px 10px;
170
- background: var(--bg-input);
171
- border: none;
172
- border-radius: 4px;
173
- font-size: 11px;
174
- color: var(--text-secondary);
175
- cursor: pointer;
176
- }
177
- .pagination button.active {
178
- background: var(--blue);
179
- color: white;
180
- }
181
-
182
- .date-picker {
183
- display: flex;
184
- align-items: center;
185
- gap: 8px;
186
- }
187
- .date-picker input {
188
- padding: 6px 10px;
189
- background: var(--bg-input);
190
- border: 1px solid var(--border);
191
- border-radius: 6px;
192
- color: var(--text-primary);
193
- font-size: 12px;
194
- }
195
- .date-picker span { color: var(--text-secondary); font-size: 12px; }
196
-
197
- .plan-selector select, .plan-selector input {
198
- padding: 6px 10px;
199
- background: var(--bg-input);
200
- border: 1px solid var(--border);
201
- border-radius: 6px;
202
- color: var(--text-primary);
203
- font-size: 12px;
204
- }
205
-
206
- .d3-tooltip {
207
- position: absolute;
208
- padding: 8px 12px;
209
- background: rgba(15, 23, 42, 0.95);
210
- border: 1px solid var(--border);
211
- border-radius: 6px;
212
- font-size: 12px;
213
- color: var(--text-primary);
214
- pointer-events: none;
215
- z-index: 100;
216
- }
217
-
218
- @media (max-width: 768px) {
219
- .summary-cards { grid-template-columns: repeat(2, 1fr); }
220
- .chart-row-3 { grid-template-columns: 1fr; }
221
- }
1
+ * { margin: 0; padding: 0; box-sizing: border-box; }
2
+
3
+ :root {
4
+ --bg-primary: #0f172a;
5
+ --bg-card: #1e293b;
6
+ --bg-input: #334155;
7
+ --border: #475569;
8
+ --text-primary: #f8fafc;
9
+ --text-secondary: #94a3b8;
10
+ --text-muted: #64748b;
11
+ --blue: #3b82f6;
12
+ --blue-light: #60a5fa;
13
+ --purple: #8b5cf6;
14
+ --orange: #f97316;
15
+ --amber: #f59e0b;
16
+ --green: #4ade80;
17
+ --red: #ef4444;
18
+ }
19
+
20
+ body {
21
+ background: var(--bg-primary);
22
+ color: var(--text-primary);
23
+ font-family: system-ui, -apple-system, sans-serif;
24
+ padding: 0 24px 40px;
25
+ }
26
+
27
+ .top-bar {
28
+ display: flex;
29
+ justify-content: space-between;
30
+ align-items: center;
31
+ padding: 16px 0;
32
+ border-bottom: 1px solid var(--bg-card);
33
+ margin-bottom: 20px;
34
+ }
35
+ .logo { font-size: 18px; font-weight: 700; }
36
+ .controls { display: flex; gap: 12px; align-items: center; }
37
+
38
+ .summary-cards {
39
+ display: grid;
40
+ grid-template-columns: repeat(4, 1fr);
41
+ gap: 12px;
42
+ margin-bottom: 20px;
43
+ }
44
+ .card {
45
+ background: var(--bg-card);
46
+ border-radius: 8px;
47
+ padding: 16px;
48
+ }
49
+ .card-label {
50
+ font-size: 11px;
51
+ color: var(--text-muted);
52
+ text-transform: uppercase;
53
+ letter-spacing: 1px;
54
+ }
55
+ .card-value {
56
+ font-size: 24px;
57
+ font-weight: 700;
58
+ margin-top: 4px;
59
+ }
60
+ .card-sub {
61
+ font-size: 11px;
62
+ color: var(--text-secondary);
63
+ margin-top: 2px;
64
+ }
65
+ #val-api-cost { color: var(--amber); }
66
+ #val-savings { color: var(--green); }
67
+ #val-cache-rate { color: var(--blue-light); }
68
+
69
+ .chart-section {
70
+ background: var(--bg-card);
71
+ border-radius: 8px;
72
+ padding: 20px;
73
+ margin-bottom: 12px;
74
+ }
75
+ .chart-section h2 {
76
+ font-size: 14px;
77
+ font-weight: 600;
78
+ margin-bottom: 16px;
79
+ }
80
+ .chart-header {
81
+ display: flex;
82
+ justify-content: space-between;
83
+ align-items: center;
84
+ margin-bottom: 16px;
85
+ }
86
+ .chart-header h2 { margin-bottom: 0; }
87
+ .chart-container { min-height: 200px; }
88
+
89
+ .chart-row-3 {
90
+ display: grid;
91
+ grid-template-columns: repeat(3, 1fr);
92
+ gap: 12px;
93
+ margin-bottom: 12px;
94
+ }
95
+
96
+ .granularity-toggle { display: flex; gap: 4px; }
97
+ .granularity-toggle button {
98
+ padding: 4px 12px;
99
+ background: var(--bg-input);
100
+ border: none;
101
+ border-radius: 4px;
102
+ color: var(--text-secondary);
103
+ font-size: 12px;
104
+ cursor: pointer;
105
+ }
106
+ .granularity-toggle button.active {
107
+ background: var(--blue);
108
+ color: white;
109
+ }
110
+
111
+ .table-controls { display: flex; gap: 8px; }
112
+ .filter-input, .sort-select {
113
+ padding: 6px 10px;
114
+ background: var(--bg-input);
115
+ border: 1px solid var(--border);
116
+ border-radius: 6px;
117
+ color: var(--text-primary);
118
+ font-size: 12px;
119
+ }
120
+ .filter-input { width: 180px; }
121
+
122
+ .table-container { overflow-x: auto; }
123
+ .table-container table {
124
+ width: 100%;
125
+ border-collapse: collapse;
126
+ font-size: 12px;
127
+ }
128
+ .table-container th {
129
+ text-align: left;
130
+ padding: 10px 8px;
131
+ border-bottom: 2px solid var(--bg-input);
132
+ color: var(--text-muted);
133
+ text-transform: uppercase;
134
+ font-size: 10px;
135
+ letter-spacing: 1px;
136
+ cursor: pointer;
137
+ }
138
+ .table-container th.align-right,
139
+ .table-container td.align-right { text-align: right; }
140
+ .table-container td {
141
+ padding: 10px 8px;
142
+ border-bottom: 1px solid var(--bg-primary);
143
+ color: var(--text-secondary);
144
+ }
145
+ .table-container tfoot td {
146
+ border-top: 2px solid var(--bg-input);
147
+ font-weight: 600;
148
+ color: var(--text-primary);
149
+ }
150
+
151
+ .tag {
152
+ display: inline-block;
153
+ padding: 2px 8px;
154
+ border-radius: 4px;
155
+ font-size: 11px;
156
+ }
157
+ .tag-project { background: #1e3a5f; color: var(--blue-light); }
158
+ .tag-model-sonnet { background: #1e3a5f; color: var(--blue-light); }
159
+ .tag-model-opus { background: #3b1764; color: #c084fc; }
160
+ .tag-model-haiku { background: #1a2e1a; color: var(--green); }
161
+
162
+ .pagination {
163
+ display: flex;
164
+ justify-content: center;
165
+ gap: 4px;
166
+ margin-top: 12px;
167
+ }
168
+ .pagination button {
169
+ padding: 4px 10px;
170
+ background: var(--bg-input);
171
+ border: none;
172
+ border-radius: 4px;
173
+ font-size: 11px;
174
+ color: var(--text-secondary);
175
+ cursor: pointer;
176
+ }
177
+ .pagination button.active {
178
+ background: var(--blue);
179
+ color: white;
180
+ }
181
+
182
+ .date-picker {
183
+ display: flex;
184
+ align-items: center;
185
+ gap: 8px;
186
+ }
187
+ .date-picker input {
188
+ padding: 6px 10px;
189
+ background: var(--bg-input);
190
+ border: 1px solid var(--border);
191
+ border-radius: 6px;
192
+ color: var(--text-primary);
193
+ font-size: 12px;
194
+ }
195
+ .date-picker span { color: var(--text-secondary); font-size: 12px; }
196
+
197
+ .plan-selector select, .plan-selector input {
198
+ padding: 6px 10px;
199
+ background: var(--bg-input);
200
+ border: 1px solid var(--border);
201
+ border-radius: 6px;
202
+ color: var(--text-primary);
203
+ font-size: 12px;
204
+ }
205
+
206
+ .d3-tooltip {
207
+ position: absolute;
208
+ padding: 8px 12px;
209
+ background: rgba(15, 23, 42, 0.95);
210
+ border: 1px solid var(--border);
211
+ border-radius: 6px;
212
+ font-size: 12px;
213
+ color: var(--text-primary);
214
+ pointer-events: none;
215
+ z-index: 100;
216
+ }
217
+
218
+ @media (max-width: 768px) {
219
+ .summary-cards { grid-template-columns: repeat(2, 1fr); }
220
+ .chart-row-3 { grid-template-columns: 1fr; }
221
+ }