ccc-notifier 0.8.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-XOGYLFX3.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-XOGYLFX3.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-XOGYLFX3.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 ?? "");
@@ -258,7 +271,11 @@ function buildTurnEmbed(rec, map) {
258
271
  bs: turnBySlot(rec, map),
259
272
  pr: text,
260
273
  tr: truncated,
261
- sa
274
+ sa,
275
+ tk: (() => {
276
+ const t = turnTokenTotals(rec);
277
+ return [t.tin, t.tout, t.tcache];
278
+ })()
262
279
  };
263
280
  if (rec.source === "codex") out.sc = "codex";
264
281
  if (rec.source === "codex" && rec.subagentActivity) {
@@ -281,28 +298,29 @@ function computeKpis(turns) {
281
298
  const week = emptyTotals();
282
299
  const month = emptyTotals();
283
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
+ };
284
309
  for (const rec of turns) {
285
310
  const usd = turnTotalUSD(rec);
286
311
  const jpy = turnTotalJPY(rec);
287
- all.usd += usd;
288
- all.jpy += jpy;
289
- all.turns += 1;
312
+ const tk = turnTokenTotals(rec);
313
+ add(all, usd, jpy, tk);
290
314
  const dt = new Date(rec.ts);
291
315
  if (Number.isNaN(dt.getTime())) continue;
292
316
  if (dt.getFullYear() === y && dt.getMonth() === mo && dt.getDate() === d) {
293
- today.usd += usd;
294
- today.jpy += jpy;
295
- today.turns += 1;
317
+ add(today, usd, jpy, tk);
296
318
  }
297
319
  if (dt.getTime() >= weekCutoff) {
298
- week.usd += usd;
299
- week.jpy += jpy;
300
- week.turns += 1;
320
+ add(week, usd, jpy, tk);
301
321
  }
302
322
  if (dt.getFullYear() === y && dt.getMonth() === mo) {
303
- month.usd += usd;
304
- month.jpy += jpy;
305
- month.turns += 1;
323
+ add(month, usd, jpy, tk);
306
324
  }
307
325
  }
308
326
  return { today, week, month, all };
@@ -360,9 +378,11 @@ section.card > .note{color:var(--muted); font-size:12px; margin:0 0 14px;}
360
378
  .sel-label b{color:var(--ink); font-weight:640;}
361
379
 
362
380
  .legend{display:flex; flex-wrap:wrap; gap:8px 18px; margin:2px 0 14px;}
381
+ .legend[hidden]{display:none;}
363
382
  .legend-item{display:inline-flex; align-items:center; gap:7px; font-size:13px; color:var(--ink2);}
364
383
  .swatch{display:inline-block; width:11px; height:11px; border-radius:3px; flex:0 0 auto;}
365
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);}
366
386
  .k5{background:var(--s5);} .k6{background:var(--s6);} .k7{background:var(--s7);} .k8{background:var(--s8);} .kother{background:var(--sother);}
367
387
 
368
388
  .budget-bar{height:12px; background:var(--plane); border:1px solid var(--border); border-radius:7px; overflow:hidden; margin:12px 0;}
@@ -380,6 +400,7 @@ section.card > .note{color:var(--muted); font-size:12px; margin:0 0 14px;}
380
400
  .chart-scroll{overflow-x:auto;}
381
401
  .seg.s1{fill:var(--s1);} .seg.s2{fill:var(--s2);} .seg.s3{fill:var(--s3);} .seg.s4{fill:var(--s4);}
382
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);}
383
404
  .seg.dim{opacity:0.28;}
384
405
  .grid-line{stroke:var(--grid); stroke-width:1;}
385
406
  .axis-line{stroke:var(--axis); stroke-width:1;}
@@ -469,6 +490,13 @@ var APP_JS = `<script>
469
490
  if(v >= 1) return '\xA5' + Math.round(v).toLocaleString('en-US');
470
491
  return '\xA5' + (Math.round(v * 10) / 10);
471
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
+ }
472
500
 
473
501
  // ---- \u65E5\u4ED8/\u30D0\u30B1\u30C3\u30C8(\u30ED\u30FC\u30AB\u30EBTZ)----
474
502
  function pad(n){ return (n < 10 ? '0' : '') + n; }
@@ -506,6 +534,9 @@ var APP_JS = `<script>
506
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
507
535
  var HAS_CODEX = document.querySelectorAll('[data-src]').length > 0;
