ccc-notifier 0.7.0 → 0.9.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 CHANGED
@@ -19,7 +19,7 @@ in 1.2k(cache 40%) / out 480 · 📁 my-app · 今日: $1.85
19
19
 
20
20
  - Claude Codeの応答完了ごとに、USD・JPYの概算をOS通知またはSlackへ送ります
21
21
  - プロンプトと概算をローカルへ保存し、あとから検索・集計できます
22
- - 日・週・月の推移やモデル別・プロジェクト別の内訳をHTMLダッシュボードで確認できます
22
+ - 日・週・月の推移やモデル別・プロジェクト別の内訳をHTMLダッシュボードで確認できます。金額とあわせて**トークン使用量**も表示し、推移チャートは金額/トークンの軸切替つきです
23
23
  - 月予算を設定し、今月の使用率を確認できます
24
24
  - 通知を一時停止しても、履歴の記録は続けられます
25
25
  - Codex CLIも任意で追加し、Claude Codeと同じダッシュボードで確認できます
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  formatJPY,
4
4
  formatUSD
5
- } from "./chunk-XG73C6CC.js";
5
+ } from "./chunk-OHTPBABH.js";
6
6
  import {
7
7
  currentMonthTotals,
8
8
  paths,
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-DGXUSPS4.js";
5
5
  import {
6
6
  formatSummary
7
- } from "./chunk-XG73C6CC.js";
7
+ } from "./chunk-OHTPBABH.js";
8
8
 
9
9
  // src/notify/os.ts
10
10
  import { spawn } from "child_process";
