tokmon 0.11.3 → 0.12.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 +740 -124
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -8,7 +8,15 @@ import { MouseProvider } from "@zenobius/ink-mouse";
8
8
  import { readFile, writeFile, mkdir } from "fs/promises";
9
9
  import { join } from "path";
10
10
  import { homedir } from "os";
11
- var DEFAULTS = { interval: 2, billingInterval: 5, clearScreen: true, timezone: null };
11
+ var DEFAULTS = {
12
+ interval: 2,
13
+ billingInterval: 5,
14
+ clearScreen: true,
15
+ timezone: null,
16
+ accounts: [],
17
+ activeAccountId: null
18
+ };
19
+ var ACCENT_COLORS = ["cyan", "magenta", "green", "yellow", "blue", "red"];
12
20
  function configDir() {
13
21
  if (process.platform === "win32") {
14
22
  return join(process.env.APPDATA ?? join(homedir(), "AppData", "Roaming"), "tokmon");
@@ -23,7 +31,12 @@ async function loadConfig() {
23
31
  try {
24
32
  const raw = await readFile(configLocation(), "utf-8");
25
33
  const parsed = JSON.parse(raw);
26
- return { ...DEFAULTS, ...parsed };
34
+ return {
35
+ ...DEFAULTS,
36
+ ...parsed,
37
+ accounts: Array.isArray(parsed.accounts) ? parsed.accounts : [],
38
+ activeAccountId: typeof parsed.activeAccountId === "string" ? parsed.activeAccountId : null
39
+ };
27
40
  } catch {
28
41
  return { ...DEFAULTS };
29
42
  }
@@ -33,6 +46,32 @@ async function saveConfig(config2) {
33
46
  await mkdir(dir, { recursive: true });
34
47
  await writeFile(configLocation(), JSON.stringify(config2, null, 2) + "\n");
35
48
  }
49
+ function slugify(value) {
50
+ return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 48);
51
+ }
52
+ function generateAccountId(name, existing) {
53
+ const base = slugify(name) || "account";
54
+ const taken = new Set(existing.map((a) => a.id));
55
+ if (!taken.has(base)) return base;
56
+ for (let i = 2; i < 1e3; i++) {
57
+ const candidate = `${base}_${i}`;
58
+ if (!taken.has(candidate)) return candidate;
59
+ }
60
+ return `${base}_${Date.now()}`;
61
+ }
62
+ function pickAccentColor(existing) {
63
+ const used = new Set(existing.map((a) => a.color).filter(Boolean));
64
+ for (const c of ACCENT_COLORS) {
65
+ if (!used.has(c)) return c;
66
+ }
67
+ return ACCENT_COLORS[existing.length % ACCENT_COLORS.length];
68
+ }
69
+ function expandHome(p) {
70
+ if (!p) return homedir();
71
+ if (p === "~" || p === "~/") return homedir();
72
+ if (p.startsWith("~/")) return join(homedir(), p.slice(2));
73
+ return p;
74
+ }
36
75
 
37
76
  // src/app.tsx
38
77
  import { useState, useEffect, useCallback, useRef } from "react";
@@ -163,7 +202,13 @@ var PRICING = {
163
202
  };
164
203
  var FALLBACK = PRICING["claude-opus-4"];
165
204
  var fileCache = /* @__PURE__ */ new Map();
166
- function getClaudeDirs() {
205
+ function getClaudeDirs(homeDir) {
206
+ if (homeDir) {
207
+ return [
208
+ join2(homeDir, ".claude", "projects"),
209
+ join2(homeDir, ".config", "claude", "projects")
210
+ ];
211
+ }
167
212
  const home = homedir2();
168
213
  const dirs = [join2(home, ".claude", "projects")];
169
214
  if (process.env.XDG_CONFIG_HOME) {
@@ -220,10 +265,10 @@ async function parseFile(path, since) {
220
265
  }
221
266
  return entries;
222
267
  }
223
- async function loadEntries(since) {
268
+ async function loadEntries(since, homeDir) {
224
269
  const chunks = [];
225
270
  const seen = /* @__PURE__ */ new Set();
226
- for (const dir of getClaudeDirs()) {
271
+ for (const dir of getClaudeDirs(homeDir)) {
227
272
  let listing;
228
273
  try {
229
274
  listing = await readdir(dir, { recursive: true });
@@ -318,12 +363,12 @@ function groupBy(entries, keyFn) {
318
363
  }
319
364
  return rows.sort((a, b) => a.label.localeCompare(b.label));
320
365
  }
321
- async function fetchDashboard(tz) {
366
+ async function fetchDashboard(tz, homeDir) {
322
367
  const now = Date.now();
323
368
  const monthStart = startOfMonth(now, tz);
324
369
  const todayStart = startOfDay(now, tz);
325
370
  const weekStart = startOfWeek(now, tz);
326
- const entries = await loadEntries(monthStart);
371
+ const entries = await loadEntries(monthStart, homeDir);
327
372
  const todayEntries = entries.filter((e) => e.ts >= todayStart);
328
373
  let burnRate = 0;
329
374
  if (todayEntries.length > 0) {
@@ -343,9 +388,9 @@ async function fetchDashboard(tz) {
343
388
  burnRate
344
389
  };
345
390
  }
346
- async function fetchTable(tz) {
391
+ async function fetchTable(tz, homeDir) {
347
392
  const lookback = monthsAgoStart(Date.now(), 6, tz);
348
- const entries = await loadEntries(lookback);
393
+ const entries = await loadEntries(lookback, homeDir);
349
394
  return {
350
395
  daily: groupBy(entries, (e) => dayKey(e.ts, tz)),
351
396
  weekly: groupBy(entries, (e) => weekKey(e.ts, tz)),
@@ -360,13 +405,14 @@ import { join as join3 } from "path";
360
405
  import { homedir as homedir3 } from "os";
361
406
  import { promisify } from "util";
362
407
  var execFile = promisify(execFileCb);
363
- function credentialsFilePath() {
408
+ function credentialsFilePath(homeDir) {
409
+ if (homeDir) return join3(homeDir, ".claude", ".credentials.json");
364
410
  const base = process.env.CLAUDE_CONFIG_DIR ?? join3(homedir3(), ".claude");
365
411
  return join3(base, ".credentials.json");
366
412
  }
367
- async function readCredentialsFile() {
413
+ async function readCredentialsFile(homeDir) {
368
414
  try {
369
- const raw = await readFile2(credentialsFilePath(), "utf-8");
415
+ const raw = await readFile2(credentialsFilePath(homeDir), "utf-8");
370
416
  const creds = JSON.parse(raw);
371
417
  return creds?.claudeAiOauth?.accessToken ?? creds?.accessToken ?? null;
372
418
  } catch {
@@ -387,7 +433,12 @@ async function readMacKeychain() {
387
433
  return null;
388
434
  }
389
435
  }
390
- async function getAccessToken() {
436
+ async function getAccessToken(homeDir) {
437
+ if (homeDir) {
438
+ const fromFile = await readCredentialsFile(homeDir);
439
+ if (fromFile) return fromFile;
440
+ return null;
441
+ }
391
442
  if (process.platform === "darwin") {
392
443
  const token = await readMacKeychain();
393
444
  if (token) return token;
@@ -395,8 +446,8 @@ async function getAccessToken() {
395
446
  return readCredentialsFile();
396
447
  }
397
448
  var EMPTY = { session: null, weekly: null, sonnet: null, extraUsage: null, peak: null, error: null };
398
- async function fetchBilling() {
399
- const token = await getAccessToken();
449
+ async function fetchBilling(homeDir) {
450
+ const token = await getAccessToken(homeDir);
400
451
  if (!token) return { ...EMPTY, error: "No OAuth token \u2014 run claude and log in" };
401
452
  const [usageRes, peak] = await Promise.all([
402
453
  fetchUsage(token),
@@ -519,12 +570,36 @@ var VIEWS = ["Daily", "Weekly", "Monthly"];
519
570
  var SORTS = ["date \u2191", "date \u2193", "cost \u2191", "cost \u2193"];
520
571
  var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
521
572
  var MONTHS = ["", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
522
- var DEFAULT_CONFIG = { interval: 2, billingInterval: 5, clearScreen: true, timezone: null };
523
- var SETTINGS_ROWS = 4;
573
+ var DEFAULT_CONFIG = {
574
+ interval: 2,
575
+ billingInterval: 5,
576
+ clearScreen: true,
577
+ timezone: null,
578
+ accounts: [],
579
+ activeAccountId: null
580
+ };
581
+ var GENERAL_ROWS = 4;
524
582
  var IS_TTY = process.stdin.isTTY === true;
583
+ function buildSlots(config2) {
584
+ const slots = [];
585
+ if (config2.accounts.length === 0) {
586
+ slots.push({ id: null, name: "Default", homeDir: void 0, color: "green" });
587
+ return slots;
588
+ }
589
+ slots.push({ id: null, name: "All", homeDir: void 0, color: "whiteBright" });
590
+ for (const a of config2.accounts) {
591
+ slots.push({
592
+ id: a.id,
593
+ name: a.name,
594
+ homeDir: expandHome(a.homeDir),
595
+ color: a.color || "cyan"
596
+ });
597
+ }
598
+ return slots;
599
+ }
525
600
  function App({ interval: cliInterval }) {
526
- const [dashboard, setDashboard] = useState(null);
527
- const [billing, setBilling] = useState(null);
601
+ const [config2, setConfig] = useState(null);
602
+ const [stats, setStats] = useState(/* @__PURE__ */ new Map());
528
603
  const [table, setTable] = useState(null);
529
604
  const [tableLoading, setTableLoading] = useState(false);
530
605
  const [error, setError] = useState(null);
@@ -535,18 +610,28 @@ function App({ interval: cliInterval }) {
535
610
  const [expanded, setExpanded] = useState(-1);
536
611
  const [sort, setSort] = useState(1);
537
612
  const [showSettings, setShowSettings] = useState(false);
538
- const [config2, setConfig] = useState(null);
539
613
  const [settingsCursor, setSettingsCursor] = useState(0);
540
614
  const [tzEdit, setTzEdit] = useState(null);
541
615
  const [tzError, setTzError] = useState(null);
616
+ const [accountForm, setAccountForm] = useState(null);
542
617
  const tableLoadedOnce = useRef(false);
543
618
  const { stdout } = useStdout();
544
619
  const { exit } = useApp();
545
620
  const rows = stdout?.rows ?? 24;
546
621
  const cols = stdout?.columns ?? 80;
547
- const interval2 = cliInterval ?? (config2?.interval ?? 2) * 1e3;
548
622
  const cfg = config2 ?? DEFAULT_CONFIG;
623
+ const interval2 = cliInterval ?? cfg.interval * 1e3;
549
624
  const tz = resolveTimezone(cfg.timezone);
625
+ const slots = buildSlots(cfg);
626
+ const activeSlotIdx = (() => {
627
+ if (cfg.accounts.length === 0) return 0;
628
+ if (cfg.activeAccountId === null) return 0;
629
+ const i = slots.findIndex((s) => s.id === cfg.activeAccountId);
630
+ return i < 0 ? 0 : i;
631
+ })();
632
+ const activeSlot = slots[activeSlotIdx];
633
+ const slotKey = (s) => s.id ?? "__default__";
634
+ const visibleSlots = activeSlot.id === null && cfg.accounts.length > 0 ? slots.slice(1) : [activeSlot];
550
635
  useEffect(() => {
551
636
  loadConfig().then((c) => {
552
637
  if (cliInterval) c = { ...c, interval: cliInterval / 1e3 };
@@ -554,12 +639,21 @@ function App({ interval: cliInterval }) {
554
639
  });
555
640
  }, []);
556
641
  useEffect(() => {
642
+ if (!config2) return;
557
643
  let active = true;
644
+ const slotsToLoad = activeSlot.id === null && cfg.accounts.length > 0 ? slots.slice(1) : [activeSlot];
558
645
  const load = async () => {
559
646
  try {
560
- const result = await fetchDashboard(tz);
647
+ const next = /* @__PURE__ */ new Map();
648
+ await Promise.all(slotsToLoad.map(async (slot) => {
649
+ const [dashboard, billing] = await Promise.all([
650
+ fetchDashboard(tz, slot.homeDir),
651
+ fetchBilling(slot.homeDir)
652
+ ]);
653
+ next.set(slotKey(slot), { slot, dashboard, billing });
654
+ }));
561
655
  if (active) {
562
- setDashboard(result);
656
+ setStats(next);
563
657
  setError(null);
564
658
  setUpdated(/* @__PURE__ */ new Date());
565
659
  }
@@ -573,31 +667,17 @@ function App({ interval: cliInterval }) {
573
667
  active = false;
574
668
  clearInterval(id);
575
669
  };
576
- }, [interval2, tz]);
577
- const billingMs = cfg.billingInterval * 6e4;
578
- useEffect(() => {
579
- let active = true;
580
- const load = () => fetchBilling().then((b) => {
581
- if (active) setBilling(b);
582
- }).catch(() => {
583
- });
584
- load();
585
- const id = setInterval(load, billingMs);
586
- return () => {
587
- active = false;
588
- clearInterval(id);
589
- };
590
- }, [billingMs]);
670
+ }, [interval2, tz, config2, activeSlot.id]);
591
671
  useEffect(() => {
592
672
  tableLoadedOnce.current = false;
593
673
  setTable(null);
594
- }, [tz]);
674
+ }, [tz, activeSlot.id]);
595
675
  useEffect(() => {
596
676
  if (tab !== 1) return;
597
677
  if (tableLoadedOnce.current && table) return;
598
678
  let active = true;
599
679
  setTableLoading(true);
600
- fetchTable(tz).then((result) => {
680
+ fetchTable(tz, activeSlot.id === null ? void 0 : activeSlot.homeDir).then((result) => {
601
681
  if (active) {
602
682
  setTable(result);
603
683
  setTableLoading(false);
@@ -609,13 +689,13 @@ function App({ interval: cliInterval }) {
609
689
  return () => {
610
690
  active = false;
611
691
  };
612
- }, [tab, tz]);
692
+ }, [tab, tz, activeSlot.id]);
613
693
  useEffect(() => {
614
694
  if (tab !== 1 || !tableLoadedOnce.current) return;
615
695
  let active = true;
616
696
  const id = setInterval(async () => {
617
697
  try {
618
- const result = await fetchTable(tz);
698
+ const result = await fetchTable(tz, activeSlot.id === null ? void 0 : activeSlot.homeDir);
619
699
  if (active) setTable(result);
620
700
  } catch {
621
701
  }
@@ -624,7 +704,7 @@ function App({ interval: cliInterval }) {
624
704
  active = false;
625
705
  clearInterval(id);
626
706
  };
627
- }, [tab, interval2, tz]);
707
+ }, [tab, interval2, tz, activeSlot.id]);
628
708
  const resetView = useCallback(() => {
629
709
  setCursor(0);
630
710
  setExpanded(-1);
@@ -643,7 +723,156 @@ function App({ interval: cliInterval }) {
643
723
  mouse.events.off("scroll", onScroll);
644
724
  };
645
725
  }, [tab]);
726
+ function updateConfig(fn) {
727
+ setConfig((prev) => {
728
+ const next = fn(prev ?? DEFAULT_CONFIG);
729
+ saveConfig(next);
730
+ return next;
731
+ });
732
+ }
733
+ function cycleAccount(dir) {
734
+ if (slots.length <= 1) return;
735
+ const next = (activeSlotIdx + dir + slots.length) % slots.length;
736
+ const targetId = slots[next].id;
737
+ updateConfig((c) => ({ ...c, activeAccountId: targetId }));
738
+ resetView();
739
+ }
740
+ function openAddAccount() {
741
+ setAccountForm({
742
+ mode: "add",
743
+ field: "name",
744
+ name: "",
745
+ homeDir: "~",
746
+ color: pickAccentColor(cfg.accounts),
747
+ editingId: null,
748
+ error: null
749
+ });
750
+ }
751
+ function openEditAccount(acc) {
752
+ setAccountForm({
753
+ mode: "edit",
754
+ field: "name",
755
+ name: acc.name,
756
+ homeDir: acc.homeDir,
757
+ color: acc.color || "cyan",
758
+ editingId: acc.id,
759
+ error: null
760
+ });
761
+ }
762
+ function commitAccountForm() {
763
+ if (!accountForm) return;
764
+ const name = accountForm.name.trim();
765
+ const homeDir = accountForm.homeDir.trim() || "~";
766
+ const color = accountForm.color;
767
+ if (!name) {
768
+ setAccountForm({ ...accountForm, error: "Name required", field: "name" });
769
+ return;
770
+ }
771
+ updateConfig((c) => {
772
+ if (accountForm.mode === "add") {
773
+ const id = generateAccountId(name, c.accounts);
774
+ const account = { id, name, homeDir, color };
775
+ return {
776
+ ...c,
777
+ accounts: [...c.accounts, account],
778
+ activeAccountId: c.accounts.length === 0 ? id : c.activeAccountId
779
+ };
780
+ } else {
781
+ return {
782
+ ...c,
783
+ accounts: c.accounts.map(
784
+ (a) => a.id === accountForm.editingId ? { ...a, name, homeDir, color } : a
785
+ )
786
+ };
787
+ }
788
+ });
789
+ setAccountForm(null);
790
+ }
791
+ function cycleFormField(dir) {
792
+ const order = ["name", "homeDir", "color"];
793
+ setAccountForm((f) => {
794
+ if (!f) return f;
795
+ const i = order.indexOf(f.field);
796
+ const next = order[(i + dir + order.length) % order.length];
797
+ return { ...f, field: next };
798
+ });
799
+ }
800
+ function cycleColor(dir) {
801
+ setAccountForm((f) => {
802
+ if (!f) return f;
803
+ const i = COLOR_PALETTE.indexOf(f.color);
804
+ const idx = i < 0 ? 0 : i;
805
+ const next = COLOR_PALETTE[(idx + dir + COLOR_PALETTE.length) % COLOR_PALETTE.length];
806
+ return { ...f, color: next };
807
+ });
808
+ }
809
+ function deleteAccount(id) {
810
+ updateConfig((c) => ({
811
+ ...c,
812
+ accounts: c.accounts.filter((a) => a.id !== id),
813
+ activeAccountId: c.activeAccountId === id ? null : c.activeAccountId
814
+ }));
815
+ }
816
+ const accountRowsStart = GENERAL_ROWS;
817
+ const totalSettingsRows = GENERAL_ROWS + cfg.accounts.length + 1;
646
818
  useInput((input, key) => {
819
+ if (showSettings && accountForm) {
820
+ if (key.escape) {
821
+ setAccountForm(null);
822
+ return;
823
+ }
824
+ if (key.tab) {
825
+ cycleFormField(key.shift ? -1 : 1);
826
+ return;
827
+ }
828
+ if (key.upArrow) {
829
+ cycleFormField(-1);
830
+ return;
831
+ }
832
+ if (key.downArrow) {
833
+ cycleFormField(1);
834
+ return;
835
+ }
836
+ if (accountForm.field === "color") {
837
+ if (key.leftArrow) {
838
+ cycleColor(-1);
839
+ return;
840
+ }
841
+ if (key.rightArrow) {
842
+ cycleColor(1);
843
+ return;
844
+ }
845
+ if (key.return) {
846
+ commitAccountForm();
847
+ return;
848
+ }
849
+ return;
850
+ }
851
+ if (key.return) {
852
+ if (accountForm.field === "name") {
853
+ setAccountForm((f) => f && { ...f, field: "homeDir" });
854
+ } else if (accountForm.field === "homeDir") {
855
+ setAccountForm((f) => f && { ...f, field: "color" });
856
+ }
857
+ return;
858
+ }
859
+ if (key.backspace || key.delete) {
860
+ setAccountForm((f) => {
861
+ if (!f || f.field === "color") return f;
862
+ const cur = f[f.field];
863
+ return { ...f, [f.field]: cur.slice(0, -1), error: null };
864
+ });
865
+ return;
866
+ }
867
+ if (input && !key.ctrl && !key.meta) {
868
+ setAccountForm((f) => {
869
+ if (!f || f.field === "color") return f;
870
+ return { ...f, [f.field]: f[f.field] + input, error: null };
871
+ });
872
+ return;
873
+ }
874
+ return;
875
+ }
647
876
  if (showSettings && tzEdit !== null) {
648
877
  if (key.escape) {
649
878
  setTzEdit(null);
@@ -682,19 +911,31 @@ function App({ interval: cliInterval }) {
682
911
  return;
683
912
  }
684
913
  if (showSettings) {
685
- if (key.escape || input === "s") setShowSettings(false);
686
- if (key.upArrow) setSettingsCursor((c) => Math.max(0, c - 1));
687
- if (key.downArrow) setSettingsCursor((c) => Math.min(SETTINGS_ROWS - 1, c + 1));
914
+ if (key.escape || input === "s") {
915
+ setShowSettings(false);
916
+ return;
917
+ }
918
+ if (key.upArrow) {
919
+ setSettingsCursor((c) => Math.max(0, c - 1));
920
+ return;
921
+ }
922
+ if (key.downArrow) {
923
+ setSettingsCursor((c) => Math.min(totalSettingsRows - 1, c + 1));
924
+ return;
925
+ }
688
926
  if (settingsCursor === 0) {
689
927
  if (key.leftArrow) updateConfig((c) => ({ ...c, interval: Math.max(1, c.interval - 1) }));
690
928
  if (key.rightArrow) updateConfig((c) => ({ ...c, interval: c.interval + 1 }));
929
+ return;
691
930
  }
692
931
  if (settingsCursor === 1) {
693
932
  if (key.leftArrow) updateConfig((c) => ({ ...c, billingInterval: Math.max(1, c.billingInterval - 1) }));
694
933
  if (key.rightArrow) updateConfig((c) => ({ ...c, billingInterval: c.billingInterval + 1 }));
934
+ return;
695
935
  }
696
936
  if (settingsCursor === 2 && (key.leftArrow || key.rightArrow || key.return)) {
697
937
  updateConfig((c) => ({ ...c, clearScreen: !c.clearScreen }));
938
+ return;
698
939
  }
699
940
  if (settingsCursor === 3) {
700
941
  if (key.return) {
@@ -704,11 +945,42 @@ function App({ interval: cliInterval }) {
704
945
  if (key.leftArrow || key.rightArrow) {
705
946
  updateConfig((c) => ({ ...c, timezone: c.timezone === null ? systemTimezone() : null }));
706
947
  }
948
+ return;
949
+ }
950
+ const accIdx = settingsCursor - accountRowsStart;
951
+ if (accIdx >= 0 && accIdx < cfg.accounts.length) {
952
+ const acc = cfg.accounts[accIdx];
953
+ if (key.return) {
954
+ openEditAccount(acc);
955
+ return;
956
+ }
957
+ if (input === "d" || input === "x") {
958
+ deleteAccount(acc.id);
959
+ return;
960
+ }
961
+ if (input === " ") {
962
+ updateConfig((c) => ({ ...c, activeAccountId: acc.id }));
963
+ return;
964
+ }
965
+ return;
966
+ }
967
+ if (accIdx === cfg.accounts.length && key.return) {
968
+ openAddAccount();
969
+ return;
707
970
  }
708
971
  return;
709
972
  }
710
973
  if (input === "s") {
711
974
  setShowSettings(true);
975
+ setSettingsCursor(0);
976
+ return;
977
+ }
978
+ if (input === "a") {
979
+ cycleAccount(1);
980
+ return;
981
+ }
982
+ if (input === "A") {
983
+ cycleAccount(-1);
712
984
  return;
713
985
  }
714
986
  if (key.tab) {
@@ -789,15 +1061,11 @@ function App({ interval: cliInterval }) {
789
1061
  return;
790
1062
  }
791
1063
  }, { isActive: IS_TTY });
792
- function updateConfig(fn) {
793
- setConfig((prev) => {
794
- const next = fn(prev ?? DEFAULT_CONFIG);
795
- saveConfig(next);
796
- return next;
797
- });
798
- }
799
1064
  if (error) return /* @__PURE__ */ jsx(Box, { padding: 1, children: /* @__PURE__ */ jsx(Text, { color: "red", children: error }) });
800
- if (!dashboard) return /* @__PURE__ */ jsx(Box, { padding: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Loading..." }) });
1065
+ if (!config2) return /* @__PURE__ */ jsx(Box, { padding: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Loading..." }) });
1066
+ const firstStats = stats.size > 0 ? [...stats.values()][0] : null;
1067
+ if (!firstStats?.dashboard) return /* @__PURE__ */ jsx(Box, { padding: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Loading..." }) });
1068
+ const peakBilling = firstStats.billing;
801
1069
  const rawTableData = table ? [table.daily, table.weekly, table.monthly][view] : [];
802
1070
  const tableData = sortRows(rawTableData, sort);
803
1071
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: 2, paddingY: 1, height: rows, children: [
@@ -809,20 +1077,47 @@ function App({ interval: cliInterval }) {
809
1077
  ] }),
810
1078
  /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
811
1079
  " \xB7 every ",
812
- cliInterval ? cliInterval / 1e3 : cfg.interval,
1080
+ cfg.interval,
813
1081
  "s"
814
1082
  ] })
815
1083
  ] }),
816
1084
  /* @__PURE__ */ jsxs(Box, { children: [
817
- billing?.peak && /* @__PURE__ */ jsxs(Fragment, { children: [
818
- /* @__PURE__ */ jsx(PeakBadge, { peak: billing.peak }),
1085
+ peakBilling?.peak && /* @__PURE__ */ jsxs(Fragment, { children: [
1086
+ /* @__PURE__ */ jsx(PeakBadge, { peak: peakBilling.peak }),
819
1087
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: " \xB7 " })
820
1088
  ] }),
821
1089
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: time(updated, tz) })
822
1090
  ] })
823
1091
  ] }),
824
- showSettings ? /* @__PURE__ */ jsx(SettingsView, { config: cfg, cursor: settingsCursor, tzEdit, tzError, resolvedTz: tz }) : /* @__PURE__ */ jsxs(Fragment, { children: [
825
- /* @__PURE__ */ jsxs(Box, { marginTop: 1, children: [
1092
+ showSettings ? /* @__PURE__ */ jsx(
1093
+ SettingsView,
1094
+ {
1095
+ config: cfg,
1096
+ cursor: settingsCursor,
1097
+ tzEdit,
1098
+ tzError,
1099
+ resolvedTz: tz,
1100
+ accountForm,
1101
+ onSettingsClick: (i) => setSettingsCursor(i),
1102
+ onAddAccount: openAddAccount,
1103
+ onEditAccount: openEditAccount,
1104
+ onActivateAccount: (id) => updateConfig((c) => ({ ...c, activeAccountId: id })),
1105
+ activeAccountId: cfg.activeAccountId
1106
+ }
1107
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
1108
+ slots.length > 1 && /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(
1109
+ AccountStrip,
1110
+ {
1111
+ slots,
1112
+ activeIdx: activeSlotIdx,
1113
+ onSelect: (i) => {
1114
+ const id = slots[i].id;
1115
+ updateConfig((c) => ({ ...c, activeAccountId: id }));
1116
+ resetView();
1117
+ }
1118
+ }
1119
+ ) }),
1120
+ /* @__PURE__ */ jsxs(Box, { marginTop: slots.length > 1 ? 0 : 1, children: [
826
1121
  /* @__PURE__ */ jsx(TabBar, { tabs: TABS, active: tab, onSelect: (i) => {
827
1122
  setTab(i);
828
1123
  resetView();
@@ -830,7 +1125,14 @@ function App({ interval: cliInterval }) {
830
1125
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: " Tab/\u2190\u2192" })
831
1126
  ] }),
832
1127
  /* @__PURE__ */ jsx(Box, { height: 1 }),
833
- tab === 0 && /* @__PURE__ */ jsx(DashboardView, { data: dashboard, billing }),
1128
+ tab === 0 && /* @__PURE__ */ jsx(
1129
+ DashboardView,
1130
+ {
1131
+ slots: visibleSlots,
1132
+ stats,
1133
+ compact: visibleSlots.length > 1
1134
+ }
1135
+ ),
834
1136
  tab === 1 && /* @__PURE__ */ jsxs(Fragment, { children: [
835
1137
  /* @__PURE__ */ jsx(ViewBar, { views: VIEWS, active: view, sort: SORTS[sort], onSelect: (i) => {
836
1138
  setView(i);
@@ -843,7 +1145,7 @@ function App({ interval: cliInterval }) {
843
1145
  rows: tableData,
844
1146
  cursor,
845
1147
  expanded,
846
- maxRows: rows - 12,
1148
+ maxRows: rows - 14,
847
1149
  cols,
848
1150
  onRowClick: (idx) => {
849
1151
  if (idx === cursor) setExpanded((e) => e === idx ? -1 : idx);
@@ -853,16 +1155,18 @@ function App({ interval: cliInterval }) {
853
1155
  )
854
1156
  ] })
855
1157
  ] }),
856
- (tab === 0 || showSettings) && /* @__PURE__ */ jsx(Footer, {})
1158
+ (tab === 0 || showSettings) && /* @__PURE__ */ jsx(Footer, { hasAccounts: slots.length > 1 })
857
1159
  ] });
858
1160
  }
859
- function Footer() {
1161
+ function Footer({ hasAccounts }) {
860
1162
  return /* @__PURE__ */ jsxs(Box, { marginTop: 1, children: [
861
1163
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "by " }),
862
1164
  /* @__PURE__ */ jsx(Text, { children: "David Ilie" }),
863
1165
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: " (" }),
864
1166
  /* @__PURE__ */ jsx(Text, { color: "cyan", children: "davidilie.com" }),
865
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: ") \xB7 s=settings q=quit" })
1167
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: ") \xB7 s=settings " }),
1168
+ hasAccounts && /* @__PURE__ */ jsx(Text, { dimColor: true, children: "a/A=cycle account " }),
1169
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "q=quit" })
866
1170
  ] });
867
1171
  }
868
1172
  function TabBar({ tabs, active, onSelect }) {
@@ -876,6 +1180,30 @@ function TabBar({ tabs, active, onSelect }) {
876
1180
  " "
877
1181
  ] }) }, t)) });
878
1182
  }
1183
+ function AccountStrip({ slots, activeIdx, onSelect }) {
1184
+ return /* @__PURE__ */ jsxs(Box, { children: [
1185
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "account " }),
1186
+ slots.map((s, i) => {
1187
+ const active = i === activeIdx;
1188
+ const dot = s.id === null ? "\u2726" : "\u25CF";
1189
+ return /* @__PURE__ */ jsx(ClickableBox, { onClick: () => onSelect(i), marginRight: 2, children: active ? /* @__PURE__ */ jsxs(Text, { bold: true, color: s.color, children: [
1190
+ /* @__PURE__ */ jsxs(Text, { children: [
1191
+ dot,
1192
+ " "
1193
+ ] }),
1194
+ /* @__PURE__ */ jsxs(Text, { inverse: true, children: [
1195
+ " ",
1196
+ s.name,
1197
+ " "
1198
+ ] })
1199
+ ] }) : /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
1200
+ dot,
1201
+ " ",
1202
+ s.name
1203
+ ] }) }, s.id ?? "__all__");
1204
+ })
1205
+ ] });
1206
+ }
879
1207
  function ViewBar({ views, active, sort, onSelect }) {
880
1208
  return /* @__PURE__ */ jsxs(Box, { children: [
881
1209
  views.map((v, i) => /* @__PURE__ */ jsx(ClickableBox, { onClick: () => onSelect(i), marginRight: 2, children: i === active ? /* @__PURE__ */ jsxs(Text, { bold: true, color: "cyan", children: [
@@ -904,13 +1232,40 @@ function sortRows(rows, sortIdx) {
904
1232
  return sorted;
905
1233
  }
906
1234
  }
907
- function SettingsView({ config: config2, cursor, tzEdit, tzError, resolvedTz }) {
908
- const editing = tzEdit !== null;
1235
+ var COLOR_PALETTE = [
1236
+ "cyan",
1237
+ "magenta",
1238
+ "green",
1239
+ "yellow",
1240
+ "blue",
1241
+ "red",
1242
+ "cyanBright",
1243
+ "magentaBright",
1244
+ "greenBright"
1245
+ ];
1246
+ function SettingsView({
1247
+ config: config2,
1248
+ cursor,
1249
+ tzEdit,
1250
+ tzError,
1251
+ resolvedTz,
1252
+ accountForm,
1253
+ onAddAccount,
1254
+ onEditAccount,
1255
+ onActivateAccount,
1256
+ activeAccountId
1257
+ }) {
1258
+ const editingTz = tzEdit !== null;
909
1259
  const tzDisplay = config2.timezone === null ? `System (${resolvedTz})` : config2.timezone;
1260
+ const accountRowsStart = GENERAL_ROWS;
1261
+ if (accountForm) {
1262
+ return /* @__PURE__ */ jsx(AccountFormView, { form: accountForm, accounts: config2.accounts });
1263
+ }
910
1264
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
911
1265
  /* @__PURE__ */ jsx(Text, { bold: true, children: "Settings" }),
912
1266
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: configLocation() }),
913
1267
  /* @__PURE__ */ jsx(Box, { height: 1 }),
1268
+ /* @__PURE__ */ jsx(Text, { bold: true, dimColor: true, children: "General" }),
914
1269
  /* @__PURE__ */ jsxs(Box, { children: [
915
1270
  /* @__PURE__ */ jsxs(Text, { color: cursor === 0 ? "green" : void 0, children: [
916
1271
  cursor === 0 ? "\u25B8" : " ",
@@ -963,7 +1318,7 @@ function SettingsView({ config: config2, cursor, tzEdit, tzError, resolvedTz })
963
1318
  " "
964
1319
  ] }),
965
1320
  /* @__PURE__ */ jsx(Text, { children: "Timezone " }),
966
- editing ? /* @__PURE__ */ jsxs(Fragment, { children: [
1321
+ editingTz ? /* @__PURE__ */ jsxs(Fragment, { children: [
967
1322
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "[" }),
968
1323
  /* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: tzEdit }),
969
1324
  /* @__PURE__ */ jsx(Text, { color: "cyan", children: "_" }),
@@ -975,72 +1330,323 @@ function SettingsView({ config: config2, cursor, tzEdit, tzError, resolvedTz })
975
1330
  tzError
976
1331
  ] }),
977
1332
  /* @__PURE__ */ jsx(Box, { height: 1 }),
978
- editing ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "type IANA name (e.g. Europe/London) \xB7 empty = System \xB7 Enter save \xB7 Esc cancel" }) : /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191\u2193 select \u2190\u2192 adjust Enter edit tz s/Esc close" })
1333
+ /* @__PURE__ */ jsx(Text, { bold: true, dimColor: true, children: "Claude accounts" }),
1334
+ config2.accounts.length === 0 && /* @__PURE__ */ jsx(Text, { dimColor: true, children: " none \u2014 using default Claude HOME" }),
1335
+ config2.accounts.map((acc, i) => {
1336
+ const idx = accountRowsStart + i;
1337
+ const selected = cursor === idx;
1338
+ const isActive = acc.id === activeAccountId;
1339
+ return /* @__PURE__ */ jsxs(Box, { children: [
1340
+ /* @__PURE__ */ jsxs(Text, { color: selected ? "green" : void 0, children: [
1341
+ selected ? "\u25B8" : " ",
1342
+ " "
1343
+ ] }),
1344
+ /* @__PURE__ */ jsxs(Text, { color: acc.color || "cyan", children: [
1345
+ isActive ? "\u25CF" : "\u25CB",
1346
+ " "
1347
+ ] }),
1348
+ /* @__PURE__ */ jsx(Box, { width: 16, children: /* @__PURE__ */ jsx(Text, { bold: true, children: acc.name }) }),
1349
+ /* @__PURE__ */ jsx(Box, { width: 14, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: acc.id }) }),
1350
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: acc.homeDir })
1351
+ ] }, acc.id);
1352
+ }),
1353
+ /* @__PURE__ */ jsxs(Box, { children: [
1354
+ /* @__PURE__ */ jsxs(Text, { color: cursor === accountRowsStart + config2.accounts.length ? "green" : void 0, children: [
1355
+ cursor === accountRowsStart + config2.accounts.length ? "\u25B8" : " ",
1356
+ " "
1357
+ ] }),
1358
+ /* @__PURE__ */ jsx(Text, { color: "greenBright", children: "+ " }),
1359
+ /* @__PURE__ */ jsx(Text, { children: "Add account" })
1360
+ ] }),
1361
+ /* @__PURE__ */ jsx(Box, { height: 1 }),
1362
+ editingTz ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "type IANA name (e.g. Europe/London) \xB7 empty = System \xB7 Enter save \xB7 Esc cancel" }) : cursor >= accountRowsStart && cursor < accountRowsStart + config2.accounts.length ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191\u2193 select \xB7 Enter edit \xB7 space activate \xB7 d delete \xB7 s/Esc close" }) : cursor === accountRowsStart + config2.accounts.length ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191\u2193 select \xB7 Enter add account \xB7 s/Esc close" }) : /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191\u2193 select \u2190\u2192 adjust Enter edit s/Esc close" })
979
1363
  ] });
980
1364
  }
981
- function DashboardView({ data, billing }) {
982
- return /* @__PURE__ */ jsxs(Fragment, { children: [
1365
+ function AccountFormView({ form, accounts }) {
1366
+ const previewId = form.mode === "add" ? generateAccountId(form.name || "account", accounts) : form.editingId ?? "";
1367
+ const accent = form.color;
1368
+ const stepIndex = { name: 1, homeDir: 2, color: 3 };
1369
+ const step = stepIndex[form.field];
1370
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
1371
+ /* @__PURE__ */ jsxs(Box, { children: [
1372
+ /* @__PURE__ */ jsx(Text, { color: accent, bold: true, children: "\u258D" }),
1373
+ /* @__PURE__ */ jsxs(Text, { bold: true, children: [
1374
+ " ",
1375
+ form.mode === "add" ? "NEW ACCOUNT" : "EDIT ACCOUNT"
1376
+ ] }),
1377
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
1378
+ " step ",
1379
+ step,
1380
+ " of 3"
1381
+ ] })
1382
+ ] }),
1383
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Stepper, { active: form.field, accent }) }),
983
1384
  /* @__PURE__ */ jsxs(
984
1385
  Box,
985
1386
  {
1387
+ marginTop: 1,
986
1388
  flexDirection: "column",
987
- paddingLeft: 1,
988
- borderStyle: "bold",
989
- borderColor: "green",
990
- borderRight: false,
991
- borderTop: false,
992
- borderBottom: false,
1389
+ borderStyle: "round",
1390
+ borderColor: accent,
1391
+ paddingX: 2,
1392
+ paddingY: 1,
993
1393
  children: [
994
- /* @__PURE__ */ jsx(Text, { bold: true, children: "Claude" }),
1394
+ /* @__PURE__ */ jsx(
1395
+ FormField,
1396
+ {
1397
+ label: "Name",
1398
+ hint: "display name for this Claude account",
1399
+ value: form.name,
1400
+ focused: form.field === "name",
1401
+ accent,
1402
+ placeholder: "e.g. Work, Personal"
1403
+ }
1404
+ ),
995
1405
  /* @__PURE__ */ jsx(Box, { height: 1 }),
996
- /* @__PURE__ */ jsx(SummaryRow, { label: "Today", summary: data.today }),
997
- /* @__PURE__ */ jsx(SummaryRow, { label: "This Week", summary: data.week }),
998
- /* @__PURE__ */ jsx(SummaryRow, { label: "This Month", summary: data.month }),
999
- data.burnRate > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
1000
- /* @__PURE__ */ jsx(Box, { height: 1 }),
1001
- /* @__PURE__ */ jsxs(Box, { children: [
1002
- /* @__PURE__ */ jsx(Box, { width: 14, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Burn rate" }) }),
1003
- /* @__PURE__ */ jsx(Box, { width: 12, justifyContent: "flex-end", children: /* @__PURE__ */ jsx(Text, { color: "red", children: currency(data.burnRate) }) }),
1004
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "/hr" })
1005
- ] })
1406
+ /* @__PURE__ */ jsx(
1407
+ FormField,
1408
+ {
1409
+ label: "Home directory",
1410
+ hint: "path containing .claude/ \xB7 ~ for default",
1411
+ value: form.homeDir,
1412
+ focused: form.field === "homeDir",
1413
+ accent,
1414
+ placeholder: "~/claude-work",
1415
+ mono: true
1416
+ }
1417
+ ),
1418
+ /* @__PURE__ */ jsx(Box, { height: 1 }),
1419
+ /* @__PURE__ */ jsx(
1420
+ ColorField,
1421
+ {
1422
+ value: form.color,
1423
+ focused: form.field === "color"
1424
+ }
1425
+ ),
1426
+ /* @__PURE__ */ jsx(Box, { height: 1 }),
1427
+ /* @__PURE__ */ jsxs(Box, { children: [
1428
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "id " }),
1429
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2524 " }),
1430
+ /* @__PURE__ */ jsx(Text, { bold: true, color: accent, children: previewId || "account" }),
1431
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: " \u251C" }),
1432
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: " auto-generated from name" })
1006
1433
  ] })
1007
1434
  ]
1008
1435
  }
1009
1436
  ),
1010
- /* @__PURE__ */ jsx(Box, { height: 1 }),
1011
- /* @__PURE__ */ jsxs(
1012
- Box,
1013
- {
1014
- flexDirection: "column",
1015
- paddingLeft: 1,
1016
- borderStyle: "bold",
1017
- borderColor: billing?.error ? "red" : "yellow",
1018
- borderRight: false,
1019
- borderTop: false,
1020
- borderBottom: false,
1021
- children: [
1022
- /* @__PURE__ */ jsx(Text, { bold: true, children: "Rate Limits" }),
1023
- /* @__PURE__ */ jsx(Box, { height: 1 }),
1024
- billing?.error ? /* @__PURE__ */ jsx(Text, { color: "red", children: billing.error }) : billing?.session || billing?.weekly ? /* @__PURE__ */ jsxs(Fragment, { children: [
1025
- billing.session && /* @__PURE__ */ jsx(LimitBar, { label: "Session", pct: billing.session.utilization, resets: billing.session.resetsAt }),
1026
- billing.weekly && /* @__PURE__ */ jsx(LimitBar, { label: "Weekly", pct: billing.weekly.utilization, resets: billing.weekly.resetsAt }),
1027
- billing.sonnet && /* @__PURE__ */ jsx(LimitBar, { label: "Sonnet", pct: billing.sonnet.utilization, resets: billing.sonnet.resetsAt }),
1028
- billing.extraUsage && /* @__PURE__ */ jsxs(Box, { children: [
1029
- /* @__PURE__ */ jsx(Box, { width: 10, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Extra" }) }),
1030
- /* @__PURE__ */ jsxs(Text, { color: "yellow", children: [
1031
- "$",
1032
- billing.extraUsage.used.toFixed(2)
1033
- ] }),
1034
- /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
1035
- " / $",
1036
- billing.extraUsage.limit.toFixed(2),
1037
- " limit"
1437
+ form.error && /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsxs(Text, { color: "red", children: [
1438
+ "\u26A0 ",
1439
+ form.error
1440
+ ] }) }),
1441
+ /* @__PURE__ */ jsxs(Box, { marginTop: 1, children: [
1442
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "tab/\u2191\u2193 " }),
1443
+ /* @__PURE__ */ jsx(Text, { children: "switch field" }),
1444
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: " \xB7 " }),
1445
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "enter " }),
1446
+ /* @__PURE__ */ jsx(Text, { children: form.field === "color" ? "save" : "next" }),
1447
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: " \xB7 " }),
1448
+ form.field === "color" && /* @__PURE__ */ jsxs(Fragment, { children: [
1449
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2190\u2192 " }),
1450
+ /* @__PURE__ */ jsx(Text, { children: "pick color" }),
1451
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: " \xB7 " })
1452
+ ] }),
1453
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "esc " }),
1454
+ /* @__PURE__ */ jsx(Text, { children: "cancel" })
1455
+ ] })
1456
+ ] });
1457
+ }
1458
+ function Stepper({ active, accent }) {
1459
+ const steps = [
1460
+ { id: "name", label: "Name" },
1461
+ { id: "homeDir", label: "Home" },
1462
+ { id: "color", label: "Color" }
1463
+ ];
1464
+ const order = steps.map((s) => s.id);
1465
+ const activeIdx = order.indexOf(active);
1466
+ return /* @__PURE__ */ jsx(Box, { children: steps.map((s, i) => {
1467
+ const done = i < activeIdx;
1468
+ const cur = i === activeIdx;
1469
+ const dot = done ? "\u25CF" : cur ? "\u25C9" : "\u25CB";
1470
+ return /* @__PURE__ */ jsxs(Box, { children: [
1471
+ /* @__PURE__ */ jsxs(Text, { color: cur ? accent : done ? accent : void 0, dimColor: !cur && !done, children: [
1472
+ dot,
1473
+ " "
1474
+ ] }),
1475
+ /* @__PURE__ */ jsx(Text, { bold: cur, color: cur ? accent : void 0, dimColor: !cur, children: s.label }),
1476
+ i < steps.length - 1 && /* @__PURE__ */ jsx(Text, { dimColor: true, children: " \u2500 " })
1477
+ ] }, s.id);
1478
+ }) });
1479
+ }
1480
+ function FormField({
1481
+ label,
1482
+ hint,
1483
+ value,
1484
+ focused,
1485
+ accent,
1486
+ placeholder,
1487
+ mono
1488
+ }) {
1489
+ const display = value === "" ? placeholder : value;
1490
+ const isPlaceholder = value === "";
1491
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
1492
+ /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Text, { color: focused ? accent : void 0, bold: focused, dimColor: !focused, children: [
1493
+ focused ? "\u25B8" : " ",
1494
+ " ",
1495
+ label
1496
+ ] }) }),
1497
+ /* @__PURE__ */ jsxs(Box, { marginTop: 0, children: [
1498
+ /* @__PURE__ */ jsxs(Text, { color: focused ? accent : void 0, children: [
1499
+ " ",
1500
+ focused ? "\u258C" : " ",
1501
+ " "
1502
+ ] }),
1503
+ /* @__PURE__ */ jsx(
1504
+ Text,
1505
+ {
1506
+ bold: focused && !isPlaceholder,
1507
+ color: focused && !isPlaceholder ? accent : void 0,
1508
+ dimColor: isPlaceholder,
1509
+ italic: mono && isPlaceholder,
1510
+ children: display
1511
+ }
1512
+ ),
1513
+ focused && /* @__PURE__ */ jsx(Text, { color: accent, children: "\u258F" })
1514
+ ] }),
1515
+ /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
1516
+ " ",
1517
+ hint
1518
+ ] }) })
1519
+ ] });
1520
+ }
1521
+ function ColorField({ value, focused }) {
1522
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
1523
+ /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Text, { color: focused ? value : void 0, bold: focused, dimColor: !focused, children: [
1524
+ focused ? "\u25B8" : " ",
1525
+ " Accent color"
1526
+ ] }) }),
1527
+ /* @__PURE__ */ jsxs(Box, { marginTop: 0, children: [
1528
+ /* @__PURE__ */ jsxs(Text, { children: [
1529
+ " ",
1530
+ focused ? "\u258C" : " ",
1531
+ " "
1532
+ ] }),
1533
+ COLOR_PALETTE.map((c, i) => {
1534
+ const selected = c === value;
1535
+ return /* @__PURE__ */ jsx(Box, { marginRight: 1, children: selected ? /* @__PURE__ */ jsx(Text, { bold: true, color: c, children: "[\u25CF]" }) : /* @__PURE__ */ jsx(Text, { color: c, dimColor: !focused, children: i === COLOR_PALETTE.length - 1 ? " \u25CF" : " \u25CF" }) }, c);
1536
+ })
1537
+ ] }),
1538
+ /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: " shows on dashboard, account strip, borders" }) })
1539
+ ] });
1540
+ }
1541
+ function DashboardView({ slots, stats, compact }) {
1542
+ const slotKey = (s) => s.id ?? "__default__";
1543
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: slots.map((slot, i) => {
1544
+ const s = stats.get(slotKey(slot));
1545
+ if (!s?.dashboard) {
1546
+ return /* @__PURE__ */ jsxs(Box, { children: [
1547
+ /* @__PURE__ */ jsxs(Text, { color: slot.color, bold: true, children: [
1548
+ "\u25CF ",
1549
+ slot.name,
1550
+ " "
1551
+ ] }),
1552
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "loading..." })
1553
+ ] }, slotKey(slot));
1554
+ }
1555
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
1556
+ /* @__PURE__ */ jsx(
1557
+ AccountCard,
1558
+ {
1559
+ slot,
1560
+ dashboard: s.dashboard,
1561
+ billing: s.billing,
1562
+ compact
1563
+ }
1564
+ ),
1565
+ i < slots.length - 1 && /* @__PURE__ */ jsx(Box, { height: 1 })
1566
+ ] }, slotKey(slot));
1567
+ }) });
1568
+ }
1569
+ function AccountCard({ slot, dashboard, billing, compact }) {
1570
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
1571
+ /* @__PURE__ */ jsxs(Box, { children: [
1572
+ /* @__PURE__ */ jsxs(Text, { color: slot.color, bold: true, children: [
1573
+ "\u25CF ",
1574
+ slot.name
1575
+ ] }),
1576
+ slot.id && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
1577
+ " ",
1578
+ slot.id
1579
+ ] })
1580
+ ] }),
1581
+ /* @__PURE__ */ jsxs(Box, { flexDirection: compact ? "row" : "column", marginTop: compact ? 0 : 0, children: [
1582
+ /* @__PURE__ */ jsxs(
1583
+ Box,
1584
+ {
1585
+ flexDirection: "column",
1586
+ paddingLeft: 1,
1587
+ marginRight: compact ? 2 : 0,
1588
+ borderStyle: "bold",
1589
+ borderColor: slot.color,
1590
+ borderRight: false,
1591
+ borderTop: false,
1592
+ borderBottom: false,
1593
+ children: [
1594
+ /* @__PURE__ */ jsx(Text, { bold: true, children: "Usage" }),
1595
+ !compact && /* @__PURE__ */ jsx(Box, { height: 1 }),
1596
+ /* @__PURE__ */ jsx(SummaryRow, { label: "Today", summary: dashboard.today, compact }),
1597
+ /* @__PURE__ */ jsx(SummaryRow, { label: compact ? "Week" : "This Week", summary: dashboard.week, compact }),
1598
+ /* @__PURE__ */ jsx(SummaryRow, { label: compact ? "Month" : "This Month", summary: dashboard.month, compact }),
1599
+ dashboard.burnRate > 0 && !compact && /* @__PURE__ */ jsxs(Fragment, { children: [
1600
+ /* @__PURE__ */ jsx(Box, { height: 1 }),
1601
+ /* @__PURE__ */ jsxs(Box, { children: [
1602
+ /* @__PURE__ */ jsx(Box, { width: 14, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Burn rate" }) }),
1603
+ /* @__PURE__ */ jsx(Box, { width: 12, justifyContent: "flex-end", children: /* @__PURE__ */ jsx(Text, { color: "red", children: currency(dashboard.burnRate) }) }),
1604
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "/hr" })
1038
1605
  ] })
1606
+ ] }),
1607
+ dashboard.burnRate > 0 && compact && /* @__PURE__ */ jsxs(Box, { children: [
1608
+ /* @__PURE__ */ jsx(Box, { width: 10, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Burn" }) }),
1609
+ /* @__PURE__ */ jsx(Text, { color: "red", children: currency(dashboard.burnRate) }),
1610
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "/hr" })
1039
1611
  ] })
1040
- ] }) : /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Fetching..." })
1041
- ]
1042
- }
1043
- )
1612
+ ]
1613
+ }
1614
+ ),
1615
+ !compact && /* @__PURE__ */ jsx(Box, { height: 1 }),
1616
+ /* @__PURE__ */ jsxs(
1617
+ Box,
1618
+ {
1619
+ flexDirection: "column",
1620
+ paddingLeft: 1,
1621
+ borderStyle: "bold",
1622
+ borderColor: billing?.error ? "red" : "yellow",
1623
+ borderRight: false,
1624
+ borderTop: false,
1625
+ borderBottom: false,
1626
+ children: [
1627
+ /* @__PURE__ */ jsx(Text, { bold: true, children: "Rate Limits" }),
1628
+ !compact && /* @__PURE__ */ jsx(Box, { height: 1 }),
1629
+ billing?.error ? /* @__PURE__ */ jsx(Text, { color: "red", children: billing.error }) : billing?.session || billing?.weekly ? /* @__PURE__ */ jsxs(Fragment, { children: [
1630
+ billing.session && /* @__PURE__ */ jsx(LimitBar, { label: "Session", pct: billing.session.utilization, resets: billing.session.resetsAt, compact }),
1631
+ billing.weekly && /* @__PURE__ */ jsx(LimitBar, { label: "Weekly", pct: billing.weekly.utilization, resets: billing.weekly.resetsAt, compact }),
1632
+ billing.sonnet && /* @__PURE__ */ jsx(LimitBar, { label: "Sonnet", pct: billing.sonnet.utilization, resets: billing.sonnet.resetsAt, compact }),
1633
+ billing.extraUsage && /* @__PURE__ */ jsxs(Box, { children: [
1634
+ /* @__PURE__ */ jsx(Box, { width: 10, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Extra" }) }),
1635
+ /* @__PURE__ */ jsxs(Text, { color: "yellow", children: [
1636
+ "$",
1637
+ billing.extraUsage.used.toFixed(2)
1638
+ ] }),
1639
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
1640
+ " / $",
1641
+ billing.extraUsage.limit.toFixed(2),
1642
+ " limit"
1643
+ ] })
1644
+ ] })
1645
+ ] }) : /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Fetching..." })
1646
+ ]
1647
+ }
1648
+ )
1649
+ ] })
1044
1650
  ] });