508
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); }
509
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
510
541
  function activeTurns(){
511
542
  if(!HAS_CODEX || SRC === 'all') return turns;
@@ -542,10 +573,13 @@ var APP_JS = `<script>
542
573
  var k = bucketKey(tn.t, gran);
543
574
  if(k === null) continue;
544
575
  var b = map[k];
545
- 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); }
546
577
  b.turns++;
547
578
  var bs = tn.bs || {};
548
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];
549
583
  }
550
584
  keys.sort();
551
585
  return keys.map(function(k){ return map[k]; });
@@ -614,8 +648,9 @@ var APP_JS = `<script>
614
648
  var plotW = band * n;
615
649
  var W = ml + plotW + mr;
616
650
 
651
+ var tokMode = MET === 'tok';
617
652
  var maxTotal = 0;
618
- 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; });
619
654
  var niceMax = niceCeil(maxTotal);
620
655
  var yscale = plotH / niceMax;
621
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
@@ -624,7 +659,7 @@ var APP_JS = `<script>
624
659
  for(var sp=0; sp<buckets.length; sp++){ if(buckets[sp].key === SEL){ selPresent = true; break; } }
625
660
 
626
661
  var svg = svgEl('svg', { 'class':'chart', viewBox:'0 0 ' + W + ' ' + H, role:'img',
627
- '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' });
628
663
  svg.setAttribute('width', String(W));
629
664
  svg.setAttribute('height', String(H));
630
665
 
@@ -634,7 +669,7 @@ var APP_JS = `<script>
634
669
  var yy = f2(baseY - val * yscale);
635
670
  svg.appendChild(svgEl('line', { 'class':'grid-line', x1:ml, y1:yy, x2:W-mr, y2:yy }));
636
671
  var lbl = svgEl('text', { 'class':'tick-label', x:ml-8, y:yy+4, 'text-anchor':'end' });
637
- lbl.textContent = formatUSD(val);
672
+ lbl.textContent = tokMode ? formatTok(val) : formatUSD(val);
638
673
  svg.appendChild(lbl);
639
674
  }
640
675
 
@@ -647,9 +682,16 @@ var APP_JS = `<script>
647
682
  var x = cx - barW/2;
648
683
  var dim = (SEL !== null && selPresent && b.key !== SEL);
649
684
  var cumV = 0;
650
- for(var j=0;j<slotOrder.length;j++){
651
- var slot = slotOrder[j];
652
- 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];
653
695
  if(v <= 0) continue;
654
696
  var vTop = cumV + v;
655
697
  var yTop = baseY - vTop * yscale;
@@ -658,10 +700,11 @@ var APP_JS = `<script>
658
700
  cumV = vTop;
659
701
  var h = yBot - yTop;
660
702
  if(h <= 0.4) continue;
661
- 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) });
662
704
  svg.appendChild(rect);
663
705
  }
664
- 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)){
665
708
  var xl = svgEl('text', { 'class':'tick-label', x:f2(cx), y:baseY+16, 'text-anchor':'middle' });
666
709
  xl.textContent = bucketLabel(b.key, GRAN);
667
710
  svg.appendChild(xl);
@@ -698,6 +741,15 @@ var APP_JS = `<script>
698
741
  lastRenderedGran = GRAN;
699
742
  }
700
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
+
701
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
702
754
  // slotOrder\u306E\u307E\u307E\u56FA\u5B9A\u3057\u3001\u540C\u984D\u3082slotOrder\u3067\u6C7A\u5B9A\u7684\u306B\u4E26\u3079\u308B\u3002
703
755
  function tooltipSlotOrder(bucket){
@@ -714,19 +766,24 @@ var APP_JS = `<script>
714
766
  if(!tip) return;
715
767
  clearNode(tip);
716
768
  var h = document.createElement('div'); h.className = 'tip-title';
717
- h.textContent = periodText(bucket.key, GRAN) + ' \u5408\u8A08 ' + formatUSD(bucket.total) + ' \xB7 ' + bucket.turns + ' \u30BF\u30FC\u30F3';
718
- tip.appendChild(h);
719
- var tipOrder = tooltipSlotOrder(bucket);
720
- for(var j=0;j<tipOrder.length;j++){
721
- var slot = tipOrder[j];
722
- var v = bucket.bs[slot] || 0;
723
- if(v <= 0) continue;
724
- var row = document.createElement('div'); row.className = 'tip-row';
725
- var key = document.createElement('span'); key.className = 'tip-key k'+slot;
726
- var name = document.createElement('span'); name.className = 'tip-name'; name.textContent = slotName[slot] || slot;
727
- var val = document.createElement('span'); val.className = 'tip-val'; val.textContent = formatUSD(v);
728
- row.appendChild(key); row.appendChild(name); row.appendChild(val);
729
- 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
+ }
730
787
  }
