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.
- package/dist/index.js +118 -113
- 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
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
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 =
|
|
898
|
+
const me = rankData.rankings.find((r) => r.userId === config.userId);
|
|
892
899
|
if (me) me.usageSnapshot = localSnapshot;
|
|
893
900
|
}
|
|
894
|
-
printGroup(
|
|
895
|
-
|
|
896
|
-
|
|
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
|
|
937
|
-
const
|
|
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
|
|
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
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
const
|
|
1014
|
-
const
|
|
1015
|
-
|
|
1016
|
-
|
|
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
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
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
|
-
|
|
1032
|
-
|
|
1036
|
+
}
|
|
1037
|
+
if (globalMax === 0) globalMax = 1;
|
|
1038
|
+
console.log(chalk6.dim(`
|
|
1033
1039
|
Activity (${range})`));
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
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
|
-
|
|
1059
|
-
console.log(` ${chalk6.dim(name + pad)} ${spark} ${chalk6.dim("$" + total.toFixed(2))}`);
|
|
1062
|
+
name = [...name].slice(0, cut).join("");
|
|
1060
1063
|
}
|
|
1061
|
-
const
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
for (let
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
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.
|
|
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)
|