ccc-notifier 0.5.0 → 0.6.1

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.
@@ -0,0 +1,226 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/pricing.ts
4
+ import { promises as fs } from "fs";
5
+ import path from "path";
6
+ var LITELLM_URL = "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json";
7
+ var LITELLM_FETCH_TIMEOUT_MS = 3e3;
8
+ var CACHE_FRESH_MS = 24 * 60 * 60 * 1e3;
9
+ var SONNET_5_STANDARD_PRICE_START_MS = Date.UTC(2026, 8, 1);
10
+ function price(input, output, cacheWrite5m, cacheWrite1h, cacheRead, source) {
11
+ return { input, output, cacheWrite5m, cacheWrite1h, cacheRead, source };
12
+ }
13
+ function builtinPriceTable(now = /* @__PURE__ */ new Date()) {
14
+ const sonnet5 = now.getTime() < SONNET_5_STANDARD_PRICE_START_MS ? price(2, 10, 2.5, 4, 0.2, "builtin") : price(3, 15, 3.75, 6, 0.3, "builtin");
15
+ return {
16
+ "claude-fable-5": price(10, 50, 12.5, 20, 1, "builtin"),
17
+ "claude-mythos-5": price(10, 50, 12.5, 20, 1, "builtin"),
18
+ "claude-opus-4-8": price(5, 25, 6.25, 10, 0.5, "builtin"),
19
+ "claude-opus-4-7": price(5, 25, 6.25, 10, 0.5, "builtin"),
20
+ "claude-opus-4-6": price(5, 25, 6.25, 10, 0.5, "builtin"),
21
+ "claude-opus-4-5": price(5, 25, 6.25, 10, 0.5, "builtin"),
22
+ "claude-opus-4-1": price(15, 75, 18.75, 30, 1.5, "builtin"),
23
+ "claude-opus-4": price(15, 75, 18.75, 30, 1.5, "builtin"),
24
+ // 旧 claude-opus-4-20250514 の受け皿
25
+ "claude-3-opus": price(15, 75, 18.75, 30, 1.5, "builtin"),
26
+ "claude-sonnet-5": sonnet5,
27
+ "claude-sonnet-4-6": price(3, 15, 3.75, 6, 0.3, "builtin"),
28
+ "claude-sonnet-4-5": price(3, 15, 3.75, 6, 0.3, "builtin"),
29
+ "claude-sonnet-4": price(3, 15, 3.75, 6, 0.3, "builtin"),
30
+ "claude-3-7-sonnet": price(3, 15, 3.75, 6, 0.3, "builtin"),
31
+ "claude-3-5-sonnet": price(3, 15, 3.75, 6, 0.3, "builtin"),
32
+ "claude-haiku-4-5": price(1, 5, 1.25, 2, 0.1, "builtin"),
33
+ "claude-3-5-haiku": price(0.8, 4, 1, 1.6, 0.08, "builtin"),
34
+ "claude-3-haiku": price(0.25, 1.25, 0.3125, 0.5, 0.025, "builtin"),
35
+ // OpenAI Codex CLI 対応(公式レートに基づく単価。キャッシュ書き込み課金は無いため 0)
36
+ "gpt-5.5": price(5, 30, 0, 0, 0.5, "builtin"),
37
+ "gpt-5.1": price(1.25, 10, 0, 0, 0.125, "builtin"),
38
+ "gpt-5": price(1.25, 10, 0, 0, 0.125, "builtin"),
39
+ "gpt-5-codex": price(1.25, 10, 0, 0, 0.125, "builtin"),
40
+ "gpt-5.1-codex": price(1.25, 10, 0, 0, 0.125, "builtin"),
41
+ "o3": price(2, 8, 0, 0, 0.5, "builtin")
42
+ };
43
+ }
44
+ function normalizeModelId(modelId) {
45
+ let s = modelId.toLowerCase();
46
+ s = s.replace(/^anthropic[\/.]/, "");
47
+ s = s.replace(/\[1m\]$/, "");
48
+ s = s.replace(/-20\d{6}$/, "");
49
+ return s.trim();
50
+ }
51
+ function resolvePrice(modelId, table) {
52
+ const target = normalizeModelId(modelId);
53
+ for (const rawKey of Object.keys(table)) {
54
+ const key = normalizeModelId(rawKey);
55
+ if (key.length > 0 && target === key) return { ...table[rawKey] };
56
+ }
57
+ return null;
58
+ }
59
+ function computeCost(main, sidechain, table) {
60
+ const byModel = /* @__PURE__ */ Object.create(null);
61
+ const unknownModels = [];
62
+ let usd = 0;
63
+ const accumulate = (usage) => {
64
+ for (const [model, tokens] of Object.entries(usage)) {
65
+ const p = resolvePrice(model, table);
66
+ let cost = 0;
67
+ if (p === null) {
68
+ if (!unknownModels.includes(model)) unknownModels.push(model);
69
+ } else {
70
+ cost = (tokens.input * p.input + tokens.output * p.output + tokens.cacheWrite5m * p.cacheWrite5m + tokens.cacheWrite1h * p.cacheWrite1h + tokens.cacheRead * p.cacheRead) / 1e6;
71
+ }
72
+ byModel[model] = (Object.hasOwn(byModel, model) ? byModel[model] : 0) + cost;
73
+ usd += cost;
74
+ }
75
+ };
76
+ accumulate(main);
77
+ accumulate(sidechain);
78
+ return { usd, byModel, unknownModels };
79
+ }
80
+ function cacheFilePath(cacheDir) {
81
+ return path.join(cacheDir, "pricing.json");
82
+ }
83
+ async function readPriceCache(cacheDir) {
84
+ try {
85
+ const raw = await fs.readFile(cacheFilePath(cacheDir), "utf8");
86
+ const parsed = JSON.parse(raw);
87
+ if (parsed !== null && typeof parsed === "object" && typeof parsed.fetchedAt === "string" && typeof parsed.table === "object" && parsed.table !== null) {
88
+ const p = parsed;
89
+ return { fetchedAt: p.fetchedAt, table: p.table };
90
+ }
91
+ return null;
92
+ } catch {
93
+ return null;
94
+ }
95
+ }
96
+ async function writePriceCache(cacheDir, table) {
97
+ const file = cacheFilePath(cacheDir);
98
+ const payload = { fetchedAt: (/* @__PURE__ */ new Date()).toISOString(), table };
99
+ await fs.mkdir(path.dirname(file), { recursive: true });
100
+ await fs.writeFile(file, JSON.stringify(payload, null, 2), "utf8");
101
+ }
102
+ function isCacheFresh(fetchedAt) {
103
+ const t = Date.parse(fetchedAt);
104
+ if (Number.isNaN(t)) return false;
105
+ return Date.now() - t <= CACHE_FRESH_MS;
106
+ }
107
+ function mergePriceTables(builtin, cached, fresh) {
108
+ const merged = { ...builtin };
109
+ const builtinKeyById = /* @__PURE__ */ new Map();
110
+ for (const rawKey of Object.keys(builtin)) {
111
+ const normalized = normalizeModelId(rawKey);
112
+ if (normalized.length > 0) builtinKeyById.set(normalized, rawKey);
113
+ }
114
+ const cachedKeyById = /* @__PURE__ */ new Map();
115
+ for (const [model, modelPrice] of Object.entries(cached)) {
116
+ const normalized = normalizeModelId(model);
117
+ if (normalized.length === 0) continue;
118
+ if (normalized === "claude-sonnet-5") continue;
119
+ const builtinKey = builtinKeyById.get(normalized);
120
+ if (builtinKey !== void 0) {
121
+ if (fresh) merged[builtinKey] = modelPrice;
122
+ continue;
123
+ }
124
+ const priorCachedKey = cachedKeyById.get(normalized);
125
+ if (priorCachedKey !== void 0 && priorCachedKey !== model) delete merged[priorCachedKey];
126
+ Object.defineProperty(merged, model, {
127
+ value: modelPrice,
128
+ enumerable: true,
129
+ configurable: true,
130
+ writable: true
131
+ });
132
+ cachedKeyById.set(normalized, model);
133
+ }
134
+ return merged;
135
+ }
136
+ function toFiniteNumber(v) {
137
+ return typeof v === "number" && Number.isFinite(v) ? v : null;
138
+ }
139
+ var LITELLM_OPENAI_KEY_RE = /^(gpt-|o3($|-)|codex-)/;
140
+ function convertLiteLLMPayload(payload) {
141
+ if (payload === null || typeof payload !== "object") {
142
+ throw new Error("invalid litellm payload: not an object");
143
+ }
144
+ const table = {};
145
+ for (const [rawKey, rawEntry] of Object.entries(payload)) {
146
+ if (rawEntry === null || typeof rawEntry !== "object") continue;
147
+ const entry = rawEntry;
148
+ const provider = entry.litellm_provider;
149
+ if (provider === "openai") {
150
+ const key2 = rawKey.toLowerCase();
151
+ if (!LITELLM_OPENAI_KEY_RE.test(key2)) continue;
152
+ const inputRaw2 = toFiniteNumber(entry.input_cost_per_token);
153
+ const outputRaw2 = toFiniteNumber(entry.output_cost_per_token);
154
+ if (inputRaw2 === null || inputRaw2 <= 0) continue;
155
+ if (outputRaw2 === null || outputRaw2 <= 0) continue;
156
+ const cacheReadRaw2 = toFiniteNumber(entry.cache_read_input_token_cost);
157
+ table[key2] = {
158
+ input: inputRaw2 * 1e6,
159
+ output: outputRaw2 * 1e6,
160
+ cacheRead: cacheReadRaw2 !== null ? cacheReadRaw2 * 1e6 : 0,
161
+ cacheWrite5m: 0,
162
+ cacheWrite1h: 0,
163
+ source: "litellm"
164
+ };
165
+ continue;
166
+ }
167
+ if (typeof provider === "string" && provider !== "anthropic") continue;
168
+ let key = rawKey.toLowerCase();
169
+ if (key.startsWith("anthropic/")) key = key.slice("anthropic/".length);
170
+ if (!key.startsWith("claude")) continue;
171
+ const inputRaw = toFiniteNumber(entry.input_cost_per_token);
172
+ const outputRaw = toFiniteNumber(entry.output_cost_per_token);
173
+ if (inputRaw === null || inputRaw <= 0) continue;
174
+ if (outputRaw === null || outputRaw <= 0) continue;
175
+ const input = inputRaw * 1e6;
176
+ const output = outputRaw * 1e6;
177
+ const cacheReadRaw = toFiniteNumber(entry.cache_read_input_token_cost);
178
+ const cacheWrite5mRaw = toFiniteNumber(entry.cache_creation_input_token_cost);
179
+ const cacheWrite1hRaw = toFiniteNumber(entry.cache_creation_input_token_cost_above_1hr);
180
+ table[key] = {
181
+ input,
182
+ output,
183
+ cacheRead: cacheReadRaw !== null ? cacheReadRaw * 1e6 : input * 0.1,
184
+ cacheWrite5m: cacheWrite5mRaw !== null ? cacheWrite5mRaw * 1e6 : input * 1.25,
185
+ cacheWrite1h: cacheWrite1hRaw !== null ? cacheWrite1hRaw * 1e6 : input * 2,
186
+ source: "litellm"
187
+ };
188
+ }
189
+ return table;
190
+ }
191
+ async function fetchLiteLLMPriceTable() {
192
+ const controller = new AbortController();
193
+ const timer = setTimeout(() => controller.abort(), LITELLM_FETCH_TIMEOUT_MS);
194
+ try {
195
+ const res = await fetch(LITELLM_URL, { signal: controller.signal });
196
+ if (!res.ok) {
197
+ throw new Error(`litellm fetch failed with status ${res.status}`);
198
+ }
199
+ const json = await res.json();
200
+ return convertLiteLLMPayload(json);
201
+ } finally {
202
+ clearTimeout(timer);
203
+ }
204
+ }
205
+ async function loadPriceTable(cacheDir, opts) {
206
+ const builtin = builtinPriceTable();
207
+ const cached = await readPriceCache(cacheDir);
208
+ if (cached !== null && isCacheFresh(cached.fetchedAt)) {
209
+ return mergePriceTables(builtin, cached.table, true);
210
+ }
211
+ if (opts?.offline === true) {
212
+ return cached !== null ? mergePriceTables(builtin, cached.table, false) : builtin;
213
+ }
214
+ try {
215
+ const remoteTable = await fetchLiteLLMPriceTable();
216
+ await writePriceCache(cacheDir, remoteTable);
217
+ return mergePriceTables(builtin, remoteTable, true);
218
+ } catch {
219
+ return cached !== null ? mergePriceTables(builtin, cached.table, false) : builtin;
220
+ }
221
+ }
222
+
223
+ export {
224
+ computeCost,
225
+ loadPriceTable
226
+ };
@@ -10,16 +10,14 @@ import {
10
10
  } from "./chunk-J5QAYTFE.js";
