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.
Files changed (2) hide show
  1. package/dist/cli.js +231 -95
  2. 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 buildDaily(entries) {
156
- const byDate = /* @__PURE__ */ new Map();
154
+ function groupBy(entries, keyFn) {
155
+ const groups = /* @__PURE__ */ new Map();
157
156
  for (const e of entries) {
158
- const date = new Date(e.ts).toISOString().slice(0, 10);
159
- const arr = byDate.get(date);
157
+ const key = keyFn(e);
158
+ const arr = groups.get(key);
160
159
  if (arr) arr.push(e);
161
- else byDate.set(date, [e]);
160
+ else groups.set(key, [e]);
162
161
  }
163
162
  const rows = [];
164
- for (const [date, dayEntries] of byDate) {
165
- const models = [...new Set(dayEntries.map((e) => shortModel(e.model)))];
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 dayEntries) {
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
- date,
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.date.localeCompare(b.date));
184
+ return rows.sort((a, b) => a.label.localeCompare(b.label));
186
185
  }
187
- async function fetchData() {
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
- daily: buildDaily(entries)
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", "Daily"];
252
- function App({ interval: initialInterval }) {
253
- const [data, setData] = useState(null);
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({ interval: (initialInterval ?? 2e3) / 1e3 });
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 interval2 = config.interval * 1e3;
287
+ const cols = stdout?.columns ?? 80;
288
+ const interval2 = cliInterval ?? (config?.interval ?? 2) * 1e3;
264
289
  useEffect(() => {
265
290
  loadConfig().then((c) => {
266
- if (!initialInterval) setConfig(c);
267
- else setConfig({ ...c, interval: initialInterval / 1e3 });
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(0, c + 1));
276
- if (key.leftArrow && settingsCursor === 0) {
277
- setConfig((c) => {
278
- const next = { ...c, interval: Math.max(1, c.interval - 1) };
279
- saveConfig(next);
280
- return next;
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.rightArrow && settingsCursor === 0) {
374
+ if (settingsCursor === 1 && (key.leftArrow || key.rightArrow || key.return)) {
284
375
  setConfig((c) => {
285
- const next = { ...c, interval: c.interval + 1 };
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 || key.rightArrow) {
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
- }, { isActive: isTTY });
315
- useEffect(() => {
316
- let active = true;
317
- const load = async () => {
318
- try {
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
- load();
330
- const id = setInterval(load, interval2);
331
- return () => {
332
- active = false;
333
- clearInterval(id);
334
- };
335
- }, [interval2]);
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 (!data) return /* @__PURE__ */ jsx(Box, { padding: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Loading..." }) });
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
- config.interval,
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/\u2190\u2192 s=settings" })
461
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: " Tab s=settings" })
357
462
  ] }),
358
463
  /* @__PURE__ */ jsx(Box, { height: 1 }),
359
- TABS[tab] === "Dashboard" && /* @__PURE__ */ jsx(DashboardView, { data }),
360
- TABS[tab] === "Daily" && /* @__PURE__ */ jsx(DailyView, { daily: data.daily, scroll, maxRows: rows - 10 })
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__ */ jsxs(Text, { dimColor: true, children: [
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
- cursor === 0 ? /* @__PURE__ */ jsxs(Text, { color: "green", children: [
392
- "\u25B8",
507
+ /* @__PURE__ */ jsxs(Text, { color: cursor === 0 ? "green" : void 0, children: [
508
+ cursor === 0 ? "\u25B8" : " ",
393
509
  " "
394
- ] }) : /* @__PURE__ */ jsx(Text, { children: " " }),
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, { dimColor: true, children: " (\u2190\u2192 to adjust)" })
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: "Press s or Esc to close" })
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__ */ jsx(Box, { justifyContent: "space-between", width: 50, children: /* @__PURE__ */ jsxs(Box, { children: [
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 DailyView({ daily, scroll, maxRows }) {
506
- const W = { date: 7, models: 16, input: 8, output: 8, cc: 8, cr: 8, cost: 10 };
507
- const lineW = W.date + W.models + W.input + W.output + W.cc + W.cr + W.cost;
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 daily) {
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 visible = daily.slice(scroll, scroll + maxRows);
517
- const more = daily.length - scroll - maxRows;
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.date, "left") }),
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(shortDate(r.date), W.date, "left") }),
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.date)),
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.date, "left") }),
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
- daily.length,
556
- " days \xB7 ",
557
- scroll + 1,
681
+ "\u2191\u2193 PgUp/Dn scroll \xB7 ",
682
+ allRows.length,
683
+ " rows \xB7 ",
684
+ clampedScroll + 1,
558
685
  "-",
559
- Math.min(scroll + maxRows, daily.length)
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";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tokmon",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "Terminal dashboard for Claude Code usage and costs",
5
5
  "type": "module",
6
6
  "bin": {