ccc-notifier 0.6.3 → 0.8.0

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 (30) hide show
  1. package/README.md +5 -0
  2. package/dist/{budget-V7CCMJHF.js → budget-U37CCG4Y.js} +2 -2
  3. package/dist/chunk-3M5UQK76.js +92 -0
  4. package/dist/chunk-6IVSZ3S4.js +22 -0
  5. package/dist/{sweep-CONNNBK7.js → chunk-C7QOYVGT.js} +324 -120
  6. package/dist/{chunk-PYU7CAK2.js → chunk-DSTGXYSY.js} +44 -11
  7. package/dist/{chunk-D4D76RGQ.js → chunk-GAOGTALW.js} +361 -20
  8. package/dist/{chunk-T6Z2ZAN4.js → chunk-ML5H43NH.js} +1 -1
  9. package/dist/{chunk-27SJELD2.js → chunk-PXMXEFM7.js} +22 -107
  10. package/dist/{chunk-6HTETN26.js → chunk-Q45AAPJY.js} +18 -1
  11. package/dist/{chunk-OXXDZZPJ.js → chunk-RE72XBXB.js} +4 -6
  12. package/dist/{chunk-NV5UOHJA.js → chunk-TTD3BGOL.js} +5 -5
  13. package/dist/{chunk-OOAC5ULQ.js → chunk-WONMZ466.js} +184 -4
  14. package/dist/{chunk-J5QAYTFE.js → chunk-XOGYLFX3.js} +12 -1
  15. package/dist/chunk-YH45W7TA.js +479 -0
  16. package/dist/cli.js +287 -30
  17. package/dist/{dashboard-O7LJLYM3.js → dashboard-YG4H3GLA.js} +6 -4
  18. package/dist/{history-DL4SG3PG.js → history-W34EJOVV.js} +5 -3
  19. package/dist/{mute-UIR5P5N5.js → mute-23BWQXS3.js} +2 -2
  20. package/dist/reset-cursors-UVQXFN5Y.js +44 -0
  21. package/dist/scan-QB2N2O4Q.js +94 -0
  22. package/dist/{setup-I3JNGWZG.js → setup-2QDA3OWE.js} +5 -6
  23. package/dist/{subagent-store-US4TJJQR.js → subagent-store-USG3WJEB.js} +1 -1
  24. package/dist/sweep-OLI2LIXJ.js +22 -0
  25. package/dist/track-TIINM4ZR.js +368 -0
  26. package/package.json +1 -1
  27. package/dist/chunk-HLJAJS2W.js +0 -55
  28. package/dist/chunk-HTYUYKFW.js +0 -21
  29. package/dist/chunk-OYD6H3ZZ.js +0 -124
  30. package/dist/track-RBLLR25M.js +0 -257
@@ -1,38 +1,39 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- listCodexRollouts
4
- } from "./chunk-HLJAJS2W.js";
5
- import {
6
- codexHome
7
- } from "./chunk-HTYUYKFW.js";
8
- import {
9
- collectSubagentUsage
10
- } from "./chunk-OYD6H3ZZ.js";
11
- import {
3
+ addCountedCalls,
4
+ aggregateNewTurn,
5
+ anyOf,
6
+ callFingerprints,
7
+ claudeTranscriptRoots,
12
8
  extractBucket,
13
9
  getUsdJpy,
10
+ listCodexRollouts,
14
11
  promptCandidate,
15
- splitIntoCodexTurnDrafts
16
- } from "./chunk-D4D76RGQ.js";
12
+ setCountedCalls,
13
+ splitIntoCodexTurnDrafts,
14
+ surfaceForClaudePath
15
+ } from "./chunk-GAOGTALW.js";
17
16
  import {
17
+ codexHome,
18
18
  computeCost,
19
19
  loadPriceTable
20
- } from "./chunk-6HTETN26.js";
20
+ } from "./chunk-Q45AAPJY.js";
21
21
  import {
22
22
  writeDashboardHtml
23
- } from "./chunk-PYU7CAK2.js";
24
- import "./chunk-DGXUSPS4.js";
25
- import {
26
- formatJPY,
27
- formatUSD,
28
- modelDisplayName
29
- } from "./chunk-J5QAYTFE.js";
23
+ } from "./chunk-DSTGXYSY.js";
30
24
  import {
31
25
  invalidateCanonicalDashboards,
32
26
  makeFullDashboardState,
33
- waitForDataLock,
34
27
  writeFullDashboardStateAtomic
35
- } from "./chunk-27SJELD2.js";
28
+ } from "./chunk-3M5UQK76.js";
29
+ import {
30
+ waitForDataLock
31
+ } from "./chunk-PXMXEFM7.js";
32
+ import {
33
+ formatJPY,
34
+ formatUSD,
35
+ modelDisplayName
36
+ } from "./chunk-XOGYLFX3.js";
36
37
  import {
37
38
  appendTurn,
38
39
  loadCursor,
@@ -44,13 +45,146 @@ import {
44
45
  resetHistoryAndCursors,
45
46
  sanitizeCursor,
46
47
  saveCursor
47
- } from "./chunk-OOAC5ULQ.js";
48
+ } from "./chunk-WONMZ466.js";
48
49
 