1045
1651
  }
1046
1652
  function PeakBadge({ peak }) {
@@ -1061,8 +1667,8 @@ function fmtMinutes(mins) {
1061
1667
  const m = mins % 60;
1062
1668
  return m === 0 ? `${h}h` : `${h}h ${m}m`;
1063
1669
  }
1064
- function LimitBar({ label, pct, resets }) {
1065
- const width = 30;
1670
+ function LimitBar({ label, pct, resets, compact }) {
1671
+ const width = compact ? 16 : 30;
1066
1672
  const filled = Math.round(pct / 100 * width);
1067
1673
  const color = pct >= 80 ? "red" : pct >= 50 ? "yellow" : "green";
1068
1674
  return /* @__PURE__ */ jsxs(Box, { children: [
@@ -1074,13 +1680,23 @@ function LimitBar({ label, pct, resets }) {
1074
1680
  Math.round(pct),
1075
1681
  "%"
1076
1682
  ] }),
1077
- /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
1683
+ !compact && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
1078
1684
  " resets ",
1079
1685
  resets
1080
1686
  ] })
1081
1687
  ] });
1082
1688
  }
1083
- function SummaryRow({ label, summary }) {
1689
+ function SummaryRow({ label, summary, compact }) {
1690
+ if (compact) {
1691
+ return /* @__PURE__ */ jsxs(Box, { children: [
1692
+ /* @__PURE__ */ jsx(Box, { width: 10, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: label }) }),
1693
+ /* @__PURE__ */ jsx(Box, { width: 10, justifyContent: "flex-end", children: /* @__PURE__ */ jsx(Text, { bold: true, color: "yellow", children: currency(summary.cost) }) }),
1694
+ /* @__PURE__ */ jsx(Box, { width: 14, justifyContent: "flex-end", children: /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
1695
+ tokens(summary.tokens),
1696
+ " tk"
1697
+ ] }) })
1698
+ ] });
1699
+ }
1084
1700
  return /* @__PURE__ */ jsxs(Box, { children: [
1085
1701
  /* @__PURE__ */ jsx(Box, { width: 14, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: label }) }),
1086
1702
  /* @__PURE__ */ jsx(Box, { width: 12, justifyContent: "flex-end", children: /* @__PURE__ */ jsx(Text, { bold: true, color: "yellow", children: currency(summary.cost) }) }),
@@ -1167,7 +1783,7 @@ function TableView({ rows: allRows, cursor, expanded, maxRows, cols, onRowClick
1167
1783
  allRows.length
1168
1784
  ] }),
1169
1785
  /* @__PURE__ */ jsx(Box, { height: 1 }),
1170
- /* @__PURE__ */ jsx(Footer, {})
1786
+ /* @__PURE__ */ jsx(Footer, { hasAccounts: false })
1171
1787
  ] });
1172
1788
  }
1173
1789
  function RowDetail({ row, indent }) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tokmon",
3
- "version": "0.11.3",
3
+ "version": "0.12.0",
4
4
  "description": "Terminal dashboard for Claude Code usage and costs",
5
5
  "type": "module",
6
6
  "bin": {