731
788
  tip.hidden = false;
732
789
  positionTip(evt);
@@ -746,23 +803,29 @@ var APP_JS = `<script>
746
803
  function renderByModel(sub){
747
804
  if(!modelEl) return;
748
805
  clearNode(modelEl);
749
- var usd = {}, jpy = {}, tcount = {}, total = 0;
806
+ var usd = {}, jpy = {}, tcount = {}, ttok = {}, total = 0;
750
807
  sub.forEach(function(tn){
751
808
  var bs = tn.bs || {}, fx = tn.fx || 0;
752
- 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)){
753
813
  usd[s] = (usd[s]||0) + bs[s]; jpy[s] = (jpy[s]||0) + bs[s]*fx; total += bs[s];
754
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];
755
818
  } }
756
819
  });
757
820
  var table = document.createElement('table'); table.className = 'data-table';
758
821
  var thead = document.createElement('thead');
759
- 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>';
760
823
  table.appendChild(thead);
761
824
  var tbody = document.createElement('tbody');
762
825
  var any = false;
763
826
  for(var j=0;j<slotOrder.length;j++){
764
827
  var slot = slotOrder[j];
765
- if(!(usd[slot] > 0)) continue;
828
+ if(!(usd[slot] > 0 || (ttok[slot]||0) > 0)) continue;
766
829
  any = true;
767
830
  var share = total > 0 ? (usd[slot]/total*100) : 0;
768
831
  var tr = document.createElement('tr');
@@ -772,8 +835,9 @@ var APP_JS = `<script>
772
835
  var c2 = document.createElement('td'); c2.className = 'c-num'; c2.textContent = String(tcount[slot]||0);
773
836
  var c3 = document.createElement('td'); c3.className = 'c-num'; c3.textContent = formatUSD(usd[slot]);
774
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);
775
839
  var c5 = document.createElement('td'); c5.className = 'c-num'; c5.textContent = share.toFixed(1) + '%';
776
- 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);
777
841
  tbody.appendChild(tr);
778
842
  }
779
843
  table.appendChild(tbody);
@@ -791,15 +855,16 @@ var APP_JS = `<script>
791
855
  var bs = tn.bs || {}, fx = tn.fx || 0, total = 0;
792
856
  for(var s in bs){ if(bs.hasOwnProperty(s)) total += bs[s]; }
793
857
  var name = tn.p || '(unknown)';
794
- var a = agg[name] || (agg[name] = { usd:0, jpy:0, turns:0 });
795
- 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];
796
861
  });
797
- 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 }; });
798
863
  rows.sort(function(a,b){ return b.usd - a.usd || a.name.localeCompare(b.name); });
799
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; }
800
865
  var table = document.createElement('table'); table.className = 'data-table';
801
866
  var thead = document.createElement('thead');
802
- 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>';
803
868
  table.appendChild(thead);
804
869
  var tbody = document.createElement('tbody');
805
870
  rows.forEach(function(r){
@@ -808,7 +873,8 @@ var APP_JS = `<script>
808
873
  var c2 = document.createElement('td'); c2.className='c-num'; c2.textContent = String(r.turns);
809
874
  var c3 = document.createElement('td'); c3.className='c-num'; c3.textContent = formatUSD(r.usd);
810
875
  var c4 = document.createElement('td'); c4.className='c-num'; c4.textContent = formatJPY(r.jpy);
811
- 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);
812
878
  tbody.appendChild(tr);
813
879
  });
814
880
  table.appendChild(tbody);