11
11
  import {
12
12
  makeFullDashboardState,
13
+ waitForDataLock,
13
14
  writeFullDashboardStateAtomic
14
- } from "./chunk-ENGUOLTD.js";
15
- import {
16
- waitForDataLock
17
- } from "./chunk-O34L3NSI.js";
15
+ } from "./chunk-27SJELD2.js";
18
16
  import {
19
17
  paths,
20
18
  readConfig,
21
19
  readTurns
22
- } from "./chunk-5PH7PPD6.js";
20
+ } from "./chunk-OOAC5ULQ.js";
23
21
 
24
22
  // src/dashboard.ts
25
23
  import { spawn, spawnSync } from "child_process";
@@ -128,6 +126,13 @@ function dateKeyOf(d) {
128
126
  const day = String(d.getDate()).padStart(2, "0");
129
127
  return `${y}-${m}-${day}`;
130
128
  }
129
+ function inclusiveLocalDateSpan(min, max) {
130
+ const toUtcDay = (d) => Date.UTC(d.getFullYear(), d.getMonth(), d.getDate());
131
+ const minDay = toUtcDay(min);
132
+ const maxDay = toUtcDay(max);
133
+ if (!Number.isFinite(minDay) || !Number.isFinite(maxDay) || maxDay < minDay) return null;
134
+ return Math.floor((maxDay - minDay) / 864e5) + 1;
135
+ }
131
136
  function fmtLocalDateTime(iso) {
132
137
  const d = new Date(iso);
133
138
  if (Number.isNaN(d.getTime())) return iso;
@@ -681,14 +686,27 @@ var APP_JS = `<script>
681
686
  lastRenderedGran = GRAN;
682
687
  }
683
688
 
689
+ // 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
690
+ // slotOrder\u306E\u307E\u307E\u56FA\u5B9A\u3057\u3001\u540C\u984D\u3082slotOrder\u3067\u6C7A\u5B9A\u7684\u306B\u4E26\u3079\u308B\u3002
691
+ function tooltipSlotOrder(bucket){
692
+ var order = slotOrder.slice();
693
+ var rank = {}; for(var i=0;i<slotOrder.length;i++){ rank[slotOrder[i]] = i; }
694
+ order.sort(function(a, b){
695
+ var diff = (bucket.bs[b] || 0) - (bucket.bs[a] || 0);
696
+ return diff !== 0 ? diff : rank[a] - rank[b];
697
+ });
698
+ return order;
699
+ }
700
+
684
701
  function showTip(evt, bucket){
685
702
  if(!tip) return;
686
703
  clearNode(tip);
687
704
  var h = document.createElement('div'); h.className = 'tip-title';
688
705
  h.textContent = periodText(bucket.key, GRAN) + ' \u5408\u8A08 ' + formatUSD(bucket.total) + ' \xB7 ' + bucket.turns + ' \u30BF\u30FC\u30F3';
689
706
  tip.appendChild(h);
690
- for(var j=0;j<slotOrder.length;j++){
691
- var slot = slotOrder[j];
707
+ var tipOrder = tooltipSlotOrder(bucket);
708
+ for(var j=0;j<tipOrder.length;j++){
709
+ var slot = tipOrder[j];
692
710
  var v = bucket.bs[slot] || 0;
693
711
  if(v <= 0) continue;
694
712
  var row = document.createElement('div'); row.className = 'tip-row';
@@ -825,12 +843,13 @@ var APP_JS = `<script>
825
843
  sub.textContent = ' ' + mk + (mk === curMonth ? '(\u4ECA\u6708)' : '');
826
844
  h.appendChild(sub);
827
845
  budgetEl.appendChild(h);
828
- // \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
846
+ // \u671F\u9593\u9650\u5B9A\u7248\u306F\u3001\u4FDD\u5B58\u6E08\u307F\u5C65\u6B74\u306E\u5F53\u6708\u5206\u3092\u96C6\u8A08\u5BFE\u8C61\u304B\u3089\u843D\u3068\u3055\u306A\u3044\u305F\u3081
847
+ // \u57CB\u3081\u8FBC\u307E\u306A\u3044\u5168\u5C65\u6B74\u304B\u3089\u56FA\u5B9A\u5024\u3092\u4F5C\u308B\u3002
829
848
  // \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
830
849
  if(BUDGET_FIXED || HAS_CODEX){
831
850
  var srcNote = document.createElement('p'); srcNote.className = 'note';
832
851
  srcNote.textContent = BUDGET_FIXED
833
- ? '\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'
852
+ ? '\u4ECA\u6708\u30FB\u4FDD\u5B58\u6E08\u307F\u5C65\u6B74\u3092\u5168\u4EF6\u96C6\u8A08(\u5168\u30BD\u30FC\u30B9\u5408\u7B97) / current month from all recorded history'
834
853
  : '\u5168\u30BD\u30FC\u30B9\u5408\u7B97 / all sources';
835
854
  budgetEl.appendChild(srcNote);
836
855
  }
@@ -1062,7 +1081,7 @@ function budgetCard(budgetUSD, month, fallbackRate, hasCodex, fixedCurrentMonth)
1062
1081
  const width = Math.max(0, Math.min(100, pct));
1063
1082
  const level = pct >= 100 ? "over" : pct >= 70 ? "warn" : "ok";
1064
1083
  const budgetJpy = budgetUSD * fallbackRate;
1065
- 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>`;
1084
+ 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\u4FDD\u5B58\u6E08\u307F\u5C65\u6B74\u3092\u5168\u4EF6\u96C6\u8A08(\u5168\u30BD\u30FC\u30B9\u5408\u7B97) / current month from all recorded 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>`;
1066
1085
  }