49
50
  // src/sweep.ts
50
51
  import { readFile } from "fs/promises";
51
52
  import { promises as fsp } from "fs";
53
+ import { join as join2 } from "path";
54
+
55
+ // src/subagents.ts
56
+ import { promises as fs } from "fs";
52
57
  import { join } from "path";
53
- import { homedir } from "os";
58
+ var MAX_AGENT_FILES = 200;
59
+ function emptyBuckets() {
60
+ return { input: 0, output: 0, cacheWrite5m: 0, cacheWrite1h: 0, cacheRead: 0 };
61
+ }
62
+ function addToModel(target, model, b) {
63
+ const cur = target[model] ?? emptyBuckets();
64
+ cur.input += b.input;
65
+ cur.output += b.output;
66
+ cur.cacheWrite5m += b.cacheWrite5m;
67
+ cur.cacheWrite1h += b.cacheWrite1h;
68
+ cur.cacheRead += b.cacheRead;
69
+ target[model] = cur;
70
+ }
71
+ function mergeUsage(target, src) {
72
+ for (const [model, b] of Object.entries(src)) addToModel(target, model, b);
73
+ }
74
+ function sameCursor(a, b) {
75
+ return a !== null && a.offset === b.offset && a.lastUuid === b.lastUuid && a.lastTs === b.lastTs && a.seenMessageKeys.length === b.seenMessageKeys.length && a.seenMessageKeys.every((key, i) => key === b.seenMessageKeys[i]);
76
+ }
77
+ function subagentsDirOf(mainTranscriptPath) {
78
+ const base = mainTranscriptPath.endsWith(".jsonl") ? mainTranscriptPath.slice(0, -".jsonl".length) : mainTranscriptPath;
79
+ return join(base, "subagents");
80
+ }
81
+ async function listAgentFiles(dir, entries, includeAllFiles) {
82
+ const files = entries.filter((e) => e.isFile() && e.name.startsWith("agent-") && e.name.endsWith(".jsonl")).map((e) => join(dir, e.name));
83
+ if (includeAllFiles || files.length <= MAX_AGENT_FILES) return files;
84
+ const withMtime = [];
85
+ for (const p of files) {
86
+ let mtime = 0;
87
+ try {
88
+ mtime = (await fs.stat(p)).mtimeMs;
89
+ } catch {
90
+ mtime = 0;
91
+ }
92
+ withMtime.push({ path: p, mtime });
93
+ }
94
+ withMtime.sort((a, b) => b.mtime - a.mtime);
95
+ return withMtime.slice(0, MAX_AGENT_FILES).map((x) => x.path);
96
+ }
97
+ async function collectSubagentUsage(mainTranscriptPath, opts = {}) {
98
+ const dir = subagentsDirOf(mainTranscriptPath);
99
+ let entries;
100
+ try {
101
+ entries = await fs.readdir(dir, { withFileTypes: true });
102
+ } catch (err) {
103
+ if (opts.strictRead && err.code !== "ENOENT") throw err;
104
+ return null;
105
+ }
106
+ const files = await listAgentFiles(dir, entries, opts.includeAllFiles === true);
107
+ const perModel = {};
108
+ let apiCalls = 0;
109
+ let agentFiles = 0;
110
+ const newCursors = [];
111
+ const groups = [];
112
+ let sessionId = "";
113
+ let cwd = null;
114
+ let gitBranch = null;
115
+ let lastTs = null;
116
+ const readCursor = opts.readCursor ?? ((path) => sanitizeCursor(loadCursor(path)));
117
+ const excluded = /* @__PURE__ */ new Set();
118
+ const excludeFilter = anyOf([excluded, opts.excludeMessageKeys]);
119
+ if (!opts.ignoreCursors) {
120
+ for (const filePath of files) {
121
+ const prior = readCursor(filePath);
122
+ for (const key of prior?.seenMessageKeys ?? []) excluded.add(key);
123
+ }
124
+ }
125
+ for (const filePath of files) {
126
+ try {
127
+ if (opts.strictRead) {
128
+ const file = await fs.open(filePath, "r");
129
+ await file.close();
130
+ }
131
+ const cursor = opts.ignoreCursors ? null : readCursor(filePath);
132
+ const recovery = cursor === null && opts.recovery !== void 0 ? opts.recovery() : void 0;
133
+ const agg = await aggregateNewTurn(filePath, cursor, {
134
+ excludeMessageKeys: recovery?.excludeMessageKeys === void 0 ? excludeFilter : anyOf([excludeFilter, recovery.excludeMessageKeys]),
135
+ minTimestampMs: opts.minTimestampMs ?? recovery?.minTimestampMs ?? null,
136
+ returnEmpty: true
137
+ });
138
+ if (agg === null) continue;
139
+ if (!sameCursor(cursor, agg.newCursor)) {
140
+ newCursors.push({ path: filePath, cursor: agg.newCursor });
141
+ }
142
+ for (const key of agg.messageKeys) excluded.add(key);
143
+ if (agg.apiCalls === 0) continue;
144
+ mergeUsage(perModel, agg.main);
145
+ mergeUsage(perModel, agg.sidechain);
146
+ apiCalls += agg.apiCalls;
147
+ agentFiles += 1;
148
+ const groupUsage = {};
149
+ mergeUsage(groupUsage, agg.main);
150
+ mergeUsage(groupUsage, agg.sidechain);
151
+ groups.push({
152
+ perModel: groupUsage,
153
+ apiCalls: agg.apiCalls,
154
+ messageKeys: agg.messageKeys,
155
+ firstTs: agg.firstTs,
156
+ lastTs: agg.lastTs
157
+ });
158
+ if (agg.sessionId) sessionId = agg.sessionId;
159
+ if (agg.cwd !== null) cwd = agg.cwd;
160
+ if (agg.gitBranch !== null) gitBranch = agg.gitBranch;
161
+ if (agg.lastTs !== null && (lastTs === null || agg.lastTs > lastTs)) lastTs = agg.lastTs;
162
+ } catch (err) {
163
+ if (opts.strictRead) throw err;
164
+ logError("subagents:file", err);
165
+ }
166
+ }
167
+ return { perModel, apiCalls, agentFiles, newCursors, groups, sessionId, cwd, gitBranch, lastTs };
168
+ }
169
+
170
+ // src/codex/originator.ts
171
+ var ORIGINATOR_TO_SURFACE = {
172
+ "codex-tui": "cli",
173
+ "codex_cli_rs": "cli",
174
+ "codex_exec": "cli",
175
+ "Codex Desktop": "desktop",
176
+ "codex_desktop": "desktop",
177
+ "codex_work_desktop": "desktop",
178
+ "codex_vscode": "vscode",
179
+ "Claude Code": "claude-code",
180
+ "codex-chrome-extension-sidepanel": "chrome-extension"
181
+ };
182
+ function normalizeCodexOriginator(originator) {
183
+ if (typeof originator !== "string" || originator.length === 0) return "other";
184
+ return ORIGINATOR_TO_SURFACE[originator] ?? "other";
185
+ }
186
+
187
+ // src/sweep.ts
54
188
  var NEWLINE = 10;
