tokmon 0.5.3 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +143 -43
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -168,6 +168,17 @@ function groupBy(entries, keyFn) {
168
168
  cacheRead += e.cacheRead;
169
169
  cost += e.cost;
170
170
  }
171
+ const byModel = /* @__PURE__ */ new Map();
172
+ for (const e of group) {
173
+ const name = shortModel(e.model);
174
+ const m = byModel.get(name) ?? { name, input: 0, output: 0, cacheCreate: 0, cacheRead: 0, cost: 0 };
175
+ m.input += e.input;
176
+ m.output += e.output;
177
+ m.cacheCreate += e.cacheCreate;
178
+ m.cacheRead += e.cacheRead;
179
+ m.cost += e.cost;
180
+ byModel.set(name, m);
181
+ }
171
182
  rows.push({
172
183
  label,
173
184
  models: models.sort(),
@@ -176,7 +187,8 @@ function groupBy(entries, keyFn) {
176
187
  cacheCreate,
177
188
  cacheRead,
178
189
  total: input + output + cacheCreate + cacheRead,
179
- cost
190
+ cost,
191
+ breakdown: [...byModel.values()].sort((a, b) => b.cost - a.cost)
180
192
  });
181
193
  }
182
194
  return rows.sort((a, b) => a.label.localeCompare(b.label));
@@ -338,7 +350,8 @@ function App({ interval: cliInterval }) {
338
350
  const [updated, setUpdated] = useState(/* @__PURE__ */ new Date());
339
351
  const [tab, setTab] = useState(0);
340
352
  const [view, setView] = useState(0);
341
- const [scroll, setScroll] = useState(0);
353
+ const [cursor, setCursor] = useState(0);
354
+ const [expanded, setExpanded] = useState(-1);
342
355
  const [showSettings, setShowSettings] = useState(false);
343
356
  const [config2, setConfig] = useState(null);
344
357
  const [settingsCursor, setSettingsCursor] = useState(0);
@@ -459,56 +472,85 @@ function App({ interval: cliInterval }) {
459
472
  }
460
473
  if (key.tab) {
461
474
  setTab((t) => (t + 1) % TABS.length);
462
- setScroll(0);
475
+ setCursor(0);
476
+ setExpanded(-1);
463
477
  return;
464
478
  }
465
479
  if (input === "1") {
466
480
  setTab(0);
467
- setScroll(0);
481
+ setCursor(0);
482
+ setExpanded(-1);
468
483
  return;
469
484
  }
470
485
  if (input === "2") {
471
486
  setTab(1);
472
- setScroll(0);
487
+ setCursor(0);
488
+ setExpanded(-1);
473
489
  return;
474
490
  }
475
491
  if (tab === 1) {
476
492
  if (input === "d") {
477
493
  setView(0);
478
- setScroll(0);
494
+ setCursor(0);
495
+ setExpanded(-1);
479
496
  return;
480
497
  }
481
498
  if (input === "w") {
482
499
  setView(1);
483
- setScroll(0);
500
+ setCursor(0);
501
+ setExpanded(-1);
484
502
  return;
485
503
  }
486
504
  if (input === "m") {
487
505
  setView(2);
488
- setScroll(0);
506
+ setCursor(0);
507
+ setExpanded(-1);
489
508
  return;
490
509
  }
491
510
  if (key.leftArrow) {
492
511
  setView((v) => (v - 1 + VIEWS.length) % VIEWS.length);
493
- setScroll(0);
512
+ setCursor(0);
513
+ setExpanded(-1);
494
514
  return;
495
515
  }
496
516
  if (key.rightArrow) {
497
517
  setView((v) => (v + 1) % VIEWS.length);
498
- setScroll(0);
518
+ setCursor(0);
519
+ setExpanded(-1);
520
+ return;
521
+ }
522
+ if (key.return) {
523
+ setExpanded((e) => e === cursor ? -1 : cursor);
524
+ return;
525
+ }
526
+ if (key.escape) {
527
+ setExpanded(-1);
499
528
  return;
500
529
  }
501
530
  } else {
502
531
  if (key.leftArrow || key.rightArrow) {
503
532
  setTab((t) => (t + 1) % TABS.length);
504
- setScroll(0);
533
+ setCursor(0);
534
+ setExpanded(-1);
505
535
  return;
506
536
  }
507
537
  }
508
- if (key.upArrow) setScroll((s) => Math.max(0, s - 1));
509
- if (key.downArrow) setScroll((s) => s + 1);
510
- if (key.pageDown) setScroll((s) => s + Math.max(1, rows - 12));
511
- if (key.pageUp) setScroll((s) => Math.max(0, s - Math.max(1, rows - 12)));
538
+ if (key.upArrow) {
539
+ setCursor((c) => Math.max(0, c - 1));
540
+ return;
541
+ }
542
+ if (key.downArrow) {
543
+ setCursor((c) => c + 1);
544
+ return;
545
+ }
546
+ if (key.pageDown) {
547
+ setCursor((c) => c + Math.max(1, rows - 12));
548
+ return;
549
+ }
550
+ if (key.pageUp) {
551
+ setCursor((c) => Math.max(0, c - Math.max(1, rows - 12)));
552
+ return;
553
+ }
512
554
  }, { isActive: isTTY });
