ccclub 0.3.9 → 0.3.11

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 +68 -18
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -241,7 +241,7 @@ function isHookInstalled() {
241
241
  import { writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
242
242
  import { join as join3 } from "path";
243
243
  import { homedir as homedir3 } from "os";
244
- import { existsSync as existsSync3 } from "fs";
244
+ import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
245
245
  import { execFile } from "child_process";
246
246
  var PLIST_NAME = "dev.ccclub.sync";
247
247
  var LAUNCH_AGENTS_DIR = join3(homedir3(), "Library", "LaunchAgents");
@@ -278,32 +278,49 @@ function getPlist() {
278
278
  </dict>
279
279
  </plist>`;
280
280
  }
281
+ function isCurrentPlist() {
282
+ if (!existsSync3(PLIST_PATH)) return false;
283
+ try {
284
+ return readFileSync2(PLIST_PATH, "utf-8") === getPlist();
285
+ } catch {
286
+ return false;
287
+ }
288
+ }
289
+ async function launchctl(args) {
290
+ await new Promise((resolve2, reject) => {
291
+ execFile("launchctl", args, (err) => err ? reject(err) : resolve2());
292
+ });
293
+ }
281
294
  async function installHeartbeat() {
282
295
  if (process.platform !== "darwin") {
283
296
  return false;
284
297
  }
285
- if (existsSync3(PLIST_PATH)) {
298
+ if (isCurrentPlist()) {
286
299
  return true;
287
300
  }
288
301
  if (!existsSync3(LAUNCH_AGENTS_DIR)) {
289
302
  await mkdir3(LAUNCH_AGENTS_DIR, { recursive: true });
290
303
  }
304
+ if (existsSync3(PLIST_PATH)) {
305
+ try {
306
+ await launchctl(["unload", PLIST_PATH]);
307
+ } catch {
308
+ }
309
+ }
291
310
  await writeFile3(PLIST_PATH, getPlist());
292
311
  try {
293
- await new Promise((resolve2, reject) => {
294
- execFile("launchctl", ["load", PLIST_PATH], (err) => err ? reject(err) : resolve2());
295
- });
312
+ await launchctl(["load", PLIST_PATH]);
296
313
  } catch {
297
314
  }
298
315
  return true;
299
316
  }
300
317
  function isHeartbeatInstalled() {
301
- return existsSync3(PLIST_PATH);
318
+ return isCurrentPlist();
302
319
  }
303
320
 
304
321
  // src/commands/sync.ts
305
322
  import { readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
306
- import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
323
+ import { existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
307
324
  import { join as join11 } from "path";
308
325
  import { homedir as homedir11 } from "os";
309
326
  import chalk2 from "chalk";
@@ -614,12 +631,21 @@ async function collectCodexUsage() {
614
631
  const entries = [];
615
632
  const turns = [];
616
633
  const seen = /* @__PURE__ */ new Set();
634
+ const seenTurns = /* @__PURE__ */ new Set();
635
+ function addTurn(timestamp, key) {
636
+ if (seenTurns.has(key)) return;
637
+ seenTurns.add(key);
638
+ turns.push({ source, timestamp, key });
639
+ }
617
640
  for (const sessionDir of sessionDirs) {
618
641
  const sessionFiles = files.filter((file) => file.startsWith(`${sessionDir}${sep}`));
619
642
  for (const file of sessionFiles) {
620
643
  const sessionId = sessionIdForFile(sessionDir, file);
621
644
  let previousTotal = null;
622
645
  let currentModel;
646
+ let sawTaskStarted = false;
647
+ const fallbackUserTurns = [];
648
+ const seenFallbackUserTurns = /* @__PURE__ */ new Set();
623
649
  await readJsonlFile(file, (value) => {
624
650
  const record = asRecord(value);
625
651
  if (record == null) return;
@@ -629,6 +655,25 @@ async function collectCodexUsage() {
629
655
  currentModel = extractModelFromPayload(payload) ?? currentModel;
630
656
  return;
631
657
  }
658
+ if (type === "event_msg" && payload?.type === "task_started") {
659
+ const timestamp2 = toIsoTimestamp(payload.started_at ?? record.timestamp);
660
+ if (timestamp2 == null) return;
661
+ const turnId = asString(payload.turn_id);
662
+ const key = turnId != null ? `${source}:${sessionId}:${turnId}` : `${source}:${sessionId}:${timestamp2}:task_started`;
663
+ sawTaskStarted = true;
664
+ addTurn(timestamp2, key);
665
+ return;
666
+ }
667
+ if (type === "event_msg" && payload?.type === "user_message") {
668
+ const timestamp2 = toIsoTimestamp(record.timestamp);
669
+ if (timestamp2 == null) return;
670
+ const key = `${source}:${sessionId}:${timestamp2}:user_message`;
671
+ if (!seenFallbackUserTurns.has(key)) {
672
+ seenFallbackUserTurns.add(key);
673
+ fallbackUserTurns.push({ source, timestamp: timestamp2, key });
674
+ }
675
+ return;
676
+ }
632
677
  if (type !== "event_msg" || payload?.type !== "token_count") return;
633
678
  const timestamp = toIsoTimestamp(record.timestamp);
634
679
  if (timestamp == null) return;
@@ -673,8 +718,10 @@ async function collectCodexUsage() {
673
718
  totalTokens,
674
719
  costUSD: calculateCost(model, inputTokens, rawUsage.outputTokens, 0, cacheReadTokens)
675
720
  });
676
- turns.push({ source, timestamp, key: dedupeKey });
677
721
  });
722
+ if (!sawTaskStarted) {
723
+ for (const turn of fallbackUserTurns) addTurn(turn.timestamp, turn.key);
724
+ }
678
725
  }
679
726
  }
680
727
  return { source, entries, turns, files: files.length, warnings: [] };
@@ -1106,7 +1153,7 @@ import { execSync as execSync2, exec } from "child_process";
1106
1153
  import { promisify } from "util";
1107
1154
  import { userInfo as userInfo2, homedir as homedir10 } from "os";
1108
1155
  import { join as join10 } from "path";
1109
- import { readFileSync as readFileSync2, writeFileSync } from "fs";
1156
+ import { readFileSync as readFileSync3, writeFileSync } from "fs";
1110
1157
  var execAsync = promisify(exec);
1111
1158
  var debug = (...args) => {
1112
1159
  if (process.env.CCCLUB_DEBUG) console.error("[usage-debug]", ...args);
@@ -1115,7 +1162,7 @@ var CACHE_TTL_MS = 5 * 60 * 1e3;
1115
1162
  var CACHE_PATH = join10(homedir10(), CCCLUB_CONFIG_DIR, "usage-cache.json");
1116
1163
  function readCache(allowStale = false) {
1117
1164
  try {
1118
- const raw = readFileSync2(CACHE_PATH, "utf-8");
1165
+ const raw = readFileSync3(CACHE_PATH, "utf-8");
1119
1166
  const { snapshot, fetchedAt } = JSON.parse(raw);
1120
1167
  if (allowStale || Date.now() - fetchedAt < CACHE_TTL_MS) return snapshot;
1121
1168
  } catch {
@@ -1186,7 +1233,7 @@ async function fetchUsageLimits() {
1186
1233
  debug("caught error:", err instanceof Error ? err.message : String(err));
1187
1234
  }
1188
1235
  try {
1189
- const tmp = JSON.parse(readFileSync2("/tmp/sl-claude-usage", "utf-8"));
1236
+ const tmp = JSON.parse(readFileSync3("/tmp/sl-claude-usage", "utf-8"));
1190
1237
  if (typeof tmp.fiveHour === "number" && typeof tmp.sevenDay === "number") {
1191
1238
  const result = { fiveHour: tmp.fiveHour, sevenDay: tmp.sevenDay, snapshotAt: (/* @__PURE__ */ new Date()).toISOString() };
1192
1239
  debug("returning cc-costline cache fallback:", result.fiveHour, result.sevenDay);
@@ -1212,7 +1259,7 @@ function needsFullSync() {
1212
1259
  const path = getSyncVersionPath();
1213
1260
  if (!existsSync4(path)) return true;
1214
1261
  try {
1215
- const stored = readFileSync3(path, "utf-8").trim();
1262
+ const stored = readFileSync4(path, "utf-8").trim();
1216
1263
  return stored !== SYNC_FORMAT_VERSION;
1217
1264
  } catch {
1218
1265
  return true;
@@ -1224,7 +1271,7 @@ async function syncCommand(options) {
1224
1271
  if (options.silent && !options.full) {
1225
1272
  if (existsSync4(timePath)) {
1226
1273
  try {
1227
- const ts = parseInt(readFileSync3(timePath, "utf-8").trim(), 10);
1274
+ const ts = parseInt(readFileSync4(timePath, "utf-8").trim(), 10);
1228
1275
  if (Date.now() - ts < THROTTLE_MS) return;
1229
1276
  } catch {
1230
1277
  }
@@ -1569,7 +1616,7 @@ import Table from "cli-table3";
1569
1616
  import ora4 from "ora";
1570
1617
 
1571
1618
  // src/update-check.ts
1572
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync5 } from "fs";
1619
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync5 } from "fs";
1573
1620
  import { join as join12 } from "path";
1574
1621
  import { homedir as homedir12 } from "os";
1575
1622
  var CHECK_INTERVAL_MS = 12 * 60 * 60 * 1e3;
@@ -1579,7 +1626,7 @@ function startUpdateCheck(currentVersion) {
1579
1626
  if (process.argv.includes("--silent") || process.argv.includes("-s")) return;
1580
1627
  try {
1581
1628
  if (existsSync5(CHECK_FILE)) {
1582
- const ts = parseInt(readFileSync4(CHECK_FILE, "utf-8").trim(), 10);
1629
+ const ts = parseInt(readFileSync5(CHECK_FILE, "utf-8").trim(), 10);
1583
1630
  if (Date.now() - ts < CHECK_INTERVAL_MS) return;
1584
1631
  }
1585
1632
  } catch {
@@ -1747,7 +1794,7 @@ function printGroup(data, code, period, config, showCache = false, showAll = fal
1747
1794
  ${data.group.name}`));
1748
1795
  const periodLabel = { daily: "TODAY", yesterday: "YESTERDAY", weekly: "7 DAYS", monthly: "30 DAYS", "all-time": "ALL TIME" };
1749
1796
  const now = Date.now();
1750
- const activeCount = data.rankings.filter((r) => r.lastSync && now - new Date(r.lastSync).getTime() < ACTIVE_THRESHOLD_MS).length;
1797
+ const activeCount = data.rankings.filter((r) => isEntryActive(r, now)).length;
1751
1798
  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`));
1752
1799
  if (activeCount > 0) {
1753
1800
  console.log(theme.success(` ${activeCount} active`));
@@ -1842,7 +1889,10 @@ function printGroup(data, code, period, config, showCache = false, showAll = fal
1842
1889
  }
1843
1890
  }
1844
1891
  function isEntryActive(entry, now) {
1845
- return Boolean(entry.lastSync && now - new Date(entry.lastSync).getTime() < ACTIVE_THRESHOLD_MS);
1892
+ const value = entry.lastActiveAt || entry.lastSync;
1893
+ if (!value) return false;
1894
+ const activeAt = new Date(value).getTime();
1895
+ return Number.isFinite(activeAt) && now - activeAt < ACTIVE_THRESHOLD_MS;
1846
1896
  }
1847
1897
  function podiumStyle(rank) {
1848
1898
  if (rank === 1) return theme.gold;
@@ -2256,7 +2306,7 @@ async function hookCommand() {
2256
2306
  }
2257
2307
 
2258
2308
  // src/index.ts
2259
- var VERSION = "0.3.9";
2309
+ var VERSION = "0.3.11";
2260
2310
  startUpdateCheck(VERSION);
2261
2311
  var program = new Command();
2262
2312
  if (process.argv.slice(2).includes("-v")) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccclub",
3
- "version": "0.3.9",
3
+ "version": "0.3.11",
4
4
  "type": "module",
5
5
  "description": "Claude Code and Codex leaderboard among friends for coding agent tokens and costs",
6
6
  "bin": {