ccclub 0.3.6 → 0.3.8
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 +121 -94
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import { Command, Option } from "commander";
|
|
|
6
6
|
// src/commands/init.ts
|
|
7
7
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
8
8
|
import { stdin, stdout } from "process";
|
|
9
|
-
import
|
|
9
|
+
import chalk5 from "chalk";
|
|
10
10
|
import ora2 from "ora";
|
|
11
11
|
|
|
12
12
|
// src/config.ts
|
|
@@ -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
|
}
|
|
@@ -1355,7 +1355,32 @@ function getLatestBlockStartBySource(blocks) {
|
|
|
1355
1355
|
|
|
1356
1356
|
// src/global-install.ts
|
|
1357
1357
|
import { exec as exec2 } from "child_process";
|
|
1358
|
+
import chalk4 from "chalk";
|
|
1359
|
+
|
|
1360
|
+
// src/theme.ts
|
|
1358
1361
|
import chalk3 from "chalk";
|
|
1362
|
+
var theme = {
|
|
1363
|
+
title: chalk3.hex("#F1EDE7").bold,
|
|
1364
|
+
text: chalk3.hex("#E8E4DE"),
|
|
1365
|
+
muted: chalk3.hex("#8A8480"),
|
|
1366
|
+
faint: chalk3.hex("#5A5550"),
|
|
1367
|
+
brand: chalk3.hex("#D4935E"),
|
|
1368
|
+
link: chalk3.hex("#7AB7C6").underline,
|
|
1369
|
+
linkText: chalk3.hex("#7AB7C6"),
|
|
1370
|
+
success: chalk3.hex("#63B486"),
|
|
1371
|
+
successBold: chalk3.hex("#63B486").bold,
|
|
1372
|
+
gold: chalk3.hex("#D6B56D"),
|
|
1373
|
+
goldBold: chalk3.hex("#D6B56D").bold,
|
|
1374
|
+
silver: chalk3.hex("#AEB7BF"),
|
|
1375
|
+
silverBold: chalk3.hex("#AEB7BF").bold,
|
|
1376
|
+
bronze: chalk3.hex("#C58A61"),
|
|
1377
|
+
bronzeBold: chalk3.hex("#C58A61"),
|
|
1378
|
+
warning: chalk3.hex("#D4A85C"),
|
|
1379
|
+
warningBold: chalk3.hex("#D4A85C").bold,
|
|
1380
|
+
danger: chalk3.hex("#D26A6A")
|
|
1381
|
+
};
|
|
1382
|
+
|
|
1383
|
+
// src/global-install.ts
|
|
1359
1384
|
function run(cmd) {
|
|
1360
1385
|
return new Promise((resolve2) => {
|
|
1361
1386
|
exec2(cmd, (err, stdout5) => resolve2(err ? "" : stdout5.trim()));
|
|
@@ -1364,13 +1389,13 @@ function run(cmd) {
|
|
|
1364
1389
|
async function ensureGlobalInstall() {
|
|
1365
1390
|
const globalList = await run("npm list -g ccclub --depth=0");
|
|
1366
1391
|
if (globalList.includes("ccclub@")) return;
|
|
1367
|
-
console.log(
|
|
1392
|
+
console.log(chalk4.dim("\n Installing ccclub globally so you can run it directly..."));
|
|
1368
1393
|
const result = await run("npm install -g ccclub");
|
|
1369
1394
|
if (result) {
|
|
1370
|
-
console.log(
|
|
1395
|
+
console.log(theme.success(" Done!") + chalk4.dim(" You can now use ") + theme.text("ccclub") + chalk4.dim(" directly."));
|
|
1371
1396
|
} else {
|
|
1372
|
-
console.log(
|
|
1373
|
-
console.log(
|
|
1397
|
+
console.log(chalk4.dim(" Could not auto-install. Run manually:"));
|
|
1398
|
+
console.log(theme.text(" npm install -g ccclub"));
|
|
1374
1399
|
}
|
|
1375
1400
|
}
|
|
1376
1401
|
|
|
@@ -1378,28 +1403,28 @@ async function ensureGlobalInstall() {
|
|
|
1378
1403
|
async function initCommand() {
|
|
1379
1404
|
const existing = await loadConfig();
|
|
1380
1405
|
if (existing) {
|
|
1381
|
-
console.log(
|
|
1406
|
+
console.log(chalk5.yellow("Already initialized!"));
|
|
1382
1407
|
console.log(` User: ${existing.displayName}`);
|
|
1383
1408
|
console.log(` Groups: ${existing.groups.join(", ") || "(none)"}`);
|
|
1384
1409
|
if (!isHookInstalled()) {
|
|
1385
1410
|
const hookOk = await installHook();
|
|
1386
|
-
if (hookOk) console.log(
|
|
1411
|
+
if (hookOk) console.log(chalk5.green(" Auto-sync hook installed!"));
|
|
1387
1412
|
}
|
|
1388
1413
|
if (!isHeartbeatInstalled()) {
|
|
1389
1414
|
const heartbeatOk = await installHeartbeat();
|
|
1390
|
-
if (heartbeatOk) console.log(
|
|
1415
|
+
if (heartbeatOk) console.log(chalk5.green(" Background sync installed!"));
|
|
1391
1416
|
}
|
|
1392
|
-
console.log(
|
|
1417
|
+
console.log(chalk5.dim('\n Run "ccclub" to see the leaderboard'));
|
|
1393
1418
|
return;
|
|
1394
1419
|
}
|
|
1395
1420
|
const rl = createInterface2({ input: stdin, output: stdout });
|
|
1396
1421
|
try {
|
|
1397
1422
|
const defaultName = getDefaultDisplayName();
|
|
1398
|
-
const prompt = defaultName ?
|
|
1423
|
+
const prompt = defaultName ? chalk5.bold(`Your display name (${defaultName}): `) : chalk5.bold("Your display name: ");
|
|
1399
1424
|
const input = await rl.question(prompt);
|
|
1400
1425
|
const displayName = input.trim() || defaultName || "";
|
|
1401
1426
|
if (!displayName) {
|
|
1402
|
-
console.error(
|
|
1427
|
+
console.error(chalk5.red("Name cannot be empty"));
|
|
1403
1428
|
return;
|
|
1404
1429
|
}
|
|
1405
1430
|
const spinner = ora2("Setting up...").start();
|
|
@@ -1436,17 +1461,17 @@ async function initCommand() {
|
|
|
1436
1461
|
]);
|
|
1437
1462
|
spinner.succeed("ccclub initialized!");
|
|
1438
1463
|
if (!hookOk) {
|
|
1439
|
-
console.log(
|
|
1464
|
+
console.log(chalk5.dim(' Tip: run "ccclub hook" to set up auto-sync'));
|
|
1440
1465
|
}
|
|
1441
1466
|
if (!heartbeatOk) {
|
|
1442
|
-
console.log(
|
|
1467
|
+
console.log(chalk5.dim(' Tip: run "ccclub sync" manually to refresh non-Claude agent usage'));
|
|
1443
1468
|
}
|
|
1444
1469
|
console.log("");
|
|
1445
|
-
console.log(
|
|
1470
|
+
console.log(chalk5.bold(" Invite friends to compete:"));
|
|
1446
1471
|
console.log("");
|
|
1447
|
-
console.log(` ${
|
|
1472
|
+
console.log(` ${theme.link(`${apiUrl}/invite/${data.groupCode}`)}`);
|
|
1448
1473
|
console.log("");
|
|
1449
|
-
console.log(
|
|
1474
|
+
console.log(chalk5.dim(` or: npx ccclub join ${data.groupCode}`));
|
|
1450
1475
|
console.log("");
|
|
1451
1476
|
await doSync(true);
|
|
1452
1477
|
await ensureGlobalInstall();
|
|
@@ -1457,14 +1482,14 @@ async function initCommand() {
|
|
|
1457
1482
|
}
|
|
1458
1483
|
function printQuickStart() {
|
|
1459
1484
|
console.log("");
|
|
1460
|
-
console.log(
|
|
1485
|
+
console.log(chalk5.dim(" Run ") + theme.text("ccclub") + chalk5.dim(" to see the leaderboard. ") + theme.text("ccclub -h") + chalk5.dim(" for all commands."));
|
|
1461
1486
|
console.log("");
|
|
1462
1487
|
}
|
|
1463
1488
|
|
|
1464
1489
|
// src/commands/join.ts
|
|
1465
1490
|
import { createInterface as createInterface3 } from "readline/promises";
|
|
1466
1491
|
import { stdin as stdin2, stdout as stdout2 } from "process";
|
|
1467
|
-
import
|
|
1492
|
+
import chalk6 from "chalk";
|
|
1468
1493
|
import ora3 from "ora";
|
|
1469
1494
|
async function joinCommand(inviteCode) {
|
|
1470
1495
|
let config = await loadConfig();
|
|
@@ -1478,11 +1503,11 @@ async function joinCommand(inviteCode) {
|
|
|
1478
1503
|
const rl = createInterface3({ input: stdin2, output: stdout2 });
|
|
1479
1504
|
try {
|
|
1480
1505
|
const defaultName = getDefaultDisplayName();
|
|
1481
|
-
const prompt = defaultName ?
|
|
1506
|
+
const prompt = defaultName ? chalk6.bold(`Your display name (${defaultName}): `) : chalk6.bold("Your display name: ");
|
|
1482
1507
|
const input = await rl.question(prompt);
|
|
1483
1508
|
displayName = input.trim() || defaultName || "";
|
|
1484
1509
|
if (!displayName) {
|
|
1485
|
-
console.error(
|
|
1510
|
+
console.error(chalk6.red("Name cannot be empty"));
|
|
1486
1511
|
return;
|
|
1487
1512
|
}
|
|
1488
1513
|
} finally {
|
|
@@ -1531,12 +1556,11 @@ async function joinCommand(inviteCode) {
|
|
|
1531
1556
|
await ensureGlobalInstall();
|
|
1532
1557
|
}
|
|
1533
1558
|
console.log("");
|
|
1534
|
-
console.log(
|
|
1559
|
+
console.log(chalk6.dim(" Run ") + chalk6.white("ccclub") + chalk6.dim(" to see the leaderboard. ") + chalk6.white("ccclub -h") + chalk6.dim(" for all commands."));
|
|
1535
1560
|
console.log("");
|
|
1536
1561
|
}
|
|
1537
1562
|
|
|
1538
1563
|
// src/commands/rank.ts
|
|
1539
|
-
import chalk6 from "chalk";
|
|
1540
1564
|
import Table from "cli-table3";
|
|
1541
1565
|
import ora4 from "ora";
|
|
1542
1566
|
|
|
@@ -1595,11 +1619,11 @@ async function rankCommand(options) {
|
|
|
1595
1619
|
Usage: ccclub -d <period>
|
|
1596
1620
|
|
|
1597
1621
|
Options:
|
|
1598
|
-
${
|
|
1599
|
-
${
|
|
1600
|
-
${
|
|
1601
|
-
${
|
|
1602
|
-
${
|
|
1622
|
+
${theme.text("ccclub -d 1")} Yesterday
|
|
1623
|
+
${theme.text("ccclub -d 7")} Last 7 days
|
|
1624
|
+
${theme.text("ccclub -d 30")} Last 30 days
|
|
1625
|
+
${theme.text("ccclub -d all")} All time
|
|
1626
|
+
${theme.text("ccclub")} Today (default)
|
|
1603
1627
|
`;
|
|
1604
1628
|
if (options.days) {
|
|
1605
1629
|
if (options.days === true) {
|
|
@@ -1609,7 +1633,7 @@ async function rankCommand(options) {
|
|
|
1609
1633
|
const DAYS_MAP = { "1": "yesterday", "7": "weekly", "30": "monthly", "all": "all-time" };
|
|
1610
1634
|
const mapped = DAYS_MAP[options.days];
|
|
1611
1635
|
if (!mapped) {
|
|
1612
|
-
console.log(
|
|
1636
|
+
console.log(theme.danger(`
|
|
1613
1637
|
Unknown value: -d ${options.days}`));
|
|
1614
1638
|
console.log(DAYS_HINT);
|
|
1615
1639
|
return;
|
|
@@ -1633,7 +1657,7 @@ async function rankCommand(options) {
|
|
|
1633
1657
|
codes = config.groups.length > 0 ? config.groups : [];
|
|
1634
1658
|
}
|
|
1635
1659
|
if (codes.length === 0) {
|
|
1636
|
-
console.log(
|
|
1660
|
+
console.log(theme.danger("No group found. Run 'ccclub init' or 'ccclub join <code>' first."));
|
|
1637
1661
|
return;
|
|
1638
1662
|
}
|
|
1639
1663
|
const localUsagePromise = fetchUsageLimits().catch(() => null);
|
|
@@ -1671,7 +1695,7 @@ async function rankCommand(options) {
|
|
|
1671
1695
|
for (let i = 0; i < groupResults.length; i++) {
|
|
1672
1696
|
const { code, rankData, activityData, range } = groupResults[i];
|
|
1673
1697
|
if (!rankData) {
|
|
1674
|
-
console.log(
|
|
1698
|
+
console.log(theme.danger(`
|
|
1675
1699
|
Couldn't load leaderboard for ${code}`));
|
|
1676
1700
|
continue;
|
|
1677
1701
|
}
|
|
@@ -1683,10 +1707,10 @@ async function rankCommand(options) {
|
|
|
1683
1707
|
if (activityData) renderActivity(activityData, range);
|
|
1684
1708
|
if (i < groupResults.length - 1) console.log("");
|
|
1685
1709
|
}
|
|
1686
|
-
console.log(
|
|
1710
|
+
console.log(theme.muted("\n Tokens = input + output + reasoning ") + theme.warning("(cache excluded)") + theme.muted(". Use ") + theme.text("--cache") + theme.muted(" to include cache tokens."));
|
|
1687
1711
|
const update = await getUpdateResult();
|
|
1688
1712
|
if (update) {
|
|
1689
|
-
console.log(
|
|
1713
|
+
console.log(theme.warningBold("\n Update available") + theme.muted(`: ${update.current} \u2192 ${update.latest} Run `) + theme.linkText("npm i -g ccclub@latest"));
|
|
1690
1714
|
}
|
|
1691
1715
|
} catch (err) {
|
|
1692
1716
|
spinner.fail(`Error: ${formatFetchError(err)}`);
|
|
@@ -1709,26 +1733,25 @@ function formatTokens(n) {
|
|
|
1709
1733
|
}
|
|
1710
1734
|
function printGroup(data, code, period, config, showCache = false, showAll = false) {
|
|
1711
1735
|
if (data.rankings.length === 0) {
|
|
1712
|
-
console.log(
|
|
1736
|
+
console.log(theme.title(`
|
|
1713
1737
|
${data.group.name}`));
|
|
1714
|
-
console.log(
|
|
1715
|
-
console.log(
|
|
1738
|
+
console.log(theme.warning(" No data for this period yet"));
|
|
1739
|
+
console.log(theme.muted(" Sync your data first: ccclub sync"));
|
|
1716
1740
|
return;
|
|
1717
1741
|
}
|
|
1718
|
-
console.log(
|
|
1742
|
+
console.log(theme.title(`
|
|
1719
1743
|
${data.group.name}`));
|
|
1720
1744
|
const periodLabel = { daily: "TODAY", yesterday: "YESTERDAY", weekly: "7 DAYS", monthly: "30 DAYS", "all-time": "ALL TIME" };
|
|
1721
1745
|
const now = Date.now();
|
|
1722
1746
|
const activeCount = data.rankings.filter((r) => r.lastSync && now - new Date(r.lastSync).getTime() < ACTIVE_THRESHOLD_MS).length;
|
|
1723
|
-
console.log(
|
|
1747
|
+
console.log(theme.muted(` ${periodLabel[period] || period.toUpperCase()} \xB7 ${data.start.slice(0, 10)} \u2192 ${data.end.slice(0, 10)} \xB7 ${data.group.memberCount} members`));
|
|
1724
1748
|
if (activeCount > 0) {
|
|
1725
|
-
console.log(
|
|
1749
|
+
console.log(theme.success(` ${activeCount} active`));
|
|
1726
1750
|
}
|
|
1727
1751
|
console.log("");
|
|
1728
1752
|
const activeRankings = showAll || data.rankings.length <= 15 ? data.rankings : data.rankings.filter((r) => r.costUSD > 0 || r.userId === config.userId);
|
|
1729
1753
|
const hiddenCount = data.rankings.length - activeRankings.length;
|
|
1730
1754
|
const hasPlan = activeRankings.some((r) => r.plan);
|
|
1731
|
-
const hasUsage = activeRankings.some((r) => r.usageSnapshot);
|
|
1732
1755
|
const hasAgents = activeRankings.some(
|
|
1733
1756
|
(r) => r.agents && r.agents.length > 0 && !(r.agents.length === 1 && r.agents[0] === "claude")
|
|
1734
1757
|
);
|
|
@@ -1746,8 +1769,7 @@ function printGroup(data, code, period, config, showCache = false, showAll = fal
|
|
|
1746
1769
|
tokens: formatTokens(tokens),
|
|
1747
1770
|
roi,
|
|
1748
1771
|
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"
|
|
1772
|
+
perTurn: entry.chatCount > 0 ? `$${(entry.costUSD / entry.chatCount).toFixed(2)}` : "\u2014"
|
|
1751
1773
|
};
|
|
1752
1774
|
});
|
|
1753
1775
|
const head = ["#", "Name", "Cost", "Tokens"];
|
|
@@ -1759,7 +1781,7 @@ function printGroup(data, code, period, config, showCache = false, showAll = fal
|
|
|
1759
1781
|
];
|
|
1760
1782
|
if (hasAgents) {
|
|
1761
1783
|
head.splice(2, 0, "Agents");
|
|
1762
|
-
widths.splice(2, 0, columnWidth("Agents", plainRows.map((r) => r.agents), 6,
|
|
1784
|
+
widths.splice(2, 0, columnWidth("Agents", plainRows.map((r) => r.agents), 6, 28));
|
|
1763
1785
|
}
|
|
1764
1786
|
if (hasPlan) {
|
|
1765
1787
|
head.push("ROI");
|
|
@@ -1770,61 +1792,66 @@ function printGroup(data, code, period, config, showCache = false, showAll = fal
|
|
|
1770
1792
|
columnWidth("Turns", plainRows.map((r) => r.turns), 3, 6),
|
|
1771
1793
|
columnWidth("$/Turn", plainRows.map((r) => r.perTurn), 6, 7)
|
|
1772
1794
|
);
|
|
1773
|
-
if (hasUsage) {
|
|
1774
|
-
head.push("Usage");
|
|
1775
|
-
widths.push(columnWidth("Usage", plainRows.map((r) => r.usage), 5, 6));
|
|
1776
|
-
}
|
|
1777
1795
|
const table = new Table({
|
|
1778
|
-
head: head.map((h) =>
|
|
1779
|
-
style: { head: [], border: [] },
|
|
1796
|
+
head: head.map((h) => theme.linkText(h)),
|
|
1797
|
+
style: { head: [], border: ["gray"] },
|
|
1780
1798
|
colWidths: widths
|
|
1781
1799
|
});
|
|
1782
1800
|
for (const plain of plainRows) {
|
|
1783
1801
|
const { entry } = plain;
|
|
1784
1802
|
const isMe = entry.userId === config.userId;
|
|
1785
|
-
const
|
|
1786
|
-
const
|
|
1787
|
-
const
|
|
1788
|
-
const
|
|
1803
|
+
const rowStyle = isMe ? theme.success : podiumStyle(entry.rank);
|
|
1804
|
+
const nameStyle = isMe ? theme.successBold : podiumNameStyle(entry.rank);
|
|
1805
|
+
const rankStyle = podiumNameStyle(entry.rank);
|
|
1806
|
+
const marker = isMe ? theme.success("\u2192") : " ";
|
|
1789
1807
|
const nameWidth = Math.max(widths[1] - 2, 4);
|
|
1790
|
-
const displayName = plain.isActive ? `${
|
|
1808
|
+
const displayName = plain.isActive ? `${theme.success("\u25CF")} ${nameStyle(truncateDisplay(entry.displayName, Math.max(nameWidth - 2, 1)))}` : nameStyle(truncateDisplay(entry.displayName, nameWidth));
|
|
1791
1809
|
const row = [
|
|
1792
|
-
`${marker}${
|
|
1810
|
+
`${marker}${rankStyle(String(entry.rank))}`,
|
|
1793
1811
|
displayName
|
|
1794
1812
|
];
|
|
1795
1813
|
if (hasAgents) {
|
|
1796
1814
|
const agentWidth = Math.max(widths[2] - 2, 4);
|
|
1797
|
-
row.push(
|
|
1815
|
+
row.push(rowStyle(truncateDisplay(plain.agents, agentWidth)));
|
|
1798
1816
|
}
|
|
1799
|
-
row.push(
|
|
1817
|
+
row.push(rowStyle(plain.cost), rowStyle(plain.tokens));
|
|
1800
1818
|
if (hasPlan) {
|
|
1801
1819
|
row.push(colorRoi(plain.roi, entry));
|
|
1802
1820
|
}
|
|
1803
|
-
row.push(
|
|
1804
|
-
row.push(entry.chatCount > 0 ?
|
|
1805
|
-
if (hasUsage) {
|
|
1806
|
-
row.push(entry.usageSnapshot ? c(plain.usage) : chalk6.dim("\u2014"));
|
|
1807
|
-
}
|
|
1821
|
+
row.push(rowStyle(plain.turns));
|
|
1822
|
+
row.push(entry.chatCount > 0 ? rowStyle(plain.perTurn) : theme.faint("\u2014"));
|
|
1808
1823
|
table.push(row);
|
|
1809
1824
|
}
|
|
1810
1825
|
console.log(table.toString());
|
|
1811
1826
|
if (hiddenCount > 0) {
|
|
1812
|
-
console.log(
|
|
1827
|
+
console.log(theme.muted(` ${hiddenCount} inactive member${hiddenCount > 1 ? "s" : ""} hidden \xB7 ccclub --all to show`));
|
|
1813
1828
|
}
|
|
1814
|
-
console.log(
|
|
1829
|
+
console.log(theme.muted(" Dashboard: ") + theme.link(`${config.apiUrl}/g/${code}`));
|
|
1815
1830
|
if (code !== "global") {
|
|
1816
|
-
console.log(
|
|
1831
|
+
console.log(theme.muted(" Invite: ") + theme.link(`${config.apiUrl}/invite/${code}`));
|
|
1817
1832
|
}
|
|
1818
1833
|
if (hasPlan) {
|
|
1819
1834
|
const me = data.rankings.find((r) => r.userId === config.userId);
|
|
1820
1835
|
if (me && !me.plan) {
|
|
1821
|
-
console.log(
|
|
1836
|
+
console.log(theme.muted(" Set your plan: ") + theme.text("ccclub profile --plan pro|max100|max200|api"));
|
|
1822
1837
|
}
|
|
1823
1838
|
}
|
|
1824
1839
|
}
|
|
1825
1840
|
function isEntryActive(entry, now) {
|
|
1826
1841
|
return Boolean(entry.lastSync && now - new Date(entry.lastSync).getTime() < ACTIVE_THRESHOLD_MS);
|
|
1827
1842
|
}
|
|
1843
|
+
function podiumStyle(rank) {
|
|
1844
|
+
if (rank === 1) return theme.gold;
|
|
1845
|
+
if (rank === 2) return theme.silver;
|
|
1846
|
+
if (rank === 3) return theme.bronze;
|
|
1847
|
+
return theme.text;
|
|
1848
|
+
}
|
|
1849
|
+
function podiumNameStyle(rank) {
|
|
1850
|
+
if (rank === 1) return theme.goldBold;
|
|
1851
|
+
if (rank === 2) return theme.silverBold;
|
|
1852
|
+
if (rank === 3) return theme.bronzeBold;
|
|
1853
|
+
return theme.text;
|
|
1854
|
+
}
|
|
1828
1855
|
function formatRoi(entry, hasPlan) {
|
|
1829
1856
|
if (!hasPlan) return "";
|
|
1830
1857
|
if (entry.plan && entry.plan !== "api") {
|
|
@@ -1841,16 +1868,16 @@ function colorRoi(roiStr, entry) {
|
|
|
1841
1868
|
const price = PLAN_PRICES[entry.plan];
|
|
1842
1869
|
const monthly = entry.monthlyCostUSD || 0;
|
|
1843
1870
|
const roi = price > 0 ? Math.round(monthly / price * 100) : 0;
|
|
1844
|
-
return roi >= 100 ?
|
|
1871
|
+
return roi >= 100 ? theme.successBold(roiStr) : roi >= 50 ? theme.warning(roiStr) : theme.faint(roiStr);
|
|
1845
1872
|
}
|
|
1846
|
-
return
|
|
1873
|
+
return theme.faint(roiStr);
|
|
1847
1874
|
}
|
|
1848
1875
|
function formatAgents(entry) {
|
|
1849
1876
|
if (entry.agentBreakdown && entry.agentBreakdown.length > 0) {
|
|
1850
1877
|
if (entry.agentBreakdown.length === 1) {
|
|
1851
1878
|
return formatAgentLabel(entry.agentBreakdown[0].source);
|
|
1852
1879
|
}
|
|
1853
|
-
const visible = entry.agentBreakdown.slice(0, 2).map((agent) => `${formatAgentLabel(agent.source)} ${agent.percent}
|
|
1880
|
+
const visible = entry.agentBreakdown.slice(0, 2).map((agent) => `${formatAgentLabel(agent.source)} (${agent.percent}%)`);
|
|
1854
1881
|
if (entry.agentBreakdown.length > visible.length) {
|
|
1855
1882
|
visible.push(`+${entry.agentBreakdown.length - visible.length}`);
|
|
1856
1883
|
}
|
|
@@ -1917,7 +1944,7 @@ function renderActivity(data, range) {
|
|
|
1917
1944
|
}
|
|
1918
1945
|
}
|
|
1919
1946
|
if (globalMax === 0) globalMax = 1;
|
|
1920
|
-
console.log(
|
|
1947
|
+
console.log(theme.muted(`
|
|
1921
1948
|
Activity (${range})`));
|
|
1922
1949
|
for (let i = 0; i < active.length; i++) {
|
|
1923
1950
|
const user = active[i];
|
|
@@ -1929,22 +1956,10 @@ function renderActivity(data, range) {
|
|
|
1929
1956
|
return SPARK_CHARS[idx];
|
|
1930
1957
|
}).join("");
|
|
1931
1958
|
const total = user.blocks.reduce((s, b) => s + b.cost, 0);
|
|
1932
|
-
const displayWidth = [...user.displayName].reduce((w, ch) => w + (ch.charCodeAt(0) > 127 ? 2 : 1), 0);
|
|
1933
1959
|
const maxWidth = 12;
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
let cut = 0;
|
|
1938
|
-
for (const ch of name) {
|
|
1939
|
-
const cw = ch.charCodeAt(0) > 127 ? 2 : 1;
|
|
1940
|
-
if (w + cw > maxWidth) break;
|
|
1941
|
-
w += cw;
|
|
1942
|
-
cut++;
|
|
1943
|
-
}
|
|
1944
|
-
name = [...name].slice(0, cut).join("");
|
|
1945
|
-
}
|
|
1946
|
-
const pad = " ".repeat(Math.max(0, maxWidth - displayWidth));
|
|
1947
|
-
console.log(` ${chalk6.dim(name + pad)} ${spark} ${chalk6.dim("$" + total.toFixed(2))}`);
|
|
1960
|
+
const name = truncateDisplay(user.displayName, maxWidth);
|
|
1961
|
+
const pad = " ".repeat(Math.max(0, maxWidth - visualWidth(name)));
|
|
1962
|
+
console.log(` ${theme.muted(name + pad)} ${theme.brand(spark)} ${theme.muted("$" + total.toFixed(2))}`);
|
|
1948
1963
|
}
|
|
1949
1964
|
const axisArr = new Array(bucketCount).fill(" ");
|
|
1950
1965
|
if (range === "24h" || range === "yesterday") {
|
|
@@ -1967,7 +1982,7 @@ function renderActivity(data, range) {
|
|
|
1967
1982
|
for (let c = 0; c < label.length && b + c < bucketCount; c++) axisArr[b + c] = label[c];
|
|
1968
1983
|
}
|
|
1969
1984
|
}
|
|
1970
|
-
console.log(
|
|
1985
|
+
console.log(theme.faint(" " + " ".repeat(12) + " " + axisArr.join("")));
|
|
1971
1986
|
}
|
|
1972
1987
|
|
|
1973
1988
|
// src/commands/profile.ts
|
|
@@ -2137,7 +2152,7 @@ async function createGroupCommand() {
|
|
|
2137
2152
|
}
|
|
2138
2153
|
spinner.succeed(`Created "${data.groupName}"`);
|
|
2139
2154
|
console.log("");
|
|
2140
|
-
console.log(` ${
|
|
2155
|
+
console.log(` ${theme.link(`${config.apiUrl}/invite/${data.groupCode}`)}`);
|
|
2141
2156
|
console.log("");
|
|
2142
2157
|
console.log(chalk9.dim(` or: npx ccclub join ${data.groupCode}`));
|
|
2143
2158
|
} finally {
|
|
@@ -2237,10 +2252,14 @@ async function hookCommand() {
|
|
|
2237
2252
|
}
|
|
2238
2253
|
|
|
2239
2254
|
// src/index.ts
|
|
2240
|
-
var VERSION = "0.3.
|
|
2255
|
+
var VERSION = "0.3.8";
|
|
2241
2256
|
startUpdateCheck(VERSION);
|
|
2242
2257
|
var program = new Command();
|
|
2243
|
-
|
|
2258
|
+
if (process.argv.slice(2).includes("-v")) {
|
|
2259
|
+
console.log(VERSION);
|
|
2260
|
+
process.exit(0);
|
|
2261
|
+
}
|
|
2262
|
+
program.name("ccclub").description("Claude Code, Codex, OpenCode, Amp, and pi-agent usage leaderboard among friends").version(VERSION);
|
|
2244
2263
|
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);
|
|
2245
2264
|
program.command("init").description("Create a group and get started (first-time setup)").action(initCommand);
|
|
2246
2265
|
program.command("join").description("Join a group with a 6-letter invite code").argument("[invite-code]", "6-character invite code").action((code) => {
|
|
@@ -2258,7 +2277,7 @@ program.command("join").description("Join a group with a 6-letter invite code").
|
|
|
2258
2277
|
}
|
|
2259
2278
|
return joinCommand(code);
|
|
2260
2279
|
});
|
|
2261
|
-
program.command("sync").description("Upload usage
|
|
2280
|
+
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(
|
|
2262
2281
|
(options) => syncCommand({ ...options, full: options.full || options.force })
|
|
2263
2282
|
);
|
|
2264
2283
|
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);
|
|
@@ -2267,6 +2286,13 @@ program.command("leave").description("Leave a group").argument("[code]", "Group
|
|
|
2267
2286
|
program.command("show-data").description("Preview exactly what gets uploaded (privacy check)").action(showDataCommand);
|
|
2268
2287
|
program.command("hook", { hidden: true }).description("Set up auto-sync hook").action(hookCommand);
|
|
2269
2288
|
program.addHelpText("after", `
|
|
2289
|
+
Setup:
|
|
2290
|
+
ccclub init Create your group and enable auto-sync
|
|
2291
|
+
ccclub join <code> Join a friend's group
|
|
2292
|
+
|
|
2293
|
+
Supported agents:
|
|
2294
|
+
Claude Code, Codex, OpenCode, Amp, pi-agent
|
|
2295
|
+
|
|
2270
2296
|
Leaderboard options:
|
|
2271
2297
|
-d <period> Time window: 1 | 7 | 30 | all (default: today)
|
|
2272
2298
|
-g <code> Show a specific group
|
|
@@ -2275,9 +2301,10 @@ Leaderboard options:
|
|
|
2275
2301
|
--all Show all members including inactive ones
|
|
2276
2302
|
|
|
2277
2303
|
Examples:
|
|
2304
|
+
$ npx ccclub init First-time setup
|
|
2278
2305
|
$ ccclub Show today's leaderboard (default)
|
|
2279
2306
|
$ ccclub -d 1|7|30|all Time window (default: today)
|
|
2280
2307
|
$ ccclub --global Global public leaderboard
|
|
2281
|
-
$ ccclub
|
|
2308
|
+
$ ccclub show-data Preview exactly what gets uploaded
|
|
2282
2309
|
`);
|
|
2283
2310
|
program.parse();
|