513
555
  if (error) return /* @__PURE__ */ jsx(Box, { padding: 1, children: /* @__PURE__ */ jsx(Text, { color: "red", children: error }) });
514
556
  if (!dashboard) return /* @__PURE__ */ jsx(Box, { padding: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Loading..." }) });
@@ -538,7 +580,7 @@ function App({ interval: cliInterval }) {
538
580
  tab === 1 && /* @__PURE__ */ jsxs(Fragment, { children: [
539
581
  /* @__PURE__ */ jsx(ViewBar, { views: VIEWS, active: view }),
540
582
  /* @__PURE__ */ jsx(Box, { height: 1 }),
541
- 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 })
583
+ tableLoading && !table ? /* @__PURE__ */ jsx(Spinner, { label: "Loading 6 months of history" }) : /* @__PURE__ */ jsx(TableView, { rows: tableData, cursor, expanded, maxRows: rows - 12, wide: cols > 90 })
542
584
  ] })
543
585
  ] }),
544
586
  /* @__PURE__ */ jsxs(Box, { marginTop: 1, children: [
@@ -701,7 +743,7 @@ function SummaryRow({ label, summary }) {
701
743
  ] }) })
702
744
  ] });
703
745
  }
704
- function TableView({ rows: allRows, scroll, maxRows, wide }) {
746
+ function TableView({ rows: allRows, cursor, expanded, maxRows, wide }) {
705
747
  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 };
706
748
  const lineW = W.label + W.models + W.input + W.output + W.cc + W.cr + W.total + W.cost;
707
749
  const totals = { input: 0, output: 0, cacheCreate: 0, cacheRead: 0, cost: 0 };
@@ -712,12 +754,15 @@ function TableView({ rows: allRows, scroll, maxRows, wide }) {
712
754
  totals.cacheRead += r.cacheRead;
713
755
  totals.cost += r.cost;
714
756
  }
715
- const clampedScroll = Math.min(scroll, Math.max(0, allRows.length - maxRows));
716
- const visible = allRows.slice(clampedScroll, clampedScroll + maxRows);
717
- const more = allRows.length - clampedScroll - maxRows;
757
+ const clampedCursor = Math.min(cursor, allRows.length - 1);
758
+ const scrollStart = Math.max(0, Math.min(clampedCursor - Math.floor(maxRows / 2), allRows.length - maxRows));
759
+ const visible = allRows.slice(scrollStart, scrollStart + maxRows);
718
760
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
719
761
  /* @__PURE__ */ jsxs(Text, { children: [
720
- /* @__PURE__ */ jsx(Text, { bold: true, children: col("Date", W.label, "left") }),
762
+ /* @__PURE__ */ jsxs(Text, { bold: true, children: [
763
+ " ",
764
+ col("Date", W.label, "left")
765
+ ] }),
721
766
  /* @__PURE__ */ jsx(Text, { bold: true, children: col("Models", W.models, "left") }),
722
767
  /* @__PURE__ */ jsx(Text, { bold: true, children: col("Input", W.input) }),
723
768
  /* @__PURE__ */ jsx(Text, { bold: true, children: col("Output", W.output) }),
@@ -726,25 +771,34 @@ function TableView({ rows: allRows, scroll, maxRows, wide }) {
726
771
  W.total > 0 && /* @__PURE__ */ jsx(Text, { bold: true, children: col("Total", W.total) }),
727
772
  /* @__PURE__ */ jsx(Text, { bold: true, children: col("Cost", W.cost) })
728
773
  ] }),
729
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500".repeat(lineW) }),
730
- visible.map((r) => /* @__PURE__ */ jsxs(Text, { children: [
731
- /* @__PURE__ */ jsx(Text, { color: "cyan", children: col(fmtLabel(r.label), W.label, "left") }),
732
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: col(r.models.join(", "), W.models, "left") }),
733
- /* @__PURE__ */ jsx(Text, { children: col(tokens(r.input), W.input) }),
734
- /* @__PURE__ */ jsx(Text, { children: col(tokens(r.output), W.output) }),
735
- /* @__PURE__ */ jsx(Text, { children: col(tokens(r.cacheCreate), W.cc) }),
736
- /* @__PURE__ */ jsx(Text, { children: col(tokens(r.cacheRead), W.cr) }),
737
- W.total > 0 && /* @__PURE__ */ jsx(Text, { children: col(tokens(r.total), W.total) }),
738
- /* @__PURE__ */ jsx(Text, { bold: true, color: "yellow", children: col(currency(r.cost), W.cost) })
739
- ] }, r.label)),
740
- more > 0 && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
741
- " \u2193 ",
742
- more,
743
- " more"
744
- ] }),
745
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500".repeat(lineW) }),
774
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500".repeat(lineW + 2) }),
775
+ visible.map((r, vi) => {
776
+ const idx = scrollStart + vi;
777
+ const selected = idx === clampedCursor;
778
+ const isExpanded = idx === expanded;
779
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
780
+ /* @__PURE__ */ jsxs(Text, { inverse: selected, children: [
781
+ /* @__PURE__ */ jsxs(Text, { color: selected ? void 0 : "cyan", children: [
782
+ selected ? "\u25B8 " : " ",
783
+ col(fmtLabel(r.label), W.label, "left")
784
+ ] }),
785
+ /* @__PURE__ */ jsx(Text, { dimColor: !selected, children: col(r.models.join(", "), W.models, "left") }),
786
+ /* @__PURE__ */ jsx(Text, { children: col(tokens(r.input), W.input) }),
787
+ /* @__PURE__ */ jsx(Text, { children: col(tokens(r.output), W.output) }),
788
+ /* @__PURE__ */ jsx(Text, { children: col(tokens(r.cacheCreate), W.cc) }),
789
+ /* @__PURE__ */ jsx(Text, { children: col(tokens(r.cacheRead), W.cr) }),
790
+ W.total > 0 && /* @__PURE__ */ jsx(Text, { children: col(tokens(r.total), W.total) }),
791
+ /* @__PURE__ */ jsx(Text, { bold: true, color: selected ? void 0 : "yellow", children: col(currency(r.cost), W.cost) })
792
+ ] }),
793
+ isExpanded && /* @__PURE__ */ jsx(RowDetail, { row: r, indent: W.label + 2 })
794
+ ] }, r.label);
795
+ }),
796
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500".repeat(lineW + 2) }),
746
797
  /* @__PURE__ */ jsxs(Text, { children: [
747
- /* @__PURE__ */ jsx(Text, { bold: true, color: "greenBright", children: col("Total", W.label, "left") }),
798
+ /* @__PURE__ */ jsxs(Text, { bold: true, color: "greenBright", children: [
799
+ " ",
800
+ col("Total", W.label, "left")
801
+ ] }),
748
802
  /* @__PURE__ */ jsx(Text, { children: col("", W.models, "left") }),
749
803
  /* @__PURE__ */ jsx(Text, { bold: true, color: "yellow", children: col(tokens(totals.input), W.input) }),
750
804
  /* @__PURE__ */ jsx(Text, { bold: true, color: "yellow", children: col(tokens(totals.output), W.output) }),
@@ -754,15 +808,61 @@ function TableView({ rows: allRows, scroll, maxRows, wide }) {
754
808
  /* @__PURE__ */ jsx(Text, { bold: true, color: "yellowBright", children: col(currency(totals.cost), W.cost) })
755
809
  ] }),
756
810
  /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
757
- "\u2191\u2193 PgUp/Dn scroll \xB7 ",
811
+ "\u2191\u2193 navigate Enter=detail Esc=close \xB7 ",
758
812
  allRows.length,
759
813
  " rows \xB7 ",
760
- clampedScroll + 1,
761
- "-",
762
- Math.min(clampedScroll + maxRows, allRows.length)
814
+ clampedCursor + 1,
815
+ "/",
816
+ allRows.length
763
817
  ] }) })
