ccgather 1.0.0 → 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/dist/index.js +286 -111
- package/package.json +51 -51
package/dist/index.js
CHANGED
|
@@ -76,16 +76,16 @@ __export(reset_exports, {
|
|
|
76
76
|
reset: () => reset
|
|
77
77
|
});
|
|
78
78
|
function getClaudeSettingsDir() {
|
|
79
|
-
return
|
|
79
|
+
return path3.join(os3.homedir(), ".claude");
|
|
80
80
|
}
|
|
81
81
|
function removeStopHook() {
|
|
82
82
|
const claudeDir = getClaudeSettingsDir();
|
|
83
|
-
const settingsPath =
|
|
84
|
-
if (!
|
|
83
|
+
const settingsPath = path3.join(claudeDir, "settings.json");
|
|
84
|
+
if (!fs3.existsSync(settingsPath)) {
|
|
85
85
|
return { success: true, message: "No settings file found" };
|
|
86
86
|
}
|
|
87
87
|
try {
|
|
88
|
-
const content =
|
|
88
|
+
const content = fs3.readFileSync(settingsPath, "utf-8");
|
|
89
89
|
const settings = JSON.parse(content);
|
|
90
90
|
if (settings.hooks && typeof settings.hooks === "object") {
|
|
91
91
|
const hooks = settings.hooks;
|
|
@@ -102,7 +102,7 @@ function removeStopHook() {
|
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
104
|
}
|
|
105
|
-
|
|
105
|
+
fs3.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
106
106
|
return { success: true, message: "Hook removed" };
|
|
107
107
|
} catch (err) {
|
|
108
108
|
return {
|
|
@@ -113,9 +113,9 @@ function removeStopHook() {
|
|
|
113
113
|
}
|
|
114
114
|
function removeSyncScript() {
|
|
115
115
|
const claudeDir = getClaudeSettingsDir();
|
|
116
|
-
const scriptPath =
|
|
117
|
-
if (
|
|
118
|
-
|
|
116
|
+
const scriptPath = path3.join(claudeDir, "ccgather-sync.js");
|
|
117
|
+
if (fs3.existsSync(scriptPath)) {
|
|
118
|
+
fs3.unlinkSync(scriptPath);
|
|
119
119
|
}
|
|
120
120
|
}
|
|
121
121
|
async function reset() {
|
|
@@ -170,18 +170,18 @@ async function reset() {
|
|
|
170
170
|
console.log(import_chalk3.default.green.bold("\u2705 Reset complete!"));
|
|
171
171
|
console.log();
|
|
172
172
|
console.log(import_chalk3.default.gray("Your usage will no longer be tracked."));
|
|
173
|
-
console.log(import_chalk3.default.gray("Run `npx
|
|
173
|
+
console.log(import_chalk3.default.gray("Run `npx ccgather` to set up again."));
|
|
174
174
|
console.log();
|
|
175
175
|
}
|
|
176
|
-
var import_chalk3, import_ora3,
|
|
176
|
+
var import_chalk3, import_ora3, fs3, path3, os3, import_inquirer;
|
|
177
177
|
var init_reset = __esm({
|
|
178
178
|
"src/commands/reset.ts"() {
|
|
179
179
|
"use strict";
|
|
180
180
|
import_chalk3 = __toESM(require("chalk"));
|
|
181
181
|
import_ora3 = __toESM(require("ora"));
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
182
|
+
fs3 = __toESM(require("fs"));
|
|
183
|
+
path3 = __toESM(require("path"));
|
|
184
|
+
os3 = __toESM(require("os"));
|
|
185
185
|
import_inquirer = __toESM(require("inquirer"));
|
|
186
186
|
init_config();
|
|
187
187
|
}
|
|
@@ -193,51 +193,59 @@ var import_commander = require("commander");
|
|
|
193
193
|
// src/commands/submit.ts
|
|
194
194
|
var import_chalk = __toESM(require("chalk"));
|
|
195
195
|
var import_ora = __toESM(require("ora"));
|
|
196
|
+
var fs2 = __toESM(require("fs"));
|
|
197
|
+
var path2 = __toESM(require("path"));
|
|
198
|
+
var os2 = __toESM(require("os"));
|
|
199
|
+
init_config();
|
|
200
|
+
|
|
201
|
+
// src/lib/ccgather-json.ts
|
|
196
202
|
var fs = __toESM(require("fs"));
|
|
197
203
|
var path = __toESM(require("path"));
|
|
198
204
|
var os = __toESM(require("os"));
|
|
199
|
-
|
|
200
|
-
function
|
|
201
|
-
|
|
202
|
-
path.join(process.cwd(), "cc.json"),
|
|
203
|
-
path.join(os.homedir(), "cc.json"),
|
|
204
|
-
path.join(os.homedir(), ".claude", "cc.json")
|
|
205
|
-
];
|
|
206
|
-
for (const p of possiblePaths) {
|
|
207
|
-
if (fs.existsSync(p)) {
|
|
208
|
-
return p;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
return null;
|
|
205
|
+
var CCGATHER_JSON_VERSION = "1.0.0";
|
|
206
|
+
function getCCGatherJsonPath() {
|
|
207
|
+
return path.join(os.homedir(), ".claude", "ccgather.json");
|
|
212
208
|
}
|
|
213
|
-
function
|
|
209
|
+
function getClaudeProjectsDir() {
|
|
210
|
+
return path.join(os.homedir(), ".claude", "projects");
|
|
211
|
+
}
|
|
212
|
+
function findJsonlFiles(dir) {
|
|
213
|
+
const files = [];
|
|
214
214
|
try {
|
|
215
|
-
const
|
|
216
|
-
const
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
daysTracked: data.daysTracked || data.days_tracked || calculateDaysTracked(data)
|
|
225
|
-
};
|
|
215
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
216
|
+
for (const entry of entries) {
|
|
217
|
+
const fullPath = path.join(dir, entry.name);
|
|
218
|
+
if (entry.isDirectory()) {
|
|
219
|
+
files.push(...findJsonlFiles(fullPath));
|
|
220
|
+
} else if (entry.name.endsWith(".jsonl")) {
|
|
221
|
+
files.push(fullPath);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
226
224
|
} catch {
|
|
227
|
-
return null;
|
|
228
225
|
}
|
|
226
|
+
return files;
|
|
229
227
|
}
|
|
230
|
-
function
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
228
|
+
function estimateCost(model, inputTokens, outputTokens) {
|
|
229
|
+
const pricing = {
|
|
230
|
+
"claude-opus-4": { input: 15, output: 75 },
|
|
231
|
+
"claude-sonnet-4": { input: 3, output: 15 },
|
|
232
|
+
"claude-haiku": { input: 0.25, output: 1.25 },
|
|
233
|
+
"default": { input: 3, output: 15 }
|
|
234
|
+
};
|
|
235
|
+
let modelKey = "default";
|
|
236
|
+
for (const key of Object.keys(pricing)) {
|
|
237
|
+
if (model.includes(key.replace("claude-", ""))) {
|
|
238
|
+
modelKey = key;
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
236
241
|
}
|
|
237
|
-
|
|
242
|
+
const price = pricing[modelKey];
|
|
243
|
+
const inputCost = inputTokens / 1e6 * price.input;
|
|
244
|
+
const outputCost = outputTokens / 1e6 * price.output;
|
|
245
|
+
return Math.round((inputCost + outputCost) * 100) / 100;
|
|
238
246
|
}
|
|
239
|
-
function
|
|
240
|
-
const projectsDir =
|
|
247
|
+
function scanUsageData() {
|
|
248
|
+
const projectsDir = getClaudeProjectsDir();
|
|
241
249
|
if (!fs.existsSync(projectsDir)) {
|
|
242
250
|
return null;
|
|
243
251
|
}
|
|
@@ -245,24 +253,14 @@ function parseUsageFromJsonl() {
|
|
|
245
253
|
let totalOutputTokens = 0;
|
|
246
254
|
let totalCacheRead = 0;
|
|
247
255
|
let totalCacheWrite = 0;
|
|
256
|
+
let totalCost = 0;
|
|
257
|
+
let sessionsCount = 0;
|
|
248
258
|
const dates = /* @__PURE__ */ new Set();
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
253
|
-
for (const entry of entries) {
|
|
254
|
-
const fullPath = path.join(dir, entry.name);
|
|
255
|
-
if (entry.isDirectory()) {
|
|
256
|
-
files.push(...findJsonlFiles(fullPath));
|
|
257
|
-
} else if (entry.name.endsWith(".jsonl")) {
|
|
258
|
-
files.push(fullPath);
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
} catch {
|
|
262
|
-
}
|
|
263
|
-
return files;
|
|
264
|
-
}
|
|
259
|
+
const models = {};
|
|
260
|
+
let firstTimestamp = null;
|
|
261
|
+
let lastTimestamp = null;
|
|
265
262
|
const jsonlFiles = findJsonlFiles(projectsDir);
|
|
263
|
+
sessionsCount = jsonlFiles.length;
|
|
266
264
|
for (const filePath of jsonlFiles) {
|
|
267
265
|
try {
|
|
268
266
|
const content = fs.readFileSync(filePath, "utf-8");
|
|
@@ -272,13 +270,25 @@ function parseUsageFromJsonl() {
|
|
|
272
270
|
const event = JSON.parse(line);
|
|
273
271
|
if (event.type === "assistant" && event.message?.usage) {
|
|
274
272
|
const usage = event.message.usage;
|
|
275
|
-
|
|
276
|
-
|
|
273
|
+
const model = event.message.model || "unknown";
|
|
274
|
+
const inputTokens = usage.input_tokens || 0;
|
|
275
|
+
const outputTokens = usage.output_tokens || 0;
|
|
276
|
+
totalInputTokens += inputTokens;
|
|
277
|
+
totalOutputTokens += outputTokens;
|
|
277
278
|
totalCacheRead += usage.cache_read_input_tokens || 0;
|
|
278
279
|
totalCacheWrite += usage.cache_creation_input_tokens || 0;
|
|
280
|
+
totalCost += estimateCost(model, inputTokens, outputTokens);
|
|
281
|
+
const totalModelTokens = inputTokens + outputTokens;
|
|
282
|
+
models[model] = (models[model] || 0) + totalModelTokens;
|
|
279
283
|
if (event.timestamp) {
|
|
280
284
|
const date = new Date(event.timestamp).toISOString().split("T")[0];
|
|
281
285
|
dates.add(date);
|
|
286
|
+
if (!firstTimestamp || event.timestamp < firstTimestamp) {
|
|
287
|
+
firstTimestamp = event.timestamp;
|
|
288
|
+
}
|
|
289
|
+
if (!lastTimestamp || event.timestamp > lastTimestamp) {
|
|
290
|
+
lastTimestamp = event.timestamp;
|
|
291
|
+
}
|
|
282
292
|
}
|
|
283
293
|
}
|
|
284
294
|
} catch {
|
|
@@ -291,18 +301,106 @@ function parseUsageFromJsonl() {
|
|
|
291
301
|
if (totalTokens === 0) {
|
|
292
302
|
return null;
|
|
293
303
|
}
|
|
294
|
-
const costPerMillion = 3;
|
|
295
|
-
const totalCost = totalTokens / 1e6 * costPerMillion;
|
|
296
304
|
return {
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
305
|
+
version: CCGATHER_JSON_VERSION,
|
|
306
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
307
|
+
lastScanned: (/* @__PURE__ */ new Date()).toISOString(),
|
|
308
|
+
usage: {
|
|
309
|
+
totalTokens,
|
|
310
|
+
totalCost: Math.round(totalCost * 100) / 100,
|
|
311
|
+
inputTokens: totalInputTokens,
|
|
312
|
+
outputTokens: totalOutputTokens,
|
|
313
|
+
cacheReadTokens: totalCacheRead,
|
|
314
|
+
cacheWriteTokens: totalCacheWrite
|
|
315
|
+
},
|
|
316
|
+
stats: {
|
|
317
|
+
daysTracked: dates.size,
|
|
318
|
+
sessionsCount,
|
|
319
|
+
firstUsed: firstTimestamp ? new Date(firstTimestamp).toISOString().split("T")[0] : null,
|
|
320
|
+
lastUsed: lastTimestamp ? new Date(lastTimestamp).toISOString().split("T")[0] : null
|
|
321
|
+
},
|
|
322
|
+
models
|
|
304
323
|
};
|
|
305
324
|
}
|
|
325
|
+
function readCCGatherJson() {
|
|
326
|
+
const jsonPath = getCCGatherJsonPath();
|
|
327
|
+
if (!fs.existsSync(jsonPath)) {
|
|
328
|
+
return null;
|
|
329
|
+
}
|
|
330
|
+
try {
|
|
331
|
+
const content = fs.readFileSync(jsonPath, "utf-8");
|
|
332
|
+
return JSON.parse(content);
|
|
333
|
+
} catch {
|
|
334
|
+
return null;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
function writeCCGatherJson(data) {
|
|
338
|
+
const jsonPath = getCCGatherJsonPath();
|
|
339
|
+
const claudeDir = path.dirname(jsonPath);
|
|
340
|
+
if (!fs.existsSync(claudeDir)) {
|
|
341
|
+
fs.mkdirSync(claudeDir, { recursive: true });
|
|
342
|
+
}
|
|
343
|
+
fs.writeFileSync(jsonPath, JSON.stringify(data, null, 2));
|
|
344
|
+
}
|
|
345
|
+
function scanAndSave() {
|
|
346
|
+
const data = scanUsageData();
|
|
347
|
+
if (data) {
|
|
348
|
+
writeCCGatherJson(data);
|
|
349
|
+
}
|
|
350
|
+
return data;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// src/commands/submit.ts
|
|
354
|
+
function ccgatherToUsageData(data) {
|
|
355
|
+
return {
|
|
356
|
+
totalTokens: data.usage.totalTokens,
|
|
357
|
+
totalCost: data.usage.totalCost,
|
|
358
|
+
inputTokens: data.usage.inputTokens,
|
|
359
|
+
outputTokens: data.usage.outputTokens,
|
|
360
|
+
cacheReadTokens: data.usage.cacheReadTokens,
|
|
361
|
+
cacheWriteTokens: data.usage.cacheWriteTokens,
|
|
362
|
+
daysTracked: data.stats.daysTracked
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
function findCcJson() {
|
|
366
|
+
const possiblePaths = [
|
|
367
|
+
path2.join(process.cwd(), "cc.json"),
|
|
368
|
+
path2.join(os2.homedir(), "cc.json"),
|
|
369
|
+
path2.join(os2.homedir(), ".claude", "cc.json")
|
|
370
|
+
];
|
|
371
|
+
for (const p of possiblePaths) {
|
|
372
|
+
if (fs2.existsSync(p)) {
|
|
373
|
+
return p;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
return null;
|
|
377
|
+
}
|
|
378
|
+
function parseCcJson(filePath) {
|
|
379
|
+
try {
|
|
380
|
+
const content = fs2.readFileSync(filePath, "utf-8");
|
|
381
|
+
const data = JSON.parse(content);
|
|
382
|
+
return {
|
|
383
|
+
totalTokens: data.totalTokens || data.total_tokens || 0,
|
|
384
|
+
totalCost: data.totalCost || data.total_cost || data.costUSD || 0,
|
|
385
|
+
inputTokens: data.inputTokens || data.input_tokens || 0,
|
|
386
|
+
outputTokens: data.outputTokens || data.output_tokens || 0,
|
|
387
|
+
cacheReadTokens: data.cacheReadTokens || data.cache_read_tokens || 0,
|
|
388
|
+
cacheWriteTokens: data.cacheWriteTokens || data.cache_write_tokens || 0,
|
|
389
|
+
daysTracked: data.daysTracked || data.days_tracked || calculateDaysTracked(data)
|
|
390
|
+
};
|
|
391
|
+
} catch {
|
|
392
|
+
return null;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
function calculateDaysTracked(data) {
|
|
396
|
+
if (data.dailyStats && Array.isArray(data.dailyStats)) {
|
|
397
|
+
return data.dailyStats.length;
|
|
398
|
+
}
|
|
399
|
+
if (data.daily && typeof data.daily === "object") {
|
|
400
|
+
return Object.keys(data.daily).length;
|
|
401
|
+
}
|
|
402
|
+
return 1;
|
|
403
|
+
}
|
|
306
404
|
function formatNumber(num) {
|
|
307
405
|
return num.toLocaleString();
|
|
308
406
|
}
|
|
@@ -373,36 +471,55 @@ async function submit(options) {
|
|
|
373
471
|
}
|
|
374
472
|
]);
|
|
375
473
|
username = confirmedUsername.trim();
|
|
376
|
-
const ccJsonPath = findCcJson();
|
|
377
474
|
let usageData = null;
|
|
378
475
|
let dataSource = "";
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
476
|
+
const ccgatherData = readCCGatherJson();
|
|
477
|
+
if (ccgatherData) {
|
|
478
|
+
usageData = ccgatherToUsageData(ccgatherData);
|
|
479
|
+
dataSource = "ccgather.json";
|
|
480
|
+
console.log(import_chalk.default.green(`\u2713 Found ${dataSource}`));
|
|
481
|
+
console.log(import_chalk.default.gray(` Last scanned: ${new Date(ccgatherData.lastScanned).toLocaleString()}
|
|
482
|
+
`));
|
|
483
|
+
}
|
|
484
|
+
if (!usageData) {
|
|
485
|
+
const ccJsonPath = findCcJson();
|
|
486
|
+
if (ccJsonPath) {
|
|
487
|
+
const { useCcJson } = await inquirer2.default.prompt([
|
|
488
|
+
{
|
|
489
|
+
type: "confirm",
|
|
490
|
+
name: "useCcJson",
|
|
491
|
+
message: `Found existing cc.json. Use this file?`,
|
|
492
|
+
default: true
|
|
493
|
+
}
|
|
494
|
+
]);
|
|
495
|
+
if (useCcJson) {
|
|
496
|
+
usageData = parseCcJson(ccJsonPath);
|
|
497
|
+
dataSource = "cc.json";
|
|
386
498
|
}
|
|
387
|
-
]);
|
|
388
|
-
if (useCcJson) {
|
|
389
|
-
usageData = parseCcJson(ccJsonPath);
|
|
390
|
-
dataSource = "cc.json";
|
|
391
499
|
}
|
|
392
500
|
}
|
|
393
501
|
if (!usageData) {
|
|
394
502
|
const parseSpinner = (0, import_ora.default)("Scanning Claude Code usage data...").start();
|
|
395
|
-
|
|
503
|
+
const scannedData = scanAndSave();
|
|
396
504
|
parseSpinner.stop();
|
|
397
|
-
|
|
505
|
+
if (scannedData) {
|
|
506
|
+
usageData = ccgatherToUsageData(scannedData);
|
|
507
|
+
dataSource = "Claude Code logs";
|
|
508
|
+
console.log(import_chalk.default.green(`\u2713 Scanned and saved to ccgather.json`));
|
|
509
|
+
console.log(import_chalk.default.gray(` Path: ${getCCGatherJsonPath()}
|
|
510
|
+
`));
|
|
511
|
+
}
|
|
398
512
|
}
|
|
399
513
|
if (!usageData) {
|
|
400
514
|
console.log(import_chalk.default.red("\n\u274C No usage data found."));
|
|
401
|
-
console.log(import_chalk.default.gray("Make sure you have used Claude Code
|
|
515
|
+
console.log(import_chalk.default.gray("Make sure you have used Claude Code."));
|
|
516
|
+
console.log(import_chalk.default.gray("Run: npx ccgather scan\n"));
|
|
402
517
|
process.exit(1);
|
|
403
518
|
}
|
|
404
|
-
|
|
519
|
+
if (dataSource && dataSource !== "Claude Code logs") {
|
|
520
|
+
console.log(import_chalk.default.green(`\u2713 Using ${dataSource}
|
|
405
521
|
`));
|
|
522
|
+
}
|
|
406
523
|
console.log(import_chalk.default.bold("Summary:"));
|
|
407
524
|
console.log(import_chalk.default.gray(` Total Cost: ${import_chalk.default.green("$" + formatNumber(Math.round(usageData.totalCost)))}`));
|
|
408
525
|
console.log(import_chalk.default.gray(` Total Tokens: ${import_chalk.default.cyan(formatNumber(usageData.totalTokens))}`));
|
|
@@ -557,14 +674,14 @@ async function status(options) {
|
|
|
557
674
|
var import_chalk4 = __toESM(require("chalk"));
|
|
558
675
|
var import_ora4 = __toESM(require("ora"));
|
|
559
676
|
var http = __toESM(require("http"));
|
|
560
|
-
var
|
|
561
|
-
var
|
|
562
|
-
var
|
|
677
|
+
var fs4 = __toESM(require("fs"));
|
|
678
|
+
var path4 = __toESM(require("path"));
|
|
679
|
+
var os4 = __toESM(require("os"));
|
|
563
680
|
init_config();
|
|
564
681
|
init_config();
|
|
565
682
|
var CALLBACK_PORT = 9876;
|
|
566
683
|
function getClaudeSettingsDir2() {
|
|
567
|
-
return
|
|
684
|
+
return path4.join(os4.homedir(), ".claude");
|
|
568
685
|
}
|
|
569
686
|
async function openBrowser(url) {
|
|
570
687
|
const { default: open } = await import("open");
|
|
@@ -794,14 +911,14 @@ if (usageData) {
|
|
|
794
911
|
}
|
|
795
912
|
function installStopHook() {
|
|
796
913
|
const claudeDir = getClaudeSettingsDir2();
|
|
797
|
-
const settingsPath =
|
|
798
|
-
if (!
|
|
799
|
-
|
|
914
|
+
const settingsPath = path4.join(claudeDir, "settings.json");
|
|
915
|
+
if (!fs4.existsSync(claudeDir)) {
|
|
916
|
+
fs4.mkdirSync(claudeDir, { recursive: true });
|
|
800
917
|
}
|
|
801
918
|
let settings = {};
|
|
802
919
|
try {
|
|
803
|
-
if (
|
|
804
|
-
const content =
|
|
920
|
+
if (fs4.existsSync(settingsPath)) {
|
|
921
|
+
const content = fs4.readFileSync(settingsPath, "utf-8");
|
|
805
922
|
settings = JSON.parse(content);
|
|
806
923
|
}
|
|
807
924
|
} catch {
|
|
@@ -810,7 +927,7 @@ function installStopHook() {
|
|
|
810
927
|
settings.hooks = {};
|
|
811
928
|
}
|
|
812
929
|
const hooks = settings.hooks;
|
|
813
|
-
const syncScriptPath =
|
|
930
|
+
const syncScriptPath = path4.join(claudeDir, "ccgather-sync.js");
|
|
814
931
|
const hookCommand = `node "${syncScriptPath}"`;
|
|
815
932
|
if (!hooks.Stop || !Array.isArray(hooks.Stop)) {
|
|
816
933
|
hooks.Stop = [];
|
|
@@ -824,16 +941,16 @@ function installStopHook() {
|
|
|
824
941
|
background: true
|
|
825
942
|
});
|
|
826
943
|
}
|
|
827
|
-
|
|
944
|
+
fs4.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
828
945
|
return { success: true, message: "Stop hook installed" };
|
|
829
946
|
}
|
|
830
947
|
function saveSyncScript(apiUrl, apiToken) {
|
|
831
948
|
const claudeDir = getClaudeSettingsDir2();
|
|
832
|
-
const scriptPath =
|
|
949
|
+
const scriptPath = path4.join(claudeDir, "ccgather-sync.js");
|
|
833
950
|
const scriptContent = generateSyncScript(apiUrl, apiToken);
|
|
834
|
-
|
|
835
|
-
if (
|
|
836
|
-
|
|
951
|
+
fs4.writeFileSync(scriptPath, scriptContent);
|
|
952
|
+
if (os4.platform() !== "win32") {
|
|
953
|
+
fs4.chmodSync(scriptPath, "755");
|
|
837
954
|
}
|
|
838
955
|
}
|
|
839
956
|
async function setupAuto(options = {}) {
|
|
@@ -841,14 +958,14 @@ async function setupAuto(options = {}) {
|
|
|
841
958
|
console.log(import_chalk4.default.bold("\n\u{1F527} Disabling Auto-Sync\n"));
|
|
842
959
|
const { reset: reset2 } = await Promise.resolve().then(() => (init_reset(), reset_exports));
|
|
843
960
|
await reset2();
|
|
844
|
-
console.log(import_chalk4.default.green("\u2713 Auto-sync disabled. Use `npx
|
|
961
|
+
console.log(import_chalk4.default.green("\u2713 Auto-sync disabled. Use `npx ccgather` to submit manually."));
|
|
845
962
|
return;
|
|
846
963
|
}
|
|
847
964
|
console.log(import_chalk4.default.bold("\n\u26A0\uFE0F Auto-Sync Mode (Optional)\n"));
|
|
848
965
|
console.log(import_chalk4.default.gray("This will install a hook that automatically syncs"));
|
|
849
966
|
console.log(import_chalk4.default.gray("your usage data when Claude Code sessions end."));
|
|
850
967
|
console.log();
|
|
851
|
-
console.log(import_chalk4.default.yellow("Note: Manual submission (`npx
|
|
968
|
+
console.log(import_chalk4.default.yellow("Note: Manual submission (`npx ccgather`) is recommended for most users."));
|
|
852
969
|
console.log();
|
|
853
970
|
const inquirer2 = await import("inquirer");
|
|
854
971
|
const { proceed } = await inquirer2.default.prompt([
|
|
@@ -860,7 +977,7 @@ async function setupAuto(options = {}) {
|
|
|
860
977
|
}
|
|
861
978
|
]);
|
|
862
979
|
if (!proceed) {
|
|
863
|
-
console.log(import_chalk4.default.gray("\nSetup cancelled. Use `npx
|
|
980
|
+
console.log(import_chalk4.default.gray("\nSetup cancelled. Use `npx ccgather` to submit manually."));
|
|
864
981
|
return;
|
|
865
982
|
}
|
|
866
983
|
const config = getConfig();
|
|
@@ -924,7 +1041,7 @@ async function setupAuto(options = {}) {
|
|
|
924
1041
|
console.log(import_chalk4.default.gray("to the leaderboard when each session ends."));
|
|
925
1042
|
console.log();
|
|
926
1043
|
console.log(import_chalk4.default.gray("View your stats:"));
|
|
927
|
-
console.log(import_chalk4.default.cyan(" npx
|
|
1044
|
+
console.log(import_chalk4.default.cyan(" npx ccgather status"));
|
|
928
1045
|
console.log();
|
|
929
1046
|
console.log(import_chalk4.default.gray("View the leaderboard:"));
|
|
930
1047
|
console.log(import_chalk4.default.cyan(" https://ccgather.dev/leaderboard"));
|
|
@@ -936,9 +1053,67 @@ async function setupAuto(options = {}) {
|
|
|
936
1053
|
}
|
|
937
1054
|
}
|
|
938
1055
|
|
|
1056
|
+
// src/commands/scan.ts
|
|
1057
|
+
var import_chalk5 = __toESM(require("chalk"));
|
|
1058
|
+
var import_ora5 = __toESM(require("ora"));
|
|
1059
|
+
function formatNumber3(num) {
|
|
1060
|
+
if (num >= 1e6) return `${(num / 1e6).toFixed(2)}M`;
|
|
1061
|
+
if (num >= 1e3) return `${(num / 1e3).toFixed(2)}K`;
|
|
1062
|
+
return num.toLocaleString();
|
|
1063
|
+
}
|
|
1064
|
+
function displayResults(data) {
|
|
1065
|
+
const orange = import_chalk5.default.hex("#FF6B35");
|
|
1066
|
+
const green = import_chalk5.default.hex("#10B981");
|
|
1067
|
+
const gray = import_chalk5.default.gray;
|
|
1068
|
+
const white = import_chalk5.default.white;
|
|
1069
|
+
const bold = import_chalk5.default.bold;
|
|
1070
|
+
console.log();
|
|
1071
|
+
console.log(gray(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
|
|
1072
|
+
console.log(gray(" \u2502") + white(" \u{1F4CA} Usage Summary") + gray(" \u2502"));
|
|
1073
|
+
console.log(gray(" \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524"));
|
|
1074
|
+
console.log(gray(" \u2502") + ` Total Tokens: ${orange(formatNumber3(data.usage.totalTokens).padEnd(15))}` + gray(" \u2502"));
|
|
1075
|
+
console.log(gray(" \u2502") + ` Total Cost: ${green("$" + data.usage.totalCost.toFixed(2).padEnd(14))}` + gray(" \u2502"));
|
|
1076
|
+
console.log(gray(" \u2502") + ` Input Tokens: ${white(formatNumber3(data.usage.inputTokens).padEnd(15))}` + gray(" \u2502"));
|
|
1077
|
+
console.log(gray(" \u2502") + ` Output Tokens: ${white(formatNumber3(data.usage.outputTokens).padEnd(15))}` + gray(" \u2502"));
|
|
1078
|
+
console.log(gray(" \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524"));
|
|
1079
|
+
console.log(gray(" \u2502") + white(" \u{1F4C8} Stats") + gray(" \u2502"));
|
|
1080
|
+
console.log(gray(" \u2502") + ` Days Tracked: ${white(data.stats.daysTracked.toString().padEnd(15))}` + gray(" \u2502"));
|
|
1081
|
+
console.log(gray(" \u2502") + ` Sessions: ${white(data.stats.sessionsCount.toString().padEnd(15))}` + gray(" \u2502"));
|
|
1082
|
+
console.log(gray(" \u2502") + ` First Used: ${gray((data.stats.firstUsed || "N/A").padEnd(15))}` + gray(" \u2502"));
|
|
1083
|
+
console.log(gray(" \u2502") + ` Last Used: ${gray((data.stats.lastUsed || "N/A").padEnd(15))}` + gray(" \u2502"));
|
|
1084
|
+
console.log(gray(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
|
|
1085
|
+
if (Object.keys(data.models).length > 0) {
|
|
1086
|
+
console.log();
|
|
1087
|
+
console.log(gray(" ") + bold("Model Breakdown:"));
|
|
1088
|
+
for (const [model, tokens] of Object.entries(data.models)) {
|
|
1089
|
+
const shortModel = model.replace("claude-", "").substring(0, 20);
|
|
1090
|
+
console.log(gray(" \u2022 ") + white(shortModel.padEnd(22)) + orange(formatNumber3(tokens)));
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
console.log();
|
|
1094
|
+
console.log(gray(` \u{1F4C1} Saved to: ${getCCGatherJsonPath()}`));
|
|
1095
|
+
console.log();
|
|
1096
|
+
}
|
|
1097
|
+
async function scan() {
|
|
1098
|
+
console.log(import_chalk5.default.bold("\n\u{1F50D} Scanning Claude Code Usage\n"));
|
|
1099
|
+
const spinner = (0, import_ora5.default)("Scanning JSONL files...").start();
|
|
1100
|
+
const data = scanAndSave();
|
|
1101
|
+
if (!data) {
|
|
1102
|
+
spinner.fail(import_chalk5.default.red("No usage data found"));
|
|
1103
|
+
console.log(import_chalk5.default.gray("\nPossible reasons:"));
|
|
1104
|
+
console.log(import_chalk5.default.gray(" \u2022 Claude Code has not been used yet"));
|
|
1105
|
+
console.log(import_chalk5.default.gray(" \u2022 ~/.claude/projects/ directory is empty"));
|
|
1106
|
+
console.log(import_chalk5.default.gray(" \u2022 No permission to read files\n"));
|
|
1107
|
+
process.exit(1);
|
|
1108
|
+
}
|
|
1109
|
+
spinner.succeed(import_chalk5.default.green("Scan complete!"));
|
|
1110
|
+
displayResults(data);
|
|
1111
|
+
}
|
|
1112
|
+
|
|
939
1113
|
// src/index.ts
|
|
940
1114
|
var program = new import_commander.Command();
|
|
941
|
-
program.name("
|
|
1115
|
+
program.name("ccgather").description("Submit your Claude Code usage to the CCgather leaderboard").version("1.0.0").option("-y, --yes", "Skip confirmation prompt").option("--auto", "Enable automatic sync on session end").option("--manual", "Disable automatic sync");
|
|
1116
|
+
program.command("scan").description("Scan Claude Code usage and create ccgather.json").action(scan);
|
|
942
1117
|
program.command("rank").description("View your current rank and stats").action(status);
|
|
943
1118
|
program.command("reset").description("Remove auto-sync hook and clear config").action(async () => {
|
|
944
1119
|
const { reset: reset2 } = await Promise.resolve().then(() => (init_reset(), reset_exports));
|
package/package.json
CHANGED
|
@@ -1,51 +1,51 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "ccgather",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "CLI tool for syncing Claude Code usage data to CCgather leaderboard",
|
|
5
|
-
"bin": {
|
|
6
|
-
"ccgather": "./dist/index.js",
|
|
7
|
-
"ccg": "./dist/index.js"
|
|
8
|
-
},
|
|
9
|
-
"main": "./dist/index.js",
|
|
10
|
-
"types": "./dist/index.d.ts",
|
|
11
|
-
"scripts": {
|
|
12
|
-
"build": "tsup src/index.ts --format cjs --dts",
|
|
13
|
-
"dev": "tsup src/index.ts --format cjs --dts --watch",
|
|
14
|
-
"start": "node dist/index.js",
|
|
15
|
-
"typecheck": "tsc --noEmit"
|
|
16
|
-
},
|
|
17
|
-
"keywords": [
|
|
18
|
-
"claude",
|
|
19
|
-
"anthropic",
|
|
20
|
-
"claude-code",
|
|
21
|
-
"ccgather",
|
|
22
|
-
"leaderboard",
|
|
23
|
-
"cli"
|
|
24
|
-
],
|
|
25
|
-
"author": "",
|
|
26
|
-
"license": "MIT",
|
|
27
|
-
"dependencies": {
|
|
28
|
-
"chalk": "^5.3.0",
|
|
29
|
-
"commander": "^12.1.0",
|
|
30
|
-
"conf": "^13.0.1",
|
|
31
|
-
"inquirer": "^10.2.2",
|
|
32
|
-
"open": "^10.1.0",
|
|
33
|
-
"ora": "^8.1.0"
|
|
34
|
-
},
|
|
35
|
-
"devDependencies": {
|
|
36
|
-
"@types/inquirer": "^9.0.7",
|
|
37
|
-
"@types/node": "^22.10.2",
|
|
38
|
-
"tsup": "^8.3.5",
|
|
39
|
-
"typescript": "^5.7.2"
|
|
40
|
-
},
|
|
41
|
-
"engines": {
|
|
42
|
-
"node": ">=18"
|
|
43
|
-
},
|
|
44
|
-
"files": [
|
|
45
|
-
"dist"
|
|
46
|
-
],
|
|
47
|
-
"repository": {
|
|
48
|
-
"type": "git",
|
|
49
|
-
"url": "https://github.com/
|
|
50
|
-
}
|
|
51
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "ccgather",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "CLI tool for syncing Claude Code usage data to CCgather leaderboard",
|
|
5
|
+
"bin": {
|
|
6
|
+
"ccgather": "./dist/index.js",
|
|
7
|
+
"ccg": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsup src/index.ts --format cjs --dts",
|
|
13
|
+
"dev": "tsup src/index.ts --format cjs --dts --watch",
|
|
14
|
+
"start": "node dist/index.js",
|
|
15
|
+
"typecheck": "tsc --noEmit"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"claude",
|
|
19
|
+
"anthropic",
|
|
20
|
+
"claude-code",
|
|
21
|
+
"ccgather",
|
|
22
|
+
"leaderboard",
|
|
23
|
+
"cli"
|
|
24
|
+
],
|
|
25
|
+
"author": "",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"chalk": "^5.3.0",
|
|
29
|
+
"commander": "^12.1.0",
|
|
30
|
+
"conf": "^13.0.1",
|
|
31
|
+
"inquirer": "^10.2.2",
|
|
32
|
+
"open": "^10.1.0",
|
|
33
|
+
"ora": "^8.1.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/inquirer": "^9.0.7",
|
|
37
|
+
"@types/node": "^22.10.2",
|
|
38
|
+
"tsup": "^8.3.5",
|
|
39
|
+
"typescript": "^5.7.2"
|
|
40
|
+
},
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=18"
|
|
43
|
+
},
|
|
44
|
+
"files": [
|
|
45
|
+
"dist"
|
|
46
|
+
],
|
|
47
|
+
"repository": {
|
|
48
|
+
"type": "git",
|
|
49
|
+
"url": "https://github.com/DHxYoon/CCgather"
|
|
50
|
+
}
|
|
51
|
+
}
|