ccc-notifier 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -12
- package/dist/{budget-5H2GQRXH.js → budget-EHWPEYXY.js} +2 -2
- package/dist/{chunk-IIYMGLV4.js → chunk-26CISNOE.js} +19 -4
- package/dist/{chunk-64N5SGTT.js → chunk-2VPBBIDW.js} +267 -57
- package/dist/{chunk-OXMHOVUL.js → chunk-5LHZOZZO.js} +228 -34
- package/dist/chunk-CKVK3UCA.js +357 -0
- package/dist/chunk-HTYUYKFW.js +21 -0
- package/dist/{chunk-TBFKGFZX.js → chunk-J5QAYTFE.js} +3 -0
- package/dist/chunk-J6Y3RMMC.js +172 -0
- package/dist/{chunk-OEG3AVU6.js → chunk-LHKBGA5K.js} +27 -1
- package/dist/{chunk-4JNZ7BHO.js → chunk-NV5UOHJA.js} +1 -1
- package/dist/{chunk-KUJZYZSG.js → chunk-TUZVISLD.js} +1 -1
- package/dist/chunk-ZHIZZ6V5.js +92 -0
- package/dist/cli.js +68 -17
- package/dist/{dashboard-LKK6NOBN.js → dashboard-GYKABTTN.js} +5 -3
- package/dist/history-6OUJLXJT.js +162 -0
- package/dist/{mute-2JW3NQXN.js → mute-GBDNN5PB.js} +2 -2
- package/dist/{setup-IXPUR4CM.js → setup-W3CSTHJC.js} +5 -4
- package/dist/{sweep-XKKODGMA.js → sweep-IBSV4LRD.js} +224 -80
- package/dist/track-QT2U3E2R.js +235 -0
- package/package.json +5 -2
- package/dist/chunk-TB5A7U7G.js +0 -82
- package/dist/history-MWFHO5VR.js +0 -114
- package/dist/track-76Q5CZM5.js +0 -168
|
@@ -2,65 +2,113 @@
|
|
|
2
2
|
import {
|
|
3
3
|
isWSL
|
|
4
4
|
} from "./chunk-DGXUSPS4.js";
|
|
5
|
+
import {
|
|
6
|
+
makeFullDashboardState,
|
|
7
|
+
writeFullDashboardStateAtomic
|
|
8
|
+
} from "./chunk-ZHIZZ6V5.js";
|
|
9
|
+
import {
|
|
10
|
+
waitForDataLock
|
|
11
|
+
} from "./chunk-J6Y3RMMC.js";
|
|
5
12
|
import {
|
|
6
13
|
formatJPY,
|
|
7
14
|
formatTokens,
|
|
8
15
|
formatUSD,
|
|
9
16
|
modelDisplayName
|
|
10
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-J5QAYTFE.js";
|
|
11
18
|
import {
|
|
12
19
|
paths,
|
|
13
20
|
readConfig,
|
|
14
21
|
readTurns
|
|
15
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-26CISNOE.js";
|
|
16
23
|
|
|
17
24
|
// src/dashboard.ts
|
|
18
25
|
import { spawn, spawnSync } from "child_process";
|
|
26
|
+
import { randomUUID } from "crypto";
|
|
19
27
|
import { createRequire } from "module";
|
|
20
|
-
import { mkdirSync, writeFileSync } from "fs";
|
|
21
|
-
import { basename, dirname
|
|
28
|
+
import { existsSync, mkdirSync, renameSync, rmSync, writeFileSync } from "fs";
|
|
29
|
+
import { basename, dirname } from "path";
|
|
22
30
|
var PROMPT_MAX = 1e4;
|
|
23
31
|
var PROMPT_TRUNC_MARK = "\u2026(\u4EE5\u4E0B\u7565)";
|
|
32
|
+
function writeAtomic(file, content) {
|
|
33
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
34
|
+
const tmp = `${file}.${process.pid}.${randomUUID()}.tmp`;
|
|
35
|
+
try {
|
|
36
|
+
writeFileSync(tmp, content, "utf8");
|
|
37
|
+
renameSync(tmp, file);
|
|
38
|
+
} finally {
|
|
39
|
+
rmSync(tmp, { force: true });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function ensurePeerPlaceholder(variant) {
|
|
43
|
+
if (variant === "custom") return;
|
|
44
|
+
const p = paths();
|
|
45
|
+
const peer = variant === "recent" ? p.fullDashboardFile : p.recentDashboardFile;
|
|
46
|
+
if (existsSync(peer)) return;
|
|
47
|
+
const peerIsFull = variant === "recent";
|
|
48
|
+
const back = peerIsFull ? "report.html" : "report-all.html";
|
|
49
|
+
const command = peerIsFull ? "ccc-notifier dashboard --all" : "ccc-notifier dashboard";
|
|
50
|
+
const label = peerIsFull ? "\u5168\u5C65\u6B74\u7248" : "\u76F4\u8FD1\u7248";
|
|
51
|
+
const html = `<!doctype html><html lang="ja"><head><meta charset="utf-8"><meta name="cccn-placeholder" content="true"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${label}\u306F\u672A\u751F\u6210\u3067\u3059</title></head><body><main><h1>${label}\u306F\u307E\u3060\u751F\u6210\u3055\u308C\u3066\u3044\u307E\u305B\u3093</h1><p><code>${command}</code> \u3092\u5B9F\u884C\u3059\u308B\u3068\u751F\u6210\u3067\u304D\u307E\u3059\u3002</p><p><a href="${back}">\u751F\u6210\u6E08\u307F\u306E\u30C0\u30C3\u30B7\u30E5\u30DC\u30FC\u30C9\u3078\u623B\u308B</a></p></main></body></html>`;
|
|
52
|
+
writeAtomic(peer, html);
|
|
53
|
+
}
|
|
24
54
|
function parseDays(value) {
|
|
25
|
-
if (value === void 0)
|
|
26
|
-
const parsed = Number
|
|
27
|
-
|
|
55
|
+
if (value === void 0 || !/^\d+$/.test(value)) throw new Error("--days \u306B\u306F\u6B63\u306E\u6574\u6570\u304C\u5FC5\u8981\u3067\u3059");
|
|
56
|
+
const parsed = Number(value);
|
|
57
|
+
if (!Number.isSafeInteger(parsed) || parsed <= 0) throw new Error("--days \u306B\u306F\u6B63\u306E\u6574\u6570\u304C\u5FC5\u8981\u3067\u3059");
|
|
58
|
+
return parsed;
|
|
28
59
|
}
|
|
29
|
-
function parseRefresh(value
|
|
30
|
-
if (value === void 0)
|
|
31
|
-
|
|
32
|
-
|
|
60
|
+
function parseRefresh(value) {
|
|
61
|
+
if (value === void 0 || value.startsWith("--") || !/^\d+$/.test(value)) {
|
|
62
|
+
throw new Error("--refresh \u306B\u306F0\u4EE5\u4E0A\u306E\u6574\u6570\u304C\u5FC5\u8981\u3067\u3059");
|
|
63
|
+
}
|
|
64
|
+
const parsed = Number(value);
|
|
65
|
+
if (!Number.isSafeInteger(parsed) || parsed < 0) throw new Error("--refresh \u306B\u306F0\u4EE5\u4E0A\u306E\u6574\u6570\u304C\u5FC5\u8981\u3067\u3059");
|
|
66
|
+
return parsed;
|
|
33
67
|
}
|
|
34
68
|
function parseArgs(argv) {
|
|
35
|
-
let
|
|
69
|
+
let daysValue = null;
|
|
70
|
+
let daysSpecified = false;
|
|
71
|
+
let all = false;
|
|
36
72
|
let open = true;
|
|
37
73
|
let out = null;
|
|
38
|
-
const
|
|
74
|
+
const cfg = readConfig().dashboard;
|
|
75
|
+
const cfgReloadSec = cfg.autoReloadSec;
|
|
39
76
|
let autoReloadSec = cfgReloadSec;
|
|
40
77
|
for (let i = 0; i < argv.length; i++) {
|
|
41
78
|
const arg = argv[i];
|
|
42
79
|
if (arg === "--no-open") {
|
|
43
80
|
open = false;
|
|
81
|
+
} else if (arg === "--all") {
|
|
82
|
+
all = true;
|
|
44
83
|
} else if (arg === "--days") {
|
|
45
|
-
|
|
84
|
+
daysSpecified = true;
|
|
85
|
+
daysValue = parseDays(argv[i + 1]);
|
|
46
86
|
i++;
|
|
47
87
|
} else if (arg.startsWith("--days=")) {
|
|
48
|
-
|
|
88
|
+
daysSpecified = true;
|
|
89
|
+
daysValue = parseDays(arg.slice("--days=".length));
|
|
49
90
|
} else if (arg === "--out") {
|
|
50
91
|
out = argv[i + 1] ?? null;
|
|
92
|
+
if (out === null || out.startsWith("--")) throw new Error("--out \u306B\u306F\u51FA\u529B\u30D1\u30B9\u304C\u5FC5\u8981\u3067\u3059");
|
|
51
93
|
i++;
|
|
52
94
|
} else if (arg.startsWith("--out=")) {
|
|
53
95
|
out = arg.slice("--out=".length) || null;
|
|
96
|
+
if (out === null) throw new Error("--out \u306B\u306F\u51FA\u529B\u30D1\u30B9\u304C\u5FC5\u8981\u3067\u3059");
|
|
54
97
|
} else if (arg === "--no-refresh") {
|
|
55
98
|
autoReloadSec = 0;
|
|
56
99
|
} else if (arg === "--refresh") {
|
|
57
|
-
autoReloadSec = parseRefresh(argv[i + 1]
|
|
100
|
+
autoReloadSec = parseRefresh(argv[i + 1]);
|
|
58
101
|
i++;
|
|
59
102
|
} else if (arg.startsWith("--refresh=")) {
|
|
60
|
-
autoReloadSec = parseRefresh(arg.slice("--refresh=".length)
|
|
103
|
+
autoReloadSec = parseRefresh(arg.slice("--refresh=".length));
|
|
104
|
+
} else {
|
|
105
|
+
throw new Error(`\u4E0D\u660E\u306A\u5F15\u6570\u3067\u3059 / unknown argument: ${arg}`);
|
|
61
106
|
}
|
|
62
107
|
}
|
|
63
|
-
|
|
108
|
+
if (all && daysSpecified) throw new Error("--all \u3068 --days \u306F\u540C\u6642\u306B\u6307\u5B9A\u3067\u304D\u307E\u305B\u3093");
|
|
109
|
+
const scope = all || out !== null && !daysSpecified ? "all" : "recent";
|
|
110
|
+
const days = scope === "all" ? null : daysValue ?? cfg.days;
|
|
111
|
+
return { scope, days, open, out, autoReloadSec };
|
|
64
112
|
}
|
|
65
113
|
function readVersion() {
|
|
66
114
|
try {
|
|
@@ -122,6 +170,13 @@ function truncatePrompt(raw) {
|
|
|
122
170
|
if (raw.length <= PROMPT_MAX) return { text: raw, truncated: false };
|
|
123
171
|
return { text: raw.slice(0, PROMPT_MAX) + PROMPT_TRUNC_MARK, truncated: true };
|
|
124
172
|
}
|
|
173
|
+
function filterTurnsByDays(turns, days) {
|
|
174
|
+
const cutoff = Date.now() - days * 864e5;
|
|
175
|
+
return turns.filter((rec) => {
|
|
176
|
+
const ts = Date.parse(rec.ts);
|
|
177
|
+
return Number.isFinite(ts) && ts >= cutoff;
|
|
178
|
+
});
|
|
179
|
+
}
|
|
125
180
|
function computeSlotMap(turns) {
|
|
126
181
|
const agg = /* @__PURE__ */ new Map();
|
|
127
182
|
for (const rec of turns) {
|
|
@@ -170,7 +225,7 @@ function buildTurnEmbed(rec, map) {
|
|
|
170
225
|
};
|
|
171
226
|
}
|
|
172
227
|
const ms = Date.parse(rec.ts);
|
|
173
|
-
|
|
228
|
+
const out = {
|
|
174
229
|
t: Number.isFinite(ms) ? ms : 0,
|
|
175
230
|
ts: fmtLocalDateTime(rec.ts),
|
|
176
231
|
p: basename(rec.project) || rec.project || "(unknown)",
|
|
@@ -187,6 +242,8 @@ function buildTurnEmbed(rec, map) {
|
|
|
187
242
|
tr: truncated,
|
|
188
243
|
sa
|
|
189
244
|
};
|
|
245
|
+
if (rec.source === "codex") out.sc = "codex";
|
|
246
|
+
return out;
|
|
190
247
|
}
|
|
191
248
|
function computeKpis(turns) {
|
|
192
249
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -331,6 +388,7 @@ section.card > .note{color:var(--muted); font-size:12px; margin:0 0 14px;}
|
|
|
331
388
|
.turns .c-time,.turns .c-model{white-space:nowrap;}
|
|
332
389
|
.turns .c-proj{max-width:160px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;}
|
|
333
390
|
.turns .c-prompt{color:var(--ink2);}
|
|
391
|
+
.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
392
|
.turn-row{cursor:pointer;}
|
|
335
393
|
.turn-row:hover{background:color-mix(in srgb, var(--ink) 4%, transparent);}
|
|
336
394
|
.turn-row:focus-visible{outline:2px solid var(--s1); outline-offset:-2px;}
|
|
@@ -408,7 +466,7 @@ var APP_JS = `<script>
|
|
|
408
466
|
return p[1] + '/' + p[2] + (gran === 'week' ? '\u9031' : '');
|
|
409
467
|
}
|
|
410
468
|
function periodText(key, gran){
|
|
411
|
-
if(key === null) return '\u901A\u7B97 / All time';
|
|
469
|
+
if(key === null) return data.allPeriodLabel || '\u901A\u7B97 / All time';
|
|
412
470
|
if(gran === 'month') return key + ' (\u6708)';
|
|
413
471
|
if(gran === 'week') return key + ' \u306E\u9031';
|
|
414
472
|
return key;
|
|
@@ -418,17 +476,43 @@ var APP_JS = `<script>
|
|
|
418
476
|
function ssGet(k){ try { return sessionStorage.getItem(k); } catch(e){ return null; } }
|
|
419
477
|
function ssSet(k, v){ try { sessionStorage.setItem(k, v); } catch(e){} }
|
|
420
478
|
var GRAN = ssGet('cccn-gran'); if(['day','week','month'].indexOf(GRAN) < 0) GRAN = 'day';
|
|
479
|
+
// \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
|
|
480
|
+
// \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
|
|
481
|
+
var HAS_CODEX = document.querySelectorAll('[data-src]').length > 0;
|
|
482
|
+
var SRC = ssGet('cccn-src'); if(['all','claude','codex'].indexOf(SRC) < 0) SRC = 'all';
|
|
483
|
+
// \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
|
|
484
|
+
function activeTurns(){
|
|
485
|
+
if(!HAS_CODEX || SRC === 'all') return turns;
|
|
486
|
+
if(SRC === 'codex') return turns.filter(function(tn){ return tn.sc === 'codex'; });
|
|
487
|
+
return turns.filter(function(tn){ return !tn.sc; });
|
|
488
|
+
}
|
|
421
489
|
// \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
490
|
var storedSel = ssGet('cccn-sel');
|
|
423
491
|
var SEL = (storedSel === null) ? defaultSel(GRAN) : (storedSel === '__all__' ? null : storedSel);
|
|
424
492
|
function setGran(g){ GRAN = g; ssSet('cccn-gran', g); }
|
|
425
493
|
function setSel(v){ SEL = v; ssSet('cccn-sel', v === null ? '__all__' : v); }
|
|
494
|
+
// \u81EA\u52D5\u518D\u751F\u6210\u3067\u57CB\u3081\u8FBC\u307F\u7BC4\u56F2\u304C\u7E2E\u3093\u3060\u5F8C\u3082\u3001\u53E4\u3044\u9078\u629E\u30AD\u30FC\u3067\u7A7A\u8868\u793A\u306B\u3057\u306A\u3044\u3002
|
|
495
|
+
if(SEL !== null){
|
|
496
|
+
var initialBuckets = buildBuckets(GRAN), initialSelPresent = false;
|
|
497
|
+
for(var ib=0;ib<initialBuckets.length;ib++){ if(initialBuckets[ib].key === SEL){ initialSelPresent = true; break; } }
|
|
498
|
+
if(!initialSelPresent) setSel(null);
|
|
499
|
+
}
|
|
500
|
+
// \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
|
|
501
|
+
function setSrc(s){
|
|
502
|
+
SRC = s; ssSet('cccn-src', s);
|
|
503
|
+
if(SEL !== null){
|
|
504
|
+
var present = false, bk = buildBuckets(GRAN);
|
|
505
|
+
for(var i=0;i<bk.length;i++){ if(bk[i].key === SEL){ present = true; break; } }
|
|
506
|
+
if(!present) setSel(null);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
426
509
|
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
510
|
|
|
428
511
|
function buildBuckets(gran){
|
|
512
|
+
var src = activeTurns();
|
|
429
513
|
var map = {}; var keys = [];
|
|
430
|
-
for(var i=0;i<
|
|
431
|
-
var tn =
|
|
514
|
+
for(var i=0;i<src.length;i++){
|
|
515
|
+
var tn = src[i];
|
|
432
516
|
var k = bucketKey(tn.t, gran);
|
|
433
517
|
if(k === null) continue;
|
|
434
518
|
var b = map[k];
|
|
@@ -452,8 +536,9 @@ var APP_JS = `<script>
|
|
|
452
536
|
}
|
|
453
537
|
|
|
454
538
|
function turnsInSelection(){
|
|
455
|
-
|
|
456
|
-
|
|
539
|
+
var src = activeTurns();
|
|
540
|
+
if(SEL === null) return src;
|
|
541
|
+
return src.filter(function(tn){ return bucketKey(tn.t, GRAN) === SEL; });
|
|
457
542
|
}
|
|
458
543
|
|
|
459
544
|
// ---- SVG \u30D8\u30EB\u30D1\u30FC ----
|
|
@@ -691,10 +776,12 @@ var APP_JS = `<script>
|
|
|
691
776
|
projEl.appendChild(table);
|
|
692
777
|
}
|
|
693
778
|
|
|
694
|
-
// ---- \u6708\u4E88\u7B97(\u9078\u629E\
|
|
779
|
+
// ---- \u6708\u4E88\u7B97(\u5168\u5C65\u6B74\u7248\u306F\u9078\u629E\u6708\u306B\u9023\u52D5\u3001\u671F\u9593\u9650\u5B9A\u7248\u306F\u5F53\u6708\u306B\u56FA\u5B9A)----
|
|
695
780
|
var budgetEl = document.getElementById('cccn-budget');
|
|
696
781
|
var BUDGET = +data.budget || 0;
|
|
697
782
|
var BUDGET_RATE = +data.budgetRate || 0;
|
|
783
|
+
var BUDGET_FIXED = data.budgetFixed === true;
|
|
784
|
+
var BUDGET_MONTH = data.budgetMonth || null;
|
|
698
785
|
function monthKeyOf(t){ var d = new Date(t); if(isNaN(d.getTime())) return null; return d.getFullYear() + '-' + pad(d.getMonth()+1); }
|
|
699
786
|
// \u4E88\u7B97\u306F\u300C\u6708\u300D\u5358\u4F4D\u3002\u9078\u629E\u4E2D\u30D0\u30B1\u30C3\u30C8\u304C\u5C5E\u3059\u308B\u66A6\u6708\u3092\u5BFE\u8C61\u306B\u3059\u308B(\u901A\u7B97\u306E\u3068\u304D\u306F\u4ECA\u6708)\u3002
|
|
700
787
|
function targetMonthKey(){
|
|
@@ -704,13 +791,16 @@ var APP_JS = `<script>
|
|
|
704
791
|
}
|
|
705
792
|
function renderBudget(){
|
|
706
793
|
if(!budgetEl || !(BUDGET > 0)) return;
|
|
707
|
-
var mk = targetMonthKey();
|
|
708
|
-
var usd =
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
794
|
+
var mk = BUDGET_FIXED ? monthKeyOf(Date.now()) : targetMonthKey();
|
|
795
|
+
var usd = BUDGET_FIXED && BUDGET_MONTH ? (+BUDGET_MONTH.usd || 0) : 0;
|
|
796
|
+
var jpy = BUDGET_FIXED && BUDGET_MONTH ? (+BUDGET_MONTH.jpy || 0) : 0;
|
|
797
|
+
if(!BUDGET_FIXED){
|
|
798
|
+
for(var i=0;i<turns.length;i++){
|
|
799
|
+
var tn = turns[i];
|
|
800
|
+
if(monthKeyOf(tn.t) !== mk) continue;
|
|
801
|
+
var bs = tn.bs || {}, fx = tn.fx || 0;
|
|
802
|
+
for(var s in bs){ if(bs.hasOwnProperty(s)){ usd += bs[s]; jpy += bs[s]*fx; } }
|
|
803
|
+
}
|
|
714
804
|
}
|
|
715
805
|
var pct = BUDGET > 0 ? (usd / BUDGET) * 100 : 0;
|
|
716
806
|
var width = Math.max(0, Math.min(100, pct));
|
|
@@ -726,6 +816,15 @@ var APP_JS = `<script>
|
|
|
726
816
|
sub.textContent = ' ' + mk + (mk === curMonth ? '(\u4ECA\u6708)' : '');
|
|
727
817
|
h.appendChild(sub);
|
|
728
818
|
budgetEl.appendChild(h);
|
|
819
|
+
// \u671F\u9593\u9650\u5B9A\u7248\u306F\u57CB\u3081\u8FBC\u307E\u306A\u3044\u5168\u5C65\u6B74\u304B\u3089\u5F53\u6708\u3092\u96C6\u8A08\u3057\u305F\u56FA\u5B9A\u5024\u3002
|
|
820
|
+
// \u5168\u5C65\u6B74\u7248\u306E\u307F\u5F93\u6765\u3069\u304A\u308A\u9078\u629E\u6708\u3078\u9023\u52D5\u3059\u308B\u3002\u3044\u305A\u308C\u3082\u30BD\u30FC\u30B9\u30D5\u30A3\u30EB\u30BF\u975E\u9023\u52D5\u3002
|
|
821
|
+
if(BUDGET_FIXED || HAS_CODEX){
|
|
822
|
+
var srcNote = document.createElement('p'); srcNote.className = 'note';
|
|
823
|
+
srcNote.textContent = BUDGET_FIXED
|
|
824
|
+
? '\u4ECA\u6708\u30FB\u5168\u5C65\u6B74\u304B\u3089\u6B63\u78BA\u306B\u96C6\u8A08(\u5168\u30BD\u30FC\u30B9\u5408\u7B97) / exact current month from full history'
|
|
825
|
+
: '\u5168\u30BD\u30FC\u30B9\u5408\u7B97 / all sources';
|
|
826
|
+
budgetEl.appendChild(srcNote);
|
|
827
|
+
}
|
|
729
828
|
|
|
730
829
|
var bar = document.createElement('div'); bar.className = 'budget-bar';
|
|
731
830
|
var fill = document.createElement('div'); fill.className = 'budget-fill lvl-' + level;
|
|
@@ -746,6 +845,55 @@ var APP_JS = `<script>
|
|
|
746
845
|
budgetEl.appendChild(foot);
|
|
747
846
|
}
|
|
748
847
|
|
|
848
|
+
// ---- KPI(\u4ECA\u65E5/\u4ECA\u9031/\u4ECA\u6708/\u901A\u7B97)+ \u30D2\u30FC\u30ED\u30FC(\u901A\u7B97\u30D0\u30CA\u30FC)----
|
|
849
|
+
// \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
|
|
850
|
+
// \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
|
|
851
|
+
// \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
|
|
852
|
+
// (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
|
|
853
|
+
// \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
|
|
854
|
+
var kpiCards = document.querySelectorAll('.kpi .stat');
|
|
855
|
+
var heroValueEl = document.querySelector('.hero .hero-value');
|
|
856
|
+
var heroMetaEls = document.querySelectorAll('.hero .hero-meta');
|
|
857
|
+
var heroSaEl = heroMetaEls.length > 1 ? heroMetaEls[1] : null; // SA \u884C(SA \u3042\u308A\u306E\u3068\u304D\u3060\u3051\u30B5\u30FC\u30D0\u304C\u63CF\u753B)
|
|
858
|
+
var heroSaPrefix = heroSaEl ? ((heroSaEl.textContent || '').split('$')[0]) : '';
|
|
859
|
+
function setKpiCard(card, tot){
|
|
860
|
+
if(!card) return;
|
|
861
|
+
var v = card.querySelector('.stat-value');
|
|
862
|
+
var m = card.querySelector('.stat-meta');
|
|
863
|
+
if(v) v.textContent = formatUSD(tot.usd);
|
|
864
|
+
if(m) m.textContent = formatJPY(tot.jpy) + ' \xB7 ' + tot.turns + ' \u30BF\u30FC\u30F3';
|
|
865
|
+
}
|
|
866
|
+
function renderKpis(){
|
|
867
|
+
if(!HAS_CODEX) return;
|
|
868
|
+
var src = activeTurns();
|
|
869
|
+
var now = new Date();
|
|
870
|
+
var y = now.getFullYear(), mo = now.getMonth(), d = now.getDate();
|
|
871
|
+
var weekCutoff = Date.now() - 7*86400000;
|
|
872
|
+
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};
|
|
873
|
+
var saUsd = 0, saJpy = 0;
|
|
874
|
+
for(var i=0;i<src.length;i++){
|
|
875
|
+
var tn = src[i], bs = tn.bs || {}, fx = tn.fx || 0, u = 0;
|
|
876
|
+
for(var s in bs){ if(bs.hasOwnProperty(s)) u += bs[s]; }
|
|
877
|
+
var j = u * fx;
|
|
878
|
+
if(tn.sa){ var sau = u - (tn.um || 0); saUsd += sau; saJpy += sau * fx; } // SA \u5206 = \u7DCF\u984D \u2212 \u30E1\u30A4\u30F3
|
|
879
|
+
all.usd += u; all.jpy += j; all.turns++;
|
|
880
|
+
var dt = new Date(tn.t);
|
|
881
|
+
if(isNaN(dt.getTime())) continue;
|
|
882
|
+
if(dt.getFullYear()===y && dt.getMonth()===mo && dt.getDate()===d){ today.usd+=u; today.jpy+=j; today.turns++; }
|
|
883
|
+
if(tn.t >= weekCutoff){ week.usd+=u; week.jpy+=j; week.turns++; }
|
|
884
|
+
if(dt.getFullYear()===y && dt.getMonth()===mo){ month.usd+=u; month.jpy+=j; month.turns++; }
|
|
885
|
+
}
|
|
886
|
+
if(kpiCards.length >= 4){
|
|
887
|
+
setKpiCard(kpiCards[0], today); setKpiCard(kpiCards[1], week); setKpiCard(kpiCards[2], month); setKpiCard(kpiCards[3], all);
|
|
888
|
+
}
|
|
889
|
+
if(heroValueEl) heroValueEl.textContent = formatUSD(all.usd);
|
|
890
|
+
if(heroMetaEls.length > 0) heroMetaEls[0].textContent = formatJPY(all.jpy) + ' \xB7 ' + all.turns + ' \u30BF\u30FC\u30F3';
|
|
891
|
+
if(heroSaEl){
|
|
892
|
+
heroSaEl.hidden = !(saUsd > 0);
|
|
893
|
+
if(saUsd > 0) heroSaEl.textContent = heroSaPrefix + formatUSD(saUsd) + '(' + formatJPY(saJpy) + ')';
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
|
|
749
897
|
// ---- \u30BF\u30FC\u30F3\u5C65\u6B74 ----
|
|
750
898
|
// \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
899
|
// \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 +913,14 @@ var APP_JS = `<script>
|
|
|
765
913
|
function cell(text, cls){ var td = document.createElement('td'); if(cls) td.className = cls; td.textContent = text; tr.appendChild(td); return td; }
|
|
766
914
|
cell(t.ts || '', 'c-time');
|
|
767
915
|
cell(t.p || '', 'c-proj');
|
|
768
|
-
cell(t.md || '', 'c-model');
|
|
916
|
+
var mcell = cell(t.md || '', 'c-model');
|
|
917
|
+
// 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
|
|
918
|
+
if(t.sc === 'codex'){
|
|
919
|
+
mcell.appendChild(document.createTextNode(' '));
|
|
920
|
+
var badge = document.createElement('span'); badge.className = 'src-badge';
|
|
921
|
+
badge.textContent = 'Codex';
|
|
922
|
+
mcell.appendChild(badge);
|
|
923
|
+
}
|
|
769
924
|
cell(t.ti || '', 'c-num');
|
|
770
925
|
cell(t.to || '', 'c-num');
|
|
771
926
|
cell(formatUSD(t.um || 0), 'c-num');
|
|
@@ -832,6 +987,8 @@ var APP_JS = `<script>
|
|
|
832
987
|
for(var i=0;i<gbtns.length;i++){ gbtns[i].classList.toggle('active', gbtns[i].getAttribute('data-gran') === GRAN); }
|
|
833
988
|
var allBtn = document.getElementById('cccn-all');
|
|
834
989
|
if(allBtn) allBtn.classList.toggle('active', SEL === null);
|
|
990
|
+
var sbtns = document.querySelectorAll('[data-src]');
|
|
991
|
+
for(var k=0;k<sbtns.length;k++){ sbtns[k].classList.toggle('active', sbtns[k].getAttribute('data-src') === SRC); }
|
|
835
992
|
}
|
|
836
993
|
|
|
837
994
|
// ---- \u5168\u4F53\u30EC\u30F3\u30C0\u30EA\u30F3\u30B0 ----
|
|
@@ -839,6 +996,7 @@ var APP_JS = `<script>
|
|
|
839
996
|
var sub = turnsInSelection();
|
|
840
997
|
renderChart();
|
|
841
998
|
renderBudget();
|
|
999
|
+
renderKpis();
|
|
842
1000
|
renderByModel(sub);
|
|
843
1001
|
renderByProject(sub);
|
|
844
1002
|
renderHistory(sub);
|
|
@@ -853,6 +1011,10 @@ var APP_JS = `<script>
|
|
|
853
1011
|
}
|
|
854
1012
|
var allBtn = document.getElementById('cccn-all');
|
|
855
1013
|
if(allBtn) allBtn.addEventListener('click', function(){ setSel(null); render(); });
|
|
1014
|
+
var srcBtns = document.querySelectorAll('[data-src]');
|
|
1015
|
+
for(var sbi=0;sbi<srcBtns.length;sbi++){
|
|
1016
|
+
(function(btn){ btn.addEventListener('click', function(){ setSrc(btn.getAttribute('data-src')); render(); }); })(srcBtns[sbi]);
|
|
1017
|
+
}
|
|
856
1018
|
if(search){
|
|
857
1019
|
var saved = ssGet('cccn-search');
|
|
858
1020
|
if(saved !== null) search.value = saved;
|
|
@@ -879,28 +1041,36 @@ function renderLegend(slots) {
|
|
|
879
1041
|
const items = slots.map((m) => `<span class="legend-item"><span class="swatch k${m.slot}"></span>${esc(m.name)}</span>`).join("");
|
|
880
1042
|
return `<div class="legend">${items}</div>`;
|
|
881
1043
|
}
|
|
882
|
-
function budgetCard(budgetUSD, month, fallbackRate) {
|
|
1044
|
+
function budgetCard(budgetUSD, month, fallbackRate, hasCodex, fixedCurrentMonth) {
|
|
883
1045
|
if (!(budgetUSD > 0)) return "";
|
|
884
1046
|
const pct = month.usd / budgetUSD * 100;
|
|
885
1047
|
const width = Math.max(0, Math.min(100, pct));
|
|
886
1048
|
const level = pct >= 100 ? "over" : pct >= 70 ? "warn" : "ok";
|
|
887
1049
|
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
|
|
1050
|
+
return `<section class="card" id="cccn-budget"><h2>\u6708\u4E88\u7B97 / Monthly budget<span class="stat-sub"> \u4ECA\u6708(\u66A6\u6708)</span></h2>` + (fixedCurrentMonth ? `<p class="note">\u4ECA\u6708\u30FB\u5168\u5C65\u6B74\u304B\u3089\u6B63\u78BA\u306B\u96C6\u8A08(\u5168\u30BD\u30FC\u30B9\u5408\u7B97) / exact current month from full history</p>` : 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
1051
|
}
|
|
890
1052
|
function escapeJsonForScript(json) {
|
|
891
1053
|
return json.replace(/</g, "\\u003c").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
892
1054
|
}
|
|
893
|
-
function renderDashboard(turns, opts) {
|
|
1055
|
+
function renderDashboard(turns, opts, fullHistory) {
|
|
894
1056
|
const version = readVersion();
|
|
895
|
-
const
|
|
1057
|
+
const generatedAtIso = opts.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
1058
|
+
const generatedAt = fmtLocalDateTime(generatedAtIso);
|
|
896
1059
|
const period = opts.days === null ? "\u5168\u671F\u9593" : `\u76F4\u8FD1 ${opts.days} \u65E5\u9593`;
|
|
897
|
-
const
|
|
1060
|
+
const limited = opts.days !== null;
|
|
1061
|
+
const variant = opts.variant ?? "custom";
|
|
1062
|
+
const totalLabel = limited ? "\u5BFE\u8C61\u671F\u9593\u5408\u8A08" : "\u901A\u7B97";
|
|
1063
|
+
const totalLabelEn = limited ? "Embedded period total" : "Total";
|
|
1064
|
+
const totalSub = limited ? "\u57CB\u3081\u8FBC\u307F\u5BFE\u8C61\u671F\u9593" : "\u5168\u671F\u9593";
|
|
1065
|
+
const map = fullHistory?.slotMap ?? computeSlotMap(turns);
|
|
898
1066
|
const kpi = computeKpis(turns);
|
|
899
1067
|
const cfg = readConfig();
|
|
900
1068
|
const budgetUSD = cfg.monthlyBudgetUSD;
|
|
1069
|
+
const budgetMonth = limited ? fullHistory?.currentMonth ?? kpi.month : kpi.month;
|
|
901
1070
|
const turnsEmbed = turns.map((rec) => buildTurnEmbed(rec, map));
|
|
902
1071
|
const anyTruncated = turnsEmbed.some((t) => t.tr);
|
|
903
1072
|
const anySub = turns.some((r) => r.subagents);
|
|
1073
|
+
const anyCodex = turns.some((r) => r.source === "codex");
|
|
904
1074
|
let subUsd = 0;
|
|
905
1075
|
let subJpy = 0;
|
|
906
1076
|
for (const r of turns) {
|
|
@@ -924,24 +1094,32 @@ function renderDashboard(turns, opts) {
|
|
|
924
1094
|
slots: map.slots,
|
|
925
1095
|
turns: turnsEmbed,
|
|
926
1096
|
budget: budgetUSD,
|
|
927
|
-
budgetRate: cfg.fx.fallbackRate
|
|
1097
|
+
budgetRate: cfg.fx.fallbackRate,
|
|
1098
|
+
budgetMonth,
|
|
1099
|
+
budgetFixed: limited,
|
|
1100
|
+
allPeriodLabel: `${totalLabel} / ${totalLabelEn}`,
|
|
1101
|
+
variant
|
|
928
1102
|
};
|
|
929
1103
|
const dataJson = escapeJsonForScript(JSON.stringify(embed));
|
|
930
1104
|
const reloadSec = Number.isFinite(opts.autoReloadSec) && opts.autoReloadSec > 0 ? Math.floor(opts.autoReloadSec) : 0;
|
|
931
1105
|
const refreshMeta = reloadSec > 0 ? `<meta http-equiv="refresh" content="${reloadSec}">` : "";
|
|
932
|
-
const
|
|
1106
|
+
const updateTrigger = anyCodex ? "Claude Code / Codex \u306E\u5FDC\u7B54\u5B8C\u4E86\u6642" : "Claude Code \u306E\u5FDC\u7B54\u5B8C\u4E86\u6642";
|
|
1107
|
+
const autoUpdateFoot = reloadSec > 0 ? variant === "full" ? `<div class="foot">\u7D04 ${reloadSec} \u79D2\u3054\u3068\u306B\u30D5\u30A1\u30A4\u30EB\u3092\u518D\u8AAD\u8FBC(\u5168\u5C65\u6B74\u7248\u306E\u751F\u6210\u306F\u30ED\u30FC\u30AB\u30EB\u65E5\u3054\u3068\u3001\u307E\u305F\u306F\u624B\u52D5 dashboard --all)</div>` : `<div class="foot">\u7D04 ${reloadSec} \u79D2\u3054\u3068\u306B\u81EA\u52D5\u66F4\u65B0(\u6700\u65B0\u5316\u306F ${updateTrigger})</div>` : "";
|
|
1108
|
+
const variantNav = variant === "recent" ? `<div class="sub"><strong>\u76F4\u8FD1 ${opts.days} \u65E5\u7248 / Recent</strong> \xB7 ${opts.peerAvailable ? `<a href="report-all.html">\u5168\u5C65\u6B74\u7248\u3078 / Full history</a>` : `<span aria-disabled="true">\u5168\u5C65\u6B74\u7248\u306F\u672A\u751F\u6210\u3067\u3059\uFF08dashboard --all \u3067\u751F\u6210\uFF09 / Full history not generated</span>`}</div>` : variant === "full" ? `<div class="sub"><strong>\u5168\u5C65\u6B74\u7248 / Full history</strong> \xB7 ${opts.peerAvailable ? `<a href="report.html">\u76F4\u8FD1\u7248\u3078 / Recent</a>` : `<span aria-disabled="true">\u76F4\u8FD1\u7248\u306F\u672A\u751F\u6210\u3067\u3059\uFF08dashboard \u3067\u751F\u6210\uFF09 / Recent not generated</span>`}</div><div class="sub muted">\u6700\u7D42\u751F\u6210 ${esc(generatedAt)}\u3002\u30ED\u30FC\u30AB\u30EB\u65E5\u306E\u6700\u521D\u306E\u6B63\u5E38\u306A\u30BF\u30FC\u30F3\u6642\u3001\u307E\u305F\u306F\u624B\u52D5\u306E dashboard --all \u3067\u66F4\u65B0\u3055\u308C\u307E\u3059\u3002</div>` : "";
|
|
933
1109
|
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
1110
|
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>`;
|
|
1111
|
+
const budgetSection = budgetCard(budgetUSD, budgetMonth, cfg.fx.fallbackRate, anyCodex, limited);
|
|
935
1112
|
if (turns.length === 0) {
|
|
936
|
-
const
|
|
937
|
-
const
|
|
938
|
-
|
|
1113
|
+
const outsideEmbeddedPeriod = limited && (fullHistory?.hasTurns ?? false);
|
|
1114
|
+
const header2 = `<div class="head"><div><h1>ccc-notifier \u30C0\u30C3\u30B7\u30E5\u30DC\u30FC\u30C9</h1>` + variantNav + `<div class="sub">${esc(period)} \xB7 \u751F\u6210 ${esc(generatedAt)}</div></div></div>`;
|
|
1115
|
+
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>`;
|
|
1116
|
+
return head + header2 + budgetSection + empty + foot;
|
|
939
1117
|
}
|
|
940
|
-
const header = `<div class="head"><div><h1>ccc-notifier \u30C0\u30C3\u30B7\u30E5\u30DC\u30FC\u30C9</h1
|
|
941
|
-
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(
|
|
942
|
-
const
|
|
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\
|
|
944
|
-
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
|
|
1118
|
+
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>`;
|
|
1119
|
+
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>`;
|
|
1120
|
+
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>` : "";
|
|
1121
|
+
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>`;
|
|
1122
|
+
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>`;
|
|
945
1123
|
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
1124
|
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>`;
|
|
947
1125
|
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>`;
|
|
@@ -983,27 +1161,59 @@ function openInBrowser(path) {
|
|
|
983
1161
|
}
|
|
984
1162
|
function writeDashboardHtml(opts) {
|
|
985
1163
|
const days = opts.days ?? null;
|
|
986
|
-
const
|
|
1164
|
+
const variant = opts.variant ?? (basename(opts.outPath) === "report.html" ? "recent" : basename(opts.outPath) === "report-all.html" ? "full" : "custom");
|
|
1165
|
+
ensurePeerPlaceholder(variant);
|
|
1166
|
+
const allTurns = opts.allTurns ?? readTurns();
|
|
1167
|
+
const turns = days === null ? allTurns : filterTurnsByDays(allTurns, days);
|
|
1168
|
+
const fullHistory = days === null ? void 0 : {
|
|
1169
|
+
currentMonth: computeKpis(allTurns).month,
|
|
1170
|
+
slotMap: computeSlotMap(allTurns),
|
|
1171
|
+
hasTurns: allTurns.length > 0
|
|
1172
|
+
};
|
|
987
1173
|
const html = renderDashboard(turns, {
|
|
1174
|
+
scope: days === null ? "all" : "recent",
|
|
988
1175
|
days,
|
|
989
1176
|
open: false,
|
|
990
1177
|
out: opts.outPath,
|
|
991
|
-
autoReloadSec: opts.autoReloadSec
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
1178
|
+
autoReloadSec: opts.autoReloadSec,
|
|
1179
|
+
variant,
|
|
1180
|
+
generatedAt: opts.generatedAt,
|
|
1181
|
+
peerAvailable: variant !== "custom"
|
|
1182
|
+
}, fullHistory);
|
|
1183
|
+
writeAtomic(opts.outPath, html);
|
|
995
1184
|
}
|
|
996
1185
|
async function runDashboard(argv) {
|
|
997
|
-
|
|
998
|
-
|
|
1186
|
+
let opts;
|
|
1187
|
+
try {
|
|
1188
|
+
opts = parseArgs(argv);
|
|
1189
|
+
} catch (err) {
|
|
1190
|
+
console.error(`dashboard \u306E\u5F15\u6570\u304C\u4E0D\u6B63\u3067\u3059 / invalid dashboard arguments: ${err instanceof Error ? err.message : String(err)}`);
|
|
1191
|
+
return 1;
|
|
1192
|
+
}
|
|
1193
|
+
const canonical = opts.out === null;
|
|
1194
|
+
const outPath = opts.out ?? (opts.scope === "all" ? paths().fullDashboardFile : paths().recentDashboardFile);
|
|
1195
|
+
let lock = null;
|
|
999
1196
|
try {
|
|
1000
|
-
|
|
1001
|
-
|
|
1197
|
+
lock = await waitForDataLock();
|
|
1198
|
+
if (lock === null) throw new Error("data lock is busy; retry later");
|
|
1199
|
+
const generatedAt = /* @__PURE__ */ new Date();
|
|
1200
|
+
writeDashboardHtml({
|
|
1201
|
+
days: opts.days,
|
|
1202
|
+
outPath,
|
|
1203
|
+
autoReloadSec: opts.autoReloadSec,
|
|
1204
|
+
variant: canonical ? opts.scope === "all" ? "full" : "recent" : "custom",
|
|
1205
|
+
generatedAt: generatedAt.toISOString()
|
|
1206
|
+
});
|
|
1207
|
+
if (canonical && opts.scope === "all") {
|
|
1208
|
+
writeFullDashboardStateAtomic(makeFullDashboardState(generatedAt));
|
|
1209
|
+
}
|
|
1002
1210
|
} catch (err) {
|
|
1003
1211
|
console.error(
|
|
1004
1212
|
`dashboard \u306E\u751F\u6210\u306B\u5931\u6557\u3057\u307E\u3057\u305F / failed to generate dashboard: ${err instanceof Error ? err.message : String(err)}`
|
|
1005
1213
|
);
|
|
1006
1214
|
return 1;
|
|
1215
|
+
} finally {
|
|
1216
|
+
lock?.release();
|
|
1007
1217
|
}
|
|
1008
1218
|
console.log(`\u30C0\u30C3\u30B7\u30E5\u30DC\u30FC\u30C9\u3092\u751F\u6210\u3057\u307E\u3057\u305F / dashboard written: ${outPath}`);
|
|
1009
1219
|
if (process.platform === "linux" && isWSL()) {
|