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.
@@ -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();