ccc-notifier 0.4.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 -122
- package/dist/{budget-EHWPEYXY.js → budget-V7CCMJHF.js} +1 -1
- package/dist/{chunk-J6Y3RMMC.js → chunk-27SJELD2.js} +107 -22
- package/dist/{chunk-2VPBBIDW.js → chunk-4JSRGJCZ.js} +61 -22
- package/dist/chunk-6HTETN26.js +226 -0
- package/dist/{chunk-5LHZOZZO.js → chunk-CH34OJ7Q.js} +241 -110
- package/dist/chunk-D4D76RGQ.js +547 -0
- package/dist/chunk-HLJAJS2W.js +55 -0
- package/dist/chunk-OOAC5ULQ.js +1101 -0
- package/dist/chunk-OYD6H3ZZ.js +124 -0
- package/dist/{chunk-TUZVISLD.js → chunk-T6Z2ZAN4.js} +1 -1
- package/dist/cli.js +388 -77
- package/dist/{dashboard-GYKABTTN.js → dashboard-NORNHUPT.js} +3 -4
- package/dist/{history-6OUJLXJT.js → history-DL4SG3PG.js} +3 -5
- package/dist/{mute-GBDNN5PB.js → mute-UIR5P5N5.js} +2 -2
- package/dist/{setup-W3CSTHJC.js → setup-3SRNQ5PG.js} +4 -3
- package/dist/subagent-store-US4TJJQR.js +31 -0
- package/dist/sweep-RUPPGAWJ.js +795 -0
- package/dist/{track-QT2U3E2R.js → track-H4AIT575.js} +53 -31
- package/package.json +1 -1
- package/dist/chunk-26CISNOE.js +0 -324
- package/dist/chunk-CKVK3UCA.js +0 -357
- package/dist/chunk-LHKBGA5K.js +0 -462
- package/dist/chunk-ZHIZZ6V5.js +0 -92
- package/dist/sweep-IBSV4LRD.js +0 -604
package/dist/chunk-LHKBGA5K.js
DELETED
|
@@ -1,462 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
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 = {};
|
|
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] = (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
|
-
// src/fx.ts
|
|
199
|
-
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
200
|
-
import { join } from "path";
|
|
201
|
-
var FETCH_TIMEOUT_MS = 1500;
|
|
202
|
-
var CACHE_FILE_NAME = "fx.json";
|
|
203
|
-
var FX_SOURCES = [
|
|
204
|
-
"https://api.frankfurter.dev/v1/latest?base=USD&symbols=JPY",
|
|
205
|
-
"https://open.er-api.com/v6/latest/USD"
|
|
206
|
-
];
|
|
207
|
-
function cacheFilePath2(cacheDir) {
|
|
208
|
-
return join(cacheDir, CACHE_FILE_NAME);
|
|
209
|
-
}
|
|
210
|
-
function isPositiveFiniteNumber(v) {
|
|
211
|
-
return typeof v === "number" && Number.isFinite(v) && v > 0;
|
|
212
|
-
}
|
|
213
|
-
function parseFxCache(raw) {
|
|
214
|
-
if (typeof raw !== "object" || raw === null) return null;
|
|
215
|
-
const obj = raw;
|
|
216
|
-
if (!isPositiveFiniteNumber(obj.rate)) return null;
|
|
217
|
-
if (typeof obj.fetchedAt !== "string") return null;
|
|
218
|
-
return { rate: obj.rate, fetchedAt: obj.fetchedAt };
|
|
219
|
-
}
|
|
220
|
-
async function readFxCache(cacheDir) {
|
|
221
|
-
try {
|
|
222
|
-
const raw = await readFile(cacheFilePath2(cacheDir), "utf8");
|
|
223
|
-
return parseFxCache(JSON.parse(raw));
|
|
224
|
-
} catch {
|
|
225
|
-
return null;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
async function writeFxCache(cacheDir, cache) {
|
|
229
|
-
try {
|
|
230
|
-
await mkdir(cacheDir, { recursive: true });
|
|
231
|
-
await writeFile(cacheFilePath2(cacheDir), JSON.stringify(cache), "utf8");
|
|
232
|
-
} catch {
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
function isFresh(fetchedAt, cacheHours) {
|
|
236
|
-
const fetchedMs = Date.parse(fetchedAt);
|
|
237
|
-
if (Number.isNaN(fetchedMs)) return false;
|
|
238
|
-
const ageMs = Date.now() - fetchedMs;
|
|
239
|
-
return ageMs <= cacheHours * 60 * 60 * 1e3;
|
|
240
|
-
}
|
|
241
|
-
function extractJpyRate(json) {
|
|
242
|
-
if (typeof json !== "object" || json === null) return null;
|
|
243
|
-
const rates = json.rates;
|
|
244
|
-
if (typeof rates !== "object" || rates === null) return null;
|
|
245
|
-
const jpy = rates.JPY;
|
|
246
|
-
return isPositiveFiniteNumber(jpy) ? jpy : null;
|
|
247
|
-
}
|
|
248
|
-
async function fetchJpyRate(url) {
|
|
249
|
-
const controller = new AbortController();
|
|
250
|
-
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
251
|
-
try {
|
|
252
|
-
const res = await fetch(url, { signal: controller.signal });
|
|
253
|
-
const json = await res.json();
|
|
254
|
-
return extractJpyRate(json);
|
|
255
|
-
} catch {
|
|
256
|
-
return null;
|
|
257
|
-
} finally {
|
|
258
|
-
clearTimeout(timer);
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
async function getUsdJpy(cfg, cacheDir) {
|
|
262
|
-
const cache = await readFxCache(cacheDir);
|
|
263
|
-
if (cache && isFresh(cache.fetchedAt, cfg.fx.cacheHours)) {
|
|
264
|
-
return { rate: cache.rate, source: "cache", fetchedAt: cache.fetchedAt };
|
|
265
|
-
}
|
|
266
|
-
for (const url of FX_SOURCES) {
|
|
267
|
-
const rate = await fetchJpyRate(url);
|
|
268
|
-
if (rate !== null) {
|
|
269
|
-
const fetchedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
270
|
-
await writeFxCache(cacheDir, { rate, fetchedAt });
|
|
271
|
-
return { rate, source: "live", fetchedAt };
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
if (cache) {
|
|
275
|
-
return { rate: cache.rate, source: "cache", fetchedAt: cache.fetchedAt };
|
|
276
|
-
}
|
|
277
|
-
return { rate: cfg.fx.fallbackRate, source: "fixed", fetchedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// src/transcript.ts
|
|
281
|
-
import { readFile as readFile2 } from "fs/promises";
|
|
282
|
-
var NEWLINE = 10;
|
|
283
|
-
var MAX_SEEN_KEYS = 500;
|
|
284
|
-
var SYNTHETIC_MODEL = "<synthetic>";
|
|
285
|
-
function isRecord(v) {
|
|
286
|
-
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
287
|
-
}
|
|
288
|
-
function numOf(v) {
|
|
289
|
-
return typeof v === "number" && Number.isFinite(v) ? v : 0;
|
|
290
|
-
}
|
|
291
|
-
function strOrNull(v) {
|
|
292
|
-
return typeof v === "string" ? v : null;
|
|
293
|
-
}
|
|
294
|
-
function emptyBuckets() {
|
|
295
|
-
return { input: 0, output: 0, cacheWrite5m: 0, cacheWrite1h: 0, cacheRead: 0 };
|
|
296
|
-
}
|
|
297
|
-
function addToModel(target, model, b) {
|
|
298
|
-
const cur = target[model] ?? emptyBuckets();
|
|
299
|
-
cur.input += b.input;
|
|
300
|
-
cur.output += b.output;
|
|
301
|
-
cur.cacheWrite5m += b.cacheWrite5m;
|
|
302
|
-
cur.cacheWrite1h += b.cacheWrite1h;
|
|
303
|
-
cur.cacheRead += b.cacheRead;
|
|
304
|
-
target[model] = cur;
|
|
305
|
-
}
|
|
306
|
-
function extractBucket(usage) {
|
|
307
|
-
const input = numOf(usage.input_tokens);
|
|
308
|
-
const output = numOf(usage.output_tokens);
|
|
309
|
-
const cacheRead = numOf(usage.cache_read_input_tokens);
|
|
310
|
-
let cacheWrite5m;
|
|
311
|
-
let cacheWrite1h;
|
|
312
|
-
const cc = usage.cache_creation;
|
|
313
|
-
if (isRecord(cc)) {
|
|
314
|
-
cacheWrite5m = numOf(cc.ephemeral_5m_input_tokens);
|
|
315
|
-
cacheWrite1h = numOf(cc.ephemeral_1h_input_tokens);
|
|
316
|
-
} else {
|
|
317
|
-
cacheWrite5m = numOf(usage.cache_creation_input_tokens);
|
|
318
|
-
cacheWrite1h = 0;
|
|
319
|
-
}
|
|
320
|
-
return { input, output, cacheWrite5m, cacheWrite1h, cacheRead };
|
|
321
|
-
}
|
|
322
|
-
function promptCandidate(content) {
|
|
323
|
-
if (typeof content === "string") return content;
|
|
324
|
-
if (Array.isArray(content)) {
|
|
325
|
-
let hasToolResult = false;
|
|
326
|
-
const texts = [];
|
|
327
|
-
for (const block of content) {
|
|
328
|
-
if (!isRecord(block)) continue;
|
|
329
|
-
if (block.type === "tool_result") hasToolResult = true;
|
|
330
|
-
else if (block.type === "text" && typeof block.text === "string") texts.push(block.text);
|
|
331
|
-
}
|
|
332
|
-
if (hasToolResult) return null;
|
|
333
|
-
return texts.join("\n");
|
|
334
|
-
}
|
|
335
|
-
return null;
|
|
336
|
-
}
|
|
337
|
-
async function readAll(path2) {
|
|
338
|
-
try {
|
|
339
|
-
return await readFile2(path2);
|
|
340
|
-
} catch {
|
|
341
|
-
return null;
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
async function aggregateNewTurn(transcriptPath, cursor) {
|
|
345
|
-
const buffer = await readAll(transcriptPath);
|
|
346
|
-
if (buffer === null) return null;
|
|
347
|
-
const fileSize = buffer.length;
|
|
348
|
-
let startOffset;
|
|
349
|
-
let rescan;
|
|
350
|
-
if (cursor !== null && cursor.offset > 0 && cursor.offset <= fileSize && buffer[cursor.offset - 1] === NEWLINE) {
|
|
351
|
-
startOffset = cursor.offset;
|
|
352
|
-
rescan = false;
|
|
353
|
-
} else {
|
|
354
|
-
startOffset = 0;
|
|
355
|
-
rescan = cursor !== null;
|
|
356
|
-
}
|
|
357
|
-
const seenKeys = new Set(cursor?.seenMessageKeys ?? []);
|
|
358
|
-
const tsFloor = cursor?.lastTs ?? null;
|
|
359
|
-
const pending = /* @__PURE__ */ new Map();
|
|
360
|
-
let sessionId = "";
|
|
361
|
-
let cwd = null;
|
|
362
|
-
let gitBranch = null;
|
|
363
|
-
let firstTs = null;
|
|
364
|
-
let lastTs = null;
|
|
365
|
-
let lastUuid = null;
|
|
366
|
-
let prompt = null;
|
|
367
|
-
const handleLine = (raw) => {
|
|
368
|
-
if (raw.trim().length === 0) return;
|
|
369
|
-
let obj;
|
|
370
|
-
try {
|
|
371
|
-
obj = JSON.parse(raw);
|
|
372
|
-
} catch {
|
|
373
|
-
return;
|
|
374
|
-
}
|
|
375
|
-
if (!isRecord(obj)) return;
|
|
376
|
-
const ts = strOrNull(obj.timestamp);
|
|
377
|
-
if (rescan && tsFloor !== null && ts !== null && ts <= tsFloor) return;
|
|
378
|
-
const isSide = obj.isSidechain === true;
|
|
379
|
-
const sid = strOrNull(obj.sessionId);
|
|
380
|
-
if (sid !== null) sessionId = sid;
|
|
381
|
-
if (!isSide) {
|
|
382
|
-
const c = strOrNull(obj.cwd);
|
|
383
|
-
if (c !== null) cwd = c;
|
|
384
|
-
const gb = strOrNull(obj.gitBranch);
|
|
385
|
-
if (gb !== null) gitBranch = gb;
|
|
386
|
-
}
|
|
387
|
-
if (ts !== null) {
|
|
388
|
-
if (firstTs === null || ts < firstTs) firstTs = ts;
|
|
389
|
-
if (lastTs === null || ts > lastTs) lastTs = ts;
|
|
390
|
-
}
|
|
391
|
-
const uuid = strOrNull(obj.uuid);
|
|
392
|
-
if (uuid !== null) lastUuid = uuid;
|
|
393
|
-
const type = obj.type;
|
|
394
|
-
const message = isRecord(obj.message) ? obj.message : null;
|
|
395
|
-
if (type === "user" && !isSide && message !== null) {
|
|
396
|
-
const cand = promptCandidate(message.content);
|
|
397
|
-
if (cand !== null) {
|
|
398
|
-
const t = cand.trim();
|
|
399
|
-
if (t.length > 0 && !t.startsWith("<")) prompt = t;
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
if (type === "assistant" && message !== null) {
|
|
403
|
-
const usage = message.usage;
|
|
404
|
-
if (isRecord(usage)) {
|
|
405
|
-
const rawModel = message.model;
|
|
406
|
-
if (rawModel !== SYNTHETIC_MODEL) {
|
|
407
|
-
const id = strOrNull(message.id) ?? "";
|
|
408
|
-
const reqId = strOrNull(obj.requestId) ?? "";
|
|
409
|
-
const key = `${id}:${reqId}`;
|
|
410
|
-
if (!seenKeys.has(key)) {
|
|
411
|
-
const model = strOrNull(rawModel) ?? "unknown";
|
|
412
|
-
pending.set(key, { model, isSidechain: isSide, bucket: extractBucket(usage) });
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
};
|
|
418
|
-
let lineStart = startOffset;
|
|
419
|
-
for (let pos = startOffset; pos < fileSize; pos++) {
|
|
420
|
-
if (buffer[pos] !== NEWLINE) continue;
|
|
421
|
-
handleLine(buffer.toString("utf8", lineStart, pos));
|
|
422
|
-
lineStart = pos + 1;
|
|
423
|
-
}
|
|
424
|
-
const newOffset = lineStart;
|
|
425
|
-
if (pending.size === 0) return null;
|
|
426
|
-
const main = {};
|
|
427
|
-
const sidechain = {};
|
|
428
|
-
const newKeys = [];
|
|
429
|
-
for (const [key, pm] of pending) {
|
|
430
|
-
newKeys.push(key);
|
|
431
|
-
if (pm.isSidechain) addToModel(sidechain, pm.model, pm.bucket);
|
|
432
|
-
else addToModel(main, pm.model, pm.bucket);
|
|
433
|
-
}
|
|
434
|
-
const combined = [...cursor?.seenMessageKeys ?? [], ...newKeys];
|
|
435
|
-
const seenMessageKeys = combined.length > MAX_SEEN_KEYS ? combined.slice(combined.length - MAX_SEEN_KEYS) : combined;
|
|
436
|
-
return {
|
|
437
|
-
sessionId,
|
|
438
|
-
main,
|
|
439
|
-
sidechain,
|
|
440
|
-
apiCalls: pending.size,
|
|
441
|
-
prompt,
|
|
442
|
-
cwd,
|
|
443
|
-
gitBranch,
|
|
444
|
-
firstTs,
|
|
445
|
-
lastTs,
|
|
446
|
-
newCursor: {
|
|
447
|
-
offset: newOffset,
|
|
448
|
-
lastUuid,
|
|
449
|
-
lastTs,
|
|
450
|
-
seenMessageKeys
|
|
451
|
-
}
|
|
452
|
-
};
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
export {
|
|
456
|
-
computeCost,
|
|
457
|
-
loadPriceTable,
|
|
458
|
-
getUsdJpy,
|
|
459
|
-
extractBucket,
|
|
460
|
-
promptCandidate,
|
|
461
|
-
aggregateNewTurn
|
|
462
|
-
};
|
package/dist/chunk-ZHIZZ6V5.js
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
paths
|
|
4
|
-
} from "./chunk-26CISNOE.js";
|
|
5
|
-
|
|
6
|
-
// src/dashboard-state.ts
|
|
7
|
-
import {
|
|
8
|
-
closeSync,
|
|
9
|
-
existsSync,
|
|
10
|
-
openSync,
|
|
11
|
-
readFileSync,
|
|
12
|
-
readSync,
|
|
13
|
-
renameSync,
|
|
14
|
-
rmSync,
|
|
15
|
-
writeFileSync
|
|
16
|
-
} from "fs";
|
|
17
|
-
import { randomUUID } from "crypto";
|
|
18
|
-
function localDate(now) {
|
|
19
|
-
const y = now.getFullYear();
|
|
20
|
-
const m = String(now.getMonth() + 1).padStart(2, "0");
|
|
21
|
-
const d = String(now.getDate()).padStart(2, "0");
|
|
22
|
-
return `${y}-${m}-${d}`;
|
|
23
|
-
}
|
|
24
|
-
function timeZone() {
|
|
25
|
-
return Intl.DateTimeFormat().resolvedOptions().timeZone || "unknown";
|
|
26
|
-
}
|
|
27
|
-
function makeFullDashboardState(now = /* @__PURE__ */ new Date()) {
|
|
28
|
-
return { localDate: localDate(now), timeZone: timeZone(), generatedAt: now.toISOString() };
|
|
29
|
-
}
|
|
30
|
-
function readState() {
|
|
31
|
-
const file = paths().dashboardFullStateFile;
|
|
32
|
-
if (!existsSync(file)) return null;
|
|
33
|
-
try {
|
|
34
|
-
const value = JSON.parse(readFileSync(file, "utf8"));
|
|
35
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
|
|
36
|
-
const v = value;
|
|
37
|
-
if (typeof v.localDate !== "string" || !/^\d{4}-\d{2}-\d{2}$/.test(v.localDate) || typeof v.timeZone !== "string" || v.timeZone.length === 0 || typeof v.generatedAt !== "string" || !Number.isFinite(Date.parse(v.generatedAt))) {
|
|
38
|
-
return null;
|
|
39
|
-
}
|
|
40
|
-
return v;
|
|
41
|
-
} catch {
|
|
42
|
-
return null;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
function isFullDashboardDue(now = /* @__PURE__ */ new Date()) {
|
|
46
|
-
const p = paths();
|
|
47
|
-
if (!existsSync(p.fullDashboardFile)) return true;
|
|
48
|
-
try {
|
|
49
|
-
const fd = openSync(p.fullDashboardFile, "r");
|
|
50
|
-
try {
|
|
51
|
-
const head = Buffer.alloc(512);
|
|
52
|
-
const n = readSync(fd, head, 0, head.length, 0);
|
|
53
|
-
if (head.toString("utf8", 0, n).includes('name="cccn-placeholder"')) return true;
|
|
54
|
-
} finally {
|
|
55
|
-
closeSync(fd);
|
|
56
|
-
}
|
|
57
|
-
} catch {
|
|
58
|
-
return true;
|
|
59
|
-
}
|
|
60
|
-
const state = readState();
|
|
61
|
-
if (state === null) return true;
|
|
62
|
-
const expected = makeFullDashboardState(now);
|
|
63
|
-
if (state.timeZone !== expected.timeZone) return true;
|
|
64
|
-
if (state.localDate !== expected.localDate) return true;
|
|
65
|
-
if (state.localDate > expected.localDate) return true;
|
|
66
|
-
if (Date.parse(state.generatedAt) > now.getTime()) return true;
|
|
67
|
-
return false;
|
|
68
|
-
}
|
|
69
|
-
function writeFullDashboardStateAtomic(state) {
|
|
70
|
-
const file = paths().dashboardFullStateFile;
|
|
71
|
-
const tmp = `${file}.${process.pid}.${randomUUID()}.tmp`;
|
|
72
|
-
try {
|
|
73
|
-
writeFileSync(tmp, `${JSON.stringify(state)}
|
|
74
|
-
`, "utf8");
|
|
75
|
-
renameSync(tmp, file);
|
|
76
|
-
} finally {
|
|
77
|
-
rmSync(tmp, { force: true });
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
function invalidateCanonicalDashboards() {
|
|
81
|
-
const p = paths();
|
|
82
|
-
for (const file of [p.recentDashboardFile, p.fullDashboardFile, p.dashboardFullStateFile]) {
|
|
83
|
-
rmSync(file, { force: true });
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export {
|
|
88
|
-
makeFullDashboardState,
|
|
89
|
-
isFullDashboardDue,
|
|
90
|
-
writeFullDashboardStateAtomic,
|
|
91
|
-
invalidateCanonicalDashboards
|
|
92
|
-
};
|