1067
1086
  function escapeJsonForScript(json) {
1068
1087
  return json.replace(/</g, "\\u003c").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
@@ -1071,7 +1090,7 @@ function renderDashboard(turns, opts, fullHistory) {
1071
1090
  const version = readVersion();
1072
1091
  const generatedAtIso = opts.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString();
1073
1092
  const generatedAt = fmtLocalDateTime(generatedAtIso);
1074
- const period = opts.days === null ? "\u5168\u671F\u9593" : `\u76F4\u8FD1 ${opts.days} \u65E5\u9593`;
1093
+ let period = opts.days === null ? "\u5168\u671F\u9593" : `\u76F4\u8FD1 ${opts.days} \u65E5\u9593`;
1075
1094
  const limited = opts.days !== null;
1076
1095
  const variant = opts.variant ?? "custom";
1077
1096
  const totalLabel = limited ? "\u5BFE\u8C61\u671F\u9593\u5408\u8A08" : "\u901A\u7B97";
@@ -1096,13 +1115,17 @@ function renderDashboard(turns, opts, fullHistory) {
1096
1115
  }
1097
1116
  let minMs = Infinity;
1098
1117
  let maxMs = -Infinity;
1099
- for (const t of turnsEmbed) {
1100
- if (t.t > 0) {
1101
- if (t.t < minMs) minMs = t.t;
1102
- if (t.t > maxMs) maxMs = t.t;
1103
- }
1118
+ for (const turn of turns) {
1119
+ const ms = Date.parse(turn.ts);
1120
+ if (!Number.isFinite(ms)) continue;
1121
+ if (ms < minMs) minMs = ms;
1122
+ if (ms > maxMs) maxMs = ms;
1104
1123
  }
1105
1124
  const rangeText = Number.isFinite(minMs) && Number.isFinite(maxMs) ? `${dateKeyOf(new Date(minMs))} \u301C ${dateKeyOf(new Date(maxMs))}` : "\u2014";
1125
+ const embeddedDaySpan = Number.isFinite(minMs) && Number.isFinite(maxMs) ? inclusiveLocalDateSpan(new Date(minMs), new Date(maxMs)) : null;
1126
+ const shortRecentHistory = opts.days !== null && embeddedDaySpan !== null && embeddedDaySpan < opts.days;
1127
+ if (shortRecentHistory) period = `\u5C65\u6B74 ${embeddedDaySpan} \u65E5\u5206`;
1128
+ const recentVariantLabel = shortRecentHistory ? `\u5C65\u6B74 ${embeddedDaySpan} \u65E5\u5206 / Recent` : `\u76F4\u8FD1 ${opts.days} \u65E5\u7248 / Recent`;
1106
1129
  const embed = {
1107
1130
  version,
1108
1131
  generatedAt,
@@ -1119,8 +1142,8 @@ function renderDashboard(turns, opts, fullHistory) {
1119
1142
  const reloadSec = Number.isFinite(opts.autoReloadSec) && opts.autoReloadSec > 0 ? Math.floor(opts.autoReloadSec) : 0;
1120
1143
  const refreshMeta = reloadSec > 0 ? `<meta http-equiv="refresh" content="${reloadSec}">` : "";
1121
1144
  const updateTrigger = anyCodex ? "Claude Code / Codex \u306E\u5FDC\u7B54\u5B8C\u4E86\u6642" : "Claude Code \u306E\u5FDC\u7B54\u5B8C\u4E86\u6642";
1122
- 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>` : "";
1123
- 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>` : "";
1145
+ 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\u6B63\u5E38\u306A sweep \u5B8C\u4E86\u6642\u3001\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}\u3001\u307E\u305F\u306F\u6B63\u5E38\u306A sweep \u5B8C\u4E86\u6642)</div>` : "";
1146
+ const variantNav = variant === "recent" ? `<div class="sub"><strong>${recentVariantLabel}</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\u6B63\u5E38\u306A sweep \u5B8C\u4E86\u6642\u3001\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>` : "";
1124
1147
  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">`;
1125
1148
  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>`;
1126
1149
  const budgetSection = budgetCard(budgetUSD, budgetMonth, cfg.fx.fallbackRate, anyCodex, limited);
@@ -7,11 +7,14 @@ import {
7
7
  codexHome,
8
8
  detectCodex
9
9
  } from "./chunk-HTYUYKFW.js";
10
+ import {
11
+ loadPriceTable
12
+ } from "./chunk-6HTETN26.js";
10
13
  import {
11
14
  configFilePath,
12
15
  paths,
13
16
  readConfig
14
- } from "./chunk-5PH7PPD6.js";
17
+ } from "./chunk-OOAC5ULQ.js";
15
18
 
16
19
  // src/setup.ts
17
20
  import {
@@ -303,8 +306,8 @@ function makeTestRecord() {
303
306
  sidechainTokens: null,
304
307
  apiCalls: 1,
305
308
  costUSD: 0.01,
306
- costJPY: 1.5,
307
- fxRate: 150,
309
+ costJPY: 1.6,
310
+ fxRate: 160,
308
311
  fxSource: "fixed",
309
312
  prompt: "\u30BB\u30C3\u30C8\u30A2\u30C3\u30D7\u5B8C\u4E86\u30C6\u30B9\u30C8"
310
313
  };
@@ -635,6 +638,7 @@ async function runInit(argv) {
635
638
  if (installCodex) {
636
639
  codexResult = registerCodexHook(process.execPath, resolveCliPath());
637
640
  }
641
+ await loadPriceTable(cccn.cacheDir, { offline: false });
638
642
  const notifyDisabled = !cfg.notify.os && !cfg.notify.slack;
639
643
  if (notifyDisabled) {
640
644
  console.log("\u30C6\u30B9\u30C8\u901A\u77E5: \u901A\u77E5\u306A\u3057\u30E2\u30FC\u30C9\u306E\u305F\u3081\u30B9\u30AD\u30C3\u30D7\u3057\u307E\u3057\u305F");