ccc-notifier 0.2.0 → 0.4.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 +20 -12
- package/dist/{budget-5H2GQRXH.js → budget-EHWPEYXY.js} +2 -2
- package/dist/{chunk-IIYMGLV4.js → chunk-26CISNOE.js} +19 -4
- package/dist/{chunk-64N5SGTT.js → chunk-2VPBBIDW.js} +267 -57
- package/dist/{chunk-OXMHOVUL.js → chunk-5LHZOZZO.js} +228 -34
- package/dist/chunk-CKVK3UCA.js +357 -0
- package/dist/chunk-HTYUYKFW.js +21 -0
- package/dist/{chunk-TBFKGFZX.js → chunk-J5QAYTFE.js} +3 -0
- package/dist/chunk-J6Y3RMMC.js +172 -0
- package/dist/{chunk-OEG3AVU6.js → chunk-LHKBGA5K.js} +27 -1
- package/dist/{chunk-4JNZ7BHO.js → chunk-NV5UOHJA.js} +1 -1
- package/dist/{chunk-KUJZYZSG.js → chunk-TUZVISLD.js} +1 -1
- package/dist/chunk-ZHIZZ6V5.js +92 -0
- package/dist/cli.js +68 -17
- package/dist/{dashboard-LKK6NOBN.js → dashboard-GYKABTTN.js} +5 -3
- package/dist/history-6OUJLXJT.js +162 -0
- package/dist/{mute-2JW3NQXN.js → mute-GBDNN5PB.js} +2 -2
- package/dist/{setup-IXPUR4CM.js → setup-W3CSTHJC.js} +5 -4
- package/dist/{sweep-XKKODGMA.js → sweep-IBSV4LRD.js} +224 -80
- package/dist/track-QT2U3E2R.js +235 -0
- package/package.json +5 -2
- package/dist/chunk-TB5A7U7G.js +0 -82
- package/dist/history-MWFHO5VR.js +0 -114
- package/dist/track-76Q5CZM5.js +0 -168
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
|
-
};
|
package/dist/history-MWFHO5VR.js
DELETED
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
paths
|
|
4
|
-
} from "./chunk-IIYMGLV4.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-76Q5CZM5.js
DELETED
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
notifyOS,
|
|
4
|
-
notifySlack
|
|
5
|
-
} from "./chunk-4JNZ7BHO.js";
|
|
6
|
-
import {
|
|
7
|
-
writeDashboardHtml
|
|
8
|
-
} from "./chunk-64N5SGTT.js";
|
|
9
|
-
import "./chunk-DGXUSPS4.js";
|
|
10
|
-
import {
|
|
11
|
-
collectSubagentUsage
|
|
12
|
-
} from "./chunk-TB5A7U7G.js";
|
|
13
|
-
import {
|
|
14
|
-
aggregateNewTurn,
|
|
15
|
-
computeCost,
|
|
16
|
-
getUsdJpy,
|
|
17
|
-
loadPriceTable
|
|
18
|
-
} from "./chunk-OEG3AVU6.js";
|
|
19
|
-
import "./chunk-TBFKGFZX.js";
|
|
20
|
-
import {
|
|
21
|
-
appendTurn,
|
|
22
|
-
isMuted,
|
|
23
|
-
loadCursor,
|
|
24
|
-
logError,
|
|
25
|
-
paths,
|
|
26
|
-
readConfig,
|
|
27
|
-
sanitizeCursor,
|
|
28
|
-
saveCursor,
|
|
29
|
-
todayTotalUSD
|
|
30
|
-
} from "./chunk-IIYMGLV4.js";
|
|
31
|
-
|
|
32
|
-
// src/track.ts
|
|
33
|
-
import { join } from "path";
|
|
34
|
-
function isRecord(v) {
|
|
35
|
-
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
36
|
-
}
|
|
37
|
-
function emptyBuckets() {
|
|
38
|
-
return { input: 0, output: 0, cacheWrite5m: 0, cacheWrite1h: 0, cacheRead: 0 };
|
|
39
|
-
}
|
|
40
|
-
function sumBuckets(usage) {
|
|
41
|
-
const total = emptyBuckets();
|
|
42
|
-
for (const b of Object.values(usage)) {
|
|
43
|
-
total.input += b.input;
|
|
44
|
-
total.output += b.output;
|
|
45
|
-
total.cacheWrite5m += b.cacheWrite5m;
|
|
46
|
-
total.cacheWrite1h += b.cacheWrite1h;
|
|
47
|
-
total.cacheRead += b.cacheRead;
|
|
48
|
-
}
|
|
49
|
-
return total;
|
|
50
|
-
}
|
|
51
|
-
function collectModels(main, sidechain) {
|
|
52
|
-
const models = [];
|
|
53
|
-
for (const m of Object.keys(main)) {
|
|
54
|
-
if (!models.includes(m)) models.push(m);
|
|
55
|
-
}
|
|
56
|
-
for (const m of Object.keys(sidechain)) {
|
|
57
|
-
if (!models.includes(m)) models.push(m);
|
|
58
|
-
}
|
|
59
|
-
return models;
|
|
60
|
-
}
|
|
61
|
-
async function runTrack(stdinText) {
|
|
62
|
-
try {
|
|
63
|
-
let parsed;
|
|
64
|
-
try {
|
|
65
|
-
parsed = JSON.parse(stdinText);
|
|
66
|
-
} catch {
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
if (!isRecord(parsed)) return;
|
|
70
|
-
const input = parsed;
|
|
71
|
-
const transcriptPath = input.transcript_path;
|
|
72
|
-
if (typeof transcriptPath !== "string") return;
|
|
73
|
-
const cfg = readConfig();
|
|
74
|
-
const cursor = sanitizeCursor(loadCursor(transcriptPath));
|
|
75
|
-
const agg = await aggregateNewTurn(transcriptPath, cursor);
|
|
76
|
-
if (agg === null) return;
|
|
77
|
-
let sa = null;
|
|
78
|
-
try {
|
|
79
|
-
sa = await collectSubagentUsage(transcriptPath);
|
|
80
|
-
} catch (err) {
|
|
81
|
-
logError("track:subagents", err);
|
|
82
|
-
sa = null;
|
|
83
|
-
}
|
|
84
|
-
const cacheDir = paths().cacheDir;
|
|
85
|
-
const table = await loadPriceTable(cacheDir, { offline: true });
|
|
86
|
-
const breakdown = computeCost(agg.main, agg.sidechain, table);
|
|
87
|
-
const fx = await getUsdJpy(cfg, cacheDir);
|
|
88
|
-
const sessionId = agg.sessionId || (typeof input.session_id === "string" ? input.session_id : "") || "";
|
|
89
|
-
const project = agg.cwd ?? (typeof input.cwd === "string" ? input.cwd : void 0) ?? "";
|
|
90
|
-
const sidechainHasModels = Object.keys(agg.sidechain).length > 0;
|
|
91
|
-
const record = {
|
|
92
|
-
schemaVersion: 1,
|
|
93
|
-
ts: agg.lastTs ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
94
|
-
sessionId,
|
|
95
|
-
project,
|
|
96
|
-
gitBranch: agg.gitBranch,
|
|
97
|
-
models: collectModels(agg.main, agg.sidechain),
|
|
98
|
-
tokens: sumBuckets(agg.main),
|
|
99
|
-
sidechainTokens: sidechainHasModels ? sumBuckets(agg.sidechain) : null,
|
|
100
|
-
apiCalls: agg.apiCalls,
|
|
101
|
-
costUSD: breakdown.usd,
|
|
102
|
-
costByModel: breakdown.byModel,
|
|
103
|
-
// モデル別 USD(main+sidechain 合算、丸めない)
|
|
104
|
-
costJPY: breakdown.usd * fx.rate,
|
|
105
|
-
// 丸めない(表示時に丸める)
|
|
106
|
-
fxRate: fx.rate,
|
|
107
|
-
fxSource: fx.source,
|
|
108
|
-
prompt: agg.prompt ?? ""
|
|
109
|
-
};
|
|
110
|
-
if (breakdown.unknownModels.length > 0) {
|
|
111
|
-
record.unknownModels = breakdown.unknownModels;
|
|
112
|
-
}
|
|
113
|
-
if (sa !== null && sa.apiCalls > 0) {
|
|
114
|
-
const saBreakdown = computeCost(sa.perModel, {}, table);
|
|
115
|
-
record.subagents = {
|
|
116
|
-
costUSD: saBreakdown.usd,
|
|
117
|
-
costByModel: saBreakdown.byModel,
|
|
118
|
-
tokens: sumBuckets(sa.perModel),
|
|
119
|
-
apiCalls: sa.apiCalls,
|
|
120
|
-
agentFiles: sa.agentFiles
|
|
121
|
-
};
|
|
122
|
-
if (saBreakdown.unknownModels.length > 0) {
|
|
123
|
-
const merged = record.unknownModels ? [...record.unknownModels] : [];
|
|
124
|
-
for (const m of saBreakdown.unknownModels) {
|
|
125
|
-
if (!merged.includes(m)) merged.push(m);
|
|
126
|
-
}
|
|
127
|
-
record.unknownModels = merged;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
appendTurn(record);
|
|
131
|
-
saveCursor(transcriptPath, agg.newCursor);
|
|
132
|
-
if (sa !== null) {
|
|
133
|
-
for (const nc of sa.newCursors) {
|
|
134
|
-
saveCursor(nc.path, nc.cursor);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
const tasks = [];
|
|
138
|
-
if ((cfg.notify.os || cfg.notify.slack !== null) && record.costUSD >= cfg.minNotifyUSD && !isMuted()) {
|
|
139
|
-
const todayUSD = cfg.includeDailyTotal ? todayTotalUSD() : void 0;
|
|
140
|
-
tasks.push(notifyOS(record, cfg, todayUSD));
|
|
141
|
-
tasks.push(notifySlack(record, cfg, todayUSD));
|
|
142
|
-
}
|
|
143
|
-
if (cfg.dashboard.autoRegenerate) {
|
|
144
|
-
tasks.push(
|
|
145
|
-
(async () => {
|
|
146
|
-
try {
|
|
147
|
-
writeDashboardHtml({
|
|
148
|
-
days: null,
|
|
149
|
-
// 全履歴を埋め込む(粒度切替・過去・通算をブラウザ側で扱うため)
|
|
150
|
-
outPath: join(paths().home, "report.html"),
|
|
151
|
-
autoReloadSec: cfg.dashboard.autoReloadSec
|
|
152
|
-
});
|
|
153
|
-
} catch (err) {
|
|
154
|
-
logError("track:dashboard", err);
|
|
155
|
-
}
|
|
156
|
-
})()
|
|
157
|
-
);
|
|
158
|
-
}
|
|
159
|
-
if (tasks.length > 0) {
|
|
160
|
-
await Promise.allSettled(tasks);
|
|
161
|
-
}
|
|
162
|
-
} catch (err) {
|
|
163
|
-
logError("track", err);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
export {
|
|
167
|
-
runTrack
|
|
168
|
-
};
|