ccc-notifier 0.5.0 → 0.6.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.
@@ -1,200 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // src/pricing.ts
4
- import { promises as fs } from "fs";
5
- import path from "path";
6
- var LITELLM_URL = "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json";
7
- var LITELLM_FETCH_TIMEOUT_MS = 3e3;
8
- var CACHE_FRESH_MS = 24 * 60 * 60 * 1e3;
9
- function price(input, output, cacheWrite5m, cacheWrite1h, cacheRead, source) {
10
- return { input, output, cacheWrite5m, cacheWrite1h, cacheRead, source };
11
- }
12
- function builtinPriceTable() {
13
- return {
14
- "claude-fable-5": price(10, 50, 12.5, 20, 1, "builtin"),
15
- "claude-mythos-5": price(10, 50, 12.5, 20, 1, "builtin"),
16
- "claude-opus-4-8": price(5, 25, 6.25, 10, 0.5, "builtin"),
17
- "claude-opus-4-7": price(5, 25, 6.25, 10, 0.5, "builtin"),
18
- "claude-opus-4-6": price(5, 25, 6.25, 10, 0.5, "builtin"),
19
- "claude-opus-4-5": price(5, 25, 6.25, 10, 0.5, "builtin"),
20
- "claude-opus-4-1": price(15, 75, 18.75, 30, 1.5, "builtin"),
21
- "claude-opus-4": price(15, 75, 18.75, 30, 1.5, "builtin"),
22
- // 旧 claude-opus-4-20250514 の受け皿
23
- "claude-3-opus": price(15, 75, 18.75, 30, 1.5, "builtin"),
24
- "claude-sonnet-5": price(3, 15, 3.75, 6, 0.3, "builtin"),
25
- "claude-sonnet-4-6": price(3, 15, 3.75, 6, 0.3, "builtin"),
26
- "claude-sonnet-4-5": price(3, 15, 3.75, 6, 0.3, "builtin"),
27
- "claude-sonnet-4": price(3, 15, 3.75, 6, 0.3, "builtin"),
28
- "claude-3-7-sonnet": price(3, 15, 3.75, 6, 0.3, "builtin"),
29
- "claude-3-5-sonnet": price(3, 15, 3.75, 6, 0.3, "builtin"),
30
- "claude-haiku-4-5": price(1, 5, 1.25, 2, 0.1, "builtin"),
31
- "claude-3-5-haiku": price(0.8, 4, 1, 1.6, 0.08, "builtin"),
32
- "claude-3-haiku": price(0.25, 1.25, 0.3125, 0.5, 0.025, "builtin"),
33
- // OpenAI Codex CLI 対応(公式レートに基づく単価。キャッシュ書き込み課金は無いため 0)
34
- "gpt-5.5": price(5, 30, 0, 0, 0.5, "builtin"),
35
- "gpt-5.1": price(1.25, 10, 0, 0, 0.125, "builtin"),
36
- "gpt-5": price(1.25, 10, 0, 0, 0.125, "builtin"),
37
- "gpt-5-codex": price(1.25, 10, 0, 0, 0.125, "builtin"),
38
- "gpt-5.1-codex": price(1.25, 10, 0, 0, 0.125, "builtin"),
39
- "o3": price(2, 8, 0, 0, 0.5, "builtin")
40
- };
41
- }
42
- function normalizeModelId(modelId) {
43
- let s = modelId.toLowerCase();
44
- s = s.replace(/^anthropic[\/.]/, "");
45
- s = s.replace(/\[1m\]$/, "");
46
- s = s.replace(/-20\d{6}$/, "");
47
- return s.trim();
48
- }
49
- function resolvePrice(modelId, table) {
50
- const target = normalizeModelId(modelId);
51
- let bestKeyLen = -1;
52
- let bestPrice = null;
53
- for (const rawKey of Object.keys(table)) {
54
- const key = normalizeModelId(rawKey);
55
- if (key.length === 0 || !target.startsWith(key)) continue;
56
- if (key.length > bestKeyLen) {
57
- bestKeyLen = key.length;
58
- bestPrice = table[rawKey];
59
- }
60
- }
61
- return bestPrice ? { ...bestPrice } : null;
62
- }
63
- function computeCost(main, sidechain, table) {
64
- const byModel = /* @__PURE__ */ Object.create(null);
65
- const unknownModels = [];
66
- let usd = 0;
67
- const accumulate = (usage) => {
68
- for (const [model, tokens] of Object.entries(usage)) {
69
- const p = resolvePrice(model, table);
70
- let cost = 0;
71
- if (p === null) {
72
- if (!unknownModels.includes(model)) unknownModels.push(model);
73
- } else {
74
- cost = (tokens.input * p.input + tokens.output * p.output + tokens.cacheWrite5m * p.cacheWrite5m + tokens.cacheWrite1h * p.cacheWrite1h + tokens.cacheRead * p.cacheRead) / 1e6;
75
- }
76
- byModel[model] = (Object.hasOwn(byModel, model) ? byModel[model] : 0) + cost;
77
- usd += cost;
78
- }
79
- };
80
- accumulate(main);
81
- accumulate(sidechain);
82
- return { usd, byModel, unknownModels };
83
- }
84
- function cacheFilePath(cacheDir) {
85
- return path.join(cacheDir, "pricing.json");
86
- }
87
- async function readPriceCache(cacheDir) {
88
- try {
89
- const raw = await fs.readFile(cacheFilePath(cacheDir), "utf8");
90
- const parsed = JSON.parse(raw);
91
- if (parsed !== null && typeof parsed === "object" && typeof parsed.fetchedAt === "string" && typeof parsed.table === "object" && parsed.table !== null) {
92
- const p = parsed;
93
- return { fetchedAt: p.fetchedAt, table: p.table };
94
- }
95
- return null;
96
- } catch {
97
- return null;
98
- }
99
- }
100
- async function writePriceCache(cacheDir, table) {
101
- const file = cacheFilePath(cacheDir);
102
- const payload = { fetchedAt: (/* @__PURE__ */ new Date()).toISOString(), table };
103
- await fs.mkdir(path.dirname(file), { recursive: true });
104
- await fs.writeFile(file, JSON.stringify(payload, null, 2), "utf8");
105
- }
106
- function isCacheFresh(fetchedAt) {
107
- const t = Date.parse(fetchedAt);
108
- if (Number.isNaN(t)) return false;
109
- return Date.now() - t <= CACHE_FRESH_MS;
110
- }
111
- function toFiniteNumber(v) {
112
- return typeof v === "number" && Number.isFinite(v) ? v : null;
113
- }
114
- var LITELLM_OPENAI_KEY_RE = /^(gpt-|o3($|-)|codex-)/;
115
- function convertLiteLLMPayload(payload) {
116
- if (payload === null || typeof payload !== "object") {
117
- throw new Error("invalid litellm payload: not an object");
118
- }
119
- const table = {};
120
- for (const [rawKey, rawEntry] of Object.entries(payload)) {
121
- if (rawEntry === null || typeof rawEntry !== "object") continue;
122
- const entry = rawEntry;
123
- const provider = entry.litellm_provider;
124
- if (provider === "openai") {
125
- const key2 = rawKey.toLowerCase();
126
- if (!LITELLM_OPENAI_KEY_RE.test(key2)) continue;
127
- const inputRaw2 = toFiniteNumber(entry.input_cost_per_token);
128
- const outputRaw2 = toFiniteNumber(entry.output_cost_per_token);
129
- if (inputRaw2 === null || inputRaw2 <= 0) continue;
130
- if (outputRaw2 === null || outputRaw2 <= 0) continue;
131
- const cacheReadRaw2 = toFiniteNumber(entry.cache_read_input_token_cost);
132
- table[key2] = {
133
- input: inputRaw2 * 1e6,
134
- output: outputRaw2 * 1e6,
135
- cacheRead: cacheReadRaw2 !== null ? cacheReadRaw2 * 1e6 : 0,
136
- cacheWrite5m: 0,
137
- cacheWrite1h: 0,
138
- source: "litellm"
139
- };
140
- continue;
141
- }
142
- if (typeof provider === "string" && provider !== "anthropic") continue;
143
- let key = rawKey.toLowerCase();
144
- if (key.startsWith("anthropic/")) key = key.slice("anthropic/".length);
145
- if (!key.startsWith("claude")) continue;
146
- const inputRaw = toFiniteNumber(entry.input_cost_per_token);
147
- const outputRaw = toFiniteNumber(entry.output_cost_per_token);
148
- if (inputRaw === null || inputRaw <= 0) continue;
149
- if (outputRaw === null || outputRaw <= 0) continue;
150
- const input = inputRaw * 1e6;
151
- const output = outputRaw * 1e6;
152
- const cacheReadRaw = toFiniteNumber(entry.cache_read_input_token_cost);
153
- const cacheWrite5mRaw = toFiniteNumber(entry.cache_creation_input_token_cost);
154
- const cacheWrite1hRaw = toFiniteNumber(entry.cache_creation_input_token_cost_above_1hr);
155
- table[key] = {
156
- input,
157
- output,
158
- cacheRead: cacheReadRaw !== null ? cacheReadRaw * 1e6 : input * 0.1,
159
- cacheWrite5m: cacheWrite5mRaw !== null ? cacheWrite5mRaw * 1e6 : input * 1.25,
160
- cacheWrite1h: cacheWrite1hRaw !== null ? cacheWrite1hRaw * 1e6 : input * 2,
161
- source: "litellm"
162
- };
163
- }
164
- return table;
165
- }
166
- async function fetchLiteLLMPriceTable() {
167
- const controller = new AbortController();
168
- const timer = setTimeout(() => controller.abort(), LITELLM_FETCH_TIMEOUT_MS);
169
- try {
170
- const res = await fetch(LITELLM_URL, { signal: controller.signal });
171
- if (!res.ok) {
172
- throw new Error(`litellm fetch failed with status ${res.status}`);
173
- }
174
- const json = await res.json();
175
- return convertLiteLLMPayload(json);
176
- } finally {
177
- clearTimeout(timer);
178
- }
179
- }
180
- async function loadPriceTable(cacheDir, opts) {
181
- const builtin = builtinPriceTable();
182
- const cached = await readPriceCache(cacheDir);
183
- if (cached !== null && isCacheFresh(cached.fetchedAt)) {
184
- return { ...builtin, ...cached.table };
185
- }
186
- if (opts?.offline === true) {
187
- return cached !== null ? { ...builtin, ...cached.table } : builtin;
188
- }
189
- try {
190
- const remoteTable = await fetchLiteLLMPriceTable();
191
- await writePriceCache(cacheDir, remoteTable);
192
- return { ...builtin, ...remoteTable };
193
- } catch {
194
- return cached !== null ? { ...builtin, ...cached.table } : builtin;
195
- }
196
- }
197
-
198
3
  // src/fx.ts
