ccc-notifier 0.6.2 → 0.7.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.
Files changed (30) hide show
  1. package/README.md +5 -0
  2. package/dist/{budget-V7CCMJHF.js → budget-O4VAP6RB.js} +2 -2
  3. package/dist/chunk-3M5UQK76.js +92 -0
  4. package/dist/{sweep-65D57PXU.js → chunk-6BXD6HU5.js} +275 -113
  5. package/dist/chunk-6IVSZ3S4.js +22 -0
  6. package/dist/{chunk-NV5UOHJA.js → chunk-JLDI4D5E.js} +5 -5
  7. package/dist/{chunk-T6Z2ZAN4.js → chunk-ML5H43NH.js} +1 -1
  8. package/dist/{chunk-6UZXXO5J.js → chunk-O2E4VTDR.js} +36 -9
  9. package/dist/{chunk-D4D76RGQ.js → chunk-OCKZZVOG.js} +353 -20
  10. package/dist/{chunk-27SJELD2.js → chunk-PXMXEFM7.js} +22 -107
  11. package/dist/{chunk-6HTETN26.js → chunk-Q45AAPJY.js} +18 -1
  12. package/dist/chunk-V4IS4FXH.js +454 -0
  13. package/dist/{chunk-OOAC5ULQ.js → chunk-WONMZ466.js} +184 -4
  14. package/dist/{chunk-OXXDZZPJ.js → chunk-X7U63BZG.js} +4 -6
  15. package/dist/{chunk-J5QAYTFE.js → chunk-XG73C6CC.js} +10 -1
  16. package/dist/cli.js +286 -29
  17. package/dist/{dashboard-VEKQ4TI3.js → dashboard-64XHDUVT.js} +6 -4
  18. package/dist/{history-DL4SG3PG.js → history-W34EJOVV.js} +5 -3
  19. package/dist/{mute-UIR5P5N5.js → mute-23BWQXS3.js} +2 -2
  20. package/dist/reset-cursors-5CCM6MQL.js +41 -0
  21. package/dist/scan-UB6ORRZK.js +87 -0
  22. package/dist/{setup-I3JNGWZG.js → setup-VLMW433R.js} +5 -6
  23. package/dist/{subagent-store-US4TJJQR.js → subagent-store-USG3WJEB.js} +1 -1
  24. package/dist/sweep-DVOV4XIZ.js +20 -0
  25. package/dist/track-P6A5WDJZ.js +368 -0
  26. package/package.json +1 -1
  27. package/dist/chunk-HLJAJS2W.js +0 -55
  28. package/dist/chunk-HTYUYKFW.js +0 -21
  29. package/dist/chunk-OYD6H3ZZ.js +0 -124
  30. package/dist/track-WCP7446C.js +0 -257
package/dist/cli.js CHANGED
@@ -1,34 +1,37 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  fmtMuteUntil
4
- } from "./chunk-T6Z2ZAN4.js";
4
+ } from "./chunk-ML5H43NH.js";
5
5
  import {
6
6
  CODEX_HOOK_EVENTS,
7
7
  CODEX_HOOK_TIMEOUT_SECONDS,
8
8
  matchesMarker,
9
9
  parseConfiguredOwnedCodexHookCommand
10
- } from "./chunk-OXXDZZPJ.js";
10
+ } from "./chunk-X7U63BZG.js";
11
11
  import {
12
12
  notifyOS,
13
13
  notifySlack,
14
14
  selectNotifyBackend
15
- } from "./chunk-NV5UOHJA.js";
16
- import {
17
- findLatestCodexRollout
18
- } from "./chunk-HLJAJS2W.js";
19
- import {
20
- codexHome,
21
- detectCodex
22
- } from "./chunk-HTYUYKFW.js";
15
+ } from "./chunk-JLDI4D5E.js";
23
16
  import {
24
17
  aggregateNewTurn,
18
+ claudeTranscriptRoots,
19
+ findLatestCodexRollout,
25
20
  getUsdJpy,
21
+ listCodexRollouts,
22
+ rootForClaudePath,
26
23
  splitIntoCodexTurnDrafts
27
- } from "./chunk-D4D76RGQ.js";
24
+ } from "./chunk-OCKZZVOG.js";
28
25
  import {
26
+ codexHome,
29
27
  computeCost,
28
+ detectCodex,
30
29
  loadPriceTable
31
- } from "./chunk-6HTETN26.js";
30
+ } from "./chunk-Q45AAPJY.js";
31
+ import {
32
+ effectiveSurface,
33
+ surfaceLabel
34
+ } from "./chunk-6IVSZ3S4.js";
32
35
  import {
33
36
  isWSL
34
37
  } from "./chunk-DGXUSPS4.js";
@@ -36,14 +39,16 @@ import {
36
39
  formatJPY,
37
40
  formatTokens,
38
41
  formatUSD
39
- } from "./chunk-J5QAYTFE.js";
42
+ } from "./chunk-XG73C6CC.js";
40
43
  import {
44
+ cursorPaths,
45
+ hasUnresolvedPendingMarker,
41
46
  isMuted,
42
47
  paths,
43
48
  readConfig,
44
49
  readMuteState,
45
50
  readTurns
46
- } from "./chunk-OOAC5ULQ.js";
51
+ } from "./chunk-WONMZ466.js";
47
52
 
48
53
  // src/cli.ts
49
54
  import { realpathSync as realpathSync2 } from "fs";
@@ -52,7 +57,7 @@ import { fileURLToPath, pathToFileURL } from "url";
52
57
 
53
58
  // src/doctor.ts
54
59
  import { existsSync as existsSync2, readdirSync, statSync as statSync2 } from "fs";
55
- import { readFile } from "fs/promises";
60
+ import { open, readFile } from "fs/promises";
56
61
  import { homedir } from "os";
