ccgather 2.0.30 → 2.0.32

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 +66 -168
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -107,19 +107,15 @@ function createProfessionalHeader() {
107
107
  );
108
108
  return lines;
109
109
  }
110
- async function printAnimatedBox(lines, width = 47, lineDelay = 60) {
111
- const top = colors.dim(` ${box.topLeft}${box.horizontal.repeat(width)}${box.topRight}`);
112
- const bottom = colors.dim(` ${box.bottomLeft}${box.horizontal.repeat(width)}${box.bottomRight}`);
113
- console.log(top);
114
- await sleep(lineDelay / 2);
115
- for (const line of lines) {
110
+ function createBox(lines, width = 47) {
111
+ const paddedLines = lines.map((line) => {
116
112
  const visibleLength = getDisplayWidth(line);
117
113
  const padding = width - 2 - visibleLength;
118
- const paddedLine = `${box.vertical} ${line}${" ".repeat(Math.max(0, padding))} ${box.vertical}`;
119
- console.log(colors.dim(" ") + paddedLine);
120
- await sleep(lineDelay);
121
- }
122
- console.log(bottom);
114
+ return `${box.vertical} ${line}${" ".repeat(Math.max(0, padding))} ${box.vertical}`;
115
+ });
116
+ const top = colors.dim(` ${box.topLeft}${box.horizontal.repeat(width)}${box.topRight}`);
117
+ const bottom = colors.dim(` ${box.bottomLeft}${box.horizontal.repeat(width)}${box.bottomRight}`);
118
+ return [top, ...paddedLines.map((l) => colors.dim(" ") + l), bottom].join("\n");
123
119
  }
124
120
  function _stripAnsi(str) {
125
121
  return str.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, "");
@@ -247,57 +243,6 @@ async function printAnimatedHeader() {
247
243
  }
248
244
  console.log();
249
245
  }
