ccc-notifier 0.6.2 → 0.7.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-O4VAP6RB.js} +2 -2
  3. package/dist/chunk-3M5UQK76.js +92 -0
  4. package/dist/{sweep-65D57PXU.js → chunk-6BXD6HU5.js} +275 -113
  5. package/dist/chunk-6IVSZ3S4.js +22 -0
  6. package/dist/{chunk-NV5UOHJA.js → chunk-JLDI4D5E.js} +5 -5
  7. package/dist/{chunk-T6Z2ZAN4.js → chunk-ML5H43NH.js} +1 -1
  8. package/dist/{chunk-6UZXXO5J.js → chunk-O2E4VTDR.js} +36 -9
  9. package/dist/{chunk-D4D76RGQ.js → chunk-OCKZZVOG.js} +353 -20
  10. package/dist/{chunk-27SJELD2.js → chunk-PXMXEFM7.js} +22 -107
  11. package/dist/{chunk-6HTETN26.js → chunk-Q45AAPJY.js} +18 -1
  12. package/dist/chunk-V4IS4FXH.js +454 -0
  13. package/dist/{chunk-OOAC5ULQ.js → chunk-WONMZ466.js} +184 -4
  14. package/dist/{chunk-OXXDZZPJ.js → chunk-X7U63BZG.js} +4 -6
  15. package/dist/{chunk-J5QAYTFE.js → chunk-XG73C6CC.js} +10 -1
  16. package/dist/cli.js +286 -29
  17. package/dist/{dashboard-VEKQ4TI3.js → dashboard-64XHDUVT.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-5CCM6MQL.js +41 -0
  21. package/dist/scan-UB6ORRZK.js +87 -0
  22. package/dist/{setup-I3JNGWZG.js → setup-VLMW433R.js} +5 -6
  23. package/dist/{subagent-store-US4TJJQR.js → subagent-store-USG3WJEB.js} +1 -1
  24. package/dist/sweep-DVOV4XIZ.js +20 -0
  25. package/dist/track-P6A5WDJZ.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-WCP7446C.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-OCKZZVOG.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-6UZXXO5J.js";
24
- import "./chunk-DGXUSPS4.js";
25
- import {
26
- formatJPY,
27
- formatUSD,
28
- modelDisplayName
29
- } from "./chunk-J5QAYTFE.js";
23
+ } from "./chunk-O2E4VTDR.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-XG73C6CC.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,12 @@ 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 ?? []);
442
591
  return rec;
443
592
  }
