ccclub 0.2.73 → 0.2.76

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 +118 -113
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -867,34 +867,40 @@ async function rankCommand(options) {
867
867
  const localUsagePromise = fetchUsageLimits().catch(() => null);
868
868
  const spinner = ora4("Loading leaderboard...").start();
869
869
  try {
870
- for (let i = 0; i < codes.length; i++) {
871
- const code = codes[i];
872
- const tz = -(/* @__PURE__ */ new Date()).getTimezoneOffset();
873
- const url = `${config.apiUrl}/api/rank/${code}?period=${period}&tz=${tz}`;
874
- const res = await fetch(url, { signal: AbortSignal.timeout(15e3) });
875
- if (!res.ok) {
876
- if (i === 0) spinner.stop();
870
+ const groupResults = await Promise.all(
871
+ codes.map(async (code) => {
872
+ const tz = -(/* @__PURE__ */ new Date()).getTimezoneOffset();
873
+ const range = period === "weekly" ? "7d" : period === "monthly" || period === "all-time" ? "30d" : period === "yesterday" ? "yesterday" : "24h";
874
+ const [rankRes, activityRes] = await Promise.all([
875
+ fetch(`${config.apiUrl}/api/rank/${code}?period=${period}&tz=${tz}`, { signal: AbortSignal.timeout(15e3) }),
876
+ fetch(`${config.apiUrl}/api/activity/${code}?range=${range}&tz=${tz}`, { signal: AbortSignal.timeout(1e4) }).catch(() => null)
877
+ ]);
878
+ if (!rankRes.ok) return { code, rankData: null, activityData: null, range };
879
+ const rankData = await rankRes.json();
880
+ const activityData = activityRes?.ok ? await activityRes.json() : null;
881
+ return { code, rankData, activityData, range };
882
+ })
883
+ );
884
+ spinner.stop();
885
+ const localSnapshot = await localUsagePromise;
886
+ if (process.env.CCCLUB_DEBUG) {
887
+ console.error("[usage-debug] localSnapshot:", localSnapshot);
888
+ console.error("[usage-debug] config.userId:", config.userId);
889
+ }
890
+ for (let i = 0; i < groupResults.length; i++) {
891
+ const { code, rankData, activityData, range } = groupResults[i];
892
+ if (!rankData) {
877
893
  console.log(chalk6.red(`
878
894
  Couldn't load leaderboard for ${code}`));
879
895
  continue;
880
896
  }
881
- const data = await res.json();
882
- if (i === 0) spinner.stop();
883
- const localSnapshot = await localUsagePromise;
884
- if (process.env.CCCLUB_DEBUG) {
885
- console.error("[usage-debug] localSnapshot:", localSnapshot);
886
- console.error("[usage-debug] config.userId:", config.userId);
887
- const dbgMe = data.rankings.find((r) => r.userId === config.userId);
888
- console.error("[usage-debug] me found:", !!dbgMe, "rankings userIds:", data.rankings.map((r) => r.userId).slice(0, 3));
889
- }
890
897
  if (localSnapshot) {
891
- const me = data.rankings.find((r) => r.userId === config.userId);
898
+ const me = rankData.rankings.find((r) => r.userId === config.userId);
892
899
  if (me) me.usageSnapshot = localSnapshot;
893
900
  }
894
- printGroup(data, code, period, config, options.cache);
895
- const range = period === "weekly" ? "7d" : period === "monthly" || period === "all-time" ? "30d" : period === "yesterday" ? "yesterday" : "24h";
896
- await printActivity(config.apiUrl, code, range);
897
- if (i < codes.length - 1) console.log("");
901
+ printGroup(rankData, code, period, config, options.cache, options.all);
902
+ if (activityData) renderActivity(activityData, range);
903
+ if (i < groupResults.length - 1) console.log("");
898
904
  }
899
905
  console.log(chalk6.dim("\n Tokens = input + output ") + chalk6.yellow("(cache excluded)") + chalk6.dim(". Use ") + chalk6.white("--cache") + chalk6.dim(" to include cache tokens."));
900
906
  const update = await getUpdateResult();
@@ -920,7 +926,7 @@ function formatTokens(n) {
920
926
  }
921
927
  return String(n);
922
928
  }
923
- function printGroup(data, code, period, config, showCache = false) {
929
+ function printGroup(data, code, period, config, showCache = false, showAll = false) {
924
930
  if (data.rankings.length === 0) {
925
931
  console.log(chalk6.bold(`
926
932
  ${data.group.name}`));
@@ -933,26 +939,28 @@ function printGroup(data, code, period, config, showCache = false) {
933
939
  const periodLabel = { daily: "TODAY", yesterday: "YESTERDAY", weekly: "7 DAYS", monthly: "30 DAYS", "all-time": "ALL TIME" };
934
940
  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
935
941
  `));
936
- const hasPlan = data.rankings.some((r) => r.plan);
937
- const hasUsage = data.rankings.some((r) => r.usageSnapshot);
942
+ const activeRankings = showAll || data.rankings.length <= 15 ? data.rankings : data.rankings.filter((r) => r.costUSD > 0 || r.userId === config.userId);
943
+ const hiddenCount = data.rankings.length - activeRankings.length;
944
+ const hasPlan = activeRankings.some((r) => r.plan);
945
+ const hasUsage = activeRankings.some((r) => r.usageSnapshot);
938
946
  const head = ["#", "Name", "Cost", "Tokens"];
939
947
  const widths = [5, 20, 12, 10];
940
- if (hasUsage) {
941
- head.push("Usage 7d");
942
- widths.push(10);
943
- }
944
948
  if (hasPlan) {
945
949
  head.push("Monthly ROI");
946
950
  widths.push(15);
947
951
  }
948
952
  head.push("Chats", "$/Chat");
949
953
  widths.push(8, 9);
954
+ if (hasUsage) {
955
+ head.push("Usage 7d");
956
+ widths.push(10);
957
+ }
950
958
  const table = new Table({
951
959
  head: head.map((h) => chalk6.cyan(h)),
952
960
  style: { head: [], border: [] },
953
961
  colWidths: widths
954
962
  });
955
- for (const entry of data.rankings) {
963
+ for (const entry of activeRankings) {
956
964
  const isMe = entry.userId === config.userId;
957
965
  const tokens = showCache ? entry.totalTokens : entry.inputTokens + entry.outputTokens;
958
966
  const marker = isMe ? chalk6.green("\u2192") : " ";
@@ -965,14 +973,6 @@ function printGroup(data, code, period, config, showCache = false) {
965
973
  c(`$${entry.costUSD.toFixed(2)}`),
966
974
  c(formatTokens(tokens))
967
975
  ];
968
- if (hasUsage) {
969
- if (entry.usageSnapshot) {
970
- const { sevenDay } = entry.usageSnapshot;
971
- row.push(c(`${Math.round(sevenDay)}%`));
972
- } else {
973
- row.push(chalk6.dim("\u2014"));
974
- }
975
- }
976
976
  if (hasPlan) {
977
977
  if (entry.plan && entry.plan !== "api") {
978
978
  const price = PLAN_PRICES[entry.plan];
@@ -989,9 +989,20 @@ function printGroup(data, code, period, config, showCache = false) {
989
989
  }
990
990
  row.push(c(String(entry.chatCount)));
991
991
  row.push(entry.chatCount > 0 ? c(`$${(entry.costUSD / entry.chatCount).toFixed(2)}`) : chalk6.dim("\u2014"));
992
+ if (hasUsage) {
993
+ if (entry.usageSnapshot) {
994
+ const { sevenDay } = entry.usageSnapshot;
995
+ row.push(c(`${Math.round(sevenDay)}%`));
996
+ } else {
997
+ row.push(chalk6.dim("\u2014"));
998
+ }
999
+ }
992
1000
  table.push(row);
993
1001
  }
994
1002
  console.log(table.toString());
1003
+ if (hiddenCount > 0) {
1004
+ console.log(chalk6.dim(` ${hiddenCount} inactive member${hiddenCount > 1 ? "s" : ""} hidden \xB7 ccclub --all to show`));
1005
+ }
995
1006
  console.log(chalk6.dim(` Dashboard: ${config.apiUrl}/g/${code}`));
996
1007
  if (hasPlan) {
997
1008
  const me = data.rankings.find((r) => r.userId === config.userId);
@@ -1001,87 +1012,80 @@ function printGroup(data, code, period, config, showCache = false) {
1001
1012
  }
1002
1013
  }
1003
1014
  var SPARK_CHARS = "\u2581\u2582\u2583\u2584\u2585\u2586\u2587";
1004
- async function printActivity(apiUrl, code, range) {
1005
- try {
1006
- const tz = -(/* @__PURE__ */ new Date()).getTimezoneOffset();
1007
- const res = await fetch(`${apiUrl}/api/activity/${code}?range=${range}&tz=${tz}`, { signal: AbortSignal.timeout(1e4) });
1008
- if (!res.ok) return;
1009
- const data = await res.json();
1010
- const active = data.series.filter((s) => s.blocks.length > 0);
1011
- if (active.length === 0) return;
1012
- const startMs = new Date(data.start).getTime();
1013
- const endMs = new Date(data.end).getTime();
1014
- const bucketCount = range === "24h" || range === "yesterday" ? 48 : range === "7d" ? 28 : 30;
1015
- const bucketMs = (endMs - startMs) / bucketCount;
1016
- const allBuckets = [];
1017
- for (const user of active) {
1018
- const buckets = new Array(bucketCount).fill(0);
1019
- for (const bl of user.blocks) {
1020
- const idx = Math.min(Math.floor((new Date(bl.t).getTime() - startMs) / bucketMs), bucketCount - 1);
1021
- if (idx >= 0) buckets[idx] += bl.cost;
1022
- }
1023
- allBuckets.push(buckets);
1015
+ function renderActivity(data, range) {
1016
+ const active = data.series.filter((s) => s.blocks.length > 0);
1017
+ if (active.length === 0) return;
1018
+ const startMs = new Date(data.start).getTime();
1019
+ const endMs = new Date(data.end).getTime();
1020
+ const bucketCount = range === "24h" || range === "yesterday" ? 48 : range === "7d" ? 28 : 30;
1021
+ const bucketMs = (endMs - startMs) / bucketCount;
1022
+ const allBuckets = [];
1023
+ for (const user of active) {
1024
+ const buckets = new Array(bucketCount).fill(0);
1025
+ for (const bl of user.blocks) {
1026
+ const idx = Math.min(Math.floor((new Date(bl.t).getTime() - startMs) / bucketMs), bucketCount - 1);
1027
+ if (idx >= 0) buckets[idx] += bl.cost;
1024
1028
  }
1025
- let globalMax = 0;
1026
- for (const buckets of allBuckets) {
1027
- for (const v of buckets) {
1028
- if (v > globalMax) globalMax = v;
1029
- }
1029
+ allBuckets.push(buckets);
1030
+ }
1031
+ let globalMax = 0;
1032
+ for (const buckets of allBuckets) {
1033
+ for (const v of buckets) {
1034
+ if (v > globalMax) globalMax = v;
1030
1035
  }
1031
- if (globalMax === 0) globalMax = 1;
1032
- console.log(chalk6.dim(`
1036
+ }
1037
+ if (globalMax === 0) globalMax = 1;
1038
+ console.log(chalk6.dim(`
1033
1039
  Activity (${range})`));
1034
- for (let i = 0; i < active.length; i++) {
1035
- const user = active[i];
1036
- const buckets = allBuckets[i];
1037
- const spark = buckets.map((v) => {
1038
- if (v === 0) return SPARK_CHARS[0];
1039
- const normalized = Math.sqrt(v / globalMax);
1040
- const idx = 1 + Math.min(Math.floor(normalized * (SPARK_CHARS.length - 1)), SPARK_CHARS.length - 2);
1041
- return SPARK_CHARS[idx];
1042
- }).join("");
1043
- const total = user.blocks.reduce((s, b) => s + b.cost, 0);
1044
- const displayWidth = [...user.displayName].reduce((w, ch) => w + (ch.charCodeAt(0) > 127 ? 2 : 1), 0);
1045
- const maxWidth = 12;
1046
- let name = user.displayName;
1047
- if (displayWidth > maxWidth) {
1048
- let w = 0;
1049
- let cut = 0;
1050
- for (const ch of name) {
1051
- const cw = ch.charCodeAt(0) > 127 ? 2 : 1;
1052
- if (w + cw > maxWidth) break;
1053
- w += cw;
1054
- cut++;
1055
- }
1056
- name = [...name].slice(0, cut).join("");
1040
+ for (let i = 0; i < active.length; i++) {
1041
+ const user = active[i];
1042
+ const buckets = allBuckets[i];
1043
+ const spark = buckets.map((v) => {
1044
+ if (v === 0) return SPARK_CHARS[0];
1045
+ const normalized = Math.sqrt(v / globalMax);
1046
+ const idx = 1 + Math.min(Math.floor(normalized * (SPARK_CHARS.length - 1)), SPARK_CHARS.length - 2);
1047
+ return SPARK_CHARS[idx];
1048
+ }).join("");
1049
+ const total = user.blocks.reduce((s, b) => s + b.cost, 0);
1050
+ const displayWidth = [...user.displayName].reduce((w, ch) => w + (ch.charCodeAt(0) > 127 ? 2 : 1), 0);
1051
+ const maxWidth = 12;
1052
+ let name = user.displayName;
1053
+ if (displayWidth > maxWidth) {
1054
+ let w = 0;
1055
+ let cut = 0;
1056
+ for (const ch of name) {
1057
+ const cw = ch.charCodeAt(0) > 127 ? 2 : 1;
1058
+ if (w + cw > maxWidth) break;
1059
+ w += cw;
1060
+ cut++;
1057
1061
  }
1058
- const pad = " ".repeat(Math.max(0, maxWidth - displayWidth));
1059
- console.log(` ${chalk6.dim(name + pad)} ${spark} ${chalk6.dim("$" + total.toFixed(2))}`);
1062
+ name = [...name].slice(0, cut).join("");
1060
1063
  }
1061
- const axisArr = new Array(bucketCount).fill(" ");
1062
- if (range === "24h" || range === "yesterday") {
1063
- for (let b = 0; b < bucketCount; b += 12) {
1064
- const t = new Date(startMs + b * bucketMs);
1065
- const label = `${t.getHours()}h`;
1066
- for (let c = 0; c < label.length && b + c < bucketCount; c++) axisArr[b + c] = label[c];
1067
- }
1068
- } else if (range === "7d") {
1069
- const dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
1070
- for (let b = 0; b < bucketCount; b += 4) {
1071
- const t = new Date(startMs + b * bucketMs);
1072
- const label = dayNames[t.getDay()];
1073
- for (let c = 0; c < label.length && b + c < bucketCount; c++) axisArr[b + c] = label[c];
1074
- }
1075
- } else {
1076
- for (let b = 0; b < bucketCount; b += 7) {
1077
- const t = new Date(startMs + b * bucketMs);
1078
- const label = `${t.getMonth() + 1}/${t.getDate()}`;
1079
- for (let c = 0; c < label.length && b + c < bucketCount; c++) axisArr[b + c] = label[c];
1080
- }
1064
+ const pad = " ".repeat(Math.max(0, maxWidth - displayWidth));
1065
+ console.log(` ${chalk6.dim(name + pad)} ${spark} ${chalk6.dim("$" + total.toFixed(2))}`);
1066
+ }
1067
+ const axisArr = new Array(bucketCount).fill(" ");
1068
+ if (range === "24h" || range === "yesterday") {
1069
+ for (let b = 0; b < bucketCount; b += 12) {
1070
+ const t = new Date(startMs + b * bucketMs);
1071
+ const label = `${t.getHours()}h`;
1072
+ for (let c = 0; c < label.length && b + c < bucketCount; c++) axisArr[b + c] = label[c];
1073
+ }
1074
+ } else if (range === "7d") {
1075
+ const dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
1076
+ for (let b = 0; b < bucketCount; b += 4) {
1077
+ const t = new Date(startMs + b * bucketMs);
1078
+ const label = dayNames[t.getDay()];
1079
+ for (let c = 0; c < label.length && b + c < bucketCount; c++) axisArr[b + c] = label[c];
1080
+ }
1081
+ } else {
1082
+ for (let b = 0; b < bucketCount; b += 7) {
1083
+ const t = new Date(startMs + b * bucketMs);
1084
+ const label = `${t.getMonth() + 1}/${t.getDate()}`;
1085
+ for (let c = 0; c < label.length && b + c < bucketCount; c++) axisArr[b + c] = label[c];
1081
1086
  }
1082
- console.log(chalk6.dim(" " + " ".repeat(12) + " " + axisArr.join("")));
1083
- } catch {
1084
1087
  }
1088
+ console.log(chalk6.dim(" " + " ".repeat(12) + " " + axisArr.join("")));
1085
1089
  }
1086
1090
 
1087
1091
  // src/commands/profile.ts
@@ -1338,11 +1342,11 @@ async function hookCommand() {
1338
1342
  }
1339
1343
 
1340
1344
  // src/index.ts
1341
- var VERSION = "0.2.73";
1345
+ var VERSION = "0.2.76";
1342
1346
  startUpdateCheck(VERSION);
1343
1347
  var program = new Command();
1344
1348
  program.name("ccclub").description("Claude Code leaderboard among friends").version(VERSION, "-v, -V, --version");
1345
- program.command("rank", { isDefault: true, hidden: true }).description("Show leaderboard").option("-d, --days [days]", "Time window: 1 | 7 | 30 | all (default: today)").addOption(new Option("-p, --period [period]").hideHelp()).option("-g, --group <code>", "Group invite code").option("--global", "Show global public ranking").option("--cache", "Include cache tokens in count").action(rankCommand);
1349
+ program.command("rank", { isDefault: true, hidden: true }).description("Show leaderboard").option("-d, --days [days]", "Time window: 1 | 7 | 30 | all (default: today)").addOption(new Option("-p, --period [period]").hideHelp()).option("-g, --group <code>", "Group invite code").option("--global", "Show global public ranking").option("--cache", "Include cache tokens in count").option("--all", "Show all members including those with no activity").action(rankCommand);
1346
1350
  program.command("init").description("Create a group and get started (first-time setup)").action(initCommand);
1347
1351
  program.command("join").description("Join a group with a 6-letter invite code").argument("[invite-code]", "6-character invite code").action((code) => {
1348
1352
  if (!code) {
@@ -1373,6 +1377,7 @@ Leaderboard options:
1373
1377
  -g <code> Show a specific group
1374
1378
  --global Global public leaderboard
1375
1379
  --cache Include cache tokens in total
1380
+ --all Show all members including inactive ones
1376
1381
 
1377
1382
  Examples:
1378
1383
  $ ccclub Show today's leaderboard (default)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccclub",
3
- "version": "0.2.73",
3
+ "version": "0.2.76",
4
4
  "type": "module",
5
5
  "description": "Claude Code leaderboard among friends",
6
6
  "bin": {