@@ -18,7 +18,7 @@ import {
18
18
  formatTokens,
19
19
  formatUSD,
20
20
  modelDisplayName
21
- } from "./chunk-XG73C6CC.js";
21
+ } from "./chunk-OHTPBABH.js";
22
22
  import {
23
23
  paths,
24
24
  readConfig,
@@ -217,7 +217,20 @@ function turnBySlot(rec, map) {
217
217
  return bs;
218
218
  }
219
219
  function emptyTotals() {
220
- return { usd: 0, jpy: 0, turns: 0 };
220
+ return { usd: 0, jpy: 0, turns: 0, tin: 0, tout: 0, tcache: 0 };
221
+ }
222
+ function turnTokenTotals(rec) {
223
+ let tin = 0;
224
+ let tout = 0;
225
+ let tcache = 0;
226
+ for (const b of [rec.tokens, rec.sidechainTokens, rec.subagents?.tokens]) {
227
+ if (!b) continue;
228
+ const cache = b.cacheRead + b.cacheWrite5m + b.cacheWrite1h;
229
+ tin += b.input + cache;
230
+ tcache += cache;
231
+ tout += b.output;
232
+ }
233
+ return { tin, tout, tcache };
221
234
  }
222
235
  function buildTurnEmbed(rec, map) {
223
236
  const raw = String(rec.prompt ?? "");
@@ -225,7 +238,7 @@ function buildTurnEmbed(rec, map) {
225
238
  const primary = rec.models[0] ?? "unknown";
226
239
  const extra = rec.models.length > 1 ? ` +${rec.models.length - 1}` : "";
227
240
  const saMark = rec.subagents ? " +SA" : "";
228
- const codexActivityMark = rec.source === "codex" && rec.subagentActivity ? " +SA(\u672A\u96C6\u8A08)" : "";
241
+ const codexActivityMark = rec.source === "codex" && rec.subagentActivity ? " +SA(\u691C\u51FA)" : "";
229
242
  let sa = null;
230
243
  if (rec.subagents) {
231
244
  const saModels = Object.keys(rec.subagents.costByModel).map((m) => modelDisplayName(m));
@@ -235,6 +248,12 @@ function buildTurnEmbed(rec, map) {
235
248
  models: saModels.join(", "),
236
249
  apiCalls: rec.subagents.apiCalls
237
250
  };
251
+ const b = rec.subagents.tokens;
252
+ const cache = b.cacheRead + b.cacheWrite5m + b.cacheWrite1h;
253
+ const total = b.input + b.output + cache;
254
+ if (total > 0) {
255
+ sa.tok = `\u8A08 ${formatTokens(total)} tokens(cache ${Math.round(cache / total * 100)}%)`;
256
+ }
238
257
  }
239
258
  const ms = Date.parse(rec.ts);
240
259
  const out = {
@@ -252,7 +271,11 @@ function buildTurnEmbed(rec, map) {
252
271
  bs: turnBySlot(rec, map),
253
272
  pr: text,
254
273
  tr: truncated,
255
- sa
274
+ sa,
275
+ tk: (() => {
276
+ const t = turnTokenTotals(rec);
277
+ return [t.tin, t.tout, t.tcache];
278
+ })()
256
279
  };
257
280
  if (rec.source === "codex") out.sc = "codex";
258
281
  if (rec.source === "codex" && rec.subagentActivity) {
@@ -275,28 +298,29 @@ function computeKpis(turns) {
275
298
  const week = emptyTotals();
276
299
  const month = emptyTotals();
277
300
  const all = emptyTotals();
301
+ const add = (t, usd, jpy, tk) => {
302
+ t.usd += usd;
303
+ t.jpy += jpy;
304
+ t.turns += 1;
305
+ t.tin += tk.tin;
306
+ t.tout += tk.tout;
307
+ t.tcache += tk.tcache;
308
+ };
278
309
  for (const rec of turns) {
279
310
  const usd = turnTotalUSD(rec);
280
311
  const jpy = turnTotalJPY(rec);
281
- all.usd += usd;
282
- all.jpy += jpy;
283
- all.turns += 1;
312
+ const tk = turnTokenTotals(rec);
313
+ add(all, usd, jpy, tk);
284
314
  const dt = new Date(rec.ts);
285
315
  if (Number.isNaN(dt.getTime())) continue;
286
316
  if (dt.getFullYear() === y && dt.getMonth() === mo && dt.getDate() === d) {
287
- today.usd += usd;
288
- today.jpy += jpy;
289
- today.turns += 1;
317
+ add(today, usd, jpy, tk);
290
318
  }
291
319
  if (dt.getTime() >= weekCutoff) {
292
- week.usd += usd;
293
- week.jpy += jpy;
294
- week.turns += 1;
320
+ add(week, usd, jpy, tk);
295
321
  }
296
322
  if (dt.getFullYear() === y && dt.getMonth() === mo) {
297
- month.usd += usd;
298
- month.jpy += jpy;
299
- month.turns += 1;
323
+ add(month, usd, jpy, tk);
300
324
  }
301
325
  }
302
326
  return { today, week, month, all };
@@ -354,9 +378,11 @@ section.card > .note{color:var(--muted); font-size:12px; margin:0 0 14px;}
354
378
  .sel-label b{color:var(--ink); font-weight:640;}
355
379
 
356
380
  .legend{display:flex; flex-wrap:wrap; gap:8px 18px; margin:2px 0 14px;}
381
+ .legend[hidden]{display:none;}
357
382
  .legend-item{display:inline-flex; align-items:center; gap:7px; font-size:13px; color:var(--ink2);}
358
383
  .swatch{display:inline-block; width:11px; height:11px; border-radius:3px; flex:0 0 auto;}
359
384
  .k1{background:var(--s1);} .k2{background:var(--s2);} .k3{background:var(--s3);} .k4{background:var(--s4);}
385
+ .ktin{background:var(--s1);} .ktcache{background:var(--sother);} .ktout{background:var(--s3);}
360
386
  .k5{background:var(--s5);} .k6{background:var(--s6);} .k7{background:var(--s7);} .k8{background:var(--s8);} .kother{background:var(--sother);}
361
387
 
362
388
  .budget-bar{height:12px; background:var(--plane); border:1px solid var(--border); border-radius:7px; overflow:hidden; margin:12px 0;}
@@ -374,6 +400,7 @@ section.card > .note{color:var(--muted); font-size:12px; margin:0 0 14px;}
374
400
  .chart-scroll{overflow-x:auto;}
375
401
  .seg.s1{fill:var(--s1);} .seg.s2{fill:var(--s2);} .seg.s3{fill:var(--s3);} .seg.s4{fill:var(--s4);}
376
402
  .seg.s5{fill:var(--s5);} .seg.s6{fill:var(--s6);} .seg.s7{fill:var(--s7);} .seg.s8{fill:var(--s8);} .seg.sother{fill:var(--sother);}
403
+ .seg.tin{fill:var(--s1);} .seg.tcache{fill:var(--sother);} .seg.tout{fill:var(--s3);}
377
404
  .seg.dim{opacity:0.28;}
378
405
  .grid-line{stroke:var(--grid); stroke-width:1;}
379
406
  .axis-line{stroke:var(--axis); stroke-width:1;}
@@ -463,6 +490,13 @@ var APP_JS = `<script>
463
490
  if(v >= 1) return '\xA5' + Math.round(v).toLocaleString('en-US');
464
491
  return '\xA5' + (Math.round(v * 10) / 10);
465
492
  }
493
+ function formatTok(n){
494
+ var v = n || 0;
495
+ if(v < 1000) return String(v);
496
+ if(v < 1e6) return (v / 1000).toFixed(1) + 'k';
497
+ if(v < 1e9) return (v / 1e6).toFixed(1) + 'M';
498
+ return (v / 1e9).toFixed(1) + 'B';
499
+ }
466
500
 
467
501
  // ---- \u65E5\u4ED8/\u30D0\u30B1\u30C3\u30C8(\u30ED\u30FC\u30AB\u30EBTZ)----
468
502
  function pad(n){ return (n < 10 ? '0' : '') + n; }
@@ -500,6 +534,9 @@ var APP_JS = `<script>
500
534
  // \u65E2\u5B9A 'all'\u3002sessionStorage \u306B\u4FDD\u5B58\u3057\u81EA\u52D5\u30EA\u30ED\u30FC\u30C9(meta refresh)\u3092\u8DE8\u3044\u3067\u7DAD\u6301\u3059\u308B(cccn-gran \u3068\u540C\u3058\u4ED5\u7D44\u307F)\u3002
501
535
  var HAS_CODEX = document.querySelectorAll('[data-src]').length > 0;
502
536
  var SRC = ssGet('cccn-src'); if(['all','claude','codex'].indexOf(SRC) < 0) SRC = 'all';
537
+ // \u30C1\u30E3\u30FC\u30C8\u306E\u7E26\u8EF8\u30E1\u30C8\u30EA\u30AF\u30B9(\u91D1\u984D / \u30C8\u30FC\u30AF\u30F3)\u3002\u4ED6\u306E\u8868\u793A(KPI\u30FB\u5185\u8A33\u30FB\u5C65\u6B74)\u306F\u5E38\u306B\u91D1\u984D\u304C\u4E3B\u5F79\u306E\u307E\u307E\u3002
538
+ var MET = ssGet('cccn-metric'); if(['usd','tok'].indexOf(MET) < 0) MET = 'usd';
539
+ function setMet(m){ MET = m; ssSet('cccn-metric', m); }
503
540
  // \u8FF0\u8A9E: claude \u2192 sc \u306A\u3057 / codex \u2192 sc==='codex' / all \u2192 \u5168\u4EF6\u3002Codex \u7121\u3057(\u30C1\u30C3\u30D7\u7121\u3057)\u306A\u3089\u5E38\u306B\u5168\u4EF6\u3002
504
541
  function activeTurns(){
505
542
  if(!HAS_CODEX || SRC === 'all') return turns;
@@ -536,10 +573,13 @@ var APP_JS = `<script>
536
573
  var k = bucketKey(tn.t, gran);
537
574
  if(k === null) continue;
538
575
  var b = map[k];
539
- if(!b){ b = map[k] = { key:k, total:0, turns:0, bs:{} }; keys.push(k); }
576
+ if(!b){ b = map[k] = { key:k, total:0, turns:0, bs:{}, tin:0, tout:0, tcache:0, tokTotal:0 }; keys.push(k); }
540
577
  b.turns++;
541
578
  var bs = tn.bs || {};
542
579
  for(var s in bs){ if(bs.hasOwnProperty(s)){ b.bs[s] = (b.bs[s]||0) + bs[s]; b.total += bs[s]; } }
580
+ var tk = tn.tk || [0,0,0];
581
+ b.tin += tk[0]; b.tout += tk[1]; b.tcache += tk[2];
582
+ b.tokTotal += tk[0] + tk[1];
543
583
  }
544
584
  keys.sort();
545
585
  return keys.map(function(k){ return map[k]; });
@@ -608,8 +648,9 @@ var APP_JS = `<script>
608
648
  var plotW = band * n;
609
649
  var W = ml + plotW + mr;
610
650
 
651
+ var tokMode = MET === 'tok';
611
652
  var maxTotal = 0;
612
- buckets.forEach(function(b){ if(b.total > maxTotal) maxTotal = b.total; });
653
+ buckets.forEach(function(b){ var v = tokMode ? b.tokTotal : b.total; if(v > maxTotal) maxTotal = v; });
613
654
  var niceMax = niceCeil(maxTotal);
614
655
  var yscale = plotH / niceMax;
615
656
  // \u9078\u629E\u4E2D\u30D0\u30B1\u30C3\u30C8\u304C\u73FE\u5728\u306E\u7C92\u5EA6\u306B\u5B58\u5728\u3059\u308B\u3068\u304D\u3060\u3051\u4ED6\u3092\u6DE1\u8272\u5316\u3059\u308B(\u30C7\u30FC\u30BF\u7121\u3057\u306E\u65E5\u3092\u9078\u3076\u3068
@@ -618,7 +659,7 @@ var APP_JS = `<script>
618
659
  for(var sp=0; sp<buckets.length; sp++){ if(buckets[sp].key === SEL){ selPresent = true; break; } }
619
660
 
620
661
  var svg = svgEl('svg', { 'class':'chart', viewBox:'0 0 ' + W + ' ' + H, role:'img',
621
- 'aria-label':'\u30B3\u30B9\u30C8\u7A4D\u307F\u4E0A\u3052\u68D2\u30B0\u30E9\u30D5', preserveAspectRatio:'xMidYMid meet' });
662
+ 'aria-label':(tokMode ? '\u30C8\u30FC\u30AF\u30F3\u7A4D\u307F\u4E0A\u3052\u68D2\u30B0\u30E9\u30D5' : '\u30B3\u30B9\u30C8\u7A4D\u307F\u4E0A\u3052\u68D2\u30B0\u30E9\u30D5'), preserveAspectRatio:'xMidYMid meet' });
622
663
  svg.setAttribute('width', String(W));
623
664
  svg.setAttribute('height', String(H));
624
665
 
@@ -628,7 +669,7 @@ var APP_JS = `<script>
628
669
  var yy = f2(baseY - val * yscale);
629
670
  svg.appendChild(svgEl('line', { 'class':'grid-line', x1:ml, y1:yy, x2:W-mr, y2:yy }));
630
671
  var lbl = svgEl('text', { 'class':'tick-label', x:ml-8, y:yy+4, 'text-anchor':'end' });
631
- lbl.textContent = formatUSD(val);
672
+ lbl.textContent = tokMode ? formatTok(val) : formatUSD(val);
632
673
  svg.appendChild(lbl);
633
674
  }
634
675
 
@@ -641,9 +682,16 @@ var APP_JS = `<script>
641
682
  var x = cx - barW/2;
642
683
  var dim = (SEL !== null && selPresent && b.key !== SEL);
643
684
  var cumV = 0;
644
- for(var j=0;j<slotOrder.length;j++){
645
- var slot = slotOrder[j];
646
- var v = b.bs[slot] || 0;
685
+ var segList;
686
+ if(tokMode){
687
+ segList = [ ['tcache', b.tcache], ['tin', b.tin - b.tcache], ['tout', b.tout] ];
688
+ } else {
689
+ segList = [];
690
+ for(var j0=0;j0<slotOrder.length;j0++){ segList.push(['s'+slotOrder[j0], b.bs[slotOrder[j0]] || 0]); }
691
+ }
692
+ for(var j=0;j<segList.length;j++){
693
+ var segCls = segList[j][0];
694
+ var v = segList[j][1];
647
695
  if(v <= 0) continue;
648
696
  var vTop = cumV + v;
649
697
  var yTop = baseY - vTop * yscale;
@@ -652,10 +700,11 @@ var APP_JS = `<script>
652
700
  cumV = vTop;
653
701
  var h = yBot - yTop;
654
702
  if(h <= 0.4) continue;
655
- var rect = svgEl('rect', { 'class':'seg s'+slot+(dim?' dim':''), x:f2(x), y:f2(yTop), width:f2(barW), height:f2(h) });
703
+ var rect = svgEl('rect', { 'class':'seg '+segCls+(dim?' dim':''), x:f2(x), y:f2(yTop), width:f2(barW), height:f2(h) });
656
704
  svg.appendChild(rect);
657
705
  }
658
- if(i % xStep === 0 || i === n-1){
706
+ // \u6700\u7D42\u30E9\u30D9\u30EB\u306F\u5E38\u306B\u51FA\u3059\u3002\u9593\u5F15\u304D\u30E9\u30D9\u30EB\u306F\u6700\u7D42\u30E9\u30D9\u30EB\u3068\u96A3\u63A5\u3057\u3066\u91CD\u306A\u3089\u306A\u3044\u4F4D\u7F6E\u3060\u3051\u306B\u51FA\u3059\u3002
707
+ if(i === n-1 || (i % xStep === 0 && (n-1) - i >= xStep)){
659
708
  var xl = svgEl('text', { 'class':'tick-label', x:f2(cx), y:baseY+16, 'text-anchor':'middle' });
660
709
  xl.textContent = bucketLabel(b.key, GRAN);
661
710
  svg.appendChild(xl);
@@ -692,6 +741,15 @@ var APP_JS = `<script>
692
741
  lastRenderedGran = GRAN;
693
742
  }
694
743
 
744
+ function tipRow(keyCls, nameText, valText){
745
+ var row = document.createElement('div'); row.className = 'tip-row';
746
+ var key = document.createElement('span'); key.className = 'tip-key ' + keyCls;
747
+ var name = document.createElement('span'); name.className = 'tip-name'; name.textContent = nameText;
748
+ var val = document.createElement('span'); val.className = 'tip-val'; val.textContent = valText;
749
+ row.appendChild(key); row.appendChild(name); row.appendChild(val);
750
+ return row;
751
+ }
752
+
695
753
  // hover\u5185\u8A33\u306F\u3001\u9078\u629E\u4E2D\u306E\u65E5/\u9031/\u6708\u306B\u304A\u3051\u308B\u91D1\u984D\u306E\u5927\u304D\u3044\u9806\u306B\u8AAD\u3080\u3002\u7A4D\u307F\u4E0A\u3052\u9806\u30FB\u8272\u306F
696
754
  // slotOrder\u306E\u307E\u307E\u56FA\u5B9A\u3057\u3001\u540C\u984D\u3082slotOrder\u3067\u6C7A\u5B9A\u7684\u306B\u4E26\u3079\u308B\u3002
697
755
  function tooltipSlotOrder(bucket){
@@ -708,19 +766,24 @@ var APP_JS = `<script>
708
766
  if(!tip) return;
709
767
  clearNode(tip);
710
768
  var h = document.createElement('div'); h.className = 'tip-title';
711
- h.textContent = periodText(bucket.key, GRAN) + ' \u5408\u8A08 ' + formatUSD(bucket.total) + ' \xB7 ' + bucket.turns + ' \u30BF\u30FC\u30F3';
712
- tip.appendChild(h);
713
- var tipOrder = tooltipSlotOrder(bucket);
714
- for(var j=0;j<tipOrder.length;j++){
715
- var slot = tipOrder[j];
716
- var v = bucket.bs[slot] || 0;
717
- if(v <= 0) continue;
718
- var row = document.createElement('div'); row.className = 'tip-row';
719
- var key = document.createElement('span'); key.className = 'tip-key k'+slot;
720
- var name = document.createElement('span'); name.className = 'tip-name'; name.textContent = slotName[slot] || slot;
721
- var val = document.createElement('span'); val.className = 'tip-val'; val.textContent = formatUSD(v);
722
- row.appendChild(key); row.appendChild(name); row.appendChild(val);
723
- tip.appendChild(row);
769
+ if(MET === 'tok'){
770
+ h.textContent = periodText(bucket.key, GRAN) + ' \u5408\u8A08 ' + formatTok(bucket.tokTotal) + ' tokens \xB7 ' + bucket.turns + ' \u30BF\u30FC\u30F3';
771
+ tip.appendChild(h);
772
+ var segs = [ ['ktout', '\u51FA\u529B', bucket.tout], ['ktin', '\u5165\u529B(\u30AD\u30E3\u30C3\u30B7\u30E5\u4EE5\u5916)', bucket.tin - bucket.tcache], ['ktcache', '\u30AD\u30E3\u30C3\u30B7\u30E5(read+write)', bucket.tcache] ];
773
+ for(var j2=0;j2<segs.length;j2++){
774
+ if(segs[j2][2] <= 0) continue;
775
+ tip.appendChild(tipRow(segs[j2][0], segs[j2][1], formatTok(segs[j2][2])));
776
+ }
777
+ } else {
778
+ h.textContent = periodText(bucket.key, GRAN) + ' \u5408\u8A08 ' + formatUSD(bucket.total) + ' \xB7 ' + bucket.turns + ' \u30BF\u30FC\u30F3';
779
+ tip.appendChild(h);
780
+ var tipOrder = tooltipSlotOrder(bucket);
781
+ for(var j=0;j<tipOrder.length;j++){
782
+ var slot = tipOrder[j];
783
+ var v = bucket.bs[slot] || 0;
784
+ if(v <= 0) continue;
785
+ tip.appendChild(tipRow('k'+slot, slotName[slot] || slot, formatUSD(v)));
786
+ }
724
787
  }
725
788
  tip.hidden = false;
726
789
  positionTip(evt);
@@ -740,23 +803,29 @@ var APP_JS = `<script>
740
803
  function renderByModel(sub){
741
804
  if(!modelEl) return;
742
805
  clearNode(modelEl);
743
- var usd = {}, jpy = {}, tcount = {}, total = 0;
806
+ var usd = {}, jpy = {}, tcount = {}, ttok = {}, total = 0;
744
807
  sub.forEach(function(tn){
745
808
  var bs = tn.bs || {}, fx = tn.fx || 0;
746
- for(var s in bs){ if(bs.hasOwnProperty(s) && bs[s] > 0){
809
+ var tk = tn.tk || [0,0,0];
810
+ // \u5358\u4FA1\u4E0D\u660E\u30E2\u30C7\u30EB\u30FB\u6599\u91D10\u306Echild\u306F bs \u306E\u5024\u304C 0 \u306E\u307E\u307E\u767B\u5834\u3059\u308B\u3002\u91D1\u984D 0 \u3067\u3082\u53C2\u52A0\u3068\u3057\u3066\u96C6\u8A08\u3057\u3001
811
+ // \u30C8\u30FC\u30AF\u30F3\u3060\u3051\u306E\u5229\u7528\u304C\u30E2\u30C7\u30EB\u5225\u8868\u304B\u3089\u6D88\u3048\u306A\u3044\u3088\u3046\u306B\u3059\u308B(\u8868\u793A\u6761\u4EF6\u5074\u3067 usd \u3082 tok \u3082 0 \u306E\u884C\u3092\u843D\u3068\u3059)\u3002
812
+ for(var s in bs){ if(bs.hasOwnProperty(s)){
747
813
  usd[s] = (usd[s]||0) + bs[s]; jpy[s] = (jpy[s]||0) + bs[s]*fx; total += bs[s];
748
814
  tcount[s] = (tcount[s]||0) + 1;
815
+ // \u30BF\u30FC\u30F3\u5217\u3068\u540C\u3058\u53C2\u52A0\u30AB\u30A6\u30F3\u30C8\u65B9\u5F0F: \u305D\u306E\u30E2\u30C7\u30EB\u304C\u767B\u5834\u3057\u305F\u30BF\u30FC\u30F3\u306E\u5168\u91CF\u3092\u5404\u30E2\u30C7\u30EB\u3078\u8A08\u4E0A(\u91CD\u8907\u3042\u308A)\u3002
816
+ // \u30EC\u30B3\u30FC\u30C9\u306B\u30E2\u30C7\u30EB\u5225\u30C8\u30FC\u30AF\u30F3\u306F\u7121\u3044\u305F\u3081\u3001\u30BF\u30FC\u30F3\u5185\u306E\u30E2\u30C7\u30EB\u9593\u5206\u5272\u306F\u3057\u306A\u3044\u3002
817
+ ttok[s] = (ttok[s]||0) + tk[0] + tk[1];
749
818
  } }
750
819
  });
751
820
  var table = document.createElement('table'); table.className = 'data-table';
752
821
  var thead = document.createElement('thead');
753
- thead.innerHTML = '<tr><th>\u30E2\u30C7\u30EB / Model</th><th class="c-num">\u30BF\u30FC\u30F3</th><th class="c-num">$</th><th class="c-num">\xA5</th><th class="c-num">\u69CB\u6210\u6BD4</th></tr>';
822
+ thead.innerHTML = '<tr><th>\u30E2\u30C7\u30EB / Model</th><th class="c-num">\u30BF\u30FC\u30F3</th><th class="c-num">$</th><th class="c-num">\xA5</th><th class="c-num">tokens</th><th class="c-num">\u69CB\u6210\u6BD4</th></tr>';
754
823
  table.appendChild(thead);
755
824
  var tbody = document.createElement('tbody');
756
825
  var any = false;
757
826
  for(var j=0;j<slotOrder.length;j++){
758
827
  var slot = slotOrder[j];
759
- if(!(usd[slot] > 0)) continue;
828
+ if(!(usd[slot] > 0 || (ttok[slot]||0) > 0)) continue;
760
829
  any = true;
761
830
  var share = total > 0 ? (usd[slot]/total*100) : 0;
762
831
  var tr = document.createElement('tr');
@@ -766,8 +835,9 @@ var APP_JS = `<script>
766
835
  var c2 = document.createElement('td'); c2.className = 'c-num'; c2.textContent = String(tcount[slot]||0);
767
836
  var c3 = document.createElement('td'); c3.className = 'c-num'; c3.textContent = formatUSD(usd[slot]);
768
837
  var c4 = document.createElement('td'); c4.className = 'c-num'; c4.textContent = formatJPY(jpy[slot]);
838
+ var c4t = document.createElement('td'); c4t.className = 'c-num'; c4t.textContent = formatTok(ttok[slot]||0);
769
839
  var c5 = document.createElement('td'); c5.className = 'c-num'; c5.textContent = share.toFixed(1) + '%';
770
- tr.appendChild(c1); tr.appendChild(c2); tr.appendChild(c3); tr.appendChild(c4); tr.appendChild(c5);
840
+ tr.appendChild(c1); tr.appendChild(c2); tr.appendChild(c3); tr.appendChild(c4); tr.appendChild(c4t); tr.appendChild(c5);
771
841
  tbody.appendChild(tr);
772
842
  }
773
843
  table.appendChild(tbody);
@@ -785,15 +855,16 @@ var APP_JS = `<script>
785
855
  var bs = tn.bs || {}, fx = tn.fx || 0, total = 0;
786
856
  for(var s in bs){ if(bs.hasOwnProperty(s)) total += bs[s]; }
787
857
  var name = tn.p || '(unknown)';
788
- var a = agg[name] || (agg[name] = { usd:0, jpy:0, turns:0 });
789
- a.usd += total; a.jpy += total*fx; a.turns++;
858
+ var tk = tn.tk || [0,0,0];
859
+ var a = agg[name] || (agg[name] = { usd:0, jpy:0, turns:0, tok:0 });
860
+ a.usd += total; a.jpy += total*fx; a.turns++; a.tok += tk[0] + tk[1];
790
861
  });
791
- var rows = Object.keys(agg).map(function(name){ var a = agg[name]; return { name:name, usd:a.usd, jpy:a.jpy, turns:a.turns }; });
862
+ var rows = Object.keys(agg).map(function(name){ var a = agg[name]; return { name:name, usd:a.usd, jpy:a.jpy, turns:a.turns, tok:a.tok }; });
792
863
  rows.sort(function(a,b){ return b.usd - a.usd || a.name.localeCompare(b.name); });
793
864
  if(rows.length === 0){ var p = document.createElement('p'); p.className='empty-hint'; p.textContent='\u3053\u306E\u671F\u9593\u306E\u30C7\u30FC\u30BF\u306F\u3042\u308A\u307E\u305B\u3093'; projEl.appendChild(p); return; }
794
865
  var table = document.createElement('table'); table.className = 'data-table';
795
866
  var thead = document.createElement('thead');
796
- thead.innerHTML = '<tr><th>\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8 / Project</th><th class="c-num">\u30BF\u30FC\u30F3</th><th class="c-num">$</th><th class="c-num">\xA5</th></tr>';
867
+ thead.innerHTML = '<tr><th>\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8 / Project</th><th class="c-num">\u30BF\u30FC\u30F3</th><th class="c-num">$</th><th class="c-num">\xA5</th><th class="c-num">tokens</th></tr>';
797
868
  table.appendChild(thead);
798
869
  var tbody = document.createElement('tbody');
799
870
  rows.forEach(function(r){
@@ -802,7 +873,8 @@ var APP_JS = `<script>
802
873
  var c2 = document.createElement('td'); c2.className='c-num'; c2.textContent = String(r.turns);
803
874
  var c3 = document.createElement('td'); c3.className='c-num'; c3.textContent = formatUSD(r.usd);
804
875
  var c4 = document.createElement('td'); c4.className='c-num'; c4.textContent = formatJPY(r.jpy);
805
- tr.appendChild(c1); tr.appendChild(c2); tr.appendChild(c3); tr.appendChild(c4);
876
+ var c5 = document.createElement('td'); c5.className='c-num'; c5.textContent = formatTok(r.tok);
877
+ tr.appendChild(c1); tr.appendChild(c2); tr.appendChild(c3); tr.appendChild(c4); tr.appendChild(c5);
806
878
  tbody.appendChild(tr);
807
879
  });
808
880
  table.appendChild(tbody);
@@ -888,14 +960,21 @@ var APP_JS = `<script>
888
960
  var kpiCards = document.querySelectorAll('.kpi .stat');
889
961
  var heroValueEl = document.querySelector('.hero .hero-value');
890
962
  var heroMetaEls = document.querySelectorAll('.hero .hero-meta');
891
- var heroSaEl = heroMetaEls.length > 1 ? heroMetaEls[1] : null; // SA \u884C(SA \u3042\u308A\u306E\u3068\u304D\u3060\u3051\u30B5\u30FC\u30D0\u304C\u63CF\u753B)
963
+ var heroTokEl = document.querySelector('.hero .hero-tok');
964
+ var heroSaEl = document.querySelector('.hero .hero-sa'); // SA \u884C(SA \u3042\u308A\u306E\u3068\u304D\u3060\u3051\u30B5\u30FC\u30D0\u304C\u63CF\u753B)
892
965
  var heroSaPrefix = heroSaEl ? ((heroSaEl.textContent || '').split('$')[0]) : '';
966
+ function tokLine(tot){
967
+ var pct = tot.ti > 0 ? Math.round(tot.tc / tot.ti * 100) : 0;
968
+ return 'in ' + formatTok(tot.ti) + '(cache ' + pct + '%)/ out ' + formatTok(tot.to) + ' tokens';
969
+ }
893
970
  function setKpiCard(card, tot){
894
971
  if(!card) return;
895
972
  var v = card.querySelector('.stat-value');
896
973
  var m = card.querySelector('.stat-meta');
974
+ var tk = card.querySelector('.stat-tok');
897
975
  if(v) v.textContent = formatUSD(tot.usd);
898
976
  if(m) m.textContent = formatJPY(tot.jpy) + ' \xB7 ' + tot.turns + ' \u30BF\u30FC\u30F3';
977
+ if(tk) tk.textContent = 'in ' + formatTok(tot.ti) + ' / out ' + formatTok(tot.to);
899
978
  }
900
979
  function renderKpis(){
901
980
  if(!HAS_CODEX) return;
@@ -903,25 +982,29 @@ var APP_JS = `<script>
903
982
  var now = new Date();
904
983
  var y = now.getFullYear(), mo = now.getMonth(), d = now.getDate();
905
984
  var weekCutoff = Date.now() - 7*86400000;
906
- var today = {usd:0,jpy:0,turns:0}, week = {usd:0,jpy:0,turns:0}, month = {usd:0,jpy:0,turns:0}, all = {usd:0,jpy:0,turns:0};
985
+ function zero(){ return {usd:0,jpy:0,turns:0,ti:0,to:0,tc:0}; }
986
+ var today = zero(), week = zero(), month = zero(), all = zero();
907
987
  var saUsd = 0, saJpy = 0;
988
+ function acc(t, u, j, tk){ t.usd+=u; t.jpy+=j; t.turns++; t.ti+=tk[0]; t.to+=tk[1]; t.tc+=tk[2]; }
908
989
  for(var i=0;i<src.length;i++){
909
990
  var tn = src[i], bs = tn.bs || {}, fx = tn.fx || 0, u = 0;
910
991
  for(var s in bs){ if(bs.hasOwnProperty(s)) u += bs[s]; }
911
992
  var j = u * fx;
993
+ var tk = tn.tk || [0,0,0];
912
994
  if(tn.sa){ var sau = u - (tn.um || 0); saUsd += sau; saJpy += sau * fx; } // SA \u5206 = \u7DCF\u984D \u2212 \u30E1\u30A4\u30F3
913
- all.usd += u; all.jpy += j; all.turns++;
995
+ acc(all, u, j, tk);
914
996
  var dt = new Date(tn.t);
915
997
  if(isNaN(dt.getTime())) continue;
916
- if(dt.getFullYear()===y && dt.getMonth()===mo && dt.getDate()===d){ today.usd+=u; today.jpy+=j; today.turns++; }
917
- if(tn.t >= weekCutoff){ week.usd+=u; week.jpy+=j; week.turns++; }
918
- if(dt.getFullYear()===y && dt.getMonth()===mo){ month.usd+=u; month.jpy+=j; month.turns++; }
998
+ if(dt.getFullYear()===y && dt.getMonth()===mo && dt.getDate()===d){ acc(today, u, j, tk); }
999
+ if(tn.t >= weekCutoff){ acc(week, u, j, tk); }
1000
+ if(dt.getFullYear()===y && dt.getMonth()===mo){ acc(month, u, j, tk); }
919
1001
  }
920
1002
  if(kpiCards.length >= 4){
921
1003
  setKpiCard(kpiCards[0], today); setKpiCard(kpiCards[1], week); setKpiCard(kpiCards[2], month); setKpiCard(kpiCards[3], all);
922
1004
  }
923
1005
  if(heroValueEl) heroValueEl.textContent = formatUSD(all.usd);
924
1006
  if(heroMetaEls.length > 0) heroMetaEls[0].textContent = formatJPY(all.jpy) + ' \xB7 ' + all.turns + ' \u30BF\u30FC\u30F3';
1007
+ if(heroTokEl) heroTokEl.textContent = tokLine(all);
925
1008
  if(heroSaEl){
926
1009
  heroSaEl.hidden = !(saUsd > 0);
927
1010
  if(saUsd > 0) heroSaEl.textContent = heroSaPrefix + formatUSD(saUsd) + '(' + formatJPY(saJpy) + ')';
@@ -974,12 +1057,12 @@ var APP_JS = `<script>
974
1057
  dtd.appendChild(meta);
975
1058
  if(t.sa){
976
1059
  var saLine = document.createElement('div'); saLine.className = 'detail-meta';
977
- saLine.textContent = '\u30B5\u30D6\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8: ' + (t.sa.usd||'') + '(' + (t.sa.jpy||'') + ')\xB7 ' + (t.sa.models||'') + ' \xB7 API\u30B3\u30FC\u30EB ' + (t.sa.apiCalls||0);
1060
+ saLine.textContent = '\u30B5\u30D6\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8: ' + (t.sa.usd||'') + '(' + (t.sa.jpy||'') + ')\xB7 ' + (t.sa.models||'') + ' \xB7 API\u30B3\u30FC\u30EB ' + (t.sa.apiCalls||0) + (t.sa.tok ? ' \xB7 ' + t.sa.tok : '');
978
1061
  dtd.appendChild(saLine);
979
1062
  }
980
1063
  if(t.ca){
981
1064
  var caLine = document.createElement('div'); caLine.className = 'detail-meta';
982
- caLine.textContent = 'Codex\u30B5\u30D6\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8: \u5229\u7528\u3042\u308A\u30FB\u6599\u91D1\u672A\u96C6\u8A08 \xB7 \u958B\u59CB ' +
1065
+ caLine.textContent = 'Codex\u30B5\u30D6\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8: \u5229\u7528\u3042\u308A(\u6599\u91D1\u306F\u3053\u306E\u30BF\u30FC\u30F3\u306B\u542B\u3081\u306A\u3044)\xB7 \u958B\u59CB ' +
983
1066
  (t.ca.started||0) + ' \xB7 \u7D42\u4E86 ' + (t.ca.stopped||0) + ' \xB7 \u7A2E\u5225 ' + ((t.ca.agentTypes||[]).join(', ')||'unknown');
984
1067
  dtd.appendChild(caLine);
985
1068
  }
@@ -1029,6 +1112,15 @@ var APP_JS = `<script>
1029
1112
  if(allBtn) allBtn.classList.toggle('active', SEL === null);
1030
1113
  var sbtns = document.querySelectorAll('[data-src]');
1031
1114
  for(var k=0;k<sbtns.length;k++){ sbtns[k].classList.toggle('active', sbtns[k].getAttribute('data-src') === SRC); }
1115
+ var mbtns = document.querySelectorAll('[data-metric]');
1116
+ for(var mi=0;mi<mbtns.length;mi++){ mbtns[mi].classList.toggle('active', mbtns[mi].getAttribute('data-metric') === MET); }
1117
+ var tokMode = MET === 'tok';
1118
+ var modelLegend = document.getElementById('cccn-model-legend');
1119
+ var tokLegend = document.getElementById('cccn-tok-legend');
1120
+ if(modelLegend) modelLegend.hidden = tokMode;
1121
+ if(tokLegend) tokLegend.hidden = !tokMode;
1122
+ var chartTitle = document.getElementById('cccn-chart-title');
1123
+ if(chartTitle) chartTitle.textContent = tokMode ? '\u30C8\u30FC\u30AF\u30F3\u63A8\u79FB / Tokens over time' : '\u30B3\u30B9\u30C8\u63A8\u79FB / Cost over time';
1032
1124
  }
1033
1125
 
1034
1126
  // ---- \u5168\u4F53\u30EC\u30F3\u30C0\u30EA\u30F3\u30B0 ----
@@ -1051,6 +1143,10 @@ var APP_JS = `<script>
1051
1143
  }
1052
1144
  var allBtn = document.getElementById('cccn-all');
1053
1145
  if(allBtn) allBtn.addEventListener('click', function(){ setSel(null); render(); });
1146
+ var metBtns = document.querySelectorAll('[data-metric]');
1147
+ for(var mbi=0;mbi<metBtns.length;mbi++){
1148
+ (function(btn){ btn.addEventListener('click', function(){ setMet(btn.getAttribute('data-metric')); render(); }); })(metBtns[mbi]);
1149
+ }
1054
1150
  var srcBtns = document.querySelectorAll('[data-src]');
1055
1151
  for(var sbi=0;sbi<srcBtns.length;sbi++){
1056
1152
  (function(btn){ btn.addEventListener('click', function(){ setSrc(btn.getAttribute('data-src')); render(); }); })(srcBtns[sbi]);
@@ -1074,16 +1170,18 @@ var APP_JS = `<script>
1074
1170
  })();
1075
1171
  </script>`;
1076
1172
  function statCard(label, sub, totals) {
1077
- return `<div class="stat"><div class="stat-label">${esc(label)}<span class="stat-sub">${esc(sub)}</span></div><div class="stat-value">${esc(formatUSD(totals.usd))}</div><div class="stat-meta">${esc(formatJPY(totals.jpy))} \xB7 ${totals.turns} \u30BF\u30FC\u30F3</div></div>`;
1173
+ return `<div class="stat"><div class="stat-label">${esc(label)}<span class="stat-sub">${esc(sub)}</span></div><div class="stat-value">${esc(formatUSD(totals.usd))}</div><div class="stat-meta">${esc(formatJPY(totals.jpy))} \xB7 ${totals.turns} \u30BF\u30FC\u30F3</div><div class="stat-meta stat-tok">in ${esc(formatTokens(totals.tin))} / out ${esc(formatTokens(totals.tout))}</div></div>`;
1078
1174
  }
1079
1175
  function computeSurfaceBreakdown(turns) {
1080
1176
  const map = /* @__PURE__ */ new Map();
1081
1177
  for (const rec of turns) {
1082
1178
  const surface = effectiveSurface(rec);
1083
- const agg = map.get(surface) ?? { surface, turns: 0, usd: 0, jpy: 0 };
1179
+ const agg = map.get(surface) ?? { surface, turns: 0, usd: 0, jpy: 0, tokens: 0 };
1180
+ const tk = turnTokenTotals(rec);
1084
1181
  agg.turns += 1;
1085
1182
  agg.usd += turnTotalUSD(rec);
1086
1183
  agg.jpy += turnTotalJPY(rec);
1184
+ agg.tokens += tk.tin + tk.tout;
1087
1185
  map.set(surface, agg);
1088
1186
  }
1089
1187
  return [...map.values()].sort((a, b) => b.usd - a.usd);
@@ -1093,9 +1191,9 @@ function surfaceSection(turns) {
1093
1191
  if (rows.length === 0) return "";
1094
1192
  if (rows.length === 1 && rows[0].surface === "cli") return "";
1095
1193
  const trs = rows.map(
1096
- (r) => `<tr><td>${esc(surfaceLabel(r.surface))}</td><td class="c-num">${r.turns}</td><td class="c-num">${esc(formatUSD(r.usd))}</td><td class="c-num">${esc(formatJPY(r.jpy))}</td></tr>`
1194
+ (r) => `<tr><td>${esc(surfaceLabel(r.surface))}</td><td class="c-num">${r.turns}</td><td class="c-num">${esc(formatUSD(r.usd))}</td><td class="c-num">${esc(formatJPY(r.jpy))}</td><td class="c-num">${esc(formatTokens(r.tokens))}</td></tr>`
1097
1195
  ).join("");
1098
- return `<section class="card"><h2>\u30B5\u30FC\u30D5\u30A7\u30B9\u5225\u5185\u8A33 / By surface</h2><p class="note">CLI \u4EE5\u5916(\u30C7\u30B9\u30AF\u30C8\u30C3\u30D7\u30A2\u30D7\u30EA\u7B49)\u304B\u3089\u306E\u5229\u7528\u5206\u306E\u5185\u8A33(\u57CB\u3081\u8FBC\u307F\u5BFE\u8C61\u671F\u9593\u30FBSA \u8FBC\u307F\u7DCF\u984D)\u3002</p><div class="table-wrap"><table class="turns"><thead><tr><th>\u30B5\u30FC\u30D5\u30A7\u30B9</th><th class="c-num">\u30BF\u30FC\u30F3</th><th class="c-num">$</th><th class="c-num">\xA5</th></tr></thead><tbody>${trs}</tbody></table></div></section>`;
1196
+ return `<section class="card"><h2>\u30B5\u30FC\u30D5\u30A7\u30B9\u5225\u5185\u8A33 / By surface</h2><p class="note">CLI \u4EE5\u5916(\u30C7\u30B9\u30AF\u30C8\u30C3\u30D7\u30A2\u30D7\u30EA\u7B49)\u304B\u3089\u306E\u5229\u7528\u5206\u306E\u5185\u8A33(\u57CB\u3081\u8FBC\u307F\u5BFE\u8C61\u671F\u9593\u30FBSA \u8FBC\u307F\u7DCF\u984D\u30FB\u7DCF\u30C8\u30FC\u30AF\u30F3)\u3002\u30E2\u30C7\u30EB\u4E0D\u660E\u3067\u91D1\u984D $0 \u306E\u5229\u7528\u5206\u3082 tokens \u5217\u3067\u898F\u6A21\u304C\u5206\u304B\u308A\u307E\u3059\u3002</p><div class="table-wrap"><table class="turns"><thead><tr><th>\u30B5\u30FC\u30D5\u30A7\u30B9</th><th class="c-num">\u30BF\u30FC\u30F3</th><th class="c-num">$</th><th class="c-num">\xA5</th><th class="c-num">tokens</th></tr></thead><tbody>${trs}</tbody></table></div></section>`;
1099
1197
  }
1100
1198
  function renderLegend(slots) {
1101
1199
  if (slots.length < 2) return "";
@@ -1180,11 +1278,11 @@ function renderDashboard(turns, opts, fullHistory) {
1180
1278
  const empty = `<div class="empty">` + (outsideEmbeddedPeriod ? `<h2>\u5BFE\u8C61\u671F\u9593\u306B\u5C65\u6B74\u304C\u3042\u308A\u307E\u305B\u3093</h2><p>\u5C65\u6B74\u306F\u3042\u308A\u307E\u3059\u304C\u3001\u76F4\u8FD1 ${opts.days} \u65E5\u9593\u306B\u306F\u8A18\u9332\u304C\u3042\u308A\u307E\u305B\u3093\u3002</p><p class="empty-hint">No history in the embedded period.</p>` : `<h2>\u307E\u3060\u5C65\u6B74\u304C\u3042\u308A\u307E\u305B\u3093</h2><p>Claude Code \u3067\u30D7\u30ED\u30F3\u30D7\u30C8\u3092\u5B9F\u884C\u3059\u308B\u3068\u3001\u3053\u3053\u306B\u30B3\u30B9\u30C8\u5C65\u6B74\u304C\u8868\u793A\u3055\u308C\u307E\u3059\u3002</p><p class="empty-hint">No history yet \u2014 run a prompt in Claude Code and it will appear here.</p>`) + `</div>`;
1181
1279
  return head + header2 + budgetSection + empty + foot;
1182
1280
  }
1183
- const header = `<div class="head"><div><h1>ccc-notifier \u30C0\u30C3\u30B7\u30E5\u30DC\u30FC\u30C9</h1>` + variantNav + `<div class="sub">${esc(period)}<span class="muted"> \xB7 ${esc(rangeText)}</span></div><div class="sub muted">\u751F\u6210 ${esc(generatedAt)} / generated</div></div><div class="hero"><div class="hero-label">${esc(totalLabel)} / ${esc(totalLabelEn)}</div><div class="hero-value">${esc(formatUSD(kpi.all.usd))}</div><div class="hero-meta">${esc(formatJPY(kpi.all.jpy))} \xB7 ${kpi.all.turns} \u30BF\u30FC\u30F3</div>` + (anySub ? `<div class="hero-meta">\u3046\u3061\u30B5\u30D6\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8 ${esc(formatUSD(subUsd))}(${esc(formatJPY(subJpy))})</div>` : "") + `</div></div>`;
1281
+ const header = `<div class="head"><div><h1>ccc-notifier \u30C0\u30C3\u30B7\u30E5\u30DC\u30FC\u30C9</h1>` + variantNav + `<div class="sub">${esc(period)}<span class="muted"> \xB7 ${esc(rangeText)}</span></div><div class="sub muted">\u751F\u6210 ${esc(generatedAt)} / generated</div></div><div class="hero"><div class="hero-label">${esc(totalLabel)} / ${esc(totalLabelEn)}</div><div class="hero-value">${esc(formatUSD(kpi.all.usd))}</div><div class="hero-meta">${esc(formatJPY(kpi.all.jpy))} \xB7 ${kpi.all.turns} \u30BF\u30FC\u30F3</div><div class="hero-meta hero-tok">in ${esc(formatTokens(kpi.all.tin))}(cache ${kpi.all.tin > 0 ? Math.round(kpi.all.tcache / kpi.all.tin * 100) : 0}%)/ out ${esc(formatTokens(kpi.all.tout))} tokens</div>` + (anySub ? `<div class="hero-meta hero-sa">\u3046\u3061\u30B5\u30D6\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8 ${esc(formatUSD(subUsd))}(${esc(formatJPY(subJpy))})</div>` : "") + `</div></div>`;
1184
1282
  const kpiSection = `<div class="kpi">` + statCard("\u4ECA\u65E5", "Today", kpi.today) + statCard("\u4ECA\u9031", "\u76F4\u8FD17\u65E5", kpi.week) + statCard("\u4ECA\u6708", "\u66A6\u6708", kpi.month) + statCard(totalLabel, totalSub, kpi.all) + `</div>`;
1185
1283
  const srcToggle = anyCodex ? `<div class="seg-toggle" id="cccn-src-toggle"><button type="button" data-src="all">\u5168\u4F53</button><button type="button" data-src="claude">Claude</button><button type="button" data-src="codex">Codex</button></div>` : "";
1186
- const chartSection = `<section class="card"><h2>\u30B3\u30B9\u30C8\u63A8\u79FB / Cost over time</h2><p class="note">\u7C92\u5EA6\u3092 \u65E5 / \u9031 / \u6708 \u3067\u5207\u308A\u66FF\u3048\u3089\u308C\u307E\u3059(\u6A2A\u30B9\u30AF\u30ED\u30FC\u30EB\u3067\u904E\u53BB\u307E\u3067)\u3002\u68D2\u3092\u30AF\u30EA\u30C3\u30AF\u3059\u308B\u3068\u305D\u306E\u671F\u9593\u304C\u9078\u629E\u3055\u308C\u3001\u4E0B\u306E\u30E2\u30C7\u30EB\u5225\u30FB\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u5225\u30FB\u5C65\u6B74\u304C\u9023\u52D5\u3057\u307E\u3059\u3002\u300C${esc(totalLabel)}\u300D\u3067${esc(totalSub)}\u306B\u623B\u308A\u307E\u3059\u3002\u30E2\u30C7\u30EB\u5225\u306B\u8272\u5206\u3051\u3002</p><div class="toolbar-row"><div class="seg-toggle"><button type="button" data-gran="day">\u65E5</button><button type="button" data-gran="week">\u9031</button><button type="button" data-gran="month">\u6708</button></div><button type="button" id="cccn-all" class="btn">${esc(totalLabel)}</button>` + srcToggle + `<span id="cccn-sel-label" class="sel-label"></span></div>` + renderLegend(map.slots) + `<div class="chart-scroll"><div id="cccn-chart"></div></div></section>`;
1187
- const breakdownSection = `<div class="grid2"><section class="card"><h2>\u30E2\u30C7\u30EB\u5225\u5185\u8A33 / By model</h2><p class="note">\u9078\u629E\u4E2D\u306E\u671F\u9593(\u65E2\u5B9A\u306F${esc(totalLabel)})\u306E\u30B3\u30B9\u30C8\u964D\u9806\u3002\u8907\u6570\u30E2\u30C7\u30EB\u306E\u30BF\u30FC\u30F3\u306F\u5404\u30E2\u30C7\u30EB\u306B1\u305A\u3064\u8A08\u4E0A\u3002</p><div class="table-wrap" id="cccn-bymodel"></div></section><section class="card"><h2>\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u5225 / By project</h2><p class="note">\u9078\u629E\u4E2D\u306E\u671F\u9593\u306E\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8(\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA basename)\u5358\u4F4D\u30FB\u30B3\u30B9\u30C8\u964D\u9806\u3002</p><div class="table-wrap" id="cccn-byproject"></div></section></div>`;
1284
+ const chartSection = `<section class="card"><h2 id="cccn-chart-title">\u30B3\u30B9\u30C8\u63A8\u79FB / Cost over time</h2><p class="note">\u7C92\u5EA6\u3092 \u65E5 / \u9031 / \u6708 \u3067\u5207\u308A\u66FF\u3048\u3089\u308C\u307E\u3059(\u6A2A\u30B9\u30AF\u30ED\u30FC\u30EB\u3067\u904E\u53BB\u307E\u3067)\u3002\u68D2\u3092\u30AF\u30EA\u30C3\u30AF\u3059\u308B\u3068\u305D\u306E\u671F\u9593\u304C\u9078\u629E\u3055\u308C\u3001\u4E0B\u306E\u30E2\u30C7\u30EB\u5225\u30FB\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u5225\u30FB\u5C65\u6B74\u304C\u9023\u52D5\u3057\u307E\u3059\u3002\u300C${esc(totalLabel)}\u300D\u3067${esc(totalSub)}\u306B\u623B\u308A\u307E\u3059\u3002\u7E26\u8EF8\u306F\u91D1\u984D\u3068\u30C8\u30FC\u30AF\u30F3\u3092\u5207\u308A\u66FF\u3048\u3089\u308C\u307E\u3059(\u91D1\u984D\u8EF8\u306F\u30E2\u30C7\u30EB\u5225\u306B\u8272\u5206\u3051\u3001\u30C8\u30FC\u30AF\u30F3\u8EF8\u306F\u30AD\u30E3\u30C3\u30B7\u30E5/\u5165\u529B/\u51FA\u529B\u306E3\u533A\u5206)\u3002</p><div class="toolbar-row"><div class="seg-toggle"><button type="button" data-gran="day">\u65E5</button><button type="button" data-gran="week">\u9031</button><button type="button" data-gran="month">\u6708</button></div><div class="seg-toggle" id="cccn-metric-toggle"><button type="button" data-metric="usd">\u91D1\u984D</button><button type="button" data-metric="tok">\u30C8\u30FC\u30AF\u30F3</button></div><button type="button" id="cccn-all" class="btn">${esc(totalLabel)}</button>` + srcToggle + `<span id="cccn-sel-label" class="sel-label"></span></div><div id="cccn-model-legend">` + renderLegend(map.slots) + `</div><div class="legend" id="cccn-tok-legend" hidden><span class="legend-item"><span class="swatch ktcache"></span>\u30AD\u30E3\u30C3\u30B7\u30E5(read+write)</span><span class="legend-item"><span class="swatch ktin"></span>\u5165\u529B(\u30AD\u30E3\u30C3\u30B7\u30E5\u4EE5\u5916)</span><span class="legend-item"><span class="swatch ktout"></span>\u51FA\u529B</span></div><div class="chart-scroll"><div id="cccn-chart"></div></div></section>`;
1285
+ const breakdownSection = `<div class="grid2"><section class="card"><h2>\u30E2\u30C7\u30EB\u5225\u5185\u8A33 / By model</h2><p class="note">\u9078\u629E\u4E2D\u306E\u671F\u9593(\u65E2\u5B9A\u306F${esc(totalLabel)})\u306E\u30B3\u30B9\u30C8\u964D\u9806\u3002\u8907\u6570\u30E2\u30C7\u30EB\u306E\u30BF\u30FC\u30F3\u306F\u3001\u30BF\u30FC\u30F3\u6570\u30FBtokens \u5217\u3068\u3082\u5404\u30E2\u30C7\u30EB\u306B\u5168\u91CF\u8A08\u4E0A(\u91CD\u8907\u3042\u308A\u3002\u30EC\u30B3\u30FC\u30C9\u306B\u30E2\u30C7\u30EB\u5225\u30C8\u30FC\u30AF\u30F3\u304C\u7121\u3044\u305F\u3081\u30BF\u30FC\u30F3\u5185\u3067\u306F\u5206\u5272\u3057\u306A\u3044)\u3002</p><div class="table-wrap" id="cccn-bymodel"></div></section><section class="card"><h2>\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u5225 / By project</h2><p class="note">\u9078\u629E\u4E2D\u306E\u671F\u9593\u306E\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8(\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA basename)\u5358\u4F4D\u30FB\u30B3\u30B9\u30C8\u964D\u9806\u3002</p><div class="table-wrap" id="cccn-byproject"></div></section></div>`;
1188
1286
  const capNote = "\u4EF6\u6570\u304C\u591A\u3044\u5834\u5408\u306F\u65B0\u3057\u3044\u9806\u306B\u5148\u982D200\u4EF6\u306E\u307F\u63CF\u753B\u3057\u307E\u3059(\u691C\u7D22\u3084\u3001\u4E0A\u306E\u30B0\u30E9\u30D5\u3067\u671F\u9593\u3092\u9078\u3076\u3068\u7D5E\u308A\u8FBC\u3081\u307E\u3059)\u3002";
1189
1287
  const truncNote = anyTruncated ? `<p class="note">\u9078\u629E\u4E2D\u306E\u671F\u9593\u306E\u30BF\u30FC\u30F3\u3092\u65B0\u3057\u3044\u9806\u306B\u8868\u793A\u3057\u307E\u3059\u3002${capNote}\u30D7\u30ED\u30F3\u30D7\u30C8\u306F1\u4EF6\u3042\u305F\u308A\u6700\u5927 ${PROMPT_MAX.toLocaleString("en-US")} \u5B57\u307E\u3067(\u8D85\u904E\u5206\u306F\u300C${esc(PROMPT_TRUNC_MARK)}\u300D)\u3002\u884C\u30AF\u30EA\u30C3\u30AF\u3067\u5168\u6587\u5C55\u958B\u3002</p>` : `<p class="note">\u9078\u629E\u4E2D\u306E\u671F\u9593\u306E\u30BF\u30FC\u30F3\u3092\u65B0\u3057\u3044\u9806\u306B\u8868\u793A\u3057\u307E\u3059\u3002${capNote}\u884C\u30AF\u30EA\u30C3\u30AF\u3067\u30D7\u30ED\u30F3\u30D7\u30C8\u5168\u6587\u3092\u5C55\u958B\u3057\u307E\u3059\u3002</p>`;
1190
1288
  const historySection = `<section class="card"><h2>\u30BF\u30FC\u30F3\u5C65\u6B74 / History</h2>` + truncNote + `<div class="toolbar"><input id="turn-search" class="search" type="search" placeholder="\u30D7\u30ED\u30F3\u30D7\u30C8\u30FB\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u30FB\u30E2\u30C7\u30EB\u3067\u691C\u7D22 / search\u2026" autocomplete="off"><span class="count"><span id="turn-count">0</span></span></div><div class="table-wrap"><table class="turns"><thead><tr><th>\u6642\u523B</th><th>\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8</th><th>\u30E2\u30C7\u30EB</th><th class="c-num">in</th><th class="c-num">out</th><th class="c-num">$</th><th class="c-num">\xA5</th><th>\u30D7\u30ED\u30F3\u30D7\u30C8</th></tr></thead><tbody id="turn-body"></tbody></table></div></section>`;
@@ -356,7 +356,8 @@ function readSessionMetaSeed(buffer) {
356
356
  sessionId: strOrNull(payload.id) ?? strOrNull(payload.session_id),
357
357
  cwd: strOrNull(payload.cwd),
358
358
  originator: strOrNull(payload.originator),
359
- isSubagentRollout: isRecord(source) && Object.hasOwn(source, "subagent")
359
+ isSubagentRollout: isRecord(source) && Object.hasOwn(source, "subagent"),
360
+ isForkedRollout: strOrNull(payload.forked_from_id) !== null
360
361
  };
361
362
  }
362
363
  function newSegmentBuf() {
@@ -395,6 +396,7 @@ async function scanWindow(rolloutPath, cursor, opts = {}) {
395
396
  let sessionMetaCwd = null;
396
397
  let sessionMetaSid = null;
397
398
  let isSubagentRollout = false;
399
+ let isForkedRollout = false;
398
400
  let originator = cursor?.codexOriginator ?? null;
399
401
  if (startOffset > 0) {
400
402
  const seed = readSessionMetaSeed(buffer);
@@ -403,6 +405,7 @@ async function scanWindow(rolloutPath, cursor, opts = {}) {
403
405
  sessionMetaCwd = seed.cwd;
404
406
  if (seed.originator !== null) originator = seed.originator;
405
407
  isSubagentRollout = seed.isSubagentRollout;
408
+ isForkedRollout = seed.isForkedRollout;
406
409
  }
407
410
  }
408
411
  let firstTs = null;
@@ -454,6 +457,7 @@ async function scanWindow(rolloutPath, cursor, opts = {}) {
454
457
  if (c !== null) sessionMetaCwd = c;
455
458
  const source = payload.source;
456
459
  if (isRecord(source) && Object.hasOwn(source, "subagent")) isSubagentRollout = true;
460
+ if (strOrNull(payload.forked_from_id) !== null) isForkedRollout = true;
457
461
  const orig = strOrNull(payload.originator);
458
462
  if (orig !== null) originator = orig;
459
463
  return;
@@ -532,6 +536,7 @@ async function scanWindow(rolloutPath, cursor, opts = {}) {
532
536
  sessionId: rolloutId(),
533
537
  eventKeys: windowEventKeys,
534
538
  isSubagentRollout,
539
+ isForkedRollout,
535
540
  originator,
536
541
  firstTs,
537
542
  lastTs,
@@ -569,6 +574,7 @@ async function aggregateCodexTurn(rolloutPath, cursor, opts = {}) {
569
574
  newCursor: windowCursor(scan),
570
575
  originator: scan.originator,
571
576
  isSubagentRollout: scan.isSubagentRollout,
577
+ isForkedRollout: scan.isForkedRollout,
572
578
  codexEventKeys: scan.eventKeys
573
579
  };
574
580
  }
@@ -640,6 +646,7 @@ function draftsFromScan(scan) {
640
646
  const lastIndex = picked.length - 1;
641
647
  return picked.map((s, i) => ({
642
648
  isSubagentRollout: scan.isSubagentRollout,
649
+ isForkedRollout: scan.isForkedRollout,
643
650
  agg: {
644
651
  sessionId: scan.sessionId,
645
652
  // session_meta はファイル先頭にしか無いので全ドラフト共通
@@ -652,6 +659,7 @@ function draftsFromScan(scan) {
652
659
  originator: scan.originator,
653
660
  // session_meta はファイル先頭にしか無いので全ドラフト共通
654
661
  isSubagentRollout: scan.isSubagentRollout,
662
+ isForkedRollout: scan.isForkedRollout,
655
663
  codexEventKeys: s.eventKeys,
656
664
  firstTs: s.firstTs,
657
665
  lastTs: s.endTs,
@@ -15,7 +15,8 @@ function formatJPY(n) {
15
15
  function formatTokens(n) {
16
16
  if (n < 1e3) return `${n}`;
17
17
  if (n < 1e6) return `${(n / 1e3).toFixed(1)}k`;
18
- return `${(n / 1e6).toFixed(1)}M`;
18
+ if (n < 1e9) return `${(n / 1e6).toFixed(1)}M`;
19
+ return `${(n / 1e9).toFixed(1)}B`;
19
20
  }
20
21
  function groupThousands(n) {
21
22
  const sign = n < 0 ? "-" : "";
@@ -84,7 +85,9 @@ function formatIngestSummary(input, cfg) {
84
85
  const label = cfg.costLabel === "api_equivalent" ? "API\u63DB\u7B97 " : "";
85
86
  const title = `\u{1F4B0} ${label}${formatUSD(input.totalUSD)}(${formatJPY(input.totalJPY)})| hook\u975E\u4F9D\u5B58\u306E\u53D6\u308A\u8FBC\u307F ${input.recordCount}\u4EF6`;
86
87
  const bySurfaceLine = Object.entries(input.bySurface).sort((a, b) => b[1].usd - a[1].usd).map(([surface, v]) => `${surface}: ${v.turns}\u4EF6 ${formatUSD(v.usd)}`).join(" / ");
87
- const body = `${bySurfaceLine}
88
+ const cachePct = input.totalTokens > 0 ? Math.round(input.cacheTokens / input.totalTokens * 100) : 0;
89
+ const tokensPart = input.totalTokens > 0 ? ` \xB7 \u8A08 ${formatTokens(input.totalTokens)} tokens(cache ${cachePct}%)` : "";
90
+ const body = `${bySurfaceLine}${tokensPart}
88
91
  hook \u975E\u4F9D\u5B58\u306E\u5897\u5206\u53D6\u308A\u8FBC\u307F(scan)\u306B\u3088\u308B\u65B0\u898F\u5206\u3067\u3059`;
89
92
  return { title, body };
90
93
  }
@@ -12,7 +12,7 @@ import {
12
12
  setCountedCalls,
13
13
  splitIntoCodexTurnDrafts,
14
14
  surfaceForClaudePath
15
- } from "./chunk-OCKZZVOG.js";
15
+ } from "./chunk-GAOGTALW.js";
16
16
  import {
17
17
  codexHome,
18
18
  computeCost,
@@ -20,7 +20,7 @@ import {
20
20
  } from "./chunk-Q45AAPJY.js";
21
21
  import {
22
22
  writeDashboardHtml
23
- } from "./chunk-O2E4VTDR.js";
23
+ } from "./chunk-6GWPAYJQ.js";
24
24
  import {
25
25
  invalidateCanonicalDashboards,
26
26
  makeFullDashboardState,
@@ -33,7 +33,7 @@ import {
33
33
  formatJPY,
34
34
  formatUSD,
35
35
  modelDisplayName
36
- } from "./chunk-XG73C6CC.js";
36
+ } from "./chunk-OHTPBABH.js";
37
37
  import {
38
38
  appendTurn,
39
39
  loadCursor,
@@ -590,6 +590,41 @@ function codexDraftToRecord(draft, ts, table, fx) {
590
590
  setCountedCalls(rec, draft.agg.codexEventKeys ?? []);
591
591
  return rec;
592
592
  }
593
+ function codexChildDraftToRecord(draft, ts, table, fx, ingest) {
594
+ const main = draft.agg.main;
595
+ const breakdown = computeCost(main, {}, table);
596
+ const rec = {
597
+ schemaVersion: 1,
598
+ ts,
599
+ sessionId: draft.agg.sessionId,
600
+ project: draft.agg.cwd ?? "",
601
+ gitBranch: null,
602
+ models: collectModels(main, {}),
603
+ tokens: emptyBuckets2(),
604
+ sidechainTokens: null,
605
+ apiCalls: 0,
606
+ costUSD: 0,
607
+ costByModel: {},
608
+ costJPY: 0,
609
+ fxRate: fx.rate,
610
+ fxSource: fx.source,
611
+ prompt: "",
612
+ ingest,
613
+ source: "codex",
614
+ surface: normalizeCodexOriginator(draft.agg.originator),
615
+ subagents: {
616
+ costUSD: breakdown.usd,
617
+ costByModel: { ...breakdown.byModel },
618
+ tokens: sumBuckets(main),
619
+ apiCalls: draft.agg.apiCalls,
620
+ agentFiles: 1
621
+ }
622
+ };
623
+ if (typeof draft.agg.originator === "string") rec.originator = draft.agg.originator;
624
+ if (breakdown.unknownModels.length > 0) rec.unknownModels = breakdown.unknownModels;
625
+ setCountedCalls(rec, draft.agg.codexEventKeys ?? []);
626
+ return rec;
627
+ }
593
628
  async function processCodexRolloutLocked(rolloutPath, table, fx, daysCutoff, dryRun, summary, opts = {}) {
594
629
  if (opts.strictRead) {
595
630
  const file = await fsp.open(rolloutPath, "r");
@@ -598,21 +633,27 @@ async function processCodexRolloutLocked(rolloutPath, table, fx, daysCutoff, dry
598
633
  const cursor = opts.ignoreCursors ? null : sanitizeCursor(loadCursor(rolloutPath));
599
634
  const drafts = await splitIntoCodexTurnDrafts(rolloutPath, cursor);
600
635
  if (drafts === null || drafts.length === 0) return;
601
- if (drafts[0].isSubagentRollout) return;
636
+ const isChild = drafts[0].isSubagentRollout;
637
+ const isForkedChild = isChild && drafts[0].isForkedRollout;
602
638
  const records = [];
603
- for (const draft of drafts) {
604
- const ts = draft.endTs ?? (/* @__PURE__ */ new Date()).toISOString();
605
- if (daysCutoff !== null) {
606
- const tsMs = Date.parse(ts);
607
- if (!Number.isFinite(tsMs) || tsMs < daysCutoff) continue;
639
+ if (!isForkedChild) {
640
+ for (const draft of drafts) {
641
+ const ts = draft.endTs ?? (/* @__PURE__ */ new Date()).toISOString();
642
+ if (daysCutoff !== null) {
643
+ const tsMs = Date.parse(ts);
644
+ if (!Number.isFinite(tsMs) || tsMs < daysCutoff) continue;
645
+ }
646
+ records.push(
647
+ isChild ? codexChildDraftToRecord(draft, ts, table, fx, "sweep") : codexDraftToRecord(draft, ts, table, fx)
648
+ );
608
649
  }
609
- records.push(codexDraftToRecord(draft, ts, table, fx));
610
650
  }
611
651
  for (const rec of records) {
612
652
  summary.newRecords += 1;
613
653
  summary.totalUSD += rec.costUSD;
614
654
  summary.codexRecords += 1;
615
655
  summary.codexUSD += rec.costUSD;
656
+ summary.subagentsUSD += rec.subagents?.costUSD ?? 0;
616
657
  if (rec.costByModel) {
617
658
  for (const [m, c] of Object.entries(rec.costByModel)) {
618
659
  summary.byModel[m] = (summary.byModel[m] ?? 0) + c;
@@ -953,5 +994,6 @@ export {
953
994
  collectSubagentUsage,
954
995
  splitIntoTurnDrafts,
955
996
  attachSubagentGroups,
997
+ codexChildDraftToRecord,
956
998
  runSweep
957
999
  };
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  notifyOS,
4
4
  notifySlack
5
- } from "./chunk-JLDI4D5E.js";
5
+ } from "./chunk-2XOKH54N.js";
6
6
  import {
7
7
  codexHome,
8
8
  detectCodex,
@@ -2,13 +2,14 @@
2
2
  import {
3
3
  notifyOS,
4
4
  notifySlack
5
- } from "./chunk-JLDI4D5E.js";
5
+ } from "./chunk-2XOKH54N.js";
6
6
  import {
7
7
  attachSubagentGroups,
8
+ codexChildDraftToRecord,
8
9
  collectSubagentUsage,
9
10
  normalizeCodexOriginator,
10
11
  splitIntoTurnDrafts
11
- } from "./chunk-6BXD6HU5.js";
12
+ } from "./chunk-SKX5EMST.js";
12
13
  import {
13
14
  anyOf,
14
15
  callFingerprints,
@@ -20,7 +21,7 @@ import {
20
21
  scanCodexTurns,
21
22
  setCountedCalls,
22
23
  surfaceForClaudePath
23
- } from "./chunk-OCKZZVOG.js";
24
+ } from "./chunk-GAOGTALW.js";
24
25
  import {
25
26
  codexHome,
26
27
  computeCost,
@@ -31,7 +32,7 @@ import {
31
32
  } from "./chunk-PXMXEFM7.js";
32
33
  import {
33
34
  formatIngestSummary
34
- } from "./chunk-XG73C6CC.js";
35
+ } from "./chunk-OHTPBABH.js";
35
36
  import {
36
37
  appendTurn,
37
38
  floorKey,
@@ -240,17 +241,33 @@ function emptyResult(dryRun, fx, lockAcquired) {
240
241
  failures: 0,
241
242
  totalUSD: 0,
242
243
  totalJPY: 0,
244
+ totalTokens: 0,
245
+ cacheTokens: 0,
243
246
  fxRate: fx.rate,
244
247
  fxSource: fx.source,
245
248
  bySurface: {}
246
249
  };
247
250
  }
251
+ function recordTokenTotals(rec) {
252
+ let total = 0;
253
+ let cache = 0;
254
+ for (const b of [rec.tokens, rec.sidechainTokens, rec.subagents?.tokens]) {
255
+ if (!b) continue;
256
+ const cached = b.cacheRead + b.cacheWrite5m + b.cacheWrite1h;
257
+ total += b.input + b.output + cached;
258
+ cache += cached;
259
+ }
260
+ return { total, cache };
261
+ }
248
262
  function addRecord(result, rec) {
249
263
  const usd = rec.costUSD + (rec.subagents?.costUSD ?? 0);
250
264
  const jpy = rec.costJPY + (rec.subagents?.costUSD ?? 0) * rec.fxRate;
265
+ const tokens = recordTokenTotals(rec);
251
266
  result.records.push(rec);
252
267
  result.totalUSD += usd;
253
268
  result.totalJPY += jpy;
269
+ result.totalTokens += tokens.total;
270
+ result.cacheTokens += tokens.cache;
254
271
  const surface = rec.surface ?? "cli";
255
272
  const cur = result.bySurface[surface] ?? { turns: 0, usd: 0 };
256
273
  cur.turns += 1;
@@ -365,17 +382,18 @@ async function processFiles(claudeFiles, codexFiles, claudeRoots, table, fx, dry
365
382
  return;
366
383
  }
367
384
  const windowCursor = scan.newCursor;
368
- if (drafts[0].isSubagentRollout) {
385
+ if (drafts[0].isSubagentRollout && drafts[0].isForkedRollout) {
369
386
  commit(filePath, [], windowCursor);
370
387
  return;
371
388
  }
389
+ const build = drafts[0].isSubagentRollout ? (draft) => codexChildDraftToRecord(draft, draft.endTs ?? (/* @__PURE__ */ new Date()).toISOString(), table, fx, "scan") : (draft) => buildCodexRecord(draft.agg, table, fx);
372
390
  if (cursor !== null) {
373
- commit(filePath, drafts.map((draft) => buildCodexRecord(draft.agg, table, fx)), windowCursor);
391
+ commit(filePath, drafts.map(build), windowCursor);
374
392
  return;
375
393
  }
376
394
  const floor = recordedFloor("codex", drafts[0].agg.sessionId);
377
395
  const target = floor === null ? drafts : (await scanCodexTurns(filePath, await codexResumePointAtTs(filePath, floor), scanOpts))?.drafts ?? [];
378
- commit(filePath, target.map((draft) => buildCodexRecord(draft.agg, table, fx)), windowCursor);
396
+ commit(filePath, target.map(build), windowCursor);
379
397
  };
380
398
  const runOver = async (files, context, handle, signature = fileMtimeMs) => {
381
399
  for (const filePath of files) {
@@ -423,7 +441,14 @@ async function notifyIngestSummary(result, cfg) {
423
441
  if (v) bySurface[surface] = v;
424
442
  }
425
443
  const summary = formatIngestSummary(
426
- { recordCount: result.records.length, totalUSD: result.totalUSD, totalJPY: result.totalJPY, bySurface },
444
+ {
445
+ recordCount: result.records.length,
446
+ totalUSD: result.totalUSD,
447
+ totalJPY: result.totalJPY,
448
+ totalTokens: result.totalTokens,
449
+ cacheTokens: result.cacheTokens,
450
+ bySurface
451
+ },
427
452
  cfg
428
453
  );
429
454
  const record = {
package/dist/cli.js CHANGED
@@ -7,12 +7,12 @@ import {
7
7
  CODEX_HOOK_TIMEOUT_SECONDS,
8
8
  matchesMarker,
9
9
  parseConfiguredOwnedCodexHookCommand
10
- } from "./chunk-X7U63BZG.js";
10
+ } from "./chunk-XT3KWNWI.js";
11
11
  import {
12
12
  notifyOS,
13
13
  notifySlack,
14
14
  selectNotifyBackend
15
- } from "./chunk-JLDI4D5E.js";
15
+ } from "./chunk-2XOKH54N.js";
16
16
  import {
17
17
  aggregateNewTurn,
18
18
  claudeTranscriptRoots,
@@ -21,7 +21,7 @@ import {
21
21
  listCodexRollouts,
22
22
  rootForClaudePath,
23
23
  splitIntoCodexTurnDrafts
24
- } from "./chunk-OCKZZVOG.js";
24
+ } from "./chunk-GAOGTALW.js";
25
25
  import {
26
26
  codexHome,
27
27
  computeCost,
@@ -39,7 +39,7 @@ import {
39
39
  formatJPY,
40
40
  formatTokens,
41
41
  formatUSD
42
- } from "./chunk-XG73C6CC.js";
42
+ } from "./chunk-OHTPBABH.js";
43
43
  import {
44
44
  cursorPaths,
45
45
  hasUnresolvedPendingMarker,
@@ -1044,7 +1044,7 @@ function printTable(result, days) {
1044
1044
  if (result.total.codexSubagentActivity) {
1045
1045
  const activity = result.total.codexSubagentActivity;
1046
1046
  console.log(
1047
- `(Codex\u30B5\u30D6\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u5229\u7528\u3042\u308A\u30FB\u6599\u91D1\u672A\u96C6\u8A08: ${activity.turns}\u30BF\u30FC\u30F3 / \u958B\u59CB${activity.started}\u30FB\u7D42\u4E86${activity.stopped})`
1047
+ `(Codex\u30B5\u30D6\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u5229\u7528\u3042\u308A(\u6599\u91D1\u306F\u89AA\u30BF\u30FC\u30F3\u306B\u542B\u3081\u306A\u3044): ${activity.turns}\u30BF\u30FC\u30F3 / \u958B\u59CB${activity.started}\u30FB\u7D42\u4E86${activity.stopped})`
1048
1048
  );
1049
1049
  }
1050
1050
  console.log("");
@@ -1157,7 +1157,7 @@ function isCodexPassiveEvent(value) {
1157
1157
  async function runCodexPassiveHook(event, text) {
1158
1158
  try {
1159
1159
  if (event === "Stop") {
1160
- const trackMod = await import("./track-P6A5WDJZ.js");
1160
+ const trackMod = await import("./track-GTQOULBP.js");
1161
1161
  await trackMod.runTrack(text, { codex: true });
1162
1162
  } else if (event === "UserPromptSubmit") {
1163
1163
  const activity = await import("./subagent-store-USG3WJEB.js");
@@ -1242,18 +1242,18 @@ async function main(argv) {
1242
1242
  const text = await readStdin();
1243
1243
  const codex = rest.includes("--codex");
1244
1244
  try {
1245
- const trackMod = await import("./track-P6A5WDJZ.js");
1245
+ const trackMod = await import("./track-GTQOULBP.js");
1246
1246
  await trackMod.runTrack(text, { codex });
1247
1247
  } catch {
1248
1248
  }
1249
1249
  return 0;
1250
1250
  }
1251
1251
  case "init": {
1252
- const { runInit } = await import("./setup-VLMW433R.js");
1252
+ const { runInit } = await import("./setup-H52OFFBQ.js");
1253
1253
  return await runInit(rest);
1254
1254
  }
1255
1255
  case "uninstall": {
1256
- const { runUninstall } = await import("./setup-VLMW433R.js");
1256
+ const { runUninstall } = await import("./setup-H52OFFBQ.js");
1257
1257
  return await runUninstall(rest);
1258
1258
  }
1259
1259
  case "doctor":
@@ -1261,19 +1261,19 @@ async function main(argv) {
1261
1261
  case "report":
1262
1262
  return await runReport(rest);
1263
1263
  case "dashboard": {
1264
- const { runDashboard } = await import("./dashboard-64XHDUVT.js");
1264
+ const { runDashboard } = await import("./dashboard-GR3OMZW5.js");
1265
1265
  return await runDashboard(rest);
1266
1266
  }
1267
1267
  case "sweep": {
1268
- const { runSweep } = await import("./sweep-DVOV4XIZ.js");
1268
+ const { runSweep } = await import("./sweep-2GHO4U5C.js");
1269
1269
  return await runSweep(rest);
1270
1270
  }
1271
1271
  case "scan": {
1272
- const { runScan } = await import("./scan-UB6ORRZK.js");
1272
+ const { runScan } = await import("./scan-YIU6CG3E.js");
1273
1273
  return await runScan(rest);
1274
1274
  }
1275
1275
  case "reset-cursors": {
1276
- const { runResetCursors } = await import("./reset-cursors-5CCM6MQL.js");
1276
+ const { runResetCursors } = await import("./reset-cursors-UVQXFN5Y.js");
1277
1277
  return await runResetCursors(rest);
1278
1278
  }
1279
1279
  case "history": {
@@ -1281,7 +1281,7 @@ async function main(argv) {
1281
1281
  return await runHistory(rest);
1282
1282
  }
1283
1283
  case "budget": {
1284
- const { runBudget } = await import("./budget-O4VAP6RB.js");
1284
+ const { runBudget } = await import("./budget-KLRUKU6V.js");
1285
1285
  return runBudget(rest);
1286
1286
  }
1287
1287
  case "mute": {
@@ -3,12 +3,12 @@ import {
3
3
  browserOpenPlan,
4
4
  runDashboard,
5
5
  writeDashboardHtml
6
- } from "./chunk-O2E4VTDR.js";
6
+ } from "./chunk-6GWPAYJQ.js";
7
7
  import "./chunk-3M5UQK76.js";
8
8
  import "./chunk-PXMXEFM7.js";
9
9
  import "./chunk-6IVSZ3S4.js";
10
10
  import "./chunk-DGXUSPS4.js";
11
- import "./chunk-XG73C6CC.js";
11
+ import "./chunk-OHTPBABH.js";
12
12
  import "./chunk-WONMZ466.js";
13
13
  export {
14
14
  browserOpenPlan,
@@ -34,6 +34,9 @@ async function runResetCursors(argv) {
34
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
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
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
+ console.log(
38
+ "\u6CE8\u610F: \u904E\u53BB\u306B\u672A\u8A08\u4E0A\u3060\u3063\u305F\u5206(\u4F8B: \u65E7\u30D0\u30FC\u30B8\u30E7\u30F3\u3067\u8A08\u4E0A\u3057\u3066\u3044\u306A\u304B\u3063\u305F Codex \u30B5\u30D6\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u5206)\u3082\u65B0\u898F\u306B\u53D6\u308A\u8FBC\u307E\u308C\u308B\u305F\u3081\u3001\u5C65\u6B74\u7DCF\u984D\u30FB\u6708\u4E88\u7B97\u306E\u6D88\u5316\u304C\u5897\u3048\u308B\u3053\u3068\u304C\u3042\u308A\u307E\u3059\u3002"
39
+ );
37
40
  return 0;
38
41
  }
39
42
  export {
@@ -2,20 +2,21 @@
2
2
  import {
3
3
  notifyIngestSummary,
4
4
  runIngest
5
- } from "./chunk-V4IS4FXH.js";
6
- import "./chunk-JLDI4D5E.js";
7
- import "./chunk-6BXD6HU5.js";
8
- import "./chunk-OCKZZVOG.js";
5
+ } from "./chunk-XXXGWLTS.js";
6
+ import "./chunk-2XOKH54N.js";
7
+ import "./chunk-SKX5EMST.js";
8
+ import "./chunk-GAOGTALW.js";
9
9
  import "./chunk-Q45AAPJY.js";
10
- import "./chunk-O2E4VTDR.js";
10
+ import "./chunk-6GWPAYJQ.js";
11
11
  import "./chunk-3M5UQK76.js";
12
12
  import "./chunk-PXMXEFM7.js";
13
13
  import "./chunk-6IVSZ3S4.js";
14
14
  import "./chunk-DGXUSPS4.js";
15
15
  import {
16
16
  formatJPY,
17
+ formatTokens,
17
18
  formatUSD
18
- } from "./chunk-XG73C6CC.js";
19
+ } from "./chunk-OHTPBABH.js";
19
20
  import {
20
21
  readConfig
21
22
  } from "./chunk-WONMZ466.js";
@@ -58,6 +59,8 @@ async function runScan(argv) {
58
59
  failures: result.failures,
59
60
  totalUSD: result.totalUSD,
60
61
  totalJPY: result.totalJPY,
62
+ totalTokens: result.totalTokens,
63
+ cacheTokens: result.cacheTokens,
61
64
  bySurface: result.bySurface
62
65
  },
63
66
  null,
@@ -73,7 +76,11 @@ async function runScan(argv) {
73
76
  if (result.records.length === 0) {
74
77
  console.log("\u65B0\u898F\u53D6\u308A\u8FBC\u307F\u306F\u3042\u308A\u307E\u305B\u3093\u3067\u3057\u305F");
75
78
  } else {
76
- console.log(`\u53D6\u308A\u8FBC\u307F: ${result.records.length} \u30BF\u30FC\u30F3\u3001\u5408\u8A08 ${formatUSD(result.totalUSD)}(${formatJPY(result.totalJPY)})`);
79
+ const cachePct = result.totalTokens > 0 ? Math.round(result.cacheTokens / result.totalTokens * 100) : 0;
80
+ const tokensPart = result.totalTokens > 0 ? `\u3001\u8A08 ${formatTokens(result.totalTokens)} tokens(cache ${cachePct}%)` : "";
81
+ console.log(
82
+ `\u53D6\u308A\u8FBC\u307F: ${result.records.length} \u30BF\u30FC\u30F3\u3001\u5408\u8A08 ${formatUSD(result.totalUSD)}(${formatJPY(result.totalJPY)})${tokensPart}`
83
+ );
77
84
  const bySurface = Object.entries(result.bySurface).sort((a, b) => (b[1]?.usd ?? 0) - (a[1]?.usd ?? 0));
78
85
  for (const [surface, v] of bySurface) {
79
86
  if (!v) continue;
@@ -4,11 +4,11 @@ import {
4
4
  matchesMarker,
5
5
  runInit,
6
6
  runUninstall
7
- } from "./chunk-X7U63BZG.js";
8
- import "./chunk-JLDI4D5E.js";
7
+ } from "./chunk-XT3KWNWI.js";
8
+ import "./chunk-2XOKH54N.js";
9
9
  import "./chunk-Q45AAPJY.js";
10
10
  import "./chunk-DGXUSPS4.js";
11
- import "./chunk-XG73C6CC.js";
11
+ import "./chunk-OHTPBABH.js";
12
12
  import "./chunk-WONMZ466.js";
13
13
  export {
14
14
  maskWebhookUrl,
@@ -1,20 +1,22 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  attachSubagentGroups,
4
+ codexChildDraftToRecord,
4
5
  runSweep,
5
6
  splitIntoTurnDrafts
6
- } from "./chunk-6BXD6HU5.js";
7
- import "./chunk-OCKZZVOG.js";
7
+ } from "./chunk-SKX5EMST.js";
8
+ import "./chunk-GAOGTALW.js";
8
9
  import "./chunk-Q45AAPJY.js";
9
- import "./chunk-O2E4VTDR.js";
10
+ import "./chunk-6GWPAYJQ.js";
10
11
  import "./chunk-3M5UQK76.js";
11
12
  import "./chunk-PXMXEFM7.js";
12
13
  import "./chunk-6IVSZ3S4.js";
13
14
  import "./chunk-DGXUSPS4.js";
14
- import "./chunk-XG73C6CC.js";
15
+ import "./chunk-OHTPBABH.js";
15
16
  import "./chunk-WONMZ466.js";
16
17
  export {
17
18
  attachSubagentGroups,
19
+ codexChildDraftToRecord,
18
20
  runSweep,
19
21
  splitIntoTurnDrafts
20
22
  };
@@ -2,15 +2,15 @@
2
2
  import {
3
3
  notifyIngestSummary,
4
4
  runIngest
5
- } from "./chunk-V4IS4FXH.js";
5
+ } from "./chunk-XXXGWLTS.js";
6
6
  import {
7
7
  notifyOS,
8
8
  notifySlack
9
- } from "./chunk-JLDI4D5E.js";
9
+ } from "./chunk-2XOKH54N.js";
10
10
  import {
11
11
  collectSubagentUsage,
12
12
  normalizeCodexOriginator
13
- } from "./chunk-6BXD6HU5.js";
13
+ } from "./chunk-SKX5EMST.js";
14
14
  import {
15
15
  aggregateCodexTurn,
16
16
  aggregateNewTurn,
@@ -22,14 +22,14 @@ import {
22
22
  getUsdJpy,
23
23
  messageKeyFilterOf,
24
24
  setCountedCalls
25
- } from "./chunk-OCKZZVOG.js";
25
+ } from "./chunk-GAOGTALW.js";
26
26
  import {
27
27
  computeCost,
28
28
  loadPriceTable
29
29
  } from "./chunk-Q45AAPJY.js";
30
30
  import {
31
31
  writeDashboardHtml
32
- } from "./chunk-O2E4VTDR.js";
32
+ } from "./chunk-6GWPAYJQ.js";
33
33
  import {
34
34
  isFullDashboardDue,
35
35
  makeFullDashboardState,
@@ -40,7 +40,7 @@ import {
40
40
  } from "./chunk-PXMXEFM7.js";
41
41
  import "./chunk-6IVSZ3S4.js";
42
42
  import "./chunk-DGXUSPS4.js";
43
- import "./chunk-XG73C6CC.js";
43
+ import "./chunk-OHTPBABH.js";
44
44
  import {
45
45
  appendTurn,
46
46
  clearPendingAppend,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccc-notifier",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "description": "Claude Code Cost notifier (now also covers Codex CLI): per-prompt cost notifications (USD/JPY) with local history and HTML dashboard",
5
5
  "keywords": [
6
6
  "claude",