cc-starter 1.0.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.
@@ -0,0 +1,249 @@
1
+ #!/usr/bin/env node
2
+
3
+ // cc-starter Token Tracker
4
+ // Track token savings from vibe-coded tooling vs traditional approaches.
5
+ // CommonJS, zero external dependencies.
6
+
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+
10
+ const STATS_FILE = path.join(process.cwd(), '.vibe-stats.json');
11
+
12
+ // ---------------------------------------------------------------------------
13
+ // Helpers
14
+ // ---------------------------------------------------------------------------
15
+
16
+ function loadStats() {
17
+ if (!fs.existsSync(STATS_FILE)) {
18
+ return { sessions: [], totalSaved: 0, operationsCount: 0, created: new Date().toISOString() };
19
+ }
20
+ try {
21
+ return JSON.parse(fs.readFileSync(STATS_FILE, 'utf-8'));
22
+ } catch {
23
+ console.error('Error: could not parse .vibe-stats.json — starting fresh.');
24
+ return { sessions: [], totalSaved: 0, operationsCount: 0, created: new Date().toISOString() };
25
+ }
26
+ }
27
+
28
+ function saveStats(stats) {
29
+ fs.writeFileSync(STATS_FILE, JSON.stringify(stats, null, 2), 'utf-8');
30
+ }
31
+
32
+ function fmt(n) {
33
+ return Number(n).toLocaleString('en-US');
34
+ }
35
+
36
+ function estimateTokens(charCount) {
37
+ return Math.ceil(charCount / 4);
38
+ }
39
+
40
+ // ---------------------------------------------------------------------------
41
+ // Commands
42
+ // ---------------------------------------------------------------------------
43
+
44
+ function cmdLog(args) {
45
+ const operation = args[0];
46
+ const traditionalSize = parseInt(args[1], 10);
47
+ const vibeSize = parseInt(args[2], 10);
48
+ const file = args[3] || null;
49
+
50
+ if (!operation || isNaN(traditionalSize) || isNaN(vibeSize)) {
51
+ console.error('Usage: vibe-stats log <operation> <traditional-size> <vibe-size> [file]');
52
+ process.exit(1);
53
+ }
54
+
55
+ const saved = traditionalSize - vibeSize;
56
+ const percentage = traditionalSize > 0 ? Math.round((saved / traditionalSize) * 100) : 0;
57
+
58
+ const entry = {
59
+ timestamp: new Date().toISOString(),
60
+ operation,
61
+ file,
62
+ traditionalTokens: traditionalSize,
63
+ vibeModeTokens: vibeSize,
64
+ savedTokens: saved,
65
+ percentage,
66
+ };
67
+
68
+ const stats = loadStats();
69
+ stats.sessions.push(entry);
70
+ stats.totalSaved += saved;
71
+ stats.operationsCount += 1;
72
+ saveStats(stats);
73
+
74
+ console.log(`Logged: ${operation} — saved ${fmt(saved)} tokens (${percentage}%)`);
75
+ }
76
+
77
+ function cmdReport() {
78
+ const stats = loadStats();
79
+
80
+ if (stats.operationsCount === 0) {
81
+ console.log('No operations recorded yet. Use "vibe-stats log" to add entries.');
82
+ return;
83
+ }
84
+
85
+ // Period
86
+ const created = new Date(stats.created);
87
+ const now = new Date();
88
+ const days = Math.max(1, Math.ceil((now - created) / (1000 * 60 * 60 * 24)));
89
+
90
+ console.log('');
91
+ console.log('=== cc-starter Token Tracker — Report ===');
92
+ console.log('');
93
+ console.log(`Period: ${created.toISOString().slice(0, 10)} → ${now.toISOString().slice(0, 10)} (${days} day${days !== 1 ? 's' : ''})`);
94
+ console.log(`Operations: ${fmt(stats.operationsCount)}`);
95
+ console.log(`Total saved: ${fmt(stats.totalSaved)} tokens`);
96
+ console.log('');
97
+
98
+ // Breakdown by operation type
99
+ const byOp = {};
100
+ for (const s of stats.sessions) {
101
+ if (!byOp[s.operation]) {
102
+ byOp[s.operation] = { count: 0, saved: 0, traditional: 0, vibe: 0 };
103
+ }
104
+ byOp[s.operation].count += 1;
105
+ byOp[s.operation].saved += s.savedTokens;
106
+ byOp[s.operation].traditional += s.traditionalTokens;
107
+ byOp[s.operation].vibe += s.vibeModeTokens;
108
+ }
109
+
110
+ console.log('--- Breakdown by operation ---');
111
+ const ops = Object.keys(byOp).sort((a, b) => byOp[b].saved - byOp[a].saved);
112
+ for (const op of ops) {
113
+ const d = byOp[op];
114
+ const pct = d.traditional > 0 ? Math.round((d.saved / d.traditional) * 100) : 0;
115
+ console.log(` ${op}: ${fmt(d.saved)} tokens saved across ${d.count} op${d.count !== 1 ? 's' : ''} (${pct}% avg reduction)`);
116
+ }
117
+
118
+ // Recent 10
119
+ const recent = stats.sessions.slice(-10).reverse();
120
+ console.log('');
121
+ console.log('--- Recent operations (last 10) ---');
122
+ for (const s of recent) {
123
+ const ts = s.timestamp.slice(0, 16).replace('T', ' ');
124
+ const fileStr = s.file ? ` [${s.file}]` : '';
125
+ console.log(` ${ts} ${s.operation}${fileStr} saved ${fmt(s.savedTokens)} (${s.percentage}%)`);
126
+ }
127
+ console.log('');
128
+ }
129
+
130
+ function cmdEstimate(args) {
131
+ const filePath = args[0];
132
+ if (!filePath) {
133
+ console.error('Usage: vibe-stats estimate <file>');
134
+ process.exit(1);
135
+ }
136
+
137
+ const resolved = path.resolve(filePath);
138
+ if (!fs.existsSync(resolved)) {
139
+ console.error(`File not found: ${resolved}`);
140
+ process.exit(1);
141
+ }
142
+
143
+ const content = fs.readFileSync(resolved, 'utf-8');
144
+ const chars = content.length;
145
+ const lines = content.split('\n').length;
146
+ const tokens = estimateTokens(chars);
147
+
148
+ console.log(`File: ${path.basename(resolved)}`);
149
+ console.log(`Chars: ${fmt(chars)}`);
150
+ console.log(`Lines: ${fmt(lines)}`);
151
+ console.log(`Tokens: ~${fmt(tokens)} (estimated at 1 token per 4 chars)`);
152
+ }
153
+
154
+ function cmdCompare(args) {
155
+ const file1 = args[0];
156
+ const file2 = args[1];
157
+ if (!file1 || !file2) {
158
+ console.error('Usage: vibe-stats compare <file1> <file2>');
159
+ process.exit(1);
160
+ }
161
+
162
+ const resolved1 = path.resolve(file1);
163
+ const resolved2 = path.resolve(file2);
164
+
165
+ for (const f of [resolved1, resolved2]) {
166
+ if (!fs.existsSync(f)) {
167
+ console.error(`File not found: ${f}`);
168
+ process.exit(1);
169
+ }
170
+ }
171
+
172
+ const content1 = fs.readFileSync(resolved1, 'utf-8');
173
+ const content2 = fs.readFileSync(resolved2, 'utf-8');
174
+ const tokens1 = estimateTokens(content1.length);
175
+ const tokens2 = estimateTokens(content2.length);
176
+ const diff = tokens1 - tokens2;
177
+ const pct = tokens1 > 0 ? Math.round((Math.abs(diff) / tokens1) * 100) : 0;
178
+
179
+ console.log(`File 1: ${path.basename(resolved1)} — ~${fmt(tokens1)} tokens`);
180
+ console.log(`File 2: ${path.basename(resolved2)} — ~${fmt(tokens2)} tokens`);
181
+ console.log('');
182
+ if (diff > 0) {
183
+ console.log(`File 1 is larger by ~${fmt(diff)} tokens (${pct}% more)`);
184
+ } else if (diff < 0) {
185
+ console.log(`File 2 is larger by ~${fmt(Math.abs(diff))} tokens (${pct}% more)`);
186
+ } else {
187
+ console.log('Both files have approximately the same token count.');
188
+ }
189
+ }
190
+
191
+ function cmdSummary() {
192
+ const stats = loadStats();
193
+ if (stats.operationsCount === 0) {
194
+ console.log('No operations recorded yet.');
195
+ return;
196
+ }
197
+ console.log(`Total: ${fmt(stats.totalSaved)} tokens saved across ${fmt(stats.operationsCount)} operations`);
198
+ }
199
+
200
+ function cmdReset(args) {
201
+ if (!args.includes('--confirm')) {
202
+ console.error('Usage: vibe-stats reset --confirm');
203
+ console.error('This will delete all recorded statistics.');
204
+ process.exit(1);
205
+ }
206
+
207
+ if (fs.existsSync(STATS_FILE)) {
208
+ fs.unlinkSync(STATS_FILE);
209
+ }
210
+ console.log('Statistics reset.');
211
+ }
212
+
213
+ // ---------------------------------------------------------------------------
214
+ // Main
215
+ // ---------------------------------------------------------------------------
216
+
217
+ const [command, ...args] = process.argv.slice(2);
218
+
219
+ switch (command) {
220
+ case 'log':
221
+ cmdLog(args);
222
+ break;
223
+ case 'report':
224
+ cmdReport();
225
+ break;
226
+ case 'estimate':
227
+ cmdEstimate(args);
228
+ break;
229
+ case 'compare':
230
+ cmdCompare(args);
231
+ break;
232
+ case 'summary':
233
+ cmdSummary();
234
+ break;
235
+ case 'reset':
236
+ cmdReset(args);
237
+ break;
238
+ default:
239
+ console.log('cc-starter Token Tracker');
240
+ console.log('');
241
+ console.log('Commands:');
242
+ console.log(' log <operation> <traditional-size> <vibe-size> [file] Log a token savings entry');
243
+ console.log(' report Show detailed statistics');
244
+ console.log(' estimate <file> Estimate tokens in a file');
245
+ console.log(' compare <file1> <file2> Compare token counts');
246
+ console.log(' summary One-liner quick check');
247
+ console.log(' reset --confirm Reset all statistics');
248
+ break;
249
+ }