ccc-notifier 0.1.0 → 0.3.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.
@@ -7,12 +7,12 @@ import {
7
7
  formatTokens,
8
8
  formatUSD,
9
9
  modelDisplayName
10
- } from "./chunk-TBFKGFZX.js";
10
+ } from "./chunk-J5QAYTFE.js";
11
11
  import {
12
12
  paths,
13
13
  readConfig,
14
14
  readTurns
15
- } from "./chunk-IIYMGLV4.js";
15
+ } from "./chunk-ECADO26T.js";
16
16
 
17
17
  // src/dashboard.ts
18
18
  import { spawn, spawnSync } from "child_process";
@@ -170,7 +170,7 @@ function buildTurnEmbed(rec, map) {
170
170
  };
171
171
  }
172
172
  const ms = Date.parse(rec.ts);
173
- return {
173
+ const out = {
174
174
  t: Number.isFinite(ms) ? ms : 0,
175
175
  ts: fmtLocalDateTime(rec.ts),
176
176
  p: basename(rec.project) || rec.project || "(unknown)",
@@ -187,6 +187,8 @@ function buildTurnEmbed(rec, map) {
187
187
  tr: truncated,
188
188
  sa
189
189
  };
190
+ if (rec.source === "codex") out.sc = "codex";
191
+ return out;
190
192
  }
191
193
  function computeKpis(turns) {
192
194
  const now = /* @__PURE__ */ new Date();
@@ -331,6 +333,7 @@ section.card > .note{color:var(--muted); font-size:12px; margin:0 0 14px;}
331
333
  .turns .c-time,.turns .c-model{white-space:nowrap;}
332
334
  .turns .c-proj{max-width:160px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;}
333
335
  .turns .c-prompt{color:var(--ink2);}
336
+ .src-badge{display:inline-block; margin-left:6px; padding:1px 6px; border-radius:6px; font-size:11px; font-weight:600; line-height:1.4; background:var(--plane); color:var(--ink2); border:1px solid var(--border); vertical-align:middle;}
334
337
  .turn-row{cursor:pointer;}
335
338
  .turn-row:hover{background:color-mix(in srgb, var(--ink) 4%, transparent);}
336
339
  .turn-row:focus-visible{outline:2px solid var(--s1); outline-offset:-2px;}
@@ -418,17 +421,37 @@ var APP_JS = `<script>
418
421
  function ssGet(k){ try { return sessionStorage.getItem(k); } catch(e){ return null; } }
419
422
  function ssSet(k, v){ try { sessionStorage.setItem(k, v); } catch(e){} }
420
423
  var GRAN = ssGet('cccn-gran'); if(['day','week','month'].indexOf(GRAN) < 0) GRAN = 'day';
424
+ // \u30BD\u30FC\u30B9(Claude/Codex)\u30D5\u30A3\u30EB\u30BF\u3002\u30C1\u30C3\u30D7\u304C\u63CF\u753B\u3055\u308C\u3066\u3044\u308B(=Codex \u30EC\u30B3\u30FC\u30C9\u304C\u5B58\u5728\u3059\u308B)\u3068\u304D\u306E\u307F\u6709\u52B9\u3002
425
+ // \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
426
+ var HAS_CODEX = document.querySelectorAll('[data-src]').length > 0;
427
+ var SRC = ssGet('cccn-src'); if(['all','claude','codex'].indexOf(SRC) < 0) SRC = 'all';
428
+ // \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
429
+ function activeTurns(){
430
+ if(!HAS_CODEX || SRC === 'all') return turns;
431
+ if(SRC === 'codex') return turns.filter(function(tn){ return tn.sc === 'codex'; });
432
+ return turns.filter(function(tn){ return !tn.sc; });
433
+ }
421
434
  // \u65E2\u5B9A\u306F\u300C\u8D77\u52D5\u3057\u305F\u65E5\u300D\u3092\u521D\u671F\u9078\u629E\u3059\u308B(\u672A\u4FDD\u5B58\u6642)\u3002\u660E\u793A\u7684\u306A\u901A\u7B97\u306F '__all__'\u3001\u305D\u306E\u4ED6\u306F\u4FDD\u5B58\u30AD\u30FC\u3002
422
435
  var storedSel = ssGet('cccn-sel');
423
436
  var SEL = (storedSel === null) ? defaultSel(GRAN) : (storedSel === '__all__' ? null : storedSel);
424
437
  function setGran(g){ GRAN = g; ssSet('cccn-gran', g); }
425
438
  function setSel(v){ SEL = v; ssSet('cccn-sel', v === null ? '__all__' : v); }
439
+ // \u30BD\u30FC\u30B9\u5207\u66FF\u3002\u30D5\u30A3\u30EB\u30BF\u5F8C\u306B\u9078\u629E\u4E2D\u30D0\u30B1\u30C3\u30C8\u304C\u6D88\u3048\u305F\u3089\u901A\u7B97\u3078\u5012\u3059(\u7A7A\u8868\u793A\u306E\u8AA4\u89E3\u9632\u6B62\u30FBgran \u30C8\u30B0\u30EB\u3068\u540C\u3058\u6D41\u5100)\u3002
440
+ function setSrc(s){
441
+ SRC = s; ssSet('cccn-src', s);
442
+ if(SEL !== null){
443
+ var present = false, bk = buildBuckets(GRAN);
444
+ for(var i=0;i<bk.length;i++){ if(bk[i].key === SEL){ present = true; break; } }
445
+ if(!present) setSel(null);
446
+ }
447
+ }
426
448
  var lastRenderedGran = null; // \u76F4\u524D\u306B\u63CF\u753B\u3057\u305F\u7C92\u5EA6\u3002\u5909\u308F\u3063\u305F\u3068\u304D(=\u30D0\u30B1\u30C3\u30C8\u6570\u304C\u5909\u308F\u308B\u3068\u304D)\u3060\u3051\u53F3\u7AEF\u3078\u5BC4\u305B\u308B\u3002
427
449
 
428
450
  function buildBuckets(gran){
451
+ var src = activeTurns();
429
452
  var map = {}; var keys = [];
430
- for(var i=0;i<turns.length;i++){
431
- var tn = turns[i];
453
+ for(var i=0;i<src.length;i++){
454
+ var tn = src[i];
432
455
  var k = bucketKey(tn.t, gran);
433
456
  if(k === null) continue;
434
457
  var b = map[k];
@@ -452,8 +475,9 @@ var APP_JS = `<script>
452
475
  }
453
476
 
454
477
  function turnsInSelection(){
455
- if(SEL === null) return turns;
456
- return turns.filter(function(tn){ return bucketKey(tn.t, GRAN) === SEL; });
478
+ var src = activeTurns();
479
+ if(SEL === null) return src;
480
+ return src.filter(function(tn){ return bucketKey(tn.t, GRAN) === SEL; });
457
481
  }
458
482
 
459
483
  // ---- SVG \u30D8\u30EB\u30D1\u30FC ----
@@ -726,6 +750,12 @@ var APP_JS = `<script>
726
750
  sub.textContent = ' ' + mk + (mk === curMonth ? '(\u4ECA\u6708)' : '');
727
751
  h.appendChild(sub);
728
752
  budgetEl.appendChild(h);
753
+ // \u4E88\u7B97\u30AB\u30FC\u30C9\u306F\u5E38\u306B\u5168\u30BD\u30FC\u30B9\u5408\u7B97(\u30BD\u30FC\u30B9\u30D5\u30A3\u30EB\u30BF\u975E\u9023\u52D5)\u3002Codex \u304C\u3042\u308B\u6642\u3060\u3051\u305D\u306E\u65E8\u3092\u660E\u8A18\u3059\u308B\u3002
754
+ if(HAS_CODEX){
755
+ var srcNote = document.createElement('p'); srcNote.className = 'note';
756
+ srcNote.textContent = '\u5168\u30BD\u30FC\u30B9\u5408\u7B97 / all sources';
757
+ budgetEl.appendChild(srcNote);
758
+ }
729
759
 
730
760
  var bar = document.createElement('div'); bar.className = 'budget-bar';
731
761
  var fill = document.createElement('div'); fill.className = 'budget-fill lvl-' + level;
@@ -746,6 +776,55 @@ var APP_JS = `<script>
746
776
  budgetEl.appendChild(foot);
747
777
  }
