ccclub 0.2.2 → 0.2.3

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 +36 -10
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -175,10 +175,12 @@ async function collectUsageEntries() {
175
175
  const projectsDir = join3(homedir3(), CLAUDE_PROJECTS_DIR);
176
176
  const files = await glob("**/*.jsonl", { cwd: projectsDir, absolute: true });
177
177
  if (files.length === 0) {
178
- return [];
178
+ return { entries: [], humanTurns: [] };
179
179
  }
180
180
  const entries = [];
181
+ const humanTurns = [];
181
182
  const seen = /* @__PURE__ */ new Set();
183
+ const seenHuman = /* @__PURE__ */ new Set();
182
184
  for (const file of files) {
183
185
  const content = await readFile3(file, "utf-8");
184
186
  const lines = content.split("\n").filter((l) => l.trim());
@@ -189,6 +191,14 @@ async function collectUsageEntries() {
189
191
  } catch {
190
192
  continue;
191
193
  }
194
+ if (parsed.type === "human" && parsed.timestamp) {
195
+ const humanKey = `${parsed.sessionId || ""}:${parsed.timestamp}`;
196
+ if (!seenHuman.has(humanKey)) {
197
+ seenHuman.add(humanKey);
198
+ humanTurns.push(parsed.timestamp);
199
+ }
200
+ continue;
201
+ }
192
202
  if (parsed.type !== "assistant" || !parsed.message?.usage) {
193
203
  continue;
194
204
  }
@@ -217,7 +227,8 @@ async function collectUsageEntries() {
217
227
  }
218
228
  }
219
229
  entries.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
220
- return entries;
230
+ humanTurns.sort();
231
+ return { entries, humanTurns };
221
232
  }
222
233
 
223
234
  // src/aggregator.ts
@@ -226,12 +237,26 @@ function floorToHour(date) {
226
237
  floored.setUTCMinutes(0, 0, 0);
227
238
  return floored;
228
239
  }
229
- function aggregateToBlocks(entries) {
240
+ function aggregateToBlocks(entries, humanTurns = []) {
230
241
  if (entries.length === 0) return [];
242
+ const humanTurnMs = humanTurns.map((t) => new Date(t).getTime());
231
243
  const blocks = [];
232
244
  let blockStart = floorToHour(new Date(entries[0].timestamp));
233
245
  let blockEnd = new Date(blockStart.getTime() + BLOCK_DURATION_MS);
234
246
  let currentBlock = [];
247
+ let humanIdx = 0;
248
+ function countHumanTurns() {
249
+ const startMs = blockStart.getTime();
250
+ const endMs = blockEnd.getTime();
251
+ let count = 0;
252
+ while (humanIdx < humanTurnMs.length && humanTurnMs[humanIdx] < startMs) humanIdx++;
253
+ let i = humanIdx;
254
+ while (i < humanTurnMs.length && humanTurnMs[i] < endMs) {
255
+ count++;
256
+ i++;
257
+ }
258
+ return count;
259
+ }
235
260
  function flushBlock() {
236
261
  if (currentBlock.length === 0) return;
237
262
  const models = /* @__PURE__ */ new Set();
@@ -270,7 +295,8 @@ function aggregateToBlocks(entries) {
270
295
  totalTokens,
271
296
  costUSD: Math.round(costUSD * 1e4) / 1e4,
272
297
  models: Array.from(models),
273
- entryCount: currentBlock.length
298
+ entryCount: currentBlock.length,
299
+ chatCount: countHumanTurns()
274
300
  });
275
301
  }
276
302
  for (const entry of entries) {
@@ -297,13 +323,13 @@ async function doSync(firstSync = false, silent = false) {
297
323
  } : console.log;
298
324
  const spinner = silent ? null : ora("Collecting usage data...").start();
299
325
  try {
300
- const entries = await collectUsageEntries();
326
+ const { entries, humanTurns } = await collectUsageEntries();
301
327
  if (spinner) spinner.text = `Found ${entries.length} entries`;
302
328
  if (entries.length === 0) {
303
329
  if (spinner) spinner.warn("No usage data found in ~/.claude/projects/");
304
330
  return;
305
331
  }
306
- const allBlocks = aggregateToBlocks(entries);
332
+ const allBlocks = aggregateToBlocks(entries, humanTurns);
307
333
  const lastSyncPath = getLastSyncPath();
308
334
  let lastSync = null;
309
335
  if (existsSync3(lastSyncPath)) {
@@ -559,7 +585,7 @@ function printGroup(data, code, period, config) {
559
585
  console.log(chalk4.dim(` ${period.toUpperCase()} \xB7 ${data.start.slice(0, 10)} \u2192 ${data.end.slice(0, 10)} \xB7 ${data.group.memberCount} members
560
586
  `));
561
587
  const table = new Table({
562
- head: ["#", "Name", "Tokens", "Cost", "Calls"].map((h) => chalk4.cyan(h)),
588
+ head: ["#", "Name", "Tokens", "Cost", "Chats"].map((h) => chalk4.cyan(h)),
563
589
  style: { head: [], border: [] },
564
590
  colWidths: [5, 20, 16, 12, 8]
565
591
  });
@@ -573,7 +599,7 @@ function printGroup(data, code, period, config) {
573
599
  name,
574
600
  entry.totalTokens.toLocaleString(),
575
601
  `$${entry.costUSD.toFixed(2)}`,
576
- String(entry.entryCount)
602
+ String(entry.chatCount || entry.entryCount)
577
603
  ]);
578
604
  }
579
605
  console.log(table.toString());
@@ -649,8 +675,8 @@ async function showDataCommand() {
649
675
  console.log(chalk6.bold("\n What CCClub uploads:\n"));
650
676
  console.log(chalk6.dim(" Only aggregated 5-hour block summaries. No conversation content,"));
651
677
  console.log(chalk6.dim(" no file paths, no project names, no session details.\n"));
652
- const entries = await collectUsageEntries();
653
- const blocks = aggregateToBlocks(entries);
678
+ const { entries, humanTurns } = await collectUsageEntries();
679
+ const blocks = aggregateToBlocks(entries, humanTurns);
654
680
  if (blocks.length === 0) {
655
681
  console.log(chalk6.yellow(" No usage data found in ~/.claude/projects/"));
656
682
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccclub",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "type": "module",
5
5
  "description": "See how much Claude Code you and your friends are using",
6
6
  "bin": {