ccc-notifier 0.2.0 → 0.3.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 +15 -9
- package/dist/{budget-5H2GQRXH.js → budget-KKTFYAXH.js} +2 -2
- package/dist/{chunk-KUJZYZSG.js → chunk-4RZ6OGTD.js} +1 -1
- package/dist/{chunk-OXMHOVUL.js → chunk-7KVOQ4UZ.js} +228 -34
- package/dist/chunk-DSV75EF7.js +357 -0
- package/dist/{chunk-IIYMGLV4.js → chunk-ECADO26T.js} +9 -2
- package/dist/chunk-HTYUYKFW.js +21 -0
- package/dist/{chunk-TBFKGFZX.js → chunk-J5QAYTFE.js} +3 -0
- package/dist/{chunk-OEG3AVU6.js → chunk-LHKBGA5K.js} +27 -1
- package/dist/{chunk-4JNZ7BHO.js → chunk-NV5UOHJA.js} +1 -1
- package/dist/{chunk-64N5SGTT.js → chunk-QX5KIRSU.js} +109 -13
- package/dist/cli.js +67 -16
- package/dist/{dashboard-LKK6NOBN.js → dashboard-TH3NOVT3.js} +3 -3
- package/dist/{history-MWFHO5VR.js → history-DLFYQJFM.js} +1 -1
- package/dist/{mute-2JW3NQXN.js → mute-EPLYH67P.js} +2 -2
- package/dist/{setup-IXPUR4CM.js → setup-PTO5N46B.js} +5 -4
- package/dist/{sweep-XKKODGMA.js → sweep-25EZAOGC.js} +121 -9
- package/dist/{track-76Q5CZM5.js → track-PXFSGMGL.js} +29 -13
- package/package.json +5 -2
- package/dist/chunk-TB5A7U7G.js +0 -82
|
@@ -1,19 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
codexHome,
|
|
4
|
+
detectCodex
|
|
5
|
+
} from "./chunk-HTYUYKFW.js";
|
|
6
|
+
import {
|
|
7
|
+
collectSubagentUsage,
|
|
8
|
+
splitIntoCodexTurnDrafts
|
|
9
|
+
} from "./chunk-DSV75EF7.js";
|
|
5
10
|
import {
|
|
6
11
|
computeCost,
|
|
7
12
|
extractBucket,
|
|
8
13
|
getUsdJpy,
|
|
9
14
|
loadPriceTable,
|
|
10
15
|
promptCandidate
|
|
11
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-LHKBGA5K.js";
|
|
12
17
|
import {
|
|
13
18
|
formatJPY,
|
|
14
19
|
formatUSD,
|
|
15
20
|
modelDisplayName
|
|
16
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-J5QAYTFE.js";
|
|
17
22
|
import {
|
|
18
23
|
appendTurn,
|
|
19
24
|
loadCursor,
|
|
@@ -22,7 +27,7 @@ import {
|
|
|
22
27
|
readConfig,
|
|
23
28
|
sanitizeCursor,
|
|
24
29
|
saveCursor
|
|
25
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-ECADO26T.js";
|
|
26
31
|
|
|
27
32
|
// src/sweep.ts
|
|
28
33
|
import { readFile } from "fs/promises";
|
|
@@ -329,6 +334,102 @@ async function processTranscript(mainPath, table, fx, daysCutoff, dryRun, summar
|
|
|
329
334
|
}
|
|
330
335
|
}
|
|
331
336
|
}
|
|
337
|
+
var CODEX_MAX_DEPTH = 4;
|
|
338
|
+
async function listCodexRollouts(sessionsRoot) {
|
|
339
|
+
const found = [];
|
|
340
|
+
const walk = async (dir, depth) => {
|
|
341
|
+
const entries = await fsp.readdir(dir, { withFileTypes: true }).catch(() => null);
|
|
342
|
+
if (entries === null) return;
|
|
343
|
+
for (const e of entries) {
|
|
344
|
+
const full = join(dir, e.name);
|
|
345
|
+
if (e.isFile()) {
|
|
346
|
+
if (e.name.startsWith("rollout-") && e.name.endsWith(".jsonl")) found.push(full);
|
|
347
|
+
} else if (e.isDirectory() && depth < CODEX_MAX_DEPTH) {
|
|
348
|
+
await walk(full, depth + 1);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
await walk(sessionsRoot, 1);
|
|
353
|
+
return found;
|
|
354
|
+
}
|
|
355
|
+
function codexDraftToRecord(draft, ts, table, fx) {
|
|
356
|
+
const main = draft.agg.main;
|
|
357
|
+
const breakdown = computeCost(main, {}, table);
|
|
358
|
+
const rec = {
|
|
359
|
+
schemaVersion: 1,
|
|
360
|
+
ts,
|
|
361
|
+
sessionId: draft.agg.sessionId,
|
|
362
|
+
project: draft.agg.cwd ?? "",
|
|
363
|
+
gitBranch: null,
|
|
364
|
+
// rollout に git 情報は無い
|
|
365
|
+
models: collectModels(main, {}),
|
|
366
|
+
tokens: sumBuckets(main),
|
|
367
|
+
sidechainTokens: null,
|
|
368
|
+
apiCalls: draft.agg.apiCalls,
|
|
369
|
+
costUSD: breakdown.usd,
|
|
370
|
+
costByModel: breakdown.byModel,
|
|
371
|
+
costJPY: breakdown.usd * fx.rate,
|
|
372
|
+
// 円換算は sweep 実行時レート(Claude 側と同じ)
|
|
373
|
+
fxRate: fx.rate,
|
|
374
|
+
fxSource: fx.source,
|
|
375
|
+
prompt: draft.agg.prompt ?? "",
|
|
376
|
+
ingest: "sweep",
|
|
377
|
+
source: "codex"
|
|
378
|
+
};
|
|
379
|
+
if (breakdown.unknownModels.length > 0) rec.unknownModels = breakdown.unknownModels;
|
|
380
|
+
return rec;
|
|
381
|
+
}
|
|
382
|
+
async function processCodexRollout(rolloutPath, table, fx, daysCutoff, dryRun, summary) {
|
|
383
|
+
const cursor = sanitizeCursor(loadCursor(rolloutPath));
|
|
384
|
+
const drafts = await splitIntoCodexTurnDrafts(rolloutPath, cursor);
|
|
385
|
+
if (drafts === null || drafts.length === 0) return;
|
|
386
|
+
const records = [];
|
|
387
|
+
for (const draft of drafts) {
|
|
388
|
+
const ts = draft.endTs ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
389
|
+
if (daysCutoff !== null) {
|
|
390
|
+
const tsMs = Date.parse(ts);
|
|
391
|
+
if (!Number.isFinite(tsMs) || tsMs < daysCutoff) continue;
|
|
392
|
+
}
|
|
393
|
+
records.push(codexDraftToRecord(draft, ts, table, fx));
|
|
394
|
+
}
|
|
395
|
+
for (const rec of records) {
|
|
396
|
+
summary.newRecords += 1;
|
|
397
|
+
summary.totalUSD += rec.costUSD;
|
|
398
|
+
summary.codexRecords += 1;
|
|
399
|
+
summary.codexUSD += rec.costUSD;
|
|
400
|
+
if (rec.costByModel) {
|
|
401
|
+
for (const [m, c] of Object.entries(rec.costByModel)) {
|
|
402
|
+
summary.byModel[m] = (summary.byModel[m] ?? 0) + c;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
if (!dryRun) {
|
|
407
|
+
for (const rec of records) appendTurn(rec);
|
|
408
|
+
saveCursor(rolloutPath, drafts[drafts.length - 1].agg.newCursor);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
async function codexSessionsRoot() {
|
|
412
|
+
if (!detectCodex()) return null;
|
|
413
|
+
const sessionsRoot = join(codexHome(), "sessions");
|
|
414
|
+
const isDir = await fsp.stat(sessionsRoot).then((st) => st.isDirectory()).catch(() => false);
|
|
415
|
+
return isDir ? sessionsRoot : null;
|
|
416
|
+
}
|
|
417
|
+
async function sweepCodex(summary, table, fx, daysCutoff, flags) {
|
|
418
|
+
const sessionsRoot = await codexSessionsRoot();
|
|
419
|
+
if (sessionsRoot === null) return;
|
|
420
|
+
const rollouts = await listCodexRollouts(sessionsRoot);
|
|
421
|
+
for (const rolloutPath of rollouts) {
|
|
422
|
+
if (!flags.includeActive && await isRecentlyModified(rolloutPath)) {
|
|
423
|
+
summary.skippedActive += 1;
|
|
424
|
+
continue;
|
|
425
|
+
}
|
|
426
|
+
try {
|
|
427
|
+
await processCodexRollout(rolloutPath, table, fx, daysCutoff, flags.dryRun, summary);
|
|
428
|
+
} catch (err) {
|
|
429
|
+
logError("sweep:codex", err);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
332
433
|
function projectsRoot(override) {
|
|
333
434
|
if (override) return override;
|
|
334
435
|
return process.env.CCCN_CLAUDE_PROJECTS || join(homedir(), ".claude", "projects");
|
|
@@ -399,6 +500,11 @@ function printSweepSummary(summary, fx) {
|
|
|
399
500
|
` \u3046\u3061\u30B5\u30D6\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8: ${formatUSD(summary.subagentsUSD)}(${formatJPY(summary.subagentsUSD * fx.rate)})`
|
|
400
501
|
);
|
|
401
502
|
}
|
|
503
|
+
if (summary.codexRecords > 0) {
|
|
504
|
+
console.log(
|
|
505
|
+
` Codex: ${summary.codexRecords} \u30BF\u30FC\u30F3 ${formatUSD(summary.codexUSD)}(${formatJPY(summary.codexUSD * fx.rate)})`
|
|
506
|
+
);
|
|
507
|
+
}
|
|
402
508
|
const top = Object.entries(summary.byModel).filter(([, c]) => c > 0).sort((a, b) => b[1] - a[1]).slice(0, 5);
|
|
403
509
|
if (top.length > 0) {
|
|
404
510
|
console.log(" \u30E2\u30C7\u30EB\u5225(\u4E0A\u4F4D):");
|
|
@@ -415,15 +521,18 @@ async function runSweep(argv) {
|
|
|
415
521
|
const root = projectsRoot(flags.projects);
|
|
416
522
|
const projectDirs = await listProjectDirs(root);
|
|
417
523
|
if (projectDirs === null) {
|
|
418
|
-
|
|
419
|
-
|
|
524
|
+
if (await codexSessionsRoot() === null) {
|
|
525
|
+
console.log(`\u8D70\u67FB\u30EB\u30FC\u30C8\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093: ${root}`);
|
|
526
|
+
return 1;
|
|
527
|
+
}
|
|
528
|
+
console.log(`Claude \u306E\u8D70\u67FB\u30EB\u30FC\u30C8\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093: ${root}(Codex \u306E\u307F\u8D70\u67FB\u3057\u307E\u3059)`);
|
|
420
529
|
}
|
|
421
530
|
const cfg = readConfig();
|
|
422
531
|
const cacheDir = paths().cacheDir;
|
|
423
532
|
const table = await loadPriceTable(cacheDir, { offline: false });
|
|
424
533
|
const fx = await getUsdJpy(cfg, cacheDir);
|
|
425
534
|
const summary = {
|
|
426
|
-
projects: projectDirs.length,
|
|
535
|
+
projects: projectDirs === null ? 0 : projectDirs.length,
|
|
427
536
|
transcripts: 0,
|
|
428
537
|
agentFiles: 0,
|
|
429
538
|
newRecords: 0,
|
|
@@ -432,10 +541,12 @@ async function runSweep(argv) {
|
|
|
432
541
|
subagentsUSD: 0,
|
|
433
542
|
byModel: {},
|
|
434
543
|
skippedActive: 0,
|
|
544
|
+
codexRecords: 0,
|
|
545
|
+
codexUSD: 0,
|
|
435
546
|
dryRun: flags.dryRun
|
|
436
547
|
};
|
|
437
548
|
const daysCutoff = flags.days !== null ? Date.now() - flags.days * DAY_MS : null;
|
|
438
|
-
for (const projectDir of projectDirs) {
|
|
549
|
+
for (const projectDir of projectDirs ?? []) {
|
|
439
550
|
const transcripts = await listTranscripts(projectDir);
|
|
440
551
|
for (const mainPath of transcripts) {
|
|
441
552
|
summary.transcripts += 1;
|
|
@@ -450,6 +561,7 @@ async function runSweep(argv) {
|
|
|
450
561
|
}
|
|
451
562
|
}
|
|
452
563
|
}
|
|
564
|
+
await sweepCodex(summary, table, fx, daysCutoff, flags);
|
|
453
565
|
summary.totalJPY = summary.totalUSD * fx.rate;
|
|
454
566
|
printSweepSummary(summary, fx);
|
|
455
567
|
return 0;
|
|
@@ -2,21 +2,22 @@
|
|
|
2
2
|
import {
|
|
3
3
|
notifyOS,
|
|
4
4
|
notifySlack
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-NV5UOHJA.js";
|
|
6
6
|
import {
|
|
7
7
|
writeDashboardHtml
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-QX5KIRSU.js";
|
|
9
9
|
import "./chunk-DGXUSPS4.js";
|
|
10
10
|
import {
|
|
11
|
+
aggregateCodexTurn,
|
|
11
12
|
collectSubagentUsage
|
|
12
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-DSV75EF7.js";
|
|
13
14
|
import {
|
|
14
15
|
aggregateNewTurn,
|
|
15
16
|
computeCost,
|
|
16
17
|
getUsdJpy,
|
|
17
18
|
loadPriceTable
|
|
18
|
-
} from "./chunk-
|
|
19
|
-
import "./chunk-
|
|
19
|
+
} from "./chunk-LHKBGA5K.js";
|
|
20
|
+
import "./chunk-J5QAYTFE.js";
|
|
20
21
|
import {
|
|
21
22
|
appendTurn,
|
|
22
23
|
isMuted,
|
|
@@ -27,7 +28,7 @@ import {
|
|
|
27
28
|
sanitizeCursor,
|
|
28
29
|
saveCursor,
|
|
29
30
|
todayTotalUSD
|
|
30
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-ECADO26T.js";
|
|
31
32
|
|
|
32
33
|
// src/track.ts
|
|
33
34
|
import { join } from "path";
|
|
@@ -58,7 +59,13 @@ function collectModels(main, sidechain) {
|
|
|
58
59
|
}
|
|
59
60
|
return models;
|
|
60
61
|
}
|
|
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) {
|
|
62
69
|
try {
|
|
63
70
|
let parsed;
|
|
64
71
|
try {
|
|
@@ -72,14 +79,20 @@ async function runTrack(stdinText) {
|
|
|
72
79
|
if (typeof transcriptPath !== "string") return;
|
|
73
80
|
const cfg = readConfig();
|
|
74
81
|
const cursor = sanitizeCursor(loadCursor(transcriptPath));
|
|
75
|
-
const
|
|
82
|
+
const isCodex = opts?.codex === true;
|
|
83
|
+
let agg = isCodex ? await aggregateCodexTurn(transcriptPath, cursor) : await aggregateNewTurn(transcriptPath, cursor);
|
|
76
84
|
if (agg === null) return;
|
|
85
|
+
if (isCodex) {
|
|
86
|
+
agg = withCodexModel(agg, input.model);
|
|
87
|
+
}
|
|
77
88
|
let sa = null;
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
89
|
+
if (!isCodex) {
|
|
90
|
+
try {
|
|
91
|
+
sa = await collectSubagentUsage(transcriptPath);
|
|
92
|
+
} catch (err) {
|
|
93
|
+
logError("track:subagents", err);
|
|
94
|
+
sa = null;
|
|
95
|
+
}
|
|
83
96
|
}
|
|
84
97
|
const cacheDir = paths().cacheDir;
|
|
85
98
|
const table = await loadPriceTable(cacheDir, { offline: true });
|
|
@@ -107,6 +120,9 @@ async function runTrack(stdinText) {
|
|
|
107
120
|
fxSource: fx.source,
|
|
108
121
|
prompt: agg.prompt ?? ""
|
|
109
122
|
};
|
|
123
|
+
if (isCodex) {
|
|
124
|
+
record.source = "codex";
|
|
125
|
+
}
|
|
110
126
|
if (breakdown.unknownModels.length > 0) {
|
|
111
127
|
record.unknownModels = breakdown.unknownModels;
|
|
112
128
|
}
|
package/package.json
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ccc-notifier",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Claude Code Cost notifier: per-prompt cost notifications (USD/JPY) with local history and HTML dashboard",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Claude Code Cost notifier (now also covers Codex CLI): per-prompt cost notifications (USD/JPY) with local history and HTML dashboard",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude",
|
|
7
7
|
"claude-code",
|
|
8
8
|
"anthropic",
|
|
9
|
+
"codex",
|
|
10
|
+
"codex-cli",
|
|
11
|
+
"openai",
|
|
9
12
|
"cost",
|
|
10
13
|
"usage",
|
|
11
14
|
"notifier",
|
package/dist/chunk-TB5A7U7G.js
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
aggregateNewTurn
|
|
4
|
-
} from "./chunk-OEG3AVU6.js";
|
|
5
|
-
import {
|
|
6
|
-
loadCursor,
|
|
7
|
-
logError,
|
|
8
|
-
sanitizeCursor
|
|
9
|
-
} from "./chunk-IIYMGLV4.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 subagentsDirOf(mainTranscriptPath) {
|
|
31
|
-
const base = mainTranscriptPath.endsWith(".jsonl") ? mainTranscriptPath.slice(0, -".jsonl".length) : mainTranscriptPath;
|
|
32
|
-
return join(base, "subagents");
|
|
33
|
-
}
|
|
34
|
-
async function listAgentFiles(dir, entries) {
|
|
35
|
-
const files = entries.filter((e) => e.isFile() && e.name.startsWith("agent-") && e.name.endsWith(".jsonl")).map((e) => join(dir, e.name));
|
|
36
|
-
if (files.length <= MAX_AGENT_FILES) return files;
|
|
37
|
-
const withMtime = [];
|
|
38
|
-
for (const p of files) {
|
|
39
|
-
let mtime = 0;
|
|
40
|
-
try {
|
|
41
|
-
mtime = (await fs.stat(p)).mtimeMs;
|
|
42
|
-
} catch {
|
|
43
|
-
mtime = 0;
|
|
44
|
-
}
|
|
45
|
-
withMtime.push({ path: p, mtime });
|
|
46
|
-
}
|
|
47
|
-
withMtime.sort((a, b) => b.mtime - a.mtime);
|
|
48
|
-
return withMtime.slice(0, MAX_AGENT_FILES).map((x) => x.path);
|
|
49
|
-
}
|
|
50
|
-
async function collectSubagentUsage(mainTranscriptPath) {
|
|
51
|
-
const dir = subagentsDirOf(mainTranscriptPath);
|
|
52
|
-
let entries;
|
|
53
|
-
try {
|
|
54
|
-
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
55
|
-
} catch {
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
const files = await listAgentFiles(dir, entries);
|
|
59
|
-
const perModel = {};
|
|
60
|
-
let apiCalls = 0;
|
|
61
|
-
let agentFiles = 0;
|
|
62
|
-
const newCursors = [];
|
|
63
|
-
for (const filePath of files) {
|
|
64
|
-
try {
|
|
65
|
-
const cursor = sanitizeCursor(loadCursor(filePath));
|
|
66
|
-
const agg = await aggregateNewTurn(filePath, cursor);
|
|
67
|
-
if (agg === null) continue;
|
|
68
|
-
mergeUsage(perModel, agg.main);
|
|
69
|
-
mergeUsage(perModel, agg.sidechain);
|
|
70
|
-
apiCalls += agg.apiCalls;
|
|
71
|
-
agentFiles += 1;
|
|
72
|
-
newCursors.push({ path: filePath, cursor: agg.newCursor });
|
|
73
|
-
} catch (err) {
|
|
74
|
-
logError("subagents:file", err);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
return { perModel, apiCalls, agentFiles, newCursors };
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export {
|
|
81
|
-
collectSubagentUsage
|
|
82
|
-
};
|