748
778
 
779
+ // ---- KPI(\u4ECA\u65E5/\u4ECA\u9031/\u4ECA\u6708/\u901A\u7B97)+ \u30D2\u30FC\u30ED\u30FC(\u901A\u7B97\u30D0\u30CA\u30FC)----
780
+ // \u30BD\u30FC\u30B9\u30D5\u30A3\u30EB\u30BF\u9023\u52D5\u3002Codex \u7121\u3057\u306A\u3089\u30B5\u30FC\u30D0\u63CF\u753B\u306E\u307E\u307E(\u65E2\u5B58\u6319\u52D5\u30921px\u3082\u5909\u3048\u306A\u3044\u305F\u3081\u4F55\u3082\u3057\u306A\u3044)\u3002
781
+ // \u30AB\u30FC\u30C9\u306F .kpi \u5185\u306E .stat \u3092\u9806\u306B today / week / month / all \u3068\u3057\u3066\u66F4\u65B0\u3059\u308B(\u30B5\u30FC\u30D0\u63CF\u753B\u9806\u306B\u4E00\u81F4)\u3002
782
+ // \u30D2\u30FC\u30ED\u30FC\u306F\u30D5\u30A3\u30EB\u30BF\u5F8C\u306E\u5168\u671F\u9593\u5408\u8A08\u306B\u5DEE\u3057\u66FF\u3048\u3001SA \u5185\u8A33\u884C\u306F SA \u5408\u8A08\u30BC\u30ED\u306A\u3089\u884C\u3054\u3068\u96A0\u3059
783
+ // (Codex \u30EC\u30B3\u30FC\u30C9\u306F SA \u3092\u6301\u305F\u306A\u3044\u305F\u3081 Codex \u30D5\u30A3\u30EB\u30BF\u6642\u306F\u5FC5\u7136\u7684\u306B\u975E\u8868\u793A)\u3002\u884C\u306E\u6587\u8A00\u306F\u30B5\u30FC\u30D0\u63CF\u753B\u306E
784
+ // \u63A5\u982D\u8F9E($ \u306E\u624B\u524D\u307E\u3067)\u3092\u518D\u5229\u7528\u3057\u3066\u6570\u5024\u90E8\u5206\u3060\u3051\u7D44\u307F\u76F4\u3059(APP_JS \u306B\u8868\u793A\u6587\u8A00\u3092\u91CD\u8907\u3055\u305B\u306A\u3044)\u3002
785
+ var kpiCards = document.querySelectorAll('.kpi .stat');
786
+ var heroValueEl = document.querySelector('.hero .hero-value');
787
+ var heroMetaEls = document.querySelectorAll('.hero .hero-meta');
788
+ var heroSaEl = heroMetaEls.length > 1 ? heroMetaEls[1] : null; // SA \u884C(SA \u3042\u308A\u306E\u3068\u304D\u3060\u3051\u30B5\u30FC\u30D0\u304C\u63CF\u753B)
789
+ var heroSaPrefix = heroSaEl ? ((heroSaEl.textContent || '').split('$')[0]) : '';
790
+ function setKpiCard(card, tot){
791
+ if(!card) return;
792
+ var v = card.querySelector('.stat-value');
793
+ var m = card.querySelector('.stat-meta');
794
+ if(v) v.textContent = formatUSD(tot.usd);
795
+ if(m) m.textContent = formatJPY(tot.jpy) + ' \xB7 ' + tot.turns + ' \u30BF\u30FC\u30F3';
796
+ }
797
+ function renderKpis(){
798
+ if(!HAS_CODEX) return;
799
+ var src = activeTurns();
800
+ var now = new Date();
801
+ var y = now.getFullYear(), mo = now.getMonth(), d = now.getDate();
802
+ var weekCutoff = Date.now() - 7*86400000;
803
+ 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};
804
+ var saUsd = 0, saJpy = 0;
805
+ for(var i=0;i<src.length;i++){
806
+ var tn = src[i], bs = tn.bs || {}, fx = tn.fx || 0, u = 0;
807
+ for(var s in bs){ if(bs.hasOwnProperty(s)) u += bs[s]; }
808
+ var j = u * fx;
809
+ if(tn.sa){ var sau = u - (tn.um || 0); saUsd += sau; saJpy += sau * fx; } // SA \u5206 = \u7DCF\u984D \u2212 \u30E1\u30A4\u30F3
810
+ all.usd += u; all.jpy += j; all.turns++;
811
+ var dt = new Date(tn.t);
812
+ if(isNaN(dt.getTime())) continue;
813
+ if(dt.getFullYear()===y && dt.getMonth()===mo && dt.getDate()===d){ today.usd+=u; today.jpy+=j; today.turns++; }
814
+ if(tn.t >= weekCutoff){ week.usd+=u; week.jpy+=j; week.turns++; }
815
+ if(dt.getFullYear()===y && dt.getMonth()===mo){ month.usd+=u; month.jpy+=j; month.turns++; }
816
+ }
817
+ if(kpiCards.length >= 4){
818
+ setKpiCard(kpiCards[0], today); setKpiCard(kpiCards[1], week); setKpiCard(kpiCards[2], month); setKpiCard(kpiCards[3], all);
819
+ }
820
+ if(heroValueEl) heroValueEl.textContent = formatUSD(all.usd);
821
+ if(heroMetaEls.length > 0) heroMetaEls[0].textContent = formatJPY(all.jpy) + ' \xB7 ' + all.turns + ' \u30BF\u30FC\u30F3';
822
+ if(heroSaEl){
823
+ heroSaEl.hidden = !(saUsd > 0);
824
+ if(saUsd > 0) heroSaEl.textContent = heroSaPrefix + formatUSD(saUsd) + '(' + formatJPY(saJpy) + ')';
825
+ }
826
+ }
827
+
749
828
  // ---- \u30BF\u30FC\u30F3\u5C65\u6B74 ----
