ccclub 0.3.4 → 0.3.6

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 +116 -39
  2. package/package.json +8 -2
package/dist/index.js CHANGED
@@ -1732,68 +1732,78 @@ function printGroup(data, code, period, config, showCache = false, showAll = fal
1732
1732
  const hasAgents = activeRankings.some(
1733
1733
  (r) => r.agents && r.agents.length > 0 && !(r.agents.length === 1 && r.agents[0] === "claude")
1734
1734
  );
1735
+ const plainRows = activeRankings.map((entry) => {
1736
+ const isActive = isEntryActive(entry, now);
1737
+ const tokens = showCache ? entry.totalTokens : entry.inputTokens + entry.outputTokens + (entry.reasoningTokens || 0);
1738
+ const roi = formatRoi(entry, hasPlan);
1739
+ return {
1740
+ entry,
1741
+ isActive,
1742
+ rank: `${entry.userId === config.userId ? "\u2192" : " "}${entry.rank}`,
1743
+ name: `${isActive ? "\u25CF " : ""}${entry.displayName}`,
1744
+ agents: formatAgents(entry),
1745
+ cost: `$${entry.costUSD.toFixed(2)}`,
1746
+ tokens: formatTokens(tokens),
1747
+ roi,
1748
+ turns: String(entry.chatCount),
1749
+ perTurn: entry.chatCount > 0 ? `$${(entry.costUSD / entry.chatCount).toFixed(2)}` : "\u2014",
1750
+ usage: entry.usageSnapshot ? `${Math.round(entry.usageSnapshot.sevenDay)}%` : "\u2014"
1751
+ };
1752
+ });
1735
1753
  const head = ["#", "Name", "Cost", "Tokens"];
1736
- const hasActive = activeRankings.some((r) => r.lastSync && now - new Date(r.lastSync).getTime() < ACTIVE_THRESHOLD_MS);
1737
- const widths = [5, hasActive ? 30 : 20, 12, 10];
1754
+ const widths = [
1755
+ columnWidth("#", plainRows.map((r) => r.rank), 2, 3),
1756
+ columnWidth("Name", plainRows.map((r) => r.name), 10, 18),
1757
+ columnWidth("Cost", plainRows.map((r) => r.cost), 5, 9),
1758
+ columnWidth("Tokens", plainRows.map((r) => r.tokens), 6, 8)
1759
+ ];
1738
1760
  if (hasAgents) {
1739
1761
  head.splice(2, 0, "Agents");
1740
- widths.splice(2, 0, 16);
1762
+ widths.splice(2, 0, columnWidth("Agents", plainRows.map((r) => r.agents), 6, 24));
1741
1763
  }
1742
1764
  if (hasPlan) {
1743
- head.push("Monthly ROI");
1744
- widths.push(15);
1765
+ head.push("ROI");
1766
+ widths.push(columnWidth("ROI", plainRows.map((r) => r.roi), 3, 11));
1745
1767
  }
1746
1768
  head.push("Turns", "$/Turn");
1747
- widths.push(8, 9);
1769
+ widths.push(
1770
+ columnWidth("Turns", plainRows.map((r) => r.turns), 3, 6),
1771
+ columnWidth("$/Turn", plainRows.map((r) => r.perTurn), 6, 7)
1772
+ );
1748
1773
  if (hasUsage) {
1749
- head.push("Usage 7d");
1750
- widths.push(10);
1774
+ head.push("Usage");
1775
+ widths.push(columnWidth("Usage", plainRows.map((r) => r.usage), 5, 6));
1751
1776
  }
1752
1777
  const table = new Table({
1753
1778
  head: head.map((h) => chalk6.cyan(h)),
1754
1779
  style: { head: [], border: [] },
1755
1780
  colWidths: widths
1756
1781
  });
1757
- for (const entry of activeRankings) {
1782
+ for (const plain of plainRows) {
1783
+ const { entry } = plain;
1758
1784
  const isMe = entry.userId === config.userId;
1759
- const tokens = showCache ? entry.totalTokens : entry.inputTokens + entry.outputTokens + (entry.reasoningTokens || 0);
1760
1785
  const marker = isMe ? chalk6.green("\u2192") : " ";
1761
- const isActive = entry.lastSync && now - new Date(entry.lastSync).getTime() < ACTIVE_THRESHOLD_MS;
1762
1786
  const id = (s) => s;
1763
1787
  const c = isMe ? chalk6.green : entry.rank === 1 ? chalk6.yellow : id;
1764
1788
  const nameC = isMe ? chalk6.green.bold : entry.rank === 1 ? chalk6.yellow.bold : id;
1765
- const displayName = isActive ? `${entry.displayName} ${chalk6.green("(active)")}` : entry.displayName;
1789
+ const nameWidth = Math.max(widths[1] - 2, 4);
1790
+ const displayName = plain.isActive ? `${chalk6.green("\u25CF")} ${nameC(truncateDisplay(entry.displayName, Math.max(nameWidth - 2, 1)))}` : nameC(truncateDisplay(entry.displayName, nameWidth));
1766
1791
  const row = [
1767
1792
  `${marker}${c(String(entry.rank))}`,
1768
- nameC(displayName)
1793
+ displayName
1769
1794
  ];
1770
1795
  if (hasAgents) {
1771
- row.push(c(formatAgents(entry.agents)));
1796
+ const agentWidth = Math.max(widths[2] - 2, 4);
1797
+ row.push(c(truncateDisplay(plain.agents, agentWidth)));
1772
1798
  }
1773
- row.push(c(`$${entry.costUSD.toFixed(2)}`), c(formatTokens(tokens)));
1799
+ row.push(c(plain.cost), c(plain.tokens));
1774
1800
  if (hasPlan) {
1775
- if (entry.plan && entry.plan !== "api") {
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
- }
1801
+ row.push(colorRoi(plain.roi, entry));
1787
1802
  }
1788
- row.push(c(String(entry.chatCount)));
1789
- row.push(entry.chatCount > 0 ? c(`$${(entry.costUSD / entry.chatCount).toFixed(2)}`) : chalk6.dim("\u2014"));
1803
+ row.push(c(plain.turns));
1804
+ row.push(entry.chatCount > 0 ? c(plain.perTurn) : chalk6.dim("\u2014"));
1790
1805
  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
- }
1806
+ row.push(entry.usageSnapshot ? c(plain.usage) : chalk6.dim("\u2014"));
1797
1807
  }
1798
1808
  table.push(row);
1799
1809
  }
