cctotals 1.0.1
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/.github/workflows/publish.yml +25 -0
- package/package.json +24 -0
- package/token-graph.js +112 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
name: Publish to npm
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
publish:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
steps:
|
|
11
|
+
- uses: actions/checkout@v4
|
|
12
|
+
|
|
13
|
+
- uses: actions/setup-node@v4
|
|
14
|
+
with:
|
|
15
|
+
node-version: '20'
|
|
16
|
+
registry-url: 'https://registry.npmjs.org'
|
|
17
|
+
|
|
18
|
+
- name: Publish
|
|
19
|
+
run: |
|
|
20
|
+
echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > .npmrc
|
|
21
|
+
npm install -g npm
|
|
22
|
+
npm version patch --git-tag-version=false
|
|
23
|
+
npm publish --access public
|
|
24
|
+
env:
|
|
25
|
+
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "cctotals",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Analyze and visualize Claude Code token usage with weekly graphs",
|
|
5
|
+
"main": "token-graph.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"cctotals": "token-graph.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"start": "node token-graph.js"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"claude",
|
|
14
|
+
"tokens",
|
|
15
|
+
"analytics",
|
|
16
|
+
"claude-code"
|
|
17
|
+
],
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "https://github.com/AnEntrypoint/cctotals"
|
|
21
|
+
},
|
|
22
|
+
"author": "AnEntrypoint",
|
|
23
|
+
"license": "MIT"
|
|
24
|
+
}
|
package/token-graph.js
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
const statsPath = path.join(process.env.HOME || process.env.USERPROFILE, '.claude', 'stats-cache.json');
|
|
7
|
+
|
|
8
|
+
if (!fs.existsSync(statsPath)) {
|
|
9
|
+
console.error('stats-cache.json not found at:', statsPath);
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const stats = JSON.parse(fs.readFileSync(statsPath, 'utf8'));
|
|
14
|
+
|
|
15
|
+
// Group daily tokens into weeks
|
|
16
|
+
function getWeekKey(dateStr) {
|
|
17
|
+
const date = new Date(dateStr);
|
|
18
|
+
// Get ISO week start (Monday)
|
|
19
|
+
const day = date.getDay();
|
|
20
|
+
const diff = date.getDate() - day + (day === 0 ? -6 : 1);
|
|
21
|
+
const weekStart = new Date(date.setDate(diff));
|
|
22
|
+
return weekStart.toISOString().split('T')[0];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const weeklyTokens = {};
|
|
26
|
+
|
|
27
|
+
// Process daily model tokens
|
|
28
|
+
for (const dayData of stats.dailyModelTokens) {
|
|
29
|
+
const weekKey = getWeekKey(dayData.date);
|
|
30
|
+
if (!weeklyTokens[weekKey]) {
|
|
31
|
+
weeklyTokens[weekKey] = { total: 0, byModel: {} };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
for (const [model, tokens] of Object.entries(dayData.tokensByModel)) {
|
|
35
|
+
weeklyTokens[weekKey].total += tokens;
|
|
36
|
+
weeklyTokens[weekKey].byModel[model] = (weeklyTokens[weekKey].byModel[model] || 0) + tokens;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Sort weeks chronologically
|
|
41
|
+
const sortedWeeks = Object.keys(weeklyTokens).sort();
|
|
42
|
+
|
|
43
|
+
// Find max for scaling
|
|
44
|
+
const maxTokens = Math.max(...sortedWeeks.map(w => weeklyTokens[w].total));
|
|
45
|
+
|
|
46
|
+
// Chart settings
|
|
47
|
+
const CHART_WIDTH = 60;
|
|
48
|
+
const LABEL_WIDTH = 12;
|
|
49
|
+
const BAR_HEIGHT = 1;
|
|
50
|
+
const SCALE = CHART_WIDTH / maxTokens;
|
|
51
|
+
|
|
52
|
+
// Colors for terminal (Windows-compatible)
|
|
53
|
+
const colors = {
|
|
54
|
+
header: '\x1b[36m', // Cyan
|
|
55
|
+
label: '\x1b[33m', // Yellow
|
|
56
|
+
bar: '\x1b[32m', // Green
|
|
57
|
+
total: '\x1b[35m', // Magenta
|
|
58
|
+
reset: '\x1b[0m'
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
console.log('\n' + colors.header + '═'.repeat(70));
|
|
62
|
+
console.log(' WEEKLY TOKEN USAGE GRAPH');
|
|
63
|
+
console.log('═'.repeat(70) + colors.reset);
|
|
64
|
+
console.log();
|
|
65
|
+
|
|
66
|
+
// Draw the chart
|
|
67
|
+
console.log(colors.label + ' Week Start ' + colors.reset + '|' + colors.bar + ' Tokens (scaled)' + colors.reset);
|
|
68
|
+
|
|
69
|
+
// Find longest bar needed for alignment
|
|
70
|
+
const maxBarWidth = Math.floor(maxTokens / (maxTokens / CHART_WIDTH));
|
|
71
|
+
const chartLine = '─'.repeat(LABEL_WIDTH) + '┼' + '─'.repeat(CHART_WIDTH);
|
|
72
|
+
console.log(colors.label + chartLine + colors.reset);
|
|
73
|
+
|
|
74
|
+
let grandTotal = 0;
|
|
75
|
+
for (const week of sortedWeeks) {
|
|
76
|
+
const data = weeklyTokens[week];
|
|
77
|
+
const barLen = Math.max(1, Math.floor(data.total / (maxTokens / CHART_WIDTH)));
|
|
78
|
+
const bar = colors.bar + '█'.repeat(barLen) + colors.reset;
|
|
79
|
+
const label = colors.label + week + colors.reset;
|
|
80
|
+
const spaces = ' '.repeat(Math.max(0, LABEL_WIDTH - label.length));
|
|
81
|
+
|
|
82
|
+
// Format token count with commas
|
|
83
|
+
const tokenStr = data.total.toLocaleString();
|
|
84
|
+
const padding = ' '.repeat(Math.max(0, CHART_WIDTH - barLen - tokenStr.length - 3));
|
|
85
|
+
|
|
86
|
+
console.log(` ${label} │${bar}${padding} ${colors.total}${tokenStr}${colors.reset}`);
|
|
87
|
+
grandTotal += data.total;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Footer
|
|
91
|
+
console.log(colors.label + chartLine + colors.reset);
|
|
92
|
+
console.log(`\n ${colors.header}Total:${colors.reset} ${colors.total}${grandTotal.toLocaleString()}${colors.reset} tokens across ${sortedWeeks.length} weeks`);
|
|
93
|
+
console.log(` ${colors.header}Average:${colors.reset} ${Math.round(grandTotal / sortedWeeks.length).toLocaleString()} tokens/week`);
|
|
94
|
+
console.log('\n' + colors.header + '═'.repeat(70) + colors.reset + '\n');
|
|
95
|
+
|
|
96
|
+
// Detailed breakdown by model
|
|
97
|
+
console.log(colors.header + ' DETAILED BREAKDOWN BY MODEL' + colors.reset);
|
|
98
|
+
console.log('─'.repeat(50));
|
|
99
|
+
|
|
100
|
+
// Aggregate model stats
|
|
101
|
+
const modelTotals = {};
|
|
102
|
+
for (const week of sortedWeeks) {
|
|
103
|
+
for (const [model, tokens] of Object.entries(weeklyTokens[week].byModel)) {
|
|
104
|
+
modelTotals[model] = (modelTotals[model] || 0) + tokens;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
for (const [model, tokens] of Object.entries(modelTotals)) {
|
|
109
|
+
const pct = ((tokens / grandTotal) * 100).toFixed(1);
|
|
110
|
+
console.log(` ${model.padEnd(30)} ${tokens.toLocaleString().padStart(12)} (${pct}%)`);
|
|
111
|
+
}
|
|
112
|
+
console.log();
|