55
189
  var MAX_SEEN_KEYS = 500;
56
190
  var SYNTHETIC_MODEL = "<synthetic>";
@@ -97,11 +231,11 @@ function isRecord(v) {
97
231
  function strOrNull(v) {
98
232
  return typeof v === "string" ? v : null;
99
233
  }
100
- function emptyBuckets() {
234
+ function emptyBuckets2() {
101
235
  return { input: 0, output: 0, cacheWrite5m: 0, cacheWrite1h: 0, cacheRead: 0 };
102
236
  }
103
- function addToModel(target, model, b) {
104
- const cur = target[model] ?? emptyBuckets();
237
+ function addToModel2(target, model, b) {
238
+ const cur = target[model] ?? emptyBuckets2();
105
239
  cur.input += b.input;
106
240
  cur.output += b.output;
107
241
  cur.cacheWrite5m += b.cacheWrite5m;
@@ -110,7 +244,7 @@ function addToModel(target, model, b) {
110
244
  target[model] = cur;
111
245
  }
112
246
  function sumBuckets(usage) {
113
- const total = emptyBuckets();
247
+ const total = emptyBuckets2();
114
248
  for (const b of Object.values(usage)) {
115
249
  total.input += b.input;
116
250
  total.output += b.output;
@@ -151,11 +285,11 @@ async function readAll(path) {
151
285
  return null;
152
286
  }
153
287
  }
154
- async function splitIntoTurnDrafts(transcriptPath, cursor) {
288
+ async function splitIntoTurnDrafts(transcriptPath, cursor, opts = {}) {
155
289
  const buffer = await readAll(transcriptPath);
156
290
  if (buffer === null) {
157
291
  const nc = cursor ?? { offset: 0, lastUuid: null, lastTs: null, seenMessageKeys: [] };
158
- return { drafts: [], newCursor: nc, messageKeys: [] };
292
+ return { drafts: [], newCursor: nc, messageKeys: [], unreadable: true };
159
293
  }
160
294
  const fileSize = buffer.length;
161
295
  let startOffset;
@@ -167,7 +301,7 @@ async function splitIntoTurnDrafts(transcriptPath, cursor) {
167
301
  startOffset = 0;
168
302
  rescan = cursor !== null;
169
303
  }
170
- const priorSeen = new Set(cursor?.seenMessageKeys ?? []);
304
+ const priorSeen = anyOf([new Set(cursor?.seenMessageKeys ?? []), opts.excludeMessageKeys]);
171
305
  const tsFloor = cursor?.lastTs ?? null;
172
306
  const drafts = [];
173
307
  const runSeen = /* @__PURE__ */ new Set();
@@ -179,17 +313,20 @@ async function splitIntoTurnDrafts(transcriptPath, cursor) {
179
313
  if (cur.pending.size === 0) return;
180
314
  const mainPerModel = {};
181
315
  const sidechainPerModel = {};
316
+ const draftKeys = [];
182
317
  for (const [key, pm] of cur.pending) {
183
318
  runSeen.add(key);
184
319
  newKeys.push(key);
185
- if (pm.isSidechain) addToModel(sidechainPerModel, pm.model, pm.bucket);
186
- else addToModel(mainPerModel, pm.model, pm.bucket);
320
+ draftKeys.push(key);
321
+ if (pm.isSidechain) addToModel2(sidechainPerModel, pm.model, pm.bucket);
322
+ else addToModel2(mainPerModel, pm.model, pm.bucket);
187
323
  }
188
324
  drafts.push({
189
325
  prompt: cur.prompt ?? "",
190
326
  mainPerModel,
191
327
  sidechainPerModel,
192
328
  apiCalls: cur.pending.size,
329
+ messageKeys: draftKeys,
193
330
  firstTs: cur.firstTs,
194
331
  lastTs: cur.lastTs,
195
332
  cwd: cur.cwd,
@@ -277,7 +414,7 @@ async function splitIntoTurnDrafts(transcriptPath, cursor) {
277
414
  };
278
415
  return { drafts, newCursor, messageKeys: newKeys };
279
416
  }
280
- function draftToRecord(draft, ts, table, fx) {
417
+ function draftToRecord(draft, ts, table, fx, surface) {
281
418
  const breakdown = computeCost(draft.mainPerModel, draft.sidechainPerModel, table);
282
419
  const sidechainHasModels = Object.keys(draft.sidechainPerModel).length > 0;
283
420
  const rec = {
@@ -297,9 +434,11 @@ function draftToRecord(draft, ts, table, fx) {
297
434
  fxRate: fx.rate,
298
435
  fxSource: fx.source,
299
436
  prompt: draft.prompt,
300
- ingest: "sweep"
437
+ ingest: "sweep",
438
+ surface
301
439
  };
302
440
  if (breakdown.unknownModels.length > 0) rec.unknownModels = breakdown.unknownModels;
441
+ setCountedCalls(rec, callFingerprints(draft.messageKeys));
303
442
  return rec;
304
443
  }
305
444
  function mergeUnknownModels(rec, extra) {
@@ -308,11 +447,73 @@ function mergeUnknownModels(rec, extra) {
308
447
  for (const m of extra) if (!merged.includes(m)) merged.push(m);
309
448
  rec.unknownModels = merged;
310
449
  }
311
- async function processTranscriptLocked(mainPath, table, fx, daysCutoff, dryRun, summary, opts = {}) {
450
+ function attachSubagentGroups(records, sa, table, fx, surface, ingest) {
451
+ const parentRecords = [...records];
452
+ let addedUSD = 0;
453
+ for (const group of sa.groups) {
454
+ const saBreakdown = computeCost(group.perModel, {}, table);
455
+ addedUSD += saBreakdown.usd;
456
+ const saBlock = {
457
+ costUSD: saBreakdown.usd,
458
+ costByModel: { ...saBreakdown.byModel },
459
+ tokens: sumBuckets(group.perModel),
460
+ apiCalls: group.apiCalls,
461
+ agentFiles: 1
462
+ };
463
+ const groupMs = group.lastTs === null ? NaN : Date.parse(group.lastTs);
464
+ let target;
465
+ if (Number.isFinite(groupMs)) {
466
+ target = parentRecords.find((rec) => {
467
+ const recMs = Date.parse(rec.ts);
468
+ return Number.isFinite(recMs) && recMs >= groupMs;
469
+ });
470
+ } else {
471
+ target = parentRecords[parentRecords.length - 1];
472
+ }
473
+ if (target === void 0) {
474
+ target = {
475
+ schemaVersion: 1,
476
+ ts: group.lastTs ?? sa.lastTs ?? (/* @__PURE__ */ new Date()).toISOString(),
477
+ sessionId: sa.sessionId,
478
+ project: sa.cwd ?? "",
479
+ gitBranch: sa.gitBranch,
480
+ models: collectModels(group.perModel, {}),
481
+ tokens: emptyBuckets2(),
482
+ sidechainTokens: null,
483
+ apiCalls: 0,
484
+ costUSD: 0,
485
+ costByModel: {},
486
+ costJPY: 0,
487
+ fxRate: fx.rate,
488
+ fxSource: fx.source,
489
+ prompt: "",
490
+ ingest,
491
+ surface
492
+ };
493
+ records.push(target);
494
+ }
495
+ addCountedCalls(target, callFingerprints(group.messageKeys));
496
+ if (target.subagents === void 0) {
497
+ target.subagents = saBlock;
498
+ } else {
499
+ target.subagents.costUSD += saBlock.costUSD;
500
+ target.subagents.apiCalls += saBlock.apiCalls;
501
+ target.subagents.agentFiles += 1;
502
+ addToBuckets(target.subagents.tokens, saBlock.tokens);
503
+ for (const [model, usd] of Object.entries(saBlock.costByModel)) {
504
+ target.subagents.costByModel[model] = (target.subagents.costByModel[model] ?? 0) + usd;
505
+ }
506
+ }
507
+ mergeUnknownModels(target, saBreakdown.unknownModels);
508
+ }
509
+ return addedUSD;
510
+ }
511
+ async function processTranscriptLocked(mainPath, table, fx, daysCutoff, dryRun, summary, roots, opts = {}) {
312
512
  if (opts.strictRead) {
313
513
  const file = await fsp.open(mainPath, "r");
314
514
  await file.close();
315
515
  }
516
+ const surface = surfaceForClaudePath(mainPath, roots);
316
517
  const cursor = opts.ignoreCursors ? null : sanitizeCursor(loadCursor(mainPath));
317
518
  const { drafts, newCursor, messageKeys } = await splitIntoTurnDrafts(mainPath, cursor);
318
519
  const records = [];
@@ -322,7 +523,7 @@ async function processTranscriptLocked(mainPath, table, fx, daysCutoff, dryRun,
322
523
  const tsMs = Date.parse(ts);
323
524
  if (!Number.isFinite(tsMs) || tsMs < daysCutoff) continue;
324
525
  }
325
- records.push(draftToRecord(draft, ts, table, fx));
526
+ records.push(draftToRecord(draft, ts, table, fx, surface));
326
527
  }
327
528
  let sa = null;
328
529
  try {
@@ -338,63 +539,8 @@ async function processTranscriptLocked(mainPath, table, fx, daysCutoff, dryRun,
338
539
  logError("sweep:subagents", err);
339
540
  sa = null;
340
541
  }
341
- const saHasUsage = sa !== null && sa.apiCalls > 0;
342
- if (saHasUsage) {
343
- const parentRecords = [...records];
344
- for (const group of sa.groups) {
345
- const saBreakdown = computeCost(group.perModel, {}, table);
346
- summary.subagentsUSD += saBreakdown.usd;
347
- const saBlock = {
348
- costUSD: saBreakdown.usd,
349
- costByModel: { ...saBreakdown.byModel },
350
- tokens: sumBuckets(group.perModel),
351
- apiCalls: group.apiCalls,
352
- agentFiles: 1
353
- };
354
- const groupMs = group.lastTs === null ? NaN : Date.parse(group.lastTs);
355
- let target;
356
- if (Number.isFinite(groupMs)) {
357
- target = parentRecords.find((rec) => {
358
- const recMs = Date.parse(rec.ts);
359
- return Number.isFinite(recMs) && recMs >= groupMs;
360
- });
361
- } else {
362
- target = parentRecords[parentRecords.length - 1];
363
- }
364
- if (target === void 0) {
365
- target = {
366
- schemaVersion: 1,
367
- ts: group.lastTs ?? sa.lastTs ?? (/* @__PURE__ */ new Date()).toISOString(),
368
- sessionId: sa.sessionId,
369
- project: sa.cwd ?? "",
370
- gitBranch: sa.gitBranch,
371
- models: collectModels(group.perModel, {}),
372
- tokens: emptyBuckets(),
373
- sidechainTokens: null,
374
- apiCalls: 0,
375
- costUSD: 0,
376
- costByModel: {},
377
- costJPY: 0,
378
- fxRate: fx.rate,
379
- fxSource: fx.source,
380
- prompt: "",
381
- ingest: "sweep"
382
- };
383
- records.push(target);
384
- }
385
- if (target.subagents === void 0) {
386
- target.subagents = saBlock;
387
- } else {
388
- target.subagents.costUSD += saBlock.costUSD;
389
- target.subagents.apiCalls += saBlock.apiCalls;
390
- target.subagents.agentFiles += 1;
391
- addToBuckets(target.subagents.tokens, saBlock.tokens);
392
- for (const [model, usd] of Object.entries(saBlock.costByModel)) {
393
- target.subagents.costByModel[model] = (target.subagents.costByModel[model] ?? 0) + usd;
394
- }
395
- }
396
- mergeUnknownModels(target, saBreakdown.unknownModels);
397
- }
542
+ if (sa !== null && sa.apiCalls > 0) {
543
+ summary.subagentsUSD += attachSubagentGroups(records, sa, table, fx, surface, "sweep");
398
544
  summary.agentFiles += sa.agentFiles;
399
545
  }
400
546
  for (const rec of records) {
@@ -436,9 +582,47 @@ function codexDraftToRecord(draft, ts, table, fx) {
436
582
  fxSource: fx.source,
437
583
  prompt: draft.agg.prompt ?? "",
438
584
  ingest: "sweep",
439
- source: "codex"
585
+ source: "codex",
586
+ surface: normalizeCodexOriginator(draft.agg.originator)
440
587
  };
588
+ if (typeof draft.agg.originator === "string") rec.originator = draft.agg.originator;
441
589
  if (breakdown.unknownModels.length > 0) rec.unknownModels = breakdown.unknownModels;
590
+ setCountedCalls(rec, draft.agg.codexEventKeys ?? []);
591
+ return rec;
592
+ }
593
+ function codexChildDraftToRecord(draft, ts, table, fx, ingest) {
594
+ const main = draft.agg.main;
595
+ const breakdown = computeCost(main, {}, table);
596
+ const rec = {
597
+ schemaVersion: 1,
598
+ ts,
599
+ sessionId: draft.agg.sessionId,
600
+ project: draft.agg.cwd ?? "",
601
+ gitBranch: null,
602
+ models: collectModels(main, {}),
603
+ tokens: emptyBuckets2(),
604
+ sidechainTokens: null,
605
+ apiCalls: 0,
606
+ costUSD: 0,
607
+ costByModel: {},
608
+ costJPY: 0,
609
+ fxRate: fx.rate,
610
+ fxSource: fx.source,
611
+ prompt: "",
612
+ ingest,
613
+ source: "codex",
614
+ surface: normalizeCodexOriginator(draft.agg.originator),
615
+ subagents: {
616
+ costUSD: breakdown.usd,
617
+ costByModel: { ...breakdown.byModel },
618
+ tokens: sumBuckets(main),
619
+ apiCalls: draft.agg.apiCalls,
620
+ agentFiles: 1
621
+ }
622
+ };
623
+ if (typeof draft.agg.originator === "string") rec.originator = draft.agg.originator;
624
+ if (breakdown.unknownModels.length > 0) rec.unknownModels = breakdown.unknownModels;
625
+ setCountedCalls(rec, draft.agg.codexEventKeys ?? []);
442
626
  return rec;
443
627
  }
444
628
  async function processCodexRolloutLocked(rolloutPath, table, fx, daysCutoff, dryRun, summary, opts = {}) {
@@ -449,21 +633,27 @@ async function processCodexRolloutLocked(rolloutPath, table, fx, daysCutoff, dry
449
633
  const cursor = opts.ignoreCursors ? null : sanitizeCursor(loadCursor(rolloutPath));
450
634
  const drafts = await splitIntoCodexTurnDrafts(rolloutPath, cursor);
451
635
  if (drafts === null || drafts.length === 0) return;
452
- if (drafts[0].isSubagentRollout) return;
636
+ const isChild = drafts[0].isSubagentRollout;
637
+ const isForkedChild = isChild && drafts[0].isForkedRollout;
453
638
  const records = [];
454
- for (const draft of drafts) {
455
- const ts = draft.endTs ?? (/* @__PURE__ */ new Date()).toISOString();
456
- if (daysCutoff !== null) {
457
- const tsMs = Date.parse(ts);
458
- if (!Number.isFinite(tsMs) || tsMs < daysCutoff) continue;
639
+ if (!isForkedChild) {
640
+ for (const draft of drafts) {
641
+ const ts = draft.endTs ?? (/* @__PURE__ */ new Date()).toISOString();
642
+ if (daysCutoff !== null) {
643
+ const tsMs = Date.parse(ts);
644
+ if (!Number.isFinite(tsMs) || tsMs < daysCutoff) continue;
645
+ }
646
+ records.push(
647
+ isChild ? codexChildDraftToRecord(draft, ts, table, fx, "sweep") : codexDraftToRecord(draft, ts, table, fx)
648
+ );
459
649
  }
460
- records.push(codexDraftToRecord(draft, ts, table, fx));
461
650
  }
462
651
  for (const rec of records) {
463
652
  summary.newRecords += 1;
464
653
  summary.totalUSD += rec.costUSD;
465
654
  summary.codexRecords += 1;
466
655
  summary.codexUSD += rec.costUSD;
656
+ summary.subagentsUSD += rec.subagents?.costUSD ?? 0;
467
657
  if (rec.costByModel) {
468
658
  for (const [m, c] of Object.entries(rec.costByModel)) {
469
659
  summary.byModel[m] = (summary.byModel[m] ?? 0) + c;
@@ -490,7 +680,7 @@ async function discoverCodexSweepSource() {
490
680
  if (!homeStat.isDirectory()) {
491
681
  return { discovery: { rollouts: [], unreadableDirs: 1 } };
492
682
  }
493
- const sessionsRoot = join(home, "sessions");
683
+ const sessionsRoot = join2(home, "sessions");
494
684
  try {
495
685
  await fsp.lstat(sessionsRoot);
496
686
  } catch (err) {
@@ -499,10 +689,6 @@ async function discoverCodexSweepSource() {
499
689
  }
500
690
  return { discovery: await listCodexRollouts(sessionsRoot) };
501
691
  }
502
- function projectsRoot(override) {
503
- if (override) return override;
504
- return process.env.CCCN_CLAUDE_PROJECTS || join(homedir(), ".claude", "projects");
505
- }
506
692
  function parseSweepFlags(argv) {
507
693
  const flags = {
508
694
  dryRun: false,
@@ -532,7 +718,7 @@ function parseSweepFlags(argv) {
532
718
  async function listProjectDirs(root) {
533
719
  try {
534
720
  const entries = await fsp.readdir(root, { withFileTypes: true });
535
- return entries.filter((e) => e.isDirectory()).map((e) => join(root, e.name));
721
+ return entries.filter((e) => e.isDirectory()).map((e) => join2(root, e.name));
536
722
  } catch {
537
723
  return null;
538
724
  }
@@ -540,7 +726,7 @@ async function listProjectDirs(root) {
540
726
  async function listTranscriptsStrict(projectDir) {
541
727
  try {
542
728
  const entries = await fsp.readdir(projectDir, { withFileTypes: true });
543
- return entries.filter((e) => e.isFile() && e.name.endsWith(".jsonl")).map((e) => join(projectDir, e.name));
729
+ return entries.filter((e) => e.isFile() && e.name.endsWith(".jsonl")).map((e) => join2(projectDir, e.name));
544
730
  } catch {
545
731
  return null;
546
732
  }
@@ -587,7 +773,7 @@ function reportSourceFailure(summary, dryRun, scope, err, count = 1) {
587
773
  }
588
774
  logError(scope, err);
589
775
  }
590
- async function scanAllSources(projectDirs, codexSource, table, fx, daysCutoff, dryRun, summary, progress) {
776
+ async function scanAllSources(projectDirs, codexSource, table, fx, daysCutoff, dryRun, summary, progress, claudeRoots) {
591
777
  const codexRollouts = codexSource?.discovery.rollouts.length ?? 0;
592
778
  progress({
593
779
  type: "scan-start",
@@ -611,7 +797,7 @@ async function scanAllSources(projectDirs, codexSource, table, fx, daysCutoff, d
611
797
  for (const mainPath of transcriptPaths) {
612
798
  summary.transcripts += 1;
613
799
  try {
614
- await processTranscriptLocked(mainPath, table, fx, daysCutoff, dryRun, summary, {
800
+ await processTranscriptLocked(mainPath, table, fx, daysCutoff, dryRun, summary, claudeRoots, {
615
801
  ignoreCursors: true,
616
802
  strictRead: true
617
803
  });
@@ -672,16 +858,29 @@ async function runSweep(argv, deps = {}) {
672
858
  }
673
859
  const flags = parsed.flags;
674
860
  const progress = createSweepProgressReporter();
675
- const root = projectsRoot(flags.projects);
676
- const projectDirs = await listProjectDirs(root);
861
+ const claudeRoots = await claudeTranscriptRoots({ projectsOverride: flags.projects });
862
+ const cliRoot = claudeRoots.find((r) => r.surface === "cli");
863
+ const cliProjectDirs = await listProjectDirs(cliRoot.path);
677
864
  const codexSource = await discoverCodexSweepSource();
678
- if (projectDirs === null) {
679
- if (codexSource === null || codexSource.discovery.unreadableDirs > 0) {
680
- console.log(`\u8D70\u67FB\u30EB\u30FC\u30C8\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093: ${root}`);
865
+ const desktopProjectDirs = [];
866
+ for (const root of claudeRoots) {
867
+ if (root.surface !== "desktop") continue;
868
+ const dirs = await listProjectDirs(root.path);
869
+ if (dirs !== null) desktopProjectDirs.push(...dirs);
870
+ }
871
+ if (cliProjectDirs === null) {
872
+ const codexUsable = codexSource !== null && codexSource.discovery.unreadableDirs === 0;
873
+ if (!codexUsable && desktopProjectDirs.length === 0) {
874
+ console.log(`\u8D70\u67FB\u30EB\u30FC\u30C8\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093: ${cliRoot.path}`);
681
875
  return 1;
682
876
  }
683
- console.log(`Claude \u306E\u8D70\u67FB\u30EB\u30FC\u30C8\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093: ${root}(Codex \u306E\u307F\u8D70\u67FB\u3057\u307E\u3059)`);
877
+ const remaining = [
878
+ desktopProjectDirs.length > 0 ? "\u30C7\u30B9\u30AF\u30C8\u30C3\u30D7" : null,
879
+ codexUsable ? "Codex" : null
880
+ ].filter((v) => v !== null);
881
+ console.log(`Claude CLI \u306E\u8D70\u67FB\u30EB\u30FC\u30C8\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093: ${cliRoot.path}(${remaining.join(" / ")} \u306E\u307F\u8D70\u67FB\u3057\u307E\u3059)`);
684
882
  }
883
+ const projectDirs = cliProjectDirs === null && desktopProjectDirs.length === 0 ? null : [...cliProjectDirs ?? [], ...desktopProjectDirs];
685
884
  progress({ type: "preparing", dryRun: flags.dryRun });
686
885
  const cfg = flags.dryRun ? readConfigReadOnly((err) => {
687
886
  const message = err instanceof Error ? err.message : String(err);
@@ -708,7 +907,7 @@ async function runSweep(argv, deps = {}) {
708
907
  const lockProvider = deps.lockProvider ?? (() => waitForDataLock());
709
908
  if (flags.dryRun) {
710
909
  try {
711
- await scanAllSources(projectDirs, codexSource, table, fx, daysCutoff, true, summary, progress);
910
+ await scanAllSources(projectDirs, codexSource, table, fx, daysCutoff, true, summary, progress, claudeRoots);
712
911
  } catch (err) {
713
912
  reportSourceFailure(summary, true, "sweep:dry-run", err);
714
913
  }
@@ -732,7 +931,7 @@ async function runSweep(argv, deps = {}) {
732
931
  try {
733
932
  invalidateCanonicalDashboards();
734
933
  resetHistoryAndCursors();
735
- await scanAllSources(projectDirs, codexSource, table, fx, daysCutoff, false, summary, progress);
934
+ await scanAllSources(projectDirs, codexSource, table, fx, daysCutoff, false, summary, progress, claudeRoots);
736
935
  try {
737
936
  invalidateCanonicalDashboards();
738
937
  if (summary.sourceFailures === 0 && cfg.dashboard.autoRegenerate) {
@@ -789,7 +988,12 @@ async function runSweep(argv, deps = {}) {
789
988
  console.log("\u5C65\u6B74\u3068\u53D6\u308A\u8FBC\u307F\u4F4D\u7F6E\u3092\u5143JSONL\u304B\u3089\u5168\u518D\u751F\u6210\u3057\u307E\u3057\u305F");
790
989
  return 0;
791
990
  }
991
+
792
992
  export {
793
- runSweep,
794
- splitIntoTurnDrafts
993
+ normalizeCodexOriginator,
994
+ collectSubagentUsage,
995
+ splitIntoTurnDrafts,
996
+ attachSubagentGroups,
997
+ codexChildDraftToRecord,
998
+ runSweep
795
999
  };