@@ -894,14 +960,21 @@ var APP_JS = `<script>
894
960
  var kpiCards = document.querySelectorAll('.kpi .stat');
895
961
  var heroValueEl = document.querySelector('.hero .hero-value');
896
962
  var heroMetaEls = document.querySelectorAll('.hero .hero-meta');
897
- 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)
898
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
+ }
899
970
  function setKpiCard(card, tot){
900
971
  if(!card) return;
901
972
  var v = card.querySelector('.stat-value');
902
973
  var m = card.querySelector('.stat-meta');
974
+ var tk = card.querySelector('.stat-tok');
903
975
  if(v) v.textContent = formatUSD(tot.usd);
904
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);
905
978
  }
906
979
  function renderKpis(){
907
980
  if(!HAS_CODEX) return;
@@ -909,25 +982,29 @@ var APP_JS = `<script>
909
982
  var now = new Date();
910
983
  var y = now.getFullYear(), mo = now.getMonth(), d = now.getDate();
911
984
  var weekCutoff = Date.now() - 7*86400000;
912
- 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();
913
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]; }
914
989
  for(var i=0;i<src.length;i++){
915
990
  var tn = src[i], bs = tn.bs || {}, fx = tn.fx || 0, u = 0;
916
991
  for(var s in bs){ if(bs.hasOwnProperty(s)) u += bs[s]; }
917
992
  var j = u * fx;
993
+ var tk = tn.tk || [0,0,0];
918
994
  if(tn.sa){ var sau = u - (tn.um || 0); saUsd += sau; saJpy += sau * fx; } // SA \u5206 = \u7DCF\u984D \u2212 \u30E1\u30A4\u30F3
919
- all.usd += u; all.jpy += j; all.turns++;
995
+ acc(all, u, j, tk);
920
996
  var dt = new Date(tn.t);
921
997
  if(isNaN(dt.getTime())) continue;
922
- if(dt.getFullYear()===y && dt.getMonth()===mo && dt.getDate()===d){ today.usd+=u; today.jpy+=j; today.turns++; }
923
- if(tn.t >= weekCutoff){ week.usd+=u; week.jpy+=j; week.turns++; }
924
- 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); }
925
1001
  }
926
1002
  if(kpiCards.length >= 4){
927
1003
  setKpiCard(kpiCards[0], today); setKpiCard(kpiCards[1], week); setKpiCard(kpiCards[2], month); setKpiCard(kpiCards[3], all);
928
1004
  }
929
1005
  if(heroValueEl) heroValueEl.textContent = formatUSD(all.usd);
930
1006
  if(heroMetaEls.length > 0) heroMetaEls[0].textContent = formatJPY(all.jpy) + ' \xB7 ' + all.turns + ' \u30BF\u30FC\u30F3';
1007
+ if(heroTokEl) heroTokEl.textContent = tokLine(all);
931
1008
  if(heroSaEl){
932
1009
  heroSaEl.hidden = !(saUsd > 0);
933
1010
  if(saUsd > 0) heroSaEl.textContent = heroSaPrefix + formatUSD(saUsd) + '(' + formatJPY(saJpy) + ')';
@@ -1035,6 +1112,15 @@ var APP_JS = `<script>
1035
1112
  if(allBtn) allBtn.classList.toggle('active', SEL === null);
1036
1113
  var sbtns = document.querySelectorAll('[data-src]');
1037
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';
1038
1124
  }
1039
1125
 
1040
1126
  // ---- \u5168\u4F53\u30EC\u30F3\u30C0\u30EA\u30F3\u30B0 ----
@@ -1057,6 +1143,10 @@ var APP_JS = `<script>
1057
1143
  }
1058
1144
  var allBtn = document.getElementById('cccn-all');
1059
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
+ }
1060
1150
  var srcBtns = document.querySelectorAll('[data-src]');
