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.
Files changed (2) hide show
  1. package/dist/index.js +286 -111
  2. 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 path2.join(os2.homedir(), ".claude");
79
+ return path3.join(os3.homedir(), ".claude");
80
80
  }
81
81
  function removeStopHook() {
82
82
  const claudeDir = getClaudeSettingsDir();
83
- const settingsPath = path2.join(claudeDir, "settings.json");
84
- if (!fs2.existsSync(settingsPath)) {
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 = fs2.readFileSync(settingsPath, "utf-8");
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
- fs2.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
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 = path2.join(claudeDir, "ccgather-sync.js");
117
- if (fs2.existsSync(scriptPath)) {
118
- fs2.unlinkSync(scriptPath);
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 ccg` to set up again."));
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, fs2, path2, os2, import_inquirer;
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
- fs2 = __toESM(require("fs"));
183
- path2 = __toESM(require("path"));
184
- os2 = __toESM(require("os"));
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
- init_config();
200
- function findCcJson() {
201
- const possiblePaths = [
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 parseCcJson(filePath) {
209
+ function getClaudeProjectsDir() {
210
+ return path.join(os.homedir(), ".claude", "projects");
211
+ }
212
+ function findJsonlFiles(dir) {
213
+ const files = [];
214
214
  try {
215
- const content = fs.readFileSync(filePath, "utf-8");
216
- const data = JSON.parse(content);
217
- return {
218
- totalTokens: data.totalTokens || data.total_tokens || 0,
219
- totalCost: data.totalCost || data.total_cost || data.costUSD || 0,
220
- inputTokens: data.inputTokens || data.input_tokens || 0,
221
- outputTokens: data.outputTokens || data.output_tokens || 0,
222
- cacheReadTokens: data.cacheReadTokens || data.cache_read_tokens || 0,
223
- cacheWriteTokens: data.cacheWriteTokens || data.cache_write_tokens || 0,
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 calculateDaysTracked(data) {
231
- if (data.dailyStats && Array.isArray(data.dailyStats)) {
232
- return data.dailyStats.length;
233
- }
234
- if (data.daily && typeof data.daily === "object") {
235
- return Object.keys(data.daily).length;
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
- return 1;
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 parseUsageFromJsonl() {
240
- const projectsDir = path.join(os.homedir(), ".claude", "projects");
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
- function findJsonlFiles(dir) {
250
- const files = [];
251
- try {
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
- totalInputTokens += usage.input_tokens || 0;
276
- totalOutputTokens += usage.output_tokens || 0;
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
- totalTokens,
298
- totalCost: Math.round(totalCost * 100) / 100,
299
- inputTokens: totalInputTokens,
300
- outputTokens: totalOutputTokens,
301
- cacheReadTokens: totalCacheRead,
302
- cacheWriteTokens: totalCacheWrite,
303
- daysTracked: dates.size || 1
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
- if (ccJsonPath) {
380
- const { useCcJson } = await inquirer2.default.prompt([
381
- {
382
- type: "confirm",
383
- name: "useCcJson",
384
- message: `Found existing cc.json. Use this file?`,
385
- default: true
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
- usageData = parseUsageFromJsonl();
503
+ const scannedData = scanAndSave();
396
504
  parseSpinner.stop();
397
- dataSource = "Claude Code logs";
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 or have a cc.json file."));
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
- console.log(import_chalk.default.green(`\u2713 Using ${dataSource}
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 fs3 = __toESM(require("fs"));
561
- var path3 = __toESM(require("path"));
562
- var os3 = __toESM(require("os"));
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 path3.join(os3.homedir(), ".claude");
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 = path3.join(claudeDir, "settings.json");
798
- if (!fs3.existsSync(claudeDir)) {
799
- fs3.mkdirSync(claudeDir, { recursive: true });
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 (fs3.existsSync(settingsPath)) {
804
- const content = fs3.readFileSync(settingsPath, "utf-8");
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 = path3.join(claudeDir, "ccgather-sync.js");
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
- fs3.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
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 = path3.join(claudeDir, "ccgather-sync.js");
949
+ const scriptPath = path4.join(claudeDir, "ccgather-sync.js");
833
950
  const scriptContent = generateSyncScript(apiUrl, apiToken);
834
- fs3.writeFileSync(scriptPath, scriptContent);
835
- if (os3.platform() !== "win32") {
836
- fs3.chmodSync(scriptPath, "755");
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 ccg` to submit manually."));
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 ccg`) is recommended for most users."));
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 ccg` to submit manually."));
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 ccg status"));
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("ccg").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");
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.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/your-username/ccgather"
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
+ }