750
829
  // \u5168\u671F\u9593\u3060\u3068\u6570\u5343\u884C\u306B\u306A\u308A\u30DA\u30FC\u30B8\u304C\u6975\u7AEF\u306B\u9577\u304F\u30FB\u91CD\u304F\u306A\u308B\u305F\u3081\u3001DOM \u63CF\u753B\u306F\u6700\u5927 HIST_CAP \u4EF6\u306B\u6291\u3048\u308B\u3002
751
830
  // \u691C\u7D22\u306F\u9078\u629E\u4E2D\u306E\u5168\u30BF\u30FC\u30F3\u3092\u5BFE\u8C61\u306B\u3057\u3001\u4E00\u81F4\u4EF6\u6570\u3092\u4EF6\u6570\u8868\u793A\u306B\u51FA\u3059(\u5148\u982D HIST_CAP \u4EF6\u3060\u3051\u63CF\u753B)\u3002
@@ -765,7 +844,14 @@ var APP_JS = `<script>
765
844
  function cell(text, cls){ var td = document.createElement('td'); if(cls) td.className = cls; td.textContent = text; tr.appendChild(td); return td; }
766
845
  cell(t.ts || '', 'c-time');
767
846
  cell(t.p || '', 'c-proj');
768
- cell(t.md || '', 'c-model');
847
+ var mcell = cell(t.md || '', 'c-model');
848
+ // Codex \u7531\u6765\u306F\u30E2\u30C7\u30EB\u540D\u306E\u96A3\u306B\u5C0F\u3055\u306A Codex \u30D0\u30C3\u30B8\u3002\u52D5\u7684\u5024\u306F\u5165\u308C\u305A\u9759\u7684\u30C6\u30AD\u30B9\u30C8\u3092 textContent \u3067\u633F\u5165\u3002
849
+ if(t.sc === 'codex'){
850
+ mcell.appendChild(document.createTextNode(' '));
851
+ var badge = document.createElement('span'); badge.className = 'src-badge';
852
+ badge.textContent = 'Codex';
853
+ mcell.appendChild(badge);
854
+ }
769
855
  cell(t.ti || '', 'c-num');
770
856
  cell(t.to || '', 'c-num');
771
857
  cell(formatUSD(t.um || 0), 'c-num');
@@ -832,6 +918,8 @@ var APP_JS = `<script>
832
918
  for(var i=0;i<gbtns.length;i++){ gbtns[i].classList.toggle('active', gbtns[i].getAttribute('data-gran') === GRAN); }
