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.
- package/README.md +129 -124
- package/dist/{budget-TBWVHUKA.js → budget-V7CCMJHF.js} +1 -1
- package/dist/{chunk-O34L3NSI.js → chunk-27SJELD2.js} +107 -22
- package/dist/{chunk-QWBQN4XY.js → chunk-4JSRGJCZ.js} +42 -18
- package/dist/chunk-6HTETN26.js +226 -0
- package/dist/{chunk-S3XKO7MY.js → chunk-CH34OJ7Q.js} +7 -3
- package/dist/{chunk-LVMKY6JB.js → chunk-D4D76RGQ.js} +27 -217
- package/dist/{chunk-5PH7PPD6.js → chunk-OOAC5ULQ.js} +25 -1
- package/dist/chunk-OYD6H3ZZ.js +124 -0
- package/dist/{chunk-DDUK4EWQ.js → chunk-T6Z2ZAN4.js} +1 -1
- package/dist/cli.js +26 -24
- package/dist/{dashboard-6F2QNUJT.js → dashboard-NORNHUPT.js} +3 -4
- package/dist/{history-7KN26LJH.js → history-DL4SG3PG.js} +3 -5
- package/dist/{mute-MPX3K7QN.js → mute-UIR5P5N5.js} +2 -2
- package/dist/{setup-IXPMVWOE.js → setup-3SRNQ5PG.js} +4 -3
- package/dist/{subagent-store-A6MIN22X.js → subagent-store-US4TJJQR.js} +1 -1
- package/dist/sweep-RUPPGAWJ.js +795 -0
- package/dist/{track-KEZON6KI.js → track-H4AIT575.js} +40 -28
- package/package.json +1 -1
- package/dist/chunk-ENGUOLTD.js +0 -92
- package/dist/chunk-O2AR4PXU.js +0 -82
- package/dist/sweep-UIONM4UA.js +0 -589
|
@@ -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
|
|
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(
|
|
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(
|
|
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(
|
|
132
|
+
async function readAll(path) {
|
|
328
133
|
try {
|
|
329
|
-
return await readFile2(
|
|
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
|
-
|
|
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(
|
|
413
|
+
async function readAll2(path) {
|
|
611
414
|
try {
|
|
612
|
-
return await readFile3(
|
|
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:
|
|
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
|
+
};
|
package/dist/cli.js
CHANGED
|
@@ -1,21 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
fmtMuteUntil
|
|
4
|
-
} from "./chunk-
|
|
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-
|
|
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-
|
|
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-
|
|
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]
|
|
875
|
-
ja: "\
|
|
876
|
-
en: "
|
|
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-
|
|
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-
|
|
914
|
+
const activity = await import("./subagent-store-US4TJJQR.js");
|
|
913
915
|
activity.handleCodexUserPromptSubmitHook(text);
|
|
914
916
|
} else {
|
|
915
|
-
const activity = await import("./subagent-store-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
1035
|
+
const { runUnmute } = await import("./mute-UIR5P5N5.js");
|
|
1034
1036
|
return runUnmute();
|
|
1035
1037
|
}
|
|
1036
1038
|
case "--version":
|