@@ -1812,9 +1822,76 @@ function printGroup(data, code, period, config, showCache = false, showAll = fal
1812
1822
  }
1813
1823
  }
1814
1824
  }
1815
- function formatAgents(agents) {
1816
- if (!agents || agents.length === 0) return "\u2014";
1817
- return agents.map((agent) => AGENT_LABELS[agent] ?? agent).join(", ");
1825
+ function isEntryActive(entry, now) {
1826
+ return Boolean(entry.lastSync && now - new Date(entry.lastSync).getTime() < ACTIVE_THRESHOLD_MS);
1827
+ }
1828
+ function formatRoi(entry, hasPlan) {
1829
+ if (!hasPlan) return "";
1830
+ if (entry.plan && entry.plan !== "api") {
1831
+ const price = PLAN_PRICES[entry.plan];
1832
+ const monthly = entry.monthlyCostUSD || 0;
1833
+ const roi = price > 0 ? Math.round(monthly / price * 100) : 0;
1834
+ return `$${price}/${roi}%`;
1835
+ }
1836
+ if (entry.plan === "api") return "API";
1837
+ return "\u2014";
1838
+ }
1839
+ function colorRoi(roiStr, entry) {
1840
+ if (entry.plan && entry.plan !== "api") {
1841
+ const price = PLAN_PRICES[entry.plan];
1842
+ const monthly = entry.monthlyCostUSD || 0;
1843
+ const roi = price > 0 ? Math.round(monthly / price * 100) : 0;
1844
+ return roi >= 100 ? chalk6.green.bold(roiStr) : roi >= 50 ? chalk6.yellow(roiStr) : chalk6.dim(roiStr);
1845
+ }
1846
+ return chalk6.dim(roiStr);
1847
+ }
1848
+ function formatAgents(entry) {
1849
+ if (entry.agentBreakdown && entry.agentBreakdown.length > 0) {
1850
+ if (entry.agentBreakdown.length === 1) {
1851
+ return formatAgentLabel(entry.agentBreakdown[0].source);
1852
+ }
1853
+ const visible = entry.agentBreakdown.slice(0, 2).map((agent) => `${formatAgentLabel(agent.source)} ${agent.percent}%`);
1854
+ if (entry.agentBreakdown.length > visible.length) {
1855
+ visible.push(`+${entry.agentBreakdown.length - visible.length}`);
1856
+ }
1857
+ return visible.join(", ");
1858
+ }
1859
+ if (!entry.agents || entry.agents.length === 0) return "\u2014";
1860
+ return entry.agents.map(formatAgentLabel).join(", ");
1861
+ }
1862
+ function formatAgentLabel(agent) {
1863
+ return AGENT_LABELS[agent] ?? agent;
1864
+ }
1865
+ function columnWidth(header, values, minContent, maxContent) {
1866
+ const contentWidth = Math.max(visualWidth(header), ...values.map(visualWidth));
1867
+ return Math.min(Math.max(contentWidth, minContent), maxContent) + 2;
1868
+ }
1869
+ function visualWidth(value) {
1870
+ let width = 0;
1871
+ for (const char of value) width += charWidth(char);
1872
+ return width;
1873
+ }
1874
+ function charWidth(char) {
1875
+ const code = char.codePointAt(0) ?? 0;
1876
+ if (code === 0 || code < 32 || code >= 127 && code < 160) return 0;
1877
+ 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)) {
1878
+ return 2;
1879
+ }
1880
+ return 1;
1881
+ }
1882
+ function truncateDisplay(value, maxWidth) {
1883
+ if (visualWidth(value) <= maxWidth) return value;
1884
+ if (maxWidth <= 1) return "\u2026";
1885
+ let result = "";
1886
+ let width = 0;
1887
+ const limit = maxWidth - 1;
1888
+ for (const char of value) {
1889
+ const next = charWidth(char);
1890
+ if (width + next > limit) break;
1891
+ result += char;
1892
+ width += next;
1893
+ }
1894
+ return `${result}\u2026`;
1818
1895
  }
