ccc-notifier 0.3.0 → 0.5.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 +11 -7
- package/dist/{budget-KKTFYAXH.js → budget-TBWVHUKA.js} +1 -1
- package/dist/chunk-5PH7PPD6.js +1077 -0
- package/dist/{chunk-4RZ6OGTD.js → chunk-DDUK4EWQ.js} +1 -1
- package/dist/chunk-ENGUOLTD.js +92 -0
- package/dist/chunk-HLJAJS2W.js +55 -0
- package/dist/{chunk-LHKBGA5K.js → chunk-LVMKY6JB.js} +304 -29
- package/dist/chunk-O2AR4PXU.js +82 -0
- package/dist/chunk-O34L3NSI.js +172 -0
- package/dist/{chunk-QX5KIRSU.js → chunk-QWBQN4XY.js} +183 -54
- package/dist/{chunk-7KVOQ4UZ.js → chunk-S3XKO7MY.js} +235 -108
- package/dist/cli.js +382 -73
- package/dist/{dashboard-TH3NOVT3.js → dashboard-6F2QNUJT.js} +4 -2
- package/dist/history-7KN26LJH.js +162 -0
- package/dist/{mute-EPLYH67P.js → mute-MPX3K7QN.js} +2 -2
- package/dist/{setup-PTO5N46B.js → setup-IXPMVWOE.js} +2 -2
- package/dist/subagent-store-A6MIN22X.js +31 -0
- package/dist/{sweep-25EZAOGC.js → sweep-UIONM4UA.js} +143 -126
- package/dist/track-KEZON6KI.js +245 -0
- package/package.json +1 -1
- package/dist/chunk-DSV75EF7.js +0 -357
- package/dist/chunk-ECADO26T.js +0 -316
- package/dist/history-DLFYQJFM.js +0 -114
- package/dist/track-PXFSGMGL.js +0 -184
package/dist/chunk-ECADO26T.js
DELETED
|
@@ -1,316 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// src/store.ts
|
|
4
|
-
import {
|
|
5
|
-
existsSync,
|
|
6
|
-
mkdirSync,
|
|
7
|
-
readFileSync,
|
|
8
|
-
writeFileSync,
|
|
9
|
-
appendFileSync,
|
|
10
|
-
renameSync,
|
|
11
|
-
rmSync,
|
|
12
|
-
statSync
|
|
13
|
-
} from "fs";
|
|
14
|
-
import { join } from "path";
|
|
15
|
-
import { homedir } from "os";
|
|
16
|
-
|
|
17
|
-
// src/types.ts
|
|
18
|
-
var DEFAULT_CONFIG = {
|
|
19
|
-
notify: { os: true, slack: null },
|
|
20
|
-
minNotifyUSD: 0,
|
|
21
|
-
costLabel: "api_equivalent",
|
|
22
|
-
fx: { fallbackRate: 150, cacheHours: 12 },
|
|
23
|
-
includeDailyTotal: true,
|
|
24
|
-
monthlyBudgetUSD: 0,
|
|
25
|
-
dashboard: { autoRegenerate: true, autoReloadSec: 30, days: 30 }
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
// src/store.ts
|
|
29
|
-
var ERROR_LOG_MAX_BYTES = 1024 * 1024;
|
|
30
|
-
function paths() {
|
|
31
|
-
const home = process.env.CCCN_HOME || join(homedir(), ".ccc-notifier");
|
|
32
|
-
const cacheDir = join(home, "cache");
|
|
33
|
-
mkdirSync(home, { recursive: true });
|
|
34
|
-
mkdirSync(cacheDir, { recursive: true });
|
|
35
|
-
return {
|
|
36
|
-
home,
|
|
37
|
-
configFile: join(home, "config.json"),
|
|
38
|
-
historyFile: join(home, "history.jsonl"),
|
|
39
|
-
cursorsFile: join(home, "cursors.json"),
|
|
40
|
-
cacheDir,
|
|
41
|
-
errorLog: join(home, "error.log"),
|
|
42
|
-
lastNotifyFile: join(home, "last-notify.json"),
|
|
43
|
-
muteFile: join(home, "muted.json")
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
function isPlainObject(v) {
|
|
47
|
-
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
48
|
-
}
|
|
49
|
-
function mergeConfig(partial) {
|
|
50
|
-
const result = structuredClone(DEFAULT_CONFIG);
|
|
51
|
-
if (!isPlainObject(partial)) return result;
|
|
52
|
-
if (isPlainObject(partial.notify)) {
|
|
53
|
-
if ("os" in partial.notify) {
|
|
54
|
-
result.notify.os = partial.notify.os;
|
|
55
|
-
}
|
|
56
|
-
if ("slack" in partial.notify) {
|
|
57
|
-
result.notify.slack = partial.notify.slack;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
if ("minNotifyUSD" in partial) {
|
|
61
|
-
result.minNotifyUSD = partial.minNotifyUSD;
|
|
62
|
-
}
|
|
63
|
-
if ("costLabel" in partial) {
|
|
64
|
-
result.costLabel = partial.costLabel;
|
|
65
|
-
}
|
|
66
|
-
if (isPlainObject(partial.fx)) {
|
|
67
|
-
if ("fallbackRate" in partial.fx) {
|
|
68
|
-
result.fx.fallbackRate = partial.fx.fallbackRate;
|
|
69
|
-
}
|
|
70
|
-
if ("cacheHours" in partial.fx) {
|
|
71
|
-
result.fx.cacheHours = partial.fx.cacheHours;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
if ("includeDailyTotal" in partial) {
|
|
75
|
-
result.includeDailyTotal = partial.includeDailyTotal;
|
|
76
|
-
}
|
|
77
|
-
if ("monthlyBudgetUSD" in partial) {
|
|
78
|
-
const b = partial.monthlyBudgetUSD;
|
|
79
|
-
if (typeof b === "number" && Number.isFinite(b) && b >= 0) {
|
|
80
|
-
result.monthlyBudgetUSD = b;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
if (isPlainObject(partial.dashboard)) {
|
|
84
|
-
if ("autoRegenerate" in partial.dashboard) {
|
|
85
|
-
result.dashboard.autoRegenerate = partial.dashboard.autoRegenerate;
|
|
86
|
-
}
|
|
87
|
-
if ("autoReloadSec" in partial.dashboard) {
|
|
88
|
-
result.dashboard.autoReloadSec = partial.dashboard.autoReloadSec;
|
|
89
|
-
}
|
|
90
|
-
if ("days" in partial.dashboard) {
|
|
91
|
-
result.dashboard.days = partial.dashboard.days;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
return result;
|
|
95
|
-
}
|
|
96
|
-
function readConfig() {
|
|
97
|
-
const p = paths();
|
|
98
|
-
if (!existsSync(p.configFile)) {
|
|
99
|
-
return structuredClone(DEFAULT_CONFIG);
|
|
100
|
-
}
|
|
101
|
-
let raw;
|
|
102
|
-
try {
|
|
103
|
-
raw = readFileSync(p.configFile, "utf8");
|
|
104
|
-
} catch (err) {
|
|
105
|
-
logError("readConfig", err);
|
|
106
|
-
return structuredClone(DEFAULT_CONFIG);
|
|
107
|
-
}
|
|
108
|
-
let parsed;
|
|
109
|
-
try {
|
|
110
|
-
parsed = JSON.parse(raw);
|
|
111
|
-
} catch (err) {
|
|
112
|
-
logError("readConfig", err);
|
|
113
|
-
return structuredClone(DEFAULT_CONFIG);
|
|
114
|
-
}
|
|
115
|
-
return mergeConfig(parsed);
|
|
116
|
-
}
|
|
117
|
-
function readMuteState() {
|
|
118
|
-
const p = paths();
|
|
119
|
-
if (!existsSync(p.muteFile)) return null;
|
|
120
|
-
try {
|
|
121
|
-
const parsed = JSON.parse(readFileSync(p.muteFile, "utf8"));
|
|
122
|
-
if (!isPlainObject(parsed) || !("until" in parsed)) return null;
|
|
123
|
-
const until = parsed.until;
|
|
124
|
-
if (until === null) return { until: null };
|
|
125
|
-
if (typeof until === "string" && !Number.isNaN(new Date(until).getTime())) {
|
|
126
|
-
return { until };
|
|
127
|
-
}
|
|
128
|
-
return null;
|
|
129
|
-
} catch (err) {
|
|
130
|
-
logError("readMuteState", err);
|
|
131
|
-
return null;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
function isMuted(now = /* @__PURE__ */ new Date()) {
|
|
135
|
-
const state = readMuteState();
|
|
136
|
-
if (state === null) return false;
|
|
137
|
-
if (state.until === null) return true;
|
|
138
|
-
return new Date(state.until).getTime() > now.getTime();
|
|
139
|
-
}
|
|
140
|
-
function writeMuteState(state) {
|
|
141
|
-
writeFileSync(paths().muteFile, `${JSON.stringify(state)}
|
|
142
|
-
`, "utf8");
|
|
143
|
-
}
|
|
144
|
-
function clearMuteState() {
|
|
145
|
-
rmSync(paths().muteFile, { force: true });
|
|
146
|
-
}
|
|
147
|
-
function loadCursor(transcriptPath) {
|
|
148
|
-
const p = paths();
|
|
149
|
-
if (!existsSync(p.cursorsFile)) return null;
|
|
150
|
-
let raw;
|
|
151
|
-
try {
|
|
152
|
-
raw = readFileSync(p.cursorsFile, "utf8");
|
|
153
|
-
} catch (err) {
|
|
154
|
-
logError("loadCursor", err);
|
|
155
|
-
return null;
|
|
156
|
-
}
|
|
157
|
-
let parsed;
|
|
158
|
-
try {
|
|
159
|
-
parsed = JSON.parse(raw);
|
|
160
|
-
} catch (err) {
|
|
161
|
-
logError("loadCursor", err);
|
|
162
|
-
return null;
|
|
163
|
-
}
|
|
164
|
-
if (!isPlainObject(parsed)) {
|
|
165
|
-
logError("loadCursor", new Error("cursors.json root is not an object"));
|
|
166
|
-
return null;
|
|
167
|
-
}
|
|
168
|
-
const cursor = parsed[transcriptPath];
|
|
169
|
-
return cursor ?? null;
|
|
170
|
-
}
|
|
171
|
-
function sanitizeCursor(raw) {
|
|
172
|
-
if (!isPlainObject(raw)) return null;
|
|
173
|
-
const { offset, lastUuid, lastTs, seenMessageKeys, codexTotals } = raw;
|
|
174
|
-
if (typeof offset !== "number" || !Number.isFinite(offset)) return null;
|
|
175
|
-
if (lastUuid !== null && typeof lastUuid !== "string") return null;
|
|
176
|
-
if (lastTs !== null && typeof lastTs !== "string") return null;
|
|
177
|
-
if (!Array.isArray(seenMessageKeys)) return null;
|
|
178
|
-
const keys = [];
|
|
179
|
-
for (const key of seenMessageKeys) {
|
|
180
|
-
if (typeof key !== "string") return null;
|
|
181
|
-
keys.push(key);
|
|
182
|
-
}
|
|
183
|
-
const cursor = { offset, lastUuid, lastTs, seenMessageKeys: keys };
|
|
184
|
-
if (isPlainObject(codexTotals)) {
|
|
185
|
-
const { input, cached, output } = codexTotals;
|
|
186
|
-
if (typeof input === "number" && Number.isFinite(input) && input >= 0 && typeof cached === "number" && Number.isFinite(cached) && cached >= 0 && typeof output === "number" && Number.isFinite(output) && output >= 0) {
|
|
187
|
-
cursor.codexTotals = { input, cached, output };
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
return cursor;
|
|
191
|
-
}
|
|
192
|
-
function saveCursor(transcriptPath, c) {
|
|
193
|
-
const p = paths();
|
|
194
|
-
let dict = {};
|
|
195
|
-
if (existsSync(p.cursorsFile)) {
|
|
196
|
-
try {
|
|
197
|
-
const raw = readFileSync(p.cursorsFile, "utf8");
|
|
198
|
-
const parsed = JSON.parse(raw);
|
|
199
|
-
if (isPlainObject(parsed)) {
|
|
200
|
-
dict = parsed;
|
|
201
|
-
}
|
|
202
|
-
} catch (err) {
|
|
203
|
-
logError("saveCursor", err);
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
dict[transcriptPath] = c;
|
|
207
|
-
const tmpFile = `${p.cursorsFile}.tmp`;
|
|
208
|
-
writeFileSync(tmpFile, JSON.stringify(dict), "utf8");
|
|
209
|
-
renameSync(tmpFile, p.cursorsFile);
|
|
210
|
-
}
|
|
211
|
-
function appendTurn(record) {
|
|
212
|
-
const p = paths();
|
|
213
|
-
appendFileSync(p.historyFile, JSON.stringify(record) + "\n", "utf8");
|
|
214
|
-
}
|
|
215
|
-
function readTurns(days) {
|
|
216
|
-
const p = paths();
|
|
217
|
-
if (!existsSync(p.historyFile)) return [];
|
|
218
|
-
let raw;
|
|
219
|
-
try {
|
|
220
|
-
raw = readFileSync(p.historyFile, "utf8");
|
|
221
|
-
} catch {
|
|
222
|
-
return [];
|
|
223
|
-
}
|
|
224
|
-
const cutoff = typeof days === "number" ? Date.now() - days * 864e5 : null;
|
|
225
|
-
const result = [];
|
|
226
|
-
for (const line of raw.split("\n")) {
|
|
227
|
-
const trimmed = line.trim();
|
|
228
|
-
if (!trimmed) continue;
|
|
229
|
-
let rec;
|
|
230
|
-
try {
|
|
231
|
-
rec = JSON.parse(trimmed);
|
|
232
|
-
} catch {
|
|
233
|
-
continue;
|
|
234
|
-
}
|
|
235
|
-
if (cutoff !== null) {
|
|
236
|
-
const ts = Date.parse(rec.ts);
|
|
237
|
-
if (!Number.isFinite(ts) || ts < cutoff) continue;
|
|
238
|
-
}
|
|
239
|
-
result.push(rec);
|
|
240
|
-
}
|
|
241
|
-
return result;
|
|
242
|
-
}
|
|
243
|
-
function todayTotalUSD() {
|
|
244
|
-
const now = /* @__PURE__ */ new Date();
|
|
245
|
-
const y = now.getFullYear();
|
|
246
|
-
const m = now.getMonth();
|
|
247
|
-
const d = now.getDate();
|
|
248
|
-
let total = 0;
|
|
249
|
-
for (const rec of readTurns()) {
|
|
250
|
-
const ts = new Date(rec.ts);
|
|
251
|
-
if (Number.isNaN(ts.getTime())) continue;
|
|
252
|
-
if (ts.getFullYear() === y && ts.getMonth() === m && ts.getDate() === d) {
|
|
253
|
-
total += rec.costUSD;
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
return total;
|
|
257
|
-
}
|
|
258
|
-
function currentMonthTotals() {
|
|
259
|
-
const now = /* @__PURE__ */ new Date();
|
|
260
|
-
const y = now.getFullYear();
|
|
261
|
-
const m = now.getMonth();
|
|
262
|
-
let usd = 0;
|
|
263
|
-
let jpy = 0;
|
|
264
|
-
let turns = 0;
|
|
265
|
-
for (const rec of readTurns()) {
|
|
266
|
-
const ts = new Date(rec.ts);
|
|
267
|
-
if (Number.isNaN(ts.getTime())) continue;
|
|
268
|
-
if (ts.getFullYear() === y && ts.getMonth() === m) {
|
|
269
|
-
const sa = rec.subagents?.costUSD ?? 0;
|
|
270
|
-
usd += rec.costUSD + sa;
|
|
271
|
-
jpy += rec.costJPY + sa * rec.fxRate;
|
|
272
|
-
turns += 1;
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
return { usd, jpy, turns };
|
|
276
|
-
}
|
|
277
|
-
function logError(context, err) {
|
|
278
|
-
try {
|
|
279
|
-
const p = paths();
|
|
280
|
-
const iso = (/* @__PURE__ */ new Date()).toISOString();
|
|
281
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
282
|
-
const stack = err instanceof Error ? err.stack : void 0;
|
|
283
|
-
let entry = `[${iso}] [${context}] ${message}
|
|
284
|
-
`;
|
|
285
|
-
if (stack) {
|
|
286
|
-
entry += `${stack}
|
|
287
|
-
`;
|
|
288
|
-
}
|
|
289
|
-
try {
|
|
290
|
-
const stat = statSync(p.errorLog);
|
|
291
|
-
if (stat.size > ERROR_LOG_MAX_BYTES) {
|
|
292
|
-
renameSync(p.errorLog, `${p.errorLog}.old`);
|
|
293
|
-
}
|
|
294
|
-
} catch {
|
|
295
|
-
}
|
|
296
|
-
appendFileSync(p.errorLog, entry, "utf8");
|
|
297
|
-
} catch {
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
export {
|
|
302
|
-
paths,
|
|
303
|
-
readConfig,
|
|
304
|
-
readMuteState,
|
|
305
|
-
isMuted,
|
|
306
|
-
writeMuteState,
|
|
307
|
-
clearMuteState,
|
|
308
|
-
loadCursor,
|
|
309
|
-
sanitizeCursor,
|
|
310
|
-
saveCursor,
|
|
311
|
-
appendTurn,
|
|
312
|
-
readTurns,
|
|
313
|
-
todayTotalUSD,
|
|
314
|
-
currentMonthTotals,
|
|
315
|
-
logError
|
|
316
|
-
};
|
package/dist/history-DLFYQJFM.js
DELETED
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
paths
|
|
4
|
-
} from "./chunk-ECADO26T.js";
|
|
5
|
-
|
|
6
|
-
// src/history.ts
|
|
7
|
-
import { existsSync, readFileSync, writeFileSync, renameSync, rmSync } from "fs";
|
|
8
|
-
import * as p from "@clack/prompts";
|
|
9
|
-
function parseFlags(argv) {
|
|
10
|
-
let days = null;
|
|
11
|
-
let yes = false;
|
|
12
|
-
for (let i = 0; i < argv.length; i++) {
|
|
13
|
-
const a = argv[i];
|
|
14
|
-
if (a === "--yes" || a === "-y") {
|
|
15
|
-
yes = true;
|
|
16
|
-
} else if (a === "--days") {
|
|
17
|
-
const n = Number.parseInt(argv[i + 1] ?? "", 10);
|
|
18
|
-
if (Number.isFinite(n) && n > 0) days = n;
|
|
19
|
-
i++;
|
|
20
|
-
} else if (a.startsWith("--days=")) {
|
|
21
|
-
const n = Number.parseInt(a.slice("--days=".length), 10);
|
|
22
|
-
if (Number.isFinite(n) && n > 0) days = n;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
return { days, yes };
|
|
26
|
-
}
|
|
27
|
-
function readLines(file) {
|
|
28
|
-
const raw = readFileSync(file, "utf8");
|
|
29
|
-
const lines = [];
|
|
30
|
-
for (const line of raw.split("\n")) {
|
|
31
|
-
if (!line.trim()) continue;
|
|
32
|
-
let rec = null;
|
|
33
|
-
try {
|
|
34
|
-
rec = JSON.parse(line);
|
|
35
|
-
} catch {
|
|
36
|
-
rec = null;
|
|
37
|
-
}
|
|
38
|
-
lines.push({ raw: line, rec });
|
|
39
|
-
}
|
|
40
|
-
return lines;
|
|
41
|
-
}
|
|
42
|
-
function isTargeted(rec, cutoffMs) {
|
|
43
|
-
if (cutoffMs === null) return true;
|
|
44
|
-
const ts = Date.parse(rec.ts);
|
|
45
|
-
if (!Number.isFinite(ts)) return false;
|
|
46
|
-
return ts < cutoffMs;
|
|
47
|
-
}
|
|
48
|
-
function atomicWrite(file, content) {
|
|
49
|
-
const tmp = `${file}.tmp`;
|
|
50
|
-
writeFileSync(tmp, content, "utf8");
|
|
51
|
-
renameSync(tmp, file);
|
|
52
|
-
}
|
|
53
|
-
async function runHistory(argv) {
|
|
54
|
-
const [sub, ...rest] = argv;
|
|
55
|
-
if (sub !== "clear" && sub !== "redact") {
|
|
56
|
-
console.error(
|
|
57
|
-
"\u4F7F\u3044\u65B9 / Usage: ccc-notifier history <clear|redact> [--days N] [--yes]\n clear \u2026 \u30EC\u30B3\u30FC\u30C9\u3054\u3068\u524A\u9664(\u30C1\u30E3\u30FC\u30C8\u30FB\u96C6\u8A08\u304B\u3089\u3082\u6D88\u3048\u308B) / delete records\n redact \u2026 \u30D7\u30ED\u30F3\u30D7\u30C8\u5168\u6587\u3060\u3051\u6D88\u3059(\u30B3\u30B9\u30C8\u30FB\u30C1\u30E3\u30FC\u30C8\u306F\u6B8B\u3059) / strip prompts only"
|
|
58
|
-
);
|
|
59
|
-
return 1;
|
|
60
|
-
}
|
|
61
|
-
const flags = parseFlags(rest);
|
|
62
|
-
const file = paths().historyFile;
|
|
63
|
-
if (!existsSync(file)) {
|
|
64
|
-
console.log("\u5C65\u6B74\u304C\u3042\u308A\u307E\u305B\u3093(history.jsonl \u306F\u672A\u4F5C\u6210\u3067\u3059)\u3002");
|
|
65
|
-
return 0;
|
|
66
|
-
}
|
|
67
|
-
const lines = readLines(file);
|
|
68
|
-
const cutoff = flags.days !== null ? Date.now() - flags.days * 864e5 : null;
|
|
69
|
-
const scope = flags.days !== null ? `${flags.days}\u65E5\u3088\u308A\u524D` : "\u5168\u671F\u9593";
|
|
70
|
-
const targetSet = /* @__PURE__ */ new Set();
|
|
71
|
-
for (let i = 0; i < lines.length; i++) {
|
|
72
|
-
const rec = lines[i].rec;
|
|
73
|
-
if (!rec || !isTargeted(rec, cutoff)) continue;
|
|
74
|
-
if (sub === "redact" && !(typeof rec.prompt === "string" && rec.prompt.length > 0)) continue;
|
|
75
|
-
targetSet.add(i);
|
|
76
|
-
}
|
|
77
|
-
if (targetSet.size === 0) {
|
|
78
|
-
console.log(`\u5BFE\u8C61\u304C\u3042\u308A\u307E\u305B\u3093(${scope})\u3002`);
|
|
79
|
-
return 0;
|
|
80
|
-
}
|
|
81
|
-
const action = sub === "clear" ? "\u30EC\u30B3\u30FC\u30C9\u3054\u3068\u524A\u9664" : "\u30D7\u30ED\u30F3\u30D7\u30C8\u5168\u6587\u3092\u6D88\u53BB";
|
|
82
|
-
if (!flags.yes) {
|
|
83
|
-
const confirmed = await p.confirm({
|
|
84
|
-
message: `${scope}\u306E\u5C65\u6B74 ${targetSet.size} \u4EF6\u3092${action}\u3057\u307E\u3059\u3002\u5143\u306B\u623B\u305B\u307E\u305B\u3093\u3002\u3088\u308D\u3057\u3044\u3067\u3059\u304B?`,
|
|
85
|
-
initialValue: false
|
|
86
|
-
});
|
|
87
|
-
if (p.isCancel(confirmed) || !confirmed) {
|
|
88
|
-
p.cancel("\u30AD\u30E3\u30F3\u30BB\u30EB\u3057\u307E\u3057\u305F");
|
|
89
|
-
return 0;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
if (sub === "clear") {
|
|
93
|
-
const kept = lines.filter((_, i) => !targetSet.has(i)).map((l) => l.raw);
|
|
94
|
-
if (kept.length === 0) {
|
|
95
|
-
rmSync(file, { force: true });
|
|
96
|
-
} else {
|
|
97
|
-
atomicWrite(file, kept.join("\n") + "\n");
|
|
98
|
-
}
|
|
99
|
-
console.log(`\u5C65\u6B74 ${targetSet.size} \u4EF6\u3092\u524A\u9664\u3057\u307E\u3057\u305F(${scope})\u3002`);
|
|
100
|
-
} else {
|
|
101
|
-
const out = lines.map((l, i) => {
|
|
102
|
-
if (!targetSet.has(i) || l.rec === null) return l.raw;
|
|
103
|
-
return JSON.stringify({ ...l.rec, prompt: "" });
|
|
104
|
-
});
|
|
105
|
-
atomicWrite(file, out.join("\n") + "\n");
|
|
106
|
-
console.log(
|
|
107
|
-
`\u30D7\u30ED\u30F3\u30D7\u30C8\u5168\u6587\u3092 ${targetSet.size} \u4EF6\u6D88\u53BB\u3057\u307E\u3057\u305F(${scope}\u3002\u30B3\u30B9\u30C8\u96C6\u8A08\u30FB\u30C1\u30E3\u30FC\u30C8\u306F\u4FDD\u6301\u3055\u308C\u307E\u3059)\u3002`
|
|
108
|
-
);
|
|
109
|
-
}
|
|
110
|
-
return 0;
|
|
111
|
-
}
|
|
112
|
-
export {
|
|
113
|
-
runHistory
|
|
114
|
-
};
|
package/dist/track-PXFSGMGL.js
DELETED
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
notifyOS,
|
|
4
|
-
notifySlack
|
|
5
|
-
} from "./chunk-NV5UOHJA.js";
|
|
6
|
-
import {
|
|
7
|
-
writeDashboardHtml
|
|
8
|
-
} from "./chunk-QX5KIRSU.js";
|
|
9
|
-
import "./chunk-DGXUSPS4.js";
|
|
10
|
-
import {
|
|
11
|
-
aggregateCodexTurn,
|
|
12
|
-
collectSubagentUsage
|
|
13
|
-
} from "./chunk-DSV75EF7.js";
|
|
14
|
-
import {
|
|
15
|
-
aggregateNewTurn,
|
|
16
|
-
computeCost,
|
|
17
|
-
getUsdJpy,
|
|
18
|
-
loadPriceTable
|
|
19
|
-
} from "./chunk-LHKBGA5K.js";
|
|
20
|
-
import "./chunk-J5QAYTFE.js";
|
|
21
|
-
import {
|
|
22
|
-
appendTurn,
|
|
23
|
-
isMuted,
|
|
24
|
-
loadCursor,
|
|
25
|
-
logError,
|
|
26
|
-
paths,
|
|
27
|
-
readConfig,
|
|
28
|
-
sanitizeCursor,
|
|
29
|
-
saveCursor,
|
|
30
|
-
todayTotalUSD
|
|
31
|
-
} from "./chunk-ECADO26T.js";
|
|
32
|
-
|
|
33
|
-
// src/track.ts
|
|
34
|
-
import { join } from "path";
|
|
35
|
-
function isRecord(v) {
|
|
36
|
-
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
37
|
-
}
|
|
38
|
-
function emptyBuckets() {
|
|
39
|
-
return { input: 0, output: 0, cacheWrite5m: 0, cacheWrite1h: 0, cacheRead: 0 };
|
|
40
|
-
}
|
|
41
|
-
function sumBuckets(usage) {
|
|
42
|
-
const total = emptyBuckets();
|
|
43
|
-
for (const b of Object.values(usage)) {
|
|
44
|
-
total.input += b.input;
|
|
45
|
-
total.output += b.output;
|
|
46
|
-
total.cacheWrite5m += b.cacheWrite5m;
|
|
47
|
-
total.cacheWrite1h += b.cacheWrite1h;
|
|
48
|
-
total.cacheRead += b.cacheRead;
|
|
49
|
-
}
|
|
50
|
-
return total;
|
|
51
|
-
}
|
|
52
|
-
function collectModels(main, sidechain) {
|
|
53
|
-
const models = [];
|
|
54
|
-
for (const m of Object.keys(main)) {
|
|
55
|
-
if (!models.includes(m)) models.push(m);
|
|
56
|
-
}
|
|
57
|
-
for (const m of Object.keys(sidechain)) {
|
|
58
|
-
if (!models.includes(m)) models.push(m);
|
|
59
|
-
}
|
|
60
|
-
return models;
|
|
61
|
-
}
|
|
62
|
-
function withCodexModel(agg, payloadModel) {
|
|
63
|
-
const model = typeof payloadModel === "string" && payloadModel.length > 0 ? payloadModel : null;
|
|
64
|
-
if (model === null) return agg;
|
|
65
|
-
const buckets = Object.values(agg.main)[0] ?? emptyBuckets();
|
|
66
|
-
return { ...agg, main: { [model]: buckets } };
|
|
67
|
-
}
|
|
68
|
-
async function runTrack(stdinText, opts) {
|
|
69
|
-
try {
|
|
70
|
-
let parsed;
|
|
71
|
-
try {
|
|
72
|
-
parsed = JSON.parse(stdinText);
|
|
73
|
-
} catch {
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
if (!isRecord(parsed)) return;
|
|
77
|
-
const input = parsed;
|
|
78
|
-
const transcriptPath = input.transcript_path;
|
|
79
|
-
if (typeof transcriptPath !== "string") return;
|
|
80
|
-
const cfg = readConfig();
|
|
81
|
-
const cursor = sanitizeCursor(loadCursor(transcriptPath));
|
|
82
|
-
const isCodex = opts?.codex === true;
|
|
83
|
-
let agg = isCodex ? await aggregateCodexTurn(transcriptPath, cursor) : await aggregateNewTurn(transcriptPath, cursor);
|
|
84
|
-
if (agg === null) return;
|
|
85
|
-
if (isCodex) {
|
|
86
|
-
agg = withCodexModel(agg, input.model);
|
|
87
|
-
}
|
|
88
|
-
let sa = null;
|
|
89
|
-
if (!isCodex) {
|
|
90
|
-
try {
|
|
91
|
-
sa = await collectSubagentUsage(transcriptPath);
|
|
92
|
-
} catch (err) {
|
|
93
|
-
logError("track:subagents", err);
|
|
94
|
-
sa = null;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
const cacheDir = paths().cacheDir;
|
|
98
|
-
const table = await loadPriceTable(cacheDir, { offline: true });
|
|
99
|
-
const breakdown = computeCost(agg.main, agg.sidechain, table);
|
|
100
|
-
const fx = await getUsdJpy(cfg, cacheDir);
|
|
101
|
-
const sessionId = agg.sessionId || (typeof input.session_id === "string" ? input.session_id : "") || "";
|
|
102
|
-
const project = agg.cwd ?? (typeof input.cwd === "string" ? input.cwd : void 0) ?? "";
|
|
103
|
-
const sidechainHasModels = Object.keys(agg.sidechain).length > 0;
|
|
104
|
-
const record = {
|
|
105
|
-
schemaVersion: 1,
|
|
106
|
-
ts: agg.lastTs ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
107
|
-
sessionId,
|
|
108
|
-
project,
|
|
109
|
-
gitBranch: agg.gitBranch,
|
|
110
|
-
models: collectModels(agg.main, agg.sidechain),
|
|
111
|
-
tokens: sumBuckets(agg.main),
|
|
112
|
-
sidechainTokens: sidechainHasModels ? sumBuckets(agg.sidechain) : null,
|
|
113
|
-
apiCalls: agg.apiCalls,
|
|
114
|
-
costUSD: breakdown.usd,
|
|
115
|
-
costByModel: breakdown.byModel,
|
|
116
|
-
// モデル別 USD(main+sidechain 合算、丸めない)
|
|
117
|
-
costJPY: breakdown.usd * fx.rate,
|
|
118
|
-
// 丸めない(表示時に丸める)
|
|
119
|
-
fxRate: fx.rate,
|
|
120
|
-
fxSource: fx.source,
|
|
121
|
-
prompt: agg.prompt ?? ""
|
|
122
|
-
};
|
|
123
|
-
if (isCodex) {
|
|
124
|
-
record.source = "codex";
|
|
125
|
-
}
|
|
126
|
-
if (breakdown.unknownModels.length > 0) {
|
|
127
|
-
record.unknownModels = breakdown.unknownModels;
|
|
128
|
-
}
|
|
129
|
-
if (sa !== null && sa.apiCalls > 0) {
|
|
130
|
-
const saBreakdown = computeCost(sa.perModel, {}, table);
|
|
131
|
-
record.subagents = {
|
|
132
|
-
costUSD: saBreakdown.usd,
|
|
133
|
-
costByModel: saBreakdown.byModel,
|
|
134
|
-
tokens: sumBuckets(sa.perModel),
|
|
135
|
-
apiCalls: sa.apiCalls,
|
|
136
|
-
agentFiles: sa.agentFiles
|
|
137
|
-
};
|
|
138
|
-
if (saBreakdown.unknownModels.length > 0) {
|
|
139
|
-
const merged = record.unknownModels ? [...record.unknownModels] : [];
|
|
140
|
-
for (const m of saBreakdown.unknownModels) {
|
|
141
|
-
if (!merged.includes(m)) merged.push(m);
|
|
142
|
-
}
|
|
143
|
-
record.unknownModels = merged;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
appendTurn(record);
|
|
147
|
-
saveCursor(transcriptPath, agg.newCursor);
|
|
148
|
-
if (sa !== null) {
|
|
149
|
-
for (const nc of sa.newCursors) {
|
|
150
|
-
saveCursor(nc.path, nc.cursor);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
const tasks = [];
|
|
154
|
-
if ((cfg.notify.os || cfg.notify.slack !== null) && record.costUSD >= cfg.minNotifyUSD && !isMuted()) {
|
|
155
|
-
const todayUSD = cfg.includeDailyTotal ? todayTotalUSD() : void 0;
|
|
156
|
-
tasks.push(notifyOS(record, cfg, todayUSD));
|
|
157
|
-
tasks.push(notifySlack(record, cfg, todayUSD));
|
|
158
|
-
}
|
|
159
|
-
if (cfg.dashboard.autoRegenerate) {
|
|
160
|
-
tasks.push(
|
|
161
|
-
(async () => {
|
|
162
|
-
try {
|
|
163
|
-
writeDashboardHtml({
|
|
164
|
-
days: null,
|
|
165
|
-
// 全履歴を埋め込む(粒度切替・過去・通算をブラウザ側で扱うため)
|
|
166
|
-
outPath: join(paths().home, "report.html"),
|
|
167
|
-
autoReloadSec: cfg.dashboard.autoReloadSec
|
|
168
|
-
});
|
|
169
|
-
} catch (err) {
|
|
170
|
-
logError("track:dashboard", err);
|
|
171
|
-
}
|
|
172
|
-
})()
|
|
173
|
-
);
|
|
174
|
-
}
|
|
175
|
-
if (tasks.length > 0) {
|
|
176
|
-
await Promise.allSettled(tasks);
|
|
177
|
-
}
|
|
178
|
-
} catch (err) {
|
|
179
|
-
logError("track", err);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
export {
|
|
183
|
-
runTrack
|
|
184
|
-
};
|