ccclub 0.3.5 → 0.3.7
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 +131 -51
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -585,7 +585,7 @@ function normalizeRawUsage(value) {
|
|
|
585
585
|
const cachedInputTokens = asNumber(record.cached_input_tokens ?? record.cache_read_input_tokens);
|
|
586
586
|
const outputTokens = asNumber(record.output_tokens);
|
|
587
587
|
const reasoningTokens = asNumber(record.reasoning_output_tokens);
|
|
588
|
-
const fallbackTotal = inputTokens + outputTokens
|
|
588
|
+
const fallbackTotal = inputTokens + outputTokens;
|
|
589
589
|
const totalTokens = asNumber(record.total_tokens) || fallbackTotal;
|
|
590
590
|
return { inputTokens, cachedInputTokens, outputTokens, reasoningTokens, totalTokens };
|
|
591
591
|
}
|
|
@@ -669,9 +669,9 @@ async function collectCodexUsage() {
|
|
|
669
669
|
outputTokens: rawUsage.outputTokens,
|
|
670
670
|
cacheCreationTokens: 0,
|
|
671
671
|
cacheReadTokens,
|
|
672
|
-
reasoningTokens:
|
|
672
|
+
reasoningTokens: 0,
|
|
673
673
|
totalTokens,
|
|
674
|
-
costUSD: calculateCost(model, inputTokens, rawUsage.outputTokens, 0, cacheReadTokens
|
|
674
|
+
costUSD: calculateCost(model, inputTokens, rawUsage.outputTokens, 0, cacheReadTokens)
|
|
675
675
|
});
|
|
676
676
|
turns.push({ source, timestamp, key: dedupeKey });
|
|
677
677
|
});
|
|
@@ -1197,7 +1197,7 @@ async function fetchUsageLimits() {
|
|
|
1197
1197
|
}
|
|
1198
1198
|
|
|
1199
1199
|
// src/commands/sync.ts
|
|
1200
|
-
var SYNC_FORMAT_VERSION = "
|
|
1200
|
+
var SYNC_FORMAT_VERSION = "8";
|
|
1201
1201
|
function getSyncVersionPath() {
|
|
1202
1202
|
return join11(homedir11(), CCCLUB_CONFIG_DIR, "sync-version");
|
|
1203
1203
|
}
|
|
@@ -1728,73 +1728,74 @@ function printGroup(data, code, period, config, showCache = false, showAll = fal
|
|
|
1728
1728
|
const activeRankings = showAll || data.rankings.length <= 15 ? data.rankings : data.rankings.filter((r) => r.costUSD > 0 || r.userId === config.userId);
|
|
1729
1729
|
const hiddenCount = data.rankings.length - activeRankings.length;
|
|
1730
1730
|
const hasPlan = activeRankings.some((r) => r.plan);
|
|
1731
|
-
const hasUsage = activeRankings.some((r) => r.usageSnapshot);
|
|
1732
1731
|
const hasAgents = activeRankings.some(
|
|
1733
1732
|
(r) => r.agents && r.agents.length > 0 && !(r.agents.length === 1 && r.agents[0] === "claude")
|
|
1734
1733
|
);
|
|
1734
|
+
const plainRows = activeRankings.map((entry) => {
|
|
1735
|
+
const isActive = isEntryActive(entry, now);
|
|
1736
|
+
const tokens = showCache ? entry.totalTokens : entry.inputTokens + entry.outputTokens + (entry.reasoningTokens || 0);
|
|
1737
|
+
const roi = formatRoi(entry, hasPlan);
|
|
1738
|
+
return {
|
|
1739
|
+
entry,
|
|
1740
|
+
isActive,
|
|
1741
|
+
rank: `${entry.userId === config.userId ? "\u2192" : " "}${entry.rank}`,
|
|
1742
|
+
name: `${isActive ? "\u25CF " : ""}${entry.displayName}`,
|
|
1743
|
+
agents: formatAgents(entry),
|
|
1744
|
+
cost: `$${entry.costUSD.toFixed(2)}`,
|
|
1745
|
+
tokens: formatTokens(tokens),
|
|
1746
|
+
roi,
|
|
1747
|
+
turns: String(entry.chatCount),
|
|
1748
|
+
perTurn: entry.chatCount > 0 ? `$${(entry.costUSD / entry.chatCount).toFixed(2)}` : "\u2014"
|
|
1749
|
+
};
|
|
1750
|
+
});
|
|
1735
1751
|
const head = ["#", "Name", "Cost", "Tokens"];
|
|
1736
|
-
const
|
|
1737
|
-
|
|
1752
|
+
const widths = [
|
|
1753
|
+
columnWidth("#", plainRows.map((r) => r.rank), 2, 3),
|
|
1754
|
+
columnWidth("Name", plainRows.map((r) => r.name), 10, 18),
|
|
1755
|
+
columnWidth("Cost", plainRows.map((r) => r.cost), 5, 9),
|
|
1756
|
+
columnWidth("Tokens", plainRows.map((r) => r.tokens), 6, 8)
|
|
1757
|
+
];
|
|
1738
1758
|
if (hasAgents) {
|
|
1739
1759
|
head.splice(2, 0, "Agents");
|
|
1740
|
-
widths.splice(2, 0,
|
|
1760
|
+
widths.splice(2, 0, columnWidth("Agents", plainRows.map((r) => r.agents), 6, 28));
|
|
1741
1761
|
}
|
|
1742
1762
|
if (hasPlan) {
|
|
1743
|
-
head.push("
|
|
1744
|
-
widths.push(
|
|
1763
|
+
head.push("ROI");
|
|
1764
|
+
widths.push(columnWidth("ROI", plainRows.map((r) => r.roi), 3, 11));
|
|
1745
1765
|
}
|
|
1746
1766
|
head.push("Turns", "$/Turn");
|
|
1747
|
-
widths.push(
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
}
|
|
1767
|
+
widths.push(
|
|
1768
|
+
columnWidth("Turns", plainRows.map((r) => r.turns), 3, 6),
|
|
1769
|
+
columnWidth("$/Turn", plainRows.map((r) => r.perTurn), 6, 7)
|
|
1770
|
+
);
|
|
1752
1771
|
const table = new Table({
|
|
1753
1772
|
head: head.map((h) => chalk6.cyan(h)),
|
|
1754
1773
|
style: { head: [], border: [] },
|
|
1755
1774
|
colWidths: widths
|
|
1756
1775
|
});
|
|
1757
|
-
for (const
|
|
1776
|
+
for (const plain of plainRows) {
|
|
1777
|
+
const { entry } = plain;
|
|
1758
1778
|
const isMe = entry.userId === config.userId;
|
|
1759
|
-
const tokens = showCache ? entry.totalTokens : entry.inputTokens + entry.outputTokens + (entry.reasoningTokens || 0);
|
|
1760
1779
|
const marker = isMe ? chalk6.green("\u2192") : " ";
|
|
1761
|
-
const isActive = entry.lastSync && now - new Date(entry.lastSync).getTime() < ACTIVE_THRESHOLD_MS;
|
|
1762
1780
|
const id = (s) => s;
|
|
1763
1781
|
const c = isMe ? chalk6.green : entry.rank === 1 ? chalk6.yellow : id;
|
|
1764
1782
|
const nameC = isMe ? chalk6.green.bold : entry.rank === 1 ? chalk6.yellow.bold : id;
|
|
1765
|
-
const
|
|
1783
|
+
const nameWidth = Math.max(widths[1] - 2, 4);
|
|
1784
|
+
const displayName = plain.isActive ? `${chalk6.green("\u25CF")} ${nameC(truncateDisplay(entry.displayName, Math.max(nameWidth - 2, 1)))}` : nameC(truncateDisplay(entry.displayName, nameWidth));
|
|
1766
1785
|
const row = [
|
|
1767
1786
|
`${marker}${c(String(entry.rank))}`,
|
|
1768
|
-
|
|
1787
|
+
displayName
|
|
1769
1788
|
];
|
|
1770
1789
|
if (hasAgents) {
|
|
1771
|
-
|
|
1790
|
+
const agentWidth = Math.max(widths[2] - 2, 4);
|
|
1791
|
+
row.push(c(truncateDisplay(plain.agents, agentWidth)));
|
|
1772
1792
|
}
|
|
1773
|
-
row.push(c(
|
|
1793
|
+
row.push(c(plain.cost), c(plain.tokens));
|
|
1774
1794
|
if (hasPlan) {
|
|
1775
|
-
|
|
1776
|
-
const price = PLAN_PRICES[entry.plan];
|
|
1777
|
-
const monthly = entry.monthlyCostUSD || 0;
|
|
1778
|
-
const roi = price > 0 ? Math.round(monthly / price * 100) : 0;
|
|
1779
|
-
const roiStr = `$${price}/${roi}%`;
|
|
1780
|
-
const roiC = roi >= 100 ? chalk6.green.bold(roiStr) : roi >= 50 ? chalk6.yellow(roiStr) : chalk6.dim(roiStr);
|
|
1781
|
-
row.push(roiC);
|
|
1782
|
-
} else if (entry.plan === "api") {
|
|
1783
|
-
row.push(chalk6.dim("API"));
|
|
1784
|
-
} else {
|
|
1785
|
-
row.push(chalk6.dim("\u2014"));
|
|
1786
|
-
}
|
|
1787
|
-
}
|
|
1788
|
-
row.push(c(String(entry.chatCount)));
|
|
1789
|
-
row.push(entry.chatCount > 0 ? c(`$${(entry.costUSD / entry.chatCount).toFixed(2)}`) : chalk6.dim("\u2014"));
|
|
1790
|
-
if (hasUsage) {
|
|
1791
|
-
if (entry.usageSnapshot) {
|
|
1792
|
-
const { sevenDay } = entry.usageSnapshot;
|
|
1793
|
-
row.push(c(`${Math.round(sevenDay)}%`));
|
|
1794
|
-
} else {
|
|
1795
|
-
row.push(chalk6.dim("\u2014"));
|
|
1796
|
-
}
|
|
1795
|
+
row.push(colorRoi(plain.roi, entry));
|
|
1797
1796
|
}
|
|
1797
|
+
row.push(c(plain.turns));
|
|
1798
|
+
row.push(entry.chatCount > 0 ? c(plain.perTurn) : chalk6.dim("\u2014"));
|
|
1798
1799
|
table.push(row);
|
|
1799
1800
|
}
|
|
1800
1801
|
console.log(table.toString());
|
|
@@ -1812,9 +1813,76 @@ function printGroup(data, code, period, config, showCache = false, showAll = fal
|
|
|
1812
1813
|
}
|
|
1813
1814
|
}
|
|
1814
1815
|
}
|
|
1815
|
-
function
|
|
1816
|
-
|
|
1817
|
-
|
|
1816
|
+
function isEntryActive(entry, now) {
|
|
1817
|
+
return Boolean(entry.lastSync && now - new Date(entry.lastSync).getTime() < ACTIVE_THRESHOLD_MS);
|
|
1818
|
+
}
|
|
1819
|
+
function formatRoi(entry, hasPlan) {
|
|
1820
|
+
if (!hasPlan) return "";
|
|
1821
|
+
if (entry.plan && entry.plan !== "api") {
|
|
1822
|
+
const price = PLAN_PRICES[entry.plan];
|
|
1823
|
+
const monthly = entry.monthlyCostUSD || 0;
|
|
1824
|
+
const roi = price > 0 ? Math.round(monthly / price * 100) : 0;
|
|
1825
|
+
return `$${price}/${roi}%`;
|
|
1826
|
+
}
|
|
1827
|
+
if (entry.plan === "api") return "API";
|
|
1828
|
+
return "\u2014";
|
|
1829
|
+
}
|
|
1830
|
+
function colorRoi(roiStr, entry) {
|
|
1831
|
+
if (entry.plan && entry.plan !== "api") {
|
|
1832
|
+
const price = PLAN_PRICES[entry.plan];
|
|
1833
|
+
const monthly = entry.monthlyCostUSD || 0;
|
|
1834
|
+
const roi = price > 0 ? Math.round(monthly / price * 100) : 0;
|
|
1835
|
+
return roi >= 100 ? chalk6.green.bold(roiStr) : roi >= 50 ? chalk6.yellow(roiStr) : chalk6.dim(roiStr);
|
|
1836
|
+
}
|
|
1837
|
+
return chalk6.dim(roiStr);
|
|
1838
|
+
}
|
|
1839
|
+
function formatAgents(entry) {
|
|
1840
|
+
if (entry.agentBreakdown && entry.agentBreakdown.length > 0) {
|
|
1841
|
+
if (entry.agentBreakdown.length === 1) {
|
|
1842
|
+
return formatAgentLabel(entry.agentBreakdown[0].source);
|
|
1843
|
+
}
|
|
1844
|
+
const visible = entry.agentBreakdown.slice(0, 2).map((agent) => `${formatAgentLabel(agent.source)} (${agent.percent}%)`);
|
|
1845
|
+
if (entry.agentBreakdown.length > visible.length) {
|
|
1846
|
+
visible.push(`+${entry.agentBreakdown.length - visible.length}`);
|
|
1847
|
+
}
|
|
1848
|
+
return visible.join(", ");
|
|
1849
|
+
}
|
|
1850
|
+
if (!entry.agents || entry.agents.length === 0) return "\u2014";
|
|
1851
|
+
return entry.agents.map(formatAgentLabel).join(", ");
|
|
1852
|
+
}
|
|
1853
|
+
function formatAgentLabel(agent) {
|
|
1854
|
+
return AGENT_LABELS[agent] ?? agent;
|
|
1855
|
+
}
|
|
1856
|
+
function columnWidth(header, values, minContent, maxContent) {
|
|
1857
|
+
const contentWidth = Math.max(visualWidth(header), ...values.map(visualWidth));
|
|
1858
|
+
return Math.min(Math.max(contentWidth, minContent), maxContent) + 2;
|
|
1859
|
+
}
|
|
1860
|
+
function visualWidth(value) {
|
|
1861
|
+
let width = 0;
|
|
1862
|
+
for (const char of value) width += charWidth(char);
|
|
1863
|
+
return width;
|
|
1864
|
+
}
|
|
1865
|
+
function charWidth(char) {
|
|
1866
|
+
const code = char.codePointAt(0) ?? 0;
|
|
1867
|
+
if (code === 0 || code < 32 || code >= 127 && code < 160) return 0;
|
|
1868
|
+
if (code >= 4352 && (code <= 4447 || code === 9001 || code === 9002 || code >= 11904 && code <= 42191 && code !== 12351 || code >= 44032 && code <= 55203 || code >= 63744 && code <= 64255 || code >= 65040 && code <= 65049 || code >= 65072 && code <= 65135 || code >= 65280 && code <= 65376 || code >= 65504 && code <= 65510)) {
|
|
1869
|
+
return 2;
|
|
1870
|
+
}
|
|
1871
|
+
return 1;
|
|
1872
|
+
}
|
|
1873
|
+
function truncateDisplay(value, maxWidth) {
|
|
1874
|
+
if (visualWidth(value) <= maxWidth) return value;
|
|
1875
|
+
if (maxWidth <= 1) return "\u2026";
|
|
1876
|
+
let result = "";
|
|
1877
|
+
let width = 0;
|
|
1878
|
+
const limit = maxWidth - 1;
|
|
1879
|
+
for (const char of value) {
|
|
1880
|
+
const next = charWidth(char);
|
|
1881
|
+
if (width + next > limit) break;
|
|
1882
|
+
result += char;
|
|
1883
|
+
width += next;
|
|
1884
|
+
}
|
|
1885
|
+
return `${result}\u2026`;
|
|
1818
1886
|
}
|
|
1819
1887
|
var SPARK_CHARS = "\u2581\u2582\u2583\u2584\u2585\u2586\u2587";
|
|
1820
1888
|
function renderActivity(data, range) {
|
|
@@ -2160,10 +2228,14 @@ async function hookCommand() {
|
|
|
2160
2228
|
}
|
|
2161
2229
|
|
|
2162
2230
|
// src/index.ts
|
|
2163
|
-
var VERSION = "0.3.
|
|
2231
|
+
var VERSION = "0.3.7";
|
|
2164
2232
|
startUpdateCheck(VERSION);
|
|
2165
2233
|
var program = new Command();
|
|
2166
|
-
|
|
2234
|
+
if (process.argv.slice(2).includes("-v")) {
|
|
2235
|
+
console.log(VERSION);
|
|
2236
|
+
process.exit(0);
|
|
2237
|
+
}
|
|
2238
|
+
program.name("ccclub").description("Claude Code, Codex, OpenCode, Amp, and pi-agent usage leaderboard among friends").version(VERSION);
|
|
2167
2239
|
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);
|
|
2168
2240
|
program.command("init").description("Create a group and get started (first-time setup)").action(initCommand);
|
|
2169
2241
|
program.command("join").description("Join a group with a 6-letter invite code").argument("[invite-code]", "6-character invite code").action((code) => {
|
|
@@ -2181,7 +2253,7 @@ program.command("join").description("Join a group with a 6-letter invite code").
|
|
|
2181
2253
|
}
|
|
2182
2254
|
return joinCommand(code);
|
|
2183
2255
|
});
|
|
2184
|
-
program.command("sync").description("Upload usage
|
|
2256
|
+
program.command("sync").description("Upload local coding agent usage now (auto-sync also runs after setup)").addOption(new Option("-s, --silent").hideHelp()).option("-f, --force", "Re-scan and upload all local usage logs").addOption(new Option("--full", "Same as --force").hideHelp()).action(
|
|
2185
2257
|
(options) => syncCommand({ ...options, full: options.full || options.force })
|
|
2186
2258
|
);
|
|
2187
2259
|
program.command("profile").description("View or update your profile").option("-n, --name <name>", "Set display name").option("--avatar <url>", "Set avatar URL (empty to reset)").option("--public", "Make profile visible in global ranking").option("--private", "Hide from global ranking").option("--plan <plan>", "pro ($20) | max100 ($100) | max200 ($200) | api | none").option("--url <url>", "Link your name to a URL (GitHub, website, etc.)").action(profileCommand);
|
|
@@ -2190,6 +2262,13 @@ program.command("leave").description("Leave a group").argument("[code]", "Group
|
|
|
2190
2262
|
program.command("show-data").description("Preview exactly what gets uploaded (privacy check)").action(showDataCommand);
|
|
2191
2263
|
program.command("hook", { hidden: true }).description("Set up auto-sync hook").action(hookCommand);
|
|
2192
2264
|
program.addHelpText("after", `
|
|
2265
|
+
Setup:
|
|
2266
|
+
ccclub init Create your group and enable auto-sync
|
|
2267
|
+
ccclub join <code> Join a friend's group
|
|
2268
|
+
|
|
2269
|
+
Supported agents:
|
|
2270
|
+
Claude Code, Codex, OpenCode, Amp, pi-agent
|
|
2271
|
+
|
|
2193
2272
|
Leaderboard options:
|
|
2194
2273
|
-d <period> Time window: 1 | 7 | 30 | all (default: today)
|
|
2195
2274
|
-g <code> Show a specific group
|
|
@@ -2198,9 +2277,10 @@ Leaderboard options:
|
|
|
2198
2277
|
--all Show all members including inactive ones
|
|
2199
2278
|
|
|
2200
2279
|
Examples:
|
|
2280
|
+
$ npx ccclub init First-time setup
|
|
2201
2281
|
$ ccclub Show today's leaderboard (default)
|
|
2202
2282
|
$ ccclub -d 1|7|30|all Time window (default: today)
|
|
2203
2283
|
$ ccclub --global Global public leaderboard
|
|
2204
|
-
$ ccclub
|
|
2284
|
+
$ ccclub show-data Preview exactly what gets uploaded
|
|
2205
2285
|
`);
|
|
2206
2286
|
program.parse();
|