444
593
  async function processCodexRolloutLocked(rolloutPath, table, fx, daysCutoff, dryRun, summary, opts = {}) {
@@ -490,7 +639,7 @@ async function discoverCodexSweepSource() {
490
639
  if (!homeStat.isDirectory()) {
491
640
  return { discovery: { rollouts: [], unreadableDirs: 1 } };
492
641
  }
493
- const sessionsRoot = join(home, "sessions");
642
+ const sessionsRoot = join2(home, "sessions");
494
643
  try {
495
644
  await fsp.lstat(sessionsRoot);
496
645
  } catch (err) {
@@ -499,10 +648,6 @@ async function discoverCodexSweepSource() {
499
648
  }
500
649
  return { discovery: await listCodexRollouts(sessionsRoot) };
501
650
  }
502
- function projectsRoot(override) {
503
- if (override) return override;
504
- return process.env.CCCN_CLAUDE_PROJECTS || join(homedir(), ".claude", "projects");
505
- }
506
651
  function parseSweepFlags(argv) {
507
652
  const flags = {
508
653
  dryRun: false,
@@ -532,7 +677,7 @@ function parseSweepFlags(argv) {
532
677
  async function listProjectDirs(root) {
533
678
  try {
534
679
  const entries = await fsp.readdir(root, { withFileTypes: true });
535
- return entries.filter((e) => e.isDirectory()).map((e) => join(root, e.name));
680
+ return entries.filter((e) => e.isDirectory()).map((e) => join2(root, e.name));
536
681
  } catch {
537
682
  return null;
538
683
  }
@@ -540,7 +685,7 @@ async function listProjectDirs(root) {
540
685
  async function listTranscriptsStrict(projectDir) {
541
686
  try {
542
687
  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));
688
+ return entries.filter((e) => e.isFile() && e.name.endsWith(".jsonl")).map((e) => join2(projectDir, e.name));
544
689
  } catch {
545
690
  return null;
546
691
  }
@@ -587,7 +732,7 @@ function reportSourceFailure(summary, dryRun, scope, err, count = 1) {
587
732
  }
588
733
  logError(scope, err);
589
734
  }
590
- async function scanAllSources(projectDirs, codexSource, table, fx, daysCutoff, dryRun, summary, progress) {
735
+ async function scanAllSources(projectDirs, codexSource, table, fx, daysCutoff, dryRun, summary, progress, claudeRoots) {
591
736
  const codexRollouts = codexSource?.discovery.rollouts.length ?? 0;
592
737
  progress({
593
738
  type: "scan-start",
@@ -611,7 +756,7 @@ async function scanAllSources(projectDirs, codexSource, table, fx, daysCutoff, d
611
756
  for (const mainPath of transcriptPaths) {
612
757
  summary.transcripts += 1;
613
758
  try {
614
- await processTranscriptLocked(mainPath, table, fx, daysCutoff, dryRun, summary, {
759
+ await processTranscriptLocked(mainPath, table, fx, daysCutoff, dryRun, summary, claudeRoots, {
615
760
  ignoreCursors: true,
616
761
  strictRead: true
617
762
  });
@@ -672,16 +817,29 @@ async function runSweep(argv, deps = {}) {
672
817
  }
673
818
  const flags = parsed.flags;
674
819
  const progress = createSweepProgressReporter();
675
- const root = projectsRoot(flags.projects);
676
- const projectDirs = await listProjectDirs(root);
820
+ const claudeRoots = await claudeTranscriptRoots({ projectsOverride: flags.projects });
821
+ const cliRoot = claudeRoots.find((r) => r.surface === "cli");
822
+ const cliProjectDirs = await listProjectDirs(cliRoot.path);
677
823
  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}`);
824
+ const desktopProjectDirs = [];
825
+ for (const root of claudeRoots) {
826
+ if (root.surface !== "desktop") continue;
827
+ const dirs = await listProjectDirs(root.path);
828
+ if (dirs !== null) desktopProjectDirs.push(...dirs);
829
+ }
830
+ if (cliProjectDirs === null) {
831
+ const codexUsable = codexSource !== null && codexSource.discovery.unreadableDirs === 0;
832
+ if (!codexUsable && desktopProjectDirs.length === 0) {
833
+ console.log(`\u8D70\u67FB\u30EB\u30FC\u30C8\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093: ${cliRoot.path}`);
681
834
  return 1;
682
835
  }
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)`);
836
+ const remaining = [
837
+ desktopProjectDirs.length > 0 ? "\u30C7\u30B9\u30AF\u30C8\u30C3\u30D7" : null,
838
+ codexUsable ? "Codex" : null
839
+ ].filter((v) => v !== null);
840
+ 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
841
  }
842
+ const projectDirs = cliProjectDirs === null && desktopProjectDirs.length === 0 ? null : [...cliProjectDirs ?? [], ...desktopProjectDirs];
685
843
  progress({ type: "preparing", dryRun: flags.dryRun });
686
844
  const cfg = flags.dryRun ? readConfigReadOnly((err) => {
687
845
  const message = err instanceof Error ? err.message : String(err);
@@ -708,7 +866,7 @@ async function runSweep(argv, deps = {}) {
708
866
  const lockProvider = deps.lockProvider ?? (() => waitForDataLock());
709
867
  if (flags.dryRun) {
710
868
  try {
711
- await scanAllSources(projectDirs, codexSource, table, fx, daysCutoff, true, summary, progress);
869
+ await scanAllSources(projectDirs, codexSource, table, fx, daysCutoff, true, summary, progress, claudeRoots);
712
870
  } catch (err) {
713
871
  reportSourceFailure(summary, true, "sweep:dry-run", err);
714
872
  }
@@ -732,7 +890,7 @@ async function runSweep(argv, deps = {}) {
732
890
  try {
733
891
  invalidateCanonicalDashboards();
734
892
  resetHistoryAndCursors();
735
- await scanAllSources(projectDirs, codexSource, table, fx, daysCutoff, false, summary, progress);
893
+ await scanAllSources(projectDirs, codexSource, table, fx, daysCutoff, false, summary, progress, claudeRoots);
736
894
  try {
737
895
  invalidateCanonicalDashboards();
738
896
  if (summary.sourceFailures === 0 && cfg.dashboard.autoRegenerate) {
@@ -789,7 +947,11 @@ async function runSweep(argv, deps = {}) {
789
947
  console.log("\u5C65\u6B74\u3068\u53D6\u308A\u8FBC\u307F\u4F4D\u7F6E\u3092\u5143JSONL\u304B\u3089\u5168\u518D\u751F\u6210\u3057\u307E\u3057\u305F");
790
948
  return 0;
791
949
  }
950
+
792
951
  export {
793
- runSweep,
794
- splitIntoTurnDrafts
952
+ normalizeCodexOriginator,
953
+ collectSubagentUsage,
954
+ splitIntoTurnDrafts,
955
+ attachSubagentGroups,
956
+ runSweep
795
957
  };
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/surface.ts
4
+ function effectiveSurface(rec) {
5
+ return rec.surface ?? "cli";
6
+ }
7
+ var SURFACE_LABELS = {
8
+ cli: "CLI",
9
+ desktop: "\u30C7\u30B9\u30AF\u30C8\u30C3\u30D7\u30A2\u30D7\u30EA",
10
+ vscode: "VS Code\u62E1\u5F35",
11
+ "claude-code": "Claude Code(Codex rollout)",
12
+ "chrome-extension": "Chrome\u62E1\u5F35",
13
+ other: "\u305D\u306E\u4ED6"
14
+ };
15
+ function surfaceLabel(surface) {
16
+ return SURFACE_LABELS[surface] ?? surface;
17
+ }
18
+
19
+ export {
20
+ effectiveSurface,
21
+ surfaceLabel
22
+ };
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-DGXUSPS4.js";
5
5
  import {
6
6
  formatSummary
7
- } from "./chunk-J5QAYTFE.js";
7
+ } from "./chunk-XG73C6CC.js";
8
8
 
9
9
  // src/notify/os.ts
10
10
  import { spawn } from "child_process";
@@ -181,10 +181,10 @@ function spawnNotifyChild(title, body) {
181
181
  }
182
182
  return { child: spawnLinuxNotify(title, body), swallowError: backend.swallowError };
183
183
  }
184
- async function notifyOS(record, cfg, todayUSD) {
184
+ async function notifyOS(record, cfg, todayUSD, summaryOverride) {
185
185
  try {
186
186
  if (!cfg?.notify?.os) return;
187
- const { title, body } = formatSummary(record, cfg, todayUSD);
187
+ const { title, body } = summaryOverride ?? formatSummary(record, cfg, todayUSD);
188
188
  if (process.env.CCCN_DRY_RUN === "1") {
189
189
  writeDryRun("os", { title, body });
190
190
  return;
@@ -221,12 +221,12 @@ async function notifyOS(record, cfg, todayUSD) {
221
221
 
222
222
  // src/notify/slack.ts
223
223
  var SEND_TIMEOUT_MS = 3e3;
224
- async function notifySlack(record, cfg, todayUSD) {
224
+ async function notifySlack(record, cfg, todayUSD, summaryOverride) {
225
225
  try {
226
226
  const slackCfg = cfg?.notify?.slack;
227
227
  const webhookUrl = slackCfg?.webhookUrl;
228
228
  if (!webhookUrl) return;
229
- const { title, body } = formatSummary(record, cfg, todayUSD);
229
+ const { title, body } = summaryOverride ?? formatSummary(record, cfg, todayUSD);
230
230
  const line1 = body.split("\n")[0] ?? "";
231
231
  const rawPrompt = record.prompt ?? "";
232
232
  let promptText;
@@ -4,7 +4,7 @@ import {
4
4
  isMuted,
5
5
  readMuteState,
6
6
  writeMuteState
7
- } from "./chunk-OOAC5ULQ.js";
7
+ } from "./chunk-WONMZ466.js";
8
8
 
9
9
  // src/mute.ts
10
10
  function parseDuration(arg) {