57
62
  import { join as join2 } from "path";
58
63
 
@@ -643,6 +648,211 @@ async function checkCodexRecentSessionTotal(configured) {
643
648
  return true;
644
649
  }
645
650
  }
651
+ var PEEK_CHUNK_BYTES = 64 * 1024;
652
+ var PEEK_MAX_BYTES = 4 * 1024 * 1024;
653
+ async function peekFirstJsonLine(path, maxBytes = PEEK_MAX_BYTES) {
654
+ try {
655
+ const fh = await open(path, "r");
656
+ try {
657
+ const chunk = Buffer.alloc(Math.min(PEEK_CHUNK_BYTES, maxBytes));
658
+ const parts = [];
659
+ let read = 0;
660
+ let line = null;
661
+ while (read < maxBytes) {
662
+ const { bytesRead } = await fh.read(chunk, 0, Math.min(chunk.length, maxBytes - read), read);
663
+ if (bytesRead === 0) {
664
+ line = Buffer.concat(parts).toString("utf8");
665
+ break;
666
+ }
667
+ const slice = chunk.subarray(0, bytesRead);
668
+ const nl = slice.indexOf(10);
669
+ if (nl !== -1) {
670
+ parts.push(Buffer.from(slice.subarray(0, nl)));
671
+ line = Buffer.concat(parts).toString("utf8");
672
+ break;
673
+ }
674
+ parts.push(Buffer.from(slice));
675
+ read += bytesRead;
676
+ }
677
+ if (line === null) return null;
678
+ const trimmed = line.trim();
679
+ if (trimmed.length === 0) return null;
680
+ const obj = JSON.parse(trimmed);
681
+ return isRecord(obj) ? obj : null;
682
+ } finally {
683
+ await fh.close();
684
+ }
685
+ } catch {
686
+ return null;
687
+ }
688
+ }
689
+ async function listProjectDirsSafe(root) {
690
+ try {
691
+ const entries = readdirSync(root, { withFileTypes: true });
692
+ return entries.filter((e) => e.isDirectory()).map((e) => join2(root, e.name));
693
+ } catch {
694
+ return [];
695
+ }
696
+ }
697
+ async function listTranscriptsSafe(projectDir) {
698
+ try {
699
+ const entries = readdirSync(projectDir, { withFileTypes: true });
700
+ return entries.filter((e) => e.isFile() && e.name.endsWith(".jsonl")).map((e) => join2(projectDir, e.name));
701
+ } catch {
702
+ return [];
703
+ }
704
+ }
705
+ async function discoverClaudeFilesForDoctor(roots) {
706
+ const files = [];
707
+ for (const root of roots) {
708
+ for (const dir of await listProjectDirsSafe(root.path)) {
709
+ files.push(...await listTranscriptsSafe(dir));
710
+ }
711
+ }
712
+ return files;
713
+ }
714
+ async function checkDesktopScan() {
715
+ const roots = await claudeTranscriptRoots();
716
+ const desktopRoots = roots.filter((r) => r.surface === "desktop");
717
+ if (desktopRoots.length === 0) {
718
+ log("ok", "Claude \u30C7\u30B9\u30AF\u30C8\u30C3\u30D7\u306E\u30B9\u30AD\u30E3\u30F3\u30EB\u30FC\u30C8\u306F\u691C\u51FA\u3055\u308C\u307E\u305B\u3093\u3067\u3057\u305F(\u672A\u4F7F\u7528\u3001\u307E\u305F\u306F macOS \u4EE5\u5916\u30FBCCCN_CLAUDE_DESKTOP_ROOTS \u672A\u8A2D\u5B9A)");
719
+ } else {
720
+ for (const root of desktopRoots) {
721
+ const exists = existsSync2(root.path);
722
+ log(
723
+ exists ? "ok" : "warn",
724
+ `Claude \u30C7\u30B9\u30AF\u30C8\u30C3\u30D7\u306E\u30B9\u30AD\u30E3\u30F3\u30EB\u30FC\u30C8: ${root.path}${exists ? "" : "(\u73FE\u5728\u306F\u4E0D\u5728\u3002\u30A2\u30D7\u30EA\u66F4\u65B0\u3067\u30EC\u30A4\u30A2\u30A6\u30C8\u304C\u5909\u308F\u3063\u305F\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059)"}`
725
+ );
726
+ }
727
+ }
728
+ const tracked = cursorPaths();
729
+ const claudeFiles = await discoverClaudeFilesForDoctor(roots);
730
+ const claudeUntracked = claudeFiles.filter((f) => !tracked.has(f)).length;
731
+ log(
732
+ "ok",
733
+ `Claude transcript \u8FFD\u8DE1\u6F0F\u308C: ${claudeUntracked}\u4EF6(\u5168 ${claudeFiles.length}\u4EF6\u4E2D\u3002\u672A\u8FFD\u8DE1\u5206\u306F ccc-notifier scan \u3067\u56DE\u53CE\u3067\u304D\u307E\u3059)`
734
+ );
735
+ const sessionToFiles = /* @__PURE__ */ new Map();
736
+ for (const f of claudeFiles) {
737
+ const line = await peekFirstJsonLine(f);
738
+ const sid = typeof line?.sessionId === "string" ? line.sessionId : null;
739
+ if (sid === null || sid.length === 0) continue;
740
+ const files = sessionToFiles.get(sid) ?? [];
741
+ files.push(f);
742
+ sessionToFiles.set(sid, files);
743
+ }
744
+ const dupEntries = [...sessionToFiles.entries()].filter(([, files]) => files.length > 1);
745
+ if (dupEntries.length > 0) {
746
+ const multiRoot = dupEntries.filter(
747
+ ([, files]) => new Set(files.map((f) => rootForClaudePath(f, roots)?.path ?? "")).size > 1
748
+ ).length;
749
+ const sample = dupEntries.slice(0, 3).map(([sid, files]) => `${sid.slice(0, 8)}\u2026(${files.length}\u30D5\u30A1\u30A4\u30EB)`).join(" / ");
750
+ log(
751
+ "warn",
752
+ `\u540C\u4E00 sessionId \u304C\u8907\u6570\u306E transcript \u30D5\u30A1\u30A4\u30EB\u306B\u73FE\u308C\u3066\u3044\u307E\u3059(${dupEntries.length}\u4EF6\u30FB\u3046\u3061\u8907\u6570\u30EB\u30FC\u30C8\u306B\u307E\u305F\u304C\u308B\u3082\u306E ${multiRoot}\u4EF6): ${sample}\u3002\u4E8C\u91CD\u8A08\u4E0A\u306E\u53EF\u80FD\u6027\u304C\u3042\u308B\u305F\u3081 history \u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044`
753
+ );
754
+ } else {
755
+ log("ok", "\u540C\u4E00 sessionId \u304C\u8907\u6570\u306E Claude transcript \u30D5\u30A1\u30A4\u30EB\u306B\u91CD\u8907\u3059\u308B\u30B1\u30FC\u30B9\u306F\u691C\u51FA\u3055\u308C\u307E\u305B\u3093\u3067\u3057\u305F");
756
+ }
757
+ const sessionsRoot = join2(codexHome(), "sessions");
758
+ if (!existsSync2(sessionsRoot)) {
759
+ log("ok", "Codex \u30BB\u30C3\u30B7\u30E7\u30F3\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u304C\u7121\u3044\u305F\u3081 originator \u5185\u8A33\u30FB\u8FFD\u8DE1\u6F0F\u308C\u306F\u30B9\u30AD\u30C3\u30D7\u3057\u307E\u3057\u305F");
760
+ return true;
761
+ }
762
+ let codexFiles = [];
763
+ try {
764
+ codexFiles = (await listCodexRollouts(sessionsRoot)).rollouts;
765
+ } catch (err) {
766
+ log("warn", `Codex rollout \u306E\u5217\u6319\u4E2D\u306B\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F: ${errMessage(err)}`);
767
+ return true;
768
+ }
769
+ const codexUntracked = codexFiles.filter((f) => !tracked.has(f)).length;
770
+ log(
771
+ "ok",
772
+ `Codex rollout \u8FFD\u8DE1\u6F0F\u308C: ${codexUntracked}\u4EF6(\u5168 ${codexFiles.length}\u4EF6\u4E2D\u3002\u672A\u8FFD\u8DE1\u5206\u306F ccc-notifier scan \u3067\u56DE\u53CE\u3067\u304D\u307E\u3059)`
773
+ );
774
+ const originatorCounts = /* @__PURE__ */ new Map();
775
+ const sessionIdToFiles = /* @__PURE__ */ new Map();
776
+ for (const f of codexFiles) {
777
+ const line = await peekFirstJsonLine(f);
778
+ const payload = isRecord(line?.payload) ? line.payload : null;
779
+ const originator = typeof payload?.originator === "string" ? payload.originator : "(unknown)";
780
+ originatorCounts.set(originator, (originatorCounts.get(originator) ?? 0) + 1);
781
+ const sid = typeof payload?.id === "string" ? payload.id : typeof payload?.session_id === "string" ? payload.session_id : null;
782
+ if (sid !== null && sid.length > 0) {
783
+ const set = sessionIdToFiles.get(sid) ?? /* @__PURE__ */ new Set();
784
+ set.add(f);
785
+ sessionIdToFiles.set(sid, set);
786
+ }
787
+ }
788
+ const originatorSummary = [...originatorCounts.entries()].sort((a, b) => b[1] - a[1]).map(([k, v]) => `${k}:${v}`).join(" / ");
789
+ log("ok", `Codex originator \u5185\u8A33: ${originatorSummary.length > 0 ? originatorSummary : "(rollout \u306A\u3057)"}`);
790
+ const dupCodex = [...sessionIdToFiles.values()].filter((files) => files.size > 1).length;
791
+ if (dupCodex > 0) {
792
+ log(
793
+ "warn",
794
+ `\u540C\u4E00 session_id \u3092\u6301\u3064 Codex rollout \u304C\u8907\u6570\u30D5\u30A1\u30A4\u30EB\u306B\u307E\u305F\u304C\u3063\u3066\u3044\u307E\u3059(${dupCodex}\u4EF6)\u3002\u4E8C\u91CD\u8A08\u4E0A\u306E\u53EF\u80FD\u6027\u304C\u3042\u308B\u305F\u3081 history \u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044`
795
+ );
796
+ } else {
797
+ log("ok", "\u540C\u4E00 session_id \u304C\u8907\u6570\u306E Codex rollout \u30D5\u30A1\u30A4\u30EB\u306B\u91CD\u8907\u3059\u308B\u30B1\u30FC\u30B9\u306F\u691C\u51FA\u3055\u308C\u307E\u305B\u3093\u3067\u3057\u305F");
798
+ }
799
+ return true;
800
+ }
801
+ function checkPendingAppendMarker() {
802
+ if (!hasUnresolvedPendingMarker()) {
803
+ log("ok", "\u53D6\u308A\u8FBC\u307F\u4FDD\u7559\u30DE\u30FC\u30AB\u30FC\u306F\u6B63\u5E38\u3067\u3059");
804
+ return true;
805
+ }
806
+ log(
807
+ "warn",
808
+ "\u53D6\u308A\u8FBC\u307F\u4FDD\u7559\u30DE\u30FC\u30AB\u30FC(cache/pending-append.json)\u304C\u58CA\u308C\u305F\u5F62\u8DE1\u304C\u3042\u308A\u307E\u3059\u3002\u5B89\u5168\u5074\u3067\u5168\u30BB\u30C3\u30B7\u30E7\u30F3\u3092\u4FDD\u7559\u6271\u3044\u306B\u3057\u3066\u3044\u308B\u305F\u3081\u3001Stop hook \u306E\u305F\u3073\u306B history \u3092\u8AAD\u307F\u76F4\u3057\u307E\u3059(\u4E8C\u91CD\u8A08\u4E0A\u306F\u8D77\u304D\u307E\u305B\u3093)\u3002\u89E3\u6D88\u3059\u308B\u306B\u306F ccc-notifier reset-cursors \u3092\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044(\u53D6\u308A\u8FBC\u307F\u4F4D\u7F6E\u3092\u6368\u3066\u3066\u304B\u3089\u89E3\u9664\u3057\u307E\u3059\u3002\u5C65\u6B74\u306F\u4FDD\u6301\u3055\u308C\u3001\u6B21\u56DE\u306E\u53D6\u308A\u8FBC\u307F\u3060\u3051\u5168\u518D\u8D70\u67FB\u3067\u6642\u9593\u304C\u304B\u304B\u308A\u307E\u3059)\u3002\u30DE\u30FC\u30AB\u30FC\u30D5\u30A1\u30A4\u30EB\u5358\u72EC\u306E\u524A\u9664\u3067\u306F\u3001\u307E\u3060\u56DE\u5FA9\u3057\u3066\u3044\u306A\u3044\u30BB\u30C3\u30B7\u30E7\u30F3\u304C\u518D\u8A08\u4E0A\u3055\u308C\u5F97\u307E\u3059"
809
+ );
810
+ return true;
811
+ }
812
+ function checkDuplicateHistoryTurns() {
813
+ const turns = readTurns();
814
+ const groups = /* @__PURE__ */ new Map();
815
+ const byIngestKey = /* @__PURE__ */ new Map();
816
+ for (const rec of turns) {
817
+ if (typeof rec.ingestKey === "string" && rec.ingestKey.length > 0) {
818
+ const keyed = byIngestKey.get(rec.ingestKey) ?? [];
819
+ keyed.push(rec);
820
+ byIngestKey.set(rec.ingestKey, keyed);
821
+ }
822
+ if (!rec.sessionId || !rec.ts) continue;
823
+ const key = `${rec.sessionId} ${rec.ts} ${rec.source ?? "claude"}`;
824
+ const list = groups.get(key) ?? [];
825
+ list.push(rec);
826
+ groups.set(key, list);
827
+ }
828
+ const dupKeys = [...byIngestKey.entries()].filter(([, list]) => list.length > 1);
829
+ if (dupKeys.length > 0) {
830
+ const rows = dupKeys.reduce((sum, [, list]) => sum + list.length, 0);
831
+ const sample2 = dupKeys.slice(0, 3).map(([key, list]) => `${key.slice(0, 8)}\u2026(${list[0].sessionId.slice(0, 8)}\u2026 apiCalls ${list.map((r) => r.apiCalls).join("/")})`);
832
+ log(
833
+ "warn",
834
+ `history.jsonl \u306B\u540C\u4E00\u306E\u53D6\u308A\u8FBC\u307F\u30AD\u30FC\u3092\u6301\u3064\u30EC\u30B3\u30FC\u30C9\u304C\u8907\u6570\u3042\u308A\u307E\u3059(${dupKeys.length}\u7D44\u30FB${rows}\u884C)\u3002\u540C\u3058 API \u547C\u3073\u51FA\u3057\u3092\u4E8C\u5EA6\u8A08\u4E0A\u3057\u3066\u3044\u307E\u3059\u3002\u4F8B: ${sample2.join(", ")}`
835
+ );
836
+ } else {
837
+ log("ok", "history.jsonl \u306B\u540C\u4E00\u306E\u53D6\u308A\u8FBC\u307F\u30AD\u30FC\u3092\u6301\u3064\u91CD\u8907\u30EC\u30B3\u30FC\u30C9\u306F\u691C\u51FA\u3055\u308C\u307E\u305B\u3093\u3067\u3057\u305F");
838
+ }
839
+ const dupGroups = [...groups.entries()].filter(([, list]) => list.length > 1);
840
+ if (dupGroups.length === 0) {
841
+ log("ok", "history.jsonl \u306B\u540C\u4E00 sessionId+ts \u306E\u91CD\u8907\u30BF\u30FC\u30F3\u306F\u691C\u51FA\u3055\u308C\u307E\u305B\u3093\u3067\u3057\u305F");
842
+ return true;
843
+ }
844
+ const totalDupRows = dupGroups.reduce((sum, [, list]) => sum + list.length, 0);
845
+ const sample = dupGroups.slice(0, 3).map(([key, list]) => {
846
+ const [sessionId] = key.split(" ");
847
+ const apiCallsList = list.map((r) => r.apiCalls).join("/");
848
+ return `${sessionId.slice(0, 8)}\u2026(apiCalls ${apiCallsList})`;
849
+ });
850
+ log(
851
+ "warn",
852
+ `history.jsonl \u306B\u540C\u4E00 sessionId+ts \u306E\u91CD\u8907\u30BF\u30FC\u30F3\u3092\u691C\u51FA\u3057\u307E\u3057\u305F(${dupGroups.length}\u7D44\u30FB${totalDupRows}\u884C)\u3002\u4E8C\u91CD\u8A08\u4E0A\u306E\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059\u3002\u4F8B: ${sample.join(", ")}\u3002\u5143 JSONL \u304B\u3089\u4F5C\u308A\u76F4\u3059\u306A\u3089 ccc-notifier sweep\u3001\u500B\u5225\u306B\u6D88\u3059\u306A\u3089 history clear \u3092\u691C\u8A0E\u3057\u3066\u304F\u3060\u3055\u3044`
853
+ );
854
+ return true;
855
+ }
646
856
  async function runDoctor() {
647
857
  const results = [];
648
858
  results.push(await safeRun("settings.json", () => checkHookRegistration()));
@@ -667,6 +877,9 @@ async function runDoctor() {
667
877
  results.push(await safeRun("notify", () => checkNotification(cfg)));
668
878
  results.push(await safeRun("claude-recent-session", () => checkClaudeRecentSessionTotal(latestTranscript)));
669
879
  results.push(await safeRun("codex-recent-session", () => checkCodexRecentSessionTotal(codexStopConfigured)));
880
+ results.push(await safeRun("desktop-scan", () => checkDesktopScan()));
881
+ results.push(await safeRun("pending-append", () => Promise.resolve(checkPendingAppendMarker())));
882
+ results.push(await safeRun("duplicate-history", () => Promise.resolve(checkDuplicateHistoryTurns())));
670
883
  const hasFailure = results.some((ok) => ok === false);
671
884
  return hasFailure ? 1 : 0;
672
885
  }
@@ -741,6 +954,7 @@ function turnCostByModelWithSA(rec) {
741
954
  function aggregate(turns) {
742
955
  const dailyMap = /* @__PURE__ */ new Map();
743
956
  const modelMap = /* @__PURE__ */ new Map();
957
+ const surfaceMap = /* @__PURE__ */ new Map();
744
958
  const total = {
745
959
  turns: 0,
746
960
  inputTokens: 0,
@@ -777,6 +991,12 @@ function aggregate(turns) {
777
991
  m.costJPY += modelUsd * rec.fxRate;
778
992
  modelMap.set(model, m);
779
993
  }
994
+ const surface = effectiveSurface(rec);
995
+ const s = surfaceMap.get(surface) ?? { turns: 0, costUSD: 0, costJPY: 0 };
996
+ s.turns += 1;
997
+ s.costUSD += totalUsd;
998
+ s.costJPY += totalJpy;
999
+ surfaceMap.set(surface, s);
780
1000
  total.turns += 1;
781
1001
  total.inputTokens += inTok;
782
1002
  total.outputTokens += outTok;
@@ -799,7 +1019,9 @@ function aggregate(turns) {
799
1019
  const daily = [...dailyMap.values()].sort((a, b) => a.date.localeCompare(b.date));
800
1020
  const byModel = {};
801
1021
  for (const [model, agg] of modelMap) byModel[model] = agg;
802
- return { daily, byModel, total };
1022
+ const bySurface = {};
1023
+ for (const [surface, agg] of surfaceMap) bySurface[surface] = agg;
1024
+ return { daily, byModel, bySurface, total };
803
1025
  }
804
1026
  function printTable(result, days) {
805
1027
  console.log(`\u76F4\u8FD1 ${days} \u65E5\u9593\u306E\u96C6\u8A08 (last ${days} days):`);
@@ -834,6 +1056,17 @@ function printTable(result, days) {
834
1056
  );
835
1057
  }
836
1058
  console.log("\u203B \u8907\u6570\u30E2\u30C7\u30EB\u3092\u4F7F\u3063\u305F\u30BF\u30FC\u30F3\u306F\u5404\u30E2\u30C7\u30EB\u306E\u884C\u306B1\u305A\u3064\u8A08\u4E0A\u3057\u307E\u3059(\u30BF\u30FC\u30F3\u6570\u306E\u5408\u8A08\u306F\u7DCF\u30BF\u30FC\u30F3\u6570\u3092\u8D85\u3048\u308B\u3053\u3068\u304C\u3042\u308A\u307E\u3059)\u3002");
1059
+ const surfaceEntries = Object.entries(result.bySurface);
1060
+ if (surfaceEntries.length > 1 || surfaceEntries.length === 1 && surfaceEntries[0][0] !== "cli") {
1061
+ console.log("");
1062
+ console.log("\u30B5\u30FC\u30D5\u30A7\u30B9\u5225 (By surface):");
1063
+ console.log("\u30B5\u30FC\u30D5\u30A7\u30B9".padEnd(20) + "\u30BF\u30FC\u30F3".padStart(8) + "USD".padStart(10) + "JPY".padStart(12));
1064
+ for (const [surface, s] of surfaceEntries.sort((a, b) => b[1].costUSD - a[1].costUSD)) {
1065
+ console.log(
1066
+ surfaceLabel(surface).padEnd(20) + String(s.turns).padStart(8) + formatUSD(s.costUSD).padStart(10) + formatJPY(s.costJPY).padStart(12)
1067
+ );
1068
+ }
1069
+ }
837
1070
  console.log("");
838
1071
  console.log(`\u5408\u8A08 (Total): ${formatUSD(result.total.costUSD)} (${formatJPY(result.total.costJPY)}) / ${result.total.turns} turns`);
839
1072
  }
@@ -846,7 +1079,13 @@ async function runReport(argv) {
846
1079
  }
847
1080
  const result = aggregate(turns);
848
1081
  if (json) {
849
- console.log(JSON.stringify({ days, daily: result.daily, byModel: result.byModel, total: result.total }, null, 2));
1082
+ console.log(
1083
+ JSON.stringify(
1084
+ { days, daily: result.daily, byModel: result.byModel, bySurface: result.bySurface, total: result.total },
1085
+ null,
1086
+ 2
1087
+ )
1088
+ );
850
1089
  return 0;
851
1090
  }
852
1091
  printTable(result, days);
@@ -872,11 +1111,21 @@ var COMMANDS = [
872
1111
  ja: "HTML\u30C0\u30C3\u30B7\u30E5\u30DC\u30FC\u30C9\u3092\u751F\u6210\u3057\u3066\u30D6\u30E9\u30A6\u30B6\u3067\u958B\u304F",
873
1112
  en: "Generate and open the HTML dashboard"
874
1113
  },
1114
+ {
1115
+ cmd: "reset-cursors",
1116
+ ja: "\u53D6\u308A\u8FBC\u307F\u4F4D\u7F6E\u3092\u6368\u3066\u3066\u6B21\u56DE\u306B\u5168\u518D\u8D70\u67FB\u3055\u305B\u308B(\u5C65\u6B74\u306F\u4FDD\u6301\u3002\u4FDD\u7559\u30DE\u30FC\u30AB\u30FC\u306E\u89E3\u9664\u306B\u3082\u4F7F\u3046)",
1117
+ en: "Discard ingest cursors so the next run rescans everything (history is kept)"
1118
+ },
875
1119
  {
876
1120
  cmd: "sweep [--dry-run] [--days N]",
877
1121
  ja: "\u5C65\u6B74\u3068\u53D6\u308A\u8FBC\u307F\u4F4D\u7F6E\u3092\u6368\u3066\u3001\u5143JSONL\u304B\u3089\u6982\u7B97\u3092\u518D\u751F\u6210(--dry-run\u306Fpreview)",
878
1122
  en: "Reset and rebuild estimates from source JSONL (--dry-run previews)"
879
1123
  },
1124
+ {
1125
+ cmd: "scan [--dry-run] [--json]",
1126
+ ja: "hook\u975E\u4F9D\u5B58\u306E\u5897\u5206\u53D6\u308A\u8FBC\u307F(\u30C7\u30B9\u30AF\u30C8\u30C3\u30D7\u30A2\u30D7\u30EA\u7B49\u306E\u53D6\u308A\u3053\u307C\u3057\u3092\u56DE\u53CE\u3002--dry-run\u306Fpreview)",
1127
+ en: "Hook-independent incremental ingest (catches desktop app etc.; --dry-run previews)"
1128
+ },
880
1129
  {
881
1130
  cmd: "history <clear|redact> [--days N] [--yes]",
882
1131
  ja: "\u5C65\u6B74\u3092\u524A\u9664(clear=\u30EC\u30B3\u30FC\u30C9\u3054\u3068 / redact=\u30D7\u30ED\u30F3\u30D7\u30C8\u306E\u307F\u3002--days \u3067\u53E4\u3044\u5206\u3060\u3051)",
@@ -908,13 +1157,13 @@ function isCodexPassiveEvent(value) {
908
1157
  async function runCodexPassiveHook(event, text) {
909
1158
  try {
910
1159
  if (event === "Stop") {
911
- const trackMod = await import("./track-WCP7446C.js");
1160
+ const trackMod = await import("./track-P6A5WDJZ.js");
912
1161
  await trackMod.runTrack(text, { codex: true });
913
1162
  } else if (event === "UserPromptSubmit") {
914
- const activity = await import("./subagent-store-US4TJJQR.js");
1163
+ const activity = await import("./subagent-store-USG3WJEB.js");
915
1164
  activity.handleCodexUserPromptSubmitHook(text);
916
1165
  } else {
917
- const activity = await import("./subagent-store-US4TJJQR.js");
1166
+ const activity = await import("./subagent-store-USG3WJEB.js");
918
1167
  activity.handleCodexSubagentHook(text, event === "SubagentStart" ? "start" : "stop");
919
1168
  }
920
1169
  } catch {
@@ -993,18 +1242,18 @@ async function main(argv) {
993
1242
  const text = await readStdin();
994
1243
  const codex = rest.includes("--codex");
995
1244
  try {
996
- const trackMod = await import("./track-WCP7446C.js");
1245
+ const trackMod = await import("./track-P6A5WDJZ.js");
997
1246
  await trackMod.runTrack(text, { codex });
998
1247
  } catch {
999
1248
  }
1000
1249
  return 0;
1001
1250
  }
1002
1251
  case "init": {
1003
- const { runInit } = await import("./setup-I3JNGWZG.js");
1252
+ const { runInit } = await import("./setup-VLMW433R.js");
1004
1253
  return await runInit(rest);
1005
1254
  }
1006
1255
  case "uninstall": {
1007
- const { runUninstall } = await import("./setup-I3JNGWZG.js");
1256
+ const { runUninstall } = await import("./setup-VLMW433R.js");
1008
1257
  return await runUninstall(rest);
1009
1258
  }
1010
1259
  case "doctor":
@@ -1012,27 +1261,35 @@ async function main(argv) {
1012
1261
  case "report":
1013
1262
  return await runReport(rest);
1014
1263
  case "dashboard": {
1015
- const { runDashboard } = await import("./dashboard-VEKQ4TI3.js");
1264
+ const { runDashboard } = await import("./dashboard-64XHDUVT.js");
1016
1265
  return await runDashboard(rest);
1017
1266
  }
1018
1267
  case "sweep": {
1019
- const { runSweep } = await import("./sweep-65D57PXU.js");
1268
+ const { runSweep } = await import("./sweep-DVOV4XIZ.js");
1020
1269
  return await runSweep(rest);
1021
1270
  }
1271
+ case "scan": {
1272
+ const { runScan } = await import("./scan-UB6ORRZK.js");
1273
+ return await runScan(rest);
1274
+ }
1275
+ case "reset-cursors": {
1276
+ const { runResetCursors } = await import("./reset-cursors-5CCM6MQL.js");
1277
+ return await runResetCursors(rest);
1278
+ }
1022
1279
  case "history": {
1023
- const { runHistory } = await import("./history-DL4SG3PG.js");
1280
+ const { runHistory } = await import("./history-W34EJOVV.js");
1024
1281
  return await runHistory(rest);
1025
1282
  }
1026
1283
  case "budget": {
1027
- const { runBudget } = await import("./budget-V7CCMJHF.js");
1284
+ const { runBudget } = await import("./budget-O4VAP6RB.js");
1028
1285
  return runBudget(rest);
1029
1286
  }
1030
1287
  case "mute": {
1031
- const { runMute } = await import("./mute-UIR5P5N5.js");
1288
+ const { runMute } = await import("./mute-23BWQXS3.js");
1032
1289
  return runMute(rest);
1033
1290
  }
1034
1291
  case "unmute": {
1035
- const { runUnmute } = await import("./mute-UIR5P5N5.js");
1292
+ const { runUnmute } = await import("./mute-23BWQXS3.js");
1036
1293
  return runUnmute();
1037
1294
  }
1038
1295
  case "--version":
@@ -3,11 +3,13 @@ import {
3
3
  browserOpenPlan,
4
4
  runDashboard,
5
5
  writeDashboardHtml
6
- } from "./chunk-6UZXXO5J.js";
6
+ } from "./chunk-O2E4VTDR.js";
7
+ import "./chunk-3M5UQK76.js";
8
+ import "./chunk-PXMXEFM7.js";
9
+ import "./chunk-6IVSZ3S4.js";
7
10
  import "./chunk-DGXUSPS4.js";
8
- import "./chunk-J5QAYTFE.js";
9
- import "./chunk-27SJELD2.js";
10
- import "./chunk-OOAC5ULQ.js";
11
+ import "./chunk-XG73C6CC.js";
12
+ import "./chunk-WONMZ466.js";
11
13
  export {
12
14
  browserOpenPlan,
13
15
  runDashboard,
@@ -1,11 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- invalidateCanonicalDashboards,
3
+ invalidateCanonicalDashboards
4
+ } from "./chunk-3M5UQK76.js";
5
+ import {
4
6
  waitForDataLock
5
- } from "./chunk-27SJELD2.js";
7
+ } from "./chunk-PXMXEFM7.js";
6
8
  import {
7
9
  paths
8
- } from "./chunk-OOAC5ULQ.js";
10
+ } from "./chunk-WONMZ466.js";
9
11
 
10
12
  // src/history.ts
11
13
  import { existsSync, readFileSync, writeFileSync, renameSync, rmSync } from "fs";
@@ -3,8 +3,8 @@ import {
3
3
  fmtMuteUntil,
4
4
  runMute,
5
5
  runUnmute
6
- } from "./chunk-T6Z2ZAN4.js";
7
- import "./chunk-OOAC5ULQ.js";
6
+ } from "./chunk-ML5H43NH.js";
7
+ import "./chunk-WONMZ466.js";
8
8
  export {
9
9
  fmtMuteUntil,
10
10
  runMute,
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ waitForDataLock
4
+ } from "./chunk-PXMXEFM7.js";
5
+ import {
6
+ paths,
7
+ pendingAppendPath
8
+ } from "./chunk-WONMZ466.js";
9
+
10
+ // src/reset-cursors.ts
11
+ import { rmSync } from "fs";
12
+ async function runResetCursors(argv) {
13
+ if (argv.length > 0) {
14
+ console.error(`\u4E0D\u660E\u306Aoption\u307E\u305F\u306F\u4F59\u5206\u306A\u5F15\u6570\u3067\u3059: ${argv[0]}
15
+ \u4F7F\u3044\u65B9 / Usage: ccc-notifier reset-cursors`);
16
+ return 1;
17
+ }
18
+ const lock = await waitForDataLock();
19
+ if (lock === null) {
20
+ console.error("reset-cursors \u306E data lock \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");
21
+ return 1;
22
+ }
23
+ try {
24
+ rmSync(paths().cursorsFile, { force: true });
25
+ rmSync(pendingAppendPath(), { force: true });
26
+ } catch (err) {
27
+ console.error(
28
+ `\u53D6\u308A\u8FBC\u307F\u4F4D\u7F6E\u306E\u7834\u68C4\u306B\u5931\u6557\u3057\u307E\u3057\u305F / failed to reset cursors: ${err instanceof Error ? err.message : String(err)}`
29
+ );
30
+ return 1;
31
+ } finally {
32
+ lock.release();
33
+ }
34
+ console.log("\u53D6\u308A\u8FBC\u307F\u4F4D\u7F6E(cursors.json)\u3068\u4FDD\u7559\u30DE\u30FC\u30AB\u30FC\u3092\u7834\u68C4\u3057\u307E\u3057\u305F\u3002");
35
+ console.log("\u5C65\u6B74(history.jsonl)\u30FB\u8A2D\u5B9A\u30FB\u901A\u77E5\u5148\u306F\u305D\u306E\u307E\u307E\u3067\u3059\u3002\u4E8C\u91CD\u8A08\u4E0A\u306F\u5C65\u6B74\u5074\u306E\u6307\u7D0B\u3067\u9632\u304E\u307E\u3059\u3002");
36
+ console.log("\u6B21\u56DE\u306E\u53D6\u308A\u8FBC\u307F\u306F\u5168 transcript / rollout \u3092\u8AAD\u307F\u76F4\u3059\u305F\u3081\u3001\u4E00\u5EA6\u3060\u3051\u6642\u9593\u304C\u304B\u304B\u308A\u307E\u3059\u3002");
37
+ return 0;
38
+ }
39
+ export {
40
+ runResetCursors
41
+ };
@@ -0,0 +1,87 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ notifyIngestSummary,
4
+ runIngest
5
+ } from "./chunk-V4IS4FXH.js";
6
+ import "./chunk-JLDI4D5E.js";
7
+ import "./chunk-6BXD6HU5.js";
8
+ import "./chunk-OCKZZVOG.js";
9
+ import "./chunk-Q45AAPJY.js";
10
+ import "./chunk-O2E4VTDR.js";
11
+ import "./chunk-3M5UQK76.js";
12
+ import "./chunk-PXMXEFM7.js";
13
+ import "./chunk-6IVSZ3S4.js";
14
+ import "./chunk-DGXUSPS4.js";
15
+ import {
16
+ formatJPY,
17
+ formatUSD
18
+ } from "./chunk-XG73C6CC.js";
19
+ import {
20
+ readConfig
21
+ } from "./chunk-WONMZ466.js";
22
+
23
+ // src/scan.ts
24
+ function parseScanFlags(argv) {
25
+ const flags = { dryRun: false, json: false };
26
+ for (const a of argv) {
27
+ if (a === "--dry-run") flags.dryRun = true;
28
+ else if (a === "--json") flags.json = true;
29
+ else return { error: `\u4E0D\u660E\u306Aoption\u307E\u305F\u306F\u4F59\u5206\u306A\u5F15\u6570\u3067\u3059: ${a}` };
30
+ }
31
+ return { flags };
32
+ }
33
+ async function runScan(argv) {
34
+ const parsed = parseScanFlags(argv);
35
+ if ("error" in parsed) {
36
+ console.error(`${parsed.error}
37
+ \u4F7F\u3044\u65B9 / Usage: ccc-notifier scan [--dry-run] [--json]`);
38
+ return 1;
39
+ }
40
+ const { dryRun, json } = parsed.flags;
41
+ const result = await runIngest({ dryRun, offlinePricing: false });
42
+ if (!dryRun && !result.lockAcquired) {
43
+ console.error("scan \u306E data lock \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");
44
+ return 1;
45
+ }
46
+ if (!dryRun && result.records.length > 0) {
47
+ const cfg = readConfig();
48
+ await notifyIngestSummary(result, cfg);
49
+ }
50
+ if (json) {
51
+ console.log(
52
+ JSON.stringify(
53
+ {
54
+ dryRun: result.dryRun,
55
+ records: result.records.length,
56
+ scannedFiles: result.scannedFiles,
57
+ skippedByMtime: result.skippedByMtime,
58
+ failures: result.failures,
59
+ totalUSD: result.totalUSD,
60
+ totalJPY: result.totalJPY,
61
+ bySurface: result.bySurface
62
+ },
63
+ null,
64
+ 2
65
+ )
66
+ );
67
+ return result.failures > 0 ? 1 : 0;
68
+ }
69
+ if (dryRun) console.log("(dry-run: \u66F8\u304D\u8FBC\u307F\u306F\u884C\u3063\u3066\u3044\u307E\u305B\u3093)");
70
+ console.log(
71
+ `\u8D70\u67FB: \u5BFE\u8C61 ${result.scannedFiles}\u30D5\u30A1\u30A4\u30EB(mtime\u5909\u5316\u306A\u3057\u3067\u30B9\u30AD\u30C3\u30D7 ${result.skippedByMtime}\u4EF6) / \u5931\u6557 ${result.failures}\u4EF6`
72
+ );
73
+ if (result.records.length === 0) {
74
+ console.log("\u65B0\u898F\u53D6\u308A\u8FBC\u307F\u306F\u3042\u308A\u307E\u305B\u3093\u3067\u3057\u305F");
75
+ } else {
76
+ console.log(`\u53D6\u308A\u8FBC\u307F: ${result.records.length} \u30BF\u30FC\u30F3\u3001\u5408\u8A08 ${formatUSD(result.totalUSD)}(${formatJPY(result.totalJPY)})`);
77
+ const bySurface = Object.entries(result.bySurface).sort((a, b) => (b[1]?.usd ?? 0) - (a[1]?.usd ?? 0));
78
+ for (const [surface, v] of bySurface) {
79
+ if (!v) continue;
80
+ console.log(` ${surface}: ${v.turns}\u30BF\u30FC\u30F3 ${formatUSD(v.usd)}`);
81
+ }
82
+ }
83
+ return result.failures > 0 ? 1 : 0;
84
+ }
85
+ export {
86
+ runScan
87
+ };
@@ -4,13 +4,12 @@ import {
4
4
  matchesMarker,
5
5
  runInit,
6
6
  runUninstall
7
- } from "./chunk-OXXDZZPJ.js";
8
- import "./chunk-NV5UOHJA.js";
9
- import "./chunk-HTYUYKFW.js";
10
- import "./chunk-6HTETN26.js";
7
+ } from "./chunk-X7U63BZG.js";
8
+ import "./chunk-JLDI4D5E.js";
9
+ import "./chunk-Q45AAPJY.js";
11
10
  import "./chunk-DGXUSPS4.js";
12
- import "./chunk-J5QAYTFE.js";
13
- import "./chunk-OOAC5ULQ.js";
11
+ import "./chunk-XG73C6CC.js";
12
+ import "./chunk-WONMZ466.js";
14
13
  export {
15
14
  maskWebhookUrl,
16
15
  matchesMarker,
@@ -13,7 +13,7 @@ import {
13
13
  recordCodexSubagentPayload,
14
14
  reduceCodexSubagentActivity,
15
15
  validateCodexSubagentPayload
16
- } from "./chunk-OOAC5ULQ.js";
16
+ } from "./chunk-WONMZ466.js";
17
17
  export {
18
18
  acquireCodexActivityLock,
19
19
  closeCodexRootContext,