833
919
  var allBtn = document.getElementById('cccn-all');
834
920
  if(allBtn) allBtn.classList.toggle('active', SEL === null);
921
+ var sbtns = document.querySelectorAll('[data-src]');
922
+ for(var k=0;k<sbtns.length;k++){ sbtns[k].classList.toggle('active', sbtns[k].getAttribute('data-src') === SRC); }
835
923
  }
836
924
 
837
925
  // ---- \u5168\u4F53\u30EC\u30F3\u30C0\u30EA\u30F3\u30B0 ----
@@ -839,6 +927,7 @@ var APP_JS = `<script>
839
927
  var sub = turnsInSelection();
840
928
  renderChart();
841
929
  renderBudget();
930
+ renderKpis();
842
931
  renderByModel(sub);
843
932
  renderByProject(sub);
844
933
  renderHistory(sub);
@@ -853,6 +942,10 @@ var APP_JS = `<script>
853
942
  }
854
943
  var allBtn = document.getElementById('cccn-all');
855
944
  if(allBtn) allBtn.addEventListener('click', function(){ setSel(null); render(); });
945
+ var srcBtns = document.querySelectorAll('[data-src]');
946
+ for(var sbi=0;sbi<srcBtns.length;sbi++){
947
+ (function(btn){ btn.addEventListener('click', function(){ setSrc(btn.getAttribute('data-src')); render(); }); })(srcBtns[sbi]);
948
+ }
856
949
  if(search){
857
950
  var saved = ssGet('cccn-search');
858
951
  if(saved !== null) search.value = saved;
@@ -879,13 +972,13 @@ function renderLegend(slots) {
879
972
  const items = slots.map((m) => `<span class="legend-item"><span class="swatch k${m.slot}"></span>${esc(m.name)}</span>`).join("");
880
973
  return `<div class="legend">${items}</div>`;
881
974
  }
882
- function budgetCard(budgetUSD, month, fallbackRate) {
975
+ function budgetCard(budgetUSD, month, fallbackRate, hasCodex) {
883
976
  if (!(budgetUSD > 0)) return "";
884
977
  const pct = month.usd / budgetUSD * 100;
885
978
  const width = Math.max(0, Math.min(100, pct));
886
979
  const level = pct >= 100 ? "over" : pct >= 70 ? "warn" : "ok";
887
980
  const budgetJpy = budgetUSD * fallbackRate;
888
- return `<section class="card" id="cccn-budget"><h2>\u6708\u4E88\u7B97 / Monthly budget<span class="stat-sub"> \u4ECA\u6708(\u66A6\u6708)</span></h2><div class="budget-bar"><div class="budget-fill lvl-${level}" style="width:${width.toFixed(1)}%"></div></div><div class="budget-foot"><span>\u4ECA\u6708 <b>${esc(formatUSD(month.usd))}</b> / ${esc(formatUSD(budgetUSD))} <span class="muted">(${esc(formatJPY(month.jpy))} / ${esc(formatJPY(budgetJpy))})</span></span><span class="budget-pct lvl-${level}">${pct.toFixed(1)}% used</span></div></section>`;
981
+ return `<section class="card" id="cccn-budget"><h2>\u6708\u4E88\u7B97 / Monthly budget<span class="stat-sub"> \u4ECA\u6708(\u66A6\u6708)</span></h2>` + (hasCodex ? `<p class="note">\u5168\u30BD\u30FC\u30B9\u5408\u7B97 / all sources</p>` : "") + `<div class="budget-bar"><div class="budget-fill lvl-${level}" style="width:${width.toFixed(1)}%"></div></div><div class="budget-foot"><span>\u4ECA\u6708 <b>${esc(formatUSD(month.usd))}</b> / ${esc(formatUSD(budgetUSD))} <span class="muted">(${esc(formatJPY(month.jpy))} / ${esc(formatJPY(budgetJpy))})</span></span><span class="budget-pct lvl-${level}">${pct.toFixed(1)}% used</span></div></section>`;
889
982
  }
890
983
  function escapeJsonForScript(json) {
891
984
  return json.replace(/</g, "\\u003c").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
@@ -901,6 +994,7 @@ function renderDashboard(turns, opts) {
901
994
  const turnsEmbed = turns.map((rec) => buildTurnEmbed(rec, map));
902
995
  const anyTruncated = turnsEmbed.some((t) => t.tr);
903
996
  const anySub = turns.some((r) => r.subagents);
997
+ const anyCodex = turns.some((r) => r.source === "codex");
904
998
  let subUsd = 0;
905
999
  let subJpy = 0;
906
1000
  for (const r of turns) {
@@ -929,7 +1023,8 @@ function renderDashboard(turns, opts) {
929
1023
  const dataJson = escapeJsonForScript(JSON.stringify(embed));
930
1024
  const reloadSec = Number.isFinite(opts.autoReloadSec) && opts.autoReloadSec > 0 ? Math.floor(opts.autoReloadSec) : 0;
931
1025
  const refreshMeta = reloadSec > 0 ? `<meta http-equiv="refresh" content="${reloadSec}">` : "";
932
- const autoUpdateFoot = reloadSec > 0 ? `<div class="foot">\u7D04 ${reloadSec} \u79D2\u3054\u3068\u306B\u81EA\u52D5\u66F4\u65B0(\u6700\u65B0\u5316\u306F Claude Code \u306E\u5FDC\u7B54\u5B8C\u4E86\u6642)</div>` : "";
1026
+ const updateTrigger = anyCodex ? "Claude Code / Codex \u306E\u5FDC\u7B54\u5B8C\u4E86\u6642" : "Claude Code \u306E\u5FDC\u7B54\u5B8C\u4E86\u6642";
1027
+ const autoUpdateFoot = reloadSec > 0 ? `<div class="foot">\u7D04 ${reloadSec} \u79D2\u3054\u3068\u306B\u81EA\u52D5\u66F4\u65B0(\u6700\u65B0\u5316\u306F ${updateTrigger})</div>` : "";
933
1028
  const head = `<!doctype html><html lang="ja"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><meta name="color-scheme" content="light dark">` + refreshMeta + `<title>ccc-notifier \u30C0\u30C3\u30B7\u30E5\u30DC\u30FC\u30C9</title>` + STYLE + `</head><body><div class="wrap">`;
934
1029
  const foot = autoUpdateFoot + `<div class="foot">ccc-notifier v${esc(version)} \xB7 \u30C7\u30FC\u30BF\u306F\u30ED\u30FC\u30AB\u30EB\u306E\u307F / all data stays local</div></div><div id="cccn-tip" class="cccn-tip" hidden></div><script id="cccn-data" type="application/json">${dataJson}</script>` + APP_JS + `</body></html>`;
935
1030
  if (turns.length === 0) {
@@ -939,8 +1034,9 @@ function renderDashboard(turns, opts) {
939
1034
  }
940
1035
  const header = `<div class="head"><div><h1>ccc-notifier \u30C0\u30C3\u30B7\u30E5\u30DC\u30FC\u30C9</h1><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">\u901A\u7B97 / Total</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>`;
941
1036
  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("\u901A\u7B97", "\u5168\u671F\u9593", kpi.all) + `</div>`;
942
- const budgetSection = budgetCard(budgetUSD, kpi.month, cfg.fx.fallbackRate);
943
- 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\u901A\u7B97\u300D\u3067\u5168\u671F\u9593\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">\u901A\u7B97</button><span id="cccn-sel-label" class="sel-label"></span></div>` + renderLegend(map.slots) + `<div class="chart-scroll"><div id="cccn-chart"></div></div></section>`;
1037
+ const budgetSection = budgetCard(budgetUSD, kpi.month, cfg.fx.fallbackRate, anyCodex);
1038
+ 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>` : "";
1039
+ 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\u901A\u7B97\u300D\u3067\u5168\u671F\u9593\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">\u901A\u7B97</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>`;
944
1040
  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\u901A\u7B97)\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>`;
945
1041
  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";
946
1042
  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>`;
package/dist/cli.js CHANGED
@@ -1,36 +1,41 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  fmtMuteUntil
4
- } from "./chunk-KUJZYZSG.js";
4
+ } from "./chunk-4RZ6OGTD.js";
5
5
  import {
6
+ codexHooksFile,
6
7
  matchesMarker
7
- } from "./chunk-L5MLVTA2.js";
8
+ } from "./chunk-7KVOQ4UZ.js";
8
9
  import {
9
10
  notifyOS,
10
11
  notifySlack,
11
12
  selectNotifyBackend
12
- } from "./chunk-4JNZ7BHO.js";
13
+ } from "./chunk-NV5UOHJA.js";
13
14
  import {
14
15
  isWSL
15
16
  } from "./chunk-DGXUSPS4.js";
17
+ import {
18
+ codexHome,
19
+ detectCodex
20
+ } from "./chunk-HTYUYKFW.js";
16
21
  import {
17
22
  aggregateNewTurn,
18
23
  computeCost,
19
24
  getUsdJpy,
20
25
  loadPriceTable
21
- } from "./chunk-OEG3AVU6.js";
26
+ } from "./chunk-LHKBGA5K.js";
22
27
  import {
23
28
  formatJPY,
24
29
  formatTokens,
25
30
  formatUSD
26
- } from "./chunk-TBFKGFZX.js";
31
+ } from "./chunk-J5QAYTFE.js";
27
32
  import {
28
33
  isMuted,
29
34
  paths,
30
35
  readConfig,
31
36
  readMuteState,
32
37
  readTurns
33
- } from "./chunk-IIYMGLV4.js";
38
+ } from "./chunk-ECADO26T.js";
34
39
 