1061
1151
  for(var sbi=0;sbi<srcBtns.length;sbi++){
1062
1152
  (function(btn){ btn.addEventListener('click', function(){ setSrc(btn.getAttribute('data-src')); render(); }); })(srcBtns[sbi]);
@@ -1080,16 +1170,18 @@ var APP_JS = `<script>
1080
1170
  })();
1081
1171
  </script>`;
1082
1172
  function statCard(label, sub, totals) {
1083
- 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>`;
1084
1174
  }
1085
1175
  function computeSurfaceBreakdown(turns) {
1086
1176
  const map = /* @__PURE__ */ new Map();
1087
1177
  for (const rec of turns) {
1088
1178
  const surface = effectiveSurface(rec);
1089
- 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);
1090
1181
  agg.turns += 1;
1091
1182
  agg.usd += turnTotalUSD(rec);
1092
1183
  agg.jpy += turnTotalJPY(rec);
1184
+ agg.tokens += tk.tin + tk.tout;
1093
1185
  map.set(surface, agg);
1094
1186
  }
1095
1187
  return [...map.values()].sort((a, b) => b.usd - a.usd);
@@ -1099,9 +1191,9 @@ function surfaceSection(turns) {
1099
1191
  if (rows.length === 0) return "";
1100
1192
  if (rows.length === 1 && rows[0].surface === "cli") return "";
1101
1193
  const trs = rows.map(
1102
- (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>`
1103
1195
  ).join("");
1104
- 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>`;
1105
1197
  }
1106
1198
  function renderLegend(slots) {
1107
1199
  if (slots.length < 2) return "";
@@ -1186,11 +1278,11 @@ function renderDashboard(turns, opts, fullHistory) {
1186
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>`;
1187
1279
  return head + header2 + budgetSection + empty + foot;
1188
1280
  }