1819
1896
  var SPARK_CHARS = "\u2581\u2582\u2583\u2584\u2585\u2586\u2587";
1820
1897
  function renderActivity(data, range) {
@@ -2160,7 +2237,7 @@ async function hookCommand() {
2160
2237
  }
2161
2238
 
2162
2239
  // src/index.ts
2163
- var VERSION = "0.3.4";
2240
+ var VERSION = "0.3.6";
2164
2241
  startUpdateCheck(VERSION);
2165
2242
  var program = new Command();
2166
2243
  program.name("ccclub").description("Coding agent usage leaderboard among friends").version(VERSION, "-v, -V, --version");
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "ccclub",
3
- "version": "0.3.4",
3
+ "version": "0.3.6",
4
4
  "type": "module",
5
- "description": "Coding agent usage leaderboard among friends",
5
+ "description": "Claude Code and Codex leaderboard among friends for coding agent tokens and costs",
6
6
  "bin": {
7
7
  "ccclub": "dist/index.js"
8
8
  },
@@ -35,10 +35,16 @@
35
35
  "keywords": [
36
36
  "claude",
37
37
  "claude-code",
38
+ "claude-code-leaderboard",
38
39
  "codex",
40
+ "codex-leaderboard",
39
41
  "opencode",
40
42
  "amp",
41
43
  "coding-agent",
44
+ "ai-coding-agent",
45
+ "token-usage",
46
+ "usage-tracker",
47
+ "cost-tracking",
42
48
  "usage",
43
49
  "ranking",
44
50
  "leaderboard"