199
4
  import { mkdir, readFile, writeFile } from "fs/promises";
200
5
  import { join } from "path";
@@ -204,7 +9,7 @@ var FX_SOURCES = [
204
9
  "https://api.frankfurter.dev/v1/latest?base=USD&symbols=JPY",
205
10
  "https://open.er-api.com/v6/latest/USD"
206
11
  ];
207
- function cacheFilePath2(cacheDir) {
12
+ function cacheFilePath(cacheDir) {
208
13
  return join(cacheDir, CACHE_FILE_NAME);
209
14
  }
210
15
  function isPositiveFiniteNumber(v) {
@@ -219,7 +24,7 @@ function parseFxCache(raw) {
219
24
  }
220
25
  async function readFxCache(cacheDir) {
221
26
  try {
222
- const raw = await readFile(cacheFilePath2(cacheDir), "utf8");
27
+ const raw = await readFile(cacheFilePath(cacheDir), "utf8");
223
28
  return parseFxCache(JSON.parse(raw));
224
29
  } catch {
225
30
  return null;
@@ -228,7 +33,7 @@ async function readFxCache(cacheDir) {
228
33
  async function writeFxCache(cacheDir, cache) {
229
34
  try {
230
35
  await mkdir(cacheDir, { recursive: true });
231
- await writeFile(cacheFilePath2(cacheDir), JSON.stringify(cache), "utf8");
36
+ await writeFile(cacheFilePath(cacheDir), JSON.stringify(cache), "utf8");
232
37
  } catch {
233
38
  }
234
39
  }
@@ -324,9 +129,9 @@ function sessionIdFromFilename(rolloutPath) {
324
129
  );
325
130
  return m !== null ? m[1] : "";
326
131
  }
327
- async function readAll(path2) {
132
+ async function readAll(path) {
328
133
  try {
329
- return await readFile2(path2);
134
+ return await readFile2(path);
330
135
  } catch {
331
136
  return null;
332
137
  }
@@ -365,6 +170,7 @@ async function scanWindow(rolloutPath, cursor) {
365
170
  let windowTurnCtxCwd = null;
366
171
  let sessionMetaCwd = null;
367
172
  let sessionMetaSid = null;
173
+ let isSubagentRollout = false;
368
174
  let firstTs = null;
369
175
  let lastTs = null;
370
176
  const segments = [];
@@ -407,6 +213,8 @@ async function scanWindow(rolloutPath, cursor) {
407
213
  if (sid !== null) sessionMetaSid = sid;
408
214
  const c = strOrNull(payload.cwd);
409
215
  if (c !== null) sessionMetaCwd = c;
216
+ const source = payload.source;
217
+ if (isRecord(source) && Object.hasOwn(source, "subagent")) isSubagentRollout = true;
410
218
  return;
411
219
  }
412
220
  if (type === "turn_context") {
@@ -474,6 +282,7 @@ async function scanWindow(rolloutPath, cursor) {
474
282
  prompt: windowPrompt,
475
283
  cwd: windowTurnCtxCwd ?? sessionMetaCwd,
476
284
  sessionId: sessionMetaSid ?? sessionIdFromFilename(rolloutPath),
285
+ isSubagentRollout,
477
286
  firstTs,
478
287
  lastTs,
479
288
  newOffset
@@ -513,17 +322,11 @@ async function splitIntoCodexTurnDrafts(rolloutPath, cursor) {
513
322
  if (scan === null || isZeroTotals(scan.acc)) return null;
514
323
  const picked = scan.segments.filter((s) => !isZeroTotals(s.acc));
515
324
  if (scan.open !== null && !isZeroTotals(scan.open.acc)) {
516
- const last = picked[picked.length - 1];
517
- if (last !== void 0) {
518
- addTotals(last.acc, scan.open.acc);
519
- last.apiCalls += scan.open.apiCalls;
520
- if (scan.open.endTs !== null) last.endTs = scan.open.endTs;
521
- } else {
522
- picked.push(scan.open);
523
- }
325
+ picked.push(scan.open);
524
326
  }
525
327
  const lastIndex = picked.length - 1;
526
328
  return picked.map((s, i) => ({
329
+ isSubagentRollout: scan.isSubagentRollout,
527
330
  agg: {
528
331
  sessionId: scan.sessionId,
529
332
  // session_meta はファイル先頭にしか無いので全ドラフト共通
@@ -607,14 +410,14 @@ function promptCandidate(content) {
607
410
  }
608
411
  return null;
609
412
  }
610
- async function readAll2(path2) {
413
+ async function readAll2(path) {
611
414
  try {
612
- return await readFile3(path2);
415
+ return await readFile3(path);
613
416
  } catch {
614
417
  return null;
615
418
  }
616
419
  }
617
- async function aggregateNewTurn(transcriptPath, cursor) {
420
+ async function aggregateNewTurn(transcriptPath, cursor, opts = {}) {
618
421
  const buffer = await readAll2(transcriptPath);
619
422
  if (buffer === null) return null;
620
423
  const fileSize = buffer.length;
@@ -630,6 +433,7 @@ async function aggregateNewTurn(transcriptPath, cursor) {
630
433
  const seenKeys = new Set(cursor?.seenMessageKeys ?? []);
631
434
  const tsFloor = cursor?.lastTs ?? null;
632
435
  const pending = /* @__PURE__ */ new Map();
436
+ const consumedKeys = /* @__PURE__ */ new Set();
633
437
  let sessionId = "";
634
438
  let cwd = null;
635
439
  let gitBranch = null;
@@ -681,6 +485,13 @@ async function aggregateNewTurn(transcriptPath, cursor) {
681
485
  const reqId = strOrNull2(obj.requestId) ?? "";
682
486
  const key = `${id}:${reqId}`;
683
487
  if (!seenKeys.has(key)) {
488
+ const tsMs = ts === null ? NaN : Date.parse(ts);
489
+ const outsidePeriod = typeof opts.minTimestampMs === "number" && (!Number.isFinite(tsMs) || tsMs < opts.minTimestampMs);
490
+ if (opts.excludeMessageKeys?.has(key) || outsidePeriod) {
491
+ consumedKeys.add(key);
492
+ pending.delete(key);
493
+ return;
494
+ }
684
495
  const model = strOrNull2(rawModel) ?? "unknown";
685
496
  pending.set(key, { model, isSidechain: isSide, bucket: extractBucket(usage) });
686
497
  }
@@ -695,7 +506,7 @@ async function aggregateNewTurn(transcriptPath, cursor) {
695
506
  lineStart = pos + 1;
696
507
  }
697
508
  const newOffset = lineStart;
698
- if (pending.size === 0) return null;
509
+ if (pending.size === 0 && !opts.returnEmpty) return null;
699
510
  const main = {};
700
511
  const sidechain = {};
701
512
  const newKeys = [];
@@ -704,13 +515,14 @@ async function aggregateNewTurn(transcriptPath, cursor) {
704
515
  if (pm.isSidechain) addToModel(sidechain, pm.model, pm.bucket);
705
516
  else addToModel(main, pm.model, pm.bucket);
706
517
  }
707
- const combined = [...cursor?.seenMessageKeys ?? [], ...newKeys];
518
+ const combined = [...cursor?.seenMessageKeys ?? [], ...consumedKeys, ...newKeys];
708
519
  const seenMessageKeys = combined.length > MAX_SEEN_KEYS ? combined.slice(combined.length - MAX_SEEN_KEYS) : combined;
709
520
  return {
710
521
  sessionId,
711
522
  main,
712
523
  sidechain,
713
524
  apiCalls: pending.size,
525
+ messageKeys: newKeys,
714
526
  prompt,
715
527
  cwd,
716
528
  gitBranch,
@@ -718,16 +530,14 @@ async function aggregateNewTurn(transcriptPath, cursor) {
718
530
  lastTs,
719
531
  newCursor: {
720
532
  offset: newOffset,
721
- lastUuid,
722
- lastTs,
533
+ lastUuid: lastUuid ?? cursor?.lastUuid ?? null,
534
+ lastTs: lastTs ?? cursor?.lastTs ?? null,
723
535
  seenMessageKeys
724
536
  }
725
537
  };
726
538
  }
727
539
 
728
540
  export {
729
- computeCost,
730
- loadPriceTable,
731
541
  getUsdJpy,
732
542
  aggregateCodexTurn,
733
543
  splitIntoCodexTurnDrafts,
@@ -36,7 +36,7 @@ var DEFAULT_CONFIG = {
36
36
  notify: { os: true, slack: null },
37
37
  minNotifyUSD: 0,
38
38
  costLabel: "api_equivalent",
39
- fx: { fallbackRate: 150, cacheHours: 12 },
39
+ fx: { fallbackRate: 160, cacheHours: 12 },
40
40
  includeDailyTotal: true,
41
41
  monthlyBudgetUSD: 0,
42
42
  dashboard: { autoRegenerate: true, autoReloadSec: 30, days: 30 }
@@ -145,6 +145,23 @@ function readConfig() {
145
145
  }
146
146
  return mergeConfig(parsed);
147
147
  }
148
+ function readConfigReadOnly(onError) {
149
+ const file = configFilePath();
150
+ if (!existsSync(file)) return structuredClone(DEFAULT_CONFIG);
151
+ let raw;
152
+ try {
153
+ raw = readFileSync(file, "utf8");
154
+ } catch (err) {
155
+ onError?.(err);
156
+ return structuredClone(DEFAULT_CONFIG);
157
+ }
158
+ try {
159
+ return mergeConfig(JSON.parse(raw));
160
+ } catch (err) {
161
+ onError?.(err);
162
+ return structuredClone(DEFAULT_CONFIG);
163
+ }
164
+ }
148
165
  function readMuteState() {
149
166
  const p = paths();
150
167
  if (!existsSync(p.muteFile)) return null;
@@ -244,6 +261,11 @@ function appendTurn(record) {
244
261
  const { subagentActivity: _runtimeActivity, ...persisted } = record;
245
262
  appendFileSync(p.historyFile, JSON.stringify(persisted) + "\n", "utf8");
246
263
  }
264
+ function resetHistoryAndCursors() {
265
+ const p = paths();
266
+ rmSync(p.historyFile, { force: true });
267
+ rmSync(p.cursorsFile, { force: true });
268
+ }
247
269
  function readTurns(days) {
248
270
  const p = paths();
249
271
  if (!existsSync(p.historyFile)) return [];
@@ -1062,6 +1084,7 @@ export {
1062
1084
  configFilePath,
1063
1085
  paths,
1064
1086
  readConfig,
1087
+ readConfigReadOnly,
1065
1088
  readMuteState,
1066
1089
  isMuted,
1067
1090
  writeMuteState,
@@ -1070,6 +1093,7 @@ export {
1070
1093
  sanitizeCursor,
1071
1094
  saveCursor,
1072
1095
  appendTurn,
1096
+ resetHistoryAndCursors,
1073
1097
  readTurns,
1074
1098
  todayTotalUSD,
1075
1099
  currentMonthTotals,
@@ -0,0 +1,124 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ aggregateNewTurn
4
+ } from "./chunk-D4D76RGQ.js";
5
+ import {
6
+ loadCursor,
7
+ logError,
8
+ sanitizeCursor
9
+ } from "./chunk-OOAC5ULQ.js";
10
+
11
+ // src/subagents.ts
12
+ import { promises as fs } from "fs";
13
+ import { join } from "path";
14
+ var MAX_AGENT_FILES = 200;
15
+ function emptyBuckets() {
16
+ return { input: 0, output: 0, cacheWrite5m: 0, cacheWrite1h: 0, cacheRead: 0 };
17
+ }
18
+ function addToModel(target, model, b) {
19
+ const cur = target[model] ?? emptyBuckets();
20
+ cur.input += b.input;
21
+ cur.output += b.output;
22
+ cur.cacheWrite5m += b.cacheWrite5m;
23
+ cur.cacheWrite1h += b.cacheWrite1h;
24
+ cur.cacheRead += b.cacheRead;
25
+ target[model] = cur;
26
+ }
27
+ function mergeUsage(target, src) {
28
+ for (const [model, b] of Object.entries(src)) addToModel(target, model, b);
29
+ }
30
+ function sameCursor(a, b) {
31
+ 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]);
32
+ }
33
+ function subagentsDirOf(mainTranscriptPath) {
34
+ const base = mainTranscriptPath.endsWith(".jsonl") ? mainTranscriptPath.slice(0, -".jsonl".length) : mainTranscriptPath;
35
+ return join(base, "subagents");
36
+ }
37
+ async function listAgentFiles(dir, entries, includeAllFiles) {
38
+ const files = entries.filter((e) => e.isFile() && e.name.startsWith("agent-") && e.name.endsWith(".jsonl")).map((e) => join(dir, e.name));
39
+ if (includeAllFiles || files.length <= MAX_AGENT_FILES) return files;
40
+ const withMtime = [];
41
+ for (const p of files) {
42
+ let mtime = 0;
43
+ try {
44
+ mtime = (await fs.stat(p)).mtimeMs;
45
+ } catch {
46
+ mtime = 0;
47
+ }
48
+ withMtime.push({ path: p, mtime });
49
+ }
50
+ withMtime.sort((a, b) => b.mtime - a.mtime);
51
+ return withMtime.slice(0, MAX_AGENT_FILES).map((x) => x.path);
52
+ }
53
+ async function collectSubagentUsage(mainTranscriptPath, opts = {}) {
54
+ const dir = subagentsDirOf(mainTranscriptPath);
55
+ let entries;
56
+ try {
57
+ entries = await fs.readdir(dir, { withFileTypes: true });
58
+ } catch (err) {
59
+ if (opts.strictRead && err.code !== "ENOENT") throw err;
60
+ return null;
61
+ }
62
+ const files = await listAgentFiles(dir, entries, opts.includeAllFiles === true);
63
+ const perModel = {};
64
+ let apiCalls = 0;
65
+ let agentFiles = 0;
66
+ const newCursors = [];
67
+ const groups = [];
68
+ let sessionId = "";
69
+ let cwd = null;
70
+ let gitBranch = null;
71
+ let lastTs = null;
72
+ const excluded = new Set(opts.excludeMessageKeys ?? []);
73
+ if (!opts.ignoreCursors) {
74
+ for (const filePath of files) {
75
+ const prior = sanitizeCursor(loadCursor(filePath));
76
+ for (const key of prior?.seenMessageKeys ?? []) excluded.add(key);
77
+ }
78
+ }
79
+ for (const filePath of files) {
80
+ try {
81
+ if (opts.strictRead) {
82
+ const file = await fs.open(filePath, "r");
83
+ await file.close();
84
+ }
85
+ const cursor = opts.ignoreCursors ? null : sanitizeCursor(loadCursor(filePath));
86
+ const agg = await aggregateNewTurn(filePath, cursor, {
87
+ excludeMessageKeys: excluded,
88
+ minTimestampMs: opts.minTimestampMs,
89
+ returnEmpty: true
90
+ });
91
+ if (agg === null) continue;
92
+ if (!sameCursor(cursor, agg.newCursor)) {
93
+ newCursors.push({ path: filePath, cursor: agg.newCursor });
94
+ }
95
+ for (const key of agg.messageKeys) excluded.add(key);
96
+ if (agg.apiCalls === 0) continue;
97
+ mergeUsage(perModel, agg.main);
98
+ mergeUsage(perModel, agg.sidechain);
99
+ apiCalls += agg.apiCalls;
100
+ agentFiles += 1;
101
+ const groupUsage = {};
102
+ mergeUsage(groupUsage, agg.main);
103
+ mergeUsage(groupUsage, agg.sidechain);
104
+ groups.push({
105
+ perModel: groupUsage,
106
+ apiCalls: agg.apiCalls,
107
+ firstTs: agg.firstTs,
108
+ lastTs: agg.lastTs
109
+ });
110
+ if (agg.sessionId) sessionId = agg.sessionId;
111
+ if (agg.cwd !== null) cwd = agg.cwd;
112
+ if (agg.gitBranch !== null) gitBranch = agg.gitBranch;
113
+ if (agg.lastTs !== null && (lastTs === null || agg.lastTs > lastTs)) lastTs = agg.lastTs;
114
+ } catch (err) {
115
+ if (opts.strictRead) throw err;
116
+ logError("subagents:file", err);
117
+ }
118
+ }
119
+ return { perModel, apiCalls, agentFiles, newCursors, groups, sessionId, cwd, gitBranch, lastTs };
120
+ }
121
+
122
+ export {
123
+ collectSubagentUsage
124
+ };
@@ -4,7 +4,7 @@ import {
4
4
  isMuted,
5
5
  readMuteState,
6
6
  writeMuteState
7
- } from "./chunk-5PH7PPD6.js";
7
+ } from "./chunk-OOAC5ULQ.js";
8
8
 
9
9
  // src/mute.ts
10
10
  function parseDuration(arg) {
package/dist/cli.js CHANGED
@@ -1,21 +1,18 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  fmtMuteUntil
4
- } from "./chunk-DDUK4EWQ.js";
4
+ } from "./chunk-T6Z2ZAN4.js";
5
5
  import {
6
6
  CODEX_HOOK_EVENTS,
7
7
  CODEX_HOOK_TIMEOUT_SECONDS,
8
8
  matchesMarker,
9
9
  parseConfiguredOwnedCodexHookCommand
10
- } from "./chunk-S3XKO7MY.js";
10
+ } from "./chunk-CH34OJ7Q.js";
11
11
  import {
12
12
  notifyOS,
13
13
  notifySlack,
14
14
  selectNotifyBackend
15
15
  } from "./chunk-NV5UOHJA.js";
16
- import {
17
- isWSL
18
- } from "./chunk-DGXUSPS4.js";
19
16
  import {
20
17
  findLatestCodexRollout
21
18
  } from "./chunk-HLJAJS2W.js";
@@ -25,11 +22,16 @@ import {
25
22
  } from "./chunk-HTYUYKFW.js";
26
23
  import {
27
24
  aggregateNewTurn,
28
- computeCost,
29
25
  getUsdJpy,
30
- loadPriceTable,
31
26
  splitIntoCodexTurnDrafts
32
- } from "./chunk-LVMKY6JB.js";
27
+ } from "./chunk-D4D76RGQ.js";
28
+ import {
29
+ computeCost,
30
+ loadPriceTable
31
+ } from "./chunk-6HTETN26.js";
32
+ import {
33
+ isWSL
34
+ } from "./chunk-DGXUSPS4.js";
33
35
  import {
34
36
  formatJPY,
35
37
  formatTokens,
@@ -41,7 +43,7 @@ import {
41
43
  readConfig,
42
44
  readMuteState,
43
45
  readTurns
44
- } from "./chunk-5PH7PPD6.js";
46
+ } from "./chunk-OOAC5ULQ.js";
45
47
 
46
48
  // src/cli.ts
47
49
  import { realpathSync as realpathSync2 } from "fs";
@@ -871,9 +873,9 @@ var COMMANDS = [
871
873
  en: "Generate and open the HTML dashboard"
872
874
  },
873
875
  {
874
- cmd: "sweep [--dry-run] [--days N] [--include-active]",
875
- ja: "\u904E\u53BB\u306E\u672A\u8A08\u4E0A\u5206\u3092\u4E00\u62EC\u3067\u5C65\u6B74\u306B\u53D6\u308A\u8FBC\u3080(\u9032\u884C\u4E2D\u30BB\u30C3\u30B7\u30E7\u30F3\u306F\u81EA\u52D5\u30B9\u30AD\u30C3\u30D7)",
876
- en: "Backfill uncounted history (active sessions are skipped)"
876
+ cmd: "sweep [--dry-run] [--days N]",
877
+ ja: "\u5C65\u6B74\u3068\u53D6\u308A\u8FBC\u307F\u4F4D\u7F6E\u3092\u6368\u3066\u3001\u5143JSONL\u304B\u3089\u6982\u7B97\u3092\u518D\u751F\u6210(--dry-run\u306Fpreview)",
878
+ en: "Reset and rebuild estimates from source JSONL (--dry-run previews)"
877
879
  },
878
880
  {
879
881
  cmd: "history <clear|redact> [--days N] [--yes]",
@@ -906,13 +908,13 @@ function isCodexPassiveEvent(value) {
906
908
  async function runCodexPassiveHook(event, text) {
907
909
  try {
908
910
  if (event === "Stop") {
909
- const trackMod = await import("./track-KEZON6KI.js");
911
+ const trackMod = await import("./track-H4AIT575.js");
910
912
  await trackMod.runTrack(text, { codex: true });
911
913
  } else if (event === "UserPromptSubmit") {
912
- const activity = await import("./subagent-store-A6MIN22X.js");
914
+ const activity = await import("./subagent-store-US4TJJQR.js");
913
915
  activity.handleCodexUserPromptSubmitHook(text);
914
916
  } else {
915
- const activity = await import("./subagent-store-A6MIN22X.js");
917
+ const activity = await import("./subagent-store-US4TJJQR.js");
916
918
  activity.handleCodexSubagentHook(text, event === "SubagentStart" ? "start" : "stop");
917
919
  }
918
920
  } catch {
@@ -991,18 +993,18 @@ async function main(argv) {
991
993
  const text = await readStdin();
992
994
  const codex = rest.includes("--codex");
993
995
  try {
994
- const trackMod = await import("./track-KEZON6KI.js");
996
+ const trackMod = await import("./track-H4AIT575.js");
995
997
  await trackMod.runTrack(text, { codex });
996
998
  } catch {
997
999
  }
998
1000
  return 0;
999
1001
  }
1000
1002
  case "init": {
1001
- const { runInit } = await import("./setup-IXPMVWOE.js");
1003
+ const { runInit } = await import("./setup-3SRNQ5PG.js");
1002
1004
  return await runInit(rest);
1003
1005
  }
1004
1006
  case "uninstall": {
1005
- const { runUninstall } = await import("./setup-IXPMVWOE.js");
1007
+ const { runUninstall } = await import("./setup-3SRNQ5PG.js");
1006
1008
  return await runUninstall(rest);
1007
1009
  }
1008
1010
  case "doctor":
@@ -1010,27 +1012,27 @@ async function main(argv) {
1010
1012
  case "report":
1011
1013
  return await runReport(rest);
1012
1014
  case "dashboard": {
1013
- const { runDashboard } = await import("./dashboard-6F2QNUJT.js");
1015
+ const { runDashboard } = await import("./dashboard-NORNHUPT.js");
1014
1016
  return await runDashboard(rest);
1015
1017
  }
1016
1018
  case "sweep": {
1017
- const { runSweep } = await import("./sweep-UIONM4UA.js");
1019
+ const { runSweep } = await import("./sweep-RUPPGAWJ.js");
1018
1020
  return await runSweep(rest);
1019
1021
  }
1020
1022
  case "history": {
1021
- const { runHistory } = await import("./history-7KN26LJH.js");
1023
+ const { runHistory } = await import("./history-DL4SG3PG.js");
1022
1024
  return await runHistory(rest);
1023
1025
  }
1024
1026
  case "budget": {
1025
- const { runBudget } = await import("./budget-TBWVHUKA.js");
1027
+ const { runBudget } = await import("./budget-V7CCMJHF.js");
1026
1028
  return runBudget(rest);
1027
1029
  }
1028
1030
  case "mute": {
1029
- const { runMute } = await import("./mute-MPX3K7QN.js");
1031
+ const { runMute } = await import("./mute-UIR5P5N5.js");
1030
1032
  return runMute(rest);
1031
1033
  }
1032
1034
  case "unmute": {
1033
- const { runUnmute } = await import("./mute-MPX3K7QN.js");
1035
+ const { runUnmute } = await import("./mute-UIR5P5N5.js");
1034
1036
  return runUnmute();
1035
1037
  }
1036
1038
  case "--version":