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
|
@@ -4,30 +4,31 @@ import {
|
|
|
4
4
|
notifySlack
|
|
5
5
|
} from "./chunk-NV5UOHJA.js";
|
|
6
6
|
import {
|
|
7
|
-
writeDashboardHtml
|
|
8
|
-
} from "./chunk-2VPBBIDW.js";
|
|
9
|
-
import "./chunk-DGXUSPS4.js";
|
|
10
|
-
import {
|
|
11
|
-
aggregateCodexTurn,
|
|
12
7
|
collectSubagentUsage
|
|
13
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-OYD6H3ZZ.js";
|
|
14
9
|
import {
|
|
10
|
+
aggregateCodexTurn,
|
|
15
11
|
aggregateNewTurn,
|
|
12
|
+
getUsdJpy
|
|
13
|
+
} from "./chunk-D4D76RGQ.js";
|
|
14
|
+
import {
|
|
16
15
|
computeCost,
|
|
17
|
-
getUsdJpy,
|
|
18
16
|
loadPriceTable
|
|
19
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-6HTETN26.js";
|
|
18
|
+
import {
|
|
19
|
+
writeDashboardHtml
|
|
20
|
+
} from "./chunk-4JSRGJCZ.js";
|
|
21
|
+
import "./chunk-DGXUSPS4.js";
|
|
22
|
+
import "./chunk-J5QAYTFE.js";
|
|
20
23
|
import {
|
|
21
24
|
isFullDashboardDue,
|
|
22
25
|
makeFullDashboardState,
|
|
26
|
+
waitForDataLock,
|
|
23
27
|
writeFullDashboardStateAtomic
|
|
24
|
-
} from "./chunk-
|
|
25
|
-
import {
|
|
26
|
-
waitForDataLock
|
|
27
|
-
} from "./chunk-J6Y3RMMC.js";
|
|
28
|
-
import "./chunk-J5QAYTFE.js";
|
|
28
|
+
} from "./chunk-27SJELD2.js";
|
|
29
29
|
import {
|
|
30
30
|
appendTurn,
|
|
31
|
+
closeCodexRootContext,
|
|
31
32
|
isMuted,
|
|
32
33
|
loadCursor,
|
|
33
34
|
logError,
|
|
@@ -37,7 +38,7 @@ import {
|
|
|
37
38
|
sanitizeCursor,
|
|
38
39
|
saveCursor,
|
|
39
40
|
todayTotalUSD
|
|
40
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-OOAC5ULQ.js";
|
|
41
42
|
|
|
42
43
|
// src/track.ts
|
|
43
44
|
function isRecord(v) {
|
|
@@ -83,14 +84,23 @@ async function runTrack(stdinText, opts) {
|
|
|
83
84
|
}
|
|
84
85
|
if (!isRecord(parsed)) return;
|
|
85
86
|
const input = parsed;
|
|
87
|
+
const isCodex = opts?.codex === true;
|
|
88
|
+
let activityProjectionKey = null;
|
|
89
|
+
if (isCodex) {
|
|
90
|
+
try {
|
|
91
|
+
activityProjectionKey = closeCodexRootContext(parsed);
|
|
92
|
+
} catch {
|
|
93
|
+
logError("track:codex-subagent-projection", new Error("activity projection was not attached"));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
86
96
|
const transcriptPath = input.transcript_path;
|
|
87
97
|
if (typeof transcriptPath !== "string") return;
|
|
88
98
|
const cfg = readConfig();
|
|
89
99
|
const cacheDir = paths().cacheDir;
|
|
90
100
|
const table = await loadPriceTable(cacheDir, { offline: true });
|
|
91
101
|
const fx = await getUsdJpy(cfg, cacheDir);
|
|
92
|
-
const isCodex = opts?.codex === true;
|
|
93
102
|
let record;
|
|
103
|
+
let hasMainUsage = false;
|
|
94
104
|
const commitLock = await waitForDataLock(1e3);
|
|
95
105
|
if (commitLock === null) {
|
|
96
106
|
logError("track:data-lock", new Error("data lock timeout; turn was not consumed"));
|
|
@@ -99,33 +109,44 @@ async function runTrack(stdinText, opts) {
|
|
|
99
109
|
try {
|
|
100
110
|
const cursor = sanitizeCursor(loadCursor(transcriptPath));
|
|
101
111
|
let agg = isCodex ? await aggregateCodexTurn(transcriptPath, cursor) : await aggregateNewTurn(transcriptPath, cursor);
|
|
102
|
-
if (agg === null) return;
|
|
103
|
-
|
|
112
|
+
if (isCodex && agg === null) return;
|
|
113
|
+
hasMainUsage = agg !== null;
|
|
114
|
+
if (isCodex && agg !== null) {
|
|
104
115
|
agg = withCodexModel(agg, input.model);
|
|
105
116
|
}
|
|
106
117
|
let sa = null;
|
|
107
118
|
if (!isCodex) {
|
|
108
119
|
try {
|
|
109
|
-
|
|
120
|
+
const excluded = new Set(cursor?.seenMessageKeys ?? []);
|
|
121
|
+
if (agg !== null && "messageKeys" in agg) {
|
|
122
|
+
for (const key of agg.messageKeys) excluded.add(key);
|
|
123
|
+
}
|
|
124
|
+
sa = await collectSubagentUsage(transcriptPath, { excludeMessageKeys: excluded });
|
|
110
125
|
} catch (err) {
|
|
111
126
|
logError("track:subagents", err);
|
|
112
127
|
sa = null;
|
|
113
128
|
}
|
|
114
129
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
130
|
+
if (agg === null && (sa === null || sa.apiCalls === 0)) {
|
|
131
|
+
for (const nc of sa?.newCursors ?? []) saveCursor(nc.path, nc.cursor);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const main = agg?.main ?? {};
|
|
135
|
+
const sidechain = agg?.sidechain ?? {};
|
|
136
|
+
const breakdown = computeCost(main, sidechain, table);
|
|
137
|
+
const sessionId = agg?.sessionId || sa?.sessionId || (typeof input.session_id === "string" ? input.session_id : "") || "";
|
|
138
|
+
const project = agg?.cwd ?? sa?.cwd ?? (typeof input.cwd === "string" ? input.cwd : void 0) ?? "";
|
|
139
|
+
const sidechainHasModels = Object.keys(sidechain).length > 0;
|
|
119
140
|
record = {
|
|
120
141
|
schemaVersion: 1,
|
|
121
|
-
ts: agg
|
|
142
|
+
ts: agg?.lastTs ?? sa?.lastTs ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
122
143
|
sessionId,
|
|
123
144
|
project,
|
|
124
|
-
gitBranch: agg
|
|
125
|
-
models: collectModels(
|
|
126
|
-
tokens: sumBuckets(
|
|
127
|
-
sidechainTokens: sidechainHasModels ? sumBuckets(
|
|
128
|
-
apiCalls: agg
|
|
145
|
+
gitBranch: agg?.gitBranch ?? sa?.gitBranch ?? null,
|
|
146
|
+
models: hasMainUsage ? collectModels(main, sidechain) : collectModels(sa?.perModel ?? {}, {}),
|
|
147
|
+
tokens: sumBuckets(main),
|
|
148
|
+
sidechainTokens: sidechainHasModels ? sumBuckets(sidechain) : null,
|
|
149
|
+
apiCalls: agg?.apiCalls ?? 0,
|
|
129
150
|
costUSD: breakdown.usd,
|
|
130
151
|
costByModel: breakdown.byModel,
|
|
131
152
|
// モデル別 USD(main+sidechain 合算、丸めない)
|
|
@@ -133,10 +154,11 @@ async function runTrack(stdinText, opts) {
|
|
|
133
154
|
// 丸めない(表示時に丸める)
|
|
134
155
|
fxRate: fx.rate,
|
|
135
156
|
fxSource: fx.source,
|
|
136
|
-
prompt: agg
|
|
157
|
+
prompt: agg?.prompt ?? ""
|
|
137
158
|
};
|
|
138
159
|
if (isCodex) {
|
|
139
160
|
record.source = "codex";
|
|
161
|
+
if (activityProjectionKey !== null) record.activityProjectionKey = activityProjectionKey;
|
|
140
162
|
}
|
|
141
163
|
if (breakdown.unknownModels.length > 0) {
|
|
142
164
|
record.unknownModels = breakdown.unknownModels;
|
|
@@ -159,7 +181,7 @@ async function runTrack(stdinText, opts) {
|
|
|
159
181
|
}
|
|
160
182
|
}
|
|
161
183
|
appendTurn(record);
|
|
162
|
-
saveCursor(transcriptPath, agg.newCursor);
|
|
184
|
+
if (agg !== null) saveCursor(transcriptPath, agg.newCursor);
|
|
163
185
|
if (sa !== null) {
|
|
164
186
|
for (const nc of sa.newCursors) {
|
|
165
187
|
saveCursor(nc.path, nc.cursor);
|
|
@@ -169,7 +191,7 @@ async function runTrack(stdinText, opts) {
|
|
|
169
191
|
commitLock.release();
|
|
170
192
|
}
|
|
171
193
|
const tasks = [];
|
|
172
|
-
if ((cfg.notify.os || cfg.notify.slack !== null) && record.costUSD >= cfg.minNotifyUSD && !isMuted()) {
|
|
194
|
+
if (hasMainUsage && (cfg.notify.os || cfg.notify.slack !== null) && record.costUSD >= cfg.minNotifyUSD && !isMuted()) {
|
|
173
195
|
const todayUSD = cfg.includeDailyTotal ? todayTotalUSD() : void 0;
|
|
174
196
|
tasks.push(notifyOS(record, cfg, todayUSD));
|
|
175
197
|
tasks.push(notifySlack(record, cfg, todayUSD));
|
package/package.json
CHANGED
package/dist/chunk-26CISNOE.js
DELETED
|
@@ -1,324 +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
|
-
recentDashboardFile: join(home, "report.html"),
|
|
45
|
-
fullDashboardFile: join(home, "report-all.html"),
|
|
46
|
-
dashboardFullStateFile: join(cacheDir, "dashboard-full-state.json"),
|
|
47
|
-
dataLockDir: join(cacheDir, "data.lock"),
|
|
48
|
-
dataReclaimDir: join(cacheDir, "data.lock.reclaim")
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
function isPlainObject(v) {
|
|
52
|
-
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
53
|
-
}
|
|
54
|
-
function mergeConfig(partial) {
|
|
55
|
-
const result = structuredClone(DEFAULT_CONFIG);
|
|
56
|
-
if (!isPlainObject(partial)) return result;
|
|
57
|
-
if (isPlainObject(partial.notify)) {
|
|
58
|
-
if ("os" in partial.notify) {
|
|
59
|
-
result.notify.os = partial.notify.os;
|
|
60
|
-
}
|
|
61
|
-
if ("slack" in partial.notify) {
|
|
62
|
-
result.notify.slack = partial.notify.slack;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
if ("minNotifyUSD" in partial) {
|
|
66
|
-
result.minNotifyUSD = partial.minNotifyUSD;
|
|
67
|
-
}
|
|
68
|
-
if ("costLabel" in partial) {
|
|
69
|
-
result.costLabel = partial.costLabel;
|
|
70
|
-
}
|
|
71
|
-
if (isPlainObject(partial.fx)) {
|
|
72
|
-
if ("fallbackRate" in partial.fx) {
|
|
73
|
-
result.fx.fallbackRate = partial.fx.fallbackRate;
|
|
74
|
-
}
|
|
75
|
-
if ("cacheHours" in partial.fx) {
|
|
76
|
-
result.fx.cacheHours = partial.fx.cacheHours;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
if ("includeDailyTotal" in partial) {
|
|
80
|
-
result.includeDailyTotal = partial.includeDailyTotal;
|
|
81
|
-
}
|
|
82
|
-
if ("monthlyBudgetUSD" in partial) {
|
|
83
|
-
const b = partial.monthlyBudgetUSD;
|
|
84
|
-
if (typeof b === "number" && Number.isFinite(b) && b >= 0) {
|
|
85
|
-
result.monthlyBudgetUSD = b;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
if (isPlainObject(partial.dashboard)) {
|
|
89
|
-
if ("autoRegenerate" in partial.dashboard) {
|
|
90
|
-
result.dashboard.autoRegenerate = partial.dashboard.autoRegenerate;
|
|
91
|
-
}
|
|
92
|
-
if ("autoReloadSec" in partial.dashboard) {
|
|
93
|
-
result.dashboard.autoReloadSec = partial.dashboard.autoReloadSec;
|
|
94
|
-
}
|
|
95
|
-
if ("days" in partial.dashboard) {
|
|
96
|
-
const days = partial.dashboard.days;
|
|
97
|
-
if (typeof days === "number" && Number.isFinite(days) && Number.isInteger(days) && days > 0) {
|
|
98
|
-
result.dashboard.days = days;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
return result;
|
|
103
|
-
}
|
|
104
|
-
function readConfig() {
|
|
105
|
-
const p = paths();
|
|
106
|
-
if (!existsSync(p.configFile)) {
|
|
107
|
-
return structuredClone(DEFAULT_CONFIG);
|
|
108
|
-
}
|
|
109
|
-
let raw;
|
|
110
|
-
try {
|
|
111
|
-
raw = readFileSync(p.configFile, "utf8");
|
|
112
|
-
} catch (err) {
|
|
113
|
-
logError("readConfig", err);
|
|
114
|
-
return structuredClone(DEFAULT_CONFIG);
|
|
115
|
-
}
|
|
116
|
-
let parsed;
|
|
117
|
-
try {
|
|
118
|
-
parsed = JSON.parse(raw);
|
|
119
|
-
} catch (err) {
|
|
120
|
-
logError("readConfig", err);
|
|
121
|
-
return structuredClone(DEFAULT_CONFIG);
|
|
122
|
-
}
|
|
123
|
-
return mergeConfig(parsed);
|
|
124
|
-
}
|
|
125
|
-
function readMuteState() {
|
|
126
|
-
const p = paths();
|
|
127
|
-
if (!existsSync(p.muteFile)) return null;
|
|
128
|
-
try {
|
|
129
|
-
const parsed = JSON.parse(readFileSync(p.muteFile, "utf8"));
|
|
130
|
-
if (!isPlainObject(parsed) || !("until" in parsed)) return null;
|
|
131
|
-
const until = parsed.until;
|
|
132
|
-
if (until === null) return { until: null };
|
|
133
|
-
if (typeof until === "string" && !Number.isNaN(new Date(until).getTime())) {
|
|
134
|
-
return { until };
|
|
135
|
-
}
|
|
136
|
-
return null;
|
|
137
|
-
} catch (err) {
|
|
138
|
-
logError("readMuteState", err);
|
|
139
|
-
return null;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
function isMuted(now = /* @__PURE__ */ new Date()) {
|
|
143
|
-
const state = readMuteState();
|
|
144
|
-
if (state === null) return false;
|
|
145
|
-
if (state.until === null) return true;
|
|
146
|
-
return new Date(state.until).getTime() > now.getTime();
|
|
147
|
-
}
|
|
148
|
-
function writeMuteState(state) {
|
|
149
|
-
writeFileSync(paths().muteFile, `${JSON.stringify(state)}
|
|
150
|
-
`, "utf8");
|
|
151
|
-
}
|
|
152
|
-
function clearMuteState() {
|
|
153
|
-
rmSync(paths().muteFile, { force: true });
|
|
154
|
-
}
|
|
155
|
-
function loadCursor(transcriptPath) {
|
|
156
|
-
const p = paths();
|
|
157
|
-
if (!existsSync(p.cursorsFile)) return null;
|
|
158
|
-
let raw;
|
|
159
|
-
try {
|
|
160
|
-
raw = readFileSync(p.cursorsFile, "utf8");
|
|
161
|
-
} catch (err) {
|
|
162
|
-
logError("loadCursor", err);
|
|
163
|
-
return null;
|
|
164
|
-
}
|
|
165
|
-
let parsed;
|
|
166
|
-
try {
|
|
167
|
-
parsed = JSON.parse(raw);
|
|
168
|
-
} catch (err) {
|
|
169
|
-
logError("loadCursor", err);
|
|
170
|
-
return null;
|
|
171
|
-
}
|
|
172
|
-
if (!isPlainObject(parsed)) {
|
|
173
|
-
logError("loadCursor", new Error("cursors.json root is not an object"));
|
|
174
|
-
return null;
|
|
175
|
-
}
|
|
176
|
-
const cursor = parsed[transcriptPath];
|
|
177
|
-
return cursor ?? null;
|
|
178
|
-
}
|
|
179
|
-
function sanitizeCursor(raw) {
|
|
180
|
-
if (!isPlainObject(raw)) return null;
|
|
181
|
-
const { offset, lastUuid, lastTs, seenMessageKeys, codexTotals } = raw;
|
|
182
|
-
if (typeof offset !== "number" || !Number.isFinite(offset)) return null;
|
|
183
|
-
if (lastUuid !== null && typeof lastUuid !== "string") return null;
|
|
184
|
-
if (lastTs !== null && typeof lastTs !== "string") return null;
|
|
185
|
-
if (!Array.isArray(seenMessageKeys)) return null;
|
|
186
|
-
const keys = [];
|
|
187
|
-
for (const key of seenMessageKeys) {
|
|
188
|
-
if (typeof key !== "string") return null;
|
|
189
|
-
keys.push(key);
|
|
190
|
-
}
|
|
191
|
-
const cursor = { offset, lastUuid, lastTs, seenMessageKeys: keys };
|
|
192
|
-
if (isPlainObject(codexTotals)) {
|
|
193
|
-
const { input, cached, output } = codexTotals;
|
|
194
|
-
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) {
|
|
195
|
-
cursor.codexTotals = { input, cached, output };
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
return cursor;
|
|
199
|
-
}
|
|
200
|
-
function saveCursor(transcriptPath, c) {
|
|
201
|
-
const p = paths();
|
|
202
|
-
let dict = {};
|
|
203
|
-
if (existsSync(p.cursorsFile)) {
|
|
204
|
-
try {
|
|
205
|
-
const raw = readFileSync(p.cursorsFile, "utf8");
|
|
206
|
-
const parsed = JSON.parse(raw);
|
|
207
|
-
if (isPlainObject(parsed)) {
|
|
208
|
-
dict = parsed;
|
|
209
|
-
}
|
|
210
|
-
} catch (err) {
|
|
211
|
-
logError("saveCursor", err);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
dict[transcriptPath] = c;
|
|
215
|
-
const tmpFile = `${p.cursorsFile}.tmp`;
|
|
216
|
-
writeFileSync(tmpFile, JSON.stringify(dict), "utf8");
|
|
217
|
-
renameSync(tmpFile, p.cursorsFile);
|
|
218
|
-
}
|
|
219
|
-
function appendTurn(record) {
|
|
220
|
-
const p = paths();
|
|
221
|
-
appendFileSync(p.historyFile, JSON.stringify(record) + "\n", "utf8");
|
|
222
|
-
}
|
|
223
|
-
function readTurns(days) {
|
|
224
|
-
const p = paths();
|
|
225
|
-
if (!existsSync(p.historyFile)) return [];
|
|
226
|
-
let raw;
|
|
227
|
-
try {
|
|
228
|
-
raw = readFileSync(p.historyFile, "utf8");
|
|
229
|
-
} catch {
|
|
230
|
-
return [];
|
|
231
|
-
}
|
|
232
|
-
const cutoff = typeof days === "number" ? Date.now() - days * 864e5 : null;
|
|
233
|
-
const result = [];
|
|
234
|
-
for (const line of raw.split("\n")) {
|
|
235
|
-
const trimmed = line.trim();
|
|
236
|
-
if (!trimmed) continue;
|
|
237
|
-
let rec;
|
|
238
|
-
try {
|
|
239
|
-
rec = JSON.parse(trimmed);
|
|
240
|
-
} catch {
|
|
241
|
-
continue;
|
|
242
|
-
}
|
|
243
|
-
if (cutoff !== null) {
|
|
244
|
-
const ts = Date.parse(rec.ts);
|
|
245
|
-
if (!Number.isFinite(ts) || ts < cutoff) continue;
|
|
246
|
-
}
|
|
247
|
-
result.push(rec);
|
|
248
|
-
}
|
|
249
|
-
return result;
|
|
250
|
-
}
|
|
251
|
-
function todayTotalUSD() {
|
|
252
|
-
const now = /* @__PURE__ */ new Date();
|
|
253
|
-
const y = now.getFullYear();
|
|
254
|
-
const m = now.getMonth();
|
|
255
|
-
const d = now.getDate();
|
|
256
|
-
let total = 0;
|
|
257
|
-
for (const rec of readTurns()) {
|
|
258
|
-
const ts = new Date(rec.ts);
|
|
259
|
-
if (Number.isNaN(ts.getTime())) continue;
|
|
260
|
-
if (ts.getFullYear() === y && ts.getMonth() === m && ts.getDate() === d) {
|
|
261
|
-
total += rec.costUSD;
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
return total;
|
|
265
|
-
}
|
|
266
|
-
function currentMonthTotals() {
|
|
267
|
-
const now = /* @__PURE__ */ new Date();
|
|
268
|
-
const y = now.getFullYear();
|
|
269
|
-
const m = now.getMonth();
|
|
270
|
-
let usd = 0;
|
|
271
|
-
let jpy = 0;
|
|
272
|
-
let turns = 0;
|
|
273
|
-
for (const rec of readTurns()) {
|
|
274
|
-
const ts = new Date(rec.ts);
|
|
275
|
-
if (Number.isNaN(ts.getTime())) continue;
|
|
276
|
-
if (ts.getFullYear() === y && ts.getMonth() === m) {
|
|
277
|
-
const sa = rec.subagents?.costUSD ?? 0;
|
|
278
|
-
usd += rec.costUSD + sa;
|
|
279
|
-
jpy += rec.costJPY + sa * rec.fxRate;
|
|
280
|
-
turns += 1;
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
return { usd, jpy, turns };
|
|
284
|
-
}
|
|
285
|
-
function logError(context, err) {
|
|
286
|
-
try {
|
|
287
|
-
const p = paths();
|
|
288
|
-
const iso = (/* @__PURE__ */ new Date()).toISOString();
|
|
289
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
290
|
-
const stack = err instanceof Error ? err.stack : void 0;
|
|
291
|
-
let entry = `[${iso}] [${context}] ${message}
|
|
292
|
-
`;
|
|
293
|
-
if (stack) {
|
|
294
|
-
entry += `${stack}
|
|
295
|
-
`;
|
|
296
|
-
}
|
|
297
|
-
try {
|
|
298
|
-
const stat = statSync(p.errorLog);
|
|
299
|
-
if (stat.size > ERROR_LOG_MAX_BYTES) {
|
|
300
|
-
renameSync(p.errorLog, `${p.errorLog}.old`);
|
|
301
|
-
}
|
|
302
|
-
} catch {
|
|
303
|
-
}
|
|
304
|
-
appendFileSync(p.errorLog, entry, "utf8");
|
|
305
|
-
} catch {
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
export {
|
|
310
|
-
paths,
|
|
311
|
-
readConfig,
|
|
312
|
-
readMuteState,
|
|
313
|
-
isMuted,
|
|
314
|
-
writeMuteState,
|
|
315
|
-
clearMuteState,
|
|
316
|
-
loadCursor,
|
|
317
|
-
sanitizeCursor,
|
|
318
|
-
saveCursor,
|
|
319
|
-
appendTurn,
|
|
320
|
-
readTurns,
|
|
321
|
-
todayTotalUSD,
|
|
322
|
-
currentMonthTotals,
|
|
323
|
-
logError
|
|
324
|
-
};
|