1189
- 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>`;
1190
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>`;
1191
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>` : "";
1192
- 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>`;
1193
- 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>`;
1194
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";
1195
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>`;
1196
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>`;
@@ -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 ? "-" : "";
@@ -20,7 +20,7 @@ import {
20
20
  } from "./chunk-Q45AAPJY.js";
21
21
  import {
22
22
  writeDashboardHtml
23
- } from "./chunk-DSTGXYSY.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-XOGYLFX3.js";
36
+ } from "./chunk-OHTPBABH.js";
37
37
  import {
38
38
  appendTurn,
39
39
  loadCursor,
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  notifyOS,
4
4
  notifySlack
5
- } from "./chunk-TTD3BGOL.js";
5
+ } from "./chunk-2XOKH54N.js";
6
6
  import {
7
7
  codexHome,
8
8
  detectCodex,
@@ -2,14 +2,14 @@
2
2
  import {
3
3
  notifyOS,
4
4
  notifySlack
5
- } from "./chunk-TTD3BGOL.js";
5
+ } from "./chunk-2XOKH54N.js";
6
6
  import {
7
7
  attachSubagentGroups,
8
8
  codexChildDraftToRecord,
9
9
  collectSubagentUsage,
10
10
  normalizeCodexOriginator,
11
11
  splitIntoTurnDrafts
12
- } from "./chunk-C7QOYVGT.js";
12
+ } from "./chunk-SKX5EMST.js";
13
13
  import {
14
14
  anyOf,
15
15
  callFingerprints,
@@ -32,7 +32,7 @@ import {
32
32
  } from "./chunk-PXMXEFM7.js";
33
33
  import {
34
34
  formatIngestSummary
35
- } from "./chunk-XOGYLFX3.js";
35
+ } from "./chunk-OHTPBABH.js";
36
36
  import {
37
37
  appendTurn,
38
38
  floorKey,
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-RE72XBXB.js";
10
+ } from "./chunk-XT3KWNWI.js";
11
11
  import {
12
12
  notifyOS,
13
13
  notifySlack,
14
14
  selectNotifyBackend
15
- } from "./chunk-TTD3BGOL.js";
15
+ } from "./chunk-2XOKH54N.js";
16
16
  import {
17
17
  aggregateNewTurn,
18
18
  claudeTranscriptRoots,
@@ -39,7 +39,7 @@ import {
39
39
  formatJPY,
40
40
  formatTokens,
41
41
  formatUSD
42
- } from "./chunk-XOGYLFX3.js";
42
+ } from "./chunk-OHTPBABH.js";
43
43
  import {
44
44
  cursorPaths,
45
45
  hasUnresolvedPendingMarker,
@@ -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-TIINM4ZR.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-TIINM4ZR.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-2QDA3OWE.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-2QDA3OWE.js");
1256
+ const { runUninstall } = await import("./setup-H52OFFBQ.js");
1257
1257
  return await runUninstall(rest);
1258
1258
  }
1259
1259
  case "doctor":
@@ -1261,15 +1261,15 @@ async function main(argv) {
1261
1261
  case "report":
1262
1262
  return await runReport(rest);
1263
1263
  case "dashboard": {
1264
- const { runDashboard } = await import("./dashboard-YG4H3GLA.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-OLI2LIXJ.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-QB2N2O4Q.js");
1272
+ const { runScan } = await import("./scan-YIU6CG3E.js");
1273
1273
  return await runScan(rest);
1274
1274
  }
1275
1275
  case "reset-cursors": {
@@ -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-U37CCG4Y.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-DSTGXYSY.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-XOGYLFX3.js";
11
+ import "./chunk-OHTPBABH.js";
12
12
  import "./chunk-WONMZ466.js";
13
13
  export {
14
14
  browserOpenPlan,
@@ -2,12 +2,12 @@
2
2
  import {
3
3
  notifyIngestSummary,
4
4
  runIngest
5
- } from "./chunk-YH45W7TA.js";
6
- import "./chunk-TTD3BGOL.js";
7
- import "./chunk-C7QOYVGT.js";
5
+ } from "./chunk-XXXGWLTS.js";
6
+ import "./chunk-2XOKH54N.js";
7
+ import "./chunk-SKX5EMST.js";
8
8
  import "./chunk-GAOGTALW.js";
9
9
  import "./chunk-Q45AAPJY.js";
10
- import "./chunk-DSTGXYSY.js";
10
+ import "./chunk-6GWPAYJQ.js";
11
11
  import "./chunk-3M5UQK76.js";
12
12
  import "./chunk-PXMXEFM7.js";
13
13
  import "./chunk-6IVSZ3S4.js";
@@ -16,7 +16,7 @@ import {
16
16
  formatJPY,
17
17
  formatTokens,
18
18
  formatUSD
19
- } from "./chunk-XOGYLFX3.js";
19
+ } from "./chunk-OHTPBABH.js";
20
20
  import {
21
21
  readConfig
22
22
  } from "./chunk-WONMZ466.js";
@@ -4,11 +4,11 @@ import {
4
4
  matchesMarker,
5
5
  runInit,
6
6
  runUninstall
7
- } from "./chunk-RE72XBXB.js";
8
- import "./chunk-TTD3BGOL.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-XOGYLFX3.js";
11
+ import "./chunk-OHTPBABH.js";
12
12
  import "./chunk-WONMZ466.js";
13
13
  export {
14
14
  maskWebhookUrl,
@@ -4,15 +4,15 @@ import {
4
4
  codexChildDraftToRecord,
5
5
  runSweep,
6
6
  splitIntoTurnDrafts
7
- } from "./chunk-C7QOYVGT.js";
7
+ } from "./chunk-SKX5EMST.js";
8
8
  import "./chunk-GAOGTALW.js";
9
9
  import "./chunk-Q45AAPJY.js";
10
- import "./chunk-DSTGXYSY.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
- import "./chunk-XOGYLFX3.js";
15
+ import "./chunk-OHTPBABH.js";
16
16
  import "./chunk-WONMZ466.js";
17
17
  export {
18
18
  attachSubagentGroups,
@@ -2,15 +2,15 @@
2
2
  import {
3
3
  notifyIngestSummary,
4
4
  runIngest
5
- } from "./chunk-YH45W7TA.js";
5
+ } from "./chunk-XXXGWLTS.js";
6
6
  import {
7
7
  notifyOS,
8
8
  notifySlack
9
- } from "./chunk-TTD3BGOL.js";
9
+ } from "./chunk-2XOKH54N.js";
10
10
  import {
11
11
  collectSubagentUsage,
12
12
  normalizeCodexOriginator
13
- } from "./chunk-C7QOYVGT.js";
13
+ } from "./chunk-SKX5EMST.js";
14
14
  import {
15
15
  aggregateCodexTurn,
16
16
  aggregateNewTurn,
@@ -29,7 +29,7 @@ import {
29
29
  } from "./chunk-Q45AAPJY.js";
30
30
  import {
31
31
  writeDashboardHtml
32
- } from "./chunk-DSTGXYSY.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-XOGYLFX3.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.8.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",