ccgather 1.2.1 → 1.3.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 +272 -141
  2. package/package.json +51 -51
package/dist/index.js CHANGED
@@ -61,7 +61,7 @@ var init_config = __esm({
61
61
  "use strict";
62
62
  import_conf = __toESM(require("conf"));
63
63
  defaults = {
64
- apiUrl: "https://ccgather.dev/api",
64
+ apiUrl: "https://ccgather.com/api",
65
65
  autoSync: false,
66
66
  syncInterval: 60,
67
67
  verbose: false
@@ -106,26 +106,6 @@ async function syncUsage(payload) {
106
106
  async function getStatus() {
107
107
  return fetchApi("/cli/status");
108
108
  }
109
- async function verifyToken(token) {
110
- try {
111
- const apiUrl = getApiUrl();
112
- const response = await fetch(`${apiUrl}/cli/verify`, {
113
- method: "POST",
114
- headers: {
115
- "Content-Type": "application/json",
116
- Authorization: `Bearer ${token}`
117
- }
118
- });
119
- const data = await response.json();
120
- if (!response.ok) {
121
- return { success: false, error: data.error || "Invalid token" };
122
- }
123
- return { success: true, data };
124
- } catch (error2) {
125
- const message = error2 instanceof Error ? error2.message : "Unknown error";
126
- return { success: false, error: message };
127
- }
128
- }
129
109
  var init_api = __esm({
130
110
  "src/lib/api.ts"() {
131
111
  "use strict";
@@ -415,7 +395,7 @@ async function sync(options) {
415
395
  }
416
396
  }
417
397
  console.log(import_chalk4.default.gray("\u2500".repeat(40)));
418
- console.log(import_chalk4.default.gray("\nView full leaderboard: https://ccgather.dev/leaderboard\n"));
398
+ console.log(import_chalk4.default.gray("\nView full leaderboard: https://ccgather.com/leaderboard\n"));
419
399
  }
420
400
  var import_chalk4, import_ora6;
421
401
  var init_sync = __esm({
@@ -436,7 +416,7 @@ __export(auth_exports, {
436
416
  });
437
417
  async function auth(options) {
438
418
  const config = getConfig();
439
- console.log(import_chalk5.default.bold("\n\u{1F310} CCgather Authentication\n"));
419
+ console.log(import_chalk5.default.bold("\n\u{1F510} CCgather Authentication\n"));
440
420
  const existingToken = config.get("apiToken");
441
421
  if (existingToken) {
442
422
  const { overwrite } = await import_inquirer2.default.prompt([
@@ -452,50 +432,122 @@ async function auth(options) {
452
432
  return;
453
433
  }
454
434
  }
455
- let token = options.token;
456
- if (!token) {
457
- console.log(import_chalk5.default.gray("Get your API token from: https://ccgather.dev/settings/api\n"));
458
- const answers = await import_inquirer2.default.prompt([
459
- {
460
- type: "password",
461
- name: "token",
462
- message: "Enter your API token:",
463
- mask: "*",
464
- validate: (input) => {
465
- if (!input || input.length < 10) {
466
- return "Please enter a valid API token";
467
- }
468
- return true;
435
+ if (options.token) {
436
+ await authenticateWithToken(options.token);
437
+ return;
438
+ }
439
+ const apiUrl = getApiUrl();
440
+ const spinner = (0, import_ora7.default)("Initializing authentication...").start();
441
+ try {
442
+ const response = await fetch(`${apiUrl}/cli/auth/device`, {
443
+ method: "POST"
444
+ });
445
+ if (!response.ok) {
446
+ spinner.fail(import_chalk5.default.red("Failed to initialize authentication"));
447
+ console.log(import_chalk5.default.red("\nPlease check your internet connection and try again."));
448
+ process.exit(1);
449
+ }
450
+ const deviceData = await response.json();
451
+ spinner.stop();
452
+ console.log(import_chalk5.default.gray(" Opening browser for authentication...\n"));
453
+ console.log(import_chalk5.default.gray(" If browser doesn't open, visit:"));
454
+ console.log(` \u{1F517} ${import_chalk5.default.cyan.underline(deviceData.verification_uri_complete)}`);
455
+ console.log();
456
+ try {
457
+ await (0, import_open.default)(deviceData.verification_uri_complete);
458
+ } catch {
459
+ console.log(import_chalk5.default.yellow(" Could not open browser automatically."));
460
+ console.log(import_chalk5.default.yellow(" Please open the URL above manually."));
461
+ }
462
+ const pollSpinner = (0, import_ora7.default)("Waiting for authorization...").start();
463
+ const startTime = Date.now();
464
+ const expiresAt = startTime + deviceData.expires_in * 1e3;
465
+ const pollInterval = Math.max(deviceData.interval * 1e3, 5e3);
466
+ while (Date.now() < expiresAt) {
467
+ await sleep(pollInterval);
468
+ try {
469
+ const pollResponse = await fetch(
470
+ `${apiUrl}/cli/auth/device/poll?device_code=${deviceData.device_code}`
471
+ );
472
+ const pollData = await pollResponse.json();
473
+ if (pollData.status === "authorized" && pollData.token) {
474
+ pollSpinner.succeed(import_chalk5.default.green("Authentication successful!"));
475
+ config.set("apiToken", pollData.token);
476
+ config.set("userId", pollData.userId);
477
+ config.set("username", pollData.username);
478
+ console.log(import_chalk5.default.gray(`
479
+ Welcome, ${import_chalk5.default.white(pollData.username)}!`));
480
+ console.log(import_chalk5.default.gray("\nYou can now submit your usage data:"));
481
+ console.log(import_chalk5.default.cyan(" npx ccgather submit\n"));
482
+ return;
483
+ }
484
+ if (pollData.status === "expired" || pollData.status === "used") {
485
+ pollSpinner.fail(import_chalk5.default.red("Authentication expired or already used"));
486
+ console.log(import_chalk5.default.gray('\nPlease run "ccgather auth" to try again.\n'));
487
+ process.exit(1);
469
488
  }
489
+ const remaining = Math.ceil((expiresAt - Date.now()) / 1e3);
490
+ pollSpinner.text = `Waiting for authorization... (${remaining}s remaining)`;
491
+ } catch {
470
492
  }
471
- ]);
472
- token = answers.token;
493
+ }
494
+ pollSpinner.fail(import_chalk5.default.red("Authentication timed out"));
495
+ console.log(import_chalk5.default.gray('\nPlease run "ccgather auth" to try again.\n'));
496
+ process.exit(1);
497
+ } catch (error2) {
498
+ spinner.fail(import_chalk5.default.red("Authentication failed"));
499
+ console.log(import_chalk5.default.red(`
500
+ Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`));
501
+ process.exit(1);
473
502
  }
503
+ }
504
+ async function authenticateWithToken(token) {
505
+ const config = getConfig();
506
+ const apiUrl = getApiUrl();
474
507
  const spinner = (0, import_ora7.default)("Verifying token...").start();
475
- const result = await verifyToken(token);
476
- if (!result.success) {
508
+ try {
509
+ const response = await fetch(`${apiUrl}/cli/verify`, {
510
+ method: "POST",
511
+ headers: {
512
+ "Content-Type": "application/json",
513
+ Authorization: `Bearer ${token}`
514
+ }
515
+ });
516
+ if (!response.ok) {
517
+ spinner.fail(import_chalk5.default.red("Authentication failed"));
518
+ const errorData = await response.json().catch(() => ({}));
519
+ console.log(import_chalk5.default.red(`Error: ${errorData.error || "Invalid token"}`));
520
+ console.log(import_chalk5.default.gray("\nMake sure your token is correct and try again."));
521
+ process.exit(1);
522
+ }
523
+ const data = await response.json();
524
+ config.set("apiToken", token);
525
+ config.set("userId", data.userId);
526
+ config.set("username", data.username);
527
+ spinner.succeed(import_chalk5.default.green("Authentication successful!"));
528
+ console.log(import_chalk5.default.gray(`
529
+ Welcome, ${import_chalk5.default.white(data.username)}!`));
530
+ console.log(import_chalk5.default.gray("\nNext step: Submit your usage data:"));
531
+ console.log(import_chalk5.default.cyan(" npx ccgather submit\n"));
532
+ } catch (error2) {
477
533
  spinner.fail(import_chalk5.default.red("Authentication failed"));
478
- console.log(import_chalk5.default.red(`Error: ${result.error}`));
479
- console.log(import_chalk5.default.gray("\nMake sure your token is correct and try again."));
534
+ console.log(import_chalk5.default.red(`
535
+ Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`));
480
536
  process.exit(1);
481
537
  }
482
- config.set("apiToken", token);
483
- config.set("userId", result.data?.userId);
484
- spinner.succeed(import_chalk5.default.green("Authentication successful!"));
485
- console.log(import_chalk5.default.gray(`
486
- Welcome, ${import_chalk5.default.white(result.data?.username)}!`));
487
- console.log(import_chalk5.default.gray("\nNext step: Sync your usage data:"));
488
- console.log(import_chalk5.default.cyan(" npx ccgather sync\n"));
489
538
  }
490
- var import_chalk5, import_ora7, import_inquirer2;
539
+ function sleep(ms) {
540
+ return new Promise((resolve) => setTimeout(resolve, ms));
541
+ }
542
+ var import_chalk5, import_ora7, import_inquirer2, import_open;
491
543
  var init_auth = __esm({
492
544
  "src/commands/auth.ts"() {
493
545
  "use strict";
494
546
  import_chalk5 = __toESM(require("chalk"));
495
547
  import_ora7 = __toESM(require("ora"));
496
548
  import_inquirer2 = __toESM(require("inquirer"));
549
+ import_open = __toESM(require("open"));
497
550
  init_config();
498
- init_api();
499
551
  }
500
552
  });
501
553
 
@@ -531,13 +583,13 @@ function mapSubscriptionToCCPlan(subscriptionType) {
531
583
  if (type === "max" || type.includes("max")) {
532
584
  return "max";
533
585
  }
534
- if (type === "team" || type === "enterprise") {
535
- return "team";
536
- }
537
586
  if (type === "pro") {
538
587
  return "pro";
539
588
  }
540
- return "free";
589
+ if (type === "free") {
590
+ return "free";
591
+ }
592
+ return type;
541
593
  }
542
594
  function readCredentials() {
543
595
  const credentialsPath = getCredentialsPath();
@@ -624,11 +676,19 @@ function estimateCost(model, inputTokens, outputTokens) {
624
676
  const outputCost = outputTokens / 1e6 * price.output;
625
677
  return Math.round((inputCost + outputCost) * 100) / 100;
626
678
  }
627
- function scanUsageData() {
679
+ function scanUsageData(options = {}) {
628
680
  const projectsDir = getClaudeProjectsDir();
629
681
  if (!fs2.existsSync(projectsDir)) {
630
682
  return null;
631
683
  }
684
+ const days = options.days ?? 30;
685
+ let cutoffDate = null;
686
+ if (days > 0) {
687
+ const cutoff = /* @__PURE__ */ new Date();
688
+ cutoff.setDate(cutoff.getDate() - days);
689
+ cutoff.setHours(0, 0, 0, 0);
690
+ cutoffDate = cutoff.toISOString();
691
+ }
632
692
  let totalInputTokens = 0;
633
693
  let totalOutputTokens = 0;
634
694
  let totalCacheRead = 0;
@@ -661,6 +721,9 @@ function scanUsageData() {
661
721
  try {
662
722
  const event = JSON.parse(line);
663
723
  if (event.type === "assistant" && event.message?.usage) {
724
+ if (cutoffDate && event.timestamp && event.timestamp < cutoffDate) {
725
+ continue;
726
+ }
664
727
  const usage = event.message.usage;
665
728
  const model = event.message.model || "unknown";
666
729
  const inputTokens = usage.input_tokens || 0;
@@ -773,8 +836,8 @@ function writeCCGatherJson(data) {
773
836
  }
774
837
  fs2.writeFileSync(jsonPath, JSON.stringify(data, null, 2));
775
838
  }
776
- function scanAndSave() {
777
- const data = scanUsageData();
839
+ function scanAndSave(options = {}) {
840
+ const data = scanUsageData(options);
778
841
  if (data) {
779
842
  writeCCGatherJson(data);
780
843
  }
@@ -824,7 +887,7 @@ var LOGO_COMPACT = `
824
887
  var TAGLINE = colors.muted(" Where Claude Code Developers Gather");
825
888
  var SLOGAN = colors.dim(" Gather. Compete. Rise.");
826
889
  function getVersionLine(version) {
827
- return colors.dim(` v${version} \u2022 ccgather.dev`);
890
+ return colors.dim(` v${version} \u2022 ccgather.com`);
828
891
  }
829
892
  var box = {
830
893
  topLeft: "\u250C",
@@ -876,10 +939,10 @@ function getRankMedal(rank) {
876
939
  function getCCplanBadge(ccplan) {
877
940
  if (!ccplan) return "";
878
941
  const badges = {
879
- max: `${colors.max("\u{1F947} MAX")}`,
880
- pro: `${colors.pro("\u{1F948} PRO")}`,
881
- team: `${colors.team("\u{1F3E2} TEAM")}`,
882
- free: `${colors.free("\u2B50 FREE")}`
942
+ max: `${colors.max("\u{1F680} MAX")}`,
943
+ pro: `${colors.pro("\u26A1 PRO")}`,
944
+ team: `${colors.team("\u{1F465} TEAM")}`,
945
+ free: `${colors.free("\u26AA FREE")}`
883
946
  };
884
947
  return badges[ccplan.toLowerCase()] || "";
885
948
  }
@@ -904,9 +967,7 @@ function getLevelInfo(tokens) {
904
967
  function createWelcomeBox(user) {
905
968
  const levelInfo = user.level && user.levelName && user.levelIcon ? `${user.levelIcon} Level ${user.level} \u2022 ${user.levelName}` : "";
906
969
  const ccplanBadge = user.ccplan ? getCCplanBadge(user.ccplan) : "";
907
- const lines = [
908
- `\u{1F44B} ${colors.white.bold(`Welcome back, ${user.username}!`)}`
909
- ];
970
+ const lines = [`\u{1F44B} ${colors.white.bold(`Welcome back, ${user.username}!`)}`];
910
971
  if (levelInfo || ccplanBadge) {
911
972
  lines.push(`${levelInfo}${ccplanBadge ? ` ${ccplanBadge}` : ""}`);
912
973
  }
@@ -1001,34 +1062,21 @@ function calculateDaysTracked(data) {
1001
1062
  }
1002
1063
  return 1;
1003
1064
  }
1004
- async function detectGitHubUsername() {
1005
- try {
1006
- const { execSync } = await import("child_process");
1007
- try {
1008
- const username = execSync("git config --get user.name", { encoding: "utf-8" }).trim();
1009
- if (username) return username;
1010
- } catch {
1011
- }
1012
- try {
1013
- const remote = execSync("git remote get-url origin", { encoding: "utf-8" }).trim();
1014
- const match = remote.match(/github\.com[:/]([^/]+)/);
1015
- if (match) return match[1];
1016
- } catch {
1017
- }
1018
- } catch {
1019
- }
1020
- return null;
1021
- }
1022
- async function submitToServer(username, data) {
1065
+ async function submitToServer(data) {
1023
1066
  const apiUrl = getApiUrl();
1067
+ const config = getConfig();
1068
+ const apiToken = config.get("apiToken");
1069
+ if (!apiToken) {
1070
+ return { success: false, error: "Not authenticated. Please run 'ccgather auth' first." };
1071
+ }
1024
1072
  try {
1025
1073
  const response = await fetch(`${apiUrl}/cli/submit`, {
1026
1074
  method: "POST",
1027
1075
  headers: {
1028
- "Content-Type": "application/json"
1076
+ "Content-Type": "application/json",
1077
+ Authorization: `Bearer ${apiToken}`
1029
1078
  },
1030
1079
  body: JSON.stringify({
1031
- username,
1032
1080
  totalTokens: data.totalTokens,
1033
1081
  totalSpent: data.totalCost,
1034
1082
  inputTokens: data.inputTokens,
@@ -1054,27 +1102,19 @@ async function submitToServer(username, data) {
1054
1102
  async function submit(options) {
1055
1103
  printCompactHeader("1.2.1");
1056
1104
  console.log(header("Submit Usage Data", "\u{1F4E4}"));
1057
- const spinner = (0, import_ora.default)({
1058
- text: "Detecting GitHub username...",
1059
- color: "cyan"
1060
- }).start();
1061
- let username = await detectGitHubUsername();
1062
- spinner.stop();
1105
+ if (!isAuthenticated()) {
1106
+ console.log(`
1107
+ ${error("Not authenticated.")}`);
1108
+ console.log(` ${colors.muted("Please run:")} ${colors.white("npx ccgather auth")}
1109
+ `);
1110
+ process.exit(1);
1111
+ }
1112
+ const config = getConfig();
1113
+ const username = config.get("username");
1063
1114
  if (username) {
1064
1115
  console.log(`
1065
- ${colors.muted("Detected:")} ${colors.white(username)}`);
1116
+ ${colors.muted("Logged in as:")} ${colors.white(username)}`);
1066
1117
  }
1067
- const inquirer4 = await import("inquirer");
1068
- const { confirmedUsername } = await inquirer4.default.prompt([
1069
- {
1070
- type: "input",
1071
- name: "confirmedUsername",
1072
- message: colors.muted("GitHub username:"),
1073
- default: username || "",
1074
- validate: (input) => input.trim().length > 0 || "Username is required"
1075
- }
1076
- ]);
1077
- username = confirmedUsername.trim();
1078
1118
  let usageData = null;
1079
1119
  let dataSource = "";
1080
1120
  const ccgatherData = readCCGatherJson();
@@ -1083,7 +1123,9 @@ async function submit(options) {
1083
1123
  dataSource = "ccgather.json";
1084
1124
  console.log(`
1085
1125
  ${success(`Found ${dataSource}`)}`);
1086
- console.log(` ${colors.dim(`Last scanned: ${new Date(ccgatherData.lastScanned).toLocaleString()}`)}`);
1126
+ console.log(
1127
+ ` ${colors.dim(`Last scanned: ${new Date(ccgatherData.lastScanned).toLocaleString()}`)}`
1128
+ );
1087
1129
  if (usageData.ccplan) {
1088
1130
  console.log(` ${colors.dim("CCplan:")} ${colors.primary(usageData.ccplan.toUpperCase())}`);
1089
1131
  }
@@ -1091,6 +1133,7 @@ async function submit(options) {
1091
1133
  if (!usageData) {
1092
1134
  const ccJsonPath = findCcJson();
1093
1135
  if (ccJsonPath) {
1136
+ const inquirer4 = await import("inquirer");
1094
1137
  const { useCcJson } = await inquirer4.default.prompt([
1095
1138
  {
1096
1139
  type: "confirm",
@@ -1132,6 +1175,25 @@ async function submit(options) {
1132
1175
  console.log(`
1133
1176
  ${success(`Using ${dataSource}`)}`);
1134
1177
  }
1178
+ if (!usageData.ccplan) {
1179
+ const inquirer4 = await import("inquirer");
1180
+ const { selectedCCplan } = await inquirer4.default.prompt([
1181
+ {
1182
+ type: "list",
1183
+ name: "selectedCCplan",
1184
+ message: colors.muted("Select your Claude plan:"),
1185
+ choices: [
1186
+ { name: "\u{1F680} Max", value: "max" },
1187
+ { name: "\u26A1 Pro", value: "pro" },
1188
+ { name: "\u26AA Free", value: "free" },
1189
+ { name: "\u{1F465} Team / Enterprise", value: "team" },
1190
+ { name: "\u23ED\uFE0F Skip", value: null }
1191
+ ],
1192
+ default: "free"
1193
+ }
1194
+ ]);
1195
+ usageData.ccplan = selectedCCplan;
1196
+ }
1135
1197
  console.log();
1136
1198
  const summaryLines = [
1137
1199
  `${colors.muted("Total Cost")} ${colors.success(formatCost(usageData.totalCost))}`,
@@ -1139,11 +1201,14 @@ async function submit(options) {
1139
1201
  `${colors.muted("Days Tracked")} ${colors.warning(usageData.daysTracked.toString())}`
1140
1202
  ];
1141
1203
  if (usageData.ccplan) {
1142
- summaryLines.push(`${colors.muted("CCplan")} ${colors.cyan(usageData.ccplan.toUpperCase())}`);
1204
+ summaryLines.push(
1205
+ `${colors.muted("CCplan")} ${colors.cyan(usageData.ccplan.toUpperCase())}`
1206
+ );
1143
1207
  }
1144
1208
  console.log(createBox(summaryLines));
1145
1209
  console.log();
1146
1210
  if (!options.yes) {
1211
+ const inquirer4 = await import("inquirer");
1147
1212
  const { confirmSubmit } = await inquirer4.default.prompt([
1148
1213
  {
1149
1214
  type: "confirm",
@@ -1163,27 +1228,31 @@ async function submit(options) {
1163
1228
  text: "Submitting to CCgather...",
1164
1229
  color: "cyan"
1165
1230
  }).start();
1166
- const result = await submitToServer(username, usageData);
1231
+ const result = await submitToServer(usageData);
1167
1232
  if (result.success) {
1168
1233
  submitSpinner.succeed(colors.success("Successfully submitted to CCgather!"));
1169
1234
  console.log();
1170
1235
  const successLines = [
1171
1236
  `${colors.success("\u2713")} ${colors.white.bold("Submission Complete!")}`,
1172
1237
  "",
1173
- `${colors.muted("Profile:")} ${link(result.profileUrl || `https://ccgather.dev/u/${username}`)}`
1238
+ `${colors.muted("Profile:")} ${link(result.profileUrl || `https://ccgather.com/u/${username}`)}`
1174
1239
  ];
1175
1240
  if (result.rank) {
1176
1241
  successLines.push(`${colors.muted("Rank:")} ${colors.warning(`#${result.rank}`)}`);
1177
1242
  }
1178
1243
  console.log(createBox(successLines));
1179
1244
  console.log();
1180
- console.log(` ${colors.dim("View leaderboard:")} ${link("https://ccgather.dev/leaderboard")}`);
1245
+ console.log(` ${colors.dim("View leaderboard:")} ${link("https://ccgather.com/leaderboard")}`);
1181
1246
  console.log();
1182
1247
  } else {
1183
1248
  submitSpinner.fail(colors.error("Failed to submit"));
1184
1249
  console.log(`
1185
- ${error(result.error || "Unknown error")}
1186
- `);
1250
+ ${error(result.error || "Unknown error")}`);
1251
+ if (result.error?.includes("auth") || result.error?.includes("token")) {
1252
+ console.log(`
1253
+ ${colors.muted("Try running:")} ${colors.white("npx ccgather auth")}`);
1254
+ }
1255
+ console.log();
1187
1256
  process.exit(1);
1188
1257
  }
1189
1258
  }
@@ -1234,7 +1303,9 @@ async function status(options) {
1234
1303
  console.log(` ${medal} ${colors.white.bold(`Rank #${stats.rank}`)}`);
1235
1304
  console.log(` ${colors.dim(`Top ${stats.percentile.toFixed(1)}% of all users`)}`);
1236
1305
  console.log();
1237
- console.log(` ${levelInfo.icon} ${levelInfo.color(`Level ${levelInfo.level} \u2022 ${levelInfo.name}`)}`);
1306
+ console.log(
1307
+ ` ${levelInfo.icon} ${levelInfo.color(`Level ${levelInfo.level} \u2022 ${levelInfo.name}`)}`
1308
+ );
1238
1309
  if (stats.tier) {
1239
1310
  const badge = getCCplanBadge(stats.tier);
1240
1311
  if (badge) {
@@ -1255,7 +1326,7 @@ async function status(options) {
1255
1326
  }
1256
1327
  console.log();
1257
1328
  console.log(colors.dim(" \u2500".repeat(25)));
1258
- console.log(` ${colors.muted("View leaderboard:")} ${link("https://ccgather.dev/leaderboard")}`);
1329
+ console.log(` ${colors.muted("View leaderboard:")} ${link("https://ccgather.com/leaderboard")}`);
1259
1330
  console.log();
1260
1331
  }
1261
1332
 
@@ -1273,8 +1344,8 @@ function getClaudeSettingsDir2() {
1273
1344
  return path5.join(os5.homedir(), ".claude");
1274
1345
  }
1275
1346
  async function openBrowser(url) {
1276
- const { default: open } = await import("open");
1277
- await open(url);
1347
+ const { default: open2 } = await import("open");
1348
+ await open2(url);
1278
1349
  }
1279
1350
  function createCallbackServer() {
1280
1351
  return new Promise((resolve, reject) => {
@@ -1633,7 +1704,7 @@ async function setupAuto(options = {}) {
1633
1704
  console.log(import_chalk3.default.cyan(" npx ccgather status"));
1634
1705
  console.log();
1635
1706
  console.log(import_chalk3.default.gray("View the leaderboard:"));
1636
- console.log(import_chalk3.default.cyan(" https://ccgather.dev/leaderboard"));
1707
+ console.log(import_chalk3.default.cyan(" https://ccgather.com/leaderboard"));
1637
1708
  console.log();
1638
1709
  } catch (err) {
1639
1710
  spinner.fail(import_chalk3.default.red("Authentication failed"));
@@ -1655,8 +1726,12 @@ function displayResults(data) {
1655
1726
  `${colors.muted("Output Tokens")} ${colors.white(formatNumber(data.usage.outputTokens))}`
1656
1727
  ];
1657
1728
  if (data.usage.cacheReadTokens > 0 || data.usage.cacheWriteTokens > 0) {
1658
- usageLines.push(`${colors.muted("Cache Read")} ${colors.dim(formatNumber(data.usage.cacheReadTokens))}`);
1659
- usageLines.push(`${colors.muted("Cache Write")} ${colors.dim(formatNumber(data.usage.cacheWriteTokens))}`);
1729
+ usageLines.push(
1730
+ `${colors.muted("Cache Read")} ${colors.dim(formatNumber(data.usage.cacheReadTokens))}`
1731
+ );
1732
+ usageLines.push(
1733
+ `${colors.muted("Cache Write")} ${colors.dim(formatNumber(data.usage.cacheWriteTokens))}`
1734
+ );
1660
1735
  }
1661
1736
  console.log(createBox(usageLines));
1662
1737
  console.log();
@@ -1684,7 +1759,9 @@ function displayResults(data) {
1684
1759
  const sortedModels = Object.entries(data.models).sort(([, a], [, b]) => b - a).slice(0, 5);
1685
1760
  for (const [model, tokens] of sortedModels) {
1686
1761
  const shortModel = model.replace("claude-", "").substring(0, 20);
1687
- console.log(` ${colors.dim("\u2022")} ${colors.white(shortModel.padEnd(22))} ${colors.primary(formatNumber(tokens))}`);
1762
+ console.log(
1763
+ ` ${colors.dim("\u2022")} ${colors.white(shortModel.padEnd(22))} ${colors.primary(formatNumber(tokens))}`
1764
+ );
1688
1765
  }
1689
1766
  if (Object.keys(data.models).length > 5) {
1690
1767
  console.log(` ${colors.dim(`... and ${Object.keys(data.models).length - 5} more models`)}`);
@@ -1702,7 +1779,9 @@ function displayResults(data) {
1702
1779
  );
1703
1780
  }
1704
1781
  if (Object.keys(data.projects).length > 5) {
1705
- console.log(` ${colors.dim(`... and ${Object.keys(data.projects).length - 5} more projects`)}`);
1782
+ console.log(
1783
+ ` ${colors.dim(`... and ${Object.keys(data.projects).length - 5} more projects`)}`
1784
+ );
1706
1785
  }
1707
1786
  }
1708
1787
  console.log();
@@ -1710,14 +1789,28 @@ function displayResults(data) {
1710
1789
  console.log(` ${colors.muted("Saved to:")} ${colors.dim(getCCGatherJsonPath())}`);
1711
1790
  console.log();
1712
1791
  }
1713
- async function scan() {
1792
+ async function scan(options = {}) {
1714
1793
  printCompactHeader("1.2.1");
1715
1794
  console.log(header("Scan Claude Code Usage", "\u{1F50D}"));
1795
+ let days;
1796
+ if (options.all) {
1797
+ days = 0;
1798
+ console.log(` ${colors.muted("Mode:")} ${colors.primary("All time")} (no date limit)
1799
+ `);
1800
+ } else if (options.days) {
1801
+ days = options.days;
1802
+ console.log(` ${colors.muted("Mode:")} ${colors.primary(`Last ${days} days`)}
1803
+ `);
1804
+ } else {
1805
+ days = 30;
1806
+ console.log(` ${colors.muted("Mode:")} ${colors.primary("Last 30 days")} (default)
1807
+ `);
1808
+ }
1716
1809
  const spinner = (0, import_ora5.default)({
1717
1810
  text: "Scanning JSONL files...",
1718
1811
  color: "cyan"
1719
1812
  }).start();
1720
- const data = scanAndSave();
1813
+ const data = scanAndSave({ days });
1721
1814
  if (!data) {
1722
1815
  spinner.fail(colors.error("No usage data found"));
1723
1816
  console.log();
@@ -1732,15 +1825,20 @@ async function scan() {
1732
1825
  displayResults(data);
1733
1826
  console.log(` ${colors.muted("Next:")} Run ${colors.white("npx ccgather")} to submit your data`);
1734
1827
  console.log();
1828
+ console.log(colors.dim(" \u2500".repeat(25)));
1829
+ console.log(
1830
+ ` ${colors.muted("Tip:")} Use ${colors.white("--all")} for all-time data or ${colors.white("--days <n>")} for custom range`
1831
+ );
1832
+ console.log();
1735
1833
  }
1736
1834
 
1737
1835
  // src/index.ts
1738
1836
  init_config();
1739
1837
  init_api();
1740
- var VERSION = "1.2.1";
1838
+ var VERSION = "1.3.1";
1741
1839
  var program = new import_commander.Command();
1742
1840
  program.name("ccgather").description("Submit your Claude Code usage to the CCgather leaderboard").version(VERSION).option("-y, --yes", "Skip confirmation prompt").option("--auto", "Enable automatic sync on session end").option("--manual", "Disable automatic sync").option("--no-menu", "Skip interactive menu (direct submit)");
1743
- program.command("scan").description("Scan Claude Code usage and create ccgather.json").action(scan);
1841
+ program.command("scan").description("Scan Claude Code usage and create ccgather.json").option("-a, --all", "Scan all-time usage (no date limit)").option("-d, --days <number>", "Number of days to scan (default: 30)", parseInt).action((opts) => scan(opts));
1744
1842
  program.command("rank").description("View your current rank and stats").action(() => status({ json: false }));
1745
1843
  program.command("status").description("View your current rank and stats").option("--json", "Output as JSON").action((opts) => status(opts));
1746
1844
  program.command("sync").description("Sync usage data to CCgather").action(async () => {
@@ -1757,6 +1855,25 @@ program.command("reset").description("Remove auto-sync hook and clear config").a
1757
1855
  });
1758
1856
  async function showInteractiveMenu() {
1759
1857
  printHeader(VERSION);
1858
+ if (!isAuthenticated()) {
1859
+ console.log(colors.warning("\n \u{1F510} Authentication required\n"));
1860
+ console.log(colors.dim(" To submit your Claude Code usage, you need to log in first.\n"));
1861
+ const { startAuth } = await import_inquirer3.default.prompt([
1862
+ {
1863
+ type: "confirm",
1864
+ name: "startAuth",
1865
+ message: "Would you like to authenticate now?",
1866
+ default: true
1867
+ }
1868
+ ]);
1869
+ if (startAuth) {
1870
+ const { auth: auth2 } = await Promise.resolve().then(() => (init_auth(), auth_exports));
1871
+ await auth2({});
1872
+ console.log();
1873
+ } else {
1874
+ console.log(colors.dim("\n You can authenticate later by running: npx ccgather auth\n"));
1875
+ }
1876
+ }
1760
1877
  if (isAuthenticated()) {
1761
1878
  try {
1762
1879
  const result = await getStatus();
@@ -1776,8 +1893,6 @@ async function showInteractiveMenu() {
1776
1893
  }
1777
1894
  } catch {
1778
1895
  }
1779
- } else {
1780
- console.log(colors.dim(' Not logged in. Run "ccgather auth" to authenticate.\n'));
1781
1896
  }
1782
1897
  const { action } = await import_inquirer3.default.prompt([
1783
1898
  {
@@ -1891,10 +2006,18 @@ async function showSettingsMenu() {
1891
2006
  console.log();
1892
2007
  console.log(colors.muted(" Current Configuration:"));
1893
2008
  console.log(colors.dim(" \u2500".repeat(25)));
1894
- console.log(` ${colors.muted("Username:")} ${config.get("username") || colors.dim("(not set)")}`);
1895
- console.log(` ${colors.muted("API Token:")} ${config.get("apiToken") ? colors.success("\u2713 Set") : colors.error("\u2717 Not set")}`);
1896
- console.log(` ${colors.muted("Auto-sync:")} ${config.get("autoSync") ? colors.success("Enabled") : colors.dim("Disabled")}`);
1897
- console.log(` ${colors.muted("Last Sync:")} ${config.get("lastSync") || colors.dim("Never")}`);
2009
+ console.log(
2010
+ ` ${colors.muted("Username:")} ${config.get("username") || colors.dim("(not set)")}`
2011
+ );
2012
+ console.log(
2013
+ ` ${colors.muted("API Token:")} ${config.get("apiToken") ? colors.success("\u2713 Set") : colors.error("\u2717 Not set")}`
2014
+ );
2015
+ console.log(
2016
+ ` ${colors.muted("Auto-sync:")} ${config.get("autoSync") ? colors.success("Enabled") : colors.dim("Disabled")}`
2017
+ );
2018
+ console.log(
2019
+ ` ${colors.muted("Last Sync:")} ${config.get("lastSync") || colors.dim("Never")}`
2020
+ );
1898
2021
  console.log();
1899
2022
  await promptContinue();
1900
2023
  await showSettingsMenu();
@@ -1908,22 +2031,30 @@ function showHelp() {
1908
2031
  console.log(colors.primary.bold(" CCgather CLI Commands"));
1909
2032
  console.log(colors.dim(" \u2500".repeat(25)));
1910
2033
  console.log();
1911
- console.log(` ${colors.white("npx ccgather")} Interactive menu (default)`);
1912
- console.log(` ${colors.white("npx ccgather scan")} Scan local Claude Code usage`);
1913
- console.log(` ${colors.white("npx ccgather rank")} View your current rank`);
1914
- console.log(` ${colors.white("npx ccgather sync")} Sync usage data`);
1915
- console.log(` ${colors.white("npx ccgather auth")} Authenticate with CCgather`);
1916
- console.log(` ${colors.white("npx ccgather reset")} Reset all settings`);
2034
+ console.log(` ${colors.white("npx ccgather")} Interactive menu (default)`);
2035
+ console.log(
2036
+ ` ${colors.white("npx ccgather scan")} Scan local Claude Code usage (last 30 days)`
2037
+ );
2038
+ console.log(` ${colors.white("npx ccgather scan --all")} Scan all-time usage (no date limit)`);
2039
+ console.log(` ${colors.white("npx ccgather scan -d 90")} Scan last 90 days of usage`);
2040
+ console.log(` ${colors.white("npx ccgather rank")} View your current rank`);
2041
+ console.log(` ${colors.white("npx ccgather sync")} Sync usage data`);
2042
+ console.log(` ${colors.white("npx ccgather auth")} Authenticate with CCgather`);
2043
+ console.log(` ${colors.white("npx ccgather reset")} Reset all settings`);
1917
2044
  console.log();
1918
2045
  console.log(colors.dim(" \u2500".repeat(25)));
1919
- console.log(` ${colors.muted("Options:")}`);
2046
+ console.log(` ${colors.muted("Scan Options:")}`);
2047
+ console.log(` ${colors.white("-a, --all")} Scan all-time usage (no date limit)`);
2048
+ console.log(` ${colors.white("-d, --days <n>")} Scan last N days (default: 30)`);
2049
+ console.log();
2050
+ console.log(` ${colors.muted("General Options:")}`);
1920
2051
  console.log(` ${colors.white("-y, --yes")} Skip confirmation prompts`);
1921
2052
  console.log(` ${colors.white("--auto")} Enable auto-sync`);
1922
2053
  console.log(` ${colors.white("--manual")} Disable auto-sync`);
1923
2054
  console.log(` ${colors.white("--no-menu")} Skip interactive menu`);
1924
2055
  console.log();
1925
- console.log(` ${colors.muted("Documentation:")} ${link("https://ccgather.dev/docs")}`);
1926
- console.log(` ${colors.muted("Leaderboard:")} ${link("https://ccgather.dev/leaderboard")}`);
2056
+ console.log(` ${colors.muted("Documentation:")} ${link("https://ccgather.com/docs")}`);
2057
+ console.log(` ${colors.muted("Leaderboard:")} ${link("https://ccgather.com/leaderboard")}`);
1927
2058
  console.log();
1928
2059
  }
1929
2060
  async function promptContinue() {
package/package.json CHANGED
@@ -1,51 +1,51 @@
1
- {
2
- "name": "ccgather",
3
- "version": "1.2.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": "git+https://github.com/DHxYoon/CCgather.git"
50
- }
51
- }
1
+ {
2
+ "name": "ccgather",
3
+ "version": "1.3.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": "git+https://github.com/DHxYoon/CCgather.git"
50
+ }
51
+ }