ccc-notifier 0.4.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 +5 -3
- package/dist/{budget-EHWPEYXY.js → budget-TBWVHUKA.js} +1 -1
- package/dist/chunk-5PH7PPD6.js +1077 -0
- package/dist/{chunk-TUZVISLD.js → chunk-DDUK4EWQ.js} +1 -1
- package/dist/{chunk-ZHIZZ6V5.js → chunk-ENGUOLTD.js} +1 -1
- 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-J6Y3RMMC.js → chunk-O34L3NSI.js} +1 -1
- package/dist/{chunk-2VPBBIDW.js → chunk-QWBQN4XY.js} +24 -9
- package/dist/{chunk-5LHZOZZO.js → chunk-S3XKO7MY.js} +235 -108
- package/dist/cli.js +381 -72
- package/dist/{dashboard-GYKABTTN.js → dashboard-6F2QNUJT.js} +4 -4
- package/dist/{history-6OUJLXJT.js → history-7KN26LJH.js} +3 -3
- package/dist/{mute-GBDNN5PB.js → mute-MPX3K7QN.js} +2 -2
- package/dist/{setup-W3CSTHJC.js → setup-IXPMVWOE.js} +2 -2
- package/dist/subagent-store-A6MIN22X.js +31 -0
- package/dist/{sweep-IBSV4LRD.js → sweep-UIONM4UA.js} +14 -29
- package/dist/{track-QT2U3E2R.js → track-KEZON6KI.js} +19 -9
- package/package.json +1 -1
- package/dist/chunk-26CISNOE.js +0 -324
- package/dist/chunk-CKVK3UCA.js +0 -357
|
@@ -3,12 +3,12 @@ import {
|
|
|
3
3
|
browserOpenPlan,
|
|
4
4
|
runDashboard,
|
|
5
5
|
writeDashboardHtml
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-QWBQN4XY.js";
|
|
7
7
|
import "./chunk-DGXUSPS4.js";
|
|
8
|
-
import "./chunk-ZHIZZ6V5.js";
|
|
9
|
-
import "./chunk-J6Y3RMMC.js";
|
|
10
8
|
import "./chunk-J5QAYTFE.js";
|
|
11
|
-
import "./chunk-
|
|
9
|
+
import "./chunk-ENGUOLTD.js";
|
|
10
|
+
import "./chunk-O34L3NSI.js";
|
|
11
|
+
import "./chunk-5PH7PPD6.js";
|
|
12
12
|
export {
|
|
13
13
|
browserOpenPlan,
|
|
14
14
|
runDashboard,
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
invalidateCanonicalDashboards
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-ENGUOLTD.js";
|
|
5
5
|
import {
|
|
6
6
|
waitForDataLock
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-O34L3NSI.js";
|
|
8
8
|
import {
|
|
9
9
|
paths
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-5PH7PPD6.js";
|
|
11
11
|
|
|
12
12
|
// src/history.ts
|
|
13
13
|
import { existsSync, readFileSync, writeFileSync, renameSync, rmSync } from "fs";
|
|
@@ -3,12 +3,12 @@ import {
|
|
|
3
3
|
matchesMarker,
|
|
4
4
|
runInit,
|
|
5
5
|
runUninstall
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-S3XKO7MY.js";
|
|
7
7
|
import "./chunk-NV5UOHJA.js";
|
|
8
8
|
import "./chunk-DGXUSPS4.js";
|
|
9
9
|
import "./chunk-HTYUYKFW.js";
|
|
10
10
|
import "./chunk-J5QAYTFE.js";
|
|
11
|
-
import "./chunk-
|
|
11
|
+
import "./chunk-5PH7PPD6.js";
|
|
12
12
|
export {
|
|
13
13
|
matchesMarker,
|
|
14
14
|
runInit,
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
acquireCodexActivityLock,
|
|
4
|
+
closeCodexRootContext,
|
|
5
|
+
codexActivityProjectionKey,
|
|
6
|
+
handleCodexSubagentHook,
|
|
7
|
+
handleCodexUserPromptSubmitHook,
|
|
8
|
+
normalizeAgentType,
|
|
9
|
+
openCodexRootContext,
|
|
10
|
+
projectCodexSubagentActivity,
|
|
11
|
+
readCodexSubagentActivity,
|
|
12
|
+
recordCodexSubagentEvent,
|
|
13
|
+
recordCodexSubagentPayload,
|
|
14
|
+
reduceCodexSubagentActivity,
|
|
15
|
+
validateCodexSubagentPayload
|
|
16
|
+
} from "./chunk-5PH7PPD6.js";
|
|
17
|
+
export {
|
|
18
|
+
acquireCodexActivityLock,
|
|
19
|
+
closeCodexRootContext,
|
|
20
|
+
codexActivityProjectionKey,
|
|
21
|
+
handleCodexSubagentHook,
|
|
22
|
+
handleCodexUserPromptSubmitHook,
|
|
23
|
+
normalizeAgentType,
|
|
24
|
+
openCodexRootContext,
|
|
25
|
+
projectCodexSubagentActivity,
|
|
26
|
+
readCodexSubagentActivity,
|
|
27
|
+
recordCodexSubagentEvent,
|
|
28
|
+
recordCodexSubagentPayload,
|
|
29
|
+
reduceCodexSubagentActivity,
|
|
30
|
+
validateCodexSubagentPayload
|
|
31
|
+
};
|
|
@@ -1,27 +1,30 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
listCodexRollouts
|
|
4
|
+
} from "./chunk-HLJAJS2W.js";
|
|
2
5
|
import {
|
|
3
6
|
codexHome,
|
|
4
7
|
detectCodex
|
|
5
8
|
} from "./chunk-HTYUYKFW.js";
|
|
6
9
|
import {
|
|
7
|
-
collectSubagentUsage
|
|
8
|
-
|
|
9
|
-
} from "./chunk-CKVK3UCA.js";
|
|
10
|
+
collectSubagentUsage
|
|
11
|
+
} from "./chunk-O2AR4PXU.js";
|
|
10
12
|
import {
|
|
11
13
|
computeCost,
|
|
12
14
|
extractBucket,
|
|
13
15
|
getUsdJpy,
|
|
14
16
|
loadPriceTable,
|
|
15
|
-
promptCandidate
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
waitForDataLock
|
|
19
|
-
} from "./chunk-J6Y3RMMC.js";
|
|
17
|
+
promptCandidate,
|
|
18
|
+
splitIntoCodexTurnDrafts
|
|
19
|
+
} from "./chunk-LVMKY6JB.js";
|
|
20
20
|
import {
|
|
21
21
|
formatJPY,
|
|
22
22
|
formatUSD,
|
|
23
23
|
modelDisplayName
|
|
24
24
|
} from "./chunk-J5QAYTFE.js";
|
|
25
|
+
import {
|
|
26
|
+
waitForDataLock
|
|
27
|
+
} from "./chunk-O34L3NSI.js";
|
|
25
28
|
import {
|
|
26
29
|
appendTurn,
|
|
27
30
|
loadCursor,
|
|
@@ -30,7 +33,7 @@ import {
|
|
|
30
33
|
readConfig,
|
|
31
34
|
sanitizeCursor,
|
|
32
35
|
saveCursor
|
|
33
|
-
} from "./chunk-
|
|
36
|
+
} from "./chunk-5PH7PPD6.js";
|
|
34
37
|
|
|
35
38
|
// src/sweep.ts
|
|
36
39
|
import { readFile } from "fs/promises";
|
|
@@ -347,24 +350,6 @@ async function processTranscript(mainPath, table, fx, daysCutoff, dryRun, summar
|
|
|
347
350
|
lock?.release();
|
|
348
351
|
}
|
|
349
352
|
}
|
|
350
|
-
var CODEX_MAX_DEPTH = 4;
|
|
351
|
-
async function listCodexRollouts(sessionsRoot) {
|
|
352
|
-
const found = [];
|
|
353
|
-
const walk = async (dir, depth) => {
|
|
354
|
-
const entries = await fsp.readdir(dir, { withFileTypes: true }).catch(() => null);
|
|
355
|
-
if (entries === null) return;
|
|
356
|
-
for (const e of entries) {
|
|
357
|
-
const full = join(dir, e.name);
|
|
358
|
-
if (e.isFile()) {
|
|
359
|
-
if (e.name.startsWith("rollout-") && e.name.endsWith(".jsonl")) found.push(full);
|
|
360
|
-
} else if (e.isDirectory() && depth < CODEX_MAX_DEPTH) {
|
|
361
|
-
await walk(full, depth + 1);
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
};
|
|
365
|
-
await walk(sessionsRoot, 1);
|
|
366
|
-
return found;
|
|
367
|
-
}
|
|
368
353
|
function codexDraftToRecord(draft, ts, table, fx) {
|
|
369
354
|
const main = draft.agg.main;
|
|
370
355
|
const breakdown = computeCost(main, {}, table);
|
|
@@ -440,8 +425,8 @@ async function codexSessionsRoot() {
|
|
|
440
425
|
async function sweepCodex(summary, table, fx, daysCutoff, flags, lockProvider) {
|
|
441
426
|
const sessionsRoot = await codexSessionsRoot();
|
|
442
427
|
if (sessionsRoot === null) return;
|
|
443
|
-
const
|
|
444
|
-
for (const rolloutPath of rollouts) {
|
|
428
|
+
const discovery = await listCodexRollouts(sessionsRoot);
|
|
429
|
+
for (const rolloutPath of discovery.rollouts) {
|
|
445
430
|
if (!flags.includeActive && await isRecentlyModified(rolloutPath)) {
|
|
446
431
|
summary.skippedActive += 1;
|
|
447
432
|
continue;
|
|
@@ -5,29 +5,30 @@ import {
|
|
|
5
5
|
} from "./chunk-NV5UOHJA.js";
|
|
6
6
|
import {
|
|
7
7
|
writeDashboardHtml
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-QWBQN4XY.js";
|
|
9
9
|
import "./chunk-DGXUSPS4.js";
|
|
10
10
|
import {
|
|
11
|
-
aggregateCodexTurn,
|
|
12
11
|
collectSubagentUsage
|
|
13
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-O2AR4PXU.js";
|
|
14
13
|
import {
|
|
14
|
+
aggregateCodexTurn,
|
|
15
15
|
aggregateNewTurn,
|
|
16
16
|
computeCost,
|
|
17
17
|
getUsdJpy,
|
|
18
18
|
loadPriceTable
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-LVMKY6JB.js";
|
|
20
|
+
import "./chunk-J5QAYTFE.js";
|
|
20
21
|
import {
|
|
21
22
|
isFullDashboardDue,
|
|
22
23
|
makeFullDashboardState,
|
|
23
24
|
writeFullDashboardStateAtomic
|
|
24
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-ENGUOLTD.js";
|
|
25
26
|
import {
|
|
26
27
|
waitForDataLock
|
|
27
|
-
} from "./chunk-
|
|
28
|
-
import "./chunk-J5QAYTFE.js";
|
|
28
|
+
} from "./chunk-O34L3NSI.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-5PH7PPD6.js";
|
|
41
42
|
|
|
42
43
|
// src/track.ts
|
|
43
44
|
function isRecord(v) {
|
|
@@ -83,13 +84,21 @@ 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;
|
|
94
103
|
const commitLock = await waitForDataLock(1e3);
|
|
95
104
|
if (commitLock === null) {
|
|
@@ -137,6 +146,7 @@ async function runTrack(stdinText, opts) {
|
|
|
137
146
|
};
|
|
138
147
|
if (isCodex) {
|
|
139
148
|
record.source = "codex";
|
|
149
|
+
if (activityProjectionKey !== null) record.activityProjectionKey = activityProjectionKey;
|
|
140
150
|
}
|
|
141
151
|
if (breakdown.unknownModels.length > 0) {
|
|
142
152
|
record.unknownModels = breakdown.unknownModels;
|
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
|
-
};
|