250
- async function slotMachineRank(finalRank, label, medal, iterations = 12, previousRank) {
251
- const maxRank = Math.max(finalRank * 3, 100);
252
- for (let i = 0; i < iterations; i++) {
253
- const fakeRank = Math.floor(Math.random() * maxRank) + 1;
254
- const speed = Math.min(30 + i * 15, 150);
255
- process.stdout.write(
256
- `\r ${medal} ${colors.muted(label)} ${colors.dim(`#${fakeRank}`)} `
257
- );
258
- await sleep(speed);
259
- }
260
- let changeText = "";
261
- if (previousRank && previousRank !== finalRank) {
262
- const change = previousRank - finalRank;
263
- if (change > 0) {
264
- changeText = ` ${colors.success(`\u2191${change}`)}`;
265
- } else if (change < 0) {
266
- changeText = ` ${colors.error(`\u2193${Math.abs(change)}`)}`;
267
- }
268
- }
269
- process.stdout.write(
270
- `\r ${medal} ${colors.muted(label)} ${colors.primary.bold(`#${finalRank}`)}${changeText}
271
- `
272
- );
273
- }
274
- async function animatedProgressBar(targetPercent, barWidth = 20, stepDelay = 25) {
275
- const steps = Math.min(targetPercent, 20);
276
- const stepSize = targetPercent / steps;
277
- for (let i = 0; i <= steps; i++) {
278
- const currentPercent = Math.round(i * stepSize);
279
- const filled = Math.round(currentPercent / 100 * barWidth);
280
- const empty = barWidth - filled;
281
- const bar = colors.primary("\u2588".repeat(filled)) + colors.dim("\u2591".repeat(empty));
282
- process.stdout.write(`\r [${bar}] ${colors.white(`${currentPercent}%`)} `);
283
- await sleep(stepDelay);
284
- }
285
- const finalFilled = Math.round(targetPercent / 100 * barWidth);
286
- const finalEmpty = barWidth - finalFilled;
287
- return colors.primary("\u2588".repeat(finalFilled)) + colors.dim("\u2591".repeat(finalEmpty));
288
- }
289
- async function suspenseDots(message, durationMs = 600) {
290
- const frames = ["", ".", "..", "..."];
291
- const frameDelay = 100;
292
- const iterations = Math.ceil(durationMs / (frames.length * frameDelay));
293
- for (let i = 0; i < iterations; i++) {
294
- for (const frame of frames) {
295
- process.stdout.write(`\r ${colors.muted(message)}${colors.primary(frame)} `);
296
- await sleep(frameDelay);
297
- }
298
- }
299
- process.stdout.write("\r" + " ".repeat(50) + "\r");
300
- }
301
246
  async function printAnimatedWelcomeBox(user) {
302
247
  console.log(` \u{1F44B} ${colors.white.bold(`Welcome back, ${user.username}!`)}`);
303
248
  await sleep(50);
@@ -308,7 +253,7 @@ var init_ui = __esm({
308
253
  "use strict";
309
254
  import_chalk = __toESM(require("chalk"));
310
255
  import_string_width = __toESM(require("string-width"));
311
- VERSION = true ? "2.0.30" : "0.0.0";
256
+ VERSION = true ? "2.0.32" : "0.0.0";
312
257
  colors = {
313
258
  primary: import_chalk.default.hex("#DA7756"),
314
259
  // Claude coral
@@ -893,9 +838,6 @@ function scanAllProjects(options = {}) {
893
838
  const { onProgress } = options;
894
839
  for (let i = 0; i < allJsonlFiles.length; i++) {
895
840
  const filePath = allJsonlFiles[i];
896
- if (onProgress) {
897
- onProgress(i + 1, allJsonlFiles.length);
898
- }
899
841
  const projectName = extractProjectName(filePath);
900
842
  if (!projects[projectName]) {
901
843
  projects[projectName] = {
@@ -975,6 +917,9 @@ function scanAllProjects(options = {}) {
975
917
  }
976
918
  } catch {
977
919
  }
920
+ if (onProgress) {
921
+ onProgress(i + 1, allJsonlFiles.length);
922
+ }
978
923
  }
979
924
  const totalTokens = totalInputTokens + totalOutputTokens + totalCacheWrite + totalCacheRead;
980
925
  if (totalTokens === 0) {
@@ -1144,30 +1089,21 @@ function formatBadgeDate(dateStr) {
1144
1089
  if (!dateStr) return (/* @__PURE__ */ new Date()).toISOString().split("T")[0].replace(/-/g, ".");
1145
1090
  return dateStr.split("T")[0].replace(/-/g, ".");
1146
1091
  }
1147
- async function displayNewBadges(badges) {
1092
+ function displayNewBadges(badges) {
1148
1093
  if (badges.length === 0) return;
1149
1094
  console.log();
1150
- await suspenseDots("Checking achievements", 800);
1151
- for (let i = 0; i < badges.length; i++) {
1152
- const badge = badges[i];
1095
+ for (const badge of badges) {
1153
1096
  const rarityColor = getRarityColor(badge.rarity);
1154
1097
  const rarityLabel = badge.rarity.toUpperCase();
1155
- if (i > 0) {
1156
- await sleep(300);
1157
- }
1158
1098
  console.log(
1159
1099
  ` \u2728 ${badge.icon} ${colors.white.bold(badge.name)} ${rarityColor(`[${rarityLabel}]`)}`
1160
1100
  );
1161
- await sleep(100);
1162
1101
  console.log(` ${colors.muted(badge.description)}`);
1163
- await sleep(80);
1164
1102
  if (badge.praise) {
1165
1103
  console.log(` ${colors.cyan(`"${badge.praise}"`)}`);
1166
- await sleep(80);
1167
1104
  }
1168
1105
  if (badge.category === "rank") {
1169
1106
  console.log(` ${colors.dim(`\u{1F4C5} Achieved: ${formatBadgeDate(badge.earnedAt)}`)}`);
1170
- await sleep(80);
1171
1107
  }
1172
1108
  }
1173
1109
  }
@@ -1280,15 +1216,21 @@ async function submit(options) {
1280
1216
  if (lastProgress > 0) {
1281
1217
  process.stdout.write("\r" + " ".repeat(60) + "\r");
1282
1218
  }
1219
+ process.stdout.write(` ${colors.muted("Processing...")}`);
1283
1220
  if (!scannedData) {
1221
+ process.stdout.write("\r" + " ".repeat(40) + "\r");
1284
1222
  console.log(` ${colors.error("\u2717")} ${colors.error("No usage data found.")}`);
1285
1223
  console.log(` ${colors.muted("Make sure you have used Claude Code at least once.")}
1286
1224
  `);
1287
1225
  process.exit(1);
1288
1226
  }
1289
1227
  const usageData = ccgatherToUsageData(scannedData);
1290
- console.log(` ${colors.success("\u2714")} ${colors.success("Scan complete!")}`);
1291
- console.log();
1228
+ process.stdout.write("\r" + " ".repeat(40) + "\r");
1229
+ const submitSpinner = (0, import_ora2.default)({
1230
+ text: "Submitting to CCgather...",
1231
+ color: "cyan"
1232
+ }).start();
1233
+ const result = await submitToServer(usageData);
1292
1234
  const formatDate = (dateStr) => {
1293
1235
  if (!dateStr) return "------";
1294
1236
  const d = new Date(dateStr);
@@ -1299,13 +1241,10 @@ async function submit(options) {
1299
1241
  };
1300
1242
  const dateRange = usageData.firstUsed && usageData.lastUsed ? `${formatDate(usageData.firstUsed)} ~ ${formatDate(usageData.lastUsed)}` : "";
1301
1243
  const daysTrackedDisplay = dateRange ? `${usageData.daysTracked} days ${colors.dim(`(${dateRange})`)}` : usageData.daysTracked.toString();
1302
- const levelProgress = getLevelProgress(usageData.totalTokens);
1303
- const currentLevel = levelProgress.current;
1304
1244
  const summaryLines = [
1305
1245
  `${colors.muted("Total Cost")} \u{1F4B0} ${colors.warning(formatCost(usageData.totalCost))}`,
1306
1246
  `${colors.muted("Total Tokens")} \u26A1 ${colors.primary(formatNumber(usageData.totalTokens))}`,
1307
- `${colors.muted("Period")} \u{1F4C5} ${colors.white(daysTrackedDisplay)}`,
1308
- `${colors.muted("Level")} ${currentLevel.icon} ${currentLevel.color(`${currentLevel.name}`)}`
1247
+ `${colors.muted("Period")} \u{1F4C5} ${colors.white(daysTrackedDisplay)}`
1309
1248
  ];
1310
1249
  if (usageData.ccplan) {
1311
1250
  const planColor = getPlanColor(usageData.ccplan);
@@ -1317,18 +1256,7 @@ async function submit(options) {
1317
1256
  if (usageData.hasOpusUsage) {
1318
1257
  summaryLines.push(`${colors.muted("Models")} ${colors.max("\u2726 Opus User")}`);
1319
1258
  }
1320
- await printAnimatedBox(summaryLines, 52, 60);
1321
- console.log();
1322
1259
  const projectCount = Object.keys(scannedData.projects).length;
1323
- console.log(
1324
- ` ${colors.dim(`Scanned ${projectCount} project(s), ${usageData.dailyUsage.length} day(s) of data`)}`
1325
- );
1326
- console.log();
1327
- const submitSpinner = (0, import_ora2.default)({
1328
- text: "Submitting to CCgather...",
1329
- color: "cyan"
1330
- }).start();
1331
- const result = await submitToServer(usageData);
1332
1260
  if (result.success) {
1333
1261
  submitSpinner.succeed(colors.success("Successfully submitted!"));
1334
1262
  console.log();
@@ -1337,75 +1265,26 @@ async function submit(options) {
1337
1265
  const lineLength = 40 - text.length;
1338
1266
  return ` ${colors.white.bold(text)}${colors.dim("\u2500".repeat(Math.max(0, lineLength)))}`;
1339
1267
  };
1268
+ const accumulatedTokens = result.previous?.totalTokens || usageData.totalTokens;
1269
+ const levelProgress = getLevelProgress(accumulatedTokens);
1270
+ const currentLevel = levelProgress.current;
1271
+ summaryLines.push(
1272
+ `${colors.muted("Level")} ${currentLevel.icon} ${currentLevel.color(`${currentLevel.name}`)}`
1273
+ );
1274
+ console.log(createBox(summaryLines, 52));
1275
+ console.log(
1276
+ ` ${colors.dim(`Scanned ${projectCount} project(s), ${usageData.dailyUsage.length} day(s) of data`)}`
1277
+ );
1340
1278
  if (result.previous) {
1341
1279
  const prev = result.previous;
1342
- const previousDates = new Set(prev.previousDates || []);
1343
- const previousDailyMap = /* @__PURE__ */ new Map();
1344
- prev.previousDaily?.forEach((d) => {
1345
- previousDailyMap.set(d.date, { tokens: d.tokens, cost: d.cost });
1346
- });
1347
- const currentDates = new Set(usageData.dailyUsage.map((d) => d.date));
1348
- const newDates = usageData.dailyUsage.filter((d) => !previousDates.has(d.date));
1349
- const expiredDates = Array.from(previousDates).filter((d) => !currentDates.has(d));
1350
- const updatedDates = usageData.dailyUsage.filter((d) => {
1351
- const prevData = previousDailyMap.get(d.date);
1352
- return prevData && d.tokens > prevData.tokens;
1353
- });
1354
- let newTokens = 0;
1355
- let newCost = 0;
1356
- newDates.forEach((d) => {
1357
- newTokens += d.tokens;
1358
- newCost += d.cost;
1359
- });
1360
- updatedDates.forEach((d) => {
1361
- const prevData = previousDailyMap.get(d.date);
1362
- if (prevData) {
1363
- newTokens += d.tokens - prevData.tokens;
1364
- newCost += d.cost - prevData.cost;
1365
- }
1366
- });
1367
- let expiredTokens = 0;
1368
- let expiredCost = 0;
1369
- expiredDates.forEach((date) => {
1370
- const prevData = previousDailyMap.get(date);
1371
- if (prevData) {
1372
- expiredTokens += prevData.tokens;
1373
- expiredCost += prevData.cost;
1374
- }
1375
- });
1376
- console.log(sectionHeader("\u{1F4C8}", "Since Last Submit"));
1377
- await sleep(40);
1280
+ console.log();
1281
+ console.log(sectionHeader("\u{1F4E6}", "Server Records"));
1378
1282
  console.log();
1379
1283
  const prevTokens = prev.totalTokens || 0;
1380
1284
  const prevCost = prev.totalCost || 0;
1381
1285
  console.log(
1382
- ` ${colors.dim(`All : \u26A1 ${formatNumber(prevTokens)} \u2502 \u{1F4B0} ${formatCost(prevCost)}`)}`
1286
+ ` ${colors.muted("Accumulated")} \u26A1 ${colors.primary(formatNumber(prevTokens))} ${colors.dim("\u2502")} \u{1F4B0} ${colors.warning(formatCost(prevCost))}`
1383
1287
  );
1384
- await sleep(50);
1385
- if (newTokens > 0 || newCost > 0) {
1386
- console.log(
1387
- ` ${colors.muted("New :")} \u26A1 ${colors.primary(`+${formatNumber(newTokens)}`)} ${colors.dim("\u2502")} \u{1F4B0} ${colors.warning(`+${formatCost(newCost)}`)}`
1388
- );
1389
- } else {
1390
- console.log(
1391
- ` ${colors.dim("New : No new data")}`
1392
- );
1393
- }
1394
- await sleep(50);
1395
- if (expiredDates.length > 0) {
1396
- console.log(
1397
- ` ${colors.warning("\u26A0")} ${colors.dim(`${expiredDates.length} day${expiredDates.length > 1 ? "s" : ""} expired (30-day window)`)}`
1398
- );
1399
- await sleep(40);
1400
- }
1401
- console.log();
1402
- await sleep(60);
1403
- const rankSpinner = (0, import_ora2.default)({
1404
- text: colors.dim("Calculating ranking..."),
1405
- color: "cyan"
1406
- }).start();
1407
- await sleep(400);
1408
- rankSpinner.stop();
1409
1288
  }
1410
1289
  if (result.rank || result.countryRank) {
1411
1290
  console.log();
@@ -1413,37 +1292,47 @@ async function submit(options) {
1413
1292
  console.log();
1414
1293
  const prevGlobalRank = result.previous?.previousGlobalRank;
1415
1294
  const prevCountryRank = result.previous?.previousCountryRank;
1295
+ const formatRankChange = (current, previous) => {
1296
+ if (!previous || previous === current) return "";
1297
+ const change = previous - current;
1298
+ if (change > 0) return ` ${colors.success(`\u2191${change}`)}`;
1299
+ return ` ${colors.error(`\u2193${Math.abs(change)}`)}`;
1300
+ };
1416
1301
  if (result.rank) {
1417
1302
  const medal = result.rank === 1 ? "\u{1F947}" : result.rank === 2 ? "\u{1F948}" : result.rank === 3 ? "\u{1F949}" : result.rank <= 10 ? "\u{1F3C5}" : "\u{1F30D}";
1418
- await slotMachineRank(result.rank, "Global:", medal, 12, prevGlobalRank);
1303
+ console.log(
1304
+ ` ${medal} ${colors.muted("Global:")} ${colors.primary.bold(`#${result.rank}`)}${formatRankChange(result.rank, prevGlobalRank)}`
1305
+ );
1419
1306
  }
1420
1307
  if (result.countryRank) {
1421
1308
  const countryMedal = result.countryRank === 1 ? "\u{1F947}" : result.countryRank <= 3 ? "\u{1F3C6}" : "\u{1F3E0}";
1422
- await slotMachineRank(result.countryRank, "Country:", countryMedal, 10, prevCountryRank);
1309
+ console.log(
1310
+ ` ${countryMedal} ${colors.muted("Country:")} ${colors.primary.bold(`#${result.countryRank}`)}${formatRankChange(result.countryRank, prevCountryRank)}`
1311
+ );
1423
1312
  }
1424
1313
  }
1425
1314
  console.log();
1426
1315
  console.log(sectionHeader("\u2B06\uFE0F", "Level Progress"));
1427
1316
  console.log();
1428
- await sleep(200);
1429
1317
  console.log(
1430
1318
  ` ${colors.muted("Lv.")}${colors.white(String(currentLevel.level))} ${currentLevel.icon} ${currentLevel.color(currentLevel.name)}`
1431
1319
  );
1432
1320
  if (!levelProgress.isMaxLevel && levelProgress.next) {
1433
- await animatedProgressBar(levelProgress.progress, 20, 30);
1434
- console.log();
1435
- await sleep(150);
1321
+ const barWidth = 20;
1322
+ const filled = Math.round(levelProgress.progress / 100 * barWidth);
1323
+ const empty = barWidth - filled;
1324
+ const bar = colors.primary("\u2588".repeat(filled)) + colors.dim("\u2591".repeat(empty));
1325
+ console.log(` [${bar}] ${colors.white(`${levelProgress.progress}%`)}`);
1436
1326
  console.log(
1437
1327
  ` ${colors.dim("\u2192")} ${levelProgress.next.icon} ${colors.white(levelProgress.next.name)} ${colors.muted("in")} ${colors.primary(formatNumber(levelProgress.tokensToNext))}`
1438
1328
  );
1439
1329
  } else {
1440
- await sleep(300);
1441
1330
  console.log(` ${colors.max("\u2605")} ${colors.max("MAX LEVEL ACHIEVED!")}`);
1442
1331
  }
1443
1332
  if (result.newBadges && result.newBadges.length > 0) {
1444
1333
  console.log();
1445
1334
  console.log(sectionHeader("\u{1F389}", "New Badge Unlocked"));
1446
- await displayNewBadges(result.newBadges);
1335
+ displayNewBadges(result.newBadges);
1447
1336
  }
1448
1337
  console.log();
1449
1338
  const leaderboardUrl = `https://ccgather.com/leaderboard?u=${username}`;
@@ -1456,14 +1345,23 @@ async function submit(options) {
1456
1345
  console.log(` ${colors.muted("Submit regularly to preserve your full history!")}`);
1457
1346
  console.log();
1458
1347
  } else {
1459
- submitSpinner.fail(colors.error("Failed to submit"));
1460
- console.log(`
1461
- ${error(result.error || "Unknown error")}`);
1462
1348
  if (result.retryAfterMinutes) {
1349
+ submitSpinner.fail(colors.warning("Submission limit reached"));
1463
1350
  console.log();
1464
1351
  console.log(
1465
- ` ${colors.warning("\u23F3")} ${colors.muted("Try again in")} ${colors.white(`${result.retryAfterMinutes} minute${result.retryAfterMinutes !== 1 ? "s" : ""}`)}`
1352
+ ` ${colors.muted("To keep our service stable, you can submit up to 2 times per hour.")}`
1466
1353
  );
1354
+ console.log(
1355
+ ` ${colors.muted("Your data is safely stored locally - submit anytime later!")}`
1356
+ );
1357
+ console.log();
1358
+ console.log(
1359
+ ` ${colors.warning("\u23F3")} ${colors.white("Ready to submit again in")} ${colors.primary(`${result.retryAfterMinutes} minute${result.retryAfterMinutes !== 1 ? "s" : ""}`)}`
1360
+ );
1361
+ } else {
1362
+ submitSpinner.fail(colors.error("Failed to submit"));
1363
+ console.log(`
1364
+ ${error(result.error || "Unknown error")}`);
1467
1365
  }
1468
1366
  console.log();
1469
1367
  process.exit(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccgather",
3
- "version": "2.0.30",
3
+ "version": "2.0.32",
4
4
  "description": "CLI tool for syncing Claude Code usage data to CCgather leaderboard",
5
5
  "bin": {
6
6
  "ccgather": "dist/index.js",
@@ -25,7 +25,7 @@
25
25
  "cli"
26
26
  ],
27
27
  "author": "",
28
- "license": "MIT",
28
+ "license": "Apache-2.0",
29
29
  "dependencies": {
30
30
  "chalk": "^5.3.0",
31
31
  "commander": "^12.1.0",
@@ -50,6 +50,6 @@
50
50
  ],
51
51
  "repository": {
52
52
  "type": "git",
53
- "url": "git+https://github.com/DHxYoon/CCgather.git"
53
+ "url": "git+https://github.com/DHxWhy/CCgather.git"
54
54
  }
55
55
  }