ccclub 0.2.67 → 0.2.68

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 +63 -3
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -391,6 +391,48 @@ function formatFetchError(err) {
391
391
  return causes.length > 1 ? causes[causes.length - 1] : detail;
392
392
  }
393
393
 
394
+ // src/usage-limits.ts
395
+ import { execSync as execSync2 } from "child_process";
396
+ import { userInfo as userInfo2 } from "os";
397
+ function parseUtilization(value) {
398
+ if (typeof value === "number") return Math.round(value * 100) / 100;
399
+ if (typeof value === "string") {
400
+ const n = parseFloat(value.replace("%", ""));
401
+ return isNaN(n) ? 0 : Math.round(n * 100) / 100;
402
+ }
403
+ return 0;
404
+ }
405
+ async function fetchUsageLimits() {
406
+ try {
407
+ const username = userInfo2().username;
408
+ const raw = execSync2(
409
+ `security find-generic-password -s "Claude Code-credentials" -a "${username}" -w`,
410
+ { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 5e3 }
411
+ ).trim();
412
+ const credentials = JSON.parse(raw);
413
+ const accessToken = credentials?.claudeAiOauth?.accessToken;
414
+ if (!accessToken || typeof accessToken !== "string") return null;
415
+ const res = await fetch("https://api.anthropic.com/api/oauth/usage", {
416
+ headers: {
417
+ Authorization: `Bearer ${accessToken}`,
418
+ "anthropic-beta": "oauth-2025-04-20"
419
+ },
420
+ signal: AbortSignal.timeout(8e3)
421
+ });
422
+ if (!res.ok) return null;
423
+ const data = await res.json();
424
+ const fiveHourRaw = data.five_hour?.utilization;
425
+ const sevenDayRaw = data.seven_day?.utilization;
426
+ return {
427
+ fiveHour: parseUtilization(fiveHourRaw),
428
+ sevenDay: parseUtilization(sevenDayRaw),
429
+ snapshotAt: (/* @__PURE__ */ new Date()).toISOString()
430
+ };
431
+ } catch {
432
+ return null;
433
+ }
434
+ }
435
+
394
436
  // src/commands/sync.ts
395
437
  var SYNC_FORMAT_VERSION = "6";
396
438
  function getSyncVersionPath() {
@@ -443,7 +485,10 @@ async function doSync(firstSync = false, silent = false) {
443
485
  } : console.log;
444
486
  const spinner = silent ? null : ora("Collecting usage data...").start();
445
487
  try {
446
- const { entries, humanTurns } = await collectUsageEntries();
488
+ const [{ entries, humanTurns }, usageSnapshot] = await Promise.all([
489
+ collectUsageEntries(),
490
+ fetchUsageLimits().catch(() => null)
491
+ ]);
447
492
  if (spinner) spinner.text = `Found ${entries.length} entries`;
448
493
  if (entries.length === 0) {
449
494
  if (spinner) spinner.warn("No usage data found in ~/.claude/projects/");
@@ -468,13 +513,15 @@ async function doSync(firstSync = false, silent = false) {
468
513
  return;
469
514
  }
470
515
  if (spinner) spinner.text = `Uploading ${blocksToSync.length} blocks...`;
516
+ const syncBody = { blocks: blocksToSync };
517
+ if (usageSnapshot) syncBody.usageSnapshot = usageSnapshot;
471
518
  const res = await fetch(`${config.apiUrl}/api/sync`, {
472
519
  method: "POST",
473
520
  headers: {
474
521
  "Content-Type": "application/json",
475
522
  Authorization: `Bearer ${config.token}`
476
523
  },
477
- body: JSON.stringify({ blocks: blocksToSync }),
524
+ body: JSON.stringify(syncBody),
478
525
  signal: AbortSignal.timeout(6e4)
479
526
  });
480
527
  if (!res.ok) {
@@ -852,8 +899,13 @@ function printGroup(data, code, period, config, showCache = false) {
852
899
  console.log(chalk6.dim(` ${periodLabel[period] || period.toUpperCase()} \xB7 ${data.start.slice(0, 10)} \u2192 ${data.end.slice(0, 10)} \xB7 ${data.group.memberCount} members
853
900
  `));
854
901
  const hasPlan = data.rankings.some((r) => r.plan);
902
+ const hasUsage = data.rankings.some((r) => r.usageSnapshot);
855
903
  const head = ["#", "Name", "Cost", "Tokens"];
856
904
  const widths = [5, 20, 12, 10];
905
+ if (hasUsage) {
906
+ head.push("Usage (5h/7d)");
907
+ widths.push(14);
908
+ }
857
909
  if (hasPlan) {
858
910
  head.push("Monthly ROI");
859
911
  widths.push(15);
@@ -878,6 +930,14 @@ function printGroup(data, code, period, config, showCache = false) {
878
930
  c(`$${entry.costUSD.toFixed(2)}`),
879
931
  c(formatTokens(tokens))
880
932
  ];
933
+ if (hasUsage) {
934
+ if (entry.usageSnapshot) {
935
+ const { fiveHour, sevenDay } = entry.usageSnapshot;
936
+ row.push(c(`${Math.round(fiveHour)}%/${Math.round(sevenDay)}%`));
937
+ } else {
938
+ row.push(chalk6.dim("\u2014"));
939
+ }
940
+ }
881
941
  if (hasPlan) {
882
942
  if (entry.plan && entry.plan !== "api") {
883
943
  const price = PLAN_PRICES[entry.plan];
@@ -1243,7 +1303,7 @@ async function hookCommand() {
1243
1303
  }
1244
1304
 
1245
1305
  // src/index.ts
1246
- var VERSION = "0.2.67";
1306
+ var VERSION = "0.2.68";
1247
1307
  startUpdateCheck(VERSION);
1248
1308
  var program = new Command();
1249
1309
  program.name("ccclub").description("Claude Code leaderboard among friends").version(VERSION, "-v, -V, --version");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccclub",
3
- "version": "0.2.67",
3
+ "version": "0.2.68",
4
4
  "type": "module",
5
5
  "description": "Claude Code leaderboard among friends",
6
6
  "bin": {