claude-usage-dashboard 1.4.0 → 1.4.2
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 +114 -77
- package/bin/cli.cjs +20 -20
- package/bin/cli.js +16 -16
- package/bin/cli.sh +11 -11
- package/package.json +43 -43
- package/public/css/style.css +265 -265
- package/public/index.html +108 -108
- package/public/js/api.js +16 -16
- package/public/js/app.js +304 -304
- package/public/js/charts/cache-efficiency.js +29 -29
- package/public/js/charts/cost-comparison.js +39 -39
- package/public/js/charts/model-distribution.js +56 -56
- package/public/js/charts/project-distribution.js +103 -103
- package/public/js/charts/session-stats.js +117 -117
- package/public/js/charts/token-trend.js +357 -357
- package/public/js/components/date-picker.js +35 -35
- package/public/js/components/plan-selector.js +57 -57
- package/server/aggregator.js +151 -151
- package/server/credentials.js +112 -112
- package/server/index.js +45 -45
- package/server/parser.js +129 -129
- package/server/pricing.js +52 -52
- package/server/routes/api.js +141 -130
- package/server/sync.js +69 -69
|
@@ -1,117 +1,117 @@
|
|
|
1
|
-
function formatTokens(n) {
|
|
2
|
-
if (n >= 1_000_000) return (n / 1_000_000).toFixed(1) + 'M';
|
|
3
|
-
if (n >= 1_000) return (n / 1_000).toFixed(0) + 'K';
|
|
4
|
-
return n.toString();
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
const MODEL_DISPLAY = {
|
|
8
|
-
'claude-opus-4-6': 'opus 4.6',
|
|
9
|
-
'claude-sonnet-4-6': 'sonnet 4.6',
|
|
10
|
-
'claude-haiku-4-5': 'haiku 4.5',
|
|
11
|
-
'claude-haiku-4-5-20251001': 'haiku 4.5',
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
function modelTag(model) {
|
|
15
|
-
const shortName = MODEL_DISPLAY[model] || model.replace('claude-', '').replace(/-(\d+)-(\d+)/, ' $1.$2');
|
|
16
|
-
let cls = 'tag-model-sonnet';
|
|
17
|
-
if (model.includes('opus')) cls = 'tag-model-opus';
|
|
18
|
-
else if (model.includes('haiku')) cls = 'tag-model-haiku';
|
|
19
|
-
return `<span class="tag ${cls}">${shortName}</span>`;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function formatDate(iso) {
|
|
23
|
-
const d = new Date(iso);
|
|
24
|
-
return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }) +
|
|
25
|
-
', ' + d.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', hour12: false });
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function formatDuration(minutes) {
|
|
29
|
-
if (minutes < 60) return `${minutes}m`;
|
|
30
|
-
const h = Math.floor(minutes / 60);
|
|
31
|
-
const m = minutes % 60;
|
|
32
|
-
return `${h}h ${m}m`;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export function renderSessionTable(container, data, { onSort, onPageChange }) {
|
|
36
|
-
container.innerHTML = '';
|
|
37
|
-
|
|
38
|
-
const table = document.createElement('table');
|
|
39
|
-
const thead = document.createElement('thead');
|
|
40
|
-
const headerRow = document.createElement('tr');
|
|
41
|
-
|
|
42
|
-
const columns = [
|
|
43
|
-
{ key: 'date', label: 'Date & Time' },
|
|
44
|
-
{ key: 'project', label: 'Project' },
|
|
45
|
-
{ key: 'models', label: 'Model(s)' },
|
|
46
|
-
{ key: 'input', label: 'Input', align: 'right' },
|
|
47
|
-
{ key: 'output', label: 'Output', align: 'right' },
|
|
48
|
-
{ key: 'cache_read', label: 'Cache Read', align: 'right' },
|
|
49
|
-
{ key: 'cache_creation', label: 'Cache Write', align: 'right' },
|
|
50
|
-
{ key: 'total', label: 'Total', align: 'right' },
|
|
51
|
-
{ key: 'cost', label: 'API Cost', align: 'right' },
|
|
52
|
-
{ key: 'duration', label: 'Duration', align: 'right' },
|
|
53
|
-
];
|
|
54
|
-
|
|
55
|
-
for (const col of columns) {
|
|
56
|
-
const th = document.createElement('th');
|
|
57
|
-
th.textContent = col.label;
|
|
58
|
-
if (col.align) th.className = 'align-right';
|
|
59
|
-
if (['date', 'cost', 'total'].includes(col.key)) {
|
|
60
|
-
th.style.cursor = 'pointer';
|
|
61
|
-
th.addEventListener('click', () => {
|
|
62
|
-
const sortKey = col.key === 'total' ? 'tokens' : col.key;
|
|
63
|
-
onSort(sortKey);
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
headerRow.appendChild(th);
|
|
67
|
-
}
|
|
68
|
-
thead.appendChild(headerRow);
|
|
69
|
-
table.appendChild(thead);
|
|
70
|
-
|
|
71
|
-
const tbody = document.createElement('tbody');
|
|
72
|
-
for (const s of data.sessions) {
|
|
73
|
-
const tr = document.createElement('tr');
|
|
74
|
-
tr.innerHTML = `
|
|
75
|
-
<td>${formatDate(s.startTime)}</td>
|
|
76
|
-
<td><span class="tag tag-project">${s.project}</span></td>
|
|
77
|
-
<td>${s.models.map(modelTag).join(' ')}</td>
|
|
78
|
-
<td class="align-right" style="color:#60a5fa">${formatTokens(s.input_tokens)}</td>
|
|
79
|
-
<td class="align-right" style="color:#f97316">${formatTokens(s.output_tokens)}</td>
|
|
80
|
-
<td class="align-right" style="color:#4ade80">${formatTokens(s.cache_read_tokens)}</td>
|
|
81
|
-
<td class="align-right" style="color:#f59e0b">${formatTokens(s.cache_creation_tokens)}</td>
|
|
82
|
-
<td class="align-right" style="font-weight:600">${formatTokens(s.total_tokens)}</td>
|
|
83
|
-
<td class="align-right" style="color:#f59e0b;font-weight:600">$${s.estimated_cost_usd.toFixed(2)}</td>
|
|
84
|
-
<td class="align-right">${formatDuration(s.duration_minutes)}</td>
|
|
85
|
-
`;
|
|
86
|
-
tbody.appendChild(tr);
|
|
87
|
-
}
|
|
88
|
-
table.appendChild(tbody);
|
|
89
|
-
|
|
90
|
-
if (data.totals) {
|
|
91
|
-
const tfoot = document.createElement('tfoot');
|
|
92
|
-
const tr = document.createElement('tr');
|
|
93
|
-
tr.innerHTML = `
|
|
94
|
-
<td colspan="3">Showing ${data.sessions.length} of ${data.pagination.total_sessions} sessions</td>
|
|
95
|
-
<td class="align-right" colspan="4"></td>
|
|
96
|
-
<td class="align-right">${formatTokens(data.totals.total_tokens)}</td>
|
|
97
|
-
<td class="align-right" style="color:#f59e0b">$${data.totals.estimated_cost_usd.toFixed(2)}</td>
|
|
98
|
-
<td></td>
|
|
99
|
-
`;
|
|
100
|
-
tfoot.appendChild(tr);
|
|
101
|
-
table.appendChild(tfoot);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
container.appendChild(table);
|
|
105
|
-
|
|
106
|
-
const pagEl = document.getElementById('session-pagination');
|
|
107
|
-
if (pagEl && data.pagination && data.pagination.total_pages > 1) {
|
|
108
|
-
pagEl.innerHTML = '';
|
|
109
|
-
for (let i = 1; i <= data.pagination.total_pages; i++) {
|
|
110
|
-
const btn = document.createElement('button');
|
|
111
|
-
btn.textContent = i;
|
|
112
|
-
if (i === data.pagination.page) btn.className = 'active';
|
|
113
|
-
btn.addEventListener('click', () => onPageChange(i));
|
|
114
|
-
pagEl.appendChild(btn);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
}
|
|
1
|
+
function formatTokens(n) {
|
|
2
|
+
if (n >= 1_000_000) return (n / 1_000_000).toFixed(1) + 'M';
|
|
3
|
+
if (n >= 1_000) return (n / 1_000).toFixed(0) + 'K';
|
|
4
|
+
return n.toString();
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const MODEL_DISPLAY = {
|
|
8
|
+
'claude-opus-4-6': 'opus 4.6',
|
|
9
|
+
'claude-sonnet-4-6': 'sonnet 4.6',
|
|
10
|
+
'claude-haiku-4-5': 'haiku 4.5',
|
|
11
|
+
'claude-haiku-4-5-20251001': 'haiku 4.5',
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
function modelTag(model) {
|
|
15
|
+
const shortName = MODEL_DISPLAY[model] || model.replace('claude-', '').replace(/-(\d+)-(\d+)/, ' $1.$2');
|
|
16
|
+
let cls = 'tag-model-sonnet';
|
|
17
|
+
if (model.includes('opus')) cls = 'tag-model-opus';
|
|
18
|
+
else if (model.includes('haiku')) cls = 'tag-model-haiku';
|
|
19
|
+
return `<span class="tag ${cls}">${shortName}</span>`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function formatDate(iso) {
|
|
23
|
+
const d = new Date(iso);
|
|
24
|
+
return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }) +
|
|
25
|
+
', ' + d.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', hour12: false });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function formatDuration(minutes) {
|
|
29
|
+
if (minutes < 60) return `${minutes}m`;
|
|
30
|
+
const h = Math.floor(minutes / 60);
|
|
31
|
+
const m = minutes % 60;
|
|
32
|
+
return `${h}h ${m}m`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function renderSessionTable(container, data, { onSort, onPageChange }) {
|
|
36
|
+
container.innerHTML = '';
|
|
37
|
+
|
|
38
|
+
const table = document.createElement('table');
|
|
39
|
+
const thead = document.createElement('thead');
|
|
40
|
+
const headerRow = document.createElement('tr');
|
|
41
|
+
|
|
42
|
+
const columns = [
|
|
43
|
+
{ key: 'date', label: 'Date & Time' },
|
|
44
|
+
{ key: 'project', label: 'Project' },
|
|
45
|
+
{ key: 'models', label: 'Model(s)' },
|
|
46
|
+
{ key: 'input', label: 'Input', align: 'right' },
|
|
47
|
+
{ key: 'output', label: 'Output', align: 'right' },
|
|
48
|
+
{ key: 'cache_read', label: 'Cache Read', align: 'right' },
|
|
49
|
+
{ key: 'cache_creation', label: 'Cache Write', align: 'right' },
|
|
50
|
+
{ key: 'total', label: 'Total', align: 'right' },
|
|
51
|
+
{ key: 'cost', label: 'API Cost', align: 'right' },
|
|
52
|
+
{ key: 'duration', label: 'Duration', align: 'right' },
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
for (const col of columns) {
|
|
56
|
+
const th = document.createElement('th');
|
|
57
|
+
th.textContent = col.label;
|
|
58
|
+
if (col.align) th.className = 'align-right';
|
|
59
|
+
if (['date', 'cost', 'total'].includes(col.key)) {
|
|
60
|
+
th.style.cursor = 'pointer';
|
|
61
|
+
th.addEventListener('click', () => {
|
|
62
|
+
const sortKey = col.key === 'total' ? 'tokens' : col.key;
|
|
63
|
+
onSort(sortKey);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
headerRow.appendChild(th);
|
|
67
|
+
}
|
|
68
|
+
thead.appendChild(headerRow);
|
|
69
|
+
table.appendChild(thead);
|
|
70
|
+
|
|
71
|
+
const tbody = document.createElement('tbody');
|
|
72
|
+
for (const s of data.sessions) {
|
|
73
|
+
const tr = document.createElement('tr');
|
|
74
|
+
tr.innerHTML = `
|
|
75
|
+
<td>${formatDate(s.startTime)}</td>
|
|
76
|
+
<td><span class="tag tag-project">${s.project}</span></td>
|
|
77
|
+
<td>${s.models.map(modelTag).join(' ')}</td>
|
|
78
|
+
<td class="align-right" style="color:#60a5fa">${formatTokens(s.input_tokens)}</td>
|
|
79
|
+
<td class="align-right" style="color:#f97316">${formatTokens(s.output_tokens)}</td>
|
|
80
|
+
<td class="align-right" style="color:#4ade80">${formatTokens(s.cache_read_tokens)}</td>
|
|
81
|
+
<td class="align-right" style="color:#f59e0b">${formatTokens(s.cache_creation_tokens)}</td>
|
|
82
|
+
<td class="align-right" style="font-weight:600">${formatTokens(s.total_tokens)}</td>
|
|
83
|
+
<td class="align-right" style="color:#f59e0b;font-weight:600">$${s.estimated_cost_usd.toFixed(2)}</td>
|
|
84
|
+
<td class="align-right">${formatDuration(s.duration_minutes)}</td>
|
|
85
|
+
`;
|
|
86
|
+
tbody.appendChild(tr);
|
|
87
|
+
}
|
|
88
|
+
table.appendChild(tbody);
|
|
89
|
+
|
|
90
|
+
if (data.totals) {
|
|
91
|
+
const tfoot = document.createElement('tfoot');
|
|
92
|
+
const tr = document.createElement('tr');
|
|
93
|
+
tr.innerHTML = `
|
|
94
|
+
<td colspan="3">Showing ${data.sessions.length} of ${data.pagination.total_sessions} sessions</td>
|
|
95
|
+
<td class="align-right" colspan="4"></td>
|
|
96
|
+
<td class="align-right">${formatTokens(data.totals.total_tokens)}</td>
|
|
97
|
+
<td class="align-right" style="color:#f59e0b">$${data.totals.estimated_cost_usd.toFixed(2)}</td>
|
|
98
|
+
<td></td>
|
|
99
|
+
`;
|
|
100
|
+
tfoot.appendChild(tr);
|
|
101
|
+
table.appendChild(tfoot);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
container.appendChild(table);
|
|
105
|
+
|
|
106
|
+
const pagEl = document.getElementById('session-pagination');
|
|
107
|
+
if (pagEl && data.pagination && data.pagination.total_pages > 1) {
|
|
108
|
+
pagEl.innerHTML = '';
|
|
109
|
+
for (let i = 1; i <= data.pagination.total_pages; i++) {
|
|
110
|
+
const btn = document.createElement('button');
|
|
111
|
+
btn.textContent = i;
|
|
112
|
+
if (i === data.pagination.page) btn.className = 'active';
|
|
113
|
+
btn.addEventListener('click', () => onPageChange(i));
|
|
114
|
+
pagEl.appendChild(btn);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|