35
40
  // src/cli.ts
36
41
  import { realpathSync } from "fs";
@@ -156,6 +161,50 @@ async function checkHookRegistration() {
156
161
  }
157
162
  return true;
158
163
  }
164
+ async function checkCodex() {
165
+ if (!detectCodex()) {
166
+ log("ok", "Codex CLI \u306F\u672A\u691C\u51FA\u3067\u3059(\u672A\u4F7F\u7528\u306A\u3089\u554F\u984C\u3042\u308A\u307E\u305B\u3093)");
167
+ return true;
168
+ }
169
+ const hooksFile = codexHooksFile();
170
+ const matchedCommands = [];
171
+ if (existsSync(hooksFile)) {
172
+ try {
173
+ const raw = await readFile(hooksFile, "utf8");
174
+ const parsed = JSON.parse(raw);
175
+ if (isRecord(parsed)) {
176
+ const hooks = parsed.hooks;
177
+ const stopEntries = isRecord(hooks) ? hooks.Stop : void 0;
178
+ if (Array.isArray(stopEntries)) {
179
+ for (const entry of stopEntries) {
180
+ if (!isRecord(entry)) continue;
181
+ const innerHooks = entry.hooks;
182
+ if (!Array.isArray(innerHooks)) continue;
183
+ for (const h of innerHooks) {
184
+ if (isRecord(h) && typeof h.command === "string" && matchesMarker(h.command)) {
185
+ matchedCommands.push(h.command);
186
+ }
187
+ }
188
+ }
189
+ }
190
+ }
191
+ } catch {
192
+ }
193
+ }
194
+ if (matchedCommands.length > 0) {
195
+ log("ok", `Codex \u306E Stop hook \u304C\u767B\u9332\u3055\u308C\u3066\u3044\u307E\u3059: ${matchedCommands.join(" / ")}`);
196
+ log("ok", "codex \u5074\u3067 hook \u3092\u627F\u8A8D\u6E08\u307F\u304B\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044(\u672A\u627F\u8A8D\u3060\u3068\u901A\u77E5\u3055\u308C\u307E\u305B\u3093)");
197
+ } else {
198
+ log("warn", "Codex \u3092\u691C\u51FA\u3057\u307E\u3057\u305F\u304C hook \u304C\u672A\u767B\u9332\u3067\u3059\u3002init --codex \u3067\u767B\u9332\u3067\u304D\u307E\u3059");
199
+ }
200
+ const sessionsDir = join(codexHome(), "sessions");
201
+ if (existsSync(sessionsDir)) {
202
+ log("ok", `Codex \u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u3092\u78BA\u8A8D\u3057\u307E\u3057\u305F: ${sessionsDir}`);
203
+ } else {
204
+ log("ok", `Codex \u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u306F\u307E\u3060\u3042\u308A\u307E\u305B\u3093: ${sessionsDir}(\u30BB\u30C3\u30B7\u30E7\u30F3\u672A\u4F5C\u6210\u306E\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059)`);
205
+ }
206
+ return true;
207
+ }
159
208
  function readDirSafe(dir) {
160
209
  try {
161
210
  return readdirSync(dir, { withFileTypes: true });
@@ -258,6 +307,11 @@ async function checkFx(cfg) {
258
307
  }
259
308
  async function checkNotification(cfg) {
260
309
  try {
310
+ if (!cfg.notify.os && !cfg.notify.slack) {
311
+ log("ok", "\u901A\u77E5\u30C1\u30E3\u30CD\u30EB\u306F\u3059\u3079\u3066\u7121\u52B9\u3067\u3059(\u901A\u77E5\u306A\u3057\u30FB\u30C0\u30C3\u30B7\u30E5\u30DC\u30FC\u30C9\u306E\u307F\u30E2\u30FC\u30C9)\u3002\u8A18\u9332\u3068\u30C0\u30C3\u30B7\u30E5\u30DC\u30FC\u30C9\u306F\u52D5\u4F5C\u3057\u307E\u3059");
312
+ log("ok", "\u901A\u77E5\u3092\u6709\u52B9\u306B\u3059\u308B\u306B\u306F npx ccc-notifier init \u3092\u518D\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044");
313
+ return true;
314
+ }
261
315
  if (isMuted()) {
262
316
  const until = readMuteState()?.until;
263
317
  log(
@@ -302,9 +356,6 @@ async function checkNotification(cfg) {
302
356
  `Slack \u306E\u30C6\u30B9\u30C8\u901A\u77E5\u3092\u9001\u4FE1\u3057\u307E\u3057\u305F${dryRun ? dryHint : "(Slack \u30C1\u30E3\u30F3\u30CD\u30EB\u306B\u5C4A\u3044\u305F\u304B\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u5C4A\u304B\u306A\u3044\u5834\u5408\u306F error.log \u3092\u53C2\u7167)"}`
303
357
  );
304
358
  }
305
- if (!cfg.notify.os && !cfg.notify.slack) {
306
- log("warn", "OS\u30FBSlack \u3068\u3082\u7121\u52B9\u306A\u305F\u3081\u3001\u30C6\u30B9\u30C8\u901A\u77E5\u306F\u9001\u4FE1\u3057\u3066\u3044\u307E\u305B\u3093");
307
- }
308
359
  return true;
309
360
  } catch (err) {
310
361
  log("warn", `\u30C6\u30B9\u30C8\u901A\u77E5\u306E\u9001\u4FE1\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${errMessage(err)}`);
@@ -337,6 +388,7 @@ async function checkRecentSessionTotal(latestTranscript) {
337
388
  async function runDoctor() {
338
389
  const results = [];
339
390
  results.push(await safeRun("settings.json", () => checkHookRegistration()));
391
+ results.push(await safeRun("codex", () => checkCodex()));
340
392
  let latestTranscript = null;
341
393
  results.push(
342
394
  await safeRun("projects", async () => {
@@ -629,19 +681,20 @@ async function main(argv) {
629
681
  switch (cmd) {
630
682
  case "track": {
631
683
  const text = await readStdin();
684
+ const codex = rest.includes("--codex");
632
685
  try {
633
- const trackMod = await import("./track-PBHTVZ3L.js");
634
- await trackMod.runTrack(text);
686
+ const trackMod = await import("./track-PXFSGMGL.js");
687
+ await trackMod.runTrack(text, { codex });
635
688
  } catch {
636
689
  }
637
690
  return 0;
638
691
  }
639
692
  case "init": {
640
- const { runInit } = await import("./setup-RT62VO37.js");
693
+ const { runInit } = await import("./setup-PTO5N46B.js");
641
694
  return await runInit(rest);
642
695
  }
643
696
  case "uninstall": {
644
- const { runUninstall } = await import("./setup-RT62VO37.js");
697
+ const { runUninstall } = await import("./setup-PTO5N46B.js");
645
698
  return await runUninstall(rest);
646
699
  }
647
700
  case "doctor":
@@ -649,27 +702,27 @@ async function main(argv) {
649
702
  case "report":
650
703
  return await runReport(rest);
651
704
  case "dashboard": {
652
- const { runDashboard } = await import("./dashboard-LKK6NOBN.js");
705
+ const { runDashboard } = await import("./dashboard-TH3NOVT3.js");
653
706
  return await runDashboard(rest);
654
707
  }
655
708
  case "sweep": {
656
- const { runSweep } = await import("./sweep-XKKODGMA.js");
709
+ const { runSweep } = await import("./sweep-25EZAOGC.js");
657
710
  return await runSweep(rest);
658
711
  }
659
712
  case "history": {
660
- const { runHistory } = await import("./history-MWFHO5VR.js");
713
+ const { runHistory } = await import("./history-DLFYQJFM.js");
661
714
  return await runHistory(rest);
662
715
  }
663
716
  case "budget": {
664
- const { runBudget } = await import("./budget-5H2GQRXH.js");
717
+ const { runBudget } = await import("./budget-KKTFYAXH.js");
665
718
  return runBudget(rest);
666
719
  }
667
720
  case "mute": {
668
- const { runMute } = await import("./mute-2JW3NQXN.js");
721
+ const { runMute } = await import("./mute-EPLYH67P.js");
669
722
  return runMute(rest);
670
723
  }
671
724
  case "unmute": {
672
- const { runUnmute } = await import("./mute-2JW3NQXN.js");
725
+ const { runUnmute } = await import("./mute-EPLYH67P.js");
673
726
  return runUnmute();
674
727
  }
675
728
  case "--version":
@@ -3,10 +3,10 @@ import {
3
3
  browserOpenPlan,
4
4
  runDashboard,
5
5
  writeDashboardHtml
6
- } from "./chunk-64N5SGTT.js";
6
+ } from "./chunk-QX5KIRSU.js";
7
7
  import "./chunk-DGXUSPS4.js";
8
- import "./chunk-TBFKGFZX.js";
9
- import "./chunk-IIYMGLV4.js";
8
+ import "./chunk-J5QAYTFE.js";
9
+ import "./chunk-ECADO26T.js";
10
10
  export {
11
11
  browserOpenPlan,
12
12
  runDashboard,
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  paths
4
- } from "./chunk-IIYMGLV4.js";
4
+ } from "./chunk-ECADO26T.js";
5
5
 
6
6
  // src/history.ts
7
7
  import { existsSync, readFileSync, writeFileSync, renameSync, rmSync } from "fs";
@@ -3,8 +3,8 @@ import {
3
3
  fmtMuteUntil,
4
4
  runMute,
5
5
  runUnmute
6
- } from "./chunk-KUJZYZSG.js";
7
- import "./chunk-IIYMGLV4.js";
6
+ } from "./chunk-4RZ6OGTD.js";
7
+ import "./chunk-ECADO26T.js";
8
8
  export {
9
9
  fmtMuteUntil,
10
10
  runMute,
@@ -3,11 +3,12 @@ import {
3
3
  matchesMarker,
4
4
  runInit,
5
5
  runUninstall
6
- } from "./chunk-L5MLVTA2.js";
7
- import "./chunk-4JNZ7BHO.js";
6
+ } from "./chunk-7KVOQ4UZ.js";
7
+ import "./chunk-NV5UOHJA.js";
8
8
  import "./chunk-DGXUSPS4.js";
9
- import "./chunk-TBFKGFZX.js";
10
- import "./chunk-IIYMGLV4.js";
9
+ import "./chunk-HTYUYKFW.js";
10
+ import "./chunk-J5QAYTFE.js";
11
+ import "./chunk-ECADO26T.js";
11
12
  export {
12
13
  matchesMarker,
13
14
  runInit,