764
818
  ] });
765
819
  }
820
+ function RowDetail({ row, indent }) {
821
+ const pad = " ".repeat(indent);
822
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", paddingLeft: indent, marginY: 0, children: row.breakdown.map((m, i) => {
823
+ const last = i === row.breakdown.length - 1;
824
+ const prefix = last ? "\u2514\u2500" : "\u251C\u2500";
825
+ return /* @__PURE__ */ jsxs(Text, { children: [
826
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
827
+ prefix,
828
+ " "
829
+ ] }),
830
+ /* @__PURE__ */ jsx(Text, { bold: true, children: col(m.name, 16, "left") }),
831
+ /* @__PURE__ */ jsxs(Text, { children: [
832
+ col(tokens(m.input), 8),
833
+ " in "
834
+ ] }),
835
+ /* @__PURE__ */ jsxs(Text, { children: [
836
+ col(tokens(m.output), 8),
837
+ " out "
838
+ ] }),
839
+ /* @__PURE__ */ jsxs(Text, { children: [
840
+ col(tokens(m.cacheCreate), 8),
841
+ " cc "
842
+ ] }),
843
+ /* @__PURE__ */ jsxs(Text, { children: [
844
+ col(tokens(m.cacheRead), 9),
845
+ " cr "
846
+ ] }),
847
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "yellow", children: currency(m.cost) })
848
+ ] }, m.name);
849
+ }) });
850
+ }
851
+ function Spinner({ label }) {
852
+ const frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
853
+ const [i, setI] = useState(0);
854
+ useEffect(() => {
855
+ const id = setInterval(() => setI((n) => (n + 1) % frames.length), 80);
856
+ return () => clearInterval(id);
857
+ }, []);
858
+ return /* @__PURE__ */ jsxs(Box, { children: [
859
+ /* @__PURE__ */ jsxs(Text, { color: "green", children: [
860
+ frames[i],
861
+ " "
862
+ ] }),
863
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: label })
864
+ ] });
865
+ }
766
866
  function fmtLabel(label) {
767
867
  if (label.length === 10 && label[4] === "-") return shortDate(label);
768
868
  if (label.length === 7 && label[4] === "-") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tokmon",
3
- "version": "0.5.3",
3
+ "version": "0.6.0",
4
4
  "description": "Terminal dashboard for Claude Code usage and costs",
5
5
  "type": "module",
6
6
  "bin": {