ccc-notifier 0.3.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 +6 -4
- package/dist/{budget-KKTFYAXH.js → budget-EHWPEYXY.js} +1 -1
- package/dist/{chunk-ECADO26T.js → chunk-26CISNOE.js} +10 -2
- package/dist/{chunk-QX5KIRSU.js → chunk-2VPBBIDW.js} +167 -53
- package/dist/{chunk-7KVOQ4UZ.js → chunk-5LHZOZZO.js} +1 -1
- package/dist/{chunk-DSV75EF7.js → chunk-CKVK3UCA.js} +1 -1
- package/dist/chunk-J6Y3RMMC.js +172 -0
- package/dist/{chunk-4RZ6OGTD.js → chunk-TUZVISLD.js} +1 -1
- package/dist/chunk-ZHIZZ6V5.js +92 -0
- package/dist/cli.js +13 -13
- package/dist/{dashboard-TH3NOVT3.js → dashboard-GYKABTTN.js} +4 -2
- package/dist/history-6OUJLXJT.js +162 -0
- package/dist/{mute-EPLYH67P.js → mute-GBDNN5PB.js} +2 -2
- package/dist/{setup-PTO5N46B.js → setup-W3CSTHJC.js} +2 -2
- package/dist/{sweep-25EZAOGC.js → sweep-IBSV4LRD.js} +134 -102
- package/dist/track-QT2U3E2R.js +235 -0
- package/package.json +1 -1
- package/dist/history-DLFYQJFM.js +0 -114
- package/dist/track-PXFSGMGL.js +0 -184
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
paths
|
|
4
|
+
} from "./chunk-26CISNOE.js";
|
|
5
|
+
|
|
6
|
+
// src/data-lock.ts
|
|
7
|
+
import { randomUUID } from "crypto";
|
|
8
|
+
import { hostname } from "os";
|
|
9
|
+
import {
|
|
10
|
+
existsSync,
|
|
11
|
+
mkdirSync,
|
|
12
|
+
readFileSync,
|
|
13
|
+
renameSync,
|
|
14
|
+
rmSync,
|
|
15
|
+
writeFileSync
|
|
16
|
+
} from "fs";
|
|
17
|
+
import { join } from "path";
|
|
18
|
+
var DATA_LOCK_LEASE_MS = 3e4;
|
|
19
|
+
var HEARTBEAT_MS = 5e3;
|
|
20
|
+
function ownerFile(dir) {
|
|
21
|
+
return join(dir, "owner.json");
|
|
22
|
+
}
|
|
23
|
+
function readOwner(dir) {
|
|
24
|
+
try {
|
|
25
|
+
const v = JSON.parse(readFileSync(ownerFile(dir), "utf8"));
|
|
26
|
+
if (typeof v.token !== "string" || typeof v.pid !== "number" || typeof v.hostname !== "string" || typeof v.acquiredAt !== "string" || typeof v.heartbeatAt !== "string") return null;
|
|
27
|
+
return v;
|
|
28
|
+
} catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function writeOwner(dir, owner) {
|
|
33
|
+
const tmp = join(dir, `owner.${owner.token}.${randomUUID()}.tmp`);
|
|
34
|
+
try {
|
|
35
|
+
writeFileSync(tmp, `${JSON.stringify(owner)}
|
|
36
|
+
`, "utf8");
|
|
37
|
+
renameSync(tmp, ownerFile(dir));
|
|
38
|
+
} finally {
|
|
39
|
+
rmSync(tmp, { force: true });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function quarantineOwned(dir, token, label) {
|
|
43
|
+
const before = readOwner(dir);
|
|
44
|
+
if (before?.token !== token) return false;
|
|
45
|
+
const quarantine = `${dir}.${label}-${token}-${randomUUID()}`;
|
|
46
|
+
try {
|
|
47
|
+
renameSync(dir, quarantine);
|
|
48
|
+
} catch {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
const moved = readOwner(quarantine);
|
|
52
|
+
if (moved?.token !== token) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
rmSync(quarantine, { recursive: true, force: true });
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
function claimDir(fixed, label, now, metadataWriter = writeOwner) {
|
|
59
|
+
const token = randomUUID();
|
|
60
|
+
const staging = `${fixed}.${label}-${token}`;
|
|
61
|
+
const iso = now.toISOString();
|
|
62
|
+
const owner = { token, pid: process.pid, hostname: hostname(), acquiredAt: iso, heartbeatAt: iso };
|
|
63
|
+
try {
|
|
64
|
+
mkdirSync(staging);
|
|
65
|
+
metadataWriter(staging, owner);
|
|
66
|
+
renameSync(staging, fixed);
|
|
67
|
+
return { token, owner };
|
|
68
|
+
} catch {
|
|
69
|
+
rmSync(staging, { recursive: true, force: true });
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function processDefinitelyDead(pid) {
|
|
74
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
75
|
+
try {
|
|
76
|
+
process.kill(pid, 0);
|
|
77
|
+
return false;
|
|
78
|
+
} catch (err) {
|
|
79
|
+
return err.code === "ESRCH";
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function reclaimerGuardBlocks(now, leaseMs) {
|
|
83
|
+
const owner = readOwner(paths().dataReclaimDir);
|
|
84
|
+
if (owner === null) return true;
|
|
85
|
+
if (owner.hostname !== hostname()) return true;
|
|
86
|
+
const heartbeat = Date.parse(owner.heartbeatAt);
|
|
87
|
+
if (!Number.isFinite(heartbeat) || now.getTime() - heartbeat <= leaseMs) return true;
|
|
88
|
+
return !processDefinitelyDead(owner.pid);
|
|
89
|
+
}
|
|
90
|
+
function tryReclaim(now, leaseMs) {
|
|
91
|
+
const p = paths();
|
|
92
|
+
const guard = claimDir(p.dataReclaimDir, "claim", now);
|
|
93
|
+
if (guard === null) return false;
|
|
94
|
+
try {
|
|
95
|
+
const first = readOwner(p.dataLockDir);
|
|
96
|
+
if (first === null || first.hostname !== hostname()) return false;
|
|
97
|
+
const heartbeatMs = Date.parse(first.heartbeatAt);
|
|
98
|
+
if (!Number.isFinite(heartbeatMs) || now.getTime() - heartbeatMs <= leaseMs) return false;
|
|
99
|
+
if (!processDefinitelyDead(first.pid)) return false;
|
|
100
|
+
const second = readOwner(p.dataLockDir);
|
|
101
|
+
if (second === null || second.token !== first.token || second.heartbeatAt !== first.heartbeatAt) return false;
|
|
102
|
+
const orphan = `${p.dataLockDir}.orphan-${first.token}-${randomUUID()}`;
|
|
103
|
+
try {
|
|
104
|
+
renameSync(p.dataLockDir, orphan);
|
|
105
|
+
} catch {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
const moved = readOwner(orphan);
|
|
109
|
+
if (moved?.token !== first.token || moved.heartbeatAt !== first.heartbeatAt) return false;
|
|
110
|
+
rmSync(orphan, { recursive: true, force: true });
|
|
111
|
+
return true;
|
|
112
|
+
} finally {
|
|
113
|
+
quarantineOwned(p.dataReclaimDir, guard.token, "released");
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function acquireDataLock(opts = {}) {
|
|
117
|
+
const now = opts.now ?? /* @__PURE__ */ new Date();
|
|
118
|
+
const leaseMs = opts.leaseMs ?? DATA_LOCK_LEASE_MS;
|
|
119
|
+
const p = paths();
|
|
120
|
+
if (existsSync(p.dataReclaimDir) && reclaimerGuardBlocks(now, leaseMs)) return null;
|
|
121
|
+
let claim = claimDir(p.dataLockDir, "acquire", now, opts.metadataWriter);
|
|
122
|
+
if (claim === null) {
|
|
123
|
+
if (!tryReclaim(now, leaseMs)) return null;
|
|
124
|
+
if (existsSync(p.dataReclaimDir) && reclaimerGuardBlocks(now, leaseMs)) return null;
|
|
125
|
+
claim = claimDir(p.dataLockDir, "acquire", now, opts.metadataWriter);
|
|
126
|
+
if (claim === null) return null;
|
|
127
|
+
}
|
|
128
|
+
if (existsSync(p.dataReclaimDir) && reclaimerGuardBlocks(now, leaseMs)) {
|
|
129
|
+
quarantineOwned(p.dataLockDir, claim.token, "yielded");
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
let released = false;
|
|
133
|
+
const heartbeat = () => {
|
|
134
|
+
if (released) return false;
|
|
135
|
+
const current = readOwner(p.dataLockDir);
|
|
136
|
+
if (current?.token !== claim.token) return false;
|
|
137
|
+
writeOwner(p.dataLockDir, { ...current, heartbeatAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
138
|
+
return readOwner(p.dataLockDir)?.token === claim.token;
|
|
139
|
+
};
|
|
140
|
+
const timer = setInterval(() => {
|
|
141
|
+
try {
|
|
142
|
+
heartbeat();
|
|
143
|
+
} catch {
|
|
144
|
+
}
|
|
145
|
+
}, HEARTBEAT_MS);
|
|
146
|
+
timer.unref();
|
|
147
|
+
return {
|
|
148
|
+
token: claim.token,
|
|
149
|
+
heartbeat,
|
|
150
|
+
release() {
|
|
151
|
+
if (released) return;
|
|
152
|
+
released = true;
|
|
153
|
+
clearInterval(timer);
|
|
154
|
+
quarantineOwned(p.dataLockDir, claim.token, "released");
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
async function waitForDataLock(timeoutMs, pollMs = 25) {
|
|
159
|
+
const envTimeout = Number.parseInt(process.env.CCCN_LOCK_TIMEOUT_MS ?? "", 10);
|
|
160
|
+
timeoutMs = timeoutMs ?? (Number.isFinite(envTimeout) && envTimeout >= 0 ? envTimeout : 5e3);
|
|
161
|
+
const deadline = Date.now() + timeoutMs;
|
|
162
|
+
do {
|
|
163
|
+
const lock = acquireDataLock();
|
|
164
|
+
if (lock !== null) return lock;
|
|
165
|
+
if (Date.now() >= deadline) return null;
|
|
166
|
+
await new Promise((resolve) => setTimeout(resolve, pollMs));
|
|
167
|
+
} while (true);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export {
|
|
171
|
+
waitForDataLock
|
|
172
|
+
};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
paths
|
|
4
|
+
} from "./chunk-26CISNOE.js";
|
|
5
|
+
|
|
6
|
+
// src/dashboard-state.ts
|
|
7
|
+
import {
|
|
8
|
+
closeSync,
|
|
9
|
+
existsSync,
|
|
10
|
+
openSync,
|
|
11
|
+
readFileSync,
|
|
12
|
+
readSync,
|
|
13
|
+
renameSync,
|
|
14
|
+
rmSync,
|
|
15
|
+
writeFileSync
|
|
16
|
+
} from "fs";
|
|
17
|
+
import { randomUUID } from "crypto";
|
|
18
|
+
function localDate(now) {
|
|
19
|
+
const y = now.getFullYear();
|
|
20
|
+
const m = String(now.getMonth() + 1).padStart(2, "0");
|
|
21
|
+
const d = String(now.getDate()).padStart(2, "0");
|
|
22
|
+
return `${y}-${m}-${d}`;
|
|
23
|
+
}
|
|
24
|
+
function timeZone() {
|
|
25
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone || "unknown";
|
|
26
|
+
}
|
|
27
|
+
function makeFullDashboardState(now = /* @__PURE__ */ new Date()) {
|
|
28
|
+
return { localDate: localDate(now), timeZone: timeZone(), generatedAt: now.toISOString() };
|
|
29
|
+
}
|
|
30
|
+
function readState() {
|
|
31
|
+
const file = paths().dashboardFullStateFile;
|
|
32
|
+
if (!existsSync(file)) return null;
|
|
33
|
+
try {
|
|
34
|
+
const value = JSON.parse(readFileSync(file, "utf8"));
|
|
35
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
|
|
36
|
+
const v = value;
|
|
37
|
+
if (typeof v.localDate !== "string" || !/^\d{4}-\d{2}-\d{2}$/.test(v.localDate) || typeof v.timeZone !== "string" || v.timeZone.length === 0 || typeof v.generatedAt !== "string" || !Number.isFinite(Date.parse(v.generatedAt))) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
return v;
|
|
41
|
+
} catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function isFullDashboardDue(now = /* @__PURE__ */ new Date()) {
|
|
46
|
+
const p = paths();
|
|
47
|
+
if (!existsSync(p.fullDashboardFile)) return true;
|
|
48
|
+
try {
|
|
49
|
+
const fd = openSync(p.fullDashboardFile, "r");
|
|
50
|
+
try {
|
|
51
|
+
const head = Buffer.alloc(512);
|
|
52
|
+
const n = readSync(fd, head, 0, head.length, 0);
|
|
53
|
+
if (head.toString("utf8", 0, n).includes('name="cccn-placeholder"')) return true;
|
|
54
|
+
} finally {
|
|
55
|
+
closeSync(fd);
|
|
56
|
+
}
|
|
57
|
+
} catch {
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
const state = readState();
|
|
61
|
+
if (state === null) return true;
|
|
62
|
+
const expected = makeFullDashboardState(now);
|
|
63
|
+
if (state.timeZone !== expected.timeZone) return true;
|
|
64
|
+
if (state.localDate !== expected.localDate) return true;
|
|
65
|
+
if (state.localDate > expected.localDate) return true;
|
|
66
|
+
if (Date.parse(state.generatedAt) > now.getTime()) return true;
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
function writeFullDashboardStateAtomic(state) {
|
|
70
|
+
const file = paths().dashboardFullStateFile;
|
|
71
|
+
const tmp = `${file}.${process.pid}.${randomUUID()}.tmp`;
|
|
72
|
+
try {
|
|
73
|
+
writeFileSync(tmp, `${JSON.stringify(state)}
|
|
74
|
+
`, "utf8");
|
|
75
|
+
renameSync(tmp, file);
|
|
76
|
+
} finally {
|
|
77
|
+
rmSync(tmp, { force: true });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function invalidateCanonicalDashboards() {
|
|
81
|
+
const p = paths();
|
|
82
|
+
for (const file of [p.recentDashboardFile, p.fullDashboardFile, p.dashboardFullStateFile]) {
|
|
83
|
+
rmSync(file, { force: true });
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export {
|
|
88
|
+
makeFullDashboardState,
|
|
89
|
+
isFullDashboardDue,
|
|
90
|
+
writeFullDashboardStateAtomic,
|
|
91
|
+
invalidateCanonicalDashboards
|
|
92
|
+
};
|
package/dist/cli.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
fmtMuteUntil
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-TUZVISLD.js";
|
|
5
5
|
import {
|
|
6
6
|
codexHooksFile,
|
|
7
7
|
matchesMarker
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-5LHZOZZO.js";
|
|
9
9
|
import {
|
|
10
10
|
notifyOS,
|
|
11
11
|
notifySlack,
|
|
@@ -35,7 +35,7 @@ import {
|
|
|
35
35
|
readConfig,
|
|
36
36
|
readMuteState,
|
|
37
37
|
readTurns
|
|
38
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-26CISNOE.js";
|
|
39
39
|
|
|
40
40
|
// src/cli.ts
|
|
41
41
|
import { realpathSync } from "fs";
|
|
@@ -586,7 +586,7 @@ var COMMANDS = [
|
|
|
586
586
|
en: "Print an aggregated cost report"
|
|
587
587
|
},
|
|
588
588
|
{
|
|
589
|
-
cmd: "dashboard [--days N] [--no-open] [--out <path>] [--refresh <sec>|--no-refresh]",
|
|
589
|
+
cmd: "dashboard [--all|--days N] [--no-open] [--out <path>] [--refresh <sec>|--no-refresh]",
|
|
590
590
|
ja: "HTML\u30C0\u30C3\u30B7\u30E5\u30DC\u30FC\u30C9\u3092\u751F\u6210\u3057\u3066\u30D6\u30E9\u30A6\u30B6\u3067\u958B\u304F",
|
|
591
591
|
en: "Generate and open the HTML dashboard"
|
|
592
592
|
},
|
|
@@ -683,18 +683,18 @@ async function main(argv) {
|
|
|
683
683
|
const text = await readStdin();
|
|
684
684
|
const codex = rest.includes("--codex");
|
|
685
685
|
try {
|
|
686
|
-
const trackMod = await import("./track-
|
|
686
|
+
const trackMod = await import("./track-QT2U3E2R.js");
|
|
687
687
|
await trackMod.runTrack(text, { codex });
|
|
688
688
|
} catch {
|
|
689
689
|
}
|
|
690
690
|
return 0;
|
|
691
691
|
}
|
|
692
692
|
case "init": {
|
|
693
|
-
const { runInit } = await import("./setup-
|
|
693
|
+
const { runInit } = await import("./setup-W3CSTHJC.js");
|
|
694
694
|
return await runInit(rest);
|
|
695
695
|
}
|
|
696
696
|
case "uninstall": {
|
|
697
|
-
const { runUninstall } = await import("./setup-
|
|
697
|
+
const { runUninstall } = await import("./setup-W3CSTHJC.js");
|
|
698
698
|
return await runUninstall(rest);
|
|
699
699
|
}
|
|
700
700
|
case "doctor":
|
|
@@ -702,27 +702,27 @@ async function main(argv) {
|
|
|
702
702
|
case "report":
|
|
703
703
|
return await runReport(rest);
|
|
704
704
|
case "dashboard": {
|
|
705
|
-
const { runDashboard } = await import("./dashboard-
|
|
705
|
+
const { runDashboard } = await import("./dashboard-GYKABTTN.js");
|
|
706
706
|
return await runDashboard(rest);
|
|
707
707
|
}
|
|
708
708
|
case "sweep": {
|
|
709
|
-
const { runSweep } = await import("./sweep-
|
|
709
|
+
const { runSweep } = await import("./sweep-IBSV4LRD.js");
|
|
710
710
|
return await runSweep(rest);
|
|
711
711
|
}
|
|
712
712
|
case "history": {
|
|
713
|
-
const { runHistory } = await import("./history-
|
|
713
|
+
const { runHistory } = await import("./history-6OUJLXJT.js");
|
|
714
714
|
return await runHistory(rest);
|
|
715
715
|
}
|
|
716
716
|
case "budget": {
|
|
717
|
-
const { runBudget } = await import("./budget-
|
|
717
|
+
const { runBudget } = await import("./budget-EHWPEYXY.js");
|
|
718
718
|
return runBudget(rest);
|
|
719
719
|
}
|
|
720
720
|
case "mute": {
|
|
721
|
-
const { runMute } = await import("./mute-
|
|
721
|
+
const { runMute } = await import("./mute-GBDNN5PB.js");
|
|
722
722
|
return runMute(rest);
|
|
723
723
|
}
|
|
724
724
|
case "unmute": {
|
|
725
|
-
const { runUnmute } = await import("./mute-
|
|
725
|
+
const { runUnmute } = await import("./mute-GBDNN5PB.js");
|
|
726
726
|
return runUnmute();
|
|
727
727
|
}
|
|
728
728
|
case "--version":
|
|
@@ -3,10 +3,12 @@ import {
|
|
|
3
3
|
browserOpenPlan,
|
|
4
4
|
runDashboard,
|
|
5
5
|
writeDashboardHtml
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-2VPBBIDW.js";
|
|
7
7
|
import "./chunk-DGXUSPS4.js";
|
|
8
|
+
import "./chunk-ZHIZZ6V5.js";
|
|
9
|
+
import "./chunk-J6Y3RMMC.js";
|
|
8
10
|
import "./chunk-J5QAYTFE.js";
|
|
9
|
-
import "./chunk-
|
|
11
|
+
import "./chunk-26CISNOE.js";
|
|
10
12
|
export {
|
|
11
13
|
browserOpenPlan,
|
|
12
14
|
runDashboard,
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
invalidateCanonicalDashboards
|
|
4
|
+
} from "./chunk-ZHIZZ6V5.js";
|
|
5
|
+
import {
|
|
6
|
+
waitForDataLock
|
|
7
|
+
} from "./chunk-J6Y3RMMC.js";
|
|
8
|
+
import {
|
|
9
|
+
paths
|
|
10
|
+
} from "./chunk-26CISNOE.js";
|
|
11
|
+
|
|
12
|
+
// src/history.ts
|
|
13
|
+
import { existsSync, readFileSync, writeFileSync, renameSync, rmSync } from "fs";
|
|
14
|
+
import { createHash } from "crypto";
|
|
15
|
+
import * as p from "@clack/prompts";
|
|
16
|
+
function parseFlags(argv) {
|
|
17
|
+
let days = null;
|
|
18
|
+
let yes = false;
|
|
19
|
+
for (let i = 0; i < argv.length; i++) {
|
|
20
|
+
const a = argv[i];
|
|
21
|
+
if (a === "--yes" || a === "-y") {
|
|
22
|
+
yes = true;
|
|
23
|
+
} else if (a === "--days") {
|
|
24
|
+
const n = Number.parseInt(argv[i + 1] ?? "", 10);
|
|
25
|
+
if (Number.isFinite(n) && n > 0) days = n;
|
|
26
|
+
i++;
|
|
27
|
+
} else if (a.startsWith("--days=")) {
|
|
28
|
+
const n = Number.parseInt(a.slice("--days=".length), 10);
|
|
29
|
+
if (Number.isFinite(n) && n > 0) days = n;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return { days, yes };
|
|
33
|
+
}
|
|
34
|
+
function readLines(file) {
|
|
35
|
+
const raw = readFileSync(file, "utf8");
|
|
36
|
+
const lines = [];
|
|
37
|
+
for (const line of raw.split("\n")) {
|
|
38
|
+
if (!line.trim()) continue;
|
|
39
|
+
let rec = null;
|
|
40
|
+
try {
|
|
41
|
+
rec = JSON.parse(line);
|
|
42
|
+
} catch {
|
|
43
|
+
rec = null;
|
|
44
|
+
}
|
|
45
|
+
lines.push({ raw: line, rec });
|
|
46
|
+
}
|
|
47
|
+
return lines;
|
|
48
|
+
}
|
|
49
|
+
function isTargeted(rec, cutoffMs) {
|
|
50
|
+
if (cutoffMs === null) return true;
|
|
51
|
+
const ts = Date.parse(rec.ts);
|
|
52
|
+
if (!Number.isFinite(ts)) return false;
|
|
53
|
+
return ts < cutoffMs;
|
|
54
|
+
}
|
|
55
|
+
function atomicWrite(file, content) {
|
|
56
|
+
const tmp = `${file}.tmp`;
|
|
57
|
+
writeFileSync(tmp, content, "utf8");
|
|
58
|
+
renameSync(tmp, file);
|
|
59
|
+
}
|
|
60
|
+
function collectTargets(lines, sub, cutoff) {
|
|
61
|
+
const targetSet = /* @__PURE__ */ new Set();
|
|
62
|
+
for (let i = 0; i < lines.length; i++) {
|
|
63
|
+
const rec = lines[i].rec;
|
|
64
|
+
if (!rec || !isTargeted(rec, cutoff)) continue;
|
|
65
|
+
if (sub === "redact" && !(typeof rec.prompt === "string" && rec.prompt.length > 0)) continue;
|
|
66
|
+
targetSet.add(i);
|
|
67
|
+
}
|
|
68
|
+
return targetSet;
|
|
69
|
+
}
|
|
70
|
+
function targetFingerprint(lines, targets) {
|
|
71
|
+
const hash = createHash("sha256");
|
|
72
|
+
hash.update(`count:${targets.size}
|
|
73
|
+
`);
|
|
74
|
+
for (const i of [...targets].sort((a, b) => a - b)) {
|
|
75
|
+
hash.update(`${i}:${lines[i]?.raw ?? ""}
|
|
76
|
+
`);
|
|
77
|
+
}
|
|
78
|
+
return hash.digest("hex");
|
|
79
|
+
}
|
|
80
|
+
async function runHistory(argv, deps = {}) {
|
|
81
|
+
const [sub, ...rest] = argv;
|
|
82
|
+
if (sub !== "clear" && sub !== "redact") {
|
|
83
|
+
console.error(
|
|
84
|
+
"\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"
|
|
85
|
+
);
|
|
86
|
+
return 1;
|
|
87
|
+
}
|
|
88
|
+
const flags = parseFlags(rest);
|
|
89
|
+
const file = paths().historyFile;
|
|
90
|
+
let lines = existsSync(file) ? readLines(file) : [];
|
|
91
|
+
const cutoff = flags.days !== null ? Date.now() - flags.days * 864e5 : null;
|
|
92
|
+
const scope = flags.days !== null ? `${flags.days}\u65E5\u3088\u308A\u524D` : "\u5168\u671F\u9593";
|
|
93
|
+
let targetSet = collectTargets(lines, sub, cutoff);
|
|
94
|
+
const initialFingerprint = targetFingerprint(lines, targetSet);
|
|
95
|
+
const action = sub === "clear" ? "\u30EC\u30B3\u30FC\u30C9\u3054\u3068\u524A\u9664" : "\u30D7\u30ED\u30F3\u30D7\u30C8\u5168\u6587\u3092\u6D88\u53BB";
|
|
96
|
+
if (targetSet.size > 0 && !flags.yes) {
|
|
97
|
+
const confirmed = await (deps.confirm ?? p.confirm)({
|
|
98
|
+
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?`,
|
|
99
|
+
initialValue: false
|
|
100
|
+
});
|
|
101
|
+
if (p.isCancel(confirmed) || !confirmed) {
|
|
102
|
+
p.cancel("\u30AD\u30E3\u30F3\u30BB\u30EB\u3057\u307E\u3057\u305F");
|
|
103
|
+
return 0;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const lock = await waitForDataLock();
|
|
107
|
+
if (lock === null) {
|
|
108
|
+
console.error("\u5C65\u6B74\u306E\u66F4\u65B0\u30ED\u30C3\u30AF\u3092\u53D6\u5F97\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002\u5F8C\u3067\u3082\u3046\u4E00\u5EA6\u304A\u8A66\u3057\u304F\u3060\u3055\u3044 / history lock is busy");
|
|
109
|
+
return 1;
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
lines = existsSync(file) ? readLines(file) : [];
|
|
113
|
+
targetSet = collectTargets(lines, sub, cutoff);
|
|
114
|
+
if (!flags.yes && targetFingerprint(lines, targetSet) !== initialFingerprint) {
|
|
115
|
+
console.error(
|
|
116
|
+
"\u78BA\u8A8D\u4E2D\u306B\u5C65\u6B74\u304C\u5909\u66F4\u3055\u308C\u305F\u305F\u3081\u51E6\u7406\u3057\u307E\u305B\u3093\u3067\u3057\u305F\u3002\u3082\u3046\u4E00\u5EA6\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044 / history changed; retry"
|
|
117
|
+
);
|
|
118
|
+
return 1;
|
|
119
|
+
}
|
|
120
|
+
invalidateCanonicalDashboards();
|
|
121
|
+
if (!existsSync(file)) {
|
|
122
|
+
console.log("\u5C65\u6B74\u304C\u3042\u308A\u307E\u305B\u3093(history.jsonl \u306F\u672A\u4F5C\u6210\u3067\u3059)\u3002");
|
|
123
|
+
return 0;
|
|
124
|
+
}
|
|
125
|
+
if (targetSet.size === 0) {
|
|
126
|
+
console.log(`\u5BFE\u8C61\u304C\u3042\u308A\u307E\u305B\u3093(${scope})\u3002`);
|
|
127
|
+
return 0;
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
if (sub === "clear") {
|
|
131
|
+
const kept = lines.filter((_, i) => !targetSet.has(i)).map((l) => l.raw);
|
|
132
|
+
if (kept.length === 0) {
|
|
133
|
+
rmSync(file, { force: true });
|
|
134
|
+
} else {
|
|
135
|
+
atomicWrite(file, kept.join("\n") + "\n");
|
|
136
|
+
}
|
|
137
|
+
console.log(`\u5C65\u6B74 ${targetSet.size} \u4EF6\u3092\u524A\u9664\u3057\u307E\u3057\u305F(${scope})\u3002`);
|
|
138
|
+
} else {
|
|
139
|
+
const out = lines.map((l, i) => {
|
|
140
|
+
if (!targetSet.has(i) || l.rec === null) return l.raw;
|
|
141
|
+
return JSON.stringify({ ...l.rec, prompt: "" });
|
|
142
|
+
});
|
|
143
|
+
atomicWrite(file, out.join("\n") + "\n");
|
|
144
|
+
console.log(
|
|
145
|
+
`\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`
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
} catch (err) {
|
|
149
|
+
console.error(
|
|
150
|
+
`\u5C65\u6B74\u306E\u66F4\u65B0\u306B\u5931\u6557\u3057\u307E\u3057\u305F / failed to update history: ${err instanceof Error ? err.message : String(err)}`
|
|
151
|
+
);
|
|
152
|
+
return 1;
|
|
153
|
+
}
|
|
154
|
+
invalidateCanonicalDashboards();
|
|
155
|
+
return 0;
|
|
156
|
+
} finally {
|
|
157
|
+
lock.release();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
export {
|
|
161
|
+
runHistory
|
|
162
|
+
};
|
|
@@ -3,12 +3,12 @@ import {
|
|
|
3
3
|
matchesMarker,
|
|
4
4
|
runInit,
|
|
5
5
|
runUninstall
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-5LHZOZZO.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-26CISNOE.js";
|
|
12
12
|
export {
|
|
13
13
|
matchesMarker,
|
|
14
14
|
runInit,
|