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.
- package/README.md +129 -124
- package/dist/{budget-TBWVHUKA.js → budget-V7CCMJHF.js} +1 -1
- package/dist/{chunk-O34L3NSI.js → chunk-27SJELD2.js} +107 -22
- package/dist/chunk-6HTETN26.js +226 -0
- package/dist/{chunk-QWBQN4XY.js → chunk-6UZXXO5J.js} +41 -18
- package/dist/{chunk-S3XKO7MY.js → chunk-CH34OJ7Q.js} +7 -3
- package/dist/{chunk-LVMKY6JB.js → chunk-D4D76RGQ.js} +27 -217
- package/dist/{chunk-5PH7PPD6.js → chunk-OOAC5ULQ.js} +25 -1
- package/dist/chunk-OYD6H3ZZ.js +124 -0
- package/dist/{chunk-DDUK4EWQ.js → chunk-T6Z2ZAN4.js} +1 -1
- package/dist/cli.js +26 -24
- package/dist/{dashboard-6F2QNUJT.js → dashboard-VEKQ4TI3.js} +3 -4
- package/dist/{history-7KN26LJH.js → history-DL4SG3PG.js} +3 -5
- package/dist/{mute-MPX3K7QN.js → mute-UIR5P5N5.js} +2 -2
- package/dist/{setup-IXPMVWOE.js → setup-3SRNQ5PG.js} +4 -3
- package/dist/{subagent-store-A6MIN22X.js → subagent-store-US4TJJQR.js} +1 -1
- package/dist/sweep-65D57PXU.js +795 -0
- package/dist/{track-KEZON6KI.js → track-WCP7446C.js} +40 -28
- package/package.json +1 -1
- package/dist/chunk-ENGUOLTD.js +0 -92
- package/dist/chunk-O2AR4PXU.js +0 -82
- package/dist/sweep-UIONM4UA.js +0 -589
|
@@ -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-
|
|
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-
|
|
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
|
-
|
|
691
|
-
|
|
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\
|
|
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\
|
|
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\
|
|
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
|
-
|
|
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
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
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
|
|
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-
|
|
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.
|
|
307
|
-
fxRate:
|
|
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");
|