ccgather 2.0.32 → 2.0.34

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 +250 -54
  2. package/package.json +55 -55
package/dist/index.js CHANGED
@@ -253,7 +253,7 @@ var init_ui = __esm({
253
253
  "use strict";
254
254
  import_chalk = __toESM(require("chalk"));
255
255
  import_string_width = __toESM(require("string-width"));
256
- VERSION = true ? "2.0.32" : "0.0.0";
256
+ VERSION = true ? "2.0.33" : "0.0.0";
257
257
  colors = {
258
258
  primary: import_chalk.default.hex("#DA7756"),
259
259
  // Claude coral
@@ -489,19 +489,45 @@ var import_ora2 = __toESM(require("ora"));
489
489
  var import_inquirer2 = __toESM(require("inquirer"));
490
490
  init_config();
491
491
 
492
- // src/lib/ccgather-json.ts
493
- var fs2 = __toESM(require("fs"));
494
- var path2 = __toESM(require("path"));
495
- var os2 = __toESM(require("os"));
492
+ // src/lib/device.ts
493
+ var os = __toESM(require("os"));
496
494
  var crypto = __toESM(require("crypto"));
495
+ init_config();
496
+ var DEVICE_ID_LENGTH = 16;
497
+ function getDeviceId() {
498
+ try {
499
+ const config = getConfig();
500
+ const existing = config.get("deviceId");
501
+ if (existing) {
502
+ return existing;
503
+ }
504
+ const salt = crypto.randomBytes(8).toString("hex");
505
+ const raw = `${os.hostname() || "unknown"}:${os.homedir()}:${os.platform()}:${os.arch()}:${salt}`;
506
+ const deviceId = crypto.createHash("sha256").update(raw).digest("hex").slice(0, DEVICE_ID_LENGTH);
507
+ try {
508
+ config.set("deviceId", deviceId);
509
+ } catch {
510
+ }
511
+ return deviceId;
512
+ } catch {
513
+ const raw = `${os.hostname() || "unknown"}:${os.homedir()}:${os.platform()}:${os.arch()}`;
514
+ return crypto.createHash("sha256").update(raw).digest("hex").slice(0, DEVICE_ID_LENGTH);
515
+ }
516
+ }
517
+
518
+ // src/lib/ccgather-json.ts
519
+ var fs3 = __toESM(require("fs"));
520
+ var path3 = __toESM(require("path"));
521
+ var os4 = __toESM(require("os"));
522
+ var crypto2 = __toESM(require("crypto"));
497
523
 
498
524
  // src/lib/credentials.ts
499
525
  var fs = __toESM(require("fs"));
500
526
  var path = __toESM(require("path"));
501
- var os = __toESM(require("os"));
527
+ var os2 = __toESM(require("os"));
502
528
  var import_child_process = require("child_process");
503
529
  function getCredentialsPath() {
504
- return path.join(os.homedir(), ".claude", ".credentials.json");
530
+ return path.join(os2.homedir(), ".claude", ".credentials.json");
505
531
  }
506
532
  function readFromMacKeychain() {
507
533
  try {
@@ -616,6 +642,154 @@ function readCredentials() {
616
642
  };
617
643
  }
618
644
 
645
+ // src/lib/pricing.ts
646
+ var fs2 = __toESM(require("fs"));
647
+ var path2 = __toESM(require("path"));
648
+ var os3 = __toESM(require("os"));
649
+ var LITELLM_PRICING_URL = "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json";
650
+ var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
651
+ var FETCH_TIMEOUT_MS = 5e3;
652
+ var FALLBACK_PRICING = {
653
+ "opus-4": { input: 5, output: 25, cacheWrite: 6.25, cacheRead: 0.5 },
654
+ "sonnet-4": { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.3 },
655
+ haiku: { input: 1, output: 5, cacheWrite: 1.25, cacheRead: 0.1 },
656
+ default: { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.3 }
657
+ };
658
+ var pricingData = null;
659
+ function getCacheFilePath() {
660
+ const configDir = process.platform === "win32" ? path2.join(
661
+ process.env.APPDATA || path2.join(os3.homedir(), "AppData", "Roaming"),
662
+ "ccgather-nodejs"
663
+ ) : path2.join(os3.homedir(), ".config", "ccgather-nodejs");
664
+ return path2.join(configDir, "pricing-cache.json");
665
+ }
666
+ function loadCache() {
667
+ try {
668
+ const cachePath = getCacheFilePath();
669
+ if (!fs2.existsSync(cachePath)) return null;
670
+ const raw = fs2.readFileSync(cachePath, "utf-8");
671
+ const cache = JSON.parse(raw);
672
+ if (Date.now() - cache.fetchedAt > CACHE_TTL_MS) return null;
673
+ if (!cache.models || Object.keys(cache.models).length === 0) return null;
674
+ return cache;
675
+ } catch {
676
+ return null;
677
+ }
678
+ }
679
+ function saveCache(models) {
680
+ try {
681
+ const cachePath = getCacheFilePath();
682
+ const cacheDir = path2.dirname(cachePath);
683
+ if (!fs2.existsSync(cacheDir)) {
684
+ fs2.mkdirSync(cacheDir, { recursive: true });
685
+ }
686
+ const cache = {
687
+ fetchedAt: Date.now(),
688
+ models
689
+ };
690
+ fs2.writeFileSync(cachePath, JSON.stringify(cache), "utf-8");
691
+ } catch {
692
+ }
693
+ }
694
+ function extractClaudePricing(rawData) {
695
+ const result = {};
696
+ for (const [key, value] of Object.entries(rawData)) {
697
+ if (!key.startsWith("claude-")) continue;
698
+ const inputCostPerToken = value.input_cost_per_token;
699
+ const outputCostPerToken = value.output_cost_per_token;
700
+ if (inputCostPerToken == null || outputCostPerToken == null) continue;
701
+ const input = inputCostPerToken * 1e6;
702
+ const output = outputCostPerToken * 1e6;
703
+ const cacheWritePerToken = value.cache_creation_input_token_cost;
704
+ const cacheReadPerToken = value.cache_read_input_token_cost;
705
+ const cacheWrite = cacheWritePerToken != null ? cacheWritePerToken * 1e6 : input * 1.25;
706
+ const cacheRead = cacheReadPerToken != null ? cacheReadPerToken * 1e6 : input * 0.1;
707
+ const pricing = {
708
+ input: Math.round(input * 1e3) / 1e3,
709
+ output: Math.round(output * 1e3) / 1e3,
710
+ cacheWrite: Math.round(cacheWrite * 1e3) / 1e3,
711
+ cacheRead: Math.round(cacheRead * 1e3) / 1e3
712
+ };
713
+ result[key] = pricing;
714
+ const withoutDate = key.replace(/-\d{8}$/, "");
715
+ if (withoutDate !== key && !result[withoutDate]) {
716
+ result[withoutDate] = pricing;
717
+ }
718
+ const withoutVersion = key.replace(/-v\d+:\d+$/, "").replace(/-\d{8}$/, "");
719
+ if (withoutVersion !== key && withoutVersion !== withoutDate && !result[withoutVersion]) {
720
+ result[withoutVersion] = pricing;
721
+ }
722
+ }
723
+ return result;
724
+ }
725
+ async function initPricing() {
726
+ if (pricingData) return;
727
+ const cached = loadCache();
728
+ if (cached) {
729
+ pricingData = cached.models;
730
+ return;
731
+ }
732
+ try {
733
+ const controller = new AbortController();
734
+ const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
735
+ const response = await fetch(LITELLM_PRICING_URL, {
736
+ signal: controller.signal
737
+ });
738
+ clearTimeout(timeout);
739
+ if (response.ok) {
740
+ const rawData = await response.json();
741
+ const extracted = extractClaudePricing(rawData);
742
+ if (Object.keys(extracted).length > 0) {
743
+ pricingData = extracted;
744
+ saveCache(extracted);
745
+ return;
746
+ }
747
+ }
748
+ } catch {
749
+ }
750
+ pricingData = null;
751
+ }
752
+ function matchModel(model) {
753
+ if (pricingData) {
754
+ if (pricingData[model]) {
755
+ return pricingData[model];
756
+ }
757
+ const withoutDate = model.replace(/-\d{8}$/, "");
758
+ if (pricingData[withoutDate]) {
759
+ return pricingData[withoutDate];
760
+ }
761
+ const withoutVersion = model.replace(/-v\d+:\d+$/, "").replace(/-\d{8}$/, "");
762
+ if (pricingData[withoutVersion]) {
763
+ return pricingData[withoutVersion];
764
+ }
765
+ const modelLower2 = model.toLowerCase();
766
+ let bestMatch = null;
767
+ for (const [key, pricing] of Object.entries(pricingData)) {
768
+ if (modelLower2.startsWith(key.toLowerCase()) || key.toLowerCase().startsWith(modelLower2.replace(/-\d{8}$/, ""))) {
769
+ if (!bestMatch || key.length > bestMatch.key.length) {
770
+ bestMatch = { key, pricing };
771
+ }
772
+ }
773
+ }
774
+ if (bestMatch) {
775
+ return bestMatch.pricing;
776
+ }
777
+ }
778
+ const modelLower = model.toLowerCase();
779
+ if (modelLower.includes("opus")) return FALLBACK_PRICING["opus-4"];
780
+ if (modelLower.includes("haiku")) return FALLBACK_PRICING["haiku"];
781
+ if (modelLower.includes("sonnet")) return FALLBACK_PRICING["sonnet-4"];
782
+ return FALLBACK_PRICING["default"];
783
+ }
784
+ function estimateCost(model, inputTokens, outputTokens, cacheWriteTokens = 0, cacheReadTokens = 0) {
785
+ const price = matchModel(model);
786
+ const inputCost = inputTokens / 1e6 * price.input;
787
+ const outputCost = outputTokens / 1e6 * price.output;
788
+ const cacheWriteCost = cacheWriteTokens / 1e6 * price.cacheWrite;
789
+ const cacheReadCost = cacheReadTokens / 1e6 * price.cacheRead;
790
+ return Math.round((inputCost + outputCost + cacheWriteCost + cacheReadCost) * 100) / 100;
791
+ }
792
+
619
793
  // src/lib/ccgather-json.ts
620
794
  function hasOpusUsageInProject(dailyUsage) {
621
795
  const opusModels = /* @__PURE__ */ new Set();
@@ -652,26 +826,26 @@ function extractProjectName(filePath) {
652
826
  }
653
827
  function getClaudeProjectsDirs() {
654
828
  const dirs = [];
655
- const home = os2.homedir();
829
+ const home = os4.homedir();
656
830
  const configDir = process.env.CLAUDE_CONFIG_DIR;
657
831
  if (configDir) {
658
- const envPath = path2.join(configDir, "projects");
832
+ const envPath = path3.join(configDir, "projects");
659
833
  dirs.push(envPath);
660
834
  }
661
835
  if (process.platform === "win32") {
662
836
  const appData = process.env.APPDATA;
663
837
  if (appData) {
664
- const appDataPath = path2.join(appData, "claude", "projects");
838
+ const appDataPath = path3.join(appData, "claude", "projects");
665
839
  dirs.push(appDataPath);
666
840
  }
667
841
  }
668
- const xdgPath = path2.join(home, ".config", "claude", "projects");
842
+ const xdgPath = path3.join(home, ".config", "claude", "projects");
669
843
  dirs.push(xdgPath);
670
- const legacyPath = path2.join(home, ".claude", "projects");
844
+ const legacyPath = path3.join(home, ".claude", "projects");
671
845
  dirs.push(legacyPath);
672
846
  const uniqueDirs = [...new Set(dirs)];
673
847
  const existingDirs = uniqueDirs.filter((dir) => {
674
- return fs2.existsSync(dir);
848
+ return fs3.existsSync(dir);
675
849
  });
676
850
  return existingDirs;
677
851
  }
@@ -690,9 +864,9 @@ function encodePathLikeClaude(inputPath) {
690
864
  function findJsonlFiles(dir) {
691
865
  const files = [];
692
866
  try {
693
- const entries = fs2.readdirSync(dir, { withFileTypes: true });
867
+ const entries = fs3.readdirSync(dir, { withFileTypes: true });
694
868
  for (const entry of entries) {
695
- const fullPath = path2.join(dir, entry.name);
869
+ const fullPath = path3.join(dir, entry.name);
696
870
  if (entry.isDirectory()) {
697
871
  files.push(...findJsonlFiles(fullPath));
698
872
  } else if (entry.name.endsWith(".jsonl")) {
@@ -703,34 +877,13 @@ function findJsonlFiles(dir) {
703
877
  }
704
878
  return files;
705
879
  }
706
- function estimateCost(model, inputTokens, outputTokens, cacheWriteTokens = 0, cacheReadTokens = 0) {
707
- const pricing = {
708
- "claude-opus-4": { input: 15, output: 75, cacheWrite: 18.75, cacheRead: 1.5 },
709
- "claude-sonnet-4": { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.3 },
710
- "claude-haiku": { input: 0.25, output: 1.25, cacheWrite: 0.3125, cacheRead: 0.025 },
711
- default: { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.3 }
712
- };
713
- let modelKey = "default";
714
- for (const key of Object.keys(pricing)) {
715
- if (model.includes(key.replace("claude-", ""))) {
716
- modelKey = key;
717
- break;
718
- }
719
- }
720
- const price = pricing[modelKey];
721
- const inputCost = inputTokens / 1e6 * price.input;
722
- const outputCost = outputTokens / 1e6 * price.output;
723
- const cacheWriteCost = cacheWriteTokens / 1e6 * price.cacheWrite;
724
- const cacheReadCost = cacheReadTokens / 1e6 * price.cacheRead;
725
- return Math.round((inputCost + outputCost + cacheWriteCost + cacheReadCost) * 100) / 100;
726
- }
727
880
  function generateSessionHash(filePath, maxLines = 50) {
728
881
  try {
729
- const content = fs2.readFileSync(filePath, "utf-8");
882
+ const content = fs3.readFileSync(filePath, "utf-8");
730
883
  const lines = content.split("\n").slice(0, maxLines).join("\n");
731
- const fileName = path2.basename(filePath);
884
+ const fileName = path3.basename(filePath);
732
885
  const hashInput = `${fileName}:${lines}`;
733
- return crypto.createHash("sha256").update(hashInput).digest("hex");
886
+ return crypto2.createHash("sha256").update(hashInput).digest("hex");
734
887
  } catch {
735
888
  return null;
736
889
  }
@@ -744,7 +897,7 @@ function generateSessionFingerprint(sessionFiles) {
744
897
  }
745
898
  }
746
899
  sessionHashes.sort();
747
- const combinedHash = crypto.createHash("sha256").update(sessionHashes.join(":")).digest("hex");
900
+ const combinedHash = crypto2.createHash("sha256").update(sessionHashes.join(":")).digest("hex");
748
901
  return {
749
902
  sessionHashes,
750
903
  combinedHash,
@@ -752,30 +905,30 @@ function generateSessionFingerprint(sessionFiles) {
752
905
  };
753
906
  }
754
907
  function getSessionPathDebugInfo() {
755
- const home = os2.homedir();
908
+ const home = os4.homedir();
756
909
  const cwd = process.cwd();
757
910
  const encodedCwd = encodePathLikeClaude(cwd);
758
911
  const pathsToCheck = [
759
- path2.join(home, ".config", "claude", "projects"),
760
- path2.join(home, ".claude", "projects")
912
+ path3.join(home, ".config", "claude", "projects"),
913
+ path3.join(home, ".claude", "projects")
761
914
  ];
762
915
  const configDir = process.env.CLAUDE_CONFIG_DIR;
763
916
  if (configDir) {
764
- pathsToCheck.unshift(path2.join(configDir, "projects"));
917
+ pathsToCheck.unshift(path3.join(configDir, "projects"));
765
918
  }
766
919
  if (process.platform === "win32" && process.env.APPDATA) {
767
- pathsToCheck.unshift(path2.join(process.env.APPDATA, "claude", "projects"));
920
+ pathsToCheck.unshift(path3.join(process.env.APPDATA, "claude", "projects"));
768
921
  }
769
922
  const searchedPaths = pathsToCheck.map((p) => {
770
- const exists = fs2.existsSync(p);
923
+ const exists = fs3.existsSync(p);
771
924
  let matchingDirs;
772
925
  if (exists) {
773
926
  try {
774
- const entries = fs2.readdirSync(p, { withFileTypes: true });
927
+ const entries = fs3.readdirSync(p, { withFileTypes: true });
775
928
  matchingDirs = entries.filter((e) => e.isDirectory()).map((e) => e.name).filter((name) => {
776
929
  const lowerName = name.toLowerCase();
777
930
  const lowerEncoded = encodedCwd.toLowerCase();
778
- return lowerName.includes(path2.basename(cwd).toLowerCase()) || lowerEncoded.includes(lowerName) || lowerName.includes(lowerEncoded.slice(-20));
931
+ return lowerName.includes(path3.basename(cwd).toLowerCase()) || lowerEncoded.includes(lowerName) || lowerName.includes(lowerEncoded.slice(-20));
779
932
  }).slice(0, 5);
780
933
  } catch {
781
934
  matchingDirs = void 0;
@@ -819,11 +972,11 @@ function scanAllProjects(options = {}) {
819
972
  const allJsonlFiles = [];
820
973
  for (const projectsDir of projectsDirs) {
821
974
  try {
822
- const entries = fs2.readdirSync(projectsDir, { withFileTypes: true });
975
+ const entries = fs3.readdirSync(projectsDir, { withFileTypes: true });
823
976
  for (const entry of entries) {
824
977
  if (!entry.isDirectory()) continue;
825
978
  if (entry.name.startsWith(".")) continue;
826
- const projectPath = path2.join(projectsDir, entry.name);
979
+ const projectPath = path3.join(projectsDir, entry.name);
827
980
  const jsonlFiles = findJsonlFiles(projectPath);
828
981
  allJsonlFiles.push(...jsonlFiles);
829
982
  }
@@ -849,7 +1002,7 @@ function scanAllProjects(options = {}) {
849
1002
  }
850
1003
  projects[projectName].sessions++;
851
1004
  try {
852
- const content = fs2.readFileSync(filePath, "utf-8");
1005
+ const content = fs3.readFileSync(filePath, "utf-8");
853
1006
  const lines = content.split("\n").filter((line) => line.trim());
854
1007
  for (const line of lines) {
855
1008
  try {
@@ -976,11 +1129,11 @@ function getAllSessionsCount() {
976
1129
  let count = 0;
977
1130
  for (const projectsDir of projectsDirs) {
978
1131
  try {
979
- const entries = fs2.readdirSync(projectsDir, { withFileTypes: true });
1132
+ const entries = fs3.readdirSync(projectsDir, { withFileTypes: true });
980
1133
  for (const entry of entries) {
981
1134
  if (!entry.isDirectory()) continue;
982
1135
  if (entry.name.startsWith(".")) continue;
983
- const projectPath = path2.join(projectsDir, entry.name);
1136
+ const projectPath = path3.join(projectsDir, entry.name);
984
1137
  count += findJsonlFiles(projectPath).length;
985
1138
  }
986
1139
  } catch {
@@ -995,6 +1148,27 @@ function hasAnySessions() {
995
1148
 
996
1149
  // src/commands/submit.ts
997
1150
  init_ui();
1151
+ async function reportSubmitAttempt(reason, debugInfo) {
1152
+ try {
1153
+ const config = getConfig();
1154
+ const apiUrl = getApiUrl();
1155
+ const token = config.get("apiToken");
1156
+ await fetch(`${apiUrl}/cli/submit-attempt`, {
1157
+ method: "POST",
1158
+ headers: {
1159
+ "Content-Type": "application/json",
1160
+ ...token && { Authorization: `Bearer ${token}` }
1161
+ },
1162
+ body: JSON.stringify({
1163
+ reason,
1164
+ debugInfo,
1165
+ cliVersion: process.env.npm_package_version || "unknown",
1166
+ platform: process.platform
1167
+ })
1168
+ });
1169
+ } catch {
1170
+ }
1171
+ }
998
1172
  function ccgatherToUsageData(data) {
999
1173
  const opusCheck = hasOpusUsageInProject(data.dailyUsage);
1000
1174
  return {
@@ -1050,7 +1224,9 @@ async function submitToServer(data) {
1050
1224
  sessionFingerprint: data.sessionFingerprint,
1051
1225
  // Opus info for badge display
1052
1226
  hasOpusUsage: data.hasOpusUsage,
1053
- opusModels: data.opusModels
1227
+ opusModels: data.opusModels,
1228
+ // Multi-device support
1229
+ deviceId: getDeviceId()
1054
1230
  })
1055
1231
  });
1056
1232
  if (!response.ok) {
@@ -1179,6 +1355,7 @@ async function submit(options) {
1179
1355
  }
1180
1356
  const username = tokenCheck.username || config.get("username");
1181
1357
  verifySpinner.succeed(colors.success(`Authenticated as ${colors.white(username || "unknown")}`));
1358
+ await initPricing();
1182
1359
  if (!hasAnySessions()) {
1183
1360
  console.log(`
1184
1361
  ${error("No Claude Code sessions found.")}`);
@@ -1200,6 +1377,7 @@ async function submit(options) {
1200
1377
  console.log(` ${status} ${pathInfo.path}`);
1201
1378
  }
1202
1379
  console.log();
1380
+ await reportSubmitAttempt("no_sessions", debugInfo);
1203
1381
  process.exit(1);
1204
1382
  }
1205
1383
  console.log(`
@@ -1222,6 +1400,7 @@ async function submit(options) {
1222
1400
  console.log(` ${colors.error("\u2717")} ${colors.error("No usage data found.")}`);
1223
1401
  console.log(` ${colors.muted("Make sure you have used Claude Code at least once.")}
1224
1402
  `);
1403
+ await reportSubmitAttempt("no_data");
1225
1404
  process.exit(1);
1226
1405
  }
1227
1406
  const usageData = ccgatherToUsageData(scannedData);
@@ -1286,6 +1465,23 @@ async function submit(options) {
1286
1465
  ` ${colors.muted("Accumulated")} \u26A1 ${colors.primary(formatNumber(prevTokens))} ${colors.dim("\u2502")} \u{1F4B0} ${colors.warning(formatCost(prevCost))}`
1287
1466
  );
1288
1467
  }
1468
+ if (result.deviceInfo && result.deviceInfo.totalDevices > 1) {
1469
+ const di = result.deviceInfo;
1470
+ const otherCount = di.totalDevices - 1;
1471
+ console.log();
1472
+ console.log(sectionHeader("\u{1F5A5}\uFE0F", "Multi-Device"));
1473
+ console.log();
1474
+ console.log(
1475
+ ` ${colors.muted("This PC")} \u26A1 ${colors.primary(formatNumber(di.thisDeviceTokens))} ${colors.dim("\u2502")} \u{1F4B0} ${colors.warning(formatCost(di.thisDeviceCost))}`
1476
+ );
1477
+ console.log(
1478
+ ` ${colors.muted(`+ ${otherCount} other`)} \u26A1 ${colors.primary(formatNumber(di.otherDevicesTokens))} ${colors.dim("\u2502")} \u{1F4B0} ${colors.warning(formatCost(di.otherDevicesCost))}`
1479
+ );
1480
+ console.log(` ${colors.dim("\u2500".repeat(33))}`);
1481
+ console.log(
1482
+ ` ${colors.white.bold("Combined")} \u26A1 ${colors.primary(formatNumber(di.combinedTokens))} ${colors.dim("\u2502")} \u{1F4B0} ${colors.warning(formatCost(di.combinedCost))} ${colors.success("\u2713")}`
1483
+ );
1484
+ }
1289
1485
  if (result.rank || result.countryRank) {
1290
1486
  console.log();
1291
1487
  console.log(sectionHeader("\u{1F4CA}", "Your Ranking"));
package/package.json CHANGED
@@ -1,55 +1,55 @@
1
- {
2
- "name": "ccgather",
3
- "version": "2.0.32",
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",
13
- "dev": "tsup --watch",
14
- "start": "node dist/index.js",
15
- "typecheck": "tsc --noEmit",
16
- "test": "vitest run",
17
- "test:watch": "vitest"
18
- },
19
- "keywords": [
20
- "claude",
21
- "anthropic",
22
- "claude-code",
23
- "ccgather",
24
- "leaderboard",
25
- "cli"
26
- ],
27
- "author": "",
28
- "license": "Apache-2.0",
29
- "dependencies": {
30
- "chalk": "^5.3.0",
31
- "commander": "^12.1.0",
32
- "conf": "^13.0.1",
33
- "inquirer": "^9.2.23",
34
- "open": "^10.1.0",
35
- "ora": "^8.1.0",
36
- "string-width": "^7.2.0"
37
- },
38
- "devDependencies": {
39
- "@types/inquirer": "^9.0.7",
40
- "@types/node": "^22.10.2",
41
- "tsup": "^8.3.5",
42
- "typescript": "^5.7.2",
43
- "vitest": "^3.0.0"
44
- },
45
- "engines": {
46
- "node": ">=18"
47
- },
48
- "files": [
49
- "dist"
50
- ],
51
- "repository": {
52
- "type": "git",
53
- "url": "git+https://github.com/DHxWhy/CCgather.git"
54
- }
55
- }
1
+ {
2
+ "name": "ccgather",
3
+ "version": "2.0.34",
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",
13
+ "dev": "tsup --watch",
14
+ "start": "node dist/index.js",
15
+ "typecheck": "tsc --noEmit",
16
+ "test": "vitest run",
17
+ "test:watch": "vitest"
18
+ },
19
+ "keywords": [
20
+ "claude",
21
+ "anthropic",
22
+ "claude-code",
23
+ "ccgather",
24
+ "leaderboard",
25
+ "cli"
26
+ ],
27
+ "author": "",
28
+ "license": "Apache-2.0",
29
+ "dependencies": {
30
+ "chalk": "^5.3.0",
31
+ "commander": "^12.1.0",
32
+ "conf": "^13.0.1",
33
+ "inquirer": "^9.2.23",
34
+ "open": "^10.1.0",
35
+ "ora": "^8.1.0",
36
+ "string-width": "^7.2.0"
37
+ },
38
+ "devDependencies": {
39
+ "@types/inquirer": "^9.0.7",
40
+ "@types/node": "^22.10.2",
41
+ "tsup": "^8.3.5",
42
+ "typescript": "^5.7.2",
43
+ "vitest": "^3.0.0"
44
+ },
45
+ "engines": {
46
+ "node": ">=18"
47
+ },
48
+ "files": [
49
+ "dist"
50
+ ],
51
+ "repository": {
52
+ "type": "git",
53
+ "url": "git+https://github.com/DHxWhy/CCgather.git"
54
+ }
55
+ }