tokmon 0.2.0 → 0.3.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/dist/cli.js +231 -95
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { render } from "ink";
|
|
5
5
|
|
|
6
6
|
// src/app.tsx
|
|
7
|
-
import { useState, useEffect } from "react";
|
|
7
|
+
import { useState, useEffect, useRef } from "react";
|
|
8
8
|
import { Box, Text, useInput, useStdout } from "ink";
|
|
9
9
|
|
|
10
10
|
// src/data.ts
|
|
@@ -97,11 +97,10 @@ async function parseFile(path, since) {
|
|
|
97
97
|
const ts = new Date(obj.timestamp ?? 0).getTime();
|
|
98
98
|
if (ts < since) continue;
|
|
99
99
|
const u = obj.message.usage;
|
|
100
|
-
const model = obj.message.model ?? "unknown";
|
|
101
100
|
entries.push({
|
|
102
101
|
ts,
|
|
103
|
-
model,
|
|
104
|
-
cost: costOf(model, u),
|
|
102
|
+
model: obj.message.model ?? "unknown",
|
|
103
|
+
cost: costOf(obj.message.model ?? "", u),
|
|
105
104
|
input: u.input_tokens ?? 0,
|
|
106
105
|
output: u.output_tokens ?? 0,
|
|
107
106
|
cacheCreate: u.cache_creation_input_tokens ?? 0,
|
|
@@ -152,19 +151,19 @@ function sum(entries) {
|
|
|
152
151
|
}
|
|
153
152
|
return { cost, tokens: tokens2 };
|
|
154
153
|
}
|
|
155
|
-
function
|
|
156
|
-
const
|
|
154
|
+
function groupBy(entries, keyFn) {
|
|
155
|
+
const groups = /* @__PURE__ */ new Map();
|
|
157
156
|
for (const e of entries) {
|
|
158
|
-
const
|
|
159
|
-
const arr =
|
|
157
|
+
const key = keyFn(e);
|
|
158
|
+
const arr = groups.get(key);
|
|
160
159
|
if (arr) arr.push(e);
|
|
161
|
-
else
|
|
160
|
+
else groups.set(key, [e]);
|
|
162
161
|
}
|
|
163
162
|
const rows = [];
|
|
164
|
-
for (const [
|
|
165
|
-
const models = [...new Set(
|
|
163
|
+
for (const [label, group] of groups) {
|
|
164
|
+
const models = [...new Set(group.map((e) => shortModel(e.model)))];
|
|
166
165
|
let input = 0, output = 0, cacheCreate = 0, cacheRead = 0, cost = 0;
|
|
167
|
-
for (const e of
|
|
166
|
+
for (const e of group) {
|
|
168
167
|
input += e.input;
|
|
169
168
|
output += e.output;
|
|
170
169
|
cacheCreate += e.cacheCreate;
|
|
@@ -172,7 +171,7 @@ function buildDaily(entries) {
|
|
|
172
171
|
cost += e.cost;
|
|
173
172
|
}
|
|
174
173
|
rows.push({
|
|
175
|
-
|
|
174
|
+
label,
|
|
176
175
|
models: models.sort(),
|
|
177
176
|
input,
|
|
178
177
|
output,
|
|
@@ -182,9 +181,20 @@ function buildDaily(entries) {
|
|
|
182
181
|
cost
|
|
183
182
|
});
|
|
184
183
|
}
|
|
185
|
-
return rows.sort((a, b) => a.
|
|
184
|
+
return rows.sort((a, b) => a.label.localeCompare(b.label));
|
|
186
185
|
}
|
|
187
|
-
|
|
186
|
+
function isoWeekLabel(ts) {
|
|
187
|
+
const d = new Date(ts);
|
|
188
|
+
const day = d.getDay();
|
|
189
|
+
const mondayOffset = day === 0 ? 6 : day - 1;
|
|
190
|
+
const monday = new Date(d);
|
|
191
|
+
monday.setDate(d.getDate() - mondayOffset);
|
|
192
|
+
return monday.toISOString().slice(0, 10);
|
|
193
|
+
}
|
|
194
|
+
function monthLabel(ts) {
|
|
195
|
+
return new Date(ts).toISOString().slice(0, 7);
|
|
196
|
+
}
|
|
197
|
+
async function fetchDashboard() {
|
|
188
198
|
const now = Date.now();
|
|
189
199
|
const d = /* @__PURE__ */ new Date();
|
|
190
200
|
const monthStart = new Date(d.getFullYear(), d.getMonth(), 1).getTime();
|
|
@@ -207,9 +217,18 @@ async function fetchData() {
|
|
|
207
217
|
return {
|
|
208
218
|
today: sum(entries.filter((e) => e.ts >= todayStart)),
|
|
209
219
|
week: sum(entries.filter((e) => e.ts >= weekStart)),
|
|
210
|
-
month: sum(entries),
|
|
211
|
-
block
|
|
212
|
-
|
|
220
|
+
month: sum(entries.filter((e) => e.ts >= monthStart)),
|
|
221
|
+
block
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
async function fetchTable() {
|
|
225
|
+
const d = /* @__PURE__ */ new Date();
|
|
226
|
+
const lookback = new Date(d.getFullYear(), d.getMonth() - 6, 1).getTime();
|
|
227
|
+
const entries = await loadEntries(lookback);
|
|
228
|
+
return {
|
|
229
|
+
daily: groupBy(entries, (e) => new Date(e.ts).toISOString().slice(0, 10)),
|
|
230
|
+
weekly: groupBy(entries, (e) => isoWeekLabel(e.ts)),
|
|
231
|
+
monthly: groupBy(entries, (e) => monthLabel(e.ts))
|
|
213
232
|
};
|
|
214
233
|
}
|
|
215
234
|
|
|
@@ -217,7 +236,7 @@ async function fetchData() {
|
|
|
217
236
|
import { readFile, writeFile, mkdir } from "fs/promises";
|
|
218
237
|
import { join as join2 } from "path";
|
|
219
238
|
import { homedir as homedir2 } from "os";
|
|
220
|
-
var DEFAULTS = { interval: 2 };
|
|
239
|
+
var DEFAULTS = { interval: 2, clearScreen: true };
|
|
221
240
|
function configDir() {
|
|
222
241
|
if (process.platform === "win32") {
|
|
223
242
|
return join2(process.env.APPDATA ?? join2(homedir2(), "AppData", "Roaming"), "tokmon");
|
|
@@ -248,41 +267,113 @@ function configLocation() {
|
|
|
248
267
|
|
|
249
268
|
// src/app.tsx
|
|
250
269
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
251
|
-
var TABS = ["Dashboard", "
|
|
252
|
-
|
|
253
|
-
|
|
270
|
+
var TABS = ["Dashboard", "Table"];
|
|
271
|
+
var VIEWS = ["Daily", "Weekly", "Monthly"];
|
|
272
|
+
function App({ interval: cliInterval }) {
|
|
273
|
+
const [dashboard, setDashboard] = useState(null);
|
|
274
|
+
const [table, setTable] = useState(null);
|
|
275
|
+
const [tableLoading, setTableLoading] = useState(false);
|
|
254
276
|
const [error, setError] = useState(null);
|
|
255
277
|
const [updated, setUpdated] = useState(/* @__PURE__ */ new Date());
|
|
256
278
|
const [tab, setTab] = useState(0);
|
|
279
|
+
const [view, setView] = useState(0);
|
|
257
280
|
const [scroll, setScroll] = useState(0);
|
|
258
281
|
const [showSettings, setShowSettings] = useState(false);
|
|
259
|
-
const [config, setConfig] = useState(
|
|
282
|
+
const [config, setConfig] = useState(null);
|
|
260
283
|
const [settingsCursor, setSettingsCursor] = useState(0);
|
|
284
|
+
const tableLoadedOnce = useRef(false);
|
|
261
285
|
const { stdout } = useStdout();
|
|
262
286
|
const rows = stdout?.rows ?? 24;
|
|
263
|
-
const
|
|
287
|
+
const cols = stdout?.columns ?? 80;
|
|
288
|
+
const interval2 = cliInterval ?? (config?.interval ?? 2) * 1e3;
|
|
264
289
|
useEffect(() => {
|
|
265
290
|
loadConfig().then((c) => {
|
|
266
|
-
if (
|
|
267
|
-
|
|
291
|
+
if (cliInterval) c = { ...c, interval: cliInterval / 1e3 };
|
|
292
|
+
setConfig(c);
|
|
293
|
+
if (c.clearScreen && stdout) stdout.write("\x1B[2J\x1B[H");
|
|
268
294
|
});
|
|
269
295
|
}, []);
|
|
296
|
+
useEffect(() => {
|
|
297
|
+
let active = true;
|
|
298
|
+
const load = async () => {
|
|
299
|
+
try {
|
|
300
|
+
const result = await fetchDashboard();
|
|
301
|
+
if (active) {
|
|
302
|
+
setDashboard(result);
|
|
303
|
+
setError(null);
|
|
304
|
+
setUpdated(/* @__PURE__ */ new Date());
|
|
305
|
+
}
|
|
306
|
+
} catch (e) {
|
|
307
|
+
if (active) setError(e instanceof Error ? e.message : String(e));
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
load();
|
|
311
|
+
const id = setInterval(load, interval2);
|
|
312
|
+
return () => {
|
|
313
|
+
active = false;
|
|
314
|
+
clearInterval(id);
|
|
315
|
+
};
|
|
316
|
+
}, [interval2]);
|
|
317
|
+
useEffect(() => {
|
|
318
|
+
if (tab !== 1) return;
|
|
319
|
+
if (tableLoadedOnce.current && table) return;
|
|
320
|
+
let active = true;
|
|
321
|
+
setTableLoading(true);
|
|
322
|
+
fetchTable().then((result) => {
|
|
323
|
+
if (active) {
|
|
324
|
+
setTable(result);
|
|
325
|
+
setTableLoading(false);
|
|
326
|
+
tableLoadedOnce.current = true;
|
|
327
|
+
}
|
|
328
|
+
}).catch(() => {
|
|
329
|
+
if (active) setTableLoading(false);
|
|
330
|
+
});
|
|
331
|
+
return () => {
|
|
332
|
+
active = false;
|
|
333
|
+
};
|
|
334
|
+
}, [tab]);
|
|
335
|
+
useEffect(() => {
|
|
336
|
+
if (tab !== 1 || !tableLoadedOnce.current) return;
|
|
337
|
+
let active = true;
|
|
338
|
+
const id = setInterval(async () => {
|
|
339
|
+
try {
|
|
340
|
+
const result = await fetchTable();
|
|
341
|
+
if (active) setTable(result);
|
|
342
|
+
} catch {
|
|
343
|
+
}
|
|
344
|
+
}, Math.max(interval2, 1e4));
|
|
345
|
+
return () => {
|
|
346
|
+
active = false;
|
|
347
|
+
clearInterval(id);
|
|
348
|
+
};
|
|
349
|
+
}, [tab, interval2]);
|
|
270
350
|
const isTTY = process.stdin.isTTY === true;
|
|
351
|
+
const settingsItems = 2;
|
|
352
|
+
const cfg = config ?? { interval: 2, clearScreen: true };
|
|
271
353
|
useInput((input, key) => {
|
|
272
354
|
if (showSettings) {
|
|
273
355
|
if (key.escape || input === "s") setShowSettings(false);
|
|
274
356
|
if (key.upArrow) setSettingsCursor((c) => Math.max(0, c - 1));
|
|
275
|
-
if (key.downArrow) setSettingsCursor((c) => Math.min(
|
|
276
|
-
if (
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
357
|
+
if (key.downArrow) setSettingsCursor((c) => Math.min(settingsItems - 1, c + 1));
|
|
358
|
+
if (settingsCursor === 0) {
|
|
359
|
+
if (key.leftArrow) {
|
|
360
|
+
setConfig((c) => {
|
|
361
|
+
const next = { ...c, interval: Math.max(1, c.interval - 1) };
|
|
362
|
+
saveConfig(next);
|
|
363
|
+
return next;
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
if (key.rightArrow) {
|
|
367
|
+
setConfig((c) => {
|
|
368
|
+
const next = { ...c, interval: c.interval + 1 };
|
|
369
|
+
saveConfig(next);
|
|
370
|
+
return next;
|
|
371
|
+
});
|
|
372
|
+
}
|
|
282
373
|
}
|
|
283
|
-
if (key.
|
|
374
|
+
if (settingsCursor === 1 && (key.leftArrow || key.rightArrow || key.return)) {
|
|
284
375
|
setConfig((c) => {
|
|
285
|
-
const next = { ...c,
|
|
376
|
+
const next = { ...c, clearScreen: !c.clearScreen };
|
|
286
377
|
saveConfig(next);
|
|
287
378
|
return next;
|
|
288
379
|
});
|
|
@@ -293,48 +384,62 @@ function App({ interval: initialInterval }) {
|
|
|
293
384
|
setShowSettings(true);
|
|
294
385
|
return;
|
|
295
386
|
}
|
|
296
|
-
if (key.tab
|
|
387
|
+
if (key.tab) {
|
|
297
388
|
setTab((t) => (t + 1) % TABS.length);
|
|
298
389
|
setScroll(0);
|
|
390
|
+
return;
|
|
299
391
|
}
|
|
300
|
-
if (key.leftArrow) {
|
|
301
|
-
setTab((t) => (t - 1 + TABS.length) % TABS.length);
|
|
302
|
-
setScroll(0);
|
|
303
|
-
}
|
|
304
|
-
if (key.upArrow) setScroll((s) => Math.max(0, s - 1));
|
|
305
|
-
if (key.downArrow) setScroll((s) => s + 1);
|
|
306
392
|
if (input === "1") {
|
|
307
393
|
setTab(0);
|
|
308
394
|
setScroll(0);
|
|
395
|
+
return;
|
|
309
396
|
}
|
|
310
397
|
if (input === "2") {
|
|
311
398
|
setTab(1);
|
|
312
399
|
setScroll(0);
|
|
400
|
+
return;
|
|
313
401
|
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
const result = await fetchData();
|
|
320
|
-
if (active) {
|
|
321
|
-
setData(result);
|
|
322
|
-
setError(null);
|
|
323
|
-
setUpdated(/* @__PURE__ */ new Date());
|
|
324
|
-
}
|
|
325
|
-
} catch (e) {
|
|
326
|
-
if (active) setError(e instanceof Error ? e.message : String(e));
|
|
402
|
+
if (tab === 1) {
|
|
403
|
+
if (input === "d") {
|
|
404
|
+
setView(0);
|
|
405
|
+
setScroll(0);
|
|
406
|
+
return;
|
|
327
407
|
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
408
|
+
if (input === "w") {
|
|
409
|
+
setView(1);
|
|
410
|
+
setScroll(0);
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
if (input === "m") {
|
|
414
|
+
setView(2);
|
|
415
|
+
setScroll(0);
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
if (key.leftArrow) {
|
|
419
|
+
setView((v) => (v - 1 + VIEWS.length) % VIEWS.length);
|
|
420
|
+
setScroll(0);
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
if (key.rightArrow) {
|
|
424
|
+
setView((v) => (v + 1) % VIEWS.length);
|
|
425
|
+
setScroll(0);
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
} else {
|
|
429
|
+
if (key.leftArrow || key.rightArrow) {
|
|
430
|
+
setTab((t) => (t + 1) % TABS.length);
|
|
431
|
+
setScroll(0);
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
if (key.upArrow) setScroll((s) => Math.max(0, s - 1));
|
|
436
|
+
if (key.downArrow) setScroll((s) => s + 1);
|
|
437
|
+
if (key.pageDown) setScroll((s) => s + Math.max(1, rows - 12));
|
|
438
|
+
if (key.pageUp) setScroll((s) => Math.max(0, s - Math.max(1, rows - 12)));
|
|
439
|
+
}, { isActive: isTTY });
|
|
336
440
|
if (error) return /* @__PURE__ */ jsx(Box, { padding: 1, children: /* @__PURE__ */ jsx(Text, { color: "red", children: error }) });
|
|
337
|
-
if (!
|
|
441
|
+
if (!dashboard) return /* @__PURE__ */ jsx(Box, { padding: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Loading..." }) });
|
|
442
|
+
const tableData = table ? [table.daily, table.weekly, table.monthly][view] : [];
|
|
338
443
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: 2, paddingY: 1, children: [
|
|
339
444
|
/* @__PURE__ */ jsxs(Box, { justifyContent: "space-between", children: [
|
|
340
445
|
/* @__PURE__ */ jsxs(Box, { children: [
|
|
@@ -344,20 +449,24 @@ function App({ interval: initialInterval }) {
|
|
|
344
449
|
] }),
|
|
345
450
|
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
346
451
|
" \xB7 ",
|
|
347
|
-
|
|
452
|
+
cliInterval ? cliInterval / 1e3 : cfg.interval,
|
|
348
453
|
"s"
|
|
349
454
|
] })
|
|
350
455
|
] }),
|
|
351
456
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: time(updated) })
|
|
352
457
|
] }),
|
|
353
|
-
showSettings ? /* @__PURE__ */ jsx(SettingsView, { config, cursor: settingsCursor }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
458
|
+
showSettings ? /* @__PURE__ */ jsx(SettingsView, { config: cfg, cursor: settingsCursor }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
354
459
|
/* @__PURE__ */ jsxs(Box, { marginTop: 1, children: [
|
|
355
460
|
/* @__PURE__ */ jsx(TabBar, { tabs: TABS, active: tab }),
|
|
356
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " Tab
|
|
461
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " Tab s=settings" })
|
|
357
462
|
] }),
|
|
358
463
|
/* @__PURE__ */ jsx(Box, { height: 1 }),
|
|
359
|
-
|
|
360
|
-
|
|
464
|
+
tab === 0 && /* @__PURE__ */ jsx(DashboardView, { data: dashboard }),
|
|
465
|
+
tab === 1 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
466
|
+
/* @__PURE__ */ jsx(ViewBar, { views: VIEWS, active: view }),
|
|
467
|
+
/* @__PURE__ */ jsx(Box, { height: 1 }),
|
|
468
|
+
tableLoading && !table ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Loading 6 months of history..." }) : /* @__PURE__ */ jsx(TableView, { rows: tableData, scroll, maxRows: rows - 12, wide: cols > 90 })
|
|
469
|
+
] })
|
|
361
470
|
] }),
|
|
362
471
|
/* @__PURE__ */ jsxs(Box, { marginTop: 1, children: [
|
|
363
472
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "by " }),
|
|
@@ -379,19 +488,26 @@ function TabBar({ tabs, active }) {
|
|
|
379
488
|
" "
|
|
380
489
|
] }) }, t)) });
|
|
381
490
|
}
|
|
491
|
+
function ViewBar({ views, active }) {
|
|
492
|
+
return /* @__PURE__ */ jsxs(Box, { children: [
|
|
493
|
+
views.map((v, i) => /* @__PURE__ */ jsx(Box, { marginRight: 2, children: i === active ? /* @__PURE__ */ jsxs(Text, { bold: true, color: "cyan", children: [
|
|
494
|
+
"[",
|
|
495
|
+
v,
|
|
496
|
+
"]"
|
|
497
|
+
] }) : /* @__PURE__ */ jsx(Text, { dimColor: true, children: v }) }, v)),
|
|
498
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " d/w/m or \u2190\u2192" })
|
|
499
|
+
] });
|
|
500
|
+
}
|
|
382
501
|
function SettingsView({ config, cursor }) {
|
|
383
502
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
|
|
384
503
|
/* @__PURE__ */ jsx(Text, { bold: true, children: "Settings" }),
|
|
385
|
-
/* @__PURE__ */
|
|
386
|
-
"Saved to ",
|
|
387
|
-
configLocation()
|
|
388
|
-
] }),
|
|
504
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: configLocation() }),
|
|
389
505
|
/* @__PURE__ */ jsx(Box, { height: 1 }),
|
|
390
506
|
/* @__PURE__ */ jsxs(Box, { children: [
|
|
391
|
-
|
|
392
|
-
"\u25B8",
|
|
507
|
+
/* @__PURE__ */ jsxs(Text, { color: cursor === 0 ? "green" : void 0, children: [
|
|
508
|
+
cursor === 0 ? "\u25B8" : " ",
|
|
393
509
|
" "
|
|
394
|
-
] })
|
|
510
|
+
] }),
|
|
395
511
|
/* @__PURE__ */ jsx(Text, { children: "Refresh interval " }),
|
|
396
512
|
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
397
513
|
"\u25C2",
|
|
@@ -404,11 +520,18 @@ function SettingsView({ config, cursor }) {
|
|
|
404
520
|
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
405
521
|
" ",
|
|
406
522
|
"\u25B8"
|
|
523
|
+
] })
|
|
524
|
+
] }),
|
|
525
|
+
/* @__PURE__ */ jsxs(Box, { children: [
|
|
526
|
+
/* @__PURE__ */ jsxs(Text, { color: cursor === 1 ? "green" : void 0, children: [
|
|
527
|
+
cursor === 1 ? "\u25B8" : " ",
|
|
528
|
+
" "
|
|
407
529
|
] }),
|
|
408
|
-
/* @__PURE__ */ jsx(Text, {
|
|
530
|
+
/* @__PURE__ */ jsx(Text, { children: "Clear screen " }),
|
|
531
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: config.clearScreen ? "green" : "red", children: config.clearScreen ? "on" : "off" })
|
|
409
532
|
] }),
|
|
410
533
|
/* @__PURE__ */ jsx(Box, { height: 1 }),
|
|
411
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "
|
|
534
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191\u2193 select \u2190\u2192 adjust s/Esc close" })
|
|
412
535
|
] });
|
|
413
536
|
}
|
|
414
537
|
function DashboardView({ data }) {
|
|
@@ -438,10 +561,10 @@ function DashboardView({ data }) {
|
|
|
438
561
|
] }),
|
|
439
562
|
/* @__PURE__ */ jsx(Box, { height: 1 }),
|
|
440
563
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500".repeat(50) }),
|
|
441
|
-
/* @__PURE__ */
|
|
564
|
+
/* @__PURE__ */ jsxs(Box, { width: 50, children: [
|
|
442
565
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "Total " }),
|
|
443
566
|
/* @__PURE__ */ jsx(Text, { bold: true, color: "yellowBright", children: currency(data.month.cost) })
|
|
444
|
-
] })
|
|
567
|
+
] })
|
|
445
568
|
] });
|
|
446
569
|
}
|
|
447
570
|
function BlockView({ block }) {
|
|
@@ -502,39 +625,42 @@ function ProgressBar({ percent, width = 36 }) {
|
|
|
502
625
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500".repeat(width - filled) })
|
|
503
626
|
] });
|
|
504
627
|
}
|
|
505
|
-
function
|
|
506
|
-
const W = {
|
|
507
|
-
const lineW = W.
|
|
628
|
+
function TableView({ rows: allRows, scroll, maxRows, wide }) {
|
|
629
|
+
const W = wide ? { label: 10, models: 18, input: 8, output: 8, cc: 8, cr: 9, total: 9, cost: 10 } : { label: 8, models: 14, input: 7, output: 7, cc: 7, cr: 8, total: 0, cost: 9 };
|
|
630
|
+
const lineW = W.label + W.models + W.input + W.output + W.cc + W.cr + W.total + W.cost;
|
|
508
631
|
const totals = { input: 0, output: 0, cacheCreate: 0, cacheRead: 0, cost: 0 };
|
|
509
|
-
for (const r of
|
|
632
|
+
for (const r of allRows) {
|
|
510
633
|
totals.input += r.input;
|
|
511
634
|
totals.output += r.output;
|
|
512
635
|
totals.cacheCreate += r.cacheCreate;
|
|
513
636
|
totals.cacheRead += r.cacheRead;
|
|
514
637
|
totals.cost += r.cost;
|
|
515
638
|
}
|
|
516
|
-
const
|
|
517
|
-
const
|
|
639
|
+
const clampedScroll = Math.min(scroll, Math.max(0, allRows.length - maxRows));
|
|
640
|
+
const visible = allRows.slice(clampedScroll, clampedScroll + maxRows);
|
|
641
|
+
const more = allRows.length - clampedScroll - maxRows;
|
|
518
642
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
519
643
|
/* @__PURE__ */ jsxs(Text, { children: [
|
|
520
|
-
/* @__PURE__ */ jsx(Text, { bold: true, children: col("Date", W.
|
|
644
|
+
/* @__PURE__ */ jsx(Text, { bold: true, children: col("Date", W.label, "left") }),
|
|
521
645
|
/* @__PURE__ */ jsx(Text, { bold: true, children: col("Models", W.models, "left") }),
|
|
522
646
|
/* @__PURE__ */ jsx(Text, { bold: true, children: col("Input", W.input) }),
|
|
523
647
|
/* @__PURE__ */ jsx(Text, { bold: true, children: col("Output", W.output) }),
|
|
524
648
|
/* @__PURE__ */ jsx(Text, { bold: true, children: col("CchCrt", W.cc) }),
|
|
525
649
|
/* @__PURE__ */ jsx(Text, { bold: true, children: col("CchRd", W.cr) }),
|
|
650
|
+
W.total > 0 && /* @__PURE__ */ jsx(Text, { bold: true, children: col("Total", W.total) }),
|
|
526
651
|
/* @__PURE__ */ jsx(Text, { bold: true, children: col("Cost", W.cost) })
|
|
527
652
|
] }),
|
|
528
653
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500".repeat(lineW) }),
|
|
529
654
|
visible.map((r) => /* @__PURE__ */ jsxs(Text, { children: [
|
|
530
|
-
/* @__PURE__ */ jsx(Text, { color: "cyan", children: col(
|
|
655
|
+
/* @__PURE__ */ jsx(Text, { color: "cyan", children: col(fmtLabel(r.label), W.label, "left") }),
|
|
531
656
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: col(r.models.join(", "), W.models, "left") }),
|
|
532
657
|
/* @__PURE__ */ jsx(Text, { children: col(tokens(r.input), W.input) }),
|
|
533
658
|
/* @__PURE__ */ jsx(Text, { children: col(tokens(r.output), W.output) }),
|
|
534
659
|
/* @__PURE__ */ jsx(Text, { children: col(tokens(r.cacheCreate), W.cc) }),
|
|
535
660
|
/* @__PURE__ */ jsx(Text, { children: col(tokens(r.cacheRead), W.cr) }),
|
|
661
|
+
W.total > 0 && /* @__PURE__ */ jsx(Text, { children: col(tokens(r.total), W.total) }),
|
|
536
662
|
/* @__PURE__ */ jsx(Text, { bold: true, color: "yellow", children: col(currency(r.cost), W.cost) })
|
|
537
|
-
] }, r.
|
|
663
|
+
] }, r.label)),
|
|
538
664
|
more > 0 && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
539
665
|
" \u2193 ",
|
|
540
666
|
more,
|
|
@@ -542,24 +668,34 @@ function DailyView({ daily, scroll, maxRows }) {
|
|
|
542
668
|
] }),
|
|
543
669
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500".repeat(lineW) }),
|
|
544
670
|
/* @__PURE__ */ jsxs(Text, { children: [
|
|
545
|
-
/* @__PURE__ */ jsx(Text, { bold: true, color: "greenBright", children: col("Total", W.
|
|
671
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "greenBright", children: col("Total", W.label, "left") }),
|
|
546
672
|
/* @__PURE__ */ jsx(Text, { children: col("", W.models, "left") }),
|
|
547
673
|
/* @__PURE__ */ jsx(Text, { bold: true, color: "yellow", children: col(tokens(totals.input), W.input) }),
|
|
548
674
|
/* @__PURE__ */ jsx(Text, { bold: true, color: "yellow", children: col(tokens(totals.output), W.output) }),
|
|
549
675
|
/* @__PURE__ */ jsx(Text, { bold: true, color: "yellow", children: col(tokens(totals.cacheCreate), W.cc) }),
|
|
550
676
|
/* @__PURE__ */ jsx(Text, { bold: true, color: "yellow", children: col(tokens(totals.cacheRead), W.cr) }),
|
|
677
|
+
W.total > 0 && /* @__PURE__ */ jsx(Text, { bold: true, color: "yellow", children: col(tokens(totals.input + totals.output + totals.cacheCreate + totals.cacheRead), W.total) }),
|
|
551
678
|
/* @__PURE__ */ jsx(Text, { bold: true, color: "yellowBright", children: col(currency(totals.cost), W.cost) })
|
|
552
679
|
] }),
|
|
553
680
|
/* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
554
|
-
"\u2191\u2193 scroll \xB7 ",
|
|
555
|
-
|
|
556
|
-
"
|
|
557
|
-
|
|
681
|
+
"\u2191\u2193 PgUp/Dn scroll \xB7 ",
|
|
682
|
+
allRows.length,
|
|
683
|
+
" rows \xB7 ",
|
|
684
|
+
clampedScroll + 1,
|
|
558
685
|
"-",
|
|
559
|
-
Math.min(
|
|
686
|
+
Math.min(clampedScroll + maxRows, allRows.length)
|
|
560
687
|
] }) })
|
|
561
688
|
] });
|
|
562
689
|
}
|
|
690
|
+
function fmtLabel(label) {
|
|
691
|
+
if (label.length === 10 && label[4] === "-") return shortDate(label);
|
|
692
|
+
if (label.length === 7 && label[4] === "-") {
|
|
693
|
+
const [, m] = label.split("-");
|
|
694
|
+
const months = ["", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
695
|
+
return `${months[Number(m)]} '${label.slice(2, 4)}`;
|
|
696
|
+
}
|
|
697
|
+
return shortDate(label);
|
|
698
|
+
}
|
|
563
699
|
|
|
564
700
|
// src/cli.tsx
|
|
565
701
|
import { jsx as jsx2 } from "react/jsx-runtime";
|